Skip to content

security(data): a nested-relation filter condition on a lookup target is evaluated without asking the target's exposure (census row 4 of #22661) #22737

Description

@objectstack-fleet

This card carries census row 4 of #22661 (part of #22661). #22661 keeps rows 1–3, landing through PR #22735, and the enumeration pin. ⛔ Classes, positions and functions only.

Filing class: ① a product defect, class (a). Reach: measured once at a public door by #22661's dev on a real stack (report 6102815147, census row 4), for an administrator and a member alike. Reader who acts: the domain:engine lane, seat 1 (#6367), which owns #22661's derived sub-issues and dispatches this one directly.

The gap

Direction (from #22661's triage, not a ruling)

Duplicate check

Issues updated since 2026-09-01 were paged to the end through REST: 4,476 issues, PRs excluded, closed included, #1795 to #22729. A local grep over titles and bodies (comments are outside this instrument's radius) for lowerRelationConditions or a relation filter or condition within 200 characters of apiEnabled or exposure found 0 hits. Control: apiEnabled alone found 11 hits.

Dedupe words: nested relation filter exposure · lowerRelationConditions apiEnabled · related object condition unexposed target

Activity

  1. objectstack-fleet commented on Oct 11, 2026

    @objectstack-fleet
    ContributorAuthor

    Claim: PM loop round 2 · 2026-10-11T02:12Z
    Session: session_01JfJfBUC3cQ6hhgm9MQK76T
    Account: os-project-manager (the seat's linked user, as GET /user answers it; the card's assignee)
    Branch: claude/issue-22737-relation-filter-exposure
    Worktree: objectstack-issue-22737
    Domain: domain:engine
    Seat: domain:engine#1 (seat post #6367)
    File surface (read on origin/main d7b26df5b5; stop on a breach and explain it in the report):


    Generated by Claude Code

  2. objectstack-fleet commented on Oct 11, 2026

    @objectstack-fleet
    ContributorAuthor

    os-dev-report
    {
    "issue": 22737,
    "status": "done",
    "branch": "claude/issue-22737-relation-filter-exposure",
    "pr": "#22768",
    "session": "session_01JfJfBUC3cQ6hhgm9MQK76T — the dispatching seat's id, inherited by this subagent",
    "premise_still_valid": true,
    "summary": "The data door's nested-relation walk (refuseInternalInFilter over relationConditionSites, called from findData's assertNoInternalFieldEvaluated) now asks each condition's TARGET the spec's one exposure decision first, through the new ObjectStackProtocolImplementation.refuseUnservedRelationTarget, which calls apiExposureDenialReason(enable, 'list'). A target that is not served is refused with the door's existing codes: 404 OBJECT_API_DISABLED, or 405 OBJECT_API_METHOD_NOT_ALLOWED with the effective set. The decision takes no caller. No new code, no second rule, and no change to filter compilation or the engine. PM hypotheses: (1) confirmed by symbol. relationConditionSites and its caller judged internal fields only. lowerRelationConditions runs this.find(target, where: condition, limit: cap+1) with the caller context and no exposure ask. (2) Confirmed on a real stack (bootStack, base bf515e7), on the list and query routes, admin and member alike. Targets apiEnabled:false, apiMethods ['create'], [] and ['get'] were evaluated: 1 row on a match, 0 on a miss. After the change: 404, 405, 405 and 405. The list-only and open controls are unchanged. (3) Operation list, not get. Unlike $expand, a condition is a predicate read over the whole target, bounded by the match cap rather than by ids the caller holds, which is the read the target's filtered list performs (query maps to list). So a get-only target is refused here while it stays served to $expand. (4) Refused, not withheld. The precedent was measured: a related object the member cannot read answers 403 PERMISSION_DENIED at this position, and this is pinned unchanged. The refusal names the target object and the position in its message. The target is the lookup's declared reference, which the source's served /meta object read already shows to both personas, and the internal-field refusal at the same hop names it too. (5) The ledger row is required: ablation D removed it and check:error-code-provenance FAILed on both stamp sites. files_changed: packages/metadata-protocol/src/protocol.ts (+57/-4); packages/metadata-protocol/src/protocol.relation-condition-target-exposure.test.ts (new, +126); packages/spec/src/api/error-code-ledger.zod.ts (+7); packages/core/src/security/second-object-read-exposure.pin.test.ts (+10/-9, row 4 open to decided, operation type widened to get|list); packages/qa/dogfood/test/second-object-exposure.dogfood.test.ts (+43); .changeset/22737-relation-condition-target-exposure.md (metadata-protocol minor, BREAKING narrowing, Clause-② no (narrowing), adr-0087 not-required (no-migration-prescription)); .changeset/22737-spec-ledger-metadata-protocol-exposure-codes.md (spec patch). line_budget: 7 files, +281/-13 = 294 changed lines, over the 250 suggestion (source +57/-4, within 80). The excess is the second changeset and the real-stack legs. deviations: (a) The ledger change is ONE provenance block in metadata-protocol's own section carrying the TWO existing exposure codes as two array entries. I read the claim's "one row" that way because the claim names the codes in the plural and both sibling sections (service-analytics, mcp) take the same two-entry shape. It is a pure append, with no schema shape change and no existing entry touched. If the seat reads "one row" as one array entry, this exceeds it. (b) Two changesets, against the claim's "one changeset". spec's published ERROR_CODE_LEDGER value changes, which the analytics precedent covered with its own spec patch changeset (22634-spec-ledger-analytics-exposure-codes.md). (c) The real-stack legs (admin and member, list and query routes, plus the 403 precedent) were added to #22661's dogfood pin, outside the named file surface, beside their sibling legs. (d) The base was bf515e7 at worktree creation, not d7b26df. origin/main e865300 (4 commits: spec formula, plugin-security, plugin-auth, plugin-sharing; no overlap with the changed files) was merged once before the PR as 151278b. The closure was rebuilt and spec check:generated reports 14 artifacts up to date. (e) The flat REST body's object member names the route object, not the refused target. That is the mapDataError declared-status passthrough in @objectstack/types, which is outside the file surface, so it is not fixed (see open_questions). (f) The harness attribution reminder named a model in the Co-Authored-By trailer. The commits carry AGENTS.md's model-free pair (Claude-Session plus Co-authored-by: Claude) instead. (g) Zero label writes: the dispatch names no label, and changesets exist, so skip-changeset does not apply. Worktree removed after the PR opened.",
    "tests": "MEASUREMENT (scratch probe, not committed; fixture objects only; real stack via @objectstack/verify bootStack). The before and after tables are in the summary and the PR. PINS at 151278b (merge head): metadata-protocol protocol.relation-condition-target-exposure.test.ts with protocol.expand-target-exposure.test.ts: 2 files, 28/28. core vitest --project repo: 5 files, 55/55, including second-object-read-exposure.pin.test.ts 4/4. dogfood second-object-exposure.dogfood.test.ts: 30/30 (25 existing plus 5 new). PACKAGES at fe8d208: metadata-protocol vitest 225 files passed, 3 skipped, 28129 tests passed, 19 skipped, VERDICT command-exit 0. metadata-protocol tsc --noEmit --listFiles exit 0, new test listed, 0 errors. core typecheck (tsc, examples, check:test-typecheck OK) exit 0. dogfood tsc --noEmit --listFiles exit 0 at 151278b, changed file listed, 0 errors. ABLATION, on committed fe8d208 via scripts/ablation-replace.mjs, each restore proved by blob == HEAD and an empty git diff HEAD. A: the hop call deleted (anchor 1 to 0, blob bef97743 to 19d384c8); the unit pin went 10 red / 3 green (the three controls). B: the call replaced by a compiling marker (void this.refuseUnservedRelationTarget; plus a globalThis marker); metadata-protocol rebuilt, exit 0; ablation-dist-preflight marker present in dist (exit 0); the dogfood pin went 2 red (the refused legs, admin and member) / 28 green. Restore leg: rebuilt exit 0, preflight --absent exit 0. The first B attempt used a non-compiling mutation (an unused private method): build exit 1, with the JS marker still emitted. It was re-run, and the numbers above come from the re-run. C: the decision call replaced by a hand-spelled apiEnabled rule; the enumeration pin went 1 red, naming refuseUnservedRelationTarget. D: the two ledger entries deleted; check:error-code-provenance FAIL naming protocol.ts:12628 and :12631. The first D attempt was refused by the tool (the replacement was a substring of the anchor), so nothing ran; it was re-run in delete mode. GATES at 151278b (final commit): the union of the dispatch list (82) and dispatch-gates --commands on this diff (91) is 95 commands, with each exit captured before any pipe. dispatch-gates --ran: 91 derived, 91 run, 0 NOT-MEASURED, 0 UNRUN, 4 outside the derivation. check:dual-build-cjs-loads: the first run at fe8d208 was exit 3 PREREQUISITE NOT MET (8 dists missing); after building them it was exit 0, and exit 0 again at 151278b. Per command: node scripts/check-adr-0087-registration.mjs --base origin/main :: 0; node scripts/check-adr-0087-registration.mjs --self-test :: 0; node scripts/check-changeset-no-major.mjs --base origin/main :: 0; node scripts/check-changeset-no-major.mjs --self-test :: 0; node scripts/check-ci-filter-parity.mjs :: 0; node scripts/check-closing-keyword-parity.mjs :: 0; node scripts/check-closing-keyword-parity.mjs --self-test :: 0; node scripts/check-comment-mask-adoption.mjs :: 0; node scripts/check-comment-mask-adoption.mjs --self-test :: 0; node scripts/check-comment-mask-corpus.mjs :: 0; node scripts/check-dev-prereqs.mjs --self-test :: 0; node scripts/check-dts-emitted.mjs --self-test :: 0; node scripts/check-empty-changeset.mjs --base origin/main :: 0; node scripts/check-empty-changeset.mjs --self-test :: 0; node scripts/check-engine-split-ratio.mjs --days 90 :: 0; node scripts/check-engine-split-ratio.mjs --self-test :: 0; node scripts/check-issue-citations.mjs :: 0; node scripts/check-keyed-text-bounds.mjs :: 0; node scripts/check-keyed-text-bounds.mjs --self-test :: 0; node scripts/check-platform-object-tenancy-census.mjs :: 0; node scripts/check-platform-object-tenancy-census.mjs --self-test :: 0; node scripts/check-plugin-teardown-shape.mjs :: 0; node scripts/check-plugin-teardown-shape.mjs --self-test :: 0; node scripts/check-registry-log-declared.mjs :: 0; node scripts/check-registry-log-declared.mjs --self-test :: 0; node scripts/check-rest-log-spy-declared.mjs :: 0; node scripts/check-rest-log-spy-declared.mjs --self-test :: 0; node scripts/check-spec-docblock-symbol-anchors.mjs :: 0; node scripts/check-spec-docblock-symbol-anchors.mjs --self-test :: 0; node scripts/check-system-context-census.mjs :: 0; node scripts/check-system-context-census.mjs --self-test :: 0; node scripts/check-undeclared-dep-imports.mjs :: 0; node scripts/check-undeclared-dep-imports.mjs --self-test :: 0; node scripts/docs-audit/check-affected-docs.mjs :: 0; node scripts/docs-audit/check-drift-comment.mjs :: 0; node scripts/pm/release-rehearsal-clone.mjs --self-test :: 0; node scripts/release-pending-publish.mjs --self-test :: 0; pnpm --filter @objectstack/lint run check:doc-formula-expressions :: 0; pnpm --filter @objectstack/spec run check:api-surface :: 0; pnpm --filter @objectstack/spec run check:authorable-surface :: 0; pnpm --filter @objectstack/spec run check:browser-reachable-entries :: 0; pnpm --filter @objectstack/spec run check:docs :: 0; pnpm --filter @objectstack/spec run check:dual-source-exports :: 0; pnpm --filter @objectstack/spec run check:duration-unit-keys :: 0; pnpm --filter @objectstack/spec run check:empty-state :: 0; pnpm --filter @objectstack/spec run check:entry-nameability :: 0; pnpm --filter @objectstack/spec run check:error-code-provenance :: 0; pnpm --filter @objectstack/spec run check:export-origins :: 0; pnpm --filter @objectstack/spec run check:exported-any :: 0; pnpm --filter @objectstack/spec run check:liveness :: 0; pnpm --filter @objectstack/spec run check:llms-txt :: 0; pnpm --filter @objectstack/spec run check:objectui-pin-citations :: 0; pnpm --filter @objectstack/spec run check:skill-refs :: 0; pnpm --filter @objectstack/spec run check:strictness-ledger :: 0; pnpm --filter @objectstack/spec run check:variant-docs :: 0; pnpm --filter @objectstack/spec run check:yaml-examples :: 0; pnpm check:changeset-gate-self-tests :: 0; pnpm check:cross-package-test-inputs :: 0; pnpm check:dispatcher-error-vocabulary :: 0; pnpm check:doc-authoring :: 0; pnpm check:driver-memory-census :: 0; pnpm check:dts-closure :: 0; pnpm check:dual-build-cjs-loads :: 0; pnpm check:durability-log-level :: 0; pnpm check:engine-double-contract :: 0; pnpm check:error-code-casing :: 0; pnpm check:error-status-conformance :: 0; pnpm check:filter-alias-parity :: 0; pnpm check:gitlink-declared :: 0; pnpm check:issue-citations :: 0; pnpm check:kernel-hook-pairs :: 0; pnpm check:lean-entry-closure :: 0; pnpm check:logger-receiver-detach :: 0; pnpm check:merge-driver :: 0; pnpm check:nul-bytes :: 0; pnpm check:objectql-double-limit :: 0; pnpm check:objectui-changeset :: 0; pnpm check:org-identifier :: 0; pnpm check:page-declaration-shape :: 0; pnpm check:pm-changeset-deadline-census :: 0; pnpm check:pm-prior-rulings :: 0; pnpm check:published-files :: 0; pnpm check:query-options-erasure :: 0; pnpm check:refd-timer-probe :: 0; pnpm check:slot-lookup :: 0; pnpm check:sourcemap-no-sources-content :: 0; pnpm check:spec-parsed-alias :: 0; pnpm check:stack-collection-maps :: 0; pnpm check:swallow-census-controls :: 0; pnpm check:test-source-alias :: 0; pnpm check:tier-file-adoption :: 0; pnpm check:type-check-coverage :: 0; pnpm check:type-check-debt :: 0; pnpm check:watch-hint-literal :: 0; pnpm check:where-matcher :: 0; LINT narrowed: eslint --no-inline-config --format json on the 5 changed .ts files: 5 files, 0 errors, 0 warnings. --print-config shows 5 active rules per file, none ignored. eslint.config.mjs enables no type-aware linting (no parserOptions.project or projectService), so no untouched file's verdict can move. Repo-wide pnpm lint is CI's. CI not awaited.",
    "mcp_calls": "0 — no MCP GitHub tool was called; reads went through gh api REST GETs (issue, comments, PR read-back)",
    "api_writes": "2 — fleet-write relay (objectstack-fleet[bot]), each one POST /repos/objectstack-ai/objectstack/dispatches. (1) pr_create = POST /repos/objectstack-ai/objectstack/pulls (draft PR #22768, run 38112298274, read-back 9889 bytes identical) plus POST /repos//issues/22768/assignees os-project-manager. (2) This os-dev-report comment = POST /repos//issues/22737/comments. Zero label writes. git push not counted.",
    "open_questions": [
    {
    "question": "The flat REST body's object member names the ROUTE object, not the refused target. For this refusal the wire reads code OBJECT_API_DISABLED (or 405) beside the SOURCE object's name, while the message names the target, and allowed does not reach that body. Cause: mapDataError's declared-status passthrough (@objectstack/types data-error-classification.ts) stamps the route object onto every relayed 4xx; only per-code arms (INVALID_FIELD, FEEDS_DISABLED, RECORD_NOT_ACCESSIBLE) prefer error.object. Widen the claim to close it here, or leave it to a follow-up?",
    "options": [
    "A — accept as is: object keeps meaning the addressed object on every generic passthrough refusal, and the target is named in prose only. Zero cost. A client or agent keying on code+object reads the wrong object as switched off.",
    "B — one arm in classifyDataError for the two exposure codes that prefers error.object and relays allowed, the REST door's own body shape for these codes. About 15 lines in @objectstack/types plus a pin and a types patch changeset. It widens this PR's file surface to packages/types, or it becomes a follow-up card."
    ],
    "recommendation": "B, as a rider on this PR if the seat widens the claim before the contract review, else as a follow-up card. Real need: the pair is new with this PR, and an AI caller acting on code+object would try to change the wrong object's enable block. Long term: one meaning per code across arms, the established pattern for target-naming refusals. AI-error axis: the loud, correct envelope is the one an agent cannot misread. Startup scope: one arm, no new code and no new gate."
    }
    ],
    "out_of_scope_findings": [
    "carrier: 承接者:无 · the MCP stdio data bridge (packages/mcp/src/stdio-data-bridge.ts) hands a caller's filter to engine.find after its own addressed-object gate, so a nested-relation condition there reaches ObjectQL.lowerRelationConditions without the target's exposure being asked. Not measured at a door in this card; noted in Acceptance notes, not filed · dedupe words: mcp stdio nested relation filter exposure; stdio-data-bridge relation condition apiEnabled; mcp query lookup condition unexposed target"
    ]
    }


    Generated by Claude Code

  3. objectstack-fleet commented on Oct 11, 2026

    @objectstack-fleet
    ContributorAuthor

    os-dev-report
    {
    "issue": 22737,
    "status": "done",
    "branch": "claude/issue-22737-relation-filter-exposure",
    "pr": "#22768",
    "session": "session_01JfJfBUC3cQ6hhgm9MQK76T — the dispatching seat's id, inherited by this subagent",
    "premise_still_valid": true,
    "summary": "PATCH ROUND 2 (seat ruling: option B as a rider). (1) The arm: @objectstack/types data-error-classification.ts gains one arm for OBJECT_API_DISABLED and OBJECT_API_METHOD_NOT_ALLOWED. It sits in structuredCodeAnswer, the shared arm table classifyDataError asks first, so both REST doors (mapDataError, and resolveErrorResponse behind sendThrownError) answer alike. The door-parity rule of that file requires this; an arm in classifyDataError's own body would split the doors. When the error names an object other than a DEFINED route object, the body is error, code, object (the error's own) and allowed (on 405): the REST door's own shape for these codes. No new code. The gate is narrower than "prefer error.object" because the seat's premise ("all existing producers have error.object equal to the route object") was measured false. The one existing thrower that reaches these doors is service-analytics' exposure door on the dataset route. It goes through classifiedRefusalAnswer, which calls resolveErrorResponse with NO route object. A plain prefer-error.object arm would have added object and allowed to that existing body. Measured before and after on both doors plus classifiedRefusalAnswer, 6 shapes (404 and 405, each with the error object differing from the route, equal to it, or no route): only the 4 cells where the error object differs from a defined route changed. The other 14 are byte-identical (JSON string compare). (2) Pins. packages/types/src/data-error-classification.exposure-arm.test.ts (new, 4 cases): target named on 404; target plus allowed on 405; CONTROL route-object error byte-identical; CONTROL no-route door byte-identical. Dogfood second-object-exposure, +2 cases per persona: the refusal body's status, code, object and allowed equal the target's own route refusal, with object being the target; CONTROL the target's own route refusal names the target, unchanged. Two existing drift guards in packages/rest demand coverage of every new arm, so each gained test-only rows: 2 CASES in error-response-structured-arm-door-parity.test.ts and 2 ARMS in error-response-sandbox-arm-message.test.ts (plus that file's re-derivation count, now 16/13). These sit outside the revised surface text and are declared here. (3) Changeset .changeset/22737-types-exposure-arm-names-target.md (@objectstack/types patch). (4) MCP stdio reading: see out_of_scope_findings; nothing changed in packages/mcp. (5) origin/main 9859e4e (6 commits, including one to packages/rest/src/error-response.ts that adds nestedRefusalEnvelope over classifiedRefusalAnswer with no route object, so the arm does not fire there) was merged as 964acef. The closure was rebuilt and spec check:generated reports 14 artifacts up to date. PR BODY NOT PATCHED: the os-dev definition says the dev writes the PR body once, at pr_create, and names later edits for the seat to write. That conflicts with the seat's instruction 3; flagged rather than resolved silently. Edits for the seat to write, verbatim: (i) the Acceptance-notes bullet on the flat body's object member becomes: "Fixed in this PR: the flat REST body named the route object beside the exposure code. One arm in @objectstack/types structuredCodeAnswer (asked first by classifyDataError, so both REST doors agree) now answers with the error's own object, and relays allowed on 405, when the error names an object other than a defined route object. An error naming the route object, or a door given none (the analytics dataset route), answers byte for byte as before." (ii) Deviation (c) becomes: "The diff is 472 changed lines (+456 / -16) across 12 files: source +57/-4 (protocol.ts) and +23 (data-error-classification.ts); the rest is tests, three changesets, the pin row and the ledger rows." (iii) Verification gains: "Round 2 at 964acef: types 27 files / 754 passed plus repo 11; rest local 272 files / 5239 passed (run in three chunks) plus repo 5 files / 209; dogfood pin 34/34; ablation E (arm deleted, types rebuilt, preflight --absent) reds the types pin 2 / 4, the rest guards 8 / 210 and the dogfood target leg 2 / 34, with controls green; restored blob == HEAD; 95 gates exit 0." files_changed (whole PR): the 7 from round 1 (dogfood now +66), plus packages/types/src/data-error-classification.ts (+23), packages/types/src/data-error-classification.exposure-arm.test.ts (new, +67), packages/rest/src/error-response-sandbox-arm-message.test.ts (+19/-3), packages/rest/src/error-response-structured-arm-door-parity.test.ts (+32) and .changeset/22737-types-exposure-arm-names-target.md (+11). line_budget: 12 files, +456/-16 = 472 changed lines; source +80/-4 (protocol.ts +57/-4, data-error-classification.ts +23). deviations this round: the arm's position and gate (above); the rest drift-guard rows (above); the PR body not patched (above); the rest local suite run in three file chunks of 88, 93 and 91, which is complete coverage and not a narrowing, because a single run exceeded the foreground cap while the gate battery shared the box. Accepted round-1 deviations (a)(b)(c)(d)(f)(g) are unchanged.",
    "tests": "ROUND 2, at 964acef (final commit, origin/main 9859e4e merged). types vitest local 27 files / 754 passed; repo 1 file / 11. rest vitest repo 5 files / 209 passed, 1 skipped. rest local, three chunks: 88 files / 1313 passed, 281 skipped; 93 / 1981, 45 skipped; 91 / 1945. Total 272 files / 5239 passed, every chunk VERDICT command-exit 0. metadata-protocol unit pins 2 files / 28. core repo 5 files / 55. dogfood second-object-exposure 34/34. Typecheck: types tsc --noEmit (new test in --listFiles), rest typecheck (tsc plus check:test-typecheck OK) and dogfood tsc --noEmit, all exit 0 with 0 errors. DOORS before/after (scratch, not committed): 18 cells. Only 404-differs and 405-differs changed, on mapDataError and on sendThrownError, from object = route to object = target, with allowed ["get"] on 405. The equal, no-route and classifiedRefusalAnswer cells are byte-identical. ABLATION E, on committed 5c17272 via ablation-replace.mjs: the arm deleted (anchor 1 to 0, blob 61b0d967 to 6f20e4cc); types rebuilt, exit 0; ablation-dist-preflight --absent exit 0. Results: types pin 2 red (target cases) / 2 green (controls); rest repo 8 red (the new door-parity and sandbox rows) / 201 green; dogfood 2 red (the target-body leg, admin and member) / 32 green (code+status legs and both controls). Restore: blob == HEAD, git diff HEAD empty; types rebuilt exit 0; the preflight's dist reading has the marker present. Its tree reading exited 3, because an untracked scratch probe was then present in the worktree (since removed); the dist reading is the one the suite consumes. Round-1 ablations A–D stand as reported in comment 6105527933. GATES at 964acef: union of the dispatch list (82) and dispatch-gates --commands on this diff (91; the new types and rest paths added no family) = 95 commands, all exit 0, each captured before any pipe. dispatch-gates --ran: 91 derived, 91 run, 0 NOT-MEASURED, 0 UNRUN, 4 outside. Per command: node scripts/check-adr-0087-registration.mjs --base origin/main :: 0; node scripts/check-adr-0087-registration.mjs --self-test :: 0; node scripts/check-changeset-no-major.mjs --base origin/main :: 0; node scripts/check-changeset-no-major.mjs --self-test :: 0; node scripts/check-ci-filter-parity.mjs :: 0; node scripts/check-closing-keyword-parity.mjs :: 0; node scripts/check-closing-keyword-parity.mjs --self-test :: 0; node scripts/check-comment-mask-adoption.mjs :: 0; node scripts/check-comment-mask-adoption.mjs --self-test :: 0; node scripts/check-comment-mask-corpus.mjs :: 0; node scripts/check-dev-prereqs.mjs --self-test :: 0; node scripts/check-dts-emitted.mjs --self-test :: 0; node scripts/check-empty-changeset.mjs --base origin/main :: 0; node scripts/check-empty-changeset.mjs --self-test :: 0; node scripts/check-engine-split-ratio.mjs --days 90 :: 0; node scripts/check-engine-split-ratio.mjs --self-test :: 0; node scripts/check-issue-citations.mjs :: 0; node scripts/check-keyed-text-bounds.mjs :: 0; node scripts/check-keyed-text-bounds.mjs --self-test :: 0; node scripts/check-platform-object-tenancy-census.mjs :: 0; node scripts/check-platform-object-tenancy-census.mjs --self-test :: 0; node scripts/check-plugin-teardown-shape.mjs :: 0; node scripts/check-plugin-teardown-shape.mjs --self-test :: 0; node scripts/check-registry-log-declared.mjs :: 0; node scripts/check-registry-log-declared.mjs --self-test :: 0; node scripts/check-rest-log-spy-declared.mjs :: 0; node scripts/check-rest-log-spy-declared.mjs --self-test :: 0; node scripts/check-spec-docblock-symbol-anchors.mjs :: 0; node scripts/check-spec-docblock-symbol-anchors.mjs --self-test :: 0; node scripts/check-system-context-census.mjs :: 0; node scripts/check-system-context-census.mjs --self-test :: 0; node scripts/check-undeclared-dep-imports.mjs :: 0; node scripts/check-undeclared-dep-imports.mjs --self-test :: 0; node scripts/docs-audit/check-affected-docs.mjs :: 0; node scripts/docs-audit/check-drift-comment.mjs :: 0; node scripts/pm/release-rehearsal-clone.mjs --self-test :: 0; node scripts/release-pending-publish.mjs --self-test :: 0; pnpm --filter @objectstack/lint run check:doc-formula-expressions :: 0; pnpm --filter @objectstack/spec run check:api-surface :: 0; pnpm --filter @objectstack/spec run check:authorable-surface :: 0; pnpm --filter @objectstack/spec run check:browser-reachable-entries :: 0; pnpm --filter @objectstack/spec run check:docs :: 0; pnpm --filter @objectstack/spec run check:dual-source-exports :: 0; pnpm --filter @objectstack/spec run check:duration-unit-keys :: 0; pnpm --filter @objectstack/spec run check:empty-state :: 0; pnpm --filter @objectstack/spec run check:entry-nameability :: 0; pnpm --filter @objectstack/spec run check:error-code-provenance :: 0; pnpm --filter @objectstack/spec run check:export-origins :: 0; pnpm --filter @objectstack/spec run check:exported-any :: 0; pnpm --filter @objectstack/spec run check:liveness :: 0; pnpm --filter @objectstack/spec run check:llms-txt :: 0; pnpm --filter @objectstack/spec run check:objectui-pin-citations :: 0; pnpm --filter @objectstack/spec run check:skill-refs :: 0; pnpm --filter @objectstack/spec run check:strictness-ledger :: 0; pnpm --filter @objectstack/spec run check:variant-docs :: 0; pnpm --filter @objectstack/spec run check:yaml-examples :: 0; pnpm check:changeset-gate-self-tests :: 0; pnpm check:cross-package-test-inputs :: 0; pnpm check:dispatcher-error-vocabulary :: 0; pnpm check:doc-authoring :: 0; pnpm check:driver-memory-census :: 0; pnpm check:dts-closure :: 0; pnpm check:dual-build-cjs-loads :: 0; pnpm check:durability-log-level :: 0; pnpm check:engine-double-contract :: 0; pnpm check:error-code-casing :: 0; pnpm check:error-status-conformance :: 0; pnpm check:filter-alias-parity :: 0; pnpm check:gitlink-declared :: 0; pnpm check:issue-citations :: 0; pnpm check:kernel-hook-pairs :: 0; pnpm check:lean-entry-closure :: 0; pnpm check:logger-receiver-detach :: 0; pnpm check:merge-driver :: 0; pnpm check:nul-bytes :: 0; pnpm check:objectql-double-limit :: 0; pnpm check:objectui-changeset :: 0; pnpm check:org-identifier :: 0; pnpm check:page-declaration-shape :: 0; pnpm check:pm-changeset-deadline-census :: 0; pnpm check:pm-prior-rulings :: 0; pnpm check:published-files :: 0; pnpm check:query-options-erasure :: 0; pnpm check:refd-timer-probe :: 0; pnpm check:slot-lookup :: 0; pnpm check:sourcemap-no-sources-content :: 0; pnpm check:spec-parsed-alias :: 0; pnpm check:stack-collection-maps :: 0; pnpm check:swallow-census-controls :: 0; pnpm check:test-source-alias :: 0; pnpm check:tier-file-adoption :: 0; pnpm check:type-check-coverage :: 0; pnpm check:type-check-debt :: 0; pnpm check:watch-hint-literal :: 0; pnpm check:where-matcher :: 0; LINT narrowed: eslint --no-inline-config --format json over the 9 changed .ts files: 9 files, 0 errors, 0 warnings. --print-config shows 5 or 6 active rules per file, none ignored, no parserOptions.project or projectService, so no untouched file's verdict can move. CI not awaited.",
    "mcp_calls": "0 — no MCP GitHub tool was called; reads through gh api REST GETs (claim comment, PR state)",
    "api_writes": "Round 2: 1 — fleet-write relay (objectstack-fleet[bot]), POST /repos/objectstack-ai/objectstack/dispatches carrying this os-dev-report comment = POST /repos//issues/22737/comments. Round 1's 2 writes as reported. No PR-body PATCH, zero label writes; git push not counted.",
    "open_questions": [],
    "out_of_scope_findings": [
    "class: a · reach: measured at the MCP stdio tool surface, not the byte transport. The real createStdioDataBridge and registerObjectTools query_records were driven over an in-memory MCP client/server pair, under an admin API key resolved through plugin.ts's own chain (resolveAuthzContext plus assembleExecutionContext, non-system). On the fixture objects, a nested-relation condition on an apiEnabled:false lookup target answered 1 record on a match and 0 on a miss (control: 2 unfiltered), while query_records addressed to the target itself is refused "not exposed via the API". Not exercised: StdioServerTransport and the process boundary, and the localization memo. · evidence: packages/mcp/src/stdio-data-bridge.ts query() passes the caller's where to engine.find after enforceApiExposure judges the ADDRESSED object only; ObjectQL.lowerRelationConditions then reads the target as the caller with no exposure ask. Same class as this card, on the stdio door; not changed here (packages/mcp untouched). · dedupe words: mcp stdio nested relation filter exposure; stdio-data-bridge relation condition apiEnabled; query_records lookup condition unexposed target"
    ]
    }


    Generated by Claude Code

  4. objectstack-fleet commented on Oct 11, 2026

    @objectstack-fleet
    ContributorAuthor

    os-dev-report
    {
    "issue": 22737,
    "status": "done",
    "branch": "claude/issue-22737-relation-filter-exposure",
    "pr": "#22768",
    "session": "session_01JfJfBUC3cQ6hhgm9MQK76T — the dispatching seat's id, inherited by this subagent",
    "premise_still_valid": true,
    "summary": "PATCH ROUND 3 (mechanical merge). origin/main e84aeb3 (6 commits: #22767, #22766 896a434, #22749 490cb6d, #22736, #22765, #22753) merged into the branch as merge commit 04cf472 (parents 964acef and e84aeb3). No rebase, no force-push; pushed as a fast-forward of the branch. Conflicted file: one, packages/core/src/security/second-object-read-exposure.pin.test.ts, in one hunk: the expected list of decided decision functions. The CALLERS rows auto-merged. packages/metadata-protocol/src/protocol.ts auto-merged with #22749; refuseUnservedRelationTarget and its hop call are intact (4 occurrences). Union as resolved: decided = refuseUnservedRelationTarget (census row 4, operation list), servesExpansionTarget, servesLabelTarget, servesPayloadDisplayTarget (row 5, get) and servesSummaryTitleTarget (row 6, get). The Classification type keeps operation get|list. No CALLERS row is left kind open. Census row 7 (the import door, #22739) is not a CALLERS row: its target is not resolved through referenceTargetOf. It stays where main has it, in the header's not-seen list, unchanged. The file's diff against origin/main is exactly this branch's round-1 change: the header sentence, the type union, row 4 decided, and refuseUnservedRelationTarget added to the expected list. The merge commit message was amended once before the push, to keep the trailer pair as the last paragraph (git had appended the conflict comment lines after it). Nothing else was changed. line_budget vs the new merge base e84aeb3: 12 files, +456/-15 = 471 changed lines.",
    "tests": "ROUND 3 at 04cf472. #22749 and #22766 touch spec, objectql, metadata-protocol, plugin-approvals and plugin-audit, all in the dogfood closure, so the closure was rebuilt first: turbo build of the dogfood dependencies plus rest, 63 tasks, VERDICT command-exit 0. The 8 dists the dual-build gate reads plus cli were built too (67 tasks, exit 0), and spec check:generated reports 14 artifacts up to date. core vitest --project repo: 5 files / 55 passed, including second-object-read-exposure.pin.test.ts. core typecheck (tsc, examples, check:test-typecheck OK) exit 0. metadata-protocol protocol.relation-condition-target-exposure.test.ts with protocol.expand-target-exposure.test.ts: 2 files / 28 passed. dogfood second-object-exposure.dogfood.test.ts: 34/34. Extra: types exposure-arm pin 4/4; rest repo 5 files / 209 passed, 1 skipped. All in one locked run, VERDICT command-exit 0. GATES at 04cf472: dispatch-gates --commands on the merged change set (12 paths vs merge base e84aeb3) derives 91. The union with the dispatch list is 95 commands, all exit 0, each captured before any pipe. --ran: 91 derived, 91 run, 0 NOT-MEASURED, 0 UNRUN. Per command: node scripts/check-adr-0087-registration.mjs --base origin/main :: 0; node scripts/check-adr-0087-registration.mjs --self-test :: 0; node scripts/check-changeset-no-major.mjs --base origin/main :: 0; node scripts/check-changeset-no-major.mjs --self-test :: 0; node scripts/check-ci-filter-parity.mjs :: 0; node scripts/check-closing-keyword-parity.mjs :: 0; node scripts/check-closing-keyword-parity.mjs --self-test :: 0; node scripts/check-comment-mask-adoption.mjs :: 0; node scripts/check-comment-mask-adoption.mjs --self-test :: 0; node scripts/check-comment-mask-corpus.mjs :: 0; node scripts/check-dev-prereqs.mjs --self-test :: 0; node scripts/check-dts-emitted.mjs --self-test :: 0; node scripts/check-empty-changeset.mjs --base origin/main :: 0; node scripts/check-empty-changeset.mjs --self-test :: 0; node scripts/check-engine-split-ratio.mjs --days 90 :: 0; node scripts/check-engine-split-ratio.mjs --self-test :: 0; node scripts/check-issue-citations.mjs :: 0; node scripts/check-keyed-text-bounds.mjs :: 0; node scripts/check-keyed-text-bounds.mjs --self-test :: 0; node scripts/check-platform-object-tenancy-census.mjs :: 0; node scripts/check-platform-object-tenancy-census.mjs --self-test :: 0; node scripts/check-plugin-teardown-shape.mjs :: 0; node scripts/check-plugin-teardown-shape.mjs --self-test :: 0; node scripts/check-registry-log-declared.mjs :: 0; node scripts/check-registry-log-declared.mjs --self-test :: 0; node scripts/check-rest-log-spy-declared.mjs :: 0; node scripts/check-rest-log-spy-declared.mjs --self-test :: 0; node scripts/check-spec-docblock-symbol-anchors.mjs :: 0; node scripts/check-spec-docblock-symbol-anchors.mjs --self-test :: 0; node scripts/check-system-context-census.mjs :: 0; node scripts/check-system-context-census.mjs --self-test :: 0; node scripts/check-undeclared-dep-imports.mjs :: 0; node scripts/check-undeclared-dep-imports.mjs --self-test :: 0; node scripts/docs-audit/check-affected-docs.mjs :: 0; node scripts/docs-audit/check-drift-comment.mjs :: 0; node scripts/pm/release-rehearsal-clone.mjs --self-test :: 0; node scripts/release-pending-publish.mjs --self-test :: 0; pnpm --filter @objectstack/lint run check:doc-formula-expressions :: 0; pnpm --filter @objectstack/spec run check:api-surface :: 0; pnpm --filter @objectstack/spec run check:authorable-surface :: 0; pnpm --filter @objectstack/spec run check:browser-reachable-entries :: 0; pnpm --filter @objectstack/spec run check:docs :: 0; pnpm --filter @objectstack/spec run check:dual-source-exports :: 0; pnpm --filter @objectstack/spec run check:duration-unit-keys :: 0; pnpm --filter @objectstack/spec run check:empty-state :: 0; pnpm --filter @objectstack/spec run check:entry-nameability :: 0; pnpm --filter @objectstack/spec run check:error-code-provenance :: 0; pnpm --filter @objectstack/spec run check:export-origins :: 0; pnpm --filter @objectstack/spec run check:exported-any :: 0; pnpm --filter @objectstack/spec run check:liveness :: 0; pnpm --filter @objectstack/spec run check:llms-txt :: 0; pnpm --filter @objectstack/spec run check:objectui-pin-citations :: 0; pnpm --filter @objectstack/spec run check:skill-refs :: 0; pnpm --filter @objectstack/spec run check:strictness-ledger :: 0; pnpm --filter @objectstack/spec run check:variant-docs :: 0; pnpm --filter @objectstack/spec run check:yaml-examples :: 0; pnpm check:changeset-gate-self-tests :: 0; pnpm check:cross-package-test-inputs :: 0; pnpm check:dispatcher-error-vocabulary :: 0; pnpm check:doc-authoring :: 0; pnpm check:driver-memory-census :: 0; pnpm check:dts-closure :: 0; pnpm check:dual-build-cjs-loads :: 0; pnpm check:durability-log-level :: 0; pnpm check:engine-double-contract :: 0; pnpm check:error-code-casing :: 0; pnpm check:error-status-conformance :: 0; pnpm check:filter-alias-parity :: 0; pnpm check:gitlink-declared :: 0; pnpm check:issue-citations :: 0; pnpm check:kernel-hook-pairs :: 0; pnpm check:lean-entry-closure :: 0; pnpm check:logger-receiver-detach :: 0; pnpm check:merge-driver :: 0; pnpm check:nul-bytes :: 0; pnpm check:objectql-double-limit :: 0; pnpm check:objectui-changeset :: 0; pnpm check:org-identifier :: 0; pnpm check:page-declaration-shape :: 0; pnpm check:pm-changeset-deadline-census :: 0; pnpm check:pm-prior-rulings :: 0; pnpm check:published-files :: 0; pnpm check:query-options-erasure :: 0; pnpm check:refd-timer-probe :: 0; pnpm check:slot-lookup :: 0; pnpm check:sourcemap-no-sources-content :: 0; pnpm check:spec-parsed-alias :: 0; pnpm check:stack-collection-maps :: 0; pnpm check:swallow-census-controls :: 0; pnpm check:test-source-alias :: 0; pnpm check:tier-file-adoption :: 0; pnpm check:type-check-coverage :: 0; pnpm check:type-check-debt :: 0; pnpm check:watch-hint-literal :: 0; pnpm check:where-matcher :: 0; PR #22768 read back after the push: draft, head 04cf472, mergeable_state blocked (no longer dirty). CI not awaited.",
    "mcp_calls": "0 — no MCP GitHub tool was called; reads through gh api REST GETs (PR state)",
    "api_writes": "Round 3: 1 — fleet-write relay (objectstack-fleet[bot]), POST /repos/objectstack-ai/objectstack/dispatches carrying this os-dev-report comment = POST /repos//issues/22737/comments. git push not counted. No PR-body or label write.",
    "open_questions": [],
    "out_of_scope_findings": []
    }


    Generated by Claude Code

  5. objectstack-fleet commented on Oct 11, 2026

    @objectstack-fleet
    ContributorAuthor

    os-dev-report
    {
    "issue": 22737,
    "status": "done",
    "branch": "claude/issue-22737-relation-filter-exposure",
    "pr": "#22768",
    "session": "session_01JfJfBUC3cQ6hhgm9MQK76T — the dispatching seat's id, inherited by this subagent",
    "premise_still_valid": true,
    "summary": "PATCH ROUND 4 (one merge after #22770, as instructed). A bounded background poller (60-minute deadline) was awaited in the foreground with tail --pid. #22770 merged_at = 2026-10-11T07:25:41Z, so no timeout fallback was needed. origin/main c74d843 (6 commits since the last merge, including #22770 c74d843 for #22739) was merged into the branch as merge commit 606e50f (parents 04cf472 and c74d843). No rebase, no force-push; fast-forward push. Conflicted file: one, packages/core/src/security/second-object-read-exposure.pin.test.ts, in one hunk: the header sentence. The decided list, the operation type and the CALLERS rows auto-merged. Both sides had widened operation to get|list identically, and the list merged to the six-entry union. Header resolved as the union: it names the $expand, the dataset label passes, the import door (#22739) and the nested-relation filter condition (#22737), and keeps main's wording "Each read classified decided below now asks the target". Union as committed: decided = refuseUnservedRelationTarget (row 4, list), servesExpansionTarget, servesLabelTarget, servesPayloadDisplayTarget (row 5, get), servesReferenceTarget (row 7, list) and servesSummaryTitleTarget (row 6, get). Zero CALLERS rows are kind open. The file's diff against origin/main is exactly this branch's change: the header clause, row 4 decided, and refuseUnservedRelationTarget in the expected list. Nothing else was changed. line_budget vs merge base c74d843: 12 files, +457/-16 = 473 changed lines. Note: origin/main moved again afterwards to 5fc57b3 (6 commits: #22779, #22798, #22796, #22784, #22791, #22746). None of them touches any of this branch's 12 paths, so it was not merged again (one merge, as instructed). PR mergeable_state read unknown, i.e. still computing, right after the push.",
    "tests": "ROUND 4 at 606e50f. The merge brings core (import-runner), plugin-audit, spec, plugin-approvals, rest and verify changes, all in the dogfood closure, so the closure was rebuilt: turbo build of the dogfood dependencies plus rest, 63 tasks, VERDICT command-exit 0. The dual-build dists plus cli and skills were built too (68 tasks); spec check:generated reports 14 artifacts up to date (re-run alone after the first attempt hit the foreground cap). core vitest --project repo: 5 files / 55 passed, including second-object-read-exposure.pin.test.ts. core typecheck (tsc, examples, check:test-typecheck OK) exit 0. metadata-protocol protocol.relation-condition-target-exposure.test.ts with protocol.expand-target-exposure.test.ts: 2 files / 28 passed. dogfood second-object-exposure.dogfood.test.ts: 34/34. Extra: types exposure-arm pin 4/4; rest repo 5 files / 209 passed, 1 skipped. All in one locked run, VERDICT command-exit 0. GATES at 606e50f: dispatch-gates --commands on the merged change set (12 paths vs merge base c74d843) derives 91. The union with the dispatch list is 95 commands, all exit 0, each captured before any pipe. --ran: 91 derived, 91 run, 0 NOT-MEASURED, 0 UNRUN. Per command: node scripts/check-adr-0087-registration.mjs --base origin/main :: 0; node scripts/check-adr-0087-registration.mjs --self-test :: 0; node scripts/check-changeset-no-major.mjs --base origin/main :: 0; node scripts/check-changeset-no-major.mjs --self-test :: 0; node scripts/check-ci-filter-parity.mjs :: 0; node scripts/check-closing-keyword-parity.mjs :: 0; node scripts/check-closing-keyword-parity.mjs --self-test :: 0; node scripts/check-comment-mask-adoption.mjs :: 0; node scripts/check-comment-mask-adoption.mjs --self-test :: 0; node scripts/check-comment-mask-corpus.mjs :: 0; node scripts/check-dev-prereqs.mjs --self-test :: 0; node scripts/check-dts-emitted.mjs --self-test :: 0; node scripts/check-empty-changeset.mjs --base origin/main :: 0; node scripts/check-empty-changeset.mjs --self-test :: 0; node scripts/check-engine-split-ratio.mjs --days 90 :: 0; node scripts/check-engine-split-ratio.mjs --self-test :: 0; node scripts/check-issue-citations.mjs :: 0; node scripts/check-keyed-text-bounds.mjs :: 0; node scripts/check-keyed-text-bounds.mjs --self-test :: 0; node scripts/check-platform-object-tenancy-census.mjs :: 0; node scripts/check-platform-object-tenancy-census.mjs --self-test :: 0; node scripts/check-plugin-teardown-shape.mjs :: 0; node scripts/check-plugin-teardown-shape.mjs --self-test :: 0; node scripts/check-registry-log-declared.mjs :: 0; node scripts/check-registry-log-declared.mjs --self-test :: 0; node scripts/check-rest-log-spy-declared.mjs :: 0; node scripts/check-rest-log-spy-declared.mjs --self-test :: 0; node scripts/check-spec-docblock-symbol-anchors.mjs :: 0; node scripts/check-spec-docblock-symbol-anchors.mjs --self-test :: 0; node scripts/check-system-context-census.mjs :: 0; node scripts/check-system-context-census.mjs --self-test :: 0; node scripts/check-undeclared-dep-imports.mjs :: 0; node scripts/check-undeclared-dep-imports.mjs --self-test :: 0; node scripts/docs-audit/check-affected-docs.mjs :: 0; node scripts/docs-audit/check-drift-comment.mjs :: 0; node scripts/pm/release-rehearsal-clone.mjs --self-test :: 0; node scripts/release-pending-publish.mjs --self-test :: 0; pnpm --filter @objectstack/lint run check:doc-formula-expressions :: 0; pnpm --filter @objectstack/spec run check:api-surface :: 0; pnpm --filter @objectstack/spec run check:authorable-surface :: 0; pnpm --filter @objectstack/spec run check:browser-reachable-entries :: 0; pnpm --filter @objectstack/spec run check:docs :: 0; pnpm --filter @objectstack/spec run check:dual-source-exports :: 0; pnpm --filter @objectstack/spec run check:duration-unit-keys :: 0; pnpm --filter @objectstack/spec run check:empty-state :: 0; pnpm --filter @objectstack/spec run check:entry-nameability :: 0; pnpm --filter @objectstack/spec run check:error-code-provenance :: 0; pnpm --filter @objectstack/spec run check:export-origins :: 0; pnpm --filter @objectstack/spec run check:exported-any :: 0; pnpm --filter @objectstack/spec run check:liveness :: 0; pnpm --filter @objectstack/spec run check:llms-txt :: 0; pnpm --filter @objectstack/spec run check:objectui-pin-citations :: 0; pnpm --filter @objectstack/spec run check:skill-refs :: 0; pnpm --filter @objectstack/spec run check:strictness-ledger :: 0; pnpm --filter @objectstack/spec run check:variant-docs :: 0; pnpm --filter @objectstack/spec run check:yaml-examples :: 0; pnpm check:changeset-gate-self-tests :: 0; pnpm check:cross-package-test-inputs :: 0; pnpm check:dispatcher-error-vocabulary :: 0; pnpm check:doc-authoring :: 0; pnpm check:driver-memory-census :: 0; pnpm check:dts-closure :: 0; pnpm check:dual-build-cjs-loads :: 0; pnpm check:durability-log-level :: 0; pnpm check:engine-double-contract :: 0; pnpm check:error-code-casing :: 0; pnpm check:error-status-conformance :: 0; pnpm check:filter-alias-parity :: 0; pnpm check:gitlink-declared :: 0; pnpm check:issue-citations :: 0; pnpm check:kernel-hook-pairs :: 0; pnpm check:lean-entry-closure :: 0; pnpm check:logger-receiver-detach :: 0; pnpm check:merge-driver :: 0; pnpm check:nul-bytes :: 0; pnpm check:objectql-double-limit :: 0; pnpm check:objectui-changeset :: 0; pnpm check:org-identifier :: 0; pnpm check:page-declaration-shape :: 0; pnpm check:pm-changeset-deadline-census :: 0; pnpm check:pm-prior-rulings :: 0; pnpm check:published-files :: 0; pnpm check:query-options-erasure :: 0; pnpm check:refd-timer-probe :: 0; pnpm check:slot-lookup :: 0; pnpm check:sourcemap-no-sources-content :: 0; pnpm check:spec-parsed-alias :: 0; pnpm check:stack-collection-maps :: 0; pnpm check:swallow-census-controls :: 0; pnpm check:test-source-alias :: 0; pnpm check:tier-file-adoption :: 0; pnpm check:type-check-coverage :: 0; pnpm check:type-check-debt :: 0; pnpm check:watch-hint-literal :: 0; pnpm check:where-matcher :: 0; CI not awaited.",
    "mcp_calls": "0 — no MCP GitHub tool was called; reads through gh api REST GETs (PR 22770 merge state, PR 22768 state)",
    "api_writes": "Round 4: 1 — fleet-write relay (objectstack-fleet[bot]), POST /repos/objectstack-ai/objectstack/dispatches carrying this os-dev-report comment = POST /repos//issues/22737/comments. git push not counted. No PR-body or label write.",
    "open_questions": [],
    "out_of_scope_findings": []
    }


    Generated by Claude Code

  6. objectstack-fleet commented on Oct 11, 2026

    @objectstack-fleet
    ContributorAuthor

    ACCEPT (seat review): PR #22768 at head 606e50f7ef

    domain:engine seat 1 (#6367) · session_01JfJfBUC3cQ6hhgm9MQK76T · 2026-10-11T08:36Z. Claim 6104514778, with its round-2 revision. Dev reports 6105527933 (round 1), 6106039434 (round 2, the seat-ruled rider), 6106452488 and 6107096768 (rounds 3 and 4, merges of main only). Contract review PASS 6106147584 (CONTRACT_REVIEW_TIER) on 964aceffd6, and the supplementary record PASS 6107185299 on this head. Read against GitHub and origin/main, not against the reports. ⛔ Classes, positions and functions only.

    Shape.

    • Ready to queue: base main, assigned os-project-manager, mergeable_state clean.
    • Line 1 is Fixes #22737. A closing-keyword scan of the whole body finds that line only.
    • Line 2 is Clause-②: no (narrowing), and each of the three changesets agrees.
    • 12 files. NOT governed.

    What it does.

    • The data door's nested-relation walk asks each condition's TARGET object the spec's one exposure decision, for list, before anything of it is judged. The new method is refuseUnservedRelationTarget, and it calls apiExposureDenialReason.
    • The operation is list, not $expand's get. A condition is a predicate over the whole target, which is the read the target's own filtered list performs. A get-only target is refused here and stays served to $expand.
    • An unserved target is refused, not withheld, because dropping a condition widens the result. The answer is the door's existing pair: 404 OBJECT_API_DISABLED, or 405 OBJECT_API_METHOD_NOT_ALLOWED with allowed. A member's 403 at the same position is the precedent, and it is pinned unchanged.
    • It covers every position the walk covers: where, the filter alias, each aggregation's filter (before the grouped branch forks), and each expand entry's own where at every level.
    • The engine and the filter compilation are untouched, so the engine's privileged callers keep their path.
    • The rider, ruled in round 2: one arm in @objectstack/types' structuredCodeAnswer. The REST body names the refused TARGET and relays allowed. It does so only when the error names an object other than a defined route object. Every other producer's answer is byte for byte unchanged, which is measured and pinned.
    • The ledger appends both existing codes in @objectstack/metadata-protocol's own section. No new code is added.

    The merges, read on GitHub.

    • 606e50f7ef has parents 04cf472669 and c74d843997 (fix(core)!: an import's reference resolution asks the lookup target its declared exposure before matching a cell (#22739) #22770). Its merge base with main is c74d843997.
    • Against that base, 11 of the 12 files carry net added and removed lines identical to the reviewed head's (9859e4ec42..964aceffd6).
    • The 12th is the census pin. It holds the union: six decided reads (row 4 refuseUnservedRelationTarget, list, beside rows 1–3 and 5–7), no CALLERS row open, and the header names all four doors.
    • main has since moved to 5fc57b382e without touching these 12 paths. The queue builds against the then-current main.

    Evidence read.

    • On a real stack, for an administrator and a member, on the list and query routes:
      • every refused shape moved from evaluated to 404 or 405;
      • the list-only and open controls are unchanged;
      • the 403 precedent is unchanged.
    • Ablations each went red where predicted, with every restore proven: A (the wiring), B (on dist), C (a hand-spelled rule against the enumeration pin), D (the ledger rows against check:error-code-provenance) and E (the types arm).
    • At 606e50f7ef:
      • core repo 5 files / 55;
      • metadata-protocol's two exposure pins 28/28;
      • the dogfood pin 34/34;
      • the types arm pin 4/4;
      • rest repo 5 files / 209.
    • Gates: 95 commands, all exit 0 (--ran: 91 derived, 91 run).
    • CI on this head: 35 check-runs, all concluded (32 success, 3 rostered skips).

    Changesets checked.

    • @objectstack/metadata-protocol is minor, with !, a **BREAKING** banner and one ADR-0087 not-required marker. FROM → TO and the migration are stated (filter on the lookup's stored id, or declare list).
    • @objectstack/spec is patch, the ledger value only.
    • @objectstack/types is patch, the arm.

    Files outside the claim's first surface:

    • the packages/types arm and its pin, from the round-2 revision;
    • test-only legs in packages/qa/dogfood;
    • two packages/rest drift guards.

    All were declared on domain:cli (#6024: 6105548307, 6106072362). The PR body's verification lines were brought to the final head in this act. They had named round 1's merge as the final commit.

    Landing.


    Generated by Claude Code

  7. objectstack-fleet commented on Oct 11, 2026

    @objectstack-fleet
    ContributorAuthor

    Landed: PR #22768 → ec7c7e0637 · domain:engine seat 1 (#6367) · session_01JfJfBUC3cQ6hhgm9MQK76T · 2026-10-11T09:07Z

    ⛔ Classes, positions and functions only.


    Generated by Claude Code

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Labels

area:accessPermissions that actually hold — RLS/FLS, sharing model, write-path guardsbugSomething isn't workingdomain:enginepriority:p2Medium: important, M3security

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions