@unson/brainbase-mcp 0.21.1 → 0.22.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.
Files changed (37) hide show
  1. package/dist/local-web-fetch-bridge.js +1 -1
  2. package/dist/local-web-host.d.ts +59 -0
  3. package/dist/local-web-host.js +171 -4
  4. package/dist/meeting-minutes-http.d.ts +22 -0
  5. package/dist/meeting-minutes-http.js +252 -0
  6. package/dist/meeting-minutes-lineage-adapters.d.ts +157 -0
  7. package/dist/meeting-minutes-lineage-adapters.js +410 -0
  8. package/dist/meeting-minutes-lineage-http.d.ts +20 -0
  9. package/dist/meeting-minutes-lineage-http.js +221 -0
  10. package/dist/meeting-minutes-lineage-lifecycle.d.ts +58 -0
  11. package/dist/meeting-minutes-lineage-lifecycle.js +146 -0
  12. package/dist/meeting-minutes-lineage.d.ts +307 -0
  13. package/dist/meeting-minutes-lineage.js +1030 -0
  14. package/dist/meeting-minutes-storage-controller.d.ts +182 -0
  15. package/dist/meeting-minutes-storage-controller.js +366 -0
  16. package/dist/meeting-minutes-storage-http.d.ts +25 -0
  17. package/dist/meeting-minutes-storage-http.js +312 -0
  18. package/dist/meeting-minutes-storage.d.ts +169 -0
  19. package/dist/meeting-minutes-storage.js +402 -0
  20. package/dist/meeting-minutes.d.ts +223 -0
  21. package/dist/meeting-minutes.js +1172 -0
  22. package/dist/world-extension.js +1 -1
  23. package/package.json +46 -3
  24. package/ui/brainbase-tokens.css +35 -35
  25. package/ui/local-web-shell.css +20 -17
  26. package/ui/local-web-shell.js +34 -1
  27. package/ui/meeting-minutes-lineage-http.js +247 -0
  28. package/ui/meeting-minutes-lineage.css +177 -0
  29. package/ui/meeting-minutes-lineage.js +485 -0
  30. package/ui/meeting-minutes-storage.css +170 -0
  31. package/ui/meeting-minutes-storage.js +388 -0
  32. package/ui/meeting-minutes.css +215 -0
  33. package/ui/meeting-minutes.js +622 -0
  34. package/ui/world/world-district.js +306 -87
  35. package/ui/world/world-scenery.js +208 -0
  36. package/ui/world/world-view.css +54 -0
  37. package/ui/world/world-view.js +65 -205
