/** * VaultClient — typed SDK for vault-service membership operations (VLT-7 US-001). * * Wraps vault-service HTTP API with shared auth, retry, and typed errors. * Colocated with hq-cloud so /invite, /promote, /accept and future commands * share one client instead of each rolling its own HTTP layer. */ import type { VaultServiceConfig } from "./types.js"; import { type CredentialScopeDiagnostic } from "./credential-scope-error.js"; /** * Structured fields the vault service attaches to a 4xx body alongside the * human message. `code` is the stable machine identifier (for example * `SKILL_REGISTER_LANDED_IDENTITY_MISMATCH`); `path` is the vault-relative * object the refusal is about. Both are optional: older routes and non-JSON * bodies carry neither. They ride on the error so `describeError` and the * sync-runner's error events can name the exact file instead of the caller * having to re-parse `body`. */ export interface VaultErrorDetails { code?: string; path?: string; } export declare class VaultClientError extends Error { readonly statusCode: number; readonly body?: string | undefined; readonly code?: string; readonly path?: string; constructor(message: string, statusCode: number, body?: string | undefined, details?: VaultErrorDetails); } export declare class VaultAuthError extends VaultClientError { readonly tokenFingerprint?: string | undefined; constructor(message?: string, tokenFingerprint?: string | undefined, details?: VaultErrorDetails); } export declare class VaultPermissionDeniedError extends VaultClientError { constructor(message?: string, details?: VaultErrorDetails); } export declare class VaultNotFoundError extends VaultClientError { constructor(message?: string, details?: VaultErrorDetails); } export declare class VaultConflictError extends VaultClientError { constructor(message?: string, details?: VaultErrorDetails); } /** * Stamp a vault-relative object path onto a `VaultClientError` that arrived * without one. No-op when the error already named a path, or is not a vault * client error (Node `ErrnoException.path` is a local filesystem path and * must not leak onto sync events). */ export declare function attachVaultErrorPath(err: unknown, path: string): unknown; export type MembershipRole = "owner" | "admin" | "member" | "guest"; export type MembershipStatus = "pending" | "active" | "revoked"; export interface Membership { membershipKey: string; personUid: string; companyUid: string; role: MembershipRole; status: MembershipStatus; allowedPrefixes?: string[]; inviteToken?: string; invitedBy: string; invitedAt: string; acceptedAt?: string; revokedAt?: string; createdAt: string; updatedAt: string; } export interface CreateInviteInput { personUid?: string; inviteeEmail?: string; companyUid: string; role: MembershipRole; allowedPrefixes?: string[]; invitedBy: string; } export interface CreateInviteResult { membership: Membership; inviteToken: string; } export interface AcceptInviteResult { membership: Membership; } export interface UpdateRoleInput { membershipKey: string; newRole: MembershipRole; allowedPrefixes?: string[]; updaterUid: string; /** Required so the server can authorize the caller as admin/owner of the company. */ companyUid: string; } export interface EntityInfo { uid: string; slug: string; type: string; /** Human-readable display name — surfaced in UIs that list companies. */ name?: string; bucketName?: string; status: string; createdAt: string; } export interface RegisterCompanySkillInput { /** Company-vault-relative canonical path: skills/{slug}/SKILL.md. */ path: string; /** Current local SKILL.md bytes. The server returns the identity-stamped bytes. */ content: string; /** * True only after these exact bytes were uploaded successfully. This lets * the registry reconcile source-authored metadata without getting ahead of * the canonical vault object when an upload conflicts or fails. */ landed?: boolean; } export interface RegisterCompanySkillResult { ok: true; skillUid: string; path: string; vaultPath: string; content: string; action: "created" | "reused" | "forked" | "moved"; accessPolicy: "open" | "preserved"; skill: { name: string; description: string; tags: string[]; }; } /** * Pick the caller's OWN entity for the `--personal` slot when the runner is an * agent machine identity (username `machine-agt_*`, idToken * `custom:entityType=agent` / `custom:entityUid=agt_*`). * * The person-only `pickCanonicalPersonEntity` filters `type === "person"`, so * an agent's own entity (`type: "agent"`) is dropped and `--personal` for an * agent emits `setup-needed` — never reaching the STS vend / S3 read (the * US-004 hard-gate finding). This selector resolves the agent's own entity by * matching `selfUid` (the `custom:entityUid` claim) against the self-listing * the agent sees, so the personal slot resolves to `hq-vault-agt-`. * * Returns null when no entity matches the claimed self-uid (the caller then * falls back to the person-only pick / `setup-needed`, exactly as before). */ export declare function pickAgentSelfEntity(list: EntityInfo[], selfUid: string): EntityInfo | null; export declare function pickCanonicalPersonEntity(list: EntityInfo[]): EntityInfo | null; export interface PendingInviteByEmail { membershipKey: string; companyUid: string; role: MembershipRole; inviteToken?: string; invitedBy: string; invitedAt: string; } export interface CreateEntityInput { type: "person" | "company"; slug: string; name: string; email?: string; ownerUid?: string; } export interface CreateEntityResult { entity: EntityInfo; } /** * Source kind for an explicit per-company file-ACL grant. Mirrors the * server enum in hq-pro `vault-service/handlers/files-grants.ts`. * * `'open'` collapses two server-side shapes that are indistinguishable to * the caller — the legacy `acl.open === true` floor and an explicit * `granteeType: 'company-wide'` row. Both mean "every active member of * this company sees this prefix". */ export type GrantSource = "creator" | "person" | "email" | "group" | "open"; /** Permission level surfaced on a grant row. Matches `AclPermission`. */ export type GrantPermission = "read" | "write" | "admin"; /** * One row in the response of `GET /v1/files/grants?company={uid}`. * * Role-bypass (owner/admin) entries are intentionally excluded by the * server — this is the caller's EXPLICIT grant graph, not the full set * of prefixes they can touch by virtue of role. */ export interface ExplicitGrant { companyUid: string; path: string; permission: GrantPermission; source: GrantSource; } /** Presign operation: download / upload / delete. */ export type PresignOp = "get" | "put" | "delete"; /** One object's metadata as returned by GET /v1/files/list. */ export interface VaultListedObject { key: string; size: number; lastModified: string | null; /** * Additive S3 storage class from the list response. Older hq-pro servers do * not send it, so callers must treat an omitted value as STANDARD. */ storageClass?: string; /** * S3 ETag (quotes stripped) or null. Load-bearing for sync change- * detection: the client stores it as remote-content identity and compares * it to decide pull/skip/delete-safety — the same role `RemoteFile.etag` * plays on the STS path. Mirrors the hq-pro `files/list` field (PR #269). */ etag: string | null; permission: GrantPermission; } /** One key in a batch presign request. */ export interface PresignKeyInput { key: string; op?: PresignOp; contentType?: string; /** Custom object metadata to sign into a PUT (x-amz-meta-*). */ metadata?: Record; /** * Conditional-write fence for a PUT presign (S3 conditional writes). When * the server supports it (hq-pro files-presign follow-up), it signs * `If-Match: ""` / `If-None-Match: *` into the URL and echoes the * header in the result row's `headers` for verbatim replay. Servers that * predate the field ignore it — the PUT stays unconditional, never broken. */ ifMatch?: string; ifNoneMatch?: "*"; /** * Base64 of the object's raw SHA-256 for a PUT. When it matches the key's * `hq-content-sha256` metadata, hq-pro signs `x-amz-checksum-sha256` into * the URL (required for Object-Locked buckets) and echoes it in `headers`. */ checksumSha256?: string; } /** One result row from POST /v1/files/presign (per key, request order). */ export interface PresignResultRow { key: string; op: PresignOp; /** Present on success: the presigned URL. */ url?: string; /** * Present on a PUT success: the EXACT headers to send on the PUT so the * SigV4 signature matches (Content-Type, SSE-KMS, every x-amz-meta-*). */ headers?: Record; expiresIn?: number; expiresAt?: string; /** Present on per-key denial/validation failure. */ error?: string; code?: string; } /** * Effective sync mode for a single membership. Mirrors the server's * resolved view from `GET /v1/memberships/{id}/sync-config`: * * - `shared` — sync only `shared/` and the caller's `personal/` prefix * - `all` — sync every prefix the caller has read access to * - `custom` — sync the explicit `customPaths` list (server validates) * * `isDefault: true` means no row exists in DDB and the server is * falling back to its built-in default (currently `'all'` for legacy * memberships created pre-US-003). When `true`, `updatedAt`/`updatedBy` * are absent because there's no row to attribute. */ export type SyncMode = "shared" | "all" | "custom"; export interface MembershipSyncConfig { membershipId: string; syncMode: SyncMode; customPaths?: string[]; /** * `true` when the server returned the built-in default because no * sync-config row exists for this membership. PUT always returns * `false` — writing the row is what makes it non-default. */ isDefault: boolean; /** Present only when a sync-config row exists (i.e. `isDefault: false`). */ updatedAt?: string; /** Present only when a sync-config row exists. PersonUid of the writer. */ updatedBy?: string; } /** * Input shape for {@link VaultClient.setMembershipSyncConfig}. The server * validates the combination — `customPaths` is required when `syncMode` * is `'custom'` and rejected otherwise. */ export interface SetMembershipSyncConfigInput { syncMode: SyncMode; customPaths?: string[]; } /** * Why the caller is requesting STS-scoped credentials. Mirrors the * hq-pro vault-service enum (`src/vault-service/policy-builder.ts`). * * - `'sync'` — background machine sync. Role-bypass MUST NOT widen * the path set: credentials are scoped to exactly the requested * paths (which the sync engine has already narrowed via US-005). * - `'browse'` — interactive exploration (hq-console Explore, * `hq files browse`, admin spelunking). Admin/owner role-bypass * APPLIES — the caller may receive credentials covering paths * beyond their explicit ACL grants. * * The server defaults missing/empty to `'sync'` (the safer choice). * The client doesn't mirror that default — every caller should be * explicit about its intent so audit rows are accurate. */ export type VendPurpose = "sync" | "browse"; export type VaultOperation = "read-only" | "read-write" | "staged-write"; /** * Input shape for {@link VaultClient.vend}. The server validates * combinations — e.g. `purpose: 'sync'` rejects bucket-wide `'*'` paths * as defense in depth against role-bypass widening on the sync path. */ export interface VendInput { paths: string[]; operations: VaultOperation; /** Why these credentials are being vended. See {@link VendPurpose}. */ purpose: VendPurpose; /** STS session lifetime in seconds. Server default is 900 (15m). */ duration?: number; } export interface VendCredentials extends CredentialScopeDiagnostic { accessKeyId: string; secretAccessKey: string; sessionToken: string; /** ISO-8601 STS-native expiration string. */ expiration: string; } export interface VendResult { credentials: VendCredentials; /** Echo of the server-resolved paths after ACL intersection. */ paths: string[]; operations: VaultOperation; /** Echo of the effective purpose (server-defaulted to 'sync' if absent). */ purpose: VendPurpose; /** * Size of the rendered IAM session policy in characters. Lets the * caller detect when it's nearing the 2048-char IAM ceiling so it can * fan out across multiple vends or shrink its path set. */ policySize: number; requestId?: string; } export type TaskAction = "read" | "write"; export interface TaskScope { /** S3 key prefixes the child may access (e.g. ["drafts/"]). */ allowedPrefixes: string[]; /** Defaults to ["read", "write"]. Use ["read"] for read-only children. */ allowedActions?: TaskAction[]; } export interface VendChildInput { companyUid: string; /** ULID generated by the parent task. Flows into STS session name for audit. */ taskId: string; /** Short human-readable description (<256 chars). Logged alongside the session. */ taskDescription: string; taskScope: TaskScope; /** * Child session duration in seconds. Defaults to 900 on the server — AWS STS * AssumeRole enforces a 900s floor. The task-scoped policy is the security * boundary, not the duration. */ durationSeconds?: number; } export interface StsChildCredentials extends CredentialScopeDiagnostic { accessKeyId: string; secretAccessKey: string; sessionToken: string; } export interface VendChildResult { credentials: StsChildCredentials; /** STS session name: `${parentPersonUid}--task--${taskId}` — used in CloudTrail. * (Dash-separated because AWS STS `roleSessionName` disallows colons.) */ sessionName: string; /** ISO-8601 session expiration. */ expiresAt: string; } export interface TelemetryOptInResponse { enabled: boolean; updatedAt: string | null; /** * `true` when the person row carries NO `telemetryOptIn` attribute — i.e. the * consent question has never been answered server-side. Distinct from * `enabled: false`, which is a deliberate opt-OUT. * * Optional because older servers omit it entirely. Absent is treated as * `false`, so a client talking to one behaves exactly as before — no * self-heal, no surprise writes. */ unset?: boolean; /** * The `prs_*` uid this answer belongs to — i.e. the authenticated caller. * * Needed because the local consent cache is a per-MACHINE file: if two people * sign in under the same OS user it holds whoever answered last. A client * must not replay it for a different account. Optional (older servers omit * it), and absence means the replay cannot be proven safe, so it is skipped. */ personUid?: string; } export interface UsageBatch { machineId: string; installerVersion: string; /** * Sanitized event rows. Each row is a plain object containing only the * fields in the server's KEEP allowlist (sessionId, timestamp, uuid, cwd, * gitBranch, userType, model, inputTokens, outputTokens, * cacheCreationInputTokens, cacheReadInputTokens, and the optional * companyUid edge-attribution field — US-002). Any extra field is * rejected by hq-pro with `unexpected-event-field`, so the sanitizer in * `./telemetry.ts` is the only thing allowed to produce these. */ events: Array>; } export interface UsageIngestResult { ok: boolean; written: number; skipped: Array<{ index: number; code: string; error: string; }>; } export interface RawTelemetryEventInput { eventName: string; app: "hq-cloud"; source: string; occurredAt: string; companyUid?: string; agentUid?: string; sessionId?: string; schemaVersion?: 1; properties?: Record; } export interface TelemetryEventsBatch { /** * Action events emitted by sync/CLI surfaces. `personUid` MUST NOT appear in * either the batch or any event — hq-pro resolves the caller server-side from * the Cognito JWT and rejects client-supplied person identifiers. */ events: RawTelemetryEventInput[]; } /** * Normalized response shape for `POST /v1/telemetry/events`. * * hq-pro response contract v1 uses the established ingest fields (`ok`, * `written`, `skipped`). The decoder also accepts the briefly deployed * `{ accepted, deduped }` shape so clients remain compatible while the server * rollout catches up. */ export type TelemetryEventsIngestResult = UsageIngestResult; export interface SkillInvocationBatch { machineId: string; installerVersion: string; /** * Skill-invocation event rows. Each row contains only the fields in the * server's KEEP allowlist (skill, source, sessionId, timestamp, uuid, cwd, * hasArgs, the optional companyUid edge-attribution field — US-002, and the * optional skillVersion content-hash marker — US-015). Raw argument text is * never included — see the privacy note in `./skill-telemetry.ts`. Any extra * field is rejected by hq-pro with `unexpected-event-field`, so the extractor * in `./skill-telemetry.ts` is the only thing allowed to produce these. */ events: Array>; } /** Same wire shape as `UsageIngestResult`; aliased for call-site clarity. */ export type SkillInvocationIngestResult = UsageIngestResult; export interface OutcomeEventsBatch { /** * Outcome-event rows. Each row is a plain object containing only the fields * in the server's KEEP allowlist (type, occurredAt, companyUid, repo, branch, * dedupeKey, and the type-specific refs projectName / storyId). Unlike the * usage/skill batch, this batch carries NO machineId/installerVersion — the * ingest handler (`apps/hq-pro/src/vault-service/handlers/outcome-events.ts`) * accepts only `{ events }`. `personUid` MUST NOT appear on the batch or any * event — hq-pro resolves the caller server-side from the Cognito JWT and * REJECTS a body-supplied person identifier. Any field outside the allowlist * is rejected with a 4xx `invalid-event`, so the emitter in * `./outcome-telemetry.ts` is the only thing allowed to produce these. */ events: Array>; } export interface OutcomeEventsIngestResult { ok: boolean; /** Rows newly written this request. */ written: number; /** Rows collapsed by the server-side conditional put (idempotent replay). */ deduped: number; } export declare class VaultClient { private readonly apiUrl; private readonly getAuthToken; private readonly clientInfo; constructor(config: VaultServiceConfig); /** * Reserve or reuse the immutable identity for a canonical company skill. * This endpoint never uploads S3 bytes; the normal sync engine owns that * transfer. Callers set `landed` only after the upload succeeds so the * registry may safely reconcile source-authored metadata such as tags. */ registerCompanySkill(companySlug: string, input: RegisterCompanySkillInput): Promise; createInvite(input: CreateInviteInput): Promise; acceptInvite(token: string, personUid: string): Promise; /** * Revoke a membership. The handler needs both the membershipKey AND the * companyUid so it can authorize the caller as admin/owner of the company * before performing the revoke. (We can't infer companyUid from the key * alone without an extra DDB read, and the caller already knows it.) */ revokeMembership(membershipKey: string, companyUid: string): Promise; /** * List the caller's own active memberships. * * Server infers the caller's identity from the Cognito JWT `sub` claim and * returns the union of active memberships across every person entity owned * by that sub (orphan-tolerant — prior failed provisioning runs can leave * multiple `prs_*` rows for the same Cognito identity). * * Returns `[]` — NOT a 404 — when the caller has no person entity yet. * This lets `hq-sync-runner` distinguish "signed in but not bootstrapped" * (empty array → emit `setup-needed`) from "auth broken" (throws * VaultAuthError) without catching HTTP errors for flow control. * * Backed by `GET /membership/me` (see hq-pro ADR-0002). */ listMyMemberships(): Promise; /** * List the caller's email-keyed pending invites. Server reads the email * from the Cognito JWT, so no parameters are needed client-side. * * Used on first sign-in (installer + sync-runner) to detect invites that * were sent to the caller's email before they had a person entity. Pair * with {@link claimPendingInvitesByEmail} to rewrite those rows once the * person exists. */ listMyPendingInvitesByEmail(): Promise; /** * Rewrite every email-keyed pending invite for the caller's email so it * becomes personUid-keyed. Idempotent — zero-cost for returning users who * have no pending invites. The caller's email is inferred from the JWT. */ claimPendingInvitesByEmail(personUid: string): Promise; listMembersOfCompany(companyUid: string): Promise; updateRole(input: UpdateRoleInput): Promise; listPendingInvites(companyUid: string): Promise; /** * List the caller's EXPLICIT per-company file-ACL grants. Backed by * `GET /v1/files/grants?company={companyUid}` (hq-pro US-002). * * Role-bypass (owner/admin) entries are excluded server-side — the * response is the caller's actual grant graph, not the full set of * prefixes they can touch by virtue of role. Used by the * browse-vs-sync UI to render an honest grant graph and by the * sync engine to narrow what it pulls. * * Returns `[]` (NOT a 404) when the caller has no explicit grants in * this company, so call sites can treat "empty graph" as a normal * state without catching errors. */ listMyExplicitGrants(companyUid: string): Promise; /** * ACL-filtered list of objects under `prefix`. Backed by * `GET /v1/files/list?company=&prefix=&cursor=`. Returns only the keys the * caller can read, each with metadata (size, lastModified, permission), plus * an opaque `cursor` for the next page (null when exhausted). Page the cursor * until it is null. */ listFiles(companyUid: string, prefix?: string, cursor?: string): Promise<{ objects: VaultListedObject[]; cursor: string | null; truncated: boolean; }>; /** * Batch-mint presigned get/put/delete URLs. Backed by * `POST /v1/files/presign`. Authorization is PER KEY — denied/invalid keys * come back as `results[i].error` (the call itself succeeds), so callers must * inspect each row. PUT rows carry `headers` the client must replay verbatim. */ presign(input: { companyUid: string; op?: PresignOp; expiresIn?: number; keys: PresignKeyInput[]; }): Promise<{ results: PresignResultRow[]; expiresAt: string; }>; /** * Read the effective sync-mode for a single membership. Backed by * `GET /v1/memberships/{id}/sync-config` (hq-pro US-003). * * The server resolves the effective view — when no row exists for the * membership it returns the built-in default with `isDefault: true` * and omits `updatedAt`/`updatedBy`. Callers should treat `isDefault: * true` as "no explicit config yet" rather than special-casing 404. * * Authorization: caller must own the membership OR hold admin/owner * on the company that the membership belongs to. The server 404s * tombstoned/revoked memberships. */ getMembershipSyncConfig(membershipId: string): Promise; /** * Write the sync-mode for a single membership. Backed by * `PUT /v1/memberships/{id}/sync-config` (hq-pro US-003). * * Server validates: `customPaths` is required when `syncMode` is * `'custom'` and rejected otherwise. The returned row reflects the * persisted state with `isDefault: false` (writing the row is what * makes it non-default) and the server-assigned `updatedAt` + * `updatedBy`. */ setMembershipSyncConfig(membershipId: string, partial: SetMembershipSyncConfigInput): Promise; readonly entity: { get: (uid: string) => Promise; /** * Legacy global slug lookup. Under the per-user-namespace model on * hq-pro (PR indigoai-us/hq-pro#67, live in prod 2026-05-15) the * server-side handler now uses `requireUnique: true` — this method * returns a single entity when only one tenant holds the slug, 404s * when nobody does, or 409s with `SlugNotUniqueError` and a list of * colliding `uids` when more than one tenant holds it. Most CLI * call sites have moved to `findInMyNamespace` (which respects the * caller's effective namespace); only flows that genuinely want a * global lookup (admin tooling) should still use this method. */ findBySlug: (type: string, slug: string) => Promise; /** * Resolve an entity by slug within the CALLER's namespace * (owned ∪ active-member-of, soft-deleted excluded). Hits the new * `GET /entity/check-slug/me?type=&slug=` endpoint added in PR 67. * * Returns the full entity when present in the caller's namespace, * or `null` when the slug isn't theirs — even if some OTHER user * happens to own a company with the same slug. This is what every * "find my-company by slug" flow wants under the per-user model; * `findBySlug`'s global semantic would over-match (return a * stranger's entity) or 409 (multi-tenant slug) in those cases. */ findInMyNamespace: (type: string, slug: string) => Promise; create: (input: CreateEntityInput) => Promise; /** Return every entity of `type` owned by the caller (scoped by JWT). */ listByType: (type: string) => Promise; }; /** * Return the caller's person entity, creating it if one does not exist. * * Mirrors the installer's `ensurePersonEntity` bootstrap (`vault-handoff.ts`): * pre-condition for {@link claimPendingInvitesByEmail}, which needs a * concrete `personUid` to rewrite the email-keyed rows against. * * The slug is derived from `displayName`; if slugification yields an empty * string, falls back to `user-` so the POST always has * a non-empty slug. */ ensureMyPersonEntity(hints: { ownerSub: string; displayName: string; }): Promise; provisionBucket(companyUid: string): Promise<{ bucketName: string; kmsKeyId: string; }>; /** * POST `/vend` — vend STS-scoped credentials for an explicit path list. * * This is the legacy raw-vend endpoint (distinct from `/sts/vend`, * `/sts/vend-self`, and `/sts/vend-child`). Per US-009 it accepts a * `purpose` discriminator that controls whether admin/owner * role-bypass widens the resulting session policy beyond the * caller's explicit ACL grants: * * - `purpose: 'browse'` — role-bypass APPLIES (interactive * `hq files browse`, admin spelunking). * - `purpose: 'sync'` — role-bypass SUPPRESSED (background sync; * credentials are scoped to exactly what the caller has explicitly * been granted, regardless of role). * * The server defaults missing/empty to `'sync'` but every first-party * caller should be explicit so audit attribution is correct. * * Used by `hq files browse`/`hq files cat` (US-008) to peek at vault * objects without ever materialising them under `companies/{co}/`. */ vend(input: VendInput): Promise; readonly sts: { /** * Vend membership-scoped credentials for a company the caller belongs to. * Backed by the vault-service `POST /sts/vend` route — the multi-tenant * path that resolves the company's per-entity bucket and builds the * session policy from the caller's role + ACL grants server-side * (owner/admin get full-access, member/guest get per-prefix scoping). * * This is the correct path for interactive reads (`hq files browse`/`cat`): * the legacy `POST /vend` ({@link VaultClient.vend}) assumes a single * static bucket and is non-functional in multi-tenant production. */ vend: (input: { companyUid: string; durationSeconds?: number; }) => Promise<{ credentials: { accessKeyId: string; secretAccessKey: string; sessionToken: string; }; expiresAt: string; }>; /** * Vend task-scoped child credentials strictly narrower than the caller's * own membership. Backed by the vault-service `POST /sts/vend-child` * route (kebab-case to match the rest of the vault-service API). * * The child policy is intersected with the caller's membership on the * server — if `taskScope.allowedPrefixes` requests anything the parent * can't see, the server throws ScopeExceedsParentError before calling STS. * * Session name format: `${parentPersonUid}--task--${taskId}` — this lands * in CloudTrail verbatim, so every child S3 action can be traced back to * the parent task for incident response. */ vendChild: (input: VendChildInput) => Promise; vendSelf: (input: { personUid: string; durationSeconds?: number; }) => Promise<{ credentials: { accessKeyId: string; secretAccessKey: string; sessionToken: string; }; expiresAt: string; }>; }; /** * `GET /v1/usage/opt-in` — read whether the authenticated caller has opted * in to per-event usage telemetry. Defaults to `false` server-side when the * person row carries no `telemetryOptIn` field. Callers should treat any * thrown error as "unknown — fall back to the local gate" rather than * either yes or no; see `./telemetry.ts::collectAndSendTelemetry`. */ getTelemetryOptIn(): Promise; /** * `POST /v1/usage/opt-in` — record the authenticated caller's consent. * * The installer is the primary writer (it owns the consent prompt). This * client-side setter exists so the sync runner can RE-ASSERT a consent the * user already gave locally but which never reached the server: the * installer's write fires before the person entity exists and 404s on * `no-person-entity`, so the answer survives only in `~/.hq/menubar.json`. * See `./telemetry.ts::collectAndSendTelemetry`, which calls this ONLY when * the server reports `unset` — never over an explicit opt-out. * * `onlyIfUnset` makes the server write conditional on the consent still never * having been recorded. The self-heal MUST pass it: reading `unset` and * replaying the answer are two separate requests, so without the condition * another device could record a real opt-out in between and this replay would * silently overwrite it. A deliberate user choice omits the flag so it always * wins. The response's `applied` reports whether the write landed. */ setTelemetryOptIn(enabled: boolean, opts?: { onlyIfUnset?: boolean; }): Promise<{ applied: boolean; }>; /** * `POST /v1/usage` — upload a batch of sanitized telemetry events. * * `personUid` MUST NOT appear in the batch — server-side resolution from * the JWT is the only path. The server caps the body at 256 KiB and the * event list at 100 rows; the collector in `./telemetry.ts` enforces a * 1 MiB pre-flush cap which is the binding limit in practice. */ postUsage(batch: UsageBatch): Promise; /** * `POST /v1/skill-invocations` — upload a batch of skill-invocation events. * * Same trust + size model as `postUsage`: `personUid` MUST NOT appear in the * batch (server resolves it from the JWT). Gated by the same telemetry * opt-in as `/v1/usage`. */ postSkillInvocations(batch: SkillInvocationBatch): Promise; /** * `POST /v1/outcome-events` — upload a batch of delivery-outcome events * (story-completed / project-shipped, per outcome-leaderboard US-004). * * Same trust model as `postUsage`: `personUid` MUST NOT appear on the batch * or any event — hq-pro resolves the caller from the JWT and rejects a * body-supplied person identifier. Gated by the same telemetry opt-in as * `/v1/usage`. Server-side conditional PutItem on the composite dedupeKey * makes re-syncs and multi-machine replays idempotent (they count as * `deduped`, never a double-write). */ postOutcomeEvents(batch: OutcomeEventsBatch): Promise; /** * `POST /v1/telemetry/events` — upload a capped batch of hq-cloud ACTION * events. * * Same trust model as `postUsage`: `personUid` MUST NOT appear in the body; * hq-pro resolves the caller from the JWT and validates any `companyUid` * against the caller's memberships. This endpoint is for server/CLI action * telemetry and is not gated by the personal `/v1/usage` opt-in flag. */ postTelemetryEvents(batch: TelemetryEventsBatch, options?: { timeoutMs?: number; }): Promise; private get; private post; private request; private mapError; private extractMessage; /** * Pull the human message plus the structured `code` / `path` the vault * service attaches to its 4xx bodies. Non-JSON bodies degrade to the raw * text with no details, exactly as before. */ private parseErrorBody; } //# sourceMappingURL=vault-client.d.ts.map