@markii/host 0.0.0-stage → 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 (193) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +19 -2
  3. package/dist/browser-worker.js +639 -0
  4. package/dist/browser.d.ts +57 -0
  5. package/dist/browser.js +167 -0
  6. package/dist/conformance/host/01-plain-open/expected.json +5 -0
  7. package/dist/conformance/host/01-plain-open/note.mk.md +8 -0
  8. package/dist/conformance/host/01-plain-open/scenario.json +6 -0
  9. package/dist/conformance/host/02-run-manual-grant-accepted/expected.json +16 -0
  10. package/dist/conformance/host/02-run-manual-grant-accepted/note.mk.md +5 -0
  11. package/dist/conformance/host/02-run-manual-grant-accepted/scenario.json +14 -0
  12. package/dist/conformance/host/03-run-manual-grant-refused/expected.json +18 -0
  13. package/dist/conformance/host/03-run-manual-grant-refused/note.mk.md +5 -0
  14. package/dist/conformance/host/03-run-manual-grant-refused/scenario.json +14 -0
  15. package/dist/conformance/host/04-run-auto-write-denied/expected.json +16 -0
  16. package/dist/conformance/host/04-run-auto-write-denied/note.mk.md +10 -0
  17. package/dist/conformance/host/04-run-auto-write-denied/scenario.json +13 -0
  18. package/dist/conformance/host/05-export-html-pack-directive/expected.json +14 -0
  19. package/dist/conformance/host/05-export-html-pack-directive/note.mk.md +8 -0
  20. package/dist/conformance/host/05-export-html-pack-directive/scenario.json +6 -0
  21. package/dist/conformance/host/06-install-pack-then-render/expected.json +18 -0
  22. package/dist/conformance/host/06-install-pack-then-render/files/ana-1.0.0/pack.json +13 -0
  23. package/dist/conformance/host/06-install-pack-then-render/files/ana-1.0.0/webview.js +1 -0
  24. package/dist/conformance/host/06-install-pack-then-render/note.mk.md +5 -0
  25. package/dist/conformance/host/06-install-pack-then-render/scenario.json +15 -0
  26. package/dist/conformance/host/07-complete-in-directive/expected.json +14 -0
  27. package/dist/conformance/host/07-complete-in-directive/note.mk.md +3 -0
  28. package/dist/conformance/host/07-complete-in-directive/scenario.json +6 -0
  29. package/dist/conformance/host/08-hover-standard-component/expected.json +5 -0
  30. package/dist/conformance/host/08-hover-standard-component/note.mk.md +5 -0
  31. package/dist/conformance/host/08-hover-standard-component/scenario.json +6 -0
  32. package/dist/conformance/host/09-insert-skeleton/expected.json +5 -0
  33. package/dist/conformance/host/09-insert-skeleton/note.mk.md +4 -0
  34. package/dist/conformance/host/09-insert-skeleton/scenario.json +6 -0
  35. package/dist/conformance/host/10-scripts-disabled/expected.json +11 -0
  36. package/dist/conformance/host/10-scripts-disabled/note.mk.md +5 -0
  37. package/dist/conformance/host/10-scripts-disabled/scenario.json +9 -0
  38. package/dist/conformance/host/11-complete-closing-fence/expected.json +5 -0
  39. package/dist/conformance/host/11-complete-closing-fence/note.mk.md +5 -0
  40. package/dist/conformance/host/11-complete-closing-fence/scenario.json +12 -0
  41. package/dist/conformance/host/12-complete-nested-container/expected.json +5 -0
  42. package/dist/conformance/host/12-complete-nested-container/note.mk.md +5 -0
  43. package/dist/conformance/host/12-complete-nested-container/scenario.json +12 -0
  44. package/dist/conformance/host/13-install-pack-declined/expected.json +12 -0
  45. package/dist/conformance/host/13-install-pack-declined/files/ana-1.0.0/pack.json +13 -0
  46. package/dist/conformance/host/13-install-pack-declined/files/ana-1.0.0/webview.js +1 -0
  47. package/dist/conformance/host/13-install-pack-declined/note.mk.md +5 -0
  48. package/dist/conformance/host/13-install-pack-declined/scenario.json +10 -0
  49. package/dist/conformance/host/14-remove-pack/expected.json +14 -0
  50. package/dist/conformance/host/14-remove-pack/files/ana-1.0.0/pack.json +13 -0
  51. package/dist/conformance/host/14-remove-pack/files/ana-1.0.0/webview.js +1 -0
  52. package/dist/conformance/host/14-remove-pack/note.mk.md +5 -0
  53. package/dist/conformance/host/14-remove-pack/scenario.json +11 -0
  54. package/dist/conformance/host/15-load-packs-engine-views/expected.json +19 -0
  55. package/dist/conformance/host/15-load-packs-engine-views/files/duo-1.0.0/pack.json +9 -0
  56. package/dist/conformance/host/15-load-packs-engine-views/files/duo-1.0.0/webview.js +1 -0
  57. package/dist/conformance/host/15-load-packs-engine-views/note.mk.md +9 -0
  58. package/dist/conformance/host/15-load-packs-engine-views/scenario.json +10 -0
  59. package/dist/conformance/host/16-pack-trust/expected.json +37 -0
  60. package/dist/conformance/host/16-pack-trust/files/ana-1.0.0/pack.json +12 -0
  61. package/dist/conformance/host/16-pack-trust/files/ana-1.0.0/webview.js +1 -0
  62. package/dist/conformance/host/16-pack-trust/files/sneaky-1.0.0/pack.json +12 -0
  63. package/dist/conformance/host/16-pack-trust/files/sneaky-1.0.0/webview.js +1 -0
  64. package/dist/conformance/host/16-pack-trust/note.mk.md +5 -0
  65. package/dist/conformance/host/16-pack-trust/scenario.json +26 -0
  66. package/dist/conformance/host/README.md +221 -0
  67. package/dist/conformance.d.ts +2 -0
  68. package/dist/conformance.js +5 -0
  69. package/dist/diagnostics/render-diagnostics.d.ts +41 -0
  70. package/dist/diagnostics/render-diagnostics.js +46 -0
  71. package/dist/export/cascade.d.ts +98 -0
  72. package/dist/export/cascade.js +159 -0
  73. package/dist/export/export-zip.d.ts +19 -0
  74. package/dist/export/export-zip.js +38 -0
  75. package/dist/export/image-embed.d.ts +126 -0
  76. package/dist/export/image-embed.js +274 -0
  77. package/dist/export/note-export.d.ts +332 -0
  78. package/dist/export/note-export.js +366 -0
  79. package/dist/export/note-links.d.ts +83 -0
  80. package/dist/export/note-links.js +241 -0
  81. package/dist/glue.wasm +0 -0
  82. package/dist/host/adapter.d.ts +223 -0
  83. package/dist/host/adapter.js +9 -0
  84. package/dist/host/cascade-export-behavior.d.ts +120 -0
  85. package/dist/host/cascade-export-behavior.js +151 -0
  86. package/dist/host/conformance-runner.d.ts +156 -0
  87. package/dist/host/conformance-runner.js +634 -0
  88. package/dist/host/create-host.d.ts +253 -0
  89. package/dist/host/create-host.js +329 -0
  90. package/dist/host/editor-behavior.d.ts +157 -0
  91. package/dist/host/editor-behavior.js +227 -0
  92. package/dist/host/export/md-plain.d.ts +15 -0
  93. package/dist/host/export/md-plain.js +125 -0
  94. package/dist/host/export-behavior.d.ts +113 -0
  95. package/dist/host/export-behavior.js +257 -0
  96. package/dist/host/labels.d.ts +24 -0
  97. package/dist/host/labels.js +32 -0
  98. package/dist/host/pack-archive.d.ts +30 -0
  99. package/dist/host/pack-archive.js +68 -0
  100. package/dist/host/pack-install.d.ts +88 -0
  101. package/dist/host/pack-install.js +189 -0
  102. package/dist/host/pack-load.d.ts +72 -0
  103. package/dist/host/pack-load.js +57 -0
  104. package/dist/host/pack-remove.d.ts +21 -0
  105. package/dist/host/pack-remove.js +65 -0
  106. package/dist/host/pack-trust-behavior.d.ts +79 -0
  107. package/dist/host/pack-trust-behavior.js +262 -0
  108. package/dist/host/refresh-interval.d.ts +39 -0
  109. package/dist/host/refresh-interval.js +61 -0
  110. package/dist/host/run-behavior.d.ts +89 -0
  111. package/dist/host/run-behavior.js +168 -0
  112. package/dist/host/run-diagnostics.d.ts +26 -0
  113. package/dist/host/run-diagnostics.js +43 -0
  114. package/dist/host/script-execution.d.ts +72 -0
  115. package/dist/host/script-execution.js +68 -0
  116. package/dist/host/terminal-packs.d.ts +39 -0
  117. package/dist/host/terminal-packs.js +51 -0
  118. package/dist/html-engine.d.ts +2 -0
  119. package/dist/html-engine.js +29 -0
  120. package/dist/index.d.ts +95 -0
  121. package/dist/index.js +70 -0
  122. package/dist/insert/component-catalog.d.ts +23 -0
  123. package/dist/insert/component-catalog.js +96 -0
  124. package/dist/node-fs.d.ts +14 -0
  125. package/dist/node-fs.js +70 -0
  126. package/dist/node.d.ts +5 -0
  127. package/dist/node.js +13 -0
  128. package/dist/package.json +3 -0
  129. package/dist/packs/discover.d.ts +119 -0
  130. package/dist/packs/discover.js +266 -0
  131. package/dist/packs/pack-diagnostics.d.ts +81 -0
  132. package/dist/packs/pack-diagnostics.js +105 -0
  133. package/dist/packs/pack-paths.d.ts +53 -0
  134. package/dist/packs/pack-paths.js +106 -0
  135. package/dist/packs/pack-render-registry.d.ts +83 -0
  136. package/dist/packs/pack-render-registry.js +182 -0
  137. package/dist/packs/pack-scripts.d.ts +20 -0
  138. package/dist/packs/pack-scripts.js +96 -0
  139. package/dist/packs/pack-trust.d.ts +57 -0
  140. package/dist/packs/pack-trust.js +111 -0
  141. package/dist/packs/prebuilt.d.ts +49 -0
  142. package/dist/packs/prebuilt.js +97 -0
  143. package/dist/path-util.d.ts +24 -0
  144. package/dist/path-util.js +82 -0
  145. package/dist/run/browser-isolate.d.ts +45 -0
  146. package/dist/run/browser-isolate.js +81 -0
  147. package/dist/run/bundle-document.d.ts +29 -0
  148. package/dist/run/bundle-document.js +40 -0
  149. package/dist/run/bundle-run.d.ts +188 -0
  150. package/dist/run/bundle-run.js +232 -0
  151. package/dist/run/grant-flow.d.ts +232 -0
  152. package/dist/run/grant-flow.js +390 -0
  153. package/dist/run/ip-address.d.ts +41 -0
  154. package/dist/run/ip-address.js +255 -0
  155. package/dist/run/isolate.d.ts +57 -0
  156. package/dist/run/isolate.js +22 -0
  157. package/dist/run/lua-resolver.d.ts +21 -0
  158. package/dist/run/lua-resolver.js +63 -0
  159. package/dist/run/net-bridge-worker.d.ts +26 -0
  160. package/dist/run/net-bridge-worker.js +66 -0
  161. package/dist/run/net-bridge.d.ts +56 -0
  162. package/dist/run/net-bridge.js +59 -0
  163. package/dist/run/net-pinning.d.ts +82 -0
  164. package/dist/run/net-pinning.js +137 -0
  165. package/dist/run/net-provider.d.ts +70 -0
  166. package/dist/run/net-provider.js +289 -0
  167. package/dist/run/node-isolate.d.ts +30 -0
  168. package/dist/run/node-isolate.js +117 -0
  169. package/dist/run/run-flow.d.ts +187 -0
  170. package/dist/run/run-flow.js +323 -0
  171. package/dist/run/run-host.d.ts +107 -0
  172. package/dist/run/run-host.js +241 -0
  173. package/dist/run/run-job.d.ts +130 -0
  174. package/dist/run/run-job.js +279 -0
  175. package/dist/run/run-progress.d.ts +55 -0
  176. package/dist/run/run-progress.js +42 -0
  177. package/dist/run/run-trace.d.ts +44 -0
  178. package/dist/run/run-trace.js +36 -0
  179. package/dist/run/script-requirements.d.ts +98 -0
  180. package/dist/run/script-requirements.js +212 -0
  181. package/dist/run/snapshot-storage.d.ts +25 -0
  182. package/dist/run/snapshot-storage.js +31 -0
  183. package/dist/run/stale-values.d.ts +25 -0
  184. package/dist/run/stale-values.js +32 -0
  185. package/dist/run/worker-entry-browser.d.ts +42 -0
  186. package/dist/run/worker-entry-browser.js +77 -0
  187. package/dist/run/worker-entry.d.ts +1 -0
  188. package/dist/run/worker-entry.js +96 -0
  189. package/dist/values-failure.d.ts +17 -0
  190. package/dist/values-failure.js +1 -0
  191. package/dist/values-merge.d.ts +39 -0
  192. package/dist/values-merge.js +33 -0
  193. package/package.json +84 -4
@@ -0,0 +1,57 @@
1
+ import type { NetProvider } from '@markii/lua';
2
+ import type { HostIsolate } from './host/adapter.js';
3
+ /** Builds the provider for one run from the job's host allowlist, byte cap and pin policy (the policy is meaningless in a browser and ignored). */
4
+ export type BrowserNetProviderFactory = (netAllowlist: string[], maxFetchBytes: number, netPolicy: unknown) => NetProvider;
5
+ export interface FetchNetProviderOptions {
6
+ /**
7
+ * The origins (`https://api.example.com`, `http://127.0.0.1:8080`) a
8
+ * script may reach. Anything else is denied. Missing or empty denies
9
+ * every request. Entries that do not parse as an http(s) URL are ignored.
10
+ */
11
+ allowedOrigins?: readonly string[];
12
+ /** How a policy refusal is built. Defaults to a plain `Error`; the isolate bridge marks every refusal as a denial on the wire either way. */
13
+ denial?: (message: string) => Error;
14
+ /** The `fetch` to use. Defaults to the global one; injectable for tests. */
15
+ fetch?: typeof fetch;
16
+ }
17
+ /**
18
+ * The page-side half of the network bridge for a browser: a request goes
19
+ * out with `fetch` only when its origin is in `allowedOrigins`. Everything
20
+ * else is refused with a denial before any request is made.
21
+ *
22
+ * The grant flow and the tier gate run in front of this (they are shared
23
+ * with every host), so a script still needs the user's grant for the host
24
+ * it names. This allowlist is the second, host-owned boundary.
25
+ *
26
+ * What a browser cannot do, stated plainly: there is NO address pinning
27
+ * here. A page cannot choose which IP a hostname connects to, so the
28
+ * DNS-rebinding and private-range protection the Node provider adds
29
+ * (docs/security.md) does not exist in this provider. The browser's own
30
+ * rules apply instead: CORS decides whether a response is readable, and
31
+ * the same-origin policy stops credentials leaving (requests are sent with
32
+ * `credentials: 'omit'`). Redirects are not followed: the browser hides a
33
+ * manual redirect's target, so it cannot be checked against the allowlist,
34
+ * and a redirect is therefore a denial.
35
+ *
36
+ * Returns a factory because the byte cap arrives per job.
37
+ */
38
+ export declare function createFetchNetProvider(options?: FetchNetProviderOptions): BrowserNetProviderFactory;
39
+ export interface BrowserHostIsolateOptions {
40
+ /** Where the module worker lives. Defaults to `browser-worker.js` beside this file. A host that embeds the bytes (a blob URL) passes its own. */
41
+ workerUrl?: string | URL;
42
+ /** Where wasmoon's `glue.wasm` lives. Defaults to `glue.wasm` beside this file. Sent on every job, so the Lua engine never falls back to a CDN. */
43
+ wasmUrl?: string | URL;
44
+ /** How the worker script is loaded. Defaults to `module`, which the packaged `browser-worker.js` is. A host that embeds a classic (IIFE) bundle behind a blob URL passes `classic`. */
45
+ workerType?: 'module' | 'classic';
46
+ /** Serves `net.*` for each run. Defaults to a provider with an empty allowlist, which denies every request. */
47
+ netProvider?: BrowserNetProviderFactory;
48
+ }
49
+ /**
50
+ * A `HostIsolate` of kind `browser`: each run starts a MODULE Web Worker
51
+ * and the job carries the `glue.wasm` URL. A Web Worker takes no heap cap,
52
+ * so a runaway allocation inside it is bounded only by the browser; Lua's
53
+ * own limits and the external watchdog still apply (docs/security.md).
54
+ */
55
+ export declare function createBrowserHostIsolate(options?: BrowserHostIsolateOptions): Extract<HostIsolate, {
56
+ kind: 'browser';
57
+ }>;
@@ -0,0 +1,167 @@
1
+ // @markii/host/browser: the browser PLATFORM entry, the twin of
2
+ // `@markii/host/node`.
3
+ //
4
+ // The main entry (`./index.ts`) is environment-neutral. This entry adds the
5
+ // two things a page needs to run scripts:
6
+ // - a Web Worker isolate that starts the packaged module worker and hands it
7
+ // the packaged `glue.wasm` URL on every job;
8
+ // - a `fetch`-backed network provider that serves `net.*` calls on the
9
+ // worker's behalf, against an explicit allowlist of ORIGINS.
10
+ //
11
+ // Nothing reachable from here imports `node:*` (executed probe:
12
+ // `apps/vscode/src/browser-entry.probe.test.ts`). In the published package
13
+ // `browser-worker.js` and `glue.wasm` sit next to `browser.js`, so the
14
+ // `new URL(..., import.meta.url)` references below resolve in a consumer's
15
+ // bundler without configuration where that bundler treats them as assets.
16
+ import { createBrowserIsolate } from './run/browser-isolate.js';
17
+ function originOf(entry) {
18
+ try {
19
+ const url = new URL(entry);
20
+ if (url.protocol !== 'http:' && url.protocol !== 'https:')
21
+ return undefined;
22
+ return url.origin;
23
+ }
24
+ catch {
25
+ return undefined;
26
+ }
27
+ }
28
+ async function readCapped(response, maxBytes, denial) {
29
+ const declared = Number(response.headers.get('content-length'));
30
+ if (Number.isFinite(declared) && declared > maxBytes) {
31
+ void response.body?.cancel();
32
+ throw denial(`response declares ${declared} bytes, exceeding the ${maxBytes}-byte cap`);
33
+ }
34
+ if (!response.body)
35
+ return '';
36
+ const reader = response.body.getReader();
37
+ const chunks = [];
38
+ let total = 0;
39
+ for (;;) {
40
+ const { done, value } = await reader.read();
41
+ if (done)
42
+ break;
43
+ total += value.byteLength;
44
+ if (total > maxBytes) {
45
+ void reader.cancel();
46
+ throw denial(`response exceeds the ${maxBytes}-byte cap`);
47
+ }
48
+ chunks.push(value);
49
+ }
50
+ const bytes = new Uint8Array(total);
51
+ let offset = 0;
52
+ for (const chunk of chunks) {
53
+ bytes.set(chunk, offset);
54
+ offset += chunk.byteLength;
55
+ }
56
+ return new TextDecoder().decode(bytes);
57
+ }
58
+ /**
59
+ * The page-side half of the network bridge for a browser: a request goes
60
+ * out with `fetch` only when its origin is in `allowedOrigins`. Everything
61
+ * else is refused with a denial before any request is made.
62
+ *
63
+ * The grant flow and the tier gate run in front of this (they are shared
64
+ * with every host), so a script still needs the user's grant for the host
65
+ * it names. This allowlist is the second, host-owned boundary.
66
+ *
67
+ * What a browser cannot do, stated plainly: there is NO address pinning
68
+ * here. A page cannot choose which IP a hostname connects to, so the
69
+ * DNS-rebinding and private-range protection the Node provider adds
70
+ * (docs/security.md) does not exist in this provider. The browser's own
71
+ * rules apply instead: CORS decides whether a response is readable, and
72
+ * the same-origin policy stops credentials leaving (requests are sent with
73
+ * `credentials: 'omit'`). Redirects are not followed: the browser hides a
74
+ * manual redirect's target, so it cannot be checked against the allowlist,
75
+ * and a redirect is therefore a denial.
76
+ *
77
+ * Returns a factory because the byte cap arrives per job.
78
+ */
79
+ export function createFetchNetProvider(options = {}) {
80
+ const allowed = new Set((options.allowedOrigins ?? [])
81
+ .map(originOf)
82
+ .filter((origin) => origin !== undefined));
83
+ const denial = options.denial ?? ((message) => new Error(message));
84
+ return (netAllowlist, maxFetchBytes) => {
85
+ // The run's granted hosts, re-checked here on the page side: the worker
86
+ // checks them too, but the worker is the isolate a hostile script runs
87
+ // in, so the page is the boundary (the same rule the Node provider
88
+ // follows). A request must pass BOTH this and the origin allowlist.
89
+ const granted = new Set(netAllowlist.map((host) => host.toLowerCase()));
90
+ async function request(rawUrl, method, body) {
91
+ let url;
92
+ try {
93
+ url = new URL(rawUrl);
94
+ }
95
+ catch {
96
+ throw denial(`"${rawUrl}" is not a valid URL`);
97
+ }
98
+ if (url.protocol !== 'http:' && url.protocol !== 'https:') {
99
+ throw denial(`scheme "${url.protocol}" is not allowed`);
100
+ }
101
+ if (url.username !== '' || url.password !== '') {
102
+ throw denial(`URL "${rawUrl}" embeds credentials, which is not allowed`);
103
+ }
104
+ if (!granted.has(url.hostname.toLowerCase())) {
105
+ throw denial(`host "${url.hostname}" is not on the run's allowlist`);
106
+ }
107
+ if (!allowed.has(url.origin)) {
108
+ throw denial(`origin "${url.origin}" is not on the host's allowlist`);
109
+ }
110
+ const doFetch = options.fetch ?? fetch;
111
+ let response;
112
+ try {
113
+ response = await doFetch(url.href, {
114
+ method,
115
+ ...(body !== undefined
116
+ ? { body, headers: { 'content-type': 'text/plain;charset=UTF-8' } }
117
+ : {}),
118
+ redirect: 'manual',
119
+ credentials: 'omit',
120
+ referrerPolicy: 'no-referrer',
121
+ });
122
+ }
123
+ catch (error) {
124
+ throw denial(`request failed: ${error instanceof Error ? error.message : String(error)}`);
125
+ }
126
+ if (response.type === 'opaqueredirect') {
127
+ throw denial('redirects are not followed in a browser');
128
+ }
129
+ if (response.status >= 300 && response.status < 400) {
130
+ throw denial('redirects are not followed in a browser');
131
+ }
132
+ return {
133
+ status: response.status,
134
+ body: await readCapped(response, maxFetchBytes, denial),
135
+ };
136
+ }
137
+ return {
138
+ get: (url) => request(url, 'GET', undefined),
139
+ post: (url, requestBody) => request(url, 'POST', requestBody),
140
+ patch: (url, requestBody) => request(url, 'PATCH', requestBody),
141
+ };
142
+ };
143
+ }
144
+ /**
145
+ * A `HostIsolate` of kind `browser`: each run starts a MODULE Web Worker
146
+ * and the job carries the `glue.wasm` URL. A Web Worker takes no heap cap,
147
+ * so a runaway allocation inside it is bounded only by the browser; Lua's
148
+ * own limits and the external watchdog still apply (docs/security.md).
149
+ */
150
+ export function createBrowserHostIsolate(options = {}) {
151
+ const netProvider = options.netProvider ?? createFetchNetProvider({ allowedOrigins: [] });
152
+ return {
153
+ kind: 'browser',
154
+ spawner: async () => {
155
+ const workerUrl = options.workerUrl ?? new URL('./browser-worker.js', import.meta.url);
156
+ const wasmUrl = options.wasmUrl ?? new URL('./glue.wasm', import.meta.url);
157
+ return createBrowserIsolate({
158
+ createWorker: () => options.workerType === 'classic'
159
+ ? new Worker(workerUrl)
160
+ : new Worker(workerUrl, { type: 'module' }),
161
+ netProvider,
162
+ wasmUri: String(wasmUrl),
163
+ });
164
+ },
165
+ dispose: () => { },
166
+ };
167
+ }
@@ -0,0 +1,5 @@
1
+ {
2
+ "outcomes": [{ "kind": "opened" }],
3
+ "diagnostics": [],
4
+ "prompts": []
5
+ }
@@ -0,0 +1,8 @@
1
+ # A plain note
2
+
3
+ Nothing here needs a script or a pack: just markdown and one standard
4
+ component.
5
+
6
+ ::callout{tone=info}
7
+ Nothing to run.
8
+ ::
@@ -0,0 +1,6 @@
1
+ {
2
+ "id": "plain-open",
3
+ "invariant": "a note with no scripts and no packs opens, renders, and writes no diagnostics",
4
+ "clock": 1700000000000,
5
+ "actions": [{ "type": "open" }]
6
+ }
@@ -0,0 +1,16 @@
1
+ {
2
+ "outcomes": [
3
+ { "kind": "opened" },
4
+ {
5
+ "kind": "ran",
6
+ "values": { "total": 3 },
7
+ "failureKinds": [],
8
+ "traceOk": true
9
+ }
10
+ ],
11
+ "diagnostics": [
12
+ "manual run: net declaration mismatch",
13
+ "uses net access to api.example.com"
14
+ ],
15
+ "prompts": ["This note's scripts can send data to api.example.com. Allow?"]
16
+ }
@@ -0,0 +1,5 @@
1
+ # Manual run, one host, grant accepted
2
+
3
+ ```lua {name=total}
4
+ return net.fetch_json("https://api.example.com/orders")
5
+ ```
@@ -0,0 +1,14 @@
1
+ {
2
+ "id": "run-manual-grant-accepted",
3
+ "invariant": "a manual run prompts once, worded with the canonical wording, and a granted host's value lands",
4
+ "clock": 1700000000000,
5
+ "actions": [
6
+ { "type": "open" },
7
+ {
8
+ "type": "run",
9
+ "trigger": "manual",
10
+ "answers": ["allow"],
11
+ "simulate": { "name": "total", "value": 3, "requiresHost": "api.example.com" }
12
+ }
13
+ ]
14
+ }
@@ -0,0 +1,18 @@
1
+ {
2
+ "outcomes": [
3
+ { "kind": "opened" },
4
+ {
5
+ "kind": "ran",
6
+ "values": {},
7
+ "failureKinds": ["capability-denied"],
8
+ "traceOk": false
9
+ }
10
+ ],
11
+ "diagnostics": [
12
+ "manual run finished with 1 script failure(s)",
13
+ "total (capability-denied)",
14
+ "manual run: net declaration mismatch",
15
+ "uses net access to api.example.com"
16
+ ],
17
+ "prompts": ["This note's scripts can send data to api.example.com. Allow?"]
18
+ }
@@ -0,0 +1,5 @@
1
+ # Manual run, one host, grant refused
2
+
3
+ ```lua {name=total}
4
+ return net.fetch_json("https://api.example.com/orders")
5
+ ```
@@ -0,0 +1,14 @@
1
+ {
2
+ "id": "run-manual-grant-refused",
3
+ "invariant": "the same prompt, refused, leaves the host reaching nothing and reports the failure on both surfaces",
4
+ "clock": 1700000000000,
5
+ "actions": [
6
+ { "type": "open" },
7
+ {
8
+ "type": "run",
9
+ "trigger": "manual",
10
+ "answers": ["deny"],
11
+ "simulate": { "name": "total", "value": 3, "requiresHost": "api.example.com" }
12
+ }
13
+ ]
14
+ }
@@ -0,0 +1,16 @@
1
+ {
2
+ "outcomes": [
3
+ { "kind": "opened" },
4
+ {
5
+ "kind": "ran",
6
+ "values": {},
7
+ "failureKinds": ["tier-blocked"],
8
+ "traceOk": false
9
+ }
10
+ ],
11
+ "diagnostics": [
12
+ "auto run finished with 1 script failure(s)",
13
+ "counter (tier-blocked)"
14
+ ],
15
+ "prompts": []
16
+ }
@@ -0,0 +1,10 @@
1
+ # Auto trigger, a write attempt
2
+
3
+ Runs on open, on a timer, or manually. This script only ever attempts an
4
+ effect (a write), never a network call, so the tier gate is the only
5
+ thing that can deny it.
6
+
7
+ ```lua {name=counter}
8
+ store.set("counter", 1)
9
+ return store.get("counter")
10
+ ```
@@ -0,0 +1,13 @@
1
+ {
2
+ "id": "run-auto-write-denied",
3
+ "invariant": "an auto trigger is read-only: a write attempt is denied by the tier gate, not by luck, and never prompts",
4
+ "clock": 1700000000000,
5
+ "actions": [
6
+ { "type": "open" },
7
+ {
8
+ "type": "run",
9
+ "trigger": "auto",
10
+ "simulate": { "name": "counter", "requiresManualTier": true }
11
+ }
12
+ ]
13
+ }
@@ -0,0 +1,14 @@
1
+ {
2
+ "outcomes": [
3
+ {
4
+ "kind": "exported",
5
+ "contains": ["mk-unknown", "ana_timeline"],
6
+ "excludes": ["<script"]
7
+ }
8
+ ],
9
+ "diagnostics": [
10
+ "Export wrote",
11
+ "Rendered with the static engine because no pack components are loaded"
12
+ ],
13
+ "prompts": []
14
+ }
@@ -0,0 +1,8 @@
1
+ # Export with a pack directive
2
+
3
+ A plain export never loads packs, so this directive from the (not
4
+ installed here) "ana" pack falls back to the unknown-component box.
5
+
6
+ ::ana_timeline
7
+ No timeline data in a plain export.
8
+ ::
@@ -0,0 +1,6 @@
1
+ {
2
+ "id": "export-html-pack-directive",
3
+ "invariant": "a pack directive the host did not load exports as the unknown-component fallback, and the export excludes script machinery",
4
+ "clock": 1700000000000,
5
+ "actions": [{ "type": "exportHtml" }]
6
+ }
@@ -0,0 +1,18 @@
1
+ {
2
+ "outcomes": [
3
+ { "kind": "opened" },
4
+ { "kind": "installed", "namespace": "ana", "replaced": false },
5
+ { "kind": "loaded", "namespaces": ["ana"] },
6
+ { "kind": "installed", "namespace": "ana", "replaced": true }
7
+ ],
8
+ "diagnostics": [
9
+ "Install pack from file installed pack \"ana\" from \"files/ana-1.0.0\"",
10
+ "Loaded pack \"ana\" (namespace: ana, 1 component)",
11
+ "replacing the previous install"
12
+ ],
13
+ "prompts": [
14
+ "Installing \"ana\" lets its code run inside",
15
+ "Installing \"ana\" lets its code run inside",
16
+ "A pack named \"ana\" is already installed. Replacing it removes the installed copy and cannot be undone."
17
+ ]
18
+ }
@@ -0,0 +1,13 @@
1
+ {
2
+ "name": "ana",
3
+ "engine": "react",
4
+ "version": "1.0.0",
5
+ "components": {
6
+ "timeline": {
7
+ "sources": {
8
+ "react": "./Timeline.tsx",
9
+ "ansi": "./Timeline.ansi.tsx"
10
+ }
11
+ }
12
+ }
13
+ }
@@ -0,0 +1 @@
1
+ window.__markiiRegisterPack(() => ({}));
@@ -0,0 +1,5 @@
1
+ # Install a pack, then load it
2
+
3
+ ::ana_timeline
4
+ Rendered once "ana" is installed and loaded.
5
+ ::
@@ -0,0 +1,15 @@
1
+ {
2
+ "id": "install-pack-then-render",
3
+ "invariant": "installing a .mkp loads its namespace; a second install of the same namespace asks before replacing",
4
+ "clock": 1700000000000,
5
+ "actions": [
6
+ { "type": "open" },
7
+ { "type": "installPack", "archive": "files/ana-1.0.0", "answers": ["allow"] },
8
+ { "type": "loadPacks" },
9
+ {
10
+ "type": "installPack",
11
+ "archive": "files/ana-1.0.0",
12
+ "answers": ["allow", "allow"]
13
+ }
14
+ ]
15
+ }
@@ -0,0 +1,14 @@
1
+ {
2
+ "outcomes": [
3
+ {
4
+ "kind": "completions",
5
+ "labels": ["callout"],
6
+ "replaceStart": 0,
7
+ "replaceEnd": 6,
8
+ "tokenStart": 3,
9
+ "fenceEditCount": 0
10
+ }
11
+ ],
12
+ "diagnostics": [],
13
+ "prompts": []
14
+ }
@@ -0,0 +1,3 @@
1
+ # Completion inside a directive
2
+
3
+ :::cal
@@ -0,0 +1,6 @@
1
+ {
2
+ "id": "complete-in-directive",
3
+ "invariant": "completion inside a directive returns the standard set",
4
+ "clock": 1700000000000,
5
+ "actions": [{ "type": "completeAt", "line": 2, "column": 6 }]
6
+ }
@@ -0,0 +1,5 @@
1
+ {
2
+ "outcomes": [{ "kind": "hover", "contains": "callout", "start": 2, "end": 9 }],
3
+ "diagnostics": [],
4
+ "prompts": []
5
+ }
@@ -0,0 +1,5 @@
1
+ # Hover on a standard component
2
+
3
+ ::callout{tone=info}
4
+ Hover over the directive name above.
5
+ ::
@@ -0,0 +1,6 @@
1
+ {
2
+ "id": "hover-standard-component",
3
+ "invariant": "hover on a standard component returns its documented attributes",
4
+ "clock": 1700000000000,
5
+ "actions": [{ "type": "hoverAt", "line": 2, "column": 5 }]
6
+ }
@@ -0,0 +1,5 @@
1
+ {
2
+ "outcomes": [{ "kind": "inserted" }],
3
+ "diagnostics": [],
4
+ "prompts": []
5
+ }
@@ -0,0 +1,4 @@
1
+ # Insert a skeleton
2
+
3
+ ::card
4
+ ::
@@ -0,0 +1,6 @@
1
+ {
2
+ "id": "insert-skeleton",
3
+ "invariant": "inserting a component applies one undoable edit",
4
+ "clock": 1700000000000,
5
+ "actions": [{ "type": "insertComponent", "name": "callout" }]
6
+ }
@@ -0,0 +1,11 @@
1
+ {
2
+ "outcomes": [
3
+ { "kind": "opened" },
4
+ {
5
+ "kind": "declined",
6
+ "reasonContains": "script execution is off"
7
+ }
8
+ ],
9
+ "diagnostics": ["run (manual) blocked: script execution is off on this device"],
10
+ "prompts": []
11
+ }
@@ -0,0 +1,5 @@
1
+ # The device switch is off
2
+
3
+ ```lua {name=total}
4
+ return net.fetch_json("https://api.example.com/orders")
5
+ ```
@@ -0,0 +1,9 @@
1
+ {
2
+ "id": "scripts-disabled",
3
+ "invariant": "the device switch blocks the run before the isolate, and says so on both surfaces",
4
+ "clock": 1700000000000,
5
+ "actions": [
6
+ { "type": "open" },
7
+ { "type": "run", "trigger": "manual", "scriptsDisabled": true }
8
+ ]
9
+ }
@@ -0,0 +1,5 @@
1
+ {
2
+ "outcomes": [{ "kind": "none" }],
3
+ "diagnostics": [],
4
+ "prompts": []
5
+ }
@@ -0,0 +1,5 @@
1
+ # Closing fence
2
+
3
+ :::callout
4
+ Body.
5
+ :::
@@ -0,0 +1,12 @@
1
+ {
2
+ "id": "complete-closing-fence",
3
+ "invariant": "a bare colon run that closes an open container offers no completions, and says so without failing",
4
+ "clock": 1700000000000,
5
+ "actions": [
6
+ {
7
+ "type": "completeAt",
8
+ "line": 4,
9
+ "column": 3
10
+ }
11
+ ]
12
+ }
@@ -0,0 +1,5 @@
1
+ {
2
+ "outcomes": [{ "kind": "completions", "labels": ["callout"], "fenceEditCount": 2 }],
3
+ "diagnostics": [],
4
+ "prompts": []
5
+ }
@@ -0,0 +1,5 @@
1
+ # Nested container
2
+
3
+ :::card
4
+ :::cal
5
+ :::
@@ -0,0 +1,12 @@
1
+ {
2
+ "id": "complete-nested-container",
3
+ "invariant": "completion of a container inside an equal-length container carries the fence edits that keep the pair legal",
4
+ "clock": 1700000000000,
5
+ "actions": [
6
+ {
7
+ "type": "completeAt",
8
+ "line": 3,
9
+ "column": 6
10
+ }
11
+ ]
12
+ }
@@ -0,0 +1,12 @@
1
+ {
2
+ "outcomes": [
3
+ { "kind": "opened" },
4
+ { "kind": "declined", "step": "consent" },
5
+ { "kind": "not-installed", "namespace": "ana" }
6
+ ],
7
+ "diagnostics": [
8
+ "Install pack from file cancelled for",
9
+ "Remove pack found no installed pack \"ana\""
10
+ ],
11
+ "prompts": ["Installing \"ana\" lets its code run inside"]
12
+ }