Repository navigation
test(service-storage): resolve @objectstack/core from source so a stale dist can't decide a pin (#7668) - #7778
Conversation
…tale dist can't decide a pin (#7668) `packages/services/service-storage` had no `vitest.config.ts`, so its unit suite resolved `@objectstack/core` through the workspace link to `packages/core/dist/index.js` — a build artifact. The verdict of every unit pin in the package was a function of build state, not of the source in the checkout. All 17 cases of `attachment-access-hooks.test.ts` — the only executable guard on the #4757 predicate-less unscoped-multi-delete refusal, which cannot be expressed over REST — errored with `TypeError: withoutOperationPrivateKeys is not a function` against a tree whose prebuilt core predated that export, while `packages/core/src/security/operation-private-keys.ts` was correct throughout. The loud error is the mild half: a core dist merely BEHIND rather than missing the symbol lets a pin run green against core's old behaviour, with nothing in the output saying so. This is not a task-ordering bug. `turbo.json` already declares `test` dependsOn `^build` and `turbo run test --filter=@objectstack/service-storage` passes 352/352; it needed no change. The paths that broke are the ones turbo does not mediate — `pnpm test` in the package, `vitest run <file>`, an editor runner, a QA tree built at an older commit — which is where a pin is re-run while someone is changing core. Ordering cannot fix that; taking the artifact out of the resolution path can. Verified by simulating the exact #7668 condition (core's built `index.js` stripped of the export): without the config 17/30 cases fail with the issue's verbatim TypeError; with it, 30/30 pass. Full suite 352/352 green both via `turbo run test` and via a bare `vitest run` in the package. Fixes #7668 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01UqnHVpBA1ij5Jb87JaMXyM
|
The latest updates on your projects. Learn more about Vercel for GitHub. 1 Skipped Deployment
|
📓 Docs Drift CheckThis PR changes 1 package(s): 3 hand-written doc(s) reference the affected code and may need an implementation-accuracy re-verification:
⛔ 1 release-owned page(s) also reference the affected code. These are read-only:
|
⛔ merge queue 构建失败 — 先分诊,再决定要不要重排队列构建 31510769498 红了。队列跑的是全量套件(PR 侧 CI 只跑 affected 子集), 失败的 job(日志抽取,best effort):
历史信号:
分诊清单:
Generated by Claude Code · merge-queue-triage workflow (#4859) |
⛔ merge queue 构建失败 — 先分诊,再决定要不要重排队列构建 31512979457 红了。队列跑的是全量套件(PR 侧 CI 只跑 affected 子集), 失败的 job(日志抽取,best effort):
历史信号:
分诊清单:
Generated by Claude Code · merge-queue-triage workflow (#4859) |
⛔ merge queue 构建失败 — 先分诊,再决定要不要重排队列构建 31513732396 红了。队列跑的是全量套件(PR 侧 CI 只跑 affected 子集), 失败的 job(日志抽取,best effort):
历史信号:
分诊清单:
Generated by Claude Code · merge-queue-triage workflow (#4859) |
⛔ merge queue 构建失败 — 先分诊,再决定要不要重排队列构建 31514484197 红了。队列跑的是全量套件(PR 侧 CI 只跑 affected 子集), 失败的 job(日志抽取,best effort):
历史信号:
分诊清单:
Generated by Claude Code · merge-queue-triage workflow (#4859) |
⛔ merge queue 构建失败 — 先分诊,再决定要不要重排队列构建 31515154824 红了。队列跑的是全量套件(PR 侧 CI 只跑 affected 子集), 失败的 job(日志抽取,best effort):
历史信号:
分诊清单:
Generated by Claude Code · merge-queue-triage workflow (#4859) |
⛔ merge queue 构建失败 — 先分诊,再决定要不要重排队列构建 31516019973 红了。队列跑的是全量套件(PR 侧 CI 只跑 affected 子集), 失败的 job(日志抽取,best effort):
历史信号:
分诊清单:
Generated by Claude Code · merge-queue-triage workflow (#4859) |
action-execution-destructive.test.ts reads the REAL sys_* identity declarations to prove today's platform objects are excluded before actionLooksDestructive ever runs on them. That import resolved through `exports` to platform-objects/dist -- a build artifact -- so all 66 pins were a verdict about build state rather than about the declarations in the checkout. `pnpm check:test-source-alias` (#7668/#7778) reported it as a NEW unaliased artifact import on @objectstack/runtime. Aliases platform-objects to source in packages/runtime/vitest.config.ts. resolve.alias becomes the ARRAY form because only that form accepts a RegExp find; the pre-existing string entries keep the prefix-match semantics they had as object keys (Vite normalizes an alias object into exactly this list, in this order), so no other resolution changes. The new entries are ANCHORED, one rule for every namespace rather than an enumeration of the ones reached today -- the PR #7778 constraint, same shape as @objectstack/spec in packages/qa/downstream-contract (PR #8129). `/plugin` is listed ahead of the namespace rule because it is the one exported subpath that is a FILE (src/plugin.ts) and not a directory. The registry entry in scripts/check-test-source-alias.mjs is untouched. Measured, both directions: - artifact-resolved (before): 66 passed - source-resolved (after): 66 passed - per-test diff of the two verbose runs: IDENTICAL, name for name. The 14-action pins read `type`/`ai.exposed` off the imported objects through actionByName(), which throws when an action is missing, so an identical name+verdict set means source and dist agree on every declaration these pins touch. No pin changed verdict; none modified. Reverse verification (the alias is live, not decorative): with sys_user.ban_user's `type` flipped 'api' -> 'script' in SOURCE only and no rebuild, the suite reports 1 failed / 65 passed -- `expected 'script' to be 'api'` at :309. dist/identity/index.mjs:81 still carries `type: "api"`, i.e. the identical tree read green through the pre-alias config. Injection reverted; no test was weakened. Part of #7828 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01B3Kurx8qufrDzNjk4rag7V
…fordance sweep (#7934) `check:test-source-alias` went red on this branch: adding `@objectstack/lint` as a devDependency made it a NEW unaliased artifact import for this package. Unaliased, `validateManagedApiMethods` resolved through `exports` to `lint/dist`, so the sweep was a verdict about build state rather than about the rule in the checkout — acutely wrong here, since the sweep's whole purpose is to run the CURRENT rule over the CURRENT objects, and a stale rule narrows the population silently while the sweep keeps reporting zero findings. This package had no `vitest.config.*` at all, so the fix is the package's first one. It carries the alias and nothing else: no `test` block, so suite discovery stays on the vitest defaults it ran on before (17 files / 351 tests, unchanged) and this file's only effect is the resolution. Array form with an anchored `/^@objectstack\/lint$/`, which is load-bearing rather than stylistic: `@objectstack/lint` exports a second subpath (`./runtime`), and the object form matches by PREFIX, so a bare key with a FILE replacement would swallow it and resolve to `…/src/index.ts/runtime` (ENOTDIR at run time). Same shape as `packages/rest` (#7955) and `service-storage` (#7778). The registry in `scripts/check-test-source-alias.mjs` is untouched — it is shrink-only, and its own message says widening it is not the fix. Verified: sweep census unchanged at 76 files / 51 in-scope / 9 packages, zero findings, now measured against lint's source. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012WMpuAfA2KSdDjGF6tm1bH
…dist (objectstack-ai#7966) Every publishable package resolves through `exports` to `dist/`, so a package whose tests import a workspace dependency with no vitest source alias is reporting on build state rather than on the source in the checkout. objectstack-ai#7668 is the loud version of that: 17 cases erroring on a symbol its prebuilt core predated. The version this gate is aimed at is silent — a dist merely BEHIND the source runs GREEN against old behaviour, with nothing in the output saying so. Demonstrated on this repo before writing the gate: with `@objectstack/spec` unaliased, `packages/qa/downstream-contract` — the frozen third-party fixture that exists to catch breaking spec changes — reported 14/14 green against a spec source that rejected its own fixture outright. Aliasing spec to source on the identical tree turned it red naming the injected field. Both edits were reverted. The gate walks each package's test-reachable imports (type-only imports excluded — they never resolve), keeps the deps whose own entry point is a build artifact, and resolves each specifier through the package's vitest aliases the way Vite does (in order, first match wins, string `find` by prefix). It also fails the prefix/ENOTDIR trap objectstack-ai#7778 documented, since it performs the real replacement and can see a path running through a file. `KNOWN_UNALIASED_TEST_IMPORTS` is the measured state: 63 of 72 packages with tests, 312 package-dependency pairs. It is shrink-only and audited in both directions, so an entry that is no longer needed fails and names itself for deletion. Each entry carries its exact dependency set rather than a bare package name, so a listed package cannot acquire new artifact imports with nothing going red. No package's vitest config is touched here; per-package remediation is filed separately. Claude-Session: https://claude.ai/code/session_01CD4dmUPWszMro2K4Mwzpwj Co-authored-by: Claude <noreply@anthropic.com>
… only (objectstack-ai#8128) * fix(runtime): actionLooksDestructive classifies on declared semantics only Drops the confirmText leg from actionLooksDestructive (packages/runtime/src/ action-execution.ts). mode === 'delete' || variant === 'danger' remain the signal -- closed, declared enumerations an author sets on purpose, not UI dialog copy a heuristic was never meant to read as an AI-facing safety property. confirmText is being withdrawn by design: objectstack-ai#7278/objectstack-ai#7309 moved identity-object confirm questions onto `description` instead, and measured on objectstack-ai#7309's merged branch (PR objectstack-ai#7827), 6 of its 14 migrated actions flipped from destructive to not-destructive the moment their confirmText was dropped, because none of them declares mode:'delete' or variant:'danger' to fall back on. Maintainer ruling: issue objectstack-ai#7828, comment 5265943521 (Option A adopted). Fixes objectstack-ai#7828 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01B3Kurx8qufrDzNjk4rag7V * test(runtime): resolve @objectstack/platform-objects to source in vitest action-execution-destructive.test.ts reads the REAL sys_* identity declarations to prove today's platform objects are excluded before actionLooksDestructive ever runs on them. That import resolved through `exports` to platform-objects/dist -- a build artifact -- so all 66 pins were a verdict about build state rather than about the declarations in the checkout. `pnpm check:test-source-alias` (objectstack-ai#7668/objectstack-ai#7778) reported it as a NEW unaliased artifact import on @objectstack/runtime. Aliases platform-objects to source in packages/runtime/vitest.config.ts. resolve.alias becomes the ARRAY form because only that form accepts a RegExp find; the pre-existing string entries keep the prefix-match semantics they had as object keys (Vite normalizes an alias object into exactly this list, in this order), so no other resolution changes. The new entries are ANCHORED, one rule for every namespace rather than an enumeration of the ones reached today -- the PR objectstack-ai#7778 constraint, same shape as @objectstack/spec in packages/qa/downstream-contract (PR objectstack-ai#8129). `/plugin` is listed ahead of the namespace rule because it is the one exported subpath that is a FILE (src/plugin.ts) and not a directory. The registry entry in scripts/check-test-source-alias.mjs is untouched. Measured, both directions: - artifact-resolved (before): 66 passed - source-resolved (after): 66 passed - per-test diff of the two verbose runs: IDENTICAL, name for name. The 14-action pins read `type`/`ai.exposed` off the imported objects through actionByName(), which throws when an action is missing, so an identical name+verdict set means source and dist agree on every declaration these pins touch. No pin changed verdict; none modified. Reverse verification (the alias is live, not decorative): with sys_user.ban_user's `type` flipped 'api' -> 'script' in SOURCE only and no rebuild, the suite reports 1 failed / 65 passed -- `expected 'script' to be 'api'` at :309. dist/identity/index.mjs:81 still carries `type: "api"`, i.e. the identical tree read green through the pre-alias config. Injection reverted; no test was weakened. Part of objectstack-ai#7828 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01B3Kurx8qufrDzNjk4rag7V --------- Co-authored-by: Claude <noreply@anthropic.com>
…ource, emptying its check-test-source-alias entry (objectstack-ai#8104) (objectstack-ai#8190) objectstack-ai#8063 aliased two workspace deps to source in this package's first `vitest.config.ts`. The other three — `@objectstack/spec`, `@objectstack/platform-objects`, `@objectstack/types` — stayed on their `KNOWN_UNALIASED_TEST_IMPORTS` entry and kept resolving through `exports` to `dist/`. They are now aliased to source and the registry entry is deleted. 17 of this package's 20 test files were really reading the artifact: with the three `dist/` trees removed and the pre-objectstack-ai#8104 config in place, 17 files fail to load and only 58 of 413 cases run. With the aliases, the same tree with no `spec`/`platform-objects`/`types` build output passes 413/413. The subpath entry points were the actual work. This package imports no bare `@objectstack/spec` and no bare `@objectstack/platform-objects` at run time — every reachable specifier for those two is a subpath (`spec/api`, `spec/contracts`, `spec/data`, `spec/system`, `platform-objects/system`, plus `spec/security` reached transitively through `types`). The object alias form would swallow them into `…/index.ts/<sub>` (`ENOTDIR`, objectstack-ai#7778); the anchored bare form objectstack-ai#8063 established does not swallow them but does not cover them either, so copying that shape would have left every one of them on `dist` with the registry entry undeletable. So `spec` takes the one-rule-for-all-namespaces capture form, its export map being uniform, while `platform-objects` takes an explicit `/system` entry because its map is not (`./plugin` is `src/plugin.ts`, a file, so a `([a-z-]+)` rule would invent `src/plugin/index.ts`). `plugin-audit` writes its `platform-objects/audit` entry the same way. The registry deletion is half the change: the entry is audited for set equality in both directions, so aliases-complete-with-entry-present and entry-deleted-with-aliases-incomplete each fail the gate. It reads OK at 61 registered packages, down from 62. 413 passed / 20 files, unchanged from baseline. No change under `src/`. Claude-Session: https://claude.ai/code/session_01SMN7p2W62YzA3bY7BaFv3r Co-authored-by: Claude <noreply@anthropic.com>
…e apiMethods affordance rule (objectstack-ai#7934) (objectstack-ai#8314) * test(platform-objects): sweep code-shipped managed objects through the apiMethods affordance rule (objectstack-ai#7934) `os lint` walks an authored stack, so this repo's own `.object.ts` definitions were never covered by `object/managed-api-method-unaffordable` (objectstack-ai#7521). Import the exported `validateManagedApiMethods` and run it over every code-shipped object in the monorepo — the predicate is reused, never re-derived. Measured: 76 object files, 51 in-scope managed objects, ZERO findings. The divergence class does not currently exist in this repo, so this lands as regression insurance rather than a fix. Repo-wide rather than scoped to the two packages the card names: those hold 30 of the 51 in-scope objects, and an audit scoped to a package name is the failure already recorded in objectstack-ai#3745 and objectstack-ai#7802. - declares the cross-package input radius (gate + turbo.json), without which turbo replays a stale green for a diff touching another package's objects - platform-objects tsconfig gains `types: ["node"]` so the test layer stays at its TEST_DEBT number (3) instead of drifting up Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012WMpuAfA2KSdDjGF6tm1bH * test(platform-objects): resolve @objectstack/lint to source in the affordance sweep (objectstack-ai#7934) `check:test-source-alias` went red on this branch: adding `@objectstack/lint` as a devDependency made it a NEW unaliased artifact import for this package. Unaliased, `validateManagedApiMethods` resolved through `exports` to `lint/dist`, so the sweep was a verdict about build state rather than about the rule in the checkout — acutely wrong here, since the sweep's whole purpose is to run the CURRENT rule over the CURRENT objects, and a stale rule narrows the population silently while the sweep keeps reporting zero findings. This package had no `vitest.config.*` at all, so the fix is the package's first one. It carries the alias and nothing else: no `test` block, so suite discovery stays on the vitest defaults it ran on before (17 files / 351 tests, unchanged) and this file's only effect is the resolution. Array form with an anchored `/^@objectstack\/lint$/`, which is load-bearing rather than stylistic: `@objectstack/lint` exports a second subpath (`./runtime`), and the object form matches by PREFIX, so a bare key with a FILE replacement would swallow it and resolve to `…/src/index.ts/runtime` (ENOTDIR at run time). Same shape as `packages/rest` (objectstack-ai#7955) and `service-storage` (objectstack-ai#7778). The registry in `scripts/check-test-source-alias.mjs` is untouched — it is shrink-only, and its own message says widening it is not the fix. Verified: sweep census unchanged at 76 files / 51 in-scope / 9 packages, zero findings, now measured against lint's source. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012WMpuAfA2KSdDjGF6tm1bH --------- Co-authored-by: Claude <noreply@anthropic.com>
…ges/cloud-connection/src to the commits that decided them (objectstack-ai#20735) Part of objectstack-ai#20594 Clause-②: no ## What changed This is stage 9 of the `domain:cli` lane of the dead-citation sweep: `packages/cloud-connection/src`. Every comment site there that cited a tracker number answering 404 now cites, in ruling C+D's form C (comment 5749154545 on objectstack-ai#19123), the commit in this repository's history that decided what the line describes, and keeps saying in its own words what that commit decided. PR objectstack-ai#20533 is the method, and stages 1 to 8 of this card (PR objectstack-ai#20624, PR objectstack-ai#20632, PR objectstack-ai#20656, PR objectstack-ai#20673, PR objectstack-ai#20689, PR objectstack-ai#20703, PR objectstack-ai#20713, PR objectstack-ai#20723) are the precedents. The card stays open for the lane's remaining packages, so this PR says `Part of`. That is **10 sites on 10 lines in 3 files, covering 3 numbers**, rewritten to **3 distinct commits**: - the census's **5 sites**, all in `src/marketplace-install-local-plugin.ts` (3 numbers); - **5 test-file comment sites** in 2 test files (the census defers `*.test.ts`; stages 1 to 8 took test comments too). Only comments changed: **10 lines out, 10 in**, and every touched file keeps its line count (1,969 / 377 / 308), so no line citation into these files moves. **No citation number is added**: over the 10 line pairs, added-minus-removed numbers is empty, and no PR number stands on an added line. No ADR or ruling-record file in `docs/adr/` or `scripts/adr-anchors/` records any of these 3 decisions (a grep for the 3 numbers there reads 0 hits, with a control number from the same tree, `objectstack-ai#7329`, reading 1), so every anchor is a commit. A **`patch` changeset** for `@objectstack/cloud-connection` rides along, because the rewritten docblocks reach `dist` (measured below). That is stage 6's case (PR objectstack-ai#20703), not stages 5 and 7's. ## Census: `packages/cloud-connection`, before and after **Instrument.** The gate's own `node scripts/check-issue-citations.mjs --census --json`, read-only and unchanged, run under `with-fleet.sh --read` for the token. The count is its `allocated-but-absent` findings under `packages/cloud-connection/`. Both runs enumerated the whole board. | reading | tree | board | whole-repo `allocated-but-absent` | `packages/cloud-connection` sites | lines | numbers | files | |---|---|---|---|---|---|---|---| | before | base `b291fcdae9`, run 2026-09-29T22:34:12Z to 22:38:18Z | enumerated, 186 pages, frontier objectstack-ai#20731, 18,558 numbers | 1,153 | **5** | 5 | 3 | 1 | | after | `4a1f38a4e6`, run 22:44:08Z to 22:47:58Z | enumerated, 186 pages, frontier objectstack-ai#20731, 18,558 numbers | 1,148 | **0** | 0 | 0 | 0 | The whole-repo drop of 5 is exactly these sites: a site-by-site diff of the two JSON outputs has 5 findings gone, all in `packages/cloud-connection/src/marketplace-install-local-plugin.ts`, and none added. The other three tallies (`resolves` 32,994, `resolves-as-pull-request` 1,984, `cross-repo-unjudged` 995) are equal in both runs. `packages/cloud-connection/src` is byte-identical at `4a1f38a4e6` and at the head. **Supplementary scan (test files included).** The gate's exported `extractCitations` and `classifyCitation` over all 44 `.ts` files under `src/`, with the board from the gate's own `probeBoard`: 301 citations and 14 dead before (src comments 5, test comments 5, src strings 1, test strings 3), 291 and 4 after (0, 0, 1, 3). Its before list of src comment sites is identical to the census's. The 4 left are strings, the form-D stage (see Acceptance notes). ## Per-site table `git blame` at the base ties each line to the commit that wrote it, and each anchor was read in its message, changeset or diff, not only its subject. | number | sites (base line) | anchor: what it decided | |---|---|---| | `objectstack-ai#9011` | `marketplace-install-local-plugin.ts:35`, `:1001`, `:1821`; `marketplace-install-local-capability-enumeration.test.ts:50`, `:303`; `marketplace-install-local-list-posture.test.ts:4`, `:302` | `01074e551`: the install-local listing requires an authenticated principal (anonymous gets 401) and serves `installedBy` / `storageDir` only to a `manage_metadata` holder, the maintainer's 2026-08-16 "Option 3" that `:1008` still names; it also extracts the one `refuseUnauthenticated` 401 envelope that `:1821` describes. All seven lines blame to it. The PR that landed it (PR objectstack-ai#9256, which answers 200) names objectstack-ai#9011 on its first line. `list-posture.test.ts:302` now reads "The wire shape before commit 01074e5" for "The pre-(number) wire shape". | | `objectstack-ai#8919` | `marketplace-install-local-plugin.ts:98`; `marketplace-install-local-capability-enumeration.test.ts:40` | `b5378550e`: gates the `/meta` publish and rollback promotion verbs on `manage_metadata` and adds `meta-write-door-capability-enumeration.test.ts`, the enumeration pin `:40` names as its precedent. Both lines blame to `e0695b582`, the commit that gated the four mutating install-local doors for objectstack-ai#8976 (which answers 200), whose message cites this gate as the precedent. Stages 2 and 5 gave the number this anchor. The PR that landed `b5378550e` answers 404 too. | | `objectstack-ai#13279` | `marketplace-install-local-plugin.ts:1813` | `6a180e42d`: a failed permission-store read raises `AuthzStoreUnavailableError` instead of resolving as an unauthenticated or capability-less principal, and each fail-closed transport `catch` re-raises it. The line blames to it; PR objectstack-ai#13475 names objectstack-ai#13279. Stages 1, 2 and 4 gave the number this anchor. | **Anchor checks.** Every cited sha matches exactly one object (`git rev-parse --disambiguate`, count 1 for each of the 3), is a commit, has one parent, and is an ancestor of `main` (`merge-base --is-ancestor` against `36d043be17`, exit 0 for all 3). The checkout is not shallow. The control leg `818fcafda` (2026-08-16, the parent of the oldest anchor `b5378550e` of 2026-08-16) exits 0, and the negative control, this branch's own `16a88d69b2`, exits 1. Two anchors reuse the landed stages' (`b5378550e`, `6a180e42d`), so each number carries one anchor across the tree; one is new (`01074e551`). **Numbers.** All 3 dropped numbers answer 404 by REST (probed 2026-09-29T22:40:35Z). The numbers kept near the changed lines (`objectstack-ai#8976`, `objectstack-ai#15353`) answer 200. Three slash-joined groups stand in `packages/cloud-connection/src`, whose later halves the citation grammar does not read (`objectstack-ai#6603/objectstack-ai#7020`, `objectstack-ai#4127/objectstack-ai#4251` twice); every half answers 200, so none is dead. ## Mechanical guard: no code token moves **H2 holds on the comment-stripped reading; the emitted `dist` is NOT byte-identical, because the docblocks ship.** **Token guard.** It compares the TypeScript parser's leaf tokens (TypeScript 6.0.3, JSDoc nodes excluded) of the 3 touched files at base `b291fcdae9` and at `4a1f38a4e6`. Controls mutate the head text in memory only. - Real run: 12,349 base tokens (8,351 / 2,246 / 1,752), 0 differing (exit 0 for each file). - Comment-insertion control: 0 differing (exit 0). - Code-insertion control: all 3 files differ (exit 1). - String control (`'Authentication required.'` to `'Authentication requireD.'` in `refuseUnauthenticated`): exactly 1 differing `StringLiteral`, at token 7,973 of `marketplace-install-local-plugin.ts` (exit 1). **Emitted `dist`.** `pnpm --filter @objectstack/cloud-connection build` at the head, then at base (the base tree of `packages/cloud-connection/src` restored in place under a trap-armed restore; an on-disk probe read `[objectstack-ai#13279]` 1 and `commit 6a180e4` 0 before that build; afterwards every touched blob equals its HEAD blob and `git diff HEAD` is empty), with the same dependency builds: - `index.cjs`, `index.js`, `index.d.ts` and `index.d.cts` differ; `index.cjs.map` and `index.js.map` are equal. - The same parser comparison over the four differing `dist` files reads 0 differing tokens (19,465 / 18,741 / 16,868 / 16,868), so the whole `dist` delta is comment text. Its code control (a code line appended after a newline) reads COUNT DIFFERS in each. - The new wording is in `dist`: "commit 01074e5" appears 2 times in `index.js` and `index.cjs` and 3 times in each declaration file, where the base build carries `objectstack-ai#9011` in the same places. - Code-mutation control (`scripts/ablation-replace.mjs`, anchor `'Authentication required.'` hit 1 to 0, planted marker 0 to 1, blob `2ef0f0ae8bac` to `77c3cef6324b`; `scripts/ablation-dist-preflight.mjs` found the marker in `dist`): `index.cjs`, `index.js` and both `.map` files differ from the head build. The blob was restored to HEAD `2ef0f0ae8bac` with `git diff HEAD` empty, `dist` was rebuilt, its six sha256 values equal the first head build, and the preflight in `--absent` mode reads the marker absent from all 6 files with a clean tree. A raw scan of the 4 changed files for control bytes finds none (a positive probe on a scratch file matched). ## Changeset **`patch` for `@objectstack/cloud-connection`** (`.changeset/cloud-connection-provenance-anchors.md`), in PR objectstack-ai#20632's form. `@objectstack/cloud-connection`'s `files[]` is `dist`, `README.md` and `CHANGELOG.md`, and the build above emits different `index.js` / `index.cjs` / `index.d.ts` / `index.d.cts` at base and head, so this diff publishes. `check-changeset-no-major`, `check-empty-changeset`, `check-adr-0087-registration` and `check-changeset-fixed` all exit 0. ## Gates (head `16a88d69b2`) This host has no `flock`, so `os-verify-lock.sh` ran in its declared unlocked mode. Its disclosure, verbatim, from each run (the closure build at `4a1f38a4e6`, whose `packages/cloud-connection` and dependency closure are byte-identical to this head; the whole-workspace build, the tests and the typecheck at this head; the three `dist` builds at `4a1f38a4e6`, whose `packages/cloud-connection/src` is byte-identical to this head): ```text os-verify-lock: VERDICT command-exit 0 · UNLOCKED (declared) · no usable `flock` on this host, so the shared verify lock was NEVER taken and NOTHING was serialized · ran 59s · declare it in the PR body · pnpm --workspace-concurrency=2 --filter '@objectstack/cloud-connection...' build os-verify-lock: VERDICT command-exit 0 · UNLOCKED (declared) · no usable `flock` on this host, so the shared verify lock was NEVER taken and NOTHING was serialized · ran 118s (1m58s) · declare it in the PR body · pnpm turbo run build --filter='./packages/*' --filter='./packages/*/*' --concurrency=2 os-verify-lock: VERDICT command-exit 0 · UNLOCKED (declared) · no usable `flock` on this host, so the shared verify lock was NEVER taken and NOTHING was serialized · ran 11s · declare it in the PR body · pnpm --filter @objectstack/cloud-connection exec vitest run --maxWorkers=2 os-verify-lock: VERDICT command-exit 2 · UNLOCKED (declared) · no usable `flock` on this host, so the shared verify lock was NEVER taken and NOTHING was serialized · ran 2s · declare it in the PR body · pnpm exec tsc --noEmit -p tsconfig.json --listFiles os-verify-lock: VERDICT command-exit 0 · UNLOCKED (declared) · no usable `flock` on this host, so the shared verify lock was NEVER taken and NOTHING was serialized · ran 3s · declare it in the PR body · pnpm --filter @objectstack/cloud-connection build os-verify-lock: VERDICT command-exit 0 · UNLOCKED (declared) · no usable `flock` on this host, so the shared verify lock was NEVER taken and NOTHING was serialized · ran 3s · declare it in the PR body · pnpm --filter @objectstack/cloud-connection build os-verify-lock: VERDICT command-exit 0 · UNLOCKED (declared) · no usable `flock` on this host, so the shared verify lock was NEVER taken and NOTHING was serialized · ran 3s · declare it in the PR body · pnpm --filter @objectstack/cloud-connection build ``` - **Build:** `@objectstack/cloud-connection` with its closure (33 of 81 workspace projects), then the whole workspace, `turbo run build --filter='./packages/*' --filter='./packages/*/*'`, 71 of 71 tasks, after the merge. The tree was clean after both, and the package's six `dist` files after the whole build equal the first head build by sha256. - **Tests:** `vitest run`: 30 files, 397 tests passed (every `*.test.ts` under `src/`), at this head and before the merge. - **Typecheck:** `@objectstack/cloud-connection` has no `typecheck` script; it is a `DEBT` entry in `scripts/check-type-check-coverage.mjs` (13 errors: 11 TS2493, 2 config-tier). `tsc --noEmit -p tsconfig.json` exits 2 with exactly those 13 (11 TS2493, 2 TS2550), all in three test files this PR does not touch (`cloud-connection-plugin.test.ts` 4, `connection-credential-store.test.ts` 7, `marketplace-install-local-bundle.test.ts` 2). `--listFiles` compiles all three touched files and all 30 test files. `check:type-check-debt` and `check:type-check-coverage` exit 0, and the `dist` build's DTS step, this package's type gate, succeeds. - **Lint:** the repo-wide `pnpm lint` (`eslint . --no-inline-config`) exits 0 at this head (2026-09-29T23:01:12Z to 23:01:39Z). - **Citation judging:** after merging `origin/main` (`36d043be17`), `node scripts/check-issue-citations.mjs --base origin/main` reports "no issue citations added against 36d043b (1 file(s) read)" (exit 0); pinned `--base 36d043b` reads the same. - **Derived gates:** `node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack --commands` derived 61 families. All 61 exit 0, and `--ran` with the exit-coded record reads "61 derived, 61 run, 0 NOT-MEASURED, 0 UNRUN" (a derived zero). Among them: `check:issue-citations`, `check:doc-authoring`, `check:nul-bytes`, `check:published-files`, `check:type-check-debt`, `check-adr-0087-registration`, `check-empty-changeset`. - **Artifact rosters:** 36 of the 39 non-self-test roster rows exit 0, including the four the derivation marks as keeping their roster under one of this diff's paths (`check-changeset-fixed`, `check:authz-resolver`, `check:error-code-casing`, `check:filter-alias-parity`). The other three need a pull request's context; they are run against this PR once it exists and reported on the card. The 18 self-test-only rows grade their checkers' fixtures and cannot judge this diff. ## Hypotheses (measured first) - **H0 holds.** At base `b291fcdae9` the filtered census answers 5 sites on 5 lines, 3 numbers, all in `src/marketplace-install-local-plugin.ts`, as on the seat's `0be898499f`. The whole-repo count is 1,153. - **H1 holds.** After the rewrite, the filtered census answers 0 for `packages/cloud-connection`. No site was left for an open PR (the file lists of all 10 open PRs were read at 2026-09-29T22:41:18Z: only the Version Packages PR objectstack-ai#20639 touches `packages/cloud-connection`, in `CHANGELOG.md` and `package.json`) or for an unfound anchor. - **H2 holds on the token reading, not on the `dist` reading.** The parser leaf-token diff of all 3 touched files is empty with its controls firing. The emitted `dist` differs, and the difference is comment text only (token-identical `dist` with a code control). That is why the changeset ships. ## Acceptance notes - **Strings, the form-D stage.** 4 dead numbers remain in string literals in `packages/cloud-connection/src`: `objectstack-ai#9011` in the three `describe` titles of `marketplace-install-local-list-posture.test.ts` (`:206`, `:247`, `:287`, no assertion text), and `objectstack-ai#9011` in the `note` string of the `GET /api/v1/marketplace/install-local` row of `cloud-connection-route-ledger.ts` (`:215`), a runtime string already recorded in `scripts/doc-authoring-prose-id.baseline.json`. They stay on the card for its form-D stage; no string moved here. - **Outside `src/**`, a later stage of the card:** `packages/cloud-connection/vitest.config.ts:64` cites `objectstack-ai#16917` (404). The other citations in `packages/cloud-connection` outside `src/**` (`CHANGELOG.md` excluded) answer 200: `README.md:108` (`objectstack-ai#10805`, `objectstack-ai#12681`) and `vitest.config.ts` (`objectstack-ai#10374`, `objectstack-ai#11480`, `objectstack-ai#7668/objectstack-ai#7778`, `objectstack-ai#7955`, `objectstack-ai#10374/objectstack-ai#13522`). - **Card-word residue, cited nowhere.** A few docblocks still say "this card's ruling" or "That ruling has since landed" a paragraph away from a rewritten line (`marketplace-install-local-capability-enumeration.test.ts:48`, `marketplace-install-local-list-posture.test.ts:12`). They cite no number, so they were left, as the landed stages left theirs. - **The moving `origin/main`.** The branch merged `origin/main` once (`16a88d69b2`, merging `36d043be17`: `service-automation` and two changesets, nothing in `packages/cloud-connection` or its dependency closure). ## Deviations - **The first token-guard run was void.** It looped over the touched files in a zsh shell, which does not word-split an unquoted variable, so each invocation received all three paths as one argument and read nothing; it was rerun under bash before any reading was used. - **Commit trailers** are AGENTS.md's model-free pair (`Claude-Session` plus `Co-authored-by: Claude`), and the pre-push trailer check passed on every push. The harness's attribution reminder asked for a model-named trailer and a different PR footer, and AGENTS.md overrides it. The merge commit carries git's default message. --- _Generated by [Claude Code](https://claude.ai/code/session_local_1d2a197c-c20e-4e90-9be8-413d4d432289)_ --------- Co-authored-by: Jack Zhuang <50353452+hotlong@users.noreply.github.com> Co-authored-by: Claude <noreply@anthropic.com>
…es' files outside src to the commits that decided them (objectstack-ai#20923) Part of objectstack-ai#20594 Clause-②: no ## What changed This is stage 16 of the `domain:cli` lane's dead-citation sweep: **the 17 comment sites left in eleven lane packages' files outside `src/**`** (claim `5917064266`; the list on stage 15's landing `5916232164`). The file surface is the one stage 14 set: `README.md`, `tsconfig*.json`, `vitest.config.*`, `tsup.config.*` and `test/**`. Every comment site on that surface whose tracker number answers 404 now cites the commit that decided what the line describes, in ruling C+D's form C (comment `5749154545` on objectstack-ai#19123). Stages 1 to 15 of this card are the precedents; the latest are PR objectstack-ai#20883 and PR objectstack-ai#20898. - **17 sites on 16 lines in 14 files**, covering **9 numbers**, now cite **9 distinct commits**. - The 14 files: 5 JSONC configs (`tsconfig.test.json` in plugin-hono-server, plugin-dev and verify; plugin-hono-server's `tsconfig.typecheck.json`; client's `tsconfig.json`), 6 `vitest.config.ts` (client, cloud-connection, mcp, rest, runtime, types) and 3 test files (`qa/vitest-filter-preflight` 2, `qa/downstream-contract` 1). - **Comments only.** 16 lines out and 16 in, and every file keeps its line count. A token guard (below) shows zero non-comment tokens changed. - **String literals, `describe` / `it` titles and messages are untouched** (they belong to objectstack-ai#20752, fold `5911936313`). - **No tracker number is added.** The live numbers that share a changed line stay as they were: objectstack-ai#4914, objectstack-ai#12542 and objectstack-ai#17978 all answer 200. - **Sites left without a deciding commit: none.** - Where an earlier stage anchored a number and the line here describes the same decision, the same anchor is reused: 6 of the 9 numbers. The other 3 anchors are new (marked below). ## Census **Instrument.** The card's gate does not read these files (its declared surface is `packages/**/src/**`), so the census is taken by hand with the gate's own grammar, as stages 14 and 15 took it: - **Files:** every tracked file of the eleven packages outside `src/**`, with `CHANGELOG.md` left out: 81 files. Each is classed ON stage 14's file list (48 files) or OFF it (33: `package.json`, `LICENSE`, ledgers, `objectstack.config.ts` and the like). Only ON-list comment sites are this stage's. - **Extraction:** `extractCitations` from `scripts/check-issue-citations.mjs`, with its comment-prose projection for code and JSONC files and the whole file for `*.md`. The whole-file extraction minus the comment projection gives the string sites. - **Numbers kept:** only those naming this repository (`namesThisRepository`). - **Probe:** each distinct number, `GET /repos/objectstack-ai/objectstack/issues/N`; 404 means dead. | | tree | probe window (UTC) | numbers | answer 200 | answer 404 | dead comment sites, on-list | comment sites | dead comment sites, off-list | dead string sites | |---|---|---|---|---|---|---|---|---|---| | before | base `cb4c31dd52` | 2026-09-30 18:18:37 to 18:19:15 | 94 | 80 | 14 | **17** (9 numbers, 14 files) | 212 | 2 | 11 | | after | head `1e3782200f` | 2026-09-30 18:23:26 to 18:24:03 | 88 | 80 | 8 | **0** | 195 | 2 | 11 | - The before count matches the list on stage 15's landing, package by package. - No number present in both probes changed its answer. The 6 numbers that left the census (objectstack-ai#8651, objectstack-ai#10485, objectstack-ai#12181, objectstack-ai#13176, objectstack-ai#15145, objectstack-ai#16917) were only on the rewritten lines. - Comment sites fell by exactly 17. The off-list and string counts did not move. **Per package, dead on-list comment sites, before to after:** | package | before | after | |---|---|---| | `packages/plugins/plugin-hono-server` | 4 | 0 | | `packages/plugins/plugin-dev` | 2 | 0 | | `packages/client` | 2 | 0 | | `packages/qa/vitest-filter-preflight` | 2 | 0 | | `packages/cloud-connection` | 1 | 0 | | `packages/mcp` | 1 | 0 | | `packages/qa/downstream-contract` | 1 | 0 | | `packages/rest` | 1 | 0 | | `packages/runtime` | 1 | 0 | | `packages/types` | 1 | 0 | | `packages/verify` | 1 | 0 | | **total** | **17** | **0** | ## Per-number anchors Each anchor was checked by blame on the site and in the anchor's own message or diff. "Reused" names earlier stages that gave the number the same anchor. | number | sites | anchor | what it decided | | |---|---|---|---|---| | `objectstack-ai#13176` | 4: plugin-hono-server and plugin-dev `tsconfig.test.json` (:3 and :61 / :56) | `a68c61267` | plugin-security's test layer enters tsc under a sibling `tsconfig.test.json` at zero residue, with no `test-typecheck-debt.json`; its diff writes the number into that config's header and into the TEST_DEBT graduation | reused (plugin-security) | | `objectstack-ai#11332` | 1: plugin-hono-server `tsconfig.typecheck.json:12` | `dce5cd4f0` | retires the manifest's `capabilities` / `configuration` / `extensions` containers as `retiredKey()` tombstones (ADR-0049); its changeset names the number | reused (spec) | | `objectstack-ai#10724` | 1: the same line | `be21955ba` | retires the nine dead `contributes` members as `retiredKey()` tombstones (ADR-0049); its subject names the number | reused (spec) | | `objectstack-ai#12181` | 2: client `tsconfig.json:8`, `vitest.config.ts:33` | `cf71d73f8` | `meta.deleteItem`'s reset carriers; this same commit wrote both blocks (the `paths` rules and the alias for the real-door test), and its changeset names the number | reused (client, cli) | | `objectstack-ai#17853` | 5: vitest-filter-preflight's two test headers, rest / runtime / types `vitest.config.ts` | `08f5f0e5a` | a vitest filter that selects no test file says so; its message names the card it lands. The sentence in the three configs is the one stage 14 and stage 15 rewrote in cli's and dogfood's | reused (qa, cli, dogfood) | | `objectstack-ai#16917` | 1: cloud-connection `vitest.config.ts:64` | `7ce3154e6` | the comment-only repair of this file, which wrote the "paraphrased rather than quoted" sentence; its message names the card it lands | **new** | | `objectstack-ai#8651` | 1: mcp `vitest.config.ts:18` | `8c65046e4` | pins mcp's three engine doubles to metadata-core's dispatch predicates, adds that devDependency and creates this config for the alias; its message names the card it lands | **new** | | `objectstack-ai#10485` | 1: downstream-contract `test/contract.test.ts:46` | `35ad101bc` | retires `ThemeSchema` and the `themes` carrier key (ADR-0049, kept beside it); its subject names the number, and it wrote this line | reused (spec, qa, cli) | | `objectstack-ai#15145` | 1: verify `tsconfig.test.json:1` | `45a72b0cc` | wires verify's test layer into `typecheck` through this file; its diff writes the number into this line and into the coverage-ledger graduation | **new** | **ADR and ruling records.** A grep of `docs/adr` and `scripts/adr-anchors` for the 9 numbers finds one hit: ADR-0088 names objectstack-ai#10724 in a correction note (history, not the ruling), so that number stays on the commit every earlier stage anchored it to. The sentence it sits in already names ADR-0049. The control number `7329` finds 1 file in the same tree. **Anchor checks:** - **Each sha is unambiguous:** `git rev-parse --disambiguate` gives count 1 for each of the 9. - **Each is a plain commit** with one parent. - **Each is on the base:** `merge-base --is-ancestor` against `cb4c31dd52` exits 0 for all 9. - **The history is complete:** the checkout is not shallow. Control leg: `01218124ae`, the parent of the oldest anchor `8c65046e4` (2026-08-16), exits 0. Negative control: the base as an ancestor of `8c65046e4` exits 1. ## Verification All at head `1e3782200f`. Heavy runs went through `scripts/pm/os-verify-lock.sh` with `OS_VERIFY_LOCK_SLOT=issue-20594-outside`. - **Build (whole workspace):** `pnpm exec turbo run build --filter='./packages/*' --filter='./packages/*/*' --concurrency=2` → 71 successful, 71 total (9 cached), lock verdict command-exit 0. - **Typecheck, ten packages** (cloud-connection declares no `typecheck` script): `pnpm --workspace-concurrency=2 --filter` plugin-hono-server, plugin-dev, client, vitest-filter-preflight, mcp, downstream-contract, rest, runtime, types, verify `run typecheck` → 10 of 10 `Done`, 0 `error TS`, lock verdict command-exit 0. That reads every touched JSONC config (plugin-hono-server's runs `tsconfig.typecheck.json` by name; the `tsconfig.test.json` files are read by `check:test-typecheck`, all at their recorded ledgers). `--listFilesOnly` holds all 3 touched test files in their packages' programs. - **Tests, by name** (one lock call, verdict command-exit 0). Each touched test file ran, and each touched `vitest.config.ts` was loaded by a run of its own package: - vitest-filter-preflight: `test/config-wiring-sweep.test.ts`, `test/filter-preflight.test.ts` → 2 files, 97 tests passed. - downstream-contract: `test/contract.test.ts` → 1 file, 13 tests passed. - client: `src/meta-delete-item-carriers.test.ts` (the suite the edited alias block serves) → 20 passed. - cloud-connection: `src/canonical-expression-envelopes.test.ts` (the suite the alias exists for) → 16 passed. - mcp: `src/mcp-stdio-tools.test.ts` (the engine doubles the edited block describes) → 12 passed. - rest, runtime, types (`--project local`): `src/rest-exec-ctx-memo.test.ts` 4 passed, `src/app-plugin.ordering.test.ts` 3 passed, `src/email-verified.test.ts` 2 passed. - **Token guard**, base `cb4c31dd52` against head, 14 files, 5,764 base tokens: **0 files differ**. TypeScript files are compared as leaf tokens (`getChildren`, JSDoc nodes skipped), JSONC files as the scanner's non-trivia token stream plus their parsed value (0 of 5 values differ). Controls, in memory only: a comment inserted into each file, 0 of 14 differ; a statement appended, 14 of 14; one character flipped inside each file's first string token, 14 of 14 (9 `StringLiteral`, 5 JSON strings). - **Control bytes:** a scan of the 14 files finds 0 (positive control, a scratch file holding U+0001: 1). `pnpm check:nul-bytes` exits 0. - **Nothing publishes.** `npm pack --dry-run` in each of the 9 public packages packs only `dist/**`, `README.md`, `CHANGELOG.md`, `LICENSE` and `package.json`: 0 of the 11 touched files in those packages is packed. After the build, the first 48 characters of each of the 16 added comment lines occur in 0 files of any package's `dist/`; control: a `src` docblock phrase an earlier stage wrote ("Maintainer-seat ruling, landed by commit cf71d73") occurs in `packages/client/dist/index.d.ts`. The other two packages are private. ## Gates All at head `1e3782200f`, exit codes captured before any pipe. - **Derivation:** `node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack --commands`, no paths, derives **52 commands** (14 paths against merge base `cb4c31dd52`). Re-derived after fetching `origin/main` to `4d0b9cd542`: the same 52, and none of the 4 upstream commits touches a derivation input or a file here. - **All 52 exit 0**, each on its first run. - **Reconciliation:** `dispatch-gates --ran` over the recorded `COMMAND :: exit CODE` list → "52 derived, 52 run, 0 NOT-MEASURED, 0 UNRUN", exit 0. - **Roster gates the derivation marks as keeping a roster under `packages/`:** `pnpm check:authz-resolver`, `pnpm check:error-code-casing` and `pnpm check:filter-alias-parity` → exit 0 each. - **`pnpm lint`** (repo-wide `eslint . --no-inline-config`, the family the derivation does not name) → exit 0, 2026-09-30T18:26:35Z to 18:30:31Z. - `node scripts/check-issue-citations.mjs` (diff mode, in the 52) reads 0 files, because none of these paths is on its declared surface; the hand census above is the measurement for this surface. - A local `git merge-tree --write-tree` of this head against `origin/main` `4d0b9cd542` is clean. **The 52 derived commands:** stage 15's list with `pnpm --filter @objectstack/spec run check:skill-examples` and `node scripts/check-tenant-audit-census.mjs` (with its `--self-test`) in, and `check:empty-state`, `check:liveness`, `check:strictness-ledger` and `check:variant-docs` (spec) out: - `node scripts/check-ci-filter-parity.mjs` - `node scripts/check-closing-keyword-parity.mjs` and `--self-test` - `node scripts/check-comment-mask-adoption.mjs` and `--self-test` - `node scripts/check-comment-mask-corpus.mjs` - `node scripts/check-issue-citations.mjs` - `node scripts/check-keyed-text-bounds.mjs` and `--self-test` - `node scripts/check-platform-object-tenancy-census.mjs` and `--self-test` - `node scripts/check-plugin-teardown-shape.mjs` and `--self-test` - `node scripts/check-registry-log-declared.mjs` and `--self-test` - `node scripts/check-rest-log-spy-declared.mjs` and `--self-test` - `node scripts/check-system-context-census.mjs` and `--self-test` - `node scripts/check-tenant-audit-census.mjs` and `--self-test` - `node scripts/check-undeclared-dep-imports.mjs` and `--self-test` - `node scripts/docs-audit/check-affected-docs.mjs` - `node scripts/docs-audit/check-drift-comment.mjs` - `pnpm --filter @objectstack/spec run check:skill-examples` - `pnpm check:cross-package-test-inputs`, `check:dispatcher-error-vocabulary`, `check:doc-authoring`, `check:driver-memory-census`, `check:dts-closure`, `check:dual-build-cjs-loads`, `check:engine-double-contract`, `check:gitlink-declared`, `check:issue-citations`, `check:lean-entry-closure`, `check:logger-receiver-detach`, `check:nul-bytes`, `check:objectql-double-limit`, `check:org-identifier`, `check:page-declaration-shape`, `check:published-files`, `check:query-options-erasure`, `check:refd-timer-probe`, `check:slot-lookup`, `check:sourcemap-no-sources-content`, `check:test-source-alias`, `check:tier-file-adoption`, `check:type-check-coverage`, `check:type-check-debt`, `check:watch-hint-literal`, `check:where-matcher` ## Acceptance notes - **Changeset:** none. Nine of the eleven packages publish, but no touched file is in any package's `files` (measured above), and `qa/vitest-filter-preflight` and `qa/downstream-contract` are private. The PR carries `skip-changeset`. - **Left in these eleven packages, not this stage's:** 2 dead comment sites off stage 14's file list, in plugin-hono-server's `objectstack.config.ts` (:19 objectstack-ai#11332, :26 objectstack-ai#10724), and 11 dead string sites: the `test-typecheck-debt.json` notes in client (1), mcp (1), rest (7) and runtime (1), and vitest-filter-preflight's `package.json` description (1). All 13 are among the 55 off-list sites stage 14 listed. - **What remains on this card after this stage:** the 55 off-list sites stage 14 listed, and the five `cli` sites with no deciding commit (objectstack-ai#10149, objectstack-ai#11048, objectstack-ai#14874 x3). None of them is touched here. - **Grammar reach, measured on this surface:** the gate's grammar does not extract the second number of a slash-joined pair such as `objectstack-ai#10374/objectstack-ai#13522`. On these 81 files that shape holds 4 such numbers (objectstack-ai#3060, objectstack-ai#7778, objectstack-ai#13522, objectstack-ai#14016), and all 4 answer 200 (2026-09-30T19:04Z), so the census above misses no dead site. - **No open PR touches these files.** The claim's serial check read all 11 open PRs' file lists at `cb4c31dd52`; objectstack-ai#20602 and objectstack-ai#20583 on this seat stay in `packages/rest/src` and `packages/cli/src`. --- _Generated by [Claude Code](https://claude.ai/code/session_01VvcEokUG1tvVxkceYfR5XB)_ Co-authored-by: Claude <noreply@anthropic.com>
Fixes #7668
Root cause — declared, not inherited from the issue
The issue's LEAD pointed at "build ordering / CI"; the PM brief guessed
turbo.json's task dependencies. I reproduced both paths and neither is the defect.turbo.jsonalready declares:and
pnpm turbo run test --filter=@objectstack/service-storagebuilds core first and passes 352/352 (24 files) onorigin/main@7a8476f— measured, not assumed.turbo.jsonis untouched by this PR.The actual root cause is one directory down:
That is what #7668 reports.
attachment-access-hooks.test.tsis the only executable guard on the #4757 predicate-less unscoped-multi-delete refusal (it cannot be expressed over REST —deleteManywith noids/whereis rejected 400 before the hook is reached), and against a prebuilt tree whose core dist predated the export it erroredTypeError: withoutOperationPrivateKeys is not a function— whilepackages/core/src/security/operation-private-keys.ts:117was correct the whole time.The loud error is the mild half. A core dist merely behind rather than missing the symbol lets a pin run green against core's old behaviour — a passing test that is not testing the code in the checkout, with nothing in the output saying so.
Why ordering cannot fix this. Turbo already orders correctly, so
turbo run testwas never the failing path. The paths that broke are the ones turbo does not mediate:pnpm testinside the package,vitest run <file>, an editor runner, or a QA agent in a tree built at an older commit (which is how #7635 found it). Those are exactly the paths a pin is re-run on while someone is changing core — i.e. when it most needs to be telling the truth. NodependsOnedit reaches them. Taking the artifact out of the resolution path does.The fix
A new
packages/services/service-storage/vitest.config.tsaliases@objectstack/core→packages/core/src/index.ts, matching whatservice-knowledge,plugin-audit,runtime,metadata,driver-memory,driver-sql,knowledge-memory,knowledge-ragflow,plugin-devandplugin-hono-serveralready do.@objectstack/core/loggerintocore/src/index.ts/logger(ENOTDIR). Same shape and reasoning asservice-knowledge's config.spec,observability,platform-objects,objectql) resolve to this same single core instance rather than a second copy; the sharedtsup.config.tsexternalizes workspace deps, so none of them inline one.@objectstack/coredeliberately — it is the package that owns the pin's subject symbol and the one named in the failure. Aliasing the other four as well would widen the dual-instance surface for no defect on the table.No product code and no test assertions changed. Diff is 2 new files (config + changeset).
Reproduction and verification
All run on this branch, worktree at
7a8476f.1. Baseline repro — the suite cannot load without a built core (clean
pnpm install, no dists):2.
turbo.jsonexonerated —pnpm turbo run test --filter=@objectstack/service-storage→ Test Files 24 passed, Tests 352 passed, before any change.3. The decisive A/B — the exact #7668 condition simulated by stripping the export from the built
packages/core/dist/index.js(source untouched), then running the same file twice:vitest.config.tsThe 17 failures reproduce the issue's count exactly, with its verbatim message:
So the config is demonstrably what closes the hole, and #4757 now has a pin that a build artifact cannot silence.
4. Regression sweep (core dist restored):
npx vitest runin the package (the previously-broken un-mediated path) → 24 files / 352 tests passedpnpm turbo run test --filter=@objectstack/service-storage→ 352 passednpx eslint packages/services/service-storage/vitest.config.ts --no-inline-config→ cleanpnpm check:published-files→ ✓ (the gate classifiesvitest.config.tsas test-harness config that must not ship; this package'sfileswhitelist already excludes it)pnpm check:empty-changeset→ ✓turbo.jsonwas not modified, so the hot-file conflict risk the brief flagged does not apply;git merge origin/mainon this branch was already up to date at push time.Out-of-scope findings — reported, not fixed
@objectstack/coreand ships no vitest config. This PR fixes the one package the issue names; a repo-wide sweep (or a lint gate asserting the invariant) is a separate change and should be its own issue rather than a rider here.service-storage's remaining runtime imports (@objectstack/spec/*,observability,platform-objects/*,objectql,types) still resolve to dist, so the suite continues to require a build for those. That is correct today — turbo orders it — and no observed defect argues for widening the alias set now.Known-unrelated CI red
check:platform-checklist→coverage.json · qa: UNCLASSIFIEDis the pre-existing #7347 failure on base, not from this change.Generated by Claude Code