@rigxyz/tapd 0.5.3 → 0.6.0
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.
- package/dist/bin.js +18 -3
- package/dist/bin.js.map +1 -1
- package/dist/{chunk-QZHSLNCO.js → chunk-GIL4KPR7.js} +395 -16
- package/dist/chunk-GIL4KPR7.js.map +1 -0
- package/dist/index.d.ts +81 -1
- package/dist/index.js +1 -1
- package/package.json +1 -1
- package/dist/chunk-QZHSLNCO.js.map +0 -1
package/dist/index.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { Ignore } from 'ignore';
|
|
2
|
-
import { GetBindingResponse, PrepareUploadRequest, PrepareUploadResponse, CompleteUploadRequest, CompleteUploadResponse, DownloadUrlResponse, SubmitChangeEvent, SubmitChangesResponse, ChangesSinceResponse, CreateInviteRequest, CreateInviteResponse, BindingInvite, ManifestSnapshotResponse, CreateBindingRequest, CreateBindingResponse, BindingDevice, CapabilityOp, Role, ChangeEvent } from '@tap/core';
|
|
2
|
+
import { GetBindingResponse, PrepareUploadRequest, PrepareUploadResponse, CompleteUploadRequest, CompleteUploadResponse, DownloadUrlResponse, SubmitChangeEvent, SubmitChangesResponse, ChangesSinceResponse, SubmitIntent, Intent, IntentUpdate, ListIntentsResponse, CreateInviteRequest, CreateInviteResponse, BindingInvite, ManifestSnapshotResponse, CreateBindingRequest, CreateBindingResponse, BindingDevice, CapabilityOp, Role, ChangeEvent } from '@tap/core';
|
|
3
3
|
|
|
4
4
|
/**
|
|
5
5
|
* .tapignore loader.
|
|
@@ -145,6 +145,15 @@ declare class RelayClient {
|
|
|
145
145
|
path?: string;
|
|
146
146
|
untilInclusive?: string;
|
|
147
147
|
}): Promise<ChangesSinceResponse>;
|
|
148
|
+
/** Open an intent. Actor (device/user) is taken from the token, not the body. */
|
|
149
|
+
createIntent(body: SubmitIntent): Promise<Intent>;
|
|
150
|
+
/** Update / close an intent. Actor-only — the relay rejects a non-actor. */
|
|
151
|
+
updateIntent(intentId: string, body: IntentUpdate): Promise<Intent>;
|
|
152
|
+
listIntents(opts?: {
|
|
153
|
+
status?: string;
|
|
154
|
+
after?: string;
|
|
155
|
+
limit?: number;
|
|
156
|
+
}): Promise<ListIntentsResponse>;
|
|
148
157
|
/**
|
|
149
158
|
* Mint a new invite (owner-only). Returns the secret + a ready-to-share
|
|
150
159
|
* accept URL, exactly once. Caller must surface to the user immediately
|
|
@@ -289,6 +298,8 @@ type InitOptions = {
|
|
|
289
298
|
accessToken: string;
|
|
290
299
|
bindingName: string;
|
|
291
300
|
deviceLabel?: string;
|
|
301
|
+
/** Org slug to scope the new binding to. Caller must already be a member. */
|
|
302
|
+
org?: string;
|
|
292
303
|
/** Test seam: override fetch (used to drive the in-process relay). */
|
|
293
304
|
fetch?: typeof fetch;
|
|
294
305
|
};
|
|
@@ -479,6 +490,60 @@ declare function applyOnce(opts: {
|
|
|
479
490
|
mode?: ApplyMode;
|
|
480
491
|
}): Promise<ApplyResult>;
|
|
481
492
|
|
|
493
|
+
/**
|
|
494
|
+
* Claude Code context provider (Context Layer § 4, docs/context-layer-design-doc.md).
|
|
495
|
+
*
|
|
496
|
+
* The capture doctrine: the fs watcher is the write ground-truth; the transcript
|
|
497
|
+
* is the authoritative payload we read to derive the "why". This module reads
|
|
498
|
+
* Claude Code's own session JSONL — no hooks required for a first cut — and
|
|
499
|
+
* splits each transcript into **work-bouts** (one DerivedSession per bout):
|
|
500
|
+
* a Claude Code session file is appended to across resumes, so one file spans
|
|
501
|
+
* days. We cut a new bout when the gap between consecutive timestamps exceeds
|
|
502
|
+
* BOUT_GAP_MS — so each intent has a real, minutes-long window and old bouts
|
|
503
|
+
* correctly go idle, instead of one ever-growing "session".
|
|
504
|
+
*
|
|
505
|
+
* Grounded in real logs (CC 2.1.165):
|
|
506
|
+
* - one file per session at ~/.claude/projects/<encoded-cwd>/<sessionId>.jsonl
|
|
507
|
+
* - `{type:"ai-title", aiTitle}` → a model-generated session title (free!)
|
|
508
|
+
* - `{type:"user", message.content}` → user prompts (title fallback)
|
|
509
|
+
* - `{type:"assistant", message:{model, content:[{type:"tool_use", name, input}]}}`
|
|
510
|
+
* → model + Read/Grep/Glob file reads
|
|
511
|
+
* - per-line `timestamp`/`cwd` → session window + sanity check
|
|
512
|
+
*
|
|
513
|
+
* Reads are filtered to in-rig paths and relativized — staleness only makes
|
|
514
|
+
* sense for files this binding actually tracks.
|
|
515
|
+
*/
|
|
516
|
+
|
|
517
|
+
type ListSessionsOptions = {
|
|
518
|
+
/** Override the projects root (default ~/.claude/projects). For tests. */
|
|
519
|
+
projectsRoot?: string;
|
|
520
|
+
};
|
|
521
|
+
type ActiveSessionOptions = ListSessionsOptions & {
|
|
522
|
+
/** Now, ms since epoch. Defaults to Date.now(). */
|
|
523
|
+
now?: number;
|
|
524
|
+
/** A session counts as active if its last activity is within this window. */
|
|
525
|
+
idleMs?: number;
|
|
526
|
+
};
|
|
527
|
+
|
|
528
|
+
/**
|
|
529
|
+
* Intent association (Context Layer § 4 "Association").
|
|
530
|
+
*
|
|
531
|
+
* Joins the active Claude Code session to an intent on the relay and returns
|
|
532
|
+
* its id, so scanAndPush can stamp it onto the batch. The mapping
|
|
533
|
+
* sessionId → intentId is cached in the state DB's meta so a session creates
|
|
534
|
+
* exactly one intent; the title/read-set are refreshed when they change.
|
|
535
|
+
*
|
|
536
|
+
* All of this is best-effort and observe-first (§ 2): callers wrap it so a
|
|
537
|
+
* parse/relay hiccup never blocks sync — it just means a batch carries no
|
|
538
|
+
* intent.
|
|
539
|
+
*/
|
|
540
|
+
|
|
541
|
+
/** How a change's intent link was decided (Context Layer § 4.2). */
|
|
542
|
+
type IntentAssignment = {
|
|
543
|
+
intentId: string;
|
|
544
|
+
attribution: "confirmed" | "window";
|
|
545
|
+
};
|
|
546
|
+
|
|
482
547
|
/**
|
|
483
548
|
* Local → remote sync direction: compute the diff between the file
|
|
484
549
|
* system and the state DB, upload any new bytes, submit one batched
|
|
@@ -509,6 +574,13 @@ declare function scanAndPush(opts: {
|
|
|
509
574
|
ignore: Ignore;
|
|
510
575
|
/** Import mount points to hard-exclude from the scan. See walk(). */
|
|
511
576
|
excludePaths?: ReadonlyArray<string>;
|
|
577
|
+
/**
|
|
578
|
+
* Per-path intent resolver (Context Layer § 4.2). Given the batch's changed
|
|
579
|
+
* paths, returns each path's intent + attribution (`confirmed`/`window`).
|
|
580
|
+
* Best-effort: the daemon wraps it so a parse/relay hiccup yields an empty
|
|
581
|
+
* map and the batch simply carries no intent.
|
|
582
|
+
*/
|
|
583
|
+
resolveIntents?: (changedPaths: ReadonlyArray<string>) => Promise<Map<string, IntentAssignment>>;
|
|
512
584
|
}): Promise<ScanAndPushResult>;
|
|
513
585
|
|
|
514
586
|
/**
|
|
@@ -844,6 +916,14 @@ type StartOptions = {
|
|
|
844
916
|
noSse?: boolean;
|
|
845
917
|
/** SSE watchdog window in ms. Default 45s. */
|
|
846
918
|
sseHeartbeatTimeoutMs?: number;
|
|
919
|
+
/**
|
|
920
|
+
* Turn off Claude Code intent capture (Context Layer). When off, pushed
|
|
921
|
+
* changes carry no `intentId`. Default on; capture is best-effort and never
|
|
922
|
+
* blocks sync.
|
|
923
|
+
*/
|
|
924
|
+
disableIntentCapture?: boolean;
|
|
925
|
+
/** Test seam — override active-session resolution (projectsRoot, now, idleMs). */
|
|
926
|
+
intentSessionOpts?: ActiveSessionOptions;
|
|
847
927
|
};
|
|
848
928
|
/** A live import mount — exposed for status + tests. */
|
|
849
929
|
type ImportHandle = {
|
package/dist/index.js
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@rigxyz/tapd",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.6.0",
|
|
4
4
|
"description": "Local daemon for the hosted Tap relay — watches files, hashes content, uploads/downloads objects, applies remote changes. Pairs with @rigxyz/cli for shared-rig sync.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "Dylan Bourgeois <dtsbourg@gmail.com>",
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/binding-config.ts","../src/relay-client.ts","../src/state-db.ts","../src/tapignore.ts","../src/walker.ts","../../core/src/sync/capability.ts","../../core/src/sync/object.ts","../../core/src/sync/path.ts","../../core/src/sync/protocol.ts","../../core/src/ids.ts","../src/hash.ts","../src/init.ts","../src/pool.ts","../src/invite.ts","../src/join.ts","../src/imports.ts","../src/import-invite.ts","../src/applier.ts","../src/safe-path.ts","../src/sse.ts","../src/subscription.ts","../src/uploader.ts","../src/start.ts"],"sourcesContent":["/**\n * .rig/tap-binding.local.json — machine-local per-binding pointer.\n *\n * Holds the bindingId, the device's capability token, and where to find\n * the relay. NEVER synced (validated by isAlwaysExcluded in tapignore.ts;\n * the relay also rejects the path via validateRigPath).\n *\n * Tokens live here in plaintext for now. A future revision can move\n * them to the OS keychain; the file structure is designed to make that\n * swap localized.\n */\n\nimport { existsSync, readFileSync, writeFileSync, mkdirSync } from \"node:fs\";\nimport { dirname, join } from \"node:path\";\n\nexport const BINDING_CONFIG_PATH = \".rig/tap-binding.local.json\";\n\nexport type BindingConfig = {\n bindingId: string;\n relayUrl: string;\n deviceId: string;\n /** Capability-token secret. Returned once by the relay, never re-derivable. */\n token: string;\n};\n\nexport function bindingConfigFile(rootDir: string): string {\n return join(rootDir, BINDING_CONFIG_PATH);\n}\n\nexport function readBindingConfig(rootDir: string): BindingConfig | null {\n const path = bindingConfigFile(rootDir);\n if (!existsSync(path)) return null;\n const raw = readFileSync(path, \"utf8\");\n const parsed = JSON.parse(raw) as Partial<BindingConfig>;\n if (\n typeof parsed.bindingId !== \"string\" ||\n typeof parsed.relayUrl !== \"string\" ||\n typeof parsed.deviceId !== \"string\" ||\n typeof parsed.token !== \"string\"\n ) {\n throw new Error(`malformed binding config at ${path}`);\n }\n return {\n bindingId: parsed.bindingId,\n relayUrl: parsed.relayUrl,\n deviceId: parsed.deviceId,\n token: parsed.token,\n };\n}\n\nexport function writeBindingConfig(rootDir: string, config: BindingConfig): void {\n const path = bindingConfigFile(rootDir);\n mkdirSync(dirname(path), { recursive: true });\n writeFileSync(path, JSON.stringify(config, null, 2) + \"\\n\", { mode: 0o600 });\n}\n\n/**\n * Thrown when an operation requires a bound workspace but\n * `.rig/tap-binding.local.json` is missing. All three callers (`invite`,\n * `status`, `uninit`) raise the same class so bin.ts maps it to a\n * single `not_initialized` JSON error code for rig.\n */\nexport class NotInitializedError extends Error {\n constructor(public readonly rootDir: string) {\n super(`not initialized — no binding config at ${rootDir}/${BINDING_CONFIG_PATH}`);\n this.name = \"NotInitializedError\";\n }\n}\n","/**\n * Thin fetch wrapper around the relay routes.\n *\n * Module-level `createBinding` and `acceptInvite` are user-authenticated\n * via a relay-issued session bearer (Phase 3.5 replaced the\n * X-Tap-User-Id placeholder). Binding-scoped routes use the\n * `RelayClient` class which carries a (baseUrl, bindingId, capability\n * token) triple.\n *\n * `RelayError` carries the route + status + parsed body so caller logic\n * can map specific 400s (\"object_not_uploaded\", \"case_collision_with\",\n * etc.) to user-facing warnings instead of \"something went wrong\".\n */\n\nimport type {\n AcceptInviteRequest,\n AcceptInviteResponse,\n BindingInvite,\n ChangesSinceResponse,\n CompleteUploadRequest,\n CompleteUploadResponse,\n CreateBindingRequest,\n CreateBindingResponse,\n CreateInviteRequest,\n CreateInviteResponse,\n DownloadUrlResponse,\n GetBindingResponse,\n ManifestSnapshotResponse,\n PrepareUploadRequest,\n PrepareUploadResponse,\n SubmitChangeEvent,\n SubmitChangesRequest,\n SubmitChangesResponse,\n} from \"@tap/core\";\n\ntype FetchLike = typeof fetch;\n\nfunction summarizeRelayError(body: unknown, status: number): string {\n if (typeof body === \"object\" && body !== null && \"error\" in body) {\n const code = String((body as { error: unknown }).error);\n if (code === \"quota_exceeded\") {\n const limit = (body as { limitBytes?: unknown }).limitBytes;\n const mb =\n typeof limit === \"number\" ? Math.round(limit / (1024 * 1024)) : null;\n return mb\n ? `workspace storage limit reached (${mb} MB) — remove files and retry`\n : \"workspace storage limit reached — remove files and retry\";\n }\n return code;\n }\n return `status ${status}`;\n}\n\nexport class RelayError extends Error {\n constructor(\n public readonly route: string,\n public readonly status: number,\n public readonly body: unknown,\n ) {\n super(`relay ${route}: ${summarizeRelayError(body, status)}`);\n this.name = \"RelayError\";\n }\n}\n\nasync function readJson(res: Response): Promise<unknown> {\n const ct = res.headers.get(\"content-type\") ?? \"\";\n if (!ct.includes(\"application/json\")) return undefined;\n try {\n return await res.json();\n } catch {\n return undefined;\n }\n}\n\nasync function expectJson<T>(route: string, res: Response): Promise<T> {\n const body = await readJson(res);\n if (!res.ok) {\n throw new RelayError(route, res.status, body);\n }\n return body as T;\n}\n\n/**\n * Accept an invite. User-authenticated via a relay-issued session\n * bearer (optional for pure-capability invites; required for member\n * invites — the route enforces).\n *\n * Returns the device's per-machine capability token exactly once.\n */\nexport async function acceptInvite(opts: {\n baseUrl: string;\n secret: string;\n /** Bearer access token. Omit for pure-capability accepts. */\n accessToken?: string | null;\n request?: AcceptInviteRequest;\n fetch?: FetchLike;\n}): Promise<AcceptInviteResponse> {\n const fn = opts.fetch ?? fetch;\n const route = \"POST /v1/invites/:secret/accept\";\n const headers: Record<string, string> = { \"content-type\": \"application/json\" };\n if (opts.accessToken) headers.authorization = `Bearer ${opts.accessToken}`;\n const res = await fn(\n joinUrl(opts.baseUrl, `/v1/invites/${encodeURIComponent(opts.secret)}/accept`),\n {\n method: \"POST\",\n headers,\n body: JSON.stringify(opts.request ?? {}),\n },\n );\n return expectJson<AcceptInviteResponse>(route, res);\n}\n\n/**\n * Create a new binding. User-authenticated via a relay-issued session\n * bearer. The response carries the owner's first capability token,\n * returned exactly once.\n */\nexport async function createBinding(opts: {\n baseUrl: string;\n request: CreateBindingRequest;\n accessToken: string;\n fetch?: FetchLike;\n}): Promise<CreateBindingResponse> {\n const fn = opts.fetch ?? fetch;\n const route = \"POST /v1/bindings\";\n const res = await fn(joinUrl(opts.baseUrl, \"/v1/bindings\"), {\n method: \"POST\",\n headers: {\n \"content-type\": \"application/json\",\n authorization: `Bearer ${opts.accessToken}`,\n },\n body: JSON.stringify(opts.request),\n });\n return expectJson<CreateBindingResponse>(route, res);\n}\n\n/**\n * Self-revoke an import edge (user-authed via the hub token). Tombstones the\n * edge + revokes its capability token. Returns the outcome rather than\n * throwing, so `rig import remove` stays best-effort (a local remove must\n * succeed even when the relay is unreachable or the grant isn't ours).\n */\nexport async function revokeImportEdge(opts: {\n baseUrl: string;\n edgeId: string;\n accessToken: string;\n fetch?: FetchLike;\n}): Promise<{ revoked: boolean; status: number; reason?: string }> {\n const fn = opts.fetch ?? fetch;\n const res = await fn(\n joinUrl(opts.baseUrl, `/v1/me/imports/${encodeURIComponent(opts.edgeId)}`),\n { method: \"DELETE\", headers: { authorization: `Bearer ${opts.accessToken}` } },\n );\n if (res.ok) return { revoked: true, status: res.status };\n const body = await readJson(res);\n const reason =\n typeof body === \"object\" && body !== null && \"reason\" in body\n ? String((body as { reason: unknown }).reason)\n : undefined;\n return { revoked: false, status: res.status, ...(reason ? { reason } : {}) };\n}\n\nexport type RelayClientOptions = {\n baseUrl: string;\n bindingId: string;\n token: string;\n fetch?: FetchLike;\n};\n\n/**\n * Binding-scoped relay client. One instance per active binding; cheap to\n * construct, holds no resources beyond the closed-over fetch impl.\n */\nexport class RelayClient {\n private readonly baseUrl: string;\n private readonly bindingId: string;\n private readonly token: string;\n private readonly fetch: FetchLike;\n\n constructor(opts: RelayClientOptions) {\n this.baseUrl = opts.baseUrl;\n this.bindingId = opts.bindingId;\n this.token = opts.token;\n this.fetch = opts.fetch ?? fetch;\n }\n\n private authHeaders(extra?: Record<string, string>): Record<string, string> {\n return { authorization: `Bearer ${this.token}`, ...extra };\n }\n\n private bindingPath(suffix: string): string {\n return joinUrl(\n this.baseUrl,\n `/v1/bindings/${encodeURIComponent(this.bindingId)}${suffix}`,\n );\n }\n\n async getBinding(): Promise<GetBindingResponse> {\n const route = \"GET /v1/bindings/:id\";\n const res = await this.fetch(this.bindingPath(\"\"), {\n headers: this.authHeaders(),\n });\n return expectJson<GetBindingResponse>(route, res);\n }\n\n // ────────── objects ──────────\n\n async prepareUpload(body: PrepareUploadRequest): Promise<PrepareUploadResponse> {\n const route = \"POST /v1/bindings/:id/objects/prepare-upload\";\n const res = await this.fetch(this.bindingPath(\"/objects/prepare-upload\"), {\n method: \"POST\",\n headers: this.authHeaders({ \"content-type\": \"application/json\" }),\n body: JSON.stringify(body),\n });\n return expectJson<PrepareUploadResponse>(route, res);\n }\n\n async completeUpload(body: CompleteUploadRequest): Promise<CompleteUploadResponse> {\n const route = \"POST /v1/bindings/:id/objects/complete-upload\";\n const res = await this.fetch(this.bindingPath(\"/objects/complete-upload\"), {\n method: \"POST\",\n headers: this.authHeaders({ \"content-type\": \"application/json\" }),\n body: JSON.stringify(body),\n });\n return expectJson<CompleteUploadResponse>(route, res);\n }\n\n async downloadUrl(hash: string): Promise<DownloadUrlResponse> {\n const route = \"GET /v1/bindings/:id/objects/:hash/download-url\";\n const res = await this.fetch(\n this.bindingPath(`/objects/${encodeURIComponent(hash)}/download-url`),\n { headers: this.authHeaders() },\n );\n return expectJson<DownloadUrlResponse>(route, res);\n }\n\n /**\n * PUT bytes to a presigned URL. Throws on non-2xx — except 409, which\n * means the content-addressed object is already stored (same hash = same\n * bytes). That's idempotent success, not a conflict: two files with\n * identical content (e.g. several empty files), or an initial upload that\n * overlaps the daemon's first scan, race to PUT the same key. Treating\n * 409 as fatal aborts the whole push and wedges sync at \"N pending\".\n */\n async putObjectBytes(uploadUrl: string, bytes: Uint8Array | Buffer): Promise<void> {\n const route = \"PUT <uploadUrl>\";\n const res = await this.fetch(uploadUrl, {\n method: \"PUT\",\n body: new Uint8Array(bytes),\n headers: { \"content-length\": String(bytes.byteLength) },\n });\n if (res.status === 409) return;\n if (!res.ok) {\n throw new RelayError(route, res.status, await readJson(res));\n }\n }\n\n /** GET bytes from a presigned URL. Throws on non-2xx. */\n async getObjectBytes(downloadUrl: string): Promise<Buffer> {\n const route = \"GET <downloadUrl>\";\n const res = await this.fetch(downloadUrl);\n if (!res.ok) {\n throw new RelayError(route, res.status, await readJson(res));\n }\n return Buffer.from(await res.arrayBuffer());\n }\n\n // ────────── changes ──────────\n\n async submitChanges(events: ReadonlyArray<SubmitChangeEvent>): Promise<SubmitChangesResponse> {\n const route = \"POST /v1/bindings/:id/changes\";\n const body: SubmitChangesRequest = { events };\n const res = await this.fetch(this.bindingPath(\"/changes\"), {\n method: \"POST\",\n headers: this.authHeaders({ \"content-type\": \"application/json\" }),\n body: JSON.stringify(body),\n });\n return expectJson<SubmitChangesResponse>(route, res);\n }\n\n async listChanges(opts?: {\n after?: string;\n limit?: number;\n path?: string;\n untilInclusive?: string;\n }): Promise<ChangesSinceResponse> {\n const route = \"GET /v1/bindings/:id/changes\";\n const params = new URLSearchParams();\n if (opts?.after) params.set(\"after\", opts.after);\n if (opts?.limit) params.set(\"limit\", String(opts.limit));\n if (opts?.path) params.set(\"path\", opts.path);\n if (opts?.untilInclusive) params.set(\"untilInclusive\", opts.untilInclusive);\n const qs = params.toString();\n const res = await this.fetch(\n this.bindingPath(\"/changes\") + (qs ? `?${qs}` : \"\"),\n { headers: this.authHeaders() },\n );\n return expectJson<ChangesSinceResponse>(route, res);\n }\n\n // ────────── invites ──────────\n\n /**\n * Mint a new invite (owner-only). Returns the secret + a ready-to-share\n * accept URL, exactly once. Caller must surface to the user immediately\n * and rely on `listInvites()` later if they want to refer back without\n * the secret.\n */\n async mintInvite(body: CreateInviteRequest): Promise<CreateInviteResponse> {\n const route = \"POST /v1/bindings/:id/invites\";\n const res = await this.fetch(this.bindingPath(\"/invites\"), {\n method: \"POST\",\n headers: this.authHeaders({ \"content-type\": \"application/json\" }),\n body: JSON.stringify(body),\n });\n return expectJson<CreateInviteResponse>(route, res);\n }\n\n async listInvites(): Promise<{ invites: BindingInvite[] }> {\n const route = \"GET /v1/bindings/:id/invites\";\n const res = await this.fetch(this.bindingPath(\"/invites\"), {\n headers: this.authHeaders(),\n });\n return expectJson<{ invites: BindingInvite[] }>(route, res);\n }\n\n async revokeInvite(\n inviteId: string,\n ): Promise<{ revoked: boolean; alreadyRevoked: boolean }> {\n const route = \"DELETE /v1/bindings/:id/invites/:inviteId\";\n const res = await this.fetch(\n this.bindingPath(`/invites/${encodeURIComponent(inviteId)}`),\n { method: \"DELETE\", headers: this.authHeaders() },\n );\n return expectJson<{ revoked: boolean; alreadyRevoked: boolean }>(route, res);\n }\n\n // ────────── members ──────────\n\n async listMembers(): Promise<{\n members: Array<{ userId: string; role: string; email: string | null; joinedAt: string }>;\n }> {\n const route = \"GET /v1/bindings/:id/members\";\n const res = await this.fetch(this.bindingPath(\"/members\"), {\n headers: this.authHeaders(),\n });\n return expectJson(route, res);\n }\n\n async removeMember(\n userId: string,\n ): Promise<{ removed: boolean; tokensRevoked: number }> {\n const route = \"DELETE /v1/bindings/:id/members/:userId\";\n const res = await this.fetch(\n this.bindingPath(`/members/${encodeURIComponent(userId)}`),\n { method: \"DELETE\", headers: this.authHeaders() },\n );\n return expectJson(route, res);\n }\n\n async setMemberRole(\n userId: string,\n role: \"owner\" | \"editor\" | \"viewer\",\n ): Promise<{ changed: boolean; role: string }> {\n const route = \"PATCH /v1/bindings/:id/members/:userId\";\n const res = await this.fetch(\n this.bindingPath(`/members/${encodeURIComponent(userId)}`),\n {\n method: \"PATCH\",\n headers: this.authHeaders({ \"content-type\": \"application/json\" }),\n body: JSON.stringify({ role }),\n },\n );\n return expectJson(route, res);\n }\n\n // ────────── manifest ──────────\n\n async getManifest(opts?: { after?: string; limit?: number }): Promise<ManifestSnapshotResponse> {\n const route = \"GET /v1/bindings/:id/manifest\";\n const params = new URLSearchParams();\n if (opts?.after) params.set(\"after\", opts.after);\n if (opts?.limit) params.set(\"limit\", String(opts.limit));\n const qs = params.toString();\n const res = await this.fetch(\n this.bindingPath(\"/manifest\") + (qs ? `?${qs}` : \"\"),\n { headers: this.authHeaders() },\n );\n return expectJson<ManifestSnapshotResponse>(route, res);\n }\n}\n\nfunction joinUrl(base: string, suffix: string): string {\n const b = base.replace(/\\/+$/, \"\");\n const s = suffix.startsWith(\"/\") ? suffix : `/${suffix}`;\n return b + s;\n}\n","/**\n * Local sync metadata — one file per binding, at\n * `<rig>/.rig/tap/state.local.db`. Machine-local; never synced.\n *\n * Tracks:\n * - per-path: last_applied_change_id, last_seen_hash, local_dirty,\n * last_scan_at\n * - cursor: the change_event id we've consumed up to (so a daemon\n * restart resumes from the right place)\n *\n * Storage is a plain JSON document, written atomically (tmp + rename) with\n * a debounced flush. No native module — earlier versions used SQLite\n * (better-sqlite3), whose native build broke installs across Node versions\n * (no prebuilt for newer Node → `npm i -g @rigxyz/tapd` failed). The state\n * is two small maps; SQL bought us nothing. An existing SQLite `.db` from an\n * older tapd fails to parse and is treated as empty — the daemon re-derives\n * it on the next scan (idempotent: objects dedupe, applies are hash-checked).\n */\n\nimport { mkdirSync, readFileSync, writeFileSync, renameSync } from \"node:fs\";\nimport { dirname } from \"node:path\";\n\nconst CURSOR_PREFIX = \"chg_\";\n\n/** Compare two `chg_<n>` cursors numerically. Non-prefixed values are treated as -∞. */\nfunction cursorGreaterThan(a: string, b: string): boolean {\n const an = a.startsWith(CURSOR_PREFIX) ? safeBigInt(a.slice(CURSOR_PREFIX.length)) : null;\n const bn = b.startsWith(CURSOR_PREFIX) ? safeBigInt(b.slice(CURSOR_PREFIX.length)) : null;\n if (an === null) return false;\n if (bn === null) return true;\n return an > bn;\n}\n\nfunction safeBigInt(s: string): bigint | null {\n try { return BigInt(s); } catch { return null; }\n}\n\nexport type LocalPathState = {\n path: string;\n lastAppliedChangeId: string | null;\n lastSeenHash: string | null;\n localDirty: boolean;\n lastScanAt: string | null;\n};\n\ntype PathRecord = {\n lastAppliedChangeId: string | null;\n lastSeenHash: string | null;\n localDirty: boolean;\n lastScanAt: string | null;\n};\n\ntype StoreShape = {\n version: 1;\n meta: Record<string, string>;\n paths: Record<string, PathRecord>;\n};\n\nexport type StateDb = {\n /** Cursor we've applied up to, or \"chg_0\" if nothing yet. */\n getCursor(): string;\n setCursor(cursor: string): void;\n\n upsertPath(state: LocalPathState): void;\n getPath(path: string): LocalPathState | null;\n deletePath(path: string): void;\n /** Enumerate every tracked path. Cheap — state DBs are small. */\n listPaths(): string[];\n\n setMeta(key: string, value: string): void;\n getMeta(key: string): string | null;\n\n close(): void;\n};\n\n// One process-level exit hook flushes every open store, so one-shot commands\n// (init/status) that don't explicitly close() still persist. Using a single\n// shared hook (not one listener per instance) avoids MaxListeners warnings.\nconst pendingFlushers = new Set<() => void>();\nlet exitHookInstalled = false;\nfunction installExitHook() {\n if (exitHookInstalled) return;\n exitHookInstalled = true;\n process.once(\"exit\", () => {\n for (const flush of pendingFlushers) {\n try { flush(); } catch { /* best-effort on exit */ }\n }\n });\n}\n\nfunction loadStore(dbPath: string): StoreShape {\n try {\n const parsed = JSON.parse(readFileSync(dbPath, \"utf8\")) as Partial<StoreShape>;\n if (parsed && parsed.version === 1 && parsed.meta && parsed.paths) {\n return { version: 1, meta: parsed.meta, paths: parsed.paths };\n }\n } catch {\n // Missing file or a legacy SQLite binary — start fresh.\n }\n return { version: 1, meta: {}, paths: {} };\n}\n\nexport function openStateDb(dbPath: string): StateDb {\n mkdirSync(dirname(dbPath), { recursive: true });\n const store = loadStore(dbPath);\n\n let dirty = false;\n let timer: ReturnType<typeof setTimeout> | null = null;\n\n const flush = () => {\n if (timer) { clearTimeout(timer); timer = null; }\n if (!dirty) return;\n const tmp = `${dbPath}.tmp`;\n writeFileSync(tmp, JSON.stringify(store), \"utf8\");\n renameSync(tmp, dbPath);\n dirty = false;\n };\n const schedule = () => {\n dirty = true;\n if (timer) return;\n timer = setTimeout(flush, 100);\n timer.unref?.();\n };\n\n pendingFlushers.add(flush);\n installExitHook();\n\n return {\n getCursor() {\n return store.meta.cursor ?? \"chg_0\";\n },\n setCursor(cursor: string) {\n // Monotonic write: refuse to move the cursor backwards. Belt and\n // suspenders for the SSE/poll race — even if two applies overlap and\n // try to commit different cursors, we never regress.\n const current = store.meta.cursor;\n if (current && !cursorGreaterThan(cursor, current)) return;\n store.meta.cursor = cursor;\n schedule();\n },\n upsertPath(state: LocalPathState) {\n store.paths[state.path] = {\n lastAppliedChangeId: state.lastAppliedChangeId,\n lastSeenHash: state.lastSeenHash,\n localDirty: state.localDirty,\n lastScanAt: state.lastScanAt,\n };\n schedule();\n },\n getPath(path: string): LocalPathState | null {\n const r = store.paths[path];\n if (!r) return null;\n return {\n path,\n lastAppliedChangeId: r.lastAppliedChangeId,\n lastSeenHash: r.lastSeenHash,\n localDirty: r.localDirty,\n lastScanAt: r.lastScanAt,\n };\n },\n deletePath(path: string) {\n if (path in store.paths) {\n delete store.paths[path];\n schedule();\n }\n },\n listPaths(): string[] {\n return Object.keys(store.paths);\n },\n setMeta(key: string, value: string) {\n store.meta[key] = value;\n schedule();\n },\n getMeta(key: string): string | null {\n return store.meta[key] ?? null;\n },\n close() {\n flush();\n pendingFlushers.delete(flush);\n },\n };\n}\n","/**\n * .tapignore loader.\n *\n * Layers:\n * 1. Built-in defaults — common secrets, dependencies, OS noise, and\n * bulky generated artifacts (per design doc § \"Large File Policy\").\n * A user CAN override these via `!pattern` in their .tapignore.\n * 2. Project .tapignore.\n * 3. Always-excluded paths — Tap's own local metadata. These cannot be\n * re-enabled by `!pattern`; the relay would reject the upload anyway\n * (validateRigPath rejects reserved paths).\n */\n\nimport { existsSync, readFileSync } from \"node:fs\";\nimport { join } from \"node:path\";\nimport ignore, { type Ignore } from \"ignore\";\n\nexport const BUILTIN_IGNORE_LINES: ReadonlyArray<string> = [\n \"# Secrets and machine-local\",\n \".env\",\n \".env.*\",\n \"*.local.*\",\n \"\",\n \"# Dependencies and build output\",\n \"node_modules/\",\n \"dist/\",\n \".venv/\",\n \"__pycache__/\",\n \"*.pyc\",\n \"\",\n \"# Git and OS noise\",\n \".git/\",\n \".DS_Store\",\n \"\",\n \"# Bulky generated artifacts\",\n \"*.mov\",\n \"*.mp4\",\n \"*.zip\",\n \"*.tar.gz\",\n \"*.sqlite\",\n \"\",\n \"# Tap conflict sidecars — local-only by default\",\n \"*.conflict-from.*\",\n];\n\n// `.rig/` as a whole is local-metadata namespace: tap state DB +\n// binding config (this repo) plus rig's per-machine provenance\n// (`.rig/instance.toml`, `.rig/setup.json` — see ~/Code/rig). Nothing\n// in here is portable across machines, so the walker never sees it.\n// Tightening from the original `.rig/tap/`-only exclusion also fixes\n// a latent bug: the walker would push `.rig` to emptyDirs (its only\n// children being always-excluded) and scanAndPush would submit a\n// phantom `mkdir .rig` on every fresh daemon's first scan.\nconst ALWAYS_EXCLUDED_PREFIXES: ReadonlyArray<string> = [\".rig/\"];\n\nexport function loadIgnore(rootDir: string): Ignore {\n const ig = ignore();\n ig.add(BUILTIN_IGNORE_LINES.join(\"\\n\"));\n const projectIgnore = join(rootDir, \".tapignore\");\n if (existsSync(projectIgnore)) {\n ig.add(readFileSync(projectIgnore, \"utf8\"));\n }\n return ig;\n}\n\n/**\n * Hard-exclude check that runs in addition to .tapignore. Tap's own\n * local metadata is invisible to sync no matter what the user puts in\n * their .tapignore — including `!pattern` overrides.\n */\nexport function isAlwaysExcluded(posixPath: string): boolean {\n for (const prefix of ALWAYS_EXCLUDED_PREFIXES) {\n const exact = prefix.endsWith(\"/\") ? prefix.slice(0, -1) : prefix;\n if (posixPath === exact) return true;\n if (posixPath.startsWith(prefix)) return true;\n }\n return false;\n}\n\n/**\n * True if `posixPath` is at or under any of `prefixes`. Used to\n * hard-exclude import mount points from the primary binding's walker\n * and watcher: a mirrored subtree must NEVER be re-scanned and uploaded\n * into the primary's own binding (the \"accidental re-share\").\n *\n * Deliberately a non-overridable predicate alongside `isAlwaysExcluded`,\n * NOT a `.tapignore` pattern — built-in ignore lines can be re-armed\n * with `!pattern`, which would defeat the guard.\n */\nexport function isUnderAny(\n posixPath: string,\n prefixes: ReadonlyArray<string>,\n): boolean {\n for (const raw of prefixes) {\n const prefix = raw.replace(/\\/+$/, \"\");\n if (prefix === \"\") continue;\n if (posixPath === prefix) return true;\n if (posixPath.startsWith(prefix + \"/\")) return true;\n }\n return false;\n}\n","/**\n * Recursive directory walker for a rig. Yields:\n * - `files`: candidates for upload (size + posix path relative to root)\n * - `emptyDirs`: paths to mkdir markers (dirs with no tracked children)\n * - `warnings`: paths that won't sync (oversized, symlink, invalid path)\n *\n * Rules enforced:\n * - `.tapignore` (loadIgnore)\n * - Tap's own reserved metadata (isAlwaysExcluded)\n * - MAX_FILE_BYTES (100 MB) — daemon refuses upload above this\n * - Symlinks (lstat-detected) — rejected; warning emitted\n * - validateRigPath — defends against control chars / weird segments\n * before the relay sees them\n *\n * Path strings are POSIX (forward slashes) even on Windows so they\n * match what the relay stores and what the manifest returns.\n */\n\nimport { readdirSync, statSync, type Dirent } from \"node:fs\";\nimport { join, sep } from \"node:path\";\nimport type { Ignore } from \"ignore\";\nimport { MAX_FILE_BYTES, validateRigPath } from \"@tap/core\";\nimport { isAlwaysExcluded, isUnderAny } from \"./tapignore.js\";\n\nexport type WalkFile = { path: string; size: number };\nexport type WalkWarning = {\n path: string;\n reason: \"too_large\" | \"symlink\" | \"invalid_path\" | \"unreadable\";\n};\n\nexport type WalkResult = {\n files: WalkFile[];\n emptyDirs: string[];\n /**\n * Every directory visited during the walk, including non-empty ones.\n * Used by `scanAndPush` to detect \"previously-tracked dir no longer\n * on disk\" cases that emptyDirs alone wouldn't catch — e.g. a\n * directory that was empty-and-tracked, then gained files (now\n * non-empty and on disk: should NOT be deleted) vs one that was\n * `rm -rf`d (gone from disk: should be rmdir'd).\n */\n directoriesScanned: string[];\n warnings: WalkWarning[];\n};\n\nexport function walk(\n rootDir: string,\n opts: {\n ignore: Ignore;\n /**\n * Mount points (posix, relative to rootDir) to hard-exclude — import\n * subdirectories materialized by mirror subscriptions. Never scanned,\n * never uploaded into this binding. Non-overridable by .tapignore.\n */\n excludePaths?: ReadonlyArray<string>;\n },\n): WalkResult {\n const files: WalkFile[] = [];\n const emptyDirs: string[] = [];\n const directoriesScanned: string[] = [];\n const warnings: WalkWarning[] = [];\n const excludePaths = opts.excludePaths ?? [];\n\n walkDir(\".\");\n\n return { files, emptyDirs, directoriesScanned, warnings };\n\n /** Returns true if this directory contained any tracked child (file or dir). */\n function walkDir(rel: string): boolean {\n const abs = join(rootDir, rel);\n let entries: Dirent[];\n try {\n entries = readdirSync(abs, { withFileTypes: true });\n } catch {\n warnings.push({ path: posixOf(rel), reason: \"unreadable\" });\n return false;\n }\n let hasVisible = false;\n for (const ent of entries) {\n const childRel = rel === \".\" ? ent.name : join(rel, ent.name);\n const posix = posixOf(childRel);\n const isDir = ent.isDirectory();\n // ignore.ignores() expects a trailing slash for directories so\n // patterns like `node_modules/` match.\n if (isAlwaysExcluded(posix)) continue;\n if (excludePaths.length > 0 && isUnderAny(posix, excludePaths)) {\n // This child is an import mount (or under one). It's never\n // tracked/uploaded — but its presence means the parent dir is NOT\n // empty, so mark visible to avoid emitting a phantom `mkdir` for\n // a directory that exists only to hold the mount.\n hasVisible = true;\n continue;\n }\n if (opts.ignore.ignores(posix + (isDir ? \"/\" : \"\"))) continue;\n\n if (ent.isSymbolicLink()) {\n warnings.push({ path: posix, reason: \"symlink\" });\n continue;\n }\n const pathOk = validateRigPath(posix);\n if (!pathOk.ok) {\n warnings.push({ path: posix, reason: \"invalid_path\" });\n continue;\n }\n if (isDir) {\n directoriesScanned.push(posix);\n const subHas = walkDir(childRel);\n if (!subHas) emptyDirs.push(posix);\n hasVisible = true;\n continue;\n }\n if (ent.isFile()) {\n let size: number;\n try {\n size = statSync(join(abs, ent.name)).size;\n } catch {\n warnings.push({ path: posix, reason: \"unreadable\" });\n continue;\n }\n if (size > MAX_FILE_BYTES) {\n warnings.push({ path: posix, reason: \"too_large\" });\n continue;\n }\n files.push({ path: posix, size });\n hasVisible = true;\n }\n }\n return hasVisible;\n }\n}\n\nfunction posixOf(s: string): string {\n return sep === \"/\" ? s : s.replaceAll(sep, \"/\");\n}\n","/**\n * Capability tokens — the daemon's access primitive. Every sync API\n * request resolves to an `AuthContext`. Roles (owner/editor/viewer) only\n * gate token *minting*; per-request authorization reads the token's\n * scope, not the underlying role.\n */\n\nexport type CapabilityOp = \"read\" | \"write\" | \"subscribe\";\n\nexport type CapabilityToken = {\n tokenHash: string; // sha256 of the secret; secret never stored\n bindingId: string;\n userId: string | null;\n deviceId: string;\n ops: ReadonlyArray<CapabilityOp>;\n pathGlobs: ReadonlyArray<string>;\n expiresAt: string | null;\n revokedAt: string | null;\n label: string | null;\n createdAt: string;\n};\n\n/**\n * What auth middleware resolves a bearer token into. Carried in\n * request context throughout the relay; never serialized to the wire.\n */\nexport type AuthContext = {\n tokenHash: string;\n bindingId: string;\n ops: ReadonlyArray<CapabilityOp>;\n pathGlobs: ReadonlyArray<string>;\n userId: string | null;\n deviceId: string;\n};\n\nexport function hasOp(auth: AuthContext, op: CapabilityOp): boolean {\n return auth.ops.includes(op);\n}\n","/**\n * Object record — metadata for one file blob in object storage.\n *\n * Objects are content-addressed by full-file sha256 (no chunking in v1).\n * Composite key is (bindingId, hash). v1 deliberately avoids cross-tenant\n * dedup; each binding has its own object pool, scoping IAM and lifecycle\n * boundaries cleanly.\n *\n * Storage key shape:\n * s3://<bucket>/bindings/<bindingId>/sha256/<first2>/<hash>\n */\n\n/** v1 hard ceiling on a single file's bytes. Daemon refuses oversize files. */\nexport const MAX_FILE_BYTES = 100 * 1024 * 1024;\n\nexport type ObjectRecord = {\n bindingId: string;\n hash: string; // \"sha256:…\"\n size: number;\n storageKey: string; // \"s3://bucket/path…\"\n createdAt: string;\n};\n\n/**\n * Build the canonical storage key for a content-addressed object.\n * Same layout in every environment; only the bucket differs.\n */\nexport function objectStorageKey(bindingId: string, hash: string): string {\n const bare = hash.startsWith(\"sha256:\") ? hash.slice(\"sha256:\".length) : hash;\n const fanout = bare.slice(0, 2);\n return `bindings/${bindingId}/sha256/${fanout}/${bare}`;\n}\n","/**\n * Path safety + case-fold for cross-platform filesystem rules.\n *\n * Rules enforced by `validateRigPath`:\n * - reject absolute paths (leading \"/\")\n * - reject \"..\" anywhere\n * - reject backslash separators (windows-style)\n * - reject embedded NUL or other control characters\n * - reject empty segments and leading/trailing slashes\n * - reject the two paths Tap reserves for local metadata\n *\n * Symlink rejection happens at the daemon (it requires an `lstat`).\n */\n\nconst RESERVED_PATHS = [\n \".rig/tap/\",\n \".rig/tap-binding.local.json\",\n];\n\n/**\n * Lower-case + NFC-normalize. Matches what macOS APFS / Windows NTFS\n * treat as case-equivalent in the common case. Pure ASCII is unchanged.\n * Non-ASCII case-folding edge cases (Turkish dotted I, German ß, etc.)\n * are out of scope; we prefer false-positive collisions over false-negative\n * ones since file *sync* should be stricter than the OS.\n */\nexport function foldPath(path: string): string {\n return path.normalize(\"NFC\").toLowerCase();\n}\n\nexport type PathValidationError =\n | \"empty\"\n | \"absolute\"\n | \"parent_segment\"\n | \"backslash\"\n | \"control_char\"\n | \"empty_segment\"\n | \"trailing_slash\"\n | \"reserved\";\n\nexport function validateRigPath(\n path: string,\n): { ok: true } | { ok: false; reason: PathValidationError } {\n if (path.length === 0) return { ok: false, reason: \"empty\" };\n if (path.startsWith(\"/\")) return { ok: false, reason: \"absolute\" };\n if (path.endsWith(\"/\")) return { ok: false, reason: \"trailing_slash\" };\n if (path.includes(\"\\\\\")) return { ok: false, reason: \"backslash\" };\n // Control chars 0x00–0x1F and 0x7F. Any of these in a filename is\n // a sign of bytes-treated-as-text confusion at minimum.\n // eslint-disable-next-line no-control-regex\n if (/[\u0000-\u001f]/.test(path)) return { ok: false, reason: \"control_char\" };\n const segments = path.split(\"/\");\n for (const seg of segments) {\n if (seg.length === 0) return { ok: false, reason: \"empty_segment\" };\n if (seg === \"..\") return { ok: false, reason: \"parent_segment\" };\n }\n for (const reserved of RESERVED_PATHS) {\n if (path === reserved.replace(/\\/$/, \"\") || path.startsWith(reserved)) {\n return { ok: false, reason: \"reserved\" };\n }\n }\n return { ok: true };\n}\n","/**\n * Sync API path constants and version header.\n *\n * The relay advertises the API version it speaks in every response via\n * `Tap-Sync-Version`. Daemons compare and refuse to talk to incompatible\n * versions.\n */\n\nexport const TAP_SYNC_VERSION = \"1\";\nexport const TAP_SYNC_VERSION_HEADER = \"tap-sync-version\";\n\n// Binding lifecycle\nexport const RIG_BINDINGS_PATH = \"/v1/bindings\";\nexport const RIG_BINDING_PATH = \"/v1/bindings/:bindingId\";\n\n// Sync\nexport const RIG_MANIFEST_PATH = \"/v1/bindings/:bindingId/manifest\";\nexport const RIG_CHANGES_PATH = \"/v1/bindings/:bindingId/changes\";\nexport const RIG_STREAM_PATH = \"/v1/bindings/:bindingId/stream\";\n\n// Objects\nexport const RIG_OBJECTS_PREPARE_UPLOAD_PATH =\n \"/v1/bindings/:bindingId/objects/prepare-upload\";\nexport const RIG_OBJECTS_COMPLETE_UPLOAD_PATH =\n \"/v1/bindings/:bindingId/objects/complete-upload\";\nexport const RIG_OBJECT_DOWNLOAD_URL_PATH =\n \"/v1/bindings/:bindingId/objects/:hash/download-url\";\n\n// Membership (Phase 3)\nexport const RIG_INVITES_PATH = \"/v1/bindings/:bindingId/invites\";\nexport const RIG_INVITE_ACCEPT_PATH = \"/v1/invites/:secret/accept\";\nexport const RIG_MEMBERS_PATH = \"/v1/bindings/:bindingId/members\";\nexport const RIG_DEVICES_PATH = \"/v1/bindings/:bindingId/devices\";\n","import { randomBytes } from \"node:crypto\";\n\nconst ALPHABET = \"0123456789abcdefghjkmnpqrstvwxyz\";\n\nexport function shortId(prefix: string, bytes = 6): string {\n const buf = randomBytes(bytes);\n let out = \"\";\n for (let i = 0; i < buf.length; i++) {\n out += ALPHABET[buf[i]! % ALPHABET.length];\n }\n return `${prefix}_${out}`;\n}\n\n// Hosted rig sync. Change-event ids come from Postgres (a bigint\n// sequence, formatted as `chg_<n>` at the wire boundary), so there\n// is no client-side minter for them.\nexport const newBindingId = () => shortId(\"bnd\");\nexport const newDeviceId = () => shortId(\"dev\");\nexport const newInviteId = () => shortId(\"inv\");\nexport const newImportEdgeId = () => shortId(\"ime\");\nexport const newUserId = () => shortId(\"usr\");\nexport const newUserSessionId = () => shortId(\"usess\");\n\n/**\n * Capability-token secret. ~192 bits, base64url-encoded. The relay\n * only stores `sha256(secret)`; the secret is returned exactly once,\n * at mint time, and never re-derivable from the DB.\n */\nexport function newCapabilitySecret(): string {\n return `tap_cap_${randomBytes(24).toString(\"base64url\")}`;\n}\n\n/**\n * Invite secret. Same shape and storage discipline as capability\n * secrets — relay stores sha256(secret), plaintext returned once at\n * mint time. URL form: `<baseUrl>/v1/invites/<secret>/accept`.\n */\nexport function newInviteSecret(): string {\n return `tap_inv_${randomBytes(24).toString(\"base64url\")}`;\n}\n\n/**\n * Personal Access Token. Long-lived, full-account, no refresh. Today\n * this is the only kind of user session the relay mints — `tapd login`\n * picks one up from `POST /v1/me/tokens` after rig's web `/auth/device`\n * page hands it the user's Clerk JWT. The distinct prefix makes\n * accidental commits to public repos easier to scan for.\n */\nexport function newPatSecret(): string {\n return `rpat_${randomBytes(24).toString(\"base64url\")}`;\n}\n","/**\n * Streaming sha256 of a file path, returned in the relay's wire format\n * (\"sha256:\" + 64 hex). Used by walker → uploader and by watcher → uploader.\n */\n\nimport { createHash } from \"node:crypto\";\nimport { createReadStream } from \"node:fs\";\n\nexport async function hashFile(absPath: string): Promise<string> {\n const hash = createHash(\"sha256\");\n await new Promise<void>((resolve, reject) => {\n const stream = createReadStream(absPath);\n stream.on(\"data\", (chunk) => hash.update(chunk));\n stream.on(\"end\", () => resolve());\n stream.on(\"error\", (err) => reject(err));\n });\n return `sha256:${hash.digest(\"hex\")}`;\n}\n","/**\n * `tapd init` — bootstrap a fresh binding from the current working\n * directory.\n *\n * Steps (mapped to design doc § \"Init Flow\"):\n * 1. Refuse if .rig/tap-binding.local.json already exists.\n * 2. Walk the rig, applying .tapignore + reserved-path rules.\n * 3. Hash every tracked file (de-duplicated by hash so identical\n * content uploads once).\n * 4. For each unique hash: prepare-upload → PUT bytes → complete-upload.\n * 5. Submit one batched POST /changes with one event per file +\n * one mkdir per empty-dir marker.\n * 6. Write .rig/tap-binding.local.json (mode 0600).\n * 7. Populate .rig/tap/state.local.db with last_applied_change_id\n * per path, and the resulting cursor.\n *\n * Returns a summary with the binding, the owner token (so the caller\n * CLI can print it once), upload + change counts, and any walker\n * warnings (oversized files / symlinks / invalid paths). Caller is\n * responsible for surfacing the warnings to the user.\n */\n\nimport { createHash } from \"node:crypto\";\nimport { readFileSync } from \"node:fs\";\nimport { join } from \"node:path\";\nimport type { CreateBindingResponse, SubmitChangeEvent } from \"@tap/core\";\n\nfunction sha256OfBuffer(bytes: Buffer | Uint8Array): string {\n return `sha256:${createHash(\"sha256\").update(bytes).digest(\"hex\")}`;\n}\nimport { createBinding, RelayClient } from \"./relay-client.js\";\nimport { openStateDb, type LocalPathState } from \"./state-db.js\";\nimport { loadIgnore } from \"./tapignore.js\";\nimport { walk, type WalkResult } from \"./walker.js\";\nimport { hashFile } from \"./hash.js\";\nimport {\n BINDING_CONFIG_PATH,\n readBindingConfig,\n writeBindingConfig,\n} from \"./binding-config.js\";\nimport { mapPool } from \"./pool.js\";\n\n// Max object uploads in flight during the initial scan (see uploader.ts).\nconst UPLOAD_CONCURRENCY = 8;\n\nexport type InitOptions = {\n rootDir: string;\n relayUrl: string;\n /** User access token (from credentials). */\n accessToken: string;\n bindingName: string;\n deviceLabel?: string;\n /** Test seam: override fetch (used to drive the in-process relay). */\n fetch?: typeof fetch;\n};\n\nexport type InitResult = {\n binding: CreateBindingResponse[\"binding\"];\n device: CreateBindingResponse[\"device\"];\n /** Owner secret. Returned exactly once. The caller should display it. */\n ownerSecret: string;\n uploadedHashes: number;\n reusedHashes: number;\n submittedEvents: number;\n warnings: WalkResult[\"warnings\"];\n /** Cursor written to state.local.db after the initial submit. */\n cursor: string;\n};\n\nexport class AlreadyInitializedError extends Error {\n constructor(public readonly path: string) {\n super(`already initialized — ${path} exists`);\n this.name = \"AlreadyInitializedError\";\n }\n}\n\nexport async function init(opts: InitOptions): Promise<InitResult> {\n if (readBindingConfig(opts.rootDir)) {\n throw new AlreadyInitializedError(join(opts.rootDir, BINDING_CONFIG_PATH));\n }\n\n // 1. Walk the rig before touching the relay, so we fail fast on a\n // structurally invalid project (e.g. wholly unreadable root).\n const ig = loadIgnore(opts.rootDir);\n const scan = walk(opts.rootDir, { ignore: ig });\n\n // 2. Hash everything. Stream sha256 so a multi-GB rig stays bounded.\n type HashedFile = { path: string; size: number; hash: string };\n const hashed: HashedFile[] = [];\n for (const f of scan.files) {\n const h = await hashFile(join(opts.rootDir, f.path));\n hashed.push({ path: f.path, size: f.size, hash: h });\n }\n\n // 3. Create the binding (the response carries the owner token once).\n // User identity comes from the Authorization: Bearer header\n // (relay-client adds it from `accessToken`).\n const created = await createBinding({\n baseUrl: opts.relayUrl,\n request: { name: opts.bindingName, deviceLabel: opts.deviceLabel },\n accessToken: opts.accessToken,\n fetch: opts.fetch,\n });\n\n // 3a. Persist the binding config IMMEDIATELY. The owner secret is\n // returned exactly once by the relay; if upload or submit fails\n // downstream, the user can resume via `tapd start` (which scans\n // and pushes anything not yet known to the relay). Without this\n // write, a mid-init failure orphans the binding — token unknown,\n // no way to access from this machine ever again.\n writeBindingConfig(opts.rootDir, {\n bindingId: created.binding.id,\n relayUrl: opts.relayUrl,\n deviceId: created.device.id,\n token: created.token.secret,\n });\n\n const client = new RelayClient({\n baseUrl: opts.relayUrl,\n bindingId: created.binding.id,\n token: created.token.secret,\n fetch: opts.fetch,\n });\n\n // 4. Upload each unique hash exactly once. Identical content across\n // paths (e.g. duplicated assets) reuses the same object.\n const uniqueHashes = new Map<string, HashedFile>();\n for (const f of hashed) {\n if (!uniqueHashes.has(f.hash)) uniqueHashes.set(f.hash, f);\n }\n const mutatedDuringScan = new Set<string>();\n let uploaded = 0;\n let reused = 0;\n // Bounded-concurrency upload — serial round-trips made a many-file initial\n // scan take minutes against a remote relay.\n await mapPool([...uniqueHashes.values()], UPLOAD_CONCURRENCY, async (f) => {\n const prep = await client.prepareUpload({ hash: f.hash, size: f.size });\n if (prep.exists) {\n reused += 1;\n return;\n }\n const bytes = readFileSync(join(opts.rootDir, f.path));\n // Re-verify that the buffer we're PUTting matches the hash we\n // promised the relay. If the file was modified between the\n // streaming hash above and this read, skip the upload and warn —\n // the user can re-run `tapd start` to pick it up.\n const actual = sha256OfBuffer(bytes);\n if (actual !== f.hash) {\n mutatedDuringScan.add(f.path);\n scan.warnings.push({\n path: f.path,\n reason: \"invalid_path\", // closest existing warning kind; semantics are file-mutated-mid-scan\n });\n return;\n }\n await client.putObjectBytes(prep.uploadUrl, bytes);\n await client.completeUpload({ hash: f.hash, size: f.size });\n uploaded += 1;\n });\n\n // 5. Build one batch: writes first, then mkdir markers. Skip writes\n // for any file whose hash failed to upload (mutated mid-scan).\n const events: SubmitChangeEvent[] = [\n ...hashed\n .filter((f) => !mutatedDuringScan.has(f.path))\n .map((f) => ({\n op: \"write\" as const,\n path: f.path,\n hash: f.hash,\n size: f.size,\n })),\n ...scan.emptyDirs.map((path) => ({ op: \"mkdir\" as const, path })),\n ];\n\n let cursor = \"chg_0\";\n let submittedEvents: ReadonlyArray<import(\"@tap/core\").ChangeEvent> = [];\n if (events.length > 0) {\n const submit = await client.submitChanges(events);\n submittedEvents = submit.events;\n cursor = submit.events[submit.events.length - 1]!.id;\n }\n\n // 6. Populate state.local.db. The binding config was written above\n // (step 3a) so the token isn't lost on a mid-init failure.\n const stateDb = openStateDb(\n join(opts.rootDir, \".rig\", \"tap\", \"state.local.db\"),\n );\n try {\n const now = new Date().toISOString();\n for (const ev of submittedEvents) {\n const state: LocalPathState = {\n path: ev.path,\n lastAppliedChangeId: ev.id,\n lastSeenHash: ev.hash, // null for mkdirs\n localDirty: false,\n lastScanAt: now,\n };\n stateDb.upsertPath(state);\n }\n stateDb.setCursor(cursor);\n stateDb.setMeta(\"device_id\", created.device.id);\n stateDb.setMeta(\"binding_id\", created.binding.id);\n } finally {\n stateDb.close();\n }\n\n return {\n binding: created.binding,\n device: created.device,\n ownerSecret: created.token.secret,\n uploadedHashes: uploaded,\n reusedHashes: reused,\n submittedEvents: submittedEvents.length,\n warnings: scan.warnings,\n cursor,\n };\n}\n","/**\n * Bounded-concurrency map. Runs `fn` over `items` with at most `limit`\n * promises in flight. Used to parallelize object uploads: a many-file\n * initial sync was O(files) serial round-trips (prepareUpload → PUT →\n * completeUpload, one at a time), which is minutes for a few hundred files\n * against a remote relay. Workers pull the next index until the list drains.\n *\n * First rejection propagates (in-flight tasks settle but no new ones start).\n * No ordering guarantees.\n */\nexport async function mapPool<T>(\n items: readonly T[],\n limit: number,\n fn: (item: T) => Promise<void>,\n): Promise<void> {\n let next = 0;\n let failed = false;\n const worker = async () => {\n while (!failed) {\n const idx = next++;\n if (idx >= items.length) break;\n const item = items[idx]!;\n try {\n await fn(item);\n } catch (err) {\n failed = true;\n throw err;\n }\n }\n };\n const n = Math.max(1, Math.min(limit, items.length));\n await Promise.all(Array.from({ length: n }, worker));\n}\n","/**\n * `tapd invite` — owner-side helpers for the invite lifecycle.\n *\n * Reads .rig/tap-binding.local.json for the relay URL + token, builds\n * a RelayClient, and calls the matching endpoint. Designed to be a\n * thin wrapper that the CLI dispatcher (bin.ts) can call without\n * thinking about plumbing.\n */\n\nimport type {\n BindingInvite,\n CapabilityOp,\n CreateInviteRequest,\n CreateInviteResponse,\n Role,\n} from \"@tap/core\";\nimport { NotInitializedError, readBindingConfig } from \"./binding-config.js\";\nimport { RelayClient } from \"./relay-client.js\";\n\n// Re-exported so existing `import { NotInitializedError } from \"./invite.js\"`\n// imports + tests don't need to change. Class lives in binding-config.\nexport { NotInitializedError };\n\nfunction clientFor(rootDir: string, fetchImpl?: typeof fetch): RelayClient {\n const cfg = readBindingConfig(rootDir);\n if (!cfg) throw new NotInitializedError(rootDir);\n return new RelayClient({\n baseUrl: cfg.relayUrl,\n bindingId: cfg.bindingId,\n token: cfg.token,\n fetch: fetchImpl,\n });\n}\n\nexport type MintInviteOptions = {\n rootDir: string;\n ops: ReadonlyArray<CapabilityOp>;\n role?: Role | null;\n pathGlobs?: ReadonlyArray<string>;\n ttlSeconds?: number;\n maxUses?: number;\n emailConstraint?: string;\n label?: string;\n fetch?: typeof fetch;\n};\n\nexport async function mint(opts: MintInviteOptions): Promise<CreateInviteResponse> {\n const client = clientFor(opts.rootDir, opts.fetch);\n const body: CreateInviteRequest = {\n ops: opts.ops,\n ...(opts.role !== undefined ? { role: opts.role } : {}),\n ...(opts.pathGlobs ? { pathGlobs: opts.pathGlobs } : {}),\n ...(opts.ttlSeconds !== undefined ? { ttlSeconds: opts.ttlSeconds } : {}),\n ...(opts.maxUses !== undefined ? { maxUses: opts.maxUses } : {}),\n ...(opts.emailConstraint ? { emailConstraint: opts.emailConstraint } : {}),\n ...(opts.label ? { label: opts.label } : {}),\n };\n return client.mintInvite(body);\n}\n\nexport async function list(opts: { rootDir: string; fetch?: typeof fetch }): Promise<BindingInvite[]> {\n const client = clientFor(opts.rootDir, opts.fetch);\n const res = await client.listInvites();\n return res.invites;\n}\n\nexport async function revoke(opts: {\n rootDir: string;\n inviteId: string;\n fetch?: typeof fetch;\n}): Promise<{ revoked: boolean; alreadyRevoked: boolean }> {\n const client = clientFor(opts.rootDir, opts.fetch);\n return client.revokeInvite(opts.inviteId);\n}\n","/**\n * `tapd join` — accept an invite URL and set up a fresh local binding.\n *\n * Unlike `tapd init`, join does NOT touch the rig contents:\n * 1. Refuse if .rig/tap-binding.local.json already exists.\n * 2. Parse the invite URL → (baseUrl, secret).\n * 3. POST /v1/invites/:secret/accept with the user's identity header,\n * receive {bindingId, device, token}.\n * 4. Write .rig/tap-binding.local.json (mode 0600) IMMEDIATELY — same\n * \"secret-returned-once survives downstream failure\" discipline as\n * `tapd init`.\n * 5. Open .rig/tap/state.local.db with cursor `chg_0`.\n *\n * After join, `tapd start` will:\n * - applyOnce: pull the remote manifest + change log\n * - scanAndPush: push any pre-existing local divergence\n *\n * That covers both the bootstrap-from-empty-dir case and the\n * \"user already has some files locally\" merge case.\n */\n\nimport { join as joinPath } from \"node:path\";\nimport type { AcceptInviteResponse, BindingDevice } from \"@tap/core\";\nimport { acceptInvite } from \"./relay-client.js\";\nimport {\n BINDING_CONFIG_PATH,\n readBindingConfig,\n writeBindingConfig,\n} from \"./binding-config.js\";\nimport { openStateDb } from \"./state-db.js\";\n\nexport type JoinOptions = {\n rootDir: string;\n inviteUrl: string;\n /**\n * User access token from credentials. Required for member invites\n * (role !== null), optional for pure-capability invites.\n */\n accessToken?: string | null;\n deviceLabel?: string;\n /** Test seam: override fetch. */\n fetch?: typeof fetch;\n};\n\nexport type JoinResult = {\n bindingId: string;\n device: BindingDevice;\n /** Capability secret. Returned exactly once. */\n tokenSecret: string;\n /** Whether the invite granted a member role (vs pure-capability). */\n becameMember: boolean;\n};\n\nexport class AlreadyJoinedError extends Error {\n constructor(public readonly path: string) {\n super(`already initialized — ${path} exists`);\n this.name = \"AlreadyJoinedError\";\n }\n}\n\nexport class InvalidInviteUrlError extends Error {\n constructor(public readonly url: string) {\n super(`invalid invite URL: ${url} (expected <baseUrl>/v1/invites/<secret>/accept)`);\n this.name = \"InvalidInviteUrlError\";\n }\n}\n\n/**\n * Parse `https://relay.example/v1/invites/<secret>/accept` into\n * (baseUrl, secret). Trailing slash + missing /accept both accepted.\n */\nexport function parseInviteUrl(url: string): { baseUrl: string; secret: string } {\n let parsed: URL;\n try {\n parsed = new URL(url);\n } catch {\n throw new InvalidInviteUrlError(url);\n }\n const match = /^\\/v1\\/invites\\/([^/]+)(?:\\/accept)?\\/?$/.exec(parsed.pathname);\n if (!match) throw new InvalidInviteUrlError(url);\n return {\n baseUrl: `${parsed.protocol}//${parsed.host}`,\n secret: decodeURIComponent(match[1]!),\n };\n}\n\nexport async function join(opts: JoinOptions): Promise<JoinResult> {\n if (readBindingConfig(opts.rootDir)) {\n throw new AlreadyJoinedError(joinPath(opts.rootDir, BINDING_CONFIG_PATH));\n }\n const { baseUrl, secret } = parseInviteUrl(opts.inviteUrl);\n\n const accepted: AcceptInviteResponse = await acceptInvite({\n baseUrl,\n secret,\n accessToken: opts.accessToken ?? null,\n request: opts.deviceLabel ? { deviceLabel: opts.deviceLabel } : undefined,\n fetch: opts.fetch,\n });\n\n // Persist config FIRST. The token is returned exactly once; if the\n // state-DB open ever fails (unlikely but possible — disk full,\n // permissions), the user can still re-attempt via `tapd start`.\n writeBindingConfig(opts.rootDir, {\n bindingId: accepted.bindingId,\n relayUrl: baseUrl,\n deviceId: accepted.device.id,\n token: accepted.token.secret,\n });\n\n const stateDb = openStateDb(\n joinPath(opts.rootDir, \".rig\", \"tap\", \"state.local.db\"),\n );\n try {\n stateDb.setMeta(\"binding_id\", accepted.bindingId);\n stateDb.setMeta(\"device_id\", accepted.device.id);\n // cursor stays at default \"chg_0\" — start's initial applyOnce\n // pulls everything from the manifest.\n } finally {\n stateDb.close();\n }\n\n return {\n bindingId: accepted.bindingId,\n device: accepted.device,\n tokenSecret: accepted.token.secret,\n becameMember: accepted.member !== null,\n };\n}\n","/**\n * Rig imports — read-only live mirrors of another binding into a\n * subdirectory (see ~/Code/rig/docs/rig-imports.md).\n *\n * This module owns the *declaration* side: the ImportSpec shape, on-disk\n * path helpers, and the security-critical validator. The daemon wiring\n * (mirror subscriptions, orchestration) lives in start.ts; the\n * apply-time behavior (remote-wins) lives in applier.ts (mode: \"mirror\").\n *\n * The validator is the single highest-risk part of the feature: a bad\n * mount point is how an import could silently re-share — or, because\n * mirror mode is remote-authoritative, destroy — primary content. Treat\n * every rule here as security code, not input hygiene.\n */\n\nimport {\n existsSync,\n mkdirSync,\n readdirSync,\n readFileSync,\n realpathSync,\n statSync,\n writeFileSync,\n} from \"node:fs\";\nimport { dirname, isAbsolute, join, relative } from \"node:path\";\n\nexport type ImportSpec = {\n /** Boring slug — becomes the token filename + state-db dir name. */\n name: string;\n bindingId: string;\n relayUrl: string;\n /** Per-import capability token (read + subscribe). Machine-local. */\n token: string;\n /** Mount point: subdirectory relative to the rig root (posix). */\n path: string;\n};\n\n/**\n * On-disk shape of a per-import mount file\n * (`.rig/tap/imports/<name>.local.json`). Machine-local, gitignored,\n * never synced — mirrors `tap-binding.local.json`. Carries the capability\n * secret plus enough to start a mirror subscription without parsing\n * `rig.toml` (tapd has no TOML parser; the daemon reads JSON like it does\n * for the primary binding). `ops` is persisted from the accept-invite\n * response so the daemon can refuse a token that lacks read+subscribe.\n */\nexport type ImportMountFile = {\n bindingId: string;\n relayUrl: string;\n token: string;\n ops: ReadonlyArray<\"read\" | \"write\" | \"subscribe\">;\n /** Mount point: subdirectory relative to the rig root (posix). */\n path: string;\n /** Import-edge id from the relay (inter-rig graph). Lets `rig import remove`\n * tombstone the exact edge. Absent for anonymous imports / pre-edge mounts. */\n edgeId?: string;\n};\n\nexport type ImportValidationError = { name: string; path: string; reason: string };\n\n/**\n * Slug rule for `import.name`. Dots are banned outright, which by\n * construction rules out every \"confusing suffix\" game (a name can never\n * end `.local.json` or collide with the `<name>.local.json` token file /\n * `<name>/state.local.db` dir). No separators, no `..`, no leading dot.\n */\nconst NAME_RE = /^[a-z0-9][a-z0-9_-]*$/;\n\nexport function validateImportName(name: string): string | null {\n if (!name) return \"empty_name\";\n if (!NAME_RE.test(name)) {\n return \"invalid_name (must match ^[a-z0-9][a-z0-9_-]*$ — lowercase slug, no dots/slashes/..)\";\n }\n return null;\n}\n\n/**\n * Canonicalize a declared import path to a posix form with `.` and empty\n * segments collapsed (`./sources/alex` and `sources/./alex` → `sources/alex`).\n * `..` is deliberately preserved so validateImportPath can reject it. This\n * canonical form is what the daemon uses for BOTH the mirror root and the\n * primary writer's exclude set — they must be byte-identical or a mirror\n * could materialize at `sources/alex` while the writer excludes\n * `./sources/alex` (which never matches walker paths) and re-uploads it.\n */\nexport function normalizeImportPath(p: string): string {\n return p\n .replace(/\\\\/g, \"/\")\n .split(\"/\")\n .filter((s) => s !== \"\" && s !== \".\")\n .join(\"/\");\n}\n\n/**\n * Static + symlink validation of a single mount point. Returns a reason\n * string on rejection, or null if the path is a legal mount.\n *\n * The mount may not exist on disk yet (first materialization hasn't\n * run), so we can't realpath the full path. Instead we realpath the\n * nearest existing ancestor and re-attach the not-yet-created remainder,\n * then assert the result stays inside the (realpathed) rig root. This\n * catches a symlinked ancestor that escapes the root even before the\n * mount dir exists.\n */\nexport function validateImportPath(rootDir: string, importPath: string): string | null {\n if (!importPath) return \"empty_path\";\n if (isAbsolute(importPath)) return \"absolute_path\";\n const norm = normalizeImportPath(importPath);\n if (norm === \"\" || norm === \".\") return \"root_path (cannot mount over the rig root)\";\n\n const segs = norm.split(\"/\");\n if (segs.some((s) => s === \"..\")) return \"dotdot_in_path\";\n if (segs.some((s) => s === \"\")) return \"empty_segment\";\n if (segs[0] === \".rig\") return \"reserved_rig_path\";\n\n // Realpath the root for an apples-to-apples containment check — on\n // macOS tmpdir() itself is a symlink (/var → /private/var).\n const realRoot = realpathSync(rootDir);\n const abs = join(realRoot, norm);\n\n // Lexical escape (belt to the symlink suspenders below).\n if (!isWithin(realRoot, abs)) return \"escapes_root\";\n\n // Symlink escape: resolve the nearest existing ancestor.\n const realMount = realpathNearestExisting(abs);\n if (!isWithin(realRoot, realMount)) return \"symlink_escape\";\n\n // Ancestor-is-file: a mirror must be able to mkdir the mount path. If\n // any existing ancestor (or the mount itself) is a FILE, materialization\n // would hit ENOTDIR; applyOnce then advances past the failed event and\n // silently drops source content. Catches \"primary tracks `sources` as a\n // file, import mounts at `sources/alex`\" (a parent collision that the\n // tracked-path string check in validateImports can't see). The primary's\n // content is materialized before imports validate, so a file ancestor is\n // on disk by the time we check.\n const fileAncestor = firstExistingFileOnPath(abs);\n if (fileAncestor) {\n return fileAncestor === abs ? \"mount_path_is_file\" : \"ancestor_is_file\";\n }\n\n return null;\n}\n\n/**\n * Validate a batch of imports against each other and against the\n * primary's already-tracked content. Never throws — returns the imports\n * that passed plus a structured error per rejection, so the orchestrator\n * can degrade a bad mount without taking down the daemon.\n */\nexport function validateImports(opts: {\n rootDir: string;\n imports: ReadonlyArray<ImportSpec>;\n /** Paths the primary binding already tracks (state-db listPaths). */\n primaryTrackedPaths: ReadonlyArray<string>;\n}): { valid: ImportSpec[]; errors: ImportValidationError[] } {\n const valid: ImportSpec[] = [];\n const errors: ImportValidationError[] = [];\n const seenNames = new Set<string>();\n const acceptedPaths: string[] = [];\n\n for (const imp of opts.imports) {\n const reject = (reason: string) =>\n errors.push({ name: imp.name, path: imp.path, reason });\n\n const nameErr = validateImportName(imp.name);\n if (nameErr) {\n reject(nameErr);\n continue;\n }\n if (seenNames.has(imp.name)) {\n reject(\"duplicate_name\");\n continue;\n }\n const pathErr = validateImportPath(opts.rootDir, imp.path);\n if (pathErr) {\n reject(pathErr);\n continue;\n }\n const norm = normalizeImportPath(imp.path);\n\n // No two imports may overlap (one a prefix of the other).\n if (acceptedPaths.some((p) => pathsOverlap(p, norm))) {\n reject(\"overlaps_other_import\");\n continue;\n }\n\n // Must not mount over content the primary already tracks — mirror\n // mode would delete/overwrite the user's real work. A deliberate\n // migration flow could opt in later; default is refuse.\n if (opts.primaryTrackedPaths.some((tp) => tp === norm || tp.startsWith(norm + \"/\"))) {\n reject(\"over_primary_content\");\n continue;\n }\n\n seenNames.add(imp.name);\n acceptedPaths.push(norm);\n // Return the CANONICAL path, not imp.path — start() uses this for both\n // the mirror root and the writer's exclude set, which must match.\n valid.push({ ...imp, path: norm });\n }\n\n return { valid, errors };\n}\n\n// ────────── on-disk path helpers ──────────\n\n/** `.rig/tap/state.local.db` — the primary binding's cursor/state. */\nexport function primaryStateDbPath(rootDir: string): string {\n return join(rootDir, \".rig\", \"tap\", \"state.local.db\");\n}\n\n/** `.rig/tap/imports/<name>/state.local.db` — per-import cursor/state. */\nexport function importStateDbPath(rootDir: string, name: string): string {\n return join(rootDir, \".rig\", \"tap\", \"imports\", name, \"state.local.db\");\n}\n\n/** `.rig/tap/imports` — holds `<name>.local.json` token files + `<name>/` state dirs. */\nexport function importsDir(rootDir: string): string {\n return join(rootDir, \".rig\", \"tap\", \"imports\");\n}\n\n/** `.rig/tap/imports/<name>.local.json` — per-import token file. */\nexport function importMountFile(rootDir: string, name: string): string {\n return join(importsDir(rootDir), `${name}.local.json`);\n}\n\nconst MOUNT_FILE_SUFFIX = \".local.json\";\n\nexport function writeImportMount(\n rootDir: string,\n name: string,\n mount: ImportMountFile,\n): void {\n const path = importMountFile(rootDir, name);\n mkdirSync(dirname(path), { recursive: true });\n // 0o600 — carries a capability secret, same as tap-binding.local.json.\n writeFileSync(path, JSON.stringify(mount, null, 2) + \"\\n\", { mode: 0o600 });\n}\n\n/**\n * Read + validate a single mount file. Returns the spec on success or a\n * structured reason on failure — never throws, so one broken mount can be\n * degraded without taking down the daemon (gotcha #7).\n */\nexport function readImportMount(\n rootDir: string,\n name: string,\n): { ok: true; spec: ImportSpec } | { ok: false; reason: string } {\n const file = importMountFile(rootDir, name);\n if (!existsSync(file)) return { ok: false, reason: \"missing_token_file\" };\n let parsed: Partial<ImportMountFile>;\n try {\n parsed = JSON.parse(readFileSync(file, \"utf8\")) as Partial<ImportMountFile>;\n } catch {\n return { ok: false, reason: \"malformed_json\" };\n }\n if (typeof parsed.bindingId !== \"string\" || parsed.bindingId === \"\") {\n return { ok: false, reason: \"missing_bindingId\" };\n }\n if (typeof parsed.relayUrl !== \"string\" || parsed.relayUrl === \"\") {\n return { ok: false, reason: \"missing_relayUrl\" };\n }\n if (typeof parsed.token !== \"string\" || parsed.token === \"\") {\n return { ok: false, reason: \"missing_token\" };\n }\n if (typeof parsed.path !== \"string\" || parsed.path === \"\") {\n return { ok: false, reason: \"missing_path\" };\n }\n // Token-vs-capability check: a mirror needs read AND subscribe. Refuse\n // a write-only/owner token pasted in, or a too-narrow scope.\n const ops = Array.isArray(parsed.ops) ? parsed.ops : [];\n if (!ops.includes(\"read\") || !ops.includes(\"subscribe\")) {\n return { ok: false, reason: \"insufficient_ops (need read+subscribe)\" };\n }\n return {\n ok: true,\n spec: {\n name,\n bindingId: parsed.bindingId,\n relayUrl: parsed.relayUrl,\n token: parsed.token,\n path: parsed.path,\n },\n };\n}\n\n/**\n * Enumerate `.rig/tap/imports/*.local.json` and resolve each to an\n * ImportSpec. Broken mount files are reported, not thrown. This is the\n * daemon's source of truth for which mirrors to run — read fresh at every\n * `tapd start` (v1 \"reload\" = restart).\n */\nexport function loadImportMounts(rootDir: string): {\n ready: ImportSpec[];\n broken: ImportValidationError[];\n} {\n const dir = importsDir(rootDir);\n const ready: ImportSpec[] = [];\n const broken: ImportValidationError[] = [];\n if (!existsSync(dir)) return { ready, broken };\n\n for (const ent of readdirSync(dir, { withFileTypes: true })) {\n if (!ent.isFile() || !ent.name.endsWith(MOUNT_FILE_SUFFIX)) continue;\n const name = ent.name.slice(0, -MOUNT_FILE_SUFFIX.length);\n const nameErr = validateImportName(name);\n if (nameErr) {\n broken.push({ name, path: \"\", reason: nameErr });\n continue;\n }\n const res = readImportMount(rootDir, name);\n if (!res.ok) {\n broken.push({ name, path: \"\", reason: res.reason });\n continue;\n }\n ready.push(res.spec);\n }\n return { ready, broken };\n}\n\n// ────────── internals ──────────\n\nfunction isWithin(parent: string, child: string): boolean {\n const rel = relative(parent, child);\n return rel === \"\" || (!rel.startsWith(\"..\") && !isAbsolute(rel));\n}\n\nfunction realpathNearestExisting(abs: string): string {\n let cur = abs;\n while (!existsSync(cur)) {\n const parent = dirname(cur);\n if (parent === cur) break;\n cur = parent;\n }\n const realExisting = realpathSync(cur);\n const remainder = relative(cur, abs);\n return remainder ? join(realExisting, remainder) : realExisting;\n}\n\n/**\n * Walk up from `abs` to the first path that exists on disk. Returns it if\n * it's a file (a collision — nothing can be created beneath a file), else\n * null. Once an existing *directory* is found we stop: on a real\n * filesystem its ancestors are all directories too.\n */\nfunction firstExistingFileOnPath(abs: string): string | null {\n let cur = abs;\n for (;;) {\n if (existsSync(cur)) {\n return statSync(cur).isFile() ? cur : null;\n }\n const parent = dirname(cur);\n if (parent === cur) return null;\n cur = parent;\n }\n}\n\nfunction pathsOverlap(a: string, b: string): boolean {\n return a === b || a.startsWith(b + \"/\") || b.startsWith(a + \"/\");\n}\n","/**\n * `tapd import` — accept an invite to another binding and mount it\n * read-only into a subdirectory of this rig (see rig-imports.md).\n *\n * Mirror of `join.ts`, but instead of making this folder a binding root\n * it writes a per-import mount file (`.rig/tap/imports/<name>.local.json`)\n * that the daemon picks up as a mirror subscription. Nothing about the\n * primary binding (if any) changes.\n *\n * 1. Canonicalize + validate the mount (name slug, path, symlink/parent\n * collisions, overlap with other mounts + primary content) BEFORE any\n * network call — reusing validateImports so the rules can't drift.\n * 2. Parse the invite URL → (baseUrl, secret).\n * 3. acceptInvite → { bindingId, token: { secret, ops } }.\n * 4. Refuse a token without read+subscribe (a mirror can't run on it).\n * 5. Write the mount file (mode 0600). The daemon must be (re)started to\n * pick it up — v1 \"reload\" = restart.\n */\n\nimport { existsSync, readFileSync, rmSync } from \"node:fs\";\nimport { dirname, join as joinPath } from \"node:path\";\nimport type { AcceptInviteResponse } from \"@tap/core\";\nimport { readBindingConfig } from \"./binding-config.js\";\nimport {\n importMountFile,\n importStateDbPath,\n loadImportMounts,\n normalizeImportPath,\n primaryStateDbPath,\n readImportMount,\n validateImports,\n writeImportMount,\n type ImportSpec,\n} from \"./imports.js\";\nimport { parseInviteUrl } from \"./join.js\";\nimport { acceptInvite, revokeImportEdge, RelayError } from \"./relay-client.js\";\nimport { openStateDb } from \"./state-db.js\";\n\nexport class ImportValidationFailed extends Error {\n constructor(public readonly reason: string) {\n super(`import rejected: ${reason}`);\n this.name = \"ImportValidationFailed\";\n }\n}\n\nexport class ImportTokenScopeError extends Error {\n constructor(public readonly ops?: ReadonlyArray<string>) {\n super(\n ops\n ? `invite token has ops [${ops.join(\", \")}] — a mirror needs read+subscribe`\n : `invite can't drive a mirror — it needs read+subscribe`,\n );\n this.name = \"ImportTokenScopeError\";\n }\n}\n\nexport type AddImportOptions = {\n rootDir: string;\n inviteUrl: string;\n /** Mount point (subdir, relative to the rig root). */\n path: string;\n /** Slug; defaults to the last path segment. */\n name?: string;\n accessToken?: string | null;\n deviceLabel?: string;\n fetch?: typeof fetch;\n};\n\nexport type AddImportResult = {\n name: string;\n path: string;\n bindingId: string;\n ops: ReadonlyArray<\"read\" | \"write\" | \"subscribe\">;\n /** Import-edge id recorded by the relay (absent for anonymous imports). */\n edgeId?: string;\n /** False when we sent our binding as the importer but the relay couldn't\n * verify membership — the edge was recorded as a personal import (you → S),\n * not rig → S. The CLI surfaces this as a warning. Undefined when we sent no\n * binding (import-only) or no edge was recorded. */\n importerBindingVerified?: boolean;\n};\n\nfunction isRecord(v: unknown): v is Record<string, unknown> {\n return typeof v === \"object\" && v !== null;\n}\n\n/** Last path segment, used as the default mount name. */\nfunction deriveName(canonicalPath: string): string {\n const segs = canonicalPath.split(\"/\").filter(Boolean);\n return segs[segs.length - 1] ?? \"\";\n}\n\nfunction readPrimaryTrackedPaths(rootDir: string): string[] {\n const dbPath = primaryStateDbPath(rootDir);\n if (!existsSync(dbPath)) return [];\n const db = openStateDb(dbPath);\n try {\n return db.listPaths();\n } finally {\n db.close();\n }\n}\n\nexport async function addImport(opts: AddImportOptions): Promise<AddImportResult> {\n const path = normalizeImportPath(opts.path);\n const name = opts.name && opts.name.length > 0 ? opts.name : deriveName(path);\n\n // Validate BEFORE accepting — reuse the daemon's validator so a path\n // that the daemon would reject is never persisted. We fold the new mount\n // into the existing set so overlap/duplicate checks see everything; the\n // candidate carries placeholder connection fields (validateImports only\n // inspects name + path).\n const existing = loadImportMounts(opts.rootDir).ready;\n const candidate: ImportSpec = { name, bindingId: \"\", relayUrl: \"\", token: \"\", path };\n const { valid, errors } = validateImports({\n rootDir: opts.rootDir,\n imports: [...existing, candidate],\n primaryTrackedPaths: readPrimaryTrackedPaths(opts.rootDir),\n });\n if (!valid.some((v) => v.name === name && v.path === path)) {\n const err = errors.find((e) => e.name === name);\n throw new ImportValidationFailed(err?.reason ?? \"invalid_import\");\n }\n\n // Accept the invite (throws InvalidInviteUrlError on a bad URL). Send our\n // own binding (if bound) as the importer so the relay can record a rig → S\n // edge — it verifies our membership before trusting the claim. Import-only\n // meta-rigs omit it and get a user → S edge.\n const primaryBindingId = readBindingConfig(opts.rootDir)?.bindingId;\n const { baseUrl, secret } = parseInviteUrl(opts.inviteUrl);\n let accepted: AcceptInviteResponse;\n try {\n accepted = await acceptInvite({\n baseUrl,\n secret,\n accessToken: opts.accessToken ?? null,\n request: {\n ...(opts.deviceLabel ? { deviceLabel: opts.deviceLabel } : {}),\n importer: { ...(primaryBindingId ? { bindingId: primaryBindingId } : {}), path },\n },\n fetch: opts.fetch,\n });\n } catch (err) {\n // The relay rejects an import accept whose invite lacks read+subscribe\n // BEFORE creating any device/token/edge. Map that to the same error the\n // local post-accept check would raise.\n if (\n err instanceof RelayError &&\n isRecord(err.body) &&\n err.body.reason === \"import_requires_read_subscribe\"\n ) {\n throw new ImportTokenScopeError();\n }\n throw err;\n }\n\n // Fallback for older relays that don't reject server-side (and defence in\n // depth): refuse to write a mount the daemon couldn't run.\n const ops = accepted.token.ops;\n if (!ops.includes(\"read\") || !ops.includes(\"subscribe\")) {\n throw new ImportTokenScopeError(ops);\n }\n\n writeImportMount(opts.rootDir, name, {\n bindingId: accepted.bindingId,\n relayUrl: baseUrl,\n token: accepted.token.secret,\n ops,\n path,\n ...(accepted.importEdgeId ? { edgeId: accepted.importEdgeId } : {}),\n });\n\n return {\n name,\n path,\n bindingId: accepted.bindingId,\n ops,\n ...(accepted.importEdgeId ? { edgeId: accepted.importEdgeId } : {}),\n // Only meaningful when we actually sent a binding to verify.\n ...(primaryBindingId !== undefined\n ? { importerBindingVerified: accepted.importerBindingVerified ?? false }\n : {}),\n };\n}\n\n/**\n * Outcome of the best-effort server-side revoke half of a remove:\n * - revoked — edge tombstoned + token revoked on the relay\n * - not-creator — 403: the grant belongs to another user; left active\n * - gone — 404: already revoked/removed server-side\n * - no-edge — anonymous / pre-edge mount; nothing to revoke\n * - no-token — not logged in; can't reach the user plane (local-only)\n * - offline — relay unreachable / unexpected error\n */\nexport type RemoveImportRevoke =\n | \"revoked\"\n | \"not-creator\"\n | \"gone\"\n | \"no-edge\"\n | \"no-token\"\n | \"offline\";\n\nexport type RemoveImportResult = {\n /** False if there was no mount file by that name. */\n removed: boolean;\n /** The mount point that was removed (for an optional purge), if known. */\n path: string | null;\n /** Whether the materialized subdir was deleted (--purge). */\n purged: boolean;\n /** What happened to the server-side grant. */\n revoke: RemoveImportRevoke;\n};\n\nfunction readRawMount(\n rootDir: string,\n name: string,\n): { edgeId?: string; relayUrl?: string; path?: string } | null {\n const file = importMountFile(rootDir, name);\n if (!existsSync(file)) return null;\n try {\n return JSON.parse(readFileSync(file, \"utf8\"));\n } catch {\n return null;\n }\n}\n\n/**\n * Remove a mount: relinquish the grant on the relay (best-effort), then delete\n * the local token file + per-import state dir. With `purge`, also delete the\n * materialized subdir. The local removal ALWAYS happens — even offline, even\n * when the grant isn't ours — so the daemon stops mirroring locally. The\n * caller restarts the daemon. See docs/inter-rig-graph.md §7.\n */\nexport async function removeImport(opts: {\n rootDir: string;\n name: string;\n purge?: boolean;\n /** Hub token for the self-revoke. Omit → local-only (\"no-token\"). */\n accessToken?: string | null;\n fetch?: typeof fetch;\n}): Promise<RemoveImportResult> {\n const mountPath = importMountFile(opts.rootDir, opts.name);\n const res = readImportMount(opts.rootDir, opts.name);\n const raw = readRawMount(opts.rootDir, opts.name);\n const path = res.ok ? res.spec.path : raw?.path ?? null;\n\n // Best-effort server-side revoke (Layer 2), BEFORE deleting the file.\n let revoke: RemoveImportRevoke;\n if (!raw?.edgeId || !raw.relayUrl) {\n revoke = \"no-edge\";\n } else if (!opts.accessToken) {\n revoke = \"no-token\";\n } else {\n try {\n const r = await revokeImportEdge({\n baseUrl: raw.relayUrl,\n edgeId: raw.edgeId,\n accessToken: opts.accessToken,\n fetch: opts.fetch,\n });\n revoke = r.revoked\n ? \"revoked\"\n : r.status === 403\n ? \"not-creator\"\n : r.status === 404\n ? \"gone\"\n : \"offline\";\n } catch {\n revoke = \"offline\";\n }\n }\n\n let removed = false;\n if (existsSync(mountPath)) {\n rmSync(mountPath, { force: true });\n removed = true;\n }\n // Per-import state dir: .rig/tap/imports/<name>/\n rmSync(dirname(importStateDbPath(opts.rootDir, opts.name)), {\n recursive: true,\n force: true,\n });\n\n let purged = false;\n if (opts.purge && path) {\n rmSync(joinPath(opts.rootDir, path), { recursive: true, force: true });\n purged = true;\n }\n\n return { removed, path, purged, revoke };\n}\n","/**\n * Apply remote change events to the local filesystem.\n *\n * Phase 2 keeps this simple: last-write-wins, no conflict materialization\n * (Phase 4 adds it). Echo suppression is handled via the state DB:\n * if the remote event's hash matches our last_seen_hash for the path,\n * we know it's our own write coming back through the poll loop and skip\n * the download.\n *\n * Materialization is atomic: download to `<file>.tap-tmp.<rand>` then\n * `rename` into place. Atomic on POSIX; on Windows, `fs.rename` falls\n * back to MoveFileEx-style replace.\n */\n\nimport { createHash, randomBytes } from \"node:crypto\";\nimport {\n existsSync,\n lstatSync,\n mkdirSync,\n readdirSync,\n readFileSync,\n renameSync,\n rmdirSync,\n rmSync,\n writeFileSync,\n type Dirent,\n} from \"node:fs\";\nimport { basename, dirname, extname, join, sep } from \"node:path\";\nimport type { ChangeEvent } from \"@tap/core\";\nimport type { RelayClient } from \"./relay-client.js\";\nimport type { StateDb } from \"./state-db.js\";\nimport { safeResolveRigPath } from \"./safe-path.js\";\n\n/** State-DB meta key — running count of conflicts surfaced by `tapd status`. */\nexport const STATE_KEY_CONFLICT_COUNT = \"conflict_count\";\n\n/**\n * Thrown when downloaded bytes don't hash to the value we trusted via\n * the change event. Caught by applyOnce so the cursor still advances\n * (the daemon would otherwise spin forever).\n */\nexport class HashMismatchError extends Error {\n constructor(\n public readonly path: string,\n public readonly expectedHash: string,\n public readonly actualHash: string,\n ) {\n super(\n `hash mismatch at ${path}: expected ${expectedHash}, got ${actualHash}`,\n );\n this.name = \"HashMismatchError\";\n }\n}\n\nfunction sha256Hex(bytes: Buffer | Uint8Array): string {\n return createHash(\"sha256\").update(bytes).digest(\"hex\");\n}\n\nfunction sha256OfFile(absPath: string): string {\n return `sha256:${sha256Hex(readFileSync(absPath))}`;\n}\n\n/**\n * Sidecar path for a conflict materialization. Original extension is\n * preserved at the end so editor highlighting still works.\n *\n * decisions/pricing.md → decisions/pricing.conflict-from.dev_xyz.chg_456.md\n *\n * Device label resolution lands when GET /v1/bindings/:id/devices does\n * (Phase 5); until then we use the raw deviceId, which is at least an\n * audit-stable reference.\n */\nexport function conflictSidecarPath(\n path: string,\n ev: { id: string; actorDeviceId: string | null },\n): string {\n const dir = dirname(path);\n const ext = extname(path);\n const stem = basename(path, ext);\n const actor = ev.actorDeviceId ?? \"unknown\";\n const sidecar = `${stem}.conflict-from.${actor}.${ev.id}${ext}`;\n return dir === \".\" ? sidecar : `${dir}/${sidecar}`;\n}\n\n/**\n * Inverse of conflictSidecarPath — given a sidecar filename, recover the\n * original path it was forked from. Returns null for any filename that\n * doesn't match the sidecar format. Used by `tapd status` to surface\n * conflicts to rig.\n */\nconst SIDECAR_RE = /^(.*)\\.conflict-from\\.[^.]+\\.(chg_\\d+)(\\.[^.]+)?$/;\nexport function parseConflictSidecar(\n filename: string,\n): { stem: string; ext: string; chgId: string } | null {\n const m = SIDECAR_RE.exec(filename);\n if (!m) return null;\n return { stem: m[1]!, chgId: m[2]!, ext: m[3] ?? \"\" };\n}\n\n/**\n * Recursively walk `rootDir` for `*.conflict-from.*` sidecars. Bypasses\n * .tapignore (sidecars are in the default ignore list — that's why\n * scanAndPush doesn't push them — but we still want `tapd status` to\n * surface them). Skips `.rig/`, `.git/`, `node_modules/` for speed.\n */\nconst SCAN_SKIP_DIRS = new Set([\".git\", \"node_modules\", \".rig\"]);\nexport function findConflictSidecars(\n rootDir: string,\n): Array<{ path: string; sidecarPath: string }> {\n const out: Array<{ path: string; sidecarPath: string }> = [];\n walk(\".\");\n return out;\n\n function walk(rel: string): void {\n const abs = rel === \".\" ? rootDir : join(rootDir, rel);\n let entries: Dirent[];\n try {\n entries = readdirSync(abs, { withFileTypes: true });\n } catch {\n return;\n }\n for (const ent of entries) {\n if (ent.isSymbolicLink()) continue;\n const childRel = rel === \".\" ? ent.name : join(rel, ent.name);\n if (ent.isDirectory()) {\n if (SCAN_SKIP_DIRS.has(ent.name)) continue;\n walk(childRel);\n continue;\n }\n if (!ent.isFile()) continue;\n const parsed = parseConflictSidecar(ent.name);\n if (!parsed) continue;\n const sidecarPosix = sep === \"/\" ? childRel : childRel.replaceAll(sep, \"/\");\n const dir = dirname(sidecarPosix);\n const original = dir === \".\" ? `${parsed.stem}${parsed.ext}` : `${dir}/${parsed.stem}${parsed.ext}`;\n out.push({ path: original, sidecarPath: sidecarPosix });\n }\n }\n}\n\nfunction bumpConflictCount(stateDb: StateDb): void {\n const current = Number.parseInt(\n stateDb.getMeta(STATE_KEY_CONFLICT_COUNT) ?? \"0\",\n 10,\n );\n stateDb.setMeta(STATE_KEY_CONFLICT_COUNT, String(current + 1));\n}\n\n/**\n * Apply mode.\n *\n * - `primary`: the local copy has write authority. Local-divergence\n * guards protect unpushed edits (sidecars, keep-on-delete, refuse to\n * clobber a dir) on the assumption scanAndPush carries them upstream.\n * - `mirror`: a read-only import of another binding. The mount has no\n * local authority, so **remote always wins** across every event type —\n * every divergence guard flips to remote-authoritative. There is no\n * scanAndPush to drain sidecars or re-submit kept files, so the primary\n * guards would instead let edited imported content linger forever.\n */\nexport type ApplyMode = \"primary\" | \"mirror\";\n\nexport type ApplyEventError = {\n eventId: string;\n path: string;\n op: string;\n reason: string;\n};\n\nexport type ApplyResult = {\n applied: number;\n echoSkipped: number;\n errored: number;\n events: ReadonlyArray<ChangeEvent>;\n errors: ReadonlyArray<ApplyEventError>;\n};\n\nconst STATE_KEY_LAST_APPLY_ERROR = \"last_apply_error\";\nconst STATE_KEY_LAST_APPLY_ERROR_AT = \"last_apply_error_at\";\n\n/**\n * One iteration of the apply loop: poll `GET /changes?after=<cursor>`,\n * materialize each event, update the cursor.\n *\n * Per-event errors (hash mismatch, file/directory collision, disk full,\n * etc.) are caught, logged, and recorded to the state DB — the cursor\n * still advances past the failed event so the daemon doesn't loop\n * forever on the same broken row. Phase 4 conflict materialization\n * will replace the \"skip + log\" behavior for the file/dir collision\n * case; for now it's the safe default.\n */\nexport async function applyOnce(opts: {\n rootDir: string;\n client: RelayClient;\n stateDb: StateDb;\n limit?: number;\n /** Defaults to \"primary\". See ApplyMode. */\n mode?: ApplyMode;\n}): Promise<ApplyResult> {\n const mode = opts.mode ?? \"primary\";\n const cursor = opts.stateDb.getCursor();\n const since = await opts.client.listChanges({ after: cursor, limit: opts.limit });\n let applied = 0;\n let echoSkipped = 0;\n let errored = 0;\n const errors: ApplyEventError[] = [];\n\n for (const ev of since.events) {\n try {\n const wasEcho = await applyEvent({\n rootDir: opts.rootDir,\n client: opts.client,\n stateDb: opts.stateDb,\n event: ev,\n mode,\n });\n if (wasEcho) echoSkipped += 1;\n else applied += 1;\n } catch (err) {\n const reason = err instanceof Error ? err.message : String(err);\n const recorded: ApplyEventError = {\n eventId: ev.id,\n path: ev.path,\n op: ev.op,\n reason,\n };\n errors.push(recorded);\n errored += 1;\n opts.stateDb.setMeta(STATE_KEY_LAST_APPLY_ERROR, JSON.stringify(recorded));\n opts.stateDb.setMeta(STATE_KEY_LAST_APPLY_ERROR_AT, new Date().toISOString());\n // Surface to the operator. We keep going so a single broken event\n // doesn't wedge the rest of the queue.\n console.error(\n `tapd apply: skipping ${ev.id} (${ev.op} ${ev.path}): ${reason}`,\n );\n }\n // Always advance the cursor, regardless of success/skip/error.\n // Crash-mid-batch resumes from the last-touched event, not the start.\n opts.stateDb.setCursor(ev.id);\n }\n\n return { applied, echoSkipped, errored, events: since.events, errors };\n}\n\nasync function applyEvent(opts: {\n rootDir: string;\n client: RelayClient;\n stateDb: StateDb;\n event: ChangeEvent;\n mode: ApplyMode;\n}): Promise<boolean> {\n const { rootDir, client, stateDb, event: ev, mode } = opts;\n const mirror = mode === \"mirror\";\n const abs = safeResolveRigPath(rootDir, ev.path);\n\n switch (ev.op) {\n case \"write\": {\n if (!ev.hash) throw new Error(`write event ${ev.id} missing hash`);\n const local = stateDb.getPath(ev.path);\n if (local?.lastSeenHash === ev.hash && existsSync(abs)) {\n // Echo: we already have these bytes (likely our own write\n // coming back through the change stream).\n stateDb.upsertPath({\n path: ev.path,\n lastAppliedChangeId: ev.id,\n lastSeenHash: ev.hash,\n localDirty: false,\n lastScanAt: new Date().toISOString(),\n });\n return true;\n }\n\n // File-vs-directory collision: a file write lands where a local\n // directory sits.\n // primary → refuse to clobber (caught by applyOnce, recorded,\n // cursor still advances).\n // mirror → remote wins: replace the directory with the file.\n if (existsSync(abs) && lstatSync(abs).isDirectory()) {\n if (!mirror) {\n throw new Error(\n `file_vs_dir_collision: ${ev.path} is a local directory; refusing to overwrite with a file`,\n );\n }\n rmSync(abs, { recursive: true, force: true });\n }\n\n // Local-divergence detection. If the local file's bytes differ\n // from our recorded last_seen_hash AND we have a recorded\n // baseline (i.e., we've seen this path before), the user edited\n // locally without pushing yet. Applying the remote would\n // overwrite those edits — instead, materialize the remote as a\n // conflict sidecar and leave local alone. The next scanAndPush\n // pushes the local edit upstream as a normal change event.\n //\n // mirror mode has no local authority and no scanAndPush, so we\n // never take the sidecar branch — remote always wins (plain\n // overwrite below).\n const localDirty =\n !mirror &&\n existsSync(abs) &&\n local?.lastSeenHash != null &&\n sha256OfFile(abs) !== local.lastSeenHash;\n\n const dl = await client.downloadUrl(ev.hash);\n const bytes = await client.getObjectBytes(dl.downloadUrl);\n // Hash-verify the downloaded bytes BEFORE any disk touch.\n const actualHash = `sha256:${sha256Hex(bytes)}`;\n if (actualHash !== ev.hash) {\n throw new HashMismatchError(ev.path, ev.hash, actualHash);\n }\n\n if (localDirty) {\n const sidecarPath = conflictSidecarPath(ev.path, ev);\n const sidecarAbs = safeResolveRigPath(rootDir, sidecarPath);\n mkdirSync(dirname(sidecarAbs), { recursive: true });\n const tmp = `${sidecarAbs}.tap-tmp.${randomBytes(6).toString(\"hex\")}`;\n writeFileSync(tmp, bytes, { mode: ev.executable ? 0o755 : 0o644 });\n renameSync(tmp, sidecarAbs);\n // Cursor advances + lastAppliedChangeId updates, but\n // lastSeenHash is NOT updated — local file still differs from\n // baseline, so the next scanAndPush sees the unpushed local\n // edit and submits it (with baseChangeId = ev.id, so the relay\n // can record the causality).\n stateDb.upsertPath({\n path: ev.path,\n lastAppliedChangeId: ev.id,\n lastSeenHash: local!.lastSeenHash,\n localDirty: true,\n lastScanAt: new Date().toISOString(),\n });\n bumpConflictCount(stateDb);\n return false;\n }\n\n mkdirSync(dirname(abs), { recursive: true });\n // Atomic rename — tmp file in the same dir so it's the same fs/volume.\n const tmp = `${abs}.tap-tmp.${randomBytes(6).toString(\"hex\")}`;\n writeFileSync(tmp, bytes, { mode: ev.executable ? 0o755 : 0o644 });\n renameSync(tmp, abs);\n stateDb.upsertPath({\n path: ev.path,\n lastAppliedChangeId: ev.id,\n lastSeenHash: ev.hash,\n localDirty: false,\n lastScanAt: new Date().toISOString(),\n });\n return false;\n }\n case \"delete\": {\n if (existsSync(abs)) {\n if (lstatSync(abs).isDirectory()) {\n // primary → refuse recursive removal of a local directory.\n // mirror → remote wins: remove the directory recursively.\n if (!mirror) {\n throw new Error(\n `delete_vs_dir: ${ev.path} is a local directory; refusing recursive removal`,\n );\n }\n rmSync(abs, { recursive: true, force: true });\n stateDb.deletePath(ev.path);\n return false;\n }\n // Symmetric to write: if the user edited the file locally\n // since we last synced (or if there's no baseline at all but\n // the file is on disk), don't destroy their unpushed work.\n // Keep the file. Record state with lastSeenHash=null and\n // lastAppliedChangeId = ev.id so the next scanAndPush detects\n // the file as \"needs push\" (hash !== null), submits a write\n // event with baseChangeId = ev.id (the delete), and the relay\n // resurrects the file on peers with proper causality.\n //\n // mirror mode skips this guard entirely (no local authority,\n // no scanAndPush) — remote always wins, the file is removed.\n if (!mirror) {\n const localStateRow = stateDb.getPath(ev.path);\n const localBytesHash = sha256OfFile(abs);\n const localDirty =\n localStateRow?.lastSeenHash != null &&\n localBytesHash !== localStateRow.lastSeenHash;\n const localUntracked = localStateRow?.lastSeenHash == null;\n if (localDirty || localUntracked) {\n stateDb.upsertPath({\n path: ev.path,\n lastAppliedChangeId: ev.id,\n // null so scanAndPush's `prev?.lastSeenHash === hash` check\n // falls through and the file gets re-submitted as a write.\n lastSeenHash: null,\n localDirty: true,\n lastScanAt: new Date().toISOString(),\n });\n bumpConflictCount(stateDb);\n // Cursor advance happens in applyOnce regardless of return value.\n return false;\n }\n }\n rmSync(abs, { force: true });\n }\n stateDb.deletePath(ev.path);\n return false;\n }\n case \"mkdir\": {\n mkdirSync(abs, { recursive: true });\n stateDb.upsertPath({\n path: ev.path,\n lastAppliedChangeId: ev.id,\n lastSeenHash: null,\n localDirty: false,\n lastScanAt: new Date().toISOString(),\n });\n return false;\n }\n case \"rmdir\": {\n if (mirror) {\n // Remote wins: remove the directory even if the mount has\n // accumulated stray content. A read-only import must converge\n // to the source — a lingering non-empty dir would never drain.\n rmSync(abs, { recursive: true, force: true });\n } else {\n // Best-effort, non-recursive: if the user has put new content\n // inside since the rmdir was issued, leave it alone. rmdirSync is\n // the right primitive — fs.rmSync requires recursive:true for any\n // directory, even an empty one.\n try {\n rmdirSync(abs);\n } catch {\n // ignore — directory was non-empty or already gone\n }\n }\n stateDb.deletePath(ev.path);\n return false;\n }\n }\n}\n","import { isAbsolute, relative, resolve } from \"node:path\";\nimport { validateRigPath, type PathValidationError } from \"@tap/core\";\n\nexport class UnsafeRigPathError extends Error {\n constructor(\n public path: string,\n public reason: PathValidationError | \"outside_root\",\n ) {\n super(`Unsafe rig path \"${path}\": ${reason}`);\n this.name = \"UnsafeRigPathError\";\n }\n}\n\nexport function safeResolveRigPath(rootDir: string, path: string): string {\n const valid = validateRigPath(path);\n if (!valid.ok) throw new UnsafeRigPathError(path, valid.reason);\n\n const rootAbs = resolve(rootDir);\n const targetAbs = resolve(rootAbs, path);\n const rel = relative(rootAbs, targetAbs);\n if (rel === \"\" || rel.startsWith(\"..\") || isAbsolute(rel)) {\n throw new UnsafeRigPathError(path, \"outside_root\");\n }\n return targetAbs;\n}\n","/**\n * SSE consumer for the relay's /stream endpoint.\n *\n * Phase 4c — push delivery so applyOnce fires within milliseconds of a\n * remote change instead of waiting on the poll interval. The poll loop\n * remains the source of truth and keeps running underneath as a\n * fallback (slower cadence is fine when SSE is healthy).\n *\n * Reconnect semantics:\n * - Server-side disconnect (stream end) or client-side fetch error\n * → backoff 1s, 2s, 4s, 8s, 16s, capped at 30s; reset on success.\n * - Heartbeat watchdog: if no event/heartbeat lands within\n * `heartbeatTimeoutMs` (default 45s), abort + reconnect.\n * - `stop()` aborts the in-flight fetch + cancels pending reconnects.\n *\n * The subscriber NEVER advances the cursor — it just nudges applyOnce.\n * Cursor management stays in the apply loop, which reads /changes as\n * the durable source of truth.\n */\n\nimport type { ChangeEvent } from \"@tap/core\";\n\ntype FetchLike = typeof fetch;\n\nexport type SseSubscriberOptions = {\n baseUrl: string;\n bindingId: string;\n token: string;\n onChange: (change: ChangeEvent) => void | Promise<void>;\n onSubscribed?: (info: { bindingId: string; pathGlobs: string[] }) => void;\n onError?: (err: Error) => void;\n fetch?: FetchLike;\n /** Watchdog window. Default 45s — matches a server heartbeat of 15s × 3. */\n heartbeatTimeoutMs?: number;\n /** Backoff for the Nth reconnect attempt (0-indexed). */\n reconnectDelayMs?: (attempt: number) => number;\n};\n\nexport type SseSubscriber = {\n stop(): Promise<void>;\n};\n\nconst DEFAULT_HEARTBEAT_TIMEOUT_MS = 45_000;\n\nfunction defaultBackoff(attempt: number): number {\n return Math.min(30_000, 1_000 * 2 ** attempt);\n}\n\nexport function subscribe(opts: SseSubscriberOptions): SseSubscriber {\n const fetchImpl = opts.fetch ?? fetch;\n const heartbeatTimeoutMs = opts.heartbeatTimeoutMs ?? DEFAULT_HEARTBEAT_TIMEOUT_MS;\n const backoff = opts.reconnectDelayMs ?? defaultBackoff;\n\n let stopped = false;\n let attempt = 0;\n let activeAbort: AbortController | null = null;\n let reconnectTimer: ReturnType<typeof setTimeout> | null = null;\n let loop: Promise<void>;\n\n const surface = (err: Error) => {\n if (opts.onError) opts.onError(err);\n else console.error(\"tapd sse:\", err.message);\n };\n\n const connect = async (): Promise<void> => {\n if (stopped) return;\n const ac = new AbortController();\n activeAbort = ac;\n const url = `${opts.baseUrl.replace(/\\/+$/, \"\")}/v1/bindings/${encodeURIComponent(opts.bindingId)}/stream`;\n\n let res: Response;\n try {\n res = await fetchImpl(url, {\n method: \"GET\",\n headers: {\n accept: \"text/event-stream\",\n authorization: `Bearer ${opts.token}`,\n },\n signal: ac.signal,\n });\n } catch (err) {\n if (stopped) return;\n surface(asError(err, \"sse_connect_failed\"));\n scheduleReconnect();\n return;\n }\n\n if (!res.ok || !res.body) {\n surface(new Error(`sse_bad_status:${res.status}`));\n scheduleReconnect();\n return;\n }\n\n // Success — reset backoff so a subsequent disconnect retries promptly.\n attempt = 0;\n await pumpStream(res.body, ac);\n if (stopped) return;\n // Stream ended without error → reconnect.\n scheduleReconnect();\n };\n\n const pumpStream = async (\n body: ReadableStream<Uint8Array>,\n ac: AbortController,\n ): Promise<void> => {\n const reader = body.getReader();\n const decoder = new TextDecoder();\n let buffer = \"\";\n let lastTick = Date.now();\n\n // Watchdog: bail out if no chunk arrives within heartbeatTimeoutMs.\n const watchdog = setInterval(() => {\n if (Date.now() - lastTick > heartbeatTimeoutMs) {\n surface(new Error(\"sse_heartbeat_timeout\"));\n ac.abort();\n }\n }, Math.max(1_000, heartbeatTimeoutMs / 5));\n\n try {\n while (!stopped) {\n let result: Awaited<ReturnType<typeof reader.read>>;\n try {\n result = await reader.read();\n } catch (err) {\n if (stopped) break;\n surface(asError(err, \"sse_read_failed\"));\n break;\n }\n if (result.done) break;\n lastTick = Date.now();\n buffer += decoder.decode(result.value, { stream: true });\n let idx: number;\n while ((idx = buffer.indexOf(\"\\n\\n\")) >= 0) {\n const frame = buffer.slice(0, idx);\n buffer = buffer.slice(idx + 2);\n await dispatchFrame(frame);\n }\n }\n } finally {\n clearInterval(watchdog);\n try {\n await reader.cancel();\n } catch {\n /* already cancelled */\n }\n }\n };\n\n const dispatchFrame = async (frame: string): Promise<void> => {\n if (frame.trim().length === 0) return;\n let event: string | undefined;\n let data: string | undefined;\n for (const line of frame.split(\"\\n\")) {\n if (line.startsWith(\":\")) continue;\n if (line.startsWith(\"event: \")) event = line.slice(7);\n else if (line.startsWith(\"data: \")) data = line.slice(6);\n }\n if (!event || !data) return;\n try {\n if (event === \"subscribed\" && opts.onSubscribed) {\n opts.onSubscribed(JSON.parse(data) as { bindingId: string; pathGlobs: string[] });\n } else if (event === \"change_created\") {\n await opts.onChange(JSON.parse(data) as ChangeEvent);\n } else if (event === \"heartbeat\") {\n // Already accounted for via watchdog tick reset.\n }\n } catch (err) {\n surface(asError(err, `sse_handler_failed:${event}`));\n }\n };\n\n const scheduleReconnect = () => {\n if (stopped) return;\n const delay = backoff(attempt++);\n reconnectTimer = setTimeout(() => {\n reconnectTimer = null;\n void connect();\n }, delay);\n };\n\n loop = connect();\n\n return {\n async stop() {\n stopped = true;\n if (reconnectTimer) {\n clearTimeout(reconnectTimer);\n reconnectTimer = null;\n }\n if (activeAbort) activeAbort.abort();\n await loop.catch(() => {});\n },\n };\n}\n\nfunction asError(err: unknown, fallbackName: string): Error {\n if (err instanceof Error) return err;\n const e = new Error(String(err));\n e.name = fallbackName;\n return e;\n}\n","/**\n * A single binding's apply loop: SSE subscriber + poll fallback +\n * apply-mutex → applyOnce. This is the \"download direction\" of the\n * daemon, extracted from start.ts so it can run N times — once for the\n * primary binding (mode \"primary\") and once per import (mode \"mirror\").\n *\n * It does NOT own the watcher or scanAndPush — that \"upload direction\"\n * stays attached only to the primary binding, in start.ts. A mirror is a\n * degenerate daemon: apply-only, no uploader.\n *\n * Each subscription owns its own state DB (separate cursor per binding)\n * and its own RelayClient. stop() closes the state DB, so the caller\n * must stop any writer that shares it FIRST.\n */\n\nimport { applyOnce, type ApplyMode, type ApplyResult } from \"./applier.js\";\nimport { RelayClient } from \"./relay-client.js\";\nimport { subscribe, type SseSubscriber } from \"./sse.js\";\nimport { openStateDb, type StateDb } from \"./state-db.js\";\n\n/** Minimal connection config — the apply side never needs deviceId. */\nexport type SubscriptionConfig = {\n bindingId: string;\n relayUrl: string;\n token: string;\n};\n\nexport type SubscriptionHandle = {\n /** Stop SSE + poll, drain the in-flight apply, then close the state DB. */\n stop(): Promise<void>;\n /** Force one apply cycle now (through the mutex). */\n applyNow(): Promise<ApplyResult>;\n /** Underlying state DB. Caller MUST NOT close it — stop() does. */\n stateDb: StateDb;\n};\n\nexport type StartSubscriptionOptions = {\n /** Directory this binding materializes into (primary root, or a mount subdir). */\n rootDir: string;\n config: SubscriptionConfig;\n /** Separate db per binding → separate cursor. */\n stateDbPath: string;\n mode: ApplyMode;\n fetch?: typeof fetch;\n pollSeconds?: number;\n /** Test seam — disable SSE (poll-only). */\n noSse?: boolean;\n sseHeartbeatTimeoutMs?: number;\n};\n\nconst DEFAULT_POLL_SECONDS = 3;\nconst SSE_APPLY_DEBOUNCE_MS = 50;\n\nexport async function startSubscription(\n opts: StartSubscriptionOptions,\n): Promise<SubscriptionHandle> {\n const { mode } = opts;\n const stateDb = openStateDb(opts.stateDbPath);\n const client = new RelayClient({\n baseUrl: opts.config.relayUrl,\n bindingId: opts.config.bindingId,\n token: opts.config.token,\n fetch: opts.fetch,\n });\n const pollSeconds = opts.pollSeconds ?? DEFAULT_POLL_SECONDS;\n\n let stopped = false;\n let pollTimer: ReturnType<typeof setTimeout> | undefined;\n let sseApplyTimer: ReturnType<typeof setTimeout> | undefined;\n let sseSubscriber: SseSubscriber | undefined;\n\n // ────────── apply mutex ──────────\n // SSE and poll both want to trigger applyOnce. Serialize: at most one\n // applyOnce in flight; a trigger arriving mid-flight sets \"rerun-after\"\n // so the next cycle picks up whatever landed since.\n let applyInFlight: Promise<ApplyResult | undefined> | null = null;\n let rerunRequested = false;\n let lastApplyResult: ApplyResult | undefined;\n const triggerApply = (): Promise<ApplyResult | undefined> => {\n if (applyInFlight) {\n rerunRequested = true;\n return applyInFlight;\n }\n const loop = (async () => {\n try {\n do {\n rerunRequested = false;\n try {\n lastApplyResult = await applyOnce({\n rootDir: opts.rootDir,\n client,\n stateDb,\n mode,\n });\n } catch (err) {\n console.error(\"tapd apply error:\", err);\n }\n } while (rerunRequested && !stopped);\n return lastApplyResult;\n } finally {\n applyInFlight = null;\n }\n })();\n applyInFlight = loop;\n return loop;\n };\n\n const schedulePoll = () => {\n if (stopped) return;\n pollTimer = setTimeout(() => {\n triggerApply().finally(() => schedulePoll());\n }, pollSeconds * 1000);\n };\n\n // SSE-triggered apply is debounced — bursts coalesce into one apply.\n const scheduleSseApply = () => {\n if (stopped) return;\n if (sseApplyTimer) clearTimeout(sseApplyTimer);\n sseApplyTimer = setTimeout(() => {\n sseApplyTimer = undefined;\n void triggerApply();\n }, SSE_APPLY_DEBOUNCE_MS);\n };\n\n if (!opts.noSse) {\n sseSubscriber = subscribe({\n baseUrl: opts.config.relayUrl,\n bindingId: opts.config.bindingId,\n token: opts.config.token,\n fetch: opts.fetch,\n heartbeatTimeoutMs: opts.sseHeartbeatTimeoutMs,\n onChange: () => scheduleSseApply(),\n onError: (err) => {\n console.error(\"tapd sse:\", err.message);\n },\n });\n }\n\n // Apply remote changes before returning so callers (and the primary's\n // initial scan) observe a reconciled tree.\n await triggerApply();\n schedulePoll();\n\n return {\n async stop() {\n stopped = true;\n if (pollTimer) clearTimeout(pollTimer);\n if (sseApplyTimer) clearTimeout(sseApplyTimer);\n if (sseSubscriber) await sseSubscriber.stop();\n await Promise.allSettled([applyInFlight ?? Promise.resolve()]);\n stateDb.close();\n },\n async applyNow() {\n const result = await triggerApply();\n return (\n result ?? {\n applied: 0,\n echoSkipped: 0,\n errored: 0,\n events: [],\n errors: [],\n }\n );\n },\n stateDb,\n };\n}\n","/**\n * Local → remote sync direction: compute the diff between the file\n * system and the state DB, upload any new bytes, submit one batched\n * `POST /changes`.\n *\n * Phase 2 calls this on startup (initial reconcile) and on each\n * watcher event (debounced).\n */\n\nimport { createHash } from \"node:crypto\";\nimport { readFileSync } from \"node:fs\";\nimport { join } from \"node:path\";\nimport type { Ignore } from \"ignore\";\nimport type { ChangeEvent, SubmitChangeEvent } from \"@tap/core\";\nimport { hashFile } from \"./hash.js\";\nimport type { RelayClient } from \"./relay-client.js\";\nimport type { StateDb } from \"./state-db.js\";\nimport { walk } from \"./walker.js\";\nimport { mapPool } from \"./pool.js\";\n\n// Max object uploads in flight. Keeps a many-file scan fast without\n// hammering the relay (a small shared VM) with an unbounded burst.\nconst UPLOAD_CONCURRENCY = 8;\n\nfunction sha256OfBuffer(bytes: Buffer | Uint8Array): string {\n return `sha256:${createHash(\"sha256\").update(bytes).digest(\"hex\")}`;\n}\n\nexport type ScanAndPushResult = {\n /** Bytes uploaded this cycle. */\n uploaded: number;\n /** Hashes that were already on the relay (no PUT needed). */\n reused: number;\n /** Change events submitted (writes + deletes + mkdir + rmdir). */\n submitted: ReadonlyArray<ChangeEvent>;\n /** Warnings from the walker (symlinks / oversize / invalid) +\n * mid-scan mutations detected before upload. */\n warnings: ReadonlyArray<{ path: string; reason: string }>;\n};\n\n/**\n * Pure-read diff: what would scanAndPush submit right now, if called?\n * Hashes every visible file (cost: one read pass), compares against\n * state DB. No relay calls, no writes. Both scanAndPush and `tapd\n * status` use this — keeps the \"is sync clean?\" gate honest because\n * status sees exactly what the next push will see.\n */\nexport type LocalDiff = {\n writes: ReadonlyArray<{\n path: string;\n size: number;\n hash: string;\n baseChangeId?: string;\n }>;\n deletes: ReadonlyArray<{ path: string; baseChangeId?: string }>;\n mkdirs: ReadonlyArray<string>;\n /** Explicit rmdirs carry baseChangeId; synthesized ones don't. */\n rmdirs: ReadonlyArray<{ path: string; baseChangeId?: string }>;\n warnings: ReadonlyArray<{ path: string; reason: string }>;\n};\n\nexport async function computeLocalDiff(opts: {\n rootDir: string;\n stateDb: StateDb;\n ignore: Ignore;\n /** Import mount points to hard-exclude from the scan. See walk(). */\n excludePaths?: ReadonlyArray<string>;\n}): Promise<LocalDiff> {\n const { rootDir, stateDb, ignore, excludePaths } = opts;\n const scan = walk(rootDir, { ignore, excludePaths });\n\n // Writes — every file whose hash differs from the state DB's last-seen.\n const writes: Array<{ path: string; size: number; hash: string; baseChangeId?: string }> = [];\n for (const f of scan.files) {\n const hash = await hashFile(join(rootDir, f.path));\n const prev = stateDb.getPath(f.path);\n if (prev?.lastSeenHash === hash) continue;\n const baseChangeId = prev?.lastAppliedChangeId ?? undefined;\n writes.push({ path: f.path, size: f.size, hash, ...(baseChangeId ? { baseChangeId } : {}) });\n }\n\n // Deletes + rmdirs. A tracked path is \"still on disk\" iff it appears\n // as a file in this scan OR appears as ANY directory in this scan\n // (including non-empty ones — `directoriesScanned` covers both).\n // Using `emptyDirs` alone would mis-classify a previously-empty\n // tracked dir that now contains files as \"deleted\".\n const filesNow = new Set(scan.files.map((f) => f.path));\n const dirsNow = new Set(scan.directoriesScanned);\n const deletes: Array<{ path: string; baseChangeId?: string }> = [];\n const explicitRmdirs: Array<{ path: string; baseChangeId?: string }> = [];\n const removedPaths = new Set<string>();\n for (const path of stateDb.listPaths()) {\n const prev = stateDb.getPath(path);\n if (!prev) continue;\n const stillOnDisk = filesNow.has(path) || dirsNow.has(path);\n if (stillOnDisk) continue;\n const baseChangeId = prev.lastAppliedChangeId ?? undefined;\n if (prev.lastSeenHash === null) {\n explicitRmdirs.push({ path, ...(baseChangeId ? { baseChangeId } : {}) });\n } else {\n deletes.push({ path, ...(baseChangeId ? { baseChangeId } : {}) });\n }\n removedPaths.add(path);\n }\n\n // Synthesize rmdir for ancestor dirs of removed paths that are no\n // longer on disk. The walker only emits `emptyDirs` for currently-\n // visible dirs, so a `rm -rf docs/` that wiped a previously-untracked\n // (because non-empty) directory tree wouldn't produce an rmdir for\n // `docs/` without this step.\n const synthesizedRmdirs = new Set<string>();\n for (const path of removedPaths) {\n let parent = parentDir(path);\n while (parent !== null) {\n if (filesNow.has(parent) || dirsNow.has(parent)) break;\n if (synthesizedRmdirs.has(parent)) break;\n if (!removedPaths.has(parent)) synthesizedRmdirs.add(parent);\n parent = parentDir(parent);\n }\n }\n // Deepest-first so peer-side best-effort rmSync succeeds.\n const synthDeepest = [...synthesizedRmdirs].sort(\n (a, b) => b.split(\"/\").length - a.split(\"/\").length,\n );\n\n // Empty-dir markers: anything in scan.emptyDirs that doesn't already\n // have a state entry.\n const mkdirs: string[] = [];\n for (const dir of scan.emptyDirs) {\n if (stateDb.getPath(dir)) continue;\n mkdirs.push(dir);\n }\n\n // Combine explicit + synthesized rmdirs into one stream; explicit\n // first so they retain their baseChangeId at submit time, then\n // synthesized deepest-first so peer rmSync(recursive:false) succeeds.\n const rmdirs = [\n ...explicitRmdirs,\n ...synthDeepest.map((path) => ({ path })),\n ];\n\n return {\n writes,\n deletes,\n mkdirs,\n rmdirs,\n warnings: scan.warnings.map((w) => ({ path: w.path, reason: w.reason })),\n };\n}\n\nexport async function scanAndPush(opts: {\n rootDir: string;\n client: RelayClient;\n stateDb: StateDb;\n ignore: Ignore;\n /** Import mount points to hard-exclude from the scan. See walk(). */\n excludePaths?: ReadonlyArray<string>;\n}): Promise<ScanAndPushResult> {\n const { rootDir, client, stateDb } = opts;\n const diff = await computeLocalDiff(opts);\n\n // Dedupe writes by hash so identical content uploads exactly once.\n const uniqueHashes = new Map<string, { path: string; size: number; hash: string }>();\n for (const w of diff.writes) if (!uniqueHashes.has(w.hash)) uniqueHashes.set(w.hash, w);\n\n // Files whose content changed between the walker's streaming hash and\n // our `readFileSync` for PUT. We skip those — the next watcher tick\n // re-hashes with the current bytes.\n const mutatedPaths = new Set<string>();\n const skipWarnings: Array<{ path: string; reason: string }> = [];\n\n let uploaded = 0;\n let reused = 0;\n // Upload unique objects with bounded concurrency — serial round-trips made\n // a many-file scan take minutes against a remote relay.\n await mapPool([...uniqueHashes.values()], UPLOAD_CONCURRENCY, async (f) => {\n const prep = await client.prepareUpload({ hash: f.hash, size: f.size });\n if (prep.exists) {\n reused += 1;\n return;\n }\n const bytes = readFileSync(join(rootDir, f.path));\n const actual = sha256OfBuffer(bytes);\n if (actual !== f.hash) {\n mutatedPaths.add(f.path);\n skipWarnings.push({ path: f.path, reason: `mutated_during_scan:${actual}` });\n return;\n }\n await client.putObjectBytes(prep.uploadUrl, bytes);\n await client.completeUpload({ hash: f.hash, size: f.size });\n uploaded += 1;\n });\n\n // Build the submit batch. Skip writes whose dedup-representative hash\n // didn't actually upload (the file mutated mid-scan).\n const events: SubmitChangeEvent[] = [];\n for (const w of diff.writes) {\n if (mutatedPaths.has(w.path)) continue;\n const repPath = (uniqueHashes.get(w.hash) ?? w).path;\n if (mutatedPaths.has(repPath)) continue;\n events.push({\n op: \"write\",\n path: w.path,\n hash: w.hash,\n size: w.size,\n ...(w.baseChangeId ? { baseChangeId: w.baseChangeId } : {}),\n });\n }\n for (const d of diff.deletes) {\n events.push({\n op: \"delete\",\n path: d.path,\n ...(d.baseChangeId ? { baseChangeId: d.baseChangeId } : {}),\n });\n }\n // Explicit rmdirs (with baseChangeId) come first then synth-deepest-\n // first, which is what we want on the wire too — emit in that order.\n for (const r of diff.rmdirs) {\n events.push({ op: \"rmdir\", path: r.path, ...(r.baseChangeId ? { baseChangeId: r.baseChangeId } : {}) });\n }\n for (const path of diff.mkdirs) {\n events.push({ op: \"mkdir\", path });\n }\n\n let submitted: ReadonlyArray<ChangeEvent> = [];\n if (events.length > 0) {\n const result = await client.submitChanges(events);\n submitted = result.events;\n const now = new Date().toISOString();\n for (const ev of submitted) {\n if (ev.op === \"delete\" || ev.op === \"rmdir\") {\n stateDb.deletePath(ev.path);\n continue;\n }\n stateDb.upsertPath({\n path: ev.path,\n lastAppliedChangeId: ev.id,\n lastSeenHash: ev.op === \"write\" ? ev.hash : null,\n localDirty: false,\n lastScanAt: now,\n });\n }\n // Advance cursor past our own writes so the apply loop doesn't\n // re-fetch them as \"remote\" events.\n const lastId = submitted[submitted.length - 1]!.id;\n if (cursorLessThan(stateDb.getCursor(), lastId)) {\n stateDb.setCursor(lastId);\n }\n }\n\n return {\n uploaded,\n reused,\n submitted,\n warnings: [...diff.warnings, ...skipWarnings],\n };\n}\n\n/** Returns the immediate parent of a POSIX path, or null at the root. */\nfunction parentDir(posix: string): string | null {\n const i = posix.lastIndexOf(\"/\");\n if (i < 0) return null;\n return posix.slice(0, i);\n}\n\nfunction cursorLessThan(a: string, b: string): boolean {\n const prefix = \"chg_\";\n const an = a.startsWith(prefix) ? BigInt(a.slice(prefix.length)) : 0n;\n const bn = b.startsWith(prefix) ? BigInt(b.slice(prefix.length)) : 0n;\n return an < bn;\n}\n","/**\n * `tapd start` — the daemon orchestrator.\n *\n * A daemon = N subscriptions (apply loops) + 1 writer (watcher + scan).\n * - The **primary** binding gets a subscription (mode \"primary\") AND\n * the writer: chokidar → debounced `scanAndPush` pushes local diffs\n * upstream.\n * - Each **import** gets an apply-only subscription (mode \"mirror\")\n * materializing another binding into a subdirectory, read-only. No\n * writer — a mirror never pushes.\n *\n * The subscription machinery (SSE + poll + apply-mutex) lives in\n * subscription.ts; this file wires the pieces together and owns the\n * primary writer.\n *\n * Critical invariant: import mount points are HARD-excluded from the\n * primary writer (watcher `ignored` + walker `excludePaths`), so a\n * mirrored subtree is never re-uploaded into the primary's own binding\n * — the \"accidental re-share\".\n */\n\nimport { join } from \"node:path\";\nimport chokidar, { type FSWatcher } from \"chokidar\";\nimport { type ApplyResult } from \"./applier.js\";\nimport { readBindingConfig } from \"./binding-config.js\";\nimport {\n importStateDbPath,\n loadImportMounts,\n primaryStateDbPath,\n validateImports,\n type ImportSpec,\n type ImportValidationError,\n} from \"./imports.js\";\nimport { RelayClient } from \"./relay-client.js\";\nimport { openStateDb, type StateDb } from \"./state-db.js\";\nimport {\n startSubscription,\n type SubscriptionHandle,\n} from \"./subscription.js\";\nimport { loadIgnore, isAlwaysExcluded, isUnderAny } from \"./tapignore.js\";\nimport { scanAndPush, type ScanAndPushResult } from \"./uploader.js\";\n\nexport type StartOptions = {\n rootDir: string;\n pollSeconds?: number;\n /** Quiescence window before the watcher triggers a rescan. */\n debounceMs?: number;\n /**\n * Read-only imports to mount into subdirectories. When omitted, the\n * daemon loads them from `.rig/tap/imports/*.local.json` (the normal\n * path). Pass explicitly to bypass disk loading (tests / programmatic).\n * Each carries its own binding + capability token.\n */\n imports?: ReadonlyArray<ImportSpec>;\n /** Test seam — override fetch (passed to RelayClient + SSE subscriber). */\n fetch?: typeof fetch;\n /** Test seam — disable the chokidar watcher (poll loop only). */\n noWatch?: boolean;\n /** Test seam — disable SSE (poll-only mode). */\n noSse?: boolean;\n /** SSE watchdog window in ms. Default 45s. */\n sseHeartbeatTimeoutMs?: number;\n};\n\n/** A live import mount — exposed for status + tests. */\nexport type ImportHandle = {\n name: string;\n path: string;\n applyNow(): Promise<ApplyResult>;\n stateDb: StateDb;\n};\n\nexport type DaemonHandle = {\n /** Cleanly shut down the writer + every subscription + close state DBs. */\n stop(): Promise<void>;\n /** Force one primary apply cycle now. No-op for an import-only daemon. */\n applyNow(): Promise<ApplyResult>;\n /** Force one upstream scan+push now. No-op for an import-only daemon. */\n scanNow(): Promise<ScanAndPushResult>;\n /** Primary state DB (or first mount's, if import-only). Do NOT close it. */\n stateDb: StateDb;\n /** Live import mounts, in declaration order (valid ones only). */\n imports: ImportHandle[];\n};\n\nconst DEFAULT_POLL_SECONDS = 3;\nconst DEFAULT_DEBOUNCE_MS = 300;\n\nconst NOOP_APPLY: ApplyResult = {\n applied: 0,\n echoSkipped: 0,\n errored: 0,\n events: [],\n errors: [],\n};\nconst NOOP_SCAN: ScanAndPushResult = {\n uploaded: 0,\n reused: 0,\n submitted: [],\n warnings: [],\n};\n\nexport async function start(opts: StartOptions): Promise<DaemonHandle> {\n const config = readBindingConfig(opts.rootDir);\n\n // Imports come from the caller (tests/programmatic) or, normally, from\n // the mount files on disk. Broken mount files degrade — they're logged\n // and skipped, never fatal.\n let importSpecs: ReadonlyArray<ImportSpec>;\n let loaderBroken: ImportValidationError[] = [];\n if (opts.imports) {\n importSpecs = opts.imports;\n } else {\n const loaded = loadImportMounts(opts.rootDir);\n importSpecs = loaded.ready;\n loaderBroken = loaded.broken;\n }\n for (const e of loaderBroken) {\n console.error(`tapd import \"${e.name}\": skipped — ${e.reason}`);\n }\n\n if (!config && importSpecs.length === 0) {\n throw new Error(\n `no binding config at ${opts.rootDir}/.rig/tap-binding.local.json and no imports — run \\`tapd init\\` first`,\n );\n }\n\n const pollSeconds = opts.pollSeconds ?? DEFAULT_POLL_SECONDS;\n const debounceMs = opts.debounceMs ?? DEFAULT_DEBOUNCE_MS;\n\n let stopped = false;\n\n // ────────── primary subscription + writer ──────────\n let primary: SubscriptionHandle | undefined;\n let watcher: FSWatcher | undefined;\n let scanTimer: ReturnType<typeof setTimeout> | undefined;\n let scanInFlight: Promise<unknown> = Promise.resolve();\n let writerClient: RelayClient | undefined;\n const ignore = loadIgnore(opts.rootDir);\n\n // Resolved at primary-startup; the writer reads it so the exclusion set\n // reflects exactly the imports that passed validation.\n let importPaths: string[] = [];\n\n if (config) {\n primary = await startSubscription({\n rootDir: opts.rootDir,\n config,\n stateDbPath: primaryStateDbPath(opts.rootDir),\n mode: \"primary\",\n fetch: opts.fetch,\n pollSeconds,\n noSse: opts.noSse,\n sseHeartbeatTimeoutMs: opts.sseHeartbeatTimeoutMs,\n });\n }\n\n // ────────── validate imports ──────────\n // Reject illegal mounts (bad name/path, symlink escape, overlap) and\n // mounts over content the primary already tracks. Rejections are\n // logged and skipped — they never take down the daemon (gotcha #7).\n const primaryTrackedPaths = primary ? primary.stateDb.listPaths() : [];\n const { valid: validImports, errors: importErrors } = validateImports({\n rootDir: opts.rootDir,\n imports: importSpecs,\n primaryTrackedPaths,\n });\n for (const e of importErrors) {\n console.error(`tapd import \"${e.name}\" (${e.path}): skipped — ${e.reason}`);\n }\n importPaths = validImports.map((i) => i.path);\n\n // ────────── primary writer (bound only) ──────────\n if (config && primary) {\n const primaryStateDb = primary.stateDb;\n writerClient = new RelayClient({\n baseUrl: config.relayUrl,\n bindingId: config.bindingId,\n token: config.token,\n fetch: opts.fetch,\n });\n\n const scheduleScan = () => {\n if (stopped) return;\n if (scanTimer) clearTimeout(scanTimer);\n scanTimer = setTimeout(() => {\n scanInFlight = scanAndPush({\n rootDir: opts.rootDir,\n client: writerClient!,\n stateDb: primaryStateDb,\n ignore,\n excludePaths: importPaths,\n }).catch((err) => {\n console.error(\"tapd scan error:\", err);\n });\n }, debounceMs);\n };\n\n if (!opts.noWatch) {\n watcher = chokidar.watch(opts.rootDir, {\n ignoreInitial: true,\n // Hard-exclude tap metadata AND every import mount point — the\n // latter so a mirrored subtree never triggers a re-upload into\n // the primary binding.\n ignored: (path: string) => {\n const rel = path.startsWith(opts.rootDir)\n ? path.slice(opts.rootDir.length).replace(/^[/\\\\]/, \"\")\n : path;\n if (isAlwaysExcluded(rel)) return true;\n if (importPaths.length > 0 && isUnderAny(rel, importPaths)) return true;\n return false;\n },\n awaitWriteFinish: { stabilityThreshold: 200, pollInterval: 50 },\n atomic: true,\n });\n for (const ev of [\"add\", \"change\", \"unlink\", \"addDir\", \"unlinkDir\"] as const) {\n watcher.on(ev, () => scheduleScan());\n }\n }\n\n // Initial scan: the primary subscription already applied remote\n // changes before returning, so now push local/offline edits. Import\n // paths are excluded so we never upload another binding's content.\n scanInFlight = scanAndPush({\n rootDir: opts.rootDir,\n client: writerClient,\n stateDb: primaryStateDb,\n ignore,\n excludePaths: importPaths,\n }).catch((err) => {\n console.error(\"tapd initial scan error:\", err);\n });\n await scanInFlight;\n }\n\n // ────────── import (mirror) subscriptions ──────────\n const imports: ImportHandle[] = [];\n const mirrorSubs: SubscriptionHandle[] = [];\n for (const imp of validImports) {\n try {\n const sub = await startSubscription({\n rootDir: join(opts.rootDir, imp.path),\n config: { bindingId: imp.bindingId, relayUrl: imp.relayUrl, token: imp.token },\n stateDbPath: importStateDbPath(opts.rootDir, imp.name),\n mode: \"mirror\",\n fetch: opts.fetch,\n pollSeconds,\n noSse: opts.noSse,\n sseHeartbeatTimeoutMs: opts.sseHeartbeatTimeoutMs,\n });\n mirrorSubs.push(sub);\n imports.push({\n name: imp.name,\n path: imp.path,\n applyNow: () => sub.applyNow(),\n stateDb: sub.stateDb,\n });\n } catch (err) {\n const reason = err instanceof Error ? err.message : String(err);\n console.error(`tapd import \"${imp.name}\" (${imp.path}): failed to start — ${reason}`);\n }\n }\n\n const stateDb = primary?.stateDb ?? imports[0]?.stateDb;\n if (!stateDb) {\n // Every import failed to start and there's no primary — nothing to do.\n throw new Error(\"tapd start: no primary binding and no import mounted successfully\");\n }\n\n return {\n async stop() {\n stopped = true;\n // Writer first — it shares the primary's state DB, which the\n // primary subscription's stop() will close.\n if (scanTimer) clearTimeout(scanTimer);\n await Promise.allSettled([scanInFlight]);\n if (watcher) await watcher.close();\n // Subscriptions (each closes its own state DB).\n const subs: Array<Promise<void>> = [];\n if (primary) subs.push(primary.stop());\n for (const sub of mirrorSubs) subs.push(sub.stop());\n await Promise.allSettled(subs);\n },\n async applyNow() {\n return primary ? primary.applyNow() : NOOP_APPLY;\n },\n async scanNow() {\n if (!config || !primary || !writerClient) return NOOP_SCAN;\n return scanAndPush({\n rootDir: opts.rootDir,\n client: writerClient,\n stateDb: primary.stateDb,\n ignore,\n excludePaths: importPaths,\n });\n },\n stateDb,\n imports,\n };\n}\n"],"mappings":"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","names":["mkdirSync","readFileSync","writeFileSync","dirname","existsSync","readFileSync","join","join","join","resolve","createHash","readFileSync","join","createHash","join","readFileSync","join","existsSync","mkdirSync","readdirSync","readFileSync","statSync","writeFileSync","dirname","join","existsSync","readFileSync","dirname","joinPath","existsSync","readFileSync","revoke","dirname","joinPath","createHash","existsSync","mkdirSync","readdirSync","readFileSync","renameSync","rmSync","writeFileSync","dirname","join","sep","isAbsolute","relative","relative","isAbsolute","createHash","readFileSync","dirname","walk","join","readdirSync","sep","existsSync","rmSync","mkdirSync","tmp","writeFileSync","renameSync","createHash","readFileSync","join","UPLOAD_CONCURRENCY","sha256OfBuffer","createHash","ignore","join","readFileSync","join","DEFAULT_POLL_SECONDS","ignore","join"]}
|