@onesi/genesis 0.2.4 → 0.2.6

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.
@@ -84,6 +84,16 @@ export interface HivesPresignedUrl {
84
84
  required_headers?: Record<string, string>;
85
85
  }
86
86
  export declare function hivesObjectKey(key: string): string;
87
+ /** A replay of the same Idempotency-Key: Hives answers with the original settled receipt and no result. */
88
+ export interface HivesStorageWriteReplay {
89
+ replayed: true;
90
+ key: string;
91
+ bucket_id: string;
92
+ operation_id: string;
93
+ state: 'settled';
94
+ storage_operation_id: string | null;
95
+ funding_source: string | null;
96
+ }
87
97
  export declare function hivesCells(client: GenesisClient): {
88
98
  list(project: string, query?: {
89
99
  page?: number;
@@ -114,18 +124,18 @@ export declare function hivesCells(client: GenesisClient): {
114
124
  }, options?: HivesResourceOptions): Promise<HivesObjectList>;
115
125
  put(project: string, id: string, key: string, bytes: Blob | ArrayBuffer | ArrayBufferView, options?: HivesFundedWriteOptions & {
116
126
  content_type?: string;
117
- }): Promise<HivesObjectAcknowledgement>;
127
+ }): Promise<HivesObjectAcknowledgement | HivesStorageWriteReplay>;
118
128
  remove: (project: string, id: string, key: string, options?: HivesResourceOptions) => Promise<void>;
119
129
  download: (project: string, id: string, key: string, options?: HivesResourceOptions) => Promise<Response>;
120
130
  /** Existing flat-key path compatibility; nested keys use canonical collection methods above. */
121
131
  putFlat(project: string, id: string, key: string, bytes: Blob | ArrayBuffer | ArrayBufferView, options?: HivesFundedWriteOptions & {
122
132
  content_type?: string;
123
- }): Promise<HivesObjectAcknowledgement>;
133
+ }): Promise<HivesObjectAcknowledgement | HivesStorageWriteReplay>;
124
134
  removeFlat: (project: string, id: string, key: string, options?: HivesResourceOptions) => Promise<void>;
125
135
  /** The flat key "download" on its own static route, which shadows /objects/{key} for that key. */
126
136
  putDownload(project: string, id: string, bytes: Blob | ArrayBuffer | ArrayBufferView, options?: HivesFundedWriteOptions & {
127
137
  content_type?: string;
128
- }): Promise<HivesObjectAcknowledgement>;
138
+ }): Promise<HivesObjectAcknowledgement | HivesStorageWriteReplay>;
129
139
  removeDownload: (project: string, id: string, options?: HivesResourceOptions) => Promise<void>;
130
140
  };
131
141
  presign(project: string, id: string, body: HivesPresign, options?: HivesResourceOptions): Promise<HivesPresignedUrl>;
@@ -1,3 +1,4 @@
1
+ import { GenesisError } from './errors.js';
1
2
  import { HIVES_OBJECT_BYTES, hivesFundedWriteHeaders, hivesResourceCall, hivesResourceId, hivesObjectBody, hivesContentType, resourceError } from './hives-resources-transport.js';
