loki-mode 10.9.1 → 10.10.2
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/.github/actions/issue-to-pr/action.yml +2 -2
- package/README.md +9 -3
- package/SKILL.md +12 -2
- package/VERSION +1 -1
- package/autonomy/lib/proof-generator.py +1 -0
- package/autonomy/loki +39 -9
- package/autonomy/receipt_jwt.py +46 -1
- package/autonomy/run.sh +1 -1
- package/autonomy/telemetry.sh +1 -1
- package/autonomy/trigger-server.py +1 -0
- package/bin/loki +12 -1
- package/bin/loki-postinstall.js +12 -0
- package/dashboard/__init__.py +1 -1
- package/docs/CLI-REFERENCE.md +139 -0
- package/docs/INSTALLATION.md +1 -1
- package/docs/LOKI-10-BUILD-PROMPT.md +2 -2
- package/docs/PLANS-INDEX.md +2 -3
- package/docs/SLO.md +3 -3
- package/docs/alternative-installations.md +1 -1
- package/docs/bug-fixes/agent-04-memory-fixes.md +1 -1
- package/docs/dev/release-checklist.md +1 -1
- package/{.github/workflows/loki-issue-to-pr.yml → docs/examples/loki-issue-to-pr.yml.example} +2 -0
- package/docs/{ONE-RUN-AUDIT.md → history/one-run-audit.md} +6 -0
- package/docs/{BUG-AUDIT-v6.61.0.md → history/v6.61.0-audit.md} +6 -0
- package/docs/{V8-AGENT-SDK-PLAN.md → history/v8-agent-sdk-plan.md} +6 -0
- package/docs/{CONFIG-FILE-PLAN.md → history/v8-config-plan.md} +6 -0
- package/docs/test-scenarios/enterprise-scenarios.md +1 -1
- package/docs/v10/BOARD.md +3 -0
- package/docs/v10/CLI-MODERN.md +190 -0
- package/docs/v10/DEPS.md +148 -169
- package/docs/v10/FAILURE-CLASSES.md +23 -2
- package/docs/v10/GUARDS.md +18 -0
- package/docs/v10/LEGACY-REMOVAL.md +334 -25
- package/docs/v10/PROGRESS.md +15 -0
- package/loki-ts/dist/loki.js +1574 -1794
- package/mcp/__init__.py +1 -1
- package/package.json +3 -2
- package/packages/control-plane/dist/server.js +733 -397
- package/packages/control-plane/src/db/fixture-cleanup.ts +47 -0
- package/packages/control-plane/src/server/app.ts +2 -0
- package/packages/control-plane/src/server/integrity.ts +48 -9
- package/packages/control-plane/src/server/routes/stats.ts +16 -5
- package/packages/control-plane/src/server/runs.ts +16 -4
- package/packages/control-plane/ui/dist/assets/index-BinL6h3i.css +1 -0
- package/packages/control-plane/ui/dist/assets/index-BrA22FUs.js +152 -0
- package/packages/control-plane/ui/dist/index.html +2 -2
- package/plugins/loki-mode/.claude-plugin/plugin.json +1 -1
- package/schemas/status-result.schema.json +13 -22
- package/skills/release-cadence.md +5 -5
- package/web-app/dist/assets/{AdminPage-DqMnfZrH.js → AdminPage-BvbxMtZO.js} +1 -1
- package/web-app/dist/assets/{Avatar-BEof29du.js → Avatar-Dc7Scg02.js} +1 -1
- package/web-app/dist/assets/{Badge-WD53OL5m.js → Badge-BmVuOqbM.js} +1 -1
- package/web-app/dist/assets/{Button-Rzk5flhU.js → Button-DvzqhxRU.js} +1 -1
- package/web-app/dist/assets/{CockpitPage-Bqz3PCMh.js → CockpitPage-D8AZo7Ha.js} +1 -1
- package/web-app/dist/assets/{ComparePage-Byo2NzAZ.js → ComparePage-NmqaTXDz.js} +1 -1
- package/web-app/dist/assets/{ErrorBoundary-BuE0kH3W.js → ErrorBoundary-PGl8Kzoq.js} +1 -1
- package/web-app/dist/assets/{EvidenceReceiptPanel-CKFHOPBx.js → EvidenceReceiptPanel-2NY4n9gv.js} +1 -1
- package/web-app/dist/assets/{GitHubIssuesPanel-Df_kTMqI.js → GitHubIssuesPanel-DBO_LHHM.js} +1 -1
- package/web-app/dist/assets/{GitHubPRsPanel-CouvcKrZ.js → GitHubPRsPanel-K7m9uJ1N.js} +1 -1
- package/web-app/dist/assets/{HomePage-dEpLzj0x.js → HomePage-qNQzAJxM.js} +2 -2
- package/web-app/dist/assets/{LoginPage-6DVjUND5.js → LoginPage-esu3OK65.js} +1 -1
- package/web-app/dist/assets/{MagicPage-BDQ9MOfY.js → MagicPage-CCdVvXrA.js} +1 -1
- package/web-app/dist/assets/{MetricsPage-CXiJ04G_.js → MetricsPage-bEvugU2t.js} +1 -1
- package/web-app/dist/assets/{NotFoundPage-BaXhZvyj.js → NotFoundPage-BlEjQ1wX.js} +1 -1
- package/web-app/dist/assets/{ProjectPage-BzE4V7Ti.js → ProjectPage-Bnug_Yzv.js} +3 -3
- package/web-app/dist/assets/{ProjectsPage-ipf6TLMX.js → ProjectsPage-M8r1vfpn.js} +1 -1
- package/web-app/dist/assets/{SettingsPage-BDxkUKN6.js → SettingsPage-NqAJpB48.js} +1 -1
- package/web-app/dist/assets/{ShowcasePage-YQXD72SM.js → ShowcasePage-8FggxL1P.js} +1 -1
- package/web-app/dist/assets/{SystemSettingsPage-Dou7zHS0.js → SystemSettingsPage-3Sz3kFOO.js} +1 -1
- package/web-app/dist/assets/{TeamsPage-CQarfcYr.js → TeamsPage-nV-6vdWM.js} +1 -1
- package/web-app/dist/assets/{TemplatesPage-CPi1R8Xj.js → TemplatesPage-Brbc_VR9.js} +1 -1
- package/web-app/dist/assets/{TerminalOutput-WiGhWdYD.js → TerminalOutput-D1yta5ac.js} +1 -1
- package/web-app/dist/assets/{activity-DOa7ejQA.js → activity-oEeKuqFe.js} +1 -1
- package/web-app/dist/assets/{bell-CX-VAlYN.js → bell-3NJtqBRR.js} +1 -1
- package/web-app/dist/assets/{bot-EDXaZmHc.js → bot-CuvJZ6x1.js} +1 -1
- package/web-app/dist/assets/{check-CnBdKk3L.js → check-n4289IZw.js} +1 -1
- package/web-app/dist/assets/{chevron-left-BJ_ydRQW.js → chevron-left-CmQeio7h.js} +1 -1
- package/web-app/dist/assets/{circle-alert-tz8ufhc_.js → circle-alert-CmmVW-Pd.js} +1 -1
- package/web-app/dist/assets/{clock-DFBZPSsH.js → clock-QFqk7GlA.js} +1 -1
- package/web-app/dist/assets/{cloud-uy3p_5Q7.js → cloud-BNkSokmq.js} +1 -1
- package/web-app/dist/assets/{code-xml-3CbYD9YL.js → code-xml-nWeketqj.js} +1 -1
- package/web-app/dist/assets/{database-YiNBXc9c.js → database-C83MXTmJ.js} +1 -1
- package/web-app/dist/assets/{dollar-sign-D0wBa4Cv.js → dollar-sign-Blg6TO4y.js} +1 -1
- package/web-app/dist/assets/{file-code-corner-DDdZVZ5v.js → file-code-corner-Tk11oUzm.js} +1 -1
- package/web-app/dist/assets/{file-plus-D09LTBqt.js → file-plus-DF_eyA6I.js} +1 -1
- package/web-app/dist/assets/{globe-BirTPwff.js → globe-D5hDqqmn.js} +1 -1
- package/web-app/dist/assets/{hammer-DaE5qgnd.js → hammer-C2hm9lzl.js} +1 -1
- package/web-app/dist/assets/{index-D8t7KnQf.js → index-pMxOIwXq.js} +3 -3
- package/web-app/dist/assets/{layers-DVVrckRY.js → layers-XWQKGXg0.js} +1 -1
- package/web-app/dist/assets/{loader-circle-2-nMGmcb.js → loader-circle-BRuvorm2.js} +1 -1
- package/web-app/dist/assets/{lock-D0Xj43pC.js → lock-BSGvQf5v.js} +1 -1
- package/web-app/dist/assets/{package-BB7S3egm.js → package-3FJYUYgJ.js} +1 -1
- package/web-app/dist/assets/{plus-BLhoMrn3.js → plus-CeIx6g9P.js} +1 -1
- package/web-app/dist/assets/{refresh-cw-DMc2uPLw.js → refresh-cw-ChutW42g.js} +1 -1
- package/web-app/dist/assets/{rotate-ccw-DM3IP9x7.js → rotate-ccw-BdSjUAk-.js} +1 -1
- package/web-app/dist/assets/{scroll-text-lH2uW7rB.js → scroll-text-BjFqxpw3.js} +1 -1
- package/web-app/dist/assets/{server-BkerZcdR.js → server-C7UEKdol.js} +1 -1
- package/web-app/dist/assets/{shield-alert-B8ohcUfu.js → shield-alert-DY8fpEs7.js} +1 -1
- package/web-app/dist/assets/{thumbs-up-CTIxLzPD.js → thumbs-up-Cz1san1g.js} +1 -1
- package/web-app/dist/assets/{trash-2-C3zIpsH5.js → trash-2-D2MvGYeP.js} +1 -1
- package/web-app/dist/assets/{trending-down-BTT7wV4Y.js → trending-down-CxO3OYOB.js} +1 -1
- package/web-app/dist/assets/{trending-up-BzHdCDvF.js → trending-up-DCHKPJLe.js} +1 -1
- package/web-app/dist/assets/{upload-HxR34-A8.js → upload-CdPJpSHC.js} +1 -1
- package/web-app/dist/assets/{usePolling-DDj6Fupy.js → usePolling-DAH6K3ST.js} +1 -1
- package/web-app/dist/assets/{user-qH5y9yNy.js → user-Byyt01W4.js} +1 -1
- package/web-app/dist/index.html +1 -1
- package/docs/BRANCH-LIFECYCLE-PLAN.md +0 -354
- package/docs/EXECUTION-COCKPIT-PLAN.md +0 -289
- package/packages/control-plane/ui/dist/assets/index-BQbFv65M.css +0 -1
- package/packages/control-plane/ui/dist/assets/index-Dbhu244h.js +0 -155
|
@@ -3696,10 +3696,10 @@ var require_resolve_block_map = __commonJS((exports) => {
|
|
|
3696
3696
|
let offset = bm.offset;
|
|
3697
3697
|
let commentEnd = null;
|
|
3698
3698
|
for (const collItem of bm.items) {
|
|
3699
|
-
const { start, key, sep:
|
|
3699
|
+
const { start, key, sep: sep4, value } = collItem;
|
|
3700
3700
|
const keyProps = resolveProps.resolveProps(start, {
|
|
3701
3701
|
indicator: "explicit-key-ind",
|
|
3702
|
-
next: key ??
|
|
3702
|
+
next: key ?? sep4?.[0],
|
|
3703
3703
|
offset,
|
|
3704
3704
|
onError,
|
|
3705
3705
|
parentIndent: bm.indent,
|
|
@@ -3713,7 +3713,7 @@ var require_resolve_block_map = __commonJS((exports) => {
|
|
|
3713
3713
|
else if ("indent" in key && key.indent !== bm.indent)
|
|
3714
3714
|
onError(offset, "BAD_INDENT", startColMsg);
|
|
3715
3715
|
}
|
|
3716
|
-
if (!keyProps.anchor && !keyProps.tag && !
|
|
3716
|
+
if (!keyProps.anchor && !keyProps.tag && !sep4) {
|
|
3717
3717
|
commentEnd = keyProps.end;
|
|
3718
3718
|
if (keyProps.comment) {
|
|
3719
3719
|
if (map.comment)
|
|
@@ -3738,7 +3738,7 @@ var require_resolve_block_map = __commonJS((exports) => {
|
|
|
3738
3738
|
ctx.atKey = false;
|
|
3739
3739
|
if (utilMapIncludes.mapIncludes(ctx, map.items, keyNode))
|
|
3740
3740
|
onError(keyStart, "DUPLICATE_KEY", "Map keys must be unique");
|
|
3741
|
-
const valueProps = resolveProps.resolveProps(
|
|
3741
|
+
const valueProps = resolveProps.resolveProps(sep4 ?? [], {
|
|
3742
3742
|
indicator: "map-value-ind",
|
|
3743
3743
|
next: value,
|
|
3744
3744
|
offset: keyNode.range[2],
|
|
@@ -3754,7 +3754,7 @@ var require_resolve_block_map = __commonJS((exports) => {
|
|
|
3754
3754
|
if (ctx.options.strict && keyProps.start < valueProps.found.offset - 1024)
|
|
3755
3755
|
onError(keyNode.range, "KEY_OVER_1024_CHARS", "The : indicator must be at most 1024 chars after the start of an implicit block mapping key");
|
|
3756
3756
|
}
|
|
3757
|
-
const valueNode = value ? composeNode(ctx, value, valueProps, onError) : composeEmptyNode(ctx, offset,
|
|
3757
|
+
const valueNode = value ? composeNode(ctx, value, valueProps, onError) : composeEmptyNode(ctx, offset, sep4, null, valueProps, onError);
|
|
3758
3758
|
if (ctx.schema.compat)
|
|
3759
3759
|
utilFlowIndentCheck.flowIndentCheck(bm.indent, value, onError);
|
|
3760
3760
|
offset = valueNode.range[2];
|
|
@@ -3840,7 +3840,7 @@ var require_resolve_end = __commonJS((exports) => {
|
|
|
3840
3840
|
let comment = "";
|
|
3841
3841
|
if (end) {
|
|
3842
3842
|
let hasSpace = false;
|
|
3843
|
-
let
|
|
3843
|
+
let sep4 = "";
|
|
3844
3844
|
for (const token of end) {
|
|
3845
3845
|
const { source, type } = token;
|
|
3846
3846
|
switch (type) {
|
|
@@ -3854,13 +3854,13 @@ var require_resolve_end = __commonJS((exports) => {
|
|
|
3854
3854
|
if (!comment)
|
|
3855
3855
|
comment = cb;
|
|
3856
3856
|
else
|
|
3857
|
-
comment +=
|
|
3858
|
-
|
|
3857
|
+
comment += sep4 + cb;
|
|
3858
|
+
sep4 = "";
|
|
3859
3859
|
break;
|
|
3860
3860
|
}
|
|
3861
3861
|
case "newline":
|
|
3862
3862
|
if (comment)
|
|
3863
|
-
|
|
3863
|
+
sep4 += source;
|
|
3864
3864
|
hasSpace = true;
|
|
3865
3865
|
break;
|
|
3866
3866
|
default:
|
|
@@ -3900,18 +3900,18 @@ var require_resolve_flow_collection = __commonJS((exports) => {
|
|
|
3900
3900
|
let offset = fc.offset + fc.start.source.length;
|
|
3901
3901
|
for (let i = 0;i < fc.items.length; ++i) {
|
|
3902
3902
|
const collItem = fc.items[i];
|
|
3903
|
-
const { start, key, sep:
|
|
3903
|
+
const { start, key, sep: sep4, value } = collItem;
|
|
3904
3904
|
const props = resolveProps.resolveProps(start, {
|
|
3905
3905
|
flow: fcName,
|
|
3906
3906
|
indicator: "explicit-key-ind",
|
|
3907
|
-
next: key ??
|
|
3907
|
+
next: key ?? sep4?.[0],
|
|
3908
3908
|
offset,
|
|
3909
3909
|
onError,
|
|
3910
3910
|
parentIndent: fc.indent,
|
|
3911
3911
|
startOnNewline: false
|
|
3912
3912
|
});
|
|
3913
3913
|
if (!props.found) {
|
|
3914
|
-
if (!props.anchor && !props.tag && !
|
|
3914
|
+
if (!props.anchor && !props.tag && !sep4 && !value) {
|
|
3915
3915
|
if (i === 0 && props.comma)
|
|
3916
3916
|
onError(props.comma, "UNEXPECTED_TOKEN", `Unexpected , in ${fcName}`);
|
|
3917
3917
|
else if (i < fc.items.length - 1)
|
|
@@ -3963,8 +3963,8 @@ var require_resolve_flow_collection = __commonJS((exports) => {
|
|
|
3963
3963
|
}
|
|
3964
3964
|
}
|
|
3965
3965
|
}
|
|
3966
|
-
if (!isMap && !
|
|
3967
|
-
const valueNode = value ? composeNode(ctx, value, props, onError) : composeEmptyNode(ctx, props.end,
|
|
3966
|
+
if (!isMap && !sep4 && !props.found) {
|
|
3967
|
+
const valueNode = value ? composeNode(ctx, value, props, onError) : composeEmptyNode(ctx, props.end, sep4, null, props, onError);
|
|
3968
3968
|
coll.items.push(valueNode);
|
|
3969
3969
|
offset = valueNode.range[2];
|
|
3970
3970
|
if (isBlock(value))
|
|
@@ -3976,7 +3976,7 @@ var require_resolve_flow_collection = __commonJS((exports) => {
|
|
|
3976
3976
|
if (isBlock(key))
|
|
3977
3977
|
onError(keyNode.range, "BLOCK_IN_FLOW", blockMsg);
|
|
3978
3978
|
ctx.atKey = false;
|
|
3979
|
-
const valueProps = resolveProps.resolveProps(
|
|
3979
|
+
const valueProps = resolveProps.resolveProps(sep4 ?? [], {
|
|
3980
3980
|
flow: fcName,
|
|
3981
3981
|
indicator: "map-value-ind",
|
|
3982
3982
|
next: value,
|
|
@@ -3987,8 +3987,8 @@ var require_resolve_flow_collection = __commonJS((exports) => {
|
|
|
3987
3987
|
});
|
|
3988
3988
|
if (valueProps.found) {
|
|
3989
3989
|
if (!isMap && !props.found && ctx.options.strict) {
|
|
3990
|
-
if (
|
|
3991
|
-
for (const st of
|
|
3990
|
+
if (sep4)
|
|
3991
|
+
for (const st of sep4) {
|
|
3992
3992
|
if (st === valueProps.found)
|
|
3993
3993
|
break;
|
|
3994
3994
|
if (st.type === "newline") {
|
|
@@ -4005,7 +4005,7 @@ var require_resolve_flow_collection = __commonJS((exports) => {
|
|
|
4005
4005
|
else
|
|
4006
4006
|
onError(valueProps.start, "MISSING_CHAR", `Missing , or : between ${fcName} items`);
|
|
4007
4007
|
}
|
|
4008
|
-
const valueNode = value ? composeNode(ctx, value, valueProps, onError) : valueProps.found ? composeEmptyNode(ctx, valueProps.end,
|
|
4008
|
+
const valueNode = value ? composeNode(ctx, value, valueProps, onError) : valueProps.found ? composeEmptyNode(ctx, valueProps.end, sep4, null, valueProps, onError) : null;
|
|
4009
4009
|
if (valueNode) {
|
|
4010
4010
|
if (isBlock(value))
|
|
4011
4011
|
onError(valueNode.range, "BLOCK_IN_FLOW", blockMsg);
|
|
@@ -4182,7 +4182,7 @@ var require_resolve_block_scalar = __commonJS((exports) => {
|
|
|
4182
4182
|
chompStart = i + 1;
|
|
4183
4183
|
}
|
|
4184
4184
|
let value = "";
|
|
4185
|
-
let
|
|
4185
|
+
let sep4 = "";
|
|
4186
4186
|
let prevMoreIndented = false;
|
|
4187
4187
|
for (let i = 0;i < contentStart; ++i)
|
|
4188
4188
|
value += lines[i][0].slice(trimIndent) + `
|
|
@@ -4200,33 +4200,33 @@ var require_resolve_block_scalar = __commonJS((exports) => {
|
|
|
4200
4200
|
indent = "";
|
|
4201
4201
|
}
|
|
4202
4202
|
if (type === Scalar.Scalar.BLOCK_LITERAL) {
|
|
4203
|
-
value +=
|
|
4204
|
-
|
|
4203
|
+
value += sep4 + indent.slice(trimIndent) + content;
|
|
4204
|
+
sep4 = `
|
|
4205
4205
|
`;
|
|
4206
4206
|
} else if (indent.length > trimIndent || content[0] === "\t") {
|
|
4207
|
-
if (
|
|
4208
|
-
|
|
4207
|
+
if (sep4 === " ")
|
|
4208
|
+
sep4 = `
|
|
4209
4209
|
`;
|
|
4210
|
-
else if (!prevMoreIndented &&
|
|
4210
|
+
else if (!prevMoreIndented && sep4 === `
|
|
4211
4211
|
`)
|
|
4212
|
-
|
|
4212
|
+
sep4 = `
|
|
4213
4213
|
|
|
4214
4214
|
`;
|
|
4215
|
-
value +=
|
|
4216
|
-
|
|
4215
|
+
value += sep4 + indent.slice(trimIndent) + content;
|
|
4216
|
+
sep4 = `
|
|
4217
4217
|
`;
|
|
4218
4218
|
prevMoreIndented = true;
|
|
4219
4219
|
} else if (content === "") {
|
|
4220
|
-
if (
|
|
4220
|
+
if (sep4 === `
|
|
4221
4221
|
`)
|
|
4222
4222
|
value += `
|
|
4223
4223
|
`;
|
|
4224
4224
|
else
|
|
4225
|
-
|
|
4225
|
+
sep4 = `
|
|
4226
4226
|
`;
|
|
4227
4227
|
} else {
|
|
4228
|
-
value +=
|
|
4229
|
-
|
|
4228
|
+
value += sep4 + content;
|
|
4229
|
+
sep4 = " ";
|
|
4230
4230
|
prevMoreIndented = false;
|
|
4231
4231
|
}
|
|
4232
4232
|
}
|
|
@@ -4406,27 +4406,27 @@ var require_resolve_flow_scalar = __commonJS((exports) => {
|
|
|
4406
4406
|
trimBoth = /^[ \t]+|[ \t]+$/g;
|
|
4407
4407
|
}
|
|
4408
4408
|
let res = match2[1].replace(trimEnd, "");
|
|
4409
|
-
let
|
|
4409
|
+
let sep4 = " ";
|
|
4410
4410
|
let pos = line2.lastIndex;
|
|
4411
4411
|
while (match2 = line2.exec(source)) {
|
|
4412
4412
|
const lm = match2[1].replace(trimBoth, "");
|
|
4413
4413
|
if (lm === "") {
|
|
4414
|
-
if (
|
|
4414
|
+
if (sep4 === `
|
|
4415
4415
|
`)
|
|
4416
|
-
res +=
|
|
4416
|
+
res += sep4;
|
|
4417
4417
|
else
|
|
4418
|
-
|
|
4418
|
+
sep4 = `
|
|
4419
4419
|
`;
|
|
4420
4420
|
} else {
|
|
4421
|
-
res +=
|
|
4422
|
-
|
|
4421
|
+
res += sep4 + lm;
|
|
4422
|
+
sep4 = " ";
|
|
4423
4423
|
}
|
|
4424
4424
|
pos = line2.lastIndex;
|
|
4425
4425
|
}
|
|
4426
4426
|
const last = /[ \t]*(.*)/sy;
|
|
4427
4427
|
last.lastIndex = pos;
|
|
4428
4428
|
match2 = last.exec(source);
|
|
4429
|
-
return res +
|
|
4429
|
+
return res + sep4 + (match2?.[1] ?? "");
|
|
4430
4430
|
}
|
|
4431
4431
|
function doubleQuotedValue(source, onError) {
|
|
4432
4432
|
let res = "";
|
|
@@ -5207,14 +5207,14 @@ var require_cst_stringify = __commonJS((exports) => {
|
|
|
5207
5207
|
}
|
|
5208
5208
|
}
|
|
5209
5209
|
}
|
|
5210
|
-
function stringifyItem({ start, key, sep:
|
|
5210
|
+
function stringifyItem({ start, key, sep: sep4, value }) {
|
|
5211
5211
|
let res = "";
|
|
5212
5212
|
for (const st of start)
|
|
5213
5213
|
res += st.source;
|
|
5214
5214
|
if (key)
|
|
5215
5215
|
res += stringifyToken(key);
|
|
5216
|
-
if (
|
|
5217
|
-
for (const st of
|
|
5216
|
+
if (sep4)
|
|
5217
|
+
for (const st of sep4)
|
|
5218
5218
|
res += st.source;
|
|
5219
5219
|
if (value)
|
|
5220
5220
|
res += stringifyToken(value);
|
|
@@ -6362,18 +6362,18 @@ var require_parser = __commonJS((exports) => {
|
|
|
6362
6362
|
if (this.type === "map-value-ind") {
|
|
6363
6363
|
const prev = getPrevProps(this.peek(2));
|
|
6364
6364
|
const start = getFirstKeyStartProps(prev);
|
|
6365
|
-
let
|
|
6365
|
+
let sep4;
|
|
6366
6366
|
if (scalar.end) {
|
|
6367
|
-
|
|
6368
|
-
|
|
6367
|
+
sep4 = scalar.end;
|
|
6368
|
+
sep4.push(this.sourceToken);
|
|
6369
6369
|
delete scalar.end;
|
|
6370
6370
|
} else
|
|
6371
|
-
|
|
6371
|
+
sep4 = [this.sourceToken];
|
|
6372
6372
|
const map = {
|
|
6373
6373
|
type: "block-map",
|
|
6374
6374
|
offset: scalar.offset,
|
|
6375
6375
|
indent: scalar.indent,
|
|
6376
|
-
items: [{ start, key: scalar, sep:
|
|
6376
|
+
items: [{ start, key: scalar, sep: sep4 }]
|
|
6377
6377
|
};
|
|
6378
6378
|
this.onKeyLine = true;
|
|
6379
6379
|
this.stack[this.stack.length - 1] = map;
|
|
@@ -6527,15 +6527,15 @@ var require_parser = __commonJS((exports) => {
|
|
|
6527
6527
|
} else if (isFlowToken(it.key) && !includesToken(it.sep, "newline")) {
|
|
6528
6528
|
const start2 = getFirstKeyStartProps(it.start);
|
|
6529
6529
|
const key = it.key;
|
|
6530
|
-
const
|
|
6531
|
-
|
|
6530
|
+
const sep4 = it.sep;
|
|
6531
|
+
sep4.push(this.sourceToken);
|
|
6532
6532
|
delete it.key;
|
|
6533
6533
|
delete it.sep;
|
|
6534
6534
|
this.stack.push({
|
|
6535
6535
|
type: "block-map",
|
|
6536
6536
|
offset: this.offset,
|
|
6537
6537
|
indent: this.indent,
|
|
6538
|
-
items: [{ start: start2, key, sep:
|
|
6538
|
+
items: [{ start: start2, key, sep: sep4 }]
|
|
6539
6539
|
});
|
|
6540
6540
|
} else if (start.length > 0) {
|
|
6541
6541
|
it.sep = it.sep.concat(start, this.sourceToken);
|
|
@@ -6729,13 +6729,13 @@ var require_parser = __commonJS((exports) => {
|
|
|
6729
6729
|
const prev = getPrevProps(parent);
|
|
6730
6730
|
const start = getFirstKeyStartProps(prev);
|
|
6731
6731
|
fixFlowSeqItems(fc);
|
|
6732
|
-
const
|
|
6733
|
-
|
|
6732
|
+
const sep4 = fc.end.splice(1, fc.end.length);
|
|
6733
|
+
sep4.push(this.sourceToken);
|
|
6734
6734
|
const map = {
|
|
6735
6735
|
type: "block-map",
|
|
6736
6736
|
offset: fc.offset,
|
|
6737
6737
|
indent: fc.indent,
|
|
6738
|
-
items: [{ start, key: fc, sep:
|
|
6738
|
+
items: [{ start, key: fc, sep: sep4 }]
|
|
6739
6739
|
};
|
|
6740
6740
|
this.onKeyLine = true;
|
|
6741
6741
|
this.stack[this.stack.length - 1] = map;
|
|
@@ -6959,8 +6959,8 @@ var require_public_api = __commonJS((exports) => {
|
|
|
6959
6959
|
});
|
|
6960
6960
|
|
|
6961
6961
|
// src/server/app.ts
|
|
6962
|
-
import { existsSync as
|
|
6963
|
-
import { join as
|
|
6962
|
+
import { existsSync as existsSync21, statSync as statSync14 } from "fs";
|
|
6963
|
+
import { join as join33, resolve as resolve6, sep as sep12 } from "path";
|
|
6964
6964
|
|
|
6965
6965
|
// node_modules/hono/dist/request/constants.js
|
|
6966
6966
|
var GET_MATCH_RESULT = Symbol();
|
|
@@ -8394,6 +8394,90 @@ var Hono3 = class extends Hono {
|
|
|
8394
8394
|
}
|
|
8395
8395
|
};
|
|
8396
8396
|
|
|
8397
|
+
// src/db/fixture-cleanup.ts
|
|
8398
|
+
import { realpathSync } from "fs";
|
|
8399
|
+
import { tmpdir } from "os";
|
|
8400
|
+
import { sep } from "path";
|
|
8401
|
+
|
|
8402
|
+
// src/db/prune.ts
|
|
8403
|
+
var n = (sqlite, sql, ...args) => sqlite.query(sql).get(...args).n;
|
|
8404
|
+
function countFor(sqlite, keys) {
|
|
8405
|
+
const bySource = new Map;
|
|
8406
|
+
for (const k of keys)
|
|
8407
|
+
bySource.set(k.source_id, [...bySource.get(k.source_id) ?? [], k.run_id]);
|
|
8408
|
+
let events = 0, sources = 0;
|
|
8409
|
+
for (const [src, runIds] of bySource) {
|
|
8410
|
+
let ev = 0;
|
|
8411
|
+
for (const r of runIds)
|
|
8412
|
+
ev += n(sqlite, "select count(*) n from events where source_id = ? and run_id = ?", src, r);
|
|
8413
|
+
events += ev;
|
|
8414
|
+
const runsLeft = n(sqlite, "select count(*) n from runs where source_id = ?", src) - runIds.length;
|
|
8415
|
+
const eventsLeft = n(sqlite, "select count(*) n from events where source_id = ?", src) - ev;
|
|
8416
|
+
if (runsLeft <= 0 && eventsLeft <= 0 && sqlite.query("select 1 x from sources where id = ?").get(src))
|
|
8417
|
+
sources++;
|
|
8418
|
+
}
|
|
8419
|
+
return { runs: keys.length, events, sources };
|
|
8420
|
+
}
|
|
8421
|
+
function audit(sqlite, action, actor, detail) {
|
|
8422
|
+
sqlite.query("insert into audit (ts, action, actor, detail) values (?, ?, ?, ?)").run(new Date().toISOString(), action, actor, JSON.stringify(detail));
|
|
8423
|
+
}
|
|
8424
|
+
var dropOrphanSources = (sqlite, ids) => {
|
|
8425
|
+
for (const id of ids)
|
|
8426
|
+
sqlite.query("delete from sources where id = ? and not exists (select 1 from runs where source_id = ?) and not exists (select 1 from events where source_id = ?)").run(id, id, id);
|
|
8427
|
+
};
|
|
8428
|
+
function removeRun(sqlite, source, run, actor = "http") {
|
|
8429
|
+
let out = null;
|
|
8430
|
+
sqlite.transaction(() => {
|
|
8431
|
+
if (!sqlite.query("select 1 x from runs where source_id = ? and run_id = ?").get(source, run))
|
|
8432
|
+
return;
|
|
8433
|
+
out = countFor(sqlite, [{ source_id: source, run_id: run }]);
|
|
8434
|
+
audit(sqlite, "run.remove", actor, { source_id: source, run_id: run, counts: out });
|
|
8435
|
+
sqlite.query("delete from events where source_id = ? and run_id = ?").run(source, run);
|
|
8436
|
+
sqlite.query("delete from runs where source_id = ? and run_id = ?").run(source, run);
|
|
8437
|
+
dropOrphanSources(sqlite, [source]);
|
|
8438
|
+
}).immediate();
|
|
8439
|
+
return out;
|
|
8440
|
+
}
|
|
8441
|
+
|
|
8442
|
+
// src/db/fixture-cleanup.ts
|
|
8443
|
+
var FIXTURE_REPO = "acme/widget";
|
|
8444
|
+
var DONE = "fixture.cleanup.done";
|
|
8445
|
+
var tempRoots = () => {
|
|
8446
|
+
const roots = new Set(["/tmp", "/private/tmp", "/var/tmp", tmpdir()]);
|
|
8447
|
+
for (const r of [...roots]) {
|
|
8448
|
+
try {
|
|
8449
|
+
roots.add(realpathSync(r));
|
|
8450
|
+
} catch {}
|
|
8451
|
+
}
|
|
8452
|
+
return [...roots].map((r) => r.replace(/[\\/]+$/, "")).filter((r) => r.length > 1);
|
|
8453
|
+
};
|
|
8454
|
+
var underTemp = (p, roots) => roots.some((r) => p === r || p.startsWith(r + sep) || p.startsWith(r + "/"));
|
|
8455
|
+
function cleanupLeakedFixtures(sqlite) {
|
|
8456
|
+
let removed = 0;
|
|
8457
|
+
sqlite.transaction(() => {
|
|
8458
|
+
if (sqlite.query("select 1 x from audit where action = ?").get(DONE))
|
|
8459
|
+
return;
|
|
8460
|
+
const roots = tempRoots();
|
|
8461
|
+
const tempSources = new Set(sqlite.query("select source_id, realpath from local_repos").all().filter((r) => underTemp(r.realpath, roots)).map((r) => r.source_id));
|
|
8462
|
+
const all = sqlite.query("select source_id, run_id, origin_repo from runs").all();
|
|
8463
|
+
const doomed = all.filter((r) => r.origin_repo === FIXTURE_REPO || tempSources.has(r.source_id));
|
|
8464
|
+
const touched = new Set;
|
|
8465
|
+
for (const k of doomed) {
|
|
8466
|
+
const ev = sqlite.query("select count(*) n from events where source_id = ? and run_id = ?").get(k.source_id, k.run_id).n;
|
|
8467
|
+
audit(sqlite, "fixture.cleanup", "startup", { source_id: k.source_id, run_id: k.run_id, origin_repo: k.origin_repo, events: ev });
|
|
8468
|
+
sqlite.query("delete from events where source_id = ? and run_id = ?").run(k.source_id, k.run_id);
|
|
8469
|
+
sqlite.query("delete from runs where source_id = ? and run_id = ?").run(k.source_id, k.run_id);
|
|
8470
|
+
touched.add(k.source_id);
|
|
8471
|
+
removed++;
|
|
8472
|
+
}
|
|
8473
|
+
for (const id of touched) {
|
|
8474
|
+
sqlite.query("delete from sources where id = ? and not exists (select 1 from runs where source_id = ?) and not exists (select 1 from events where source_id = ?)").run(id, id, id);
|
|
8475
|
+
}
|
|
8476
|
+
audit(sqlite, DONE, "startup", { removed });
|
|
8477
|
+
}).immediate();
|
|
8478
|
+
return removed;
|
|
8479
|
+
}
|
|
8480
|
+
|
|
8397
8481
|
// src/db/migrate.ts
|
|
8398
8482
|
import { Database as Database2 } from "bun:sqlite";
|
|
8399
8483
|
|
|
@@ -12436,7 +12520,7 @@ __export(exports_schema, {
|
|
|
12436
12520
|
runs: () => runs,
|
|
12437
12521
|
localRepos: () => localRepos,
|
|
12438
12522
|
events: () => events,
|
|
12439
|
-
audit: () =>
|
|
12523
|
+
audit: () => audit2,
|
|
12440
12524
|
actions: () => actions
|
|
12441
12525
|
});
|
|
12442
12526
|
var sources = sqliteTable("sources", {
|
|
@@ -12495,7 +12579,7 @@ var runs = sqliteTable("runs", {
|
|
|
12495
12579
|
index("runs_verdict").on(t.verdict),
|
|
12496
12580
|
index("runs_group").on(t.groupId)
|
|
12497
12581
|
]);
|
|
12498
|
-
var
|
|
12582
|
+
var audit2 = sqliteTable("audit", {
|
|
12499
12583
|
id: integer("id").primaryKey({ autoIncrement: true }),
|
|
12500
12584
|
ts: text("ts").notNull(),
|
|
12501
12585
|
action: text("action").notNull(),
|
|
@@ -12698,6 +12782,8 @@ function writeAnswer(dir, source, run, answer) {
|
|
|
12698
12782
|
// src/server/integrity.ts
|
|
12699
12783
|
import { createHash, createPublicKey, verify } from "crypto";
|
|
12700
12784
|
import { readFileSync as readFileSync2 } from "fs";
|
|
12785
|
+
import { homedir as homedir2 } from "os";
|
|
12786
|
+
import { join as join3 } from "path";
|
|
12701
12787
|
var SHA = /^[0-9a-f]{64}$/;
|
|
12702
12788
|
var SEAL_LINE = "log.sealed";
|
|
12703
12789
|
var REDACTED = /\[REDACTED(?::[A-Z_]+)?\]/;
|
|
@@ -12706,17 +12792,48 @@ var UNATTESTED_SUFFIX = " (unattested)";
|
|
|
12706
12792
|
var SUCCESS_VERDICTS = new Set(["VERIFIED", "ALREADY_SATISFIED"]);
|
|
12707
12793
|
var normVerdict = (v) => typeof v === "string" ? v.trim().toUpperCase() : null;
|
|
12708
12794
|
var kidOf = (pub) => createHash("sha256").update(`{"crv":"Ed25519","kty":"OKP","x":"${pub.export({ format: "jwk" }).x}"}`).digest("base64url");
|
|
12709
|
-
|
|
12710
|
-
|
|
12711
|
-
|
|
12712
|
-
|
|
12713
|
-
|
|
12714
|
-
|
|
12795
|
+
var isEd25519 = (k) => k.asymmetricKeyType === "ed25519";
|
|
12796
|
+
function localKeyInfo(env = process.env) {
|
|
12797
|
+
const inline = env["LOKI_RECEIPT_SIGNING_KEY"]?.trim();
|
|
12798
|
+
const given = env["LOKI_RECEIPT_SIGNING_KEY_FILE"]?.trim();
|
|
12799
|
+
try {
|
|
12800
|
+
let pub;
|
|
12801
|
+
if (inline)
|
|
12802
|
+
pub = createPublicKey(inline);
|
|
12803
|
+
else {
|
|
12804
|
+
const file = given || join3(env["HOME"] || homedir2(), ".loki", "keys", "receipt-ed25519.pem");
|
|
12805
|
+
try {
|
|
12806
|
+
pub = createPublicKey(readFileSync2(file));
|
|
12807
|
+
} catch (e) {
|
|
12808
|
+
if (!given && e.code === "ENOENT")
|
|
12809
|
+
return {};
|
|
12810
|
+
throw e;
|
|
12811
|
+
}
|
|
12715
12812
|
}
|
|
12716
|
-
|
|
12813
|
+
return isEd25519(pub) ? { key: pub } : { reason: "local signing key is not an Ed25519 key" };
|
|
12814
|
+
} catch (e) {
|
|
12815
|
+
const code = e.code;
|
|
12816
|
+
return { reason: `local signing key ${inline ? "(LOKI_RECEIPT_SIGNING_KEY)" : given ? "file" : "default file"} could not be used: ${code ?? "malformed PEM"}` };
|
|
12817
|
+
}
|
|
12818
|
+
}
|
|
12819
|
+
var pemList = (raw2) => (raw2 ?? "").split(":").map((f) => f.trim()).filter(Boolean).flatMap((f) => {
|
|
12820
|
+
try {
|
|
12821
|
+
const k = createPublicKey(readFileSync2(f));
|
|
12822
|
+
return isEd25519(k) ? [k] : [];
|
|
12823
|
+
} catch {
|
|
12824
|
+
return [];
|
|
12825
|
+
}
|
|
12826
|
+
});
|
|
12827
|
+
function pubkeysFromEnv(env = process.env) {
|
|
12828
|
+
const info = localKeyInfo(env);
|
|
12829
|
+
const explicit = pemList(env["LOKI_CP_RECEIPT_PUBKEYS"]);
|
|
12830
|
+
const keys = [...info.key ? [info.key] : [], ...pemList(env["LOKI_RECEIPT_RETIRED_PUBKEYS"]), ...explicit];
|
|
12717
12831
|
const fn = (kid) => keys.find((k) => kidOf(k) === kid);
|
|
12718
12832
|
fn.configured = keys.length > 0;
|
|
12719
|
-
fn.
|
|
12833
|
+
fn.strict = explicit.length > 0;
|
|
12834
|
+
if (info.reason)
|
|
12835
|
+
fn.reason = info.reason;
|
|
12836
|
+
fn.fingerprint = createHash("sha256").update(keys.map(kidOf).sort().join(",") + (fn.strict ? "|strict" : "")).digest("hex").slice(0, 16);
|
|
12720
12837
|
return fn;
|
|
12721
12838
|
}
|
|
12722
12839
|
var line = (e) => JSON.stringify({ v: e.v, seq: e.seq, ts: e.ts, run: e.run, type: e.type, stage: e.stage, data: e.data });
|
|
@@ -12731,7 +12848,7 @@ function verifyRunIntegrity(evs, opts = {}) {
|
|
|
12731
12848
|
const sealedAt = evs.map((e, i) => e.type === "receipt.sealed" ? i : -1).filter((i) => i >= 0);
|
|
12732
12849
|
const finalised = doneAt >= 0 || sealedAt.length > 0;
|
|
12733
12850
|
const lines = evs.map(line);
|
|
12734
|
-
const keysConfigured = opts.keysConfigured ?? opts.pubkeyFor?.configured ?? false;
|
|
12851
|
+
const keysConfigured = opts.keysConfigured ?? opts.pubkeyFor?.strict ?? opts.pubkeyFor?.configured ?? false;
|
|
12735
12852
|
let sigChecked = false;
|
|
12736
12853
|
if (finalised) {
|
|
12737
12854
|
const gap = evs.findIndex((e, i) => e.seq !== i);
|
|
@@ -12928,8 +13045,15 @@ var parseRun = (r) => ({
|
|
|
12928
13045
|
status: r.endedAt ? "completed" : "running",
|
|
12929
13046
|
elapsed_s: elapsedS(r)
|
|
12930
13047
|
});
|
|
13048
|
+
function taskTitle(db, sourceId, runId) {
|
|
13049
|
+
const d = db.select({ data: events.data }).from(events).where(and(eq(events.sourceId, sourceId), eq(events.runId, runId), eq(events.type, "stage.completed"), eq(events.stage, "intake"))).orderBy(asc(events.seq)).limit(1).get()?.data;
|
|
13050
|
+
const raw2 = str(d?.["title"]) ?? str(d?.["task"]);
|
|
13051
|
+
const t = raw2?.split(`
|
|
13052
|
+
`)[0]?.trim();
|
|
13053
|
+
return t ? t.length > 140 ? `${t.slice(0, 139)}...` : t : null;
|
|
13054
|
+
}
|
|
12931
13055
|
var withLive = (db, r) => {
|
|
12932
|
-
const p = parseRun(r);
|
|
13056
|
+
const p = { ...parseRun(r), title: taskTitle(db, r.sourceId, r.runId) };
|
|
12933
13057
|
return r.endedAt ? { ...p, current_stage: null, files_touched: [] } : { ...p, ...liveInfo(loadEvents(db, r.sourceId, r.runId)) };
|
|
12934
13058
|
};
|
|
12935
13059
|
function liveInfo(evs) {
|
|
@@ -12980,11 +13104,11 @@ var verdictFilter = (v) => {
|
|
|
12980
13104
|
var RECOMPUTE_CHUNK = 200;
|
|
12981
13105
|
function recomputeLegacy(db) {
|
|
12982
13106
|
const fp = pubkeysFromEnv().fingerprint ?? "";
|
|
12983
|
-
let
|
|
13107
|
+
let n2 = 0;
|
|
12984
13108
|
for (;; ) {
|
|
12985
13109
|
const rows = db.select({ s: runs.sourceId, r: runs.runId }).from(runs).where(sql`${runs.attested} is null or ${runs.integrityKeyFp} is not ${fp}`).limit(RECOMPUTE_CHUNK).all();
|
|
12986
13110
|
if (rows.length === 0)
|
|
12987
|
-
return
|
|
13111
|
+
return n2;
|
|
12988
13112
|
db.transaction((tx) => {
|
|
12989
13113
|
const t = tx;
|
|
12990
13114
|
for (const x of rows) {
|
|
@@ -12994,7 +13118,7 @@ function recomputeLegacy(db) {
|
|
|
12994
13118
|
rebuildRun(t, x.s, x.r);
|
|
12995
13119
|
}
|
|
12996
13120
|
});
|
|
12997
|
-
|
|
13121
|
+
n2 += rows.length;
|
|
12998
13122
|
}
|
|
12999
13123
|
}
|
|
13000
13124
|
function listRuns(db, q) {
|
|
@@ -13016,12 +13140,14 @@ function runDetail(db, sourceId, runId) {
|
|
|
13016
13140
|
continue;
|
|
13017
13141
|
let s = stages.find((x) => x.stage === e.stage);
|
|
13018
13142
|
if (!s)
|
|
13019
|
-
stages.push(s = { stage: e.stage, started_at: null, ended_at: null, status: "started" });
|
|
13143
|
+
stages.push(s = { stage: e.stage, started_at: null, ended_at: null, status: "started", reason: null });
|
|
13020
13144
|
if (e.type === "stage.started")
|
|
13021
13145
|
s.started_at = e.ts;
|
|
13022
13146
|
else {
|
|
13023
13147
|
s.ended_at = e.ts;
|
|
13024
13148
|
s.status = e.type.slice("stage.".length);
|
|
13149
|
+
const why = e.data?.reason;
|
|
13150
|
+
s.reason = typeof why === "string" && why ? why : null;
|
|
13025
13151
|
}
|
|
13026
13152
|
}
|
|
13027
13153
|
const f = fold(evs);
|
|
@@ -13135,19 +13261,19 @@ function tokenGuard(token) {
|
|
|
13135
13261
|
|
|
13136
13262
|
// src/shipper/backfill.ts
|
|
13137
13263
|
import { readdirSync } from "fs";
|
|
13138
|
-
import { join as
|
|
13264
|
+
import { join as join5 } from "path";
|
|
13139
13265
|
|
|
13140
13266
|
// src/shipper/ship.ts
|
|
13141
13267
|
import { createHash as createHash4 } from "crypto";
|
|
13142
|
-
import { existsSync as existsSync3, readFileSync as readFileSync3, realpathSync, renameSync, writeFileSync as writeFileSync2 } from "fs";
|
|
13268
|
+
import { existsSync as existsSync3, readFileSync as readFileSync3, realpathSync as realpathSync2, renameSync, writeFileSync as writeFileSync2 } from "fs";
|
|
13143
13269
|
import { hostname } from "os";
|
|
13144
|
-
import { join as
|
|
13270
|
+
import { join as join4 } from "path";
|
|
13145
13271
|
var BATCH = 200;
|
|
13146
13272
|
var MAX_BYTES = 800000;
|
|
13147
13273
|
function sourceId(repoDir) {
|
|
13148
13274
|
let real2 = repoDir;
|
|
13149
13275
|
try {
|
|
13150
|
-
real2 =
|
|
13276
|
+
real2 = realpathSync2(repoDir);
|
|
13151
13277
|
} catch {}
|
|
13152
13278
|
return createHash4("sha256").update(`${hostname()}\x00${real2}`).digest("hex").slice(0, 16);
|
|
13153
13279
|
}
|
|
@@ -13156,24 +13282,24 @@ var redactEvent = (e) => ({ ...e, data: redactDeep(e.data) });
|
|
|
13156
13282
|
var backoffMs = (failures, rnd = Math.random) => Math.min(60000, 1000 * 2 ** Math.max(0, failures - 1)) * (1 + 0.25 * rnd());
|
|
13157
13283
|
function readShip(runDir, url) {
|
|
13158
13284
|
try {
|
|
13159
|
-
const s = JSON.parse(readFileSync3(
|
|
13285
|
+
const s = JSON.parse(readFileSync3(join4(runDir, "ship.json"), "utf8"));
|
|
13160
13286
|
if (s.url === url && Number.isInteger(s.acked_seq))
|
|
13161
13287
|
return { acked_seq: s.acked_seq, url };
|
|
13162
13288
|
} catch {}
|
|
13163
13289
|
return { acked_seq: -1, url };
|
|
13164
13290
|
}
|
|
13165
13291
|
function writeShip(runDir, s) {
|
|
13166
|
-
const tmp =
|
|
13292
|
+
const tmp = join4(runDir, `ship.json.${process.pid}.tmp`);
|
|
13167
13293
|
writeFileSync2(tmp, JSON.stringify(s) + `
|
|
13168
13294
|
`);
|
|
13169
|
-
renameSync(tmp,
|
|
13295
|
+
renameSync(tmp, join4(runDir, "ship.json"));
|
|
13170
13296
|
}
|
|
13171
13297
|
async function shipRun(o) {
|
|
13172
13298
|
const st = readShip(o.runDir, o.url);
|
|
13173
|
-
const all = readEvents(
|
|
13299
|
+
const all = readEvents(join4(o.runDir, "events.jsonl"));
|
|
13174
13300
|
let lines = 0;
|
|
13175
13301
|
try {
|
|
13176
|
-
lines = readFileSync3(
|
|
13302
|
+
lines = readFileSync3(join4(o.runDir, "events.jsonl"), "utf8").split(`
|
|
13177
13303
|
`).filter((l) => l.trim() !== "").length;
|
|
13178
13304
|
} catch {}
|
|
13179
13305
|
const skipped = lines - all.length;
|
|
@@ -13216,7 +13342,7 @@ async function shipRun(o) {
|
|
|
13216
13342
|
return { ok: true, sent, acked_seq: st.acked_seq, pending: 0 };
|
|
13217
13343
|
}
|
|
13218
13344
|
var shipEnabled = (env) => env.LOKI_CONTROL_URL || null;
|
|
13219
|
-
var hasSpool = (runDir) => existsSync3(
|
|
13345
|
+
var hasSpool = (runDir) => existsSync3(join4(runDir, "events.jsonl"));
|
|
13220
13346
|
|
|
13221
13347
|
// src/shipper/backfill.ts
|
|
13222
13348
|
async function backfill(o) {
|
|
@@ -13226,7 +13352,7 @@ async function backfill(o) {
|
|
|
13226
13352
|
return { runs: 0, sent: 0, failed: [] };
|
|
13227
13353
|
const sleep = o.sleep ?? ((ms) => new Promise((r) => setTimeout(r, ms)));
|
|
13228
13354
|
const attempts = o.attempts ?? 3;
|
|
13229
|
-
const root =
|
|
13355
|
+
const root = join5(o.repoDir, ".loki", "runs");
|
|
13230
13356
|
let ids = [];
|
|
13231
13357
|
try {
|
|
13232
13358
|
ids = readdirSync(root).sort();
|
|
@@ -13237,7 +13363,7 @@ async function backfill(o) {
|
|
|
13237
13363
|
let runs2 = 0, sent = 0;
|
|
13238
13364
|
const failed = [];
|
|
13239
13365
|
for (const id of ids) {
|
|
13240
|
-
const runDir =
|
|
13366
|
+
const runDir = join5(root, id);
|
|
13241
13367
|
if (!hasSpool(runDir))
|
|
13242
13368
|
continue;
|
|
13243
13369
|
runs2++;
|
|
@@ -13256,57 +13382,17 @@ async function backfill(o) {
|
|
|
13256
13382
|
return { runs: runs2, sent, failed };
|
|
13257
13383
|
}
|
|
13258
13384
|
|
|
13259
|
-
// src/db/prune.ts
|
|
13260
|
-
var n = (sqlite, sql2, ...args) => sqlite.query(sql2).get(...args).n;
|
|
13261
|
-
function countFor(sqlite, keys) {
|
|
13262
|
-
const bySource = new Map;
|
|
13263
|
-
for (const k of keys)
|
|
13264
|
-
bySource.set(k.source_id, [...bySource.get(k.source_id) ?? [], k.run_id]);
|
|
13265
|
-
let events2 = 0, sources2 = 0;
|
|
13266
|
-
for (const [src, runIds] of bySource) {
|
|
13267
|
-
let ev = 0;
|
|
13268
|
-
for (const r of runIds)
|
|
13269
|
-
ev += n(sqlite, "select count(*) n from events where source_id = ? and run_id = ?", src, r);
|
|
13270
|
-
events2 += ev;
|
|
13271
|
-
const runsLeft = n(sqlite, "select count(*) n from runs where source_id = ?", src) - runIds.length;
|
|
13272
|
-
const eventsLeft = n(sqlite, "select count(*) n from events where source_id = ?", src) - ev;
|
|
13273
|
-
if (runsLeft <= 0 && eventsLeft <= 0 && sqlite.query("select 1 x from sources where id = ?").get(src))
|
|
13274
|
-
sources2++;
|
|
13275
|
-
}
|
|
13276
|
-
return { runs: keys.length, events: events2, sources: sources2 };
|
|
13277
|
-
}
|
|
13278
|
-
function audit2(sqlite, action, actor, detail) {
|
|
13279
|
-
sqlite.query("insert into audit (ts, action, actor, detail) values (?, ?, ?, ?)").run(new Date().toISOString(), action, actor, JSON.stringify(detail));
|
|
13280
|
-
}
|
|
13281
|
-
var dropOrphanSources = (sqlite, ids) => {
|
|
13282
|
-
for (const id of ids)
|
|
13283
|
-
sqlite.query("delete from sources where id = ? and not exists (select 1 from runs where source_id = ?) and not exists (select 1 from events where source_id = ?)").run(id, id, id);
|
|
13284
|
-
};
|
|
13285
|
-
function removeRun(sqlite, source, run, actor = "http") {
|
|
13286
|
-
let out = null;
|
|
13287
|
-
sqlite.transaction(() => {
|
|
13288
|
-
if (!sqlite.query("select 1 x from runs where source_id = ? and run_id = ?").get(source, run))
|
|
13289
|
-
return;
|
|
13290
|
-
out = countFor(sqlite, [{ source_id: source, run_id: run }]);
|
|
13291
|
-
audit2(sqlite, "run.remove", actor, { source_id: source, run_id: run, counts: out });
|
|
13292
|
-
sqlite.query("delete from events where source_id = ? and run_id = ?").run(source, run);
|
|
13293
|
-
sqlite.query("delete from runs where source_id = ? and run_id = ?").run(source, run);
|
|
13294
|
-
dropOrphanSources(sqlite, [source]);
|
|
13295
|
-
}).immediate();
|
|
13296
|
-
return out;
|
|
13297
|
-
}
|
|
13298
|
-
|
|
13299
13385
|
// src/shipper/discover.ts
|
|
13300
|
-
import { readFileSync as readFileSync5, realpathSync as
|
|
13301
|
-
import { basename, dirname, join as
|
|
13386
|
+
import { readFileSync as readFileSync5, realpathSync as realpathSync3, statSync as statSync2 } from "fs";
|
|
13387
|
+
import { basename, dirname, join as join7, resolve } from "path";
|
|
13302
13388
|
|
|
13303
13389
|
// src/shipper/watch.ts
|
|
13304
13390
|
import { readFileSync as readFileSync4, readdirSync as readdirSync2, statSync } from "fs";
|
|
13305
|
-
import { homedir as
|
|
13306
|
-
import { join as
|
|
13391
|
+
import { homedir as homedir3 } from "os";
|
|
13392
|
+
import { join as join6 } from "path";
|
|
13307
13393
|
function registryRepos(env) {
|
|
13308
13394
|
try {
|
|
13309
|
-
const reg = JSON.parse(readFileSync4(
|
|
13395
|
+
const reg = JSON.parse(readFileSync4(join6(env.HOME || homedir3(), ".loki", "dashboard", "projects.json"), "utf8"));
|
|
13310
13396
|
return Object.values(reg.projects ?? {}).map((p) => p.path).filter((p) => typeof p === "string" && p.startsWith("/"));
|
|
13311
13397
|
} catch {
|
|
13312
13398
|
return [];
|
|
@@ -13318,7 +13404,7 @@ function discoverLocalRepos(repoDir, env = process.env) {
|
|
|
13318
13404
|
const out = new Map;
|
|
13319
13405
|
for (const dir of [repoDir, ...registryRepos(env)]) {
|
|
13320
13406
|
try {
|
|
13321
|
-
const real2 =
|
|
13407
|
+
const real2 = realpathSync3(dir);
|
|
13322
13408
|
if (!statSync2(real2).isDirectory())
|
|
13323
13409
|
continue;
|
|
13324
13410
|
const id = sourceId(real2);
|
|
@@ -13346,8 +13432,8 @@ function mountRepos(act, db, peerIsLoopback2) {
|
|
|
13346
13432
|
}
|
|
13347
13433
|
|
|
13348
13434
|
// src/server/routes/artifacts.ts
|
|
13349
|
-
import { closeSync as closeSync2, fstatSync as fstatSync2, openSync as openSync2, readSync as readSync2, realpathSync as
|
|
13350
|
-
import { join as
|
|
13435
|
+
import { closeSync as closeSync2, fstatSync as fstatSync2, openSync as openSync2, readSync as readSync2, realpathSync as realpathSync4 } from "fs";
|
|
13436
|
+
import { join as join8, sep as sep2 } from "path";
|
|
13351
13437
|
var MAX_ARTIFACT_BYTES = 5 * 1024 * 1024;
|
|
13352
13438
|
var EVENTS_DEFAULT = 500;
|
|
13353
13439
|
var EVENTS_MAX = 1000;
|
|
@@ -13428,19 +13514,19 @@ function mount(ctx) {
|
|
|
13428
13514
|
return c.json({ error: "artifact not available" }, 404);
|
|
13429
13515
|
let runsRoot, runDir, file;
|
|
13430
13516
|
try {
|
|
13431
|
-
runsRoot =
|
|
13432
|
-
runDir =
|
|
13517
|
+
runsRoot = realpathSync4(join8(repo.p, ".loki", "runs"));
|
|
13518
|
+
runDir = realpathSync4(join8(runsRoot, run));
|
|
13433
13519
|
} catch {
|
|
13434
13520
|
return c.json({ error: "artifact not found" }, 404);
|
|
13435
13521
|
}
|
|
13436
|
-
if (runDir !==
|
|
13522
|
+
if (runDir !== join8(runsRoot, run))
|
|
13437
13523
|
return c.json({ error: "artifact not found" }, 404);
|
|
13438
13524
|
try {
|
|
13439
|
-
file =
|
|
13525
|
+
file = realpathSync4(join8(runDir, name));
|
|
13440
13526
|
} catch {
|
|
13441
13527
|
return c.json({ error: "artifact not found" }, 404);
|
|
13442
13528
|
}
|
|
13443
|
-
if (!file.startsWith(runDir +
|
|
13529
|
+
if (!file.startsWith(runDir + sep2))
|
|
13444
13530
|
return c.json({ error: "artifact not found" }, 404);
|
|
13445
13531
|
const r = readCapped(file);
|
|
13446
13532
|
if (r.status === 413)
|
|
@@ -13478,8 +13564,8 @@ function mount2(ctx) {
|
|
|
13478
13564
|
|
|
13479
13565
|
// src/server/routes/checkpoints.ts
|
|
13480
13566
|
import { execFileSync } from "child_process";
|
|
13481
|
-
import { appendFileSync, copyFileSync, cpSync, existsSync as existsSync5, lstatSync, mkdirSync as mkdirSync3, readdirSync as readdirSync4, readFileSync as readFileSync7, realpathSync as
|
|
13482
|
-
import { join as
|
|
13567
|
+
import { appendFileSync, copyFileSync, cpSync, existsSync as existsSync5, lstatSync, mkdirSync as mkdirSync3, readdirSync as readdirSync4, readFileSync as readFileSync7, realpathSync as realpathSync5, rmSync, statSync as statSync4, writeFileSync as writeFileSync3 } from "fs";
|
|
13568
|
+
import { join as join10, sep as sep3 } from "path";
|
|
13483
13569
|
|
|
13484
13570
|
// src/server/audit.ts
|
|
13485
13571
|
var clip = (s, n2) => s === undefined ? null : s.slice(0, n2);
|
|
@@ -13494,7 +13580,7 @@ function audit3(db, e) {
|
|
|
13494
13580
|
|
|
13495
13581
|
// src/server/routes/cost_ledger.ts
|
|
13496
13582
|
import { existsSync as existsSync4, readdirSync as readdirSync3, readFileSync as readFileSync6, statSync as statSync3 } from "fs";
|
|
13497
|
-
import { join as
|
|
13583
|
+
import { join as join9 } from "path";
|
|
13498
13584
|
var isNum = (v) => typeof v === "number" && Number.isFinite(v);
|
|
13499
13585
|
var isObj2 = (v) => typeof v === "object" && v !== null && !Array.isArray(v);
|
|
13500
13586
|
var r6 = (n2) => Math.round(n2 * 1e6) / 1e6;
|
|
@@ -13525,7 +13611,7 @@ var DEFAULT_PRICING = {
|
|
|
13525
13611
|
"gpt-5.6-luna": { input: 0.5, output: 4 }
|
|
13526
13612
|
};
|
|
13527
13613
|
function pricing(lokiDir) {
|
|
13528
|
-
const d = readJson(
|
|
13614
|
+
const d = readJson(join9(lokiDir, "pricing.json"));
|
|
13529
13615
|
const models = isObj2(d) && isObj2(d.models) ? d.models : {};
|
|
13530
13616
|
return { ...DEFAULT_PRICING, ...models };
|
|
13531
13617
|
}
|
|
@@ -13538,7 +13624,7 @@ function modelCost(table, model, inp, out, cr = 0, cw = 0) {
|
|
|
13538
13624
|
return r6((inp * ir + out * or2 + cr * crr + cw * cwr) / 1e6);
|
|
13539
13625
|
}
|
|
13540
13626
|
function iterationRecords(lokiDir) {
|
|
13541
|
-
const dir =
|
|
13627
|
+
const dir = join9(lokiDir, "metrics", "efficiency");
|
|
13542
13628
|
let names = [];
|
|
13543
13629
|
try {
|
|
13544
13630
|
names = readdirSync3(dir).filter((n2) => /^iteration-.*\.json$/.test(n2)).sort();
|
|
@@ -13547,7 +13633,7 @@ function iterationRecords(lokiDir) {
|
|
|
13547
13633
|
}
|
|
13548
13634
|
const out = [];
|
|
13549
13635
|
for (const n2 of names) {
|
|
13550
|
-
const d = readJson(
|
|
13636
|
+
const d = readJson(join9(dir, n2));
|
|
13551
13637
|
if (isObj2(d))
|
|
13552
13638
|
out.push(d);
|
|
13553
13639
|
}
|
|
@@ -13584,7 +13670,7 @@ function costSnapshot(lokiDir) {
|
|
|
13584
13670
|
}
|
|
13585
13671
|
}
|
|
13586
13672
|
if (tIn === 0 && tOut === 0) {
|
|
13587
|
-
const ctx = readJson(
|
|
13673
|
+
const ctx = readJson(join9(lokiDir, "context", "tracking.json"));
|
|
13588
13674
|
const totals = isObj2(ctx) && isObj2(ctx.totals) ? ctx.totals : {};
|
|
13589
13675
|
tIn = num0(totals.total_input);
|
|
13590
13676
|
tOut = num0(totals.total_output);
|
|
@@ -13610,7 +13696,7 @@ function costSnapshot(lokiDir) {
|
|
|
13610
13696
|
}
|
|
13611
13697
|
}
|
|
13612
13698
|
let limit = null, used = null, remaining = null;
|
|
13613
|
-
const bd = readJson(
|
|
13699
|
+
const bd = readJson(join9(lokiDir, "metrics", "budget.json"));
|
|
13614
13700
|
if (isObj2(bd) && isNum(bd.limit)) {
|
|
13615
13701
|
limit = bd.limit;
|
|
13616
13702
|
if (recorded && !unknownUsd) {
|
|
@@ -13661,7 +13747,7 @@ function budgetSnapshot(lokiDir) {
|
|
|
13661
13747
|
total += priced(table, d);
|
|
13662
13748
|
}
|
|
13663
13749
|
let limit = null;
|
|
13664
|
-
const bd = readJson(
|
|
13750
|
+
const bd = readJson(join9(lokiDir, "metrics", "budget.json"));
|
|
13665
13751
|
if (isObj2(bd)) {
|
|
13666
13752
|
const l = bd.limit || bd.budget_limit;
|
|
13667
13753
|
limit = isNum(l) ? l : null;
|
|
@@ -13712,19 +13798,19 @@ function costTimeline(lokiDir) {
|
|
|
13712
13798
|
}
|
|
13713
13799
|
const runs2 = [];
|
|
13714
13800
|
let projectTotal = 0, runsMeasured = 0;
|
|
13715
|
-
const proofs =
|
|
13801
|
+
const proofs = join9(lokiDir, "proofs");
|
|
13716
13802
|
let entries = [];
|
|
13717
13803
|
try {
|
|
13718
13804
|
entries = readdirSync3(proofs).sort();
|
|
13719
13805
|
} catch {}
|
|
13720
13806
|
for (const e of entries) {
|
|
13721
13807
|
try {
|
|
13722
|
-
if (!statSync3(
|
|
13808
|
+
if (!statSync3(join9(proofs, e)).isDirectory())
|
|
13723
13809
|
continue;
|
|
13724
13810
|
} catch {
|
|
13725
13811
|
continue;
|
|
13726
13812
|
}
|
|
13727
|
-
const p = readJson(
|
|
13813
|
+
const p = readJson(join9(proofs, e, "proof.json"));
|
|
13728
13814
|
if (!isObj2(p))
|
|
13729
13815
|
continue;
|
|
13730
13816
|
const cost = isObj2(p.cost) ? p.cost : {};
|
|
@@ -13754,7 +13840,7 @@ function costTimeline(lokiDir) {
|
|
|
13754
13840
|
...recorded || runsMeasured ? {} : { not_measured: NOT_MEASURED }
|
|
13755
13841
|
};
|
|
13756
13842
|
}
|
|
13757
|
-
var lokiDirOf = (repoDir) =>
|
|
13843
|
+
var lokiDirOf = (repoDir) => join9(repoDir, ".loki");
|
|
13758
13844
|
var lokiDirExists = (repoDir) => existsSync4(lokiDirOf(repoDir));
|
|
13759
13845
|
function mount3(ctx) {
|
|
13760
13846
|
const deny = { error: "loopback only without a token" };
|
|
@@ -13768,8 +13854,8 @@ var str2 = (v) => typeof v === "string" && v !== "" ? v : null;
|
|
|
13768
13854
|
var SAFE_ID = /^[A-Za-z0-9_-]{1,128}$/;
|
|
13769
13855
|
var MAX_CHECKPOINTS = 50;
|
|
13770
13856
|
var validCheckpointId = (id) => SAFE_ID.test(id);
|
|
13771
|
-
var storeOf = (repoDir) =>
|
|
13772
|
-
var lokiOf = (repoDir) =>
|
|
13857
|
+
var storeOf = (repoDir) => join10(repoDir, ".loki", "state", "checkpoints");
|
|
13858
|
+
var lokiOf = (repoDir) => join10(repoDir, ".loki");
|
|
13773
13859
|
var readObj = (f) => {
|
|
13774
13860
|
try {
|
|
13775
13861
|
const d = JSON.parse(readFileSync7(f, "utf8"));
|
|
@@ -13783,7 +13869,7 @@ function listCheckpoints(repoDir, limit = 20) {
|
|
|
13783
13869
|
const store = storeOf(repoDir);
|
|
13784
13870
|
let lines = [];
|
|
13785
13871
|
try {
|
|
13786
|
-
lines = readFileSync7(
|
|
13872
|
+
lines = readFileSync7(join10(store, "index.jsonl"), "utf8").split(`
|
|
13787
13873
|
`);
|
|
13788
13874
|
} catch {
|
|
13789
13875
|
return [];
|
|
@@ -13811,7 +13897,7 @@ function listCheckpoints(repoDir, limit = 20) {
|
|
|
13811
13897
|
message: str2(raw2.task) ?? str2(raw2.message) ?? str2(raw2.task_description),
|
|
13812
13898
|
iteration: Number.isInteger(it) ? it : null
|
|
13813
13899
|
};
|
|
13814
|
-
const meta = readObj(
|
|
13900
|
+
const meta = readObj(join10(store, id, "metadata.json"));
|
|
13815
13901
|
if (meta) {
|
|
13816
13902
|
cp.git_branch = str2(meta.git_branch);
|
|
13817
13903
|
cp.provider = str2(meta.provider);
|
|
@@ -13819,7 +13905,7 @@ function listCheckpoints(repoDir, limit = 20) {
|
|
|
13819
13905
|
cp.message ??= str2(meta.task_description);
|
|
13820
13906
|
cp.git_sha ??= str2(meta.git_sha);
|
|
13821
13907
|
cp.created_at ??= str2(meta.timestamp);
|
|
13822
|
-
cp.files_count = countFiles(
|
|
13908
|
+
cp.files_count = countFiles(join10(store, id));
|
|
13823
13909
|
}
|
|
13824
13910
|
out.push(cp);
|
|
13825
13911
|
}
|
|
@@ -13830,7 +13916,7 @@ function countFiles(dir) {
|
|
|
13830
13916
|
const walk = (d, top) => {
|
|
13831
13917
|
for (const e of readdirSync4(d, { withFileTypes: true })) {
|
|
13832
13918
|
if (e.isDirectory())
|
|
13833
|
-
walk(
|
|
13919
|
+
walk(join10(d, e.name), false);
|
|
13834
13920
|
else if (e.isFile() && !(top && e.name === "metadata.json"))
|
|
13835
13921
|
n2++;
|
|
13836
13922
|
}
|
|
@@ -13841,7 +13927,7 @@ function countFiles(dir) {
|
|
|
13841
13927
|
return n2;
|
|
13842
13928
|
}
|
|
13843
13929
|
function getCheckpoint(repoDir, id) {
|
|
13844
|
-
return validCheckpointId(id) ? readObj(
|
|
13930
|
+
return validCheckpointId(id) ? readObj(join10(storeOf(repoDir), id, "metadata.json")) : null;
|
|
13845
13931
|
}
|
|
13846
13932
|
var gitHead = (repoDir) => {
|
|
13847
13933
|
try {
|
|
@@ -13853,69 +13939,69 @@ var gitHead = (repoDir) => {
|
|
|
13853
13939
|
function createCheckpoint(repoDir, message, now = new Date) {
|
|
13854
13940
|
const store = storeOf(repoDir), loki = lokiOf(repoDir);
|
|
13855
13941
|
mkdirSync3(store, { recursive: true });
|
|
13856
|
-
const id = `chk-${stamp(now)}`, dir =
|
|
13942
|
+
const id = `chk-${stamp(now)}`, dir = join10(store, id);
|
|
13857
13943
|
mkdirSync3(dir, { recursive: true });
|
|
13858
13944
|
for (const f of ["dashboard-state.json", "session.json"]) {
|
|
13859
13945
|
try {
|
|
13860
|
-
if (existsSync5(
|
|
13861
|
-
copyFileSync(
|
|
13946
|
+
if (existsSync5(join10(loki, f)))
|
|
13947
|
+
copyFileSync(join10(loki, f), join10(dir, f));
|
|
13862
13948
|
} catch {}
|
|
13863
13949
|
}
|
|
13864
13950
|
try {
|
|
13865
|
-
if (existsSync5(
|
|
13866
|
-
cpSync(
|
|
13951
|
+
if (existsSync5(join10(loki, "queue")))
|
|
13952
|
+
cpSync(join10(loki, "queue"), join10(dir, "queue"), { recursive: true });
|
|
13867
13953
|
} catch {}
|
|
13868
13954
|
const meta = { id, created_at: now.toISOString(), git_sha: gitHead(repoDir), message: message || null, files: readdirSync4(dir, { withFileTypes: true }).filter((e) => e.isFile()).map((e) => e.name) };
|
|
13869
|
-
writeFileSync3(
|
|
13870
|
-
appendFileSync(
|
|
13955
|
+
writeFileSync3(join10(dir, "metadata.json"), JSON.stringify(meta, null, 2));
|
|
13956
|
+
appendFileSync(join10(store, "index.jsonl"), `${JSON.stringify(meta)}
|
|
13871
13957
|
`);
|
|
13872
13958
|
const mine = readdirSync4(store, { withFileTypes: true }).filter((e) => e.isDirectory() && e.name.startsWith("chk-")).map((e) => e.name).sort();
|
|
13873
13959
|
while (mine.length > MAX_CHECKPOINTS)
|
|
13874
|
-
rmSync(
|
|
13960
|
+
rmSync(join10(store, mine.shift()), { recursive: true, force: true });
|
|
13875
13961
|
return meta;
|
|
13876
13962
|
}
|
|
13877
13963
|
function rollbackCheckpoint(repoDir, id, now = new Date) {
|
|
13878
13964
|
const store = storeOf(repoDir), loki = lokiOf(repoDir);
|
|
13879
13965
|
let real2 = "", realStore = "";
|
|
13880
13966
|
try {
|
|
13881
|
-
real2 =
|
|
13882
|
-
realStore =
|
|
13967
|
+
real2 = realpathSync5(join10(store, id));
|
|
13968
|
+
realStore = realpathSync5(store);
|
|
13883
13969
|
} catch {
|
|
13884
13970
|
return null;
|
|
13885
13971
|
}
|
|
13886
|
-
if (!real2.startsWith(`${realStore}${
|
|
13972
|
+
if (!real2.startsWith(`${realStore}${sep3}`) || !statSync4(real2).isDirectory())
|
|
13887
13973
|
return null;
|
|
13888
|
-
const preId = `rb-pre-${stamp(now)}`, pre =
|
|
13974
|
+
const preId = `rb-pre-${stamp(now)}`, pre = join10(store, preId);
|
|
13889
13975
|
mkdirSync3(pre, { recursive: true });
|
|
13890
13976
|
const notStore = (src) => src !== store;
|
|
13891
13977
|
for (const n2 of ["session.json", "dashboard-state.json", "CONTINUITY.md", "autonomy-state.json"]) {
|
|
13892
13978
|
try {
|
|
13893
|
-
if (existsSync5(
|
|
13894
|
-
copyFileSync(
|
|
13979
|
+
if (existsSync5(join10(loki, n2)))
|
|
13980
|
+
copyFileSync(join10(loki, n2), join10(pre, n2));
|
|
13895
13981
|
} catch {}
|
|
13896
13982
|
}
|
|
13897
13983
|
for (const d of ["state", "queue"]) {
|
|
13898
13984
|
try {
|
|
13899
|
-
if (existsSync5(
|
|
13900
|
-
cpSync(
|
|
13985
|
+
if (existsSync5(join10(loki, d)))
|
|
13986
|
+
cpSync(join10(loki, d), join10(pre, d), { recursive: true, filter: notStore });
|
|
13901
13987
|
} catch {}
|
|
13902
13988
|
}
|
|
13903
13989
|
const preMeta = { id: preId, created_at: now.toISOString(), message: `pre-rollback snapshot (before restoring ${id})`, created_by: "control plane rollback" };
|
|
13904
|
-
writeFileSync3(
|
|
13905
|
-
appendFileSync(
|
|
13990
|
+
writeFileSync3(join10(pre, "metadata.json"), JSON.stringify(preMeta, null, 2));
|
|
13991
|
+
appendFileSync(join10(store, "index.jsonl"), `${JSON.stringify(preMeta)}
|
|
13906
13992
|
`);
|
|
13907
13993
|
let restored = 0;
|
|
13908
13994
|
const errors = [];
|
|
13909
13995
|
for (const e of readdirSync4(real2, { withFileTypes: true })) {
|
|
13910
13996
|
if (e.name === "metadata.json" || e.name === "worktree-snapshot.txt")
|
|
13911
13997
|
continue;
|
|
13912
|
-
const src =
|
|
13998
|
+
const src = join10(real2, e.name);
|
|
13913
13999
|
try {
|
|
13914
14000
|
if (lstatSync(src).isSymbolicLink()) {
|
|
13915
14001
|
errors.push(`${e.name}: symlink skipped`);
|
|
13916
14002
|
continue;
|
|
13917
14003
|
}
|
|
13918
|
-
cpSync(src,
|
|
14004
|
+
cpSync(src, join10(loki, e.name), { recursive: true, filter: (s) => s !== join10(real2, "state", "checkpoints") });
|
|
13919
14005
|
restored++;
|
|
13920
14006
|
} catch (err) {
|
|
13921
14007
|
errors.push(`${e.name}: ${err.message}`);
|
|
@@ -13925,14 +14011,14 @@ function rollbackCheckpoint(repoDir, id, now = new Date) {
|
|
|
13925
14011
|
}
|
|
13926
14012
|
function auditSummary(db, days, now = Date.now()) {
|
|
13927
14013
|
const since = new Date(now - days * 86400000).toISOString();
|
|
13928
|
-
const where = gte(
|
|
13929
|
-
const total = db.select({ n: sql`count(*)` }).from(
|
|
13930
|
-
const by = (col) => Object.fromEntries(db.select({ k: col, n: sql`count(*)` }).from(
|
|
14014
|
+
const where = gte(audit2.ts, since);
|
|
14015
|
+
const total = db.select({ n: sql`count(*)` }).from(audit2).where(where).get()?.n ?? 0;
|
|
14016
|
+
const by = (col) => Object.fromEntries(db.select({ k: col, n: sql`count(*)` }).from(audit2).where(where).groupBy(col).all().map((r) => [r.k, r.n]));
|
|
13931
14017
|
return {
|
|
13932
14018
|
period_days: days,
|
|
13933
14019
|
total_events: total,
|
|
13934
|
-
by_action: by(
|
|
13935
|
-
by_user: by(
|
|
14020
|
+
by_action: by(audit2.action),
|
|
14021
|
+
by_user: by(audit2.actor),
|
|
13936
14022
|
successful_events: null,
|
|
13937
14023
|
failed_events: null,
|
|
13938
14024
|
by_resource_type: null,
|
|
@@ -13998,8 +14084,8 @@ function mount4(ctx) {
|
|
|
13998
14084
|
|
|
13999
14085
|
// src/server/routes/config.ts
|
|
14000
14086
|
import { createHash as createHash5, randomBytes } from "crypto";
|
|
14001
|
-
import { closeSync as closeSync3, fchmodSync, fsyncSync, lstatSync as lstatSync2, openSync as openSync3, readFileSync as readFileSync9, realpathSync as
|
|
14002
|
-
import { basename as basename3, dirname as dirname2, join as
|
|
14087
|
+
import { closeSync as closeSync3, fchmodSync, fsyncSync, lstatSync as lstatSync2, openSync as openSync3, readFileSync as readFileSync9, realpathSync as realpathSync6, renameSync as renameSync2, statSync as statSync6, unlinkSync, writeSync as writeSync2 } from "fs";
|
|
14088
|
+
import { basename as basename3, dirname as dirname2, join as join12, sep as sep4 } from "path";
|
|
14003
14089
|
|
|
14004
14090
|
// node_modules/yaml/dist/index.js
|
|
14005
14091
|
var composer = require_composer();
|
|
@@ -14050,8 +14136,8 @@ var $visitAsync = visit.visitAsync;
|
|
|
14050
14136
|
// src/server/spawn.ts
|
|
14051
14137
|
import { spawn } from "child_process";
|
|
14052
14138
|
import { existsSync as existsSync6, readFileSync as readFileSync8, statSync as statSync5 } from "fs";
|
|
14053
|
-
import { homedir as
|
|
14054
|
-
import { basename as basename2, join as
|
|
14139
|
+
import { homedir as homedir4 } from "os";
|
|
14140
|
+
import { basename as basename2, join as join11, resolve as resolve2 } from "path";
|
|
14055
14141
|
var ISSUE = /^[A-Za-z0-9][A-Za-z0-9._-]{0,99}\/[A-Za-z0-9._-]{1,100}#[1-9][0-9]{0,8}$/;
|
|
14056
14142
|
var TASK = /^[A-Za-z0-9][A-Za-z0-9 .,_/#@+=-]{0,499}$/;
|
|
14057
14143
|
var hasDotDot = (t) => t.split(/[\\/ ]/).some((seg) => seg.startsWith(".."));
|
|
@@ -14067,7 +14153,7 @@ var BUDGET = /^(?:[1-9][0-9]{0,5}|0)(?:\.[0-9]{1,2})?$/;
|
|
|
14067
14153
|
var WORKSPACE = /^[A-Za-z0-9][A-Za-z0-9_-]{0,63}$/;
|
|
14068
14154
|
function registryRepos2(env = process.env) {
|
|
14069
14155
|
try {
|
|
14070
|
-
const j = JSON.parse(readFileSync8(
|
|
14156
|
+
const j = JSON.parse(readFileSync8(join11(env.HOME || homedir4(), ".loki", "dashboard", "projects.json"), "utf8"));
|
|
14071
14157
|
return Object.values(j.projects ?? {}).map((p) => typeof p?.path === "string" ? p.path : "").filter((p) => p && existsSync6(p) && statSync5(p).isDirectory());
|
|
14072
14158
|
} catch {
|
|
14073
14159
|
return [];
|
|
@@ -14512,11 +14598,11 @@ function renderVerified(doc, next, quote = needsQuote) {
|
|
|
14512
14598
|
function resolveConfigFile(repoDir) {
|
|
14513
14599
|
let root;
|
|
14514
14600
|
try {
|
|
14515
|
-
root =
|
|
14601
|
+
root = realpathSync6(repoDir);
|
|
14516
14602
|
} catch {
|
|
14517
14603
|
return { ok: false, status: 400, error: "repo directory not found" };
|
|
14518
14604
|
}
|
|
14519
|
-
const p =
|
|
14605
|
+
const p = join12(root, "loki.yaml");
|
|
14520
14606
|
let st;
|
|
14521
14607
|
try {
|
|
14522
14608
|
st = lstatSync2(p);
|
|
@@ -14526,11 +14612,11 @@ function resolveConfigFile(repoDir) {
|
|
|
14526
14612
|
if (st.isSymbolicLink()) {
|
|
14527
14613
|
let real2;
|
|
14528
14614
|
try {
|
|
14529
|
-
real2 =
|
|
14615
|
+
real2 = realpathSync6(p);
|
|
14530
14616
|
} catch {
|
|
14531
14617
|
return { ok: false, status: 400, error: "loki.yaml is a broken symlink" };
|
|
14532
14618
|
}
|
|
14533
|
-
if (!real2.startsWith(root +
|
|
14619
|
+
if (!real2.startsWith(root + sep4))
|
|
14534
14620
|
return { ok: false, status: 400, error: "loki.yaml is a symlink that resolves outside the repo" };
|
|
14535
14621
|
if (!statSync6(real2).isFile())
|
|
14536
14622
|
return { ok: false, status: 422, error: "loki.yaml is not a regular file" };
|
|
@@ -14541,7 +14627,7 @@ function resolveConfigFile(repoDir) {
|
|
|
14541
14627
|
return { ok: true, file: p, exists: true };
|
|
14542
14628
|
}
|
|
14543
14629
|
function atomicWrite(file, text2, mode) {
|
|
14544
|
-
const tmp =
|
|
14630
|
+
const tmp = join12(dirname2(file), `.${basename3(file)}.${process.pid}.${randomBytes(6).toString("hex")}.tmp`);
|
|
14545
14631
|
const fd = openSync3(tmp, "wx", mode);
|
|
14546
14632
|
try {
|
|
14547
14633
|
writeSync2(fd, text2);
|
|
@@ -14691,14 +14777,14 @@ function mount6(ctx) {
|
|
|
14691
14777
|
}
|
|
14692
14778
|
|
|
14693
14779
|
// src/server/routes/control.ts
|
|
14694
|
-
import { existsSync as existsSync7, realpathSync as
|
|
14695
|
-
import { join as
|
|
14780
|
+
import { existsSync as existsSync7, realpathSync as realpathSync7 } from "fs";
|
|
14781
|
+
import { join as join14, resolve as resolve3, sep as sep5 } from "path";
|
|
14696
14782
|
|
|
14697
14783
|
// ../../loki-ts/src/util/run_pid.ts
|
|
14698
14784
|
import { execFileSync as execFileSync2 } from "child_process";
|
|
14699
14785
|
import { chmodSync as chmodSync2, readFileSync as readFileSync10, renameSync as renameSync3, rmSync as rmSync2, writeFileSync as writeFileSync4 } from "fs";
|
|
14700
|
-
import { join as
|
|
14701
|
-
var runPidPath = (runDir) =>
|
|
14786
|
+
import { join as join13 } from "path";
|
|
14787
|
+
var runPidPath = (runDir) => join13(runDir, "run.pid");
|
|
14702
14788
|
function processStartTime(pid) {
|
|
14703
14789
|
if (!Number.isInteger(pid) || pid <= 1)
|
|
14704
14790
|
return null;
|
|
@@ -14754,14 +14840,14 @@ function mount7(ctx) {
|
|
|
14754
14840
|
const repoRow = db.select().from(localRepos).where(eq(localRepos.sourceId, source)).get();
|
|
14755
14841
|
let repo = "";
|
|
14756
14842
|
try {
|
|
14757
|
-
repo = repoRow ?
|
|
14843
|
+
repo = repoRow ? realpathSync7(repoRow.realpath) : "";
|
|
14758
14844
|
} catch {
|
|
14759
14845
|
repo = "";
|
|
14760
14846
|
}
|
|
14761
14847
|
if (!repo)
|
|
14762
14848
|
return done(404, "refused", { error: "this run's repo is not known on this machine" });
|
|
14763
|
-
const runsRoot =
|
|
14764
|
-
if (!runDir.startsWith(runsRoot +
|
|
14849
|
+
const runsRoot = join14(repo, ".loki", "runs"), runDir = resolve3(runsRoot, id);
|
|
14850
|
+
if (!runDir.startsWith(runsRoot + sep5))
|
|
14765
14851
|
return done(400, "refused", { error: "invalid run id" }, "path escape");
|
|
14766
14852
|
return handler({ source, run: id, repo, runDir }, done);
|
|
14767
14853
|
});
|
|
@@ -14808,7 +14894,7 @@ function mount7(ctx) {
|
|
|
14808
14894
|
return done(404, "refused", { error: "run not found" });
|
|
14809
14895
|
if (!blockedQuestion(loadEvents(db, a.source, a.run), row.verdict))
|
|
14810
14896
|
return done(409, "refused", { error: "run is not BLOCKED, so there is nothing to resume" });
|
|
14811
|
-
if (!existsSync7(
|
|
14897
|
+
if (!existsSync7(join14(answerDir, a.source, `${a.run}.answer.txt`)))
|
|
14812
14898
|
return done(409, "refused", { error: "no answer recorded for this run; answer it first" });
|
|
14813
14899
|
return launch(a, [bin, "answer", a.run], done);
|
|
14814
14900
|
});
|
|
@@ -14887,11 +14973,11 @@ function mount8(ctx) {
|
|
|
14887
14973
|
|
|
14888
14974
|
// src/server/routes/fleet.ts
|
|
14889
14975
|
import { readdirSync as readdirSync5, readFileSync as readFileSync11 } from "fs";
|
|
14890
|
-
import { homedir as
|
|
14891
|
-
import { basename as basename4, join as
|
|
14976
|
+
import { homedir as homedir5 } from "os";
|
|
14977
|
+
import { basename as basename4, join as join15 } from "path";
|
|
14892
14978
|
var isNum2 = (v) => typeof v === "number" && Number.isFinite(v);
|
|
14893
14979
|
var isObj5 = (v) => typeof v === "object" && v !== null && !Array.isArray(v);
|
|
14894
|
-
var registryFile = () =>
|
|
14980
|
+
var registryFile = () => join15(process.env.HOME ?? homedir5(), ".loki", "dashboard", "projects.json");
|
|
14895
14981
|
function pidAlive(pid) {
|
|
14896
14982
|
if (typeof pid !== "number" || !Number.isInteger(pid) || pid <= 0)
|
|
14897
14983
|
return false;
|
|
@@ -14906,33 +14992,33 @@ function projectSnapshot(path) {
|
|
|
14906
14992
|
const snap = { phase: "", iteration: null, cost_usd: null, started_at: null, ended_at: null };
|
|
14907
14993
|
if (!path)
|
|
14908
14994
|
return snap;
|
|
14909
|
-
const loki =
|
|
14910
|
-
const st = readJson(
|
|
14995
|
+
const loki = join15(path, ".loki");
|
|
14996
|
+
const st = readJson(join15(loki, "dashboard-state.json"));
|
|
14911
14997
|
if (isObj5(st)) {
|
|
14912
14998
|
snap.phase = typeof st.phase === "string" ? st.phase : "";
|
|
14913
14999
|
snap.iteration = Number.isInteger(st.iteration) ? st.iteration : null;
|
|
14914
15000
|
}
|
|
14915
|
-
const ss = readJson(
|
|
15001
|
+
const ss = readJson(join15(loki, "session.json"));
|
|
14916
15002
|
if (isObj5(ss)) {
|
|
14917
15003
|
const a = ss.startedAt || ss.started_at, b = ss.endedAt || ss.ended_at;
|
|
14918
15004
|
snap.started_at = typeof a === "string" ? a : null;
|
|
14919
15005
|
snap.ended_at = typeof b === "string" ? b : null;
|
|
14920
15006
|
}
|
|
14921
15007
|
let cost = 0, found = false;
|
|
14922
|
-
const eff =
|
|
15008
|
+
const eff = join15(loki, "metrics", "efficiency");
|
|
14923
15009
|
let names = [];
|
|
14924
15010
|
try {
|
|
14925
15011
|
names = readdirSync5(eff).filter((n2) => n2.endsWith(".json"));
|
|
14926
15012
|
} catch {}
|
|
14927
15013
|
for (const n2 of names) {
|
|
14928
|
-
const d = readJson(
|
|
15014
|
+
const d = readJson(join15(eff, n2));
|
|
14929
15015
|
if (isObj5(d) && isNum2(d.cost_usd) && recordIsMeasured(d)) {
|
|
14930
15016
|
cost += d.cost_usd;
|
|
14931
15017
|
found = true;
|
|
14932
15018
|
}
|
|
14933
15019
|
}
|
|
14934
15020
|
if (!found) {
|
|
14935
|
-
const t = readJson(
|
|
15021
|
+
const t = readJson(join15(loki, "context", "tracking.json"));
|
|
14936
15022
|
const totals = isObj5(t) && isObj5(t.totals) ? t.totals : null;
|
|
14937
15023
|
if (totals && isNum2(totals.total_cost_usd) && recordIsMeasured({ cost_usd: totals.total_cost_usd, input_tokens: totals.total_input, output_tokens: totals.total_output })) {
|
|
14938
15024
|
cost = totals.total_cost_usd;
|
|
@@ -15005,8 +15091,8 @@ function mount9(ctx) {
|
|
|
15005
15091
|
}
|
|
15006
15092
|
|
|
15007
15093
|
// src/server/routes/memory.ts
|
|
15008
|
-
import { existsSync as existsSync8, readFileSync as readFileSync12, readdirSync as readdirSync6, realpathSync as
|
|
15009
|
-
import { join as
|
|
15094
|
+
import { existsSync as existsSync8, readFileSync as readFileSync12, readdirSync as readdirSync6, realpathSync as realpathSync8, statSync as statSync7 } from "fs";
|
|
15095
|
+
import { join as join16, sep as sep6 } from "path";
|
|
15010
15096
|
var isObj6 = (v) => typeof v === "object" && v !== null && !Array.isArray(v);
|
|
15011
15097
|
var nonEmpty = (v) => !(v === undefined || v === null || v === "" || Array.isArray(v) && v.length === 0 || isObj6(v) && Object.keys(v).length === 0);
|
|
15012
15098
|
var focus = { dir: null };
|
|
@@ -15037,7 +15123,7 @@ function jsonFiles(dir, cap = Infinity) {
|
|
|
15037
15123
|
for (const e of ents.sort((a, b) => a.name < b.name ? -1 : 1)) {
|
|
15038
15124
|
if (out2.length >= cap)
|
|
15039
15125
|
return;
|
|
15040
|
-
const p =
|
|
15126
|
+
const p = join16(d, e.name);
|
|
15041
15127
|
if (e.isDirectory())
|
|
15042
15128
|
walk(p);
|
|
15043
15129
|
else if (e.isFile() && e.name.endsWith(".json"))
|
|
@@ -15058,24 +15144,24 @@ function setFocus(repoDir, raw2, registry = registryFile()) {
|
|
|
15058
15144
|
return { ok: false, error: "project_dir must be an absolute path without .. segments" };
|
|
15059
15145
|
let real2 = "";
|
|
15060
15146
|
try {
|
|
15061
|
-
real2 =
|
|
15147
|
+
real2 = realpathSync8(s);
|
|
15062
15148
|
if (!statSync7(real2).isDirectory())
|
|
15063
15149
|
throw new Error("not a dir");
|
|
15064
15150
|
} catch {
|
|
15065
15151
|
return { ok: false, error: `Directory does not exist: ${s}` };
|
|
15066
15152
|
}
|
|
15067
|
-
if (!existsSync8(
|
|
15153
|
+
if (!existsSync8(join16(real2, ".loki")) || !statSync7(join16(real2, ".loki")).isDirectory())
|
|
15068
15154
|
return { ok: false, error: `Directory does not contain a .loki/ subdirectory: ${s}` };
|
|
15069
15155
|
const allowed = new Set;
|
|
15070
15156
|
try {
|
|
15071
|
-
allowed.add(
|
|
15157
|
+
allowed.add(realpathSync8(repoDir));
|
|
15072
15158
|
} catch {}
|
|
15073
15159
|
const reg = readJson(registry);
|
|
15074
15160
|
if (isObj6(reg) && isObj6(reg.projects))
|
|
15075
15161
|
for (const p of Object.values(reg.projects)) {
|
|
15076
15162
|
if (isObj6(p) && typeof p.path === "string") {
|
|
15077
15163
|
try {
|
|
15078
|
-
allowed.add(
|
|
15164
|
+
allowed.add(realpathSync8(p.path));
|
|
15079
15165
|
} catch {}
|
|
15080
15166
|
}
|
|
15081
15167
|
}
|
|
@@ -15089,7 +15175,7 @@ var QUEUES = [["pending.json", "pending"], ["in-progress.json", "in_progress"],
|
|
|
15089
15175
|
var RANK = { done: 3, in_progress: 2, review: 1, pending: 0 };
|
|
15090
15176
|
function tasksList(loki, status) {
|
|
15091
15177
|
const all = [];
|
|
15092
|
-
const st = readJson(
|
|
15178
|
+
const st = readJson(join16(loki, "dashboard-state.json"));
|
|
15093
15179
|
const groups = isObj6(st) && isObj6(st.tasks) ? st.tasks : {};
|
|
15094
15180
|
for (const [key, mapped] of GROUPS) {
|
|
15095
15181
|
const g = groups[key];
|
|
@@ -15111,7 +15197,7 @@ function tasksList(loki, status) {
|
|
|
15111
15197
|
});
|
|
15112
15198
|
}
|
|
15113
15199
|
for (const [file, qs] of QUEUES) {
|
|
15114
|
-
const raw2 = readJson(
|
|
15200
|
+
const raw2 = readJson(join16(loki, "queue", file));
|
|
15115
15201
|
const items = isObj6(raw2) ? raw2.tasks : raw2;
|
|
15116
15202
|
if (!Array.isArray(items))
|
|
15117
15203
|
continue;
|
|
@@ -15165,7 +15251,7 @@ function tasksList(loki, status) {
|
|
|
15165
15251
|
}
|
|
15166
15252
|
return status ? out2.filter((t) => t.status === status) : out2;
|
|
15167
15253
|
}
|
|
15168
|
-
var memDir = (loki, sub) =>
|
|
15254
|
+
var memDir = (loki, sub) => join16(loki, "memory", sub);
|
|
15169
15255
|
function memoryEpisodes(loki, limit = 50) {
|
|
15170
15256
|
const eps = jsonFiles(memDir(loki, "episodic")).map(readJson).filter(isObj6);
|
|
15171
15257
|
eps.sort((a, b) => String(b.timestamp ?? "").localeCompare(String(a.timestamp ?? "")));
|
|
@@ -15174,19 +15260,19 @@ function memoryEpisodes(loki, limit = 50) {
|
|
|
15174
15260
|
function memoryEpisode(loki, id) {
|
|
15175
15261
|
for (const f of jsonFiles(memDir(loki, "episodic"))) {
|
|
15176
15262
|
const d = readJson(f);
|
|
15177
|
-
if (isObj6(d) && (d.id === id || f.split(
|
|
15263
|
+
if (isObj6(d) && (d.id === id || f.split(sep6).pop() === `${id}.json`))
|
|
15178
15264
|
return d;
|
|
15179
15265
|
}
|
|
15180
15266
|
return null;
|
|
15181
15267
|
}
|
|
15182
15268
|
function memoryPatterns(loki) {
|
|
15183
|
-
const d = readJson(
|
|
15269
|
+
const d = readJson(join16(memDir(loki, "semantic"), "patterns.json"));
|
|
15184
15270
|
const list = Array.isArray(d) ? d : isObj6(d) && Array.isArray(d.patterns) ? d.patterns : [];
|
|
15185
15271
|
return list.filter(isObj6);
|
|
15186
15272
|
}
|
|
15187
15273
|
var memorySkills = (loki) => jsonFiles(memDir(loki, "skills")).map(readJson).filter(isObj6);
|
|
15188
15274
|
function memoryEconomics(loki) {
|
|
15189
|
-
const rawV = readJson(
|
|
15275
|
+
const rawV = readJson(join16(loki, "memory", "token_economics.json"));
|
|
15190
15276
|
const raw2 = isObj6(rawV) ? rawV : {};
|
|
15191
15277
|
const m = isObj6(raw2.metrics) ? raw2.metrics : {};
|
|
15192
15278
|
const n2 = (v) => typeof v === "number" && Number.isFinite(v) ? Math.trunc(v) : 0;
|
|
@@ -15222,7 +15308,7 @@ function memoryEconomics(loki) {
|
|
|
15222
15308
|
};
|
|
15223
15309
|
}
|
|
15224
15310
|
function contextTracking(loki) {
|
|
15225
|
-
const f =
|
|
15311
|
+
const f = join16(loki, "context", "tracking.json");
|
|
15226
15312
|
if (!existsSync8(f)) {
|
|
15227
15313
|
return { status: 200, body: {
|
|
15228
15314
|
session_id: null,
|
|
@@ -15284,7 +15370,7 @@ function mount10(ctx) {
|
|
|
15284
15370
|
|
|
15285
15371
|
// src/server/routes/metrics.ts
|
|
15286
15372
|
import { readdirSync as readdirSync7, readFileSync as readFileSync13, statSync as statSync8 } from "fs";
|
|
15287
|
-
import { join as
|
|
15373
|
+
import { join as join17 } from "path";
|
|
15288
15374
|
var isObj7 = (v) => typeof v === "object" && v !== null && !Array.isArray(v);
|
|
15289
15375
|
var arr = (v) => Array.isArray(v) ? v : [];
|
|
15290
15376
|
function buildMetricsText(repoDir, now = Date.now()) {
|
|
@@ -15301,7 +15387,7 @@ function buildMetricsText(repoDir, now = Date.now()) {
|
|
|
15301
15387
|
L.push(...samples);
|
|
15302
15388
|
L.push("");
|
|
15303
15389
|
};
|
|
15304
|
-
const stRaw = readJson(
|
|
15390
|
+
const stRaw = readJson(join17(loki, "dashboard-state.json"));
|
|
15305
15391
|
const state = isObj7(stRaw) ? stRaw : null;
|
|
15306
15392
|
let status = 0;
|
|
15307
15393
|
if (state?.mode === "paused")
|
|
@@ -15310,7 +15396,7 @@ function buildMetricsText(repoDir, now = Date.now()) {
|
|
|
15310
15396
|
status = 1;
|
|
15311
15397
|
else {
|
|
15312
15398
|
try {
|
|
15313
|
-
if (pidAlive(parseInt(readFileSync13(
|
|
15399
|
+
if (pidAlive(parseInt(readFileSync13(join17(loki, "loki.pid"), "utf8").trim(), 10)))
|
|
15314
15400
|
status = 1;
|
|
15315
15401
|
} catch {}
|
|
15316
15402
|
}
|
|
@@ -15326,7 +15412,7 @@ function buildMetricsText(repoDir, now = Date.now()) {
|
|
|
15326
15412
|
`loki_tasks_total{status="failed"} ${arr(tasks.failed).length}`
|
|
15327
15413
|
] : null);
|
|
15328
15414
|
let agents = null;
|
|
15329
|
-
const af = readJson(
|
|
15415
|
+
const af = readJson(join17(loki, "state", "agents.json"));
|
|
15330
15416
|
if (Array.isArray(af) && af.length)
|
|
15331
15417
|
agents = af;
|
|
15332
15418
|
else if (state && Array.isArray(state.agents) && state.agents.length)
|
|
@@ -15338,7 +15424,7 @@ function buildMetricsText(repoDir, now = Date.now()) {
|
|
|
15338
15424
|
gauge("loki_cost_usd", "Estimated total cost in USD", "gauge", cost.estimated_cost_usd !== null ? [`loki_cost_usd ${cost.estimated_cost_usd}`] : null);
|
|
15339
15425
|
let events2 = null;
|
|
15340
15426
|
try {
|
|
15341
|
-
events2 = readFileSync13(
|
|
15427
|
+
events2 = readFileSync13(join17(loki, "events.jsonl"), "utf8").split(`
|
|
15342
15428
|
`).filter((l) => l.trim()).length;
|
|
15343
15429
|
} catch {}
|
|
15344
15430
|
gauge("loki_events_total", "Total number of events recorded", "counter", events2 !== null ? [`loki_events_total ${events2}`] : null);
|
|
@@ -15350,19 +15436,19 @@ function buildMetricsText(repoDir, now = Date.now()) {
|
|
|
15350
15436
|
}
|
|
15351
15437
|
gauge("loki_uptime_seconds", "Seconds since session started", "gauge", uptime !== null ? [`loki_uptime_seconds ${uptime}`] : null);
|
|
15352
15438
|
let total = 0, ver = 0, gaps = 0, notver = 0, unk = 0, att = 0, sig = 0;
|
|
15353
|
-
const pdir =
|
|
15439
|
+
const pdir = join17(loki, "proofs");
|
|
15354
15440
|
let entries = [];
|
|
15355
15441
|
try {
|
|
15356
15442
|
entries = readdirSync7(pdir).sort();
|
|
15357
15443
|
} catch {}
|
|
15358
15444
|
for (const e of entries) {
|
|
15359
15445
|
try {
|
|
15360
|
-
if (!statSync8(
|
|
15446
|
+
if (!statSync8(join17(pdir, e)).isDirectory())
|
|
15361
15447
|
continue;
|
|
15362
15448
|
} catch {
|
|
15363
15449
|
continue;
|
|
15364
15450
|
}
|
|
15365
|
-
const d = readJson(
|
|
15451
|
+
const d = readJson(join17(pdir, e, "proof.json"));
|
|
15366
15452
|
if (!isObj7(d))
|
|
15367
15453
|
continue;
|
|
15368
15454
|
total++;
|
|
@@ -15407,7 +15493,7 @@ function mount11(ctx) {
|
|
|
15407
15493
|
|
|
15408
15494
|
// src/server/routes/session_control.ts
|
|
15409
15495
|
import { existsSync as existsSync9, mkdirSync as mkdirSync4, readdirSync as readdirSync8, readFileSync as readFileSync14, unlinkSync as unlinkSync2, writeFileSync as writeFileSync5 } from "fs";
|
|
15410
|
-
import { dirname as dirname3, join as
|
|
15496
|
+
import { dirname as dirname3, join as join18 } from "path";
|
|
15411
15497
|
var isObj8 = (v) => typeof v === "object" && v !== null && !Array.isArray(v);
|
|
15412
15498
|
var ACTIONS = ["pause", "resume", "stop", "council-review"];
|
|
15413
15499
|
var isAction = (v) => ACTIONS.includes(v);
|
|
@@ -15432,7 +15518,7 @@ var readJson2 = (p) => {
|
|
|
15432
15518
|
};
|
|
15433
15519
|
function runnerPid(loki) {
|
|
15434
15520
|
try {
|
|
15435
|
-
const pid = parseInt(readFileSync14(
|
|
15521
|
+
const pid = parseInt(readFileSync14(join18(loki, "loki.pid"), "utf8").trim(), 10);
|
|
15436
15522
|
return pidAlive(pid) ? pid : null;
|
|
15437
15523
|
} catch {
|
|
15438
15524
|
return null;
|
|
@@ -15442,18 +15528,18 @@ function applyControl(repoDir, action) {
|
|
|
15442
15528
|
const loki = lokiDirOf(repoDir);
|
|
15443
15529
|
const running = runnerPid(loki) !== null;
|
|
15444
15530
|
const put = (rel) => {
|
|
15445
|
-
const f =
|
|
15531
|
+
const f = join18(loki, rel);
|
|
15446
15532
|
mkdirSync4(dirname3(f), { recursive: true });
|
|
15447
15533
|
writeFileSync5(f, new Date().toISOString());
|
|
15448
15534
|
};
|
|
15449
15535
|
if (action === "council-review") {
|
|
15450
|
-
put(
|
|
15536
|
+
put(join18("signals", "COUNCIL_REVIEW_REQUESTED"));
|
|
15451
15537
|
return { status: 200, body: { success: true, message: "Council review requested" } };
|
|
15452
15538
|
}
|
|
15453
15539
|
if (action === "resume") {
|
|
15454
15540
|
for (const n2 of ["PAUSE", "STOP"]) {
|
|
15455
15541
|
try {
|
|
15456
|
-
unlinkSync2(
|
|
15542
|
+
unlinkSync2(join18(loki, n2));
|
|
15457
15543
|
} catch {}
|
|
15458
15544
|
}
|
|
15459
15545
|
return running ? { status: 200, body: { success: true, message: "Session resumed", process_verified: true } } : { status: 503, body: { success: false, message: "Session process is not running; the signal files were cleared" } };
|
|
@@ -15473,16 +15559,16 @@ function runControl(db, repoDir, action, c, via) {
|
|
|
15473
15559
|
return c.json(r.body, r.status);
|
|
15474
15560
|
}
|
|
15475
15561
|
function councilState(loki) {
|
|
15476
|
-
const f =
|
|
15562
|
+
const f = join18(loki, "council", "state.json");
|
|
15477
15563
|
if (!existsSync9(f))
|
|
15478
15564
|
return { enabled: false, total_votes: 0, verdicts: [] };
|
|
15479
15565
|
const d = readJson2(f);
|
|
15480
15566
|
return isObj8(d) ? d : { enabled: null, total_votes: null, verdicts: null, error: "unreadable_state" };
|
|
15481
15567
|
}
|
|
15482
15568
|
function councilVerdicts(loki, limit = 20) {
|
|
15483
|
-
const st = readJson2(
|
|
15569
|
+
const st = readJson2(join18(loki, "council", "state.json"));
|
|
15484
15570
|
const verdicts = isObj8(st) && Array.isArray(st.verdicts) ? st.verdicts : [];
|
|
15485
|
-
const votes =
|
|
15571
|
+
const votes = join18(loki, "council", "votes");
|
|
15486
15572
|
const details = [];
|
|
15487
15573
|
let names = [];
|
|
15488
15574
|
try {
|
|
@@ -15496,8 +15582,8 @@ function councilVerdicts(loki, limit = 20) {
|
|
|
15496
15582
|
}
|
|
15497
15583
|
};
|
|
15498
15584
|
for (const n2 of names.slice(0, limit)) {
|
|
15499
|
-
const dir =
|
|
15500
|
-
const ev = text2(
|
|
15585
|
+
const dir = join18(votes, n2), d = { iteration: n2 };
|
|
15586
|
+
const ev = text2(join18(dir, "evidence.md"));
|
|
15501
15587
|
if (ev !== null)
|
|
15502
15588
|
d.evidence_preview = ev.slice(0, 500);
|
|
15503
15589
|
let files = [];
|
|
@@ -15505,10 +15591,10 @@ function councilVerdicts(loki, limit = 20) {
|
|
|
15505
15591
|
files = readdirSync8(dir).filter((f) => /^member-.*\.txt$/.test(f)).sort();
|
|
15506
15592
|
} catch {}
|
|
15507
15593
|
d.members = files.flatMap((f) => {
|
|
15508
|
-
const t = text2(
|
|
15594
|
+
const t = text2(join18(dir, f));
|
|
15509
15595
|
return t === null ? [] : [{ member: f.replace(/\.txt$/, ""), content: t.trim() }];
|
|
15510
15596
|
});
|
|
15511
|
-
const con = text2(
|
|
15597
|
+
const con = text2(join18(dir, "contrarian.txt"));
|
|
15512
15598
|
if (con !== null)
|
|
15513
15599
|
d.contrarian = con.trim();
|
|
15514
15600
|
details.push(d);
|
|
@@ -15519,7 +15605,7 @@ function councilConvergence(loki) {
|
|
|
15519
15605
|
const dataPoints = [];
|
|
15520
15606
|
let raw2 = "";
|
|
15521
15607
|
try {
|
|
15522
|
-
raw2 = readFileSync14(
|
|
15608
|
+
raw2 = readFileSync14(join18(loki, "council", "convergence.log"), "utf8");
|
|
15523
15609
|
} catch {}
|
|
15524
15610
|
for (const line2 of raw2.trim().split(`
|
|
15525
15611
|
`)) {
|
|
@@ -15535,7 +15621,7 @@ function councilConvergence(loki) {
|
|
|
15535
15621
|
}
|
|
15536
15622
|
function councilReport(loki) {
|
|
15537
15623
|
try {
|
|
15538
|
-
return { report: readFileSync14(
|
|
15624
|
+
return { report: readFileSync14(join18(loki, "council", "report.md"), "utf8") };
|
|
15539
15625
|
} catch {
|
|
15540
15626
|
return { report: null };
|
|
15541
15627
|
}
|
|
@@ -15548,7 +15634,7 @@ function councilTranscripts(loki, q) {
|
|
|
15548
15634
|
if (Number.isNaN(since))
|
|
15549
15635
|
return { error: "Invalid 'since' timestamp format; expected ISO8601" };
|
|
15550
15636
|
}
|
|
15551
|
-
const dir =
|
|
15637
|
+
const dir = join18(loki, "council", "transcripts");
|
|
15552
15638
|
let names = [];
|
|
15553
15639
|
try {
|
|
15554
15640
|
names = readdirSync8(dir).filter((f) => /^iter-.*\.json$/.test(f)).sort().reverse();
|
|
@@ -15557,7 +15643,7 @@ function councilTranscripts(loki, q) {
|
|
|
15557
15643
|
}
|
|
15558
15644
|
const out2 = [];
|
|
15559
15645
|
for (const n2 of names) {
|
|
15560
|
-
const rec = readJson2(
|
|
15646
|
+
const rec = readJson2(join18(dir, n2));
|
|
15561
15647
|
if (!isObj8(rec) || typeof rec.iteration_id !== "string")
|
|
15562
15648
|
continue;
|
|
15563
15649
|
if (since !== null) {
|
|
@@ -15610,11 +15696,11 @@ function mount12(ctx) {
|
|
|
15610
15696
|
// src/server/routes/integrations.ts
|
|
15611
15697
|
import { spawn as spawn2 } from "child_process";
|
|
15612
15698
|
import { existsSync as existsSync10, readFileSync as readFileSync15, statSync as statSync9 } from "fs";
|
|
15613
|
-
import { delimiter, join as
|
|
15699
|
+
import { delimiter, join as join19 } from "path";
|
|
15614
15700
|
var GH_TIMEOUT_MS = 3000;
|
|
15615
15701
|
var isSet = (n2) => !!n2 && (process.env[n2] ?? "") !== "";
|
|
15616
15702
|
function ghAuthed(pathVar = process.env.PATH ?? "", timeoutMs = GH_TIMEOUT_MS) {
|
|
15617
|
-
const exe = pathVar.split(delimiter).filter(Boolean).map((d) =>
|
|
15703
|
+
const exe = pathVar.split(delimiter).filter(Boolean).map((d) => join19(d, "gh")).find((p) => existsSync10(p));
|
|
15618
15704
|
if (!exe)
|
|
15619
15705
|
return Promise.resolve(null);
|
|
15620
15706
|
return new Promise((resolve4) => {
|
|
@@ -15659,7 +15745,7 @@ function configuredName(repoDir, path) {
|
|
|
15659
15745
|
}
|
|
15660
15746
|
function mcpServers(repoDir) {
|
|
15661
15747
|
try {
|
|
15662
|
-
const p =
|
|
15748
|
+
const p = join19(repoDir, ".mcp.json");
|
|
15663
15749
|
if (!existsSync10(p) || statSync9(p).size > 1e6)
|
|
15664
15750
|
return null;
|
|
15665
15751
|
const j = JSON.parse(readFileSync15(p, "utf8"));
|
|
@@ -15740,7 +15826,7 @@ function mount13(ctx) {
|
|
|
15740
15826
|
|
|
15741
15827
|
// src/server/routes/merge_risk.ts
|
|
15742
15828
|
import { spawn as spawn3 } from "child_process";
|
|
15743
|
-
import { realpathSync as
|
|
15829
|
+
import { realpathSync as realpathSync9 } from "fs";
|
|
15744
15830
|
import { basename as basename5, resolve as resolve4 } from "path";
|
|
15745
15831
|
var PR = /^[0-9]{1,7}$/;
|
|
15746
15832
|
var REF = /^[A-Za-z0-9._/][A-Za-z0-9._/-]{0,99}$/;
|
|
@@ -15839,7 +15925,7 @@ function mount14(ctx) {
|
|
|
15839
15925
|
const active = { run: 0, queue: 0, risk: 0 };
|
|
15840
15926
|
const real2 = (p) => {
|
|
15841
15927
|
try {
|
|
15842
|
-
return
|
|
15928
|
+
return realpathSync9(p);
|
|
15843
15929
|
} catch {
|
|
15844
15930
|
return null;
|
|
15845
15931
|
}
|
|
@@ -16051,7 +16137,7 @@ function mount15(ctx) {
|
|
|
16051
16137
|
// src/server/routes/providers.ts
|
|
16052
16138
|
import { spawn as spawn4 } from "child_process";
|
|
16053
16139
|
import { existsSync as existsSync11, readFileSync as readFileSync16 } from "fs";
|
|
16054
|
-
import { delimiter as delimiter2, join as
|
|
16140
|
+
import { delimiter as delimiter2, join as join20 } from "path";
|
|
16055
16141
|
var ORDER = ["claude", "codex", "cline", "aider", "opencode"];
|
|
16056
16142
|
var DEPRECATED = ["gemini"];
|
|
16057
16143
|
var AUTH_ENV = {
|
|
@@ -16067,7 +16153,7 @@ function findOnPath(bin, pathVar) {
|
|
|
16067
16153
|
for (const d of pathVar.split(delimiter2)) {
|
|
16068
16154
|
if (!d)
|
|
16069
16155
|
continue;
|
|
16070
|
-
const p =
|
|
16156
|
+
const p = join20(d, bin);
|
|
16071
16157
|
if (existsSync11(p))
|
|
16072
16158
|
return p;
|
|
16073
16159
|
}
|
|
@@ -16112,14 +16198,14 @@ function probeCli(bin, timeoutMs, pathVar = process.env.PATH ?? "") {
|
|
|
16112
16198
|
});
|
|
16113
16199
|
}
|
|
16114
16200
|
function providersDir() {
|
|
16115
|
-
const cands = [process.env.LOKI_PROVIDERS_DIR,
|
|
16116
|
-
return cands.find((d) => !!d && existsSync11(
|
|
16201
|
+
const cands = [process.env.LOKI_PROVIDERS_DIR, join20(import.meta.dir, "../../../../../providers"), join20(import.meta.dir, "../../../providers")];
|
|
16202
|
+
return cands.find((d) => !!d && existsSync11(join20(d, "model_catalog.json"))) ?? null;
|
|
16117
16203
|
}
|
|
16118
16204
|
function loadCatalog(dir) {
|
|
16119
16205
|
if (!dir)
|
|
16120
16206
|
return { providers: {}, updated: null };
|
|
16121
16207
|
try {
|
|
16122
|
-
const j = JSON.parse(readFileSync16(
|
|
16208
|
+
const j = JSON.parse(readFileSync16(join20(dir, "model_catalog.json"), "utf8"));
|
|
16123
16209
|
return { providers: j.providers ?? {}, updated: typeof j.updated === "string" ? j.updated : null };
|
|
16124
16210
|
} catch {
|
|
16125
16211
|
return { providers: {}, updated: null };
|
|
@@ -16165,7 +16251,7 @@ function mount16(ctx) {
|
|
|
16165
16251
|
function computeStats(db, since) {
|
|
16166
16252
|
const all = db.select().from(runs).all().filter((r) => !since || r.startedAt !== null && r.startedAt >= since);
|
|
16167
16253
|
const by = {};
|
|
16168
|
-
let finished = 0, running = 0, blocked = 0,
|
|
16254
|
+
let finished = 0, running = 0, blocked = 0, verified = 0, unchecked = 0, failed = 0, satisfied = 0, other = 0;
|
|
16169
16255
|
let mUsd = 0, mRuns = 0, pUsd = 0, pRuns = 0;
|
|
16170
16256
|
for (const r of all) {
|
|
16171
16257
|
if (r.endedAt)
|
|
@@ -16176,11 +16262,16 @@ function computeStats(db, since) {
|
|
|
16176
16262
|
if (verdict) {
|
|
16177
16263
|
by[verdict] = (by[verdict] ?? 0) + 1;
|
|
16178
16264
|
if (r.endedAt) {
|
|
16179
|
-
withVerdict++;
|
|
16180
16265
|
if (verdict === "VERIFIED")
|
|
16181
16266
|
verified++;
|
|
16182
16267
|
else if (verdict === "VERIFIED (signature not checked)")
|
|
16183
16268
|
unchecked++;
|
|
16269
|
+
else if (verdict === "ALREADY_SATISFIED" || verdict === "ALREADY_SATISFIED (signature not checked)")
|
|
16270
|
+
satisfied++;
|
|
16271
|
+
else if (verdict === "FAILED" || verdict === "PARTIAL" || verdict === "TAMPERED")
|
|
16272
|
+
failed++;
|
|
16273
|
+
else
|
|
16274
|
+
other++;
|
|
16184
16275
|
}
|
|
16185
16276
|
}
|
|
16186
16277
|
if (r.verdict === "SPEC_CONFLICT")
|
|
@@ -16210,9 +16301,11 @@ function computeStats(db, since) {
|
|
|
16210
16301
|
runs_running: running,
|
|
16211
16302
|
by_verdict: by,
|
|
16212
16303
|
blocked_waiting: blocked,
|
|
16213
|
-
verified_rate:
|
|
16304
|
+
verified_rate: verified + unchecked + failed ? verified / (verified + unchecked + failed) : null,
|
|
16305
|
+
breakdown: { verified, failed, already_satisfied: satisfied, other },
|
|
16214
16306
|
verified_unchecked: unchecked,
|
|
16215
16307
|
keys_configured: pubkeysFromEnv().configured === true,
|
|
16308
|
+
...pubkeysFromEnv().reason ? { keys_reason: pubkeysFromEnv().reason } : {},
|
|
16216
16309
|
cost: {
|
|
16217
16310
|
measured_usd: mRuns ? r62(mUsd) : null,
|
|
16218
16311
|
measured_runs: mRuns,
|
|
@@ -16355,13 +16448,13 @@ data: ${JSON.stringify({ source: k.slice(0, i), run: k.slice(i + 1) })}
|
|
|
16355
16448
|
}
|
|
16356
16449
|
|
|
16357
16450
|
// src/server/routes/verify.ts
|
|
16358
|
-
import { readFileSync as
|
|
16451
|
+
import { readFileSync as readFileSync26, realpathSync as realpathSync13 } from "fs";
|
|
16359
16452
|
import { createPublicKey as createPublicKey5 } from "crypto";
|
|
16360
|
-
import { join as
|
|
16453
|
+
import { join as join31, sep as sep11 } from "path";
|
|
16361
16454
|
|
|
16362
16455
|
// ../../loki-ts/src/engine10/verify_cmd.ts
|
|
16363
|
-
import { existsSync as
|
|
16364
|
-
import { dirname as dirname12, join as
|
|
16456
|
+
import { existsSync as existsSync19, readdirSync as readdirSync12, readFileSync as readFileSync25, statSync as statSync13 } from "fs";
|
|
16457
|
+
import { dirname as dirname12, join as join30 } from "path";
|
|
16365
16458
|
import { createHash as createHash10, createPublicKey as createPublicKey4, verify as verify3 } from "crypto";
|
|
16366
16459
|
|
|
16367
16460
|
// ../../loki-ts/src/util/paths.ts
|
|
@@ -16386,15 +16479,15 @@ var REPO_ROOT = findRepoRoot();
|
|
|
16386
16479
|
|
|
16387
16480
|
// ../../loki-ts/src/engine10/stages/seal.ts
|
|
16388
16481
|
import { createHash as createHash9, randomBytes as randomBytes3, createPrivateKey, createPublicKey as createPublicKey2, generateKeyPairSync, sign } from "crypto";
|
|
16389
|
-
import { chmodSync as chmodSync4, existsSync as
|
|
16390
|
-
import { homedir as
|
|
16391
|
-
import { basename as basename9, dirname as dirname11, join as
|
|
16482
|
+
import { chmodSync as chmodSync4, existsSync as existsSync18, readdirSync as readdirSync11, linkSync, mkdirSync as mkdirSync9, readFileSync as readFileSync23, statSync as statSync12, unlinkSync as unlinkSync4, writeFileSync as writeFileSync10 } from "fs";
|
|
16483
|
+
import { homedir as homedir6 } from "os";
|
|
16484
|
+
import { basename as basename9, dirname as dirname11, join as join29 } from "path";
|
|
16392
16485
|
|
|
16393
16486
|
// ../../loki-ts/src/e10ext/assert_delta.ts
|
|
16394
16487
|
import { execFileSync as execFileSync3 } from "child_process";
|
|
16395
16488
|
import { mkdtempSync, readFileSync as readFileSync17, rmSync as rmSync3 } from "fs";
|
|
16396
|
-
import { tmpdir } from "os";
|
|
16397
|
-
import { join as
|
|
16489
|
+
import { tmpdir as tmpdir2 } from "os";
|
|
16490
|
+
import { join as join21 } from "path";
|
|
16398
16491
|
import { basename as basename6 } from "path";
|
|
16399
16492
|
var WEAKENED = { verdict: "weakened", changes: [] };
|
|
16400
16493
|
var PY = String.raw`
|
|
@@ -16553,7 +16646,7 @@ function classifyAssertDelta(inp) {
|
|
|
16553
16646
|
return WEAKENED;
|
|
16554
16647
|
let cwd = "";
|
|
16555
16648
|
try {
|
|
16556
|
-
cwd = mkdtempSync(
|
|
16649
|
+
cwd = mkdtempSync(join21(tmpdir2(), "loki-ad-"));
|
|
16557
16650
|
const raw2 = execFileSync3("python3", ["-I", "-c", PY], {
|
|
16558
16651
|
input: JSON.stringify({ path: inp.path, base: inp.base, head: inp.head, task: inp.task }),
|
|
16559
16652
|
cwd,
|
|
@@ -16584,7 +16677,7 @@ function assertDeltaNotes(repoDir, baseSha, headRef, path, task, baseCounts, hea
|
|
|
16584
16677
|
return null;
|
|
16585
16678
|
try {
|
|
16586
16679
|
const show = (ref) => execFileSync3("git", ["show", `${ref}:${path}`], { cwd: repoDir, encoding: "utf8", env: process.env, stdio: ["ignore", "pipe", "ignore"] });
|
|
16587
|
-
const head = headRef ? show(headRef) : readFileSync17(
|
|
16680
|
+
const head = headRef ? show(headRef) : readFileSync17(join21(repoDir, path), "utf8");
|
|
16588
16681
|
const r = classifyTestEdit(path, show(baseSha), head, task, baseCounts, headCounts);
|
|
16589
16682
|
return r[0]?.kind === "literal-only" ? r.map((e) => `assertion value changed (not shown to be required by the task): ${e.file}:${e.line} ${e.old} -> ${e.new}`) : null;
|
|
16590
16683
|
} catch {
|
|
@@ -16601,15 +16694,15 @@ function classifyTestEdit(file, oldText, newText, taskText, baseCounts, headCoun
|
|
|
16601
16694
|
|
|
16602
16695
|
// ../../loki-ts/src/e10ext/discard.ts
|
|
16603
16696
|
import { execFileSync as execFileSync4 } from "child_process";
|
|
16604
|
-
import { chmodSync as chmodSync3, closeSync as closeSync4, lstatSync as lstatSync3, mkdirSync as mkdirSync5, openSync as openSync4, readFileSync as readFileSync18, realpathSync as
|
|
16605
|
-
import { dirname as dirname5, join as
|
|
16697
|
+
import { chmodSync as chmodSync3, closeSync as closeSync4, lstatSync as lstatSync3, mkdirSync as mkdirSync5, openSync as openSync4, readFileSync as readFileSync18, realpathSync as realpathSync10, rmdirSync, unlinkSync as unlinkSync3, writeFileSync as writeFileSync6 } from "fs";
|
|
16698
|
+
import { dirname as dirname5, join as join22, relative, sep as sep7 } from "path";
|
|
16606
16699
|
function safeRestore(repoDir, f, want, mode) {
|
|
16607
|
-
const root =
|
|
16700
|
+
const root = realpathSync10(repoDir), fp = join22(root, f), rel = relative(root, dirname5(fp));
|
|
16608
16701
|
if (rel.startsWith(".."))
|
|
16609
16702
|
throw new Error("outside repo");
|
|
16610
16703
|
let cur = root;
|
|
16611
|
-
for (const part of rel === "" ? [] : rel.split(
|
|
16612
|
-
cur =
|
|
16704
|
+
for (const part of rel === "" ? [] : rel.split(sep7)) {
|
|
16705
|
+
cur = join22(cur, part);
|
|
16613
16706
|
let st2;
|
|
16614
16707
|
try {
|
|
16615
16708
|
st2 = lstatSync3(cur);
|
|
@@ -16658,7 +16751,7 @@ async function discardIfSatisfied(git, base, o, staged, keep, repoDir) {
|
|
|
16658
16751
|
return { status: "failed", data: {}, reason: "already-satisfied discard failed" };
|
|
16659
16752
|
const notProven = [];
|
|
16660
16753
|
for (const [f, v] of Object.entries(o.intake?.preexisting_untracked_blobs ?? {})) {
|
|
16661
|
-
const fp =
|
|
16754
|
+
const fp = join22(repoDir, f);
|
|
16662
16755
|
try {
|
|
16663
16756
|
if (v.startsWith("!")) {
|
|
16664
16757
|
const st = lstatSync3(fp);
|
|
@@ -16677,7 +16770,7 @@ async function discardIfSatisfied(git, base, o, staged, keep, repoDir) {
|
|
|
16677
16770
|
try {
|
|
16678
16771
|
let m = 420;
|
|
16679
16772
|
try {
|
|
16680
|
-
const l = lstatSync3(
|
|
16773
|
+
const l = lstatSync3(join22(repoDir, f));
|
|
16681
16774
|
if (l.isFile())
|
|
16682
16775
|
m = l.mode & 511;
|
|
16683
16776
|
} catch {}
|
|
@@ -16717,7 +16810,7 @@ function dropSet(repoDir, staged, preexistingDirty, env = process.env) {
|
|
|
16717
16810
|
import { basename as basename8, dirname as dirname7 } from "path";
|
|
16718
16811
|
|
|
16719
16812
|
// ../../loki-ts/src/engine10/testmap.ts
|
|
16720
|
-
import { basename as basename7, extname, join as
|
|
16813
|
+
import { basename as basename7, extname, join as join23, relative as relative2, sep as sep8 } from "path";
|
|
16721
16814
|
var SKIP_DIRS = new Set([
|
|
16722
16815
|
".git",
|
|
16723
16816
|
"node_modules",
|
|
@@ -16742,7 +16835,7 @@ function isTestFile(rel) {
|
|
|
16742
16835
|
}
|
|
16743
16836
|
|
|
16744
16837
|
// ../../loki-ts/src/features/speed/unit_mode.ts
|
|
16745
|
-
import { closeSync as closeSync5, constants, fstatSync as fstatSync3, lstatSync as lstatSync4, openSync as openSync5, readSync as readSync3, realpathSync as
|
|
16838
|
+
import { closeSync as closeSync5, constants, fstatSync as fstatSync3, lstatSync as lstatSync4, openSync as openSync5, readSync as readSync3, realpathSync as realpathSync11 } from "fs";
|
|
16746
16839
|
var MAX_SPEC_BYTES = 1024 * 1024;
|
|
16747
16840
|
var MAX_TOKEN_BUDGET = 1e9;
|
|
16748
16841
|
var norm2 = (p) => p.replace(/^\.\//, "");
|
|
@@ -16911,10 +17004,257 @@ async function run(argv, opts = {}) {
|
|
|
16911
17004
|
|
|
16912
17005
|
// ../../loki-ts/src/features/visual_evidence.ts
|
|
16913
17006
|
import { spawn as spawn5 } from "child_process";
|
|
17007
|
+
|
|
17008
|
+
// ../../loki-ts/src/project_model/discover.ts
|
|
17009
|
+
import { existsSync as existsSync14, mkdirSync as mkdirSync6, readFileSync as readFileSync19, rmSync as rmSync4, writeFileSync as writeFileSync7 } from "fs";
|
|
17010
|
+
import { join as join25 } from "path";
|
|
17011
|
+
|
|
17012
|
+
// ../../loki-ts/src/engine10/types.ts
|
|
17013
|
+
var STAGE_BUDGETS = {
|
|
17014
|
+
intake: { targetS: 15, limitS: 60 },
|
|
17015
|
+
plan: { targetS: 45, limitS: 90 },
|
|
17016
|
+
wall: { targetS: 45, limitS: 90 },
|
|
17017
|
+
implement: { targetS: 180, limitS: 480 },
|
|
17018
|
+
verify: { targetS: 60, limitS: 120 },
|
|
17019
|
+
fix: { targetS: 90, limitS: 180 },
|
|
17020
|
+
commit: { targetS: 5, limitS: 30 },
|
|
17021
|
+
seal: { targetS: 15, limitS: 60 },
|
|
17022
|
+
pr: { targetS: 15, limitS: 60 },
|
|
17023
|
+
deep: { targetS: null, limitS: 2700 }
|
|
17024
|
+
};
|
|
17025
|
+
|
|
17026
|
+
// ../../loki-ts/src/project_model/schema.ts
|
|
17027
|
+
import { existsSync as existsSync13, lstatSync as lstatSync5, realpathSync as realpathSync12, statSync as statSync10 } from "fs";
|
|
17028
|
+
import { isAbsolute, join as join24, normalize, sep as sep9 } from "path";
|
|
17029
|
+
var PROJECT_MODEL_SCHEMA = "loki.v10.project/1";
|
|
17030
|
+
var COMMAND_KINDS = ["test", "lint", "build", "start"];
|
|
17031
|
+
var isRec = (v) => typeof v === "object" && v !== null && !Array.isArray(v);
|
|
17032
|
+
var isStr = (v) => typeof v === "string" && v.trim() !== "";
|
|
17033
|
+
function safeRel(p) {
|
|
17034
|
+
const n2 = normalize(p.trim()).split(sep9).join("/").replace(/\/$/, "");
|
|
17035
|
+
if (n2 === "" || isAbsolute(n2) || n2 === ".." || n2.startsWith("../"))
|
|
17036
|
+
return null;
|
|
17037
|
+
return n2;
|
|
17038
|
+
}
|
|
17039
|
+
function insideRepo(repoDir, rel) {
|
|
17040
|
+
try {
|
|
17041
|
+
const real2 = realpathSync12(join24(repoDir, rel));
|
|
17042
|
+
const base = realpathSync12(repoDir);
|
|
17043
|
+
return real2 === base || real2.startsWith(base + sep9);
|
|
17044
|
+
} catch {
|
|
17045
|
+
return false;
|
|
17046
|
+
}
|
|
17047
|
+
}
|
|
17048
|
+
function checkCitation(repoDir, raw2) {
|
|
17049
|
+
if (!isStr(raw2))
|
|
17050
|
+
return "citation is not a non-empty string";
|
|
17051
|
+
const rel = safeRel(raw2.replace(/:\d+(?:-\d+)?$/, ""));
|
|
17052
|
+
if (rel === null)
|
|
17053
|
+
return `citation "${raw2}" is not a repo-relative path`;
|
|
17054
|
+
const abs = join24(repoDir, rel);
|
|
17055
|
+
if (!existsSync13(abs) || !statSync10(abs).isFile() || !insideRepo(repoDir, rel))
|
|
17056
|
+
return `citation "${raw2}" names a file that does not exist`;
|
|
17057
|
+
return null;
|
|
17058
|
+
}
|
|
17059
|
+
function checkFingerprint(repoDir, raw2) {
|
|
17060
|
+
if (!isStr(raw2))
|
|
17061
|
+
return "fingerprint entry is not a non-empty string";
|
|
17062
|
+
const rel = safeRel(raw2.replace(/:\d+(?:-\d+)?$/, ""));
|
|
17063
|
+
if (rel === null)
|
|
17064
|
+
return `fingerprint "${raw2}" is not a repo-relative path`;
|
|
17065
|
+
try {
|
|
17066
|
+
if (!lstatSync5(join24(repoDir, rel)).isFile() || !insideRepo(repoDir, rel))
|
|
17067
|
+
return `fingerprint "${raw2}" is not a regular file inside the repo`;
|
|
17068
|
+
} catch {
|
|
17069
|
+
return `fingerprint "${raw2}" names a file that does not exist`;
|
|
17070
|
+
}
|
|
17071
|
+
return null;
|
|
17072
|
+
}
|
|
17073
|
+
function checkCites(repoDir, v, where, errs) {
|
|
17074
|
+
if (!Array.isArray(v) || v.length === 0) {
|
|
17075
|
+
errs.push(`${where}: needs at least one citation (a repo-relative file path)`);
|
|
17076
|
+
return [];
|
|
17077
|
+
}
|
|
17078
|
+
const out2 = [];
|
|
17079
|
+
for (const c of v) {
|
|
17080
|
+
const e = checkCitation(repoDir, c);
|
|
17081
|
+
if (e)
|
|
17082
|
+
errs.push(`${where}: ${e}`);
|
|
17083
|
+
else
|
|
17084
|
+
out2.push(String(c));
|
|
17085
|
+
}
|
|
17086
|
+
return out2;
|
|
17087
|
+
}
|
|
17088
|
+
function checkDir(repoDir, v, where, errs) {
|
|
17089
|
+
const rel = isStr(v) ? safeRel(v) : null;
|
|
17090
|
+
if (rel === null) {
|
|
17091
|
+
errs.push(`${where}: must be a repo-relative directory ("." for the repo root)`);
|
|
17092
|
+
return ".";
|
|
17093
|
+
}
|
|
17094
|
+
const abs = join24(repoDir, rel);
|
|
17095
|
+
if (!existsSync13(abs) || !statSync10(abs).isDirectory() || !insideRepo(repoDir, rel))
|
|
17096
|
+
errs.push(`${where}: directory "${rel}" does not exist`);
|
|
17097
|
+
return rel;
|
|
17098
|
+
}
|
|
17099
|
+
function checkCommand(repoDir, v, where, errs) {
|
|
17100
|
+
if (v === null || v === undefined)
|
|
17101
|
+
return null;
|
|
17102
|
+
if (!isRec(v) || !isStr(v.cmd)) {
|
|
17103
|
+
errs.push(`${where}: must be null or {cmd, cwd, cite}`);
|
|
17104
|
+
return null;
|
|
17105
|
+
}
|
|
17106
|
+
const cwd = checkDir(repoDir, v.cwd, `${where}.cwd`, errs);
|
|
17107
|
+
return { cmd: v.cmd.trim(), cwd, cite: checkCites(repoDir, v.cite, `${where}.cite`, errs) };
|
|
17108
|
+
}
|
|
17109
|
+
function validateAnswer(repoDir, raw2) {
|
|
17110
|
+
const errs = [];
|
|
17111
|
+
if (!isRec(raw2))
|
|
17112
|
+
return { ok: false, errors: ["answer must be a JSON object"] };
|
|
17113
|
+
if (!isStr(raw2.workspaceKind))
|
|
17114
|
+
errs.push("workspaceKind: required non-empty string");
|
|
17115
|
+
const workspaceCite = checkCites(repoDir, raw2.workspaceCite, "workspaceCite", errs);
|
|
17116
|
+
if (!Array.isArray(raw2.packages))
|
|
17117
|
+
errs.push("packages: required array");
|
|
17118
|
+
const packages = [];
|
|
17119
|
+
for (const [i, p] of (Array.isArray(raw2.packages) ? raw2.packages : []).entries()) {
|
|
17120
|
+
const w = `packages[${i}]`;
|
|
17121
|
+
if (!isRec(p) || !isStr(p.name)) {
|
|
17122
|
+
errs.push(`${w}: must be an object with a name`);
|
|
17123
|
+
continue;
|
|
17124
|
+
}
|
|
17125
|
+
const root = checkDir(repoDir, p.root, `${w}.root`, errs);
|
|
17126
|
+
const cmds = isRec(p.commands) ? p.commands : {};
|
|
17127
|
+
if (!isRec(p.commands))
|
|
17128
|
+
errs.push(`${w}.commands: required object with keys ${COMMAND_KINDS.join(", ")} (each null or a command)`);
|
|
17129
|
+
const commands = {};
|
|
17130
|
+
for (const k of COMMAND_KINDS)
|
|
17131
|
+
commands[k] = checkCommand(repoDir, cmds[k], `${w}.commands.${k}`, errs);
|
|
17132
|
+
const ui = isRec(p.ui) ? p.ui : {};
|
|
17133
|
+
if (!isRec(p.ui) || typeof ui.present !== "boolean")
|
|
17134
|
+
errs.push(`${w}.ui: required {present: boolean, boot: command|null, cite}`);
|
|
17135
|
+
packages.push({
|
|
17136
|
+
name: p.name.trim(),
|
|
17137
|
+
root,
|
|
17138
|
+
runner: isStr(p.runner) ? p.runner.trim() : null,
|
|
17139
|
+
commands,
|
|
17140
|
+
ui: { present: ui.present === true, boot: checkCommand(repoDir, ui.boot, `${w}.ui.boot`, errs), cite: checkCites(repoDir, ui.cite, `${w}.ui.cite`, errs) },
|
|
17141
|
+
cite: checkCites(repoDir, p.cite, `${w}.cite`, errs)
|
|
17142
|
+
});
|
|
17143
|
+
}
|
|
17144
|
+
if (!Array.isArray(raw2.fingerprintFiles))
|
|
17145
|
+
errs.push("fingerprintFiles: required array of the manifest and lockfile paths");
|
|
17146
|
+
const fingerprintFiles = (Array.isArray(raw2.fingerprintFiles) ? raw2.fingerprintFiles : []).filter((f) => {
|
|
17147
|
+
const e = checkFingerprint(repoDir, f);
|
|
17148
|
+
if (e)
|
|
17149
|
+
errs.push(`fingerprintFiles: ${e}`);
|
|
17150
|
+
return e === null;
|
|
17151
|
+
}).map((f) => String(f).replace(/:\d+(?:-\d+)?$/, ""));
|
|
17152
|
+
if (errs.length > 0)
|
|
17153
|
+
return { ok: false, errors: errs };
|
|
17154
|
+
return { ok: true, model: { schema: PROJECT_MODEL_SCHEMA, status: "ok", workspaceKind: String(raw2.workspaceKind).trim(), workspaceCite, packages, fingerprintFiles } };
|
|
17155
|
+
}
|
|
17156
|
+
function unknownModel(key, reason, expiresAt) {
|
|
17157
|
+
return { schema: PROJECT_MODEL_SCHEMA, status: "unknown", key, workspaceKind: "unknown", workspaceCite: [], packages: [], fingerprintFiles: [], reason, ...expiresAt === undefined ? {} : { expiresAt } };
|
|
17158
|
+
}
|
|
17159
|
+
function parseCached(repoDir, raw2, now) {
|
|
17160
|
+
if (!isRec(raw2) || raw2.schema !== PROJECT_MODEL_SCHEMA || typeof raw2.key !== "string")
|
|
17161
|
+
return null;
|
|
17162
|
+
if (raw2.status === "unknown")
|
|
17163
|
+
return typeof raw2.expiresAt === "number" && raw2.expiresAt > now ? unknownModel(raw2.key, typeof raw2.reason === "string" ? raw2.reason : "cached unknown", raw2.expiresAt) : null;
|
|
17164
|
+
if (raw2.status !== "ok")
|
|
17165
|
+
return null;
|
|
17166
|
+
const v = validateAnswer(repoDir, raw2);
|
|
17167
|
+
return v.ok ? { ...v.model, key: raw2.key } : null;
|
|
17168
|
+
}
|
|
17169
|
+
|
|
17170
|
+
// ../../loki-ts/src/project_model/gather.ts
|
|
17171
|
+
var GATHER_CAPS = {
|
|
17172
|
+
maxListFiles: 20000,
|
|
17173
|
+
maxDepth: 3,
|
|
17174
|
+
maxFileBytes: 16 * 1024,
|
|
17175
|
+
maxFiles: 60,
|
|
17176
|
+
maxTotalBytes: 96 * 1024,
|
|
17177
|
+
maxTreeLines: 300,
|
|
17178
|
+
maxHashBytes: 16 * 1024 * 1024
|
|
17179
|
+
};
|
|
17180
|
+
|
|
17181
|
+
// ../../loki-ts/src/project_model/discover.ts
|
|
17182
|
+
function projectModelEnabled(env = process.env) {
|
|
17183
|
+
return env["LOKI_E10_PROJECT_MODEL"] !== "0";
|
|
17184
|
+
}
|
|
17185
|
+
var PROJECT_FILE = ".loki/project.json";
|
|
17186
|
+
var UNKNOWN_TTL_MS = 60 * 60 * 1000;
|
|
17187
|
+
function loadCached(repoDir, now = Date.now()) {
|
|
17188
|
+
try {
|
|
17189
|
+
return parseCached(repoDir, JSON.parse(readFileSync19(join25(repoDir, PROJECT_FILE), "utf8")), now);
|
|
17190
|
+
} catch {
|
|
17191
|
+
return null;
|
|
17192
|
+
}
|
|
17193
|
+
}
|
|
17194
|
+
|
|
17195
|
+
// ../../loki-ts/src/project_model/api.ts
|
|
17196
|
+
import { posix } from "path";
|
|
17197
|
+
var clean = (p) => typeof p === "string" ? posix.normalize(p.replace(/\\/g, "/")).replace(/^\.\//, "") : "";
|
|
17198
|
+
var isRec2 = (v) => typeof v === "object" && v !== null;
|
|
17199
|
+
var wellFormed = (p) => isRec2(p) && typeof p.root === "string" && typeof p.name === "string";
|
|
17200
|
+
function projectApi(model) {
|
|
17201
|
+
const known = isRec2(model) && model.status === "ok";
|
|
17202
|
+
const packages = known && Array.isArray(model.packages) ? model.packages.filter(wellFormed) : [];
|
|
17203
|
+
const packageOf = (file) => {
|
|
17204
|
+
const f = clean(file);
|
|
17205
|
+
if (f === "")
|
|
17206
|
+
return null;
|
|
17207
|
+
let best = null;
|
|
17208
|
+
for (const p of packages) {
|
|
17209
|
+
const hit = p.root === "." || f === p.root || f.startsWith(`${p.root}/`);
|
|
17210
|
+
if (hit && (best === null || p.root !== "." && (best.root === "." || p.root.length > best.root.length)))
|
|
17211
|
+
best = p;
|
|
17212
|
+
}
|
|
17213
|
+
return best;
|
|
17214
|
+
};
|
|
17215
|
+
return {
|
|
17216
|
+
model,
|
|
17217
|
+
known: () => known,
|
|
17218
|
+
packages: () => packages,
|
|
17219
|
+
workspaceKind: () => isRec2(model) && typeof model.workspaceKind === "string" ? model.workspaceKind : "unknown",
|
|
17220
|
+
packageRootOf: (file) => packageOf(file)?.root ?? null,
|
|
17221
|
+
packageOf,
|
|
17222
|
+
relativeToRoot: (file) => {
|
|
17223
|
+
const p = packageOf(file);
|
|
17224
|
+
return p ? p.root === "." ? clean(file) : clean(file).slice(p.root.length + 1) : null;
|
|
17225
|
+
},
|
|
17226
|
+
commandFor: (pkg, kind) => (packages.find((p) => p.root === clean(pkg) || p.name === pkg) ?? null)?.commands?.[kind] ?? null,
|
|
17227
|
+
hasUI: () => packages.some((p) => isRec2(p.ui) && p.ui.present === true),
|
|
17228
|
+
uiBoot: () => {
|
|
17229
|
+
for (const p of packages)
|
|
17230
|
+
if (isRec2(p.ui) && p.ui.present === true && isRec2(p.ui.boot))
|
|
17231
|
+
return { pkg: p, boot: p.ui.boot };
|
|
17232
|
+
return null;
|
|
17233
|
+
}
|
|
17234
|
+
};
|
|
17235
|
+
}
|
|
17236
|
+
|
|
17237
|
+
// ../../loki-ts/src/project_model/resolve.ts
|
|
17238
|
+
function loadProjectApi(repoDir, env = process.env) {
|
|
17239
|
+
if (!projectModelEnabled(env))
|
|
17240
|
+
return null;
|
|
17241
|
+
try {
|
|
17242
|
+
const model = loadCached(repoDir);
|
|
17243
|
+
const api = model ? projectApi(model) : null;
|
|
17244
|
+
return api && api.known() ? api : null;
|
|
17245
|
+
} catch {
|
|
17246
|
+
return null;
|
|
17247
|
+
}
|
|
17248
|
+
}
|
|
17249
|
+
function isMultiRoot(api) {
|
|
17250
|
+
return !!api && api.known() && api.packages().some((p) => p.root !== ".");
|
|
17251
|
+
}
|
|
17252
|
+
|
|
17253
|
+
// ../../loki-ts/src/features/visual_evidence.ts
|
|
16914
17254
|
import { createHash as createHash6, randomBytes as randomBytes2 } from "crypto";
|
|
16915
|
-
import { existsSync as
|
|
17255
|
+
import { existsSync as existsSync15, lstatSync as lstatSync6, mkdirSync as mkdirSync7, readdirSync as readdirSync9, readFileSync as readFileSync20, writeFileSync as writeFileSync8 } from "fs";
|
|
16916
17256
|
import { createServer } from "net";
|
|
16917
|
-
import { dirname as dirname8, isAbsolute, join as
|
|
17257
|
+
import { dirname as dirname8, isAbsolute as isAbsolute2, join as join26, normalize as normalize2, relative as relative3, sep as sep10 } from "path";
|
|
16918
17258
|
var visualEvidenceEnabled = (env = process.env) => env["LOKI_VISUAL_EVIDENCE"] !== "0";
|
|
16919
17259
|
var PAGE_RE = /^(?:.*\/)?(?:app|pages|src|public)\/.*\.(?:html|jsx|tsx|vue|svelte)$/;
|
|
16920
17260
|
var isPageFile = (p) => PAGE_RE.test(p);
|
|
@@ -16924,12 +17264,12 @@ function routeFor(file) {
|
|
|
16924
17264
|
return `/${r}`.replace(/\/+/g, "/");
|
|
16925
17265
|
}
|
|
16926
17266
|
var screenName = (route) => (route === "/" ? "index" : route.slice(1).replace(/[^A-Za-z0-9._-]+/g, "_")) || "index";
|
|
16927
|
-
var sha256File = (p) => createHash6("sha256").update(
|
|
17267
|
+
var sha256File = (p) => createHash6("sha256").update(readFileSync20(p)).digest("hex");
|
|
16928
17268
|
function hashScreens(runDir, rels) {
|
|
16929
17269
|
const out2 = [];
|
|
16930
17270
|
for (const rel of rels) {
|
|
16931
17271
|
try {
|
|
16932
|
-
out2.push({ path: rel, sha256: sha256File(
|
|
17272
|
+
out2.push({ path: rel, sha256: sha256File(join26(runDir, rel)) });
|
|
16933
17273
|
} catch {}
|
|
16934
17274
|
}
|
|
16935
17275
|
return out2;
|
|
@@ -16940,16 +17280,16 @@ function checkScreens(runDir, screens) {
|
|
|
16940
17280
|
for (const s of screens) {
|
|
16941
17281
|
if (typeof s?.path !== "string" || typeof s.sha256 !== "string")
|
|
16942
17282
|
return "evidence_screens entry is malformed";
|
|
16943
|
-
if (
|
|
17283
|
+
if (isAbsolute2(s.path) || normalize2(s.path).startsWith(".."))
|
|
16944
17284
|
return `evidence screenshot path escapes the evidence dir: ${s.path}`;
|
|
16945
|
-
const file =
|
|
17285
|
+
const file = join26(runDir, s.path);
|
|
16946
17286
|
let cur = runDir;
|
|
16947
|
-
for (const seg of
|
|
16948
|
-
cur =
|
|
17287
|
+
for (const seg of normalize2(s.path).split(sep10)) {
|
|
17288
|
+
cur = join26(cur, seg);
|
|
16949
17289
|
if (isSymlink(cur))
|
|
16950
17290
|
return `evidence screenshot path is a symlink: ${s.path}`;
|
|
16951
17291
|
}
|
|
16952
|
-
if (!
|
|
17292
|
+
if (!existsSync15(file))
|
|
16953
17293
|
return `evidence screenshot is missing: ${s.path}`;
|
|
16954
17294
|
if (!isRegularFile(file))
|
|
16955
17295
|
return `evidence screenshot is not a regular file: ${s.path}`;
|
|
@@ -16991,16 +17331,16 @@ async function waitUp(url, ms, signal) {
|
|
|
16991
17331
|
return false;
|
|
16992
17332
|
}
|
|
16993
17333
|
var playwrightBin = (repoDir) => {
|
|
16994
|
-
const bin =
|
|
16995
|
-
return
|
|
17334
|
+
const bin = join26(repoDir, "node_modules", ".bin", "playwright");
|
|
17335
|
+
return existsSync15(bin) ? bin : null;
|
|
16996
17336
|
};
|
|
16997
17337
|
function openapiPaths(repoDir) {
|
|
16998
17338
|
for (const f of ["openapi.json", "openapi.yaml", "openapi.yml", "swagger.json"]) {
|
|
16999
|
-
const p =
|
|
17000
|
-
if (!
|
|
17339
|
+
const p = join26(repoDir, f);
|
|
17340
|
+
if (!existsSync15(p))
|
|
17001
17341
|
continue;
|
|
17002
17342
|
try {
|
|
17003
|
-
const txt =
|
|
17343
|
+
const txt = readFileSync20(p, "utf8");
|
|
17004
17344
|
const paths = f.endsWith(".json") ? Object.keys(JSON.parse(txt).paths ?? {}) : [...txt.matchAll(/^ {2}(\/[^\s:]*):\s*$/gm)].map((m) => m[1]);
|
|
17005
17345
|
return paths.filter((x) => !x.includes("{")).slice(0, 10);
|
|
17006
17346
|
} catch {
|
|
@@ -17010,8 +17350,8 @@ function openapiPaths(repoDir) {
|
|
|
17010
17350
|
return [];
|
|
17011
17351
|
}
|
|
17012
17352
|
var playwrightTestPkg = (repoDir) => {
|
|
17013
|
-
const d =
|
|
17014
|
-
return
|
|
17353
|
+
const d = join26(repoDir, "node_modules", "@playwright", "test");
|
|
17354
|
+
return existsSync15(join26(d, "package.json")) ? d : null;
|
|
17015
17355
|
};
|
|
17016
17356
|
var e2eSpecSource = (pkgDir, base, routes) => `const { test } = require(${JSON.stringify(pkgDir)});
|
|
17017
17357
|
test("loki e2e walkthrough", async ({ page }) => {
|
|
@@ -17023,24 +17363,24 @@ async function recordE2eMedia(repoDir, runDir, relDir, base, routes, ms, signal)
|
|
|
17023
17363
|
if (!pw || !pkg)
|
|
17024
17364
|
return { rels: [], skipped: "playwright e2e video and trace skipped: @playwright/test is not installed in the repo" };
|
|
17025
17365
|
try {
|
|
17026
|
-
const dir =
|
|
17027
|
-
|
|
17028
|
-
|
|
17029
|
-
|
|
17366
|
+
const dir = join26(runDir, relDir, "e2e");
|
|
17367
|
+
mkdirSync7(dir, { recursive: true });
|
|
17368
|
+
writeFileSync8(join26(dir, "walk.spec.cjs"), e2eSpecSource(pkg, base, routes));
|
|
17369
|
+
writeFileSync8(join26(dir, "playwright.config.cjs"), `module.exports = { testDir: ".", testMatch: "walk.spec.cjs", outputDir: "./out", reporter: "null", use: { video: "on", trace: "on" } };
|
|
17030
17370
|
`);
|
|
17031
|
-
if (!await shoot(pw, ["test", "--config",
|
|
17371
|
+
if (!await shoot(pw, ["test", "--config", join26(dir, "playwright.config.cjs")], repoDir, ms, signal))
|
|
17032
17372
|
return { rels: [], skipped: "playwright e2e video and trace skipped: the run failed or timed out" };
|
|
17033
17373
|
const rels = [];
|
|
17034
17374
|
const walk = (d) => {
|
|
17035
17375
|
for (const e of readdirSync9(d, { withFileTypes: true })) {
|
|
17036
|
-
const full =
|
|
17376
|
+
const full = join26(d, e.name);
|
|
17037
17377
|
if (e.isDirectory())
|
|
17038
17378
|
walk(full);
|
|
17039
17379
|
else if (e.isFile() && /\.(webm|zip)$/.test(e.name))
|
|
17040
|
-
rels.push(
|
|
17380
|
+
rels.push(join26(relDir, "e2e", relative3(dir, full)));
|
|
17041
17381
|
}
|
|
17042
17382
|
};
|
|
17043
|
-
walk(
|
|
17383
|
+
walk(join26(dir, "out"));
|
|
17044
17384
|
return rels.length > 0 ? { rels, skipped: null } : { rels, skipped: "playwright e2e video and trace skipped: no video or trace was produced" };
|
|
17045
17385
|
} catch (e) {
|
|
17046
17386
|
return { rels: [], skipped: `playwright e2e video and trace skipped: ${String(e?.message ?? e).slice(0, 120)}` };
|
|
@@ -17078,14 +17418,14 @@ function shoot(pw, args, cwd, ms, signal) {
|
|
|
17078
17418
|
}
|
|
17079
17419
|
var isSymlink = (p) => {
|
|
17080
17420
|
try {
|
|
17081
|
-
return
|
|
17421
|
+
return lstatSync6(p).isSymbolicLink();
|
|
17082
17422
|
} catch {
|
|
17083
17423
|
return false;
|
|
17084
17424
|
}
|
|
17085
17425
|
};
|
|
17086
17426
|
var isRegularFile = (p) => {
|
|
17087
17427
|
try {
|
|
17088
|
-
return
|
|
17428
|
+
return lstatSync6(p).isFile();
|
|
17089
17429
|
} catch {
|
|
17090
17430
|
return false;
|
|
17091
17431
|
}
|
|
@@ -17133,24 +17473,30 @@ async function captureVisualEvidence(repoDir, runDir, changed, opts = {}) {
|
|
|
17133
17473
|
try {
|
|
17134
17474
|
if (!visualEvidenceEnabled(opts.env))
|
|
17135
17475
|
return skip("LOKI_VISUAL_EVIDENCE is 0");
|
|
17136
|
-
const
|
|
17137
|
-
|
|
17138
|
-
|
|
17139
|
-
|
|
17140
|
-
if (!
|
|
17141
|
-
|
|
17476
|
+
const api = loadProjectApi(repoDir), boot = isMultiRoot(api) ? api.uiBoot() : null;
|
|
17477
|
+
const appDir = boot ? join26(repoDir, boot.boot.cwd) : repoDir;
|
|
17478
|
+
const pkgPath = join26(appDir, "package.json");
|
|
17479
|
+
let script = null;
|
|
17480
|
+
if (!boot) {
|
|
17481
|
+
if (!existsSync15(pkgPath))
|
|
17482
|
+
return skip("no package.json");
|
|
17483
|
+
script = pickScript(JSON.parse(readFileSync20(pkgPath, "utf8")));
|
|
17484
|
+
if (!script)
|
|
17485
|
+
return skip("no dev, preview or start script");
|
|
17486
|
+
}
|
|
17142
17487
|
const pages = changed.filter(isPageFile);
|
|
17143
17488
|
const explicitOn = (opts.env ?? process.env)["LOKI_VISUAL_EVIDENCE"] === "1";
|
|
17144
17489
|
const apiPaths = pages.length === 0 && explicitOn ? openapiPaths(repoDir) : [];
|
|
17145
17490
|
if (pages.length === 0 && apiPaths.length === 0)
|
|
17146
17491
|
return skip(explicitOn ? "no changed page files and no openapi routes" : "no changed page files (set LOKI_VISUAL_EVIDENCE=1 for the API transcript)");
|
|
17147
|
-
const pw = pages.length > 0 ? playwrightBin(repoDir) : null;
|
|
17492
|
+
const pw = pages.length > 0 ? playwrightBin(appDir) ?? playwrightBin(repoDir) : null;
|
|
17148
17493
|
if (pages.length > 0 && !pw)
|
|
17149
17494
|
return skip("playwright not resolvable in the repo");
|
|
17150
17495
|
const budget = opts.budgetMs ?? DEFAULT_BUDGET_MS, end = Date.now() + budget, left = () => end - Date.now();
|
|
17151
17496
|
const aborted = () => signal?.aborted === true;
|
|
17152
17497
|
const port = await freePort(), base = `http://127.0.0.1:${port}`;
|
|
17153
|
-
|
|
17498
|
+
const [bc, ba] = boot ? ["bash", ["-c", boot.boot.cmd]] : ["npm", ["run", script, "--silent"]];
|
|
17499
|
+
child = spawn5(bc, [...ba], { cwd: appDir, detached: true, env: { ...opts.env ?? process.env, PORT: String(port), HOST: "127.0.0.1", BROWSER: "none" }, stdio: "ignore" });
|
|
17154
17500
|
child.on("error", () => {
|
|
17155
17501
|
return;
|
|
17156
17502
|
});
|
|
@@ -17161,26 +17507,26 @@ async function captureVisualEvidence(repoDir, runDir, changed, opts = {}) {
|
|
|
17161
17507
|
}
|
|
17162
17508
|
if (!await waitUp(base, left(), signal))
|
|
17163
17509
|
return skip(aborted() ? "aborted" : `${script} server did not answer within ${Math.round(budget / 1000)}s`);
|
|
17164
|
-
const evRoot =
|
|
17510
|
+
const evRoot = join26(runDir, "evidence");
|
|
17165
17511
|
if (isSymlink(evRoot))
|
|
17166
17512
|
return skip("evidence dir is a symlink");
|
|
17167
|
-
|
|
17513
|
+
mkdirSync7(evRoot, { recursive: true });
|
|
17168
17514
|
if (pages.length > 0) {
|
|
17169
|
-
const rel0 =
|
|
17170
|
-
|
|
17515
|
+
const rel0 = join26("evidence", randomBytes2(6).toString("hex"));
|
|
17516
|
+
mkdirSync7(join26(runDir, rel0));
|
|
17171
17517
|
const rels = [];
|
|
17172
17518
|
for (const route of [...new Set(pages.map(routeFor))].slice(0, opts.maxRoutes ?? MAX_ROUTES)) {
|
|
17173
17519
|
if (aborted() || left() <= 0)
|
|
17174
17520
|
break;
|
|
17175
|
-
const rel =
|
|
17176
|
-
const ok = await shoot(pw, ["screenshot", `${base}${route}`,
|
|
17177
|
-
if (ok && !aborted() && !isSymlink(
|
|
17521
|
+
const rel = join26(rel0, `${screenName(route)}.png`);
|
|
17522
|
+
const ok = await shoot(pw, ["screenshot", `${base}${route}`, join26(runDir, rel)], appDir, left(), signal);
|
|
17523
|
+
if (ok && !aborted() && !isSymlink(join26(runDir, rel)) && existsSync15(join26(runDir, rel)))
|
|
17178
17524
|
rels.push(rel);
|
|
17179
17525
|
}
|
|
17180
17526
|
if (aborted())
|
|
17181
17527
|
return skip("aborted");
|
|
17182
17528
|
const routes = [...new Set(pages.map(routeFor))].slice(0, opts.maxRoutes ?? MAX_ROUTES);
|
|
17183
|
-
const media = rels.length > 0 && left() > 0 ? await recordE2eMedia(
|
|
17529
|
+
const media = rels.length > 0 && left() > 0 ? await recordE2eMedia(appDir, runDir, rel0, base, routes, left(), signal) : { rels: [], skipped: null };
|
|
17184
17530
|
if (aborted())
|
|
17185
17531
|
return skip("aborted");
|
|
17186
17532
|
const screens = hashScreens(runDir, [...rels, ...media.rels]);
|
|
@@ -17199,9 +17545,9 @@ async function captureVisualEvidence(repoDir, runDir, changed, opts = {}) {
|
|
|
17199
17545
|
}
|
|
17200
17546
|
if (aborted())
|
|
17201
17547
|
return skip("aborted");
|
|
17202
|
-
const hdir =
|
|
17203
|
-
|
|
17204
|
-
|
|
17548
|
+
const hdir = join26(evRoot, randomBytes2(6).toString("hex"));
|
|
17549
|
+
mkdirSync7(hdir);
|
|
17550
|
+
writeFileSync8(join26(hdir, "http.json"), JSON.stringify(rows, null, 2) + `
|
|
17205
17551
|
`);
|
|
17206
17552
|
return { screens: [], http: true, skipped: null };
|
|
17207
17553
|
} catch (e) {
|
|
@@ -17227,23 +17573,9 @@ function receiptScreensProblem(receiptPath, receipt) {
|
|
|
17227
17573
|
return receipt["evidence_screens"] === undefined ? null : checkScreens(dirname8(receiptPath), receipt["evidence_screens"]);
|
|
17228
17574
|
}
|
|
17229
17575
|
|
|
17230
|
-
// ../../loki-ts/src/engine10/types.ts
|
|
17231
|
-
var STAGE_BUDGETS = {
|
|
17232
|
-
intake: { targetS: 15, limitS: 60 },
|
|
17233
|
-
plan: { targetS: 45, limitS: 90 },
|
|
17234
|
-
wall: { targetS: 45, limitS: 90 },
|
|
17235
|
-
implement: { targetS: 180, limitS: 480 },
|
|
17236
|
-
verify: { targetS: 60, limitS: 120 },
|
|
17237
|
-
fix: { targetS: 90, limitS: 180 },
|
|
17238
|
-
commit: { targetS: 5, limitS: 30 },
|
|
17239
|
-
seal: { targetS: 15, limitS: 60 },
|
|
17240
|
-
pr: { targetS: 15, limitS: 60 },
|
|
17241
|
-
deep: { targetS: null, limitS: 2700 }
|
|
17242
|
-
};
|
|
17243
|
-
|
|
17244
17576
|
// ../../loki-ts/src/features/contract.ts
|
|
17245
|
-
import { existsSync as
|
|
17246
|
-
import { dirname as dirname9, join as
|
|
17577
|
+
import { existsSync as existsSync16, lstatSync as lstatSync7, mkdirSync as mkdirSync8, readFileSync as readFileSync21, writeFileSync as writeFileSync9 } from "fs";
|
|
17578
|
+
import { dirname as dirname9, join as join27 } from "path";
|
|
17247
17579
|
import { createHash as createHash7 } from "crypto";
|
|
17248
17580
|
var MAX_CRITERIA = 50;
|
|
17249
17581
|
var MAX_TEXT = 500;
|
|
@@ -17278,14 +17610,14 @@ function untracedLines(trace) {
|
|
|
17278
17610
|
return trace.criteria.filter((c) => c.status === "no_match").map((c) => `contract ${sanitizeCriterion(c.id)} untraced (no keyword match in changed files): ${sanitizeCriterion(c.text)}`);
|
|
17279
17611
|
}
|
|
17280
17612
|
function contractPath(repoDir) {
|
|
17281
|
-
return
|
|
17613
|
+
return join27(repoDir, ".loki", "contract.json");
|
|
17282
17614
|
}
|
|
17283
17615
|
var MAX_CONTRACT_BYTES = 1024 * 1024;
|
|
17284
17616
|
function readContract(repoDir) {
|
|
17285
17617
|
const p = contractPath(repoDir);
|
|
17286
17618
|
let st;
|
|
17287
17619
|
try {
|
|
17288
|
-
st =
|
|
17620
|
+
st = lstatSync7(p);
|
|
17289
17621
|
} catch {
|
|
17290
17622
|
return { contract: null, notes: [], sha256: null };
|
|
17291
17623
|
}
|
|
@@ -17295,7 +17627,7 @@ function readContract(repoDir) {
|
|
|
17295
17627
|
return { contract: null, notes: ["contract unreadable: too large"], sha256: null };
|
|
17296
17628
|
let raw2;
|
|
17297
17629
|
try {
|
|
17298
|
-
raw2 =
|
|
17630
|
+
raw2 = readFileSync21(p);
|
|
17299
17631
|
} catch {
|
|
17300
17632
|
return { contract: null, notes: ["contract unreadable: read failed"], sha256: null };
|
|
17301
17633
|
}
|
|
@@ -17330,7 +17662,7 @@ function sealContract(repoDir, body, rawDiff, checks, env = process.env, snap) {
|
|
|
17330
17662
|
const live = readContract(repoDir);
|
|
17331
17663
|
let exists2 = true;
|
|
17332
17664
|
try {
|
|
17333
|
-
|
|
17665
|
+
lstatSync7(contractPath(repoDir));
|
|
17334
17666
|
} catch {
|
|
17335
17667
|
exists2 = false;
|
|
17336
17668
|
}
|
|
@@ -17359,18 +17691,18 @@ function sealContract(repoDir, body, rawDiff, checks, env = process.env, snap) {
|
|
|
17359
17691
|
|
|
17360
17692
|
// ../../loki-ts/src/features/speed/seal_group.ts
|
|
17361
17693
|
import { createHash as createHash8 } from "crypto";
|
|
17362
|
-
import { existsSync as
|
|
17363
|
-
import { dirname as dirname10, join as
|
|
17694
|
+
import { existsSync as existsSync17, readdirSync as readdirSync10, readFileSync as readFileSync22, statSync as statSync11 } from "fs";
|
|
17695
|
+
import { dirname as dirname10, join as join28 } from "path";
|
|
17364
17696
|
var ID_RE = /^[A-Za-z0-9][A-Za-z0-9._-]{0,63}$/;
|
|
17365
17697
|
var PASSING = new Set(["VERIFIED", "ALREADY_SATISFIED"]);
|
|
17366
17698
|
var sha2 = (b) => createHash8("sha256").update(b).digest("hex");
|
|
17367
17699
|
var isObj9 = (v) => v !== null && typeof v === "object" && !Array.isArray(v);
|
|
17368
17700
|
var text2 = (v, cap) => typeof v === "string" ? v.replace(/[\x00-\x1f\x7f]+/g, " ").trim().slice(0, cap) : "";
|
|
17369
17701
|
var num2 = (v) => typeof v === "number" && Number.isFinite(v) && v >= 0 ? v : 0;
|
|
17370
|
-
var unitsDir = (runDir) =>
|
|
17702
|
+
var unitsDir = (runDir) => join28(runDir, "group", "units");
|
|
17371
17703
|
var readJson3 = (p) => {
|
|
17372
17704
|
try {
|
|
17373
|
-
return JSON.parse(
|
|
17705
|
+
return JSON.parse(readFileSync22(p, "utf8"));
|
|
17374
17706
|
} catch {
|
|
17375
17707
|
return;
|
|
17376
17708
|
}
|
|
@@ -17383,8 +17715,8 @@ var dirNames = (d) => {
|
|
|
17383
17715
|
}
|
|
17384
17716
|
};
|
|
17385
17717
|
function sealGroup(runDir, rh) {
|
|
17386
|
-
const manifestPath =
|
|
17387
|
-
if (!
|
|
17718
|
+
const manifestPath = join28(runDir, "group", "manifest.json");
|
|
17719
|
+
if (!existsSync17(manifestPath))
|
|
17388
17720
|
return { section: undefined, notProven: [], problems: 0, allUnitsPass: true };
|
|
17389
17721
|
const notProven = [];
|
|
17390
17722
|
let problems = 0;
|
|
@@ -17408,10 +17740,10 @@ function sealGroup(runDir, rh) {
|
|
|
17408
17740
|
return fail(`unit ${index2} has an invalid, duplicate or case-colliding id`);
|
|
17409
17741
|
seen.add(id);
|
|
17410
17742
|
seenLower.add(id.toLowerCase());
|
|
17411
|
-
const dir =
|
|
17743
|
+
const dir = join28(unitsDir(runDir), id), rp = join28(dir, "receipt.json"), ep = join28(dir, "events.jsonl");
|
|
17412
17744
|
let bytes;
|
|
17413
17745
|
try {
|
|
17414
|
-
bytes =
|
|
17746
|
+
bytes = readFileSync22(rp);
|
|
17415
17747
|
} catch {
|
|
17416
17748
|
return fail(`unit ${id} sub-receipt missing`);
|
|
17417
17749
|
}
|
|
@@ -17428,7 +17760,7 @@ function sealGroup(runDir, rh) {
|
|
|
17428
17760
|
unit_id: id,
|
|
17429
17761
|
sub_receipt_sha256: sha2(bytes),
|
|
17430
17762
|
receipt_sha256: sub["receipt_sha256"],
|
|
17431
|
-
events_file_sha256:
|
|
17763
|
+
events_file_sha256: existsSync17(ep) ? sha2(readFileSync22(ep)) : null,
|
|
17432
17764
|
verdict,
|
|
17433
17765
|
goal: text2(u["goal"], 200),
|
|
17434
17766
|
files: (Array.isArray(u["files"]) ? u["files"] : []).map((f) => text2(f, 200)).filter(Boolean).slice(0, 50),
|
|
@@ -17453,7 +17785,7 @@ async function verifyGroup(receiptPath, receipt, rh, verifyOne, deps = {}) {
|
|
|
17453
17785
|
const bad = (reason) => ({ verdict: "TAMPERED", reason: `group: ${reason}` });
|
|
17454
17786
|
const g = receipt["group"];
|
|
17455
17787
|
if (g === undefined)
|
|
17456
|
-
return
|
|
17788
|
+
return existsSync17(join28(runDir, "group", "manifest.json")) ? bad("group directory present but the receipt has no group section") : null;
|
|
17457
17789
|
if (!isObj9(g) || !ID_RE.test(String(g["group_id"] ?? "")) || !Array.isArray(g["units"]) || g["units"].length === 0)
|
|
17458
17790
|
return bad("group section malformed");
|
|
17459
17791
|
const entries = g["units"];
|
|
@@ -17465,20 +17797,20 @@ async function verifyGroup(receiptPath, receipt, rh, verifyOne, deps = {}) {
|
|
|
17465
17797
|
return bad("unit directories do not match the receipt (missing or extra unit)");
|
|
17466
17798
|
let unchecked = null;
|
|
17467
17799
|
for (let i = 0;i < entries.length; i++) {
|
|
17468
|
-
const e = entries[i], id = ids[i], dir =
|
|
17800
|
+
const e = entries[i], id = ids[i], dir = join28(unitsDir(runDir), id), rp = join28(dir, "receipt.json"), ep = join28(dir, "events.jsonl");
|
|
17469
17801
|
if (e["index"] !== i)
|
|
17470
17802
|
return bad(`unit ${id} is out of order (index ${String(e["index"])} at position ${i})`);
|
|
17471
17803
|
let bytes;
|
|
17472
17804
|
try {
|
|
17473
|
-
if (!
|
|
17805
|
+
if (!statSync11(dir).isDirectory())
|
|
17474
17806
|
return bad(`unit ${id} is not a directory`);
|
|
17475
|
-
bytes =
|
|
17807
|
+
bytes = readFileSync22(rp);
|
|
17476
17808
|
} catch {
|
|
17477
17809
|
return bad(`unit ${id} sub-receipt missing`);
|
|
17478
17810
|
}
|
|
17479
17811
|
if (sha2(bytes) !== e["sub_receipt_sha256"])
|
|
17480
17812
|
return bad(`unit ${id} sub-receipt does not match its recorded hash`);
|
|
17481
|
-
const evSha =
|
|
17813
|
+
const evSha = existsSync17(ep) ? sha2(readFileSync22(ep)) : null;
|
|
17482
17814
|
if (evSha !== (e["events_file_sha256"] ?? null))
|
|
17483
17815
|
return bad(`unit ${id} events.jsonl does not match its recorded hash`);
|
|
17484
17816
|
const sub = readJson3(rp);
|
|
@@ -17533,21 +17865,21 @@ function loadSigningKey(generate = true) {
|
|
|
17533
17865
|
let pem = inline ?? "";
|
|
17534
17866
|
if (!inline) {
|
|
17535
17867
|
const given = process.env["LOKI_RECEIPT_SIGNING_KEY_FILE"]?.trim();
|
|
17536
|
-
const file = given ||
|
|
17868
|
+
const file = given || join29(process.env["HOME"] || homedir6(), ".loki", "keys", RECEIPT_SIGNER_BASENAME);
|
|
17537
17869
|
try {
|
|
17538
|
-
pem =
|
|
17870
|
+
pem = readFileSync23(file);
|
|
17539
17871
|
if (!given) {
|
|
17540
17872
|
for (const [f, m] of [[file, 384], [dirname11(file), 448]])
|
|
17541
|
-
if (
|
|
17873
|
+
if (statSync12(f).mode & 63)
|
|
17542
17874
|
chmodSync4(f, m);
|
|
17543
17875
|
}
|
|
17544
17876
|
} catch (e) {
|
|
17545
17877
|
if (e.code !== "ENOENT" || !generate)
|
|
17546
17878
|
return null;
|
|
17547
|
-
|
|
17548
|
-
|
|
17879
|
+
mkdirSync9(dirname11(dirname11(file)), { recursive: true, mode: 448 });
|
|
17880
|
+
mkdirSync9(dirname11(file), { recursive: true, mode: 448 });
|
|
17549
17881
|
const tmp = `${file}.${randomBytes3(6).toString("hex")}.tmp`;
|
|
17550
|
-
|
|
17882
|
+
writeFileSync10(tmp, generateKeyPairSync("ed25519").privateKey.export({ type: "pkcs8", format: "pem" }), { flag: "wx", mode: 384 });
|
|
17551
17883
|
try {
|
|
17552
17884
|
linkSync(tmp, file);
|
|
17553
17885
|
} catch (l) {
|
|
@@ -17556,7 +17888,7 @@ function loadSigningKey(generate = true) {
|
|
|
17556
17888
|
} finally {
|
|
17557
17889
|
unlinkSync4(tmp);
|
|
17558
17890
|
}
|
|
17559
|
-
pem =
|
|
17891
|
+
pem = readFileSync23(file);
|
|
17560
17892
|
}
|
|
17561
17893
|
}
|
|
17562
17894
|
const k = createPrivateKey(pem);
|
|
@@ -17683,7 +18015,7 @@ var sealStage = {
|
|
|
17683
18015
|
if (wallNotRun > 0) {
|
|
17684
18016
|
try {
|
|
17685
18017
|
const kept = new Set((Array.isArray(o.wall?.files) ? o.wall.files : []).map((f) => basename9(String(f.path))));
|
|
17686
|
-
for (const n2 of readdirSync11(
|
|
18018
|
+
for (const n2 of readdirSync11(join29(ctx.runDir, "wall")).sort())
|
|
17687
18019
|
if (!kept.has(n2))
|
|
17688
18020
|
notProven.add(`wall test discarded: ${n2} (not_run)`);
|
|
17689
18021
|
} catch {}
|
|
@@ -17743,7 +18075,7 @@ var sealStage = {
|
|
|
17743
18075
|
const cost = ctx.cost.read(ctx.repoDir, iterIds);
|
|
17744
18076
|
if (cost.unmetered)
|
|
17745
18077
|
notProven.add("cost unmetered (CLI invoker; recorded as 0)");
|
|
17746
|
-
const eventsPath =
|
|
18078
|
+
const eventsPath = join29(ctx.runDir, "events.jsonl");
|
|
17747
18079
|
const wallFiles = Array.isArray(o.wall?.files) ? o.wall.files : [];
|
|
17748
18080
|
const wallPassed = typeof o.verify?.wall_passed === "boolean" ? o.verify.wall_passed : null;
|
|
17749
18081
|
if (wallFiles.length > 0 && wallPassed === null)
|
|
@@ -17779,7 +18111,7 @@ var sealStage = {
|
|
|
17779
18111
|
provider: ctx.provider,
|
|
17780
18112
|
model: ctx.model,
|
|
17781
18113
|
resumed: o.intake?.resumed === true,
|
|
17782
|
-
events_sha256: sha2562(
|
|
18114
|
+
events_sha256: sha2562(existsSync18(eventsPath) ? readFileSync23(eventsPath) : ""),
|
|
17783
18115
|
...await sealEvidence(ctx.repoDir, ctx.runDir, o, notProven, signal, ctx.emit),
|
|
17784
18116
|
log_seal: true
|
|
17785
18117
|
};
|
|
@@ -17793,11 +18125,11 @@ var sealStage = {
|
|
|
17793
18125
|
hash = receiptSha256(body);
|
|
17794
18126
|
}
|
|
17795
18127
|
const receipt = { ...body, receipt_sha256: hash, verification: { jwt: sig.jwt, kid: sig.kid } };
|
|
17796
|
-
|
|
17797
|
-
const path =
|
|
17798
|
-
|
|
18128
|
+
mkdirSync9(ctx.runDir, { recursive: true });
|
|
18129
|
+
const path = join29(ctx.runDir, "receipt.json");
|
|
18130
|
+
writeFileSync10(path, JSON.stringify(receipt, null, 2) + `
|
|
17799
18131
|
`);
|
|
17800
|
-
|
|
18132
|
+
writeFileSync10(join29(ctx.runDir, "receipt.md"), renderReceiptMd(receipt));
|
|
17801
18133
|
const signed = sig.jwt !== null;
|
|
17802
18134
|
const data = { receipt_path: path, receipt_sha256: hash, signed, kid: sig.kid, verdict, not_proven: receipt.not_proven };
|
|
17803
18135
|
ctx.emit("receipt.sealed", "seal", { path, receipt_sha256: hash, signed, kid: sig.kid, verdict, not_proven: receipt.not_proven });
|
|
@@ -17807,7 +18139,7 @@ var sealStage = {
|
|
|
17807
18139
|
|
|
17808
18140
|
// ../../loki-ts/src/features/receipt_dsse.ts
|
|
17809
18141
|
import { createPublicKey as createPublicKey3, sign as sign2, verify as verify2 } from "crypto";
|
|
17810
|
-
import { readFileSync as
|
|
18142
|
+
import { readFileSync as readFileSync24 } from "fs";
|
|
17811
18143
|
var DSSE_PAYLOAD_TYPE = "application/vnd.in-toto+json";
|
|
17812
18144
|
var STATEMENT_TYPE = "https://in-toto.io/Statement/v1";
|
|
17813
18145
|
var PREDICATE_TYPE = "https://autonomi.dev/loki/receipt/v10";
|
|
@@ -17888,7 +18220,7 @@ function verifyDsseReceipt(env, d) {
|
|
|
17888
18220
|
}
|
|
17889
18221
|
function runIdGuard(receiptPath, runId) {
|
|
17890
18222
|
try {
|
|
17891
|
-
return envelopeRunIdProblem(JSON.parse(
|
|
18223
|
+
return envelopeRunIdProblem(JSON.parse(readFileSync24(receiptPath, "utf8")), runId);
|
|
17892
18224
|
} catch {
|
|
17893
18225
|
return null;
|
|
17894
18226
|
}
|
|
@@ -17902,7 +18234,7 @@ function pubFor(kid, given) {
|
|
|
17902
18234
|
const active = loadSigningKey(false);
|
|
17903
18235
|
const pubs = (process.env["LOKI_RECEIPT_RETIRED_PUBKEYS"] ?? "").split(":").map((f) => f.trim()).filter(Boolean).flatMap((f) => {
|
|
17904
18236
|
try {
|
|
17905
|
-
return [createPublicKey4(
|
|
18237
|
+
return [createPublicKey4(readFileSync25(f))];
|
|
17906
18238
|
} catch {
|
|
17907
18239
|
return [];
|
|
17908
18240
|
}
|
|
@@ -17934,10 +18266,10 @@ function checkEventLog(receiptPath, receipt) {
|
|
|
17934
18266
|
const bound = receipt["events_sha256"];
|
|
17935
18267
|
if (typeof bound !== "string" || bound === createHash10("sha256").digest("hex"))
|
|
17936
18268
|
return null;
|
|
17937
|
-
const path =
|
|
17938
|
-
if (!
|
|
18269
|
+
const path = join30(dirname12(receiptPath), "events.jsonl");
|
|
18270
|
+
if (!existsSync19(path))
|
|
17939
18271
|
return "events.jsonl is missing";
|
|
17940
|
-
const raw2 =
|
|
18272
|
+
const raw2 = readFileSync25(path, "utf8"), events2 = readEvents(path), lines = raw2.split(`
|
|
17941
18273
|
`).filter((l) => l.trim() !== "");
|
|
17942
18274
|
if (events2.length !== lines.length || events2.some((e, i) => e.seq !== i))
|
|
17943
18275
|
return "events.jsonl has a forged, edited or missing line";
|
|
@@ -17953,7 +18285,7 @@ function checkEventLog(receiptPath, receipt) {
|
|
|
17953
18285
|
return h.digest("hex") === bound ? null : "events.jsonl does not match the hash recorded at seal";
|
|
17954
18286
|
}
|
|
17955
18287
|
function checkLogSeal(receiptPath, hash, kid, given) {
|
|
17956
|
-
const path =
|
|
18288
|
+
const path = join30(dirname12(receiptPath), "events.jsonl"), events2 = readEvents(path), done = events2.findIndex((e) => e.type === "run.completed");
|
|
17957
18289
|
if (!events2.some((e) => e.type === "receipt.sealed" && e.data["receipt_sha256"] === hash))
|
|
17958
18290
|
return { verdict: "TAMPERED", reason: "events.jsonl has no receipt.sealed event for this receipt" };
|
|
17959
18291
|
if (done < 0)
|
|
@@ -17961,7 +18293,7 @@ function checkLogSeal(receiptPath, hash, kid, given) {
|
|
|
17961
18293
|
const seal = events2[done + 1], d = seal?.type === "log.sealed" ? seal.data : null;
|
|
17962
18294
|
if (!d)
|
|
17963
18295
|
return { verdict: "TAMPERED", reason: "events.jsonl has no signed log.sealed line after run.completed" };
|
|
17964
|
-
const lines =
|
|
18296
|
+
const lines = readFileSync25(path, "utf8").split(`
|
|
17965
18297
|
`).filter((l) => l.trim() !== ""), sha3 = createHash10("sha256").update(lines.slice(0, done + 1).join(`
|
|
17966
18298
|
`) + `
|
|
17967
18299
|
`).digest("hex");
|
|
@@ -17971,11 +18303,11 @@ function checkLogSeal(receiptPath, hash, kid, given) {
|
|
|
17971
18303
|
return null;
|
|
17972
18304
|
}
|
|
17973
18305
|
async function verifyReceipt(receiptPath, deps = {}) {
|
|
17974
|
-
if (!
|
|
18306
|
+
if (!existsSync19(receiptPath))
|
|
17975
18307
|
return { verdict: "UNCHECKED", reasons: [`receipt not found: ${receiptPath}`] };
|
|
17976
18308
|
let receipt;
|
|
17977
18309
|
try {
|
|
17978
|
-
receipt = JSON.parse((deps.read ?? ((p) =>
|
|
18310
|
+
receipt = JSON.parse((deps.read ?? ((p) => readFileSync25(p, "utf8")))(receiptPath));
|
|
17979
18311
|
} catch {
|
|
17980
18312
|
return { verdict: "UNCHECKED", reasons: ["receipt.json is not valid JSON"] };
|
|
17981
18313
|
}
|
|
@@ -18008,6 +18340,9 @@ async function verifyReceipt(receiptPath, deps = {}) {
|
|
|
18008
18340
|
return { verdict: "UNCHECKED", reasons: [outcome.reason ?? "attestation not checked"] };
|
|
18009
18341
|
if (outcome.status === "tampered")
|
|
18010
18342
|
return { verdict: "TAMPERED", reasons: [outcome.reason ?? "attestation invalid"] };
|
|
18343
|
+
const shownKid = verification.kid, signedKid = JSON.parse(Buffer.from(jwt.split(".")[0], "base64url").toString()).kid;
|
|
18344
|
+
if (shownKid !== undefined && shownKid !== signedKid)
|
|
18345
|
+
return { verdict: "TAMPERED", reasons: ["verification.kid differs from the signing kid in the attestation header (metadata outside the signed digest was edited)"] };
|
|
18011
18346
|
const seal = receipt["log_seal"] !== true ? null : checkLogSeal(receiptPath, computed, JSON.parse(Buffer.from(jwt.split(".")[0], "base64url").toString()).kid, deps.pubkey);
|
|
18012
18347
|
if (seal)
|
|
18013
18348
|
return { verdict: seal.verdict, reasons: [seal.reason] };
|
|
@@ -18040,15 +18375,15 @@ function mount19(ctx) {
|
|
|
18040
18375
|
return done(404, "receipt not available", "not_found");
|
|
18041
18376
|
let runDir, file;
|
|
18042
18377
|
try {
|
|
18043
|
-
const root =
|
|
18044
|
-
runDir =
|
|
18045
|
-
if (runDir !==
|
|
18378
|
+
const root = realpathSync13(join31(repo.p, ".loki", "runs"));
|
|
18379
|
+
runDir = realpathSync13(join31(root, run2));
|
|
18380
|
+
if (runDir !== join31(root, run2))
|
|
18046
18381
|
return done(404, "receipt not found", "not_found");
|
|
18047
|
-
file =
|
|
18382
|
+
file = realpathSync13(join31(runDir, "receipt.json"));
|
|
18048
18383
|
} catch {
|
|
18049
18384
|
return done(404, "receipt not found", "not_found");
|
|
18050
18385
|
}
|
|
18051
|
-
if (!file.startsWith(runDir +
|
|
18386
|
+
if (!file.startsWith(runDir + sep11))
|
|
18052
18387
|
return done(404, "receipt not found", "not_found");
|
|
18053
18388
|
let integrity, reasons, sha3 = null, outcome = "UNREADABLE";
|
|
18054
18389
|
try {
|
|
@@ -18063,7 +18398,7 @@ function mount19(ctx) {
|
|
|
18063
18398
|
sha3 = r.receiptSha256 ?? null;
|
|
18064
18399
|
}
|
|
18065
18400
|
try {
|
|
18066
|
-
outcome = outcomeOf(JSON.parse(
|
|
18401
|
+
outcome = outcomeOf(JSON.parse(readFileSync26(file, "utf8")));
|
|
18067
18402
|
} catch {
|
|
18068
18403
|
outcome = "UNREADABLE";
|
|
18069
18404
|
}
|
|
@@ -18125,8 +18460,8 @@ function ttlCache(ttlMs = PROBE_CACHE_MS, now = Date.now) {
|
|
|
18125
18460
|
}
|
|
18126
18461
|
|
|
18127
18462
|
// src/server/legacy/shim.ts
|
|
18128
|
-
import { existsSync as
|
|
18129
|
-
import { join as
|
|
18463
|
+
import { existsSync as existsSync20, readFileSync as readFileSync27 } from "fs";
|
|
18464
|
+
import { join as join32 } from "path";
|
|
18130
18465
|
|
|
18131
18466
|
// src/server/legacy/routes.ts
|
|
18132
18467
|
var LEGACY_ROUTES = [
|
|
@@ -18498,10 +18833,10 @@ var logged = new Set;
|
|
|
18498
18833
|
var toHono = (p) => p.replace(/\{([A-Za-z_]+):path\}/g, ":$1{.+}").replace(/\{([A-Za-z_]+)\}/g, ":$1");
|
|
18499
18834
|
var NOT_SUPPORTED = "not yet supported by the Control Plane";
|
|
18500
18835
|
var repoVersion = () => {
|
|
18501
|
-
for (const f of [
|
|
18836
|
+
for (const f of [join32(import.meta.dir, "../../../../../VERSION")]) {
|
|
18502
18837
|
try {
|
|
18503
|
-
if (
|
|
18504
|
-
return
|
|
18838
|
+
if (existsSync20(f))
|
|
18839
|
+
return readFileSync27(f, "utf8").trim();
|
|
18505
18840
|
} catch {}
|
|
18506
18841
|
}
|
|
18507
18842
|
return "unknown";
|
|
@@ -18515,8 +18850,8 @@ var intParam = (v, def, min, max) => {
|
|
|
18515
18850
|
return n2 >= min && n2 <= max ? n2 : null;
|
|
18516
18851
|
};
|
|
18517
18852
|
function queryAudit(db, q) {
|
|
18518
|
-
const where = and(q.action ? eq(
|
|
18519
|
-
return db.select().from(
|
|
18853
|
+
const where = and(q.action ? eq(audit2.action, q.action) : undefined, q.start_date ? gte(audit2.ts, q.start_date) : undefined, q.end_date ? lt(audit2.ts, `${q.end_date}\uFFFF`) : undefined);
|
|
18854
|
+
return db.select().from(audit2).where(where).orderBy(desc(audit2.id)).limit(q.limit).offset(q.offset).all().map((r) => ({ id: r.id, timestamp: r.ts, action: r.action, actor: r.actor, details: r.detail }));
|
|
18520
18855
|
}
|
|
18521
18856
|
function legacyShim(opts) {
|
|
18522
18857
|
const { db } = opts;
|
|
@@ -18683,10 +19018,11 @@ function legacyShim(opts) {
|
|
|
18683
19018
|
|
|
18684
19019
|
// src/server/app.ts
|
|
18685
19020
|
var MAX_BODY2 = 1e6;
|
|
18686
|
-
var defaultUiDir = () => [
|
|
19021
|
+
var defaultUiDir = () => [join33(import.meta.dir, "../../ui/dist"), join33(import.meta.dir, "../ui/dist")].find((d) => existsSync21(join33(d, "index.html")));
|
|
18687
19022
|
function createApp(opts) {
|
|
18688
19023
|
const uiDir = opts.uiDir ?? defaultUiDir();
|
|
18689
19024
|
const { db, sqlite } = openDb(opts.dbPath);
|
|
19025
|
+
cleanupLeakedFixtures(sqlite);
|
|
18690
19026
|
recomputeLegacy(db);
|
|
18691
19027
|
let ready = true;
|
|
18692
19028
|
const answerDir = opts.answerDir ?? defaultAnswerDir();
|
|
@@ -18808,11 +19144,11 @@ function createApp(opts) {
|
|
|
18808
19144
|
return c.json({ error: "not found" }, 404);
|
|
18809
19145
|
const root = resolve6(uiDir);
|
|
18810
19146
|
const f = resolve6(root, `.${p}`);
|
|
18811
|
-
if (f.startsWith(root +
|
|
19147
|
+
if (f.startsWith(root + sep12) && existsSync21(f) && statSync14(f).isFile())
|
|
18812
19148
|
return new Response(Bun.file(f));
|
|
18813
19149
|
if (/\.[a-z0-9]+$/i.test(p))
|
|
18814
19150
|
return c.json({ error: "not found" }, 404);
|
|
18815
|
-
return new Response(Bun.file(
|
|
19151
|
+
return new Response(Bun.file(join33(root, "index.html")));
|
|
18816
19152
|
});
|
|
18817
19153
|
return { app, db, close: () => sqlite.close() };
|
|
18818
19154
|
}
|