create-flowdular 0.4.3 → 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 +16 -10
- package/agent-template/.agents/skills/agent-tool-design/SKILL.md +1 -1
- package/agent-template/.agents/skills/auth-security-review/SKILL.md +2 -2
- package/agent-template/.agents/skills/bug-hunt/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/spec-interview/SKILL.md +20 -20
- package/agent-template/.agents/skills/test-hardening/SKILL.md +2 -2
- package/agent-template/.agents/skills/ux-design/SKILL.md +1 -1
- package/agent-template/.agents/skills/workflow-development/SKILL.md +95 -9
- package/agent-template/.ai/README.md +5 -3
- package/agent-template/.ai/agents/README.md +1 -1
- package/agent-template/.ai/agents/sandbox/agentic-engineer.md +1 -0
- package/agent-template/.ai/agents/sandbox/backend-engineer.md +1 -0
- package/agent-template/.ai/agents/sandbox/frontend-engineer.md +1 -0
- package/agent-template/.ai/blueprints/add-migration/README.md +1 -1
- 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/examples/bad/client-imports-server/README.md +1 -1
- package/agent-template/.ai/examples/bad/missing-acl/README.md +1 -1
- package/agent-template/.ai/examples/bad/tenant-from-body/README.md +1 -1
- package/agent-template/.ai/guides/application-development.md +7 -5
- package/agent-template/.ai/platform-capabilities.md +9 -5
- package/agent-template/.ai/policies/capabilities.yaml +28 -12
- package/agent-template/.ai/policies/task-budgets.yaml +1 -1
- package/agent-template/.ai/rules/flowdular.md +3 -2
- 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 +2 -2
- package/agent-template/.ai/skills/bug-hunt/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/spec-interview/SKILL.md +20 -20
- package/agent-template/.ai/skills/test-hardening/SKILL.md +2 -2
- package/agent-template/.ai/skills/ux-design/SKILL.md +1 -1
- package/agent-template/.ai/skills/workflow-development/SKILL.md +96 -10
- package/agent-template/.ai/subagents/module-executor.md +25 -0
- package/agent-template/.ai/subagents/reviewer.md +23 -0
- package/agent-template/.ai/subagents/spec-author.md +23 -0
- package/agent-template/.claude/agents/module-executor.md +22 -0
- package/agent-template/.claude/agents/reviewer.md +24 -0
- package/agent-template/.claude/agents/spec-author.md +20 -0
- package/agent-template/.claude/skills/agent-tool-design/SKILL.md +1 -1
- package/agent-template/.claude/skills/auth-security-review/SKILL.md +2 -2
- package/agent-template/.claude/skills/bug-hunt/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/spec-interview/SKILL.md +20 -20
- package/agent-template/.claude/skills/test-hardening/SKILL.md +2 -2
- package/agent-template/.claude/skills/ux-design/SKILL.md +1 -1
- package/agent-template/.claude/skills/workflow-development/SKILL.md +95 -9
- package/agent-template/.codex/agents/module-executor.toml +17 -0
- package/agent-template/.codex/agents/reviewer.toml +14 -0
- package/agent-template/.codex/agents/spec-author.toml +15 -0
- package/agent-template/AGENTS.md +3 -2
- package/agent-template/CLAUDE.md +3 -2
- 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 +59 -5
- package/agent-template/docs/database-adapters.md +20 -20
- package/agent-template/docs/design-system.md +3 -3
- 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 +9 -1
- package/agent-template/docs/sandbox.md +117 -6
- package/agent-template/platform/scripts/build.mjs +7 -0
- package/agent-template/rulesync.jsonc +1 -1
- package/dist/bin.js +12 -6
- package/package.json +2 -2
- package/template/default/.env.example +10 -3
- package/template/default/.prettierignore +2 -0
- package/template/default/.vercelignore +8 -0
- package/template/default/README.md +26 -15
- 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 +3 -2
- package/template/default/platform/index.html +7 -19
- package/template/default/platform/octane.config.ts +252 -156
- package/template/default/platform/package.json +5 -5
- package/template/default/platform/public/favicon.svg +1 -1
- package/template/default/platform/scripts/build.mjs +56 -0
- package/template/default/platform/scripts/dev.mjs +38 -0
- package/template/default/platform/src/App.tsrx +25 -1
- package/template/default/platform/src/generated/modules.server.ts +3 -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
|
@@ -1,9 +1,8 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: workflow-development
|
|
3
3
|
description: >-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
audit boundaries.
|
|
4
|
+
Author module-owned action templates, or build, publish, invoke, and test a
|
|
5
|
+
workflows.core DAG through its typed graph and public execution capability.
|
|
7
6
|
---
|
|
8
7
|
# Build and integrate an agentic workflow
|
|
9
8
|
|
|
@@ -12,18 +11,22 @@ pinned agent revisions, deterministic gates, schema validators, registered
|
|
|
12
11
|
module actions, data mappings, and terminal output. It does not own schedules or
|
|
13
12
|
webhook secrets. Those remain optional concerns of `automations.core`.
|
|
14
13
|
|
|
15
|
-
|
|
16
|
-
`modules/workflows/spec/module.yaml`, and the contracts
|
|
17
|
-
|
|
14
|
+
At the repository root, read `docs/adr/0006-agentic-workflows.md`, the approved
|
|
15
|
+
`modules/workflows/spec/module.yaml`, and the owning public contracts before
|
|
16
|
+
changing a workflow surface. In the Sandbox, read the approved active-module
|
|
17
|
+
specification and the relevant public contract under `reference/sdk` when it is
|
|
18
|
+
installed. A missing public contract is a core blocker, not a reason to invent
|
|
19
|
+
one or search beyond the session workspace.
|
|
18
20
|
|
|
19
21
|
## Pick the correct extension point
|
|
20
22
|
|
|
21
23
|
- A workflow definition belongs in `workflows.core` and is edited through its
|
|
22
24
|
API or canvas. Do not hardcode a tenant workflow in source.
|
|
23
25
|
- A business operation that a workflow may call is a versioned agent action.
|
|
24
|
-
Register
|
|
25
|
-
|
|
26
|
-
|
|
26
|
+
Register one ordinary `AgentTool` with `context.agentTools`. Optional
|
|
27
|
+
`workflowTemplate` metadata makes that action a named palette choice through
|
|
28
|
+
`agents.actions.v2`; it creates no second handler or node kind. Use the
|
|
29
|
+
authoring recipe below when the action is part of this task.
|
|
27
30
|
- A business module that starts a workflow resolves
|
|
28
31
|
`workflows.execution.v1` from `context.capabilities`. It never imports a
|
|
29
32
|
workflow repository or database.
|
|
@@ -33,6 +36,87 @@ Read `docs/adr/0006-agentic-workflows.md`, the approved
|
|
|
33
36
|
- If the workflow module is absent, the capability registry returns `null`.
|
|
34
37
|
Hide an optional feature or return a clear stable refusal.
|
|
35
38
|
|
|
39
|
+
## Author a module-owned workflow node template
|
|
40
|
+
|
|
41
|
+
An action-backed template is module source, not graph source. The business
|
|
42
|
+
manager first records its stable action id, permission, named inputs and
|
|
43
|
+
validation rules, output, effect, replay behavior, and success and refusal
|
|
44
|
+
scenarios in the owning module's specification. In a Sandbox session, the
|
|
45
|
+
operator must approve the hash of that exact spec before implementation. A
|
|
46
|
+
later edit requires renewed approval. If any of these decisions are absent,
|
|
47
|
+
hand the spec delta to `business-manager`; do not infer a field, permission,
|
|
48
|
+
external effect, or idempotency rule from the brief.
|
|
49
|
+
|
|
50
|
+
Work inside the existing role boundaries:
|
|
51
|
+
|
|
52
|
+
1. `backend-engineer` owns the tenant-bound service, code-level validator,
|
|
53
|
+
endpoint or CLI target, and durable target ledger for a mutation. Validation
|
|
54
|
+
runs before any write and returns a stable bounded refusal. A repeated
|
|
55
|
+
idempotency key returns its first result; the same key with different input
|
|
56
|
+
conflicts. Ask backend to supply a missing service rather than writing in
|
|
57
|
+
`src/services/**` or `src/api/**` from the agentic role.
|
|
58
|
+
2. `agentic-engineer` defines the tool under `src/agent/**`, registers it once
|
|
59
|
+
from `createServerComposition` in `src/platform.ts`, and adds behavioral
|
|
60
|
+
tests. `defineApiAgentTool` or `defineCliAgentTool` carries the existing
|
|
61
|
+
public target. `agents.core` exposes the registered action through
|
|
62
|
+
`agents.actions.v2`; the business module does not register that capability
|
|
63
|
+
or access the workflow database.
|
|
64
|
+
3. Give the tool a stable dotted id, positive `contractVersion`, required
|
|
65
|
+
permission, bounded input and output JSON Schemas, `risk: 'read'` or
|
|
66
|
+
`'workspace-write'`, `idempotency: 'required'`, cancellation policy, and
|
|
67
|
+
timeout. A workspace write also needs
|
|
68
|
+
`idempotencyProtection: 'target-ledger'` backed by the service's real ledger.
|
|
69
|
+
The handler receives trusted tenant, actor, permission snapshot,
|
|
70
|
+
idempotency key, and `AbortSignal` from `AgentToolContext`; none comes from
|
|
71
|
+
graph input. Its output must satisfy its declared schema.
|
|
72
|
+
4. Add static `workflowTemplate: { label, description, effect }` on that same
|
|
73
|
+
tool. The label is at most 80 characters, the description at most 240, and
|
|
74
|
+
effect is `local` or `connector-egress`. Metadata contains no code,
|
|
75
|
+
credential, or tenant value. Invalid or duplicate metadata must fail
|
|
76
|
+
composition with a safe diagnostic. A workflow-eligible tool without the
|
|
77
|
+
metadata remains in the generic action editor.
|
|
78
|
+
|
|
79
|
+
The selected template becomes a normal `action` node with input, success, and
|
|
80
|
+
failure ports. It pins the action id, contract version, schemas, permissions,
|
|
81
|
+
risk, idempotency protection, timeout, cancellation, and effect. Changing any
|
|
82
|
+
of those or the handler's behavior requires a new action identity or contract
|
|
83
|
+
version. The current registry keeps one version per action id, so retain the
|
|
84
|
+
old id and register a distinct id when published graphs must keep running;
|
|
85
|
+
label and description may change without rebinding a published graph.
|
|
86
|
+
|
|
87
|
+
Graph bindings may never supply a raw secret. `writeOnly` and
|
|
88
|
+
`x-flowdular-secret` apply at every schema depth, including array items and
|
|
89
|
+
alternatives. A marked field cannot carry `default`, `const`, `enum`,
|
|
90
|
+
`example`, or `examples` data. A template requiring
|
|
91
|
+
a raw secret input is ineligible. Accept a nonsecret opaque reference and let
|
|
92
|
+
the owning module resolve a credential from its tenant-bound vault or a
|
|
93
|
+
declared capability. Never put secret values in fixtures, graph definitions,
|
|
94
|
+
events, audit, or error text.
|
|
95
|
+
|
|
96
|
+
For `connector-egress`, the consumer module declares `connectors.core` and
|
|
97
|
+
`connectors.calls.v1`, uses `caller: 'workflow'`, checks the instance's
|
|
98
|
+
`allowWorkflows` consent, and passes the stable workflow side-effect key to
|
|
99
|
+
the connector. Credentials remain in `connectors.core`. A replay-stable
|
|
100
|
+
output may contain the recorded call id and outcome, not a response body the
|
|
101
|
+
connector replay does not return. Propagate `CALL_OUTCOME_UNKNOWN` as a
|
|
102
|
+
terminal failure when the remote mutation may have happened but no call was
|
|
103
|
+
recorded; never send a second request just to rebuild output. A separate
|
|
104
|
+
provider contract with remote idempotency or readback is required before
|
|
105
|
+
retrying an uncertain mutation.
|
|
106
|
+
|
|
107
|
+
Prove the module behavior at its public service or action boundary: valid
|
|
108
|
+
input, structural and business validation refusal before mutation, missing
|
|
109
|
+
permission, foreign tenant, same-key replay, different-input conflict,
|
|
110
|
+
cancellation, and recovery around the target commit. Connector actions also
|
|
111
|
+
prove absent consent and `CALL_OUTCOME_UNKNOWN` with a recorded fixture. Run a
|
|
112
|
+
deterministic workflow simulation on invented, reviewed fixtures for success,
|
|
113
|
+
failure, and refusal edges. Assert semantic attempts and edge outcomes, not
|
|
114
|
+
real timestamps; simulation must never invoke the handler, connector, or
|
|
115
|
+
network. Inspect the named template and safe run trail in Sandbox preview,
|
|
116
|
+
then run scoped gates, exact-source `auto-review`, and host eject. The agent
|
|
117
|
+
cannot approve the spec, install code into the running server, or push a
|
|
118
|
+
repository from the Sandbox.
|
|
119
|
+
|
|
36
120
|
## Graph contract
|
|
37
121
|
|
|
38
122
|
Version one is a bounded DAG. The graph contains:
|
|
@@ -41,9 +125,11 @@ Version one is a bounded DAG. The graph contains:
|
|
|
41
125
|
- `agent`: calls one exact immutable agent revision and validates structured
|
|
42
126
|
output.
|
|
43
127
|
- `agent-decision`: produces one schema-valid `pass` or `fail` outcome.
|
|
128
|
+
- `typed-decision`: routes one bounded typed answer through `pass` or `fail`.
|
|
44
129
|
- `gate`: evaluates the versioned allowlisted logic language.
|
|
45
130
|
- `validator`: validates an envelope against a pinned JSON schema.
|
|
46
131
|
- `action`: calls one exact registered action contract version.
|
|
132
|
+
- `human-approval`: waits for an `approvals.core` request to resolve.
|
|
47
133
|
- `merge`: waits for all declared incoming paths.
|
|
48
134
|
- `output`: settles the workflow with a typed result.
|
|
49
135
|
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
name = "module-executor"
|
|
2
|
+
description = "Implement one Flowdular blueprint end to end at the repository root: scaffold from an approved spec, write the module, run the gates and join it to the platform through the CLI. Use once a spec is approved and the change is ready to be built."
|
|
3
|
+
developer_instructions = '''
|
|
4
|
+
Your role prompt is `.ai/agents/module-executor.md`. Read it with the blueprint
|
|
5
|
+
under `.ai/blueprints/<id>/` and the one matching skill in
|
|
6
|
+
`.ai/skills/<name>/SKILL.md` before changing anything.
|
|
7
|
+
|
|
8
|
+
Implement only what the approved spec's acceptance scenarios describe, inside the
|
|
9
|
+
blueprint's `allowed-paths.yaml`. Join the platform through
|
|
10
|
+
`pnpm flowdular module enable <id> --apply` and `auth sync-scopes`; never edit
|
|
11
|
+
`flowdular.json`, `platform/package.json`, `platform/src/generated/**` or
|
|
12
|
+
`platform/octane.config.ts` by hand.
|
|
13
|
+
|
|
14
|
+
Run the module gates and `pnpm verify` yourself and report their exact commands
|
|
15
|
+
and results. Never waive a gate. End with `HANDOFF: reviewer - <what to review>`
|
|
16
|
+
or `HANDOFF: none - <blocker>`.
|
|
17
|
+
'''
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
name = "reviewer"
|
|
2
|
+
description = "Review a finished Flowdular change against its approved spec, the blueprint, the invariants and the executable evidence, and report findings by severity. Use as a separate phase before delivery or a pull request. Reports defects and fixes nothing."
|
|
3
|
+
developer_instructions = '''
|
|
4
|
+
Your role prompt is `.ai/agents/reviewer.md` and the one task skill for this
|
|
5
|
+
phase is `.ai/skills/auto-review/SKILL.md`. Read both before reviewing.
|
|
6
|
+
|
|
7
|
+
You write no production code. Review the complete requested change, its
|
|
8
|
+
requirements, callers and tests; report concrete findings by severity with file,
|
|
9
|
+
line and failure scenario. Never waive missing or failing verification, and never
|
|
10
|
+
approve a change whose evidence you have not seen run.
|
|
11
|
+
|
|
12
|
+
End with `HANDOFF: module-executor - <findings to fix>` or
|
|
13
|
+
`HANDOFF: none - <review result and remaining verification>`.
|
|
14
|
+
'''
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
name = "spec-author"
|
|
2
|
+
description = "Turn a business request into a schema-valid Flowdular module specification at the repository root. Use before any implementation, for a new module spec or a change to an existing one. Writes only modules/<dir>/spec/module.yaml and never approves it."
|
|
3
|
+
developer_instructions = '''
|
|
4
|
+
Your role prompt is `.ai/agents/spec-author.md` and the one task skill for this
|
|
5
|
+
phase is `.ai/skills/spec-interview/SKILL.md`. Read both before writing.
|
|
6
|
+
|
|
7
|
+
You write only `modules/<dir>/spec/module.yaml`. Propose a platform default for
|
|
8
|
+
every decision and ask the user for what cannot be inferred; never guess a
|
|
9
|
+
business fact. Never set `status: approved`: approval is the user's, recorded by
|
|
10
|
+
the `spec-approval` skill.
|
|
11
|
+
|
|
12
|
+
`pnpm flowdular spec validate --all --json` must report the file valid before you
|
|
13
|
+
report. End with `HANDOFF: reviewer - spec ready for owner approval` or
|
|
14
|
+
`HANDOFF: none - <open question>`.
|
|
15
|
+
'''
|
package/agent-template/AGENTS.md
CHANGED
|
@@ -78,5 +78,6 @@ made in the Flowdular repository and released before this application uses it.
|
|
|
78
78
|
The skills and examples use @flowdular/sdk subpath imports. For pnpm --filter,
|
|
79
79
|
read the actual module package name from its package.json. Use pnpm verify and
|
|
80
80
|
pnpm build for this application. Root .ai files are editable project guidance;
|
|
81
|
-
run pnpm rules:generate after changing rules or
|
|
82
|
-
AGENTS.md, CLAUDE.md, .agents/skills
|
|
81
|
+
run pnpm rules:generate after changing rules, skills or subagents, then pnpm
|
|
82
|
+
rules:check. AGENTS.md, CLAUDE.md, .agents/skills, .claude/skills, .claude/agents
|
|
83
|
+
and .codex/agents are generated copies.
|
package/agent-template/CLAUDE.md
CHANGED
|
@@ -78,5 +78,6 @@ made in the Flowdular repository and released before this application uses it.
|
|
|
78
78
|
The skills and examples use @flowdular/sdk subpath imports. For pnpm --filter,
|
|
79
79
|
read the actual module package name from its package.json. Use pnpm verify and
|
|
80
80
|
pnpm build for this application. Root .ai files are editable project guidance;
|
|
81
|
-
run pnpm rules:generate after changing rules or
|
|
82
|
-
AGENTS.md, CLAUDE.md, .agents/skills
|
|
81
|
+
run pnpm rules:generate after changing rules, skills or subagents, then pnpm
|
|
82
|
+
rules:check. AGENTS.md, CLAUDE.md, .agents/skills, .claude/skills, .claude/agents
|
|
83
|
+
and .codex/agents are generated copies.
|
|
@@ -266,6 +266,39 @@ existing bindings in one transaction:
|
|
|
266
266
|
- a definition absent from the sealed registry is unavailable for new work;
|
|
267
267
|
retained revisions and historical runs remain untouched.
|
|
268
268
|
|
|
269
|
+
Amendment (2026-10-05, `agents.core` 0.15.2): reconciliation no longer runs at
|
|
270
|
+
runtime start, and a lower revision no longer fails boot.
|
|
271
|
+
|
|
272
|
+
- `agents.core` opens once in every role, before its first request, capability
|
|
273
|
+
call or `startWorker()`. Opening runs its migrations, then reconciles the
|
|
274
|
+
registered definitions into the durable catalogue. When every definition
|
|
275
|
+
matches its stored revision and content hash, opening costs one catalogue read
|
|
276
|
+
and writes nothing. Otherwise it takes the advisory lock
|
|
277
|
+
`agents.core.module-catalog`, reads the catalogue again under it and writes
|
|
278
|
+
only what is still missing or behind, so roles that open together never fail
|
|
279
|
+
with a duplicate key. A failed opening fails only the request or worker start
|
|
280
|
+
that triggered it, and the next one opens again.
|
|
281
|
+
- Opening never reads or writes a tenant binding. A request that lists, reads or
|
|
282
|
+
runs a module agent advances that tenant's stale binding first, inside its own
|
|
283
|
+
tenant transaction, under the binding row lock and a share lock on the
|
|
284
|
+
catalogue row, so concurrent requests advance it once. Configuring a binding
|
|
285
|
+
writes it at the served revision under the same locks. After its execution
|
|
286
|
+
workers start, the worker advances the bindings no request has touched, in
|
|
287
|
+
pages of at most 100 tenants, and logs a failing tenant without tenant data.
|
|
288
|
+
That pass never gates worker readiness.
|
|
289
|
+
- Tenant-created definitions saved before the retained revision ledger are
|
|
290
|
+
adopted once. A request adopts its own tenant's owed definitions in its own
|
|
291
|
+
transaction, and the worker adopts the rest in pages after it starts.
|
|
292
|
+
- An instance that registers a lower revision than the catalogue holds, such as
|
|
293
|
+
an older deployment during a rolling deploy, is superseded for that agent and
|
|
294
|
+
keeps running. It writes nothing for the agent, lists it unavailable with
|
|
295
|
+
`MODULE_AGENT_REVISION_SUPERSEDED`, refuses a binding change or a run of it
|
|
296
|
+
with status 409 and logs a warning once per process and agent. Queued runs and
|
|
297
|
+
workflow references pinned to a retained executable revision keep the
|
|
298
|
+
exact-revision contract.
|
|
299
|
+
- The same revision with different content is still refused, by preparation and
|
|
300
|
+
by the opening, as `MODULE_AGENT_REVISION_DRIFT`.
|
|
301
|
+
|
|
269
302
|
An old retained revision remains executable after a newer module definition is
|
|
270
303
|
registered only while the owning module is still present, the selected provider
|
|
271
304
|
is usable, and every required registered tool still exists. Module removal is
|
|
@@ -327,7 +360,8 @@ not change shape.
|
|
|
327
360
|
- Business-agent definition content is immutable within one definition
|
|
328
361
|
revision.
|
|
329
362
|
- Duplicate ids, late registration, revision downgrade and same-revision content
|
|
330
|
-
drift fail platform boot before workers start.
|
|
363
|
+
drift fail platform boot before workers start. Since the 2026-10-05
|
|
364
|
+
amendment, a revision downgrade supersedes the agent instead.
|
|
331
365
|
- Module behavior cannot be edited or deleted through tenant APIs.
|
|
332
366
|
- Tenant bindings cannot enable a tool outside the code allowlist.
|
|
333
367
|
- Every execution is tenant-scoped and uses the initiating Actor and trusted
|
|
@@ -14,7 +14,7 @@ Use the already selected task skill. Consult `.ai/references/catalog` for implem
|
|
|
14
14
|
3. Identifiers: module, permission and capability ids match `^[a-z][a-z0-9-]*(\.[a-z][a-z0-9-]*)+$`. `inventory.core` lives in `modules/inventory` as `@flowdular/module-inventory`; permissions are `<module>.<entity>.read` and `<module>.<entity>.manage`.
|
|
15
15
|
4. A module is created only from a spec with `status: approved`. An agent never initiates, infers, or grants approval from its own judgment. After a current, explicit user instruction naming the spec, a host coding agent may record that decision mechanically by following `spec-approval`; a sandbox specialist can only request approval, and the operator route records the exact approved hash. Spec `permissions[].id` equal the constants in `src/acl/permissions.ts`: the spec is what `auth sync-scopes` grants.
|
|
16
16
|
5. Every endpoint is `defineEndpoint` from `@flowdular/sdk/server` with `access: { kind: 'permission', permission }` and `resolveIdentity: endpointIdentityFromContext`. Deny by default; a public endpoint needs a written reason. No raw `new ServerRoute` outside `modules/auth`.
|
|
17
|
-
6. The tenant id comes only from `principalFromContext(octane)!.tenantId`, never from the body, query or headers. Every query on a tenant-owned table filters by `tenant_id`; unique constraints and indexes start with `tenant_id`; SQL uses bound parameters. PostgreSQL tenant tables also enable and force row-level security with `USING` and `WITH CHECK` policies bound to transaction-local `
|
|
17
|
+
6. The tenant id comes only from `principalFromContext(octane)!.tenantId`, never from the body, query or headers. Every query on a tenant-owned table filters by `tenant_id`; unique constraints and indexes start with `tenant_id`; SQL uses bound parameters. PostgreSQL tenant tables also enable and force row-level security with `USING` and `WITH CHECK` policies bound to transaction-local `flowdular.tenant_id`. Every repository operation runs through `database.transaction(..., { tenantId, access })`; the runtime role is never a superuser and never has `BYPASSRLS`, while a separate migration lease may own DDL. `WHERE tenant_id = ...` remains defense in depth.
|
|
18
18
|
7. Every mutation calls `sessionMutationDenial(octane, auth)` first and reads its body with `readJsonObject` plus `requiredString`, `optionalString`, `requiredInteger`. Clients send `content-type: application/json`, `x-csrf-token`, and `credentials: 'same-origin'`.
|
|
19
19
|
8. Routes mount only through `src/platform.ts` exporting `createServerComposition(context)` with `platform.server: true` in `module.json` and a `./platform` export in `package.json`; the context carries `auth`, `settings`, `agentTools`, `agentDefinitions`, `capabilities` and `databases`. A database module passes `context.databases` into one runtime, which acquires and releases one provider lease lazily; `prepare` stays read-only and never opens a database. A composition may return `settings`, read-only `prepare`, `start`, background-work `stop`, and final `dispose`. Client contributions mount only through `createClientContribution(context)` in `src/client/index.ts` with `platform.client: true`. `pnpm flowdular module validate` fails on a missing entry (`PLATFORM_*`).
|
|
20
20
|
9. Never edit the composition by hand: `platform/octane.config.ts`, `platform/src/App.tsrx`, `platform/src/generated/**`, `platform/package.json` dependencies and `modules.enabled` in `flowdular.json` are written by `pnpm flowdular module enable <id> --apply` and `pnpm flowdular module sync --apply`.
|
|
@@ -26,7 +26,7 @@ Use the already selected task skill. Consult `.ai/references/catalog` for implem
|
|
|
26
26
|
15. One screen, form, table or stateful region per named component. Records own the page; create and edit happen in a `Drawer`. Every screen shows loading, empty, error, populated and denied.
|
|
27
27
|
16. Tests live in `tests/*.test.ts`: identity, tenant isolation, uniqueness, one 401 and one 403 per endpoint, each validation bound. Repository behavior uses `createTestDatabaseProvider()` from `@flowdular/sdk/database-testing`: PGlite locally and isolated server PostgreSQL in CI. Open one provider per test file, migrate once, and truncate module tables between cases (`modules/profile/tests/support/database.ts`). Tenancy tests use two tenants, prove `TENANT_CONTEXT_REQUIRED` without a transaction tenant id, prove RLS prevents cross-tenant reads and writes under the non-bypass runtime role, and cover `WITH CHECK`. Tenant fixture work also supplies tenant context on a migration connection. The sandbox `tests` gate passes with zero tests, so an empty suite is a defect.
|
|
28
28
|
17. Translations are live. Every module contribution registers all declared `translations/*.json` bundles, user-facing copy uses fully qualified `t('<module>.<key>')` keys, navigation labels are lazy getters, locale-aware formatting uses `activeLocale()`, and every locale has the same key set, where a plural family (`<key>.one`/`.other`, plus `.few`/`.many` in `pl`, read as `t(key, { count })`) counts as one key. `module validate` rejects missing files, key drift, and missing static translation keys.
|
|
29
|
-
18. Numbered migration SQL is immutable source. `migrations/000N_<module>_<name>.up.sql` and `.down.sql` are PostgreSQL and the only schema source; there is no dialect subdirectory. `src/services/migration.ts` mirrors every `.up.sql` byte for byte as `databaseMigrations: readonly DatabaseMigration[]` with `sql: { postgresql: ... }`, and the runtime calls `runDatabaseMigrations` through its provider lease. The namespaced `
|
|
29
|
+
18. Numbered migration SQL is immutable source. `migrations/000N_<module>_<name>.up.sql` and `.down.sql` are PostgreSQL and the only schema source; there is no dialect subdirectory. `src/services/migration.ts` mirrors every `.up.sql` byte for byte as `databaseMigrations: readonly DatabaseMigration[]` with `sql: { postgresql: ... }`, and the runtime calls `runDatabaseMigrations` through its provider lease. The namespaced `_flowdular_migrations_v2` ledger records the checksum, adopts only an explicit complete `inspectExisting` result (`postgresTenantTableState` is the standard check), and refuses drift, duplicates, or partial schema. Add a new numbered, additive migration instead of changing existing bytes. A tenant table's migration includes enabled and forced RLS plus a tenant policy, with the policy behavior covered by tests.
|
|
30
30
|
19. Module CLI commands live in `src/cli/commands.json` and `src/cli/index.ts`, metadata-identical, inside the module namespace.
|
|
31
31
|
20. Passwords, session tokens and provider credentials never leave `auth.core` (or the `agents.core` vault) and never appear in logs, audit metadata or responses.
|
|
32
32
|
21. `setup quick` and `auth greenfield` are destructive local resets. Preview first, stop the app, never point them at a custom or deployed database.
|
|
@@ -47,8 +47,29 @@ flowdular database restore --input <dir> --apply --confirm restore-database
|
|
|
47
47
|
flowdular setup check # alias of doctor
|
|
48
48
|
flowdular setup quick [--apply --confirm reset-local-auth]
|
|
49
49
|
flowdular setup migrate-state [--apply --confirm migrate-legacy-state]
|
|
50
|
+
flowdular deploy targets # runtime support and launch modes
|
|
51
|
+
flowdular deploy plan <target> [--json] # read-only provider preflight
|
|
52
|
+
flowdular deploy start docker [--apply] # local Compose launch; without --apply returns plan
|
|
53
|
+
flowdular deploy start vercel [--apply] # provision and deploy to Vercel Production; without --apply returns plan
|
|
50
54
|
```
|
|
51
55
|
|
|
56
|
+
`deploy start docker --apply` prints a one-time setup token, so it refuses
|
|
57
|
+
`--json` and redirected output and must run in a private interactive terminal. The deployment targets are
|
|
58
|
+
documented in [infra/README.md](../infra/README.md). `deploy plan vercel`
|
|
59
|
+
checks the build source and links to Vercel import when the branch is pushed.
|
|
60
|
+
|
|
61
|
+
`deploy start vercel --apply` drives the signed-in Vercel CLI: it provisions
|
|
62
|
+
Neon PostgreSQL and its roles, writes the stable keys to
|
|
63
|
+
`.flowdular/deploy/vercel-<project id>.env` (mode 0600) before uploading them
|
|
64
|
+
through stdin, connects a private Blob store and deploys to Production. While
|
|
65
|
+
the database has no workspace it prints a one-time token for `/setup`, where the
|
|
66
|
+
first workspace and owner are created in the browser; Vercel holds only the
|
|
67
|
+
token's SHA-256. That gives it the same terminal rules as the Docker launch.
|
|
68
|
+
`--database-url-env NAME` takes the owner URL from a variable instead of Neon;
|
|
69
|
+
`--plan hobby|pro` overrides the plan read from `vercel whoami --json`;
|
|
70
|
+
`--project`, `--scope`, `--origin` and `--cron` are optional. A rerun resumes
|
|
71
|
+
without regenerating keys. See [infra/vercel/README.md](../infra/vercel/README.md).
|
|
72
|
+
|
|
52
73
|
### Authoring a migration
|
|
53
74
|
|
|
54
75
|
`migration new` writes exactly two files,
|
|
@@ -59,7 +80,7 @@ already filled in. Replace the placeholder columns with the real schema.
|
|
|
59
80
|
`migration verify` then checks that every applied ledger checksum still matches,
|
|
60
81
|
that every tenant table a migration leaves behind has `ENABLE ROW LEVEL
|
|
61
82
|
SECURITY`, `FORCE ROW LEVEL SECURITY` and a tenant policy declared after the
|
|
62
|
-
last statement that puts the table in place, that a `
|
|
83
|
+
last statement that puts the table in place, that a `flowdular_background` policy
|
|
63
84
|
grants no more than `FOR SELECT`, and that every `migrations/*.up.sql` file has
|
|
64
85
|
a matching id in `databaseMigrations` and the other way round.
|
|
65
86
|
|
|
@@ -198,6 +219,6 @@ per-package seconds in `scripts/test-weights.json`. A new package without a
|
|
|
198
219
|
weight counts as 30 seconds; refresh the file from a CI run when the shards
|
|
199
220
|
drift apart.
|
|
200
221
|
|
|
201
|
-
##
|
|
222
|
+
## Module Studio distribution
|
|
202
223
|
|
|
203
|
-
`module search`, `module info`, `module
|
|
224
|
+
`module source`, `module search`, `module info`, `module plan`, `module apply`, `module recover`, and `module validate --locked` manage reviewed external source. See [Module Studio](module-distribution.md) for flags, trust, activation and recovery.
|
|
@@ -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,43 @@ 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
|
+
|
|
32
|
+
## Branding
|
|
33
|
+
|
|
34
|
+
The name, the document title, the description, the link preview image, the
|
|
35
|
+
browser icon, the theme colour and the logo are not environment variables: they
|
|
36
|
+
are `system.core` settings an owner with `system.settings.manage` changes under
|
|
37
|
+
Administration, Branding, and every change is audited. One value serves the
|
|
38
|
+
whole deployment, so the sign-in screen and a shared link carry it too, and a
|
|
39
|
+
setting nobody changed renders the product's own.
|
|
40
|
+
|
|
41
|
+
An address is stored only as a path on this deployment (`/brand/logo.svg`) or an
|
|
42
|
+
https URL; `javascript:`, `data:` and protocol-relative values are refused when
|
|
43
|
+
they are written. Every https branding image origin is added to `img-src` of the
|
|
44
|
+
policy this deployment serves, including an `FD_CSP` of your own, so the browser
|
|
45
|
+
loads it; an `FD_CSP` without an `img-src` directive is left alone and then has
|
|
46
|
+
to name the origin itself. The label an authenticator lists an enrolled account
|
|
47
|
+
under is the same name, decided by the server, so a member who enrols after a
|
|
48
|
+
rename sees the new one.
|
|
49
|
+
|
|
50
|
+
An application scaffolded before this release owns its own `platform/src/App.tsrx`
|
|
51
|
+
and `platform/index.html`, so its head does not follow the settings until it
|
|
52
|
+
adopts two changes the template now carries: `configureBrandingFromPage(props)`
|
|
53
|
+
plus the `Seo`, `Link` and `Meta` block in the entry, and the removal of the
|
|
54
|
+
static `<link rel="icon">` and `<meta name="theme-color">` from the page, which
|
|
55
|
+
would otherwise compete with the rendered ones. `pnpm flowdular doctor` reports
|
|
56
|
+
both as the `platform.branding` check. The navigation, the mobile header and the
|
|
57
|
+
sign-in screen follow the settings without any change.
|
|
58
|
+
|
|
21
59
|
## Observability
|
|
22
60
|
|
|
23
61
|
Spans are always recorded into a bounded in-process buffer and the logger always
|
|
@@ -262,6 +300,7 @@ message was not delivered.
|
|
|
262
300
|
| `FD_AGENT_RUN_GRANT_KEY` | generated dev key | Base64 32-byte key signing run grants |
|
|
263
301
|
| `FD_AGENT_WORKER_CONCURRENCY` | `2` (1 to 16) | Parallel run workers |
|
|
264
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 |
|
|
265
304
|
| `FD_AGENT_PROVIDER_HOST_ALLOWLIST` | empty | Hostnames an external provider may be called on |
|
|
266
305
|
|
|
267
306
|
Outside production the keys are generated once under `.flowdular/data`. The
|
|
@@ -631,7 +670,7 @@ an object moved into another tenant's prefix does not open.
|
|
|
631
670
|
|
|
632
671
|
| Variable | Default | Purpose |
|
|
633
672
|
| ------------------------------------ | ----------------------------------- | ---------------------------------------------------------------------------- |
|
|
634
|
-
| `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 |
|
|
635
674
|
| `FD_STORAGE_LOCAL_DIRECTORY` | `.flowdular/data/storage` | Object directory of the local adapter |
|
|
636
675
|
| `FD_STORAGE_S3_BUCKET` | none | Bucket name; required by the S3 adapter |
|
|
637
676
|
| `FD_STORAGE_S3_REGION` | none | Signing region; required by the S3 adapter |
|
|
@@ -639,10 +678,23 @@ an object moved into another tenant's prefix does not open.
|
|
|
639
678
|
| `FD_STORAGE_S3_ACCESS_KEY_ID` | none | Access key id; required by the S3 adapter |
|
|
640
679
|
| `FD_STORAGE_S3_SECRET_ACCESS_KEY` | none | Secret access key; required by the S3 adapter |
|
|
641
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 |
|
|
642
683
|
| `FD_STORAGE_MAX_OBJECT_BYTES` | `26214400` (25 MiB) | Per-object limit, 1024 to 268435456; a stream is cut off at it |
|
|
643
684
|
| `FD_STORAGE_ENCRYPTION_KEY` | derived dev key | Base64 32-byte key sealing every object and read URL; required in production |
|
|
644
685
|
| `FD_STORAGE_ENCRYPTION_KEY_PREVIOUS` | empty | Retired object keys, comma separated, read only |
|
|
645
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
|
+
|
|
646
698
|
A module writes through `context.storage` and never sees an adapter, a bucket or
|
|
647
699
|
a path. Only these content types are stored, and the bytes are verified against
|
|
648
700
|
the declared type before the write: PDF, PNG, JPEG, GIF, WebP, plain text, CSV,
|
|
@@ -719,7 +771,9 @@ acquires a lease from the platform provider configured above, so the
|
|
|
719
771
|
openssl rand -base64 32
|
|
720
772
|
```
|
|
721
773
|
|
|
722
|
-
For
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
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
|
|
@@ -25,7 +25,7 @@ shared primitives, tokens, and the rules for using them.
|
|
|
25
25
|
refuses a class no stylesheet declares, there and in every `.tsrx`.
|
|
26
26
|
- `platform/public`: `favicon.svg`, `og.png` (1200x630 Open Graph image).
|
|
27
27
|
- Brand mark geometry is generated: `node packages/ui/scripts/gen-mark.mjs`
|
|
28
|
-
rewrites `packages/ui/src/brand/mark.ts` from the
|
|
28
|
+
rewrites `packages/ui/src/brand/mark.ts` from the bar parameters.
|
|
29
29
|
|
|
30
30
|
## Rules
|
|
31
31
|
|
|
@@ -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 |
|
|
@@ -282,7 +282,7 @@ them; outside the shell the English defaults and the host locale apply.
|
|
|
282
282
|
| `Switch` | Boolean setting that applies on its own (no form submit): `checked`, `label` as the accessible name, `disabled`, `onChange` |
|
|
283
283
|
| `ConfirmDialog` | One question before an irreversible action: `open`, `title`, children, `confirmLabel`, `tone` danger (default) or primary, `busy`, `onConfirm`, `onCancel`; traps Tab and restores focus to the opener |
|
|
284
284
|
| `Icon` | Stroke icon by `name` from `ICON_PATHS`; `size` 18 default, 16 in controls, 14 in `Button size="sm"`; `strokeWidth` 1.75 default |
|
|
285
|
-
| `BrandMark` |
|
|
285
|
+
| `BrandMark` | Three bars, the short one copper: `size`, `tone` brand, current, inverse (`signature` is accepted and changes nothing) |
|
|
286
286
|
|
|
287
287
|
`Drawer` takes one child, a `ui-drawer__form` (fields in `ui-drawer__body`, actions in `ui-drawer__foot`) or a plain `ui-drawer__body`; it closes on Escape and on the scrim. `SearchField` carries no visible label, so pass `label` as its accessible name. `FormField` renders `error` in place of `help` and marks it `role="alert"`. `SettingRow` is presentation only: the caller owns the draft value, the save call, and passes the result back as `status`. `ScopeSummary` is the read side of `CheckGrid`; both keep the first-seen module order, and `summarizeScopes` is exported for callers that need the grouping without the markup.
|
|
288
288
|
|