@tinycloud/operations 0.7.11-beta.1 → 0.8.0-beta.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/contract.ts","../src/errors.ts","../src/cli-name.ts","../src/revoked-grant.ts","../src/kv-failure.ts","../../sdk-core/src/policy/errors.ts","../../sdk-core/src/policy/jcs.ts","../src/authority.ts","../src/state.ts","../src/artifacts.ts","../src/recovery.ts","../src/secrets.ts","../src/mcp-authority.ts","../src/invoke.ts","../src/redaction.ts"],"sourcesContent":["import type { z } from \"zod\";\n\nimport type { OperationError, OperationErrorCode } from \"./errors.js\";\n\n/** A stable, cross-surface operation identifier. */\nexport type OperationId = string;\n\n/** JSON Pointer syntax from RFC 6901, including the empty pointer for a root value. */\nexport type JsonPointer = \"\" | `/${string}`;\n\nexport type OperationEffect =\n | \"read\"\n | \"local_write\"\n | \"write\"\n | \"destructive\"\n | \"external\";\n\nexport type TinyCloudPosture =\n | \"owner-openkey\"\n | \"delegate-session\"\n | \"local-owner-key\"\n | \"unauthenticated\";\n\nexport type OperationOperatorType = \"human\" | \"agent\";\n\nexport type SurfaceDisposition =\n | Readonly<{ status: \"required\" }>\n | Readonly<{ status: \"excluded\"; reason: string }>;\n\n/**\n * Safe identity reported with every result. This deliberately excludes keys,\n * tokens, delegations, grants, and operation inputs or outputs.\n */\nexport interface OperationContextSummary {\n readonly profile: string;\n readonly host: string;\n readonly posture: TinyCloudPosture;\n readonly operatorType?: OperationOperatorType;\n readonly principalDid?: string;\n readonly sessionDid?: string;\n readonly ownerDid?: string;\n readonly space?: string;\n}\n\n/**\n * The profile foundation constructs this context for every invocation. The\n * invocation target never supplies an execution context or runtime object.\n */\nexport interface OperationContext {\n readonly summary: OperationContextSummary;\n /** Present for every normal invocation; optional for focused definition tests. */\n readonly runtime?: OperationRuntime;\n}\n\n/**\n * Per-invocation authority that is constructed inside operations. It is never\n * caller supplied and it is deliberately absent from result envelopes.\n */\nexport interface OperationRuntime {\n readonly node: unknown;\n readonly granted: readonly CapabilityRequirement[];\n /** Diagnostics gathered while building the runtime, attached to the result. */\n readonly warnings?: readonly OperationWarning[];\n /**\n * Internal, never projected. Whether the live identity is a local owner key\n * (an explicit key, or a profile `isLocalOwnerProfile` accepts). Set by every\n * runtime this package builds; optional only for focused definition tests.\n */\n readonly localOwner?: boolean;\n /** Internal, never projected: the reasons stored grants were skipped during replay. */\n readonly skippedGrantReasons?: readonly StoredGrantSkipReason[];\n /** Successful stored-grant replays, for value-free hosted call diagnostics. */\n readonly replayedGrants?: number;\n}\n\n/**\n * Why a stored grant was not used. A fixed vocabulary: diagnostics never\n * carry exception text, node responses, URLs or other stored transport data.\n * `unbound`: the record has no valid request binding where one is required;\n * `outside_request`: its signed authority exceeds its binding;\n * `activation_unavailable`: the node could not be reached, timed out, was\n * rate limited or failed (transport, 408, 429, 5xx) while activating it;\n * `activation_unauthenticated`: the node answered 401 while activating it.\n */\nexport const STORED_GRANT_SKIP_REASONS = [\n \"malformed\",\n \"unbound\",\n \"outside_request\",\n \"host_mismatch\",\n \"expired\",\n \"cid_mismatch\",\n \"authority_invalid\",\n \"proof_missing\",\n \"proof_invalid\",\n \"owner_mismatch\",\n \"audience_mismatch\",\n \"resources_mismatch\",\n \"unsupported\",\n \"activation_rejected\",\n \"activation_unavailable\",\n \"activation_unauthenticated\",\n \"invalid\",\n] as const;\n\nexport type StoredGrantSkipReason = typeof STORED_GRANT_SKIP_REASONS[number];\n\n/**\n * The string form of every delegation CID TinyCloud computes: CIDv1, raw\n * codec, BLAKE3-256 multihash, base32. A published `grantCid` also has to be\n * the CID recomputed from the stored authorization bytes.\n */\nexport const DELEGATION_CID_PATTERN = \"^bafkr4i[a-z2-7]{52}$\";\n\n/**\n * A non-fatal diagnostic attached to a result. `STORED_GRANT_SKIPPED`: a\n * stored grant failed validation and gave no authority; `grantCid` is present\n * only when the stored CID is the CID of the stored authorization bytes.\n */\nexport interface OperationWarning {\n readonly code: \"STORED_GRANT_SKIPPED\";\n readonly reason: StoredGrantSkipReason;\n readonly grantCid?: string;\n}\n\n/** Context given to planners and handlers after runtime authentication. */\nexport interface RuntimeOperationContext extends OperationContext {\n readonly runtime: OperationRuntime;\n}\n\nexport type AuthorityPlanningContext = RuntimeOperationContext;\n\n/**\n * Capability details are finalized by the authority/artifacts increment. The\n * kernel intentionally preserves them as canonical operation-owned data.\n */\nexport type CapabilityRequirement = Readonly<Record<string, unknown>>;\n\nexport interface PermissionRequestArtifact {\n readonly requestId: string;\n readonly [key: string]: unknown;\n}\n\nexport interface ApprovalAction {\n readonly kind: \"openkey\";\n readonly requestId: string;\n readonly url?: string;\n readonly fallback: string;\n}\n\nexport interface SetupAction {\n readonly kind: string;\n readonly [key: string]: unknown;\n}\n\nexport interface OperationRef {\n readonly operationId: OperationId;\n readonly operationVersion: number;\n}\n\nexport interface RetryDescriptor extends OperationRef {\n readonly inputDigest: string;\n readonly safeInput?: Readonly<Record<string, unknown>>;\n readonly requiresCallerInput: boolean;\n}\n\nexport interface OperationSensitivity {\n readonly input: readonly JsonPointer[];\n readonly output: readonly JsonPointer[];\n}\n\n/**\n * An inspection operation may read safe local profile state without creating a\n * node or authenticating. An authenticated operation gets a runtime that may\n * sign in. A restored operation gets a runtime built only from the profile's\n * stored session: it never signs in, migrates, takes a profile lock or writes\n * profile state.\n */\nexport type OperationRuntimeRequirement = \"authenticated\" | \"inspection\" | \"restored\";\n\nexport interface OperationExposure {\n readonly cli: SurfaceDisposition;\n readonly mcp: SurfaceDisposition;\n readonly skill: SurfaceDisposition;\n readonly docs: SurfaceDisposition;\n}\n\nexport interface OperationOkResult<O> {\n readonly status: \"ok\";\n readonly operation: OperationRef;\n readonly context: OperationContextSummary;\n readonly output: O;\n readonly warnings?: readonly OperationWarning[];\n}\n\nexport interface OperationAuthorityRequiredResult {\n readonly status: \"authority_required\";\n readonly operation: OperationRef;\n readonly context: OperationContextSummary;\n readonly missing: readonly CapabilityRequirement[];\n readonly request: PermissionRequestArtifact;\n readonly approval: ApprovalAction;\n readonly retry: RetryDescriptor;\n readonly warnings?: readonly OperationWarning[];\n}\n\nexport interface OperationSetupRequiredResult {\n readonly status: \"setup_required\";\n readonly operation: OperationRef;\n readonly context: OperationContextSummary;\n readonly setup: SetupAction;\n readonly retry: RetryDescriptor;\n readonly warnings?: readonly OperationWarning[];\n}\n\nexport interface OperationErrorResult {\n readonly status: \"error\";\n readonly operation: OperationRef;\n readonly context: OperationContextSummary;\n readonly error: OperationError;\n readonly warnings?: readonly OperationWarning[];\n}\n\nexport type OperationResult<O> =\n | OperationOkResult<O>\n | OperationAuthorityRequiredResult\n | OperationSetupRequiredResult\n | OperationErrorResult;\n\n/**\n * Handler outcomes intentionally omit invocation-owned operation, context, and\n * retry metadata. The kernel attaches those values after validation.\n */\nexport type OperationExecutionOutcome<O> =\n | Readonly<{ status: \"ok\"; output: O }>\n | Readonly<{\n status: \"authority_required\";\n missing: readonly CapabilityRequirement[];\n request: PermissionRequestArtifact;\n approval: ApprovalAction;\n requiresCallerInput?: boolean;\n }>\n | Readonly<{\n status: \"setup_required\";\n setup: SetupAction;\n requiresCallerInput?: boolean;\n }>\n | Readonly<{ status: \"error\"; error: OperationError }>;\n\n/**\n * Internal registry material. It is intentionally never re-exported by the\n * package root: projections can execute only by operation id and version.\n */\nexport interface OperationDefinition<I, O> {\n readonly id: OperationId;\n readonly version: 1;\n readonly title: string;\n readonly description: string;\n readonly input: z.ZodType<I>;\n readonly output: z.ZodType<O>;\n readonly effects: readonly OperationEffect[];\n readonly runtime: OperationRuntimeRequirement;\n readonly postures: readonly TinyCloudPosture[];\n readonly exposure: OperationExposure;\n readonly sensitivity: OperationSensitivity;\n /** A definition may give malformed transport artifacts a stable error code. */\n readonly invalidInputErrorCode?: OperationErrorCode;\n /**\n * Response deadline. When set, `invokeOperation` returns a retryable\n * `NODE_UNREACHABLE` if the invocation has not finished within it; work\n * already started is abandoned, not cancelled.\n */\n readonly deadlineMs?: number;\n readonly authority: (\n context: AuthorityPlanningContext,\n input: I,\n ) => Promise<readonly CapabilityRequirement[]>;\n readonly execute: (\n context: OperationContext,\n input: I,\n ) => Promise<OperationExecutionOutcome<O>>;\n}\n\n/**\n * The only caller-controlled invocation settings. `privateKey` is a CLI-only,\n * nonpersistable override; it is deliberately excluded from result metadata,\n * retry descriptors, diagnostics, and all state written by this package.\n */\nexport interface InvocationTarget {\n readonly profile?: string;\n readonly host?: string;\n /**\n * Absolute home directory used for this invocation's TinyCloud profile\n * state. Hosted projections set this per authenticated principal so\n * concurrent tenants never share process-global TC_HOME state.\n */\n readonly stateRoot?: string;\n readonly allowOwnerProfile?: boolean;\n readonly privateKey?: string;\n}\n\n/**\n * Copy only the identity fields allowed in a public result. Keeping this\n * projection centralized prevents a profile/runtime implementation from\n * accidentally smuggling execution material into an envelope.\n */\nexport function safeOperationContextSummary(\n summary: OperationContextSummary,\n): OperationContextSummary {\n return {\n profile: summary.profile,\n host: summary.host,\n posture: summary.posture,\n ...(summary.operatorType === undefined\n ? {}\n : { operatorType: summary.operatorType }),\n ...(summary.principalDid === undefined\n ? {}\n : { principalDid: summary.principalDid }),\n ...(summary.sessionDid === undefined ? {} : { sessionDid: summary.sessionDid }),\n ...(summary.ownerDid === undefined ? {} : { ownerDid: summary.ownerDid }),\n ...(summary.space === undefined ? {} : { space: summary.space }),\n };\n}\n","export const OPERATION_ERROR_CODES = [\n \"INPUT_INVALID\",\n \"OPERATION_NOT_FOUND\",\n \"OPERATION_VERSION_UNSUPPORTED\",\n \"PROFILE_NOT_FOUND\",\n \"PROFILE_POSTURE_NOT_ALLOWED\",\n \"PROFILE_OWNER_OPT_IN_REQUIRED\",\n \"PROFILE_MIGRATION_REQUIRED\",\n \"AUTH_REQUIRED\",\n \"PERMISSION_DENIED\",\n \"SESSION_NOT_FOUND\",\n \"PERMISSION_HINT_INVALID\",\n \"DELEGATION_ARTIFACT_INVALID\",\n \"DELEGATION_EXPIRED\",\n \"DELEGATION_AUDIENCE_MISMATCH\",\n \"DELEGATION_HOST_MISMATCH\",\n \"DELEGATION_REJECTED\",\n \"ENCRYPTION_NETWORK_UNRESOLVED\",\n \"NODE_UNREACHABLE\",\n \"NODE_ERROR\",\n \"STORAGE_QUOTA_EXCEEDED\",\n \"KV_NOT_FOUND\",\n \"KV_PRECONDITION_FAILED\",\n \"KV_CONFLICT\",\n \"KV_RESPONSE_TOO_LARGE\",\n \"SQL_QUERY_INVALID\",\n \"SQL_EXECUTION_FAILED\",\n \"SQL_RESULT_LIMIT_EXCEEDED\",\n \"SQL_VALUE_UNSAFE\",\n \"SECRET_READ_FAILED\",\n \"SECRET_DECRYPT_FAILED\",\n \"OUTPUT_INVALID\",\n \"INTERNAL_ERROR\",\n] as const;\n\nexport type OperationErrorCode = (typeof OPERATION_ERROR_CODES)[number];\n\nexport interface OperationError {\n readonly code: OperationErrorCode;\n readonly message: string;\n readonly retryable: boolean;\n readonly details?: Readonly<Record<string, unknown>>;\n}\n\nexport function operationError(\n code: OperationErrorCode,\n message: string,\n options: Readonly<{\n retryable?: boolean;\n details?: Readonly<Record<string, unknown>>;\n }> = {},\n): OperationError {\n return {\n code,\n message,\n retryable: options.retryable ?? false,\n ...(options.details === undefined ? {} : { details: options.details }),\n };\n}\n\nconst MANAGE_STORAGE = \"Do not retry: the owner must free up space or upgrade at https://account.tinycloud.xyz/storage.\";\n\n/**\n * A write the node refused because the owner's TinyCloud storage is full\n * (SDK `STORAGE_QUOTA_EXCEEDED`, HTTP 402) or too small for this write\n * (`STORAGE_LIMIT_REACHED`, HTTP 413). Nothing was written, so the outcome is\n * known, and it stays refused until the owner acts, so it is never retryable.\n * The SDK's account totals travel as details; per-space numbers never do.\n */\nexport function storageFullOperationError(error: unknown): OperationError | undefined {\n if (typeof error !== \"object\" || error === null) return undefined;\n const { code, meta } = error as { code?: unknown; meta?: unknown };\n if (code !== \"STORAGE_QUOTA_EXCEEDED\" && code !== \"STORAGE_LIMIT_REACHED\") return undefined;\n const account = typeof meta === \"object\" && meta !== null ? (meta as { account?: unknown }).account : undefined;\n return operationError(\n \"STORAGE_QUOTA_EXCEEDED\",\n code === \"STORAGE_LIMIT_REACHED\"\n ? `This write is larger than the TinyCloud storage left, so nothing was written. Reading still works. ${MANAGE_STORAGE}`\n : `TinyCloud storage is full, so nothing was written. Reading still works. ${MANAGE_STORAGE}`,\n { retryable: false, ...(account === undefined ? {} : { details: { account } }) },\n );\n}\n\n/**\n * The SDK's node retry codes (`NODE_BUSY`, `REQUEST_TIMEOUT`, `OUTCOME_UNKNOWN`) as `NODE_ERROR` with the\n * SDK's own verdict: a busy node or a deadline hit before any write began applied nothing, so a retry is\n * safe; `OUTCOME_UNKNOWN` may have applied a write, so it is never retryable and says to read back first.\n */\nexport function nodeRetryOperationError(error: unknown): OperationError | undefined {\n const code = typeof error === \"object\" && error !== null && \"code\" in error ? error.code : undefined;\n if (code === \"OUTCOME_UNKNOWN\") {\n return operationError(\n \"NODE_ERROR\",\n \"The TinyCloud node may have applied the write; read the data back before retrying.\",\n { retryable: false },\n );\n }\n if (code !== \"NODE_BUSY\" && code !== \"REQUEST_TIMEOUT\") return undefined;\n return operationError(\"NODE_ERROR\", \"The TinyCloud node was busy and applied nothing; try again shortly.\", { retryable: true });\n}\n\n/**\n * Internal operations and profile resolution use this for expected failures.\n * Generic exceptions are never exposed at the projection boundary.\n */\nexport class OperationInvocationError extends Error {\n public readonly operationError: OperationError;\n\n constructor(operationError: OperationError) {\n super(operationError.message);\n this.name = \"OperationInvocationError\";\n this.operationError = operationError;\n }\n}\n\nexport function isOperationErrorCode(value: unknown): value is OperationErrorCode {\n return typeof value === \"string\" && OPERATION_ERROR_CODES.includes(value as OperationErrorCode);\n}\n\nexport function internalOperationError(): OperationError {\n return operationError(\n \"INTERNAL_ERROR\",\n \"The operation could not be completed.\",\n );\n}\n\nexport function sanitizeThrownOperationError(error: unknown): OperationError {\n if (error instanceof OperationInvocationError) {\n return sanitizeOperationError(error.operationError);\n }\n\n return internalOperationError();\n}\n\nexport function sanitizeOperationError(error: unknown): OperationError {\n if (\n typeof error !== \"object\" ||\n error === null ||\n !isOperationErrorCode((error as { code?: unknown }).code) ||\n typeof (error as { message?: unknown }).message !== \"string\" ||\n typeof (error as { retryable?: unknown }).retryable !== \"boolean\"\n ) {\n return internalOperationError();\n }\n\n const candidate = error as OperationError;\n return operationError(candidate.code, candidate.message, {\n retryable: candidate.retryable,\n ...(candidate.details === undefined ? {} : { details: candidate.details }),\n });\n}\n","import { basename } from \"node:path\";\n\n/**\n * The command name hints and messages tell users to run. The CLI installs as\n * both `tc` and `tinycloud` (Ubuntu's iproute2 ships `/usr/sbin/tc`), so the\n * name follows the invoked bin. Any other host process, such as the MCP server,\n * gets `tc`.\n */\nexport const CLI_NAME = basename(process.argv[1] ?? \"\") === \"tinycloud\" ? \"tinycloud\" : \"tc\";\n","import { authorizationVerdictOf } from \"@tinycloud/sdk-services\";\nimport { CLI_NAME } from \"./cli-name.js\";\n\n/** Cause links read for the node's refusal; the SDK wraps it, so it is rarely the outermost error. */\nconst MAX_CAUSE_DEPTH = 8;\n/**\n * The node's whole 401 body for a revoked chain (`delegation-revoked: <cid>`, or\n * `delegation-ancestor-revoked: ancestor=<cid> invoked=<cid>`; older nodes prefix `Invalid invocation: `),\n * as it ends an SDK error message (`<what failed>: 401 - <body>`). The match starts at the `401 - ` separator\n * and runs to the end of the message, and only letters and digits may follow the error kind (the CIDs are not\n * checked beyond that), so a key or path in another 401 body (`Unauthorized Action: <space>/kv/…/delegation-revoked/ / …`)\n * or any other text that mentions \"revoked\" does not match.\n */\nconst REVOKED_GRANT_BODY =\n /\\b401 - (?:Invalid invocation: )?delegation-(?:revoked: [A-Za-z0-9]+|ancestor-revoked: ancestor=[A-Za-z0-9]+ invoked=[A-Za-z0-9]+)\\s*$/;\n/** The KV service's own typed codes for the same two bodies. */\nconst REVOKED_GRANT_CODES: ReadonlySet<unknown> = new Set([\"AUTH_DELEGATION_REVOKED\", \"AUTH_DELEGATION_ANCESTOR_REVOKED\"]);\n\nexport const REVOKED_GRANT_MESSAGE =\n `This grant was revoked. Ask the owner for a new grant (\\`${CLI_NAME} auth request ...\\`).`;\n\n/**\n * Whether a node refusal says the presented grant (or one it was derived from) was revoked.\n * Status alone cannot tell this from a stale session, so it also needs the typed code or the node's own\n * `delegation-revoked` / `delegation-ancestor-revoked` 401 body; any other 401 keeps its meaning.\n */\nexport function isRevokedGrantRefusal(error: unknown): boolean {\n if (authorizationVerdictOf(error) !== \"unauthenticated\") return false;\n const seen = new Set<unknown>();\n let node: unknown = error;\n for (let depth = 0; depth < MAX_CAUSE_DEPTH && typeof node === \"object\" && node !== null && !seen.has(node); depth += 1) {\n seen.add(node);\n const { code, message, cause } = node as { code?: unknown; message?: unknown; cause?: unknown };\n if (REVOKED_GRANT_CODES.has(code)) return true;\n if (typeof message === \"string\" && REVOKED_GRANT_BODY.test(message)) return true;\n node = cause;\n }\n return false;\n}\n","import { authorizationVerdictOf, validatedCapabilityOf } from \"@tinycloud/sdk-services\";\n\nimport { DELEGATION_CID_PATTERN } from \"./contract.js\";\nimport type { OperationExecutionOutcome } from \"./contract.js\";\nimport { operationError } from \"./errors.js\";\nimport { isRevokedGrantRefusal, REVOKED_GRANT_MESSAGE } from \"./revoked-grant.js\";\n\nexport type NodeRevocation =\n | Readonly<{ reason: \"delegation_revoked\"; revokedCid: string }>\n | Readonly<{ reason: \"delegation_ancestor_revoked\"; revokedCid: string; ancestorCid: string }>;\n\nconst DELEGATION_CID = new RegExp(DELEGATION_CID_PATTERN);\nconst NODE_REVOCATION_BODY =\n /^(?:Invalid invocation: )?delegation-(?:(revoked): ([A-Za-z0-9]+)|(ancestor-revoked): ancestor=([A-Za-z0-9]+) invoked=([A-Za-z0-9]+))$/;\n\n/**\n * Classify a definite node refusal from a KV SDK service error. The SDK puts\n * the entire node response after its `: 401 - ` separator; never search for a\n * marker within arbitrary error prose or accept a different HTTP status.\n */\nexport function nodeRevocationOf(error: unknown): NodeRevocation | undefined {\n if (\n typeof error !== \"object\" || error === null ||\n !(\"service\" in error) || error.service !== \"kv\" ||\n !(\"code\" in error) ||\n (error.code !== \"AUTH_UNAUTHORIZED\" && error.code !== \"AUTH_DELEGATION_REVOKED\" && error.code !== \"AUTH_DELEGATION_ANCESTOR_REVOKED\") ||\n !(\"message\" in error) || typeof error.message !== \"string\" ||\n !(\"meta\" in error) || typeof error.meta !== \"object\" || error.meta === null ||\n !(\"status\" in error.meta) || error.meta.status !== 401 ||\n authorizationVerdictOf(error) !== \"unauthenticated\"\n ) return undefined;\n const { code, message } = error;\n\n const separator = \": 401 - \";\n const start = message.indexOf(separator);\n if (start < 0) return undefined;\n const body = message.slice(start + separator.length);\n const match = NODE_REVOCATION_BODY.exec(body);\n if (match === null || match[0] !== body) return undefined;\n if (match[1] !== undefined && code !== \"AUTH_DELEGATION_ANCESTOR_REVOKED\") {\n const revokedCid = match[2]!;\n return DELEGATION_CID.test(revokedCid) ? { reason: \"delegation_revoked\", revokedCid } : undefined;\n }\n if (match[3] !== undefined && code !== \"AUTH_DELEGATION_REVOKED\") {\n const ancestorCid = match[4]!;\n const revokedCid = match[5]!;\n return DELEGATION_CID.test(ancestorCid) && DELEGATION_CID.test(revokedCid)\n ? { reason: \"delegation_ancestor_revoked\", revokedCid, ancestorCid }\n : undefined;\n }\n return undefined;\n}\n\n/**\n * The TC-634 split of a node refusal: a 403, or a 401 that names a validated\n * missing capability, is a permission denial; any other 401 means the caller\n * must authenticate again. `undefined` when the error is not a refusal.\n */\nexport function kvRefusal(error: unknown): \"permission_denied\" | \"auth_required\" | undefined {\n const verdict = authorizationVerdictOf(error);\n if (verdict === \"unauthenticated\") {\n return validatedCapabilityOf(error) === undefined ? \"auth_required\" : \"permission_denied\";\n }\n return verdict === \"forbidden\" ? \"permission_denied\" : undefined;\n}\n\n/** The canonical error for a node refusal, or `undefined` when `error` is not one. */\nexport function authorizationFailure(error: unknown, action: string): OperationExecutionOutcome<never> | undefined {\n switch (kvRefusal(error)) {\n case \"auth_required\": {\n const revocation = nodeRevocationOf(error);\n return {\n status: \"error\",\n error: operationError(\"AUTH_REQUIRED\", isRevokedGrantRefusal(error)\n ? REVOKED_GRANT_MESSAGE\n : `Authentication is required to ${action}.`, {\n ...(revocation === undefined ? {} : { details: revocation }),\n }),\n };\n }\n case \"permission_denied\":\n return { status: \"error\", error: operationError(\"PERMISSION_DENIED\", `Permission is denied to ${action}.`) };\n case undefined:\n return undefined;\n }\n}\n","export type SignedObjectErrorCode =\n | \"schema-invalid\"\n | \"canonicalization-mismatch\"\n | \"digest-mismatch\"\n | \"id-mismatch\"\n | \"signing-key-binding\"\n | \"signature-material-invalid\"\n | \"signature-suite-unsupported\"\n | \"signature-invalid\";\n\nexport class SignedObjectProfileError extends Error {\n public readonly code: SignedObjectErrorCode;\n\n constructor(code: SignedObjectErrorCode, message: string) {\n super(message);\n this.name = \"SignedObjectProfileError\";\n this.code = code;\n }\n}\n\nexport class SignedObjectSchemaError extends SignedObjectProfileError {\n constructor(message: string) {\n super(\"schema-invalid\", message);\n this.name = \"SignedObjectSchemaError\";\n }\n}\n\nexport class SignedObjectCanonicalizationError extends SignedObjectProfileError {\n constructor(message: string) {\n super(\"canonicalization-mismatch\", message);\n this.name = \"SignedObjectCanonicalizationError\";\n }\n}\n\nexport class SignedObjectDigestError extends SignedObjectProfileError {\n constructor(message: string) {\n super(\"digest-mismatch\", message);\n this.name = \"SignedObjectDigestError\";\n }\n}\n\nexport class SignedObjectIdError extends SignedObjectProfileError {\n constructor(message: string) {\n super(\"id-mismatch\", message);\n this.name = \"SignedObjectIdError\";\n }\n}\n\nexport class SigningKeyBindingError extends SignedObjectProfileError {\n constructor(message: string) {\n super(\"signing-key-binding\", message);\n this.name = \"SigningKeyBindingError\";\n }\n}\n\nexport class SignatureMaterialError extends SignedObjectProfileError {\n constructor(message: string) {\n super(\"signature-material-invalid\", message);\n this.name = \"SignatureMaterialError\";\n }\n}\n\nexport class UnsupportedSignatureSuiteError extends SignedObjectProfileError {\n constructor(message: string) {\n super(\"signature-suite-unsupported\", message);\n this.name = \"UnsupportedSignatureSuiteError\";\n }\n}\n\nexport class SignatureVerificationError extends SignedObjectProfileError {\n constructor(message: string) {\n super(\"signature-invalid\", message);\n this.name = \"SignatureVerificationError\";\n }\n}\n\nexport function toSignedObjectError(error: unknown): SignedObjectProfileError {\n if (error instanceof SignedObjectProfileError) {\n return error;\n }\n return new SignedObjectProfileError(\n \"schema-invalid\",\n error instanceof Error ? error.message : String(error),\n );\n}\n","import { SignedObjectCanonicalizationError } from \"./errors\";\n\nexport type JsonValue =\n | null\n | boolean\n | number\n | string\n | JsonValue[]\n | { readonly [key: string]: JsonValue };\n\ntype MutableJsonObject = { [key: string]: JsonValue };\nconst objectHasOwn: (object: object, propertyKey: PropertyKey) => boolean =\n (Object as ObjectConstructor & {\n hasOwn?: (object: object, propertyKey: PropertyKey) => boolean;\n }).hasOwn ??\n (Object.prototype.hasOwnProperty.call.bind(\n Object.prototype.hasOwnProperty,\n ) as (object: object, propertyKey: PropertyKey) => boolean);\n\n/**\n * RFC 8785 JSON Canonicalization Scheme encoder for the signed-object profile.\n *\n * This local encoder is intentionally stricter than JSON.stringify:\n * it rejects non-plain JSON inputs, sparse arrays, undefined/functions/symbols,\n * non-finite numbers, BigInt, dangerous prototype keys, and lone surrogates.\n * Object keys are sorted lexicographically by their raw UTF-16 code units,\n * as required by RFC 8785 section 3.2.3, before serialization.\n */\nexport function jcsCanonicalize(input: unknown): string {\n return serialize(normalizeJson(input, \"$\"));\n}\n\nexport function normalizeJson(input: unknown, path = \"$\"): JsonValue {\n if (input === null) {\n return null;\n }\n\n switch (typeof input) {\n case \"boolean\":\n return input;\n case \"number\":\n if (!Number.isFinite(input)) {\n throw new SignedObjectCanonicalizationError(\n `${path} must be a finite JSON number`,\n );\n }\n return input;\n case \"string\":\n assertUnicodeScalarString(input, path);\n return input;\n case \"object\":\n return normalizeJsonObjectOrArray(input, path);\n case \"bigint\":\n case \"function\":\n case \"symbol\":\n case \"undefined\":\n default:\n throw new SignedObjectCanonicalizationError(\n `${path} is not a JSON value`,\n );\n }\n}\n\nexport function serialize(value: JsonValue): string {\n if (value === null) {\n return \"null\";\n }\n\n switch (typeof value) {\n case \"boolean\":\n return value ? \"true\" : \"false\";\n case \"number\": {\n const encoded = JSON.stringify(value);\n if (encoded === undefined) {\n throw new SignedObjectCanonicalizationError(\n \"number could not be serialized as JSON\",\n );\n }\n return encoded;\n }\n case \"string\":\n return JSON.stringify(value);\n case \"object\":\n if (Array.isArray(value)) {\n return `[${value.map((item) => serialize(item)).join(\",\")}]`;\n }\n return serializeObject(value);\n default:\n throw new SignedObjectCanonicalizationError(\n `unsupported JSON value type ${typeof value}`,\n );\n }\n}\n\nfunction normalizeJsonObjectOrArray(input: object, path: string): JsonValue {\n assertNoSymbolKeys(input, path);\n\n if (Array.isArray(input)) {\n for (const key of Object.getOwnPropertyNames(input)) {\n if (key === \"length\") {\n continue;\n }\n if (!isArrayIndexKey(key, input.length)) {\n throw new SignedObjectCanonicalizationError(\n `${path}.${key} is not allowed on a JSON array`,\n );\n }\n const descriptor = Object.getOwnPropertyDescriptor(input, key);\n if (descriptor?.enumerable !== true) {\n throw new SignedObjectCanonicalizationError(\n `${path}[${key}] must be an enumerable JSON array item`,\n );\n }\n if (!(\"value\" in descriptor)) {\n throw new SignedObjectCanonicalizationError(\n `${path}[${key}] must be a JSON data property`,\n );\n }\n }\n const output: JsonValue[] = [];\n for (let index = 0; index < input.length; index++) {\n if (!objectHasOwn(input, index)) {\n throw new SignedObjectCanonicalizationError(\n `${path}[${index}] must not be a sparse array hole`,\n );\n }\n output.push(normalizeJson(input[index], `${path}[${index}]`));\n }\n return output;\n }\n\n const proto = Object.getPrototypeOf(input);\n if (proto !== Object.prototype && proto !== null) {\n throw new SignedObjectCanonicalizationError(\n `${path} must be a plain JSON object`,\n );\n }\n\n const output = Object.create(null) as MutableJsonObject;\n for (const key of Object.getOwnPropertyNames(input)) {\n assertUnicodeScalarString(key, `${path} key`);\n if (key === \"__proto__\" || key === \"constructor\") {\n throw new SignedObjectCanonicalizationError(\n `${path}.${key} is not allowed`,\n );\n }\n const descriptor = Object.getOwnPropertyDescriptor(input, key);\n if (descriptor?.enumerable !== true) {\n throw new SignedObjectCanonicalizationError(\n `${path}.${key} must be an enumerable JSON object property`,\n );\n }\n if (!(\"value\" in descriptor)) {\n throw new SignedObjectCanonicalizationError(\n `${path}.${key} must be a JSON data property`,\n );\n }\n const value = descriptor.value;\n output[key] = normalizeJson(value, `${path}.${key}`);\n }\n return output;\n}\n\nfunction assertNoSymbolKeys(input: object, path: string): void {\n if (Object.getOwnPropertySymbols(input).length > 0) {\n throw new SignedObjectCanonicalizationError(\n `${path} must not have symbol properties`,\n );\n }\n}\n\nfunction isArrayIndexKey(key: string, length: number): boolean {\n if (!/^(0|[1-9]\\d*)$/.test(key)) {\n return false;\n }\n const index = Number(key);\n return Number.isSafeInteger(index) && index >= 0 && index < length;\n}\n\nfunction serializeObject(value: { readonly [key: string]: JsonValue }): string {\n const keys = Object.keys(value).sort(compareUtf16CodeUnits);\n const parts = keys.map((key) => `${JSON.stringify(key)}:${serialize(value[key])}`);\n return `{${parts.join(\",\")}}`;\n}\n\nfunction compareUtf16CodeUnits(a: string, b: string): number {\n const max = Math.min(a.length, b.length);\n for (let index = 0; index < max; index++) {\n const leftCodeUnit = a.charCodeAt(index);\n const rightCodeUnit = b.charCodeAt(index);\n if (leftCodeUnit !== rightCodeUnit) {\n return leftCodeUnit - rightCodeUnit;\n }\n }\n return a.length - b.length;\n}\n\nfunction assertUnicodeScalarString(value: string, path: string): void {\n for (let index = 0; index < value.length; index++) {\n const code = value.charCodeAt(index);\n if (code >= 0xd800 && code <= 0xdbff) {\n const next = value.charCodeAt(index + 1);\n if (Number.isNaN(next) || next < 0xdc00 || next > 0xdfff) {\n throw new SignedObjectCanonicalizationError(\n `${path} contains a lone high surrogate`,\n );\n }\n index++;\n continue;\n }\n if (code >= 0xdc00 && code <= 0xdfff) {\n throw new SignedObjectCanonicalizationError(\n `${path} contains a lone low surrogate`,\n );\n }\n }\n}\n","import {\n canonicalizeRecapCaveats,\n isCapabilitySubset,\n type PermissionEntry,\n} from \"@tinycloud/sdk-core\";\n\n/**\n * A capability in the canonical form persisted in a v1 request artifact.\n *\n * The SDK remains the semantic authority for containment. This module only\n * establishes deterministic exact identity for persistence and display.\n */\nexport type CanonicalPermissionEntry = Readonly<PermissionEntry>;\n\nexport interface AuthorityEvaluation {\n readonly satisfied: boolean;\n readonly missing: readonly CanonicalPermissionEntry[];\n}\n\n/**\n * Operation-owned space resolution. The SDK's general capability evaluator\n * intentionally preserves its historical short/full-space compatibility; the\n * operation kernel supplies this narrower resolver when a space identifies a\n * remote owner's resource.\n */\nexport type OperationSpaceResolver = (space: string) => string;\n\nconst EXACT_PERMISSION_KEYS = new Set([\n \"service\",\n \"space\",\n \"path\",\n \"actions\",\n \"skipPrefix\",\n \"expiry\",\n \"description\",\n]);\n\n/**\n * Validate requirements that can be represented without semantic broadening in\n * a v1 artifact. Caveated authority is intentionally rejected: the v1 request\n * schema and grant path cannot preserve those signed branches end-to-end.\n */\nexport function validateExactCapabilities(value: unknown): PermissionEntry[] | undefined {\n if (!Array.isArray(value)) return undefined;\n\n const permissions: PermissionEntry[] = [];\n for (const candidate of value) {\n if (!isExactPermission(candidate)) return undefined;\n permissions.push(candidate);\n }\n return canonicalizeCapabilities(permissions);\n}\n\n/**\n * Evaluates required authority with the SDK's public subset primitive.\n *\n * In particular, this intentionally does not implement matching rules or\n * manufacture wildcards. A wildcard can cover a requirement only when it was\n * already present in the supplied granted authority and the SDK says so.\n */\nexport function evaluateAuthority(\n granted: readonly PermissionEntry[],\n required: readonly PermissionEntry[],\n): AuthorityEvaluation {\n const canonicalGranted = canonicalizeCapabilities(granted);\n const canonicalRequired = canonicalizeCapabilities(required);\n const result = isCapabilitySubset(canonicalRequired, canonicalGranted);\n\n return {\n satisfied: result.subset,\n missing: canonicalizeCapabilities(result.missing),\n };\n}\n\n/**\n * Evaluate operation capabilities without allowing two owners' same-named\n * spaces to compare equal. Path and action containment still come from the\n * SDK primitive; only the operation space identity is made owner-exact.\n *\n * Each requirement is judged against every grant in its owner-exact space\n * together: a verified SIWE ReCap reports one entry per signed action, so\n * `get` and `list` on one path arrive as two grants.\n */\nexport function evaluateOperationAuthority(\n granted: readonly PermissionEntry[],\n required: readonly PermissionEntry[],\n resolveSpace?: OperationSpaceResolver,\n): AuthorityEvaluation {\n const canonicalGranted = canonicalizeOperationCapabilities(granted, resolveSpace);\n const canonicalRequired = canonicalizeOperationCapabilities(required, resolveSpace);\n const missing: CanonicalPermissionEntry[] = [];\n\n for (const requested of canonicalRequired) {\n const sameScope = canonicalGranted.filter((candidate) =>\n candidate.service === requested.service &&\n operationSpacesEqual(candidate.space, requested.space)\n );\n if (!isCapabilitySubset([requested], sameScope).subset) missing.push(requested);\n }\n\n return {\n satisfied: missing.length === 0,\n missing: canonicalizeCapabilities(missing),\n };\n}\n\n/**\n * Whether a delegation's signed capabilities stay inside the stored request\n * it answers. `tinycloud.auth.import` and runtime replay share this check, so\n * a delegation import accepts is never one replay discards.\n */\nexport function delegationWithinRequest(\n requested: readonly PermissionEntry[],\n effective: readonly PermissionEntry[],\n resolveSpace?: OperationSpaceResolver,\n): boolean {\n return requested.length > 0 &&\n evaluateOperationAuthority(requested, canonicalizeCapabilities(effective), resolveSpace).satisfied;\n}\n\n/**\n * Validate a runtime hint as an exact member of the registered plan. Runtime\n * nodes may report the one phase that failed, but they may not introduce a\n * broader, differently-owned, caveated, or otherwise new capability.\n */\nexport function isExactCapabilityMemberSubset(\n hinted: readonly PermissionEntry[],\n planned: readonly PermissionEntry[],\n resolveSpace?: OperationSpaceResolver,\n): boolean {\n const canonicalHinted = canonicalizeOperationCapabilities(hinted, resolveSpace);\n const canonicalPlanned = canonicalizeOperationCapabilities(planned, resolveSpace);\n return canonicalHinted.length > 0 && canonicalHinted.every((hint) => canonicalPlanned.some((plan) =>\n JSON.stringify(hint) === JSON.stringify(plan),\n ));\n}\n\n/** Canonical identity used by operation preflight, hints, and artifacts. */\nexport function canonicalizeOperationCapabilities(\n permissions: readonly PermissionEntry[],\n resolveSpace?: OperationSpaceResolver,\n): CanonicalPermissionEntry[] {\n return canonicalizeCapabilities(permissions.map((permission) => {\n const canonicalPath = canonicalizeOperationPath(permission);\n if (permission.space === undefined || resolveSpace === undefined) {\n return canonicalPath;\n }\n try {\n return { ...canonicalPath, space: resolveSpace(permission.space) };\n } catch {\n // An unresolvable identity remains a non-matching literal. It must never\n // be widened to a wildcard or to another authenticated owner.\n return canonicalPath;\n }\n }));\n}\n\nfunction canonicalizeOperationPath(permission: PermissionEntry): PermissionEntry {\n if (permission.service !== \"tinycloud.encryption\") return permission;\n const match = permission.path.match(\n /^(urn:tinycloud:encryption:did:pkh:eip155:\\d+:)(0x[0-9a-fA-F]{40})(:.*)$/,\n );\n if (!match) return permission;\n return { ...permission, path: `${match[1]}${match[2]!.toLowerCase()}${match[3]}` };\n}\n\nfunction operationSpacesEqual(\n granted: string | undefined,\n requested: string | undefined,\n): boolean {\n return granted === requested;\n}\n\n/** Returns a stable exact identity for one capability without containment. */\nexport function permissionIdentity(permission: PermissionEntry): string {\n return JSON.stringify(canonicalizeCapability(permission));\n}\n\n/**\n * Sorts actions and entries and removes only exactly-identical capabilities.\n * It does not coalesce paths, services, spaces, or actions into a broader\n * capability.\n */\nexport function canonicalizeCapabilities(\n permissions: readonly PermissionEntry[],\n): CanonicalPermissionEntry[] {\n const byIdentity = new Map<string, CanonicalPermissionEntry>();\n for (const permission of permissions) {\n const canonical = canonicalizeCapability(permission);\n byIdentity.set(permissionIdentityFromCanonical(canonical), canonical);\n }\n\n return [...byIdentity.entries()]\n .sort(([left], [right]) => left.localeCompare(right))\n .map(([, permission]) => permission);\n}\n\nfunction canonicalizeCapability(permission: PermissionEntry): CanonicalPermissionEntry {\n const actions = [...new Set(permission.actions)].sort((left, right) => left.localeCompare(right));\n return {\n service: permission.service,\n ...(permission.space !== undefined ? { space: permission.space } : {}),\n path: permission.path,\n actions,\n ...(permission.skipPrefix !== undefined ? { skipPrefix: permission.skipPrefix } : {}),\n ...(permission.expiry !== undefined ? { expiry: permission.expiry } : {}),\n ...(permission.description !== undefined ? { description: permission.description } : {}),\n ...(permission.caveats === undefined\n ? {}\n : { caveats: canonicalizeCaveats(permission.caveats) }),\n };\n}\n\nfunction permissionIdentityFromCanonical(permission: CanonicalPermissionEntry): string {\n return JSON.stringify({\n service: permission.service,\n ...(permission.space !== undefined ? { space: permission.space } : {}),\n path: permission.path,\n actions: permission.actions,\n ...(permission.skipPrefix !== undefined ? { skipPrefix: permission.skipPrefix } : {}),\n ...(permission.expiry !== undefined ? { expiry: permission.expiry } : {}),\n ...(permission.description !== undefined ? { description: permission.description } : {}),\n ...(permission.caveats === undefined ? {} : { caveats: permission.caveats }),\n });\n}\n\nfunction isExactPermission(value: unknown): value is PermissionEntry {\n if (\n !isExactRecord(value) ||\n Object.getOwnPropertyNames(value).some((key) => !EXACT_PERMISSION_KEYS.has(key)) ||\n Object.getOwnPropertySymbols(value).length > 0\n ) {\n return false;\n }\n if (\n !isExactText(value.service) ||\n !isExactPath(value.path, value.actions) ||\n (value.space !== undefined && !isExactText(value.space)) ||\n !Array.isArray(value.actions) ||\n value.actions.length === 0 ||\n !value.actions.every(isExactText) ||\n new Set(value.actions).size !== value.actions.length ||\n value.skipPrefix === true ||\n (value.skipPrefix !== undefined && value.skipPrefix !== false) ||\n (value.expiry !== undefined && !isExactText(value.expiry)) ||\n (value.description !== undefined && !isExactText(value.description))\n ) {\n return false;\n }\n return !(\"caveats\" in value);\n}\n\nfunction isExactRecord(value: unknown): value is Record<string, unknown> {\n if (typeof value !== \"object\" || value === null || Array.isArray(value)) return false;\n const prototype = Object.getPrototypeOf(value);\n return prototype === Object.prototype || prototype === null;\n}\n\nfunction isExactText(value: unknown): value is string {\n return typeof value === \"string\" && value.length > 0 && !value.includes(\"*\");\n}\n\nfunction isExactPath(value: unknown, actions: unknown): value is string {\n if (typeof value !== \"string\" || value === \"/\" || value.includes(\"*\")) return false;\n if (value === \"\" || value.endsWith(\"/\")) {\n return Array.isArray(actions) && actions.includes(\"tinycloud.kv/list\");\n }\n return true;\n}\n\nfunction canonicalizeCaveats(\n caveats: readonly Record<string, unknown>[],\n): Record<string, unknown>[] {\n return [...caveats]\n .map((caveat) => ({\n identity: canonicalizeRecapCaveats([caveat]),\n value: JSON.parse(JSON.stringify(caveat)) as Record<string, unknown>,\n }))\n .sort((left, right) => left.identity.localeCompare(right.identity))\n .map(({ value }) => value);\n}\n","import { randomUUID } from \"node:crypto\";\nimport { AsyncLocalStorage } from \"node:async_hooks\";\nimport { constants as fsConstants, fstatSync, readFileSync, readlinkSync } from \"node:fs\";\nimport {\n chmod,\n link,\n mkdir,\n lstat,\n open,\n readFile,\n readdir,\n realpath,\n rename,\n rmdir,\n rm,\n stat,\n unlink,\n writeFile,\n} from \"node:fs/promises\";\nimport type { FileHandle } from \"node:fs/promises\";\nimport { connect, createServer, type Server } from \"node:net\";\nimport { homedir } from \"node:os\";\nimport { basename, dirname, isAbsolute, join, relative, resolve } from \"node:path\";\n\nconst DEFAULT_PROFILE = \"default\";\nconst DEFAULT_LOCK_TIMEOUT_MS = 2_000;\nconst DEFAULT_LOCK_RETRY_MS = 25;\nconst DEFAULT_STALE_LOCK_MS = 30_000;\nconst TEST_LOCK_CONTENTION_SIGNAL_PATH = \"TC_TEST_PROFILE_LOCK_CONTENTION_SIGNAL_PATH\";\n// Profile state holds keys, sessions and delegations: owner-only access.\nconst PRIVATE_FILE_MODE = 0o600;\nconst PRIVATE_DIR_MODE = 0o700;\nconst TEST_LOCK_RECOVERY_BARRIER_DIR = \"TC_TEST_PROFILE_LOCK_RECOVERY_BARRIER_DIR\";\nconst TEST_LOCK_OWNERLESS_BARRIER_DIR = \"TC_TEST_PROFILE_LOCK_OWNERLESS_BARRIER_DIR\";\nconst TEST_LOCK_PUBLISH_BARRIER_DIR = \"TC_TEST_PROFILE_LOCK_PUBLISH_BARRIER_DIR\";\nconst TEST_LOCK_RELEASE_BARRIER_DIR = \"TC_TEST_PROFILE_LOCK_RELEASE_BARRIER_DIR\";\nconst TEST_LOCK_CLAIM_BARRIER_DIR = \"TC_TEST_PROFILE_LOCK_CLAIM_BARRIER_DIR\";\nconst TEST_LOCK_CLAIMED_BARRIER_DIR = \"TC_TEST_PROFILE_LOCK_CLAIMED_BARRIER_DIR\";\nconst TEST_LOCK_VERIFIED_BARRIER_DIR = \"TC_TEST_PROFILE_LOCK_VERIFIED_BARRIER_DIR\";\nconst TEST_LOCK_MOVED_BARRIER_DIR = \"TC_TEST_PROFILE_LOCK_MOVED_BARRIER_DIR\";\nconst TEST_LOCK_TURN_PUBLISH_BARRIER_DIR = \"TC_TEST_PROFILE_LOCK_TURN_PUBLISH_BARRIER_DIR\";\nconst TEST_LOCK_TURN_SETTLE_BARRIER_DIR = \"TC_TEST_PROFILE_LOCK_TURN_SETTLE_BARRIER_DIR\";\nconst TEST_LOCK_TURN_COLLECT_BARRIER_DIR = \"TC_TEST_PROFILE_LOCK_TURN_COLLECT_BARRIER_DIR\";\nconst TEST_LOCK_LIVENESS_BARRIER_DIR = \"TC_TEST_PROFILE_LOCK_LIVENESS_BARRIER_DIR\";\n/** One acquisition of a profile lock; `active` is cleared before release. */\ninterface HeldProfileLock {\n readonly lockPath: string;\n /** The number of the turn this acquisition holds (see acquireTurn). */\n readonly turnSlot: number;\n /** A deletion of the profile ran while this acquisition waited for the lock. */\n readonly deletedWhileWaiting: boolean;\n active: boolean;\n deletedProfile?: boolean;\n}\n/**\n * Async context shared by every copy of this module in the process. Each\n * bundled operations entry point (`state`, `delegation-binding`, the root)\n * carries its own copy, so module-level context would make lock reentrancy and\n * the invocation state root stop at an entry-point boundary.\n * Each key is independently versioned for the shape and meaning of the\n * value it stores. Held-lock entries gained `turnSlot` and\n * `deletedWhileWaiting` in TC-633, so `.v2` must not read TC-602's `.v1`\n * entries. The invocation root remains a string path with unchanged\n * semantics and stays `.v1`. Relinquished turn tokens and the process\n * instance ID use separate versioned process-wide keys: only an explicitly\n * given-up turn of this process may be settled across bundled entry points.\n */\nfunction processWideContext<T>(key: string): AsyncLocalStorage<T> {\n const registry = globalThis as unknown as Record<symbol, AsyncLocalStorage<T> | undefined>;\n return registry[Symbol.for(key)] ??= new AsyncLocalStorage<T>();\n}\nconst invocationStateRoot = processWideContext<string>(\"tinycloud.operations.invocationStateRoot.v1\");\n/** Profile lock acquisitions held by the current async call chain (see withProfileLock). */\nconst heldProfileLocks = processWideContext<readonly HeldProfileLock[]>(\"tinycloud.operations.heldProfileLocks.v2\");\n/** Descriptors inherited by deferred async work are unusable after their owner's action returns. */\ninterface PinnedProfileDirectory {\n readonly path: string;\n readonly fd: number;\n readonly dev: bigint;\n readonly ino: bigint;\n active: boolean;\n}\nconst pinnedProfiles = processWideContext<ReadonlyMap<string, PinnedProfileDirectory>>(\"tinycloud.operations.pinnedProfiles.v2\");\nconst pinnedHomes = processWideContext<ReadonlyMap<string, PinnedProfileDirectory>>(\"tinycloud.operations.pinnedHomes.v2\");\n/** One canonical target per configured home across ESM/CJS copies in this process. */\nconst canonicalHomes: Map<string, string> =\n (globalThis as unknown as Record<symbol, Map<string, string> | undefined>)[Symbol.for(\"tinycloud.operations.canonicalHomes.v1\")] ??= new Map();\n/** One ID across separately bundled ESM, CJS, CLI and MCP copies in this process. */\nconst processInstanceId: string =\n (globalThis as unknown as Record<symbol, string | undefined>)[Symbol.for(\"tinycloud.operations.profileLockInstance.v1\")] ??=\n randomUUID();\n\ninterface ProcessOrigin {\n readonly bootId: string;\n readonly pidNamespace: string;\n /** /proc/self/stat field 22, in kernel clock ticks since boot. */\n readonly processStartTime: string;\n}\n\n/** A listening UNIX socket in the turn directory, held for one acquisition. */\ninterface Liveness {\n /**\n * Name of the socket in the turn directory, `.live-<dev>-<ino>-<uuid>`:\n * dev and ino (hex) identify the turn directory its holder bound it in.\n */\n readonly socket: string;\n /** lstat dev and ino of the socket as its holder bound it, decimal. */\n readonly dev: string;\n readonly ino: string;\n}\n\ninterface RecordedProcess {\n readonly pid?: unknown;\n readonly instanceId?: unknown;\n readonly bootId?: unknown;\n readonly pidNamespace?: unknown;\n readonly processStartTime?: unknown;\n readonly liveness?: unknown;\n}\n\nlet cachedProcessOrigin: ProcessOrigin | null | undefined;\n\n/** Missing/inaccessible Linux provenance is not evidence that an owner died. */\nfunction processOrigin(): ProcessOrigin | null {\n if (cachedProcessOrigin !== undefined) return cachedProcessOrigin;\n if (process.platform !== \"linux\") return cachedProcessOrigin = null;\n try {\n const bootId = readFileSync(\"/proc/sys/kernel/random/boot_id\", \"utf8\").trim();\n const pidNamespace = readlinkSync(\"/proc/self/ns/pid\");\n const processStat = readFileSync(\"/proc/self/stat\", \"utf8\");\n const closingParen = processStat.lastIndexOf(\") \");\n const processStartTime = closingParen < 0 ? undefined : processStat.slice(closingParen + 2).trim().split(/\\s+/)[19];\n if (!/^[0-9a-f-]{36}$/.test(bootId) || !/^pid:\\[[0-9]+\\]$/.test(pidNamespace) ||\n !processStartTime || !/^[0-9]+$/.test(processStartTime)) {\n return cachedProcessOrigin = null;\n }\n return cachedProcessOrigin = { bootId, pidNamespace, processStartTime };\n } catch {\n return cachedProcessOrigin = null;\n }\n}\n\ninterface HeldLiveness {\n readonly record: Liveness;\n readonly path: string;\n readonly server: Server;\n}\n\n/** Extra JSON properties only: older releases still read the original owner fields. */\nfunction processOwner(liveness: HeldLiveness | null): { instanceId: string; liveness?: Liveness } & Partial<ProcessOrigin> {\n return { instanceId: processInstanceId, ...processOrigin(), ...(liveness ? { liveness: liveness.record } : {}) };\n}\n\n/**\n * Liveness socket names (see Liveness), and the same name with `.new` while\n * it is bound but not yet listening. A socket appears under its final name\n * only once it accepts connections, so a refused connect to a final name\n * means its listener is closed for good.\n */\nconst LIVENESS_SOCKET = /^\\.live-([0-9a-f]+)-([0-9a-f]+)-[0-9a-f]{8}-(?:[0-9a-f]{4}-){3}[0-9a-f]{12}(\\.new)?$/;\nconst LIVENESS_PROBE_TIMEOUT_MS = 1_000;\n\n/** The `dev-ino` (hex) a liveness socket name gives for its turn directory; null for other names. */\nfunction socketDirectory(name: string): string | null {\n const match = LIVENESS_SOCKET.exec(name);\n return match ? `${match[1]}-${match[2]}` : null;\n}\n\n/** A turn directory held open, so every lookup in it names the same directory. */\ninterface PinnedDirectory {\n /** `dev-ino` of the directory, hex, as in liveness socket names. */\n readonly identity: string;\n /** `name` in this directory, through /proc/self/fd: short enough for sun_path. */\n path(name: string): string;\n close(): Promise<void>;\n}\n\nasync function pinDirectory(directory: string): Promise<PinnedDirectory> {\n const handle = await open(directory, \"r\");\n try {\n const { dev, ino } = await handle.stat({ bigint: true });\n return {\n identity: `${dev.toString(16)}-${ino.toString(16)}`,\n path: (name) => `/proc/self/fd/${handle.fd}/${name}`,\n close: () => handle.close(),\n };\n } catch (error) {\n await handle.close();\n throw error;\n }\n}\n\n/**\n * Binds this acquisition's liveness socket in the turn directory: Linux with\n * provenance only. Null if it cannot be bound (no /proc, a filesystem\n * without socket files, a sandbox without networking): records then carry\n * no liveness, and a same-PID process in another PID namespace waits. The\n * socket is bound as `<name>.new` and renamed once listening; a sweep that\n * removes the `.new` name in between (it refused) makes the rename, and so\n * the bind, fail.\n */\nasync function bindLiveness(directory: string): Promise<HeldLiveness | null> {\n if (!processOrigin()) return null;\n let pinned: PinnedDirectory;\n try {\n pinned = await pinDirectory(directory);\n } catch {\n return null;\n }\n const socket = `.live-${pinned.identity}-${randomUUID()}`;\n const server = createServer((connection) => connection.destroy());\n let bound = false;\n try {\n // Node 20 has no Promise.withResolvers.\n await new Promise<void>((resolve, reject) => {\n server.once(\"error\", reject);\n // exclusive: a cluster worker binds its own socket, not the primary's.\n server.listen({ path: pinned.path(`${socket}.new`), exclusive: true }, () => {\n server.off(\"error\", reject);\n resolve();\n });\n });\n bound = true;\n server.on(\"error\", () => undefined);\n server.unref();\n await rename(pinned.path(`${socket}.new`), pinned.path(socket));\n const stats = await lstat(pinned.path(socket), { bigint: true });\n if (!stats.isSocket()) throw new Error(`${socket} is not a socket`);\n await waitForTestBarrier(TEST_LOCK_LIVENESS_BARRIER_DIR, basename(directory));\n return { record: { socket, dev: String(stats.dev), ino: String(stats.ino) }, path: join(directory, socket), server };\n } catch {\n if (bound) {\n server.close();\n for (const name of [`${socket}.new`, socket]) await unlink(pinned.path(name)).catch(() => undefined);\n }\n return null;\n } finally {\n await pinned.close();\n }\n}\n\n/** This acquisition's liveness socket, bound in its turn directory on first need. */\nasync function ensureLiveness(acquisition: AcquisitionLiveness): Promise<HeldLiveness | null> {\n if (acquisition.held === undefined) acquisition.held = await bindLiveness(acquisition.directory);\n return acquisition.held;\n}\n\n/**\n * Removes the socket and stops listening: its holder is past its critical\n * section, so a contender may see it gone or refused from here on. The close\n * is not awaited (Bun calls back a millisecond later).\n */\nasync function closeLiveness(held: HeldLiveness): Promise<void> {\n held.server.close();\n await unlink(held.path).catch(() => undefined);\n}\n\nfunction parseLiveness(value: unknown): Liveness | null {\n if (typeof value !== \"object\" || value === null) return null;\n const { socket, dev, ino } = value as Record<string, unknown>;\n // Records name a final socket name, never a `.new` one.\n const name = typeof socket === \"string\" ? LIVENESS_SOCKET.exec(socket) : null;\n return name !== null && name[3] === undefined &&\n typeof dev === \"string\" && /^[0-9]+$/.test(dev) && typeof ino === \"string\" && /^[0-9]+$/.test(ino)\n ? { socket: name[0], dev, ino }\n : null;\n}\n\n/**\n * This acquisition's own socket, as the control for judging others in\n * `pinned`: bound (on first need) in that same directory, and found there\n * as bound. Null if there is none.\n */\nasync function controlIn(pinned: PinnedDirectory, acquisition: AcquisitionLiveness): Promise<HeldLiveness | null> {\n const own = await ensureLiveness(acquisition);\n if (!own || socketDirectory(own.record.socket) !== pinned.identity) return null;\n const stats = await lstat(pinned.path(own.record.socket), { bigint: true }).catch(() => null);\n return stats?.isSocket() && String(stats.dev) === own.record.dev && String(stats.ino) === own.record.ino ? own : null;\n}\n\n/** Connects to a UNIX socket and disconnects: \"connected\", \"refused\" (ECONNREFUSED) or \"other\". */\nasync function tryConnect(path: string): Promise<\"connected\" | \"refused\" | \"other\"> {\n // Node 20 has no Promise.withResolvers.\n return new Promise((resolve) => {\n const probe = connect({ path });\n const settle = (outcome: \"connected\" | \"refused\" | \"other\") => {\n clearTimeout(timer);\n probe.destroy();\n resolve(outcome);\n };\n const timer = setTimeout(() => settle(\"other\"), LIVENESS_PROBE_TIMEOUT_MS);\n probe.once(\"connect\", () => settle(\"connected\"));\n probe.once(\"error\", (error) => settle(isErrno(error, \"ECONNREFUSED\") ? \"refused\" : \"other\"));\n });\n}\n\n/**\n * Whether the kernel refuses connections to `name` in `pinned`. A refusal\n * counts only if this process then connects to its own socket (`control`)\n * in the same directory through the same kind of address. This catches a\n * sandbox that refuses every connect, but not one that fabricates refusals\n * selectively (a seccomp filter keyed on call arguments, a seccomp\n * user-notification supervisor, or a BPF LSM program). Like the PID check,\n * which trusts kill(2), this decision trusts the kernel's answers. Never\n * under Bun, which reports a refused UNIX connect as ENOENT.\n */\nasync function kernelRefuses(pinned: PinnedDirectory, name: string, control: HeldLiveness): Promise<boolean> {\n if (process.versions.bun !== undefined) return false;\n return await tryConnect(pinned.path(name)) === \"refused\" &&\n await tryConnect(pinned.path(control.record.socket)) === \"connected\";\n}\n\n/**\n * Whether a liveness socket's holder is gone, judged in the turn directory\n * of `acquisition` and only if the socket's name says it was bound in that\n * very directory (not another one under that path: a separate turn lock, or\n * one removed and recreated). \"gone\" if the socket file was removed (only\n * its holder does that, after its critical section, or a process that found\n * it refused), \"refused\" if the kernel refuses connections to that very\n * inode (kernelRefuses). Both need this acquisition's own socket in that\n * directory as a control. Anything else, including a paused holder's full\n * backlog, is \"unknown\".\n */\nasync function probeLiveness(liveness: Liveness, acquisition: AcquisitionLiveness): Promise<\"gone\" | \"refused\" | \"unknown\"> {\n let pinned: PinnedDirectory;\n try {\n pinned = await pinDirectory(acquisition.directory);\n } catch {\n return \"unknown\";\n }\n try {\n if (socketDirectory(liveness.socket) !== pinned.identity) return \"unknown\";\n const control = await controlIn(pinned, acquisition);\n if (!control) return \"unknown\";\n let stats;\n try {\n stats = await lstat(pinned.path(liveness.socket), { bigint: true });\n } catch (error) {\n return isErrno(error, \"ENOENT\") ? \"gone\" : \"unknown\";\n }\n if (!stats.isSocket() || String(stats.dev) !== liveness.dev || String(stats.ino) !== liveness.ino) return \"unknown\";\n return await kernelRefuses(pinned, liveness.socket, control) ? \"refused\" : \"unknown\";\n } finally {\n await pinned.close();\n }\n}\n\n/**\n * Removes the liveness sockets in this acquisition's turn directory that the\n * kernel refuses (kernelRefuses): those of acquisitions that are over,\n * whether or not a record still names them (a crash before the first\n * record, a turn removed by a Bun process, which cannot judge refusal). A\n * socket that accepts or cannot be judged stays; nothing is removed by age.\n * Not under Bun.\n */\nasync function sweepLiveness(acquisition: AcquisitionLiveness): Promise<void> {\n if (process.versions.bun !== undefined || !acquisition.held) return;\n const pinned = await pinDirectory(acquisition.directory);\n try {\n const control = await controlIn(pinned, acquisition);\n if (!control) return;\n for (const name of await readdir(pinned.path(\".\"))) {\n if (name === control.record.socket || socketDirectory(name) !== pinned.identity) continue;\n const stats = await lstat(pinned.path(name)).catch(() => null);\n if (stats?.isSocket() && await kernelRefuses(pinned, name, control)) {\n await unlink(pinned.path(name)).catch(() => undefined);\n }\n }\n } finally {\n await pinned.close();\n }\n}\n\nexport type ProfileStoreName =\n | \"session\"\n | \"additional-delegations\"\n | \"auth-requests\";\n\nexport interface StoreMetadata {\n formatVersion: number;\n}\n\nexport interface ProfileStoreContents<T> {\n formatVersion: number;\n records: T[];\n}\n\nexport interface ProfileLockOptions {\n timeoutMs?: number;\n retryMs?: number;\n staleAfterMs?: number;\n /**\n * Lock only a profile that exists (its `profile.json` is present), and\n * never create its directory: a missing or deleted profile is refused with\n * ProfileDeletedError, before and after waiting for the lock. For writers\n * of state that lives inside an existing profile (replicas).\n */\n requireProfile?: boolean;\n}\n\nexport class ProfileLockTimeoutError extends Error {\n readonly code = \"PROFILE_LOCK_TIMEOUT\";\n\n /** `detail` names the cause when it is not simply another process holding the lock. */\n constructor(profile: string, timeoutMs: number, detail?: string) {\n super(`Timed out waiting for the profile lock for \"${profile}\" after ${timeoutMs}ms.${detail ? ` ${detail}` : \"\"}`);\n this.name = \"ProfileLockTimeoutError\";\n }\n}\n\n/**\n * A store write that waited for the profile lock while a deletion of that\n * profile held it, and found no profile settings once it got the lock. The\n * write is refused rather than recreating a profile with only a session or\n * store in it.\n */\nexport class ProfileDeletedError extends Error {\n readonly code = \"PROFILE_NOT_FOUND\";\n\n constructor(profile: string) {\n super(`Profile \"${profile}\" was deleted while this change waited for its lock, so the change was not written.`);\n this.name = \"ProfileDeletedError\";\n }\n}\n\n/**\n * The CLI's delegated-secret path treats TC_HOME as a home directory, rather\n * than a direct TinyCloud directory. Keep the shared store on that convention.\n */\nexport function tinycloudHomePath(): string {\n const home = invocationStateRoot.getStore() ??\n process.env.TC_HOME ??\n process.env.HOME ??\n process.env.USERPROFILE ??\n homedir();\n return join(home, \".tinycloud\");\n}\n\n/** Run one operation against an isolated TinyCloud home directory. */\nexport function withTinyCloudStateRoot<T>(\n stateRoot: string | undefined,\n action: () => Promise<T>,\n): Promise<T> {\n if (stateRoot === undefined) return action();\n if (!isAbsolute(stateRoot) || stateRoot.includes(\"\\0\")) {\n throw new TypeError(\"The TinyCloud state root must be an absolute path.\");\n }\n return invocationStateRoot.run(resolve(stateRoot), action);\n}\n\nexport function tinycloudConfigPath(): string {\n return join(tinycloudHomePath(), \"config.json\");\n}\n\nexport function profilesPath(): string {\n return join(tinycloudHomePath(), \"profiles\");\n}\n\nexport function namedProfilePath(profile: string): string {\n return join(profilesPath(), validateProfileName(profile));\n}\n\nexport function profilePath(profile: string): string {\n const named = namedProfilePath(profile);\n const pinned = pinnedProfiles.getStore()?.get(named);\n return pinned ? livePinnedPath(profile, pinned) : named;\n}\n\nexport function profileConfigPath(profile: string): string {\n return join(profilePath(profile), \"profile.json\");\n}\n\nexport function sessionPath(profile: string): string {\n return profileStorePath(profile, \"session\");\n}\n\nexport function additionalDelegationsPath(profile: string): string {\n return profileStorePath(profile, \"additional-delegations\");\n}\n\nexport function authRequestsPath(profile: string): string {\n return profileStorePath(profile, \"auth-requests\");\n}\n\nexport function profileStorePath(profile: string, store: ProfileStoreName): string {\n return join(profilePath(profile), `${store}.json`);\n}\n\n/** The format record is deliberately separate from each legacy JSON payload. */\nexport function profileStoreMetadataPath(profile: string, store: ProfileStoreName): string {\n return `${profileStorePath(profile, store)}.metadata.json`;\n}\n\nexport function profileLockPath(profile: string): string {\n return join(namedProfilePath(profile), \".lock\");\n}\n\nexport function profileLockMetadataPath(profile: string): string {\n return join(profileLockPath(profile), \"owner.json\");\n}\n\n/**\n * The turn lock of a profile (see acquireTurn). It lives outside the profile\n * directory, so neither a profile deletion nor an older release's cleanup\n * removes it.\n */\nexport function profileTurnLockPath(profile: string): string {\n const home = tinycloudHomePath();\n const pinned = pinnedHomes.getStore()?.get(home);\n return join(pinned ? livePinnedPath(profile, pinned) : home, \"profile-locks\", validateProfileName(profile));\n}\n\n/**\n * Reads JSON without hiding malformed or inaccessible files. A missing file is\n * the only condition represented as null.\n */\nexport async function readJson<T>(filePath: string): Promise<T | null> {\n try {\n return JSON.parse(await readFile(filePath, \"utf8\")) as T;\n } catch (error) {\n if (isErrno(error, \"ENOENT\")) return null;\n throw error;\n }\n}\n\n/** A profile's direct JSON files must not resolve through a child alias. */\nasync function readProfileJson<T>(profile: string, filePath: string): Promise<T | null> {\n // On platforms without descriptor-relative profile paths, check the named file on\n // both sides of the read so a replacement cannot silently redirect it.\n const before = process.platform !== \"linux\"\n ? await lstat(filePath, { bigint: true }).catch((error: NodeJS.ErrnoException) => {\n if (error.code === \"ENOENT\") return null;\n throw error;\n })\n : null;\n if (process.platform !== \"linux\" && !before) return null;\n if (before && !before.isFile()) throw new ProfileSymlinkError(profile);\n const dev = before && exactFileId(before.dev);\n const ino = before && exactFileId(before.ino);\n try {\n const handle = await open(filePath, fsConstants.O_RDONLY | (fsConstants.O_NOFOLLOW ?? 0));\n try {\n if (before) {\n const opened = await handle.stat({ bigint: true });\n if (!opened.isFile() || dev === null || ino === null ||\n dev !== exactFileId(opened.dev) || ino !== exactFileId(opened.ino)) {\n throw new ProfileSymlinkError(profile, \"changed while opening its file\");\n }\n }\n const contents = await handle.readFile(\"utf8\");\n if (before) {\n const named = await lstat(filePath, { bigint: true }).catch((error: NodeJS.ErrnoException) => {\n if (error.code === \"ENOENT\") return null;\n throw error;\n });\n if (!named?.isFile() || dev !== exactFileId(named.dev) || ino !== exactFileId(named.ino)) {\n throw new ProfileSymlinkError(profile, \"changed while reading its file\");\n }\n }\n return JSON.parse(contents) as T;\n } finally {\n await handle.close();\n }\n } catch (error) {\n if (isErrno(error, \"ENOENT\")) return null;\n if (isErrno(error, \"ELOOP\") || isErrno(error, \"ENOTDIR\")) throw new ProfileSymlinkError(profile);\n throw error;\n }\n}\n\n/**\n * Writes the same pretty-printed, trailing-newline JSON shape used by the CLI,\n * but publishes it with an atomic rename from the same directory.\n */\nexport async function writeJsonAtomic(filePath: string, value: unknown): Promise<void> {\n await replaceJson(filePath, value, false);\n}\n\n/**\n * writeJsonAtomic; `durable` also flushes the data to disk before the rename,\n * so a crash or power loss leaves the old record or the new one, never a\n * truncated one (for lock records, whose damage would stop every writer).\n */\nasync function replaceJson(filePath: string, value: unknown, durable: boolean): Promise<void> {\n const directory = dirname(filePath);\n const temporaryPath = join(\n directory,\n `.${basename(filePath)}.${process.pid}.${randomUUID()}.tmp`,\n );\n const contents = `${JSON.stringify(value, null, 2)}\\n`;\n\n await mkdir(directory, { recursive: true, mode: PRIVATE_DIR_MODE });\n try {\n if (durable) await writeFileDurably(temporaryPath, contents);\n else await writeFile(temporaryPath, contents, { encoding: \"utf8\", mode: PRIVATE_FILE_MODE });\n await rename(temporaryPath, filePath);\n } catch (error) {\n await rm(temporaryPath, { force: true }).catch(() => undefined);\n throw error;\n }\n}\n\n/** Creates a file (exclusively) and, when it has contents, flushes them to disk. */\nasync function writeFileDurably(path: string, contents: string): Promise<void> {\n const handle = await open(path, \"wx\", PRIVATE_FILE_MODE);\n try {\n if (contents) {\n await handle.writeFile(contents, \"utf8\");\n await handle.datasync();\n }\n } finally {\n await handle.close();\n }\n}\n\nexport async function readProfile<T extends object = Record<string, unknown>>(\n profile: string,\n): Promise<T | null> {\n return withPinnedProfileDirectory(profile, (directory) =>\n directory ? readProfileJson<T>(profile, join(directory, \"profile.json\")) : Promise.resolve(null));\n}\n\nexport async function readSession<T extends object = Record<string, unknown>>(\n profile: string,\n): Promise<T | null> {\n return withPinnedProfileDirectory(profile, async (directory) => {\n if (!directory) return null;\n await readStoreMetadata(profile, \"session\");\n return readProfileJson<T>(profile, sessionPath(profile));\n });\n}\n\nexport async function readAdditionalDelegations<T = Record<string, unknown>>(\n profile: string,\n): Promise<T[]> {\n return (await readProfileStore<T>(profile, \"additional-delegations\")).records;\n}\n\nexport async function readAuthRequests<T = Record<string, unknown>>(\n profile: string,\n): Promise<T[]> {\n return (await readProfileStore<T>(profile, \"auth-requests\")).records;\n}\n\nexport async function readStoreMetadata(\n profile: string,\n store: ProfileStoreName,\n): Promise<StoreMetadata> {\n return withPinnedProfileDirectory(profile, async (directory) => {\n const metadata = directory ? await readProfileJson<StoreMetadata>(profile, profileStoreMetadataPath(profile, store)) : null;\n if (metadata === null) return { formatVersion: 1 };\n if (\n typeof metadata !== \"object\" ||\n metadata.formatVersion !== 1\n ) {\n throw new TypeError(`Unsupported store format for \"${store}\".`);\n }\n return metadata;\n });\n}\n\nexport async function readProfileStore<T>(\n profile: string,\n store: Exclude<ProfileStoreName, \"session\">,\n): Promise<ProfileStoreContents<T>> {\n return withPinnedProfileDirectory(profile, async (directory) => {\n if (!directory) return { formatVersion: 1, records: [] };\n const [metadata, raw] = await Promise.all([\n readStoreMetadata(profile, store),\n readProfileJson<unknown>(profile, profileStorePath(profile, store)),\n ]);\n return {\n formatVersion: metadata.formatVersion,\n records: Array.isArray(raw) ? raw as T[] : [],\n };\n });\n}\n\nexport class ProfileSymlinkError extends Error {\n readonly code = \"PROFILE_SYMLINK\";\n\n constructor(profile: string, detail = \"must be a real directory, not a symlink or another file type\") {\n super(`Profile \"${profile}\" ${detail}.`);\n this.name = \"ProfileSymlinkError\";\n }\n}\n\n/** Bun versions may return numeric FileHandle.stat IDs despite bigint: true. */\nfunction exactFileId(value: number | bigint): bigint | null {\n if (typeof value === \"bigint\") return value >= 0n ? value : null;\n return Number.isSafeInteger(value) && value >= 0 ? BigInt(value) : null;\n}\n\nfunction isPinnedLive(pinned: PinnedProfileDirectory): boolean {\n if (!pinned.active) return false;\n try {\n const stats = fstatSync(pinned.fd, { bigint: true });\n return exactFileId(stats.dev) === pinned.dev && exactFileId(stats.ino) === pinned.ino;\n } catch {\n return false;\n }\n}\n\nfunction livePinnedPath(profile: string, pinned: PinnedProfileDirectory): string {\n if (isPinnedLive(pinned)) return pinned.path;\n throw new ProfileSymlinkError(profile, \"its pinned directory is no longer active or changed during use\");\n}\n\n/** Missing profiles remain missing; existing profiles are always opened without following aliases. */\nexport async function withPinnedProfileDirectory<T>(\n profile: string,\n action: (directory: string | null) => Promise<T>,\n options: { create?: boolean } = {},\n): Promise<T> {\n const named = namedProfilePath(profile);\n const existing = pinnedProfiles.getStore()?.get(named);\n if (existing) return action(livePinnedPath(profile, existing));\n if (process.platform !== \"linux\") {\n // Only destructive replica purge needs a descriptor-relative child path.\n // Preserve ordinary profile state access on platforms without /proc/self/fd.\n if (options.create) await mkdir(named, { recursive: true, mode: PRIVATE_DIR_MODE });\n const stats = await lstat(named).catch((error: NodeJS.ErrnoException) => {\n if (error.code === \"ENOENT\") return null;\n throw error;\n });\n if (!stats) return action(null);\n if (!stats.isDirectory() || stats.isSymbolicLink()) throw new ProfileSymlinkError(profile);\n return action(named);\n }\n const home = tinycloudHomePath();\n const handles: FileHandle[] = [];\n const fdPath = (fd: number) => `/proc/self/fd/${fd}`;\n const flags = fsConstants.O_RDONLY | fsConstants.O_DIRECTORY | fsConstants.O_NOFOLLOW;\n try {\n if (options.create) await mkdir(home, { recursive: true, mode: PRIVATE_DIR_MODE });\n // User-owned home aliases are allowed, but each configured home resolves\n // only once in this process. A later alias retarget cannot select another profile.\n const resolved = canonicalHomes.get(home) ?? await realpath(home).catch((error: NodeJS.ErrnoException) => {\n if (error.code === \"ENOENT\" && !options.create) return null;\n if (error.code === \"ELOOP\" || error.code === \"ENOTDIR\") throw new ProfileSymlinkError(profile);\n throw error;\n });\n if (!resolved) return await action(null);\n const root = canonicalHomes.get(home) ?? resolved;\n canonicalHomes.set(home, root);\n const segments = [root, \"profiles\", validateProfileName(profile)];\n for (let index = 0; index < segments.length; index++) {\n const path = handles.length ? join(fdPath(handles[handles.length - 1]!.fd), segments[index]!) : segments[index]!;\n if (options.create && handles.length) {\n await mkdir(path, { mode: PRIVATE_DIR_MODE }).catch((error: NodeJS.ErrnoException) => {\n if (error.code !== \"EEXIST\") throw error;\n });\n }\n // A profiles/ container may be shared by two homes; pin its target, but never follow an alias for the selected profile.\n const handle = await open(path, index === 1 ? flags & ~fsConstants.O_NOFOLLOW : flags).catch((error: NodeJS.ErrnoException) => {\n if (error.code === \"ENOENT\" && !options.create) return null;\n if (error.code === \"ELOOP\" || error.code === \"ENOTDIR\") throw new ProfileSymlinkError(profile);\n throw error;\n });\n if (!handle) return await action(null);\n handles.push(handle);\n if (options.create) await handle.chmod(PRIVATE_DIR_MODE);\n }\n const opened = handles[2]!;\n const [actual, namedStats] = await Promise.all([opened.stat({ bigint: true }), lstat(named, { bigint: true })]);\n const dev = exactFileId(actual.dev);\n const ino = exactFileId(actual.ino);\n if (!namedStats.isDirectory() || dev === null || ino === null ||\n dev !== exactFileId(namedStats.dev) || ino !== exactFileId(namedStats.ino)) {\n throw new ProfileSymlinkError(profile, \"changed while opening its directory\");\n }\n const directory = fdPath(opened.fd);\n const pinned: PinnedProfileDirectory = {\n path: directory, fd: opened.fd, dev, ino, active: true,\n };\n const homeHandle = handles[0]!;\n const homeStats = await homeHandle.stat({ bigint: true });\n const homeDev = exactFileId(homeStats.dev);\n const homeIno = exactFileId(homeStats.ino);\n if (homeDev === null || homeIno === null) throw new ProfileSymlinkError(profile, \"has an unverified home directory identity\");\n const homePinned: PinnedProfileDirectory = {\n path: fdPath(homeHandle.fd), fd: homeHandle.fd, dev: homeDev, ino: homeIno, active: true,\n };\n const context = new Map(pinnedProfiles.getStore());\n context.set(named, pinned);\n const homes = new Map(pinnedHomes.getStore());\n homes.set(home, homePinned);\n try {\n return await pinnedHomes.run(homes, () => pinnedProfiles.run(context, () => action(directory)));\n } finally {\n pinned.active = false;\n homePinned.active = false;\n }\n } finally {\n for (const handle of handles.reverse()) await handle.close();\n }\n}\n\n/** Missing profiles retain their existing read/create behavior; aliases never do. */\nexport async function assertRealProfileDirectory(profile: string): Promise<void> {\n const named = namedProfilePath(profile);\n const pinned = pinnedProfiles.getStore()?.get(named);\n if (pinned) {\n livePinnedPath(profile, pinned);\n return;\n }\n const stats = await lstat(named).catch((error: NodeJS.ErrnoException) => {\n if (error.code === \"ENOENT\") return null;\n throw error;\n });\n if (stats && (!stats.isDirectory() || stats.isSymbolicLink())) throw new ProfileSymlinkError(profile);\n}\n\n/**\n * Runs a small critical section under the one lock shared by all profile\n * stores (see acquireProfileLock). Lock release verifies its ownership token\n * before removing the exact metadata instance it acquired.\n *\n * Reentrant within one async call chain: a critical section that already\n * holds a profile's lock (for example a login's compare-and-commit) can call\n * store writers that take the same lock without deadlocking. Ownership is\n * keyed by the resolved lock path (so switching state roots inside a lock\n * still takes the other root's lock) and lasts exactly as long as the\n * acquisition: it is revoked before release, so work deferred past the\n * critical section waits for the lock like any other caller.\n */\nexport async function withProfileLock<T>(\n profile: string,\n action: () => Promise<T>,\n options: ProfileLockOptions = {},\n): Promise<T> {\n const normalizedProfile = validateProfileName(profile);\n const lockPath = profileLockPath(normalizedProfile);\n const held = (heldProfileLocks.getStore() ?? []).filter((ownership) => ownership.active);\n if (held.some((ownership) => ownership.lockPath === lockPath)) {\n await assertRealProfileDirectory(normalizedProfile);\n return action();\n }\n const named = namedProfilePath(normalizedProfile);\n const inherited = pinnedProfiles.getStore()?.get(named);\n if (inherited && !isPinnedLive(inherited)) {\n // Deferred work may explicitly acquire a new lock, but never reuse its old fd.\n const profiles = new Map(pinnedProfiles.getStore());\n profiles.delete(named);\n const homes = new Map(pinnedHomes.getStore());\n homes.delete(tinycloudHomePath());\n return pinnedHomes.run(homes, () =>\n pinnedProfiles.run(profiles, () => withProfileLock(normalizedProfile, action, options)));\n }\n return withPinnedProfileDirectory(normalizedProfile, async (directory) => {\n if (!directory) throw new ProfileDeletedError(normalizedProfile);\n const acquired = await acquireProfileLock(normalizedProfile, options);\n const ownership: HeldProfileLock = {\n lockPath,\n turnSlot: acquired.turnSlot,\n deletedWhileWaiting: acquired.deletedWhileWaiting,\n active: true,\n };\n try {\n return await heldProfileLocks.run([...held, ownership], action);\n } finally {\n ownership.active = false;\n await acquired.release();\n if (ownership.deletedProfile || acquired.deletedWhileWaiting) {\n const current = await lstat(namedProfilePath(normalizedProfile), { bigint: true }).catch(() => null);\n const original = await stat(directory, { bigint: true }).catch(() => null);\n const currentDev = current && exactFileId(current.dev);\n const currentIno = current && exactFileId(current.ino);\n if (currentDev !== null && currentIno !== null && original &&\n currentDev === exactFileId(original.dev) && currentIno === exactFileId(original.ino)) {\n await rmdir(namedProfilePath(normalizedProfile)).catch(() => undefined);\n }\n }\n }\n }, { create: !options.requireProfile });\n}\n\n/**\n * Records, inside a profile deletion's critical section and after its files\n * are gone, that the profile was deleted, with the number of the deleting\n * turn. A store write whose wait overlapped that turn (it arrived before the\n * deletion released the lock, whether before or after this record) then\n * refuses (ProfileDeletedError) instead of recreating a profile with only a\n * session or store in it. The caller must hold the profile's lock.\n */\nexport async function recordProfileDeletion(profile: string): Promise<void> {\n const lockPath = profileLockPath(profile);\n const ownership = (heldProfileLocks.getStore() ?? []).find((candidate) => candidate.active && candidate.lockPath === lockPath);\n if (!ownership) throw new Error(`Recording the deletion of profile \"${profile}\" requires holding its lock.`);\n await replaceJson(join(profileTurnLockPath(profile), \"deleted.json\"), { slot: ownership.turnSlot }, true);\n ownership.deletedProfile = true;\n}\n\n/**\n * Refuses a write that would recreate a profile deleted while this critical\n * section waited for the lock (ProfileDeletedError). Every writer of profile\n * state other than the profile settings themselves calls this inside its\n * critical section, before writing. The caller must hold the profile's lock.\n */\nexport async function refuseWriteToDeletedProfile(profile: string): Promise<void> {\n const lockPath = profileLockPath(profile);\n const ownership = (heldProfileLocks.getStore() ?? []).find((candidate) => candidate.active && candidate.lockPath === lockPath);\n if (!ownership?.deletedWhileWaiting) return;\n // Recreated meanwhile (or by this critical section): writing is fine.\n if (await exists(profileConfigPath(profile))) return;\n throw new ProfileDeletedError(profile);\n}\n\n/**\n * Appends a record or replaces the existing record with the supplied explicit\n * key. The extractor is supplied by the owning store so state.ts never needs\n * to know permission-request or delegation record shapes.\n */\nexport async function upsertProfileRecord<T>(\n profile: string,\n store: Exclude<ProfileStoreName, \"session\">,\n key: string,\n record: T,\n getKey: (candidate: T) => string | undefined,\n options: ProfileLockOptions = {},\n): Promise<T[]> {\n if (!key) throw new TypeError(\"A non-empty record key is required.\");\n\n return withProfileLock(profile, async () => {\n await refuseWriteToDeletedProfile(profile);\n const current = (await readProfileStore<T>(profile, store)).records;\n const next = current.filter((candidate) => getKey(candidate) !== key);\n next.push(record);\n await writeJsonAtomic(profileStorePath(profile, store), next);\n await writeFormatOneMetadata(profile, store);\n return next;\n }, options);\n}\n\n/**\n * Performs a typed read-modify-write of a legacy array store under the one\n * per-profile lock. The payload stays an array and the sibling metadata stays\n * format 1, so existing CLI readers keep their byte/layout contract.\n */\nexport async function updateProfileStore<T, Result>(\n profile: string,\n store: Exclude<ProfileStoreName, \"session\">,\n update: (\n records: readonly T[],\n ) => Promise<{ readonly records: readonly T[]; readonly result: Result }> | {\n readonly records: readonly T[];\n readonly result: Result;\n },\n options: ProfileLockOptions = {},\n): Promise<Result> {\n return withProfileLock(\n profile,\n () => updateProfileStoreWhileLocked(profile, store, update),\n options,\n );\n}\n\n/** The caller must already own this profile's lock. */\nasync function updateProfileStoreWhileLocked<T, Result>(\n profile: string,\n store: Exclude<ProfileStoreName, \"session\">,\n update: (\n records: readonly T[],\n ) => Promise<{ readonly records: readonly T[]; readonly result: Result }> | {\n readonly records: readonly T[];\n readonly result: Result;\n },\n): Promise<Result> {\n await refuseWriteToDeletedProfile(profile);\n const current = await readProfileStore<T>(profile, store);\n const next = await update(current.records);\n await writeJsonAtomic(profileStorePath(profile, store), next.records);\n await writeFormatOneMetadata(profile, store);\n return next.result;\n}\n\nexport async function writeSession<T extends object>(\n profile: string,\n session: T,\n options: ProfileLockOptions = {},\n): Promise<void> {\n await withProfileLock(profile, async () => {\n await refuseWriteToDeletedProfile(profile);\n await readStoreMetadata(profile, \"session\");\n await writeJsonAtomic(sessionPath(profile), session);\n await writeFormatOneMetadata(profile, \"session\");\n }, options);\n}\n\nexport async function removeSession(\n profile: string,\n options: ProfileLockOptions = {},\n): Promise<void> {\n await withProfileLock(profile, async () => {\n await readStoreMetadata(profile, \"session\");\n await rm(sessionPath(profile), { force: true });\n }, options);\n}\n\nasync function writeFormatOneMetadata(profile: string, store: ProfileStoreName): Promise<void> {\n await writeJsonAtomic(profileStoreMetadataPath(profile, store), { formatVersion: 1 });\n}\n\ninterface AcquiredProfileLock {\n readonly deletedWhileWaiting: boolean;\n readonly turnSlot: number;\n release(): Promise<void>;\n}\n\ninterface LockDeadline {\n readonly startedAt: number;\n readonly timeoutMs: number;\n readonly retryMs: number;\n}\n\n/**\n * Takes a profile's lock in two layers:\n *\n * 1. The turn lock (acquireTurn), used by this release and later ones. It\n * decides nothing from elapsed time, so a process paused anywhere, for\n * any length of time, cannot let a second holder in.\n * 2. Holding a turn, the `.lock` directory every older release uses\n * (publishProfileLock), so they keep excluding this release and it them.\n * Only one process of this release at a time acquires, recovers or\n * releases `.lock`.\n *\n * Also reports whether a profile deletion (recordProfileDeletion) ran while\n * this acquisition waited.\n */\nasync function acquireProfileLock(\n profile: string,\n options: ProfileLockOptions,\n): Promise<AcquiredProfileLock> {\n const deadline: LockDeadline = {\n startedAt: Date.now(),\n timeoutMs: options.timeoutMs ?? DEFAULT_LOCK_TIMEOUT_MS,\n retryMs: options.retryMs ?? DEFAULT_LOCK_RETRY_MS,\n };\n const staleAfterMs = options.staleAfterMs ?? DEFAULT_STALE_LOCK_MS;\n const lockPath = join(profilePath(profile), \".lock\");\n const turnDirectory = profileTurnLockPath(profile);\n\n const directory = profilePath(profile);\n await assertRealProfileDirectory(profile);\n if (options.requireProfile && !(await exists(profileConfigPath(profile)))) throw new ProfileDeletedError(profile);\n if (!options.requireProfile) await mkdir(directory, { recursive: true, mode: PRIVATE_DIR_MODE });\n await assertRealProfileDirectory(profile);\n await mkdir(dirname(turnDirectory), { recursive: true, mode: PRIVATE_DIR_MODE });\n // Every store write takes this lock: tighten the tree older releases\n // created 0775 before writing sessions or delegations into it.\n await chmod(directory, PRIVATE_DIR_MODE);\n\n // Bound before this acquisition's first turn record (or first probe of\n // another's socket), closed once the acquisition is over.\n const liveness: AcquisitionLiveness = { directory: turnDirectory, held: undefined };\n const closeAcquisitionLiveness = async () => {\n if (liveness.held) await closeLiveness(liveness.held);\n };\n let taken: { turn: Turn; firstWaitedTurn: number };\n try {\n taken = await acquireTurn(profile, turnDirectory, deadline, liveness);\n } catch (error) {\n await closeAcquisitionLiveness();\n throw error;\n }\n const { turn, firstWaitedTurn } = taken;\n try {\n // Holding the turn, no deletion runs: a profile present now stays until release.\n if (options.requireProfile && !(await exists(profileConfigPath(profile)))) {\n // A deletion that finished while this waited could not remove the\n // directory if a lock was held then; remove it if empty.\n await rmdir(directory).catch(() => undefined);\n throw new ProfileDeletedError(profile);\n }\n // An unreadable deletion record is ignored rather than failing every\n // acquisition; it is written durably, so only damage makes it so.\n const deletion = await readJson<{ slot?: unknown }>(join(turnDirectory, \"deleted.json\")).catch(() => null);\n const deletedWhileWaiting = typeof deletion?.slot === \"number\" && deletion.slot >= firstWaitedTurn;\n if (deletedWhileWaiting && !(await exists(profileConfigPath(profile)))) throw new ProfileDeletedError(profile);\n while (true) {\n const token = randomUUID();\n if (await publishProfileLock(profile, lockPath, token, options.requireProfile === true, liveness.held ?? null)) {\n return {\n deletedWhileWaiting,\n turnSlot: turn.slot,\n release: async () => {\n try {\n await releaseProfileLock(profile, lockPath, token);\n } finally {\n await releaseTurn(profile, turn, liveness);\n await closeAcquisitionLiveness();\n }\n },\n };\n }\n await signalTestLockContention(profile);\n if (await recoverStaleLock(profile, lockPath, liveness, staleAfterMs)) continue;\n await waitOrTimeOut(profile, deadline);\n }\n } catch (error) {\n await releaseTurn(profile, turn, liveness);\n await closeAcquisitionLiveness();\n throw error;\n }\n}\n\n/**\n * An acquisition's liveness socket in its turn directory: undefined until\n * bound, null if it could not be. Same-PID records are judged in that\n * directory, with this socket as the control (probeLiveness).\n */\ninterface AcquisitionLiveness {\n readonly directory: string;\n held: HeldLiveness | null | undefined;\n}\n\n/** Throws once the acquisition's time is up; `detail` explains a damaged turn lock. */\nfunction checkDeadline(profile: string, deadline: LockDeadline, detail?: string): void {\n if (Date.now() - deadline.startedAt >= deadline.timeoutMs) {\n throw new ProfileLockTimeoutError(profile, deadline.timeoutMs, detail);\n }\n}\n\n/** Sleeps one retry interval, or throws once the acquisition's time is up. */\nasync function waitOrTimeOut(profile: string, deadline: LockDeadline, detail?: string): Promise<void> {\n checkDeadline(profile, deadline, detail);\n await sleep(Math.min(deadline.retryMs, deadline.timeoutMs - (Date.now() - deadline.startedAt)));\n}\n\n/** One granted turn of the turn lock. */\ninterface Turn {\n readonly directory: string;\n readonly slot: number;\n readonly token: string;\n}\n\n/** A turn as recorded on disk. */\ninterface TurnState extends RecordedProcess {\n readonly slot: number;\n readonly token: string;\n readonly pid: number;\n /** The token of the turn before it that its creator observed; null for turn 0. */\n readonly after: string | null;\n readonly held: boolean;\n readonly done: boolean;\n}\n\n/** Turn directory names: decimal turn numbers. */\nconst TURN = /^(?:0|[1-9][0-9]*)$/;\n\n/**\n * Tokens of turns this process published and then gave up (a marker write or\n * a read failed after publication): the positive evidence that lets this\n * process settle a turn of its own PID. A live process's turn that is not in\n * here is never settled by it, so a module instance that cannot see another\n * instance's set (another bundled copy of this package) waits rather than\n * settling a turn that copy still holds.\n *\n * One set per process, not per module instance: the package's entry points\n * (state, index, artifacts, cli-runtime; ESM and CJS) each bundle their own\n * copy of this module, and one process may load several. The key is\n * versioned so that a release changing what the set holds uses a new one.\n */\nconst relinquishedTurns: Set<string> =\n (globalThis as unknown as Record<symbol, Set<string> | undefined>)[Symbol.for(\"tinycloud.operations.relinquishedProfileTurns.v1\")] ??=\n new Set<string>();\n\n/** Marker writes are retried this many times, after these delays (ms), before giving up. */\nconst MARKER_RETRY_DELAYS_MS = [10, 50, 250];\n/** A given-up turn is settled in the background every second for up to five minutes. */\nconst SETTLE_RETRY_MS = 1_000;\nconst SETTLE_RETRIES = 300;\n\n/**\n * The turn lock: mutual exclusion among processes of this release that does\n * not depend on how long any of them is paused. It uses only `mkdir`,\n * `rename`, exclusive file creation, and isRecordedProcessAlive (also used\n * for `.lock` recovery), so it needs no hard links.\n *\n * Turns are numbered directories in profileTurnLockPath(profile). Turn n has\n * `owner.json` ({ pid, createdAt, token, after, instanceId, ...origin }), where\n * `after` is the token of its observed predecessor. It gains `<token>.held`\n * when granted and `<token>.done` when over. Markers name the turn's own token,\n * so one written late into a recreated directory of the same number means nothing.\n *\n * - A turn is published by renaming a fully written staging directory to\n * `n`, which fails while `n` exists (rename never replaces a non-empty\n * directory, and turn directories are never empty). The turn directory\n * itself is published the same way, with a granted, finished turn 0.\n * - A process takes turn n only after reading turn n-1 granted and done.\n * Once `n` is published it checks that turn n-1 is still the one it read\n * (same token, granted, done) and only then writes `held`; otherwise the\n * turn is void: it writes `done` alone and starts over.\n * - Turns are removed by renaming them aside: a void turn at any time, a\n * granted one only once no older turn is left and turn n+1 is granted and\n * done. The latest turn is never removed.\n *\n * Why at most one granted turn is ever not done: a removed number can be\n * published again, but turn n-1 is always removed before turn n, so a\n * process whose stale read led it to republish n finds n-1 gone or holding\n * another token, and its turn is void. A process that finds n-1 as it read\n * it published `n` for the first time, so it is the only process granted n,\n * and n-1 was done before. A turn is marked done only by its holder, by a\n * process with evidence the holder is gone, or by its own process once it\n * explicitly gave the turn up; a paused holder is alive.\n * A turn is voided only on definite evidence (the turn it follows is gone or\n * holds another token), never because a read failed. Nothing above is\n * decided from elapsed time.\n *\n * Returns the turn and the number of the first turn this acquisition waited\n * on: the latest turn when it first looked if that was not done yet, else\n * the next one.\n */\nasync function acquireTurn(\n profile: string,\n directory: string,\n deadline: LockDeadline,\n liveness: AcquisitionLiveness,\n): Promise<{ turn: Turn; firstWaitedTurn: number }> {\n let firstWaitedTurn: number | undefined;\n // Why the turn lock looks damaged, as of the last look, for the timeout message.\n let problem: string | undefined;\n const damaged = (what: string) =>\n `The turn lock ${directory} ${what}, so no process can take it. ` +\n `If no tc or MCP process is using profile \"${profile}\", remove that directory.`;\n for (let first = true; ; first = false) {\n if (!first) checkDeadline(profile, deadline, problem);\n problem = undefined;\n const latest = await readLatestTurn(directory);\n if (latest === \"missing\") {\n await createTurnDirectory(directory);\n continue;\n }\n if (latest === \"empty\") {\n // Usually a listing that straddled a turn's publication and an older\n // turn's removal (readdir is not a snapshot); damaged if it persists.\n problem = damaged(\"has no turns\");\n await waitOrTimeOut(profile, deadline, problem);\n continue;\n }\n if (latest === null) continue;\n if (typeof latest === \"number\") {\n problem = damaged(`has an unreadable turn ${latest}`);\n await waitOrTimeOut(profile, deadline, problem);\n continue;\n }\n firstWaitedTurn ??= latest.done ? latest.slot + 1 : latest.slot;\n if (latest.held && latest.done) {\n const turn = await takeTurn(profile, directory, latest, await ensureLiveness(liveness));\n if (turn !== null) return { turn, firstWaitedTurn };\n await waitOrTimeOut(profile, deadline);\n } else if (latest.done) {\n problem = damaged(`ends with turn ${latest.slot}, which was never granted`);\n await waitOrTimeOut(profile, deadline, problem);\n } else if (\n (latest.pid === process.pid && relinquishedTurns.has(latest.token)) ||\n !await isRecordedProcessAlive(latest, liveness)\n ) {\n await waitForTestBarrier(TEST_LOCK_TURN_SETTLE_BARRIER_DIR, profile);\n await settleAbandonedTurn(directory, latest);\n relinquishedTurns.delete(latest.token);\n } else {\n await signalTestLockContention(profile);\n await waitOrTimeOut(profile, deadline);\n }\n }\n}\n\n/**\n * The highest-numbered turn: its state, its number if it is damaged, null if\n * it went away while being read.\n */\nasync function readLatestTurn(directory: string): Promise<TurnState | number | \"missing\" | \"empty\" | null> {\n let names: string[];\n try {\n names = await readdir(directory);\n } catch (error) {\n if (isErrno(error, \"ENOENT\")) return \"missing\";\n throw error;\n }\n const slots = names.filter((name) => TURN.test(name)).map(Number);\n if (slots.length === 0) return \"empty\";\n const slot = Math.max(...slots);\n const turn = await readTurn(directory, slot);\n return turn === \"damaged\" ? slot : turn;\n}\n\n/**\n * Turn `slot` as recorded now; null if it is gone; \"damaged\" if its owner\n * record is missing or unreadable (turns are renamed into place and aside\n * whole, with their owner record written durably). Any other read error is\n * thrown: it says nothing about the turn. `done` is read before `held`,\n * which every writer creates first, so a turn read as done is never misread\n * as void.\n */\nasync function readTurn(directory: string, slot: number): Promise<TurnState | \"damaged\" | null> {\n const path = join(directory, String(slot));\n let text: string;\n try {\n text = await readFile(join(path, \"owner.json\"), \"utf8\");\n } catch (error) {\n if (!isErrno(error, \"ENOENT\") && !isErrno(error, \"ENOTDIR\")) throw error;\n return await exists(path) ? \"damaged\" : null;\n }\n let owner: (RecordedProcess & { token?: unknown; after?: unknown }) | null;\n try {\n owner = JSON.parse(text) as typeof owner;\n } catch {\n return \"damaged\";\n }\n if (owner === null || typeof owner !== \"object\" || typeof owner.token !== \"string\" || typeof owner.pid !== \"number\") {\n return \"damaged\";\n }\n const done = await exists(join(path, `${owner.token}.done`));\n const held = await exists(join(path, `${owner.token}.held`));\n return {\n ...owner,\n slot,\n token: owner.token,\n pid: owner.pid,\n after: typeof owner.after === \"string\" ? owner.after : null,\n held,\n done,\n };\n}\n\n/**\n * Whether turn `slot` is still the granted, finished turn with `token`. False\n * only on definite evidence: it is gone, or holds another token. Throws if it\n * cannot be read, or is damaged, so the caller leaves its own turn undecided.\n */\nasync function isFinishedTurn(directory: string, slot: number, token: string): Promise<boolean> {\n const turn = await readTurn(directory, slot);\n if (turn === \"damaged\") throw new Error(`Turn ${slot} of the turn lock ${directory} is unreadable.`);\n return turn !== null && turn.token === token && turn.held && turn.done;\n}\n\n/**\n * Publishes the turn after `latest` and claims it. Null if another process\n * published that number first, or the turn is void. If a step after\n * publication fails, the turn is handed to settleOwnTurn and the error thrown.\n */\nasync function takeTurn(profile: string, directory: string, latest: TurnState, liveness: HeldLiveness | null): Promise<Turn | null> {\n const token = randomUUID();\n const slot = latest.slot + 1;\n const path = join(directory, String(slot));\n const owner = { pid: process.pid, createdAt: new Date().toISOString(), token, after: latest.token, ...processOwner(liveness) };\n let published = false;\n try {\n published = await publishDirectory(\n join(directory, `.stage-${process.pid}-${token}`),\n path,\n { \"owner.json\": `${JSON.stringify(owner, null, 2)}\\n` },\n () => waitForTestBarrier(TEST_LOCK_TURN_PUBLISH_BARRIER_DIR, profile),\n );\n if (published && await isFinishedTurn(directory, latest.slot, latest.token) && await markTurn(path, token, \"held\")) {\n return { directory, slot, token };\n }\n if (published) await markTurn(path, token, \"done\");\n return null;\n } catch (error) {\n if (published) await settleOwnTurn(directory, slot, token);\n throw error;\n }\n}\n\n/**\n * Finishes the turn of a process that is gone (or one this process gave up).\n * A turn its creator never got to grant is granted first if the turn before\n * it is still the one its creator read (the check the creator would have\n * made; that turn cannot have been removed, since that needs this turn\n * done), and left void only if it definitely is not.\n */\nasync function settleAbandonedTurn(directory: string, abandoned: TurnState): Promise<void> {\n const path = join(directory, String(abandoned.slot));\n if (!abandoned.held && abandoned.after !== null &&\n await isFinishedTurn(directory, abandoned.slot - 1, abandoned.after)) {\n await markTurn(path, abandoned.token, \"held\");\n }\n await markTurn(path, abandoned.token, \"done\");\n}\n\n/**\n * Settles a turn this process published but could not grant or finish (a\n * marker write or a read failed). Other processes wait on this live PID, so\n * it is retried in the background until it succeeds or the turn is settled\n * otherwise (by this process's next acquisition, or by another process once\n * this one has exited). The timer does not keep the process alive.\n */\nasync function settleOwnTurn(directory: string, slot: number, token: string): Promise<void> {\n relinquishedTurns.add(token);\n const pinned = await pinDirectory(directory).catch(() => null);\n const path = pinned?.path(\".\") ?? directory;\n let retries = SETTLE_RETRIES;\n const attempt = () => {\n void (async () => {\n try {\n const turn = await readTurn(path, slot);\n if (turn !== null && turn !== \"damaged\" && turn.token === token && !turn.done) await settleAbandonedTurn(path, turn);\n relinquishedTurns.delete(token);\n await pinned?.close();\n } catch {\n // Still relinquished: this process's next acquisition settles it too.\n if (retries-- > 0) setTimeout(attempt, SETTLE_RETRY_MS).unref();\n else await pinned?.close();\n }\n })();\n };\n attempt();\n}\n\n/**\n * Creates `<token>.<kind>` in a turn directory; false if the directory is\n * gone. Other failures (a full disk, a permission error) are retried a few\n * times, then thrown.\n */\nasync function markTurn(path: string, token: string, kind: \"held\" | \"done\"): Promise<boolean> {\n for (let attempt = 0; ; attempt++) {\n try {\n await writeFile(join(path, `${token}.${kind}`), \"\", { mode: PRIVATE_FILE_MODE, flag: \"wx\" });\n return true;\n } catch (error) {\n if (isErrno(error, \"EEXIST\")) return true;\n if (isErrno(error, \"ENOENT\")) return false;\n if (attempt >= MARKER_RETRY_DELAYS_MS.length) throw error;\n await sleep(MARKER_RETRY_DELAYS_MS[attempt]!);\n }\n }\n}\n\n/** Creates the turn directory with a granted, finished turn 0, unless another process did. */\nasync function createTurnDirectory(directory: string): Promise<void> {\n const token = randomUUID();\n const owner = { pid: process.pid, createdAt: new Date().toISOString(), token, after: null, ...processOwner(null) };\n await publishDirectory(join(dirname(directory), `.stage-${basename(directory)}-${process.pid}-${token}`), directory, {\n \"0/owner.json\": `${JSON.stringify(owner, null, 2)}\\n`,\n [`0/${token}.held`]: \"\",\n [`0/${token}.done`]: \"\",\n });\n}\n\n/**\n * Writes `files` (durably, when not empty) into a new staging directory and\n * renames it to `target`. False if `target` already exists (or the turn\n * directory is gone); the caller starts over. Nothing else removes a live\n * process's staging directory (see collectTurns), so `target` never appears\n * incomplete.\n */\nasync function publishDirectory(\n stage: string,\n target: string,\n files: Readonly<Record<string, string>>,\n beforeRename?: () => Promise<void>,\n): Promise<boolean> {\n try {\n for (const [name, contents] of Object.entries(files)) {\n await mkdir(dirname(join(stage, name)), { recursive: true, mode: PRIVATE_DIR_MODE });\n await writeFileDurably(join(stage, name), contents);\n }\n await beforeRename?.();\n await rename(stage, target);\n return true;\n } catch (error) {\n // ENOTEMPTY/EEXIST: `target` exists, though it may be gone again by now.\n // Windows reports an existing target directory as EPERM.\n if ([\"ENOTEMPTY\", \"EEXIST\", \"ENOENT\"].some((code) => isErrno(error, code)) || await exists(target)) return false;\n throw error;\n } finally {\n await rm(stage, { recursive: true, force: true }).catch(() => undefined);\n }\n}\n\n/**\n * Finishes this process's turn. If `done` cannot be written even after\n * retries, the critical section is still over and its writes stand: the turn\n * is handed to settleOwnTurn rather than failing the caller.\n */\nasync function releaseTurn(profile: string, turn: Turn, liveness: AcquisitionLiveness): Promise<void> {\n try {\n await markTurn(join(turn.directory, String(turn.slot)), turn.token, \"done\");\n } catch {\n await settleOwnTurn(turn.directory, turn.slot, turn.token);\n return;\n }\n await collectTurns(profile, turn, liveness).catch(() => undefined);\n}\n\n/**\n * Leftovers in a turn directory whose writer is gone: trash (a removed turn\n * renamed aside, garbage from then on), and staging directories and\n * deletion-record temporaries named after their writer's PID. A staging\n * directory named after this process's PID is judged by the owner record\n * staged in it, which a predecessor with this PID may have left.\n */\nasync function isAbandonedTurnEntry(directory: string, name: string, liveness: AcquisitionLiveness): Promise<boolean> {\n if (name.startsWith(\".trash-\")) return true;\n const writer = /^\\.(?:stage|deleted\\.json)[-.]([0-9]+)[-.]/.exec(name)?.[1];\n if (writer === undefined) return false;\n if (Number(writer) !== process.pid) return !await isRecordedProcessAlive({ pid: Number(writer) }, liveness);\n return name.startsWith(\".stage-\") && await isStagedOwnerGone(join(directory, name, \"owner.json\"), liveness);\n}\n\n/** A turn directory's staging directory left beside it by a process that is gone. */\nasync function isAbandonedTurnDirectoryStage(directory: string, name: string, liveness: AcquisitionLiveness): Promise<boolean> {\n const writer = /^\\.stage-.+-([0-9]+)-[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/.exec(name)?.[1];\n if (writer === undefined) return false;\n if (Number(writer) !== process.pid) return !await isRecordedProcessAlive({ pid: Number(writer) }, liveness);\n return isStagedOwnerGone(join(directory, name, \"0\", \"owner.json\"), liveness);\n}\n\n/**\n * Whether a complete staged owner record of this PID belongs to a process\n * that is gone. Its socket, if any, goes with the next sweep (sweepLiveness).\n */\nasync function isStagedOwnerGone(recordPath: string, liveness: AcquisitionLiveness): Promise<boolean> {\n const owner = await readJson<RecordedProcess>(recordPath).catch(() => null);\n return typeof owner === \"object\" && owner !== null && owner.pid === process.pid &&\n !await isRecordedProcessAlive(owner, liveness);\n}\n\n/**\n * Removes the turns before `turn`, which just finished: void turns, and\n * granted ones oldest first, each once no older turn is left and the next is\n * granted and done (see acquireTurn). A turn is renamed aside before it is\n * deleted, so its number is never an empty directory. A turn that is damaged\n * or cannot be read is left alone, and a read error stops the collection.\n * Leftovers of crashed processes go too, judged by PID (or, for this PID, by\n * the record they hold), never by age: removing a paused process's staging\n * directory could publish an incomplete turn. Last, liveness sockets the\n * kernel refuses are swept (sweepLiveness).\n */\nasync function collectTurns(profile: string, turn: Turn, liveness: AcquisitionLiveness): Promise<void> {\n for (const [directory, abandoned] of [\n [turn.directory, isAbandonedTurnEntry],\n [dirname(turn.directory), isAbandonedTurnDirectoryStage],\n ] as const) {\n for (const name of await readdir(directory)) {\n if (await abandoned(directory, name, liveness)) await rm(join(directory, name), { recursive: true, force: true }).catch(() => undefined);\n }\n }\n const older = (await readdir(turn.directory)).filter((name) => TURN.test(name)).map(Number)\n .filter((slot) => slot < turn.slot)\n .sort((left, right) => left - right);\n for (const slot of older) {\n const candidate = await readTurn(turn.directory, slot);\n if (candidate === null || candidate === \"damaged\" || !candidate.done) continue;\n if (candidate.held) {\n if (await exists(join(turn.directory, String(slot - 1)))) continue;\n const next = await readTurn(turn.directory, slot + 1);\n if (next === null || next === \"damaged\" || !next.held || !next.done) continue;\n }\n await waitForTestBarrier(TEST_LOCK_TURN_COLLECT_BARRIER_DIR, profile);\n const trash = join(turn.directory, `.trash-${randomUUID()}`);\n if (await rename(join(turn.directory, String(slot)), trash).then(() => true, () => false)) {\n await rm(trash, { recursive: true, force: true }).catch(() => undefined);\n }\n }\n await sweepLiveness(liveness);\n}\n\n/**\n * One attempt to take `.lock`, made while holding a turn. Nothing here\n * replaces an existing path:\n *\n * 1. `mkdir(.lock)` (never recursive) is exclusive, and is the primitive\n * older releases use too, so they exclude each other.\n * 2. Ownership is published by `link`ing a fully written owner file (staged\n * beside `.lock`, on the same filesystem) to `.lock/owner.json`. `link`\n * fails with EEXIST if an owner exists, and with ENOENT if the directory\n * was removed meanwhile (an aged ownerless-lock reclaim by an older\n * release). Only a successful link holds the lock; on either failure\n * nothing is released, since nothing was acquired, and the caller retries.\n * On a filesystem without hard links (FAT/exFAT, some SMB mounts) the\n * owner file is created in place with an exclusive create instead, with\n * the same EEXIST/ENOENT outcomes. A reader may then briefly see it\n * incomplete; every release treats an owner file it cannot parse as a\n * held lock, and this release removes one only once it has aged (see\n * isAbandonedOwner).\n *\n * A profile deletion removes the profile directory once it has released its\n * lock, so `mkdir(.lock)` can see ENOENT; the directory is recreated (0700)\n * and the caller retries within its deadline, as for a first write to a new\n * profile.\n *\n * Only one owner.json can exist at the lock path, and it is removed only by\n * its holder's release or by recovery of an abandoned holder, so at most one\n * process holds the lock. Returns false when the lock is not acquired.\n */\nasync function publishProfileLock(\n profile: string,\n lockPath: string,\n token: string,\n requireProfile: boolean,\n liveness: HeldLiveness | null,\n): Promise<boolean> {\n try {\n await mkdir(lockPath, { mode: PRIVATE_DIR_MODE });\n } catch (error) {\n if (isErrno(error, \"EEXIST\")) return false;\n if (!isErrno(error, \"ENOENT\")) throw error;\n // With requireProfile only an older release's deletion (which takes no turn) gets here.\n if (requireProfile) throw new ProfileDeletedError(profile);\n await mkdir(profilePath(profile), { recursive: true, mode: PRIVATE_DIR_MODE });\n return false;\n }\n await waitForTestBarrier(TEST_LOCK_PUBLISH_BARRIER_DIR, profile);\n const ownerPath = join(lockPath, \"owner.json\");\n const staged = join(dirname(lockPath), `.lock-owner-${token}.tmp`);\n const owner = `${JSON.stringify({ pid: process.pid, createdAt: new Date().toISOString(), token, ...processOwner(liveness) }, null, 2)}\\n`;\n try {\n await writeFile(staged, owner, { encoding: \"utf8\", mode: PRIVATE_FILE_MODE, flag: \"wx\" });\n try {\n await link(staged, ownerPath);\n } catch (error) {\n if (!lacksHardLinks(error)) throw error;\n await writeFile(ownerPath, owner, { encoding: \"utf8\", mode: PRIVATE_FILE_MODE, flag: \"wx\" });\n }\n return true;\n } catch (error) {\n if (isErrno(error, \"EEXIST\") || isErrno(error, \"ENOENT\")) return false;\n // Do not leave an ownerless lock behind for others to wait out. rmdir\n // removes only an empty directory; anyone who created it after ours went\n // away sees ENOENT on link and retries.\n await rmdir(lockPath).catch(() => undefined);\n throw error;\n } finally {\n await rm(staged, { force: true }).catch(() => undefined);\n }\n}\n\n/**\n * Errors `link` reports on a filesystem without hard links: EPERM (Linux\n * FAT/exFAT), ENOTSUP/EOPNOTSUPP (macOS FAT/exFAT, SMB), ENOSYS, EISDIR\n * (libuv's code for Windows' ERROR_INVALID_FUNCTION on FAT32), and EMLINK\n * (too many links), for which the same fallback is just as correct.\n */\nconst NO_HARD_LINK_ERRORS = new Set([\"EPERM\", \"ENOTSUP\", \"EOPNOTSUPP\", \"ENOSYS\", \"EISDIR\", \"EMLINK\"]);\n\nfunction lacksHardLinks(error: unknown): boolean {\n return typeof error === \"object\" && error !== null && \"code\" in error &&\n NO_HARD_LINK_ERRORS.has(String((error as { code?: unknown }).code));\n}\n\n/**\n * Releases the lock if `.lock/owner.json` is still this acquisition's. No\n * one else can put an owner record there while it exists (owners are only\n * published into a directory their own `mkdir` created, and recovery\n * removes only an abandoned holder's record), so the record read is the one\n * unlinked.\n */\nasync function releaseProfileLock(profile: string, lockPath: string, token: string): Promise<void> {\n const ownerPath = join(lockPath, \"owner.json\");\n const owner = await readJson<{ token?: unknown }>(ownerPath).catch(() => null);\n if (owner?.token !== token) return;\n await rm(ownerPath, { force: true });\n // `.lock` is empty from here until the rmdir; a contender that removes it\n // and creates its own meanwhile is not harmed: rmdir fails on its owner\n // file, or the contender's link sees ENOENT and retries. If a recoverer's\n // claim file is still inside, rmdir fails and that recoverer removes the\n // directory when it drops its claim.\n await waitForTestBarrier(TEST_LOCK_RELEASE_BARRIER_DIR, profile);\n await rmdir(lockPath).catch(() => undefined);\n}\n\n/**\n * Gives process-level contention tests an event-driven witness that a second\n * writer has reached an already-held lock. This deliberately has no effect\n * outside an explicit test environment, and only creates a new signal file\n * beneath TC_HOME; it never changes lock acquisition or release semantics.\n */\nasync function signalTestLockContention(profile: string): Promise<void> {\n if (process.env.NODE_ENV !== \"test\") return;\n\n const configuredPath = process.env[TEST_LOCK_CONTENTION_SIGNAL_PATH];\n const configuredHome = process.env.TC_HOME;\n if (!configuredPath || !configuredHome) return;\n\n const home = resolve(configuredHome);\n const signalPath = resolve(configuredPath);\n const pathFromHome = relative(home, signalPath);\n if (\n pathFromHome === \"\" ||\n pathFromHome === \"..\" ||\n pathFromHome.startsWith(`..${process.platform === \"win32\" ? \"\\\\\" : \"/\"}`) ||\n pathFromHome.startsWith(\"../\") ||\n pathFromHome.startsWith(\"..\\\\\")\n ) {\n return;\n }\n\n await writeFile(signalPath, `${profile}\\n`, { encoding: \"utf8\", flag: \"wx\" })\n .catch(() => undefined);\n}\n\n/**\n * Reclaims a `.lock` with no owner record, while holding a turn. A\n * `.recover-*` claim may contain a live legacy owner's record moved by a\n * no-hard-links recoverer just before it crashed. Restore that record first;\n * only orphaned claims holding dead or malformed records can be removed.\n * Otherwise the directory must be older than the stale threshold: one left\n * by a crash, or by an older release, whose `.stale-*` / `.release-*` claim\n * files are removed too. owner.json is never unlinked, and the directory\n * itself is only `rmdir`ed, which succeeds only while it is empty.\n *\n * Holding a turn, no other process of this release reclaims concurrently.\n * That closes the race in which two reclaimers' age checks let the later one\n * remove a directory a 1.0.0-beta.16-or-older release had just created,\n * which holds the lock from its `mkdir`. That race remains only if a\n * 1.0.0-beta.17 … 1.0.1-beta.4 release reclaims at the same moment, or the\n * older process stays paused between its `mkdir` and its owner write for\n * longer than the stale threshold; recoveryFenced bounds how stale this\n * process's age check can be.\n */\nasync function recoverOwnerlessLock(profile: string, lockPath: string, liveness: AcquisitionLiveness, staleAfterMs: number): Promise<boolean> {\n let aged: boolean;\n try {\n const { mtimeMs } = await stat(lockPath);\n aged = Date.now() - mtimeMs >= staleAfterMs;\n } catch {\n return false;\n }\n const checkedAt = performance.now();\n await waitForTestBarrier(TEST_LOCK_OWNERLESS_BARRIER_DIR, profile);\n try {\n const entries = await readdir(lockPath);\n if (entries.includes(\"owner.json\")) return false;\n const onlyOrphanedClaims = entries.length > 0 && entries.every((entry) => RECOVERY_CLAIM.test(entry));\n if (!onlyOrphanedClaims && (!aged || recoveryFenced(checkedAt, staleAfterMs))) return false;\n for (const name of entries.filter((entry) => RECOVERY_CLAIM.test(entry))) {\n const claimPath = join(lockPath, name);\n if (await hasLiveRecoveryOwner(claimPath, liveness)) {\n // The claim keeps the directory in place. Only the original mkdir\n // owner could publish owner.json here; never overwrite that record.\n if (!await exists(join(lockPath, \"owner.json\"))) await rename(claimPath, join(lockPath, \"owner.json\"));\n return false;\n }\n await rm(claimPath, { force: true });\n }\n for (const name of entries.filter((entry) => ABANDONED_CLAIM.test(entry))) {\n await rm(join(lockPath, name), { force: true });\n }\n await rmdir(lockPath);\n } catch {\n return false;\n }\n await removeAgedOwnerFiles(lockPath, staleAfterMs);\n return true;\n}\n\n/** A no-hard-links claim may contain a live owner's record moved before a crash. */\nasync function hasLiveRecoveryOwner(claimPath: string, liveness: AcquisitionLiveness): Promise<boolean> {\n let contents: string;\n try {\n contents = await readFile(claimPath, \"utf8\");\n } catch (error) {\n if (isErrno(error, \"ENOENT\")) return false;\n throw error; // An I/O failure is not evidence that the holder is gone.\n }\n let owner: unknown;\n try {\n owner = JSON.parse(contents);\n } catch {\n return false;\n }\n const record = typeof owner === \"object\" && owner !== null && \"pid\" in owner ? owner as RecordedProcess : null;\n return typeof record?.pid === \"number\" && Number.isInteger(record.pid) && record.pid > 0 &&\n await isRecordedProcessAlive(record, liveness);\n}\n\n/**\n * Whether an ownerless-reclaim decision made at `since` (a monotonic\n * timestamp) is too old to act on. The age check is only good for a while:\n * past half the stale threshold the reclaim starts over.\n */\nfunction recoveryFenced(since: number, staleAfterMs: number): boolean {\n return performance.now() - since >= staleAfterMs / 2;\n}\n\n/** Claim files of older releases' stale recovery and (up to 1.0.0-beta.16) release. */\nconst ABANDONED_CLAIM = /^\\.(?:release|stale)-[0-9a-f-]+\\.json$/;\n/**\n * Claim files of this release's stale recovery. Releases up to 1.0.1-beta.4\n * never remove them, which is what makes this recovery safe against them; a\n * recoverer that crashes leaves one that only a release with the turn lock\n * clears (see REFERENCE.md, \"Profile lock\").\n */\nconst RECOVERY_CLAIM = /^\\.recover-[0-9a-f-]+\\.json$/;\n/** Owner files staged by publishProfileLock beside `.lock`. */\nconst STAGED_OWNER = /^\\.lock-owner-[0-9a-f-]+\\.tmp$/;\n/**\n * Removes owner files a crashed acquirer staged beside `.lock`. A live\n * acquirer's staged file exists only for its write and link, so only files\n * older than the stale threshold are removed. Runs after a recovery, never\n * on the uncontended path.\n */\nasync function removeAgedOwnerFiles(lockPath: string, staleAfterMs: number): Promise<void> {\n const directory = dirname(lockPath);\n const names = await readdir(directory).catch(() => []);\n for (const name of names.filter((entry) => STAGED_OWNER.test(entry))) {\n const path = join(directory, name);\n const aged = await stat(path).then(({ mtimeMs }) => Date.now() - mtimeMs >= staleAfterMs, () => false);\n if (aged) await rm(path, { force: true }).catch(() => undefined);\n }\n}\n\n\n/**\n * Removes an abandoned owner record (see isAbandonedOwner) and its `.lock`,\n * while holding a turn, so no other process of this release recovers,\n * reclaims or acquires `.lock` meanwhile.\n *\n * The record is claimed first: hard-linked to `.recover-<uuid>.json`, and the\n * claim compared with the exact bytes read. A record that replaced the\n * observed one is left in place. While the claim is inside `.lock`, the\n * directory cannot be removed and recreated: older releases never remove a\n * `.recover-*` claim (they clean up only their own claim names), and other\n * processes of this release are kept out by the turn lock. So no other owner\n * record can appear in it, and the unlink of owner.json can only remove the\n * claimed, dead record. A live record is never unlinked or moved. `.lock`\n * itself is removed only after this process's claim is unlinked from it,\n * which proves it is still the dead holder's directory; if the claim cannot\n * be made, `.lock` is left alone (it may be a directory a 1.0.0-beta.16-or-\n * older writer just created, which it holds from that `mkdir`). No step\n * depends on how long this process takes.\n *\n * Without hard links (where 1.0.0-beta.17 … 1.0.1-beta.4 cannot run at all),\n * the record is renamed into the claim instead, and put back if it is not\n * the observed one: one a 1.0.0-beta.16-or-older process published after\n * recovering the same lock at the same moment. The claim keeps `.lock` held\n * meanwhile, so no second writer gets in, but if that holder releases\n * between the two renames, the put-back restores a released record, and\n * `.lock` stays held until that PID is gone and the record is 30 s old.\n */\nasync function recoverStaleLock(profile: string, lockPath: string, liveness: AcquisitionLiveness, staleAfterMs: number): Promise<boolean> {\n const ownerPath = join(lockPath, \"owner.json\");\n const observed = await readFile(ownerPath, \"utf8\").catch(() => null);\n if (observed === null) return recoverOwnerlessLock(profile, lockPath, liveness, staleAfterMs);\n if (!await isAbandonedOwner(observed, ownerPath, liveness, staleAfterMs)) return false;\n\n await waitForTestBarrier(TEST_LOCK_RECOVERY_BARRIER_DIR, profile);\n const claimPath = join(lockPath, `.recover-${randomUUID()}.json`);\n let moved = false;\n try {\n await link(ownerPath, claimPath);\n } catch (error) {\n // Gone or replaced already (another release recovered it): leave `.lock`.\n if (!lacksHardLinks(error)) return false;\n try {\n await rename(ownerPath, claimPath);\n moved = true;\n } catch {\n return false;\n }\n }\n if (moved) await waitForTestBarrier(TEST_LOCK_MOVED_BARRIER_DIR, profile);\n const sameInstance = await readFile(claimPath, \"utf8\").then((claimed) => claimed === observed, () => false);\n await waitForTestBarrier(TEST_LOCK_VERIFIED_BARRIER_DIR, profile);\n if (!sameInstance) {\n if (moved) {\n // No other owner.json can have appeared meanwhile: only a process\n // whose mkdir created this `.lock` publishes one, and it already has.\n await rename(claimPath, ownerPath).catch(() => undefined);\n } else {\n await unlink(claimPath).catch(() => undefined);\n }\n return false;\n }\n if (!moved) await rm(ownerPath, { force: true });\n await waitForTestBarrier(TEST_LOCK_CLAIM_BARRIER_DIR, profile);\n await waitForTestBarrier(TEST_LOCK_CLAIMED_BARRIER_DIR, profile);\n // Drop any claim a crashed recoverer of this release left, then this one.\n for (const name of (await readdir(lockPath).catch(() => [])).filter((entry) => RECOVERY_CLAIM.test(entry))) {\n if (join(lockPath, name) !== claimPath) await rm(join(lockPath, name), { force: true });\n }\n // Empty now, and still the dead holder's directory if this claim was in\n // it: remove it rather than leave an ownerless lock to age out.\n if (await unlink(claimPath).then(() => true, () => false)) await rmdir(lockPath).catch(() => undefined);\n await removeAgedOwnerFiles(lockPath, staleAfterMs);\n return true;\n}\n\n/**\n * Whether an owner record (its exact bytes, read from `ownerPath`) is\n * abandoned: its holder is confirmed gone (including a proven same-PID\n * predecessor), and it is older than the existing stale threshold; or, if\n * it is not a complete record, the file is older than that threshold.\n * Every release publishes complete records (by link or rename) except this\n * one's no-hard-link fallback, which writes in place while holding a turn;\n * so an incomplete record seen while holding a turn was left by a crash.\n */\nasync function isAbandonedOwner(observed: string, ownerPath: string, liveness: AcquisitionLiveness, staleAfterMs: number): Promise<boolean> {\n let owner: unknown;\n try {\n owner = JSON.parse(observed);\n } catch {\n owner = null;\n }\n if (typeof owner === \"object\" && owner !== null && \"pid\" in owner) {\n return isStaleOwner(owner as RecordedProcess & { createdAt?: unknown }, liveness, staleAfterMs);\n }\n const modifiedAt = await stat(ownerPath).then(({ mtimeMs }) => mtimeMs, () => Number.NaN);\n return Date.now() - modifiedAt >= staleAfterMs;\n}\n\n/** Test-only rendezvous (NODE_ENV=test and the named barrier directory set). */\nasync function waitForTestBarrier(environmentName: string, profile: string): Promise<void> {\n if (process.env.NODE_ENV !== \"test\") return;\n const barrierDirectory = process.env[environmentName];\n if (!barrierDirectory) return;\n await mkdir(barrierDirectory, { recursive: true });\n const readyPath = join(barrierDirectory, `ready-${process.pid}-${profile}`);\n await writeFile(readyPath, \"ready\\n\", { encoding: \"utf8\", flag: \"wx\" }).catch(() => undefined);\n const releasePath = join(barrierDirectory, \"release\");\n while (true) {\n try {\n await readFile(releasePath, \"utf8\");\n return;\n } catch {\n await sleep(1);\n }\n }\n}\n\nasync function isStaleOwner(\n owner: (RecordedProcess & { createdAt?: unknown }) | null,\n liveness: AcquisitionLiveness,\n staleAfterMs: number,\n): Promise<boolean> {\n const now = Date.now();\n const createdAt = typeof owner?.createdAt === \"string\"\n ? Date.parse(owner.createdAt)\n : Number.NaN;\n\n // Reclaim only a fully published lock whose owner is confirmed gone.\n // A lock without an owner record is handled by recoverOwnerlessLock: a\n // process holds the lock only once its owner.json is linked in.\n if (!Number.isFinite(createdAt) || now - createdAt < staleAfterMs) return false;\n if (typeof owner?.pid !== \"number\" || !Number.isInteger(owner.pid) || owner.pid <= 0) {\n return false;\n }\n return !await isRecordedProcessAlive(owner, liveness);\n}\n\n/** Whether `path` exists. Only ENOENT/ENOTDIR mean it does not; other errors are thrown. */\nasync function exists(path: string): Promise<boolean> {\n try {\n await stat(path);\n return true;\n } catch (error) {\n if (isErrno(error, \"ENOENT\") || isErrno(error, \"ENOTDIR\")) return false;\n throw error;\n }\n}\n\n/**\n * Whether the process that wrote an owner record may still be in its\n * critical section. A record of another PID is alive while that PID exists\n * (master's rule). A record of this PID but another instance is dead only on\n * authoritative evidence from the same kernel boot: in this PID namespace, a\n * different process start (the PID was reused); in another PID namespace,\n * its liveness socket, bound in this acquisition's turn directory, gone or\n * refused by the kernel (probeLiveness). A paused holder still accepts.\n * Everything else waits.\n */\nasync function isRecordedProcessAlive(owner: RecordedProcess, acquisition: AcquisitionLiveness): Promise<boolean> {\n const pid = owner.pid;\n if (typeof pid !== \"number\" || !Number.isInteger(pid) || pid <= 0) return true;\n try {\n process.kill(pid, 0);\n } catch (error) {\n return !isErrno(error, \"ESRCH\");\n }\n if (pid !== process.pid || typeof owner.instanceId !== \"string\" ||\n !/^[0-9a-f]{8}-(?:[0-9a-f]{4}-){3}[0-9a-f]{12}$/.test(owner.instanceId) ||\n owner.instanceId === processInstanceId) return true;\n const origin = processOrigin();\n if (!origin || owner.bootId !== origin.bootId ||\n typeof owner.pidNamespace !== \"string\" || !/^pid:\\[[0-9]+\\]$/.test(owner.pidNamespace)) return true;\n if (owner.pidNamespace === origin.pidNamespace) {\n return typeof owner.processStartTime !== \"string\" || !/^[0-9]+$/.test(owner.processStartTime) ||\n owner.processStartTime === origin.processStartTime;\n }\n const liveness = parseLiveness(owner.liveness);\n return liveness === null || await probeLiveness(liveness, acquisition) === \"unknown\";\n}\n\nfunction validateProfileName(profile: string): string {\n if (\n !profile ||\n profile === \".\" ||\n profile === \"..\" ||\n profile.includes(\"/\") ||\n profile.includes(\"\\\\\") ||\n profile.includes(\"\\0\")\n ) {\n throw new TypeError(\"Profile names must be non-empty path segments.\");\n }\n return profile;\n}\n\nfunction isErrno(error: unknown, code: string): boolean {\n return typeof error === \"object\" && error !== null && \"code\" in error &&\n (error as { code?: unknown }).code === code;\n}\n\nfunction sleep(milliseconds: number): Promise<void> {\n return new Promise((resolve) => setTimeout(resolve, milliseconds));\n}\n\nexport { DEFAULT_PROFILE };\n","import { randomUUID } from \"node:crypto\";\n\nimport type { PermissionEntry } from \"@tinycloud/sdk-core\";\nimport { z } from \"zod\";\n\nimport {\n canonicalizeCapabilities,\n evaluateOperationAuthority,\n evaluateAuthority,\n} from \"./authority.js\";\nimport type { OperationSpaceResolver } from \"./authority.js\";\nimport type {\n OperationOperatorType,\n TinyCloudPosture,\n} from \"./contract.js\";\nimport { readAuthRequests, updateProfileStore } from \"./state.js\";\n\nexport { evaluateAuthority } from \"./authority.js\";\n\nconst THIRTY_DAYS_MS = 30 * 24 * 60 * 60 * 1_000;\n\n/** The shared wire schema for SDK PermissionEntry values, including signed ReCap caveats. */\nexport const PermissionEntrySchema = z.object({\n service: z.string().min(1),\n space: z.string().min(1).optional(),\n path: z.string(),\n actions: z.array(z.string().min(1)).min(1),\n caveats: z.array(z.record(z.unknown())).optional(),\n skipPrefix: z.boolean().optional(),\n expiry: z.string().min(1).optional(),\n description: z.string().optional(),\n}).strict();\n\nconst CommandSchema = z.object({\n argv: z.array(z.string()).min(1),\n cwd: z.string().min(1),\n}).strict();\n\nconst LegacyPermissionRequestArtifactSchema = z.object({\n kind: z.literal(\"tinycloud.auth.request\"),\n version: z.literal(1),\n requestId: z.string(),\n sessionDid: z.string().min(1),\n requested: z.array(PermissionEntrySchema),\n requestedExpiry: z.union([z.string(), z.number().finite()]).optional(),\n}).strict();\n\nconst PortableDelegationSchema: z.ZodType<Record<string, unknown>> = z.lazy(() => z.object({\n cid: z.string().min(1),\n spaceId: z.string().min(1),\n path: z.string(),\n actions: z.array(z.string().min(1)).min(1),\n delegateDID: z.string().min(1),\n ownerAddress: z.string().min(1),\n chainId: z.number().int().positive(),\n expiry: z.union([z.string().datetime({ offset: true }), z.date()]),\n delegationHeader: z.object({ Authorization: z.string().min(1) }).strict(),\n host: z.string().min(1).optional(),\n disableSubDelegation: z.boolean().optional(),\n publicDelegation: PortableDelegationSchema.optional(),\n resources: z.array(PermissionEntrySchema).optional(),\n}).passthrough());\n\n/** Canonical wire-format v1 request retained for CLI and OpenKey compatibility. */\nexport const PermissionRequestArtifactSchema = z.object({\n kind: z.literal(\"tinycloud.auth.request\"),\n version: z.literal(1),\n requestId: z.string().min(1),\n createdAt: z.string().datetime({ offset: true }),\n profile: z.string().min(1),\n posture: z.enum([\"owner-openkey\", \"delegate-session\", \"local-owner-key\", \"unauthenticated\"]),\n operatorType: z.enum([\"human\", \"agent\"]),\n host: z.string().min(1),\n sessionDid: z.string().min(1),\n ownerDid: z.string().min(1).optional(),\n spaceId: z.string().min(1).optional(),\n requestedExpiry: z.union([z.string().min(1), z.number().finite()]).optional(),\n requested: z.array(PermissionEntrySchema),\n command: CommandSchema.optional(),\n}).strict();\n\n/** Canonical structured v1 delegation import accepted by the auth operation. */\nexport const DelegationImportArtifactSchema = z.object({\n kind: z.literal(\"tinycloud.auth.delegation\"),\n version: z.literal(1),\n requestId: z.string().min(1),\n delegationCid: z.string().min(1).optional(),\n delegation: PortableDelegationSchema,\n // This field is presentation metadata only. A later import operation obtains\n // effective permissions from validated, installed delegation authority.\n permissions: z.array(PermissionEntrySchema).optional(),\n expiry: z.string().datetime({ offset: true }).optional(),\n prompted: z.boolean().optional(),\n}).strict();\n\n/**\n * The stored request an imported delegation was checked against, persisted\n * with the delegation. Its fields are exactly the request artifact's, so\n * replay accepts every binding import can write.\n */\nexport const DelegationRequestBindingSchema = PermissionRequestArtifactSchema\n .pick({ requestId: true, requested: true })\n .strict();\nexport type DelegationRequestBinding = z.infer<typeof DelegationRequestBindingSchema>;\n\nexport type PermissionRequestArtifact = z.infer<typeof PermissionRequestArtifactSchema>;\nexport type DelegationImportArtifact = z.infer<typeof DelegationImportArtifactSchema>;\n/** The minimal public node-sdk request shape accepted by the legacy reader. */\nexport type LegacyPermissionRequestArtifact = z.infer<typeof LegacyPermissionRequestArtifactSchema>;\nexport type ArtifactClock = () => Date;\nexport type RequestIdSource = () => string;\n\n/** Invocation-owned values used to safely complete a legacy request record. */\nexport interface LegacyRequestContext {\n readonly profile: string;\n readonly host: string;\n readonly sessionDid: string;\n readonly posture: TinyCloudPosture;\n readonly operatorType: OperationOperatorType;\n readonly ownerDid?: string;\n readonly spaceId?: string;\n readonly now?: Date;\n}\n\nexport interface PermissionRequestIdentityInput {\n readonly profile: string;\n readonly sessionDid: string;\n readonly host: string;\n readonly missing: readonly PermissionEntry[];\n}\n\nexport interface BuildPermissionRequestArtifactInput extends PermissionRequestIdentityInput {\n readonly posture: TinyCloudPosture;\n readonly operatorType: OperationOperatorType;\n readonly ownerDid?: string;\n readonly spaceId?: string;\n readonly requestedExpiry?: string | number;\n readonly command?: { readonly argv: readonly string[]; readonly cwd: string };\n readonly now?: ArtifactClock;\n readonly createRequestId?: RequestIdSource;\n}\n\nexport interface CreateOrReusePermissionRequestInput extends BuildPermissionRequestArtifactInput {\n readonly granted: readonly PermissionEntry[];\n /** Owner-aware identity resolver used by registered operation callers. */\n readonly resolveSpace?: OperationSpaceResolver;\n readonly replace?: boolean;\n}\n\nexport type PermissionRequestResolution =\n | Readonly<{ status: \"satisfied\"; reused: false }>\n | Readonly<{ status: \"created\"; reused: boolean; request: PermissionRequestArtifact }>;\n\n/** Parses a request artifact without accepting a newer wire format as v1. */\nexport function validatePermissionRequestArtifact(value: unknown): PermissionRequestArtifact {\n return PermissionRequestArtifactSchema.parse(value);\n}\n\n/** Parses a structured active-session import artifact without accepting a newer wire format as v1. */\nexport function validateDelegationImportArtifact(value: unknown): DelegationImportArtifact {\n return DelegationImportArtifactSchema.parse(value);\n}\n\nexport function isPermissionRequestArtifact(value: unknown): value is PermissionRequestArtifact {\n return PermissionRequestArtifactSchema.safeParse(value).success;\n}\n\nexport function isDelegationImportArtifact(value: unknown): value is DelegationImportArtifact {\n return DelegationImportArtifactSchema.safeParse(value).success;\n}\n\nfunction normalizeLegacyPermissionRequest(\n value: LegacyPermissionRequestArtifact,\n context: LegacyRequestContext,\n): PermissionRequestArtifact {\n if (principal(value.sessionDid) !== principal(context.sessionDid)) {\n throw new TypeError(\"Stored authority request belongs to a different session.\");\n }\n const createdAt = context.now ?? new Date();\n if (Number.isNaN(createdAt.getTime())) {\n throw new TypeError(\"The legacy authority request has no usable creation time.\");\n }\n return {\n kind: \"tinycloud.auth.request\",\n version: 1,\n requestId: value.requestId,\n createdAt: createdAt.toISOString(),\n profile: context.profile,\n posture: context.posture,\n operatorType: context.operatorType,\n host: context.host,\n sessionDid: context.sessionDid,\n ...(context.ownerDid === undefined ? {} : { ownerDid: context.ownerDid }),\n ...(context.spaceId === undefined ? {} : { spaceId: context.spaceId }),\n ...(value.requestedExpiry === undefined ? {} : { requestedExpiry: value.requestedExpiry }),\n requested: canonicalizeCapabilities(value.requested),\n } as PermissionRequestArtifact;\n}\n\nfunction principal(value: string): string {\n return value.split(\"#\", 1)[0]!;\n}\n\n/**\n * Stable exact identity used only for request reuse. It binds a request to its\n * selected profile, active session DID, host, and canonical missing subset.\n */\nexport function createPermissionRequestIdentity(input: PermissionRequestIdentityInput): string {\n return JSON.stringify({\n profile: input.profile,\n sessionDid: input.sessionDid,\n host: input.host,\n missing: canonicalizeCapabilities(input.missing),\n });\n}\n\n/** Builds an on-disk-compatible v1 record. Omitted values stay omitted. */\nexport function buildPermissionRequestArtifact(\n input: BuildPermissionRequestArtifactInput,\n): PermissionRequestArtifact {\n const now = input.now ?? (() => new Date());\n const createdAt = now();\n if (Number.isNaN(createdAt.getTime())) throw new TypeError(\"The artifact clock returned an invalid date.\");\n const requested = canonicalizeCapabilities(input.missing);\n if (requested.length === 0) throw new TypeError(\"Permission requests require at least one missing capability.\");\n\n return validatePermissionRequestArtifact({\n kind: \"tinycloud.auth.request\",\n version: 1,\n requestId: input.createRequestId?.() ?? defaultRequestId(createdAt),\n createdAt: createdAt.toISOString(),\n profile: input.profile,\n posture: input.posture,\n operatorType: input.operatorType,\n host: input.host,\n sessionDid: input.sessionDid,\n ...(input.ownerDid !== undefined ? { ownerDid: input.ownerDid } : {}),\n ...(input.spaceId !== undefined ? { spaceId: input.spaceId } : {}),\n ...(input.requestedExpiry !== undefined ? { requestedExpiry: input.requestedExpiry } : {}),\n requested,\n ...(input.command !== undefined\n ? { command: { argv: [...input.command.argv], cwd: input.command.cwd } }\n : {}),\n });\n}\n\n/** Reads only valid canonical v1 records and refuses malformed stored artifacts. */\nexport async function listPermissionRequests(\n profile: string,\n context?: LegacyRequestContext,\n): Promise<PermissionRequestArtifact[]> {\n const records: PermissionRequestArtifact[] = [];\n for (const value of await readAuthRequests<unknown>(profile)) {\n const canonical = PermissionRequestArtifactSchema.safeParse(value);\n if (canonical.success) {\n if (context !== undefined && principal(canonical.data.sessionDid) !== principal(context.sessionDid)) {\n continue;\n }\n records.push(canonical.data);\n continue;\n }\n const legacy = LegacyPermissionRequestArtifactSchema.safeParse(value);\n if (!legacy.success) throw new TypeError(\"Stored authority request is malformed.\");\n if (context === undefined) {\n throw new TypeError(\"Legacy authority requests require the selected invocation context.\");\n }\n if (principal(legacy.data.sessionDid) !== principal(context.sessionDid)) continue;\n records.push(normalizeLegacyPermissionRequest(legacy.data, context));\n }\n return records;\n}\n\nexport async function findPermissionRequest(\n profile: string,\n requestId: string,\n identity: Pick<PermissionRequestIdentityInput, \"sessionDid\" | \"host\"> &\n Partial<Omit<LegacyRequestContext, \"profile\" | \"host\" | \"sessionDid\">>,\n): Promise<PermissionRequestArtifact | null> {\n const records = await listPermissionRequests(profile, {\n profile,\n host: identity.host,\n sessionDid: identity.sessionDid,\n posture: identity.posture ?? \"delegate-session\",\n operatorType: identity.operatorType ?? \"human\",\n ...(identity.ownerDid === undefined ? {} : { ownerDid: identity.ownerDid }),\n ...(identity.spaceId === undefined ? {} : { spaceId: identity.spaceId }),\n });\n return records.find((record) =>\n record.requestId === requestId &&\n record.sessionDid === identity.sessionDid &&\n record.host === identity.host,\n ) ?? null;\n}\n\n/**\n * Atomically returns the newest identical unresolved request, or writes a new\n * v1 record. Retention, stale-session pruning, covered-record pruning, and\n * partial-grant supersession all happen in the same existing profile lock.\n */\nexport async function createOrReusePermissionRequest(\n input: CreateOrReusePermissionRequestInput,\n): Promise<PermissionRequestResolution> {\n // Registered operations already computed this exact missing set during\n // canonical preflight. Do not recompute it with the legacy global matcher:\n // that matcher intentionally treats same-named short/full spaces as equal.\n const missing = input.resolveSpace === undefined\n ? evaluateAuthority(input.granted, input.missing).missing\n : canonicalizeCapabilities(input.missing);\n const now = input.now ?? (() => new Date());\n const observedNow = now();\n if (Number.isNaN(observedNow.getTime())) throw new TypeError(\"The artifact clock returned an invalid date.\");\n\n return updateProfileStore<unknown, PermissionRequestResolution>(\n input.profile,\n \"auth-requests\",\n (rawRecords) => {\n const records: PermissionRequestArtifact[] = [];\n for (const value of rawRecords) {\n const canonical = PermissionRequestArtifactSchema.safeParse(value);\n if (canonical.success) {\n if (principal(canonical.data.sessionDid) !== principal(input.sessionDid)) continue;\n records.push(canonical.data);\n continue;\n }\n const legacy = LegacyPermissionRequestArtifactSchema.safeParse(value);\n if (!legacy.success) throw new TypeError(\"Stored authority request is malformed.\");\n if (principal(legacy.data.sessionDid) !== principal(input.sessionDid)) continue;\n records.push(normalizeLegacyPermissionRequest(legacy.data, {\n profile: input.profile,\n host: input.host,\n sessionDid: input.sessionDid,\n posture: input.posture,\n operatorType: input.operatorType,\n ...(input.ownerDid === undefined ? {} : { ownerDid: input.ownerDid }),\n ...(input.spaceId === undefined ? {} : { spaceId: input.spaceId }),\n now: observedNow,\n }));\n }\n const retained = prunePermissionRequests(records, {\n profile: input.profile,\n sessionDid: input.sessionDid,\n granted: input.granted,\n resolveSpace: input.resolveSpace,\n now: observedNow,\n });\n\n if (missing.length === 0) {\n return { records: retained, result: { status: \"satisfied\", reused: false } };\n }\n\n const identity = createPermissionRequestIdentity({ ...input, missing });\n const withoutSuperseded = retained.filter((record) => !isSupersededBy(record, input, missing));\n const equivalent = newestEquivalentRequest(withoutSuperseded, identity);\n if (equivalent !== undefined && input.replace !== true) {\n return { records: withoutSuperseded, result: { status: \"created\", reused: true, request: equivalent } };\n }\n\n const next = withoutSuperseded.filter((record) =>\n input.replace !== true || permissionRequestArtifactIdentity(record) !== identity,\n );\n const request = buildPermissionRequestArtifact({ ...input, missing, now: () => observedNow });\n return {\n records: [...next, request],\n result: { status: \"created\", reused: false, request },\n };\n },\n );\n}\n\nexport interface PrunePermissionRequestsInput {\n readonly profile: string;\n readonly sessionDid: string;\n readonly granted: readonly PermissionEntry[];\n readonly resolveSpace?: OperationSpaceResolver;\n readonly now: Date;\n}\n\n/** Pure retention policy used by the locked persistence operation. */\nexport function prunePermissionRequests(\n records: readonly PermissionRequestArtifact[],\n input: PrunePermissionRequestsInput,\n): PermissionRequestArtifact[] {\n const cutoff = input.now.getTime() - THIRTY_DAYS_MS;\n return records.filter((record) => {\n const createdAt = Date.parse(record.createdAt);\n if (!Number.isFinite(createdAt) || createdAt < cutoff) return false;\n if (record.profile === input.profile && principal(record.sessionDid) !== principal(input.sessionDid)) return false;\n // Empty request arrays are historical CLI records. They carry no\n // authority, but remain readable and are retained byte-for-byte by the\n // shared format-1 writer for compatibility.\n if (record.requested.length === 0) return true;\n return !evaluateArtifactAuthority(\n input.granted,\n record.requested,\n input.resolveSpace,\n ).satisfied;\n });\n}\n\nfunction newestEquivalentRequest(\n records: readonly PermissionRequestArtifact[],\n identity: string,\n): PermissionRequestArtifact | undefined {\n let newest: PermissionRequestArtifact | undefined;\n for (const record of records) {\n if (permissionRequestArtifactIdentity(record) !== identity) continue;\n if (newest === undefined || record.createdAt >= newest.createdAt) newest = record;\n }\n return newest;\n}\n\nfunction isSupersededBy(\n record: PermissionRequestArtifact,\n input: CreateOrReusePermissionRequestInput,\n missing: readonly PermissionEntry[],\n): boolean {\n if (\n record.profile !== input.profile ||\n record.sessionDid !== input.sessionDid ||\n record.host !== input.host\n ) {\n return false;\n }\n const remaining = evaluateArtifactAuthority(\n input.granted,\n record.requested,\n input.resolveSpace,\n ).missing;\n return remaining.length > 0 &&\n permissionRequestArtifactIdentity(record) !==\n createPermissionRequestIdentity({ ...input, missing }) &&\n createPermissionRequestIdentity({ ...input, missing: remaining }) ===\n createPermissionRequestIdentity({ ...input, missing });\n}\n\nfunction permissionRequestArtifactIdentity(record: PermissionRequestArtifact): string {\n return createPermissionRequestIdentity({\n profile: record.profile,\n sessionDid: record.sessionDid,\n host: record.host,\n missing: record.requested,\n });\n}\n\nfunction evaluateArtifactAuthority(\n granted: readonly PermissionEntry[],\n required: readonly PermissionEntry[],\n resolveSpace?: OperationSpaceResolver,\n) {\n return resolveSpace === undefined\n ? evaluateAuthority(granted, required)\n : evaluateOperationAuthority(granted, required, resolveSpace);\n}\n\nfunction defaultRequestId(now: Date): string {\n return `req_${now.getTime().toString(36)}_${randomUUID().replace(/-/g, \"\")}`;\n}\n","import { CLI_NAME } from \"./cli-name.js\";\nimport type { TinyCloudPosture } from \"./contract.js\";\n\n/**\n * What a caller does to recover from an operation error it cannot retry. The\n * single source of truth for recovery commands: operations attach it to error\n * details, and projections render hints from it.\n */\nexport type OperationRecovery =\n | { readonly action: \"sign_in\"; readonly command?: string }\n | {\n readonly action: \"complete_migration\";\n readonly command?: string;\n readonly operationId: typeof MIGRATION_OPERATION_ID;\n };\n\n/**\n * Profile names that may appear in a command. Profile names are any single\n * path segment, so a name outside this token gets a recovery without a\n * `command`: an agent never receives a command built from an unsafe string.\n */\nexport const SAFE_PROFILE_TOKEN = /^[A-Za-z0-9][A-Za-z0-9._-]{0,63}$/;\n\n/** Runs the authenticated runtime on the profile's own restored session, which completes its migration. */\nconst MIGRATION_OPERATION_ID = \"tinycloud.auth.capabilities\";\n\nconst SIGN_IN_TEMPLATES: Readonly<Record<\"local\" | \"openkey\" | \"scoped\", (profile: string) => string>> = {\n local: (profile) => `${CLI_NAME} --profile ${profile} auth login --method local`,\n openkey: (profile) => `${CLI_NAME} --profile ${profile} auth login --method openkey`,\n scoped: (profile) => `${CLI_NAME} --profile ${profile} auth login --method openkey --paste --manifest <manifest.json>`,\n};\n\nconst migrationCommand = (profile: string): string => `${CLI_NAME} --profile ${profile} auth caps`;\n\n/**\n * The persistent login (one that writes a stored session) for a profile of\n * this posture. A delegate profile needs its owner to approve a scoped login.\n */\nexport function signInRecovery(profile: string, posture: TinyCloudPosture): OperationRecovery {\n if (!SAFE_PROFILE_TOKEN.test(profile)) return { action: \"sign_in\" };\n const template = posture === \"local-owner-key\"\n ? SIGN_IN_TEMPLATES.local\n : posture === \"delegate-session\"\n ? SIGN_IN_TEMPLATES.scoped\n : SIGN_IN_TEMPLATES.openkey;\n return { action: \"sign_in\", command: template(profile) };\n}\n\n/** One authenticated command on the profile's own session finishes its one-time binding migration. */\nexport function migrationRecovery(profile: string): OperationRecovery {\n return SAFE_PROFILE_TOKEN.test(profile)\n ? { action: \"complete_migration\", command: migrationCommand(profile), operationId: MIGRATION_OPERATION_ID }\n : { action: \"complete_migration\", operationId: MIGRATION_OPERATION_ID };\n}\n\n/**\n * Exact-shape check used by the error-detail redaction policies: exact keys, a\n * fixed `action`, and a `command` equal to one of the fixed templates for a\n * safe profile token.\n */\nexport function isSafeRecovery(\n value: unknown,\n action: OperationRecovery[\"action\"],\n): value is OperationRecovery {\n if (typeof value !== \"object\" || value === null || Array.isArray(value)) return false;\n const record = value as Record<string, unknown>;\n if (record.action !== action) return false;\n const keys = Object.keys(record);\n const allowed = action === \"sign_in\" ? [\"action\", \"command\"] : [\"action\", \"command\", \"operationId\"];\n if (!keys.every((key) => allowed.includes(key))) return false;\n if (action === \"complete_migration\" && record.operationId !== MIGRATION_OPERATION_ID) return false;\n if (!(\"command\" in record)) return true;\n const command = record.command;\n if (typeof command !== \"string\") return false;\n const profile = /^\\S+ --profile (\\S+) /.exec(command)?.[1];\n if (profile === undefined || !SAFE_PROFILE_TOKEN.test(profile)) return false;\n return action === \"sign_in\"\n ? Object.values(SIGN_IN_TEMPLATES).some((template) => template(profile) === command)\n : migrationCommand(profile) === command;\n}\n","// Namespace-shaped so modules that bundle this one stay compatible with\n// lightweight node-sdk test doubles.\nimport * as nodeSdk from \"@tinycloud/node-sdk\";\n\nimport { OperationInvocationError, operationError } from \"./errors.js\";\n\nconst DEFAULT_SECRETS_SPACE = \"secrets\";\nconst SECRET_NAME_RE = /^[A-Z][A-Z0-9_]*$/;\nconst RESERVED_SECRET_SCOPES = new Set([\"default\", \"global\"]);\n\nexport interface SecretsGetInput {\n readonly name: string;\n readonly scope?: string;\n readonly space?: string;\n}\n\nexport interface SecretReference {\n readonly name: string;\n readonly scope?: string;\n readonly space: string;\n readonly vaultKey: string;\n readonly permissionPath: string;\n}\n\nexport function normalizeSecretsGetInput(\n input: SecretsGetInput,\n): SecretsGetInput {\n const reference = resolveSecretReference(input);\n return {\n name: reference.name,\n ...(reference.scope === undefined ? {} : { scope: reference.scope }),\n // Omission is meaningful: the CLI's default is the literal `secrets`\n // space, while an explicitly supplied short name is resolved later against\n // the authenticated identity.\n ...(input.space === undefined ? {} : { space: reference.space }),\n };\n}\n\n/** Resolves the one canonical backing key/path used by planning and setup. */\nexport function resolveSecretReference(\n input: SecretsGetInput,\n): SecretReference {\n const name = input.name.trim();\n if (!SECRET_NAME_RE.test(name)) {\n throw invalidSecretInput();\n }\n\n const scope = canonicalizeSecretScope(input.scope);\n const space = normalizeSecretsSpace(input.space);\n const vaultKey = scope === undefined\n ? `secrets/${name}`\n : `secrets/scoped/${scope}/${name}`;\n\n return {\n name,\n ...(scope === undefined ? {} : { scope }),\n space,\n vaultKey,\n permissionPath: `vault/${vaultKey}`,\n };\n}\n\n/**\n * Resolve the operation's space against the authenticated identity. The\n * public short-name contract remains `secrets`, while an authenticated\n * operation uses its owner-exact URI for the actual read boundary.\n */\nexport function resolveSecretReferenceForOperation(\n input: SecretsGetInput,\n node: unknown,\n ownerSpace?: string,\n): SecretReference {\n const reference = resolveSecretReference(input);\n if (input.space === undefined && ownerSpace === undefined) return reference;\n return {\n ...reference,\n space: resolveOperationSpace(reference.space, node, ownerSpace),\n };\n}\n\n/** Creates the one owner-aware resolver shared by operation matching paths. */\nexport function operationSpaceResolver(\n node: unknown,\n ownerSpace?: string,\n): (space: string) => string {\n return (space) => resolveOperationSpace(space, node, ownerSpace);\n}\n\nfunction resolveOperationSpace(space: string, node: unknown, ownerSpace?: string): string {\n if (space.startsWith(\"tinycloud:\")) return canonicalizeSpaceUri(space);\n\n const ownerSpaceId = ownerSpace === undefined ? undefined : ownedSpaceForName(ownerSpace, space);\n if (ownerSpaceId !== undefined) return canonicalizeSpaceUri(ownerSpaceId);\n\n const candidate = node as {\n resolveOwnedSpace?: (spaceName: string) => unknown;\n spaceId?: unknown;\n address?: unknown;\n did?: unknown;\n };\n try {\n const resolved = candidate.resolveOwnedSpace?.(space);\n if (typeof resolved === \"string\" && resolved.length > 0) {\n return canonicalizeSpaceUri(resolved);\n }\n } catch {\n // Continue to the identity-derived resolver below.\n }\n\n // A restored delegate session carries the owner's primary space even though\n // node.did is the delegate/session principal. This is the same owner\n // identity used by the CLI space resolver.\n if (typeof candidate.spaceId === \"string\") {\n const resolved = ownedSpaceForName(candidate.spaceId, space);\n if (resolved !== undefined) return canonicalizeSpaceUri(resolved);\n }\n\n const did = typeof candidate.did === \"string\" ? candidate.did : undefined;\n const pkh = did === undefined ? null : nodeSdk.parsePkhDid(did);\n if (pkh === null) {\n throw invalidSecretInput();\n }\n return nodeSdk.makePkhSpaceId(pkh.address, pkh.chainId, space);\n}\n\nfunction ownedSpaceForName(ownerSpace: string, name: string): string | undefined {\n const parsed = nodeSdk.parseSpaceUri(ownerSpace);\n if (parsed === null) return undefined;\n try {\n const pkh = nodeSdk.parsePkhDid(parsed.owner);\n return pkh === null ? undefined : nodeSdk.makePkhSpaceId(pkh.address, pkh.chainId, name);\n } catch {\n return undefined;\n }\n}\n\nfunction canonicalizeSpaceUri(space: string): string {\n const parts = space.split(\":\");\n const name = parts.at(-1);\n if (parts.length < 3 || name === undefined || name === \"\") {\n throw invalidSecretInput();\n }\n // EIP-155 address casing is interchangeable only in the repository's\n // anchored PKH space form. Other TinyCloud space/DID syntaxes are opaque\n // method-specific identifiers and must remain byte-for-byte unchanged.\n const pkh = space.match(\n /^tinycloud:pkh:eip155:(\\d+):(0x[0-9a-fA-F]{40}):([^:]+)$/,\n );\n if (pkh !== null) {\n return `tinycloud:pkh:eip155:${pkh[1]}:${nodeSdk.canonicalizeAddress(pkh[2]!).toLowerCase()}:${pkh[3]}`;\n }\n return space;\n}\n\n/**\n * The operations scope canonicalizer: trims, lowercases and folds every other\n * character to `-`. Empty means unscoped; `default`/`global` are refused.\n */\nexport function canonicalizeSecretScope(scope: string | undefined): string | undefined {\n if (scope === undefined || scope === \"\") return undefined;\n\n const trimmed = scope.trim();\n if (trimmed === \"\") throw invalidSecretInput();\n\n const canonical = trimmed\n .toLowerCase()\n .replace(/[^a-z0-9-]/g, \"-\")\n .replace(/-+/g, \"-\")\n .replace(/^-|-$/g, \"\");\n if (canonical === \"\" || RESERVED_SECRET_SCOPES.has(canonical)) {\n throw invalidSecretInput();\n }\n return canonical;\n}\n\nfunction normalizeSecretsSpace(space: string | undefined): string {\n if (space === undefined || space === \"\") return DEFAULT_SECRETS_SPACE;\n\n if (space.startsWith(\"tinycloud:\")) {\n const parts = space.split(\":\");\n if (parts.length < 3 || parts.at(-1) === undefined || parts.at(-1) === \"\") {\n throw invalidSecretInput();\n }\n return `tinycloud:${parts.slice(1, -1).join(\":\")}:${parts.at(-1)}`;\n }\n\n if (!/^[A-Za-z0-9_-]+$/.test(space)) throw invalidSecretInput();\n return space;\n}\n\nfunction invalidSecretInput(): OperationInvocationError {\n return new OperationInvocationError(\n operationError(\"INPUT_INVALID\", \"The secret reference is invalid.\"),\n );\n}\n","export { nodeRevocationOf } from \"./kv-failure.js\";\nexport type { NodeRevocation } from \"./kv-failure.js\";\nexport { operationRetryDescriptor } from \"./invoke.js\";\n","import { createHash } from \"node:crypto\";\nimport { isAbsolute } from \"node:path\";\n\nimport { CLI_NAME } from \"./cli-name.js\";\nimport {\n makePkhSpaceId,\n parsePkhDid,\n parseSpaceUri,\n type PermissionEntry,\n} from \"@tinycloud/node-sdk\";\nimport { jcsCanonicalize } from \"@tinycloud/sdk-core/policy\";\nimport type { ZodError } from \"zod\";\n\nimport {\n type ApprovalAction,\n type CapabilityRequirement,\n type InvocationTarget,\n type OperationContextSummary,\n type OperationDefinition,\n type OperationRef,\n type OperationResult,\n type RetryDescriptor,\n type OperationRuntimeRequirement,\n type RuntimeOperationContext,\n} from \"./contract.js\";\nimport {\n internalOperationError,\n operationError,\n sanitizeOperationError,\n sanitizeThrownOperationError,\n} from \"./errors.js\";\nimport {\n canonicalizeOperationCapabilities,\n evaluateOperationAuthority,\n isExactCapabilityMemberSubset,\n validateExactCapabilities,\n} from \"./authority.js\";\nimport {\n createOrReusePermissionRequest,\n buildPermissionRequestArtifact,\n type PermissionRequestArtifact,\n} from \"./artifacts.js\";\nimport { redactOperationError } from \"./redaction.js\";\nimport { safeOriginHost } from \"./safe-values.js\";\nimport { withTinyCloudStateRoot } from \"./state.js\";\nimport type { InvocationRuntimeResolution } from \"./runtime.js\";\nimport {\n operationSpaceResolver,\n resolveSecretReferenceForOperation,\n} from \"./secrets.js\";\n\n/** Value-free counters; the caller owns this optional diagnostic sink. */\nexport interface OperationMetrics {\n replayedGrants: number;\n}\n\n/** The sole projection-facing execution API. */\nexport async function invokeOperation(\n operationId: string,\n operationVersion: number,\n invocationTarget: InvocationTarget,\n unknownInput: unknown,\n metrics?: OperationMetrics,\n): Promise<OperationResult<unknown>> {\n if (metrics !== undefined) metrics.replayedGrants = 0;\n const target = normalizeInvocationTarget(invocationTarget);\n if (target === undefined) {\n return errorResult(\n { operationId, operationVersion },\n unresolvedContextSummary(),\n operationError(\"INPUT_INVALID\", \"The invocation target is invalid.\"),\n );\n }\n const run = withTinyCloudStateRoot(target.stateRoot, async () => {\n const prepared = await prepareInvocation(\n operationId,\n operationVersion,\n target,\n unknownInput,\n );\n if (isPreparedError(prepared)) return prepared.result;\n if (metrics !== undefined) {\n metrics.replayedGrants = prepared.runtimeResolution.context.runtime?.replayedGrants ?? 0;\n }\n return withRuntimeWarnings(await executePreparedInvocation(prepared), prepared);\n });\n // Lazy like prepareInvocation's lookup: the packed root entrypoint stays\n // free of registry/SDK initialization until a call is made.\n const { lookupOperation } = await import(\"./registry.js\");\n const lookup = lookupOperation(operationId, operationVersion);\n const deadlineMs = lookup.status === \"found\" ? lookup.definition.deadlineMs : undefined;\n if (deadlineMs === undefined) return run;\n return withResponseDeadline(run, deadlineMs, () => errorResult(\n { operationId, operationVersion },\n unresolvedContextSummary(target),\n operationError(\n \"NODE_UNREACHABLE\",\n `The operation did not finish within ${deadlineMs / 1000} s.`,\n { retryable: true },\n ),\n ));\n}\n\n/**\n * Returns `run`'s result, or the deadline result once `deadlineMs` passes.\n * The abandoned run is never cancelled: it keeps running to completion, and\n * its eventual rejection is absorbed so nothing reports it as unhandled.\n */\nasync function withResponseDeadline(\n run: Promise<OperationResult<unknown>>,\n deadlineMs: number,\n expired: () => OperationResult<unknown>,\n): Promise<OperationResult<unknown>> {\n run.catch(() => {});\n let timer: NodeJS.Timeout | undefined;\n const deadline = new Promise<OperationResult<unknown>>((resolve) => {\n timer = setTimeout(() => resolve(expired()), deadlineMs);\n timer.unref();\n });\n try {\n return await Promise.race([run, deadline]);\n } finally {\n clearTimeout(timer);\n }\n}\n\n/**\n * CLI-only local-owner acquisition path. The runtime is deliberately created\n * and retained inside operations so a grant is installed on the exact session\n * that performs the one retry. The explicit key remains a non-persisted target\n * value and no runtime object crosses the operations boundary.\n */\nexport async function invokeOperationWithLocalAuthorityRetry(\n operationId: string,\n operationVersion: number,\n invocationTarget: InvocationTarget,\n unknownInput: unknown,\n): Promise<OperationResult<unknown>> {\n const target = normalizeInvocationTarget(invocationTarget);\n if (target === undefined) {\n return errorResult(\n { operationId, operationVersion },\n unresolvedContextSummary(),\n operationError(\"INPUT_INVALID\", \"The invocation target is invalid.\"),\n );\n }\n if (typeof target.privateKey !== \"string\" || target.privateKey.length === 0) {\n return errorResult(\n { operationId, operationVersion },\n unresolvedContextSummary(target),\n operationError(\n \"PROFILE_POSTURE_NOT_ALLOWED\",\n \"Local authority retry requires an explicit private key.\",\n ),\n );\n }\n\n return withTinyCloudStateRoot(target.stateRoot, () =>\n invokeLocalAuthorityRetry(\n operationId,\n operationVersion,\n target,\n unknownInput,\n )\n );\n}\n\nasync function invokeLocalAuthorityRetry(\n operationId: string,\n operationVersion: number,\n target: InvocationTarget,\n unknownInput: unknown,\n): Promise<OperationResult<unknown>> {\n const prepared = await prepareInvocation(\n operationId,\n operationVersion,\n target,\n unknownInput,\n );\n if (isPreparedError(prepared)) return prepared.result;\n return withRuntimeWarnings(await retryWithLocalAuthority(prepared), prepared);\n}\n\nasync function retryWithLocalAuthority(\n prepared: Exclude<PreparedInvocation, { status: \"error\" }>,\n): Promise<OperationResult<unknown>> {\n if (\n prepared.operation.operationId !== \"tinycloud.secrets.get\" ||\n prepared.operation.operationVersion !== 1\n ) {\n return errorResult(\n prepared.operation,\n prepared.runtimeResolution.context.summary,\n operationError(\n \"OPERATION_NOT_FOUND\",\n \"The local CLI authority helper supports only tinycloud.secrets.get@1.\",\n ),\n );\n }\n\n const runtimeContext = prepared.runtimeResolution.context;\n if (runtimeContext.runtime === undefined) return internalOperationErrorResult(prepared);\n const operationContext = runtimeContext as RuntimeOperationContext;\n let planned: CapabilityRequirement[] | undefined;\n try {\n planned = exactCapabilities(await prepared.definition.authority(\n operationContext,\n prepared.parsedInput.data,\n ));\n } catch {\n return errorResult(\n prepared.operation,\n operationContext.summary,\n operationError(\n \"PERMISSION_HINT_INVALID\",\n \"The local CLI authority plan is invalid.\",\n ),\n );\n }\n const planStatus = planned === undefined\n ? \"invalid\"\n : isExactLocalSecretsGetPlan(prepared, planned, operationContext);\n if (planStatus === \"ineligible\") {\n return errorResult(\n prepared.operation,\n operationContext.summary,\n operationError(\n \"PROFILE_POSTURE_NOT_ALLOWED\",\n \"The explicit private key cannot authorize the requested owner space.\",\n ),\n );\n }\n if (planStatus !== \"valid\") {\n return errorResult(\n prepared.operation,\n operationContext.summary,\n operationError(\n \"PERMISSION_HINT_INVALID\",\n \"The local CLI authority plan is invalid.\",\n ),\n );\n }\n\n const first = await executePreparedInvocation(prepared, { deferAuthorityRequest: true });\n if (first.status !== \"authority_required\") {\n return first;\n }\n if (\n operationContext.summary.posture !== \"local-owner-key\" ||\n !requirementsBelongToAuthenticatedOwner(\n first.missing as unknown as PermissionEntry[],\n operationContext.summary.space,\n )\n ) {\n return localAuthorityFailure(prepared);\n }\n\n try {\n const grantRuntimePermissions = (operationContext.runtime.node as {\n grantRuntimePermissions?: (\n permissions: PermissionEntry[],\n ) => Promise<unknown>;\n }).grantRuntimePermissions;\n if (typeof grantRuntimePermissions !== \"function\") {\n return localAuthorityFailure(prepared);\n }\n await grantRuntimePermissions.call(\n operationContext.runtime.node,\n first.missing as unknown as PermissionEntry[],\n );\n\n const resolveSpace = operationSpaceResolver(\n operationContext.runtime.node,\n operationContext.summary.space,\n );\n const refreshedGranted = refreshLiveRuntimeAuthority(\n operationContext.runtime.node,\n first.missing as unknown as PermissionEntry[],\n planned as unknown as PermissionEntry[],\n resolveSpace,\n );\n if (refreshedGranted === undefined) {\n return localAuthorityFailure(prepared);\n }\n const retryContext: RuntimeOperationContext = {\n ...operationContext,\n runtime: {\n ...operationContext.runtime,\n granted: refreshedGranted,\n },\n };\n const retry = await executePreparedInvocation({\n ...prepared,\n runtimeResolution: { ok: true, context: retryContext },\n }, { deferAuthorityRequest: true });\n return retry.status === \"authority_required\"\n ? localAuthorityFailure(prepared)\n : retry;\n } catch {\n return localAuthorityFailure(prepared);\n }\n}\n\n/** Diagnostics from building the runtime belong to the invocation's result. */\nfunction withRuntimeWarnings(\n result: OperationResult<unknown>,\n prepared: Exclude<PreparedInvocation, { status: \"error\" }>,\n): OperationResult<unknown> {\n const warnings = prepared.runtimeResolution.context.runtime?.warnings;\n return warnings === undefined || warnings.length === 0 ? result : { ...result, warnings: [...warnings] };\n}\n\ntype PreparedInvocation = Readonly<{\n operation: OperationRef;\n definition: OperationDefinition<any, any>;\n parsedInput: { data: any };\n runtimeResolution: Extract<InvocationRuntimeResolution, { ok: true }>;\n}> | Readonly<{\n status: \"error\";\n result: OperationResult<never>;\n}>;\n\nfunction isPreparedError(\n prepared: PreparedInvocation,\n): prepared is Extract<PreparedInvocation, { status: \"error\" }> {\n return \"status\" in prepared;\n}\n\nasync function prepareInvocation(\n operationId: string,\n operationVersion: number,\n invocationTarget: InvocationTarget,\n unknownInput: unknown,\n): Promise<PreparedInvocation> {\n const operation: OperationRef = { operationId, operationVersion };\n const target = normalizeInvocationTarget(invocationTarget);\n if (target === undefined) {\n return { status: \"error\", result: errorResult(\n operation,\n unresolvedContextSummary(),\n operationError(\"INPUT_INVALID\", \"The invocation target is invalid.\"),\n ) };\n }\n // Load internal registry material only when invoking. This keeps the packed\n // root entrypoint free of auth/runtime SDK initialization until a call is\n // made, while preserving registry resolution before input or profile access.\n const { lookupOperation } = await import(\"./registry.js\");\n const lookup = lookupOperation(operationId, operationVersion);\n\n // Registry resolution is deliberately complete before any input parsing or\n // profile access. An unrecognized operation must not cause an invocation to\n // consult profile state.\n if (lookup.status === \"operation_not_found\") {\n return { status: \"error\", result: errorResult(\n operation,\n unresolvedContextSummary(target),\n operationError(\n \"OPERATION_NOT_FOUND\",\n \"The requested operation is not registered.\",\n ),\n ) };\n }\n if (lookup.status === \"operation_version_unsupported\") {\n return { status: \"error\", result: errorResult(\n operation,\n unresolvedContextSummary(target),\n operationError(\n \"OPERATION_VERSION_UNSUPPORTED\",\n \"The requested operation version is not supported.\",\n { details: { supportedVersions: lookup.supportedVersions } },\n ),\n ) };\n }\n\n const definition = lookup.definition;\n const parsedInput = definition.input.safeParse(unknownInput);\n if (!parsedInput.success) {\n return { status: \"error\", result: errorResult(\n operation,\n unresolvedContextSummary(target),\n operationError(\n definition.invalidInputErrorCode ?? \"INPUT_INVALID\",\n \"The operation input is invalid.\",\n {\n details: zodIssueDetails(parsedInput.error),\n },\n ),\n ) };\n }\n\n // Only a registered operation with valid input may inspect the pinned\n // profile. Inspection is deliberately node-free, so owner opt-in and status\n // availability are decided before any authentication or external effect.\n const inspectionResolution = await resolveRuntime(target, \"inspection\");\n if (!inspectionResolution.ok) {\n return { status: \"error\", result: errorResult(\n operation,\n inspectionResolution.context,\n inspectionResolution.error,\n ) };\n }\n\n if (\n target.privateKey === undefined &&\n !definition.postures.includes(inspectionResolution.context.summary.posture)\n ) {\n return { status: \"error\", result: errorResult(\n operation,\n inspectionResolution.context.summary,\n operationError(\n \"PROFILE_POSTURE_NOT_ALLOWED\",\n \"The active profile posture cannot execute this operation.\",\n ),\n ) };\n }\n if (\n target.privateKey === undefined &&\n requiresRuntime(definition.runtime) &&\n isOwnerPosture(inspectionResolution.context.summary.posture) &&\n target.allowOwnerProfile !== true\n ) {\n return { status: \"error\", result: errorResult(\n operation,\n inspectionResolution.context.summary,\n operationError(\n \"PROFILE_OWNER_OPT_IN_REQUIRED\",\n \"Owner-profile execution requires explicit opt-in.\",\n ),\n ) };\n }\n\n const runtimeResolution =\n definition.runtime === \"inspection\"\n ? inspectionResolution\n : await resolveRuntime(target, definition.runtime);\n if (!runtimeResolution.ok) {\n // Runtime-preparation errors share the execution errors' detail allowlist.\n return { status: \"error\", result: errorResult(\n operation,\n runtimeResolution.context,\n redactOperationError(definition, runtimeResolution.error),\n ) };\n }\n if (requiresRuntime(definition.runtime)) {\n const actualPosture = runtimeResolution.context.summary.posture;\n if (!definition.postures.includes(actualPosture)) {\n return { status: \"error\", result: errorResult(\n operation,\n runtimeResolution.context.summary,\n operationError(\n \"PROFILE_POSTURE_NOT_ALLOWED\",\n \"The authenticated profile posture cannot execute this operation.\",\n ),\n ) };\n }\n if (isOwnerPosture(actualPosture) && target.allowOwnerProfile !== true) {\n return { status: \"error\", result: errorResult(\n operation,\n runtimeResolution.context.summary,\n operationError(\n \"PROFILE_OWNER_OPT_IN_REQUIRED\",\n \"Owner-profile execution requires explicit opt-in.\",\n ),\n ) };\n }\n }\n return {\n operation,\n definition,\n parsedInput,\n runtimeResolution,\n };\n}\n\nasync function executePreparedInvocation(\n prepared: Exclude<PreparedInvocation, { status: \"error\" }>,\n options: Readonly<{ deferAuthorityRequest?: boolean }> = {},\n): Promise<OperationResult<unknown>> {\n const { operation, definition, parsedInput, runtimeResolution } = prepared;\n const runtimeContext =\n runtimeResolution.context.runtime === undefined\n ? undefined\n : (runtimeResolution.context as RuntimeOperationContext);\n const resolveSpace = runtimeContext === undefined\n ? undefined\n : operationSpaceResolver(\n runtimeContext.runtime.node,\n runtimeContext.summary.space,\n );\n\n try {\n const planned = await definition.authority(\n (runtimeContext ?? runtimeResolution.context) as RuntimeOperationContext,\n parsedInput.data,\n );\n const required = exactCapabilities(planned);\n if (required === undefined) {\n return errorResult(\n operation,\n runtimeResolution.context.summary,\n operationError(\n \"PERMISSION_HINT_INVALID\",\n \"The operation returned an invalid permission requirement.\",\n ),\n );\n }\n\n const evaluation = evaluateOperationAuthority(\n (runtimeContext?.runtime.granted ?? []) as unknown as PermissionEntry[],\n required as unknown as PermissionEntry[],\n resolveSpace,\n );\n if (!evaluation.satisfied) {\n if (runtimeContext === undefined) {\n return errorResult(\n operation,\n runtimeResolution.context.summary,\n internalOperationError(),\n );\n }\n if (options.deferAuthorityRequest) {\n try {\n const request = buildPermissionRequestArtifact({\n profile: runtimeContext.summary.profile,\n host: runtimeContext.summary.host,\n sessionDid: runtimeContext.summary.sessionDid!,\n posture: runtimeContext.summary.posture,\n operatorType: runtimeContext.summary.operatorType ?? \"human\",\n ...(runtimeContext.summary.ownerDid === undefined\n ? {}\n : { ownerDid: runtimeContext.summary.ownerDid }),\n ...(runtimeContext.summary.space === undefined\n ? {}\n : { spaceId: runtimeContext.summary.space }),\n missing: evaluation.missing as unknown as PermissionEntry[],\n });\n return authorityRequiredResult(\n operation,\n runtimeResolution.context.summary,\n parsedInput.data,\n request,\n evaluation.missing,\n runtimeContext.runtime.node,\n );\n } catch {\n return errorResult(\n operation,\n runtimeResolution.context.summary,\n internalOperationError(),\n );\n }\n }\n const authority = await persistAuthorityRequest(\n runtimeContext,\n evaluation.missing,\n );\n if (authority.status === \"error\") {\n return errorResult(\n operation,\n runtimeResolution.context.summary,\n authority.error,\n );\n }\n return authorityRequiredResult(\n operation,\n runtimeResolution.context.summary,\n parsedInput.data,\n authority.request,\n authority.missing,\n runtimeContext.runtime.node,\n );\n }\n\n const outcome = await definition.execute(\n runtimeResolution.context,\n parsedInput.data,\n );\n\n switch (outcome.status) {\n case \"ok\": {\n const parsedOutput = definition.output.safeParse(outcome.output);\n if (!parsedOutput.success) {\n return errorResult(\n operation,\n runtimeResolution.context.summary,\n operationError(\n \"OUTPUT_INVALID\",\n \"The operation returned an invalid result.\",\n ),\n );\n }\n return {\n status: \"ok\",\n operation,\n context: runtimeResolution.context.summary,\n output: parsedOutput.data,\n };\n }\n case \"authority_required\": {\n const hinted = exactCapabilities(outcome.missing);\n if (\n hinted === undefined ||\n !isExactCapabilityMemberSubset(\n hinted as unknown as PermissionEntry[],\n required as unknown as PermissionEntry[],\n resolveSpace,\n )\n ) {\n return errorResult(\n operation,\n runtimeResolution.context.summary,\n operationError(\n \"PERMISSION_HINT_INVALID\",\n \"The node returned an invalid permission hint.\",\n ),\n );\n }\n if (runtimeContext === undefined) {\n return errorResult(\n operation,\n runtimeResolution.context.summary,\n internalOperationError(),\n );\n }\n if (options.deferAuthorityRequest) {\n try {\n const request = buildPermissionRequestArtifact({\n profile: runtimeContext.summary.profile,\n host: runtimeContext.summary.host,\n sessionDid: runtimeContext.summary.sessionDid!,\n posture: runtimeContext.summary.posture,\n operatorType: runtimeContext.summary.operatorType ?? \"human\",\n ...(runtimeContext.summary.ownerDid === undefined\n ? {}\n : { ownerDid: runtimeContext.summary.ownerDid }),\n ...(runtimeContext.summary.space === undefined\n ? {}\n : { spaceId: runtimeContext.summary.space }),\n missing: hinted as unknown as PermissionEntry[],\n });\n return authorityRequiredResult(\n operation,\n runtimeResolution.context.summary,\n parsedInput.data,\n request,\n hinted,\n runtimeContext.runtime.node,\n outcome.requiresCallerInput ?? false,\n );\n } catch {\n return errorResult(\n operation,\n runtimeResolution.context.summary,\n internalOperationError(),\n );\n }\n }\n const authority = await persistAuthorityRequest(\n runtimeContext,\n hinted,\n true,\n );\n if (authority.status === \"error\") {\n return errorResult(\n operation,\n runtimeResolution.context.summary,\n authority.error,\n );\n }\n return authorityRequiredResult(\n operation,\n runtimeResolution.context.summary,\n parsedInput.data,\n authority.request,\n authority.missing,\n runtimeContext.runtime.node,\n outcome.requiresCallerInput ?? false,\n );\n }\n case \"setup_required\":\n return {\n status: \"setup_required\",\n operation,\n context: runtimeResolution.context.summary,\n setup: outcome.setup,\n retry: operationRetryDescriptor(\n operation,\n parsedInput.data,\n outcome.requiresCallerInput ?? false,\n ),\n };\n case \"error\":\n return errorResult(\n operation,\n runtimeResolution.context.summary,\n redactOperationError(\n definition,\n sanitizeOperationError(outcome.error),\n ),\n );\n default:\n return errorResult(\n operation,\n runtimeResolution.context.summary,\n internalOperationError(),\n );\n }\n } catch (error) {\n return errorResult(\n operation,\n runtimeResolution.context.summary,\n redactOperationError(definition, sanitizeThrownOperationError(error)),\n );\n }\n}\n\nfunction normalizeInvocationTarget(\n target: unknown,\n): InvocationTarget | undefined {\n if (typeof target !== \"object\" || target === null) return undefined;\n\n try {\n const candidate = target as InvocationTarget;\n const profile = candidate.profile;\n const host = candidate.host;\n const stateRoot = candidate.stateRoot;\n const allowOwnerProfile = candidate.allowOwnerProfile;\n const privateKey = candidate.privateKey;\n\n if (\n (profile !== undefined && typeof profile !== \"string\") ||\n (host !== undefined && typeof host !== \"string\") ||\n (stateRoot !== undefined &&\n (typeof stateRoot !== \"string\" || !isAbsolute(stateRoot))) ||\n (allowOwnerProfile !== undefined &&\n typeof allowOwnerProfile !== \"boolean\") ||\n (privateKey !== undefined && typeof privateKey !== \"string\")\n ) {\n return undefined;\n }\n\n return {\n ...(profile === undefined ? {} : { profile }),\n ...(host === undefined ? {} : { host }),\n ...(stateRoot === undefined ? {} : { stateRoot }),\n ...(allowOwnerProfile === true ? { allowOwnerProfile: true } : {}),\n ...(privateKey === undefined ? {} : { privateKey }),\n };\n } catch {\n return undefined;\n }\n}\n\nasync function resolveRuntime(\n target: InvocationTarget,\n requirement: OperationRuntimeRequirement,\n): Promise<InvocationRuntimeResolution> {\n try {\n // Keep the root package loadable for projections that only inspect its\n // public invocation export. The SDK runtime is needed only once a valid\n // operation reaches runtime construction.\n const { createInvocationRuntime } = await import(\"./runtime.js\");\n return await createInvocationRuntime(target, requirement);\n } catch (error) {\n return {\n ok: false,\n context: unresolvedContextSummary(target),\n error: sanitizeThrownOperationError(error),\n };\n }\n}\n\nfunction unresolvedContextSummary(\n target: InvocationTarget = {},\n): OperationContextSummary {\n return {\n profile: typeof target.profile === \"string\" ? target.profile : \"unresolved\",\n host: safeOriginHost(target.host) ?? \"unresolved\",\n posture: \"unauthenticated\",\n };\n}\n\nfunction errorResult(\n operation: OperationRef,\n context: OperationContextSummary,\n error: ReturnType<typeof sanitizeOperationError>,\n): OperationResult<never> {\n return { status: \"error\", operation, context, error };\n}\n\n/** Build the same canonical retry identity used by operation authority results. */\nexport function operationRetryDescriptor(\n operation: OperationRef,\n input: unknown,\n requiresCallerInput = false,\n): RetryDescriptor {\n return {\n operationId: operation.operationId,\n operationVersion: operation.operationVersion,\n inputDigest: createHash(\"sha256\")\n .update(jcsCanonicalize(input), \"utf8\")\n .digest(\"hex\"),\n requiresCallerInput,\n };\n}\n\ntype PersistedAuthority =\n | Readonly<{\n status: \"ok\";\n request: PermissionRequestArtifact;\n missing: readonly CapabilityRequirement[];\n }>\n | Readonly<{\n status: \"error\";\n error: ReturnType<typeof sanitizeOperationError>;\n }>;\n\nasync function persistAuthorityRequest(\n context: RuntimeOperationContext,\n missing: readonly CapabilityRequirement[],\n force = false,\n): Promise<PersistedAuthority> {\n const sessionDid = context.summary.sessionDid;\n if (sessionDid === undefined) {\n return {\n status: \"error\",\n error: operationError(\n \"SESSION_NOT_FOUND\",\n \"The selected profile does not have an active session.\",\n ),\n };\n }\n try {\n const resolveSpace = operationSpaceResolver(\n context.runtime.node,\n context.summary.space,\n );\n const canonicalMissing = canonicalizeOperationCapabilities(missing as unknown as PermissionEntry[], resolveSpace);\n const canonicalGranted = canonicalizeOperationCapabilities(context.runtime.granted as unknown as PermissionEntry[], resolveSpace);\n const resolution = await createOrReusePermissionRequest({\n profile: context.summary.profile,\n posture: context.summary.posture,\n operatorType: context.summary.operatorType ?? \"human\",\n host: context.summary.host,\n sessionDid,\n ...(context.summary.ownerDid === undefined\n ? {}\n : { ownerDid: context.summary.ownerDid }),\n ...(context.summary.space === undefined\n ? {}\n : { spaceId: context.summary.space }),\n missing: canonicalMissing as unknown as PermissionEntry[],\n granted: (force\n ? []\n : canonicalGranted) as unknown as PermissionEntry[],\n resolveSpace,\n });\n if (resolution.status !== \"created\") {\n return {\n status: \"error\",\n error: operationError(\n \"INTERNAL_ERROR\",\n \"The authority request could not be created.\",\n ),\n };\n }\n return {\n status: \"ok\",\n request: resolution.request,\n missing: canonicalMissing as CapabilityRequirement[],\n };\n } catch {\n return {\n status: \"error\",\n error: operationError(\n \"INTERNAL_ERROR\",\n \"The authority request could not be created.\",\n ),\n };\n }\n}\n\nfunction authorityRequiredResult(\n operation: OperationRef,\n context: OperationContextSummary,\n input: unknown,\n request: PermissionRequestArtifact,\n missing: readonly CapabilityRequirement[],\n node: unknown,\n requiresCallerInput = false,\n): OperationResult<never> {\n return {\n status: \"authority_required\",\n operation,\n context,\n missing,\n request,\n approval: approvalAction(request, node),\n retry: operationRetryDescriptor(operation, input, requiresCallerInput),\n };\n}\n\nfunction approvalAction(\n request: PermissionRequestArtifact,\n node: unknown,\n): ApprovalAction {\n const maybeNode = node as {\n getOpenKeyApprovalUrl?: (artifact: PermissionRequestArtifact) => unknown;\n };\n let url: string | undefined;\n try {\n const candidate = maybeNode.getOpenKeyApprovalUrl?.(request);\n if (typeof candidate === \"string\" && candidate.length > 0) url = candidate;\n } catch {\n // The stored request remains the source of truth when a runtime does not\n // expose a canonical OpenKey URL.\n }\n return {\n kind: \"openkey\",\n requestId: request.requestId,\n ...(url === undefined ? {} : { url }),\n fallback: `${CLI_NAME} auth grant <request-artifact>`,\n };\n}\n\nfunction exactCapabilities(\n value: unknown,\n): CapabilityRequirement[] | undefined {\n return validateExactCapabilities(value) as\n | CapabilityRequirement[]\n | undefined;\n}\n\nfunction requirementsBelongToAuthenticatedOwner(\n requirements: readonly PermissionEntry[],\n authenticatedSpace: string | undefined,\n): boolean {\n const authenticatedOwner = canonicalPkhOwner(authenticatedSpace);\n if (authenticatedOwner === undefined) return false;\n return requirements.every((requirement) => {\n if (requirement.service === \"tinycloud.encryption\") {\n return requirement.space === undefined;\n }\n const requirementOwner = canonicalPkhOwner(requirement.space);\n return requirement.service === \"tinycloud.kv\" && requirementOwner === authenticatedOwner;\n });\n}\n\nfunction canonicalPkhOwner(space: string | undefined): string | undefined {\n if (typeof space !== \"string\") return undefined;\n const parsedSpace = parseSpaceUri(space);\n if (parsedSpace === null) return undefined;\n try {\n const pkh = parsePkhDid(parsedSpace.owner);\n return pkh === null\n ? undefined\n : parseSpaceUri(makePkhSpaceId(pkh.address, pkh.chainId, parsedSpace.name))?.owner;\n } catch {\n return undefined;\n }\n}\n\nfunction isExactLocalSecretsGetPlan(\n prepared: Exclude<PreparedInvocation, { status: \"error\" }>,\n planned: readonly CapabilityRequirement[],\n context: RuntimeOperationContext,\n): \"valid\" | \"ineligible\" | \"invalid\" {\n if (\n prepared.definition.id !== \"tinycloud.secrets.get\" ||\n prepared.definition.version !== 1 ||\n JSON.stringify(prepared.definition.effects) !== JSON.stringify([\"read\", \"local_write\"])\n ) return \"invalid\";\n\n const resolveSpace = operationSpaceResolver(context.runtime.node, context.summary.space);\n let reference;\n try {\n reference = resolveSecretReferenceForOperation(\n prepared.parsedInput.data,\n context.runtime.node,\n context.summary.space,\n );\n } catch {\n return \"invalid\";\n }\n const canonicalPlan = canonicalizeOperationCapabilities(\n planned as unknown as PermissionEntry[],\n resolveSpace,\n );\n if (canonicalPlan.length !== 2) return \"invalid\";\n const kv = canonicalPlan.find((permission) => permission.service === \"tinycloud.kv\");\n const decrypt = canonicalPlan.find((permission) => permission.service === \"tinycloud.encryption\");\n if (kv === undefined || decrypt === undefined) return \"invalid\";\n const requestedOwner = canonicalPkhOwner(kv.space);\n const authenticatedOwner = canonicalPkhOwner(context.summary.space);\n if (requestedOwner === undefined || authenticatedOwner === undefined || requestedOwner !== authenticatedOwner) {\n return \"ineligible\";\n }\n if (JSON.stringify(kv) !== JSON.stringify({\n service: \"tinycloud.kv\",\n space: resolveSpace(reference.space),\n path: reference.permissionPath,\n actions: [\"tinycloud.kv/get\"],\n })) return \"invalid\";\n if (\n JSON.stringify(decrypt) !== JSON.stringify({\n service: \"tinycloud.encryption\",\n path: decrypt.path,\n actions: [\"tinycloud.encryption/decrypt\"],\n }) ||\n typeof decrypt.path !== \"string\" ||\n !decrypt.path.startsWith(\"urn:tinycloud:encryption:\")\n ) return \"invalid\";\n return \"valid\";\n}\n\nfunction refreshLiveRuntimeAuthority(\n node: unknown,\n requested: readonly PermissionEntry[],\n planned: readonly PermissionEntry[],\n resolveSpace: (space: string) => string,\n): readonly CapabilityRequirement[] | undefined {\n const candidate = node as {\n hasRuntimePermissions?: (permissions: PermissionEntry[]) => unknown;\n getVerifiedSessionCapabilities?: () => unknown;\n getEffectiveRuntimePermissionEntries?: () => unknown;\n };\n if (typeof candidate.hasRuntimePermissions !== \"function\") return undefined;\n if (candidate.hasRuntimePermissions([...requested]) !== true) return undefined;\n if (typeof candidate.getVerifiedSessionCapabilities !== \"function\" ||\n typeof candidate.getEffectiveRuntimePermissionEntries !== \"function\") return undefined;\n\n const base = candidate.getVerifiedSessionCapabilities();\n const activated = candidate.getEffectiveRuntimePermissionEntries();\n if (!Array.isArray(base) || !Array.isArray(activated)) return undefined;\n const exactActivated = validateExactCapabilities(activated);\n if (exactActivated === undefined) return undefined;\n const selectedActivated = exactActivated.filter((permission) =>\n isExactCapabilityMemberSubset([permission], planned, resolveSpace),\n );\n if (!isExactCapabilityMemberSubset(requested, selectedActivated, resolveSpace)) return undefined;\n const combined = canonicalizeOperationCapabilities(\n [...base, ...selectedActivated],\n resolveSpace,\n );\n return evaluateOperationAuthority(combined, planned as unknown as PermissionEntry[], resolveSpace).satisfied\n ? combined\n : undefined;\n}\n\nfunction localAuthorityFailure(\n prepared: Exclude<PreparedInvocation, { status: \"error\" }>,\n): OperationResult<never> {\n return errorResult(\n prepared.operation,\n prepared.runtimeResolution.context.summary,\n operationError(\n \"NODE_ERROR\",\n \"The local owner could not acquire the requested secret permissions.\",\n { retryable: true },\n ),\n );\n}\n\nfunction internalOperationErrorResult(\n prepared: Exclude<PreparedInvocation, { status: \"error\" }>,\n): OperationResult<never> {\n return errorResult(\n prepared.operation,\n prepared.runtimeResolution.context.summary,\n internalOperationError(),\n );\n}\n\nfunction isOwnerPosture(posture: OperationContextSummary[\"posture\"]): boolean {\n return posture === \"owner-openkey\" || posture === \"local-owner-key\";\n}\n\n/** Runtimes that build a TinyCloud node from the profile, and so need the owner and posture gates. */\nfunction requiresRuntime(requirement: OperationRuntimeRequirement): boolean {\n switch (requirement) {\n case \"authenticated\":\n case \"restored\":\n return true;\n case \"inspection\":\n return false;\n }\n}\n\nfunction zodIssueDetails(error: ZodError): Readonly<Record<string, unknown>> {\n return {\n // The canonical error channel carries only schema-owned issue codes.\n // Messages and paths can contain arbitrary input keys and values.\n issues: error.issues.map((issue) => ({ code: issue.code })),\n };\n}\n","import { RUNTIME_DELEGATION_REJECTIONS } from \"@tinycloud/node-sdk\";\n\nimport { DELEGATION_CID_PATTERN } from \"./contract.js\";\nimport type {\n OperationContextSummary,\n OperationDefinition,\n OperationRef,\n} from \"./contract.js\";\nimport type { OperationError, OperationErrorCode } from \"./errors.js\";\nimport { isSafeRecovery } from \"./recovery.js\";\nimport { safeOriginHost, safeSessionDid } from \"./safe-values.js\";\n\nexport const REDACTED_VALUE = \"[REDACTED]\";\nconst DELEGATION_CID = new RegExp(DELEGATION_CID_PATTERN);\n\n\nexport interface SafeOperationDiagnostic {\n readonly operation: OperationRef;\n readonly context: OperationContextSummary;\n readonly input?: unknown;\n readonly output?: unknown;\n readonly error?: OperationError;\n}\n\n/**\n * Return a detached redacted value. A missing pointer is intentionally a no-op\n * so an operation can evolve an optional shape without leaking another field.\n */\nexport function redactOperationValue(\n definition: Pick<OperationDefinition<unknown, unknown>, \"sensitivity\">,\n value: unknown,\n): unknown {\n const clone = cloneValue(value);\n const pointers = [\n ...definition.sensitivity.input,\n ...definition.sensitivity.output,\n ];\n\n let redacted: unknown = clone;\n for (const pointer of pointers) {\n redacted = redactPointer(redacted, pointer);\n }\n return redacted;\n}\n\n/**\n * Diagnostic payloads are a supported non-result channel. They accept raw\n * operation values only through this helper, never an invocation target.\n */\nexport function createSafeOperationDiagnostic(\n definition: Pick<OperationDefinition<unknown, unknown>, \"sensitivity\">,\n diagnostic: Readonly<{\n operation: OperationRef;\n context: OperationContextSummary;\n input?: unknown;\n output?: unknown;\n error?: OperationError;\n }>,\n): SafeOperationDiagnostic {\n return {\n operation: {\n operationId: diagnostic.operation.operationId,\n operationVersion: diagnostic.operation.operationVersion,\n },\n context: {\n profile: diagnostic.context.profile,\n host: diagnostic.context.host,\n posture: diagnostic.context.posture,\n ...(diagnostic.context.operatorType === undefined\n ? {}\n : { operatorType: diagnostic.context.operatorType }),\n ...(diagnostic.context.principalDid === undefined\n ? {}\n : { principalDid: diagnostic.context.principalDid }),\n ...(diagnostic.context.sessionDid === undefined\n ? {}\n : { sessionDid: diagnostic.context.sessionDid }),\n ...(diagnostic.context.ownerDid === undefined\n ? {}\n : { ownerDid: diagnostic.context.ownerDid }),\n ...(diagnostic.context.space === undefined\n ? {}\n : { space: diagnostic.context.space }),\n },\n ...(diagnostic.input === undefined\n ? {}\n : { input: redactOperationValue(definition, diagnostic.input) }),\n ...(diagnostic.output === undefined\n ? {}\n : { output: redactOperationValue(definition, diagnostic.output) }),\n ...(diagnostic.error === undefined\n ? {}\n : { error: redactOperationError(definition, diagnostic.error) }),\n };\n}\n\nexport function redactOperationError(\n _definition: Pick<OperationDefinition<unknown, unknown>, \"sensitivity\">,\n error: OperationError,\n): OperationError {\n const details =\n error.details === undefined\n ? undefined\n : redactSafeErrorDetails(error.code, error.details);\n\n return {\n code: error.code,\n message: error.message,\n retryable: error.retryable,\n ...(details === undefined\n ? {}\n : {\n details: isRecord(details) ? details : { redacted: details },\n }),\n };\n}\n\n/**\n * Error details are a separate, code-owned channel. Only fields with an\n * explicit stable policy can cross the canonical result or diagnostic\n * boundary; arbitrary handler details are dropped rather than redacted by\n * input pointers, which could accidentally preserve a transport secret.\n */\nconst SAFE_ERROR_DETAIL_POLICIES: Readonly<\n Partial<\n Record<\n OperationErrorCode,\n Readonly<Record<string, (value: unknown) => unknown | undefined>>\n >\n >\n> = {\n INPUT_INVALID: {\n issues: (value) =>\n Array.isArray(value) ? safeIssueCodes(value) : undefined,\n reason: (value) =>\n value === \"OWNER_MISMATCH\" || value === \"OWNER_DID_UNKNOWN\" ? value : undefined,\n },\n AUTH_REQUIRED: {\n recovery: (value) => isSafeRecovery(value, \"sign_in\") ? { ...value } : undefined,\n reason: (value) => value === \"explicit_key\" ? value : undefined,\n },\n SESSION_NOT_FOUND: {\n recovery: (value) => isSafeRecovery(value, \"sign_in\") ? { ...value } : undefined,\n },\n NODE_ERROR: {\n reason: (value) => value === \"space_not_hosted\" || value === \"copy_unavailable\" ? value : undefined,\n },\n PROFILE_MIGRATION_REQUIRED: {\n recovery: (value) => isSafeRecovery(value, \"complete_migration\") ? { ...value } : undefined,\n },\n // `secrets.get` not found for an identity no request link serves.\n KV_NOT_FOUND: {\n recoveryKind: (value) =>\n value === \"put_locally\" || value === \"not_requestable\" ? value : undefined,\n names: (value) =>\n Array.isArray(value) && value.length >= 1 && value.length <= 10 &&\n value.every((name) => typeof name === \"string\" && name.length <= 256 && /^[A-Z][A-Z0-9_]*$/.test(name))\n ? [...value]\n : undefined,\n scope: (value) =>\n typeof value === \"string\" && value.length <= 256 && /^[a-z0-9]+(-[a-z0-9]+)*$/.test(value)\n ? value\n : undefined,\n owner: (value) =>\n typeof value === \"string\" && /^did:pkh:eip155:[1-9][0-9]*:0x[0-9a-fA-F]{40}$/.test(value)\n ? value\n : undefined,\n },\n OPERATION_VERSION_UNSUPPORTED: {\n supportedVersions: (value) =>\n Array.isArray(value) && value.every((item) => Number.isInteger(item))\n ? value\n : undefined,\n },\n DELEGATION_AUDIENCE_MISMATCH: {\n expectedSessionDid: safeSessionDid,\n artifactAudience: safeSessionDid,\n },\n DELEGATION_HOST_MISMATCH: {\n expectedHost: safeOriginHost,\n artifactHost: safeOriginHost,\n },\n // `RuntimeDelegationRejectedError.reason` from validated activation.\n DELEGATION_REJECTED: {\n reason: (value) =>\n RUNTIME_DELEGATION_REJECTIONS.some((reason) => reason === value) ? value : undefined,\n },\n STORAGE_QUOTA_EXCEEDED: {\n account: safeStorageAccount,\n },\n};\n\n/** Account-wide totals only; a zero budget explains nothing and is dropped. */\nfunction safeStorageAccount(value: unknown): unknown | undefined {\n if (!isRecord(value)) return undefined;\n const { usedBytes, limitBytes, plan } = value;\n if (\n !Number.isSafeInteger(usedBytes) || (usedBytes as number) < 0 ||\n !Number.isSafeInteger(limitBytes) || (limitBytes as number) <= 0\n ) {\n return undefined;\n }\n return typeof plan === \"string\" && /^[A-Za-z0-9][A-Za-z0-9 _.-]{0,31}$/.test(plan)\n ? { usedBytes, limitBytes, plan }\n : { usedBytes, limitBytes };\n}\n\nfunction redactSafeErrorDetails(\n code: OperationErrorCode,\n details: Readonly<Record<string, unknown>>,\n): Readonly<Record<string, unknown>> | undefined {\n const policy = SAFE_ERROR_DETAIL_POLICIES[code];\n if (policy === undefined) return undefined;\n\n const safeDetails: Record<string, unknown> = {};\n for (const [key, sanitize] of Object.entries(policy)) {\n if (!hasOwn(details, key)) continue;\n const value = sanitize(details[key]);\n if (value !== undefined) safeDetails[key] = value;\n }\n\n if (code === \"AUTH_REQUIRED\") {\n const { reason, revokedCid, ancestorCid } = details;\n if (typeof revokedCid === \"string\" && DELEGATION_CID.exec(revokedCid)?.[0] === revokedCid) {\n if (reason === \"delegation_revoked\") {\n safeDetails.reason = reason;\n safeDetails.revokedCid = revokedCid;\n } else if (reason === \"delegation_ancestor_revoked\" && typeof ancestorCid === \"string\" && DELEGATION_CID.exec(ancestorCid)?.[0] === ancestorCid) {\n safeDetails.reason = reason;\n safeDetails.revokedCid = revokedCid;\n safeDetails.ancestorCid = ancestorCid;\n }\n }\n }\n return Object.keys(safeDetails).length === 0 ? undefined : safeDetails;\n}\n\nfunction safeIssueCodes(\n value: readonly unknown[],\n): readonly Readonly<{ code: string }>[] | undefined {\n const codes = value.map((issue) => {\n if (!isRecord(issue) || typeof issue.code !== \"string\") return undefined;\n return SAFE_ISSUE_CODES.has(issue.code) ? { code: issue.code } : undefined;\n });\n return codes.every(\n (issue): issue is Readonly<{ code: string }> => issue !== undefined,\n )\n ? codes\n : undefined;\n}\n\nconst SAFE_ISSUE_CODES = new Set([\n \"invalid_type\",\n \"invalid_literal\",\n \"custom\",\n \"invalid_union\",\n \"invalid_union_discriminator\",\n \"invalid_enum_value\",\n \"unrecognized_keys\",\n \"invalid_arguments\",\n \"invalid_return_type\",\n \"invalid_date\",\n \"invalid_string\",\n \"too_small\",\n \"too_big\",\n \"invalid_intersection_types\",\n \"not_multiple_of\",\n \"not_finite\",\n]);\n\nfunction redactPointer(value: unknown, pointer: string): unknown {\n if (pointer === \"\") {\n return REDACTED_VALUE;\n }\n if (!pointer.startsWith(\"/\")) {\n return value;\n }\n\n const tokens = pointer.slice(1).split(\"/\").map(unescapePointerToken);\n const parent = getPointerParent(value, tokens);\n if (parent === undefined) {\n return value;\n }\n\n const key = tokens[tokens.length - 1];\n if (key === undefined || !hasOwn(parent, key)) {\n return value;\n }\n\n Reflect.set(parent, key, REDACTED_VALUE);\n return value;\n}\n\nfunction getPointerParent(\n value: unknown,\n tokens: readonly string[],\n): Record<string, unknown> | unknown[] | undefined {\n if (tokens.length === 0) {\n return undefined;\n }\n\n let cursor = value;\n for (const token of tokens.slice(0, -1)) {\n if (!isRecordOrArray(cursor) || !hasOwn(cursor, token)) {\n return undefined;\n }\n cursor = Reflect.get(cursor, token);\n }\n return isRecordOrArray(cursor) ? cursor : undefined;\n}\n\nfunction unescapePointerToken(token: string): string {\n return token.replace(/~1/g, \"/\").replace(/~0/g, \"~\");\n}\n\nfunction cloneValue(value: unknown): unknown {\n if (value === null || typeof value !== \"object\") {\n return value;\n }\n\n if (Array.isArray(value)) {\n return value.map((entry) => cloneValue(entry));\n }\n\n const clone: Record<string, unknown> = {};\n for (const key of Object.keys(value)) {\n Object.defineProperty(clone, key, {\n configurable: true,\n enumerable: true,\n value: cloneValue((value as Record<string, unknown>)[key]),\n writable: true,\n });\n }\n return clone;\n}\n\nfunction isRecordOrArray(\n value: unknown,\n): value is Record<string, unknown> | unknown[] {\n return value !== null && typeof value === \"object\";\n}\n\nfunction hasOwn(value: object, key: string): boolean {\n return Object.prototype.hasOwnProperty.call(value, key);\n}\n\nfunction isRecord(value: unknown): value is Readonly<Record<string, unknown>> {\n return value !== null && typeof value === \"object\" && !Array.isArray(value);\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA,IA+Ga;AA/Gb;AAAA;AAAA;AA+GO,IAAM,yBAAyB;AAAA;AAAA;;;AC/GtC;AAAA;AAAA;AAAA;AAAA;;;ACAA,sBAQa;AARb;AAAA;AAAA;AAAA,uBAAyB;AAQlB,IAAM,eAAW,2BAAS,QAAQ,KAAK,CAAC,KAAK,EAAE,MAAM,cAAc,cAAc;AAAA;AAAA;;;ACRxF,yBAkBa;AAlBb;AAAA;AAAA;AAAA,0BAAuC;AACvC;AAiBO,IAAM,wBACX,4DAA4D,QAAQ;AAAA;AAAA;;;ACC/D,SAAS,iBAAiB,OAA4C;AAC3E,MACE,OAAO,UAAU,YAAY,UAAU,QACvC,EAAE,aAAa,UAAU,MAAM,YAAY,QAC3C,EAAE,UAAU,UACX,MAAM,SAAS,uBAAuB,MAAM,SAAS,6BAA6B,MAAM,SAAS,sCAClG,EAAE,aAAa,UAAU,OAAO,MAAM,YAAY,YAClD,EAAE,UAAU,UAAU,OAAO,MAAM,SAAS,YAAY,MAAM,SAAS,QACvE,EAAE,YAAY,MAAM,SAAS,MAAM,KAAK,WAAW,WACnD,6CAAuB,KAAK,MAAM,kBAClC,QAAO;AACT,QAAM,EAAE,MAAM,QAAQ,IAAI;AAE1B,QAAM,YAAY;AAClB,QAAM,QAAQ,QAAQ,QAAQ,SAAS;AACvC,MAAI,QAAQ,EAAG,QAAO;AACtB,QAAM,OAAO,QAAQ,MAAM,QAAQ,UAAU,MAAM;AACnD,QAAM,QAAQ,qBAAqB,KAAK,IAAI;AAC5C,MAAI,UAAU,QAAQ,MAAM,CAAC,MAAM,KAAM,QAAO;AAChD,MAAI,MAAM,CAAC,MAAM,UAAa,SAAS,oCAAoC;AACzE,UAAM,aAAa,MAAM,CAAC;AAC1B,WAAO,eAAe,KAAK,UAAU,IAAI,EAAE,QAAQ,sBAAsB,WAAW,IAAI;AAAA,EAC1F;AACA,MAAI,MAAM,CAAC,MAAM,UAAa,SAAS,2BAA2B;AAChE,UAAM,cAAc,MAAM,CAAC;AAC3B,UAAM,aAAa,MAAM,CAAC;AAC1B,WAAO,eAAe,KAAK,WAAW,KAAK,eAAe,KAAK,UAAU,IACrE,EAAE,QAAQ,+BAA+B,YAAY,YAAY,IACjE;AAAA,EACN;AACA,SAAO;AACT;AAnDA,IAAAA,sBAWM,gBACA;AAZN;AAAA;AAAA;AAAA,IAAAA,uBAA8D;AAE9D;AAEA;AACA;AAMA,IAAM,iBAAiB,IAAI,OAAO,sBAAsB;AACxD,IAAM,uBACJ;AAAA;AAAA;;;ACbF,IAUa,0BAiBA;AA3Bb,IAAAC,eAAA;AAAA;AAAA;AAUO,IAAM,2BAAN,cAAuC,MAAM;AAAA,MAGlD,YAAY,MAA6B,SAAiB;AACxD,cAAM,OAAO;AACb,aAAK,OAAO;AACZ,aAAK,OAAO;AAAA,MACd;AAAA,IACF;AASO,IAAM,oCAAN,cAAgD,yBAAyB;AAAA,MAC9E,YAAY,SAAiB;AAC3B,cAAM,6BAA6B,OAAO;AAC1C,aAAK,OAAO;AAAA,MACd;AAAA,IACF;AAAA;AAAA;;;ACJO,SAAS,gBAAgB,OAAwB;AACtD,SAAO,UAAU,cAAc,OAAO,GAAG,CAAC;AAC5C;AAEO,SAAS,cAAc,OAAgB,OAAO,KAAgB;AACnE,MAAI,UAAU,MAAM;AAClB,WAAO;AAAA,EACT;AAEA,UAAQ,OAAO,OAAO;AAAA,IACpB,KAAK;AACH,aAAO;AAAA,IACT,KAAK;AACH,UAAI,CAAC,OAAO,SAAS,KAAK,GAAG;AAC3B,cAAM,IAAI;AAAA,UACR,GAAG,IAAI;AAAA,QACT;AAAA,MACF;AACA,aAAO;AAAA,IACT,KAAK;AACH,gCAA0B,OAAO,IAAI;AACrC,aAAO;AAAA,IACT,KAAK;AACH,aAAO,2BAA2B,OAAO,IAAI;AAAA,IAC/C,KAAK;AAAA,IACL,KAAK;AAAA,IACL,KAAK;AAAA,IACL,KAAK;AAAA,IACL;AACE,YAAM,IAAI;AAAA,QACR,GAAG,IAAI;AAAA,MACT;AAAA,EACJ;AACF;AAEO,SAAS,UAAU,OAA0B;AAClD,MAAI,UAAU,MAAM;AAClB,WAAO;AAAA,EACT;AAEA,UAAQ,OAAO,OAAO;AAAA,IACpB,KAAK;AACH,aAAO,QAAQ,SAAS;AAAA,IAC1B,KAAK,UAAU;AACb,YAAM,UAAU,KAAK,UAAU,KAAK;AACpC,UAAI,YAAY,QAAW;AACzB,cAAM,IAAI;AAAA,UACR;AAAA,QACF;AAAA,MACF;AACA,aAAO;AAAA,IACT;AAAA,IACA,KAAK;AACH,aAAO,KAAK,UAAU,KAAK;AAAA,IAC7B,KAAK;AACH,UAAI,MAAM,QAAQ,KAAK,GAAG;AACxB,eAAO,IAAI,MAAM,IAAI,CAAC,SAAS,UAAU,IAAI,CAAC,EAAE,KAAK,GAAG,CAAC;AAAA,MAC3D;AACA,aAAO,gBAAgB,KAAK;AAAA,IAC9B;AACE,YAAM,IAAI;AAAA,QACR,+BAA+B,OAAO,KAAK;AAAA,MAC7C;AAAA,EACJ;AACF;AAEA,SAAS,2BAA2B,OAAe,MAAyB;AAC1E,qBAAmB,OAAO,IAAI;AAE9B,MAAI,MAAM,QAAQ,KAAK,GAAG;AACxB,eAAW,OAAO,OAAO,oBAAoB,KAAK,GAAG;AACnD,UAAI,QAAQ,UAAU;AACpB;AAAA,MACF;AACA,UAAI,CAAC,gBAAgB,KAAK,MAAM,MAAM,GAAG;AACvC,cAAM,IAAI;AAAA,UACR,GAAG,IAAI,IAAI,GAAG;AAAA,QAChB;AAAA,MACF;AACA,YAAM,aAAa,OAAO,yBAAyB,OAAO,GAAG;AAC7D,UAAI,YAAY,eAAe,MAAM;AACnC,cAAM,IAAI;AAAA,UACR,GAAG,IAAI,IAAI,GAAG;AAAA,QAChB;AAAA,MACF;AACA,UAAI,EAAE,WAAW,aAAa;AAC5B,cAAM,IAAI;AAAA,UACR,GAAG,IAAI,IAAI,GAAG;AAAA,QAChB;AAAA,MACF;AAAA,IACF;AACA,UAAMC,UAAsB,CAAC;AAC7B,aAAS,QAAQ,GAAG,QAAQ,MAAM,QAAQ,SAAS;AACjD,UAAI,CAAC,aAAa,OAAO,KAAK,GAAG;AAC/B,cAAM,IAAI;AAAA,UACR,GAAG,IAAI,IAAI,KAAK;AAAA,QAClB;AAAA,MACF;AACA,MAAAA,QAAO,KAAK,cAAc,MAAM,KAAK,GAAG,GAAG,IAAI,IAAI,KAAK,GAAG,CAAC;AAAA,IAC9D;AACA,WAAOA;AAAA,EACT;AAEA,QAAM,QAAQ,OAAO,eAAe,KAAK;AACzC,MAAI,UAAU,OAAO,aAAa,UAAU,MAAM;AAChD,UAAM,IAAI;AAAA,MACR,GAAG,IAAI;AAAA,IACT;AAAA,EACF;AAEA,QAAM,SAAS,uBAAO,OAAO,IAAI;AACjC,aAAW,OAAO,OAAO,oBAAoB,KAAK,GAAG;AACnD,8BAA0B,KAAK,GAAG,IAAI,MAAM;AAC5C,QAAI,QAAQ,eAAe,QAAQ,eAAe;AAChD,YAAM,IAAI;AAAA,QACR,GAAG,IAAI,IAAI,GAAG;AAAA,MAChB;AAAA,IACF;AACA,UAAM,aAAa,OAAO,yBAAyB,OAAO,GAAG;AAC7D,QAAI,YAAY,eAAe,MAAM;AACnC,YAAM,IAAI;AAAA,QACR,GAAG,IAAI,IAAI,GAAG;AAAA,MAChB;AAAA,IACF;AACA,QAAI,EAAE,WAAW,aAAa;AAC5B,YAAM,IAAI;AAAA,QACR,GAAG,IAAI,IAAI,GAAG;AAAA,MAChB;AAAA,IACF;AACA,UAAM,QAAQ,WAAW;AACzB,WAAO,GAAG,IAAI,cAAc,OAAO,GAAG,IAAI,IAAI,GAAG,EAAE;AAAA,EACrD;AACA,SAAO;AACT;AAEA,SAAS,mBAAmB,OAAe,MAAoB;AAC7D,MAAI,OAAO,sBAAsB,KAAK,EAAE,SAAS,GAAG;AAClD,UAAM,IAAI;AAAA,MACR,GAAG,IAAI;AAAA,IACT;AAAA,EACF;AACF;AAEA,SAAS,gBAAgB,KAAa,QAAyB;AAC7D,MAAI,CAAC,iBAAiB,KAAK,GAAG,GAAG;AAC/B,WAAO;AAAA,EACT;AACA,QAAM,QAAQ,OAAO,GAAG;AACxB,SAAO,OAAO,cAAc,KAAK,KAAK,SAAS,KAAK,QAAQ;AAC9D;AAEA,SAAS,gBAAgB,OAAsD;AAC7E,QAAM,OAAO,OAAO,KAAK,KAAK,EAAE,KAAK,qBAAqB;AAC1D,QAAM,QAAQ,KAAK,IAAI,CAAC,QAAQ,GAAG,KAAK,UAAU,GAAG,CAAC,IAAI,UAAU,MAAM,GAAG,CAAC,CAAC,EAAE;AACjF,SAAO,IAAI,MAAM,KAAK,GAAG,CAAC;AAC5B;AAEA,SAAS,sBAAsB,GAAW,GAAmB;AAC3D,QAAM,MAAM,KAAK,IAAI,EAAE,QAAQ,EAAE,MAAM;AACvC,WAAS,QAAQ,GAAG,QAAQ,KAAK,SAAS;AACxC,UAAM,eAAe,EAAE,WAAW,KAAK;AACvC,UAAM,gBAAgB,EAAE,WAAW,KAAK;AACxC,QAAI,iBAAiB,eAAe;AAClC,aAAO,eAAe;AAAA,IACxB;AAAA,EACF;AACA,SAAO,EAAE,SAAS,EAAE;AACtB;AAEA,SAAS,0BAA0B,OAAe,MAAoB;AACpE,WAAS,QAAQ,GAAG,QAAQ,MAAM,QAAQ,SAAS;AACjD,UAAM,OAAO,MAAM,WAAW,KAAK;AACnC,QAAI,QAAQ,SAAU,QAAQ,OAAQ;AACpC,YAAM,OAAO,MAAM,WAAW,QAAQ,CAAC;AACvC,UAAI,OAAO,MAAM,IAAI,KAAK,OAAO,SAAU,OAAO,OAAQ;AACxD,cAAM,IAAI;AAAA,UACR,GAAG,IAAI;AAAA,QACT;AAAA,MACF;AACA;AACA;AAAA,IACF;AACA,QAAI,QAAQ,SAAU,QAAQ,OAAQ;AACpC,YAAM,IAAI;AAAA,QACR,GAAG,IAAI;AAAA,MACT;AAAA,IACF;AAAA,EACF;AACF;AAxNA,IAWM;AAXN;AAAA;AAAA;AAAA,IAAAC;AAWA,IAAM,eACH,OAEE,UACF,OAAO,UAAU,eAAe,KAAK;AAAA,MACpC,OAAO,UAAU;AAAA,IACnB;AAAA;AAAA;;;ACjBF;AAAA;AAAA;AAAA;AAAA;;;ACmEA,SAAS,mBAAsB,KAAmC;AAnElE,MAAAC;AAoEE,QAAM,WAAW;AACjB,SAAO,SAAAA,MAAS,OAAO,IAAI,GAAG,OAAvB,SAAAA,OAA8B,IAAI,0CAAqB;AAChE;AAtEA,wBACA,yBACA,gBACA,iBAiBA,iBACA,gBACAC,mBAiDM,qBAEA,kBASA,gBACA,aAnFN,IAqFM,gBArFND,KAwFM,mBAxFNA,KA2oCM;AA3oCN;AAAA;AAAA;AAAA,yBAA2B;AAC3B,8BAAkC;AAClC,qBAAgF;AAChF,sBAeO;AAEP,sBAAmD;AACnD,qBAAwB;AACxB,IAAAC,oBAAuE;AAiDvE,IAAM,sBAAsB,mBAA2B,6CAA6C;AAEpG,IAAM,mBAAmB,mBAA+C,0CAA0C;AASlH,IAAM,iBAAiB,mBAAgE,wCAAwC;AAC/H,IAAM,cAAc,mBAAgE,qCAAqC;AAEzH,IAAM,iBACH,gBAA0E,uBAAO,IAAI,wCAAwC,OAA7H,iBAAoI,oBAAI,IAAI;AAE/I,IAAM,oBACH,WAAAD,MAA6D,uBAAO,IAAI,6CAA6C,OAArH,WAAAA,WACC,+BAAW;AAijCf,IAAM,oBACH,WAAAA,MAAkE,uBAAO,IAAI,kDAAkD,OAA/H,WAAAA,OACC,oBAAI,IAAY;AAAA;AAAA;;;AC7oCpB,IAAAE,qBAGA,YAgBM,gBAGO,uBAWP,eAKA,uCASA,0BAiBO,iCAkBA,gCAkBA;AApGb;AAAA;AAAA;AAAA,IAAAA,sBAA2B;AAG3B,iBAAkB;AAElB;AAUA;AAEA;AAEA,IAAM,iBAAiB,KAAK,KAAK,KAAK,KAAK;AAGpC,IAAM,wBAAwB,aAAE,OAAO;AAAA,MAC5C,SAAS,aAAE,OAAO,EAAE,IAAI,CAAC;AAAA,MACzB,OAAO,aAAE,OAAO,EAAE,IAAI,CAAC,EAAE,SAAS;AAAA,MAClC,MAAM,aAAE,OAAO;AAAA,MACf,SAAS,aAAE,MAAM,aAAE,OAAO,EAAE,IAAI,CAAC,CAAC,EAAE,IAAI,CAAC;AAAA,MACzC,SAAS,aAAE,MAAM,aAAE,OAAO,aAAE,QAAQ,CAAC,CAAC,EAAE,SAAS;AAAA,MACjD,YAAY,aAAE,QAAQ,EAAE,SAAS;AAAA,MACjC,QAAQ,aAAE,OAAO,EAAE,IAAI,CAAC,EAAE,SAAS;AAAA,MACnC,aAAa,aAAE,OAAO,EAAE,SAAS;AAAA,IACnC,CAAC,EAAE,OAAO;AAEV,IAAM,gBAAgB,aAAE,OAAO;AAAA,MAC7B,MAAM,aAAE,MAAM,aAAE,OAAO,CAAC,EAAE,IAAI,CAAC;AAAA,MAC/B,KAAK,aAAE,OAAO,EAAE,IAAI,CAAC;AAAA,IACvB,CAAC,EAAE,OAAO;AAEV,IAAM,wCAAwC,aAAE,OAAO;AAAA,MACrD,MAAM,aAAE,QAAQ,wBAAwB;AAAA,MACxC,SAAS,aAAE,QAAQ,CAAC;AAAA,MACpB,WAAW,aAAE,OAAO;AAAA,MACpB,YAAY,aAAE,OAAO,EAAE,IAAI,CAAC;AAAA,MAC5B,WAAW,aAAE,MAAM,qBAAqB;AAAA,MACxC,iBAAiB,aAAE,MAAM,CAAC,aAAE,OAAO,GAAG,aAAE,OAAO,EAAE,OAAO,CAAC,CAAC,EAAE,SAAS;AAAA,IACvE,CAAC,EAAE,OAAO;AAEV,IAAM,2BAA+D,aAAE,KAAK,MAAM,aAAE,OAAO;AAAA,MACzF,KAAK,aAAE,OAAO,EAAE,IAAI,CAAC;AAAA,MACrB,SAAS,aAAE,OAAO,EAAE,IAAI,CAAC;AAAA,MACzB,MAAM,aAAE,OAAO;AAAA,MACf,SAAS,aAAE,MAAM,aAAE,OAAO,EAAE,IAAI,CAAC,CAAC,EAAE,IAAI,CAAC;AAAA,MACzC,aAAa,aAAE,OAAO,EAAE,IAAI,CAAC;AAAA,MAC7B,cAAc,aAAE,OAAO,EAAE,IAAI,CAAC;AAAA,MAC9B,SAAS,aAAE,OAAO,EAAE,IAAI,EAAE,SAAS;AAAA,MACnC,QAAQ,aAAE,MAAM,CAAC,aAAE,OAAO,EAAE,SAAS,EAAE,QAAQ,KAAK,CAAC,GAAG,aAAE,KAAK,CAAC,CAAC;AAAA,MACjE,kBAAkB,aAAE,OAAO,EAAE,eAAe,aAAE,OAAO,EAAE,IAAI,CAAC,EAAE,CAAC,EAAE,OAAO;AAAA,MACxE,MAAM,aAAE,OAAO,EAAE,IAAI,CAAC,EAAE,SAAS;AAAA,MACjC,sBAAsB,aAAE,QAAQ,EAAE,SAAS;AAAA,MAC3C,kBAAkB,yBAAyB,SAAS;AAAA,MACpD,WAAW,aAAE,MAAM,qBAAqB,EAAE,SAAS;AAAA,IACrD,CAAC,EAAE,YAAY,CAAC;AAGT,IAAM,kCAAkC,aAAE,OAAO;AAAA,MACtD,MAAM,aAAE,QAAQ,wBAAwB;AAAA,MACxC,SAAS,aAAE,QAAQ,CAAC;AAAA,MACpB,WAAW,aAAE,OAAO,EAAE,IAAI,CAAC;AAAA,MAC3B,WAAW,aAAE,OAAO,EAAE,SAAS,EAAE,QAAQ,KAAK,CAAC;AAAA,MAC/C,SAAS,aAAE,OAAO,EAAE,IAAI,CAAC;AAAA,MACzB,SAAS,aAAE,KAAK,CAAC,iBAAiB,oBAAoB,mBAAmB,iBAAiB,CAAC;AAAA,MAC3F,cAAc,aAAE,KAAK,CAAC,SAAS,OAAO,CAAC;AAAA,MACvC,MAAM,aAAE,OAAO,EAAE,IAAI,CAAC;AAAA,MACtB,YAAY,aAAE,OAAO,EAAE,IAAI,CAAC;AAAA,MAC5B,UAAU,aAAE,OAAO,EAAE,IAAI,CAAC,EAAE,SAAS;AAAA,MACrC,SAAS,aAAE,OAAO,EAAE,IAAI,CAAC,EAAE,SAAS;AAAA,MACpC,iBAAiB,aAAE,MAAM,CAAC,aAAE,OAAO,EAAE,IAAI,CAAC,GAAG,aAAE,OAAO,EAAE,OAAO,CAAC,CAAC,EAAE,SAAS;AAAA,MAC5E,WAAW,aAAE,MAAM,qBAAqB;AAAA,MACxC,SAAS,cAAc,SAAS;AAAA,IAClC,CAAC,EAAE,OAAO;AAGH,IAAM,iCAAiC,aAAE,OAAO;AAAA,MACrD,MAAM,aAAE,QAAQ,2BAA2B;AAAA,MAC3C,SAAS,aAAE,QAAQ,CAAC;AAAA,MACpB,WAAW,aAAE,OAAO,EAAE,IAAI,CAAC;AAAA,MAC3B,eAAe,aAAE,OAAO,EAAE,IAAI,CAAC,EAAE,SAAS;AAAA,MAC1C,YAAY;AAAA;AAAA;AAAA,MAGZ,aAAa,aAAE,MAAM,qBAAqB,EAAE,SAAS;AAAA,MACrD,QAAQ,aAAE,OAAO,EAAE,SAAS,EAAE,QAAQ,KAAK,CAAC,EAAE,SAAS;AAAA,MACvD,UAAU,aAAE,QAAQ,EAAE,SAAS;AAAA,IACjC,CAAC,EAAE,OAAO;AAOH,IAAM,iCAAiC,gCAC3C,KAAK,EAAE,WAAW,MAAM,WAAW,KAAK,CAAC,EACzC,OAAO;AAAA;AAAA;;;ACtGV;AAAA;AAAA;AAAA;AAAA;AAAA;;;ACAA,IAEA;AAFA;AAAA;AAAA;AAEA,cAAyB;AAEzB;AAAA;AAAA;;;ACJA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;;ACAA,IAAAC,sBAA2B;AAC3B,IAAAC,oBAA2B;AAE3B;AACA,IAAAC,mBAKO;AACP;AAeA;AAMA;AAMA;;;ACrCA,sBAA8C;AAE9C;AAOA;AAIA,IAAMC,kBAAiB,IAAI,OAAO,sBAAsB;;;AD+BxD;AAEA;AAyuBO,SAAS,yBACd,WACA,OACA,sBAAsB,OACL;AACjB,SAAO;AAAA,IACL,aAAa,UAAU;AAAA,IACvB,kBAAkB,UAAU;AAAA,IAC5B,iBAAa,gCAAW,QAAQ,EAC7B,OAAO,gBAAgB,KAAK,GAAG,MAAM,EACrC,OAAO,KAAK;AAAA,IACf;AAAA,EACF;AACF;","names":["import_sdk_services","init_errors","output","init_errors","_a","import_node_path","import_node_crypto","import_node_crypto","import_node_path","import_node_sdk","DELEGATION_CID"]}
1
+ {"version":3,"sources":["../src/contract.ts","../src/errors.ts","../src/cli-name.ts","../src/revoked-grant.ts","../src/kv-failure.ts","../../sdk-core/src/policy/errors.ts","../../sdk-core/src/policy/jcs.ts","../src/authority.ts","../src/state.ts","../src/artifacts.ts","../src/recovery.ts","../src/secrets.ts","../src/mcp-authority.ts","../src/invoke.ts","../src/redaction.ts"],"sourcesContent":["import type { z } from \"zod\";\n\nimport type { OperationError, OperationErrorCode } from \"./errors.js\";\n\n/** A stable, cross-surface operation identifier. */\nexport type OperationId = string;\n\n/** JSON Pointer syntax from RFC 6901, including the empty pointer for a root value. */\nexport type JsonPointer = \"\" | `/${string}`;\n\nexport type OperationEffect =\n | \"read\"\n | \"local_write\"\n | \"write\"\n | \"destructive\"\n | \"external\";\n\nexport type TinyCloudPosture =\n | \"owner-openkey\"\n | \"delegate-session\"\n | \"local-owner-key\"\n | \"unauthenticated\";\n\nexport type OperationOperatorType = \"human\" | \"agent\";\n\nexport type SurfaceDisposition =\n | Readonly<{ status: \"required\" }>\n | Readonly<{ status: \"excluded\"; reason: string }>;\n\n/**\n * Safe identity reported with every result. This deliberately excludes keys,\n * tokens, delegations, grants, and operation inputs or outputs.\n */\nexport interface OperationContextSummary {\n readonly profile: string;\n readonly host: string;\n readonly posture: TinyCloudPosture;\n readonly operatorType?: OperationOperatorType;\n readonly principalDid?: string;\n readonly sessionDid?: string;\n readonly ownerDid?: string;\n readonly space?: string;\n}\n\n/**\n * The profile foundation constructs this context for every invocation. The\n * invocation target never supplies an execution context or runtime object.\n */\nexport interface OperationContext {\n readonly summary: OperationContextSummary;\n /** Present for every normal invocation; optional for focused definition tests. */\n readonly runtime?: OperationRuntime;\n}\n\n/**\n * Per-invocation authority that is constructed inside operations. It is never\n * caller supplied and it is deliberately absent from result envelopes.\n */\nexport interface OperationRuntime {\n readonly node: unknown;\n readonly granted: readonly CapabilityRequirement[];\n /** Diagnostics gathered while building the runtime, attached to the result. */\n readonly warnings?: readonly OperationWarning[];\n /**\n * Internal, never projected. Whether the live identity is a local owner key\n * (an explicit key, or a profile `isLocalOwnerProfile` accepts). Set by every\n * runtime this package builds; optional only for focused definition tests.\n */\n readonly localOwner?: boolean;\n /** Internal, never projected: the reasons stored grants were skipped during replay. */\n readonly skippedGrantReasons?: readonly StoredGrantSkipReason[];\n /** Successful stored-grant replays, for value-free hosted call diagnostics. */\n readonly replayedGrants?: number;\n}\n\n/**\n * Why a stored grant was not used. A fixed vocabulary: diagnostics never\n * carry exception text, node responses, URLs or other stored transport data.\n * `unbound`: the record has no valid request binding where one is required;\n * `outside_request`: its signed authority exceeds its binding;\n * `activation_unavailable`: the node could not be reached, timed out, was\n * rate limited or failed (transport, 408, 429, 5xx) while activating it;\n * `activation_unauthenticated`: the node answered 401 while activating it.\n */\nexport const STORED_GRANT_SKIP_REASONS = [\n \"malformed\",\n \"unbound\",\n \"outside_request\",\n \"host_mismatch\",\n \"expired\",\n \"cid_mismatch\",\n \"authority_invalid\",\n \"proof_missing\",\n \"proof_invalid\",\n \"owner_mismatch\",\n \"audience_mismatch\",\n \"resources_mismatch\",\n \"unsupported\",\n \"activation_rejected\",\n \"activation_unavailable\",\n \"activation_unauthenticated\",\n \"invalid\",\n] as const;\n\nexport type StoredGrantSkipReason = typeof STORED_GRANT_SKIP_REASONS[number];\n\n/**\n * The string form of every delegation CID TinyCloud computes: CIDv1, raw\n * codec, BLAKE3-256 multihash, base32. A published `grantCid` also has to be\n * the CID recomputed from the stored authorization bytes.\n */\nexport const DELEGATION_CID_PATTERN = \"^bafkr4i[a-z2-7]{52}$\";\n\n/**\n * A non-fatal diagnostic attached to a result. `STORED_GRANT_SKIPPED`: a\n * stored grant failed validation and gave no authority; `grantCid` is present\n * only when the stored CID is the CID of the stored authorization bytes.\n */\nexport interface OperationWarning {\n readonly code: \"STORED_GRANT_SKIPPED\";\n readonly reason: StoredGrantSkipReason;\n readonly grantCid?: string;\n}\n\n/** Context given to planners and handlers after runtime authentication. */\nexport interface RuntimeOperationContext extends OperationContext {\n readonly runtime: OperationRuntime;\n}\n\nexport type AuthorityPlanningContext = RuntimeOperationContext;\n\n/**\n * Capability details are finalized by the authority/artifacts increment. The\n * kernel intentionally preserves them as canonical operation-owned data.\n */\nexport type CapabilityRequirement = Readonly<Record<string, unknown>>;\n\nexport interface PermissionRequestArtifact {\n readonly requestId: string;\n readonly [key: string]: unknown;\n}\n\nexport interface ApprovalAction {\n readonly kind: \"openkey\";\n readonly requestId: string;\n readonly url?: string;\n readonly fallback: string;\n}\n\nexport interface SetupAction {\n readonly kind: string;\n readonly [key: string]: unknown;\n}\n\nexport interface OperationRef {\n readonly operationId: OperationId;\n readonly operationVersion: number;\n}\n\nexport interface RetryDescriptor extends OperationRef {\n readonly inputDigest: string;\n readonly safeInput?: Readonly<Record<string, unknown>>;\n readonly requiresCallerInput: boolean;\n}\n\nexport interface OperationSensitivity {\n readonly input: readonly JsonPointer[];\n readonly output: readonly JsonPointer[];\n}\n\n/**\n * An inspection operation may read safe local profile state without creating a\n * node or authenticating. An authenticated operation gets a runtime that may\n * sign in. A restored operation gets a runtime built only from the profile's\n * stored session: it never signs in, migrates, takes a profile lock or writes\n * profile state.\n */\nexport type OperationRuntimeRequirement = \"authenticated\" | \"inspection\" | \"restored\";\n\nexport interface OperationExposure {\n readonly cli: SurfaceDisposition;\n readonly mcp: SurfaceDisposition;\n readonly skill: SurfaceDisposition;\n readonly docs: SurfaceDisposition;\n}\n\nexport interface OperationOkResult<O> {\n readonly status: \"ok\";\n readonly operation: OperationRef;\n readonly context: OperationContextSummary;\n readonly output: O;\n readonly warnings?: readonly OperationWarning[];\n}\n\nexport interface OperationAuthorityRequiredResult {\n readonly status: \"authority_required\";\n readonly operation: OperationRef;\n readonly context: OperationContextSummary;\n readonly missing: readonly CapabilityRequirement[];\n readonly request: PermissionRequestArtifact;\n readonly approval: ApprovalAction;\n readonly retry: RetryDescriptor;\n readonly warnings?: readonly OperationWarning[];\n}\n\nexport interface OperationSetupRequiredResult {\n readonly status: \"setup_required\";\n readonly operation: OperationRef;\n readonly context: OperationContextSummary;\n readonly setup: SetupAction;\n readonly retry: RetryDescriptor;\n readonly warnings?: readonly OperationWarning[];\n}\n\nexport interface OperationErrorResult {\n readonly status: \"error\";\n readonly operation: OperationRef;\n readonly context: OperationContextSummary;\n readonly error: OperationError;\n readonly warnings?: readonly OperationWarning[];\n}\n\nexport type OperationResult<O> =\n | OperationOkResult<O>\n | OperationAuthorityRequiredResult\n | OperationSetupRequiredResult\n | OperationErrorResult;\n\n/**\n * Handler outcomes intentionally omit invocation-owned operation, context, and\n * retry metadata. The kernel attaches those values after validation.\n */\nexport type OperationExecutionOutcome<O> =\n | Readonly<{ status: \"ok\"; output: O }>\n | Readonly<{\n status: \"authority_required\";\n missing: readonly CapabilityRequirement[];\n request: PermissionRequestArtifact;\n approval: ApprovalAction;\n requiresCallerInput?: boolean;\n }>\n | Readonly<{\n status: \"setup_required\";\n setup: SetupAction;\n requiresCallerInput?: boolean;\n }>\n | Readonly<{ status: \"error\"; error: OperationError }>;\n\n/**\n * Internal registry material. It is intentionally never re-exported by the\n * package root: projections can execute only by operation id and version.\n */\nexport interface OperationDefinition<I, O> {\n readonly id: OperationId;\n readonly version: 1;\n readonly title: string;\n readonly description: string;\n readonly input: z.ZodType<I>;\n readonly output: z.ZodType<O>;\n readonly effects: readonly OperationEffect[];\n readonly runtime: OperationRuntimeRequirement;\n readonly postures: readonly TinyCloudPosture[];\n readonly exposure: OperationExposure;\n readonly sensitivity: OperationSensitivity;\n /** A definition may give malformed transport artifacts a stable error code. */\n readonly invalidInputErrorCode?: OperationErrorCode;\n /**\n * Response deadline. When set, `invokeOperation` returns a retryable\n * `NODE_UNREACHABLE` if the invocation has not finished within it; work\n * already started is abandoned, not cancelled.\n */\n readonly deadlineMs?: number;\n readonly authority: (\n context: AuthorityPlanningContext,\n input: I,\n ) => Promise<readonly CapabilityRequirement[]>;\n readonly execute: (\n context: OperationContext,\n input: I,\n ) => Promise<OperationExecutionOutcome<O>>;\n}\n\n/**\n * The only caller-controlled invocation settings. `privateKey` is a CLI-only,\n * nonpersistable override; it is deliberately excluded from result metadata,\n * retry descriptors, diagnostics, and all state written by this package.\n */\nexport interface InvocationTarget {\n readonly profile?: string;\n readonly host?: string;\n /**\n * Absolute home directory used for this invocation's TinyCloud profile\n * state. Hosted projections set this per authenticated principal so\n * concurrent tenants never share process-global TC_HOME state.\n */\n readonly stateRoot?: string;\n readonly allowOwnerProfile?: boolean;\n readonly privateKey?: string;\n}\n\n/**\n * Copy only the identity fields allowed in a public result. Keeping this\n * projection centralized prevents a profile/runtime implementation from\n * accidentally smuggling execution material into an envelope.\n */\nexport function safeOperationContextSummary(\n summary: OperationContextSummary,\n): OperationContextSummary {\n return {\n profile: summary.profile,\n host: summary.host,\n posture: summary.posture,\n ...(summary.operatorType === undefined\n ? {}\n : { operatorType: summary.operatorType }),\n ...(summary.principalDid === undefined\n ? {}\n : { principalDid: summary.principalDid }),\n ...(summary.sessionDid === undefined ? {} : { sessionDid: summary.sessionDid }),\n ...(summary.ownerDid === undefined ? {} : { ownerDid: summary.ownerDid }),\n ...(summary.space === undefined ? {} : { space: summary.space }),\n };\n}\n","export const OPERATION_ERROR_CODES = [\n \"INPUT_INVALID\",\n \"OPERATION_NOT_FOUND\",\n \"OPERATION_VERSION_UNSUPPORTED\",\n \"PROFILE_NOT_FOUND\",\n \"PROFILE_POSTURE_NOT_ALLOWED\",\n \"PROFILE_OWNER_OPT_IN_REQUIRED\",\n \"PROFILE_MIGRATION_REQUIRED\",\n \"AUTH_REQUIRED\",\n \"PERMISSION_DENIED\",\n \"SESSION_NOT_FOUND\",\n \"PERMISSION_HINT_INVALID\",\n \"DELEGATION_ARTIFACT_INVALID\",\n \"DELEGATION_EXPIRED\",\n \"DELEGATION_AUDIENCE_MISMATCH\",\n \"DELEGATION_HOST_MISMATCH\",\n \"DELEGATION_REJECTED\",\n \"ENCRYPTION_NETWORK_UNRESOLVED\",\n \"NODE_UNREACHABLE\",\n \"NODE_ERROR\",\n \"STORAGE_QUOTA_EXCEEDED\",\n \"KV_NOT_FOUND\",\n \"KV_PRECONDITION_FAILED\",\n \"KV_CONFLICT\",\n \"KV_RESPONSE_TOO_LARGE\",\n \"SQL_QUERY_INVALID\",\n \"SQL_EXECUTION_FAILED\",\n \"SQL_RESULT_LIMIT_EXCEEDED\",\n \"SQL_VALUE_UNSAFE\",\n \"READ_SOURCE_CHANGED\",\n \"READ_CONTEXT_MISMATCH\",\n \"SECRET_READ_FAILED\",\n \"SECRET_DECRYPT_FAILED\",\n \"OUTPUT_INVALID\",\n \"INTERNAL_ERROR\",\n] as const;\n\nexport type OperationErrorCode = (typeof OPERATION_ERROR_CODES)[number];\n\nexport interface OperationError {\n readonly code: OperationErrorCode;\n readonly message: string;\n readonly retryable: boolean;\n readonly details?: Readonly<Record<string, unknown>>;\n}\n\nexport function operationError(\n code: OperationErrorCode,\n message: string,\n options: Readonly<{\n retryable?: boolean;\n details?: Readonly<Record<string, unknown>>;\n }> = {},\n): OperationError {\n return {\n code,\n message,\n retryable: options.retryable ?? false,\n ...(options.details === undefined ? {} : { details: options.details }),\n };\n}\n\nconst MANAGE_STORAGE = \"Do not retry: the owner must free up space or upgrade at https://account.tinycloud.xyz/storage.\";\n\n/**\n * A write the node refused because the owner's TinyCloud storage is full\n * (SDK `STORAGE_QUOTA_EXCEEDED`, HTTP 402) or too small for this write\n * (`STORAGE_LIMIT_REACHED`, HTTP 413). Nothing was written, so the outcome is\n * known, and it stays refused until the owner acts, so it is never retryable.\n * The SDK's account totals travel as details; per-space numbers never do.\n */\nexport function storageFullOperationError(error: unknown): OperationError | undefined {\n if (typeof error !== \"object\" || error === null) return undefined;\n const { code, meta } = error as { code?: unknown; meta?: unknown };\n if (code !== \"STORAGE_QUOTA_EXCEEDED\" && code !== \"STORAGE_LIMIT_REACHED\") return undefined;\n const account = typeof meta === \"object\" && meta !== null ? (meta as { account?: unknown }).account : undefined;\n return operationError(\n \"STORAGE_QUOTA_EXCEEDED\",\n code === \"STORAGE_LIMIT_REACHED\"\n ? `This write is larger than the TinyCloud storage left, so nothing was written. Reading still works. ${MANAGE_STORAGE}`\n : `TinyCloud storage is full, so nothing was written. Reading still works. ${MANAGE_STORAGE}`,\n { retryable: false, ...(account === undefined ? {} : { details: { account } }) },\n );\n}\n\n/**\n * The SDK's node retry codes (`NODE_BUSY`, `REQUEST_TIMEOUT`, `OUTCOME_UNKNOWN`) as `NODE_ERROR` with the\n * SDK's own verdict: a busy node or a deadline hit before any write began applied nothing, so a retry is\n * safe; `OUTCOME_UNKNOWN` may have applied a write, so it is never retryable and says to read back first.\n */\nexport function nodeRetryOperationError(error: unknown): OperationError | undefined {\n const code = typeof error === \"object\" && error !== null && \"code\" in error ? error.code : undefined;\n if (code === \"OUTCOME_UNKNOWN\") {\n return operationError(\n \"NODE_ERROR\",\n \"The TinyCloud node may have applied the write; read the data back before retrying.\",\n { retryable: false },\n );\n }\n if (code !== \"NODE_BUSY\" && code !== \"REQUEST_TIMEOUT\") return undefined;\n return operationError(\"NODE_ERROR\", \"The TinyCloud node was busy and applied nothing; try again shortly.\", { retryable: true });\n}\n\n/**\n * Internal operations and profile resolution use this for expected failures.\n * Generic exceptions are never exposed at the projection boundary.\n */\nexport class OperationInvocationError extends Error {\n public readonly operationError: OperationError;\n\n constructor(operationError: OperationError) {\n super(operationError.message);\n this.name = \"OperationInvocationError\";\n this.operationError = operationError;\n }\n}\n\nexport function isOperationErrorCode(value: unknown): value is OperationErrorCode {\n return typeof value === \"string\" && OPERATION_ERROR_CODES.includes(value as OperationErrorCode);\n}\n\nexport function internalOperationError(): OperationError {\n return operationError(\n \"INTERNAL_ERROR\",\n \"The operation could not be completed.\",\n );\n}\n\nexport function sanitizeThrownOperationError(error: unknown): OperationError {\n if (error instanceof OperationInvocationError) {\n return sanitizeOperationError(error.operationError);\n }\n\n return internalOperationError();\n}\n\nexport function sanitizeOperationError(error: unknown): OperationError {\n if (\n typeof error !== \"object\" ||\n error === null ||\n !isOperationErrorCode((error as { code?: unknown }).code) ||\n typeof (error as { message?: unknown }).message !== \"string\" ||\n typeof (error as { retryable?: unknown }).retryable !== \"boolean\"\n ) {\n return internalOperationError();\n }\n\n const candidate = error as OperationError;\n return operationError(candidate.code, candidate.message, {\n retryable: candidate.retryable,\n ...(candidate.details === undefined ? {} : { details: candidate.details }),\n });\n}\n","import { basename } from \"node:path\";\n\n/**\n * The command name hints and messages tell users to run. The CLI installs as\n * both `tc` and `tinycloud` (Ubuntu's iproute2 ships `/usr/sbin/tc`), so the\n * name follows the invoked bin. Any other host process, such as the MCP server,\n * gets `tc`.\n */\nexport const CLI_NAME = basename(process.argv[1] ?? \"\") === \"tinycloud\" ? \"tinycloud\" : \"tc\";\n","import { authorizationVerdictOf } from \"@tinycloud/sdk-services\";\nimport { CLI_NAME } from \"./cli-name.js\";\n\n/** Cause links read for the node's refusal; the SDK wraps it, so it is rarely the outermost error. */\nconst MAX_CAUSE_DEPTH = 8;\n/**\n * The node's whole 401 body for a revoked chain (`delegation-revoked: <cid>`, or\n * `delegation-ancestor-revoked: ancestor=<cid> invoked=<cid>`; older nodes prefix `Invalid invocation: `),\n * as it ends an SDK error message (`<what failed>: 401 - <body>`). The match starts at the `401 - ` separator\n * and runs to the end of the message, and only letters and digits may follow the error kind (the CIDs are not\n * checked beyond that), so a key or path in another 401 body (`Unauthorized Action: <space>/kv/…/delegation-revoked/ / …`)\n * or any other text that mentions \"revoked\" does not match.\n */\nconst REVOKED_GRANT_BODY =\n /\\b401 - (?:Invalid invocation: )?delegation-(?:revoked: [A-Za-z0-9]+|ancestor-revoked: ancestor=[A-Za-z0-9]+ invoked=[A-Za-z0-9]+)\\s*$/;\n/** The KV service's own typed codes for the same two bodies. */\nconst REVOKED_GRANT_CODES: ReadonlySet<unknown> = new Set([\"AUTH_DELEGATION_REVOKED\", \"AUTH_DELEGATION_ANCESTOR_REVOKED\"]);\n\nexport const REVOKED_GRANT_MESSAGE =\n `This grant was revoked. Ask the owner for a new grant (\\`${CLI_NAME} auth request ...\\`).`;\n\n/**\n * Whether a node refusal says the presented grant (or one it was derived from) was revoked.\n * Status alone cannot tell this from a stale session, so it also needs the typed code or the node's own\n * `delegation-revoked` / `delegation-ancestor-revoked` 401 body; any other 401 keeps its meaning.\n */\nexport function isRevokedGrantRefusal(error: unknown): boolean {\n if (authorizationVerdictOf(error) !== \"unauthenticated\") return false;\n const seen = new Set<unknown>();\n let node: unknown = error;\n for (let depth = 0; depth < MAX_CAUSE_DEPTH && typeof node === \"object\" && node !== null && !seen.has(node); depth += 1) {\n seen.add(node);\n const { code, message, cause } = node as { code?: unknown; message?: unknown; cause?: unknown };\n if (REVOKED_GRANT_CODES.has(code)) return true;\n if (typeof message === \"string\" && REVOKED_GRANT_BODY.test(message)) return true;\n node = cause;\n }\n return false;\n}\n","import { authorizationVerdictOf, validatedCapabilityOf } from \"@tinycloud/sdk-services\";\n\nimport { DELEGATION_CID_PATTERN } from \"./contract.js\";\nimport type { OperationExecutionOutcome } from \"./contract.js\";\nimport { operationError } from \"./errors.js\";\nimport { isRevokedGrantRefusal, REVOKED_GRANT_MESSAGE } from \"./revoked-grant.js\";\n\nexport type NodeRevocation =\n | Readonly<{ reason: \"delegation_revoked\"; revokedCid: string }>\n | Readonly<{ reason: \"delegation_ancestor_revoked\"; revokedCid: string; ancestorCid: string }>;\n\nconst DELEGATION_CID = new RegExp(DELEGATION_CID_PATTERN);\nconst NODE_REVOCATION_BODY =\n /^(?:Invalid invocation: )?delegation-(?:(revoked): ([A-Za-z0-9]+)|(ancestor-revoked): ancestor=([A-Za-z0-9]+) invoked=([A-Za-z0-9]+))$/;\n\n/**\n * Classify a definite node refusal from a KV SDK service error. The SDK puts\n * the entire node response after its `: 401 - ` separator; never search for a\n * marker within arbitrary error prose or accept a different HTTP status.\n */\nexport function nodeRevocationOf(error: unknown): NodeRevocation | undefined {\n if (\n typeof error !== \"object\" || error === null ||\n !(\"service\" in error) || error.service !== \"kv\" ||\n !(\"code\" in error) ||\n (error.code !== \"AUTH_UNAUTHORIZED\" && error.code !== \"AUTH_DELEGATION_REVOKED\" && error.code !== \"AUTH_DELEGATION_ANCESTOR_REVOKED\") ||\n !(\"message\" in error) || typeof error.message !== \"string\" ||\n !(\"meta\" in error) || typeof error.meta !== \"object\" || error.meta === null ||\n !(\"status\" in error.meta) || error.meta.status !== 401 ||\n authorizationVerdictOf(error) !== \"unauthenticated\"\n ) return undefined;\n const { code, message } = error;\n\n const separator = \": 401 - \";\n const start = message.indexOf(separator);\n if (start < 0) return undefined;\n const body = message.slice(start + separator.length);\n const match = NODE_REVOCATION_BODY.exec(body);\n if (match === null || match[0] !== body) return undefined;\n if (match[1] !== undefined && code !== \"AUTH_DELEGATION_ANCESTOR_REVOKED\") {\n const revokedCid = match[2]!;\n return DELEGATION_CID.test(revokedCid) ? { reason: \"delegation_revoked\", revokedCid } : undefined;\n }\n if (match[3] !== undefined && code !== \"AUTH_DELEGATION_REVOKED\") {\n const ancestorCid = match[4]!;\n const revokedCid = match[5]!;\n return DELEGATION_CID.test(ancestorCid) && DELEGATION_CID.test(revokedCid)\n ? { reason: \"delegation_ancestor_revoked\", revokedCid, ancestorCid }\n : undefined;\n }\n return undefined;\n}\n\n/**\n * The TC-634 split of a node refusal: a 403, or a 401 that names a validated\n * missing capability, is a permission denial; any other 401 means the caller\n * must authenticate again. `undefined` when the error is not a refusal.\n */\nexport function kvRefusal(error: unknown): \"permission_denied\" | \"auth_required\" | undefined {\n const verdict = authorizationVerdictOf(error);\n if (verdict === \"unauthenticated\") {\n return validatedCapabilityOf(error) === undefined ? \"auth_required\" : \"permission_denied\";\n }\n return verdict === \"forbidden\" ? \"permission_denied\" : undefined;\n}\n\n/** The canonical error for a node refusal, or `undefined` when `error` is not one. */\nexport function authorizationFailure(error: unknown, action: string): OperationExecutionOutcome<never> | undefined {\n switch (kvRefusal(error)) {\n case \"auth_required\": {\n const revocation = nodeRevocationOf(error);\n return {\n status: \"error\",\n error: operationError(\"AUTH_REQUIRED\", isRevokedGrantRefusal(error)\n ? REVOKED_GRANT_MESSAGE\n : `Authentication is required to ${action}.`, {\n ...(revocation === undefined ? {} : { details: revocation }),\n }),\n };\n }\n case \"permission_denied\":\n return { status: \"error\", error: operationError(\"PERMISSION_DENIED\", `Permission is denied to ${action}.`) };\n case undefined:\n return undefined;\n }\n}\n","export type SignedObjectErrorCode =\n | \"schema-invalid\"\n | \"canonicalization-mismatch\"\n | \"digest-mismatch\"\n | \"id-mismatch\"\n | \"signing-key-binding\"\n | \"signature-material-invalid\"\n | \"signature-suite-unsupported\"\n | \"signature-invalid\";\n\nexport class SignedObjectProfileError extends Error {\n public readonly code: SignedObjectErrorCode;\n\n constructor(code: SignedObjectErrorCode, message: string) {\n super(message);\n this.name = \"SignedObjectProfileError\";\n this.code = code;\n }\n}\n\nexport class SignedObjectSchemaError extends SignedObjectProfileError {\n constructor(message: string) {\n super(\"schema-invalid\", message);\n this.name = \"SignedObjectSchemaError\";\n }\n}\n\nexport class SignedObjectCanonicalizationError extends SignedObjectProfileError {\n constructor(message: string) {\n super(\"canonicalization-mismatch\", message);\n this.name = \"SignedObjectCanonicalizationError\";\n }\n}\n\nexport class SignedObjectDigestError extends SignedObjectProfileError {\n constructor(message: string) {\n super(\"digest-mismatch\", message);\n this.name = \"SignedObjectDigestError\";\n }\n}\n\nexport class SignedObjectIdError extends SignedObjectProfileError {\n constructor(message: string) {\n super(\"id-mismatch\", message);\n this.name = \"SignedObjectIdError\";\n }\n}\n\nexport class SigningKeyBindingError extends SignedObjectProfileError {\n constructor(message: string) {\n super(\"signing-key-binding\", message);\n this.name = \"SigningKeyBindingError\";\n }\n}\n\nexport class SignatureMaterialError extends SignedObjectProfileError {\n constructor(message: string) {\n super(\"signature-material-invalid\", message);\n this.name = \"SignatureMaterialError\";\n }\n}\n\nexport class UnsupportedSignatureSuiteError extends SignedObjectProfileError {\n constructor(message: string) {\n super(\"signature-suite-unsupported\", message);\n this.name = \"UnsupportedSignatureSuiteError\";\n }\n}\n\nexport class SignatureVerificationError extends SignedObjectProfileError {\n constructor(message: string) {\n super(\"signature-invalid\", message);\n this.name = \"SignatureVerificationError\";\n }\n}\n\nexport function toSignedObjectError(error: unknown): SignedObjectProfileError {\n if (error instanceof SignedObjectProfileError) {\n return error;\n }\n return new SignedObjectProfileError(\n \"schema-invalid\",\n error instanceof Error ? error.message : String(error),\n );\n}\n","import { SignedObjectCanonicalizationError } from \"./errors\";\n\nexport type JsonValue =\n | null\n | boolean\n | number\n | string\n | JsonValue[]\n | { readonly [key: string]: JsonValue };\n\ntype MutableJsonObject = { [key: string]: JsonValue };\nconst objectHasOwn: (object: object, propertyKey: PropertyKey) => boolean =\n (Object as ObjectConstructor & {\n hasOwn?: (object: object, propertyKey: PropertyKey) => boolean;\n }).hasOwn ??\n (Object.prototype.hasOwnProperty.call.bind(\n Object.prototype.hasOwnProperty,\n ) as (object: object, propertyKey: PropertyKey) => boolean);\n\n/**\n * RFC 8785 JSON Canonicalization Scheme encoder for the signed-object profile.\n *\n * This local encoder is intentionally stricter than JSON.stringify:\n * it rejects non-plain JSON inputs, sparse arrays, undefined/functions/symbols,\n * non-finite numbers, BigInt, dangerous prototype keys, and lone surrogates.\n * Object keys are sorted lexicographically by their raw UTF-16 code units,\n * as required by RFC 8785 section 3.2.3, before serialization.\n */\nexport function jcsCanonicalize(input: unknown): string {\n return serialize(normalizeJson(input, \"$\"));\n}\n\nexport function normalizeJson(input: unknown, path = \"$\"): JsonValue {\n if (input === null) {\n return null;\n }\n\n switch (typeof input) {\n case \"boolean\":\n return input;\n case \"number\":\n if (!Number.isFinite(input)) {\n throw new SignedObjectCanonicalizationError(\n `${path} must be a finite JSON number`,\n );\n }\n return input;\n case \"string\":\n assertUnicodeScalarString(input, path);\n return input;\n case \"object\":\n return normalizeJsonObjectOrArray(input, path);\n case \"bigint\":\n case \"function\":\n case \"symbol\":\n case \"undefined\":\n default:\n throw new SignedObjectCanonicalizationError(\n `${path} is not a JSON value`,\n );\n }\n}\n\nexport function serialize(value: JsonValue): string {\n if (value === null) {\n return \"null\";\n }\n\n switch (typeof value) {\n case \"boolean\":\n return value ? \"true\" : \"false\";\n case \"number\": {\n const encoded = JSON.stringify(value);\n if (encoded === undefined) {\n throw new SignedObjectCanonicalizationError(\n \"number could not be serialized as JSON\",\n );\n }\n return encoded;\n }\n case \"string\":\n return JSON.stringify(value);\n case \"object\":\n if (Array.isArray(value)) {\n return `[${value.map((item) => serialize(item)).join(\",\")}]`;\n }\n return serializeObject(value);\n default:\n throw new SignedObjectCanonicalizationError(\n `unsupported JSON value type ${typeof value}`,\n );\n }\n}\n\nfunction normalizeJsonObjectOrArray(input: object, path: string): JsonValue {\n assertNoSymbolKeys(input, path);\n\n if (Array.isArray(input)) {\n for (const key of Object.getOwnPropertyNames(input)) {\n if (key === \"length\") {\n continue;\n }\n if (!isArrayIndexKey(key, input.length)) {\n throw new SignedObjectCanonicalizationError(\n `${path}.${key} is not allowed on a JSON array`,\n );\n }\n const descriptor = Object.getOwnPropertyDescriptor(input, key);\n if (descriptor?.enumerable !== true) {\n throw new SignedObjectCanonicalizationError(\n `${path}[${key}] must be an enumerable JSON array item`,\n );\n }\n if (!(\"value\" in descriptor)) {\n throw new SignedObjectCanonicalizationError(\n `${path}[${key}] must be a JSON data property`,\n );\n }\n }\n const output: JsonValue[] = [];\n for (let index = 0; index < input.length; index++) {\n if (!objectHasOwn(input, index)) {\n throw new SignedObjectCanonicalizationError(\n `${path}[${index}] must not be a sparse array hole`,\n );\n }\n output.push(normalizeJson(input[index], `${path}[${index}]`));\n }\n return output;\n }\n\n const proto = Object.getPrototypeOf(input);\n if (proto !== Object.prototype && proto !== null) {\n throw new SignedObjectCanonicalizationError(\n `${path} must be a plain JSON object`,\n );\n }\n\n const output = Object.create(null) as MutableJsonObject;\n for (const key of Object.getOwnPropertyNames(input)) {\n assertUnicodeScalarString(key, `${path} key`);\n if (key === \"__proto__\" || key === \"constructor\") {\n throw new SignedObjectCanonicalizationError(\n `${path}.${key} is not allowed`,\n );\n }\n const descriptor = Object.getOwnPropertyDescriptor(input, key);\n if (descriptor?.enumerable !== true) {\n throw new SignedObjectCanonicalizationError(\n `${path}.${key} must be an enumerable JSON object property`,\n );\n }\n if (!(\"value\" in descriptor)) {\n throw new SignedObjectCanonicalizationError(\n `${path}.${key} must be a JSON data property`,\n );\n }\n const value = descriptor.value;\n output[key] = normalizeJson(value, `${path}.${key}`);\n }\n return output;\n}\n\nfunction assertNoSymbolKeys(input: object, path: string): void {\n if (Object.getOwnPropertySymbols(input).length > 0) {\n throw new SignedObjectCanonicalizationError(\n `${path} must not have symbol properties`,\n );\n }\n}\n\nfunction isArrayIndexKey(key: string, length: number): boolean {\n if (!/^(0|[1-9]\\d*)$/.test(key)) {\n return false;\n }\n const index = Number(key);\n return Number.isSafeInteger(index) && index >= 0 && index < length;\n}\n\nfunction serializeObject(value: { readonly [key: string]: JsonValue }): string {\n const keys = Object.keys(value).sort(compareUtf16CodeUnits);\n const parts = keys.map((key) => `${JSON.stringify(key)}:${serialize(value[key])}`);\n return `{${parts.join(\",\")}}`;\n}\n\nfunction compareUtf16CodeUnits(a: string, b: string): number {\n const max = Math.min(a.length, b.length);\n for (let index = 0; index < max; index++) {\n const leftCodeUnit = a.charCodeAt(index);\n const rightCodeUnit = b.charCodeAt(index);\n if (leftCodeUnit !== rightCodeUnit) {\n return leftCodeUnit - rightCodeUnit;\n }\n }\n return a.length - b.length;\n}\n\nfunction assertUnicodeScalarString(value: string, path: string): void {\n for (let index = 0; index < value.length; index++) {\n const code = value.charCodeAt(index);\n if (code >= 0xd800 && code <= 0xdbff) {\n const next = value.charCodeAt(index + 1);\n if (Number.isNaN(next) || next < 0xdc00 || next > 0xdfff) {\n throw new SignedObjectCanonicalizationError(\n `${path} contains a lone high surrogate`,\n );\n }\n index++;\n continue;\n }\n if (code >= 0xdc00 && code <= 0xdfff) {\n throw new SignedObjectCanonicalizationError(\n `${path} contains a lone low surrogate`,\n );\n }\n }\n}\n","import {\n canonicalizeRecapCaveats,\n isCapabilitySubset,\n type PermissionEntry,\n} from \"@tinycloud/sdk-core\";\n\n/**\n * A capability in the canonical form persisted in a v1 request artifact.\n *\n * The SDK remains the semantic authority for containment. This module only\n * establishes deterministic exact identity for persistence and display.\n */\nexport type CanonicalPermissionEntry = Readonly<PermissionEntry>;\n\nexport interface AuthorityEvaluation {\n readonly satisfied: boolean;\n readonly missing: readonly CanonicalPermissionEntry[];\n}\n\n/**\n * Operation-owned space resolution. The SDK's general capability evaluator\n * intentionally preserves its historical short/full-space compatibility; the\n * operation kernel supplies this narrower resolver when a space identifies a\n * remote owner's resource.\n */\nexport type OperationSpaceResolver = (space: string) => string;\n\nconst EXACT_PERMISSION_KEYS = new Set([\n \"service\",\n \"space\",\n \"path\",\n \"actions\",\n \"skipPrefix\",\n \"expiry\",\n \"description\",\n]);\n\n/**\n * Validate requirements that can be represented without semantic broadening in\n * a v1 artifact. Caveated authority is intentionally rejected: the v1 request\n * schema and grant path cannot preserve those signed branches end-to-end.\n */\nexport function validateExactCapabilities(value: unknown): PermissionEntry[] | undefined {\n if (!Array.isArray(value)) return undefined;\n\n const permissions: PermissionEntry[] = [];\n for (const candidate of value) {\n if (!isExactPermission(candidate)) return undefined;\n permissions.push(candidate);\n }\n return canonicalizeCapabilities(permissions);\n}\n\n/**\n * Evaluates required authority with the SDK's public subset primitive.\n *\n * In particular, this intentionally does not implement matching rules or\n * manufacture wildcards. A wildcard can cover a requirement only when it was\n * already present in the supplied granted authority and the SDK says so.\n */\nexport function evaluateAuthority(\n granted: readonly PermissionEntry[],\n required: readonly PermissionEntry[],\n): AuthorityEvaluation {\n const canonicalGranted = canonicalizeCapabilities(granted);\n const canonicalRequired = canonicalizeCapabilities(required);\n const result = isCapabilitySubset(canonicalRequired, canonicalGranted);\n\n return {\n satisfied: result.subset,\n missing: canonicalizeCapabilities(result.missing),\n };\n}\n\n/**\n * Evaluate operation capabilities without allowing two owners' same-named\n * spaces to compare equal. Path and action containment still come from the\n * SDK primitive; only the operation space identity is made owner-exact.\n *\n * Each requirement is judged against every grant in its owner-exact space\n * together: a verified SIWE ReCap reports one entry per signed action, so\n * `get` and `list` on one path arrive as two grants.\n */\nexport function evaluateOperationAuthority(\n granted: readonly PermissionEntry[],\n required: readonly PermissionEntry[],\n resolveSpace?: OperationSpaceResolver,\n): AuthorityEvaluation {\n const canonicalGranted = canonicalizeOperationCapabilities(granted, resolveSpace);\n const canonicalRequired = canonicalizeOperationCapabilities(required, resolveSpace);\n const missing: CanonicalPermissionEntry[] = [];\n\n for (const requested of canonicalRequired) {\n const sameScope = canonicalGranted.filter((candidate) =>\n candidate.service === requested.service &&\n operationSpacesEqual(candidate.space, requested.space)\n );\n if (!isCapabilitySubset([requested], sameScope).subset) missing.push(requested);\n }\n\n return {\n satisfied: missing.length === 0,\n missing: canonicalizeCapabilities(missing),\n };\n}\n\n/**\n * Whether a delegation's signed capabilities stay inside the stored request\n * it answers. `tinycloud.auth.import` and runtime replay share this check, so\n * a delegation import accepts is never one replay discards.\n */\nexport function delegationWithinRequest(\n requested: readonly PermissionEntry[],\n effective: readonly PermissionEntry[],\n resolveSpace?: OperationSpaceResolver,\n): boolean {\n return requested.length > 0 &&\n evaluateOperationAuthority(requested, canonicalizeCapabilities(effective), resolveSpace).satisfied;\n}\n\n/**\n * Validate a runtime hint as an exact member of the registered plan. Runtime\n * nodes may report the one phase that failed, but they may not introduce a\n * broader, differently-owned, caveated, or otherwise new capability.\n */\nexport function isExactCapabilityMemberSubset(\n hinted: readonly PermissionEntry[],\n planned: readonly PermissionEntry[],\n resolveSpace?: OperationSpaceResolver,\n): boolean {\n const canonicalHinted = canonicalizeOperationCapabilities(hinted, resolveSpace);\n const canonicalPlanned = canonicalizeOperationCapabilities(planned, resolveSpace);\n return canonicalHinted.length > 0 && canonicalHinted.every((hint) => canonicalPlanned.some((plan) =>\n JSON.stringify(hint) === JSON.stringify(plan),\n ));\n}\n\n/** Canonical identity used by operation preflight, hints, and artifacts. */\nexport function canonicalizeOperationCapabilities(\n permissions: readonly PermissionEntry[],\n resolveSpace?: OperationSpaceResolver,\n): CanonicalPermissionEntry[] {\n return canonicalizeCapabilities(permissions.map((permission) => {\n const canonicalPath = canonicalizeOperationPath(permission);\n if (permission.space === undefined || resolveSpace === undefined) {\n return canonicalPath;\n }\n try {\n return { ...canonicalPath, space: resolveSpace(permission.space) };\n } catch {\n // An unresolvable identity remains a non-matching literal. It must never\n // be widened to a wildcard or to another authenticated owner.\n return canonicalPath;\n }\n }));\n}\n\nfunction canonicalizeOperationPath(permission: PermissionEntry): PermissionEntry {\n if (permission.service !== \"tinycloud.encryption\") return permission;\n const match = permission.path.match(\n /^(urn:tinycloud:encryption:did:pkh:eip155:\\d+:)(0x[0-9a-fA-F]{40})(:.*)$/,\n );\n if (!match) return permission;\n return { ...permission, path: `${match[1]}${match[2]!.toLowerCase()}${match[3]}` };\n}\n\nfunction operationSpacesEqual(\n granted: string | undefined,\n requested: string | undefined,\n): boolean {\n return granted === requested;\n}\n\n/** Returns a stable exact identity for one capability without containment. */\nexport function permissionIdentity(permission: PermissionEntry): string {\n return JSON.stringify(canonicalizeCapability(permission));\n}\n\n/**\n * Sorts actions and entries and removes only exactly-identical capabilities.\n * It does not coalesce paths, services, spaces, or actions into a broader\n * capability.\n */\nexport function canonicalizeCapabilities(\n permissions: readonly PermissionEntry[],\n): CanonicalPermissionEntry[] {\n const byIdentity = new Map<string, CanonicalPermissionEntry>();\n for (const permission of permissions) {\n const canonical = canonicalizeCapability(permission);\n byIdentity.set(permissionIdentityFromCanonical(canonical), canonical);\n }\n\n return [...byIdentity.entries()]\n .sort(([left], [right]) => left.localeCompare(right))\n .map(([, permission]) => permission);\n}\n\nfunction canonicalizeCapability(permission: PermissionEntry): CanonicalPermissionEntry {\n const actions = [...new Set(permission.actions)].sort((left, right) => left.localeCompare(right));\n return {\n service: permission.service,\n ...(permission.space !== undefined ? { space: permission.space } : {}),\n path: permission.path,\n actions,\n ...(permission.skipPrefix !== undefined ? { skipPrefix: permission.skipPrefix } : {}),\n ...(permission.expiry !== undefined ? { expiry: permission.expiry } : {}),\n ...(permission.description !== undefined ? { description: permission.description } : {}),\n ...(permission.caveats === undefined\n ? {}\n : { caveats: canonicalizeCaveats(permission.caveats) }),\n };\n}\n\nfunction permissionIdentityFromCanonical(permission: CanonicalPermissionEntry): string {\n return JSON.stringify({\n service: permission.service,\n ...(permission.space !== undefined ? { space: permission.space } : {}),\n path: permission.path,\n actions: permission.actions,\n ...(permission.skipPrefix !== undefined ? { skipPrefix: permission.skipPrefix } : {}),\n ...(permission.expiry !== undefined ? { expiry: permission.expiry } : {}),\n ...(permission.description !== undefined ? { description: permission.description } : {}),\n ...(permission.caveats === undefined ? {} : { caveats: permission.caveats }),\n });\n}\n\nfunction isExactPermission(value: unknown): value is PermissionEntry {\n if (\n !isExactRecord(value) ||\n Object.getOwnPropertyNames(value).some((key) => !EXACT_PERMISSION_KEYS.has(key)) ||\n Object.getOwnPropertySymbols(value).length > 0\n ) {\n return false;\n }\n if (\n !isExactText(value.service) ||\n !isExactPath(value.path, value.actions) ||\n (value.space !== undefined && !isExactText(value.space)) ||\n !Array.isArray(value.actions) ||\n value.actions.length === 0 ||\n !value.actions.every(isExactText) ||\n new Set(value.actions).size !== value.actions.length ||\n value.skipPrefix === true ||\n (value.skipPrefix !== undefined && value.skipPrefix !== false) ||\n (value.expiry !== undefined && !isExactText(value.expiry)) ||\n (value.description !== undefined && !isExactText(value.description))\n ) {\n return false;\n }\n return !(\"caveats\" in value);\n}\n\nfunction isExactRecord(value: unknown): value is Record<string, unknown> {\n if (typeof value !== \"object\" || value === null || Array.isArray(value)) return false;\n const prototype = Object.getPrototypeOf(value);\n return prototype === Object.prototype || prototype === null;\n}\n\nfunction isExactText(value: unknown): value is string {\n return typeof value === \"string\" && value.length > 0 && !value.includes(\"*\");\n}\n\nfunction isExactPath(value: unknown, actions: unknown): value is string {\n if (typeof value !== \"string\" || value === \"/\" || value.includes(\"*\")) return false;\n if (value === \"\" || value.endsWith(\"/\")) {\n return Array.isArray(actions) && actions.includes(\"tinycloud.kv/list\");\n }\n return true;\n}\n\nfunction canonicalizeCaveats(\n caveats: readonly Record<string, unknown>[],\n): Record<string, unknown>[] {\n return [...caveats]\n .map((caveat) => ({\n identity: canonicalizeRecapCaveats([caveat]),\n value: JSON.parse(JSON.stringify(caveat)) as Record<string, unknown>,\n }))\n .sort((left, right) => left.identity.localeCompare(right.identity))\n .map(({ value }) => value);\n}\n","import { randomUUID } from \"node:crypto\";\nimport { AsyncLocalStorage } from \"node:async_hooks\";\nimport { constants as fsConstants, fstatSync, readFileSync, readlinkSync } from \"node:fs\";\nimport {\n chmod,\n link,\n mkdir,\n lstat,\n open,\n readFile,\n readdir,\n realpath,\n rename,\n rmdir,\n rm,\n stat,\n unlink,\n writeFile,\n} from \"node:fs/promises\";\nimport type { FileHandle } from \"node:fs/promises\";\nimport { connect, createServer, type Server } from \"node:net\";\nimport { homedir } from \"node:os\";\nimport { basename, dirname, isAbsolute, join, relative, resolve } from \"node:path\";\n\nconst DEFAULT_PROFILE = \"default\";\nconst DEFAULT_LOCK_TIMEOUT_MS = 2_000;\nconst DEFAULT_LOCK_RETRY_MS = 25;\nconst DEFAULT_STALE_LOCK_MS = 30_000;\nconst TEST_LOCK_CONTENTION_SIGNAL_PATH = \"TC_TEST_PROFILE_LOCK_CONTENTION_SIGNAL_PATH\";\n// Profile state holds keys, sessions and delegations: owner-only access.\nconst PRIVATE_FILE_MODE = 0o600;\nconst PRIVATE_DIR_MODE = 0o700;\nconst TEST_LOCK_RECOVERY_BARRIER_DIR = \"TC_TEST_PROFILE_LOCK_RECOVERY_BARRIER_DIR\";\nconst TEST_LOCK_OWNERLESS_BARRIER_DIR = \"TC_TEST_PROFILE_LOCK_OWNERLESS_BARRIER_DIR\";\nconst TEST_LOCK_PUBLISH_BARRIER_DIR = \"TC_TEST_PROFILE_LOCK_PUBLISH_BARRIER_DIR\";\nconst TEST_LOCK_RELEASE_BARRIER_DIR = \"TC_TEST_PROFILE_LOCK_RELEASE_BARRIER_DIR\";\nconst TEST_LOCK_CLAIM_BARRIER_DIR = \"TC_TEST_PROFILE_LOCK_CLAIM_BARRIER_DIR\";\nconst TEST_LOCK_CLAIMED_BARRIER_DIR = \"TC_TEST_PROFILE_LOCK_CLAIMED_BARRIER_DIR\";\nconst TEST_LOCK_VERIFIED_BARRIER_DIR = \"TC_TEST_PROFILE_LOCK_VERIFIED_BARRIER_DIR\";\nconst TEST_LOCK_MOVED_BARRIER_DIR = \"TC_TEST_PROFILE_LOCK_MOVED_BARRIER_DIR\";\nconst TEST_LOCK_TURN_PUBLISH_BARRIER_DIR = \"TC_TEST_PROFILE_LOCK_TURN_PUBLISH_BARRIER_DIR\";\nconst TEST_LOCK_TURN_SETTLE_BARRIER_DIR = \"TC_TEST_PROFILE_LOCK_TURN_SETTLE_BARRIER_DIR\";\nconst TEST_LOCK_TURN_COLLECT_BARRIER_DIR = \"TC_TEST_PROFILE_LOCK_TURN_COLLECT_BARRIER_DIR\";\nconst TEST_LOCK_LIVENESS_BARRIER_DIR = \"TC_TEST_PROFILE_LOCK_LIVENESS_BARRIER_DIR\";\n/** One acquisition of a profile lock; `active` is cleared before release. */\ninterface HeldProfileLock {\n readonly lockPath: string;\n /** The number of the turn this acquisition holds (see acquireTurn). */\n readonly turnSlot: number;\n /** A deletion of the profile ran while this acquisition waited for the lock. */\n readonly deletedWhileWaiting: boolean;\n active: boolean;\n deletedProfile?: boolean;\n}\n/**\n * Async context shared by every copy of this module in the process. Each\n * bundled operations entry point (`state`, `delegation-binding`, the root)\n * carries its own copy, so module-level context would make lock reentrancy and\n * the invocation state root stop at an entry-point boundary.\n * Each key is independently versioned for the shape and meaning of the\n * value it stores. Held-lock entries gained `turnSlot` and\n * `deletedWhileWaiting` in TC-633, so `.v2` must not read TC-602's `.v1`\n * entries. The invocation root remains a string path with unchanged\n * semantics and stays `.v1`. Relinquished turn tokens and the process\n * instance ID use separate versioned process-wide keys: only an explicitly\n * given-up turn of this process may be settled across bundled entry points.\n */\nfunction processWideContext<T>(key: string): AsyncLocalStorage<T> {\n const registry = globalThis as unknown as Record<symbol, AsyncLocalStorage<T> | undefined>;\n return registry[Symbol.for(key)] ??= new AsyncLocalStorage<T>();\n}\nconst invocationStateRoot = processWideContext<string>(\"tinycloud.operations.invocationStateRoot.v1\");\n/** Profile lock acquisitions held by the current async call chain (see withProfileLock). */\nconst heldProfileLocks = processWideContext<readonly HeldProfileLock[]>(\"tinycloud.operations.heldProfileLocks.v2\");\n/** Descriptors inherited by deferred async work are unusable after their owner's action returns. */\ninterface PinnedProfileDirectory {\n readonly path: string;\n readonly fd: number;\n readonly dev: bigint;\n readonly ino: bigint;\n active: boolean;\n}\nconst pinnedProfiles = processWideContext<ReadonlyMap<string, PinnedProfileDirectory>>(\"tinycloud.operations.pinnedProfiles.v2\");\nconst pinnedHomes = processWideContext<ReadonlyMap<string, PinnedProfileDirectory>>(\"tinycloud.operations.pinnedHomes.v2\");\n/** One canonical target per configured home across ESM/CJS copies in this process. */\nconst canonicalHomes: Map<string, string> =\n (globalThis as unknown as Record<symbol, Map<string, string> | undefined>)[Symbol.for(\"tinycloud.operations.canonicalHomes.v1\")] ??= new Map();\n/** One ID across separately bundled ESM, CJS, CLI and MCP copies in this process. */\nconst processInstanceId: string =\n (globalThis as unknown as Record<symbol, string | undefined>)[Symbol.for(\"tinycloud.operations.profileLockInstance.v1\")] ??=\n randomUUID();\n\ninterface ProcessOrigin {\n readonly bootId: string;\n readonly pidNamespace: string;\n /** /proc/self/stat field 22, in kernel clock ticks since boot. */\n readonly processStartTime: string;\n}\n\n/** A listening UNIX socket in the turn directory, held for one acquisition. */\ninterface Liveness {\n /**\n * Name of the socket in the turn directory, `.live-<dev>-<ino>-<uuid>`:\n * dev and ino (hex) identify the turn directory its holder bound it in.\n */\n readonly socket: string;\n /** lstat dev and ino of the socket as its holder bound it, decimal. */\n readonly dev: string;\n readonly ino: string;\n}\n\ninterface RecordedProcess {\n readonly pid?: unknown;\n readonly instanceId?: unknown;\n readonly bootId?: unknown;\n readonly pidNamespace?: unknown;\n readonly processStartTime?: unknown;\n readonly liveness?: unknown;\n}\n\nlet cachedProcessOrigin: ProcessOrigin | null | undefined;\n\n/** Missing/inaccessible Linux provenance is not evidence that an owner died. */\nfunction processOrigin(): ProcessOrigin | null {\n if (cachedProcessOrigin !== undefined) return cachedProcessOrigin;\n if (process.platform !== \"linux\") return cachedProcessOrigin = null;\n try {\n const bootId = readFileSync(\"/proc/sys/kernel/random/boot_id\", \"utf8\").trim();\n const pidNamespace = readlinkSync(\"/proc/self/ns/pid\");\n const processStat = readFileSync(\"/proc/self/stat\", \"utf8\");\n const closingParen = processStat.lastIndexOf(\") \");\n const processStartTime = closingParen < 0 ? undefined : processStat.slice(closingParen + 2).trim().split(/\\s+/)[19];\n if (!/^[0-9a-f-]{36}$/.test(bootId) || !/^pid:\\[[0-9]+\\]$/.test(pidNamespace) ||\n !processStartTime || !/^[0-9]+$/.test(processStartTime)) {\n return cachedProcessOrigin = null;\n }\n return cachedProcessOrigin = { bootId, pidNamespace, processStartTime };\n } catch {\n return cachedProcessOrigin = null;\n }\n}\n\ninterface HeldLiveness {\n readonly record: Liveness;\n readonly path: string;\n readonly server: Server;\n}\n\n/** Extra JSON properties only: older releases still read the original owner fields. */\nfunction processOwner(liveness: HeldLiveness | null): { instanceId: string; liveness?: Liveness } & Partial<ProcessOrigin> {\n return { instanceId: processInstanceId, ...processOrigin(), ...(liveness ? { liveness: liveness.record } : {}) };\n}\n\n/**\n * Liveness socket names (see Liveness), and the same name with `.new` while\n * it is bound but not yet listening. A socket appears under its final name\n * only once it accepts connections, so a refused connect to a final name\n * means its listener is closed for good.\n */\nconst LIVENESS_SOCKET = /^\\.live-([0-9a-f]+)-([0-9a-f]+)-[0-9a-f]{8}-(?:[0-9a-f]{4}-){3}[0-9a-f]{12}(\\.new)?$/;\nconst LIVENESS_PROBE_TIMEOUT_MS = 1_000;\n\n/** The `dev-ino` (hex) a liveness socket name gives for its turn directory; null for other names. */\nfunction socketDirectory(name: string): string | null {\n const match = LIVENESS_SOCKET.exec(name);\n return match ? `${match[1]}-${match[2]}` : null;\n}\n\n/** A turn directory held open, so every lookup in it names the same directory. */\ninterface PinnedDirectory {\n /** `dev-ino` of the directory, hex, as in liveness socket names. */\n readonly identity: string;\n /** `name` in this directory, through /proc/self/fd: short enough for sun_path. */\n path(name: string): string;\n close(): Promise<void>;\n}\n\nasync function pinDirectory(directory: string): Promise<PinnedDirectory> {\n const handle = await open(directory, \"r\");\n try {\n const { dev, ino } = await handle.stat({ bigint: true });\n return {\n identity: `${dev.toString(16)}-${ino.toString(16)}`,\n path: (name) => `/proc/self/fd/${handle.fd}/${name}`,\n close: () => handle.close(),\n };\n } catch (error) {\n await handle.close();\n throw error;\n }\n}\n\n/**\n * Binds this acquisition's liveness socket in the turn directory: Linux with\n * provenance only. Null if it cannot be bound (no /proc, a filesystem\n * without socket files, a sandbox without networking): records then carry\n * no liveness, and a same-PID process in another PID namespace waits. The\n * socket is bound as `<name>.new` and renamed once listening; a sweep that\n * removes the `.new` name in between (it refused) makes the rename, and so\n * the bind, fail.\n */\nasync function bindLiveness(directory: string): Promise<HeldLiveness | null> {\n if (!processOrigin()) return null;\n let pinned: PinnedDirectory;\n try {\n pinned = await pinDirectory(directory);\n } catch {\n return null;\n }\n const socket = `.live-${pinned.identity}-${randomUUID()}`;\n const server = createServer((connection) => connection.destroy());\n let bound = false;\n try {\n // Node 20 has no Promise.withResolvers.\n await new Promise<void>((resolve, reject) => {\n server.once(\"error\", reject);\n // exclusive: a cluster worker binds its own socket, not the primary's.\n server.listen({ path: pinned.path(`${socket}.new`), exclusive: true }, () => {\n server.off(\"error\", reject);\n resolve();\n });\n });\n bound = true;\n server.on(\"error\", () => undefined);\n server.unref();\n await rename(pinned.path(`${socket}.new`), pinned.path(socket));\n const stats = await lstat(pinned.path(socket), { bigint: true });\n if (!stats.isSocket()) throw new Error(`${socket} is not a socket`);\n await waitForTestBarrier(TEST_LOCK_LIVENESS_BARRIER_DIR, basename(directory));\n return { record: { socket, dev: String(stats.dev), ino: String(stats.ino) }, path: join(directory, socket), server };\n } catch {\n if (bound) {\n server.close();\n for (const name of [`${socket}.new`, socket]) await unlink(pinned.path(name)).catch(() => undefined);\n }\n return null;\n } finally {\n await pinned.close();\n }\n}\n\n/** This acquisition's liveness socket, bound in its turn directory on first need. */\nasync function ensureLiveness(acquisition: AcquisitionLiveness): Promise<HeldLiveness | null> {\n if (acquisition.held === undefined) acquisition.held = await bindLiveness(acquisition.directory);\n return acquisition.held;\n}\n\n/**\n * Removes the socket and stops listening: its holder is past its critical\n * section, so a contender may see it gone or refused from here on. The close\n * is not awaited (Bun calls back a millisecond later).\n */\nasync function closeLiveness(held: HeldLiveness): Promise<void> {\n held.server.close();\n await unlink(held.path).catch(() => undefined);\n}\n\nfunction parseLiveness(value: unknown): Liveness | null {\n if (typeof value !== \"object\" || value === null) return null;\n const { socket, dev, ino } = value as Record<string, unknown>;\n // Records name a final socket name, never a `.new` one.\n const name = typeof socket === \"string\" ? LIVENESS_SOCKET.exec(socket) : null;\n return name !== null && name[3] === undefined &&\n typeof dev === \"string\" && /^[0-9]+$/.test(dev) && typeof ino === \"string\" && /^[0-9]+$/.test(ino)\n ? { socket: name[0], dev, ino }\n : null;\n}\n\n/**\n * This acquisition's own socket, as the control for judging others in\n * `pinned`: bound (on first need) in that same directory, and found there\n * as bound. Null if there is none.\n */\nasync function controlIn(pinned: PinnedDirectory, acquisition: AcquisitionLiveness): Promise<HeldLiveness | null> {\n const own = await ensureLiveness(acquisition);\n if (!own || socketDirectory(own.record.socket) !== pinned.identity) return null;\n const stats = await lstat(pinned.path(own.record.socket), { bigint: true }).catch(() => null);\n return stats?.isSocket() && String(stats.dev) === own.record.dev && String(stats.ino) === own.record.ino ? own : null;\n}\n\n/** Connects to a UNIX socket and disconnects: \"connected\", \"refused\" (ECONNREFUSED) or \"other\". */\nasync function tryConnect(path: string): Promise<\"connected\" | \"refused\" | \"other\"> {\n // Node 20 has no Promise.withResolvers.\n return new Promise((resolve) => {\n const probe = connect({ path });\n const settle = (outcome: \"connected\" | \"refused\" | \"other\") => {\n clearTimeout(timer);\n probe.destroy();\n resolve(outcome);\n };\n const timer = setTimeout(() => settle(\"other\"), LIVENESS_PROBE_TIMEOUT_MS);\n probe.once(\"connect\", () => settle(\"connected\"));\n probe.once(\"error\", (error) => settle(isErrno(error, \"ECONNREFUSED\") ? \"refused\" : \"other\"));\n });\n}\n\n/**\n * Whether the kernel refuses connections to `name` in `pinned`. A refusal\n * counts only if this process then connects to its own socket (`control`)\n * in the same directory through the same kind of address. This catches a\n * sandbox that refuses every connect, but not one that fabricates refusals\n * selectively (a seccomp filter keyed on call arguments, a seccomp\n * user-notification supervisor, or a BPF LSM program). Like the PID check,\n * which trusts kill(2), this decision trusts the kernel's answers. Never\n * under Bun, which reports a refused UNIX connect as ENOENT.\n */\nasync function kernelRefuses(pinned: PinnedDirectory, name: string, control: HeldLiveness): Promise<boolean> {\n if (process.versions.bun !== undefined) return false;\n return await tryConnect(pinned.path(name)) === \"refused\" &&\n await tryConnect(pinned.path(control.record.socket)) === \"connected\";\n}\n\n/**\n * Whether a liveness socket's holder is gone, judged in the turn directory\n * of `acquisition` and only if the socket's name says it was bound in that\n * very directory (not another one under that path: a separate turn lock, or\n * one removed and recreated). \"gone\" if the socket file was removed (only\n * its holder does that, after its critical section, or a process that found\n * it refused), \"refused\" if the kernel refuses connections to that very\n * inode (kernelRefuses). Both need this acquisition's own socket in that\n * directory as a control. Anything else, including a paused holder's full\n * backlog, is \"unknown\".\n */\nasync function probeLiveness(liveness: Liveness, acquisition: AcquisitionLiveness): Promise<\"gone\" | \"refused\" | \"unknown\"> {\n let pinned: PinnedDirectory;\n try {\n pinned = await pinDirectory(acquisition.directory);\n } catch {\n return \"unknown\";\n }\n try {\n if (socketDirectory(liveness.socket) !== pinned.identity) return \"unknown\";\n const control = await controlIn(pinned, acquisition);\n if (!control) return \"unknown\";\n let stats;\n try {\n stats = await lstat(pinned.path(liveness.socket), { bigint: true });\n } catch (error) {\n return isErrno(error, \"ENOENT\") ? \"gone\" : \"unknown\";\n }\n if (!stats.isSocket() || String(stats.dev) !== liveness.dev || String(stats.ino) !== liveness.ino) return \"unknown\";\n return await kernelRefuses(pinned, liveness.socket, control) ? \"refused\" : \"unknown\";\n } finally {\n await pinned.close();\n }\n}\n\n/**\n * Removes the liveness sockets in this acquisition's turn directory that the\n * kernel refuses (kernelRefuses): those of acquisitions that are over,\n * whether or not a record still names them (a crash before the first\n * record, a turn removed by a Bun process, which cannot judge refusal). A\n * socket that accepts or cannot be judged stays; nothing is removed by age.\n * Not under Bun.\n */\nasync function sweepLiveness(acquisition: AcquisitionLiveness): Promise<void> {\n if (process.versions.bun !== undefined || !acquisition.held) return;\n const pinned = await pinDirectory(acquisition.directory);\n try {\n const control = await controlIn(pinned, acquisition);\n if (!control) return;\n for (const name of await readdir(pinned.path(\".\"))) {\n if (name === control.record.socket || socketDirectory(name) !== pinned.identity) continue;\n const stats = await lstat(pinned.path(name)).catch(() => null);\n if (stats?.isSocket() && await kernelRefuses(pinned, name, control)) {\n await unlink(pinned.path(name)).catch(() => undefined);\n }\n }\n } finally {\n await pinned.close();\n }\n}\n\nexport type ProfileStoreName =\n | \"session\"\n | \"additional-delegations\"\n | \"auth-requests\";\n\nexport interface StoreMetadata {\n formatVersion: number;\n}\n\nexport interface ProfileStoreContents<T> {\n formatVersion: number;\n records: T[];\n}\n\nexport interface ProfileLockOptions {\n timeoutMs?: number;\n retryMs?: number;\n staleAfterMs?: number;\n /**\n * Lock only a profile that exists (its `profile.json` is present), and\n * never create its directory: a missing or deleted profile is refused with\n * ProfileDeletedError, before and after waiting for the lock. For writers\n * of state that lives inside an existing profile (replicas).\n */\n requireProfile?: boolean;\n}\n\nexport class ProfileLockTimeoutError extends Error {\n readonly code = \"PROFILE_LOCK_TIMEOUT\";\n\n /** `detail` names the cause when it is not simply another process holding the lock. */\n constructor(profile: string, timeoutMs: number, detail?: string) {\n super(`Timed out waiting for the profile lock for \"${profile}\" after ${timeoutMs}ms.${detail ? ` ${detail}` : \"\"}`);\n this.name = \"ProfileLockTimeoutError\";\n }\n}\n\n/**\n * A store write that waited for the profile lock while a deletion of that\n * profile held it, and found no profile settings once it got the lock. The\n * write is refused rather than recreating a profile with only a session or\n * store in it.\n */\nexport class ProfileDeletedError extends Error {\n readonly code = \"PROFILE_NOT_FOUND\";\n\n constructor(profile: string) {\n super(`Profile \"${profile}\" was deleted while this change waited for its lock, so the change was not written.`);\n this.name = \"ProfileDeletedError\";\n }\n}\n\n/**\n * The CLI's delegated-secret path treats TC_HOME as a home directory, rather\n * than a direct TinyCloud directory. Keep the shared store on that convention.\n */\nexport function tinycloudHomePath(): string {\n const home = invocationStateRoot.getStore() ??\n process.env.TC_HOME ??\n process.env.HOME ??\n process.env.USERPROFILE ??\n homedir();\n return join(home, \".tinycloud\");\n}\n\n/** Run one operation against an isolated TinyCloud home directory. */\nexport function withTinyCloudStateRoot<T>(\n stateRoot: string | undefined,\n action: () => Promise<T>,\n): Promise<T> {\n if (stateRoot === undefined) return action();\n if (!isAbsolute(stateRoot) || stateRoot.includes(\"\\0\")) {\n throw new TypeError(\"The TinyCloud state root must be an absolute path.\");\n }\n return invocationStateRoot.run(resolve(stateRoot), action);\n}\n\nexport function tinycloudConfigPath(): string {\n return join(tinycloudHomePath(), \"config.json\");\n}\n\nexport function profilesPath(): string {\n return join(tinycloudHomePath(), \"profiles\");\n}\n\nexport function namedProfilePath(profile: string): string {\n return join(profilesPath(), validateProfileName(profile));\n}\n\nexport function profilePath(profile: string): string {\n const named = namedProfilePath(profile);\n const pinned = pinnedProfiles.getStore()?.get(named);\n return pinned ? livePinnedPath(profile, pinned) : named;\n}\n\nexport function profileConfigPath(profile: string): string {\n return join(profilePath(profile), \"profile.json\");\n}\n\nexport function sessionPath(profile: string): string {\n return profileStorePath(profile, \"session\");\n}\n\nexport function additionalDelegationsPath(profile: string): string {\n return profileStorePath(profile, \"additional-delegations\");\n}\n\nexport function authRequestsPath(profile: string): string {\n return profileStorePath(profile, \"auth-requests\");\n}\n\nexport function profileStorePath(profile: string, store: ProfileStoreName): string {\n return join(profilePath(profile), `${store}.json`);\n}\n\n/** The format record is deliberately separate from each legacy JSON payload. */\nexport function profileStoreMetadataPath(profile: string, store: ProfileStoreName): string {\n return `${profileStorePath(profile, store)}.metadata.json`;\n}\n\nexport function profileLockPath(profile: string): string {\n return join(namedProfilePath(profile), \".lock\");\n}\n\nexport function profileLockMetadataPath(profile: string): string {\n return join(profileLockPath(profile), \"owner.json\");\n}\n\n/**\n * The turn lock of a profile (see acquireTurn). It lives outside the profile\n * directory, so neither a profile deletion nor an older release's cleanup\n * removes it.\n */\nexport function profileTurnLockPath(profile: string): string {\n const home = tinycloudHomePath();\n const pinned = pinnedHomes.getStore()?.get(home);\n return join(pinned ? livePinnedPath(profile, pinned) : home, \"profile-locks\", validateProfileName(profile));\n}\n\n/**\n * Reads JSON without hiding malformed or inaccessible files. A missing file is\n * the only condition represented as null.\n */\nexport async function readJson<T>(filePath: string): Promise<T | null> {\n try {\n return JSON.parse(await readFile(filePath, \"utf8\")) as T;\n } catch (error) {\n if (isErrno(error, \"ENOENT\")) return null;\n throw error;\n }\n}\n\n/** A profile's direct JSON files must not resolve through a child alias. */\nasync function readProfileJson<T>(profile: string, filePath: string): Promise<T | null> {\n // On platforms without descriptor-relative profile paths, check the named file on\n // both sides of the read so a replacement cannot silently redirect it.\n const before = process.platform !== \"linux\"\n ? await lstat(filePath, { bigint: true }).catch((error: NodeJS.ErrnoException) => {\n if (error.code === \"ENOENT\") return null;\n throw error;\n })\n : null;\n if (process.platform !== \"linux\" && !before) return null;\n if (before && !before.isFile()) throw new ProfileSymlinkError(profile);\n const dev = before && exactFileId(before.dev);\n const ino = before && exactFileId(before.ino);\n try {\n const handle = await open(filePath, fsConstants.O_RDONLY | (fsConstants.O_NOFOLLOW ?? 0));\n try {\n if (before) {\n const opened = await handle.stat({ bigint: true });\n if (!opened.isFile() || dev === null || ino === null ||\n dev !== exactFileId(opened.dev) || ino !== exactFileId(opened.ino)) {\n throw new ProfileSymlinkError(profile, \"changed while opening its file\");\n }\n }\n const contents = await handle.readFile(\"utf8\");\n if (before) {\n const named = await lstat(filePath, { bigint: true }).catch((error: NodeJS.ErrnoException) => {\n if (error.code === \"ENOENT\") return null;\n throw error;\n });\n if (!named?.isFile() || dev !== exactFileId(named.dev) || ino !== exactFileId(named.ino)) {\n throw new ProfileSymlinkError(profile, \"changed while reading its file\");\n }\n }\n return JSON.parse(contents) as T;\n } finally {\n await handle.close();\n }\n } catch (error) {\n if (isErrno(error, \"ENOENT\")) return null;\n if (isErrno(error, \"ELOOP\") || isErrno(error, \"ENOTDIR\")) throw new ProfileSymlinkError(profile);\n throw error;\n }\n}\n\n/**\n * Writes the same pretty-printed, trailing-newline JSON shape used by the CLI,\n * but publishes it with an atomic rename from the same directory.\n */\nexport async function writeJsonAtomic(filePath: string, value: unknown): Promise<void> {\n await replaceJson(filePath, value, false);\n}\n\n/**\n * writeJsonAtomic; `durable` also flushes the data to disk before the rename,\n * so a crash or power loss leaves the old record or the new one, never a\n * truncated one (for lock records, whose damage would stop every writer).\n */\nasync function replaceJson(filePath: string, value: unknown, durable: boolean): Promise<void> {\n const directory = dirname(filePath);\n const temporaryPath = join(\n directory,\n `.${basename(filePath)}.${process.pid}.${randomUUID()}.tmp`,\n );\n const contents = `${JSON.stringify(value, null, 2)}\\n`;\n\n await mkdir(directory, { recursive: true, mode: PRIVATE_DIR_MODE });\n try {\n if (durable) await writeFileDurably(temporaryPath, contents);\n else await writeFile(temporaryPath, contents, { encoding: \"utf8\", mode: PRIVATE_FILE_MODE });\n await rename(temporaryPath, filePath);\n } catch (error) {\n await rm(temporaryPath, { force: true }).catch(() => undefined);\n throw error;\n }\n}\n\n/** Creates a file (exclusively) and, when it has contents, flushes them to disk. */\nasync function writeFileDurably(path: string, contents: string): Promise<void> {\n const handle = await open(path, \"wx\", PRIVATE_FILE_MODE);\n try {\n if (contents) {\n await handle.writeFile(contents, \"utf8\");\n await handle.datasync();\n }\n } finally {\n await handle.close();\n }\n}\n\nexport async function readProfile<T extends object = Record<string, unknown>>(\n profile: string,\n): Promise<T | null> {\n return withPinnedProfileDirectory(profile, (directory) =>\n directory ? readProfileJson<T>(profile, join(directory, \"profile.json\")) : Promise.resolve(null));\n}\n\nexport async function readSession<T extends object = Record<string, unknown>>(\n profile: string,\n): Promise<T | null> {\n return withPinnedProfileDirectory(profile, async (directory) => {\n if (!directory) return null;\n await readStoreMetadata(profile, \"session\");\n return readProfileJson<T>(profile, sessionPath(profile));\n });\n}\n\nexport async function readAdditionalDelegations<T = Record<string, unknown>>(\n profile: string,\n): Promise<T[]> {\n return (await readProfileStore<T>(profile, \"additional-delegations\")).records;\n}\n\nexport async function readAuthRequests<T = Record<string, unknown>>(\n profile: string,\n): Promise<T[]> {\n return (await readProfileStore<T>(profile, \"auth-requests\")).records;\n}\n\nexport async function readStoreMetadata(\n profile: string,\n store: ProfileStoreName,\n): Promise<StoreMetadata> {\n return withPinnedProfileDirectory(profile, async (directory) => {\n const metadata = directory ? await readProfileJson<StoreMetadata>(profile, profileStoreMetadataPath(profile, store)) : null;\n if (metadata === null) return { formatVersion: 1 };\n if (\n typeof metadata !== \"object\" ||\n metadata.formatVersion !== 1\n ) {\n throw new TypeError(`Unsupported store format for \"${store}\".`);\n }\n return metadata;\n });\n}\n\nexport async function readProfileStore<T>(\n profile: string,\n store: Exclude<ProfileStoreName, \"session\">,\n): Promise<ProfileStoreContents<T>> {\n return withPinnedProfileDirectory(profile, async (directory) => {\n if (!directory) return { formatVersion: 1, records: [] };\n const [metadata, raw] = await Promise.all([\n readStoreMetadata(profile, store),\n readProfileJson<unknown>(profile, profileStorePath(profile, store)),\n ]);\n return {\n formatVersion: metadata.formatVersion,\n records: Array.isArray(raw) ? raw as T[] : [],\n };\n });\n}\n\nexport class ProfileSymlinkError extends Error {\n readonly code = \"PROFILE_SYMLINK\";\n\n constructor(profile: string, detail = \"must be a real directory, not a symlink or another file type\") {\n super(`Profile \"${profile}\" ${detail}.`);\n this.name = \"ProfileSymlinkError\";\n }\n}\n\n/** Bun versions may return numeric FileHandle.stat IDs despite bigint: true. */\nfunction exactFileId(value: number | bigint): bigint | null {\n if (typeof value === \"bigint\") return value >= 0n ? value : null;\n return Number.isSafeInteger(value) && value >= 0 ? BigInt(value) : null;\n}\n\nfunction isPinnedLive(pinned: PinnedProfileDirectory): boolean {\n if (!pinned.active) return false;\n try {\n const stats = fstatSync(pinned.fd, { bigint: true });\n return exactFileId(stats.dev) === pinned.dev && exactFileId(stats.ino) === pinned.ino;\n } catch {\n return false;\n }\n}\n\nfunction livePinnedPath(profile: string, pinned: PinnedProfileDirectory): string {\n if (isPinnedLive(pinned)) return pinned.path;\n throw new ProfileSymlinkError(profile, \"its pinned directory is no longer active or changed during use\");\n}\n\n/** Missing profiles remain missing; existing profiles are always opened without following aliases. */\nexport async function withPinnedProfileDirectory<T>(\n profile: string,\n action: (directory: string | null) => Promise<T>,\n options: { create?: boolean } = {},\n): Promise<T> {\n const named = namedProfilePath(profile);\n const existing = pinnedProfiles.getStore()?.get(named);\n if (existing) return action(livePinnedPath(profile, existing));\n if (process.platform !== \"linux\") {\n // Only destructive replica purge needs a descriptor-relative child path.\n // Preserve ordinary profile state access on platforms without /proc/self/fd.\n if (options.create) await mkdir(named, { recursive: true, mode: PRIVATE_DIR_MODE });\n const stats = await lstat(named).catch((error: NodeJS.ErrnoException) => {\n if (error.code === \"ENOENT\") return null;\n throw error;\n });\n if (!stats) return action(null);\n if (!stats.isDirectory() || stats.isSymbolicLink()) throw new ProfileSymlinkError(profile);\n return action(named);\n }\n const home = tinycloudHomePath();\n const handles: FileHandle[] = [];\n const fdPath = (fd: number) => `/proc/self/fd/${fd}`;\n const flags = fsConstants.O_RDONLY | fsConstants.O_DIRECTORY | fsConstants.O_NOFOLLOW;\n try {\n if (options.create) await mkdir(home, { recursive: true, mode: PRIVATE_DIR_MODE });\n // User-owned home aliases are allowed, but each configured home resolves\n // only once in this process. A later alias retarget cannot select another profile.\n const resolved = canonicalHomes.get(home) ?? await realpath(home).catch((error: NodeJS.ErrnoException) => {\n if (error.code === \"ENOENT\" && !options.create) return null;\n if (error.code === \"ELOOP\" || error.code === \"ENOTDIR\") throw new ProfileSymlinkError(profile);\n throw error;\n });\n if (!resolved) return await action(null);\n const root = canonicalHomes.get(home) ?? resolved;\n canonicalHomes.set(home, root);\n const segments = [root, \"profiles\", validateProfileName(profile)];\n for (let index = 0; index < segments.length; index++) {\n const path = handles.length ? join(fdPath(handles[handles.length - 1]!.fd), segments[index]!) : segments[index]!;\n if (options.create && handles.length) {\n await mkdir(path, { mode: PRIVATE_DIR_MODE }).catch((error: NodeJS.ErrnoException) => {\n if (error.code !== \"EEXIST\") throw error;\n });\n }\n // A profiles/ container may be shared by two homes; pin its target, but never follow an alias for the selected profile.\n const handle = await open(path, index === 1 ? flags & ~fsConstants.O_NOFOLLOW : flags).catch((error: NodeJS.ErrnoException) => {\n if (error.code === \"ENOENT\" && !options.create) return null;\n if (error.code === \"ELOOP\" || error.code === \"ENOTDIR\") throw new ProfileSymlinkError(profile);\n throw error;\n });\n if (!handle) return await action(null);\n handles.push(handle);\n if (options.create) await handle.chmod(PRIVATE_DIR_MODE);\n }\n const opened = handles[2]!;\n const [actual, namedStats] = await Promise.all([opened.stat({ bigint: true }), lstat(named, { bigint: true })]);\n const dev = exactFileId(actual.dev);\n const ino = exactFileId(actual.ino);\n if (!namedStats.isDirectory() || dev === null || ino === null ||\n dev !== exactFileId(namedStats.dev) || ino !== exactFileId(namedStats.ino)) {\n throw new ProfileSymlinkError(profile, \"changed while opening its directory\");\n }\n const directory = fdPath(opened.fd);\n const pinned: PinnedProfileDirectory = {\n path: directory, fd: opened.fd, dev, ino, active: true,\n };\n const homeHandle = handles[0]!;\n const homeStats = await homeHandle.stat({ bigint: true });\n const homeDev = exactFileId(homeStats.dev);\n const homeIno = exactFileId(homeStats.ino);\n if (homeDev === null || homeIno === null) throw new ProfileSymlinkError(profile, \"has an unverified home directory identity\");\n const homePinned: PinnedProfileDirectory = {\n path: fdPath(homeHandle.fd), fd: homeHandle.fd, dev: homeDev, ino: homeIno, active: true,\n };\n const context = new Map(pinnedProfiles.getStore());\n context.set(named, pinned);\n const homes = new Map(pinnedHomes.getStore());\n homes.set(home, homePinned);\n try {\n return await pinnedHomes.run(homes, () => pinnedProfiles.run(context, () => action(directory)));\n } finally {\n pinned.active = false;\n homePinned.active = false;\n }\n } finally {\n for (const handle of handles.reverse()) await handle.close();\n }\n}\n\n/** Missing profiles retain their existing read/create behavior; aliases never do. */\nexport async function assertRealProfileDirectory(profile: string): Promise<void> {\n const named = namedProfilePath(profile);\n const pinned = pinnedProfiles.getStore()?.get(named);\n if (pinned) {\n livePinnedPath(profile, pinned);\n return;\n }\n const stats = await lstat(named).catch((error: NodeJS.ErrnoException) => {\n if (error.code === \"ENOENT\") return null;\n throw error;\n });\n if (stats && (!stats.isDirectory() || stats.isSymbolicLink())) throw new ProfileSymlinkError(profile);\n}\n\n/**\n * Runs a small critical section under the one lock shared by all profile\n * stores (see acquireProfileLock). Lock release verifies its ownership token\n * before removing the exact metadata instance it acquired.\n *\n * Reentrant within one async call chain: a critical section that already\n * holds a profile's lock (for example a login's compare-and-commit) can call\n * store writers that take the same lock without deadlocking. Ownership is\n * keyed by the resolved lock path (so switching state roots inside a lock\n * still takes the other root's lock) and lasts exactly as long as the\n * acquisition: it is revoked before release, so work deferred past the\n * critical section waits for the lock like any other caller.\n */\nexport async function withProfileLock<T>(\n profile: string,\n action: () => Promise<T>,\n options: ProfileLockOptions = {},\n): Promise<T> {\n const normalizedProfile = validateProfileName(profile);\n const lockPath = profileLockPath(normalizedProfile);\n const held = (heldProfileLocks.getStore() ?? []).filter((ownership) => ownership.active);\n if (held.some((ownership) => ownership.lockPath === lockPath)) {\n await assertRealProfileDirectory(normalizedProfile);\n return action();\n }\n const named = namedProfilePath(normalizedProfile);\n const inherited = pinnedProfiles.getStore()?.get(named);\n if (inherited && !isPinnedLive(inherited)) {\n // Deferred work may explicitly acquire a new lock, but never reuse its old fd.\n const profiles = new Map(pinnedProfiles.getStore());\n profiles.delete(named);\n const homes = new Map(pinnedHomes.getStore());\n homes.delete(tinycloudHomePath());\n return pinnedHomes.run(homes, () =>\n pinnedProfiles.run(profiles, () => withProfileLock(normalizedProfile, action, options)));\n }\n return withPinnedProfileDirectory(normalizedProfile, async (directory) => {\n if (!directory) throw new ProfileDeletedError(normalizedProfile);\n const acquired = await acquireProfileLock(normalizedProfile, options);\n const ownership: HeldProfileLock = {\n lockPath,\n turnSlot: acquired.turnSlot,\n deletedWhileWaiting: acquired.deletedWhileWaiting,\n active: true,\n };\n try {\n return await heldProfileLocks.run([...held, ownership], action);\n } finally {\n ownership.active = false;\n await acquired.release();\n if (ownership.deletedProfile || acquired.deletedWhileWaiting) {\n const current = await lstat(namedProfilePath(normalizedProfile), { bigint: true }).catch(() => null);\n const original = await stat(directory, { bigint: true }).catch(() => null);\n const currentDev = current && exactFileId(current.dev);\n const currentIno = current && exactFileId(current.ino);\n if (currentDev !== null && currentIno !== null && original &&\n currentDev === exactFileId(original.dev) && currentIno === exactFileId(original.ino)) {\n await rmdir(namedProfilePath(normalizedProfile)).catch(() => undefined);\n }\n }\n }\n }, { create: !options.requireProfile });\n}\n\n/**\n * Records, inside a profile deletion's critical section and after its files\n * are gone, that the profile was deleted, with the number of the deleting\n * turn. A store write whose wait overlapped that turn (it arrived before the\n * deletion released the lock, whether before or after this record) then\n * refuses (ProfileDeletedError) instead of recreating a profile with only a\n * session or store in it. The caller must hold the profile's lock.\n */\nexport async function recordProfileDeletion(profile: string): Promise<void> {\n const lockPath = profileLockPath(profile);\n const ownership = (heldProfileLocks.getStore() ?? []).find((candidate) => candidate.active && candidate.lockPath === lockPath);\n if (!ownership) throw new Error(`Recording the deletion of profile \"${profile}\" requires holding its lock.`);\n await replaceJson(join(profileTurnLockPath(profile), \"deleted.json\"), { slot: ownership.turnSlot }, true);\n ownership.deletedProfile = true;\n}\n\n/**\n * Refuses a write that would recreate a profile deleted while this critical\n * section waited for the lock (ProfileDeletedError). Every writer of profile\n * state other than the profile settings themselves calls this inside its\n * critical section, before writing. The caller must hold the profile's lock.\n */\nexport async function refuseWriteToDeletedProfile(profile: string): Promise<void> {\n const lockPath = profileLockPath(profile);\n const ownership = (heldProfileLocks.getStore() ?? []).find((candidate) => candidate.active && candidate.lockPath === lockPath);\n if (!ownership?.deletedWhileWaiting) return;\n // Recreated meanwhile (or by this critical section): writing is fine.\n if (await exists(profileConfigPath(profile))) return;\n throw new ProfileDeletedError(profile);\n}\n\n/**\n * Appends a record or replaces the existing record with the supplied explicit\n * key. The extractor is supplied by the owning store so state.ts never needs\n * to know permission-request or delegation record shapes.\n */\nexport async function upsertProfileRecord<T>(\n profile: string,\n store: Exclude<ProfileStoreName, \"session\">,\n key: string,\n record: T,\n getKey: (candidate: T) => string | undefined,\n options: ProfileLockOptions = {},\n): Promise<T[]> {\n if (!key) throw new TypeError(\"A non-empty record key is required.\");\n\n return withProfileLock(profile, async () => {\n await refuseWriteToDeletedProfile(profile);\n const current = (await readProfileStore<T>(profile, store)).records;\n const next = current.filter((candidate) => getKey(candidate) !== key);\n next.push(record);\n await writeJsonAtomic(profileStorePath(profile, store), next);\n await writeFormatOneMetadata(profile, store);\n return next;\n }, options);\n}\n\n/**\n * Performs a typed read-modify-write of a legacy array store under the one\n * per-profile lock. The payload stays an array and the sibling metadata stays\n * format 1, so existing CLI readers keep their byte/layout contract.\n */\nexport async function updateProfileStore<T, Result>(\n profile: string,\n store: Exclude<ProfileStoreName, \"session\">,\n update: (\n records: readonly T[],\n ) => Promise<{ readonly records: readonly T[]; readonly result: Result }> | {\n readonly records: readonly T[];\n readonly result: Result;\n },\n options: ProfileLockOptions = {},\n): Promise<Result> {\n return withProfileLock(\n profile,\n () => updateProfileStoreWhileLocked(profile, store, update),\n options,\n );\n}\n\n/** The caller must already own this profile's lock. */\nasync function updateProfileStoreWhileLocked<T, Result>(\n profile: string,\n store: Exclude<ProfileStoreName, \"session\">,\n update: (\n records: readonly T[],\n ) => Promise<{ readonly records: readonly T[]; readonly result: Result }> | {\n readonly records: readonly T[];\n readonly result: Result;\n },\n): Promise<Result> {\n await refuseWriteToDeletedProfile(profile);\n const current = await readProfileStore<T>(profile, store);\n const next = await update(current.records);\n await writeJsonAtomic(profileStorePath(profile, store), next.records);\n await writeFormatOneMetadata(profile, store);\n return next.result;\n}\n\nexport async function writeSession<T extends object>(\n profile: string,\n session: T,\n options: ProfileLockOptions = {},\n): Promise<void> {\n await withProfileLock(profile, async () => {\n await refuseWriteToDeletedProfile(profile);\n await readStoreMetadata(profile, \"session\");\n await writeJsonAtomic(sessionPath(profile), session);\n await writeFormatOneMetadata(profile, \"session\");\n }, options);\n}\n\nexport async function removeSession(\n profile: string,\n options: ProfileLockOptions = {},\n): Promise<void> {\n await withProfileLock(profile, async () => {\n await readStoreMetadata(profile, \"session\");\n await rm(sessionPath(profile), { force: true });\n }, options);\n}\n\nasync function writeFormatOneMetadata(profile: string, store: ProfileStoreName): Promise<void> {\n await writeJsonAtomic(profileStoreMetadataPath(profile, store), { formatVersion: 1 });\n}\n\ninterface AcquiredProfileLock {\n readonly deletedWhileWaiting: boolean;\n readonly turnSlot: number;\n release(): Promise<void>;\n}\n\ninterface LockDeadline {\n readonly startedAt: number;\n readonly timeoutMs: number;\n readonly retryMs: number;\n}\n\n/**\n * Takes a profile's lock in two layers:\n *\n * 1. The turn lock (acquireTurn), used by this release and later ones. It\n * decides nothing from elapsed time, so a process paused anywhere, for\n * any length of time, cannot let a second holder in.\n * 2. Holding a turn, the `.lock` directory every older release uses\n * (publishProfileLock), so they keep excluding this release and it them.\n * Only one process of this release at a time acquires, recovers or\n * releases `.lock`.\n *\n * Also reports whether a profile deletion (recordProfileDeletion) ran while\n * this acquisition waited.\n */\nasync function acquireProfileLock(\n profile: string,\n options: ProfileLockOptions,\n): Promise<AcquiredProfileLock> {\n const deadline: LockDeadline = {\n startedAt: Date.now(),\n timeoutMs: options.timeoutMs ?? DEFAULT_LOCK_TIMEOUT_MS,\n retryMs: options.retryMs ?? DEFAULT_LOCK_RETRY_MS,\n };\n const staleAfterMs = options.staleAfterMs ?? DEFAULT_STALE_LOCK_MS;\n const lockPath = join(profilePath(profile), \".lock\");\n const turnDirectory = profileTurnLockPath(profile);\n\n const directory = profilePath(profile);\n await assertRealProfileDirectory(profile);\n if (options.requireProfile && !(await exists(profileConfigPath(profile)))) throw new ProfileDeletedError(profile);\n if (!options.requireProfile) await mkdir(directory, { recursive: true, mode: PRIVATE_DIR_MODE });\n await assertRealProfileDirectory(profile);\n await mkdir(dirname(turnDirectory), { recursive: true, mode: PRIVATE_DIR_MODE });\n // Every store write takes this lock: tighten the tree older releases\n // created 0775 before writing sessions or delegations into it.\n await chmod(directory, PRIVATE_DIR_MODE);\n\n // Bound before this acquisition's first turn record (or first probe of\n // another's socket), closed once the acquisition is over.\n const liveness: AcquisitionLiveness = { directory: turnDirectory, held: undefined };\n const closeAcquisitionLiveness = async () => {\n if (liveness.held) await closeLiveness(liveness.held);\n };\n let taken: { turn: Turn; firstWaitedTurn: number };\n try {\n taken = await acquireTurn(profile, turnDirectory, deadline, liveness);\n } catch (error) {\n await closeAcquisitionLiveness();\n throw error;\n }\n const { turn, firstWaitedTurn } = taken;\n try {\n // Holding the turn, no deletion runs: a profile present now stays until release.\n if (options.requireProfile && !(await exists(profileConfigPath(profile)))) {\n // A deletion that finished while this waited could not remove the\n // directory if a lock was held then; remove it if empty.\n await rmdir(directory).catch(() => undefined);\n throw new ProfileDeletedError(profile);\n }\n // An unreadable deletion record is ignored rather than failing every\n // acquisition; it is written durably, so only damage makes it so.\n const deletion = await readJson<{ slot?: unknown }>(join(turnDirectory, \"deleted.json\")).catch(() => null);\n const deletedWhileWaiting = typeof deletion?.slot === \"number\" && deletion.slot >= firstWaitedTurn;\n if (deletedWhileWaiting && !(await exists(profileConfigPath(profile)))) throw new ProfileDeletedError(profile);\n while (true) {\n const token = randomUUID();\n if (await publishProfileLock(profile, lockPath, token, options.requireProfile === true, liveness.held ?? null)) {\n return {\n deletedWhileWaiting,\n turnSlot: turn.slot,\n release: async () => {\n try {\n await releaseProfileLock(profile, lockPath, token);\n } finally {\n await releaseTurn(profile, turn, liveness);\n await closeAcquisitionLiveness();\n }\n },\n };\n }\n await signalTestLockContention(profile);\n if (await recoverStaleLock(profile, lockPath, liveness, staleAfterMs)) continue;\n await waitOrTimeOut(profile, deadline);\n }\n } catch (error) {\n await releaseTurn(profile, turn, liveness);\n await closeAcquisitionLiveness();\n throw error;\n }\n}\n\n/**\n * An acquisition's liveness socket in its turn directory: undefined until\n * bound, null if it could not be. Same-PID records are judged in that\n * directory, with this socket as the control (probeLiveness).\n */\ninterface AcquisitionLiveness {\n readonly directory: string;\n held: HeldLiveness | null | undefined;\n}\n\n/** Throws once the acquisition's time is up; `detail` explains a damaged turn lock. */\nfunction checkDeadline(profile: string, deadline: LockDeadline, detail?: string): void {\n if (Date.now() - deadline.startedAt >= deadline.timeoutMs) {\n throw new ProfileLockTimeoutError(profile, deadline.timeoutMs, detail);\n }\n}\n\n/** Sleeps one retry interval, or throws once the acquisition's time is up. */\nasync function waitOrTimeOut(profile: string, deadline: LockDeadline, detail?: string): Promise<void> {\n checkDeadline(profile, deadline, detail);\n await sleep(Math.min(deadline.retryMs, deadline.timeoutMs - (Date.now() - deadline.startedAt)));\n}\n\n/** One granted turn of the turn lock. */\ninterface Turn {\n readonly directory: string;\n readonly slot: number;\n readonly token: string;\n}\n\n/** A turn as recorded on disk. */\ninterface TurnState extends RecordedProcess {\n readonly slot: number;\n readonly token: string;\n readonly pid: number;\n /** The token of the turn before it that its creator observed; null for turn 0. */\n readonly after: string | null;\n readonly held: boolean;\n readonly done: boolean;\n}\n\n/** Turn directory names: decimal turn numbers. */\nconst TURN = /^(?:0|[1-9][0-9]*)$/;\n\n/**\n * Tokens of turns this process published and then gave up (a marker write or\n * a read failed after publication): the positive evidence that lets this\n * process settle a turn of its own PID. A live process's turn that is not in\n * here is never settled by it, so a module instance that cannot see another\n * instance's set (another bundled copy of this package) waits rather than\n * settling a turn that copy still holds.\n *\n * One set per process, not per module instance: the package's entry points\n * (state, index, artifacts, cli-runtime; ESM and CJS) each bundle their own\n * copy of this module, and one process may load several. The key is\n * versioned so that a release changing what the set holds uses a new one.\n */\nconst relinquishedTurns: Set<string> =\n (globalThis as unknown as Record<symbol, Set<string> | undefined>)[Symbol.for(\"tinycloud.operations.relinquishedProfileTurns.v1\")] ??=\n new Set<string>();\n\n/** Marker writes are retried this many times, after these delays (ms), before giving up. */\nconst MARKER_RETRY_DELAYS_MS = [10, 50, 250];\n/** A given-up turn is settled in the background every second for up to five minutes. */\nconst SETTLE_RETRY_MS = 1_000;\nconst SETTLE_RETRIES = 300;\n\n/**\n * The turn lock: mutual exclusion among processes of this release that does\n * not depend on how long any of them is paused. It uses only `mkdir`,\n * `rename`, exclusive file creation, and isRecordedProcessAlive (also used\n * for `.lock` recovery), so it needs no hard links.\n *\n * Turns are numbered directories in profileTurnLockPath(profile). Turn n has\n * `owner.json` ({ pid, createdAt, token, after, instanceId, ...origin }), where\n * `after` is the token of its observed predecessor. It gains `<token>.held`\n * when granted and `<token>.done` when over. Markers name the turn's own token,\n * so one written late into a recreated directory of the same number means nothing.\n *\n * - A turn is published by renaming a fully written staging directory to\n * `n`, which fails while `n` exists (rename never replaces a non-empty\n * directory, and turn directories are never empty). The turn directory\n * itself is published the same way, with a granted, finished turn 0.\n * - A process takes turn n only after reading turn n-1 granted and done.\n * Once `n` is published it checks that turn n-1 is still the one it read\n * (same token, granted, done) and only then writes `held`; otherwise the\n * turn is void: it writes `done` alone and starts over.\n * - Turns are removed by renaming them aside: a void turn at any time, a\n * granted one only once no older turn is left and turn n+1 is granted and\n * done. The latest turn is never removed.\n *\n * Why at most one granted turn is ever not done: a removed number can be\n * published again, but turn n-1 is always removed before turn n, so a\n * process whose stale read led it to republish n finds n-1 gone or holding\n * another token, and its turn is void. A process that finds n-1 as it read\n * it published `n` for the first time, so it is the only process granted n,\n * and n-1 was done before. A turn is marked done only by its holder, by a\n * process with evidence the holder is gone, or by its own process once it\n * explicitly gave the turn up; a paused holder is alive.\n * A turn is voided only on definite evidence (the turn it follows is gone or\n * holds another token), never because a read failed. Nothing above is\n * decided from elapsed time.\n *\n * Returns the turn and the number of the first turn this acquisition waited\n * on: the latest turn when it first looked if that was not done yet, else\n * the next one.\n */\nasync function acquireTurn(\n profile: string,\n directory: string,\n deadline: LockDeadline,\n liveness: AcquisitionLiveness,\n): Promise<{ turn: Turn; firstWaitedTurn: number }> {\n let firstWaitedTurn: number | undefined;\n // Why the turn lock looks damaged, as of the last look, for the timeout message.\n let problem: string | undefined;\n const damaged = (what: string) =>\n `The turn lock ${directory} ${what}, so no process can take it. ` +\n `If no tc or MCP process is using profile \"${profile}\", remove that directory.`;\n for (let first = true; ; first = false) {\n if (!first) checkDeadline(profile, deadline, problem);\n problem = undefined;\n const latest = await readLatestTurn(directory);\n if (latest === \"missing\") {\n await createTurnDirectory(directory);\n continue;\n }\n if (latest === \"empty\") {\n // Usually a listing that straddled a turn's publication and an older\n // turn's removal (readdir is not a snapshot); damaged if it persists.\n problem = damaged(\"has no turns\");\n await waitOrTimeOut(profile, deadline, problem);\n continue;\n }\n if (latest === null) continue;\n if (typeof latest === \"number\") {\n problem = damaged(`has an unreadable turn ${latest}`);\n await waitOrTimeOut(profile, deadline, problem);\n continue;\n }\n firstWaitedTurn ??= latest.done ? latest.slot + 1 : latest.slot;\n if (latest.held && latest.done) {\n const turn = await takeTurn(profile, directory, latest, await ensureLiveness(liveness));\n if (turn !== null) return { turn, firstWaitedTurn };\n await waitOrTimeOut(profile, deadline);\n } else if (latest.done) {\n problem = damaged(`ends with turn ${latest.slot}, which was never granted`);\n await waitOrTimeOut(profile, deadline, problem);\n } else if (\n (latest.pid === process.pid && relinquishedTurns.has(latest.token)) ||\n !await isRecordedProcessAlive(latest, liveness)\n ) {\n await waitForTestBarrier(TEST_LOCK_TURN_SETTLE_BARRIER_DIR, profile);\n await settleAbandonedTurn(directory, latest);\n relinquishedTurns.delete(latest.token);\n } else {\n await signalTestLockContention(profile);\n await waitOrTimeOut(profile, deadline);\n }\n }\n}\n\n/**\n * The highest-numbered turn: its state, its number if it is damaged, null if\n * it went away while being read.\n */\nasync function readLatestTurn(directory: string): Promise<TurnState | number | \"missing\" | \"empty\" | null> {\n let names: string[];\n try {\n names = await readdir(directory);\n } catch (error) {\n if (isErrno(error, \"ENOENT\")) return \"missing\";\n throw error;\n }\n const slots = names.filter((name) => TURN.test(name)).map(Number);\n if (slots.length === 0) return \"empty\";\n const slot = Math.max(...slots);\n const turn = await readTurn(directory, slot);\n return turn === \"damaged\" ? slot : turn;\n}\n\n/**\n * Turn `slot` as recorded now; null if it is gone; \"damaged\" if its owner\n * record is missing or unreadable (turns are renamed into place and aside\n * whole, with their owner record written durably). Any other read error is\n * thrown: it says nothing about the turn. `done` is read before `held`,\n * which every writer creates first, so a turn read as done is never misread\n * as void.\n */\nasync function readTurn(directory: string, slot: number): Promise<TurnState | \"damaged\" | null> {\n const path = join(directory, String(slot));\n let text: string;\n try {\n text = await readFile(join(path, \"owner.json\"), \"utf8\");\n } catch (error) {\n if (!isErrno(error, \"ENOENT\") && !isErrno(error, \"ENOTDIR\")) throw error;\n return await exists(path) ? \"damaged\" : null;\n }\n let owner: (RecordedProcess & { token?: unknown; after?: unknown }) | null;\n try {\n owner = JSON.parse(text) as typeof owner;\n } catch {\n return \"damaged\";\n }\n if (owner === null || typeof owner !== \"object\" || typeof owner.token !== \"string\" || typeof owner.pid !== \"number\") {\n return \"damaged\";\n }\n const done = await exists(join(path, `${owner.token}.done`));\n const held = await exists(join(path, `${owner.token}.held`));\n return {\n ...owner,\n slot,\n token: owner.token,\n pid: owner.pid,\n after: typeof owner.after === \"string\" ? owner.after : null,\n held,\n done,\n };\n}\n\n/**\n * Whether turn `slot` is still the granted, finished turn with `token`. False\n * only on definite evidence: it is gone, or holds another token. Throws if it\n * cannot be read, or is damaged, so the caller leaves its own turn undecided.\n */\nasync function isFinishedTurn(directory: string, slot: number, token: string): Promise<boolean> {\n const turn = await readTurn(directory, slot);\n if (turn === \"damaged\") throw new Error(`Turn ${slot} of the turn lock ${directory} is unreadable.`);\n return turn !== null && turn.token === token && turn.held && turn.done;\n}\n\n/**\n * Publishes the turn after `latest` and claims it. Null if another process\n * published that number first, or the turn is void. If a step after\n * publication fails, the turn is handed to settleOwnTurn and the error thrown.\n */\nasync function takeTurn(profile: string, directory: string, latest: TurnState, liveness: HeldLiveness | null): Promise<Turn | null> {\n const token = randomUUID();\n const slot = latest.slot + 1;\n const path = join(directory, String(slot));\n const owner = { pid: process.pid, createdAt: new Date().toISOString(), token, after: latest.token, ...processOwner(liveness) };\n let published = false;\n try {\n published = await publishDirectory(\n join(directory, `.stage-${process.pid}-${token}`),\n path,\n { \"owner.json\": `${JSON.stringify(owner, null, 2)}\\n` },\n () => waitForTestBarrier(TEST_LOCK_TURN_PUBLISH_BARRIER_DIR, profile),\n );\n if (published && await isFinishedTurn(directory, latest.slot, latest.token) && await markTurn(path, token, \"held\")) {\n return { directory, slot, token };\n }\n if (published) await markTurn(path, token, \"done\");\n return null;\n } catch (error) {\n if (published) await settleOwnTurn(directory, slot, token);\n throw error;\n }\n}\n\n/**\n * Finishes the turn of a process that is gone (or one this process gave up).\n * A turn its creator never got to grant is granted first if the turn before\n * it is still the one its creator read (the check the creator would have\n * made; that turn cannot have been removed, since that needs this turn\n * done), and left void only if it definitely is not.\n */\nasync function settleAbandonedTurn(directory: string, abandoned: TurnState): Promise<void> {\n const path = join(directory, String(abandoned.slot));\n if (!abandoned.held && abandoned.after !== null &&\n await isFinishedTurn(directory, abandoned.slot - 1, abandoned.after)) {\n await markTurn(path, abandoned.token, \"held\");\n }\n await markTurn(path, abandoned.token, \"done\");\n}\n\n/**\n * Settles a turn this process published but could not grant or finish (a\n * marker write or a read failed). Other processes wait on this live PID, so\n * it is retried in the background until it succeeds or the turn is settled\n * otherwise (by this process's next acquisition, or by another process once\n * this one has exited). The timer does not keep the process alive.\n */\nasync function settleOwnTurn(directory: string, slot: number, token: string): Promise<void> {\n relinquishedTurns.add(token);\n const pinned = await pinDirectory(directory).catch(() => null);\n const path = pinned?.path(\".\") ?? directory;\n let retries = SETTLE_RETRIES;\n const attempt = () => {\n void (async () => {\n try {\n const turn = await readTurn(path, slot);\n if (turn !== null && turn !== \"damaged\" && turn.token === token && !turn.done) await settleAbandonedTurn(path, turn);\n relinquishedTurns.delete(token);\n await pinned?.close();\n } catch {\n // Still relinquished: this process's next acquisition settles it too.\n if (retries-- > 0) setTimeout(attempt, SETTLE_RETRY_MS).unref();\n else await pinned?.close();\n }\n })();\n };\n attempt();\n}\n\n/**\n * Creates `<token>.<kind>` in a turn directory; false if the directory is\n * gone. Other failures (a full disk, a permission error) are retried a few\n * times, then thrown.\n */\nasync function markTurn(path: string, token: string, kind: \"held\" | \"done\"): Promise<boolean> {\n for (let attempt = 0; ; attempt++) {\n try {\n await writeFile(join(path, `${token}.${kind}`), \"\", { mode: PRIVATE_FILE_MODE, flag: \"wx\" });\n return true;\n } catch (error) {\n if (isErrno(error, \"EEXIST\")) return true;\n if (isErrno(error, \"ENOENT\")) return false;\n if (attempt >= MARKER_RETRY_DELAYS_MS.length) throw error;\n await sleep(MARKER_RETRY_DELAYS_MS[attempt]!);\n }\n }\n}\n\n/** Creates the turn directory with a granted, finished turn 0, unless another process did. */\nasync function createTurnDirectory(directory: string): Promise<void> {\n const token = randomUUID();\n const owner = { pid: process.pid, createdAt: new Date().toISOString(), token, after: null, ...processOwner(null) };\n await publishDirectory(join(dirname(directory), `.stage-${basename(directory)}-${process.pid}-${token}`), directory, {\n \"0/owner.json\": `${JSON.stringify(owner, null, 2)}\\n`,\n [`0/${token}.held`]: \"\",\n [`0/${token}.done`]: \"\",\n });\n}\n\n/**\n * Writes `files` (durably, when not empty) into a new staging directory and\n * renames it to `target`. False if `target` already exists (or the turn\n * directory is gone); the caller starts over. Nothing else removes a live\n * process's staging directory (see collectTurns), so `target` never appears\n * incomplete.\n */\nasync function publishDirectory(\n stage: string,\n target: string,\n files: Readonly<Record<string, string>>,\n beforeRename?: () => Promise<void>,\n): Promise<boolean> {\n try {\n for (const [name, contents] of Object.entries(files)) {\n await mkdir(dirname(join(stage, name)), { recursive: true, mode: PRIVATE_DIR_MODE });\n await writeFileDurably(join(stage, name), contents);\n }\n await beforeRename?.();\n await rename(stage, target);\n return true;\n } catch (error) {\n // ENOTEMPTY/EEXIST: `target` exists, though it may be gone again by now.\n // Windows reports an existing target directory as EPERM.\n if ([\"ENOTEMPTY\", \"EEXIST\", \"ENOENT\"].some((code) => isErrno(error, code)) || await exists(target)) return false;\n throw error;\n } finally {\n await rm(stage, { recursive: true, force: true }).catch(() => undefined);\n }\n}\n\n/**\n * Finishes this process's turn. If `done` cannot be written even after\n * retries, the critical section is still over and its writes stand: the turn\n * is handed to settleOwnTurn rather than failing the caller.\n */\nasync function releaseTurn(profile: string, turn: Turn, liveness: AcquisitionLiveness): Promise<void> {\n try {\n await markTurn(join(turn.directory, String(turn.slot)), turn.token, \"done\");\n } catch {\n await settleOwnTurn(turn.directory, turn.slot, turn.token);\n return;\n }\n await collectTurns(profile, turn, liveness).catch(() => undefined);\n}\n\n/**\n * Leftovers in a turn directory whose writer is gone: trash (a removed turn\n * renamed aside, garbage from then on), and staging directories and\n * deletion-record temporaries named after their writer's PID. A staging\n * directory named after this process's PID is judged by the owner record\n * staged in it, which a predecessor with this PID may have left.\n */\nasync function isAbandonedTurnEntry(directory: string, name: string, liveness: AcquisitionLiveness): Promise<boolean> {\n if (name.startsWith(\".trash-\")) return true;\n const writer = /^\\.(?:stage|deleted\\.json)[-.]([0-9]+)[-.]/.exec(name)?.[1];\n if (writer === undefined) return false;\n if (Number(writer) !== process.pid) return !await isRecordedProcessAlive({ pid: Number(writer) }, liveness);\n return name.startsWith(\".stage-\") && await isStagedOwnerGone(join(directory, name, \"owner.json\"), liveness);\n}\n\n/** A turn directory's staging directory left beside it by a process that is gone. */\nasync function isAbandonedTurnDirectoryStage(directory: string, name: string, liveness: AcquisitionLiveness): Promise<boolean> {\n const writer = /^\\.stage-.+-([0-9]+)-[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/.exec(name)?.[1];\n if (writer === undefined) return false;\n if (Number(writer) !== process.pid) return !await isRecordedProcessAlive({ pid: Number(writer) }, liveness);\n return isStagedOwnerGone(join(directory, name, \"0\", \"owner.json\"), liveness);\n}\n\n/**\n * Whether a complete staged owner record of this PID belongs to a process\n * that is gone. Its socket, if any, goes with the next sweep (sweepLiveness).\n */\nasync function isStagedOwnerGone(recordPath: string, liveness: AcquisitionLiveness): Promise<boolean> {\n const owner = await readJson<RecordedProcess>(recordPath).catch(() => null);\n return typeof owner === \"object\" && owner !== null && owner.pid === process.pid &&\n !await isRecordedProcessAlive(owner, liveness);\n}\n\n/**\n * Removes the turns before `turn`, which just finished: void turns, and\n * granted ones oldest first, each once no older turn is left and the next is\n * granted and done (see acquireTurn). A turn is renamed aside before it is\n * deleted, so its number is never an empty directory. A turn that is damaged\n * or cannot be read is left alone, and a read error stops the collection.\n * Leftovers of crashed processes go too, judged by PID (or, for this PID, by\n * the record they hold), never by age: removing a paused process's staging\n * directory could publish an incomplete turn. Last, liveness sockets the\n * kernel refuses are swept (sweepLiveness).\n */\nasync function collectTurns(profile: string, turn: Turn, liveness: AcquisitionLiveness): Promise<void> {\n for (const [directory, abandoned] of [\n [turn.directory, isAbandonedTurnEntry],\n [dirname(turn.directory), isAbandonedTurnDirectoryStage],\n ] as const) {\n for (const name of await readdir(directory)) {\n if (await abandoned(directory, name, liveness)) await rm(join(directory, name), { recursive: true, force: true }).catch(() => undefined);\n }\n }\n const older = (await readdir(turn.directory)).filter((name) => TURN.test(name)).map(Number)\n .filter((slot) => slot < turn.slot)\n .sort((left, right) => left - right);\n for (const slot of older) {\n const candidate = await readTurn(turn.directory, slot);\n if (candidate === null || candidate === \"damaged\" || !candidate.done) continue;\n if (candidate.held) {\n if (await exists(join(turn.directory, String(slot - 1)))) continue;\n const next = await readTurn(turn.directory, slot + 1);\n if (next === null || next === \"damaged\" || !next.held || !next.done) continue;\n }\n await waitForTestBarrier(TEST_LOCK_TURN_COLLECT_BARRIER_DIR, profile);\n const trash = join(turn.directory, `.trash-${randomUUID()}`);\n if (await rename(join(turn.directory, String(slot)), trash).then(() => true, () => false)) {\n await rm(trash, { recursive: true, force: true }).catch(() => undefined);\n }\n }\n await sweepLiveness(liveness);\n}\n\n/**\n * One attempt to take `.lock`, made while holding a turn. Nothing here\n * replaces an existing path:\n *\n * 1. `mkdir(.lock)` (never recursive) is exclusive, and is the primitive\n * older releases use too, so they exclude each other.\n * 2. Ownership is published by `link`ing a fully written owner file (staged\n * beside `.lock`, on the same filesystem) to `.lock/owner.json`. `link`\n * fails with EEXIST if an owner exists, and with ENOENT if the directory\n * was removed meanwhile (an aged ownerless-lock reclaim by an older\n * release). Only a successful link holds the lock; on either failure\n * nothing is released, since nothing was acquired, and the caller retries.\n * On a filesystem without hard links (FAT/exFAT, some SMB mounts) the\n * owner file is created in place with an exclusive create instead, with\n * the same EEXIST/ENOENT outcomes. A reader may then briefly see it\n * incomplete; every release treats an owner file it cannot parse as a\n * held lock, and this release removes one only once it has aged (see\n * isAbandonedOwner).\n *\n * A profile deletion removes the profile directory once it has released its\n * lock, so `mkdir(.lock)` can see ENOENT; the directory is recreated (0700)\n * and the caller retries within its deadline, as for a first write to a new\n * profile.\n *\n * Only one owner.json can exist at the lock path, and it is removed only by\n * its holder's release or by recovery of an abandoned holder, so at most one\n * process holds the lock. Returns false when the lock is not acquired.\n */\nasync function publishProfileLock(\n profile: string,\n lockPath: string,\n token: string,\n requireProfile: boolean,\n liveness: HeldLiveness | null,\n): Promise<boolean> {\n try {\n await mkdir(lockPath, { mode: PRIVATE_DIR_MODE });\n } catch (error) {\n if (isErrno(error, \"EEXIST\")) return false;\n if (!isErrno(error, \"ENOENT\")) throw error;\n // With requireProfile only an older release's deletion (which takes no turn) gets here.\n if (requireProfile) throw new ProfileDeletedError(profile);\n await mkdir(profilePath(profile), { recursive: true, mode: PRIVATE_DIR_MODE });\n return false;\n }\n await waitForTestBarrier(TEST_LOCK_PUBLISH_BARRIER_DIR, profile);\n const ownerPath = join(lockPath, \"owner.json\");\n const staged = join(dirname(lockPath), `.lock-owner-${token}.tmp`);\n const owner = `${JSON.stringify({ pid: process.pid, createdAt: new Date().toISOString(), token, ...processOwner(liveness) }, null, 2)}\\n`;\n try {\n await writeFile(staged, owner, { encoding: \"utf8\", mode: PRIVATE_FILE_MODE, flag: \"wx\" });\n try {\n await link(staged, ownerPath);\n } catch (error) {\n if (!lacksHardLinks(error)) throw error;\n await writeFile(ownerPath, owner, { encoding: \"utf8\", mode: PRIVATE_FILE_MODE, flag: \"wx\" });\n }\n return true;\n } catch (error) {\n if (isErrno(error, \"EEXIST\") || isErrno(error, \"ENOENT\")) return false;\n // Do not leave an ownerless lock behind for others to wait out. rmdir\n // removes only an empty directory; anyone who created it after ours went\n // away sees ENOENT on link and retries.\n await rmdir(lockPath).catch(() => undefined);\n throw error;\n } finally {\n await rm(staged, { force: true }).catch(() => undefined);\n }\n}\n\n/**\n * Errors `link` reports on a filesystem without hard links: EPERM (Linux\n * FAT/exFAT), ENOTSUP/EOPNOTSUPP (macOS FAT/exFAT, SMB), ENOSYS, EISDIR\n * (libuv's code for Windows' ERROR_INVALID_FUNCTION on FAT32), and EMLINK\n * (too many links), for which the same fallback is just as correct.\n */\nconst NO_HARD_LINK_ERRORS = new Set([\"EPERM\", \"ENOTSUP\", \"EOPNOTSUPP\", \"ENOSYS\", \"EISDIR\", \"EMLINK\"]);\n\nfunction lacksHardLinks(error: unknown): boolean {\n return typeof error === \"object\" && error !== null && \"code\" in error &&\n NO_HARD_LINK_ERRORS.has(String((error as { code?: unknown }).code));\n}\n\n/**\n * Releases the lock if `.lock/owner.json` is still this acquisition's. No\n * one else can put an owner record there while it exists (owners are only\n * published into a directory their own `mkdir` created, and recovery\n * removes only an abandoned holder's record), so the record read is the one\n * unlinked.\n */\nasync function releaseProfileLock(profile: string, lockPath: string, token: string): Promise<void> {\n const ownerPath = join(lockPath, \"owner.json\");\n const owner = await readJson<{ token?: unknown }>(ownerPath).catch(() => null);\n if (owner?.token !== token) return;\n await rm(ownerPath, { force: true });\n // `.lock` is empty from here until the rmdir; a contender that removes it\n // and creates its own meanwhile is not harmed: rmdir fails on its owner\n // file, or the contender's link sees ENOENT and retries. If a recoverer's\n // claim file is still inside, rmdir fails and that recoverer removes the\n // directory when it drops its claim.\n await waitForTestBarrier(TEST_LOCK_RELEASE_BARRIER_DIR, profile);\n await rmdir(lockPath).catch(() => undefined);\n}\n\n/**\n * Gives process-level contention tests an event-driven witness that a second\n * writer has reached an already-held lock. This deliberately has no effect\n * outside an explicit test environment, and only creates a new signal file\n * beneath TC_HOME; it never changes lock acquisition or release semantics.\n */\nasync function signalTestLockContention(profile: string): Promise<void> {\n if (process.env.NODE_ENV !== \"test\") return;\n\n const configuredPath = process.env[TEST_LOCK_CONTENTION_SIGNAL_PATH];\n const configuredHome = process.env.TC_HOME;\n if (!configuredPath || !configuredHome) return;\n\n const home = resolve(configuredHome);\n const signalPath = resolve(configuredPath);\n const pathFromHome = relative(home, signalPath);\n if (\n pathFromHome === \"\" ||\n pathFromHome === \"..\" ||\n pathFromHome.startsWith(`..${process.platform === \"win32\" ? \"\\\\\" : \"/\"}`) ||\n pathFromHome.startsWith(\"../\") ||\n pathFromHome.startsWith(\"..\\\\\")\n ) {\n return;\n }\n\n await writeFile(signalPath, `${profile}\\n`, { encoding: \"utf8\", flag: \"wx\" })\n .catch(() => undefined);\n}\n\n/**\n * Reclaims a `.lock` with no owner record, while holding a turn. A\n * `.recover-*` claim may contain a live legacy owner's record moved by a\n * no-hard-links recoverer just before it crashed. Restore that record first;\n * only orphaned claims holding dead or malformed records can be removed.\n * Otherwise the directory must be older than the stale threshold: one left\n * by a crash, or by an older release, whose `.stale-*` / `.release-*` claim\n * files are removed too. owner.json is never unlinked, and the directory\n * itself is only `rmdir`ed, which succeeds only while it is empty.\n *\n * Holding a turn, no other process of this release reclaims concurrently.\n * That closes the race in which two reclaimers' age checks let the later one\n * remove a directory a 1.0.0-beta.16-or-older release had just created,\n * which holds the lock from its `mkdir`. That race remains only if a\n * 1.0.0-beta.17 … 1.0.1-beta.4 release reclaims at the same moment, or the\n * older process stays paused between its `mkdir` and its owner write for\n * longer than the stale threshold; recoveryFenced bounds how stale this\n * process's age check can be.\n */\nasync function recoverOwnerlessLock(profile: string, lockPath: string, liveness: AcquisitionLiveness, staleAfterMs: number): Promise<boolean> {\n let aged: boolean;\n try {\n const { mtimeMs } = await stat(lockPath);\n aged = Date.now() - mtimeMs >= staleAfterMs;\n } catch {\n return false;\n }\n const checkedAt = performance.now();\n await waitForTestBarrier(TEST_LOCK_OWNERLESS_BARRIER_DIR, profile);\n try {\n const entries = await readdir(lockPath);\n if (entries.includes(\"owner.json\")) return false;\n const onlyOrphanedClaims = entries.length > 0 && entries.every((entry) => RECOVERY_CLAIM.test(entry));\n if (!onlyOrphanedClaims && (!aged || recoveryFenced(checkedAt, staleAfterMs))) return false;\n for (const name of entries.filter((entry) => RECOVERY_CLAIM.test(entry))) {\n const claimPath = join(lockPath, name);\n if (await hasLiveRecoveryOwner(claimPath, liveness)) {\n // The claim keeps the directory in place. Only the original mkdir\n // owner could publish owner.json here; never overwrite that record.\n if (!await exists(join(lockPath, \"owner.json\"))) await rename(claimPath, join(lockPath, \"owner.json\"));\n return false;\n }\n await rm(claimPath, { force: true });\n }\n for (const name of entries.filter((entry) => ABANDONED_CLAIM.test(entry))) {\n await rm(join(lockPath, name), { force: true });\n }\n await rmdir(lockPath);\n } catch {\n return false;\n }\n await removeAgedOwnerFiles(lockPath, staleAfterMs);\n return true;\n}\n\n/** A no-hard-links claim may contain a live owner's record moved before a crash. */\nasync function hasLiveRecoveryOwner(claimPath: string, liveness: AcquisitionLiveness): Promise<boolean> {\n let contents: string;\n try {\n contents = await readFile(claimPath, \"utf8\");\n } catch (error) {\n if (isErrno(error, \"ENOENT\")) return false;\n throw error; // An I/O failure is not evidence that the holder is gone.\n }\n let owner: unknown;\n try {\n owner = JSON.parse(contents);\n } catch {\n return false;\n }\n const record = typeof owner === \"object\" && owner !== null && \"pid\" in owner ? owner as RecordedProcess : null;\n return typeof record?.pid === \"number\" && Number.isInteger(record.pid) && record.pid > 0 &&\n await isRecordedProcessAlive(record, liveness);\n}\n\n/**\n * Whether an ownerless-reclaim decision made at `since` (a monotonic\n * timestamp) is too old to act on. The age check is only good for a while:\n * past half the stale threshold the reclaim starts over.\n */\nfunction recoveryFenced(since: number, staleAfterMs: number): boolean {\n return performance.now() - since >= staleAfterMs / 2;\n}\n\n/** Claim files of older releases' stale recovery and (up to 1.0.0-beta.16) release. */\nconst ABANDONED_CLAIM = /^\\.(?:release|stale)-[0-9a-f-]+\\.json$/;\n/**\n * Claim files of this release's stale recovery. Releases up to 1.0.1-beta.4\n * never remove them, which is what makes this recovery safe against them; a\n * recoverer that crashes leaves one that only a release with the turn lock\n * clears (see REFERENCE.md, \"Profile lock\").\n */\nconst RECOVERY_CLAIM = /^\\.recover-[0-9a-f-]+\\.json$/;\n/** Owner files staged by publishProfileLock beside `.lock`. */\nconst STAGED_OWNER = /^\\.lock-owner-[0-9a-f-]+\\.tmp$/;\n/**\n * Removes owner files a crashed acquirer staged beside `.lock`. A live\n * acquirer's staged file exists only for its write and link, so only files\n * older than the stale threshold are removed. Runs after a recovery, never\n * on the uncontended path.\n */\nasync function removeAgedOwnerFiles(lockPath: string, staleAfterMs: number): Promise<void> {\n const directory = dirname(lockPath);\n const names = await readdir(directory).catch(() => []);\n for (const name of names.filter((entry) => STAGED_OWNER.test(entry))) {\n const path = join(directory, name);\n const aged = await stat(path).then(({ mtimeMs }) => Date.now() - mtimeMs >= staleAfterMs, () => false);\n if (aged) await rm(path, { force: true }).catch(() => undefined);\n }\n}\n\n\n/**\n * Removes an abandoned owner record (see isAbandonedOwner) and its `.lock`,\n * while holding a turn, so no other process of this release recovers,\n * reclaims or acquires `.lock` meanwhile.\n *\n * The record is claimed first: hard-linked to `.recover-<uuid>.json`, and the\n * claim compared with the exact bytes read. A record that replaced the\n * observed one is left in place. While the claim is inside `.lock`, the\n * directory cannot be removed and recreated: older releases never remove a\n * `.recover-*` claim (they clean up only their own claim names), and other\n * processes of this release are kept out by the turn lock. So no other owner\n * record can appear in it, and the unlink of owner.json can only remove the\n * claimed, dead record. A live record is never unlinked or moved. `.lock`\n * itself is removed only after this process's claim is unlinked from it,\n * which proves it is still the dead holder's directory; if the claim cannot\n * be made, `.lock` is left alone (it may be a directory a 1.0.0-beta.16-or-\n * older writer just created, which it holds from that `mkdir`). No step\n * depends on how long this process takes.\n *\n * Without hard links (where 1.0.0-beta.17 … 1.0.1-beta.4 cannot run at all),\n * the record is renamed into the claim instead, and put back if it is not\n * the observed one: one a 1.0.0-beta.16-or-older process published after\n * recovering the same lock at the same moment. The claim keeps `.lock` held\n * meanwhile, so no second writer gets in, but if that holder releases\n * between the two renames, the put-back restores a released record, and\n * `.lock` stays held until that PID is gone and the record is 30 s old.\n */\nasync function recoverStaleLock(profile: string, lockPath: string, liveness: AcquisitionLiveness, staleAfterMs: number): Promise<boolean> {\n const ownerPath = join(lockPath, \"owner.json\");\n const observed = await readFile(ownerPath, \"utf8\").catch(() => null);\n if (observed === null) return recoverOwnerlessLock(profile, lockPath, liveness, staleAfterMs);\n if (!await isAbandonedOwner(observed, ownerPath, liveness, staleAfterMs)) return false;\n\n await waitForTestBarrier(TEST_LOCK_RECOVERY_BARRIER_DIR, profile);\n const claimPath = join(lockPath, `.recover-${randomUUID()}.json`);\n let moved = false;\n try {\n await link(ownerPath, claimPath);\n } catch (error) {\n // Gone or replaced already (another release recovered it): leave `.lock`.\n if (!lacksHardLinks(error)) return false;\n try {\n await rename(ownerPath, claimPath);\n moved = true;\n } catch {\n return false;\n }\n }\n if (moved) await waitForTestBarrier(TEST_LOCK_MOVED_BARRIER_DIR, profile);\n const sameInstance = await readFile(claimPath, \"utf8\").then((claimed) => claimed === observed, () => false);\n await waitForTestBarrier(TEST_LOCK_VERIFIED_BARRIER_DIR, profile);\n if (!sameInstance) {\n if (moved) {\n // No other owner.json can have appeared meanwhile: only a process\n // whose mkdir created this `.lock` publishes one, and it already has.\n await rename(claimPath, ownerPath).catch(() => undefined);\n } else {\n await unlink(claimPath).catch(() => undefined);\n }\n return false;\n }\n if (!moved) await rm(ownerPath, { force: true });\n await waitForTestBarrier(TEST_LOCK_CLAIM_BARRIER_DIR, profile);\n await waitForTestBarrier(TEST_LOCK_CLAIMED_BARRIER_DIR, profile);\n // Drop any claim a crashed recoverer of this release left, then this one.\n for (const name of (await readdir(lockPath).catch(() => [])).filter((entry) => RECOVERY_CLAIM.test(entry))) {\n if (join(lockPath, name) !== claimPath) await rm(join(lockPath, name), { force: true });\n }\n // Empty now, and still the dead holder's directory if this claim was in\n // it: remove it rather than leave an ownerless lock to age out.\n if (await unlink(claimPath).then(() => true, () => false)) await rmdir(lockPath).catch(() => undefined);\n await removeAgedOwnerFiles(lockPath, staleAfterMs);\n return true;\n}\n\n/**\n * Whether an owner record (its exact bytes, read from `ownerPath`) is\n * abandoned: its holder is confirmed gone (including a proven same-PID\n * predecessor), and it is older than the existing stale threshold; or, if\n * it is not a complete record, the file is older than that threshold.\n * Every release publishes complete records (by link or rename) except this\n * one's no-hard-link fallback, which writes in place while holding a turn;\n * so an incomplete record seen while holding a turn was left by a crash.\n */\nasync function isAbandonedOwner(observed: string, ownerPath: string, liveness: AcquisitionLiveness, staleAfterMs: number): Promise<boolean> {\n let owner: unknown;\n try {\n owner = JSON.parse(observed);\n } catch {\n owner = null;\n }\n if (typeof owner === \"object\" && owner !== null && \"pid\" in owner) {\n return isStaleOwner(owner as RecordedProcess & { createdAt?: unknown }, liveness, staleAfterMs);\n }\n const modifiedAt = await stat(ownerPath).then(({ mtimeMs }) => mtimeMs, () => Number.NaN);\n return Date.now() - modifiedAt >= staleAfterMs;\n}\n\n/** Test-only rendezvous (NODE_ENV=test and the named barrier directory set). */\nasync function waitForTestBarrier(environmentName: string, profile: string): Promise<void> {\n if (process.env.NODE_ENV !== \"test\") return;\n const barrierDirectory = process.env[environmentName];\n if (!barrierDirectory) return;\n await mkdir(barrierDirectory, { recursive: true });\n const readyPath = join(barrierDirectory, `ready-${process.pid}-${profile}`);\n await writeFile(readyPath, \"ready\\n\", { encoding: \"utf8\", flag: \"wx\" }).catch(() => undefined);\n const releasePath = join(barrierDirectory, \"release\");\n while (true) {\n try {\n await readFile(releasePath, \"utf8\");\n return;\n } catch {\n await sleep(1);\n }\n }\n}\n\nasync function isStaleOwner(\n owner: (RecordedProcess & { createdAt?: unknown }) | null,\n liveness: AcquisitionLiveness,\n staleAfterMs: number,\n): Promise<boolean> {\n const now = Date.now();\n const createdAt = typeof owner?.createdAt === \"string\"\n ? Date.parse(owner.createdAt)\n : Number.NaN;\n\n // Reclaim only a fully published lock whose owner is confirmed gone.\n // A lock without an owner record is handled by recoverOwnerlessLock: a\n // process holds the lock only once its owner.json is linked in.\n if (!Number.isFinite(createdAt) || now - createdAt < staleAfterMs) return false;\n if (typeof owner?.pid !== \"number\" || !Number.isInteger(owner.pid) || owner.pid <= 0) {\n return false;\n }\n return !await isRecordedProcessAlive(owner, liveness);\n}\n\n/** Whether `path` exists. Only ENOENT/ENOTDIR mean it does not; other errors are thrown. */\nasync function exists(path: string): Promise<boolean> {\n try {\n await stat(path);\n return true;\n } catch (error) {\n if (isErrno(error, \"ENOENT\") || isErrno(error, \"ENOTDIR\")) return false;\n throw error;\n }\n}\n\n/**\n * Whether the process that wrote an owner record may still be in its\n * critical section. A record of another PID is alive while that PID exists\n * (master's rule). A record of this PID but another instance is dead only on\n * authoritative evidence from the same kernel boot: in this PID namespace, a\n * different process start (the PID was reused); in another PID namespace,\n * its liveness socket, bound in this acquisition's turn directory, gone or\n * refused by the kernel (probeLiveness). A paused holder still accepts.\n * Everything else waits.\n */\nasync function isRecordedProcessAlive(owner: RecordedProcess, acquisition: AcquisitionLiveness): Promise<boolean> {\n const pid = owner.pid;\n if (typeof pid !== \"number\" || !Number.isInteger(pid) || pid <= 0) return true;\n try {\n process.kill(pid, 0);\n } catch (error) {\n return !isErrno(error, \"ESRCH\");\n }\n if (pid !== process.pid || typeof owner.instanceId !== \"string\" ||\n !/^[0-9a-f]{8}-(?:[0-9a-f]{4}-){3}[0-9a-f]{12}$/.test(owner.instanceId) ||\n owner.instanceId === processInstanceId) return true;\n const origin = processOrigin();\n if (!origin || owner.bootId !== origin.bootId ||\n typeof owner.pidNamespace !== \"string\" || !/^pid:\\[[0-9]+\\]$/.test(owner.pidNamespace)) return true;\n if (owner.pidNamespace === origin.pidNamespace) {\n return typeof owner.processStartTime !== \"string\" || !/^[0-9]+$/.test(owner.processStartTime) ||\n owner.processStartTime === origin.processStartTime;\n }\n const liveness = parseLiveness(owner.liveness);\n return liveness === null || await probeLiveness(liveness, acquisition) === \"unknown\";\n}\n\nfunction validateProfileName(profile: string): string {\n if (\n !profile ||\n profile === \".\" ||\n profile === \"..\" ||\n profile.includes(\"/\") ||\n profile.includes(\"\\\\\") ||\n profile.includes(\"\\0\")\n ) {\n throw new TypeError(\"Profile names must be non-empty path segments.\");\n }\n return profile;\n}\n\nfunction isErrno(error: unknown, code: string): boolean {\n return typeof error === \"object\" && error !== null && \"code\" in error &&\n (error as { code?: unknown }).code === code;\n}\n\nfunction sleep(milliseconds: number): Promise<void> {\n return new Promise((resolve) => setTimeout(resolve, milliseconds));\n}\n\nexport { DEFAULT_PROFILE };\n","import { randomUUID } from \"node:crypto\";\n\nimport type { PermissionEntry } from \"@tinycloud/sdk-core\";\nimport { z } from \"zod\";\n\nimport {\n canonicalizeCapabilities,\n evaluateOperationAuthority,\n evaluateAuthority,\n} from \"./authority.js\";\nimport type { OperationSpaceResolver } from \"./authority.js\";\nimport type {\n OperationOperatorType,\n TinyCloudPosture,\n} from \"./contract.js\";\nimport { readAuthRequests, updateProfileStore } from \"./state.js\";\n\nexport { evaluateAuthority } from \"./authority.js\";\n\nconst THIRTY_DAYS_MS = 30 * 24 * 60 * 60 * 1_000;\n\n/** The shared wire schema for SDK PermissionEntry values, including signed ReCap caveats. */\nexport const PermissionEntrySchema = z.object({\n service: z.string().min(1),\n space: z.string().min(1).optional(),\n path: z.string(),\n actions: z.array(z.string().min(1)).min(1),\n caveats: z.array(z.record(z.unknown())).optional(),\n skipPrefix: z.boolean().optional(),\n expiry: z.string().min(1).optional(),\n description: z.string().optional(),\n}).strict();\n\nconst CommandSchema = z.object({\n argv: z.array(z.string()).min(1),\n cwd: z.string().min(1),\n}).strict();\n\nconst LegacyPermissionRequestArtifactSchema = z.object({\n kind: z.literal(\"tinycloud.auth.request\"),\n version: z.literal(1),\n requestId: z.string(),\n sessionDid: z.string().min(1),\n requested: z.array(PermissionEntrySchema),\n requestedExpiry: z.union([z.string(), z.number().finite()]).optional(),\n}).strict();\n\nconst PortableDelegationSchema: z.ZodType<Record<string, unknown>> = z.lazy(() => z.object({\n cid: z.string().min(1),\n spaceId: z.string().min(1),\n path: z.string(),\n actions: z.array(z.string().min(1)).min(1),\n delegateDID: z.string().min(1),\n ownerAddress: z.string().min(1),\n chainId: z.number().int().positive(),\n expiry: z.union([z.string().datetime({ offset: true }), z.date()]),\n delegationHeader: z.object({ Authorization: z.string().min(1) }).strict(),\n host: z.string().min(1).optional(),\n disableSubDelegation: z.boolean().optional(),\n publicDelegation: PortableDelegationSchema.optional(),\n resources: z.array(PermissionEntrySchema).optional(),\n}).passthrough());\n\n/** Canonical wire-format v1 request retained for CLI and OpenKey compatibility. */\nexport const PermissionRequestArtifactSchema = z.object({\n kind: z.literal(\"tinycloud.auth.request\"),\n version: z.literal(1),\n requestId: z.string().min(1),\n createdAt: z.string().datetime({ offset: true }),\n profile: z.string().min(1),\n posture: z.enum([\"owner-openkey\", \"delegate-session\", \"local-owner-key\", \"unauthenticated\"]),\n operatorType: z.enum([\"human\", \"agent\"]),\n host: z.string().min(1),\n sessionDid: z.string().min(1),\n ownerDid: z.string().min(1).optional(),\n spaceId: z.string().min(1).optional(),\n requestedExpiry: z.union([z.string().min(1), z.number().finite()]).optional(),\n requested: z.array(PermissionEntrySchema),\n command: CommandSchema.optional(),\n}).strict();\n\n/** Canonical structured v1 delegation import accepted by the auth operation. */\nexport const DelegationImportArtifactSchema = z.object({\n kind: z.literal(\"tinycloud.auth.delegation\"),\n version: z.literal(1),\n requestId: z.string().min(1),\n delegationCid: z.string().min(1).optional(),\n delegation: PortableDelegationSchema,\n // This field is presentation metadata only. A later import operation obtains\n // effective permissions from validated, installed delegation authority.\n permissions: z.array(PermissionEntrySchema).optional(),\n expiry: z.string().datetime({ offset: true }).optional(),\n prompted: z.boolean().optional(),\n}).strict();\n\n/**\n * The stored request an imported delegation was checked against, persisted\n * with the delegation. Its fields are exactly the request artifact's, so\n * replay accepts every binding import can write.\n */\nexport const DelegationRequestBindingSchema = PermissionRequestArtifactSchema\n .pick({ requestId: true, requested: true })\n .strict();\nexport type DelegationRequestBinding = z.infer<typeof DelegationRequestBindingSchema>;\n\nexport type PermissionRequestArtifact = z.infer<typeof PermissionRequestArtifactSchema>;\nexport type DelegationImportArtifact = z.infer<typeof DelegationImportArtifactSchema>;\n/** The minimal public node-sdk request shape accepted by the legacy reader. */\nexport type LegacyPermissionRequestArtifact = z.infer<typeof LegacyPermissionRequestArtifactSchema>;\nexport type ArtifactClock = () => Date;\nexport type RequestIdSource = () => string;\n\n/** Invocation-owned values used to safely complete a legacy request record. */\nexport interface LegacyRequestContext {\n readonly profile: string;\n readonly host: string;\n readonly sessionDid: string;\n readonly posture: TinyCloudPosture;\n readonly operatorType: OperationOperatorType;\n readonly ownerDid?: string;\n readonly spaceId?: string;\n readonly now?: Date;\n}\n\nexport interface PermissionRequestIdentityInput {\n readonly profile: string;\n readonly sessionDid: string;\n readonly host: string;\n readonly missing: readonly PermissionEntry[];\n}\n\nexport interface BuildPermissionRequestArtifactInput extends PermissionRequestIdentityInput {\n readonly posture: TinyCloudPosture;\n readonly operatorType: OperationOperatorType;\n readonly ownerDid?: string;\n readonly spaceId?: string;\n readonly requestedExpiry?: string | number;\n readonly command?: { readonly argv: readonly string[]; readonly cwd: string };\n readonly now?: ArtifactClock;\n readonly createRequestId?: RequestIdSource;\n}\n\nexport interface CreateOrReusePermissionRequestInput extends BuildPermissionRequestArtifactInput {\n readonly granted: readonly PermissionEntry[];\n /** Owner-aware identity resolver used by registered operation callers. */\n readonly resolveSpace?: OperationSpaceResolver;\n readonly replace?: boolean;\n}\n\nexport type PermissionRequestResolution =\n | Readonly<{ status: \"satisfied\"; reused: false }>\n | Readonly<{ status: \"created\"; reused: boolean; request: PermissionRequestArtifact }>;\n\n/** Parses a request artifact without accepting a newer wire format as v1. */\nexport function validatePermissionRequestArtifact(value: unknown): PermissionRequestArtifact {\n return PermissionRequestArtifactSchema.parse(value);\n}\n\n/** Parses a structured active-session import artifact without accepting a newer wire format as v1. */\nexport function validateDelegationImportArtifact(value: unknown): DelegationImportArtifact {\n return DelegationImportArtifactSchema.parse(value);\n}\n\nexport function isPermissionRequestArtifact(value: unknown): value is PermissionRequestArtifact {\n return PermissionRequestArtifactSchema.safeParse(value).success;\n}\n\nexport function isDelegationImportArtifact(value: unknown): value is DelegationImportArtifact {\n return DelegationImportArtifactSchema.safeParse(value).success;\n}\n\nfunction normalizeLegacyPermissionRequest(\n value: LegacyPermissionRequestArtifact,\n context: LegacyRequestContext,\n): PermissionRequestArtifact {\n if (principal(value.sessionDid) !== principal(context.sessionDid)) {\n throw new TypeError(\"Stored authority request belongs to a different session.\");\n }\n const createdAt = context.now ?? new Date();\n if (Number.isNaN(createdAt.getTime())) {\n throw new TypeError(\"The legacy authority request has no usable creation time.\");\n }\n return {\n kind: \"tinycloud.auth.request\",\n version: 1,\n requestId: value.requestId,\n createdAt: createdAt.toISOString(),\n profile: context.profile,\n posture: context.posture,\n operatorType: context.operatorType,\n host: context.host,\n sessionDid: context.sessionDid,\n ...(context.ownerDid === undefined ? {} : { ownerDid: context.ownerDid }),\n ...(context.spaceId === undefined ? {} : { spaceId: context.spaceId }),\n ...(value.requestedExpiry === undefined ? {} : { requestedExpiry: value.requestedExpiry }),\n requested: canonicalizeCapabilities(value.requested),\n } as PermissionRequestArtifact;\n}\n\nfunction principal(value: string): string {\n return value.split(\"#\", 1)[0]!;\n}\n\n/**\n * Stable exact identity used only for request reuse. It binds a request to its\n * selected profile, active session DID, host, and canonical missing subset.\n */\nexport function createPermissionRequestIdentity(input: PermissionRequestIdentityInput): string {\n return JSON.stringify({\n profile: input.profile,\n sessionDid: input.sessionDid,\n host: input.host,\n missing: canonicalizeCapabilities(input.missing),\n });\n}\n\n/** Builds an on-disk-compatible v1 record. Omitted values stay omitted. */\nexport function buildPermissionRequestArtifact(\n input: BuildPermissionRequestArtifactInput,\n): PermissionRequestArtifact {\n const now = input.now ?? (() => new Date());\n const createdAt = now();\n if (Number.isNaN(createdAt.getTime())) throw new TypeError(\"The artifact clock returned an invalid date.\");\n const requested = canonicalizeCapabilities(input.missing);\n if (requested.length === 0) throw new TypeError(\"Permission requests require at least one missing capability.\");\n\n return validatePermissionRequestArtifact({\n kind: \"tinycloud.auth.request\",\n version: 1,\n requestId: input.createRequestId?.() ?? defaultRequestId(createdAt),\n createdAt: createdAt.toISOString(),\n profile: input.profile,\n posture: input.posture,\n operatorType: input.operatorType,\n host: input.host,\n sessionDid: input.sessionDid,\n ...(input.ownerDid !== undefined ? { ownerDid: input.ownerDid } : {}),\n ...(input.spaceId !== undefined ? { spaceId: input.spaceId } : {}),\n ...(input.requestedExpiry !== undefined ? { requestedExpiry: input.requestedExpiry } : {}),\n requested,\n ...(input.command !== undefined\n ? { command: { argv: [...input.command.argv], cwd: input.command.cwd } }\n : {}),\n });\n}\n\n/** Reads only valid canonical v1 records and refuses malformed stored artifacts. */\nexport async function listPermissionRequests(\n profile: string,\n context?: LegacyRequestContext,\n): Promise<PermissionRequestArtifact[]> {\n const records: PermissionRequestArtifact[] = [];\n for (const value of await readAuthRequests<unknown>(profile)) {\n const canonical = PermissionRequestArtifactSchema.safeParse(value);\n if (canonical.success) {\n if (context !== undefined && principal(canonical.data.sessionDid) !== principal(context.sessionDid)) {\n continue;\n }\n records.push(canonical.data);\n continue;\n }\n const legacy = LegacyPermissionRequestArtifactSchema.safeParse(value);\n if (!legacy.success) throw new TypeError(\"Stored authority request is malformed.\");\n if (context === undefined) {\n throw new TypeError(\"Legacy authority requests require the selected invocation context.\");\n }\n if (principal(legacy.data.sessionDid) !== principal(context.sessionDid)) continue;\n records.push(normalizeLegacyPermissionRequest(legacy.data, context));\n }\n return records;\n}\n\nexport async function findPermissionRequest(\n profile: string,\n requestId: string,\n identity: Pick<PermissionRequestIdentityInput, \"sessionDid\" | \"host\"> &\n Partial<Omit<LegacyRequestContext, \"profile\" | \"host\" | \"sessionDid\">>,\n): Promise<PermissionRequestArtifact | null> {\n const records = await listPermissionRequests(profile, {\n profile,\n host: identity.host,\n sessionDid: identity.sessionDid,\n posture: identity.posture ?? \"delegate-session\",\n operatorType: identity.operatorType ?? \"human\",\n ...(identity.ownerDid === undefined ? {} : { ownerDid: identity.ownerDid }),\n ...(identity.spaceId === undefined ? {} : { spaceId: identity.spaceId }),\n });\n return records.find((record) =>\n record.requestId === requestId &&\n record.sessionDid === identity.sessionDid &&\n record.host === identity.host,\n ) ?? null;\n}\n\n/**\n * Atomically returns the newest identical unresolved request, or writes a new\n * v1 record. Retention, stale-session pruning, covered-record pruning, and\n * partial-grant supersession all happen in the same existing profile lock.\n */\nexport async function createOrReusePermissionRequest(\n input: CreateOrReusePermissionRequestInput,\n): Promise<PermissionRequestResolution> {\n // Registered operations already computed this exact missing set during\n // canonical preflight. Do not recompute it with the legacy global matcher:\n // that matcher intentionally treats same-named short/full spaces as equal.\n const missing = input.resolveSpace === undefined\n ? evaluateAuthority(input.granted, input.missing).missing\n : canonicalizeCapabilities(input.missing);\n const now = input.now ?? (() => new Date());\n const observedNow = now();\n if (Number.isNaN(observedNow.getTime())) throw new TypeError(\"The artifact clock returned an invalid date.\");\n\n return updateProfileStore<unknown, PermissionRequestResolution>(\n input.profile,\n \"auth-requests\",\n (rawRecords) => {\n const records: PermissionRequestArtifact[] = [];\n for (const value of rawRecords) {\n const canonical = PermissionRequestArtifactSchema.safeParse(value);\n if (canonical.success) {\n if (principal(canonical.data.sessionDid) !== principal(input.sessionDid)) continue;\n records.push(canonical.data);\n continue;\n }\n const legacy = LegacyPermissionRequestArtifactSchema.safeParse(value);\n if (!legacy.success) throw new TypeError(\"Stored authority request is malformed.\");\n if (principal(legacy.data.sessionDid) !== principal(input.sessionDid)) continue;\n records.push(normalizeLegacyPermissionRequest(legacy.data, {\n profile: input.profile,\n host: input.host,\n sessionDid: input.sessionDid,\n posture: input.posture,\n operatorType: input.operatorType,\n ...(input.ownerDid === undefined ? {} : { ownerDid: input.ownerDid }),\n ...(input.spaceId === undefined ? {} : { spaceId: input.spaceId }),\n now: observedNow,\n }));\n }\n const retained = prunePermissionRequests(records, {\n profile: input.profile,\n sessionDid: input.sessionDid,\n granted: input.granted,\n resolveSpace: input.resolveSpace,\n now: observedNow,\n });\n\n if (missing.length === 0) {\n return { records: retained, result: { status: \"satisfied\", reused: false } };\n }\n\n const identity = createPermissionRequestIdentity({ ...input, missing });\n const withoutSuperseded = retained.filter((record) => !isSupersededBy(record, input, missing));\n const equivalent = newestEquivalentRequest(withoutSuperseded, identity);\n if (equivalent !== undefined && input.replace !== true) {\n return { records: withoutSuperseded, result: { status: \"created\", reused: true, request: equivalent } };\n }\n\n const next = withoutSuperseded.filter((record) =>\n input.replace !== true || permissionRequestArtifactIdentity(record) !== identity,\n );\n const request = buildPermissionRequestArtifact({ ...input, missing, now: () => observedNow });\n return {\n records: [...next, request],\n result: { status: \"created\", reused: false, request },\n };\n },\n );\n}\n\nexport interface PrunePermissionRequestsInput {\n readonly profile: string;\n readonly sessionDid: string;\n readonly granted: readonly PermissionEntry[];\n readonly resolveSpace?: OperationSpaceResolver;\n readonly now: Date;\n}\n\n/** Pure retention policy used by the locked persistence operation. */\nexport function prunePermissionRequests(\n records: readonly PermissionRequestArtifact[],\n input: PrunePermissionRequestsInput,\n): PermissionRequestArtifact[] {\n const cutoff = input.now.getTime() - THIRTY_DAYS_MS;\n return records.filter((record) => {\n const createdAt = Date.parse(record.createdAt);\n if (!Number.isFinite(createdAt) || createdAt < cutoff) return false;\n if (record.profile === input.profile && principal(record.sessionDid) !== principal(input.sessionDid)) return false;\n // Empty request arrays are historical CLI records. They carry no\n // authority, but remain readable and are retained byte-for-byte by the\n // shared format-1 writer for compatibility.\n if (record.requested.length === 0) return true;\n return !evaluateArtifactAuthority(\n input.granted,\n record.requested,\n input.resolveSpace,\n ).satisfied;\n });\n}\n\nfunction newestEquivalentRequest(\n records: readonly PermissionRequestArtifact[],\n identity: string,\n): PermissionRequestArtifact | undefined {\n let newest: PermissionRequestArtifact | undefined;\n for (const record of records) {\n if (permissionRequestArtifactIdentity(record) !== identity) continue;\n if (newest === undefined || record.createdAt >= newest.createdAt) newest = record;\n }\n return newest;\n}\n\nfunction isSupersededBy(\n record: PermissionRequestArtifact,\n input: CreateOrReusePermissionRequestInput,\n missing: readonly PermissionEntry[],\n): boolean {\n if (\n record.profile !== input.profile ||\n record.sessionDid !== input.sessionDid ||\n record.host !== input.host\n ) {\n return false;\n }\n const remaining = evaluateArtifactAuthority(\n input.granted,\n record.requested,\n input.resolveSpace,\n ).missing;\n return remaining.length > 0 &&\n permissionRequestArtifactIdentity(record) !==\n createPermissionRequestIdentity({ ...input, missing }) &&\n createPermissionRequestIdentity({ ...input, missing: remaining }) ===\n createPermissionRequestIdentity({ ...input, missing });\n}\n\nfunction permissionRequestArtifactIdentity(record: PermissionRequestArtifact): string {\n return createPermissionRequestIdentity({\n profile: record.profile,\n sessionDid: record.sessionDid,\n host: record.host,\n missing: record.requested,\n });\n}\n\nfunction evaluateArtifactAuthority(\n granted: readonly PermissionEntry[],\n required: readonly PermissionEntry[],\n resolveSpace?: OperationSpaceResolver,\n) {\n return resolveSpace === undefined\n ? evaluateAuthority(granted, required)\n : evaluateOperationAuthority(granted, required, resolveSpace);\n}\n\nfunction defaultRequestId(now: Date): string {\n return `req_${now.getTime().toString(36)}_${randomUUID().replace(/-/g, \"\")}`;\n}\n","import { CLI_NAME } from \"./cli-name.js\";\nimport type { TinyCloudPosture } from \"./contract.js\";\n\n/**\n * What a caller does to recover from an operation error it cannot retry. The\n * single source of truth for recovery commands: operations attach it to error\n * details, and projections render hints from it.\n */\nexport type OperationRecovery =\n | { readonly action: \"sign_in\"; readonly command?: string }\n | {\n readonly action: \"complete_migration\";\n readonly command?: string;\n readonly operationId: typeof MIGRATION_OPERATION_ID;\n };\n\n/**\n * Profile names that may appear in a command. Profile names are any single\n * path segment, so a name outside this token gets a recovery without a\n * `command`: an agent never receives a command built from an unsafe string.\n */\nexport const SAFE_PROFILE_TOKEN = /^[A-Za-z0-9][A-Za-z0-9._-]{0,63}$/;\n\n/** Runs the authenticated runtime on the profile's own restored session, which completes its migration. */\nconst MIGRATION_OPERATION_ID = \"tinycloud.auth.capabilities\";\n\nconst SIGN_IN_TEMPLATES: Readonly<Record<\"local\" | \"openkey\" | \"scoped\", (profile: string) => string>> = {\n local: (profile) => `${CLI_NAME} --profile ${profile} auth login --method local`,\n openkey: (profile) => `${CLI_NAME} --profile ${profile} auth login --method openkey`,\n scoped: (profile) => `${CLI_NAME} --profile ${profile} auth login --method openkey --paste --manifest <manifest.json>`,\n};\n\nconst migrationCommand = (profile: string): string => `${CLI_NAME} --profile ${profile} auth caps`;\n\n/**\n * The persistent login (one that writes a stored session) for a profile of\n * this posture. A delegate profile needs its owner to approve a scoped login.\n */\nexport function signInRecovery(profile: string, posture: TinyCloudPosture): OperationRecovery {\n if (!SAFE_PROFILE_TOKEN.test(profile)) return { action: \"sign_in\" };\n const template = posture === \"local-owner-key\"\n ? SIGN_IN_TEMPLATES.local\n : posture === \"delegate-session\"\n ? SIGN_IN_TEMPLATES.scoped\n : SIGN_IN_TEMPLATES.openkey;\n return { action: \"sign_in\", command: template(profile) };\n}\n\n/** One authenticated command on the profile's own session finishes its one-time binding migration. */\nexport function migrationRecovery(profile: string): OperationRecovery {\n return SAFE_PROFILE_TOKEN.test(profile)\n ? { action: \"complete_migration\", command: migrationCommand(profile), operationId: MIGRATION_OPERATION_ID }\n : { action: \"complete_migration\", operationId: MIGRATION_OPERATION_ID };\n}\n\n/**\n * Exact-shape check used by the error-detail redaction policies: exact keys, a\n * fixed `action`, and a `command` equal to one of the fixed templates for a\n * safe profile token.\n */\nexport function isSafeRecovery(\n value: unknown,\n action: OperationRecovery[\"action\"],\n): value is OperationRecovery {\n if (typeof value !== \"object\" || value === null || Array.isArray(value)) return false;\n const record = value as Record<string, unknown>;\n if (record.action !== action) return false;\n const keys = Object.keys(record);\n const allowed = action === \"sign_in\" ? [\"action\", \"command\"] : [\"action\", \"command\", \"operationId\"];\n if (!keys.every((key) => allowed.includes(key))) return false;\n if (action === \"complete_migration\" && record.operationId !== MIGRATION_OPERATION_ID) return false;\n if (!(\"command\" in record)) return true;\n const command = record.command;\n if (typeof command !== \"string\") return false;\n const profile = /^\\S+ --profile (\\S+) /.exec(command)?.[1];\n if (profile === undefined || !SAFE_PROFILE_TOKEN.test(profile)) return false;\n return action === \"sign_in\"\n ? Object.values(SIGN_IN_TEMPLATES).some((template) => template(profile) === command)\n : migrationCommand(profile) === command;\n}\n","// Namespace-shaped so modules that bundle this one stay compatible with\n// lightweight node-sdk test doubles.\nimport * as nodeSdk from \"@tinycloud/node-sdk\";\n\nimport { OperationInvocationError, operationError } from \"./errors.js\";\n\nconst DEFAULT_SECRETS_SPACE = \"secrets\";\nconst SECRET_NAME_RE = /^[A-Z][A-Z0-9_]*$/;\nconst RESERVED_SECRET_SCOPES = new Set([\"default\", \"global\"]);\n\nexport interface SecretsGetInput {\n readonly name: string;\n readonly scope?: string;\n readonly space?: string;\n}\n\nexport interface SecretReference {\n readonly name: string;\n readonly scope?: string;\n readonly space: string;\n readonly vaultKey: string;\n readonly permissionPath: string;\n}\n\nexport function normalizeSecretsGetInput(\n input: SecretsGetInput,\n): SecretsGetInput {\n const reference = resolveSecretReference(input);\n return {\n name: reference.name,\n ...(reference.scope === undefined ? {} : { scope: reference.scope }),\n // Omission is meaningful: the CLI's default is the literal `secrets`\n // space, while an explicitly supplied short name is resolved later against\n // the authenticated identity.\n ...(input.space === undefined ? {} : { space: reference.space }),\n };\n}\n\n/** Resolves the one canonical backing key/path used by planning and setup. */\nexport function resolveSecretReference(\n input: SecretsGetInput,\n): SecretReference {\n const name = input.name.trim();\n if (!SECRET_NAME_RE.test(name)) {\n throw invalidSecretInput();\n }\n\n const scope = canonicalizeSecretScope(input.scope);\n const space = normalizeSecretsSpace(input.space);\n const vaultKey = scope === undefined\n ? `secrets/${name}`\n : `secrets/scoped/${scope}/${name}`;\n\n return {\n name,\n ...(scope === undefined ? {} : { scope }),\n space,\n vaultKey,\n permissionPath: `vault/${vaultKey}`,\n };\n}\n\n/**\n * Resolve the operation's space against the authenticated identity. The\n * public short-name contract remains `secrets`, while an authenticated\n * operation uses its owner-exact URI for the actual read boundary.\n */\nexport function resolveSecretReferenceForOperation(\n input: SecretsGetInput,\n node: unknown,\n ownerSpace?: string,\n): SecretReference {\n const reference = resolveSecretReference(input);\n if (input.space === undefined && ownerSpace === undefined) return reference;\n return {\n ...reference,\n space: resolveOperationSpace(reference.space, node, ownerSpace),\n };\n}\n\n/** Creates the one owner-aware resolver shared by operation matching paths. */\nexport function operationSpaceResolver(\n node: unknown,\n ownerSpace?: string,\n): (space: string) => string {\n return (space) => resolveOperationSpace(space, node, ownerSpace);\n}\n\nfunction resolveOperationSpace(space: string, node: unknown, ownerSpace?: string): string {\n if (space.startsWith(\"tinycloud:\")) return canonicalizeSpaceUri(space);\n\n const ownerSpaceId = ownerSpace === undefined ? undefined : ownedSpaceForName(ownerSpace, space);\n if (ownerSpaceId !== undefined) return canonicalizeSpaceUri(ownerSpaceId);\n\n const candidate = node as {\n resolveOwnedSpace?: (spaceName: string) => unknown;\n spaceId?: unknown;\n address?: unknown;\n did?: unknown;\n };\n try {\n const resolved = candidate.resolveOwnedSpace?.(space);\n if (typeof resolved === \"string\" && resolved.length > 0) {\n return canonicalizeSpaceUri(resolved);\n }\n } catch {\n // Continue to the identity-derived resolver below.\n }\n\n // A restored delegate session carries the owner's primary space even though\n // node.did is the delegate/session principal. This is the same owner\n // identity used by the CLI space resolver.\n if (typeof candidate.spaceId === \"string\") {\n const resolved = ownedSpaceForName(candidate.spaceId, space);\n if (resolved !== undefined) return canonicalizeSpaceUri(resolved);\n }\n\n const did = typeof candidate.did === \"string\" ? candidate.did : undefined;\n const pkh = did === undefined ? null : nodeSdk.parsePkhDid(did);\n if (pkh === null) {\n throw invalidSecretInput();\n }\n return nodeSdk.makePkhSpaceId(pkh.address, pkh.chainId, space);\n}\n\nfunction ownedSpaceForName(ownerSpace: string, name: string): string | undefined {\n const parsed = nodeSdk.parseSpaceUri(ownerSpace);\n if (parsed === null) return undefined;\n try {\n const pkh = nodeSdk.parsePkhDid(parsed.owner);\n return pkh === null ? undefined : nodeSdk.makePkhSpaceId(pkh.address, pkh.chainId, name);\n } catch {\n return undefined;\n }\n}\n\nfunction canonicalizeSpaceUri(space: string): string {\n const parts = space.split(\":\");\n const name = parts.at(-1);\n if (parts.length < 3 || name === undefined || name === \"\") {\n throw invalidSecretInput();\n }\n // EIP-155 address casing is interchangeable only in the repository's\n // anchored PKH space form. Other TinyCloud space/DID syntaxes are opaque\n // method-specific identifiers and must remain byte-for-byte unchanged.\n const pkh = space.match(\n /^tinycloud:pkh:eip155:(\\d+):(0x[0-9a-fA-F]{40}):([^:]+)$/,\n );\n if (pkh !== null) {\n return `tinycloud:pkh:eip155:${pkh[1]}:${nodeSdk.canonicalizeAddress(pkh[2]!).toLowerCase()}:${pkh[3]}`;\n }\n return space;\n}\n\n/**\n * The operations scope canonicalizer: trims, lowercases and folds every other\n * character to `-`. Empty means unscoped; `default`/`global` are refused.\n */\nexport function canonicalizeSecretScope(scope: string | undefined): string | undefined {\n if (scope === undefined || scope === \"\") return undefined;\n\n const trimmed = scope.trim();\n if (trimmed === \"\") throw invalidSecretInput();\n\n const canonical = trimmed\n .toLowerCase()\n .replace(/[^a-z0-9-]/g, \"-\")\n .replace(/-+/g, \"-\")\n .replace(/^-|-$/g, \"\");\n if (canonical === \"\" || RESERVED_SECRET_SCOPES.has(canonical)) {\n throw invalidSecretInput();\n }\n return canonical;\n}\n\nfunction normalizeSecretsSpace(space: string | undefined): string {\n if (space === undefined || space === \"\") return DEFAULT_SECRETS_SPACE;\n\n if (space.startsWith(\"tinycloud:\")) {\n const parts = space.split(\":\");\n if (parts.length < 3 || parts.at(-1) === undefined || parts.at(-1) === \"\") {\n throw invalidSecretInput();\n }\n return `tinycloud:${parts.slice(1, -1).join(\":\")}:${parts.at(-1)}`;\n }\n\n if (!/^[A-Za-z0-9_-]+$/.test(space)) throw invalidSecretInput();\n return space;\n}\n\nfunction invalidSecretInput(): OperationInvocationError {\n return new OperationInvocationError(\n operationError(\"INPUT_INVALID\", \"The secret reference is invalid.\"),\n );\n}\n","export { nodeRevocationOf } from \"./kv-failure.js\";\nexport type { NodeRevocation } from \"./kv-failure.js\";\nexport { operationRetryDescriptor } from \"./invoke.js\";\n","import { createHash } from \"node:crypto\";\nimport { isAbsolute } from \"node:path\";\n\nimport { CLI_NAME } from \"./cli-name.js\";\nimport {\n makePkhSpaceId,\n parsePkhDid,\n parseSpaceUri,\n type PermissionEntry,\n} from \"@tinycloud/node-sdk\";\nimport { jcsCanonicalize } from \"@tinycloud/sdk-core/policy\";\nimport type { ZodError } from \"zod\";\n\nimport {\n type ApprovalAction,\n type CapabilityRequirement,\n type InvocationTarget,\n type OperationContextSummary,\n type OperationDefinition,\n type OperationRef,\n type OperationResult,\n type RetryDescriptor,\n type OperationRuntimeRequirement,\n type RuntimeOperationContext,\n} from \"./contract.js\";\nimport {\n internalOperationError,\n operationError,\n sanitizeOperationError,\n sanitizeThrownOperationError,\n} from \"./errors.js\";\nimport {\n canonicalizeOperationCapabilities,\n evaluateOperationAuthority,\n isExactCapabilityMemberSubset,\n validateExactCapabilities,\n} from \"./authority.js\";\nimport {\n createOrReusePermissionRequest,\n buildPermissionRequestArtifact,\n type PermissionRequestArtifact,\n} from \"./artifacts.js\";\nimport { redactOperationError } from \"./redaction.js\";\nimport { safeOriginHost } from \"./safe-values.js\";\nimport { withTinyCloudStateRoot } from \"./state.js\";\nimport type { InvocationRuntimeResolution } from \"./runtime.js\";\nimport {\n operationSpaceResolver,\n resolveSecretReferenceForOperation,\n} from \"./secrets.js\";\n\n/** Value-free counters; the caller owns this optional diagnostic sink. */\nexport interface OperationMetrics {\n replayedGrants: number;\n}\n\n/** The sole projection-facing execution API. */\nexport async function invokeOperation(\n operationId: string,\n operationVersion: number,\n invocationTarget: InvocationTarget,\n unknownInput: unknown,\n metrics?: OperationMetrics,\n): Promise<OperationResult<unknown>> {\n if (metrics !== undefined) metrics.replayedGrants = 0;\n const target = normalizeInvocationTarget(invocationTarget);\n if (target === undefined) {\n return errorResult(\n { operationId, operationVersion },\n unresolvedContextSummary(),\n operationError(\"INPUT_INVALID\", \"The invocation target is invalid.\"),\n );\n }\n const run = withTinyCloudStateRoot(target.stateRoot, async () => {\n const prepared = await prepareInvocation(\n operationId,\n operationVersion,\n target,\n unknownInput,\n );\n if (isPreparedError(prepared)) return prepared.result;\n if (metrics !== undefined) {\n metrics.replayedGrants = prepared.runtimeResolution.context.runtime?.replayedGrants ?? 0;\n }\n return withRuntimeWarnings(await executePreparedInvocation(prepared), prepared);\n });\n // Lazy like prepareInvocation's lookup: the packed root entrypoint stays\n // free of registry/SDK initialization until a call is made.\n const { lookupOperation } = await import(\"./registry.js\");\n const lookup = lookupOperation(operationId, operationVersion);\n const deadlineMs = lookup.status === \"found\" ? lookup.definition.deadlineMs : undefined;\n if (deadlineMs === undefined) return run;\n return withResponseDeadline(run, deadlineMs, () => errorResult(\n { operationId, operationVersion },\n unresolvedContextSummary(target),\n operationError(\n \"NODE_UNREACHABLE\",\n `The operation did not finish within ${deadlineMs / 1000} s.`,\n { retryable: true },\n ),\n ));\n}\n\n/**\n * Returns `run`'s result, or the deadline result once `deadlineMs` passes.\n * The abandoned run is never cancelled: it keeps running to completion, and\n * its eventual rejection is absorbed so nothing reports it as unhandled.\n */\nasync function withResponseDeadline(\n run: Promise<OperationResult<unknown>>,\n deadlineMs: number,\n expired: () => OperationResult<unknown>,\n): Promise<OperationResult<unknown>> {\n run.catch(() => {});\n let timer: NodeJS.Timeout | undefined;\n const deadline = new Promise<OperationResult<unknown>>((resolve) => {\n timer = setTimeout(() => resolve(expired()), deadlineMs);\n timer.unref();\n });\n try {\n return await Promise.race([run, deadline]);\n } finally {\n clearTimeout(timer);\n }\n}\n\n/**\n * CLI-only local-owner acquisition path. The runtime is deliberately created\n * and retained inside operations so a grant is installed on the exact session\n * that performs the one retry. The explicit key remains a non-persisted target\n * value and no runtime object crosses the operations boundary.\n */\nexport async function invokeOperationWithLocalAuthorityRetry(\n operationId: string,\n operationVersion: number,\n invocationTarget: InvocationTarget,\n unknownInput: unknown,\n): Promise<OperationResult<unknown>> {\n const target = normalizeInvocationTarget(invocationTarget);\n if (target === undefined) {\n return errorResult(\n { operationId, operationVersion },\n unresolvedContextSummary(),\n operationError(\"INPUT_INVALID\", \"The invocation target is invalid.\"),\n );\n }\n if (typeof target.privateKey !== \"string\" || target.privateKey.length === 0) {\n return errorResult(\n { operationId, operationVersion },\n unresolvedContextSummary(target),\n operationError(\n \"PROFILE_POSTURE_NOT_ALLOWED\",\n \"Local authority retry requires an explicit private key.\",\n ),\n );\n }\n\n return withTinyCloudStateRoot(target.stateRoot, () =>\n invokeLocalAuthorityRetry(\n operationId,\n operationVersion,\n target,\n unknownInput,\n )\n );\n}\n\nasync function invokeLocalAuthorityRetry(\n operationId: string,\n operationVersion: number,\n target: InvocationTarget,\n unknownInput: unknown,\n): Promise<OperationResult<unknown>> {\n const prepared = await prepareInvocation(\n operationId,\n operationVersion,\n target,\n unknownInput,\n );\n if (isPreparedError(prepared)) return prepared.result;\n return withRuntimeWarnings(await retryWithLocalAuthority(prepared), prepared);\n}\n\nasync function retryWithLocalAuthority(\n prepared: Exclude<PreparedInvocation, { status: \"error\" }>,\n): Promise<OperationResult<unknown>> {\n if (\n prepared.operation.operationId !== \"tinycloud.secrets.get\" ||\n prepared.operation.operationVersion !== 1\n ) {\n return errorResult(\n prepared.operation,\n prepared.runtimeResolution.context.summary,\n operationError(\n \"OPERATION_NOT_FOUND\",\n \"The local CLI authority helper supports only tinycloud.secrets.get@1.\",\n ),\n );\n }\n\n const runtimeContext = prepared.runtimeResolution.context;\n if (runtimeContext.runtime === undefined) return internalOperationErrorResult(prepared);\n const operationContext = runtimeContext as RuntimeOperationContext;\n let planned: CapabilityRequirement[] | undefined;\n try {\n planned = exactCapabilities(await prepared.definition.authority(\n operationContext,\n prepared.parsedInput.data,\n ));\n } catch {\n return errorResult(\n prepared.operation,\n operationContext.summary,\n operationError(\n \"PERMISSION_HINT_INVALID\",\n \"The local CLI authority plan is invalid.\",\n ),\n );\n }\n const planStatus = planned === undefined\n ? \"invalid\"\n : isExactLocalSecretsGetPlan(prepared, planned, operationContext);\n if (planStatus === \"ineligible\") {\n return errorResult(\n prepared.operation,\n operationContext.summary,\n operationError(\n \"PROFILE_POSTURE_NOT_ALLOWED\",\n \"The explicit private key cannot authorize the requested owner space.\",\n ),\n );\n }\n if (planStatus !== \"valid\") {\n return errorResult(\n prepared.operation,\n operationContext.summary,\n operationError(\n \"PERMISSION_HINT_INVALID\",\n \"The local CLI authority plan is invalid.\",\n ),\n );\n }\n\n const first = await executePreparedInvocation(prepared, { deferAuthorityRequest: true });\n if (first.status !== \"authority_required\") {\n return first;\n }\n if (\n operationContext.summary.posture !== \"local-owner-key\" ||\n !requirementsBelongToAuthenticatedOwner(\n first.missing as unknown as PermissionEntry[],\n operationContext.summary.space,\n )\n ) {\n return localAuthorityFailure(prepared);\n }\n\n try {\n const grantRuntimePermissions = (operationContext.runtime.node as {\n grantRuntimePermissions?: (\n permissions: PermissionEntry[],\n ) => Promise<unknown>;\n }).grantRuntimePermissions;\n if (typeof grantRuntimePermissions !== \"function\") {\n return localAuthorityFailure(prepared);\n }\n await grantRuntimePermissions.call(\n operationContext.runtime.node,\n first.missing as unknown as PermissionEntry[],\n );\n\n const resolveSpace = operationSpaceResolver(\n operationContext.runtime.node,\n operationContext.summary.space,\n );\n const refreshedGranted = refreshLiveRuntimeAuthority(\n operationContext.runtime.node,\n first.missing as unknown as PermissionEntry[],\n planned as unknown as PermissionEntry[],\n resolveSpace,\n );\n if (refreshedGranted === undefined) {\n return localAuthorityFailure(prepared);\n }\n const retryContext: RuntimeOperationContext = {\n ...operationContext,\n runtime: {\n ...operationContext.runtime,\n granted: refreshedGranted,\n },\n };\n const retry = await executePreparedInvocation({\n ...prepared,\n runtimeResolution: { ok: true, context: retryContext },\n }, { deferAuthorityRequest: true });\n return retry.status === \"authority_required\"\n ? localAuthorityFailure(prepared)\n : retry;\n } catch {\n return localAuthorityFailure(prepared);\n }\n}\n\n/** Diagnostics from building the runtime belong to the invocation's result. */\nfunction withRuntimeWarnings(\n result: OperationResult<unknown>,\n prepared: Exclude<PreparedInvocation, { status: \"error\" }>,\n): OperationResult<unknown> {\n const warnings = prepared.runtimeResolution.context.runtime?.warnings;\n return warnings === undefined || warnings.length === 0 ? result : { ...result, warnings: [...warnings] };\n}\n\ntype PreparedInvocation = Readonly<{\n operation: OperationRef;\n definition: OperationDefinition<any, any>;\n parsedInput: { data: any };\n runtimeResolution: Extract<InvocationRuntimeResolution, { ok: true }>;\n}> | Readonly<{\n status: \"error\";\n result: OperationResult<never>;\n}>;\n\nfunction isPreparedError(\n prepared: PreparedInvocation,\n): prepared is Extract<PreparedInvocation, { status: \"error\" }> {\n return \"status\" in prepared;\n}\n\nasync function prepareInvocation(\n operationId: string,\n operationVersion: number,\n invocationTarget: InvocationTarget,\n unknownInput: unknown,\n): Promise<PreparedInvocation> {\n const operation: OperationRef = { operationId, operationVersion };\n const target = normalizeInvocationTarget(invocationTarget);\n if (target === undefined) {\n return { status: \"error\", result: errorResult(\n operation,\n unresolvedContextSummary(),\n operationError(\"INPUT_INVALID\", \"The invocation target is invalid.\"),\n ) };\n }\n // Load internal registry material only when invoking. This keeps the packed\n // root entrypoint free of auth/runtime SDK initialization until a call is\n // made, while preserving registry resolution before input or profile access.\n const { lookupOperation } = await import(\"./registry.js\");\n const lookup = lookupOperation(operationId, operationVersion);\n\n // Registry resolution is deliberately complete before any input parsing or\n // profile access. An unrecognized operation must not cause an invocation to\n // consult profile state.\n if (lookup.status === \"operation_not_found\") {\n return { status: \"error\", result: errorResult(\n operation,\n unresolvedContextSummary(target),\n operationError(\n \"OPERATION_NOT_FOUND\",\n \"The requested operation is not registered.\",\n ),\n ) };\n }\n if (lookup.status === \"operation_version_unsupported\") {\n return { status: \"error\", result: errorResult(\n operation,\n unresolvedContextSummary(target),\n operationError(\n \"OPERATION_VERSION_UNSUPPORTED\",\n \"The requested operation version is not supported.\",\n { details: { supportedVersions: lookup.supportedVersions } },\n ),\n ) };\n }\n\n const definition = lookup.definition;\n const parsedInput = definition.input.safeParse(unknownInput);\n if (!parsedInput.success) {\n return { status: \"error\", result: errorResult(\n operation,\n unresolvedContextSummary(target),\n operationError(\n definition.invalidInputErrorCode ?? \"INPUT_INVALID\",\n \"The operation input is invalid.\",\n {\n details: zodIssueDetails(parsedInput.error),\n },\n ),\n ) };\n }\n\n // Only a registered operation with valid input may inspect the pinned\n // profile. Inspection is deliberately node-free, so owner opt-in and status\n // availability are decided before any authentication or external effect.\n const inspectionResolution = await resolveRuntime(target, \"inspection\");\n if (!inspectionResolution.ok) {\n return { status: \"error\", result: errorResult(\n operation,\n inspectionResolution.context,\n inspectionResolution.error,\n ) };\n }\n\n if (\n target.privateKey === undefined &&\n !definition.postures.includes(inspectionResolution.context.summary.posture)\n ) {\n return { status: \"error\", result: errorResult(\n operation,\n inspectionResolution.context.summary,\n operationError(\n \"PROFILE_POSTURE_NOT_ALLOWED\",\n \"The active profile posture cannot execute this operation.\",\n ),\n ) };\n }\n if (\n target.privateKey === undefined &&\n requiresRuntime(definition.runtime) &&\n isOwnerPosture(inspectionResolution.context.summary.posture) &&\n target.allowOwnerProfile !== true\n ) {\n return { status: \"error\", result: errorResult(\n operation,\n inspectionResolution.context.summary,\n operationError(\n \"PROFILE_OWNER_OPT_IN_REQUIRED\",\n \"Owner-profile execution requires explicit opt-in.\",\n ),\n ) };\n }\n\n const runtimeResolution =\n definition.runtime === \"inspection\"\n ? inspectionResolution\n : await resolveRuntime(target, definition.runtime);\n if (!runtimeResolution.ok) {\n // Runtime-preparation errors share the execution errors' detail allowlist.\n return { status: \"error\", result: errorResult(\n operation,\n runtimeResolution.context,\n redactOperationError(definition, runtimeResolution.error),\n ) };\n }\n if (requiresRuntime(definition.runtime)) {\n const actualPosture = runtimeResolution.context.summary.posture;\n if (!definition.postures.includes(actualPosture)) {\n return { status: \"error\", result: errorResult(\n operation,\n runtimeResolution.context.summary,\n operationError(\n \"PROFILE_POSTURE_NOT_ALLOWED\",\n \"The authenticated profile posture cannot execute this operation.\",\n ),\n ) };\n }\n if (isOwnerPosture(actualPosture) && target.allowOwnerProfile !== true) {\n return { status: \"error\", result: errorResult(\n operation,\n runtimeResolution.context.summary,\n operationError(\n \"PROFILE_OWNER_OPT_IN_REQUIRED\",\n \"Owner-profile execution requires explicit opt-in.\",\n ),\n ) };\n }\n }\n return {\n operation,\n definition,\n parsedInput,\n runtimeResolution,\n };\n}\n\nasync function executePreparedInvocation(\n prepared: Exclude<PreparedInvocation, { status: \"error\" }>,\n options: Readonly<{ deferAuthorityRequest?: boolean }> = {},\n): Promise<OperationResult<unknown>> {\n const { operation, definition, parsedInput, runtimeResolution } = prepared;\n const runtimeContext =\n runtimeResolution.context.runtime === undefined\n ? undefined\n : (runtimeResolution.context as RuntimeOperationContext);\n const resolveSpace = runtimeContext === undefined\n ? undefined\n : operationSpaceResolver(\n runtimeContext.runtime.node,\n runtimeContext.summary.space,\n );\n\n try {\n const planned = await definition.authority(\n (runtimeContext ?? runtimeResolution.context) as RuntimeOperationContext,\n parsedInput.data,\n );\n const required = exactCapabilities(planned);\n if (required === undefined) {\n return errorResult(\n operation,\n runtimeResolution.context.summary,\n operationError(\n \"PERMISSION_HINT_INVALID\",\n \"The operation returned an invalid permission requirement.\",\n ),\n );\n }\n\n const evaluation = evaluateOperationAuthority(\n (runtimeContext?.runtime.granted ?? []) as unknown as PermissionEntry[],\n required as unknown as PermissionEntry[],\n resolveSpace,\n );\n if (!evaluation.satisfied) {\n if (runtimeContext === undefined) {\n return errorResult(\n operation,\n runtimeResolution.context.summary,\n internalOperationError(),\n );\n }\n if (options.deferAuthorityRequest) {\n try {\n const request = buildPermissionRequestArtifact({\n profile: runtimeContext.summary.profile,\n host: runtimeContext.summary.host,\n sessionDid: runtimeContext.summary.sessionDid!,\n posture: runtimeContext.summary.posture,\n operatorType: runtimeContext.summary.operatorType ?? \"human\",\n ...(runtimeContext.summary.ownerDid === undefined\n ? {}\n : { ownerDid: runtimeContext.summary.ownerDid }),\n ...(runtimeContext.summary.space === undefined\n ? {}\n : { spaceId: runtimeContext.summary.space }),\n missing: evaluation.missing as unknown as PermissionEntry[],\n });\n return authorityRequiredResult(\n operation,\n runtimeResolution.context.summary,\n parsedInput.data,\n request,\n evaluation.missing,\n runtimeContext.runtime.node,\n );\n } catch {\n return errorResult(\n operation,\n runtimeResolution.context.summary,\n internalOperationError(),\n );\n }\n }\n const authority = await persistAuthorityRequest(\n runtimeContext,\n evaluation.missing,\n );\n if (authority.status === \"error\") {\n return errorResult(\n operation,\n runtimeResolution.context.summary,\n authority.error,\n );\n }\n return authorityRequiredResult(\n operation,\n runtimeResolution.context.summary,\n parsedInput.data,\n authority.request,\n authority.missing,\n runtimeContext.runtime.node,\n );\n }\n\n const outcome = await definition.execute(\n runtimeResolution.context,\n parsedInput.data,\n );\n\n switch (outcome.status) {\n case \"ok\": {\n const parsedOutput = definition.output.safeParse(outcome.output);\n if (!parsedOutput.success) {\n return errorResult(\n operation,\n runtimeResolution.context.summary,\n operationError(\n \"OUTPUT_INVALID\",\n \"The operation returned an invalid result.\",\n ),\n );\n }\n return {\n status: \"ok\",\n operation,\n context: runtimeResolution.context.summary,\n output: parsedOutput.data,\n };\n }\n case \"authority_required\": {\n const hinted = exactCapabilities(outcome.missing);\n if (\n hinted === undefined ||\n !isExactCapabilityMemberSubset(\n hinted as unknown as PermissionEntry[],\n required as unknown as PermissionEntry[],\n resolveSpace,\n )\n ) {\n return errorResult(\n operation,\n runtimeResolution.context.summary,\n operationError(\n \"PERMISSION_HINT_INVALID\",\n \"The node returned an invalid permission hint.\",\n ),\n );\n }\n if (runtimeContext === undefined) {\n return errorResult(\n operation,\n runtimeResolution.context.summary,\n internalOperationError(),\n );\n }\n if (options.deferAuthorityRequest) {\n try {\n const request = buildPermissionRequestArtifact({\n profile: runtimeContext.summary.profile,\n host: runtimeContext.summary.host,\n sessionDid: runtimeContext.summary.sessionDid!,\n posture: runtimeContext.summary.posture,\n operatorType: runtimeContext.summary.operatorType ?? \"human\",\n ...(runtimeContext.summary.ownerDid === undefined\n ? {}\n : { ownerDid: runtimeContext.summary.ownerDid }),\n ...(runtimeContext.summary.space === undefined\n ? {}\n : { spaceId: runtimeContext.summary.space }),\n missing: hinted as unknown as PermissionEntry[],\n });\n return authorityRequiredResult(\n operation,\n runtimeResolution.context.summary,\n parsedInput.data,\n request,\n hinted,\n runtimeContext.runtime.node,\n outcome.requiresCallerInput ?? false,\n );\n } catch {\n return errorResult(\n operation,\n runtimeResolution.context.summary,\n internalOperationError(),\n );\n }\n }\n const authority = await persistAuthorityRequest(\n runtimeContext,\n hinted,\n true,\n );\n if (authority.status === \"error\") {\n return errorResult(\n operation,\n runtimeResolution.context.summary,\n authority.error,\n );\n }\n return authorityRequiredResult(\n operation,\n runtimeResolution.context.summary,\n parsedInput.data,\n authority.request,\n authority.missing,\n runtimeContext.runtime.node,\n outcome.requiresCallerInput ?? false,\n );\n }\n case \"setup_required\":\n return {\n status: \"setup_required\",\n operation,\n context: runtimeResolution.context.summary,\n setup: outcome.setup,\n retry: operationRetryDescriptor(\n operation,\n parsedInput.data,\n outcome.requiresCallerInput ?? false,\n ),\n };\n case \"error\":\n return errorResult(\n operation,\n runtimeResolution.context.summary,\n redactOperationError(\n definition,\n sanitizeOperationError(outcome.error),\n ),\n );\n default:\n return errorResult(\n operation,\n runtimeResolution.context.summary,\n internalOperationError(),\n );\n }\n } catch (error) {\n return errorResult(\n operation,\n runtimeResolution.context.summary,\n redactOperationError(definition, sanitizeThrownOperationError(error)),\n );\n }\n}\n\nfunction normalizeInvocationTarget(\n target: unknown,\n): InvocationTarget | undefined {\n if (typeof target !== \"object\" || target === null) return undefined;\n\n try {\n const candidate = target as InvocationTarget;\n const profile = candidate.profile;\n const host = candidate.host;\n const stateRoot = candidate.stateRoot;\n const allowOwnerProfile = candidate.allowOwnerProfile;\n const privateKey = candidate.privateKey;\n\n if (\n (profile !== undefined && typeof profile !== \"string\") ||\n (host !== undefined && typeof host !== \"string\") ||\n (stateRoot !== undefined &&\n (typeof stateRoot !== \"string\" || !isAbsolute(stateRoot))) ||\n (allowOwnerProfile !== undefined &&\n typeof allowOwnerProfile !== \"boolean\") ||\n (privateKey !== undefined && typeof privateKey !== \"string\")\n ) {\n return undefined;\n }\n\n return {\n ...(profile === undefined ? {} : { profile }),\n ...(host === undefined ? {} : { host }),\n ...(stateRoot === undefined ? {} : { stateRoot }),\n ...(allowOwnerProfile === true ? { allowOwnerProfile: true } : {}),\n ...(privateKey === undefined ? {} : { privateKey }),\n };\n } catch {\n return undefined;\n }\n}\n\nasync function resolveRuntime(\n target: InvocationTarget,\n requirement: OperationRuntimeRequirement,\n): Promise<InvocationRuntimeResolution> {\n try {\n // Keep the root package loadable for projections that only inspect its\n // public invocation export. The SDK runtime is needed only once a valid\n // operation reaches runtime construction.\n const { createInvocationRuntime } = await import(\"./runtime.js\");\n return await createInvocationRuntime(target, requirement);\n } catch (error) {\n return {\n ok: false,\n context: unresolvedContextSummary(target),\n error: sanitizeThrownOperationError(error),\n };\n }\n}\n\nfunction unresolvedContextSummary(\n target: InvocationTarget = {},\n): OperationContextSummary {\n return {\n profile: typeof target.profile === \"string\" ? target.profile : \"unresolved\",\n host: safeOriginHost(target.host) ?? \"unresolved\",\n posture: \"unauthenticated\",\n };\n}\n\nfunction errorResult(\n operation: OperationRef,\n context: OperationContextSummary,\n error: ReturnType<typeof sanitizeOperationError>,\n): OperationResult<never> {\n return { status: \"error\", operation, context, error };\n}\n\n/** Build the same canonical retry identity used by operation authority results. */\nexport function operationRetryDescriptor(\n operation: OperationRef,\n input: unknown,\n requiresCallerInput = false,\n): RetryDescriptor {\n return {\n operationId: operation.operationId,\n operationVersion: operation.operationVersion,\n inputDigest: createHash(\"sha256\")\n .update(jcsCanonicalize(input), \"utf8\")\n .digest(\"hex\"),\n requiresCallerInput,\n };\n}\n\ntype PersistedAuthority =\n | Readonly<{\n status: \"ok\";\n request: PermissionRequestArtifact;\n missing: readonly CapabilityRequirement[];\n }>\n | Readonly<{\n status: \"error\";\n error: ReturnType<typeof sanitizeOperationError>;\n }>;\n\nasync function persistAuthorityRequest(\n context: RuntimeOperationContext,\n missing: readonly CapabilityRequirement[],\n force = false,\n): Promise<PersistedAuthority> {\n const sessionDid = context.summary.sessionDid;\n if (sessionDid === undefined) {\n return {\n status: \"error\",\n error: operationError(\n \"SESSION_NOT_FOUND\",\n \"The selected profile does not have an active session.\",\n ),\n };\n }\n try {\n const resolveSpace = operationSpaceResolver(\n context.runtime.node,\n context.summary.space,\n );\n const canonicalMissing = canonicalizeOperationCapabilities(missing as unknown as PermissionEntry[], resolveSpace);\n const canonicalGranted = canonicalizeOperationCapabilities(context.runtime.granted as unknown as PermissionEntry[], resolveSpace);\n const resolution = await createOrReusePermissionRequest({\n profile: context.summary.profile,\n posture: context.summary.posture,\n operatorType: context.summary.operatorType ?? \"human\",\n host: context.summary.host,\n sessionDid,\n ...(context.summary.ownerDid === undefined\n ? {}\n : { ownerDid: context.summary.ownerDid }),\n ...(context.summary.space === undefined\n ? {}\n : { spaceId: context.summary.space }),\n missing: canonicalMissing as unknown as PermissionEntry[],\n granted: (force\n ? []\n : canonicalGranted) as unknown as PermissionEntry[],\n resolveSpace,\n });\n if (resolution.status !== \"created\") {\n return {\n status: \"error\",\n error: operationError(\n \"INTERNAL_ERROR\",\n \"The authority request could not be created.\",\n ),\n };\n }\n return {\n status: \"ok\",\n request: resolution.request,\n missing: canonicalMissing as CapabilityRequirement[],\n };\n } catch {\n return {\n status: \"error\",\n error: operationError(\n \"INTERNAL_ERROR\",\n \"The authority request could not be created.\",\n ),\n };\n }\n}\n\nfunction authorityRequiredResult(\n operation: OperationRef,\n context: OperationContextSummary,\n input: unknown,\n request: PermissionRequestArtifact,\n missing: readonly CapabilityRequirement[],\n node: unknown,\n requiresCallerInput = false,\n): OperationResult<never> {\n return {\n status: \"authority_required\",\n operation,\n context,\n missing,\n request,\n approval: approvalAction(request, node),\n retry: operationRetryDescriptor(operation, input, requiresCallerInput),\n };\n}\n\nfunction approvalAction(\n request: PermissionRequestArtifact,\n node: unknown,\n): ApprovalAction {\n const maybeNode = node as {\n getOpenKeyApprovalUrl?: (artifact: PermissionRequestArtifact) => unknown;\n };\n let url: string | undefined;\n try {\n const candidate = maybeNode.getOpenKeyApprovalUrl?.(request);\n if (typeof candidate === \"string\" && candidate.length > 0) url = candidate;\n } catch {\n // The stored request remains the source of truth when a runtime does not\n // expose a canonical OpenKey URL.\n }\n return {\n kind: \"openkey\",\n requestId: request.requestId,\n ...(url === undefined ? {} : { url }),\n fallback: `${CLI_NAME} auth grant <request-artifact>`,\n };\n}\n\nfunction exactCapabilities(\n value: unknown,\n): CapabilityRequirement[] | undefined {\n return validateExactCapabilities(value) as\n | CapabilityRequirement[]\n | undefined;\n}\n\nfunction requirementsBelongToAuthenticatedOwner(\n requirements: readonly PermissionEntry[],\n authenticatedSpace: string | undefined,\n): boolean {\n const authenticatedOwner = canonicalPkhOwner(authenticatedSpace);\n if (authenticatedOwner === undefined) return false;\n return requirements.every((requirement) => {\n if (requirement.service === \"tinycloud.encryption\") {\n return requirement.space === undefined;\n }\n const requirementOwner = canonicalPkhOwner(requirement.space);\n return requirement.service === \"tinycloud.kv\" && requirementOwner === authenticatedOwner;\n });\n}\n\nfunction canonicalPkhOwner(space: string | undefined): string | undefined {\n if (typeof space !== \"string\") return undefined;\n const parsedSpace = parseSpaceUri(space);\n if (parsedSpace === null) return undefined;\n try {\n const pkh = parsePkhDid(parsedSpace.owner);\n return pkh === null\n ? undefined\n : parseSpaceUri(makePkhSpaceId(pkh.address, pkh.chainId, parsedSpace.name))?.owner;\n } catch {\n return undefined;\n }\n}\n\nfunction isExactLocalSecretsGetPlan(\n prepared: Exclude<PreparedInvocation, { status: \"error\" }>,\n planned: readonly CapabilityRequirement[],\n context: RuntimeOperationContext,\n): \"valid\" | \"ineligible\" | \"invalid\" {\n if (\n prepared.definition.id !== \"tinycloud.secrets.get\" ||\n prepared.definition.version !== 1 ||\n JSON.stringify(prepared.definition.effects) !== JSON.stringify([\"read\", \"local_write\"])\n ) return \"invalid\";\n\n const resolveSpace = operationSpaceResolver(context.runtime.node, context.summary.space);\n let reference;\n try {\n reference = resolveSecretReferenceForOperation(\n prepared.parsedInput.data,\n context.runtime.node,\n context.summary.space,\n );\n } catch {\n return \"invalid\";\n }\n const canonicalPlan = canonicalizeOperationCapabilities(\n planned as unknown as PermissionEntry[],\n resolveSpace,\n );\n if (canonicalPlan.length !== 2) return \"invalid\";\n const kv = canonicalPlan.find((permission) => permission.service === \"tinycloud.kv\");\n const decrypt = canonicalPlan.find((permission) => permission.service === \"tinycloud.encryption\");\n if (kv === undefined || decrypt === undefined) return \"invalid\";\n const requestedOwner = canonicalPkhOwner(kv.space);\n const authenticatedOwner = canonicalPkhOwner(context.summary.space);\n if (requestedOwner === undefined || authenticatedOwner === undefined || requestedOwner !== authenticatedOwner) {\n return \"ineligible\";\n }\n if (JSON.stringify(kv) !== JSON.stringify({\n service: \"tinycloud.kv\",\n space: resolveSpace(reference.space),\n path: reference.permissionPath,\n actions: [\"tinycloud.kv/get\"],\n })) return \"invalid\";\n if (\n JSON.stringify(decrypt) !== JSON.stringify({\n service: \"tinycloud.encryption\",\n path: decrypt.path,\n actions: [\"tinycloud.encryption/decrypt\"],\n }) ||\n typeof decrypt.path !== \"string\" ||\n !decrypt.path.startsWith(\"urn:tinycloud:encryption:\")\n ) return \"invalid\";\n return \"valid\";\n}\n\nfunction refreshLiveRuntimeAuthority(\n node: unknown,\n requested: readonly PermissionEntry[],\n planned: readonly PermissionEntry[],\n resolveSpace: (space: string) => string,\n): readonly CapabilityRequirement[] | undefined {\n const candidate = node as {\n hasRuntimePermissions?: (permissions: PermissionEntry[]) => unknown;\n getVerifiedSessionCapabilities?: () => unknown;\n getEffectiveRuntimePermissionEntries?: () => unknown;\n };\n if (typeof candidate.hasRuntimePermissions !== \"function\") return undefined;\n if (candidate.hasRuntimePermissions([...requested]) !== true) return undefined;\n if (typeof candidate.getVerifiedSessionCapabilities !== \"function\" ||\n typeof candidate.getEffectiveRuntimePermissionEntries !== \"function\") return undefined;\n\n const base = candidate.getVerifiedSessionCapabilities();\n const activated = candidate.getEffectiveRuntimePermissionEntries();\n if (!Array.isArray(base) || !Array.isArray(activated)) return undefined;\n const exactActivated = validateExactCapabilities(activated);\n if (exactActivated === undefined) return undefined;\n const selectedActivated = exactActivated.filter((permission) =>\n isExactCapabilityMemberSubset([permission], planned, resolveSpace),\n );\n if (!isExactCapabilityMemberSubset(requested, selectedActivated, resolveSpace)) return undefined;\n const combined = canonicalizeOperationCapabilities(\n [...base, ...selectedActivated],\n resolveSpace,\n );\n return evaluateOperationAuthority(combined, planned as unknown as PermissionEntry[], resolveSpace).satisfied\n ? combined\n : undefined;\n}\n\nfunction localAuthorityFailure(\n prepared: Exclude<PreparedInvocation, { status: \"error\" }>,\n): OperationResult<never> {\n return errorResult(\n prepared.operation,\n prepared.runtimeResolution.context.summary,\n operationError(\n \"NODE_ERROR\",\n \"The local owner could not acquire the requested secret permissions.\",\n { retryable: true },\n ),\n );\n}\n\nfunction internalOperationErrorResult(\n prepared: Exclude<PreparedInvocation, { status: \"error\" }>,\n): OperationResult<never> {\n return errorResult(\n prepared.operation,\n prepared.runtimeResolution.context.summary,\n internalOperationError(),\n );\n}\n\nfunction isOwnerPosture(posture: OperationContextSummary[\"posture\"]): boolean {\n return posture === \"owner-openkey\" || posture === \"local-owner-key\";\n}\n\n/** Runtimes that build a TinyCloud node from the profile, and so need the owner and posture gates. */\nfunction requiresRuntime(requirement: OperationRuntimeRequirement): boolean {\n switch (requirement) {\n case \"authenticated\":\n case \"restored\":\n return true;\n case \"inspection\":\n return false;\n }\n}\n\nfunction zodIssueDetails(error: ZodError): Readonly<Record<string, unknown>> {\n return {\n // The canonical error channel carries only schema-owned issue codes.\n // Messages and paths can contain arbitrary input keys and values.\n issues: error.issues.map((issue) => ({ code: issue.code })),\n };\n}\n","import { RUNTIME_DELEGATION_REJECTIONS } from \"@tinycloud/node-sdk\";\n\nimport { DELEGATION_CID_PATTERN } from \"./contract.js\";\nimport type {\n OperationContextSummary,\n OperationDefinition,\n OperationRef,\n} from \"./contract.js\";\nimport type { OperationError, OperationErrorCode } from \"./errors.js\";\nimport { isSafeRecovery } from \"./recovery.js\";\nimport { safeOriginHost, safeSessionDid } from \"./safe-values.js\";\n\nexport const REDACTED_VALUE = \"[REDACTED]\";\nconst DELEGATION_CID = new RegExp(DELEGATION_CID_PATTERN);\n\n\nexport interface SafeOperationDiagnostic {\n readonly operation: OperationRef;\n readonly context: OperationContextSummary;\n readonly input?: unknown;\n readonly output?: unknown;\n readonly error?: OperationError;\n}\n\n/**\n * Return a detached redacted value. A missing pointer is intentionally a no-op\n * so an operation can evolve an optional shape without leaking another field.\n */\nexport function redactOperationValue(\n definition: Pick<OperationDefinition<unknown, unknown>, \"sensitivity\">,\n value: unknown,\n): unknown {\n const clone = cloneValue(value);\n const pointers = [\n ...definition.sensitivity.input,\n ...definition.sensitivity.output,\n ];\n\n let redacted: unknown = clone;\n for (const pointer of pointers) {\n redacted = redactPointer(redacted, pointer);\n }\n return redacted;\n}\n\n/**\n * Diagnostic payloads are a supported non-result channel. They accept raw\n * operation values only through this helper, never an invocation target.\n */\nexport function createSafeOperationDiagnostic(\n definition: Pick<OperationDefinition<unknown, unknown>, \"sensitivity\">,\n diagnostic: Readonly<{\n operation: OperationRef;\n context: OperationContextSummary;\n input?: unknown;\n output?: unknown;\n error?: OperationError;\n }>,\n): SafeOperationDiagnostic {\n return {\n operation: {\n operationId: diagnostic.operation.operationId,\n operationVersion: diagnostic.operation.operationVersion,\n },\n context: {\n profile: diagnostic.context.profile,\n host: diagnostic.context.host,\n posture: diagnostic.context.posture,\n ...(diagnostic.context.operatorType === undefined\n ? {}\n : { operatorType: diagnostic.context.operatorType }),\n ...(diagnostic.context.principalDid === undefined\n ? {}\n : { principalDid: diagnostic.context.principalDid }),\n ...(diagnostic.context.sessionDid === undefined\n ? {}\n : { sessionDid: diagnostic.context.sessionDid }),\n ...(diagnostic.context.ownerDid === undefined\n ? {}\n : { ownerDid: diagnostic.context.ownerDid }),\n ...(diagnostic.context.space === undefined\n ? {}\n : { space: diagnostic.context.space }),\n },\n ...(diagnostic.input === undefined\n ? {}\n : { input: redactOperationValue(definition, diagnostic.input) }),\n ...(diagnostic.output === undefined\n ? {}\n : { output: redactOperationValue(definition, diagnostic.output) }),\n ...(diagnostic.error === undefined\n ? {}\n : { error: redactOperationError(definition, diagnostic.error) }),\n };\n}\n\nexport function redactOperationError(\n _definition: Pick<OperationDefinition<unknown, unknown>, \"sensitivity\">,\n error: OperationError,\n): OperationError {\n const details =\n error.details === undefined\n ? undefined\n : redactSafeErrorDetails(error.code, error.details);\n\n return {\n code: error.code,\n message: error.message,\n retryable: error.retryable,\n ...(details === undefined\n ? {}\n : {\n details: isRecord(details) ? details : { redacted: details },\n }),\n };\n}\n\n/**\n * Error details are a separate, code-owned channel. Only fields with an\n * explicit stable policy can cross the canonical result or diagnostic\n * boundary; arbitrary handler details are dropped rather than redacted by\n * input pointers, which could accidentally preserve a transport secret.\n */\nconst SAFE_ERROR_DETAIL_POLICIES: Readonly<\n Partial<\n Record<\n OperationErrorCode,\n Readonly<Record<string, (value: unknown) => unknown | undefined>>\n >\n >\n> = {\n INPUT_INVALID: {\n issues: (value) =>\n Array.isArray(value) ? safeIssueCodes(value) : undefined,\n reason: (value) =>\n value === \"OWNER_MISMATCH\" || value === \"OWNER_DID_UNKNOWN\" ? value : undefined,\n },\n AUTH_REQUIRED: {\n recovery: (value) => isSafeRecovery(value, \"sign_in\") ? { ...value } : undefined,\n reason: (value) => value === \"explicit_key\" ? value : undefined,\n },\n SESSION_NOT_FOUND: {\n recovery: (value) => isSafeRecovery(value, \"sign_in\") ? { ...value } : undefined,\n },\n NODE_ERROR: {\n reason: (value) => value === \"space_not_hosted\" || value === \"copy_unavailable\" ? value : undefined,\n },\n PROFILE_MIGRATION_REQUIRED: {\n recovery: (value) => isSafeRecovery(value, \"complete_migration\") ? { ...value } : undefined,\n },\n // `secrets.get` not found for an identity no request link serves.\n KV_NOT_FOUND: {\n recoveryKind: (value) =>\n value === \"put_locally\" || value === \"not_requestable\" ? value : undefined,\n names: (value) =>\n Array.isArray(value) && value.length >= 1 && value.length <= 10 &&\n value.every((name) => typeof name === \"string\" && name.length <= 256 && /^[A-Z][A-Z0-9_]*$/.test(name))\n ? [...value]\n : undefined,\n scope: (value) =>\n typeof value === \"string\" && value.length <= 256 && /^[a-z0-9]+(-[a-z0-9]+)*$/.test(value)\n ? value\n : undefined,\n owner: (value) =>\n typeof value === \"string\" && /^did:pkh:eip155:[1-9][0-9]*:0x[0-9a-fA-F]{40}$/.test(value)\n ? value\n : undefined,\n },\n OPERATION_VERSION_UNSUPPORTED: {\n supportedVersions: (value) =>\n Array.isArray(value) && value.every((item) => Number.isInteger(item))\n ? value\n : undefined,\n },\n DELEGATION_AUDIENCE_MISMATCH: {\n expectedSessionDid: safeSessionDid,\n artifactAudience: safeSessionDid,\n },\n DELEGATION_HOST_MISMATCH: {\n expectedHost: safeOriginHost,\n artifactHost: safeOriginHost,\n },\n // `RuntimeDelegationRejectedError.reason` from validated activation.\n DELEGATION_REJECTED: {\n reason: (value) =>\n RUNTIME_DELEGATION_REJECTIONS.some((reason) => reason === value) ? value : undefined,\n },\n STORAGE_QUOTA_EXCEEDED: {\n account: safeStorageAccount,\n },\n};\n\n/** Account-wide totals only; a zero budget explains nothing and is dropped. */\nfunction safeStorageAccount(value: unknown): unknown | undefined {\n if (!isRecord(value)) return undefined;\n const { usedBytes, limitBytes, plan } = value;\n if (\n !Number.isSafeInteger(usedBytes) || (usedBytes as number) < 0 ||\n !Number.isSafeInteger(limitBytes) || (limitBytes as number) <= 0\n ) {\n return undefined;\n }\n return typeof plan === \"string\" && /^[A-Za-z0-9][A-Za-z0-9 _.-]{0,31}$/.test(plan)\n ? { usedBytes, limitBytes, plan }\n : { usedBytes, limitBytes };\n}\n\nfunction redactSafeErrorDetails(\n code: OperationErrorCode,\n details: Readonly<Record<string, unknown>>,\n): Readonly<Record<string, unknown>> | undefined {\n const policy = SAFE_ERROR_DETAIL_POLICIES[code];\n if (policy === undefined) return undefined;\n\n const safeDetails: Record<string, unknown> = {};\n for (const [key, sanitize] of Object.entries(policy)) {\n if (!hasOwn(details, key)) continue;\n const value = sanitize(details[key]);\n if (value !== undefined) safeDetails[key] = value;\n }\n\n if (code === \"AUTH_REQUIRED\") {\n const { reason, revokedCid, ancestorCid } = details;\n if (typeof revokedCid === \"string\" && DELEGATION_CID.exec(revokedCid)?.[0] === revokedCid) {\n if (reason === \"delegation_revoked\") {\n safeDetails.reason = reason;\n safeDetails.revokedCid = revokedCid;\n } else if (reason === \"delegation_ancestor_revoked\" && typeof ancestorCid === \"string\" && DELEGATION_CID.exec(ancestorCid)?.[0] === ancestorCid) {\n safeDetails.reason = reason;\n safeDetails.revokedCid = revokedCid;\n safeDetails.ancestorCid = ancestorCid;\n }\n }\n }\n return Object.keys(safeDetails).length === 0 ? undefined : safeDetails;\n}\n\nfunction safeIssueCodes(\n value: readonly unknown[],\n): readonly Readonly<{ code: string }>[] | undefined {\n const codes = value.map((issue) => {\n if (!isRecord(issue) || typeof issue.code !== \"string\") return undefined;\n return SAFE_ISSUE_CODES.has(issue.code) ? { code: issue.code } : undefined;\n });\n return codes.every(\n (issue): issue is Readonly<{ code: string }> => issue !== undefined,\n )\n ? codes\n : undefined;\n}\n\nconst SAFE_ISSUE_CODES = new Set([\n \"invalid_type\",\n \"invalid_literal\",\n \"custom\",\n \"invalid_union\",\n \"invalid_union_discriminator\",\n \"invalid_enum_value\",\n \"unrecognized_keys\",\n \"invalid_arguments\",\n \"invalid_return_type\",\n \"invalid_date\",\n \"invalid_string\",\n \"too_small\",\n \"too_big\",\n \"invalid_intersection_types\",\n \"not_multiple_of\",\n \"not_finite\",\n]);\n\nfunction redactPointer(value: unknown, pointer: string): unknown {\n if (pointer === \"\") {\n return REDACTED_VALUE;\n }\n if (!pointer.startsWith(\"/\")) {\n return value;\n }\n\n const tokens = pointer.slice(1).split(\"/\").map(unescapePointerToken);\n const parent = getPointerParent(value, tokens);\n if (parent === undefined) {\n return value;\n }\n\n const key = tokens[tokens.length - 1];\n if (key === undefined || !hasOwn(parent, key)) {\n return value;\n }\n\n Reflect.set(parent, key, REDACTED_VALUE);\n return value;\n}\n\nfunction getPointerParent(\n value: unknown,\n tokens: readonly string[],\n): Record<string, unknown> | unknown[] | undefined {\n if (tokens.length === 0) {\n return undefined;\n }\n\n let cursor = value;\n for (const token of tokens.slice(0, -1)) {\n if (!isRecordOrArray(cursor) || !hasOwn(cursor, token)) {\n return undefined;\n }\n cursor = Reflect.get(cursor, token);\n }\n return isRecordOrArray(cursor) ? cursor : undefined;\n}\n\nfunction unescapePointerToken(token: string): string {\n return token.replace(/~1/g, \"/\").replace(/~0/g, \"~\");\n}\n\nfunction cloneValue(value: unknown): unknown {\n if (value === null || typeof value !== \"object\") {\n return value;\n }\n\n if (Array.isArray(value)) {\n return value.map((entry) => cloneValue(entry));\n }\n\n const clone: Record<string, unknown> = {};\n for (const key of Object.keys(value)) {\n Object.defineProperty(clone, key, {\n configurable: true,\n enumerable: true,\n value: cloneValue((value as Record<string, unknown>)[key]),\n writable: true,\n });\n }\n return clone;\n}\n\nfunction isRecordOrArray(\n value: unknown,\n): value is Record<string, unknown> | unknown[] {\n return value !== null && typeof value === \"object\";\n}\n\nfunction hasOwn(value: object, key: string): boolean {\n return Object.prototype.hasOwnProperty.call(value, key);\n}\n\nfunction isRecord(value: unknown): value is Readonly<Record<string, unknown>> {\n return value !== null && typeof value === \"object\" && !Array.isArray(value);\n}\n"],"mappings":"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","names":["import_sdk_services","init_errors","output","init_errors","_a","import_node_path","import_node_crypto","import_node_crypto","import_node_path","import_node_sdk","DELEGATION_CID"]}