@mcsherrylabs/evolver-mcp 0.12.0 → 0.14.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/dist/application-signer.d.ts +16 -0
- package/dist/application-signer.d.ts.map +1 -0
- package/dist/application-signer.js +55 -0
- package/dist/application-signer.js.map +1 -0
- package/dist/approved-request.d.ts +30 -0
- package/dist/approved-request.d.ts.map +1 -0
- package/dist/approved-request.js +167 -0
- package/dist/approved-request.js.map +1 -0
- package/dist/attachment-tools.d.ts +3 -0
- package/dist/attachment-tools.d.ts.map +1 -0
- package/dist/attachment-tools.js +32 -0
- package/dist/attachment-tools.js.map +1 -0
- package/dist/attachments.d.ts +41 -0
- package/dist/attachments.d.ts.map +1 -0
- package/dist/attachments.js +458 -0
- package/dist/attachments.js.map +1 -0
- package/dist/content-file-tools.d.ts +3 -0
- package/dist/content-file-tools.d.ts.map +1 -0
- package/dist/content-file-tools.js +67 -0
- package/dist/content-file-tools.js.map +1 -0
- package/dist/content-files.d.ts +32 -0
- package/dist/content-files.d.ts.map +1 -0
- package/dist/content-files.js +79 -0
- package/dist/content-files.js.map +1 -0
- package/dist/content-ranges.d.ts +41 -0
- package/dist/content-ranges.d.ts.map +1 -0
- package/dist/content-ranges.js +75 -0
- package/dist/content-ranges.js.map +1 -0
- package/dist/files.d.ts +60 -0
- package/dist/files.d.ts.map +1 -0
- package/dist/files.js +355 -0
- package/dist/files.js.map +1 -0
- package/dist/group-crypto-tools.d.ts +3 -0
- package/dist/group-crypto-tools.d.ts.map +1 -0
- package/dist/group-crypto-tools.js +47 -0
- package/dist/group-crypto-tools.js.map +1 -0
- package/dist/group-crypto.d.ts +24 -0
- package/dist/group-crypto.d.ts.map +1 -0
- package/dist/group-crypto.js +135 -0
- package/dist/group-crypto.js.map +1 -0
- package/dist/group-key-provider.d.ts +30 -0
- package/dist/group-key-provider.d.ts.map +1 -0
- package/dist/group-key-provider.js +72 -0
- package/dist/group-key-provider.js.map +1 -0
- package/dist/group-keys.d.ts +34 -0
- package/dist/group-keys.d.ts.map +1 -0
- package/dist/group-keys.js +88 -0
- package/dist/group-keys.js.map +1 -0
- package/dist/http-profiles.d.ts +47 -0
- package/dist/http-profiles.d.ts.map +1 -0
- package/dist/http-profiles.js +161 -0
- package/dist/http-profiles.js.map +1 -0
- package/dist/human-approval.d.ts +14 -0
- package/dist/human-approval.d.ts.map +1 -0
- package/dist/human-approval.js +34 -0
- package/dist/human-approval.js.map +1 -0
- package/dist/inbox.d.ts +1 -1
- package/dist/inbox.d.ts.map +1 -1
- package/dist/inbox.js +1 -1
- package/dist/inbox.js.map +1 -1
- package/dist/index.d.ts +11 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +94 -41
- package/dist/index.js.map +1 -1
- package/dist/login-profiles.d.ts +25 -0
- package/dist/login-profiles.d.ts.map +1 -0
- package/dist/login-profiles.js +77 -0
- package/dist/login-profiles.js.map +1 -0
- package/dist/login-tools.d.ts +3 -0
- package/dist/login-tools.d.ts.map +1 -0
- package/dist/login-tools.js +27 -0
- package/dist/login-tools.js.map +1 -0
- package/dist/login.d.ts +36 -0
- package/dist/login.d.ts.map +1 -0
- package/dist/login.js +144 -0
- package/dist/login.js.map +1 -0
- package/dist/mcp.d.ts +2 -2
- package/dist/mcp.d.ts.map +1 -1
- package/dist/p2p-tools.d.ts +3 -0
- package/dist/p2p-tools.d.ts.map +1 -0
- package/dist/p2p-tools.js +52 -0
- package/dist/p2p-tools.js.map +1 -0
- package/dist/p2p.d.ts +67 -0
- package/dist/p2p.d.ts.map +1 -0
- package/dist/p2p.js +326 -0
- package/dist/p2p.js.map +1 -0
- package/dist/processor.d.ts +1 -1
- package/dist/processor.d.ts.map +1 -1
- package/dist/processor.js +1 -1
- package/dist/processor.js.map +1 -1
- package/dist/proof-attempts.d.ts +19 -0
- package/dist/proof-attempts.d.ts.map +1 -0
- package/dist/proof-attempts.js +30 -0
- package/dist/proof-attempts.js.map +1 -0
- package/dist/signed-http-tools.d.ts +3 -0
- package/dist/signed-http-tools.d.ts.map +1 -0
- package/dist/signed-http-tools.js +33 -0
- package/dist/signed-http-tools.js.map +1 -0
- package/dist/signed-http.d.ts +30 -0
- package/dist/signed-http.d.ts.map +1 -0
- package/dist/signed-http.js +114 -0
- package/dist/signed-http.js.map +1 -0
- package/dist/tools.d.ts +29 -4
- package/dist/tools.d.ts.map +1 -1
- package/dist/tools.js +24 -10
- package/dist/tools.js.map +1 -1
- package/dist/transaction-interchange-tools.d.ts +3 -0
- package/dist/transaction-interchange-tools.d.ts.map +1 -0
- package/dist/transaction-interchange-tools.js +41 -0
- package/dist/transaction-interchange-tools.js.map +1 -0
- package/dist/transaction-interchange.d.ts +141 -0
- package/dist/transaction-interchange.d.ts.map +1 -0
- package/dist/transaction-interchange.js +502 -0
- package/dist/transaction-interchange.js.map +1 -0
- package/dist/transaction-policy.d.ts +32 -0
- package/dist/transaction-policy.d.ts.map +1 -0
- package/dist/transaction-policy.js +89 -0
- package/dist/transaction-policy.js.map +1 -0
- package/package.json +7 -18
- package/dist/amount.d.ts +0 -23
- package/dist/amount.d.ts.map +0 -1
- package/dist/amount.js +0 -53
- package/dist/amount.js.map +0 -1
- package/dist/channel.d.ts +0 -77
- package/dist/channel.d.ts.map +0 -1
- package/dist/channel.js +0 -287
- package/dist/channel.js.map +0 -1
- package/dist/claim.d.ts +0 -48
- package/dist/claim.d.ts.map +0 -1
- package/dist/claim.js +0 -81
- package/dist/claim.js.map +0 -1
- package/dist/content.d.ts +0 -81
- package/dist/content.d.ts.map +0 -1
- package/dist/content.js +0 -215
- package/dist/content.js.map +0 -1
- package/dist/group-ownership.d.ts +0 -78
- package/dist/group-ownership.d.ts.map +0 -1
- package/dist/group-ownership.js +0 -325
- package/dist/group-ownership.js.map +0 -1
- package/dist/group.d.ts +0 -60
- package/dist/group.d.ts.map +0 -1
- package/dist/group.js +0 -204
- package/dist/group.js.map +0 -1
- package/dist/identity.d.ts +0 -87
- package/dist/identity.d.ts.map +0 -1
- package/dist/identity.js +0 -236
- package/dist/identity.js.map +0 -1
- package/dist/read.d.ts +0 -252
- package/dist/read.d.ts.map +0 -1
- package/dist/read.js +0 -320
- package/dist/read.js.map +0 -1
- package/dist/result.d.ts +0 -110
- package/dist/result.d.ts.map +0 -1
- package/dist/result.js +0 -97
- package/dist/result.js.map +0 -1
- package/dist/signer.d.ts +0 -37
- package/dist/signer.d.ts.map +0 -1
- package/dist/signer.js +0 -76
- package/dist/signer.js.map +0 -1
- package/dist/write.d.ts +0 -208
- package/dist/write.d.ts.map +0 -1
- package/dist/write.js +0 -385
- package/dist/write.js.map +0 -1
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
// File-backed content is an explicit host-filesystem capability, never a model-context blob.
|
|
2
|
+
// The SDK still owns HTTP and streaming transport. The file layer owns byte bounds, incremental
|
|
3
|
+
// hashing and atomic output; neither this module nor the tools implement the node protocol.
|
|
4
|
+
import { parseAccess, OPEN_NOTE, RESTRICTED_NOTE } from "@mcsherrylabs/evolver-core";
|
|
5
|
+
import { ContentHttpError } from "@mcsherrylabs/evolver-client";
|
|
6
|
+
import { FileAccessError, fileName } from "./files.js";
|
|
7
|
+
/** Deliberately do not echo exceptions/response text/host paths: a server can reflect content,
|
|
8
|
+
* and some TLS errors include certificate details. A completed HTTP refusal is not transport loss. */
|
|
9
|
+
export function contentFileFailure(e, url, operation) {
|
|
10
|
+
const aftermath = operation === "publish" ? "upload storage is unconfirmed" : "no completed output metadata returned";
|
|
11
|
+
if (e instanceof FileAccessError && e.code === "publication_unknown")
|
|
12
|
+
return { kind: "partial", what: operation, cause: "file_publication_unknown; target may exist or have been visible; this is NOT a no-write refusal. Do not retry or automatically delete the final target; operator inspection is required." +
|
|
13
|
+
(e.cleanupWarning ? " staging_cleanup_failed; protected staging may also remain." : "") };
|
|
14
|
+
if (e instanceof FileAccessError && e.code !== "transfer_failed")
|
|
15
|
+
return { kind: "partial", what: operation, cause: `file_${e.code}; ${aftermath}` +
|
|
16
|
+
(e.cleanupWarning ? "; staging_cleanup_failed; operator inspection of protected staging may be required" : "") };
|
|
17
|
+
if (e instanceof Error && e.name === "AbortError")
|
|
18
|
+
return { kind: "partial", what: operation, cause: `cancelled; ${aftermath}` };
|
|
19
|
+
const message = e instanceof Error ? e.message : "";
|
|
20
|
+
const status = e instanceof ContentHttpError ? String(e.status) :
|
|
21
|
+
/^(?:postContent(?:Stream)?|getContentStream): HTTP ([1-5][0-9]{2})\b/.exec(message)?.[1];
|
|
22
|
+
if (status)
|
|
23
|
+
return { kind: "partial", what: operation,
|
|
24
|
+
cause: `content_http_${status}; not complete, not retried or republished open; ${aftermath}` };
|
|
25
|
+
return { kind: "unreachable", chain: url, cause: `content_transfer_failed; ${aftermath}` +
|
|
26
|
+
(e instanceof FileAccessError && e.cleanupWarning ? "; staging_cleanup_failed; operator inspection of protected staging may be required" : "") };
|
|
27
|
+
}
|
|
28
|
+
export const contentFileDisabled = () => ({ kind: "misconfigured", what: "file content access is disabled",
|
|
29
|
+
fix: "operator must opt in with EVOLVER_FILE_ROOT (dedicated server-local0700 directory) and reload; client roots do not authorize host access" });
|
|
30
|
+
export async function publishFile(deps, req, signal) {
|
|
31
|
+
// Share the INLINE ACL parser; do not trim/filter a malformed grant into open publication.
|
|
32
|
+
const access = parseAccess(req.access, req.authorizedPrincipals);
|
|
33
|
+
if ("kind" in access)
|
|
34
|
+
return access;
|
|
35
|
+
if (!deps)
|
|
36
|
+
return contentFileDisabled();
|
|
37
|
+
try {
|
|
38
|
+
const file = fileName(req.file);
|
|
39
|
+
return await deps.files.transfer(files => files.withInput(file, async (input) => {
|
|
40
|
+
const result = await deps.client.postContentStream(input.stream, { size: input.size, signal: files.signal,
|
|
41
|
+
...(access.mode === "restricted" ? { authorizedPrincipals: access.principals } : {}) });
|
|
42
|
+
const metadata = input.metadata();
|
|
43
|
+
if (result.size !== metadata.size || !/^[0-9a-f]{64}$/.test(result.key))
|
|
44
|
+
throw new FileAccessError("incomplete");
|
|
45
|
+
return { kind: "ok", value: { ...metadata, key: result.key, access: access.mode,
|
|
46
|
+
...(access.mode === "restricted" ? { authorizedPrincipals: access.principals } : {}),
|
|
47
|
+
note: access.mode === "restricted" ? RESTRICTED_NOTE : OPEN_NOTE } };
|
|
48
|
+
}), signal);
|
|
49
|
+
}
|
|
50
|
+
catch (e) {
|
|
51
|
+
return contentFileFailure(e, deps.contentUrl, "publish");
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
export async function fetchFile(deps, req, signal) {
|
|
55
|
+
if (!deps)
|
|
56
|
+
return contentFileDisabled();
|
|
57
|
+
if (typeof req.key !== "string" || !/^[0-9a-f]{64}$/.test(req.key))
|
|
58
|
+
return { kind: "partial", what: "key", cause: "a content key must be exactly64 lowercase hex characters" };
|
|
59
|
+
const key = req.key;
|
|
60
|
+
try {
|
|
61
|
+
const file = fileName(req.file);
|
|
62
|
+
return await deps.files.transfer(async (files) => {
|
|
63
|
+
const result = await deps.client.getContentStream(key, { signal: files.signal });
|
|
64
|
+
if (result === null)
|
|
65
|
+
return { kind: "ok", value: null };
|
|
66
|
+
// A full-file tool never silently treats a partial response as the complete object.
|
|
67
|
+
if (result.status !== 200 || result.range !== null) {
|
|
68
|
+
await result.stream.cancel().catch(() => undefined);
|
|
69
|
+
throw new FileAccessError("incomplete");
|
|
70
|
+
}
|
|
71
|
+
const metadata = await files.writeNew(file, result.stream, result.size);
|
|
72
|
+
return { kind: "ok", value: { ...metadata, key } };
|
|
73
|
+
}, signal);
|
|
74
|
+
}
|
|
75
|
+
catch (e) {
|
|
76
|
+
return contentFileFailure(e, deps.contentUrl, "download");
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
//# sourceMappingURL=content-files.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"content-files.js","sourceRoot":"","sources":["../src/content-files.ts"],"names":[],"mappings":"AAAA,6FAA6F;AAC7F,gGAAgG;AAChG,4FAA4F;AAC5F,OAAO,EAAE,WAAW,EAAE,SAAS,EAAE,eAAe,EAAE,MAAM,4BAA4B,CAAC;AACrF,OAAO,EAAE,gBAAgB,EAAE,MAAM,8BAA8B,CAAC;AAEhE,OAAO,EAAE,eAAe,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AASvD;sGACsG;AACtG,MAAM,UAAU,kBAAkB,CAAC,CAAU,EAAE,GAAW,EAAE,SAA2C;IACrG,MAAM,SAAS,GAAG,SAAS,KAAK,SAAS,CAAC,CAAC,CAAC,+BAA+B,CAAC,CAAC,CAAC,uCAAuC,CAAC;IACtH,IAAI,CAAC,YAAY,eAAe,IAAI,CAAC,CAAC,IAAI,KAAK,qBAAqB;QAClE,OAAO,EAAE,IAAI,EAAE,SAAS,EAAE,IAAI,EAAE,SAAS,EAAE,KAAK,EAAE,0LAA0L;gBAC1O,CAAC,CAAC,CAAC,cAAc,CAAC,CAAC,CAAC,6DAA6D,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC;IAC9F,IAAI,CAAC,YAAY,eAAe,IAAI,CAAC,CAAC,IAAI,KAAK,iBAAiB;QAC9D,OAAO,EAAE,IAAI,EAAE,SAAS,EAAE,IAAI,EAAE,SAAS,EAAE,KAAK,EAAE,QAAQ,CAAC,CAAC,IAAI,KAAK,SAAS,EAAE;gBAC9E,CAAC,CAAC,CAAC,cAAc,CAAC,CAAC,CAAC,oFAAoF,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC;IACrH,IAAI,CAAC,YAAY,KAAK,IAAI,CAAC,CAAC,IAAI,KAAK,YAAY;QAC/C,OAAO,EAAE,IAAI,EAAE,SAAS,EAAE,IAAI,EAAE,SAAS,EAAE,KAAK,EAAE,cAAc,SAAS,EAAE,EAAE,CAAC;IAChF,MAAM,OAAO,GAAG,CAAC,YAAY,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,CAAC;IACpD,MAAM,MAAM,GAAG,CAAC,YAAY,gBAAgB,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC;QAC/D,sEAAsE,CAAC,IAAI,CAAC,OAAO,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC;IAC5F,IAAI,MAAM;QAAE,OAAO,EAAE,IAAI,EAAE,SAAS,EAAE,IAAI,EAAE,SAAS;YACnD,KAAK,EAAE,gBAAgB,MAAM,oDAAoD,SAAS,EAAE,EAAE,CAAC;IACjG,OAAO,EAAE,IAAI,EAAE,aAAa,EAAE,KAAK,EAAE,GAAG,EAAE,KAAK,EAAE,4BAA4B,SAAS,EAAE;YACtF,CAAC,CAAC,YAAY,eAAe,IAAI,CAAC,CAAC,cAAc,CAAC,CAAC,CAAC,oFAAoF,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC;AACrJ,CAAC;AACD,MAAM,CAAC,MAAM,mBAAmB,GAAG,GAAsB,EAAE,CAAC,CAAC,EAAE,IAAI,EAAE,eAAe,EAAE,IAAI,EAAE,iCAAiC;IAC3H,GAAG,EAAE,0IAA0I,EAAE,CAAC,CAAC;AAErJ,MAAM,CAAC,KAAK,UAAU,WAAW,CAAC,IAAiC,EAAE,GAEpE,EAAE,MAAoB;IACrB,2FAA2F;IAC3F,MAAM,MAAM,GAAG,WAAW,CAAC,GAAG,CAAC,MAAM,EAAE,GAAG,CAAC,oBAAoB,CAAC,CAAC;IACjE,IAAI,MAAM,IAAI,MAAM;QAAE,OAAO,MAAM,CAAC;IACpC,IAAI,CAAC,IAAI;QAAE,OAAO,mBAAmB,EAAE,CAAC;IACxC,IAAI,CAAC;QACH,MAAM,IAAI,GAAG,QAAQ,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;QAChC,OAAO,MAAM,IAAI,CAAC,KAAK,CAAC,QAAQ,CAAC,KAAK,CAAC,EAAE,CAAC,KAAK,CAAC,SAAS,CAAC,IAAI,EAAE,KAAK,EAAC,KAAK,EAAC,EAAE;YAC5E,MAAM,MAAM,GAAG,MAAM,IAAI,CAAC,MAAM,CAAC,iBAAiB,CAAC,KAAK,CAAC,MAAM,EAAE,EAAE,IAAI,EAAE,KAAK,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,CAAC,MAAM;gBACvG,GAAG,CAAC,MAAM,CAAC,IAAI,KAAK,YAAY,CAAC,CAAC,CAAC,EAAE,oBAAoB,EAAE,MAAM,CAAC,UAAU,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC;YAC1F,MAAM,QAAQ,GAAG,KAAK,CAAC,QAAQ,EAAE,CAAC;YAClC,IAAI,MAAM,CAAC,IAAI,KAAK,QAAQ,CAAC,IAAI,IAAI,CAAC,gBAAgB,CAAC,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC;gBAAE,MAAM,IAAI,eAAe,CAAC,YAAY,CAAC,CAAC;YACjH,OAAO,EAAE,IAAI,EAAE,IAAI,EAAE,KAAK,EAAE,EAAE,GAAG,QAAQ,EAAE,GAAG,EAAE,MAAM,CAAC,GAAG,EAAE,MAAM,EAAE,MAAM,CAAC,IAAI;oBAC7E,GAAG,CAAC,MAAM,CAAC,IAAI,KAAK,YAAY,CAAC,CAAC,CAAC,EAAE,oBAAoB,EAAE,MAAM,CAAC,UAAU,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;oBACpF,IAAI,EAAE,MAAM,CAAC,IAAI,KAAK,YAAY,CAAC,CAAC,CAAC,eAAe,CAAC,CAAC,CAAC,SAAS,EAAE,EAAE,CAAC;QACzE,CAAC,CAAC,EAAE,MAAM,CAAC,CAAC;IACd,CAAC;IAAC,OAAO,CAAC,EAAE,CAAC;QAAC,OAAO,kBAAkB,CAAC,CAAC,EAAE,IAAI,CAAC,UAAU,EAAE,SAAS,CAAC,CAAC;IAAC,CAAC;AAC3E,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,SAAS,CAAC,IAAiC,EAAE,GAElE,EAAE,MAAoB;IACrB,IAAI,CAAC,IAAI;QAAE,OAAO,mBAAmB,EAAE,CAAC;IACxC,IAAI,OAAO,GAAG,CAAC,GAAG,KAAK,QAAQ,IAAI,CAAC,gBAAgB,CAAC,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC;QAChE,OAAO,EAAE,IAAI,EAAE,SAAS,EAAE,IAAI,EAAE,KAAK,EAAE,KAAK,EAAE,0DAA0D,EAAE,CAAC;IAC7G,MAAM,GAAG,GAAG,GAAG,CAAC,GAAG,CAAC;IACpB,IAAI,CAAC;QACH,MAAM,IAAI,GAAG,QAAQ,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;QAChC,OAAO,MAAM,IAAI,CAAC,KAAK,CAAC,QAAQ,CAAC,KAAK,EAAC,KAAK,EAAC,EAAE;YAC7C,MAAM,MAAM,GAAG,MAAM,IAAI,CAAC,MAAM,CAAC,gBAAgB,CAAC,GAAG,EAAE,EAAE,MAAM,EAAE,KAAK,CAAC,MAAM,EAAE,CAAC,CAAC;YACjF,IAAI,MAAM,KAAK,IAAI;gBAAE,OAAO,EAAE,IAAI,EAAE,IAAI,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC;YACxD,oFAAoF;YACpF,IAAI,MAAM,CAAC,MAAM,KAAK,GAAG,IAAI,MAAM,CAAC,KAAK,KAAK,IAAI,EAAE,CAAC;gBACnD,MAAM,MAAM,CAAC,MAAM,CAAC,MAAM,EAAE,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,SAAS,CAAC,CAAC;gBACpD,MAAM,IAAI,eAAe,CAAC,YAAY,CAAC,CAAC;YAC1C,CAAC;YACD,MAAM,QAAQ,GAAG,MAAM,KAAK,CAAC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,IAAI,CAAC,CAAC;YACxE,OAAO,EAAE,IAAI,EAAE,IAAI,EAAE,KAAK,EAAE,EAAE,GAAG,QAAQ,EAAE,GAAG,EAAE,EAAE,CAAC;QACrD,CAAC,EAAE,MAAM,CAAC,CAAC;IACb,CAAC;IAAC,OAAO,CAAC,EAAE,CAAC;QAAC,OAAO,kBAAkB,CAAC,CAAC,EAAE,IAAI,CAAC,UAAU,EAAE,UAAU,CAAC,CAAC;IAAC,CAAC;AAC5E,CAAC"}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import type { FileContentDeps } from "./content-files.js";
|
|
2
|
+
import type { FileCleanupWarning } from "./files.js";
|
|
3
|
+
import type { ToolResult } from "@mcsherrylabs/evolver-core";
|
|
4
|
+
export interface ByteInterval {
|
|
5
|
+
readonly from: number;
|
|
6
|
+
readonly toInclusive: number;
|
|
7
|
+
}
|
|
8
|
+
export type FileRangeResult = {
|
|
9
|
+
readonly status: "range";
|
|
10
|
+
readonly key: string;
|
|
11
|
+
readonly file: string;
|
|
12
|
+
readonly size: number;
|
|
13
|
+
readonly sha256: string;
|
|
14
|
+
readonly hashScope: "served-range";
|
|
15
|
+
/** False refers to the ORIGINAL object, even if this interval happens to cover all of it. */
|
|
16
|
+
readonly complete: false;
|
|
17
|
+
readonly rangeComplete: true;
|
|
18
|
+
readonly fileComplete: true;
|
|
19
|
+
readonly requested: ByteInterval;
|
|
20
|
+
readonly served: ByteInterval & {
|
|
21
|
+
readonly totalSize: number;
|
|
22
|
+
};
|
|
23
|
+
readonly clipped: boolean;
|
|
24
|
+
readonly cleanupWarning?: FileCleanupWarning;
|
|
25
|
+
} | {
|
|
26
|
+
readonly status: "unsatisfiable";
|
|
27
|
+
readonly key: string;
|
|
28
|
+
readonly requested: ByteInterval;
|
|
29
|
+
readonly totalSize: number | null;
|
|
30
|
+
readonly complete: false;
|
|
31
|
+
readonly rangeComplete: false;
|
|
32
|
+
readonly fileComplete: false;
|
|
33
|
+
};
|
|
34
|
+
export declare function byteInterval(from: unknown, toInclusive: unknown, maxBytes: number): ByteInterval | ToolResult<never>;
|
|
35
|
+
export declare function fetchRangeFile(deps: FileContentDeps | undefined, req: {
|
|
36
|
+
key?: unknown;
|
|
37
|
+
file?: unknown;
|
|
38
|
+
from?: unknown;
|
|
39
|
+
toInclusive?: unknown;
|
|
40
|
+
}, signal?: AbortSignal): Promise<ToolResult<FileRangeResult | null>>;
|
|
41
|
+
//# sourceMappingURL=content-ranges.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"content-ranges.d.ts","sourceRoot":"","sources":["../src/content-ranges.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,oBAAoB,CAAC;AAE1D,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,YAAY,CAAC;AACrD,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,4BAA4B,CAAC;AAE7D,MAAM,WAAW,YAAY;IAAG,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAA;CAAE;AACrF,MAAM,MAAM,eAAe,GAAG;IAC5B,QAAQ,CAAC,MAAM,EAAE,OAAO,CAAC;IACzB,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IAC5F,QAAQ,CAAC,SAAS,EAAE,cAAc,CAAC;IACnC,6FAA6F;IAC7F,QAAQ,CAAC,QAAQ,EAAE,KAAK,CAAC;IACzB,QAAQ,CAAC,aAAa,EAAE,IAAI,CAAC;IAAC,QAAQ,CAAC,YAAY,EAAE,IAAI,CAAC;IAC1D,QAAQ,CAAC,SAAS,EAAE,YAAY,CAAC;IACjC,QAAQ,CAAC,MAAM,EAAE,YAAY,GAAG;QAAE,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAA;KAAE,CAAC;IAC/D,QAAQ,CAAC,OAAO,EAAE,OAAO,CAAC;IAC1B,QAAQ,CAAC,cAAc,CAAC,EAAE,kBAAkB,CAAC;CAC9C,GAAG;IACF,QAAQ,CAAC,MAAM,EAAE,eAAe,CAAC;IAAC,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,SAAS,EAAE,YAAY,CAAC;IACzF,QAAQ,CAAC,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;IAClC,QAAQ,CAAC,QAAQ,EAAE,KAAK,CAAC;IAAC,QAAQ,CAAC,aAAa,EAAE,KAAK,CAAC;IAAC,QAAQ,CAAC,YAAY,EAAE,KAAK,CAAC;CACvF,CAAC;AAGF,wBAAgB,YAAY,CAAC,IAAI,EAAE,OAAO,EAAE,WAAW,EAAE,OAAO,EAAE,QAAQ,EAAE,MAAM,GAAG,YAAY,GAAG,UAAU,CAAC,KAAK,CAAC,CAOpH;AAWD,wBAAsB,cAAc,CAAC,IAAI,EAAE,eAAe,GAAG,SAAS,EAAE,GAAG,EAAE;IAC3E,GAAG,CAAC,EAAE,OAAO,CAAC;IAAC,IAAI,CAAC,EAAE,OAAO,CAAC;IAAC,IAAI,CAAC,EAAE,OAAO,CAAC;IAAC,WAAW,CAAC,EAAE,OAAO,CAAC;CACtE,EAAE,MAAM,CAAC,EAAE,WAAW,GAAG,OAAO,CAAC,UAAU,CAAC,eAAe,GAAG,IAAI,CAAC,CAAC,CAyCpE"}
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
// Inclusive range semantics sit above the SDK's truthful HTTP metadata and reuse #11's
|
|
2
|
+
// exact-byte, atomic no-overwrite file boundary. No headers, HTTP request or wire encoder here.
|
|
3
|
+
import { contentFileDisabled, contentFileFailure } from "./content-files.js";
|
|
4
|
+
import { fileName } from "./files.js";
|
|
5
|
+
const nat = (value) => typeof value === "number" && Number.isSafeInteger(value) && value >= 0;
|
|
6
|
+
export function byteInterval(from, toInclusive, maxBytes) {
|
|
7
|
+
if (!nat(from) || !nat(toInclusive) || from > toInclusive)
|
|
8
|
+
return { kind: "partial", what: "range", cause: "invalid_range; inclusive bounds must be non-negative safe integers with from <= toInclusive" };
|
|
9
|
+
const count = toInclusive - from + 1;
|
|
10
|
+
if (!Number.isSafeInteger(count) || count > maxBytes)
|
|
11
|
+
return { kind: "partial", what: "range", cause: `range_too_large; requested inclusive interval must fit the ${maxBytes}-byte file bound` };
|
|
12
|
+
return { from, toInclusive };
|
|
13
|
+
}
|
|
14
|
+
/** SDK0.22 prerequisite: structured416, never regex-inferred from a transport exception.
|
|
15
|
+
* Unknown total stays null; malformed metadata is not an unsatisfiable interval result. */
|
|
16
|
+
function unsatisfiable(e) {
|
|
17
|
+
if (!(e instanceof Error) || e.name !== "ContentRangeError")
|
|
18
|
+
return undefined;
|
|
19
|
+
const value = e;
|
|
20
|
+
if (value.status !== 416 || (value.totalSize !== null && !nat(value.totalSize)))
|
|
21
|
+
return undefined;
|
|
22
|
+
return { totalSize: value.totalSize };
|
|
23
|
+
}
|
|
24
|
+
export async function fetchRangeFile(deps, req, signal) {
|
|
25
|
+
if (!deps)
|
|
26
|
+
return contentFileDisabled();
|
|
27
|
+
if (typeof req.key !== "string" || !/^[0-9a-f]{64}$/.test(req.key))
|
|
28
|
+
return { kind: "partial", what: "key", cause: "a content key must be exactly 64 lowercase hex characters" };
|
|
29
|
+
const key = req.key;
|
|
30
|
+
const requested = byteInterval(req.from, req.toInclusive, deps.files.maxBytes);
|
|
31
|
+
if ("kind" in requested)
|
|
32
|
+
return requested;
|
|
33
|
+
try {
|
|
34
|
+
const file = fileName(req.file);
|
|
35
|
+
return await deps.files.transfer(async (files) => {
|
|
36
|
+
let result;
|
|
37
|
+
try {
|
|
38
|
+
result = await deps.client.getContentStream(key, { range: requested, signal: files.signal });
|
|
39
|
+
}
|
|
40
|
+
catch (e) {
|
|
41
|
+
const refusal = unsatisfiable(e);
|
|
42
|
+
if (!refusal)
|
|
43
|
+
throw e;
|
|
44
|
+
return { kind: "ok", value: { status: "unsatisfiable", key, requested, totalSize: refusal.totalSize,
|
|
45
|
+
complete: false, rangeComplete: false, fileComplete: false } };
|
|
46
|
+
}
|
|
47
|
+
if (result === null)
|
|
48
|
+
return { kind: "ok", value: null };
|
|
49
|
+
if (result.status === 200) {
|
|
50
|
+
await result.stream.cancel().catch(() => undefined);
|
|
51
|
+
return { kind: "partial", what: "range", cause: "range_ignored; server returned a full object, discarded without writing or retry" };
|
|
52
|
+
}
|
|
53
|
+
const served = result.range;
|
|
54
|
+
// The SDK validates headers. Also refuse a collaborator lacking/corrupting that contract;
|
|
55
|
+
// a stream alone can never prove the node honored the requested range.
|
|
56
|
+
if (result.status !== 206 || !served || !nat(served.from) || !nat(served.toInclusive) ||
|
|
57
|
+
!nat(served.totalSize) || served.from > served.toInclusive || served.toInclusive >= served.totalSize ||
|
|
58
|
+
served.from !== requested.from || served.toInclusive !== Math.min(requested.toInclusive, served.totalSize - 1) ||
|
|
59
|
+
!nat(result.size) || result.size !== served.toInclusive - served.from + 1) {
|
|
60
|
+
await result.stream.cancel().catch(() => undefined);
|
|
61
|
+
return { kind: "partial", what: "range", cause: "range_metadata_invalid; validated SDK206/served interval/total/byte-count metadata is required; no output" };
|
|
62
|
+
}
|
|
63
|
+
const metadata = await files.writeNew(file, result.stream, result.size);
|
|
64
|
+
return { kind: "ok", value: { status: "range", key, file: metadata.file, size: metadata.size,
|
|
65
|
+
sha256: metadata.sha256, hashScope: "served-range", complete: false, rangeComplete: true, fileComplete: true,
|
|
66
|
+
requested, served: { from: served.from, toInclusive: served.toInclusive, totalSize: served.totalSize },
|
|
67
|
+
clipped: served.toInclusive !== requested.toInclusive,
|
|
68
|
+
...(metadata.cleanupWarning ? { cleanupWarning: metadata.cleanupWarning } : {}) } };
|
|
69
|
+
}, signal);
|
|
70
|
+
}
|
|
71
|
+
catch (e) {
|
|
72
|
+
return contentFileFailure(e, deps.contentUrl, "range");
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
//# sourceMappingURL=content-ranges.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"content-ranges.js","sourceRoot":"","sources":["../src/content-ranges.ts"],"names":[],"mappings":"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"}
|
package/dist/files.d.ts
ADDED
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
export declare const FILE_DEFAULT_MAX_BYTES: number;
|
|
2
|
+
export declare const FILE_HARD_MAX_BYTES: number;
|
|
3
|
+
export declare const FILE_CHUNK_BYTES = 65536;
|
|
4
|
+
export type FileFailure = "disabled" | "invalid_name" | "unsafe_root" | "unsafe_file" | "not_found" | "not_regular" | "too_large" | "changed" | "exists" | "busy" | "cancelled" | "incomplete" | "transfer_failed" | "commit_unconfirmed" | "publication_unknown" | "io";
|
|
5
|
+
export type FileCleanupWarning = "staging_cleanup_failed";
|
|
6
|
+
export declare class FileAccessError extends Error {
|
|
7
|
+
readonly code: FileFailure;
|
|
8
|
+
cleanupWarning?: FileCleanupWarning;
|
|
9
|
+
constructor(code: FileFailure);
|
|
10
|
+
}
|
|
11
|
+
export interface FileMetadata {
|
|
12
|
+
file: string;
|
|
13
|
+
size: number;
|
|
14
|
+
sha256: string;
|
|
15
|
+
complete: true;
|
|
16
|
+
cleanupWarning?: FileCleanupWarning;
|
|
17
|
+
}
|
|
18
|
+
/** Host-only authorization, supplied by a key lease for plaintext output, never caller JSON. */
|
|
19
|
+
export interface FileCommitGuard {
|
|
20
|
+
check(): void;
|
|
21
|
+
commit(): void;
|
|
22
|
+
}
|
|
23
|
+
export interface FileInput {
|
|
24
|
+
readonly size: number;
|
|
25
|
+
readonly stream: ReadableStream<Uint8Array>;
|
|
26
|
+
/** Available only after EOF and stable-descriptor verification. Hashes actual streamed bytes. */
|
|
27
|
+
metadata(): FileMetadata;
|
|
28
|
+
}
|
|
29
|
+
export declare function fileName(value: unknown): string;
|
|
30
|
+
/** One bounded operation. The session permits an input-to-output transformation without
|
|
31
|
+
* reacquiring the root's single transfer slot (#12/#16/#18 reuse the same byte boundary). */
|
|
32
|
+
export declare class FileSession {
|
|
33
|
+
private readonly base;
|
|
34
|
+
readonly maxBytes: number;
|
|
35
|
+
readonly signal: AbortSignal;
|
|
36
|
+
private inputActive;
|
|
37
|
+
private outputActive;
|
|
38
|
+
constructor(base: string, maxBytes: number, signal: AbortSignal);
|
|
39
|
+
withInput<T>(name: unknown, job: (input: FileInput) => Promise<T>): Promise<T>;
|
|
40
|
+
/** Protected staging, incremental digest and no-overwrite atomic publication. Known sizes
|
|
41
|
+
* enforce exact counts; undefined is only for an authenticated transform whose EOF proves
|
|
42
|
+
* completeness, still bounded by maxBytes. Optional host guard spans the asynchronous link.
|
|
43
|
+
* A refusal AFTER linking is commit_unconfirmed, NOT a claim plaintext was never published. */
|
|
44
|
+
writeNew(name: unknown, stream: ReadableStream<Uint8Array>, size: number | undefined, authorization?: FileCommitGuard): Promise<FileMetadata>;
|
|
45
|
+
}
|
|
46
|
+
export declare class FileRoot {
|
|
47
|
+
private readonly handle;
|
|
48
|
+
readonly maxBytes: number;
|
|
49
|
+
private busy;
|
|
50
|
+
private closed;
|
|
51
|
+
private readonly lifetime;
|
|
52
|
+
private pending;
|
|
53
|
+
private constructor();
|
|
54
|
+
static open(path: string, maxBytes?: number, excludedPaths?: readonly string[]): Promise<FileRoot>;
|
|
55
|
+
transfer<T>(job: (files: FileSession) => Promise<T>, signal?: AbortSignal): Promise<T>;
|
|
56
|
+
close(): Promise<void>;
|
|
57
|
+
}
|
|
58
|
+
/** Unconfigured access is absent, not a cwd/home fallback. Invalid opt-in refuses startup. */
|
|
59
|
+
export declare function fileRootFromEnv(env: NodeJS.ProcessEnv, excludedPaths?: readonly string[]): Promise<FileRoot | undefined>;
|
|
60
|
+
//# sourceMappingURL=files.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"files.d.ts","sourceRoot":"","sources":["../src/files.ts"],"names":[],"mappings":"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"}
|
package/dist/files.js
ADDED
|
@@ -0,0 +1,355 @@
|
|
|
1
|
+
// The file capability is explicit, server-local and Linux-only. Pin the DIRECTORY descriptor,
|
|
2
|
+
// then resolve one basename through procfs and use O_NOFOLLOW on the final entry. A realpath
|
|
3
|
+
// check followed by opening the original pathname would reintroduce the symlink/rename race.
|
|
4
|
+
// OS account/process isolation is still required: another process with our UID can read our FDs.
|
|
5
|
+
import { constants } from "node:fs";
|
|
6
|
+
import { open, link, unlink, realpath } from "node:fs/promises";
|
|
7
|
+
import { createHash, randomUUID } from "node:crypto";
|
|
8
|
+
import { isAbsolute, relative, sep } from "node:path";
|
|
9
|
+
export const FILE_DEFAULT_MAX_BYTES = 64 * 1024 * 1024;
|
|
10
|
+
export const FILE_HARD_MAX_BYTES = 1024 * 1024 * 1024;
|
|
11
|
+
export const FILE_CHUNK_BYTES = 65_536;
|
|
12
|
+
export class FileAccessError extends Error {
|
|
13
|
+
code;
|
|
14
|
+
cleanupWarning;
|
|
15
|
+
constructor(code) {
|
|
16
|
+
super(`file access: ${code}`);
|
|
17
|
+
this.code = code;
|
|
18
|
+
this.name = "FileAccessError";
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
function checkSignal(signal) { if (signal.aborted)
|
|
22
|
+
throw new FileAccessError("cancelled"); }
|
|
23
|
+
export function fileName(value) {
|
|
24
|
+
if (typeof value !== "string" || !/^[A-Za-z0-9][A-Za-z0-9._-]{0,127}$/.test(value))
|
|
25
|
+
throw new FileAccessError("invalid_name");
|
|
26
|
+
return value;
|
|
27
|
+
}
|
|
28
|
+
function fileError(e) {
|
|
29
|
+
if (e instanceof FileAccessError)
|
|
30
|
+
return e;
|
|
31
|
+
const code = e && typeof e === "object" && "code" in e ? e.code : undefined;
|
|
32
|
+
return new FileAccessError(code === "ENOENT" ? "not_found" : code === "EEXIST" ? "exists" :
|
|
33
|
+
code === "ELOOP" ? "unsafe_file" : "io");
|
|
34
|
+
}
|
|
35
|
+
function checkSize(size, max) {
|
|
36
|
+
if (!Number.isSafeInteger(size) || size < 0)
|
|
37
|
+
throw new FileAccessError("incomplete");
|
|
38
|
+
if (size > max)
|
|
39
|
+
throw new FileAccessError("too_large");
|
|
40
|
+
}
|
|
41
|
+
/** One bounded operation. The session permits an input-to-output transformation without
|
|
42
|
+
* reacquiring the root's single transfer slot (#12/#16/#18 reuse the same byte boundary). */
|
|
43
|
+
export class FileSession {
|
|
44
|
+
base;
|
|
45
|
+
maxBytes;
|
|
46
|
+
signal;
|
|
47
|
+
inputActive = false;
|
|
48
|
+
outputActive = false;
|
|
49
|
+
constructor(base, maxBytes, signal) {
|
|
50
|
+
this.base = base;
|
|
51
|
+
this.maxBytes = maxBytes;
|
|
52
|
+
this.signal = signal;
|
|
53
|
+
}
|
|
54
|
+
async withInput(name, job) {
|
|
55
|
+
const file = fileName(name);
|
|
56
|
+
checkSignal(this.signal);
|
|
57
|
+
if (this.inputActive)
|
|
58
|
+
throw new FileAccessError("busy");
|
|
59
|
+
this.inputActive = true;
|
|
60
|
+
let fh;
|
|
61
|
+
let stream;
|
|
62
|
+
try {
|
|
63
|
+
// NONBLOCK prevents a FIFO open from hanging before fstat can refuse it.
|
|
64
|
+
try {
|
|
65
|
+
fh = await open(`${this.base}/${file}`, constants.O_RDONLY | constants.O_NOFOLLOW | constants.O_NONBLOCK);
|
|
66
|
+
}
|
|
67
|
+
catch (e) {
|
|
68
|
+
throw fileError(e);
|
|
69
|
+
}
|
|
70
|
+
const before = await fh.stat({ bigint: true });
|
|
71
|
+
if (!before.isFile())
|
|
72
|
+
throw new FileAccessError("not_regular");
|
|
73
|
+
if (before.nlink !== 1n || before.uid !== BigInt(process.getuid()))
|
|
74
|
+
throw new FileAccessError("unsafe_file");
|
|
75
|
+
if (before.size > BigInt(this.maxBytes))
|
|
76
|
+
throw new FileAccessError("too_large");
|
|
77
|
+
const size = Number(before.size);
|
|
78
|
+
checkSize(size, this.maxBytes);
|
|
79
|
+
let seen = 0;
|
|
80
|
+
let metadata;
|
|
81
|
+
const hash = createHash("sha256");
|
|
82
|
+
const handle = fh;
|
|
83
|
+
const signal = this.signal;
|
|
84
|
+
stream = new ReadableStream({
|
|
85
|
+
async pull(ctrl) {
|
|
86
|
+
try {
|
|
87
|
+
checkSignal(signal);
|
|
88
|
+
// Read one extra byte to detect growth. Never enqueue bytes beyond declared size.
|
|
89
|
+
const buf = Buffer.allocUnsafe(Math.min(FILE_CHUNK_BYTES, size - seen + 1));
|
|
90
|
+
const { bytesRead } = await handle.read(buf, 0, buf.length, null);
|
|
91
|
+
checkSignal(signal);
|
|
92
|
+
if (bytesRead > size - seen)
|
|
93
|
+
throw new FileAccessError("changed");
|
|
94
|
+
if (bytesRead === 0) {
|
|
95
|
+
const after = await handle.stat({ bigint: true });
|
|
96
|
+
if (seen !== size || before.size !== after.size || before.mtimeNs !== after.mtimeNs ||
|
|
97
|
+
before.ctimeNs !== after.ctimeNs || after.nlink !== 1n)
|
|
98
|
+
throw new FileAccessError("changed");
|
|
99
|
+
metadata = { file, size, sha256: hash.digest("hex"), complete: true };
|
|
100
|
+
ctrl.close();
|
|
101
|
+
return;
|
|
102
|
+
}
|
|
103
|
+
const bytes = buf.subarray(0, bytesRead);
|
|
104
|
+
seen += bytesRead;
|
|
105
|
+
hash.update(bytes);
|
|
106
|
+
ctrl.enqueue(bytes);
|
|
107
|
+
}
|
|
108
|
+
catch (e) {
|
|
109
|
+
ctrl.error(fileError(e));
|
|
110
|
+
}
|
|
111
|
+
},
|
|
112
|
+
}, { highWaterMark: 0 });
|
|
113
|
+
const result = await job({ size, stream, metadata() {
|
|
114
|
+
if (!metadata)
|
|
115
|
+
throw new FileAccessError("incomplete");
|
|
116
|
+
return metadata;
|
|
117
|
+
} });
|
|
118
|
+
checkSignal(this.signal);
|
|
119
|
+
return result;
|
|
120
|
+
}
|
|
121
|
+
finally {
|
|
122
|
+
// Locked streams are owned by the callback/client; it must honor signal and release them.
|
|
123
|
+
await stream?.cancel().catch(() => undefined);
|
|
124
|
+
await fh?.close();
|
|
125
|
+
this.inputActive = false;
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
/** Protected staging, incremental digest and no-overwrite atomic publication. Known sizes
|
|
129
|
+
* enforce exact counts; undefined is only for an authenticated transform whose EOF proves
|
|
130
|
+
* completeness, still bounded by maxBytes. Optional host guard spans the asynchronous link.
|
|
131
|
+
* A refusal AFTER linking is commit_unconfirmed, NOT a claim plaintext was never published. */
|
|
132
|
+
async writeNew(name, stream, size, authorization) {
|
|
133
|
+
let fh;
|
|
134
|
+
let reader;
|
|
135
|
+
let temp;
|
|
136
|
+
let claimed = false;
|
|
137
|
+
let completed;
|
|
138
|
+
let failure;
|
|
139
|
+
try {
|
|
140
|
+
const file = fileName(name);
|
|
141
|
+
if (size !== undefined)
|
|
142
|
+
checkSize(size, this.maxBytes);
|
|
143
|
+
const limit = size ?? this.maxBytes;
|
|
144
|
+
const check = () => { checkSignal(this.signal); authorization?.check(); };
|
|
145
|
+
check();
|
|
146
|
+
if (this.outputActive)
|
|
147
|
+
throw new FileAccessError("busy");
|
|
148
|
+
this.outputActive = true;
|
|
149
|
+
claimed = true;
|
|
150
|
+
temp = `${this.base}/.partial-${randomUUID()}`;
|
|
151
|
+
fh = await open(temp, constants.O_WRONLY | constants.O_CREAT | constants.O_EXCL | constants.O_NOFOLLOW, 0o600);
|
|
152
|
+
reader = stream.getReader();
|
|
153
|
+
const hash = createHash("sha256");
|
|
154
|
+
let seen = 0;
|
|
155
|
+
const abort = () => { void reader.cancel().catch(() => undefined); };
|
|
156
|
+
this.signal.addEventListener("abort", abort, { once: true });
|
|
157
|
+
try {
|
|
158
|
+
for (;;) {
|
|
159
|
+
check();
|
|
160
|
+
let next;
|
|
161
|
+
try {
|
|
162
|
+
next = await reader.read();
|
|
163
|
+
}
|
|
164
|
+
catch {
|
|
165
|
+
throw new FileAccessError(this.signal.aborted ? "cancelled" : "transfer_failed");
|
|
166
|
+
}
|
|
167
|
+
const { done, value } = next;
|
|
168
|
+
check();
|
|
169
|
+
if (done)
|
|
170
|
+
break;
|
|
171
|
+
if (!(value instanceof Uint8Array) || value.byteLength > limit - seen)
|
|
172
|
+
throw new FileAccessError(size === undefined ? "too_large" : "incomplete");
|
|
173
|
+
seen += value.byteLength;
|
|
174
|
+
// Copy ONE bounded chunk before hashing/writing. A producer reusing a source buffer
|
|
175
|
+
// cannot make the saved bytes differ from the bytes we hashed during async writes.
|
|
176
|
+
for (let offset = 0; offset < value.byteLength;) {
|
|
177
|
+
const chunk = Buffer.from(value.subarray(offset, Math.min(offset + FILE_CHUNK_BYTES, value.byteLength)));
|
|
178
|
+
hash.update(chunk);
|
|
179
|
+
// FileHandle.write may be short. Disk paces both the reader and the next copy.
|
|
180
|
+
for (let written = 0; written < chunk.byteLength;) {
|
|
181
|
+
check();
|
|
182
|
+
const { bytesWritten } = await fh.write(chunk, written, chunk.byteLength - written, null);
|
|
183
|
+
if (bytesWritten < 1)
|
|
184
|
+
throw new FileAccessError("io");
|
|
185
|
+
written += bytesWritten;
|
|
186
|
+
}
|
|
187
|
+
offset += chunk.byteLength;
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
finally {
|
|
192
|
+
this.signal.removeEventListener("abort", abort);
|
|
193
|
+
}
|
|
194
|
+
if (size !== undefined && seen !== size)
|
|
195
|
+
throw new FileAccessError("incomplete");
|
|
196
|
+
await fh.sync();
|
|
197
|
+
const stat = await fh.stat();
|
|
198
|
+
if (stat.size !== seen || stat.nlink !== 1)
|
|
199
|
+
throw new FileAccessError("changed");
|
|
200
|
+
await fh.close();
|
|
201
|
+
fh = undefined;
|
|
202
|
+
check();
|
|
203
|
+
// Finalize exact metadata before publication. Cleanup cannot erase a known commit.
|
|
204
|
+
const metadata = { file, size: seen, sha256: hash.digest("hex"), complete: true };
|
|
205
|
+
const target = `${this.base}/${file}`;
|
|
206
|
+
try {
|
|
207
|
+
await link(temp, target);
|
|
208
|
+
}
|
|
209
|
+
catch (e) {
|
|
210
|
+
if (e && typeof e === "object" && "code" in e && e.code === "EEXIST")
|
|
211
|
+
throw fileError(e);
|
|
212
|
+
// An error can arrive after visibility. Never automatically delete an uncertain target.
|
|
213
|
+
throw new FileAccessError("publication_unknown");
|
|
214
|
+
}
|
|
215
|
+
if (authorization) {
|
|
216
|
+
try {
|
|
217
|
+
check();
|
|
218
|
+
authorization.commit();
|
|
219
|
+
}
|
|
220
|
+
catch {
|
|
221
|
+
// Preserve the guarded plaintext boundary: revoke during async link can have exposed
|
|
222
|
+
// output. Best-effort removal is not proof that plaintext was never visible.
|
|
223
|
+
await unlink(target).catch(() => undefined);
|
|
224
|
+
throw new FileAccessError("commit_unconfirmed");
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
// Ordinary output commits at successful link; guarded output at synchronous authorization.
|
|
228
|
+
// Later cancellation, lease disposal or staging cleanup cannot turn that fact into failure.
|
|
229
|
+
completed = metadata;
|
|
230
|
+
return metadata;
|
|
231
|
+
}
|
|
232
|
+
catch (e) {
|
|
233
|
+
failure = fileError(e);
|
|
234
|
+
throw failure;
|
|
235
|
+
}
|
|
236
|
+
finally {
|
|
237
|
+
await reader?.cancel().catch(() => undefined);
|
|
238
|
+
try {
|
|
239
|
+
reader?.releaseLock();
|
|
240
|
+
}
|
|
241
|
+
catch { /* cancellation already requested */ }
|
|
242
|
+
if (!reader)
|
|
243
|
+
await stream.cancel().catch(() => undefined);
|
|
244
|
+
await fh?.close().catch(() => undefined);
|
|
245
|
+
if (temp) {
|
|
246
|
+
try {
|
|
247
|
+
await unlink(temp);
|
|
248
|
+
}
|
|
249
|
+
catch {
|
|
250
|
+
if (completed)
|
|
251
|
+
completed.cleanupWarning = "staging_cleanup_failed";
|
|
252
|
+
else if (failure)
|
|
253
|
+
failure.cleanupWarning = "staging_cleanup_failed";
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
if (claimed)
|
|
257
|
+
this.outputActive = false;
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
export class FileRoot {
|
|
262
|
+
handle;
|
|
263
|
+
maxBytes;
|
|
264
|
+
busy = false;
|
|
265
|
+
closed = false;
|
|
266
|
+
lifetime = new AbortController();
|
|
267
|
+
pending = Promise.resolve();
|
|
268
|
+
constructor(handle, maxBytes) {
|
|
269
|
+
this.handle = handle;
|
|
270
|
+
this.maxBytes = maxBytes;
|
|
271
|
+
}
|
|
272
|
+
static async open(path, maxBytes = FILE_DEFAULT_MAX_BYTES, excludedPaths = []) {
|
|
273
|
+
if (process.platform !== "linux" || !process.getuid || !isAbsolute(path))
|
|
274
|
+
throw new FileAccessError("unsafe_root");
|
|
275
|
+
checkSize(maxBytes, FILE_HARD_MAX_BYTES);
|
|
276
|
+
if (maxBytes < 1)
|
|
277
|
+
throw new FileAccessError("too_large");
|
|
278
|
+
let handle;
|
|
279
|
+
try {
|
|
280
|
+
// Administrator config, not a caller path. Reject a symlink root; retain its descriptor.
|
|
281
|
+
handle = await open(path, constants.O_RDONLY | constants.O_DIRECTORY | constants.O_NOFOLLOW);
|
|
282
|
+
const st = await handle.stat();
|
|
283
|
+
if (!st.isDirectory() || st.uid !== process.getuid() || (st.mode & 0o777) !== 0o700)
|
|
284
|
+
throw new FileAccessError("unsafe_root");
|
|
285
|
+
const pinned = `/proc/self/fd/${handle.fd}`;
|
|
286
|
+
const actual = await realpath(pinned); // also proves usable procfs
|
|
287
|
+
for (const excludedPath of excludedPaths) {
|
|
288
|
+
const excluded = await realpath(excludedPath);
|
|
289
|
+
const within = (a, b) => {
|
|
290
|
+
const rel = relative(a, b);
|
|
291
|
+
return rel === "" || (rel !== ".." && !rel.startsWith(`..${sep}`) && !isAbsolute(rel));
|
|
292
|
+
};
|
|
293
|
+
if (within(actual, excluded) || within(excluded, actual))
|
|
294
|
+
throw new FileAccessError("unsafe_root");
|
|
295
|
+
}
|
|
296
|
+
return new FileRoot(handle, maxBytes);
|
|
297
|
+
}
|
|
298
|
+
catch {
|
|
299
|
+
await handle?.close();
|
|
300
|
+
throw new FileAccessError("unsafe_root");
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
async transfer(job, signal) {
|
|
304
|
+
if (this.closed)
|
|
305
|
+
throw new FileAccessError("disabled");
|
|
306
|
+
if (this.busy)
|
|
307
|
+
throw new FileAccessError("busy");
|
|
308
|
+
this.busy = true;
|
|
309
|
+
let done;
|
|
310
|
+
this.pending = new Promise(resolve => { done = resolve; });
|
|
311
|
+
const controller = new AbortController();
|
|
312
|
+
const forwardAbort = () => controller.abort();
|
|
313
|
+
const sources = signal ? [signal, this.lifetime.signal] : [this.lifetime.signal];
|
|
314
|
+
try {
|
|
315
|
+
for (const source of sources)
|
|
316
|
+
source.addEventListener("abort", forwardAbort, { once: true });
|
|
317
|
+
if (sources.some(source => source.aborted))
|
|
318
|
+
controller.abort();
|
|
319
|
+
checkSignal(controller.signal);
|
|
320
|
+
return await job(new FileSession(`/proc/self/fd/${this.handle.fd}`, this.maxBytes, controller.signal));
|
|
321
|
+
}
|
|
322
|
+
finally {
|
|
323
|
+
for (const source of sources)
|
|
324
|
+
source.removeEventListener("abort", forwardAbort);
|
|
325
|
+
this.busy = false;
|
|
326
|
+
done();
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
async close() {
|
|
330
|
+
if (this.closed)
|
|
331
|
+
return;
|
|
332
|
+
this.closed = true;
|
|
333
|
+
this.lifetime.abort();
|
|
334
|
+
await this.pending;
|
|
335
|
+
await this.handle.close();
|
|
336
|
+
}
|
|
337
|
+
}
|
|
338
|
+
/** Unconfigured access is absent, not a cwd/home fallback. Invalid opt-in refuses startup. */
|
|
339
|
+
export async function fileRootFromEnv(env, excludedPaths = []) {
|
|
340
|
+
const path = env.EVOLVER_FILE_ROOT;
|
|
341
|
+
const max = env.EVOLVER_FILE_MAX_BYTES;
|
|
342
|
+
if (path === undefined) {
|
|
343
|
+
if (max !== undefined)
|
|
344
|
+
throw new FileAccessError("unsafe_root");
|
|
345
|
+
return undefined;
|
|
346
|
+
}
|
|
347
|
+
if (path.length === 0 || (max !== undefined && !/^[1-9][0-9]*$/.test(max)))
|
|
348
|
+
throw new FileAccessError("unsafe_root");
|
|
349
|
+
const protectedPaths = [env.EVOLVER_KEY_PATH, env.EVOLVER_APPLICATION_KEY_PATH,
|
|
350
|
+
env.EVOLVER_GROUP_CRYPTO_KEY_FILE, env.EVOLVER_LOGIN_PROFILES_PATH,
|
|
351
|
+
env.EVOLVER_HTTP_PROFILES_PATH, env.EVOLVER_TRANSACTION_POLICY_PATH, env.EVOLVER_MCP_CONFIG, ...excludedPaths]
|
|
352
|
+
.filter((p) => typeof p === "string" && p.length > 0);
|
|
353
|
+
return FileRoot.open(path, max === undefined ? FILE_DEFAULT_MAX_BYTES : Number(max), protectedPaths);
|
|
354
|
+
}
|
|
355
|
+
//# sourceMappingURL=files.js.map
|