Skip to content

spec: lift grantsRefused?: number (optional) into SharingRuleEvaluationResult so the SDK type stops lagging the wire by one key #14969

Description

@os-sales

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)

Activity

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions