trellis
Version:
Agentic State Engine — event-sourced causal graph with branching, decision traces, and realtime sync for AI-native applications
138 lines • 6.9 kB
TypeScript
/**
* Capability Model — Zone-scoped read/write authorization
*
* ADR 0022. Resolves the §5 "room = boundary" decision and the §2a refs
* decision under one principle: partition, don't filter.
*
* The kernel understands only four capability levels and opaque `zoneId`s.
* Zone *names* ("Workshop", "Lab", …) are a mutable `alias` fact and never
* appear in kernel logic, so renaming a zone never requires a refactor.
*
* Grants are explicit (principal, zoneId, level) facts. Absence = `None`
* (deny-by-default). Revocation retracts the grant fact; `None` is never
* persisted. Nesting via `parentZone` yields a capability closure.
*
* **Writes mint ops; reads run against the materialized store.** The store is
* derived — rebuilt by op replay on boot — so writing to it directly would give
* grants that vanish on restart, never replicate to peers, are not hash-covered
* and carry no provenance. An authorization change a peer cannot see is not a
* boundary, so every mutation here goes through the journal as a first-class,
* nameable op (`vcs:zoneDefine` / `zoneRename` / `grantSet` / `grantRetract`).
*
* **Authority is checked at mint AND enforced at ingest.** `assertOwner` stops
* an honest caller from over-reaching on the trusted local path;
* `enforceIngestAuthorization` stops a peer from minting a grant op directly and
* integrating it past the boundary (ADR 0022 Phase 3). The two are the same
* check at two ends of one pipe: hash integrity (already at ingest) plus
* attribution (this module) converge on the single ingest gate.
*/
import type { EAVStore } from '../core/store/eav-store.js';
import type { EngineContext } from '../vcs/engine-context.js';
import type { VcsOp } from '../vcs/types.js';
import type { IdentityResolver } from './signing-middleware.js';
/**
* Capability levels, ordered. Higher numeric value = broader access, so
* "at least level L" is a simple ordinal comparison.
*
* `None` is the deny-by-default absence of a grant. It is NEVER persisted as a
* fact (see ADR 0022 §1): revocation retracts the grant fact instead.
*/
export declare enum CapabilityLevel {
None = 0,// absence of grant = default. Never persisted.
Reader = 1,// read-only within the boundary
Member = 2,// read + write within the boundary
Owner = 3
}
/** Authoritative zone id: `turtle://<ownerDid>/zone/<uuid>`. Immutable. */
export type ZoneId = string;
export interface Zone {
/** Immutable, authority-bearing identifier. */
zoneId: ZoneId;
/** Mutable human name. Renaming edits this, never the id. */
alias: string;
/** Level granted to anon (Reader = public; None = private). */
defaultVisibility: CapabilityLevel;
/** Opaque parent zoneId for nesting → grant inheritance (closure). */
parentZone?: ZoneId;
}
/** A single explicit capability grant. */
export interface Grant {
principal: string;
zoneId: ZoneId;
level: CapabilityLevel;
}
/**
* Build an immutable, authority-bearing zone id.
* `turtle://<ownerDid>/zone/<uuid>` — ownership is self-describing in the id.
*/
export declare function makeZoneId(ownerDid: string, uuid: string): ZoneId;
/** Parse the owner DID out of a zoneId, or null if malformed. */
export declare function zoneOwnerDid(zoneId: ZoneId): string | null;
/**
* Define a zone. `alias` is the only mutable display field; `zoneId` is fixed.
* The zone's owner (from the id) is auto-granted `Owner` so the zone is
* administerable from creation; subsequent grants are Owner-gated.
*/
export declare function defineZone(ctx: EngineContext, zone: Zone): Promise<VcsOp>;
/**
* Rename a zone. Edits only the `alias` fact — `zoneId` and all grants are
* untouched. This is the rename-proof guarantee (ADR 0022 §2).
*/
export declare function renameZone(ctx: EngineContext, zoneId: ZoneId, alias: string): Promise<VcsOp>;
/** Load a zone definition from the store. */
export declare function getZone(store: EAVStore, zoneId: ZoneId): Zone | undefined;
/**
* Set a grant. Only an existing `Owner` of the zone may mutate grants
* (ADR 0022 invariant). `level` of `None` is rejected — revoke via
* `retractGrant` instead, so `None` is never persisted.
*/
export declare function setGrant(ctx: EngineContext, grant: Grant, actor: string): Promise<VcsOp>;
/**
* Retract a grant (revocation). Owner-gated. Removes the grant fact; the
* principal falls back to `defaultVisibility` / `None`.
*/
export declare function retractGrant(ctx: EngineContext, zoneId: ZoneId, principal: string, actor: string): Promise<VcsOp>;
/**
* Resolve the effective capability level of `principal` over `zone`.
*
* `effective = max(defaultVisibility, direct grant, inherited grants…)`
*
* - The target zone's `defaultVisibility` is a **floor**, always included.
* - Direct grant on the zone (if any).
* - Inherited grants up the `parentZone` chain (closure, `allOf`-style).
* - `None` if the zone does not exist (deny-by-default).
*
* Positive-only (union/max) — no explicit-deny override, which keeps resolution
* precedence-free. A consequence worth stating: **you cannot grant someone less
* than the zone's default.** If a principal must have less than the public
* floor, that is a different zone, not a smaller grant. (Partition, don't
* filter — the same rule the zone model is built on.)
*/
export declare function resolveCapability(store: EAVStore, principal: string, zoneId: ZoneId): CapabilityLevel;
/** Op kinds that mutate zone authority and therefore require attribution. */
export declare const AUTH_OP_KINDS: Set<string>;
export interface IngestAuthResult {
ok: boolean;
reason?: 'unauthorized';
message?: string;
}
/**
* Ingest-time authorization gate (ADR 0022 Phase 3).
*
* `integrateOps` already verifies op *integrity* (the hash) but, until now,
* verified no *attribution* — so a peer could mint a `vcs:grantSet` /
* `vcs:zoneDefine` op directly and the store would apply it, bypassing the
* `assertOwner` check that only guards the trusted local mint path
* (`setGrant` / `defineZone` → `applyOp`). This gate closes that: every
* authorization-bearing op must be attributable to a principal the zone already
* trusts at `Owner` (or, for `zoneDefine`, to the identity named in the
* zoneId — authority is self-describing in the id).
*
* Attribution: the op must be signed. When an `IdentityResolver` is wired, the
* signature is cryptographically verified and `signedBy` is trusted only if it
* verifies; without a resolver the kernel can at best require a signature
* envelope to exist (deny-by-default for unattributable auth ops). Full PKI
* validation is the resolver-wiring follow-on (ADR 0020).
*/
export declare function enforceIngestAuthorization(store: EAVStore, op: VcsOp, resolver?: IdentityResolver): Promise<IngestAuthResult>;
//# sourceMappingURL=capability.d.ts.map