@mean-weasel/lineage 0.1.12 → 0.1.14
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 +12 -0
- package/README.md +152 -56
- package/dist/cli/lineage-channel.js +303 -0
- package/dist/cli/lineage-channel.js.map +7 -0
- package/dist/cli/lineage-dev.js +900 -220
- package/dist/cli/lineage-dev.js.map +4 -4
- package/dist/cli/lineage-preview.js +6182 -0
- package/dist/cli/lineage-preview.js.map +7 -0
- package/dist/cli/lineage.js +891 -216
- package/dist/cli/lineage.js.map +4 -4
- package/dist/cli/managed-service.js +430 -0
- package/dist/cli/managed-service.js.map +7 -0
- package/dist/runtime-build.json +9 -0
- package/dist/server.js +452 -164
- package/dist/server.js.map +4 -4
- package/dist/web/assets/{index-OkymIyPO.js → index-DJ5UR_oa.js} +1 -1
- package/dist/web/index.html +1 -1
- package/package.json +22 -9
package/dist/server.js
CHANGED
|
@@ -5,8 +5,8 @@ import { existsSync as existsSync14 } from "node:fs";
|
|
|
5
5
|
import { join as join14 } from "node:path";
|
|
6
6
|
|
|
7
7
|
// src/server/assetCore.ts
|
|
8
|
-
import { existsSync as existsSync6, mkdirSync as mkdirSync5, readdirSync as readdirSync2, readFileSync as readFileSync4, unlinkSync, writeFileSync as
|
|
9
|
-
import { dirname as dirname4, join as join6, resolve as
|
|
8
|
+
import { existsSync as existsSync6, mkdirSync as mkdirSync5, readdirSync as readdirSync2, readFileSync as readFileSync4, unlinkSync, writeFileSync as writeFileSync3 } from "node:fs";
|
|
9
|
+
import { dirname as dirname4, join as join6, resolve as resolve4 } from "node:path";
|
|
10
10
|
import { spawnSync } from "node:child_process";
|
|
11
11
|
import { fileURLToPath } from "node:url";
|
|
12
12
|
|
|
@@ -284,13 +284,31 @@ function createS3StorageAdapter(deps) {
|
|
|
284
284
|
// src/server/assetLineageDb.ts
|
|
285
285
|
import { createRequire as createRequire2 } from "node:module";
|
|
286
286
|
import { existsSync as existsSync5, mkdirSync as mkdirSync4 } from "node:fs";
|
|
287
|
-
import { join as join5
|
|
287
|
+
import { join as join5 } from "node:path";
|
|
288
|
+
|
|
289
|
+
// src/server/profileWriterLease.ts
|
|
290
|
+
import { createHash as createHash3, randomUUID as randomUUID2, timingSafeEqual } from "node:crypto";
|
|
291
|
+
import { existsSync as existsSync4, lstatSync, mkdirSync as mkdirSync3, readFileSync as readFileSync3, renameSync, rmSync as rmSync2, writeFileSync as writeFileSync2 } from "node:fs";
|
|
292
|
+
import { dirname as dirname3, join as join4, resolve as resolve3 } from "node:path";
|
|
288
293
|
|
|
289
294
|
// src/server/lineageProfiles.ts
|
|
295
|
+
import { createHash as createHash2, randomUUID } from "node:crypto";
|
|
290
296
|
import { createRequire } from "node:module";
|
|
291
|
-
import {
|
|
297
|
+
import {
|
|
298
|
+
chmodSync,
|
|
299
|
+
constants as fsConstants,
|
|
300
|
+
copyFileSync as copyFileSync2,
|
|
301
|
+
existsSync as existsSync3,
|
|
302
|
+
linkSync,
|
|
303
|
+
mkdirSync as mkdirSync2,
|
|
304
|
+
readFileSync as readFileSync2,
|
|
305
|
+
realpathSync,
|
|
306
|
+
rmSync,
|
|
307
|
+
statSync as statSync3,
|
|
308
|
+
writeFileSync
|
|
309
|
+
} from "node:fs";
|
|
292
310
|
import { homedir, platform } from "node:os";
|
|
293
|
-
import { dirname as dirname2, isAbsolute, join as join3, resolve as resolve2 } from "node:path";
|
|
311
|
+
import { dirname as dirname2, isAbsolute, join as join3, relative as relative2, resolve as resolve2 } from "node:path";
|
|
294
312
|
|
|
295
313
|
// src/shared/lineageProfileTypes.ts
|
|
296
314
|
var lineageProfileSchemaVersion = "lineage.profile.v1";
|
|
@@ -346,6 +364,16 @@ function validateChannel(value) {
|
|
|
346
364
|
if (value === "stable" || value === "preview" || value === "dev") return value;
|
|
347
365
|
throw new Error(`Invalid expected runtime channel: ${String(value)}`);
|
|
348
366
|
}
|
|
367
|
+
function validateCodeOrigin(value) {
|
|
368
|
+
if (value === void 0) return void 0;
|
|
369
|
+
if (value === "checkout" || value === "package") return value;
|
|
370
|
+
throw new Error(`Invalid expected runtime code_origin: ${String(value)}`);
|
|
371
|
+
}
|
|
372
|
+
function validateFingerprint(value, field) {
|
|
373
|
+
if (value === void 0) return void 0;
|
|
374
|
+
if (!/^[a-f0-9]{64}$/i.test(value)) throw new Error(`Profile ${field} must be a 64-character SHA-256 fingerprint`);
|
|
375
|
+
return value.toLowerCase();
|
|
376
|
+
}
|
|
349
377
|
function resolveManifestPath(manifestPath, value) {
|
|
350
378
|
return resolve2(dirname2(manifestPath), value);
|
|
351
379
|
}
|
|
@@ -361,9 +389,6 @@ function validateServiceOrigin(value) {
|
|
|
361
389
|
if (parsed.username || parsed.password || parsed.pathname !== "/" || parsed.search || parsed.hash) {
|
|
362
390
|
throw new Error("Profile service_origin must contain only scheme, host, and port");
|
|
363
391
|
}
|
|
364
|
-
const hostname = parsed.hostname.replace(/^\[|\]$/g, "").toLowerCase();
|
|
365
|
-
const isLocal = hostname === "localhost" || hostname.endsWith(".localhost") || hostname === "::1" || /^127(?:\.\d{1,3}){3}$/.test(hostname);
|
|
366
|
-
if (!isLocal) throw new Error("Profile service_origin must use a loopback or localhost host");
|
|
367
392
|
return parsed.origin;
|
|
368
393
|
}
|
|
369
394
|
function resolveLineageProfile(selector) {
|
|
@@ -391,6 +416,8 @@ function resolveLineageProfile(selector) {
|
|
|
391
416
|
const expected = expectedRaw;
|
|
392
417
|
const expectedGitSha = expected ? optionalString(expected, "git_sha") : void 0;
|
|
393
418
|
const expectedVersion = expected ? optionalString(expected, "version") : void 0;
|
|
419
|
+
const expectedCodeFingerprint = validateFingerprint(expected ? optionalString(expected, "code_fingerprint") : void 0, "expected_runtime.code_fingerprint");
|
|
420
|
+
const expectedCodeOrigin = validateCodeOrigin(expected?.code_origin);
|
|
394
421
|
const migrationsRaw = record.required_schema_migrations;
|
|
395
422
|
if (migrationsRaw !== void 0 && (!Array.isArray(migrationsRaw) || migrationsRaw.some((value) => typeof value !== "string" || !value.trim()))) {
|
|
396
423
|
throw new Error("Profile required_schema_migrations must be an array of non-empty strings");
|
|
@@ -407,6 +434,8 @@ function resolveLineageProfile(selector) {
|
|
|
407
434
|
...expected ? {
|
|
408
435
|
expected_runtime: {
|
|
409
436
|
...expectedChannel ? { channel: expectedChannel } : {},
|
|
437
|
+
...expectedCodeFingerprint ? { code_fingerprint: expectedCodeFingerprint } : {},
|
|
438
|
+
...expectedCodeOrigin ? { code_origin: expectedCodeOrigin } : {},
|
|
410
439
|
...expectedGitSha ? { git_sha: expectedGitSha } : {},
|
|
411
440
|
...expectedVersion ? { version: expectedVersion } : {}
|
|
412
441
|
}
|
|
@@ -416,7 +445,31 @@ function resolveLineageProfile(selector) {
|
|
|
416
445
|
schema_version: lineageProfileSchemaVersion,
|
|
417
446
|
service_origin: validateServiceOrigin(requiredString(record, "service_origin"))
|
|
418
447
|
};
|
|
419
|
-
return { ...manifest, manifest_path: manifestPath };
|
|
448
|
+
return { ...manifest, manifest_path: manifestPath, profile_fingerprint: lineageProfileFingerprint(manifest) };
|
|
449
|
+
}
|
|
450
|
+
function lineageProfileFingerprint(profile) {
|
|
451
|
+
return createHash2("sha256").update(JSON.stringify({
|
|
452
|
+
asset_root: resolve2(profile.asset_root),
|
|
453
|
+
database_path: resolve2(profile.database_path),
|
|
454
|
+
environment: profile.environment,
|
|
455
|
+
profile_id: profile.profile_id,
|
|
456
|
+
schema_version: profile.schema_version,
|
|
457
|
+
service_origin: profile.service_origin
|
|
458
|
+
})).digest("hex");
|
|
459
|
+
}
|
|
460
|
+
function assertProfileRuntimePin(profile, runtime) {
|
|
461
|
+
if (!profile.expected_runtime?.code_fingerprint || !profile.expected_runtime.code_origin) {
|
|
462
|
+
throw new Error(`Profile ${profile.profile_id} must pin expected_runtime.code_fingerprint and expected_runtime.code_origin before binding or writing`);
|
|
463
|
+
}
|
|
464
|
+
if (!runtime?.code?.verified) {
|
|
465
|
+
throw new Error(`Profile ${profile.profile_id} requires a verified runtime code identity before binding or writing`);
|
|
466
|
+
}
|
|
467
|
+
if (runtime.code.fingerprint !== profile.expected_runtime.code_fingerprint) {
|
|
468
|
+
throw new Error(`Profile ${profile.profile_id} expects code fingerprint ${profile.expected_runtime.code_fingerprint}, found ${runtime.code.fingerprint}`);
|
|
469
|
+
}
|
|
470
|
+
if (runtime.code.origin !== profile.expected_runtime.code_origin) {
|
|
471
|
+
throw new Error(`Profile ${profile.profile_id} expects code origin ${profile.expected_runtime.code_origin}, found ${runtime.code.origin}`);
|
|
472
|
+
}
|
|
420
473
|
}
|
|
421
474
|
function assertProfileChannel(profile, channel) {
|
|
422
475
|
const requiredChannel = environmentChannel(profile.environment);
|
|
@@ -429,12 +482,9 @@ function assertProfileChannel(profile, channel) {
|
|
|
429
482
|
function tableExists(database, table) {
|
|
430
483
|
return Boolean(database.prepare("select name from sqlite_master where type = 'table' and name = ?").get(table));
|
|
431
484
|
}
|
|
432
|
-
function
|
|
433
|
-
if (!
|
|
434
|
-
|
|
435
|
-
const normalizedActual = actual.toLowerCase();
|
|
436
|
-
const shorterLength = Math.min(normalizedExpected.length, normalizedActual.length);
|
|
437
|
-
return shorterLength >= 12 && normalizedExpected.slice(0, shorterLength) === normalizedActual.slice(0, shorterLength);
|
|
485
|
+
function tableColumns(database, table) {
|
|
486
|
+
if (!tableExists(database, table)) return /* @__PURE__ */ new Set();
|
|
487
|
+
return new Set(database.prepare(`pragma table_info(${table})`).all().map((row) => row.name));
|
|
438
488
|
}
|
|
439
489
|
function inspectDatabase(profile) {
|
|
440
490
|
const result = {
|
|
@@ -448,11 +498,14 @@ function inspectDatabase(profile) {
|
|
|
448
498
|
const database = new DatabaseSync(profile.database_path, { readOnly: true });
|
|
449
499
|
try {
|
|
450
500
|
if (tableExists(database, "lineage_profile_identity")) {
|
|
451
|
-
const
|
|
501
|
+
const columns = tableColumns(database, "lineage_profile_identity");
|
|
502
|
+
const fingerprintExpression = columns.has("profile_fingerprint") ? "profile_fingerprint" : "'' as profile_fingerprint";
|
|
503
|
+
const rows = database.prepare(`select profile_id, environment, bound_at, ${fingerprintExpression} from lineage_profile_identity`).all();
|
|
452
504
|
if (rows.length === 1) {
|
|
453
505
|
const identity = {
|
|
454
506
|
bound_at: typeof rows[0].bound_at === "string" ? rows[0].bound_at : void 0,
|
|
455
507
|
environment: String(rows[0].environment),
|
|
508
|
+
profile_fingerprint: typeof rows[0].profile_fingerprint === "string" ? rows[0].profile_fingerprint : "",
|
|
456
509
|
profile_id: String(rows[0].profile_id)
|
|
457
510
|
};
|
|
458
511
|
result.identity = identity;
|
|
@@ -476,7 +529,14 @@ function doctorLineageProfile(selector, runtime) {
|
|
|
476
529
|
const result = {
|
|
477
530
|
checks,
|
|
478
531
|
ok: false,
|
|
479
|
-
runtime: {
|
|
532
|
+
runtime: {
|
|
533
|
+
channel: runtime.channel,
|
|
534
|
+
code_fingerprint: runtime.code?.fingerprint,
|
|
535
|
+
code_origin: runtime.code?.origin,
|
|
536
|
+
code_verified: runtime.code?.verified,
|
|
537
|
+
git_sha: runtime.gitSha,
|
|
538
|
+
version: runtime.version
|
|
539
|
+
},
|
|
480
540
|
schema_version: lineageProfileDoctorSchemaVersion
|
|
481
541
|
};
|
|
482
542
|
let profile;
|
|
@@ -495,12 +555,18 @@ function doctorLineageProfile(selector, runtime) {
|
|
|
495
555
|
} catch (error) {
|
|
496
556
|
checks.push({ id: "runtime_channel", message: error instanceof Error ? error.message : String(error), status: "fail" });
|
|
497
557
|
}
|
|
558
|
+
try {
|
|
559
|
+
assertProfileRuntimePin(profile, runtime);
|
|
560
|
+
checks.push({ id: "runtime_code", message: `Verified ${runtime.code.origin} code ${runtime.code.fingerprint}`, status: "pass" });
|
|
561
|
+
} catch (error) {
|
|
562
|
+
checks.push({ id: "runtime_code", message: error instanceof Error ? error.message : String(error), status: "fail" });
|
|
563
|
+
}
|
|
498
564
|
if (profile.expected_runtime?.version && profile.expected_runtime.version !== runtime.version) {
|
|
499
565
|
checks.push({ id: "runtime_version", message: `Expected version ${profile.expected_runtime.version}, found ${runtime.version}`, status: "fail" });
|
|
500
566
|
} else {
|
|
501
567
|
checks.push({ id: "runtime_version", message: `Runtime version ${runtime.version}`, status: "pass" });
|
|
502
568
|
}
|
|
503
|
-
if (profile.expected_runtime?.git_sha &&
|
|
569
|
+
if (profile.expected_runtime?.git_sha && profile.expected_runtime.git_sha !== runtime.gitSha) {
|
|
504
570
|
checks.push({ id: "runtime_git_sha", message: `Expected Git SHA ${profile.expected_runtime.git_sha}, found ${runtime.gitSha || "unavailable"}`, status: "fail" });
|
|
505
571
|
} else if (profile.expected_runtime?.git_sha) {
|
|
506
572
|
checks.push({ id: "runtime_git_sha", message: `Git SHA ${runtime.gitSha}`, status: "pass" });
|
|
@@ -523,10 +589,10 @@ function doctorLineageProfile(selector, runtime) {
|
|
|
523
589
|
if (database.error) checks.push({ id: "database_read", message: database.error, status: "fail" });
|
|
524
590
|
if (!database.identity) {
|
|
525
591
|
checks.push({ id: "database_identity", message: "Database is not bound to a Lineage profile", status: "fail" });
|
|
526
|
-
} else if (database.identity.profile_id !== profile.profile_id || database.identity.environment !== profile.environment) {
|
|
592
|
+
} else if (database.identity.profile_id !== profile.profile_id || database.identity.environment !== profile.environment || database.identity.profile_fingerprint !== profile.profile_fingerprint) {
|
|
527
593
|
checks.push({
|
|
528
594
|
id: "database_identity",
|
|
529
|
-
message: `Database identity ${database.identity.profile_id}/${database.identity.environment} does not match ${profile.profile_id}/${profile.environment}`,
|
|
595
|
+
message: `Database identity ${database.identity.profile_id}/${database.identity.environment}/${database.identity.profile_fingerprint || "no-fingerprint"} does not match ${profile.profile_id}/${profile.environment}/${profile.profile_fingerprint}`,
|
|
530
596
|
status: "fail"
|
|
531
597
|
});
|
|
532
598
|
} else {
|
|
@@ -549,6 +615,7 @@ function runtimeProfileIdentity(channel) {
|
|
|
549
615
|
return {
|
|
550
616
|
bound: true,
|
|
551
617
|
environment,
|
|
618
|
+
fingerprint: process.env.LINEAGE_PROFILE_FINGERPRINT,
|
|
552
619
|
id,
|
|
553
620
|
manifest_path: process.env.LINEAGE_PROFILE_MANIFEST,
|
|
554
621
|
service_origin: process.env.LINEAGE_PROFILE_SERVICE_ORIGIN
|
|
@@ -558,12 +625,12 @@ function runtimeProfileIdentity(channel) {
|
|
|
558
625
|
bound: false,
|
|
559
626
|
environment: channelEnvironment(channel),
|
|
560
627
|
id: "legacy-unbound",
|
|
561
|
-
warning: id || environment || process.env.LINEAGE_PROFILE_MANIFEST ? "Invalid unbound runtime: derived profile identity was supplied without a resolved LINEAGE_PROFILE selector." : "Legacy unbound runtime: database and asset paths are not protected by a named profile."
|
|
628
|
+
warning: id || environment || process.env.LINEAGE_PROFILE_FINGERPRINT || process.env.LINEAGE_PROFILE_MANIFEST ? "Invalid unbound runtime: derived profile identity was supplied without a resolved LINEAGE_PROFILE selector." : "Legacy unbound runtime: database and asset paths are not protected by a named profile."
|
|
562
629
|
};
|
|
563
630
|
}
|
|
564
631
|
function assertRuntimeProfileSafety(channel) {
|
|
565
632
|
const hasDerivedIdentity = Boolean(
|
|
566
|
-
process.env.LINEAGE_PROFILE_ID || process.env.LINEAGE_PROFILE_ENVIRONMENT || process.env.LINEAGE_PROFILE_MANIFEST || process.env.LINEAGE_PROFILE_SERVICE_ORIGIN
|
|
633
|
+
process.env.LINEAGE_PROFILE_ID || process.env.LINEAGE_PROFILE_ENVIRONMENT || process.env.LINEAGE_PROFILE_FINGERPRINT || process.env.LINEAGE_PROFILE_MANIFEST || process.env.LINEAGE_PROFILE_SERVICE_ORIGIN
|
|
567
634
|
);
|
|
568
635
|
if (hasDerivedIdentity && !process.env.LINEAGE_PROFILE) {
|
|
569
636
|
throw new Error("Derived Lineage profile identity requires LINEAGE_PROFILE; start through the Lineage CLI with --profile");
|
|
@@ -574,16 +641,6 @@ function assertRuntimeProfileSafety(channel) {
|
|
|
574
641
|
}
|
|
575
642
|
}
|
|
576
643
|
function assertUnselectedDatabaseIsUnbound(runtime) {
|
|
577
|
-
if (runtime.database.exists && runtime.database.error) {
|
|
578
|
-
throw new Error(
|
|
579
|
-
`Database ${runtime.database.path} identity could not be verified: ${runtime.database.error}; refusing unselected access`
|
|
580
|
-
);
|
|
581
|
-
}
|
|
582
|
-
if (runtime.schema.profile_identity_rows !== void 0 && runtime.schema.profile_identity_rows !== 1) {
|
|
583
|
-
throw new Error(
|
|
584
|
-
`Database ${runtime.database.path} has invalid Lineage profile identity row count ${runtime.schema.profile_identity_rows}; refusing unselected access`
|
|
585
|
-
);
|
|
586
|
-
}
|
|
587
644
|
if (!runtime.schema.profile_id) return;
|
|
588
645
|
const environment = runtime.schema.profile_environment || "unknown";
|
|
589
646
|
throw new Error(
|
|
@@ -592,15 +649,15 @@ function assertUnselectedDatabaseIsUnbound(runtime) {
|
|
|
592
649
|
}
|
|
593
650
|
function assertResolvedRuntimeProfileEnvironment(profile) {
|
|
594
651
|
const serviceUrl = new URL(profile.service_origin);
|
|
595
|
-
const serviceHost = serviceUrl.hostname.startsWith("[") && serviceUrl.hostname.endsWith("]") ? serviceUrl.hostname.slice(1, -1) : serviceUrl.hostname;
|
|
596
652
|
const expected = /* @__PURE__ */ new Map([
|
|
597
653
|
["LINEAGE_PROFILE_ID", profile.profile_id],
|
|
598
654
|
["LINEAGE_PROFILE_ENVIRONMENT", profile.environment],
|
|
655
|
+
["LINEAGE_PROFILE_FINGERPRINT", profile.profile_fingerprint],
|
|
599
656
|
["LINEAGE_PROFILE_MANIFEST", profile.manifest_path],
|
|
600
657
|
["LINEAGE_PROFILE_SERVICE_ORIGIN", profile.service_origin],
|
|
601
658
|
["LINEAGE_DB", profile.database_path],
|
|
602
659
|
["LINEAGE_ASSET_ROOT", profile.asset_root],
|
|
603
|
-
["HOST",
|
|
660
|
+
["HOST", serviceUrl.hostname],
|
|
604
661
|
["PORT", serviceUrl.port]
|
|
605
662
|
]);
|
|
606
663
|
const conflicts = [];
|
|
@@ -616,9 +673,6 @@ function assertResolvedRuntimeProfileEnvironment(profile) {
|
|
|
616
673
|
}
|
|
617
674
|
|
|
618
675
|
// src/server/profileWriterLease.ts
|
|
619
|
-
import { createHash as createHash2, randomUUID, timingSafeEqual } from "node:crypto";
|
|
620
|
-
import { existsSync as existsSync4, lstatSync, mkdirSync as mkdirSync3, readFileSync as readFileSync3, realpathSync, renameSync, rmSync, writeFileSync } from "node:fs";
|
|
621
|
-
import { basename as basename3, dirname as dirname3, join as join4, resolve as resolve3 } from "node:path";
|
|
622
676
|
var writerLeaseSchemaVersion = "lineage.profile_writer_lease.v1";
|
|
623
677
|
var ownerFileName = "owner.json";
|
|
624
678
|
var ProfileWriterLeaseConflictError = class extends Error {
|
|
@@ -629,24 +683,8 @@ var ProfileWriterLeaseConflictError = class extends Error {
|
|
|
629
683
|
}
|
|
630
684
|
owner;
|
|
631
685
|
};
|
|
632
|
-
function canonicalDatabasePath(databasePath) {
|
|
633
|
-
const resolved = resolve3(databasePath);
|
|
634
|
-
const missingSegments = [];
|
|
635
|
-
let candidate = resolved;
|
|
636
|
-
while (!existsSync4(candidate)) {
|
|
637
|
-
const parent = dirname3(candidate);
|
|
638
|
-
if (parent === candidate) return resolved;
|
|
639
|
-
missingSegments.unshift(basename3(candidate));
|
|
640
|
-
candidate = parent;
|
|
641
|
-
}
|
|
642
|
-
try {
|
|
643
|
-
return join4(realpathSync(candidate), ...missingSegments);
|
|
644
|
-
} catch {
|
|
645
|
-
return resolved;
|
|
646
|
-
}
|
|
647
|
-
}
|
|
648
686
|
function profileWriterLockPath(profile) {
|
|
649
|
-
return
|
|
687
|
+
return join4(dirname3(profile.manifest_path), "writer.lock");
|
|
650
688
|
}
|
|
651
689
|
function ownerPath(lockPath) {
|
|
652
690
|
return join4(lockPath, ownerFileName);
|
|
@@ -669,7 +707,7 @@ function readOwner(lockPath) {
|
|
|
669
707
|
throw new Error(`Writer lease metadata is invalid at ${lockPath}; refusing automatic recovery`);
|
|
670
708
|
}
|
|
671
709
|
const owner = raw;
|
|
672
|
-
if (owner.schema_version !== writerLeaseSchemaVersion || typeof owner.profile_id !== "string" || owner.environment !== "production" && owner.environment !== "preview" && owner.environment !== "development" || owner.role !== "service" && owner.role !== "cli" || !Number.isSafeInteger(owner.pid) || Number(owner.pid) <= 0 || typeof owner.acquired_at !== "string" || Number.isNaN(Date.parse(owner.acquired_at)) || owner.role === "service" && (typeof owner.service_origin !== "string" || !owner.service_origin) || owner.role === "cli" && owner.service_origin !== void 0 || typeof owner.token !== "string" || owner.token.length < 16) {
|
|
710
|
+
if (owner.schema_version !== writerLeaseSchemaVersion || typeof owner.profile_id !== "string" || typeof owner.profile_fingerprint !== "string" || !/^[a-f0-9]{64}$/i.test(owner.profile_fingerprint) || owner.environment !== "production" && owner.environment !== "preview" && owner.environment !== "development" || owner.role !== "service" && owner.role !== "cli" || !Number.isSafeInteger(owner.pid) || Number(owner.pid) <= 0 || typeof owner.acquired_at !== "string" || Number.isNaN(Date.parse(owner.acquired_at)) || owner.role === "service" && (typeof owner.service_origin !== "string" || !owner.service_origin) || owner.role === "cli" && owner.service_origin !== void 0 || typeof owner.token !== "string" || owner.token.length < 16) {
|
|
673
711
|
throw new Error(`Writer lease metadata is invalid at ${lockPath}; refusing automatic recovery`);
|
|
674
712
|
}
|
|
675
713
|
return owner;
|
|
@@ -686,10 +724,10 @@ function processIsAlive(pid) {
|
|
|
686
724
|
function createLeaseDirectory(lockPath, owner) {
|
|
687
725
|
mkdirSync3(lockPath, { mode: 448 });
|
|
688
726
|
try {
|
|
689
|
-
|
|
727
|
+
writeFileSync2(ownerPath(lockPath), `${JSON.stringify(owner, null, 2)}
|
|
690
728
|
`, { encoding: "utf8", flag: "wx", mode: 384 });
|
|
691
729
|
} catch (error) {
|
|
692
|
-
|
|
730
|
+
rmSync2(lockPath, { force: true, recursive: true });
|
|
693
731
|
throw error;
|
|
694
732
|
}
|
|
695
733
|
}
|
|
@@ -701,14 +739,15 @@ function reclaimDeadOwner(lockPath, owner) {
|
|
|
701
739
|
publicOwner
|
|
702
740
|
);
|
|
703
741
|
}
|
|
704
|
-
const tokenFence =
|
|
742
|
+
const tokenFence = createHash3("sha256").update(owner.token).digest("hex").slice(0, 24);
|
|
705
743
|
const tombstone = `${lockPath}.stale-${owner.pid}-${tokenFence}`;
|
|
706
744
|
try {
|
|
707
745
|
renameSync(lockPath, tombstone);
|
|
708
746
|
} catch (error) {
|
|
709
|
-
if (errorCode(error) === "ENOENT"
|
|
747
|
+
if (errorCode(error) === "ENOENT") return;
|
|
710
748
|
throw error;
|
|
711
749
|
}
|
|
750
|
+
rmSync2(tombstone, { force: true, recursive: true });
|
|
712
751
|
}
|
|
713
752
|
function acquireProfileWriterLease(profile, channel, role = "service") {
|
|
714
753
|
assertProfileChannel(profile, channel);
|
|
@@ -717,11 +756,12 @@ function acquireProfileWriterLease(profile, channel, role = "service") {
|
|
|
717
756
|
acquired_at: (/* @__PURE__ */ new Date()).toISOString(),
|
|
718
757
|
environment: profile.environment,
|
|
719
758
|
pid: process.pid,
|
|
759
|
+
profile_fingerprint: profile.profile_fingerprint,
|
|
720
760
|
profile_id: profile.profile_id,
|
|
721
761
|
role,
|
|
722
762
|
schema_version: writerLeaseSchemaVersion,
|
|
723
763
|
...role === "service" ? { service_origin: profile.service_origin } : {},
|
|
724
|
-
token:
|
|
764
|
+
token: randomUUID2()
|
|
725
765
|
};
|
|
726
766
|
for (let attempt = 0; attempt < 4; attempt += 1) {
|
|
727
767
|
try {
|
|
@@ -744,7 +784,7 @@ function acquireProfileWriterLease(profile, channel, role = "service") {
|
|
|
744
784
|
released = true;
|
|
745
785
|
try {
|
|
746
786
|
const current = readOwner(lockPath);
|
|
747
|
-
if (current.token === owner.token && current.pid === owner.pid)
|
|
787
|
+
if (current.token === owner.token && current.pid === owner.pid) rmSync2(lockPath, { force: true, recursive: true });
|
|
748
788
|
} catch {
|
|
749
789
|
}
|
|
750
790
|
if (process.env.LINEAGE_WRITER_LEASE_TOKEN === owner.token) delete process.env.LINEAGE_WRITER_LEASE_TOKEN;
|
|
@@ -760,7 +800,7 @@ function acquireProfileWriterLease(profile, channel, role = "service") {
|
|
|
760
800
|
if (errorCode(readError) === "ENOENT") continue;
|
|
761
801
|
throw readError;
|
|
762
802
|
}
|
|
763
|
-
if (existing.profile_id !== profile.profile_id || existing.environment !== profile.environment) {
|
|
803
|
+
if (existing.profile_id !== profile.profile_id || existing.environment !== profile.environment || existing.profile_fingerprint !== profile.profile_fingerprint) {
|
|
764
804
|
throw new Error(`Writer lease identity at ${lockPath} does not match ${profile.profile_id}/${profile.environment}; manual inspection is required`, { cause: error });
|
|
765
805
|
}
|
|
766
806
|
reclaimDeadOwner(lockPath, existing);
|
|
@@ -769,23 +809,47 @@ function acquireProfileWriterLease(profile, channel, role = "service") {
|
|
|
769
809
|
throw new Error(`Could not acquire writer lease for Lineage profile ${profile.profile_id}; another writer is racing for ownership`);
|
|
770
810
|
}
|
|
771
811
|
function assertProfileWriterLeaseHeld() {
|
|
772
|
-
if (!process.env.LINEAGE_PROFILE)
|
|
812
|
+
if (!process.env.LINEAGE_PROFILE) {
|
|
813
|
+
throw new Error("Persistent writes require a selected named Lineage profile and its writer lease; legacy-unbound access is read-only");
|
|
814
|
+
}
|
|
773
815
|
const profileId = process.env.LINEAGE_PROFILE_ID;
|
|
816
|
+
const profileFingerprint = process.env.LINEAGE_PROFILE_FINGERPRINT;
|
|
774
817
|
const environment = process.env.LINEAGE_PROFILE_ENVIRONMENT;
|
|
775
818
|
const manifestPath = process.env.LINEAGE_PROFILE_MANIFEST;
|
|
776
|
-
const databasePath = process.env.LINEAGE_DB;
|
|
777
819
|
const lockPath = process.env.LINEAGE_WRITER_LOCK_PATH;
|
|
778
820
|
const token = process.env.LINEAGE_WRITER_LEASE_TOKEN;
|
|
779
|
-
if (!profileId || !
|
|
821
|
+
if (!profileId || !profileFingerprint || !environment || !manifestPath || !lockPath || !token) {
|
|
780
822
|
throw new Error("Named Lineage profiles may write only through a process holding the profile writer lease");
|
|
781
823
|
}
|
|
782
|
-
const expectedLockPath =
|
|
783
|
-
if (resolve3(lockPath) !== expectedLockPath) throw new Error("Profile writer lease path does not match the selected profile
|
|
824
|
+
const expectedLockPath = join4(dirname3(resolve3(manifestPath)), "writer.lock");
|
|
825
|
+
if (resolve3(lockPath) !== expectedLockPath) throw new Error("Profile writer lease path does not match the selected profile manifest");
|
|
784
826
|
const owner = readOwner(lockPath);
|
|
785
|
-
if (owner.pid !== process.pid || owner.token !== token || owner.profile_id !== profileId || owner.environment !== environment) {
|
|
827
|
+
if (owner.pid !== process.pid || owner.token !== token || owner.profile_id !== profileId || owner.profile_fingerprint !== profileFingerprint || owner.environment !== environment) {
|
|
786
828
|
throw new Error("Current process does not own the selected Lineage profile writer lease");
|
|
787
829
|
}
|
|
788
830
|
}
|
|
831
|
+
function assertSelectedProfileDatabaseIdentity(database) {
|
|
832
|
+
const profileId = process.env.LINEAGE_PROFILE_ID;
|
|
833
|
+
const environment = process.env.LINEAGE_PROFILE_ENVIRONMENT;
|
|
834
|
+
const profileFingerprint = process.env.LINEAGE_PROFILE_FINGERPRINT;
|
|
835
|
+
if (!process.env.LINEAGE_PROFILE || !profileId || !environment || !profileFingerprint) {
|
|
836
|
+
throw new Error("Writable SQLite identity validation requires a fully resolved named Lineage profile");
|
|
837
|
+
}
|
|
838
|
+
const table = database.prepare("select name from sqlite_master where type = 'table' and name = 'lineage_profile_identity'").get();
|
|
839
|
+
if (!table) throw new Error(`Refusing writable open: database is not bound to Lineage profile ${profileId}`);
|
|
840
|
+
const columns = new Set(database.prepare("pragma table_info(lineage_profile_identity)").all().map((row) => row.name));
|
|
841
|
+
if (!columns.has("profile_id") || !columns.has("environment") || !columns.has("profile_fingerprint")) {
|
|
842
|
+
throw new Error(`Refusing writable open: database identity for ${profileId} is missing required fingerprint fields`);
|
|
843
|
+
}
|
|
844
|
+
const rows = database.prepare("select profile_id, environment, profile_fingerprint from lineage_profile_identity").all();
|
|
845
|
+
if (rows.length !== 1) throw new Error(`Refusing writable open: expected exactly one database profile identity, found ${rows.length}`);
|
|
846
|
+
const identity = rows[0];
|
|
847
|
+
if (identity.profile_id !== profileId || identity.environment !== environment || identity.profile_fingerprint !== profileFingerprint) {
|
|
848
|
+
throw new Error(
|
|
849
|
+
`Refusing writable open: database identity ${String(identity.profile_id)}/${String(identity.environment)}/${String(identity.profile_fingerprint)} does not match selected profile ${profileId}/${environment}/${profileFingerprint}`
|
|
850
|
+
);
|
|
851
|
+
}
|
|
852
|
+
}
|
|
789
853
|
|
|
790
854
|
// src/server/assetLineageDb.ts
|
|
791
855
|
var require3 = createRequire2(import.meta.url);
|
|
@@ -795,44 +859,19 @@ function nowIso() {
|
|
|
795
859
|
function lineageDbPath() {
|
|
796
860
|
return process.env.LINEAGE_DB || join5(packageRoot, ".lineage", "asset-lineage.sqlite");
|
|
797
861
|
}
|
|
798
|
-
function assertOpenedProfileIdentity(database, databasePath) {
|
|
799
|
-
const selector = process.env.LINEAGE_PROFILE;
|
|
800
|
-
if (!selector) return;
|
|
801
|
-
const profile = resolveLineageProfile(selector);
|
|
802
|
-
if (resolve4(databasePath) !== profile.database_path) {
|
|
803
|
-
throw new Error(`Profile ${profile.profile_id} requires database ${profile.database_path}, not ${databasePath}`);
|
|
804
|
-
}
|
|
805
|
-
const table = database.prepare("select 1 from sqlite_master where type = 'table' and name = 'lineage_profile_identity'").get();
|
|
806
|
-
if (!table) throw new Error(`Profile database ${databasePath} is not bound to a Lineage profile identity`);
|
|
807
|
-
const rows = database.prepare("select profile_id, environment from lineage_profile_identity").all();
|
|
808
|
-
if (rows.length !== 1) {
|
|
809
|
-
throw new Error(`Profile database ${databasePath} has invalid Lineage profile identity row count ${rows.length}`);
|
|
810
|
-
}
|
|
811
|
-
if (rows[0].profile_id !== profile.profile_id || rows[0].environment !== profile.environment) {
|
|
812
|
-
throw new Error(
|
|
813
|
-
`Profile database ${databasePath} is bound to ${rows[0].profile_id}/${rows[0].environment}, not ${profile.profile_id}/${profile.environment}`
|
|
814
|
-
);
|
|
815
|
-
}
|
|
816
|
-
}
|
|
817
862
|
function lineageDb() {
|
|
818
863
|
const readOnly = process.env.LINEAGE_DB_ACCESS === "read-only";
|
|
819
|
-
const databasePath = lineageDbPath();
|
|
820
864
|
if (!readOnly) assertProfileWriterLeaseHeld();
|
|
821
|
-
if (
|
|
822
|
-
throw new Error(`
|
|
865
|
+
if (!readOnly && !existsSync5(lineageDbPath())) {
|
|
866
|
+
throw new Error(`Refusing writable open of missing profile database ${lineageDbPath()}; bind an existing database or create a non-production clone first`);
|
|
823
867
|
}
|
|
824
|
-
if (!readOnly) mkdirSync4(join5(
|
|
868
|
+
if (!readOnly) mkdirSync4(join5(lineageDbPath(), ".."), { recursive: true });
|
|
825
869
|
const { DatabaseSync } = require3("node:sqlite");
|
|
826
|
-
const database = readOnly ? new DatabaseSync(
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
if (readOnly) return database;
|
|
832
|
-
} catch (error) {
|
|
833
|
-
database.close();
|
|
834
|
-
throw error;
|
|
835
|
-
}
|
|
870
|
+
const database = readOnly ? new DatabaseSync(lineageDbPath(), { readOnly: true }) : new DatabaseSync(lineageDbPath());
|
|
871
|
+
if (process.env.LINEAGE_PROFILE) assertSelectedProfileDatabaseIdentity(database);
|
|
872
|
+
database.exec("PRAGMA foreign_keys = ON");
|
|
873
|
+
database.exec("PRAGMA busy_timeout = 5000");
|
|
874
|
+
if (readOnly) return database;
|
|
836
875
|
database.exec("PRAGMA journal_mode = WAL");
|
|
837
876
|
database.exec(`
|
|
838
877
|
create table if not exists projects (
|
|
@@ -1680,8 +1719,8 @@ function resolvePackageRoot() {
|
|
|
1680
1719
|
const moduleDir = dirname4(fileURLToPath(import.meta.url));
|
|
1681
1720
|
const candidates = [
|
|
1682
1721
|
process.env.LINEAGE_REPO_ROOT,
|
|
1683
|
-
|
|
1684
|
-
|
|
1722
|
+
resolve4(moduleDir, ".."),
|
|
1723
|
+
resolve4(moduleDir, "../.."),
|
|
1685
1724
|
process.cwd()
|
|
1686
1725
|
].filter((candidate) => Boolean(candidate));
|
|
1687
1726
|
const root = candidates.find(isPackageRoot);
|
|
@@ -1689,9 +1728,9 @@ function resolvePackageRoot() {
|
|
|
1689
1728
|
return root;
|
|
1690
1729
|
}
|
|
1691
1730
|
var packageRoot = resolvePackageRoot();
|
|
1692
|
-
var repoRoot =
|
|
1731
|
+
var repoRoot = resolve4(process.env.LINEAGE_ASSET_ROOT || packageRoot);
|
|
1693
1732
|
function setLineageAssetRoot(path) {
|
|
1694
|
-
repoRoot =
|
|
1733
|
+
repoRoot = resolve4(path || packageRoot);
|
|
1695
1734
|
return repoRoot;
|
|
1696
1735
|
}
|
|
1697
1736
|
var defaultProject = "demo-project";
|
|
@@ -1881,7 +1920,7 @@ function loadCatalog(project = defaultProject) {
|
|
|
1881
1920
|
function saveCatalog(project, catalog) {
|
|
1882
1921
|
const normalized = normalizeCatalog(catalog, project);
|
|
1883
1922
|
mkdirSync5(dirname4(catalogPath(project)), { recursive: true });
|
|
1884
|
-
|
|
1923
|
+
writeFileSync3(catalogPath(project), `${JSON.stringify(normalized, null, 2)}
|
|
1885
1924
|
`);
|
|
1886
1925
|
return normalized;
|
|
1887
1926
|
}
|
|
@@ -2072,7 +2111,7 @@ function ensureUploadDir() {
|
|
|
2072
2111
|
function cleanupUploadedTemp(file) {
|
|
2073
2112
|
if (!file) return;
|
|
2074
2113
|
const uploadRoot = ensureUploadDir();
|
|
2075
|
-
const resolved =
|
|
2114
|
+
const resolved = resolve4(file);
|
|
2076
2115
|
if (resolved.startsWith(`${uploadRoot}/`) && existsSync6(resolved)) {
|
|
2077
2116
|
unlinkSync(resolved);
|
|
2078
2117
|
}
|
|
@@ -2130,7 +2169,7 @@ function normalizeSelectionInput(fields) {
|
|
|
2130
2169
|
import { randomBytes as randomBytes2 } from "node:crypto";
|
|
2131
2170
|
|
|
2132
2171
|
// src/server/agentClaims.ts
|
|
2133
|
-
import { createHash as
|
|
2172
|
+
import { createHash as createHash4, randomBytes, timingSafeEqual as timingSafeEqual2 } from "node:crypto";
|
|
2134
2173
|
var defaultTtlSeconds = 20 * 60;
|
|
2135
2174
|
var idleAfterSeconds = 5 * 60;
|
|
2136
2175
|
var staleAfterSeconds = 15 * 60;
|
|
@@ -2166,7 +2205,7 @@ function randomId(prefix) {
|
|
|
2166
2205
|
return `${prefix}_${Date.now().toString(36)}_${randomBytes(6).toString("base64url").toLowerCase()}`;
|
|
2167
2206
|
}
|
|
2168
2207
|
function tokenHash(token) {
|
|
2169
|
-
return
|
|
2208
|
+
return createHash4("sha256").update(token).digest("hex");
|
|
2170
2209
|
}
|
|
2171
2210
|
function safeEqual(a, b) {
|
|
2172
2211
|
const left = Buffer.from(a);
|
|
@@ -3474,8 +3513,8 @@ function rootFor(database, project, assetId) {
|
|
|
3474
3513
|
}
|
|
3475
3514
|
function assertCanonicalRoot(database, project, root) {
|
|
3476
3515
|
requireAsset3(database, project, root);
|
|
3477
|
-
const
|
|
3478
|
-
if (
|
|
3516
|
+
const canonicalRoot2 = rootFor(database, project, root);
|
|
3517
|
+
if (canonicalRoot2 !== root) throw new LineageError(`Asset ${root} is not a lineage root`, 400);
|
|
3479
3518
|
}
|
|
3480
3519
|
function explicitWorkspaceRoot(database, project, assetId) {
|
|
3481
3520
|
const row = database.prepare(`
|
|
@@ -5947,7 +5986,7 @@ function getAdapterStatus(project, env = process.env) {
|
|
|
5947
5986
|
}
|
|
5948
5987
|
|
|
5949
5988
|
// src/server/adapters/posting/bufferPostingService.ts
|
|
5950
|
-
import { mkdirSync as mkdirSync6, writeFileSync as
|
|
5989
|
+
import { mkdirSync as mkdirSync6, writeFileSync as writeFileSync4 } from "node:fs";
|
|
5951
5990
|
import { join as join9 } from "node:path";
|
|
5952
5991
|
|
|
5953
5992
|
// src/server/contentPostHandoff.ts
|
|
@@ -6449,7 +6488,7 @@ function payloadPath(project, postId) {
|
|
|
6449
6488
|
function writePayload(project, postId, payload) {
|
|
6450
6489
|
const file = payloadPath(project, postId);
|
|
6451
6490
|
mkdirSync6(join9(file, ".."), { recursive: true });
|
|
6452
|
-
|
|
6491
|
+
writeFileSync4(file, `${JSON.stringify(payload, null, 2)}
|
|
6453
6492
|
`);
|
|
6454
6493
|
return file;
|
|
6455
6494
|
}
|
|
@@ -6559,7 +6598,7 @@ import { Router } from "express";
|
|
|
6559
6598
|
|
|
6560
6599
|
// src/server/contentBatchImport.ts
|
|
6561
6600
|
import { existsSync as existsSync8, readdirSync as readdirSync3, readFileSync as readFileSync5 } from "node:fs";
|
|
6562
|
-
import { basename as
|
|
6601
|
+
import { basename as basename3, join as join10, relative as relative3 } from "node:path";
|
|
6563
6602
|
function walk(dir) {
|
|
6564
6603
|
return readdirSync3(dir, { withFileTypes: true }).flatMap((entry) => {
|
|
6565
6604
|
const path = join10(dir, entry.name);
|
|
@@ -6587,7 +6626,7 @@ function phaseFromStatus(value) {
|
|
|
6587
6626
|
return "draft";
|
|
6588
6627
|
}
|
|
6589
6628
|
function postIdFor(kind, file) {
|
|
6590
|
-
const slug2 =
|
|
6629
|
+
const slug2 = basename3(file, ".md").replace(/^\d{4}-\d{2}-/, "");
|
|
6591
6630
|
return `${kind}-${slug2}`;
|
|
6592
6631
|
}
|
|
6593
6632
|
function itemFromFile(file) {
|
|
@@ -6607,8 +6646,8 @@ function itemFromFile(file) {
|
|
|
6607
6646
|
phase: phaseFromStatus(meta.status),
|
|
6608
6647
|
postId: postIdFor(kind, file),
|
|
6609
6648
|
relatedAsset,
|
|
6610
|
-
sourcePath:
|
|
6611
|
-
title: text.match(/^#\s+(.+)$/m)?.[1] ||
|
|
6649
|
+
sourcePath: relative3(repoRoot, file),
|
|
6650
|
+
title: text.match(/^#\s+(.+)$/m)?.[1] || basename3(file, ".md")
|
|
6612
6651
|
};
|
|
6613
6652
|
}
|
|
6614
6653
|
function demoMarkdownItems(kind) {
|
|
@@ -6953,8 +6992,8 @@ function contentBatchRouter(projectFrom2) {
|
|
|
6953
6992
|
|
|
6954
6993
|
// src/server/generationReceipts.ts
|
|
6955
6994
|
import { existsSync as existsSync9, realpathSync as realpathSync2, statSync as statSync4 } from "node:fs";
|
|
6956
|
-
import { relative as
|
|
6957
|
-
import { randomUUID as
|
|
6995
|
+
import { relative as relative4, resolve as resolve5 } from "node:path";
|
|
6996
|
+
import { randomUUID as randomUUID3 } from "node:crypto";
|
|
6958
6997
|
var adapterVersion = "generation-receipts-v1";
|
|
6959
6998
|
var provider = "codex-handoff";
|
|
6960
6999
|
var GenerationReceiptError = class extends Error {
|
|
@@ -6968,7 +7007,7 @@ function isGenerationReceiptError(error) {
|
|
|
6968
7007
|
return error instanceof GenerationReceiptError;
|
|
6969
7008
|
}
|
|
6970
7009
|
function jobId() {
|
|
6971
|
-
return `gen-${Date.now().toString(36)}-${
|
|
7010
|
+
return `gen-${Date.now().toString(36)}-${randomUUID3().slice(0, 8)}`;
|
|
6972
7011
|
}
|
|
6973
7012
|
function quote(value) {
|
|
6974
7013
|
return JSON.stringify(value);
|
|
@@ -7164,12 +7203,12 @@ function planImageReroll(project = defaultProject, fields) {
|
|
|
7164
7203
|
}
|
|
7165
7204
|
}
|
|
7166
7205
|
function isPathInside2(child, parent) {
|
|
7167
|
-
const rel =
|
|
7206
|
+
const rel = relative4(parent, child);
|
|
7168
7207
|
return Boolean(rel) && !rel.startsWith("..") && !rel.startsWith("/");
|
|
7169
7208
|
}
|
|
7170
7209
|
function resolveScratchFile(file) {
|
|
7171
|
-
const scratchRoot =
|
|
7172
|
-
const candidate = file.startsWith(".asset-scratch/") ||
|
|
7210
|
+
const scratchRoot = resolve5(repoRoot, ".asset-scratch");
|
|
7211
|
+
const candidate = file.startsWith(".asset-scratch/") || resolve5(file).startsWith(scratchRoot) ? resolve5(repoRoot, file) : resolve5(scratchRoot, file);
|
|
7173
7212
|
if (!isPathInside2(candidate, scratchRoot)) throw new GenerationReceiptError(`Import file must be under .asset-scratch: ${file}`);
|
|
7174
7213
|
if (!existsSync9(candidate)) throw new GenerationReceiptError(`Missing import file: ${file}`, 404);
|
|
7175
7214
|
const realScratchRoot = realpathSync2(scratchRoot);
|
|
@@ -7179,7 +7218,7 @@ function resolveScratchFile(file) {
|
|
|
7179
7218
|
if (!stats.isFile()) throw new GenerationReceiptError(`Import path is not a file: ${file}`);
|
|
7180
7219
|
const checksum = fileSha256(candidate);
|
|
7181
7220
|
return {
|
|
7182
|
-
relativePath:
|
|
7221
|
+
relativePath: relative4(scratchRoot, candidate),
|
|
7183
7222
|
checksum,
|
|
7184
7223
|
size: stats.size,
|
|
7185
7224
|
contentType: contentTypeFor(candidate),
|
|
@@ -7343,8 +7382,8 @@ function registerLineageTaskRoutes(app2, projectFrom2, asyncRoute2) {
|
|
|
7343
7382
|
}
|
|
7344
7383
|
|
|
7345
7384
|
// src/server/assetLineageDemo.ts
|
|
7346
|
-
import { createHash as
|
|
7347
|
-
import { copyFileSync as
|
|
7385
|
+
import { createHash as createHash5 } from "node:crypto";
|
|
7386
|
+
import { copyFileSync as copyFileSync3, existsSync as existsSync10, mkdirSync as mkdirSync7, mkdtempSync, readFileSync as readFileSync6, rmSync as rmSync3, writeFileSync as writeFileSync5 } from "node:fs";
|
|
7348
7387
|
import { tmpdir } from "node:os";
|
|
7349
7388
|
import { dirname as dirname5, join as join11, posix } from "node:path";
|
|
7350
7389
|
import { gunzipSync } from "node:zlib";
|
|
@@ -7435,7 +7474,7 @@ function swissifierMediaState(manifest) {
|
|
|
7435
7474
|
return { invalid, missing };
|
|
7436
7475
|
}
|
|
7437
7476
|
function sha256Hex(input) {
|
|
7438
|
-
return
|
|
7477
|
+
return createHash5("sha256").update(input).digest("hex");
|
|
7439
7478
|
}
|
|
7440
7479
|
function safeTarEntryName(rawName) {
|
|
7441
7480
|
const stripped = rawName.replace(/\0.*$/, "").replace(/^\.\//, "");
|
|
@@ -7500,7 +7539,7 @@ function extractSwissifierMediaArchive(archive, manifest, destination) {
|
|
|
7500
7539
|
if (actualSha !== asset.checksum_sha256) throw new Error(`Checksum mismatch for Swissifier media archive entry: ${name}`);
|
|
7501
7540
|
const target = join11(destination, directName);
|
|
7502
7541
|
mkdirSync7(dirname5(target), { recursive: true });
|
|
7503
|
-
|
|
7542
|
+
writeFileSync5(target, file);
|
|
7504
7543
|
extracted.add(directName);
|
|
7505
7544
|
offset = nextOffset;
|
|
7506
7545
|
}
|
|
@@ -7607,7 +7646,7 @@ async function downloadSwissifierRichDemoMedia(project = defaultProject, fields
|
|
|
7607
7646
|
media_status: swissifierRichDemoMediaStatus(project)
|
|
7608
7647
|
};
|
|
7609
7648
|
} finally {
|
|
7610
|
-
|
|
7649
|
+
rmSync3(sourceDir, { force: true, recursive: true });
|
|
7611
7650
|
}
|
|
7612
7651
|
}
|
|
7613
7652
|
function restoreDemoSeedMedia(project = defaultProject, fields = { confirmWrite: false }) {
|
|
@@ -7666,7 +7705,7 @@ function restoreSwissifierRichDemoMedia(project = defaultProject, fields = { con
|
|
|
7666
7705
|
for (const item of copyable) {
|
|
7667
7706
|
const target = swissifierFilePath(manifest, item.asset);
|
|
7668
7707
|
mkdirSync7(dirname5(target), { recursive: true });
|
|
7669
|
-
|
|
7708
|
+
copyFileSync3(item.source, target);
|
|
7670
7709
|
}
|
|
7671
7710
|
}
|
|
7672
7711
|
return {
|
|
@@ -7696,7 +7735,7 @@ function svg(label, fill, stroke) {
|
|
|
7696
7735
|
`;
|
|
7697
7736
|
}
|
|
7698
7737
|
function assetIdFor(asset) {
|
|
7699
|
-
return `local-${
|
|
7738
|
+
return `local-${createHash5("sha256").update(svg(asset.label, asset.fill, asset.stroke)).digest("hex").slice(0, 12)}`;
|
|
7700
7739
|
}
|
|
7701
7740
|
function demoAssetIds() {
|
|
7702
7741
|
return Object.fromEntries(demoAssets.map((asset) => [asset.key, assetIdFor(asset)]));
|
|
@@ -7704,7 +7743,7 @@ function demoAssetIds() {
|
|
|
7704
7743
|
function writeDemoFile(project, asset) {
|
|
7705
7744
|
const path = demoFilePath(project, asset);
|
|
7706
7745
|
mkdirSync7(dirname5(path), { recursive: true });
|
|
7707
|
-
|
|
7746
|
+
writeFileSync5(path, svg(asset.label, asset.fill, asset.stroke));
|
|
7708
7747
|
}
|
|
7709
7748
|
function writeDemoFiles(project) {
|
|
7710
7749
|
const ids = demoAssetIds();
|
|
@@ -7729,7 +7768,7 @@ function writeSwissifierRerollFiles(manifest) {
|
|
|
7729
7768
|
swissifierRerollAsset(attempt);
|
|
7730
7769
|
const target = swissifierRerollFilePath(manifest, attempt);
|
|
7731
7770
|
mkdirSync7(dirname5(target), { recursive: true });
|
|
7732
|
-
|
|
7771
|
+
copyFileSync3(swissifierRerollFixturePath(attempt), target);
|
|
7733
7772
|
}
|
|
7734
7773
|
}
|
|
7735
7774
|
function upsertDemoAssets(project, ids) {
|
|
@@ -7763,7 +7802,7 @@ function upsertDemoAssets(project, ids) {
|
|
|
7763
7802
|
ids[asset.key],
|
|
7764
7803
|
project,
|
|
7765
7804
|
demoRelativePath(project, asset),
|
|
7766
|
-
|
|
7805
|
+
createHash5("sha256").update(body).digest("hex"),
|
|
7767
7806
|
asset.label,
|
|
7768
7807
|
asset.channel,
|
|
7769
7808
|
Buffer.byteLength(body),
|
|
@@ -8022,7 +8061,7 @@ function archiveDemoLineageWorkspace(project, confirmWrite) {
|
|
|
8022
8061
|
if (!isLineageWorkspaceError(error) || error.status !== 404) throw error;
|
|
8023
8062
|
archived = { ok: true, message: "No demo lineage workspace exists yet", workspace: null };
|
|
8024
8063
|
}
|
|
8025
|
-
if (confirmWrite)
|
|
8064
|
+
if (confirmWrite) rmSync3(demoProjectDir(project), { force: true, recursive: true });
|
|
8026
8065
|
return {
|
|
8027
8066
|
ok: true,
|
|
8028
8067
|
message: confirmWrite ? `Archived ${demoWorkspaceTitle}` : `Would archive ${demoWorkspaceTitle}`,
|
|
@@ -8116,14 +8155,49 @@ function registerLineageWorkspaceRoutes(app2, projectFrom2, asyncRoute2) {
|
|
|
8116
8155
|
}
|
|
8117
8156
|
|
|
8118
8157
|
// src/server/runtimeInfo.ts
|
|
8158
|
+
import { createHash as createHash6 } from "node:crypto";
|
|
8119
8159
|
import { spawnSync as spawnSync2 } from "node:child_process";
|
|
8120
8160
|
import { createRequire as createRequire4 } from "node:module";
|
|
8121
|
-
import { existsSync as existsSync11, readFileSync as readFileSync7, statSync as statSync5 } from "node:fs";
|
|
8122
|
-
import { join as join12 } from "node:path";
|
|
8161
|
+
import { existsSync as existsSync11, lstatSync as lstatSync2, readFileSync as readFileSync7, readdirSync as readdirSync4, readlinkSync, realpathSync as realpathSync3, statSync as statSync5 } from "node:fs";
|
|
8162
|
+
import { dirname as dirname6, isAbsolute as isAbsolute2, join as join12, resolve as resolve6 } from "node:path";
|
|
8163
|
+
import { fileURLToPath as fileURLToPath2 } from "node:url";
|
|
8164
|
+
|
|
8165
|
+
// src/shared/runtimeInfoTypes.ts
|
|
8166
|
+
var lineageRuntimeBuildSchemaVersion = "lineage.runtime_build.v1";
|
|
8167
|
+
var lineageRuntimeInstallSchemaVersion = "lineage.runtime_install.v1";
|
|
8168
|
+
|
|
8169
|
+
// src/server/runtimeInfo.ts
|
|
8123
8170
|
var require5 = createRequire4(import.meta.url);
|
|
8124
|
-
|
|
8171
|
+
var processStartedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
8172
|
+
function isLineagePackageRoot(root) {
|
|
8125
8173
|
try {
|
|
8126
|
-
const info = JSON.parse(readFileSync7(join12(
|
|
8174
|
+
const info = JSON.parse(readFileSync7(join12(root, "package.json"), "utf8"));
|
|
8175
|
+
return info.name === "@mean-weasel/lineage";
|
|
8176
|
+
} catch {
|
|
8177
|
+
return false;
|
|
8178
|
+
}
|
|
8179
|
+
}
|
|
8180
|
+
function executingCodeRoot() {
|
|
8181
|
+
const moduleDirectory = dirname6(fileURLToPath2(import.meta.url));
|
|
8182
|
+
const candidates = [resolve6(moduleDirectory, "../.."), resolve6(moduleDirectory, "..")];
|
|
8183
|
+
const root = candidates.find(isLineagePackageRoot);
|
|
8184
|
+
if (!root) throw new Error(`Unable to derive Lineage code root from executing module ${fileURLToPath2(import.meta.url)}`);
|
|
8185
|
+
return canonicalRoot(root);
|
|
8186
|
+
}
|
|
8187
|
+
var codeRoot = executingCodeRoot();
|
|
8188
|
+
function sha256(value) {
|
|
8189
|
+
return createHash6("sha256").update(value).digest("hex");
|
|
8190
|
+
}
|
|
8191
|
+
function canonicalRoot(root) {
|
|
8192
|
+
try {
|
|
8193
|
+
return realpathSync3(root);
|
|
8194
|
+
} catch {
|
|
8195
|
+
return resolve6(root);
|
|
8196
|
+
}
|
|
8197
|
+
}
|
|
8198
|
+
function packageInfo(root = codeRoot) {
|
|
8199
|
+
try {
|
|
8200
|
+
const info = JSON.parse(readFileSync7(join12(root, "package.json"), "utf8"));
|
|
8127
8201
|
return { name: info.name || "@mean-weasel/lineage", version: info.version || "0.0.0" };
|
|
8128
8202
|
} catch {
|
|
8129
8203
|
return { name: "@mean-weasel/lineage", version: "0.0.0" };
|
|
@@ -8136,12 +8210,199 @@ function normalizeRuntimeChannel(value) {
|
|
|
8136
8210
|
if (value === "development") return "dev";
|
|
8137
8211
|
return process.env.NODE_ENV === "production" ? "stable" : "dev";
|
|
8138
8212
|
}
|
|
8139
|
-
function
|
|
8140
|
-
const
|
|
8141
|
-
|
|
8142
|
-
|
|
8143
|
-
|
|
8144
|
-
|
|
8213
|
+
function git(root, args) {
|
|
8214
|
+
const result = spawnSync2("git", args, { cwd: root, encoding: "utf8", maxBuffer: 32 * 1024 * 1024 });
|
|
8215
|
+
return result.status === 0 ? result.stdout : void 0;
|
|
8216
|
+
}
|
|
8217
|
+
function untrackedFingerprint(root, status) {
|
|
8218
|
+
const hash = createHash6("sha256");
|
|
8219
|
+
hash.update(status);
|
|
8220
|
+
const listed = git(root, ["ls-files", "--others", "--exclude-standard", "-z"]);
|
|
8221
|
+
if (listed === void 0) return hash.digest("hex");
|
|
8222
|
+
for (const relativePath of listed.split("\0").filter(Boolean).sort()) {
|
|
8223
|
+
hash.update("\0");
|
|
8224
|
+
hash.update(relativePath);
|
|
8225
|
+
const path = join12(root, relativePath);
|
|
8226
|
+
try {
|
|
8227
|
+
const stat = lstatSync2(path);
|
|
8228
|
+
if (stat.isSymbolicLink()) hash.update(readlinkSync(path));
|
|
8229
|
+
else if (stat.isFile()) hash.update(readFileSync7(path));
|
|
8230
|
+
else hash.update(`[${stat.mode}:${stat.size}]`);
|
|
8231
|
+
} catch (error) {
|
|
8232
|
+
hash.update(`[unreadable:${error instanceof Error ? error.message : String(error)}]`);
|
|
8233
|
+
}
|
|
8234
|
+
}
|
|
8235
|
+
return hash.digest("hex");
|
|
8236
|
+
}
|
|
8237
|
+
function checkoutCodeIdentity(root, channel, version) {
|
|
8238
|
+
const errors = [];
|
|
8239
|
+
const gitRootRaw = git(root, ["rev-parse", "--show-toplevel"])?.trim();
|
|
8240
|
+
const gitSha = git(root, ["rev-parse", "HEAD"])?.trim();
|
|
8241
|
+
const status = git(root, ["status", "--porcelain=v1", "-z", "--untracked-files=all"]);
|
|
8242
|
+
const diff = git(root, ["diff", "--binary", "HEAD", "--"]);
|
|
8243
|
+
const canonicalGitRoot = gitRootRaw ? canonicalRoot(gitRootRaw) : void 0;
|
|
8244
|
+
if (!canonicalGitRoot || canonicalGitRoot !== root) errors.push("Code root is not the canonical root of a Git checkout/worktree");
|
|
8245
|
+
if (!gitSha || !/^[a-f0-9]{40}$/i.test(gitSha)) errors.push("Checkout Git revision is unavailable");
|
|
8246
|
+
if (status === void 0 || diff === void 0) errors.push("Checkout dirty state could not be inspected");
|
|
8247
|
+
if (channel !== "dev") errors.push(`Checkout code may run only as dev, not ${channel}`);
|
|
8248
|
+
const dirty = Boolean(status);
|
|
8249
|
+
const sourceFingerprint = sha256(`${gitSha || "unknown"}\0${sha256(diff || "")}\0${untrackedFingerprint(root, status || "")}`);
|
|
8250
|
+
return {
|
|
8251
|
+
channel,
|
|
8252
|
+
dirty,
|
|
8253
|
+
errors,
|
|
8254
|
+
fingerprint: sha256(JSON.stringify({ channel, dirty, git_sha: gitSha, origin: "checkout", root, source_fingerprint: sourceFingerprint, version })),
|
|
8255
|
+
git_sha: gitSha,
|
|
8256
|
+
origin: "checkout",
|
|
8257
|
+
package_version: version,
|
|
8258
|
+
root,
|
|
8259
|
+
source_fingerprint: sourceFingerprint,
|
|
8260
|
+
verified: errors.length === 0
|
|
8261
|
+
};
|
|
8262
|
+
}
|
|
8263
|
+
function buildFingerprint(build) {
|
|
8264
|
+
return sha256(JSON.stringify({
|
|
8265
|
+
package_name: build.package_name,
|
|
8266
|
+
package_version: build.package_version,
|
|
8267
|
+
schema_version: build.schema_version,
|
|
8268
|
+
source_dirty: build.source_dirty,
|
|
8269
|
+
source_fingerprint: build.source_fingerprint,
|
|
8270
|
+
source_git_sha: build.source_git_sha
|
|
8271
|
+
}));
|
|
8272
|
+
}
|
|
8273
|
+
function readBuildIdentity(root, errors) {
|
|
8274
|
+
const path = join12(root, "dist", "runtime-build.json");
|
|
8275
|
+
let build;
|
|
8276
|
+
try {
|
|
8277
|
+
build = JSON.parse(readFileSync7(path, "utf8"));
|
|
8278
|
+
} catch (error) {
|
|
8279
|
+
errors.push(`Embedded build identity is missing or unreadable at ${path}: ${error instanceof Error ? error.message : String(error)}`);
|
|
8280
|
+
return void 0;
|
|
8281
|
+
}
|
|
8282
|
+
if (build.schema_version !== lineageRuntimeBuildSchemaVersion) errors.push(`Unsupported build identity schema: ${String(build.schema_version)}`);
|
|
8283
|
+
if (!/^[a-f0-9]{40}$/i.test(build.source_git_sha || "")) errors.push("Embedded build Git revision is invalid");
|
|
8284
|
+
if (!/^[a-f0-9]{64}$/i.test(build.source_fingerprint || "")) errors.push("Embedded source fingerprint is invalid");
|
|
8285
|
+
const expected = buildFingerprint(build);
|
|
8286
|
+
if (build.build_fingerprint !== expected) errors.push("Embedded build fingerprint does not match its contents");
|
|
8287
|
+
return build;
|
|
8288
|
+
}
|
|
8289
|
+
function readInstallReceipt(path, errors) {
|
|
8290
|
+
if (!path) {
|
|
8291
|
+
errors.push("Packaged runtime was not launched through a channel install receipt");
|
|
8292
|
+
return void 0;
|
|
8293
|
+
}
|
|
8294
|
+
let receipt;
|
|
8295
|
+
const receiptPath = resolve6(path);
|
|
8296
|
+
try {
|
|
8297
|
+
receipt = JSON.parse(readFileSync7(receiptPath, "utf8"));
|
|
8298
|
+
} catch (error) {
|
|
8299
|
+
errors.push(`Runtime install receipt is missing or unreadable at ${receiptPath}: ${error instanceof Error ? error.message : String(error)}`);
|
|
8300
|
+
return void 0;
|
|
8301
|
+
}
|
|
8302
|
+
if (receipt.schema_version !== lineageRuntimeInstallSchemaVersion) errors.push(`Unsupported runtime install receipt schema: ${String(receipt.schema_version)}`);
|
|
8303
|
+
if (!isAbsolute2(receipt.package_root || "")) errors.push("Runtime install receipt package_root must be absolute");
|
|
8304
|
+
if (!receipt.package_integrity?.startsWith("sha512-")) errors.push("Runtime install receipt package integrity is invalid");
|
|
8305
|
+
if (receipt.package_source !== "registry" && receipt.package_source !== "local") errors.push("Runtime install receipt package source is invalid");
|
|
8306
|
+
if (typeof receipt.package_spec !== "string" || !receipt.package_spec) errors.push("Runtime install receipt package spec is invalid");
|
|
8307
|
+
if (!/^[a-f0-9]{64}$/i.test(receipt.package_tree_sha256 || "")) errors.push("Runtime install receipt package tree hash is invalid");
|
|
8308
|
+
if (Number.isNaN(Date.parse(receipt.installed_at))) errors.push("Runtime install receipt timestamp is invalid");
|
|
8309
|
+
return { ...receipt, receipt_path: receiptPath };
|
|
8310
|
+
}
|
|
8311
|
+
function lineagePackageTreeSha256(root) {
|
|
8312
|
+
const hash = createHash6("sha256");
|
|
8313
|
+
const visit = (directory, relativeDirectory = "") => {
|
|
8314
|
+
for (const entry of readdirSync4(directory, { withFileTypes: true }).sort((left, right) => left.name.localeCompare(right.name))) {
|
|
8315
|
+
const relativePath = relativeDirectory ? join12(relativeDirectory, entry.name) : entry.name;
|
|
8316
|
+
const path = join12(directory, entry.name);
|
|
8317
|
+
hash.update(relativePath.replaceAll("\\", "/"));
|
|
8318
|
+
hash.update("\0");
|
|
8319
|
+
if (entry.isDirectory()) {
|
|
8320
|
+
hash.update("directory\0");
|
|
8321
|
+
visit(path, relativePath);
|
|
8322
|
+
} else if (entry.isSymbolicLink()) {
|
|
8323
|
+
hash.update("symlink\0");
|
|
8324
|
+
hash.update(readlinkSync(path));
|
|
8325
|
+
} else if (entry.isFile()) {
|
|
8326
|
+
hash.update("file\0");
|
|
8327
|
+
hash.update(readFileSync7(path));
|
|
8328
|
+
} else {
|
|
8329
|
+
hash.update("other\0");
|
|
8330
|
+
}
|
|
8331
|
+
hash.update("\0");
|
|
8332
|
+
}
|
|
8333
|
+
};
|
|
8334
|
+
visit(root);
|
|
8335
|
+
return hash.digest("hex");
|
|
8336
|
+
}
|
|
8337
|
+
function packageCodeIdentity(root, channel, info, receiptPath) {
|
|
8338
|
+
const errors = [];
|
|
8339
|
+
const build = readBuildIdentity(root, errors);
|
|
8340
|
+
const install = readInstallReceipt(receiptPath, errors);
|
|
8341
|
+
if (channel === "dev") errors.push("Published package code cannot run as dev; use a Git checkout/worktree");
|
|
8342
|
+
if (build?.package_name !== info.name || build?.package_version !== info.version) {
|
|
8343
|
+
errors.push("Embedded build package identity does not match package.json");
|
|
8344
|
+
}
|
|
8345
|
+
if (build?.source_dirty) errors.push("Stable and preview package installs require a clean-source build");
|
|
8346
|
+
if (install) {
|
|
8347
|
+
if (install.channel !== channel) errors.push(`Install receipt channel ${install.channel} does not match requested ${channel}`);
|
|
8348
|
+
if (canonicalRoot(install.package_root) !== root) errors.push("Install receipt package root does not match the executing package root");
|
|
8349
|
+
if (install.package_name !== info.name || install.package_version !== info.version) errors.push("Install receipt package identity does not match package.json");
|
|
8350
|
+
if (build && install.build_fingerprint !== build.build_fingerprint) errors.push("Install receipt build fingerprint does not match the embedded build");
|
|
8351
|
+
try {
|
|
8352
|
+
if (lineagePackageTreeSha256(root) !== install.package_tree_sha256) errors.push("Installed package tree does not match the channel install receipt");
|
|
8353
|
+
} catch (error) {
|
|
8354
|
+
errors.push(`Installed package tree could not be verified: ${error instanceof Error ? error.message : String(error)}`);
|
|
8355
|
+
}
|
|
8356
|
+
}
|
|
8357
|
+
const gitSha = build?.source_git_sha;
|
|
8358
|
+
const sourceFingerprint = build?.source_fingerprint;
|
|
8359
|
+
return {
|
|
8360
|
+
build,
|
|
8361
|
+
channel,
|
|
8362
|
+
dirty: build?.source_dirty,
|
|
8363
|
+
errors,
|
|
8364
|
+
fingerprint: sha256(JSON.stringify({
|
|
8365
|
+
build_fingerprint: build?.build_fingerprint,
|
|
8366
|
+
channel,
|
|
8367
|
+
install_integrity: install?.package_integrity,
|
|
8368
|
+
origin: "package",
|
|
8369
|
+
root,
|
|
8370
|
+
version: info.version
|
|
8371
|
+
})),
|
|
8372
|
+
git_sha: gitSha,
|
|
8373
|
+
install,
|
|
8374
|
+
origin: "package",
|
|
8375
|
+
package_version: info.version,
|
|
8376
|
+
root,
|
|
8377
|
+
source_fingerprint: sourceFingerprint,
|
|
8378
|
+
verified: errors.length === 0
|
|
8379
|
+
};
|
|
8380
|
+
}
|
|
8381
|
+
function getLineageCodeIdentity(channel, options = {}) {
|
|
8382
|
+
const root = canonicalRoot(options.root || codeRoot);
|
|
8383
|
+
const info = packageInfo(root);
|
|
8384
|
+
if (existsSync11(join12(root, ".git"))) return checkoutCodeIdentity(root, channel, info.version);
|
|
8385
|
+
if (existsSync11(join12(root, "package.json"))) {
|
|
8386
|
+
return packageCodeIdentity(root, channel, info, options.receiptPath ?? process.env.LINEAGE_RUNTIME_RECEIPT);
|
|
8387
|
+
}
|
|
8388
|
+
const errors = [`Code root has neither checkout metadata nor package.json: ${root}`];
|
|
8389
|
+
return {
|
|
8390
|
+
channel,
|
|
8391
|
+
errors,
|
|
8392
|
+
fingerprint: sha256(JSON.stringify({ channel, origin: "unknown", root, version: info.version })),
|
|
8393
|
+
origin: "unknown",
|
|
8394
|
+
package_version: info.version,
|
|
8395
|
+
root,
|
|
8396
|
+
verified: false
|
|
8397
|
+
};
|
|
8398
|
+
}
|
|
8399
|
+
function assertLineageCodeOrigin(channel) {
|
|
8400
|
+
const identity = getLineageCodeIdentity(channel);
|
|
8401
|
+
if (!identity.verified) {
|
|
8402
|
+
const migration = channel === "dev" ? "Run dev from a Git checkout with npm run lineage:dev -- <command>." : `Install and launch an isolated ${channel} runtime with lineage-channel install ${channel}.`;
|
|
8403
|
+
throw new Error(`Unverified ${channel} code origin: ${identity.errors.join("; ")}. ${migration}`);
|
|
8404
|
+
}
|
|
8405
|
+
return identity;
|
|
8145
8406
|
}
|
|
8146
8407
|
function tableExists2(database, table) {
|
|
8147
8408
|
return Boolean(database.prepare("select name from sqlite_master where type = 'table' and name = ?").get(table));
|
|
@@ -8159,6 +8420,7 @@ function getLineageRuntimeInfo(options = {}) {
|
|
|
8159
8420
|
const info = packageInfo();
|
|
8160
8421
|
const dbPath = options.dbPath || lineageDbPath();
|
|
8161
8422
|
const channel = normalizeRuntimeChannel(options.channel || process.env.LINEAGE_CHANNEL || process.env.LINEAGE_RELEASE_CHANNEL);
|
|
8423
|
+
const code = options.code || getLineageCodeIdentity(channel);
|
|
8162
8424
|
const databaseInfo = { exists: existsSync11(dbPath), path: dbPath };
|
|
8163
8425
|
const schema = { migration_keys: [] };
|
|
8164
8426
|
if (databaseInfo.exists) {
|
|
@@ -8173,11 +8435,13 @@ function getLineageRuntimeInfo(options = {}) {
|
|
|
8173
8435
|
databaseInfo.workspaces = tableCount(database, "lineage_workspaces");
|
|
8174
8436
|
schema.migration_keys = migrationKeys(database);
|
|
8175
8437
|
if (tableExists2(database, "lineage_profile_identity")) {
|
|
8176
|
-
const
|
|
8177
|
-
|
|
8438
|
+
const columns = new Set(database.prepare("pragma table_info(lineage_profile_identity)").all().map((row) => row.name));
|
|
8439
|
+
const fingerprintExpression = columns.has("profile_fingerprint") ? "profile_fingerprint" : "null as profile_fingerprint";
|
|
8440
|
+
const rows = database.prepare(`select profile_id, environment, ${fingerprintExpression} from lineage_profile_identity`).all();
|
|
8178
8441
|
if (rows.length === 1) {
|
|
8179
8442
|
schema.profile_id = rows[0].profile_id;
|
|
8180
8443
|
schema.profile_environment = rows[0].environment;
|
|
8444
|
+
if (rows[0].profile_fingerprint) schema.profile_fingerprint = rows[0].profile_fingerprint;
|
|
8181
8445
|
}
|
|
8182
8446
|
}
|
|
8183
8447
|
} finally {
|
|
@@ -8190,13 +8454,20 @@ function getLineageRuntimeInfo(options = {}) {
|
|
|
8190
8454
|
return {
|
|
8191
8455
|
asset_root: repoRoot,
|
|
8192
8456
|
channel,
|
|
8457
|
+
code,
|
|
8193
8458
|
database: databaseInfo,
|
|
8194
8459
|
fetchedAt: nowIso(),
|
|
8195
|
-
git_sha:
|
|
8460
|
+
git_sha: code.git_sha,
|
|
8196
8461
|
node_env: process.env.NODE_ENV,
|
|
8197
8462
|
package_name: info.name,
|
|
8198
8463
|
profile: runtimeProfileIdentity(channel),
|
|
8199
8464
|
schema,
|
|
8465
|
+
service: {
|
|
8466
|
+
instance_id: process.env.LINEAGE_SERVICE_INSTANCE_ID,
|
|
8467
|
+
launcher_pid: process.env.LINEAGE_LAUNCHER_PID ? Number(process.env.LINEAGE_LAUNCHER_PID) : void 0,
|
|
8468
|
+
pid: process.pid,
|
|
8469
|
+
started_at: processStartedAt
|
|
8470
|
+
},
|
|
8200
8471
|
version: info.version
|
|
8201
8472
|
};
|
|
8202
8473
|
}
|
|
@@ -8275,14 +8546,14 @@ function registerManagedWriterRoute(app2, fields) {
|
|
|
8275
8546
|
}
|
|
8276
8547
|
|
|
8277
8548
|
// src/cli/lineageCli.ts
|
|
8278
|
-
import { existsSync as existsSync13, mkdirSync as mkdirSync8, readFileSync as readFileSync8, writeFileSync as
|
|
8279
|
-
import { dirname as
|
|
8280
|
-
import { fileURLToPath as
|
|
8549
|
+
import { existsSync as existsSync13, mkdirSync as mkdirSync8, readFileSync as readFileSync8, writeFileSync as writeFileSync6 } from "node:fs";
|
|
8550
|
+
import { dirname as dirname7, join as join13, resolve as resolve8 } from "node:path";
|
|
8551
|
+
import { fileURLToPath as fileURLToPath3 } from "node:url";
|
|
8281
8552
|
|
|
8282
8553
|
// src/server/lineageSelectionPacket.ts
|
|
8283
|
-
import { createHash as
|
|
8554
|
+
import { createHash as createHash7 } from "node:crypto";
|
|
8284
8555
|
import { existsSync as existsSync12, statSync as statSync6 } from "node:fs";
|
|
8285
|
-
import { isAbsolute as
|
|
8556
|
+
import { isAbsolute as isAbsolute3, resolve as resolve7 } from "node:path";
|
|
8286
8557
|
var LineageSelectionPacketError = class extends Error {
|
|
8287
8558
|
constructor(message, warnings = [], errors = [], diagnostics = []) {
|
|
8288
8559
|
super(message);
|
|
@@ -8328,7 +8599,7 @@ function resolveWorkspace(project, options) {
|
|
|
8328
8599
|
}
|
|
8329
8600
|
function resolveLocalReference(reference) {
|
|
8330
8601
|
if (!reference) return void 0;
|
|
8331
|
-
if (
|
|
8602
|
+
if (isAbsolute3(reference)) return reference;
|
|
8332
8603
|
const repoRelative = resolve7(repoRoot, reference);
|
|
8333
8604
|
if (existsSync12(repoRelative)) return repoRelative;
|
|
8334
8605
|
return resolve7(repoRoot, ".asset-scratch", reference);
|
|
@@ -8407,7 +8678,7 @@ function assetForNode(node, catalogAsset, warnings) {
|
|
|
8407
8678
|
};
|
|
8408
8679
|
}
|
|
8409
8680
|
function packetId(packetIdentity) {
|
|
8410
|
-
const digest =
|
|
8681
|
+
const digest = createHash7("sha256").update(JSON.stringify(packetIdentity)).digest("hex").slice(0, 24);
|
|
8411
8682
|
return `lineage_packet_${digest}`;
|
|
8412
8683
|
}
|
|
8413
8684
|
var SHA256_PATTERN = /^[a-f0-9]{64}$/;
|
|
@@ -8478,7 +8749,7 @@ function lineageSelectionPacketV2IdentityProjection(packet) {
|
|
|
8478
8749
|
}
|
|
8479
8750
|
function lineageSelectionPacketV2IdentitySha256(packet) {
|
|
8480
8751
|
const projection = lineageSelectionPacketV2IdentityProjection(packet);
|
|
8481
|
-
return
|
|
8752
|
+
return createHash7("sha256").update(canonicalLineageSelectionPacketIdentityJson(projection)).digest("hex");
|
|
8482
8753
|
}
|
|
8483
8754
|
function addDiagnostic(diagnostics, diagnostic) {
|
|
8484
8755
|
if (diagnostics.some((item) => item.code === diagnostic.code && item.severity === diagnostic.severity && item.asset_id === diagnostic.asset_id)) return;
|
|
@@ -8784,7 +9055,7 @@ function getLineageSelectionPacket(project, options = {}) {
|
|
|
8784
9055
|
|
|
8785
9056
|
// src/cli/lineageCli.ts
|
|
8786
9057
|
function packageRoot2() {
|
|
8787
|
-
return resolve8(
|
|
9058
|
+
return resolve8(dirname7(fileURLToPath3(import.meta.url)), "..", "..");
|
|
8788
9059
|
}
|
|
8789
9060
|
function packageVersion() {
|
|
8790
9061
|
try {
|
|
@@ -8881,8 +9152,8 @@ function runLineageDataCommand(command, args) {
|
|
|
8881
9152
|
const out = readOption(args, "--out");
|
|
8882
9153
|
if (out) {
|
|
8883
9154
|
const outPath = resolve8(out);
|
|
8884
|
-
mkdirSync8(
|
|
8885
|
-
|
|
9155
|
+
mkdirSync8(dirname7(outPath), { recursive: true });
|
|
9156
|
+
writeFileSync6(outPath, `${JSON.stringify(packet, null, 2)}
|
|
8886
9157
|
`);
|
|
8887
9158
|
}
|
|
8888
9159
|
return packet;
|
|
@@ -9092,15 +9363,21 @@ function runLineageAgentCommand(command, args) {
|
|
|
9092
9363
|
|
|
9093
9364
|
// src/server.ts
|
|
9094
9365
|
var runtimeChannel = normalizeRuntimeChannel(process.env.LINEAGE_CHANNEL || process.env.LINEAGE_RELEASE_CHANNEL);
|
|
9366
|
+
var startupCode = assertLineageCodeOrigin(runtimeChannel);
|
|
9095
9367
|
assertRuntimeProfileSafety(runtimeChannel);
|
|
9096
|
-
var startupRuntime = getLineageRuntimeInfo({ channel: runtimeChannel });
|
|
9368
|
+
var startupRuntime = getLineageRuntimeInfo({ channel: runtimeChannel, code: startupCode });
|
|
9097
9369
|
if (!process.env.LINEAGE_PROFILE) assertUnselectedDatabaseIsUnbound(startupRuntime);
|
|
9098
9370
|
var startupProfile;
|
|
9099
9371
|
if (process.env.LINEAGE_PROFILE) {
|
|
9100
9372
|
if (!process.env.LINEAGE_PROFILE_ID || !process.env.LINEAGE_PROFILE_ENVIRONMENT || !process.env.LINEAGE_PROFILE_MANIFEST) {
|
|
9101
9373
|
throw new Error("LINEAGE_PROFILE must be resolved by the Lineage CLI before server startup; use lineage start --profile or lineage-dev start --profile");
|
|
9102
9374
|
}
|
|
9103
|
-
const doctor = doctorLineageProfile(process.env.LINEAGE_PROFILE, {
|
|
9375
|
+
const doctor = doctorLineageProfile(process.env.LINEAGE_PROFILE, {
|
|
9376
|
+
channel: runtimeChannel,
|
|
9377
|
+
code: startupRuntime.code,
|
|
9378
|
+
gitSha: startupRuntime.git_sha,
|
|
9379
|
+
version: startupRuntime.version
|
|
9380
|
+
});
|
|
9104
9381
|
if (!doctor.ok) {
|
|
9105
9382
|
const failures = doctor.checks.filter((check) => check.status === "fail").map((check) => `${check.id}: ${check.message}`).join("; ");
|
|
9106
9383
|
throw new Error(`Configured Lineage profile failed doctor: ${failures}`);
|
|
@@ -9109,7 +9386,8 @@ if (process.env.LINEAGE_PROFILE) {
|
|
|
9109
9386
|
startupProfile = doctor.profile;
|
|
9110
9387
|
}
|
|
9111
9388
|
var writerLease = startupProfile ? acquireProfileWriterLease(startupProfile, runtimeChannel, "service") : void 0;
|
|
9112
|
-
delete process.env.LINEAGE_DB_ACCESS;
|
|
9389
|
+
if (startupProfile) delete process.env.LINEAGE_DB_ACCESS;
|
|
9390
|
+
else process.env.LINEAGE_DB_ACCESS = "read-only";
|
|
9113
9391
|
if (writerLease) {
|
|
9114
9392
|
try {
|
|
9115
9393
|
const startupDatabase = lineageDb();
|
|
@@ -9124,7 +9402,10 @@ var port = Number(process.env.PORT || 5173);
|
|
|
9124
9402
|
var host = process.env.HOST || "lineage.localhost";
|
|
9125
9403
|
var isProduction = process.env.NODE_ENV === "production";
|
|
9126
9404
|
var maxUploadBytes = Number(process.env.LINEAGE_MAX_UPLOAD_MB || 200) * 1024 * 1024;
|
|
9127
|
-
var upload = multer({
|
|
9405
|
+
var upload = multer({
|
|
9406
|
+
limits: { fileSize: maxUploadBytes },
|
|
9407
|
+
storage: startupProfile ? multer.diskStorage({ destination: ensureUploadDir() }) : multer.memoryStorage()
|
|
9408
|
+
});
|
|
9128
9409
|
app.use(express2.json({ limit: "1mb" }));
|
|
9129
9410
|
registerManagedWriterRoute(app, {
|
|
9130
9411
|
accepts: lineageCliCanDelegateMutation,
|
|
@@ -9133,6 +9414,13 @@ registerManagedWriterRoute(app, {
|
|
|
9133
9414
|
profile: startupProfile,
|
|
9134
9415
|
writerLease
|
|
9135
9416
|
});
|
|
9417
|
+
app.use((req, res, next) => {
|
|
9418
|
+
if (startupProfile || req.method === "GET" || req.method === "HEAD" || req.method === "OPTIONS") return next();
|
|
9419
|
+
res.status(409).json({
|
|
9420
|
+
error: "profile_required",
|
|
9421
|
+
message: "Persistent writes require a selected named Lineage profile; legacy-unbound service access is read-only."
|
|
9422
|
+
});
|
|
9423
|
+
});
|
|
9136
9424
|
function projectFrom(input) {
|
|
9137
9425
|
const candidate = input.body?.project || input.body?.product || input.query?.project || input.query?.product;
|
|
9138
9426
|
return typeof candidate === "string" ? candidate : defaultProduct;
|
|
@@ -9146,7 +9434,7 @@ app.get("/api/projects", asyncRoute((_req, res) => {
|
|
|
9146
9434
|
res.json({ projects: listProjects() });
|
|
9147
9435
|
}));
|
|
9148
9436
|
app.get("/api/runtime", asyncRoute((_req, res) => {
|
|
9149
|
-
res.json({ ok: true, runtime: getLineageRuntimeInfo() });
|
|
9437
|
+
res.json({ ok: true, runtime: getLineageRuntimeInfo({ channel: runtimeChannel, code: startupCode }) });
|
|
9150
9438
|
}));
|
|
9151
9439
|
app.get(
|
|
9152
9440
|
"/api/assets",
|