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
@@ -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
 
@@ -0,0 +1,17 @@
1
+ name = "module-executor"
2
+ description = "Implement one Flowdular blueprint end to end at the repository root: scaffold from an approved spec, write the module, run the gates and join it to the platform through the CLI. Use once a spec is approved and the change is ready to be built."
3
+ developer_instructions = '''
4
+ Your role prompt is `.ai/agents/module-executor.md`. Read it with the blueprint
5
+ under `.ai/blueprints/<id>/` and the one matching skill in
6
+ `.ai/skills/<name>/SKILL.md` before changing anything.
7
+
8
+ Implement only what the approved spec's acceptance scenarios describe, inside the
9
+ blueprint's `allowed-paths.yaml`. Join the platform through
10
+ `pnpm flowdular module enable <id> --apply` and `auth sync-scopes`; never edit
11
+ `flowdular.json`, `platform/package.json`, `platform/src/generated/**` or
12
+ `platform/octane.config.ts` by hand.
13
+
14
+ Run the module gates and `pnpm verify` yourself and report their exact commands
15
+ and results. Never waive a gate. End with `HANDOFF: reviewer - <what to review>`
16
+ or `HANDOFF: none - <blocker>`.
17
+ '''
@@ -0,0 +1,14 @@
1
+ name = "reviewer"
2
+ description = "Review a finished Flowdular change against its approved spec, the blueprint, the invariants and the executable evidence, and report findings by severity. Use as a separate phase before delivery or a pull request. Reports defects and fixes nothing."
3
+ developer_instructions = '''
4
+ Your role prompt is `.ai/agents/reviewer.md` and the one task skill for this
5
+ phase is `.ai/skills/auto-review/SKILL.md`. Read both before reviewing.
6
+
7
+ You write no production code. Review the complete requested change, its
8
+ requirements, callers and tests; report concrete findings by severity with file,
9
+ line and failure scenario. Never waive missing or failing verification, and never
10
+ approve a change whose evidence you have not seen run.
11
+
12
+ End with `HANDOFF: module-executor - <findings to fix>` or
13
+ `HANDOFF: none - <review result and remaining verification>`.
14
+ '''
@@ -0,0 +1,15 @@
1
+ name = "spec-author"
2
+ description = "Turn a business request into a schema-valid Flowdular module specification at the repository root. Use before any implementation, for a new module spec or a change to an existing one. Writes only modules/<dir>/spec/module.yaml and never approves it."
3
+ developer_instructions = '''
4
+ Your role prompt is `.ai/agents/spec-author.md` and the one task skill for this
5
+ phase is `.ai/skills/spec-interview/SKILL.md`. Read both before writing.
6
+
7
+ You write only `modules/<dir>/spec/module.yaml`. Propose a platform default for
8
+ every decision and ask the user for what cannot be inferred; never guess a
9
+ business fact. Never set `status: approved`: approval is the user's, recorded by
10
+ the `spec-approval` skill.
11
+
12
+ `pnpm flowdular spec validate --all --json` must report the file valid before you
13
+ report. End with `HANDOFF: reviewer - spec ready for owner approval` or
14
+ `HANDOFF: none - <open question>`.
15
+ '''
@@ -78,5 +78,6 @@ made in the Flowdular repository and released before this application uses it.
78
78
  The skills and examples use @flowdular/sdk subpath imports. For pnpm --filter,
79
79
  read the actual module package name from its package.json. Use pnpm verify and
80
80
  pnpm build for this application. Root .ai files are editable project guidance;
81
- run pnpm rules:generate after changing rules or skills, then pnpm rules:check.
82
- AGENTS.md, CLAUDE.md, .agents/skills and .claude/skills are generated copies.
81
+ run pnpm rules:generate after changing rules, skills or subagents, then pnpm
82
+ rules:check. AGENTS.md, CLAUDE.md, .agents/skills, .claude/skills, .claude/agents
83
+ and .codex/agents are generated copies.
@@ -78,5 +78,6 @@ made in the Flowdular repository and released before this application uses it.
78
78
  The skills and examples use @flowdular/sdk subpath imports. For pnpm --filter,
