feat(spec): SharingRuleEvaluationResult declares grantsRefused?: number — the optional seventh key the evaluate route already answers - #15714
Conversation
…er, the optional seventh key the evaluate route already answers `POST /api/v1/sharing/rules/:idOrName/evaluate` passes the service's return value through unfiltered, and plugin-sharing counts refused grants on its own subtype, so the wire carried `grantsRefused` while the declared client type could not name it. The key is OPTIONAL: required would break every other ISharingRuleService implementer; optional composes with the plugin-local covariant narrowing. Absent means "this implementation does not report refusals", never 0 — the JSDoc says so and the contracts pin test reads it. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01M59rPZZFzqhfMUPFqqZTkf
📓 Docs Drift Check4 anchor(s) derived from 1 changed package(s); no hand-written page names any of them, so this run has nothing to list — not a clean bill of health. This check sees only pages that NAME a derived anchor: one that documents this change in prose, or enumerates it in an authoring dialect, names none and stays invisible to it on every run. What this run could not see
Coarse fallback — 129 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 1bb481a41ed52ac4c7dad9c0f0cf1426588e3190 && git checkout 1bb481a41ed52ac4c7dad9c0f0cf1426588e3190
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin 8e8860ed8144feb6e5151c5a95655cb5bcb40f1e 08c6eecbb55e526b8f2080b125c5aa140cd715dc && git checkout -B drift-repro 8e8860ed8144feb6e5151c5a95655cb5bcb40f1e && git merge --no-ff 08c6eecbb55e526b8f2080b125c5aa140cd715dc
node scripts/docs-audit/affected-docs.mjs --json 8e8860ed8144feb6e5151c5a95655cb5bcb40f1e |
Fixes #14969
SharingRuleEvaluationResult(packages/spec/src/contracts/sharing-service.ts) gains its seventh key,grantsRefused?: number— optional, not required — with a JSDoc that carries the absent-is-not-zero rule. The wire already answered the key:POST /api/v1/sharing/rules/:idOrName/evaluatepasses the service's return value tores.jsonunfiltered (rest-server.ts:11335-11336), the route is ledgeredsdk/shares.rules.evaluate(rest-route-ledger.ts:390), and the client typesevaluateas aPromiseofSharingRuleEvaluationResult(client/src/index.ts:4912,4917) — so the declared SDK type lagged the route by exactly this key. The client gains it through its@objectstack/spec/contractsimport with no edit.What changed
packages/spec/src/contracts/sharing-service.ts— one optional key on the interface, its JSDoc, and a two-sentence addition to the interface's own doc block.packages/spec/src/contracts/sharing-rule-evaluation-result-grants-refused.pin.test.ts— the contracts-side pin (type-level, compiled bycheck:test-typecheckundertsconfig.test.json; membership confirmed withtsc --listFiles, 0 errors in the file): the six counts stay the exact required set,grantsRefusedis the ONLY optional key and reads asnumber | undefined, a string is refused (@ts-expect-error), a subtype that REQUIRES the key is still a legalISharingRuleService['evaluateRule']return while a six-key implementation keeps compiling, and the JSDoc above the key is read and required to state the rule below..changeset/sharing-rule-evaluation-result-grants-refused.md—@objectstack/specminor (derivation in the changeset: additive public API on a published contract interface; not major because the key is optional so every implementer and consumer keeps compiling).Not touched, per dispatch:
packages/plugins/plugin-sharing/**,packages/client/src/index.ts,packages/spec/src/contracts/job-service.ts,packages/spec/src/migrations/registry.ts(no ADR-0087 entry — nothing retired). Generated followers: none —check:generatedon the rebuilt tree reports all 15 artifacts up to date (api-surface/contracts.jsonandexport-origins/contracts.jsonlist export NAMES, so a new key on an existing interface leaves them byte-identical; nocontent/docs/references/**page renders this interface).The JSDoc, verbatim
On the interface:
On the key:
Reverse verification (H3) — read from the REBUILT
dist/contracts/index.d.tsScratch consumer outside the tree,
paths-mapped topackages/spec/dist/contracts/index.d.ts(grantsRefused?: numberat its line 7243),tsc --strict --noEmit, exit captured before any pipe:grantsRefused)grantsRefused: 2grantsRefused: 'x'TS2322: Type 'string' is not assignable to type 'number'The error CODE is the proof the reading came from the rebuilt declarations and not a cache: against a
.d.tswithout the key the same literal answersTS2353(excess property), notTS2322.Gate derivation (
node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack, no paths, derived at08c6eecbb)Everything below ran at
08c6eecbb(the final commit; the tree has not changed since), exit codes captured by redirect before any pipe; verdict lines quoted from the gates themselves.pnpm --filter @objectstack/spec build(throughos-verify-lock.sh):VERDICT command-exit 0, twice — once after the contract edit, once after the pin test was added (the firstcheck:api-surfacerun refused with "dist is OLDER than src", a prerequisite, not a measurement).check:generated(spec, on the rebuilt tree):✓ All 15 generated artifacts are up to date.— coverscheck:api-surface,check:authorable-surface,check:export-origins,check:declaration-map,check:docs,check:strictness-ledger,check:liveness,check:skill-refs,check:skill-docs,check:test-typecheck,check:migration-registry,check:spec-changes,check:upgrade-guide,check:meta-url-spelling,check:react-blocks, each✓.check:dual-source-exports"no new dual-source exports: 5060 names across 17 entry points";check:empty-state"✓ all classified";check:entry-nameability"439 call probes across 17 public entries, 0 new unnameable structural mentions";check:error-code-provenance"OK — every registered-code stamp site is listed under its own owner key or carries a recorded waiver (9 waiver(s), all live)";check:exported-any"no exported type resolves toany: 2457 types + 1521 schemas";check:llms-txt"97 claim(s) re-derived";check:browser-reachable-entries✅;check:variant-docs✓;check:yaml-examples✅ 18 examples.check:nul-bytes,check:cross-package-test-inputs,check:doc-authoring,check:changeset-gate-self-tests,check:spec-parsed-alias,check:test-source-alias,check:objectql-double-limit,check:where-matcher,check:type-check-coverage("OK — 75/79 workspace packages type-checked"),check-keyed-text-bounds,check-comment-mask-adoption,check-closing-keyword-parity,check:published-files,check-undeclared-dep-imports,docs-audit/check-affected-docs,docs-audit/check-drift-comment,check:merge-driver,check:org-identifier,check:page-declaration-shape,check:slot-lookup,check:logger-receiver-detach,check:type-source-resolution,check:doc-formula-expressions(after building@objectstack/formula; its first run was aPREREQUISITE NOT METexit 3, not a measurement).check-empty-changeset"✓ No empty-frontmatter changeset introduced by this diff (1 declaring changeset(s) added)";check-changeset-no-major"✓ This diff introduces nomajorbump";check-adr-0087-registration --base origin/main"✓ this PR adds no declared-breaking changeset (1 non-breaking changeset(s) seen)".check:type-check-debt(PREREQUISITE NOT MET— wants the whole./packages/*closure built),check:dual-build-cjs-loads(same class), the remainingpackages/**catch-all families the derivation lists (check:engine-double-contract,check:driver-memory-census,check:query-options-erasure,check:refd-timer-probe,check:watch-hint-literal,check-platform-object-tenancy-census,check-plugin-teardown-shape,check-registry-log-declared,check-system-context-census,check-reference-carrier-shape,check-ci-filter-parity,check:pm-half-states,check:objectui-changeset,check-dev-prereqs --self-test,release-rehearsal-clone --self-test) — none reads a contracts interface or a.pin.test.ts; declared, not skipped silently.Consumer-package readings (downstream of
@objectstack/spec, closure built:turbo run build32/32 successful through the lock), all at08c6eecbb@objectstack/spectypecheck— exit 0, 0error TS;check:test-typecheck: OK(the new pin test is in the program:--listFilesline 1365, 0 errors); vitestsharing-rule-evaluation-result-grants-refused.pin.test.ts+sharing-service.test.ts:Test Files 2 passed (2),Tests 18 passed (18),VERDICT command-exit 0.@objectstack/plugin-sharingtypecheck— exit 0, 0error TS(itsSharingRuleReconcilePassResult extends SharingRuleEvaluationResult { grantsRefused: number }stays a legal covariant narrowing); vitestsrc/reconcile-refused-grant-continues.test.ts:Test Files 1 passed (1),Tests 10 passed (10),VERDICT command-exit 0.@objectstack/clienttypecheck— exit 0, 0error TS,check:test-typecheck: OK.@objectstack/resttypecheck— exit 0, 0error TS,check:test-typecheck: OK.origin/mainmoved 9 commits past the branch base22a006b26while this ran; the overlap with this diff's surface is empty (other.changeset/*.mdfiles and theautomationapi-surface / export-origins shard, textually disjoint from thecontractsshard) — no merge taken; the queue rebuilds on currentmain.Out of scope
SharingRuleEvaluationResultas six-key / "another lane's to move" once #14969 liftsgrantsRefused?into the spec #15712 (finding, unassigned): once this lands, two comments inpackages/plugins/plugin-sharing/src(index.ts:26-29,sharing-rule-service.ts:94-98) still describe the spec type as six-key and "another lane's to move" — prose drift in the services lane's surface, not touched here. Dedup was a full REST page-through of open issues (627 fetched =open_issues_count) plus the 300 most recently updated closed ones; the only keyword hit was spec: liftgrantsRefused?: number(optional) intoSharingRuleEvaluationResultso the SDK type stops lagging the wire by one key #14969 itself.fieldsharing recipient per record —expandRecipientreads the user field on the matched row, re-materialises on that record's own update,positionstays rule-wide (services half of #14103, ruling B) #15072, spec: adjudicate how a CONSUMER matching another package's error code is recorded in the provenance ledger —plugin-sharingmatchingERR_SYSTEM_WRITE_ORGANIZATION_REQUIREDfits neither existing remedy cleanly #14937, objectql: publish a recognizer forSystemWriteOrganizationRequiredErrorso consumers stop re-spellingERR_SYSTEM_WRITE_ORGANIZATION_REQUIRED—instanceofis measured unsound across the CJS/ESM split #14936 are known related cards and were not re-filed.Contract review
Clause-②
yes(a new key on a published SDK payload type):needs:contract-reviewis hung on this PR and on #14969; this PR stays draft for that review.🤖 Generated with Claude Code
Generated by Claude Code