create-flowdular 0.5.1 → 0.6.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/README.md +8 -5
- package/agent-template/.agents/skills/agent-tool-design/SKILL.md +1 -1
- package/agent-template/.agents/skills/auth-security-review/SKILL.md +1 -1
- package/agent-template/.agents/skills/database-adapter/SKILL.md +5 -5
- package/agent-template/.agents/skills/database-adapter/references/first-run-and-matrix.md +2 -2
- package/agent-template/.agents/skills/deploy-operate/SKILL.md +1 -1
- package/agent-template/.agents/skills/migration-authoring/SKILL.md +4 -4
- package/agent-template/.agents/skills/module-new/SKILL.md +1 -1
- package/agent-template/.agents/skills/test-hardening/SKILL.md +2 -2
- package/agent-template/.agents/skills/workflow-development/SKILL.md +95 -9
- package/agent-template/.ai/blueprints/add-migration/required-files.yaml +1 -1
- package/agent-template/.ai/blueprints/new-module/required-files.yaml +1 -1
- package/agent-template/.ai/platform-capabilities.md +7 -5
- package/agent-template/.ai/policies/capabilities.yaml +28 -12
- package/agent-template/.ai/skills/README.md +1 -1
- package/agent-template/.ai/skills/agent-tool-design/SKILL.md +1 -1
- package/agent-template/.ai/skills/auth-security-review/SKILL.md +1 -1
- package/agent-template/.ai/skills/database-adapter/SKILL.md +5 -5
- package/agent-template/.ai/skills/database-adapter/references/first-run-and-matrix.md +2 -2
- package/agent-template/.ai/skills/deploy-operate/SKILL.md +1 -1
- package/agent-template/.ai/skills/migration-authoring/SKILL.md +4 -4
- package/agent-template/.ai/skills/module-new/SKILL.md +1 -1
- package/agent-template/.ai/skills/test-hardening/SKILL.md +2 -2
- package/agent-template/.ai/skills/workflow-development/SKILL.md +96 -10
- package/agent-template/.claude/skills/agent-tool-design/SKILL.md +1 -1
- package/agent-template/.claude/skills/auth-security-review/SKILL.md +1 -1
- package/agent-template/.claude/skills/database-adapter/SKILL.md +5 -5
- package/agent-template/.claude/skills/database-adapter/references/first-run-and-matrix.md +2 -2
- package/agent-template/.claude/skills/deploy-operate/SKILL.md +1 -1
- package/agent-template/.claude/skills/migration-authoring/SKILL.md +4 -4
- package/agent-template/.claude/skills/module-new/SKILL.md +1 -1
- package/agent-template/.claude/skills/test-hardening/SKILL.md +2 -2
- package/agent-template/.claude/skills/workflow-development/SKILL.md +95 -9
- package/agent-template/docs/adr/0007-module-owned-agents.md +35 -1
- package/agent-template/docs/agent-contract.md +2 -2
- package/agent-template/docs/cli.md +24 -3
- package/agent-template/docs/configuration.md +32 -5
- package/agent-template/docs/database-adapters.md +20 -20
- package/agent-template/docs/design-system.md +1 -1
- package/agent-template/docs/getting-started.md +25 -32
- package/agent-template/docs/module-distribution.md +79 -86
- package/agent-template/docs/module-web-surfaces.md +9 -7
- package/agent-template/docs/modules.md +3 -1
- package/agent-template/platform/scripts/build.mjs +7 -0
- package/dist/bin.js +3 -6
- package/package.json +1 -1
- package/template/default/.env.example +3 -3
- package/template/default/.vercelignore +8 -0
- package/template/default/README.md +20 -11
- package/template/default/_gitignore +3 -2
- package/template/default/infra/README.md +86 -65
- package/template/default/infra/docker/.env.example +66 -0
- package/template/default/infra/docker/Dockerfile +24 -10
- package/template/default/infra/docker/app-entrypoint.mjs +5 -0
- package/template/default/infra/docker/compose.yaml +105 -58
- package/template/default/infra/docker/database-urls.mjs +28 -0
- package/template/default/infra/docker/pitr.sh +177 -0
- package/template/default/infra/docker/postgres/10-roles.sh +16 -12
- package/template/default/infra/docker/start.mjs +402 -0
- package/template/default/infra/kubernetes/database-secret.example.yaml +3 -3
- package/template/default/infra/vercel/README.md +262 -0
- package/template/default/infra/vercel/build.mjs +214 -0
- package/template/default/infra/vercel/handler.mjs +100 -0
- package/template/default/modules/example/migrations/0001_example_core.up.sql +2 -2
- package/template/default/modules/example/module.json +1 -1
- package/template/default/modules/example/package.json +3 -3
- package/template/default/modules/example/spec/module.yaml +1 -1
- package/template/default/modules/example/src/services/migration.ts +2 -2
- package/template/default/modules/example/tests/module.test.ts +1 -1
- package/template/default/package.json +2 -2
- package/template/default/platform/octane.config.ts +252 -156
- package/template/default/platform/package.json +5 -5
- package/template/default/platform/scripts/build.mjs +56 -0
- package/template/default/platform/scripts/dev.mjs +38 -0
- package/template/default/platform/src/generated/modules.server.ts +1 -0
- package/template/default/platform/src/server/database.ts +24 -0
- package/template/default/platform/src/server/runtime-role.ts +33 -0
- package/template/default/platform/src/server/setup/access.ts +160 -0
- package/template/default/platform/src/server/setup/adapters.ts +554 -0
- package/template/default/platform/src/server/setup/environment.ts +154 -0
- package/template/default/platform/src/server/setup/gate.ts +84 -0
- package/template/default/platform/src/server/setup/index.ts +181 -0
- package/template/default/platform/src/server/setup/modules.ts +123 -0
- package/template/default/platform/src/server/setup/page.ts +497 -0
- package/template/default/platform/src/server/setup/routes.ts +787 -0
- package/template/default/platform/src/server/setup/sanitize.ts +111 -0
- package/template/default/platform/src/server/setup/seed.ts +145 -0
- package/template/default/platform/src/server/setup/token.ts +79 -0
- package/template/default/platform/src/server/worker-tick.ts +193 -0
- package/template/default/platform/src/server/workspace-root.ts +16 -0
- package/template/default/render.yaml +70 -0
- package/template/default/vercel.json +5 -0
|
@@ -11,6 +11,7 @@ deployments must set the secret keys.
|
|
|
11
11
|
| `FD_ENV` | `NODE_ENV`, else `development` | Environment the CLI and destructive guards check |
|
|
12
12
|
| `FD_PORT` | `3000` | Host port published by the container |
|
|
13
13
|
| `FD_TRUST_PROXY` | `false` | Trust `X-Forwarded-*` behind a reverse proxy |
|
|
14
|
+
| `FD_RUNTIME_ROLE` | `combined` | `combined` serves HTTP and runs module workers; `web` or `tick`, see below |
|
|
14
15
|
| `FD_CSP` | built-in policy | Override the Content Security Policy |
|
|
15
16
|
| `FD_CSP_REPORT_ONLY` | `true` outside production | Report CSP violations instead of enforcing them |
|
|
16
17
|
| `FD_LOG_FORMAT` | `json` in production, else `text` | `json` (one object per line) or `text`; see [operations.md](operations.md) |
|
|
@@ -18,6 +19,16 @@ deployments must set the secret keys.
|
|
|
18
19
|
| `FD_METRICS` | `false` | Expose `GET /api/metrics`; see [operations.md](operations.md) |
|
|
19
20
|
| `FD_METRICS_TOKEN` | none | Bearer token a metrics scrape must present |
|
|
20
21
|
|
|
22
|
+
A `web` process serves HTTP only: it never starts a module worker and never
|
|
23
|
+
claims queued work from a request, so a deployment of `web` processes also needs
|
|
24
|
+
a `combined` or a `tick` process on the same database, object storage and keys.
|
|
25
|
+
A `tick` process runs the module workers only inside a tick request that
|
|
26
|
+
presents `FD_WORKER_TICK_SECRET` (at least 32 characters) as a bearer token, for
|
|
27
|
+
at most `FD_WORKER_TICK_WINDOW_MS` (default 50000), and drains them before it
|
|
28
|
+
answers.
|
|
29
|
+
A Vercel deployment works this way; see
|
|
30
|
+
[infra/vercel/README.md](../infra/vercel/README.md).
|
|
31
|
+
|
|
21
32
|
## Branding
|
|
22
33
|
|
|
23
34
|
The name, the document title, the description, the link preview image, the
|
|
@@ -289,6 +300,7 @@ message was not delivered.
|
|
|
289
300
|
| `FD_AGENT_RUN_GRANT_KEY` | generated dev key | Base64 32-byte key signing run grants |
|
|
290
301
|
| `FD_AGENT_WORKER_CONCURRENCY` | `2` (1 to 16) | Parallel run workers |
|
|
291
302
|
| `FD_AGENT_WORKER_LEASE_MS` | `30000` | Run lease before recovery reclaims it |
|
|
303
|
+
| `FD_AGENT_WORKER_DRAIN_MS` | `0` (0 to 720000) | Time a stopping worker lets claimed runs finish |
|
|
292
304
|
| `FD_AGENT_PROVIDER_HOST_ALLOWLIST` | empty | Hostnames an external provider may be called on |
|
|
293
305
|
|
|
294
306
|
Outside production the keys are generated once under `.flowdular/data`. The
|
|
@@ -658,7 +670,7 @@ an object moved into another tenant's prefix does not open.
|
|
|
658
670
|
|
|
659
671
|
| Variable | Default | Purpose |
|
|
660
672
|
| ------------------------------------ | ----------------------------------- | ---------------------------------------------------------------------------- |
|
|
661
|
-
| `FD_STORAGE_ADAPTER` | `s3` in production, else `local` | `local` or `
|
|
673
|
+
| `FD_STORAGE_ADAPTER` | `s3` in production, else `local` | `local`, `s3` or `vercel-blob`; `local` is refused in production |
|
|
662
674
|
| `FD_STORAGE_LOCAL_DIRECTORY` | `.flowdular/data/storage` | Object directory of the local adapter |
|
|
663
675
|
| `FD_STORAGE_S3_BUCKET` | none | Bucket name; required by the S3 adapter |
|
|
664
676
|
| `FD_STORAGE_S3_REGION` | none | Signing region; required by the S3 adapter |
|
|
@@ -666,10 +678,23 @@ an object moved into another tenant's prefix does not open.
|
|
|
666
678
|
| `FD_STORAGE_S3_ACCESS_KEY_ID` | none | Access key id; required by the S3 adapter |
|
|
667
679
|
| `FD_STORAGE_S3_SECRET_ACCESS_KEY` | none | Secret access key; required by the S3 adapter |
|
|
668
680
|
| `FD_STORAGE_S3_FORCE_PATH_STYLE` | `false` | `<endpoint>/<bucket>/<key>` instead of a bucket subdomain |
|
|
681
|
+
| `BLOB_STORE_ID` | set by Vercel | Blob store of the `vercel-blob` adapter, authenticated with Vercel OIDC |
|
|
682
|
+
| `BLOB_READ_WRITE_TOKEN` | none | Blob read-write token for the `vercel-blob` adapter outside Vercel |
|
|
669
683
|
| `FD_STORAGE_MAX_OBJECT_BYTES` | `26214400` (25 MiB) | Per-object limit, 1024 to 268435456; a stream is cut off at it |
|
|
670
684
|
| `FD_STORAGE_ENCRYPTION_KEY` | derived dev key | Base64 32-byte key sealing every object and read URL; required in production |
|
|
671
685
|
| `FD_STORAGE_ENCRYPTION_KEY_PREVIOUS` | empty | Retired object keys, comma separated, read only |
|
|
672
686
|
|
|
687
|
+
`vercel-blob` keeps the encrypted objects in a private Vercel Blob store, so a
|
|
688
|
+
Vercel deployment needs no separate bucket. Connecting a Blob store to the
|
|
689
|
+
Vercel project sets `BLOB_STORE_ID`, and the SDK authenticates with the
|
|
690
|
+
deployment's OIDC token, so nothing else is configured there. Outside Vercel,
|
|
691
|
+
set `BLOB_READ_WRITE_TOKEN`. The platform refuses to start when neither is
|
|
692
|
+
present. Reads bypass the Blob cache, so a re-sealed or deleted object is never
|
|
693
|
+
served from an older copy. A Vercel Function accepts at most 4.5 MB of request
|
|
694
|
+
or response body, so set `FD_STORAGE_MAX_OBJECT_BYTES` to at most `4194304`
|
|
695
|
+
there. Lowering the limit makes an existing larger object unreadable through
|
|
696
|
+
this adapter, and a key rotation leaves it sealed under its old key.
|
|
697
|
+
|
|
673
698
|
A module writes through `context.storage` and never sees an adapter, a bucket or
|
|
674
699
|
a path. Only these content types are stored, and the bytes are verified against
|
|
675
700
|
the declared type before the write: PDF, PNG, JPEG, GIF, WebP, plain text, CSV,
|
|
@@ -746,7 +771,9 @@ acquires a lease from the platform provider configured above, so the
|
|
|
746
771
|
openssl rand -base64 32
|
|
747
772
|
```
|
|
748
773
|
|
|
749
|
-
For
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
passwords
|
|
774
|
+
For a local Docker installation, run `node infra/docker/start.mjs`. It creates
|
|
775
|
+
`infra/docker/.env` with missing secrets, starts PostgreSQL and the bundled
|
|
776
|
+
object store, then opens the first-run web setup. Keep that file with database
|
|
777
|
+
and object-store backups; changing its keys or passwords later does not rotate
|
|
778
|
+
existing data or PostgreSQL roles. Other deployments supply these values through
|
|
779
|
+
their own secret manager. See [../infra/README.md](../infra/README.md).
|
|
@@ -79,11 +79,11 @@ memory.
|
|
|
79
79
|
The embedded adapter creates the same roles a deployment configures, so a local
|
|
80
80
|
run enforces the isolation a deployment enforces instead of approximating it.
|
|
81
81
|
|
|
82
|
-
| Role
|
|
83
|
-
|
|
|
84
|
-
| `
|
|
85
|
-
| `
|
|
86
|
-
| `
|
|
82
|
+
| Role | Owns | Constraints |
|
|
83
|
+
| ---------------------- | ------------------------------------------- | ------------------------------------------------------------------------------------------- |
|
|
84
|
+
| `flowdular_migrator` | The schema. Serves the `migration` purpose. | Owns every table the migrations create. |
|
|
85
|
+
| `flowdular_runtime` | Request-time reads and writes. | No `SUPERUSER`, no `BYPASSRLS`, and every handle it lends requires a transaction tenant id. |
|
|
86
|
+
| `flowdular_background` | Cross-tenant polls. | Read-only, and no blanket table grant. |
|
|
87
87
|
|
|
88
88
|
## Leases
|
|
89
89
|
|
|
@@ -100,13 +100,13 @@ const lease = await context.databases.acquire({
|
|
|
100
100
|
});
|
|
101
101
|
```
|
|
102
102
|
|
|
103
|
-
| Purpose | Role
|
|
104
|
-
| ------------ |
|
|
105
|
-
| `migration` | `
|
|
106
|
-
| `runtime` | `
|
|
107
|
-
| `preview` | `
|
|
108
|
-
| `test` | `
|
|
109
|
-
| `background` | `
|
|
103
|
+
| Purpose | Role | Used for |
|
|
104
|
+
| ------------ | ---------------------- | --------------------------------------------------- |
|
|
105
|
+
| `migration` | `flowdular_migrator` | Applying migrations and resetting a database |
|
|
106
|
+
| `runtime` | `flowdular_runtime` | The deployed application |
|
|
107
|
+
| `preview` | `flowdular_runtime` | A local run and the sandbox preview |
|
|
108
|
+
| `test` | `flowdular_runtime` | A test suite |
|
|
109
|
+
| `background` | `flowdular_background` | A scheduler poll or recovery that precedes a tenant |
|
|
110
110
|
|
|
111
111
|
A runtime acquires its leases lazily, one per runtime, and releases them from
|
|
112
112
|
composition `dispose()`. `modules/profile/src/server/runtime.ts` is the shape:
|
|
@@ -164,14 +164,14 @@ codes.
|
|
|
164
164
|
|
|
165
165
|
Every tenant table enables and forces row-level security and carries a policy
|
|
166
166
|
whose `USING` and `WITH CHECK` compare `tenant_id` with the transaction-local
|
|
167
|
-
`
|
|
167
|
+
`flowdular.tenant_id` setting:
|
|
168
168
|
|
|
169
169
|
```sql
|
|
170
170
|
ALTER TABLE profile_records ENABLE ROW LEVEL SECURITY;
|
|
171
171
|
ALTER TABLE profile_records FORCE ROW LEVEL SECURITY;
|
|
172
172
|
CREATE POLICY profile_records_tenant_policy ON profile_records
|
|
173
|
-
USING (tenant_id = current_setting('
|
|
174
|
-
WITH CHECK (tenant_id = current_setting('
|
|
173
|
+
USING (tenant_id = current_setting('flowdular.tenant_id', true))
|
|
174
|
+
WITH CHECK (tenant_id = current_setting('flowdular.tenant_id', true));
|
|
175
175
|
```
|
|
176
176
|
|
|
177
177
|
`database.transaction(body, { tenantId, access })` sets that value for the
|
|
@@ -201,7 +201,7 @@ repository.
|
|
|
201
201
|
|
|
202
202
|
## Migrations
|
|
203
203
|
|
|
204
|
-
The ledger is `
|
|
204
|
+
The ledger is `_flowdular_migrations_v2`. It carries the module namespace because
|
|
205
205
|
every module shares one database. Checksums cover the exact SQL, and a mismatch
|
|
206
206
|
is checked before any outstanding migration runs.
|
|
207
207
|
|
|
@@ -257,7 +257,7 @@ schema.
|
|
|
257
257
|
`migration verify` checks that every applied ledger checksum still matches, that
|
|
258
258
|
every tenant table a migration leaves behind has `ENABLE ROW LEVEL SECURITY`,
|
|
259
259
|
`FORCE ROW LEVEL SECURITY` and a tenant policy declared after the last statement
|
|
260
|
-
that puts the table in place, that a `
|
|
260
|
+
that puts the table in place, that a `flowdular_background` policy grants no more
|
|
261
261
|
than `FOR SELECT`, and that every `migrations/*.up.sql` file has a matching id in
|
|
262
262
|
`databaseMigrations` and the other way round.
|
|
263
263
|
|
|
@@ -272,10 +272,10 @@ A table it may poll says so itself, in its own migration:
|
|
|
272
272
|
|
|
273
273
|
```sql
|
|
274
274
|
CREATE POLICY <table>_background_policy ON <table>
|
|
275
|
-
FOR SELECT TO
|
|
275
|
+
FOR SELECT TO flowdular_background
|
|
276
276
|
USING (<the narrowest predicate that still finds the work>);
|
|
277
|
-
REVOKE SELECT ON <table> FROM
|
|
278
|
-
GRANT SELECT (<only the columns the poll reads>) ON <table> TO
|
|
277
|
+
REVOKE SELECT ON <table> FROM flowdular_background;
|
|
278
|
+
GRANT SELECT (<only the columns the poll reads>) ON <table> TO flowdular_background;
|
|
279
279
|
```
|
|
280
280
|
|
|
281
281
|
A table that forgets to is invisible to that role, and every column outside the
|
|
@@ -270,7 +270,7 @@ them; outside the shell the English defaults and the host locale apply.
|
|
|
270
270
|
| `Tabs` | Accessible tablist: required `id`, `items` (`id`, `label`, `disabled`), `active`, `onChange`, `label`; arrows, Home and End move roving focus, an `active` that names no enabled tab selects the first enabled one, and the caller renders the panel |
|
|
271
271
|
| `SortableList` | Reorderable list: required `id`, `label`, `items` (`id`, `label`), `onReorder(ids)`, `handleLabel(item)`, `instructions`, `announce(event)`, `renderItem(item, index)`, `disabled`; a grip per item drags with a pointer or picks up, moves and drops from the keyboard, with a polite live region |
|
|
272
272
|
| `ToastHost` | Renders the toast queue: `label`, `closeLabel`, optional `store`. Raise toasts with `toasts.success/error/info(message)`; `createToastStore` makes a scoped queue |
|
|
273
|
-
| `PageHeader` | Every view starts with it: `eyebrow`, `title`, `description`; children render as right-side actions
|
|
273
|
+
| `PageHeader` | Every view starts with it: `eyebrow`, `title`, `description`; children render as right-side actions, which move under the title and wrap onto more rows when the header is too narrow |
|
|
274
274
|
| `EmptyState` | `icon`, `title`, children, optional `code` |
|
|
275
275
|
| `Alert` | Inline message: `tone` danger (default), warning, info |
|
|
276
276
|
| `Drawer` | Editor panel over the records: `open`, `title`, `subtitle`, `width` md/lg, `onClose`; traps Tab and restores focus to the opener |
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Getting started
|
|
2
2
|
|
|
3
|
-
Run Flowdular on your machine,
|
|
3
|
+
Run Flowdular on your machine, create a workspace, and sign in.
|
|
4
4
|
|
|
5
5
|
## Requirements
|
|
6
6
|
|
|
@@ -19,48 +19,40 @@ pnpm flowdular doctor
|
|
|
19
19
|
`doctor` reports workspace health (configuration, enabled modules, generated
|
|
20
20
|
composition, guardrail files). Add `--json` for a machine-readable envelope.
|
|
21
21
|
|
|
22
|
-
##
|
|
23
|
-
|
|
24
|
-
`pnpm flowdular setup` opens an interactive wizard. Choose a local demo, configure PostgreSQL, or check the existing configuration. Local initialization requires confirmation and a stopped application.
|
|
25
|
-
|
|
26
|
-
For scripts and CI, `setup quick` is a destructive local reset. It prints its full plan first and
|
|
27
|
-
writes only after a typed confirmation:
|
|
22
|
+
## Run the platform
|
|
28
23
|
|
|
29
24
|
```bash
|
|
30
|
-
pnpm
|
|
31
|
-
pnpm flowdular setup quick --apply --confirm reset-local-auth # resets and seeds
|
|
25
|
+
pnpm dev
|
|
32
26
|
```
|
|
33
27
|
|
|
34
|
-
|
|
35
|
-
|
|
28
|
+
On the first run, open [localhost:4310/setup](http://localhost:4310/setup).
|
|
29
|
+
Enter the one-time token from the terminal, then create your
|
|
30
|
+
workspace and owner account. Embedded PostgreSQL is already configured. Restart
|
|
31
|
+
`pnpm dev` after setup and sign in with that account.
|
|
36
32
|
|
|
37
|
-
|
|
33
|
+
Vite HMR covers TSRX, TypeScript and styles. The launcher keeps tool warnings
|
|
34
|
+
quiet; use `pnpm dev -- --verbose` for full diagnostics. `pnpm dev` runs
|
|
35
|
+
`module sync` first, so a composition change is picked up without a manual
|
|
36
|
+
step. The session lives in an HttpOnly cookie and carries the scopes of the
|
|
37
|
+
selected tenant membership. A bookmark pointing at another workspace you
|
|
38
|
+
belong to switches the session on load.
|
|
38
39
|
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
| `admin@example.com` | `Owner!23456789` | Owner of both demo tenants |
|
|
42
|
-
| `user@example.com` | `Member!2345678` | Reduced scope member |
|
|
40
|
+
`Development` navigation is visible only to tenant owners; server permissions
|
|
41
|
+
stay authoritative either way.
|
|
43
42
|
|
|
44
|
-
##
|
|
43
|
+
## Optional local demo reset
|
|
44
|
+
|
|
45
|
+
`pnpm flowdular setup quick` resets local authentication data and seeds two
|
|
46
|
+
demo workspaces. It is for development or test databases only. Stop `pnpm dev`
|
|
47
|
+
and review the dry-run plan before applying it:
|
|
45
48
|
|
|
46
49
|
```bash
|
|
47
|
-
pnpm
|
|
50
|
+
pnpm flowdular setup quick # dry run, prints the plan
|
|
51
|
+
pnpm flowdular setup quick --apply --confirm reset-local-auth # resets and seeds
|
|
48
52
|
```
|
|
49
53
|
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
diagnostics. `pnpm dev` runs `module sync` first, so a composition change is
|
|
53
|
-
picked up without a manual step.
|
|
54
|
-
|
|
55
|
-
The first visit opens the `auth.core` sign-in flow. The session lives in an
|
|
56
|
-
HttpOnly cookie and carries the scopes of the selected tenant membership. On a
|
|
57
|
-
clean database the sign-up wizard is available: workspace name plus a unique
|
|
58
|
-
workspace id (the first URL segment, `/{workspace}/{view}`), then the
|
|
59
|
-
administrator account, then an optional email confirmation step. A bookmark
|
|
60
|
-
pointing at another workspace you belong to switches the session on load.
|
|
61
|
-
|
|
62
|
-
`Development` navigation is visible only to tenant owners; server permissions
|
|
63
|
-
stay authoritative either way.
|
|
54
|
+
The demo logins are `admin@example.com` / `Owner!23456789` (owner) and
|
|
55
|
+
`user@example.com` / `Member!2345678` (member).
|
|
64
56
|
|
|
65
57
|
## Where local state lives
|
|
66
58
|
|
|
@@ -69,6 +61,7 @@ module shares one embedded PostgreSQL in `.flowdular/data/pglite`, which
|
|
|
69
61
|
`FD_DATABASE_PGLITE_DIRECTORY` can redirect. Point `FD_DATABASE_ADAPTER` at
|
|
70
62
|
`postgresql` and give it `FD_DATABASE_URL` to run against a real server instead.
|
|
71
63
|
See [configuration.md](configuration.md).
|
|
64
|
+
Workspaces from Flowdular 0.5 or earlier: see [flowdular-rename.md](https://github.com/flowdular/flowdular/blob/main/docs/flowdular-rename.md).
|
|
72
65
|
|
|
73
66
|
## Migrating preserved state from `.octane-erp`
|
|
74
67
|
|
|
@@ -1,103 +1,96 @@
|
|
|
1
|
-
#
|
|
1
|
+
# Module Studio
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
3
|
+
Module Studio uses one reviewable plan for a module source change. A platform
|
|
4
|
+
checkout owns its sources in `flowdular.module-sources.json`, its exact plans in
|
|
5
|
+
`module-plans/<sha256>.json`, and installed source hashes in
|
|
6
|
+
`flowdular.modules.lock.json`. Commit these files with the application when
|
|
7
|
+
they change. A catalog can be a local JSON file or an HTTPS URL. A Git source
|
|
8
|
+
names a repository and a full commit, and may itself be local or HTTPS. No
|
|
9
|
+
publisher or repository name is built into the installer.
|
|
7
10
|
|
|
8
11
|
```sh
|
|
9
|
-
pnpm flowdular module
|
|
10
|
-
pnpm flowdular module
|
|
11
|
-
|
|
12
|
-
pnpm flowdular module
|
|
12
|
+
pnpm flowdular module source add community https://modules.example/registry/index.json --apply
|
|
13
|
+
pnpm flowdular module source add team https://github.com/acme/modules.git \
|
|
14
|
+
--git-commit <40-character-commit> --catalog-path registry/index.json --apply
|
|
15
|
+
pnpm flowdular module source add local ./registry/index.json --apply
|
|
16
|
+
pnpm flowdular module source list
|
|
17
|
+
pnpm flowdular module search expenses --source community
|
|
18
|
+
pnpm flowdular module plan expenses.core@1.2.0 --source community --apply
|
|
19
|
+
pnpm flowdular module plan show <plan-id>
|
|
20
|
+
pnpm flowdular module apply <plan-id> --apply
|
|
13
21
|
pnpm flowdular module enable expenses.core --apply
|
|
14
|
-
pnpm
|
|
22
|
+
pnpm build
|
|
15
23
|
```
|
|
16
24
|
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
`
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
25
|
+
`source add`, `plan`, and `apply` preview their work unless `--apply` is present.
|
|
26
|
+
With exactly one configured source, `--source` may be omitted. `module plan
|
|
27
|
+
list` shows saved plans; `module plan remove <plan-id> --apply` deletes an
|
|
28
|
+
obsolete plan. At most 32 plans may exist at once, with 4 MiB per plan and
|
|
29
|
+
8 MiB in total. A plan records the selected
|
|
30
|
+
releases, artifact SHA-256, Git commit where applicable, dependency closure,
|
|
31
|
+
requested permissions, migration file hashes, and server/client build impact.
|
|
32
|
+
Its ID hashes the plan content. Applying it refuses changed workspace module
|
|
33
|
+
manifests or a changed install lock and verifies every artifact again. If the
|
|
34
|
+
source is no longer available, regenerate or provide it again; the plan does
|
|
35
|
+
not contain executable source bytes.
|
|
36
|
+
|
|
37
|
+
Installation copies reviewed source into a configured module root and writes
|
|
38
|
+
the install lock. It does not run downloaded scripts, npm install, migrations,
|
|
39
|
+
or permission grants. `module enable --apply` links the package, regenerates
|
|
40
|
+
composition and follows the platform's scope grant process. Rebuild and
|
|
41
|
+
restart the application to serve new code. A deployed container shows plans
|
|
42
|
+
and module state in Administration, Module Studio; it never writes its own code.
|
|
43
|
+
For creating or changing your own module, the same view links to Sandbox when
|
|
44
|
+
`sandbox.core` is active. Sandbox keeps the approved spec hash, gate and PR
|
|
45
|
+
checks, and delivers to the workspace or its configured Git repository.
|
|
37
46
|
|
|
38
47
|
## Trust and recovery
|
|
39
48
|
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
49
|
+
Only a configured host CLI resolves sources. Sandbox specialists do not gain
|
|
50
|
+
network, Git, database or filesystem permissions from a source entry. HTTPS
|
|
51
|
+
downloads reject redirects and have time and size limits. HTTPS artifacts must
|
|
52
|
+
use the catalog origin and a path containing their declared source commit.
|
|
53
|
+
The artifact digest is verified after download. Git checkouts verify their full
|
|
54
|
+
commit. Catalogs and artifacts are bounded; paths, manifests, package scripts,
|
|
55
|
+
review evidence and approved specs are checked before a plan is saved. A
|
|
56
|
+
checksum identifies bytes but does not certify a publisher, so review the
|
|
57
|
+
source and permissions before `module apply` and `module enable`.
|
|
58
|
+
|
|
59
|
+
Updates use `module plan <id[@version]> --source <name> --update --apply`.
|
|
60
|
+
The installer refuses local changes, changed historical migrations and
|
|
61
|
+
downgrades. `module validate --locked` checks installed file hashes. Installs
|
|
62
|
+
use an exclusive transaction directory; after a process crash, run `module
|
|
63
|
+
recover` and then `module recover --apply`. Recovery restores source and lock
|
|
64
|
+
state, not database migrations. Source-list edits use a separate
|
|
65
|
+
`flowdular.module-sources.json.lock` directory. If a host process crashes
|
|
66
|
+
during that short write, confirm no other source command is running and remove
|
|
67
|
+
that stale directory before retrying.
|
|
68
|
+
Plan writes use the same atomic pattern and a `module-plans.lock` directory.
|
|
69
|
+
After a crash, confirm the writer has stopped and remove a stale plan lock
|
|
70
|
+
before retrying; no partial plan is published.
|
|
71
|
+
|
|
72
|
+
Scripts that called `module install` or `module update` with `--registry` must
|
|
73
|
+
add that catalog as a named source, save a plan, and apply its ID. An existing
|
|
74
|
+
`flowdular.modules.lock.json` remains the installed-source record, so an
|
|
75
|
+
already managed module can use `module plan <id> --update --apply` for its next
|
|
76
|
+
release. No direct registry installation path remains.
|
|
77
|
+
Automation that imported `installModule` from `flowdular/distribution` must use
|
|
78
|
+
the host CLI plan and apply commands; that direct install export was removed.
|
|
79
|
+
|
|
80
|
+
The old `official-modules` Sandbox delivery target was removed. Change
|
|
81
|
+
`sandbox.delivery.targets` to `workspace` or `git-pr`; `git-pr` points to the
|
|
82
|
+
platform repository configured in `flowdular.json`. A catalog publisher can
|
|
83
|
+
use any Git repository and publish immutable artifacts independently. Historical
|
|
84
|
+
review and RFC documents retain the old project name as provenance.
|
|
61
85
|
|
|
62
86
|
## SDK publication and consumer checks
|
|
63
87
|
|
|
64
88
|
```sh
|
|
65
89
|
pnpm release:pack
|
|
66
90
|
pnpm release:smoke
|
|
67
|
-
# Also install and test actual source artifacts from the official repo:
|
|
68
|
-
node scripts/smoke-sdk.mjs release-artifacts/sdk /path/to/official-modules/registry/local-index.json
|
|
69
91
|
```
|
|
70
92
|
|
|
71
|
-
`release-artifacts/sdk/sdk.json` lists
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
artifacts outside runtime state directories; creating both `.flowdular` and legacy
|
|
76
|
-
`.coreloom` state would correctly stop the application.
|
|
77
|
-
|
|
78
|
-
The smoke test creates a separate project and resolves SDK dependencies from
|
|
79
|
-
packed artifacts, without aliases or symlinks into core sources. With a module
|
|
80
|
-
catalog it installs all module source artifacts, enables their source composition,
|
|
81
|
-
checks the lock, typechecks and executes the consumer's tests. Its composition
|
|
82
|
-
check does not grant tenant scopes; auth CLI tests cover that separate boundary.
|
|
83
|
-
|
|
84
|
-
Sandbox examples use `.ai/references/catalog`, generated from a reviewed official
|
|
85
|
-
artifact. `pnpm reference:check` verifies every file against the pinned provenance
|
|
86
|
-
record. Do not edit the generated reference. To update it, build the CLI and run
|
|
87
|
-
`scripts/module-reference.mjs` with `--artifact`, `--sha256`, `--source-commit` and
|
|
88
|
-
`--apply`. Skills and sandbox preparation refer to that offline snapshot.
|
|
89
|
-
|
|
90
|
-
`pnpm release:publish` previews the exact publication set after validating every
|
|
91
|
-
artifact digest. The operator can then use `pnpm release:publish --apply` after npm
|
|
92
|
-
authentication. It skips already-published identical tarballs and stops if a
|
|
93
|
-
version exists with different bytes. No npm publication is performed by packing,
|
|
94
|
-
smoke testing or the default publication preview.
|
|
95
|
-
|
|
96
|
-
The `SDK consumer smoke` workflow (`.github/workflows/sdk-release.yml`, job
|
|
97
|
-
`consumer`) runs the pack and the smoke on every pull request and push to main
|
|
98
|
-
that touches `packages`, `modules` or `scripts`. It never publishes: its token
|
|
99
|
-
can only read the repository, and the packed tarballs are kept as an Actions
|
|
100
|
-
artifact only after a merge to main. `pnpm verify` is not repeated there, the
|
|
101
|
-
CI workflow owns it.
|
|
102
|
-
|
|
103
|
-
The SDK is assembled from private internal workspaces. Import UI from `@flowdular/sdk/ui` and styles from `@flowdular/sdk/ui/styles`; use `@flowdular/sdk/server`, `client`, `contracts` or `modules/<name>` for other surfaces. There is no root SDK barrel, so browser imports do not load server entrypoints. See [npm publication](https://github.com/flowdular/flowdular/blob/main/docs/npm-publication.md).
|
|
93
|
+
`release-artifacts/sdk/sdk.json` lists the SDK, CLI, project generator and
|
|
94
|
+
Sandbox tarballs with SHA-256 digests. Publication is separate from packing
|
|
95
|
+
and smoke tests. The project generator and SDK must be released together so a
|
|
96
|
+
new application's module tooling sees the same contracts.
|
|
@@ -140,13 +140,15 @@ in front of it, as the bundled Node server does.
|
|
|
140
140
|
### Backoffice address
|
|
141
141
|
|
|
142
142
|
The first-run setup includes **Backoffice address**, defaulting to `/app` (or the
|
|
143
|
-
installation's configured default).
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
143
|
+
installation's configured default). When choosing a database in the wizard, an
|
|
144
|
+
operator can choose `/backoffice` to leave `/` available for a storefront. Setup
|
|
145
|
+
validates the address, includes it in the review, and writes
|
|
146
|
+
`FD_APPLICATION_PATH=/backoffice` alongside the database settings. Restart after
|
|
147
|
+
setup; no client rebuild is needed. When the database is already configured by
|
|
148
|
+
the deployment, the wizard skips that step and the deployment's
|
|
149
|
+
`FD_APPLICATION_PATH` controls the address. A read-only deployment must store
|
|
150
|
+
new settings in its environment before restart. Setup never echoes connection
|
|
151
|
+
secrets back to the browser.
|
|
150
152
|
|
|
151
153
|
For configuration managed in source control, add:
|
|
152
154
|
|
|
@@ -222,7 +222,9 @@ dependents, which the bump command does. `module validate` reports
|
|
|
222
222
|
`PLATFORM_API_MISSING` when a manifest lacks `platformApi`.
|
|
223
223
|
|
|
224
224
|
`platformApi` is the range of the platform contract the module compiles against
|
|
225
|
-
(`^0.
|
|
225
|
+
(`^0.2.0`). Module sync and install refuse a manifest without one, and a range
|
|
226
|
+
that also admits a version before the platform's current minor line, such as
|
|
227
|
+
`*` or `>=0.1.0`. The contract surface is pinned in
|
|
226
228
|
`packages/kernel/platform-api.snapshot.d.ts`; a change to it without a
|
|
227
229
|
`PLATFORM_API_VERSION` bump fails `pnpm verify`. `module search --compatible`
|
|
228
230
|
lists only releases whose range accepts the running platform.
|
|
@@ -7,6 +7,13 @@ import { join } from 'node:path';
|
|
|
7
7
|
const stateDirectory = mkdtempSync(join(tmpdir(), 'flowdular-build-'));
|
|
8
8
|
const buildSecret = () => randomBytes(32).toString('base64');
|
|
9
9
|
|
|
10
|
+
/* Vite clears its output trees, but can leave hidden runtime state at the
|
|
11
|
+
dist root. A prior preview must never become part of the next bundle. */
|
|
12
|
+
rmSync(join(import.meta.dirname, '..', 'dist'), {
|
|
13
|
+
recursive: true,
|
|
14
|
+
force: true,
|
|
15
|
+
});
|
|
16
|
+
|
|
10
17
|
/* The Octane plugin evaluates the server composition while bundling it. Give
|
|
11
18
|
that build-time process isolated state and ephemeral keys, without using deployment database settings or encryption keys. The emitted server still reads its real
|
|
12
19
|
production environment when it starts. */
|
package/dist/bin.js
CHANGED
|
@@ -294,7 +294,6 @@ function nextSteps(input) {
|
|
|
294
294
|
return [
|
|
295
295
|
`cd ${input.directory}`,
|
|
296
296
|
...input.installed ? [] : [`${input.packageManager} install`],
|
|
297
|
-
runScript(input.packageManager, "flowdular", "setup"),
|
|
298
297
|
runScript(input.packageManager, "dev")
|
|
299
298
|
];
|
|
300
299
|
}
|
|
@@ -303,8 +302,7 @@ function renderNextSteps(input, color = false) {
|
|
|
303
302
|
const labels = [
|
|
304
303
|
"Open your project",
|
|
305
304
|
...input.installed ? [] : ["Install dependencies"],
|
|
306
|
-
"
|
|
307
|
-
"Start your app"
|
|
305
|
+
"Start your app and first-run setup"
|
|
308
306
|
];
|
|
309
307
|
return [
|
|
310
308
|
"",
|
|
@@ -322,9 +320,8 @@ function renderNextSteps(input, color = false) {
|
|
|
322
320
|
` ${paint("Build a module by chat", "dim")} ${paint(runScript(input.packageManager, "sandbox"), "bold")}`,
|
|
323
321
|
` ${paint("Agent guidance", "dim")} AGENTS.md, CLAUDE.md, .ai/skills`,
|
|
324
322
|
"",
|
|
325
|
-
` ${paint("
|
|
326
|
-
` ${paint("
|
|
327
|
-
` ${paint("Demo account is created only with local demo setup.", "dim")}`,
|
|
323
|
+
` ${paint("Setup URL", "dim")} ${paint(`${DEV_URL}/setup`, "cyan")}`,
|
|
324
|
+
` ${paint("Setup opens in a browser when available. Use the printed token, then choose your workspace owner.", "dim")}`,
|
|
328
325
|
""
|
|
329
326
|
].join("\n");
|
|
330
327
|
}
|
package/package.json
CHANGED
|
@@ -13,9 +13,9 @@ FD_PORT=3000
|
|
|
13
13
|
# every tenant table forces actually binds the app, and the background role
|
|
14
14
|
# serves the cross-tenant scheduler poll. pglite is refused in production.
|
|
15
15
|
FD_DATABASE_ADAPTER=postgresql
|
|
16
|
-
FD_DATABASE_URL=postgresql://
|
|
17
|
-
FD_DATABASE_MIGRATOR_URL=postgresql://
|
|
18
|
-
FD_DATABASE_BACKGROUND_URL=postgresql://
|
|
16
|
+
FD_DATABASE_URL=postgresql://flowdular_runtime:REPLACE_ME@postgres:5432/flowdular
|
|
17
|
+
FD_DATABASE_MIGRATOR_URL=postgresql://flowdular_migrator:REPLACE_ME@postgres:5432/flowdular
|
|
18
|
+
FD_DATABASE_BACKGROUND_URL=postgresql://flowdular_background:REPLACE_ME@postgres:5432/flowdular
|
|
19
19
|
FD_DATABASE_TLS=verify-full
|
|
20
20
|
FD_DATABASE_TLS_CA_FILE=/tls/server.crt
|
|
21
21
|
|