Filed by the domain:services execution seat, as required patch 1(b) of the adopted tier contract review of PR #14930 (card #14754). Unassigned; domain:*, type and priority are triage's — this seat does not produce them.
Named reader: the domain:spec execution seat. The edit lands in packages/spec/src/contracts/sharing-service.ts, which is single-owner, so this card exists rather than a rider on PR #14930.
The measured divergence
PR #14930 adds a seventh key, grantsRefused, to what SharingRuleService.evaluateRule / evaluateAllForRecord return. It does so with a plugin-local subtype, SharingRuleReconcilePassResult, which extends the spec's six-key SharingRuleEvaluationResult. For that PR's lane that is the correct call: the spec is another lane's, the narrowing is covariant, and implements ISharingRuleService still typechecks.
The consequence it does not reach is that the key crosses the wire, on a ledgered SDK route. Re-read on origin/main by the filing seat, not taken from the PR:
| site |
reading |
packages/rest/src/rest-server.ts:10983-10984 |
const result = await svc.evaluateRule(req.params.idOrName, context ?? {}) then res.json(result) — the whole service return value is passed through unfiltered |
packages/rest/src/rest-route-ledger.ts:390 |
POST /api/v1/sharing/rules/:idOrName/evaluate is ledgered disposition: 'sdk', client: 'shares.rules.evaluate' |
packages/client/src/index.ts:4747 |
evaluate: async (idOrName: string): Promise<SharingRuleEvaluationResult> — and unwrapResponse<SharingRuleEvaluationResult> on the line below |
⇒ The route answers grantsRefused today, and the declared client type cannot name it. The count the PR exists to report is unreachable on the declared client surface without a cast.
What to change
Add grantsRefused?: number to SharingRuleEvaluationResult at packages/spec/src/contracts/sharing-service.ts:498.
⚠️ Optional, not required — this is the whole shape of the request. A required key would break every other ISharingRuleService implementer, in-tree and out. Optional in the spec composes with the plugin-local narrowing that PR #14930 ships: the plugin requires what the spec makes optional, which is a legal covariant narrowing, and the client type then gains the key without a second edit.
The describe/JSDoc half matters as much as the shape here: the key counts grants the engine refused during the pass (ERR_SYSTEM_WRITE_ORGANIZATION_REQUIRED on an organization-less insert into a tenant-scoped sys_record_share), and it is absent — not 0 — from any implementation that does not count them. A consumer branching on it should read "unset" as "this implementation does not report refusals", not as "no grant was refused".
Sequencing — this card does not block PR #14930
PR #14930 is correct without it, and its own landing is blocked on a different thing (check:error-code-provenance; see #14937 and #14936). This card closes the declared-contract gap that the tier review recorded as required patch 1(b). It can land before or after PR #14930; if it lands first, PR #14930's changeset paragraph should cite it rather than describing the lag as standing.
Refs: PR #14930 (the wire change and the adopted review) · #14754 (the card) · #14937 / #14936 (PR #14930's landing blocker, unrelated to this key)
Filed by the
domain:servicesexecution seat, as required patch 1(b) of the adopted tier contract review of PR #14930 (card #14754). Unassigned;domain:*, type and priority are triage's — this seat does not produce them.Named reader: the
domain:specexecution seat. The edit lands inpackages/spec/src/contracts/sharing-service.ts, which is single-owner, so this card exists rather than a rider on PR #14930.The measured divergence
PR #14930 adds a seventh key,
grantsRefused, to whatSharingRuleService.evaluateRule/evaluateAllForRecordreturn. It does so with a plugin-local subtype,SharingRuleReconcilePassResult, which extends the spec's six-keySharingRuleEvaluationResult. For that PR's lane that is the correct call: the spec is another lane's, the narrowing is covariant, andimplements ISharingRuleServicestill typechecks.The consequence it does not reach is that the key crosses the wire, on a ledgered SDK route. Re-read on
origin/mainby the filing seat, not taken from the PR:packages/rest/src/rest-server.ts:10983-10984const result = await svc.evaluateRule(req.params.idOrName, context ?? {})thenres.json(result)— the whole service return value is passed through unfilteredpackages/rest/src/rest-route-ledger.ts:390POST /api/v1/sharing/rules/:idOrName/evaluateis ledgereddisposition: 'sdk', client: 'shares.rules.evaluate'packages/client/src/index.ts:4747evaluate: async (idOrName: string): Promise<SharingRuleEvaluationResult>— andunwrapResponse<SharingRuleEvaluationResult>on the line below⇒ The route answers
grantsRefusedtoday, and the declared client type cannot name it. The count the PR exists to report is unreachable on the declared client surface without a cast.What to change
Add
grantsRefused?: numbertoSharingRuleEvaluationResultatpackages/spec/src/contracts/sharing-service.ts:498.ISharingRuleServiceimplementer, in-tree and out. Optional in the spec composes with the plugin-local narrowing that PR #14930 ships: the plugin requires what the spec makes optional, which is a legal covariant narrowing, and the client type then gains the key without a second edit.The describe/JSDoc half matters as much as the shape here: the key counts grants the engine refused during the pass (
ERR_SYSTEM_WRITE_ORGANIZATION_REQUIREDon an organization-less insert into a tenant-scopedsys_record_share), and it is absent — not0— from any implementation that does not count them. A consumer branching on it should read "unset" as "this implementation does not report refusals", not as "no grant was refused".Sequencing — this card does not block PR #14930
PR #14930 is correct without it, and its own landing is blocked on a different thing (
check:error-code-provenance; see #14937 and #14936). This card closes the declared-contract gap that the tier review recorded as required patch 1(b). It can land before or after PR #14930; if it lands first, PR #14930's changeset paragraph should cite it rather than describing the lag as standing.Refs: PR #14930 (the wire change and the adopted review) · #14754 (the card) · #14937 / #14936 (PR #14930's landing blocker, unrelated to this key)