@flowdular/sdk 0.1.0 → 0.2.1

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 (175) hide show
  1. package/.ai/skills/test-hardening/SKILL.md +1 -1
  2. package/README.md +69 -12
  3. package/assets/flowdular-banner.png +0 -0
  4. package/modules/agents/package.json +1 -1
  5. package/modules/auth/package.json +1 -1
  6. package/modules/auth/src/client/AuthenticationCore.tsrx +3 -1
  7. package/modules/auth/src/server/endpoints.ts +3 -1
  8. package/modules/auth/src/server/runtime.ts +4 -0
  9. package/modules/automations/package.json +1 -1
  10. package/modules/automations-workflows-integration/package.json +1 -1
  11. package/modules/profile/package.json +1 -1
  12. package/modules/sandbox/package.json +1 -1
  13. package/modules/system/package.json +1 -1
  14. package/modules/users/package.json +1 -1
  15. package/modules/workflows/package.json +1 -1
  16. package/package.json +5 -13
  17. package/packages/ai-provider/package.json +1 -1
  18. package/packages/cli-protocol/package.json +1 -1
  19. package/packages/client/package.json +1 -1
  20. package/packages/client/src/routing.ts +39 -2
  21. package/packages/client/src/shell/BrandMark.tsrx +5 -1
  22. package/packages/client/src/shell/navigation.ts +3 -1
  23. package/packages/client/src/state.ts +6 -2
  24. package/packages/contracts/package.json +1 -1
  25. package/packages/contracts/schemas/project.schema.json +16 -42
  26. package/packages/database/package.json +1 -1
  27. package/packages/database-pglite/package.json +2 -2
  28. package/packages/database-pglite/src/driver.ts +21 -3
  29. package/packages/database-testing/package.json +1 -1
  30. package/packages/dev-console/package.json +1 -1
  31. package/packages/harness/package.json +1 -1
  32. package/packages/kernel/package.json +1 -1
  33. package/packages/server/package.json +1 -1
  34. package/packages/server/src/application-routes.ts +80 -8
  35. package/packages/server/src/index.ts +4 -1
  36. package/packages/server/src/web-html.ts +9 -5
  37. package/packages/server/src/web.ts +59 -7
  38. package/packages/ui/package.json +1 -1
  39. package/packages/coding-agent/package.json +0 -44
  40. package/packages/coding-agent/src/drivers/byok.ts +0 -322
  41. package/packages/coding-agent/src/drivers/claude-code.ts +0 -290
  42. package/packages/coding-agent/src/drivers/codex.ts +0 -281
  43. package/packages/coding-agent/src/index.ts +0 -56
  44. package/packages/coding-agent/src/registry.ts +0 -119
  45. package/packages/coding-agent/src/roles/contract.ts +0 -60
  46. package/packages/coding-agent/src/roles/defaults.ts +0 -134
  47. package/packages/coding-agent/src/roles/handoff.ts +0 -41
  48. package/packages/coding-agent/src/roles/registry.ts +0 -156
  49. package/packages/coding-agent/src/roles/skills.ts +0 -72
  50. package/packages/coding-agent/src/roles/sync.ts +0 -88
  51. package/packages/coding-agent/src/types.ts +0 -102
  52. package/packages/coding-agent/src/workspace.ts +0 -275
  53. package/packages/sandbox/README.md +0 -691
  54. package/packages/sandbox/bin/flowdular-sandbox.mjs +0 -393
  55. package/packages/sandbox/dist/client/assets/App-ClMOrpyg.js +0 -11
  56. package/packages/sandbox/dist/client/assets/PreviewHost-BHiHrfbQ.js +0 -1
  57. package/packages/sandbox/dist/client/assets/auto-buGEq6de.js +0 -3
  58. package/packages/sandbox/dist/client/assets/ibm-plex-mono-cyrillic-400-normal-BSMlKf0J.woff2 +0 -0
  59. package/packages/sandbox/dist/client/assets/ibm-plex-mono-cyrillic-400-normal-CEL4l2ZJ.woff +0 -0
  60. package/packages/sandbox/dist/client/assets/ibm-plex-mono-cyrillic-500-normal-Ael50iVv.woff +0 -0
  61. package/packages/sandbox/dist/client/assets/ibm-plex-mono-cyrillic-500-normal-Bq9vWWag.woff2 +0 -0
  62. package/packages/sandbox/dist/client/assets/ibm-plex-mono-cyrillic-ext-400-normal-DMdlQ8Kv.woff +0 -0
  63. package/packages/sandbox/dist/client/assets/ibm-plex-mono-cyrillic-ext-400-normal-xuaO2J-f.woff2 +0 -0
  64. package/packages/sandbox/dist/client/assets/ibm-plex-mono-cyrillic-ext-500-normal-BIfNGwUT.woff +0 -0
  65. package/packages/sandbox/dist/client/assets/ibm-plex-mono-cyrillic-ext-500-normal-BqneJy0T.woff2 +0 -0
  66. package/packages/sandbox/dist/client/assets/ibm-plex-mono-latin-400-normal-CvHOgSBP.woff +0 -0
  67. package/packages/sandbox/dist/client/assets/ibm-plex-mono-latin-400-normal-DMJ8VG8y.woff2 +0 -0
  68. package/packages/sandbox/dist/client/assets/ibm-plex-mono-latin-500-normal-CB9ihrfo.woff +0 -0
  69. package/packages/sandbox/dist/client/assets/ibm-plex-mono-latin-500-normal-DSY6xOcd.woff2 +0 -0
  70. package/packages/sandbox/dist/client/assets/ibm-plex-mono-latin-ext-400-normal-BmRBH3aV.woff2 +0 -0
  71. package/packages/sandbox/dist/client/assets/ibm-plex-mono-latin-ext-400-normal-D3D2R8hC.woff +0 -0
  72. package/packages/sandbox/dist/client/assets/ibm-plex-mono-latin-ext-500-normal-CAhNIIs5.woff2 +0 -0
  73. package/packages/sandbox/dist/client/assets/ibm-plex-mono-latin-ext-500-normal-CZ70TYgx.woff +0 -0
  74. package/packages/sandbox/dist/client/assets/ibm-plex-mono-vietnamese-400-normal-BulugwFq.woff2 +0 -0
  75. package/packages/sandbox/dist/client/assets/ibm-plex-mono-vietnamese-400-normal-DDuiU_S-.woff +0 -0
  76. package/packages/sandbox/dist/client/assets/ibm-plex-mono-vietnamese-500-normal-C8zxqsMH.woff +0 -0
  77. package/packages/sandbox/dist/client/assets/ibm-plex-mono-vietnamese-500-normal-DZ4AoWbu.woff2 +0 -0
  78. package/packages/sandbox/dist/client/assets/ibm-plex-sans-cyrillic-ext-wght-normal-d45eAU9y.woff2 +0 -0
  79. package/packages/sandbox/dist/client/assets/ibm-plex-sans-cyrillic-wght-normal-BAAhND-U.woff2 +0 -0
  80. package/packages/sandbox/dist/client/assets/ibm-plex-sans-greek-wght-normal-CmyJS8uq.woff2 +0 -0
  81. package/packages/sandbox/dist/client/assets/ibm-plex-sans-latin-ext-wght-normal-CIII54If.woff2 +0 -0
  82. package/packages/sandbox/dist/client/assets/ibm-plex-sans-latin-wght-normal-IvpUvPa2.woff2 +0 -0
  83. package/packages/sandbox/dist/client/assets/ibm-plex-sans-vietnamese-wght-normal-Dg1JeJN0.woff2 +0 -0
  84. package/packages/sandbox/dist/client/assets/index-CvwhlQ87.js +0 -4
  85. package/packages/sandbox/dist/client/assets/styles-BPWaGf55.js +0 -3
  86. package/packages/sandbox/dist/client/assets/styles-Dx1_KOag.css +0 -1
  87. package/packages/sandbox/dist/client/favicon.svg +0 -1
  88. package/packages/sandbox/dist/server/assets/auto-CVGNYyb3.js +0 -12785
  89. package/packages/sandbox/dist/server/assets/chunk-DDJLRBDX-3OUNo51g.js +0 -4125
  90. package/packages/sandbox/dist/server/assets/nodefs-DVSI7kG0.js +0 -24
  91. package/packages/sandbox/dist/server/assets/opfs-ahp-D56iokt1.js +0 -388
  92. package/packages/sandbox/dist/server/assets/token-Bvaq7Anz.js +0 -55
  93. package/packages/sandbox/dist/server/assets/token-util-BqSvdIPQ.js +0 -3
  94. package/packages/sandbox/dist/server/assets/token-util-CktFFT4z.js +0 -494
  95. package/packages/sandbox/dist/server/entry.js +0 -91671
  96. package/packages/sandbox/dist/server/favicon.svg +0 -1
  97. package/packages/sandbox/dist/server/index.html +0 -161
  98. package/packages/sandbox/index.html +0 -159
  99. package/packages/sandbox/octane.config.ts +0 -36
  100. package/packages/sandbox/package.json +0 -83
  101. package/packages/sandbox/public/favicon.svg +0 -1
  102. package/packages/sandbox/public/og.png +0 -0
  103. package/packages/sandbox/src/App.tsrx +0 -924
  104. package/packages/sandbox/src/client/ApprovalHandoff.tsrx +0 -39
  105. package/packages/sandbox/src/client/ChatPane.tsrx +0 -694
  106. package/packages/sandbox/src/client/ConnectView.tsrx +0 -86
  107. package/packages/sandbox/src/client/DiffPane.tsrx +0 -150
  108. package/packages/sandbox/src/client/EjectModal.tsrx +0 -571
  109. package/packages/sandbox/src/client/GateResults.tsrx +0 -60
  110. package/packages/sandbox/src/client/GitHubSettingsModal.tsrx +0 -219
  111. package/packages/sandbox/src/client/NewSessionForm.tsrx +0 -214
  112. package/packages/sandbox/src/client/SessionBar.tsrx +0 -151
  113. package/packages/sandbox/src/client/SessionPicker.tsrx +0 -472
  114. package/packages/sandbox/src/client/SpecEditor.tsrx +0 -109
  115. package/packages/sandbox/src/client/SpecReviewCard.tsrx +0 -321
  116. package/packages/sandbox/src/client/WorkCard.tsrx +0 -128
  117. package/packages/sandbox/src/client/WorkbenchModal.tsrx +0 -161
  118. package/packages/sandbox/src/client/WorkspaceMenu.tsrx +0 -100
  119. package/packages/sandbox/src/client/api.ts +0 -797
  120. package/packages/sandbox/src/client/attachments.ts +0 -73
  121. package/packages/sandbox/src/client/dashboard.ts +0 -46
  122. package/packages/sandbox/src/client/github-settings.ts +0 -48
  123. package/packages/sandbox/src/client/highlight.ts +0 -105
  124. package/packages/sandbox/src/client/i18n.ts +0 -32
  125. package/packages/sandbox/src/client/locales/en.json +0 -466
  126. package/packages/sandbox/src/client/locales/pl.json +0 -466
  127. package/packages/sandbox/src/client/session-labels.ts +0 -77
  128. package/packages/sandbox/src/client/state.ts +0 -108
  129. package/packages/sandbox/src/index.ts +0 -10
  130. package/packages/sandbox/src/preview/PreviewHost.tsrx +0 -373
  131. package/packages/sandbox/src/preview/load-module.ts +0 -16
  132. package/packages/sandbox/src/server/attachments.ts +0 -308
  133. package/packages/sandbox/src/server/auto-review.ts +0 -181
  134. package/packages/sandbox/src/server/checkpoints.ts +0 -90
  135. package/packages/sandbox/src/server/config.ts +0 -460
  136. package/packages/sandbox/src/server/dashboard.ts +0 -245
  137. package/packages/sandbox/src/server/delivery/configuration.ts +0 -193
  138. package/packages/sandbox/src/server/delivery/git-pr.ts +0 -1473
  139. package/packages/sandbox/src/server/delivery/index.ts +0 -83
  140. package/packages/sandbox/src/server/delivery/local.ts +0 -151
  141. package/packages/sandbox/src/server/delivery/official-modules.ts +0 -516
  142. package/packages/sandbox/src/server/delivery/plan.ts +0 -218
  143. package/packages/sandbox/src/server/delivery/policies.ts +0 -239
  144. package/packages/sandbox/src/server/delivery/record.ts +0 -41
  145. package/packages/sandbox/src/server/delivery/steps.ts +0 -464
  146. package/packages/sandbox/src/server/delivery/types.ts +0 -158
  147. package/packages/sandbox/src/server/dependencies.ts +0 -88
  148. package/packages/sandbox/src/server/diff.ts +0 -230
  149. package/packages/sandbox/src/server/gates.ts +0 -388
  150. package/packages/sandbox/src/server/index.ts +0 -139
  151. package/packages/sandbox/src/server/path-guard.ts +0 -282
  152. package/packages/sandbox/src/server/planning.ts +0 -749
  153. package/packages/sandbox/src/server/platform-client.ts +0 -189
  154. package/packages/sandbox/src/server/preview-database-host.ts +0 -340
  155. package/packages/sandbox/src/server/preview-database-protocol.ts +0 -163
  156. package/packages/sandbox/src/server/preview-database-proxy.ts +0 -356
  157. package/packages/sandbox/src/server/preview-database.ts +0 -109
  158. package/packages/sandbox/src/server/preview-ipc.ts +0 -15
  159. package/packages/sandbox/src/server/preview-modules.ts +0 -83
  160. package/packages/sandbox/src/server/preview-revision.ts +0 -44
  161. package/packages/sandbox/src/server/preview-runtime.ts +0 -467
  162. package/packages/sandbox/src/server/preview-worker-manager.ts +0 -436
  163. package/packages/sandbox/src/server/preview-worker.ts +0 -168
  164. package/packages/sandbox/src/server/reference.ts +0 -251
  165. package/packages/sandbox/src/server/routes.ts +0 -2258
  166. package/packages/sandbox/src/server/runtime.ts +0 -188
  167. package/packages/sandbox/src/server/session-owner.ts +0 -23
  168. package/packages/sandbox/src/server/sessions.ts +0 -849
  169. package/packages/sandbox/src/server/spec.ts +0 -471
  170. package/packages/sandbox/src/server/turn-lifecycle.ts +0 -56
  171. package/packages/sandbox/src/server/turns.ts +0 -993
  172. package/packages/sandbox/src/server/workspace-install.ts +0 -266
  173. package/packages/sandbox/src/server/workspace-root.ts +0 -73
  174. package/packages/sandbox/src/styles.css +0 -1573
  175. package/packages/sandbox/vite.config.ts +0 -133
