@mean-weasel/lineage 0.1.13 → 0.1.15

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.
@@ -17,7 +17,7 @@ import { existsSync as existsSync6, mkdirSync as mkdirSync5 } from "node:fs";
17
17
  import { join as join6 } from "node:path";
18
18
 
19
19
  // src/server/assetCore.ts
20
- import { existsSync as existsSync3, mkdirSync as mkdirSync2, readdirSync as readdirSync2, readFileSync as readFileSync2, unlinkSync, writeFileSync } from "node:fs";
20
+ import { chmodSync, existsSync as existsSync3, mkdirSync as mkdirSync2, readdirSync as readdirSync2, readFileSync as readFileSync2, unlinkSync, writeFileSync } from "node:fs";
21
21
  import { dirname as dirname2, join as join3, resolve as resolve2 } from "node:path";
22
22
  import { spawnSync } from "node:child_process";
23
23
  import { fileURLToPath } from "node:url";
@@ -590,21 +590,26 @@ function validateProject(project = defaultProject) {
590
590
 
591
591
  // src/server/profileWriterLease.ts
592
592
  import { createHash as createHash3, randomUUID as randomUUID2, timingSafeEqual } from "node:crypto";
593
- import { existsSync as existsSync5, lstatSync, mkdirSync as mkdirSync4, readFileSync as readFileSync4, renameSync, rmSync as rmSync2, writeFileSync as writeFileSync3 } from "node:fs";
593
+ import { existsSync as existsSync5, lstatSync as lstatSync2, mkdirSync as mkdirSync4, readFileSync as readFileSync4, renameSync as renameSync2, rmSync as rmSync2, writeFileSync as writeFileSync3 } from "node:fs";
594
594
  import { dirname as dirname4, join as join5, resolve as resolve4 } from "node:path";
595
595
 
596
596
  // src/server/lineageProfiles.ts
597
597
  import { createHash as createHash2, randomUUID } from "node:crypto";
598
598
  import { createRequire } from "node:module";
599
599
  import {
600
- chmodSync,
600
+ chmodSync as chmodSync2,
601
+ closeSync,
601
602
  constants as fsConstants,
602
603
  copyFileSync as copyFileSync2,
603
604
  existsSync as existsSync4,
605
+ fsyncSync,
604
606
  linkSync,
607
+ lstatSync,
605
608
  mkdirSync as mkdirSync3,
609
+ openSync,
606
610
  readFileSync as readFileSync3,
607
611
  realpathSync,
612
+ renameSync,
608
613
  rmSync,
609
614
  statSync as statSync3,
610
615
  writeFileSync as writeFileSync2
@@ -617,6 +622,7 @@ var lineageProfileSchemaVersion = "lineage.profile.v1";
617
622
  var lineageProfileDoctorSchemaVersion = "lineage.profile_doctor.v1";
618
623
  var lineageProfileCloneReceiptSchemaVersion = "lineage.profile_clone_receipt.v1";
619
624
  var lineageProfileAssetsCloneReceiptSchemaVersion = "lineage.profile_assets_clone_receipt.v1";
625
+ var lineageProfileRuntimeRepinReceiptSchemaVersion = "lineage.profile_runtime_repin_receipt.v1";
620
626
 
621
627
  // src/server/lineageProfiles.ts
622
628
  var require2 = createRequire(import.meta.url);
@@ -761,6 +767,129 @@ function lineageProfileFingerprint(profile) {
761
767
  service_origin: profile.service_origin
762
768
  })).digest("hex");
763
769
  }
