@mean-weasel/lineage 0.1.23 → 0.1.24

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.
Files changed (53) hide show
  1. package/CHANGELOG.md +5 -0
  2. package/README.md +37 -0
  3. package/dist/cli/lineage-dev.js +605 -36
  4. package/dist/cli/lineage-dev.js.map +4 -4
  5. package/dist/cli/lineage-preview.js +605 -36
  6. package/dist/cli/lineage-preview.js.map +4 -4
  7. package/dist/cli/lineage.js +605 -36
  8. package/dist/cli/lineage.js.map +4 -4
  9. package/dist/docs-site/operations/channels/index.html +11 -2
  10. package/dist/docs-site/operations/profiles-database-identity/index.html +16 -2
  11. package/dist/docs-site/pagefind/fragment/en_761fab1.pf_fragment +0 -0
  12. package/dist/docs-site/pagefind/fragment/en_bd3e194.pf_fragment +0 -0
  13. package/dist/docs-site/pagefind/fragment/en_ee82be6.pf_fragment +0 -0
  14. package/dist/docs-site/pagefind/fragment/en_f4db9e2.pf_fragment +0 -0
  15. package/dist/docs-site/pagefind/index/en_7b1363c.pf_index +0 -0
  16. package/dist/docs-site/pagefind/pagefind-entry.json +1 -1
  17. package/dist/docs-site/pagefind/pagefind.en_4e77743529.pf_meta +0 -0
  18. package/dist/docs-site/reference/cli/index.html +11 -2
  19. package/dist/docs-site/start-here/installation-first-run/index.html +7 -1
  20. package/dist/pages/assets/{app-B-fAyNsU.css → app-CuBsKrS-.css} +1 -1
  21. package/dist/pages/assets/app-TEMX-pOn.js +50 -0
  22. package/dist/pages/docs/operations/channels/index.html +11 -2
  23. package/dist/pages/docs/operations/profiles-database-identity/index.html +16 -2
  24. package/dist/pages/docs/pagefind/fragment/en_761fab1.pf_fragment +0 -0
  25. package/dist/pages/docs/pagefind/fragment/en_bd3e194.pf_fragment +0 -0
  26. package/dist/pages/docs/pagefind/fragment/en_ee82be6.pf_fragment +0 -0
  27. package/dist/pages/docs/pagefind/fragment/en_f4db9e2.pf_fragment +0 -0
  28. package/dist/pages/docs/pagefind/index/en_7b1363c.pf_index +0 -0
  29. package/dist/pages/docs/pagefind/pagefind-entry.json +1 -1
  30. package/dist/pages/docs/pagefind/pagefind.en_4e77743529.pf_meta +0 -0
  31. package/dist/pages/docs/reference/cli/index.html +11 -2
  32. package/dist/pages/docs/start-here/installation-first-run/index.html +7 -1
  33. package/dist/runtime-build.json +4 -4
  34. package/dist/server.js +375 -39
  35. package/dist/server.js.map +4 -4
  36. package/dist/web/assets/{app-B-fAyNsU.css → app-CuBsKrS-.css} +1 -1
  37. package/dist/web/assets/app-TEMX-pOn.js +50 -0
  38. package/dist/web/index.html +2 -2
  39. package/package.json +3 -2
  40. package/dist/docs-site/pagefind/fragment/en_4db923b.pf_fragment +0 -0
  41. package/dist/docs-site/pagefind/fragment/en_581a7cb.pf_fragment +0 -0
  42. package/dist/docs-site/pagefind/fragment/en_8c1c735.pf_fragment +0 -0
  43. package/dist/docs-site/pagefind/fragment/en_bcc29c5.pf_fragment +0 -0
  44. package/dist/docs-site/pagefind/index/en_f279b04.pf_index +0 -0
  45. package/dist/docs-site/pagefind/pagefind.en_74951c3ca7.pf_meta +0 -0
  46. package/dist/pages/assets/app-DdU93vp2.js +0 -50
  47. package/dist/pages/docs/pagefind/fragment/en_4db923b.pf_fragment +0 -0
  48. package/dist/pages/docs/pagefind/fragment/en_581a7cb.pf_fragment +0 -0
  49. package/dist/pages/docs/pagefind/fragment/en_8c1c735.pf_fragment +0 -0
  50. package/dist/pages/docs/pagefind/fragment/en_bcc29c5.pf_fragment +0 -0
  51. package/dist/pages/docs/pagefind/index/en_f279b04.pf_index +0 -0
  52. package/dist/pages/docs/pagefind/pagefind.en_74951c3ca7.pf_meta +0 -0
  53. package/dist/web/assets/app-DdU93vp2.js +0 -50
@@ -1,10 +1,10 @@
1
1
  #!/usr/bin/env node
2
2
 
3
3
  // src/cli/lineageCli.ts
4
- import { existsSync as existsSync11, mkdirSync as mkdirSync6, readFileSync as readFileSync6, writeFileSync as writeFileSync4 } from "node:fs";
4
+ import { existsSync as existsSync12, mkdirSync as mkdirSync6, readFileSync as readFileSync6, writeFileSync as writeFileSync4 } from "node:fs";
5
5
  import { randomUUID as randomUUID4 } from "node:crypto";
6
6
  import { homedir as homedir2, platform as platform2 } from "node:os";
7
- import { dirname as dirname6, join as join10, resolve as resolve9 } from "node:path";
7
+ import { dirname as dirname6, join as join10, resolve as resolve10 } from "node:path";
8
8
  import { spawn } from "node:child_process";
9
9
  import { fileURLToPath as fileURLToPath3 } from "node:url";
10
10
 
@@ -648,6 +648,7 @@ var lineageProfileDoctorSchemaVersion = "lineage.profile_doctor.v1";
648
648
  var lineageProfileCloneReceiptSchemaVersion = "lineage.profile_clone_receipt.v1";
649
649
  var lineageProfileAssetsCloneReceiptSchemaVersion = "lineage.profile_assets_clone_receipt.v1";
650
650
  var lineageProfileRuntimeRepinReceiptSchemaVersion = "lineage.profile_runtime_repin_receipt.v1";
651
+ var lineageProfileRuntimeUpgradeReceiptSchemaVersion = "lineage.profile_runtime_upgrade_receipt.v1";
651
652
 
652
653
  // src/server/nodeSqlite.ts
653
654
  import { createRequire } from "node:module";
