import type { AppId, IsoDateTime } from "./ids.js"; import type { RunContext } from "./run-context.js"; /** * Build contract §9.3 — the `can()` seam. * * The SHAPE lives here and the implementation lives in `@vendoai/vendo/store` * (`appAccess(store)`, re-exported from there): the apps runtime, the wire and * the MCP door all speak this interface, and `apps → core` is the only edge * layering allows them (dependency-guard). Same split as `Check`/`Finding`. */ /** The closed, ORDERED level vocabulary. Assignments are fully flexible; defining new level types is deliberately not a surface. */ export type AccessLevel = "viewer" | "editor" | "owner"; /** What `can()` is asked about: an app, or a workspace path. */ export type CanThing = { app: AppId; } | { path: string; }; /** One stored grant (build contract §9.2) — the only multi-party rows Vendo keeps. `principal` is one string: `user:` · `team:/` · `org:`, matched against the memberships the host ASSERTS. */ export interface AppGrantRecord { id: string; appId: AppId; orgId: string; principal: string; level: AccessLevel; /** The granting subject, for audit. */ createdBy: string; createdAt: IsoDateTime; } /** Build contract §9.3 — one function, three doors. */ export interface AppAccess { can(ctx: RunContext, level: AccessLevel, thing: CanThing): Promise; levelFor(ctx: RunContext, appId: AppId): Promise; grant(ctx: RunContext, appId: AppId, principal: string, level: AccessLevel): Promise; revoke(ctx: RunContext, appId: AppId, principal: string): Promise; list(ctx: RunContext, appId: AppId): Promise; } /** The closed level ORDER. `viewer < editor < owner`. */ export declare const ACCESS_RANK: Record; /** Does a held level satisfy a required one? `null` = no access at all. */ export declare const holdsLevel: (held: AccessLevel | null, needed: AccessLevel) => boolean; /** Effective access is the MAX of what applies (§9.3). */ export declare const strongerLevel: (left: AccessLevel | null, right: AccessLevel | null) => AccessLevel | null; /** The §9.2 principal encoding, parsed. One string, ref-queryable. */ export type GrantPrincipal = { kind: "user"; subject: string; } | { kind: "team"; org: string; team: string; } | { kind: "org"; org: string; }; export declare function parseGrantPrincipal(encoded: string): GrantPrincipal | undefined; export declare function isGrantPrincipal(encoded: string): boolean; /** Render the encoding, so no caller has to know the grammar. */ export declare function encodeGrantPrincipal(target: GrantPrincipal): string; /** `/orgs//**` → the org. Owner derivation is a pure function of the path (§9.7), and this is that function. */ export declare function orgOfPath(path: string): string | undefined; /** `/orgs//apps/` and everything under it — the app grant governs the whole subtree INCLUDING its root, or a member holding no grant could write the root as a file and the app's own subtree could never exist. */ export declare function appOfOrgPath(path: string): AppId | undefined; /** Does an ASSERTED membership satisfy this grant row's principal? Memberships come from the ctx only — Vendo has no org chart to query (§9.1). */ export declare function grantMatches(ctx: RunContext, encoded: string): boolean; /** * §9.3's path variant, decided as far as it can be without rows: either the * answer, or the app whose grants govern the rest. */ export type PathAccess = { decision: boolean; } | { app: AppId; }; export declare function accessForPath(ctx: RunContext, level: AccessLevel, path: string): PathAccess;