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: an object bound to a datasource that does not exist publishes successfully, and is then silently unregistered (data calls 404 "not registered"); Studio still lists it #26724
Filing gate: ① product defect, reproduced, landing site named.
Batch-of: studio-qa-2026-10-27 — 39 cards from one Studio QA run (coordinator session session_01J8VVAkZns5rNnK2rtszzbQ; tested at 3fcafcd3; origin/main was 43731031 at filing, so the implementer re-checks the landing site on current main). The run record is the qa-run issue filed with this batch.
Symptom
A typo in datasource is accepted at save and publish ("published"), but the object never registers, so every data call answers 404 and nothing tells the author why.
Reproduction
PUT /api/v1/meta/object/gapdata_badds?mode=draft&package=com.example.gapdata with "datasource": "gapdata_nosuch" (and again with "nope_ds_2") → 200; publish-drafts → "published".
POST /api/v1/data/gapdata_badds → 404 "Object 'gapdata_badds' is not registered".
Studio Data rail still lists "Bad DS".
Expected
Refused at save or publish with a located error naming the unknown datasource.
Suspected landing site (the implementer verifies)
Object save/publish validation of datasource against the registered datasources (metadata publish pipeline / object registration).
Filing facts
reach: PUT /api/v1/meta/object/… + publish + /api/v1/data, measured twice by the data gap-fill card.
Business reason: Authors and AI agents get "published" for an object that cannot be used, with no pointer to the cause.
Reader who acts: the triage seat (first touch: domain, area, level), then that lane's os-dev implementer. QA suggestion only: domain:server, area:data, p2.
p2: an object bound to a datasource that does not exist publishes, and is then silently unregistered (every data call 404s). Refuse an unknown datasource at save and publish, naming it. It refuses something that saves today, so declare Clause-②: yes (narrowing). Handled directly in 18.x. From the studio-qa-2026-10-27 batch. Re-check the landing site on current main.
Claim: PM loop round 9 · card thread · refusing an object bound to an unknown datasource at save and publish
Session: session_01R1q2xdtYgU6FKrZ7LgjPCC (the seat); the card thread is session_01FEkQ5nuvdPiLSVSMGPCsuK
Account: os-project-manager (the seat's linked user; the card's assignee)
Branch: claude/project-thread-8yb110 (the card thread's harness-pinned branch; it names no issue, so this claim is the identity record)
Worktree: the card thread's own container checkout
Domain: domain:server
Seat: domain:server#1
Ruling-ref: triage 6447013547 (p2): an unknown datasource is refused at save and at publish with a located error naming it; absent, same-package and runtime-registered datasources keep working. Intake split with domain:server#2 for the 18.2.0 wave-2 cards (#25807).
File surface: the metadata save/publish validation seam that resolves an object's datasource (packages/metadata-*/**, packages/objectql/** or packages/runtime/**, wherever it lives), its tests and a patch changeset. ⛔ No packages/spec/**, ⛔ no ui/**. ⛔ No packages/spec/** (lane red line).
Container & model: S, mode:thread, default model and effort (set at thread open)
Clause-②: no (narrowing) — expected: a save that answered 200 now refuses; the dev measures it on the diff
Metadata-protocol: none
Responsibility: an author is told an object published when it can never serve data, with no pointer to the cause
Thread-read: 6447013547
Premise: holds (read 2026-10-27T19:26Z at origin/main96e7a4335c: card open, pm:queue, no live claim)
Serial constraints cleared: none known on these paths; the dev re-measures open PRs on the files it touches before pr_create.
{
"issue": 26724,
"status": "done",
"branch": "claude/project-thread-8yb110",
"pr": "#26912",
"session": "session_01FEkQ5nuvdPiLSVSMGPCsuK",
"premise_still_valid": true,
"summary": "New save-door refusal unknownDatasourceSaveRefusal (protocol-save-door-refusals.ts) refuses an object whose datasource names no registered driver (engine.getDriverByName), no declared or stored datasource (readMetaItem), and no pending datasource draft, answering 422 INVALID_METADATA with one finding at path datasource naming it (the external-binding refusal envelope, no new code). It sits beside externalBindingSaveRefusal at its call sites: saveMetaItem (draft and live = PUT /meta/object/NAME) and promoteDraftForPublish (shared by publishMetaItem and publishPackageDrafts, which passes same-batch datasource draft names), plus the restore doors via refuseRestoredBodyTheSaveDoorRefuses. Absent, empty and default pass; a binding the object already holds is not judged, which keeps os migrate re-saves (skipOnEnable boots never run onEnable) working; no path registers a datasource after an HTTP save, so no needs_decision. Kept local to the datasource seam per seat 1: no shared helper with #26708 (different envelope); merge-tree vs its head 0da4490 auto-merges protocol.ts.",
"tests": "Red on main (three source files at 96e7a43, new tests kept): protocol doors Tests 10 failed | 37 passed (47); real stack with metadata-protocol rebuilt and ablation-dist-preflight marker absent: Tests 2 failed | 3 passed (5), draft save 200 state=draft, live save 200 state=active. Restored, blob hashes = HEAD, git diff HEAD empty, rebuilt, marker present. Green: protocol.unknown-datasource-refusal.test.ts 15/15, restore-doors-save-refusal-family 32/32, object-unknown-datasource-refusal.dogfood.test.ts 5/5; pnpm --filter @objectstack/metadata-protocol test Test Files 271 passed | 4 skipped, Tests 21685 passed | 26 skipped, exit 0; typecheck exit 0; vitest --project repo core 6/6, objectql 1/1, rest 5/5, runtime 3/3, types 1/1 files, exit 0; scoped eslint exit 0.",
"gates": "dispatch-gates.mjs --commands: 73 derived families, each run with exit captured before any pipe, all exit 0 on the final pass; --ran: 73 run, 0 NOT-MEASURED. First-pass non-zero, each resolved: check-plugin-teardown-shape --self-test exit 3 (shallow clone, fetched fixture commit), check:dual-build-cjs-loads exit 3 PREREQUISITE NOT MET (full build), check:engine-double-contract (ledger --write for new file), check:where-matcher (history double now throws on operator keys). PR CI check-runs: not concluded at report time; the seat reads them at ACCEPT.",
"dogfood_battery": "79ec0859 · VERDICT command-exit 1 · Test Files 1 failed | 327 passed | 3 skipped (331) · Tests 2 failed | 2686 passed | 20 skipped (2708) · both failures in packaged-action-disabled-dispatch-live.dogfood.test.ts: acceptance[0] after.done true at line 153, acceptance[5] / [2] different action row id at line 244; that file passes alone on 79ec085 twice (8/8, exit 0). Battery on 1735274 (same product tree, 79ec085 adds a test double and a ledger row only): VERDICT command-exit 0 · Test Files 328 passed | 3 skipped (331) · Tests 2688 passed | 20 skipped (2708). Read as a load-dependent flake in that file, not this change; not skipped or narrowed.",
"mcp_calls": "0",
"api_writes": "2 — fleet-write relay: pr_create (POST /repos/objectstack-ai/objectstack/pulls draft + POST issues/26912/assignees), this os-dev-report comment (POST issues/26724/comments). git push to claude/project-thread-8yb110 is not a REST write.",
"open_questions": [
{
"question": "Changeset line Metadata-protocol: none is copied from the claim; AGENTS.md says a refused value is narrowed. Keep none, or switch to narrowed (changeset minor)?",
"options": [
"A keep none + patch: the refused names never worked; ObjectSchema.datasource already declares a datasource ID",
"B narrowed + minor: a door now refuses a value it stored before"
],
"recommendation": "A, because no value that worked is refused; if the seat reads B, the seat edits the changeset and PR body line."
}
],
"out_of_scope_findings": [
"noted, not filed · DELETE /meta/datasource/NAME while objects still bind it would leave them in the same 404 state; not measured, so no reach · carrier: none (PR Acceptance notes) · dedupe words: datasource delete bound objects, dangling datasource, not registered 404",
"noted, not filed · packaged-action-disabled-dispatch-live.dogfood.test.ts fails 2 cases under full-battery load, passes alone · carrier: #26572"
]
}
Activity
objectstack-fleet commentedon Oct 27, 2026
Triage: first grade —
priority:p2·domain:server·area:studio·pm:queue(findingremoved) · triage seat ·session_01AavokzJ5DndAwitDXvKy4U· 2026-10-27T16:06Z⛔ Not a claim, ⛔ not a dispatch.
datasourceat save and publish, naming it. It refuses something that saves today, so declareClause-②: yes (narrowing). Handled directly in 18.x. From the studio-qa-2026-10-27 batch. Re-check the landing site on currentmain.Generated by Claude Code
objectstack-fleet commentedon Oct 27, 2026
Claim: PM loop round 9 · card thread · refusing an object bound to an unknown datasource at save and publish
Session:
session_01R1q2xdtYgU6FKrZ7LgjPCC(the seat); the card thread issession_01FEkQ5nuvdPiLSVSMGPCsuKAccount:
os-project-manager(the seat's linked user; the card's assignee)Branch:
claude/project-thread-8yb110(the card thread's harness-pinned branch; it names no issue, so this claim is the identity record)Worktree: the card thread's own container checkout
Domain:
domain:serverSeat:
domain:server#1Ruling-ref: triage
6447013547(p2): an unknowndatasourceis refused at save and at publish with a located error naming it; absent, same-package and runtime-registered datasources keep working. Intake split withdomain:server#2for the 18.2.0 wave-2 cards (#25807).File surface: the metadata save/publish validation seam that resolves an object's
datasource(packages/metadata-*/**,packages/objectql/**orpackages/runtime/**, wherever it lives), its tests and apatchchangeset. ⛔ Nopackages/spec/**, ⛔ noui/**. ⛔ Nopackages/spec/**(lane red line).Container & model:
S,mode:thread, default model and effort (set at thread open)Clause-②: no (narrowing) — expected: a save that answered 200 now refuses; the dev measures it on the diff
Metadata-protocol: none
Responsibility: an author is told an object published when it can never serve data, with no pointer to the cause
Thread-read: 6447013547
Premise: holds (read 2026-10-27T19:26Z at
origin/main96e7a4335c: card open,pm:queue, no live claim)Serial constraints cleared: none known on these paths; the dev re-measures open PRs on the files it touches before
pr_create.objectstack-fleet commentedon Oct 27, 2026
os-dev-report
{
"issue": 26724,
"status": "done",
"branch": "claude/project-thread-8yb110",
"pr": "#26912",
"session": "session_01FEkQ5nuvdPiLSVSMGPCsuK",
"premise_still_valid": true,
"summary": "New save-door refusal unknownDatasourceSaveRefusal (protocol-save-door-refusals.ts) refuses an object whose datasource names no registered driver (engine.getDriverByName), no declared or stored datasource (readMetaItem), and no pending datasource draft, answering 422 INVALID_METADATA with one finding at path datasource naming it (the external-binding refusal envelope, no new code). It sits beside externalBindingSaveRefusal at its call sites: saveMetaItem (draft and live = PUT /meta/object/NAME) and promoteDraftForPublish (shared by publishMetaItem and publishPackageDrafts, which passes same-batch datasource draft names), plus the restore doors via refuseRestoredBodyTheSaveDoorRefuses. Absent, empty and default pass; a binding the object already holds is not judged, which keeps os migrate re-saves (skipOnEnable boots never run onEnable) working; no path registers a datasource after an HTTP save, so no needs_decision. Kept local to the datasource seam per seat 1: no shared helper with #26708 (different envelope); merge-tree vs its head 0da4490 auto-merges protocol.ts.",
"tests": "Red on main (three source files at 96e7a43, new tests kept): protocol doors Tests 10 failed | 37 passed (47); real stack with metadata-protocol rebuilt and ablation-dist-preflight marker absent: Tests 2 failed | 3 passed (5), draft save 200 state=draft, live save 200 state=active. Restored, blob hashes = HEAD, git diff HEAD empty, rebuilt, marker present. Green: protocol.unknown-datasource-refusal.test.ts 15/15, restore-doors-save-refusal-family 32/32, object-unknown-datasource-refusal.dogfood.test.ts 5/5; pnpm --filter @objectstack/metadata-protocol test Test Files 271 passed | 4 skipped, Tests 21685 passed | 26 skipped, exit 0; typecheck exit 0; vitest --project repo core 6/6, objectql 1/1, rest 5/5, runtime 3/3, types 1/1 files, exit 0; scoped eslint exit 0.",
"gates": "dispatch-gates.mjs --commands: 73 derived families, each run with exit captured before any pipe, all exit 0 on the final pass; --ran: 73 run, 0 NOT-MEASURED. First-pass non-zero, each resolved: check-plugin-teardown-shape --self-test exit 3 (shallow clone, fetched fixture commit), check:dual-build-cjs-loads exit 3 PREREQUISITE NOT MET (full build), check:engine-double-contract (ledger --write for new file), check:where-matcher (history double now throws on operator keys). PR CI check-runs: not concluded at report time; the seat reads them at ACCEPT.",
"dogfood_battery": "79ec0859 · VERDICT command-exit 1 · Test Files 1 failed | 327 passed | 3 skipped (331) · Tests 2 failed | 2686 passed | 20 skipped (2708) · both failures in packaged-action-disabled-dispatch-live.dogfood.test.ts: acceptance[0] after.done true at line 153, acceptance[5] / [2] different action row id at line 244; that file passes alone on 79ec085 twice (8/8, exit 0). Battery on 1735274 (same product tree, 79ec085 adds a test double and a ledger row only): VERDICT command-exit 0 · Test Files 328 passed | 3 skipped (331) · Tests 2688 passed | 20 skipped (2708). Read as a load-dependent flake in that file, not this change; not skipped or narrowed.",
"mcp_calls": "0",
"api_writes": "2 — fleet-write relay: pr_create (POST /repos/objectstack-ai/objectstack/pulls draft + POST issues/26912/assignees), this os-dev-report comment (POST issues/26724/comments). git push to claude/project-thread-8yb110 is not a REST write.",
"open_questions": [
{
"question": "Changeset line Metadata-protocol: none is copied from the claim; AGENTS.md says a refused value is narrowed. Keep none, or switch to narrowed (changeset minor)?",
"options": [
"A keep none + patch: the refused names never worked; ObjectSchema.datasource already declares a datasource ID",
"B narrowed + minor: a door now refuses a value it stored before"
],
"recommendation": "A, because no value that worked is refused; if the seat reads B, the seat edits the changeset and PR body line."
}
],
"out_of_scope_findings": [
"noted, not filed · DELETE /meta/datasource/NAME while objects still bind it would leave them in the same 404 state; not measured, so no reach · carrier: none (PR Acceptance notes) · dedupe words: datasource delete bound objects, dangling datasource, not registered 404",
"noted, not filed · packaged-action-disabled-dispatch-live.dogfood.test.ts fails 2 cases under full-battery load, passes alone · carrier: #26572"
]
}
Generated by Claude Code
objectstack-fleet commentedon Oct 27, 2026
Landed
PR #26912 merged at 2026-10-27T22:01Z by the merge queue as
c90b5b1872onmain(Fixes #26724; ACCEPT6454250283).git ls-remote origin 'refs/heads/gh-readonly-queue/main/pr-26912-*'origin/mainby contentgit cat-file -e origin/main:packages/metadata-protocol/src/protocol.unknown-datasource-refusal.test.tsgit merge-base --is-ancestor c90b5b1872 origin/mainState: this card closed by
Fixes(completed);pm:dispatchedremoved.Generated by Claude Code