@markii/host 0.0.1 → 0.17.0-next.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 (192) hide show
  1. package/README.md +18 -2
  2. package/dist/browser-worker.js +639 -0
  3. package/dist/browser.d.ts +57 -0
  4. package/dist/browser.js +167 -0
  5. package/dist/conformance/host/01-plain-open/expected.json +5 -0
  6. package/dist/conformance/host/01-plain-open/note.mk.md +8 -0
  7. package/dist/conformance/host/01-plain-open/scenario.json +6 -0
  8. package/dist/conformance/host/02-run-manual-grant-accepted/expected.json +16 -0
  9. package/dist/conformance/host/02-run-manual-grant-accepted/note.mk.md +5 -0
  10. package/dist/conformance/host/02-run-manual-grant-accepted/scenario.json +14 -0
  11. package/dist/conformance/host/03-run-manual-grant-refused/expected.json +18 -0
  12. package/dist/conformance/host/03-run-manual-grant-refused/note.mk.md +5 -0
  13. package/dist/conformance/host/03-run-manual-grant-refused/scenario.json +14 -0
  14. package/dist/conformance/host/04-run-auto-write-denied/expected.json +16 -0
  15. package/dist/conformance/host/04-run-auto-write-denied/note.mk.md +10 -0
  16. package/dist/conformance/host/04-run-auto-write-denied/scenario.json +13 -0
  17. package/dist/conformance/host/05-export-html-pack-directive/expected.json +14 -0
  18. package/dist/conformance/host/05-export-html-pack-directive/note.mk.md +8 -0
  19. package/dist/conformance/host/05-export-html-pack-directive/scenario.json +6 -0
  20. package/dist/conformance/host/06-install-pack-then-render/expected.json +18 -0
  21. package/dist/conformance/host/06-install-pack-then-render/files/ana-1.0.0/pack.json +13 -0
  22. package/dist/conformance/host/06-install-pack-then-render/files/ana-1.0.0/webview.js +1 -0
  23. package/dist/conformance/host/06-install-pack-then-render/note.mk.md +5 -0
  24. package/dist/conformance/host/06-install-pack-then-render/scenario.json +15 -0
  25. package/dist/conformance/host/07-complete-in-directive/expected.json +14 -0
  26. package/dist/conformance/host/07-complete-in-directive/note.mk.md +3 -0
  27. package/dist/conformance/host/07-complete-in-directive/scenario.json +6 -0
  28. package/dist/conformance/host/08-hover-standard-component/expected.json +5 -0
  29. package/dist/conformance/host/08-hover-standard-component/note.mk.md +5 -0
  30. package/dist/conformance/host/08-hover-standard-component/scenario.json +6 -0
  31. package/dist/conformance/host/09-insert-skeleton/expected.json +5 -0
  32. package/dist/conformance/host/09-insert-skeleton/note.mk.md +4 -0
  33. package/dist/conformance/host/09-insert-skeleton/scenario.json +6 -0
  34. package/dist/conformance/host/10-scripts-disabled/expected.json +11 -0
  35. package/dist/conformance/host/10-scripts-disabled/note.mk.md +5 -0
  36. package/dist/conformance/host/10-scripts-disabled/scenario.json +9 -0
  37. package/dist/conformance/host/11-complete-closing-fence/expected.json +5 -0
  38. package/dist/conformance/host/11-complete-closing-fence/note.mk.md +5 -0
  39. package/dist/conformance/host/11-complete-closing-fence/scenario.json +12 -0
  40. package/dist/conformance/host/12-complete-nested-container/expected.json +5 -0
  41. package/dist/conformance/host/12-complete-nested-container/note.mk.md +5 -0
  42. package/dist/conformance/host/12-complete-nested-container/scenario.json +12 -0
  43. package/dist/conformance/host/13-install-pack-declined/expected.json +12 -0
  44. package/dist/conformance/host/13-install-pack-declined/files/ana-1.0.0/pack.json +13 -0
  45. package/dist/conformance/host/13-install-pack-declined/files/ana-1.0.0/webview.js +1 -0
  46. package/dist/conformance/host/13-install-pack-declined/note.mk.md +5 -0
  47. package/dist/conformance/host/13-install-pack-declined/scenario.json +10 -0
  48. package/dist/conformance/host/14-remove-pack/expected.json +14 -0
  49. package/dist/conformance/host/14-remove-pack/files/ana-1.0.0/pack.json +13 -0
  50. package/dist/conformance/host/14-remove-pack/files/ana-1.0.0/webview.js +1 -0
  51. package/dist/conformance/host/14-remove-pack/note.mk.md +5 -0
  52. package/dist/conformance/host/14-remove-pack/scenario.json +11 -0
  53. package/dist/conformance/host/15-load-packs-engine-views/expected.json +19 -0
  54. package/dist/conformance/host/15-load-packs-engine-views/files/duo-1.0.0/pack.json +9 -0
  55. package/dist/conformance/host/15-load-packs-engine-views/files/duo-1.0.0/webview.js +1 -0
  56. package/dist/conformance/host/15-load-packs-engine-views/note.mk.md +9 -0
  57. package/dist/conformance/host/15-load-packs-engine-views/scenario.json +10 -0
  58. package/dist/conformance/host/16-pack-trust/expected.json +37 -0
  59. package/dist/conformance/host/16-pack-trust/files/ana-1.0.0/pack.json +12 -0
  60. package/dist/conformance/host/16-pack-trust/files/ana-1.0.0/webview.js +1 -0
  61. package/dist/conformance/host/16-pack-trust/files/sneaky-1.0.0/pack.json +12 -0
  62. package/dist/conformance/host/16-pack-trust/files/sneaky-1.0.0/webview.js +1 -0
  63. package/dist/conformance/host/16-pack-trust/note.mk.md +5 -0
  64. package/dist/conformance/host/16-pack-trust/scenario.json +26 -0
  65. package/dist/conformance/host/README.md +221 -0
  66. package/dist/conformance.d.ts +2 -0
  67. package/dist/conformance.js +5 -0
  68. package/dist/diagnostics/render-diagnostics.d.ts +41 -0
  69. package/dist/diagnostics/render-diagnostics.js +46 -0
  70. package/dist/export/cascade.d.ts +98 -0
  71. package/dist/export/cascade.js +159 -0
  72. package/dist/export/export-zip.d.ts +19 -0
  73. package/dist/export/export-zip.js +38 -0
  74. package/dist/export/image-embed.d.ts +126 -0
  75. package/dist/export/image-embed.js +274 -0
  76. package/dist/export/note-export.d.ts +332 -0
  77. package/dist/export/note-export.js +366 -0
  78. package/dist/export/note-links.d.ts +83 -0
  79. package/dist/export/note-links.js +241 -0
  80. package/dist/glue.wasm +0 -0
  81. package/dist/host/adapter.d.ts +223 -0
  82. package/dist/host/adapter.js +9 -0
  83. package/dist/host/cascade-export-behavior.d.ts +120 -0
  84. package/dist/host/cascade-export-behavior.js +151 -0
  85. package/dist/host/conformance-runner.d.ts +156 -0
  86. package/dist/host/conformance-runner.js +634 -0
  87. package/dist/host/create-host.d.ts +253 -0
  88. package/dist/host/create-host.js +329 -0
  89. package/dist/host/editor-behavior.d.ts +157 -0
  90. package/dist/host/editor-behavior.js +227 -0
  91. package/dist/host/export/md-plain.d.ts +15 -0
  92. package/dist/host/export/md-plain.js +125 -0
  93. package/dist/host/export-behavior.d.ts +113 -0
  94. package/dist/host/export-behavior.js +257 -0
  95. package/dist/host/labels.d.ts +24 -0
  96. package/dist/host/labels.js +32 -0
  97. package/dist/host/pack-archive.d.ts +30 -0
  98. package/dist/host/pack-archive.js +68 -0
  99. package/dist/host/pack-install.d.ts +88 -0
  100. package/dist/host/pack-install.js +189 -0
  101. package/dist/host/pack-load.d.ts +72 -0
  102. package/dist/host/pack-load.js +57 -0
  103. package/dist/host/pack-remove.d.ts +21 -0
  104. package/dist/host/pack-remove.js +65 -0
  105. package/dist/host/pack-trust-behavior.d.ts +79 -0
  106. package/dist/host/pack-trust-behavior.js +262 -0
  107. package/dist/host/refresh-interval.d.ts +39 -0
  108. package/dist/host/refresh-interval.js +61 -0
  109. package/dist/host/run-behavior.d.ts +89 -0
  110. package/dist/host/run-behavior.js +168 -0
  111. package/dist/host/run-diagnostics.d.ts +26 -0
  112. package/dist/host/run-diagnostics.js +43 -0
  113. package/dist/host/script-execution.d.ts +72 -0
  114. package/dist/host/script-execution.js +68 -0
  115. package/dist/host/terminal-packs.d.ts +39 -0
  116. package/dist/host/terminal-packs.js +51 -0
  117. package/dist/html-engine.d.ts +2 -0
  118. package/dist/html-engine.js +29 -0
  119. package/dist/index.d.ts +95 -0
  120. package/dist/index.js +70 -0
  121. package/dist/insert/component-catalog.d.ts +23 -0
  122. package/dist/insert/component-catalog.js +96 -0
  123. package/dist/node-fs.d.ts +14 -0
  124. package/dist/node-fs.js +70 -0
  125. package/dist/node.d.ts +5 -0
  126. package/dist/node.js +13 -0
  127. package/dist/package.json +3 -0
  128. package/dist/packs/discover.d.ts +119 -0
  129. package/dist/packs/discover.js +266 -0
  130. package/dist/packs/pack-diagnostics.d.ts +81 -0
  131. package/dist/packs/pack-diagnostics.js +105 -0
  132. package/dist/packs/pack-paths.d.ts +53 -0
  133. package/dist/packs/pack-paths.js +106 -0
  134. package/dist/packs/pack-render-registry.d.ts +83 -0
  135. package/dist/packs/pack-render-registry.js +182 -0
  136. package/dist/packs/pack-scripts.d.ts +20 -0
  137. package/dist/packs/pack-scripts.js +96 -0
  138. package/dist/packs/pack-trust.d.ts +57 -0
  139. package/dist/packs/pack-trust.js +111 -0
  140. package/dist/packs/prebuilt.d.ts +49 -0
  141. package/dist/packs/prebuilt.js +97 -0
  142. package/dist/path-util.d.ts +24 -0
  143. package/dist/path-util.js +82 -0
  144. package/dist/run/browser-isolate.d.ts +45 -0
  145. package/dist/run/browser-isolate.js +81 -0
  146. package/dist/run/bundle-document.d.ts +29 -0
  147. package/dist/run/bundle-document.js +40 -0
  148. package/dist/run/bundle-run.d.ts +188 -0
  149. package/dist/run/bundle-run.js +232 -0
  150. package/dist/run/grant-flow.d.ts +232 -0
  151. package/dist/run/grant-flow.js +390 -0
  152. package/dist/run/ip-address.d.ts +41 -0
  153. package/dist/run/ip-address.js +255 -0
  154. package/dist/run/isolate.d.ts +57 -0
  155. package/dist/run/isolate.js +22 -0
  156. package/dist/run/lua-resolver.d.ts +21 -0
  157. package/dist/run/lua-resolver.js +63 -0
  158. package/dist/run/net-bridge-worker.d.ts +26 -0
  159. package/dist/run/net-bridge-worker.js +66 -0
  160. package/dist/run/net-bridge.d.ts +56 -0
  161. package/dist/run/net-bridge.js +59 -0
  162. package/dist/run/net-pinning.d.ts +82 -0
  163. package/dist/run/net-pinning.js +137 -0
  164. package/dist/run/net-provider.d.ts +70 -0
  165. package/dist/run/net-provider.js +289 -0
  166. package/dist/run/node-isolate.d.ts +30 -0
  167. package/dist/run/node-isolate.js +117 -0
  168. package/dist/run/run-flow.d.ts +187 -0
  169. package/dist/run/run-flow.js +323 -0
  170. package/dist/run/run-host.d.ts +107 -0
  171. package/dist/run/run-host.js +241 -0
  172. package/dist/run/run-job.d.ts +130 -0
  173. package/dist/run/run-job.js +279 -0
  174. package/dist/run/run-progress.d.ts +55 -0
  175. package/dist/run/run-progress.js +42 -0
  176. package/dist/run/run-trace.d.ts +44 -0
  177. package/dist/run/run-trace.js +36 -0
  178. package/dist/run/script-requirements.d.ts +98 -0
  179. package/dist/run/script-requirements.js +212 -0
  180. package/dist/run/snapshot-storage.d.ts +25 -0
  181. package/dist/run/snapshot-storage.js +31 -0
  182. package/dist/run/stale-values.d.ts +25 -0
  183. package/dist/run/stale-values.js +32 -0
  184. package/dist/run/worker-entry-browser.d.ts +42 -0
  185. package/dist/run/worker-entry-browser.js +77 -0
  186. package/dist/run/worker-entry.d.ts +1 -0
  187. package/dist/run/worker-entry.js +96 -0
  188. package/dist/values-failure.d.ts +17 -0
  189. package/dist/values-failure.js +1 -0
  190. package/dist/values-merge.d.ts +39 -0
  191. package/dist/values-merge.js +33 -0
  192. package/package.json +77 -4
