@pulsarinteractive/geniustasker 0.1.0-beta.2 → 0.1.0-beta.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/src/commands.mjs CHANGED
@@ -7,102 +7,263 @@ import catalog from './command-catalog.mjs';
7
7
  import { CredentialStore } from './store.mjs';
8
8
  import { AgentError, fail, safeError } from './errors.mjs';
9
9
 
10
- export const operationIdSchema = z.string().regex(/^[a-fA-F0-9]{8}-[a-fA-F0-9]{4}-[a-fA-F0-9]{4}-[a-fA-F0-9]{4}-[a-fA-F0-9]{12}$/);
11
- const identifier = z.string().min(1).max(128).regex(/^[^\u0000-\u001f]+$/u);
12
- export const commandSchema = z.object({
13
- operationId: operationIdSchema, issuedAt: z.number().int().nonnegative().max(Number.MAX_SAFE_INTEGER),
14
- scope: z.string().max(256).regex(/^(project|memory):[^\u0000-\u001f]+$/u),
15
- epoch: identifier, id: identifier, expectedRevision: z.string().regex(/^[1-9][0-9]{0,15}$/).nullable(),
16
- kind: z.enum(Object.keys(catalog.commands)), data: z.record(z.string(), z.json()),
17
- }).strict();
10
+ export const operationIdSchema = z
11
+ .string()
12
+ .regex(/^[a-fA-F0-9]{8}-[a-fA-F0-9]{4}-[a-fA-F0-9]{4}-[a-fA-F0-9]{4}-[a-fA-F0-9]{12}$/);
13
+ const identifier = z
14
+ .string()
15
+ .min(1)
16
+ .max(128)
17
+ // eslint-disable-next-line no-control-regex -- Reject control characters in untrusted input.
18
+ .regex(/^[^\u0000-\u001f]+$/u);
19
+ export const commandSchema = z
20
+ .object({
21
+ operationId: operationIdSchema,
22
+ issuedAt: z.number().int().nonnegative().max(Number.MAX_SAFE_INTEGER),
23
+ scope: z
24
+ .string()
25
+ .max(256)
26
+ // eslint-disable-next-line no-control-regex -- Reject control characters in untrusted input.
27
+ .regex(/^(project|memory):[^\u0000-\u001f]+$/u),
28
+ epoch: identifier,
29
+ id: identifier,
30
+ expectedRevision: z
31
+ .string()
32
+ .regex(/^[1-9][0-9]{0,15}$/)
33
+ .nullable(),
34
+ kind: z.enum(Object.keys(catalog.commands)),
35
+ data: z.record(z.string(), z.json()),
36
+ })
37
+ .strict();
18
38
  export const draftSchema = commandSchema.omit({ operationId: true, issuedAt: true });
19
- const canonical = value => JSON.stringify(value, (_, item) => item && typeof item === 'object' && !Array.isArray(item)
20
- ? Object.fromEntries(Object.keys(item).sort().map(key => [key, item[key]])) : item);
39
+ const canonical = (value) =>
40
+ JSON.stringify(value, (_, item) =>
41
+ item && typeof item === 'object' && !Array.isArray(item)
42
+ ? Object.fromEntries(
43
+ Object.keys(item)
44
+ .sort()
45
+ .map((key) => [key, item[key]]),
46
+ )
47
+ : item,
48
+ );
21
49
  const checked = (schema, value) => {
22
50
  const parsed = schema.safeParse(value);
23
- if (!parsed.success) fail('invalid_arguments', 'The command does not match its schema. Use commands describe; do not supply credentials or server-owned fields.');
51
+ if (!parsed.success) {
52
+ fail(
53
+ 'invalid_arguments',
54
+ 'The command does not match its schema. Use commands describe; do not supply credentials or server-owned fields.',
55
+ );
56
+ }
24
57
  return parsed.data;
25
58
  };
