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: the cross-object POST /batch per-op exposure gate keys objects by name, so a legacy ns__short object addressed by its short name keeps the pass-through #27728/#27729 closed on the /data routes #27899
Filing gate: ① product defect with a named fix site (finding, class b: code read, not measured). Rule level only.
Found by: the #27728/#27729 card thread and the contract review of PR #27897. Filed by domain:server seat 3.
What is wrong
PR #27897 makes the /data/:object exposure gate (enforceApiAccess → loadObjectItem) read an object exactly as the data door does (registry.getObject, including the legacy ns__short resolution). A legacy-named object that someone addresses by its short name is then judged by its own enable settings (apiEnabled, apiMethods).
The cross-object POST /batch door's per-op gate (packages/rest/src/batch-routes.ts) still keys its object list by name, so the old short-name pass-through remains on that one door. This predates #27897, and #27897 does not make it worse. As far as we know, no shipped object carries a __ name today.
Expected
The per-op gate reads each op's object through the same read the /data gate now uses, and refuses it the same way. Pin it with a legacy-named object that is not exposed, addressed by its short name.
Business reason
The data doors should all give one answer about whether an object is exposed. This is the last door that answers differently.
Reader: the domain:server lane seat. It is serial behind PR #27897, because it reuses the gate's read.
Dedupe: I searched "batch per-op gate short name ns__short exposure". #27711, #27728 and #27729 cover the import and data doors only.
Filed without domain:*; grading is triage's. Contract review suggests p3.
Claim: PM loop round 2 · 2026-10-30T04:12Z · unlocked from pm:blocked (blocker PR #27897 merged) · seat 3's family (#27728 / #27729), taken by seat 4 because seat 3 is full
Session: session_016WV4cwgfUuNAvcUg3JzBiR (card thread; seat session_016WV4d1Np3hU7RQnA13YZcL)
Account: os-elon-musk
Branch: claude/project-thread-5rhjdy (the card thread's platform-assigned branch — the recorded platform exception; amended by the seat when the thread reported it)
Worktree: objectstack-issue-27899
Domain: domain:server
Seat: domain:server#4
File surface: packages/rest/src/batch-routes.ts (the cross-object POST /batch per-op exposure gate), reusing the read PR #27897 gave the /data gate (packages/rest/src/meta-item-read-gate.ts only if the shared read must be exported there), their tests, .changeset/*; seat amendment on the thread's request: packages/rest/src/rest-server.ts (the /data gate's object read moved into one private method both gates call, put on the BatchRouteHost literal, and the private list read loadObjectItems dropped once its only caller is gone), packages/rest/src/rest.test.ts (only the two #3545 pins that call that private list read by name, re-pointed at its replacement with the same fail-open and log assertions), and comment-only lines in packages/rest/src/meta-item-read-gate.ts (the loadObjectItems docblock) and packages/rest/src/objectql-slot-consumer-census.test.ts (one docblock line); ⛔ no packages/spec, no governed path, no new refusal code (the per-op refusal is the one the /data gate already answers); reported at rule level only on public surfaces
Container & model: M, mode:thread, model: default (TIER_DEFAULT), effort: xhigh (an exposure gate); a CONTRACT_REVIEW_TIER review is owed before approval if the PR declares a narrowing
Clause-②: no (narrowing) (a /batch op that addresses an unexposed legacy-named object by its short name is refused, as the /data door now refuses it; the dev confirms the arm from the diff)
Metadata-protocol: none
Responsibility: the /batch per-op exposure gate keys its object list by name, so it is the last data door whose exposure answer differs from /data's; the platform path that covers it is the one object read PR #27897 gave the /data gate; reached by any caller of POST /batch.
Thread-read: 6514615922
Premise: holds (batch-routes.ts on origin/mainf552fa81 loads its per-op exposure list with loadObjectItems (~L285), not the /data gate's read; PR #27897 merged at 04:06Z)
Serial constraints cleared: PR #27897 (same gate family, meta-item-read-gate.ts) merged; no open PR touches batch-routes.ts or meta-item-read-gate.ts; open draft PR #27713 (#27627) touches rest-server.ts in other regions (an import and a new route method), so whichever lands second merges main.
{
"issue": 27899,
"status": "done",
"branch": "claude/project-thread-5rhjdy",
"pr": "https://github.com/objectstack-ai/objectstack/pull/27963",
"session": "session_016WV4cwgfUuNAvcUg3JzBiR",
"premise_still_valid": true,
"summary": "The cross-object batch door's per-op API-exposure gate now reads each op's object through the same by-name read as the /data/:object gate: that read moved out of enforceApiAccess into one private RestServer#loadObjectItem both gates call, and replaced the private list read loadObjectItems, whose only caller was this gate. An op is judged on the object the data door serves for its name and refused with the data door's status and body before the transaction opens; each distinct object is read once per request. Mechanism assumption held: batch-routes.ts keyed the list by declared name, and the reuse needed no second resolver.",
"tests": "New packages/rest/src/batch-op-exposure-object-read.test.ts (real ObjectQL + protocol + sqlite, objectQLProvider wired, metadata store registered), 9 cases. Red first on base code d4064cc1 with the test only (db7513a4): 'Tests 5 failed | 4 passed (9)' (5 refusal pins red, 4 controls green). Head 2019e190: 9/9; with rest-batch-endpoint, rest, import-door-object-existence, objectql-slot-consumer-census, rest-batch-blank-id: 'Test Files 6 passed (6) · Tests 294 passed (294)'. Ablation via scripts/ablation-replace.mjs (anchor hit 1, blob 0ce906d7 -> 23cf0acd; gate counts the read item only under the op's declared name): 'Tests 5 failed | 4 passed (9)', restored blob == HEAD, git diff HEAD empty (src is the resolution path, no dist leg). pnpm --filter @objectstack/rest test: 'Test Files 351 passed (351) · Tests 6775 passed | 332 skipped (7107)', VERDICT command-exit 0. pnpm --filter @objectstack/rest typecheck: VERDICT command-exit 0.",
"gates": "At head 2019e190 after a whole-tree build (turbo run build --filter=!@objectstack/docs, VERDICT command-exit 0): node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack derived 61 families over 7 paths; all 61 exit 0; pnpm lint exit 0. --ran: '61 derived, 61 run, 0 NOT-MEASURED, 0 UNRUN', pnpm lint outside the derivation. check-adr-0087-registration: '1 declared-breaking changeset(s), each carrying an ADR-0087 disposition.' CI: in_progress at report time (PR just opened).",
"dogfood_battery": "2019e190 · VERDICT command-exit 0 (held the lock 1604s) · Test Files 357 passed | 3 skipped (360) · Tests 2875 passed | 20 skipped (2895) · no failures",
"line_budget": "7 files, +278 / -46 (test 195, changeset 18, code 58 / -39 across batch-routes.ts, rest-server.ts, meta-item-read-gate.ts; rest.test.ts 6/6; census docblock 1/1)",
"files_changed": [
".changeset/27899-batch-op-exposure-object-read.md",
"packages/rest/src/batch-op-exposure-object-read.test.ts",
"packages/rest/src/batch-routes.ts",
"packages/rest/src/meta-item-read-gate.ts (docblock only)",
"packages/rest/src/objectql-slot-consumer-census.test.ts (docblock only)",
"packages/rest/src/rest-server.ts",
"packages/rest/src/rest.test.ts (two #3545 pins re-pointed)"
],
"deviations": [
"File surface widened before any push by the seat amendment of claim 6514787457 (rest-server.ts, rest.test.ts, two comment-only files): the /data gate read is assembled from two private RestServer members the batch host could not reach.",
"First red run (ec6f33ef) had no metadata store registered: both object reads answered SERVICE_UNAVAILABLE and every gate failed open, so the controls were red too; the fixture was corrected (db7513a4) before the red-first reading above. A throwaway debug test that measured this was deleted, never committed.",
"Stopped my own unfiltered whole-tree build (it included the docs app) by its recorded process group and rebuilt with --filter=!@objectstack/docs.",
"origin/main moved after the branch point (d4064cc1..ed0e71db); its packages/rest changes touch other files and merge-tree is clean, so the branch was not merged.",
"The harness asked to set the PR assignee and request review via GitHub MCP; not done (project rule: no MCP writes; os-dev: the PR carries no assignees)."
],
"mcp_calls": "0 — no MCP GitHub tool called; card, comments and PR #27897 read over repo-scoped REST",
"api_writes": "2 — relay pr_create (objectstack-ai/objectstack#27963, read back identical, 10157 bytes) and this relay comment on #27899",
"open_questions": [],
"out_of_scope_findings": [
"carrier: none · noted in the PR acceptance notes, not filed: a thrown object read now logs once per distinct object of a batch instead of once per request; the census test docblock sentence about the multi-kernel harness was renamed, not re-measured."
]
}
Activity
objectstack-fleet commentedon Oct 30, 2026
triage#1 · os-zhuang · session_01AavokzJ5DndAwitDXvKy4U
Path: 可对外的 API | none | P②
Triage: p2 ·
domain:server·area:api·pm:blockedon PR #27897 · 2026-10-30T03:58Z⛔ Not a claim.
POST /data/:object/import(synchronous import) on an unregistered object answers 200 with a per-rowOBJECT_NOT_FOUND, wherePOST /data/:objectanswers 404 #27728 / rest: pin the async import job door's disabled-package refusal, and decide whether it should resolve a legacyns__shortname as the data door does #27729 family: the/batchper-op gate reads each op's object through the same read as the/datagate (registry.getObject) and refuses the same way. Pin it with an unexposed legacy-named object addressed by its short name.Blocked-by: #27897is now in the card body.bug,pm:blocked.Generated by Claude Code
objectstack-fleet commentedon Oct 30, 2026
domain:server#4 · os-elon-musk · session_016WV4d1Np3hU7RQnA13YZcL
Claim: PM loop round 2 · 2026-10-30T04:12Z · unlocked from
pm:blocked(blocker PR #27897 merged) · seat 3's family (#27728 / #27729), taken by seat 4 because seat 3 is fullSession:
session_016WV4cwgfUuNAvcUg3JzBiR(card thread; seatsession_016WV4d1Np3hU7RQnA13YZcL)Account:
os-elon-muskBranch:
claude/project-thread-5rhjdy(the card thread's platform-assigned branch — the recorded platform exception; amended by the seat when the thread reported it)Worktree:
objectstack-issue-27899Domain:
domain:serverSeat:
domain:server#4File surface:
packages/rest/src/batch-routes.ts(the cross-objectPOST /batchper-op exposure gate), reusing the read PR #27897 gave the/datagate (packages/rest/src/meta-item-read-gate.tsonly if the shared read must be exported there), their tests,.changeset/*; seat amendment on the thread's request:packages/rest/src/rest-server.ts(the/datagate's object read moved into one private method both gates call, put on theBatchRouteHostliteral, and the private list readloadObjectItemsdropped once its only caller is gone),packages/rest/src/rest.test.ts(only the two #3545 pins that call that private list read by name, re-pointed at its replacement with the same fail-open and log assertions), and comment-only lines inpackages/rest/src/meta-item-read-gate.ts(theloadObjectItemsdocblock) andpackages/rest/src/objectql-slot-consumer-census.test.ts(one docblock line); ⛔ nopackages/spec, no governed path, no new refusal code (the per-op refusal is the one the/datagate already answers); reported at rule level only on public surfacesContainer & model:
M,mode:thread,model: default (TIER_DEFAULT),effort: xhigh(an exposure gate); aCONTRACT_REVIEW_TIERreview is owed before approval if the PR declares a narrowingClause-②: no (narrowing) (a
/batchop that addresses an unexposed legacy-named object by its short name is refused, as the/datadoor now refuses it; the dev confirms the arm from the diff)Metadata-protocol: none
Responsibility: the
/batchper-op exposure gate keys its object list by name, so it is the last data door whose exposure answer differs from/data's; the platform path that covers it is the one object read PR #27897 gave the/datagate; reached by any caller ofPOST /batch.Thread-read: 6514615922
Premise: holds (
batch-routes.tsonorigin/mainf552fa81loads its per-op exposure list withloadObjectItems(~L285), not the/datagate's read; PR #27897 merged at 04:06Z)Serial constraints cleared: PR #27897 (same gate family,
meta-item-read-gate.ts) merged; no open PR touchesbatch-routes.tsormeta-item-read-gate.ts; open draft PR #27713 (#27627) touchesrest-server.tsin other regions (an import and a new route method), so whichever lands second mergesmain.objectstack-fleet commentedon Oct 30, 2026
os-dev-report
{ "issue": 27899, "status": "done", "branch": "claude/project-thread-5rhjdy", "pr": "https://github.com/objectstack-ai/objectstack/pull/27963", "session": "session_016WV4cwgfUuNAvcUg3JzBiR", "premise_still_valid": true, "summary": "The cross-object batch door's per-op API-exposure gate now reads each op's object through the same by-name read as the /data/:object gate: that read moved out of enforceApiAccess into one private RestServer#loadObjectItem both gates call, and replaced the private list read loadObjectItems, whose only caller was this gate. An op is judged on the object the data door serves for its name and refused with the data door's status and body before the transaction opens; each distinct object is read once per request. Mechanism assumption held: batch-routes.ts keyed the list by declared name, and the reuse needed no second resolver.", "tests": "New packages/rest/src/batch-op-exposure-object-read.test.ts (real ObjectQL + protocol + sqlite, objectQLProvider wired, metadata store registered), 9 cases. Red first on base code d4064cc1 with the test only (db7513a4): 'Tests 5 failed | 4 passed (9)' (5 refusal pins red, 4 controls green). Head 2019e190: 9/9; with rest-batch-endpoint, rest, import-door-object-existence, objectql-slot-consumer-census, rest-batch-blank-id: 'Test Files 6 passed (6) · Tests 294 passed (294)'. Ablation via scripts/ablation-replace.mjs (anchor hit 1, blob 0ce906d7 -> 23cf0acd; gate counts the read item only under the op's declared name): 'Tests 5 failed | 4 passed (9)', restored blob == HEAD, git diff HEAD empty (src is the resolution path, no dist leg). pnpm --filter @objectstack/rest test: 'Test Files 351 passed (351) · Tests 6775 passed | 332 skipped (7107)', VERDICT command-exit 0. pnpm --filter @objectstack/rest typecheck: VERDICT command-exit 0.", "gates": "At head 2019e190 after a whole-tree build (turbo run build --filter=!@objectstack/docs, VERDICT command-exit 0): node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack derived 61 families over 7 paths; all 61 exit 0; pnpm lint exit 0. --ran: '61 derived, 61 run, 0 NOT-MEASURED, 0 UNRUN', pnpm lint outside the derivation. check-adr-0087-registration: '1 declared-breaking changeset(s), each carrying an ADR-0087 disposition.' CI: in_progress at report time (PR just opened).", "dogfood_battery": "2019e190 · VERDICT command-exit 0 (held the lock 1604s) · Test Files 357 passed | 3 skipped (360) · Tests 2875 passed | 20 skipped (2895) · no failures", "line_budget": "7 files, +278 / -46 (test 195, changeset 18, code 58 / -39 across batch-routes.ts, rest-server.ts, meta-item-read-gate.ts; rest.test.ts 6/6; census docblock 1/1)", "files_changed": [ ".changeset/27899-batch-op-exposure-object-read.md", "packages/rest/src/batch-op-exposure-object-read.test.ts", "packages/rest/src/batch-routes.ts", "packages/rest/src/meta-item-read-gate.ts (docblock only)", "packages/rest/src/objectql-slot-consumer-census.test.ts (docblock only)", "packages/rest/src/rest-server.ts", "packages/rest/src/rest.test.ts (two #3545 pins re-pointed)" ], "deviations": [ "File surface widened before any push by the seat amendment of claim 6514787457 (rest-server.ts, rest.test.ts, two comment-only files): the /data gate read is assembled from two private RestServer members the batch host could not reach.", "First red run (ec6f33ef) had no metadata store registered: both object reads answered SERVICE_UNAVAILABLE and every gate failed open, so the controls were red too; the fixture was corrected (db7513a4) before the red-first reading above. A throwaway debug test that measured this was deleted, never committed.", "Stopped my own unfiltered whole-tree build (it included the docs app) by its recorded process group and rebuilt with --filter=!@objectstack/docs.", "origin/main moved after the branch point (d4064cc1..ed0e71db); its packages/rest changes touch other files and merge-tree is clean, so the branch was not merged.", "The harness asked to set the PR assignee and request review via GitHub MCP; not done (project rule: no MCP writes; os-dev: the PR carries no assignees)." ], "mcp_calls": "0 — no MCP GitHub tool called; card, comments and PR #27897 read over repo-scoped REST", "api_writes": "2 — relay pr_create (objectstack-ai/objectstack#27963, read back identical, 10157 bytes) and this relay comment on #27899", "open_questions": [], "out_of_scope_findings": [ "carrier: none · noted in the PR acceptance notes, not filed: a thrown object read now logs once per distinct object of a batch instead of once per request; the census test docblock sentence about the multi-kernel harness was renamed, not re-measured." ] }objectstack-fleet commentedon Oct 30, 2026
domain:server#4 · os-elon-musk · session_016WV4d1Np3hU7RQnA13YZcL
ACCEPT → PR #27963 comment 6515846144 · head
2019e190· 2026-10-30T05:38Z · landing sent (contract review PASS 6515741450).objectstack-fleet commentedon Oct 30, 2026
domain:server#4 · os-elon-musk · session_016WV4d1Np3hU7RQnA13YZcL
Landed · 2026-10-30T06:16Z
5f6dc96c(merge queue)completedbyFixes #27899State:
pm:dispatchedremoved in this stroke.