@loj-lang/cli 0.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (36) hide show
  1. package/README.md +87 -0
  2. package/agent-assets/loj-authoring/SKILL.md +179 -0
  3. package/agent-assets/loj-authoring/agents/openai.yaml +3 -0
  4. package/agent-assets/loj-authoring/metadata.json +6 -0
  5. package/agent-assets/loj-authoring/references/backend-family.md +340 -0
  6. package/agent-assets/loj-authoring/references/backend-targets.md +171 -0
  7. package/agent-assets/loj-authoring/references/frontend-family.md +794 -0
  8. package/agent-assets/loj-authoring/references/frontend-runtime-trace.md +204 -0
  9. package/agent-assets/loj-authoring/references/policy-rules-proof.md +178 -0
  10. package/agent-assets/loj-authoring/references/project-and-transport.md +454 -0
  11. package/agent-assets/loj-authoring/references/workflow-flow-proof.md +263 -0
  12. package/dist/database-native-sql.d.ts +4 -0
  13. package/dist/database-native-sql.d.ts.map +1 -0
  14. package/dist/database-native-sql.js +266 -0
  15. package/dist/database-native-sql.js.map +1 -0
  16. package/dist/env.d.ts +31 -0
  17. package/dist/env.d.ts.map +1 -0
  18. package/dist/env.js +229 -0
  19. package/dist/env.js.map +1 -0
  20. package/dist/fastapi-dev-runner.d.ts +3 -0
  21. package/dist/fastapi-dev-runner.d.ts.map +1 -0
  22. package/dist/fastapi-dev-runner.js +263 -0
  23. package/dist/fastapi-dev-runner.js.map +1 -0
  24. package/dist/flow-proof.d.ts +3 -0
  25. package/dist/flow-proof.d.ts.map +1 -0
  26. package/dist/flow-proof.js +2 -0
  27. package/dist/flow-proof.js.map +1 -0
  28. package/dist/index.d.ts +36 -0
  29. package/dist/index.d.ts.map +1 -0
  30. package/dist/index.js +5353 -0
  31. package/dist/index.js.map +1 -0
  32. package/dist/rules-proof.d.ts +3 -0
  33. package/dist/rules-proof.d.ts.map +1 -0
  34. package/dist/rules-proof.js +2 -0
  35. package/dist/rules-proof.js.map +1 -0
  36. package/package.json +49 -0
