@penpair/core 0.2.0 → 0.3.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 (101) hide show
  1. package/dist/adapters.js +58 -11
  2. package/dist/approvals.js +143 -29
  3. package/dist/atomic-output.js +9 -4
  4. package/dist/audit-rules.js +345 -79
  5. package/dist/backup-note.js +29 -8
  6. package/dist/baseline-coverage.js +169 -35
  7. package/dist/baselines-config.js +119 -29
  8. package/dist/baselines.js +280 -57
  9. package/dist/cli-arguments.js +18 -6
  10. package/dist/cli-main.js +1621 -392
  11. package/dist/client.d.ts +5 -2
  12. package/dist/client.js +127 -28
  13. package/dist/command-result.js +341 -52
  14. package/dist/coordination.js +91 -29
  15. package/dist/credentials.js +57 -13
  16. package/dist/deep-check.js +56 -12
  17. package/dist/design-spec.js +189 -49
  18. package/dist/endpoint.js +102 -34
  19. package/dist/export.js +86 -18
  20. package/dist/file-archive.js +33 -7
  21. package/dist/file-guard.js +29 -11
  22. package/dist/formats.js +215 -14
  23. package/dist/index.d.ts +8 -7
  24. package/dist/index.js +8 -7
  25. package/dist/inspection.d.ts +1 -1
  26. package/dist/inspection.js +378 -65
  27. package/dist/instance/config-schema.js +400 -41
  28. package/dist/instance/config.js +101 -30
  29. package/dist/instance/connect.js +151 -44
  30. package/dist/instance/connection.d.ts +26 -5
  31. package/dist/instance/connection.js +194 -21
  32. package/dist/instance/gitignore.js +5 -2
  33. package/dist/instance/init.js +32 -7
  34. package/dist/instance/runner.js +32 -10
  35. package/dist/instance/setup.js +46 -13
  36. package/dist/instance/tty.js +26 -8
  37. package/dist/journal.d.ts +10 -0
  38. package/dist/journal.js +212 -49
  39. package/dist/kit-smoke.js +82 -20
  40. package/dist/kit-source.js +10 -40
  41. package/dist/kit.js +102 -34
  42. package/dist/layout-policy.d.ts +2 -0
  43. package/dist/layout-policy.js +86 -0
  44. package/dist/layout.d.ts +7 -1
  45. package/dist/layout.js +27 -70
  46. package/dist/live-smoke.js +13 -4
  47. package/dist/local-profile.js +2 -1
  48. package/dist/mcp-server.js +360 -47
  49. package/dist/native-api.js +47 -11
  50. package/dist/onboard.js +33 -7
  51. package/dist/operation.d.ts +2 -2
  52. package/dist/operation.js +279 -69
  53. package/dist/page-session.d.ts +23 -0
  54. package/dist/page-session.js +142 -0
  55. package/dist/png.js +18 -3
  56. package/dist/portable-tokens.js +254 -57
  57. package/dist/probe.d.ts +1 -1
  58. package/dist/probe.js +48 -11
  59. package/dist/project-input.js +24 -10
  60. package/dist/project-snippets.js +18 -6
  61. package/dist/project-status.js +133 -24
  62. package/dist/registry.js +217 -60
  63. package/dist/run-file.js +129 -30
  64. package/dist/shots.js +108 -26
  65. package/dist/skill-instructions.js +91 -22
  66. package/dist/skills.js +129 -39
  67. package/dist/snippet.js +27 -7
  68. package/dist/snippets/read.js +8 -2
  69. package/dist/structure-snapshot.d.ts +12 -0
  70. package/dist/structure-snapshot.js +439 -85
  71. package/dist/token-edit.js +219 -64
  72. package/dist/token-projection.d.ts +56 -0
  73. package/dist/token-projection.js +286 -0
  74. package/dist/tokens-diff.js +125 -32
  75. package/dist/version.d.ts +1 -1
  76. package/dist/version.js +1 -1
  77. package/package.json +1 -1
  78. package/skills/penpair-design/SKILL.md +3 -0
  79. package/skills/penpair-design/references/penpot-plugin-gotchas.md +2 -2
  80. package/skills/penpair-design/references/project-facts.md +2 -0
  81. package/skills/penpair-handoff/SKILL.md +2 -0
  82. package/skills/penpair-handoff/references/comparison-companion.md +13 -0
  83. package/skills/penpair-handoff/references/handoff-flow.md +2 -0
  84. package/skills/penpair-recipes/SKILL.md +5 -0
  85. package/skills/penpair-setup/SKILL.md +3 -1
  86. package/dist/cli.d.ts +0 -2
  87. package/dist/cli.js +0 -3
  88. package/dist/docker/lifecycle.d.ts +0 -96
  89. package/dist/docker/lifecycle.js +0 -273
  90. package/dist/docker/secrets.d.ts +0 -11
  91. package/dist/docker/secrets.js +0 -38
  92. package/dist/docker/template.d.ts +0 -8
  93. package/dist/docker/template.js +0 -158
  94. package/dist/docker-profile.d.ts +0 -114
  95. package/dist/docker-profile.js +0 -18
  96. package/dist/instance/lifecycle.d.ts +0 -129
  97. package/dist/instance/lifecycle.js +0 -336
  98. package/dist/instance/secrets.d.ts +0 -11
  99. package/dist/instance/secrets.js +0 -38
  100. package/dist/instance/template.d.ts +0 -8
  101. package/dist/instance/template.js +0 -158