@@ -848,7 +849,9 @@ function initializeLineageProfile(profileId, serviceOrigin, runtime, confirmWrit
848
849
  expected_runtime: {
849
850
  channel: runtime.channel,
850
851
  code_fingerprint: runtime.code.fingerprint.toLowerCase(),
851
- code_origin: runtime.code.origin
852
+ code_origin: runtime.code.origin,
853
+ ...runtime.gitSha ? { git_sha: runtime.gitSha } : {},
854
+ version: runtime.version
852
855
  },
853
856
  profile_id: profileId,
854
857
  schema_version: lineageProfileSchemaVersion,
@@ -914,7 +917,9 @@ function initializeLineageProfile(profileId, serviceOrigin, runtime, confirmWrit
914
917
  runtime: {
915
918
  channel: runtime.channel,
916
919
  code_fingerprint: runtime.code.fingerprint,
917
- code_origin: runtime.code.origin
920
+ code_origin: runtime.code.origin,
921
+ ...runtime.gitSha ? { git_sha: runtime.gitSha } : {},
922
+ version: runtime.version
918
923
  },
919
924
  schema_version: lineageProfileInitSchemaVersion,
920
925
  service_origin: profile.service_origin
@@ -950,6 +955,229 @@ function runtimeRepinInvariant(profile) {
950
955
  function sameFileIdentity(left, right) {
951
956
  return left.dev === right.dev && left.ino === right.ino && left.size === right.size && left.mtimeMs === right.mtimeMs && left.ctimeMs === right.ctimeMs;
952
957
  }
958
+ function fsyncDirectory(path) {
959
+ const directoryFd = openSync(path, "r");
960
+ try {
961
+ fsyncSync(directoryFd);
962
+ } finally {
963
+ closeSync(directoryFd);
964
+ }
965
+ }
966
+ function parseStableVersion(value, label) {
967
+ const match = /^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(?:\+[0-9A-Za-z-]+(?:\.[0-9A-Za-z-]+)*)?$/.exec(value);
968
+ if (!match) throw new Error(`${label} must be a valid stable SemVer without a prerelease, found ${value}`);
969
+ return match.slice(1).map(BigInt);
970
+ }
971
+ function compareStableVersions(left, right) {
972
+ for (let index = 0; index < left.length; index += 1) {
973
+ if (left[index] !== right[index]) return left[index] < right[index] ? -1 : 1;
974
+ }
975
+ return 0;
976
+ }
977
+ function profileUpgradePreflight(profile, runtime) {
978
+ const doctor = doctorLineageProfile(profile.manifest_path, runtime);
979
+ const allowedRuntimeMismatches = /* @__PURE__ */ new Set(["runtime_code", "runtime_version", "runtime_git_sha"]);
980
+ const failures = doctor.checks.filter((check) => check.status === "fail" && !allowedRuntimeMismatches.has(check.id));
981
+ if (failures.length > 0) {
982
+ throw new Error(`Stable profile runtime upgrade requires a healthy production profile before changing its pin: ${failures.map((check) => `${check.id}: ${check.message}`).join("; ")}`);
983
+ }
984
+ }
985
+ function upgradeRuntimeIdentity(fingerprint, options = {}) {
986
+ return {
987
+ channel: "stable",
988
+ code_fingerprint: fingerprint,
989
+ code_origin: "package",
990
+ ...options.codeRoot ? { code_root: options.codeRoot } : {},
991
+ ...options.gitSha ? { git_sha: options.gitSha } : {},
992
+ ...options.version ? { version: options.version } : {}
993
+ };
994
+ }
995
+ function upgradeLineageStableProfileRuntime(selector, runtime, confirmWrite, testHooks = {}) {
996
+ if (!confirmWrite) throw new Error("Stable profile runtime upgrade requires --confirm-write");
997
+ if (runtime.channel !== "stable") throw new Error(`Stable profile runtime upgrade requires stable code, not ${runtime.channel}`);
998
+ if (!runtime.code?.verified) throw new Error("Stable profile runtime upgrade requires a verified package runtime");
999
+ if (runtime.code.channel !== "stable") throw new Error(`Stable profile runtime upgrade requires stable code identity, not ${runtime.code.channel}`);
1000
+ if (runtime.code.origin !== "package") throw new Error(`Stable profile runtime upgrade requires package code, not ${runtime.code.origin}`);
1001
+ if (!runtime.code.fingerprint || !/^[a-f0-9]{64}$/i.test(runtime.code.fingerprint)) {
1002
+ throw new Error("Stable profile runtime upgrade requires a valid executing code fingerprint");
1003
+ }
1004
+ if (runtime.code.package_version !== runtime.version) {
1005
+ throw new Error(`Stable profile runtime upgrade version ${runtime.version} does not match package version ${runtime.code.package_version || "missing"}`);
1006
+ }
1007
+ const targetVersion = parseStableVersion(runtime.version, "Stable profile runtime upgrade target version");
1008
+ const executingRoot = realpathSync(runtime.code.root);
1009
+ if (!statSync3(executingRoot).isDirectory()) {
1010
+ throw new Error(`Stable profile runtime upgrade code root is not a directory: ${executingRoot}`);
1011
+ }
1012
+ const profile = resolveLineageProfile(selector);
1013
+ if (profile.environment !== "production") {
1014
+ throw new Error(`Stable profile runtime upgrade requires a production profile, not ${profile.environment}`);
1015
+ }
1016
+ if (profile.expected_runtime?.channel !== "stable") {
1017
+ throw new Error("Stable profile runtime upgrade requires an existing stable channel pin");
1018
+ }
1019
+ if (profile.expected_runtime.code_origin !== "package") {
1020
+ throw new Error("Stable profile runtime upgrade requires an existing package origin pin");
1021
+ }
1022
+ const previousFingerprint = profile.expected_runtime.code_fingerprint;
1023
+ if (!previousFingerprint) throw new Error("Stable profile runtime upgrade requires an existing code fingerprint pin");
1024
+ const previousVersion = profile.expected_runtime.version;
1025
+ if (previousVersion) {
1026
+ const comparison = compareStableVersions(targetVersion, parseStableVersion(previousVersion, "Stable profile runtime upgrade source version"));
1027
+ if (previousFingerprint === runtime.code.fingerprint && previousVersion !== runtime.version) {
1028
+ throw new Error(`Stable profile runtime upgrade found one code fingerprint claiming conflicting versions ${previousVersion} and ${runtime.version}`);
1029
+ }
1030
+ if (comparison === 0 && previousFingerprint !== runtime.code.fingerprint) {
1031
+ throw new Error(`Stable profile runtime upgrade refuses same-version fingerprint change for ${runtime.version}`);
1032
+ }
1033
+ if (comparison < 0) {
1034
+ throw new Error(`Stable profile runtime upgrade refuses downgrade from ${previousVersion} to ${runtime.version}`);
1035
+ }
1036
+ if (comparison > 0 && previousFingerprint === runtime.code.fingerprint) {
1037
+ throw new Error(`Stable profile runtime upgrade requires a new code fingerprint for version ${runtime.version}`);
1038
+ }
1039
+ }
1040
+ if (previousFingerprint === runtime.code.fingerprint && profile.expected_runtime.git_sha && runtime.gitSha && profile.expected_runtime.git_sha !== runtime.gitSha) {
1041
+ throw new Error("Stable profile runtime upgrade found one code fingerprint claiming conflicting Git revisions");
1042
+ }
1043
+ profileUpgradePreflight(profile, runtime);
1044
+ const manifestPath = profile.manifest_path;
1045
+ const profileDirectory = dirname3(manifestPath);
1046
+ assertOwnerOnlyPath(profileDirectory, "profile directory");
1047
+ const beforeStats = assertOwnerOnlyPath(manifestPath, "manifest");
1048
+ const beforeText = readFileSync3(manifestPath, "utf8");
1049
+ const beforeHash = sha256(beforeText);
1050
+ let raw;
1051
+ try {
1052
+ raw = JSON.parse(beforeText);
1053
+ } catch (error) {
1054
+ throw new Error(`Could not parse profile manifest ${manifestPath}: ${error instanceof Error ? error.message : String(error)}`, { cause: error });
1055
+ }
1056
+ if (!raw || typeof raw !== "object" || Array.isArray(raw)) throw new Error("Profile manifest must be a JSON object");
1057
+ const nextExpectedRuntime = {
1058
+ channel: "stable",
1059
+ code_fingerprint: runtime.code.fingerprint.toLowerCase(),
1060
+ code_origin: "package",
1061
+ ...runtime.gitSha ? { git_sha: runtime.gitSha } : {},
1062
+ version: runtime.version
1063
+ };
1064
+ const afterText = `${JSON.stringify({ ...raw, expected_runtime: nextExpectedRuntime }, null, 2)}
1065
+ `;
1066
+ const afterHash = sha256(afterText);
1067
+ const normalizedTargetMatches = previousFingerprint === nextExpectedRuntime.code_fingerprint && previousVersion === nextExpectedRuntime.version && profile.expected_runtime.git_sha === nextExpectedRuntime.git_sha;
1068
+ const newRuntime = upgradeRuntimeIdentity(runtime.code.fingerprint, {
1069
+ codeRoot: executingRoot,
1070
+ gitSha: runtime.gitSha,
1071
+ version: runtime.version
1072
+ });
1073
+ const result = {
1074
+ asset_root: profile.asset_root,
1075
+ changed: !normalizedTargetMatches,
1076
+ database_path: profile.database_path,
1077
+ manifest_after_sha256: normalizedTargetMatches ? beforeHash : afterHash,
1078
+ manifest_before_sha256: beforeHash,
1079
+ manifest_path: manifestPath,
1080
+ new_runtime: newRuntime,
1081
+ post_upgrade_doctor_ok: true,
1082
+ previous_runtime: upgradeRuntimeIdentity(previousFingerprint, {
1083
+ gitSha: profile.expected_runtime.git_sha,
1084
+ version: previousVersion
1085
+ }),
1086
+ profile_fingerprint: profile.profile_fingerprint,
1087
+ profile_id: profile.profile_id,
1088
+ schema_version: lineageProfileRuntimeUpgradeReceiptSchemaVersion,
1089
+ service_origin: profile.service_origin,
1090
+ upgrade_kind: normalizedTargetMatches ? "idempotent" : previousVersion ? "versioned" : "legacy_unversioned"
1091
+ };
1092
+ if (!result.changed) {
1093
+ const doctor = doctorLineageProfile(manifestPath, runtime);
1094
+ if (!doctor.ok) throw new Error("Stable profile runtime upgrade idempotency check did not pass profile doctor");
1095
+ return result;
1096
+ }
1097
+ const operationId = randomUUID();
1098
+ const temporaryPath = join4(profileDirectory, `.profile.runtime-upgrade-${operationId}.tmp`);
1099
+ const rollbackPath = join4(profileDirectory, `.profile.runtime-upgrade-${operationId}.rollback`);
1100
+ let temporaryExists = false;
1101
+ let rollbackExists = false;
1102
+ let published = false;
1103
+ let preserveRollback = false;
1104
+ try {
1105
+ writeFileSync2(temporaryPath, afterText, { encoding: "utf8", flag: "wx", mode: 384 });
1106
+ temporaryExists = true;
1107
+ chmodSync2(temporaryPath, 384);
1108
+ const temporaryFd = openSync(temporaryPath, "r");
1109
+ try {
1110
+ fsyncSync(temporaryFd);
1111
+ } finally {
1112
+ closeSync(temporaryFd);
1113
+ }
1114
+ const preparedReplacement = resolveLineageProfile(temporaryPath);
1115
+ if (runtimeRepinInvariant(preparedReplacement) !== runtimeRepinInvariant(profile)) {
1116
+ throw new Error("Stable profile runtime upgrade would change immutable profile routing identity");
1117
+ }
1118
+ testHooks.beforePublish?.();
1119
+ const currentStats = assertOwnerOnlyPath(manifestPath, "manifest");
1120
+ const currentText = readFileSync3(manifestPath, "utf8");
1121
+ if (!sameFileIdentity(beforeStats, currentStats) || sha256(currentText) !== beforeHash) {
1122
+ throw new Error("Profile manifest changed while stable runtime upgrade was being prepared; refusing replacement");
1123
+ }
1124
+ linkSync(manifestPath, rollbackPath);
1125
+ rollbackExists = true;
1126
+ chmodSync2(rollbackPath, 384);
1127
+ const rollbackStats = assertOwnerOnlyPath(rollbackPath, "manifest");
1128
+ const linkedCurrentStats = assertOwnerOnlyPath(manifestPath, "manifest");
1129
+ if (!sameFileIdentity(rollbackStats, linkedCurrentStats) || readFileSync3(rollbackPath, "utf8") !== beforeText) {
1130
+ throw new Error("Stable profile runtime upgrade could not preserve an exact rollback preimage");
1131
+ }
1132
+ fsyncDirectory(profileDirectory);
1133
+ renameSync(temporaryPath, manifestPath);
1134
+ temporaryExists = false;
1135
+ published = true;
1136
+ fsyncDirectory(profileDirectory);
1137
+ testHooks.afterPublish?.();
1138
+ const replacement = resolveLineageProfile(manifestPath);
1139
+ if (runtimeRepinInvariant(replacement) !== runtimeRepinInvariant(profile)) {
1140
+ throw new Error("Stable profile runtime upgrade changed immutable profile routing identity");
1141
+ }
1142
+ if (readFileSync3(manifestPath, "utf8") !== afterText) {
1143
+ throw new Error("Stable profile runtime upgrade replacement bytes do not match the prepared manifest");
1144
+ }
1145
+ const doctor = doctorLineageProfile(manifestPath, runtime);
1146
+ if (!doctor.ok) {
1147
+ const failures = doctor.checks.filter((check) => check.status === "fail").map((check) => `${check.id}: ${check.message}`).join("; ");
1148
+ throw new Error(`Stable profile runtime upgrade failed post-upgrade profile doctor: ${failures}`);
1149
+ }
1150
+ rmSync(rollbackPath);
1151
+ rollbackExists = false;
1152
+ fsyncDirectory(profileDirectory);
1153
+ return result;
1154
+ } catch (error) {
1155
+ if (published && rollbackExists) {
1156
+ try {
1157
+ if (readFileSync3(manifestPath, "utf8") !== afterText) {
1158
+ throw new Error("published manifest no longer matches the prepared replacement", { cause: error });
1159
+ }
1160
+ renameSync(rollbackPath, manifestPath);
1161
+ rollbackExists = false;
1162
+ fsyncDirectory(profileDirectory);
1163
+ const restoredText = readFileSync3(manifestPath, "utf8");
1164
+ if (restoredText !== beforeText || sha256(restoredText) !== beforeHash) {
1165
+ throw new Error("restored manifest does not match the exact preimage", { cause: error });
1166
+ }
1167
+ } catch (rollbackError) {
1168
+ preserveRollback = true;
1169
+ throw new Error(
1170
+ `Stable profile runtime upgrade failed and automatic rollback could not be proven: ${rollbackError instanceof Error ? rollbackError.message : String(rollbackError)}`,
1171
+ { cause: rollbackError }
1172
+ );
1173
+ }
1174
+ }
1175
+ throw error;
1176
+ } finally {
1177
+ if (temporaryExists) rmSync(temporaryPath, { force: true });
1178
+ if (rollbackExists && !preserveRollback) rmSync(rollbackPath, { force: true });
1179
+ }
1180
+ }
953
1181
  function repinLineageDevelopmentProfileRuntime(selector, checkoutRoot, runtime, confirmWrite) {
954
1182
  if (!confirmWrite) throw new Error("Profile runtime repin requires --confirm-write");
955
1183
  if (runtime.channel !== "dev") throw new Error(`Profile runtime repin requires dev code, not ${runtime.channel}`);
@@ -1824,6 +2052,21 @@ function lineageDb() {
1824
2052
  where status = 'pending';
1825
2053
  create index if not exists asset_reroll_requests_root_status
1826
2054
  on asset_reroll_requests(project_id, root_asset_id, status, created_at);
2055
+ create table if not exists asset_social_marks (
2056
+ id text primary key,
2057
+ project_id text not null references projects(id),
2058
+ root_asset_id text not null references assets(id),
2059
+ asset_id text not null references assets(id),
2060
+ notes text,
2061
+ marked_by text not null,
2062
+ marked_at text not null,
2063
+ unmarked_by text,
2064
+ unmarked_at text,
2065
+ updated_at text not null,
2066
+ unique(project_id, root_asset_id, asset_id)
2067
+ );
2068
+ create index if not exists asset_social_marks_root_active
2069
+ on asset_social_marks(project_id, root_asset_id, unmarked_at, marked_at);
1827
2070
  create table if not exists asset_reviews (
1828
2071
  asset_id text primary key references assets(id),
1829
2072
  review_state text not null check (review_state in ('unreviewed', 'approved', 'needs_revision', 'rejected', 'ignored')),
@@ -3915,6 +4158,19 @@ function getLineageSnapshot(project, assetId) {
3915
4158
  const request = rerollRequestFrom(row);
3916
4159
  return [request.node_asset_id, withRerollTask(request, lineageTasksByNode.get(request.node_asset_id)?.reroll)];
3917
4160
  }));
4161
+ const hasSocialMarks = database.prepare("select 1 from sqlite_master where type = 'table' and name = 'asset_social_marks'").get() !== void 0;
4162
+ const socialMarkRows = ids.length > 0 && hasSocialMarks ? database.prepare(`select * from asset_social_marks where project_id = ? and root_asset_id = ? and unmarked_at is null and asset_id in (${placeholders})`).all(project, root, ...ids) : [];
4163
+ const socialMarksByNode = new Map(socialMarkRows.map((row) => [String(row.asset_id), {
4164
+ active: true,
4165
+ asset_id: String(row.asset_id),
4166
+ id: String(row.id),
4167
+ marked_at: String(row.marked_at),
4168
+ marked_by: String(row.marked_by),
4169
+ notes: rowString(row.notes),
4170
+ project_id: String(row.project_id),
4171
+ root_asset_id: String(row.root_asset_id),
4172
+ updated_at: String(row.updated_at)
4173
+ }]));
3918
4174
  const childIds = new Set(edges.map((edge) => edge.parent_asset_id));
3919
4175
  const selectedIds = new Set(selected.map((row) => row.asset_id));
3920
4176
  const selections = selected.map((row) => ({
@@ -3943,6 +4199,7 @@ function getLineageSnapshot(project, assetId) {
3943
4199
  preview_url: canPreviewLocally(row.media_type, previewPath) ? localPreviewUrl(project, previewPath) : void 0,
3944
4200
  reroll_request: rerollsByNode.get(row.asset_id),
3945
4201
  selection_note: nodeSelection?.notes,
4202
+ social_mark: socialMarksByNode.get(row.asset_id),
3946
4203
  user_selected: selectedIds.has(row.asset_id)
3947
4204
  };
3948
4205
  });
@@ -4365,9 +4622,246 @@ function linkSelectedLineageChild(project, fields) {
4365
4622
  };
4366
4623
  }
4367
4624
 
4625
+ // src/server/assetSocialMarks.ts
4626
+ import { accessSync, constants, existsSync as existsSync8 } from "node:fs";
4627
+ import { isAbsolute as isAbsolute3, resolve as resolve6 } from "node:path";
4628
+ function markFromRow(row) {
4629
+ return {
4630
+ active: row.unmarked_at === null,
4631
+ asset_id: row.asset_id,
4632
+ id: row.id,
4633
+ marked_at: row.marked_at,
4634
+ marked_by: row.marked_by,
4635
+ notes: row.notes || void 0,
4636
+ project_id: row.project_id,
4637
+ root_asset_id: row.root_asset_id,
4638
+ unmarked_at: row.unmarked_at || void 0,
4639
+ unmarked_by: row.unmarked_by || void 0,
4640
+ updated_at: row.updated_at
4641
+ };
4642
+ }
4643
+ function requireCanonicalSnapshot(project, rootAssetId) {
4644
+ const snapshot = getLineageSnapshot(project, rootAssetId);
4645
+ if (snapshot.root_asset_id !== rootAssetId) {
4646
+ throw new LineageError(`Asset ${rootAssetId} is not a canonical lineage canvas root`, 400);
4647
+ }
4648
+ return snapshot;
4649
+ }
4650
+ function resolveVisibleNode(snapshot, asset) {
4651
+ const exactId = snapshot.nodes.find((node) => node.asset_id === asset);
4652
+ if (exactId) return exactId;
4653
+ const title = asset.trim().toLocaleLowerCase();
4654
+ const matches = snapshot.nodes.filter((node) => node.title.trim().toLocaleLowerCase() === title);
4655
+ if (matches.length > 1) throw new LineageError(`Asset title "${asset}" matches multiple visible nodes; use an exact asset ID.`, 409);
4656
+ if (matches.length === 1) return matches[0];
4657
+ throw new LineageError(`Asset ${asset} is not visible in lineage canvas ${snapshot.root_asset_id}`, 404);
4658
+ }
4659
+ function rootChannel(snapshot) {
4660
+ return snapshot.nodes.find((node) => node.asset_id === snapshot.root_asset_id)?.channel;
4661
+ }
4662
+ function requireActor(actor, field) {
4663
+ const normalized = actor.trim();
4664
+ if (!normalized) throw new LineageError(`Social mark ${field} is required`);
4665
+ return normalized;
4666
+ }
4667
+ function socialMarkId(project, rootAssetId, assetId) {
4668
+ return `${project}:${rootAssetId}:social:${assetId}`;
4669
+ }
4670
+ function activeRow(project, rootAssetId, assetId) {
4671
+ const database = lineageDb();
4672
+ try {
4673
+ return database.prepare(`
4674
+ select * from asset_social_marks
4675
+ where project_id = ? and root_asset_id = ? and asset_id = ? and unmarked_at is null
4676
+ `).get(project, rootAssetId, assetId);
4677
+ } finally {
4678
+ database.close();
4679
+ }
4680
+ }
4681
+ function mutationResponse(project, rootAssetId, active, mark, dryRun) {
4682
+ return {
4683
+ active,
4684
+ ...dryRun ? { dryRun } : {},
4685
+ ...mark ? { mark } : {},
4686
+ ok: true,
4687
+ snapshot: getLineageSnapshot(project, rootAssetId)
4688
+ };
4689
+ }
4690
+ function markAssetSocial(project, fields) {
4691
+ const snapshot = requireCanonicalSnapshot(project, fields.rootAssetId);
4692
+ const node = resolveVisibleNode(snapshot, fields.asset);
4693
+ const markedBy = requireActor(fields.markedBy, "markedBy");
4694
+ requireLineageWorkspaceClaimForWrite({
4695
+ channel: rootChannel(snapshot),
4696
+ claimToken: fields.claimToken,
4697
+ confirmWrite: fields.confirmWrite,
4698
+ project,
4699
+ rootAssetId: fields.rootAssetId,
4700
+ writeKind: "social_mark"
4701
+ });
4702
+ const existing = activeRow(project, fields.rootAssetId, node.asset_id);
4703
+ if (existing) return mutationResponse(project, fields.rootAssetId, true, markFromRow(existing), fields.confirmWrite ? void 0 : true);
4704
+ const timestamp = nowIso();
4705
+ const preview = {
4706
+ active: true,
4707
+ asset_id: node.asset_id,
4708
+ id: socialMarkId(project, fields.rootAssetId, node.asset_id),
4709
+ marked_at: timestamp,
4710
+ marked_by: markedBy,
4711
+ notes: fields.notes?.trim() || void 0,
4712
+ project_id: project,
4713
+ root_asset_id: fields.rootAssetId,
4714
+ updated_at: timestamp
4715
+ };
4716
+ if (!fields.confirmWrite) return mutationResponse(project, fields.rootAssetId, true, preview, true);
4717
+ const database = lineageDb();
4718
+ try {
4719
+ database.prepare(`
4720
+ insert into asset_social_marks (
4721
+ id, project_id, root_asset_id, asset_id, notes, marked_by, marked_at,
4722
+ unmarked_by, unmarked_at, updated_at
4723
+ ) values (?, ?, ?, ?, ?, ?, ?, null, null, ?)
4724
+ on conflict(project_id, root_asset_id, asset_id) do update set
4725
+ notes = excluded.notes,
4726
+ marked_by = excluded.marked_by,
4727
+ marked_at = excluded.marked_at,
4728
+ unmarked_by = null,
4729
+ unmarked_at = null,
4730
+ updated_at = excluded.updated_at
4731
+ `).run(
4732
+ preview.id,
4733
+ project,
4734
+ fields.rootAssetId,
4735
+ node.asset_id,
4736
+ preview.notes || null,
4737
+ markedBy,
4738
+ timestamp,
4739
+ timestamp
4740
+ );
4741
+ } finally {
4742
+ database.close();
4743
+ }
4744
+ return mutationResponse(project, fields.rootAssetId, true, markFromRow(activeRow(project, fields.rootAssetId, node.asset_id)));
4745
+ }
4746
+ function unmarkAssetSocial(project, fields) {
4747
+ const snapshot = requireCanonicalSnapshot(project, fields.rootAssetId);
4748
+ const node = resolveVisibleNode(snapshot, fields.asset);
4749
+ const unmarkedBy = requireActor(fields.unmarkedBy, "unmarkedBy");
4750
+ requireLineageWorkspaceClaimForWrite({
4751
+ channel: rootChannel(snapshot),
4752
+ claimToken: fields.claimToken,
4753
+ confirmWrite: fields.confirmWrite,
4754
+ project,
4755
+ rootAssetId: fields.rootAssetId,
4756
+ writeKind: "social_unmark"
4757
+ });
4758
+ const existing = activeRow(project, fields.rootAssetId, node.asset_id);
4759
+ if (!existing) return mutationResponse(project, fields.rootAssetId, false, void 0, fields.confirmWrite ? void 0 : true);
4760
+ const timestamp = nowIso();
4761
+ const preview = markFromRow({
4762
+ ...existing,
4763
+ unmarked_at: timestamp,
4764
+ unmarked_by: unmarkedBy,
4765
+ updated_at: timestamp
4766
+ });
4767
+ if (!fields.confirmWrite) return mutationResponse(project, fields.rootAssetId, false, preview, true);
4768
+ const database = lineageDb();
4769
+ try {
4770
+ database.prepare(`
4771
+ update asset_social_marks
4772
+ set unmarked_by = ?, unmarked_at = ?, updated_at = ?
4773
+ where project_id = ? and root_asset_id = ? and asset_id = ? and unmarked_at is null
4774
+ `).run(unmarkedBy, timestamp, timestamp, project, fields.rootAssetId, node.asset_id);
4775
+ } finally {
4776
+ database.close();
4777
+ }
4778
+ return mutationResponse(project, fields.rootAssetId, false, preview);
4779
+ }
4780
+ function absoluteLocalPath2(reference) {
4781
+ if (!reference) return void 0;
4782
+ if (isAbsolute3(reference)) return reference;
4783
+ const repoRelative = resolve6(repoRoot, reference);
4784
+ return existsSync8(repoRelative) || reference === ".asset-scratch" || reference.startsWith(".asset-scratch/") ? repoRelative : resolve6(repoRoot, ".asset-scratch", reference);
4785
+ }
4786
+ function isReadable(path) {
4787
+ try {
4788
+ accessSync(path, constants.R_OK);
4789
+ return true;
4790
+ } catch {
4791
+ return false;
4792
+ }
4793
+ }
4794
+ function listItem(project, rootAssetId, node, row) {
4795
+ const relativePath = node.current_attempt?.file_path || node.local_path;
4796
+ const absolutePath = absoluteLocalPath2(relativePath);
4797
+ const exists = absolutePath ? existsSync8(absolutePath) : false;
4798
+ const warnings = [];
4799
+ if (absolutePath && !exists) warnings.push(`Asset ${node.asset_id} local file is missing: ${absolutePath}`);
4800
+ if (absolutePath && exists && !isReadable(absolutePath)) {
4801
+ warnings.push(`Asset ${node.asset_id} local file is unreadable: ${absolutePath}. Check file permissions before handoff.`);
4802
+ }
4803
+ if (!absolutePath && node.s3_key) {
4804
+ warnings.push(`Asset ${node.asset_id} is only available in non-local storage; create a readable local copy before handoff.`);
4805
+ }
4806
+ if (!absolutePath && !node.s3_key) warnings.push(`Asset ${node.asset_id} has no local file or storage key.`);
4807
+ const base = `--project ${shellQuote(project)} --root ${shellQuote(rootAssetId)} --asset ${shellQuote(node.asset_id)}`;
4808
+ return {
4809
+ ...markFromRow(row),
4810
+ checksum_sha256: node.current_attempt?.checksum_sha256 || node.checksum_sha256,
4811
+ commands: {
4812
+ mark: lineageCliCommand(`social mark ${base} --confirm-write`),
4813
+ unmark: lineageCliCommand(`social unmark ${base} --confirm-write`)
4814
+ },
4815
+ current_attempt: node.current_attempt,
4816
+ local: {
4817
+ absolute_path: absolutePath,
4818
+ exists,
4819
+ relative_path: relativePath
4820
+ },
4821
+ media_type: node.media_type,
4822
+ s3: node.s3_key ? { key: node.s3_key } : void 0,
4823
+ source: node.source,
4824
+ title: node.title,
4825
+ warnings
4826
+ };
4827
+ }
4828
+ function listAssetSocialMarks(project, rootAssetId) {
4829
+ const snapshot = requireCanonicalSnapshot(project, rootAssetId);
4830
+ const database = lineageDb();
4831
+ let rows;
4832
+ try {
4833
+ const hasSocialMarks = database.prepare("select 1 from sqlite_master where type = 'table' and name = 'asset_social_marks'").get() !== void 0;
4834
+ rows = hasSocialMarks ? database.prepare(`
4835
+ select * from asset_social_marks
4836
+ where project_id = ? and root_asset_id = ? and unmarked_at is null
4837
+ order by marked_at, asset_id
4838
+ `).all(project, rootAssetId) : [];
4839
+ } finally {
4840
+ database.close();
4841
+ }
4842
+ const nodesById = new Map(snapshot.nodes.map((node) => [node.asset_id, node]));
4843
+ return {
4844
+ commands: {
4845
+ mark: lineageCliCommand(`social mark --project ${shellQuote(project)} --root ${shellQuote(rootAssetId)} --asset <asset-id> --confirm-write`)
4846
+ },
4847
+ fetchedAt: nowIso(),
4848
+ marks: rows.flatMap((row) => {
4849
+ const node = nodesById.get(row.asset_id);
4850
+ return node ? [listItem(project, rootAssetId, node, row)] : [];
4851
+ }),
4852
+ project,
4853
+ root_asset_id: rootAssetId,
4854
+ schema_version: "lineage.social_marks.v1",
4855
+ workspace: {
4856
+ id: lineageWorkspaceId(project, rootAssetId),
4857
+ root_asset_id: rootAssetId
4858
+ }
4859
+ };
4860
+ }
4861
+
4368
4862
  // src/server/generationReceipts.ts
4369
- import { existsSync as existsSync8, realpathSync as realpathSync2, statSync as statSync4 } from "node:fs";
4370
- import { relative as relative3, resolve as resolve6 } from "node:path";
4863
+ import { existsSync as existsSync9, realpathSync as realpathSync2, statSync as statSync4 } from "node:fs";
4864
+ import { relative as relative3, resolve as resolve7 } from "node:path";
4371
4865
  import { randomUUID as randomUUID3 } from "node:crypto";
4372
4866
 
4373
4867
  // src/shared/generationOutputManifest.ts
@@ -4902,8 +5396,8 @@ function isPathInside(child, parent) {
4902
5396
  return Boolean(rel) && !rel.startsWith("..") && !rel.startsWith("/");
4903
5397
  }
4904
5398
  function scratchCandidate(file) {
4905
- const scratchRoot = resolve6(repoRoot, ".asset-scratch");
4906
- const candidate = file.startsWith(".asset-scratch/") || resolve6(file).startsWith(scratchRoot) ? resolve6(repoRoot, file) : resolve6(scratchRoot, file);
5399
+ const scratchRoot = resolve7(repoRoot, ".asset-scratch");
5400
+ const candidate = file.startsWith(".asset-scratch/") || resolve7(file).startsWith(scratchRoot) ? resolve7(repoRoot, file) : resolve7(scratchRoot, file);
4907
5401
  if (!isPathInside(candidate, scratchRoot)) throw new GenerationReceiptError(`Import file must be under .asset-scratch: ${file}`);
4908
5402
  return { candidate, scratchRoot };
4909
5403
  }
@@ -4913,7 +5407,7 @@ function resolveScratchManifestPath(file) {
4913
5407
  }
4914
5408
  function resolveScratchFile(file) {
4915
5409
  const { candidate, scratchRoot } = scratchCandidate(file);
4916
- if (!existsSync8(candidate)) throw new GenerationReceiptError(`Missing import file: ${file}`, 404);
5410
+ if (!existsSync9(candidate)) throw new GenerationReceiptError(`Missing import file: ${file}`, 404);
4917
5411
  const realScratchRoot = realpathSync2(scratchRoot);
4918
5412
  const realCandidate = realpathSync2(candidate);
4919
5413
  if (!isPathInside(realCandidate, realScratchRoot)) throw new GenerationReceiptError(`Import file must be under .asset-scratch: ${file}`);
@@ -5157,8 +5651,8 @@ function importImageRerollOutput(project = defaultProject, fields) {
5157
5651
 
5158
5652
  // src/server/lineageSelectionPacket.ts
5159
5653
  import { createHash as createHash5 } from "node:crypto";
5160
- import { existsSync as existsSync9, statSync as statSync5 } from "node:fs";
5161
- import { isAbsolute as isAbsolute3, resolve as resolve7 } from "node:path";
5654
+ import { existsSync as existsSync10, statSync as statSync5 } from "node:fs";
5655
+ import { isAbsolute as isAbsolute4, resolve as resolve8 } from "node:path";
5162
5656
  var LineageSelectionPacketError = class extends Error {
5163
5657
  constructor(message, warnings = [], errors = [], diagnostics = []) {
5164
5658
  super(message);
@@ -5204,13 +5698,13 @@ function resolveWorkspace(project, options) {
5204
5698
  }
5205
5699
  function resolveLocalReference(reference) {
5206
5700
  if (!reference) return void 0;
5207
- if (isAbsolute3(reference)) return reference;
5208
- const repoRelative = resolve7(repoRoot, reference);
5209
- if (existsSync9(repoRelative)) return repoRelative;
5210
- return resolve7(repoRoot, ".asset-scratch", reference);
5701
+ if (isAbsolute4(reference)) return reference;
5702
+ const repoRelative = resolve8(repoRoot, reference);
5703
+ if (existsSync10(repoRelative)) return repoRelative;
5704
+ return resolve8(repoRoot, ".asset-scratch", reference);
5211
5705
  }
5212
5706
  function fileSize(path) {
5213
- if (!path || !existsSync9(path)) return void 0;
5707
+ if (!path || !existsSync10(path)) return void 0;
5214
5708
  try {
5215
5709
  return statSync5(path).size;
5216
5710
  } catch {
@@ -5238,7 +5732,7 @@ function currentAttemptFor(node) {
5238
5732
  function assetForNode(node, catalogAsset, warnings) {
5239
5733
  const localReference = catalogAsset?.local?.absolute_path || node.current_attempt?.file_path || node.local_path || catalogAsset?.local?.relative_path;
5240
5734
  const absolutePath = catalogAsset?.local?.absolute_path || resolveLocalReference(localReference);
5241
- const localExists = absolutePath ? existsSync9(absolutePath) : false;
5735
+ const localExists = absolutePath ? existsSync10(absolutePath) : false;
5242
5736
  const hasLocalClaim = Boolean(absolutePath || node.local_path || catalogAsset?.local?.relative_path);
5243
5737
  const s3Key = catalogAsset?.s3?.key || node.s3_key;
5244
5738
  const hasS3 = Boolean(s3Key);
@@ -5395,7 +5889,7 @@ function assetForNodeV2(node, visibleCatalogAsset, catalogById, warnings, errors
5395
5889
  const mediaCatalogAsset = currentAttemptCatalogAsset || (isVisibleNodeAttempt ? visibleCatalogAsset : void 0);
5396
5890
  const localReference = currentAttempt.file_path || mediaCatalogAsset?.local?.absolute_path || mediaCatalogAsset?.local?.relative_path || (isVisibleNodeAttempt ? node.local_path : void 0);
5397
5891
  const absolutePath = resolveLocalReference(localReference);
5398
- const localExists = absolutePath ? existsSync9(absolutePath) : false;
5892
+ const localExists = absolutePath ? existsSync10(absolutePath) : false;
5399
5893
  const hasLocalClaim = Boolean(localReference);
5400
5894
  const s3Key = mediaCatalogAsset?.s3?.key || (isVisibleNodeAttempt ? node.s3_key : void 0);
5401
5895
  const hasS3 = Boolean(s3Key);
@@ -5661,8 +6155,8 @@ function getLineageSelectionPacket(project, options = {}) {
5661
6155
  // src/server/runtimeInfo.ts
5662
6156
  import { createHash as createHash6 } from "node:crypto";
5663
6157
  import { spawnSync as spawnSync2 } from "node:child_process";
5664
- import { existsSync as existsSync10, lstatSync as lstatSync3, readFileSync as readFileSync5, readdirSync as readdirSync3, readlinkSync, realpathSync as realpathSync3, statSync as statSync6 } from "node:fs";
5665
- import { dirname as dirname5, isAbsolute as isAbsolute4, join as join9, resolve as resolve8 } from "node:path";
6158
+ import { existsSync as existsSync11, lstatSync as lstatSync3, readFileSync as readFileSync5, readdirSync as readdirSync3, readlinkSync, realpathSync as realpathSync3, statSync as statSync6 } from "node:fs";
6159
+ import { dirname as dirname5, isAbsolute as isAbsolute5, join as join9, resolve as resolve9 } from "node:path";
5666
6160
  import { fileURLToPath as fileURLToPath2 } from "node:url";
5667
6161
 
5668
6162
  // src/shared/runtimeInfoTypes.ts
@@ -5681,7 +6175,7 @@ function isLineagePackageRoot(root) {
5681
6175
  }
5682
6176
  function executingCodeRoot() {
5683
6177
  const moduleDirectory = dirname5(fileURLToPath2(import.meta.url));
5684
- const candidates = [resolve8(moduleDirectory, "../.."), resolve8(moduleDirectory, "..")];
6178
+ const candidates = [resolve9(moduleDirectory, "../.."), resolve9(moduleDirectory, "..")];
5685
6179
  const root = candidates.find(isLineagePackageRoot);
5686
6180
  if (!root) throw new Error(`Unable to derive Lineage code root from executing module ${fileURLToPath2(import.meta.url)}`);
5687
6181
  return canonicalRoot(root);
@@ -5694,7 +6188,7 @@ function canonicalRoot(root) {
5694
6188
  try {
5695
6189
  return realpathSync3(root);
5696
6190
  } catch {
5697
- return resolve8(root);
6191
+ return resolve9(root);
5698
6192
  }
5699
6193
  }
5700
6194
  function packageInfo(root = codeRoot) {
@@ -5794,7 +6288,7 @@ function readInstallReceipt(path, errors) {
5794
6288
  return void 0;
5795
6289
  }
5796
6290
  let receipt;
5797
- const receiptPath = resolve8(path);
6291
+ const receiptPath = resolve9(path);
5798
6292
  try {
5799
6293
  receipt = JSON.parse(readFileSync5(receiptPath, "utf8"));
5800
6294
  } catch (error) {
@@ -5802,7 +6296,7 @@ function readInstallReceipt(path, errors) {
5802
6296
  return void 0;
5803
6297
  }
5804
6298
  if (receipt.schema_version !== lineageRuntimeInstallSchemaVersion) errors.push(`Unsupported runtime install receipt schema: ${String(receipt.schema_version)}`);
5805
- if (!isAbsolute4(receipt.package_root || "")) errors.push("Runtime install receipt package_root must be absolute");
6299
+ if (!isAbsolute5(receipt.package_root || "")) errors.push("Runtime install receipt package_root must be absolute");
5806
6300
  if (!receipt.package_integrity?.startsWith("sha512-")) errors.push("Runtime install receipt package integrity is invalid");
5807
6301
  if (receipt.package_source !== "registry" && receipt.package_source !== "local") errors.push("Runtime install receipt package source is invalid");
5808
6302
  if (typeof receipt.package_spec !== "string" || !receipt.package_spec) errors.push("Runtime install receipt package spec is invalid");
@@ -5883,8 +6377,8 @@ function packageCodeIdentity(root, channel, info, receiptPath) {
5883
6377
  function getLineageCodeIdentity(channel, options = {}) {
5884
6378
  const root = canonicalRoot(options.root || codeRoot);
5885
6379
  const info = packageInfo(root);
5886
- if (existsSync10(join9(root, ".git"))) return checkoutCodeIdentity(root, channel, info.version);
5887
- if (existsSync10(join9(root, "package.json"))) {
6380
+ if (existsSync11(join9(root, ".git"))) return checkoutCodeIdentity(root, channel, info.version);
6381
+ if (existsSync11(join9(root, "package.json"))) {
5888
6382
  return packageCodeIdentity(root, channel, info, options.receiptPath ?? process.env.LINEAGE_RUNTIME_RECEIPT);
5889
6383
  }
5890
6384
  const errors = [`Code root has neither checkout metadata nor package.json: ${root}`];
@@ -5923,7 +6417,7 @@ function getLineageRuntimeInfo(options = {}) {
5923
6417
  const dbPath = options.dbPath || lineageDbPath();
5924
6418
  const channel = normalizeRuntimeChannel(options.channel || process.env.LINEAGE_CHANNEL || process.env.LINEAGE_RELEASE_CHANNEL);
5925
6419
  const code = options.code || getLineageCodeIdentity(channel);
5926
- const databaseInfo = { exists: existsSync10(dbPath), path: dbPath };
6420
+ const databaseInfo = { exists: existsSync11(dbPath), path: dbPath };
5927
6421
  const schema = { migration_keys: [] };
5928
6422
  const serviceInstanceId = process.env.LINEAGE_SERVICE_INSTANCE_ID;
5929
6423
  const launcherPid = process.env.LINEAGE_LAUNCHER_PID ? Number(process.env.LINEAGE_LAUNCHER_PID) : void 0;
@@ -6080,7 +6574,7 @@ var signalExitCodes = {
6080
6574
  SIGTERM: 143
6081
6575
  };
6082
6576
  function packageRoot2() {
6083
- return resolve9(dirname6(fileURLToPath3(import.meta.url)), "..", "..");
6577
+ return resolve10(dirname6(fileURLToPath3(import.meta.url)), "..", "..");
6084
6578
  }
6085
6579
  function packageVersion() {
6086
6580
  try {
@@ -6119,7 +6613,7 @@ function readOptions(args, name) {
6119
6613
  }
6120
6614
  function readJsonFile(path, label) {
6121
6615
  try {
6122
- return JSON.parse(readFileSync6(resolve9(path), "utf8"));
6616
+ return JSON.parse(readFileSync6(resolve10(path), "utf8"));
6123
6617
  } catch (error) {
6124
6618
  throw new Error(`${label} must be a readable JSON file: ${error instanceof Error ? error.message : String(error)}`, { cause: error });
6125
6619
  }
@@ -6163,7 +6657,7 @@ function resolveStartOptions(config, args) {
6163
6657
  };
6164
6658
  }
6165
6659
  function resolveCliAssetRoot(args) {
6166
- return resolve9(readOption(args, "--asset-root") || process.env.LINEAGE_ASSET_ROOT || packageRoot);
6660
+ return resolve10(readOption(args, "--asset-root") || process.env.LINEAGE_ASSET_ROOT || packageRoot);
6167
6661
  }
6168
6662
  function configureCliAssetRoot(args) {
6169
6663
  const assetRoot = resolveCliAssetRoot(args);
@@ -6195,10 +6689,10 @@ function prepareCliProfile(config, args) {
6195
6689
  if (hasOption(args, "--db")) throw new Error("A named profile cannot be combined with --db");
6196
6690
  if (hasOption(args, "--asset-root")) throw new Error("A named profile cannot be combined with --asset-root");
6197
6691
  const profile = resolveLineageProfile(selector);
6198
- if (process.env.LINEAGE_DB && resolve9(process.env.LINEAGE_DB) !== profile.database_path) {
6692
+ if (process.env.LINEAGE_DB && resolve10(process.env.LINEAGE_DB) !== profile.database_path) {
6199
6693
  throw new Error(`Profile ${profile.profile_id} database_path conflicts with LINEAGE_DB`);
6200
6694
  }
6201
- if (process.env.LINEAGE_ASSET_ROOT && resolve9(process.env.LINEAGE_ASSET_ROOT) !== profile.asset_root) {
6695
+ if (process.env.LINEAGE_ASSET_ROOT && resolve10(process.env.LINEAGE_ASSET_ROOT) !== profile.asset_root) {
6202
6696
  throw new Error(`Profile ${profile.profile_id} asset_root conflicts with LINEAGE_ASSET_ROOT`);
6203
6697
  }
6204
6698
  assertProfileChannel(profile, config.channel);
@@ -6227,6 +6721,7 @@ Usage:
6227
6721
  ${config.binName} profile clone --source-db <snapshot-source> --target-profile <id-or-manifest> --confirm-write [--json]
6228
6722
  ${config.binName} profile clone-assets --source-asset-root <path> --target-profile <id-or-manifest> --confirm-write [--json]
6229
6723
  ${config.binName} profile repin-runtime --profile <development-profile> --checkout-root <path> --confirm-write [--json]
6724
+ ${config.binName} profile upgrade-runtime --profile <production-profile> --confirm-write [--json]
6230
6725
  ${config.binName} runtime info [--json]
6231
6726
  ${config.binName} runtime doctor [--json]
6232
6727
  ${config.binName} next [--project <project>] [--root <asset-id>] [--db <path>] [--json]
@@ -6243,6 +6738,9 @@ Usage:
6243
6738
  ${config.binName} reroll cancel --root <asset-id> --target <asset-id> [--project <project>] [--confirm-write] [--db <path>] [--json]
6244
6739
  ${config.binName} reroll plan --root <asset-id> --target <asset-id> --prompt <text> [--project <project>] [--db <path>] [--json]
6245
6740
  ${config.binName} reroll import --job-id <job-id> --file <scratch-file> --confirm-write [--project <project>] [--db <path>] [--json]
6741
+ ${config.binName} social list --project <project> --root <asset-id> [--db <path>] [--json]
6742
+ ${config.binName} social mark --project <project> --root <asset-id> --asset <asset-id-or-exact-title> --confirm-write [--actor <actor>] [--notes <text>] [--claim-token <claim-id.secret>] [--db <path>] [--json]
6743
+ ${config.binName} social unmark --project <project> --root <asset-id> --asset <asset-id-or-exact-title> --confirm-write [--actor <actor>] [--claim-token <claim-id.secret>] [--db <path>] [--json]
6246
6744
  ${config.binName} tasks list --root <asset-id> [--project <project>] [--db <path>] [--json]
6247
6745
  ${config.binName} tasks inspect --task <task-id> [--project <project>] [--db <path>] [--json]
6248
6746
  ${config.binName} tasks claim --task <task-id> --agent-name <name> [--project <project>] [--db <path>] [--json]
@@ -6299,7 +6797,7 @@ function lineageServiceIdentityErrors(runtime, expected) {
6299
6797
  if (runtime.channel !== expected.channel) errors.push(`channel ${runtime.channel} != ${expected.channel}`);
6300
6798
  if (!runtime.code?.verified) errors.push("service code identity is not verified");
6301
6799
  if (runtime.code?.fingerprint !== expected.code_fingerprint) errors.push(`code fingerprint ${runtime.code?.fingerprint || "missing"} != ${expected.code_fingerprint}`);
6302
- if (resolve9(runtime.database.path) !== resolve9(expected.database_path)) errors.push(`database ${runtime.database.path} != ${expected.database_path}`);
6800
+ if (resolve10(runtime.database.path) !== resolve10(expected.database_path)) errors.push(`database ${runtime.database.path} != ${expected.database_path}`);
6303
6801
  if (runtime.service?.instance_id !== expected.instance_id) errors.push(`service instance ${runtime.service?.instance_id || "missing"} != ${expected.instance_id}`);
6304
6802
  if (runtime.service?.launcher_pid !== expected.launcher_pid) errors.push(`launcher pid ${runtime.service?.launcher_pid || "missing"} != ${expected.launcher_pid}`);
6305
6803
  if (expected.profile) {
@@ -6394,7 +6892,7 @@ function runLineageDataCommand(command, args) {
6394
6892
  });
6395
6893
  const out = readOption(args, "--out");
6396
6894
  if (out) {
6397
- const outPath = resolve9(out);
6895
+ const outPath = resolve10(out);
6398
6896
  mkdirSync6(dirname6(outPath), { recursive: true });
6399
6897
  writeFileSync4(outPath, `${JSON.stringify(packet, null, 2)}
6400
6898
  `);
@@ -6505,6 +7003,35 @@ function runLineageDataCommand(command, args) {
6505
7003
  }
6506
7004
  throw new Error(`Unknown reroll command: ${subcommand}`);
6507
7005
  }
7006
+ if (command === "social") {
7007
+ const subcommand = positionalArgs(args)[0] || "";
7008
+ const explicitProject = readOption(args, "--project");
7009
+ const asset = readOption(args, "--asset");
7010
+ if (!explicitProject) throw new Error(`lineage social ${subcommand || "command"} requires --project`);
7011
+ if (!options.rootAssetId) throw new Error(`lineage social ${subcommand || "command"} requires --root`);
7012
+ if (subcommand === "list") return listAssetSocialMarks(explicitProject, options.rootAssetId);
7013
+ if (!asset) throw new Error(`lineage social ${subcommand || "command"} requires --asset`);
7014
+ if (subcommand === "mark") {
7015
+ return markAssetSocial(explicitProject, {
7016
+ asset,
7017
+ claimToken: options.claimToken,
7018
+ confirmWrite: options.confirmWrite,
7019
+ markedBy: readOption(args, "--actor") || "agent",
7020
+ notes: readOption(args, "--notes"),
7021
+ rootAssetId: options.rootAssetId
7022
+ });
7023
+ }
7024
+ if (subcommand === "unmark") {
7025
+ return unmarkAssetSocial(explicitProject, {
7026
+ asset,
7027
+ claimToken: options.claimToken,
7028
+ confirmWrite: options.confirmWrite,
7029
+ rootAssetId: options.rootAssetId,
7030
+ unmarkedBy: readOption(args, "--actor") || "agent"
7031
+ });
7032
+ }
7033
+ throw new Error(`Unknown social command: ${subcommand}`);
7034
+ }
6508
7035
  if (command === "tasks") {
6509
7036
  const subcommand = positionalArgs(args)[0] || "";
6510
7037
  const taskId = readOption(args, "--task");
@@ -6634,6 +7161,20 @@ function printDataResult(command, result, json) {
6634
7161
  return;
6635
7162
  }
6636
7163
  }
7164
+ if (command === "social" && result && typeof result === "object") {
7165
+ if ("marks" in result) {
7166
+ const listed = result;
7167
+ console.log(`${listed.marks.length} social-marked asset(s)`);
7168
+ for (const mark of listed.marks) {
7169
+ console.log(`${mark.asset_id}: ${mark.title}`);
7170
+ for (const warning of mark.warnings || []) console.log(`Warning: ${warning}`);
7171
+ }
7172
+ return;
7173
+ }
7174
+ const mutation = result;
7175
+ console.log(`${mutation.dryRun ? "Dry run: " : ""}${mutation.active ? "Marked" : "Unmarked"} ${mutation.mark?.asset_id || "asset"} for social`);
7176
+ return;
7177
+ }
6637
7178
  if (command === "tasks" && result && typeof result === "object") {
6638
7179
  if ("tasks" in result) {
6639
7180
  const listed = result;
@@ -6722,6 +7263,30 @@ function runLineageProfileCommand(config, command, args) {
6722
7263
  writerLease.release();
6723
7264
  }
6724
7265
  }
7266
+ if (command === "upgrade-runtime") {
7267
+ if (!args.includes("--confirm-write")) throw new Error("Profile runtime upgrade requires --confirm-write");
7268
+ for (const option of [
7269
+ "--checkout-root",
7270
+ "--code-fingerprint",
7271
+ "--code-root",
7272
+ "--from-code-fingerprint",
7273
+ "--git-sha",
7274
+ "--runtime-receipt",
7275
+ "--runtime-root",
7276
+ "--to-code-fingerprint",
7277
+ "--to-version",
7278
+ "--version"
7279
+ ]) {
7280
+ if (hasOption(args, option)) throw new Error(`Profile runtime upgrade derives its target from the executing stable package and does not accept ${option}`);
7281
+ }
7282
+ const profile = resolveLineageProfile(selector);
7283
+ const writerLease = acquireProfileWriterLease(profile, config.channel, "cli");
7284
+ try {
7285
+ return upgradeLineageStableProfileRuntime(selector, runtimeIdentity, true);
7286
+ } finally {
7287
+ writerLease.release();
7288
+ }
7289
+ }
6725
7290
  if (command === "bind") {
6726
7291
  if (!args.includes("--confirm-write")) throw new Error("Profile bind requires --confirm-write");
6727
7292
  const profile = resolveLineageProfile(selector);
@@ -6753,6 +7318,9 @@ function printProfileResult(result, json) {
6753
7318
  } else if (result.schema_version === "lineage.profile_runtime_repin_receipt.v1") {
6754
7319
  console.log(`${result.changed ? "Repinned" : "Already pinned"} ${result.profile_id} to checkout ${result.checkout_root}`);
6755
7320
  console.log(`Code fingerprint: ${result.new_code_fingerprint}`);
7321
+ } else if (result.schema_version === "lineage.profile_runtime_upgrade_receipt.v1") {
7322
+ console.log(`${result.changed ? "Upgraded" : "Already pinned"} ${result.profile_id} to stable ${result.new_runtime.version}`);
7323
+ console.log(`Code fingerprint: ${result.new_runtime.code_fingerprint}`);
6756
7324
  } else {
6757
7325
  console.log(`Cloned ${result.files_copied} referenced asset file(s) into ${result.asset_root}`);
6758
7326
  console.log(`Receipt: ${result.receipt_path}`);
@@ -6787,6 +7355,7 @@ function lineageCliRequiresWriterLease(command, args) {
6787
7355
  const subcommand = positions[0] || "";
6788
7356
  if (command === "generate") return positions[0] !== "image" || positions[1] !== "inspect";
6789
7357
  if (command === "reroll") return subcommand !== "list";
7358
+ if (command === "social") return subcommand !== "list";
6790
7359
  if (command === "tasks") return subcommand !== "list" && subcommand !== "inspect";
6791
7360
  if (command === "db") return subcommand !== "info";
6792
7361
  if (command === "agent") return subcommand !== "status" && subcommand !== "graph" && subcommand !== "inspect";
@@ -6960,7 +7529,7 @@ function start(config, args) {
6960
7529
  process.exit(1);
6961
7530
  }
6962
7531
  const serverPath = join10(packageRoot2(), "dist", "server.js");
6963
- if (!existsSync11(serverPath)) {
7532
+ if (!existsSync12(serverPath)) {
6964
7533
  const message = `Missing bundled server at ${serverPath}. Run npm run build before using ${config.binName} start from a source checkout.`;
6965
7534
  if (options.json) console.error(JSON.stringify({ ok: false, error: message }, null, 2));
6966
7535
  else console.error(`${config.binName}: ${message}`);
@@ -7124,7 +7693,7 @@ async function runLineageCli(config, args = process.argv.slice(2)) {
7124
7693
  else console.error(`${config.binName}: ${message2}`);
7125
7694
  process.exit(1);
7126
7695
  }
7127
- if (command === "next" || command === "brief" || command === "inspect" || command === "selection" || command === "link-child" || command === "generate" || command === "reroll" || command === "tasks") {
7696
+ if (command === "next" || command === "brief" || command === "inspect" || command === "selection" || command === "link-child" || command === "generate" || command === "reroll" || command === "social" || command === "tasks") {
7128
7697
  const commandArgs = normalizedArgs.slice(1);
7129
7698
  const json2 = commandArgs.includes("--json");
7130
7699
  try {