770
+ function sha256(value) {
771
+ return createHash2("sha256").update(value).digest("hex");
772
+ }
773
+ function assertOwnerOnlyPath(path, kind) {
774
+ const stats = lstatSync(path);
775
+ const expectedType = kind === "manifest" ? stats.isFile() : stats.isDirectory();
776
+ if (!expectedType || stats.isSymbolicLink()) throw new Error(`Profile ${kind} must be a nonsymlink ${kind === "manifest" ? "regular file" : "directory"}: ${path}`);
777
+ if ((stats.mode & 63) !== 0) throw new Error(`Profile ${kind} must be owner-only: ${path}`);
778
+ if (typeof process.getuid === "function" && stats.uid !== process.getuid()) throw new Error(`Profile ${kind} must be owned by the current user: ${path}`);
779
+ return stats;
780
+ }
781
+ function runtimeRepinInvariant(profile) {
782
+ return JSON.stringify({
783
+ asset_root: profile.asset_root,
784
+ database_path: profile.database_path,
785
+ environment: profile.environment,
786
+ profile_fingerprint: profile.profile_fingerprint,
787
+ profile_id: profile.profile_id,
788
+ required_schema_migrations: profile.required_schema_migrations || [],
789
+ schema_version: profile.schema_version,
790
+ service_origin: profile.service_origin
791
+ });
792
+ }
793
+ function sameFileIdentity(left, right) {
794
+ return left.dev === right.dev && left.ino === right.ino && left.size === right.size && left.mtimeMs === right.mtimeMs && left.ctimeMs === right.ctimeMs;
795
+ }
796
+ function repinLineageDevelopmentProfileRuntime(selector, checkoutRoot, runtime, confirmWrite) {
797
+ if (!confirmWrite) throw new Error("Profile runtime repin requires --confirm-write");
798
+ if (runtime.channel !== "dev") throw new Error(`Profile runtime repin requires dev code, not ${runtime.channel}`);
799
+ if (!runtime.code?.verified) throw new Error("Profile runtime repin requires a verified checkout runtime");
800
+ if (runtime.code.origin !== "checkout") throw new Error(`Profile runtime repin requires checkout code, not ${runtime.code.origin}`);
801
+ if (!runtime.code.fingerprint || !/^[a-f0-9]{64}$/i.test(runtime.code.fingerprint)) {
802
+ throw new Error("Profile runtime repin requires a valid executing code fingerprint");
803
+ }
804
+ if (!checkoutRoot.trim()) throw new Error("Profile runtime repin requires --checkout-root");
805
+ const intendedRoot = realpathSync(resolve3(checkoutRoot));
806
+ if (!statSync3(intendedRoot).isDirectory()) throw new Error(`Profile runtime repin checkout root is not a directory: ${intendedRoot}`);
807
+ const executingRoot = realpathSync(runtime.code.root);
808
+ if (intendedRoot !== executingRoot) {
809
+ throw new Error(`Profile runtime repin checkout root ${intendedRoot} does not match executing code root ${executingRoot}`);
810
+ }
811
+ const profile = resolveLineageProfile(selector);
812
+ if (profile.environment !== "development") throw new Error(`Profile runtime repin requires a development profile, not ${profile.environment}`);
813
+ if (profile.expected_runtime?.channel !== "dev") throw new Error("Development profile runtime repin requires an existing dev channel pin");
814
+ if (profile.expected_runtime.code_origin !== "checkout") throw new Error("Development profile runtime repin requires an existing checkout origin pin");
815
+ if (!profile.expected_runtime.code_fingerprint) throw new Error("Development profile runtime repin requires an existing code fingerprint pin");
816
+ const manifestPath = profile.manifest_path;
817
+ assertOwnerOnlyPath(dirname3(manifestPath), "profile directory");
818
+ const beforeStats = assertOwnerOnlyPath(manifestPath, "manifest");
819
+ const beforeText = readFileSync3(manifestPath, "utf8");
820
+ const beforeHash = sha256(beforeText);
821
+ let raw;
822
+ try {
823
+ raw = JSON.parse(beforeText);
824
+ } catch (error) {
825
+ throw new Error(`Could not parse profile manifest ${manifestPath}: ${error instanceof Error ? error.message : String(error)}`, { cause: error });
826
+ }
827
+ if (!raw || typeof raw !== "object" || Array.isArray(raw)) throw new Error("Profile manifest must be a JSON object");
828
+ const previousFingerprint = profile.expected_runtime.code_fingerprint;
829
+ const nextExpectedRuntime = {
830
+ channel: "dev",
831
+ code_fingerprint: runtime.code.fingerprint,
832
+ code_origin: "checkout",
833
+ ...runtime.gitSha ? { git_sha: runtime.gitSha } : {},
834
+ version: runtime.version
835
+ };
836
+ const updated = { ...raw, expected_runtime: nextExpectedRuntime };
837
+ const afterText = `${JSON.stringify(updated, null, 2)}
838
+ `;
839
+ const afterHash = sha256(afterText);
840
+ const result = {
841
+ changed: beforeText !== afterText,
842
+ checkout_root: intendedRoot,
843
+ manifest_after_sha256: afterHash,
844
+ manifest_before_sha256: beforeHash,
845
+ manifest_path: manifestPath,
846
+ new_code_fingerprint: runtime.code.fingerprint,
847
+ previous_code_fingerprint: previousFingerprint,
848
+ profile_fingerprint: profile.profile_fingerprint,
849
+ profile_id: profile.profile_id,
850
+ schema_version: lineageProfileRuntimeRepinReceiptSchemaVersion
851
+ };
852
+ if (!result.changed) return result;
853
+ const temporaryPath = join4(dirname3(manifestPath), `.profile.runtime-repin-${randomUUID()}.tmp`);
854
+ let temporaryExists = false;
855
+ try {
856
+ writeFileSync2(temporaryPath, afterText, { encoding: "utf8", flag: "wx", mode: 384 });
857
+ temporaryExists = true;
858
+ chmodSync2(temporaryPath, 384);
859
+ const temporaryFd = openSync(temporaryPath, "r");
860
+ try {
861
+ fsyncSync(temporaryFd);
862
+ } finally {
863
+ closeSync(temporaryFd);
864
+ }
865
+ const preparedReplacement = resolveLineageProfile(temporaryPath);
866
+ if (runtimeRepinInvariant(preparedReplacement) !== runtimeRepinInvariant(profile)) {
867
+ throw new Error("Profile runtime repin would change immutable profile routing identity");
868
+ }
869
+ const currentStats = assertOwnerOnlyPath(manifestPath, "manifest");
870
+ const currentText = readFileSync3(manifestPath, "utf8");
871
+ if (!sameFileIdentity(beforeStats, currentStats) || sha256(currentText) !== beforeHash) {
872
+ throw new Error("Profile manifest changed while runtime repin was being prepared; refusing replacement");
873
+ }
874
+ renameSync(temporaryPath, manifestPath);
875
+ temporaryExists = false;
876
+ chmodSync2(manifestPath, 384);
877
+ const directoryFd = openSync(dirname3(manifestPath), "r");
878
+ try {
879
+ fsyncSync(directoryFd);
880
+ } finally {
881
+ closeSync(directoryFd);
882
+ }
883
+ const replacement = resolveLineageProfile(manifestPath);
884
+ if (runtimeRepinInvariant(replacement) !== runtimeRepinInvariant(profile)) {
885
+ throw new Error("Profile runtime repin changed immutable profile routing identity");
886
+ }
887
+ if (readFileSync3(manifestPath, "utf8") !== afterText) throw new Error("Profile runtime repin replacement bytes do not match the prepared manifest");
888
+ return result;
889
+ } finally {
890
+ if (temporaryExists) rmSync(temporaryPath, { force: true });
891
+ }
892
+ }
764
893
  function assertProfileRuntimePin(profile, runtime) {
765
894
  if (!profile.expected_runtime?.code_fingerprint || !profile.expected_runtime.code_origin) {
766
895
  throw new Error(`Profile ${profile.profile_id} must pin expected_runtime.code_fingerprint and expected_runtime.code_origin before binding or writing`);
@@ -1051,6 +1180,7 @@ async function cloneLineageProfileDatabase(sourceDatabasePath, targetSelector, r
1051
1180
  const source = new DatabaseSync(sourcePath, { readOnly: true });
1052
1181
  try {
1053
1182
  const pagesCopied = await backup(source, temporaryPath);
1183
+ chmodSync2(temporaryPath, 384);
1054
1184
  const cloned = new DatabaseSync(temporaryPath);
1055
1185
  let identity2;
1056
1186
  try {
@@ -1188,7 +1318,7 @@ function cloneLineageProfileAssets(sourceAssetRoot, targetSelector, runtime, con
1188
1318
  const destinationPath = join4(targetRoot, relativePath);
1189
1319
  mkdirSync3(dirname3(destinationPath), { recursive: true, mode: 448 });
1190
1320
  copyFileSync2(sourcePath, destinationPath, fsConstants.COPYFILE_EXCL);
1191
- chmodSync(destinationPath, 384);
1321
+ chmodSync2(destinationPath, 384);
1192
1322
  const sourceHash = fileSha2562(sourcePath);
1193
1323
  const destinationHash = fileSha2562(destinationPath);
1194
1324
  if (sourceHash !== destinationHash) throw new Error("A source asset changed or failed checksum verification during clone");
@@ -1198,7 +1328,7 @@ function cloneLineageProfileAssets(sourceAssetRoot, targetSelector, runtime, con
1198
1328
  treeHash.update(destinationHash);
1199
1329
  treeHash.update("\0");
1200
1330
  }
1201
- chmodSync(targetRoot, 448);
1331
+ chmodSync2(targetRoot, 448);
1202
1332
  const receiptPath = join4(targetRoot, ".lineage-profile-assets.json");
1203
1333
  const result = {
1204
1334
  asset_root: targetRoot,
@@ -1251,7 +1381,7 @@ function errorCode(error) {
1251
1381
  return error && typeof error === "object" && "code" in error ? String(error.code) : void 0;
1252
1382
  }
1253
1383
  function readOwner(lockPath) {
1254
- const stat = lstatSync(lockPath);
1384
+ const stat = lstatSync2(lockPath);
1255
1385
  if (!stat.isDirectory() || stat.isSymbolicLink()) {
1256
1386
  throw new Error(`Writer lease path is not a safe directory: ${lockPath}; manual inspection is required`);
1257
1387
  }
@@ -1300,7 +1430,7 @@ function reclaimDeadOwner(lockPath, owner) {
1300
1430
  const tokenFence = createHash3("sha256").update(owner.token).digest("hex").slice(0, 24);
1301
1431
  const tombstone = `${lockPath}.stale-${owner.pid}-${tokenFence}`;
1302
1432
  try {
1303
- renameSync(lockPath, tombstone);
1433
+ renameSync2(lockPath, tombstone);
1304
1434
  } catch (error) {
1305
1435
  if (errorCode(error) === "ENOENT") return;
1306
1436
  throw error;
@@ -4783,7 +4913,7 @@ function getLineageSelectionPacket(project, options = {}) {
4783
4913
  import { createHash as createHash6 } from "node:crypto";
4784
4914
  import { spawnSync as spawnSync2 } from "node:child_process";
4785
4915
  import { createRequire as createRequire4 } from "node:module";
4786
- import { existsSync as existsSync10, lstatSync as lstatSync2, readFileSync as readFileSync5, readdirSync as readdirSync3, readlinkSync, realpathSync as realpathSync3, statSync as statSync6 } from "node:fs";
4916
+ import { existsSync as existsSync10, lstatSync as lstatSync3, readFileSync as readFileSync5, readdirSync as readdirSync3, readlinkSync, realpathSync as realpathSync3, statSync as statSync6 } from "node:fs";
4787
4917
  import { dirname as dirname5, isAbsolute as isAbsolute3, join as join9, resolve as resolve7 } from "node:path";
4788
4918
  import { fileURLToPath as fileURLToPath2 } from "node:url";
4789
4919
 
@@ -4810,7 +4940,7 @@ function executingCodeRoot() {
4810
4940
  return canonicalRoot(root);
4811
4941
  }
4812
4942
  var codeRoot = executingCodeRoot();
4813
- function sha256(value) {
4943
+ function sha2562(value) {
4814
4944
  return createHash6("sha256").update(value).digest("hex");
4815
4945
  }
4816
4946
  function canonicalRoot(root) {
@@ -4849,7 +4979,7 @@ function untrackedFingerprint(root, status) {
4849
4979
  hash.update(relativePath);
4850
4980
  const path = join9(root, relativePath);
4851
4981
  try {
4852
- const stat = lstatSync2(path);
4982
+ const stat = lstatSync3(path);
4853
4983
  if (stat.isSymbolicLink()) hash.update(readlinkSync(path));
4854
4984
  else if (stat.isFile()) hash.update(readFileSync5(path));
4855
4985
  else hash.update(`[${stat.mode}:${stat.size}]`);
@@ -4871,12 +5001,12 @@ function checkoutCodeIdentity(root, channel, version) {
4871
5001
  if (status === void 0 || diff === void 0) errors.push("Checkout dirty state could not be inspected");
4872
5002
  if (channel !== "dev") errors.push(`Checkout code may run only as dev, not ${channel}`);
4873
5003
  const dirty = Boolean(status);
4874
- const sourceFingerprint = sha256(`${gitSha || "unknown"}\0${sha256(diff || "")}\0${untrackedFingerprint(root, status || "")}`);
5004
+ const sourceFingerprint = sha2562(`${gitSha || "unknown"}\0${sha2562(diff || "")}\0${untrackedFingerprint(root, status || "")}`);
4875
5005
  return {
4876
5006
  channel,
4877
5007
  dirty,
4878
5008
  errors,
4879
- fingerprint: sha256(JSON.stringify({ channel, dirty, git_sha: gitSha, origin: "checkout", root, source_fingerprint: sourceFingerprint, version })),
5009
+ fingerprint: sha2562(JSON.stringify({ channel, dirty, git_sha: gitSha, origin: "checkout", root, source_fingerprint: sourceFingerprint, version })),
4880
5010
  git_sha: gitSha,
4881
5011
  origin: "checkout",
4882
5012
  package_version: version,
@@ -4886,7 +5016,7 @@ function checkoutCodeIdentity(root, channel, version) {
4886
5016
  };
4887
5017
  }
4888
5018
  function buildFingerprint(build) {
4889
- return sha256(JSON.stringify({
5019
+ return sha2562(JSON.stringify({
4890
5020
  package_name: build.package_name,
4891
5021
  package_version: build.package_version,
4892
5022
  schema_version: build.schema_version,
@@ -4986,7 +5116,7 @@ function packageCodeIdentity(root, channel, info, receiptPath) {
4986
5116
  channel,
4987
5117
  dirty: build?.source_dirty,
4988
5118
  errors,
4989
- fingerprint: sha256(JSON.stringify({
5119
+ fingerprint: sha2562(JSON.stringify({
4990
5120
  build_fingerprint: build?.build_fingerprint,
4991
5121
  channel,
4992
5122
  install_integrity: install?.package_integrity,
@@ -5014,7 +5144,7 @@ function getLineageCodeIdentity(channel, options = {}) {
5014
5144
  return {
5015
5145
  channel,
5016
5146
  errors,
5017
- fingerprint: sha256(JSON.stringify({ channel, origin: "unknown", root, version: info.version })),
5147
+ fingerprint: sha2562(JSON.stringify({ channel, origin: "unknown", root, version: info.version })),
5018
5148
  origin: "unknown",
5019
5149
  package_version: info.version,
5020
5150
  root,
@@ -5309,6 +5439,7 @@ Usage:
5309
5439
  ${config.binName} profile bind --profile <id-or-manifest> --confirm-write [--json]
5310
5440
  ${config.binName} profile clone --source-db <snapshot-source> --target-profile <id-or-manifest> --confirm-write [--json]
5311
5441
  ${config.binName} profile clone-assets --source-asset-root <path> --target-profile <id-or-manifest> --confirm-write [--json]
5442
+ ${config.binName} profile repin-runtime --profile <development-profile> --checkout-root <path> --confirm-write [--json]
5312
5443
  ${config.binName} runtime info [--json]
5313
5444
  ${config.binName} runtime doctor [--json]
5314
5445
  ${config.binName} next [--project <project>] [--root <asset-id>] [--db <path>] [--json]
@@ -5715,6 +5846,18 @@ function runLineageProfileCommand(config, command, args) {
5715
5846
  const selector = profileSelector(args);
5716
5847
  if (!selector) throw new Error(`lineage profile ${command} requires --profile or LINEAGE_PROFILE`);
5717
5848
  if (command === "doctor") return doctorLineageProfile(selector, runtimeIdentity);
5849
+ if (command === "repin-runtime") {
5850
+ const checkoutRoot = readOption(args, "--checkout-root");
5851
+ if (!checkoutRoot) throw new Error("Profile runtime repin requires --checkout-root");
5852
+ if (!args.includes("--confirm-write")) throw new Error("Profile runtime repin requires --confirm-write");
5853
+ const profile = resolveLineageProfile(selector);
5854
+ const writerLease = acquireProfileWriterLease(profile, config.channel, "cli");
5855
+ try {
5856
+ return repinLineageDevelopmentProfileRuntime(selector, checkoutRoot, runtimeIdentity, true);
5857
+ } finally {
5858
+ writerLease.release();
5859
+ }
5860
+ }
5718
5861
  if (command === "bind") {
5719
5862
  if (!args.includes("--confirm-write")) throw new Error("Profile bind requires --confirm-write");
5720
5863
  const profile = resolveLineageProfile(selector);
@@ -5740,6 +5883,9 @@ function printProfileResult(result, json) {
5740
5883
  } else if (result.schema_version === "lineage.profile_clone_receipt.v1") {
5741
5884
  console.log(`Cloned ${result.source_database_path} to ${result.database_path} for ${result.target_identity.profile_id}`);
5742
5885
  console.log(`Receipt: ${result.receipt_path}`);
5886
+ } else if (result.schema_version === "lineage.profile_runtime_repin_receipt.v1") {
5887
+ console.log(`${result.changed ? "Repinned" : "Already pinned"} ${result.profile_id} to checkout ${result.checkout_root}`);
5888
+ console.log(`Code fingerprint: ${result.new_code_fingerprint}`);
5743
5889
  } else {
5744
5890
  console.log(`Cloned ${result.files_copied} referenced asset file(s) into ${result.asset_root}`);
5745
5891
  console.log(`Receipt: ${result.receipt_path}`);