79
79
  read the actual module package name from its package.json. Use pnpm verify and
80
80
  pnpm build for this application. Root .ai files are editable project guidance;
81
- run pnpm rules:generate after changing rules or skills, then pnpm rules:check.
82
- AGENTS.md, CLAUDE.md, .agents/skills and .claude/skills are generated copies.
81
+ run pnpm rules:generate after changing rules, skills or subagents, then pnpm
82
+ rules:check. AGENTS.md, CLAUDE.md, .agents/skills, .claude/skills, .claude/agents
83
+ and .codex/agents are generated copies.
@@ -266,6 +266,39 @@ existing bindings in one transaction:
266
266
  - a definition absent from the sealed registry is unavailable for new work;
267
267
  retained revisions and historical runs remain untouched.
268
268
 
269
+ Amendment (2026-10-05, `agents.core` 0.15.2): reconciliation no longer runs at
270
+ runtime start, and a lower revision no longer fails boot.
271
+
272
+ - `agents.core` opens once in every role, before its first request, capability
273
+ call or `startWorker()`. Opening runs its migrations, then reconciles the
274
+ registered definitions into the durable catalogue. When every definition
275
+ matches its stored revision and content hash, opening costs one catalogue read
276
+ and writes nothing. Otherwise it takes the advisory lock
277
+ `agents.core.module-catalog`, reads the catalogue again under it and writes
278
+ only what is still missing or behind, so roles that open together never fail
279
+ with a duplicate key. A failed opening fails only the request or worker start
280
+ that triggered it, and the next one opens again.
281
+ - Opening never reads or writes a tenant binding. A request that lists, reads or
282
+ runs a module agent advances that tenant's stale binding first, inside its own
283
+ tenant transaction, under the binding row lock and a share lock on the
284
+ catalogue row, so concurrent requests advance it once. Configuring a binding
285
+ writes it at the served revision under the same locks. After its execution
286
+ workers start, the worker advances the bindings no request has touched, in
287
+ pages of at most 100 tenants, and logs a failing tenant without tenant data.
288
+ That pass never gates worker readiness.
289
+ - Tenant-created definitions saved before the retained revision ledger are
290
+ adopted once. A request adopts its own tenant's owed definitions in its own
291
+ transaction, and the worker adopts the rest in pages after it starts.
292
+ - An instance that registers a lower revision than the catalogue holds, such as
293
+ an older deployment during a rolling deploy, is superseded for that agent and
294
+ keeps running. It writes nothing for the agent, lists it unavailable with
295
+ `MODULE_AGENT_REVISION_SUPERSEDED`, refuses a binding change or a run of it
296
+ with status 409 and logs a warning once per process and agent. Queued runs and
297
+ workflow references pinned to a retained executable revision keep the
298
+ exact-revision contract.
299
+ - The same revision with different content is still refused, by preparation and
300
+ by the opening, as `MODULE_AGENT_REVISION_DRIFT`.
301
+
269
302
  An old retained revision remains executable after a newer module definition is
270
303
  registered only while the owning module is still present, the selected provider
271
304
  is usable, and every required registered tool still exists. Module removal is
@@ -327,7 +360,8 @@ not change shape.
327
360
  - Business-agent definition content is immutable within one definition
328
361
  revision.
329
362
  - Duplicate ids, late registration, revision downgrade and same-revision content
330
- drift fail platform boot before workers start.
363
+ drift fail platform boot before workers start. Since the 2026-10-05
364
+ amendment, a revision downgrade supersedes the agent instead.
331
365
  - Module behavior cannot be edited or deleted through tenant APIs.
332
366
  - Tenant bindings cannot enable a tool outside the code allowlist.
333
367
  - Every execution is tenant-scoped and uses the initiating Actor and trusted