59
+ /** Validate the public envelope, generated field allowlist and resource scope. */
26
60
  export function validateCommand(value) {
27
- const command = checked(commandSchema, value), definition = catalog.commands[command.kind];
28
- if (Buffer.byteLength(JSON.stringify(command)) > catalog.maxCommandBytes ||
29
- Object.keys(command.data).some(key => !definition.fields.includes(key)) ||
30
- (definition.create ? command.expectedRevision !== null : command.expectedRevision === null))
31
- fail('invalid_arguments', 'Use the declared command fields and size limit. Creation requires a null revision; other commands require the current record revision.');
61
+ const command = checked(commandSchema, value),
62
+ definition = catalog.commands[command.kind];
63
+ if (
64
+ Buffer.byteLength(JSON.stringify(command)) > catalog.maxCommandBytes ||
65
+ Object.keys(command.data).some((key) => !definition.fields.includes(key)) ||
66
+ (definition.create ? command.expectedRevision !== null : command.expectedRevision === null)
67
+ ) {
68
+ fail(
69
+ 'invalid_arguments',
70
+ 'Use the declared command fields and size limit. Creation requires a null revision; other commands require the current record revision.',
71
+ );
72
+ }
32
73
  const prefix = definition.module.startsWith('Memory/') ? 'memory:' : 'project:';
33
- if (!command.scope.startsWith(prefix) || definition.module.endsWith('/$') && command.id !== command.scope.slice(prefix.length)) fail('invalid_arguments', 'The command must target the matching project or memory scope.');
34
- if (command.kind === 'ticket.plan' && command.id !== command.scope.slice(8))
35
- fail('invalid_arguments', 'ticket.plan targets the project ID and root revision; changes carry each task expectedVersion.');
74
+ if (
75
+ !command.scope.startsWith(prefix) ||
76
+ (definition.module.endsWith('/$') && command.id !== command.scope.slice(prefix.length))
77
+ ) {
78
+ fail('invalid_arguments', 'The command must target the matching project or memory scope.');
79
+ }
80
+ if (
81
+ command.kind === 'memory.create' &&
82
+ (!/^[a-f\d]{8}-[a-f\d]{4}-4[a-f\d]{3}-[89ab][a-f\d]{3}-[a-f\d]{12}$/i.test(command.id) ||
83
+ command.epoch !== command.id)
84
+ ) {
85
+ fail(
86
+ 'invalid_arguments',
87
+ 'Create Memory with a UUID v4 as id, the same initial epoch, and scope memory:<id>. Keep source identifiers in page sourceId.',
88
+ );
89
+ }
90
+ if (command.kind === 'ticket.plan' && command.id !== command.scope.slice(8)) {
91
+ fail(
92
+ 'invalid_arguments',
93
+ 'ticket.plan targets the project ID and root revision; changes carry each task expectedVersion.',
94
+ );
95
+ }
36
96
  return command;
37
97
  }
38
- export function describeCommands() { return { ...catalog, envelope: z.toJSONSchema(commandSchema), draft: z.toJSONSchema(draftSchema),
39
- recovery: 'Retain operationId, issuedAt and the complete body. Retry an uncertain write unchanged; reread before creating a new edit after a conflict.' }; }
98
+ export function describeCommands() {
99
+ return {
100
+ ...catalog,
101
+ envelope: z.toJSONSchema(commandSchema),
102
+ draft: z.toJSONSchema(draftSchema),
103
+ recovery:
104
+ 'Retain operationId, issuedAt and the complete body. Retry an uncertain write unchanged; reread before creating a new edit after a conflict.',
105
+ };
106
+ }
107
+ /** Read one bounded regular JSON file, rejecting symlinks and non-file inputs. */
40
108
  export async function readCommandFile(path) {
41
- if (typeof path !== 'string') fail('invalid_arguments', 'Supply --file with a JSON command or draft.');
109
+ if (typeof path !== 'string') {
110
+ fail('invalid_arguments', 'Supply --file with a JSON command or draft.');
111
+ }
42
112
  let handle;
43
113
  try {
44
114
  handle = await open(path, constants.O_RDONLY | constants.O_NOFOLLOW | constants.O_NONBLOCK);
45
115
  const stat = await handle.stat();
46
- if (!stat.isFile() || stat.size > catalog.maxCommandBytes) fail('invalid_arguments', 'Use a regular JSON file no larger than 32 KiB.');
47
- const bytes = Buffer.alloc(catalog.maxCommandBytes + 1), result = await handle.read(bytes, 0, bytes.length, 0);
48
- if (result.bytesRead > catalog.maxCommandBytes) fail('invalid_arguments', 'Command file exceeds 32 KiB.');
116
+ if (!stat.isFile() || stat.size > catalog.maxCommandBytes) {
117
+ fail(
118
+ 'invalid_arguments',
119
+ `Use a regular JSON file no larger than ${catalog.maxCommandBytes} bytes.`,
120
+ );
121
+ }
122
+ const bytes = Buffer.alloc(catalog.maxCommandBytes + 1),
123
+ result = await handle.read(bytes, 0, bytes.length, 0);
124
+ if (result.bytesRead > catalog.maxCommandBytes) {
125
+ fail('invalid_arguments', `Command file exceeds ${catalog.maxCommandBytes} bytes.`);
126
+ }
49
127
  return JSON.parse(bytes.subarray(0, result.bytesRead).toString('utf8'));
50
128
  } catch (error) {
51
- if (error instanceof AgentError) throw error;
129
+ if (error instanceof AgentError) {
130
+ throw error;
131
+ }
52
132
  fail('invalid_arguments', 'Could not read a regular, valid JSON command file.');
53
- } finally { await handle?.close(); }
133
+ } finally {
134
+ await handle?.close();
135
+ }
54
136
  }
