@mean-weasel/lineage 0.1.14 → 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.
- package/CHANGELOG.md +6 -0
- package/README.md +37 -3
- package/dist/cli/lineage-dev.js +161 -15
- package/dist/cli/lineage-dev.js.map +3 -3
- package/dist/cli/lineage-preview.js +161 -15
- package/dist/cli/lineage-preview.js.map +3 -3
- package/dist/cli/lineage.js +161 -15
- package/dist/cli/lineage.js.map +3 -3
- package/dist/runtime-build.json +4 -4
- package/dist/server.js +13 -7
- package/dist/server.js.map +3 -3
- package/dist/web/assets/{index-3uJbXqPS.css → app-CDxSvoD3.css} +1 -1
- package/dist/web/assets/app-CYUgf6rX.js +16 -0
- package/dist/web/assets/jsx-runtime-_Rdg6et1.js +8 -0
- package/dist/web/assets/landing-D3Y679_B.css +1 -0
- package/dist/web/assets/landing-ii8AH_DG.js +1 -0
- package/dist/web/index.html +3 -2
- package/dist/web/landing/index.html +19 -0
- package/package.json +2 -1
- package/dist/web/assets/index-DJ5UR_oa.js +0 -23
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,11 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.1.15
|
|
4
|
+
|
|
5
|
+
- Add a subtle root marker that makes the starting point of each lineage tree immediately recognizable.
|
|
6
|
+
- Add full-asset previews on node hover and keyboard focus while preserving double-click for full details and attempt history.
|
|
7
|
+
- Add a browser-local setting to disable hover previews, plus refresh-state safeguards that keep dismissed previews closed during background updates.
|
|
8
|
+
|
|
3
9
|
## 0.1.14
|
|
4
10
|
|
|
5
11
|
- Fix isolated channel installs against npm clients that return registry integrity as a flat `dist.integrity` field, while rejecting missing or conflicting identity metadata.
|
package/README.md
CHANGED
|
@@ -19,7 +19,7 @@ lineage-channel install preview
|
|
|
19
19
|
|
|
20
20
|
By default the installer writes immutable version/integrity roots beneath
|
|
21
21
|
`~/Library/Application Support/Lineage/runtimes`, plus channel-qualified
|
|
22
|
-
launchers
|
|
22
|
+
launchers under `~/.local/bin`. Every launcher is pinned to an absolute
|
|
23
23
|
package root and install receipt; it does not select code from `PATH` at runtime.
|
|
24
24
|
|
|
25
25
|
The three exact channel meanings are:
|
|
@@ -131,6 +131,30 @@ matching legacy identity, but refuses a conflicting identity. An unprofiled
|
|
|
131
131
|
CLI or service is `legacy-unbound` and read-only; mutating CLI commands and HTTP
|
|
132
132
|
methods fail with a profile-required error instead of creating a database.
|
|
133
133
|
|
|
134
|
+
Checkout fingerprints intentionally change as tracked or untracked source
|
|
135
|
+
changes. To repin an existing development profile, first stop its managed
|
|
136
|
+
service, then run the guarded checkout-only workflow:
|
|
137
|
+
|
|
138
|
+
```bash
|
|
139
|
+
make repin-dev LINEAGE_DEV_PROFILE=team-development
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
The underlying explicit command is:
|
|
143
|
+
|
|
144
|
+
```bash
|
|
145
|
+
npm run lineage:dev -- profile repin-runtime \
|
|
146
|
+
--profile team-development --checkout-root "$PWD" --confirm-write --json
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
Repin accepts only a verified dev checkout whose canonical root equals
|
|
150
|
+
`--checkout-root`, an owner-only development manifest already pinned to
|
|
151
|
+
dev/checkout, and an available profile writer lease. It atomically replaces
|
|
152
|
+
only `expected_runtime`; profile/database identity, paths, environment,
|
|
153
|
+
service origin, and migrations stay unchanged. Production, preview, package,
|
|
154
|
+
wrong-root, unsafe-manifest, unconfirmed, and active-service cases fail closed.
|
|
155
|
+
The command may prepare a new structurally valid development manifest before
|
|
156
|
+
its database and asset root are cloned.
|
|
157
|
+
|
|
134
158
|
To create a realistic preview or development database without copying a live
|
|
135
159
|
SQLite file directly, define a new non-production target profile whose database
|
|
136
160
|
does not exist, then run:
|
|
@@ -321,8 +345,12 @@ npx @mean-weasel/lineage-plugin-installer install --channel latest
|
|
|
321
345
|
|
|
322
346
|
The installer verifies the plugin artifact checksum and rejects plugin manifests
|
|
323
347
|
whose version or `lineage.version` does not exactly match the resolved Lineage
|
|
324
|
-
package version.
|
|
325
|
-
|
|
348
|
+
package version. It then creates a dedicated marketplace under the active
|
|
349
|
+
`CODEX_HOME`, registers that marketplace with supported `codex plugin`
|
|
350
|
+
commands, installs the plugin, and verifies that Codex reports the exact version
|
|
351
|
+
as installed and enabled. Use `--target-dir <path>` for a files-only install or
|
|
352
|
+
`--dry-run` for a non-mutating activation plan. The plugin artifact and installer
|
|
353
|
+
package are released from this public repository.
|
|
326
354
|
|
|
327
355
|
Every Lineage release preflights the current operator skill, packs and checksums
|
|
328
356
|
the exact matching plugin, installs that artifact into a temporary Codex plugin
|
|
@@ -331,6 +359,12 @@ or dist-tag mutation is allowed. Release status reports the local version lock
|
|
|
331
359
|
and GitHub assets. The installed skill requires isolated channel launchers,
|
|
332
360
|
named profiles, managed service identity status, and profile-only writes.
|
|
333
361
|
|
|
362
|
+
The checkout also exposes `.agents/plugins/marketplace.json`, which points only
|
|
363
|
+
at the checkout plugin and marks it installed by default for new development
|
|
364
|
+
tasks. That repo marketplace is separate from the installer-managed marketplace
|
|
365
|
+
under `CODEX_HOME`; restart Codex and use a brand-new task when validating either
|
|
366
|
+
activation path.
|
|
367
|
+
|
|
334
368
|
## Local Development
|
|
335
369
|
|
|
336
370
|
```bash
|
package/dist/cli/lineage-dev.js
CHANGED
|
@@ -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
|
-
|
|
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
|
-
|
|
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 =
|
|
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
|
-
|
|
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
|
|
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
|
|
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 =
|
|
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 =
|
|
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:
|
|
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
|
|
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:
|
|
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:
|
|
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}`);
|