@tangle-network/agent-app 0.45.58 → 0.45.60

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.
@@ -2,7 +2,7 @@ import {
2
2
  ChatComposer,
3
3
  ChatEmptyState,
4
4
  ChatMessages
5
- } from "../chunk-UPXDBHEE.js";
5
+ } from "../chunk-FOXGPGXF.js";
6
6
  import "../chunk-FBVLEGEG.js";
7
7
  import "../chunk-ENLRJYVW.js";
8
8
  import "../chunk-GEYACSFW.js";
@@ -46,39 +46,82 @@ export type AttachmentReadResult = {
46
46
  * turn-level exception.
47
47
  */
48
48
  export type ReadAttachmentFn = (scopeId: string, path: string) => Promise<AttachmentReadResult>;
49
- /** Outcome of persisting one attachment. Mirrors `AttachmentReadResult`'s
50
- * `ok`/`reason` shape and `upload.ts`'s `{ ok }` convention. */
49
+ /** The ownership identity for one attempted write. The upload and promotion
50
+ * routes create a unique path for each attempt, and the product adapter stores
51
+ * this id with it. */
52
+ export interface AttachmentWriteOwnership {
53
+ readonly id: string;
54
+ readonly path: string;
55
+ }
56
+ /** Public-safe outcome for a storage outage. Backend details belong in logs. */
57
+ export declare const ATTACHMENT_STORAGE_FAILURE_MESSAGE = "Attachment storage is temporarily unavailable. Please try again.";
58
+ /** Stable client error code when compensating cleanup did not complete. */
59
+ export declare const ATTACHMENT_ROLLBACK_FAILURE_CODE = "rollback_failed";
60
+ /** Public-safe outcome when compensating cleanup did not complete. */
61
+ export declare const ATTACHMENT_ROLLBACK_FAILURE_MESSAGE = "Attachment cleanup failed. Please try again.";
62
+ /** Add a path-safe ownership id to a logical path and return an immutable store key. */
63
+ export declare function immutableAttachmentPath(logicalPath: string, ownershipId: string): string;
64
+ /** Compensation for one attachment write. The adapter MUST compare the stored
65
+ * ownership id before deleting, so an older rollback cannot delete a newer
66
+ * overwrite. */
67
+ export interface AttachmentWriteReceipt {
68
+ rollback(): void | Promise<void>;
69
+ ownership: AttachmentWriteOwnership;
70
+ }
71
+ /** Clean up a write whose function threw after the store may have committed.
72
+ * The adapter must delete only the supplied ownership, never the logical path
73
+ * unconditionally. */
74
+ export type AbortAttachmentWriteFn = (scopeId: string, ownership: AttachmentWriteOwnership) => void | Promise<void>;
75
+ /**
76
+ * The stable writer result from the original attachment-store contract.
77
+ * Existing products may return this shape and keep using the legacy writer
78
+ * lane. New products should use {@link AtomicAttachmentWriteResult}.
79
+ */
51
80
  export type AttachmentWriteResult = {
52
81
  ok: true;
53
82
  } | {
54
83
  ok: false;
55
84
  reason: string;
56
85
  };
57
- /**
58
- * Persist `content` for `scopeId` at `path`. `content` is either raw `bytes`
59
- * or a base64 `string` — a string argument is ALWAYS base64 (never utf8), so
60
- * a store that speaks base64 (gtm's vault) writes it verbatim and one that
61
- * speaks bytes decodes once. Like the reader, failures resolve to
62
- * `{ ok: false, reason }` rather than throwing.
63
- *
64
- * `opts` mirrors the vault frontmatter gtm's `writeAttachmentVaultFile`
65
- * persists alongside the body (promote-file-parts.ts:181-190), so a product
66
- * reimplementing that vault writer through this seam can reproduce it
67
- * exactly:
68
- * - `mediaType` — the resolved MIME type; gtm's frontmatter key `mime`.
69
- * - `name` — the sanitized (store-path-safe) display filename; gtm passes
70
- * this only to shape its oversize message, not into frontmatter.
71
- * - `originalName` — the filename as the harness/browser reported it, BEFORE
72
- * sanitization (`raw.filename ?? filename` — falls back to the sanitized
73
- * name when the source carried none); gtm's frontmatter key `originalName`.
74
- * This is the one field with no other recovery path once sanitization has
75
- * run, so it must ride the write, not be re-derived after the fact.
76
- * - `size` — the authoritative decoded byte length being written; gtm's
77
- * frontmatter key `size`.
78
- */
79
- export type WriteAttachmentFn = (scopeId: string, path: string, content: Uint8Array | string, opts: {
86
+ /** Options from the stable attachment-store contract. */
87
+ export interface AttachmentWriteOptions {
80
88
  mediaType?: string;
81
89
  name?: string;
82
90
  originalName?: string;
83
91
  size?: number;
84
- }) => Promise<AttachmentWriteResult>;
92
+ }
93
+ /**
94
+ * The stable writer port. It is intentionally unchanged so products released
95
+ * before the ownership receipt contract continue to compile.
96
+ *
97
+ * The legacy lane does not promise batch rollback. Use
98
+ * {@link createAtomicAttachmentWriter} for ownership-safe writes.
99
+ */
100
+ export type WriteAttachmentFn = (scopeId: string, path: string, content: Uint8Array | string, opts: AttachmentWriteOptions) => Promise<AttachmentWriteResult>;
101
+ /** Options for an ownership-safe writer. */
102
+ export interface AtomicAttachmentWriteOptions extends AttachmentWriteOptions {
103
+ ownership: AttachmentWriteOwnership;
104
+ }
105
+ /** Result for an ownership-safe writer. A receipt is required on both paths. */
106
+ export type AtomicAttachmentWriteResult = {
107
+ ok: true;
108
+ receipt: AttachmentWriteReceipt;
109
+ } | {
110
+ ok: false;
111
+ reason: string;
112
+ receipt: AttachmentWriteReceipt;
113
+ };
114
+ /** Ownership-safe writer port used by the atomic upload and promotion lanes. */
115
+ export type AtomicWriteAttachmentFn = (scopeId: string, path: string, content: Uint8Array | string, opts: AtomicAttachmentWriteOptions) => Promise<AtomicAttachmentWriteResult>;
116
+ /** A complete ownership-safe attachment store adapter. */
117
+ export interface AtomicAttachmentWriter {
118
+ write: AtomicWriteAttachmentFn;
119
+ abort: AbortAttachmentWriteFn;
120
+ }
121
+ /**
122
+ * Build the explicit atomic adapter used by new routes.
123
+ *
124
+ * Keeping the write and abort functions together prevents a caller from
125
+ * enabling ownership paths while forgetting the ambiguous-write cleanup.
126
+ */
127
+ export declare function createAtomicAttachmentWriter(input: AtomicAttachmentWriter): AtomicAttachmentWriter;
@@ -1,14 +1,16 @@
1
1
  /**
2
2
  * `createAttachmentUploadRoute` — the fleet-primitive durable-store upload
3
- * route: a two-phase atomic batch (every file is validated before any file is
4
- * written a batch never partially lands), a content-sniffed type gate
3
+ * route: a two-phase batch (every file is validated before any file is written),
4
+ * with a stable legacy lane and an ownership-safe receipt lane for new callers,
5
+ * a content-sniffed type gate
5
6
  * (`checkAttachmentType` over `sniffBinary`'s magic-byte read, not the
6
7
  * extension or the browser-reported MIME), binary/text caps with optional
7
8
  * per-sniffed-mime overrides, an aggregate byte cap, and sanitized filenames.
8
9
  * Storage is fully seamed through the injected
9
- * `WriteAttachmentFn` (`./attachment-store`) — no default store, the product
10
- * owns where bytes actually live (vault, object store, …) — and auth/rate
11
- * limiting is entirely the injected `authorize` seam's job: this factory
10
+ * `WriteAttachmentFn` or `AtomicAttachmentWriter` (`./attachment-store`) — no
11
+ * default store, the product owns where bytes actually live (vault, object
12
+ * store, …), and auth/rate limiting is entirely the injected `authorize`
13
+ * seam's job: this factory
12
14
  * never invents a 401 or 429 response, it only returns `auth.response`
13
15
  * verbatim on failure.
14
16
  *
@@ -26,14 +28,9 @@
26
28
  * comment for the up-to-date framing between the two.
27
29
  */
28
30
  import type { ChatAttachmentKind } from './wire';
29
- import type { WriteAttachmentFn } from './attachment-store';
31
+ import { type AtomicAttachmentWriter, type WriteAttachmentFn } from './attachment-store';
30
32
  import { type AttachmentPathCheck } from './resolve-attachments';
31
- /** Outcome of the injected `authorize` seam: auth + rate limiting +
32
- * scope resolution, all in one place so a 429 rides `{ok:false, response}`
33
- * exactly like a 401 does — this factory has no rate-limit opinion of its
34
- * own. `writeAttachment` lets a single request override the option-level
35
- * store (e.g. routing per-tenant), defaulting to `options.writeAttachment`
36
- * when absent. */
33
+ /** Stable authorization result for the original writer contract. */
37
34
  export type AttachmentUploadAuthorization = {
38
35
  ok: true;
39
36
  scopeId: string;
@@ -42,48 +39,72 @@ export type AttachmentUploadAuthorization = {
42
39
  ok: false;
43
40
  response: Response;
44
41
  };
45
- /** Define options to authorize, write, and limit attachment uploads in a route */
46
- export interface CreateAttachmentUploadRouteOptions {
47
- /** Authenticate the caller, rate-limit, and resolve the store scope
48
- * (workspace/tenant id) — never a query param. */
49
- authorize(args: {
50
- request: Request;
51
- }): Promise<AttachmentUploadAuthorization>;
52
- /** Default store writer. `authorize` may override it per-request. */
53
- writeAttachment: WriteAttachmentFn;
42
+ /** Authorization result for an ownership-safe attachment writer. */
43
+ export type AtomicAttachmentUploadAuthorization = {
44
+ ok: true;
45
+ scopeId: string;
46
+ attachmentWriter?: AtomicAttachmentWriter;
47
+ } | {
48
+ ok: false;
49
+ response: Response;
50
+ };
51
+ /** The logger receives only sanitized backend details. The client receives the
52
+ * opaque message below, regardless of the store's failure text. */
53
+ export type AttachmentUploadLogger = Pick<Console, 'error'>;
54
+ interface AttachmentUploadRouteCommonOptions {
54
55
  /** Overridable caps. Defaults come from `./attachment-validation`. */
55
56
  limits?: {
56
57
  /** Most files one request may carry. Default {@link ATTACHMENT_MAX_COUNT}. */
57
58
  maxCount?: number;
58
59
  /** Ceiling on a binary file's raw size. Default {@link MAX_BINARY_ATTACHMENT_BYTES}. */
59
60
  maxBinaryBytes?: number;
60
- /** Optional binary-file ceilings keyed by the content-sniffed mime. A
61
- * missing mime falls back to `maxBinaryBytes`; text files continue to use
62
- * `maxTextBytes`. */
61
+ /** Optional binary-file ceilings keyed by the content-sniffed mime. */
63
62
  maxBytesBySniffedMime?: ReadonlyMap<string, number>;
64
63
  /** Ceiling on a text file's raw size. Default {@link MAX_TEXT_ATTACHMENT_BYTES}. */
65
64
  maxTextBytes?: number;
66
- /** Aggregate raw-byte ceiling across the batch. Default {@link MAX_ATTACHMENT_TOTAL_BYTES}. */
65
+ /** Aggregate raw-byte ceiling. Default {@link MAX_ATTACHMENT_TOTAL_BYTES}. */
67
66
  maxTotalBytes?: number;
68
67
  };
69
68
  /** Attachment kinds this route accepts. Default `['image', 'file']`. */
70
69
  allowedKinds?: ChatAttachmentKind[];
71
- /** Sniffed-mime allowlist fed to `checkAttachmentType`. Default
72
- * {@link ALLOWED_ATTACHMENT_SNIFFED_MIMES}. Narrow it to accept less than
73
- * the default (`new Set(['application/pdf'])`), or widen it to accept a
74
- * format the default refuses — macro-enabled Office packages are the
75
- * shipped case:
76
- * `new Set([...ALLOWED_ATTACHMENT_SNIFFED_MIMES, ...MACRO_ENABLED_OOXML_SNIFFED_MIMES])`. */
70
+ /** Sniffed-mime allowlist fed to `checkAttachmentType`. */
77
71
  allowedSniffedMimes?: ReadonlySet<string>;
78
- /** Sanitized-name → store path. Default identity (the sanitized name IS
79
- * the path); gtm passes `vaultFolderForFileName`, a tenant product a
80
- * scope prefix. */
72
+ /** Sanitized-name → logical store path. */
81
73
  pathFor?: (name: string) => string;
82
74
  /** Store-path validator. Default {@link defaultValidateAttachmentPath}. */
83
75
  validatePath?: (path: string) => AttachmentPathCheck;
84
- /** Last-resort media-type hook for text content the sniffer can't type.
85
- * Default {@link sniffMimeFromName}. */
76
+ /** Last-resort media-type hook for text content the sniffer cannot type. */
86
77
  sniffMime?: (name: string) => string;
78
+ /** Unique id source for atomic ownership keys. */
79
+ createWriteId?: () => string;
80
+ /** Server-side error sink. Backend text is sanitized before it is logged. */
81
+ logger?: AttachmentUploadLogger;
82
+ }
83
+ /** Stable route options for products using the original writer contract. */
84
+ export interface CreateLegacyAttachmentUploadRouteOptions extends AttachmentUploadRouteCommonOptions {
85
+ /** Authenticate the caller, rate-limit, and resolve the store scope
86
+ * (workspace/tenant id) — never a query param. */
87
+ authorize(args: {
88
+ request: Request;
89
+ }): Promise<AttachmentUploadAuthorization>;
90
+ /** Stable writer. `authorize` may override it per-request. */
91
+ writeAttachment: WriteAttachmentFn;
92
+ }
93
+ /**
94
+ * The original public route-options interface remains available for consumers
95
+ * that extend it. Ownership-safe callers use the atomic interface below.
96
+ */
97
+ export interface CreateAttachmentUploadRouteOptions extends CreateLegacyAttachmentUploadRouteOptions {
98
+ }
99
+ /** Ownership-safe route options for new products. */
100
+ export interface CreateAtomicAttachmentUploadRouteOptions extends AttachmentUploadRouteCommonOptions {
101
+ /** Authenticate the caller, rate-limit, and resolve the store scope. */
102
+ authorize(args: {
103
+ request: Request;
104
+ }): Promise<AtomicAttachmentUploadAuthorization>;
105
+ /** Complete writer + ambiguous-write cleanup adapter. */
106
+ attachmentWriter: AtomicAttachmentWriter;
87
107
  }
88
- /** Resolve an attachment upload route handler with customizable limits and validation options */
89
- export declare function createAttachmentUploadRoute(options: CreateAttachmentUploadRouteOptions): (request: Request) => Promise<Response>;
108
+ /** Resolve an attachment upload route handler with customizable limits and validation options. */
109
+ export declare function createAttachmentUploadRoute(options: CreateAttachmentUploadRouteOptions | CreateAtomicAttachmentUploadRouteOptions): (request: Request) => Promise<Response>;
110
+ export {};
@@ -0,0 +1,11 @@
1
+ import type { AtomicAttachmentWriteResult, AttachmentWriteOwnership, AttachmentWriteResult } from './attachment-store';
2
+ /** Read an injected writer result without allowing hostile getters to escape. */
3
+ export declare function inspectLegacyAttachmentWriteResult(value: unknown): AttachmentWriteResult | undefined;
4
+ /**
5
+ * Read an ownership-aware writer result with strict runtime checks.
6
+ *
7
+ * The public types protect TypeScript callers only. The product adapter is an
8
+ * injection boundary, so a proxy, malformed value, or truthy non-boolean must
9
+ * fail closed before the route invokes cleanup or publishes a path.
10
+ */
11
+ export declare function inspectAtomicAttachmentWriteResult(value: unknown, ownership: AttachmentWriteOwnership): AtomicAttachmentWriteResult | undefined;