create-mercato-app 0.6.5-develop.4639.1.0416d895fa → 0.6.5-develop.4656.1.5b0d4fd8a6

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.
@@ -154,12 +154,35 @@ When the user asks to **create a new application** or a **new module**, do not i
154
154
  | **Multi-tenant scoping (default for every entity)** | Every tenant-scoped entity MUST include indexed `organization_id` and `tenant_id` columns and every read/write MUST filter by them. The CRUD factory injects scope automatically — do NOT bypass it. For ad-hoc queries use `withScopedPayload` from `@open-mercato/shared/lib/api/scoped` | `.ai/skills/om-data-model-design/SKILL.md`; <https://docs.open-mercato.dev/architecture/system-overview> |
155
155
  | **Encryption maps for sensitive data** | Declare a module-level `<module>/encryption.ts` exporting `defaultEncryptionMaps: ModuleEncryptionMap[]` from `@open-mercato/shared/modules/encryption`. Read encrypted columns via `findWithDecryption` / `findOneWithDecryption` from `@open-mercato/shared/lib/encryption/find`. NEVER hand-roll AES/KMS, NEVER use `em.find` on encrypted columns | `.ai/skills/om-data-model-design/SKILL.md` → Sensitive Data and Encryption Maps; <https://docs.open-mercato.dev/user-guide/encryption> |
156
156
  | **Cache** | Resolve the cache from DI (`container.resolve('cache')`) — never `new Redis(...)` or raw SQLite. Tag with `tenant:<id>` / `org:<id>` and the entity-type tag so invalidation stays tenant-scoped | `.ai/guides/cache.md`; <https://docs.open-mercato.dev/user-guide/cache-management> |
157
+ | **Entity update safety** | Multi-phase scalar + relation mutations use `withAtomicFlush(em, phases, { transaction: true })` from `@open-mercato/shared/lib/commands/flush` — never interleave `em.find`/`em.findOne` between a scalar mutation and `em.flush()`. Keep `emitCrudSideEffects` + cache invalidation OUTSIDE it (they fire after commit) | See **Entity Update Safety** section below |
157
158
  | **Background workers** | `src/modules/<id>/workers/*.ts` exporting `metadata: { queue, id?, concurrency? }` + default handler. Never spin up custom queues | `.ai/guides/queue.md`; <https://docs.open-mercato.dev/framework/events/queue-workers> |
158
159
  | **Events between modules** | `<module>/events.ts` with `createModuleEvents({ moduleId, events } as const)`. Subscribers in `subscribers/*.ts`. Never call other modules' services directly across module boundaries | `.ai/guides/events.md`; <https://docs.open-mercato.dev/framework/events/overview> |
159
160
  | **i18n (every user-facing string)** | `useT()` client-side from `@open-mercato/shared/lib/i18n/context`, `resolveTranslations()` server-side from `@open-mercato/shared/lib/i18n/server`; keys in `src/i18n/<locale>.json`. Never hard-code labels in components | `.ai/guides/shared.md` → i18n |
160
161
 
161
162
  > Rule of thumb: if you find yourself reaching for raw `fetch`, raw `<form>`, ad-hoc `crypto`, ad-hoc `Redis`, or a manual `m2m` join across modules, stop and check the row above — there is a canonical helper.
162
163
 
164
+ ## Entity Update Safety
165
+
166
+ MikroORM v7 can silently discard pending scalar changes when a query (`em.find`, `em.findOne`, sync helper) runs on the same `EntityManager` between a scalar mutation and `em.flush()`. For any command in `{{PROJECT_NAME}}` that mutates entities across multiple phases:
167
+
168
+ - MUST use `withAtomicFlush(em, phases, { transaction: true })` from `@open-mercato/shared/lib/commands/flush`.
169
+ - NEVER interleave `em.find`/`em.findOne`/sync helpers between a scalar mutation and `em.flush()` on the same `EntityManager` without `withAtomicFlush` — the UPDATE is dropped.
170
+ - Keep `emitCrudSideEffects` (and `emitCrudUndoSideEffects`) AND cache invalidation OUTSIDE the `withAtomicFlush` block — they fire only AFTER the DB write commits.
171
+
172
+ ```typescript
173
+ import { withAtomicFlush } from '@open-mercato/shared/lib/commands/flush'
174
+
175
+ await withAtomicFlush(em, [
176
+ () => { record.name = 'New Name'; record.status = 'active' },
177
+ () => syncEntityTags(em, record, tags),
178
+ ], { transaction: true, label: '<module>.<command>' })
179
+
180
+ // Side effects + cache invalidation AFTER the atomic flush (post-commit)
181
+ await emitCrudSideEffects({ /* ... */ })
182
+ ```
183
+
184
+ Because invalidation runs post-commit and the query-index read-projection tail (search tokens, vectors, fulltext, coverage) converges asynchronously, reads can briefly see a convergence window after a write. An opt-in env flag, `OM_CACHE_SAFETY_ALWAYS_CONSISTENT` (default OFF, backward compatible), is planned to make that tail converge synchronously on write at a write-latency cost — treat it as opt-in/forthcoming, not on by default.
185
+
163
186
  ## CRITICAL rules — always follow without exception
164
187
 
165
188
  1. **Entity classes live in `src/modules/<module>/data/entities.ts` and MUST import decorators from `@mikro-orm/decorators/legacy`.** Start there for every schema change.
@@ -25,6 +25,8 @@
25
25
  - [ ] UUID primary keys with standard columns (`id`, `created_at`, `updated_at`)
26
26
  - [ ] Soft delete via `deleted_at` where applicable
27
27
  - [ ] Atomic transactions for multi-step writes
28
+ - [ ] `withAtomicFlush(em, phases, { transaction: true })` used when mutating across phases that include queries on the same `EntityManager`
29
+ - [ ] No `em.find`/`em.findOne`/sync helpers between a scalar mutation and `em.flush()` without `withAtomicFlush`
28
30
 
29
31
  ## 4. API Routes
30
32
 
@@ -42,6 +44,7 @@
42
44
  - [ ] Workers export `metadata` with `{ queue, id?, concurrency? }`
43
45
  - [ ] All mutations implemented as commands with undo logic
44
46
  - [ ] Side effects outside `withAtomicFlush`
47
+ - [ ] Cache invalidation and side effects (`emitCrudSideEffects`) fire AFTER the DB write commits — never inside the `withAtomicFlush` block
45
48
 
46
49
  ## 6. UI & Backend Pages
47
50
 
