@mcsherrylabs/evolver-mcp 0.11.0 → 0.12.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.
@@ -1,5 +1,5 @@
1
1
  {
2
- "nodeImage": "nexus.mcsherrylabs.com/evolver-node:v0.26.0",
2
+ "nodeImage": "nexus.mcsherrylabs.com/evolver-node:v0.26.1",
3
3
  "images": {
4
4
  "processor": "evolver-mcp-processor",
5
5
  "node": "evolver-mcp-node",
package/dist/content.d.ts CHANGED
@@ -1,7 +1,14 @@
1
1
  import type { ToolResult } from "./result.js";
2
+ /** Inline bound on ORIGINAL (decoded) bytes, for both PUT and GET: 256 KiB. */
3
+ export declare const CONTENT_MAX_BYTES = 262144;
4
+ /** Longest canonical base64 accepted/returned for `CONTENT_MAX_BYTES` bytes (349 528 chars). */
5
+ export declare const CONTENT_MAX_BASE64_CHARS: number;
6
+ /** Most principal names one restricted publication may grant. */
7
+ export declare const CONTENT_MAX_PRINCIPALS = 32;
2
8
  /** Only what the content verbs need from a client. `NodeClient` satisfies it structurally, which
3
9
  * is what lets the rules be exercised against a fake — a rule only checked against a live node
4
- * is a rule that drifts. */
10
+ * is a rule that drifts. The GET goes through the stream so the declared size is checked BEFORE
11
+ * any body is buffered (the SDK's `getContent` buffers an object of any size). */
5
12
  export interface ContentClient {
6
13
  postContent(bytes: Uint8Array, opts?: {
7
14
  authorizedPrincipals?: string[];
@@ -9,7 +16,10 @@ export interface ContentClient {
9
16
  key: string;
10
17
  size: number;
11
18
  }>;
12
- getContent(key: string): Promise<Uint8Array | null>;
19
+ getContentStream(key: string): Promise<{
20
+ stream: ReadableStream<Uint8Array>;
21
+ size: number;
22
+ } | null>;
13
23
  }
14
24
  /** What the content verbs need from the world: the SEPARATE content client (built against
15
25
  * `contentBaseUrl`, never the chain client) plus that content endpoint name for refusals.
@@ -19,33 +29,53 @@ export interface ContentDeps {
19
29
  readonly client: ContentClient;
20
30
  readonly contentUrl: string;
21
31
  }
22
- /** What a successful publish answers: the node's key and size, plus a locally-computed sha256
23
- * over the exact published bytes. No `txId` — content has nothing to submit later. */
32
+ /** What a publish is asked to do. Raw tool parameters: every field is validated here, never coerced. */
33
+ export interface PublishRequest {
34
+ readonly access?: unknown;
35
+ readonly authorizedPrincipals?: unknown;
36
+ readonly text?: unknown;
37
+ readonly base64?: unknown;
38
+ }
39
+ /** What a successful publish answers: the node's key, the ORIGINAL byte size, a locally-computed
40
+ * sha256 over those bytes, and the access actually requested. No `txId` — content has nothing to
41
+ * submit later. */
24
42
  export interface Published {
25
43
  readonly key: string;
26
44
  readonly size: number;
27
45
  readonly sha256: string;
46
+ readonly encoding: "text" | "base64";
47
+ readonly access: "open" | "restricted";
48
+ readonly authorizedPrincipals?: readonly string[];
49
+ readonly note: string;
28
50
  }
29
- /** What a successful fetch answers: the key, size, locally-recomputed sha256, and the bytes
30
- * decoded to UTF-8 `text`. The raw `bytes` field is DROPPED — never surfaced to the model
31
- * (Pitfall 5). `null` is the DEFINITIVE "no reachable holder" negative (Pitfall 3). */
32
- export interface Fetched {
51
+ /** What a successful fetch answers: always the COMPLETE object (`complete: true`), its size and
52
+ * locally-recomputed sha256, and EXACTLY ONE of `text` (strict, lossless UTF-8) or `base64` (the
53
+ * actual fetched bytes). `null` is the DEFINITIVE "no reachable holder" negative (Pitfall 3). */
54
+ export type Fetched = {
33
55
  readonly key: string;
34
56
  readonly size: number;
35
57
  readonly sha256: string;
58
+ readonly complete: true;
59
+ } & ({
60
+ readonly encoding: "text";
36
61
  readonly text: string;
37
- }
62
+ } | {
63
+ readonly encoding: "base64";
64
+ readonly base64: string;
65
+ });
38
66
  /** SHA-256 of the given bytes, hex-encoded. Computed locally via Web Crypto — never trusted from
39
67
  * the node (T-5-04). */
40
68
  export declare function sha256Hex(bytes: Uint8Array): Promise<string>;
41
- /** Publish UTF-8 text to the content cache (READ-11). OPEN-only publish for v1: no
42
- * `authorizedPrincipals` is exposed, closing the blank-grant-list footgun (Pitfall 4). Empty or
43
- * blank text is refused locally as `partial` BEFORE any call. Returns `{ key, size, sha256 }`
44
- * with the sha256 computed locally over the exact bytes published. */
45
- export declare function publishBytes(deps: ContentDeps, text: string): Promise<ToolResult<Published>>;
46
- /** Fetch content from the cache by key and decode it to text (READ-10). Empty/blank key refused
47
- * locally as `partial` BEFORE any call. A `null` from the SDK is the DEFINITIVE negative — no
48
- * reachable holder — surfaced as `ok { value: null }`, NOT `not_found` (Pitfall 3); a throw is
49
- * `unreachable`. Raw bytes are decoded to `text` and dropped (Pitfall 5). */
50
- export declare function fetchBytes(deps: ContentDeps, key: string): Promise<ToolResult<Fetched | null>>;
69
+ /** Publish text or binary to the content cache (READ-11, #8). `access` must be stated; a
70
+ * restricted publication forwards its grants through the SDK's `authorizedPrincipals`. Every
71
+ * refusal happens BEFORE any call. A node answer that is not 201 (e.g. HTTP 400 for an unknown
72
+ * principal) is `partial publish` naming the node's answer — never `ok`, and never retried open;
73
+ * a transport throw is `unreachable`. */
74
+ export declare function publishBytes(deps: ContentDeps, req: PublishRequest): Promise<ToolResult<Published>>;
75
+ /** Fetch content from the cache by key (READ-10, #8), returning the COMPLETE object as strict
76
+ * UTF-8 `text` (default) or `base64` of the actual bytes. Empty/blank key or unknown encoding is
77
+ * refused locally BEFORE any call. `null` is the DEFINITIVE negative (Pitfall 3); a throw or a
78
+ * short/overlong transfer is `unreachable`. Over `CONTENT_MAX_BYTES`, or not valid UTF-8 in text
79
+ * mode, answers `partial` with NO content — never a preview, never a lossy decode. */
80
+ export declare function fetchBytes(deps: ContentDeps, key: string, encoding?: unknown): Promise<ToolResult<Fetched | null>>;
51
81
  //# sourceMappingURL=content.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"content.d.ts","sourceRoot":"","sources":["../src/content.ts"],"names":[],"mappings":"AAgBA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AAE9C;;6BAE6B;AAC7B,MAAM,WAAW,aAAa;IAC5B,WAAW,CAAC,KAAK,EAAE,UAAU,EAAE,IAAI,CAAC,EAAE;QAAE,oBAAoB,CAAC,EAAE,MAAM,EAAE,CAAA;KAAE,GAAG,OAAO,CAAC;QAAE,GAAG,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IACnH,UAAU,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,UAAU,GAAG,IAAI,CAAC,CAAC;CACrD;AAED;;;0CAG0C;AAC1C,MAAM,WAAW,WAAW;IAC1B,QAAQ,CAAC,MAAM,EAAE,aAAa,CAAC;IAC/B,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;CAC7B;AAED;uFACuF;AACvF,MAAM,WAAW,SAAS;IACxB,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;CACzB;AAED;;wFAEwF;AACxF,MAAM,WAAW,OAAO;IACtB,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;CACvB;AAED;yBACyB;AACzB,wBAAsB,SAAS,CAAC,KAAK,EAAE,UAAU,GAAG,OAAO,CAAC,MAAM,CAAC,CAKlE;AAED;;;uEAGuE;AACvE,wBAAsB,YAAY,CAChC,IAAI,EAAE,WAAW,EACjB,IAAI,EAAE,MAAM,GACX,OAAO,CAAC,UAAU,CAAC,SAAS,CAAC,CAAC,CAchC;AAED;;;8EAG8E;AAC9E,wBAAsB,UAAU,CAC9B,IAAI,EAAE,WAAW,EACjB,GAAG,EAAE,MAAM,GACV,OAAO,CAAC,UAAU,CAAC,OAAO,GAAG,IAAI,CAAC,CAAC,CAYrC"}
1
+ {"version":3,"file":"content.d.ts","sourceRoot":"","sources":["../src/content.ts"],"names":[],"mappings":"AA8BA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AAE9C,+EAA+E;AAC/E,eAAO,MAAM,iBAAiB,SAAU,CAAC;AACzC,gGAAgG;AAChG,eAAO,MAAM,wBAAwB,QAAuC,CAAC;AAC7E,iEAAiE;AACjE,eAAO,MAAM,sBAAsB,KAAK,CAAC;AAEzC;;;mFAGmF;AACnF,MAAM,WAAW,aAAa;IAC5B,WAAW,CAAC,KAAK,EAAE,UAAU,EAAE,IAAI,CAAC,EAAE;QAAE,oBAAoB,CAAC,EAAE,MAAM,EAAE,CAAA;KAAE,GAAG,OAAO,CAAC;QAAE,GAAG,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IACnH,gBAAgB,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC;QAAE,MAAM,EAAE,cAAc,CAAC,UAAU,CAAC,CAAC;QAAC,IAAI,EAAE,MAAM,CAAA;KAAE,GAAG,IAAI,CAAC,CAAC;CACrG;AAED;;;0CAG0C;AAC1C,MAAM,WAAW,WAAW;IAC1B,QAAQ,CAAC,MAAM,EAAE,aAAa,CAAC;IAC/B,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;CAC7B;AAED,wGAAwG;AACxG,MAAM,WAAW,cAAc;IAC7B,QAAQ,CAAC,MAAM,CAAC,EAAE,OAAO,CAAC;IAC1B,QAAQ,CAAC,oBAAoB,CAAC,EAAE,OAAO,CAAC;IACxC,QAAQ,CAAC,IAAI,CAAC,EAAE,OAAO,CAAC;IACxB,QAAQ,CAAC,MAAM,CAAC,EAAE,OAAO,CAAC;CAC3B;AAED;;oBAEoB;AACpB,MAAM,WAAW,SAAS;IACxB,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,QAAQ,EAAE,MAAM,GAAG,QAAQ,CAAC;IACrC,QAAQ,CAAC,MAAM,EAAE,MAAM,GAAG,YAAY,CAAC;IACvC,QAAQ,CAAC,oBAAoB,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;IAClD,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;CACvB;AAED;;kGAEkG;AAClG,MAAM,MAAM,OAAO,GAAG;IACpB,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,QAAQ,EAAE,IAAI,CAAC;CACzB,GAAG,CAAC;IAAE,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;CAAE,GAAG;IAAE,QAAQ,CAAC,QAAQ,EAAE,QAAQ,CAAC;IAAC,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAA;CAAE,CAAC,CAAC;AAEtH;yBACyB;AACzB,wBAAsB,SAAS,CAAC,KAAK,EAAE,UAAU,GAAG,OAAO,CAAC,MAAM,CAAC,CAKlE;AA4ED;;;;0CAI0C;AAC1C,wBAAsB,YAAY,CAChC,IAAI,EAAE,WAAW,EACjB,GAAG,EAAE,cAAc,GAClB,OAAO,CAAC,UAAU,CAAC,SAAS,CAAC,CAAC,CAmChC;AAED;;;;uFAIuF;AACvF,wBAAsB,UAAU,CAC9B,IAAI,EAAE,WAAW,EACjB,GAAG,EAAE,MAAM,EACX,QAAQ,GAAE,OAAgB,GACzB,OAAO,CAAC,UAAU,CAAC,OAAO,GAAG,IAAI,CAAC,CAAC,CAiDrC"}
package/dist/content.js CHANGED
@@ -1,18 +1,38 @@
1
1
  // The content cache is a SECOND non-signing verb family, alongside `claimIdentity` (SIGN-08): it
2
- // is a DIRECT node call through `NodeClient.postContent`/`getContent`, because the MCP holds no
3
- // key for the content it publishes or fetches — content is a loopback byte store on its OWN
2
+ // is a DIRECT node call through `NodeClient.postContent`/`getContentStream`, because the MCP holds
3
+ // no key for the content it publishes or fetches — content is a loopback byte store on its OWN
4
4
  // listener (a different port from the chain API), and reaching that listener is the entire
5
5
  // authorization. Nothing is recorded in the session stores, no `txId` is returned, and nothing
6
6
  // reaches `evolver_submit`.
7
7
  //
8
8
  // The digest is NEVER trusted from the node: `sha256Hex` is computed locally over the exact
9
- // bytes, so a reader verifies against what the PUBLISHER asserted, not against what the node
10
- // claims (a node-trusted digest would verify the node against itself — T-5-04).
9
+ // ORIGINAL bytes (never a base64 transport string), so a reader verifies against what the
10
+ // PUBLISHER asserted, not against what the node claims (a node-trusted digest would verify the
11
+ // node against itself — T-5-04).
11
12
  //
12
13
  // A GET that returns `null` is a DEFINITIVE negative ("no reachable holder had it" — the node
13
14
  // walked the overlay on a miss), NOT a throw: a throw is `unreachable` (transport failure), and
14
15
  // the two are never collapsed (Pitfall 3).
16
+ //
17
+ // ⚠️ READ GRANTS (#8). `access` is REQUIRED and never inferred: "open" sends no grant, "restricted"
18
+ // sends `authorizedPrincipals` through the SDK's own grant header. Every malformed/blank/oversized
19
+ // grant list is refused BEFORE the node is asked, so an intended restriction can never become an
20
+ // open publish. The grant governs which authenticated remote peer NODE may read on the node's
21
+ // peer route — not the MCP signer, not the trusted local listener this module talks to — and it
22
+ // is a serving control, not encryption. Grants on identical bytes merge by union on the node, so a
23
+ // narrower re-publication cannot retract an earlier wider one.
24
+ //
25
+ // ⚠️ BOUNDS. Everything is inline in model context, so both directions are capped at
26
+ // `CONTENT_MAX_BYTES` of ORIGINAL bytes. An over-bound GET returns NO content: there is no preview
27
+ // and no truncated object reported as complete. Streaming/range reads are a follow-up.
28
+ import { isValidPrincipalName } from "@mcsherrylabs/evolver-client";
15
29
  import { unreachableFrom } from "./result.js";
30
+ /** Inline bound on ORIGINAL (decoded) bytes, for both PUT and GET: 256 KiB. */
31
+ export const CONTENT_MAX_BYTES = 262_144;
32
+ /** Longest canonical base64 accepted/returned for `CONTENT_MAX_BYTES` bytes (349 528 chars). */
33
+ export const CONTENT_MAX_BASE64_CHARS = 4 * Math.ceil(CONTENT_MAX_BYTES / 3);
34
+ /** Most principal names one restricted publication may grant. */
35
+ export const CONTENT_MAX_PRINCIPALS = 32;
16
36
  /** SHA-256 of the given bytes, hex-encoded. Computed locally via Web Crypto — never trusted from
17
37
  * the node (T-5-04). */
18
38
  export async function sha256Hex(bytes) {
@@ -21,40 +41,175 @@ export async function sha256Hex(bytes) {
21
41
  .map((b) => b.toString(16).padStart(2, "0"))
22
42
  .join("");
23
43
  }
24
- /** Publish UTF-8 text to the content cache (READ-11). OPEN-only publish for v1: no
25
- * `authorizedPrincipals` is exposed, closing the blank-grant-list footgun (Pitfall 4). Empty or
26
- * blank text is refused locally as `partial` BEFORE any call. Returns `{ key, size, sha256 }`
27
- * with the sha256 computed locally over the exact bytes published. */
28
- export async function publishBytes(deps, text) {
29
- const textStr = String(text ?? "");
30
- if (textStr.trim().length === 0)
31
- return { kind: "partial", what: "text", cause: "publishing empty content is refused — write something first" };
32
- const bytes = new TextEncoder().encode(textStr);
44
+ const OPEN_NOTE = "OPEN: any peer node that can reach this node's content peer route and knows the key may read it. " +
45
+ "Not encryption; there is no retraction.";
46
+ const RESTRICTED_NOTE = "RESTRICTED: remote reads are served only to authenticated peer NODES matching a granted identity, " +
47
+ "or currently in/owning a granted group. The trusted local content listener is NOT restricted, the " +
48
+ "storage operator and prior recipients are not excluded, and grants on identical bytes merge by union — " +
49
+ "an earlier open or wider publication is not narrowed. Not encryption, not an availability guarantee.";
50
+ /** Canonical, padded, standard-alphabet base64 — no whitespace, no URL-safe alphabet. */
51
+ const BASE64 = /^(?:[A-Za-z0-9+/]{4})*(?:[A-Za-z0-9+/]{2}==|[A-Za-z0-9+/]{3}=)?$/;
52
+ /** A lone UTF-16 surrogate cannot be UTF-8 encoded losslessly (TextEncoder writes U+FFFD). */
53
+ const LONE_SURROGATE = /[\uD800-\uDBFF](?![\uDC00-\uDFFF])|(?<![\uD800-\uDBFF])[\uDC00-\uDFFF]/;
54
+ /** The access the caller DELIBERATELY asked for, or a refusal. Nothing is trimmed, filtered or
55
+ * defaulted: an entry the node would reinterpret (blank, whitespace, comma-joined) is refused,
56
+ * because the alternative is publishing to a set the caller did not name. */
57
+ function parseAccess(access, grants) {
58
+ if (access === "open") {
59
+ if (grants !== undefined)
60
+ return { kind: "partial", what: "authorizedPrincipals",
61
+ cause: 'access "open" takes no authorizedPrincipals — use access "restricted" to grant read access' };
62
+ return { mode: "open" };
63
+ }
64
+ if (access !== "restricted")
65
+ return { kind: "partial", what: "access",
66
+ cause: 'say access "open" (any peer with the key may read) or "restricted" (with authorizedPrincipals) — it is never inferred' };
67
+ if (!Array.isArray(grants) || grants.length === 0)
68
+ return { kind: "partial", what: "authorizedPrincipals",
69
+ cause: 'access "restricted" needs a non-empty list of identity/group names — nothing was published' };
70
+ if (grants.length > CONTENT_MAX_PRINCIPALS)
71
+ return { kind: "partial", what: "authorizedPrincipals",
72
+ cause: `at most ${CONTENT_MAX_PRINCIPALS} principals per publication — nothing was published` };
73
+ const principals = [];
74
+ for (const g of grants) {
75
+ if (typeof g !== "string" || !isValidPrincipalName(g))
76
+ return { kind: "partial", what: "authorizedPrincipals",
77
+ cause: `${JSON.stringify(g).slice(0, 80)} is not a principal name (1-128 of A-Z a-z 0-9 . _ -, no spaces or commas) — nothing was published` };
78
+ if (!principals.includes(g))
79
+ principals.push(g);
80
+ }
81
+ return { mode: "restricted", principals };
82
+ }
83
+ /** The ORIGINAL bytes to publish from exactly one of `text` / `base64`, or a refusal. */
84
+ function parsePayload(text, base64) {
85
+ if ((text === undefined) === (base64 === undefined))
86
+ return { kind: "partial", what: "content", cause: "give exactly one of `text` (UTF-8) or `base64` (binary)" };
87
+ if (text !== undefined) {
88
+ if (typeof text !== "string" || text.trim().length === 0)
89
+ return { kind: "partial", what: "text", cause: "publishing empty content is refused — write something first" };
90
+ if (LONE_SURROGATE.test(text))
91
+ return { kind: "partial", what: "text", cause: "text contains an unpaired surrogate that UTF-8 cannot carry losslessly — use `base64`" };
92
+ const bytes = new TextEncoder().encode(text);
93
+ if (bytes.length > CONTENT_MAX_BYTES)
94
+ return { kind: "partial", what: "text", cause: `${bytes.length} UTF-8 bytes exceeds the ${CONTENT_MAX_BYTES}-byte inline bound` };
95
+ return { bytes, encoding: "text" };
96
+ }
97
+ if (typeof base64 !== "string" || base64.length === 0)
98
+ return { kind: "partial", what: "base64", cause: "publishing empty content is refused — give non-empty base64" };
99
+ if (base64.length > CONTENT_MAX_BASE64_CHARS)
100
+ return { kind: "partial", what: "base64",
101
+ cause: `${base64.length} base64 characters exceeds the ${CONTENT_MAX_BASE64_CHARS}-character bound (${CONTENT_MAX_BYTES} bytes)` };
102
+ const buf = BASE64.test(base64) ? Buffer.from(base64, "base64") : undefined;
103
+ // Round-trip equality rejects non-zero pad bits, so exactly one string names each byte sequence.
104
+ if (buf === undefined || buf.toString("base64") !== base64)
105
+ return { kind: "partial", what: "base64",
106
+ cause: "not canonical standard base64 (A-Z a-z 0-9 + /, `=` padding, no whitespace or URL-safe alphabet)" };
107
+ if (buf.length > CONTENT_MAX_BYTES)
108
+ return { kind: "partial", what: "base64", cause: `${buf.length} bytes exceeds the ${CONTENT_MAX_BYTES}-byte inline bound` };
109
+ return { bytes: new Uint8Array(buf), encoding: "base64" };
110
+ }
111
+ /** Publish text or binary to the content cache (READ-11, #8). `access` must be stated; a
112
+ * restricted publication forwards its grants through the SDK's `authorizedPrincipals`. Every
113
+ * refusal happens BEFORE any call. A node answer that is not 201 (e.g. HTTP 400 for an unknown
114
+ * principal) is `partial publish` naming the node's answer — never `ok`, and never retried open;
115
+ * a transport throw is `unreachable`. */
116
+ export async function publishBytes(deps, req) {
117
+ const access = parseAccess(req.access, req.authorizedPrincipals);
118
+ if ("kind" in access)
119
+ return access;
120
+ const payload = parsePayload(req.text, req.base64);
121
+ if ("kind" in payload)
122
+ return payload;
123
+ const { bytes, encoding } = payload;
124
+ let r;
125
+ let sha256;
33
126
  try {
34
- const sha256 = await sha256Hex(bytes);
35
- const r = await deps.client.postContent(bytes);
36
- return { kind: "ok", value: { key: r.key, size: r.size, sha256 } };
127
+ sha256 = await sha256Hex(bytes);
128
+ r = access.mode === "restricted"
129
+ ? await deps.client.postContent(bytes, { authorizedPrincipals: access.principals })
130
+ : await deps.client.postContent(bytes);
37
131
  }
38
132
  catch (e) {
39
- return unreachableFrom(e, deps.contentUrl);
133
+ const message = e instanceof Error ? e.message : String(e);
134
+ const status = /^postContent: HTTP (\d{3})\b/.exec(message)?.[1];
135
+ if (status === undefined)
136
+ return unreachableFrom(e, deps.contentUrl);
137
+ return {
138
+ kind: "partial",
139
+ what: "publish",
140
+ cause: status.startsWith("4")
141
+ ? `the content node refused it (${message}) — nothing was stored; an unknown principal name is answered HTTP 400. Not retried, never re-sent open.`
142
+ : `the content node answered ${message} — NOT a successful publication, and storage is unconfirmed. Not retried.`,
143
+ };
40
144
  }
145
+ if (r.size !== bytes.length)
146
+ return { kind: "partial", what: "publish",
147
+ cause: `the node reported ${r.size} bytes for key ${r.key} but ${bytes.length} were sent — treat this publication as unverified` };
148
+ return {
149
+ kind: "ok",
150
+ value: access.mode === "restricted"
151
+ ? { key: r.key, size: bytes.length, sha256, encoding, access: "restricted", authorizedPrincipals: access.principals, note: RESTRICTED_NOTE }
152
+ : { key: r.key, size: bytes.length, sha256, encoding, access: "open", note: OPEN_NOTE },
153
+ };
41
154
  }
42
- /** Fetch content from the cache by key and decode it to text (READ-10). Empty/blank key refused
43
- * locally as `partial` BEFORE any call. A `null` from the SDK is the DEFINITIVE negative — no
44
- * reachable holder — surfaced as `ok { value: null }`, NOT `not_found` (Pitfall 3); a throw is
45
- * `unreachable`. Raw bytes are decoded to `text` and dropped (Pitfall 5). */
46
- export async function fetchBytes(deps, key) {
155
+ /** Fetch content from the cache by key (READ-10, #8), returning the COMPLETE object as strict
156
+ * UTF-8 `text` (default) or `base64` of the actual bytes. Empty/blank key or unknown encoding is
157
+ * refused locally BEFORE any call. `null` is the DEFINITIVE negative (Pitfall 3); a throw or a
158
+ * short/overlong transfer is `unreachable`. Over `CONTENT_MAX_BYTES`, or not valid UTF-8 in text
159
+ * mode, answers `partial` with NO content — never a preview, never a lossy decode. */
160
+ export async function fetchBytes(deps, key, encoding = "text") {
47
161
  const k = String(key ?? "").trim();
48
162
  if (k.length === 0)
49
163
  return { kind: "partial", what: "key", cause: "which content? a non-empty key is required" };
164
+ if (encoding !== "text" && encoding !== "base64")
165
+ return { kind: "partial", what: "encoding", cause: 'encoding is "text" (strict UTF-8) or "base64"' };
166
+ let bytes;
50
167
  try {
51
- const bytes = await deps.client.getContent(k);
52
- if (bytes === null)
168
+ const got = await deps.client.getContentStream(k);
169
+ if (got === null)
53
170
  return { kind: "ok", value: null };
54
- return { kind: "ok", value: { key: k, size: bytes.length, sha256: await sha256Hex(bytes), text: new TextDecoder().decode(bytes) } };
171
+ if (got.size > CONTENT_MAX_BYTES) {
172
+ await got.stream.cancel().catch(() => undefined);
173
+ return { kind: "partial", what: "content",
174
+ cause: `the object is ${got.size} bytes, over the ${CONTENT_MAX_BYTES}-byte inline bound — nothing returned (no preview); streaming/range reads are not exposed` };
175
+ }
176
+ const reader = got.stream.getReader();
177
+ const parts = [];
178
+ let total = 0;
179
+ for (;;) {
180
+ const { done, value } = await reader.read();
181
+ if (done)
182
+ break;
183
+ total += value.length;
184
+ if (total > got.size) {
185
+ await reader.cancel().catch(() => undefined);
186
+ throw new Error(`content transfer overran its declared ${got.size} bytes — discarded`);
187
+ }
188
+ parts.push(value);
189
+ }
190
+ // The SDK errors the stream on a short read, so reaching here means exactly `got.size` bytes.
191
+ bytes = new Uint8Array(total);
192
+ let off = 0;
193
+ for (const p of parts) {
194
+ bytes.set(p, off);
195
+ off += p.length;
196
+ }
55
197
  }
56
198
  catch (e) {
57
199
  return unreachableFrom(e, deps.contentUrl);
58
200
  }
201
+ const sha256 = await sha256Hex(bytes);
202
+ if (encoding === "base64")
203
+ return { kind: "ok", value: { key: k, size: bytes.length, sha256, complete: true, encoding: "base64", base64: Buffer.from(bytes).toString("base64") } };
204
+ let text;
205
+ try {
206
+ // fatal: refuse rather than substitute U+FFFD; ignoreBOM: keep a leading BOM as content.
207
+ text = new TextDecoder("utf-8", { fatal: true, ignoreBOM: true }).decode(bytes);
208
+ }
209
+ catch {
210
+ return { kind: "partial", what: "text",
211
+ cause: `the object (${bytes.length} bytes, sha256 ${sha256}) is not valid UTF-8 — nothing lossy is returned; fetch with encoding "base64"` };
212
+ }
213
+ return { kind: "ok", value: { key: k, size: bytes.length, sha256, complete: true, encoding: "text", text } };
59
214
  }
60
215
  //# sourceMappingURL=content.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"content.js","sourceRoot":"","sources":["../src/content.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"content.js","sourceRoot":"","sources":["../src/content.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,78 @@
1
+ import type { Signer } from "@mcsherrylabs/evolver-client";
2
+ import type { ChainReads } from "./read.js";
3
+ import type { ToolResult } from "./result.js";
4
+ import type { SignedGroup } from "./write.js";
5
+ export interface OwnershipDeps {
6
+ readonly identity: string;
7
+ readonly signer: Signer;
8
+ readonly reads: ChainReads;
9
+ }
10
+ /** Rounds of life for a two-party transfer. Longer than `DEFAULT_TTL_OFFSET` (50, sized for no
11
+ * human wait) because a second party must act, but never beyond the node's default
12
+ * `max-ttl-offset` (RoutingConfig.DefaultMaxTtlOffset = 100): a longer ttl is `ttl_too_far`, and
13
+ * a long-lived co-signed envelope is a bearer artefact. The default stays 10 rounds inside that
14
+ * bound so the two parties' nodes may disagree slightly about the tip. Expiry is final. */
15
+ export declare const OWNERSHIP_TTL_MAX = 100;
16
+ export declare const OWNERSHIP_TTL_DEFAULT = 90;
17
+ /** The one key type whose consent can be verified with a published SDK primitive. */
18
+ export declare const CONSENT_KEY_TYPE = "Ed25519";
19
+ export declare const INTENT_KIND = "evolver.group_ownership_intent.v1";
20
+ export declare const CONSENT_KIND = "evolver.group_ownership_consent.v1";
21
+ declare const PURPOSE = "AUTHORIZE";
22
+ export interface OwnershipIntent {
23
+ readonly kind: typeof INTENT_KIND;
24
+ readonly txId: string;
25
+ readonly ledgerId: string;
26
+ readonly groupName: string;
27
+ readonly outgoingOwner: string;
28
+ readonly incomingOwner: string;
29
+ readonly expiresAtRound: number;
30
+ readonly preparedAtRound: number;
31
+ readonly maxToll: "0";
32
+ readonly next: string;
33
+ }
34
+ export interface OwnershipConsent {
35
+ readonly kind: typeof CONSENT_KIND;
36
+ readonly txId: string;
37
+ readonly ledgerId: string;
38
+ readonly groupName: string;
39
+ readonly outgoingOwner: string;
40
+ readonly incomingOwner: string;
41
+ readonly expiresAtRound: number;
42
+ readonly maxToll: "0";
43
+ readonly auth: {
44
+ readonly nodeId: string;
45
+ readonly keyType: string;
46
+ readonly keyLabel: string;
47
+ readonly purpose: typeof PURPOSE;
48
+ readonly signatureHex: string;
49
+ };
50
+ readonly next: string;
51
+ }
52
+ /**
53
+ * PREPARE (outgoing owner). Signs nothing; returns the public intent for the incoming owner.
54
+ */
55
+ export declare function prepareOwnershipTransfer(deps: OwnershipDeps, params: {
56
+ groupName: unknown;
57
+ incomingOwner: unknown;
58
+ ttlRounds?: unknown;
59
+ }): Promise<ToolResult<OwnershipIntent>>;
60
+ /**
61
+ * CONSENT (incoming owner). Recomputes the intent's txId and signs it with THIS session's key.
62
+ */
63
+ export declare function consentOwnershipTransfer(deps: OwnershipDeps, params: {
64
+ txId: unknown;
65
+ groupName: unknown;
66
+ outgoingOwner: unknown;
67
+ incomingOwner: unknown;
68
+ expiresAtRound: unknown;
69
+ }): Promise<ToolResult<OwnershipConsent>>;
70
+ /**
71
+ * FINALIZE (outgoing owner). Verifies the consent, signs as sender, assembles both auths, records.
72
+ */
73
+ export declare function finalizeOwnershipTransfer(deps: OwnershipDeps, params: {
74
+ txId: unknown;
75
+ consent: unknown;
76
+ }): Promise<ToolResult<SignedGroup>>;
77
+ export {};
78
+ //# sourceMappingURL=group-ownership.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"group-ownership.d.ts","sourceRoot":"","sources":["../src/group-ownership.ts"],"names":[],"mappings":"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"}