Skip to content

test(service-storage): resolve @objectstack/core from source so a stale dist can't decide a pin (#7668) - #7778

Merged
huangyiirene merged 1 commit into
mainfrom
claude/issue-7668-storage-test-build-order
Aug 11, 2026
Merged

huangyiirene merged 1 commit into
mainfrom
claude/issue-7668-storage-test-build-order

Conversation

@huangyiirene

Copy link
Copy Markdown
Collaborator

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.json already declares:

"test": { "dependsOn": ["^build"], ... }

and pnpm turbo run test --filter=@objectstack/service-storage builds core first and passes 352/352 (24 files) on origin/main @ 7a8476f — measured, not assumed. turbo.json is untouched by this PR.

The actual root cause is one directory down:

packages/services/service-storage has no vitest.config.ts, so @objectstack/core resolves through the workspace link to packages/core/dist/index.js — a build artifact. The verdict of every unit pin in the package is therefore a function of build state, not of the source in the checkout.

That is what #7668 reports. attachment-access-hooks.test.ts is the only executable guard on the #4757 predicate-less unscoped-multi-delete refusal (it cannot be expressed over REST — deleteMany with no ids/where is rejected 400 before the hook is reached), and against a prebuilt tree whose core dist predated the export it errored TypeError: withoutOperationPrivateKeys is not a function — while packages/core/src/security/operation-private-keys.ts:117 was 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 test was never the failing path. The paths that broke are the ones turbo does not mediate: pnpm test inside 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. No dependsOn edit reaches them. Taking the artifact out of the resolution path does.

The fix

A new packages/services/service-storage/vitest.config.ts aliases @objectstack/core → packages/core/src/index.ts, matching what service-knowledge, plugin-audit, runtime, metadata, driver-memory, driver-sql, knowledge-memory, knowledge-ragflow, plugin-dev and plugin-hono-server already do.

  • Anchored regex, array form — the object form matches by prefix and would swallow @objectstack/core/logger into core/src/index.ts/logger (ENOTDIR). Same shape and reasoning as service-knowledge's config.
  • Aliasing is graph-wide, so the deps still loaded from dist (spec, observability, platform-objects, objectql) resolve to this same single core instance rather than a second copy; the shared tsup.config.ts externalizes workspace deps, so none of them inline one.
  • Scoped to @objectstack/core deliberately — 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):

$ cd packages/services/service-storage && npx vitest run src/attachment-access-hooks.test.ts
Error: Failed to resolve entry for package "@objectstack/core".
 ❯ src/attachment-access-hooks.ts:3:1  import { withoutOperationPrivateKeys } from '@objectstack/core';

2. turbo.json exonerated — 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:

core dist vitest.config.ts result
stale (export stripped) present (this PR) 30/30 pass
stale (export stripped) absent (pre-PR state) 17 failed / 13 passed

The 17 failures reproduce the issue's count exactly, with its verbatim message:

