export interface UserEntry { userId: string; pin: string; } /** * Identity + destination for one credential-store audit line. The writer is * pure (it only knows the file paths); the caller is the only party that knows * WHO is acting and WHERE the audit log lives, so it is threaded in. `actor` is * an admin userId or a non-user sentinel (`onboarding` | `install` | `boot` | * `unknown` when an MCP caller has no resolvable USER_ID); `session` is the * spawning agent session (SESSION_NODE_ID) when one drove the write. `logFile` * is the absolute path to the append-only users-audit log, distinct from * server.log. Mirrored in platform/lib/admin-access-password. */ export interface AuditContext { actor: string; session?: string; logFile: string; /** Task 1573 — the row being written, recorded on every credential write so * `actor != target` is the standing cross-identity signature. Omitted => * the line carries no target field (a self write not routed through the * identity-resolving tools). */ target?: string; /** Task 1573 — true when the caller passed an explicit `userId` (a deliberate * cross-identity or self write); false/absent for an implicit self write. */ explicitUserId?: boolean; } /** Format a userId list as the comma-joined short-id set used in audit lines. * Exported for direct users.json writers that do not route through * writeAdminEntry (the MCP admin-update-pin path). */ export declare function formatAuditRowIds(userIds: string[]): string; /** Public wrapper so a direct users.json writer that cannot route through * writeAdminEntry (the MCP admin-update-pin path) still emits the one * canonical `[users-audit]` line, keeping every write covered. */ export declare function logUsersAudit(audit: AuditContext, fields: { action: string; field: string; rowsBefore: string; rowsAfter: string; }): void; export interface AdminEntry { userId: string; role: "owner" | "admin"; } export interface AdminWriteInput { userId: string; pin: string; role: "owner" | "admin"; usersFile: string; accountDir: string; caller: string; audit: AuditContext; } export interface AdminWriteResult { usersJsonResult: "ok" | "fail" | "noop"; accountJsonResult: "ok" | "fail" | "noop"; usersError?: string; accountError?: string; } /** * Write a single admin entry across both auth stores (users.json + account.json admins[]). * * Idempotent on userId: if the userId already exists in users.json, the PIN * field is updated in place rather than duplicated; admins[] is checked for * presence before pushing. * * Always emits exactly one `[admins-write] caller=… userId= usersJsonResult=… accountJsonResult=…` * line — success and failure paths both fire it. Operators grep for this single * predicate when reconstructing a partial-write incident. */ export declare function writeAdminEntry(input: AdminWriteInput): AdminWriteResult; export interface AdminRemoveInput { userId: string; accountDir: string; caller: string; audit: AuditContext; } export interface AdminRemoveResult { accountJsonResult: "ok" | "fail" | "noop"; accountError?: string; } /** * Remove a single admin entry from account.json admins[] — does NOT touch * users.json (the device-level entry is preserved because the user may admin * other accounts). Single-file operation; the [admins-write] line records * `usersJsonResult=skip` so the grep-by-helper-call still picks it up. */ export declare function removeAdminFromAccount(input: AdminRemoveInput): AdminRemoveResult; export interface InvariantCheckInput { /** Absolute path to users.json (persistent location). */ usersFile: string; /** Absolute path to the accounts root (e.g. /data/accounts). */ accountsDir: string; /** Tag for the log prefix — `install-invariant` or `admin-invariant`. */ tag: "install-invariant" | "admin-invariant"; } export interface InvariantCheckResult { divergences: number; /** Userids in account.json admins[] that have no row in users.json. */ accountWithoutUsers: Array<{ userId: string; source: string; }>; /** Userids in users.json that no account.json admins[] references. */ usersWithoutAccount: Array<{ userId: string; }>; } /** Pure divergence computation — read faults collected, never logged. */ export interface DivergenceResult { divergences: number; /** Userids in account.json admins[] that have no row in users.json. */ accountWithoutUsers: Array<{ userId: string; source: string; }>; /** Userids in users.json that no account.json admins[] references. */ usersWithoutAccount: Array<{ userId: string; }>; /** Read faults encountered during the walk (unreadable users/account file). */ errors: Array<{ source: string; detail: string; }>; } /** * Walk every account.json under accountsDir and compare its admins[] to * users.json. PURE: returns the divergence sets and any read faults; does no * logging and never throws. Shared by `checkAdminAuthInvariant` (the * `[admin-invariant]` boot one-shot) and the `[users-reconcile]` standing tick * so the two never disagree on the predicate. An absent users.json or accounts * dir is a legitimate first-boot state → empty set, not a fault. */ export declare function computeAdminStoreDivergence(input: { usersFile: string; accountsDir: string; }): DivergenceResult; /** * `[admin-invariant]` / `[install-invariant]` boot one-shot. Wraps the pure * `computeAdminStoreDivergence` and emits the documented grep lines: * `direction=…`, the per-fault unreadable line, and the always-emitted * `check complete divergences=` summary. Log-only — does NOT throw, does NOT * refuse boot or install. The recurring standing check is the separate * `[users-reconcile]` tick in the admin server boot. */ export declare function checkAdminAuthInvariant(input: InvariantCheckInput): InvariantCheckResult; //# sourceMappingURL=index.d.ts.map