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.
Files changed (130) hide show
  1. package/README.md +16 -10
  2. package/agent-template/.agents/skills/agent-tool-design/SKILL.md +1 -1
  3. package/agent-template/.agents/skills/auth-security-review/SKILL.md +2 -2
  4. package/agent-template/.agents/skills/bug-hunt/SKILL.md +1 -1
  5. package/agent-template/.agents/skills/database-adapter/SKILL.md +5 -5
  6. package/agent-template/.agents/skills/database-adapter/references/first-run-and-matrix.md +2 -2
  7. package/agent-template/.agents/skills/deploy-operate/SKILL.md +1 -1
  8. package/agent-template/.agents/skills/migration-authoring/SKILL.md +4 -4
  9. package/agent-template/.agents/skills/module-new/SKILL.md +1 -1
  10. package/agent-template/.agents/skills/spec-interview/SKILL.md +20 -20
  11. package/agent-template/.agents/skills/test-hardening/SKILL.md +2 -2
  12. package/agent-template/.agents/skills/ux-design/SKILL.md +1 -1
  13. package/agent-template/.agents/skills/workflow-development/SKILL.md +95 -9
  14. package/agent-template/.ai/README.md +5 -3
  15. package/agent-template/.ai/agents/README.md +1 -1
  16. package/agent-template/.ai/agents/sandbox/agentic-engineer.md +1 -0
  17. package/agent-template/.ai/agents/sandbox/backend-engineer.md +1 -0
  18. package/agent-template/.ai/agents/sandbox/frontend-engineer.md +1 -0
  19. package/agent-template/.ai/blueprints/add-migration/README.md +1 -1
  20. package/agent-template/.ai/blueprints/add-migration/required-files.yaml +1 -1
  21. package/agent-template/.ai/blueprints/new-module/required-files.yaml +1 -1
  22. package/agent-template/.ai/examples/bad/client-imports-server/README.md +1 -1
  23. package/agent-template/.ai/examples/bad/missing-acl/README.md +1 -1
  24. package/agent-template/.ai/examples/bad/tenant-from-body/README.md +1 -1
  25. package/agent-template/.ai/guides/application-development.md +7 -5
  26. package/agent-template/.ai/platform-capabilities.md +9 -5
  27. package/agent-template/.ai/policies/capabilities.yaml +28 -12
  28. package/agent-template/.ai/policies/task-budgets.yaml +1 -1
  29. package/agent-template/.ai/rules/flowdular.md +3 -2
  30. package/agent-template/.ai/skills/README.md +1 -1
  31. package/agent-template/.ai/skills/agent-tool-design/SKILL.md +1 -1
  32. package/agent-template/.ai/skills/auth-security-review/SKILL.md +2 -2
  33. package/agent-template/.ai/skills/bug-hunt/SKILL.md +1 -1
  34. package/agent-template/.ai/skills/database-adapter/SKILL.md +5 -5
  35. package/agent-template/.ai/skills/database-adapter/references/first-run-and-matrix.md +2 -2
  36. package/agent-template/.ai/skills/deploy-operate/SKILL.md +1 -1
  37. package/agent-template/.ai/skills/migration-authoring/SKILL.md +4 -4
  38. package/agent-template/.ai/skills/module-new/SKILL.md +1 -1
  39. package/agent-template/.ai/skills/spec-interview/SKILL.md +20 -20
  40. package/agent-template/.ai/skills/test-hardening/SKILL.md +2 -2
  41. package/agent-template/.ai/skills/ux-design/SKILL.md +1 -1
  42. package/agent-template/.ai/skills/workflow-development/SKILL.md +96 -10
  43. package/agent-template/.ai/subagents/module-executor.md +25 -0
  44. package/agent-template/.ai/subagents/reviewer.md +23 -0
  45. package/agent-template/.ai/subagents/spec-author.md +23 -0
  46. package/agent-template/.claude/agents/module-executor.md +22 -0
  47. package/agent-template/.claude/agents/reviewer.md +24 -0
  48. package/agent-template/.claude/agents/spec-author.md +20 -0
  49. package/agent-template/.claude/skills/agent-tool-design/SKILL.md +1 -1
  50. package/agent-template/.claude/skills/auth-security-review/SKILL.md +2 -2
  51. package/agent-template/.claude/skills/bug-hunt/SKILL.md +1 -1
  52. package/agent-template/.claude/skills/database-adapter/SKILL.md +5 -5
  53. package/agent-template/.claude/skills/database-adapter/references/first-run-and-matrix.md +2 -2
  54. package/agent-template/.claude/skills/deploy-operate/SKILL.md +1 -1
  55. package/agent-template/.claude/skills/migration-authoring/SKILL.md +4 -4
  56. package/agent-template/.claude/skills/module-new/SKILL.md +1 -1
  57. package/agent-template/.claude/skills/spec-interview/SKILL.md +20 -20
  58. package/agent-template/.claude/skills/test-hardening/SKILL.md +2 -2
  59. package/agent-template/.claude/skills/ux-design/SKILL.md +1 -1
  60. package/agent-template/.claude/skills/workflow-development/SKILL.md +95 -9
  61. package/agent-template/.codex/agents/module-executor.toml +17 -0
  62. package/agent-template/.codex/agents/reviewer.toml +14 -0
  63. package/agent-template/.codex/agents/spec-author.toml +15 -0
  64. package/agent-template/AGENTS.md +3 -2
  65. package/agent-template/CLAUDE.md +3 -2
  66. package/agent-template/docs/adr/0007-module-owned-agents.md +35 -1
  67. package/agent-template/docs/agent-contract.md +2 -2
  68. package/agent-template/docs/cli.md +24 -3
  69. package/agent-template/docs/configuration.md +59 -5
  70. package/agent-template/docs/database-adapters.md +20 -20
  71. package/agent-template/docs/design-system.md +3 -3
  72. package/agent-template/docs/getting-started.md +25 -32
  73. package/agent-template/docs/module-distribution.md +79 -86
  74. package/agent-template/docs/module-web-surfaces.md +9 -7
  75. package/agent-template/docs/modules.md +9 -1
  76. package/agent-template/docs/sandbox.md +117 -6
  77. package/agent-template/platform/scripts/build.mjs +7 -0
  78. package/agent-template/rulesync.jsonc +1 -1
  79. package/dist/bin.js +12 -6
  80. package/package.json +2 -2
  81. package/template/default/.env.example +10 -3
  82. package/template/default/.prettierignore +2 -0
  83. package/template/default/.vercelignore +8 -0
  84. package/template/default/README.md +26 -15
  85. package/template/default/_gitignore +3 -2
  86. package/template/default/infra/README.md +86 -65
  87. package/template/default/infra/docker/.env.example +66 -0
  88. package/template/default/infra/docker/Dockerfile +24 -10
  89. package/template/default/infra/docker/app-entrypoint.mjs +5 -0
  90. package/template/default/infra/docker/compose.yaml +105 -58
  91. package/template/default/infra/docker/database-urls.mjs +28 -0
  92. package/template/default/infra/docker/pitr.sh +177 -0
  93. package/template/default/infra/docker/postgres/10-roles.sh +16 -12
  94. package/template/default/infra/docker/start.mjs +402 -0
  95. package/template/default/infra/kubernetes/database-secret.example.yaml +3 -3
  96. package/template/default/infra/vercel/README.md +262 -0
  97. package/template/default/infra/vercel/build.mjs +214 -0
  98. package/template/default/infra/vercel/handler.mjs +100 -0
  99. package/template/default/modules/example/migrations/0001_example_core.up.sql +2 -2
  100. package/template/default/modules/example/module.json +1 -1
  101. package/template/default/modules/example/package.json +3 -3
  102. package/template/default/modules/example/spec/module.yaml +1 -1
  103. package/template/default/modules/example/src/services/migration.ts +2 -2
  104. package/template/default/modules/example/tests/module.test.ts +1 -1
  105. package/template/default/package.json +3 -2
  106. package/template/default/platform/index.html +7 -19
  107. package/template/default/platform/octane.config.ts +252 -156
  108. package/template/default/platform/package.json +5 -5
  109. package/template/default/platform/public/favicon.svg +1 -1
  110. package/template/default/platform/scripts/build.mjs +56 -0
  111. package/template/default/platform/scripts/dev.mjs +38 -0
  112. package/template/default/platform/src/App.tsrx +25 -1
  113. package/template/default/platform/src/generated/modules.server.ts +3 -0
  114. package/template/default/platform/src/server/database.ts +24 -0
  115. package/template/default/platform/src/server/runtime-role.ts +33 -0
  116. package/template/default/platform/src/server/setup/access.ts +160 -0
  117. package/template/default/platform/src/server/setup/adapters.ts +554 -0
  118. package/template/default/platform/src/server/setup/environment.ts +154 -0
  119. package/template/default/platform/src/server/setup/gate.ts +84 -0
  120. package/template/default/platform/src/server/setup/index.ts +181 -0
  121. package/template/default/platform/src/server/setup/modules.ts +123 -0
  122. package/template/default/platform/src/server/setup/page.ts +497 -0
  123. package/template/default/platform/src/server/setup/routes.ts +787 -0
  124. package/template/default/platform/src/server/setup/sanitize.ts +111 -0
  125. package/template/default/platform/src/server/setup/seed.ts +145 -0
  126. package/template/default/platform/src/server/setup/token.ts +79 -0
  127. package/template/default/platform/src/server/worker-tick.ts +193 -0
  128. package/template/default/platform/src/server/workspace-root.ts +16 -0
  129. package/template/default/render.yaml +70 -0
  130. package/template/default/vercel.json +5 -0
package/README.md CHANGED
@@ -17,16 +17,19 @@ npm create flowdular@latest my-app
17
17
  cd my-app
18
18
  ```
19
19
 
20
- For this new local application, initialize demo authentication and start the development server:
20
+ Start the development server:
21
21
 
22
22
  ```sh
23
- pnpm flowdular setup
24
23
  pnpm dev
25
24
  ```
26
25
 
27
- Open [localhost:4310](http://localhost:4310). `setup` opens an interactive wizard for a local demo, PostgreSQL settings or a configuration check. It asks before resetting the local demo database.
26
+ The browser opens the first-run setup automatically. If it cannot open, visit
27
+ [localhost:4310/setup](http://localhost:4310/setup). Enter the token printed in
28
+ the terminal, then create your workspace and owner account. Embedded PostgreSQL
29
+ is already configured. Restart `pnpm dev` when setup finishes, then sign in with
30
+ the owner account you chose.
28
31
 
29
- The starter enables all nine platform modules: system, authentication, users, profile, agents, automations, workflows, their integration, and sandbox access. Local demo setup grants their declared permissions to demo owners. The example module stays available as a starting point; business modules from Official Modules are optional downloads.
32
+ The starter enables all nine platform modules: system, authentication, users, profile, agents, automations, workflows, their integration, and sandbox access. First-run setup grants their declared permissions to the new owner. The example module stays available as a starting point. Module Studio can later connect a catalog, a pinned Git repository or local releases; Sandbox helps create your own modules.
30
33
 
31
34
  `npm create` launches the generator. The generated application uses **pnpm workspaces**. If pnpm is unavailable during installation, the generator invokes its pinned version through `npm exec`.
32
35
 
@@ -50,12 +53,15 @@ The generator installs dependencies and initializes Git by default. Local develo
50
53
 
51
54
  ## Ready for coding agents
52
55
 
53
- Every app includes `.ai` rules, skills, role prompts, blueprints, policies and
54
- reference examples, plus `AGENTS.md`, `CLAUDE.md`, `.agents/skills` and
55
- `.claude/skills`. These files are bundled with the generator and are available
56
- with `--no-install`. Personal agent settings and credentials are never copied.
56
+ Every app includes `.ai` rules, skills, role prompts, subagents, blueprints,
57
+ policies and reference examples, plus `AGENTS.md`, `CLAUDE.md`, `.agents/skills`,
58
+ `.claude/skills` and the spec author, module executor and reviewer as subagents in
59
+ `.claude/agents` and `.codex/agents`. The chat-first sandbox is installed with the
60
+ app, so `pnpm sandbox` starts it without a download. These files are bundled with
61
+ the generator and are available with `--no-install`. Personal agent settings and
62
+ credentials are never copied.
57
63
 
58
- Edit `.ai/rules` or `.ai/skills`, then run `pnpm rules:generate`. `pnpm verify`
64
+ Edit `.ai/rules`, `.ai/skills` or `.ai/subagents`, then run `pnpm rules:generate`. `pnpm verify`
59
65
  checks that the generated instructions are in sync. The instructions explain
60
66
  where to find the installed SDK and how to extend the application's modules.
61
67
 
@@ -93,7 +99,7 @@ pnpm verify
93
99
  pnpm flowdular help
94
100
  ```