@@ -14,7 +14,7 @@ Use the already selected task skill. Consult `.ai/references/catalog` for implem
14
14
  3. Identifiers: module, permission and capability ids match `^[a-z][a-z0-9-]*(\.[a-z][a-z0-9-]*)+$`. `inventory.core` lives in `modules/inventory` as `@flowdular/module-inventory`; permissions are `<module>.<entity>.read` and `<module>.<entity>.manage`.
15
15
  4. A module is created only from a spec with `status: approved`. An agent never initiates, infers, or grants approval from its own judgment. After a current, explicit user instruction naming the spec, a host coding agent may record that decision mechanically by following `spec-approval`; a sandbox specialist can only request approval, and the operator route records the exact approved hash. Spec `permissions[].id` equal the constants in `src/acl/permissions.ts`: the spec is what `auth sync-scopes` grants.
16
16
  5. Every endpoint is `defineEndpoint` from `@flowdular/sdk/server` with `access: { kind: 'permission', permission }` and `resolveIdentity: endpointIdentityFromContext`. Deny by default; a public endpoint needs a written reason. No raw `new ServerRoute` outside `modules/auth`.
17
- 6. The tenant id comes only from `principalFromContext(octane)!.tenantId`, never from the body, query or headers. Every query on a tenant-owned table filters by `tenant_id`; unique constraints and indexes start with `tenant_id`; SQL uses bound parameters. PostgreSQL tenant tables also enable and force row-level security with `USING` and `WITH CHECK` policies bound to transaction-local `coreloom.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.
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 `_coreloom_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.
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 `coreloom_background` policy
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
- ## Official module distribution
222
+ ## Module Studio distribution
202
223
 
203
- `module search`, `module info`, `module install`, `module update`, `module recover`, and `module validate --locked` manage reviewed external source. See [the distribution contract](module-distribution.md) for flags, trust, activation and recovery.
224
+ `module source`, `module search`, `module info`, `module plan`, `module apply`, `module recover`, and `module validate --locked` manage reviewed external source. See [Module Studio](module-distribution.md) for flags, trust, activation and recovery.
@@ -11,6 +11,7 @@ deployments must set the secret keys.
11
11
  | `FD_ENV` | `NODE_ENV`, else `development` | Environment the CLI and destructive guards check |
12
12
  | `FD_PORT` | `3000` | Host port published by the container |
13
13
  | `FD_TRUST_PROXY` | `false` | Trust `X-Forwarded-*` behind a reverse proxy |
14
+ | `FD_RUNTIME_ROLE` | `combined` | `combined` serves HTTP and runs module workers; `web` or `tick`, see below |
14
15
  | `FD_CSP` | built-in policy | Override the Content Security Policy |
15
16
  | `FD_CSP_REPORT_ONLY` | `true` outside production | Report CSP violations instead of enforcing them |
16
17
  | `FD_LOG_FORMAT` | `json` in production, else `text` | `json` (one object per line) or `text`; see [operations.md](operations.md) |
@@ -18,6 +19,43 @@ deployments must set the secret keys.
18
19
  | `FD_METRICS` | `false` | Expose `GET /api/metrics`; see [operations.md](operations.md) |
19
20
  | `FD_METRICS_TOKEN` | none | Bearer token a metrics scrape must present |
20
21
 
22
+ A `web` process serves HTTP only: it never starts a module worker and never
23
+ claims queued work from a request, so a deployment of `web` processes also needs
24
+ a `combined` or a `tick` process on the same database, object storage and keys.
25
+ A `tick` process runs the module workers only inside a tick request that
26
+ presents `FD_WORKER_TICK_SECRET` (at least 32 characters) as a bearer token, for
27
+ at most `FD_WORKER_TICK_WINDOW_MS` (default 50000), and drains them before it
28
+ answers.
29
+ A Vercel deployment works this way; see
30
+ [infra/vercel/README.md](../infra/vercel/README.md).
31
+
32
+ ## Branding
33
+
34
+ The name, the document title, the description, the link preview image, the
35
+ browser icon, the theme colour and the logo are not environment variables: they
36
+ are `system.core` settings an owner with `system.settings.manage` changes under
37
+ Administration, Branding, and every change is audited. One value serves the
38
+ whole deployment, so the sign-in screen and a shared link carry it too, and a
39
+ setting nobody changed renders the product's own.
40
+
41
+ An address is stored only as a path on this deployment (`/brand/logo.svg`) or an
42
+ https URL; `javascript:`, `data:` and protocol-relative values are refused when
43
+ they are written. Every https branding image origin is added to `img-src` of the
44
+ policy this deployment serves, including an `FD_CSP` of your own, so the browser
45
+ loads it; an `FD_CSP` without an `img-src` directive is left alone and then has
46
+ to name the origin itself. The label an authenticator lists an enrolled account
47
+ under is the same name, decided by the server, so a member who enrols after a
48
+ rename sees the new one.
49
+
50
+ An application scaffolded before this release owns its own `platform/src/App.tsrx`
51
+ and `platform/index.html`, so its head does not follow the settings until it
52
+ adopts two changes the template now carries: `configureBrandingFromPage(props)`
53
+ plus the `Seo`, `Link` and `Meta` block in the entry, and the removal of the
54
+ static `<link rel="icon">` and `<meta name="theme-color">` from the page, which
55
+ would otherwise compete with the rendered ones. `pnpm flowdular doctor` reports
56
+ both as the `platform.branding` check. The navigation, the mobile header and the
57
+ sign-in screen follow the settings without any change.
58
+
21
59
  ## Observability
22
60
 
23
61
  Spans are always recorded into a bounded in-process buffer and the logger always
@@ -262,6 +300,7 @@ message was not delivered.
262
300
  | `FD_AGENT_RUN_GRANT_KEY` | generated dev key | Base64 32-byte key signing run grants |
263
301
  | `FD_AGENT_WORKER_CONCURRENCY` | `2` (1 to 16) | Parallel run workers |
264
302
  | `FD_AGENT_WORKER_LEASE_MS` | `30000` | Run lease before recovery reclaims it |
303
+ | `FD_AGENT_WORKER_DRAIN_MS` | `0` (0 to 720000) | Time a stopping worker lets claimed runs finish |
265
304
  | `FD_AGENT_PROVIDER_HOST_ALLOWLIST` | empty | Hostnames an external provider may be called on |
266
305
 
267
306
  Outside production the keys are generated once under `.flowdular/data`. The
@@ -631,7 +670,7 @@ an object moved into another tenant's prefix does not open.
631
670
 
632
671
  | Variable | Default | Purpose |
633
672
  | ------------------------------------ | ----------------------------------- | ---------------------------------------------------------------------------- |
634
- | `FD_STORAGE_ADAPTER` | `s3` in production, else `local` | `local` or `s3`; `local` is refused in production |
673
+ | `FD_STORAGE_ADAPTER` | `s3` in production, else `local` | `local`, `s3` or `vercel-blob`; `local` is refused in production |
635
674
  | `FD_STORAGE_LOCAL_DIRECTORY` | `.flowdular/data/storage` | Object directory of the local adapter |
636
675
  | `FD_STORAGE_S3_BUCKET` | none | Bucket name; required by the S3 adapter |
637
676
  | `FD_STORAGE_S3_REGION` | none | Signing region; required by the S3 adapter |
@@ -639,10 +678,23 @@ an object moved into another tenant's prefix does not open.
639
678
  | `FD_STORAGE_S3_ACCESS_KEY_ID` | none | Access key id; required by the S3 adapter |
640
679
  | `FD_STORAGE_S3_SECRET_ACCESS_KEY` | none | Secret access key; required by the S3 adapter |
641
680
  | `FD_STORAGE_S3_FORCE_PATH_STYLE` | `false` | `<endpoint>/<bucket>/<key>` instead of a bucket subdomain |
681
+ | `BLOB_STORE_ID` | set by Vercel | Blob store of the `vercel-blob` adapter, authenticated with Vercel OIDC |
682
+ | `BLOB_READ_WRITE_TOKEN` | none | Blob read-write token for the `vercel-blob` adapter outside Vercel |
642
683
  | `FD_STORAGE_MAX_OBJECT_BYTES` | `26214400` (25 MiB) | Per-object limit, 1024 to 268435456; a stream is cut off at it |
643
684
  | `FD_STORAGE_ENCRYPTION_KEY` | derived dev key | Base64 32-byte key sealing every object and read URL; required in production |
644
685
  | `FD_STORAGE_ENCRYPTION_KEY_PREVIOUS` | empty | Retired object keys, comma separated, read only |
645
686
 
687
+ `vercel-blob` keeps the encrypted objects in a private Vercel Blob store, so a
688
+ Vercel deployment needs no separate bucket. Connecting a Blob store to the
689
+ Vercel project sets `BLOB_STORE_ID`, and the SDK authenticates with the
690
+ deployment's OIDC token, so nothing else is configured there. Outside Vercel,
691
+ set `BLOB_READ_WRITE_TOKEN`. The platform refuses to start when neither is
692
+ present. Reads bypass the Blob cache, so a re-sealed or deleted object is never
693
+ served from an older copy. A Vercel Function accepts at most 4.5 MB of request
694
+ or response body, so set `FD_STORAGE_MAX_OBJECT_BYTES` to at most `4194304`
695
+ there. Lowering the limit makes an existing larger object unreadable through
696
+ this adapter, and a key rotation leaves it sealed under its old key.
697
+
646
698
  A module writes through `context.storage` and never sees an adapter, a bucket or
647
699
  a path. Only these content types are stored, and the bytes are verified against
648
700
  the declared type before the write: PDF, PNG, JPEG, GIF, WebP, plain text, CSV,
@@ -719,7 +771,9 @@ acquires a lease from the platform provider configured above, so the
719
771
  openssl rand -base64 32
720
772
  ```
721
773
 
722
- For containers, copy `infra/docker/.env.example` to `infra/docker/.env` and fill
723
- in every empty value: every encryption key above, including the connectors
724
- and audit anchor keys, the object store settings and the four PostgreSQL role
725
- passwords. See [../infra/README.md](../infra/README.md).
774
+ For a local Docker installation, run `node infra/docker/start.mjs`. It creates
775
+ `infra/docker/.env` with missing secrets, starts PostgreSQL and the bundled
776
+ object store, then opens the first-run web setup. Keep that file with database
777
+ and object-store backups; changing its keys or passwords later does not rotate
778
+ existing data or PostgreSQL roles. Other deployments supply these values through
779
+ their own secret manager. See [../infra/README.md](../infra/README.md).
@@ -79,11 +79,11 @@ memory.
79
79
  The embedded adapter creates the same roles a deployment configures, so a local
80
80
  run enforces the isolation a deployment enforces instead of approximating it.
81
81
 
82
- | Role | Owns | Constraints |
83
- | --------------------- | ------------------------------------------- | ------------------------------------------------------------------------------------------- |
84
- | `coreloom_migrator` | The schema. Serves the `migration` purpose. | Owns every table the migrations create. |
85
- | `coreloom_runtime` | Request-time reads and writes. | No `SUPERUSER`, no `BYPASSRLS`, and every handle it lends requires a transaction tenant id. |
86
- | `coreloom_background` | Cross-tenant polls. | Read-only, and no blanket table grant. |
82
+ | Role | Owns | Constraints |
83
+ | ---------------------- | ------------------------------------------- | ------------------------------------------------------------------------------------------- |
84
+ | `flowdular_migrator` | The schema. Serves the `migration` purpose. | Owns every table the migrations create. |
85
+ | `flowdular_runtime` | Request-time reads and writes. | No `SUPERUSER`, no `BYPASSRLS`, and every handle it lends requires a transaction tenant id. |
86
+ | `flowdular_background` | Cross-tenant polls. | Read-only, and no blanket table grant. |
87
87
 
88
88
  ## Leases
89
89
 
@@ -100,13 +100,13 @@ const lease = await context.databases.acquire({
100
100
  });
101
101
  ```
