@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.
- package/apps/studio/acp/bridge.ts +18 -13
- package/apps/studio/acp/transcript-io.ts +394 -0
- package/apps/studio/acp/transcript.ts +103 -38
- package/apps/studio/api.ts +139 -26
- package/apps/studio/bin/_import-figma.mjs +57 -11
- package/apps/studio/bin/_seed-fixture.mjs +163 -0
- package/apps/studio/canvas-imports.ts +89 -0
- package/apps/studio/client/app.jsx +257 -7
- package/apps/studio/client/export-center.jsx +15 -7
- package/apps/studio/client/panels/SyncPanel.jsx +125 -4
- package/apps/studio/client/report-bug.jsx +19 -1
- package/apps/studio/client/styles/3-shell-maude.css +34 -0
- package/apps/studio/client/styles/4-components.css +26 -0
- package/apps/studio/collab/persistence.ts +132 -16
- package/apps/studio/collab/registry.ts +10 -18
- package/apps/studio/debug-bundle.ts +13 -0
- package/apps/studio/dist/client.bundle.js +1128 -1128
- package/apps/studio/dist/styles.css +1 -1
- package/apps/studio/export-dialog.tsx +54 -0
- package/apps/studio/exporters/degraded.ts +19 -3
- package/apps/studio/exporters/ghostscript.ts +194 -0
- package/apps/studio/exporters/pdf-fonts.ts +261 -0
- package/apps/studio/exporters/pdf.ts +152 -3
- package/apps/studio/exporters/remote.ts +36 -1
- package/apps/studio/fs-watch.ts +45 -1
- package/apps/studio/http.ts +7 -1
- package/apps/studio/mem.ts +45 -9
- package/apps/studio/server.ts +11 -4
- package/apps/studio/sync/agent.ts +136 -29
- package/apps/studio/sync/asset-push.ts +1 -47
- package/apps/studio/sync/codec.ts +122 -31
- package/apps/studio/sync/cold-start-apply.ts +7 -0
- package/apps/studio/sync/cold-start.ts +118 -22
- package/apps/studio/sync/comment-identity.ts +68 -0
- package/apps/studio/sync/connection-state.ts +15 -1
- package/apps/studio/sync/file-ledger.ts +117 -0
- package/apps/studio/sync/file-plane.ts +623 -27
- package/apps/studio/sync/index.ts +711 -20
- package/apps/studio/sync/journal-client.ts +13 -1
- package/apps/studio/sync/migrate-seed.ts +40 -3
- package/apps/studio/sync/presentation.ts +26 -3
- package/apps/studio/sync/retry-after.ts +169 -0
- package/apps/studio/sync/seed-progress.ts +218 -0
- package/apps/studio/sync/status.ts +122 -5
- package/apps/studio/sync/transport-error.ts +111 -0
- package/apps/studio/whats-new.json +72 -0
- package/cli/bin/maude.mjs +2 -0
- package/cli/commands/codex.mjs +191 -0
- package/cli/commands/harness.mjs +1051 -0
- package/cli/commands/help.mjs +13 -0
- package/cli/lib/design-link.mjs +190 -2
- package/cli/lib/harness/capabilities.mjs +302 -0
- package/cli/lib/harness/codex-runtime.mjs +889 -0
- package/cli/lib/harness/compatibility.mjs +27 -0
- package/cli/lib/harness/discover-claude.mjs +1393 -0
- package/cli/lib/harness/managed-state.mjs +1296 -0
- package/cli/lib/harness/model.mjs +86 -0
- package/cli/lib/harness/secrets.mjs +390 -0
- package/cli/lib/harness/targets/codex.mjs +1141 -0
- package/cli/lib/harness/targets/opencode.mjs +807 -0
- package/cli/lib/harness/transaction.mjs +1099 -0
- package/cli/lib/hubs-config.mjs +35 -8
- package/cli/templates/harness/opencode/maude-projector.ts +58 -0
- package/docs/harness-capability-matrix.md +125 -0
- package/docs/harness-environment-projection.md +95 -0
- package/package.json +32 -9
- package/plugins/flow/.claude-plugin/config.schema.json +8 -0
- package/apps/studio/.ai/cache/_stats.json +0 -7
- package/apps/studio/bin/_agent-browser-safe.test.mjs +0 -165
- package/apps/studio/bin/_curl-local.test.mjs +0 -280
- package/apps/studio/bin/_pw-launch.test.mjs +0 -90
- package/apps/studio/bin/_smart-frames.test.mjs +0 -195
- package/apps/studio/bin/_transcribe.test.mjs +0 -80
- package/apps/studio/bun.lock +0 -928
- package/apps/studio/draw/test/animate.test.ts +0 -172
- package/apps/studio/draw/test/brush.test.ts +0 -213
- package/apps/studio/draw/test/composition.test.ts +0 -141
- package/apps/studio/draw/test/geometry.test.ts +0 -199
- package/apps/studio/draw/test/gradient.test.ts +0 -167
- package/apps/studio/draw/test/layout.test.ts +0 -120
- package/apps/studio/draw/test/morph.test.ts +0 -107
- package/apps/studio/draw/test/optimize.test.ts +0 -40
- package/apps/studio/draw/test/palette.test.ts +0 -123
- package/apps/studio/draw/test/primitives.test.ts +0 -129
- package/apps/studio/draw/test/serialize-animate.test.ts +0 -142
- package/apps/studio/draw/test/serialize.test.ts +0 -105
- package/apps/studio/figma/assets.test.ts +0 -464
- package/apps/studio/figma/client.test.ts +0 -395
- package/apps/studio/figma/codegen-client.test.ts +0 -276
- package/apps/studio/figma/codegen-fonts.test.ts +0 -103
- package/apps/studio/figma/codegen-values.test.ts +0 -179
- package/apps/studio/figma/comments-to-strokes.test.ts +0 -194
- package/apps/studio/figma/fig-decode.test.ts +0 -788
- package/apps/studio/figma/fig-differential.test.ts +0 -182
- package/apps/studio/figma/fig-translator.test.ts +0 -192
- package/apps/studio/figma/fig-vector.test.ts +0 -113
- package/apps/studio/figma/from-codegen.test.ts +0 -408
- package/apps/studio/figma/sanitize.test.ts +0 -325
- package/apps/studio/figma/tailwind-map.test.ts +0 -142
- package/apps/studio/figma/to-artboard.test.ts +0 -808
- package/apps/studio/figma/to-render.test.ts +0 -180
- package/apps/studio/figma/to-strokes-roundtrip.test.ts +0 -152
- package/apps/studio/figma/to-strokes.test.ts +0 -705
- package/apps/studio/figma/to-tokens.test.ts +0 -321
- package/apps/studio/figma/url.test.ts +0 -167
- package/apps/studio/footage/schema.test.ts +0 -287
- package/apps/studio/generation/adapters/groq.test.ts +0 -80
- package/apps/studio/generation/audio-library.test.ts +0 -95
- package/apps/studio/generation/captions.test.ts +0 -78
- package/apps/studio/generation/elevenlabs.test.ts +0 -223
- package/apps/studio/generation/gemini.test.ts +0 -350
- package/apps/studio/generation/jobs.test.ts +0 -128
- package/apps/studio/generation/keys.test.ts +0 -75
- package/apps/studio/generation/prefs.test.ts +0 -95
- package/apps/studio/generation/types.test.ts +0 -123
- package/apps/studio/generation/whisper-models.test.ts +0 -179
- package/apps/studio/test/_helpers.ts +0 -108
- package/apps/studio/test/acp-activity-endpoint.test.ts +0 -183
- package/apps/studio/test/acp-activity.test.ts +0 -154
- package/apps/studio/test/acp-ai-activity.test.ts +0 -182
- package/apps/studio/test/acp-attachment-serve.test.ts +0 -74
- package/apps/studio/test/acp-bootstrap-brief.test.ts +0 -178
- package/apps/studio/test/acp-branch-guard.test.ts +0 -123
- package/apps/studio/test/acp-bridge-lifetime.test.ts +0 -475
- package/apps/studio/test/acp-bridge.test.ts +0 -545
- package/apps/studio/test/acp-capabilities.test.ts +0 -123
- package/apps/studio/test/acp-caps-bridge.test.ts +0 -279
- package/apps/studio/test/acp-commands.test.ts +0 -119
- package/apps/studio/test/acp-elicitation-bridge.test.ts +0 -480
- package/apps/studio/test/acp-elicitation.test.ts +0 -251
- package/apps/studio/test/acp-env.test.ts +0 -95
- package/apps/studio/test/acp-mode-banner.test.ts +0 -45
- package/apps/studio/test/acp-origin-gate.test.ts +0 -160
- package/apps/studio/test/acp-permission-prompt.test.ts +0 -251
- package/apps/studio/test/acp-permission.test.ts +0 -278
- package/apps/studio/test/acp-plugin-bootstrap.test.ts +0 -89
- package/apps/studio/test/acp-session-allowed-tools.test.ts +0 -235
- package/apps/studio/test/acp-session-plugins.test.ts +0 -209
- package/apps/studio/test/acp-toolgroup.test.ts +0 -76
- package/apps/studio/test/acp-transcript-view.test.ts +0 -71
- package/apps/studio/test/acp-transcript.test.ts +0 -238
- package/apps/studio/test/acp-usage-bridge.test.ts +0 -141
- package/apps/studio/test/acp-usage.test.ts +0 -143
- package/apps/studio/test/acp-write-gate.test.ts +0 -323
- package/apps/studio/test/acp-write-scope.test.ts +0 -533
- package/apps/studio/test/active-state.test.ts +0 -186
- package/apps/studio/test/activity.test.ts +0 -189
- package/apps/studio/test/ai-activity.test.ts +0 -113
- package/apps/studio/test/annotate-write.test.ts +0 -1074
- package/apps/studio/test/annotation-strokes-command.test.ts +0 -166
- package/apps/studio/test/annotations-align.test.ts +0 -88
- package/apps/studio/test/annotations-api.test.ts +0 -145
- package/apps/studio/test/annotations-bindings.test.ts +0 -262
- package/apps/studio/test/annotations-draw-modifiers.test.ts +0 -206
- package/apps/studio/test/annotations-groups.test.ts +0 -231
- package/apps/studio/test/annotations-layer.test.ts +0 -1011
- package/apps/studio/test/annotations-roundtrip.test.ts +0 -663
- package/apps/studio/test/annotations-snap.test.ts +0 -135
- package/apps/studio/test/annotations-text-format.test.ts +0 -396
- package/apps/studio/test/artboard-activity-overlay.test.tsx +0 -56
- package/apps/studio/test/artboard-guides-overlay.test.tsx +0 -152
- package/apps/studio/test/artboard-hug-commit.test.ts +0 -36
- package/apps/studio/test/artboard-kinds.test.tsx +0 -83
- package/apps/studio/test/artboard-selection-attrs.test.ts +0 -69
- package/apps/studio/test/asset-api.test.ts +0 -149
- package/apps/studio/test/assets-s3.test.ts +0 -249
- package/apps/studio/test/binary-smoke.test.ts +0 -47
- package/apps/studio/test/boot-self-heal.test.ts +0 -87
- package/apps/studio/test/browse-posture.test.tsx +0 -107
- package/apps/studio/test/bundle-smoke.test.ts +0 -46
- package/apps/studio/test/camera-reveal.test.tsx +0 -173
- package/apps/studio/test/canvas-artifacts.test.ts +0 -125
- package/apps/studio/test/canvas-build-sandbox.test.ts +0 -69
- package/apps/studio/test/canvas-build.test.ts +0 -141
- package/apps/studio/test/canvas-create-api.test.ts +0 -676
- package/apps/studio/test/canvas-cursors.test.ts +0 -164
- package/apps/studio/test/canvas-edit.test.ts +0 -470
- package/apps/studio/test/canvas-freshness-deps.test.ts +0 -64
- package/apps/studio/test/canvas-header.test.ts +0 -127
- package/apps/studio/test/canvas-hide-chrome.test.ts +0 -88
- package/apps/studio/test/canvas-hmr-runtime.test.tsx +0 -114
- package/apps/studio/test/canvas-lib-inline.test.ts +0 -146
- package/apps/studio/test/canvas-lib-motion.test.ts +0 -131
- package/apps/studio/test/canvas-lib-resolver.test.ts +0 -111
- package/apps/studio/test/canvas-list-watch.test.ts +0 -388
- package/apps/studio/test/canvas-media-drop.test.ts +0 -187
- package/apps/studio/test/canvas-meta-api.test.ts +0 -707
- package/apps/studio/test/canvas-move-api.test.ts +0 -587
- package/apps/studio/test/canvas-origin-gate.test.ts +0 -417
- package/apps/studio/test/canvas-pipeline.test.ts +0 -180
- package/apps/studio/test/canvas-rects.test.ts +0 -198
- package/apps/studio/test/canvas-reorder.test.ts +0 -211
- package/apps/studio/test/canvas-route.test.ts +0 -179
- package/apps/studio/test/canvas-shell-base.test.ts +0 -107
- package/apps/studio/test/canvas-shell-build-error.test.ts +0 -52
- package/apps/studio/test/canvas-url.test.ts +0 -138
- package/apps/studio/test/canvas-wheel-delta.test.ts +0 -42
- package/apps/studio/test/canvas-zoom-floor.test.ts +0 -73
- package/apps/studio/test/capture-core.test.ts +0 -32
- package/apps/studio/test/capture-determinism-shape.test.ts +0 -135
- package/apps/studio/test/chat-attachments.test.ts +0 -117
- package/apps/studio/test/chat-context.test.ts +0 -129
- package/apps/studio/test/chat-markdown.test.tsx +0 -58
- package/apps/studio/test/clip-addressing.test.ts +0 -738
- package/apps/studio/test/clip-ops.test.ts +0 -922
- package/apps/studio/test/cloud-build.test.ts +0 -95
- package/apps/studio/test/cloud-connect-note.test.ts +0 -135
- package/apps/studio/test/cloud-endpoints.test.ts +0 -590
- package/apps/studio/test/cloud-history-hardening.test.ts +0 -165
- package/apps/studio/test/cloud-history-posture.test.ts +0 -230
- package/apps/studio/test/cloud-managed-save-surfaces.test.ts +0 -90
- package/apps/studio/test/cloud-renew.test.ts +0 -205
- package/apps/studio/test/cloud-session-role.test.ts +0 -161
- package/apps/studio/test/cloud-shell-surfaces.test.ts +0 -170
- package/apps/studio/test/cold-start-apply.test.ts +0 -303
- package/apps/studio/test/collab-annotations-bridge.test.ts +0 -55
- package/apps/studio/test/collab-awareness-bridge.test.ts +0 -223
- package/apps/studio/test/collab-bridge.test.ts +0 -79
- package/apps/studio/test/collab-loopback.test.ts +0 -63
- package/apps/studio/test/collab-origin-gate.test.ts +0 -323
- package/apps/studio/test/collab-protocol.test.ts +0 -146
- package/apps/studio/test/collab-readonly-gate.test.ts +0 -194
- package/apps/studio/test/collab-reseed-guard.test.ts +0 -78
- package/apps/studio/test/collab-room.test.ts +0 -188
- package/apps/studio/test/collab-session-survival.test.tsx +0 -176
- package/apps/studio/test/collab-stress.test.ts +0 -129
- package/apps/studio/test/comment-mount.test.ts +0 -97
- package/apps/studio/test/comment-relay-origin-gate.test.ts +0 -117
- package/apps/studio/test/comments-api.test.ts +0 -277
- package/apps/studio/test/comments-fs-rebroadcast.test.ts +0 -155
- package/apps/studio/test/comments-overlay.test.ts +0 -117
- package/apps/studio/test/compile-entry.test.ts +0 -133
- package/apps/studio/test/component-map.test.ts +0 -48
- package/apps/studio/test/config-projection.test.ts +0 -104
- package/apps/studio/test/config-reload.test.ts +0 -230
- package/apps/studio/test/config-version.test.ts +0 -88
- package/apps/studio/test/context-resolve-tokens.test.ts +0 -97
- package/apps/studio/test/convert-to-absolute.test.ts +0 -333
- package/apps/studio/test/credential-grammar.test.ts +0 -107
- package/apps/studio/test/csp-canvas-shell.test.ts +0 -62
- package/apps/studio/test/csrf-write-guard.test.ts +0 -141
- package/apps/studio/test/debug-bundle.test.ts +0 -105
- package/apps/studio/test/design-setup-readiness.test.ts +0 -155
- package/apps/studio/test/detach-component.test.ts +0 -94
- package/apps/studio/test/dns-rebinding-guard.test.ts +0 -101
- package/apps/studio/test/dom-selection.test.ts +0 -198
- package/apps/studio/test/dynamic-text-edit.test.ts +0 -162
- package/apps/studio/test/edit-css-occurrence.test.ts +0 -81
- package/apps/studio/test/edit-persistence.test.ts +0 -91
- package/apps/studio/test/edit-scope-api.test.ts +0 -119
- package/apps/studio/test/edit-source-command.test.ts +0 -142
- package/apps/studio/test/edit-suppress.test.ts +0 -170
- package/apps/studio/test/editing-presence.test.ts +0 -103
- package/apps/studio/test/element-resize.test.ts +0 -136
- package/apps/studio/test/element-structural-api.test.ts +0 -434
- package/apps/studio/test/element-structural-edit.test.ts +0 -750
- package/apps/studio/test/ensure-browser.test.ts +0 -92
- package/apps/studio/test/equal-spacing-detector.test.ts +0 -257
- package/apps/studio/test/export-assemble.test.ts +0 -53
- package/apps/studio/test/export-browser-lane.test.ts +0 -126
- package/apps/studio/test/export-capture-fidelity.test.ts +0 -146
- package/apps/studio/test/export-capture-hygiene.test.ts +0 -675
- package/apps/studio/test/export-center.test.tsx +0 -287
- package/apps/studio/test/export-e2e-lanes.test.ts +0 -857
- package/apps/studio/test/export-format-scope-coherence.test.ts +0 -89
- package/apps/studio/test/export-lane.test.ts +0 -245
- package/apps/studio/test/export-shim-multi-capture.test.ts +0 -83
- package/apps/studio/test/exporters/canva.test.ts +0 -63
- package/apps/studio/test/exporters/degraded-propagation.test.ts +0 -123
- package/apps/studio/test/exporters/endpoint.test.ts +0 -121
- package/apps/studio/test/exporters/history.test.ts +0 -107
- package/apps/studio/test/exporters/html.test.ts +0 -26
- package/apps/studio/test/exporters/jobs.test.ts +0 -342
- package/apps/studio/test/exporters/pdf.test.ts +0 -85
- package/apps/studio/test/exporters/png.test.ts +0 -103
- package/apps/studio/test/exporters/pptx-deck.test.ts +0 -112
- package/apps/studio/test/exporters/pptx.test.ts +0 -31
- package/apps/studio/test/exporters/pw-launch.test.ts +0 -49
- package/apps/studio/test/exporters/runtime.test.ts +0 -59
- package/apps/studio/test/exporters/scope.test.ts +0 -292
- package/apps/studio/test/exporters/svg.test.ts +0 -29
- package/apps/studio/test/exporters/unsupported-media.test.ts +0 -123
- package/apps/studio/test/exporters/zip.test.ts +0 -104
- package/apps/studio/test/fetch-asset-gate.test.ts +0 -189
- package/apps/studio/test/figjam-v3-model.test.ts +0 -342
- package/apps/studio/test/figma-explode.test.ts +0 -438
- package/apps/studio/test/figma-provenance.test.ts +0 -108
- package/apps/studio/test/figma-routes.test.ts +0 -294
- package/apps/studio/test/file-lock.test.ts +0 -84
- package/apps/studio/test/fixtures/backgrounding-claude-auth.sh +0 -12
- package/apps/studio/test/fixtures/fake-claude-auth.mjs +0 -23
- package/apps/studio/test/fixtures/figjam-v3-groups-bindings.svg +0 -1
- package/apps/studio/test/fixtures/mock-acp-agent-caps.mjs +0 -158
- package/apps/studio/test/fixtures/mock-acp-agent-commands.mjs +0 -44
- package/apps/studio/test/fixtures/mock-acp-agent-elicit-flood.mjs +0 -46
- package/apps/studio/test/fixtures/mock-acp-agent-elicit-url.mjs +0 -42
- package/apps/studio/test/fixtures/mock-acp-agent-elicit.mjs +0 -63
- package/apps/studio/test/fixtures/mock-acp-agent-permission-flood.mjs +0 -51
- package/apps/studio/test/fixtures/mock-acp-agent-permission.mjs +0 -50
- package/apps/studio/test/fixtures/mock-acp-agent-slow.mjs +0 -45
- package/apps/studio/test/fixtures/mock-acp-agent-usage.mjs +0 -56
- package/apps/studio/test/fixtures/mock-acp-agent-wedged.mjs +0 -37
- package/apps/studio/test/fixtures/mock-acp-agent-write.mjs +0 -118
- package/apps/studio/test/fixtures/mock-acp-agent.mjs +0 -69
- package/apps/studio/test/fixtures/noisy-claude-auth.mjs +0 -27
- package/apps/studio/test/fixtures/perf-canvas.mjs +0 -201
- package/apps/studio/test/fixtures/phase-20-annotations.svg +0 -1
- package/apps/studio/test/fixtures/phase-21-annotations.svg +0 -1
- package/apps/studio/test/fixtures/tiny-subset.woff2 +0 -0
- package/apps/studio/test/fixtures/video-comp-fixture.tsx +0 -82
- package/apps/studio/test/fixtures/wedged-claude-auth.sh +0 -11
- package/apps/studio/test/footage-store.test.ts +0 -100
- package/apps/studio/test/fs-mkdir-api.test.ts +0 -248
- package/apps/studio/test/fs-watch.test.ts +0 -41
- package/apps/studio/test/gemma-models.test.ts +0 -245
- package/apps/studio/test/generate-route.test.ts +0 -106
- package/apps/studio/test/git-api.test.ts +0 -0
- package/apps/studio/test/git-branches.test.ts +0 -341
- package/apps/studio/test/git-cloud-posture.test.ts +0 -59
- package/apps/studio/test/git-grouping.test.ts +0 -106
- package/apps/studio/test/git-lifecycle.test.ts +0 -100
- package/apps/studio/test/git-log-format.test.ts +0 -95
- package/apps/studio/test/git-safe-rel.test.ts +0 -132
- package/apps/studio/test/git-system-engine.test.ts +0 -135
- package/apps/studio/test/git-watch.test.ts +0 -97
- package/apps/studio/test/github-api.test.ts +0 -577
- package/apps/studio/test/grid-track-handles.test.ts +0 -160
- package/apps/studio/test/handoff-static-frames.test.ts +0 -215
- package/apps/studio/test/handoff.test.ts +0 -329
- package/apps/studio/test/history-rollback.test.ts +0 -88
- package/apps/studio/test/hmr-broadcast.test.ts +0 -236
- package/apps/studio/test/hmr-classify.test.ts +0 -87
- package/apps/studio/test/hub-link.test.ts +0 -80
- package/apps/studio/test/identity-cache.test.ts +0 -83
- package/apps/studio/test/import-asset-browser.test.ts +0 -64
- package/apps/studio/test/import-asset-route.test.ts +0 -71
- package/apps/studio/test/import-asset.test.ts +0 -427
- package/apps/studio/test/import-brand-browser.test.ts +0 -88
- package/apps/studio/test/import-brand-route.test.ts +0 -120
- package/apps/studio/test/import-brand.test.ts +0 -206
- package/apps/studio/test/import-figma.test.ts +0 -667
- package/apps/studio/test/import-tokens.test.ts +0 -722
- package/apps/studio/test/index-data-canvas-ds.test.ts +0 -97
- package/apps/studio/test/input-router.test.ts +0 -757
- package/apps/studio/test/inspect-script-syntax.test.ts +0 -53
- package/apps/studio/test/inspect-selections.test.ts +0 -219
- package/apps/studio/test/knob-props-authored.test.ts +0 -87
- package/apps/studio/test/layers-synthetic-groups.test.ts +0 -96
- package/apps/studio/test/locator.test.ts +0 -214
- package/apps/studio/test/marquee-overlay.test.ts +0 -94
- package/apps/studio/test/media-commit-chain.test.ts +0 -210
- package/apps/studio/test/mode-toggle.test.tsx +0 -226
- package/apps/studio/test/motion-sample.test.ts +0 -35
- package/apps/studio/test/move-artboards-command.test.ts +0 -108
- package/apps/studio/test/participants-chrome.test.ts +0 -65
- package/apps/studio/test/paths.test.ts +0 -88
- package/apps/studio/test/pdf-print-boxes.test.ts +0 -359
- package/apps/studio/test/peer-selection-follows-camera.test.tsx +0 -131
- package/apps/studio/test/perf-harness.ts +0 -193
- package/apps/studio/test/phase-3.6-smoke.test.ts +0 -79
- package/apps/studio/test/photo-bg-remove-validation.test.ts +0 -71
- package/apps/studio/test/photo-canvas-bundle.test.ts +0 -79
- package/apps/studio/test/photo-edit-api.test.ts +0 -201
- package/apps/studio/test/photo-filters.test.ts +0 -149
- package/apps/studio/test/photo-pipeline.test.ts +0 -106
- package/apps/studio/test/photo-taxonomy.test.ts +0 -96
- package/apps/studio/test/print-marks.test.ts +0 -113
- package/apps/studio/test/print-units.test.ts +0 -173
- package/apps/studio/test/read-annotations.test.ts +0 -727
- package/apps/studio/test/read-only-gate.test.ts +0 -188
- package/apps/studio/test/read-only-role.test.ts +0 -95
- package/apps/studio/test/readiness.test.ts +0 -337
- package/apps/studio/test/remote-ahead-behind-cache.test.ts +0 -89
- package/apps/studio/test/reorder-api.test.ts +0 -210
- package/apps/studio/test/repo-concurrency.test.ts +0 -146
- package/apps/studio/test/repo-lock.test.ts +0 -350
- package/apps/studio/test/report-proxy.test.ts +0 -125
- package/apps/studio/test/ripple.test.ts +0 -197
- package/apps/studio/test/runtime-bundle-error-mapping.test.ts +0 -43
- package/apps/studio/test/runtime-bundle.test.ts +0 -69
- package/apps/studio/test/sanitize-annotation-svg.test.ts +0 -238
- package/apps/studio/test/scaffold-design.test.ts +0 -122
- package/apps/studio/test/server-lifecycle.test.ts +0 -38
- package/apps/studio/test/session-runtime-state.test.ts +0 -226
- package/apps/studio/test/session-scope.test.ts +0 -100
- package/apps/studio/test/shared-doc-cell-pairing.test.ts +0 -642
- package/apps/studio/test/shared-doc-convergence.test.ts +0 -265
- package/apps/studio/test/shared-doc-foundation.test.ts +0 -63
- package/apps/studio/test/shared-doc-migrate.test.ts +0 -369
- package/apps/studio/test/shared-doc-projection.test.ts +0 -238
- package/apps/studio/test/shell-importmap.test.ts +0 -56
- package/apps/studio/test/sizing-mode.test.ts +0 -102
- package/apps/studio/test/slash-commands.test.ts +0 -117
- package/apps/studio/test/snap-distance-pill.test.ts +0 -68
- package/apps/studio/test/spacing-handles.test.ts +0 -138
- package/apps/studio/test/specimen-select.test.ts +0 -88
- package/apps/studio/test/sync-agent.test.ts +0 -660
- package/apps/studio/test/sync-annotations-cold-start.test.ts +0 -302
- package/apps/studio/test/sync-asset-push.test.ts +0 -931
- package/apps/studio/test/sync-atomic-write.test.ts +0 -62
- package/apps/studio/test/sync-attach-incremental.test.ts +0 -527
- package/apps/studio/test/sync-autocommit.test.ts +0 -537
- package/apps/studio/test/sync-canvas-path.test.ts +0 -200
- package/apps/studio/test/sync-cell-write-nudge.test.ts +0 -346
- package/apps/studio/test/sync-codec.test.ts +0 -230
- package/apps/studio/test/sync-cold-seed-dedup.test.ts +0 -187
- package/apps/studio/test/sync-cold-start.test.ts +0 -304
- package/apps/studio/test/sync-connect-honesty.test.ts +0 -114
- package/apps/studio/test/sync-connection-state.test.ts +0 -337
- package/apps/studio/test/sync-ctl-channel.test.ts +0 -508
- package/apps/studio/test/sync-decide-file.test.ts +0 -420
- package/apps/studio/test/sync-doc-name.test.ts +0 -281
- package/apps/studio/test/sync-echo-guard.test.ts +0 -96
- package/apps/studio/test/sync-file-ledger.test.ts +0 -334
- package/apps/studio/test/sync-file-membership.test.ts +0 -347
- package/apps/studio/test/sync-file-plane.test.ts +0 -976
- package/apps/studio/test/sync-file-pull.test.ts +0 -333
- package/apps/studio/test/sync-fresh-link-parity.test.ts +0 -267
- package/apps/studio/test/sync-fs-mirror.test.ts +0 -181
- package/apps/studio/test/sync-hardening.test.ts +0 -586
- package/apps/studio/test/sync-hub-listing.test.ts +0 -46
- package/apps/studio/test/sync-hubs-config.test.ts +0 -133
- package/apps/studio/test/sync-incident-replay.test.ts +0 -211
- package/apps/studio/test/sync-journal.test.ts +0 -176
- package/apps/studio/test/sync-meta-codec.test.ts +0 -199
- package/apps/studio/test/sync-migrate-flat-fallback.test.ts +0 -98
- package/apps/studio/test/sync-move-retirement.test.ts +0 -231
- package/apps/studio/test/sync-panel-surface.test.ts +0 -153
- package/apps/studio/test/sync-path-pull.test.ts +0 -531
- package/apps/studio/test/sync-presentation.test.ts +0 -262
- package/apps/studio/test/sync-pull-budget.test.ts +0 -169
- package/apps/studio/test/sync-remote-docs.test.ts +0 -333
- package/apps/studio/test/sync-resync-routes.test.ts +0 -168
- package/apps/studio/test/sync-runtime.test.ts +0 -1675
- package/apps/studio/test/sync-seed-defers-to-hub.test.ts +0 -83
- package/apps/studio/test/sync-settings-routes.test.ts +0 -195
- package/apps/studio/test/sync-settings.test.ts +0 -151
- package/apps/studio/test/sync-status.test.ts +0 -261
- package/apps/studio/test/sync-supervisor.test.ts +0 -262
- package/apps/studio/test/sync-tombstone-apply.test.ts +0 -111
- package/apps/studio/test/sync-trash.test.ts +0 -132
- package/apps/studio/test/sync-two-peer-discovery.test.ts +0 -343
- package/apps/studio/test/sync-untrusted.test.ts +0 -103
- package/apps/studio/test/system-endpoint.test.ts +0 -144
- package/apps/studio/test/text-editability-stamp.test.ts +0 -131
- package/apps/studio/test/timeline-comp-target.test.ts +0 -139
- package/apps/studio/test/timeline-media-cache.test.ts +0 -41
- package/apps/studio/test/timeline-parse.test.ts +0 -184
- package/apps/studio/test/timeline-scale.test.ts +0 -65
- package/apps/studio/test/timeline-snap.test.ts +0 -85
- package/apps/studio/test/tool-palette-insert-anchor.test.ts +0 -86
- package/apps/studio/test/tool-palette.test.tsx +0 -55
- package/apps/studio/test/tour-overlay.test.tsx +0 -90
- package/apps/studio/test/trusted-request-host.test.ts +0 -66
- package/apps/studio/test/undo-sequence-byte-compare.test.ts +0 -211
- package/apps/studio/test/undo-stack.test.ts +0 -244
- package/apps/studio/test/use-agent-presence.test.tsx +0 -117
- package/apps/studio/test/use-annotation-resize.test.ts +0 -402
- package/apps/studio/test/use-annotation-selection.test.tsx +0 -77
- package/apps/studio/test/use-artboard-drag.test.ts +0 -324
- package/apps/studio/test/use-canvas-activity.test.tsx +0 -206
- package/apps/studio/test/use-collab.test.ts +0 -0
- package/apps/studio/test/use-cursor-modifiers.test.ts +0 -59
- package/apps/studio/test/use-keyboard-discipline.test.ts +0 -27
- package/apps/studio/test/use-selection-set.test.tsx +0 -166
- package/apps/studio/test/use-snap-guides.test.ts +0 -271
- package/apps/studio/test/use-tool-mode.test.tsx +0 -186
- package/apps/studio/test/use-undo-stack.test.tsx +0 -223
- package/apps/studio/test/video-asset.test.ts +0 -163
- package/apps/studio/test/video-comp-fixture.test.ts +0 -50
- package/apps/studio/test/video-comp.test.ts +0 -270
- package/apps/studio/test/video-encode-lib.test.ts +0 -63
- package/apps/studio/test/video-render-bridge.test.ts +0 -171
- package/apps/studio/test/whats-new.test.ts +0 -113
- package/apps/studio/test/whisper-setup.test.ts +0 -97
- package/apps/studio/test/workspace-containment.test.ts +0 -382
- package/apps/studio/test/workspace-signin.test.ts +0 -292
- package/apps/studio/test/ws-handshake.test.ts +0 -33
- package/cli/commands/cache.test.mjs +0 -166
- package/cli/commands/design-link.test.mjs +0 -419
- package/cli/commands/design.test.mjs +0 -240
- package/cli/commands/doctor.test.mjs +0 -237
- package/cli/commands/hub-workspace.test.mjs +0 -275
- package/cli/commands/hub.test.mjs +0 -225
- package/cli/commands/kg.test.mjs +0 -142
- package/cli/commands/preflight.test.mjs +0 -46
- package/cli/lib/argv.test.mjs +0 -46
- package/cli/lib/cache.test.mjs +0 -303
- package/cli/lib/cell-plan.test.mjs +0 -225
- package/cli/lib/config-lint.test.mjs +0 -117
- package/cli/lib/ddr-to-kgai.test.mjs +0 -118
- package/cli/lib/design-ownership.test.mjs +0 -329
- package/cli/lib/fetch-asset.test.mjs +0 -213
- package/cli/lib/figma-codegen-reachability.test.mjs +0 -104
- package/cli/lib/figma-import-controls.test.mjs +0 -70
- package/cli/lib/flow-design-integration.test.mjs +0 -165
- package/cli/lib/gitignore-block.test.mjs +0 -136
- package/cli/lib/gitignore-drift.test.mjs +0 -156
- package/cli/lib/hubs-config.test.mjs +0 -165
- package/cli/lib/pkg-root.test.mjs +0 -111
- package/cli/lib/plugin-cli-reachability.test.mjs +0 -57
- package/cli/lib/plugin-name-namespace.test.mjs +0 -71
- package/cli/lib/reconstruct-toolset.test.mjs +0 -194
- package/cli/lib/stack-detect.test.mjs +0 -121
- package/cli/lib/update-check.test.mjs +0 -32
- 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
|
-
});
|