@svenroth-ai/shipwright 0.24.7-next.0 → 0.25.0
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/client/dist/assets/{EmbeddedTerminal-D2BLGWdW.js → EmbeddedTerminal-5QvKe3_a.js} +20 -20
- package/client/dist/assets/{abnfDiagram-VRR7QNED-C-W_2Jd4.js → abnfDiagram-VRR7QNED-CXi9HegQ.js} +1 -1
- package/client/dist/assets/{arc-CpqwESP5.js → arc-DLh4GiqR.js} +1 -1
- package/client/dist/assets/{architectureDiagram-ZJ3FMSHR-DZVZ895V.js → architectureDiagram-ZJ3FMSHR-DUypQ8dQ.js} +1 -1
- package/client/dist/assets/{blockDiagram-677ZJIJ3-CCCZiAA7.js → blockDiagram-677ZJIJ3-D2cVMKJG.js} +1 -1
- package/client/dist/assets/{c4Diagram-LMCZKHZV-Bg1beZAE.js → c4Diagram-LMCZKHZV-DtruBGJw.js} +1 -1
- package/client/dist/assets/channel-C_k-9jsa.js +1 -0
- package/client/dist/assets/{chunk-2Q5K7J3B-CoaXSarh.js → chunk-2Q5K7J3B-CAfQkFw2.js} +1 -1
- package/client/dist/assets/{chunk-32BRIVSS-CeFXS-CM.js → chunk-32BRIVSS-CPWXoLnQ.js} +1 -1
- package/client/dist/assets/{chunk-5VM5RSS4-QyJMVe0m.js → chunk-5VM5RSS4-xVc3oppQ.js} +1 -1
- package/client/dist/assets/{chunk-EX3LRPZG-BIOtWVF8.js → chunk-EX3LRPZG-CUpl6fRR.js} +1 -1
- package/client/dist/assets/{chunk-JWPE2WC7-Dcq-lWyX.js → chunk-JWPE2WC7-NejUp56x.js} +1 -1
- package/client/dist/assets/{chunk-MOJQB5TN-C95323ja.js → chunk-MOJQB5TN-C9-2lLg0.js} +1 -1
- package/client/dist/assets/{chunk-RYQCIY6F-C0HPwpWy.js → chunk-RYQCIY6F-BAPtwABy.js} +1 -1
- package/client/dist/assets/{chunk-V7JOEXUC-BPrTbZqA.js → chunk-V7JOEXUC-Dwzihw26.js} +1 -1
- package/client/dist/assets/{chunk-VR4S4FIN--BCnNoqt.js → chunk-VR4S4FIN-9-iArFJM.js} +1 -1
- package/client/dist/assets/{chunk-XXDRQBXY-Dj8vtcXf.js → chunk-XXDRQBXY-DiWHOrqe.js} +1 -1
- package/client/dist/assets/classDiagram-OUVF2IWQ-CsEPq9Ac.js +1 -0
- package/client/dist/assets/classDiagram-v2-EOCWNBFH-CsEPq9Ac.js +1 -0
- package/client/dist/assets/{cose-bilkent-JH36ORCC-y51otZKz.js → cose-bilkent-JH36ORCC-Cp04EOcH.js} +1 -1
- package/client/dist/assets/{cynefin-VYW2F7L2-BRfS3cHo.js → cynefin-VYW2F7L2-D--GN1v8.js} +1 -1
- package/client/dist/assets/{cynefinDiagram-TSTJHNR4-Dpg2RtfL.js → cynefinDiagram-TSTJHNR4-BGH0o_qp.js} +1 -1
- package/client/dist/assets/{dagre-VKFMJZFB-BdZtXgKZ.js → dagre-VKFMJZFB-DSKkIKlj.js} +1 -1
- package/client/dist/assets/{diagram-FQU43EPY-K-RCwWxW.js → diagram-FQU43EPY-Cs_C4lb-.js} +1 -1
- package/client/dist/assets/{diagram-G47NLZAW-CHNn7F2-.js → diagram-G47NLZAW-oDtAA8O_.js} +1 -1
- package/client/dist/assets/{diagram-NH7WQ7WH-B3zu6xib.js → diagram-NH7WQ7WH-BiFMjqSH.js} +1 -1
- package/client/dist/assets/{diagram-OA4YK3LP-Wp5ju7L7.js → diagram-OA4YK3LP-Ccs3thCj.js} +1 -1
- package/client/dist/assets/{diagram-WEI45ONY-C94GpsRk.js → diagram-WEI45ONY-CdZNqgO3.js} +1 -1
- package/client/dist/assets/{ebnfDiagram-CCIWWBDH-DXrTPOrP.js → ebnfDiagram-CCIWWBDH-CapTA0VL.js} +1 -1
- package/client/dist/assets/{erDiagram-Q63AITRT-CGejnW0f.js → erDiagram-Q63AITRT-BcRNEP8s.js} +1 -1
- package/client/dist/assets/{flowDiagram-23GEKE2U-CFJMM637.js → flowDiagram-23GEKE2U-CJ_zF7e_.js} +1 -1
- package/client/dist/assets/{ganttDiagram-NO4QXBWP-DnaH94Su.js → ganttDiagram-NO4QXBWP-CnJdcg9A.js} +1 -1
- package/client/dist/assets/{gitGraphDiagram-IHSO6WYX-DT-D0gbn.js → gitGraphDiagram-IHSO6WYX-YTUQ_ELk.js} +1 -1
- package/client/dist/assets/index-6nZ1bt0a.css +1 -0
- package/client/dist/assets/index-irQhGNpn.js +828 -0
- package/client/dist/assets/{infoDiagram-FWYZ7A6U-DW3Ps00w.js → infoDiagram-FWYZ7A6U-CLOP_ZWD.js} +1 -1
- package/client/dist/assets/{ishikawaDiagram-FXEZZL3T-DjxjIT3n.js → ishikawaDiagram-FXEZZL3T-aFTIzr7o.js} +1 -1
- package/client/dist/assets/{journeyDiagram-5HDEW3XC-DTQRI93Y.js → journeyDiagram-5HDEW3XC-CbPJB4xd.js} +1 -1
- package/client/dist/assets/{kanban-definition-HUTT4EX6-BeDvbiEs.js → kanban-definition-HUTT4EX6-BoyhytIS.js} +1 -1
- package/client/dist/assets/{linear-B4foxDlh.js → linear-1VXA-uBg.js} +1 -1
- package/client/dist/assets/{mermaid.core-c-L4qgSB.js → mermaid.core-D_lO2yK9.js} +4 -4
- package/client/dist/assets/{mindmap-definition-LN4V7U3C-dh8v0J8T.js → mindmap-definition-LN4V7U3C-iFK7LXMx.js} +1 -1
- package/client/dist/assets/{pegDiagram-2B236MQR-D5vNrtFh.js → pegDiagram-2B236MQR-DNmcmfA-.js} +1 -1
- package/client/dist/assets/{pieDiagram-ENE6RG2P-CIjg_6lR.js → pieDiagram-ENE6RG2P-ekx83O5b.js} +1 -1
- package/client/dist/assets/{quadrantDiagram-ABIIQ3AL-CF37qWQD.js → quadrantDiagram-ABIIQ3AL-DayyD1l8.js} +1 -1
- package/client/dist/assets/{railroadDiagram-RFXS5EU6-x0Hn30Kj.js → railroadDiagram-RFXS5EU6-Bdgz7S6G.js} +1 -1
- package/client/dist/assets/{requirementDiagram-TGXJPOKE-BE2QnVbN.js → requirementDiagram-TGXJPOKE-szP3Muld.js} +1 -1
- package/client/dist/assets/{sankeyDiagram-HTMAVEWB-CUv6cIlV.js → sankeyDiagram-HTMAVEWB-Ct3rzgUj.js} +1 -1
- package/client/dist/assets/{sequenceDiagram-DBY2YBRQ-C3ACj5LS.js → sequenceDiagram-DBY2YBRQ-_69EfmOU.js} +1 -1
- package/client/dist/assets/{sizeCapture-X5ZJPWSS-DTMIb5gX.js → sizeCapture-X5ZJPWSS-BSOU06J2.js} +1 -1
- package/client/dist/assets/{stateDiagram-2N3HPSRC-DI1Z8P74.js → stateDiagram-2N3HPSRC-HmOmFwQm.js} +1 -1
- package/client/dist/assets/stateDiagram-v2-6OUMAXLB-BW8H33dv.js +1 -0
- package/client/dist/assets/{swimlanes-5IMT3BWC-CLSr9w29.js → swimlanes-5IMT3BWC-CVg_4oT7.js} +2 -2
- package/client/dist/assets/swimlanesDiagram-G3AALYLV-BaRlxcPD.js +8 -0
- package/client/dist/assets/{timeline-definition-FHXFAJF6-BTWmUhDh.js → timeline-definition-FHXFAJF6-CBBaDKXm.js} +1 -1
- package/client/dist/assets/{vennDiagram-L72KCM5P-Ce1dlLCM.js → vennDiagram-L72KCM5P-CBXiWPMc.js} +1 -1
- package/client/dist/assets/{wardleyDiagram-EHGQE667-ClOE-aae.js → wardleyDiagram-EHGQE667-uhBCZoW0.js} +1 -1
- package/client/dist/assets/{xychartDiagram-FW5EYKEG-Fa2pqWKw.js → xychartDiagram-FW5EYKEG-xxE_wY5O.js} +1 -1
- package/client/dist/index.html +2 -2
- package/package.json +68 -68
- package/server/dist/core/grade-runner.d.ts +4 -4
- package/server/dist/core/grade-runner.d.ts.map +1 -1
- package/server/dist/core/grade-runner.js +51 -26
- package/server/dist/core/grade-runner.js.map +1 -1
- package/server/dist/core/grade-target.d.ts +8 -0
- package/server/dist/core/grade-target.d.ts.map +1 -1
- package/server/dist/core/grade-target.js +10 -0
- package/server/dist/core/grade-target.js.map +1 -1
- package/server/dist/core/triage-cli-runner.d.ts.map +1 -1
- package/server/dist/core/triage-cli-runner.js +29 -10
- package/server/dist/core/triage-cli-runner.js.map +1 -1
- package/server/dist/core/uv-runner.d.ts +23 -0
- package/server/dist/core/uv-runner.d.ts.map +1 -0
- package/server/dist/core/uv-runner.js +39 -0
- package/server/dist/core/uv-runner.js.map +1 -0
- package/server/dist/external/file/exists-routes.d.ts +7 -0
- package/server/dist/external/file/exists-routes.d.ts.map +1 -0
- package/server/dist/external/file/exists-routes.js +91 -0
- package/server/dist/external/file/exists-routes.js.map +1 -0
- package/server/dist/external/org/audit-log.d.ts +27 -0
- package/server/dist/external/org/audit-log.d.ts.map +1 -0
- package/server/dist/external/org/audit-log.js +92 -0
- package/server/dist/external/org/audit-log.js.map +1 -0
- package/server/dist/external/org/beat-register.d.ts +15 -22
- package/server/dist/external/org/beat-register.d.ts.map +1 -1
- package/server/dist/external/org/beat-register.js +37 -32
- package/server/dist/external/org/beat-register.js.map +1 -1
- package/server/dist/external/org/file-read.d.ts +16 -0
- package/server/dist/external/org/file-read.d.ts.map +1 -1
- package/server/dist/external/org/file-read.js +86 -46
- package/server/dist/external/org/file-read.js.map +1 -1
- package/server/dist/external/org/file-write-core.d.ts +18 -0
- package/server/dist/external/org/file-write-core.d.ts.map +1 -0
- package/server/dist/external/org/file-write-core.js +97 -0
- package/server/dist/external/org/file-write-core.js.map +1 -0
- package/server/dist/external/org/file-write.d.ts +25 -0
- package/server/dist/external/org/file-write.d.ts.map +1 -1
- package/server/dist/external/org/file-write.js +82 -132
- package/server/dist/external/org/file-write.js.map +1 -1
- package/server/dist/external/org/last-run.d.ts +25 -16
- package/server/dist/external/org/last-run.d.ts.map +1 -1
- package/server/dist/external/org/last-run.js +78 -64
- package/server/dist/external/org/last-run.js.map +1 -1
- package/server/dist/external/org/lead-doc-read.d.ts +21 -0
- package/server/dist/external/org/lead-doc-read.d.ts.map +1 -0
- package/server/dist/external/org/lead-doc-read.js +84 -0
- package/server/dist/external/org/lead-doc-read.js.map +1 -0
- package/server/dist/external/org/mount.d.ts +12 -0
- package/server/dist/external/org/mount.d.ts.map +1 -0
- package/server/dist/external/org/mount.js +34 -0
- package/server/dist/external/org/mount.js.map +1 -0
- package/server/dist/external/org/org-chart-lookup.d.ts +13 -0
- package/server/dist/external/org/org-chart-lookup.d.ts.map +1 -1
- package/server/dist/external/org/org-chart-lookup.js +40 -5
- package/server/dist/external/org/org-chart-lookup.js.map +1 -1
- package/server/dist/external/org/org-chart.d.ts +21 -12
- package/server/dist/external/org/org-chart.d.ts.map +1 -1
- package/server/dist/external/org/org-chart.js +50 -32
- package/server/dist/external/org/org-chart.js.map +1 -1
- package/server/dist/external/org/role-extract.d.ts +8 -0
- package/server/dist/external/org/role-extract.d.ts.map +1 -0
- package/server/dist/external/org/role-extract.js +72 -0
- package/server/dist/external/org/role-extract.js.map +1 -0
- package/server/dist/external/org/usage.d.ts +15 -11
- package/server/dist/external/org/usage.d.ts.map +1 -1
- package/server/dist/external/org/usage.js +64 -54
- package/server/dist/external/org/usage.js.map +1 -1
- package/server/dist/external/routes.d.ts +1 -1
- package/server/dist/external/routes.d.ts.map +1 -1
- package/server/dist/external/routes.js +10 -11
- package/server/dist/external/routes.js.map +1 -1
- package/server/dist/routes/grade.d.ts.map +1 -1
- package/server/dist/routes/grade.js +3 -2
- package/server/dist/routes/grade.js.map +1 -1
- package/server/dist/routes/org-leads-composite.d.ts +21 -0
- package/server/dist/routes/org-leads-composite.d.ts.map +1 -0
- package/server/dist/routes/org-leads-composite.js +95 -0
- package/server/dist/routes/org-leads-composite.js.map +1 -0
- package/server/dist/routes/org.d.ts +15 -0
- package/server/dist/routes/org.d.ts.map +1 -0
- package/server/dist/routes/org.js +198 -0
- package/server/dist/routes/org.js.map +1 -0
- package/server/dist/terminal/pty-manager.d.ts +9 -0
- package/server/dist/terminal/pty-manager.d.ts.map +1 -1
- package/server/dist/terminal/pty-manager.js +15 -0
- package/server/dist/terminal/pty-manager.js.map +1 -1
- package/server/dist/terminal/replay-snapshot.d.ts +10 -1
- package/server/dist/terminal/replay-snapshot.d.ts.map +1 -1
- package/server/dist/terminal/replay-snapshot.js +36 -1
- package/server/dist/terminal/replay-snapshot.js.map +1 -1
- package/server/dist/terminal/ws-upgrade-handler.d.ts +17 -1
- package/server/dist/terminal/ws-upgrade-handler.d.ts.map +1 -1
- package/server/dist/terminal/ws-upgrade-handler.fixtures.d.ts +1 -0
- package/server/dist/terminal/ws-upgrade-handler.fixtures.d.ts.map +1 -1
- package/server/dist/terminal/ws-upgrade-handler.fixtures.js +3 -2
- package/server/dist/terminal/ws-upgrade-handler.fixtures.js.map +1 -1
- package/server/dist/terminal/ws-upgrade-handler.js +45 -4
- package/server/dist/terminal/ws-upgrade-handler.js.map +1 -1
- package/server/dist/test/python-spawn-guard-util.d.ts +14 -0
- package/server/dist/test/python-spawn-guard-util.d.ts.map +1 -0
- package/server/dist/test/python-spawn-guard-util.js +124 -0
- package/server/dist/test/python-spawn-guard-util.js.map +1 -0
- package/server/dist/types/org.d.ts +115 -0
- package/server/dist/types/org.d.ts.map +1 -0
- package/server/dist/types/org.js +16 -0
- package/server/dist/types/org.js.map +1 -0
- package/server/package.json +1 -1
- package/client/dist/assets/MarkdownEditorModal-CP5yG9OU.js +0 -212
- package/client/dist/assets/channel-BNXbCvDS.js +0 -1
- package/client/dist/assets/classDiagram-OUVF2IWQ-w-wCtLx3.js +0 -1
- package/client/dist/assets/classDiagram-v2-EOCWNBFH-w-wCtLx3.js +0 -1
- package/client/dist/assets/index-CU2QJgvx.js +0 -609
- package/client/dist/assets/index-ftvVcUT8.css +0 -1
- package/client/dist/assets/stateDiagram-v2-6OUMAXLB-DvwiHlJv.js +0 -1
- package/client/dist/assets/swimlanesDiagram-G3AALYLV-JTQZTlZ3.js +0 -8
|
@@ -10,5 +10,30 @@ export interface OrgFileWriteDeps extends DecisionsLockDeps {
|
|
|
10
10
|
/** Injectable for tests; production wires the real `withDecisionsLock`. */
|
|
11
11
|
withDecisionsLock?: typeof withDecisionsLock;
|
|
12
12
|
}
|
|
13
|
+
export type OrgFileWriteCoreResult = {
|
|
14
|
+
status: 200;
|
|
15
|
+
body: {
|
|
16
|
+
written: true;
|
|
17
|
+
fingerprint: string;
|
|
18
|
+
size: number;
|
|
19
|
+
};
|
|
20
|
+
} | {
|
|
21
|
+
status: 400 | 403 | 404 | 409 | 413 | 500;
|
|
22
|
+
body: Record<string, unknown>;
|
|
23
|
+
};
|
|
24
|
+
export interface OrgFileWriteParams {
|
|
25
|
+
relpath: string | undefined;
|
|
26
|
+
contentLengthHeader: string | undefined;
|
|
27
|
+
body: string;
|
|
28
|
+
ifMatch: string | undefined;
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Pure core — shared by the secret-gated route and the plain-surface proxy.
|
|
32
|
+
* Kind restriction (e.g. "only `charter` may pass through the browser-facing
|
|
33
|
+
* proxy") is the CALLER's job, applied against `resolveOrgAllowlistedTarget`'s
|
|
34
|
+
* `kind` before or after invoking this — this core stays as general as the
|
|
35
|
+
* existing route (all six allowlisted kinds).
|
|
36
|
+
*/
|
|
37
|
+
export declare function orgFileWriteCore(deps: OrgFileWriteDeps, params: OrgFileWriteParams): Promise<OrgFileWriteCoreResult>;
|
|
13
38
|
export declare function registerOrgFileWrite(app: Hono, deps: OrgFileWriteDeps): void;
|
|
14
39
|
//# sourceMappingURL=file-write.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"file-write.d.ts","sourceRoot":"","sources":["../../../src/external/org/file-write.ts"],"names":[],"mappings":"AA6CA,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,MAAM,CAAC;
|
|
1
|
+
{"version":3,"file":"file-write.d.ts","sourceRoot":"","sources":["../../../src/external/org/file-write.ts"],"names":[],"mappings":"AA6CA,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,MAAM,CAAC;AAOjC,OAAO,EACL,iBAAiB,EAEjB,KAAK,iBAAiB,EACvB,MAAM,qBAAqB,CAAC;AAG7B,sFAAsF;AACtF,eAAO,MAAM,mBAAmB,QAAkB,CAAC;AAOnD,MAAM,WAAW,gBAAiB,SAAQ,iBAAiB;IACzD,SAAS,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK;QAAE,cAAc,IAAI,OAAO,CAAC;QAAC,MAAM,IAAI,OAAO,CAAA;KAAE,CAAC;IAC/E,2EAA2E;IAC3E,iBAAiB,CAAC,EAAE,OAAO,iBAAiB,CAAC;CAC9C;AAED,MAAM,MAAM,sBAAsB,GAC9B;IAAE,MAAM,EAAE,GAAG,CAAC;IAAC,IAAI,EAAE;QAAE,OAAO,EAAE,IAAI,CAAC;QAAC,WAAW,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAA;KAAE,CAAA;CAAE,GAC3E;IAAE,MAAM,EAAE,GAAG,GAAG,GAAG,GAAG,GAAG,GAAG,GAAG,GAAG,GAAG,GAAG,GAAG,CAAC;IAAC,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;CAAE,CAAC;AAEjF,MAAM,WAAW,kBAAkB;IACjC,OAAO,EAAE,MAAM,GAAG,SAAS,CAAC;IAC5B,mBAAmB,EAAE,MAAM,GAAG,SAAS,CAAC;IACxC,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,GAAG,SAAS,CAAC;CAC7B;AAED;;;;;;GAMG;AACH,wBAAsB,gBAAgB,CACpC,IAAI,EAAE,gBAAgB,EACtB,MAAM,EAAE,kBAAkB,GACzB,OAAO,CAAC,sBAAsB,CAAC,CA6EjC;AAED,wBAAgB,oBAAoB,CAAC,GAAG,EAAE,IAAI,EAAE,IAAI,EAAE,gBAAgB,GAAG,IAAI,CAyB5E"}
|
|
@@ -42,164 +42,114 @@
|
|
|
42
42
|
* 413 body over ORG_WRITE_MAX_BYTES
|
|
43
43
|
* 500 stat / read / write failure
|
|
44
44
|
*/
|
|
45
|
-
import {
|
|
46
|
-
import { dirname, join } from "node:path";
|
|
47
|
-
import { realPathGuard } from "../../core/path-guard.js";
|
|
45
|
+
import { lstatSync } from "node:fs";
|
|
48
46
|
import { resolveOrgAllowlistedTarget, } from "./_helpers.js";
|
|
49
|
-
import { fileFingerprint, unquoteEtag } from "../file/_helpers.js";
|
|
50
47
|
import { withDecisionsLock, OrgSymlinkEscapeError, } from "./decisions-lock.js";
|
|
48
|
+
import { performWrite } from "./file-write-core.js";
|
|
51
49
|
/** 2 MiB — org docs are small prose files, same cap as the project markdown write. */
|
|
52
50
|
export const ORG_WRITE_MAX_BYTES = 2 * 1024 * 1024;
|
|
53
51
|
const LOCKED_KINDS = new Set([
|
|
54
52
|
"decision_log",
|
|
55
53
|
"decisions_proposed",
|
|
56
54
|
]);
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
};
|
|
72
|
-
}
|
|
73
|
-
if (lst.isSymbolicLink()) {
|
|
74
|
-
return { ok: false, status: 403, body: { error: "symlink_forbidden", path: relpath } };
|
|
75
|
-
}
|
|
76
|
-
if (!lst.isFile()) {
|
|
77
|
-
return { ok: false, status: 400, body: { error: "not_a_file", path: relpath } };
|
|
78
|
-
}
|
|
79
|
-
const realGuard = realPathGuard(leadsRoot, absoluteTarget);
|
|
80
|
-
if (!realGuard.ok) {
|
|
81
|
-
return {
|
|
82
|
-
ok: false,
|
|
83
|
-
status: 400,
|
|
84
|
-
body: { error: "path_traversal", detail: realGuard.reason },
|
|
85
|
-
};
|
|
86
|
-
}
|
|
87
|
-
if (!ifMatch) {
|
|
88
|
-
return { ok: false, status: 400, body: { error: "precondition_required" } };
|
|
55
|
+
/**
|
|
56
|
+
* Pure core — shared by the secret-gated route and the plain-surface proxy.
|
|
57
|
+
* Kind restriction (e.g. "only `charter` may pass through the browser-facing
|
|
58
|
+
* proxy") is the CALLER's job, applied against `resolveOrgAllowlistedTarget`'s
|
|
59
|
+
* `kind` before or after invoking this — this core stays as general as the
|
|
60
|
+
* existing route (all six allowlisted kinds).
|
|
61
|
+
*/
|
|
62
|
+
export async function orgFileWriteCore(deps, params) {
|
|
63
|
+
const { leadsRoot } = deps;
|
|
64
|
+
const lstat = deps.lstatSync ?? ((p) => lstatSync(p));
|
|
65
|
+
const lockFn = deps.withDecisionsLock ?? withDecisionsLock;
|
|
66
|
+
const { relpath, contentLengthHeader, body, ifMatch } = params;
|
|
67
|
+
if (!relpath || relpath.length === 0) {
|
|
68
|
+
return { status: 400, body: { error: "path_required" } };
|
|
89
69
|
}
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
70
|
+
const target = resolveOrgAllowlistedTarget(leadsRoot, relpath);
|
|
71
|
+
if (!target.ok) {
|
|
72
|
+
const status = target.reason === "not_allowlisted" ? 403 : 400;
|
|
73
|
+
const err = target.reason === "traversal" ? "path_traversal" : target.reason;
|
|
74
|
+
return { status, body: { error: err, detail: target.reason } };
|
|
93
75
|
}
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
if (code === "ENOENT") {
|
|
97
|
-
return { ok: false, status: 404, body: { error: "not_found", path: relpath } };
|
|
98
|
-
}
|
|
76
|
+
const declaredLength = Number(contentLengthHeader ?? "");
|
|
77
|
+
if (Number.isFinite(declaredLength) && declaredLength > ORG_WRITE_MAX_BYTES) {
|
|
99
78
|
return {
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
body: { error: "file_read_failed", detail: String(err).slice(0, 200) },
|
|
79
|
+
status: 413,
|
|
80
|
+
body: { error: "payload_too_large", maxBytes: ORG_WRITE_MAX_BYTES, size: declaredLength },
|
|
103
81
|
};
|
|
104
82
|
}
|
|
105
|
-
const
|
|
106
|
-
if (
|
|
83
|
+
const byteLen = Buffer.byteLength(body, "utf8");
|
|
84
|
+
if (byteLen > ORG_WRITE_MAX_BYTES) {
|
|
107
85
|
return {
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
body: { error: "fingerprint_mismatch", currentFingerprint },
|
|
86
|
+
status: 413,
|
|
87
|
+
body: { error: "payload_too_large", maxBytes: ORG_WRITE_MAX_BYTES, size: byteLen },
|
|
111
88
|
};
|
|
112
89
|
}
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
catch (err) {
|
|
90
|
+
let outcome;
|
|
91
|
+
if (LOCKED_KINDS.has(target.kind)) {
|
|
92
|
+
// Pre-lock existence probe (external-review fix) — must run BEFORE
|
|
93
|
+
// withDecisionsLock, since its own ensureFile() would otherwise
|
|
94
|
+
// auto-vivify decisions-proposed.md first and hide a genuinely
|
|
95
|
+
// missing target from performWrite's existence check.
|
|
120
96
|
try {
|
|
121
|
-
|
|
122
|
-
unlinkSync(tmp);
|
|
97
|
+
lstat(target.absolute);
|
|
123
98
|
}
|
|
124
|
-
catch {
|
|
125
|
-
|
|
99
|
+
catch (err) {
|
|
100
|
+
const code = err?.code;
|
|
101
|
+
if (code === "ENOENT") {
|
|
102
|
+
return { status: 404, body: { error: "not_found", path: relpath } };
|
|
103
|
+
}
|
|
104
|
+
return {
|
|
105
|
+
status: 500,
|
|
106
|
+
body: { error: "file_stat_failed", detail: String(err).slice(0, 200) },
|
|
107
|
+
};
|
|
108
|
+
}
|
|
109
|
+
try {
|
|
110
|
+
outcome = await lockFn({ leadsRoot, lstatSync: deps.lstatSync }, () => performWrite(target.absolute, leadsRoot, relpath, body, ifMatch, lstat));
|
|
111
|
+
}
|
|
112
|
+
catch (err) {
|
|
113
|
+
// Doubt-review fix: `withDecisionsLock` symlink-checks BOTH
|
|
114
|
+
// decision_log.md and decisions-proposed.md (whichever one this PUT
|
|
115
|
+
// targets, the OTHER is still checked because it shares the lock) —
|
|
116
|
+
// uncaught, that 403-shaped condition fell through to the generic
|
|
117
|
+
// 500 handler instead of the family's typed symlink_forbidden.
|
|
118
|
+
// ELOCKED is deliberately NOT caught here: it propagates to the
|
|
119
|
+
// caller (either the app-level errorHandler for the gated route, or
|
|
120
|
+
// the new router's own catch, both of which map it to the same
|
|
121
|
+
// retryable 409 every other multi-writer state file uses (CLAUDE.md
|
|
122
|
+
// DO-NOT #6)).
|
|
123
|
+
if (err instanceof OrgSymlinkEscapeError) {
|
|
124
|
+
return { status: 403, body: { error: "symlink_forbidden", path: err.path } };
|
|
125
|
+
}
|
|
126
|
+
throw err;
|
|
126
127
|
}
|
|
127
|
-
return {
|
|
128
|
-
ok: false,
|
|
129
|
-
status: 500,
|
|
130
|
-
body: { error: "write_failed", detail: String(err).slice(0, 200), path: relpath },
|
|
131
|
-
};
|
|
132
128
|
}
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
};
|
|
129
|
+
else {
|
|
130
|
+
outcome = performWrite(target.absolute, leadsRoot, relpath, body, ifMatch, lstat);
|
|
131
|
+
}
|
|
132
|
+
return { status: outcome.status, body: outcome.body };
|
|
138
133
|
}
|
|
139
134
|
export function registerOrgFileWrite(app, deps) {
|
|
140
|
-
const { leadsRoot } = deps;
|
|
141
|
-
const lstat = deps.lstatSync ?? ((p) => lstatSync(p));
|
|
142
|
-
const lockFn = deps.withDecisionsLock ?? withDecisionsLock;
|
|
143
135
|
app.put("/api/external/org/file", async (c) => {
|
|
144
136
|
const relpath = c.req.query("path");
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
return c.json({ error: err, detail: target.reason }, status);
|
|
153
|
-
}
|
|
154
|
-
const declaredLength = Number(c.req.header("content-length") ?? "");
|
|
155
|
-
if (Number.isFinite(declaredLength) && declaredLength > ORG_WRITE_MAX_BYTES) {
|
|
137
|
+
const contentLengthHeader = c.req.header("content-length");
|
|
138
|
+
// Bail before reading the request body when the declared Content-Length
|
|
139
|
+
// alone already exceeds the cap — preserved from the pre-extraction
|
|
140
|
+
// handler so an oversized upload is never buffered into memory just to
|
|
141
|
+
// be rejected.
|
|
142
|
+
const declaredLength = Number(contentLengthHeader ?? "");
|
|
143
|
+
if (relpath && Number.isFinite(declaredLength) && declaredLength > ORG_WRITE_MAX_BYTES) {
|
|
156
144
|
return c.json({ error: "payload_too_large", maxBytes: ORG_WRITE_MAX_BYTES, size: declaredLength }, 413);
|
|
157
145
|
}
|
|
158
|
-
const
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
if (LOCKED_KINDS.has(target.kind)) {
|
|
166
|
-
// Pre-lock existence probe (external-review fix) — must run BEFORE
|
|
167
|
-
// withDecisionsLock, since its own ensureFile() would otherwise
|
|
168
|
-
// auto-vivify decisions-proposed.md first and hide a genuinely
|
|
169
|
-
// missing target from performWrite's existence check.
|
|
170
|
-
try {
|
|
171
|
-
lstat(target.absolute);
|
|
172
|
-
}
|
|
173
|
-
catch (err) {
|
|
174
|
-
const code = err?.code;
|
|
175
|
-
if (code === "ENOENT") {
|
|
176
|
-
return c.json({ error: "not_found", path: relpath }, 404);
|
|
177
|
-
}
|
|
178
|
-
return c.json({ error: "file_stat_failed", detail: String(err).slice(0, 200) }, 500);
|
|
179
|
-
}
|
|
180
|
-
try {
|
|
181
|
-
outcome = await lockFn({ leadsRoot, lstatSync: deps.lstatSync }, () => performWrite(target.absolute, leadsRoot, relpath, body, ifMatch, lstat));
|
|
182
|
-
}
|
|
183
|
-
catch (err) {
|
|
184
|
-
// Doubt-review fix: `withDecisionsLock` symlink-checks BOTH
|
|
185
|
-
// decision_log.md and decisions-proposed.md (whichever one this PUT
|
|
186
|
-
// targets, the OTHER is still checked because it shares the lock) —
|
|
187
|
-
// uncaught, that 403-shaped condition fell through to the generic
|
|
188
|
-
// 500 handler instead of the family's typed symlink_forbidden.
|
|
189
|
-
// ELOCKED is deliberately NOT caught here: it propagates to the
|
|
190
|
-
// app-level errorHandler (middleware/error-handler.ts), which already
|
|
191
|
-
// maps it to the same retryable 409 every other multi-writer state
|
|
192
|
-
// file uses (CLAUDE.md DO-NOT #6).
|
|
193
|
-
if (err instanceof OrgSymlinkEscapeError) {
|
|
194
|
-
return c.json({ error: "symlink_forbidden", path: err.path }, 403);
|
|
195
|
-
}
|
|
196
|
-
throw err;
|
|
197
|
-
}
|
|
198
|
-
}
|
|
199
|
-
else {
|
|
200
|
-
outcome = performWrite(target.absolute, leadsRoot, relpath, body, ifMatch, lstat);
|
|
201
|
-
}
|
|
202
|
-
return c.json(outcome.body, outcome.status);
|
|
146
|
+
const result = await orgFileWriteCore(deps, {
|
|
147
|
+
relpath,
|
|
148
|
+
contentLengthHeader,
|
|
149
|
+
body: await c.req.text(),
|
|
150
|
+
ifMatch: c.req.header("if-match"),
|
|
151
|
+
});
|
|
152
|
+
return c.json(result.body, result.status);
|
|
203
153
|
});
|
|
204
154
|
}
|
|
205
155
|
//# sourceMappingURL=file-write.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"file-write.js","sourceRoot":"","sources":["../../../src/external/org/file-write.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2CG;AAGH,OAAO,
|
|
1
|
+
{"version":3,"file":"file-write.js","sourceRoot":"","sources":["../../../src/external/org/file-write.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,20 +1,7 @@
|
|
|
1
1
|
import type { Hono } from "hono";
|
|
2
|
-
import { type
|
|
3
|
-
import {
|
|
4
|
-
export type CadenceUnresolvedReason
|
|
5
|
-
export type LastRunResponse = {
|
|
6
|
-
leadId: string;
|
|
7
|
-
measured: false;
|
|
8
|
-
} | {
|
|
9
|
-
leadId: string;
|
|
10
|
-
measured: true;
|
|
11
|
-
lastRunAt: string;
|
|
12
|
-
sessionId: string;
|
|
13
|
-
staleness: Staleness | "unknown";
|
|
14
|
-
thresholdMs: number | null;
|
|
15
|
-
cadenceMs: number | null;
|
|
16
|
-
cadenceUnresolvedReason?: CadenceUnresolvedReason;
|
|
17
|
-
};
|
|
2
|
+
import { type LeadOrgInfoResult } from "./org-chart-lookup.js";
|
|
3
|
+
import type { LastRunResponse } from "../../types/org.js";
|
|
4
|
+
export type { CadenceUnresolvedReason, LastRunResponse } from "../../types/org.js";
|
|
18
5
|
export interface LastRunRouteDeps {
|
|
19
6
|
leadsRoot: string;
|
|
20
7
|
/** Injectable for tests; production wires the real lstatSync. */
|
|
@@ -23,6 +10,28 @@ export interface LastRunRouteDeps {
|
|
|
23
10
|
};
|
|
24
11
|
/** Injectable for deterministic boundary tests; production wires `() => new Date()`. */
|
|
25
12
|
now?: () => Date;
|
|
13
|
+
/**
|
|
14
|
+
* Pre-fetched cron/reports_to lookup for this lead — when supplied,
|
|
15
|
+
* `lastRunCore` uses it INSTEAD of calling `readLeadOrgInfo` itself.
|
|
16
|
+
* The composite `/api/org/leads` read passes this (via
|
|
17
|
+
* `readAllLeadOrgInfo`, one chart parse for the whole roster); the
|
|
18
|
+
* single-lead secret-gated route omits it and keeps its own self-
|
|
19
|
+
* contained one-lead read (already O(1), not the N+1 this exists to fix).
|
|
20
|
+
*/
|
|
21
|
+
orgInfo?: LeadOrgInfoResult;
|
|
26
22
|
}
|
|
23
|
+
export type LastRunCoreResult = {
|
|
24
|
+
status: 200;
|
|
25
|
+
body: LastRunResponse;
|
|
26
|
+
} | {
|
|
27
|
+
status: 400 | 403 | 500 | 502;
|
|
28
|
+
body: {
|
|
29
|
+
error: string;
|
|
30
|
+
leadId?: string;
|
|
31
|
+
detail?: string;
|
|
32
|
+
};
|
|
33
|
+
};
|
|
34
|
+
/** Pure core — shared by the secret-gated route and the plain-surface proxy. */
|
|
35
|
+
export declare function lastRunCore(deps: LastRunRouteDeps, leadId: string): LastRunCoreResult;
|
|
27
36
|
export declare function registerLastRunRoute(app: Hono, deps: LastRunRouteDeps): void;
|
|
28
37
|
//# sourceMappingURL=last-run.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"last-run.d.ts","sourceRoot":"","sources":["../../../src/external/org/last-run.ts"],"names":[],"mappings":"AA2BA,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,MAAM,CAAC;AAMjC,OAAO,EAAmB,KAAK,iBAAiB,EAAE,MAAM,uBAAuB,CAAC;
|
|
1
|
+
{"version":3,"file":"last-run.d.ts","sourceRoot":"","sources":["../../../src/external/org/last-run.ts"],"names":[],"mappings":"AA2BA,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,MAAM,CAAC;AAMjC,OAAO,EAAmB,KAAK,iBAAiB,EAAE,MAAM,uBAAuB,CAAC;AAEhF,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,oBAAoB,CAAC;AAE1D,YAAY,EAAE,uBAAuB,EAAE,eAAe,EAAE,MAAM,oBAAoB,CAAC;AAcnF,MAAM,WAAW,gBAAgB;IAC/B,SAAS,EAAE,MAAM,CAAC;IAClB,iEAAiE;IACjE,SAAS,CAAC,EAAE,CAAC,CAAC,EAAE,MAAM,KAAK;QAAE,cAAc,IAAI,OAAO,CAAA;KAAE,CAAC;IACzD,wFAAwF;IACxF,GAAG,CAAC,EAAE,MAAM,IAAI,CAAC;IACjB;;;;;;;OAOG;IACH,OAAO,CAAC,EAAE,iBAAiB,CAAC;CAC7B;AAED,MAAM,MAAM,iBAAiB,GACzB;IAAE,MAAM,EAAE,GAAG,CAAC;IAAC,IAAI,EAAE,eAAe,CAAA;CAAE,GACtC;IAAE,MAAM,EAAE,GAAG,GAAG,GAAG,GAAG,GAAG,GAAG,GAAG,CAAC;IAAC,IAAI,EAAE;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,MAAM,CAAC,EAAE,MAAM,CAAC;QAAC,MAAM,CAAC,EAAE,MAAM,CAAA;KAAE,CAAA;CAAE,CAAC;AAEjG,gFAAgF;AAChF,wBAAgB,WAAW,CAAC,IAAI,EAAE,gBAAgB,EAAE,MAAM,EAAE,MAAM,GAAG,iBAAiB,CA2GrF;AAED,wBAAgB,oBAAoB,CAAC,GAAG,EAAE,IAAI,EAAE,IAAI,EAAE,gBAAgB,GAAG,IAAI,CAK5E"}
|
|
@@ -40,62 +40,67 @@ function isValidLastRunPayload(v) {
|
|
|
40
40
|
typeof u.sessionId === "string" &&
|
|
41
41
|
u.sessionId.length > 0);
|
|
42
42
|
}
|
|
43
|
-
|
|
43
|
+
/** Pure core — shared by the secret-gated route and the plain-surface proxy. */
|
|
44
|
+
export function lastRunCore(deps, leadId) {
|
|
44
45
|
const { leadsRoot } = deps;
|
|
45
46
|
const lstat = deps.lstatSync ?? ((p) => lstatSync(p));
|
|
46
47
|
const now = deps.now ?? (() => new Date());
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
if (code === "ENOENT") {
|
|
60
|
-
const body = { leadId, measured: false };
|
|
61
|
-
return c.json(body, 200);
|
|
62
|
-
}
|
|
63
|
-
return c.json({ error: "last_run_read_failed", detail: String(err).slice(0, 200) }, 500);
|
|
64
|
-
}
|
|
65
|
-
if (lst.isSymbolicLink()) {
|
|
66
|
-
return c.json({ error: "symlink_forbidden", leadId }, 403);
|
|
67
|
-
}
|
|
68
|
-
const containment = realPathGuard(leadsRoot, absolute);
|
|
69
|
-
if (!containment.ok) {
|
|
70
|
-
return c.json({ error: "path_traversal", detail: containment.reason }, 400);
|
|
71
|
-
}
|
|
72
|
-
let raw;
|
|
73
|
-
try {
|
|
74
|
-
raw = readFileSync(absolute, "utf8");
|
|
75
|
-
}
|
|
76
|
-
catch (err) {
|
|
77
|
-
const code = err?.code;
|
|
78
|
-
if (code === "ENOENT") {
|
|
79
|
-
const body = { leadId, measured: false };
|
|
80
|
-
return c.json(body, 200);
|
|
81
|
-
}
|
|
82
|
-
return c.json({ error: "last_run_read_failed", detail: String(err).slice(0, 200) }, 500);
|
|
83
|
-
}
|
|
84
|
-
let parsed;
|
|
85
|
-
try {
|
|
86
|
-
parsed = JSON.parse(raw);
|
|
87
|
-
}
|
|
88
|
-
catch {
|
|
89
|
-
return c.json({ error: "last_run_invalid", leadId }, 502);
|
|
48
|
+
if (!LEAD_ID_RE.test(leadId)) {
|
|
49
|
+
return { status: 400, body: { error: "invalid_lead_id", leadId } };
|
|
50
|
+
}
|
|
51
|
+
const absolute = path.join(leadsRoot, leadId, "last-run.json");
|
|
52
|
+
let lst;
|
|
53
|
+
try {
|
|
54
|
+
lst = lstat(absolute);
|
|
55
|
+
}
|
|
56
|
+
catch (err) {
|
|
57
|
+
const code = err?.code;
|
|
58
|
+
if (code === "ENOENT") {
|
|
59
|
+
return { status: 200, body: { leadId, measured: false } };
|
|
90
60
|
}
|
|
91
|
-
|
|
92
|
-
|
|
61
|
+
return {
|
|
62
|
+
status: 500,
|
|
63
|
+
body: { error: "last_run_read_failed", detail: String(err).slice(0, 200) },
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
if (lst.isSymbolicLink()) {
|
|
67
|
+
return { status: 403, body: { error: "symlink_forbidden", leadId } };
|
|
68
|
+
}
|
|
69
|
+
const containment = realPathGuard(leadsRoot, absolute);
|
|
70
|
+
if (!containment.ok) {
|
|
71
|
+
return { status: 400, body: { error: "path_traversal", detail: containment.reason } };
|
|
72
|
+
}
|
|
73
|
+
let raw;
|
|
74
|
+
try {
|
|
75
|
+
raw = readFileSync(absolute, "utf8");
|
|
76
|
+
}
|
|
77
|
+
catch (err) {
|
|
78
|
+
const code = err?.code;
|
|
79
|
+
if (code === "ENOENT") {
|
|
80
|
+
return { status: 200, body: { leadId, measured: false } };
|
|
93
81
|
}
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
82
|
+
return {
|
|
83
|
+
status: 500,
|
|
84
|
+
body: { error: "last_run_read_failed", detail: String(err).slice(0, 200) },
|
|
85
|
+
};
|
|
86
|
+
}
|
|
87
|
+
let parsed;
|
|
88
|
+
try {
|
|
89
|
+
parsed = JSON.parse(raw);
|
|
90
|
+
}
|
|
91
|
+
catch {
|
|
92
|
+
return { status: 502, body: { error: "last_run_invalid", leadId } };
|
|
93
|
+
}
|
|
94
|
+
if (!isValidLastRunPayload(parsed)) {
|
|
95
|
+
return { status: 502, body: { error: "last_run_invalid", leadId } };
|
|
96
|
+
}
|
|
97
|
+
const { lastRunAt, sessionId } = parsed;
|
|
98
|
+
const nowValue = now();
|
|
99
|
+
const orgInfo = deps.orgInfo ?? readLeadOrgInfo(leadsRoot, leadId);
|
|
100
|
+
if (!orgInfo.ok) {
|
|
101
|
+
return {
|
|
102
|
+
status: 200,
|
|
103
|
+
body: {
|
|
99
104
|
leadId,
|
|
100
105
|
measured: true,
|
|
101
106
|
lastRunAt,
|
|
@@ -104,12 +109,14 @@ export function registerLastRunRoute(app, deps) {
|
|
|
104
109
|
thresholdMs: null,
|
|
105
110
|
cadenceMs: null,
|
|
106
111
|
cadenceUnresolvedReason: orgInfo.reason,
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
112
|
+
},
|
|
113
|
+
};
|
|
114
|
+
}
|
|
115
|
+
const interval = cronIntervalMs(orgInfo.cron, nowValue);
|
|
116
|
+
if (!interval.ok) {
|
|
117
|
+
return {
|
|
118
|
+
status: 200,
|
|
119
|
+
body: {
|
|
113
120
|
leadId,
|
|
114
121
|
measured: true,
|
|
115
122
|
lastRunAt,
|
|
@@ -118,11 +125,13 @@ export function registerLastRunRoute(app, deps) {
|
|
|
118
125
|
thresholdMs: null,
|
|
119
126
|
cadenceMs: null,
|
|
120
127
|
cadenceUnresolvedReason: "invalid_cron",
|
|
121
|
-
}
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
128
|
+
},
|
|
129
|
+
};
|
|
130
|
+
}
|
|
131
|
+
const evaluation = evaluateStaleness(lastRunAt, interval.ms, nowValue);
|
|
132
|
+
return {
|
|
133
|
+
status: 200,
|
|
134
|
+
body: {
|
|
126
135
|
leadId,
|
|
127
136
|
measured: true,
|
|
128
137
|
lastRunAt,
|
|
@@ -130,8 +139,13 @@ export function registerLastRunRoute(app, deps) {
|
|
|
130
139
|
staleness: evaluation.staleness,
|
|
131
140
|
thresholdMs: evaluation.thresholdMs,
|
|
132
141
|
cadenceMs: interval.ms,
|
|
133
|
-
}
|
|
134
|
-
|
|
142
|
+
},
|
|
143
|
+
};
|
|
144
|
+
}
|
|
145
|
+
export function registerLastRunRoute(app, deps) {
|
|
146
|
+
app.get("/api/external/org/leads/:leadId/last-run", async (c) => {
|
|
147
|
+
const result = lastRunCore(deps, c.req.param("leadId"));
|
|
148
|
+
return c.json(result.body, result.status);
|
|
135
149
|
});
|
|
136
150
|
}
|
|
137
151
|
//# sourceMappingURL=last-run.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"last-run.js","sourceRoot":"","sources":["../../../src/external/org/last-run.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AAGH,OAAO,EAAE,YAAY,EAAE,SAAS,EAAE,MAAM,SAAS,CAAC;AAClD,OAAO,IAAI,MAAM,WAAW,CAAC;AAE7B,OAAO,EAAE,UAAU,EAAE,MAAM,eAAe,CAAC;AAC3C,OAAO,EAAE,aAAa,EAAE,MAAM,0BAA0B,CAAC;AACzD,OAAO,EAAE,eAAe,EAA0B,MAAM,uBAAuB,CAAC;AAChF,OAAO,EAAE,cAAc,EAAE,iBAAiB,
|
|
1
|
+
{"version":3,"file":"last-run.js","sourceRoot":"","sources":["../../../src/external/org/last-run.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { openSync } from "node:fs";
|
|
2
|
+
export type LeadDocReadResult = {
|
|
3
|
+
status: 200;
|
|
4
|
+
kind: "file";
|
|
5
|
+
body: Buffer;
|
|
6
|
+
headers: Record<string, string>;
|
|
7
|
+
} | {
|
|
8
|
+
status: 400 | 403 | 404 | 500;
|
|
9
|
+
kind: "json";
|
|
10
|
+
body: {
|
|
11
|
+
error: string;
|
|
12
|
+
detail?: string;
|
|
13
|
+
};
|
|
14
|
+
};
|
|
15
|
+
export interface LeadDocReadDeps {
|
|
16
|
+
leadsRoot: string;
|
|
17
|
+
openSync?: typeof openSync;
|
|
18
|
+
}
|
|
19
|
+
/** Pure core — reads `<leadsRoot>/<leadId>/learnings.md`. */
|
|
20
|
+
export declare function leadLearningsReadCore(deps: LeadDocReadDeps, leadId: string | undefined): LeadDocReadResult;
|
|
21
|
+
//# sourceMappingURL=lead-doc-read.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"lead-doc-read.d.ts","sourceRoot":"","sources":["../../../src/external/org/lead-doc-read.ts"],"names":[],"mappings":"AAuBA,OAAO,EAAkD,QAAQ,EAAgB,MAAM,SAAS,CAAC;AAOjG,MAAM,MAAM,iBAAiB,GACzB;IAAE,MAAM,EAAE,GAAG,CAAC;IAAC,IAAI,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;CAAE,GAC5E;IAAE,MAAM,EAAE,GAAG,GAAG,GAAG,GAAG,GAAG,GAAG,GAAG,CAAC;IAAC,IAAI,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,MAAM,CAAC,EAAE,MAAM,CAAA;KAAE,CAAA;CAAE,CAAC;AAE9F,MAAM,WAAW,eAAe;IAC9B,SAAS,EAAE,MAAM,CAAC;IAClB,QAAQ,CAAC,EAAE,OAAO,QAAQ,CAAC;CAC5B;AAID,6DAA6D;AAC7D,wBAAgB,qBAAqB,CAAC,IAAI,EAAE,eAAe,EAAE,MAAM,EAAE,MAAM,GAAG,SAAS,GAAG,iBAAiB,CAsD1G"}
|