2
3
  export function hivesObjectKey(key) {
3
4
  const bytes = new TextEncoder().encode(key);
@@ -59,11 +60,28 @@ function object(value, id) {
59
60
  hivesObjectKey(value.key);
60
61
  return Object.fromEntries(['key', 'bucket_id', 'size_bytes', 'etag', 'last_modified', 'generation', 'content_type', 'metadata', 'metadata_scope'].map(k => [k, value[k]]));
61
62
  }
62
- function acknowledgement(answer, key, id, size) {
63
+ const OPERATION_ID = /^fop_[0-9a-f]{32}$/, STATE = /^[a-z][a-z_]{0,31}$/;
64
+ function acknowledgement(answer, key, id, size, operation) {
63
65
  // A funded write answers with its settled storage operation receipt; the acknowledgement is its result.
64
66
  const receipt = answer;
65
- if (receipt && receipt.object !== undefined && (receipt.object !== 'storage_write_operation' || receipt.state !== 'settled'))
66
- throw resourceError(0);
67
+ if (receipt && receipt.object !== undefined) {
68
+ if (receipt.object !== 'storage_write_operation')
69
+ throw resourceError(0);
70
+ const operation_id = typeof receipt.operation_id === 'string' && OPERATION_ID.test(receipt.operation_id) ? receipt.operation_id : undefined;
71
+ if (receipt.state !== 'settled') {
72
+ const state = typeof receipt.state === 'string' && STATE.test(receipt.state) ? receipt.state : 'unknown';
73
+ throw new GenesisError({ status: 0, type: 'invalid_request', code: 'HIVES_STORAGE_WRITE_NOT_SETTLED', operationId: operation, body: { operation_id: operation_id ?? null, state },
74
+ message: ['denied', 'released'].includes(state)
75
+ ? `Hives did not perform this storage write (state ${state}${operation_id ? `, operation_id ${operation_id}` : ''}). This outcome is final: a replay of the same Idempotency-Key returns it again. No automatic retry was made.`
76
+ : `Hives has not settled this storage write (state ${state}${operation_id ? `, operation_id ${operation_id}` : ''}). Replay the same Idempotency-Key to read its outcome; no automatic retry was made.` });
77
+ }
78
+ if (receipt.result === undefined) {
79
+ if (!operation_id)
80
+ throw resourceError(0);
81
+ const text = (v) => typeof v === 'string' && v.length <= 256 ? v : null;
82
+ return { replayed: true, key, bucket_id: id, operation_id, state: 'settled', storage_operation_id: text(receipt.storage_operation_id), funding_source: text(receipt.funding_source) };
83
+ }
84
+ }
67
85
  const value = (receipt?.object === undefined ? answer : receipt.result);
68
86
  if (!value || value.bucket_id !== id || value.key !== key || value.size_bytes !== size || !Number.isSafeInteger(value.size_bytes) || value.size_bytes < 0 || typeof value.etag !== 'string' || typeof value.generation !== 'string' || value.content_type !== null && typeof value.content_type !== 'string')
69
87
  throw resourceError(0);
@@ -120,7 +138,7 @@ export function hivesCells(client) {
120
138
  async put(project, id, key, bytes, options = {}) {
121
139
  const body = await hivesObjectBody(bytes);
122
140
  const content_type = hivesContentType(options.content_type);
123
- return acknowledgement(await write('hives_upload_object_by_key', { ...ids(project, id), key_base64url: hivesObjectKey(key), body, content_type }, options), key, id, body.byteLength);
141
+ return acknowledgement(await write('hives_upload_object_by_key', { ...ids(project, id), key_base64url: hivesObjectKey(key), body, content_type }, options), key, id, body.byteLength, 'hives_upload_object_by_key');
124
142
  },
125
143
  remove: (project, id, key, options) => call('hives_delete_object_by_key', { ...ids(project, id), key_base64url: hivesObjectKey(key) }, options, 'empty'),
126
144
  download: (project, id, key, options) => call('hives_download_object_by_key', { ...ids(project, id), key_base64url: hivesObjectKey(key) }, options, 'binary', HIVES_OBJECT_BYTES),
@@ -130,14 +148,14 @@ export function hivesCells(client) {
130
148
  if (!/^[A-Za-z0-9_-]+(?:\.[A-Za-z0-9_-]+)*$/.test(key))
131
149
  throw resourceError(0);
132
150
  const body = await hivesObjectBody(bytes);
133
- return acknowledgement(await write('hives_upload_object', { ...ids(project, id), key, body, content_type: hivesContentType(options.content_type) }, options), key, id, body.byteLength);
151
+ return acknowledgement(await write('hives_upload_object', { ...ids(project, id), key, body, content_type: hivesContentType(options.content_type) }, options), key, id, body.byteLength, 'hives_upload_object');
134
152
  },
135
153
  removeFlat: (project, id, key, options) => { hivesObjectKey(key); if (!/^[A-Za-z0-9_-]+(?:\.[A-Za-z0-9_-]+)*$/.test(key))
136
154
  throw resourceError(0); return call('hives_delete_object', { ...ids(project, id), key }, options, 'empty'); },
137
155
  /** The flat key "download" on its own static route, which shadows /objects/{key} for that key. */
138
156
  async putDownload(project, id, bytes, options = {}) {
139
157
  const body = await hivesObjectBody(bytes);
140
- return acknowledgement(await write('hives_upload_object_named_download', { ...ids(project, id), body, content_type: hivesContentType(options.content_type) }, options), 'download', id, body.byteLength);
158
+ return acknowledgement(await write('hives_upload_object_named_download', { ...ids(project, id), body, content_type: hivesContentType(options.content_type) }, options), 'download', id, body.byteLength, 'hives_upload_object_named_download');
141
159
  },
142
160
  removeDownload: (project, id, options) => call('hives_delete_object_named_download', ids(project, id), options, 'empty'),
143
161
  },
@@ -2,6 +2,8 @@ import { Genesis } from './genesis.js';
2
2
  import type { GenesisOptions } from './client.js';
3
3
  export declare function closeHivesNodeTransport(): Promise<void>;
4
4
  export declare function hivesOperationBudget(id: string): number;
5
+ /** Live log streams must start within 45 s (15 s heartbeat + 15 s window + margin). The 330 s budget runs from the start of the request, so the body gets at least 285 s. */
6
+ export declare function hivesHeaderBudget(id: string): number | undefined;
5
7
  /** Hives leaf transport. Caller fetch overrides stay authoritative; only native
6
8
  * global fetch gets our Agent. Every operation retains an overall body deadline. */
7
9
  export declare function createHivesNodeTransport(opts?: GenesisOptions, callerSignal?: AbortSignal): typeof fetch;
@@ -25,6 +25,10 @@ export function hivesOperationBudget(id) {
25
25
  return 100_000;
26
26
  return 20_000;
27
27
  }
28
+ /** Live log streams must start within 45 s (15 s heartbeat + 15 s window + margin). The 330 s budget runs from the start of the request, so the body gets at least 285 s. */
29
+ export function hivesHeaderBudget(id) {
30
+ return id === 'hives_stream_logs' || id === 'hives_stream_deployment_logs' ? 45_000 : undefined;
31
+ }
28
32
  const escape = (value) => value.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
29
33
  const templatePattern = (value) => new RegExp('^' + value.split(/(\{[^}]+\})/).map((part) => part.startsWith('{') ? '[^/]+' : escape(part)).join('') + '$');
30
34
  /** Hives leaf transport. Caller fetch overrides stay authoritative; only native
@@ -79,6 +83,12 @@ export function createHivesNodeTransport(opts = {}, callerSignal) {
79
83
  controller.abort();
80
84
  }, hivesOperationBudget(route.id));
81
85
  timer.unref();
86
+ const headerBudget = hivesHeaderBudget(route.id);
87
+ const headerTimer = headerBudget === undefined ? undefined : setTimeout(() => {
88
+ message = 'Hives stream did not start within 45 s. Retry the stream.';
89
+ controller.abort();
90
+ }, headerBudget);
91
+ headerTimer?.unref();
82
92
  let abortReject = () => { };
83
93
  let reader;
84
94
  let bodyController;
@@ -93,6 +103,7 @@ export function createHivesNodeTransport(opts = {}, callerSignal) {
93
103
  void abortPromise.catch(() => { });
94
104
  const cleanup = () => {
95
105
  clearTimeout(timer);
106
+ clearTimeout(headerTimer);
96
107
  for (const signal of signals)
97
108
  signal.removeEventListener('abort', canceled);
98
109
  controller.signal.removeEventListener('abort', aborted);
@@ -113,6 +124,7 @@ export function createHivesNodeTransport(opts = {}, callerSignal) {
113
124
  void pending.then((response) => { if (controller.signal.aborted)
114
125
  void response.body?.cancel().catch(() => { }); }, () => { });
115
126
  const response = await Promise.race([pending, abortPromise]);
127
+ clearTimeout(headerTimer);
116
128
  if (controller.signal.aborted)
117
129
  throw safeError();
118
130
  if (!response.body) {
@@ -31,16 +31,16 @@ export declare function hivesOperations(client: GenesisClient): {
31
31
  }, options?: import("./hives-resources-transport.js").HivesResourceOptions): Promise<import("./hives-cells.js").HivesObjectList>;
32
32
  put(project: string, id: string, key: string, bytes: Blob | ArrayBuffer | ArrayBufferView, options?: import("./hives-resources-transport.js").HivesFundedWriteOptions & {
33
33
  content_type?: string;
34
- }): Promise<import("./hives-cells.js").HivesObjectAcknowledgement>;
34
+ }): Promise<import("./hives-cells.js").HivesObjectAcknowledgement | import("./hives-cells.js").HivesStorageWriteReplay>;
35
35
  remove: (project: string, id: string, key: string, options?: import("./hives-resources-transport.js").HivesResourceOptions) => Promise<void>;
36
36
  download: (project: string, id: string, key: string, options?: import("./hives-resources-transport.js").HivesResourceOptions) => Promise<Response>;
37
37
  putFlat(project: string, id: string, key: string, bytes: Blob | ArrayBuffer | ArrayBufferView, options?: import("./hives-resources-transport.js").HivesFundedWriteOptions & {
38
38
  content_type?: string;
39
- }): Promise<import("./hives-cells.js").HivesObjectAcknowledgement>;
39
+ }): Promise<import("./hives-cells.js").HivesObjectAcknowledgement | import("./hives-cells.js").HivesStorageWriteReplay>;
40
40
  removeFlat: (project: string, id: string, key: string, options?: import("./hives-resources-transport.js").HivesResourceOptions) => Promise<void>;
41
41
  putDownload(project: string, id: string, bytes: Blob | ArrayBuffer | ArrayBufferView, options?: import("./hives-resources-transport.js").HivesFundedWriteOptions & {
42
42
  content_type?: string;
43
- }): Promise<import("./hives-cells.js").HivesObjectAcknowledgement>;
43
+ }): Promise<import("./hives-cells.js").HivesObjectAcknowledgement | import("./hives-cells.js").HivesStorageWriteReplay>;
44
44
  removeDownload: (project: string, id: string, options?: import("./hives-resources-transport.js").HivesResourceOptions) => Promise<void>;
45
45
  };
46
46
  presign(project: string, id: string, body: import("./hives-cells.js").HivesPresign, options?: import("./hives-resources-transport.js").HivesResourceOptions): Promise<import("./hives-cells.js").HivesPresignedUrl>;
@@ -0,0 +1,19 @@
1
+ import { GenesisError } from './errors.js';
2
+ import type { OperationMeta } from './types.js';
3
+ /** The one description of a Hives `project_id` (spec, CLI help and MCP schemas). */
4
+ export declare const HIVES_PROJECT_ID_DESCRIPTION = "Hives project id (prj_\u2026)";
5
+ /** Appended where an omitted project_id resolves from the selected Genesis context. */
6
+ export declare const HIVES_PROJECT_ID_DEFAULT = "omit it to use the Hives project bound to your selected Genesis context";
7
+ /** A Genesis project id (`pr_…`). A Hives project id is `prj_…`, so the two never collide. */
8
+ export declare const isGenesisProjectId: (value: unknown) => value is string;
9
+ /** Hives operations addressed by a Hives project in their path. */
10
+ export declare const isHivesProjectOperation: (op: OperationMeta | undefined) => boolean;
11
+ /** A Genesis pr_… id given where Hives wants its own prj_… id: refused before any request. */
12
+ export declare function hivesProjectIdRefusal(value: string, operationId?: string): GenesisError;
13
+ /**
14
+ * Map a Hives project argument to a Hives project id. A prj_… (or any non-Genesis) value is returned unchanged.
15
+ * Omitted, or a Genesis pr_… id that is the selected context's project, resolves to that selection's Hives binding
16
+ * read from `hives_get_context_project`; anything else is refused with how to find the prj_… id. When the binding
17
+ * came from a cache and a pr_… id does not match it, `reread` (if given) reads the selection once more before refusing.
18
+ */
19
+ export declare function resolveHivesProjectId(value: unknown, readContext: () => Promise<unknown>, reread?: () => Promise<unknown> | undefined): Promise<string>;
@@ -0,0 +1,62 @@
1
+ import { GenesisError } from './errors.js';
2
+ /** The one description of a Hives `project_id` (spec, CLI help and MCP schemas). */
3
+ export const HIVES_PROJECT_ID_DESCRIPTION = 'Hives project id (prj_…)';
4
+ /** Appended where an omitted project_id resolves from the selected Genesis context. */
5
+ export const HIVES_PROJECT_ID_DEFAULT = 'omit it to use the Hives project bound to your selected Genesis context';
6
+ /** A Genesis project id (`pr_…`). A Hives project id is `prj_…`, so the two never collide. */
7
+ export const isGenesisProjectId = (value) => typeof value === 'string' && /^pr_[A-Za-z0-9]{1,124}$/.test(value);
8
+ const HIVES_PROJECT = /^prj_[A-Za-z0-9]{1,124}$/;
9
+ /** Hives operations addressed by a Hives project in their path. */
10
+ export const isHivesProjectOperation = (op) => op?.service === 'hives' && op.pathParams.includes('project_id');
11
+ const FIND = 'Find the Hives project bound to your selected Genesis context with `genesis hives context get` (SDK: genesis.hives.resolveProject()), or list Hives projects with `genesis hives projects list`.';
12
+ /** A Genesis pr_… id given where Hives wants its own prj_… id: refused before any request. */
13
+ export function hivesProjectIdRefusal(value, operationId) {
14
+ return new GenesisError({ status: 0, type: 'invalid_request', code: 'HIVES_PROJECT_ID_REQUIRED', operationId,
15
+ message: `${value} is a Genesis project id. Hives operations take a Hives project id (prj_…). ${FIND}` });
16
+ }
17
+ const notBound = (message, from) => new GenesisError({ status: from?.status ?? 0, type: 'invalid_request', code: 'HIVES_PROJECT_NOT_BOUND',
18
+ message, requestId: from?.requestId, operationId: 'hives_get_context_project' });
19
+ /**
20
+ * Map a Hives project argument to a Hives project id. A prj_… (or any non-Genesis) value is returned unchanged.
21
+ * Omitted, or a Genesis pr_… id that is the selected context's project, resolves to that selection's Hives binding
22
+ * read from `hives_get_context_project`; anything else is refused with how to find the prj_… id. When the binding
23
+ * came from a cache and a pr_… id does not match it, `reread` (if given) reads the selection once more before refusing.
24
+ */
25
+ export async function resolveHivesProjectId(value, readContext, reread) {
26
+ if (value !== undefined && value !== null && value !== '' && !isGenesisProjectId(value)) {
27
+ if (typeof value !== 'string')
28
+ throw new GenesisError({ status: 0, type: 'invalid_request', code: 'HIVES_PROJECT_ID_REQUIRED', message: `project_id must be a ${HIVES_PROJECT_ID_DESCRIPTION}.` });
29
+ return value;
30
+ }
31
+ const load = async (read) => {
32
+ try {
33
+ return binding(await read());
34
+ }
35
+ catch (e) {
36
+ if (!(e instanceof GenesisError) || ![404, 409].includes(e.status))
37
+ throw e;
38
+ const named = isGenesisProjectId(value) ? `${value} is a Genesis project id and ` : '';
39
+ throw notBound(e.status === 404
40
+ ? `${named}No Hives project is bound to your selected Genesis context. Bind one with \`genesis hives context create <name>\` or \`genesis hives context link <prj_…>\`, or pass a Hives project id (prj_…) from \`genesis hives projects list\`.`
41
+ : `${named}No Genesis project is selected. Select an organization, workspace and project with \`genesis context select\`, or pass a Hives project id (prj_…) from \`genesis hives projects list\`.`, e);
42
+ }
43
+ };
44
+ let { selected, bound } = await load(readContext);
45
+ if (isGenesisProjectId(value) && value !== selected && reread) {
46
+ const fresh = reread();
47
+ if (fresh)
48
+ ({ selected, bound } = await load(() => fresh));
49
+ }
50
+ if (isGenesisProjectId(value) && value !== selected) {
51
+ throw notBound(`${value} is a Genesis project id, and it is not your selected Genesis project (${selected}, bound to Hives project ${bound}). Select it with \`genesis context select\`, or pass its Hives project id (prj_…) from \`genesis hives projects list\`.`);
52
+ }
53
+ return bound;
54
+ }
55
+ function binding(context) {
56
+ const row = context && typeof context === 'object' ? context : {};
57
+ const selected = row.selectedContext?.projectId, bound = row.binding?.hivesProjectId;
58
+ if (typeof bound !== 'string' || !HIVES_PROJECT.test(bound) || typeof selected !== 'string') {
59
+ throw new GenesisError({ status: 0, type: 'invalid_response', operationId: 'hives_get_context_project', message: 'Hives returned an invalid context binding.' });
60
+ }
61
+ return { selected, bound };
62
+ }
@@ -7,7 +7,7 @@ export interface HivesSseEvent {
7
7
  export interface HivesSseWindowOptions {
8
8
  /** Stop after this many events (1-1000, default 100). */
9
9
  maxEvents?: number;
10
- /** Stop after this many seconds (1-15, default 10). The CLI and MCP Hives node transport allows a body 20 s, so a window stays below it. */
10
+ /** Stop after this many seconds (1-15, default 10). The CLI and MCP Hives node transport waits at most 45 s for a log stream to start, within a 330 s budget from the start of the request (at least 285 s of body), so a window always fits. */
11
11
  seconds?: number;
12
12
  signal?: AbortSignal;
13
13
  }
package/dist/index.d.ts CHANGED
@@ -11,6 +11,7 @@ export type { paths, operations, components } from './generated/schema.js';
11
11
  export { hivesUploadBody, MAX_WASM_BYTES, type HivesUploadOptions } from './hives-upload.js';
12
12
  export { validateHivesInvocation, hivesFunctionResponseHeaders, HivesOperationError, MAX_FUNCTION_BODY_BYTES, MAX_FUNCTION_JSON_BYTES, type HivesInvokeRequest, type HivesInvokeOptions, type HivesOperationStatus } from './hives-functions.js';
13
13
  export { hivesResponseBytes } from './hives-response.js';
14
+ export { HIVES_PROJECT_ID_DESCRIPTION, HIVES_PROJECT_ID_DEFAULT, isGenesisProjectId, isHivesProjectOperation, hivesProjectIdRefusal } from './hives-project.js';
14
15
  export { runnerModule, type RunnerStartInput, type RunnerSnapshot, type RunnerCatalog, type RunnerEvent, type RunnerProviderResult, type RunnerRequestOptions } from './runner.js';
15
16
  export type { RunnerSelfExecutionGrantInput, RunnerSelfExecutionGrant } from './runner-grant.js';
16
17
  export { beaconsModule, BEACONS_RESOURCE_FAMILIES, BEACONS_CLIENT_PLATFORMS, type BeaconsResourceFamily } from './beacons.js';
@@ -29,7 +30,7 @@ export { scopesModule } from './scopes.js';
29
30
  export { billingModule, gatewayModule, orgsModule } from './gateway.js';
30
31
  export { boxesModule } from './boxes.js';
31
32
  export { dialsModule, dialsEmergencyAddressFeedback, type DialsEmergencyAddressFeedback, type DialsScope, type DialsOutboundCall, type DialsCallControl, type DialsEmergencyAddress, type DialsCallListItem, type DialsCallDispatchState, type DialsMessage, type DialsMessageTemplate, type DialsMessagingBrand, type DialsMessagingCampaign, type DialsWorkflowDocument, type DialsRoutingAction, type DialsRoutingRule, type DialsRoutingRuleReceipt, type DialsOutboundCallReceipt, type DialsRecoveredOutboundCallReceipt } from './dials.js';
32
- export { hivesObjectKey, type HivesBucket, type HivesObject, type HivesObjectAcknowledgement, type HivesObjectList, type HivesCorsRule, type HivesCreateBucket, type HivesPresign, type HivesPresignedUrl } from './hives-cells.js';
33
+ export { hivesObjectKey, type HivesBucket, type HivesObject, type HivesObjectAcknowledgement, type HivesStorageWriteReplay, type HivesObjectList, type HivesCorsRule, type HivesCreateBucket, type HivesPresign, type HivesPresignedUrl } from './hives-cells.js';
33
34
  export { hivesSseEvents, hivesSseReader, type HivesLogStreamEvent, type HivesLogStreamQuery, type HivesLogWindow, type HivesLogWindowOptions } from './hives-platform.js';
34
35
  export { hivesQueueBody, type HivesQueue, type HivesQueueConfig, type HivesQueueCreate, type HivesQueueSend, type HivesQueueDelivery, type HivesDeadLetter, type HivesDeadLetterStats } from './hives-queues.js';
35
36
  export type { HivesRuntime, HivesRuntimeSelection } from './hives-runtimes.js';
package/dist/index.js CHANGED
@@ -9,6 +9,7 @@ export { operationTable, services } from './generated/operations.js';
9
9
  export { hivesUploadBody, MAX_WASM_BYTES } from './hives-upload.js';
10
10
  export { validateHivesInvocation, hivesFunctionResponseHeaders, HivesOperationError, MAX_FUNCTION_BODY_BYTES, MAX_FUNCTION_JSON_BYTES } from './hives-functions.js';
11
11
  export { hivesResponseBytes } from './hives-response.js';
12
+ export { HIVES_PROJECT_ID_DESCRIPTION, HIVES_PROJECT_ID_DEFAULT, isGenesisProjectId, isHivesProjectOperation, hivesProjectIdRefusal } from './hives-project.js';
12
13
  export { runnerModule } from './runner.js';
13
14
  export { beaconsModule, BEACONS_RESOURCE_FAMILIES, BEACONS_CLIENT_PLATFORMS } from './beacons.js';
14
15
  export { rigsModule, panesModule, shellsModule } from './instances.js';
package/dist/minds.js CHANGED
@@ -35,7 +35,7 @@ export async function mindsTransport(options) {
35
35
  const id = options.operationId, input = options.input, write = writes.has(id);
36
36
  let dispatchAttempted = false;
37
37
  const error = (status, type, code, unknownOutcome = false) => new GenesisError({ status, type, code, operationId: id,
38
- message: status === 403 ? code === 'HUMAN_SANDBOX_SELECTION_REQUIRED'
38
+ message: status === 410 ? 'This Mind was deleted; its data is archived. It cannot be resumed or used.' : status === 403 ? code === 'HUMAN_SANDBOX_SELECTION_REQUIRED'
39
39
  ? 'HUMAN_SANDBOX_SELECTION_REQUIRED: Minds accepts an original human One account session with a Console Sandbox selection, or organization API keys with a selected (bound) context; M2M tokens are refused.'
40
40
  : code === 'SCOPE_MISSING' ? 'SCOPE_MISSING: this organization API key lacks the Minds scope the operation needs (minds:read, minds:write or minds:manage; minds:manage does not imply read).'
41
41
  : 'Minds requires an admitted original human One account session with a Console Sandbox selection, or an organization API key with the operation\'s Minds scope.'
@@ -182,7 +182,7 @@ export async function mindsTransport(options) {
182
182
  const code = detail && Object.hasOwn(detail, 'code') && typeof detail.code === 'string' ? detail.code : undefined;
183
183
  const unknownOutcome = lifecycle.includes(id) && response.status === 401 && code === 'SELECTION_EXPIRED'
184
184
  && detail !== undefined && Object.hasOwn(detail, 'outcome') && detail.outcome === 'unknown';
185
- throw error(response.status, 'minds_error', code, unknownOutcome || id === 'minds_createWebhookDelivery' && detail?.outcome === 'unknown');
185
+ throw error(response.status, response.status === 410 ? 'gone' : 'minds_error', code, unknownOutcome || id === 'minds_createWebhookDelivery' && detail?.outcome === 'unknown');
186
186
  }
187
187
  if (['minds_createMind', ...lifecycle, 'minds_createWebhookDelivery'].includes(id) && response.status !== 202)
188
188
  throw error(response.status, 'invalid_response');
package/dist/urls.js CHANGED
@@ -41,34 +41,54 @@ const ENCODED_USERINFO = /(%(?:25){0,2}2F%(?:25){0,2}2F)(?:(?!%(?:25){0,2}2F)[^\
41
41
  const BARE_USERINFO = /(^|[\s"'(<=,;[{])[^\s/?#@@:"'<>\\]{1,64}:[^\s/?#"'<>\\]{0,128}[@@](?=[\w[])/g;
42
42
  /** Every place a userinfo can end: `@`, fullwidth `@`, or an encoded `%40` / `%2540`. */
43
43
  const AT_HITS = /[@@]|%(?:25){0,2}40/gi;
44
+ /** The scheme-less form can only end at an `@` or `@` that is followed by a word character or `[` (see BARE_USERINFO). */
45
+ const BARE_HITS = /[@@](?=[\w[])/g;
44
46
  /** The longest URL-form match (slashes and userinfo, encoded) is under 640 characters; the bare form is under 200. */
45
47
  const URL_WINDOW = 700;
46
48
  const BARE_WINDOW = 200;
47
- /** Apply `fn` to the stretches of `text` within `window` characters before an `@`-like hit, and copy the rest as it is. */
48
- function overHits(text, window, fn) {
49
- AT_HITS.lastIndex = 0;
50
- let hit = AT_HITS.exec(text);
51
- if (!hit)
49
+ /** With at least one hit per DENSE characters the windows would cover the text anyway, so it is scanned whole. The regexes are linear on their own; the windows only skip stretches that cannot hold a userinfo. */
50
+ const DENSE = 64;
51
+ /** Start and end of every hit of `re` in `text`, flattened; `null` when they are dense. */
52
+ function hitSpans(text, re) {
53
+ const limit = Math.floor(text.length / DENSE);
54
+ const spans = [];
55
+ re.lastIndex = 0;
56
+ for (let hit = re.exec(text); hit; hit = re.exec(text)) {
57
+ if (spans.length >= 2 * limit)
58
+ return null;
59
+ spans.push(hit.index, hit.index + hit[0].length);
60
+ }
61
+ return spans;
62
+ }
63
+ /**
64
+ * Apply `fn` to the stretches of `text` within `window` characters before a hit, and copy the rest as it is.
65
+ * `spans` of `null` means dense: `fn` runs over the whole text. A stretch without the character `needs` cannot hold a match.
66
+ */
67
+ function overHits(text, spans, window, fn, needs = '') {
68
+ if (spans === null)
69
+ return fn(text);
70
+ if (spans.length === 0)
52
71
  return text;
53
72
  let out = '';
54
73
  let copied = 0;
55
- let start = Math.max(0, hit.index - window);
56
- let end = hit.index + hit[0].length + 1;
74
+ let start = Math.max(0, spans[0] - window);
75
+ let end = spans[1] + 1;
57
76
  const flush = () => {
77
+ const seg = text.slice(start, end);
58
78
  // A sentinel before a window that does not start the text keeps `^` (start of text) from matching mid-text.
59
- const piece = start > 0 ? fn(`x${text.slice(start, end)}`).slice(1) : fn(text.slice(start, end));
79
+ const piece = needs && !seg.includes(needs) ? seg : start > 0 ? fn(`x${seg}`).slice(1) : fn(seg);
60
80
  out += text.slice(copied, start) + piece;
61
81
  copied = Math.min(end, text.length);
62
82
  };
63
- for (hit = AT_HITS.exec(text); hit; hit = AT_HITS.exec(text)) {
64
- const s = Math.max(0, hit.index - window);
83
+ for (let i = 2; i < spans.length; i += 2) {
84
+ const s = Math.max(0, spans[i] - window);
65
85
  if (s <= end) {
66
- end = hit.index + hit[0].length + 1;
86
+ end = spans[i + 1] + 1;
67
87
  continue;
68
88
  }
69
89
  flush();
70
90
  start = s;
71
- end = hit.index + hit[0].length + 1;
91
+ end = spans[i + 1] + 1;
72
92
  }
73
93
  flush();
74
94
  return out + text.slice(copied);
@@ -83,8 +103,10 @@ export function redactUserinfo(text, mask = '***') {
83
103
  // Pre-check: no `@`, `@` or `%40` means no userinfo in any form.
84
104
  if (!/[@@]|%(?:25){0,2}40/i.test(text))
85
105
  return text;
86
- const urlForms = overHits(text, URL_WINDOW, (t) => t.replace(ENCODED_USERINFO, `$1${mask}%40`).replace(USERINFO, `$1${mask}@`));
87
- return overHits(urlForms, BARE_WINDOW, (t) => t.replace(BARE_USERINFO, `$1${mask}@`));
106
+ const urlForms = overHits(text, hitSpans(text, AT_HITS), URL_WINDOW, (t) => t.replace(ENCODED_USERINFO, `$1${mask}%40`).replace(USERINFO, `$1${mask}@`));
107
+ if (!urlForms.includes(':'))
108
+ return urlForms; // the scheme-less form needs its colon
109
+ return overHits(urlForms, hitSpans(urlForms, BARE_HITS), BARE_WINDOW, (t) => t.replace(BARE_USERINFO, `$1${mask}@`), ':');
88
110
  }
89
111
  /** `url` without its userinfo (`https://u:p@host/x` becomes `https://host/x`); text that is not an absolute URL passes through redactUserinfo. */
90
112
  export function stripUserinfo(url) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@onesi/genesis",
3
- "version": "0.2.4",
3
+ "version": "0.2.6",
4
4
  "repository": {
5
5
  "type": "git",
6
6
  "url": "git+https://github.com/l1felabs/genesis-api.git",