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
|
@@ -148,7 +148,7 @@ export function createServerComposition(
|
|
|
148
148
|
|
|
149
149
|
`PlatformServerContext` also carries `databases: DatabaseProvider`, `settings: ModuleSettingsRuntime`, `agentTools: PlatformToolRegistry`, `agentDefinitions: PlatformAgentRegistry`, and `capabilities: PlatformCapabilityRegistry`. The runtime shares one lazy database initialization, uses separate migration and runtime leases, and releases the runtime lease from `dispose()`; `prepare()` remains read-only. A composition may return module settings, lifecycle hooks, agent registrations, and typed cross-module capabilities as described in the focused skills.
|
|
150
150
|
|
|
151
|
-
Schema: write PostgreSQL SQL in `migrations/0001_inventory_core.up.sql` and mirror it byte for byte in `databaseMigrations` as `sql: { postgresql: ... }` with an `inspectExisting` built from `postgresTenantTableState(...)`. Tenant tables enable and force row-level security with an `<table>_tenant_policy` whose `USING` and `WITH CHECK` compare `tenant_id` with `current_setting('
|
|
151
|
+
Schema: write PostgreSQL SQL in `migrations/0001_inventory_core.up.sql` and mirror it byte for byte in `databaseMigrations` as `sql: { postgresql: ... }` with an `inspectExisting` built from `postgresTenantTableState(...)`. Tenant tables enable and force row-level security with an `<table>_tenant_policy` whose `USING` and `WITH CHECK` compare `tenant_id` with `current_setting('flowdular.tenant_id', true)`; the runtime role has no superuser or `BYPASSRLS`. Repository operations use `database.transaction(..., { tenantId, access })` and retain explicit tenant predicates. Details in `migration-authoring` and `database-adapter`.
|
|
152
152
|
|
|
153
153
|
Errors: `{ error: { code, message } }`; service errors `class XServiceError extends Error { constructor(readonly code: string, message: string, readonly status = 400) }`; a `failure(error)` helper routes them to `jsonResponse(..., error.status)` and everything else to `problemResponse(error, 'The <module> operation failed.')`.
|
|
154
154
|
|
|
@@ -23,7 +23,7 @@ when: A module has few or tautological tests, a bug escaped the suite, or a revi
|
|
|
23
23
|
|
|
24
24
|
## 2. Repositories on an embedded PostgreSQL
|
|
25
25
|
|
|
26
|
-
`createPgliteTestProvider()` from `@flowdular/sdk/database-testing` runs a real PostgreSQL inside the test process, with the same `
|
|
26
|
+
`createPgliteTestProvider()` from `@flowdular/sdk/database-testing` runs a real PostgreSQL inside the test process, with the same `flowdular_runtime` and `flowdular_background` roles and the same forced row-level security a deployment enforces. Booting it costs about two seconds, so a suite opens one provider per test file, migrates it once, and truncates the module's tables between cases; `.ai/references/catalog/tests/support/database.ts` is the shape (`createCatalogTestDatabase` hands out a lease per fixture, `closeCatalogTestDatabases` runs in `afterAll`). Build the service on top: `new CatalogService((await createCatalogTestDatabase()).repository)`. A database module also keeps `tests/migrations.test.ts` for SQL byte parity, fresh apply, safe pre-ledger adoption, and a clean second start. A module whose spec has no `database` capability gets a `MemoryXRepository` from the scaffold instead; a module with a database tests the database repository, never a hand-written fake, because the SQL, the ledger and the row-level security are what need testing.
|
|
27
27
|
|
|
28
28
|
## 3. Route recipe (from `modules/auth/tests/endpoints.test.ts`)
|
|
29
29
|
|
|
@@ -63,7 +63,7 @@ The auth middleware must have set the principal for `endpointIdentityFromContext
|
|
|
63
63
|
- 403 on a mutation without `x-csrf-token` (`CSRF_REJECTED`) and without `origin` (`ORIGIN_REQUIRED`).
|
|
64
64
|
- 400 with the stable code for each validation bound (`INVALID_INPUT`, module codes such as `INVALID_ITEM_KIND`).
|
|
65
65
|
- 409 for the tenant-scoped uniqueness rule, and success for the same key in another tenant.
|
|
66
|
-
- Tenant isolation: rows created for `tenant-a` are invisible to `list('tenant-b')`. The provider hands the suite the non-bypass `
|
|
66
|
+
- Tenant isolation: rows created for `tenant-a` are invisible to `list('tenant-b')`. The provider hands the suite the non-bypass `flowdular_runtime` role, so this runs against real forced row-level security; also assert that a call without tenant context fails with `TENANT_CONTEXT_REQUIRED`.
|
|
67
67
|
- Identity: `moduleDefinition.manifest.id` equals the module id (keeps `module.json` and `src/index.ts` aligned). The scaffold writes this and the isolation case; everything else in this list is yours.
|
|
68
68
|
|
|
69
69
|
Assert at the observation boundary: status code, `error.code`, returned record fields. Do not assert internal helper names, call order, or SQL text.
|
|
@@ -1,14 +1,13 @@
|
|
|
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
|
roles:
|
|
8
7
|
- agentic-engineer
|
|
9
8
|
- frontend-engineer
|
|
10
9
|
- module-executor
|
|
11
|
-
when: A brief asks for a workflow, pipeline, canvas node, workflow
|
|
10
|
+
when: A brief asks for a workflow, pipeline, canvas node, action-backed workflow template, or a module feature that starts a workflow.
|
|
12
11
|
---
|
|
13
12
|
|
|
14
13
|
# Build and integrate an agentic workflow
|
|
@@ -18,18 +17,22 @@ pinned agent revisions, deterministic gates, schema validators, registered
|
|
|
18
17
|
module actions, data mappings, and terminal output. It does not own schedules or
|
|
19
18
|
webhook secrets. Those remain optional concerns of `automations.core`.
|
|
20
19
|
|
|
21
|
-
|
|
22
|
-
`modules/workflows/spec/module.yaml`, and the contracts
|
|
23
|
-
|
|
20
|
+
At the repository root, read `docs/adr/0006-agentic-workflows.md`, the approved
|
|
21
|
+
`modules/workflows/spec/module.yaml`, and the owning public contracts before
|
|
22
|
+
changing a workflow surface. In the Sandbox, read the approved active-module
|
|
23
|
+
specification and the relevant public contract under `reference/sdk` when it is
|
|
24
|
+
installed. A missing public contract is a core blocker, not a reason to invent
|
|
25
|
+
one or search beyond the session workspace.
|
|
24
26
|
|
|
25
27
|
## Pick the correct extension point
|
|
26
28
|
|
|
27
29
|
- A workflow definition belongs in `workflows.core` and is edited through its
|
|
28
30
|
API or canvas. Do not hardcode a tenant workflow in source.
|
|
29
31
|
- A business operation that a workflow may call is a versioned agent action.
|
|
30
|
-
Register
|
|
31
|
-
|
|
32
|
-
|
|
32
|
+
Register one ordinary `AgentTool` with `context.agentTools`. Optional
|
|
33
|
+
`workflowTemplate` metadata makes that action a named palette choice through
|
|
34
|
+
`agents.actions.v2`; it creates no second handler or node kind. Use the
|
|
35
|
+
authoring recipe below when the action is part of this task.
|
|
33
36
|
- A business module that starts a workflow resolves
|
|
34
37
|
`workflows.execution.v1` from `context.capabilities`. It never imports a
|
|
35
38
|
workflow repository or database.
|
|
@@ -39,6 +42,87 @@ Read `docs/adr/0006-agentic-workflows.md`, the approved
|
|
|
39
42
|
- If the workflow module is absent, the capability registry returns `null`.
|
|
40
43
|
Hide an optional feature or return a clear stable refusal.
|
|
41
44
|
|
|
45
|
+
## Author a module-owned workflow node template
|
|
46
|
+
|
|
47
|
+
An action-backed template is module source, not graph source. The business
|
|
48
|
+
manager first records its stable action id, permission, named inputs and
|
|
49
|
+
validation rules, output, effect, replay behavior, and success and refusal
|
|
50
|
+
scenarios in the owning module's specification. In a Sandbox session, the
|
|
51
|
+
operator must approve the hash of that exact spec before implementation. A
|
|
52
|
+
later edit requires renewed approval. If any of these decisions are absent,
|
|
53
|
+
hand the spec delta to `business-manager`; do not infer a field, permission,
|
|
54
|
+
external effect, or idempotency rule from the brief.
|
|
55
|
+
|
|
56
|
+
Work inside the existing role boundaries:
|
|
57
|
+
|
|
58
|
+
1. `backend-engineer` owns the tenant-bound service, code-level validator,
|
|
59
|
+
endpoint or CLI target, and durable target ledger for a mutation. Validation
|
|
60
|
+
runs before any write and returns a stable bounded refusal. A repeated
|
|
61
|
+
idempotency key returns its first result; the same key with different input
|
|
62
|
+
conflicts. Ask backend to supply a missing service rather than writing in
|
|
63
|
+
`src/services/**` or `src/api/**` from the agentic role.
|
|
64
|
+
2. `agentic-engineer` defines the tool under `src/agent/**`, registers it once
|
|
65
|
+
from `createServerComposition` in `src/platform.ts`, and adds behavioral
|
|
66
|
+
tests. `defineApiAgentTool` or `defineCliAgentTool` carries the existing
|
|
67
|
+
public target. `agents.core` exposes the registered action through
|
|
68
|
+
`agents.actions.v2`; the business module does not register that capability
|
|
69
|
+
or access the workflow database.
|
|
70
|
+
3. Give the tool a stable dotted id, positive `contractVersion`, required
|
|
71
|
+
permission, bounded input and output JSON Schemas, `risk: 'read'` or
|
|
72
|
+
`'workspace-write'`, `idempotency: 'required'`, cancellation policy, and
|
|
73
|
+
timeout. A workspace write also needs
|
|
74
|
+
`idempotencyProtection: 'target-ledger'` backed by the service's real ledger.
|
|
75
|
+
The handler receives trusted tenant, actor, permission snapshot,
|
|
76
|
+
idempotency key, and `AbortSignal` from `AgentToolContext`; none comes from
|
|
77
|
+
graph input. Its output must satisfy its declared schema.
|
|
78
|
+
4. Add static `workflowTemplate: { label, description, effect }` on that same
|
|
79
|
+
tool. The label is at most 80 characters, the description at most 240, and
|
|
80
|
+
effect is `local` or `connector-egress`. Metadata contains no code,
|
|
81
|
+
credential, or tenant value. Invalid or duplicate metadata must fail
|
|
82
|
+
composition with a safe diagnostic. A workflow-eligible tool without the
|
|
83
|
+
metadata remains in the generic action editor.
|
|
84
|
+
|
|
85
|
+
The selected template becomes a normal `action` node with input, success, and
|
|
86
|
+
failure ports. It pins the action id, contract version, schemas, permissions,
|
|
87
|
+
risk, idempotency protection, timeout, cancellation, and effect. Changing any
|
|
88
|
+
of those or the handler's behavior requires a new action identity or contract
|
|
89
|
+
version. The current registry keeps one version per action id, so retain the
|
|
90
|
+
old id and register a distinct id when published graphs must keep running;
|
|
91
|
+
label and description may change without rebinding a published graph.
|
|
92
|
+
|
|
93
|
+
Graph bindings may never supply a raw secret. `writeOnly` and
|
|
94
|
+
`x-flowdular-secret` apply at every schema depth, including array items and
|
|
95
|
+
alternatives. A marked field cannot carry `default`, `const`, `enum`,
|
|
96
|
+
`example`, or `examples` data. A template requiring
|
|
97
|
+
a raw secret input is ineligible. Accept a nonsecret opaque reference and let
|
|
98
|
+
the owning module resolve a credential from its tenant-bound vault or a
|
|
99
|
+
declared capability. Never put secret values in fixtures, graph definitions,
|
|
100
|
+
events, audit, or error text.
|
|
101
|
+
|
|
102
|
+
For `connector-egress`, the consumer module declares `connectors.core` and
|
|
103
|
+
`connectors.calls.v1`, uses `caller: 'workflow'`, checks the instance's
|
|
104
|
+
`allowWorkflows` consent, and passes the stable workflow side-effect key to
|
|
105
|
+
the connector. Credentials remain in `connectors.core`. A replay-stable
|
|
106
|
+
output may contain the recorded call id and outcome, not a response body the
|
|
107
|
+
connector replay does not return. Propagate `CALL_OUTCOME_UNKNOWN` as a
|
|
108
|
+
terminal failure when the remote mutation may have happened but no call was
|
|
109
|
+
recorded; never send a second request just to rebuild output. A separate
|
|
110
|
+
provider contract with remote idempotency or readback is required before
|
|
111
|
+
retrying an uncertain mutation.
|
|
112
|
+
|
|
113
|
+
Prove the module behavior at its public service or action boundary: valid
|
|
114
|
+
input, structural and business validation refusal before mutation, missing
|
|
115
|
+
permission, foreign tenant, same-key replay, different-input conflict,
|
|
116
|
+
cancellation, and recovery around the target commit. Connector actions also
|
|
117
|
+
prove absent consent and `CALL_OUTCOME_UNKNOWN` with a recorded fixture. Run a
|
|
118
|
+
deterministic workflow simulation on invented, reviewed fixtures for success,
|
|
119
|
+
failure, and refusal edges. Assert semantic attempts and edge outcomes, not
|
|
120
|
+
real timestamps; simulation must never invoke the handler, connector, or
|
|
121
|
+
network. Inspect the named template and safe run trail in Sandbox preview,
|
|
122
|
+
then run scoped gates, exact-source `auto-review`, and host eject. The agent
|
|
123
|
+
cannot approve the spec, install code into the running server, or push a
|
|
124
|
+
repository from the Sandbox.
|
|
125
|
+
|
|
42
126
|
## Graph contract
|
|
43
127
|
|
|
44
128
|
Version one is a bounded DAG. The graph contains:
|
|
@@ -47,9 +131,11 @@ Version one is a bounded DAG. The graph contains:
|
|
|
47
131
|
- `agent`: calls one exact immutable agent revision and validates structured
|
|
48
132
|
output.
|
|
49
133
|
- `agent-decision`: produces one schema-valid `pass` or `fail` outcome.
|
|
134
|
+
- `typed-decision`: routes one bounded typed answer through `pass` or `fail`.
|
|
50
135
|
- `gate`: evaluates the versioned allowlisted logic language.
|
|
51
136
|
- `validator`: validates an envelope against a pinned JSON schema.
|
|
52
137
|
- `action`: calls one exact registered action contract version.
|
|
138
|
+
- `human-approval`: waits for an `approvals.core` request to resolve.
|
|
53
139
|
- `merge`: waits for all declared incoming paths.
|
|
54
140
|
- `output`: settles the workflow with a typed result.
|
|
55
141
|
|
|
@@ -19,7 +19,7 @@ also ship a ready business agent through `defineAgent()`, read
|
|
|
19
19
|
## 1. The contract in code
|
|
20
20
|
|
|
21
21
|
- Composition: `PlatformServerContext` (`modules/auth/src/server/composition.ts`) carries `agentTools: PlatformToolRegistry` (`register(tools)`, `list()`; `packages/kernel/src/tool-registry.ts`; a duplicate tool id throws at boot), `settings: ModuleSettingsRuntime`, and `capabilities: PlatformCapabilityRegistry` (`register(id, service)`, `get(id)`, `has(id)`; `packages/kernel/src/capability-registry.ts`). `platform/octane.config.ts` creates the registries, passes them to every module's `createServerComposition`, declares each `settings`, owns each `dispose`, then calls each `start`.
|
|
22
|
-
- Ordering is a non-issue: `agents.core` (`modules/agents/src/platform.ts`) passes `tools: () => context.agentTools.list()` into `createAgentRuntime`, and the harness is built
|
|
22
|
+
- Ordering is a non-issue: `agents.core` (`modules/agents/src/platform.ts`) passes `tools: () => context.agentTools.list()` into `createAgentRuntime`, and the harness is built when `agents.core` opens, on its first request, capability call or `startWorker()`, all of which run after every module has composed. Tools any module registers during its own compose are therefore visible, whatever the module order.
|
|
23
23
|
- Helpers: import `defineApiAgentTool` from `@flowdular/sdk/harness/tool-adapters` and the types `AgentTool`, `AgentToolContext` from `@flowdular/sdk/harness/runtime`. Both subpaths are free of the Vercel AI SDK; only the harness root (`@flowdular/sdk/harness`) and `@flowdular/sdk/modules/agents/server` pull it. `defineApiAgentTool` returns a frozen `AgentTool { id, transport: 'api', target, description, requiredPermissions, inputSchema?, execute }`. `defineCliAgentTool({ id, capability: { id, risk }, ... })` wraps a CLI capability and throws at definition time for `external` or `destructive` risk.
|
|
24
24
|
- Skills inside `agents.core` are tenant database records behind `agents.skills.*`, appended to agent instructions. They are unrelated to `.ai/skills/**`, which are files for coding agents.
|
|
25
25
|
- A read tool's output can also become a resolvable `{{ variable }}` for variable-aware fields: the tool's `requiredPermissions` is the variable's scope mask. Register a source on `platformVariableRegistry(context.capabilities)`, require an explicit record binding, and invoke the tool with the trusted tenant, actor permission snapshot, and signal. See the `variables` skill for the complete refusal contract.
|
|
@@ -57,7 +57,7 @@ Recipe in `modules/auth/tests/endpoints.test.ts`: build the runtime with a `Data
|
|
|
57
57
|
|
|
58
58
|
- 401 without a cookie or token.
|
|
59
59
|
- 403 with a principal that lacks the permission.
|
|
60
|
-
- Cross-tenant read returns an empty list (service level, on the suite's test provider under the non-bypass `
|
|
60
|
+
- Cross-tenant read returns an empty list (service level, on the suite's test provider under the non-bypass `flowdular_runtime` role).
|
|
61
61
|
- Mutation without `x-csrf-token` returns 403 `CSRF_REJECTED`; without `origin` returns 403.
|
|
62
62
|
- Each validation bound returns 400 with its code.
|
|
63
63
|
|
|
@@ -88,12 +88,12 @@ PostgreSQL tenant-owned tables also use database-enforced isolation:
|
|
|
88
88
|
|
|
89
89
|
- enable and force row-level security on the table;
|
|
90
90
|
- define a policy whose `USING` and `WITH CHECK` clauses compare `tenant_id`
|
|
91
|
-
with `current_setting('
|
|
91
|
+
with `current_setting('flowdular.tenant_id', true)`;
|
|
92
92
|
- run application traffic under a role that is neither a superuser nor granted
|
|
93
93
|
`BYPASSRLS`;
|
|
94
94
|
- use a separate migration role for DDL or policy ownership when required.
|
|
95
95
|
|
|
96
|
-
The adapter sets `
|
|
96
|
+
The adapter sets `flowdular.tenant_id` with parameterized `set_config(..., true)`
|
|
97
97
|
after `BEGIN` on the pinned connection. Never use an unpinned root query.
|
|
98
98
|
Explicit tenant predicates remain required as defense in depth.
|
|
99
99
|
|
|
@@ -133,7 +133,7 @@ transactions without `tenantId`, fail with `TENANT_CONTEXT_REQUIRED`.
|
|
|
133
133
|
|
|
134
134
|
Use `DatabaseMigration` and `runDatabaseMigrations` from `@flowdular/sdk/database`.
|
|
135
135
|
Each migration has one immutable id and its PostgreSQL SQL. The ledger is
|
|
136
|
-
`
|
|
136
|
+
`_flowdular_migrations_v2`, keyed by module namespace and migration id; its
|
|
137
137
|
checksum covers that exact SQL.
|
|
138
138
|
|
|
139
139
|
`inspectExisting(database)` is the only pre-ledger adoption proof. Use
|
|
@@ -157,8 +157,8 @@ constant. A migration-only task uses `migration-authoring` in a separate phase.
|
|
|
157
157
|
## 7. Tests run on a real PostgreSQL
|
|
158
158
|
|
|
159
159
|
`createTestDatabaseProvider()` from `@flowdular/sdk/database-testing` gives a suite its
|
|
160
|
-
own PostgreSQL in process by default, with the same `
|
|
161
|
-
`
|
|
160
|
+
own PostgreSQL in process by default, with the same `flowdular_runtime` and
|
|
161
|
+
`flowdular_background` roles and the same forced row-level security a deployment
|
|
162
162
|
enforces. There is no server to start and no second dialect to keep green, so
|
|
163
163
|
the isolation assertions run on every turn rather than behind an environment
|
|
164
164
|
flag. CI selects server PostgreSQL with `FD_TEST_DATABASE_ADAPTER=postgresql`
|
|
@@ -83,8 +83,8 @@ adapter change, sandbox eject, and deployment validation.
|
|
|
83
83
|
|
|
84
84
|
Every turn runs against a real PostgreSQL, because the embedded one starts in
|
|
85
85
|
process. The sandbox and the test suites use `createTestDatabaseProvider()` from
|
|
86
|
-
`@flowdular/sdk/database-testing`, which brings the `
|
|
87
|
-
`
|
|
86
|
+
`@flowdular/sdk/database-testing`, which brings the `flowdular_runtime` and
|
|
87
|
+
`flowdular_background` roles and forced row-level security with it.
|
|
88
88
|
|
|
89
89
|
A target run covers tenant A and B fixtures, operations without tenant context,
|
|
90
90
|
forged cross-tenant inserts, direct row-security bypass probes, concurrent
|
|
@@ -48,7 +48,7 @@ Set `FD_TRUST_PROXY` behind a load balancer. `FD_DATABASE_BACKGROUND_URL` gives
|
|
|
48
48
|
|
|
49
49
|
## 3. Migrations at rollout
|
|
50
50
|
|
|
51
|
-
Migrations are module-owned, numbered, immutable once applied, and verified by checksum against the `
|
|
51
|
+
Migrations are module-owned, numbered, immutable once applied, and verified by checksum against the `_flowdular_migrations_v2` ledger. The commands (`packages/cli/src/runner.ts`):
|
|
52
52
|
|
|
53
53
|
```bash
|
|
54
54
|
pnpm flowdular migration status [--module <id>] # what the ledger holds
|
|
@@ -26,7 +26,7 @@ Migration SQL is checked in and immutable after release:
|
|
|
26
26
|
## 2. What the v2 runner guarantees
|
|
27
27
|
|
|
28
28
|
`runDatabaseMigrations(database, namespace, databaseMigrations)` uses the
|
|
29
|
-
namespaced `
|
|
29
|
+
namespaced `_flowdular_migrations_v2` ledger. A row records namespace, migration
|
|
30
30
|
id, dialect id, checksum, and applied time. The checksum covers the selected
|
|
31
31
|
dialect's exact SQL.
|
|
32
32
|
|
|
@@ -76,14 +76,14 @@ Every PostgreSQL tenant table includes:
|
|
|
76
76
|
ALTER TABLE inventory_locations ENABLE ROW LEVEL SECURITY;
|
|
77
77
|
ALTER TABLE inventory_locations FORCE ROW LEVEL SECURITY;
|
|
78
78
|
CREATE POLICY inventory_locations_tenant_policy ON inventory_locations
|
|
79
|
-
USING (tenant_id = current_setting('
|
|
80
|
-
WITH CHECK (tenant_id = current_setting('
|
|
79
|
+
USING (tenant_id = current_setting('flowdular.tenant_id', true))
|
|
80
|
+
WITH CHECK (tenant_id = current_setting('flowdular.tenant_id', true));
|
|
81
81
|
```
|
|
82
82
|
|
|
83
83
|
The runtime role is not a superuser and has no `BYPASSRLS`. DDL and policy
|
|
84
84
|
ownership use `purpose: 'migration'`. Runtime repository calls use
|
|
85
85
|
`database.transaction(operation, { tenantId, access })`; the adapter sets
|
|
86
|
-
transaction-local `
|
|
86
|
+
transaction-local `flowdular.tenant_id` on the pinned connection. Queries still
|
|
87
87
|
include `WHERE tenant_id = ...` as defense in depth.
|
|
88
88
|
|
|
89
89
|
## 6. Add one migration
|
|
@@ -142,7 +142,7 @@ export function createServerComposition(
|
|
|
142
142
|
|
|
143
143
|
`PlatformServerContext` also carries `databases: DatabaseProvider`, `settings: ModuleSettingsRuntime`, `agentTools: PlatformToolRegistry`, `agentDefinitions: PlatformAgentRegistry`, and `capabilities: PlatformCapabilityRegistry`. The runtime shares one lazy database initialization, uses separate migration and runtime leases, and releases the runtime lease from `dispose()`; `prepare()` remains read-only. A composition may return module settings, lifecycle hooks, agent registrations, and typed cross-module capabilities as described in the focused skills.
|
|
144
144
|
|
|
145
|
-
Schema: write PostgreSQL SQL in `migrations/0001_inventory_core.up.sql` and mirror it byte for byte in `databaseMigrations` as `sql: { postgresql: ... }` with an `inspectExisting` built from `postgresTenantTableState(...)`. Tenant tables enable and force row-level security with an `<table>_tenant_policy` whose `USING` and `WITH CHECK` compare `tenant_id` with `current_setting('
|
|
145
|
+
Schema: write PostgreSQL SQL in `migrations/0001_inventory_core.up.sql` and mirror it byte for byte in `databaseMigrations` as `sql: { postgresql: ... }` with an `inspectExisting` built from `postgresTenantTableState(...)`. Tenant tables enable and force row-level security with an `<table>_tenant_policy` whose `USING` and `WITH CHECK` compare `tenant_id` with `current_setting('flowdular.tenant_id', true)`; the runtime role has no superuser or `BYPASSRLS`. Repository operations use `database.transaction(..., { tenantId, access })` and retain explicit tenant predicates. Details in `migration-authoring` and `database-adapter`.
|
|
146
146
|
|
|
147
147
|
Errors: `{ error: { code, message } }`; service errors `class XServiceError extends Error { constructor(readonly code: string, message: string, readonly status = 400) }`; a `failure(error)` helper routes them to `jsonResponse(..., error.status)` and everything else to `problemResponse(error, 'The <module> operation failed.')`.
|
|
148
148
|
|
|
@@ -15,7 +15,7 @@ description: >-
|
|
|
15
15
|
|
|
16
16
|
## 2. Repositories on an embedded PostgreSQL
|
|
17
17
|
|
|
18
|
-
`createPgliteTestProvider()` from `@flowdular/sdk/database-testing` runs a real PostgreSQL inside the test process, with the same `
|
|
18
|
+
`createPgliteTestProvider()` from `@flowdular/sdk/database-testing` runs a real PostgreSQL inside the test process, with the same `flowdular_runtime` and `flowdular_background` roles and the same forced row-level security a deployment enforces. Booting it costs about two seconds, so a suite opens one provider per test file, migrates it once, and truncates the module's tables between cases; `.ai/references/catalog/tests/support/database.ts` is the shape (`createCatalogTestDatabase` hands out a lease per fixture, `closeCatalogTestDatabases` runs in `afterAll`). Build the service on top: `new CatalogService((await createCatalogTestDatabase()).repository)`. A database module also keeps `tests/migrations.test.ts` for SQL byte parity, fresh apply, safe pre-ledger adoption, and a clean second start. A module whose spec has no `database` capability gets a `MemoryXRepository` from the scaffold instead; a module with a database tests the database repository, never a hand-written fake, because the SQL, the ledger and the row-level security are what need testing.
|
|
19
19
|
|
|
20
20
|
## 3. Route recipe (from `modules/auth/tests/endpoints.test.ts`)
|
|
21
21
|
|
|
@@ -55,7 +55,7 @@ The auth middleware must have set the principal for `endpointIdentityFromContext
|
|
|
55
55
|
- 403 on a mutation without `x-csrf-token` (`CSRF_REJECTED`) and without `origin` (`ORIGIN_REQUIRED`).
|
|
56
56
|
- 400 with the stable code for each validation bound (`INVALID_INPUT`, module codes such as `INVALID_ITEM_KIND`).
|
|
57
57
|
- 409 for the tenant-scoped uniqueness rule, and success for the same key in another tenant.
|
|
58
|
-
- Tenant isolation: rows created for `tenant-a` are invisible to `list('tenant-b')`. The provider hands the suite the non-bypass `
|
|
58
|
+
- Tenant isolation: rows created for `tenant-a` are invisible to `list('tenant-b')`. The provider hands the suite the non-bypass `flowdular_runtime` role, so this runs against real forced row-level security; also assert that a call without tenant context fails with `TENANT_CONTEXT_REQUIRED`.
|
|
59
59
|
- Identity: `moduleDefinition.manifest.id` equals the module id (keeps `module.json` and `src/index.ts` aligned). The scaffold writes this and the isolation case; everything else in this list is yours.
|
|
60
60
|
|
|
61
61
|
Assert at the observation boundary: status code, `error.code`, returned record fields. Do not assert internal helper names, call order, or SQL text.
|
|
@@ -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
|
|
|
@@ -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.
|