@mean-weasel/lineage 0.1.4 → 0.1.5

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.
@@ -1297,6 +1297,37 @@ function activeLineageWorkspaceRoot(project) {
1297
1297
  }
1298
1298
  }
1299
1299
 
1300
+ // src/server/lineageClaimGuards.ts
1301
+ function channelOverlaps2(left, right) {
1302
+ return !left || !right || left === right;
1303
+ }
1304
+ function hasActiveLineageWorkspaceClaim(project, targetId, channel) {
1305
+ return listAgentClaims(project).claims.some((claim) => {
1306
+ if (claim.project !== project || claim.status !== "active" || claim.derived_state === "expired") return false;
1307
+ if (claim.scope_type === "lineage_workspace") return claim.target_id === targetId;
1308
+ return claim.scope_type === "project_channel" && channelOverlaps2(channel, claim.channel);
1309
+ });
1310
+ }
1311
+ function requireLineageWorkspaceClaimForWrite(fields) {
1312
+ if (!fields.confirmWrite) return;
1313
+ const targetId = lineageWorkspaceId(fields.project, fields.rootAssetId);
1314
+ if (!fields.claimToken && !hasActiveLineageWorkspaceClaim(fields.project, targetId, fields.channel)) return;
1315
+ const validation = validateAgentClaimForWrite({
1316
+ channel: fields.channel,
1317
+ claimToken: fields.claimToken,
1318
+ confirmWrite: fields.confirmWrite,
1319
+ dangerLevel: "enforce",
1320
+ project: fields.project,
1321
+ scopeType: "lineage_workspace",
1322
+ targetId,
1323
+ writeKind: fields.writeKind
1324
+ });
1325
+ if (!validation.ok) {
1326
+ const status = validation.code === "claim_required" || validation.code === "claim_token_invalid" ? 401 : 409;
1327
+ throw new AgentClaimError(validation.message, status, validation.code, validation.conflicts);
1328
+ }
1329
+ }
1330
+
1300
1331
  // src/server/assetLineage.ts
