create-flowdular 0.4.1 → 0.5.1
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 +9 -6
- package/agent-template/.agents/skills/auth-security-review/SKILL.md +1 -1
- package/agent-template/.agents/skills/bug-hunt/SKILL.md +1 -1
- package/agent-template/.agents/skills/module-new/SKILL.md +20 -19
- package/agent-template/.agents/skills/module-update/SKILL.md +4 -2
- package/agent-template/.agents/skills/spec-interview/SKILL.md +21 -20
- package/agent-template/.agents/skills/ux-design/SKILL.md +24 -2
- package/agent-template/.ai/README.md +5 -3
- package/agent-template/.ai/agents/README.md +1 -1
- package/agent-template/.ai/agents/sandbox/agentic-engineer.md +1 -0
- package/agent-template/.ai/agents/sandbox/backend-engineer.md +1 -0
- package/agent-template/.ai/agents/sandbox/frontend-engineer.md +1 -0
- package/agent-template/.ai/blueprints/add-migration/README.md +1 -1
- package/agent-template/.ai/examples/bad/client-imports-server/README.md +1 -1
- package/agent-template/.ai/examples/bad/missing-acl/README.md +1 -1
- package/agent-template/.ai/examples/bad/tenant-from-body/README.md +1 -1
- package/agent-template/.ai/guides/application-development.md +7 -5
- package/agent-template/.ai/platform-capabilities.md +10 -4
- package/agent-template/.ai/policies/capabilities.yaml +1 -0
- package/agent-template/.ai/policies/task-budgets.yaml +1 -1
- package/agent-template/.ai/rules/flowdular.md +3 -2
- package/agent-template/.ai/skills/auth-security-review/SKILL.md +1 -1
- package/agent-template/.ai/skills/bug-hunt/SKILL.md +1 -1
- package/agent-template/.ai/skills/module-new/SKILL.md +20 -19
- package/agent-template/.ai/skills/module-update/SKILL.md +4 -2
- package/agent-template/.ai/skills/spec-interview/SKILL.md +21 -20
- package/agent-template/.ai/skills/ux-design/SKILL.md +24 -2
- package/agent-template/.ai/subagents/module-executor.md +25 -0
- package/agent-template/.ai/subagents/reviewer.md +23 -0
- package/agent-template/.ai/subagents/spec-author.md +23 -0
- package/agent-template/.claude/agents/module-executor.md +22 -0
- package/agent-template/.claude/agents/reviewer.md +24 -0
- package/agent-template/.claude/agents/spec-author.md +20 -0
- package/agent-template/.claude/skills/auth-security-review/SKILL.md +1 -1
- package/agent-template/.claude/skills/bug-hunt/SKILL.md +1 -1
- package/agent-template/.claude/skills/module-new/SKILL.md +20 -19
- package/agent-template/.claude/skills/module-update/SKILL.md +4 -2
- package/agent-template/.claude/skills/spec-interview/SKILL.md +21 -20
- package/agent-template/.claude/skills/ux-design/SKILL.md +24 -2
- package/agent-template/.codex/agents/module-executor.toml +17 -0
- package/agent-template/.codex/agents/reviewer.toml +14 -0
- package/agent-template/.codex/agents/spec-author.toml +15 -0
- package/agent-template/AGENTS.md +3 -2
- package/agent-template/CLAUDE.md +3 -2
- package/agent-template/docs/cli.md +3 -0
- package/agent-template/docs/configuration.md +44 -0
- package/agent-template/docs/design-system.md +10 -3
- package/agent-template/docs/module-web-surfaces.md +31 -3
- package/agent-template/docs/modules.md +6 -0
- package/agent-template/docs/sandbox.md +117 -6
- package/agent-template/rulesync.jsonc +1 -1
- package/dist/bin.js +9 -0
- package/package.json +2 -2
- package/template/default/.env.example +7 -0
- package/template/default/.prettierignore +2 -0
- package/template/default/README.md +6 -4
- package/template/default/modules/example/module.json +1 -1
- package/template/default/modules/example/package.json +1 -1
- package/template/default/modules/example/spec/module.yaml +1 -1
- package/template/default/package.json +3 -2
- package/template/default/platform/index.html +7 -19
- package/template/default/platform/octane.config.ts +26 -2
- package/template/default/platform/package.json +1 -1
- package/template/default/platform/public/favicon.svg +1 -1
- package/template/default/platform/src/App.tsrx +25 -1
- package/template/default/platform/src/generated/modules.server.ts +2 -0
|
@@ -26,25 +26,26 @@ Every `acceptanceScenarios[]` entry maps to at least one test in `tests/`. A sce
|
|
|
26
26
|
|
|
27
27
|
Each spec element maps to files:
|
|
28
28
|
|
|
29
|
-
| Spec element | Files it produces
|
|
30
|
-
| ----------------------------- |
|
|
31
|
-
| `entities[]` | `src/domain/types.ts`, `migrations/000N_<module>_<name>.{up,down}.sql`, `src/services/migration.ts`, `src/services/{repository,database-repository}.ts`
|
|
32
|
-
| `entities[].fields[]` | the columns and row mapping above, the validation bounds in `src/api/endpoints.ts`, the form field and table cell in `src/client/*.tsrx`
|
|
33
|
-
| `fields[].unique: tenant` | a `(tenant_id, <field>)` unique index in the migration plus the stable conflict code in the service
|
|
34
|
-
| `entities[].states` | the status column, the transition guard in the service, the `Tag` tone in the view
|
|
35
|
-
| `screens[]` | `src/client/<Pascal>View.tsrx`, a `views` entry and a `navigation` entry in `src/client/contribution.tsrx`, `translations/*.json`
|
|
36
|
-
| `
|
|
37
|
-
| `screens[].
|
|
38
|
-
| `
|
|
39
|
-
| `
|
|
40
|
-
| `
|
|
41
|
-
| `
|
|
42
|
-
| `
|
|
43
|
-
| `
|
|
44
|
-
| `
|
|
45
|
-
| `
|
|
46
|
-
| `
|
|
47
|
-
| `
|
|
29
|
+
| Spec element | Files it produces |
|
|
30
|
+
| ----------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
31
|
+
| `entities[]` | `src/domain/types.ts`, `migrations/000N_<module>_<name>.{up,down}.sql`, `src/services/migration.ts`, `src/services/{repository,database-repository}.ts` |
|
|
32
|
+
| `entities[].fields[]` | the columns and row mapping above, the validation bounds in `src/api/endpoints.ts`, the form field and table cell in `src/client/*.tsrx` |
|
|
33
|
+
| `fields[].unique: tenant` | a `(tenant_id, <field>)` unique index in the migration plus the stable conflict code in the service |
|
|
34
|
+
| `entities[].states` | the status column, the transition guard in the service, the `Tag` tone in the view |
|
|
35
|
+
| `screens[]` | `src/client/<Pascal>View.tsrx`, a `views` entry and a `navigation` entry in `src/client/contribution.tsrx`, `translations/*.json` |
|
|
36
|
+
| `design/<screen>.html` | the approved look of that screen: mirror its structure and keep its copy. Render it with `pnpm ui:preview <path>`; absent, design it first with `ux-design` |
|
|
37
|
+
| `screens[].columns`/`filters` | the `TableColumn[]` outside the component, and the controls inside the `Filters` dropdown |
|
|
38
|
+
| `screens[].navigationGroup` | the navigation entry's `group` |
|
|
39
|
+
| `actions[]` | the service method with its error code, the `defineEndpoint` route, the fetch in `src/client/api.ts` |
|
|
40
|
+
| `widgets[]` | a widget component plus a `widgets` entry with its `slot` in `src/client/contribution.tsrx` |
|
|
41
|
+
| `settings[]` | `src/settings.ts` (`defineModuleSettings`) and `settings:` in `src/platform.ts`; a `kind: flag` entry keeps `kind: 'flag'` there, so owners switch it on the Flags tab of Administration, Modules |
|
|
42
|
+
| `agentTools[]` | `src/agent/tools.ts` and the `context.agentTools.register` call, as a separate `agent-tool-design` phase |
|
|
43
|
+
| `research` | `src/research.ts`, `research-fixtures.json`, `requires` of the capabilities, the evidence attach where the `evidenceOwner` record is written |
|
|
44
|
+
| `adapters[]` | `src/adapters/<name>.ts`, the port, the adapter registration, `adapters/<name>.recorded.json`, as a separate `integration-adapter` phase |
|
|
45
|
+
| `templates[]` | `templates/<name>.md`, `templates` in `package.json` `files`, `src/templates.ts` registered through `documents.templates.v1` in `src/platform.ts` |
|
|
46
|
+
| `permissions[]` | `src/acl/permissions.ts`, the endpoint `access.permission`, the client `scope` |
|
|
47
|
+
| `acceptanceScenarios[]` | `tests/module.test.ts` and its siblings, at least one case each |
|
|
48
|
+
| `outOfScope[]`, `decisions[]` | no code. Read them so you do not rebuild a decision or implement a deferred feature. |
|
|
48
49
|
|
|
49
50
|
A v1 spec stays valid and carries none of these arrays. Then the requirements are `invariants`, `permissions` and `acceptanceScenarios`, and everything the spec leaves open is still a question rather than a guess.
|
|
50
51
|
|
|
@@ -23,7 +23,7 @@ The spec-element to file mapping is the table in `module-new`; the change classe
|
|
|
23
23
|
|
|
24
24
|
## 1. Read first
|
|
25
25
|
|
|
26
|
-
Read the whole module before changing it: `spec/module.yaml`, `src/index.ts`, `src/acl/permissions.ts`, `src/api/endpoints.ts`, `src/services/*`, `src/client/*`, `tests/`. Keep every exported name in `src/index.ts`, `src/server/index.ts` and `src/client/index.ts` stable: other modules import them (`modules/users` uses `AuthRuntime` from `@flowdular/sdk/modules/auth/server`), and the generated composition imports `createServerComposition` and `createClientContribution`.
|
|
26
|
+
Read the whole module before changing it: `spec/module.yaml`, `design/*.html` when the change touches a screen, `src/index.ts`, `src/acl/permissions.ts`, `src/api/endpoints.ts`, `src/services/*`, `src/client/*`, `tests/`. Keep every exported name in `src/index.ts`, `src/server/index.ts` and `src/client/index.ts` stable: other modules import them (`modules/users` uses `AuthRuntime` from `@flowdular/sdk/modules/auth/server`), and the generated composition imports `createServerComposition` and `createClientContribution`.
|
|
27
27
|
|
|
28
28
|
Sandbox facts for an edit session (`packages/sandbox/src/server/sessions.ts`): the module is copied to `workspace/modules/<dir>` and a pristine copy to `base/modules/<dir>`; the diff shown to the operator and the eject plan compare the two. The workspace is a pnpm workspace of its own (declared dependencies install for real; a `package.json` change triggers a reinstall that counts as the `dependencies` gate). The business manager updates the spec before implementation. The operator approves the exact spec hash for every affected module; editing that spec, requesting changes, or adding another module reopens its approval gate. A sandbox specialist never writes `status: approved`; a host agent may invoke approval only after an explicit current user request through `spec-approval`. Delivery checks the recorded hash again.
|
|
29
29
|
|
|
@@ -62,9 +62,11 @@ New screen or widget:
|
|
|
62
62
|
3. `src/client/index.ts`: re-export the view.
|
|
63
63
|
4. Add user-facing copy to every declared `translations/*.json` bundle and resolve it with fully qualified `t()` keys. Navigation copy uses getters because contributions exist before bundles are registered.
|
|
64
64
|
|
|
65
|
+
New feature flag: a setting with `kind: 'flag'`, `type: 'boolean'`, `scope: 'tenant'` and a `defaultValue`. It reads like any other setting, `context.settings.get<boolean>(tenantId, '<module>.core', 'key')`, and appears on the Flags tab of Administration, Modules beside the module's other flags; a change is audited as `settings.flag.changed`. Declare it in the spec as `kind: flag` too, and state in an invariant what the module does while it is off. Before changing behaviour a workspace already relies on, removing a step, or adding an outward-facing or costly path, propose a flag and let the operator decide: ship the new path behind it, default off, and keep the old path working while it is off. A flag is on or off for one workspace; there is no percentage rollout and no targeting.
|
|
66
|
+
|
|
65
67
|
New setting:
|
|
66
68
|
|
|
67
|
-
1. `src/settings.ts`: `export const X_MODULE_SETTINGS = defineModuleSettings({ moduleId: '<module>.core', settings: { key: { type: 'string' | 'number' | 'boolean', defaultValue, visibility: 'private' | 'shared', client: boolean, scope: 'tenant' | 'platform', labelKey, label, descriptionKey, description, min?, max?, enum?, secret? } } })` from `@flowdular/sdk/kernel` (`packages/kernel/src/module-settings.ts`; setting keys match `^[a-z][a-zA-Z0-9]*$`). `labelKey` and `descriptionKey` are fully qualified module translation keys present in every locale. Keep the English literals as compatibility fallbacks; values, ids and secrets are never translated.
|
|
69
|
+
1. `src/settings.ts`: `export const X_MODULE_SETTINGS = defineModuleSettings({ moduleId: '<module>.core', settings: { key: { type: 'string' | 'number' | 'boolean', kind?: 'flag', defaultValue, visibility: 'private' | 'shared', client: boolean, scope: 'tenant' | 'platform', labelKey, label, descriptionKey, description, min?, max?, enum?, secret? } } })` from `@flowdular/sdk/kernel` (`packages/kernel/src/module-settings.ts`; setting keys match `^[a-z][a-zA-Z0-9]*$`). `labelKey` and `descriptionKey` are fully qualified module translation keys present in every locale. Keep the English literals as compatibility fallbacks; values, ids and secrets are never translated.
|
|
68
70
|
2. `src/platform.ts`: return `settings: X_MODULE_SETTINGS` next to `routes`; the platform declares it at boot and Administration, Modules renders it in the module's drawer (`modules/system/src/client/ModuleSettingsSection.tsrx`, behind `system.settings.read` and `system.settings.manage`; the API is `GET /api/settings` and `POST /api/settings/update` in `modules/system/src/server/endpoints.ts`).
|
|
69
71
|
3. Read it live where it is used: `context.settings.get<number>(tenantId, '<module>.core', 'key')` at request time, never cached at boot; pass `context.settings` into the runtime or service that needs it (`modules/agents/src/settings.ts`, `agentSettings`, shows the pattern with an environment fallback).
|
|
70
72
|
4. `spec/module.yaml`: an invariant or scenario naming the setting and its bounds; `specVersion` bump. Cross-module reads of a setting need `visibility: 'shared'` and a declared dependency.
|
|
@@ -29,25 +29,26 @@ For an edit, also read the module's current `spec/module.yaml` and write the sma
|
|
|
29
29
|
|
|
30
30
|
One pass, in this order. For each row, write the default from the card into the spec and record it as a `decisions[]` entry with `decidedBy: default`. Ask only where the answer is a business fact that no default can supply.
|
|
31
31
|
|
|
32
|
-
| Decision | Default to propose
|
|
33
|
-
| ---------------------- |
|
|
34
|
-
| Actors | Owner manages, member reads
|
|
35
|
-
| Entities and fields | One primary entity; `name` required, `maxLength` 120; no field the request did not name
|
|
36
|
-
| Uniqueness | The human-facing code is `unique: tenant`; everything else `none`
|
|
37
|
-
| States and transitions | `active` and `archived`, every transition behind the manage permission
|
|
38
|
-
| Who sees what | Both permissions in the same navigation entry; the manage action hidden without the scope
|
|
39
|
-
| What is denied | Unauthenticated 401, missing permission 403, cross-tenant read returns nothing
|
|
40
|
-
| Failure behaviour | A duplicate returns a stable conflict and changes nothing; bounds return 400
|
|
41
|
-
| Cross-module reads | None. A read of another module goes through its public capability and a declared dependency
|
|
42
|
-
| Screens | One `list` screen with the entity's identifying columns
|
|
43
|
-
| Widgets | None. A count belongs on `dashboard.metrics` only when the request asks for it
|
|
44
|
-
| Settings | None. A number the business may change later is `scope: tenant` with a stated default
|
|
45
|
-
|
|
|
46
|
-
|
|
|
47
|
-
|
|
|
48
|
-
|
|
|
49
|
-
|
|
|
50
|
-
|
|
|
32
|
+
| Decision | Default to propose | Lands in |
|
|
33
|
+
| ---------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------- |
|
|
34
|
+
| Actors | Owner manages, member reads | `permissions`, `invariants` |
|
|
35
|
+
| Entities and fields | One primary entity; `name` required, `maxLength` 120; no field the request did not name | `entities[]` |
|
|
36
|
+
| Uniqueness | The human-facing code is `unique: tenant`; everything else `none` | `entities[].fields[].unique` |
|
|
37
|
+
| States and transitions | `active` and `archived`, every transition behind the manage permission | `entities[].states` |
|
|
38
|
+
| Who sees what | Both permissions in the same navigation entry; the manage action hidden without the scope | `permissions`, `screens[]`, `invariants` |
|
|
39
|
+
| What is denied | Unauthenticated 401, missing permission 403, cross-tenant read returns nothing | `acceptanceScenarios` |
|
|
40
|
+
| Failure behaviour | A duplicate returns a stable conflict and changes nothing; bounds return 400 | `invariants`, `acceptanceScenarios` |
|
|
41
|
+
| Cross-module reads | None. A read of another module goes through its public capability and a declared dependency | `dependencies`, `dataOwnership` |
|
|
42
|
+
| Screens | One `list` screen with the entity's identifying columns | `screens[]` |
|
|
43
|
+
| Widgets | None. A count belongs on `dashboard.metrics` only when the request asks for it | `widgets[]` |
|
|
44
|
+
| Settings | None. A number the business may change later is `scope: tenant` with a stated default | `settings[]` |
|
|
45
|
+
| Feature flags | Ask for one whenever a change alters behaviour a workspace already relies on, or is hard to undo: `kind: flag`, boolean, `scope: tenant`, a stated default, and the behaviour named in an invariant | `settings[]` |
|
|
46
|
+
| Agent tools | None. A tool is a later phase and `risk` may only be `read` or `workspace-write` | `agentTools[]` |
|
|
47
|
+
| Outside sources | None. A named public source is `research`, with the entity its findings attach to | `research` |
|
|
48
|
+
| Other systems | None. A named system is one `source` adapter per record kind, run on demand | `adapters[]` |
|
|
49
|
+
| Documents | None. A named document is one `templates[]` entry on the record it describes | `templates[]` |
|
|
50
|
+
| Reports | A named report is one provider on `reports.v1` returning tiles and series; a named list export is one `defineListExport`; named records are findable through `search.providers.v1`. None of the three has a specification key, so each needs a decision naming the screen or the `actions[]` entry that registers it | `actions[]`, `invariants` |
|
|
51
|
+
| Out of scope | Every item from the card's gap list the request touched, each with its business decision | `outOfScope[]`, `decisions[]` |
|
|
51
52
|
|
|
52
53
|
A default you propose is still a decision: it goes into `decisions[]` so the operator can see and overturn it, and so the next agent never re-derives it.
|
|
53
54
|
|
|
@@ -95,7 +96,7 @@ A number the case needs (a score, a premium, a price per square metre) is an `ac
|
|
|
95
96
|
- `screens[]`: `{ id, kind: list|record|form|dashboard, entity?, title?, columns?, filters?, navigationGroup? }`. `navigationGroup` is one of the six values on the card.
|
|
96
97
|
- `actions[]`: `{ id, entity?, permission, kind: create|update|delete|custom, risk, idempotent, description }`. `risk: external` is refused by the platform, so an action may not declare it.
|
|
97
98
|
- `widgets[]`: `{ id, slot, entity?, description }`; `slot` is one of the four workspace slots.
|
|
98
|
-
- `settings[]`: `{ key, type: string|integer|boolean|enum, scope: tenant|platform, default?, values?, description }`.
|
|
99
|
+
- `settings[]`: `{ key, type: string|integer|boolean|enum, scope: tenant|platform, kind?: setting|flag, default?, values?, description }`. A `flag` is boolean, `scope: tenant` and carries a default; owners switch it per workspace on the Flags tab of Administration, Modules, and every change is audited. Ask whether the new behaviour ships behind one: a change to something a workspace already does, an outward-facing action, a costly or slow path, or anything a business would want to turn off without a deployment. Say what the module does with the flag off, as an invariant.
|
|
99
100
|
- `agentTools[]`: `{ id, permission, description, risk: read|workspace-write }`.
|
|
100
101
|
- `outOfScope[]`: plain sentences, each naming the gap and the decision taken instead.
|
|
101
102
|
- `decisions[]`: `{ id, question, answer, decidedBy: user|default }`; ids match `^[A-Z][A-Z0-9-]+$`, for example `D-UNIQUE-SKU`.
|
|
@@ -86,7 +86,7 @@ Read-only master-detail (runs, playground) keeps `ui-two-col` (+ `--wide-aside`)
|
|
|
86
86
|
- `Alert`: `tone` danger (default), warning, info.
|
|
87
87
|
- `Avatar`: `name`, `square` (organizations), `large`.
|
|
88
88
|
- `Icon`: `name`, `size` (18 default, 16 in controls, 14 in `Button size="sm"`), `strokeWidth`.
|
|
89
|
-
- `BrandMark`: `size`, `
|
|
89
|
+
- `BrandMark`: `size`, `tone`; three bars with a copper accent bar, brand moments only.
|
|
90
90
|
|
|
91
91
|
Icon keys (`ICON_PATHS`, `packages/ui/src/icons/Icon.tsrx`): `dashboard`, `parties`, `catalog`, `user`, `users`, `shield`, `code`, `modules`, `file-text`, `play`, `bot`, `flask`, `activity`, `plug`, `search`, `chevron-down`, `chevron-left`, `chevron-right`, `chevrons-up-down`, `sort`, `calendar`, `plus`, `panel-left`, `check`, `filter`, `download`, `more`, `external`, `alert`, `x`, `sign-out`, `refresh`, `help`, `info`, `key`, `settings`, `braces`, `copy`. An unknown name renders `modules` silently, so check the list.
|
|
92
92
|
|
|
@@ -98,7 +98,28 @@ Layout `ui-view`, `ui-two-col` (+`--wide-aside`), `ui-grid-2`, `ui-kpi-grid`, `u
|
|
|
98
98
|
|
|
99
99
|
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.
|
|
100
100
|
|
|
101
|
-
## 7.
|
|
101
|
+
## 7. Design the screen as a preview first
|
|
102
|
+
|
|
103
|
+
A design is a file, not a description: `modules/<dir>/design/<screen>.html`, beside the spec. It is markup only, dressed by the platform's own stylesheets, so it shows what the screen will look like before a component exists and long before the application boots. An implementation phase reads it the way it reads the spec.
|
|
104
|
+
|
|
105
|
+
```bash
|
|
106
|
+
pnpm ui:preview modules/<dir>/design/<screen>.html --scaffold # the record recipe in all five states
|
|
107
|
+
pnpm ui:preview modules/<dir>/design/<screen>.html # renders it, prints a file:// address
|
|
108
|
+
pnpm ui:preview modules/<dir>/design/<screen>.html --shot .flowdular/ui-preview/<screen>.png
|
|
109
|
+
pnpm ui:preview modules/<dir>/design/<screen>.html --open # shows it to the person asking
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
Rules that keep a preview honest:
|
|
113
|
+
|
|
114
|
+
- Markup only. No `<style>`, no CSS rule, no `<html>` or `<body>`; the command refuses a fragment that carries one. Every visual decision comes from `packages/ui`, which is what stops a preview from becoming a second source of truth.
|
|
115
|
+
- One `<section class="ui-view" data-state="...">` per state: `populated`, `loading`, `empty`, `error`, `denied`. The command labels each one, so a single page answers for all five.
|
|
116
|
+
- Real content. The longest realistic name, a real identifier, the copy the screen will actually carry. A preview of `Lorem ipsum` proves nothing about overflow or alignment.
|
|
117
|
+
- Only classes a stylesheet declares. `pnpm ui-classes:check` fails on a class nothing defines, in a preview and in a `.tsrx` alike, which is the one mistake that compiles, passes its tests and renders unstyled. A class a module needs and the design system lacks is declared in the module's own CSS, as rule 4 says.
|
|
118
|
+
- `--shot` writes a PNG, which is how an agent with no browser looks at its own work. `--open` opens the preview in the person's browser, which is how a design is shown for approval; a correction loop renders without it rather than throwing a window at whoever is at the keyboard.
|
|
119
|
+
|
|
120
|
+
Hand off the path, not a description. An implementation phase opens the preview, mirrors its structure with the components in section 4, and keeps the copy.
|
|
121
|
+
|
|
122
|
+
## 8. Inspect the rendered screen
|
|
102
123
|
|
|
103
124
|
A screen is not finished until it has been looked at. Typecheck and tests say nothing about overflow, alignment, a duplicate label or a column that collapses.
|
|
104
125
|
|
|
@@ -122,6 +143,7 @@ Check the drawer form in the same pass: one label per field, fields top-aligned,
|
|
|
122
143
|
|
|
123
144
|
## Pitfalls
|
|
124
145
|
|
|
146
|
+
- A preview that renders unstyled is a class nothing declares, not a broken harness; run `pnpm ui-classes:check`.
|
|
125
147
|
- `Kpi value={items.length}` does not typecheck; use `String(items.length)`.
|
|
126
148
|
- A `Tag` for a lifecycle state uses `success` for active and `neutral` for archived, with `dot`.
|
|
127
149
|
- An `Icon` inside `Button size="sm"` is 14, not 18.
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: module-executor
|
|
3
|
+
targets: ['*']
|
|
4
|
+
description: >-
|
|
5
|
+
Implement one Flowdular blueprint end to end at the repository root: scaffold
|
|
6
|
+
from an approved spec, write the module, run the gates and join it to the
|
|
7
|
+
platform through the CLI. Use once a spec is approved and the change is ready
|
|
8
|
+
to be built.
|
|
9
|
+
claudecode:
|
|
10
|
+
model: inherit
|
|
11
|
+
---
|
|
12
|
+
|
|
13
|
+
Your role prompt is `.ai/agents/module-executor.md`. Read it with the blueprint
|
|
14
|
+
under `.ai/blueprints/<id>/` and the one matching skill in
|
|
15
|
+
`.ai/skills/<name>/SKILL.md` before changing anything.
|
|
16
|
+
|
|
17
|
+
Implement only what the approved spec's acceptance scenarios describe, inside the
|
|
18
|
+
blueprint's `allowed-paths.yaml`. Join the platform through
|
|
19
|
+
`pnpm flowdular module enable <id> --apply` and `auth sync-scopes`; never edit
|
|
20
|
+
`flowdular.json`, `platform/package.json`, `platform/src/generated/**` or
|
|
21
|
+
`platform/octane.config.ts` by hand.
|
|
22
|
+
|
|
23
|
+
Run the module gates and `pnpm verify` yourself and report their exact commands
|
|
24
|
+
and results. Never waive a gate. End with `HANDOFF: reviewer - <what to review>`
|
|
25
|
+
or `HANDOFF: none - <blocker>`.
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: reviewer
|
|
3
|
+
targets: ['*']
|
|
4
|
+
description: >-
|
|
5
|
+
Review a finished Flowdular change against its approved spec, the blueprint,
|
|
6
|
+
the invariants and the executable evidence, and report findings by severity.
|
|
7
|
+
Use as a separate phase before delivery or a pull request. Reports defects and
|
|
8
|
+
fixes nothing.
|
|
9
|
+
claudecode:
|
|
10
|
+
model: inherit
|
|
11
|
+
tools: ['Read', 'Grep', 'Glob', 'Bash']
|
|
12
|
+
---
|
|
13
|
+
|
|
14
|
+
Your role prompt is `.ai/agents/reviewer.md` and the one task skill for this
|
|
15
|
+
phase is `.ai/skills/auto-review/SKILL.md`. Read both before reviewing.
|
|
16
|
+
|
|
17
|
+
You write no production code. Review the complete requested change, its
|
|
18
|
+
requirements, callers and tests; report concrete findings by severity with file,
|
|
19
|
+
line and failure scenario. Never waive missing or failing verification, and never
|
|
20
|
+
approve a change whose evidence you have not seen run.
|
|
21
|
+
|
|
22
|
+
End with `HANDOFF: module-executor - <findings to fix>` or
|
|
23
|
+
`HANDOFF: none - <review result and remaining verification>`.
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: spec-author
|
|
3
|
+
targets: ['*']
|
|
4
|
+
description: >-
|
|
5
|
+
Turn a business request into a schema-valid Flowdular module specification at
|
|
6
|
+
the repository root. Use before any implementation, for a new module spec or a
|
|
7
|
+
change to an existing one. Writes only modules/<dir>/spec/module.yaml and never
|
|
8
|
+
approves it.
|
|
9
|
+
claudecode:
|
|
10
|
+
model: inherit
|
|
11
|
+
---
|
|
12
|
+
|
|
13
|
+
Your role prompt is `.ai/agents/spec-author.md` and the one task skill for this
|
|
14
|
+
phase is `.ai/skills/spec-interview/SKILL.md`. Read both before writing.
|
|
15
|
+
|
|
16
|
+
You write only `modules/<dir>/spec/module.yaml`. Propose a platform default for
|
|
17
|
+
every decision and ask the user for what cannot be inferred; never guess a
|
|
18
|
+
business fact. Never set `status: approved`: approval is the user's, recorded by
|
|
19
|
+
the `spec-approval` skill.
|
|
20
|
+
|
|
21
|
+
`pnpm flowdular spec validate --all --json` must report the file valid before you
|
|
22
|
+
report. End with `HANDOFF: reviewer - spec ready for owner approval` or
|
|
23
|
+
`HANDOFF: none - <open question>`.
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: module-executor
|
|
3
|
+
description: >-
|
|
4
|
+
Implement one Flowdular blueprint end to end at the repository root: scaffold
|
|
5
|
+
from an approved spec, write the module, run the gates and join it to the
|
|
6
|
+
platform through the CLI. Use once a spec is approved and the change is ready
|
|
7
|
+
to be built.
|
|
8
|
+
model: inherit
|
|
9
|
+
---
|
|
10
|
+
Your role prompt is `.ai/agents/module-executor.md`. Read it with the blueprint
|
|
11
|
+
under `.ai/blueprints/<id>/` and the one matching skill in
|
|
12
|
+
`.ai/skills/<name>/SKILL.md` before changing anything.
|
|
13
|
+
|
|
14
|
+
Implement only what the approved spec's acceptance scenarios describe, inside the
|
|
15
|
+
blueprint's `allowed-paths.yaml`. Join the platform through
|
|
16
|
+
`pnpm flowdular module enable <id> --apply` and `auth sync-scopes`; never edit
|
|
17
|
+
`flowdular.json`, `platform/package.json`, `platform/src/generated/**` or
|
|
18
|
+
`platform/octane.config.ts` by hand.
|
|
19
|
+
|
|
20
|
+
Run the module gates and `pnpm verify` yourself and report their exact commands
|
|
21
|
+
and results. Never waive a gate. End with `HANDOFF: reviewer - <what to review>`
|
|
22
|
+
or `HANDOFF: none - <blocker>`.
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: reviewer
|
|
3
|
+
description: >-
|
|
4
|
+
Review a finished Flowdular change against its approved spec, the blueprint,
|
|
5
|
+
the invariants and the executable evidence, and report findings by severity.
|
|
6
|
+
Use as a separate phase before delivery or a pull request. Reports defects and
|
|
7
|
+
fixes nothing.
|
|
8
|
+
model: inherit
|
|
9
|
+
tools:
|
|
10
|
+
- Read
|
|
11
|
+
- Grep
|
|
12
|
+
- Glob
|
|
13
|
+
- Bash
|
|
14
|
+
---
|
|
15
|
+
Your role prompt is `.ai/agents/reviewer.md` and the one task skill for this
|
|
16
|
+
phase is `.ai/skills/auto-review/SKILL.md`. Read both before reviewing.
|
|
17
|
+
|
|
18
|
+
You write no production code. Review the complete requested change, its
|
|
19
|
+
requirements, callers and tests; report concrete findings by severity with file,
|
|
20
|
+
line and failure scenario. Never waive missing or failing verification, and never
|
|
21
|
+
approve a change whose evidence you have not seen run.
|
|
22
|
+
|
|
23
|
+
End with `HANDOFF: module-executor - <findings to fix>` or
|
|
24
|
+
`HANDOFF: none - <review result and remaining verification>`.
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: spec-author
|
|
3
|
+
description: >-
|
|
4
|
+
Turn a business request into a schema-valid Flowdular module specification at
|
|
5
|
+
the repository root. Use before any implementation, for a new module spec or a
|
|
6
|
+
change to an existing one. Writes only modules/<dir>/spec/module.yaml and
|
|
7
|
+
never approves it.
|
|
8
|
+
model: inherit
|
|
9
|
+
---
|
|
10
|
+
Your role prompt is `.ai/agents/spec-author.md` and the one task skill for this
|
|
11
|
+
phase is `.ai/skills/spec-interview/SKILL.md`. Read both before writing.
|
|
12
|
+
|
|
13
|
+
You write only `modules/<dir>/spec/module.yaml`. Propose a platform default for
|
|
14
|
+
every decision and ask the user for what cannot be inferred; never guess a
|
|
15
|
+
business fact. Never set `status: approved`: approval is the user's, recorded by
|
|
16
|
+
the `spec-approval` skill.
|
|
17
|
+
|
|
18
|
+
`pnpm flowdular spec validate --all --json` must report the file valid before you
|
|
19
|
+
report. End with `HANDOFF: reviewer - spec ready for owner approval` or
|
|
20
|
+
`HANDOFF: none - <open question>`.
|
|
@@ -74,7 +74,7 @@ Report each finding as: severity (`blocker`, `should-fix`, `taste`), claim, `fil
|
|
|
74
74
|
```text
|
|
75
75
|
blocker Tenant id read from body modules/inventory/src/api/endpoints.ts:41
|
|
76
76
|
A member of tenant A posts { tenantId: "B" } and creates a location in B.
|
|
77
|
-
AGENTS.md
|
|
77
|
+
AGENTS.md 4. Fix: principalFromContext(octane)!.tenantId; drop the field.
|
|
78
78
|
```
|
|
79
79
|
|
|
80
80
|
## 8. Known platform gaps to keep in mind (not module defects)
|
|
@@ -29,7 +29,7 @@ description: >-
|
|
|
29
29
|
| View falls back to the dashboard | `ApplicationShell.tsrx` renders `overview` for a view id that no visible navigation or account menu entry reaches |
|
|
30
30
|
| Icon renders as a grid | `glyph` or `Icon name` is not an `ICON_PATHS` key (`packages/ui/src/icons/Icon.tsrx` falls back to `modules`) |
|
|
31
31
|
| Stale data after a change | each component owns a store instance (`useMemo(() => createXClientState(), [])`); check the `store.act` that should have written it. `store.commits(cb)` and `store.stats()` from `segment-state` show what was committed |
|
|
32
|
-
| Schema error on start | `
|
|
32
|
+
| Schema error on start | `runDatabaseMigrations`: `CHECKSUM_MISMATCH` means applied SQL bytes changed; `PARTIAL_MIGRATION` means only part of a pending migration exists. Never delete or bypass the database to hide either condition; restore the shipped bytes or diagnose the partial schema |
|
|
33
33
|
|
|
34
34
|
## 2b. Reproduction snippets
|
|
35
35
|
|
|
@@ -20,25 +20,26 @@ Every `acceptanceScenarios[]` entry maps to at least one test in `tests/`. A sce
|
|
|
20
20
|
|
|
21
21
|
Each spec element maps to files:
|
|
22
22
|
|
|
23
|
-
| Spec element | Files it produces
|
|
24
|
-
| ----------------------------- |
|
|
25
|
-
| `entities[]` | `src/domain/types.ts`, `migrations/000N_<module>_<name>.{up,down}.sql`, `src/services/migration.ts`, `src/services/{repository,database-repository}.ts`
|
|
26
|
-
| `entities[].fields[]` | the columns and row mapping above, the validation bounds in `src/api/endpoints.ts`, the form field and table cell in `src/client/*.tsrx`
|
|
27
|
-
| `fields[].unique: tenant` | a `(tenant_id, <field>)` unique index in the migration plus the stable conflict code in the service
|
|
28
|
-
| `entities[].states` | the status column, the transition guard in the service, the `Tag` tone in the view
|
|
29
|
-
| `screens[]` | `src/client/<Pascal>View.tsrx`, a `views` entry and a `navigation` entry in `src/client/contribution.tsrx`, `translations/*.json`
|
|
30
|
-
| `
|
|
31
|
-
| `screens[].
|
|
32
|
-
| `
|
|
33
|
-
| `
|
|
34
|
-
| `
|
|
35
|
-
| `
|
|
36
|
-
| `
|
|
37
|
-
| `
|
|
38
|
-
| `
|
|
39
|
-
| `
|
|
40
|
-
| `
|
|
41
|
-
| `
|
|
23
|
+
| Spec element | Files it produces |
|
|
24
|
+
| ----------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
25
|
+
| `entities[]` | `src/domain/types.ts`, `migrations/000N_<module>_<name>.{up,down}.sql`, `src/services/migration.ts`, `src/services/{repository,database-repository}.ts` |
|
|
26
|
+
| `entities[].fields[]` | the columns and row mapping above, the validation bounds in `src/api/endpoints.ts`, the form field and table cell in `src/client/*.tsrx` |
|
|
27
|
+
| `fields[].unique: tenant` | a `(tenant_id, <field>)` unique index in the migration plus the stable conflict code in the service |
|
|
28
|
+
| `entities[].states` | the status column, the transition guard in the service, the `Tag` tone in the view |
|
|
29
|
+
| `screens[]` | `src/client/<Pascal>View.tsrx`, a `views` entry and a `navigation` entry in `src/client/contribution.tsrx`, `translations/*.json` |
|
|
30
|
+
| `design/<screen>.html` | the approved look of that screen: mirror its structure and keep its copy. Render it with `pnpm ui:preview <path>`; absent, design it first with `ux-design` |
|
|
31
|
+
| `screens[].columns`/`filters` | the `TableColumn[]` outside the component, and the controls inside the `Filters` dropdown |
|
|
32
|
+
| `screens[].navigationGroup` | the navigation entry's `group` |
|
|
33
|
+
| `actions[]` | the service method with its error code, the `defineEndpoint` route, the fetch in `src/client/api.ts` |
|
|
34
|
+
| `widgets[]` | a widget component plus a `widgets` entry with its `slot` in `src/client/contribution.tsrx` |
|
|
35
|
+
| `settings[]` | `src/settings.ts` (`defineModuleSettings`) and `settings:` in `src/platform.ts`; a `kind: flag` entry keeps `kind: 'flag'` there, so owners switch it on the Flags tab of Administration, Modules |
|
|
36
|
+
| `agentTools[]` | `src/agent/tools.ts` and the `context.agentTools.register` call, as a separate `agent-tool-design` phase |
|
|
37
|
+
| `research` | `src/research.ts`, `research-fixtures.json`, `requires` of the capabilities, the evidence attach where the `evidenceOwner` record is written |
|
|
38
|
+
| `adapters[]` | `src/adapters/<name>.ts`, the port, the adapter registration, `adapters/<name>.recorded.json`, as a separate `integration-adapter` phase |
|
|
39
|
+
| `templates[]` | `templates/<name>.md`, `templates` in `package.json` `files`, `src/templates.ts` registered through `documents.templates.v1` in `src/platform.ts` |
|
|
40
|
+
| `permissions[]` | `src/acl/permissions.ts`, the endpoint `access.permission`, the client `scope` |
|
|
41
|
+
| `acceptanceScenarios[]` | `tests/module.test.ts` and its siblings, at least one case each |
|
|
42
|
+
| `outOfScope[]`, `decisions[]` | no code. Read them so you do not rebuild a decision or implement a deferred feature. |
|
|
42
43
|
|
|
43
44
|
A v1 spec stays valid and carries none of these arrays. Then the requirements are `invariants`, `permissions` and `acceptanceScenarios`, and everything the spec leaves open is still a question rather than a guess.
|
|
44
45
|
|
|
@@ -16,7 +16,7 @@ The spec-element to file mapping is the table in `module-new`; the change classe
|
|
|
16
16
|
|
|
17
17
|
## 1. Read first
|
|
18
18
|
|
|
19
|
-
Read the whole module before changing it: `spec/module.yaml`, `src/index.ts`, `src/acl/permissions.ts`, `src/api/endpoints.ts`, `src/services/*`, `src/client/*`, `tests/`. Keep every exported name in `src/index.ts`, `src/server/index.ts` and `src/client/index.ts` stable: other modules import them (`modules/users` uses `AuthRuntime` from `@flowdular/sdk/modules/auth/server`), and the generated composition imports `createServerComposition` and `createClientContribution`.
|
|
19
|
+
Read the whole module before changing it: `spec/module.yaml`, `design/*.html` when the change touches a screen, `src/index.ts`, `src/acl/permissions.ts`, `src/api/endpoints.ts`, `src/services/*`, `src/client/*`, `tests/`. Keep every exported name in `src/index.ts`, `src/server/index.ts` and `src/client/index.ts` stable: other modules import them (`modules/users` uses `AuthRuntime` from `@flowdular/sdk/modules/auth/server`), and the generated composition imports `createServerComposition` and `createClientContribution`.
|
|
20
20
|
|
|
21
21
|
Sandbox facts for an edit session (`packages/sandbox/src/server/sessions.ts`): the module is copied to `workspace/modules/<dir>` and a pristine copy to `base/modules/<dir>`; the diff shown to the operator and the eject plan compare the two. The workspace is a pnpm workspace of its own (declared dependencies install for real; a `package.json` change triggers a reinstall that counts as the `dependencies` gate). The business manager updates the spec before implementation. The operator approves the exact spec hash for every affected module; editing that spec, requesting changes, or adding another module reopens its approval gate. A sandbox specialist never writes `status: approved`; a host agent may invoke approval only after an explicit current user request through `spec-approval`. Delivery checks the recorded hash again.
|
|
22
22
|
|
|
@@ -55,9 +55,11 @@ New screen or widget:
|
|
|
55
55
|
3. `src/client/index.ts`: re-export the view.
|
|
56
56
|
4. Add user-facing copy to every declared `translations/*.json` bundle and resolve it with fully qualified `t()` keys. Navigation copy uses getters because contributions exist before bundles are registered.
|
|
57
57
|
|
|
58
|
+
New feature flag: a setting with `kind: 'flag'`, `type: 'boolean'`, `scope: 'tenant'` and a `defaultValue`. It reads like any other setting, `context.settings.get<boolean>(tenantId, '<module>.core', 'key')`, and appears on the Flags tab of Administration, Modules beside the module's other flags; a change is audited as `settings.flag.changed`. Declare it in the spec as `kind: flag` too, and state in an invariant what the module does while it is off. Before changing behaviour a workspace already relies on, removing a step, or adding an outward-facing or costly path, propose a flag and let the operator decide: ship the new path behind it, default off, and keep the old path working while it is off. A flag is on or off for one workspace; there is no percentage rollout and no targeting.
|
|
59
|
+
|
|
58
60
|
New setting:
|
|
59
61
|
|
|
60
|
-
1. `src/settings.ts`: `export const X_MODULE_SETTINGS = defineModuleSettings({ moduleId: '<module>.core', settings: { key: { type: 'string' | 'number' | 'boolean', defaultValue, visibility: 'private' | 'shared', client: boolean, scope: 'tenant' | 'platform', labelKey, label, descriptionKey, description, min?, max?, enum?, secret? } } })` from `@flowdular/sdk/kernel` (`packages/kernel/src/module-settings.ts`; setting keys match `^[a-z][a-zA-Z0-9]*$`). `labelKey` and `descriptionKey` are fully qualified module translation keys present in every locale. Keep the English literals as compatibility fallbacks; values, ids and secrets are never translated.
|
|
62
|
+
1. `src/settings.ts`: `export const X_MODULE_SETTINGS = defineModuleSettings({ moduleId: '<module>.core', settings: { key: { type: 'string' | 'number' | 'boolean', kind?: 'flag', defaultValue, visibility: 'private' | 'shared', client: boolean, scope: 'tenant' | 'platform', labelKey, label, descriptionKey, description, min?, max?, enum?, secret? } } })` from `@flowdular/sdk/kernel` (`packages/kernel/src/module-settings.ts`; setting keys match `^[a-z][a-zA-Z0-9]*$`). `labelKey` and `descriptionKey` are fully qualified module translation keys present in every locale. Keep the English literals as compatibility fallbacks; values, ids and secrets are never translated.
|
|
61
63
|
2. `src/platform.ts`: return `settings: X_MODULE_SETTINGS` next to `routes`; the platform declares it at boot and Administration, Modules renders it in the module's drawer (`modules/system/src/client/ModuleSettingsSection.tsrx`, behind `system.settings.read` and `system.settings.manage`; the API is `GET /api/settings` and `POST /api/settings/update` in `modules/system/src/server/endpoints.ts`).
|
|
62
64
|
3. Read it live where it is used: `context.settings.get<number>(tenantId, '<module>.core', 'key')` at request time, never cached at boot; pass `context.settings` into the runtime or service that needs it (`modules/agents/src/settings.ts`, `agentSettings`, shows the pattern with an environment fallback).
|
|
63
65
|
4. `spec/module.yaml`: an invariant or scenario naming the setting and its bounds; `specVersion` bump. Cross-module reads of a setting need `visibility: 'shared'` and a declared dependency.
|
|
@@ -23,25 +23,26 @@ For an edit, also read the module's current `spec/module.yaml` and write the sma
|
|
|
23
23
|
|
|
24
24
|
One pass, in this order. For each row, write the default from the card into the spec and record it as a `decisions[]` entry with `decidedBy: default`. Ask only where the answer is a business fact that no default can supply.
|
|
25
25
|
|
|
26
|
-
| Decision | Default to propose
|
|
27
|
-
| ---------------------- |
|
|
28
|
-
| Actors | Owner manages, member reads
|
|
29
|
-
| Entities and fields | One primary entity; `name` required, `maxLength` 120; no field the request did not name
|
|
30
|
-
| Uniqueness | The human-facing code is `unique: tenant`; everything else `none`
|
|
31
|
-
| States and transitions | `active` and `archived`, every transition behind the manage permission
|
|
32
|
-
| Who sees what | Both permissions in the same navigation entry; the manage action hidden without the scope
|
|
33
|
-
| What is denied | Unauthenticated 401, missing permission 403, cross-tenant read returns nothing
|
|
34
|
-
| Failure behaviour | A duplicate returns a stable conflict and changes nothing; bounds return 400
|
|
35
|
-
| Cross-module reads | None. A read of another module goes through its public capability and a declared dependency
|
|
36
|
-
| Screens | One `list` screen with the entity's identifying columns
|
|
37
|
-
| Widgets | None. A count belongs on `dashboard.metrics` only when the request asks for it
|
|
38
|
-
| Settings | None. A number the business may change later is `scope: tenant` with a stated default
|
|
39
|
-
|
|
|
40
|
-
|
|
|
41
|
-
|
|
|
42
|
-
|
|
|
43
|
-
|
|
|
44
|
-
|
|
|
26
|
+
| Decision | Default to propose | Lands in |
|
|
27
|
+
| ---------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------- |
|
|
28
|
+
| Actors | Owner manages, member reads | `permissions`, `invariants` |
|
|
29
|
+
| Entities and fields | One primary entity; `name` required, `maxLength` 120; no field the request did not name | `entities[]` |
|
|
30
|
+
| Uniqueness | The human-facing code is `unique: tenant`; everything else `none` | `entities[].fields[].unique` |
|
|
31
|
+
| States and transitions | `active` and `archived`, every transition behind the manage permission | `entities[].states` |
|
|
32
|
+
| Who sees what | Both permissions in the same navigation entry; the manage action hidden without the scope | `permissions`, `screens[]`, `invariants` |
|
|
33
|
+
| What is denied | Unauthenticated 401, missing permission 403, cross-tenant read returns nothing | `acceptanceScenarios` |
|
|
34
|
+
| Failure behaviour | A duplicate returns a stable conflict and changes nothing; bounds return 400 | `invariants`, `acceptanceScenarios` |
|
|
35
|
+
| Cross-module reads | None. A read of another module goes through its public capability and a declared dependency | `dependencies`, `dataOwnership` |
|
|
36
|
+
| Screens | One `list` screen with the entity's identifying columns | `screens[]` |
|
|
37
|
+
| Widgets | None. A count belongs on `dashboard.metrics` only when the request asks for it | `widgets[]` |
|
|
38
|
+
| Settings | None. A number the business may change later is `scope: tenant` with a stated default | `settings[]` |
|
|
39
|
+
| Feature flags | Ask for one whenever a change alters behaviour a workspace already relies on, or is hard to undo: `kind: flag`, boolean, `scope: tenant`, a stated default, and the behaviour named in an invariant | `settings[]` |
|
|
40
|
+
| Agent tools | None. A tool is a later phase and `risk` may only be `read` or `workspace-write` | `agentTools[]` |
|
|
41
|
+
| Outside sources | None. A named public source is `research`, with the entity its findings attach to | `research` |
|
|
42
|
+
| Other systems | None. A named system is one `source` adapter per record kind, run on demand | `adapters[]` |
|
|
43
|
+
| Documents | None. A named document is one `templates[]` entry on the record it describes | `templates[]` |
|
|
44
|
+
| Reports | A named report is one provider on `reports.v1` returning tiles and series; a named list export is one `defineListExport`; named records are findable through `search.providers.v1`. None of the three has a specification key, so each needs a decision naming the screen or the `actions[]` entry that registers it | `actions[]`, `invariants` |
|
|
45
|
+
| Out of scope | Every item from the card's gap list the request touched, each with its business decision | `outOfScope[]`, `decisions[]` |
|
|
45
46
|
|
|
46
47
|
A default you propose is still a decision: it goes into `decisions[]` so the operator can see and overturn it, and so the next agent never re-derives it.
|
|
47
48
|
|
|
@@ -89,7 +90,7 @@ A number the case needs (a score, a premium, a price per square metre) is an `ac
|
|
|
89
90
|
- `screens[]`: `{ id, kind: list|record|form|dashboard, entity?, title?, columns?, filters?, navigationGroup? }`. `navigationGroup` is one of the six values on the card.
|
|
90
91
|
- `actions[]`: `{ id, entity?, permission, kind: create|update|delete|custom, risk, idempotent, description }`. `risk: external` is refused by the platform, so an action may not declare it.
|
|
91
92
|
- `widgets[]`: `{ id, slot, entity?, description }`; `slot` is one of the four workspace slots.
|
|
92
|
-
- `settings[]`: `{ key, type: string|integer|boolean|enum, scope: tenant|platform, default?, values?, description }`.
|
|
93
|
+
- `settings[]`: `{ key, type: string|integer|boolean|enum, scope: tenant|platform, kind?: setting|flag, default?, values?, description }`. A `flag` is boolean, `scope: tenant` and carries a default; owners switch it per workspace on the Flags tab of Administration, Modules, and every change is audited. Ask whether the new behaviour ships behind one: a change to something a workspace already does, an outward-facing action, a costly or slow path, or anything a business would want to turn off without a deployment. Say what the module does with the flag off, as an invariant.
|
|
93
94
|
- `agentTools[]`: `{ id, permission, description, risk: read|workspace-write }`.
|
|
94
95
|
- `outOfScope[]`: plain sentences, each naming the gap and the decision taken instead.
|
|
95
96
|
- `decisions[]`: `{ id, question, answer, decidedBy: user|default }`; ids match `^[A-Z][A-Z0-9-]+$`, for example `D-UNIQUE-SKU`.
|