@@ -0,0 +1,117 @@
1
+ /**
2
+ * The Node half of the Run path's isolate seam: a `node:worker_threads`
3
+ * worker, plus the dev/Vitest worker-entry fallback. Split out of
4
+ * `./run-host.ts` so the watchdog, the exactly-once settlement and the
5
+ * never-rejects contract stay in a module with no `node:*` import and run
6
+ * unchanged for every host kind. Reached only through `@markii/host/node`.
7
+ */
8
+ import { Worker } from 'node:worker_threads';
9
+ import { existsSync } from 'node:fs';
10
+ import * as path from 'node:path';
11
+ import { spawnRun } from './run-host.js';
12
+ /** `execArgv` needed to run a `.ts` worker entry directly (dev/Vitest only) — see `./worker-entry.ts`'s doc comment on why a plain `node` `.ts` import can't resolve this repo's `@markii/*` bare specifiers or its `./foo.js` -> `./foo.ts` import convention on its own. `--require tsx/cjs` (rather than `tsx/esm`) is deliberate: empirically, the ESM loader hook deadlocks Node's CJS/ESM interop translator for a worker thread requiring `.js`-suffixed relative imports that resolve to `.ts` files (this repo's convention throughout `@markii/*`), while the CJS `require` hook has no such issue. */
13
+ const TSX_DEV_EXEC_ARGV = ['--require', 'tsx/cjs'];
14
+ /**
15
+ * The ONLY fallback this shared package supplies for an unset `workerPath`:
16
+ * the sibling `worker-entry.ts`, run straight from source via `tsx`
17
+ * (`TSX_DEV_EXEC_ARGV`, above). That is correct for this package's own
18
+ * dev/Vitest runs — `__dirname` at that point is this real source
19
+ * directory — but it says nothing about where a packaged host's bundled
20
+ * worker lives, because this package does not know that host's `dist/`
21
+ * layout. A packaged host (e.g. `apps/vscode`) MUST resolve its own bundled
22
+ * worker path itself and pass it as `SpawnRunOptions.workerPath`; see
23
+ * `apps/vscode/src/worker-path.ts` for that host's resolution and
24
+ * `apps/vscode/esbuild.config.mjs`'s worker build for where it bundles to.
25
+ */
26
+ export function defaultWorkerPath() {
27
+ // The built package is ESM, where `__dirname` does not exist: a packaged
28
+ // host always passes its own bundled worker, so say so instead of
29
+ // throwing a ReferenceError.
30
+ const devSource = typeof __dirname === 'undefined'
31
+ ? undefined
32
+ : path.join(__dirname, 'worker-entry.ts');
33
+ if (devSource !== undefined && existsSync(devSource))
34
+ return devSource;
35
+ throw new Error('spawnRun: could not resolve the worker entry file (looked for ' +
36
+ `${devSource ?? 'worker-entry.ts beside the package source'}) — pass workerPath explicitly`);
37
+ }
38
+ function execArgvFor(workerPath) {
39
+ return workerPath.endsWith('.ts') ? TSX_DEV_EXEC_ARGV : undefined;
40
+ }
41
+ /**
42
+ * Dev/Vitest only: point `tsx` at the repo's `tsconfig.dev.json` so a
43
+ * `.ts` worker entry resolves `@markii/*` to each package's SOURCE.
44
+ *
45
+ * Without this the worker is a plain Node resolution: a bare `@markii/lua`
46
+ * goes through `node_modules` to that package's `exports`, which name the
47
+ * built `dist/`. `npm run build` only type-checks (`tsc --noEmit`); the
48
+ * emitting script is `build:dist`, which runs at release. So `dist/` is
49
+ * whatever a past release left behind, and a real-isolate probe would
50
+ * silently exercise that stale code instead of the source it is written to
51
+ * test. A capability added since the last `build:dist` simply would not
52
+ * exist inside the worker.
53
+ *
54
+ * Gated on a `.ts` entry, the same signal `execArgvFor` already uses for
55
+ * the tsx hook, so a packaged host passing its own bundled `.js` worker is
56
+ * never touched by this.
57
+ */
58
+ function devTsconfigEnv(entryPath) {
59
+ if (!entryPath.endsWith('.ts') || typeof __dirname === 'undefined') {
60
+ return undefined;
61
+ }
62
+ const tsconfig = path.resolve(__dirname, '../../../..', 'tsconfig.dev.json');
63
+ if (!existsSync(tsconfig))
64
+ return undefined;
65
+ return { env: { ...process.env, TSX_TSCONFIG_PATH: tsconfig } };
66
+ }
67
+ /**
68
+ * The default isolate: a `node:worker_threads` worker, which is what every
69
+ * Node host (the VS Code extension host, this package's own tests) uses. It
70
+ * is the only kind that accepts `resourceLimits`, so it is also the only
71
+ * kind whose V8 heap is bounded — see `./isolate.ts` for why a host might
72
+ * still have to supply the other one.
73
+ */
74
+ export const workerThreadIsolate = (options) => {
75
+ const execArgv = options.execArgv ?? execArgvFor(options.entryPath);
76
+ const worker = new Worker(options.entryPath, {
77
+ ...(execArgv ? { execArgv } : {}),
78
+ ...(devTsconfigEnv(options.entryPath) ?? {}),
79
+ resourceLimits: {
80
+ maxOldGenerationSizeMb: options.maxOldGenerationSizeMb,
81
+ },
82
+ });
83
+ return {
84
+ send: (job) => {
85
+ worker.postMessage(job);
86
+ },
87
+ kill: () => {
88
+ void worker.terminate();
89
+ },
90
+ onMessage: (listener) => {
91
+ // `on`, not `once`: a run now sends one progress message per script
92
+ // (GitHub issue #35) ahead of its single result message, and
93
+ // `spawnRun` is what tells the two apart. Messages arriving after
94
+ // the run settles are harmless — `settled` guards every path — and
95
+ // the worker is terminated on settle anyway.
96
+ worker.on('message', listener);
97
+ },
98
+ onError: (listener) => {
99
+ worker.once('error', listener);
100
+ },
101
+ onExit: (listener) => {
102
+ worker.once('exit', listener);
103
+ },
104
+ };
105
+ };
106
+ /**
107
+ * `spawnRun` with the Node defaults filled in: the worker-thread isolate and
108
+ * the dev/Vitest worker entry. For this package's own tests and any Node
109
+ * caller that wants them; a packaged host passes its own `workerPath`.
110
+ */
111
+ export function spawnRunInThread(options) {
112
+ return spawnRun({
113
+ ...options,
114
+ workerPath: options.workerPath ?? defaultWorkerPath(),
115
+ spawnIsolate: options.spawnIsolate ?? workerThreadIsolate,
116
+ });
117
+ }
@@ -0,0 +1,187 @@
1
+ import type { GrantMemento, PromptHost, PromptManyHosts, PromptUnknownHosts } from './grant-flow.js';
2
+ import type { RunResult, SpawnRunRequest } from './run-host.js';
3
+ import type { ValuesFailure } from '../values-failure.js';
4
+ import type { RunTrigger, StoredValue } from '@markii/runtime';
5
+ import type { BundleManifest } from '@markii/bundle';
6
+ import type { PackModulesMap } from './lua-resolver.js';
7
+ /** A `CacheEntry`-keyed snapshot, structurally — this module never imports `@markii/lua` just for the type; it only ever passes the value through. */
8
+ export type CacheSnapshot = Record<string, unknown>;
9
+ /**
10
+ * A sane upper bound on a persisted cache snapshot: real cached `net.*`
11
+ * responses are small JSON payloads, so the cap exists to guarantee a
12
+ * runaway/adversarial script can never grow `workspaceState` without
13
+ * bound. A snapshot over this size is DROPPED, not truncated — a partial
14
+ * cache would silently look valid while missing entries a script assumed
15
+ * were still there, which is worse than starting the next run with none
16
+ * at all.
17
+ */
18
+ export declare const MAX_CACHE_SNAPSHOT_BYTES = 1000000;
19
+ /** The single `workspaceState`/Memento key a document's cache snapshot lives under. */
20
+ export declare function cacheStorageKeyFor(documentKey: string): string;
21
+ /**
22
+ * The `workspaceState`/Memento key a document's LAST-KNOWN values live under
23
+ * (GitHub issue #11, gap 1). Separate from the cache snapshot
24
+ * (`cacheStorageKeyFor`): the cache is the sandbox's own `cache.get` state,
25
+ * keyed and TTL'd inside Lua; THIS is the rendered value store
26
+ * (`ValueStore.snapshot()`), the numbers a data-bound component actually
27
+ * showed. Persisting it is what lets a reopened monitoring note render its
28
+ * last figures instantly and offline, marked stale, before (or without) any
29
+ * re-run — exactly the "opening a note ... showing its last data marked as
30
+ * stale" behavior docs/scripting.md and docs/spec.md require.
31
+ */
32
+ export declare function valuesStorageKeyFor(documentKey: string): string;
33
+ /** Same cap and drop-not-truncate posture as `MAX_CACHE_SNAPSHOT_BYTES` — a runaway value store never grows `workspaceState` without bound, and a too-large snapshot is dropped whole (a partial value store would silently omit names a note binds). */
34
+ export declare const MAX_VALUES_SNAPSHOT_BYTES = 1000000;
35
+ /**
36
+ * Reads a document's persisted last-known value store, or `{}` when there is
37
+ * none / it is unusable. Never throws, never trusts the stored shape past
38
+ * "plain object" — see `isStoredValueRecord`.
39
+ */
40
+ export declare function readPersistedValues(memento: GrantMemento, documentKey: string): Record<string, StoredValue>;
41
+ /**
42
+ * Merges a completed run's value store onto the previously-persisted one,
43
+ * keeping the LAST-KNOWN-GOOD value for any name whose fresh run FAILED
44
+ * (GitHub issue #11). A scheduled/auto run that hits a transient failure
45
+ * (offline, an API hiccup, an ungranted host under the read-only tier) must
46
+ * not wipe the good numbers a monitoring note was already showing — mirroring
47
+ * the same "the cache only ever remembers good data" guarantee
48
+ * docs/scripting.md gives `cache.get`. For each name in the new run: a
49
+ * non-error outcome always wins; an error outcome is kept ONLY if there is no
50
+ * prior good value to fall back to (so the error surfaces on a note that
51
+ * never succeeded), otherwise the prior value is retained and marked stale.
52
+ * Names present only in the prior store (a script the note no longer has) are
53
+ * dropped — the note's current scripts define the namespace.
54
+ */
55
+ export declare function mergePersistedValues(previous: Record<string, StoredValue>, next: Record<string, StoredValue>): Record<string, StoredValue>;
56
+ /** A plausible cache-snapshot shape read back from storage — never trusts it further than "is this a plain object at all" (an old/foreign/corrupt value degrades to "no cache", matching this whole module's fail-safe posture). */
57
+ export declare function isCacheSnapshotShape(value: unknown): value is CacheSnapshot;
58
+ /**
59
+ * The JSON text for `snapshot`, or `undefined` when it must be dropped
60
+ * instead of persisted: either it isn't serializable at all (a value
61
+ * `JSON.stringify` cannot handle), or its serialized size exceeds
62
+ * `MAX_CACHE_SNAPSHOT_BYTES`. Callers write `undefined` back to storage in
63
+ * that case — see `MAX_CACHE_SNAPSHOT_BYTES`'s doc comment on why a partial
64
+ * write is never attempted.
65
+ */
66
+ export declare function serializeCacheSnapshotIfSmallEnough(snapshot: CacheSnapshot): string | undefined;
67
+ export interface RunOnceOptions {
68
+ /** Stable identity for the note this run belongs to — `vscode.Uri.toString()` of the document, in the real adapter. Shared with `./grant-flow.ts`'s `documentKey`. */
69
+ documentKey: string;
70
+ text: string;
71
+ /**
72
+ * How this run was triggered (GitHub issue #11). `'manual'` (the default
73
+ * when omitted) is the only trigger that runs the INTERACTIVE grant flow
74
+ * (`runGrantFlow`) — prompting on a miss and persisting the answer — and
75
+ * the only one the worker maps to the full `'manual'` capability tier.
76
+ * `'auto'` (run-on-open) and `'scheduled'` (interval) both resolve grants
77
+ * NON-INTERACTIVELY (`resolveStoredGrant`): they reuse a grant the user
78
+ * already made by hand, never prompt, never persist, and the worker runs
79
+ * them at the read-only tier (`@markii/runtime`'s `tierForTrigger`). This
80
+ * is the run-path half of the "effects always cost a click" rule
81
+ * (docs/scripting.md); the tier gate in the worker is the other half.
82
+ */
83
+ trigger?: RunTrigger;
84
+ /**
85
+ * The DNS-rebinding / private-range policy (GitHub issue #10), forwarded
86
+ * verbatim to `spawnRun`'s `netPolicy` — see `./net-pinning.ts`'s
87
+ * `PinPolicy`. Omitted defaults to the fail-closed posture the worker
88
+ * itself applies (`allowRestrictedAddresses: false`); this is the
89
+ * deployment-level opt-in `markii.allowPrivateNetworkAddresses` maps to
90
+ * (`preview-panel.ts`), never something a note can set for itself.
91
+ */
92
+ netPolicy?: SpawnRunRequest['netPolicy'];
93
+ memento: GrantMemento;
94
+ promptHost: PromptHost;
95
+ promptUnknownHosts: PromptUnknownHosts;
96
+ /** PROMPT-STORM guard's consolidated gate — see `./grant-flow.ts`'s `MAX_HOST_PROMPTS`. */
97
+ promptManyHosts: PromptManyHosts;
98
+ /** Injected so this function is testable with a fake worker runner — the real adapter passes `./run-host.ts`'s `spawnRun`. */
99
+ spawnRun: (options: SpawnRunRequest) => Promise<RunResult>;
100
+ /**
101
+ * Called once per script as its value arrives, while the run is still
102
+ * going (GitHub issue #35) — a host uses it to un-stale one bound
103
+ * component at a time instead of waiting for the whole batch. The value
104
+ * handed over has already been through `scrubStoredValueForWire`, so it
105
+ * is safe to post straight to a renderer, exactly like the `values` this
106
+ * function finally returns.
107
+ *
108
+ * Purely a notification. Persistence is NOT done here: the merged value
109
+ * store is written once, at the end (see below), so a run killed
110
+ * half-way can never leave a half-written record behind.
111
+ */
112
+ onValue?: (name: string, value: StoredValue) => void;
113
+ timeoutMs: number;
114
+ /**
115
+ * Slice 2 of the `.mkz` Run-path arc (GitHub issue #9): present only for a
116
+ * bundle-backed document. `buildSnapshot` is called exactly once per run
117
+ * (`./bundle-run.ts`'s `buildBundleSnapshot`, already applied to whatever
118
+ * `BundleStorage` the caller has) — deferred to run time rather than
119
+ * built eagerly, so a snapshot is only ever read when a run actually
120
+ * happens. As of the F-1 fix it is built BEFORE the grant flow rather
121
+ * than immediately before `spawnRun`, so a `src=` script's resolved
122
+ * content (`bundleModulesFromSnapshot`) can be folded into the grant
123
+ * closure `computeGrantKey` hashes, ahead of any prompting — still the
124
+ * same single call per run, just earlier. `persistCacheOut` receives the
125
+ * worker's `RunResult.cacheOut`
126
+ * (its bundle `.cache/` subtree, post-run) when the run produced one, and
127
+ * is the caller's own persistence: writing back into a directory-form
128
+ * bundle on disk, or into extension storage keyed by bundle identity for
129
+ * a read-only zip form (see `preview-panel.ts`'s adapters).
130
+ */
131
+ bundle?: {
132
+ manifest: BundleManifest;
133
+ buildSnapshot: () => Promise<Record<string, Uint8Array>>;
134
+ persistCacheOut: (cacheOut: Record<string, Uint8Array>) => Promise<void>;
135
+ };
136
+ /**
137
+ * Slice 5 of the pack-loading arc (GitHub issue #3): pre-read pack Lua
138
+ * modules for every installed pack (`../packs/pack-scripts.ts`'s
139
+ * `loadPackModules`), forwarded verbatim to `spawnRun`'s `packModules` so
140
+ * the worker's `require "packName/..."` can resolve them. Absent when no
141
+ * packs are configured — `require` for a pack namespace then denies
142
+ * exactly as it did before packs existed.
143
+ */
144
+ packModules?: PackModulesMap;
145
+ }
146
+ export interface RunOnceResult {
147
+ values: Record<string, StoredValue>;
148
+ failures: ValuesFailure[];
149
+ /**
150
+ * The same failures, message included, for the host's DIAGNOSTICS
151
+ * surface only (the "Markii" output channel, a developer console) —
152
+ * never for the rendered page or any per-note UI, which take the
153
+ * scrubbed `failures` above (D-1: a message can be a raw Lua traceback,
154
+ * or a denial that embeds the request URL). AGENTS.md's "clean is not
155
+ * silent": a failure whose reason reaches neither surface is a bug, and
156
+ * `{name, kind}` alone is not a reason a user can act on.
157
+ */
158
+ failureDetails: {
159
+ name: string;
160
+ kind: ValuesFailure['kind'];
161
+ message: string;
162
+ }[];
163
+ /**
164
+ * Consent unification (SECURITY-RELEVANT): lines describing any mismatch
165
+ * between what is DECLARED (a bundle manifest's `permissions.net`, and/or
166
+ * an individual script's own `permissions` fence attribute — batch 7
167
+ * #47) and the hosts this run's scripts actually reach, from
168
+ * `./bundle-run.ts`'s `netDeclarationDiagnostics`. `[]` when there is
169
+ * nothing to compare against (no bundle manifest and no script declares
170
+ * `permissions`) or every declared host agrees with the scan. For the
171
+ * host's diagnostics surface only — never the rendered page — same
172
+ * posture as `failureDetails` above.
173
+ */
174
+ netDeclarationDiagnostics: string[];
175
+ }
176
+ /**
177
+ * Runs one manual pass of `options.text`'s scripts end to end: the grant
178
+ * flow decides the net allowlist (prompting only on a miss), the persisted
179
+ * cache snapshot seeds the run, and the run's own (possibly mutated) cache
180
+ * snapshot is written back — capped, never partially. The returned shape
181
+ * is exactly what the `values` wire message (`../protocol.ts`) needs,
182
+ * minus the `revision` tag, which the caller (who knows what revision this
183
+ * run was actually performed against) attaches. Every `StoredValue`'s raw
184
+ * `error` text is stripped before it is returned (D-1, `scrubValuesForWire`
185
+ * above) — same treatment `failures` already got.
186
+ */
187
+ export declare function runOnce(options: RunOnceOptions): Promise<RunOnceResult>;
@@ -0,0 +1,323 @@
1
+ /**
2
+ * Slice 2's top-level orchestration for one `markii.runScripts` press:
3
+ * extract requirements -> run the grant flow -> read/spawn/persist the
4
+ * cache snapshot -> shape the result for the `values` wire message
5
+ * (`../protocol.ts`). Deliberately vscode-free, like every other module
6
+ * under `./`: the ONLY vscode-shaped things this needs (a Memento, a
7
+ * `spawnRun`-like runner, and two prompt functions) arrive as plain
8
+ * parameters, so `extension.ts`/`preview-panel.ts` — the only files
9
+ * allowed to import `vscode` — reduce to supplying those and posting the
10
+ * resulting message. See `./grant-flow.ts` and `./run-host.ts` for the
11
+ * pieces this file composes.
12
+ */
13
+ import { extractScriptBlocks, scanRunRequirementHosts, scriptDeclaredHosts, } from './script-requirements.js';
14
+ import { resolveStoredGrant, runGrantFlow } from './grant-flow.js';
15
+ import { bundleModulesFromSnapshot, manifestBundleFsGrants, manifestNetHosts, netDeclarationDiagnostics, } from './bundle-run.js';
16
+ /**
17
+ * Strips a `StoredValue`'s `error` field before it ever reaches the wire
18
+ * (D-1): `.error` is whatever text the executor happened to produce —
19
+ * a raw Lua traceback, or (worse, per B-3) a net-provider denial message
20
+ * that can embed the actual request URL/host — and none of that is meant
21
+ * for the rendered page (AGENTS.md's cleanliness principle: quiet markers,
22
+ * never error dumps). This mirrors exactly what `runOnce` below already
23
+ * does to `RunResult.failures` (reduced to `{name, kind}`, never the raw
24
+ * message): `failureKind` is kept, so a renderer (`@markii/react`'s
25
+ * `failure-presentation.ts`) still branches on the closed `FailureKind`
26
+ * taxonomy — nothing in this reference renderer ever reads `.error` for
27
+ * display, only for a value that DIDN'T fail (`status !== 'error'`), where
28
+ * `error` is absent anyway.
29
+ */
30
+ function scrubStoredValueForWire(value) {
31
+ if (value.status !== 'error' || value.error === undefined)
32
+ return value;
33
+ const { error: _error, ...rest } = value;
34
+ return rest;
35
+ }
36
+ function scrubValuesForWire(values) {
37
+ // `Object.fromEntries` for the same reason `mergePersistedValues` uses it:
38
+ // a plain `scrubbed[name] = ...` silently loses the value of a script
39
+ // legitimately named `__proto__`.
40
+ return Object.fromEntries(Object.entries(values).map(([name, value]) => [
41
+ name,
42
+ scrubStoredValueForWire(value),
43
+ ]));
44
+ }
45
+ /**
46
+ * A sane upper bound on a persisted cache snapshot: real cached `net.*`
47
+ * responses are small JSON payloads, so the cap exists to guarantee a
48
+ * runaway/adversarial script can never grow `workspaceState` without
49
+ * bound. A snapshot over this size is DROPPED, not truncated — a partial
50
+ * cache would silently look valid while missing entries a script assumed
51
+ * were still there, which is worse than starting the next run with none
52
+ * at all.
53
+ */
54
+ export const MAX_CACHE_SNAPSHOT_BYTES = 1_000_000;
55
+ /** The single `workspaceState`/Memento key a document's cache snapshot lives under. */
56
+ export function cacheStorageKeyFor(documentKey) {
57
+ return `markii.runCache:${documentKey}`;
58
+ }
59
+ /**
60
+ * The `workspaceState`/Memento key a document's LAST-KNOWN values live under
61
+ * (GitHub issue #11, gap 1). Separate from the cache snapshot
62
+ * (`cacheStorageKeyFor`): the cache is the sandbox's own `cache.get` state,
63
+ * keyed and TTL'd inside Lua; THIS is the rendered value store
64
+ * (`ValueStore.snapshot()`), the numbers a data-bound component actually
65
+ * showed. Persisting it is what lets a reopened monitoring note render its
66
+ * last figures instantly and offline, marked stale, before (or without) any
67
+ * re-run — exactly the "opening a note ... showing its last data marked as
68
+ * stale" behavior docs/scripting.md and docs/spec.md require.
69
+ */
70
+ export function valuesStorageKeyFor(documentKey) {
71
+ return `markii.runValues:${documentKey}`;
72
+ }
73
+ /** Same cap and drop-not-truncate posture as `MAX_CACHE_SNAPSHOT_BYTES` — a runaway value store never grows `workspaceState` without bound, and a too-large snapshot is dropped whole (a partial value store would silently omit names a note binds). */
74
+ export const MAX_VALUES_SNAPSHOT_BYTES = 1_000_000;
75
+ /**
76
+ * True for a value this module is willing to persist and re-hydrate as a
77
+ * last-known value store — the same "is this a plain object at all"
78
+ * shallow trust `isCacheSnapshotShape` applies. A renderer-side hostile-shape
79
+ * guard (`../protocol.ts`'s `isWireStoredValueRecord`) still re-validates
80
+ * every entry when the host re-posts these over the wire, so this only needs
81
+ * to keep a corrupt/foreign top-level value from being merged.
82
+ */
83
+ function isStoredValueRecord(value) {
84
+ return typeof value === 'object' && value !== null && !Array.isArray(value);
85
+ }
86
+ /**
87
+ * Reads a document's persisted last-known value store, or `{}` when there is
88
+ * none / it is unusable. Never throws, never trusts the stored shape past
89
+ * "plain object" — see `isStoredValueRecord`.
90
+ */
91
+ export function readPersistedValues(memento, documentKey) {
92
+ const raw = memento.get(valuesStorageKeyFor(documentKey));
93
+ return isStoredValueRecord(raw) ? raw : {};
94
+ }
95
+ /**
96
+ * Merges a completed run's value store onto the previously-persisted one,
97
+ * keeping the LAST-KNOWN-GOOD value for any name whose fresh run FAILED
98
+ * (GitHub issue #11). A scheduled/auto run that hits a transient failure
99
+ * (offline, an API hiccup, an ungranted host under the read-only tier) must
100
+ * not wipe the good numbers a monitoring note was already showing — mirroring
101
+ * the same "the cache only ever remembers good data" guarantee
102
+ * docs/scripting.md gives `cache.get`. For each name in the new run: a
103
+ * non-error outcome always wins; an error outcome is kept ONLY if there is no
104
+ * prior good value to fall back to (so the error surfaces on a note that
105
+ * never succeeded), otherwise the prior value is retained and marked stale.
106
+ * Names present only in the prior store (a script the note no longer has) are
107
+ * dropped — the note's current scripts define the namespace.
108
+ */
109
+ export function mergePersistedValues(previous, next) {
110
+ // Built through entries and `Object.fromEntries` rather than
111
+ // `merged[name] = ...`: assigning the name `__proto__` on a plain object
112
+ // sets its prototype instead of creating an own property, silently losing
113
+ // that value, and `__proto__` is a legal script name (docs/spec.md's
114
+ // `[A-Za-z_][A-Za-z0-9_-]*`). The same guard is in `./stale-values.ts`.
115
+ const merged = [];
116
+ for (const [name, value] of Object.entries(next)) {
117
+ if (typeof value !== 'object' || value === null)
118
+ continue;
119
+ if (value.status !== 'error') {
120
+ merged.push([name, value]);
121
+ continue;
122
+ }
123
+ // `previous` is a PERSISTED store, guarded only down to "plain object"
124
+ // when it was read back, so an individual entry can be anything.
125
+ const priorRaw = Object.hasOwn(previous, name) ? previous[name] : undefined;
126
+ const prior = typeof priorRaw === 'object' && priorRaw !== null ? priorRaw : undefined;
127
+ merged.push([
128
+ name,
129
+ prior && prior.status !== 'error' ? { ...prior, status: 'stale' } : value,
130
+ ]);
131
+ }
132
+ return Object.fromEntries(merged);
133
+ }
134
+ /** The JSON-and-size-guarded form of a value store to persist, or `undefined` to drop it (unserializable, or over `MAX_VALUES_SNAPSHOT_BYTES`) — same contract as `serializeCacheSnapshotIfSmallEnough`. */
135
+ function valuesSnapshotIsSmallEnough(values) {
136
+ let serialized;
137
+ try {
138
+ serialized = JSON.stringify(values);
139
+ }
140
+ catch {
141
+ return false;
142
+ }
143
+ return serialized.length <= MAX_VALUES_SNAPSHOT_BYTES;
144
+ }
145
+ /** A plausible cache-snapshot shape read back from storage — never trusts it further than "is this a plain object at all" (an old/foreign/corrupt value degrades to "no cache", matching this whole module's fail-safe posture). */
146
+ export function isCacheSnapshotShape(value) {
147
+ return typeof value === 'object' && value !== null && !Array.isArray(value);
148
+ }
149
+ /**
150
+ * The JSON text for `snapshot`, or `undefined` when it must be dropped
151
+ * instead of persisted: either it isn't serializable at all (a value
152
+ * `JSON.stringify` cannot handle), or its serialized size exceeds
153
+ * `MAX_CACHE_SNAPSHOT_BYTES`. Callers write `undefined` back to storage in
154
+ * that case — see `MAX_CACHE_SNAPSHOT_BYTES`'s doc comment on why a partial
155
+ * write is never attempted.
156
+ */
157
+ export function serializeCacheSnapshotIfSmallEnough(snapshot) {
158
+ let serialized;
159
+ try {
160
+ serialized = JSON.stringify(snapshot);
161
+ }
162
+ catch {
163
+ return undefined;
164
+ }
165
+ return serialized.length <= MAX_CACHE_SNAPSHOT_BYTES ? serialized : undefined;
166
+ }
167
+ /**
168
+ * Runs one manual pass of `options.text`'s scripts end to end: the grant
169
+ * flow decides the net allowlist (prompting only on a miss), the persisted
170
+ * cache snapshot seeds the run, and the run's own (possibly mutated) cache
171
+ * snapshot is written back — capped, never partially. The returned shape
172
+ * is exactly what the `values` wire message (`../protocol.ts`) needs,
173
+ * minus the `revision` tag, which the caller (who knows what revision this
174
+ * run was actually performed against) attaches. Every `StoredValue`'s raw
175
+ * `error` text is stripped before it is returned (D-1, `scrubValuesForWire`
176
+ * above) — same treatment `failures` already got.
177
+ */
178
+ export async function runOnce(options) {
179
+ const { scripts, grantScripts } = extractScriptBlocks(options.text);
180
+ // F-1 fix, still true under the consent-unification change below: the
181
+ // bundle snapshot is built BEFORE the host scan so a `src=` script's
182
+ // resolved content can both (a) be folded into the grant closure
183
+ // `computeGrantKey` hashes (`bundleModulesFromSnapshot`'s doc comment)
184
+ // and (b) let the scan resolve that block's `net.*` hosts instead of
185
+ // folding it into `hasUnknownHosts` — see `./bundle-run.ts`'s "Consent
186
+ // unification" section. Built exactly once per run either way.
187
+ const bundleSnapshot = options.bundle
188
+ ? await options.bundle.buildSnapshot()
189
+ : undefined;
190
+ const bundleModules = options.bundle && bundleSnapshot
191
+ ? bundleModulesFromSnapshot(grantScripts, bundleSnapshot)
192
+ : undefined;
193
+ // SECURITY-RELEVANT (consent unification): the static scan of this run's
194
+ // executable closure is the ONLY source of the hostnames prompted for —
195
+ // for a bundle too. A `src=` block resolves through `bundleModules` when
196
+ // its file is in the snapshot; otherwise it folds into
197
+ // `hasUnknownHosts`, exactly like a plain `.mk.md` document's `src=`
198
+ // reference always has. The manifest's `permissions.net` is never
199
+ // consulted here — see below for how it's compared instead.
200
+ const { hosts: scannedHosts, hasUnknownHosts } = scanRunRequirementHosts(scripts, bundleModules
201
+ ? (block) => block.src !== undefined ? bundleModules[block.src] : undefined
202
+ : undefined);
203
+ // The manifest's declared `permissions.net` (for a bundle) and each
204
+ // script's own `permissions` fence attribute are BOTH declared intent
205
+ // only: neither widens or narrows `scannedHosts` above. A mismatch
206
+ // between what is declared (either source) and what is scanned is
207
+ // surfaced as diagnostics lines instead (host's diagnostics surface
208
+ // only, never the rendered page) — see `netDeclarationDiagnostics`'s
209
+ // doc comment. The two declared sets are merged into one comparison:
210
+ // a host a script declares but a bundle manifest does not (or vice
211
+ // versa) is not itself a mismatch worth its own line, only a mismatch
212
+ // against what actually runs.
213
+ const declaredNetHosts = [
214
+ ...(options.bundle ? manifestNetHosts(options.bundle.manifest) : []),
215
+ ...scriptDeclaredHosts(scripts),
216
+ ];
217
+ const netDiagnostics = netDeclarationDiagnostics(declaredNetHosts, scannedHosts);
218
+ // The `bundle` capability needs no user-facing prompt any more (consent
219
+ // unification): the manifest's declared bundle-fs grants are passed
220
+ // straight through as this run's granted permissions.
221
+ // `@markii/bundle`'s `createScriptView` still intersects this against
222
+ // the manifest's own declaration, the path-jail still confines every
223
+ // read/write, and the read-only tier for auto/scheduled triggers still
224
+ // blocks `bundle.write` outright — only the prompt is gone.
225
+ const grantedBundlePermissions = options.bundle
226
+ ? manifestBundleFsGrants(options.bundle.manifest)
227
+ : [];
228
+ const trigger = options.trigger ?? 'manual';
229
+ const grantRequirements = {
230
+ scripts,
231
+ grantScripts,
232
+ hosts: scannedHosts,
233
+ hasUnknownHosts,
234
+ ...(bundleModules ? { bundleModules } : {}),
235
+ };
236
+ // GitHub issue #11: only a MANUAL run prompts and persists. An auto/
237
+ // scheduled run resolves grants non-interactively from what the user
238
+ // already granted by hand for this exact closure — never a modal on a
239
+ // timer, never a widened allowlist. See `resolveStoredGrant`'s doc comment.
240
+ const grant = trigger === 'manual'
241
+ ? await runGrantFlow({
242
+ documentKey: options.documentKey,
243
+ requirements: grantRequirements,
244
+ memento: options.memento,
245
+ promptHost: options.promptHost,
246
+ declaredHosts: scriptDeclaredHosts(scripts),
247
+ promptUnknownHosts: options.promptUnknownHosts,
248
+ promptManyHosts: options.promptManyHosts,
249
+ })
250
+ : await resolveStoredGrant({
251
+ documentKey: options.documentKey,
252
+ requirements: grantRequirements,
253
+ memento: options.memento,
254
+ });
255
+ const cacheKey = cacheStorageKeyFor(options.documentKey);
256
+ const rawCache = options.memento.get(cacheKey);
257
+ const cacheSnapshot = isCacheSnapshotShape(rawCache) ? rawCache : {};
258
+ const result = await options.spawnRun({
259
+ text: options.text,
260
+ trigger,
261
+ // GitHub issue #35: forwarded per script, scrubbed the same way the
262
+ // final `values` are (D-1) so a raw executor message can never reach a
263
+ // renderer through the faster path either.
264
+ ...(options.onValue
265
+ ? {
266
+ onValue: (name, value) => {
267
+ options.onValue?.(name, scrubStoredValueForWire(value));
268
+ },
269
+ }
270
+ : {}),
271
+ netAllowlist: grant.allowedHosts,
272
+ ...(options.netPolicy !== undefined
273
+ ? { netPolicy: options.netPolicy }
274
+ : {}),
275
+ cacheSnapshot: cacheSnapshot,
276
+ timeoutMs: options.timeoutMs,
277
+ ...(options.packModules !== undefined
278
+ ? { packModules: options.packModules }
279
+ : {}),
280
+ ...(options.bundle && bundleSnapshot
281
+ ? {
282
+ bundle: {
283
+ snapshot: bundleSnapshot,
284
+ manifest: options.bundle.manifest,
285
+ grantedBundlePermissions,
286
+ },
287
+ }
288
+ : {}),
289
+ });
290
+ const nextSnapshotText = serializeCacheSnapshotIfSmallEnough(result.cacheSnapshot);
291
+ await options.memento.update(cacheKey, nextSnapshotText === undefined ? undefined : result.cacheSnapshot);
292
+ if (options.bundle && result.cacheOut) {
293
+ await options.bundle.persistCacheOut(result.cacheOut);
294
+ }
295
+ // GitHub issue #11, gap 1: persist the (wire-scrubbed) value store as this
296
+ // note's last-known values, merged onto the prior store so a failed
297
+ // scheduled/auto run never wipes good numbers (`mergePersistedValues`).
298
+ // Dropped whole, never partially, if it fails the size/serialize guard.
299
+ //
300
+ // GitHub issue #35 deliberately did NOT move this write per value. One
301
+ // write at the end is the only way a killed run cannot leave a partial
302
+ // record, and there is nothing to gain from the earlier writes: a run
303
+ // that dies half-way still arrives here, because `spawnRun` carries the
304
+ // values that already landed into its synthetic failure result, so the
305
+ // scripts that finished are persisted either way.
306
+ const scrubbedValues = scrubValuesForWire(result.values);
307
+ const valuesKey = valuesStorageKeyFor(options.documentKey);
308
+ const mergedValues = mergePersistedValues(readPersistedValues(options.memento, options.documentKey), scrubbedValues);
309
+ await options.memento.update(valuesKey, valuesSnapshotIsSmallEnough(mergedValues) ? mergedValues : undefined);
310
+ return {
311
+ values: scrubbedValues,
312
+ failures: result.failures.map((failure) => ({
313
+ name: failure.name,
314
+ kind: failure.kind,
315
+ })),
316
+ failureDetails: result.failures.map((failure) => ({
317
+ name: failure.name,
318
+ kind: failure.kind,
319
+ message: failure.message,
320
+ })),
321
+ netDeclarationDiagnostics: netDiagnostics,
322
+ };
323
+ }