@h1v35/hivex 0.2.0 → 0.2.2

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 (39) hide show
  1. package/README.md +55 -163
  2. package/docs/CONTEXT.md +20 -36
  3. package/docs/README.md +6 -12
  4. package/docs/adr/0003-independent-bun-installation.md +5 -19
  5. package/docs/adr/0010-practical-knowledge-assistance.md +28 -81
  6. package/docs/adr/0011-shared-knowledge-and-selective-history.md +16 -43
  7. package/docs/guidelines/engineering.md +74 -0
  8. package/docs/procedures/self-hosted-runner.md +7 -0
  9. package/package.json +32 -11
  10. package/skills/hivex/SKILL.md +28 -92
  11. package/skills/hivex/references/markdown.md +12 -42
  12. package/src/cli/diagnostic.ts +21 -11
  13. package/src/cli.ts +46 -36
  14. package/src/documents.ts +502 -320
  15. package/src/errors.ts +8 -6
  16. package/src/implementation.ts +185 -87
  17. package/src/ingestion-units.ts +107 -64
  18. package/src/knowledge-maintenance.ts +35 -22
  19. package/src/knowledge-model.ts +386 -268
  20. package/src/knowledge-serialization.ts +239 -0
  21. package/src/knowledge-snapshot.ts +100 -77
  22. package/src/knowledge-store.ts +634 -453
  23. package/src/knowledge.ts +1001 -758
  24. package/src/markdown.ts +107 -45
  25. package/src/model/connection.ts +134 -76
  26. package/src/model/failure.ts +46 -23
  27. package/src/model/invoke.ts +346 -166
  28. package/src/model/profile.ts +201 -103
  29. package/src/model/rpc-error.ts +21 -0
  30. package/src/model/server.ts +151 -82
  31. package/src/model/thread.ts +24 -14
  32. package/src/model/transcript.ts +87 -46
  33. package/src/ordering.ts +9 -0
  34. package/src/retrieval/lexical.ts +64 -41
  35. package/src/review.ts +83 -55
  36. package/src/runtime.d.ts +4 -0
  37. package/src/snapshot-command.ts +82 -43
  38. package/src/source-relocation.ts +222 -0
  39. package/docs/engineering.md +0 -174
package/src/markdown.ts CHANGED
@@ -1,98 +1,160 @@
1
1
  import { createHash } from 'node:crypto';
2
- import { basename } from 'node:path';
2
+ import pathModule from 'node:path';
3
3
  import { fromMarkdown } from 'mdast-util-from-markdown';
4
- import { toString } from 'mdast-util-to-string';
5
4
  import { gfmFromMarkdown } from 'mdast-util-gfm';
6
5
  import { gfm } from 'micromark-extension-gfm';
7
6
  import { frontmatterFromMarkdown } from 'mdast-util-frontmatter';
8
7
  import { frontmatter } from 'micromark-extension-frontmatter';
9
8
  import { normalizeIdentifier } from 'micromark-util-normalize-identifier';
9
+ import { toString as mdastToString } from 'mdast-util-to-string';
10
10
  import { parseDocument } from 'yaml';
11
11
 
12
12
  export const hash = (text: string) => createHash('sha256').update(text).digest('hex');
13
- export const isMarkdownPath = (path: string) => /\.(?:md|markdown|mdown)$/i.test(path);
13
+ export const isMarkdownPath = (path: string) => /\.(?:md|markdown|mdown)$/iu.test(path);
14
14
 
15
- export function parseMarkdown(content: string) {
15
+ export const parseMarkdown = function parseMarkdown(content: string) {
16
16
  return fromMarkdown(content, {
17
17
  extensions: [gfm(), frontmatter(['yaml'])],
18
18
  mdastExtensions: [gfmFromMarkdown(), frontmatterFromMarkdown(['yaml'])],
19
19
  });
20
- }
20
+ };
21
21
 