@@ -0,0 +1,454 @@
1
+ # Project File And Transport
2
+
3
+ Use this reference for `loj.project.yaml`, full-stack orchestration, target-level database/runtime
4
+ profiles, and the shared frontend↔backend HTTP/JSON baseline.
5
+
6
+ ## `loj.project.yaml` Purpose
7
+
8
+ `loj.project.yaml` is orchestration only.
9
+
10
+ It answers:
11
+
12
+ - what targets belong to the app
13
+ - where each target entry file lives
14
+ - where generated output should go
15
+ - which dev host/server topology should run together
16
+ - which project-shell database/runtime profile should be prepared for generated targets
17
+
18
+ It must not contain business semantics such as:
19
+
20
+ - `model`
21
+ - `resource`
22
+ - `page`
23
+ - controllers/entities/services
24
+ - frontend layout details
25
+
26
+ ## Recommended Directory Structure
27
+
28
+ For new projects, default to this shape unless the task explicitly needs something else:
29
+
30
+ ```text
31
+ my-app/
32
+ loj.project.yaml
33
+
34
+ frontend/
35
+ app.web.loj
36
+ models/
37
+ resources/
38
+ read-models/
39
+ pages/
40
+ rules/
41
+ workflows/
42
+ styles/
43
+ components/
44
+ logic/
45
+ assets/
46
+
47
+ backend/
48
+ app.api.loj
49
+ models/
50
+ resources/
51
+ read-models/
52
+ rules/
53
+ workflows/
54
+ handlers/
55
+ policies/
56
+ queries/
57
+ ```
58
+
59
+ Use this as a recommendation, not a parser requirement.
60
+
61
+ Intent:
62
+
63
+ - `frontend/` keeps `.web.loj`, `.style.loj`, frontend rules/workflows, host components, and assets together
64
+ - `backend/` keeps `.api.loj`, backend rules/workflows, `@fn(...)` handlers/policies, and `@sql(...)` queries together
65
+ - `loj.project.yaml` stays at the app root
66
+
67
+ ## Minimal Shape
68
+
69
+ ```yaml
70
+ app:
71
+ name: user-admin
72
+
73
+ targets:
74
+ frontend:
75
+ type: web
76
+ entry: frontend/app.web.loj
77
+ backend:
78
+ type: api
79
+ entry: backend/app.api.loj
80
+ ```
81
+
82
+ Rules:
83
+
84
+ - `app.name` is required
85
+ - `targets` is required
86
+ - each target has:
87
+ - logical alias key
88
+ - `type`
89
+ - `entry`
90
+ - `entry` is relative to the project file
91
+
92
+ ## Target Types
93
+
94
+ Preferred:
95
+
96
+ - `web`
97
+ - `api`
98
+
99
+ Legacy aliases still accepted during the beta window:
100
+
101
+ - `rdsl`
102
+ - `sdsl`
103
+
104
+ Use `web/api` for new project files.
105
+
106
+ ## Current Expanded Shape
107
+
108
+ ```yaml
109
+ app:
110
+ name: flight-booking-proof
111
+
112
+ targets:
113
+ frontend:
114
+ type: web
115
+ entry: frontend/app.web.loj
116
+ outDir: generated/frontend
117
+ runtime:
118
+ basePath: /console
119
+ backend:
120
+ type: api
121
+ entry: backend/app.api.loj
122
+ outDir: generated/backend
123
+ database:
124
+ vendor: postgres
125
+ mode: docker-compose
126
+ name: booking_app
127
+ username: loj
128
+ password: loj
129
+ autoProvision: true
130
+ migrations: native-sql
131
+ runtime:
132
+ basePath: /internal-api
133
+ shutdown:
134
+ mode: graceful
135
+ timeout: 30s
136
+ health:
137
+ path: /health
138
+ readiness:
139
+ path: /ready
140
+ drain:
141
+ path: /drain
142
+ cors:
143
+ origins: ["http://127.0.0.1:5173"]
144
+ forwardedHeaders:
145
+ mode: standard
146
+ trustedProxy:
147
+ mode: local
148
+ requestSizeLimit: 10mb
149
+
150
+ dev:
151
+ host:
152
+ type: react-vite
153
+ target: frontend
154
+ dir: ./generated/frontend
155
+ apiBase: /api
156
+ proxyTarget: backend
157
+ server:
158
+ target: backend
159
+ ```
160
+
161
+ Rules:
162
+
163
+ - the project file stays declarative
164
+ - do not put shell commands in it
165
+ - do not turn it into a generic env bag
166
+ - database/runtime slices live here, not in `.web.loj` / `.api.loj`
167
+
168
+ Complete Spring-style example:
169
+
170
+ ```yaml
171
+ app:
172
+ name: flight-booking-proof
173
+
174
+ targets:
175
+ frontend:
176
+ type: web
177
+ entry: frontend/app.web.loj
178
+ outDir: generated/frontend
179
+ runtime:
180
+ basePath: /console
181
+ backend:
182
+ type: api
183
+ entry: backend/app.api.loj
184
+ outDir: generated/backend-postgres
185
+ database:
186
+ vendor: postgres
187
+ mode: docker-compose
188
+ name: flight_booking_proof
189
+ username: loj
190
+ password: loj
191
+ autoProvision: true
192
+ migrations: flyway
193
+ runtime:
194
+ basePath: /internal-api
195
+ shutdown:
196
+ mode: graceful
197
+ timeout: 30s
198
+ cors:
199
+ origins: [http://127.0.0.1:5173]
200
+ methods: [GET, POST, PUT, PATCH, DELETE, OPTIONS]
201
+ headers: [Authorization, Content-Type]
202
+ credentials: true
203
+ forwardedHeaders:
204
+ mode: standard
205
+ trustedProxy:
206
+ mode: local
207
+ health:
208
+ path: /healthz
209
+ readiness:
210
+ path: /readyz
211
+ drain:
212
+ path: /drainz
213
+ requestSizeLimit: 10mb
214
+
215
+ dev:
216
+ host:
217
+ type: react-vite
218
+ target: frontend
219
+ dir: ../../subprojects/rdsl/examples/user-admin/host
220
+ apiBase: /api
221
+ proxyTarget: backend
222
+ server:
223
+ target: backend
224
+ ```
225
+
226
+ ## Current CLI Mapping
227
+
228
+ Project-shell commands are the preferred default when `loj.project.yaml` exists:
229
+
230
+ - `loj validate loj.project.yaml`
231
+ - `loj build loj.project.yaml`
232
+ - `loj dev loj.project.yaml`
233
+ - `loj dev loj.project.yaml --debug`
234
+ - `loj rebuild loj.project.yaml --target frontend`
235
+ - `loj restart loj.project.yaml --service host`
236
+ - `loj status loj.project.yaml`
237
+ - `loj stop loj.project.yaml`
238
+ - `loj doctor loj.project.yaml`
239
+
240
+ Single-target project-shell flows keep database/runtime profiles active:
241
+
242
+ - `loj validate loj.project.yaml --target backend`
243
+ - `loj build loj.project.yaml --target backend`
244
+ - `loj dev loj.project.yaml --target backend`
245
+ - `loj status loj.project.yaml --target backend`
246
+ - `loj doctor loj.project.yaml --target backend`
247
+
248
+ Target-local CLIs remain available as secondary tools:
249
+
250
+ - `rdsl validate/build`
251
+ - `sdsl validate/build/dev`
252
+
253
+ Use `loj.project.yaml` whenever you need project-shell database/runtime/dev orchestration, even if
254
+ you are only generating one target.
255
+
256
+ Command stance:
257
+
258
+ - default: `loj ...`
259
+ - secondary/high-signal local tooling: `rdsl ...` / `sdsl ...`
260
+ - do not recommend `rdsl build` / `sdsl build` as the default path for new user-facing full-stack
261
+ examples
262
+
263
+ ## Required, Optional, Defaults
264
+
265
+ Core field status:
266
+
267
+ | Path | Status | Current default / note |
268
+ | --- | --- | --- |
269
+ | `app.name` | required | non-empty string |
270
+ | `targets` | required | must contain at least one target |
271
+ | `targets.<alias>.type` | required | `web | api` preferred; `rdsl | sdsl` still accepted as legacy aliases |
272
+ | `targets.<alias>.entry` | required | non-empty relative path |
273
+ | `targets.<alias>.outDir` | optional | build falls back to target-local defaults when omitted |
274
+ | `targets.<alias>.database` | optional | `api` targets only |
275
+ | `targets.<alias>.runtime` | optional | `api` targets may use the narrow runtime slice; `web` currently only uses `runtime.basePath` |
276
+ | `dev` | optional | no managed dev processes when omitted |
277
+ | `dev.server` | optional | starts no backend process when omitted |
278
+ | `dev.server.target` | required when `dev.server` exists | must reference an `api` target |
279
+ | `dev.server.host` | optional | `127.0.0.1` |
280
+ | `dev.server.port` | optional | `3001` |
281
+ | `dev.host` | optional | starts no frontend host when omitted |
282
+ | `dev.host.type` | optional | `react-vite` |
283
+ | `dev.host.target` | required when `dev.host` exists | must reference a `web` target |
284
+ | `dev.host.dir` | required when `dev.host` exists | non-empty relative path |
285
+ | `dev.host.host` | optional | `127.0.0.1` |
286
+ | `dev.host.port` | optional | `5173` |
287
+ | `dev.host.previewPort` | optional | `4173` |
288
+ | `dev.host.apiBase` | optional | `/api` |
289
+ | `dev.host.proxyTarget` | optional | defaults to `dev.server.target` when a local backend is also configured |
290
+ | `dev.host.proxyAuth` | optional | no default |
291
+
292
+ Database defaults:
293
+
294
+ | Path | Status | Current default / note |
295
+ | --- | --- | --- |
296
+ | `database.vendor` | required when `database` exists | `h2 | sqlite | postgres | mysql | mariadb | sqlserver | oracle` |
297
+ | `database.mode` | optional | `embedded` for `h2/sqlite`, otherwise `external` |
298
+ | `database.name` | optional | derived from `app.name`; `sqlite` becomes `<app>.db`, `oracle` becomes `FREEPDB1` |
299
+ | `database.host` | optional | external vendors default to `127.0.0.1` |
300
+ | `database.port` | optional | vendor default where applicable |
301
+ | `database.username` | optional | vendor default |
302
+ | `database.password` | optional | vendor default |
303
+ | `database.autoProvision` | optional | `false` |
304
+ | `database.migrations` | optional | `none` |
305
+
306
+ Runtime defaults:
307
+
308
+ | Path | Status | Current default / note |
309
+ | --- | --- | --- |
310
+ | `runtime.basePath` | optional | no default; supported on `api` and `web` targets |
311
+ | `runtime.shutdown` | optional | `api` targets only |
312
+ | `runtime.shutdown.mode` | optional inside `shutdown` | `graceful` |
313
+ | `runtime.shutdown.timeout` | optional inside `shutdown` | `30s` |
314
+ | `runtime.health.path` | optional | no default helper emitted unless declared |
315
+ | `runtime.readiness.path` | optional | no default helper emitted unless declared |
316
+ | `runtime.drain.path` | optional | no default helper emitted unless declared |
317
+ | `runtime.cors` | optional | `api` targets only |
318
+ | `runtime.cors.origins` | required when `cors` exists | non-empty string array |
319
+ | `runtime.cors.methods` | optional | target defaults when omitted |
320
+ | `runtime.cors.headers` | optional | target defaults when omitted |
321
+ | `runtime.cors.credentials` | optional | `false` |
322
+ | `runtime.forwardedHeaders` | optional | `api` targets only |
323
+ | `runtime.forwardedHeaders.mode` | optional inside `forwardedHeaders` | `standard` |
324
+ | `runtime.trustedProxy` | optional | `api` targets only |
325
+ | `runtime.trustedProxy.mode` | optional inside `trustedProxy` | `local` |
326
+ | `runtime.trustedProxy.cidrs` | optional | required only when `mode: cidrs` |
327
+ | `runtime.requestSizeLimit` | optional | no default |
328
+
329
+ Current auto-fill behavior:
330
+
331
+ - if `runtime.trustedProxy` is declared without `runtime.forwardedHeaders`, the project shell
332
+ currently auto-enables `forwardedHeaders.mode: standard`
333
+
334
+ ## Env Story
335
+
336
+ Current conventional env files:
337
+
338
+ - `.env`
339
+ - `.env.local`
340
+ - `.env.<target-alias>`
341
+ - `.env.<target-alias>.local`
342
+
343
+ `loj validate`, `loj build`, and `loj dev` load these conventionally.
344
+
345
+ `loj dev` also watches them and reloads managed target sessions when they change.
346
+
347
+ ## Database Slice
348
+
349
+ `targets.<alias>.database` is currently supported on `api` targets.
350
+
351
+ Supported fields:
352
+
353
+ - `vendor`: `h2 | sqlite | postgres | mysql | mariadb | sqlserver | oracle`
354
+ - `mode`: `embedded | external | docker-compose`
355
+ - `name`
356
+ - `host`
357
+ - `port`
358
+ - `username`
359
+ - `password`
360
+ - `autoProvision`
361
+ - `migrations`: `none | native-sql | flyway`
362
+
363
+ Current intent:
364
+
365
+ - database choice stays in project/runtime orchestration
366
+ - generated targets may emit runtime-specific config plus native `db/schema.sql`
367
+ - `loj dev` may auto-start/stop generated `docker-compose.database.yaml` when
368
+ `mode: docker-compose` and `autoProvision: true`
369
+
370
+ ## Runtime Slice
371
+
372
+ `targets.<alias>.runtime` currently supports:
373
+
374
+ - on `api` targets:
375
+ - `basePath`
376
+ - `shutdown.mode`
377
+ - `shutdown.timeout`
378
+ - `health.path`
379
+ - `readiness.path`
380
+ - `drain.path`
381
+ - `cors.origins`
382
+ - `cors.methods`
383
+ - `cors.headers`
384
+ - `cors.credentials`
385
+ - `forwardedHeaders.mode`
386
+ - `trustedProxy.mode`
387
+ - `trustedProxy.cidrs`
388
+ - `requestSizeLimit`
389
+ - on `web` targets:
390
+ - `basePath`
391
+
392
+ Keep this narrow. It expresses generated runtime/deploy intent, not business semantics.
393
+
394
+ ## Dev Story
395
+
396
+ Current `loj dev` scope:
397
+
398
+ - starts target-local rebuild loops
399
+ - can run a React/Vite host for the configured web target
400
+ - can run the configured backend server
401
+ - can auto-provision supported Docker Compose databases
402
+ - persists dev-session state for `status`, `stop`, and editor reuse
403
+ - accepts `loj rebuild` to queue a manual rebuild for all or selected active targets
404
+ - accepts `loj restart` to restart managed `host` and/or `server` processes without restarting the whole loop
405
+ - may expose attachable debugger endpoints through `loj dev --debug`
406
+
407
+ Keep this conventional. Do not invent arbitrary process orchestration syntax.
408
+
409
+ ## Neutral Transport Contract
410
+
411
+ Frontend and backend targets align to a shared HTTP/JSON contract, not directly to each other's
412
+ frameworks.
413
+
414
+ ### Lists
415
+
416
+ Accepted list responses:
417
+
418
+ - raw JSON array
419
+ - `{ items: [...] }`
420
+ - `{ data: [...] }`
421
+
422
+ ### Single records
423
+
424
+ Accepted single-record responses:
425
+
426
+ - raw JSON object
427
+ - `{ item: {...} }`
428
+ - `{ data: {...} }`
429
+
430
+ ### Errors
431
+
432
+ Errors should be non-2xx JSON objects with string `message`.
433
+
434
+ Current message rule:
435
+
436
+ - backend targets should return a stable human-readable `message`
437
+ - do not invent target-private message templating in source DSL
438
+
439
+ ### IDs
440
+
441
+ - every resource record includes `id`
442
+ - frontend may coerce it to string
443
+
444
+ ### Pagination
445
+
446
+ - server pagination metadata is still optional
447
+ - generated list pagination remains client-side unless the shared transport contract widens later
448
+
449
+ ## Full-Stack Authoring Guardrails
450
+
451
+ - Keep frontend-family and backend-family semantics in their own files.
452
+ - Use `loj.project.yaml` only to compose them.
453
+ - If frontend and backend need a richer shared transport shape, evolve the transport contract first.
454
+ - Do not hide target mismatches inside project orchestration.
@@ -0,0 +1,263 @@
1
+ # Workflow / Flow (`.flow.loj`)
2
+
3
+ Use this reference for the current first-slice workflow/state-machine surface.
4
+
5
+ It still has standalone CLI entry points, but it is no longer standalone-only:
6
+
7
+ - `.api.loj` may link it through `resource workflow: '@flow("./workflows/x")'`
8
+ - `.web.loj` may link it through either:
9
+ - `resource workflow: '@flow("./workflows/x")'`
10
+ - `resource workflow: { source: '@flow("./workflows/x")', style: workflowShell }`
11
+ - `loj.project.yaml` still does not orchestrate `.flow.loj` directly
12
+
13
+ ## Current Scope
14
+
15
+ Implemented today:
16
+
17
+ - `.flow.loj` file suffix
18
+ - one named workflow per file
19
+ - parser + validator + shared workflow-manifest generation
20
+ - repo CLI entry:
21
+ - `loj flow validate <file.flow.loj>`
22
+ - `loj flow build <file.flow.loj> --out-dir <dir>`
23
+ - narrow `.api.loj` linkage through `resource workflow: '@flow("./workflows/x")'`
24
+ - narrow `.web.loj` linkage through scalar or mapping `resource workflow`
25
+ - generated backend transition-enforcement surface for Spring Boot + FastAPI
26
+ - generated frontend create/edit/read workflow summaries, step-aware create/edit submit CTA labels, and next-step-prioritized read-surface transition actions
27
+ - fixed generated frontend workflow route/page at `/:id/workflow` for workflow-linked resources
28
+
29
+ Not implemented yet:
30
+
31
+ - project-shell orchestration for flow targets
32
+ - broader custom page-level wizard routing or long-transaction semantics beyond the fixed generated workflow page
33
+ - non-resource workflow consumers
34
+
35
+ ## File Shape
36
+
37
+ Use one top-level block:
38
+
39
+ ```yaml
40
+ workflow booking-process:
41
+ model: Booking
42
+ field: status
43
+
44
+ states:
45
+ DRAFT:
46
+ label: "Draft"
47
+ color: gray
48
+ READY:
49
+ label: "Ready"
50
+ color: blue
51
+ CONFIRMED:
52
+ label: "Confirmed"
53
+ color: green
54
+
55
+ wizard:
56
+ steps:
57
+ - name: select-flight
58
+ completesWith: DRAFT
59
+ surface: form
60
+ - name: enter-passengers
61
+ completesWith: READY
62
+ surface: read
63
+ allow: currentUser.role in [ADMIN, AGENT]
64
+
65
+ transitions:
66
+ confirm:
67
+ from: READY
68
+ to: CONFIRMED
69
+ allow: currentUser.role == ADMIN
70
+ ```
71
+
72
+ Rules:
73
+
74
+ - exactly one top-level `workflow <name>:` block per file
75
+ - `model` is required
76
+ - `field` is required
77
+ - `states` is required
78
+ - `transitions` is required
79
+ - `wizard` is optional
80
+
81
+ Richer booking-style example:
82
+
83
+ ```yaml
84
+ workflow booking-process:
85
+ model: Booking
86
+ field: status
87
+
88
+ states:
89
+ DRAFT:
90
+ label: "Draft"
91
+ color: gray
92
+ READY:
93
+ label: "Ready"
94
+ color: blue
95
+ CONFIRMED:
96
+ label: "Confirmed"
97
+ color: green
98
+ FAILED:
99
+ label: "Failed"
100
+ color: red
101
+
102
+ wizard:
103
+ steps:
104
+ - name: select-itinerary
105
+ completesWith: DRAFT
106
+ surface: form
107
+ - name: review-booking
108
+ completesWith: READY
109
+ surface: read
110
+ - name: complete-ticketing
111
+ completesWith: CONFIRMED
112
+ surface: workflow
113
+
114
+ transitions:
115
+ confirm:
116
+ from: READY
117
+ to: CONFIRMED
118
+ allow: currentUser.role in [ADMIN, AGENT]
119
+ fail_ticketing:
120
+ from: READY
121
+ to: FAILED
122
+ allow: currentUser.role in [ADMIN, AGENT]
123
+ reopen:
124
+ from: FAILED
125
+ to: READY
126
+ allow: currentUser.role == ADMIN
127
+ ```
128
+
129
+ Use this kind of thicker example when the task is booking/approval/recovery oriented. It is still
130
+ within the current first-slice workflow surface.
131
+
132
+ ## Supported Keys
133
+
134
+ Top-level workflow keys:
135
+
136
+ - `model`
137
+ - `field`
138
+ - `states`
139
+ - `wizard`
140
+ - `transitions`
141
+
142
+ Inside each `states:` entry:
143
+
144
+ - optional `label`
145
+ - optional `color`
146
+
147
+ Inside each `wizard.steps:` item:
148
+
149
+ - required `name`
150
+ - required `completesWith`
151
+ - optional `surface`
152
+ - optional `allow`
153
+
154
+ Current `surface` values:
155
+
156
+ - `form`
157
+ - `read`
158
+ - `workflow`
159
+
160
+ Current defaulting:
161
+
162
+ - first wizard step defaults to `form`
163
+ - later wizard steps default to `workflow`
164
+
165
+ Inside each `transitions:` entry:
166
+
167
+ - required `from`
168
+ - required `to`
169
+ - optional `allow`
170
+
171
+ ## Expression Language
172
+
173
+ The current flow proof reuses the same constrained expression language used by `.web.loj`,
174
+ `.api.loj`, and `.rules.loj`.
175
+
176
+ Use:
177
+
178
+ - comparisons like `==`, `!=`, `>`, `<`, `>=`, `<=`
179
+ - logical `&&`, `||`, `not`
180
+ - arithmetic like `+`, `-`, `*`, `/`
181
+ - dotted paths like `currentUser.role`, `record.status`
182
+ - membership like `currentUser.role in [ADMIN, AGENT]`
183
+
184
+ Do not use:
185
+
186
+ - raw JavaScript, Java, or Python
187
+ - statements, loops, imports, closures
188
+ - router/state-machine/transaction-framework internals
189
+
190
+ ## Current Command Path
191
+
192
+ Validate:
193
+
194
+ ```bash
195
+ loj flow validate ./workflows/booking-process.flow.loj
196
+ ```
197
+
198
+ Build manifest:
199
+
200
+ ```bash
201
+ loj flow build ./workflows/booking-process.flow.loj --out-dir ./generated/flow
202
+ ```
203
+
204
+ Current build output is a standalone workflow manifest JSON file. Treat it as the first proof
205
+ surface, even though that same manifest is now also consumed by narrow `.api.loj` / `.web.loj`
206
+ resource linkage.
207
+
208
+ ## Current Linked Resource Surfaces
209
+
210
+ Backend:
211
+
212
+ ```yaml
213
+ resource bookings:
214
+ model: Booking
215
+ api: /api/bookings
216
+ workflow: '@flow("./workflows/booking-process")'
217
+ ```
218
+
219
+ - Spring Boot + FastAPI generate workflow-aware create/update wrappers
220
+ - create seeds the initial workflow state from the first wizard step's `completesWith` when present, otherwise the first declared workflow state
221
+ - update preserves the current workflow state rather than accepting direct state mutation through the normal update payload
222
+ - workflow-linked resources also gain `POST /.../{id}/transitions/{transition}` for transition enforcement
223
+
224
+ Frontend:
225
+
226
+ ```yaml
227
+ resource bookings:
228
+ model: Booking
229
+ api: /api/bookings
230
+ workflow:
231
+ source: '@flow("./workflows/booking-process")'
232
+ style: workflowShell
233
+ read:
234
+ fields: [reference, status]
235
+ ```
236
+
237
+ - generated create/edit/read surfaces reuse the linked workflow manifest
238
+ - on `.web.loj`, `workflow:` may be a scalar `@flow("./workflows/x")` or a mapping with
239
+ `source:` plus optional shell-level `style:`
240
+ - `wizard.steps[].surface` now controls narrow generated handoff across existing surfaces: `form` reuses generated edit after record creation, `read` reuses the generated read surface, and `workflow` reuses the fixed generated workflow page
241
+ - create shows workflow state plus visible wizard steps, derives narrow current/next-step summaries, upgrades the primary submit CTA to `Create and continue to <next step>` when a visible next step exists, and defaults successful submit into the next step's declared surface when no explicit redirect or app-local `returnTo` exists
242
+ - edit shows workflow state plus visible wizard steps, derives the same narrow current/next-step summaries, upgrades the primary submit CTA to `Save and continue to <next step>` when a visible next step exists, surfaces a narrow `Workflow` link into the fixed workflow page, and defaults successful submit into that same next-step surface resolution when no explicit redirect or app-local `returnTo` exists
243
+ - read also shows next-step-prioritized allowed transitions, narrow current/next-step summaries, a generated `workflowStep` handoff when later-step review is requested, and a narrow `Redo <previous step>` link when a previous visible step exists; it posts to the generated backend transition route, and successful transitions now also hand off into the next visible wizard step's declared surface when that surface changes
244
+ - generated routing also adds a fixed `/:id/workflow` page that reuses the same manifest for current-state summary, narrow current/next-step summaries, wizard-step progress, next-step-prioritized transition actions, narrow `workflowStep` review handoff, `Redo <previous step>` navigation, post-transition next-step handoff, a narrow related-surface summary derived from existing `read.related` anchors when a read view exists, generated `read.fields` record-context details plus generated `read.related` panel context when a read view exists, preserves workflow links and workflow state labels across label-list related fallbacks when the target resource has no generated read/edit surface, and narrow `View` / `Edit` / `Back` links with sanitized app-local `returnTo`
245
+ - record-scoped relation pages that already exist for the same parent resource also pass a narrow `parentWorkflow` summary into adjacent generated custom blocks when that parent resource is workflow-linked
246
+ - the workflow-controlled enum state field may not be listed as a plain generated `create.fields` or `edit.fields` entry
247
+
248
+ Current expression boundary for linked workflow usage:
249
+
250
+ - wizard step `allow` stays inside the shared expression language
251
+ - web wizard-step visibility currently supports `currentUser`, `record`, `formData`, and bare enum-like literals
252
+ - backend transition enforcement currently supports `currentUser`, `record`, and bare enum-like literals
253
+
254
+ ## Hard Guardrails
255
+
256
+ - Do not invent any `.flow.loj` linkage beyond:
257
+ - `resource workflow: '@flow("./workflows/x")'` in `.api.loj`
258
+ - `resource workflow: '@flow("./workflows/x")'` in `.web.loj`
259
+ - `resource workflow: { source: '@flow("./workflows/x")', style: workflowShell }` in `.web.loj`
260
+ - Do not add policy/rules syntax to `.flow.loj`.
261
+ - Do not add target-specific router or transaction vocabulary to `.flow.loj`.
262
+ - Do not invent `loj.project.yaml` workflow target types yet.
263
+ - Keep workflow authoring declarative and target-neutral.