fix(examples): app-multi-package's module is served WITH scope: 'project' — drop the "no scope key" claim - #14804

Merged
os-sam merged 1 commit into
mainfrom
claude/issue-14597-multi-package-scope-claim
Sep 3, 2026
Merged

fix(examples): app-multi-package's module is served WITH scope: 'project' — drop the "no scope key" claim#14804
os-sam merged 1 commit into
mainfrom
claude/issue-14597-multi-package-scope-claim

Conversation

@claude

@claudeclaudeBot commented Sep 3, 2026

Copy link
Copy Markdown
Contributor

Fixes#14597

The fixture documented a property its producer never emits. The triage made one question a precondition: does a genuinely scope-less served row exist on any path? It does — just not on this fixture's path, and not on the row the fixture claimed.

Measurement (read-only; all line numbers on origin/main8f9ef6f7c)

H1 — the artifact path materialises scope at compile time. CONFIRMED.
defineStack parses every packages[] entry's manifest through ManifestSchema (packages/spec/src/stack.zod.ts:233, ArtifactPackageEntrySchema), whose scope is .default('project') (packages/spec/src/kernel/manifest.zod.ts:311). Measured:

measurementresult
dist/objectstack.json after a real buildboth packages has_scope: true, scope: "project"
ManifestSchema.parse of the authored orders bodyhas_scope: false in, scope: "project" out
GET /api/v1/packages on a booted stackboth rows scope: "project", writable: false

So no package of a compiled artifact can be scope-less — the fixture's claim was not merely untrue here, it is unreachable from any examples/** app.

H2 — a raw manifest body CAN reach the registry unparsed. CONFIRMED; H1's "no served row is ever scope-less" is FALSIFIED.
registerApp (packages/objectql/src/engine.ts:4927) hands its argument to installPackage (:4956), which stores toRecordManifest(manifest) (packages/objectql/src/registry.ts:3956) — a key-by-key copy (:1229) that applies no defaults and runs no parse. Two production paths reach it without a ManifestSchema parse:

  • booted, read-only — marketplace install / offline file import / ledger rehydrate: manifestService.register(rawBody) at packages/cloud-connection/src/marketplace-install-local-plugin.ts:291 and :827, routed through packages/objectql/src/plugin.ts:457. This is the card's own parenthetical ("an installed marketplace package whose manifest bypasses defineStack?") and the answer is yes.
  • database base, writablePOST /api/v1/packages: const manifest = body.manifest || body (packages/runtime/src/domains/packages.ts:383) reaches protocol.installPackage (packages/metadata-protocol/src/protocol.ts:21188), which defaults version and derives namespace by hand and parses nothing (:21215). The code says so itself: "defineStack enforces it at compile time, but runtime-created packages never take that path".

Measured: SchemaRegistry.installPackage of the unparsed orders body yields a record with noscope key; of the parsed body, scope: "project".

H3 — the fixture's module would not have been the discriminator even if it were scope-less.
isWritablePackage (packages/metadata-protocol/src/package-writability.ts:77) reads engine.manifests FIRST, so scope is never consulted for a booted package. Measured verdicts:

rowin engine.manifestsscopeserver writable
scope-less booted (marketplace import)yesabsentfalse
scope-less database base (Studio)noabsenttrue
the fixture's moduleyes'project'false

The pair that separates the server verdict from any scope-only rule is rows 1 and 2 — and it is already pinned where it is true, in packages/runtime/src/domains/packages-writable-verdict.test.ts (Pins 2 + 4) and packages/metadata-protocol/src/get-meta-items-package-writable.test.ts. Nothing needed to move; the claim needed to come off the fixture.

