import { type ComponentLogger } from '../core/logger.service.js'; import { type CallerIdentity, type IdentifiableRequest } from '../../middleware/caller-identity.middleware.js'; /** Who acted. */ export type ActivityActorType = 'owner' | 'agent' | 'system'; /** The actor of an entry. */ export interface ActivityActor { /** Owner, an agent, or Crewly itself (schedulers, deadlines, anonymous local calls) */ type: ActivityActorType; /** The agent session, Slack user id, scheduler name, … when known */ id?: string; /** How the actor was recognised (`owner-session`, `api-token`, `agent-badge`, `deadline`, …) */ via?: string; } /** What an entry is about. */ export interface ActivityTarget { /** `team`, `member`, `decision`, … */ type: string; id: string; /** Display name at the time of the action */ name?: string; } /** One line of the log. */ export interface ActivityEntry { id: string; /** ISO timestamp */ ts: string; actor: ActivityActor; /** Dotted, past tense: `team.member.updated`, `decision.resolved` */ action: string; target: ActivityTarget; /** Changed fields before the action (masked, truncated) */ before?: Record; /** Changed fields after the action (masked, truncated) */ after?: Record; /** The Crewly Request this happened under, when known */ requestId?: string; /** The WorkItem this happened under, when known */ workItemId?: string; /** Short free text (an answer, a reason) */ note?: string; } /** What a writer hands in; id and timestamp are filled here. */ export type ActivityEntryInput = Omit & { ts?: string; }; /** Filters of a read. All optional; `action` and ids match exactly, `action` also by `prefix.` */ export interface ActivityQuery { actorType?: ActivityActorType; actorId?: string; /** Exact action, or a dotted prefix such as `team.` */ action?: string; targetType?: string; targetId?: string; /** ISO timestamp (inclusive) */ since?: string; /** ISO timestamp (inclusive) */ until?: string; limit?: number; } /** File operations the log uses (injectable to simulate failures). */ export interface ActivityFsOps { mkdir(dir: string, opts: { recursive: true; }): Promise; appendFile(file: string, data: string): Promise; readFile(file: string): Promise; readdir(dir: string): Promise; } /** Construction options. */ export interface ActivityLogOptions { /** Log directory (default `/activity-log`) */ dir?: string; fs?: ActivityFsOps; now?: () => Date; logger?: ComponentLogger; } /** * Whether a string names an actor type. * * @param value - Candidate * @returns True for owner / agent / system */ export declare function isActivityActorType(value: unknown): value is ActivityActorType; /** * Mask secret-looking keys and cut long strings, recursively. Arrays and plain * objects are walked; anything else is kept as is. * * @param value - Any JSON-ish value * @returns The value safe to write */ export declare function maskForActivity(value: unknown): unknown; /** * The fields that differ between two shallow records, as a before / after * pair (masked). Equal fields are left out so an entry shows only the change; * a key absent on one side appears as `undefined` → left out of that side. * * @param before - Record before the change (null when created) * @param after - Record after the change (null when deleted) * @returns Masked before / after summaries, each undefined when empty */ export declare function summarizeChange(before: Record | null | undefined, after: Record | null | undefined): { before?: Record; after?: Record; }; /** * The actor behind a caller identity: owner credentials (dashboard session, * API token, phone / portal relay, cloud) are the owner; an agent is itself; * schedulers and anonymous local calls are the system. * * @param identity - What the caller-identity middleware decided * @returns The actor */ export declare function actorFromCaller(identity: CallerIdentity): ActivityActor; /** * The actor of an HTTP request. Never throws. * * @param req - Request * @returns The actor */ export declare function actorFromRequest(req: IdentifiableRequest): ActivityActor; /** * The append-only activity log. */ export declare class ActivityLogService { private readonly dir; private readonly fs; private readonly now; private readonly logger; private chain; private dirReady; private seq; /** * @param options - Directory, file ops, clock, logger (all optional) */ constructor(options?: ActivityLogOptions); /** * Append one entry. Returns at once; the write is chained after earlier * writes and a failure is logged, never thrown. * * @param input - The entry without id / timestamp * @returns The entry as it will be written */ record(input: ActivityEntryInput): ActivityEntry; /** * Wait for every write handed in so far. * * @returns When the chain is idle */ flush(): Promise; /** * Read entries newest first. * * @param query - Filters and limit * @returns Matching entries, newest first, at most `limit` */ list(query?: ActivityQuery): Promise; /** * The monthly files worth opening for a query, newest first, bounded by * MAX_FILES_PER_READ and by the query's time window. * * @param query - Filters (since / until narrow the months) * @returns File names */ private monthlyFiles; /** * The file an entry at a timestamp belongs to. * * @param ts - ISO timestamp * @returns Absolute path */ private fileFor; } /** * The process-wide activity log. * * @returns The log (created on first use) */ export declare function getActivityLog(): ActivityLogService; /** * Replace the process-wide log (tests). * * @param log - Log, or null to rebuild the default lazily */ export declare function setActivityLogForTesting(log: ActivityLogService | null): void; /** * Append to the process-wide log without ever throwing. The one call writers * make. * * @param input - The entry */ export declare function recordActivity(input: ActivityEntryInput): void; //# sourceMappingURL=activity-log.service.d.ts.map