1301
1332
  var LineageError = class extends Error {
1302
1333
  constructor(message, status = 400) {
@@ -1324,6 +1355,45 @@ function rootFor(database, project, assetId) {
1324
1355
  }
1325
1356
  return assetId;
1326
1357
  }
1358
+ function explicitWorkspaceRoot(database, project, assetId) {
1359
+ const row = database.prepare(`
1360
+ select root_asset_id from lineage_workspaces
1361
+ where project_id = ? and root_asset_id = ? and status != 'archived'
1362
+ `).get(project, assetId);
1363
+ return row?.root_asset_id;
1364
+ }
1365
+ function nearestWorkspaceRoot(database, project, assetId) {
1366
+ let current = assetId;
1367
+ const seen = /* @__PURE__ */ new Set();
1368
+ while (!seen.has(current)) {
1369
+ seen.add(current);
1370
+ const explicit = explicitWorkspaceRoot(database, project, current);
1371
+ if (explicit) return explicit;
1372
+ const parent = parentOf(database, project, current);
1373
+ if (!parent) return current;
1374
+ current = parent;
1375
+ }
1376
+ return assetId;
1377
+ }
1378
+ function assetChannel(database, project, assetId) {
1379
+ const row = database.prepare("select channel from assets where project_id = ? and id = ?").get(project, assetId);
1380
+ return row?.channel;
1381
+ }
1382
+ function lineageWriteClaimContext(database, project, assetId) {
1383
+ return {
1384
+ channel: assetChannel(database, project, assetId),
1385
+ rootAssetId: nearestWorkspaceRoot(database, project, assetId)
1386
+ };
1387
+ }
1388
+ function getLineageWriteClaimContext(project, assetId) {
1389
+ const database = lineageDb();
1390
+ try {
1391
+ requireAsset(database, project, assetId);
1392
+ return lineageWriteClaimContext(database, project, assetId);
1393
+ } finally {
1394
+ database.close();
1395
+ }
1396
+ }
1327
1397
  function latestSelectedRoot(database, project) {
1328
1398
  const row = database.prepare("select root_asset_id from asset_selections where project_id = ? order by selected_at desc limit 1").get(project);
1329
1399
  return row?.root_asset_id;
@@ -1354,6 +1424,20 @@ function linkLineageAssets(project, fields) {
1354
1424
  requireAsset(database, project, fields.parentAssetId);
1355
1425
  requireAsset(database, project, fields.childAssetId);
1356
1426
  if (fields.parentAssetId === fields.childAssetId) throw new LineageError("Lineage link cannot point to itself");
1427
+ const claimContext = lineageWriteClaimContext(database, project, fields.parentAssetId);
1428
+ try {
1429
+ requireLineageWorkspaceClaimForWrite({
1430
+ channel: claimContext.channel,
1431
+ claimToken: fields.claimToken,
1432
+ confirmWrite: fields.confirmWrite,
1433
+ project,
1434
+ rootAssetId: claimContext.rootAssetId,
1435
+ writeKind: "lineage_link"
1436
+ });
1437
+ } catch (error) {
1438
+ database.close();
1439
+ throw error;
1440
+ }
1357
1441
  const edge = {
1358
1442
  id: edgeId(project, fields.parentAssetId, fields.childAssetId),
1359
1443
  parent_asset_id: fields.parentAssetId,
@@ -1390,7 +1474,7 @@ function descendants(database, project, root) {
1390
1474
  function getLineageSnapshot(project, assetId) {
1391
1475
  const database = lineageDb();
1392
1476
  requireAsset(database, project, assetId);
1393
- const root = rootFor(database, project, assetId);
1477
+ const root = explicitWorkspaceRoot(database, project, assetId) || rootFor(database, project, assetId);
1394
1478
  const edges = descendants(database, project, root);
1395
1479
  const ids = [.../* @__PURE__ */ new Set([root, ...edges.flatMap((edge) => [edge.parent_asset_id, edge.child_asset_id])])];
1396
1480
  const placeholders = ids.map(() => "?").join(",");
@@ -1566,14 +1650,15 @@ function linkSelectedLineageChild(project, fields) {
1566
1650
  const next = getLineageNextAsset(project, fields.rootAssetId);
1567
1651
  if (!next.next_asset) throw new LineageError("Cannot link child until a next base is selected or unambiguous");
1568
1652
  if (fields.confirmWrite) {
1653
+ const claimContext = getLineageWriteClaimContext(project, next.next_asset.asset_id);
1569
1654
  const validation = validateAgentClaimForWrite({
1570
- channel: next.next_asset.channel,
1655
+ channel: claimContext.channel,
1571
1656
  claimToken: fields.claimToken,
1572
1657
  confirmWrite: fields.confirmWrite,
1573
1658
  dangerLevel: "enforce",
1574
1659
  project,
1575
1660
  scopeType: "lineage_workspace",
1576
- targetId: lineageWorkspaceId(project, next.root_asset_id),
1661
+ targetId: lineageWorkspaceId(project, claimContext.rootAssetId),
1577
1662
  writeKind: "link_child"
1578
1663
  });
1579
1664
  if (!validation.ok) throw new AgentClaimError(validation.message, validation.code === "claim_required" ? 401 : 403, validation.code, validation.conflicts);
@@ -1649,6 +1734,7 @@ Usage:
1649
1734
  ${config.binName} inspect --asset-id <asset-id> [--project <project>] [--db <path>] [--json]
1650
1735
  ${config.binName} link-child --root <asset-id> --child <asset-id> [--project <project>] [--claim-token <claim-id.secret>] [--confirm-write] [--db <path>] [--json]
1651
1736
  ${config.binName} agent claim --project <project> --scope <scope> --target <target-id> --agent-name <name> [--channel <channel>] [--ttl 20m] [--json]
1737
+ ${config.binName} agent graph --root <asset-id> [--project <project>] [--db <path>] [--json]
1652
1738
  ${config.binName} agent status [--project <project>] [--json]
1653
1739
  ${config.binName} agent inspect --claim <claim-id> [--project <project>] [--json]
1654
1740
  ${config.binName} agent heartbeat --claim-token <claim-id.secret> [--json]
@@ -1765,6 +1851,11 @@ function runLineageAgentCommand(command, args) {
1765
1851
  });
1766
1852
  }
1767
1853
  if (command === "status") return listAgentClaims(project);
1854
+ if (command === "graph") {
1855
+ const rootAssetId = readOption(args, "--root") || readOption(args, "--asset-id") || positionalArgs(args)[0];
1856
+ if (!rootAssetId) throw new Error("lineage agent graph requires --root");
1857
+ return getLineageSnapshot(project, rootAssetId);
1858
+ }
1768
1859
  if (command === "inspect") {
1769
1860
  if (!claimId) throw new Error("lineage agent inspect requires --claim");
1770
1861
  return inspectAgentClaim(claimId, project);
@@ -1821,8 +1912,37 @@ function printAgentResult(command, result, json) {
1821
1912
  console.log(`${inspected.claim?.id || "claim"} ${inspected.claim?.status || "unknown"} ${inspected.claim?.target_id || ""}`.trim());
1822
1913
  return;
1823
1914
  }
1915
+ if (command === "graph" && result && typeof result === "object" && "nodes" in result) {
1916
+ console.log(formatAgentGraphDigest(result));
1917
+ return;
1918
+ }
1824
1919
  console.log(String(result));
1825
1920
  }
1921
+ function formatAgentGraphDigest(snapshot) {
1922
+ const lines = [];
1923
+ const titleFor = (assetId) => {
1924
+ const node = snapshot.nodes.find((item) => item.asset_id === assetId);
1925
+ return node?.title ? `${node.title} (${assetId})` : assetId;
1926
+ };
1927
+ const root = snapshot.nodes.find((node) => node.asset_id === snapshot.root_asset_id);
1928
+ lines.push(`Lineage graph: ${root?.title || snapshot.root_asset_id}`);
1929
+ lines.push(`Root: ${snapshot.root_asset_id}`);
1930
+ lines.push(`Active: ${titleFor(snapshot.active_asset_id)}`);
1931
+ lines.push(`Nodes: ${snapshot.nodes.length} Edges: ${snapshot.edges.length}`);
1932
+ const selected = snapshot.selected || [];
1933
+ if (selected.length > 0) {
1934
+ lines.push("Next variation:");
1935
+ for (const assetId of selected) lines.push(`- ${titleFor(assetId)}`);
1936
+ }
1937
+ const latest = snapshot.latest || snapshot.nodes.filter((node) => node.is_latest).map((node) => node.asset_id);
1938
+ if (latest.length > 0) {
1939
+ lines.push("Latest leaves:");
1940
+ for (const assetId of latest) lines.push(`- ${titleFor(assetId)}`);
1941
+ }
1942
+ lines.push("Edges:");
1943
+ for (const edge of snapshot.edges) lines.push(`- ${titleFor(edge.parent_asset_id)} -> ${titleFor(edge.child_asset_id)}`);
1944
+ return lines.join("\n");
1945
+ }
1826
1946
  function start(config, args) {
1827
1947
  let options;
1828
1948
  const json = args.includes("--json");