55
- const binding = session => ({ origin: session.origin, connectionId: session.connection_id, profileId: session.profile_id, sessionId: session.session_id });
56
- const receiptSchema = z.object({ operationId: operationIdSchema, scope: z.string(), kind: z.string(), epoch: z.string(), cursor: z.string(),
57
- items: z.array(z.object({ id: z.string(), revision: z.string(), collection: z.string(), deleted: z.boolean() }).strict()),
58
- affectedCollections: z.array(z.string()),
59
- }).strict();
137
+ const binding = (session) => ({
138
+ origin: session.origin,
139
+ connectionId: session.connection_id,
140
+ profileId: session.profile_id,
141
+ sessionId: session.session_id,
142
+ });
143
+ const receiptSchema = z
144
+ .object({
145
+ operationId: operationIdSchema,
146
+ scope: z.string(),
147
+ kind: z.string(),
148
+ epoch: z.string(),
149
+ cursor: z.string(),
150
+ items: z.array(
151
+ z
152
+ .object({
153
+ id: z.string(),
154
+ revision: z.string(),
155
+ collection: z.string(),
156
+ deleted: z.boolean(),
157
+ })
158
+ .strict(),
159
+ ),
160
+ affectedCollections: z.array(z.string()),
161
+ })
162
+ .strict();
60
163
 
61
164
  /** A journal is bound to one authenticated terminal identity. Persist the full
62
165
  * request before the network call, never regenerate a UUID after a timeout.
63
166
  * It contains private project text but no access/refresh tokens. A process crash
64
167
  * leaves its operation lock for explicit recovery after all clients stop. */