@@ -443,6 +443,8 @@ const items = await em.find(Entity, {
443
443
  })
444
444
  ```
445
445
 
446
+ > **Multi-phase or relation-syncing writes:** the bare `em.flush()` above is fine for a single scalar update. As soon as a write mutates across multiple phases or runs a query (`em.find`/`em.findOne`/sync helper) between a scalar mutation and the flush, switch to `withAtomicFlush(em, phases, { transaction: true })` from `@open-mercato/shared/lib/commands/flush` — MikroORM v7 silently drops the scalar UPDATE otherwise. Never query between scalar mutations and flush; keep side effects + cache invalidation outside the flush (after commit).
447
+
446
448
  ### Audit/History Table
447
449
 
448
450
  For tracking changes to an entity:
@@ -7,7 +7,7 @@
7
7
  | Module ID | plural, snake_case | `fleet_vehicles`, `loyalty_points` |
8
8
  | Module folder | same as module ID | `src/modules/fleet_vehicles/` |
9
9
  | Entity class | PascalCase, singular | `FleetVehicle`, `LoyaltyPoint` |
10
- | Entity file | PascalCase, singular | `entities/FleetVehicle.ts` |
10
+ | Entity file | single `data/entities.ts` (one class per entity) | `data/entities.ts` → `class FleetVehicle` |
11
11
  | Table name | plural, snake_case | `fleet_vehicles`, `loyalty_points` |
12
12
  | Column name | snake_case | `vehicle_type`, `point_balance` |
13
13
 
@@ -17,7 +17,7 @@
17
17
  |---------|-----------|---------|
18
18
  | JS/TS fields | camelCase | `vehicleType`, `pointBalance` |
19
19
  | Event ID | `module.entity.action` (dots, singular entity, past tense) | `fleet_vehicles.vehicle.created` |
20
- | Feature ID | `module.action` | `fleet_vehicles.view`, `fleet_vehicles.create` |
20
+ | Feature ID | `module.entity.action` (per-entity; use `view` / `manage`) | `fleet_vehicles.vehicle.view`, `fleet_vehicles.vehicle.manage` |
21
21
  | Enricher ID | `module.enricher-name` | `fleet_vehicles.maintenance-stats` |
22
22
  | Widget ID | `module.injection.widget-name` | `fleet_vehicles.injection.status-column` |
23
23
  | Interceptor ID | `module.interceptor-name` | `fleet_vehicles.validate-vin` |
@@ -39,12 +39,11 @@ deleted_at: Date | null // Soft delete timestamp
39
39
 
40
40
  ## API Routes
41
41
 
42
- | Method | File Path | URL |
43
- |--------|----------|-----|
44
- | GET | `api/get/<entities>.ts` | `GET /api/<module>/<entities>` |
45
- | POST | `api/post/<entities>.ts` | `POST /api/<module>/<entities>` |
46
- | PUT | `api/put/<entities>.ts` | `PUT /api/<module>/<entities>` |
47
- | DELETE | `api/delete/<entities>.ts` | `DELETE /api/<module>/<entities>` |
42
+ All HTTP methods live in a **single** `api/<entities>/route.ts` that exports named handlers `{ GET, POST, PUT, DELETE }` + `metadata` + `openApi` (not separate `api/get/`, `api/post/` files).
43
+
44
+ | File Path | Methods | URL |
45
+ |-----------|---------|-----|
46
+ | `api/<entities>/route.ts` | `GET` / `POST` / `PUT` / `DELETE` | `/api/<module>/<entities>` |
48
47
 
49
48
  ## Backend Pages
50
49
 
@@ -0,0 +1,202 @@
1
+ ---
2
+ name: om-prepare-issue
3
+ description: Capture a feature the user wants built later without building it now. Researches and writes a spec via the om-spec-writing conventions, ships it as a docs-only spec PR against `develop` (reusing om-auto-create-pr mechanics; `skip-qa`, `documentation`), then opens a tracking GitHub issue that links the spec path and the spec PR so the work can be picked up later with om-auto-fix-github or om-implement-spec. Use for "park this idea", "write a spec and an issue for later", "prepare an issue to build X eventually", "spec it out but don't implement yet".
4
+ ---
5
+
6
+ # Prepare Issue (deferred work)
7
+
8
+ Turn a "we want this eventually" brief into durable, actionable backlog without implementing it. The deliverable is three linked artifacts:
9
+
10
+ 1. A **spec** under `.ai/specs/` written to `om-spec-writing` standards.
11
+ 2. A **docs-only spec PR** against `develop` that adds only that spec file (`documentation`, `skip-qa`).
12
+ 3. A **GitHub issue** to implement the spec, linking both the spec path and the spec PR.
13
+
14
+ This skill is for **deferred** work. It does NOT implement the feature. If the user wants the feature built now, hand off to `om-auto-create-pr` (free-form task) or `om-implement-spec` (after the spec exists) instead.
15
+
16
+ This skill reuses the worktree/branch/commit/label discipline of `.ai/skills/om-auto-create-pr/SKILL.md` for the PR, the spec methodology of `.ai/skills/om-spec-writing/SKILL.md` for the spec, and the issue-claim/linking conventions of `.ai/skills/om-auto-fix-github/SKILL.md` for the tracking issue. Read those before deviating.
17
+
18
+ ## Arguments
19
+
20
+ - `{brief}` (required) — free-form description of the feature to capture. One sentence or several paragraphs.
21
+ - `--slug <kebab-case>` (optional) — override the slug used in the spec filename and branch. Default: derived from the brief.
22
+ - `--enterprise` (optional) — write the spec under `.ai/specs/enterprise/` instead of `.ai/specs/` (commercial scope). Default: OSS scope (`.ai/specs/`).
23
+ - `--priority <low|medium|high|extreme>` (optional) — priority label for the tracking issue. Default: unset (treated as `priority-medium`).
24
+ - `--no-issue` (optional) — write the spec and open the spec PR, but skip issue creation. Use when the user only wants the spec on record.
25
+ - `--force` (optional) — bypass the claim-conflict check when a previous run left a branch or spec file behind.
26
+
27
+ ## Workflow
28
+
29
+ ### 0. Pre-flight and claim
30
+
31
+ Follow `.ai/skills/om-auto-create-pr/SKILL.md` step 0 verbatim. This is new docs work, so the branch MUST use the `feat/` prefix.
32
+
33
+ ```bash
34
+ CURRENT_USER=$(gh api user --jq '.login')
35
+ DATE=$(date -u +%Y-%m-%d)
36
+ SLUG="{slug-or-derived}"
37
+ SPEC_DIR=".ai/specs" # or .ai/specs/enterprise when --enterprise
38
+ SPEC_PATH="${SPEC_DIR}/${DATE}-${SLUG}.md"
39
+ BRANCH="feat/prepare-${SLUG}"
40
+ ```
41
+
42
+ A run is **already in progress** when ANY of these is true (treat as re-entry if the current user owns it, otherwise STOP and ask via `AskUserQuestion` unless `--force`):
43
+
44
+ - A file at `$SPEC_PATH` already exists on `origin/develop` or any remote branch.
45
+ - A remote branch `origin/${BRANCH}` already exists.
46
+ - An open PR already adds `$SPEC_PATH`.
47
+ - An open issue already tracks the same feature (search before creating — see step 5).
48
+
49
+ ### 1. Triage the brief before writing
50
+
51
+ Read enough context to write a credible spec, not to build it:
52
+
53
+ - Match the brief to rows in the root `AGENTS.md` Task Router and read every matching guide (module, UI, search, events, etc.).
54
+ - Check `.ai/specs/` and `.ai/specs/enterprise/` for an existing spec covering the same area. If one exists, extend or supersede it instead of duplicating — confirm the direction with the user via `AskUserQuestion`.
55
+ - Skim `.ai/lessons.md`.
56
+
57
+ Reduce the brief to: goal in one sentence, affected modules/packages, and the rough scope. If a wrong assumption would force a spec rewrite, surface it as an Open Question (see step 2) rather than guessing.
58
+
59
+ ### 2. Write the spec with om-spec-writing
60
+
61
+ Follow `.ai/skills/om-spec-writing/SKILL.md` end to end. Key points for this skill:
62
+
63
+ - Create the spec at `$SPEC_PATH` (`{YYYY-MM-DD}-{kebab-title}.md`, `date` UTC). Enterprise scope goes under `.ai/specs/enterprise/`.
64
+ - Start with a **Skeleton Spec** (TLDR + 2-3 key sections). If critical unknowns exist, add a numbered **Open Questions** block right after the TLDR and **STOP** — ask the user before filling in the rest. This is a hard gate; do not invent answers to architecture-blocking questions.
65
+ - After answers land, expand the spec: Problem Statement, Proposed Solution, Phasing (stories), Implementation Plan (testable Steps), and the architectural concerns the `om-spec-writing` lens requires (singular naming, FK-only cross-module links, `organization_id` scoping, undoability, zod validation, encryption maps for sensitive columns, canonical primitives, Design System tokens, Frontend Architecture Contract when UI is touched).
66
+ - The spec MUST include an **integration coverage** section listing the affected API paths and key UI paths the implementer will need to test (root `AGENTS.md` → Documentation and Specifications requires this for every feature).
67
+ - Apply the `om-spec-writing` Spec Checklist and Final Compliance Review before finalizing.
68
+
69
+ Do NOT write any implementation code, migrations, or module files. The only file this run adds is the spec.
70
+
71
+ ### 3. Isolated worktree, branch, and first commit
72
+
73
+ Follow `.ai/skills/om-auto-create-pr/SKILL.md` steps 4–5 verbatim. Base is always `develop`. Commit the spec as the first commit, then push.
74
+
75
+ ```bash
76
+ git add "$SPEC_PATH"
77
+ git commit -m "docs(spec): add ${SLUG} spec for deferred implementation"
78
+ git push -u origin "$BRANCH"
79
+ ```
80
+
81
+ If the Open Questions gate in step 2 is still unresolved, do not create the worktree or push — resolve the questions with the user first.
82
+
83
+ ### 4. Open the spec PR
84
+
85
+ This is a docs-only / spec-only PR. The minimum validation gate is the docs-only gate from `.ai/skills/om-auto-create-pr/SKILL.md` step 7:
86
+
87
+ - `yarn lint` if it catches markdown/YAML issues.
88
+ - `git diff --check` — no trailing whitespace, no merge markers.
89
+ - A manual re-read of the spec.
90
+
91
+ Never run the full code gate (`yarn test` / `yarn typecheck` / `yarn build:app`) for a spec-only run.
92
+
93
+ Open the PR against `develop`:
94
+
95
+ ```bash
96
+ gh pr create --base develop \
97
+ --title "docs(spec): ${SLUG} — deferred implementation spec" \
98
+ --body-file <(cat <<'EOF'
99
+ ## Goal
100
+ - {one-line feature summary from the brief}
101
+
102
+ ## What this PR adds
103
+ - Spec only: `{SPEC_PATH}`. No implementation, no code, no migrations.
104
+
105
+ ## Why now
106
+ - Captures deferred work as a reviewable spec so it can be implemented later via `om-implement-spec` / `om-auto-fix-github`.
107
+
108
+ ## Tracking issue
109
+ - {issue URL — filled in after step 5, or "see follow-up comment"}
110
+
111
+ ## Backward Compatibility
112
+ - No contract surface changes (spec document only).
113
+ EOF
114
+ )
115
+ ```
116
+
117
+ Apply labels per root `AGENTS.md` PR workflow, each followed by a short explanatory comment (per the `om-auto-create-pr` label-comment rule):
118
+
119
+ - `review` — "PR is ready for code review."
120
+ - `documentation` — "spec-only deliverable under `.ai/specs/`."
121
+ - `skip-qa` — "spec/docs-only; no customer-facing runtime behavior."
122
+
123
+ Never add `needs-qa` to a prepare-issue PR — it adds no runtime behavior to exercise.
124
+
125
+ ### 5. Create the tracking issue
126
+
127
+ Skip this step only when `--no-issue` was passed.
128
+
129
+ First, avoid duplicates — search for an existing tracking issue:
130
+
131
+ ```bash
132
+ gh issue list --state open --search "${SLUG} in:title,body" --json number,title,url
133
+ ```
134
+
135
+ If a credible duplicate exists, link the spec/PR in a comment on that issue instead of opening a new one, and report which issue you reused.
136
+
137
+ Otherwise create the issue. It MUST link the spec path and the spec PR, and name the recommended implementer skill so a future run can pick it up:
138
+
139
+ ```bash
140
+ gh issue create \
141
+ --title "Implement: {feature title}" \
142
+ --label "feature" \
143
+ --body-file <(cat <<'EOF'
144
+ ## Summary
145
+ - {one-line feature summary from the brief}
146
+
147
+ ## Spec
148
+ - Implementation spec: `{SPEC_PATH}`
149
+ - Spec PR: {spec PR URL}
150
+
151
+ ## How to implement
152
+ - Once the spec PR merges, run `/om-implement-spec {SPEC_PATH}` (multi-phase) or `/om-auto-fix-github {thisIssueNumber}` (smaller scope).
153
+ - Do not start implementation until the spec PR is merged into `develop`.
154
+
155
+ ## Scope notes
156
+ - {affected modules/packages}
157
+ - {non-goals captured in the spec}
158
+ EOF
159
+ )
160
+ ```
161
+
162
+ Label rules for the issue:
163
+
164
+ - Always add the `feature` category label (or `refactor` / `bug` when the brief is clearly one of those).
165
+ - Add `enterprise` when `--enterprise` was passed.
166
+ - Add a priority label only when `--priority` was passed (`priority-low` / `priority-medium` / `priority-high` / `priority-extreme`); otherwise leave priority unset (treated as `priority-medium`).
167
+ - Do NOT add pipeline labels (`review`, `qa`, `merge-queue`, …) to the issue — those are PR-only.
168
+
169
+ ### 6. Cross-link the artifacts
170
+
171
+ Make the three artifacts point at each other so the trail is navigable:
172
+
173
+ - Edit the spec PR body (or post a comment) so the `Tracking issue` line resolves to the real issue URL.
174
+ - The issue already links the spec path and spec PR from step 5.
175
+ - Optionally add a one-line reference to the issue at the top of the spec (e.g. `Tracking issue: #{n}`), committed and pushed to the PR branch.
176
+
177
+ ### 7. Cleanup and report back
178
+
179
+ Clean up any worktree you created (per `.ai/skills/om-auto-create-pr/SKILL.md` step 13). Then report:
180
+
181
+ ```text
182
+ prepare-issue: {brief}
183
+ Spec: {SPEC_PATH}
184
+ Spec PR: {url}
185
+ Tracking issue: {url | skipped (--no-issue) | reused #{n}}
186
+ Branch: {branch}
187
+ Status: {spec-and-issue ready | open-questions pending — answer to continue}
188
+ ```
189
+
190
+ If the Open Questions gate is still pending, say so explicitly and list the unanswered questions — do not claim the spec is complete.
191
+
192
+ ## Rules
193
+
194
+ - This skill captures deferred work only. NEVER implement the feature, write module code, or run migrations. The only file added is the spec.
195
+ - Always write the spec with `om-spec-writing` standards, including the Open Questions hard gate and the integration-coverage section.
196
+ - The spec PR is docs-only: run only the docs-only validation gate, never the full code gate.
197
+ - Spec PR labels are `review`, `documentation`, `skip-qa` — never `needs-qa`. Post a short comment after each label.
198
+ - Base branch is always `develop`. Branch uses the `feat/prepare-<slug>` prefix; never `codex/`.
199
+ - The tracking issue MUST link the spec path and the spec PR, and name the recommended implementer skill (`om-implement-spec` / `om-auto-fix-github`).
200
+ - Search for an existing tracking issue before creating a new one; reuse via comment if a credible duplicate exists.
201
+ - Respect existing claims/locks per `om-auto-create-pr` step 0 and `om-auto-fix-github` step 0; never silently clobber another actor's branch, spec file, or issue.
202
+ - Never paste secrets, tokens, or `.env` content into the spec, PR, or issue.
@@ -20,7 +20,7 @@ Every item must be answered in the spec or marked N/A with justification.
20
20
  ## 3. Data Integrity & Security
21
21
 
22
22
  - [ ] Entities include `id`, `organization_id`, `tenant_id`, `created_at`, `updated_at`, `deleted_at`, `is_active`
23
- - [ ] Write operations define transaction boundaries
23
+ - [ ] Write operations define transaction boundaries — specs touching multi-phase scalar + relation writes MUST name `withAtomicFlush({ transaction: true })` and declare that cache invalidation / side effects (`emitCrudSideEffects`) fire AFTER commit (outside the flush block)
24
24
  - [ ] Input validation uses zod schemas
25
25
  - [ ] All user input validated before business logic/persistence
26
26
  - [ ] Auth guards declared per-method in `metadata` (`requireAuth`, `requireFeatures`) — never legacy top-level `export const requireAuth`
@@ -135,4 +135,4 @@ await withAtomicFlush(em, [
135
135
  await emitCrudSideEffects({ ... })
136
136
  ```
137
137
 
138
- Never run `em.find`/`em.findOne` between scalar mutations and `em.flush()` without `withAtomicFlush` — changes will be silently lost.
138
+ Never run `em.find`/`em.findOne` between scalar mutations and `em.flush()` without `withAtomicFlush` — changes will be silently lost. Cache invalidation must also stay outside `withAtomicFlush` and fire after commit, so the opt-in `OM_CACHE_SAFETY_ALWAYS_CONSISTENT` mode (default OFF) never serves stale or partially-committed reads.
@@ -154,12 +154,35 @@ When the user asks to **create a new application** or a **new module**, do not i
154
154
  | **Multi-tenant scoping (default for every entity)** | Every tenant-scoped entity MUST include indexed `organization_id` and `tenant_id` columns and every read/write MUST filter by them. The CRUD factory injects scope automatically — do NOT bypass it. For ad-hoc queries use `withScopedPayload` from `@open-mercato/shared/lib/api/scoped` | `.ai/skills/om-data-model-design/SKILL.md`; <https://docs.open-mercato.dev/architecture/system-overview> |
155
155
  | **Encryption maps for sensitive data** | Declare a module-level `<module>/encryption.ts` exporting `defaultEncryptionMaps: ModuleEncryptionMap[]` from `@open-mercato/shared/modules/encryption`. Read encrypted columns via `findWithDecryption` / `findOneWithDecryption` from `@open-mercato/shared/lib/encryption/find`. NEVER hand-roll AES/KMS, NEVER use `em.find` on encrypted columns | `.ai/skills/om-data-model-design/SKILL.md` → Sensitive Data and Encryption Maps; <https://docs.open-mercato.dev/user-guide/encryption> |
156
156
  | **Cache** | Resolve the cache from DI (`container.resolve('cache')`) — never `new Redis(...)` or raw SQLite. Tag with `tenant:<id>` / `org:<id>` and the entity-type tag so invalidation stays tenant-scoped | `.ai/guides/cache.md`; <https://docs.open-mercato.dev/user-guide/cache-management> |
157
+ | **Entity update safety** | Multi-phase scalar + relation mutations use `withAtomicFlush(em, phases, { transaction: true })` from `@open-mercato/shared/lib/commands/flush` — never interleave `em.find`/`em.findOne` between a scalar mutation and `em.flush()`. Keep `emitCrudSideEffects` + cache invalidation OUTSIDE it (they fire after commit) | See **Entity Update Safety** section below |
157
158
  | **Background workers** | `src/modules/<id>/workers/*.ts` exporting `metadata: { queue, id?, concurrency? }` + default handler. Never spin up custom queues | `.ai/guides/queue.md`; <https://docs.open-mercato.dev/framework/events/queue-workers> |
158
159
  | **Events between modules** | `<module>/events.ts` with `createModuleEvents({ moduleId, events } as const)`. Subscribers in `subscribers/*.ts`. Never call other modules' services directly across module boundaries | `.ai/guides/events.md`; <https://docs.open-mercato.dev/framework/events/overview> |
159
160
  | **i18n (every user-facing string)** | `useT()` client-side from `@open-mercato/shared/lib/i18n/context`, `resolveTranslations()` server-side from `@open-mercato/shared/lib/i18n/server`; keys in `src/i18n/<locale>.json`. Never hard-code labels in components | `.ai/guides/shared.md` → i18n |
160
161
 
161
162
  > Rule of thumb: if you find yourself reaching for raw `fetch`, raw `<form>`, ad-hoc `crypto`, ad-hoc `Redis`, or a manual `m2m` join across modules, stop and check the row above — there is a canonical helper.
162
163
 
164
+ ## Entity Update Safety
165
+
166
+ MikroORM v7 can silently discard pending scalar changes when a query (`em.find`, `em.findOne`, sync helper) runs on the same `EntityManager` between a scalar mutation and `em.flush()`. For any command in `{{PROJECT_NAME}}` that mutates entities across multiple phases:
167
+
168
+ - MUST use `withAtomicFlush(em, phases, { transaction: true })` from `@open-mercato/shared/lib/commands/flush`.
169
+ - NEVER interleave `em.find`/`em.findOne`/sync helpers between a scalar mutation and `em.flush()` on the same `EntityManager` without `withAtomicFlush` — the UPDATE is dropped.
170
+ - Keep `emitCrudSideEffects` (and `emitCrudUndoSideEffects`) AND cache invalidation OUTSIDE the `withAtomicFlush` block — they fire only AFTER the DB write commits.
171
+
172
+ ```typescript
173
+ import { withAtomicFlush } from '@open-mercato/shared/lib/commands/flush'
174
+
175
+ await withAtomicFlush(em, [
176
+ () => { record.name = 'New Name'; record.status = 'active' },
177
+ () => syncEntityTags(em, record, tags),
178
+ ], { transaction: true, label: '<module>.<command>' })
179
+
180
+ // Side effects + cache invalidation AFTER the atomic flush (post-commit)
181
+ await emitCrudSideEffects({ /* ... */ })
182
+ ```
183
+
184
+ Because invalidation runs post-commit and the query-index read-projection tail (search tokens, vectors, fulltext, coverage) converges asynchronously, reads can briefly see a convergence window after a write. An opt-in env flag, `OM_CACHE_SAFETY_ALWAYS_CONSISTENT` (default OFF, backward compatible), is planned to make that tail converge synchronously on write at a write-latency cost — treat it as opt-in/forthcoming, not on by default.
185
+
163
186
  ## CRITICAL rules — always follow without exception
164
187
 
165
188
  1. **Entity classes live in `src/modules/<module>/data/entities.ts` and MUST import decorators from `@mikro-orm/decorators/legacy`.** Start there for every schema change.
@@ -25,6 +25,8 @@
25
25
  - [ ] UUID primary keys with standard columns (`id`, `created_at`, `updated_at`)
26
26
  - [ ] Soft delete via `deleted_at` where applicable
27
27
  - [ ] Atomic transactions for multi-step writes
28
+ - [ ] `withAtomicFlush(em, phases, { transaction: true })` used when mutating across phases that include queries on the same `EntityManager`
29
+ - [ ] No `em.find`/`em.findOne`/sync helpers between a scalar mutation and `em.flush()` without `withAtomicFlush`
28
30
 
29
31
  ## 4. API Routes
30
32
 
@@ -42,6 +44,7 @@
42
44
  - [ ] Workers export `metadata` with `{ queue, id?, concurrency? }`
43
45
  - [ ] All mutations implemented as commands with undo logic
44
46
  - [ ] Side effects outside `withAtomicFlush`
47
+ - [ ] Cache invalidation and side effects (`emitCrudSideEffects`) fire AFTER the DB write commits — never inside the `withAtomicFlush` block
45
48
 
46
49
  ## 6. UI & Backend Pages
47
50
 
@@ -443,6 +443,8 @@ const items = await em.find(Entity, {
443
443
  })
444
444
  ```
445
445
 
446
+ > **Multi-phase or relation-syncing writes:** the bare `em.flush()` above is fine for a single scalar update. As soon as a write mutates across multiple phases or runs a query (`em.find`/`em.findOne`/sync helper) between a scalar mutation and the flush, switch to `withAtomicFlush(em, phases, { transaction: true })` from `@open-mercato/shared/lib/commands/flush` — MikroORM v7 silently drops the scalar UPDATE otherwise. Never query between scalar mutations and flush; keep side effects + cache invalidation outside the flush (after commit).
447
+
446
448
  ### Audit/History Table
447
449
 
448
450
  For tracking changes to an entity:
@@ -7,7 +7,7 @@
7
7
  | Module ID | plural, snake_case | `fleet_vehicles`, `loyalty_points` |
8
8
  | Module folder | same as module ID | `src/modules/fleet_vehicles/` |
9
9
  | Entity class | PascalCase, singular | `FleetVehicle`, `LoyaltyPoint` |
10
- | Entity file | PascalCase, singular | `entities/FleetVehicle.ts` |
10
+ | Entity file | single `data/entities.ts` (one class per entity) | `data/entities.ts` → `class FleetVehicle` |
11
11
  | Table name | plural, snake_case | `fleet_vehicles`, `loyalty_points` |
12
12
  | Column name | snake_case | `vehicle_type`, `point_balance` |
13
13
 
@@ -17,7 +17,7 @@
17
17
  |---------|-----------|---------|
18
18
  | JS/TS fields | camelCase | `vehicleType`, `pointBalance` |
19
19
  | Event ID | `module.entity.action` (dots, singular entity, past tense) | `fleet_vehicles.vehicle.created` |
20
- | Feature ID | `module.action` | `fleet_vehicles.view`, `fleet_vehicles.create` |
20
+ | Feature ID | `module.entity.action` (per-entity; use `view` / `manage`) | `fleet_vehicles.vehicle.view`, `fleet_vehicles.vehicle.manage` |
21
21
  | Enricher ID | `module.enricher-name` | `fleet_vehicles.maintenance-stats` |
22
22
  | Widget ID | `module.injection.widget-name` | `fleet_vehicles.injection.status-column` |
23
23
  | Interceptor ID | `module.interceptor-name` | `fleet_vehicles.validate-vin` |
@@ -39,12 +39,11 @@ deleted_at: Date | null // Soft delete timestamp
39
39
 
40
40
  ## API Routes
41
41
 
42
- | Method | File Path | URL |
43
- |--------|----------|-----|
44
- | GET | `api/get/<entities>.ts` | `GET /api/<module>/<entities>` |
45
- | POST | `api/post/<entities>.ts` | `POST /api/<module>/<entities>` |
46
- | PUT | `api/put/<entities>.ts` | `PUT /api/<module>/<entities>` |
47
- | DELETE | `api/delete/<entities>.ts` | `DELETE /api/<module>/<entities>` |
42
+ All HTTP methods live in a **single** `api/<entities>/route.ts` that exports named handlers `{ GET, POST, PUT, DELETE }` + `metadata` + `openApi` (not separate `api/get/`, `api/post/` files).
43
+
44
+ | File Path | Methods | URL |
45
+ |-----------|---------|-----|
46
+ | `api/<entities>/route.ts` | `GET` / `POST` / `PUT` / `DELETE` | `/api/<module>/<entities>` |
48
47
 
49
48
  ## Backend Pages
50
49
 
@@ -0,0 +1,202 @@
1
+ ---
2
+ name: om-prepare-issue
3
+ description: Capture a feature the user wants built later without building it now. Researches and writes a spec via the om-spec-writing conventions, ships it as a docs-only spec PR against `develop` (reusing om-auto-create-pr mechanics; `skip-qa`, `documentation`), then opens a tracking GitHub issue that links the spec path and the spec PR so the work can be picked up later with om-auto-fix-github or om-implement-spec. Use for "park this idea", "write a spec and an issue for later", "prepare an issue to build X eventually", "spec it out but don't implement yet".
4
+ ---
5
+
6
+ # Prepare Issue (deferred work)
7
+
8
+ Turn a "we want this eventually" brief into durable, actionable backlog without implementing it. The deliverable is three linked artifacts:
9
+
10
+ 1. A **spec** under `.ai/specs/` written to `om-spec-writing` standards.
11
+ 2. A **docs-only spec PR** against `develop` that adds only that spec file (`documentation`, `skip-qa`).
12
+ 3. A **GitHub issue** to implement the spec, linking both the spec path and the spec PR.
13
+
14
+ This skill is for **deferred** work. It does NOT implement the feature. If the user wants the feature built now, hand off to `om-auto-create-pr` (free-form task) or `om-implement-spec` (after the spec exists) instead.
15
+
16
+ This skill reuses the worktree/branch/commit/label discipline of `.ai/skills/om-auto-create-pr/SKILL.md` for the PR, the spec methodology of `.ai/skills/om-spec-writing/SKILL.md` for the spec, and the issue-claim/linking conventions of `.ai/skills/om-auto-fix-github/SKILL.md` for the tracking issue. Read those before deviating.
17
+
18
+ ## Arguments
19
+
20
+ - `{brief}` (required) — free-form description of the feature to capture. One sentence or several paragraphs.
21
+ - `--slug <kebab-case>` (optional) — override the slug used in the spec filename and branch. Default: derived from the brief.
22
+ - `--enterprise` (optional) — write the spec under `.ai/specs/enterprise/` instead of `.ai/specs/` (commercial scope). Default: OSS scope (`.ai/specs/`).
23
+ - `--priority <low|medium|high|extreme>` (optional) — priority label for the tracking issue. Default: unset (treated as `priority-medium`).
24
+ - `--no-issue` (optional) — write the spec and open the spec PR, but skip issue creation. Use when the user only wants the spec on record.
25
+ - `--force` (optional) — bypass the claim-conflict check when a previous run left a branch or spec file behind.
26
+
27
+ ## Workflow
28
+
29
+ ### 0. Pre-flight and claim
30
+
31
+ Follow `.ai/skills/om-auto-create-pr/SKILL.md` step 0 verbatim. This is new docs work, so the branch MUST use the `feat/` prefix.
32
+
33
+ ```bash
34
+ CURRENT_USER=$(gh api user --jq '.login')
35
+ DATE=$(date -u +%Y-%m-%d)
36
+ SLUG="{slug-or-derived}"
37
+ SPEC_DIR=".ai/specs" # or .ai/specs/enterprise when --enterprise
38
+ SPEC_PATH="${SPEC_DIR}/${DATE}-${SLUG}.md"
39
+ BRANCH="feat/prepare-${SLUG}"
40
+ ```
41
+
42
+ A run is **already in progress** when ANY of these is true (treat as re-entry if the current user owns it, otherwise STOP and ask via `AskUserQuestion` unless `--force`):
43
+
44
+ - A file at `$SPEC_PATH` already exists on `origin/develop` or any remote branch.
45
+ - A remote branch `origin/${BRANCH}` already exists.
46
+ - An open PR already adds `$SPEC_PATH`.
47
+ - An open issue already tracks the same feature (search before creating — see step 5).
48
+
49
+ ### 1. Triage the brief before writing
50
+
51
+ Read enough context to write a credible spec, not to build it:
52
+
53
+ - Match the brief to rows in the root `AGENTS.md` Task Router and read every matching guide (module, UI, search, events, etc.).
54
+ - Check `.ai/specs/` and `.ai/specs/enterprise/` for an existing spec covering the same area. If one exists, extend or supersede it instead of duplicating — confirm the direction with the user via `AskUserQuestion`.
55
+ - Skim `.ai/lessons.md`.
56
+
57
+ Reduce the brief to: goal in one sentence, affected modules/packages, and the rough scope. If a wrong assumption would force a spec rewrite, surface it as an Open Question (see step 2) rather than guessing.
58
+
59
+ ### 2. Write the spec with om-spec-writing
60
+
61
+ Follow `.ai/skills/om-spec-writing/SKILL.md` end to end. Key points for this skill:
62
+
63
+ - Create the spec at `$SPEC_PATH` (`{YYYY-MM-DD}-{kebab-title}.md`, `date` UTC). Enterprise scope goes under `.ai/specs/enterprise/`.
64
+ - Start with a **Skeleton Spec** (TLDR + 2-3 key sections). If critical unknowns exist, add a numbered **Open Questions** block right after the TLDR and **STOP** — ask the user before filling in the rest. This is a hard gate; do not invent answers to architecture-blocking questions.
65
+ - After answers land, expand the spec: Problem Statement, Proposed Solution, Phasing (stories), Implementation Plan (testable Steps), and the architectural concerns the `om-spec-writing` lens requires (singular naming, FK-only cross-module links, `organization_id` scoping, undoability, zod validation, encryption maps for sensitive columns, canonical primitives, Design System tokens, Frontend Architecture Contract when UI is touched).
66
+ - The spec MUST include an **integration coverage** section listing the affected API paths and key UI paths the implementer will need to test (root `AGENTS.md` → Documentation and Specifications requires this for every feature).
67
+ - Apply the `om-spec-writing` Spec Checklist and Final Compliance Review before finalizing.
68
+
69
+ Do NOT write any implementation code, migrations, or module files. The only file this run adds is the spec.
70
+
71
+ ### 3. Isolated worktree, branch, and first commit
72
+
73
+ Follow `.ai/skills/om-auto-create-pr/SKILL.md` steps 4–5 verbatim. Base is always `develop`. Commit the spec as the first commit, then push.
74
+
75
+ ```bash
76
+ git add "$SPEC_PATH"
77
+ git commit -m "docs(spec): add ${SLUG} spec for deferred implementation"
78
+ git push -u origin "$BRANCH"
79
+ ```
80
+
81
+ If the Open Questions gate in step 2 is still unresolved, do not create the worktree or push — resolve the questions with the user first.
82
+
83
+ ### 4. Open the spec PR
84
+
85
+ This is a docs-only / spec-only PR. The minimum validation gate is the docs-only gate from `.ai/skills/om-auto-create-pr/SKILL.md` step 7:
86
+
87
+ - `yarn lint` if it catches markdown/YAML issues.
88
+ - `git diff --check` — no trailing whitespace, no merge markers.
89
+ - A manual re-read of the spec.
90
+
91
+ Never run the full code gate (`yarn test` / `yarn typecheck` / `yarn build:app`) for a spec-only run.
92
+
93
+ Open the PR against `develop`:
94
+
95
+ ```bash
96
+ gh pr create --base develop \
97
+ --title "docs(spec): ${SLUG} — deferred implementation spec" \
98
+ --body-file <(cat <<'EOF'
99
+ ## Goal
100
+ - {one-line feature summary from the brief}
101
+
102
+ ## What this PR adds
103
+ - Spec only: `{SPEC_PATH}`. No implementation, no code, no migrations.
104
+
105
+ ## Why now
106
+ - Captures deferred work as a reviewable spec so it can be implemented later via `om-implement-spec` / `om-auto-fix-github`.
107
+
108
+ ## Tracking issue
109
+ - {issue URL — filled in after step 5, or "see follow-up comment"}
110
+
111
+ ## Backward Compatibility
112
+ - No contract surface changes (spec document only).
113
+ EOF
114
+ )
115
+ ```
116
+
117
+ Apply labels per root `AGENTS.md` PR workflow, each followed by a short explanatory comment (per the `om-auto-create-pr` label-comment rule):
118
+
119
+ - `review` — "PR is ready for code review."
120
+ - `documentation` — "spec-only deliverable under `.ai/specs/`."
121
+ - `skip-qa` — "spec/docs-only; no customer-facing runtime behavior."
122
+
123
+ Never add `needs-qa` to a prepare-issue PR — it adds no runtime behavior to exercise.
124
+
125
+ ### 5. Create the tracking issue
126
+
127
+ Skip this step only when `--no-issue` was passed.
128
+
129
+ First, avoid duplicates — search for an existing tracking issue:
130
+
131
+ ```bash
132
+ gh issue list --state open --search "${SLUG} in:title,body" --json number,title,url
133
+ ```
134
+
135
+ If a credible duplicate exists, link the spec/PR in a comment on that issue instead of opening a new one, and report which issue you reused.
136
+
137
+ Otherwise create the issue. It MUST link the spec path and the spec PR, and name the recommended implementer skill so a future run can pick it up:
138
+
139
+ ```bash
140
+ gh issue create \
141
+ --title "Implement: {feature title}" \
142
+ --label "feature" \
143
+ --body-file <(cat <<'EOF'
144
+ ## Summary
145
+ - {one-line feature summary from the brief}
146
+
147
+ ## Spec
148
+ - Implementation spec: `{SPEC_PATH}`
149
+ - Spec PR: {spec PR URL}
150
+
151
+ ## How to implement
152
+ - Once the spec PR merges, run `/om-implement-spec {SPEC_PATH}` (multi-phase) or `/om-auto-fix-github {thisIssueNumber}` (smaller scope).
153
+ - Do not start implementation until the spec PR is merged into `develop`.
154
+
155
+ ## Scope notes
156
+ - {affected modules/packages}
157
+ - {non-goals captured in the spec}
158
+ EOF
159
+ )
160
+ ```
161
+
162
+ Label rules for the issue:
163
+
164
+ - Always add the `feature` category label (or `refactor` / `bug` when the brief is clearly one of those).
165
+ - Add `enterprise` when `--enterprise` was passed.
166
+ - Add a priority label only when `--priority` was passed (`priority-low` / `priority-medium` / `priority-high` / `priority-extreme`); otherwise leave priority unset (treated as `priority-medium`).
167
+ - Do NOT add pipeline labels (`review`, `qa`, `merge-queue`, …) to the issue — those are PR-only.
168
+
169
+ ### 6. Cross-link the artifacts
170
+
171
+ Make the three artifacts point at each other so the trail is navigable:
172
+
173
+ - Edit the spec PR body (or post a comment) so the `Tracking issue` line resolves to the real issue URL.
174
+ - The issue already links the spec path and spec PR from step 5.
175
+ - Optionally add a one-line reference to the issue at the top of the spec (e.g. `Tracking issue: #{n}`), committed and pushed to the PR branch.
176
+
177
+ ### 7. Cleanup and report back
178
+
179
+ Clean up any worktree you created (per `.ai/skills/om-auto-create-pr/SKILL.md` step 13). Then report:
180
+
181
+ ```text
182
+ prepare-issue: {brief}
183
+ Spec: {SPEC_PATH}
184
+ Spec PR: {url}
185
+ Tracking issue: {url | skipped (--no-issue) | reused #{n}}
186
+ Branch: {branch}
187
+ Status: {spec-and-issue ready | open-questions pending — answer to continue}
188
+ ```
189
+
190
+ If the Open Questions gate is still pending, say so explicitly and list the unanswered questions — do not claim the spec is complete.
191
+
192
+ ## Rules
193
+
194
+ - This skill captures deferred work only. NEVER implement the feature, write module code, or run migrations. The only file added is the spec.
195
+ - Always write the spec with `om-spec-writing` standards, including the Open Questions hard gate and the integration-coverage section.
196
+ - The spec PR is docs-only: run only the docs-only validation gate, never the full code gate.
197
+ - Spec PR labels are `review`, `documentation`, `skip-qa` — never `needs-qa`. Post a short comment after each label.
198
+ - Base branch is always `develop`. Branch uses the `feat/prepare-<slug>` prefix; never `codex/`.
199
+ - The tracking issue MUST link the spec path and the spec PR, and name the recommended implementer skill (`om-implement-spec` / `om-auto-fix-github`).
200
+ - Search for an existing tracking issue before creating a new one; reuse via comment if a credible duplicate exists.
201
+ - Respect existing claims/locks per `om-auto-create-pr` step 0 and `om-auto-fix-github` step 0; never silently clobber another actor's branch, spec file, or issue.
202
+ - Never paste secrets, tokens, or `.env` content into the spec, PR, or issue.
@@ -20,7 +20,7 @@ Every item must be answered in the spec or marked N/A with justification.
20
20
  ## 3. Data Integrity & Security
21
21
 
22
22
  - [ ] Entities include `id`, `organization_id`, `tenant_id`, `created_at`, `updated_at`, `deleted_at`, `is_active`
23
- - [ ] Write operations define transaction boundaries
23
+ - [ ] Write operations define transaction boundaries — specs touching multi-phase scalar + relation writes MUST name `withAtomicFlush({ transaction: true })` and declare that cache invalidation / side effects (`emitCrudSideEffects`) fire AFTER commit (outside the flush block)
24
24
  - [ ] Input validation uses zod schemas
25
25
  - [ ] All user input validated before business logic/persistence
26
26
  - [ ] Auth guards declared per-method in `metadata` (`requireAuth`, `requireFeatures`) — never legacy top-level `export const requireAuth`
package/dist/index.js CHANGED
@@ -571,7 +571,8 @@ function generateShared(config) {
571
571
  "om-auto-create-pr-loop",
572
572
  "om-auto-continue-pr-loop",
573
573
  "om-auto-review-pr",
574
- "om-auto-fix-github"
574
+ "om-auto-fix-github",
575
+ "om-prepare-issue"
575
576
  ]) {
576
577
  if (!existsSync2(join3(AGENTIC_DIR, "ai", "skills", autoSkill, "SKILL.md"))) {
577
578
  continue;
@@ -836,6 +837,7 @@ function printSummary(selectedIds) {
836
837
  console.log(" /om-auto-continue-pr <PR#> \u2014 resume an in-progress agent PR");
837
838
  console.log(" /om-auto-review-pr <PR#> \u2014 automated code review (optional autofix)");
838
839
  console.log(" /om-auto-fix-github <issue#> \u2014 fix a GitHub issue and open a PR");
840
+ console.log(" /om-prepare-issue <idea> \u2014 spec out deferred work + open a tracking issue (no build)");
839
841
  console.log(" /om-trim-unused-modules \u2014 slim classic-mode defaults after adding your own module");
840
842
  console.log(" See .ai/skills/om-auto-create-pr/STANDALONE.md for portability notes");
841
843
  console.log(" (base-branch discovery, opt-in pipeline labels, script probing).");
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "create-mercato-app",
3
- "version": "0.6.5-develop.4639.1.0416d895fa",
3
+ "version": "0.6.5-develop.4656.1.5b0d4fd8a6",
4
4
  "type": "module",
5
5
  "description": "Create a new Open Mercato application",
6
6
  "main": "./dist/index.js",
@@ -268,6 +268,28 @@ Practical consequences:
268
268
  4. Re-apply (`yarn db:migrate`) and commit.
269
269
  - Never hand-edit historical migrations that have shipped; add a **new** migration that performs the correction instead.
270
270
 
271
+ ## Entity Update Safety / Transaction Safety
272
+
273
+ MikroORM v7 can silently drop a pending scalar UPDATE when a query (`em.find`, `em.findOne`, or a sync helper) runs on the same `EntityManager` between the scalar mutation and `em.flush()`. For commands that mutate entities across multiple phases:
274
+
275
+ - Use `withAtomicFlush(em, phases, { transaction: true })` from `@open-mercato/shared/lib/commands/flush` for multi-phase scalar + relation mutations.
276
+ - Never interleave `em.find`/`em.findOne` between a scalar mutation and `em.flush()` without `withAtomicFlush`.
277
+ - Keep `emitCrudSideEffects` and cache invalidation OUTSIDE the `withAtomicFlush` block — they run only AFTER the DB write commits.
278
+
279
+ ```ts
280
+ import { withAtomicFlush } from '@open-mercato/shared/lib/commands/flush'
281
+
282
+ await withAtomicFlush(em, [
283
+ () => { record.name = 'New Name'; record.status = 'active' },
284
+ () => syncEntityTags(em, record, tags),
285
+ ], { transaction: true })
286
+
287
+ // Cache invalidation + side effects AFTER commit
288
+ await emitCrudSideEffects({ /* ... */ })
289
+ ```
290
+
291
+ Cache invalidation running post-commit means reads can briefly observe a query-index convergence window. The opt-in `OM_CACHE_SAFETY_ALWAYS_CONSISTENT` env flag (default OFF, backward compatible) is planned to make the query-index read-projection tail converge synchronously on write, at a write-latency cost — opt-in/forthcoming, not on by default.
292
+
271
293
  ## AI Assistant — adding agents, tools, UI parts, and overrides
272
294
 
273
295
  Standalone apps consume the AI framework from `@open-mercato/ai-assistant` (in `node_modules/`). The same conventions used in the monorepo apply here: