Repository navigation
feat(spec): ExplainAccessRequest declares recordIds, the batch selector the explain wire schema already carries - #29470
Conversation
…s batch selector The service contract the REST explain route calls now declares the optional batch selector the wire schema (ExplainRequestSchema) and the reference security service already honour. The member's type is the wire schema's own, and a type-level pin holds the two equal. Claude-Session: https://claude.ai/code/session_01ADHkmQS1wrCba8FkVowBLy Co-authored-by: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01ADHkmQS1wrCba8FkVowBLy Co-authored-by: Claude <noreply@anthropic.com>
📓 Docs Drift CheckThis PR changes 1 package(s): ⛔ 1 release-owned page(s) name something this change touched. These are read-only:
What this run could not see
Coarse fallback — 168 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): Which tree this was computed onThis run read A worktree cut from an older # while this PR is open — GitHub drops the merge commit once it closes
git fetch origin 3d62871e06a879c774bb36baa255a70d7180067c && git checkout 3d62871e06a879c774bb36baa255a70d7180067c
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin 6af3d295757186a6e5299b4004d1810a9d144765 57913a379525abcbfb63ab4fba331fda9b2ce136 && git checkout -B drift-repro 6af3d295757186a6e5299b4004d1810a9d144765 && git merge --no-ff 57913a379525abcbfb63ab4fba331fda9b2ce136
node scripts/docs-audit/affected-docs.mjs --json 6af3d295757186a6e5299b4004d1810a9d144765
|
|
domain:spec#4 · os-steve · session_01ADHkmQNap7ryQcyQXocWct Contract-review-claim: session_01ADHkmQNap7ryQcyQXocWct · head 29000bb · 2026-11-02T15:57Z The lane seat reviews this head itself at |
|
domain:spec#4 · os-steve · session_01ADHkmQNap7ryQcyQXocWct Contract reviewServed-tier: Inputs read: card #29297 (body, comments 6576596125, 6580454301, 6581669782), PR #29470 body and file list, ① Derived judgments
② Semver level
③ Boundary flags
④ Design judgmentThe hits file holds against the ruling it implements (card #29297, the follow-up the #29208 landing record owed) and against its first consumers (the REST explain route and the reference Must-fix: none. Should-fix:
Implemented-by: VERDICT: PASS |
…rdIds The contract lets an implementation ignore recordIds, so the refusal is not a property of the contract: the wire schema refuses it at the REST explain route and the reference security service refuses it. A direct caller of explain sends one spelling. Claude-Session: https://claude.ai/code/session_01ADHkmQS1wrCba8FkVowBLy Co-authored-by: Claude <noreply@anthropic.com>
|
domain:spec#4 · os-steve · session_01ADHkmQNap7ryQcyQXocWct Contract-review-claim: session_01ADHkmQNap7ryQcyQXocWct · head 57913a3 · 2026-11-02T16:31Z Delta review of the should-fix commit (record 6581862373, should-fix 1) by the same reviewer. |
|
domain:spec#4 · os-steve · session_01ADHkmQNap7ryQcyQXocWct Contract reviewServed-tier: Delta record: one commit on 29000bb ( ① Derived judgments
② Semver levelUnchanged and still right: ③ Boundary flags
④ Design judgmentThe hits file still holds against the card's acceptance and against its two consumers; the delta makes the one doc claim that over-promised precise, and introduces no new claim the cited code does not back. Tried to break it on: whether "at the REST explain route" misattributes the refusal (it does not — the route runs the schema, the schema carries the refine, L236/L313–323); whether the sentence now under-promises for the reference implementation (it does not — L142–145 throws before any evaluation); whether a Must-fix: none. Should-fix: none on this PR (should-fix 2 of the prior record remains a plugin-security follow-up card). Implemented-by: VERDICT: PASS |
|
domain:spec#4 · os-steve · session_01ADHkmQNap7ryQcyQXocWct Head-sha: 维护者速读
席位意见:可落地。契约复审在 29000bb 判 PASS(6581862373),席位要的一句注释修正已在现 head 由同一复审者 delta 复审 PASS(本 PR 上一条记录)。CI 全绿。 席位核对 — ACCEPT ·
|
Requested by Steve · project thread
Fixes #29297
Clause-②: yes (widening)
Metadata-protocol: none
Skills-docs: none — no page under
skills/orcontent/docs/spells the members of theExplainAccessRequestcontract type;content/docs/permissions/explain.mdxshows the kernel call withobject/operation/userIdonly, and the batch form is already a wire-schema fact ofExplainRequestSchema(grepexplainandrecordIdsin both trees, on 29000bb).维护者速读
security服务契约的 explain 请求类型ExplainAccessRequest补上可选的recordIds(批量记录 id),类型直接取自线上 schemaExplainRequestSchema的同名成员。packages/spec/src/contracts/security-service.ts(新成员与文档注释)、packages/spec/src/contracts/security-service.test.ts(类型钉)。pnpm --filter @objectstack/spec check:test-typecheck,预期 OK;把新成员改成readonly string[]再跑,预期该测试文件报 3 个类型错误。What changes
ExplainAccessRequest(packages/spec/src/contracts/security-service.ts) gainsrecordIds?: ExplainRequest['recordIds'], with a doc comment: mutually exclusive withrecordId; at least one and at mostEXPLAIN_BATCH_MAX_RECORD_IDSids (linked, not spelled); answered byExplainDecision.recordsin the same order; an implementation may ignore it and answer only the object-level decision, and the caller then asks each id singularly, as the REST explain route does.ExplainRequest=z.input<typeof ExplainRequestSchema>), not a hand-writtenstring[]. Only this member is derived: tying the whole interface to the schema would changeoperation(the contract'sExplainOperation | stringagainst the schema's closed enum) and replace the contract's member docs.recordsof the right length (packages/rest/src/security-routes.tsL256–299, the call at L279, the check at L280, the per-id loop at L289–296); the reference service reads the member and refuses it besiderecordId(packages/plugins/plugin-security/src/security-plugin-explain-access.tsL128, L142–145, L526–527).check:api-surfaceagrees with no regeneration (it records export names; no export was added or removed)..changeset/29297-explain-access-request-record-ids.md,@objectstack/specpatch.Tests
packages/spec/src/contracts/security-service.test.ts(no existing test was edited):ExplainRecordIdsIsOptional: a type alias that reds if the member becomes required.ExplainRecordIdsMatchesWire: a type alias holdingExplainAccessRequest['recordIds']equal toz.input<typeof ExplainRequestSchema>['recordIds'].explain accepts a batch request…: the same equality throughexpectTypeOf, a batch literal reaching the service, and a@ts-expect-errorthat a bare string is not a batch.check:test-typecheck(tsconfig.test.json) and is not intest-typecheck-debt.json(0 hits), so the aliases are live checks.Ablation (type pin)
node scripts/ablation-replace.mjs --file packages/spec/src/contracts/security-service.ts --anchor "recordIds?: ExplainRequest['recordIds'];" --replacement 'recordIds?: readonly string[];' -- pnpm --filter @objectstack/spec check:test-typecheckea8eb5b53fb0→f7a8cee931f6src/contracts/security-service.test.ts: 3 type error(s) in a file the ledger does not cover· command exited 1ea8eb5b53fb0),git diff HEADemptyPackage suites on 29000bb (os-verify-lock)
--filter '@objectstack/plugin-security^...' --filter '@objectstack/rest^...': VERDICT command-exit 0@objectstack/spectest: Test Files 791 passed (791) · Tests 23470 passed, 1 todo · exit 0@objectstack/spectypecheck (tsc + scripts + test layer): exit 0 ·check:test-typecheck: OK — 52 file(s) / 240 error(s) / 131 pinned signature(s) held@objectstack/plugin-securitytest: Test Files 240 passed (240) · Tests 5175 passed, 45 skipped · exit 0@objectstack/plugin-securitytypecheck: exit 0@objectstack/resttest: Test Files 371 passed (371) · Tests 7032 passed, 332 skipped · exit 0@objectstack/resttypecheck: exit 0--no-inline-configon both changed files: exit 0Acceptance notes
@objectstack/plugin-sharing,@objectstack/service-storageand@objectstack/plugin-auditalso importExplainAccessRequest; the change only adds an optional member, so their code and test doubles stay assignable. Not run locally; CI's Test Core and TypeScript Type Check cover them.Verification
dispatch-gates.mjs --commandsderived 79;--ranreconciles 79 derived, 78 run, 1 NOT-MEASURED, 0 UNRUN. 78 exit 0.check-plugin-teardown-shape.mjs --self-testfirst exited 3 on this shallow clone (its positive-control fixture is pinned to a commit the clone lacked); after fetching that one commit it exits 0 (48 cases pass).pnpm check:dual-build-cjs-loadsexits 3,PREREQUISITE NOT MET(48 packages have nodist/; it needs a whole-repo build). NOT MEASURED locally; this diff changes one declared type member and no emitted JavaScript. CI's Lint & Repo Gates runs it.check-changeset-no-major.mjs --base origin/main --eventagainst this body, offline: exit 0,LEVEL AXIS … clause-② yes (widening) and metadata-protocol none … a non-protocol change at patch.Generated by Claude Code