You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
metadata-protocol: publish-drafts build probes query datasets with no execution context, so a correct dataset widget is reported as an error-tier widget_query_failed #25484
Filing gate: ① a product defect with a named location and a live reproduction (finding class (a)).
Reader: the packages/metadata-protocol lane (build-probes.ts, the dataset probe call), next PR touching publish-time probes.
Dedupe (open AND closed): "publish-drafts build probes widget_query_failed access denied dataset no execution context" → 6 hits, none on this (nearest: #10377, same-batch dataset closure; #8400/#8594/#8595, publish audit rows — all different defects).
reach: public HTTP entry, measured twice — POST /api/v1/packages/:id/publish-drafts answers an error-tier widget_query_failed for a widget that renders correctly.
Checklist item priority: P1
type: Bug
QA-source: #25479 · studio-authoring.draft-publish-lifecycle · observed beside the clauses
What happens
After POST /api/v1/packages/PKG/publish-drafts publishes a dashboard whose widget is bound to a valid dataset (showcase_task_metrics), the response's data.probes.issues carries an error-severitywidget_query_failed: [Analytics] Access denied: reading "showcase_task" is not permitted for this user, with fix text "Fix the dataset definition…". The same widget renders correctly for the admin who published it and for the member Mei. So the probe reports a defect that does not exist and tells the author to fix a correct dataset. The per-item publish runs no probes, which is why this appears only on the package publish.
Reproduced twice (two dashboards in one batch, then a third alone) on main77392e9.
Mechanism
packages/metadata-protocol/src/build-probes.ts (around line 343) calls the analytics service with no execution context:
const result = await analytics.queryDataset(dataset, selection, undefined);
Read admission refuses a context-less read, so every dataset over a permission-gated object fails the probe. The probe's own docblock says it runs "the SAME queryDataset path the dashboard renderer hits", and the renderer passes the caller's context.
Expected: the probe runs in the publishing caller's context, or in an explicit system context for a build check. Either way a correct dataset produces no error-tier issue.
Triage: lands in packages/metadata-protocol/src/build-probes.ts (~L343, queryDataset(dataset, selection, undefined)); rationale: finding class (a), reach measured twice. The probe runs with no execution context, so read admission refuses it and a correct dataset is reported as an error-tier widget_query_failed. Run it in the publishing caller's context (the renderer's path), or an explicit system context.
Path: studio · 先草稿后发布 | studio-authoring.draft-publish-lifecycle (P1, publish still succeeds) | step ①
Grade: priority:p2 — the publish lands; the cost is a false error telling the author to fix a correct dataset.
Type: Bug
Session: session_01AkmasAfXt3cu83xGzkuKhe (objectstack triage, run on the maintainer's request in the project thread)
Claim: PM loop · domain:cli seat 1 · p2 · 2026-10-23T18:23Z
Session: session_01CxBKycaiUpA1yGx1XyoGqX
Account: os-sales (the seat's linked user; always the card's assignee)
Branch: claude/issue-25484-build-probe-context
Worktree: objectstack-issue-25484
Domain: domain:server (this seat's side of the server lane under the coordinator's split: former cli and engine paths)
Seat: domain:cli#1 (seat post #6024; 由Sales账户的项目负责)
File surface (read on origin/maindc3e5b9d7b; stop on a breach): packages/metadata-protocol/src/build-probes.ts (~:343, the dataset probe call) and its caller only as far as passing the publishing caller's context, their tests, one changeset.
Container & model: S, mode:subagent, model: default tier
Clause-②: no
Basis for the Clause-② line: a build-time probe report: a false error-tier issue goes away; no door, accept set or permission moves.
Responsibility: n/a — QA-source #25479, triaged 6370245650.
Thread-read: 6370245650
Serial constraints cleared: none of the 10 open PRs touches this surface (file lists read in this act).
{
"issue": 25484,
"status": "done",
"branch": "claude/issue-25484-build-probe-context",
"pr": "https://github.com/objectstack-ai/objectstack/pull/25533",
"session": "session_01CxBKycaiUpA1yGx1XyoGqX",
"premise_still_valid": true,
"summary": "Verified on origin/main dc3e5b9d7b: build-probes.ts called analytics.queryDataset(dataset, selection, undefined), and the publish-drafts door handed the protocol the caller's executionContext only when a backfill was named. Fix threads the PUBLISHING caller's context: runtime door always passes context: _context.executionContext (request type already declared context?: unknown, so no published interface or spec change); protocol.publishPackageDrafts passes request.context into runBuildProbes; RunBuildProbesOptions gains optional context; the widget probe calls queryDataset as that caller and judges empty_query's 'object has rows' over the rows that caller can see (a system count beside a caller-scoped query would be a new false empty_query). Never falls back to a system context; with no context the plane is byte-for-byte the old behaviour. A denied caller now still sees the error-tier widget_query_failed with the '[Analytics] Access denied: reading X is not permitted for this user' message (severity unchanged) - only the false error from the missing context is gone.",
"tests": "HEAD 1bb247c7b6. Locked runs: runtime typecheck + full suite (vitest --project local): VERDICT command-exit 0, Test Files 440 passed (440), Tests 7034 passed | 19 skipped. metadata-protocol typecheck + full suite: Test Files 265 (262 passed, 3 skipped), Tests 21459 passed | 19 skipped, typecheck clean (earlier run on 5e25f33a caught TS2493 in the new test; fixed in bb15e5f1c3). objectql full suite: VERDICT command-exit 0, Test Files 466 passed, Tests 8699 passed. New: runtime packages-publish-drafts-probe-caller-context.test.ts (real HttpDispatcher.handlePackages -> real ObjectStackProtocolImplementation.publishPackageDrafts -> runBuildProbes -> real AnalyticsService with admitObjectRead gating task_item to u_reader): reader => no error-tier issue, admission sees the reader and never isSystem; non-reader => exactly one error-tier widget_query_failed. metadata-protocol build-probes-caller-context.test.ts: context reaches queryDataset arg 2 and the rows count; no-context path keeps context-less query + system count => empty_query. Ablation (fix committed first, scripts/ablation-replace.mjs, anchor 'analytics.queryDataset(dataset, selection, context)' x1->x0, blob d23a96693a7e->57d1a971a0d0, rebuild, ablation-dist-preflight: marker present in 2 built files): both runtime cases red (reader case: the expected red; non-reader case red only on its 'admission saw this caller' assertion - the widget_query_failed it pins was still produced). Restore: blob == HEAD d23a96693a7e, git diff HEAD empty, rebuild, --absent preflight clean over 24 built files, 2/2 green.",
"gates": "Local, HEAD 1bb247c7b6: dispatch-gates --commands (80 derived) all run with exit captured before any pipe; 78 exit 0; NOT MEASURED: check:dual-build-cjs-loads, reason: exit 3 PREREQUISITE NOT MET (reads every package's dist/, no full pnpm build in this worktree); NOT MEASURED: check:type-check-debt, reason: exit 3, its closure build got a verify-lock queue-timeout (99). dispatch-gates --ran: '80 derived famil(ies) accounted for - 78 run, 2 NOT-MEASURED', exit 0. pnpm lint: exit 0. Mid-run fixes: check:error-code-casing (adr0112-ok mark on the new test's widget_query_failed literal) and check:engine-double-contract (findOne via assertEngineFindOnePredicate + --write ledger) were red on 5e25f33a and are 0 now. CI on PR #25533 head 1bb247c7b6, all success: Lint & Repo Gates, TypeScript Type Check (incl. Type Check · debt ledger), Test Core (1-6/6), Build Core, Governed Surface Queue Guard, Check Changeset, Dogfood Regression Gate, Check PR Size. On the earlier head bb15e5f1c3, Test Core 6/6 was red on packages-seed-apply-org-scope.test.ts:394 (a pin that the request equals {packageId}; this change makes it carry context) - fixed in 1bb247c7b6. Test Core 2/6 was red on objectql hook-run-as.test.ts:388, a 5000ms timeout on a dynamic import of ./index.js, not touched by this diff; it is green locally (466/466) and in CI on 1bb247c7b6.",
"line_budget": "9 files, +318 / -15 vs base dc3e5b9d7b (source: build-probes.ts +35/-5, protocol.ts +4/-1, packages.ts +9/-5; tests +249/-4; changeset 16; ledger 5). Size S.",
"dogfood_battery": "n/a - no `!` in the PR title and no (narrowing) arm",
"deviations": [
"protocol.ts touched (one hunk in publishPackageDrafts around line 27146, +4/-1). The dispatch said to stop if protocol.ts was needed, but the claim's file surface names the caller, and the only caller of runBuildProbes is in protocol.ts. Measured against the in-flight #25467 slice PR #25518: its protocol.ts hunks sit at lines 301, 2301 and 9254-9450, so the two diffs do not overlap. Proceeded on that measurement; the seat may hold the draft if it disagrees.",
"File surface widened beyond build-probes.ts and its direct caller: packages/runtime/src/domains/packages.ts (the publish-drafts door, which passed the context only with a backfill - the context could not reach the probe otherwise), two existing runtime pins whose assertions the change makes false (packages-publish-drafts-response-conformance.test.ts, packages-seed-apply-org-scope.test.ts), and scripts/engine-double-contract.pinned.json (gate-written via --write for the new test's engine double).",
"Widget plane's empty_query row-count now runs as the caller when a context is present: a follow-on of the same change (without it a row-scoped caller gets a new false error-tier empty_query). Pinned in build-probes-caller-context.test.ts.",
"PR body was written once at create (on head bb15e5f1c3) and does not mention the later org-scope pin edit (1bb247c7b6) or cite the final head sha. Suggested seat edit: add to Tests 'packages-seed-apply-org-scope.test.ts: the request now equals {packageId, context: caller}, still no organizationId', and cite head 1bb247c7b6 for the local runs.",
"PR body: the dispatch-mandated first line is an HTML-comment attribution marker; it was kept verbatim as ordered (the read-back reported it stored identical), although the role file bans HTML comments in GitHub text. The body ends with the dispatch-ordered footer, not the AGENTS.md session-URL form.",
"Ran a few check:* gate batches and the PR-create wait with nohup or background shells so they could outlive the 10-minute foreground limit; each was waited on within the run, and none is still running."
],
"files_changed": [
".changeset/25484-build-probe-caller-context.md",
"packages/metadata-protocol/src/build-probes.ts",
"packages/metadata-protocol/src/protocol.ts",
"packages/metadata-protocol/src/build-probes-caller-context.test.ts",
"packages/runtime/src/domains/packages.ts",
"packages/runtime/src/domains/packages-publish-drafts-probe-caller-context.test.ts",
"packages/runtime/src/domains/packages-publish-drafts-response-conformance.test.ts",
"packages/runtime/src/domains/packages-seed-apply-org-scope.test.ts",
"scripts/engine-double-contract.pinned.json"
],
"mcp_calls": "3 - mcp__github__get_job_logs x3 (read-only, CI job logs for Test Core 2/6 and 6/6); no MCP write tool used",
"api_writes": "2 relay strokes via scripts/pm/fleet-write/dispatch.mjs (each POST /repos/objectstack-ai/objectstack/dispatches): (1) pr_create => POST /repos/objectstack-ai/objectstack/pulls (draft) + POST /repos/objectstack-ai/objectstack/issues/25533/assignees [os-sales]; (2) comment => POST /repos/objectstack-ai/objectstack/issues/25484/comments (this report). Plus git push (not REST).",
"open_questions": [
{
"question": "Is a protocol.ts hunk that does not overlap #25518 acceptable while the #25467 split is in flight?",
"options": [
"A: keep PR #25533 as is; whichever lands second takes a one-line rebase if a later slice moves publishPackageDrafts",
"B: hold #25533 until the slice that moves publishPackageDrafts lands, then re-apply the one line in its new file"
],
"recommendation": "A, because the hunk is 4 lines, measured disjoint from the only open slice, and the defect is a live false error on a public door"
}
],
"out_of_scope_findings": [
"class: a (misleading author-facing text) · reach: POST /api/v1/packages/:id/publish-drafts, measured in the new runtime test: a publisher without read access gets widget_query_failed whose fix says 'Fix the dataset definition (or the widget's values/dimensions) so the query compiles' though the dataset is correct and the cause is the caller's access · evidence: packages/metadata-protocol/src/build-probes.ts widget catch block, one fix text for every throw · dedupe words: widget_query_failed fix text access denied, build probe permission denied prescription, publish-drafts probe misleading fix",
"carrier: none · noted, not filed: objectql src/hook-run-as.test.ts:388 timed out at 5000ms on a dynamic import of ./index.js in CI Test Core 2/6 (head bb15e5f1c3); green locally and on the next CI run - a load-time flake, not this diff"
]
}
Activity
objectstack-fleet commentedon Oct 23, 2026
Triage: lands in
packages/metadata-protocol/src/build-probes.ts(~L343,queryDataset(dataset, selection, undefined)); rationale: finding class (a), reach measured twice. The probe runs with no execution context, so read admission refuses it and a correct dataset is reported as an error-tierwidget_query_failed. Run it in the publishing caller's context (the renderer's path), or an explicit system context.Path: studio · 先草稿后发布 | studio-authoring.draft-publish-lifecycle (P1, publish still succeeds) | step ①
Grade:
priority:p2— the publish lands; the cost is a false error telling the author to fix a correct dataset.Type: Bug
Session:
session_01AkmasAfXt3cu83xGzkuKhe(objectstack triage, run on the maintainer's request in the project thread)Generated by Claude Code
objectstack-fleet commentedon Oct 23, 2026
Claim: PM loop ·
domain:cliseat 1 · p2 · 2026-10-23T18:23ZSession:
session_01CxBKycaiUpA1yGx1XyoGqXAccount:
os-sales(the seat's linked user; always the card's assignee)Branch:
claude/issue-25484-build-probe-contextWorktree:
objectstack-issue-25484Domain:
domain:server(this seat's side of the server lane under the coordinator's split: former cli and engine paths)Seat:
domain:cli#1(seat post #6024; 由Sales账户的项目负责)File surface (read on
origin/maindc3e5b9d7b; stop on a breach):packages/metadata-protocol/src/build-probes.ts(~:343, the dataset probe call) and its caller only as far as passing the publishing caller's context, their tests, one changeset.Container & model:
S,mode:subagent,model: default tierClause-②: no
Basis for the Clause-② line: a build-time probe report: a false error-tier issue goes away; no door, accept set or permission moves.
Responsibility: n/a — QA-source #25479, triaged
6370245650.Thread-read: 6370245650
Serial constraints cleared: none of the 10 open PRs touches this surface (file lists read in this act).
Generated by Claude Code
objectstack-fleet commentedon Oct 23, 2026
os-dev-report
{ "issue": 25484, "status": "done", "branch": "claude/issue-25484-build-probe-context", "pr": "https://github.com/objectstack-ai/objectstack/pull/25533", "session": "session_01CxBKycaiUpA1yGx1XyoGqX", "premise_still_valid": true, "summary": "Verified on origin/main dc3e5b9d7b: build-probes.ts called analytics.queryDataset(dataset, selection, undefined), and the publish-drafts door handed the protocol the caller's executionContext only when a backfill was named. Fix threads the PUBLISHING caller's context: runtime door always passes context: _context.executionContext (request type already declared context?: unknown, so no published interface or spec change); protocol.publishPackageDrafts passes request.context into runBuildProbes; RunBuildProbesOptions gains optional context; the widget probe calls queryDataset as that caller and judges empty_query's 'object has rows' over the rows that caller can see (a system count beside a caller-scoped query would be a new false empty_query). Never falls back to a system context; with no context the plane is byte-for-byte the old behaviour. A denied caller now still sees the error-tier widget_query_failed with the '[Analytics] Access denied: reading X is not permitted for this user' message (severity unchanged) - only the false error from the missing context is gone.", "tests": "HEAD 1bb247c7b6. Locked runs: runtime typecheck + full suite (vitest --project local): VERDICT command-exit 0, Test Files 440 passed (440), Tests 7034 passed | 19 skipped. metadata-protocol typecheck + full suite: Test Files 265 (262 passed, 3 skipped), Tests 21459 passed | 19 skipped, typecheck clean (earlier run on 5e25f33a caught TS2493 in the new test; fixed in bb15e5f1c3). objectql full suite: VERDICT command-exit 0, Test Files 466 passed, Tests 8699 passed. New: runtime packages-publish-drafts-probe-caller-context.test.ts (real HttpDispatcher.handlePackages -> real ObjectStackProtocolImplementation.publishPackageDrafts -> runBuildProbes -> real AnalyticsService with admitObjectRead gating task_item to u_reader): reader => no error-tier issue, admission sees the reader and never isSystem; non-reader => exactly one error-tier widget_query_failed. metadata-protocol build-probes-caller-context.test.ts: context reaches queryDataset arg 2 and the rows count; no-context path keeps context-less query + system count => empty_query. Ablation (fix committed first, scripts/ablation-replace.mjs, anchor 'analytics.queryDataset(dataset, selection, context)' x1->x0, blob d23a96693a7e->57d1a971a0d0, rebuild, ablation-dist-preflight: marker present in 2 built files): both runtime cases red (reader case: the expected red; non-reader case red only on its 'admission saw this caller' assertion - the widget_query_failed it pins was still produced). Restore: blob == HEAD d23a96693a7e, git diff HEAD empty, rebuild, --absent preflight clean over 24 built files, 2/2 green.", "gates": "Local, HEAD 1bb247c7b6: dispatch-gates --commands (80 derived) all run with exit captured before any pipe; 78 exit 0; NOT MEASURED: check:dual-build-cjs-loads, reason: exit 3 PREREQUISITE NOT MET (reads every package's dist/, no full pnpm build in this worktree); NOT MEASURED: check:type-check-debt, reason: exit 3, its closure build got a verify-lock queue-timeout (99). dispatch-gates --ran: '80 derived famil(ies) accounted for - 78 run, 2 NOT-MEASURED', exit 0. pnpm lint: exit 0. Mid-run fixes: check:error-code-casing (adr0112-ok mark on the new test's widget_query_failed literal) and check:engine-double-contract (findOne via assertEngineFindOnePredicate + --write ledger) were red on 5e25f33a and are 0 now. CI on PR #25533 head 1bb247c7b6, all success: Lint & Repo Gates, TypeScript Type Check (incl. Type Check · debt ledger), Test Core (1-6/6), Build Core, Governed Surface Queue Guard, Check Changeset, Dogfood Regression Gate, Check PR Size. On the earlier head bb15e5f1c3, Test Core 6/6 was red on packages-seed-apply-org-scope.test.ts:394 (a pin that the request equals {packageId}; this change makes it carry context) - fixed in 1bb247c7b6. Test Core 2/6 was red on objectql hook-run-as.test.ts:388, a 5000ms timeout on a dynamic import of ./index.js, not touched by this diff; it is green locally (466/466) and in CI on 1bb247c7b6.", "line_budget": "9 files, +318 / -15 vs base dc3e5b9d7b (source: build-probes.ts +35/-5, protocol.ts +4/-1, packages.ts +9/-5; tests +249/-4; changeset 16; ledger 5). Size S.", "dogfood_battery": "n/a - no `!` in the PR title and no (narrowing) arm", "deviations": [ "protocol.ts touched (one hunk in publishPackageDrafts around line 27146, +4/-1). The dispatch said to stop if protocol.ts was needed, but the claim's file surface names the caller, and the only caller of runBuildProbes is in protocol.ts. Measured against the in-flight #25467 slice PR #25518: its protocol.ts hunks sit at lines 301, 2301 and 9254-9450, so the two diffs do not overlap. Proceeded on that measurement; the seat may hold the draft if it disagrees.", "File surface widened beyond build-probes.ts and its direct caller: packages/runtime/src/domains/packages.ts (the publish-drafts door, which passed the context only with a backfill - the context could not reach the probe otherwise), two existing runtime pins whose assertions the change makes false (packages-publish-drafts-response-conformance.test.ts, packages-seed-apply-org-scope.test.ts), and scripts/engine-double-contract.pinned.json (gate-written via --write for the new test's engine double).", "Widget plane's empty_query row-count now runs as the caller when a context is present: a follow-on of the same change (without it a row-scoped caller gets a new false error-tier empty_query). Pinned in build-probes-caller-context.test.ts.", "PR body was written once at create (on head bb15e5f1c3) and does not mention the later org-scope pin edit (1bb247c7b6) or cite the final head sha. Suggested seat edit: add to Tests 'packages-seed-apply-org-scope.test.ts: the request now equals {packageId, context: caller}, still no organizationId', and cite head 1bb247c7b6 for the local runs.", "PR body: the dispatch-mandated first line is an HTML-comment attribution marker; it was kept verbatim as ordered (the read-back reported it stored identical), although the role file bans HTML comments in GitHub text. The body ends with the dispatch-ordered footer, not the AGENTS.md session-URL form.", "Ran a few check:* gate batches and the PR-create wait with nohup or background shells so they could outlive the 10-minute foreground limit; each was waited on within the run, and none is still running." ], "files_changed": [ ".changeset/25484-build-probe-caller-context.md", "packages/metadata-protocol/src/build-probes.ts", "packages/metadata-protocol/src/protocol.ts", "packages/metadata-protocol/src/build-probes-caller-context.test.ts", "packages/runtime/src/domains/packages.ts", "packages/runtime/src/domains/packages-publish-drafts-probe-caller-context.test.ts", "packages/runtime/src/domains/packages-publish-drafts-response-conformance.test.ts", "packages/runtime/src/domains/packages-seed-apply-org-scope.test.ts", "scripts/engine-double-contract.pinned.json" ], "mcp_calls": "3 - mcp__github__get_job_logs x3 (read-only, CI job logs for Test Core 2/6 and 6/6); no MCP write tool used", "api_writes": "2 relay strokes via scripts/pm/fleet-write/dispatch.mjs (each POST /repos/objectstack-ai/objectstack/dispatches): (1) pr_create => POST /repos/objectstack-ai/objectstack/pulls (draft) + POST /repos/objectstack-ai/objectstack/issues/25533/assignees [os-sales]; (2) comment => POST /repos/objectstack-ai/objectstack/issues/25484/comments (this report). Plus git push (not REST).", "open_questions": [ { "question": "Is a protocol.ts hunk that does not overlap #25518 acceptable while the #25467 split is in flight?", "options": [ "A: keep PR #25533 as is; whichever lands second takes a one-line rebase if a later slice moves publishPackageDrafts", "B: hold #25533 until the slice that moves publishPackageDrafts lands, then re-apply the one line in its new file" ], "recommendation": "A, because the hunk is 4 lines, measured disjoint from the only open slice, and the defect is a live false error on a public door" } ], "out_of_scope_findings": [ "class: a (misleading author-facing text) · reach: POST /api/v1/packages/:id/publish-drafts, measured in the new runtime test: a publisher without read access gets widget_query_failed whose fix says 'Fix the dataset definition (or the widget's values/dimensions) so the query compiles' though the dataset is correct and the cause is the caller's access · evidence: packages/metadata-protocol/src/build-probes.ts widget catch block, one fix text for every throw · dedupe words: widget_query_failed fix text access denied, build probe permission denied prescription, publish-drafts probe misleading fix", "carrier: none · noted, not filed: objectql src/hook-run-as.test.ts:388 timed out at 5000ms on a dynamic import of ./index.js in CI Test Core 2/6 (head bb15e5f1c3); green locally and on the next CI run - a load-time flake, not this diff" ] }Generated by Claude Code
objectstack-fleet commentedon Oct 23, 2026
ACCEPT → PR #25533 comment 6375774888 ·
domain:cliseat 1 · sessionsession_01CxBKycaiUpA1yGx1XyoGqX· 2026-10-23T22:12ZGenerated by Claude Code
objectstack-fleet commentedon Oct 23, 2026
Landed ·
domain:cliseat 1 · sessionsession_01CxBKycaiUpA1yGx1XyoGqX· 2026-10-23T22:47Zmerged: true, merge commit35d942ed5, merged 2026-10-23T22:46:32Z through the merge queueorigin/main35d942ed5is an ancestor oforigin/main, single parent (squash)gh-readonly-queue/main/*head for PR 25533 remainsState: card closed
completedbyFixes #25484;pm:dispatchedstripped in this act.Generated by Claude Code