@@ -0,0 +1,312 @@
1
+ import { createHash } from 'node:crypto';
2
+ import { MeetingMinutesError, } from './meeting-minutes.js';
3
+ import { MeetingMinutesStorageControllerError, } from './meeting-minutes-storage-controller.js';
4
+ import { LocalWebHttpError, readJsonObjectBody, requestUrl, writeHttpError, writeJson, } from './local-web-security.js';
5
+ /** HTTP boundary for host supplied native/external minutes placement. */
6
+ export const MEETING_MINUTES_STORAGE_HTTP_VERSION = 'brainbase.meeting-minutes-storage-http.v1';
7
+ export const MEETING_MINUTES_STORAGE_HTTP_PREFIX = '/api/meeting-minutes';
8
+ const DEFAULT_BODY_LIMIT_BYTES = 1024 * 1024;
9
+ const MAX_IDEMPOTENCY_KEY_LENGTH = 200;
10
+ function isRecord(value) {
11
+ return value !== null && typeof value === 'object' && !Array.isArray(value);
12
+ }
13
+ function normalizeBasePath(path) {
14
+ const value = (path ?? MEETING_MINUTES_STORAGE_HTTP_PREFIX).trim().replace(/\/+$/u, '');
15
+ if (!value.startsWith('/') || value === '')
16
+ throw new TypeError('basePath must be an absolute path');
17
+ return value;
18
+ }
19
+ function decodePathSegment(value) {
20
+ try {
21
+ const decoded = decodeURIComponent(value);
22
+ return decoded === '' || decoded.includes('/') ? undefined : decoded;
23
+ }
24
+ catch {
25
+ return undefined;
26
+ }
27
+ }
28
+ function routeFor(pathname, basePath) {
29
+ if (pathname === basePath)
30
+ return { kind: 'collection' };
31
+ if (!pathname.startsWith(`${basePath}/`))
32
+ return undefined;
33
+ const rawSegments = pathname.slice(basePath.length + 1).split('/');
34
+ if (rawSegments.some((segment) => segment === ''))
35
+ return { kind: 'meeting' };
36
+ const segments = rawSegments.map(decodePathSegment);
37
+ if (segments.some((segment) => segment === undefined))
38
+ return { kind: 'meeting' };
39
+ const [meetingId, noun, minutesId, action, versionId] = segments;
40
+ if (!meetingId)
41
+ return { kind: 'meeting' };
42
+ if (segments.length === 1)
43
+ return { kind: 'meeting', meetingId };
44
+ if (segments.length === 2 && noun === 'minutes')
45
+ return { kind: 'minutes', meetingId };
46
+ if (segments.length === 3 && noun === 'minutes' && minutesId)
47
+ return { kind: 'save', meetingId, minutesId };
48
+ if (segments.length === 4 && noun === 'minutes' && minutesId && action === 'confirm') {
49
+ return { kind: 'confirm', meetingId, minutesId };
50
+ }
51
+ if (segments.length === 5 && noun === 'minutes' && minutesId && action === 'versions' && versionId) {
52
+ return { kind: 'version', meetingId, minutesId, versionId };
53
+ }
54
+ return { kind: 'meeting' };
55
+ }
56
+ function canonicalJson(value) {
57
+ if (value === null || typeof value !== 'object')
58
+ return JSON.stringify(value);
59
+ if (Array.isArray(value))
60
+ return `[${value.map(canonicalJson).join(',')}]`;
61
+ const entries = Object.entries(value)
62
+ .sort(([left], [right]) => left.localeCompare(right))
63
+ .map(([key, child]) => `${JSON.stringify(key)}:${canonicalJson(child)}`);
64
+ return `{${entries.join(',')}}`;
65
+ }
66
+ function headerValue(request, name) {
67
+ const value = request.headers[name.toLowerCase()];
68
+ return typeof value === 'string' ? value : undefined;
69
+ }
70
+ function idempotencyKey(request) {
71
+ const value = headerValue(request, 'idempotency-key');
72
+ if (value === undefined)
73
+ return undefined;
74
+ const key = value.trim();
75
+ if (key === '' || key.length > MAX_IDEMPOTENCY_KEY_LENGTH) {
76
+ throw new LocalWebHttpError(422, 'invalid_idempotency_key', 'Idempotency-Key must be 1–200 characters');
77
+ }
78
+ return key;
79
+ }
80
+ function requestFingerprint(value) {
81
+ return `sha256:${createHash('sha256').update(canonicalJson(value), 'utf8').digest('hex')}`;
82
+ }
83
+ function idempotencyFields(key, fingerprint) {
84
+ if (key === undefined || fingerprint === undefined)
85
+ return {};
86
+ return { idempotency_key: key, idempotency_fingerprint: fingerprint };
87
+ }
88
+ function hasOwn(body, key) {
89
+ return Object.prototype.hasOwnProperty.call(body, key);
90
+ }
91
+ function text(value, label) {
92
+ if (typeof value !== 'string' || value.trim() === '')
93
+ throw new MeetingMinutesError('invalid_input', `${label} is required`);
94
+ return value.trim();
95
+ }
96
+ function optionalText(value, label) {
97
+ if (value === undefined)
98
+ return undefined;
99
+ return text(value, label);
100
+ }
101
+ function placement(value, fallbackBody, fallbackLabel) {
102
+ if (value === undefined) {
103
+ return { kind: 'native', body: fallbackBody === undefined ? '' : text(fallbackBody, fallbackLabel) };
104
+ }
105
+ if (!isRecord(value) || (value.kind !== 'native' && value.kind !== 'external')) {
106
+ throw new MeetingMinutesError('invalid_input', 'placement.kind must be native or external');
107
+ }
108
+ if (value.kind === 'native') {
109
+ // Keep an omitted native body distinguishable from an explicit empty body.
110
+ // Rebind uses the former to copy the current exact source under ACL; create
111
+ // and save operations still normalize it to an empty native body in the
112
+ // controller when no body was supplied.
113
+ return { kind: 'native', body: value.body === undefined ? undefined : text(value.body, 'placement.body') };
114
+ }
115
+ if (hasOwn(value, 'body'))
116
+ throw new MeetingMinutesError('invalid_input', 'External placement must not carry a native body');
117
+ return {
118
+ kind: 'external',
119
+ provider: text(value.provider, 'placement.provider'),
120
+ locator: text(value.locator, 'placement.locator'),
121
+ };
122
+ }
123
+ function rebindPlacement(body) {
124
+ if (hasOwn(body, 'placement'))
125
+ return placement(body.placement, body.body, 'body');
126
+ // An omitted body means “keep the current exact version” for native rebind.
127
+ // This lets the controller read an external current body under the current
128
+ // ACL instead of replacing it with an empty native version.
129
+ if (!hasOwn(body, 'body'))
130
+ return { kind: 'native' };
131
+ return { kind: 'native', body: text(body.body, 'body') };
132
+ }
133
+ function createInput(body, key, fingerprint) {
134
+ return {
135
+ title: text(body.title, 'title'),
136
+ scheduled_at: optionalText(body.scheduled_at, 'scheduled_at'),
137
+ ended_at: optionalText(body.ended_at, 'ended_at'),
138
+ participant_ids: Array.isArray(body.participant_ids) ? body.participant_ids : undefined,
139
+ metadata: isRecord(body.metadata) ? body.metadata : undefined,
140
+ minutes_title: optionalText(body.minutes_title, 'minutes_title'),
141
+ minutes_metadata: isRecord(body.minutes_metadata) ? body.minutes_metadata : undefined,
142
+ ...idempotencyFields(key, fingerprint),
143
+ };
144
+ }
145
+ function createMinutesInput(body, key, fingerprint) {
146
+ return {
147
+ title: optionalText(body.title, 'title'),
148
+ metadata: isRecord(body.metadata) ? body.metadata : undefined,
149
+ expected_revision: body.expected_revision,
150
+ ...idempotencyFields(key, fingerprint),
151
+ };
152
+ }
153
+ function saveInput(body, minutesId, meetingId, key, fingerprint) {
154
+ return {
155
+ meeting_id: meetingId,
156
+ minutes_id: minutesId,
157
+ metadata: isRecord(body.metadata) ? body.metadata : undefined,
158
+ expected_revision: body.expected_revision,
159
+ predecessor_version_id: optionalText(body.predecessor_version_id, 'predecessor_version_id'),
160
+ ...idempotencyFields(key, fingerprint),
161
+ };
162
+ }
163
+ function confirmInput(body, meetingId, minutesId, key, fingerprint) {
164
+ return {
165
+ meeting_id: meetingId,
166
+ minutes_id: minutesId,
167
+ version_id: text(body.version_id, 'version_id'),
168
+ expected_revision: body.expected_revision,
169
+ ...idempotencyFields(key, fingerprint),
170
+ };
171
+ }
172
+ function versionPayload(resolved) {
173
+ const body = resolved.body === undefined ? {} : { body: resolved.body };
174
+ return {
175
+ ...resolved.version,
176
+ ...body,
177
+ placement: resolved.placement,
178
+ source_status: resolved.source_status,
179
+ ...(resolved.capabilities ? { capabilities: resolved.capabilities } : {}),
180
+ ...(resolved.source_result ? { source_result: resolved.source_result } : {}),
181
+ ...(resolved.reason ? { reason: resolved.reason } : {}),
182
+ };
183
+ }
184
+ function statusForStorageError(error) {
185
+ switch (error.code) {
186
+ case 'source_denied': return 403;
187
+ case 'source_changed': return 409;
188
+ case 'source_unavailable': return 503;
189
+ case 'idempotency_conflict': return 409;
190
+ case 'invalid_configuration': return 422;
191
+ case 'storage_unavailable': return 500;
192
+ }
193
+ }
194
+ function operationError(error) {
195
+ if (error instanceof MeetingMinutesError) {
196
+ const statusCode = error.code === 'invalid_input' ? 422
197
+ : error.code === 'not_found' ? 404
198
+ : error.code === 'authorization_denied' ? 403
199
+ : error.code === 'revision_conflict' || error.code === 'idempotency_conflict' ? 409 : 500;
200
+ return { statusCode, body: { error: { code: error.code, message: error.message } } };
201
+ }
202
+ if (error instanceof MeetingMinutesStorageControllerError) {
203
+ return { statusCode: statusForStorageError(error), body: { error: { code: error.code, message: error.message } } };
204
+ }
205
+ return { statusCode: 500, body: { error: { code: 'internal_error', message: 'Meeting minutes request failed' } } };
206
+ }
207
+ export function createMeetingMinutesStorageHttpHandler(options) {
208
+ if (!options || !options.store || !options.controller || typeof options.resolveContext !== 'function') {
209
+ throw new TypeError('store, controller and resolveContext are required');
210
+ }
211
+ const basePath = normalizeBasePath(options.basePath);
212
+ const bodyLimitBytes = options.bodyLimitBytes ?? DEFAULT_BODY_LIMIT_BYTES;
213
+ if (!Number.isInteger(bodyLimitBytes) || bodyLimitBytes <= 0)
214
+ throw new TypeError('bodyLimitBytes must be a positive integer');
215
+ async function execute(route, body, context, key, fingerprint) {
216
+ try {
217
+ switch (route.kind) {
218
+ case 'collection': {
219
+ if (body === undefined) {
220
+ return { statusCode: 200, body: { state: 'ready', meetings: await options.store.list_meetings(context), absence_confirmed: true } };
221
+ }
222
+ const input = createInput(body, key, fingerprint);
223
+ return {
224
+ statusCode: 201,
225
+ body: await options.controller.create_meeting(input, placement(body.placement, body.initial_body, 'initial_body'), context),
226
+ };
227
+ }
228
+ case 'meeting':
229
+ return { statusCode: 200, body: await options.store.get_meeting(route.meetingId ?? '', context) };
230
+ case 'minutes': {
231
+ if (body === undefined)
232
+ throw new MeetingMinutesError('invalid_input', 'Request body is required');
233
+ return {
234
+ statusCode: 201,
235
+ body: await options.controller.create_minutes(route.meetingId ?? '', createMinutesInput(body, key, fingerprint), placement(body.placement, body.body, 'body'), context),
236
+ };
237
+ }
238
+ case 'save': {
239
+ if (body === undefined)
240
+ throw new MeetingMinutesError('invalid_input', 'Request body is required');
241
+ const before = await options.store.get_meeting(route.meetingId ?? '', context);
242
+ if (!before.minutes.some((candidate) => candidate.minutes_id === route.minutesId)) {
243
+ throw new MeetingMinutesError('not_found', `Minutes document ${route.minutesId} was not found`);
244
+ }
245
+ const after = await options.controller.rebind(saveInput(body, route.minutesId ?? '', route.meetingId ?? '', key, fingerprint), rebindPlacement(body), context);
246
+ // The controller detail intentionally omits native audit timestamps.
247
+ // Read the canonical store again for lifecycle hooks so they receive
248
+ // the same exact version shape as the native HTTP handler while the
249
+ // response keeps the controller's placement fields.
250
+ const lineageAfter = options.afterSave
251
+ ? await options.store.get_meeting(route.meetingId ?? '', context)
252
+ : undefined;
253
+ const lineage = options.afterSave && lineageAfter
254
+ ? await options.afterSave({ before, after: lineageAfter, context })
255
+ : undefined;
256
+ return {
257
+ statusCode: 201,
258
+ body: lineage === undefined ? after : { ...after, lineage },
259
+ };
260
+ }
261
+ case 'confirm': {
262
+ if (body === undefined)
263
+ throw new MeetingMinutesError('invalid_input', 'Request body is required');
264
+ return {
265
+ statusCode: 200,
266
+ body: await options.controller.confirm_version(confirmInput(body, route.meetingId ?? '', route.minutesId ?? '', key, fingerprint), context),
267
+ };
268
+ }
269
+ case 'version':
270
+ return {
271
+ statusCode: 200,
272
+ body: versionPayload(await options.controller.get_version(route.meetingId ?? '', route.minutesId ?? '', route.versionId ?? '', context)),
273
+ };
274
+ }
275
+ }
276
+ catch (error) {
277
+ return operationError(error);
278
+ }
279
+ }
280
+ return async (request, response) => {
281
+ const path = requestUrl(request).pathname;
282
+ const route = routeFor(path, basePath);
283
+ if (!route)
284
+ return false;
285
+ const method = (request.method ?? 'GET').toUpperCase();
286
+ const allowed = route.kind === 'collection' ? ['GET', 'POST']
287
+ : route.kind === 'meeting' || route.kind === 'version' ? ['GET'] : ['POST'];
288
+ if (!allowed.includes(method)) {
289
+ response.setHeader('Allow', allowed.join(', '));
290
+ writeJson(response, 405, { error: { code: 'method_not_allowed', message: `Use ${allowed.join(' or ')}` } });
291
+ return true;
292
+ }
293
+ try {
294
+ const context = await options.resolveContext(request);
295
+ if (!context || typeof context.principal_id !== 'string' || context.principal_id.trim() === '') {
296
+ throw new LocalWebHttpError(401, 'authorization_required', 'A trusted meeting-minutes principal is required');
297
+ }
298
+ const body = method === 'POST' ? await readJsonObjectBody(request, bodyLimitBytes) : undefined;
299
+ const key = method === 'POST' ? idempotencyKey(request) : undefined;
300
+ const fingerprint = key === undefined ? undefined : requestFingerprint({ method, path, body: body ?? null });
301
+ const result = await execute(route, body, context, key, fingerprint);
302
+ writeJson(response, result.statusCode, result.body);
303
+ }
304
+ catch (error) {
305
+ if (error instanceof LocalWebHttpError)
306
+ writeHttpError(response, error);
307
+ else
308
+ writeJson(response, 500, { error: { code: 'internal_error', message: 'Meeting minutes request failed' } });
309
+ }
310
+ return true;
311
+ };
312
+ }
@@ -0,0 +1,169 @@
1
+ /** The first real provider adapter. Its root is supplied by the customer. */
2
+ export declare const FILESYSTEM_MEETING_MINUTES_ADAPTER: "filesystem";
3
+ export interface MeetingMinutesStorageCapabilities {
4
+ readonly save: false;
5
+ readonly read: true;
6
+ readonly read_only: true;
7
+ readonly history: false;
8
+ /** ACL is supplied by the organization boundary for every operation. */
9
+ readonly current_acl: 'injected';
10
+ readonly retention_delete: false;
11
+ }
12
+ export type MeetingMinutesStorageAuthorizationOperation = 'register' | 'read' | 'read_history' | 'retention_delete';
13
+ /** Context resolved by the host before calling the adapter. */
14
+ export interface MeetingMinutesStorageRequestContext {
15
+ readonly principal_id: string;
16
+ }
17
+ export type MeetingMinutesAuthorizationTarget = {
18
+ readonly locator: string;
19
+ readonly reference?: never;
20
+ } | {
21
+ readonly reference: MeetingMinutesExternalReference;
22
+ readonly locator?: never;
23
+ };
24
+ /** Result produced by the host-injected authorization callback. */
25
+ export interface MeetingMinutesAuthorizationDecision {
26
+ readonly allowed: boolean;
27
+ readonly reason?: string;
28
+ readonly checked_at?: string;
29
+ }
30
+ export type MeetingMinutesAuthorize = (context: MeetingMinutesStorageRequestContext, operation: MeetingMinutesStorageAuthorizationOperation, target: MeetingMinutesAuthorizationTarget) => MeetingMinutesAuthorizationDecision | boolean | Promise<MeetingMinutesAuthorizationDecision | boolean>;
31
+ export interface MeetingMinutesExternalProvenance {
32
+ readonly source: 'external';
33
+ readonly adapter: typeof FILESYSTEM_MEETING_MINUTES_ADAPTER;
34
+ readonly observed_at: string;
35
+ }
36
+ /** A locator and exact source version; it never contains the external body. */
37
+ export interface MeetingMinutesExternalReference {
38
+ readonly provider: string;
39
+ /** Root-relative POSIX path for the filesystem adapter. */
40
+ readonly locator: string;
41
+ /** Content-addressed revision because a plain filesystem has no history API. */
42
+ readonly revision: string;
43
+ readonly digest: string;
44
+ readonly provenance: MeetingMinutesExternalProvenance;
45
+ }
46
+ export interface FilesystemMeetingMinutesAdapterOptions {
47
+ /** Absolute customer-configured root. No default is inferred. */
48
+ readonly root: string;
49
+ readonly provider?: string;
50
+ /** Host-owned ACL check; called before every register/read/history/delete operation. */
51
+ readonly authorize: MeetingMinutesAuthorize;
52
+ /** Maximum external source size accepted by the read-only adapter. */
53
+ readonly max_bytes?: number;
54
+ }
55
+ export interface MeetingMinutesRegisterRequest {
56
+ readonly locator: string;
57
+ readonly request_context: MeetingMinutesStorageRequestContext;
58
+ }
59
+ export interface MeetingMinutesReadRequest {
60
+ readonly reference: MeetingMinutesExternalReference;
61
+ readonly request_context: MeetingMinutesStorageRequestContext;
62
+ }
63
+ /**
64
+ * Read a source reference persisted by the provider-independent minutes core.
65
+ *
66
+ * The core deliberately keeps only provider/locator/revision/digest. The
67
+ * adapter recreates its local provenance for this read; the observed source
68
+ * body still has to pass the exact revision and digest checks below.
69
+ */
70
+ export interface MeetingMinutesReadSourceRequest {
71
+ readonly source_ref: {
72
+ readonly provider: string;
73
+ readonly locator: string;
74
+ readonly revision: string;
75
+ readonly digest: string;
76
+ readonly provenance?: Readonly<Record<string, unknown>>;
77
+ };
78
+ readonly request_context: MeetingMinutesStorageRequestContext;
79
+ }
80
+ export interface MeetingMinutesSaveRequest {
81
+ readonly reference?: MeetingMinutesExternalReference;
82
+ readonly body?: string;
83
+ }
84
+ export interface MeetingMinutesDeleteRequest {
85
+ readonly reference: MeetingMinutesExternalReference;
86
+ readonly request_context: MeetingMinutesStorageRequestContext;
87
+ }
88
+ export type MeetingMinutesUnavailableStatus = 'not_found' | 'symlink_rejected' | 'unavailable' | 'too_large' | 'invalid_encoding';
89
+ export interface MeetingMinutesRegisterResultBase {
90
+ readonly capabilities: MeetingMinutesStorageCapabilities;
91
+ }
92
+ export type MeetingMinutesRegisterResult = MeetingMinutesRegisterResultBase & ({
93
+ readonly status: 'registered';
94
+ readonly reference: MeetingMinutesExternalReference;
95
+ } | {
96
+ readonly status: 'denied' | 'invalid_locator' | MeetingMinutesUnavailableStatus;
97
+ readonly reason: string;
98
+ });
99
+ export type MeetingMinutesReadResult = MeetingMinutesRegisterResultBase & ({
100
+ readonly status: 'available';
101
+ readonly reference: MeetingMinutesExternalReference;
102
+ readonly body: string;
103
+ } | {
104
+ readonly status: 'denied' | 'invalid_reference' | MeetingMinutesUnavailableStatus;
105
+ readonly reason: string;
106
+ } | {
107
+ readonly status: 'integrity_mismatch';
108
+ readonly reason: string;
109
+ /** Metadata only; the changed body is deliberately not returned. */
110
+ readonly observed_reference: MeetingMinutesExternalReference;
111
+ });
112
+ export type MeetingMinutesUnsupportedOperation = MeetingMinutesRegisterResultBase & {
113
+ readonly status: 'unsupported';
114
+ readonly operation: 'save' | 'read_history' | 'retention_delete';
115
+ readonly capability: 'save' | 'history' | 'retention_delete';
116
+ readonly reason: string;
117
+ };
118
+ export type MeetingMinutesHistoryResult = MeetingMinutesUnsupportedOperation | (MeetingMinutesRegisterResultBase & {
119
+ readonly status: 'denied';
120
+ readonly reason: string;
121
+ });
122
+ export type MeetingMinutesDeleteResult = MeetingMinutesUnsupportedOperation | (MeetingMinutesRegisterResultBase & {
123
+ readonly status: 'denied';
124
+ readonly reason: string;
125
+ });
126
+ export interface MeetingMinutesStorageAdapter {
127
+ readonly capabilities: MeetingMinutesStorageCapabilities;
128
+ register(input: MeetingMinutesRegisterRequest): Promise<MeetingMinutesRegisterResult>;
129
+ read(input: MeetingMinutesReadRequest): Promise<MeetingMinutesReadResult>;
130
+ /** Resolve a core source_ref without asking callers to fabricate provenance. */
131
+ read_source?(input: MeetingMinutesReadSourceRequest): Promise<MeetingMinutesReadResult>;
132
+ read_history(input: MeetingMinutesReadRequest): Promise<MeetingMinutesHistoryResult>;
133
+ save(input: MeetingMinutesSaveRequest): Promise<MeetingMinutesUnsupportedOperation>;
134
+ delete(input: MeetingMinutesDeleteRequest): Promise<MeetingMinutesDeleteResult>;
135
+ }
136
+ /**
137
+ * Read-only adapter for a customer-selected filesystem root.
138
+ *
139
+ * The adapter keeps no native copy and performs no external write. A plain
140
+ * filesystem has no historical version contract, so old references fail with
141
+ * an integrity mismatch when the current file changes.
142
+ */
143
+ export declare class FilesystemMeetingMinutesAdapter implements MeetingMinutesStorageAdapter {
144
+ readonly capabilities: MeetingMinutesStorageCapabilities;
145
+ private readonly root;
146
+ private readonly provider;
147
+ private readonly authorize;
148
+ private readonly maxBytes;
149
+ constructor(options: FilesystemMeetingMinutesAdapterOptions);
150
+ register(input: MeetingMinutesRegisterRequest): Promise<MeetingMinutesRegisterResult>;
151
+ read(input: MeetingMinutesReadRequest): Promise<MeetingMinutesReadResult>;
152
+ read_source(input: MeetingMinutesReadSourceRequest): Promise<MeetingMinutesReadResult>;
153
+ read_history(input: MeetingMinutesReadRequest): Promise<MeetingMinutesHistoryResult>;
154
+ save(_input: MeetingMinutesSaveRequest): Promise<MeetingMinutesUnsupportedOperation>;
155
+ delete(input: MeetingMinutesDeleteRequest): Promise<MeetingMinutesDeleteResult>;
156
+ private authorizationFailure;
157
+ private readStableFile;
158
+ private registerFailure;
159
+ private readFailure;
160
+ private unsupported;
161
+ private reference;
162
+ private referenceFailure;
163
+ private resolveFile;
164
+ }
165
+ export declare class MeetingMinutesStorageError extends Error {
166
+ readonly code: 'invalid_configuration';
167
+ constructor(code: 'invalid_configuration', message: string);
168
+ }
169
+ export declare function createFilesystemMeetingMinutesAdapter(options: FilesystemMeetingMinutesAdapterOptions): FilesystemMeetingMinutesAdapter;