@mean-weasel/lineage 0.1.23 → 0.1.25
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 +9 -0
- package/README.md +37 -0
- package/dist/cli/lineage-dev.js +605 -36
- package/dist/cli/lineage-dev.js.map +4 -4
- package/dist/cli/lineage-preview.js +605 -36
- package/dist/cli/lineage-preview.js.map +4 -4
- package/dist/cli/lineage.js +605 -36
- package/dist/cli/lineage.js.map +4 -4
- package/dist/docs-site/operations/channels/index.html +11 -2
- package/dist/docs-site/operations/profiles-database-identity/index.html +16 -2
- package/dist/docs-site/pagefind/fragment/en_761fab1.pf_fragment +0 -0
- package/dist/docs-site/pagefind/fragment/en_bd3e194.pf_fragment +0 -0
- package/dist/docs-site/pagefind/fragment/en_ee82be6.pf_fragment +0 -0
- package/dist/docs-site/pagefind/fragment/en_f4db9e2.pf_fragment +0 -0
- package/dist/docs-site/pagefind/index/en_7b1363c.pf_index +0 -0
- package/dist/docs-site/pagefind/pagefind-entry.json +1 -1
- package/dist/docs-site/pagefind/pagefind.en_4e77743529.pf_meta +0 -0
- package/dist/docs-site/reference/cli/index.html +11 -2
- package/dist/docs-site/start-here/installation-first-run/index.html +7 -1
- package/dist/pages/assets/app-BfxPwDL1.js +50 -0
- package/dist/pages/assets/{app-B-fAyNsU.css → app-CuBsKrS-.css} +1 -1
- package/dist/pages/docs/operations/channels/index.html +11 -2
- package/dist/pages/docs/operations/profiles-database-identity/index.html +16 -2
- package/dist/pages/docs/pagefind/fragment/en_761fab1.pf_fragment +0 -0
- package/dist/pages/docs/pagefind/fragment/en_bd3e194.pf_fragment +0 -0
- package/dist/pages/docs/pagefind/fragment/en_ee82be6.pf_fragment +0 -0
- package/dist/pages/docs/pagefind/fragment/en_f4db9e2.pf_fragment +0 -0
- package/dist/pages/docs/pagefind/index/en_7b1363c.pf_index +0 -0
- package/dist/pages/docs/pagefind/pagefind-entry.json +1 -1
- package/dist/pages/docs/pagefind/pagefind.en_4e77743529.pf_meta +0 -0
- package/dist/pages/docs/reference/cli/index.html +11 -2
- package/dist/pages/docs/start-here/installation-first-run/index.html +7 -1
- package/dist/runtime-build.json +5 -5
- package/dist/server.js +375 -39
- package/dist/server.js.map +4 -4
- package/dist/web/assets/app-BfxPwDL1.js +50 -0
- package/dist/web/assets/{app-B-fAyNsU.css → app-CuBsKrS-.css} +1 -1
- package/dist/web/index.html +2 -2
- package/package.json +4 -3
- package/dist/docs-site/pagefind/fragment/en_4db923b.pf_fragment +0 -0
- package/dist/docs-site/pagefind/fragment/en_581a7cb.pf_fragment +0 -0
- package/dist/docs-site/pagefind/fragment/en_8c1c735.pf_fragment +0 -0
- package/dist/docs-site/pagefind/fragment/en_bcc29c5.pf_fragment +0 -0
- package/dist/docs-site/pagefind/index/en_f279b04.pf_index +0 -0
- package/dist/docs-site/pagefind/pagefind.en_74951c3ca7.pf_meta +0 -0
- package/dist/pages/assets/app-DdU93vp2.js +0 -50
- package/dist/pages/docs/pagefind/fragment/en_4db923b.pf_fragment +0 -0
- package/dist/pages/docs/pagefind/fragment/en_581a7cb.pf_fragment +0 -0
- package/dist/pages/docs/pagefind/fragment/en_8c1c735.pf_fragment +0 -0
- package/dist/pages/docs/pagefind/fragment/en_bcc29c5.pf_fragment +0 -0
- package/dist/pages/docs/pagefind/index/en_f279b04.pf_index +0 -0
- package/dist/pages/docs/pagefind/pagefind.en_74951c3ca7.pf_meta +0 -0
- package/dist/web/assets/app-DdU93vp2.js +0 -50
package/dist/cli/lineage-dev.js
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
3
|
// src/cli/lineageCli.ts
|
|
4
|
-
import { existsSync as
|
|
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
|
|
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 preview2 = {
|
|
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, preview2, 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
|
+
preview2.id,
|
|
4733
|
+
project,
|
|
4734
|
+
fields.rootAssetId,
|
|
4735
|
+
node.asset_id,
|
|
4736
|
+
preview2.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 preview2 = 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, preview2, 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, preview2);
|
|
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
|
|
4370
|
-
import { relative as relative3, resolve as
|
|
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 =
|
|
4906
|
-
const candidate = file.startsWith(".asset-scratch/") ||
|
|
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 (!
|
|
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
|
|
5161
|
-
import { isAbsolute as
|
|
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 (
|
|
5208
|
-
const repoRelative =
|
|
5209
|
-
if (
|
|
5210
|
-
return
|
|
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 || !
|
|
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 ?
|
|
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 ?
|
|
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
|
|
5665
|
-
import { dirname as dirname5, isAbsolute as
|
|
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 = [
|
|
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
|
|
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 =
|
|
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 (!
|
|
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 (
|
|
5887
|
-
if (
|
|
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:
|
|
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
|
|
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(
|
|
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
|
|
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 &&
|
|
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 &&
|
|
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 (
|
|
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 =
|
|
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 (!
|
|
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 {
|