@@ -1,691 +0,0 @@
1
- <div align="center">
2
-
3
- <picture>
4
- <source
5
- media="(prefers-color-scheme: dark)"
6
- srcset="https://raw.githubusercontent.com/flowdular/flowdular/main/docs/assets/flowdular-logo-dark.svg"
7
- />
8
- <img
9
- src="https://raw.githubusercontent.com/flowdular/flowdular/main/docs/assets/flowdular-logo.svg"
10
- alt="Flowdular"
11
- width="320"
12
- />
13
- </picture>
14
-
15
- ### The Flowdular sandbox
16
-
17
- Chat a change, watch it build behind the gates, preview it in the real
18
- application, deliver it as code you own.
19
-
20
- ![Node](https://img.shields.io/badge/Node-%E2%89%A5%2022.22.2-3A6BE0)
21
- ![License](https://img.shields.io/badge/license-MIT-141B2E)
22
- ![Built with OctaneJS](https://img.shields.io/badge/built%20with-OctaneJS-2557D6)
23
- ![Status](https://img.shields.io/badge/status-preview-8290A8)
24
-
25
- </div>
26
-
27
- The sandbox is the workshop of [Flowdular](https://github.com/flowdular/flowdular),
28
- the agentic foundation framework. It builds a change in an isolated workspace,
29
- drives the coding agent your team already uses inside it, runs the same gates
30
- the platform runs, and shows the result in the real application shell. A
31
- session carries as many modules as the work touches: one turn writes in one
32
- module, and the whole set is previewed and delivered together.
33
-
34
- ```bash
35
- npx @flowdular/sdk/sandbox # from a Flowdular workspace
36
- npx @flowdular/sdk/sandbox --port 4320 --workspace /path/to/workspace
37
- ```
38
-
39
- The launcher finds the workspace by walking up to `flowdular.json`, then opens
40
- http://127.0.0.1:4320.
41
-
42
- ## What it does
43
-
44
- - **Turns a brief into a module.** A planner names the modules and the first
45
- specialist; business, UX, backend, frontend and agentic roles hand off inside
46
- one session.
47
- - **Runs your coding agent.** Claude Code or Codex CLI on your machine, or a
48
- key you bring yourself. The sandbox never ships a model of its own.
49
- - **Keeps the work isolated.** Every session gets its own pnpm workspace, its
50
- own ephemeral databases, and a preview account that never touches your data.
51
- - **Gates every turn.** Spec schema, module schema, dependencies, typecheck,
52
- tests and format run against the draft before anything can land.
53
- - **Delivers as code.** Eject into `modules/` and enable it, or open a pull
54
- request with the gate evidence attached.
55
-
56
- ## Requirements
57
-
58
- Node.js 22.22.2 or newer, pnpm 11, a Flowdular workspace (a directory with
59
- `flowdular.json`), and a running Flowdular application to connect to.
60
-
61
- ## Documentation
62
-
63
- - [Flowdular repository](https://github.com/flowdular/flowdular)
64
- - [Architecture blueprint](https://github.com/flowdular/flowdular/blob/main/docs/architecture-blueprint.md)
65
- - [Module contract (AGENTS.md)](https://github.com/flowdular/flowdular/blob/main/AGENTS.md)
66
- - [Design system](https://github.com/flowdular/flowdular/blob/main/docs/design-system.md)
67
-
68
- ## Connecting
69
-
70
- The sandbox is a client of a running Flowdular application. It never opens the
71
- platform database.
72
-
73
- 1. In the application, open Administration, API tokens, and issue a token with
74
- `sandbox.access.use` plus the read scopes the preview should see. Add
75
- `sandbox.preview.data` for live data and `sandbox.modules.eject` for eject.
76
- 2. Paste the token and the application address into the sandbox connect screen.
77
-
78
- The token is encrypted at rest with a key in `.flowdular/sandbox/secret.key` and
79
- is never returned to the browser. The application may run anywhere: the sandbox
80
- works the same against `http://127.0.0.1:4310` and a deployment.
81
-
82
- Sign-in is enforced on the platform side: the token authenticates as its
83
- account, and `sandbox.core` must hold an active grant for it.
84
-
85
- ## Security model
86
-
87
- Every request to the sandbox API is checked before it does anything:
88
-
89
- - `loopback` mode trusts the machine only. A request must be addressed to the
90
- loopback name and the port the launcher bound (`Host: 127.0.0.1:4320` or
91
- `localhost:4320`); a page on another local port cannot drive the sandbox by
92
- name. State and configuration calls do not require a connection, because they
93
- are how one gets connected; everything else does.
94
- - `self-hosted` mode requires a browser session: `POST /sandbox/api/connect`
95
- with an API token sets an `HttpOnly; SameSite=Strict; Secure` cookie that
96
- expires after 12 hours. Without it, `GET /sandbox/api/state` answers 401 with
97
- the mode and the application address, and nothing else.
98
- - Every mutation must come from the sandbox page: `Sec-Fetch-Site` (when the
99
- browser sends it) and `Origin` must match the sandbox host, and the request
100
- must carry `x-flowdular-sandbox: 1`. A cross-site form post can do neither, so
101
- it is refused with 403 before any body is read.
102
- - The mode is the launcher's decision (`--mode`, `FD_SANDBOX_MODE`) and is
103
- never accepted over HTTP. Changing the application address needs the token
104
- for that application in the same request, so a stored token is never replayed
105
- to another host.
106
- - Session ids are UUIDs. Anything else in a `:id` segment or the preview cookie
107
- is refused before a path is built from it, and a delete additionally checks
108
- that the real path stays inside `.flowdular/sandbox/sessions`.
109
- - Capabilities (`sandbox.modules.eject`, `sandbox.preview.data`) are read from
110
- the acting principal: the browser session in `self-hosted` mode, the
111
- configured connection in `loopback` mode.
112
- - The preview API (`/api/*`) carries the same sandbox authentication. The bridge
113
- never forwards `/api/auth/*` or `/api/sandbox/*`, and the preview's own
114
- authentication runtime keeps sign-up closed: the only account is the seeded
115
- preview account.
116
- - Session views expose workspace-relative paths only. The preview loads draft
117
- sources through `/preview-module/<session>/<module>/...`, which the sandbox
118
- maps onto the session workspace itself.
119
-
120
- ## Modes
121
-
122
- | Mode | Binding | Coding agents |
123
- | ------------- | -------------------- | ----------------------------------- |
124
- | `loopback` | loopback interface | local `claude` and `codex`, or BYOK |
125
- | `self-hosted` | configured interface | BYOK only |
126
-
127
- `--mode` selects it, and a non-loopback `--host` forces `self-hosted`. A
128
- sandbox that cannot prove it is loopback never offers a local binary, because a
129
- local binary carries the operator's own login.
130
-
131
- ## Sessions
132
-
133
- The home dashboard lists the operator's ideas, current stages, recorded token
134
- usage and provider-reported costs. A session summary breaks usage down by
135
- specialist and offers archive, reject, restore and delete actions. Unknown
136
- amounts are marked rather than treated as free work. See
137
- [dashboard accounting and limitations](../../docs/sandbox-dashboard.md).
138
-
139
- A session owns a directory under `.flowdular/sandbox/sessions/<id>`:
140
-
141
- - `workspace/` is a Flowdular workspace and a pnpm workspace of its own: the
142
- draft modules under `modules/`, the manifests of every other enabled module
143
- for dependency validation, `reference/` with read-only copies of the platform
144
- contracts, one complete example module and the skills under
145
- `.ai/skills`, and the pointer file the coding agent auto-loads (`CLAUDE.md`
146
- for claude, `AGENTS.md` for codex).
147
- - `base/modules/<directory>` is the pristine copy an edit diffs against, one
148
- per edited module.
149
- - `chat.jsonl` is the durable transcript, including every agent event.
150
- - `session.json` is the record. Its shape:
151
-
152
- ```json
153
- {
154
- "id": "<uuid>",
155
- "kind": "edit-module",
156
- "moduleId": "parties.core",
157
- "moduleSuffix": "parties",
158
- "modules": [
159
- { "id": "parties.core", "directory": "parties", "kind": "edit" },
160
- { "id": "catalog.core", "directory": "catalog", "kind": "edit" }
161
- ],
162
- "title": "...",
163
- "brief": "...",
164
- "blueprint": "edit-module@1.0.0",
165
- "role": "backend-engineer",
166
- "driver": "codex",
167
- "model": null,
168
- "resumeIds": { "codex": "<thread id>" },
169
- "autoContinue": true,
170
- "chainDepth": 0,
171
- "state": "previewing",
172
- "createdAt": 0,
173
- "updatedAt": 0,
174
- "ejectedAt": null,
175
- "archivedAt": null,
176
- "registeredWithPlatform": true
177
- }
178
- ```
179
-
180
- `modules` is the session: every entry is materialized in the workspace, diffed
181
- against its own base, gated, previewed (all server routes and all client
182
- contributions compose together) and delivered in one eject. `modules[0]` is the
183
- primary module and is repeated as `moduleId` and `moduleSuffix`, which stay in
184
- sync with it; the primary never changes after creation, so everything keyed on
185
- it keeps working.
186
-
187
- ### Modules of a session
188
-
189
- The planner names them from the brief. Rules first: every existing module the
190
- brief names by its whole dotted id (`parties.core`) or as `module parties` is a
191
- change to that module, in the order the brief names them, and a `<domain>.core`
192
- id the workspace does not have is a new module. A bare English word never
193
- selects a module, and a file name (`package.json`) is never read as one. The
194
- planner agent may name more than the rules found; it can never turn a known
195
- module into a new one, and it can never drop a module the brief named. The
196
- classification is the first system entry of the transcript.
197
-
198
- A session can gain a module afterwards:
199
-
200
- ```
201
- POST /sandbox/api/sessions/:id/modules { "moduleId": "catalog.core" }
202
- ```
203
-
204
- It copies that workspace module into `workspace/modules/<directory>` and
205
- `base/modules/<directory>`, appends it to `modules`, regenerates the workspace
206
- manifests so the new draft is a project of the session's pnpm workspace (and no
207
- longer a `link:` override), re-runs the install when the new `package.json`
208
- changes the dependency signature, records a system entry, and answers with the
209
- refreshed session view. Refusals, each a stable error code: `404
210
- MODULE_NOT_FOUND` for a module this workspace does not have, `409
211
- MODULE_ALREADY_IN_SESSION`, `409 SESSION_RUNNING` while a turn is in flight,
212
- `409 SESSION_ARCHIVED`, and `409 SESSION_DELIVERED`. A checkpoint taken before
213
- the module joined has no snapshot of it, so a rollback to that point leaves the
214
- new module's files alone.
215
-
216
- ### One turn, one module
217
-
218
- The specialist works in a single module per turn, while the session context
219
- lists all of them. The turn body takes an optional `module` (the draft module
220
- directory):
221
-
222
- ```
223
- POST /sandbox/api/sessions/:id/turn { "message": "...", "module": "catalog" }
224
- ```
225
-
226
- Without it, the module the last handoff named decides, and otherwise the
227
- primary. A `module` the session does not carry is refused with
228
- `MODULE_NOT_IN_SESSION`. The active module is what the instruction calls the
229
- target module, its directory is what the role's `allowedPaths` resolve against
230
- (so a turn may write in that module only), and the state routing (specification,
231
- manifest, server, screen) reads that module. The instruction also lists every
232
- module of the session and says which one this turn owns. Each handoff carries
233
- the module it belongs to: a failed gate hands the fix back in the module the
234
- gate ran in, and an automatically continued turn stays there. Transcript entries
235
- carry their module, so the conversation says where each turn worked.
236
-
237
- ### Dependencies
238
-
239
- The session workspace installs for real. `package.json` and
240
- `pnpm-workspace.yaml` are generated at creation: the draft modules are the
241
- workspace projects, every other workspace package (`@flowdular/*`, the other
242
- modules) is an `overrides` entry pointing at the live checkout with `link:`,
243
- the host `pnpm-lock.yaml` seeds the resolution so versions match the platform,
244
- and the host `patches/` travel along. `pnpm install --offline` runs at creation
245
- and again whenever a draft module's `package.json` changes (the fallback is
246
- `--prefer-offline`, which fetches only what the store lacks). Measured on this
247
- machine: an edit session of `parties.core` installs in about 0.5 s with the
248
- seeded lockfile, a fresh new-module session in about 1 s, a re-run with
249
- nothing changed in about 0.2 s. A failed install is reported as a failed
250
- `dependencies` gate with the installer's output.
251
-
252
- A draft that depends on another draft of the same session resolves the session
253
- copy, because both are projects of that workspace; a module that is not part of
254
- the session resolves the live checkout.
255
-
256
- New module sessions start from a specification. Once it is approved, the
257
- sandbox runs the same `module new` capability the CLI exposes, so the skeleton
258
- is never hand-written, and formats the skeleton with the workspace Prettier
259
- settings right away. Files the business manager already wrote under the module
260
- (translations, for example) step aside for the scaffold and come back over it.
261
-
262
- ### Lifecycle
263
-
264
- A session can be archived, restored, and deleted from its dashboard summary,
265
- or from the platform CLI (`flowdular sandbox session-archive`,
266
- `flowdular sandbox session-delete`, dry run by default). Archived sessions are
267
- hidden until "Show archived" and refuse new turns until restored. Deleting
268
- removes the workspace, the base copy and the preview data; the record and the
269
- transcript stay as a tombstone unless `keepTranscript: false` is passed. Both
270
- actions refuse a session with a running turn unless asked to stop it
271
- (`stop: true`), and both are recorded on the platform as
272
- `sandbox.session.archived`, `sandbox.session.restored`,
273
- `sandbox.session.deleted`.
274
-
275
- ### Attachments
276
-
277
- An operator can paste a screenshot or attach files (concepts, mockups, specs)
278
- to a turn to show what they want changed. Attachments belong to the session:
279
-
280
- - The bytes are stored under `.flowdular/sandbox/sessions/<id>/attachments/<attachmentId>-<safeName>`
281
- and copied into the session workspace at `workspace/reference/attachments/<safeName>`,
282
- so the coding agent, which may only read inside the workspace, opens them by
283
- name with its normal file tools.
284
- - `attachmentId` is a UUID. `safeName` is the original filename reduced to
285
- `[A-Za-z0-9._-]` with no path segments, no `..` and no leading dot, capped at
286
- 128 characters and made unique within the session.
287
- - Limits: at most 10 per session, 5 MB per file, and only these extensions,
288
- verified by extension plus a magic-byte sniff for the image and pdf/svg
289
- formats: `png`, `jpg`, `jpeg`, `gif`, `webp`, `md`, `txt`, `json`, `csv`,
290
- `pdf`, `svg`. Anything else is refused with a stable error code.
291
- - The record carries them as `attachments: [{ id, name, kind: 'image' | 'file',
292
- size, addedAt }]`, backfilled to `[]` for older sessions.
293
-
294
- The endpoints, each behind the same `authorize()`, same-origin and
295
- `x-flowdular-sandbox` boundary as every other mutation, and each validating the
296
- session id and attachment id before building a path:
297
-
298
- - `POST /sandbox/api/sessions/:id/attachments` with `{ name, contentBase64 }`
299
- returns the created attachment metadata.
300
- - `POST /sandbox/api/sessions/:id/attachments/:attachmentId/delete` removes one.
301
- - `GET /sandbox/api/sessions/:id/attachments/:attachmentId` serves the bytes
302
- with the right content type, `Content-Disposition: inline` and
303
- `Cache-Control: private, no-store`, for the composer thumbnail.
304
-
305
- When a turn runs and the session has attachments, the instruction the driver
306
- receives is prefixed with a short note naming them and their kinds and pointing
307
- at `reference/attachments/`; the operator's own message follows it. The user
308
- entry in the transcript records which attachments were included. Both composers
309
- support paste and an attach button and show each file as a chip with a remove
310
- control. The new-session screen holds the files until the session exists: it
311
- creates the session, uploads them to it, and only then starts the first turn, so
312
- that turn's prompt already names them. It refuses a file the sandbox would
313
- refuse (wrong extension, empty, over 5 MB, more than 10) before uploading, and a
314
- failed upload keeps the created session and stops instead of starting a turn
315
- that cannot see the file.
316
-
317
- ### Checkpoints
318
-
319
- Every turn that changes files leaves a restore point, so an operator can roll a
320
- session's workspace back to an earlier state when a coding agent goes wrong
321
- without losing the transcript.
322
-
323
- - A snapshot of each draft module tree is copied to
324
- `.flowdular/sandbox/sessions/<id>/checkpoints/<sequence>/modules/<directory>`,
325
- excluding `node_modules`; `base/`, `reference/` and attachments are never
326
- snapshotted. One is taken at session creation as the pristine start
327
- (`sequence` 0), and one after every turn that produced a diff, keyed by that
328
- turn's handoff chat entry so the transcript line and its restore point share
329
- one sequence.
330
- - The record carries them as `checkpoints: [{ sequence, at, label, role }]`,
331
- oldest first, backfilled to `[]` for older sessions. `label` is a short human
332
- line (the role that produced it, `the starting point` for the initial one).
333
- - Bounded to the last 24: when a new one exceeds the cap the oldest is pruned,
334
- directory and all, except the start, which is never dropped.
335
-
336
- Restore replaces the draft files, keeps the transcript, and appends a marker:
337
-
338
- - `POST /sandbox/api/sessions/:id/checkpoints/restore` with `{ sequence }`,
339
- behind the same `authorize()`, same-origin and `x-flowdular-sandbox` boundary
340
- as every other mutation, validating the session id first. The path is distinct
341
- from `/restore`, which un-archives a session.
342
- - It replaces `workspace/modules/<directory>` with the snapshot (the current
343
- contents step aside, `node_modules` stays so the install survives), invalidates
344
- the diff cache, appends a `system` entry (`Restored the workspace to the state
345
- after <label> (turn <sequence>).`), sets the state back to `editing`, and
346
- returns the refreshed session view.
347
- - Refusals, each a stable error code: `400 INVALID_SESSION_ID` for a hostile id,
348
- `400 INVALID_INPUT` for an absent or non-integer sequence,
349
- `400 CHECKPOINT_NOT_FOUND` for a sequence the session has no snapshot for,
350
- `409 SESSION_RUNNING` while a turn is in flight, `409 SESSION_ARCHIVED` for an
351
- archived session, and `409 SESSION_DELIVERED` once the session was ejected
352
- (start a new session to change the module again).
353
-
354
- Each agent turn and handoff block that has a matching checkpoint shows a quiet
355
- `Restore to here` affordance with an inline confirm; it is disabled while the
356
- session runs. Restoring reloads the session so the transcript picks up the
357
- marker and the preview refreshes.
358
-
359
- ## Roles and routing
360
-
361
- Every turn is driven by one specialist with its own instruction, writable paths
362
- and gates: business manager, UX designer, frontend engineer, backend engineer,
363
- agentic engineer. They are workspace configuration in `.ai/agents/sandbox` and
364
- can be edited per workspace.
365
-
366
- Nobody picks an agent to start. A session begins with one brief, and the
367
- planner classifies it: new module or change, which modules, what to call them,
368
- and which specialist takes the first turn (see "Modules of a session" for the
369
- rules). The classification is the first system entry of the transcript, so a
370
- wrong guess is corrected in the first message.
371
-
372
- Later turns route the same way, against the module the turn targets. Its state
373
- decides who works next
374
- (no specification means the business manager, no server means the backend
375
- engineer, no screen means the frontend engineer), and the words of the request
376
- only choose between specialists that are already valid for that state. An
377
- answer to a question goes back to the specialist who asked it. Every routed
378
- turn says who took it and why; the role picker in the composer overrides it for
379
- one turn, and the module picker beside it overrides which module that turn
380
- works in.
381
-
382
- The role documents in `.ai/agents/sandbox` are the source of truth; the
383
- bundled defaults in `@flowdular/sdk/coding-agent` are regenerated from them with
384
- `pnpm --filter @flowdular/sdk/coding-agent sync-roles`, and a test fails when the
385
- two disagree.
386
-
387
- The coding agent is chosen when the session starts, next to the brief, and the
388
- composer can change it for a later turn. A session remembers its agent, so a
389
- handed-off turn runs on the same one.
390
-
391
- ## Handoffs
392
-
393
- A turn never ends in silence. Every role closes its final message with one
394
- line, `HANDOFF: <role-id> - <why>` or `HANDOFF: none - <why>` (a display name
395
- or a trailing full stop is forgiven), and the orchestrator turns that into the
396
- next step:
397
-
398
- - **continue**: the named specialist takes over with a prompt that carries the
399
- original brief. The line is honoured only when it names a role the finishing
400
- role may hand to (its `handoff` list) and never itself; otherwise the state
401
- routing decides and the transcript says why. With **Auto handoff** on, which
402
- is the default, the server starts the next turn by itself on the same stream,
403
- up to four chained turns per operator message (`chainDepth` in the record).
404
- With it off, the transcript shows a `Continue with <role>` button instead.
405
- - **approval**: every new or edited module stops before implementation until
406
- the operator approves its current specification. The approval route moves
407
- the `status` line to `approved` and records the SHA-256 hash of that exact
408
- text in the session. An `approved` line written by an agent is not authority.
409
- Editing the specification or requesting changes makes the recorded hash
410
- stale and opens the approval gate again. In a multi-module session each
411
- affected module needs its own current approved hash.
412
- - **question**: the turn changed nothing and needs an answer, whatever the
413
- specification's status. The answer routes back to the specialist who asked.
414
- - **review**: the specialist reports the request satisfied. Run the gates and
415
- eject when the change looks right. A business manager who updated a
416
- specification stops at approval; only the approved handoff starts its
417
- implementer.
418
- - **blocked**: the coding agent errored. Nothing continues on its own.
419
-
420
- A failed gate is its own handoff: the specialist that caused it fixes it before
421
- anyone else works, so a broken change never travels down the chain. The fix
422
- prompt carries the gate command and the first 4000 characters of its output;
423
- the transcript keeps the whole output (head and tail of a long one).
424
-
425
- ## Turn lifetime
426
-
427
- A turn runs to completion on the server whatever happens to the browser: the
428
- driver, the gates and the handoff are written to the transcript even when the
429
- tab closed after the first event. The response is a subscription to that turn,
430
- and closing it only unsubscribes; stopping is an explicit action. The session
431
- reports whether a turn is still running (`running` in the session view and
432
- `running` ids in the state), and a browser that reopens it attaches to the live
433
- stream (`GET /sandbox/api/sessions/:id/turn/stream`) until the chain ends.
434
-
435
- A new turn on a session that already has one supersedes it, and waits for the
436
- old process to exit before it starts. Coding agents keep one writer per
437
- conversation thread, so resuming a thread whose previous process is still alive
438
- fails; the sandbox removes that race, and both local drivers recover from a
439
- lock they did not cause by continuing on a fresh thread or session with the
440
- conversation replayed.
441
-
442
- ## Eject
443
-
444
- Eject is a delivery with its own screen. It carries every module of the session
445
- in one delivery, and the plan lists them: per module the files that land, how
446
- many are overwritten, how many the session deleted and will be removed, the
447
- packages it adds that the workspace does not have yet, and whether it has to be
448
- enabled in the platform (new modules only). One confirmation applies them all.
449
- The plan also names the gates that run first and whether the connected
450
- application has to restart. Confirming runs it step
451
- by step and reports each step as it happens:
452
-
453
- 1. every gate, one by one (module gates once per draft module),
454
- 2. the copy into `modules/` for every module of the session, then the removal
455
- of the files an edit deleted,
456
- 3. `pnpm install`, which links workspace packages and fetches whatever a module
457
- newly declares,
458
- 4. `module enable` for each new module, through the same capability the CLI
459
- exposes,
460
- 5. `auth sync-scopes` for each module, which grants the scopes the module's
461
- specification declares to every workspace owner, because a module nobody has
462
- permission for is installed and invisible,
463
- 6. a platform typecheck with the modules in the composition,
464
- 7. optionally a full build,
465
- 8. a restart note: the connected application loads the new composition and
466
- runs new migrations only when it starts, so a local `pnpm dev` has to be
467
- restarted, and a remote application redeployed.
468
-
469
- A failing gate stops the delivery before anything is written, and a failing
470
- step stops it there: the session is marked delivered only when every step
471
- passed, and the failure carries the step's output. A delivered session says so
472
- in the session list, with the time it landed, and the platform records
473
- `sandbox.module.ejected` with what landed.
474
-
475
- Delivery is a target behind one interface (`DeliveryTarget` in
476
- `src/server/delivery/types.ts`): `available`, `plan`, `apply`. The eject
477
- request names the target (`target: 'workspace' | 'git-pr' | 'official-modules'`, default from
478
- configuration); the plan answer lists `availableTargets` with a reason for each
479
- one that cannot be used here, and the screen offers the choice only when more
480
- than one is usable.
481
-
482
- ### Contribute to Official Modules (`official-modules`)
483
-
484
- Choose **Send to Official Modules** in the eject dialog, then confirm the displayed
485
- repository and source scope. This target accepts one new module with a current
486
- human-approved specification and passing exact-source auto-review. GitHub access
487
- to `Flowdular/official-modules`, git, gh and pnpm are required. The repository must
488
- be accessible to the configured GitHub identity; private repositories remain private.
489
-
490
- The host repeats all sandbox gates, clones the registry to a temporary directory,
491
- refuses an existing module ID/directory, copies the reviewed source and runs the
492
- registry's `pnpm verify`. It records source-bound evidence, packages an immutable
493
- release, and opens a PR to `Flowdular/official-modules` against `main`. Contributors
494
- without upstream push rights use a personal fork. Only module source, its review
495
- and release artifact enter the commit. Credentials stay out of package-script
496
- environments, command arguments and PR text. The local application is not enabled
497
- or changed. Existing branches are never overwritten on retry; only an identical
498
- source tree can be reused after an interrupted PR request.
499
-
500
- The target is included in default delivery targets. Projects with an explicit
501
- `sandbox.delivery.targets` list must add `official-modules`. Disabling GitHub
502
- in sandbox settings disables this target too. SDK/CLI npm releases must exist for
503
- the clean registry installation; an unavailable dependency stops before any push.
504
-
505
- This action submits code for maintainer review. It does not publish the registry,
506
- merge the PR or bypass PostgreSQL CI. Existing registry modules and multi-module
507
- changes use the contributor skills and a manually prepared branch for now.
508
-
509
- ### As a pull request (`git-pr`)
510
-
511
- The same change, committed on a branch and pushed, so review happens in the
512
- repository and nothing in this working tree moves. One pull request carries
513
- every module of the session, which makes it the unit for a change that spans
514
- modules. Available when the workspace is a git work tree with at least one
515
- commit, the configured remote exists, and the base branch can be read. A pull
516
- request is opened when `gh` is signed in or a provider token is sealed in the
517
- sandbox configuration. Without usable GitHub authentication the sandbox still
518
- pushes the branch and returns a compare link. Set
519
- `sandbox.delivery.git.provider` to `none` when that is always the intended
520
- result. The steps:
521
-
522
- 1. every gate, as above,
523
- 2. `git fetch <remote> <base>`, a detached worktree of `<remote>/<base>` under
524
- `.flowdular/sandbox/worktrees/<session>`, and the branch
525
- `<prefix>/<module-dir>-<session id prefix>` in it,
526
- 3. the copy and the removals into the worktree,
527
- 4. `pnpm install --offline` there (`--prefer-offline` when a package is new),
528
- 5. `module enable` for each new module, with the worktree as its root,
529
- 6. a platform typecheck in the worktree,
530
- 7. the guardrail check: `git status` in the worktree may list only
531
- `modules/<dir>/**` of the session's modules and `pnpm-lock.yaml`. A delivery
532
- with a new module may also change `flowdular.json`, `platform/package.json`
533
- and `platform/src/generated/**`;
534
- the count stays within `sandbox.delivery.maxChangedFiles` (else the
535
- `.ai/policies/task-budgets.yaml` figure for the session kind); new packages
536
- stay within `maxNewDependencies`. A violation names the paths and stops
537
- before anything is committed,
538
- 8. a check that an existing local or remote session branch belongs to this
539
- session, `git add` of the allowed paths, a commit
540
- `sandbox: add|update <module id>` with the session id and the gate summary,
541
- `git push -u --force-with-lease`,
542
- 9. `gh pr create` with a plain body: what changed, the gate table, the file
543
- list (added, modified, removed), the specification versions and field diff,
544
- detected deployment risks, the post-merge
545
- `pnpm flowdular auth sync-scopes --module <id> --apply`, the session id, and a
546
- reviewer note when `.ai/policies/path-ownership.yaml` says a cross-owner
547
- change needs one. A second delivery of the same session updates the branch
548
- and keeps the open pull request.
549
-
550
- The worktree is removed whatever the outcome; the branch is kept on success
551
- and deleted on failure. A newly pushed remote branch is also removed when PR
552
- creation fails, while an earlier branch for the same session is preserved.
553
- `sync-scopes` is not run: it is a runtime action on
554
- the deployment's database, so it is the post-merge step in the body. Git,
555
- GitHub CLI and pnpm receive only an allowlisted process environment. The raw
556
- sealed token is passed only to GitHub CLI as `GH_TOKEN`; Git receives only a
557
- process-local authorization header. Command output and pull request summary
558
- text are redacted before they can reach the browser or GitHub.
559
-
560
- The done screen shows the pull request (or compare) link, and
561
- `.flowdular/sandbox/sessions/<id>/delivery.json` keeps the branch and the URL
562
- for the session.
563
-
564
- Open **Delivery settings** on the dashboard to configure GitHub delivery for
565
- this sandbox. Project defaults apply unless local overrides are selected.
566
- Repository, reviewers and delivery mode appear first; the source remote,
567
- base branch, branch prefix and fork owner are under **Advanced settings**.
568
- Saving the form does not create, push or merge a pull request. Push modes are:
569
-
570
- - `auto` uses direct delivery only after GitHub confirms push permission and
571
- refuses otherwise,
572
- - `direct` pushes the session branch to the configured repository,
573
- - `fork` uses the configured fork owner or the account returned by GitHub, and
574
- creates the fork only after the operator confirms the eject.
575
-
576
- The sandbox never creates a fork in `auto` mode. Selecting `fork` is the
577
- operator's explicit consent. The repository must ignore `.flowdular/`; delivery
578
- is refused otherwise so the temporary worktree cannot dirty the active
579
- checkout.
580
-
581
- The optional token is encrypted in `.flowdular/sandbox/config.json` with the
582
- local sandbox key. The browser receives only its eight-character fingerprint.
583
- Git receives it through process-local configuration, never in a command
584
- argument or remote URL. The repository settings below remain authoritative
585
- until the operator checks **Use custom settings for this sandbox**.
586
- That local override never modifies `flowdular.json` and can be turned
587
- off again from the same form.
588
-
589
- Configuration lives in `flowdular.json`, all of it optional (defaults shown):
590
-
591
- ```json
592
- {
593
- "sandbox": {
594
- "delivery": {
595
- "default": "workspace",
596
- "targets": ["workspace", "git-pr"],
597
- "git": {
598
- "remote": "origin",
599
- "baseBranch": "main",
600
- "branchPrefix": "sandbox",
601
- "provider": "github",
602
- "reviewers": []
603
- },
604
- "maxChangedFiles": 18
605
- }
606
- }
607
- }
608
- ```
609
-
610
- `provider: "none"` pushes the branch without opening a pull request.
611
- `maxChangedFiles` is unset by default, which means the task budget applies.
612
- The block is validated by `packages/contracts/schemas/project.schema.json`
613
- (`pnpm flowdular doctor`) and read at request time.
614
-
615
- ## Gates
616
-
617
- The session's **Check modules** action opens per-module results with a plain
618
- status and expandable diagnostic output. A skipped check is not a success.
619
- Specification approval requires the matching module's readable review; switching
620
- the specification editor to another module disables saving until that document
621
- loads. A failed turn start retains the user's message and releases the composer.
622
-
623
- On desktop the preview is a floating card over the right side of the chat
624
- surface, with room reserved for messages and the composer. **Preview** also opens
625
- the full-screen workbench on mobile, including sessions without file changes.
626
-
627
- The browser regression in `tests/browser/session-workflow.mjs` exercises the real
628
- UI with synthetic session, model and delivery responses. Start a sandbox against
629
- a disposable workspace, then run:
630
-
631
- ```bash
632
- node packages/sandbox/tests/browser/session-workflow.mjs http://127.0.0.1:4438
633
- ```
634
-
635
- It requires Playwright with Chromium available. If Playwright is provided by an
636
- external runtime, set `PLAYWRIGHT_MODULE` to its absolute `index.mjs` path.
637
- Screenshots and results are saved in a temporary directory. No paid model calls,
638
- platform mutations or GitHub operations are made by this regression.
639
-
640
- Gates are a fixed list run by the sandbox: `spec-schema` and `module-schema`
641
- once per session workspace, `dependencies`, `typecheck`, `tests` and `format`
642
- once per draft module, with the module's own binaries from the session install.
643
- After a turn the module gates run for the modules that hold changes, not for
644
- every module of the session, and every result names the module it ran in. The
645
- `dependencies` gate runs after every turn that changed files, whatever the role
646
- lists. A failing gate is written back into the conversation, with its
647
- command and output, so the next turn can fix it. An agent whose driver has a
648
- shell may run the same commands itself; the sandbox still runs them after the
649
- turn.
650
-
651
- ## Preview
652
-
653
- The preview renders the session's draft modules inside the real application
654
- shell, so a screen looks exactly as it will in production, including the
655
- navigation entry and dashboard widgets each module contributes. Every draft with
656
- a client entry composes into the same shell, so the navigation shows all of
657
- them; the module selector in the preview head opens the preview on one module's
658
- first screen (`/preview/<session>?module=<directory>`), and the same selection
659
- filters the diff.
660
-
661
- The preview API is composed, not stubbed. A request from a preview screen is
662
- answered in this order:
663
-
664
- 1. The draft module's own routes, loaded from `src/platform.ts` in the session
665
- workspace and reloaded whenever its sources change.
666
- 2. The session's own `auth.core` routes, running on an ephemeral database with
667
- a seeded preview account.
668
- 3. The bridge to the connected application, for everything the draft does not
669
- own.
670
-
671
- That gives a draft screen a real principal, its declared scopes, a real CSRF
672
- contract, and a real database of its own, while still reading live records from
673
- other modules. Preview data has two modes: `fixtures` is fully offline, and
674
- `bridge` forwards the leftovers to the connected application with the sandbox
675
- token. The bridge is read only and refuses without the `sandbox.preview.data`
676
- scope.
677
-
678
- ### Auto-review
679
-
680
- At the final completion handoff, a missing or stale `auto-review` result routes the
681
- same specialist to a separate read-only review turn. The report covers correctness,
682
- security, compatibility, lifecycle, tests and UI, with specific evidence. Findings
683
- return to implementation. A passing report is stored by the server outside the
684
- agent workspace and tied to every module file's content. Later edits invalidate it.
685
- With auto-continue disabled, continue the generated review handoff manually.
686
-
687
- All eject targets require a current report for every delivered module and passing
688
- schema, dependency, typecheck, test and format checks. Missing or skipped results
689
- block eject; empty test suites fail. Old sessions need a review before delivery.
690
- The report is a model assessment and does not guarantee correctness; executable
691
- checks remain mandatory, and operator spec approval stays separate.