Caused by: TypeError: withoutOperationPrivateKeys is not a function

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 run in the package (the previously-broken un-mediated path) → 24 files / 352 tests passed
  • pnpm turbo run test --filter=@objectstack/service-storage → 352 passed
  • npx eslint packages/services/service-storage/vitest.config.ts --no-inline-config → clean
  • pnpm check:published-files → ✓ (the gate classifies vitest.config.ts as test-harness config that must not ship; this package's files whitelist already excludes it)
  • pnpm check:empty-changeset → ✓

turbo.json was not modified, so the hot-file conflict risk the brief flagged does not apply; git merge origin/main on this branch was already up to date at push time.

Out-of-scope findings — reported, not fixed

  • The same hazard shape exists wherever a package with unit tests imports @objectstack/core and 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: UNCLASSIFIED is the pre-existing #7347 failure on base, not from this change.


Generated by Claude Code

…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
@vercel

vercel Bot commented Aug 11, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

1 Skipped Deployment
Project Deployment Actions Updated (UTC)
objectstack Ignored Ignored Aug 11, 2026 2:32pm

Request Review

@github-actions

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

This PR changes 1 package(s): @objectstack/service-storage.

3 hand-written doc(s) reference the affected code and may need an implementation-accuracy re-verification:

  • content/docs/api/plugin-endpoints.mdx (via @objectstack/service-storage)
  • content/docs/kernel/services-checklist.mdx (via @objectstack/service-storage)
  • content/docs/plugins/packages.mdx (via @objectstack/service-storage)

⛔ 1 release-owned page(s) also reference the affected code. These are read-only:

  • content/docs/releases/implementation-status.mdx (via @objectstack/service-storage)

content/docs/releases/ is RELEASE-OWNED (AGENTS.md "Documentation Guardrails"): release
notes are written centrally at release time, and a code PR that edits them is the exact PR
that guardrail exists to stop. They are still audited — read-only. If one of them is actually
wrong, file an issue or open a dedicated docs-only PR; do not edit it here.

Advisory only. To re-verify, run the docs-accuracy-audit workflow scoped to these files:
node scripts/docs-audit/affected-docs.mjs origin/main → pass the list as args.docs.

@github-actions

Copy link
Copy Markdown
Contributor

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

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

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

  • Test Core (1/3) — 失败步骤: Run this shard's tests

    �[41m�[1m FAIL �[22m�[49m src/data/api-methods-batch-conformance.test.ts�[2m > �[22mapiMethods conformance — single-record writes imply batch (#3026)�[2m > �[22mgrants bulk wherever it grants create /
    

历史信号:

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

分诊清单:

  1. 失败测试在本 PR 改动的包里 → 真回归,修 PR。
  2. 失败测试与本 PR 无关 → 在其他 PR 的同类评论里搜同名测试;出现过 ⇒ flaky 实锤,开 issue 修/隔离那条测试。修好前重排只会再烧一轮全队列。
  3. 两者都不是 → 可能与同组 PR 语义冲突;等前面的 PR 落地或失败出队后再重排一次即可,不要连续重排。

Generated by Claude Code · merge-queue-triage workflow (#4859)

@github-actions

Copy link
Copy Markdown
Contributor

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

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

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

  • Test Core (1/3) — 失败步骤: Run this shard's tests

    �[41m�[1m FAIL �[22m�[49m src/data/api-methods-batch-conformance.test.ts�[2m > �[22mapiMethods conformance — single-record writes imply batch (#3026)�[2m > �[22mgrants bulk wherever it grants create /
    

历史信号:

  • ⚠️ 本 PR 过去 24h 已在队列失败 1 次(不含本次)。 内容未变而反复失败 ⇒ 高度怀疑 flaky 测试或与同组 PR 的语义冲突,重排不解决。
  • 过去 24h 队列共有 36 个失败构建(不含本次)。

分诊清单:

  1. 失败测试在本 PR 改动的包里 → 真回归,修 PR。
  2. 失败测试与本 PR 无关 → 在其他 PR 的同类评论里搜同名测试;出现过 ⇒ flaky 实锤,开 issue 修/隔离那条测试。修好前重排只会再烧一轮全队列。
  3. 两者都不是 → 可能与同组 PR 语义冲突;等前面的 PR 落地或失败出队后再重排一次即可,不要连续重排。

Generated by Claude Code · merge-queue-triage workflow (#4859)

@github-actions

Copy link
Copy Markdown
Contributor

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

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

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

  • Test Core (1/3) — 失败步骤: Run this shard's tests

    �[41m�[1m FAIL �[22m�[49m src/data/api-methods-batch-conformance.test.ts�[2m > �[22mapiMethods conformance — single-record writes imply batch (#3026)�[2m > �[22mgrants bulk wherever it grants create /
    

历史信号:

  • ⚠️ 本 PR 过去 24h 已在队列失败 2 次(不含本次)。 内容未变而反复失败 ⇒ 高度怀疑 flaky 测试或与同组 PR 的语义冲突,重排不解决。
  • 过去 24h 队列共有 41 个失败构建(不含本次)。

分诊清单:

  1. 失败测试在本 PR 改动的包里 → 真回归,修 PR。
  2. 失败测试与本 PR 无关 → 在其他 PR 的同类评论里搜同名测试;出现过 ⇒ flaky 实锤,开 issue 修/隔离那条测试。修好前重排只会再烧一轮全队列。
  3. 两者都不是 → 可能与同组 PR 语义冲突;等前面的 PR 落地或失败出队后再重排一次即可,不要连续重排。

Generated by Claude Code · merge-queue-triage workflow (#4859)

@github-actions

Copy link
Copy Markdown
Contributor

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

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

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

  • Test Core (1/3) — 失败步骤: Run this shard's tests

    �[41m�[1m FAIL �[22m�[49m src/data/api-methods-batch-conformance.test.ts�[2m > �[22mapiMethods conformance — single-record writes imply batch (#3026)�[2m > �[22mgrants bulk wherever it grants create /
    

历史信号:

  • ⚠️ 本 PR 过去 24h 已在队列失败 3 次(不含本次)。 内容未变而反复失败 ⇒ 高度怀疑 flaky 测试或与同组 PR 的语义冲突,重排不解决。
  • 过去 24h 队列共有 45 个失败构建(不含本次)。

分诊清单:

  1. 失败测试在本 PR 改动的包里 → 真回归,修 PR。
  2. 失败测试与本 PR 无关 → 在其他 PR 的同类评论里搜同名测试;出现过 ⇒ flaky 实锤,开 issue 修/隔离那条测试。修好前重排只会再烧一轮全队列。
  3. 两者都不是 → 可能与同组 PR 语义冲突;等前面的 PR 落地或失败出队后再重排一次即可,不要连续重排。

Generated by Claude Code · merge-queue-triage workflow (#4859)

@github-actions

Copy link
Copy Markdown
Contributor

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

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

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

  • Test Core (1/3) — 失败步骤: Run this shard's tests

    �[41m�[1m FAIL �[22m�[49m src/data/api-methods-batch-conformance.test.ts�[2m > �[22mapiMethods conformance — single-record writes imply batch (#3026)�[2m > �[22mgrants bulk wherever it grants create /
    

历史信号:

  • ⚠️ 本 PR 过去 24h 已在队列失败 4 次(不含本次)。 内容未变而反复失败 ⇒ 高度怀疑 flaky 测试或与同组 PR 的语义冲突,重排不解决。
  • 过去 24h 队列共有 49 个失败构建(不含本次)。

分诊清单:

  1. 失败测试在本 PR 改动的包里 → 真回归,修 PR。
  2. 失败测试与本 PR 无关 → 在其他 PR 的同类评论里搜同名测试;出现过 ⇒ flaky 实锤,开 issue 修/隔离那条测试。修好前重排只会再烧一轮全队列。
  3. 两者都不是 → 可能与同组 PR 语义冲突;等前面的 PR 落地或失败出队后再重排一次即可,不要连续重排。

Generated by Claude Code · merge-queue-triage workflow (#4859)

@github-merge-queue
github-merge-queue Bot removed this pull request from the merge queue due to failed status checks Aug 11, 2026
@github-actions

Copy link
Copy Markdown
Contributor

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

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

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

  • Test Core (1/3) — 失败步骤: Run this shard's tests

    �[41m�[1m FAIL �[22m�[49m src/data/api-methods-batch-conformance.test.ts�[2m > �[22mapiMethods conformance — single-record writes imply batch (#3026)�[2m > �[22mgrants bulk wherever it grants create /
    

历史信号:

  • ⚠️ 本 PR 过去 24h 已在队列失败 5 次(不含本次)。 内容未变而反复失败 ⇒ 高度怀疑 flaky 测试或与同组 PR 的语义冲突,重排不解决。
  • 过去 24h 队列共有 55 个失败构建(不含本次)。

分诊清单:

  1. 失败测试在本 PR 改动的包里 → 真回归,修 PR。
  2. 失败测试与本 PR 无关 → 在其他 PR 的同类评论里搜同名测试;出现过 ⇒ flaky 实锤,开 issue 修/隔离那条测试。修好前重排只会再烧一轮全队列。
  3. 两者都不是 → 可能与同组 PR 语义冲突;等前面的 PR 落地或失败出队后再重排一次即可,不要连续重排。

Generated by Claude Code · merge-queue-triage workflow (#4859)

@huangyiirene
huangyiirene added this pull request to the merge queue Aug 11, 2026
Merged via the queue into main with commit 93be029 Aug 11, 2026
26 checks passed
@huangyiirene
huangyiirene deleted the claude/issue-7668-storage-test-build-order branch August 11, 2026 19:49
hotlong pushed a commit that referenced this pull request Aug 12, 2026
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
os-zhuang pushed a commit that referenced this pull request Aug 13, 2026
…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
akarma-synetal pushed a commit to akarma-synetal/framework that referenced this pull request Aug 17, 2026
…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>
akarma-synetal pushed a commit to akarma-synetal/framework that referenced this pull request Aug 17, 2026
… 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>
akarma-synetal pushed a commit to akarma-synetal/framework that referenced this pull request Aug 17, 2026
…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>
akarma-synetal pushed a commit to akarma-synetal/framework that referenced this pull request Aug 17, 2026
…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>
akarma-synetal pushed a commit to akarma-synetal/framework that referenced this pull request Oct 7, 2026
…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>
akarma-synetal pushed a commit to akarma-synetal/framework that referenced this pull request Oct 7, 2026
…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>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentation Improvements or additions to documentation size/s tests tooling

Projects

None yet

Development

Successfully merging this pull request may close these issues.

attach-requires-parent-edit c3: #4757 multi-delete pin can't execute against the prebuilt @objectstack/core (stale-dist / CI)

2 participants