create-flowdular 0.2.3 → 0.2.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +11 -0
- package/agent-template/.agents/skills/agent-tool-design/SKILL.md +203 -0
- package/agent-template/.agents/skills/auth-security-review/SKILL.md +90 -0
- package/agent-template/.agents/skills/auto-review/SKILL.md +103 -0
- package/agent-template/.agents/skills/bug-hunt/SKILL.md +104 -0
- package/agent-template/.agents/skills/business-agent-design/SKILL.md +182 -0
- package/agent-template/.agents/skills/cli-extension/SKILL.md +108 -0
- package/agent-template/.agents/skills/core-extend/SKILL.md +99 -0
- package/agent-template/.agents/skills/database-adapter/SKILL.md +198 -0
- package/agent-template/.agents/skills/database-adapter/references/first-run-and-matrix.md +105 -0
- package/agent-template/.agents/skills/migration-authoring/SKILL.md +161 -0
- package/agent-template/.agents/skills/module-new/SKILL.md +171 -0
- package/agent-template/.agents/skills/module-update/SKILL.md +91 -0
- package/agent-template/.agents/skills/perf-audit/SKILL.md +98 -0
- package/agent-template/.agents/skills/release-eject-pr/SKILL.md +107 -0
- package/agent-template/.agents/skills/spec-approval/SKILL.md +106 -0
- package/agent-template/.agents/skills/test-hardening/SKILL.md +79 -0
- package/agent-template/.agents/skills/translations-i18n/SKILL.md +78 -0
- package/agent-template/.agents/skills/ux-design/SKILL.md +92 -0
- package/agent-template/.agents/skills/variables/SKILL.md +156 -0
- package/agent-template/.agents/skills/workflow-development/SKILL.md +192 -0
- package/agent-template/.ai/README.md +62 -0
- package/agent-template/.ai/agents/README.md +27 -0
- package/agent-template/.ai/agents/module-executor.md +36 -0
- package/agent-template/.ai/agents/reviewer.md +23 -0
- package/agent-template/.ai/agents/sandbox/agentic-engineer.md +31 -0
- package/agent-template/.ai/agents/sandbox/backend-engineer.md +36 -0
- package/agent-template/.ai/agents/sandbox/business-manager.md +23 -0
- package/agent-template/.ai/agents/sandbox/frontend-engineer.md +27 -0
- package/agent-template/.ai/agents/sandbox/ux-designer.md +23 -0
- package/agent-template/.ai/agents/spec-author.md +29 -0
- package/agent-template/.ai/blueprints/add-migration/README.md +5 -0
- package/agent-template/.ai/blueprints/add-migration/allowed-paths.yaml +23 -0
- package/agent-template/.ai/blueprints/add-migration/blueprint.json +14 -0
- package/agent-template/.ai/blueprints/add-migration/examples/invalid/input-destructive.json +6 -0
- package/agent-template/.ai/blueprints/add-migration/examples/invalid/plan-unnumbered-file.json +9 -0
- package/agent-template/.ai/blueprints/add-migration/examples/valid/input.json +6 -0
- package/agent-template/.ai/blueprints/add-migration/examples/valid/plan.json +9 -0
- package/agent-template/.ai/blueprints/add-migration/gates.yaml +30 -0
- package/agent-template/.ai/blueprints/add-migration/input.schema.json +23 -0
- package/agent-template/.ai/blueprints/add-migration/plan.schema.json +54 -0
- package/agent-template/.ai/blueprints/add-migration/required-files.yaml +18 -0
- package/agent-template/.ai/blueprints/add-migration/spec-requirements.yaml +13 -0
- package/agent-template/.ai/blueprints/add-migration/steps.yaml +62 -0
- package/agent-template/.ai/blueprints/author-spec/README.md +5 -0
- package/agent-template/.ai/blueprints/author-spec/allowed-paths.yaml +7 -0
- package/agent-template/.ai/blueprints/author-spec/blueprint.json +14 -0
- package/agent-template/.ai/blueprints/author-spec/examples/invalid/input-missing-outcome.json +5 -0
- package/agent-template/.ai/blueprints/author-spec/examples/valid/input.json +6 -0
- package/agent-template/.ai/blueprints/author-spec/gates.yaml +13 -0
- package/agent-template/.ai/blueprints/author-spec/input.schema.json +20 -0
- package/agent-template/.ai/blueprints/author-spec/plan.schema.json +14 -0
- package/agent-template/.ai/blueprints/author-spec/required-files.yaml +6 -0
- package/agent-template/.ai/blueprints/author-spec/spec-requirements.yaml +35 -0
- package/agent-template/.ai/blueprints/author-spec/steps.yaml +28 -0
- package/agent-template/.ai/blueprints/author-spec/templates/module.yaml +45 -0
- package/agent-template/.ai/blueprints/bug-fix/README.md +5 -0
- package/agent-template/.ai/blueprints/bug-fix/allowed-paths.yaml +29 -0
- package/agent-template/.ai/blueprints/bug-fix/blueprint.json +14 -0
- package/agent-template/.ai/blueprints/bug-fix/examples/invalid/input-no-symptom.json +4 -0
- package/agent-template/.ai/blueprints/bug-fix/examples/invalid/plan-no-test.json +8 -0
- package/agent-template/.ai/blueprints/bug-fix/examples/valid/input.json +6 -0
- package/agent-template/.ai/blueprints/bug-fix/examples/valid/plan.json +8 -0
- package/agent-template/.ai/blueprints/bug-fix/gates.yaml +30 -0
- package/agent-template/.ai/blueprints/bug-fix/input.schema.json +25 -0
- package/agent-template/.ai/blueprints/bug-fix/plan.schema.json +53 -0
- package/agent-template/.ai/blueprints/bug-fix/required-files.yaml +7 -0
- package/agent-template/.ai/blueprints/bug-fix/spec-requirements.yaml +7 -0
- package/agent-template/.ai/blueprints/bug-fix/steps.yaml +51 -0
- package/agent-template/.ai/blueprints/core-extend/README.md +5 -0
- package/agent-template/.ai/blueprints/core-extend/allowed-paths.yaml +49 -0
- package/agent-template/.ai/blueprints/core-extend/blueprint.json +14 -0
- package/agent-template/.ai/blueprints/core-extend/examples/invalid/input-unknown-package.json +5 -0
- package/agent-template/.ai/blueprints/core-extend/examples/invalid/plan-missing-gates.json +7 -0
- package/agent-template/.ai/blueprints/core-extend/examples/valid/input.json +6 -0
- package/agent-template/.ai/blueprints/core-extend/examples/valid/plan.json +10 -0
- package/agent-template/.ai/blueprints/core-extend/gates.yaml +16 -0
- package/agent-template/.ai/blueprints/core-extend/input.schema.json +54 -0
- package/agent-template/.ai/blueprints/core-extend/plan.schema.json +39 -0
- package/agent-template/.ai/blueprints/core-extend/required-files.yaml +36 -0
- package/agent-template/.ai/blueprints/core-extend/spec-requirements.yaml +17 -0
- package/agent-template/.ai/blueprints/core-extend/steps.yaml +54 -0
- package/agent-template/.ai/blueprints/edit-module/README.md +9 -0
- package/agent-template/.ai/blueprints/edit-module/allowed-paths.yaml +27 -0
- package/agent-template/.ai/blueprints/edit-module/blueprint.json +20 -0
- package/agent-template/.ai/blueprints/edit-module/examples/invalid/input-unknown-change.json +5 -0
- package/agent-template/.ai/blueprints/edit-module/examples/invalid/plan-touches-platform.json +15 -0
- package/agent-template/.ai/blueprints/edit-module/examples/valid/input.json +5 -0
- package/agent-template/.ai/blueprints/edit-module/examples/valid/plan.json +25 -0
- package/agent-template/.ai/blueprints/edit-module/gates.yaml +30 -0
- package/agent-template/.ai/blueprints/edit-module/input.schema.json +31 -0
- package/agent-template/.ai/blueprints/edit-module/plan.schema.json +65 -0
- package/agent-template/.ai/blueprints/edit-module/required-files.yaml +80 -0
- package/agent-template/.ai/blueprints/edit-module/spec-requirements.yaml +15 -0
- package/agent-template/.ai/blueprints/edit-module/steps.yaml +115 -0
- package/agent-template/.ai/blueprints/new-module/README.md +7 -0
- package/agent-template/.ai/blueprints/new-module/allowed-paths.yaml +27 -0
- package/agent-template/.ai/blueprints/new-module/blueprint.json +20 -0
- package/agent-template/.ai/blueprints/new-module/examples/invalid/input-spec-outside-modules.json +4 -0
- package/agent-template/.ai/blueprints/new-module/examples/invalid/plan-unknown-gate.json +8 -0
- package/agent-template/.ai/blueprints/new-module/examples/valid/input.json +5 -0
- package/agent-template/.ai/blueprints/new-module/examples/valid/plan.json +22 -0
- package/agent-template/.ai/blueprints/new-module/gates.yaml +30 -0
- package/agent-template/.ai/blueprints/new-module/input.schema.json +21 -0
- package/agent-template/.ai/blueprints/new-module/plan.schema.json +58 -0
- package/agent-template/.ai/blueprints/new-module/required-files.yaml +73 -0
- package/agent-template/.ai/blueprints/new-module/spec-requirements.yaml +30 -0
- package/agent-template/.ai/blueprints/new-module/steps.yaml +138 -0
- package/agent-template/.ai/blueprints/release/README.md +5 -0
- package/agent-template/.ai/blueprints/release/allowed-paths.yaml +19 -0
- package/agent-template/.ai/blueprints/release/blueprint.json +14 -0
- package/agent-template/.ai/blueprints/release/examples/invalid/input-bad-version.json +4 -0
- package/agent-template/.ai/blueprints/release/examples/invalid/plan-bad-branch.json +7 -0
- package/agent-template/.ai/blueprints/release/examples/valid/input.json +5 -0
- package/agent-template/.ai/blueprints/release/examples/valid/plan.json +20 -0
- package/agent-template/.ai/blueprints/release/gates.yaml +20 -0
- package/agent-template/.ai/blueprints/release/input.schema.json +24 -0
- package/agent-template/.ai/blueprints/release/plan.schema.json +46 -0
- package/agent-template/.ai/blueprints/release/required-files.yaml +19 -0
- package/agent-template/.ai/blueprints/release/spec-requirements.yaml +8 -0
- package/agent-template/.ai/blueprints/release/steps.yaml +47 -0
- package/agent-template/.ai/blueprints/security-review/README.md +5 -0
- package/agent-template/.ai/blueprints/security-review/allowed-paths.yaml +6 -0
- package/agent-template/.ai/blueprints/security-review/blueprint.json +14 -0
- package/agent-template/.ai/blueprints/security-review/examples/invalid/input-unknown-kind.json +4 -0
- package/agent-template/.ai/blueprints/security-review/examples/invalid/plan-finding-without-scenario.json +14 -0
- package/agent-template/.ai/blueprints/security-review/examples/valid/input.json +4 -0
- package/agent-template/.ai/blueprints/security-review/examples/valid/plan.json +19 -0
- package/agent-template/.ai/blueprints/security-review/gates.yaml +22 -0
- package/agent-template/.ai/blueprints/security-review/input.schema.json +20 -0
- package/agent-template/.ai/blueprints/security-review/plan.schema.json +65 -0
- package/agent-template/.ai/blueprints/security-review/required-files.yaml +6 -0
- package/agent-template/.ai/blueprints/security-review/spec-requirements.yaml +9 -0
- package/agent-template/.ai/blueprints/security-review/steps.yaml +38 -0
- package/agent-template/.ai/examples/README.md +8 -0
- package/agent-template/.ai/examples/bad/client-imports-server/README.md +20 -0
- package/agent-template/.ai/examples/bad/client-imports-server/api.ts +12 -0
- package/agent-template/.ai/examples/bad/missing-acl/README.md +23 -0
- package/agent-template/.ai/examples/bad/missing-acl/endpoints.ts +12 -0
- package/agent-template/.ai/examples/bad/tenant-from-body/README.md +19 -0
- package/agent-template/.ai/examples/bad/tenant-from-body/endpoints.ts +33 -0
- package/agent-template/.ai/examples/client-contribution/CustomerListView.tsrx +34 -0
- package/agent-template/.ai/examples/client-contribution/README.md +11 -0
- package/agent-template/.ai/examples/client-contribution/contribution.tsrx +48 -0
- package/agent-template/.ai/examples/client-contribution/index.ts +20 -0
- package/agent-template/.ai/examples/client-contribution/permissions.ts +8 -0
- package/agent-template/.ai/examples/customer-cli-extension/README.md +14 -0
- package/agent-template/.ai/examples/customer-cli-extension/commands.json +17 -0
- package/agent-template/.ai/examples/customer-cli-extension/index.ts +36 -0
- package/agent-template/.ai/examples/module-create/task-packet.json +11 -0
- package/agent-template/.ai/guides/application-development.md +97 -0
- package/agent-template/.ai/policies/capabilities.yaml +164 -0
- package/agent-template/.ai/policies/model-routing.yaml +72 -0
- package/agent-template/.ai/policies/path-ownership.yaml +65 -0
- package/agent-template/.ai/policies/task-budgets.yaml +37 -0
- package/agent-template/.ai/references/catalog/LICENSE +21 -0
- package/agent-template/.ai/references/catalog/migrations/0001_catalog_core.down.sql +2 -0
- package/agent-template/.ai/references/catalog/migrations/0001_catalog_core.up.sql +21 -0
- package/agent-template/.ai/references/catalog/migrations/0002_catalog_history.down.sql +3 -0
- package/agent-template/.ai/references/catalog/migrations/0002_catalog_history.up.sql +20 -0
- package/agent-template/.ai/references/catalog/migrations/0003_catalog_history_service_actors.down.sql +3 -0
- package/agent-template/.ai/references/catalog/migrations/0003_catalog_history_service_actors.up.sql +36 -0
- package/agent-template/.ai/references/catalog/migrations/0004_catalog_idempotency_ledger.down.sql +3 -0
- package/agent-template/.ai/references/catalog/migrations/0004_catalog_idempotency_ledger.up.sql +19 -0
- package/agent-template/.ai/references/catalog/migrations/README.md +3 -0
- package/agent-template/.ai/references/catalog/module.json +27 -0
- package/agent-template/.ai/references/catalog/package.json +49 -0
- package/agent-template/.ai/references/catalog/spec/module.yaml +86 -0
- package/agent-template/.ai/references/catalog/src/acl/permissions.ts +6 -0
- package/agent-template/.ai/references/catalog/src/agent/tools.ts +164 -0
- package/agent-template/.ai/references/catalog/src/api/endpoints.ts +243 -0
- package/agent-template/.ai/references/catalog/src/client/CatalogHistoryDrawer.tsrx +123 -0
- package/agent-template/.ai/references/catalog/src/client/CatalogItemForm.tsrx +190 -0
- package/agent-template/.ai/references/catalog/src/client/CatalogView.tsrx +473 -0
- package/agent-template/.ai/references/catalog/src/client/api.ts +111 -0
- package/agent-template/.ai/references/catalog/src/client/contribution.tsrx +61 -0
- package/agent-template/.ai/references/catalog/src/client/index.ts +18 -0
- package/agent-template/.ai/references/catalog/src/client/navigation-copy.ts +9 -0
- package/agent-template/.ai/references/catalog/src/client/state.ts +24 -0
- package/agent-template/.ai/references/catalog/src/domain/types.ts +32 -0
- package/agent-template/.ai/references/catalog/src/domain/variables.ts +111 -0
- package/agent-template/.ai/references/catalog/src/index.ts +31 -0
- package/agent-template/.ai/references/catalog/src/platform.ts +35 -0
- package/agent-template/.ai/references/catalog/src/server/index.ts +4 -0
- package/agent-template/.ai/references/catalog/src/server/runtime.ts +86 -0
- package/agent-template/.ai/references/catalog/src/services/catalog-service.ts +306 -0
- package/agent-template/.ai/references/catalog/src/services/database-repository.ts +440 -0
- package/agent-template/.ai/references/catalog/src/services/index.ts +4 -0
- package/agent-template/.ai/references/catalog/src/services/migration.ts +171 -0
- package/agent-template/.ai/references/catalog/src/services/repository.ts +36 -0
- package/agent-template/.ai/references/catalog/src/services/target-idempotency.ts +59 -0
- package/agent-template/.ai/references/catalog/tests/agent-tools.test.ts +277 -0
- package/agent-template/.ai/references/catalog/tests/endpoints.test.ts +320 -0
- package/agent-template/.ai/references/catalog/tests/idempotency.test.ts +297 -0
- package/agent-template/.ai/references/catalog/tests/migrations.test.ts +149 -0
- package/agent-template/.ai/references/catalog/tests/module.test.ts +271 -0
- package/agent-template/.ai/references/catalog/tests/support/database.ts +76 -0
- package/agent-template/.ai/references/catalog/translations/en.json +101 -0
- package/agent-template/.ai/references/catalog/translations/pl.json +101 -0
- package/agent-template/.ai/references/catalog/tsconfig.json +15 -0
- package/agent-template/.ai/references/catalog/vitest.config.ts +16 -0
- package/agent-template/.ai/references/catalog.provenance.json +55 -0
- package/agent-template/.ai/rules/flowdular.md +86 -0
- package/agent-template/.ai/skills/README.md +36 -0
- package/agent-template/.ai/skills/agent-tool-design/SKILL.md +209 -0
- package/agent-template/.ai/skills/auth-security-review/SKILL.md +96 -0
- package/agent-template/.ai/skills/auto-review/SKILL.md +112 -0
- package/agent-template/.ai/skills/bug-hunt/SKILL.md +110 -0
- package/agent-template/.ai/skills/business-agent-design/SKILL.md +188 -0
- package/agent-template/.ai/skills/cli-extension/SKILL.md +114 -0
- package/agent-template/.ai/skills/core-extend/SKILL.md +104 -0
- package/agent-template/.ai/skills/database-adapter/SKILL.md +204 -0
- package/agent-template/.ai/skills/database-adapter/references/first-run-and-matrix.md +105 -0
- package/agent-template/.ai/skills/migration-authoring/SKILL.md +167 -0
- package/agent-template/.ai/skills/module-new/SKILL.md +180 -0
- package/agent-template/.ai/skills/module-update/SKILL.md +100 -0
- package/agent-template/.ai/skills/perf-audit/SKILL.md +105 -0
- package/agent-template/.ai/skills/release-eject-pr/SKILL.md +113 -0
- package/agent-template/.ai/skills/spec-approval/SKILL.md +112 -0
- package/agent-template/.ai/skills/test-hardening/SKILL.md +86 -0
- package/agent-template/.ai/skills/translations-i18n/SKILL.md +85 -0
- package/agent-template/.ai/skills/ux-design/SKILL.md +97 -0
- package/agent-template/.ai/skills/variables/SKILL.md +164 -0
- package/agent-template/.ai/skills/workflow-development/SKILL.md +199 -0
- package/agent-template/.claude/skills/agent-tool-design/SKILL.md +203 -0
- package/agent-template/.claude/skills/auth-security-review/SKILL.md +90 -0
- package/agent-template/.claude/skills/auto-review/SKILL.md +103 -0
- package/agent-template/.claude/skills/bug-hunt/SKILL.md +104 -0
- package/agent-template/.claude/skills/business-agent-design/SKILL.md +182 -0
- package/agent-template/.claude/skills/cli-extension/SKILL.md +108 -0
- package/agent-template/.claude/skills/core-extend/SKILL.md +99 -0
- package/agent-template/.claude/skills/database-adapter/SKILL.md +198 -0
- package/agent-template/.claude/skills/database-adapter/references/first-run-and-matrix.md +105 -0
- package/agent-template/.claude/skills/migration-authoring/SKILL.md +161 -0
- package/agent-template/.claude/skills/module-new/SKILL.md +171 -0
- package/agent-template/.claude/skills/module-update/SKILL.md +91 -0
- package/agent-template/.claude/skills/perf-audit/SKILL.md +98 -0
- package/agent-template/.claude/skills/release-eject-pr/SKILL.md +107 -0
- package/agent-template/.claude/skills/spec-approval/SKILL.md +106 -0
- package/agent-template/.claude/skills/test-hardening/SKILL.md +79 -0
- package/agent-template/.claude/skills/translations-i18n/SKILL.md +78 -0
- package/agent-template/.claude/skills/ux-design/SKILL.md +92 -0
- package/agent-template/.claude/skills/variables/SKILL.md +156 -0
- package/agent-template/.claude/skills/workflow-development/SKILL.md +192 -0
- package/agent-template/AGENTS.md +77 -0
- package/agent-template/CLAUDE.md +77 -0
- package/agent-template/docs/adr/0001-development-reload.md +16 -0
- package/agent-template/docs/adr/0002-durable-agent-execution.md +21 -0
- package/agent-template/docs/adr/0003-module-settings.md +22 -0
- package/agent-template/docs/adr/0004-enterprise-access-and-audit.md +36 -0
- package/agent-template/docs/adr/0005-sandbox-runtime-and-coding-agents.md +81 -0
- package/agent-template/docs/adr/0006-agentic-workflows.md +1702 -0
- package/agent-template/docs/adr/0007-module-owned-agents.md +429 -0
- package/agent-template/docs/adr/0008-database-adapter-contract.md +90 -0
- package/agent-template/docs/agent-contract.md +45 -0
- package/agent-template/docs/configuration.md +122 -0
- package/agent-template/docs/database-adapters.md +346 -0
- package/agent-template/docs/design-system.md +217 -0
- package/agent-template/docs/modules.md +146 -0
- package/agent-template/platform/scripts/build.mjs +38 -0
- package/agent-template/rulesync.jsonc +11 -0
- package/dist/bin.js +3 -1
- package/package.json +3 -2
- package/template/default/.prettierignore +9 -0
- package/template/default/README.md +12 -0
- package/template/default/flowdular.json +3 -3
- package/template/default/modules/example/package.json +1 -1
- package/template/default/package.json +6 -2
- package/template/default/platform/octane.config.ts +17 -6
- package/template/default/platform/package.json +3 -2
- package/template/default/platform/scripts/dev.mjs +39 -6
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: release-eject-pr
|
|
3
|
+
description: >-
|
|
4
|
+
Land a module or core change: the sandbox eject sequence, the repository
|
|
5
|
+
verification gates, the git branch and PR conventions, and the post-merge
|
|
6
|
+
scope grant.
|
|
7
|
+
---
|
|
8
|
+
# Eject, verify, deliver
|
|
9
|
+
|
|
10
|
+
Two paths reach the same place. The sandbox path is chat, gates, preview, eject. The direct path is a skill in your own coding tool, `pnpm verify`, a pull request. Both end with the module enabled through the CLI and its scopes granted.
|
|
11
|
+
|
|
12
|
+
## 1. Sandbox eject (`packages/sandbox/src/server/delivery/{local,steps}.ts`, `routes.ts`)
|
|
13
|
+
|
|
14
|
+
`POST /sandbox/api/sessions/:id/eject` with `{ apply: false }` returns the plan: for every module of the session the files that land in `modules/<dir>`, the files that would be overwritten, the files the session deleted and the eject will remove, packages a module declares that the workspace cannot resolve yet, the gates, and whether the connected application has to restart. With `{ apply: true }` the sandbox streams the steps:
|
|
15
|
+
|
|
16
|
+
1. Gates: `spec-schema` and `module-schema` once, then `dependencies`, `typecheck`, `tests`, `format` per draft module. Any failure stops the eject before a file is written (`EJECT_GATES_FAILED`).
|
|
17
|
+
2. Copy of each session module into `modules/<dir>`, then removal of the files an edit deleted (`removeModuleFiles`, empty directories included).
|
|
18
|
+
3. `pnpm install` at the workspace root.
|
|
19
|
+
4. `pnpm flowdular module enable <id> --apply --json` for each new module (writes `flowdular.json`, `platform/package.json`, `platform/src/generated/*`, and grants the module's scopes itself).
|
|
20
|
+
5. `pnpm flowdular auth sync-scopes --module <id> --apply --json` for each module (idempotent re-grant, needed for edited modules that added a permission).
|
|
21
|
+
6. `pnpm --filter @flowdular/platform typecheck`.
|
|
22
|
+
7. Optionally `pnpm build`.
|
|
23
|
+
8. A restart note: the connected application loads the new composition and runs new schema constants only at start, so a local `pnpm dev` restarts and a remote deployment redeploys.
|
|
24
|
+
|
|
25
|
+
A failing step stops the delivery there with the step's output; the session is marked delivered only when every step passed. Eject requires the connected grant to hold `sandbox.modules.eject`. Delivery targets sit behind one interface (`delivery/types.ts`); the request names one with `target: 'workspace' | 'git-pr'` (default from `flowdular.json`), `workspace` is the one above, `git-pr` is section 2.
|
|
26
|
+
|
|
27
|
+
## 2. Git delivery from a sandbox (`target: 'git-pr'`, `packages/sandbox/src/server/delivery/git-pr.ts`)
|
|
28
|
+
|
|
29
|
+
The pull request is the unit of a delivery: one session, one branch, one PR, every module the session touched. Nothing in the operator's working tree or index changes; the work happens in a detached worktree under `.flowdular/sandbox/worktrees/<session>` that is removed afterwards, whatever the outcome.
|
|
30
|
+
|
|
31
|
+
- Available when the workspace is a git work tree with at least one commit, `.flowdular/` is ignored, and the configured remote exists (`git rev-parse --verify HEAD`, `git remote get-url <remote>`); a repository without commits answers "make the first commit before delivering as a pull request". A PR is opened when `gh auth status` succeeds (a provider token sealed in the sandbox configuration is handed to gh as `GH_TOKEN`); otherwise the branch is pushed and the compare link shown.
|
|
32
|
+
- Branch `<branchPrefix>/<module-dir>-<session id first 8>` from `<remote>/<baseBranch>`: `git fetch`, `git worktree add --detach`, `git switch -C`.
|
|
33
|
+
- In the worktree: the copy and the removals, `pnpm install --offline` (fallback `--prefer-offline`), `pnpm flowdular module enable <id> --apply` for each new module with the worktree as `--dir`, the platform typecheck.
|
|
34
|
+
- Guardrails before the commit: `git status --porcelain` in the worktree may list only `modules/<dir>/**` of the session's modules and `pnpm-lock.yaml`. A delivery with a new module may also change `flowdular.json`, `platform/package.json` and `platform/src/generated/**`. The count stays within `sandbox.delivery.maxChangedFiles` or, unset, the `.ai/policies/task-budgets.yaml` figure for the session kind (`new-module` 30, `edit-module` 12, default 18); new packages within `maxNewDependencies` (0). Owners come from `.ai/policies/path-ownership.yaml`; with `crossOwnerChanges.requireReviewer` a cross-owner change asks for a reviewer from each owner in the body. A violation lists the offending paths and stops before anything is committed; the branch is deleted.
|
|
35
|
+
- Commit `sandbox: add|update <module id>` (author from git config) with the session id and the gate summary, `git push -u --force-with-lease <remote> <branch>`, `gh pr create --base <baseBranch> --head <branch> --title "Add|Update <module id>" --body-file <tmp>` (`--reviewer` from `git.reviewers`). A second delivery of the same session updates the branch and keeps the open PR.
|
|
36
|
+
- PR body, plain: two or three sentences from the brief and the last review handoff, `Session <id>.`, the gate table (gate, module, result), the file list grouped as added, modified, removed, `Post-merge: pnpm flowdular auth sync-scopes --module <id> --apply` per module, the reviewer note. No attribution footers, no dashes.
|
|
37
|
+
- `sync-scopes` does not run in the worktree: it is a runtime action against the deployment database, so it stays the post-merge step. Deploy, run it with `FD_AUTH_DATABASE` pointing at that database, verify the navigation entry appears for an owner.
|
|
38
|
+
- Configuration in `flowdular.json`, all optional and validated by `packages/contracts/schemas/project.schema.json`: `sandbox.delivery { default: 'workspace' | 'git-pr', targets: ['workspace', 'git-pr'], git: { remote: 'origin', baseBranch: 'main', branchPrefix: 'sandbox', provider: 'github' | 'none', mode: 'auto' | 'direct' | 'fork', forkOwner: null, reviewers: [] }, maxChangedFiles }`. Read at request time. `auto` never creates a fork: it uses direct delivery only after GitHub confirms push access and otherwise asks the operator to choose `direct` or `fork`. Only an explicit `fork` choice authorizes fork creation.
|
|
39
|
+
- The screen: "Into this workspace" / "As a pull request", offered only when both are usable here; an unusable target says why. The git plan shows branch, base, changed files against the budget, new packages, owners touched and the guardrail verdict; done shows the PR or compare link. `.flowdular/sandbox/sessions/<id>/delivery.json` keeps the branch and the URL.
|
|
40
|
+
|
|
41
|
+
## 3. Direct path from a working tree
|
|
42
|
+
|
|
43
|
+
```bash
|
|
44
|
+
pnpm flowdular module enable <id> --apply # new module only; also grants its scopes (result: scopes)
|
|
45
|
+
pnpm flowdular auth sync-scopes --module <id> --apply # re-grant after a new permission, or against another database
|
|
46
|
+
pnpm verify # typecheck, test, validate, format:check
|
|
47
|
+
pnpm build # cli build and smoke, module sync --apply, platform build
|
|
48
|
+
pnpm audit --prod --audit-level high # what CI runs (.github/workflows/ci.yml)
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
`pnpm validate` runs `spec validate --all`, `blueprint validate --all` (every `.ai/blueprints/*/blueprint.json` plus its companion files) and `module validate`. It checks manifests and schemas, not behaviour; typecheck and tests are the evidence.
|
|
52
|
+
|
|
53
|
+
Branch names: `feat/<module>-<topic>`, `fix/<module>-<topic>`, `core/<package>-<topic>`. Commit one logical change per commit; generated files travel with the command that produced them.
|
|
54
|
+
|
|
55
|
+
## 4. PR conventions (repository rules)
|
|
56
|
+
|
|
57
|
+
- Short body: what changed and why in a few sentences, gotchas, one line on verification (`pnpm verify passes; pnpm build passes`). No file tables, no design essays, no restating the diff.
|
|
58
|
+
- No AI attribution: no AI `Co-Authored-By` line and no `Generated with` footer.
|
|
59
|
+
- No em or en dashes anywhere in commits, PR titles or bodies.
|
|
60
|
+
- Generated files and `modules.enabled` change only through the CLI, and the PR says which command produced them.
|
|
61
|
+
- Changes to `packages/**` name the consumers that were migrated (`core-extend`).
|
|
62
|
+
|
|
63
|
+
## 4b. Pull request body template
|
|
64
|
+
|
|
65
|
+
```text
|
|
66
|
+
Adds inventory.core: tenant-scoped stock locations with read and manage scopes,
|
|
67
|
+
a list and create endpoint, a Locations screen with a drawer form, and a
|
|
68
|
+
dashboard KPI. Covers INVENTORY-LIST, INVENTORY-CREATE, INVENTORY-DENY,
|
|
69
|
+
INVENTORY-ISOLATION.
|
|
70
|
+
|
|
71
|
+
Generated by the CLI in this PR: flowdular.json and platform/package.json
|
|
72
|
+
(pnpm flowdular module enable inventory.core --apply), platform/src/generated/*
|
|
73
|
+
(module sync), pnpm-lock.yaml (pnpm install).
|
|
74
|
+
|
|
75
|
+
Gates: spec-schema, module-schema, dependencies, typecheck, tests (7), format
|
|
76
|
+
all passed in the sandbox eject; pnpm verify and pnpm build pass locally.
|
|
77
|
+
|
|
78
|
+
Post-merge: pnpm flowdular auth sync-scopes --module inventory.core --apply against
|
|
79
|
+
the deployment database.
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
## 4c. Pre-flight checklist
|
|
83
|
+
|
|
84
|
+
- `git status` shows only `modules/<dir>/**` plus the CLI-generated files named above.
|
|
85
|
+
- `module.json` `version`, `spec/module.yaml` `specVersion` and `package.json` `version` are equal.
|
|
86
|
+
- `spec/module.yaml` is `approved`; the PR does not change its status.
|
|
87
|
+
- No `console.log` left in module code; no secrets or tokens in tests.
|
|
88
|
+
- The PR title is under 70 characters and names the module (`inventory.core: stock locations`).
|
|
89
|
+
|
|
90
|
+
## 5. Container and tags
|
|
91
|
+
|
|
92
|
+
CI builds the image from `infra/docker/Dockerfile` on every PR (no push). A release tag `v*.*.*` is the trigger for publishing (workflow owned by the platform team). The image runs `node platform/dist/server/entry.js` with `/data` as the database volume. Compose and Kubernetes provide `FD_AUTH_DATABASE`, `FD_AGENTS_DATABASE`, and `FD_WORKFLOWS_DATABASE`. Production also requires `FD_AGENT_CREDENTIAL_KEY`, `FD_AGENT_RUN_GRANT_KEY`, `FD_WORKFLOWS_PAYLOAD_KEY`, and `FD_WORKFLOWS_CURSOR_KEY`; generate every key independently with `openssl rand -base64 32` and supply it through the deployment secret.
|
|
93
|
+
|
|
94
|
+
## Pitfalls
|
|
95
|
+
|
|
96
|
+
- An eject removes the files a session deleted; a rename shows up as one removal and one addition in the plan.
|
|
97
|
+
- `module enable` runs `pnpm install` when the package is not linked; a failing install is reported as `pnpm install failed while linking the module package`. A failed scope grant after a successful enable is `MODULE_SCOPES_SYNC_FAILED`; rerun `auth sync-scopes`.
|
|
98
|
+
- `platform/.generated/` is a stale ignore entry; the live generated directory is `platform/src/generated/`.
|
|
99
|
+
- `pnpm flowdular module sync --apply` is also run by `pnpm dev` and `pnpm build`; a dirty generated file after a checkout means the enabled list and the files disagree.
|
|
100
|
+
|
|
101
|
+
## Required auto-review
|
|
102
|
+
|
|
103
|
+
Before delivery, complete the separate `auto-review` phase. Sandbox eject requires
|
|
104
|
+
a current per-module review record and passing schema, dependency, typecheck,
|
|
105
|
+
test and format gates. Missing, skipped and empty-suite results block delivery.
|
|
106
|
+
Any module edit invalidates its review. Host changes also need the auto-review
|
|
107
|
+
report and full verification described by that skill before completion.
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: spec-approval
|
|
3
|
+
description: >-
|
|
4
|
+
Apply an explicit user approval to the exact current Flowdular module
|
|
5
|
+
specification. Use only when the user directly asks to approve one or more
|
|
6
|
+
named current specs, never to infer or initiate approval.
|
|
7
|
+
---
|
|
8
|
+
# Approve a module specification
|
|
9
|
+
|
|
10
|
+
Approval is a user decision that an agent may record only as a mechanical
|
|
11
|
+
delegate. Never decide that a specification is good enough, treat a review
|
|
12
|
+
verdict as approval, or infer approval from requests such as "continue", "looks
|
|
13
|
+
good", or "build it".
|
|
14
|
+
|
|
15
|
+
## 1. Required authority
|
|
16
|
+
|
|
17
|
+
Proceed only when the current user message explicitly approves:
|
|
18
|
+
|
|
19
|
+
- one named module;
|
|
20
|
+
- the clearly active module referred to as "this module"; or
|
|
21
|
+
- every current module in one named sandbox session.
|
|
22
|
+
|
|
23
|
+
The instruction must refer to the current specification. An approval copied from
|
|
24
|
+
an earlier conversation, a different hash, or an earlier session is not
|
|
25
|
+
authority for changed content.
|
|
26
|
+
|
|
27
|
+
If the target is ambiguous, ask which module. If the user approves several
|
|
28
|
+
modules, process and report each separately.
|
|
29
|
+
|
|
30
|
+
## 2. Review the exact input
|
|
31
|
+
|
|
32
|
+
Before recording approval:
|
|
33
|
+
|
|
34
|
+
1. Read the entire spec/module.yaml.
|
|
35
|
+
2. Confirm its module id and current specVersion.
|
|
36
|
+
3. Run pnpm flowdular spec validate --all --json.
|
|
37
|
+
4. Check the current diff or sandbox review for the requirements, permissions,
|
|
38
|
+
data ownership and acceptance scenarios being approved.
|
|
39
|
+
5. Stop if validation fails, the module cannot be resolved, or the spec changed
|
|
40
|
+
while it was being reviewed.
|
|
41
|
+
|
|
42
|
+
Do not rewrite requirements while applying approval. A requested content change
|
|
43
|
+
is a new spec-authoring step and needs approval after that edit.
|
|
44
|
+
|
|
45
|
+
## 3. Sandbox path
|
|
46
|
+
|
|
47
|
+
In the sandbox, use the operator approval action for the selected session module.
|
|
48
|
+
The live route is POST /sandbox/api/sessions/:id/approve, exposed by
|
|
49
|
+
approveSpecification in packages/sandbox/src/client/api.ts.
|
|
50
|
+
|
|
51
|
+
The route changes the status presentation and records the SHA-256 hash of the
|
|
52
|
+
exact approved text in the session. Do not patch the session workspace file to
|
|
53
|
+
bypass that route. Do not forge browser cookies or sandbox request headers. If
|
|
54
|
+
the operator route is unavailable, report the blocker and leave the spec
|
|
55
|
+
unapproved.
|
|
56
|
+
|
|
57
|
+
A multi-module session requires an approval record for every affected module.
|
|
58
|
+
Approving one module does not unblock another.
|
|
59
|
+
|
|
60
|
+
## 4. Repository checkout path
|
|
61
|
+
|
|
62
|
+
Outside the sandbox, after the explicit current user instruction:
|
|
63
|
+
|
|
64
|
+
1. Change only the top-level status value to approved.
|
|
65
|
+
2. Format the file without changing its requirements.
|
|
66
|
+
3. Run pnpm flowdular spec validate --all --json again.
|
|
67
|
+
4. Compute shasum -a 256 modules/<dir>/spec/module.yaml.
|
|
68
|
+
5. Report the module id, version and exact approved hash.
|
|
69
|
+
|
|
70
|
+
Do not combine approval with implementation changes in the same edit. Once the
|
|
71
|
+
approved state and hash are reported, implementation follows module-new or
|
|
72
|
+
module-update.
|
|
73
|
+
|
|
74
|
+
## 5. Staleness
|
|
75
|
+
|
|
76
|
+
Approval applies only to the exact content that was approved.
|
|
77
|
+
|
|
78
|
+
- In a sandbox session, the recorded hash is authoritative. Any later edit,
|
|
79
|
+
request for changes, or added module reopens the approval gate.
|
|
80
|
+
- In a checkout, any later requirement change must return the status to draft
|
|
81
|
+
or in-review before authoring continues, then receive a new explicit user
|
|
82
|
+
approval.
|
|
83
|
+
- A version bump alone is still a content change and needs fresh approval.
|
|
84
|
+
- Never copy an approved status line into another module or session.
|
|
85
|
+
|
|
86
|
+
## 6. Refuse
|
|
87
|
+
|
|
88
|
+
Refuse to approve when:
|
|
89
|
+
|
|
90
|
+
- no current user instruction explicitly grants approval;
|
|
91
|
+
- the user asked only for review, implementation or continuation;
|
|
92
|
+
- validation fails;
|
|
93
|
+
- unresolved business questions remain in the spec;
|
|
94
|
+
- the target module or session is ambiguous;
|
|
95
|
+
- the content changed after the user's decision;
|
|
96
|
+
- a sandbox role attempts to approve its own output.
|
|
97
|
+
|
|
98
|
+
A sandbox business manager may request approval in its handoff. That request is
|
|
99
|
+
not approval and cannot satisfy this skill's authority requirement.
|
|
100
|
+
|
|
101
|
+
## 7. Handoff
|
|
102
|
+
|
|
103
|
+
After approval, state exactly what was approved and which hash now represents
|
|
104
|
+
it. Do not claim that implementation or delivery also passed. Continue to
|
|
105
|
+
implementation only when the user's request includes it and the matching skill
|
|
106
|
+
allows it.
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: test-hardening
|
|
3
|
+
description: >-
|
|
4
|
+
Make a module test suite prove behaviour: where tests live and run, the route
|
|
5
|
+
recipe, the embedded PostgreSQL provider, the denial and isolation cases every
|
|
6
|
+
endpoint needs, and the break-the-implementation check.
|
|
7
|
+
---
|
|
8
|
+
# Harden a test suite
|
|
9
|
+
|
|
10
|
+
## 1. Where tests live and run
|
|
11
|
+
|
|
12
|
+
- `tests/module.test.ts` (one file per module today; more files are fine). `tsconfig.json` includes `src/**/*` and `tests/**/*.ts`, so a test file is `.ts`; `.tsrx` components are not compiled by vitest here. Testable client logic (formatting, filtering, mapping, state transitions) goes into a `.ts` helper next to the view and is imported by the test.
|
|
13
|
+
- Runner: `vitest run` (`pnpm --filter @flowdular/module-<dir> test`). In the sandbox the `tests` gate runs `vitest run --passWithNoTests` inside the module directory, so a module with no tests passes the gate. Treat an empty or trivial suite as a defect, not a pass.
|
|
14
|
+
- New modules use vitest 4.1.11, typescript 5.9.3 and `@types/node` 24.13.3. The catalog reference is an immutable older release; use the current scaffold dependency versions for new code.
|
|
15
|
+
|
|
16
|
+
## 2. Repositories on an embedded PostgreSQL
|
|
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.
|
|
19
|
+
|
|
20
|
+
## 3. Route recipe (from `modules/auth/tests/endpoints.test.ts`)
|
|
21
|
+
|
|
22
|
+
```ts
|
|
23
|
+
import { createContext } from '@octanejs/app-core';
|
|
24
|
+
import { createAuthenticationMiddleware } from '@flowdular/sdk/modules/auth/server';
|
|
25
|
+
// build an AuthRuntime around a DatabaseAuthRepository on a createPgliteTestProvider() lease and a cheap scrypt cost,
|
|
26
|
+
// sign up through the auth sign-up route to obtain a cookie and csrfToken, then:
|
|
27
|
+
const routes = createCatalogRoutes(auth, runtime);
|
|
28
|
+
const create = routes.find(
|
|
29
|
+
(route) =>
|
|
30
|
+
route.path === '/api/catalog/items' && route.methods.includes('POST'),
|
|
31
|
+
)!;
|
|
32
|
+
const response = await create.handler(
|
|
33
|
+
createContext(
|
|
34
|
+
new Request('https://erp.example/api/catalog/items', {
|
|
35
|
+
method: 'POST',
|
|
36
|
+
headers: {
|
|
37
|
+
'content-type': 'application/json',
|
|
38
|
+
origin: 'https://erp.example',
|
|
39
|
+
cookie,
|
|
40
|
+
'x-csrf-token': csrfToken,
|
|
41
|
+
},
|
|
42
|
+
body: JSON.stringify(input),
|
|
43
|
+
}),
|
|
44
|
+
{},
|
|
45
|
+
),
|
|
46
|
+
);
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
The auth middleware must have set the principal for `endpointIdentityFromContext` to find it: either run `auth.middleware(context, next)` before the handler or resolve the session and set `AUTH_PRINCIPAL_STATE_KEY` on `context.state` (both exported from `@flowdular/sdk/modules/auth/server`). Read `modules/auth/tests/endpoints.test.ts` for the runtime shape (`cookie`, `settings`, `service`, `middleware`).
|
|
50
|
+
|
|
51
|
+
## 4. Cases every endpoint needs
|
|
52
|
+
|
|
53
|
+
- 401 `UNAUTHENTICATED`: no cookie, no bearer token.
|
|
54
|
+
- 403 `FORBIDDEN`: a principal whose scopes lack the permission.
|
|
55
|
+
- 403 on a mutation without `x-csrf-token` (`CSRF_REJECTED`) and without `origin` (`ORIGIN_REQUIRED`).
|
|
56
|
+
- 400 with the stable code for each validation bound (`INVALID_INPUT`, module codes such as `INVALID_ITEM_KIND`).
|
|
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`.
|
|
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
|
+
|
|
61
|
+
Assert at the observation boundary: status code, `error.code`, returned record fields. Do not assert internal helper names, call order, or SQL text.
|
|
62
|
+
|
|
63
|
+
## 5. Break the implementation
|
|
64
|
+
|
|
65
|
+
A regression test that never failed proves nothing. For each new test: comment out the guard it protects (`if (denial) return denial;`, the `WHERE tenant_id = $1`, the `UNIQUE` constraint), run the suite, confirm the test fails, restore the code. Record in the handoff which tests were verified this way.
|
|
66
|
+
|
|
67
|
+
## 6. Flake sources here
|
|
68
|
+
|
|
69
|
+
`Date.now()` in `createdAt` (sort by `sku`, not by time); `randomUUID()` ids (never assert them); scrypt with the default cost is slow, so tests pass `passwordHash: { cost: 2 ** 12, ... }` as `modules/auth/tests/endpoints.test.ts` does; two tests sharing one provider see each other's rows unless the tables are truncated between them, so take a fresh fixture per test from the file's `tests/support/database.ts` helper.
|
|
70
|
+
|
|
71
|
+
## 7. Landing
|
|
72
|
+
|
|
73
|
+
Sandbox: the `tests` gate output appears in the chat after your turn. Repository root: `pnpm --filter @flowdular/module-<dir> test`, then `pnpm verify` before a PR.
|
|
74
|
+
|
|
75
|
+
## Pitfalls
|
|
76
|
+
|
|
77
|
+
- `expect(() => service.create(...)).toThrowError(/active tenant/)` pins a message; prefer the error `code` (`DUPLICATE_SKU`) when the class exposes one.
|
|
78
|
+
- A test that imports `@flowdular/sdk/ui` pulls fonts and CSS; keep client tests to `.ts` helpers.
|
|
79
|
+
- `vitest run` picks up `tests/**/*.test.ts`; a `.spec.ts` name also works but keep one convention.
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: translations-i18n
|
|
3
|
+
description: >-
|
|
4
|
+
Add or review Flowdular UI translations through the shared client runtime,
|
|
5
|
+
module bundles, locale-aware formatting, and validation gates.
|
|
6
|
+
---
|
|
7
|
+
# Translate Flowdular UI
|
|
8
|
+
|
|
9
|
+
Flowdular loads translations at runtime. The shell owns locale selection and the fallback chain; each module owns its copy.
|
|
10
|
+
|
|
11
|
+
## Runtime contract
|
|
12
|
+
|
|
13
|
+
- `packages/client/src/i18n` registers the shell bundle and every enabled module bundle. Resolution is active locale, then `en`, then the key itself so a missing key stays visible.
|
|
14
|
+
- A module contribution imports `translations/en.json` and every declared locale, then returns `translations: { en, pl }` with its `moduleId`.
|
|
15
|
+
- Use fully qualified keys with `t()`, for example `t('catalog.items.title')`. In `.tsrx`, import from `@flowdular/sdk/client`. In plain `.ts` helpers, import from `@flowdular/sdk/client/i18n` so tests do not pull the TSRX shell entry.
|
|
16
|
+
- Navigation and account-menu labels use getters. Contributions are created before their bundles are registered, so eager `label: t(...)` can paint a raw key.
|
|
17
|
+
- Locale-sensitive dates, numbers and currency use `activeLocale()` with `Intl.DateTimeFormat` or `Intl.NumberFormat`.
|
|
18
|
+
- The personal locale selector lives in Profile and applies immediately. The tenant default remains an Administration setting and is the fallback when the browser has no personal choice.
|
|
19
|
+
|
|
20
|
+
## Module workflow
|
|
21
|
+
|
|
22
|
+
1. Keep the same locale list in `spec/module.yaml`, `module.json` and `flowdular.json`. Every module ships `en`.
|
|
23
|
+
2. Put all user-facing labels, hints, empty states, errors and accessible names in `translations/<locale>.json`. Keep flat, module-local keys such as `items.form.save`; the runtime adds the module namespace.
|
|
24
|
+
3. Add the same key to every locale in the same change. Write natural copy in each language.
|
|
25
|
+
4. Import the bundles in `src/client/contribution.tsrx` and expose them through `translations`.
|
|
26
|
+
5. Replace literals with `t('<module>.<key>')`. Dynamic families such as `t('expenses.status.' + status)` require every possible suffix in every bundle.
|
|
27
|
+
6. For a new locale-sensitive helper, add a test that changes the active locale and proves both the text and formatting.
|
|
28
|
+
|
|
29
|
+
Navigation pattern:
|
|
30
|
+
|
|
31
|
+
```ts
|
|
32
|
+
import { t, type ModuleClientContribution } from '@flowdular/sdk/client';
|
|
33
|
+
import translationsEn from '../../translations/en.json';
|
|
34
|
+
import translationsPl from '../../translations/pl.json';
|
|
35
|
+
|
|
36
|
+
return {
|
|
37
|
+
moduleId: 'inventory.core',
|
|
38
|
+
translations: { en: translationsEn, pl: translationsPl },
|
|
39
|
+
navigation: [
|
|
40
|
+
{
|
|
41
|
+
get label() {
|
|
42
|
+
return t('inventory.navigation.label');
|
|
43
|
+
},
|
|
44
|
+
get description() {
|
|
45
|
+
return t('inventory.navigation.description');
|
|
46
|
+
},
|
|
47
|
+
// remaining contribution fields
|
|
48
|
+
},
|
|
49
|
+
],
|
|
50
|
+
};
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
## Validation
|
|
54
|
+
|
|
55
|
+
Run:
|
|
56
|
+
|
|
57
|
+
```bash
|
|
58
|
+
pnpm flowdular module validate --module <module-id>
|
|
59
|
+
pnpm --filter @flowdular/module-<dir> typecheck
|
|
60
|
+
pnpm --filter @flowdular/module-<dir> test
|
|
61
|
+
pnpm format:check
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
`module validate` rejects a missing locale file, mismatched locale key sets, and a static `t('module.key')` whose module bundle does not contain the key. A dynamic key cannot be proven statically, so test its complete value set.
|
|
65
|
+
|
|
66
|
+
When a raw key appears in the UI, check in this order:
|
|
67
|
+
|
|
68
|
+
1. The key exists in `translations/en.json` and the active locale.
|
|
69
|
+
2. The contribution exposes the bundle under the correct `moduleId` namespace.
|
|
70
|
+
3. Navigation copy is lazy through getters.
|
|
71
|
+
4. The running dev server has rebuilt after the contribution changed.
|
|
72
|
+
|
|
73
|
+
## Do not
|
|
74
|
+
|
|
75
|
+
- Add a module-local translation runtime or import JSON directly in each view.
|
|
76
|
+
- Leave English fallbacks in client API helpers. Use a translated fallback and preserve server messages when present.
|
|
77
|
+
- Translate identifiers, provider names, currency codes, shortcuts or stable error codes.
|
|
78
|
+
- Hide a missing key with an empty string.
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: ux-design
|
|
3
|
+
description: >-
|
|
4
|
+
Design a module screen on the shared design system, with the record-screen
|
|
5
|
+
recipe, the five states, the component and class inventory, and the icon keys.
|
|
6
|
+
---
|
|
7
|
+
# Design a screen
|
|
8
|
+
|
|
9
|
+
`docs/design-system.md` (in a session: `reference/design-system.md`) is the only source of visual decisions. Primitives live in `packages/ui` (`reference/packages/ui/components/*.tsrx` and `components.css`). The reference screen is `.ai/references/catalog/src/client/CatalogView.tsrx` with `CatalogItemForm.tsrx`.
|
|
10
|
+
|
|
11
|
+
## 1. Rules (design-system.md, section Rules)
|
|
12
|
+
|
|
13
|
+
1. Primitives first: a `ui-*` class or an exported component before any new visual code.
|
|
14
|
+
2. Colors, fonts, sizes, radii and shadows only from tokens (`var(--...)`); no hex in module CSS.
|
|
15
|
+
3. Never restyle or override a `ui-*` class outside `packages/ui`.
|
|
16
|
+
4. A missing primitive becomes a module-local component on tokens, flagged as a promotion candidate for `packages/ui`.
|
|
17
|
+
5. Blue is action and selection; green, amber and red are state; copper is the brand only.
|
|
18
|
+
6. Minimum text size 12 px; labels `--text-xs` uppercase; numbers tabular (`.num`, `ui-kpi__value`).
|
|
19
|
+
7. Containment is owned by the primitives: children of `ui-view`, `ui-two-col`, `ui-grid-2`, `ui-kpi-grid` shrink, long words wrap, wide content scrolls inside `ui-table-wrap`.
|
|
20
|
+
8. `Kpi` is a stat tile: the value is a number or a short state word; identifiers, addresses and paths go into `note` or a `ui-mono` line.
|
|
21
|
+
9. Records own the page; creating and editing happens in a `Drawer`. Never split the width between a table and a form.
|
|
22
|
+
|
|
23
|
+
## 2. Record screen recipe
|
|
24
|
+
|
|
25
|
+
```text
|
|
26
|
+
div.ui-view
|
|
27
|
+
PageHeader eyebrow title description actions: Button sm [Icon refresh 14] Refresh, Button sm primary [Icon plus 14] New ...
|
|
28
|
+
Alert only when error && !formOpen
|
|
29
|
+
TableCard title count head is one line: title with its count left, SearchField and Filters right
|
|
30
|
+
search SearchField value placeholder label onInput
|
|
31
|
+
filters Filters open onToggle activeCount; the controls live inside the dropdown
|
|
32
|
+
columns rows rowKey columns is a module-level readonly TableColumn<Row>[] outside the component
|
|
33
|
+
status 'loading' | 'idle' 'loading' only while status === 'loading' && rows.length === 0
|
|
34
|
+
empty emptyFiltered filtered filtered picks which of the two the table renders
|
|
35
|
+
actions actionsLabel visible compact buttons; undefined when the scope is missing
|
|
36
|
+
note one constraint worth stating
|
|
37
|
+
Drawer open title subtitle onClose form keyed by 'form-' + formSession
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
`TableCard` is the record card and `Table` is the only table in the product: never hand-roll `table.ui-table` again, and never rebuild the head, the loading row or the empty state that these already own. `actions(row)` returns `TableAction[]`; the component renders visible compact buttons in its narrow trailing column. Do not build a dropdown or module-owned action markup. Fixed column widths apply through loading, empty and populated states. A cell returns nodes: `span.ui-cell` (`<b>` primary, `<small>` secondary), `ui-mono` for an identifier, `Tag` for state, `numeric: true` on the column for tabular figures.
|
|
41
|
+
|
|
42
|
+
The shared `Table` is backed by the official `@octanejs/tanstack-table` adapter. A module never imports TanStack directly. It supplies the Flowdular columns, rows and actions above, while `@flowdular/sdk/ui` owns the features, row model, header model and cell rendering.
|
|
43
|
+
|
|
44
|
+
Every column declares `width`. Primary identity and descriptions get the largest share, dates and identifiers a medium share, and counts or status the smallest. For a table with actions, data widths normally add up to about 90 percent because the shared action column is 160 px. Without actions they add up to 100 percent. Do not leave all columns unspecified: equal distribution wastes space and weakens the hierarchy.
|
|
45
|
+
|
|
46
|
+
Drawer form: `form.ui-drawer__form > div.ui-drawer__body > div.ui-form > div.ui-form__row > FormField label required help` wrapping a native `input.ui-input`, `select.ui-select` or `textarea.ui-textarea`; `Alert` inside the body for the submit error; `div.ui-drawer__foot` with `<small>` for the constraint and `div.ui-form__actions` (Cancel, primary submit with `disabled={busy}` and a progressive label `Creating…`). `Drawer width="lg"` when rows have two columns or an editor.
|
|
47
|
+
|
|
48
|
+
Read-only master-detail (runs, playground) keeps `ui-two-col` (+ `--wide-aside`). Admin overviews use `ui-kpi-grid` with several `Kpi`; a module dashboard widget is one `Kpi` with `href` and `linkLabel`, rendered by the shell in `dashboard.metrics`.
|
|
49
|
+
|
|
50
|
+
## 3. Five states
|
|
51
|
+
|
|
52
|
+
- Loading: `Table status="loading"` while `status === 'loading' && rows.length === 0`, so a refresh never blanks rows the user is reading.
|
|
53
|
+
- Empty: `empty` with an icon and a sentence that names the first action; `emptyFiltered` says no match and is chosen by `filtered`.
|
|
54
|
+
- Error: `Alert` (tone `danger` default) under the header, or inside the drawer while the form is open.
|
|
55
|
+
- Populated: the `Table` rows, or a list where records are not tabular.
|
|
56
|
+
- Denied: the shell already hides navigation and widgets whose `scope` the principal lacks. Inside a view, pass booleans derived from `ModuleClientContext.scopes` (`canManage={options.scopes.includes(X_PERMISSIONS.manage)}` in `contribution.tsrx`, as `modules/agents` does) and do not render the action. A 403 from the server still becomes an `Alert`; it is never a crash.
|
|
57
|
+
|
|
58
|
+
## 4. Component and prop inventory (`packages/ui/src/components`)
|
|
59
|
+
|
|
60
|
+
- `Button`: `variant` primary, secondary (default), ghost, danger; `size` sm, md, lg; `type` button, submit; `block`; `disabled`; `onClick`.
|
|
61
|
+
- `FormField`: `label`, `required`, `help`, `error`; one control child with `ui-input`, `ui-select` or `ui-textarea`.
|
|
62
|
+
- `SearchField`: `value`, `placeholder`, `label` (accessible name), `onInput(value)`.
|
|
63
|
+
- `Table`: `columns: TableColumn<Row>[]` (`key`, `header`, required `width`, `cell(row)`, `numeric`), `rows`, `rowKey(row)`, `status`, `loadingLabel`, `empty`, `emptyFiltered`, `filtered`, `actions(row): TableAction[]`, `actionsLabel`, optional stable `actionsWidth` (160 px default, 280 px for two actions), `onSelect(row)`, `selectedKey`, `caption`.
|
|
64
|
+
- `TableCard`: every `Table` prop plus `title`, `count`, `head`, `search`, `filters`, `before`, `after`, `note`, `noteIcon`.
|
|
65
|
+
- `Filters`: `open`, `onToggle`, `activeCount`, `label`; children are the filter controls, which belong in the dropdown and nowhere else.
|
|
66
|
+
- `CheckGrid`: `groups: { label, options: { value, label, hint? }[] }[]`, `value: string[]`, `mono`, `disabled`, `onChange(next)`.
|
|
67
|
+
- `Drawer`: `open`, `title`, `subtitle`, `width` md or lg, `onClose`; child is `ui-drawer__form` or `ui-drawer__body`. Escape and the scrim close it.
|
|
68
|
+
- `Tag`: `tone` neutral, success, warning, danger, info, ink; `dot`; `mono`.
|
|
69
|
+
- `Kpi`: `label`, `value` (string), `unit`, `badge`, `note`, `href`, `linkLabel`.
|
|
70
|
+
- `PageHeader`: `eyebrow`, `title`, `description`; children are the right-side actions.
|
|
71
|
+
- `EmptyState`: `icon`, `title`, `code`, children as the sentence.
|
|
72
|
+
- `Alert`: `tone` danger (default), warning, info.
|
|
73
|
+
- `Avatar`: `name`, `square` (organizations), `large`.
|
|
74
|
+
- `Icon`: `name`, `size` (18 default, 16 in controls, 14 in `Button size="sm"`), `strokeWidth`.
|
|
75
|
+
- `BrandMark`: `size`, `signature`, `tone`; brand moments only.
|
|
76
|
+
|
|
77
|
+
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`, `chevrons-up-down`, `plus`, `panel-left`, `check`, `filter`, `download`, `more`, `external`, `alert`, `x`, `sign-out`, `refresh`, `help`, `key`, `settings`, `braces`. An unknown name renders `modules` silently, so check the list.
|
|
78
|
+
|
|
79
|
+
## 5. Classes a module writes by hand (`packages/ui/src/styles/components.css`)
|
|
80
|
+
|
|
81
|
+
Layout `ui-view`, `ui-two-col` (+`--wide-aside`), `ui-grid-2`, `ui-kpi-grid`, `ui-tag-cloud`, `ui-section-head` (h2 plus actions inside a view), `ui-toolbar` (+`__spacer`). Surfaces `ui-card` (+`__head`, `__title`, `__body`). Data `ui-table` (+`ui-table-wrap`, `ui-table__empty`, `ui-table__state` for a dot plus label, `.num`), `ui-cell` (+`ui-cell__muted`), `ui-mono`, `ui-code`, `ui-dot` (+`--muted`). Row action classes are component-owned and are never written by a module. Forms `ui-form` (+`__row`, `__row--4`, `__foot`, `__actions`), `ui-input` (+`--error`), `ui-select`, `ui-textarea` (+`--error`), `ui-checkbox`, `ui-label`, `ui-help` (+`--error`). Drawer `ui-drawer__form`, `ui-drawer__body`, `ui-drawer__foot`. Bits `ui-kbd`, `ui-note`, `ui-menu` (+`__label`, `__item`, `__item--active`, `__item--danger`, `__sep`), `ui-btn ui-btn--icon` for an icon-only button. Classes rendered by components (`ui-drawer__panel`, `ui-search`, `ui-page-head*`, `ui-field`, `ui-empty*`, `ui-alert*`, `ui-tag*`, `ui-kpi__*`, `ui-checks*`, `ui-avatar*`) are not written by hand.
|
|
82
|
+
|
|
83
|
+
## 6. Copy
|
|
84
|
+
|
|
85
|
+
User-facing copy lives in every declared `translations/*.json` bundle and is read with fully qualified `t()` keys. Eyebrow names the domain, title names the records, and description is one sentence. Table headers say what the value is. Buttons start with a verb. Loading text ends with `…`. Drawer footer states the constraint the user cannot see. Write natural copy in each locale, with no exclamation marks or database jargon.
|
|
86
|
+
|
|
87
|
+
## Pitfalls
|
|
88
|
+
|
|
89
|
+
- `Kpi value={items.length}` does not typecheck; use `String(items.length)`.
|
|
90
|
+
- A `Tag` for a lifecycle state uses `success` for active and `neutral` for archived, with `dot`.
|
|
91
|
+
- An `Icon` inside `Button size="sm"` is 14, not 18.
|
|
92
|
+
- A new component file per screen, form, table, or stateful region; a page composes them.
|
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: variables
|
|
3
|
+
description: >-
|
|
4
|
+
Build variable-aware fields and templates on the {{ }} contract, the scope
|
|
5
|
+
mask, and server-side resolution, with agents.core as the worked example.
|
|
6
|
+
---
|
|
7
|
+
# Variables (templating and linked fields)
|
|
8
|
+
|
|
9
|
+
A variable field lets a stored value embed `{{ key }}` tokens that are filled at
|
|
10
|
+
run time from context or another module's data. The contract is pure and lives
|
|
11
|
+
in `@flowdular/sdk/contracts` (`packages/contracts/src/variables.ts`); the fields are
|
|
12
|
+
presentational primitives in `@flowdular/sdk/ui`; resolution happens on the server
|
|
13
|
+
before the consumer sees the text. `agents.core` is the worked example: an agent
|
|
14
|
+
author writes instructions as a template and the run snapshot carries the
|
|
15
|
+
resolved text.
|
|
16
|
+
|
|
17
|
+
## The `{{ }}` contract
|
|
18
|
+
|
|
19
|
+
`VariableDefinition { key, label, kind, scope?, sample?, description? }` is one
|
|
20
|
+
offerable variable. `kind` is `text | number | date | money | identifier`.
|
|
21
|
+
`key` is a dot path whose segments start lowercase and may continue in
|
|
22
|
+
camelCase (`context.user.displayName`), matched by `VARIABLE_KEY_PATTERN` /
|
|
23
|
+
`isVariableKey`.
|
|
24
|
+
|
|
25
|
+
- `extractVariables(template)`: the distinct trimmed `{{ key }}` tokens, in
|
|
26
|
+
first-seen order.
|
|
27
|
+
- `validateTemplate(template, available, allowedScopes?)`: `{ unknown, forbidden }`.
|
|
28
|
+
`unknown` are tokens not in `available`; `forbidden` are tokens whose def
|
|
29
|
+
declares a `scope` not present in `allowedScopes` (omit `allowedScopes` to skip
|
|
30
|
+
the scope check).
|
|
31
|
+
- `resolveTemplate(template, values, { onMissing })`: substitutes each
|
|
32
|
+
`{{ key }}` with `values[key]`. `onMissing` is `keep` (default, leave the token
|
|
33
|
+
verbatim) or `blank`.
|
|
34
|
+
- `tokenizeTemplate(template)`: the segments the UI overlay highlights; the
|
|
35
|
+
`text` fields concatenate back to the exact input.
|
|
36
|
+
|
|
37
|
+
Rules the resolver guarantees: tokens are `{{ key }}` with optional inner spaces;
|
|
38
|
+
`\{{` outputs a literal `{{`; substitution is a single pass, so a value that
|
|
39
|
+
itself looks like a token is emitted verbatim (never recursive); only own,
|
|
40
|
+
string keys resolve, so prototype keys (`__proto__`, `toString`) never resolve.
|
|
41
|
+
No eval, no expressions, only key substitution.
|
|
42
|
+
|
|
43
|
+
## The scope mask
|
|
44
|
+
|
|
45
|
+
`VariableDefinition.scope` is the permission required to read the source. A
|
|
46
|
+
variable is offered and resolved only when the principal holds that scope:
|
|
47
|
+
|
|
48
|
+
- The UI fields never fetch and never check scopes. The caller passes
|
|
49
|
+
`variables` already filtered to what this principal may use, so a variable the
|
|
50
|
+
principal cannot read is simply absent from the menu and highlights as an error
|
|
51
|
+
pill if typed.
|
|
52
|
+
- On the server, filter the definition list by the principal's scopes before
|
|
53
|
+
building `values`, or call `validateTemplate(..., allowedScopes)` and refuse a
|
|
54
|
+
template whose `forbidden` is non-empty. A scope-less variable
|
|
55
|
+
(`context.*`) is always allowed.
|
|
56
|
+
|
|
57
|
+
## The UI fields (`@flowdular/sdk/ui`)
|
|
58
|
+
|
|
59
|
+
`VariableTextarea` (multiline), `VariableInput` (single line), and
|
|
60
|
+
`VariableSelect` (one literal option or one variable token) are presentational.
|
|
61
|
+
All take `value`, `onInput`, `variables: readonly VariableDefinition[]`, optional
|
|
62
|
+
`sampleValues?: Record<string,string>`, required translated `label` (accessible
|
|
63
|
+
name), `name` (so a `FormData` submit still captures it), `required`, `disabled`,
|
|
64
|
+
and `error`. Input and textarea also require translated `insertLabel`,
|
|
65
|
+
`variablesLabel`, and `emptyLabel`; the shared primitive has no English copy to
|
|
66
|
+
fall back to. Select takes `options: readonly VariableSelectLiteralOption[]` and
|
|
67
|
+
requires translated `literalGroupLabel` and `variablesGroupLabel`. A caller also
|
|
68
|
+
localizes every `VariableDefinition.label` before passing the definitions, since
|
|
69
|
+
that label is visible in the picker.
|
|
70
|
+
|
|
71
|
+
- The `braces` affordance (a `{}` icon in `ICON_PATHS`) opens a menu of the
|
|
72
|
+
available variables with label, key, and current or sample value; picking one
|
|
73
|
+
inserts `{{ key }}` at the caret. Typing `{{` opens the same menu filtered by
|
|
74
|
+
what follows; ArrowUp/Down and Enter pick, Escape closes.
|
|
75
|
+
- Tokens are highlighted by an overlay layer (`tokenizeTemplate`) sitting behind
|
|
76
|
+
a transparent control, so `{{ key }}` reads as a pill while the real value
|
|
77
|
+
stays plain text; an unknown or forbidden token gets the error pill. All
|
|
78
|
+
color comes from tokens; measurement is client-only in an effect, so SSR is
|
|
79
|
+
safe. See `packages/ui/src/components/VariableField.tsrx` and its wrappers.
|
|
80
|
+
- `VariableSelect` stays a native `<select>`. Literal values and allowed
|
|
81
|
+
`{{ key }}` tokens are real `<option>` values, so keyboard navigation,
|
|
82
|
+
validation, disabled state, accessible naming, and `FormData` submission keep
|
|
83
|
+
browser semantics. Samples appear only in option labels. The component never
|
|
84
|
+
resolves the selected token.
|
|
85
|
+
|
|
86
|
+
Keep the fields presentational: the caller supplies `variables` and
|
|
87
|
+
`sampleValues`, the component never fetches.
|
|
88
|
+
|
|
89
|
+
The platform variable registry (`@flowdular/sdk/kernel`) registers definitions and
|
|
90
|
+
their execution-time resolvers. Reach the shared instance with
|
|
91
|
+
`platformVariableRegistry(context.capabilities)`. `list(scopes)` requires an
|
|
92
|
+
explicit permission snapshot and is the only
|
|
93
|
+
definition list a server sends to a field. `resolve(template, request)` takes a
|
|
94
|
+
trusted tenant id, actor, immutable permission snapshot, `AbortSignal`, explicit
|
|
95
|
+
record bindings and optional values owned by the consumer. It validates the
|
|
96
|
+
whole template before invoking a source. An unknown token, missing scope,
|
|
97
|
+
missing binding, aborted request, unavailable record, or source failure is a
|
|
98
|
+
refusal. Source exceptions are replaced with a generic error so SQL, provider,
|
|
99
|
+
and record details do not cross the module boundary.
|
|
100
|
+
|
|
101
|
+
The source declares `requiredBindings` per variable. For example, `party.name`
|
|
102
|
+
requires `partyId`; the resolver receives that id explicitly and asks the
|
|
103
|
+
parties public capability or read tool under `request.tenantId`. It never infers
|
|
104
|
+
a record from browser state and never reads the parties database. Local form
|
|
105
|
+
values go in `request.values`, while cross-module values must come from the
|
|
106
|
+
registered resolver. The resolved text is returned to the server consumer only;
|
|
107
|
+
the raw template remains stored.
|
|
108
|
+
|
|
109
|
+
## Server-side resolution rule
|
|
110
|
+
|
|
111
|
+
Resolve the template before the consumer sees it, and keep the raw template
|
|
112
|
+
stored. The stored record keeps the `{{ }}` template; the run or send snapshot
|
|
113
|
+
carries the resolved text. Never resolve in the client and never store the
|
|
114
|
+
resolved text back onto the definition.
|
|
115
|
+
|
|
116
|
+
Worked example in `agents.core`:
|
|
117
|
+
|
|
118
|
+
- `modules/agents/src/domain/context-variables.ts` declares
|
|
119
|
+
`AGENT_CONTEXT_VARIABLES` (`context.tenantName`, `context.today`,
|
|
120
|
+
`context.user.displayName`, `context.user.email`, all scope-less) and
|
|
121
|
+
`agentContextValues(input)` that builds their values from the run's
|
|
122
|
+
tenant/principal/date.
|
|
123
|
+
- `AgentService.enqueueRun` (`services/agent-service.ts`) resolves
|
|
124
|
+
`agent.instructions` with `resolveTemplate` against those values before it
|
|
125
|
+
builds the instruction snapshot; the stored definition is untouched. The
|
|
126
|
+
endpoint (`api/endpoints.ts`) supplies the tenant name and principal from the
|
|
127
|
+
request; `today` comes from the queue timestamp.
|
|
128
|
+
- `AgentDefinitionForm.tsrx` feeds `VariableTextarea` the context variable list
|
|
129
|
+
and sample values, so an author gets the menu and highlighting.
|
|
130
|
+
|
|
131
|
+
## Adding a variable source via a capability or tool
|
|
132
|
+
|
|
133
|
+
Business-data variables (for example `{{ party.name }}`) resolve through a
|
|
134
|
+
public capability or an agent read tool owned by the source module:
|
|
135
|
+
|
|
136
|
+
1. Declare the `VariableDefinition` with `scope` equal to the source tool's
|
|
137
|
+
`requiredPermissions` (`AgentTool` in `packages/harness/src/runtime.ts`). The
|
|
138
|
+
tool's permission is the mask: one source of truth, no parallel table.
|
|
139
|
+
2. Register the source on the shared registry during module composition. Declare
|
|
140
|
+
the record id in `requiredBindings`; do not accept a tenant binding.
|
|
141
|
+
3. Offer it only through `registry.list(principal.scopes)`. The UI fields take
|
|
142
|
+
this already-filtered list and never fetch.
|
|
143
|
+
4. Call `registry.resolve` on the server with the trusted tenant, actor,
|
|
144
|
+
permission snapshot, signal, and explicit bindings. The source invokes only
|
|
145
|
+
its owning public capability or read tool and maps the bounded result to
|
|
146
|
+
strings. The registry refuses before the source runs if the scope or binding
|
|
147
|
+
is absent.
|
|
148
|
+
|
|
149
|
+
`automations.core` is the live cross-module example. `agent.name` requires the
|
|
150
|
+
schedule's explicit `agentId`, and its resolver calls the `agents.run-queue`
|
|
151
|
+
capability with the active tenant. A binding containing an agent from another
|
|
152
|
+
tenant resolves to no record and the run is refused. The schedule keeps the raw
|
|
153
|
+
template; only the queued run receives the resolved input.
|
|
154
|
+
|
|
155
|
+
Register a new tool with the `agent-tool-design` skill; this skill covers only
|
|
156
|
+
how its output becomes a resolvable variable.
|