102
102
 
103
- | Purpose | Role | Used for |
104
- | ------------ | --------------------- | --------------------------------------------------- |
105
- | `migration` | `coreloom_migrator` | Applying migrations and resetting a database |
106
- | `runtime` | `coreloom_runtime` | The deployed application |
107
- | `preview` | `coreloom_runtime` | A local run and the sandbox preview |
108
- | `test` | `coreloom_runtime` | A test suite |
109
- | `background` | `coreloom_background` | A scheduler poll or recovery that precedes a tenant |
103
+ | Purpose | Role | Used for |
104
+ | ------------ | ---------------------- | --------------------------------------------------- |
105
+ | `migration` | `flowdular_migrator` | Applying migrations and resetting a database |
106
+ | `runtime` | `flowdular_runtime` | The deployed application |
107
+ | `preview` | `flowdular_runtime` | A local run and the sandbox preview |
108
+ | `test` | `flowdular_runtime` | A test suite |
109
+ | `background` | `flowdular_background` | A scheduler poll or recovery that precedes a tenant |
110
110
 
111
111
  A runtime acquires its leases lazily, one per runtime, and releases them from
112
112
  composition `dispose()`. `modules/profile/src/server/runtime.ts` is the shape:
@@ -164,14 +164,14 @@ codes.
164
164
 
165
165
  Every tenant table enables and forces row-level security and carries a policy
166
166
  whose `USING` and `WITH CHECK` compare `tenant_id` with the transaction-local
167
- `coreloom.tenant_id` setting:
167
+ `flowdular.tenant_id` setting:
168
168
 
169
169
  ```sql
170
170
  ALTER TABLE profile_records ENABLE ROW LEVEL SECURITY;
171
171
  ALTER TABLE profile_records FORCE ROW LEVEL SECURITY;
172
172
  CREATE POLICY profile_records_tenant_policy ON profile_records
173
- USING (tenant_id = current_setting('coreloom.tenant_id', true))
174
- WITH CHECK (tenant_id = current_setting('coreloom.tenant_id', true));
173
+ USING (tenant_id = current_setting('flowdular.tenant_id', true))
174
+ WITH CHECK (tenant_id = current_setting('flowdular.tenant_id', true));
175
175
  ```
176
176
 
177
177
  `database.transaction(body, { tenantId, access })` sets that value for the
@@ -201,7 +201,7 @@ repository.
201
201
 
202
202
  ## Migrations
203
203
 
204
- The ledger is `_coreloom_migrations_v2`. It carries the module namespace because
204
+ The ledger is `_flowdular_migrations_v2`. It carries the module namespace because
205
205
  every module shares one database. Checksums cover the exact SQL, and a mismatch
206
206
  is checked before any outstanding migration runs.
207
207
 
@@ -257,7 +257,7 @@ schema.
257
257
  `migration verify` checks that every applied ledger checksum still matches, that
258
258
  every tenant table a migration leaves behind has `ENABLE ROW LEVEL SECURITY`,
259
259
  `FORCE ROW LEVEL SECURITY` and a tenant policy declared after the last statement
260
- that puts the table in place, that a `coreloom_background` policy grants no more
260
+ that puts the table in place, that a `flowdular_background` policy grants no more
261
261
  than `FOR SELECT`, and that every `migrations/*.up.sql` file has a matching id in
262
262
  `databaseMigrations` and the other way round.
263
263
 
@@ -272,10 +272,10 @@ A table it may poll says so itself, in its own migration:
272
272
 
273
273
  ```sql
274
274
  CREATE POLICY <table>_background_policy ON <table>
275
- FOR SELECT TO coreloom_background
275
+ FOR SELECT TO flowdular_background
276
276
  USING (<the narrowest predicate that still finds the work>);
277
- REVOKE SELECT ON <table> FROM coreloom_background;
278
- GRANT SELECT (<only the columns the poll reads>) ON <table> TO coreloom_background;
277
+ REVOKE SELECT ON <table> FROM flowdular_background;
278
+ GRANT SELECT (<only the columns the poll reads>) ON <table> TO flowdular_background;
279
279
  ```