95
101
 
96
- Read the [module guide](https://github.com/Flowdular/flowdular/blob/main/docs/modules.md) to extend the application. Business modules from [Official Modules](https://github.com/Flowdular/official-modules) are installed as source through the CLI.
102
+ Read the [module guide](https://github.com/Flowdular/flowdular/blob/main/docs/modules.md) to extend the application and the [Module Studio guide](https://github.com/Flowdular/flowdular/blob/main/docs/module-distribution.md) to install reviewed module source.
97
103
 
98
104
  ## Packages and support
99
105
 
@@ -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 lazily in `start()`, which runs after every module has composed. Tools any module registers during its own compose are therefore visible, whatever the module order.
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 `coreloom_runtime` role).
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
 
@@ -74,7 +74,7 @@ Report each finding as: severity (`blocker`, `should-fix`, `taste`), claim, `fil
74
74
  ```text
75
75
  blocker Tenant id read from body modules/inventory/src/api/endpoints.ts:41
76
76
  A member of tenant A posts { tenantId: "B" } and creates a location in B.
77
- AGENTS.md 6. Fix: principalFromContext(octane)!.tenantId; drop the field.
77
+ AGENTS.md 4. Fix: principalFromContext(octane)!.tenantId; drop the field.
78
78
  ```
79
79
 
80
80
  ## 8. Known platform gaps to keep in mind (not module defects)
@@ -29,7 +29,7 @@ description: >-
29
29
  | View falls back to the dashboard | `ApplicationShell.tsrx` renders `overview` for a view id that no visible navigation or account menu entry reaches |
30
30
  | Icon renders as a grid | `glyph` or `Icon name` is not an `ICON_PATHS` key (`packages/ui/src/icons/Icon.tsrx` falls back to `modules`) |
31
31
  | Stale data after a change | each component owns a store instance (`useMemo(() => createXClientState(), [])`); check the `store.act` that should have written it. `store.commits(cb)` and `store.stats()` from `segment-state` show what was committed |
32
- | Schema error on start | `runModuleMigrations`: `CHECKSUM_MISMATCH` means applied SQL bytes changed; `PARTIAL_OBJECTS` means only part of a pending migration exists. Never delete or bypass the database to hide either condition; restore the shipped bytes or diagnose the partial schema |
32
+ | Schema error on start | `runDatabaseMigrations`: `CHECKSUM_MISMATCH` means applied SQL bytes changed; `PARTIAL_MIGRATION` means only part of a pending migration exists. Never delete or bypass the database to hide either condition; restore the shipped bytes or diagnose the partial schema |
33
33
 
34
34
  ## 2b. Reproduction snippets
35
35
 
@@ -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('coreloom.tenant_id', true)`;
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 `coreloom.tenant_id` with parameterized `set_config(..., true)`
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
- `_coreloom_migrations_v2`, keyed by module namespace and migration id; its
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 `coreloom_runtime` and
161
- `coreloom_background` roles and the same forced row-level security a deployment
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 `coreloom_runtime` and
87
- `coreloom_background` roles and forced row-level security with it.
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 `_coreloom_migrations_v2` ledger. The commands (`packages/cli/src/runner.ts`):
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 `_coreloom_migrations_v2` ledger. A row records namespace, migration
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('coreloom.tenant_id', true))
80
- WITH CHECK (tenant_id = current_setting('coreloom.tenant_id', true));
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 `coreloom.tenant_id` on the pinned connection. Queries still
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('coreloom.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`.
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
 
@@ -23,26 +23,26 @@ For an edit, also read the module's current `spec/module.yaml` and write the sma
23
23
 
24
24
  One pass, in this order. For each row, write the default from the card into the spec and record it as a `decisions[]` entry with `decidedBy: default`. Ask only where the answer is a business fact that no default can supply.
25
25
 
26
- | Decision | Default to propose | Lands in |
27
- | ---------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------- |
28
- | Actors | Owner manages, member reads | `permissions`, `invariants` |
29
- | Entities and fields | One primary entity; `name` required, `maxLength` 120; no field the request did not name | `entities[]` |
30
- | Uniqueness | The human-facing code is `unique: tenant`; everything else `none` | `entities[].fields[].unique` |
31
- | States and transitions | `active` and `archived`, every transition behind the manage permission | `entities[].states` |
32
- | Who sees what | Both permissions in the same navigation entry; the manage action hidden without the scope | `permissions`, `screens[]`, `invariants` |
33
- | What is denied | Unauthenticated 401, missing permission 403, cross-tenant read returns nothing | `acceptanceScenarios` |
34
- | Failure behaviour | A duplicate returns a stable conflict and changes nothing; bounds return 400 | `invariants`, `acceptanceScenarios` |
35
- | Cross-module reads | None. A read of another module goes through its public capability and a declared dependency | `dependencies`, `dataOwnership` |
36
- | Screens | One `list` screen with the entity's identifying columns | `screens[]` |
37
- | Widgets | None. A count belongs on `dashboard.metrics` only when the request asks for it | `widgets[]` |
38
- | Settings | None. A number the business may change later is `scope: tenant` with a stated default | `settings[]` |
39
- | Feature flags | Ask for one whenever a change alters behaviour a workspace already relies on, or is hard to undo: `kind: flag`, boolean, `scope: tenant`, a stated default, and the behaviour named in an invariant | `settings[]` |
40
- | Agent tools | None. A tool is a later phase and `risk` may only be `read` or `workspace-write` | `agentTools[]` |
41
- | Outside sources | None. A named public source is `research`, with the entity its findings attach to | `research` |
42
- | Other systems | None. A named system is one `source` adapter per record kind, run on demand | `adapters[]` |
43
- | Documents | None. A named document is one `templates[]` entry on the record it describes | `templates[]` |
44
- | Reports | None. There is no export, no PDF and no search; a report is a screen or it is out of scope | `outOfScope[]` |
45
- | Out of scope | Every item from the card's gap list the request touched, each with its business decision | `outOfScope[]`, `decisions[]` |
26
+ | Decision | Default to propose | Lands in |
27
+ | ---------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------- |
28
+ | Actors | Owner manages, member reads | `permissions`, `invariants` |
29
+ | Entities and fields | One primary entity; `name` required, `maxLength` 120; no field the request did not name | `entities[]` |
30
+ | Uniqueness | The human-facing code is `unique: tenant`; everything else `none` | `entities[].fields[].unique` |
31
+ | States and transitions | `active` and `archived`, every transition behind the manage permission | `entities[].states` |
32
+ | Who sees what | Both permissions in the same navigation entry; the manage action hidden without the scope | `permissions`, `screens[]`, `invariants` |
33
+ | What is denied | Unauthenticated 401, missing permission 403, cross-tenant read returns nothing | `acceptanceScenarios` |
34
+ | Failure behaviour | A duplicate returns a stable conflict and changes nothing; bounds return 400 | `invariants`, `acceptanceScenarios` |
35
+ | Cross-module reads | None. A read of another module goes through its public capability and a declared dependency | `dependencies`, `dataOwnership` |
36
+ | Screens | One `list` screen with the entity's identifying columns | `screens[]` |
37
+ | Widgets | None. A count belongs on `dashboard.metrics` only when the request asks for it | `widgets[]` |
38
+ | Settings | None. A number the business may change later is `scope: tenant` with a stated default | `settings[]` |
39
+ | Feature flags | Ask for one whenever a change alters behaviour a workspace already relies on, or is hard to undo: `kind: flag`, boolean, `scope: tenant`, a stated default, and the behaviour named in an invariant | `settings[]` |
40
+ | Agent tools | None. A tool is a later phase and `risk` may only be `read` or `workspace-write` | `agentTools[]` |
41
+ | Outside sources | None. A named public source is `research`, with the entity its findings attach to | `research` |
42
+ | Other systems | None. A named system is one `source` adapter per record kind, run on demand | `adapters[]` |
43
+ | Documents | None. A named document is one `templates[]` entry on the record it describes | `templates[]` |
44
+ | Reports | A named report is one provider on `reports.v1` returning tiles and series; a named list export is one `defineListExport`; named records are findable through `search.providers.v1`. None of the three has a specification key, so each needs a decision naming the screen or the `actions[]` entry that registers it | `actions[]`, `invariants` |
45
+ | Out of scope | Every item from the card's gap list the request touched, each with its business decision | `outOfScope[]`, `decisions[]` |
46
46
 
47
47
  A default you propose is still a decision: it goes into `decisions[]` so the operator can see and overturn it, and so the next agent never re-derives it.
48
48
 
@@ -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 `coreloom_runtime` and `coreloom_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.
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 `coreloom_runtime` role, so this runs against real forced row-level security; also assert that a call without tenant context fails with `TENANT_CONTEXT_REQUIRED`.
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.
@@ -81,7 +81,7 @@ Read-only master-detail (runs, playground) keeps `ui-two-col` (+ `--wide-aside`)
81
81
  - `Alert`: `tone` danger (default), warning, info.
82
82
  - `Avatar`: `name`, `square` (organizations), `large`.
83
83
  - `Icon`: `name`, `size` (18 default, 16 in controls, 14 in `Button size="sm"`), `strokeWidth`.
84
- - `BrandMark`: `size`, `signature`, `tone`; brand moments only.
84
+ - `BrandMark`: `size`, `tone`; three bars with a copper accent bar, brand moments only.
85
85
 
86
86
  Icon keys (`ICON_PATHS`, `packages/ui/src/icons/Icon.tsrx`): `dashboard`, `parties`, `catalog`, `user`, `users`, `shield`, `code`, `modules`, `file-text`, `play`, `bot`, `flask`, `activity`, `plug`, `search`, `chevron-down`, `chevron-left`, `chevron-right`, `chevrons-up-down`, `sort`, `calendar`, `plus`, `panel-left`, `check`, `filter`, `download`, `more`, `external`, `alert`, `x`, `sign-out`, `refresh`, `help`, `info`, `key`, `settings`, `braces`, `copy`. An unknown name renders `modules` silently, so check the list.
87
87
 
@@ -1,9 +1,8 @@
1
1
  ---
2
2
  name: workflow-development
3
3
  description: >-
4
- Build, publish, invoke, and test a workflows.core DAG through its typed graph
5
- and public execution capability without bypassing agent, action, tenant, or
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
- Read `docs/adr/0006-agentic-workflows.md`, the approved
16
- `modules/workflows/spec/module.yaml`, and the contracts in
17
- `modules/workflows/src/domain/types.ts` before changing a workflow surface.
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 it through the agents action catalog. If missing, implement it in a
25
- separate `agent-tool-design` phase with permission, input, output, timeout,
26
- idempotency and audit tests before returning to workflow integration.
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
 
@@ -10,6 +10,7 @@ Flowdular is an agentic foundation framework. The platform under `packages/`, `m
10
10
  | `rules/*.md` | Canonical cross-agent instructions. RuleSync generates the root `AGENTS.md` and `CLAUDE.md` from these files; `pnpm rules:check` rejects drift. |
11
11
  | `agents/sandbox/*.md` | Loaded at sandbox start by `packages/coding-agent/src/roles/registry.ts` (`loadAgentRoles`); `gates`, `handoff` and `allowedPaths` are enforced. `dependencies` always runs, a `HANDOFF:` line must name a role from the list and never the role itself, and writes outside the active role allowlist are quarantined and restored before validation. Defaults in `packages/coding-agent/src/roles/defaults.ts` are regenerated from these files by `pnpm --filter @flowdular/coding-agent sync-roles`, and `tests/sync.test.ts` fails when they drift. |
12
12
  | `agents/{module-executor,reviewer,spec-author}.md` | Read by people and coding tools at the repository root; named in `blueprints/*/blueprint.json`. Not loaded by code. |
13
+ | `subagents/*.md` | The same three root roles as delegable subagents. RuleSync generates `.claude/agents/<name>.md` and `.codex/agents/<name>.toml` from them, so Claude Code and Codex can hand a phase to the role; each body points at its `agents/<id>.md` prompt and its one task skill. |
13
14
  | `skills/*/SKILL.md` | Canonical cross-agent procedures. They are copied into every sandbox session as `reference/skills/` (`packages/sandbox/src/server/reference.ts`) and RuleSync generates the discovery copies under `.agents/skills` and `.claude/skills`. |
14
15
  | `blueprints/*/blueprint.json` | `pnpm flowdular blueprint list` and `blueprint validate --all` (`packages/cli/src/runner.ts`, discovery in `packages/cli/src/validation.ts` `findNamedFiles`) validate every `blueprint.json` against `packages/contracts/schemas/blueprint.schema.json` and check the companion files exist; `pnpm validate` runs it in CI. The sandbox labels sessions `new-module@1.0.0` and `edit-module@1.0.0`. |
15
16
  | `blueprints/*/*.yaml`, `*.schema.json`, `examples/` | Existence-checked by `validateBlueprint`; otherwise documentation for agents and reviewers. Nothing executes `steps.yaml` or `gates.yaml`. |
@@ -17,7 +18,7 @@ Flowdular is an agentic foundation framework. The platform under `packages/`, `m
17
18
  | `policies/task-budgets.yaml`, `policies/path-ownership.yaml` | Read by the `git-pr` delivery target (`packages/sandbox/src/server/delivery/policies.ts`): the changed-file and new-dependency budget per session kind, and path owners plus `crossOwnerChanges.requireReviewer` for the pull request body. Review guidance otherwise. |
18
19
  | `examples/**` | Reference shapes for agents; not compiled or tested. |
19
20
 
20
- `AGENTS.md` and `docs/design-system.md` are copied into each session's `reference/` as well (`packages/sandbox/src/server/reference.ts`). `AGENTS.md`, `CLAUDE.md`, `.agents/skills` and `.claude/skills` are generated compatibility outputs. Edit `.ai/rules` or `.ai/skills`, then run `pnpm rules:generate`.
21
+ `AGENTS.md` and `docs/design-system.md` are copied into each session's `reference/` as well (`packages/sandbox/src/server/reference.ts`). `AGENTS.md`, `CLAUDE.md`, `.agents/skills`, `.claude/skills`, `.claude/agents` and `.codex/agents` are generated compatibility outputs. Edit `.ai/rules`, `.ai/skills` or `.ai/subagents`, then run `pnpm rules:generate`.
21
22
 
22
23
  ## Using the skills from your own tool
23
24
 
@@ -27,8 +28,8 @@ sandbox turn receives one Task skill from `packages/coding-agent/src/roles/skill
27
28
  not the entire catalog. Character-budget and routing tests guard against prompt
28
29
  growth; characters are a stable size proxy, not a tokenizer-specific cost estimate.
29
30
 
30
- - Claude Code reads the generated `CLAUDE.md` and discovers the generated `.claude/skills` copies.
31
- - Codex reads the generated `AGENTS.md` and discovers the generated `.agents/skills` copies.
31
+ - Claude Code reads the generated `CLAUDE.md` and discovers the generated `.claude/skills` copies and the `.claude/agents` subagents.
32
+ - Codex reads the generated `AGENTS.md` and discovers the generated `.agents/skills` copies and the `.codex/agents` subagents.
32
33
  - Any other tool: paste `AGENTS.md` and the skill into the instruction.
33
34
 
34
35
  Both paths land the same way: gates, then `pnpm flowdular module enable <id> --apply` for a new module (it grants the module's scopes as its last step; `auth sync-scopes` re-grants later), `pnpm verify`, pull request (`skills/release-eject-pr`). A sandbox session is a pnpm workspace of its own with the draft modules as projects, so declared dependencies resolve for real and the `dependencies` gate runs after every turn.
@@ -39,6 +40,7 @@ Both paths land the same way: gates, then `pnpm flowdular module enable <id> --a
39
40
  - Skill: edit `skills/<kebab-name>/SKILL.md` with `name`, `description`, `roles`, `when`; keep it focused, verify API claims against the cited code, add a row in `skills/README.md`, then run `pnpm rules:generate`.
40
41
  - Blueprint: a directory under `blueprints/` with `blueprint.json` valid against `packages/contracts/schemas/blueprint.schema.json` plus `README.md`, `input.schema.json`, `plan.schema.json`, `spec-requirements.yaml`, `allowed-paths.yaml`, `required-files.yaml`, `steps.yaml`, `gates.yaml` (all required by `validateBlueprint`), and `examples/valid`, `examples/invalid` (`input*.json` validate against `input.schema.json`, `plan*.json` against `plan.schema.json`). `agentRoles` use the role ids from `agents/`; `executorProfiles` use the profile ids in `policies/model-routing.yaml`; gate ids for module blueprints come from `packages/sandbox/src/server/gates.ts`.
41
42
  - Sandbox role: `agents/sandbox/<id>.md` with the front matter `id`, `name`, `purpose`, `allowedPaths`, `gates`, `handoff` (see `agents/README.md`), then regenerate the bundled defaults: `pnpm --filter @flowdular/coding-agent sync-roles` (the script provided by `packages/coding-agent`; it rewrites `src/roles/defaults.ts` from these files) and run `pnpm --filter @flowdular/coding-agent test`.
43
+ - Root subagent: `subagents/<name>.md` with the front matter `name`, `targets`, `description` and an optional `claudecode` block, a body that names the `agents/<id>.md` prompt and the one task skill, then `pnpm rules:generate`. Keep the procedure in the role prompt; the subagent file stays a pointer so the two cannot drift.
42
44
  - Policy: keep it truthful about what code enforces; name the file that does.
43
45
 
44
46
  Formatting: `npx prettier --write .ai docs`, then `pnpm rules:generate`. No em or en dashes anywhere.
@@ -17,7 +17,7 @@ The five roles and who takes the first turn: `business-manager` for both a new m
17
17
 
18
18
  ## Root roles run at the repository root
19
19
 
20
- `module-executor.md`, `reviewer.md`, `spec-author.md` describe the same jobs for an agent working in a checkout with a shell (Claude Code, Codex, a person). No code loads them; `.ai/blueprints/*/blueprint.json` names them in `agentRoles` and `requiredReviewers`. Their `allowedPaths` are relative to the repository root and they run the gates themselves with the commands listed in each blueprint's `gates.yaml`.
20
+ `module-executor.md`, `reviewer.md`, `spec-author.md` describe the same jobs for an agent working in a checkout with a shell (Claude Code, Codex, a person). No code loads them; `.ai/blueprints/*/blueprint.json` names them in `agentRoles` and `requiredReviewers`, and `.ai/subagents/<id>.md` exposes each one as a delegable subagent that points back here (RuleSync writes `.claude/agents` and `.codex/agents`). Their `allowedPaths` are relative to the repository root and they run the gates themselves with the commands listed in each blueprint's `gates.yaml`.
21
21
 
22
22
  ## Adding or changing a role
23
23
 
@@ -13,6 +13,7 @@ gates:
13
13
  - spec-schema
14
14
  - module-schema
15
15
  - dependencies
16
+ - module-rules
16
17
  - typecheck
17
18
  - tests
18
19
  handoff:
@@ -24,6 +24,7 @@ allowedPaths:
24
24
  gates:
25
25
  - module-schema
26
26
  - dependencies
27
+ - module-rules
27
28
  - typecheck
28
29
  - tests
29
30
  - format
@@ -8,6 +8,7 @@ allowedPaths:
8
8
  - 'package.json'
9
9
  gates:
10
10
  - dependencies
11
+ - module-rules
11
12
  - typecheck
12
13
  - tests
13
14
  - format
@@ -1,5 +1,5 @@
1
1
  # add-migration
2
2
 
3
- Add a table, column, index or constraint through the numbered migration runner. The `.up.sql` file is the source, `src/services/migration.ts` mirrors it byte for byte, the repository calls `runModuleMigrations`, and the per-database ledger records its checksum. Existing databases adopt complete pre-ledger schema without replaying it. Procedure: `.ai/skills/migration-authoring/SKILL.md`.
3
+ Add a table, column, index or constraint through the numbered migration runner. The `.up.sql` file is the source, `src/services/migration.ts` mirrors it byte for byte, the repository calls `runDatabaseMigrations`, and the per-database ledger records its checksum. Existing databases adopt complete pre-ledger schema without replaying it. Procedure: `.ai/skills/migration-authoring/SKILL.md`.
4
4
 
5
5
  Additive only. A destructive change (drop, type change, tightened check) is refused by this blueprint's input schema and needs an operator decision.
@@ -11,7 +11,7 @@ rules:
11
11
  - PostgreSQL only; CREATE TABLE IF NOT EXISTS
12
12
  - tenant_id TEXT NOT NULL on tenant-owned tables
13
13
  - UNIQUE and indexes start with tenant_id, indexes end with id
14
- - a new tenant table enables and forces row-level security and adds a <table>_tenant_policy on current_setting('coreloom.tenant_id', true)
14
+ - a new tenant table enables and forces row-level security and adds a <table>_tenant_policy on current_setting('flowdular.tenant_id', true)
15
15
  - ALTER TABLE ADD COLUMN runs once through the ledger; inspectExisting detects an existing column
16
16
  - applied migration bytes are immutable; a change always gets a new number
17
17
  - inspectExisting returns complete only for every effect, absent only for none, partial for anything mixed
@@ -26,7 +26,7 @@ manifest:
26
26
  - ./server
27
27
  - ./platform
28
28
  src/platform.ts:
29
- - createServerComposition(context) returns routes, optional settings (defineModuleSettings), optional start() and optional dispose()
29
+ - createServerComposition(context) returns routes, optional settings (defineModuleSettings), optional start() for registration only, optional startWorker() that starts every poll loop and background worker, optional stop() and optional dispose()
30
30
  - context carries auth, settings (ModuleSettingsRuntime), agentTools (PlatformToolRegistry), agentDefinitions (PlatformAgentRegistry) and capabilities (PlatformCapabilityRegistry)
31
31
  conditional:
32
32
  api:
@@ -2,7 +2,7 @@
2
2
 
3
3
  `api.ts` sits under `src/client` and imports `../services/database-repository.ts`, which pulls in `@flowdular/sdk/database` and its `node:fs`, `node:path` and `node:async_hooks` imports. The Vite client build fails on the Node built-ins, and even where it compiled the browser would hold a database handle and a `tenantId` parameter chosen by the caller, bypassing every endpoint check.
4
4
 
5
- Violated rules: `AGENTS.md` 5 and 6 (data reaches the client only through an endpoint that resolves identity and takes the tenant from the principal) and the layering in `.ai/skills/core-extend/SKILL.md` (client code imports `@flowdular/sdk/client`, `@flowdular/sdk/ui`, `octane`, `segment-state`, and the module's own `src/client` and `src/domain` types; never `src/services` or `src/server`).
5
+ Violated rules: `AGENTS.md` 3 and 4 (data reaches the client only through an endpoint that resolves identity and takes the tenant from the principal) and the layering in `.ai/skills/core-extend/SKILL.md` (client code imports `@flowdular/sdk/client`, `@flowdular/sdk/ui`, `octane`, `segment-state`, and the module's own `src/client` and `src/domain` types; never `src/services` or `src/server`).
6
6
 
7
7
  Repair with the `bug-fix` blueprint: replace the import with a `fetch` in `src/client/api.ts` as in `.ai/references/catalog/src/client/api.ts`:
8
8
 
@@ -2,7 +2,7 @@
2
2
 
3
3
  `endpoints.ts` mounts `/api/customers` with `new ServerRoute` from `@octanejs/app-core`. Nothing resolves an identity, nothing checks a permission, and `listAll()` has no tenant. Anyone who can reach the server reads every tenant's customers.
4
4
 
5
- Violated rules: `AGENTS.md` 5 (every endpoint is `defineEndpoint` with `access: { kind: 'permission' }` and `resolveIdentity: endpointIdentityFromContext`) and 6 (tenant id from the principal). The security skill's first grep (`grep -rn "new ServerRoute" modules/*/src | grep -v modules/auth`) finds it.
5
+ Violated rules: `AGENTS.md` 3 (every endpoint is `defineEndpoint` with `access: { kind: 'permission' }` and `resolveIdentity: endpointIdentityFromContext`) and 4 (tenant id from the principal). The security skill's first grep (`grep -rn "new ServerRoute" modules/*/src | grep -v modules/auth`) finds it.
6
6
 
7
7
  Repair with the `bug-fix` blueprint (or `edit-module`, change class `endpoint`):
8
8
 
@@ -2,7 +2,7 @@
2
2
 
3
3
  `endpoints.ts` has a permission and an identity resolver, and still lets a member of tenant A create a customer in tenant B by posting `{ "tenantId": "B", "name": "..." }`. The permission check passes because the principal holds `customers.records.manage` in its own tenant; the body decides where the row lands. The mutation also skips `sessionMutationDenial`, so a cross-site form post with a valid cookie would be accepted.
4
4
 
5
- Violated rules: `AGENTS.md` 6 (tenant id only from `principalFromContext(octane)!.tenantId`) and 7 (`sessionMutationDenial(octane, auth)` first on every mutation). The security skill's grep `grep -rn tenantId modules/*/src/api | grep -v principalFromContext` finds it.
5
+ Violated rules: `AGENTS.md` 4 (tenant id only from `principalFromContext(octane)!.tenantId`) and 3 (an explicit permission and an identity resolver on every endpoint). The security skill's grep `grep -rn tenantId modules/*/src/api | grep -v principalFromContext` finds it.
6
6
 
7
7
  Repair with the `bug-fix` blueprint: remove the body field, take `auth: AuthRuntime` in `createCustomerRoutes`, and write
8
8