@mean-weasel/lineage 0.1.15 → 0.1.17
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 +13 -0
- package/README.md +249 -19
- package/dist/cli/lineage-channel.js +25 -5
- package/dist/cli/lineage-channel.js.map +2 -2
- package/dist/cli/lineage-dev.js +891 -52
- package/dist/cli/lineage-dev.js.map +4 -4
- package/dist/cli/lineage-preview.js +891 -52
- package/dist/cli/lineage-preview.js.map +4 -4
- package/dist/cli/lineage.js +891 -52
- package/dist/cli/lineage.js.map +4 -4
- package/dist/cli/managed-service.js +19 -5
- package/dist/cli/managed-service.js.map +2 -2
- package/dist/runtime-build.json +4 -4
- package/dist/server.js +883 -89
- package/dist/server.js.map +4 -4
- package/dist/web/assets/agent-shared-state-DOfpTLiw.webp +0 -0
- package/dist/web/assets/agent-to-canvas-DtIu_cPq.mp4 +0 -0
- package/dist/web/assets/{app-CDxSvoD3.css → app-B-fAyNsU.css} +1 -1
- package/dist/web/assets/app-DMogkUoS.js +16 -0
- package/dist/web/assets/attempt-history-D6wdic6n.webp +0 -0
- package/dist/web/assets/branching-tree-B8I5_S6D.png +0 -0
- package/dist/web/assets/canvas-cli-DHBacq7r.png +0 -0
- package/dist/web/assets/hero-agent-sync-CSyh0tHy.mp4 +0 -0
- package/dist/web/assets/hero-board-DG3ED4AW.webp +0 -0
- package/dist/web/assets/hero-lineage-growth-Bsl9tiq-.mp4 +0 -0
- package/dist/web/assets/hero-trace-connections-Bb52ABBc.mp4 +0 -0
- package/dist/web/assets/human-selection-BdDBDE-I.webp +0 -0
- package/dist/web/assets/human-to-agent-D1z92ZO9.mp4 +0 -0
- package/dist/web/assets/{jsx-runtime-_Rdg6et1.js → jsx-runtime-DAFSxiwi.js} +1 -1
- package/dist/web/assets/landing-Bn4qKbIO.css +1 -0
- package/dist/web/assets/landing-CoyGmxBo.js +1 -0
- package/dist/web/assets/reroll-history-DQcEH42R.mp4 +0 -0
- package/dist/web/index.html +3 -3
- package/dist/web/landing/index.html +3 -3
- package/fixtures/demo-project/lineage/swissifier-rich-demo.json +13 -13
- package/package.json +4 -3
- package/dist/web/assets/app-CYUgf6rX.js +0 -16
- package/dist/web/assets/landing-D3Y679_B.css +0 -1
- package/dist/web/assets/landing-ii8AH_DG.js +0 -1
package/dist/cli/lineage-dev.js
CHANGED
|
@@ -8,6 +8,32 @@ import { dirname as dirname6, join as join10, resolve as resolve8 } from "node:p
|
|
|
8
8
|
import { spawn } from "node:child_process";
|
|
9
9
|
import { fileURLToPath as fileURLToPath3 } from "node:url";
|
|
10
10
|
|
|
11
|
+
// src/shared/edgeSummary.ts
|
|
12
|
+
var edgeSummaryWordLimit = 2;
|
|
13
|
+
var EdgeSummaryValidationError = class extends Error {
|
|
14
|
+
constructor(code, message) {
|
|
15
|
+
super(message);
|
|
16
|
+
this.code = code;
|
|
17
|
+
this.name = "EdgeSummaryValidationError";
|
|
18
|
+
}
|
|
19
|
+
code;
|
|
20
|
+
};
|
|
21
|
+
function normalizeEdgeSummary(value) {
|
|
22
|
+
if (value === void 0 || value === null) return void 0;
|
|
23
|
+
if (typeof value !== "string") throw new EdgeSummaryValidationError("not_text", "Edge summary must be text");
|
|
24
|
+
const words = value.trim().split(/\s+/u).filter(Boolean);
|
|
25
|
+
if (words.length === 0) return void 0;
|
|
26
|
+
if (words.length > edgeSummaryWordLimit) {
|
|
27
|
+
throw new EdgeSummaryValidationError("too_many_words", `Edge summary must contain at most ${edgeSummaryWordLimit} words`);
|
|
28
|
+
}
|
|
29
|
+
return words.join(" ");
|
|
30
|
+
}
|
|
31
|
+
function requireEdgeSummary(value) {
|
|
32
|
+
const summary = normalizeEdgeSummary(value);
|
|
33
|
+
if (!summary) throw new EdgeSummaryValidationError("required", "Edge summary is required");
|
|
34
|
+
return summary;
|
|
35
|
+
}
|
|
36
|
+
|
|
11
37
|
// src/server/agentClaims.ts
|
|
12
38
|
import { createHash as createHash4, randomBytes, timingSafeEqual as timingSafeEqual2 } from "node:crypto";
|
|
13
39
|
|
|
@@ -619,6 +645,7 @@ import { dirname as dirname3, isAbsolute, join as join4, relative as relative2,
|
|
|
619
645
|
|
|
620
646
|
// src/shared/lineageProfileTypes.ts
|
|
621
647
|
var lineageProfileSchemaVersion = "lineage.profile.v1";
|
|
648
|
+
var lineageProfileInitSchemaVersion = "lineage.profile_init.v1";
|
|
622
649
|
var lineageProfileDoctorSchemaVersion = "lineage.profile_doctor.v1";
|
|
623
650
|
var lineageProfileCloneReceiptSchemaVersion = "lineage.profile_clone_receipt.v1";
|
|
624
651
|
var lineageProfileAssetsCloneReceiptSchemaVersion = "lineage.profile_assets_clone_receipt.v1";
|
|
@@ -654,13 +681,13 @@ function profileManifestPath(selector) {
|
|
|
654
681
|
if (!profileIdPattern.test(value)) throw new Error(`Invalid profile ID: ${value}`);
|
|
655
682
|
return { manifestPath: join4(lineageProfileRoot(), value, "profile.json"), namedProfileId: value };
|
|
656
683
|
}
|
|
657
|
-
function requiredString(
|
|
658
|
-
const value =
|
|
684
|
+
function requiredString(record2, key) {
|
|
685
|
+
const value = record2[key];
|
|
659
686
|
if (typeof value !== "string" || !value.trim()) throw new Error(`Profile manifest requires a non-empty ${key}`);
|
|
660
687
|
return value.trim();
|
|
661
688
|
}
|
|
662
|
-
function optionalString(
|
|
663
|
-
const value =
|
|
689
|
+
function optionalString(record2, key) {
|
|
690
|
+
const value = record2[key];
|
|
664
691
|
if (value === void 0) return void 0;
|
|
665
692
|
if (typeof value !== "string" || !value.trim()) throw new Error(`Profile manifest ${key} must be a non-empty string`);
|
|
666
693
|
return value.trim();
|
|
@@ -711,15 +738,15 @@ function resolveLineageProfile(selector) {
|
|
|
711
738
|
throw new Error(`Could not parse profile manifest ${manifestPath}: ${error instanceof Error ? error.message : String(error)}`, { cause: error });
|
|
712
739
|
}
|
|
713
740
|
if (!raw || typeof raw !== "object" || Array.isArray(raw)) throw new Error("Profile manifest must be a JSON object");
|
|
714
|
-
const
|
|
715
|
-
const schemaVersion = requiredString(
|
|
741
|
+
const record2 = raw;
|
|
742
|
+
const schemaVersion = requiredString(record2, "schema_version");
|
|
716
743
|
if (schemaVersion !== lineageProfileSchemaVersion) throw new Error(`Unsupported profile schema_version: ${schemaVersion}`);
|
|
717
|
-
const profileId = requiredString(
|
|
744
|
+
const profileId = requiredString(record2, "profile_id");
|
|
718
745
|
if (!profileIdPattern.test(profileId)) throw new Error(`Invalid profile ID: ${profileId}`);
|
|
719
746
|
if (namedProfileId && namedProfileId !== profileId) {
|
|
720
747
|
throw new Error(`Named profile ${namedProfileId} does not match immutable manifest profile_id ${profileId}`);
|
|
721
748
|
}
|
|
722
|
-
const expectedRaw =
|
|
749
|
+
const expectedRaw = record2.expected_runtime;
|
|
723
750
|
if (expectedRaw !== void 0 && (!expectedRaw || typeof expectedRaw !== "object" || Array.isArray(expectedRaw))) {
|
|
724
751
|
throw new Error("Profile expected_runtime must be an object");
|
|
725
752
|
}
|
|
@@ -728,18 +755,18 @@ function resolveLineageProfile(selector) {
|
|
|
728
755
|
const expectedVersion = expected ? optionalString(expected, "version") : void 0;
|
|
729
756
|
const expectedCodeFingerprint = validateFingerprint(expected ? optionalString(expected, "code_fingerprint") : void 0, "expected_runtime.code_fingerprint");
|
|
730
757
|
const expectedCodeOrigin = validateCodeOrigin(expected?.code_origin);
|
|
731
|
-
const migrationsRaw =
|
|
758
|
+
const migrationsRaw = record2.required_schema_migrations;
|
|
732
759
|
if (migrationsRaw !== void 0 && (!Array.isArray(migrationsRaw) || migrationsRaw.some((value) => typeof value !== "string" || !value.trim()))) {
|
|
733
760
|
throw new Error("Profile required_schema_migrations must be an array of non-empty strings");
|
|
734
761
|
}
|
|
735
|
-
const environment = validateEnvironment(requiredString(
|
|
762
|
+
const environment = validateEnvironment(requiredString(record2, "environment"));
|
|
736
763
|
const expectedChannel = validateChannel(expected?.channel);
|
|
737
764
|
if (expectedChannel && expectedChannel !== environmentChannel(environment)) {
|
|
738
765
|
throw new Error(`Profile environment ${environment} conflicts with expected runtime channel ${expectedChannel}`);
|
|
739
766
|
}
|
|
740
767
|
const manifest = {
|
|
741
|
-
asset_root: resolveManifestPath(manifestPath, requiredString(
|
|
742
|
-
database_path: resolveManifestPath(manifestPath, requiredString(
|
|
768
|
+
asset_root: resolveManifestPath(manifestPath, requiredString(record2, "asset_root")),
|
|
769
|
+
database_path: resolveManifestPath(manifestPath, requiredString(record2, "database_path")),
|
|
743
770
|
environment,
|
|
744
771
|
...expected ? {
|
|
745
772
|
expected_runtime: {
|
|
@@ -753,7 +780,7 @@ function resolveLineageProfile(selector) {
|
|
|
753
780
|
profile_id: profileId,
|
|
754
781
|
...migrationsRaw ? { required_schema_migrations: migrationsRaw.map((value) => value.trim()) } : {},
|
|
755
782
|
schema_version: lineageProfileSchemaVersion,
|
|
756
|
-
service_origin: validateServiceOrigin(requiredString(
|
|
783
|
+
service_origin: validateServiceOrigin(requiredString(record2, "service_origin"))
|
|
757
784
|
};
|
|
758
785
|
return { ...manifest, manifest_path: manifestPath, profile_fingerprint: lineageProfileFingerprint(manifest) };
|
|
759
786
|
}
|
|
@@ -767,6 +794,120 @@ function lineageProfileFingerprint(profile) {
|
|
|
767
794
|
service_origin: profile.service_origin
|
|
768
795
|
})).digest("hex");
|
|
769
796
|
}
|
|
797
|
+
function initializeLineageProfile(profileId, serviceOrigin, runtime, confirmWrite, withWriterLease) {
|
|
798
|
+
if (!confirmWrite) throw new Error("Profile init requires --confirm-write");
|
|
799
|
+
if (!profileIdPattern.test(profileId)) throw new Error(`Invalid profile ID: ${profileId}`);
|
|
800
|
+
if (!runtime.code?.verified) throw new Error("Profile init requires a verified runtime code identity");
|
|
801
|
+
if (runtime.code.origin !== "checkout" && runtime.code.origin !== "package") {
|
|
802
|
+
throw new Error(`Profile init requires checkout or package code, not ${runtime.code.origin}`);
|
|
803
|
+
}
|
|
804
|
+
if (!runtime.code.fingerprint || !/^[a-f0-9]{64}$/i.test(runtime.code.fingerprint)) {
|
|
805
|
+
throw new Error("Profile init requires a valid executing code fingerprint");
|
|
806
|
+
}
|
|
807
|
+
const validatedServiceOrigin = validateServiceOrigin(serviceOrigin);
|
|
808
|
+
const root = lineageProfileRoot();
|
|
809
|
+
mkdirSync3(root, { recursive: true, mode: 448 });
|
|
810
|
+
const profileDirectory = join4(root, profileId);
|
|
811
|
+
try {
|
|
812
|
+
mkdirSync3(profileDirectory, { mode: 448 });
|
|
813
|
+
} catch (error) {
|
|
814
|
+
if (error && typeof error === "object" && "code" in error && error.code === "EEXIST") {
|
|
815
|
+
const doctor = doctorLineageProfile(profileId, runtime);
|
|
816
|
+
if (doctor.ok) {
|
|
817
|
+
throw new Error(`Profile ${profileId} already exists and passes doctor. No files were changed. Reuse it with \`start --profile ${profileId}\`; verify it first with \`profile doctor --profile ${profileId} --json\`.`, { cause: error });
|
|
818
|
+
}
|
|
819
|
+
const failures = doctor.checks.filter((check) => check.status === "fail").map((check) => `${check.id}: ${check.message}`).join("; ");
|
|
820
|
+
throw new Error(`Profile ${profileId} already exists but did not pass doctor. No files were changed. Run \`profile doctor --profile ${profileId} --json\`, then inspect ${profileDirectory} manually before retrying. ${failures}`, { cause: error });
|
|
821
|
+
}
|
|
822
|
+
throw error;
|
|
823
|
+
}
|
|
824
|
+
const manifestPath = join4(profileDirectory, "profile.json");
|
|
825
|
+
const databasePath = join4(profileDirectory, "lineage.sqlite");
|
|
826
|
+
const assetRoot = join4(profileDirectory, "media");
|
|
827
|
+
const environment = channelEnvironment(runtime.channel);
|
|
828
|
+
const manifest = {
|
|
829
|
+
asset_root: assetRoot,
|
|
830
|
+
database_path: databasePath,
|
|
831
|
+
environment,
|
|
832
|
+
expected_runtime: {
|
|
833
|
+
channel: runtime.channel,
|
|
834
|
+
code_fingerprint: runtime.code.fingerprint.toLowerCase(),
|
|
835
|
+
code_origin: runtime.code.origin
|
|
836
|
+
},
|
|
837
|
+
profile_id: profileId,
|
|
838
|
+
schema_version: lineageProfileSchemaVersion,
|
|
839
|
+
service_origin: validatedServiceOrigin
|
|
840
|
+
};
|
|
841
|
+
const profile = {
|
|
842
|
+
...manifest,
|
|
843
|
+
manifest_path: manifestPath,
|
|
844
|
+
profile_fingerprint: lineageProfileFingerprint(manifest)
|
|
845
|
+
};
|
|
846
|
+
try {
|
|
847
|
+
assertProfileChannel(profile, runtime.channel);
|
|
848
|
+
assertProfileRuntimePin(profile, runtime);
|
|
849
|
+
const identity2 = withWriterLease(profile, () => {
|
|
850
|
+
mkdirSync3(assetRoot, { mode: 448 });
|
|
851
|
+
const databaseFd = openSync(databasePath, fsConstants.O_CREAT | fsConstants.O_EXCL | fsConstants.O_RDWR, 384);
|
|
852
|
+
closeSync(databaseFd);
|
|
853
|
+
const { DatabaseSync } = require2("node:sqlite");
|
|
854
|
+
const database = new DatabaseSync(databasePath);
|
|
855
|
+
let boundIdentity;
|
|
856
|
+
try {
|
|
857
|
+
boundIdentity = bindOpenDatabase(database, profile, false);
|
|
858
|
+
} finally {
|
|
859
|
+
database.close();
|
|
860
|
+
}
|
|
861
|
+
const manifestPayload = {
|
|
862
|
+
asset_root: "./media",
|
|
863
|
+
database_path: "./lineage.sqlite",
|
|
864
|
+
environment: profile.environment,
|
|
865
|
+
expected_runtime: profile.expected_runtime,
|
|
866
|
+
profile_id: profile.profile_id,
|
|
867
|
+
schema_version: profile.schema_version,
|
|
868
|
+
service_origin: profile.service_origin
|
|
869
|
+
};
|
|
870
|
+
writeFileSync2(manifestPath, `${JSON.stringify(manifestPayload, null, 2)}
|
|
871
|
+
`, { encoding: "utf8", flag: "wx", mode: 384 });
|
|
872
|
+
const manifestFd = openSync(manifestPath, "r");
|
|
873
|
+
try {
|
|
874
|
+
fsyncSync(manifestFd);
|
|
875
|
+
} finally {
|
|
876
|
+
closeSync(manifestFd);
|
|
877
|
+
}
|
|
878
|
+
const directoryFd = openSync(profileDirectory, "r");
|
|
879
|
+
try {
|
|
880
|
+
fsyncSync(directoryFd);
|
|
881
|
+
} finally {
|
|
882
|
+
closeSync(directoryFd);
|
|
883
|
+
}
|
|
884
|
+
const published = resolveLineageProfile(profileId);
|
|
885
|
+
if (published.profile_fingerprint !== profile.profile_fingerprint) {
|
|
886
|
+
throw new Error("Initialized profile fingerprint does not match its published manifest");
|
|
887
|
+
}
|
|
888
|
+
return boundIdentity;
|
|
889
|
+
});
|
|
890
|
+
return {
|
|
891
|
+
asset_root: profile.asset_root,
|
|
892
|
+
database_path: profile.database_path,
|
|
893
|
+
environment: profile.environment,
|
|
894
|
+
identity: identity2,
|
|
895
|
+
manifest_path: profile.manifest_path,
|
|
896
|
+
profile_fingerprint: profile.profile_fingerprint,
|
|
897
|
+
profile_id: profile.profile_id,
|
|
898
|
+
runtime: {
|
|
899
|
+
channel: runtime.channel,
|
|
900
|
+
code_fingerprint: runtime.code.fingerprint,
|
|
901
|
+
code_origin: runtime.code.origin
|
|
902
|
+
},
|
|
903
|
+
schema_version: lineageProfileInitSchemaVersion,
|
|
904
|
+
service_origin: profile.service_origin
|
|
905
|
+
};
|
|
906
|
+
} catch (error) {
|
|
907
|
+
rmSync(profileDirectory, { force: true, recursive: true });
|
|
908
|
+
throw error;
|
|
909
|
+
}
|
|
910
|
+
}
|
|
770
911
|
function sha256(value) {
|
|
771
912
|
return createHash2("sha256").update(value).digest("hex");
|
|
772
913
|
}
|
|
@@ -1498,7 +1639,7 @@ function acquireProfileWriterLease(profile, channel, role = "service") {
|
|
|
1498
1639
|
}
|
|
1499
1640
|
function assertProfileWriterLeaseHeld() {
|
|
1500
1641
|
if (!process.env.LINEAGE_PROFILE) {
|
|
1501
|
-
throw new Error("Persistent writes require a selected named Lineage profile and its writer lease; legacy-unbound access is read-only");
|
|
1642
|
+
throw new Error("Persistent writes require a selected named Lineage profile and its writer lease; legacy-unbound access is read-only. Create a fresh profile with `profile init --profile <id> --confirm-write`, then pass that profile to the command");
|
|
1502
1643
|
}
|
|
1503
1644
|
const profileId = process.env.LINEAGE_PROFILE_ID;
|
|
1504
1645
|
const profileFingerprint = process.env.LINEAGE_PROFILE_FINGERPRINT;
|
|
@@ -1622,6 +1763,10 @@ function lineageDb() {
|
|
|
1622
1763
|
child_asset_id text not null references assets(id),
|
|
1623
1764
|
relation_type text not null check (relation_type in ('derived_from')),
|
|
1624
1765
|
created_at text not null,
|
|
1766
|
+
summary text,
|
|
1767
|
+
summary_created_by text check (summary_created_by in ('human', 'agent', 'system')),
|
|
1768
|
+
summary_updated_by text check (summary_updated_by in ('human', 'agent', 'system')),
|
|
1769
|
+
summary_updated_at text,
|
|
1625
1770
|
unique (project_id, parent_asset_id, child_asset_id, relation_type)
|
|
1626
1771
|
);
|
|
1627
1772
|
create index if not exists edges_parent on asset_edges(project_id, parent_asset_id);
|
|
@@ -1905,6 +2050,7 @@ function lineageDb() {
|
|
|
1905
2050
|
imported_asset_id text not null references assets(id),
|
|
1906
2051
|
parent_asset_id text not null references assets(id),
|
|
1907
2052
|
imported_at text not null,
|
|
2053
|
+
edge_summary text,
|
|
1908
2054
|
unique(job_id, output_index),
|
|
1909
2055
|
unique(job_id, file_path)
|
|
1910
2056
|
);
|
|
@@ -1993,6 +2139,11 @@ function lineageDb() {
|
|
|
1993
2139
|
);
|
|
1994
2140
|
create index if not exists agent_claim_events_claim_created on agent_claim_events(claim_id, created_at);
|
|
1995
2141
|
`);
|
|
2142
|
+
ensureColumn(database, "asset_edges", "summary", "text");
|
|
2143
|
+
ensureColumn(database, "asset_edges", "summary_created_by", "text check (summary_created_by in ('human', 'agent', 'system'))");
|
|
2144
|
+
ensureColumn(database, "asset_edges", "summary_updated_by", "text check (summary_updated_by in ('human', 'agent', 'system'))");
|
|
2145
|
+
ensureColumn(database, "asset_edges", "summary_updated_at", "text");
|
|
2146
|
+
ensureColumn(database, "generation_job_outputs", "edge_summary", "text");
|
|
1996
2147
|
migrateAssetSelections(database);
|
|
1997
2148
|
dropLegacyAssetSelectionRootUnique(database);
|
|
1998
2149
|
ensureColumn(database, "asset_selections", "notes", "text");
|
|
@@ -3118,6 +3269,14 @@ function cancelLineageTask(project, fields) {
|
|
|
3118
3269
|
database.close();
|
|
3119
3270
|
}
|
|
3120
3271
|
}
|
|
3272
|
+
function cancelLineageIterateTasksForAssets(project, fields) {
|
|
3273
|
+
const targetIds = fields.assetIds ? new Set(fields.assetIds) : void 0;
|
|
3274
|
+
return listLineageTasks(project, fields.rootAssetId, ["pending"]).tasks.filter((task) => task.task_type === "iterate").filter((task) => !targetIds || targetIds.has(task.target_asset_id)).map((task) => cancelLineageTask(project, {
|
|
3275
|
+
actor: fields.actor,
|
|
3276
|
+
confirmWrite: fields.confirmWrite,
|
|
3277
|
+
taskId: task.id
|
|
3278
|
+
}));
|
|
3279
|
+
}
|
|
3121
3280
|
function resolveLineageTask(project, fields) {
|
|
3122
3281
|
const normalizedProject = normalizeProject(project);
|
|
3123
3282
|
const actor = normalizeActor(fields.actor, "Resolve actor");
|
|
@@ -3483,6 +3642,23 @@ function rerollRequestId(project, root, node, timestamp) {
|
|
|
3483
3642
|
function rowString(value) {
|
|
3484
3643
|
return typeof value === "string" && value.length > 0 ? value : void 0;
|
|
3485
3644
|
}
|
|
3645
|
+
function lineageEdgeFromRow(row) {
|
|
3646
|
+
const summary = rowString(row.summary);
|
|
3647
|
+
const summaryCreatedBy = rowString(row.summary_created_by);
|
|
3648
|
+
const summaryUpdatedBy = rowString(row.summary_updated_by);
|
|
3649
|
+
const summaryUpdatedAt = rowString(row.summary_updated_at);
|
|
3650
|
+
return {
|
|
3651
|
+
id: String(row.id),
|
|
3652
|
+
parent_asset_id: String(row.parent_asset_id),
|
|
3653
|
+
child_asset_id: String(row.child_asset_id),
|
|
3654
|
+
relation_type: row.relation_type,
|
|
3655
|
+
created_at: String(row.created_at),
|
|
3656
|
+
...summary ? { summary } : {},
|
|
3657
|
+
...summaryCreatedBy ? { summary_created_by: summaryCreatedBy } : {},
|
|
3658
|
+
...summaryUpdatedBy ? { summary_updated_by: summaryUpdatedBy } : {},
|
|
3659
|
+
...summaryUpdatedAt ? { summary_updated_at: summaryUpdatedAt } : {}
|
|
3660
|
+
};
|
|
3661
|
+
}
|
|
3486
3662
|
function rerollRequestFrom(row) {
|
|
3487
3663
|
return {
|
|
3488
3664
|
id: String(row.id),
|
|
@@ -3582,6 +3758,9 @@ function localPreviewUrl(project, localPath) {
|
|
|
3582
3758
|
return `/api/assets/local-preview?${params.toString()}`;
|
|
3583
3759
|
}
|
|
3584
3760
|
function linkLineageAssets(project, fields) {
|
|
3761
|
+
const summary = normalizeEdgeSummary(fields.summary);
|
|
3762
|
+
if (summary && !fields.summaryActor) throw new LineageError("Lineage edge summary requires an explicit author");
|
|
3763
|
+
if (!summary && fields.summaryActor) throw new LineageError("Lineage edge summary author requires a summary");
|
|
3585
3764
|
const database = lineageDb();
|
|
3586
3765
|
requireAsset2(database, project, fields.parentAssetId);
|
|
3587
3766
|
requireAsset2(database, project, fields.childAssetId);
|
|
@@ -3600,22 +3779,66 @@ function linkLineageAssets(project, fields) {
|
|
|
3600
3779
|
database.close();
|
|
3601
3780
|
throw error;
|
|
3602
3781
|
}
|
|
3782
|
+
const createdAt = nowIso();
|
|
3603
3783
|
const edge = {
|
|
3604
3784
|
id: edgeId(project, fields.parentAssetId, fields.childAssetId),
|
|
3605
3785
|
parent_asset_id: fields.parentAssetId,
|
|
3606
3786
|
child_asset_id: fields.childAssetId,
|
|
3607
3787
|
relation_type: "derived_from",
|
|
3608
|
-
created_at:
|
|
3788
|
+
created_at: createdAt,
|
|
3789
|
+
...summary ? {
|
|
3790
|
+
summary,
|
|
3791
|
+
summary_created_by: fields.summaryActor,
|
|
3792
|
+
summary_updated_by: fields.summaryActor,
|
|
3793
|
+
summary_updated_at: createdAt
|
|
3794
|
+
} : {}
|
|
3609
3795
|
};
|
|
3610
3796
|
if (!fields.confirmWrite) {
|
|
3611
3797
|
database.close();
|
|
3612
3798
|
return { ok: true, dryRun: true, edge };
|
|
3613
3799
|
}
|
|
3614
|
-
database.prepare(`
|
|
3615
|
-
insert into asset_edges (
|
|
3616
|
-
|
|
3800
|
+
const inserted = database.prepare(`
|
|
3801
|
+
insert into asset_edges (
|
|
3802
|
+
id, project_id, parent_asset_id, child_asset_id, relation_type, created_at,
|
|
3803
|
+
summary, summary_created_by, summary_updated_by, summary_updated_at
|
|
3804
|
+
)
|
|
3805
|
+
values (?, ?, ?, ?, 'derived_from', ?, ?, ?, ?, ?)
|
|
3617
3806
|
on conflict(project_id, parent_asset_id, child_asset_id, relation_type) do nothing
|
|
3618
|
-
`).run(
|
|
3807
|
+
`).run(
|
|
3808
|
+
edge.id,
|
|
3809
|
+
project,
|
|
3810
|
+
edge.parent_asset_id,
|
|
3811
|
+
edge.child_asset_id,
|
|
3812
|
+
edge.created_at,
|
|
3813
|
+
edge.summary || null,
|
|
3814
|
+
edge.summary_created_by || null,
|
|
3815
|
+
edge.summary_updated_by || null,
|
|
3816
|
+
edge.summary_updated_at || null
|
|
3817
|
+
);
|
|
3818
|
+
if (inserted.changes === 0) {
|
|
3819
|
+
const existingRow = database.prepare(`
|
|
3820
|
+
select id, parent_asset_id, child_asset_id, relation_type, created_at,
|
|
3821
|
+
summary, summary_created_by, summary_updated_by, summary_updated_at
|
|
3822
|
+
from asset_edges
|
|
3823
|
+
where project_id = ? and parent_asset_id = ? and child_asset_id = ? and relation_type = 'derived_from'
|
|
3824
|
+
`).get(project, edge.parent_asset_id, edge.child_asset_id);
|
|
3825
|
+
if (!existingRow) {
|
|
3826
|
+
database.close();
|
|
3827
|
+
throw new LineageError("Lineage edge conflict could not be resolved", 409);
|
|
3828
|
+
}
|
|
3829
|
+
const existingEdge = lineageEdgeFromRow(existingRow);
|
|
3830
|
+
if (summary && (existingEdge.summary !== summary || existingEdge.summary_created_by !== fields.summaryActor || existingEdge.summary_updated_by !== fields.summaryActor || !existingEdge.summary_updated_at)) {
|
|
3831
|
+
database.close();
|
|
3832
|
+
throw new LineageError("Lineage edge already exists with a different summary or provenance", 409);
|
|
3833
|
+
}
|
|
3834
|
+
database.close();
|
|
3835
|
+
return {
|
|
3836
|
+
ok: true,
|
|
3837
|
+
idempotent: true,
|
|
3838
|
+
message: `Already linked ${existingEdge.child_asset_id} from ${existingEdge.parent_asset_id}`,
|
|
3839
|
+
edge: existingEdge
|
|
3840
|
+
};
|
|
3841
|
+
}
|
|
3619
3842
|
database.close();
|
|
3620
3843
|
return { ok: true, message: `Linked ${edge.child_asset_id} from ${edge.parent_asset_id}`, edge };
|
|
3621
3844
|
}
|
|
@@ -3627,7 +3850,13 @@ function descendants(database, project, root) {
|
|
|
3627
3850
|
const parent = queue.shift();
|
|
3628
3851
|
if (seen.has(parent)) continue;
|
|
3629
3852
|
seen.add(parent);
|
|
3630
|
-
const rows = database.prepare(
|
|
3853
|
+
const rows = database.prepare(`
|
|
3854
|
+
select id, parent_asset_id, child_asset_id, relation_type, created_at,
|
|
3855
|
+
summary, summary_created_by, summary_updated_by, summary_updated_at
|
|
3856
|
+
from asset_edges
|
|
3857
|
+
where project_id = ? and parent_asset_id = ?
|
|
3858
|
+
order by created_at
|
|
3859
|
+
`).all(project, parent).map(lineageEdgeFromRow);
|
|
3631
3860
|
edges.push(...rows);
|
|
3632
3861
|
queue.push(...rows.map((row) => row.child_asset_id));
|
|
3633
3862
|
}
|
|
@@ -4013,7 +4242,7 @@ function lineageCommand(command, project, rootAssetId) {
|
|
|
4013
4242
|
return `${lineagePublicPackageCommand()} ${command} --project ${shellQuote(project)} --root ${shellQuote(rootAssetId)} ${lineageRuntimeSelector()} --json`;
|
|
4014
4243
|
}
|
|
4015
4244
|
function linkChildCommand(project, rootAssetId) {
|
|
4016
|
-
return `${lineagePublicPackageCommand()} link-child --project ${shellQuote(project)} --root ${shellQuote(rootAssetId)} --child <asset-id> --confirm-write ${lineageRuntimeSelector()} --json`;
|
|
4245
|
+
return `${lineagePublicPackageCommand()} link-child --project ${shellQuote(project)} --root ${shellQuote(rootAssetId)} --child <asset-id> --summary "<one-or-two-words>" --confirm-write ${lineageRuntimeSelector()} --json`;
|
|
4017
4246
|
}
|
|
4018
4247
|
function rerollImportGuidance(rootAssetId, targetAssetId) {
|
|
4019
4248
|
return `Use lineage reroll plan --root ${rootAssetId} --target ${targetAssetId} and lineage reroll import instead.`;
|
|
@@ -4062,6 +4291,7 @@ function getLineageBrief(project, rootAssetId) {
|
|
|
4062
4291
|
};
|
|
4063
4292
|
}
|
|
4064
4293
|
function linkSelectedLineageChild(project, fields) {
|
|
4294
|
+
const summary = fields.summaryActor ? requireEdgeSummary(fields.summary) : fields.summary;
|
|
4065
4295
|
const next = getLineageNextAsset(project, fields.rootAssetId);
|
|
4066
4296
|
if (!next.next_asset) throw new LineageError("Cannot link child until a next base is selected or unambiguous");
|
|
4067
4297
|
const rerollRequests = listLineageRerollRequests(project, next.root_asset_id).requests;
|
|
@@ -4090,7 +4320,9 @@ function linkSelectedLineageChild(project, fields) {
|
|
|
4090
4320
|
childAssetId: fields.childAssetId,
|
|
4091
4321
|
confirmWrite: fields.confirmWrite,
|
|
4092
4322
|
claimToken: fields.claimToken,
|
|
4093
|
-
parentAssetId: next.next_asset.asset_id
|
|
4323
|
+
parentAssetId: next.next_asset.asset_id,
|
|
4324
|
+
summary,
|
|
4325
|
+
summaryActor: fields.summaryActor
|
|
4094
4326
|
});
|
|
4095
4327
|
return {
|
|
4096
4328
|
...result,
|
|
@@ -4110,7 +4342,141 @@ function linkSelectedLineageChild(project, fields) {
|
|
|
4110
4342
|
import { existsSync as existsSync8, realpathSync as realpathSync2, statSync as statSync4 } from "node:fs";
|
|
4111
4343
|
import { relative as relative3, resolve as resolve5 } from "node:path";
|
|
4112
4344
|
import { randomUUID as randomUUID3 } from "node:crypto";
|
|
4113
|
-
|
|
4345
|
+
|
|
4346
|
+
// src/shared/generationOutputManifest.ts
|
|
4347
|
+
var generationOutputManifestSchemaVersion = "lineage.generation_output_manifest.v1";
|
|
4348
|
+
var GenerationOutputManifestError = class extends Error {
|
|
4349
|
+
constructor(message) {
|
|
4350
|
+
super(message);
|
|
4351
|
+
this.name = "GenerationOutputManifestError";
|
|
4352
|
+
}
|
|
4353
|
+
};
|
|
4354
|
+
function record(value, label) {
|
|
4355
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
|
4356
|
+
throw new GenerationOutputManifestError(`${label} must be an object`);
|
|
4357
|
+
}
|
|
4358
|
+
return value;
|
|
4359
|
+
}
|
|
4360
|
+
function exactKeys(value, allowed, label) {
|
|
4361
|
+
const allowedKeys = new Set(allowed);
|
|
4362
|
+
const unsupported = Object.keys(value).filter((key) => !allowedKeys.has(key)).sort();
|
|
4363
|
+
if (unsupported.length > 0) {
|
|
4364
|
+
throw new GenerationOutputManifestError(`${label} contains unsupported field: ${unsupported[0]}`);
|
|
4365
|
+
}
|
|
4366
|
+
}
|
|
4367
|
+
function nonEmptyText(value, label) {
|
|
4368
|
+
if (typeof value !== "string" || !value.trim()) throw new GenerationOutputManifestError(`${label} is required`);
|
|
4369
|
+
return value.trim();
|
|
4370
|
+
}
|
|
4371
|
+
function expectedGenerationOutputParents(job) {
|
|
4372
|
+
if (!Number.isInteger(job.expected_output_count) || job.expected_output_count <= 0) {
|
|
4373
|
+
throw new GenerationOutputManifestError("Generation job expected output count must be a positive integer");
|
|
4374
|
+
}
|
|
4375
|
+
const parents = job.inputs.filter((input) => input.role === "lineage_next_base").sort((left, right) => left.position - right.position);
|
|
4376
|
+
if (parents.length === 0) throw new GenerationOutputManifestError("Generation job has no lineage_next_base input");
|
|
4377
|
+
const parentIds = /* @__PURE__ */ new Set();
|
|
4378
|
+
const parentPositions = /* @__PURE__ */ new Set();
|
|
4379
|
+
for (const parent of parents) {
|
|
4380
|
+
if (typeof parent.asset_id !== "string" || !parent.asset_id || parent.asset_id.trim() !== parent.asset_id) {
|
|
4381
|
+
throw new GenerationOutputManifestError("Generation job has invalid lineage parent asset id");
|
|
4382
|
+
}
|
|
4383
|
+
if (!Number.isInteger(parent.position) || parent.position < 0 || parentPositions.has(parent.position)) {
|
|
4384
|
+
throw new GenerationOutputManifestError("Generation job has invalid lineage parent positions");
|
|
4385
|
+
}
|
|
4386
|
+
if (parentIds.has(parent.asset_id)) throw new GenerationOutputManifestError(`Generation job has duplicate lineage parent: ${parent.asset_id}`);
|
|
4387
|
+
parentIds.add(parent.asset_id);
|
|
4388
|
+
parentPositions.add(parent.position);
|
|
4389
|
+
}
|
|
4390
|
+
if (job.expected_output_count % parents.length !== 0) {
|
|
4391
|
+
throw new GenerationOutputManifestError("Generation job has invalid parent mapping");
|
|
4392
|
+
}
|
|
4393
|
+
const outputsPerParent = job.expected_output_count / parents.length;
|
|
4394
|
+
return Array.from({ length: job.expected_output_count }, (_value, outputIndex) => {
|
|
4395
|
+
return parents[Math.floor(outputIndex / outputsPerParent)].asset_id;
|
|
4396
|
+
});
|
|
4397
|
+
}
|
|
4398
|
+
function createGenerationOutputManifestDraft(job) {
|
|
4399
|
+
return {
|
|
4400
|
+
schema_version: generationOutputManifestSchemaVersion,
|
|
4401
|
+
job_id: job.id,
|
|
4402
|
+
outputs: expectedGenerationOutputParents(job).map((parentAssetId, outputIndex) => ({
|
|
4403
|
+
output_index: outputIndex,
|
|
4404
|
+
file_path: "",
|
|
4405
|
+
parent_asset_id: parentAssetId,
|
|
4406
|
+
edge_summary: ""
|
|
4407
|
+
}))
|
|
4408
|
+
};
|
|
4409
|
+
}
|
|
4410
|
+
function parseGenerationOutputManifest(value, job, options) {
|
|
4411
|
+
if (!options || typeof options.resolveFilePath !== "function") {
|
|
4412
|
+
throw new GenerationOutputManifestError("Generation output manifest requires a file-path resolver");
|
|
4413
|
+
}
|
|
4414
|
+
const manifest = record(value, "Generation output manifest");
|
|
4415
|
+
exactKeys(manifest, ["schema_version", "job_id", "outputs"], "Generation output manifest");
|
|
4416
|
+
if (manifest.schema_version !== generationOutputManifestSchemaVersion) {
|
|
4417
|
+
throw new GenerationOutputManifestError(`Generation output manifest schema_version must be ${generationOutputManifestSchemaVersion}`);
|
|
4418
|
+
}
|
|
4419
|
+
if (manifest.job_id !== job.id) throw new GenerationOutputManifestError(`Generation output manifest job_id must be ${job.id}`);
|
|
4420
|
+
if (!Array.isArray(manifest.outputs)) throw new GenerationOutputManifestError("Generation output manifest outputs must be an array");
|
|
4421
|
+
const expectedParents = expectedGenerationOutputParents(job);
|
|
4422
|
+
if (manifest.outputs.length !== expectedParents.length) {
|
|
4423
|
+
throw new GenerationOutputManifestError(`Generation output manifest requires ${expectedParents.length} outputs, received ${manifest.outputs.length}`);
|
|
4424
|
+
}
|
|
4425
|
+
const seenIndexes = /* @__PURE__ */ new Set();
|
|
4426
|
+
const outputs = manifest.outputs.map((value2, position) => {
|
|
4427
|
+
const output = record(value2, `Generation output at position ${position}`);
|
|
4428
|
+
exactKeys(output, ["output_index", "file_path", "parent_asset_id", "edge_summary"], `Generation output at position ${position}`);
|
|
4429
|
+
const outputIndex = output.output_index;
|
|
4430
|
+
if (!Number.isInteger(outputIndex) || Number(outputIndex) < 0) {
|
|
4431
|
+
throw new GenerationOutputManifestError(`Generation output at position ${position} requires a non-negative integer output_index`);
|
|
4432
|
+
}
|
|
4433
|
+
const normalizedIndex = Number(outputIndex);
|
|
4434
|
+
if (seenIndexes.has(normalizedIndex)) throw new GenerationOutputManifestError(`Duplicate generation output_index: ${normalizedIndex}`);
|
|
4435
|
+
seenIndexes.add(normalizedIndex);
|
|
4436
|
+
const expectedParent = expectedParents[normalizedIndex];
|
|
4437
|
+
if (!expectedParent) throw new GenerationOutputManifestError(`Unknown generation output_index: ${normalizedIndex}`);
|
|
4438
|
+
const filePath = nonEmptyText(output.file_path, `Generation output ${normalizedIndex} file_path`);
|
|
4439
|
+
const parentAssetId = nonEmptyText(output.parent_asset_id, `Generation output ${normalizedIndex} parent_asset_id`);
|
|
4440
|
+
if (parentAssetId !== expectedParent) {
|
|
4441
|
+
throw new GenerationOutputManifestError(`Generation output ${normalizedIndex} must use parent_asset_id ${expectedParent}`);
|
|
4442
|
+
}
|
|
4443
|
+
let edgeSummary;
|
|
4444
|
+
try {
|
|
4445
|
+
edgeSummary = requireEdgeSummary(output.edge_summary);
|
|
4446
|
+
} catch (error) {
|
|
4447
|
+
if (error instanceof EdgeSummaryValidationError) {
|
|
4448
|
+
throw new GenerationOutputManifestError(`Generation output ${normalizedIndex}: ${error.message}`);
|
|
4449
|
+
}
|
|
4450
|
+
throw error;
|
|
4451
|
+
}
|
|
4452
|
+
return {
|
|
4453
|
+
output_index: normalizedIndex,
|
|
4454
|
+
file_path: filePath,
|
|
4455
|
+
parent_asset_id: parentAssetId,
|
|
4456
|
+
edge_summary: edgeSummary
|
|
4457
|
+
};
|
|
4458
|
+
});
|
|
4459
|
+
for (const outputIndex of expectedParents.keys()) {
|
|
4460
|
+
if (!seenIndexes.has(outputIndex)) throw new GenerationOutputManifestError(`Missing generation output_index: ${outputIndex}`);
|
|
4461
|
+
}
|
|
4462
|
+
outputs.sort((left, right) => left.output_index - right.output_index);
|
|
4463
|
+
const seenPaths = /* @__PURE__ */ new Set();
|
|
4464
|
+
const resolvedOutputs = outputs.map((output) => {
|
|
4465
|
+
const filePath = nonEmptyText(options.resolveFilePath(output.file_path), `Generation output ${output.output_index} resolved file_path`);
|
|
4466
|
+
if (seenPaths.has(filePath)) throw new GenerationOutputManifestError(`Duplicate generation output file_path: ${filePath}`);
|
|
4467
|
+
seenPaths.add(filePath);
|
|
4468
|
+
return { ...output, file_path: filePath };
|
|
4469
|
+
});
|
|
4470
|
+
return {
|
|
4471
|
+
schema_version: generationOutputManifestSchemaVersion,
|
|
4472
|
+
job_id: job.id,
|
|
4473
|
+
outputs: resolvedOutputs
|
|
4474
|
+
};
|
|
4475
|
+
}
|
|
4476
|
+
|
|
4477
|
+
// src/server/generationReceipts.ts
|
|
4478
|
+
var legacyAdapterVersion = "generation-receipts-v1";
|
|
4479
|
+
var manifestAdapterVersion = "generation-receipts-v2";
|
|
4114
4480
|
var provider = "codex-handoff";
|
|
4115
4481
|
var GenerationReceiptError = class extends Error {
|
|
4116
4482
|
constructor(message, status = 400) {
|
|
@@ -4129,6 +4495,70 @@ function parseJson(value, fallback) {
|
|
|
4129
4495
|
if (!value) return fallback;
|
|
4130
4496
|
return JSON.parse(value);
|
|
4131
4497
|
}
|
|
4498
|
+
function positiveInteger(value) {
|
|
4499
|
+
return Number.isInteger(value) && Number(value) > 0;
|
|
4500
|
+
}
|
|
4501
|
+
function resolveLineageSelection(project) {
|
|
4502
|
+
const rootAssetId = activeLineageWorkspaceRoot(project);
|
|
4503
|
+
if (!rootAssetId) throw new GenerationReceiptError("No active lineage workspace for generation planning");
|
|
4504
|
+
const next = getLineageNextAsset(project, rootAssetId);
|
|
4505
|
+
if (!next.next_asset) throw new GenerationReceiptError(`No clear lineage next asset: ${next.reason}`);
|
|
4506
|
+
if (next.strategy !== "selected") throw new GenerationReceiptError("Generation v1 requires an explicit selected lineage next base");
|
|
4507
|
+
if (next.selection_mode !== "multiple" && next.selection?.asset_id !== next.next_asset.asset_id) throw new GenerationReceiptError("Generation v1 requires one explicit selected lineage next base");
|
|
4508
|
+
return next;
|
|
4509
|
+
}
|
|
4510
|
+
function selectedParents(next) {
|
|
4511
|
+
const parents = next.next_assets.length > 0 ? next.next_assets : next.next_asset ? [next.next_asset] : [];
|
|
4512
|
+
if (parents.length === 0) throw new GenerationReceiptError("Missing lineage next base");
|
|
4513
|
+
return parents;
|
|
4514
|
+
}
|
|
4515
|
+
function parentMappings(next, perBaseCount) {
|
|
4516
|
+
return selectedParents(next).map((parent, parentIndex) => ({
|
|
4517
|
+
parent,
|
|
4518
|
+
output_indexes: Array.from({ length: perBaseCount }, (_value, index) => parentIndex * perBaseCount + index)
|
|
4519
|
+
}));
|
|
4520
|
+
}
|
|
4521
|
+
function buildHandoff(project, id, prompt, count, perBaseCount, next, inputs) {
|
|
4522
|
+
const parent = next.next_asset;
|
|
4523
|
+
if (!parent) throw new GenerationReceiptError("Missing lineage next base");
|
|
4524
|
+
const parents = parentMappings(next, perBaseCount);
|
|
4525
|
+
const outputManifest = createGenerationOutputManifestDraft({ id, expected_output_count: count, inputs });
|
|
4526
|
+
const importCommand = `${lineagePublicPackageCommand()} generate image import --project ${quote(project)} --job-id ${quote(id)} --manifest ${quote(".asset-scratch/generation-output-manifest.json")} --confirm-write ${lineageRuntimeSelector()} --json`;
|
|
4527
|
+
return {
|
|
4528
|
+
schema_version: "lineage.generation_handoff.v2",
|
|
4529
|
+
provider,
|
|
4530
|
+
project,
|
|
4531
|
+
job_id: id,
|
|
4532
|
+
prompt,
|
|
4533
|
+
expected_output_count: count,
|
|
4534
|
+
per_base_count: next.selection_mode === "multiple" ? perBaseCount : void 0,
|
|
4535
|
+
lineage: {
|
|
4536
|
+
root_asset_id: next.root_asset_id,
|
|
4537
|
+
parent_asset_id: parent.asset_id,
|
|
4538
|
+
selection_strategy: next.strategy,
|
|
4539
|
+
parent_title: parent.title,
|
|
4540
|
+
parent_local_path: parent.local_path,
|
|
4541
|
+
parent_s3_key: parent.s3_key,
|
|
4542
|
+
parents: parents.length > 1 ? parents.map((mapping) => ({
|
|
4543
|
+
parent_asset_id: mapping.parent.asset_id,
|
|
4544
|
+
parent_title: mapping.parent.title,
|
|
4545
|
+
parent_local_path: mapping.parent.local_path,
|
|
4546
|
+
parent_s3_key: mapping.parent.s3_key,
|
|
4547
|
+
output_indexes: mapping.output_indexes
|
|
4548
|
+
})) : void 0
|
|
4549
|
+
},
|
|
4550
|
+
instructions: [
|
|
4551
|
+
"Use Codex image generation outside Lineage server code.",
|
|
4552
|
+
"Write generated output files under .asset-scratch before import.",
|
|
4553
|
+
"Do not call live provider APIs from the CLI or server.",
|
|
4554
|
+
"Fill every output_manifest entry with its generated file path and a one- or two-word edge summary.",
|
|
4555
|
+
"Import the completed manifest with --confirm-write to persist every lineage child."
|
|
4556
|
+
],
|
|
4557
|
+
import_command: importCommand,
|
|
4558
|
+
output_manifest: outputManifest,
|
|
4559
|
+
guardrails: { live_generation: false, external_services: false, output_root: ".asset-scratch", confirm_write_required: true }
|
|
4560
|
+
};
|
|
4561
|
+
}
|
|
4132
4562
|
function buildRerollHandoff(project, id, prompt, rootAssetId, target, request) {
|
|
4133
4563
|
const importCommand = `${lineagePublicPackageCommand()} reroll import --project ${quote(project)} --job-id ${quote(id)} --file <.asset-scratch-file> --confirm-write ${lineageRuntimeSelector()} --json`;
|
|
4134
4564
|
return {
|
|
@@ -4157,6 +4587,19 @@ function buildRerollHandoff(project, id, prompt, rootAssetId, target, request) {
|
|
|
4157
4587
|
guardrails: { live_generation: false, external_services: false, output_root: ".asset-scratch", confirm_write_required: true }
|
|
4158
4588
|
};
|
|
4159
4589
|
}
|
|
4590
|
+
function inputsFrom(jobIdValue, project, next) {
|
|
4591
|
+
return selectedParents(next).map((parent, position) => ({
|
|
4592
|
+
id: `${jobIdValue}:input:${position}`,
|
|
4593
|
+
job_id: jobIdValue,
|
|
4594
|
+
project_id: project,
|
|
4595
|
+
asset_id: parent.asset_id,
|
|
4596
|
+
root_asset_id: next.root_asset_id,
|
|
4597
|
+
role: "lineage_next_base",
|
|
4598
|
+
position,
|
|
4599
|
+
selection_strategy: next.strategy,
|
|
4600
|
+
selection_snapshot: next
|
|
4601
|
+
}));
|
|
4602
|
+
}
|
|
4160
4603
|
function receiptFrom(row) {
|
|
4161
4604
|
return {
|
|
4162
4605
|
id: String(row.id),
|
|
@@ -4169,6 +4612,7 @@ function receiptFrom(row) {
|
|
|
4169
4612
|
};
|
|
4170
4613
|
}
|
|
4171
4614
|
function outputFrom(row) {
|
|
4615
|
+
const edgeSummary = typeof row.edge_summary === "string" && row.edge_summary.length > 0 ? row.edge_summary : void 0;
|
|
4172
4616
|
return {
|
|
4173
4617
|
id: String(row.id),
|
|
4174
4618
|
job_id: String(row.job_id),
|
|
@@ -4180,7 +4624,8 @@ function outputFrom(row) {
|
|
|
4180
4624
|
content_type: String(row.content_type),
|
|
4181
4625
|
imported_asset_id: String(row.imported_asset_id),
|
|
4182
4626
|
parent_asset_id: String(row.parent_asset_id),
|
|
4183
|
-
imported_at: String(row.imported_at)
|
|
4627
|
+
imported_at: String(row.imported_at),
|
|
4628
|
+
...edgeSummary ? { edge_summary: edgeSummary } : {}
|
|
4184
4629
|
};
|
|
4185
4630
|
}
|
|
4186
4631
|
function loadGenerationJob(database, project, id) {
|
|
@@ -4226,6 +4671,72 @@ function insertReceipt(database, id, type, command, payload) {
|
|
|
4226
4671
|
values (?, ?, ?, 'ok', ?, ?, ?)
|
|
4227
4672
|
`).run(`${id}:receipt:${type}:${Date.now()}`, id, type, command, JSON.stringify(payload), nowIso());
|
|
4228
4673
|
}
|
|
4674
|
+
function planImageGeneration(project = defaultProject, fields) {
|
|
4675
|
+
const prompt = fields.prompt.trim();
|
|
4676
|
+
if (!prompt) throw new GenerationReceiptError("Missing --prompt");
|
|
4677
|
+
if (!fields.fromLineageSelection) throw new GenerationReceiptError("Generation v1 requires --from-lineage-selection");
|
|
4678
|
+
const next = resolveLineageSelection(project);
|
|
4679
|
+
const parentCount = selectedParents(next).length;
|
|
4680
|
+
if (parentCount > 1 && !positiveInteger(fields.perBaseCount)) throw new GenerationReceiptError("Multi-parent generation requires --per-base-count");
|
|
4681
|
+
const perBaseCount = parentCount > 1 ? Number(fields.perBaseCount) : Number(fields.count ?? fields.perBaseCount);
|
|
4682
|
+
const count = parentCount * perBaseCount;
|
|
4683
|
+
if (!positiveInteger(perBaseCount)) throw new GenerationReceiptError("Generation count must be a positive integer");
|
|
4684
|
+
if (fields.count !== void 0 && fields.count !== count) throw new GenerationReceiptError(`Generation count mismatch: expected ${count} from selected bases, received ${fields.count}`);
|
|
4685
|
+
const id = jobId();
|
|
4686
|
+
const inputs = inputsFrom(id, project, next);
|
|
4687
|
+
const handoff = buildHandoff(project, id, prompt, count, perBaseCount, next, inputs);
|
|
4688
|
+
const mappings = parentMappings(next, perBaseCount).map((mapping) => ({ parent_asset_id: mapping.parent.asset_id, output_indexes: mapping.output_indexes }));
|
|
4689
|
+
const timestamp = nowIso();
|
|
4690
|
+
const preview2 = {
|
|
4691
|
+
id,
|
|
4692
|
+
project_id: project,
|
|
4693
|
+
provider,
|
|
4694
|
+
adapter_version: manifestAdapterVersion,
|
|
4695
|
+
source_mode: "lineage_selection",
|
|
4696
|
+
root_asset_id: next.root_asset_id,
|
|
4697
|
+
prompt,
|
|
4698
|
+
expected_output_count: count,
|
|
4699
|
+
status: "planned",
|
|
4700
|
+
output_dir: ".asset-scratch",
|
|
4701
|
+
handoff,
|
|
4702
|
+
created_at: timestamp,
|
|
4703
|
+
updated_at: timestamp,
|
|
4704
|
+
inputs,
|
|
4705
|
+
outputs: [],
|
|
4706
|
+
receipts: [{
|
|
4707
|
+
id: `${id}:receipt:plan:preview`,
|
|
4708
|
+
job_id: id,
|
|
4709
|
+
receipt_type: "plan",
|
|
4710
|
+
status: "ok",
|
|
4711
|
+
command: "generate image plan",
|
|
4712
|
+
payload: { prompt, expected_output_count: count, per_base_count: parentCount > 1 ? perBaseCount : void 0, lineage: handoff.lineage, parent_mappings: mappings },
|
|
4713
|
+
created_at: timestamp
|
|
4714
|
+
}]
|
|
4715
|
+
};
|
|
4716
|
+
if (fields.dryRun) return { ok: true, command: "generate image plan", project, dryRun: true, wouldWrite: true, job: preview2 };
|
|
4717
|
+
const database = lineageDb();
|
|
4718
|
+
try {
|
|
4719
|
+
database.exec("BEGIN IMMEDIATE");
|
|
4720
|
+
try {
|
|
4721
|
+
database.prepare(`
|
|
4722
|
+
insert into generation_jobs (
|
|
4723
|
+
id, project_id, provider, adapter_version, source_mode, root_asset_id, prompt,
|
|
4724
|
+
expected_output_count, status, output_dir, handoff_json, created_at, updated_at
|
|
4725
|
+
) values (?, ?, ?, ?, 'lineage_selection', ?, ?, ?, 'planned', ?, ?, ?, ?)
|
|
4726
|
+
`).run(id, project, provider, manifestAdapterVersion, next.root_asset_id, prompt, count, ".asset-scratch", JSON.stringify(handoff), timestamp, timestamp);
|
|
4727
|
+
const insertInput = database.prepare("insert into generation_job_inputs (id, job_id, project_id, asset_id, root_asset_id, role, position, selection_strategy, selection_snapshot_json) values (?, ?, ?, ?, ?, ?, ?, ?, ?)");
|
|
4728
|
+
for (const input of inputs) insertInput.run(input.id, id, project, input.asset_id, input.root_asset_id, input.role, input.position, input.selection_strategy, JSON.stringify(next));
|
|
4729
|
+
insertReceipt(database, id, "plan", "generate image plan", preview2.receipts[0].payload);
|
|
4730
|
+
database.exec("COMMIT");
|
|
4731
|
+
} catch (error) {
|
|
4732
|
+
database.exec("ROLLBACK");
|
|
4733
|
+
throw error;
|
|
4734
|
+
}
|
|
4735
|
+
return { ok: true, command: "generate image plan", project, job: loadGenerationJob(database, project, id) };
|
|
4736
|
+
} finally {
|
|
4737
|
+
database.close();
|
|
4738
|
+
}
|
|
4739
|
+
}
|
|
4229
4740
|
function planImageReroll(project = defaultProject, fields) {
|
|
4230
4741
|
const prompt = fields.prompt.trim();
|
|
4231
4742
|
if (!prompt) throw new GenerationReceiptError("Missing --prompt");
|
|
@@ -4270,7 +4781,7 @@ function planImageReroll(project = defaultProject, fields) {
|
|
|
4270
4781
|
id,
|
|
4271
4782
|
project_id: project,
|
|
4272
4783
|
provider,
|
|
4273
|
-
adapter_version:
|
|
4784
|
+
adapter_version: legacyAdapterVersion,
|
|
4274
4785
|
source_mode: "lineage_reroll",
|
|
4275
4786
|
root_asset_id: snapshot.root_asset_id,
|
|
4276
4787
|
prompt,
|
|
@@ -4302,7 +4813,7 @@ function planImageReroll(project = defaultProject, fields) {
|
|
|
4302
4813
|
id, project_id, provider, adapter_version, source_mode, root_asset_id, prompt,
|
|
4303
4814
|
expected_output_count, status, output_dir, handoff_json, created_at, updated_at
|
|
4304
4815
|
) values (?, ?, ?, ?, 'lineage_reroll', ?, ?, 1, 'planned', ?, ?, ?, ?)
|
|
4305
|
-
`).run(id, project, provider,
|
|
4816
|
+
`).run(id, project, provider, legacyAdapterVersion, snapshot.root_asset_id, prompt, ".asset-scratch", JSON.stringify(handoff), timestamp, timestamp);
|
|
4306
4817
|
database.prepare("insert into generation_job_inputs (id, job_id, project_id, asset_id, root_asset_id, role, position, selection_strategy, selection_snapshot_json) values (?, ?, ?, ?, ?, ?, ?, ?, ?)").run(input.id, id, project, input.asset_id, input.root_asset_id, input.role, input.position, input.selection_strategy, JSON.stringify(input.selection_snapshot));
|
|
4307
4818
|
insertReceipt(database, id, "plan", "reroll plan", preview2.receipts[0].payload);
|
|
4308
4819
|
database.exec("COMMIT");
|
|
@@ -4315,14 +4826,66 @@ function planImageReroll(project = defaultProject, fields) {
|
|
|
4315
4826
|
database.close();
|
|
4316
4827
|
}
|
|
4317
4828
|
}
|
|
4829
|
+
function parentForOutput(job, outputIndex) {
|
|
4830
|
+
const inputs = job.inputs.filter((input) => input.role === "lineage_next_base");
|
|
4831
|
+
if (inputs.length === 0) throw new GenerationReceiptError("Generation job has no lineage_next_base input");
|
|
4832
|
+
if (inputs.length === 1) return inputs[0].asset_id;
|
|
4833
|
+
if (job.expected_output_count % inputs.length !== 0) throw new GenerationReceiptError("Generation job has invalid parent mapping");
|
|
4834
|
+
return inputs[Math.floor(outputIndex / (job.expected_output_count / inputs.length))]?.asset_id || inputs[inputs.length - 1].asset_id;
|
|
4835
|
+
}
|
|
4836
|
+
function parentInputs(job) {
|
|
4837
|
+
const inputs = job.inputs.filter((input) => input.role === "lineage_next_base");
|
|
4838
|
+
if (inputs.length === 0) throw new GenerationReceiptError("Generation job has no lineage_next_base input");
|
|
4839
|
+
return inputs;
|
|
4840
|
+
}
|
|
4841
|
+
function parentFilesFor(job, parentFiles) {
|
|
4842
|
+
const inputs = parentInputs(job);
|
|
4843
|
+
const expectedPerParent = job.expected_output_count / inputs.length;
|
|
4844
|
+
if (!Number.isInteger(expectedPerParent)) throw new GenerationReceiptError("Generation job has invalid parent mapping");
|
|
4845
|
+
const allowedParents = new Set(inputs.map((input) => input.asset_id));
|
|
4846
|
+
const seenParents = /* @__PURE__ */ new Set();
|
|
4847
|
+
const mapped = [];
|
|
4848
|
+
for (const parentAssetId of Object.keys(parentFiles)) {
|
|
4849
|
+
if (!allowedParents.has(parentAssetId)) throw new GenerationReceiptError(`Unknown generation parent mapping: ${parentAssetId}`);
|
|
4850
|
+
if (seenParents.has(parentAssetId)) throw new GenerationReceiptError(`Duplicate generation parent mapping: ${parentAssetId}`);
|
|
4851
|
+
seenParents.add(parentAssetId);
|
|
4852
|
+
}
|
|
4853
|
+
for (const input of inputs) {
|
|
4854
|
+
const files = (parentFiles[input.asset_id] || []).map((file) => file.trim()).filter(Boolean);
|
|
4855
|
+
if (files.length === 0) throw new GenerationReceiptError(`Missing generation parent mapping for ${input.asset_id}`);
|
|
4856
|
+
if (files.length !== expectedPerParent) throw new GenerationReceiptError(`Parent ${input.asset_id} requires ${expectedPerParent} output file${expectedPerParent === 1 ? "" : "s"}, received ${files.length}`);
|
|
4857
|
+
for (const file of files) mapped.push({ file, parentAssetId: input.asset_id });
|
|
4858
|
+
}
|
|
4859
|
+
return mapped;
|
|
4860
|
+
}
|
|
4861
|
+
function orderedFilesFor(job, files) {
|
|
4862
|
+
return files.map((file, index) => ({ file, parentAssetId: parentForOutput(job, index) }));
|
|
4863
|
+
}
|
|
4864
|
+
function inspectImageGeneration(project = defaultProject, jobIdValue) {
|
|
4865
|
+
if (!jobIdValue) throw new GenerationReceiptError("Missing --job-id");
|
|
4866
|
+
const database = lineageDb();
|
|
4867
|
+
try {
|
|
4868
|
+
return { ok: true, command: "generate image inspect", project, job: loadGenerationJob(database, project, jobIdValue) };
|
|
4869
|
+
} finally {
|
|
4870
|
+
database.close();
|
|
4871
|
+
}
|
|
4872
|
+
}
|
|
4318
4873
|
function isPathInside(child, parent) {
|
|
4319
4874
|
const rel = relative3(parent, child);
|
|
4320
4875
|
return Boolean(rel) && !rel.startsWith("..") && !rel.startsWith("/");
|
|
4321
4876
|
}
|
|
4322
|
-
function
|
|
4877
|
+
function scratchCandidate(file) {
|
|
4323
4878
|
const scratchRoot = resolve5(repoRoot, ".asset-scratch");
|
|
4324
4879
|
const candidate = file.startsWith(".asset-scratch/") || resolve5(file).startsWith(scratchRoot) ? resolve5(repoRoot, file) : resolve5(scratchRoot, file);
|
|
4325
4880
|
if (!isPathInside(candidate, scratchRoot)) throw new GenerationReceiptError(`Import file must be under .asset-scratch: ${file}`);
|
|
4881
|
+
return { candidate, scratchRoot };
|
|
4882
|
+
}
|
|
4883
|
+
function resolveScratchManifestPath(file) {
|
|
4884
|
+
const { candidate, scratchRoot } = scratchCandidate(file);
|
|
4885
|
+
return relative3(scratchRoot, candidate);
|
|
4886
|
+
}
|
|
4887
|
+
function resolveScratchFile(file) {
|
|
4888
|
+
const { candidate, scratchRoot } = scratchCandidate(file);
|
|
4326
4889
|
if (!existsSync8(candidate)) throw new GenerationReceiptError(`Missing import file: ${file}`, 404);
|
|
4327
4890
|
const realScratchRoot = realpathSync2(scratchRoot);
|
|
4328
4891
|
const realCandidate = realpathSync2(candidate);
|
|
@@ -4338,6 +4901,165 @@ function resolveScratchFile(file) {
|
|
|
4338
4901
|
assetId: `local-${checksum.slice(0, 12)}`
|
|
4339
4902
|
};
|
|
4340
4903
|
}
|
|
4904
|
+
function generationManifestConflict() {
|
|
4905
|
+
throw new GenerationReceiptError("Generation import already exists with different output, summary, or provenance", 409);
|
|
4906
|
+
}
|
|
4907
|
+
function confirmManifestRetry(project, job, manifest) {
|
|
4908
|
+
if (job.outputs.length !== manifest.outputs.length) generationManifestConflict();
|
|
4909
|
+
const database = lineageDb();
|
|
4910
|
+
try {
|
|
4911
|
+
for (const expected of manifest.outputs) {
|
|
4912
|
+
const recorded = job.outputs.find((output) => output.output_index === expected.output_index);
|
|
4913
|
+
if (!recorded || recorded.file_path !== expected.file_path || recorded.parent_asset_id !== expected.parent_asset_id || recorded.edge_summary !== expected.edge_summary) generationManifestConflict();
|
|
4914
|
+
const edge = database.prepare(`
|
|
4915
|
+
select summary, summary_created_by, summary_updated_by, summary_updated_at
|
|
4916
|
+
from asset_edges
|
|
4917
|
+
where project_id = ? and parent_asset_id = ? and child_asset_id = ? and relation_type = 'derived_from'
|
|
4918
|
+
`).get(project, recorded.parent_asset_id, recorded.imported_asset_id);
|
|
4919
|
+
if (!edge || edge.summary !== expected.edge_summary || edge.summary_created_by !== "agent" || edge.summary_updated_by !== "agent" || typeof edge.summary_updated_at !== "string" || edge.summary_updated_at.length === 0) generationManifestConflict();
|
|
4920
|
+
}
|
|
4921
|
+
return { ok: true, command: "generate image import", project, job, imported: job.outputs, idempotent: true };
|
|
4922
|
+
} finally {
|
|
4923
|
+
database.close();
|
|
4924
|
+
}
|
|
4925
|
+
}
|
|
4926
|
+
function importImageGenerationOutputs(project = defaultProject, fields) {
|
|
4927
|
+
if (!fields.jobId) throw new GenerationReceiptError("Missing --job-id");
|
|
4928
|
+
if (!fields.confirmWrite) throw new GenerationReceiptError("Generation import requires --confirm-write");
|
|
4929
|
+
const database = lineageDb();
|
|
4930
|
+
let job;
|
|
4931
|
+
try {
|
|
4932
|
+
job = loadGenerationJob(database, project, fields.jobId);
|
|
4933
|
+
} finally {
|
|
4934
|
+
database.close();
|
|
4935
|
+
}
|
|
4936
|
+
if (job.source_mode !== "lineage_selection") throw new GenerationReceiptError(`Generation job is not an image-selection job: ${job.source_mode}`);
|
|
4937
|
+
const hasManifestInput = fields.manifest !== void 0;
|
|
4938
|
+
const hasParentFilesInput = fields.parentFiles !== void 0;
|
|
4939
|
+
const hasFilesInput = fields.files !== void 0;
|
|
4940
|
+
if (hasManifestInput && (hasParentFilesInput || hasFilesInput)) {
|
|
4941
|
+
throw new GenerationReceiptError("Use --manifest or legacy --files/--parent-files, not both");
|
|
4942
|
+
}
|
|
4943
|
+
if (hasParentFilesInput && hasFilesInput) throw new GenerationReceiptError("Use --files or --parent-files, not both");
|
|
4944
|
+
let manifest;
|
|
4945
|
+
let parentFileRows;
|
|
4946
|
+
let mappingStrategy;
|
|
4947
|
+
if (job.adapter_version === manifestAdapterVersion) {
|
|
4948
|
+
if (!hasManifestInput) throw new GenerationReceiptError("New generation jobs require --manifest");
|
|
4949
|
+
manifest = parseGenerationOutputManifest(fields.manifest, job, { resolveFilePath: resolveScratchManifestPath });
|
|
4950
|
+
if (job.status === "imported") return confirmManifestRetry(project, job, manifest);
|
|
4951
|
+
if (job.status !== "planned") throw new GenerationReceiptError(`Generation job is not importable from status: ${job.status}`);
|
|
4952
|
+
parentFileRows = manifest.outputs.map((output) => ({
|
|
4953
|
+
edgeSummary: output.edge_summary,
|
|
4954
|
+
file: output.file_path,
|
|
4955
|
+
parentAssetId: output.parent_asset_id
|
|
4956
|
+
}));
|
|
4957
|
+
mappingStrategy = "generation_output_manifest_v1";
|
|
4958
|
+
} else if (job.adapter_version === legacyAdapterVersion) {
|
|
4959
|
+
if (hasManifestInput) throw new GenerationReceiptError("Already-planned legacy generation jobs require --files or --parent-files");
|
|
4960
|
+
if (job.status !== "planned") throw new GenerationReceiptError(`Generation job is not importable from status: ${job.status}`);
|
|
4961
|
+
const hasExplicitParentFiles = Boolean(fields.parentFiles && Object.keys(fields.parentFiles).length > 0);
|
|
4962
|
+
parentFileRows = hasExplicitParentFiles ? parentFilesFor(job, fields.parentFiles || {}) : orderedFilesFor(job, (fields.files || []).map((file) => file.trim()).filter(Boolean));
|
|
4963
|
+
mappingStrategy = hasExplicitParentFiles ? "explicit_parent_files" : "ordered_per_base";
|
|
4964
|
+
} else {
|
|
4965
|
+
throw new GenerationReceiptError(`Unsupported generation adapter version: ${job.adapter_version}`);
|
|
4966
|
+
}
|
|
4967
|
+
if (parentFileRows.length === 0) throw new GenerationReceiptError("Generation import requires --files or --parent-files");
|
|
4968
|
+
if (parentFileRows.length !== job.expected_output_count) {
|
|
4969
|
+
throw new GenerationReceiptError(`Output count mismatch: expected ${job.expected_output_count}, received ${parentFileRows.length}`);
|
|
4970
|
+
}
|
|
4971
|
+
const resolved = parentFileRows.map((row) => ({ ...resolveScratchFile(row.file), edgeSummary: row.edgeSummary, parentAssetId: row.parentAssetId }));
|
|
4972
|
+
const uniquePaths = new Set(resolved.map((file) => file.relativePath));
|
|
4973
|
+
if (uniquePaths.size !== resolved.length) throw new GenerationReceiptError("Generation import files must be unique");
|
|
4974
|
+
cancelLineageIterateTasksForAssets(project, {
|
|
4975
|
+
actor: "system",
|
|
4976
|
+
confirmWrite: false,
|
|
4977
|
+
rootAssetId: job.root_asset_id
|
|
4978
|
+
});
|
|
4979
|
+
indexLineageAssets(project);
|
|
4980
|
+
const writeDb = lineageDb();
|
|
4981
|
+
try {
|
|
4982
|
+
const timestamp = nowIso();
|
|
4983
|
+
writeDb.exec("BEGIN IMMEDIATE");
|
|
4984
|
+
try {
|
|
4985
|
+
for (const [index, file] of resolved.entries()) {
|
|
4986
|
+
const assetRow = writeDb.prepare("select id from assets where project_id = ? and id = ?").get(project, file.assetId);
|
|
4987
|
+
if (!assetRow) throw new GenerationReceiptError(`Indexed local asset was not found: ${file.relativePath}`);
|
|
4988
|
+
const outputId = `${fields.jobId}:output:${index}`;
|
|
4989
|
+
writeDb.prepare(`insert into generation_job_outputs (
|
|
4990
|
+
id, job_id, project_id, output_index, file_path, checksum_sha256, size_bytes, content_type,
|
|
4991
|
+
imported_asset_id, parent_asset_id, imported_at, edge_summary
|
|
4992
|
+
) values (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`).run(
|
|
4993
|
+
outputId,
|
|
4994
|
+
fields.jobId,
|
|
4995
|
+
project,
|
|
4996
|
+
index,
|
|
4997
|
+
file.relativePath,
|
|
4998
|
+
file.checksum,
|
|
4999
|
+
file.size,
|
|
5000
|
+
file.contentType,
|
|
5001
|
+
file.assetId,
|
|
5002
|
+
file.parentAssetId,
|
|
5003
|
+
timestamp,
|
|
5004
|
+
file.edgeSummary || null
|
|
5005
|
+
);
|
|
5006
|
+
const insertedEdge = writeDb.prepare(`insert into asset_edges (
|
|
5007
|
+
id, project_id, parent_asset_id, child_asset_id, relation_type, created_at,
|
|
5008
|
+
summary, summary_created_by, summary_updated_by, summary_updated_at
|
|
5009
|
+
) values (?, ?, ?, ?, 'derived_from', ?, ?, ?, ?, ?)
|
|
5010
|
+
on conflict(project_id, parent_asset_id, child_asset_id, relation_type) do nothing`).run(
|
|
5011
|
+
`${project}:${file.parentAssetId}:derived_from:${file.assetId}`,
|
|
5012
|
+
project,
|
|
5013
|
+
file.parentAssetId,
|
|
5014
|
+
file.assetId,
|
|
5015
|
+
timestamp,
|
|
5016
|
+
file.edgeSummary || null,
|
|
5017
|
+
file.edgeSummary ? "agent" : null,
|
|
5018
|
+
file.edgeSummary ? "agent" : null,
|
|
5019
|
+
file.edgeSummary ? timestamp : null
|
|
5020
|
+
);
|
|
5021
|
+
if (file.edgeSummary && insertedEdge.changes === 0) {
|
|
5022
|
+
const edge = writeDb.prepare(`
|
|
5023
|
+
select summary, summary_created_by, summary_updated_by, summary_updated_at
|
|
5024
|
+
from asset_edges
|
|
5025
|
+
where project_id = ? and parent_asset_id = ? and child_asset_id = ? and relation_type = 'derived_from'
|
|
5026
|
+
`).get(project, file.parentAssetId, file.assetId);
|
|
5027
|
+
if (!edge || edge.summary !== file.edgeSummary || edge.summary_created_by !== "agent" || edge.summary_updated_by !== "agent" || typeof edge.summary_updated_at !== "string" || edge.summary_updated_at.length === 0) generationManifestConflict();
|
|
5028
|
+
}
|
|
5029
|
+
}
|
|
5030
|
+
writeDb.prepare(`
|
|
5031
|
+
update generation_jobs
|
|
5032
|
+
set status = 'imported', imported_at = ?, updated_at = ?
|
|
5033
|
+
where project_id = ? and id = ?
|
|
5034
|
+
`).run(timestamp, timestamp, project, fields.jobId);
|
|
5035
|
+
insertReceipt(writeDb, fields.jobId, "import", "generate image import", {
|
|
5036
|
+
mapping_strategy: mappingStrategy,
|
|
5037
|
+
files: resolved.map((file, index) => ({
|
|
5038
|
+
output_index: index,
|
|
5039
|
+
file_path: file.relativePath,
|
|
5040
|
+
imported_asset_id: file.assetId,
|
|
5041
|
+
parent_asset_id: file.parentAssetId,
|
|
5042
|
+
...file.edgeSummary ? { edge_summary: file.edgeSummary } : {}
|
|
5043
|
+
})),
|
|
5044
|
+
selection_reset: { root_asset_id: job.root_asset_id, cleared: true }
|
|
5045
|
+
});
|
|
5046
|
+
writeDb.prepare("delete from asset_selections where project_id = ? and root_asset_id = ?").run(project, job.root_asset_id);
|
|
5047
|
+
writeDb.exec("COMMIT");
|
|
5048
|
+
} catch (error) {
|
|
5049
|
+
writeDb.exec("ROLLBACK");
|
|
5050
|
+
throw error;
|
|
5051
|
+
}
|
|
5052
|
+
cancelLineageIterateTasksForAssets(project, {
|
|
5053
|
+
actor: "system",
|
|
5054
|
+
confirmWrite: true,
|
|
5055
|
+
rootAssetId: job.root_asset_id
|
|
5056
|
+
});
|
|
5057
|
+
const importedJob = loadGenerationJob(writeDb, project, fields.jobId);
|
|
5058
|
+
return { ok: true, command: "generate image import", project, job: importedJob, imported: importedJob.outputs };
|
|
5059
|
+
} finally {
|
|
5060
|
+
writeDb.close();
|
|
5061
|
+
}
|
|
5062
|
+
}
|
|
4341
5063
|
function importImageRerollOutput(project = defaultProject, fields) {
|
|
4342
5064
|
if (!fields.jobId) throw new GenerationReceiptError("Missing --job-id");
|
|
4343
5065
|
if (!fields.confirmWrite) throw new GenerationReceiptError("Generation import requires --confirm-write");
|
|
@@ -5178,6 +5900,9 @@ function getLineageRuntimeInfo(options = {}) {
|
|
|
5178
5900
|
const code = options.code || getLineageCodeIdentity(channel);
|
|
5179
5901
|
const databaseInfo = { exists: existsSync10(dbPath), path: dbPath };
|
|
5180
5902
|
const schema = { migration_keys: [] };
|
|
5903
|
+
const serviceInstanceId = process.env.LINEAGE_SERVICE_INSTANCE_ID;
|
|
5904
|
+
const launcherPid = process.env.LINEAGE_LAUNCHER_PID ? Number(process.env.LINEAGE_LAUNCHER_PID) : void 0;
|
|
5905
|
+
const isServiceProcess = Boolean(serviceInstanceId && Number.isInteger(launcherPid) && launcherPid > 0);
|
|
5181
5906
|
if (databaseInfo.exists) {
|
|
5182
5907
|
try {
|
|
5183
5908
|
const stat = statSync6(dbPath);
|
|
@@ -5215,14 +5940,22 @@ function getLineageRuntimeInfo(options = {}) {
|
|
|
5215
5940
|
git_sha: code.git_sha,
|
|
5216
5941
|
node_env: process.env.NODE_ENV,
|
|
5217
5942
|
package_name: info.name,
|
|
5218
|
-
|
|
5219
|
-
schema,
|
|
5220
|
-
service: {
|
|
5221
|
-
instance_id: process.env.LINEAGE_SERVICE_INSTANCE_ID,
|
|
5222
|
-
launcher_pid: process.env.LINEAGE_LAUNCHER_PID ? Number(process.env.LINEAGE_LAUNCHER_PID) : void 0,
|
|
5943
|
+
process: {
|
|
5223
5944
|
pid: process.pid,
|
|
5945
|
+
role: isServiceProcess ? "service" : "command",
|
|
5224
5946
|
started_at: processStartedAt
|
|
5225
5947
|
},
|
|
5948
|
+
profile: runtimeProfileIdentity(channel),
|
|
5949
|
+
schema,
|
|
5950
|
+
...isServiceProcess ? {
|
|
5951
|
+
service: {
|
|
5952
|
+
instance_id: serviceInstanceId,
|
|
5953
|
+
launcher_pid: launcherPid,
|
|
5954
|
+
mode: process.env.LINEAGE_SERVICE_MODE === "managed" ? "managed" : "foreground",
|
|
5955
|
+
pid: process.pid,
|
|
5956
|
+
started_at: processStartedAt
|
|
5957
|
+
}
|
|
5958
|
+
} : {},
|
|
5226
5959
|
version: info.version
|
|
5227
5960
|
};
|
|
5228
5961
|
}
|
|
@@ -5241,22 +5974,23 @@ var ManagedWriterRoutingError = class extends Error {
|
|
|
5241
5974
|
status;
|
|
5242
5975
|
};
|
|
5243
5976
|
function managedWriterTimeoutMs(command, args) {
|
|
5244
|
-
const
|
|
5245
|
-
|
|
5977
|
+
const positions = args.filter((arg) => !arg.startsWith("-"));
|
|
5978
|
+
const slowImport = command === "reroll" && positions[0] === "import" || command === "generate" && positions[0] === "image" && positions[1] === "import";
|
|
5979
|
+
return slowImport ? 5 * 6e4 : 3e4;
|
|
5246
5980
|
}
|
|
5247
5981
|
function errorMessage(body, fallback) {
|
|
5248
5982
|
if (!body || typeof body !== "object" || Array.isArray(body)) return fallback;
|
|
5249
|
-
const
|
|
5250
|
-
if (typeof
|
|
5251
|
-
if (typeof
|
|
5983
|
+
const record2 = body;
|
|
5984
|
+
if (typeof record2.message === "string") return record2.message;
|
|
5985
|
+
if (typeof record2.error === "string") return record2.error;
|
|
5252
5986
|
return fallback;
|
|
5253
5987
|
}
|
|
5254
5988
|
function agentClaimError(body, status) {
|
|
5255
5989
|
if (!body || typeof body !== "object" || Array.isArray(body)) return void 0;
|
|
5256
|
-
const
|
|
5257
|
-
if (typeof
|
|
5258
|
-
const conflicts = Array.isArray(
|
|
5259
|
-
return new AgentClaimError(
|
|
5990
|
+
const record2 = body;
|
|
5991
|
+
if (typeof record2.message !== "string" || typeof record2.error !== "string") return void 0;
|
|
5992
|
+
const conflicts = Array.isArray(record2.conflicts) ? record2.conflicts : [];
|
|
5993
|
+
return new AgentClaimError(record2.message, status, record2.error, conflicts);
|
|
5260
5994
|
}
|
|
5261
5995
|
function assertResponseIdentity(body, profile, channel) {
|
|
5262
5996
|
if (!body || typeof body !== "object" || Array.isArray(body)) {
|
|
@@ -5354,7 +6088,30 @@ function readOptions(args, name) {
|
|
|
5354
6088
|
}
|
|
5355
6089
|
return values;
|
|
5356
6090
|
}
|
|
6091
|
+
function readJsonFile(path, label) {
|
|
6092
|
+
try {
|
|
6093
|
+
return JSON.parse(readFileSync6(resolve8(path), "utf8"));
|
|
6094
|
+
} catch (error) {
|
|
6095
|
+
throw new Error(`${label} must be a readable JSON file: ${error instanceof Error ? error.message : String(error)}`, { cause: error });
|
|
6096
|
+
}
|
|
6097
|
+
}
|
|
6098
|
+
function parentFilesOption(value) {
|
|
6099
|
+
if (value === void 0) return void 0;
|
|
6100
|
+
const mappings = {};
|
|
6101
|
+
for (const rawMapping of value.split(";")) {
|
|
6102
|
+
const separator = rawMapping.indexOf("=");
|
|
6103
|
+
const parent = separator >= 0 ? rawMapping.slice(0, separator).trim() : "";
|
|
6104
|
+
const files = separator >= 0 ? rawMapping.slice(separator + 1).split(",").map((file) => file.trim()).filter(Boolean) : [];
|
|
6105
|
+
if (!parent || files.length === 0) throw new Error("lineage generate image import --parent-files requires parent=file[,file][;parent=file]");
|
|
6106
|
+
if (Object.hasOwn(mappings, parent)) throw new Error(`Duplicate generation parent mapping: ${parent}`);
|
|
6107
|
+
mappings[parent] = files;
|
|
6108
|
+
}
|
|
6109
|
+
return mappings;
|
|
6110
|
+
}
|
|
5357
6111
|
function resolveStartOptions(config, args) {
|
|
6112
|
+
if (!profileSelector(args)) {
|
|
6113
|
+
throw new Error("Start requires --profile or LINEAGE_PROFILE. Create one with `profile init --profile <id> --confirm-write`, then start again with that profile");
|
|
6114
|
+
}
|
|
5358
6115
|
const profile = prepareCliProfile(config, args);
|
|
5359
6116
|
const serviceUrl = profile ? new URL(profile.service_origin) : void 0;
|
|
5360
6117
|
const rawPort = readOption(args, "--port") || process.env.PORT || serviceUrl?.port || String(config.defaultPort);
|
|
@@ -5434,7 +6191,8 @@ function formatLineageHelp(config) {
|
|
|
5434
6191
|
return `${config.binName} ${packageVersion()}
|
|
5435
6192
|
|
|
5436
6193
|
Usage:
|
|
5437
|
-
${config.binName} start
|
|
6194
|
+
${config.binName} start --profile <id-or-manifest> [--open] [--json]
|
|
6195
|
+
${config.binName} profile init --profile <new-profile-id> [--service-origin <http-origin>] --confirm-write [--json]
|
|
5438
6196
|
${config.binName} profile doctor --profile <id-or-manifest> [--json]
|
|
5439
6197
|
${config.binName} profile bind --profile <id-or-manifest> --confirm-write [--json]
|
|
5440
6198
|
${config.binName} profile clone --source-db <snapshot-source> --target-profile <id-or-manifest> --confirm-write [--json]
|
|
@@ -5446,7 +6204,11 @@ Usage:
|
|
|
5446
6204
|
${config.binName} brief [--project <project>] [--root <asset-id>] [--db <path>] [--json]
|
|
5447
6205
|
${config.binName} inspect --asset-id <asset-id> [--project <project>] [--db <path>] [--json]
|
|
5448
6206
|
${config.binName} selection packet [--project <project>] [--workspace <id-or-root>|--root <asset-id>] [--channel <channel>] [--campaign <campaign>] [--context-notes <text>] [--label <label>] [--schema v2] [--out <path>] [--strict] [--db <path>] [--json]
|
|
5449
|
-
${config.binName} link-child --root <asset-id> --child <asset-id> [--project <project>] [--claim-token <claim-id.secret>] [--confirm-write] [--db <path>] [--json]
|
|
6207
|
+
${config.binName} link-child --root <asset-id> --child <asset-id> --summary "<one-or-two-words>" [--project <project>] [--claim-token <claim-id.secret>] [--confirm-write] [--db <path>] [--json]
|
|
6208
|
+
${config.binName} generate image plan --prompt <text> --from-lineage-selection [--count <count>|--per-base-count <count>] [--project <project>] [--dry-run] [--db <path>] [--json]
|
|
6209
|
+
${config.binName} generate image inspect --job-id <job-id> [--project <project>] [--db <path>] [--json]
|
|
6210
|
+
${config.binName} generate image import --job-id <job-id> --manifest <json-file> --confirm-write [--project <project>] [--db <path>] [--json]
|
|
6211
|
+
${config.binName} generate image import --job-id <legacy-job-id> (--files <file,file>|--parent-files <parent=file;parent=file>) --confirm-write [--project <project>] [--db <path>] [--json]
|
|
5450
6212
|
${config.binName} reroll list --root <asset-id> [--project <project>] [--db <path>] [--json]
|
|
5451
6213
|
${config.binName} reroll mark --root <asset-id> --target <asset-id> [--notes <text>] [--requested-by agent|human|system] [--project <project>] [--confirm-write] [--db <path>] [--json]
|
|
5452
6214
|
${config.binName} reroll cancel --root <asset-id> --target <asset-id> [--project <project>] [--confirm-write] [--db <path>] [--json]
|
|
@@ -5568,7 +6330,8 @@ function resolveDataCommandOptions(args) {
|
|
|
5568
6330
|
dbPath: readOption(args, "--db"),
|
|
5569
6331
|
json: args.includes("--json"),
|
|
5570
6332
|
project: readOption(args, "--project") || process.env.LINEAGE_DEFAULT_PRODUCT || defaultProduct,
|
|
5571
|
-
rootAssetId: readOption(args, "--root")
|
|
6333
|
+
rootAssetId: readOption(args, "--root"),
|
|
6334
|
+
summary: readOption(args, "--summary")
|
|
5572
6335
|
};
|
|
5573
6336
|
if (options.dbPath) process.env.LINEAGE_DB = options.dbPath;
|
|
5574
6337
|
return options;
|
|
@@ -5611,13 +6374,57 @@ function runLineageDataCommand(command, args) {
|
|
|
5611
6374
|
}
|
|
5612
6375
|
if (command === "link-child") {
|
|
5613
6376
|
if (!options.childAssetId) throw new Error("lineage link-child requires --child");
|
|
6377
|
+
const summary = requireEdgeSummary(options.summary);
|
|
5614
6378
|
return linkSelectedLineageChild(options.project, {
|
|
5615
6379
|
childAssetId: options.childAssetId,
|
|
5616
6380
|
claimToken: options.claimToken,
|
|
5617
6381
|
confirmWrite: options.confirmWrite,
|
|
5618
|
-
rootAssetId: options.rootAssetId || options.assetId
|
|
6382
|
+
rootAssetId: options.rootAssetId || options.assetId,
|
|
6383
|
+
summary,
|
|
6384
|
+
summaryActor: "agent"
|
|
5619
6385
|
});
|
|
5620
6386
|
}
|
|
6387
|
+
if (command === "generate") {
|
|
6388
|
+
const [resource, subcommand] = positionalArgs(args);
|
|
6389
|
+
if (resource !== "image") throw new Error(`Unknown generate command: ${resource}`);
|
|
6390
|
+
if (subcommand === "plan") {
|
|
6391
|
+
const prompt = readOption(args, "--prompt");
|
|
6392
|
+
const rawCount = readOption(args, "--count");
|
|
6393
|
+
const rawPerBaseCount = readOption(args, "--per-base-count");
|
|
6394
|
+
if (!prompt) throw new Error("lineage generate image plan requires --prompt");
|
|
6395
|
+
return planImageGeneration(options.project, {
|
|
6396
|
+
count: rawCount === void 0 ? void 0 : Number(rawCount),
|
|
6397
|
+
dryRun: args.includes("--dry-run"),
|
|
6398
|
+
fromLineageSelection: args.includes("--from-lineage-selection"),
|
|
6399
|
+
perBaseCount: rawPerBaseCount === void 0 ? void 0 : Number(rawPerBaseCount),
|
|
6400
|
+
prompt
|
|
6401
|
+
});
|
|
6402
|
+
}
|
|
6403
|
+
if (subcommand === "inspect") {
|
|
6404
|
+
const jobId2 = readOption(args, "--job-id");
|
|
6405
|
+
if (!jobId2) throw new Error("lineage generate image inspect requires --job-id");
|
|
6406
|
+
return inspectImageGeneration(options.project, jobId2);
|
|
6407
|
+
}
|
|
6408
|
+
if (subcommand === "import") {
|
|
6409
|
+
const jobId2 = readOption(args, "--job-id");
|
|
6410
|
+
const manifestPath = readOption(args, "--manifest");
|
|
6411
|
+
const filesValue = readOption(args, "--files");
|
|
6412
|
+
const parentFilesValue = readOption(args, "--parent-files");
|
|
6413
|
+
if (!jobId2) throw new Error("lineage generate image import requires --job-id");
|
|
6414
|
+
if (manifestPath !== void 0 && (filesValue !== void 0 || parentFilesValue !== void 0)) {
|
|
6415
|
+
throw new Error("Use --manifest or legacy --files/--parent-files, not both");
|
|
6416
|
+
}
|
|
6417
|
+
if (filesValue !== void 0 && parentFilesValue !== void 0) throw new Error("Use --files or --parent-files, not both");
|
|
6418
|
+
return importImageGenerationOutputs(options.project, {
|
|
6419
|
+
confirmWrite: options.confirmWrite,
|
|
6420
|
+
files: filesValue === void 0 ? void 0 : filesValue.split(",").map((file) => file.trim()).filter(Boolean),
|
|
6421
|
+
jobId: jobId2,
|
|
6422
|
+
manifest: manifestPath === void 0 ? void 0 : readJsonFile(manifestPath, "Generation output manifest"),
|
|
6423
|
+
parentFiles: parentFilesOption(parentFilesValue)
|
|
6424
|
+
});
|
|
6425
|
+
}
|
|
6426
|
+
throw new Error(`Unknown generate image command: ${subcommand}`);
|
|
6427
|
+
}
|
|
5621
6428
|
if (command === "reroll") {
|
|
5622
6429
|
const subcommand = positionalArgs(args)[0] || "";
|
|
5623
6430
|
if (subcommand === "list") {
|
|
@@ -5771,6 +6578,15 @@ function printDataResult(command, result, json) {
|
|
|
5771
6578
|
if (link.warning) console.log(`Warning: ${link.warning}`);
|
|
5772
6579
|
return;
|
|
5773
6580
|
}
|
|
6581
|
+
if (command === "generate" && result && typeof result === "object" && "job" in result) {
|
|
6582
|
+
const generation = result;
|
|
6583
|
+
if (generation.imported) {
|
|
6584
|
+
console.log(`${generation.idempotent ? "Already imported" : "Imported"} ${generation.imported.length} output(s) for ${generation.job?.id || "job"}`);
|
|
6585
|
+
} else {
|
|
6586
|
+
console.log(`Generation job ${generation.job?.id || "job"} ${generation.job?.status || "unknown"}`);
|
|
6587
|
+
}
|
|
6588
|
+
return;
|
|
6589
|
+
}
|
|
5774
6590
|
if (command === "reroll" && result && typeof result === "object") {
|
|
5775
6591
|
if ("requests" in result) {
|
|
5776
6592
|
const listed = result;
|
|
@@ -5817,6 +6633,25 @@ function runLineageDbCommand(config, command, args) {
|
|
|
5817
6633
|
function runLineageProfileCommand(config, command, args) {
|
|
5818
6634
|
const runtime = getLineageRuntimeInfo({ channel: config.channel });
|
|
5819
6635
|
const runtimeIdentity = { channel: config.channel, code: runtime.code, gitSha: runtime.git_sha, version: runtime.version };
|
|
6636
|
+
if (command === "init") {
|
|
6637
|
+
if (hasOption(args, "--db")) throw new Error("Profile init cannot be combined with --db");
|
|
6638
|
+
if (hasOption(args, "--asset-root")) throw new Error("Profile init cannot be combined with --asset-root");
|
|
6639
|
+
if (!args.includes("--confirm-write")) throw new Error("Profile init requires --confirm-write");
|
|
6640
|
+
const profileId = readOption(args, "--profile");
|
|
6641
|
+
if (!profileId) throw new Error("lineage profile init requires --profile");
|
|
6642
|
+
if (process.env.LINEAGE_PROFILE && process.env.LINEAGE_PROFILE !== profileId) {
|
|
6643
|
+
throw new Error("Profile init requires an explicit --profile that does not conflict with LINEAGE_PROFILE");
|
|
6644
|
+
}
|
|
6645
|
+
const serviceOrigin = readOption(args, "--service-origin") || `http://${config.defaultHost}:${config.defaultPort}`;
|
|
6646
|
+
return initializeLineageProfile(profileId, serviceOrigin, runtimeIdentity, true, (profile, initialize) => {
|
|
6647
|
+
const writerLease = acquireProfileWriterLease(profile, config.channel, "cli");
|
|
6648
|
+
try {
|
|
6649
|
+
return initialize();
|
|
6650
|
+
} finally {
|
|
6651
|
+
writerLease.release();
|
|
6652
|
+
}
|
|
6653
|
+
});
|
|
6654
|
+
}
|
|
5820
6655
|
if (command === "clone") {
|
|
5821
6656
|
const source = readOption(args, "--source-db");
|
|
5822
6657
|
const target = readOption(args, "--target-profile");
|
|
@@ -5875,7 +6710,10 @@ function printProfileResult(result, json) {
|
|
|
5875
6710
|
console.log(JSON.stringify(result, null, 2));
|
|
5876
6711
|
return;
|
|
5877
6712
|
}
|
|
5878
|
-
if (result.schema_version === "lineage.
|
|
6713
|
+
if (result.schema_version === "lineage.profile_init.v1") {
|
|
6714
|
+
console.log(`Initialized ${result.profile_id} at ${result.manifest_path}`);
|
|
6715
|
+
console.log(`Service: ${result.service_origin}`);
|
|
6716
|
+
} else if (result.schema_version === "lineage.profile_doctor.v1") {
|
|
5879
6717
|
console.log(`Profile doctor: ${result.ok ? "ok" : "failed"}`);
|
|
5880
6718
|
for (const check of result.checks) console.log(`${check.status.toUpperCase()} ${check.id}: ${check.message}`);
|
|
5881
6719
|
} else if (result.schema_version === "lineage.profile_bind.v1") {
|
|
@@ -5916,7 +6754,9 @@ function printRuntimeResult(result, json) {
|
|
|
5916
6754
|
}
|
|
5917
6755
|
function lineageCliRequiresWriterLease(command, args) {
|
|
5918
6756
|
if (command === "next" || command === "brief" || command === "inspect" || command === "selection") return false;
|
|
5919
|
-
const
|
|
6757
|
+
const positions = positionalArgs(args);
|
|
6758
|
+
const subcommand = positions[0] || "";
|
|
6759
|
+
if (command === "generate") return positions[0] !== "image" || positions[1] !== "inspect";
|
|
5920
6760
|
if (command === "reroll") return subcommand !== "list";
|
|
5921
6761
|
if (command === "tasks") return subcommand !== "list" && subcommand !== "inspect";
|
|
5922
6762
|
if (command === "db") return subcommand !== "info";
|
|
@@ -6104,7 +6944,6 @@ function start(config, args) {
|
|
|
6104
6944
|
} else {
|
|
6105
6945
|
console.log(`${config.displayName} starting at ${url}`);
|
|
6106
6946
|
if (options.profile) console.log(`Profile: ${options.profile.profile_id} (${options.profile.environment})`);
|
|
6107
|
-
else console.warn("Warning: legacy-unbound runtime; database and asset paths are not protected by a named profile.");
|
|
6108
6947
|
console.log(`SQLite: ${options.dbPath}`);
|
|
6109
6948
|
console.log(`Assets: ${options.assetRoot}`);
|
|
6110
6949
|
}
|
|
@@ -6117,7 +6956,7 @@ function start(config, args) {
|
|
|
6117
6956
|
LINEAGE_ASSET_ROOT: options.assetRoot,
|
|
6118
6957
|
LINEAGE_CHANNEL: config.channel,
|
|
6119
6958
|
LINEAGE_DB: options.dbPath,
|
|
6120
|
-
LINEAGE_PROFILE:
|
|
6959
|
+
LINEAGE_PROFILE: process.env.LINEAGE_PROFILE,
|
|
6121
6960
|
LINEAGE_PROFILE_ENVIRONMENT: options.profile?.environment,
|
|
6122
6961
|
LINEAGE_PROFILE_FINGERPRINT: options.profile?.profile_fingerprint,
|
|
6123
6962
|
LINEAGE_PROFILE_ID: options.profile?.profile_id,
|
|
@@ -6125,7 +6964,7 @@ function start(config, args) {
|
|
|
6125
6964
|
LINEAGE_PROFILE_SERVICE_ORIGIN: options.profile?.service_origin,
|
|
6126
6965
|
LINEAGE_LAUNCHER_PID: String(process.pid),
|
|
6127
6966
|
LINEAGE_SERVICE_INSTANCE_ID: serviceInstanceId,
|
|
6128
|
-
LINEAGE_DB_ACCESS:
|
|
6967
|
+
LINEAGE_DB_ACCESS: void 0,
|
|
6129
6968
|
NODE_ENV: "production",
|
|
6130
6969
|
PORT: String(options.port)
|
|
6131
6970
|
},
|
|
@@ -6245,7 +7084,7 @@ async function runLineageCli(config, args = process.argv.slice(2)) {
|
|
|
6245
7084
|
process.env.LINEAGE_DB_ACCESS = "read-only";
|
|
6246
7085
|
}
|
|
6247
7086
|
} else if (requiresWriter) {
|
|
6248
|
-
throw new Error("Persistent writes require --profile; legacy-unbound access is read-only");
|
|
7087
|
+
throw new Error("Persistent writes require --profile; legacy-unbound access is read-only. Create a fresh profile with `profile init --profile <id> --confirm-write`, then pass that profile to the command");
|
|
6249
7088
|
} else {
|
|
6250
7089
|
process.env.LINEAGE_DB_ACCESS = "read-only";
|
|
6251
7090
|
}
|
|
@@ -6256,7 +7095,7 @@ async function runLineageCli(config, args = process.argv.slice(2)) {
|
|
|
6256
7095
|
else console.error(`${config.binName}: ${message2}`);
|
|
6257
7096
|
process.exit(1);
|
|
6258
7097
|
}
|
|
6259
|
-
if (command === "next" || command === "brief" || command === "inspect" || command === "selection" || command === "link-child" || command === "reroll" || command === "tasks") {
|
|
7098
|
+
if (command === "next" || command === "brief" || command === "inspect" || command === "selection" || command === "link-child" || command === "generate" || command === "reroll" || command === "tasks") {
|
|
6260
7099
|
const commandArgs = normalizedArgs.slice(1);
|
|
6261
7100
|
const json2 = commandArgs.includes("--json");
|
|
6262
7101
|
try {
|