Files
skills/docs/networking/drive/drive-sharing/src/sharing/grants/grants.md
2026-05-29 08:20:56 +01:00

4.2 KiB

sharing/grants/grants.ts

(no submodules)

Purpose

GrantManager is the deterministic in-memory grant store. It holds Grant records keyed by GrantId and answers permission queries via check(grantId, userId, action). The check is exhaustive: existence, non-revocation, non-expiry, grantee identity, and permission inclusion are all verified, in that order, with the first failure short-circuiting the result. Every check increments share.grant.check_total; denials additionally increment share.grant.denied_total with a reason attribute so dashboards can split denials by cause.

Features

  • Synchronous check — sub-millisecond by design (no IO, no crypto).
  • Injectable Clock so expiry tests are deterministic.
  • EventBusLike integration: grant:created and grant:revoked events carry full context but never key material.
  • Hard cap on active grants (GRANT_MANAGER_MAX_ENTRIES = 10_000_000) per the spec scalability table.
  • Filtered list(query) supporting granteeId, partitionId, and active filters.
  • revoke is idempotent — already-revoked / missing grants are no-ops.

Usage: Basic Example

import { GrantManager } from "@suaveplan/drive-sharing/sharing/grants";
import { partitionId, userId } from "@suaveplan/drive-sharing/types/common";

const grants = new GrantManager();
const grant = grants.create({
    grantorId: userId("alice"),
    granteeId: userId("bob"),
    partitionId: partitionId("p"),
    permissions: ["read"],
});
grants.check(grant.id, userId("bob"), "read"); // { allowed: true }

Usage: Advanced Example

import { GrantManager } from "@suaveplan/drive-sharing/sharing/grants";
import { partitionId, userId } from "@suaveplan/drive-sharing/types/common";

let now = 0;
const events = new Map<string, unknown[]>();
const grants = new GrantManager({
    clock: () => now,
    events: {
        on: () => () => {},
        emit: (k, p) => {
            const arr = events.get(k) ?? [];
            arr.push(p);
            events.set(k, arr);
        },
    },
});

const g = grants.create({
    grantorId: userId("alice"),
    granteeId: userId("bob"),
    partitionId: partitionId("p"),
    permissions: ["read"],
    expiresAt: 1000,
});

now = 999;
console.log(grants.check(g.id, userId("bob"), "read")); // allowed
now = 1001;
console.log(grants.check(g.id, userId("bob"), "read")); // denied: expired

grants.revoke(g.id);
console.log(grants.list({ active: true })); // []

API Reference

class GrantManager

  • constructor(config?: GrantManagerConfig)
  • create(options: CreateGrantOptions): Grant
  • check(id, userId, action): GrantCheckResult
  • revoke(id): void
  • list(query?): Grant[]
  • get(id): Grant | undefined

interface CreateGrantOptions

{ grantorId, granteeId, partitionId, permissions, expiresAt? }.

interface Grant

{ id, grantorId, granteeId, partitionId, permissions, createdAt, expiresAt?, revoked }.

interface GrantCheckResult

{ allowed: boolean; reason?: string } — reason is one of grant_not_found, revoked, expired, grantee_mismatch, permission_missing.

interface GrantQuery

{ granteeId?, partitionId?, active? }.

interface GrantManagerEvents

  • grant:created — full grant
  • grant:revoked — { id, partitionId, userId }

Implementation Notes

  • The denial-reason strings are stable contract — dashboards may key on them. Renaming a reason is a major-version bump.
  • revoke is intentionally idempotent so a duplicate "revoke this grant" message from a retry-loop does not trigger a second grant:revoked event.
  • list({ active: true }) reads the clock once per grant; for very large grant stores callers should prefer list({ partitionId, active: true }) so the filter narrows before the active-check fires.
  • The Zod schema (CreateGrantOptionsSchema) enforces non-empty permissions. An attacker who calls create with permissions: [] is rejected at the boundary, never silently inserted.
  • check runs the action verb through permissions.includes(action as SharePermission) — the cast is safe because every PolicyAction is also a valid SharePermission in the canonical list.