280
280
 
281
281
  A table that forgets to is invisible to that role, and every column outside the
@@ -25,7 +25,7 @@ shared primitives, tokens, and the rules for using them.
25
25
  refuses a class no stylesheet declares, there and in every `.tsrx`.
26
26
  - `platform/public`: `favicon.svg`, `og.png` (1200x630 Open Graph image).
27
27
  - Brand mark geometry is generated: `node packages/ui/scripts/gen-mark.mjs`
28
- rewrites `packages/ui/src/brand/mark.ts` from the weave parameters.
28
+ rewrites `packages/ui/src/brand/mark.ts` from the bar parameters.
29
29
 
30
30
  ## Rules
31
31
 
@@ -270,7 +270,7 @@ them; outside the shell the English defaults and the host locale apply.
270
270
  | `Tabs` | Accessible tablist: required `id`, `items` (`id`, `label`, `disabled`), `active`, `onChange`, `label`; arrows, Home and End move roving focus, an `active` that names no enabled tab selects the first enabled one, and the caller renders the panel |
271
271
  | `SortableList` | Reorderable list: required `id`, `label`, `items` (`id`, `label`), `onReorder(ids)`, `handleLabel(item)`, `instructions`, `announce(event)`, `renderItem(item, index)`, `disabled`; a grip per item drags with a pointer or picks up, moves and drops from the keyboard, with a polite live region |
272
272
  | `ToastHost` | Renders the toast queue: `label`, `closeLabel`, optional `store`. Raise toasts with `toasts.success/error/info(message)`; `createToastStore` makes a scoped queue |
273
- | `PageHeader` | Every view starts with it: `eyebrow`, `title`, `description`; children render as right-side actions |
273
+ | `PageHeader` | Every view starts with it: `eyebrow`, `title`, `description`; children render as right-side actions, which move under the title and wrap onto more rows when the header is too narrow |
274
274
  | `EmptyState` | `icon`, `title`, children, optional `code` |
275
275
  | `Alert` | Inline message: `tone` danger (default), warning, info |
276
276
  | `Drawer` | Editor panel over the records: `open`, `title`, `subtitle`, `width` md/lg, `onClose`; traps Tab and restores focus to the opener |
@@ -282,7 +282,7 @@ them; outside the shell the English defaults and the host locale apply.
282
282
  | `Switch` | Boolean setting that applies on its own (no form submit): `checked`, `label` as the accessible name, `disabled`, `onChange` |
283
283
  | `ConfirmDialog` | One question before an irreversible action: `open`, `title`, children, `confirmLabel`, `tone` danger (default) or primary, `busy`, `onConfirm`, `onCancel`; traps Tab and restores focus to the opener |
284
284
  | `Icon` | Stroke icon by `name` from `ICON_PATHS`; `size` 18 default, 16 in controls, 14 in `Button size="sm"`; `strokeWidth` 1.75 default |
285
- | `BrandMark` | The weave: `size`, `signature` (copper weft, large brand moments only), `tone` brand, current, inverse |
285
+ | `BrandMark` | Three bars, the short one copper: `size`, `tone` brand, current, inverse (`signature` is accepted and changes nothing) |
286
286
 
287
287
  `Drawer` takes one child, a `ui-drawer__form` (fields in `ui-drawer__body`, actions in `ui-drawer__foot`) or a plain `ui-drawer__body`; it closes on Escape and on the scrim. `SearchField` carries no visible label, so pass `label` as its accessible name. `FormField` renders `error` in place of `help` and marks it `role="alert"`. `SettingRow` is presentation only: the caller owns the draft value, the save call, and passes the result back as `status`. `ScopeSummary` is the read side of `CheckGrid`; both keep the first-seen module order, and `summarizeScopes` is exported for callers that need the grouping without the markup.
288
288