# `partitions/partition-manager/partition-manager.ts` _(no submodules)_ ## Purpose `PartitionManager` orchestrates the lifecycle of partition records. It accepts a `PartitionStore` for persistence and a `KeyRing` for per-user key material, validates every mutation against the active policy via `validateAction`, and emits `partition:*` and `member:*` events on a caller-supplied `EventBusLike`. The manager is the single throat through which all partition state changes pass; downstream modules (grants, tokens, transfer) treat the partition as immutable input. ## Features - Five mutating operations (`create`, `delete`, `addMember`, `removeMember`, `update`) plus three read-only operations (`get`, `list`, `getKeyRing`). - Zod-validated inputs at every public entrypoint via `CreatePartitionOptionsSchema` / `AddMemberOptionsSchema` / `UpdatePartitionOptionsSchema`. - Injectable `Clock` for deterministic timestamping and an injectable id generator for deterministic IDs in tests. - `EventBusLike` carries `partition:created`, `partition:deleted`, `partition:updated`, `member:added`, `member:removed`. No key material in event payloads. - OTel spans on every mutating operation; counter `share.partition.member_total` per partition; counter `share.partition.create_total`. - Graceful-degradation read-only toggle (`setReadOnly`) — mutations throw `SHARE_POLICY_VIOLATION` while the flag is true. ## Usage: Basic Example ```ts import { PartitionManager } from "@suaveplan/drive-sharing/partitions/partition-manager"; import { InMemoryPartitionStore } from "@suaveplan/drive-sharing/partitions/partition-store"; import { KeyRing } from "@suaveplan/drive-sharing/encryption/key-ring"; import { userId } from "@suaveplan/drive-sharing/types/common"; const manager = new PartitionManager({ store: new InMemoryPartitionStore(), keyRing: new KeyRing(), }); const partition = await manager.create({ name: "research", type: "shared", ownerId: userId("alice"), }); ``` ## Usage: Advanced Example ```ts import { PartitionManager } from "@suaveplan/drive-sharing/partitions/partition-manager"; import { InMemoryPartitionStore } from "@suaveplan/drive-sharing/partitions/partition-store"; import { KeyRing } from "@suaveplan/drive-sharing/encryption/key-ring"; import { partitionId, userId } from "@suaveplan/drive-sharing/types/common"; let now = 0; let counter = 0; const events = new Map(); const bus = { on: () => () => {}, emit: (k: string, p: unknown) => { const arr = events.get(k) ?? []; arr.push(p); events.set(k, arr); }, }; const manager = new PartitionManager({ store: new InMemoryPartitionStore(), keyRing: new KeyRing(), clock: () => (now += 1000), idGenerator: () => `id-${(counter += 1)}`, events: bus, }); const p = await manager.create({ name: "vault", type: "encrypted", ownerId: userId("alice"), policy: { maxMembers: 3 }, }); await manager.addMember(p.id, userId("bob"), { permissions: ["read"] }); manager.setReadOnly(true); // further mutations throw SHARE_POLICY_VIOLATION ``` ## API Reference ### `class PartitionManager` - `constructor(config: PartitionManagerConfig)` - `create(options): Promise` - `get(id): Promise` — throws `SHARE_PARTITION_NOT_FOUND`. - `list(): Promise` - `delete(id): Promise` - `addMember(id, userId, options?): Promise` — throws `SHARE_POLICY_VIOLATION` when policy denies. - `removeMember(id, userId): Promise` — idempotent. - `update(id, options): Promise` - `setReadOnly(value): void` / `isReadOnly(): boolean` - `getKeyRing(): KeyRing` ### `interface CreatePartitionOptions` `{ name, type, ownerId, policy? }`. ### `interface AddMemberOptions` `{ permissions?, wrappedKey? }`. Default permissions are `["read"]`. ### `interface UpdatePartitionOptions` `{ name?, policy? }`. ### `interface PartitionManagerEvents` `partition:created` / `partition:deleted` / `partition:updated` / `member:added` / `member:removed`. No key material. ### `interface PartitionManagerConfig` `{ store, keyRing, policy?, clock?, events?, idGenerator? }`. ## Implementation Notes - The default policy is computed once in the constructor (`createPolicy(config.policy ?? {})`). Per-partition policies are layered on top via `mergePolicies` in `create` and `update`. - `addMember` validates the partition's policy against the action `share` (the canonical "I am modifying membership" verb); when the policy denies, the call throws `SHARE_POLICY_VIOLATION`. Importantly, the check is performed against the partition's CURRENT membership — adding the Nth member when `maxMembers` is N triggers the denial branch. - `removeMember` is intentionally idempotent. Calling it twice with the same userId returns the same partition the second time and does NOT emit `member:removed`. This matches the spec scenario "RemoveMember restores count". - `setReadOnly(true)` is the spec's documented graceful-degradation hook; the partition store's health check is the trigger but the toggle is exposed publicly so consumers can wire any policy they like. - Event emission is fire-and-forget — the `EventBusLike.emit` contract is synchronous and does not surface listener errors. Asynchronous side effects belong to the listener, not the manager.