22
- type MarkdownNode = {
22
+ interface MarkdownNode {
23
23
  type: string;
24
24
  children?: MarkdownNode[];
25
25
  url?: string;
26
26
  identifier?: string;
27
- position?: { start: { line: number; offset?: number }; end: { line: number; offset?: number } };
28
- };
27
+ position?: {
28
+ start: { line: number; offset?: number };
29
+ end: { line: number; offset?: number };
30
+ };
31
+ }
29
32
 
30
- export function* descendants(tree: MarkdownNode): Generator<MarkdownNode> {
33
+ export const descendants = function* descendants(tree: MarkdownNode): Generator<MarkdownNode> {
31
34
  const pending = [tree];
32
35
  while (pending.length) {
33
36
  const node = pending.pop();
34
- if (!node) break;
37
+ if (!node) {
38
+ break;
39
+ }
35
40
  yield node;
36
- for (let index = (node.children?.length ?? 0) - 1; index >= 0; index--) {
41
+ for (let index = (node.children?.length ?? 0) - 1; index >= 0; index -= 1) {
37
42
  const child = node.children?.[index];
38
- if (child) pending.push(child);
43
+ if (child) {
44
+ pending.push(child);
45
+ }
39
46
  }
40
47
  }
41
- }
48
+ };
42
49
 
43
- function metadata(tree: ReturnType<typeof parseMarkdown>) {
44
- const node = tree.children.find((entry) => entry.type === 'yaml');
45
- if (node?.type !== 'yaml') return { title: null, status: null };
50
+ const emptyMetadata = { status: null, title: null };
51
+
52
+ const yamlValue = function yamlValue(content: string): unknown {
53
+ let parsed: ReturnType<typeof parseDocument>;
46
54
  try {
47
- const parsed = parseDocument(node.value, { uniqueKeys: true });
48
- if (parsed.errors.length) return { title: null, status: null };
49
- const value: unknown = parsed.toJS({ maxAliasCount: 0 });
50
- if (!value || typeof value !== 'object' || Array.isArray(value))
51
- return { title: null, status: null };
52
- return {
53
- title:
54
- 'title' in value && typeof value.title === 'string' && value.title.trim()
55
- ? value.title
56
- : null,
57
- status: 'status' in value && typeof value.status === 'string' ? value.status : null,
58
- };
55
+ parsed = parseDocument(content, { uniqueKeys: true });
59
56
  } catch {
60
- return { title: null, status: null };
57
+ return null;
61
58
  }
62
- }
59
+ if (parsed.errors.length) {
60
+ return null;
61
+ }
62
+ try {
63
+ return parsed.toJS({ maxAliasCount: 0 });
64
+ } catch {
65
+ return null;
66
+ }
67
+ };
68
+
69
+ const isRecord = function isRecord(value: unknown): value is Record<string, unknown> {
70
+ return value !== null && typeof value === 'object' && !Array.isArray(value);
71
+ };
72
+
73
+ const metadata = function metadata(tree: ReturnType<typeof parseMarkdown>) {
74
+ const node = tree.children.find((entry) => entry.type === 'yaml');
75
+ if (node?.type !== 'yaml') {
76
+ return emptyMetadata;
77
+ }
78
+ const value = yamlValue(node.value);
79
+ if (!isRecord(value)) {
80
+ return emptyMetadata;
81
+ }
82
+ const { status: statusValue, title: titleValue } = value;
83
+ return {
84
+ status: typeof statusValue === 'string' ? statusValue : null,
85
+ title: typeof titleValue === 'string' && titleValue.trim().length > 0 ? titleValue : null,
86
+ };
87
+ };
63
88
 
64
- export function describeMarkdown(path: string, content: string) {
89
+ export const describeMarkdown = function describeMarkdown(path: string, content: string) {
65
90
  const tree = parseMarkdown(content);
66
91
  const front = metadata(tree);
67
92
  const heading = tree.children.find((node) => node.type === 'heading');
68
93
  const definitions = new Map<string, string>();
69
94
  for (const node of descendants(tree)) {
70
- if (node.type === 'definition' && node.identifier && node.url) {
95
+ if (node.type !== 'definition') {
96
+ continue;
97
+ }
98
+ if (
99
+ typeof node.identifier === 'string' &&
100
+ node.identifier.length > 0 &&
101
+ typeof node.url === 'string' &&
102
+ node.url.length > 0
103
+ ) {
71
104
  const id = normalizeIdentifier(node.identifier);
72
- if (!definitions.has(id)) definitions.set(id, node.url);
105
+ if (!definitions.has(id)) {
106
+ definitions.set(id, node.url);
107
+ }
73
108
  }
74
109
  }
75
110
  const links = [...descendants(tree)].flatMap((node) => {
76
- if (node.type === 'link' && node.url) return [node.url];
77
- if (node.type !== 'linkReference' || !node.identifier) return [];
111
+ if (node.type === 'link' && typeof node.url === 'string' && node.url.length > 0) {
112
+ return [node.url];
113
+ }
114
+ if (
115
+ node.type !== 'linkReference' ||
116
+ typeof node.identifier !== 'string' ||
117
+ node.identifier.length === 0
118
+ ) {
119
+ return [];
120
+ }
78
121
  const url = definitions.get(normalizeIdentifier(node.identifier));
79
- return url ? [url] : [];
122
+ if (url === undefined) {
123
+ return [];
124
+ }
125
+ return [url];
80
126
  });
81
127
  return {
82
- title: front.title ?? (heading ? toString(heading) : basename(path)),
83
- status: front.status,
84
128
  links,
129
+ status: front.status,
130
+ title: front.title ?? (heading ? mdastToString(heading) : pathModule.basename(path)),
85
131
  };
86
- }
132
+ };
87
133
 
88
- export function rawMarkdownLines(text: string): string[] {
89
- const lines = (text.match(/[^\r\n]*(?:\r\n|\r|\n|$)/g) ?? []).filter((line) => line.length > 0);
134
+ export const rawMarkdownLines = function rawMarkdownLines(text: string): string[] {
135
+ const lines: string[] = [];
136
+ let lineStart = 0;
137
+ let index = 0;
138
+ while (index < text.length) {
139
+ const character = text[index];
140
+ if (character === '\r' || character === '\n') {
141
+ const lineEnd = index + (character === '\r' && text[index + 1] === '\n' ? 2 : 1);
142
+ lines.push(text.slice(lineStart, lineEnd));
143
+ lineStart = lineEnd;
144
+ index = lineEnd;
145
+ } else {
146
+ index += 1;
147
+ }
148
+ }
149
+ if (lineStart < text.length) {
150
+ lines.push(text.slice(lineStart));
151
+ }
90
152
  return lines.length ? lines : [''];
91
- }
153
+ };
92
154
 
93
- export const lineContent = (line: string) => line.replace(/(?:\r\n|\r|\n)$/, '');
155
+ export const lineContent = (line: string) => line.replace(/(?:\r\n|\r|\n)$/u, '');
94
156
 
95
- export function sourceRange(text: string, from: number, to: number) {
157
+ export const sourceRange = function sourceRange(text: string, from: number, to: number) {
96
158
  const selected = rawMarkdownLines(text).slice(from - 1, to);
97
159
  return selected.slice(0, -1).join('') + lineContent(selected.at(-1) ?? '');
98
- }
160
+ };
@@ -1,26 +1,39 @@
1
- import { createInterface, type Interface } from 'node:readline';
2
- import type { Readable, Writable } from 'node:stream';
1
+ import { createInterface } from 'node:readline';
3
2
  import { z } from 'zod';
3
+ import { AppServerRpcError, RemoteErrorSchema } from './rpc-error.ts';
4
+ import type { Interface } from 'node:readline';
5
+ import type { Readable, Writable } from 'node:stream';
6
+
7
+ export { AppServerRpcError } from './rpc-error.ts';
8
+
9
+ const observeRejection = async (promise: Promise<unknown>): Promise<void> => {
10
+ try {
11
+ await promise;
12
+ } catch {
13
+ // The rejection is handled by the caller through the public promise.
14
+ }
15
+ };
16
+
17
+ const createEnding = () => {
18
+ const ending = Promise.withResolvers<never>();
19
+ void observeRejection(ending.promise);
20
+ return ending;
21
+ };
4
22
 
5
23
  const IdSchema = z.union([z.string(), z.number().int()]);
6
- const RemoteErrorSchema = z.looseObject({
7
- code: z.number().int(),
8
- message: z.string(),
9
- data: z.unknown().optional(),
10
- });
11
24
  const FrameSchema = z.looseObject({
25
+ error: RemoteErrorSchema.optional(),
12
26
  id: IdSchema.optional(),
13
27
  method: z.string().optional(),
14
28
  params: z.unknown().optional(),
15
29
  result: z.unknown().optional(),
16
- error: RemoteErrorSchema.optional(),
17
30
  });
18
31
  type Frame = z.infer<typeof FrameSchema>;
19
32
 
20
33
  interface ConnectionOptions {
21
34
  readonly input: Writable;
22
35
  readonly output: Readable;
23
- readonly onNotification: (method: string, params: unknown) => void;
36
+ readonly onNotification: (method: string, parameters: unknown) => void;
24
37
  readonly onInteractiveRequest?: (method: string) => void;
25
38
  }
26
39
  interface RequestOptions {
@@ -34,87 +47,113 @@ interface PendingRequest {
34
47
  readonly cleanup: () => void;
35
48
  }
36
49
 
37
- export class AppServerRpcError extends Error {
38
- readonly code: number;
39
- readonly data: unknown;
40
-
41
- constructor(error: z.infer<typeof RemoteErrorSchema>) {
42
- super(error.message);
43
- this.name = 'AppServerRpcError';
44
- this.code = error.code;
45
- this.data = error.data;
46
- }
50
+ interface ConnectionListeners {
51
+ readonly close: () => void;
52
+ readonly data: (chunk: Buffer) => void;
53
+ readonly error: (error: Error) => void;
54
+ readonly line: (line: string) => void;
47
55
  }
48
56
 
49
57
  export class AppServerConnection {
50
58
  private readonly options: ConnectionOptions;
51
59
  private readonly reader: Interface;
52
60
  private readonly pending = new Map<string | number, PendingRequest>();
53
- private readonly ending = Promise.withResolvers<never>();
61
+ private readonly ending = createEnding();
54
62
  private failure: Error | undefined;
63
+ private readonly listeners: ConnectionListeners;
55
64
  private nextId = 1;
56
65
  private trailingBytes = 0;
57
66
  private streamBytes = 0;
58
- readonly closed = this.ending.promise;
59
67
 
60
68
  constructor(options: ConnectionOptions) {
61
69
  this.options = options;
62
- options.output.on('data', this.measureChunk);
63
- this.reader = createInterface({ input: options.output, crlfDelay: Infinity });
64
- this.reader.on('line', this.receiveLine);
65
- this.reader.on('close', this.receiveClose);
66
- options.input.on('error', this.receiveStreamError);
67
- options.output.on('error', this.receiveStreamError);
68
- void this.closed.catch(() => undefined);
70
+ this.listeners = {
71
+ close: this.receiveClose.bind(this),
72
+ data: this.measureChunk.bind(this),
73
+ error: this.receiveStreamError.bind(this),
74
+ line: this.receiveLine.bind(this),
75
+ };
76
+ options.output.on('data', this.listeners.data);
77
+ this.reader = createInterface({
78
+ crlfDelay: Infinity,
79
+ input: options.output,
80
+ });
81
+ this.reader.on('line', this.listeners.line);
82
+ this.reader.on('close', this.listeners.close);
83
+ options.input.on('error', this.listeners.error);
84
+ options.output.on('error', this.listeners.error);
85
+ }
86
+
87
+ get closed(): Promise<never> {
88
+ return this.ending.promise;
69
89
  }
70
90
 
71
- request(method: string, params: unknown, options: RequestOptions = {}): Promise<unknown> {
72
- if (this.failure !== undefined) return Promise.reject(this.failure);
73
- if (options.signal?.aborted) return Promise.reject(new Error('request cancelled'));
91
+ async request(
92
+ method: string,
93
+ parameters: unknown,
94
+ options: RequestOptions = {}
95
+ ): Promise<unknown> {
96
+ if (this.failure !== undefined) {
97
+ throw this.failure;
98
+ }
99
+ if (options.signal?.aborted === true) {
100
+ throw new Error('request cancelled');
101
+ }
74
102
  const timeout = options.timeoutMilliseconds ?? 30_000;
75
103
  if (!Number.isSafeInteger(timeout) || timeout <= 0) {
76
- return Promise.reject(new Error('request timeout must be a positive integer'));
104
+ throw new Error('request timeout must be a positive integer');
77
105
  }
78
- const id = this.nextId++;
106
+ const id = this.nextId;
107
+ this.nextId += 1;
79
108
  const result = Promise.withResolvers<unknown>();
80
- const abort = () => this.rejectRequest(id, new Error('request cancelled'));
81
- const timer = setTimeout(
82
- () => this.rejectRequest(id, new Error(`request timed out: ${method}`)),
83
- timeout,
84
- );
109
+ const abort = (): void => {
110
+ this.rejectRequest(id, new Error('request cancelled'));
111
+ };
112
+ const timer = setTimeout(() => {
113
+ this.rejectRequest(id, new Error(`request timed out: ${method}`));
114
+ }, timeout);
85
115
  this.pending.set(id, {
86
- resolve: result.resolve,
87
- reject: result.reject,
88
116
  cleanup: () => {
89
117
  clearTimeout(timer);
90
118
  options.signal?.removeEventListener('abort', abort);
91
119
  },
120
+ reject: result.reject,
121
+ resolve: result.resolve,
92
122
  });
93
123
  options.signal?.addEventListener('abort', abort, { once: true });
94
- this.send({ id, method, params });
95
- return result.promise;
124
+ this.send({ id, method, params: parameters });
125
+ return await result.promise;
96
126
  }
97
127
 
98
- notify(method: string, params?: unknown): void {
99
- if (this.failure !== undefined) throw this.failure;
100
- this.send({ method, params });
128
+ notify(method: string, parameters?: unknown): void {
129
+ if (this.failure !== undefined) {
130
+ throw this.failure;
131
+ }
132
+ this.send({ method, params: parameters });
101
133
  }
102
134
 
103
135
  dispose(): void {
104
136
  this.fail(new Error('connection closed'));
105
- this.reader.off('line', this.receiveLine);
106
- this.reader.off('close', this.receiveClose);
137
+ this.reader.off('line', this.listeners.line);
138
+ this.reader.off('close', this.listeners.close);
107
139
  this.reader.close();
108
- this.options.input.off('error', this.receiveStreamError);
109
- this.options.output.off('error', this.receiveStreamError);
110
- this.options.output.off('data', this.measureChunk);
140
+ this.options.input.off('error', this.listeners.error);
141
+ this.options.output.off('error', this.listeners.error);
142
+ this.options.output.off('data', this.listeners.data);
111
143
  }
112
144
 
113
- private readonly receiveClose = (): void => this.fail(new Error('connection closed'));
114
- private readonly receiveStreamError = (error: Error): void => this.fail(error);
145
+ protected receiveClose(): void {
146
+ this.fail(new Error('connection closed'));
147
+ }
148
+
149
+ protected receiveStreamError(error: Error): void {
150
+ this.fail(error);
151
+ }
115
152
 
116
- private readonly measureChunk = (chunk: Buffer): void => {
117
- if (this.failure) return;
153
+ protected measureChunk(chunk: Buffer): void {
154
+ if (this.failure !== undefined) {
155
+ return;
156
+ }
118
157
  this.streamBytes += chunk.byteLength;
119
158
  if (this.streamBytes > 33_554_432) {
120
159
  this.fail(new Error('app-server stream exceeded 32 MiB'));
@@ -131,40 +170,52 @@ export class AppServerConnection {
131
170
  offset = newline + 1;
132
171
  }
133
172
  this.trailingBytes += chunk.byteLength - offset;
134
- if (this.trailingBytes > 4_194_304) this.fail(new Error('app-server frame exceeded 4 MiB'));
135
- };
173
+ if (this.trailingBytes > 4_194_304) {
174
+ this.fail(new Error('app-server frame exceeded 4 MiB'));
175
+ }
176
+ }
136
177
 
137
- private readonly receiveLine = (line: string): void => {
138
- if (this.failure !== undefined) return;
178
+ protected receiveLine(line: string): void {
179
+ if (this.failure !== undefined) {
180
+ return;
181
+ }
139
182
  try {
140
183
  const frame = FrameSchema.parse(JSON.parse(line) as unknown);
141
184
  this.receiveFrame(frame);
142
185
  } catch (error: unknown) {
143
186
  this.fail(new Error('invalid app-server frame', { cause: error }));
144
187
  }
145
- };
188
+ }
146
189
 
147
- private receiveFrame(frame: Frame): void {
190
+ protected receiveFrame(frame: Frame): void {
148
191
  if (frame.method !== undefined) {
149
192
  if (Object.hasOwn(frame, 'result') || frame.error !== undefined) {
150
193
  throw new Error('a method frame cannot also be a response');
151
194
  }
152
- if (frame.id === undefined) this.options.onNotification(frame.method, frame.params);
153
- else this.declineInteractiveRequest(frame.id, frame.method);
195
+ if (frame.id === undefined) {
196
+ this.options.onNotification(frame.method, frame.params);
197
+ } else {
198
+ this.declineInteractiveRequest(frame.id, frame.method);
199
+ }
154
200
  return;
155
201
  }
156
202
  if (frame.id === undefined || Object.hasOwn(frame, 'result') === (frame.error !== undefined)) {
157
203
  throw new Error('a response requires an id and exactly one outcome');
158
204
  }
159
205
  const pending = this.pending.get(frame.id);
160
- if (pending === undefined) return;
206
+ if (pending === undefined) {
207
+ return;
208
+ }
161
209
  this.pending.delete(frame.id);
162
210
  pending.cleanup();
163
- if (frame.error !== undefined) pending.reject(new AppServerRpcError(frame.error));
164
- else pending.resolve(frame.result);
211
+ if (frame.error === undefined) {
212
+ pending.resolve(frame.result);
213
+ return;
214
+ }
215
+ pending.reject(new AppServerRpcError(frame.error));
165
216
  }
166
217
 
167
- private declineInteractiveRequest(id: string | number, method: string): void {
218
+ protected declineInteractiveRequest(id: string | number, method: string): void {
168
219
  this.options.onInteractiveRequest?.(method);
169
220
  if (
170
221
  method === 'item/commandExecution/requestApproval' ||
@@ -174,34 +225,41 @@ export class AppServerConnection {
174
225
  return;
175
226
  }
176
227
  this.send({
228
+ error: {
229
+ code: -32_601,
230
+ message: 'interactive requests are outside this pilot',
231
+ },
177
232
  id,
178
- error: { code: -32601, message: 'interactive requests are outside this pilot' },
179
233
  });
180
234
  }
181
235
 
182
- private send(frame: unknown): void {
236
+ protected send(frame: unknown): void {
183
237
  try {
184
- this.options.input.write(`${JSON.stringify(frame)}\n`, (error) => {
185
- if (error !== null && error !== undefined) this.fail(error);
186
- });
238
+ this.options.input.write(`${JSON.stringify(frame)}\n`);
187
239
  } catch (error: unknown) {
188
240
  this.fail(new Error('app-server write failed', { cause: error }));
189
241
  }
190
242
  }
191
243
 
192
- private rejectRequest(id: string | number, error: Error): void {
244
+ protected rejectRequest(id: string | number, error: Error): void {
193
245
  const pending = this.pending.get(id);
194
- if (pending === undefined) return;
246
+ if (pending === undefined) {
247
+ return;
248
+ }
195
249
  this.pending.delete(id);
196
250
  pending.cleanup();
197
251
  pending.reject(error);
198
252
  }
199
253
 
200
- private fail(error: Error): void {
201
- if (this.failure !== undefined) return;
254
+ protected fail(error: Error): void {
255
+ if (this.failure !== undefined) {
256
+ return;
257
+ }
202
258
  this.failure = error;
203
259
  this.reader.close();
204
- for (const id of this.pending.keys()) this.rejectRequest(id, error);
260
+ for (const id of this.pending.keys()) {
261
+ this.rejectRequest(id, error);
262
+ }
205
263
  this.ending.reject(error);
206
264
  }
207
265
  }
@@ -1,33 +1,56 @@
1
1
  import { z } from 'zod';
2
- import { AppServerRpcError } from './connection.ts';
2
+ import { AppServerRpcError } from './rpc-error.ts';
3
3
  import type { ProfileEvidence } from './profile.ts';
4
4
 
5
- export class ServerAdmissionFailure extends Error {
5
+ class ServerAdmissionFailureError extends Error {
6
+ name = 'ServerAdmissionFailureError';
7
+ readonly admission: (ProfileEvidence & { launchPolicyHash: string }) | undefined;
6
8
  readonly cleanup: 'confirmed' | 'failed';
7
9
  readonly processId: number;
8
- readonly admission: (ProfileEvidence & { launchPolicyHash: string }) | undefined;
9
- constructor(options: {
10
- cause: unknown;
11
- cleanup: 'confirmed' | 'failed';
12
- processId: number;
13
- admission?: ProfileEvidence & { launchPolicyHash: string };
14
- }) {
15
- super('Native server admission failed', { cause: options.cause });
16
- this.cleanup = options.cleanup;
17
- this.processId = options.processId;
18
- this.admission = options.admission;
10
+
11
+ constructor(
12
+ parameters: {
13
+ admission?: ProfileEvidence & { launchPolicyHash: string };
14
+ cause: unknown;
15
+ cleanup: 'confirmed' | 'failed';
16
+ processId: number;
17
+ },
18
+ options?: ErrorOptions
19
+ ) {
20
+ super('Native server admission failed', options);
21
+ Object.defineProperty(this, 'cause', {
22
+ configurable: true,
23
+ enumerable: false,
24
+ value: parameters.cause,
25
+ writable: true,
26
+ });
27
+ this.admission = parameters.admission;
28
+ this.cleanup = parameters.cleanup;
29
+ this.processId = parameters.processId;
19
30
  }
20
31
  }
21
32
 
22
- export function failureDiagnostic(error: unknown): Record<string, unknown> {
23
- if (error instanceof ServerAdmissionFailure) return failureDiagnostic(error.cause);
24
- if (error instanceof z.ZodError)
25
- return {
26
- kind: 'invalid-native-response',
27
- fields: error.issues.map((issue) => issue.path.join('.')),
33
+ export { ServerAdmissionFailureError as ServerAdmissionFailure };
34
+
35
+ export const failureDiagnostic = (error: unknown): Record<string, unknown> => {
36
+ let current = error;
37
+ while (current instanceof ServerAdmissionFailureError) {
38
+ current = current.cause;
39
+ }
40
+ if (current instanceof z.ZodError) {
41
+ const diagnosticKind = { kind: 'invalid-native-response' };
42
+ const diagnosticFields = {
43
+ fields: current.issues.map((issue) => issue.path.join('.')),
28
44
  };
29
- if (error instanceof AppServerRpcError) return { kind: 'rpc-rejection', code: error.code };
30
- if (error instanceof Error && error.constructor === Error)
31
- return { kind: 'native-admission', message: error.message };
45
+ return { ...diagnosticKind, ...diagnosticFields };
46
+ }
47
+ if (current instanceof AppServerRpcError) {
48
+ const diagnosticKind = { kind: 'rpc-rejection' };
49
+ const diagnosticCode = { code: current.code };
50
+ return { ...diagnosticKind, ...diagnosticCode };
51
+ }
52
+ if (Error.isError(current) && current.constructor === Error) {
53
+ return { kind: 'native-admission', message: current.message };
54
+ }
32
55
  return { kind: 'native-failure' };
33
- }
56
+ };