Repository navigation
security(auth): the identity import hands runImport a protocol without getMetaItem, so its reference cells skip the target's exposure ask (census row 9 of #22661) #22800
Copy link
Copy link
Closed
Labels
area:accessPermissions that actually hold — RLS/FLS, sharing model, write-path guardsPermissions that actually hold — RLS/FLS, sharing model, write-path guardsbugSomething isn't workingSomething isn't workingdomain:enginepriority:p3security
Description
This card carries census row 9 of #22661 (part of #22661). It was found by the contract review of PR #22770 (6106439614, ①.7 and ③), and ruled in this seat's ACCEPT on #22739 (6106456351). ⛔ Classes, positions and functions only.
Filing class: ① a product defect, class (a). Reach: a named real producer, by source reading on origin/main c74d843997; not measured at the door.
Reader who acts: the domain:engine lane, seat 1 (#6367), which owns #22661's derived sub-issues. The fix lands in packages/plugins/plugin-auth (domain:services), so the claim declares the cross-domain path.
The gap
runImport'sresolveRef(packages/core/src/utils/import-runner.ts) asks a lookup target its declared exposure through the protocol's optionalgetMetaItem. A protocol without that member is not judged. That is the capability-gate shape PR fix(core)!: an import's reference resolution asks the lookup target its declared exposure before matching a cell (#22739) #22770 landed.- The identity import (
packages/plugins/plugin-auth/src/admin-import-users.ts) builds its run protocol withoutgetMetaItem, so its reference cells are matched without the ask. - Yet the same function holds
deps.getMetaItemand hands it to the prepare step a few lines earlier (around:456). So "no declaration to judge" is a choice at this door, not a fact of it. - security(data, analytics): a lookup target's exposure declaration is not judged when the data door's
$expand, or the dataset door's dimension-label pass, reads it — detail withheld pending maintainer #22661's family refused "reads run under the system identity" as an exemption at the data door.
Why p3
- It is reachable only from a platform-admin door.
- The targets are
sys_user's own reference fields' targets. Whether any of them declares an exposure that refuseslistis NOT MEASURED. - The privileged-door rule caps such a defect at
p3.
Direction (not a ruling)
- The identity import's run protocol carries
getMetaItem(thedepsmember it already holds), soservesReferenceTargetjudges its targets like every otherrunImportcaller. ⛔ No second rule. - Measure first: which targets
sys_user's reference fields name, and whether any refuseslist. If none does, state the null and pin the wiring anyway, so a future refusing declaration is judged. - Pin: the identity import asks the decision (an unserved fixture target is refused, and a served one is unchanged). Ablation-verified.
- The second residual, a future
runImportcaller without the member, rides the parent's close-out.
Duplicate check
- Population: 4,501 issues, closed included, ADR-0048: Semantic-reference metadata embeds in package docs (flow:// / view:// / dashboard:// / object://) #1795 to [Decision] explain 的
read判定:调用方读不到的那一行,要不要答得和一个不存在的 id 完全一样(#21771 裁决 A 是否延伸到 explain 的 read) #22795 (REST paging since 2026-09-01, plus recent windows). A local grep over titles and bodies. - Queries:
admin-import-users: 8, all closed (lint: theapproval-approvers-may-resolve-emptyremedy tells authors the admin bulk import does not writesys_user.manager_id; it has since PR #18046 #22683, [ruling row 7] the user bulk import must admitmanager_id, resolved in a second pass keyed on the importer's identity key — split from #16678 #18028, ApproverType's.describe()still offersmanagerunqualified, so the generated reference page sells a rung whose column has no product write surface #17579, [finding]admin-import-users'sfindData(args: any)annotation opts it out of theImportProtocolLikecontract #16952 just declared — the one implementor the class actually bit stays unchecked #17422, [finding]ImportProtocolLike's three methods takeargs: any, so the exported extension point never declares which query dialectrunImportsends — and an in-repo implementor froze on the undeclared one #16952, [finding] plugin-auth: 5 more first-sibling-pays dynamic imports charged to a case's own testTimeout — census attached, same shape as the durability-swallow-repair flake #15916, [finding] plugin-authdurability-swallow-repair.test.ts:673times out at 10 s on a coldTest Core (6/6)shard — a dynamic import charged to the test's own budget (red twice on PR #15791, green on main) #15852, [finding] plugin-authdurability-swallow-repair.test.ts:673times out at 10000 ms on the PR-CI Test Core (6/6) shard — three unrelated PRs red on it in one hour while merge-group runs of the same shard passed #15603).- "identity import": 2, both closed.
- import with
plugin-authand exposure orapiEnabled: 3. Two are closed (QA run · surface:api (89/89) · 251a7dd4 · 2026-10-04 · 68 PASS / 7 PARTIAL / 14 FAIL / 0 BLOCKED / 0 NOT-RUN #21720, [finding] packages/core/PHASE2_IMPLEMENTATION.md's fenced TypeScript blocks are in no tsc program — a block that has NEVER compiled survived two sweeps, and the gate #15931 proposed would not have caught it #18715), and the third is thedomain:cliseat post.
- Control:
plugin-auth187.
Generated by Claude Code
Reactions are currently unavailable
Metadata
Metadata
Assignees
Labels
area:accessPermissions that actually hold — RLS/FLS, sharing model, write-path guardsPermissions that actually hold — RLS/FLS, sharing model, write-path guardsbugSomething isn't workingSomething isn't workingdomain:enginepriority:p3security
Activity
objectstack-fleet commentedon Oct 11, 2026
Release:
session_01JfJfBUC3cQ6hhgm9MQK76T(domain:engineseat 1, #6367,os-project-manager) · 2026-10-11T14:33Z · reason: seat 1 dispatches serially, so cards whose regions do not meet its serial queue go to seat 2 to be developed in parallel · destination:domain:engineseat 2 (#20966, 🟢os-steve,session_01ADzJtzYTLUfgrRZHxkagkX), which claims it afresh at its own pick.Claim:fromsession_01JfJfBUC3cQ6hhgm9MQK76Tis on it, and it carries no assignee. Its state stayspm:queue.$expand, or the dataset door's dimension-label pass, reads it — detail withheld pending maintainer #22661 (census row 9). The fix point is inpackages/plugins/plugin-auth(domain:services, declared on [PM seat] domain:services — 🟢 os-bill · session_017WLmd5in9RXu7AjyHR8fKQ · p1/p2 only (maintainer) #6021 at dispatch). It adds row 9 topackages/core/src/security/second-object-read-exposure.pin.test.ts, the same file security(runtime):callData's engine fallback hands a caller'swheretoql.findafter judging only the addressed object, so a nested-relation condition skips the target's exposure ask (census row 10 of #22661) #22823 edits, so the two run one after the other, not side by side.$expand, or the dataset door's dimension-label pass, reads it — detail withheld pending maintainer #22661's row 9. Seat 1 ticks the parent's checklist (6102873261) and closes security(data, analytics): a lookup target's exposure declaration is not judged when the data door's$expand, or the dataset door's dimension-label pass, reads it — detail withheld pending maintainer #22661 when rows 9 and 10 have both landed.Generated by Claude Code
objectstack-fleet commentedon Oct 11, 2026
Claim: PM loop round 1 · 2026-10-11T14:49Z
Session:
session_01ADzJtzYTLUfgrRZHxkagkXAccount:
os-steve(the seat's linked user asGET /useranswers it; always the card's assignee)Branch:
claude/issue-22800-identity-import-exposureWorktree:
objectstack-issue-22800Domain:
domain:engineSeat:
domain:engine#2(seat post #20966); taken at this seat's own pick after seat 1's release 6110107168File surface (read on
origin/maineed637c09f; stop on a breach and explain it in the report):domain:services(declared on [PM seat] domain:services — 🟢 os-bill · session_017WLmd5in9RXu7AjyHR8fKQ · p1/p2 only (maintainer) #6021 at this claim):packages/plugins/plugin-auth/src/admin-import-users.ts, the run protocol handed torunImportgains thegetMetaItemmemberdepsalready holds; and its tests inplugin-auth.packages/core/src/security/second-object-read-exposure.pin.test.ts: census row 9 moves from open to decided.packages/core/src/utils/import-runner.ts(servesReferenceTarget). ⛔ No edit there.Container & model:
S,mode:subagent,model: default(dispatch-gates --tier: no path-derived mandate; a security judgement, so not the floor tier)Clause-②: no
listanswers as a target the caller cannot read, as every otherrunImportcaller already does (security(import): an import's reference resolution matches a lookup cell against a target whose exposure refuses reads (census row 7 of #22661) #22739). It owes one contract-review-tier review before the queue.Responsibility:
plugin-auth's identity import, which builds its run protocol withoutgetMetaItem, sorunImport'sservesReferenceTargetanswers served without asking | the one exposure decisionservesReferenceTargetasks for every otherrunImportcaller (security(import): an import's reference resolution matches a lookup cell against a target whose exposure refuses reads (census row 7 of #22661) #22739, PR fix(core)!: an import's reference resolution asks the lookup target its declared exposure before matching a cell (#22739) #22770) | a platform administrator using the identity import; whether any ofsys_user's reference targets refuseslistis NOT MEASURED, and the card's first step measures itThread-read: 6110107168
Serial constraints cleared: security(import): an import's reference resolution matches a lookup cell against a target whose exposure refuses reads (census row 7 of #22661) #22739 (PR fix(core)!: an import's reference resolution asks the lookup target its declared exposure before matching a cell (#22739) #22770,
c74d843997) landed the gate this card wires. None of the 11 open PRs touchesadmin-import-users.tsor the census pin file. security(runtime):callData's engine fallback hands a caller'swheretoql.findafter judging only the addressed object, so a nested-relation condition skips the target's exposure ask (census row 10 of #22661) #22823 (census row 10, same pin file) runs after this card, on this seat's serial line. This seat's [finding] import: a sandbox's own fault (CPU budget, wall-clock ceiling) reaches an import row as its debug wrapper, where the data doors answerInternal server error#22741 editsimport-runner.ts'stoFailedResult, which this card only reads. Epic [epic] ADR-0131 — total organization ownership: no NULL organization_id (v18 line) #15194'splugin-authterritory (catalog reads inauth-manager,ensure-default-organization,last-admin-guard,auto-org-admin-grant) does not includeadmin-import-users.ts.fix(plugin-auth): the identity import's run protocol carries the decl…
test(core): census row 9 — the identity import's door is named beside…
test(plugin-auth): the identity import's exposure pins reuse the file…
objectstack-fleet commentedon Oct 11, 2026
os-dev-report
{
"issue": 22800,
"status": "done",
"branch": "claude/issue-22800-identity-import-exposure",
"pr": "#22866",
"session": "session_01ADzJtzYTLUfgrRZHxkagkX — the dispatching seat's id, inherited by this subagent",
"premise_still_valid": true,
"summary": "Premise confirmed on origin/main eed637c. runAdminImportUsers (plugin-auth/src/admin-import-users.ts) gave its declaration reader (deps.getMetaItem) to prepareImportRequest but built its run protocol without it, so runImport's servesReferenceTarget answered served without asking. Fix: one binding, metaReader, handed to the parser AND spread into the run protocol, so whenever a cell is a reference (the field map exists only with the reader) its target is asked the family's one decision for list. No second rule, and no edit to core or rest. Signature measured: deps' parameter type { type; name } is a supertype of GetMetaItemRequest's required members, so it is assignable to ImportProtocolLike.getMetaItem by parameter contravariance; tsc green, no adapter written. The producer (auth-plugin.ts) forwards the protocol service's getMetaItem, which answers the { type, name, item } envelope servesReferenceTarget reads. First measurement, the NULL: sys_user declares two lookups, manager_id -> sys_user (apiMethods get/list/update/bulk) and primary_business_unit_id -> sys_business_unit (apiEnabled, default-open). Both serve list. The registry injects no columns into a managedBy better-auth object, no in-repo extension targets sys_user, and manager_id is stripped before runImport. So no shipped answer changes, and the wiring is pinned anyway. Census row 9: the pin file had NO row for this door to move (the identity import never calls referenceTargetOf). The door is named in the docblock in row 8's shape (it reaches the classified import-field-meta.ts read without calling the arbiter, and is held by its own package's pin).",
"tests": "All at final head 49e1628 unless named. PINS: the [#22800] block of plugin-auth/src/admin-import-users.test.ts, 6 cases, on the file's already-pinned engine double with only find overridden: two refusing declarations (apiEnabled false; get-only whitelist) answer match, miss and id reference_not_found, never read the target, create no user; two serving controls resolve match and id and report the miss; the target is asked through the reader once by name; with no reader no cell is a reference. admin-import-users.test.ts + admin-import-users-manager-pass.test.ts 56/56. ABLATION via scripts/ablation-replace.mjs on committed 49e1628, anchor '...metaReader,' deleted, 1 -> 0, blob 7be1a3b399e7 -> c5bf2e31b008. Subject imported by relative path from source, so no dist hop. Result 3 red / 3 green: both refusing legs (match and id came back ok:true, the pre-fix answer) and the asked-through-reader leg (only sys_user asked); the 2 serving controls and the no-reader control stayed green. Restore proven: blob == HEAD 7be1a3b399e7, git diff HEAD empty. The same 3/3 was seen on 574f795 with the pins in a separate file. PACKAGES: turbo build --filter='@objectstack/plugin-auth^...' 27/27 at 574f795; plugin-auth built at 4405bc2 (admin-import-users.ts blob unchanged since). plugin-auth vitest 136 files / 2786 passed / 10 skipped. plugin-auth typecheck exit 0, including check:test-typecheck: the edited test file's 11 ledgered errors all sit above the new block, and the new block adds none. core census pin 4/4 (--project repo); core typecheck exit 0. CJS probe: plugin-auth dist/index.js loads, 148 exports. LINT narrowed and proven: eslint --no-inline-config --format json on the 3 changed .ts files gives 3 files, 0 errors, 0 warnings. Population from eslint's own config: isPathIgnored false and a config resolves for each. eslint.config.mjs enables no type-aware linting (no parserOptions.project), so no untouched file's verdict can move. Repo-wide pnpm lint is CI's. GATES: see gates; dispatch-gates --ran with exit codes recorded: 67 derived, 67 run, 0 NOT-MEASURED (derived), 0 UNRUN. CI not awaited.",
"mcp_calls": "0 — no MCP GitHub tool was called; reads went through gh api REST GETs",
"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 #22866, run 38156146492, read-back 10074 bytes sent and stored, identical) + POST /repos//issues/22866/assignees os-steve; (2) this os-dev-report comment = POST /repos//issues/22800/comments via scripts/pm/post-stamped.mjs. Zero label writes (the dispatch names none; a changeset exists). git push of the branch not counted (4 pushes: empty probe, 3 commits).",
"open_questions": [
{
"question": "Clause-② was copied from the claim as bare
no, with a patch changeset. The family's two precedents declared their narrowing with the(narrowing)arm: #22739no (narrowing)minor, the MCP bridge rowyes (narrowing). Should this card carry the arm too?","options": [
"A keep bare
no+ patch: the measured shipped population whose answer moves is zero, and the door is platform-admin only","B
no (narrowing): BREAKING, so minor + an ADR-0087 disposition marker in the changeset, matching the family's two precedents; deployed extensions of sys_user (not measured) are the population that could move"],
"recommendation": "B, for the contract review the claim already names to confirm: an accept-set narrowing is real in principle even at zero measured producers, and the family has spelled it with the arm both times. A was left in place because the dispatch says copy the claim; switching is one changeset edit plus the PR's line 2, by the seat."
}
],
"out_of_scope_findings": [
"carrier: #22741's PR (this seat, edits packages/core/src/utils/import-runner.ts now), else the next PR to touch that file · noted, not filed — the ImportProtocolLike.getMetaItem TSDoc (import-runner.ts around :214, emitted into core's dist/index.d.ts) still gives "plugin-auth's identity import" as the example of a protocol without the member. After this PR that example is false; the rule it illustrates still holds. Not yet released (npm @objectstack/core is 17.7.0 and the #22739 changeset is still pending). Not edited here, because the dispatch forbids that file.",
"carrier: the release-notes compilation (maintainer); no seat named · noted, not filed — the pending .changeset/22739-import-reference-target-exposure.md Bindings bullet says the identity import 'is not judged'. Neither changeset has been released, so the next CHANGELOG carries both sentences. This PR's changeset states FROM -> TO, so the order is legible."
],
"gates": [
"node scripts/check-adr-0087-registration.mjs --base origin/main :: exit 0",
"node scripts/check-adr-0087-registration.mjs --self-test :: exit 0",
"node scripts/check-changeset-no-major.mjs --base origin/main :: exit 0",
"node scripts/check-changeset-no-major.mjs --self-test :: exit 0",
"node scripts/check-ci-filter-parity.mjs :: exit 0",
"node scripts/check-closing-keyword-parity.mjs :: exit 0",
"node scripts/check-closing-keyword-parity.mjs --self-test :: exit 0",
"node scripts/check-comment-mask-adoption.mjs :: exit 0",
"node scripts/check-comment-mask-adoption.mjs --self-test :: exit 0",
"node scripts/check-comment-mask-corpus.mjs :: exit 0",
"node scripts/check-dev-prereqs.mjs --self-test :: exit 0",
"node scripts/check-dts-emitted.mjs --self-test :: exit 0",
"node scripts/check-empty-changeset.mjs --base origin/main :: exit 0",
"node scripts/check-empty-changeset.mjs --self-test :: exit 0",
"node scripts/check-issue-citations.mjs :: exit 0",
"node scripts/check-keyed-text-bounds.mjs :: exit 0",
"node scripts/check-keyed-text-bounds.mjs --self-test :: exit 0",
"node scripts/check-platform-object-tenancy-census.mjs :: exit 0",
"node scripts/check-platform-object-tenancy-census.mjs --self-test :: exit 0",
"node scripts/check-plugin-teardown-shape.mjs :: exit 0",
"node scripts/check-plugin-teardown-shape.mjs --self-test :: exit 0",
"node scripts/check-registry-log-declared.mjs :: exit 0",
"node scripts/check-registry-log-declared.mjs --self-test :: exit 0",
"node scripts/check-rest-log-spy-declared.mjs :: exit 0",
"node scripts/check-rest-log-spy-declared.mjs --self-test :: exit 0",
"node scripts/check-system-context-census.mjs :: exit 0",
"node scripts/check-system-context-census.mjs --self-test :: exit 0",
"node scripts/check-tenant-audit-census.mjs :: exit 0",
"node scripts/check-tenant-audit-census.mjs --self-test :: exit 0",
"node scripts/check-undeclared-dep-imports.mjs :: exit 0",
"node scripts/check-undeclared-dep-imports.mjs --self-test :: exit 0",
"node scripts/docs-audit/check-affected-docs.mjs :: exit 0",
"node scripts/docs-audit/check-drift-comment.mjs :: exit 0",
"node scripts/pm/release-rehearsal-clone.mjs --self-test :: exit 0",
"node scripts/release-pending-publish.mjs --self-test :: exit 0",
"pnpm --filter @objectstack/spec run check:duration-unit-keys :: exit 0",
"pnpm check:changeset-gate-self-tests :: exit 0",
"pnpm check:cross-package-test-inputs :: exit 0",
"pnpm check:dispatcher-error-vocabulary :: exit 0",
"pnpm check:doc-authoring :: exit 0",
"pnpm check:driver-memory-census :: exit 0",
"pnpm check:dts-closure :: exit 0",
"pnpm check:dual-build-cjs-loads :: exit 0",
"pnpm check:engine-double-contract :: exit 0",
"pnpm check:error-status-conformance :: exit 0",
"pnpm check:gitlink-declared :: exit 0",
"pnpm check:issue-citations :: exit 0",
"pnpm check:kernel-hook-pairs :: exit 0",
"pnpm check:lean-entry-closure :: exit 0",
"pnpm check:logger-receiver-detach :: exit 0",
"pnpm check:nul-bytes :: exit 0",
"pnpm check:objectql-double-limit :: exit 0",
"pnpm check:objectui-changeset :: exit 0",
"pnpm check:org-identifier :: exit 0",
"pnpm check:page-declaration-shape :: exit 0",
"pnpm check:pm-changeset-deadline-census :: exit 0",
"pnpm check:published-files :: exit 0",
"pnpm check:query-options-erasure :: exit 0",
"pnpm check:refd-timer-probe :: exit 0",
"pnpm check:slot-lookup :: exit 0",
"pnpm check:sourcemap-no-sources-content :: exit 0",
"pnpm check:test-source-alias :: exit 0",
"pnpm check:tier-file-adoption :: exit 0",
"pnpm check:type-check-coverage :: exit 0",
"pnpm check:type-check-debt :: exit 0",
"pnpm check:watch-hint-literal :: exit 0",
"pnpm check:where-matcher :: exit 0",
"node scripts/check-changeset-fixed.mjs :: exit 0",
"pnpm check:auth-mount-ledger :: exit 0",
"pnpm check:authz-resolver :: exit 0",
"pnpm check:error-code-casing :: exit 0",
"pnpm check:filter-alias-parity :: exit 0"
],
"line_budget": "167 — PR #22866 reads additions 164 + deletions 3, 4 files (no generated files)",
"deviations": [
"PM assumption falsified in form: the census pin file had no
openrow 9 to move. The identity import never calls referenceTargetOf, so a CALLERS row would turn the pin's 'classified files still call the arbiter' leg red. The door was named in the docblock instead, following row 8's (#22777) precedent, and is held by plugin-auth's own pin.","Pins live in the existing admin-import-users.test.ts and reuse its pinned makeDeps engine double through a spread override, instead of a new file with a new double. The role file prefers overriding an existing double to pinning a new one, so the ledger is untouched. The first head (574f795) had a separate file whose new double check:engine-double-contract flagged (exit 1); it was moved at 49e1628.",
"Not merged with origin/main: it moved 6 commits (eed637c -> 55382dc) with no path overlap. The only gate input that changed, scripts/engine-double-contract.pinned.json, dropped two rows for a removed plugin-security test and none of this file's rows. AGENTS.md section 10's pre-PR full suite vs the role file's targeted local scope: the role file governs, and CI's merge ref validates the joint tree.",
"Conflict named: the role file says a published defect this change makes false must be fixed. The ImportProtocolLike.getMetaItem TSDoc in import-runner.ts becomes stale, but the dispatch forbids editing that file and the text is not yet released. Left with a carrier (out_of_scope_findings).",
"The harness attribution reminder asked for a model-named Co-Authored-By trailer and a different PR footer. AGENTS.md was followed: the model-free trailer pair 'Co-authored-by: Claude' + Claude-Session, and the session-URL PR footer. The harness form was neither imitated nor written into history.",
"check:dual-build-cjs-loads exited 3 (PREREQUISITE NOT MET) in the first union at 4405bc2 and exited 0 in the final union at 49e1628. The workspace dist was built in between as a side effect of check:type-check-debt's run; check:* gates are not lock-wrapped, per the role file."
],
"files_changed": [
".changeset/22800-identity-import-exposure.md",
"packages/core/src/security/second-object-read-exposure.pin.test.ts",
"packages/plugins/plugin-auth/src/admin-import-users.test.ts",
"packages/plugins/plugin-auth/src/admin-import-users.ts"
]
}
objectstack-fleet commentedon Oct 11, 2026
File-surface addendum to claim 6110244295 ·
domain:engine#2·session_01ADzJtzYTLUfgrRZHxkagkX· 2026-10-11T16:42Z. Patch round 1 on PR #22866 (report 6111252864).packages/core/src/utils/import-runner.ts, theImportProtocolLike.getMetaItemmember's example of a protocol without the reader ("plugin-auth's identity import"). This PR makes that sentence false, and it ships incore'sdist/index.d.ts. ⛔ No other line of the file: [finding] import: a sandbox's own fault (CPU budget, wall-clock ceiling) reaches an import row as its debug wrapper, where the data doors answerInternal server error#22741 (this seat, claim 6110232592) editstoFailedResultin it, a disjoint region.Clause-②: no (narrowing),minorwith an ADR-0087 disposition, following the family's precedent (security(import): an import's reference resolution matches a lookup cell against a target whose exposure refuses reads (census row 7 of #22661) #22739). The PR body's line 2 follows it..changeset/22739-import-reference-target-exposure.mdsays the identity import "is not judged". Both changesets are unreleased, so the next CHANGELOG carries that sentence and this PR's FROM → TO together, in order.fix(plugin-auth)!: declare the identity import's narrowing (Clause-② …
objectstack-fleet commentedon Oct 11, 2026
os-dev-report
{
"issue": 22800,
"status": "done",
"branch": "claude/issue-22800-identity-import-exposure",
"pr": "#22866",
"session": "session_01ADzJtzYTLUfgrRZHxkagkX — the dispatching seat's id, inherited by this subagent",
"premise_still_valid": true,
"summary": "Patch round 1, head 8c40d53. This report supersedes os-dev-report 6111252864. (1) The changeset declares the narrowing, per the seat's answer B. Bump
'@objectstack/plugin-auth': minor. Summary linefix(plugin-auth)!:.Clause-②: no (narrowing). The one-line**BREAKING** (an accept-set narrowing), shipped as minor under the launch-window convention for breaking changes, as #22739's changeset has. The ADR-0087 marker isnot-required (no-migration-prescription), the only true category: no spec key, spelling, export or stored shape moves; plugin-auth publishes, so not unpublished; no ADR-0087 id is touched; and no exported declaration is removed or narrowed, so not runtime-interface-only or type-surface-only. The gate accepted it: [BREAKING+bang+clause-②-narrowing] not-required (no-migration-prescription). (2) In packages/core/src/utils/import-runner.ts, one sentence of the ImportProtocolLike.getMetaItem TSDoc is reworded (3 lines in, 3 out, :214-216). It now says a protocol without the member has no declaration to judge, and that plugin-auth's identity import carries it like every other caller (#22800). core's rebuilt dist/index.d.ts carries the new sentence (1 hit; the old one has 0). No other line was touched. The :204 mention is about validateData, which the identity import still omits, so it remains true and is left alone. @objectstack/core gets no changeset entry: the role file lists comments on the fast lane (not published), the edit changes no declaration, and Check Changeset needs only a changeset to exist. check-changeset-no-major with the narrowing line passes ('no package whose published source it moves is graded patch'). (3) The pending .changeset/22739 file is untouched. (4) Gates re-derived (68) and reconciled. (5) The PR body was not edited; the seat's line 2 rewrite is already on it. The sentences this round makes false are in pr_body_false_sentences.","tests": "All at 8c40d53. BUILD: turbo run build --filter='@objectstack/plugin-auth...' 28/28 (8 cache hits) under the lock. core's dist/index.d.ts carries the reworded sentence. PINS: admin-import-users.test.ts + admin-import-users-manager-pass.test.ts 56/56 (the [#22800] block's 6 included); core census pin second-object-read-exposure.pin.test.ts 4/4 (--project repo). core's 12 import-runner*.test.ts files: 138/138. core typecheck exit 0, including check:test-typecheck (4 ledgered files, unchanged). plugin-auth source is unchanged this round (admin-import-users.ts blob 7be1a3b399e7), so the round-0 readings stand: 136 files / 2786 passed / 10 skipped, typecheck exit 0, and the ablation (3 red / 3 green, restore proven) at 49e1628. LINT narrowed and proven: eslint --no-inline-config --format json on the 4 changed .ts files gives 4 files, 0 errors, 0 warnings. isPathIgnored is false and a config resolves for each (5-6 rules). eslint.config.mjs enables no type-aware linting, so no untouched file's verdict can move. Repo-wide pnpm lint is CI's. GATES: dispatch-gates --commands derived 68 (the round-0 67 plus pnpm check:error-code-casing, added because import-runner.ts is now in the diff). --ran with exit codes: 68 derived, 68 run, 0 NOT-MEASURED (derived zero), 0 UNRUN. Per-command codes are in gates. CI not awaited.",
"mcp_calls": "0 — no MCP GitHub tool was called; reads went through gh api REST GETs",
"api_writes": "1 this round — this os-dev-report comment, POST /repos//issues/22800/comments through scripts/pm/post-stamped.mjs (fleet-write relay, one POST /repos/objectstack-ai/objectstack/dispatches). No PR body edit and no label write. Round 0's 2 writes (pr_create + assignee; the first report) stand. git push not counted (1 push this round, 8c40d53).",
"open_questions": [],
"out_of_scope_findings": [
"carrier: the release-notes compilation (maintainer); no seat named · noted, not filed (an acceptance note, which the seat records) — the pending .changeset/22739-import-reference-target-exposure.md Bindings bullet says the identity import 'is not judged'. This PR's changeset now narrows exactly that (FROM -> TO). Both are unreleased, so the next CHANGELOG carries both sentences."
],
"pr_body_false_sentences": [
{
"sentence": "What changed, bullet 2: 'There is no second rule, and nothing in
@objectstack/coreor@objectstack/restis edited.'","replacement": "There is no second rule, and no code in
@objectstack/coreor@objectstack/restchanges; the one@objectstack/coreedit is a TSDoc sentence (see Acceptance notes)."},
{
"sentence": "Verification: 'Each reading names its commit. The final head is
49e162847a.'","replacement": "Each reading names its commit. The final head is
8c40d537f4. That commit changes the changeset and one TSDoc sentence inimport-runner.ts;plugin-auth's source is unchanged since49e162847a(blob7be1a3b399e7), so theplugin-authreadings and the ablation below stand."},
{
"sentence": "Verification: '
@objectstack/coreat49e162847a. The census pin passes 4 / 4 (--project repo).typecheckexit 0.'","replacement": "
@objectstack/coreat8c40d537f4. Rebuilt;dist/index.d.tscarries the reworded sentence. The census pin passes 4 / 4 (--project repo), the 12import-runner*.test.tsfiles pass 138 / 138, andtypecheckexits 0."},
{
"sentence": "Verification: 'Lint ... on the 3 changed
.tsfiles gives 3 files, 0 errors, 0 warnings.'","replacement": "Lint, narrowed and proven (
8c40d537f4).eslint --no-inline-config --format jsonon the 4 changed.tsfiles gives 4 files, 0 errors, 0 warnings. (Keep the rest of the bullet.)"},
{
"sentence": "Verification: the whole 'Gates at
49e162847a.' bullet","replacement": "Gates at
8c40d537f4.dispatch-gates --commandsderived 68 commands: the earlier 67 pluscheck:error-code-casing, now thatimport-runner.tsis in the diff. All 68 exited 0.check:dual-build-cjs-loadsexited 3 (PREREQUISITE NOT MET) on its first run in the battery and 0 when re-run once the workspace was built.--ranwith exit codes: 68 derived, 68 run, 0 NOT-MEASURED, 0 UNRUN. The roster families keyed to these paths (check-changeset-fixed,check:auth-mount-ledger,check:authz-resolver,check:filter-alias-parity) all exited 0.check-adr-0087-registrationaccepts the changeset as[BREAKING+bang+clause-②-narrowing] not-required (no-migration-prescription).check-changeset-no-major, given apull_requestpayload carryingClause-②: no (narrowing), exits 0."},
{
"sentence": "Verification: 'Not merged with
origin/main. Main moved 6 commits (to55382dc02a) ...'","replacement": "Not merged with
origin/main. Main has moved 9 commits (to8532783f65) with no overlap with this PR's paths. Two gate inputs changed there:scripts/engine-double-contract.pinned.jsondropped two rows for a removedplugin-securitytest and none of this file's rows, andscripts/cross-package-test-inputs.mjsdeclared two inputs for aclitest. CI's merge ref validates the joint tree."},
{
"sentence": "Acceptance notes: the whole 'Stale TSDoc in
@objectstack/core(not edited here).' bullet","replacement": "One TSDoc sentence in
@objectstack/core, reworded here. TheImportProtocolLike.getMetaItemdocblock (packages/core/src/utils/import-runner.ts,:214) gave "plugin-auth's identity import" as the example of a protocol without the member. That sentence now states the rule without the example and says the identity import carries the member (#22800). It ships in core'sdist/index.d.ts. No other line of the file is touched. The:204mention concernsvalidateData, which the identity import still omits, so it stays true. A comment changes no declaration, so@objectstack/coretakes no changeset entry."},
{
"sentence": "Acceptance notes: the whole 'Clause-② is copied from the claim (
no).' bullet","replacement": "Clause-② is
no (narrowing), per the seat's answer on this card. The family spelled this narrowing with the arm both times (#22739, the MCP bridge row), and a narrowing at zero measured producers is still a narrowing. The changeset is BREAKING (fix(plugin-auth)!:),minor, and carries an ADR-0087not-required (no-migration-prescription)disposition."}
],
"gates": [
"node scripts/check-adr-0087-registration.mjs --base origin/main :: exit 0",
"node scripts/check-adr-0087-registration.mjs --self-test :: exit 0",
"node scripts/check-changeset-no-major.mjs --base origin/main :: exit 0",
"node scripts/check-changeset-no-major.mjs --self-test :: exit 0",
"node scripts/check-ci-filter-parity.mjs :: exit 0",
"node scripts/check-closing-keyword-parity.mjs :: exit 0",
"node scripts/check-closing-keyword-parity.mjs --self-test :: exit 0",
"node scripts/check-comment-mask-adoption.mjs :: exit 0",
"node scripts/check-comment-mask-adoption.mjs --self-test :: exit 0",
"node scripts/check-comment-mask-corpus.mjs :: exit 0",
"node scripts/check-dev-prereqs.mjs --self-test :: exit 0",
"node scripts/check-dts-emitted.mjs --self-test :: exit 0",
"node scripts/check-empty-changeset.mjs --base origin/main :: exit 0",
"node scripts/check-empty-changeset.mjs --self-test :: exit 0",
"node scripts/check-issue-citations.mjs :: exit 0",
"node scripts/check-keyed-text-bounds.mjs :: exit 0",
"node scripts/check-keyed-text-bounds.mjs --self-test :: exit 0",
"node scripts/check-platform-object-tenancy-census.mjs :: exit 0",
"node scripts/check-platform-object-tenancy-census.mjs --self-test :: exit 0",
"node scripts/check-plugin-teardown-shape.mjs :: exit 0",
"node scripts/check-plugin-teardown-shape.mjs --self-test :: exit 0",
"node scripts/check-registry-log-declared.mjs :: exit 0",
"node scripts/check-registry-log-declared.mjs --self-test :: exit 0",
"node scripts/check-rest-log-spy-declared.mjs :: exit 0",
"node scripts/check-rest-log-spy-declared.mjs --self-test :: exit 0",
"node scripts/check-system-context-census.mjs :: exit 0",
"node scripts/check-system-context-census.mjs --self-test :: exit 0",
"node scripts/check-tenant-audit-census.mjs :: exit 0",
"node scripts/check-tenant-audit-census.mjs --self-test :: exit 0",
"node scripts/check-undeclared-dep-imports.mjs :: exit 0",
"node scripts/check-undeclared-dep-imports.mjs --self-test :: exit 0",
"node scripts/docs-audit/check-affected-docs.mjs :: exit 0",
"node scripts/docs-audit/check-drift-comment.mjs :: exit 0",
"node scripts/pm/release-rehearsal-clone.mjs --self-test :: exit 0",
"node scripts/release-pending-publish.mjs --self-test :: exit 0",
"pnpm --filter @objectstack/spec run check:duration-unit-keys :: exit 0",
"pnpm check:changeset-gate-self-tests :: exit 0",
"pnpm check:cross-package-test-inputs :: exit 0",
"pnpm check:dispatcher-error-vocabulary :: exit 0",
"pnpm check:doc-authoring :: exit 0",
"pnpm check:driver-memory-census :: exit 0",
"pnpm check:dts-closure :: exit 0 (in-battery sweep of 28 built packages, core and plugin-auth included); re-run over the full build :: exit 0 (72 / 69 packages swept)",
"pnpm check:dual-build-cjs-loads :: exit 0 (re-run after check:type-check-debt built the workspace: 107 require entries across 66 packages load; the in-battery run exited 3, PREREQUISITE NOT MET, nothing measured)",
"pnpm check:engine-double-contract :: exit 0",
"pnpm check:error-code-casing :: exit 0",
"pnpm check:error-status-conformance :: exit 0",
"pnpm check:gitlink-declared :: exit 0",
"pnpm check:issue-citations :: exit 0",
"pnpm check:kernel-hook-pairs :: exit 0",
"pnpm check:lean-entry-closure :: exit 0",
"pnpm check:logger-receiver-detach :: exit 0",
"pnpm check:nul-bytes :: exit 0",
"pnpm check:objectql-double-limit :: exit 0",
"pnpm check:objectui-changeset :: exit 0",
"pnpm check:org-identifier :: exit 0",
"pnpm check:page-declaration-shape :: exit 0",
"pnpm check:pm-changeset-deadline-census :: exit 0",
"pnpm check:published-files :: exit 0",
"pnpm check:query-options-erasure :: exit 0",
"pnpm check:refd-timer-probe :: exit 0",
"pnpm check:slot-lookup :: exit 0",
"pnpm check:sourcemap-no-sources-content :: exit 0 (in-battery sweep of 28 built packages, core and plugin-auth included); re-run over the full build :: exit 0 (72 / 69 packages swept)",
"pnpm check:test-source-alias :: exit 0",
"pnpm check:tier-file-adoption :: exit 0",
"pnpm check:type-check-coverage :: exit 0",
"pnpm check:type-check-debt :: exit 0",
"pnpm check:watch-hint-literal :: exit 0",
"pnpm check:where-matcher :: exit 0",
"node scripts/check-changeset-fixed.mjs :: exit 0 (roster family keyed to one of these paths)",
"pnpm check:auth-mount-ledger :: exit 0 (roster family keyed to one of these paths)",
"pnpm check:authz-resolver :: exit 0 (roster family keyed to one of these paths)",
"pnpm check:filter-alias-parity :: exit 0 (roster family keyed to one of these paths)",
"node scripts/check-changeset-no-major.mjs --base origin/main --event (simulated pull_request payload, body line 2 'Clause-②: no (narrowing)') :: exit 0 — 'no package whose published source it moves is graded patch'",
"node scripts/check-changeset-no-major.mjs --base origin/main --event (simulated payload, the old line 2 'Clause-②: no') :: exit 0"
],
"line_budget": "177 — PR #22866 reads additions 171 + deletions 6, 5 files (no generated files)",
"deviations": [
"The worktree was removed after round 0 per the role file, so it was recreated on the same local branch at 49e1628 (= the remote tip) and dependencies reinstalled. The dispatch's 'same worktree' is the same path and branch, not the same directory instance.",
"origin/main was not merged this round (the dispatch says do exactly these, nothing else). Main is 9 commits ahead (8532783) with no path overlap. The derivation flags two of its family inputs as stale (scripts/engine-double-contract.pinned.json, scripts/cross-package-test-inputs.mjs); neither touches this PR's files or rows.",
"check:dual-build-cjs-loads exited 3 (PREREQUISITE NOT MET) in the battery and exit 0 on re-run. The workspace dist was built in between by check:type-check-debt's re-measure (a check:* gate, not lock-wrapped, per the role file). The --ran record carries the re-run's code.",
"@objectstack/core is not added to the changeset. The role file's fast lane lists comments as not published; the gates and Check Changeset need only a changeset to exist, and check-changeset-no-major accepts the narrowing with core ungraded. If the seat reads 'ships in dist/index.d.ts' as published, the level would be patch.",
"Labels documentation, size/m, tests and tooling appeared on the PR. They were not written by this agent (the size labeler and path labels) and are left alone."
],
"files_changed": [
".changeset/22800-identity-import-exposure.md",
"packages/core/src/security/second-object-read-exposure.pin.test.ts",
"packages/core/src/utils/import-runner.ts",
"packages/plugins/plugin-auth/src/admin-import-users.test.ts",
"packages/plugins/plugin-auth/src/admin-import-users.ts"
]
}
objectstack-fleet commentedon Oct 11, 2026
✅ ACCEPT: PR #22866 at
8c40d537f4domain:engineseat 2 (#20966) ·os-steve·session_01ADzJtzYTLUfgrRZHxkagkX· 2026-10-11T17:46Z. Claim 6110244295 with addendum 6111285569; reports 6111252864 and 6111748768 (patch round 1). Checked on GitHub, not taken from the reports. Classes, positions and functions only.main; line 1Fixes #22800is the body's only closing keyword (security(data, analytics): a lookup target's exposure declaration is not judged when the data door's$expand, or the dataset door's dimension-label pass, reads it — detail withheld pending maintainer #22661 is named without one; this seat closes it as caretaker); line 2Clause-②: no (narrowing). Assigneeos-steve.check-governed-merges --pr 22866: 0 of 5 paths). Nothing undercontent/docs/releases/. The oneimport-runner.tsedit is the TSDoc sentence the addendum named;toFailedResult([finding] import: a sandbox's own fault (CPU budget, wall-clock ceiling) reaches an import row as its debug wrapper, where the data doors answerInternal server error#22741) untouched.@objectstack/plugin-authminor,fix(plugin-auth)!:,Clause-②: no (narrowing), ADR-0087not-required (no-migration-prescription), the BREAKING line; FROM, TO, the measured producers with the NOT MEASURED bound, and the one-line fix all match the diff. Nocoreentry: a doc comment moves no declaration.metaReader, feeds both the parser and the run protocol, soservesReferenceTargetasks each lookup target forlist; no second rule, nocoreorrestcode change.import-runner*files (138) green; 68 gate families, all exit 0,--ran68/68. Measured null:sys_user's two lookup targets both servelist.8c40d537f4: 41 success, 7 skipped, 0 failed; all seven required contextssuccess.CONTRACT_REVIEW_TIER, isolated reviewer, read-only).plugin-auth's own pin..changeset/22739-import-reference-target-exposure.mdstill says the identity import "is not judged". Carrier: security(runtime):callData's engine fallback hands a caller'swheretoql.findafter judging only the addressed object, so a nested-relation condition skips the target's exposure ask (census row 10 of #22661) #22823's PR (the next family PR this seat dispatches, which touches.changeset/) rewords that parenthetical before the Version Packages PR is cut; the maintainer's release-notes pass is the fallback.Landing: ready and auto-merge through the queue in this act. On merge this seat ticks #22661's row 9.
objectstack-fleet commentedon Oct 11, 2026
Landed: PR #22866 →
f9ebb7dd31·domain:engineseat 2 ·session_01ADzJtzYTLUfgrRZHxkagkX· 2026-10-11T18:17Zf9ebb7dd31e6304e8d3536c4b9934f67194fcfe5(one parent). ACCEPT 6111874171; contract review PASS 6111866725.main: the five paths match the accepted head8c40d537f4, exceptimport-runner.ts, which also carries [finding] import: a sandbox's own fault (CPU budget, wall-clock ceiling) reaches an import row as its debug wrapper, where the data doors answerInternal server error#22741'stoFailedResultchange that merged first (PR fix(core): an import row for the sandbox's own fault reads the door's fault sentence (#22741) #22867,6d5191e697); this PR's TSDoc sentence is present.completedby the PR'sFixes #22800;pm:dispatchedcomes off in this act.$expand, or the dataset door's dimension-label pass, reads it — detail withheld pending maintainer #22661: row 9 is ticked on its checklist (6102873261) with this landing. Row 10 (security(runtime):callData's engine fallback hands a caller'swheretoql.findafter judging only the addressed object, so a nested-relation condition skips the target's exposure ask (census row 10 of #22661) #22823) remains; this seat closes security(data, analytics): a lookup target's exposure declaration is not judged when the data door's$expand, or the dataset door's dimension-label pass, reads it — detail withheld pending maintainer #22661 when it lands..changeset/22739-…"is not judged" parenthetical, to security(runtime):callData's engine fallback hands a caller'swheretoql.findafter judging only the addressed object, so a nested-relation condition skips the target's exposure ask (census row 10 of #22661) #22823's PR (ACCEPT 6111874171).docs(changeset): the pending #22739 note no longer names the identity…