@@ -2,12 +2,12 @@ import { setTimeout as wait } from "node:timers/promises";
2
2
  import { AsyncLocalStorage } from "node:async_hooks";
3
3
  import { redactEndpoint } from "./endpoint.js";
4
4
  const scope = new AsyncLocalStorage();
5
- const line = (values) => values.map((value) => typeof value === "string" ? value : JSON.stringify(value)).join(" ");
5
+ const line = (values) => values.map((value) => (typeof value === "string" ? value : JSON.stringify(value))).join(" ");
6
6
  export const commandOutput = {
7
7
  log(...values) {
8
8
  const current = scope.getStore();
9
9
  if (!current) {
10
- console.log(...values.map(value => renderHuman(value)));
10
+ console.log(...values.map((value) => renderHuman(value)));
11
11
  return;
12
12
  }
13
13
  const message = line(values);
@@ -22,38 +22,78 @@ export const commandOutput = {
22
22
  const current = scope.getStore();
23
23
  if (current) {
24
24
  const message = current.redactors.reduce((text, redact) => redact(text), redactEndpoint(line(values)));
25
- if (message !== line(values))
25
+ if (message !== line(values)) {
26
26
  current.redacted = true;
27
+ }
27
28
  current.warnings.push(message);
28
29
  console.error(message);
29
30
  }
30
- else
31
- console.error(...values.map(value => renderHuman(value)));
31
+ else {
32
+ console.error(...values.map((value) => renderHuman(value)));
33
+ }
34
+ },
35
+ result(value) {
36
+ const current = scope.getStore();
37
+ if (current) {
38
+ current.authoritative = value;
39
+ }
32
40
  },
33
- result(value) { const current = scope.getStore(); if (current)
34
- current.authoritative = value; },
35
41
  };
36
- export function commandRedactor(redact) { (scope.getStore()?.redactors ?? signals.getStore()?.redactors)?.push(redact); }
42
+ export function commandRedactor(redact) {
43
+ (scope.getStore()?.redactors ?? signals.getStore()?.redactors)?.push(redact);
44
+ }
37
45
  export function commandFailure(error) {
38
46
  const current = scope.getStore();
39
- if (!current)
47
+ if (!current) {
40
48
  return;
49
+ }
41
50
  const value = error;
42
- if (current.signal?.aborted && value?.code !== "write-uncertain" && value?.outcome !== "uncertain") {
51
+ if (current.signal?.aborted &&
52
+ value?.code !== "write-uncertain" &&
53
+ value?.outcome !== "uncertain") {
43
54
  const reason = current.signal.reason;
44
- current.errors.push({ code: reason?.code === "deadline" ? "deadline" : "cancelled", message: reason?.code === "deadline" ? "Command deadline exceeded" : "Command cancelled", remedy: "Inspect any recorded run and current state before starting again." });
55
+ current.errors.push({
56
+ code: reason?.code === "deadline" ? "deadline" : "cancelled",
57
+ message: reason?.code === "deadline" ? "Command deadline exceeded" : "Command cancelled",
58
+ remedy: "Inspect any recorded run and current state before starting again.",
59
+ });
45
60
  return;
46
61
  }
47
- current.errors.push({ code: typeof value?.code === "string" ? value.code : typeof value?.outcome === "string" ? value.outcome : "command-failed",
48
- message: error instanceof Error ? error.message : String(error), remedy: typeof value?.remedy === "string" ? value.remedy : "Inspect the command result and current project state before trying again.",
49
- ...(typeof value?.runId === "string" ? { runId: value.runId } : {}) });
62
+ current.errors.push({
63
+ code: typeof value?.code === "string"
64
+ ? value.code
65
+ : typeof value?.outcome === "string"
66
+ ? value.outcome
67
+ : "command-failed",
68
+ message: error instanceof Error ? error.message : String(error),
69
+ remedy: typeof value?.remedy === "string"
70
+ ? value.remedy
71
+ : "Inspect the command result and current project state before trying again.",
72
+ ...(typeof value?.runId === "string"
73
+ ? {
74
+ runId: value.runId,
75
+ }
76
+ : {}),
77
+ });
50
78
  }
51
79
  const signals = new AsyncLocalStorage();
52
- function redactHuman(text) { return (signals.getStore()?.redactors ?? []).reduce((value, redact) => redact(value), redactEndpoint(text)); }
80
+ function redactHuman(text) {
81
+ return (signals.getStore()?.redactors ?? []).reduce((value, redact) => redact(value), redactEndpoint(text));
82
+ }
53
83
  function renderHuman(value) {
54
- const clean = (entry) => typeof entry === "string" ? redactHuman(entry) : Array.isArray(entry) ? entry.map(clean) : entry && typeof entry === "object" ? Object.fromEntries(Object.entries(entry).map(([key, child]) => [redactHuman(key), clean(child)])) : entry;
55
- if (typeof value !== "string")
84
+ const clean = (entry) => typeof entry === "string"
85
+ ? redactHuman(entry)
86
+ : Array.isArray(entry)
87
+ ? entry.map(clean)
88
+ : entry && typeof entry === "object"
89
+ ? Object.fromEntries(Object.entries(entry).map(([key, child]) => [
90
+ redactHuman(key),
91
+ clean(child),
92
+ ]))
93
+ : entry;
94
+ if (typeof value !== "string") {
56
95
  return clean(value);
96
+ }
57
97
  try {
58
98
  return JSON.stringify(clean(JSON.parse(value)), null, 2);
59
99
  }
@@ -61,12 +101,18 @@ function renderHuman(value) {
61
101
  return redactHuman(value);
62
102
  }
63
103
  }
64
- export function withCommandSignal(signal, work) { return signals.run({ signal, redactors: [] }, work); }
104
+ export function withCommandSignal(signal, work) {
105
+ return signals.run({
106
+ signal,
107
+ redactors: [],
108
+ }, work);
109
+ }
65
110
  const cleanupSignals = new AsyncLocalStorage();
66
111
  /** Bounded explicit restoration work may finish after the main command is cancelled. */
67
112
  export async function withCleanupDeadline(work, milliseconds = 10000) {
68
- if (!Number.isInteger(milliseconds) || milliseconds < 1 || milliseconds > 30000)
113
+ if (!Number.isInteger(milliseconds) || milliseconds < 1 || milliseconds > 30000) {
69
114
  throw new Error("Cleanup deadline must be from 1 to 30000 ms");
115
+ }
70
116
  const controller = new AbortController();
71
117
  const timer = setTimeout(() => controller.abort(new Error("Restoration deadline exceeded")), milliseconds);
72
118
  try {
@@ -76,15 +122,20 @@ export async function withCleanupDeadline(work, milliseconds = 10000) {
76
122
  clearTimeout(timer);
77
123
  }
78
124
  }
79
- export function commandSignal() { return cleanupSignals.getStore() ?? scope.getStore()?.signal ?? signals.getStore()?.signal; }
125
+ export function commandSignal() {
126
+ return cleanupSignals.getStore() ?? scope.getStore()?.signal ?? signals.getStore()?.signal;
127
+ }
80
128
  export async function commandSleep(milliseconds) {
81
129
  const signal = commandSignal();
82
130
  try {
83
- await wait(milliseconds, undefined, { signal });
131
+ await wait(milliseconds, undefined, {
132
+ signal,
133
+ });
84
134
  }
85
135
  catch (error) {
86
- if (signal?.aborted)
136
+ if (signal?.aborted) {
87
137
  throw signal.reason;
138
+ }
88
139
  throw error;
89
140
  }
90
141
  }
@@ -98,24 +149,64 @@ export class CommandInterruptedError extends Error {
98
149
  /** Shared service boundary for CLI and future tool surfaces; no stdout or global console interception. */
99
150
  export async function collectCommand(command, work, options = {}) {
100
151
  const started = Date.now();
101
- const capture = { data: [], messages: [], warnings: [], errors: [], redactors: [], signal: options.signal };
152
+ const capture = {
153
+ data: [],
154
+ messages: [],
155
+ warnings: [],
156
+ errors: [],
157
+ redactors: [],
158
+ signal: options.signal,
159
+ };
102
160
  let exitCode;
103
161
  try {
104
- exitCode = await scope.run(capture, async () => { options.signal?.throwIfAborted(); const code = await work(); options.signal?.throwIfAborted(); return code; });
162
+ exitCode = await scope.run(capture, async () => {
163
+ options.signal?.throwIfAborted();
164
+ const code = await work();
165
+ options.signal?.throwIfAborted();
166
+ return code;
167
+ });
105
168
  }
106
169
  catch (error) {
107
170
  await scope.run(capture, async () => commandFailure(error));
108
171
  exitCode = 1;
109
172
  }
110
- if (![0, 1, 2, 3, 4, 124, 130].includes(exitCode)) {
173
+ if (![
174
+ 0,
175
+ 1,
176
+ 2,
177
+ 3,
178
+ 4,
179
+ 124,
180
+ 130,
181
+ ].includes(exitCode)) {
111
182
  capture.warnings.push("Unsupported extension exit code was normalized to failure");
112
183
  exitCode = 1;
113
184
  }
114
- const data = capture.authoritative !== undefined ? capture.authoritative : capture.data.length === 1 ? capture.data[0] : capture.data.length ? capture.data : null;
115
- const record = data && typeof data === "object" && !Array.isArray(data) ? data : {};
185
+ const data = capture.authoritative !== undefined
186
+ ? capture.authoritative
187
+ : capture.data.length === 1
188
+ ? capture.data[0]
189
+ : capture.data.length
190
+ ? capture.data
191
+ : null;
192
+ const record = data && typeof data === "object" && !Array.isArray(data)
193
+ ? data
194
+ : {};
116
195
  const errorCode = capture.errors[0]?.code;
117
196
  const incomplete = record.complete === false || record.truncated === true || errorCode === "incomplete-output";
118
- let outcome = exitCode === 0 ? "success" : exitCode === 2 ? "drift" : exitCode === 3 ? "incomplete" : exitCode === 4 ? "uncertain" : exitCode === 130 ? "cancelled" : exitCode === 124 ? "deadline" : "failed";
197
+ let outcome = exitCode === 0
198
+ ? "success"
199
+ : exitCode === 2
200
+ ? "drift"
201
+ : exitCode === 3
202
+ ? "incomplete"
203
+ : exitCode === 4
204
+ ? "uncertain"
205
+ : exitCode === 130
206
+ ? "cancelled"
207
+ : exitCode === 124
208
+ ? "deadline"
209
+ : "failed";
119
210
  if (errorCode === "write-uncertain" || errorCode === "uncertain") {
120
211
  outcome = "uncertain";
121
212
  exitCode = 4;
@@ -128,31 +219,93 @@ export async function collectCommand(command, work, options = {}) {
128
219
  outcome = "incomplete";
129
220
  exitCode = 3;
130
221
  }
131
- if (exitCode !== 0 && !capture.errors.length && !["drift", "incomplete"].includes(outcome))
132
- capture.errors.push({ code: "command-failed", message: "Command checks failed; inspect data and diagnostics.", remedy: "Resolve the reported checks before proceeding." });
222
+ if (exitCode !== 0 &&
223
+ !capture.errors.length &&
224
+ ![
225
+ "drift",
226
+ "incomplete",
227
+ ].includes(outcome)) {
228
+ capture.errors.push({
229
+ code: "command-failed",
230
+ message: "Command checks failed; inspect data and diagnostics.",
231
+ remedy: "Resolve the reported checks before proceeding.",
232
+ });
233
+ }
133
234
  const identity = (record.capture ?? record.target ?? record);
134
- const target = typeof identity.fileId === "string" ? { fileId: identity.fileId, ...(typeof identity.pageId === "string" ? { pageId: identity.pageId } : {}) } : null;
235
+ const target = typeof identity.fileId === "string"
236
+ ? {
237
+ fileId: identity.fileId,
238
+ ...(typeof identity.pageId === "string"
239
+ ? {
240
+ pageId: identity.pageId,
241
+ }
242
+ : {}),
243
+ }
244
+ : null;
135
245
  const journal = record.journal;
136
246
  const sanitize = (value) => {
137
- if (value === undefined || typeof value === "function" || typeof value === "symbol" || typeof value === "bigint" || typeof value === "number" && !Number.isFinite(value))
247
+ if (value === undefined ||
248
+ typeof value === "function" ||
249
+ typeof value === "symbol" ||
250
+ typeof value === "bigint" ||
251
+ (typeof value === "number" && !Number.isFinite(value))) {
138
252
  return null;
139
- if (typeof value === "string")
253
+ }
254
+ if (typeof value === "string") {
140
255
  return capture.redactors.reduce((text, redact) => redact(text), redactEndpoint(value));
141
- if (Array.isArray(value))
256
+ }
257
+ if (Array.isArray(value)) {
142
258
  return value.map(sanitize);
143
- if (value && typeof value === "object")
144
- return Object.fromEntries(Object.entries(value).map(([key, entry]) => [String(sanitize(key)), sanitize(entry)]));
259
+ }
260
+ if (value && typeof value === "object") {
261
+ return Object.fromEntries(Object.entries(value).map(([key, entry]) => [
262
+ String(sanitize(key)),
263
+ sanitize(entry),
264
+ ]));
265
+ }
145
266
  return value;
146
267
  };
147
- const raw = { schemaVersion: 1, command, outcome, phase: typeof record.phase === "string" ? record.phase : outcome === "uncertain" || outcome === "cancelled" || outcome === "deadline" ? "interrupted" : "finished", exitCode, target,
148
- runId: capture.errors.find((error) => error.runId)?.runId ?? (typeof journal?.runId === "string" ? journal.runId : null), data, messages: capture.messages, warnings: capture.warnings, errors: capture.errors,
149
- completeness: { complete: outcome === "success" || outcome === "drift", ...(incomplete ? { reason: "Output is truncated or required observations are missing" } : {}) }, timings: { startedAt: new Date(started).toISOString(), durationMs: Math.max(0, Date.now() - started) } };
268
+ const raw = {
269
+ schemaVersion: 1,
270
+ command,
271
+ outcome,
272
+ phase: typeof record.phase === "string"
273
+ ? record.phase
274
+ : outcome === "uncertain" || outcome === "cancelled" || outcome === "deadline"
275
+ ? "interrupted"
276
+ : "finished",
277
+ exitCode,
278
+ target,
279
+ runId: capture.errors.find((error) => error.runId)?.runId ??
280
+ (typeof journal?.runId === "string" ? journal.runId : null),
281
+ data,
282
+ messages: capture.messages,
283
+ warnings: capture.warnings,
284
+ errors: capture.errors,
285
+ completeness: {
286
+ complete: outcome === "success" || outcome === "drift",
287
+ ...(incomplete
288
+ ? {
289
+ reason: "Output is truncated or required observations are missing",
290
+ }
291
+ : {}),
292
+ },
293
+ timings: {
294
+ startedAt: new Date(started).toISOString(),
295
+ durationMs: Math.max(0, Date.now() - started),
296
+ },
297
+ };
150
298
  const result = sanitize(raw);
151
- if (record.redacted === true || capture.redacted || JSON.stringify(result) !== JSON.stringify(raw)) {
299
+ if (record.redacted === true ||
300
+ capture.redacted ||
301
+ JSON.stringify(result) !== JSON.stringify(raw)) {
152
302
  result.redacted = true;
153
303
  result.warnings.push("Sensitive or non-JSON output was redacted; hashes describe original evidence.");
154
304
  if (JSON.stringify(result.data) !== JSON.stringify(data)) {
155
- result.completeness = { complete: false, reason: "Displayed data required redaction and is not lossless source evidence" };
305
+ result.completeness = {
306
+ complete: false,
307
+ reason: "Displayed data required redaction and is not lossless source evidence",
308
+ };
156
309
  if (result.outcome === "success" || result.outcome === "drift") {
157
310
  result.outcome = "incomplete";
158
311
  result.exitCode = 3;
@@ -163,17 +316,153 @@ export async function collectCommand(command, work, options = {}) {
163
316
  }
164
317
  /** Outer transport contract. Command-specific data remains owned by the command service. */
165
318
  export const commandResultSchema = {
166
- type: "object", additionalProperties: false,
167
- required: ["schemaVersion", "command", "outcome", "phase", "exitCode", "target", "runId", "data", "messages", "warnings", "errors", "completeness", "timings"],
319
+ type: "object",
320
+ additionalProperties: false,
321
+ required: [
322
+ "schemaVersion",
323
+ "command",
324
+ "outcome",
325
+ "phase",
326
+ "exitCode",
327
+ "target",
328
+ "runId",
329
+ "data",
330
+ "messages",
331
+ "warnings",
332
+ "errors",
333
+ "completeness",
334
+ "timings",
335
+ ],
168
336
  properties: {
169
- schemaVersion: { const: 1 }, redacted: { type: "boolean" }, command: { type: "string" },
170
- outcome: { enum: ["success", "drift", "failed", "incomplete", "uncertain", "cancelled", "deadline"] },
171
- phase: { type: "string" }, exitCode: { enum: [0, 1, 2, 3, 4, 124, 130] },
172
- target: { anyOf: [{ type: "null" }, { type: "object", properties: { fileId: { type: "string" }, pageId: { type: "string" } }, additionalProperties: false }] },
173
- runId: { type: ["string", "null"] }, data: {},
174
- messages: { type: "array", items: { type: "string" } }, warnings: { type: "array", items: { type: "string" } },
175
- errors: { type: "array", items: { type: "object", required: ["code", "message", "remedy"], properties: { code: { type: "string" }, message: { type: "string" }, remedy: { type: "string" }, runId: { type: "string" } }, additionalProperties: false } },
176
- completeness: { type: "object", required: ["complete"], properties: { complete: { type: "boolean" }, reason: { type: "string" } }, additionalProperties: false },
177
- timings: { type: "object", required: ["startedAt", "durationMs"], properties: { startedAt: { type: "string" }, durationMs: { type: "number", minimum: 0 } }, additionalProperties: false },
337
+ schemaVersion: {
338
+ const: 1,
339
+ },
340
+ redacted: {
341
+ type: "boolean",
342
+ },
343
+ command: {
344
+ type: "string",
345
+ },
346
+ outcome: {
347
+ enum: [
348
+ "success",
349
+ "drift",
350
+ "failed",
351
+ "incomplete",
352
+ "uncertain",
353
+ "cancelled",
354
+ "deadline",
355
+ ],
356
+ },
357
+ phase: {
358
+ type: "string",
359
+ },
360
+ exitCode: {
361
+ enum: [
362
+ 0,
363
+ 1,
364
+ 2,
365
+ 3,
366
+ 4,
367
+ 124,
368
+ 130,
369
+ ],
370
+ },
371
+ target: {
372
+ anyOf: [
373
+ {
374
+ type: "null",
375
+ },
376
+ {
377
+ type: "object",
378
+ properties: {
379
+ fileId: {
380
+ type: "string",
381
+ },
382
+ pageId: {
383
+ type: "string",
384
+ },
385
+ },
386
+ additionalProperties: false,
387
+ },
388
+ ],
389
+ },
390
+ runId: {
391
+ type: [
392
+ "string",
393
+ "null",
394
+ ],
395
+ },
396
+ data: {},
397
+ messages: {
398
+ type: "array",
399
+ items: {
400
+ type: "string",
401
+ },
402
+ },
403
+ warnings: {
404
+ type: "array",
405
+ items: {
406
+ type: "string",
407
+ },
408
+ },
409
+ errors: {
410
+ type: "array",
411
+ items: {
412
+ type: "object",
413
+ required: [
414
+ "code",
415
+ "message",
416
+ "remedy",
417
+ ],
418
+ properties: {
419
+ code: {
420
+ type: "string",
421
+ },
422
+ message: {
423
+ type: "string",
424
+ },
425
+ remedy: {
426
+ type: "string",
427
+ },
428
+ runId: {
429
+ type: "string",
430
+ },
431
+ },
432
+ additionalProperties: false,
433
+ },
434
+ },
435
+ completeness: {
436
+ type: "object",
437
+ required: [
438
+ "complete",
439
+ ],
440
+ properties: {
441
+ complete: {
442
+ type: "boolean",
443
+ },
444
+ reason: {
445
+ type: "string",
446
+ },
447
+ },
448
+ additionalProperties: false,
449
+ },
450
+ timings: {
451
+ type: "object",
452
+ required: [
453
+ "startedAt",
454
+ "durationMs",
455
+ ],
456
+ properties: {
457
+ startedAt: {
458
+ type: "string",
459
+ },
460
+ durationMs: {
461
+ type: "number",
462
+ minimum: 0,
463
+ },
464
+ },
465
+ additionalProperties: false,
466
+ },
178
467
  },
179
468
  };