What changed

  • examples/app-multi-package/src/packages/orders/index.ts — the second "load-bearing property" now states what the fixture delivers (the server's own writable: false verdict), plus a ⛔ note recording why no compiled artifact can carry a scope-less row, so the claim cannot be re-added.
  • examples/app-multi-package/README.md — same correction to the prose under the table.
  • packages/qa/dogfood/test/multi-package-artifact.dogfood.test.ts — the harness that already boots this fixture. Its comment repeated the claim; corrected, and a new pin asserts both served rows carry scope: 'project', on a real boot. That is the assertion the old claim could only ever have been checked by, and never was.

No changeset: comment-and-test only, from a private example and a private QA package — nothing publishes. skip-changeset applied at creation.

packages/spec/src/kernel/manifest.zod.ts is untouched, per the triage: the default is correct and load-bearing.

Verification — union run at bff31b601

  • 31 gate families, re-derived on the actual diff (node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack), all run: 29 green, 2 needed the workspace built and are green after it (check:dual-build-cjs-loads, check:type-check-debt — the latter's --re-measure reports 22 ledger entries, 755 raw errors, none above record). scripts/check-test-completeness.mjs returns its own exit-3 NOT MEASURED branch (it parses a test-run log CI supplies; no local log exists) — recorded as NOT MEASURED, not as a pass.
  • pnpm lint (repo-wide eslint . --no-inline-config) — green. No narrowing claimed.
  • pnpm --filter './examples/*' run typecheck — green; log confirms examples/app-multi-package typecheck$ tsc --noEmit ... Done (checked against the zero-match-exits-0 trap).
  • pnpm check:nul-bytes green, plus a manual control-character scan over the three edited files.
  • The new pin, on a real boot: pnpm --filter @objectstack/dogfood exec vitest run test/multi-package-artifact.dogfood.test.ts — 9 passed (was 8).
  • Reverse verification of the new pin. Committed first, then mutated the orders assertion to the OLD claim (toBeUndefined()) and re-ran: 1 failed | 8 passed, AssertionError: expected 'project' to be undefined — the card's claim, falsified mechanically on a booted stack. Mutation confirmed on disk both ways before the run (injected marker present = 1, removed text = 0, blob 1b93be01 vs HEAD blob b6a7adf5); restore leg proved by git diff HEAD empty and the worktree blob back to b6a7adf5, marker gone, pin present. Restore ran from an EXIT INT TERM trap using absolute paths.

Out of scope, filed unassigned

The same false attribution is repeated in five platform-side comments and in the objectui console's rationale. Neither is a file this card may touch (the triage fenced the objectui half explicitly, and packages/metadata-protocol/src/protocol.ts is held by open PR #14767, so that sweep cannot be atomic here anyway).

🤖 Generated with Claude Code

https://claude.ai/code/session_017RbbUMnxkUnWhE4j94v8FE


Generated by Claude Code

…ackage
`defineStack` parses every `packages[]` entry through `ManifestSchema`, whose
`scope` is `.default('project')`, so the fixture's module is served with
`scope: 'project'` and never was the scope-less row it documented. Measured:
`dist/objectstack.json` carries `has_scope: true, scope: "project"` on both
packages.
State what the fixture actually delivers instead — the server's own
`writable: false` verdict (ADR-0070 D2), which reads `engine.manifests` before
any scope — and pin `scope: 'project'` on both served rows in the dogfood
harness that already boots the fixture, so the corrected docs stay true.
The genuinely scope-less rows exist where a manifest reaches the registry
without a `ManifestSchema` parse (marketplace / offline import; a Studio base
via `POST /api/v1/packages`); that pair is already pinned in
`packages/runtime/src/domains/packages-writable-verdict.test.ts`.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017RbbUMnxkUnWhE4j94v8FE
@claudeclaudeBot added the skip-changeset PR has no user-facing published change; bypasses the changeset gate label Sep 3, 2026
@github-actions

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

Nothing in this diff resolved to a documentable surface (no symbol, route or SDK anchor derived from 0 changed package(s)), so this run has no opinion about the docs.

What this run could not see
  • a page that states a rule by its inputs shares no identifier with the emitter that implements the rule, so an emitter-only diff cannot list it — not on this run and not on any run. Measured on fix(driver-sql): emit varchar(maxLength) for a text field a declared index keys on #11430: content/docs/protocol/objectql/types.mdx documents the text-family column mapping by the ObjectQL type names it maps FROM (text / textarea / html) while the diff changed createColumn; it went unlisted, and it was the page that diff falsified, in four places. No shared token exists to detect this on, so a rule your change carries has to be re-read by hand in the pages that restate it.

Coarse fallback — 0 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): node scripts/docs-audit/affected-docs.mjs --json fc648a256e0968950a10bb0454a6c835d3b064d5packageMentionDocs.

@claude

claudeBot commented Sep 3, 2026

Copy link
Copy Markdown
ContributorAuthor

Landing (domain:spec seat, session_017RbbUMnxkUnWhE4j94v8FE, 2026-09-03T03:06Z) — flipping to ready and enabling auto-merge (SQUASH) in this stroke.

Pre-landing checks: seat ACCEPT on #14597 (comment 5519582946; clause ② no — no schema, accept/reject or public-surface change; manifest.zod.ts untouched); all 38 checks on bff31b601 complete — 31 success / 7 skipped / 0 red; trial merge git merge-tree --write-tree origin/main refs/pull/14804/head at origin/maine6ac0c6fd: clean; governed-surface predicate: 0 of 3 paths — ordinary queue landing; skip-changeset read back on the PR (private example + private QA package).


Generated by Claude Code

@os-sam
os-sam marked this pull request as ready for review September 3, 2026 03:05
@os-sam
os-sam enabled auto-merge September 3, 2026 03:06
@os-sam
os-sam added this pull request to the merge queueSep 3, 2026
@github-actions

Copy link
Copy Markdown
Contributor

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

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

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

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

    @objectstack/cli:test: FAIL integration test/run-dev-unbuilt-workspace.e2e.test.ts > the mirror direction: a reader that is never coming back > gives up and exits instead of waiting forever
    ↳ 失败原因: @objectstack/cli:test: AssertionError: the harness SIGKILLed the child — it was still alive at the ceiling. cap 180000 ms (RUN_TIMEOUT_MS, constant and load-independent by design); this child ran 1800
    

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

⚠️断言这一侧有一类例外,判据是断言在测什么,不是它是不是 AssertionError 断言的对象是产品行为(一个值、一个形状、一次拒收)⇒ 照上面读:真实的行为改变,去查,⛔ 不要重排掉;
断言的对象是这次实验自身的有效性前提(跑完的耗时、负载下的先后、任何只在时间预算内才成立的条件)⇒ 它跟超时是同一类,同样对负载敏感,重排一次是合法的判别手段。
识别是机械的:断言的消息或它比较的值本身点名了一段时长、一个时间戳、一个耗时计数。实测过的一对 —— AssertionError: SecurityPlugin.init() ran: expected false to be true 测的是产品行为(真回归);
AssertionError: this run took over a second, so second-precision stamps could have differed too: expected 1006 to be less than 1000 测的是实验前提:它守护的那条不变式当时是绿的,同一个 head 原样重排一次即成功。
穿着 AssertionError 外衣的时间测量,仍然是时间测量。(⛔ 这只改「怎么读一次红」,不改「哪些测试可以重排」——后者由别处管。)

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

历史信号:

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

分诊清单:

  1. 失败测试在本 PR 改动的包里 → 真回归,修 PR。
  2. 失败测试与本 PR 无关 → 看上面的「跨 PR 相同签名」;已有汇总 issue ⇒ flaky/环境问题实锤,去那张 issue 上谈,修好前重排只会再烧一轮全队列。
  3. 两者都不是 → 可能与同组 PR 语义冲突;等前面的 PR 落地或失败出队后再重排一次即可,不要连续重排。

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

@github-actions

Copy link
Copy Markdown
Contributor

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

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

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

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

    @objectstack/cli:test: FAIL integration test/run-dev-unbuilt-workspace.e2e.test.ts > the mirror direction: a reader that is never coming back > gives up and exits instead of waiting forever
    ↳ 失败原因: @objectstack/cli:test: AssertionError: the harness SIGKILLed the child — it was still alive at the ceiling. cap 180000 ms (RUN_TIMEOUT_MS, constant and load-independent by design); this child ran 1801
    

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

⚠️断言这一侧有一类例外,判据是断言在测什么,不是它是不是 AssertionError 断言的对象是产品行为(一个值、一个形状、一次拒收)⇒ 照上面读:真实的行为改变,去查,⛔ 不要重排掉;
断言的对象是这次实验自身的有效性前提(跑完的耗时、负载下的先后、任何只在时间预算内才成立的条件)⇒ 它跟超时是同一类,同样对负载敏感,重排一次是合法的判别手段。
识别是机械的:断言的消息或它比较的值本身点名了一段时长、一个时间戳、一个耗时计数。实测过的一对 —— AssertionError: SecurityPlugin.init() ran: expected false to be true 测的是产品行为(真回归);
AssertionError: this run took over a second, so second-precision stamps could have differed too: expected 1006 to be less than 1000 测的是实验前提:它守护的那条不变式当时是绿的,同一个 head 原样重排一次即成功。
穿着 AssertionError 外衣的时间测量,仍然是时间测量。(⛔ 这只改「怎么读一次红」,不改「哪些测试可以重排」——后者由别处管。)

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

历史信号:

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

分诊清单:

  1. 失败测试在本 PR 改动的包里 → 真回归,修 PR。
  2. 失败测试与本 PR 无关 → 看上面的「跨 PR 相同签名」;已有汇总 issue ⇒ flaky/环境问题实锤,去那张 issue 上谈,修好前重排只会再烧一轮全队列。
  3. 两者都不是 → 可能与同组 PR 语义冲突;等前面的 PR 落地或失败出队后再重排一次即可,不要连续重排。

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

Merged via the queue into main with commit f3ae441Sep 3, 2026
40 checks passed
@os-sam
os-sam deleted the claude/issue-14597-multi-package-scope-claim branch September 3, 2026 04:51
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentationImprovements or additions to documentationsize/sskip-changesetPR has no user-facing published change; bypasses the changeset gatetests

Projects

None yet

2 participants

@os-sam@claude
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Add copy buttons to all
 blocks\n(function() {\n function addCopyButtons() {\n document.querySelectorAll('pre code').forEach(function(codeBlock) {\n if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;\n codeBlock.parentElement.setAttribute('data-copy-added', 'true');\n \n var btn = document.createElement('button');\n btn.textContent = 'Copy';\n btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';\n btn.onmouseover = function() { this.style.opacity = '1'; };\n btn.onmouseout = function() { this.style.opacity = '0.7'; };\n btn.onclick = function() {\n navigator.clipboard.writeText(codeBlock.textContent).then(function() {\n btn.textContent = 'Copied!';\n setTimeout(function() { btn.textContent = 'Copy'; }, 1500);\n });\n };\n codeBlock.parentElement.style.position = 'relative';\n codeBlock.parentElement.appendChild(btn);\n });\n }\n \n addCopyButtons();\n \n // Re-run on dynamic content\n var observer = new MutationObserver(addCopyButtons);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Add Copy Buttons to Code Blocks");
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
Skip to content

fix(examples): app-multi-package's module is served WITH scope: 'project' — drop the "no scope key" claim - #14804

Merged
os-sam merged 1 commit into
mainfrom
claude/issue-14597-multi-package-scope-claim
Sep 3, 2026
Merged

fix(examples): app-multi-package's module is served WITH scope: 'project' — drop the "no scope key" claim#14804
os-sam merged 1 commit into
mainfrom
claude/issue-14597-multi-package-scope-claim

Conversation

@claude

@claudeclaudeBot commented Sep 3, 2026

Copy link
Copy Markdown
Contributor

Fixes#14597

The fixture documented a property its producer never emits. The triage made one question a precondition: does a genuinely scope-less served row exist on any path? It does — just not on this fixture's path, and not on the row the fixture claimed.

Measurement (read-only; all line numbers on origin/main8f9ef6f7c)

H1 — the artifact path materialises scope at compile time. CONFIRMED.
defineStack parses every packages[] entry's manifest through ManifestSchema (packages/spec/src/stack.zod.ts:233, ArtifactPackageEntrySchema), whose scope is .default('project') (packages/spec/src/kernel/manifest.zod.ts:311). Measured:

measurementresult
dist/objectstack.json after a real buildboth packages has_scope: true, scope: "project"
ManifestSchema.parse of the authored orders bodyhas_scope: false in, scope: "project" out
GET /api/v1/packages on a booted stackboth rows scope: "project", writable: false

So no package of a compiled artifact can be scope-less — the fixture's claim was not merely untrue here, it is unreachable from any examples/** app.

H2 — a raw manifest body CAN reach the registry unparsed. CONFIRMED; H1's "no served row is ever scope-less" is FALSIFIED.
registerApp (packages/objectql/src/engine.ts:4927) hands its argument to installPackage (:4956), which stores toRecordManifest(manifest) (packages/objectql/src/registry.ts:3956) — a key-by-key copy (:1229) that applies no defaults and runs no parse. Two production paths reach it without a ManifestSchema parse:

  • booted, read-only — marketplace install / offline file import / ledger rehydrate: manifestService.register(rawBody) at packages/cloud-connection/src/marketplace-install-local-plugin.ts:291 and :827, routed through packages/objectql/src/plugin.ts:457. This is the card's own parenthetical ("an installed marketplace package whose manifest bypasses defineStack?") and the answer is yes.
  • database base, writablePOST /api/v1/packages: const manifest = body.manifest || body (packages/runtime/src/domains/packages.ts:383) reaches protocol.installPackage (packages/metadata-protocol/src/protocol.ts:21188), which defaults version and derives namespace by hand and parses nothing (:21215). The code says so itself: "defineStack enforces it at compile time, but runtime-created packages never take that path".

Measured: SchemaRegistry.installPackage of the unparsed orders body yields a record with noscope key; of the parsed body, scope: "project".

H3 — the fixture's module would not have been the discriminator even if it were scope-less.
isWritablePackage (packages/metadata-protocol/src/package-writability.ts:77) reads engine.manifests FIRST, so scope is never consulted for a booted package. Measured verdicts:

rowin engine.manifestsscopeserver writable
scope-less booted (marketplace import)yesabsentfalse
scope-less database base (Studio)noabsenttrue
the fixture's moduleyes'project'false

The pair that separates the server verdict from any scope-only rule is rows 1 and 2 — and it is already pinned where it is true, in packages/runtime/src/domains/packages-writable-verdict.test.ts (Pins 2 + 4) and packages/metadata-protocol/src/get-meta-items-package-writable.test.ts. Nothing needed to move; the claim needed to come off the fixture.

What changed

  • examples/app-multi-package/src/packages/orders/index.ts — the second "load-bearing property" now states what the fixture delivers (the server's own writable: false verdict), plus a ⛔ note recording why no compiled artifact can carry a scope-less row, so the claim cannot be re-added.
  • examples/app-multi-package/README.md — same correction to the prose under the table.
  • packages/qa/dogfood/test/multi-package-artifact.dogfood.test.ts — the harness that already boots this fixture. Its comment repeated the claim; corrected, and a new pin asserts both served rows carry scope: 'project', on a real boot. That is the assertion the old claim could only ever have been checked by, and never was.

No changeset: comment-and-test only, from a private example and a private QA package — nothing publishes. skip-changeset applied at creation.

packages/spec/src/kernel/manifest.zod.ts is untouched, per the triage: the default is correct and load-bearing.

Verification — union run at bff31b601

  • 31 gate families, re-derived on the actual diff (node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack), all run: 29 green, 2 needed the workspace built and are green after it (check:dual-build-cjs-loads, check:type-check-debt — the latter's --re-measure reports 22 ledger entries, 755 raw errors, none above record). scripts/check-test-completeness.mjs returns its own exit-3 NOT MEASURED branch (it parses a test-run log CI supplies; no local log exists) — recorded as NOT MEASURED, not as a pass.
  • pnpm lint (repo-wide eslint . --no-inline-config) — green. No narrowing claimed.
  • pnpm --filter './examples/*' run typecheck — green; log confirms examples/app-multi-package typecheck$ tsc --noEmit ... Done (checked against the zero-match-exits-0 trap).
  • pnpm check:nul-bytes green, plus a manual control-character scan over the three edited files.
  • The new pin, on a real boot: pnpm --filter @objectstack/dogfood exec vitest run test/multi-package-artifact.dogfood.test.ts — 9 passed (was 8).
  • Reverse verification of the new pin. Committed first, then mutated the orders assertion to the OLD claim (toBeUndefined()) and re-ran: 1 failed | 8 passed, AssertionError: expected 'project' to be undefined — the card's claim, falsified mechanically on a booted stack. Mutation confirmed on disk both ways before the run (injected marker present = 1, removed text = 0, blob 1b93be01 vs HEAD blob b6a7adf5); restore leg proved by git diff HEAD empty and the worktree blob back to b6a7adf5, marker gone, pin present. Restore ran from an EXIT INT TERM trap using absolute paths.

Out of scope, filed unassigned

The same false attribution is repeated in five platform-side comments and in the objectui console's rationale. Neither is a file this card may touch (the triage fenced the objectui half explicitly, and packages/metadata-protocol/src/protocol.ts is held by open PR #14767, so that sweep cannot be atomic here anyway).

🤖 Generated with Claude Code

https://claude.ai/code/session_017RbbUMnxkUnWhE4j94v8FE


Generated by Claude Code

…ackage
`defineStack` parses every `packages[]` entry through `ManifestSchema`, whose
`scope` is `.default('project')`, so the fixture's module is served with
`scope: 'project'` and never was the scope-less row it documented. Measured:
`dist/objectstack.json` carries `has_scope: true, scope: "project"` on both
packages.
State what the fixture actually delivers instead — the server's own
`writable: false` verdict (ADR-0070 D2), which reads `engine.manifests` before
any scope — and pin `scope: 'project'` on both served rows in the dogfood
harness that already boots the fixture, so the corrected docs stay true.
The genuinely scope-less rows exist where a manifest reaches the registry
without a `ManifestSchema` parse (marketplace / offline import; a Studio base
via `POST /api/v1/packages`); that pair is already pinned in
`packages/runtime/src/domains/packages-writable-verdict.test.ts`.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017RbbUMnxkUnWhE4j94v8FE
@claudeclaudeBot added the skip-changeset PR has no user-facing published change; bypasses the changeset gate label Sep 3, 2026
@github-actions

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

Nothing in this diff resolved to a documentable surface (no symbol, route or SDK anchor derived from 0 changed package(s)), so this run has no opinion about the docs.

What this run could not see
  • a page that states a rule by its inputs shares no identifier with the emitter that implements the rule, so an emitter-only diff cannot list it — not on this run and not on any run. Measured on fix(driver-sql): emit varchar(maxLength) for a text field a declared index keys on #11430: content/docs/protocol/objectql/types.mdx documents the text-family column mapping by the ObjectQL type names it maps FROM (text / textarea / html) while the diff changed createColumn; it went unlisted, and it was the page that diff falsified, in four places. No shared token exists to detect this on, so a rule your change carries has to be re-read by hand in the pages that restate it.

Coarse fallback — 0 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): node scripts/docs-audit/affected-docs.mjs --json fc648a256e0968950a10bb0454a6c835d3b064d5packageMentionDocs.

@claude

claudeBot commented Sep 3, 2026

Copy link
Copy Markdown
ContributorAuthor

Landing (domain:spec seat, session_017RbbUMnxkUnWhE4j94v8FE, 2026-09-03T03:06Z) — flipping to ready and enabling auto-merge (SQUASH) in this stroke.

Pre-landing checks: seat ACCEPT on #14597 (comment 5519582946; clause ② no — no schema, accept/reject or public-surface change; manifest.zod.ts untouched); all 38 checks on bff31b601 complete — 31 success / 7 skipped / 0 red; trial merge git merge-tree --write-tree origin/main refs/pull/14804/head at origin/maine6ac0c6fd: clean; governed-surface predicate: 0 of 3 paths — ordinary queue landing; skip-changeset read back on the PR (private example + private QA package).


Generated by Claude Code

@os-sam
os-sam marked this pull request as ready for review September 3, 2026 03:05
@os-sam
os-sam enabled auto-merge September 3, 2026 03:06
@os-sam
os-sam added this pull request to the merge queueSep 3, 2026
@github-actions

Copy link
Copy Markdown
Contributor

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

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

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

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

    @objectstack/cli:test: FAIL integration test/run-dev-unbuilt-workspace.e2e.test.ts > the mirror direction: a reader that is never coming back > gives up and exits instead of waiting forever
    ↳ 失败原因: @objectstack/cli:test: AssertionError: the harness SIGKILLed the child — it was still alive at the ceiling. cap 180000 ms (RUN_TIMEOUT_MS, constant and load-independent by design); this child ran 1800
    

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

⚠️断言这一侧有一类例外,判据是断言在测什么,不是它是不是 AssertionError 断言的对象是产品行为(一个值、一个形状、一次拒收)⇒ 照上面读:真实的行为改变,去查,⛔ 不要重排掉;
断言的对象是这次实验自身的有效性前提(跑完的耗时、负载下的先后、任何只在时间预算内才成立的条件)⇒ 它跟超时是同一类,同样对负载敏感,重排一次是合法的判别手段。
识别是机械的:断言的消息或它比较的值本身点名了一段时长、一个时间戳、一个耗时计数。实测过的一对 —— AssertionError: SecurityPlugin.init() ran: expected false to be true 测的是产品行为(真回归);
AssertionError: this run took over a second, so second-precision stamps could have differed too: expected 1006 to be less than 1000 测的是实验前提:它守护的那条不变式当时是绿的,同一个 head 原样重排一次即成功。
穿着 AssertionError 外衣的时间测量,仍然是时间测量。(⛔ 这只改「怎么读一次红」,不改「哪些测试可以重排」——后者由别处管。)

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

历史信号:

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

分诊清单:

  1. 失败测试在本 PR 改动的包里 → 真回归,修 PR。
  2. 失败测试与本 PR 无关 → 看上面的「跨 PR 相同签名」;已有汇总 issue ⇒ flaky/环境问题实锤,去那张 issue 上谈,修好前重排只会再烧一轮全队列。
  3. 两者都不是 → 可能与同组 PR 语义冲突;等前面的 PR 落地或失败出队后再重排一次即可,不要连续重排。

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

@github-actions

Copy link
Copy Markdown
Contributor

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

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

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

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

    @objectstack/cli:test: FAIL integration test/run-dev-unbuilt-workspace.e2e.test.ts > the mirror direction: a reader that is never coming back > gives up and exits instead of waiting forever
    ↳ 失败原因: @objectstack/cli:test: AssertionError: the harness SIGKILLed the child — it was still alive at the ceiling. cap 180000 ms (RUN_TIMEOUT_MS, constant and load-independent by design); this child ran 1801
    

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

⚠️断言这一侧有一类例外,判据是断言在测什么,不是它是不是 AssertionError 断言的对象是产品行为(一个值、一个形状、一次拒收)⇒ 照上面读:真实的行为改变,去查,⛔ 不要重排掉;
断言的对象是这次实验自身的有效性前提(跑完的耗时、负载下的先后、任何只在时间预算内才成立的条件)⇒ 它跟超时是同一类,同样对负载敏感,重排一次是合法的判别手段。
识别是机械的:断言的消息或它比较的值本身点名了一段时长、一个时间戳、一个耗时计数。实测过的一对 —— AssertionError: SecurityPlugin.init() ran: expected false to be true 测的是产品行为(真回归);
AssertionError: this run took over a second, so second-precision stamps could have differed too: expected 1006 to be less than 1000 测的是实验前提:它守护的那条不变式当时是绿的,同一个 head 原样重排一次即成功。
穿着 AssertionError 外衣的时间测量,仍然是时间测量。(⛔ 这只改「怎么读一次红」,不改「哪些测试可以重排」——后者由别处管。)

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

历史信号:

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

分诊清单:

  1. 失败测试在本 PR 改动的包里 → 真回归,修 PR。
  2. 失败测试与本 PR 无关 → 看上面的「跨 PR 相同签名」;已有汇总 issue ⇒ flaky/环境问题实锤,去那张 issue 上谈,修好前重排只会再烧一轮全队列。
  3. 两者都不是 → 可能与同组 PR 语义冲突;等前面的 PR 落地或失败出队后再重排一次即可,不要连续重排。

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

Merged via the queue into main with commit f3ae441Sep 3, 2026
40 checks passed
@os-sam
os-sam deleted the claude/issue-14597-multi-package-scope-claim branch September 3, 2026 04:51
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentationImprovements or additions to documentationsize/sskip-changesetPR has no user-facing published change; bypasses the changeset gatetests

Projects

None yet

2 participants

@os-sam@claude
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Force GitHub README to respect dark mode\n(function() {\n var style = document.createElement('style');\n style.textContent = '\n .markdown-body {\n color-scheme: dark light;\n }\n .markdown-body pre { background: #161b22 !important; }\n .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; }\n .markdown-body table th, .markdown-body table td { border-color: #30363d !important; }\n .markdown-body img { background: #0d1117; }\n .markdown-body blockquote { border-left-color: #8b949e; }\n .markdown-body hr { border-color: #30363d; }\n ';\n document.head.appendChild(style);\n})();", "GitHub Dark Mode README Fix"); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

fix(examples): app-multi-package's module is served WITH scope: 'project' — drop the "no scope key" claim - #14804

Merged
os-sam merged 1 commit into
mainfrom
claude/issue-14597-multi-package-scope-claim
Sep 3, 2026
Merged

fix(examples): app-multi-package's module is served WITH scope: 'project' — drop the "no scope key" claim#14804
os-sam merged 1 commit into
mainfrom
claude/issue-14597-multi-package-scope-claim

Conversation

@claude

@claudeclaudeBot commented Sep 3, 2026

Copy link
Copy Markdown
Contributor

Fixes#14597

The fixture documented a property its producer never emits. The triage made one question a precondition: does a genuinely scope-less served row exist on any path? It does — just not on this fixture's path, and not on the row the fixture claimed.

Measurement (read-only; all line numbers on origin/main8f9ef6f7c)

H1 — the artifact path materialises scope at compile time. CONFIRMED.
defineStack parses every packages[] entry's manifest through ManifestSchema (packages/spec/src/stack.zod.ts:233, ArtifactPackageEntrySchema), whose scope is .default('project') (packages/spec/src/kernel/manifest.zod.ts:311). Measured:

measurementresult
dist/objectstack.json after a real buildboth packages has_scope: true, scope: "project"
ManifestSchema.parse of the authored orders bodyhas_scope: false in, scope: "project" out
GET /api/v1/packages on a booted stackboth rows scope: "project", writable: false

So no package of a compiled artifact can be scope-less — the fixture's claim was not merely untrue here, it is unreachable from any examples/** app.

H2 — a raw manifest body CAN reach the registry unparsed. CONFIRMED; H1's "no served row is ever scope-less" is FALSIFIED.
registerApp (packages/objectql/src/engine.ts:4927) hands its argument to installPackage (:4956), which stores toRecordManifest(manifest) (packages/objectql/src/registry.ts:3956) — a key-by-key copy (:1229) that applies no defaults and runs no parse. Two production paths reach it without a ManifestSchema parse:

  • booted, read-only — marketplace install / offline file import / ledger rehydrate: manifestService.register(rawBody) at packages/cloud-connection/src/marketplace-install-local-plugin.ts:291 and :827, routed through packages/objectql/src/plugin.ts:457. This is the card's own parenthetical ("an installed marketplace package whose manifest bypasses defineStack?") and the answer is yes.
  • database base, writablePOST /api/v1/packages: const manifest = body.manifest || body (packages/runtime/src/domains/packages.ts:383) reaches protocol.installPackage (packages/metadata-protocol/src/protocol.ts:21188), which defaults version and derives namespace by hand and parses nothing (:21215). The code says so itself: "defineStack enforces it at compile time, but runtime-created packages never take that path".

Measured: SchemaRegistry.installPackage of the unparsed orders body yields a record with noscope key; of the parsed body, scope: "project".

H3 — the fixture's module would not have been the discriminator even if it were scope-less.
isWritablePackage (packages/metadata-protocol/src/package-writability.ts:77) reads engine.manifests FIRST, so scope is never consulted for a booted package. Measured verdicts:

rowin engine.manifestsscopeserver writable
scope-less booted (marketplace import)yesabsentfalse
scope-less database base (Studio)noabsenttrue
the fixture's moduleyes'project'false

The pair that separates the server verdict from any scope-only rule is rows 1 and 2 — and it is already pinned where it is true, in packages/runtime/src/domains/packages-writable-verdict.test.ts (Pins 2 + 4) and packages/metadata-protocol/src/get-meta-items-package-writable.test.ts. Nothing needed to move; the claim needed to come off the fixture.

What changed

  • examples/app-multi-package/src/packages/orders/index.ts — the second "load-bearing property" now states what the fixture delivers (the server's own writable: false verdict), plus a ⛔ note recording why no compiled artifact can carry a scope-less row, so the claim cannot be re-added.
  • examples/app-multi-package/README.md — same correction to the prose under the table.
  • packages/qa/dogfood/test/multi-package-artifact.dogfood.test.ts — the harness that already boots this fixture. Its comment repeated the claim; corrected, and a new pin asserts both served rows carry scope: 'project', on a real boot. That is the assertion the old claim could only ever have been checked by, and never was.

No changeset: comment-and-test only, from a private example and a private QA package — nothing publishes. skip-changeset applied at creation.

packages/spec/src/kernel/manifest.zod.ts is untouched, per the triage: the default is correct and load-bearing.

Verification — union run at bff31b601

  • 31 gate families, re-derived on the actual diff (node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack), all run: 29 green, 2 needed the workspace built and are green after it (check:dual-build-cjs-loads, check:type-check-debt — the latter's --re-measure reports 22 ledger entries, 755 raw errors, none above record). scripts/check-test-completeness.mjs returns its own exit-3 NOT MEASURED branch (it parses a test-run log CI supplies; no local log exists) — recorded as NOT MEASURED, not as a pass.
  • pnpm lint (repo-wide eslint . --no-inline-config) — green. No narrowing claimed.
  • pnpm --filter './examples/*' run typecheck — green; log confirms examples/app-multi-package typecheck$ tsc --noEmit ... Done (checked against the zero-match-exits-0 trap).
  • pnpm check:nul-bytes green, plus a manual control-character scan over the three edited files.
  • The new pin, on a real boot: pnpm --filter @objectstack/dogfood exec vitest run test/multi-package-artifact.dogfood.test.ts — 9 passed (was 8).
  • Reverse verification of the new pin. Committed first, then mutated the orders assertion to the OLD claim (toBeUndefined()) and re-ran: 1 failed | 8 passed, AssertionError: expected 'project' to be undefined — the card's claim, falsified mechanically on a booted stack. Mutation confirmed on disk both ways before the run (injected marker present = 1, removed text = 0, blob 1b93be01 vs HEAD blob b6a7adf5); restore leg proved by git diff HEAD empty and the worktree blob back to b6a7adf5, marker gone, pin present. Restore ran from an EXIT INT TERM trap using absolute paths.

Out of scope, filed unassigned

The same false attribution is repeated in five platform-side comments and in the objectui console's rationale. Neither is a file this card may touch (the triage fenced the objectui half explicitly, and packages/metadata-protocol/src/protocol.ts is held by open PR #14767, so that sweep cannot be atomic here anyway).

🤖 Generated with Claude Code

https://claude.ai/code/session_017RbbUMnxkUnWhE4j94v8FE


Generated by Claude Code

…ackage
`defineStack` parses every `packages[]` entry through `ManifestSchema`, whose
`scope` is `.default('project')`, so the fixture's module is served with
`scope: 'project'` and never was the scope-less row it documented. Measured:
`dist/objectstack.json` carries `has_scope: true, scope: "project"` on both
packages.
State what the fixture actually delivers instead — the server's own
`writable: false` verdict (ADR-0070 D2), which reads `engine.manifests` before
any scope — and pin `scope: 'project'` on both served rows in the dogfood
harness that already boots the fixture, so the corrected docs stay true.
The genuinely scope-less rows exist where a manifest reaches the registry
without a `ManifestSchema` parse (marketplace / offline import; a Studio base
via `POST /api/v1/packages`); that pair is already pinned in
`packages/runtime/src/domains/packages-writable-verdict.test.ts`.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017RbbUMnxkUnWhE4j94v8FE
@claudeclaudeBot added the skip-changeset PR has no user-facing published change; bypasses the changeset gate label Sep 3, 2026
@github-actions

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

Nothing in this diff resolved to a documentable surface (no symbol, route or SDK anchor derived from 0 changed package(s)), so this run has no opinion about the docs.

What this run could not see
  • a page that states a rule by its inputs shares no identifier with the emitter that implements the rule, so an emitter-only diff cannot list it — not on this run and not on any run. Measured on fix(driver-sql): emit varchar(maxLength) for a text field a declared index keys on #11430: content/docs/protocol/objectql/types.mdx documents the text-family column mapping by the ObjectQL type names it maps FROM (text / textarea / html) while the diff changed createColumn; it went unlisted, and it was the page that diff falsified, in four places. No shared token exists to detect this on, so a rule your change carries has to be re-read by hand in the pages that restate it.

Coarse fallback — 0 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): node scripts/docs-audit/affected-docs.mjs --json fc648a256e0968950a10bb0454a6c835d3b064d5packageMentionDocs.

@claude

claudeBot commented Sep 3, 2026

Copy link
Copy Markdown
ContributorAuthor

Landing (domain:spec seat, session_017RbbUMnxkUnWhE4j94v8FE, 2026-09-03T03:06Z) — flipping to ready and enabling auto-merge (SQUASH) in this stroke.

Pre-landing checks: seat ACCEPT on #14597 (comment 5519582946; clause ② no — no schema, accept/reject or public-surface change; manifest.zod.ts untouched); all 38 checks on bff31b601 complete — 31 success / 7 skipped / 0 red; trial merge git merge-tree --write-tree origin/main refs/pull/14804/head at origin/maine6ac0c6fd: clean; governed-surface predicate: 0 of 3 paths — ordinary queue landing; skip-changeset read back on the PR (private example + private QA package).


Generated by Claude Code

@os-sam
os-sam marked this pull request as ready for review September 3, 2026 03:05
@os-sam
os-sam enabled auto-merge September 3, 2026 03:06
@os-sam
os-sam added this pull request to the merge queueSep 3, 2026
@github-actions

Copy link
Copy Markdown
Contributor

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

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

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

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

    @objectstack/cli:test: FAIL integration test/run-dev-unbuilt-workspace.e2e.test.ts > the mirror direction: a reader that is never coming back > gives up and exits instead of waiting forever
    ↳ 失败原因: @objectstack/cli:test: AssertionError: the harness SIGKILLed the child — it was still alive at the ceiling. cap 180000 ms (RUN_TIMEOUT_MS, constant and load-independent by design); this child ran 1800
    

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

⚠️断言这一侧有一类例外,判据是断言在测什么,不是它是不是 AssertionError 断言的对象是产品行为(一个值、一个形状、一次拒收)⇒ 照上面读:真实的行为改变,去查,⛔ 不要重排掉;
断言的对象是这次实验自身的有效性前提(跑完的耗时、负载下的先后、任何只在时间预算内才成立的条件)⇒ 它跟超时是同一类,同样对负载敏感,重排一次是合法的判别手段。
识别是机械的:断言的消息或它比较的值本身点名了一段时长、一个时间戳、一个耗时计数。实测过的一对 —— AssertionError: SecurityPlugin.init() ran: expected false to be true 测的是产品行为(真回归);
AssertionError: this run took over a second, so second-precision stamps could have differed too: expected 1006 to be less than 1000 测的是实验前提:它守护的那条不变式当时是绿的,同一个 head 原样重排一次即成功。
穿着 AssertionError 外衣的时间测量,仍然是时间测量。(⛔ 这只改「怎么读一次红」,不改「哪些测试可以重排」——后者由别处管。)

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

历史信号:

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

分诊清单:

  1. 失败测试在本 PR 改动的包里 → 真回归,修 PR。
  2. 失败测试与本 PR 无关 → 看上面的「跨 PR 相同签名」;已有汇总 issue ⇒ flaky/环境问题实锤,去那张 issue 上谈,修好前重排只会再烧一轮全队列。
  3. 两者都不是 → 可能与同组 PR 语义冲突;等前面的 PR 落地或失败出队后再重排一次即可,不要连续重排。

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

@github-actions

Copy link
Copy Markdown
Contributor

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

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

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

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

    @objectstack/cli:test: FAIL integration test/run-dev-unbuilt-workspace.e2e.test.ts > the mirror direction: a reader that is never coming back > gives up and exits instead of waiting forever
    ↳ 失败原因: @objectstack/cli:test: AssertionError: the harness SIGKILLed the child — it was still alive at the ceiling. cap 180000 ms (RUN_TIMEOUT_MS, constant and load-independent by design); this child ran 1801
    

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

⚠️断言这一侧有一类例外,判据是断言在测什么,不是它是不是 AssertionError 断言的对象是产品行为(一个值、一个形状、一次拒收)⇒ 照上面读:真实的行为改变,去查,⛔ 不要重排掉;
断言的对象是这次实验自身的有效性前提(跑完的耗时、负载下的先后、任何只在时间预算内才成立的条件)⇒ 它跟超时是同一类,同样对负载敏感,重排一次是合法的判别手段。
识别是机械的:断言的消息或它比较的值本身点名了一段时长、一个时间戳、一个耗时计数。实测过的一对 —— AssertionError: SecurityPlugin.init() ran: expected false to be true 测的是产品行为(真回归);
AssertionError: this run took over a second, so second-precision stamps could have differed too: expected 1006 to be less than 1000 测的是实验前提:它守护的那条不变式当时是绿的,同一个 head 原样重排一次即成功。
穿着 AssertionError 外衣的时间测量,仍然是时间测量。(⛔ 这只改「怎么读一次红」,不改「哪些测试可以重排」——后者由别处管。)

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

历史信号:

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

分诊清单:

  1. 失败测试在本 PR 改动的包里 → 真回归,修 PR。
  2. 失败测试与本 PR 无关 → 看上面的「跨 PR 相同签名」;已有汇总 issue ⇒ flaky/环境问题实锤,去那张 issue 上谈,修好前重排只会再烧一轮全队列。
  3. 两者都不是 → 可能与同组 PR 语义冲突;等前面的 PR 落地或失败出队后再重排一次即可,不要连续重排。

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

Merged via the queue into main with commit f3ae441Sep 3, 2026
40 checks passed
@os-sam
os-sam deleted the claude/issue-14597-multi-package-scope-claim branch September 3, 2026 04:51
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentationImprovements or additions to documentationsize/sskip-changesetPR has no user-facing published change; bypasses the changeset gatetests

Projects

None yet

2 participants

@os-sam@claude
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Highlight search terms from Google/DuckDuckGo/Bing referrer\n(function() {\n var ref = document.referrer;\n var terms = [];\n \n if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) {\n var url = new URL(ref);\n var q = url.searchParams.get('q') || url.searchParams.get('p');\n if (q) {\n terms = q.split(/\\s+/).filter(function(t) { return t.length > 2; });\n }\n }\n \n if (terms.length === 0) return;\n \n var style = document.createElement('style');\n style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }';\n document.head.appendChild(style);\n \n function highlight(node) {\n if (node.nodeType === 3) { // text node\n var text = node.textContent;\n var found = false;\n terms.forEach(function(term) {\n var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\') + ')', 'gi');\n if (regex.test(text)) {\n found = true;\n var frag = document.createDocumentFragment();\n var parts = text.split(regex);\n parts.forEach(function(part, i) {\n if (i % 2 === 0) {\n frag.appendChild(document.createTextNode(part));\n } else {\n var span = document.createElement('span');\n span.className = 'userscript-highlight';\n span.textContent = part;\n frag.appendChild(span);\n }\n });\n node.parentNode.replaceChild(frag, node);\n }\n });\n } else if (node.nodeType === 1 && node.childNodes) { // element\n var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT'];\n if (!skipTags.includes(node.tagName)) {\n Array.from(node.childNodes).forEach(highlight);\n }\n }\n }\n \n highlight(document.body);\n \n // Re-highlight on dynamic content\n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1 || node.nodeType === 3) highlight(node);\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Highlight Search Terms"); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

fix(examples): app-multi-package's module is served WITH scope: 'project' — drop the "no scope key" claim - #14804

Merged
os-sam merged 1 commit into
mainfrom
claude/issue-14597-multi-package-scope-claim
Sep 3, 2026
Merged

fix(examples): app-multi-package's module is served WITH scope: 'project' — drop the "no scope key" claim#14804
os-sam merged 1 commit into
mainfrom
claude/issue-14597-multi-package-scope-claim

Conversation

@claude

@claudeclaudeBot commented Sep 3, 2026

Copy link
Copy Markdown
Contributor

Fixes#14597

The fixture documented a property its producer never emits. The triage made one question a precondition: does a genuinely scope-less served row exist on any path? It does — just not on this fixture's path, and not on the row the fixture claimed.

Measurement (read-only; all line numbers on origin/main8f9ef6f7c)

H1 — the artifact path materialises scope at compile time. CONFIRMED.
defineStack parses every packages[] entry's manifest through ManifestSchema (packages/spec/src/stack.zod.ts:233, ArtifactPackageEntrySchema), whose scope is .default('project') (packages/spec/src/kernel/manifest.zod.ts:311). Measured:

measurementresult
dist/objectstack.json after a real buildboth packages has_scope: true, scope: "project"
ManifestSchema.parse of the authored orders bodyhas_scope: false in, scope: "project" out
GET /api/v1/packages on a booted stackboth rows scope: "project", writable: false

So no package of a compiled artifact can be scope-less — the fixture's claim was not merely untrue here, it is unreachable from any examples/** app.

H2 — a raw manifest body CAN reach the registry unparsed. CONFIRMED; H1's "no served row is ever scope-less" is FALSIFIED.
registerApp (packages/objectql/src/engine.ts:4927) hands its argument to installPackage (:4956), which stores toRecordManifest(manifest) (packages/objectql/src/registry.ts:3956) — a key-by-key copy (:1229) that applies no defaults and runs no parse. Two production paths reach it without a ManifestSchema parse:

  • booted, read-only — marketplace install / offline file import / ledger rehydrate: manifestService.register(rawBody) at packages/cloud-connection/src/marketplace-install-local-plugin.ts:291 and :827, routed through packages/objectql/src/plugin.ts:457. This is the card's own parenthetical ("an installed marketplace package whose manifest bypasses defineStack?") and the answer is yes.
  • database base, writablePOST /api/v1/packages: const manifest = body.manifest || body (packages/runtime/src/domains/packages.ts:383) reaches protocol.installPackage (packages/metadata-protocol/src/protocol.ts:21188), which defaults version and derives namespace by hand and parses nothing (:21215). The code says so itself: "defineStack enforces it at compile time, but runtime-created packages never take that path".

Measured: SchemaRegistry.installPackage of the unparsed orders body yields a record with noscope key; of the parsed body, scope: "project".

H3 — the fixture's module would not have been the discriminator even if it were scope-less.
isWritablePackage (packages/metadata-protocol/src/package-writability.ts:77) reads engine.manifests FIRST, so scope is never consulted for a booted package. Measured verdicts:

rowin engine.manifestsscopeserver writable
scope-less booted (marketplace import)yesabsentfalse
scope-less database base (Studio)noabsenttrue
the fixture's moduleyes'project'false

The pair that separates the server verdict from any scope-only rule is rows 1 and 2 — and it is already pinned where it is true, in packages/runtime/src/domains/packages-writable-verdict.test.ts (Pins 2 + 4) and packages/metadata-protocol/src/get-meta-items-package-writable.test.ts. Nothing needed to move; the claim needed to come off the fixture.

What changed

  • examples/app-multi-package/src/packages/orders/index.ts — the second "load-bearing property" now states what the fixture delivers (the server's own writable: false verdict), plus a ⛔ note recording why no compiled artifact can carry a scope-less row, so the claim cannot be re-added.
  • examples/app-multi-package/README.md — same correction to the prose under the table.
  • packages/qa/dogfood/test/multi-package-artifact.dogfood.test.ts — the harness that already boots this fixture. Its comment repeated the claim; corrected, and a new pin asserts both served rows carry scope: 'project', on a real boot. That is the assertion the old claim could only ever have been checked by, and never was.

No changeset: comment-and-test only, from a private example and a private QA package — nothing publishes. skip-changeset applied at creation.

packages/spec/src/kernel/manifest.zod.ts is untouched, per the triage: the default is correct and load-bearing.

Verification — union run at bff31b601

  • 31 gate families, re-derived on the actual diff (node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack), all run: 29 green, 2 needed the workspace built and are green after it (check:dual-build-cjs-loads, check:type-check-debt — the latter's --re-measure reports 22 ledger entries, 755 raw errors, none above record). scripts/check-test-completeness.mjs returns its own exit-3 NOT MEASURED branch (it parses a test-run log CI supplies; no local log exists) — recorded as NOT MEASURED, not as a pass.
  • pnpm lint (repo-wide eslint . --no-inline-config) — green. No narrowing claimed.
  • pnpm --filter './examples/*' run typecheck — green; log confirms examples/app-multi-package typecheck$ tsc --noEmit ... Done (checked against the zero-match-exits-0 trap).
  • pnpm check:nul-bytes green, plus a manual control-character scan over the three edited files.
  • The new pin, on a real boot: pnpm --filter @objectstack/dogfood exec vitest run test/multi-package-artifact.dogfood.test.ts — 9 passed (was 8).
  • Reverse verification of the new pin. Committed first, then mutated the orders assertion to the OLD claim (toBeUndefined()) and re-ran: 1 failed | 8 passed, AssertionError: expected 'project' to be undefined — the card's claim, falsified mechanically on a booted stack. Mutation confirmed on disk both ways before the run (injected marker present = 1, removed text = 0, blob 1b93be01 vs HEAD blob b6a7adf5); restore leg proved by git diff HEAD empty and the worktree blob back to b6a7adf5, marker gone, pin present. Restore ran from an EXIT INT TERM trap using absolute paths.

Out of scope, filed unassigned

The same false attribution is repeated in five platform-side comments and in the objectui console's rationale. Neither is a file this card may touch (the triage fenced the objectui half explicitly, and packages/metadata-protocol/src/protocol.ts is held by open PR #14767, so that sweep cannot be atomic here anyway).

🤖 Generated with Claude Code

https://claude.ai/code/session_017RbbUMnxkUnWhE4j94v8FE


Generated by Claude Code

…ackage
`defineStack` parses every `packages[]` entry through `ManifestSchema`, whose
`scope` is `.default('project')`, so the fixture's module is served with
`scope: 'project'` and never was the scope-less row it documented. Measured:
`dist/objectstack.json` carries `has_scope: true, scope: "project"` on both
packages.
State what the fixture actually delivers instead — the server's own
`writable: false` verdict (ADR-0070 D2), which reads `engine.manifests` before
any scope — and pin `scope: 'project'` on both served rows in the dogfood
harness that already boots the fixture, so the corrected docs stay true.
The genuinely scope-less rows exist where a manifest reaches the registry
without a `ManifestSchema` parse (marketplace / offline import; a Studio base
via `POST /api/v1/packages`); that pair is already pinned in
`packages/runtime/src/domains/packages-writable-verdict.test.ts`.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017RbbUMnxkUnWhE4j94v8FE
@claudeclaudeBot added the skip-changeset PR has no user-facing published change; bypasses the changeset gate label Sep 3, 2026
@github-actions

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

Nothing in this diff resolved to a documentable surface (no symbol, route or SDK anchor derived from 0 changed package(s)), so this run has no opinion about the docs.

What this run could not see
  • a page that states a rule by its inputs shares no identifier with the emitter that implements the rule, so an emitter-only diff cannot list it — not on this run and not on any run. Measured on fix(driver-sql): emit varchar(maxLength) for a text field a declared index keys on #11430: content/docs/protocol/objectql/types.mdx documents the text-family column mapping by the ObjectQL type names it maps FROM (text / textarea / html) while the diff changed createColumn; it went unlisted, and it was the page that diff falsified, in four places. No shared token exists to detect this on, so a rule your change carries has to be re-read by hand in the pages that restate it.

Coarse fallback — 0 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): node scripts/docs-audit/affected-docs.mjs --json fc648a256e0968950a10bb0454a6c835d3b064d5packageMentionDocs.

@claude

claudeBot commented Sep 3, 2026

Copy link
Copy Markdown
ContributorAuthor

Landing (domain:spec seat, session_017RbbUMnxkUnWhE4j94v8FE, 2026-09-03T03:06Z) — flipping to ready and enabling auto-merge (SQUASH) in this stroke.

Pre-landing checks: seat ACCEPT on #14597 (comment 5519582946; clause ② no — no schema, accept/reject or public-surface change; manifest.zod.ts untouched); all 38 checks on bff31b601 complete — 31 success / 7 skipped / 0 red; trial merge git merge-tree --write-tree origin/main refs/pull/14804/head at origin/maine6ac0c6fd: clean; governed-surface predicate: 0 of 3 paths — ordinary queue landing; skip-changeset read back on the PR (private example + private QA package).


Generated by Claude Code

@os-sam
os-sam marked this pull request as ready for review September 3, 2026 03:05
@os-sam
os-sam enabled auto-merge September 3, 2026 03:06
@os-sam
os-sam added this pull request to the merge queueSep 3, 2026
@github-actions

Copy link
Copy Markdown
Contributor

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

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

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

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

    @objectstack/cli:test: FAIL integration test/run-dev-unbuilt-workspace.e2e.test.ts > the mirror direction: a reader that is never coming back > gives up and exits instead of waiting forever
    ↳ 失败原因: @objectstack/cli:test: AssertionError: the harness SIGKILLed the child — it was still alive at the ceiling. cap 180000 ms (RUN_TIMEOUT_MS, constant and load-independent by design); this child ran 1800
    

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

⚠️断言这一侧有一类例外,判据是断言在测什么,不是它是不是 AssertionError 断言的对象是产品行为(一个值、一个形状、一次拒收)⇒ 照上面读:真实的行为改变,去查,⛔ 不要重排掉;
断言的对象是这次实验自身的有效性前提(跑完的耗时、负载下的先后、任何只在时间预算内才成立的条件)⇒ 它跟超时是同一类,同样对负载敏感,重排一次是合法的判别手段。
识别是机械的:断言的消息或它比较的值本身点名了一段时长、一个时间戳、一个耗时计数。实测过的一对 —— AssertionError: SecurityPlugin.init() ran: expected false to be true 测的是产品行为(真回归);
AssertionError: this run took over a second, so second-precision stamps could have differed too: expected 1006 to be less than 1000 测的是实验前提:它守护的那条不变式当时是绿的,同一个 head 原样重排一次即成功。
穿着 AssertionError 外衣的时间测量,仍然是时间测量。(⛔ 这只改「怎么读一次红」,不改「哪些测试可以重排」——后者由别处管。)

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

历史信号:

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

分诊清单:

  1. 失败测试在本 PR 改动的包里 → 真回归,修 PR。
  2. 失败测试与本 PR 无关 → 看上面的「跨 PR 相同签名」;已有汇总 issue ⇒ flaky/环境问题实锤,去那张 issue 上谈,修好前重排只会再烧一轮全队列。
  3. 两者都不是 → 可能与同组 PR 语义冲突;等前面的 PR 落地或失败出队后再重排一次即可,不要连续重排。

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

@github-actions

Copy link
Copy Markdown
Contributor

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

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

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

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

    @objectstack/cli:test: FAIL integration test/run-dev-unbuilt-workspace.e2e.test.ts > the mirror direction: a reader that is never coming back > gives up and exits instead of waiting forever
    ↳ 失败原因: @objectstack/cli:test: AssertionError: the harness SIGKILLed the child — it was still alive at the ceiling. cap 180000 ms (RUN_TIMEOUT_MS, constant and load-independent by design); this child ran 1801
    

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

⚠️断言这一侧有一类例外,判据是断言在测什么,不是它是不是 AssertionError 断言的对象是产品行为(一个值、一个形状、一次拒收)⇒ 照上面读:真实的行为改变,去查,⛔ 不要重排掉;
断言的对象是这次实验自身的有效性前提(跑完的耗时、负载下的先后、任何只在时间预算内才成立的条件)⇒ 它跟超时是同一类,同样对负载敏感,重排一次是合法的判别手段。
识别是机械的:断言的消息或它比较的值本身点名了一段时长、一个时间戳、一个耗时计数。实测过的一对 —— AssertionError: SecurityPlugin.init() ran: expected false to be true 测的是产品行为(真回归);
AssertionError: this run took over a second, so second-precision stamps could have differed too: expected 1006 to be less than 1000 测的是实验前提:它守护的那条不变式当时是绿的,同一个 head 原样重排一次即成功。
穿着 AssertionError 外衣的时间测量,仍然是时间测量。(⛔ 这只改「怎么读一次红」,不改「哪些测试可以重排」——后者由别处管。)

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

历史信号:

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

分诊清单:

  1. 失败测试在本 PR 改动的包里 → 真回归,修 PR。
  2. 失败测试与本 PR 无关 → 看上面的「跨 PR 相同签名」;已有汇总 issue ⇒ flaky/环境问题实锤,去那张 issue 上谈,修好前重排只会再烧一轮全队列。
  3. 两者都不是 → 可能与同组 PR 语义冲突;等前面的 PR 落地或失败出队后再重排一次即可,不要连续重排。

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

Merged via the queue into main with commit f3ae441Sep 3, 2026
40 checks passed
@os-sam
os-sam deleted the claude/issue-14597-multi-package-scope-claim branch September 3, 2026 04:51
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentationImprovements or additions to documentationsize/sskip-changesetPR has no user-facing published change; bypasses the changeset gatetests

Projects

None yet

2 participants

@os-sam@claude
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Strip utm_, fbclid, gclid, etc. from all links on page\n(function() {\n var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content',\n 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid',\n 'ref', 'ref_src', 'source', 'medium', 'campaign'];\n \n function cleanUrl(url) {\n try {\n var u = new URL(url, window.location.origin);\n var changed = false;\n trackingParams.forEach(function(p) {\n if (u.searchParams.has(p)) {\n u.searchParams.delete(p);\n changed = true;\n }\n });\n return changed ? u.toString() : url;\n } catch (e) {\n return url;\n }\n }\n \n function cleanLinks() {\n document.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n \n cleanLinks();\n \n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1) {\n if (node.tagName === 'A') cleanLinks();\n node.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Remove Tracking Parameters from Links"); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + '
Skip to content

fix(examples): app-multi-package's module is served WITH scope: 'project' — drop the "no scope key" claim - #14804

Merged
os-sam merged 1 commit into
mainfrom
claude/issue-14597-multi-package-scope-claim
Sep 3, 2026
Merged

fix(examples): app-multi-package's module is served WITH scope: 'project' — drop the "no scope key" claim#14804
os-sam merged 1 commit into
mainfrom
claude/issue-14597-multi-package-scope-claim

Conversation

@claude

@claudeclaudeBot commented Sep 3, 2026

Copy link
Copy Markdown
Contributor

Fixes#14597

The fixture documented a property its producer never emits. The triage made one question a precondition: does a genuinely scope-less served row exist on any path? It does — just not on this fixture's path, and not on the row the fixture claimed.

Measurement (read-only; all line numbers on origin/main8f9ef6f7c)

H1 — the artifact path materialises scope at compile time. CONFIRMED.
defineStack parses every packages[] entry's manifest through ManifestSchema (packages/spec/src/stack.zod.ts:233, ArtifactPackageEntrySchema), whose scope is .default('project') (packages/spec/src/kernel/manifest.zod.ts:311). Measured:

measurementresult
dist/objectstack.json after a real buildboth packages has_scope: true, scope: "project"
ManifestSchema.parse of the authored orders bodyhas_scope: false in, scope: "project" out
GET /api/v1/packages on a booted stackboth rows scope: "project", writable: false

So no package of a compiled artifact can be scope-less — the fixture's claim was not merely untrue here, it is unreachable from any examples/** app.

H2 — a raw manifest body CAN reach the registry unparsed. CONFIRMED; H1's "no served row is ever scope-less" is FALSIFIED.
registerApp (packages/objectql/src/engine.ts:4927) hands its argument to installPackage (:4956), which stores toRecordManifest(manifest) (packages/objectql/src/registry.ts:3956) — a key-by-key copy (:1229) that applies no defaults and runs no parse. Two production paths reach it without a ManifestSchema parse:

  • booted, read-only — marketplace install / offline file import / ledger rehydrate: manifestService.register(rawBody) at packages/cloud-connection/src/marketplace-install-local-plugin.ts:291 and :827, routed through packages/objectql/src/plugin.ts:457. This is the card's own parenthetical ("an installed marketplace package whose manifest bypasses defineStack?") and the answer is yes.
  • database base, writablePOST /api/v1/packages: const manifest = body.manifest || body (packages/runtime/src/domains/packages.ts:383) reaches protocol.installPackage (packages/metadata-protocol/src/protocol.ts:21188), which defaults version and derives namespace by hand and parses nothing (:21215). The code says so itself: "defineStack enforces it at compile time, but runtime-created packages never take that path".

Measured: SchemaRegistry.installPackage of the unparsed orders body yields a record with noscope key; of the parsed body, scope: "project".

H3 — the fixture's module would not have been the discriminator even if it were scope-less.
isWritablePackage (packages/metadata-protocol/src/package-writability.ts:77) reads engine.manifests FIRST, so scope is never consulted for a booted package. Measured verdicts:

rowin engine.manifestsscopeserver writable
scope-less booted (marketplace import)yesabsentfalse
scope-less database base (Studio)noabsenttrue
the fixture's moduleyes'project'false

The pair that separates the server verdict from any scope-only rule is rows 1 and 2 — and it is already pinned where it is true, in packages/runtime/src/domains/packages-writable-verdict.test.ts (Pins 2 + 4) and packages/metadata-protocol/src/get-meta-items-package-writable.test.ts. Nothing needed to move; the claim needed to come off the fixture.

What changed

  • examples/app-multi-package/src/packages/orders/index.ts — the second "load-bearing property" now states what the fixture delivers (the server's own writable: false verdict), plus a ⛔ note recording why no compiled artifact can carry a scope-less row, so the claim cannot be re-added.
  • examples/app-multi-package/README.md — same correction to the prose under the table.
  • packages/qa/dogfood/test/multi-package-artifact.dogfood.test.ts — the harness that already boots this fixture. Its comment repeated the claim; corrected, and a new pin asserts both served rows carry scope: 'project', on a real boot. That is the assertion the old claim could only ever have been checked by, and never was.

No changeset: comment-and-test only, from a private example and a private QA package — nothing publishes. skip-changeset applied at creation.

packages/spec/src/kernel/manifest.zod.ts is untouched, per the triage: the default is correct and load-bearing.

Verification — union run at bff31b601

  • 31 gate families, re-derived on the actual diff (node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack), all run: 29 green, 2 needed the workspace built and are green after it (check:dual-build-cjs-loads, check:type-check-debt — the latter's --re-measure reports 22 ledger entries, 755 raw errors, none above record). scripts/check-test-completeness.mjs returns its own exit-3 NOT MEASURED branch (it parses a test-run log CI supplies; no local log exists) — recorded as NOT MEASURED, not as a pass.
  • pnpm lint (repo-wide eslint . --no-inline-config) — green. No narrowing claimed.
  • pnpm --filter './examples/*' run typecheck — green; log confirms examples/app-multi-package typecheck$ tsc --noEmit ... Done (checked against the zero-match-exits-0 trap).
  • pnpm check:nul-bytes green, plus a manual control-character scan over the three edited files.
  • The new pin, on a real boot: pnpm --filter @objectstack/dogfood exec vitest run test/multi-package-artifact.dogfood.test.ts — 9 passed (was 8).
  • Reverse verification of the new pin. Committed first, then mutated the orders assertion to the OLD claim (toBeUndefined()) and re-ran: 1 failed | 8 passed, AssertionError: expected 'project' to be undefined — the card's claim, falsified mechanically on a booted stack. Mutation confirmed on disk both ways before the run (injected marker present = 1, removed text = 0, blob 1b93be01 vs HEAD blob b6a7adf5); restore leg proved by git diff HEAD empty and the worktree blob back to b6a7adf5, marker gone, pin present. Restore ran from an EXIT INT TERM trap using absolute paths.

Out of scope, filed unassigned

The same false attribution is repeated in five platform-side comments and in the objectui console's rationale. Neither is a file this card may touch (the triage fenced the objectui half explicitly, and packages/metadata-protocol/src/protocol.ts is held by open PR #14767, so that sweep cannot be atomic here anyway).

🤖 Generated with Claude Code

https://claude.ai/code/session_017RbbUMnxkUnWhE4j94v8FE


Generated by Claude Code

…ackage
`defineStack` parses every `packages[]` entry through `ManifestSchema`, whose
`scope` is `.default('project')`, so the fixture's module is served with
`scope: 'project'` and never was the scope-less row it documented. Measured:
`dist/objectstack.json` carries `has_scope: true, scope: "project"` on both
packages.
State what the fixture actually delivers instead — the server's own
`writable: false` verdict (ADR-0070 D2), which reads `engine.manifests` before
any scope — and pin `scope: 'project'` on both served rows in the dogfood
harness that already boots the fixture, so the corrected docs stay true.
The genuinely scope-less rows exist where a manifest reaches the registry
without a `ManifestSchema` parse (marketplace / offline import; a Studio base
via `POST /api/v1/packages`); that pair is already pinned in
`packages/runtime/src/domains/packages-writable-verdict.test.ts`.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017RbbUMnxkUnWhE4j94v8FE
@claudeclaudeBot added the skip-changeset PR has no user-facing published change; bypasses the changeset gate label Sep 3, 2026
@github-actions

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

Nothing in this diff resolved to a documentable surface (no symbol, route or SDK anchor derived from 0 changed package(s)), so this run has no opinion about the docs.

What this run could not see
  • a page that states a rule by its inputs shares no identifier with the emitter that implements the rule, so an emitter-only diff cannot list it — not on this run and not on any run. Measured on fix(driver-sql): emit varchar(maxLength) for a text field a declared index keys on #11430: content/docs/protocol/objectql/types.mdx documents the text-family column mapping by the ObjectQL type names it maps FROM (text / textarea / html) while the diff changed createColumn; it went unlisted, and it was the page that diff falsified, in four places. No shared token exists to detect this on, so a rule your change carries has to be re-read by hand in the pages that restate it.

Coarse fallback — 0 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): node scripts/docs-audit/affected-docs.mjs --json fc648a256e0968950a10bb0454a6c835d3b064d5packageMentionDocs.

@claude

claudeBot commented Sep 3, 2026

Copy link
Copy Markdown
ContributorAuthor

Landing (domain:spec seat, session_017RbbUMnxkUnWhE4j94v8FE, 2026-09-03T03:06Z) — flipping to ready and enabling auto-merge (SQUASH) in this stroke.

Pre-landing checks: seat ACCEPT on #14597 (comment 5519582946; clause ② no — no schema, accept/reject or public-surface change; manifest.zod.ts untouched); all 38 checks on bff31b601 complete — 31 success / 7 skipped / 0 red; trial merge git merge-tree --write-tree origin/main refs/pull/14804/head at origin/maine6ac0c6fd: clean; governed-surface predicate: 0 of 3 paths — ordinary queue landing; skip-changeset read back on the PR (private example + private QA package).


Generated by Claude Code

@os-sam
os-sam marked this pull request as ready for review September 3, 2026 03:05
@os-sam
os-sam enabled auto-merge September 3, 2026 03:06
@os-sam
os-sam added this pull request to the merge queueSep 3, 2026
@github-actions

Copy link
Copy Markdown
Contributor

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

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

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

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

    @objectstack/cli:test: FAIL integration test/run-dev-unbuilt-workspace.e2e.test.ts > the mirror direction: a reader that is never coming back > gives up and exits instead of waiting forever
    ↳ 失败原因: @objectstack/cli:test: AssertionError: the harness SIGKILLed the child — it was still alive at the ceiling. cap 180000 ms (RUN_TIMEOUT_MS, constant and load-independent by design); this child ran 1800
    

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

⚠️断言这一侧有一类例外,判据是断言在测什么,不是它是不是 AssertionError 断言的对象是产品行为(一个值、一个形状、一次拒收)⇒ 照上面读:真实的行为改变,去查,⛔ 不要重排掉;
断言的对象是这次实验自身的有效性前提(跑完的耗时、负载下的先后、任何只在时间预算内才成立的条件)⇒ 它跟超时是同一类,同样对负载敏感,重排一次是合法的判别手段。
识别是机械的:断言的消息或它比较的值本身点名了一段时长、一个时间戳、一个耗时计数。实测过的一对 —— AssertionError: SecurityPlugin.init() ran: expected false to be true 测的是产品行为(真回归);
AssertionError: this run took over a second, so second-precision stamps could have differed too: expected 1006 to be less than 1000 测的是实验前提:它守护的那条不变式当时是绿的,同一个 head 原样重排一次即成功。
穿着 AssertionError 外衣的时间测量,仍然是时间测量。(⛔ 这只改「怎么读一次红」,不改「哪些测试可以重排」——后者由别处管。)

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

历史信号:

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

分诊清单:

  1. 失败测试在本 PR 改动的包里 → 真回归,修 PR。
  2. 失败测试与本 PR 无关 → 看上面的「跨 PR 相同签名」;已有汇总 issue ⇒ flaky/环境问题实锤,去那张 issue 上谈,修好前重排只会再烧一轮全队列。
  3. 两者都不是 → 可能与同组 PR 语义冲突;等前面的 PR 落地或失败出队后再重排一次即可,不要连续重排。

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

@github-actions

Copy link
Copy Markdown
Contributor

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

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

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

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

    @objectstack/cli:test: FAIL integration test/run-dev-unbuilt-workspace.e2e.test.ts > the mirror direction: a reader that is never coming back > gives up and exits instead of waiting forever
    ↳ 失败原因: @objectstack/cli:test: AssertionError: the harness SIGKILLed the child — it was still alive at the ceiling. cap 180000 ms (RUN_TIMEOUT_MS, constant and load-independent by design); this child ran 1801
    

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

⚠️断言这一侧有一类例外,判据是断言在测什么,不是它是不是 AssertionError 断言的对象是产品行为(一个值、一个形状、一次拒收)⇒ 照上面读:真实的行为改变,去查,⛔ 不要重排掉;
断言的对象是这次实验自身的有效性前提(跑完的耗时、负载下的先后、任何只在时间预算内才成立的条件)⇒ 它跟超时是同一类,同样对负载敏感,重排一次是合法的判别手段。
识别是机械的:断言的消息或它比较的值本身点名了一段时长、一个时间戳、一个耗时计数。实测过的一对 —— AssertionError: SecurityPlugin.init() ran: expected false to be true 测的是产品行为(真回归);
AssertionError: this run took over a second, so second-precision stamps could have differed too: expected 1006 to be less than 1000 测的是实验前提:它守护的那条不变式当时是绿的,同一个 head 原样重排一次即成功。
穿着 AssertionError 外衣的时间测量,仍然是时间测量。(⛔ 这只改「怎么读一次红」,不改「哪些测试可以重排」——后者由别处管。)

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

历史信号:

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

分诊清单:

  1. 失败测试在本 PR 改动的包里 → 真回归,修 PR。
  2. 失败测试与本 PR 无关 → 看上面的「跨 PR 相同签名」;已有汇总 issue ⇒ flaky/环境问题实锤,去那张 issue 上谈,修好前重排只会再烧一轮全队列。
  3. 两者都不是 → 可能与同组 PR 语义冲突;等前面的 PR 落地或失败出队后再重排一次即可,不要连续重排。

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

Merged via the queue into main with commit f3ae441Sep 3, 2026
40 checks passed
@os-sam
os-sam deleted the claude/issue-14597-multi-package-scope-claim branch September 3, 2026 04:51
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentationImprovements or additions to documentationsize/sskip-changesetPR has no user-facing published change; bypasses the changeset gatetests

Projects

None yet

2 participants

@os-sam@claude
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Auto-enable theater mode on YouTube\n(function() {\n function tryTheater() {\n var btn = document.querySelector('button[aria-label=\"Theater mode\"], ytd-player #player button[title=\"Theater mode\"]');\n if (btn && !btn.classList.contains('activated')) {\n btn.click();\n }\n }\n \n // Try immediately\n tryTheater();\n \n // Try after navigation (SPA)\n var lastUrl = location.href;\n setInterval(function() {\n if (location.href !== lastUrl) {\n lastUrl = location.href;\n setTimeout(tryTheater, 500);\n }\n }, 1000);\n \n // Also try on player load\n var observer = new MutationObserver(tryTheater);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "YouTube Theater Mode Default"); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

fix(examples): app-multi-package's module is served WITH scope: 'project' — drop the "no scope key" claim - #14804

Merged
os-sam merged 1 commit into
mainfrom
claude/issue-14597-multi-package-scope-claim
Sep 3, 2026
Merged

fix(examples): app-multi-package's module is served WITH scope: 'project' — drop the "no scope key" claim#14804
os-sam merged 1 commit into
mainfrom
claude/issue-14597-multi-package-scope-claim

Conversation

@claude

@claudeclaudeBot commented Sep 3, 2026

Copy link
Copy Markdown
Contributor

Fixes#14597

The fixture documented a property its producer never emits. The triage made one question a precondition: does a genuinely scope-less served row exist on any path? It does — just not on this fixture's path, and not on the row the fixture claimed.

Measurement (read-only; all line numbers on origin/main8f9ef6f7c)

H1 — the artifact path materialises scope at compile time. CONFIRMED.
defineStack parses every packages[] entry's manifest through ManifestSchema (packages/spec/src/stack.zod.ts:233, ArtifactPackageEntrySchema), whose scope is .default('project') (packages/spec/src/kernel/manifest.zod.ts:311). Measured:

measurementresult
dist/objectstack.json after a real buildboth packages has_scope: true, scope: "project"
ManifestSchema.parse of the authored orders bodyhas_scope: false in, scope: "project" out
GET /api/v1/packages on a booted stackboth rows scope: "project", writable: false

So no package of a compiled artifact can be scope-less — the fixture's claim was not merely untrue here, it is unreachable from any examples/** app.

H2 — a raw manifest body CAN reach the registry unparsed. CONFIRMED; H1's "no served row is ever scope-less" is FALSIFIED.
registerApp (packages/objectql/src/engine.ts:4927) hands its argument to installPackage (:4956), which stores toRecordManifest(manifest) (packages/objectql/src/registry.ts:3956) — a key-by-key copy (:1229) that applies no defaults and runs no parse. Two production paths reach it without a ManifestSchema parse:

  • booted, read-only — marketplace install / offline file import / ledger rehydrate: manifestService.register(rawBody) at packages/cloud-connection/src/marketplace-install-local-plugin.ts:291 and :827, routed through packages/objectql/src/plugin.ts:457. This is the card's own parenthetical ("an installed marketplace package whose manifest bypasses defineStack?") and the answer is yes.
  • database base, writablePOST /api/v1/packages: const manifest = body.manifest || body (packages/runtime/src/domains/packages.ts:383) reaches protocol.installPackage (packages/metadata-protocol/src/protocol.ts:21188), which defaults version and derives namespace by hand and parses nothing (:21215). The code says so itself: "defineStack enforces it at compile time, but runtime-created packages never take that path".

Measured: SchemaRegistry.installPackage of the unparsed orders body yields a record with noscope key; of the parsed body, scope: "project".

H3 — the fixture's module would not have been the discriminator even if it were scope-less.
isWritablePackage (packages/metadata-protocol/src/package-writability.ts:77) reads engine.manifests FIRST, so scope is never consulted for a booted package. Measured verdicts:

rowin engine.manifestsscopeserver writable
scope-less booted (marketplace import)yesabsentfalse
scope-less database base (Studio)noabsenttrue
the fixture's moduleyes'project'false

The pair that separates the server verdict from any scope-only rule is rows 1 and 2 — and it is already pinned where it is true, in packages/runtime/src/domains/packages-writable-verdict.test.ts (Pins 2 + 4) and packages/metadata-protocol/src/get-meta-items-package-writable.test.ts. Nothing needed to move; the claim needed to come off the fixture.

What changed

  • examples/app-multi-package/src/packages/orders/index.ts — the second "load-bearing property" now states what the fixture delivers (the server's own writable: false verdict), plus a ⛔ note recording why no compiled artifact can carry a scope-less row, so the claim cannot be re-added.
  • examples/app-multi-package/README.md — same correction to the prose under the table.
  • packages/qa/dogfood/test/multi-package-artifact.dogfood.test.ts — the harness that already boots this fixture. Its comment repeated the claim; corrected, and a new pin asserts both served rows carry scope: 'project', on a real boot. That is the assertion the old claim could only ever have been checked by, and never was.

No changeset: comment-and-test only, from a private example and a private QA package — nothing publishes. skip-changeset applied at creation.

packages/spec/src/kernel/manifest.zod.ts is untouched, per the triage: the default is correct and load-bearing.

Verification — union run at bff31b601

  • 31 gate families, re-derived on the actual diff (node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack), all run: 29 green, 2 needed the workspace built and are green after it (check:dual-build-cjs-loads, check:type-check-debt — the latter's --re-measure reports 22 ledger entries, 755 raw errors, none above record). scripts/check-test-completeness.mjs returns its own exit-3 NOT MEASURED branch (it parses a test-run log CI supplies; no local log exists) — recorded as NOT MEASURED, not as a pass.
  • pnpm lint (repo-wide eslint . --no-inline-config) — green. No narrowing claimed.
  • pnpm --filter './examples/*' run typecheck — green; log confirms examples/app-multi-package typecheck$ tsc --noEmit ... Done (checked against the zero-match-exits-0 trap).
  • pnpm check:nul-bytes green, plus a manual control-character scan over the three edited files.
  • The new pin, on a real boot: pnpm --filter @objectstack/dogfood exec vitest run test/multi-package-artifact.dogfood.test.ts — 9 passed (was 8).
  • Reverse verification of the new pin. Committed first, then mutated the orders assertion to the OLD claim (toBeUndefined()) and re-ran: 1 failed | 8 passed, AssertionError: expected 'project' to be undefined — the card's claim, falsified mechanically on a booted stack. Mutation confirmed on disk both ways before the run (injected marker present = 1, removed text = 0, blob 1b93be01 vs HEAD blob b6a7adf5); restore leg proved by git diff HEAD empty and the worktree blob back to b6a7adf5, marker gone, pin present. Restore ran from an EXIT INT TERM trap using absolute paths.

Out of scope, filed unassigned

The same false attribution is repeated in five platform-side comments and in the objectui console's rationale. Neither is a file this card may touch (the triage fenced the objectui half explicitly, and packages/metadata-protocol/src/protocol.ts is held by open PR #14767, so that sweep cannot be atomic here anyway).

🤖 Generated with Claude Code

https://claude.ai/code/session_017RbbUMnxkUnWhE4j94v8FE


Generated by Claude Code

…ackage
`defineStack` parses every `packages[]` entry through `ManifestSchema`, whose
`scope` is `.default('project')`, so the fixture's module is served with
`scope: 'project'` and never was the scope-less row it documented. Measured:
`dist/objectstack.json` carries `has_scope: true, scope: "project"` on both
packages.
State what the fixture actually delivers instead — the server's own
`writable: false` verdict (ADR-0070 D2), which reads `engine.manifests` before
any scope — and pin `scope: 'project'` on both served rows in the dogfood
harness that already boots the fixture, so the corrected docs stay true.
The genuinely scope-less rows exist where a manifest reaches the registry
without a `ManifestSchema` parse (marketplace / offline import; a Studio base
via `POST /api/v1/packages`); that pair is already pinned in
`packages/runtime/src/domains/packages-writable-verdict.test.ts`.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017RbbUMnxkUnWhE4j94v8FE
@claudeclaudeBot added the skip-changeset PR has no user-facing published change; bypasses the changeset gate label Sep 3, 2026
@github-actions

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

Nothing in this diff resolved to a documentable surface (no symbol, route or SDK anchor derived from 0 changed package(s)), so this run has no opinion about the docs.

What this run could not see
  • a page that states a rule by its inputs shares no identifier with the emitter that implements the rule, so an emitter-only diff cannot list it — not on this run and not on any run. Measured on fix(driver-sql): emit varchar(maxLength) for a text field a declared index keys on #11430: content/docs/protocol/objectql/types.mdx documents the text-family column mapping by the ObjectQL type names it maps FROM (text / textarea / html) while the diff changed createColumn; it went unlisted, and it was the page that diff falsified, in four places. No shared token exists to detect this on, so a rule your change carries has to be re-read by hand in the pages that restate it.

Coarse fallback — 0 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): node scripts/docs-audit/affected-docs.mjs --json fc648a256e0968950a10bb0454a6c835d3b064d5packageMentionDocs.

@claude

claudeBot commented Sep 3, 2026

Copy link
Copy Markdown
ContributorAuthor

Landing (domain:spec seat, session_017RbbUMnxkUnWhE4j94v8FE, 2026-09-03T03:06Z) — flipping to ready and enabling auto-merge (SQUASH) in this stroke.

Pre-landing checks: seat ACCEPT on #14597 (comment 5519582946; clause ② no — no schema, accept/reject or public-surface change; manifest.zod.ts untouched); all 38 checks on bff31b601 complete — 31 success / 7 skipped / 0 red; trial merge git merge-tree --write-tree origin/main refs/pull/14804/head at origin/maine6ac0c6fd: clean; governed-surface predicate: 0 of 3 paths — ordinary queue landing; skip-changeset read back on the PR (private example + private QA package).


Generated by Claude Code

@os-sam
os-sam marked this pull request as ready for review September 3, 2026 03:05
@os-sam
os-sam enabled auto-merge September 3, 2026 03:06
@os-sam
os-sam added this pull request to the merge queueSep 3, 2026
@github-actions

Copy link
Copy Markdown
Contributor

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

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

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

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

    @objectstack/cli:test: FAIL integration test/run-dev-unbuilt-workspace.e2e.test.ts > the mirror direction: a reader that is never coming back > gives up and exits instead of waiting forever
    ↳ 失败原因: @objectstack/cli:test: AssertionError: the harness SIGKILLed the child — it was still alive at the ceiling. cap 180000 ms (RUN_TIMEOUT_MS, constant and load-independent by design); this child ran 1800
    

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

⚠️断言这一侧有一类例外,判据是断言在测什么,不是它是不是 AssertionError 断言的对象是产品行为(一个值、一个形状、一次拒收)⇒ 照上面读:真实的行为改变,去查,⛔ 不要重排掉;
断言的对象是这次实验自身的有效性前提(跑完的耗时、负载下的先后、任何只在时间预算内才成立的条件)⇒ 它跟超时是同一类,同样对负载敏感,重排一次是合法的判别手段。
识别是机械的:断言的消息或它比较的值本身点名了一段时长、一个时间戳、一个耗时计数。实测过的一对 —— AssertionError: SecurityPlugin.init() ran: expected false to be true 测的是产品行为(真回归);
AssertionError: this run took over a second, so second-precision stamps could have differed too: expected 1006 to be less than 1000 测的是实验前提:它守护的那条不变式当时是绿的,同一个 head 原样重排一次即成功。
穿着 AssertionError 外衣的时间测量,仍然是时间测量。(⛔ 这只改「怎么读一次红」,不改「哪些测试可以重排」——后者由别处管。)

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

历史信号:

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

分诊清单:

  1. 失败测试在本 PR 改动的包里 → 真回归,修 PR。
  2. 失败测试与本 PR 无关 → 看上面的「跨 PR 相同签名」;已有汇总 issue ⇒ flaky/环境问题实锤,去那张 issue 上谈,修好前重排只会再烧一轮全队列。
  3. 两者都不是 → 可能与同组 PR 语义冲突;等前面的 PR 落地或失败出队后再重排一次即可,不要连续重排。

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

@github-actions

Copy link
Copy Markdown
Contributor

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

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

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

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

    @objectstack/cli:test: FAIL integration test/run-dev-unbuilt-workspace.e2e.test.ts > the mirror direction: a reader that is never coming back > gives up and exits instead of waiting forever
    ↳ 失败原因: @objectstack/cli:test: AssertionError: the harness SIGKILLed the child — it was still alive at the ceiling. cap 180000 ms (RUN_TIMEOUT_MS, constant and load-independent by design); this child ran 1801
    

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

⚠️断言这一侧有一类例外,判据是断言在测什么,不是它是不是 AssertionError 断言的对象是产品行为(一个值、一个形状、一次拒收)⇒ 照上面读:真实的行为改变,去查,⛔ 不要重排掉;
断言的对象是这次实验自身的有效性前提(跑完的耗时、负载下的先后、任何只在时间预算内才成立的条件)⇒ 它跟超时是同一类,同样对负载敏感,重排一次是合法的判别手段。
识别是机械的:断言的消息或它比较的值本身点名了一段时长、一个时间戳、一个耗时计数。实测过的一对 —— AssertionError: SecurityPlugin.init() ran: expected false to be true 测的是产品行为(真回归);
AssertionError: this run took over a second, so second-precision stamps could have differed too: expected 1006 to be less than 1000 测的是实验前提:它守护的那条不变式当时是绿的,同一个 head 原样重排一次即成功。
穿着 AssertionError 外衣的时间测量,仍然是时间测量。(⛔ 这只改「怎么读一次红」,不改「哪些测试可以重排」——后者由别处管。)

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

历史信号:

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

分诊清单:

  1. 失败测试在本 PR 改动的包里 → 真回归,修 PR。
  2. 失败测试与本 PR 无关 → 看上面的「跨 PR 相同签名」;已有汇总 issue ⇒ flaky/环境问题实锤,去那张 issue 上谈,修好前重排只会再烧一轮全队列。
  3. 两者都不是 → 可能与同组 PR 语义冲突;等前面的 PR 落地或失败出队后再重排一次即可,不要连续重排。

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

Merged via the queue into main with commit f3ae441Sep 3, 2026
40 checks passed
@os-sam
os-sam deleted the claude/issue-14597-multi-package-scope-claim branch September 3, 2026 04:51
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentationImprovements or additions to documentationsize/sskip-changesetPR has no user-facing published change; bypasses the changeset gatetests

Projects

None yet

2 participants

@os-sam@claude
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Remove or un-stick sticky/fixed headers that block content\n(function() {\n function unstick() {\n document.querySelectorAll('header, nav, [role=\"banner\"], .header, .navbar, .sticky, .fixed-top, [style*=\"position: fixed\"], [style*=\"position:sticky\"]').forEach(function(el) {\n if (el.style.position === 'fixed' || el.style.position === 'sticky' || \n getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') {\n el.style.position = 'static';\n el.style.top = 'auto';\n el.style.zIndex = 'auto';\n }\n });\n }\n \n unstick();\n \n var observer = new MutationObserver(unstick);\n observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] });\n})();", "Kill Sticky Headers"); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

fix(examples): app-multi-package's module is served WITH scope: 'project' — drop the "no scope key" claim - #14804

Merged
os-sam merged 1 commit into
mainfrom
claude/issue-14597-multi-package-scope-claim
Sep 3, 2026
Merged

fix(examples): app-multi-package's module is served WITH scope: 'project' — drop the "no scope key" claim#14804
os-sam merged 1 commit into
mainfrom
claude/issue-14597-multi-package-scope-claim

Conversation

@claude

@claudeclaudeBot commented Sep 3, 2026

Copy link
Copy Markdown
Contributor

Fixes#14597

The fixture documented a property its producer never emits. The triage made one question a precondition: does a genuinely scope-less served row exist on any path? It does — just not on this fixture's path, and not on the row the fixture claimed.

Measurement (read-only; all line numbers on origin/main8f9ef6f7c)

H1 — the artifact path materialises scope at compile time. CONFIRMED.
defineStack parses every packages[] entry's manifest through ManifestSchema (packages/spec/src/stack.zod.ts:233, ArtifactPackageEntrySchema), whose scope is .default('project') (packages/spec/src/kernel/manifest.zod.ts:311). Measured:

measurementresult
dist/objectstack.json after a real buildboth packages has_scope: true, scope: "project"
ManifestSchema.parse of the authored orders bodyhas_scope: false in, scope: "project" out
GET /api/v1/packages on a booted stackboth rows scope: "project", writable: false

So no package of a compiled artifact can be scope-less — the fixture's claim was not merely untrue here, it is unreachable from any examples/** app.

H2 — a raw manifest body CAN reach the registry unparsed. CONFIRMED; H1's "no served row is ever scope-less" is FALSIFIED.
registerApp (packages/objectql/src/engine.ts:4927) hands its argument to installPackage (:4956), which stores toRecordManifest(manifest) (packages/objectql/src/registry.ts:3956) — a key-by-key copy (:1229) that applies no defaults and runs no parse. Two production paths reach it without a ManifestSchema parse:

  • booted, read-only — marketplace install / offline file import / ledger rehydrate: manifestService.register(rawBody) at packages/cloud-connection/src/marketplace-install-local-plugin.ts:291 and :827, routed through packages/objectql/src/plugin.ts:457. This is the card's own parenthetical ("an installed marketplace package whose manifest bypasses defineStack?") and the answer is yes.
  • database base, writablePOST /api/v1/packages: const manifest = body.manifest || body (packages/runtime/src/domains/packages.ts:383) reaches protocol.installPackage (packages/metadata-protocol/src/protocol.ts:21188), which defaults version and derives namespace by hand and parses nothing (:21215). The code says so itself: "defineStack enforces it at compile time, but runtime-created packages never take that path".

Measured: SchemaRegistry.installPackage of the unparsed orders body yields a record with noscope key; of the parsed body, scope: "project".

H3 — the fixture's module would not have been the discriminator even if it were scope-less.
isWritablePackage (packages/metadata-protocol/src/package-writability.ts:77) reads engine.manifests FIRST, so scope is never consulted for a booted package. Measured verdicts:

rowin engine.manifestsscopeserver writable
scope-less booted (marketplace import)yesabsentfalse
scope-less database base (Studio)noabsenttrue
the fixture's moduleyes'project'false

The pair that separates the server verdict from any scope-only rule is rows 1 and 2 — and it is already pinned where it is true, in packages/runtime/src/domains/packages-writable-verdict.test.ts (Pins 2 + 4) and packages/metadata-protocol/src/get-meta-items-package-writable.test.ts. Nothing needed to move; the claim needed to come off the fixture.

What changed

  • examples/app-multi-package/src/packages/orders/index.ts — the second "load-bearing property" now states what the fixture delivers (the server's own writable: false verdict), plus a ⛔ note recording why no compiled artifact can carry a scope-less row, so the claim cannot be re-added.
  • examples/app-multi-package/README.md — same correction to the prose under the table.
  • packages/qa/dogfood/test/multi-package-artifact.dogfood.test.ts — the harness that already boots this fixture. Its comment repeated the claim; corrected, and a new pin asserts both served rows carry scope: 'project', on a real boot. That is the assertion the old claim could only ever have been checked by, and never was.

No changeset: comment-and-test only, from a private example and a private QA package — nothing publishes. skip-changeset applied at creation.

packages/spec/src/kernel/manifest.zod.ts is untouched, per the triage: the default is correct and load-bearing.

Verification — union run at bff31b601

  • 31 gate families, re-derived on the actual diff (node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack), all run: 29 green, 2 needed the workspace built and are green after it (check:dual-build-cjs-loads, check:type-check-debt — the latter's --re-measure reports 22 ledger entries, 755 raw errors, none above record). scripts/check-test-completeness.mjs returns its own exit-3 NOT MEASURED branch (it parses a test-run log CI supplies; no local log exists) — recorded as NOT MEASURED, not as a pass.
  • pnpm lint (repo-wide eslint . --no-inline-config) — green. No narrowing claimed.
  • pnpm --filter './examples/*' run typecheck — green; log confirms examples/app-multi-package typecheck$ tsc --noEmit ... Done (checked against the zero-match-exits-0 trap).
  • pnpm check:nul-bytes green, plus a manual control-character scan over the three edited files.
  • The new pin, on a real boot: pnpm --filter @objectstack/dogfood exec vitest run test/multi-package-artifact.dogfood.test.ts — 9 passed (was 8).
  • Reverse verification of the new pin. Committed first, then mutated the orders assertion to the OLD claim (toBeUndefined()) and re-ran: 1 failed | 8 passed, AssertionError: expected 'project' to be undefined — the card's claim, falsified mechanically on a booted stack. Mutation confirmed on disk both ways before the run (injected marker present = 1, removed text = 0, blob 1b93be01 vs HEAD blob b6a7adf5); restore leg proved by git diff HEAD empty and the worktree blob back to b6a7adf5, marker gone, pin present. Restore ran from an EXIT INT TERM trap using absolute paths.

Out of scope, filed unassigned

The same false attribution is repeated in five platform-side comments and in the objectui console's rationale. Neither is a file this card may touch (the triage fenced the objectui half explicitly, and packages/metadata-protocol/src/protocol.ts is held by open PR #14767, so that sweep cannot be atomic here anyway).

🤖 Generated with Claude Code

https://claude.ai/code/session_017RbbUMnxkUnWhE4j94v8FE


Generated by Claude Code

…ackage
`defineStack` parses every `packages[]` entry through `ManifestSchema`, whose
`scope` is `.default('project')`, so the fixture's module is served with
`scope: 'project'` and never was the scope-less row it documented. Measured:
`dist/objectstack.json` carries `has_scope: true, scope: "project"` on both
packages.
State what the fixture actually delivers instead — the server's own
`writable: false` verdict (ADR-0070 D2), which reads `engine.manifests` before
any scope — and pin `scope: 'project'` on both served rows in the dogfood
harness that already boots the fixture, so the corrected docs stay true.
The genuinely scope-less rows exist where a manifest reaches the registry
without a `ManifestSchema` parse (marketplace / offline import; a Studio base
via `POST /api/v1/packages`); that pair is already pinned in
`packages/runtime/src/domains/packages-writable-verdict.test.ts`.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017RbbUMnxkUnWhE4j94v8FE
@claudeclaudeBot added the skip-changeset PR has no user-facing published change; bypasses the changeset gate label Sep 3, 2026
@github-actions

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

Nothing in this diff resolved to a documentable surface (no symbol, route or SDK anchor derived from 0 changed package(s)), so this run has no opinion about the docs.

What this run could not see
  • a page that states a rule by its inputs shares no identifier with the emitter that implements the rule, so an emitter-only diff cannot list it — not on this run and not on any run. Measured on fix(driver-sql): emit varchar(maxLength) for a text field a declared index keys on #11430: content/docs/protocol/objectql/types.mdx documents the text-family column mapping by the ObjectQL type names it maps FROM (text / textarea / html) while the diff changed createColumn; it went unlisted, and it was the page that diff falsified, in four places. No shared token exists to detect this on, so a rule your change carries has to be re-read by hand in the pages that restate it.

Coarse fallback — 0 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): node scripts/docs-audit/affected-docs.mjs --json fc648a256e0968950a10bb0454a6c835d3b064d5packageMentionDocs.

@claude

claudeBot commented Sep 3, 2026

Copy link
Copy Markdown
ContributorAuthor

Landing (domain:spec seat, session_017RbbUMnxkUnWhE4j94v8FE, 2026-09-03T03:06Z) — flipping to ready and enabling auto-merge (SQUASH) in this stroke.

Pre-landing checks: seat ACCEPT on #14597 (comment 5519582946; clause ② no — no schema, accept/reject or public-surface change; manifest.zod.ts untouched); all 38 checks on bff31b601 complete — 31 success / 7 skipped / 0 red; trial merge git merge-tree --write-tree origin/main refs/pull/14804/head at origin/maine6ac0c6fd: clean; governed-surface predicate: 0 of 3 paths — ordinary queue landing; skip-changeset read back on the PR (private example + private QA package).


Generated by Claude Code

@os-sam
os-sam marked this pull request as ready for review September 3, 2026 03:05
@os-sam
os-sam enabled auto-merge September 3, 2026 03:06
@os-sam
os-sam added this pull request to the merge queueSep 3, 2026
@github-actions

Copy link
Copy Markdown
Contributor

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

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

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

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

    @objectstack/cli:test: FAIL integration test/run-dev-unbuilt-workspace.e2e.test.ts > the mirror direction: a reader that is never coming back > gives up and exits instead of waiting forever
    ↳ 失败原因: @objectstack/cli:test: AssertionError: the harness SIGKILLed the child — it was still alive at the ceiling. cap 180000 ms (RUN_TIMEOUT_MS, constant and load-independent by design); this child ran 1800
    

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

⚠️断言这一侧有一类例外,判据是断言在测什么,不是它是不是 AssertionError 断言的对象是产品行为(一个值、一个形状、一次拒收)⇒ 照上面读:真实的行为改变,去查,⛔ 不要重排掉;
断言的对象是这次实验自身的有效性前提(跑完的耗时、负载下的先后、任何只在时间预算内才成立的条件)⇒ 它跟超时是同一类,同样对负载敏感,重排一次是合法的判别手段。
识别是机械的:断言的消息或它比较的值本身点名了一段时长、一个时间戳、一个耗时计数。实测过的一对 —— AssertionError: SecurityPlugin.init() ran: expected false to be true 测的是产品行为(真回归);
AssertionError: this run took over a second, so second-precision stamps could have differed too: expected 1006 to be less than 1000 测的是实验前提:它守护的那条不变式当时是绿的,同一个 head 原样重排一次即成功。
穿着 AssertionError 外衣的时间测量,仍然是时间测量。(⛔ 这只改「怎么读一次红」,不改「哪些测试可以重排」——后者由别处管。)

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

历史信号:

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

分诊清单:

  1. 失败测试在本 PR 改动的包里 → 真回归,修 PR。
  2. 失败测试与本 PR 无关 → 看上面的「跨 PR 相同签名」;已有汇总 issue ⇒ flaky/环境问题实锤,去那张 issue 上谈,修好前重排只会再烧一轮全队列。
  3. 两者都不是 → 可能与同组 PR 语义冲突;等前面的 PR 落地或失败出队后再重排一次即可,不要连续重排。

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

@github-actions

Copy link
Copy Markdown
Contributor

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

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

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

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

    @objectstack/cli:test: FAIL integration test/run-dev-unbuilt-workspace.e2e.test.ts > the mirror direction: a reader that is never coming back > gives up and exits instead of waiting forever
    ↳ 失败原因: @objectstack/cli:test: AssertionError: the harness SIGKILLed the child — it was still alive at the ceiling. cap 180000 ms (RUN_TIMEOUT_MS, constant and load-independent by design); this child ran 1801
    

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

⚠️断言这一侧有一类例外,判据是断言在测什么,不是它是不是 AssertionError 断言的对象是产品行为(一个值、一个形状、一次拒收)⇒ 照上面读:真实的行为改变,去查,⛔ 不要重排掉;
断言的对象是这次实验自身的有效性前提(跑完的耗时、负载下的先后、任何只在时间预算内才成立的条件)⇒ 它跟超时是同一类,同样对负载敏感,重排一次是合法的判别手段。
识别是机械的:断言的消息或它比较的值本身点名了一段时长、一个时间戳、一个耗时计数。实测过的一对 —— AssertionError: SecurityPlugin.init() ran: expected false to be true 测的是产品行为(真回归);
AssertionError: this run took over a second, so second-precision stamps could have differed too: expected 1006 to be less than 1000 测的是实验前提:它守护的那条不变式当时是绿的,同一个 head 原样重排一次即成功。
穿着 AssertionError 外衣的时间测量,仍然是时间测量。(⛔ 这只改「怎么读一次红」,不改「哪些测试可以重排」——后者由别处管。)

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

历史信号:

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

分诊清单:

  1. 失败测试在本 PR 改动的包里 → 真回归,修 PR。
  2. 失败测试与本 PR 无关 → 看上面的「跨 PR 相同签名」;已有汇总 issue ⇒ flaky/环境问题实锤,去那张 issue 上谈,修好前重排只会再烧一轮全队列。
  3. 两者都不是 → 可能与同组 PR 语义冲突;等前面的 PR 落地或失败出队后再重排一次即可,不要连续重排。

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

Merged via the queue into main with commit f3ae441Sep 3, 2026
40 checks passed
@os-sam
os-sam deleted the claude/issue-14597-multi-package-scope-claim branch September 3, 2026 04:51
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentationImprovements or additions to documentationsize/sskip-changesetPR has no user-facing published change; bypasses the changeset gatetests

Projects

None yet

2 participants

@os-sam@claude
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Universal Dark Mode - works on any site\n(function() {\n var enabled = true;\n \n function applyDarkMode() {\n if (!enabled) return;\n \n // Create style element if it doesn't exist\n var style = document.getElementById('universal-dark-mode-style');\n if (!style) {\n style = document.createElement('style');\n style.id = 'universal-dark-mode-style';\n document.head.appendChild(style);\n }\n \n // Dark mode CSS - inverts colors but preserves images/video\n style.textContent = '\n /* Invert everything except media */\n html {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #1a1a2e !important;\n }\n \n /* Restore images, videos, iframes, canvas */\n img, video, iframe, canvas, svg, picture, [style*=\"background-image\"] {\n filter: invert(1) hue-rotate(180deg) !important;\n }\n \n /* Preserve specific elements that should not be inverted */\n .no-dark-mode, .no-dark-mode *,\n [data-theme=\"light\"], [data-theme=\"light\"],\n .ace_editor, .ace_editor *,\n .CodeMirror, .CodeMirror *,\n .monaco-editor, .monaco-editor *,\n .markdown-body pre, .markdown-body pre *,\n .highlight, .highlight *,\n pre code, pre code * {\n filter: none !important;\n }\n \n /* Fix common UI elements */\n .modal, .popup, .dropdown-menu, .tooltip, .popover {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #2d2d44 !important;\n border-color: #444 !important;\n }\n \n /* Scrollbars */\n ::-webkit-scrollbar { background: #1a1a2e !important; }\n ::-webkit-scrollbar-thumb { background: #444 !important; }\n ::-webkit-scrollbar-thumb:hover { background: #555 !important; }\n \n /* Selection */\n ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ';\n }\n \n function removeDarkMode() {\n var style = document.getElementById('universal-dark-mode-style');\n if (style) style.remove();\n }\n \n // Toggle with Alt+Shift+D\n document.addEventListener('keydown', function(e) {\n if (e.altKey && e.shiftKey && e.key === 'D') {\n e.preventDefault();\n enabled = !enabled;\n if (enabled) {\n applyDarkMode();\n console.log('[Universal Dark Mode] Enabled');\n } else {\n removeDarkMode();\n console.log('[Universal Dark Mode] Disabled');\n }\n }\n });\n \n // Apply on load\n applyDarkMode();\n \n // Re-apply on dynamic content\n var observer = new MutationObserver(function(mutations) {\n if (enabled && !document.getElementById('universal-dark-mode-style')) {\n applyDarkMode();\n }\n });\n observer.observe(document.head, { childList: true });\n \n console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle');\n})();", "Universal Dark Mode"); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })();
Skip to content

fix(examples): app-multi-package's module is served WITH scope: 'project' — drop the "no scope key" claim - #14804

Merged
os-sam merged 1 commit into
mainfrom
claude/issue-14597-multi-package-scope-claim
Sep 3, 2026
Merged

fix(examples): app-multi-package's module is served WITH scope: 'project' — drop the "no scope key" claim#14804
os-sam merged 1 commit into
mainfrom
claude/issue-14597-multi-package-scope-claim

Conversation

@claude

@claudeclaudeBot commented Sep 3, 2026

Copy link
Copy Markdown
Contributor

Fixes#14597

The fixture documented a property its producer never emits. The triage made one question a precondition: does a genuinely scope-less served row exist on any path? It does — just not on this fixture's path, and not on the row the fixture claimed.

Measurement (read-only; all line numbers on origin/main8f9ef6f7c)

H1 — the artifact path materialises scope at compile time. CONFIRMED.
defineStack parses every packages[] entry's manifest through ManifestSchema (packages/spec/src/stack.zod.ts:233, ArtifactPackageEntrySchema), whose scope is .default('project') (packages/spec/src/kernel/manifest.zod.ts:311). Measured:

measurementresult
dist/objectstack.json after a real buildboth packages has_scope: true, scope: "project"
ManifestSchema.parse of the authored orders bodyhas_scope: false in, scope: "project" out
GET /api/v1/packages on a booted stackboth rows scope: "project", writable: false

So no package of a compiled artifact can be scope-less — the fixture's claim was not merely untrue here, it is unreachable from any examples/** app.

H2 — a raw manifest body CAN reach the registry unparsed. CONFIRMED; H1's "no served row is ever scope-less" is FALSIFIED.
registerApp (packages/objectql/src/engine.ts:4927) hands its argument to installPackage (:4956), which stores toRecordManifest(manifest) (packages/objectql/src/registry.ts:3956) — a key-by-key copy (:1229) that applies no defaults and runs no parse. Two production paths reach it without a ManifestSchema parse:

  • booted, read-only — marketplace install / offline file import / ledger rehydrate: manifestService.register(rawBody) at packages/cloud-connection/src/marketplace-install-local-plugin.ts:291 and :827, routed through packages/objectql/src/plugin.ts:457. This is the card's own parenthetical ("an installed marketplace package whose manifest bypasses defineStack?") and the answer is yes.
  • database base, writablePOST /api/v1/packages: const manifest = body.manifest || body (packages/runtime/src/domains/packages.ts:383) reaches protocol.installPackage (packages/metadata-protocol/src/protocol.ts:21188), which defaults version and derives namespace by hand and parses nothing (:21215). The code says so itself: "defineStack enforces it at compile time, but runtime-created packages never take that path".

Measured: SchemaRegistry.installPackage of the unparsed orders body yields a record with noscope key; of the parsed body, scope: "project".

H3 — the fixture's module would not have been the discriminator even if it were scope-less.
isWritablePackage (packages/metadata-protocol/src/package-writability.ts:77) reads engine.manifests FIRST, so scope is never consulted for a booted package. Measured verdicts:

rowin engine.manifestsscopeserver writable
scope-less booted (marketplace import)yesabsentfalse
scope-less database base (Studio)noabsenttrue
the fixture's moduleyes'project'false

The pair that separates the server verdict from any scope-only rule is rows 1 and 2 — and it is already pinned where it is true, in packages/runtime/src/domains/packages-writable-verdict.test.ts (Pins 2 + 4) and packages/metadata-protocol/src/get-meta-items-package-writable.test.ts. Nothing needed to move; the claim needed to come off the fixture.

What changed

  • examples/app-multi-package/src/packages/orders/index.ts — the second "load-bearing property" now states what the fixture delivers (the server's own writable: false verdict), plus a ⛔ note recording why no compiled artifact can carry a scope-less row, so the claim cannot be re-added.
  • examples/app-multi-package/README.md — same correction to the prose under the table.
  • packages/qa/dogfood/test/multi-package-artifact.dogfood.test.ts — the harness that already boots this fixture. Its comment repeated the claim; corrected, and a new pin asserts both served rows carry scope: 'project', on a real boot. That is the assertion the old claim could only ever have been checked by, and never was.

No changeset: comment-and-test only, from a private example and a private QA package — nothing publishes. skip-changeset applied at creation.

packages/spec/src/kernel/manifest.zod.ts is untouched, per the triage: the default is correct and load-bearing.

Verification — union run at bff31b601

  • 31 gate families, re-derived on the actual diff (node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack), all run: 29 green, 2 needed the workspace built and are green after it (check:dual-build-cjs-loads, check:type-check-debt — the latter's --re-measure reports 22 ledger entries, 755 raw errors, none above record). scripts/check-test-completeness.mjs returns its own exit-3 NOT MEASURED branch (it parses a test-run log CI supplies; no local log exists) — recorded as NOT MEASURED, not as a pass.
  • pnpm lint (repo-wide eslint . --no-inline-config) — green. No narrowing claimed.
  • pnpm --filter './examples/*' run typecheck — green; log confirms examples/app-multi-package typecheck$ tsc --noEmit ... Done (checked against the zero-match-exits-0 trap).
  • pnpm check:nul-bytes green, plus a manual control-character scan over the three edited files.
  • The new pin, on a real boot: pnpm --filter @objectstack/dogfood exec vitest run test/multi-package-artifact.dogfood.test.ts — 9 passed (was 8).
  • Reverse verification of the new pin. Committed first, then mutated the orders assertion to the OLD claim (toBeUndefined()) and re-ran: 1 failed | 8 passed, AssertionError: expected 'project' to be undefined — the card's claim, falsified mechanically on a booted stack. Mutation confirmed on disk both ways before the run (injected marker present = 1, removed text = 0, blob 1b93be01 vs HEAD blob b6a7adf5); restore leg proved by git diff HEAD empty and the worktree blob back to b6a7adf5, marker gone, pin present. Restore ran from an EXIT INT TERM trap using absolute paths.

Out of scope, filed unassigned

The same false attribution is repeated in five platform-side comments and in the objectui console's rationale. Neither is a file this card may touch (the triage fenced the objectui half explicitly, and packages/metadata-protocol/src/protocol.ts is held by open PR #14767, so that sweep cannot be atomic here anyway).

🤖 Generated with Claude Code

https://claude.ai/code/session_017RbbUMnxkUnWhE4j94v8FE


Generated by Claude Code

…ackage
`defineStack` parses every `packages[]` entry through `ManifestSchema`, whose
`scope` is `.default('project')`, so the fixture's module is served with
`scope: 'project'` and never was the scope-less row it documented. Measured:
`dist/objectstack.json` carries `has_scope: true, scope: "project"` on both
packages.
State what the fixture actually delivers instead — the server's own
`writable: false` verdict (ADR-0070 D2), which reads `engine.manifests` before
any scope — and pin `scope: 'project'` on both served rows in the dogfood
harness that already boots the fixture, so the corrected docs stay true.
The genuinely scope-less rows exist where a manifest reaches the registry
without a `ManifestSchema` parse (marketplace / offline import; a Studio base
via `POST /api/v1/packages`); that pair is already pinned in
`packages/runtime/src/domains/packages-writable-verdict.test.ts`.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017RbbUMnxkUnWhE4j94v8FE
@claudeclaudeBot added the skip-changeset PR has no user-facing published change; bypasses the changeset gate label Sep 3, 2026
@github-actions

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

Nothing in this diff resolved to a documentable surface (no symbol, route or SDK anchor derived from 0 changed package(s)), so this run has no opinion about the docs.

What this run could not see
  • a page that states a rule by its inputs shares no identifier with the emitter that implements the rule, so an emitter-only diff cannot list it — not on this run and not on any run. Measured on fix(driver-sql): emit varchar(maxLength) for a text field a declared index keys on #11430: content/docs/protocol/objectql/types.mdx documents the text-family column mapping by the ObjectQL type names it maps FROM (text / textarea / html) while the diff changed createColumn; it went unlisted, and it was the page that diff falsified, in four places. No shared token exists to detect this on, so a rule your change carries has to be re-read by hand in the pages that restate it.

Coarse fallback — 0 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): node scripts/docs-audit/affected-docs.mjs --json fc648a256e0968950a10bb0454a6c835d3b064d5packageMentionDocs.

@claude

claudeBot commented Sep 3, 2026

Copy link
Copy Markdown
ContributorAuthor

Landing (domain:spec seat, session_017RbbUMnxkUnWhE4j94v8FE, 2026-09-03T03:06Z) — flipping to ready and enabling auto-merge (SQUASH) in this stroke.

Pre-landing checks: seat ACCEPT on #14597 (comment 5519582946; clause ② no — no schema, accept/reject or public-surface change; manifest.zod.ts untouched); all 38 checks on bff31b601 complete — 31 success / 7 skipped / 0 red; trial merge git merge-tree --write-tree origin/main refs/pull/14804/head at origin/maine6ac0c6fd: clean; governed-surface predicate: 0 of 3 paths — ordinary queue landing; skip-changeset read back on the PR (private example + private QA package).


Generated by Claude Code

@os-sam
os-sam marked this pull request as ready for review September 3, 2026 03:05
@os-sam
os-sam enabled auto-merge September 3, 2026 03:06
@os-sam
os-sam added this pull request to the merge queueSep 3, 2026
@github-actions

Copy link
Copy Markdown
Contributor

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

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

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

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

    @objectstack/cli:test: FAIL integration test/run-dev-unbuilt-workspace.e2e.test.ts > the mirror direction: a reader that is never coming back > gives up and exits instead of waiting forever
    ↳ 失败原因: @objectstack/cli:test: AssertionError: the harness SIGKILLed the child — it was still alive at the ceiling. cap 180000 ms (RUN_TIMEOUT_MS, constant and load-independent by design); this child ran 1800
    

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

⚠️断言这一侧有一类例外,判据是断言在测什么,不是它是不是 AssertionError 断言的对象是产品行为(一个值、一个形状、一次拒收)⇒ 照上面读:真实的行为改变,去查,⛔ 不要重排掉;
断言的对象是这次实验自身的有效性前提(跑完的耗时、负载下的先后、任何只在时间预算内才成立的条件)⇒ 它跟超时是同一类,同样对负载敏感,重排一次是合法的判别手段。
识别是机械的:断言的消息或它比较的值本身点名了一段时长、一个时间戳、一个耗时计数。实测过的一对 —— AssertionError: SecurityPlugin.init() ran: expected false to be true 测的是产品行为(真回归);
AssertionError: this run took over a second, so second-precision stamps could have differed too: expected 1006 to be less than 1000 测的是实验前提:它守护的那条不变式当时是绿的,同一个 head 原样重排一次即成功。
穿着 AssertionError 外衣的时间测量,仍然是时间测量。(⛔ 这只改「怎么读一次红」,不改「哪些测试可以重排」——后者由别处管。)

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

历史信号:

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

分诊清单:

  1. 失败测试在本 PR 改动的包里 → 真回归,修 PR。
  2. 失败测试与本 PR 无关 → 看上面的「跨 PR 相同签名」;已有汇总 issue ⇒ flaky/环境问题实锤,去那张 issue 上谈,修好前重排只会再烧一轮全队列。
  3. 两者都不是 → 可能与同组 PR 语义冲突;等前面的 PR 落地或失败出队后再重排一次即可,不要连续重排。

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

@github-actions

Copy link
Copy Markdown
Contributor

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

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

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

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

    @objectstack/cli:test: FAIL integration test/run-dev-unbuilt-workspace.e2e.test.ts > the mirror direction: a reader that is never coming back > gives up and exits instead of waiting forever
    ↳ 失败原因: @objectstack/cli:test: AssertionError: the harness SIGKILLed the child — it was still alive at the ceiling. cap 180000 ms (RUN_TIMEOUT_MS, constant and load-independent by design); this child ran 1801
    

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

⚠️断言这一侧有一类例外,判据是断言在测什么,不是它是不是 AssertionError 断言的对象是产品行为(一个值、一个形状、一次拒收)⇒ 照上面读:真实的行为改变,去查,⛔ 不要重排掉;
断言的对象是这次实验自身的有效性前提(跑完的耗时、负载下的先后、任何只在时间预算内才成立的条件)⇒ 它跟超时是同一类,同样对负载敏感,重排一次是合法的判别手段。
识别是机械的:断言的消息或它比较的值本身点名了一段时长、一个时间戳、一个耗时计数。实测过的一对 —— AssertionError: SecurityPlugin.init() ran: expected false to be true 测的是产品行为(真回归);
AssertionError: this run took over a second, so second-precision stamps could have differed too: expected 1006 to be less than 1000 测的是实验前提:它守护的那条不变式当时是绿的,同一个 head 原样重排一次即成功。
穿着 AssertionError 外衣的时间测量,仍然是时间测量。(⛔ 这只改「怎么读一次红」,不改「哪些测试可以重排」——后者由别处管。)

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

历史信号:

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

分诊清单:

  1. 失败测试在本 PR 改动的包里 → 真回归,修 PR。
  2. 失败测试与本 PR 无关 → 看上面的「跨 PR 相同签名」;已有汇总 issue ⇒ flaky/环境问题实锤,去那张 issue 上谈,修好前重排只会再烧一轮全队列。
  3. 两者都不是 → 可能与同组 PR 语义冲突;等前面的 PR 落地或失败出队后再重排一次即可,不要连续重排。

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

Merged via the queue into main with commit f3ae441Sep 3, 2026
40 checks passed
@os-sam
os-sam deleted the claude/issue-14597-multi-package-scope-claim branch September 3, 2026 04:51
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentationImprovements or additions to documentationsize/sskip-changesetPR has no user-facing published change; bypasses the changeset gatetests

Projects

None yet

2 participants

@os-sam@claude