create-flowdular 0.2.4 → 0.2.6
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 +40 -2
- 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 +2 -2
- 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/pnpm-workspace.yaml +1 -0
package/README.md
CHANGED
|
@@ -48,6 +48,17 @@ npx create-flowdular@latest my-app
|
|
|
48
48
|
|
|
49
49
|
The generator installs dependencies and initializes Git by default. Local development uses PGlite, an embedded PostgreSQL implementation, so the default setup needs no external database server.
|
|
50
50
|
|
|
51
|
+
## Ready for coding agents
|
|
52
|
+
|
|
53
|
+
Every app includes `.ai` rules, skills, role prompts, blueprints, policies and
|
|
54
|
+
reference examples, plus `AGENTS.md`, `CLAUDE.md`, `.agents/skills` and
|
|
55
|
+
`.claude/skills`. These files are bundled with the generator and are available
|
|
56
|
+
with `--no-install`. Personal agent settings and credentials are never copied.
|
|
57
|
+
|
|
58
|
+
Edit `.ai/rules` or `.ai/skills`, then run `pnpm rules:generate`. `pnpm verify`
|
|
59
|
+
checks that the generated instructions are in sync. The instructions explain
|
|
60
|
+
where to find the installed SDK and how to extend the application's modules.
|
|
61
|
+
|
|
51
62
|
## Options
|
|
52
63
|
|
|
53
64
|
Pass generator flags after `--` when using `npm create`:
|
|
@@ -0,0 +1,203 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: agent-tool-design
|
|
3
|
+
description: >-
|
|
4
|
+
Register an API or CLI tool that lets business agents act on a module, with
|
|
5
|
+
the real harness, permission, idempotency, audit, and test contract.
|
|
6
|
+
---
|
|
7
|
+
# Design and register an agent tool
|
|
8
|
+
|
|
9
|
+
A module lets agents act on it by registering tools during composition. A tool
|
|
10
|
+
wraps one service operation, takes the tenant from the run context, reuses the
|
|
11
|
+
service validation, bounds its output, and declares the exact permission the
|
|
12
|
+
matching endpoint requires. `parties.core` and `catalog.core` are the reference
|
|
13
|
+
implementations.
|
|
14
|
+
|
|
15
|
+
This skill designs tools, not business-agent behavior. When a module should
|
|
16
|
+
also ship a ready business agent through `defineAgent()`, read
|
|
17
|
+
`business-agent-design` and register only the exact tools that agent needs.
|
|
18
|
+
|
|
19
|
+
## 1. The contract in code
|
|
20
|
+
|
|
21
|
+
- Composition: `PlatformServerContext` (`modules/auth/src/server/composition.ts`) carries `agentTools: PlatformToolRegistry` (`register(tools)`, `list()`; `packages/kernel/src/tool-registry.ts`; a duplicate tool id throws at boot), `settings: ModuleSettingsRuntime`, and `capabilities: PlatformCapabilityRegistry` (`register(id, service)`, `get(id)`, `has(id)`; `packages/kernel/src/capability-registry.ts`). `platform/octane.config.ts` creates the registries, passes them to every module's `createServerComposition`, declares each `settings`, owns each `dispose`, then calls each `start`.
|
|
22
|
+
- Ordering is a non-issue: `agents.core` (`modules/agents/src/platform.ts`) passes `tools: () => context.agentTools.list()` into `createAgentRuntime`, and the harness is built lazily in `start()`, which runs after every module has composed. Tools any module registers during its own compose are therefore visible, whatever the module order.
|
|
23
|
+
- Helpers: import `defineApiAgentTool` from `@flowdular/sdk/harness/tool-adapters` and the types `AgentTool`, `AgentToolContext` from `@flowdular/sdk/harness/runtime`. Both subpaths are free of the Vercel AI SDK; only the harness root (`@flowdular/sdk/harness`) and `@flowdular/sdk/modules/agents/server` pull it. `defineApiAgentTool` returns a frozen `AgentTool { id, transport: 'api', target, description, requiredPermissions, inputSchema?, execute }`. `defineCliAgentTool({ id, capability: { id, risk }, ... })` wraps a CLI capability and throws at definition time for `external` or `destructive` risk.
|
|
24
|
+
- Skills inside `agents.core` are tenant database records behind `agents.skills.*`, appended to agent instructions. They are unrelated to `.ai/skills/**`, which are files for coding agents.
|
|
25
|
+
- A read tool's output can also become a resolvable `{{ variable }}` for variable-aware fields: the tool's `requiredPermissions` is the variable's scope mask. Register a source on `platformVariableRegistry(context.capabilities)`, require an explicit record binding, and invoke the tool with the trusted tenant, actor permission snapshot, and signal. See the `variables` skill for the complete refusal contract.
|
|
26
|
+
- ADR 0002 (`docs/adr/0002-durable-agent-execution.md`): instructions are data, tools are registered by the composition, each tool records an endpoint id or a CLI capability id plus its required permissions, runs are durable with leases.
|
|
27
|
+
|
|
28
|
+
## 2. Tool shape
|
|
29
|
+
|
|
30
|
+
```ts
|
|
31
|
+
// src/agent/tools.ts
|
|
32
|
+
import { defineApiAgentTool } from '@flowdular/sdk/harness/tool-adapters';
|
|
33
|
+
import type { AgentTool } from '@flowdular/sdk/harness/runtime';
|
|
34
|
+
import { PARTY_PERMISSIONS } from '../acl/permissions.ts';
|
|
35
|
+
import type { PartyKind } from '../domain/types.ts';
|
|
36
|
+
import type { PartiesRuntime } from '../server/runtime.ts';
|
|
37
|
+
|
|
38
|
+
const MAX_TOOL_ROWS = 200;
|
|
39
|
+
|
|
40
|
+
export function partiesAgentTools(
|
|
41
|
+
runtime: PartiesRuntime,
|
|
42
|
+
): readonly AgentTool[] {
|
|
43
|
+
return [
|
|
44
|
+
defineApiAgentTool({
|
|
45
|
+
id: 'parties.customer.list', // ^[a-z][a-z0-9-]*(\.[a-z][a-z0-9-]*)+$
|
|
46
|
+
endpointId: 'parties.records.list', // the read endpoint this wraps
|
|
47
|
+
description: 'List customers and suppliers of the active tenant.',
|
|
48
|
+
requiredPermissions: [PARTY_PERMISSIONS.read],
|
|
49
|
+
inputSchema: {
|
|
50
|
+
type: 'object',
|
|
51
|
+
additionalProperties: false,
|
|
52
|
+
properties: {
|
|
53
|
+
status: { type: 'string', enum: ['active', 'archived'] },
|
|
54
|
+
query: { type: 'string', maxLength: 120 },
|
|
55
|
+
},
|
|
56
|
+
},
|
|
57
|
+
execute: async (input, context) => {
|
|
58
|
+
const value = (input ?? {}) as Record<string, unknown>;
|
|
59
|
+
const query =
|
|
60
|
+
typeof value.query === 'string'
|
|
61
|
+
? value.query.trim().toLocaleLowerCase('en-US')
|
|
62
|
+
: '';
|
|
63
|
+
return runtime
|
|
64
|
+
.service()
|
|
65
|
+
.list(context.tenantId) // tenant from the run, never from input
|
|
66
|
+
.filter(
|
|
67
|
+
(party) =>
|
|
68
|
+
query === '' ||
|
|
69
|
+
party.name.toLocaleLowerCase('en-US').includes(query),
|
|
70
|
+
)
|
|
71
|
+
.slice(0, MAX_TOOL_ROWS); // bound output
|
|
72
|
+
},
|
|
73
|
+
}),
|
|
74
|
+
defineApiAgentTool({
|
|
75
|
+
id: 'parties.customer.create',
|
|
76
|
+
endpointId: 'parties.records.create',
|
|
77
|
+
description: 'Create a customer or supplier owned by the active tenant.',
|
|
78
|
+
requiredPermissions: [PARTY_PERMISSIONS.manage],
|
|
79
|
+
inputSchema: {
|
|
80
|
+
type: 'object',
|
|
81
|
+
additionalProperties: false,
|
|
82
|
+
required: ['name', 'kind'],
|
|
83
|
+
properties: {
|
|
84
|
+
name: { type: 'string', maxLength: 160 },
|
|
85
|
+
kind: { type: 'string', enum: ['customer', 'supplier', 'both'] },
|
|
86
|
+
vatId: { type: 'string', maxLength: 20 },
|
|
87
|
+
},
|
|
88
|
+
},
|
|
89
|
+
execute: async (input, context) => {
|
|
90
|
+
const value = (input ?? {}) as Record<string, unknown>;
|
|
91
|
+
// The service revalidates every field, so a tool cannot persist
|
|
92
|
+
// what the endpoint would reject.
|
|
93
|
+
return runtime.service().create(context.tenantId, {
|
|
94
|
+
name: String(value.name ?? ''),
|
|
95
|
+
kind: value.kind as PartyKind,
|
|
96
|
+
vatId: typeof value.vatId === 'string' ? value.vatId : null,
|
|
97
|
+
});
|
|
98
|
+
},
|
|
99
|
+
}),
|
|
100
|
+
];
|
|
101
|
+
}
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
```ts
|
|
105
|
+
// src/platform.ts, inside createServerComposition, before the return.
|
|
106
|
+
// register takes readonly unknown[], so no cast is needed.
|
|
107
|
+
context.agentTools.register(partiesAgentTools(runtime));
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
Export the factory from `src/server/index.ts` so tests and the composition reach it.
|
|
111
|
+
|
|
112
|
+
Dependencies: `package.json` gets `"@flowdular/sdk/harness": "workspace:*"`. You do not import `@flowdular/sdk/modules/agents` and you do not add `agents.core` to `module.json`: registration flows through the platform-provided registry on the composition context, not an import of agents.core. Adding a scenario bumps `spec/module.yaml` `specVersion` and `module.json` `version` together.
|
|
113
|
+
|
|
114
|
+
## 3. Execution model you design against (`packages/harness/src/runtime.ts`, `AgentHarness.execute`)
|
|
115
|
+
|
|
116
|
+
- A tool is offered only when the agent definition lists it in `allowedTools`, the run's explicit `toolGrants` include it, every `requiredPermissions` entry is in the enqueue-time permission ceiling, and the initiating user still holds every permission when the tool is called. The auth runtime reauthorizes the trusted actor and tenant before every call. A stored snapshot never becomes future authority, newly granted scopes do not elevate an old run, and a service actor stays tool-less until its owning module supplies an explicit revocable policy. Otherwise the harness emits `tool.denied` and throws a stable refusal. `assertToolsRegistered` rejects a new run whose agent names an unregistered tool, while an exact idempotent retry returns its already persisted run before consulting mutable definitions or registries.
|
|
117
|
+
- `invokeTool` validates `input` against `inputSchema` first, using a small JSON Schema subset (`type`, `enum`, `required`, `properties`, `additionalProperties: false`, `items`; `packages/harness/src/tool-contract.ts` `validateToolInput`). A violation emits `tool.denied` (reason `TOOL_INPUT_INVALID`) before `execute` runs. The subset does not check string length or format, so the tool enforces those by passing input through the module service.
|
|
118
|
+
- `execute(input, { runId, tenantId, requestedBy, actor, idempotencyKey, permissions, signal })`. Take the tenant from `context.tenantId`. `permissions` is the intersection of the original ceiling and live authorization. `actor` describes the agent run for record history. `additionalProperties: false` already makes the harness refuse a stray `tenantId` field, but never read one anyway.
|
|
119
|
+
- A mutating tool with `idempotency: 'required'` is executable only after the target module implements a durable ledger and the definition declares `idempotencyProtection: 'target-ledger'`. The harness derives a stable key from the durable run id and deterministic tool-call ordinal. The target ledger binds `(tenant, tool id, key)` to a canonical input hash and the first result. A replay returns that result without another mutation; the same key with another tool or input fails closed. Provider tool-call ids are audit metadata only. Never add the declaration before the target migration, repository transaction, and crash-recovery test exist.
|
|
120
|
+
- Each call has a deadline (`tool.timeoutMs`, default 30 s, range 250 to 600000) and the harness caps serialized output at 32 KB (`boundToolOutput`), marking `truncated`. Still page or limit your rows so one call cannot dominate the run window.
|
|
121
|
+
- Events per call land in the run's persisted audit chain: `tool.started`, then `tool.completed` (metadata `tool`, `outputCharacters`, `truncated`) on success, `tool.failed` (reason) on error, or `tool.denied` (reason) when not granted or input-invalid.
|
|
122
|
+
- Runs are enqueued by `POST /api/agent-runs` behind `agents.runs.execute`, claimed by `AgentWorker` with a lease, observed through `GET /api/agent-runs` and the SSE stream. The registered tool ids surface in `GET /api/agents` `tools`, which the Agents form reads to build the allowed-tools grid. Never make a tool block on user input.
|
|
123
|
+
|
|
124
|
+
## 4. Deliverables for a module
|
|
125
|
+
|
|
126
|
+
1. Spec: one acceptance scenario per tool group (`PARTIES-AGENT-TOOL`): endpoint wrapped, permission required, validated input, what it refuses (no tenant input; a `manage` tool needs an explicit scenario; bounded output). Bump `specVersion` and `module.json` `version` together.
|
|
127
|
+
2. `src/agent/tools.ts`: every tool calls the module service through the runtime (never a repository, database handle, filesystem or shell), takes `context.tenantId`, and reuses the service validation.
|
|
128
|
+
3. The `register` line in `src/platform.ts`, and the factory exported from `src/server/index.ts`.
|
|
129
|
+
4. For a mutating tool, a numbered migration and target-side idempotency ledger, with the migration mirrored byte for byte in `src/services/migration.ts`. The service commits the business mutation and ledger result in one transaction.
|
|
130
|
+
5. Tests, below, including a replay of the complete harness execution with the same run id and no second business row.
|
|
131
|
+
6. In the Agents screen an agent definition lists the tool id in its allowed tools, the request carries it in `toolGrants`, and the initiating principal still holds the permission. Without all three the tool stays invisible to the model.
|
|
132
|
+
|
|
133
|
+
## 4b. Worked example
|
|
134
|
+
|
|
135
|
+
Spec scenario:
|
|
136
|
+
|
|
137
|
+
```yaml
|
|
138
|
+
acceptanceScenarios:
|
|
139
|
+
- id: PARTIES-AGENT-TOOL
|
|
140
|
+
given: An agent run holds parties.records.manage in its permission snapshot and the tool parties.customer.create in its grants.
|
|
141
|
+
when: The agent invokes the tool with a valid party, and a run without the manage scope invokes the same tool.
|
|
142
|
+
then: The scoped run creates a tenant-owned party from the run tenant and the harness denies the unscoped run before any write.
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
Module-local test (`tests/agent-tools.test.ts`) drives `execute` directly. It does
|
|
146
|
+
not assert on `context.permissions`: RBAC is the harness's job, not the tool's.
|
|
147
|
+
|
|
148
|
+
```ts
|
|
149
|
+
import type { AgentToolContext } from '@flowdular/sdk/harness/runtime';
|
|
150
|
+
import { describe, expect, it } from 'vitest';
|
|
151
|
+
import { partiesAgentTools } from '../src/agent/tools.ts';
|
|
152
|
+
import { createPartiesRuntime } from '../src/server/runtime.ts';
|
|
153
|
+
|
|
154
|
+
const context = (tenantId: string): AgentToolContext => ({
|
|
155
|
+
runId: 'run-1',
|
|
156
|
+
tenantId,
|
|
157
|
+
requestedBy: 'account-1',
|
|
158
|
+
permissions: new Set<string>(),
|
|
159
|
+
signal: new AbortController().signal,
|
|
160
|
+
});
|
|
161
|
+
|
|
162
|
+
it('creates under the run tenant and ignores a tenant in the input', async () => {
|
|
163
|
+
const runtime = createPartiesRuntime({ databasePath: ':memory:' });
|
|
164
|
+
const [, create] = partiesAgentTools(runtime);
|
|
165
|
+
await create!.execute(
|
|
166
|
+
{ name: 'Acme', kind: 'customer', tenantId: 'tenant-b' },
|
|
167
|
+
context('tenant-a'),
|
|
168
|
+
);
|
|
169
|
+
expect(runtime.service().list('tenant-a')).toHaveLength(1);
|
|
170
|
+
expect(runtime.service().list('tenant-b')).toHaveLength(0);
|
|
171
|
+
});
|
|
172
|
+
|
|
173
|
+
it('reuses the service validation so a tool cannot bypass the endpoint', async () => {
|
|
174
|
+
const [, create] = partiesAgentTools(
|
|
175
|
+
createPartiesRuntime({ databasePath: ':memory:' }),
|
|
176
|
+
);
|
|
177
|
+
await expect(
|
|
178
|
+
create!.execute(
|
|
179
|
+
{ name: 'Acme', kind: 'customer', vatId: 'PL-123' },
|
|
180
|
+
context('tenant-a'),
|
|
181
|
+
),
|
|
182
|
+
).rejects.toMatchObject({ code: 'INVALID_VAT_ID' });
|
|
183
|
+
});
|
|
184
|
+
```
|
|
185
|
+
|
|
186
|
+
Prove RBAC through the harness (in `modules/agents/tests` with a fake provider,
|
|
187
|
+
where the create tool runs against a `:memory:` repository): a run holding the
|
|
188
|
+
`manage` scope creates the row and emits `tool.started`/`tool.completed`; a run
|
|
189
|
+
whose snapshot lacks it emits `tool.denied` (`TOOL_NOT_GRANTED`) and writes
|
|
190
|
+
nothing.
|
|
191
|
+
|
|
192
|
+
## 5. Settings a tool may depend on
|
|
193
|
+
|
|
194
|
+
Declare `settings: defineModuleSettings({...})` (from `@flowdular/sdk/kernel`) by returning it from the composition, keep a reference to `PlatformServerContext.settings` in the tool factory, and read it per call as `settings.get<number>(context.tenantId, '<module>.core', 'key')` at request time, never at boot. Declared settings render in the module's drawer under Administration, Modules automatically.
|
|
195
|
+
|
|
196
|
+
## Pitfalls
|
|
197
|
+
|
|
198
|
+
- A tool id equal to an endpoint id is a convention, not a requirement; keep them parallel for traceability. A read-by-id tool with no dedicated endpoint reuses the read endpoint id under the same permission.
|
|
199
|
+
- `requiredPermissions` must be exactly the endpoint's permission; a weaker list lets a run bypass the endpoint's ACL because the tool calls the service directly.
|
|
200
|
+
- Register once per composition; a duplicate id throws in the registry at boot and the platform does not start.
|
|
201
|
+
- Import the helpers from `@flowdular/sdk/harness/tool-adapters` and `@flowdular/sdk/harness/runtime`; never import the harness root or `@flowdular/sdk/modules/agents` from `src/index.ts` or the client, which would pull the Vercel AI SDK into the client bundle.
|
|
202
|
+
- The harness validates only the schema subset; deep validation is the service's job. Pass input through the service so a tool cannot persist what the endpoint would reject.
|
|
203
|
+
- Playground runs use the tenant's readiness-probed provider; the local simulation provider performs no network call and is the only provider in a fresh install.
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: auth-security-review
|
|
3
|
+
description: >-
|
|
4
|
+
Review a module or platform change for authorization, tenancy, CSRF, input
|
|
5
|
+
bounds, secrets, and destructive CLI use, with the exact checks and tests the
|
|
6
|
+
platform relies on.
|
|
7
|
+
---
|
|
8
|
+
# Authentication and security review
|
|
9
|
+
|
|
10
|
+
## 1. Threat surface of a module endpoint
|
|
11
|
+
|
|
12
|
+
Check every route in `src/api/endpoints.ts` against `.ai/references/catalog/src/api/endpoints.ts`:
|
|
13
|
+
|
|
14
|
+
1. `defineEndpoint` (`packages/server/src/endpoint.ts`) with `access: { kind: 'permission', permission }` and `resolveIdentity: endpointIdentityFromContext`. `access: { kind: 'public' }` is allowed only with a written reason (health, sign-in). A raw `new ServerRoute` from `@octanejs/app-core` bypasses all of this; outside `modules/auth` and `packages/server` it is a finding.
|
|
15
|
+
2. Non-GET handlers call `sessionMutationDenial(octane, auth)` before any work. Order inside it (`modules/auth/src/server/session-security.ts`): API token principal (403 `TOKEN_MUTATION_DENIED`), `assertSameOrigin` (`sec-fetch-site`, then `origin`, then `referer`; 403 `CROSS_ORIGIN_REQUEST` or `ORIGIN_REQUIRED`), session cookie (401), `x-csrf-token` compared with `timingSafeEqual` (403 `CSRF_REJECTED`).
|
|
16
|
+
3. Body through `readJsonObject` (415 without `application/json`, 413 above 16 KB) and `requiredString`, `optionalString`, `requiredInteger` with `min` and `max`. A handler that calls `request.json()` itself has no size limit.
|
|
17
|
+
4. Tenant id only from `principalFromContext(octane)!.tenantId`. A `tenantId` read from the body, query or headers is a blocker (`.ai/examples/bad/tenant-from-body`).
|
|
18
|
+
5. Repository: every query on a tenant-owned table has `WHERE tenant_id = ?`; parameters are bound, never interpolated; unique constraints start with `tenant_id`.
|
|
19
|
+
6. Errors return `{ error: { code, message } }` with stable codes and safe messages; no stack, path, or SQL text reaches the client.
|
|
20
|
+
|
|
21
|
+
## 2. Scope model
|
|
22
|
+
|
|
23
|
+
`modules/auth/src/acl/scopes.ts`: `AUTH_SCOPES`, `PLATFORM_SCOPES` (`system.workspace.access` gates the shell in `platform/src/App.tsrx`; `system.settings.read` and `system.settings.manage` guard `GET /api/settings` and `POST /api/settings/update` in `modules/auth/src/server/settings-endpoints.ts`), `BUNDLED_MODULE_SCOPES`, `OWNER_SCOPES` (all of them), `MEMBER_SCOPES` (read scopes plus `agents.runs.execute`). Sign-up creates an owner with `OWNER_SCOPES`; member creation copies `OWNER_SCOPES` or `MEMBER_SCOPES` by role (`modules/auth/src/services/auth-service.ts`). A module's scopes reach existing owners through `pnpm flowdular module enable <id> --apply` (which runs the grant) or `pnpm flowdular auth sync-scopes --module <id> --apply` for a re-grant. Navigation in the `Development` group is owner-only in the client (`packages/client/src/shell/navigation.ts`); the server permission stays authoritative.
|
|
24
|
+
|
|
25
|
+
Review question: does every new scope appear in the spec `permissions`, in `src/acl/permissions.ts`, on the endpoint, and on the client contribution that exposes it?
|
|
26
|
+
|
|
27
|
+
### Unified audit read surface
|
|
28
|
+
|
|
29
|
+
Three tenant-scoped trails are readable over HTTP, each a GET behind a read scope with the tenant taken from the principal: `GET /api/auth/audit` (`auth.audit.read`), `GET /api/agent-audit` (`agents.runs.read`), and `GET /api/sandbox/audit` (`sandbox.sessions.read`). They are surfaced together in `auth.core`'s Administration > Audit view, whose source selector is derived from `ModuleClientContext.scopes` so a reader is never offered a source it cannot read. The agent and sandbox trails are hash-chained; `GET /api/agent-audit/verify` and `GET /api/sandbox/audit/verify` (same read scopes) recompute the chain and return `{ verified, brokenAt }` through the same repository walk the `flowdular <module> audit-verify` CLI uses, so CLI and endpoint cannot drift. Reviewing an audit change: the read scope guards both list and verify, the list cursor is `(occurred_at, sequence)` and the sequence is trusted from storage (never from input), and no chain field or metadata may carry a credential, token, or request body.
|
|
30
|
+
|
|
31
|
+
## 3. API tokens
|
|
32
|
+
|
|
33
|
+
`Authorization: Bearer clat_...` (`API_TOKEN_PREFIX` in `auth-service.ts`), 256-bit random, stored as SHA-256, scopes intersected with the live membership, max lifetime one year, resolved only when no session cookie is present (`modules/auth/src/middleware/authentication.ts`). Tokens are refused for session-guarded mutations. A module endpoint that should be callable by a token (read for the sandbox bridge) must be a GET behind a permission.
|
|
34
|
+
|
|
35
|
+
## 4. Secrets
|
|
36
|
+
|
|
37
|
+
Passwords: scrypt `N=2^17, r=8, p=1`, 64-byte key (`modules/auth/src/services/password.ts`). Sessions: 32 random bytes, CSRF 24 bytes, SHA-256 at rest. Cookies: `HttpOnly; SameSite=Strict; Path=/`, `Secure` and the `__Host-` prefix when `FD_AUTH_SECURE_COOKIE` is true (default in production), 12 hour TTL (`modules/auth/src/server/runtime.ts`). Provider credentials: AES-256-GCM in `modules/agents/src/services/credential-vault.ts`, key from `FD_AGENT_CREDENTIAL_KEY` (required in production). `redactSecrets` in `packages/ai-provider/src/errors.ts` scrubs provider messages; there is no general redacting logger, and a raw driver error can carry the failing statement, so a handler maps it to a Flowdular error code and logs that instead of `console.error(..., error)` with the driver message. A module never logs a principal, a token, or a request body.
|
|
38
|
+
|
|
39
|
+
## 5. Greps to run
|
|
40
|
+
|
|
41
|
+
```bash
|
|
42
|
+
grep -rn "new ServerRoute" modules/*/src | grep -v modules/auth # raw routes outside auth
|
|
43
|
+
grep -rn "tenantId" modules/*/src/api | grep -v principalFromContext # tenant from input
|
|
44
|
+
grep -rn "request.json()" modules/*/src # unbounded body reads
|
|
45
|
+
grep -rn "console\.\(log\|error\)" modules/*/src # logging of principals or bodies
|
|
46
|
+
grep -rn "kind: 'public'" modules/*/src # public endpoints need a reason
|
|
47
|
+
grep -rn "\${" modules/*/src/services/database-repository.ts # interpolation into SQL
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
## 6. Destructive and external CLI capabilities
|
|
51
|
+
|
|
52
|
+
`packages/cli/src/runner.ts`: `external` risk and non-local `destructive` capabilities fail with `APPROVAL_VERIFIER_REQUIRED`; `localOnly` runs only when `FD_ENV` or `NODE_ENV` is `development` or `test` (unset counts as development); `requiresApprovedSpec` needs `--spec` pointing at an approved spec; `destructive` with `--apply` needs `--confirm <token>` equal to the descriptor's `confirmation`. `setup quick` is `auth greenfield` (`--apply --confirm reset-local-auth`) and resets `.flowdular/data/auth.db`. Never point it at `FD_AUTH_DATABASE` of a deployment.
|
|
53
|
+
|
|
54
|
+
## 7. Required tests per endpoint
|
|
55
|
+
|
|
56
|
+
Recipe in `modules/auth/tests/endpoints.test.ts`: build the runtime with a `DatabaseAuthRepository` on a `createPgliteTestProvider()` lease, call `route.handler(createContext(new Request(...), {}))`.
|
|
57
|
+
|
|
58
|
+
- 401 without a cookie or token.
|
|
59
|
+
- 403 with a principal that lacks the permission.
|
|
60
|
+
- Cross-tenant read returns an empty list (service level, on the suite's test provider under the non-bypass `coreloom_runtime` role).
|
|
61
|
+
- Mutation without `x-csrf-token` returns 403 `CSRF_REJECTED`; without `origin` returns 403.
|
|
62
|
+
- Each validation bound returns 400 with its code.
|
|
63
|
+
|
|
64
|
+
## 7b. Compliance table and report
|
|
65
|
+
|
|
66
|
+
Fill one row per endpoint before writing findings; a blank cell is a finding.
|
|
67
|
+
|
|
68
|
+
| Endpoint | Permission | Identity | Tenant source | Mutation guard | Body bounds | Tests |
|
|
69
|
+
| ------------------------------- | ---------------------------- | ----------------------------- | ---------------------- | ----------------------------- | ----------------------------------------- | ------------------- |
|
|
70
|
+
| `POST /api/inventory/locations` | `inventory.locations.manage` | `endpointIdentityFromContext` | `principalFromContext` | `sessionMutationDenial` first | `readJsonObject`, `requiredString` max 32 | 401, 403, CSRF, 400 |
|
|
71
|
+
|
|
72
|
+
Report each finding as: severity (`blocker`, `should-fix`, `taste`), claim, `file:line`, the concrete scenario (who sends what, what happens), the rule (`AGENTS.md` number), the fix. Finish with a verdict: `approved` or `changes-required`. Do not fix code in the review run.
|
|
73
|
+
|
|
74
|
+
```text
|
|
75
|
+
blocker Tenant id read from body modules/inventory/src/api/endpoints.ts:41
|
|
76
|
+
A member of tenant A posts { tenantId: "B" } and creates a location in B.
|
|
77
|
+
AGENTS.md 6. Fix: principalFromContext(octane)!.tenantId; drop the field.
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
## 8. Known platform gaps to keep in mind (not module defects)
|
|
81
|
+
|
|
82
|
+
The sandbox server (`packages/sandbox/src/server/routes.ts`) has routes without authorization or CSRF, and the session id parameter is joined into paths unvalidated; the sign-in limiter is keyed on email plus `user-agent` (`modules/auth/src/server/endpoints.ts`, `limiterKey`); `users.members.manage` can create an owner because `role` comes from the body (`modules/users/src/api/endpoints.ts`); `emailConfirmation` does not gate sign-in; there are no security headers or a global body limit. A module review does not fix these; name them when a change touches the same area.
|
|
83
|
+
|
|
84
|
+
## Pitfalls
|
|
85
|
+
|
|
86
|
+
- `principalFromContext(octane)!` before `resolveIdentity` ran is a null dereference on a public route.
|
|
87
|
+
- A GET that mutates skips `sessionMutationDenial`; mutations are POST or PUT.
|
|
88
|
+
- A `secret: true` setting is write-only through `/api/settings/update`; a module never returns a setting value to the client unless its declaration says `client: true`.
|
|
89
|
+
- Matching a unique violation by message text is the accepted pattern, but the string must include the table name (`<table>.tenant_id`).
|
|
90
|
+
- An `Alert` with the raw server message is fine because the server already returns safe messages; never include the response body of a 500 verbatim.
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: auto-review
|
|
3
|
+
description: >-
|
|
4
|
+
Review a finished core or module change against its requirements, public
|
|
5
|
+
contracts and executable regression evidence before delivery. Report defects
|
|
6
|
+
with failure scenarios; never approve an unverified change.
|
|
7
|
+
---
|
|
8
|
+
# Auto-review
|
|
9
|
+
|
|
10
|
+
This is a separate review phase after implementation. Read the owning code, the
|
|
11
|
+
complete change including additions and deletions, its callers, requirements and
|
|
12
|
+
tests. Preserve unrelated edits. Do not load other skills during this phase.
|
|
13
|
+
Do not modify production code, tests, specifications or generated files. Findings
|
|
14
|
+
return to the implementation phase; every later edit requires another review.
|
|
15
|
+
Review is a model assessment, not a guarantee of correctness or spec approval.
|
|
16
|
+
|
|
17
|
+
## Required checks
|
|
18
|
+
|
|
19
|
+
For every check, cite the relevant files and concrete evidence. Use a reasoned
|
|
20
|
+
not-applicable explanation only after inspecting the change. Generic "looks good"
|
|
21
|
+
or "tests pass" is not evidence. A missing check blocks a passing verdict.
|
|
22
|
+
|
|
23
|
+
1. **Correctness:** map each requested behavior and acceptance scenario to code
|
|
24
|
+
and an observable test. Trace invalid inputs, empty results, boundaries and
|
|
25
|
+
failure paths. Check existing behavior outside the requested change. Find
|
|
26
|
+
sibling call sites and copies that need the same fix.
|
|
27
|
+
2. **Security:** trusted principal and tenant identity, explicit permissions,
|
|
28
|
+
denial tests, CSRF for mutations, input bounds, parameterized SQL, tenant
|
|
29
|
+
predicates and forced RLS, secret redaction and cross-module authority.
|
|
30
|
+
Test unauthenticated, denied and cross-tenant requests where applicable.
|
|
31
|
+
3. **Compatibility:** exports, signatures, optional fields, errors, dependency
|
|
32
|
+
direction and every consumer affected by the contract. Check manifests,
|
|
33
|
+
declared dependencies, spec/package versions and CLI-generated composition.
|
|
34
|
+
Applied migrations must remain byte-identical; check new migrations, fresh
|
|
35
|
+
apply, adoption and tenant isolation through the actual database provider.
|
|
36
|
+
4. **Lifecycle:** concurrency, cancellation, cleanup, bounded memory, resource
|
|
37
|
+
ownership, durable background work, retries, idempotency and recovery. State
|
|
38
|
+
the cost of changed loops or queries; investigate new unbounded work.
|
|
39
|
+
5. **Tests:** assertions must observe behavior, not duplicate implementation.
|
|
40
|
+
A regression test must fail with the defect restored and pass with the fix.
|
|
41
|
+
Cover relevant negative and failure cases. No skipped assertions, empty
|
|
42
|
+
suites, mocked-away ownership boundary or weakened existing checks. Record
|
|
43
|
+
actual commands, results and failures, and distinguish tests not yet run.
|
|
44
|
+
6. **UI:** when rendered behavior changes, inspect the rendered result, keyboard
|
|
45
|
+
interaction, shared components, translated copy and loading/empty/error/
|
|
46
|
+
populated/denied states. Record the inspected scenario; otherwise explain
|
|
47
|
+
why the diff has no rendered effect.
|
|
48
|
+
|
|
49
|
+
## Core and host module changes
|
|
50
|
+
|
|
51
|
+
Review only the requested change while accounting for existing workspace edits.
|
|
52
|
+
Run scoped checks first, then `pnpm verify`. For a core change also run `pnpm build`
|
|
53
|
+
(the CLI smoke and platform build exercise integration). Do not waive a failing,
|
|
54
|
+
missing or skipped required check; list unrelated existing failures separately
|
|
55
|
+
and leave verification incomplete. Re-read the final diff after generated output
|
|
56
|
+
or formatting changes. Save a concise report with files reviewed, scenario-to-test
|
|
57
|
+
mapping, commands/results, unresolved findings and remaining risks. Finish with
|
|
58
|
+
pass only when all applicable checks have evidence and no actionable defect
|
|
59
|
+
remains. Root instructions require this phase before declaring the work complete;
|
|
60
|
+
there is no host-side runtime enforcement of a model's review verdict.
|
|
61
|
+
|
|
62
|
+
## Sandbox
|
|
63
|
+
|
|
64
|
+
The orchestrator routes a failed `auto-review` gate to this skill. The turn is
|
|
65
|
+
read-only and retains the current role and active module. Inspect the whole module
|
|
66
|
+
change relative to `reference/auto-review-base/`, including deleted files, not
|
|
67
|
+
just your last edit. Preserve the intended next-specialist handoff from the
|
|
68
|
+
implementation turn after a passing review. Use the provided
|
|
69
|
+
reference code and recorded gate output. The orchestrator runs schema, dependency,
|
|
70
|
+
typecheck, tests and format gates; never claim you ran a command it ran later.
|
|
71
|
+
Report verification still pending when needed. Deterministic checks are independent
|
|
72
|
+
of your assessment and must all pass before eject.
|
|
73
|
+
|
|
74
|
+
Return exactly one fenced `auto-review` JSON object in the closing response,
|
|
75
|
+
followed by the normal handoff line. Each check is a string of 20 to 4000 characters
|
|
76
|
+
with actual evidence or a specific not-applicable explanation. Keep the response
|
|
77
|
+
under 32000 characters. The structure is:
|
|
78
|
+
|
|
79
|
+
```auto-review
|
|
80
|
+
{
|
|
81
|
+
"verdict": "fail",
|
|
82
|
+
"checks": {
|
|
83
|
+
"correctness": "Files, acceptance scenarios and observed behavior.",
|
|
84
|
+
"security": "Relevant authorization and tenant denial evidence.",
|
|
85
|
+
"compatibility": "Public consumers and migration or manifest evidence.",
|
|
86
|
+
"lifecycle": "Resource ownership and failure-path evidence.",
|
|
87
|
+
"tests": "Test paths, assertions, actual gate results or pending checks.",
|
|
88
|
+
"ui": "Rendered inspection evidence or a specific reason not applicable."
|
|
89
|
+
},
|
|
90
|
+
"findings": ["Severity; file:line; input/state; wrong outcome; required fix."]
|
|
91
|
+
}
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
Use `pass` and an empty findings array only when no actionable defect remains.
|
|
95
|
+
Do not copy the example evidence. Include every actionable finding, not taste
|
|
96
|
+
preferences. A failing review returns to implementation; never repair files in
|
|
97
|
+
this turn. A passing record is written by the orchestrator outside the workspace
|
|
98
|
+
and bound to all module file bytes. Subsequent edits invalidate it. Old sessions
|
|
99
|
+
without a current report must run auto-review before eject. Eject rejects skipped
|
|
100
|
+
and missing gates as well as failures; a test suite with no tests fails.
|
|
101
|
+
|
|
102
|
+
Owning code: `packages/sandbox/src/server/auto-review.ts`, `turns.ts`, `gates.ts`,
|
|
103
|
+
`delivery/steps.ts`, and `packages/coding-agent/src/roles/skills.ts`.
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: bug-hunt
|
|
3
|
+
description: >-
|
|
4
|
+
Reproduce a defect with the gate runner, map the symptom to the owning layer,
|
|
5
|
+
fix it there with a failing test first, then hunt its siblings.
|
|
6
|
+
---
|
|
7
|
+
# Hunt a bug
|
|
8
|
+
|
|
9
|
+
## 1. Reproduce with the tools the gates use
|
|
10
|
+
|
|
11
|
+
- Server: a vitest case in `tests/module.test.ts` that builds the route and calls it directly. Recipe: `const routes = createXRoutes(auth, runtime); const route = routes.find(...)`, then `await route.handler(createContext(new Request('https://erp.example/api/x', { method: 'POST', headers: { 'content-type': 'application/json', origin: 'https://erp.example', cookie, 'x-csrf-token': csrf }, body }), {}))`. `createContext` comes from `@octanejs/app-core`; a full example with sign-up, cookie and CSRF is `modules/auth/tests/endpoints.test.ts`.
|
|
12
|
+
- Service or repository: `new XService((await createXTestDatabase()).repository)` from the module's `tests/support/database.ts` and await the method.
|
|
13
|
+
- Client logic: move the pure part into a `.ts` helper and test it; `.tsrx` files are outside `tests/**/*.ts`.
|
|
14
|
+
- Run: `pnpm --filter @flowdular/module-<dir> test` (repository root) or ask for the `tests` gate (sandbox). Keep the failing test; it becomes the regression test.
|
|
15
|
+
|
|
16
|
+
## 2. Symptom to layer
|
|
17
|
+
|
|
18
|
+
| Symptom | Where it is decided |
|
|
19
|
+
| ---------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
20
|
+
| 401 `UNAUTHENTICATED` | `packages/server/src/endpoint.ts` (no identity from `resolveIdentity`) or `modules/auth/src/server/session-security.ts` (no session cookie on a mutation) |
|
|
21
|
+
| 403 `FORBIDDEN` | `endpoint.ts`: the permission string is not in the principal's scopes. Check `src/acl/permissions.ts` against the spec and whether `auth sync-scopes` ran |
|
|
22
|
+
| 403 `TOKEN_MUTATION_DENIED`, `CSRF_REJECTED`, `ORIGIN_REJECTED`, `CROSS_ORIGIN_REQUEST`, `ORIGIN_REQUIRED` | `sessionMutationDenial` order: API token, `assertSameOrigin` (`modules/auth/src/api/origin.ts`), cookie session, `x-csrf-token` |
|
|
23
|
+
| 415 `CONTENT_TYPE_REQUIRED`, 413 `PAYLOAD_TOO_LARGE`, 400 `INVALID_JSON` or `INVALID_INPUT` | `packages/server/src/http.ts` `readJsonObject` (16 KB cap) and `requiredString`, `requiredInteger`, `optionalString` |
|
|
24
|
+
| 409 on create | the repository maps the unique violation by SQLSTATE `23505` plus the constraint name (`.ai/references/catalog/src/services/database-repository.ts`, `create`); match the code, never the driver message text, and remember that renaming the unique index breaks the constraint check silently |
|
|
25
|
+
| 500 `INTERNAL_ERROR` | the handler threw; `endpoint.ts` logs `[requestId] endpoint <id> failed` with the error |
|
|
26
|
+
| 404 on a module route | route not mounted: `module.json` `platform.server`, `./platform` export, `src/platform.ts`, `flowdular.json` `modules.enabled`, `platform/src/generated/modules.server.ts` regenerated by `pnpm flowdular module sync --apply` |
|
|
27
|
+
| Blank shell or boot error | `packages/client/src/contributions.ts` throws on a duplicate contribution id, a navigation entry whose `viewId` has no view, or an unknown widget slot |
|
|
28
|
+
| Navigation entry missing | scope not granted (`navigationForIdentity` in `packages/client/src/shell/navigation.ts`), or `Development` group for a non-owner |
|
|
29
|
+
| View falls back to the dashboard | `ApplicationShell.tsrx` renders `overview` for a view id that no visible navigation or account menu entry reaches |
|
|
30
|
+
| Icon renders as a grid | `glyph` or `Icon name` is not an `ICON_PATHS` key (`packages/ui/src/icons/Icon.tsrx` falls back to `modules`) |
|
|
31
|
+
| Stale data after a change | each component owns a store instance (`useMemo(() => createXClientState(), [])`); check the `store.act` that should have written it. `store.commits(cb)` and `store.stats()` from `segment-state` show what was committed |
|
|
32
|
+
| Schema error on start | `runModuleMigrations`: `CHECKSUM_MISMATCH` means applied SQL bytes changed; `PARTIAL_OBJECTS` means only part of a pending migration exists. Never delete or bypass the database to hide either condition; restore the shipped bytes or diagnose the partial schema |
|
|
33
|
+
|
|
34
|
+
## 2b. Reproduction snippets
|
|
35
|
+
|
|
36
|
+
Service level, no HTTP:
|
|
37
|
+
|
|
38
|
+
```ts
|
|
39
|
+
import { describe, expect, it } from 'vitest';
|
|
40
|
+
import { CatalogService } from '../src/services/catalog-service.ts';
|
|
41
|
+
import { createCatalogTestDatabase } from './support/database.ts';
|
|
42
|
+
|
|
43
|
+
it('rejects a sku above 64 characters with a stable code', async () => {
|
|
44
|
+
const service = new CatalogService(
|
|
45
|
+
(await createCatalogTestDatabase()).repository,
|
|
46
|
+
);
|
|
47
|
+
await expect(
|
|
48
|
+
service.create('tenant-a', {
|
|
49
|
+
sku: 'x'.repeat(70),
|
|
50
|
+
name: 'Too long',
|
|
51
|
+
kind: 'product',
|
|
52
|
+
unit: 'each',
|
|
53
|
+
basePriceMinor: 100,
|
|
54
|
+
currency: 'EUR',
|
|
55
|
+
}),
|
|
56
|
+
).rejects.toThrowError(/between 1 and 64/);
|
|
57
|
+
});
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
HTTP level, the denial path only needs the route and a bare request:
|
|
61
|
+
|
|
62
|
+
```ts
|
|
63
|
+
import { createContext } from '@octanejs/app-core';
|
|
64
|
+
|
|
65
|
+
const [list] = createCatalogRoutes(auth, runtime);
|
|
66
|
+
const response = await list.handler(
|
|
67
|
+
createContext(new Request('https://erp.example/api/catalog/items'), {}),
|
|
68
|
+
);
|
|
69
|
+
expect(response.status).toBe(401);
|
|
70
|
+
expect(await response.json()).toMatchObject({
|
|
71
|
+
error: { code: 'UNAUTHENTICATED' },
|
|
72
|
+
});
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
For a 403 or a mutation you need a principal; the recipe with sign-up, cookie and CSRF is in `test-hardening`.
|
|
76
|
+
|
|
77
|
+
## 2c. Reading gate output
|
|
78
|
+
|
|
79
|
+
- `typecheck`: the first error is usually the cause; later ones cascade. `TS2307 Cannot find module` inside a module means an undeclared package or a missing `.ts` extension.
|
|
80
|
+
- `tests`: vitest prints the failing assertion with `Expected` and `Received`; a driver error naming a column type is a value that does not fit the column (`Number.isSafeInteger` at the service boundary), and a comparison that fails on a count or a flag is usually a `BIGINT` returned as a string that skipped the repository's `integer()` helper.
|
|
81
|
+
- `format`: run `pnpm format` (or the sandbox format action); never hand-format.
|
|
82
|
+
- `dependencies`: the message lists the undeclared packages; add them to `package.json` dependencies (the session installs what `package.json` declares and nothing else). A failed `pnpm install` after a `package.json` change is reported under the same gate with the installer output.
|
|
83
|
+
- `module-schema`: `SCHEMA_ADDITIONALPROPERTIES` names a key `module.json` does not know; `MODULE_ENABLED_MISSING` means `flowdular.json` names a module without a manifest; `PLATFORM_SERVER_ENTRY_MISSING`, `PLATFORM_EXPORT_MISSING`, `PLATFORM_CLIENT_ENTRY_MISSING`, `PLATFORM_CLIENT_EXPORT_MISSING` name a composition entry the flags promise but the module lacks; `TRANSLATION_KEYS_MISMATCH` and `TRANSLATION_FILE_MISSING` are locale drift; `SPEC_VERSION_DRIFT` and `LOCALE_NOT_IN_PROJECT` are warnings.
|
|
84
|
+
|
|
85
|
+
## 3. Fix at the owning layer
|
|
86
|
+
|
|
87
|
+
Validation belongs to the HTTP helpers and the service, not to the client. Tenant scoping belongs to the repository query and the endpoint's `principalFromContext`. Presentation belongs to the view. A fix that adds a second check in a different layer hides the defect; move it instead.
|
|
88
|
+
|
|
89
|
+
Smallest change: write the failing test, make it pass, run `pnpm --filter @flowdular/module-<dir> typecheck` and `test`, and `pnpm format` when the format gate complains (the sandbox has a format action for that).
|
|
90
|
+
|
|
91
|
+
## 4. Hunt siblings
|
|
92
|
+
|
|
93
|
+
The bundled modules share one shape. After fixing `modules/<dir>`, grep the same pattern in `.ai/references/catalog`, `modules/users`, `modules/profile`, `modules/agents`, `modules/sandbox`: `grep -rn '<pattern>' modules/*/src`. Report siblings you did not fix.
|
|
94
|
+
|
|
95
|
+
## 5. Reporting
|
|
96
|
+
|
|
97
|
+
State the root cause (input and state to wrong outcome), the file and line, the regression test name, and the siblings. Do not claim a fix without the test passing where it failed before.
|
|
98
|
+
|
|
99
|
+
## Pitfalls
|
|
100
|
+
|
|
101
|
+
- The sandbox `tests` gate passes with zero tests; a green gate is not evidence.
|
|
102
|
+
- A test never points at a running deployment; it takes its database from `createPgliteTestProvider()` through the module's `tests/support/database.ts`, which runs PostgreSQL inside the test process.
|
|
103
|
+
- `principalFromContext(octane)!` is only safe after `resolveIdentity`; a public endpoint has no principal.
|
|
104
|
+
- Do not add `try/catch` that swallows the error to make a 500 disappear; map it to a stable code or let it surface.
|