@1agh/maude 1.0.11 → 1.2.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 (506) hide show
  1. package/apps/studio/acp/bridge.ts +18 -13
  2. package/apps/studio/acp/transcript-io.ts +394 -0
  3. package/apps/studio/acp/transcript.ts +103 -38
  4. package/apps/studio/api.ts +139 -26
  5. package/apps/studio/bin/_import-figma.mjs +57 -11
  6. package/apps/studio/bin/_seed-fixture.mjs +163 -0
  7. package/apps/studio/canvas-imports.ts +89 -0
  8. package/apps/studio/client/app.jsx +257 -7
  9. package/apps/studio/client/export-center.jsx +15 -7
  10. package/apps/studio/client/panels/SyncPanel.jsx +125 -4
  11. package/apps/studio/client/report-bug.jsx +19 -1
  12. package/apps/studio/client/styles/3-shell-maude.css +34 -0
  13. package/apps/studio/client/styles/4-components.css +26 -0
  14. package/apps/studio/collab/persistence.ts +132 -16
  15. package/apps/studio/collab/registry.ts +10 -18
  16. package/apps/studio/debug-bundle.ts +13 -0
  17. package/apps/studio/dist/client.bundle.js +1128 -1128
  18. package/apps/studio/dist/styles.css +1 -1
  19. package/apps/studio/export-dialog.tsx +54 -0
  20. package/apps/studio/exporters/degraded.ts +19 -3
  21. package/apps/studio/exporters/ghostscript.ts +194 -0
  22. package/apps/studio/exporters/pdf-fonts.ts +261 -0
  23. package/apps/studio/exporters/pdf.ts +152 -3
  24. package/apps/studio/exporters/remote.ts +36 -1
  25. package/apps/studio/fs-watch.ts +45 -1
  26. package/apps/studio/http.ts +7 -1
  27. package/apps/studio/mem.ts +45 -9
  28. package/apps/studio/server.ts +11 -4
  29. package/apps/studio/sync/agent.ts +136 -29
  30. package/apps/studio/sync/asset-push.ts +1 -47
  31. package/apps/studio/sync/codec.ts +122 -31
  32. package/apps/studio/sync/cold-start-apply.ts +7 -0
  33. package/apps/studio/sync/cold-start.ts +118 -22
  34. package/apps/studio/sync/comment-identity.ts +68 -0
  35. package/apps/studio/sync/connection-state.ts +15 -1
  36. package/apps/studio/sync/file-ledger.ts +117 -0
  37. package/apps/studio/sync/file-plane.ts +623 -27
  38. package/apps/studio/sync/index.ts +711 -20
  39. package/apps/studio/sync/journal-client.ts +13 -1
  40. package/apps/studio/sync/migrate-seed.ts +40 -3
  41. package/apps/studio/sync/presentation.ts +26 -3
  42. package/apps/studio/sync/retry-after.ts +169 -0
  43. package/apps/studio/sync/seed-progress.ts +218 -0
  44. package/apps/studio/sync/status.ts +122 -5
  45. package/apps/studio/sync/transport-error.ts +111 -0
  46. package/apps/studio/whats-new.json +72 -0
  47. package/cli/bin/maude.mjs +2 -0
  48. package/cli/commands/codex.mjs +191 -0
  49. package/cli/commands/harness.mjs +1051 -0
  50. package/cli/commands/help.mjs +13 -0
  51. package/cli/lib/design-link.mjs +190 -2
  52. package/cli/lib/harness/capabilities.mjs +302 -0
  53. package/cli/lib/harness/codex-runtime.mjs +889 -0
  54. package/cli/lib/harness/compatibility.mjs +27 -0
  55. package/cli/lib/harness/discover-claude.mjs +1393 -0
  56. package/cli/lib/harness/managed-state.mjs +1296 -0
  57. package/cli/lib/harness/model.mjs +86 -0
  58. package/cli/lib/harness/secrets.mjs +390 -0
  59. package/cli/lib/harness/targets/codex.mjs +1141 -0
  60. package/cli/lib/harness/targets/opencode.mjs +807 -0
  61. package/cli/lib/harness/transaction.mjs +1099 -0
  62. package/cli/lib/hubs-config.mjs +35 -8
  63. package/cli/templates/harness/opencode/maude-projector.ts +58 -0
  64. package/docs/harness-capability-matrix.md +125 -0
  65. package/docs/harness-environment-projection.md +95 -0
  66. package/package.json +32 -9
  67. package/plugins/flow/.claude-plugin/config.schema.json +8 -0
  68. package/apps/studio/.ai/cache/_stats.json +0 -7
  69. package/apps/studio/bin/_agent-browser-safe.test.mjs +0 -165
  70. package/apps/studio/bin/_curl-local.test.mjs +0 -280
  71. package/apps/studio/bin/_pw-launch.test.mjs +0 -90
  72. package/apps/studio/bin/_smart-frames.test.mjs +0 -195
  73. package/apps/studio/bin/_transcribe.test.mjs +0 -80
  74. package/apps/studio/bun.lock +0 -928
  75. package/apps/studio/draw/test/animate.test.ts +0 -172
  76. package/apps/studio/draw/test/brush.test.ts +0 -213
  77. package/apps/studio/draw/test/composition.test.ts +0 -141
  78. package/apps/studio/draw/test/geometry.test.ts +0 -199
  79. package/apps/studio/draw/test/gradient.test.ts +0 -167
  80. package/apps/studio/draw/test/layout.test.ts +0 -120
  81. package/apps/studio/draw/test/morph.test.ts +0 -107
  82. package/apps/studio/draw/test/optimize.test.ts +0 -40
  83. package/apps/studio/draw/test/palette.test.ts +0 -123
  84. package/apps/studio/draw/test/primitives.test.ts +0 -129
  85. package/apps/studio/draw/test/serialize-animate.test.ts +0 -142
  86. package/apps/studio/draw/test/serialize.test.ts +0 -105
  87. package/apps/studio/figma/assets.test.ts +0 -464
  88. package/apps/studio/figma/client.test.ts +0 -395
  89. package/apps/studio/figma/codegen-client.test.ts +0 -276
  90. package/apps/studio/figma/codegen-fonts.test.ts +0 -103
  91. package/apps/studio/figma/codegen-values.test.ts +0 -179
  92. package/apps/studio/figma/comments-to-strokes.test.ts +0 -194
  93. package/apps/studio/figma/fig-decode.test.ts +0 -788
  94. package/apps/studio/figma/fig-differential.test.ts +0 -182
  95. package/apps/studio/figma/fig-translator.test.ts +0 -192
  96. package/apps/studio/figma/fig-vector.test.ts +0 -113
  97. package/apps/studio/figma/from-codegen.test.ts +0 -408
  98. package/apps/studio/figma/sanitize.test.ts +0 -325
  99. package/apps/studio/figma/tailwind-map.test.ts +0 -142
  100. package/apps/studio/figma/to-artboard.test.ts +0 -808
  101. package/apps/studio/figma/to-render.test.ts +0 -180
  102. package/apps/studio/figma/to-strokes-roundtrip.test.ts +0 -152
  103. package/apps/studio/figma/to-strokes.test.ts +0 -705
  104. package/apps/studio/figma/to-tokens.test.ts +0 -321
  105. package/apps/studio/figma/url.test.ts +0 -167
  106. package/apps/studio/footage/schema.test.ts +0 -287
  107. package/apps/studio/generation/adapters/groq.test.ts +0 -80
  108. package/apps/studio/generation/audio-library.test.ts +0 -95
  109. package/apps/studio/generation/captions.test.ts +0 -78
  110. package/apps/studio/generation/elevenlabs.test.ts +0 -223
  111. package/apps/studio/generation/gemini.test.ts +0 -350
  112. package/apps/studio/generation/jobs.test.ts +0 -128
  113. package/apps/studio/generation/keys.test.ts +0 -75
  114. package/apps/studio/generation/prefs.test.ts +0 -95
  115. package/apps/studio/generation/types.test.ts +0 -123
  116. package/apps/studio/generation/whisper-models.test.ts +0 -179
  117. package/apps/studio/test/_helpers.ts +0 -108
  118. package/apps/studio/test/acp-activity-endpoint.test.ts +0 -183
  119. package/apps/studio/test/acp-activity.test.ts +0 -154
  120. package/apps/studio/test/acp-ai-activity.test.ts +0 -182
  121. package/apps/studio/test/acp-attachment-serve.test.ts +0 -74
  122. package/apps/studio/test/acp-bootstrap-brief.test.ts +0 -178
  123. package/apps/studio/test/acp-branch-guard.test.ts +0 -123
  124. package/apps/studio/test/acp-bridge-lifetime.test.ts +0 -475
  125. package/apps/studio/test/acp-bridge.test.ts +0 -545
  126. package/apps/studio/test/acp-capabilities.test.ts +0 -123
  127. package/apps/studio/test/acp-caps-bridge.test.ts +0 -279
  128. package/apps/studio/test/acp-commands.test.ts +0 -119
  129. package/apps/studio/test/acp-elicitation-bridge.test.ts +0 -480
  130. package/apps/studio/test/acp-elicitation.test.ts +0 -251
  131. package/apps/studio/test/acp-env.test.ts +0 -95
  132. package/apps/studio/test/acp-mode-banner.test.ts +0 -45
  133. package/apps/studio/test/acp-origin-gate.test.ts +0 -160
  134. package/apps/studio/test/acp-permission-prompt.test.ts +0 -251
  135. package/apps/studio/test/acp-permission.test.ts +0 -278
  136. package/apps/studio/test/acp-plugin-bootstrap.test.ts +0 -89
  137. package/apps/studio/test/acp-session-allowed-tools.test.ts +0 -235
  138. package/apps/studio/test/acp-session-plugins.test.ts +0 -209
  139. package/apps/studio/test/acp-toolgroup.test.ts +0 -76
  140. package/apps/studio/test/acp-transcript-view.test.ts +0 -71
  141. package/apps/studio/test/acp-transcript.test.ts +0 -238
  142. package/apps/studio/test/acp-usage-bridge.test.ts +0 -141
  143. package/apps/studio/test/acp-usage.test.ts +0 -143
  144. package/apps/studio/test/acp-write-gate.test.ts +0 -323
  145. package/apps/studio/test/acp-write-scope.test.ts +0 -533
  146. package/apps/studio/test/active-state.test.ts +0 -186
  147. package/apps/studio/test/activity.test.ts +0 -189
  148. package/apps/studio/test/ai-activity.test.ts +0 -113
  149. package/apps/studio/test/annotate-write.test.ts +0 -1074
  150. package/apps/studio/test/annotation-strokes-command.test.ts +0 -166
  151. package/apps/studio/test/annotations-align.test.ts +0 -88
  152. package/apps/studio/test/annotations-api.test.ts +0 -145
  153. package/apps/studio/test/annotations-bindings.test.ts +0 -262
  154. package/apps/studio/test/annotations-draw-modifiers.test.ts +0 -206
  155. package/apps/studio/test/annotations-groups.test.ts +0 -231
  156. package/apps/studio/test/annotations-layer.test.ts +0 -1011
  157. package/apps/studio/test/annotations-roundtrip.test.ts +0 -663
  158. package/apps/studio/test/annotations-snap.test.ts +0 -135
  159. package/apps/studio/test/annotations-text-format.test.ts +0 -396
  160. package/apps/studio/test/artboard-activity-overlay.test.tsx +0 -56
  161. package/apps/studio/test/artboard-guides-overlay.test.tsx +0 -152
  162. package/apps/studio/test/artboard-hug-commit.test.ts +0 -36
  163. package/apps/studio/test/artboard-kinds.test.tsx +0 -83
  164. package/apps/studio/test/artboard-selection-attrs.test.ts +0 -69
  165. package/apps/studio/test/asset-api.test.ts +0 -149
  166. package/apps/studio/test/assets-s3.test.ts +0 -249
  167. package/apps/studio/test/binary-smoke.test.ts +0 -47
  168. package/apps/studio/test/boot-self-heal.test.ts +0 -87
  169. package/apps/studio/test/browse-posture.test.tsx +0 -107
  170. package/apps/studio/test/bundle-smoke.test.ts +0 -46
  171. package/apps/studio/test/camera-reveal.test.tsx +0 -173
  172. package/apps/studio/test/canvas-artifacts.test.ts +0 -125
  173. package/apps/studio/test/canvas-build-sandbox.test.ts +0 -69
  174. package/apps/studio/test/canvas-build.test.ts +0 -141
  175. package/apps/studio/test/canvas-create-api.test.ts +0 -676
  176. package/apps/studio/test/canvas-cursors.test.ts +0 -164
  177. package/apps/studio/test/canvas-edit.test.ts +0 -470
  178. package/apps/studio/test/canvas-freshness-deps.test.ts +0 -64
  179. package/apps/studio/test/canvas-header.test.ts +0 -127
  180. package/apps/studio/test/canvas-hide-chrome.test.ts +0 -88
  181. package/apps/studio/test/canvas-hmr-runtime.test.tsx +0 -114
  182. package/apps/studio/test/canvas-lib-inline.test.ts +0 -146
  183. package/apps/studio/test/canvas-lib-motion.test.ts +0 -131
  184. package/apps/studio/test/canvas-lib-resolver.test.ts +0 -111
  185. package/apps/studio/test/canvas-list-watch.test.ts +0 -388
  186. package/apps/studio/test/canvas-media-drop.test.ts +0 -187
  187. package/apps/studio/test/canvas-meta-api.test.ts +0 -707
  188. package/apps/studio/test/canvas-move-api.test.ts +0 -587
  189. package/apps/studio/test/canvas-origin-gate.test.ts +0 -417
  190. package/apps/studio/test/canvas-pipeline.test.ts +0 -180
  191. package/apps/studio/test/canvas-rects.test.ts +0 -198
  192. package/apps/studio/test/canvas-reorder.test.ts +0 -211
  193. package/apps/studio/test/canvas-route.test.ts +0 -179
  194. package/apps/studio/test/canvas-shell-base.test.ts +0 -107
  195. package/apps/studio/test/canvas-shell-build-error.test.ts +0 -52
  196. package/apps/studio/test/canvas-url.test.ts +0 -138
  197. package/apps/studio/test/canvas-wheel-delta.test.ts +0 -42
  198. package/apps/studio/test/canvas-zoom-floor.test.ts +0 -73
  199. package/apps/studio/test/capture-core.test.ts +0 -32
  200. package/apps/studio/test/capture-determinism-shape.test.ts +0 -135
  201. package/apps/studio/test/chat-attachments.test.ts +0 -117
  202. package/apps/studio/test/chat-context.test.ts +0 -129
  203. package/apps/studio/test/chat-markdown.test.tsx +0 -58
  204. package/apps/studio/test/clip-addressing.test.ts +0 -738
  205. package/apps/studio/test/clip-ops.test.ts +0 -922
  206. package/apps/studio/test/cloud-build.test.ts +0 -95
  207. package/apps/studio/test/cloud-connect-note.test.ts +0 -135
  208. package/apps/studio/test/cloud-endpoints.test.ts +0 -590
  209. package/apps/studio/test/cloud-history-hardening.test.ts +0 -165
  210. package/apps/studio/test/cloud-history-posture.test.ts +0 -230
  211. package/apps/studio/test/cloud-managed-save-surfaces.test.ts +0 -90
  212. package/apps/studio/test/cloud-renew.test.ts +0 -205
  213. package/apps/studio/test/cloud-session-role.test.ts +0 -161
  214. package/apps/studio/test/cloud-shell-surfaces.test.ts +0 -170
  215. package/apps/studio/test/cold-start-apply.test.ts +0 -303
  216. package/apps/studio/test/collab-annotations-bridge.test.ts +0 -55
  217. package/apps/studio/test/collab-awareness-bridge.test.ts +0 -223
  218. package/apps/studio/test/collab-bridge.test.ts +0 -79
  219. package/apps/studio/test/collab-loopback.test.ts +0 -63
  220. package/apps/studio/test/collab-origin-gate.test.ts +0 -323
  221. package/apps/studio/test/collab-protocol.test.ts +0 -146
  222. package/apps/studio/test/collab-readonly-gate.test.ts +0 -194
  223. package/apps/studio/test/collab-reseed-guard.test.ts +0 -78
  224. package/apps/studio/test/collab-room.test.ts +0 -188
  225. package/apps/studio/test/collab-session-survival.test.tsx +0 -176
  226. package/apps/studio/test/collab-stress.test.ts +0 -129
  227. package/apps/studio/test/comment-mount.test.ts +0 -97
  228. package/apps/studio/test/comment-relay-origin-gate.test.ts +0 -117
  229. package/apps/studio/test/comments-api.test.ts +0 -277
  230. package/apps/studio/test/comments-fs-rebroadcast.test.ts +0 -155
  231. package/apps/studio/test/comments-overlay.test.ts +0 -117
  232. package/apps/studio/test/compile-entry.test.ts +0 -133
  233. package/apps/studio/test/component-map.test.ts +0 -48
  234. package/apps/studio/test/config-projection.test.ts +0 -104
  235. package/apps/studio/test/config-reload.test.ts +0 -230
  236. package/apps/studio/test/config-version.test.ts +0 -88
  237. package/apps/studio/test/context-resolve-tokens.test.ts +0 -97
  238. package/apps/studio/test/convert-to-absolute.test.ts +0 -333
  239. package/apps/studio/test/credential-grammar.test.ts +0 -107
  240. package/apps/studio/test/csp-canvas-shell.test.ts +0 -62
  241. package/apps/studio/test/csrf-write-guard.test.ts +0 -141
  242. package/apps/studio/test/debug-bundle.test.ts +0 -105
  243. package/apps/studio/test/design-setup-readiness.test.ts +0 -155
  244. package/apps/studio/test/detach-component.test.ts +0 -94
  245. package/apps/studio/test/dns-rebinding-guard.test.ts +0 -101
  246. package/apps/studio/test/dom-selection.test.ts +0 -198
  247. package/apps/studio/test/dynamic-text-edit.test.ts +0 -162
  248. package/apps/studio/test/edit-css-occurrence.test.ts +0 -81
  249. package/apps/studio/test/edit-persistence.test.ts +0 -91
  250. package/apps/studio/test/edit-scope-api.test.ts +0 -119
  251. package/apps/studio/test/edit-source-command.test.ts +0 -142
  252. package/apps/studio/test/edit-suppress.test.ts +0 -170
  253. package/apps/studio/test/editing-presence.test.ts +0 -103
  254. package/apps/studio/test/element-resize.test.ts +0 -136
  255. package/apps/studio/test/element-structural-api.test.ts +0 -434
  256. package/apps/studio/test/element-structural-edit.test.ts +0 -750
  257. package/apps/studio/test/ensure-browser.test.ts +0 -92
  258. package/apps/studio/test/equal-spacing-detector.test.ts +0 -257
  259. package/apps/studio/test/export-assemble.test.ts +0 -53
  260. package/apps/studio/test/export-browser-lane.test.ts +0 -126
  261. package/apps/studio/test/export-capture-fidelity.test.ts +0 -146
  262. package/apps/studio/test/export-capture-hygiene.test.ts +0 -675
  263. package/apps/studio/test/export-center.test.tsx +0 -287
  264. package/apps/studio/test/export-e2e-lanes.test.ts +0 -857
  265. package/apps/studio/test/export-format-scope-coherence.test.ts +0 -89
  266. package/apps/studio/test/export-lane.test.ts +0 -245
  267. package/apps/studio/test/export-shim-multi-capture.test.ts +0 -83
  268. package/apps/studio/test/exporters/canva.test.ts +0 -63
  269. package/apps/studio/test/exporters/degraded-propagation.test.ts +0 -123
  270. package/apps/studio/test/exporters/endpoint.test.ts +0 -121
  271. package/apps/studio/test/exporters/history.test.ts +0 -107
  272. package/apps/studio/test/exporters/html.test.ts +0 -26
  273. package/apps/studio/test/exporters/jobs.test.ts +0 -342
  274. package/apps/studio/test/exporters/pdf.test.ts +0 -85
  275. package/apps/studio/test/exporters/png.test.ts +0 -103
  276. package/apps/studio/test/exporters/pptx-deck.test.ts +0 -112
  277. package/apps/studio/test/exporters/pptx.test.ts +0 -31
  278. package/apps/studio/test/exporters/pw-launch.test.ts +0 -49
  279. package/apps/studio/test/exporters/runtime.test.ts +0 -59
  280. package/apps/studio/test/exporters/scope.test.ts +0 -292
  281. package/apps/studio/test/exporters/svg.test.ts +0 -29
  282. package/apps/studio/test/exporters/unsupported-media.test.ts +0 -123
  283. package/apps/studio/test/exporters/zip.test.ts +0 -104
  284. package/apps/studio/test/fetch-asset-gate.test.ts +0 -189
  285. package/apps/studio/test/figjam-v3-model.test.ts +0 -342
  286. package/apps/studio/test/figma-explode.test.ts +0 -438
  287. package/apps/studio/test/figma-provenance.test.ts +0 -108
  288. package/apps/studio/test/figma-routes.test.ts +0 -294
  289. package/apps/studio/test/file-lock.test.ts +0 -84
  290. package/apps/studio/test/fixtures/backgrounding-claude-auth.sh +0 -12
  291. package/apps/studio/test/fixtures/fake-claude-auth.mjs +0 -23
  292. package/apps/studio/test/fixtures/figjam-v3-groups-bindings.svg +0 -1
  293. package/apps/studio/test/fixtures/mock-acp-agent-caps.mjs +0 -158
  294. package/apps/studio/test/fixtures/mock-acp-agent-commands.mjs +0 -44
  295. package/apps/studio/test/fixtures/mock-acp-agent-elicit-flood.mjs +0 -46
  296. package/apps/studio/test/fixtures/mock-acp-agent-elicit-url.mjs +0 -42
  297. package/apps/studio/test/fixtures/mock-acp-agent-elicit.mjs +0 -63
  298. package/apps/studio/test/fixtures/mock-acp-agent-permission-flood.mjs +0 -51
  299. package/apps/studio/test/fixtures/mock-acp-agent-permission.mjs +0 -50
  300. package/apps/studio/test/fixtures/mock-acp-agent-slow.mjs +0 -45
  301. package/apps/studio/test/fixtures/mock-acp-agent-usage.mjs +0 -56
  302. package/apps/studio/test/fixtures/mock-acp-agent-wedged.mjs +0 -37
  303. package/apps/studio/test/fixtures/mock-acp-agent-write.mjs +0 -118
  304. package/apps/studio/test/fixtures/mock-acp-agent.mjs +0 -69
  305. package/apps/studio/test/fixtures/noisy-claude-auth.mjs +0 -27
  306. package/apps/studio/test/fixtures/perf-canvas.mjs +0 -201
  307. package/apps/studio/test/fixtures/phase-20-annotations.svg +0 -1
  308. package/apps/studio/test/fixtures/phase-21-annotations.svg +0 -1
  309. package/apps/studio/test/fixtures/tiny-subset.woff2 +0 -0
  310. package/apps/studio/test/fixtures/video-comp-fixture.tsx +0 -82
  311. package/apps/studio/test/fixtures/wedged-claude-auth.sh +0 -11
  312. package/apps/studio/test/footage-store.test.ts +0 -100
  313. package/apps/studio/test/fs-mkdir-api.test.ts +0 -248
  314. package/apps/studio/test/fs-watch.test.ts +0 -41
  315. package/apps/studio/test/gemma-models.test.ts +0 -245
  316. package/apps/studio/test/generate-route.test.ts +0 -106
  317. package/apps/studio/test/git-api.test.ts +0 -0
  318. package/apps/studio/test/git-branches.test.ts +0 -341
  319. package/apps/studio/test/git-cloud-posture.test.ts +0 -59
  320. package/apps/studio/test/git-grouping.test.ts +0 -106
  321. package/apps/studio/test/git-lifecycle.test.ts +0 -100
  322. package/apps/studio/test/git-log-format.test.ts +0 -95
  323. package/apps/studio/test/git-safe-rel.test.ts +0 -132
  324. package/apps/studio/test/git-system-engine.test.ts +0 -135
  325. package/apps/studio/test/git-watch.test.ts +0 -97
  326. package/apps/studio/test/github-api.test.ts +0 -577
  327. package/apps/studio/test/grid-track-handles.test.ts +0 -160
  328. package/apps/studio/test/handoff-static-frames.test.ts +0 -215
  329. package/apps/studio/test/handoff.test.ts +0 -329
  330. package/apps/studio/test/history-rollback.test.ts +0 -88
  331. package/apps/studio/test/hmr-broadcast.test.ts +0 -236
  332. package/apps/studio/test/hmr-classify.test.ts +0 -87
  333. package/apps/studio/test/hub-link.test.ts +0 -80
  334. package/apps/studio/test/identity-cache.test.ts +0 -83
  335. package/apps/studio/test/import-asset-browser.test.ts +0 -64
  336. package/apps/studio/test/import-asset-route.test.ts +0 -71
  337. package/apps/studio/test/import-asset.test.ts +0 -427
  338. package/apps/studio/test/import-brand-browser.test.ts +0 -88
  339. package/apps/studio/test/import-brand-route.test.ts +0 -120
  340. package/apps/studio/test/import-brand.test.ts +0 -206
  341. package/apps/studio/test/import-figma.test.ts +0 -667
  342. package/apps/studio/test/import-tokens.test.ts +0 -722
  343. package/apps/studio/test/index-data-canvas-ds.test.ts +0 -97
  344. package/apps/studio/test/input-router.test.ts +0 -757
  345. package/apps/studio/test/inspect-script-syntax.test.ts +0 -53
  346. package/apps/studio/test/inspect-selections.test.ts +0 -219
  347. package/apps/studio/test/knob-props-authored.test.ts +0 -87
  348. package/apps/studio/test/layers-synthetic-groups.test.ts +0 -96
  349. package/apps/studio/test/locator.test.ts +0 -214
  350. package/apps/studio/test/marquee-overlay.test.ts +0 -94
  351. package/apps/studio/test/media-commit-chain.test.ts +0 -210
  352. package/apps/studio/test/mode-toggle.test.tsx +0 -226
  353. package/apps/studio/test/motion-sample.test.ts +0 -35
  354. package/apps/studio/test/move-artboards-command.test.ts +0 -108
  355. package/apps/studio/test/participants-chrome.test.ts +0 -65
  356. package/apps/studio/test/paths.test.ts +0 -88
  357. package/apps/studio/test/pdf-print-boxes.test.ts +0 -359
  358. package/apps/studio/test/peer-selection-follows-camera.test.tsx +0 -131
  359. package/apps/studio/test/perf-harness.ts +0 -193
  360. package/apps/studio/test/phase-3.6-smoke.test.ts +0 -79
  361. package/apps/studio/test/photo-bg-remove-validation.test.ts +0 -71
  362. package/apps/studio/test/photo-canvas-bundle.test.ts +0 -79
  363. package/apps/studio/test/photo-edit-api.test.ts +0 -201
  364. package/apps/studio/test/photo-filters.test.ts +0 -149
  365. package/apps/studio/test/photo-pipeline.test.ts +0 -106
  366. package/apps/studio/test/photo-taxonomy.test.ts +0 -96
  367. package/apps/studio/test/print-marks.test.ts +0 -113
  368. package/apps/studio/test/print-units.test.ts +0 -173
  369. package/apps/studio/test/read-annotations.test.ts +0 -727
  370. package/apps/studio/test/read-only-gate.test.ts +0 -188
  371. package/apps/studio/test/read-only-role.test.ts +0 -95
  372. package/apps/studio/test/readiness.test.ts +0 -337
  373. package/apps/studio/test/remote-ahead-behind-cache.test.ts +0 -89
  374. package/apps/studio/test/reorder-api.test.ts +0 -210
  375. package/apps/studio/test/repo-concurrency.test.ts +0 -146
  376. package/apps/studio/test/repo-lock.test.ts +0 -350
  377. package/apps/studio/test/report-proxy.test.ts +0 -125
  378. package/apps/studio/test/ripple.test.ts +0 -197
  379. package/apps/studio/test/runtime-bundle-error-mapping.test.ts +0 -43
  380. package/apps/studio/test/runtime-bundle.test.ts +0 -69
  381. package/apps/studio/test/sanitize-annotation-svg.test.ts +0 -238
  382. package/apps/studio/test/scaffold-design.test.ts +0 -122
  383. package/apps/studio/test/server-lifecycle.test.ts +0 -38
  384. package/apps/studio/test/session-runtime-state.test.ts +0 -226
  385. package/apps/studio/test/session-scope.test.ts +0 -100
  386. package/apps/studio/test/shared-doc-cell-pairing.test.ts +0 -642
  387. package/apps/studio/test/shared-doc-convergence.test.ts +0 -265
  388. package/apps/studio/test/shared-doc-foundation.test.ts +0 -63
  389. package/apps/studio/test/shared-doc-migrate.test.ts +0 -369
  390. package/apps/studio/test/shared-doc-projection.test.ts +0 -238
  391. package/apps/studio/test/shell-importmap.test.ts +0 -56
  392. package/apps/studio/test/sizing-mode.test.ts +0 -102
  393. package/apps/studio/test/slash-commands.test.ts +0 -117
  394. package/apps/studio/test/snap-distance-pill.test.ts +0 -68
  395. package/apps/studio/test/spacing-handles.test.ts +0 -138
  396. package/apps/studio/test/specimen-select.test.ts +0 -88
  397. package/apps/studio/test/sync-agent.test.ts +0 -660
  398. package/apps/studio/test/sync-annotations-cold-start.test.ts +0 -302
  399. package/apps/studio/test/sync-asset-push.test.ts +0 -931
  400. package/apps/studio/test/sync-atomic-write.test.ts +0 -62
  401. package/apps/studio/test/sync-attach-incremental.test.ts +0 -527
  402. package/apps/studio/test/sync-autocommit.test.ts +0 -537
  403. package/apps/studio/test/sync-canvas-path.test.ts +0 -200
  404. package/apps/studio/test/sync-cell-write-nudge.test.ts +0 -346
  405. package/apps/studio/test/sync-codec.test.ts +0 -230
  406. package/apps/studio/test/sync-cold-seed-dedup.test.ts +0 -187
  407. package/apps/studio/test/sync-cold-start.test.ts +0 -304
  408. package/apps/studio/test/sync-connect-honesty.test.ts +0 -114
  409. package/apps/studio/test/sync-connection-state.test.ts +0 -337
  410. package/apps/studio/test/sync-ctl-channel.test.ts +0 -508
  411. package/apps/studio/test/sync-decide-file.test.ts +0 -420
  412. package/apps/studio/test/sync-doc-name.test.ts +0 -281
  413. package/apps/studio/test/sync-echo-guard.test.ts +0 -96
  414. package/apps/studio/test/sync-file-ledger.test.ts +0 -334
  415. package/apps/studio/test/sync-file-membership.test.ts +0 -347
  416. package/apps/studio/test/sync-file-plane.test.ts +0 -976
  417. package/apps/studio/test/sync-file-pull.test.ts +0 -333
  418. package/apps/studio/test/sync-fresh-link-parity.test.ts +0 -267
  419. package/apps/studio/test/sync-fs-mirror.test.ts +0 -181
  420. package/apps/studio/test/sync-hardening.test.ts +0 -586
  421. package/apps/studio/test/sync-hub-listing.test.ts +0 -46
  422. package/apps/studio/test/sync-hubs-config.test.ts +0 -133
  423. package/apps/studio/test/sync-incident-replay.test.ts +0 -211
  424. package/apps/studio/test/sync-journal.test.ts +0 -176
  425. package/apps/studio/test/sync-meta-codec.test.ts +0 -199
  426. package/apps/studio/test/sync-migrate-flat-fallback.test.ts +0 -98
  427. package/apps/studio/test/sync-move-retirement.test.ts +0 -231
  428. package/apps/studio/test/sync-panel-surface.test.ts +0 -153
  429. package/apps/studio/test/sync-path-pull.test.ts +0 -531
  430. package/apps/studio/test/sync-presentation.test.ts +0 -262
  431. package/apps/studio/test/sync-pull-budget.test.ts +0 -169
  432. package/apps/studio/test/sync-remote-docs.test.ts +0 -333
  433. package/apps/studio/test/sync-resync-routes.test.ts +0 -168
  434. package/apps/studio/test/sync-runtime.test.ts +0 -1675
  435. package/apps/studio/test/sync-seed-defers-to-hub.test.ts +0 -83
  436. package/apps/studio/test/sync-settings-routes.test.ts +0 -195
  437. package/apps/studio/test/sync-settings.test.ts +0 -151
  438. package/apps/studio/test/sync-status.test.ts +0 -261
  439. package/apps/studio/test/sync-supervisor.test.ts +0 -262
  440. package/apps/studio/test/sync-tombstone-apply.test.ts +0 -111
  441. package/apps/studio/test/sync-trash.test.ts +0 -132
  442. package/apps/studio/test/sync-two-peer-discovery.test.ts +0 -343
  443. package/apps/studio/test/sync-untrusted.test.ts +0 -103
  444. package/apps/studio/test/system-endpoint.test.ts +0 -144
  445. package/apps/studio/test/text-editability-stamp.test.ts +0 -131
  446. package/apps/studio/test/timeline-comp-target.test.ts +0 -139
  447. package/apps/studio/test/timeline-media-cache.test.ts +0 -41
  448. package/apps/studio/test/timeline-parse.test.ts +0 -184
  449. package/apps/studio/test/timeline-scale.test.ts +0 -65
  450. package/apps/studio/test/timeline-snap.test.ts +0 -85
  451. package/apps/studio/test/tool-palette-insert-anchor.test.ts +0 -86
  452. package/apps/studio/test/tool-palette.test.tsx +0 -55
  453. package/apps/studio/test/tour-overlay.test.tsx +0 -90
  454. package/apps/studio/test/trusted-request-host.test.ts +0 -66
  455. package/apps/studio/test/undo-sequence-byte-compare.test.ts +0 -211
  456. package/apps/studio/test/undo-stack.test.ts +0 -244
  457. package/apps/studio/test/use-agent-presence.test.tsx +0 -117
  458. package/apps/studio/test/use-annotation-resize.test.ts +0 -402
  459. package/apps/studio/test/use-annotation-selection.test.tsx +0 -77
  460. package/apps/studio/test/use-artboard-drag.test.ts +0 -324
  461. package/apps/studio/test/use-canvas-activity.test.tsx +0 -206
  462. package/apps/studio/test/use-collab.test.ts +0 -0
  463. package/apps/studio/test/use-cursor-modifiers.test.ts +0 -59
  464. package/apps/studio/test/use-keyboard-discipline.test.ts +0 -27
  465. package/apps/studio/test/use-selection-set.test.tsx +0 -166
  466. package/apps/studio/test/use-snap-guides.test.ts +0 -271
  467. package/apps/studio/test/use-tool-mode.test.tsx +0 -186
  468. package/apps/studio/test/use-undo-stack.test.tsx +0 -223
  469. package/apps/studio/test/video-asset.test.ts +0 -163
  470. package/apps/studio/test/video-comp-fixture.test.ts +0 -50
  471. package/apps/studio/test/video-comp.test.ts +0 -270
  472. package/apps/studio/test/video-encode-lib.test.ts +0 -63
  473. package/apps/studio/test/video-render-bridge.test.ts +0 -171
  474. package/apps/studio/test/whats-new.test.ts +0 -113
  475. package/apps/studio/test/whisper-setup.test.ts +0 -97
  476. package/apps/studio/test/workspace-containment.test.ts +0 -382
  477. package/apps/studio/test/workspace-signin.test.ts +0 -292
  478. package/apps/studio/test/ws-handshake.test.ts +0 -33
  479. package/cli/commands/cache.test.mjs +0 -166
  480. package/cli/commands/design-link.test.mjs +0 -419
  481. package/cli/commands/design.test.mjs +0 -240
  482. package/cli/commands/doctor.test.mjs +0 -237
  483. package/cli/commands/hub-workspace.test.mjs +0 -275
  484. package/cli/commands/hub.test.mjs +0 -225
  485. package/cli/commands/kg.test.mjs +0 -142
  486. package/cli/commands/preflight.test.mjs +0 -46
  487. package/cli/lib/argv.test.mjs +0 -46
  488. package/cli/lib/cache.test.mjs +0 -303
  489. package/cli/lib/cell-plan.test.mjs +0 -225
  490. package/cli/lib/config-lint.test.mjs +0 -117
  491. package/cli/lib/ddr-to-kgai.test.mjs +0 -118
  492. package/cli/lib/design-ownership.test.mjs +0 -329
  493. package/cli/lib/fetch-asset.test.mjs +0 -213
  494. package/cli/lib/figma-codegen-reachability.test.mjs +0 -104
  495. package/cli/lib/figma-import-controls.test.mjs +0 -70
  496. package/cli/lib/flow-design-integration.test.mjs +0 -165
  497. package/cli/lib/gitignore-block.test.mjs +0 -136
  498. package/cli/lib/gitignore-drift.test.mjs +0 -156
  499. package/cli/lib/hubs-config.test.mjs +0 -165
  500. package/cli/lib/pkg-root.test.mjs +0 -111
  501. package/cli/lib/plugin-cli-reachability.test.mjs +0 -57
  502. package/cli/lib/plugin-name-namespace.test.mjs +0 -71
  503. package/cli/lib/reconstruct-toolset.test.mjs +0 -194
  504. package/cli/lib/stack-detect.test.mjs +0 -121
  505. package/cli/lib/update-check.test.mjs +0 -32
  506. package/cli/lib/workspace-plan.test.mjs +0 -699
@@ -1,188 +0,0 @@
1
- // Cloud Phase 25 C2 — the local half of the read-only gate.
2
- //
3
- // When the linked hub's credential carries the `viewer` role (vouched at
4
- // workspace sign-in, stored in hubs.json), the LOCAL dev-server refuses
5
- // project-mutating writes with a legible 403 — so a viewer's clone can never
6
- // silently diverge from the cell (which refuses the same writes at sync,
7
- // Phase 25 C1). Writes are default-denied with a short allowlist
8
- // (READ_ONLY_ALLOWED_WRITES in http.ts): per-user runtime state (camera,
9
- // ui-prefs) plus the two role-granted verbs the cell itself allows (export,
10
- // session management).
11
-
12
- import { afterAll, describe, expect, test } from 'bun:test';
13
- import { readFileSync, writeFileSync } from 'node:fs';
14
- import { join } from 'node:path';
15
- import type { Subprocess } from 'bun';
16
-
17
- import { bootServer, killProc, makeSandbox, nextPort } from './_helpers.ts';
18
-
19
- const HUB_URL = 'http://hub.invalid:9';
20
-
21
- /** Sandbox linked to a hub whose credential vouches `role`. */
22
- function makeLinkedSandbox(role?: string) {
23
- const { root, designRoot } = makeSandbox();
24
- const cfgPath = join(designRoot, 'config.json');
25
- const cfg = JSON.parse(readFileSync(cfgPath, 'utf8'));
26
- cfg.linkedHub = { url: HUB_URL, linkedAt: Date.now() };
27
- writeFileSync(cfgPath, JSON.stringify(cfg, null, 2));
28
- // The hubs.json credential store the role is read from (isHubReadOnly).
29
- const hubsPath = join(root, 'hubs.json');
30
- writeFileSync(
31
- hubsPath,
32
- JSON.stringify({
33
- hubs: { [HUB_URL]: { token: 't', linkedAt: Date.now(), ...(role ? { role } : {}) } },
34
- })
35
- );
36
- return { root, designRoot, hubsPath };
37
- }
38
-
39
- const procs: Subprocess[] = [];
40
- afterAll(async () => {
41
- for (const p of procs) await killProc(p);
42
- });
43
-
44
- async function boot(role?: string) {
45
- const { root, hubsPath } = makeLinkedSandbox(role);
46
- const port = nextPort();
47
- const proc = await bootServer(root, port, { HUBS_CONFIG_PATH: hubsPath });
48
- procs.push(proc);
49
- return { base: `http://localhost:${port}` };
50
- }
51
-
52
- describe('read-only gate — viewer role refuses project writes locally', () => {
53
- test('viewer: writes 403 read-only, per-user + role-granted writes pass, /_config says so', async () => {
54
- const { base } = await boot('viewer');
55
-
56
- // /_config tells the client at boot.
57
- const cfg = (await (await fetch(`${base}/_config`)).json()) as { readOnly?: boolean };
58
- expect(cfg.readOnly).toBe(true);
59
-
60
- // Project-mutating writes: refused with the legible read-only shape.
61
- const editText = await fetch(`${base}/_api/edit-text`, {
62
- method: 'POST',
63
- headers: { 'content-type': 'application/json' },
64
- body: JSON.stringify({ file: 'ui/fixture.html', text: 'x' }),
65
- });
66
- expect(editText.status).toBe(403);
67
- const body = (await editText.json()) as { error?: string; message?: string };
68
- expect(body.error).toBe('read-only');
69
- expect(body.message).toContain('viewer');
70
-
71
- // Canvas create + delete-element + git commit: same refusal.
72
- for (const [path, method] of [
73
- ['/_api/canvas', 'POST'],
74
- ['/_api/delete-element', 'POST'],
75
- ['/_api/git/commit', 'POST'],
76
- ['/_api/fs-mkdir', 'POST'],
77
- ['/_api/annotations', 'PUT'],
78
- ['/_api/acp/chat', 'POST'],
79
- // Cloud Phase 27 D2 — both USED to be on READ_ONLY_ALLOWED_WRITES, on the
80
- // reasoning that "viewing another branch is not changing one". Both
81
- // rewrite the working TREE, and the tree is shared: a viewer switching
82
- // branches replaces the files under whoever is mid-edit. The cell's proxy
83
- // refuses them too, but this is the gate the hub-linked desktop and the
84
- // self-host path consult, where no manifest runs at all.
85
- ['/_api/git/checkout', 'POST'],
86
- ['/_api/git/pull', 'POST'],
87
- ] as const) {
88
- const r = await fetch(base + path, { method, body: '{}' });
89
- expect(`${path} ${r.status}`).toBe(`${path} 403`);
90
- }
91
-
92
- // canvas-meta: the LAYOUT lane is refused…
93
- const layout = await fetch(`${base}/_api/canvas-meta`, {
94
- method: 'PATCH',
95
- headers: { 'content-type': 'application/json' },
96
- body: JSON.stringify({
97
- file: '.design/ui/fixture.html',
98
- patch: { layout: { artboards: [] } },
99
- }),
100
- });
101
- expect(layout.status).toBe(403);
102
- // …the VIEWPORT lane (per-user camera) is not.
103
- const viewport = await fetch(`${base}/_api/canvas-meta`, {
104
- method: 'PATCH',
105
- headers: { 'content-type': 'application/json' },
106
- body: JSON.stringify({
107
- file: '.design/ui/fixture.html',
108
- patch: { viewport: { x: 0, y: 0, zoom: 1 } },
109
- }),
110
- });
111
- expect(viewport.status).not.toBe(403);
112
-
113
- // Per-user runtime state stays writable (DDR-115 taxonomy).
114
- const camera = await fetch(`${base}/_canvas-state`, {
115
- method: 'POST',
116
- headers: { 'content-type': 'application/json' },
117
- body: JSON.stringify({ file: '.design/ui/fixture.html', viewport: { x: 1, y: 2, zoom: 1 } }),
118
- });
119
- expect(camera.status).toBe(204);
120
-
121
- // Role-granted verb: export reaches its handler (may 400 on shape, never 403).
122
- const exp = await fetch(`${base}/_api/export`, { method: 'POST', body: '{}' });
123
- expect(exp.status).not.toBe(403);
124
-
125
- // Reads are untouched.
126
- expect((await fetch(`${base}/_index-data`)).status).toBe(200);
127
- expect((await fetch(`${base}/_comments-all`)).status).toBe(200);
128
- }, 30000);
129
-
130
- test('member (no role on the credential): writes are NOT gated', async () => {
131
- const { base } = await boot(undefined);
132
- const cfg = (await (await fetch(`${base}/_config`)).json()) as { readOnly?: boolean };
133
- expect(cfg.readOnly).toBe(false);
134
- // A write reaches its handler (400 on bad shape — not the 403 refusal).
135
- const r = await fetch(`${base}/_api/edit-text`, { method: 'POST', body: '{}' });
136
- expect(r.status).not.toBe(403);
137
- }, 30000);
138
- });
139
-
140
- // ---------------------------------------------------------------------------
141
- // Cloud Phase 27 — the one write a viewer holds.
142
- //
143
- // The role matrix has said `viewer.comment === true` since Phase 25 C4 and the
144
- // People page promises it in those words. This list did not have it, so a
145
- // viewer's comment was allowed by the authority (the cell's proxy) and then
146
- // refused by the defence-in-depth layer behind it. Found by commenting as a
147
- // viewer against a real cell, not by reading either file.
148
-
149
- describe('a viewer may comment — the matrix says so, and so must this gate', () => {
150
- test('comment + reply pass; annotate still does not', async () => {
151
- const { base } = await boot('viewer');
152
-
153
- const comment = await fetch(`${base}/_comments`, {
154
- method: 'POST',
155
- headers: { 'content-type': 'application/json' },
156
- body: JSON.stringify({ file: 'ui/Sample.tsx', text: 'looks off to me' }),
157
- });
158
- expect(comment.status).not.toBe(403);
159
-
160
- const reply = await fetch(`${base}/_api/comments/abc123/reply`, {
161
- method: 'POST',
162
- headers: { 'content-type': 'application/json' },
163
- body: JSON.stringify({ text: 'agreed' }),
164
- });
165
- expect(reply.status).not.toBe(403);
166
-
167
- // An annotation is drawn ON the design and versioned with it — the matrix
168
- // files it with `edit`, and that distinction has to survive this change.
169
- const annotate = await fetch(`${base}/_api/annotations`, {
170
- method: 'PUT',
171
- headers: { 'content-type': 'application/json' },
172
- body: JSON.stringify({ file: 'ui/Sample.tsx', svg: '<svg/>' }),
173
- });
174
- expect(annotate.status).toBe(403);
175
- });
176
-
177
- test('the reply pattern does not open the whole comment namespace', async () => {
178
- const { base } = await boot('viewer');
179
- for (const path of [
180
- '/_api/comments/abc/delete',
181
- '/_api/comments/abc/reply/../../edit-text',
182
- '/_api/comments//reply',
183
- ]) {
184
- const res = await fetch(`${base}${path}`, { method: 'POST' });
185
- expect(res.status).not.toBe(200);
186
- }
187
- });
188
- });
@@ -1,95 +0,0 @@
1
- // The client learns it is a viewer at BOOT — Cloud Phase 25 C2.
2
- //
3
- // The alternative is learning it from the first refused write, which means
4
- // drawing somebody an editor and then taking it away. So the role the
5
- // workspace vouched for at sign-in is persisted with the credential and
6
- // resolved before the UI paints.
7
- //
8
- // This flag decides what the UI OFFERS. It is never what stops a write: the
9
- // cell enforces that (Phase 25 C1 — a read-only peer token, Hocuspocus
10
- // dropping SyncStep2/Update, one HTTP gate), and it holds whatever a patched
11
- // client believes about itself.
12
-
13
- import { describe, expect, test } from 'bun:test';
14
- import { mkdtempSync, readFileSync, rmSync } from 'node:fs';
15
- import { tmpdir } from 'node:os';
16
- import { join } from 'node:path';
17
-
18
- import { saveHubCredential } from '../sync/hub-link.ts';
19
- import { signInToWorkspace } from '../sync/workspace-signin.ts';
20
-
21
- function withHome<T>(fn: (home: string) => T): T {
22
- const home = mkdtempSync(join(tmpdir(), 'maude-ro-'));
23
- const prevXdg = process.env.XDG_CONFIG_HOME;
24
- process.env.XDG_CONFIG_HOME = home;
25
- try {
26
- return fn(home);
27
- } finally {
28
- if (prevXdg === undefined) delete process.env.XDG_CONFIG_HOME;
29
- else process.env.XDG_CONFIG_HOME = prevXdg;
30
- rmSync(home, { recursive: true, force: true });
31
- }
32
- }
33
-
34
- describe('the vouched role is persisted with the credential', () => {
35
- test('a viewer is recorded as one', () => {
36
- withHome((home) => {
37
- saveHubCredential('https://p.cloud.maude.sh', 'mau_tok', 'viewer');
38
- const cfg = JSON.parse(readFileSync(join(home, 'maude', 'hubs.json'), 'utf8'));
39
- expect(cfg.hubs['https://p.cloud.maude.sh'].role).toBe('viewer');
40
- });
41
- });
42
-
43
- test('a credential written without a role stays write-capable', () => {
44
- // The upgrade promise: nobody is demoted by shipping this.
45
- withHome((home) => {
46
- saveHubCredential('https://p.cloud.maude.sh', 'mau_tok');
47
- const cfg = JSON.parse(readFileSync(join(home, 'maude', 'hubs.json'), 'utf8'));
48
- expect(cfg.hubs['https://p.cloud.maude.sh'].role).toBeUndefined();
49
- });
50
- });
51
- });
52
-
53
- describe('sign-in carries the role through to the credential', () => {
54
- const okHub = (role: string) =>
55
- (async (url: string) => {
56
- const u = String(url);
57
- if (u.endsWith('/health')) return Response.json({ ok: true, version: '1.0.0' });
58
- return Response.json({ token: 'mau_minted', user: { email: 'v@example.com', role } });
59
- }) as unknown as typeof fetch;
60
-
61
- test('a viewer sign-in saves the role, and reports it back', async () => {
62
- const saved: Array<[string, string, string | undefined]> = [];
63
- const res = await signInToWorkspace(
64
- { url: 'https://p.cloud.maude.sh', email: 'v@example.com', password: 'x'.repeat(12) },
65
- {
66
- save: (u, t, r) => saved.push([u, t, r]),
67
- fetchImpl: okHub('viewer'),
68
- }
69
- );
70
- expect(res.status).toBe(200);
71
- expect(saved[0][2]).toBe('viewer');
72
- expect((res.json as { user: { role: string } }).user.role).toBe('viewer');
73
- });
74
-
75
- test('a hub that names no role is treated as member, never as viewer', async () => {
76
- // Failing OPEN on the UI flag is correct: the cell is what refuses a
77
- // write, so a wrong guess here costs a redundant affordance, never
78
- // access. Guessing "viewer" instead would lock out every self-hoster
79
- // whose hub predates this field.
80
- const saved: Array<[string, string, string | undefined]> = [];
81
- const res = await signInToWorkspace(
82
- { url: 'https://hub.example.com', email: 'm@example.com', password: 'x'.repeat(12) },
83
- {
84
- save: (u, t, r) => saved.push([u, t, r]),
85
- fetchImpl: (async (url: string) => {
86
- const u = String(url);
87
- if (u.endsWith('/health')) return Response.json({ ok: true, version: '1.0.0' });
88
- return Response.json({ token: 'mau_minted', user: { email: 'm@example.com' } });
89
- }) as unknown as typeof fetch,
90
- }
91
- );
92
- expect(res.status).toBe(200);
93
- expect(saved[0][2]).toBe('member');
94
- });
95
- });
@@ -1,337 +0,0 @@
1
- // Readiness probe (DDR-128) — plugin-registry scan + PATH resolution. The
2
- // login-shell fallback's full correctness is proven on the packaged `.app`
3
- // (native-verification ceiling); here we cover the registry-scan branches, the
4
- // no-throw contracts, and the report shape. The probe is async (the fallback shells
5
- // out off the event loop — DDR-128 hardening), so everything awaits.
6
-
7
- import { describe, expect, test } from 'bun:test';
8
- import { existsSync, mkdirSync, mkdtempSync, writeFileSync } from 'node:fs';
9
- import { tmpdir } from 'node:os';
10
- import { join } from 'node:path';
11
-
12
- import { probeReadiness, resolveOnPath } from '../readiness.ts';
13
-
14
- const MARKET_OK = {
15
- maude: { source: { source: 'github', repo: '1aGh/maude' }, installLocation: '/x' },
16
- };
17
- const INSTALLED_BOTH = {
18
- version: 2,
19
- plugins: {
20
- 'design@maude': [{ scope: 'user', version: '0.29.0' }],
21
- 'flow@maude': [{ scope: 'user', version: '0.29.0' }],
22
- },
23
- };
24
- const INSTALLED_DESIGN_ONLY = { version: 2, plugins: { 'design@maude': [{ scope: 'user' }] } };
25
- // `/flow` is no longer part of the chat (2026-07-03) — the gate tracks `design`
26
- // alone, so a registry with flow-but-not-design is the "design missing" case.
27
- const INSTALLED_FLOW_ONLY = { version: 2, plugins: { 'flow@maude': [{ scope: 'user' }] } };
28
-
29
- function fixtureClaudeDir(opts: { markets?: unknown; installed?: unknown }): string {
30
- const dir = mkdtempSync(join(tmpdir(), 'maude-readiness-'));
31
- const pluginsDir = join(dir, 'plugins');
32
- mkdirSync(pluginsDir, { recursive: true });
33
- if (opts.markets !== undefined)
34
- writeFileSync(join(pluginsDir, 'known_marketplaces.json'), JSON.stringify(opts.markets));
35
- if (opts.installed !== undefined)
36
- writeFileSync(join(pluginsDir, 'installed_plugins.json'), JSON.stringify(opts.installed));
37
- return dir;
38
- }
39
-
40
- async function withClaudeDir<T>(dir: string, fn: () => Promise<T> | T): Promise<T> {
41
- const saved = process.env.CLAUDE_CONFIG_DIR;
42
- process.env.CLAUDE_CONFIG_DIR = dir;
43
- try {
44
- return await fn();
45
- } finally {
46
- if (saved === undefined) delete process.env.CLAUDE_CONFIG_DIR;
47
- else process.env.CLAUDE_CONFIG_DIR = saved;
48
- }
49
- }
50
-
51
- /** Plugins row under the given ~/.claude fixture. `nativeBootstrap:false` sets
52
- * MAUDE_NO_PLUGIN_BOOTSTRAP to force the web / manual (DDR-128) path — needed to
53
- * isolate the registry scan, since this suite runs in the dev tree where the
54
- * bundled plugin dirs resolve and DDR-143 auto-load would otherwise mask it. */
55
- const pluginsItem = async (dir: string, opts: { nativeBootstrap?: boolean } = {}) => {
56
- const savedOptOut = process.env.MAUDE_NO_PLUGIN_BOOTSTRAP;
57
- if (opts.nativeBootstrap === false) process.env.MAUDE_NO_PLUGIN_BOOTSTRAP = '1';
58
- try {
59
- return (await withClaudeDir(dir, () => probeReadiness())).items.find(
60
- (i) => i.id === 'plugins'
61
- )!;
62
- } finally {
63
- if (savedOptOut === undefined) delete process.env.MAUDE_NO_PLUGIN_BOOTSTRAP;
64
- else process.env.MAUDE_NO_PLUGIN_BOOTSTRAP = savedOptOut;
65
- }
66
- };
67
-
68
- // Registry-scan branches under the WEB / manual path (auto-bootstrap opted out) —
69
- // the pure DDR-128 detect-and-guide behavior, still what a `maude design serve`
70
- // user sees.
71
- describe('readiness — plugin registry scan (web / manual path)', () => {
72
- test('design@maude + marketplace present → plugins item is present, no remediation', async () => {
73
- const item = await pluginsItem(
74
- fixtureClaudeDir({ markets: MARKET_OK, installed: INSTALLED_DESIGN_ONLY }),
75
- { nativeBootstrap: false }
76
- );
77
- expect(item.status).toBe('present');
78
- expect(item.remediation).toBeUndefined();
79
- });
80
-
81
- test('design@maude NOT installed → missing, names the absent design@maude + offers the fix', async () => {
82
- // flow-only registry: `/flow` is irrelevant to the chat now, so this reads as
83
- // "design missing" — the gate tracks design alone.
84
- const item = await pluginsItem(
85
- fixtureClaudeDir({ markets: MARKET_OK, installed: INSTALLED_FLOW_ONLY }),
86
- { nativeBootstrap: false }
87
- );
88
- expect(item.status).toBe('missing');
89
- expect(item.detail).toContain('design@maude');
90
- expect(item.remediation).toContain('/plugin install');
91
- });
92
-
93
- test('registry absent → unknown (Claude Code internal contract; never throws)', async () => {
94
- const item = await pluginsItem(fixtureClaudeDir({}), { nativeBootstrap: false });
95
- expect(item.status).toBe('unknown');
96
- });
97
-
98
- test('plugin presence is the gate — a foreign-repo marketplace does not change a design-installed verdict', async () => {
99
- const item = await pluginsItem(
100
- fixtureClaudeDir({
101
- markets: { other: { source: { repo: 'someone/else' } } },
102
- installed: INSTALLED_BOTH,
103
- }),
104
- { nativeBootstrap: false }
105
- );
106
- expect(item.status).toBe('present');
107
- });
108
- });
109
-
110
- // DDR-143 / DDR-168 — on the native/desktop path the ACP session ALWAYS loads
111
- // the bundled plugins, so the row is satisfied even against a pristine
112
- // registry, AND even when a marketplace copy is also installed at the user
113
- // level (DDR-168 removed the old scan-gated no-op — the bundled copy wins
114
- // regardless of disk state). This suite runs in the dev tree, so the bundled
115
- // plugin dirs resolve → native context is on by default (no opt-out).
116
- describe('readiness — plugin auto-bootstrap (native, DDR-143/DDR-168)', () => {
117
- test('pristine registry → present + "Bundled with this app" detail, no manual remediation', async () => {
118
- const item = await pluginsItem(fixtureClaudeDir({}));
119
- expect(item.status).toBe('present');
120
- expect(item.detail).toContain('Bundled with this app');
121
- expect(item.remediation).toBeUndefined();
122
- });
123
-
124
- test('design NOT installed (flow-only registry) → still present (design is bundled, no red wall)', async () => {
125
- const item = await pluginsItem(
126
- fixtureClaudeDir({ markets: MARKET_OK, installed: INSTALLED_FLOW_ONLY })
127
- );
128
- expect(item.status).toBe('present');
129
- expect(item.detail).toContain('Bundled with this app');
130
- expect(item.remediation).toBeUndefined();
131
- });
132
-
133
- test('design ALSO installed on disk → present, STILL reads as bundled (DDR-168 — no more "is installed" framing)', async () => {
134
- const item = await pluginsItem(
135
- fixtureClaudeDir({ markets: MARKET_OK, installed: INSTALLED_DESIGN_ONLY })
136
- );
137
- expect(item.status).toBe('present');
138
- expect(item.detail).toContain('Bundled with this app');
139
- });
140
- });
141
-
142
- // Issue #107 — the `claude` row must never assert "not signed in" about a CLI
143
- // whose auth status it could not READ. That lie is what a mise-shim user hit:
144
- // signed in in the terminal, told otherwise by the app, and routed to a Sign-in
145
- // button whose poll re-ran the same failing probe until it timed out.
146
- describe('readiness — claude row, unreadable auth status (#107)', () => {
147
- const claudeItem = async (bin: string) => {
148
- const saved = process.env.MAUDE_CLAUDE_BIN;
149
- process.env.MAUDE_CLAUDE_BIN = bin;
150
- try {
151
- return (await probeReadiness()).items.find((i) => i.id === 'claude')!;
152
- } finally {
153
- if (saved === undefined) delete process.env.MAUDE_CLAUDE_BIN;
154
- else process.env.MAUDE_CLAUDE_BIN = saved;
155
- }
156
- };
157
-
158
- test('resolvable CLI + unreadable status → unknown, not a "not signed in" claim', async () => {
159
- // `sh` resolves as a binary but answers nothing useful to `auth status --json`.
160
- const item = await claudeItem('/bin/sh');
161
- expect(item.status).toBe('unknown');
162
- expect(item.detail).not.toContain('not signed in');
163
- expect(item.detail).toContain("couldn't read");
164
- });
165
-
166
- test('the remediation names the wrapper cause but keeps the path OUT of the copyable sentence', async () => {
167
- const item = await claudeItem('/bin/sh');
168
- expect(item.remediation ?? '').toMatch(/shim|wrapper/i);
169
- // The row has a Copy affordance and `copyFix` strips backticks, so a path
170
- // with shell metacharacters would ride onto the clipboard inside a sentence
171
- // the user is told to paste — and it would turn the possibly-hijacked binary
172
- // into an instruction to RUN it (attacker R2-2). It renders on its own line.
173
- expect(item.remediation ?? '').not.toContain('/bin/sh');
174
- // Nor does it coach an un-content-pinned PATH override at a confused user
175
- // (attacker F4 — Maude would be clipboard-loading the incantation).
176
- expect(item.remediation ?? '').not.toContain('MAUDE_CLAUDE_BIN');
177
- });
178
-
179
- test('the resolved path stays visible in the unknown state (DDR-166 Decision 3)', async () => {
180
- // The row must still say WHICH binary it resolved — that display is the
181
- // control against an invisible PATH hijack, and the unknown state is exactly
182
- // where a hijack is most likely.
183
- expect((await claudeItem('/bin/sh')).resolvedPath).toBe('/bin/sh');
184
- });
185
-
186
- test('no Sign-in button offered — its success signal is the probe that just failed', async () => {
187
- expect((await claudeItem('/bin/sh')).action).toBeUndefined();
188
- });
189
-
190
- test('unknown still leaves the report not-ready (claude is a required item)', async () => {
191
- const saved = process.env.MAUDE_CLAUDE_BIN;
192
- process.env.MAUDE_CLAUDE_BIN = '/bin/sh';
193
- try {
194
- expect((await probeReadiness()).ready).toBe(false);
195
- } finally {
196
- if (saved === undefined) delete process.env.MAUDE_CLAUDE_BIN;
197
- else process.env.MAUDE_CLAUDE_BIN = saved;
198
- }
199
- });
200
-
201
- // Attacker R2-3 — a metered user could suppress DDR-123's billing warning by
202
- // OMITTING apiProvider rather than forging 'firstParty'. Absent is not
203
- // first-party; over-warning is the safe direction.
204
- test('an absent apiProvider still trips the metered-billing warning', async () => {
205
- const noisy = join(import.meta.dir, 'fixtures', 'noisy-claude-auth.mjs');
206
- const saved = process.env.MAUDE_CLAUDE_BIN;
207
- process.env.MAUDE_CLAUDE_BIN = noisy;
208
- process.env.FAKE_CLAUDE_OUT = '{"loggedIn":true}'; // signed in, no provider field
209
- try {
210
- const item = (await probeReadiness()).items.find((i) => i.id === 'claude')!;
211
- expect(item.status).toBe('present');
212
- expect(item.detail).toContain('may bill metered API usage');
213
- expect(item.detail).not.toContain('Pro/Max subscription.');
214
- } finally {
215
- delete process.env.FAKE_CLAUDE_OUT;
216
- if (saved === undefined) delete process.env.MAUDE_CLAUDE_BIN;
217
- else process.env.MAUDE_CLAUDE_BIN = saved;
218
- }
219
- });
220
-
221
- test('an explicit firstParty still reads as on-subscription, with no warning', async () => {
222
- const noisy = join(import.meta.dir, 'fixtures', 'noisy-claude-auth.mjs');
223
- const saved = process.env.MAUDE_CLAUDE_BIN;
224
- process.env.MAUDE_CLAUDE_BIN = noisy;
225
- process.env.FAKE_CLAUDE_OUT =
226
- '{"loggedIn":true,"apiProvider":"firstParty","subscriptionType":"max"}';
227
- try {
228
- const item = (await probeReadiness()).items.find((i) => i.id === 'claude')!;
229
- expect(item.detail).toContain('Pro/Max subscription');
230
- expect(item.detail).not.toContain('metered');
231
- } finally {
232
- delete process.env.FAKE_CLAUDE_OUT;
233
- if (saved === undefined) delete process.env.MAUDE_CLAUDE_BIN;
234
- else process.env.MAUDE_CLAUDE_BIN = saved;
235
- }
236
- });
237
-
238
- test('a signed-out CLI is still reported as signed out, with the Sign-in action intact', async () => {
239
- const saved = process.env.MAUDE_E2E_FORCE_CLAUDE_STATUS;
240
- process.env.MAUDE_E2E_FORCE_CLAUDE_STATUS = 'signed-out';
241
- try {
242
- const item = (await probeReadiness()).items.find((i) => i.id === 'claude')!;
243
- expect(item.status).toBe('missing');
244
- expect(item.detail).toContain('not signed in');
245
- expect(item.action).toBe('signin');
246
- } finally {
247
- if (saved === undefined) delete process.env.MAUDE_E2E_FORCE_CLAUDE_STATUS;
248
- else process.env.MAUDE_E2E_FORCE_CLAUDE_STATUS = saved;
249
- }
250
- });
251
- });
252
-
253
- describe('readiness — resolveOnPath', () => {
254
- test('finds a binary that is on PATH', async () => {
255
- const hit = await resolveOnPath('sh');
256
- expect(hit).not.toBeNull();
257
- expect(existsSync(hit!)).toBe(true);
258
- });
259
-
260
- test('returns null for a bogus binary without throwing', async () => {
261
- expect(await resolveOnPath('maude-definitely-not-real-xyz')).toBeNull();
262
- });
263
-
264
- test('login-shell fallback recovers a binary hidden from the app PATH', async () => {
265
- const saved = process.env.PATH;
266
- try {
267
- // Simulate the truncated/empty GUI env so Bun.which misses and the fallback fires.
268
- process.env.PATH = '';
269
- const hit = await resolveOnPath('sh');
270
- // When a login shell is usable it must recover an absolute, real path. In a
271
- // sandbox with no usable login shell the fallback returns null — we don't fail
272
- // on that, but a non-null result must be a real absolute path.
273
- if (hit !== null) {
274
- expect(hit.startsWith('/')).toBe(true);
275
- expect(existsSync(hit)).toBe(true);
276
- }
277
- } finally {
278
- if (saved === undefined) delete process.env.PATH;
279
- else process.env.PATH = saved;
280
- }
281
- });
282
- });
283
-
284
- describe('readiness — report shape', () => {
285
- test('always returns the five items with stable ids and a boolean ready', async () => {
286
- const report = await probeReadiness();
287
- expect(report.items.map((i) => i.id)).toEqual([
288
- 'claude',
289
- 'maude',
290
- 'plugins',
291
- 'agent-browser',
292
- 'adapter',
293
- ]);
294
- expect(typeof report.ready).toBe('boolean');
295
- });
296
-
297
- test('screenshot engine: required + present (bundled) on native → never blocks ready', async () => {
298
- // Native (this dev tree): agent-browser is bundled, so the row is first-class
299
- // (required) but PRESENT — it can't turn `ready` false.
300
- const ab = (await probeReadiness()).items.find((i) => i.id === 'agent-browser')!;
301
- expect(ab.required).toBe(true);
302
- expect(ab.status).toBe('present');
303
- });
304
-
305
- test('screenshot engine: optional on the web/opt-out path', async () => {
306
- const saved = process.env.MAUDE_NO_PLUGIN_BOOTSTRAP;
307
- process.env.MAUDE_NO_PLUGIN_BOOTSTRAP = '1'; // force native=false
308
- try {
309
- const ab = (await probeReadiness()).items.find((i) => i.id === 'agent-browser')!;
310
- expect(ab.required).toBe(false);
311
- } finally {
312
- if (saved === undefined) delete process.env.MAUDE_NO_PLUGIN_BOOTSTRAP;
313
- else process.env.MAUDE_NO_PLUGIN_BOOTSTRAP = saved;
314
- }
315
- });
316
-
317
- test('adapter row tracks the real chat gate (resolveAdapterEntry) and is required', async () => {
318
- const saved = process.env.MAUDE_ACP_ADAPTER_ENTRY;
319
- try {
320
- // Point the override at a real file → the bridge is "bundled".
321
- process.env.MAUDE_ACP_ADAPTER_ENTRY = import.meta.path;
322
- const present = (await probeReadiness()).items.find((i) => i.id === 'adapter')!;
323
- expect(present.required).toBe(true);
324
- expect(present.status).toBe('present');
325
-
326
- // Point it at a missing path → the row goes missing with remediation, the
327
- // honest signal the v0.31–0.32 builds lacked (all other rows green, chat dead).
328
- process.env.MAUDE_ACP_ADAPTER_ENTRY = join(tmpdir(), 'no-such-acp-adapter-xyz');
329
- const missing = (await probeReadiness()).items.find((i) => i.id === 'adapter')!;
330
- expect(missing.status).toBe('missing');
331
- expect(missing.remediation).toBeDefined();
332
- } finally {
333
- if (saved === undefined) delete process.env.MAUDE_ACP_ADAPTER_ENTRY;
334
- else process.env.MAUDE_ACP_ADAPTER_ENTRY = saved;
335
- }
336
- });
337
- });