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
rest: three doors answer an unknown resource as empty or bad input (publish-drafts 200, flow runs 200 [], datasource PATCH 400) where their siblings answer 404 #25486
Filing gate: ① a product defect with named locations and a live reproduction (finding class (a); one closing card for the family, per-door rows below).
Reader: the packages/rest lane (the three doors in the table below), next PR touching not-found answers on these routes.
Dedupe (open AND closed): "unknown resource answers 200 empty instead of 404 publish-drafts flow runs datasource PATCH" → 27 hits, none on this (nearest: #24768, NOT NULL door parity; #23495, draft doors with no package — both different defects).
reach: public HTTP entry, measured on a fresh boot — all three doors answered as tabulated.
Checklist item priority: P0
type: Bug
QA-source: #25478 · studio-authoring.first-run-loop · observed beside the clauses
Three doors answer a request about a resource that does not exist as if it were an empty or bad request, while their sibling doors answer 404 for the same name. An AI or SDK caller cannot tell "nothing there yet" from "no such thing" (AGENTS.md route rules 3 and 4: absence must be loud, machine-readable surfaces must not lie).
Re-measured by the lead on a fresh stock showcase boot, main77392e9, as admin:
door
answer
sibling for the same name
POST /api/v1/packages/com.example.nope/publish-drafts {}
400 DATASOURCE_ADMIN_ERROR "Datasource 'no_such_ds' not found."
GET /api/v1/datasources/no_such_ds → 404 RESOURCE_NOT_FOUND
Expected: each answers 404 with the same code as its sibling, naming the missing resource. An empty runs list and a nothing_to_publish outcome are reserved for resources that exist.
(Found by off-clause probing in QA runs #25478 and #25479: the datasource door beside integration-system.datasource-credential-refusal-matrix, and the runs door beside automation.flow-node-type-matrix.)
Triage: lands in three doors in the server lane: the package publish-drafts route, GET /api/v1/automation/:name/runs, and PATCH /api/v1/datasources/:name (the closing card for this family, per its table); rationale: finding class (a), reach measured on a fresh boot. Each door must answer 404 RESOURCE_NOT_FOUND like its sibling, so an AI or SDK caller can tell "no such thing" from "nothing yet" (AGENTS.md route rules 3–4).
Path: api · 错误信封与路由台账 | api-backend.error-envelope-ledger (P1, passes) | step ②
Grade: priority:p2 — the card's Checklist item priority: P0 names the QA-source item (studio-authoring.first-run-loop), which passes; these were found beside it. A wrong-but-working answer is P2 under North Star rule 2.
Type: Bug
Session: session_01AkmasAfXt3cu83xGzkuKhe (objectstack triage, run on the maintainer's request in the project thread)
Claim: PM loop round 1
Session: session_01JVTd3PprStGa26aez5omLB
Account: sunhaolin (the seat's linked user as GET /user answers it; the card's assignee)
Branch: claude/issue-25486-unknown-resource-404
Worktree: objectstack-issue-25486
Domain: domain:server
Seat: domain:server#2
File surface: packages/runtime/src/domains/** and packages/runtime/src/dispatcher-plugin.ts (the package publish-drafts route and GET /automation/:name/runs), packages/services/service-datasource/src/** (PATCH /datasources/:name), packages/metadata-protocol/src/protocol.ts only if the publish-drafts absence must be decided there (⛔ not a region an open #25467 slice moves), tests, one changeset (stop on breach; explain in the report)
Container & model: M, mode:cloud (maintainer: 「并发加到5,可以用云卡片」), model: TIER_DEFAULT (dispatch-gates --tier: "no path-derived mandate … the tier stays the PM's per-card judgment call"), effort: high
Clause-②: no
Metadata-protocol: none
Responsibility: the three handlers produce it (the runtime's packages and automation domains, service-datasource's admin service throwing a plain Error that its route maps to DATASOURCE_ADMIN_ERROR 400) | the sibling doors already answer 404 RESOURCE_NOT_FOUND for the same names (POST /automation/:name/trigger, GET /datasources/:name), so the fix reuses that answer | any API, SDK or AI caller reaches all three over public HTTP; triage's audit 6370289652 records them measured on a fresh boot
Thread-read: 6370289652
Serial constraints cleared: PR #25601 (seat 1, #25467 slice) moves the lock gate and OCC write addresses in protocol.ts; this card stays out of every region an open #25467 slice moves. No open PR touches packages/runtime/src/domains/**, dispatcher-plugin.ts or service-datasource (13 open PRs read this fire).
Premise: re-read on origin/maine649c2d91: datasource-admin-service.ts still throws a plain Error("Datasource '…' not found.") on update; the dispatcher still mounts /:id/publish-drafts and /automation/:name/runs.
os-dev-report
{
"issue": 25486,
"status": "done",
"branch": "claude/issue-25486-unknown-resource-404",
"pr": "#25628",
"session": "session_01X15AahTqrmeh8gAVdpKMCK",
"premise_still_valid": true,
"summary": "All three doors now answer an unknown resource 404 RESOURCE_NOT_FOUND, naming it in the same words as the sibling door. Datasource (A1 confirmed): DatasourceAdminService's three by-name sites (updateDatasource, removeDatasource, migrateCredential) threw a plain Error. They now throw datasourceNotFoundError (code RESOURCE_NOT_FOUND, status 404), and admin-routes badRequest relays that envelope, so PATCH, DELETE and migrate-credential match GET. Runs (A2 confirmed): GET /automation/:name/runs reuses the trigger door's flowIsUnknown probe and message. Publish-drafts (A3 measured): absence is decided in the runtime packages domain against registry.getPackage, the authority GET /packages/:id uses, and only when the protocol's answer was empty, so protocol.ts is untouched. discard-drafts, the same door's twin with the identical defect, is fixed in place under the rule-3 bounded exemption. The card's claim was found and named this branch. The card's assignee was not touched. No new error code.",
"tests": "Pre-merge head 8247e6c, through os-verify-lock: runtime full: Test Files 442 passed (442), Tests 7045 passed and 19 skipped (7064). service-datasource full: Test Files 57 passed (57), Tests 975 passed (975). Post-merge head 7da7e5a (origin/main 481ac62 merged; the incoming deps spec, objectql, metadata-protocol, service-automation, rest, plugin-auth, plugin-approvals, plugin-security, service-analytics and driver-sql were rebuilt): runtime 5 files, 98 tests, all passed; service-datasource 3 files, 68 tests, all passed (the new pins, the flipped pins, the sibling suites); typecheck for both packages exit 0; eslint on the touched files exit 0. Ablation, one per changed behaviour, through scripts/ablation-replace.mjs in wrap mode (anchor 1 to 0 on disk, blob changed, restored blob equal to HEAD, git diff HEAD empty; the subjects are imported from src, so no dist leg): admin-routes 404 relay off gives datasource-unknown-name 4 failed, 2 passed; runs flowIsUnknown off gives automation-runs-unknown-flow 3 failed, 1 passed; packageIsUnknown forced false gives packages-draft-doors-unknown-package 3 failed, 3 passed. The controls stayed green.",
"gates": "dispatch-gates --commands --repo objectstack-ai/objectstack derived 72 families. Each was run with its exit code captured before any pipe; --ran on the exit-coded record: 72 derived, 71 run, 1 NOT-MEASURED. 71 exited 0. node scripts/check-plugin-teardown-shape.mjs --self-test first exited 3 (shallow clone, pinned fixture commit 621a487 missing); after fetching that commit it exited 0. NOT MEASURED: pnpm check:dual-build-cjs-loads exit 3, PREREQUISITE NOT MET: it needs every package's dist and only the closure was built; CI Lint and Repo Gates runs it. Ratchet subset re-run on head 7da7e5a, all exit 0: type-check-coverage, type-check-debt, durability-log-level, cross-package-test-inputs, test-source-alias, nul-bytes, issue-citations, doc-authoring, adr-0087-registration, changeset-no-major, empty-changeset, error-status-conformance, dispatcher-error-vocabulary, route-envelope. Lint (separate from dispatch-gates): pnpm exec eslint --no-inline-config on the 4 runtime files and service-datasource/src, exit 0. PR check-runs: in_progress when this report was written, not waited on.",
"dogfood_battery": "n/a — no ! in the PR title and no (narrowing) arm",
"mcp_calls": "3 — mcp__github__issue_read x2 (get, get_comments on #25486), mcp__github__pull_request_read x1 (get #25628). No write tool.",
"api_writes": "2 relay strokes, each a POST /repos/objectstack-ai/objectstack/dispatches. (1) pr_create, which executed POST /repos/objectstack-ai/objectstack/pulls (draft #25628) and POST /repos//issues/25628/assignees [sunhaolin]. (2) This report: POST /repos//issues/25486/comments, via post-stamped.mjs. git push is not counted as a REST write.",
"fixed_overhead": "pnpm install --frozen-lockfile: 50 s wall (real 0m49.9s). Turbo remote-cache pull: 0 s, unavailable: TURBO_TOKEN and TURBO_TEAM are unset in this container (checked for presence only, values never printed). The build closure (runtime and service-datasource dependencies, 33 packages, pnpm --filter build) was built locally in 502 s wall under the lock. The post-merge rebuild of 10 moved dependencies took 280 s.",
"files_changed": [
".changeset/25486-unknown-resource-404.md",
"packages/runtime/src/domains/automation.ts",
"packages/runtime/src/domains/packages.ts",
"packages/runtime/src/domains/automation-runs-unknown-flow.test.ts",
"packages/runtime/src/domains/packages-draft-doors-unknown-package.test.ts",
"packages/services/service-datasource/src/admin-routes.ts",
"packages/services/service-datasource/src/datasource-admin-service.ts",
"packages/services/service-datasource/src/datasource-route-ledger.ts",
"packages/services/service-datasource/src/tests/datasource-unknown-name.test.ts",
"packages/services/service-datasource/src/tests/datasource-admin-service.test.ts",
"packages/services/service-datasource/src/tests/envelope.conformance.test.ts"
],
"line_budget": "+526 / -16 across 11 files (dispatch-gates: 542 changed lines, under the 3000 threshold). No governed surface.",
"deviations": [
"dispatcher-plugin.ts was not edited: the publish-drafts route body lives in packages/runtime/src/domains/packages.ts, inside the declared runtime/src/domains surface.",
"discard-drafts was fixed in place, the twin door, under the rule-3 bounded exemption (same file, already in the claim's surface); the PR body names it.",
"The platform's PR-subscription posture was not taken: os-dev.md's dispatch contract overrides it, so no CI wait or poll.",
"pnpm check:dual-build-cjs-loads: NOT MEASURED (exit 3, needs a full repo build)."
],
"open_questions": [
{
"question": "Runs door: a flow deleted from the registry whose run history is still in the store now answers 404 instead of its old runs. Acceptable?",
"options": [
"A keep: the card's ruling reserves an empty page for flows that exist, and the trigger door already says this flow does not exist",
"B list the history of an unregistered name too, which needs a store-side existence question and contradicts the sibling"
],
"recommendation": "A, because it is the card's Expected verbatim and keeps the two doors agreeing; recorded as a trade-off, not a fork"
},
{
"question": "Publish-drafts: decide absence after the protocol answers (implemented) or before calling it?",
"options": [
"A after, only when the answer was empty: orphan drafts for an unregistered id still publish; protocol.ts untouched",
"B before, from the registry alone: also refuses publishing orphan drafts, a behaviour change beyond the card"
],
"recommendation": "A, because it flips exactly the answer that lied and stays out of every #25467 region"
}
],
"out_of_scope_findings": [
"class: a · reach: NOT MEASURED, a read inference: DELETE /api/v1/automation/:name calls unregisterAndDeleteFlow with no existence probe, so it may answer 200 { deleted: true } for an unknown flow · evidence: packages/runtime/src/domains/automation.ts DELETE /:name branch · dedupe words: automation delete unknown flow 200 deleted · carrier: none; noted in Acceptance notes only (no measured reach)",
"class: a · reach: NOT MEASURED: GET /api/v1/packages/:id/commits for an unknown id likely 200 { commits: [] } · evidence: packages.ts commits branch calls protocol.listCommits with no registry check · dedupe words: package commits unknown id empty list 404 · carrier: none; Acceptance notes only",
"class: a · reach: NOT MEASURED: the external-datasource by-name doors (remote-tables, :name/test, object-draft) for an unknown datasource; external-datasource-service.ts says this is deliberately not a throw · dedupe words: external datasource unknown name 400 404 · carrier: none; Acceptance notes only",
"objectui consumers to check (not attached to this session): the Studio Publish app and Discard drafts actions (packages.publishDrafts / discardDrafts) and the flow run-history panel (automation.listRuns). The SDK now rejects for an unknown id, where it used to resolve an empty result. carrier: the seat"
]
}
os-dev-report
{
"issue": 25486,
"status": "done",
"addendum": "patch round requested by the seat (session_01JVTd3PprStGa26aez5omLB): the two in-file family members, measured and then fixed in place; changed fields only",
"pr": "#25628",
"summary": "Both doors were measured at the dispatcher with real-shaped doubles before the fix, and both answered as suspected. GET /packages/:id/commits gave 200 { commits: [] } for an unknown package. DELETE /automation/:name gave 200 { name, deleted: true } for an unknown flow: unregisterFlow is a no-op for that name, and deleteMetaItem answers reset false, nothing to delete. Both are fixed in place in the files this PR already edits. Commits uses the packageIsUnknown / registry.getPackage authority, only when the trail is empty. Delete uses flowIsUnknown and flowNotFoundMessage, only when the store deleted no row, so a stored flow the engine does not hold is still deletable. Both answer 404 RESOURCE_NOT_FOUND naming the resource. The changeset door list now has eight doors. The external-datasource doors are left; the reason is given in body_patch. The PR body was not edited: os-dev writes it once at pr_create. Exact text for the seat is in body_patch.",
"tests": "Head 71d6c23. The unfixed code was measured first: the 4 new 404 and parity cases failed with expected 200 to be 404 (Tests 4 failed, 10 passed). After the fix, targeted (new pins plus automation-authoring-doors-durable, automation-packaged-base-lock, automation-write-capability-gate): 5 files, 66 tests, all passed. Runtime full, through os-verify-lock: Test Files 443 passed (443), Tests 7053 passed and 19 skipped (7072). Runtime typecheck exit 0. Ablations through scripts/ablation-replace.mjs in wrap mode: the delete-door 404 disabled gives Tests 2 failed, 2 passed; the commits-door packageIsUnknown forced false gives Tests 2 failed, 8 passed. Both restored with blob equal to HEAD and git diff HEAD empty. service-datasource is untouched this round.",
"gates": "dispatch-gates --commands at 71d6c23 derived 72 families, the same set as before. All were run with exit codes captured before any pipe. --ran: 72 derived, 71 run, 1 NOT-MEASURED. 71 exited 0. NOT MEASURED: pnpm check:dual-build-cjs-loads, exit 3 (needs every package's dist). eslint --no-inline-config on the 4 touched runtime files: exit 0.",
"mcp_calls": "3 — unchanged; no MCP call this round",
"api_writes": "3 relay strokes in total. Stroke 3 is this addendum: POST /repos/objectstack-ai/objectstack/dispatches executing POST /repos//issues/25486/comments. git push is not a REST write.",
"fixed_overhead": "Patch round, after the worktree was recreated: pnpm install 35 s wall; build closure 481 s wall (no turbo remote cache, TURBO_TOKEN unset).",
"files_changed": [
"+ packages/runtime/src/domains/automation-delete-unknown-flow.test.ts (new)",
"packages/runtime/src/domains/automation.ts",
"packages/runtime/src/domains/packages.ts",
"packages/runtime/src/domains/packages-draft-doors-unknown-package.test.ts",
".changeset/25486-unknown-resource-404.md"
],
"line_budget": "+178 / -16 this round across 5 files (git diff 7da7e5a..71d6c23).",
"out_of_scope_findings": [
"class: a · the external-datasource by-name doors: left as they are; external-datasource-service.ts records not throwing as deliberate · carrier: none"
],
"body_patch": "Insert these two rows into the What changes table, after the discard-drafts row; change "Six doors" to "Eight doors" in the paragraph above it:\n| GET /api/v1/packages/:id/commits (unknown id) | 200 { commits: [] } | same 404 | GET /api/v1/packages/:id |\n| DELETE /api/v1/automation/:name (unknown flow) | 200 { name, deleted: true } | 404 \"Flow 'NAME' not found\" | POST /api/v1/automation/:name/trigger |\n\nAdd these two bullets to "Where absence is decided, per door":\n- Commit trail (patch round). Measured before the fix: 200 { commits: [] } for an unknown id. GET /packages/:id/commits now applies the same packageIsUnknown / registry.getPackage rule as the draft doors, after listCommits answers, and only when the trail is empty. A trail that outlived its package's registry entry is still listed.\n- Flow delete (patch round). Measured before the fix: 200 { name, deleted: true } for an unknown flow. unregisterFlow is a no-op for a name the engine does not hold, and deleteMetaItem answers a name with no row { success: true, reset: false } ("nothing to delete"). unregisterAndDeleteFlow now asks flowIsUnknown, the trigger door's probe, and answers 404 with flowNotFoundMessage only when the engine did not hold the flow AND the store deleted no row (reset !== true). A stored flow that the engine does not hold, such as one that failed to register, is still deleted and answered 200.\n\nAdd these bullets to Pins:\n - runtime/src/domains/automation-delete-unknown-flow.test.ts (new): 404 with code and status, naming the flow; parity with POST /:name/trigger; controls: an engine-held flow is still deleted with 200, and a stored-only flow is still deleted from the store with 200.\n - runtime/src/domains/packages-draft-doors-unknown-package.test.ts: a new GET /packages/:id/commits block covering 404, parity with GET /packages/:id, a known empty trail (200 []), and the trail of an unregistered id that has commits (200).\n\nAdd to the Verification record:\n- Patch round, head 71d6c23c4. Measured on the unfixed code: the four new 404 and parity cases failed with expected 200 to be 404. After the fix, runtime full: Test Files 443 passed (443), Tests 7053 passed and 19 skipped (7072). Runtime typecheck exit 0. eslint on the touched files exit 0. dispatch-gates --ran: 72 derived, 71 run, 1 NOT-MEASURED (check:dual-build-cjs-loads, exit 3, as before). Ablations: unknownToEngine && !rowDeleted disabled gives automation-delete-unknown-flow Tests 2 failed, 2 passed. packageIsUnknown on the commits door forced false gives packages-draft-doors-unknown-package Tests 2 failed, 8 passed. Both restored with blob equal to HEAD and git diff HEAD empty.\n\nReplace the Acceptance notes with:\n- The external-datasource by-name doors (GET /api/v1/datasources/:name/remote-tables, POST …/:name/test, POST …/:name/object-draft) are left as they are. external-datasource-service.ts records not throwing for an unknown datasource as deliberate: whether an unknown datasource is an error there is a question about the driver registration, and that service's comment declines to upgrade it to a throw.\n- PublishPackageDraftsResponseSchema.outcome's describe says nothing_to_publish means "the package had no pending drafts". That is still true at the protocol. The REST door now answers 404 before an unknown package reaches it. No spec text is changed here (Metadata-protocol: none)."
}
Pointer: PR #25628 ACCEPTed by domain:server seat 2 (record 6386121090, head f3e50da5f4) · 2026-10-24T15:38Z
The PR now declares Clause-②: no (narrowing) (fix(rest)!:, patch, BREAKING banner, ADR-0087 not-required). It waits on one CONTRACT_REVIEW_TIER review by the director seat (needs:contract-review on the PR) and stays draft until that PASS is on its head. This card stays pm:dispatched.
Activity
objectstack-fleet commentedon Oct 23, 2026
Triage: lands in three doors in the server lane: the package
publish-draftsroute,GET /api/v1/automation/:name/runs, andPATCH /api/v1/datasources/:name(the closing card for this family, per its table); rationale: finding class (a), reach measured on a fresh boot. Each door must answer404 RESOURCE_NOT_FOUNDlike its sibling, so an AI or SDK caller can tell "no such thing" from "nothing yet" (AGENTS.md route rules 3–4).Path: api · 错误信封与路由台账 | api-backend.error-envelope-ledger (P1, passes) | step ②
Grade:
priority:p2— the card'sChecklist item priority: P0names the QA-source item (studio-authoring.first-run-loop), which passes; these were found beside it. A wrong-but-working answer is P2 under North Star rule 2.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 24, 2026
Claim: PM loop round 1
Session:
session_01JVTd3PprStGa26aez5omLBAccount:
sunhaolin(the seat's linked user asGET /useranswers it; the card's assignee)Branch:
claude/issue-25486-unknown-resource-404Worktree:
objectstack-issue-25486Domain:
domain:serverSeat:
domain:server#2File surface:
packages/runtime/src/domains/**andpackages/runtime/src/dispatcher-plugin.ts(the packagepublish-draftsroute andGET /automation/:name/runs),packages/services/service-datasource/src/**(PATCH /datasources/:name),packages/metadata-protocol/src/protocol.tsonly if thepublish-draftsabsence must be decided there (⛔ not a region an open #25467 slice moves), tests, one changeset (stop on breach; explain in the report)Container & model:
M,mode:cloud(maintainer: 「并发加到5,可以用云卡片」),model: TIER_DEFAULT(dispatch-gates --tier: "no path-derived mandate … the tier stays the PM's per-card judgment call"),effort: highClause-②: no
Metadata-protocol: none
Responsibility: the three handlers produce it (the runtime's packages and automation domains,
service-datasource's admin service throwing a plainErrorthat its route maps toDATASOURCE_ADMIN_ERROR400) | the sibling doors already answer404 RESOURCE_NOT_FOUNDfor the same names (POST /automation/:name/trigger,GET /datasources/:name), so the fix reuses that answer | any API, SDK or AI caller reaches all three over public HTTP; triage's audit 6370289652 records them measured on a fresh bootThread-read: 6370289652
Serial constraints cleared: PR #25601 (seat 1, #25467 slice) moves the lock gate and OCC write addresses in
protocol.ts; this card stays out of every region an open #25467 slice moves. No open PR touchespackages/runtime/src/domains/**,dispatcher-plugin.tsorservice-datasource(13 open PRs read this fire).Premise: re-read on
origin/maine649c2d91:datasource-admin-service.tsstill throws a plainError("Datasource '…' not found.")on update; the dispatcher still mounts/:id/publish-draftsand/automation/:name/runs.Generated by Claude Code
objectstack-fleet commentedon Oct 24, 2026
os-dev-report
{
"issue": 25486,
"status": "done",
"branch": "claude/issue-25486-unknown-resource-404",
"pr": "#25628",
"session": "session_01X15AahTqrmeh8gAVdpKMCK",
"premise_still_valid": true,
"summary": "All three doors now answer an unknown resource 404 RESOURCE_NOT_FOUND, naming it in the same words as the sibling door. Datasource (A1 confirmed): DatasourceAdminService's three by-name sites (updateDatasource, removeDatasource, migrateCredential) threw a plain Error. They now throw datasourceNotFoundError (code RESOURCE_NOT_FOUND, status 404), and admin-routes badRequest relays that envelope, so PATCH, DELETE and migrate-credential match GET. Runs (A2 confirmed): GET /automation/:name/runs reuses the trigger door's flowIsUnknown probe and message. Publish-drafts (A3 measured): absence is decided in the runtime packages domain against registry.getPackage, the authority GET /packages/:id uses, and only when the protocol's answer was empty, so protocol.ts is untouched. discard-drafts, the same door's twin with the identical defect, is fixed in place under the rule-3 bounded exemption. The card's claim was found and named this branch. The card's assignee was not touched. No new error code.",
"tests": "Pre-merge head 8247e6c, through os-verify-lock: runtime full: Test Files 442 passed (442), Tests 7045 passed and 19 skipped (7064). service-datasource full: Test Files 57 passed (57), Tests 975 passed (975). Post-merge head 7da7e5a (origin/main 481ac62 merged; the incoming deps spec, objectql, metadata-protocol, service-automation, rest, plugin-auth, plugin-approvals, plugin-security, service-analytics and driver-sql were rebuilt): runtime 5 files, 98 tests, all passed; service-datasource 3 files, 68 tests, all passed (the new pins, the flipped pins, the sibling suites); typecheck for both packages exit 0; eslint on the touched files exit 0. Ablation, one per changed behaviour, through scripts/ablation-replace.mjs in wrap mode (anchor 1 to 0 on disk, blob changed, restored blob equal to HEAD, git diff HEAD empty; the subjects are imported from src, so no dist leg): admin-routes 404 relay off gives datasource-unknown-name 4 failed, 2 passed; runs flowIsUnknown off gives automation-runs-unknown-flow 3 failed, 1 passed; packageIsUnknown forced false gives packages-draft-doors-unknown-package 3 failed, 3 passed. The controls stayed green.",
"gates": "dispatch-gates --commands --repo objectstack-ai/objectstack derived 72 families. Each was run with its exit code captured before any pipe; --ran on the exit-coded record: 72 derived, 71 run, 1 NOT-MEASURED. 71 exited 0. node scripts/check-plugin-teardown-shape.mjs --self-test first exited 3 (shallow clone, pinned fixture commit 621a487 missing); after fetching that commit it exited 0. NOT MEASURED: pnpm check:dual-build-cjs-loads exit 3, PREREQUISITE NOT MET: it needs every package's dist and only the closure was built; CI Lint and Repo Gates runs it. Ratchet subset re-run on head 7da7e5a, all exit 0: type-check-coverage, type-check-debt, durability-log-level, cross-package-test-inputs, test-source-alias, nul-bytes, issue-citations, doc-authoring, adr-0087-registration, changeset-no-major, empty-changeset, error-status-conformance, dispatcher-error-vocabulary, route-envelope. Lint (separate from dispatch-gates): pnpm exec eslint --no-inline-config on the 4 runtime files and service-datasource/src, exit 0. PR check-runs: in_progress when this report was written, not waited on.",
"dogfood_battery": "n/a — no ! in the PR title and no (narrowing) arm",
"mcp_calls": "3 — mcp__github__issue_read x2 (get, get_comments on #25486), mcp__github__pull_request_read x1 (get #25628). No write tool.",
"api_writes": "2 relay strokes, each a POST /repos/objectstack-ai/objectstack/dispatches. (1) pr_create, which executed POST /repos/objectstack-ai/objectstack/pulls (draft #25628) and POST /repos//issues/25628/assignees [sunhaolin]. (2) This report: POST /repos//issues/25486/comments, via post-stamped.mjs. git push is not counted as a REST write.",
"fixed_overhead": "pnpm install --frozen-lockfile: 50 s wall (real 0m49.9s). Turbo remote-cache pull: 0 s, unavailable: TURBO_TOKEN and TURBO_TEAM are unset in this container (checked for presence only, values never printed). The build closure (runtime and service-datasource dependencies, 33 packages, pnpm --filter build) was built locally in 502 s wall under the lock. The post-merge rebuild of 10 moved dependencies took 280 s.",
"files_changed": [
".changeset/25486-unknown-resource-404.md",
"packages/runtime/src/domains/automation.ts",
"packages/runtime/src/domains/packages.ts",
"packages/runtime/src/domains/automation-runs-unknown-flow.test.ts",
"packages/runtime/src/domains/packages-draft-doors-unknown-package.test.ts",
"packages/services/service-datasource/src/admin-routes.ts",
"packages/services/service-datasource/src/datasource-admin-service.ts",
"packages/services/service-datasource/src/datasource-route-ledger.ts",
"packages/services/service-datasource/src/tests/datasource-unknown-name.test.ts",
"packages/services/service-datasource/src/tests/datasource-admin-service.test.ts",
"packages/services/service-datasource/src/tests/envelope.conformance.test.ts"
],
"line_budget": "+526 / -16 across 11 files (dispatch-gates: 542 changed lines, under the 3000 threshold). No governed surface.",
"deviations": [
"dispatcher-plugin.ts was not edited: the publish-drafts route body lives in packages/runtime/src/domains/packages.ts, inside the declared runtime/src/domains surface.",
"discard-drafts was fixed in place, the twin door, under the rule-3 bounded exemption (same file, already in the claim's surface); the PR body names it.",
"The platform's PR-subscription posture was not taken: os-dev.md's dispatch contract overrides it, so no CI wait or poll.",
"pnpm check:dual-build-cjs-loads: NOT MEASURED (exit 3, needs a full repo build)."
],
"open_questions": [
{
"question": "Runs door: a flow deleted from the registry whose run history is still in the store now answers 404 instead of its old runs. Acceptable?",
"options": [
"A keep: the card's ruling reserves an empty page for flows that exist, and the trigger door already says this flow does not exist",
"B list the history of an unregistered name too, which needs a store-side existence question and contradicts the sibling"
],
"recommendation": "A, because it is the card's Expected verbatim and keeps the two doors agreeing; recorded as a trade-off, not a fork"
},
{
"question": "Publish-drafts: decide absence after the protocol answers (implemented) or before calling it?",
"options": [
"A after, only when the answer was empty: orphan drafts for an unregistered id still publish; protocol.ts untouched",
"B before, from the registry alone: also refuses publishing orphan drafts, a behaviour change beyond the card"
],
"recommendation": "A, because it flips exactly the answer that lied and stays out of every #25467 region"
}
],
"out_of_scope_findings": [
"class: a · reach: NOT MEASURED, a read inference: DELETE /api/v1/automation/:name calls unregisterAndDeleteFlow with no existence probe, so it may answer 200 { deleted: true } for an unknown flow · evidence: packages/runtime/src/domains/automation.ts DELETE /:name branch · dedupe words: automation delete unknown flow 200 deleted · carrier: none; noted in Acceptance notes only (no measured reach)",
"class: a · reach: NOT MEASURED: GET /api/v1/packages/:id/commits for an unknown id likely 200 { commits: [] } · evidence: packages.ts commits branch calls protocol.listCommits with no registry check · dedupe words: package commits unknown id empty list 404 · carrier: none; Acceptance notes only",
"class: a · reach: NOT MEASURED: the external-datasource by-name doors (remote-tables, :name/test, object-draft) for an unknown datasource; external-datasource-service.ts says this is deliberately not a throw · dedupe words: external datasource unknown name 400 404 · carrier: none; Acceptance notes only",
"objectui consumers to check (not attached to this session): the Studio Publish app and Discard drafts actions (packages.publishDrafts / discardDrafts) and the flow run-history panel (automation.listRuns). The SDK now rejects for an unknown id, where it used to resolve an empty result. carrier: the seat"
]
}
Generated by Claude Code
objectstack-fleet commentedon Oct 24, 2026
os-dev-report
{
"issue": 25486,
"status": "done",
"addendum": "patch round requested by the seat (session_01JVTd3PprStGa26aez5omLB): the two in-file family members, measured and then fixed in place; changed fields only",
"pr": "#25628",
"summary": "Both doors were measured at the dispatcher with real-shaped doubles before the fix, and both answered as suspected. GET /packages/:id/commits gave 200 { commits: [] } for an unknown package. DELETE /automation/:name gave 200 { name, deleted: true } for an unknown flow: unregisterFlow is a no-op for that name, and deleteMetaItem answers reset false, nothing to delete. Both are fixed in place in the files this PR already edits. Commits uses the packageIsUnknown / registry.getPackage authority, only when the trail is empty. Delete uses flowIsUnknown and flowNotFoundMessage, only when the store deleted no row, so a stored flow the engine does not hold is still deletable. Both answer 404 RESOURCE_NOT_FOUND naming the resource. The changeset door list now has eight doors. The external-datasource doors are left; the reason is given in body_patch. The PR body was not edited: os-dev writes it once at pr_create. Exact text for the seat is in body_patch.",
"tests": "Head 71d6c23. The unfixed code was measured first: the 4 new 404 and parity cases failed with expected 200 to be 404 (Tests 4 failed, 10 passed). After the fix, targeted (new pins plus automation-authoring-doors-durable, automation-packaged-base-lock, automation-write-capability-gate): 5 files, 66 tests, all passed. Runtime full, through os-verify-lock: Test Files 443 passed (443), Tests 7053 passed and 19 skipped (7072). Runtime typecheck exit 0. Ablations through scripts/ablation-replace.mjs in wrap mode: the delete-door 404 disabled gives Tests 2 failed, 2 passed; the commits-door packageIsUnknown forced false gives Tests 2 failed, 8 passed. Both restored with blob equal to HEAD and git diff HEAD empty. service-datasource is untouched this round.",
"gates": "dispatch-gates --commands at 71d6c23 derived 72 families, the same set as before. All were run with exit codes captured before any pipe. --ran: 72 derived, 71 run, 1 NOT-MEASURED. 71 exited 0. NOT MEASURED: pnpm check:dual-build-cjs-loads, exit 3 (needs every package's dist). eslint --no-inline-config on the 4 touched runtime files: exit 0.",
"mcp_calls": "3 — unchanged; no MCP call this round",
"api_writes": "3 relay strokes in total. Stroke 3 is this addendum: POST /repos/objectstack-ai/objectstack/dispatches executing POST /repos//issues/25486/comments. git push is not a REST write.",
"fixed_overhead": "Patch round, after the worktree was recreated: pnpm install 35 s wall; build closure 481 s wall (no turbo remote cache, TURBO_TOKEN unset).",
"files_changed": [
"+ packages/runtime/src/domains/automation-delete-unknown-flow.test.ts (new)",
"packages/runtime/src/domains/automation.ts",
"packages/runtime/src/domains/packages.ts",
"packages/runtime/src/domains/packages-draft-doors-unknown-package.test.ts",
".changeset/25486-unknown-resource-404.md"
],
"line_budget": "+178 / -16 this round across 5 files (git diff 7da7e5a..71d6c23).",
"out_of_scope_findings": [
"class: a · the external-datasource by-name doors: left as they are; external-datasource-service.ts records not throwing as deliberate · carrier: none"
],
"body_patch": "Insert these two rows into the What changes table, after the discard-drafts row; change "Six doors" to "Eight doors" in the paragraph above it:\n|
GET /api/v1/packages/:id/commits(unknown id) |200 { commits: [] }| same404|GET /api/v1/packages/:id|\n|DELETE /api/v1/automation/:name(unknown flow) |200 { name, deleted: true }|404 \"Flow 'NAME' not found\"|POST /api/v1/automation/:name/trigger|\n\nAdd these two bullets to "Where absence is decided, per door":\n- Commit trail (patch round). Measured before the fix:200 { commits: [] }for an unknown id.GET /packages/:id/commitsnow applies the samepackageIsUnknown/registry.getPackagerule as the draft doors, afterlistCommitsanswers, and only when the trail is empty. A trail that outlived its package's registry entry is still listed.\n- Flow delete (patch round). Measured before the fix:200 { name, deleted: true }for an unknown flow.unregisterFlowis a no-op for a name the engine does not hold, anddeleteMetaItemanswers a name with no row{ success: true, reset: false }("nothing to delete").unregisterAndDeleteFlownow asksflowIsUnknown, the trigger door's probe, and answers404withflowNotFoundMessageonly when the engine did not hold the flow AND the store deleted no row (reset !== true). A stored flow that the engine does not hold, such as one that failed to register, is still deleted and answered200.\n\nAdd these bullets to Pins:\n -runtime/src/domains/automation-delete-unknown-flow.test.ts(new): 404 withcodeandstatus, naming the flow; parity withPOST /:name/trigger; controls: an engine-held flow is still deleted with200, and a stored-only flow is still deleted from the store with200.\n -runtime/src/domains/packages-draft-doors-unknown-package.test.ts: a newGET /packages/:id/commitsblock covering 404, parity withGET /packages/:id, a known empty trail (200 []), and the trail of an unregistered id that has commits (200).\n\nAdd to the Verification record:\n- Patch round, head71d6c23c4. Measured on the unfixed code: the four new 404 and parity cases failed withexpected 200 to be 404. After the fix, runtime full: Test Files 443 passed (443), Tests 7053 passed and 19 skipped (7072). Runtimetypecheckexit 0. eslint on the touched files exit 0.dispatch-gates --ran: 72 derived, 71 run, 1 NOT-MEASURED (check:dual-build-cjs-loads, exit 3, as before). Ablations:unknownToEngine && !rowDeleteddisabled givesautomation-delete-unknown-flowTests 2 failed, 2 passed.packageIsUnknownon the commits door forced false givespackages-draft-doors-unknown-packageTests 2 failed, 8 passed. Both restored with blob equal to HEAD andgit diff HEADempty.\n\nReplace the Acceptance notes with:\n- Theexternal-datasourceby-name doors (GET /api/v1/datasources/:name/remote-tables,POST …/:name/test,POST …/:name/object-draft) are left as they are.external-datasource-service.tsrecords not throwing for an unknown datasource as deliberate: whether an unknown datasource is an error there is a question about the driver registration, and that service's comment declines to upgrade it to a throw.\n-PublishPackageDraftsResponseSchema.outcome's describe saysnothing_to_publishmeans "the package had no pending drafts". That is still true at the protocol. The REST door now answers 404 before an unknown package reaches it. No spec text is changed here (Metadata-protocol: none)."}
Generated by Claude Code
objectstack-fleet commentedon Oct 24, 2026
Pointer: PR #25628 ACCEPTed by
domain:serverseat 2 (record 6386121090, headf3e50da5f4) · 2026-10-24T15:38ZThe PR now declares
Clause-②: no (narrowing)(fix(rest)!:,patch, BREAKING banner, ADR-0087not-required). It waits on oneCONTRACT_REVIEW_TIERreview by the director seat (needs:contract-reviewon the PR) and stays draft until that PASS is on its head. This card stayspm:dispatched.Generated by Claude Code