65
168
  export class CommandJournal {
66
- constructor(client) { this.client = client; }
169
+ constructor(client) {
170
+ this.client = client;
171
+ }
67
172
  store(id) {
68
173
  checked(operationIdSchema, id);
69
- return new CredentialStore({ directory: join(this.client.store.directory, 'operations', this.client.store.context), context: id, maxBytes: 524288 });
174
+ return new CredentialStore({
175
+ directory: join(this.client.store.directory, 'operations', this.client.store.context),
176
+ context: id,
177
+ maxBytes: 524288,
178
+ });
70
179
  }
71
180
  async prepare(draft) {
72
- const input = validateCommand({ ...checked(draftSchema, draft), operationId: randomUUID(), issuedAt: Date.now() });
73
- const identity = binding(await this.client.session()), store = this.store(input.operationId);
74
- await store.locked(() => store.write({ formatVersion: 1, binding: identity, input, state: 'prepared' }), this.client.transport.signal);
181
+ const input = validateCommand({
182
+ ...checked(draftSchema, draft),
183
+ operationId: randomUUID(),
184
+ issuedAt: Date.now(),
185
+ });
186
+ const identity = binding(await this.client.session()),
187
+ store = this.store(input.operationId);
188
+ await store.locked(
189
+ () => store.write({ formatVersion: 1, binding: identity, input, state: 'prepared' }),
190
+ this.client.transport.signal,
191
+ );
75
192
  return { operationId: input.operationId, state: 'prepared', request: input };
76
193
  }
77
194
  async inspect(id) {
78
195
  const row = await this.store(id).read();
79
- if (!row || row.formatVersion !== 1 || row.input?.operationId !== id) fail('operation_not_found', 'This context has no valid local operation with that ID.');
80
- return { operationId: id, source: 'local_journal', state: row.state, request: row.input, ...(row.receipt ? { receipt: row.receipt } : {}), ...(row.error ? { error: row.error } : {}) };
196
+ if (!row || row.formatVersion !== 1 || row.input?.operationId !== id) {
197
+ fail('operation_not_found', 'This context has no valid local operation with that ID.');
198
+ }
199
+ return {
200
+ operationId: id,
201
+ source: 'local_journal',
202
+ state: row.state,
203
+ request: row.input,
204
+ ...(row.receipt ? { receipt: row.receipt } : {}),
205
+ ...(row.error ? { error: row.error } : {}),
206
+ };
81
207
  }
82
208
  async execute(value) {
83
- const input = validateCommand(value), store = this.store(input.operationId);
209
+ const input = validateCommand(value),
210
+ store = this.store(input.operationId);
84
211
  return store.locked(async () => {
85
212
  // Capture token and identity together under the refresh lock. A later
86
213
  // logout/login cannot turn this pending operation into another profile.
87
- const session = await this.client.authorizedSession(), identity = binding(session);
214
+ const session = await this.client.authorizedSession(),
215
+ identity = binding(session);
88
216
  let row = await store.read();
89
- if (row && (row.formatVersion !== 1 || canonical(row.binding) !== canonical(identity)))
90
- fail('operation_identity_mismatch', 'This operation belongs to another connection, profile, session or environment.', { remediation: 'Inspect it locally and reread current records. Do not replay it through a replacement login.' });
91
- if (row && canonical(row.input) !== canonical(input))
92
- fail('idempotency_mismatch', 'The operation ID already belongs to a different immutable request.', { status: 409, remediation: 'Use commands inspect, then retry the saved operation.' });
93
- if (row?.state === 'confirmed') return { source: 'local_receipt', receipt: row.receipt };
217
+ if (row && (row.formatVersion !== 1 || canonical(row.binding) !== canonical(identity))) {
218
+ fail(
219
+ 'operation_identity_mismatch',
220
+ 'This operation belongs to another connection, profile, session or environment.',
221
+ {
222
+ remediation:
223
+ 'Inspect it locally and reread current records. Do not replay it through a replacement login.',
224
+ },
225
+ );
226
+ }
227
+ if (row && canonical(row.input) !== canonical(input)) {
228
+ fail(
229
+ 'idempotency_mismatch',
230
+ 'The operation ID already belongs to a different immutable request.',
231
+ { status: 409, remediation: 'Use commands inspect, then retry the saved operation.' },
232
+ );
233
+ }
234
+ if (row?.state === 'confirmed') {
235
+ return { source: 'local_receipt', receipt: row.receipt };
236
+ }
94
237
  row = { formatVersion: 1, binding: identity, input, state: 'uncertain' };
95
238
  await store.write(row);
96
239
  try {
97
- const receipt = await this.client.transport.request('/api/agents/v1/commands', { method: 'POST', json: input, token: session.access_token });
98
- if (!receiptSchema.safeParse(receipt).success || ['operationId', 'scope', 'kind', 'epoch'].some(key => receipt[key] !== input[key]))
99
- fail('invalid_response', 'The command receipt was invalid. The server may have committed.', { execution: 'unknown', remediation: 'Retry the identical saved operation.' });
240
+ const receipt = await this.client.transport.request('/api/agents/v1/commands', {
241
+ method: 'POST',
242
+ json: input,
243
+ token: session.access_token,
244
+ });
245
+ if (
246
+ !receiptSchema.safeParse(receipt).success ||
247
+ ['operationId', 'scope', 'kind', 'epoch'].some((key) => receipt[key] !== input[key])
248
+ ) {
249
+ fail(
250
+ 'invalid_response',
251
+ 'The command receipt was invalid. The server may have committed.',
252
+ { execution: 'unknown', remediation: 'Retry the identical saved operation.' },
253
+ );
254
+ }
100
255
  await store.write({ ...row, state: 'confirmed', receipt });
101
256
  return { source: 'server', receipt };
102
257
  } catch (error) {
103
258
  const safe = safeError(error);
104
- const result = new AgentError(safe.code, safe.message, { status: safe.status, retryable: safe.retryable, remediation: safe.remediation,
105
- execution: 'unknown', ...safe.details, operationId: input.operationId });
259
+ const result = new AgentError(safe.code, safe.message, {
260
+ status: safe.status,
261
+ retryable: safe.retryable,
262
+ remediation: safe.remediation,
263
+ execution: 'unknown',
264
+ ...safe.details,
265
+ operationId: input.operationId,
266
+ });
106
267
  // The uncertainty marker is already durable. Preserve the useful original
107
268
  // error/operation ID even if disk pressure prevents saving diagnostics.
108
269
  await store.write({ ...row, error: result.toJSON() }).catch(() => {});
@@ -110,5 +271,7 @@ export class CommandJournal {
110
271
  }
111
272
  }, this.client.transport.signal);
112
273
  }
113
- async retry(id) { return this.execute((await this.inspect(id)).request); }
274
+ async retry(id) {
275
+ return this.execute((await this.inspect(id)).request);
276
+ }
114
277
  }
package/src/errors.mjs CHANGED
@@ -1,22 +1,65 @@
1
1
  /** Never forward raw network exceptions: URLs, HTML and provider diagnostics
2
2
  * can contain credentials. API messages are data, never terminal control. */
3
3
  export class AgentError extends Error {
4
- constructor(code, message, { status = 0, retryable = false, remediation = 'Check help and retry.', ...details } = {}) {
5
- super(message); this.code = code; this.status = status; this.retryable = retryable;
6
- this.remediation = remediation; this.details = details;
4
+ constructor(
5
+ code,
6
+ message,
7
+ { status = 0, retryable = false, remediation = 'Check help and retry.', ...details } = {},
8
+ ) {
9
+ super(message);
10
+ this.code = code;
11
+ this.status = status;
12
+ this.retryable = retryable;
13
+ this.remediation = remediation;
14
+ this.details = details;
15
+ }
16
+ toJSON() {
17
+ return {
18
+ code: this.code,
19
+ message: this.message,
20
+ retryable: this.retryable,
21
+ remediation: this.remediation,
22
+ ...this.details,
23
+ };
7
24
  }
8
- toJSON() { return { code: this.code, message: this.message, retryable: this.retryable, remediation: this.remediation, ...this.details }; }
9
25
  }
10
- export const fail = (code, message, options) => { throw new AgentError(code, message, options); };
26
+ export const fail = (code, message, options) => {
27
+ throw new AgentError(code, message, options);
28
+ };
11
29
  export function safeError(error) {
12
- return error instanceof AgentError ? error : new AgentError('internal_error', 'The command could not complete.', { remediation: 'Retry once; run doctor if the problem persists.' });
30
+ return error instanceof AgentError
31
+ ? error
32
+ : new AgentError('internal_error', 'The command could not complete.', {
33
+ remediation: 'Retry once; run doctor if the problem persists.',
34
+ });
13
35
  }
14
36
  export function exitCode(error) {
15
- if (['invalid_arguments', 'invalid_origin'].includes(error.code)) return 2;
16
- if (error.status === 401 || ['not_authenticated', 'reauthentication_required', 'access_denied', 'expired_token', 'invalid_grant'].includes(error.code)) return 3;
17
- if (error.status === 403 || error.code === 'profile_selection_denied') return 4;
18
- if (error.status === 409 || error.code === 'profile_selection_conflict') return 5;
19
- if (error.status === 429) return 6;
20
- if (error.retryable) return 7;
37
+ if (['invalid_arguments', 'invalid_origin'].includes(error.code)) {
38
+ return 2;
39
+ }
40
+ if (
41
+ error.status === 401 ||
42
+ [
43
+ 'not_authenticated',
44
+ 'reauthentication_required',
45
+ 'access_denied',
46
+ 'expired_token',
47
+ 'invalid_grant',
48
+ ].includes(error.code)
49
+ ) {
50
+ return 3;
51
+ }
52
+ if (error.status === 403 || error.code === 'profile_selection_denied') {
53
+ return 4;
54
+ }
55
+ if (error.status === 409 || error.code === 'profile_selection_conflict') {
56
+ return 5;
57
+ }
58
+ if (error.status === 429) {
59
+ return 6;
60
+ }
61
+ if (error.retryable) {
62
+ return 7;
63
+ }
21
64
  return 1;
22
65
  }
@@ -0,0 +1,109 @@
1
+ import * as z from 'zod/v4';
2
+ import { fail } from './errors.mjs';
3
+
4
+ const id = z.string().regex(/^[a-zA-Z0-9_-]{1,128}$/);
5
+ const scope = z.string().regex(/^memory:[a-zA-Z0-9_-]{1,128}$/);
6
+ const cursor = {
7
+ epoch: z.string().min(1).max(128).optional(),
8
+ checkpoint: z
9
+ .string()
10
+ .regex(/^(0|[1-9][0-9]*)$/)
11
+ .max(32)
12
+ .optional(),
13
+ };
14
+ export const searchSchema = z
15
+ .object({
16
+ scope,
17
+ query: z.string().trim().min(1).max(200),
18
+ collection: z.enum(['pages', 'nodes']).optional(),
19
+ after: id.optional(),
20
+ ...cursor,
21
+ })
22
+ .strict()
23
+ .refine((value) => !value.after || !!(value.epoch && value.checkpoint), {
24
+ message: 'Continuation needs epoch and checkpoint.',
25
+ });
26
+ export const graphSchema = z
27
+ .object({ scope, center: id, depth: z.number().int().min(0).max(3).optional(), ...cursor })
28
+ .strict();
29
+ export const referenceSchema = z
30
+ .object({
31
+ scope,
32
+ pageId: id,
33
+ blockId: z
34
+ .string()
35
+ .regex(/^[a-zA-Z0-9_-]{1,80}$/)
36
+ .optional(),
37
+ })
38
+ .strict();
39
+
40
+ function input(schema, value) {
41
+ const parsed = schema.safeParse(value);
42
+ if (!parsed.success) {
43
+ fail(
44
+ 'invalid_arguments',
45
+ 'Use a memory scope and the documented search, Graph or reference parameters.',
46
+ );
47
+ }
48
+ return parsed.data;
49
+ }
50
+
51
+ /** Query one bounded page without following cursors or retrying automatically. */
52
+ export async function searchMemory(client, value) {
53
+ const query = input(searchSchema, value);
54
+ const result = await client.get('knowledge', { ...query, mode: 'search' });
55
+ if (
56
+ result?.scope !== query.scope ||
57
+ result.mode !== 'search' ||
58
+ !Array.isArray(result.items) ||
59
+ result.items.length > 25
60
+ ) {
61
+ fail('invalid_response', 'The search response does not match the requested scope.');
62
+ }
63
+ return result;
64
+ }
65
+
66
+ /** Read a compact neighborhood; full node text remains a separate resource read. */
67
+ export async function exploreGraph(client, value) {
68
+ const query = input(graphSchema, value);
69
+ const result = await client.get('knowledge', { ...query, mode: 'graph' });
70
+ if (
71
+ result?.scope !== query.scope ||
72
+ result.mode !== 'graph' ||
73
+ !Array.isArray(result.nodes) ||
74
+ !Array.isArray(result.edges) ||
75
+ result.nodes.length > 80 ||
76
+ result.edges.length > 160
77
+ ) {
78
+ fail('invalid_response', 'The Graph response does not match the requested scope or bounds.');
79
+ }
80
+ const ids = new Set(result.nodes.map((node) => node.id));
81
+ if (
82
+ !ids.has(query.center) ||
83
+ result.edges.some((edge) => !ids.has(edge.data?.source) || !ids.has(edge.data?.target))
84
+ ) {
85
+ fail('invalid_response', 'The Graph response contains unresolved endpoints.');
86
+ }
87
+ return result;
88
+ }
89
+
90
+ /** Linked content is never persisted by this client. Resolve again after access
91
+ * changes; unavailable content must not delete the user's node or relations. */
92
+ export async function resolveMemory(client, value) {
93
+ const query = input(referenceSchema, value);
94
+ const result = await client.get('reference', query);
95
+ if (
96
+ result?.scope !== query.scope ||
97
+ result.pageId !== query.pageId ||
98
+ result.blockId !== (query.blockId ?? null) ||
99
+ !['available', 'unavailable', 'missing'].includes(result.status) ||
100
+ (result.status === 'available' ? result.page?.id !== query.pageId : result.page !== null) ||
101
+ (result.status === 'unavailable' && (result.epoch !== null || result.accessRevision !== null))
102
+ ) {
103
+ fail(
104
+ 'invalid_response',
105
+ 'The reference response does not match the requested target or current access.',
106
+ );
107
+ }
108
+ return result;
109
+ }