/** * Company fallback for usage events whose `cwd` maps to no company folder. * * `./company-resolver.ts` attributes an event by where it ran: inside a * company's repo checkout or inside the company's own HQ folder. A session * started from the HQ root matches neither, so its usage was uploaded with no * company. This module supplies the next two sources, in order, and only when * the existing resolver found nothing: * * 1. The session's own saved company: the session's meta.yaml under the HQ * root's session workspace (`company_slug`, when its `company_source` is * `session`), or the work-context session state file (when its company * is verified, the same test hq-cli applies). * 2. The device default company (work-context `config.json`), only when the * person opted in to usage attribution for that same company * (`usageAttribution`, see usageAttributionOptIn). A session bind needs no * opt-in: the person chose that company for that session. * * Both apply ONLY when the event's cwd is a location inside the HQ root that * belongs to no tenant: the root itself and the core, personal, and workspace * folders (except workspace worktrees), and not inside any git work tree whose * top level is below the HQ root (a repo clone or worktree has its own owner). * Everything else is an unknown owner and gets nothing: a company's own * folder, a declared company path, any repo checkout (a repo the manifest does * not map may belong to a company that is not cloud-backed), any worktree (its * HQ layout is another tenant boundary), and anything outside the HQ root * (which may be personal work). * * The cwd is canonicalised first: `..` segments are resolved and symlinks are * followed, so a path that merely spells itself inside HQ is not inside HQ. A * cwd that no longer exists cannot be canonicalised and gets nothing. * * It fails closed. Attributing work to the wrong company is worse than leaving * it unattributed, so every doubt resolves to "no company": * * - Only a company the manifest marks cloud-backed AND the signed-in person * is an active member of is ever returned. Membership comes from data the * run already has (the sync runner's own membership listing) or from the * local snapshot hq-cli keeps, never from a new request. * - `company_source: parent` and `spawn` are inherited from an ancestor * session whose own source is not recorded, so a device-default guess * could have been carried down as a bind. Neither is trusted. * - A session that names a company which cannot be used (not cloud-backed, * not a member, `personal`) stays empty. It does not fall to the default. * - Two session sources that disagree stay empty. * - The default yields to any other company the session names. * * The server may refuse a company (the person left it, or it switched * collection off). A refusal is remembered on disk for 24 hours so the next * passes do not pay for it again, and every event uuid that was sent without * its fallback company is remembered so a later pass can never send the same * event with one. Both live in a small JSON file next to the cursor. * * Cost: the manifest, the device config, and the state file are read at most * once per upload pass; each session's two files and each distinct cwd at most * once per pass. Reads are size-bounded and refuse anything that is not a * regular file. A missing, oversized, or corrupt file is the same as an absent * one. * * Privacy: this module returns a `cmp_*` uid or undefined. It adds nothing else * to an event. */ import { type RepoCompanyMap } from "./company-resolver.js"; /** How long a company the server refused stays unstamped. */ export declare const REJECTION_TTL_MS: number; export interface UsageCompanyFallbackOptions { hqRoot: string; /** The manifest map already built for this pass. */ map: RepoCompanyMap; /** The work-context folder (or its override). */ workContextRoot: string; /** Where refusals and sent-without-company uuids are remembered. */ statePath: string; /** * Companies the signed-in person is an active member of, as already known to * the caller for this run. When omitted, the local membership snapshot in the * device config is used if it is fresh; otherwise nothing is stamped. */ memberCompanyUids?: Iterable; /** * The Cognito `sub` of the person session this upload runs as. The device * default is used only when the opt-in names this same sub; without it, * never. */ cognitoSub?: string; now?: () => number; } export interface UsageCompanyFallback { /** * The company for an event the existing resolver left empty, or undefined. * `uuid` is the event's own id; an event that was already sent without its * fallback company is never stamped again. */ resolve(cwd: string | undefined, sessionId: string | undefined, uuid: string | undefined): Promise; /** Stop stamping this company (the server refused it), for REJECTION_TTL_MS. */ reject(companyUid: string): void; /** Whether the server refused this company, this pass or within the TTL. */ isRejected(companyUid: string): boolean; /** Remember events that went out without their fallback company. */ markSentWithoutCompany(uuids: Iterable): void; /** Write the remembered refusals and uuids. Never throws. */ persist(): Promise; } export declare function createUsageCompanyFallback(options: UsageCompanyFallbackOptions): UsageCompanyFallback; //# sourceMappingURL=usage-company-fallback.d.ts.map