syntaur 0.16.2 → 0.17.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/dashboard/dist/assets/{_basePickBy-DTuHiZCP.js → _basePickBy-Dek5-ACb.js} +1 -1
- package/dashboard/dist/assets/{_baseUniq-BjbISCNN.js → _baseUniq-BeLddDKU.js} +1 -1
- package/dashboard/dist/assets/{arc-D9mCa8If.js → arc-kotdj4Uh.js} +1 -1
- package/dashboard/dist/assets/{architectureDiagram-2XIMDMQ5-CLWheiZu.js → architectureDiagram-2XIMDMQ5-BaqFXQ-q.js} +1 -1
- package/dashboard/dist/assets/{blockDiagram-WCTKOSBZ-BBxrVTWB.js → blockDiagram-WCTKOSBZ-BSyGne6h.js} +1 -1
- package/dashboard/dist/assets/{c4Diagram-IC4MRINW-DnhDZ2W3.js → c4Diagram-IC4MRINW-iYjZFKOW.js} +1 -1
- package/dashboard/dist/assets/channel-eXdj7ayV.js +1 -0
- package/dashboard/dist/assets/{chunk-4BX2VUAB-DeAfVeDa.js → chunk-4BX2VUAB-DvY9Y7jB.js} +1 -1
- package/dashboard/dist/assets/{chunk-55IACEB6-cKEeGDNI.js → chunk-55IACEB6-2vCDm2cJ.js} +1 -1
- package/dashboard/dist/assets/{chunk-FMBD7UC4-7RuZ6HEq.js → chunk-FMBD7UC4-BVPaoRtV.js} +1 -1
- package/dashboard/dist/assets/{chunk-JSJVCQXG-B5dwG0bv.js → chunk-JSJVCQXG-B9uJpMzd.js} +1 -1
- package/dashboard/dist/assets/{chunk-KX2RTZJC-DeyQYDVj.js → chunk-KX2RTZJC-CHcjjGaq.js} +1 -1
- package/dashboard/dist/assets/{chunk-NQ4KR5QH-NYo4hSqA.js → chunk-NQ4KR5QH-B8n19pSs.js} +1 -1
- package/dashboard/dist/assets/{chunk-QZHKN3VN-CtJFICF8.js → chunk-QZHKN3VN-CF9ZYFGK.js} +1 -1
- package/dashboard/dist/assets/{chunk-WL4C6EOR-DZ3WYdab.js → chunk-WL4C6EOR-mY_1Vmdf.js} +1 -1
- package/dashboard/dist/assets/classDiagram-VBA2DB6C-B-bBQACr.js +1 -0
- package/dashboard/dist/assets/classDiagram-v2-RAHNMMFH-B-bBQACr.js +1 -0
- package/dashboard/dist/assets/clone-CL04pv9J.js +1 -0
- package/dashboard/dist/assets/{cose-bilkent-S5V4N54A-DaYOsf-M.js → cose-bilkent-S5V4N54A-DOYoG3gB.js} +1 -1
- package/dashboard/dist/assets/{dagre-KLK3FWXG-DzLc7ykF.js → dagre-KLK3FWXG-CZFrp-Yq.js} +1 -1
- package/dashboard/dist/assets/{diagram-E7M64L7V-kpjEdOqb.js → diagram-E7M64L7V-F3rF5YwM.js} +1 -1
- package/dashboard/dist/assets/{diagram-IFDJBPK2-D3EHoh6j.js → diagram-IFDJBPK2-BynWv-Vo.js} +1 -1
- package/dashboard/dist/assets/{diagram-P4PSJMXO-Cp6Un2ys.js → diagram-P4PSJMXO-NIyzhm_M.js} +1 -1
- package/dashboard/dist/assets/{erDiagram-INFDFZHY-DDjOUPWk.js → erDiagram-INFDFZHY-Ca9NbihE.js} +1 -1
- package/dashboard/dist/assets/{flowDiagram-PKNHOUZH-CfO51xge.js → flowDiagram-PKNHOUZH-YhS0MOu4.js} +1 -1
- package/dashboard/dist/assets/{ganttDiagram-A5KZAMGK-BDzAtkcp.js → ganttDiagram-A5KZAMGK-G7P3qq_2.js} +1 -1
- package/dashboard/dist/assets/{gitGraphDiagram-K3NZZRJ6-JPMFN2zF.js → gitGraphDiagram-K3NZZRJ6-BJAK7M7E.js} +1 -1
- package/dashboard/dist/assets/{graph-DXDryilu.js → graph-11geMy3f.js} +1 -1
- package/dashboard/dist/assets/index--qwybK3W.js +535 -0
- package/dashboard/dist/assets/index-DwrhlK8r.css +1 -0
- package/dashboard/dist/assets/{infoDiagram-LFFYTUFH-CYi5kkvz.js → infoDiagram-LFFYTUFH-COcQYcLf.js} +1 -1
- package/dashboard/dist/assets/{ishikawaDiagram-PHBUUO56-DQl_IUe8.js → ishikawaDiagram-PHBUUO56-B7Jjyw8J.js} +1 -1
- package/dashboard/dist/assets/{journeyDiagram-4ABVD52K-BXXGPcrS.js → journeyDiagram-4ABVD52K--snU-QAX.js} +1 -1
- package/dashboard/dist/assets/{kanban-definition-K7BYSVSG-BqJestUY.js → kanban-definition-K7BYSVSG-BucLoc89.js} +1 -1
- package/dashboard/dist/assets/{layout-D5VdYmWn.js → layout-Bcgxdz8A.js} +1 -1
- package/dashboard/dist/assets/{linear-dZA_O_GN.js → linear-CRF8xwvk.js} +1 -1
- package/dashboard/dist/assets/{mermaid.core-B0Ixd1yP.js → mermaid.core-D5JD5uYg.js} +4 -4
- package/dashboard/dist/assets/{mindmap-definition-YRQLILUH-CSJYdSMG.js → mindmap-definition-YRQLILUH-BqGcTcbk.js} +1 -1
- package/dashboard/dist/assets/{pieDiagram-SKSYHLDU-DmYrRZHN.js → pieDiagram-SKSYHLDU-CnQ2EkyX.js} +1 -1
- package/dashboard/dist/assets/{quadrantDiagram-337W2JSQ-La3ce5kE.js → quadrantDiagram-337W2JSQ-CKCfmTRc.js} +1 -1
- package/dashboard/dist/assets/{requirementDiagram-Z7DCOOCP-DPitIZQl.js → requirementDiagram-Z7DCOOCP-B9J4bvUK.js} +1 -1
- package/dashboard/dist/assets/{sankeyDiagram-WA2Y5GQK-CAmCqGkr.js → sankeyDiagram-WA2Y5GQK-CMSzI5rg.js} +1 -1
- package/dashboard/dist/assets/{sequenceDiagram-2WXFIKYE-6lEzNTWG.js → sequenceDiagram-2WXFIKYE-BFQDtxd0.js} +1 -1
- package/dashboard/dist/assets/{stateDiagram-RAJIS63D-DyGKCS2C.js → stateDiagram-RAJIS63D-DgWYKT02.js} +1 -1
- package/dashboard/dist/assets/stateDiagram-v2-FVOUBMTO-Nmwft4cR.js +1 -0
- package/dashboard/dist/assets/{timeline-definition-YZTLITO2-D9RxQE3j.js → timeline-definition-YZTLITO2-jANu0e7M.js} +1 -1
- package/dashboard/dist/assets/{treemap-KZPCXAKY-TuXbGNN4.js → treemap-KZPCXAKY-BFjWMkyM.js} +1 -1
- package/dashboard/dist/assets/{vennDiagram-LZ73GAT5-Bly5knr-.js → vennDiagram-LZ73GAT5-DGwrAJYj.js} +1 -1
- package/dashboard/dist/assets/{xychartDiagram-JWTSCODW-CA-5z7-4.js → xychartDiagram-JWTSCODW-BLCPfoFM.js} +1 -1
- package/dashboard/dist/index.html +2 -2
- package/dist/dashboard/server.js +2220 -479
- package/dist/dashboard/server.js.map +1 -1
- package/dist/db/leases-db.js.map +1 -1
- package/dist/index.js +4499 -1127
- package/dist/index.js.map +1 -1
- package/dist/launch/index.js +127 -63
- package/dist/launch/index.js.map +1 -1
- package/package.json +1 -1
- package/platforms/claude-code/skills/bundle-worktree/SKILL.md +107 -0
- package/platforms/claude-code/skills/complete-bundle/SKILL.md +113 -0
- package/platforms/claude-code/skills/grab-bundle/SKILL.md +111 -0
- package/platforms/claude-code/skills/plan-bundle/SKILL.md +88 -0
- package/platforms/codex/skills/bundle-worktree/SKILL.md +107 -0
- package/platforms/codex/skills/complete-bundle/SKILL.md +113 -0
- package/platforms/codex/skills/grab-bundle/SKILL.md +111 -0
- package/platforms/codex/skills/plan-bundle/SKILL.md +88 -0
- package/skills/bundle-worktree/SKILL.md +107 -0
- package/skills/complete-bundle/SKILL.md +113 -0
- package/skills/grab-bundle/SKILL.md +111 -0
- package/skills/plan-bundle/SKILL.md +88 -0
- package/dashboard/dist/assets/channel-CN5VmjNa.js +0 -1
- package/dashboard/dist/assets/classDiagram-VBA2DB6C-CqLb9GuU.js +0 -1
- package/dashboard/dist/assets/classDiagram-v2-RAHNMMFH-CqLb9GuU.js +0 -1
- package/dashboard/dist/assets/clone-DwvrjCQs.js +0 -1
- package/dashboard/dist/assets/index-D7UtkCYM.js +0 -515
- package/dashboard/dist/assets/index-DTXSWSzH.css +0 -1
- package/dashboard/dist/assets/stateDiagram-v2-FVOUBMTO-BOmRICoY.js +0 -1
package/dist/db/leases-db.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/db/leases-db.ts","../../src/utils/paths.ts"],"sourcesContent":["/**\n * Resource leases database module.\n *\n * Shares `~/.syntaur/syntaur.db` with `session-db.ts` and `proof-db.ts`. Each\n * module owns its own schema-version row in the shared `meta` table and creates\n * `meta` with `IF NOT EXISTS` so init order is irrelevant and standalone init\n * (e.g. in a leases-only test DB) is safe.\n *\n * Concurrency model:\n * - `journal_mode = WAL` allows concurrent readers and one writer.\n * - `busy_timeout = 5000` lets SQLite absorb short lock contention internally.\n * - All mutating ops run inside `db.transaction(fn).immediate()` which issues\n * `BEGIN IMMEDIATE` — acquires the reserved lock up-front so two concurrent\n * writers serialize cleanly.\n *\n * Atomic claim uses the tuple-subquery form (the bundled SQLite is built\n * without `SQLITE_ENABLE_UPDATE_DELETE_LIMIT`, so `UPDATE...ORDER BY...LIMIT...\n * RETURNING` is a syntax error — verified empirically).\n *\n * Timestamp invariant: every timestamp written from this module is produced by\n * `nowIso()` which calls `new Date().toISOString()` (canonical UTC, lexicographic-\n * safe). Lexicographic `<=` comparisons in SQL rely on this. The CLI never\n * accepts user-supplied timestamps. A future caller writing a non-canonical\n * timestamp would silently misorder `<=` checks.\n */\n\nimport Database from 'better-sqlite3';\nimport { randomUUID } from 'node:crypto';\nimport { resolve } from 'node:path';\nimport { syntaurRoot } from '../utils/paths.js';\n\nlet db: Database.Database | null = null;\n\nconst LEASE_SCHEMA_VERSION = '1';\n\nconst SCHEMA_SQL = `\nCREATE TABLE IF NOT EXISTS meta (\n key TEXT PRIMARY KEY,\n value TEXT\n);\n\nCREATE TABLE IF NOT EXISTS inventories (\n slug TEXT PRIMARY KEY,\n kind TEXT NOT NULL,\n display_name TEXT,\n default_ttl_s INTEGER NOT NULL CHECK (default_ttl_s > 0),\n created_at TEXT NOT NULL\n);\n\nCREATE TABLE IF NOT EXISTS inventory_members (\n inventory_slug TEXT NOT NULL,\n member_id TEXT NOT NULL,\n status TEXT NOT NULL DEFAULT 'idle'\n CHECK (status IN ('idle','leased','retired')),\n generation INTEGER NOT NULL DEFAULT 0,\n metadata_json TEXT,\n last_used_at TEXT,\n retired_at TEXT,\n PRIMARY KEY (inventory_slug, member_id),\n FOREIGN KEY (inventory_slug) REFERENCES inventories(slug)\n);\n\nCREATE INDEX IF NOT EXISTS idx_members_idle\n ON inventory_members (inventory_slug, status, last_used_at);\n\nCREATE TABLE IF NOT EXISTS leases (\n lease_id TEXT PRIMARY KEY,\n inventory_slug TEXT NOT NULL,\n member_id TEXT NOT NULL,\n member_gen INTEGER NOT NULL,\n state TEXT NOT NULL\n CHECK (state IN ('active','released','expired','revoked')),\n granted_at TEXT NOT NULL,\n expires_at TEXT NOT NULL,\n released_at TEXT,\n requested_for TEXT,\n FOREIGN KEY (inventory_slug, member_id)\n REFERENCES inventory_members(inventory_slug, member_id)\n);\n\nCREATE INDEX IF NOT EXISTS idx_leases_gc\n ON leases (state, expires_at);\n\nCREATE TABLE IF NOT EXISTS lease_events (\n id INTEGER PRIMARY KEY AUTOINCREMENT,\n lease_id TEXT NOT NULL,\n event TEXT NOT NULL,\n at TEXT NOT NULL,\n detail_json TEXT,\n FOREIGN KEY (lease_id) REFERENCES leases(lease_id)\n);\n\nCREATE INDEX IF NOT EXISTS idx_events_lease\n ON lease_events (lease_id, at);\n`;\n\n// --- Types -----------------------------------------------------------------\n\nexport type MemberStatus = 'idle' | 'leased' | 'retired';\nexport type LeaseState = 'active' | 'released' | 'expired' | 'revoked';\n\nexport interface InventoryRow {\n slug: string;\n kind: string;\n display_name: string | null;\n default_ttl_s: number;\n created_at: string;\n}\n\nexport interface InventoryMemberRow {\n inventory_slug: string;\n member_id: string;\n status: MemberStatus;\n generation: number;\n metadata_json: string | null;\n last_used_at: string | null;\n retired_at: string | null;\n}\n\nexport interface LeaseRow {\n lease_id: string;\n inventory_slug: string;\n member_id: string;\n member_gen: number;\n state: LeaseState;\n granted_at: string;\n expires_at: string;\n released_at: string | null;\n requested_for: string | null;\n}\n\nexport interface LeaseEventRow {\n id: number;\n lease_id: string;\n event: string;\n at: string;\n detail_json: string | null;\n}\n\nexport interface InventoryDetail {\n inventory: InventoryRow;\n members: InventoryMemberRow[];\n active_leases: LeaseRow[];\n}\n\nexport interface CreateInventoryInput {\n slug: string;\n kind: string;\n display_name?: string;\n default_ttl_s: number;\n}\n\nexport interface AddMemberInput {\n inventory_slug: string;\n member_id: string;\n metadata?: Record<string, unknown>;\n}\n\nexport interface ClaimResult {\n lease_id: string;\n inventory_slug: string;\n member_id: string;\n member_gen: number;\n granted_at: string;\n expires_at: string;\n metadata: Record<string, unknown> | null;\n}\n\nexport interface ListLeasesFilter {\n inventory?: string;\n state?: LeaseState;\n}\n\n// --- Errors ----------------------------------------------------------------\n\nexport class NoIdleMemberError extends Error {\n constructor(public readonly inventorySlug: string) {\n super(`no idle members in '${inventorySlug}'`);\n this.name = 'NoIdleMemberError';\n }\n}\n\nexport class StaleLeaseError extends Error {\n constructor(public readonly leaseId: string) {\n super(`lease ${leaseId} is no longer active (expired or revoked)`);\n this.name = 'StaleLeaseError';\n }\n}\n\nexport class NotFoundError extends Error {\n constructor(public readonly id: string) {\n super(`lease ${id} not found`);\n this.name = 'NotFoundError';\n }\n}\n\nexport class LeaseContentionError extends Error {\n constructor(public readonly inventorySlug: string) {\n super(`contention timeout on '${inventorySlug}'; retry`);\n this.name = 'LeaseContentionError';\n }\n}\n\nexport class MemberInUseError extends Error {\n constructor(public readonly inventorySlug: string, public readonly memberId: string) {\n super(`member ${inventorySlug}/${memberId} is currently leased; release before retiring`);\n this.name = 'MemberInUseError';\n }\n}\n\nexport class DuplicateInventoryError extends Error {\n constructor(public readonly slug: string) {\n super(`inventory '${slug}' already exists`);\n this.name = 'DuplicateInventoryError';\n }\n}\n\nexport class DuplicateMemberError extends Error {\n constructor(public readonly inventorySlug: string, public readonly memberId: string) {\n super(`member ${inventorySlug}/${memberId} already exists`);\n this.name = 'DuplicateMemberError';\n }\n}\n\n// --- Helpers ---------------------------------------------------------------\n\n/** Canonical UTC ISO 8601 timestamp. Lexicographic-safe for SQL `<=` checks. */\nexport function nowIso(): string {\n return new Date().toISOString();\n}\n\nfunction isBusyError(err: unknown): boolean {\n if (!err || typeof err !== 'object') return false;\n const e = err as { code?: string };\n return e.code === 'SQLITE_BUSY' || e.code === 'SQLITE_BUSY_SNAPSHOT';\n}\n\n// --- Lifecycle -------------------------------------------------------------\n\n/**\n * Initialize the leases database. Idempotent — repeated calls return the same\n * singleton handle. Pass an explicit `dbPath` for tests; defaults to\n * `~/.syntaur/syntaur.db`. Safe to run standalone (creates its own `meta` table).\n */\nexport function initLeasesDb(dbPath?: string): Database.Database {\n if (db) return db;\n\n const finalPath = dbPath ?? resolve(syntaurRoot(), 'syntaur.db');\n db = new Database(finalPath);\n db.pragma('journal_mode = WAL');\n db.pragma('busy_timeout = 5000');\n db.pragma('foreign_keys = ON');\n\n const database = db;\n const runMigrations = database.transaction(() => {\n database.exec(SCHEMA_SQL);\n database\n .prepare('INSERT OR IGNORE INTO meta (key, value) VALUES (?, ?)')\n .run('lease_schema_version', LEASE_SCHEMA_VERSION);\n });\n runMigrations.exclusive();\n\n return db;\n}\n\nexport function getLeasesDb(): Database.Database {\n if (!db) {\n throw new Error('Leases database not initialized. Call initLeasesDb() first.');\n }\n return db;\n}\n\nexport function closeLeasesDb(): void {\n if (db) {\n db.close();\n db = null;\n }\n}\n\nexport function resetLeasesDb(): void {\n db = null;\n}\n\n// --- Inventories -----------------------------------------------------------\n\nexport function createInventory(input: CreateInventoryInput): InventoryRow {\n const database = getLeasesDb();\n const row: InventoryRow = {\n slug: input.slug,\n kind: input.kind,\n display_name: input.display_name ?? null,\n default_ttl_s: input.default_ttl_s,\n created_at: nowIso(),\n };\n try {\n database\n .prepare(\n `INSERT INTO inventories (slug, kind, display_name, default_ttl_s, created_at)\n VALUES (?, ?, ?, ?, ?)`,\n )\n .run(row.slug, row.kind, row.display_name, row.default_ttl_s, row.created_at);\n } catch (err) {\n if (err instanceof Error && err.message.includes('UNIQUE')) {\n throw new DuplicateInventoryError(input.slug);\n }\n throw err;\n }\n return row;\n}\n\nexport function listInventories(): InventoryRow[] {\n const database = getLeasesDb();\n return database\n .prepare(\n `SELECT slug, kind, display_name, default_ttl_s, created_at\n FROM inventories ORDER BY slug`,\n )\n .all() as InventoryRow[];\n}\n\nexport function getInventoryDetail(slug: string): InventoryDetail | null {\n const database = getLeasesDb();\n const inv = database\n .prepare(\n `SELECT slug, kind, display_name, default_ttl_s, created_at\n FROM inventories WHERE slug = ?`,\n )\n .get(slug) as InventoryRow | undefined;\n if (!inv) return null;\n const members = database\n .prepare(\n `SELECT inventory_slug, member_id, status, generation, metadata_json, last_used_at, retired_at\n FROM inventory_members WHERE inventory_slug = ?\n ORDER BY member_id`,\n )\n .all(slug) as InventoryMemberRow[];\n const active_leases = database\n .prepare(\n `SELECT lease_id, inventory_slug, member_id, member_gen, state, granted_at, expires_at, released_at, requested_for\n FROM leases WHERE inventory_slug = ? AND state = 'active'\n ORDER BY granted_at`,\n )\n .all(slug) as LeaseRow[];\n return { inventory: inv, members, active_leases };\n}\n\n// --- Members ---------------------------------------------------------------\n\nexport function addMember(input: AddMemberInput): InventoryMemberRow {\n const database = getLeasesDb();\n const row: InventoryMemberRow = {\n inventory_slug: input.inventory_slug,\n member_id: input.member_id,\n status: 'idle',\n generation: 0,\n metadata_json: input.metadata ? JSON.stringify(input.metadata) : null,\n last_used_at: null,\n retired_at: null,\n };\n try {\n database\n .prepare(\n `INSERT INTO inventory_members\n (inventory_slug, member_id, status, generation, metadata_json)\n VALUES (?, ?, 'idle', 0, ?)`,\n )\n .run(row.inventory_slug, row.member_id, row.metadata_json);\n } catch (err) {\n if (err instanceof Error && err.message.includes('UNIQUE')) {\n throw new DuplicateMemberError(input.inventory_slug, input.member_id);\n }\n throw err;\n }\n return row;\n}\n\nexport function retireMember(inventorySlug: string, memberId: string): void {\n const database = getLeasesDb();\n const retire = database.transaction(() => {\n const row = database\n .prepare(\n `SELECT status FROM inventory_members\n WHERE inventory_slug = ? AND member_id = ?`,\n )\n .get(inventorySlug, memberId) as { status: MemberStatus } | undefined;\n if (!row) throw new NotFoundError(`${inventorySlug}/${memberId}`);\n if (row.status === 'leased') {\n throw new MemberInUseError(inventorySlug, memberId);\n }\n database\n .prepare(\n `UPDATE inventory_members\n SET status = 'retired', generation = generation + 1, retired_at = ?\n WHERE inventory_slug = ? AND member_id = ?`,\n )\n .run(nowIso(), inventorySlug, memberId);\n });\n retire.immediate();\n}\n\n// --- Claim -----------------------------------------------------------------\n\n/**\n * Claim an idle member of `slug`. Atomic — uses `BEGIN IMMEDIATE` so two\n * concurrent claimants serialize. Returns lease metadata or throws\n * `NoIdleMemberError` if the inventory has nothing claimable after sweeping.\n */\nexport function claimLease(\n slug: string,\n ttl_s: number,\n requested_for?: string,\n): ClaimResult {\n const database = getLeasesDb();\n\n try {\n const fn = database.transaction(() => {\n const now = nowIso();\n\n // (1) Opportunistic sweep — expire active leases on this inventory whose TTL is up.\n database\n .prepare(\n `UPDATE leases\n SET state = 'expired'\n WHERE state = 'active'\n AND expires_at <= ?\n AND inventory_slug = ?`,\n )\n .run(now, slug);\n\n // (2) Free their members (CAS on generation match).\n database\n .prepare(\n `UPDATE inventory_members\n SET status = 'idle', generation = generation + 1\n WHERE inventory_slug = ?\n AND status = 'leased'\n AND (inventory_slug, member_id) IN (\n SELECT inventory_slug, member_id\n FROM leases\n WHERE inventory_slug = ?\n AND state = 'expired'\n AND released_at IS NULL\n )\n AND generation = (\n SELECT member_gen FROM leases l\n WHERE l.inventory_slug = inventory_members.inventory_slug\n AND l.member_id = inventory_members.member_id\n AND l.state = 'expired'\n AND l.released_at IS NULL\n ORDER BY l.granted_at DESC LIMIT 1\n )`,\n )\n .run(slug, slug);\n\n // (3) Mark those freshly-expired leases as fully released (idempotent sentinel).\n database\n .prepare(\n `UPDATE leases\n SET released_at = ?\n WHERE state = 'expired'\n AND released_at IS NULL\n AND inventory_slug = ?`,\n )\n .run(now, slug);\n\n // (4) Pick an idle member via tuple-subquery (verified portable form).\n const picked = database\n .prepare(\n `UPDATE inventory_members\n SET status = 'leased',\n generation = generation + 1,\n last_used_at = ?\n WHERE (inventory_slug, member_id) = (\n SELECT inventory_slug, member_id\n FROM inventory_members\n WHERE inventory_slug = ? AND status = 'idle'\n ORDER BY last_used_at ASC NULLS FIRST\n LIMIT 1\n )\n RETURNING member_id, generation, metadata_json`,\n )\n .get(now, slug) as\n | { member_id: string; generation: number; metadata_json: string | null }\n | undefined;\n\n if (!picked) {\n throw new NoIdleMemberError(slug);\n }\n\n // (5) Insert the lease tied to the new member_gen.\n const lease_id = randomUUID();\n const expires_at = new Date(Date.now() + ttl_s * 1000).toISOString();\n database\n .prepare(\n `INSERT INTO leases\n (lease_id, inventory_slug, member_id, member_gen,\n state, granted_at, expires_at, requested_for)\n VALUES (?, ?, ?, ?, 'active', ?, ?, ?)`,\n )\n .run(\n lease_id,\n slug,\n picked.member_id,\n picked.generation,\n now,\n expires_at,\n requested_for ?? null,\n );\n\n // (6) Event.\n database\n .prepare(\n `INSERT INTO lease_events (lease_id, event, at, detail_json)\n VALUES (?, 'claimed', ?, ?)`,\n )\n .run(lease_id, now, JSON.stringify({ member_id: picked.member_id }));\n\n return {\n lease_id,\n inventory_slug: slug,\n member_id: picked.member_id,\n member_gen: picked.generation,\n granted_at: now,\n expires_at,\n metadata: picked.metadata_json\n ? (JSON.parse(picked.metadata_json) as Record<string, unknown>)\n : null,\n } satisfies ClaimResult;\n });\n\n return fn.immediate();\n } catch (err) {\n if (isBusyError(err)) throw new LeaseContentionError(slug);\n throw err;\n }\n}\n\n// --- Release ---------------------------------------------------------------\n\nexport function releaseLease(lease_id: string): void {\n const database = getLeasesDb();\n\n try {\n const fn = database.transaction(() => {\n const now = nowIso();\n\n // (1) CAS-guarded member free.\n const memberRes = database\n .prepare(\n `UPDATE inventory_members\n SET status = 'idle', generation = generation + 1\n WHERE (inventory_slug, member_id) = (\n SELECT inventory_slug, member_id FROM leases\n WHERE lease_id = ? AND state = 'active'\n )\n AND generation = (\n SELECT member_gen FROM leases\n WHERE lease_id = ? AND state = 'active'\n )`,\n )\n .run(lease_id, lease_id);\n\n if (memberRes.changes === 0) {\n throw new StaleLeaseError(lease_id);\n }\n\n // (2) Mark lease released.\n database\n .prepare(\n `UPDATE leases\n SET state = 'released', released_at = ?\n WHERE lease_id = ? AND state = 'active'`,\n )\n .run(now, lease_id);\n\n // (3) Event.\n database\n .prepare(\n `INSERT INTO lease_events (lease_id, event, at)\n VALUES (?, 'released', ?)`,\n )\n .run(lease_id, now);\n });\n\n fn.immediate();\n } catch (err) {\n if (isBusyError(err)) throw new LeaseContentionError(lease_id);\n throw err;\n }\n}\n\n// --- Extend ----------------------------------------------------------------\n\n/**\n * Extend a lease's TTL from NOW. Refuses if the lease is no longer active,\n * already past its wall-clock expiry, or its bound member generation has\n * advanced.\n */\nexport function extendLease(lease_id: string, ttl_s: number): { new_expires_at: string } {\n const database = getLeasesDb();\n\n try {\n const fn = database.transaction(() => {\n const now = nowIso();\n const new_expires_at = new Date(Date.now() + ttl_s * 1000).toISOString();\n\n const res = database\n .prepare(\n `UPDATE leases\n SET expires_at = ?\n WHERE lease_id = ?\n AND state = 'active'\n AND expires_at > ?\n AND member_gen = (\n SELECT generation FROM inventory_members\n WHERE inventory_slug = leases.inventory_slug\n AND member_id = leases.member_id\n )`,\n )\n .run(new_expires_at, lease_id, now);\n\n if (res.changes === 0) {\n throw new StaleLeaseError(lease_id);\n }\n\n database\n .prepare(\n `INSERT INTO lease_events (lease_id, event, at, detail_json)\n VALUES (?, 'extended', ?, ?)`,\n )\n .run(lease_id, now, JSON.stringify({ new_expires_at }));\n\n return { new_expires_at };\n });\n\n return fn.immediate();\n } catch (err) {\n if (isBusyError(err)) throw new LeaseContentionError(lease_id);\n throw err;\n }\n}\n\n// --- Read ------------------------------------------------------------------\n\nexport function getLease(lease_id: string): LeaseRow | null {\n const database = getLeasesDb();\n const row = database\n .prepare(\n `SELECT lease_id, inventory_slug, member_id, member_gen, state,\n granted_at, expires_at, released_at, requested_for\n FROM leases WHERE lease_id = ?`,\n )\n .get(lease_id) as LeaseRow | undefined;\n return row ?? null;\n}\n\nexport function listLeases(filter?: ListLeasesFilter): LeaseRow[] {\n const database = getLeasesDb();\n const where: string[] = [];\n const params: unknown[] = [];\n if (filter?.inventory) {\n where.push('inventory_slug = ?');\n params.push(filter.inventory);\n }\n if (filter?.state) {\n where.push('state = ?');\n params.push(filter.state);\n }\n const whereSql = where.length > 0 ? `WHERE ${where.join(' AND ')}` : '';\n return database\n .prepare(\n `SELECT lease_id, inventory_slug, member_id, member_gen, state,\n granted_at, expires_at, released_at, requested_for\n FROM leases ${whereSql}\n ORDER BY granted_at DESC`,\n )\n .all(...params) as LeaseRow[];\n}\n\nexport function listMembers(inventory_slug: string): InventoryMemberRow[] {\n const database = getLeasesDb();\n const inv = database\n .prepare('SELECT slug FROM inventories WHERE slug = ?')\n .get(inventory_slug);\n if (!inv) throw new NotFoundError(inventory_slug);\n return database\n .prepare(\n `SELECT inventory_slug, member_id, status, generation, metadata_json, last_used_at, retired_at\n FROM inventory_members WHERE inventory_slug = ?\n ORDER BY member_id`,\n )\n .all(inventory_slug) as InventoryMemberRow[];\n}\n\n/**\n * Read lease_events. With a `lease_id`, returns that lease's event timeline in\n * chronological order (oldest first). Without, returns the most recent `limit`\n * events across all leases in reverse-chronological order (newest first).\n */\nexport function getLeaseEvents(lease_id?: string, limit = 50): LeaseEventRow[] {\n const database = getLeasesDb();\n if (lease_id) {\n return database\n .prepare(\n `SELECT id, lease_id, event, at, detail_json\n FROM lease_events WHERE lease_id = ?\n ORDER BY at ASC, id ASC\n LIMIT ?`,\n )\n .all(lease_id, limit) as LeaseEventRow[];\n }\n return database\n .prepare(\n `SELECT id, lease_id, event, at, detail_json\n FROM lease_events\n ORDER BY at DESC, id DESC\n LIMIT ?`,\n )\n .all(limit) as LeaseEventRow[];\n}\n\n// --- GC --------------------------------------------------------------------\n\n/**\n * Sweep expired leases across ALL inventories. Idempotent; safe to run\n * concurrently with claims (`BEGIN IMMEDIATE` serializes writers).\n * Returns the number of leases newly transitioned to `expired`.\n */\nexport function gcExpiredLeases(): number {\n const database = getLeasesDb();\n\n try {\n const fn = database.transaction(() => {\n const now = nowIso();\n\n const expRes = database\n .prepare(\n `UPDATE leases SET state = 'expired'\n WHERE state = 'active' AND expires_at <= ?`,\n )\n .run(now);\n\n database\n .prepare(\n `UPDATE inventory_members\n SET status = 'idle', generation = generation + 1\n WHERE status = 'leased'\n AND (inventory_slug, member_id) IN (\n SELECT inventory_slug, member_id FROM leases\n WHERE state = 'expired' AND released_at IS NULL\n )\n AND generation = (\n SELECT member_gen FROM leases l\n WHERE l.inventory_slug = inventory_members.inventory_slug\n AND l.member_id = inventory_members.member_id\n AND l.state = 'expired'\n AND l.released_at IS NULL\n ORDER BY l.granted_at DESC LIMIT 1\n )`,\n )\n .run();\n\n database\n .prepare(\n `UPDATE leases SET released_at = ?\n WHERE state = 'expired' AND released_at IS NULL AND expires_at <= ?`,\n )\n .run(now, now);\n\n return expRes.changes;\n });\n\n return fn.immediate();\n } catch (err) {\n if (isBusyError(err)) throw new LeaseContentionError('gc');\n throw err;\n }\n}\n\n// --- Force release ---------------------------------------------------------\n\n/**\n * Administrative force-release. Bypasses the lease's `state = 'active'` check\n * (humans force-releasing an already-expired-but-not-yet-released lease is\n * fine), but CAS-guards the member free on the lease's bound `member_gen`.\n * This prevents stomping a member that's currently held by a *different*\n * active lease (i.e. the targeted lease is already terminal and the slot has\n * been re-claimed).\n *\n * Returns `member_freed: true` if the member was actually idled by this call,\n * `false` if a newer claim has already taken the slot.\n */\nexport function forceReleaseLease(lease_id: string): { member_freed: boolean } {\n const database = getLeasesDb();\n\n try {\n const fn = database.transaction(() => {\n const now = nowIso();\n\n const lookup = database\n .prepare(\n `SELECT inventory_slug, member_id, member_gen, state\n FROM leases WHERE lease_id = ?`,\n )\n .get(lease_id) as\n | { inventory_slug: string; member_id: string; member_gen: number; state: LeaseState }\n | undefined;\n\n if (!lookup) throw new NotFoundError(lease_id);\n if (lookup.state === 'revoked') return { member_freed: false };\n\n // CAS: free the member only if the lease is still its current holder.\n const memberRes = database\n .prepare(\n `UPDATE inventory_members\n SET status = 'idle', generation = generation + 1\n WHERE inventory_slug = ? AND member_id = ?\n AND generation = ?`,\n )\n .run(lookup.inventory_slug, lookup.member_id, lookup.member_gen);\n\n database\n .prepare(\n `UPDATE leases\n SET state = 'revoked', released_at = ?\n WHERE lease_id = ? AND state != 'revoked'`,\n )\n .run(now, lease_id);\n\n const member_freed = memberRes.changes > 0;\n database\n .prepare(\n `INSERT INTO lease_events (lease_id, event, at, detail_json)\n VALUES (?, 'force_released', ?, ?)`,\n )\n .run(lease_id, now, JSON.stringify({ member_freed }));\n\n return { member_freed };\n });\n\n return fn.immediate();\n } catch (err) {\n if (isBusyError(err)) throw new LeaseContentionError(lease_id);\n throw err;\n }\n}\n\n// --- Inventory update / delete --------------------------------------------\n\nexport interface UpdateInventoryInput {\n default_ttl_s?: number;\n display_name?: string | null;\n}\n\n/**\n * Update mutable inventory fields. `kind` is immutable in v1 — the typed\n * signature excludes it, and a runtime guard rejects any caller that sneaks\n * a `kind` key through (defense in depth).\n */\nexport function updateInventory(\n slug: string,\n input: UpdateInventoryInput,\n): InventoryRow {\n if ('kind' in input) {\n throw new Error('inventory kind is immutable');\n }\n if (\n input.default_ttl_s === undefined &&\n input.display_name === undefined\n ) {\n throw new Error('nothing to update');\n }\n if (input.default_ttl_s !== undefined && input.default_ttl_s <= 0) {\n throw new Error('default_ttl_s must be positive');\n }\n const database = getLeasesDb();\n\n const fn = database.transaction(() => {\n const existing = database\n .prepare('SELECT slug FROM inventories WHERE slug = ?')\n .get(slug);\n if (!existing) throw new NotFoundError(slug);\n\n const sets: string[] = [];\n const params: unknown[] = [];\n if (input.default_ttl_s !== undefined) {\n sets.push('default_ttl_s = ?');\n params.push(input.default_ttl_s);\n }\n if (input.display_name !== undefined) {\n sets.push('display_name = ?');\n params.push(input.display_name);\n }\n params.push(slug);\n\n database\n .prepare(`UPDATE inventories SET ${sets.join(', ')} WHERE slug = ?`)\n .run(...params);\n\n return database\n .prepare(\n `SELECT slug, kind, display_name, default_ttl_s, created_at\n FROM inventories WHERE slug = ?`,\n )\n .get(slug) as InventoryRow;\n });\n\n try {\n return fn.immediate();\n } catch (err) {\n if (isBusyError(err)) throw new LeaseContentionError(slug);\n throw err;\n }\n}\n\n/**\n * Delete an inventory and all of its members, leases, and lease_events.\n *\n * Without `force`: refuses if any lease for the inventory is currently\n * `active` (throws `MemberInUseError`).\n *\n * With `force`: tallies active leases as `revoked` and cascades through\n * events → leases → members → inventory row, ALL inside one `BEGIN IMMEDIATE`\n * transaction. The acquire-up-front lock prevents a concurrent `claimLease`\n * from grabbing a freshly-idled member between the active-lease snapshot and\n * the cascade — what would otherwise be a use-after-free window. No explicit\n * `force_released` event is written because the entire event log for this\n * inventory is deleted in the same tx anyway.\n */\nexport function deleteInventory(\n slug: string,\n opts: { force?: boolean } = {},\n): { deleted: boolean; revoked: number } {\n const database = getLeasesDb();\n\n let revoked = 0;\n const fn = database.transaction(() => {\n const existing = database\n .prepare('SELECT slug FROM inventories WHERE slug = ?')\n .get(slug);\n if (!existing) throw new NotFoundError(slug);\n\n const activeLeases = database\n .prepare(\n `SELECT lease_id FROM leases\n WHERE inventory_slug = ? AND state = 'active'`,\n )\n .all(slug) as Array<{ lease_id: string }>;\n\n if (activeLeases.length > 0 && !opts.force) {\n throw new MemberInUseError(slug, '*');\n }\n revoked = activeLeases.length;\n\n database\n .prepare(\n `DELETE FROM lease_events\n WHERE lease_id IN (SELECT lease_id FROM leases WHERE inventory_slug = ?)`,\n )\n .run(slug);\n database.prepare('DELETE FROM leases WHERE inventory_slug = ?').run(slug);\n database\n .prepare('DELETE FROM inventory_members WHERE inventory_slug = ?')\n .run(slug);\n database.prepare('DELETE FROM inventories WHERE slug = ?').run(slug);\n });\n\n try {\n fn.immediate();\n } catch (err) {\n if (isBusyError(err)) throw new LeaseContentionError(slug);\n throw err;\n }\n return { deleted: true, revoked };\n}\n\n// --- Bulk release by tag --------------------------------------------------\n\n/**\n * Release every `active` lease whose `requested_for` matches `tag`. Per-row\n * `releaseLease` calls keep each release in its own transaction; a\n * `StaleLeaseError` from any individual release is tallied as `stale` and\n * swallowed (the caller asked for a best-effort sweep). Returns the\n * per-lease ids in two arrays so callers can render one-line-per-lease\n * summaries without re-querying.\n */\nexport function releaseLeasesByRequestedFor(\n tag: string,\n): { released: string[]; stale: string[] } {\n const database = getLeasesDb();\n const rows = database\n .prepare(\n `SELECT lease_id FROM leases\n WHERE state = 'active' AND requested_for = ?`,\n )\n .all(tag) as Array<{ lease_id: string }>;\n\n const released: string[] = [];\n const stale: string[] = [];\n for (const { lease_id } of rows) {\n try {\n releaseLease(lease_id);\n released.push(lease_id);\n } catch (err) {\n if (err instanceof StaleLeaseError) {\n stale.push(lease_id);\n continue;\n }\n throw err;\n }\n }\n return { released, stale };\n}\n","import { homedir } from 'node:os';\nimport { resolve } from 'node:path';\n\nexport function expandHome(p: string): string {\n if (p.startsWith('~/') || p === '~') {\n return resolve(homedir(), p.slice(2));\n }\n return p;\n}\n\nexport function syntaurRoot(): string {\n const override = process.env.SYNTAUR_HOME;\n if (override && override.length > 0) {\n return resolve(expandHome(override));\n }\n return resolve(homedir(), '.syntaur');\n}\n\nexport function defaultProjectDir(): string {\n return resolve(syntaurRoot(), 'projects');\n}\n\nexport function assignmentsDir(): string {\n return resolve(syntaurRoot(), 'assignments');\n}\n\nexport function serversDir(): string {\n return resolve(syntaurRoot(), 'servers');\n}\n\nexport function playbooksDir(): string {\n return resolve(syntaurRoot(), 'playbooks');\n}\n\nexport function todosDir(): string {\n return resolve(syntaurRoot(), 'todos');\n}\n\nexport function viewPrefsFile(): string {\n return resolve(syntaurRoot(), 'view-prefs.json');\n}\n\nexport function projectTodosDir(projectsDir: string, projectSlug: string): string {\n return resolve(projectsDir, projectSlug, 'todos');\n}\n\nexport function todoPlanDir(todosDir: string, workspaceOrProject: string, todoId: string): string {\n return resolve(todosDir, 'plans', workspaceOrProject, todoId);\n}\n\nexport function proofDir(assignmentDir: string): string {\n return resolve(assignmentDir, 'proof');\n}\n"],"mappings":"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","names":["resolve","resolve"]}
|
|
1
|
+
{"version":3,"sources":["../../src/db/leases-db.ts","../../src/utils/paths.ts"],"sourcesContent":["/**\n * Resource leases database module.\n *\n * Shares `~/.syntaur/syntaur.db` with `session-db.ts` and `proof-db.ts`. Each\n * module owns its own schema-version row in the shared `meta` table and creates\n * `meta` with `IF NOT EXISTS` so init order is irrelevant and standalone init\n * (e.g. in a leases-only test DB) is safe.\n *\n * Concurrency model:\n * - `journal_mode = WAL` allows concurrent readers and one writer.\n * - `busy_timeout = 5000` lets SQLite absorb short lock contention internally.\n * - All mutating ops run inside `db.transaction(fn).immediate()` which issues\n * `BEGIN IMMEDIATE` — acquires the reserved lock up-front so two concurrent\n * writers serialize cleanly.\n *\n * Atomic claim uses the tuple-subquery form (the bundled SQLite is built\n * without `SQLITE_ENABLE_UPDATE_DELETE_LIMIT`, so `UPDATE...ORDER BY...LIMIT...\n * RETURNING` is a syntax error — verified empirically).\n *\n * Timestamp invariant: every timestamp written from this module is produced by\n * `nowIso()` which calls `new Date().toISOString()` (canonical UTC, lexicographic-\n * safe). Lexicographic `<=` comparisons in SQL rely on this. The CLI never\n * accepts user-supplied timestamps. A future caller writing a non-canonical\n * timestamp would silently misorder `<=` checks.\n */\n\nimport Database from 'better-sqlite3';\nimport { randomUUID } from 'node:crypto';\nimport { resolve } from 'node:path';\nimport { syntaurRoot } from '../utils/paths.js';\n\nlet db: Database.Database | null = null;\n\nconst LEASE_SCHEMA_VERSION = '1';\n\nconst SCHEMA_SQL = `\nCREATE TABLE IF NOT EXISTS meta (\n key TEXT PRIMARY KEY,\n value TEXT\n);\n\nCREATE TABLE IF NOT EXISTS inventories (\n slug TEXT PRIMARY KEY,\n kind TEXT NOT NULL,\n display_name TEXT,\n default_ttl_s INTEGER NOT NULL CHECK (default_ttl_s > 0),\n created_at TEXT NOT NULL\n);\n\nCREATE TABLE IF NOT EXISTS inventory_members (\n inventory_slug TEXT NOT NULL,\n member_id TEXT NOT NULL,\n status TEXT NOT NULL DEFAULT 'idle'\n CHECK (status IN ('idle','leased','retired')),\n generation INTEGER NOT NULL DEFAULT 0,\n metadata_json TEXT,\n last_used_at TEXT,\n retired_at TEXT,\n PRIMARY KEY (inventory_slug, member_id),\n FOREIGN KEY (inventory_slug) REFERENCES inventories(slug)\n);\n\nCREATE INDEX IF NOT EXISTS idx_members_idle\n ON inventory_members (inventory_slug, status, last_used_at);\n\nCREATE TABLE IF NOT EXISTS leases (\n lease_id TEXT PRIMARY KEY,\n inventory_slug TEXT NOT NULL,\n member_id TEXT NOT NULL,\n member_gen INTEGER NOT NULL,\n state TEXT NOT NULL\n CHECK (state IN ('active','released','expired','revoked')),\n granted_at TEXT NOT NULL,\n expires_at TEXT NOT NULL,\n released_at TEXT,\n requested_for TEXT,\n FOREIGN KEY (inventory_slug, member_id)\n REFERENCES inventory_members(inventory_slug, member_id)\n);\n\nCREATE INDEX IF NOT EXISTS idx_leases_gc\n ON leases (state, expires_at);\n\nCREATE TABLE IF NOT EXISTS lease_events (\n id INTEGER PRIMARY KEY AUTOINCREMENT,\n lease_id TEXT NOT NULL,\n event TEXT NOT NULL,\n at TEXT NOT NULL,\n detail_json TEXT,\n FOREIGN KEY (lease_id) REFERENCES leases(lease_id)\n);\n\nCREATE INDEX IF NOT EXISTS idx_events_lease\n ON lease_events (lease_id, at);\n`;\n\n// --- Types -----------------------------------------------------------------\n\nexport type MemberStatus = 'idle' | 'leased' | 'retired';\nexport type LeaseState = 'active' | 'released' | 'expired' | 'revoked';\n\nexport interface InventoryRow {\n slug: string;\n kind: string;\n display_name: string | null;\n default_ttl_s: number;\n created_at: string;\n}\n\nexport interface InventoryMemberRow {\n inventory_slug: string;\n member_id: string;\n status: MemberStatus;\n generation: number;\n metadata_json: string | null;\n last_used_at: string | null;\n retired_at: string | null;\n}\n\nexport interface LeaseRow {\n lease_id: string;\n inventory_slug: string;\n member_id: string;\n member_gen: number;\n state: LeaseState;\n granted_at: string;\n expires_at: string;\n released_at: string | null;\n requested_for: string | null;\n}\n\nexport interface LeaseEventRow {\n id: number;\n lease_id: string;\n event: string;\n at: string;\n detail_json: string | null;\n}\n\nexport interface InventoryDetail {\n inventory: InventoryRow;\n members: InventoryMemberRow[];\n active_leases: LeaseRow[];\n}\n\nexport interface CreateInventoryInput {\n slug: string;\n kind: string;\n display_name?: string;\n default_ttl_s: number;\n}\n\nexport interface AddMemberInput {\n inventory_slug: string;\n member_id: string;\n metadata?: Record<string, unknown>;\n}\n\nexport interface ClaimResult {\n lease_id: string;\n inventory_slug: string;\n member_id: string;\n member_gen: number;\n granted_at: string;\n expires_at: string;\n metadata: Record<string, unknown> | null;\n}\n\nexport interface ListLeasesFilter {\n inventory?: string;\n state?: LeaseState;\n}\n\n// --- Errors ----------------------------------------------------------------\n\nexport class NoIdleMemberError extends Error {\n constructor(public readonly inventorySlug: string) {\n super(`no idle members in '${inventorySlug}'`);\n this.name = 'NoIdleMemberError';\n }\n}\n\nexport class StaleLeaseError extends Error {\n constructor(public readonly leaseId: string) {\n super(`lease ${leaseId} is no longer active (expired or revoked)`);\n this.name = 'StaleLeaseError';\n }\n}\n\nexport class NotFoundError extends Error {\n constructor(public readonly id: string) {\n super(`lease ${id} not found`);\n this.name = 'NotFoundError';\n }\n}\n\nexport class LeaseContentionError extends Error {\n constructor(public readonly inventorySlug: string) {\n super(`contention timeout on '${inventorySlug}'; retry`);\n this.name = 'LeaseContentionError';\n }\n}\n\nexport class MemberInUseError extends Error {\n constructor(public readonly inventorySlug: string, public readonly memberId: string) {\n super(`member ${inventorySlug}/${memberId} is currently leased; release before retiring`);\n this.name = 'MemberInUseError';\n }\n}\n\nexport class DuplicateInventoryError extends Error {\n constructor(public readonly slug: string) {\n super(`inventory '${slug}' already exists`);\n this.name = 'DuplicateInventoryError';\n }\n}\n\nexport class DuplicateMemberError extends Error {\n constructor(public readonly inventorySlug: string, public readonly memberId: string) {\n super(`member ${inventorySlug}/${memberId} already exists`);\n this.name = 'DuplicateMemberError';\n }\n}\n\n// --- Helpers ---------------------------------------------------------------\n\n/** Canonical UTC ISO 8601 timestamp. Lexicographic-safe for SQL `<=` checks. */\nexport function nowIso(): string {\n return new Date().toISOString();\n}\n\nfunction isBusyError(err: unknown): boolean {\n if (!err || typeof err !== 'object') return false;\n const e = err as { code?: string };\n return e.code === 'SQLITE_BUSY' || e.code === 'SQLITE_BUSY_SNAPSHOT';\n}\n\n// --- Lifecycle -------------------------------------------------------------\n\n/**\n * Initialize the leases database. Idempotent — repeated calls return the same\n * singleton handle. Pass an explicit `dbPath` for tests; defaults to\n * `~/.syntaur/syntaur.db`. Safe to run standalone (creates its own `meta` table).\n */\nexport function initLeasesDb(dbPath?: string): Database.Database {\n if (db) return db;\n\n const finalPath = dbPath ?? resolve(syntaurRoot(), 'syntaur.db');\n db = new Database(finalPath);\n db.pragma('journal_mode = WAL');\n db.pragma('busy_timeout = 5000');\n db.pragma('foreign_keys = ON');\n\n const database = db;\n const runMigrations = database.transaction(() => {\n database.exec(SCHEMA_SQL);\n database\n .prepare('INSERT OR IGNORE INTO meta (key, value) VALUES (?, ?)')\n .run('lease_schema_version', LEASE_SCHEMA_VERSION);\n });\n runMigrations.exclusive();\n\n return db;\n}\n\nexport function getLeasesDb(): Database.Database {\n if (!db) {\n throw new Error('Leases database not initialized. Call initLeasesDb() first.');\n }\n return db;\n}\n\nexport function closeLeasesDb(): void {\n if (db) {\n db.close();\n db = null;\n }\n}\n\nexport function resetLeasesDb(): void {\n db = null;\n}\n\n// --- Inventories -----------------------------------------------------------\n\nexport function createInventory(input: CreateInventoryInput): InventoryRow {\n const database = getLeasesDb();\n const row: InventoryRow = {\n slug: input.slug,\n kind: input.kind,\n display_name: input.display_name ?? null,\n default_ttl_s: input.default_ttl_s,\n created_at: nowIso(),\n };\n try {\n database\n .prepare(\n `INSERT INTO inventories (slug, kind, display_name, default_ttl_s, created_at)\n VALUES (?, ?, ?, ?, ?)`,\n )\n .run(row.slug, row.kind, row.display_name, row.default_ttl_s, row.created_at);\n } catch (err) {\n if (err instanceof Error && err.message.includes('UNIQUE')) {\n throw new DuplicateInventoryError(input.slug);\n }\n throw err;\n }\n return row;\n}\n\nexport function listInventories(): InventoryRow[] {\n const database = getLeasesDb();\n return database\n .prepare(\n `SELECT slug, kind, display_name, default_ttl_s, created_at\n FROM inventories ORDER BY slug`,\n )\n .all() as InventoryRow[];\n}\n\nexport function getInventoryDetail(slug: string): InventoryDetail | null {\n const database = getLeasesDb();\n const inv = database\n .prepare(\n `SELECT slug, kind, display_name, default_ttl_s, created_at\n FROM inventories WHERE slug = ?`,\n )\n .get(slug) as InventoryRow | undefined;\n if (!inv) return null;\n const members = database\n .prepare(\n `SELECT inventory_slug, member_id, status, generation, metadata_json, last_used_at, retired_at\n FROM inventory_members WHERE inventory_slug = ?\n ORDER BY member_id`,\n )\n .all(slug) as InventoryMemberRow[];\n const active_leases = database\n .prepare(\n `SELECT lease_id, inventory_slug, member_id, member_gen, state, granted_at, expires_at, released_at, requested_for\n FROM leases WHERE inventory_slug = ? AND state = 'active'\n ORDER BY granted_at`,\n )\n .all(slug) as LeaseRow[];\n return { inventory: inv, members, active_leases };\n}\n\n// --- Members ---------------------------------------------------------------\n\nexport function addMember(input: AddMemberInput): InventoryMemberRow {\n const database = getLeasesDb();\n const row: InventoryMemberRow = {\n inventory_slug: input.inventory_slug,\n member_id: input.member_id,\n status: 'idle',\n generation: 0,\n metadata_json: input.metadata ? JSON.stringify(input.metadata) : null,\n last_used_at: null,\n retired_at: null,\n };\n try {\n database\n .prepare(\n `INSERT INTO inventory_members\n (inventory_slug, member_id, status, generation, metadata_json)\n VALUES (?, ?, 'idle', 0, ?)`,\n )\n .run(row.inventory_slug, row.member_id, row.metadata_json);\n } catch (err) {\n if (err instanceof Error && err.message.includes('UNIQUE')) {\n throw new DuplicateMemberError(input.inventory_slug, input.member_id);\n }\n throw err;\n }\n return row;\n}\n\nexport function retireMember(inventorySlug: string, memberId: string): void {\n const database = getLeasesDb();\n const retire = database.transaction(() => {\n const row = database\n .prepare(\n `SELECT status FROM inventory_members\n WHERE inventory_slug = ? AND member_id = ?`,\n )\n .get(inventorySlug, memberId) as { status: MemberStatus } | undefined;\n if (!row) throw new NotFoundError(`${inventorySlug}/${memberId}`);\n if (row.status === 'leased') {\n throw new MemberInUseError(inventorySlug, memberId);\n }\n database\n .prepare(\n `UPDATE inventory_members\n SET status = 'retired', generation = generation + 1, retired_at = ?\n WHERE inventory_slug = ? AND member_id = ?`,\n )\n .run(nowIso(), inventorySlug, memberId);\n });\n retire.immediate();\n}\n\n// --- Claim -----------------------------------------------------------------\n\n/**\n * Claim an idle member of `slug`. Atomic — uses `BEGIN IMMEDIATE` so two\n * concurrent claimants serialize. Returns lease metadata or throws\n * `NoIdleMemberError` if the inventory has nothing claimable after sweeping.\n */\nexport function claimLease(\n slug: string,\n ttl_s: number,\n requested_for?: string,\n): ClaimResult {\n const database = getLeasesDb();\n\n try {\n const fn = database.transaction(() => {\n const now = nowIso();\n\n // (1) Opportunistic sweep — expire active leases on this inventory whose TTL is up.\n database\n .prepare(\n `UPDATE leases\n SET state = 'expired'\n WHERE state = 'active'\n AND expires_at <= ?\n AND inventory_slug = ?`,\n )\n .run(now, slug);\n\n // (2) Free their members (CAS on generation match).\n database\n .prepare(\n `UPDATE inventory_members\n SET status = 'idle', generation = generation + 1\n WHERE inventory_slug = ?\n AND status = 'leased'\n AND (inventory_slug, member_id) IN (\n SELECT inventory_slug, member_id\n FROM leases\n WHERE inventory_slug = ?\n AND state = 'expired'\n AND released_at IS NULL\n )\n AND generation = (\n SELECT member_gen FROM leases l\n WHERE l.inventory_slug = inventory_members.inventory_slug\n AND l.member_id = inventory_members.member_id\n AND l.state = 'expired'\n AND l.released_at IS NULL\n ORDER BY l.granted_at DESC LIMIT 1\n )`,\n )\n .run(slug, slug);\n\n // (3) Mark those freshly-expired leases as fully released (idempotent sentinel).\n database\n .prepare(\n `UPDATE leases\n SET released_at = ?\n WHERE state = 'expired'\n AND released_at IS NULL\n AND inventory_slug = ?`,\n )\n .run(now, slug);\n\n // (4) Pick an idle member via tuple-subquery (verified portable form).\n const picked = database\n .prepare(\n `UPDATE inventory_members\n SET status = 'leased',\n generation = generation + 1,\n last_used_at = ?\n WHERE (inventory_slug, member_id) = (\n SELECT inventory_slug, member_id\n FROM inventory_members\n WHERE inventory_slug = ? AND status = 'idle'\n ORDER BY last_used_at ASC NULLS FIRST\n LIMIT 1\n )\n RETURNING member_id, generation, metadata_json`,\n )\n .get(now, slug) as\n | { member_id: string; generation: number; metadata_json: string | null }\n | undefined;\n\n if (!picked) {\n throw new NoIdleMemberError(slug);\n }\n\n // (5) Insert the lease tied to the new member_gen.\n const lease_id = randomUUID();\n const expires_at = new Date(Date.now() + ttl_s * 1000).toISOString();\n database\n .prepare(\n `INSERT INTO leases\n (lease_id, inventory_slug, member_id, member_gen,\n state, granted_at, expires_at, requested_for)\n VALUES (?, ?, ?, ?, 'active', ?, ?, ?)`,\n )\n .run(\n lease_id,\n slug,\n picked.member_id,\n picked.generation,\n now,\n expires_at,\n requested_for ?? null,\n );\n\n // (6) Event.\n database\n .prepare(\n `INSERT INTO lease_events (lease_id, event, at, detail_json)\n VALUES (?, 'claimed', ?, ?)`,\n )\n .run(lease_id, now, JSON.stringify({ member_id: picked.member_id }));\n\n return {\n lease_id,\n inventory_slug: slug,\n member_id: picked.member_id,\n member_gen: picked.generation,\n granted_at: now,\n expires_at,\n metadata: picked.metadata_json\n ? (JSON.parse(picked.metadata_json) as Record<string, unknown>)\n : null,\n } satisfies ClaimResult;\n });\n\n return fn.immediate();\n } catch (err) {\n if (isBusyError(err)) throw new LeaseContentionError(slug);\n throw err;\n }\n}\n\n// --- Release ---------------------------------------------------------------\n\nexport function releaseLease(lease_id: string): void {\n const database = getLeasesDb();\n\n try {\n const fn = database.transaction(() => {\n const now = nowIso();\n\n // (1) CAS-guarded member free.\n const memberRes = database\n .prepare(\n `UPDATE inventory_members\n SET status = 'idle', generation = generation + 1\n WHERE (inventory_slug, member_id) = (\n SELECT inventory_slug, member_id FROM leases\n WHERE lease_id = ? AND state = 'active'\n )\n AND generation = (\n SELECT member_gen FROM leases\n WHERE lease_id = ? AND state = 'active'\n )`,\n )\n .run(lease_id, lease_id);\n\n if (memberRes.changes === 0) {\n throw new StaleLeaseError(lease_id);\n }\n\n // (2) Mark lease released.\n database\n .prepare(\n `UPDATE leases\n SET state = 'released', released_at = ?\n WHERE lease_id = ? AND state = 'active'`,\n )\n .run(now, lease_id);\n\n // (3) Event.\n database\n .prepare(\n `INSERT INTO lease_events (lease_id, event, at)\n VALUES (?, 'released', ?)`,\n )\n .run(lease_id, now);\n });\n\n fn.immediate();\n } catch (err) {\n if (isBusyError(err)) throw new LeaseContentionError(lease_id);\n throw err;\n }\n}\n\n// --- Extend ----------------------------------------------------------------\n\n/**\n * Extend a lease's TTL from NOW. Refuses if the lease is no longer active,\n * already past its wall-clock expiry, or its bound member generation has\n * advanced.\n */\nexport function extendLease(lease_id: string, ttl_s: number): { new_expires_at: string } {\n const database = getLeasesDb();\n\n try {\n const fn = database.transaction(() => {\n const now = nowIso();\n const new_expires_at = new Date(Date.now() + ttl_s * 1000).toISOString();\n\n const res = database\n .prepare(\n `UPDATE leases\n SET expires_at = ?\n WHERE lease_id = ?\n AND state = 'active'\n AND expires_at > ?\n AND member_gen = (\n SELECT generation FROM inventory_members\n WHERE inventory_slug = leases.inventory_slug\n AND member_id = leases.member_id\n )`,\n )\n .run(new_expires_at, lease_id, now);\n\n if (res.changes === 0) {\n throw new StaleLeaseError(lease_id);\n }\n\n database\n .prepare(\n `INSERT INTO lease_events (lease_id, event, at, detail_json)\n VALUES (?, 'extended', ?, ?)`,\n )\n .run(lease_id, now, JSON.stringify({ new_expires_at }));\n\n return { new_expires_at };\n });\n\n return fn.immediate();\n } catch (err) {\n if (isBusyError(err)) throw new LeaseContentionError(lease_id);\n throw err;\n }\n}\n\n// --- Read ------------------------------------------------------------------\n\nexport function getLease(lease_id: string): LeaseRow | null {\n const database = getLeasesDb();\n const row = database\n .prepare(\n `SELECT lease_id, inventory_slug, member_id, member_gen, state,\n granted_at, expires_at, released_at, requested_for\n FROM leases WHERE lease_id = ?`,\n )\n .get(lease_id) as LeaseRow | undefined;\n return row ?? null;\n}\n\nexport function listLeases(filter?: ListLeasesFilter): LeaseRow[] {\n const database = getLeasesDb();\n const where: string[] = [];\n const params: unknown[] = [];\n if (filter?.inventory) {\n where.push('inventory_slug = ?');\n params.push(filter.inventory);\n }\n if (filter?.state) {\n where.push('state = ?');\n params.push(filter.state);\n }\n const whereSql = where.length > 0 ? `WHERE ${where.join(' AND ')}` : '';\n return database\n .prepare(\n `SELECT lease_id, inventory_slug, member_id, member_gen, state,\n granted_at, expires_at, released_at, requested_for\n FROM leases ${whereSql}\n ORDER BY granted_at DESC`,\n )\n .all(...params) as LeaseRow[];\n}\n\nexport function listMembers(inventory_slug: string): InventoryMemberRow[] {\n const database = getLeasesDb();\n const inv = database\n .prepare('SELECT slug FROM inventories WHERE slug = ?')\n .get(inventory_slug);\n if (!inv) throw new NotFoundError(inventory_slug);\n return database\n .prepare(\n `SELECT inventory_slug, member_id, status, generation, metadata_json, last_used_at, retired_at\n FROM inventory_members WHERE inventory_slug = ?\n ORDER BY member_id`,\n )\n .all(inventory_slug) as InventoryMemberRow[];\n}\n\n/**\n * Read lease_events. With a `lease_id`, returns that lease's event timeline in\n * chronological order (oldest first). Without, returns the most recent `limit`\n * events across all leases in reverse-chronological order (newest first).\n */\nexport function getLeaseEvents(lease_id?: string, limit = 50): LeaseEventRow[] {\n const database = getLeasesDb();\n if (lease_id) {\n return database\n .prepare(\n `SELECT id, lease_id, event, at, detail_json\n FROM lease_events WHERE lease_id = ?\n ORDER BY at ASC, id ASC\n LIMIT ?`,\n )\n .all(lease_id, limit) as LeaseEventRow[];\n }\n return database\n .prepare(\n `SELECT id, lease_id, event, at, detail_json\n FROM lease_events\n ORDER BY at DESC, id DESC\n LIMIT ?`,\n )\n .all(limit) as LeaseEventRow[];\n}\n\n// --- GC --------------------------------------------------------------------\n\n/**\n * Sweep expired leases across ALL inventories. Idempotent; safe to run\n * concurrently with claims (`BEGIN IMMEDIATE` serializes writers).\n * Returns the number of leases newly transitioned to `expired`.\n */\nexport function gcExpiredLeases(): number {\n const database = getLeasesDb();\n\n try {\n const fn = database.transaction(() => {\n const now = nowIso();\n\n const expRes = database\n .prepare(\n `UPDATE leases SET state = 'expired'\n WHERE state = 'active' AND expires_at <= ?`,\n )\n .run(now);\n\n database\n .prepare(\n `UPDATE inventory_members\n SET status = 'idle', generation = generation + 1\n WHERE status = 'leased'\n AND (inventory_slug, member_id) IN (\n SELECT inventory_slug, member_id FROM leases\n WHERE state = 'expired' AND released_at IS NULL\n )\n AND generation = (\n SELECT member_gen FROM leases l\n WHERE l.inventory_slug = inventory_members.inventory_slug\n AND l.member_id = inventory_members.member_id\n AND l.state = 'expired'\n AND l.released_at IS NULL\n ORDER BY l.granted_at DESC LIMIT 1\n )`,\n )\n .run();\n\n database\n .prepare(\n `UPDATE leases SET released_at = ?\n WHERE state = 'expired' AND released_at IS NULL AND expires_at <= ?`,\n )\n .run(now, now);\n\n return expRes.changes;\n });\n\n return fn.immediate();\n } catch (err) {\n if (isBusyError(err)) throw new LeaseContentionError('gc');\n throw err;\n }\n}\n\n// --- Force release ---------------------------------------------------------\n\n/**\n * Administrative force-release. Bypasses the lease's `state = 'active'` check\n * (humans force-releasing an already-expired-but-not-yet-released lease is\n * fine), but CAS-guards the member free on the lease's bound `member_gen`.\n * This prevents stomping a member that's currently held by a *different*\n * active lease (i.e. the targeted lease is already terminal and the slot has\n * been re-claimed).\n *\n * Returns `member_freed: true` if the member was actually idled by this call,\n * `false` if a newer claim has already taken the slot.\n */\nexport function forceReleaseLease(lease_id: string): { member_freed: boolean } {\n const database = getLeasesDb();\n\n try {\n const fn = database.transaction(() => {\n const now = nowIso();\n\n const lookup = database\n .prepare(\n `SELECT inventory_slug, member_id, member_gen, state\n FROM leases WHERE lease_id = ?`,\n )\n .get(lease_id) as\n | { inventory_slug: string; member_id: string; member_gen: number; state: LeaseState }\n | undefined;\n\n if (!lookup) throw new NotFoundError(lease_id);\n if (lookup.state === 'revoked') return { member_freed: false };\n\n // CAS: free the member only if the lease is still its current holder.\n const memberRes = database\n .prepare(\n `UPDATE inventory_members\n SET status = 'idle', generation = generation + 1\n WHERE inventory_slug = ? AND member_id = ?\n AND generation = ?`,\n )\n .run(lookup.inventory_slug, lookup.member_id, lookup.member_gen);\n\n database\n .prepare(\n `UPDATE leases\n SET state = 'revoked', released_at = ?\n WHERE lease_id = ? AND state != 'revoked'`,\n )\n .run(now, lease_id);\n\n const member_freed = memberRes.changes > 0;\n database\n .prepare(\n `INSERT INTO lease_events (lease_id, event, at, detail_json)\n VALUES (?, 'force_released', ?, ?)`,\n )\n .run(lease_id, now, JSON.stringify({ member_freed }));\n\n return { member_freed };\n });\n\n return fn.immediate();\n } catch (err) {\n if (isBusyError(err)) throw new LeaseContentionError(lease_id);\n throw err;\n }\n}\n\n// --- Inventory update / delete --------------------------------------------\n\nexport interface UpdateInventoryInput {\n default_ttl_s?: number;\n display_name?: string | null;\n}\n\n/**\n * Update mutable inventory fields. `kind` is immutable in v1 — the typed\n * signature excludes it, and a runtime guard rejects any caller that sneaks\n * a `kind` key through (defense in depth).\n */\nexport function updateInventory(\n slug: string,\n input: UpdateInventoryInput,\n): InventoryRow {\n if ('kind' in input) {\n throw new Error('inventory kind is immutable');\n }\n if (\n input.default_ttl_s === undefined &&\n input.display_name === undefined\n ) {\n throw new Error('nothing to update');\n }\n if (input.default_ttl_s !== undefined && input.default_ttl_s <= 0) {\n throw new Error('default_ttl_s must be positive');\n }\n const database = getLeasesDb();\n\n const fn = database.transaction(() => {\n const existing = database\n .prepare('SELECT slug FROM inventories WHERE slug = ?')\n .get(slug);\n if (!existing) throw new NotFoundError(slug);\n\n const sets: string[] = [];\n const params: unknown[] = [];\n if (input.default_ttl_s !== undefined) {\n sets.push('default_ttl_s = ?');\n params.push(input.default_ttl_s);\n }\n if (input.display_name !== undefined) {\n sets.push('display_name = ?');\n params.push(input.display_name);\n }\n params.push(slug);\n\n database\n .prepare(`UPDATE inventories SET ${sets.join(', ')} WHERE slug = ?`)\n .run(...params);\n\n return database\n .prepare(\n `SELECT slug, kind, display_name, default_ttl_s, created_at\n FROM inventories WHERE slug = ?`,\n )\n .get(slug) as InventoryRow;\n });\n\n try {\n return fn.immediate();\n } catch (err) {\n if (isBusyError(err)) throw new LeaseContentionError(slug);\n throw err;\n }\n}\n\n/**\n * Delete an inventory and all of its members, leases, and lease_events.\n *\n * Without `force`: refuses if any lease for the inventory is currently\n * `active` (throws `MemberInUseError`).\n *\n * With `force`: tallies active leases as `revoked` and cascades through\n * events → leases → members → inventory row, ALL inside one `BEGIN IMMEDIATE`\n * transaction. The acquire-up-front lock prevents a concurrent `claimLease`\n * from grabbing a freshly-idled member between the active-lease snapshot and\n * the cascade — what would otherwise be a use-after-free window. No explicit\n * `force_released` event is written because the entire event log for this\n * inventory is deleted in the same tx anyway.\n */\nexport function deleteInventory(\n slug: string,\n opts: { force?: boolean } = {},\n): { deleted: boolean; revoked: number } {\n const database = getLeasesDb();\n\n let revoked = 0;\n const fn = database.transaction(() => {\n const existing = database\n .prepare('SELECT slug FROM inventories WHERE slug = ?')\n .get(slug);\n if (!existing) throw new NotFoundError(slug);\n\n const activeLeases = database\n .prepare(\n `SELECT lease_id FROM leases\n WHERE inventory_slug = ? AND state = 'active'`,\n )\n .all(slug) as Array<{ lease_id: string }>;\n\n if (activeLeases.length > 0 && !opts.force) {\n throw new MemberInUseError(slug, '*');\n }\n revoked = activeLeases.length;\n\n database\n .prepare(\n `DELETE FROM lease_events\n WHERE lease_id IN (SELECT lease_id FROM leases WHERE inventory_slug = ?)`,\n )\n .run(slug);\n database.prepare('DELETE FROM leases WHERE inventory_slug = ?').run(slug);\n database\n .prepare('DELETE FROM inventory_members WHERE inventory_slug = ?')\n .run(slug);\n database.prepare('DELETE FROM inventories WHERE slug = ?').run(slug);\n });\n\n try {\n fn.immediate();\n } catch (err) {\n if (isBusyError(err)) throw new LeaseContentionError(slug);\n throw err;\n }\n return { deleted: true, revoked };\n}\n\n// --- Bulk release by tag --------------------------------------------------\n\n/**\n * Release every `active` lease whose `requested_for` matches `tag`. Per-row\n * `releaseLease` calls keep each release in its own transaction; a\n * `StaleLeaseError` from any individual release is tallied as `stale` and\n * swallowed (the caller asked for a best-effort sweep). Returns the\n * per-lease ids in two arrays so callers can render one-line-per-lease\n * summaries without re-querying.\n */\nexport function releaseLeasesByRequestedFor(\n tag: string,\n): { released: string[]; stale: string[] } {\n const database = getLeasesDb();\n const rows = database\n .prepare(\n `SELECT lease_id FROM leases\n WHERE state = 'active' AND requested_for = ?`,\n )\n .all(tag) as Array<{ lease_id: string }>;\n\n const released: string[] = [];\n const stale: string[] = [];\n for (const { lease_id } of rows) {\n try {\n releaseLease(lease_id);\n released.push(lease_id);\n } catch (err) {\n if (err instanceof StaleLeaseError) {\n stale.push(lease_id);\n continue;\n }\n throw err;\n }\n }\n return { released, stale };\n}\n","import { homedir } from 'node:os';\nimport { resolve } from 'node:path';\n\nexport function expandHome(p: string): string {\n if (p.startsWith('~/') || p === '~') {\n return resolve(homedir(), p.slice(2));\n }\n return p;\n}\n\nexport function syntaurRoot(): string {\n const override = process.env.SYNTAUR_HOME;\n if (override && override.length > 0) {\n return resolve(expandHome(override));\n }\n return resolve(homedir(), '.syntaur');\n}\n\nexport function defaultProjectDir(): string {\n return resolve(syntaurRoot(), 'projects');\n}\n\nexport function assignmentsDir(): string {\n return resolve(syntaurRoot(), 'assignments');\n}\n\nexport function serversDir(): string {\n return resolve(syntaurRoot(), 'servers');\n}\n\nexport function playbooksDir(): string {\n return resolve(syntaurRoot(), 'playbooks');\n}\n\nexport function todosDir(): string {\n return resolve(syntaurRoot(), 'todos');\n}\n\nexport function viewPrefsFile(): string {\n return resolve(syntaurRoot(), 'view-prefs.json');\n}\n\nexport function savedViewsFile(): string {\n return resolve(syntaurRoot(), 'saved-views.json');\n}\n\nexport function projectTodosDir(projectsDir: string, projectSlug: string): string {\n return resolve(projectsDir, projectSlug, 'todos');\n}\n\nexport function todoPlanDir(todosDir: string, workspaceOrProject: string, todoId: string): string {\n return resolve(todosDir, 'plans', workspaceOrProject, todoId);\n}\n\n// Bundle plan files live under `plans/<scopeOrProject>/bundles/<bundleId>/`,\n// keeping them disjoint from todo plans (which omit the `bundles/` segment).\nexport function bundlePlanDir(todosDir: string, scopeOrProject: string, bundleId: string): string {\n return resolve(todosDir, 'plans', scopeOrProject, 'bundles', bundleId);\n}\n\n// Bundle storage lives under a `bundles/` subdirectory so the workspace-checklist\n// discovery glob (which scans top-level *.md files in todosDir) does not pick it up.\nexport function bundlesDir(todosDir: string): string {\n return resolve(todosDir, 'bundles');\n}\n\nexport function bundlesPath(todosDir: string): string {\n return resolve(todosDir, 'bundles', 'index.md');\n}\n\nexport function proofDir(assignmentDir: string): string {\n return resolve(assignmentDir, 'proof');\n}\n"],"mappings":"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","names":["resolve","resolve"]}
|