@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.
- package/CHANGELOG.md +7 -0
- package/README.md +8 -1
- package/dist/cli/lineage-dev.js +123 -3
- package/dist/cli/lineage-dev.js.map +4 -4
- package/dist/cli/lineage.js +123 -3
- package/dist/cli/lineage.js.map +4 -4
- package/dist/server.js +782 -654
- package/dist/server.js.map +4 -4
- package/dist/web/assets/index-C54JeMzP.js +22 -0
- package/dist/web/assets/index-DkLQJqxa.css +1 -0
- package/dist/web/index.html +2 -2
- package/package.json +2 -1
- package/dist/web/assets/index-DI7dn5M6.css +0 -1
- package/dist/web/assets/index-dJy71x2a.js +0 -21
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,12 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.1.5
|
|
4
|
+
|
|
5
|
+
- Add graph orientation controls for lineage views and CLI flows, including browser coverage for orientation behavior.
|
|
6
|
+
- Improve agent claim visibility with workspace/content occupancy badges, claim lifecycle controls, and release claim smoke coverage.
|
|
7
|
+
- Harden lineage claim enforcement for explicit child workspaces and `project_channel` claims so scoped claims cannot authorize broader writes.
|
|
8
|
+
- Make local startup helpers durable with tmux-backed Makefile commands.
|
|
9
|
+
|
|
3
10
|
## 0.1.4
|
|
4
11
|
|
|
5
12
|
- Implement target-scoped agent claims for lineage and content-post agent writes, including claim lifecycle CLI/API commands, heartbeat/release/revoke/transfer controls, and token-redacted read APIs.
|
package/README.md
CHANGED
|
@@ -54,6 +54,12 @@ lineage agent claim --project demo-project --scope lineage_workspace --target de
|
|
|
54
54
|
export LINEAGE_CLAIM_TOKEN='claim_abc.secret_xyz'
|
|
55
55
|
```
|
|
56
56
|
|
|
57
|
+
Agents can inspect the current lineage graph without a claim:
|
|
58
|
+
|
|
59
|
+
```bash
|
|
60
|
+
lineage agent graph --project demo-project --root <root-asset-id> --json
|
|
61
|
+
```
|
|
62
|
+
|
|
57
63
|
Keep the claim fresh and pass it to mutating commands:
|
|
58
64
|
|
|
59
65
|
```bash
|
|
@@ -127,12 +133,13 @@ For local release validation:
|
|
|
127
133
|
|
|
128
134
|
```bash
|
|
129
135
|
npm run release:dry-run -- --tag next
|
|
136
|
+
npm run release:claim-smoke -- --package @mean-weasel/lineage@next
|
|
130
137
|
npm run release:next
|
|
131
138
|
npm run release:dry-run -- --tag latest
|
|
132
139
|
npm run release:latest
|
|
133
140
|
```
|
|
134
141
|
|
|
135
|
-
The release script verifies package metadata, changelog version coverage, public-readiness scans, install smoke, browser smoke, audit, and package contents before publishing. GitHub Actions runs CI on pull requests and `main`; publishing is manual through the Release workflow.
|
|
142
|
+
The release script verifies package metadata, changelog version coverage, public-readiness scans, install smoke, browser smoke, audit, and package contents before publishing. Promotion also installs the candidate package and runs a claim lifecycle smoke that creates a target claim, proves missing-token writes fail, proves matching-token writes succeed, verifies read surfaces do not expose the raw token, and proves release invalidates the token. GitHub Actions runs CI on pull requests and `main`; publishing is manual through the Release workflow.
|
|
136
143
|
|
|
137
144
|
Use the Release workflow operations this way:
|
|
138
145
|
|
package/dist/cli/lineage-dev.js
CHANGED
|
@@ -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:
|
|
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,
|
|
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");
|