---
name: audit-dev-core
description: >
  Audit the Phase 0 (Core Foundation Seed) bundle against what scaffold-core-seed EMITS — deterministic CLI
  (DEV-CORE-000/001/005/006/008/009/010: the 6 per-app providers under
  Seeding/Applications/{AppPascal}/Core/, the per-app `<<< CORE-SEED-DI-{AppPascal} >>>` block, test users
  actually created, the `.smartstack/core-seed/{app}.state.json` snapshot, the CoreSeedScriptRunner + its
  registration + the csproj EmbeddedResource block, the seed-owned AssignedBy marker, and a scan that inspected
  something). After-Phase-0 gate of /ba-develop. DEV-CORE-002/003/004/007 stay prose (the gates.md pre-entry
  greps run 002/004); DEV-CORE-011 (rbac.md ⇄ seed grant parity, BOTH directions) runs in
  `derive-rbac-grants --mode check`.
group: D
phase: devCore
kind: audit
audit_only: true
section_label: 'AUDIT-DEV-CORE (rules to apply against the per-app Core bundle generated by scaffold-core-seed)'
allowed-tools: [Read, Glob, Grep, Bash]  # Bash: CLI invocation
---

# audit-dev-core — Phase 0 Code-vs-PRD Audit

## Context

You are auditing the output of Phase 0 (Core Foundation Seed) of
`ba-develop`. This phase invokes the deterministic `scaffold-core-seed` CLI,
which writes — PER APPLICATION declared in the spec — 6 `IClientSeedDataProvider`
classes under `src/{AppCode}.Infrastructure/Persistence/Seeding/Applications/{AppPascal}/Core/`
(class names prefixed by the PascalCase application code, e.g. `Crm…`):

- `{AppPascal}CoreNavigationSeedDataProvider.cs` — Application + every Module + every Section + every Resource (Order 10)
- `{AppPascal}CoreTenantApplicationsSeedDataProvider.cs` — tenant↔app links (`core.tenant_TenantApplications`, Order 15); the platform only links its own apps — without it every tenant gets an EMPTY business sidebar (STU-RUN-005). Intentional no-op in extend-built-in mode.
- `{AppPascal}CoreRolesSeedDataProvider.cs` — one Role per actor × applicationCode, `Role.Create(… applicationId: navApp_<role>.Id …)` (Order 20)
- `{AppPascal}CorePermissionsSeedDataProvider.cs` — one NavigationPermission per `permissions[]` row + the derived floor (Order 30)
- `{AppPascal}CoreRolePermissionsSeedDataProvider.cs` — role → permission mappings, stamped `AssignedBy = "scaffold-core-seed:{app}"` (Order 40)
- `{AppPascal}CoreTestUsersSeedDataProvider.cs` — dev test users CREATED at boot (`User.CreateForDevelopment`, gated by `IHostEnvironment.IsDevelopment()`, Order 50)

plus, per project: the DI host (`DependencyInjection.cs` / `ServiceCollectionExtensions.cs` /
`InfrastructureModule.cs`) patched with ONE `<<< CORE-SEED-DI-{AppPascal} BEGIN/END >>>`
block per application (6 `AddScoped` lines each) and the
`<<< CORE-SEED-SCRIPT-RUNNER BEGIN/END >>>` block; `Persistence/Seeding/Scripts/CoreSeedScriptRunner.cs`
(Order 5); the csproj `<<< CORE-SEED-SCRIPTS BEGIN/END >>>` EmbeddedResource block; and
`.smartstack/core-seed/{app}.state.json`, the desired-state snapshot the release
reconciliation diffs.

> Until 2026-09 this SKILL (and the After-Phase-0 gate) expected the PRE-per-app
> layout — `Core/CoreNavigationSeedDataProvider.cs`, one `<<< CORE-SEED-DI BEGIN >>>`
> block « of 5 lines », `Role.Create(applicationId: {Pascal}ApplicationId)` — none of
> which the generator had emitted for months. The gate could only pass by not
> looking. The CLI below imports the scaffolder's OWN constants, so the two
> sides can no longer drift.

You receive in the system prompt:

- `--- BA MODULES (expected) ---` — Markdown listing every module code declared in the BA tables for the active analysis.
- `--- BA APPLICATION CODE ---` — the lowercase application code(s) (e.g. `crm`).
- `--- PROJECT INVENTORY (core) ---` — deterministic scan listing every existing `Applications/*/Core/*SeedDataProvider.cs` file path.
- `--- FILES TOUCHED THIS PHASE ---` — files modified during the current run.

You may use `Read` / `Glob` / `Grep` on the project to verify the prose rules,
and `Bash` ONLY to invoke the deterministic CLIs (this pack's CLI and
`derive-rbac-grants --mode check` for DEV-CORE-011). **No `Edit` / `Write`** —
you are read-only on the project.

## Deterministic engine — how this audit runs

The mechanical rules run through the colocated CLI, never by reading the
sources by hand:

```bash
npx --prefer-offline tsx skills/development/audit-dev-core/cli/audit-dev-core/index.ts \
  --project-path "<the root scaffold-core-seed wrote into (its --outdir)>" \
  --app-code     "<APP>" \
  --mode audit
```

`--app-code` takes one code or a comma-separated list (`crm,budgeting`) — one
bundle is audited per application, every per-app finding carries `params.app`.
`--project-path` is the SAME root Phase 0 passed as `--outdir` (it holds
`src/<Ns>.Infrastructure/**` AND `.smartstack/core-seed/*.state.json`).
Exit 1 on any `err`; `_audit/dev-core.md` written under the project
(`--no-write-report` to skip). `--rules DEV-CORE-005,…` narrows the run —
an unknown id is refused, a prose/external id is answered as an explicit `ok`
stub that says where the rule really runs.

The CLI locates the bundle with the scaffolder's own constants
(`CORE_PROVIDER_SUFFIXES`, `coreProviderClassName`, `diBeginMarker`/`diEndMarker`,
`RUNNER_DI_BEGIN/END`, `CSPROJ_SCRIPTS_BEGIN/END`, `stateFilePath`,
`seedAssignedBy` — imported from `backend/core-seed/cli/scaffold-core-seed/`).
A bundle still on the pre-per-app unprefixed layout is reported as
`legacy-layout` (err, heal = additive re-run of Phase 0), never as a green
scan and never as a false « no markers ».

Implemented: DEV-CORE-000, 001, 005, 006, 008, 009, 010. **DEV-CORE-002/003/004/007**
stay prose (below) — the gates.md Phase 0 pre-entry checks 1 and 2 grep 002 and
004 per module. **DEV-CORE-011** runs in ANOTHER CLI (`derive-rbac-grants --mode check`,
Phase 0 gate check 3).

Every rule of the CLI is SILENT on a freshly generated bundle (its test suite
runs the real `generate()` output through it): a red finding on fresh output is
a regression of the audit, never a defect of the bundle.

## Rules

### DEV-CORE-000 — The run inspected something
- **Severity**: err (application codes requested but 0 Phase 0 files read — mis-pointed `--project-path` or Phase 0 never ran), warn (nothing on either side), ok
- Check (CLI): `report.coverage = { baItems: <app codes>, filesInspected }`. A gate that inspects nothing cannot pass; every other finding of such a run answered on an empty scan.
- **ok**: label=`DEV_CORE_000_ok` · **err**: label=`DEV_CORE_000_err`, params=`{ baItems, filesInspected, infrastructureRoot: null, note }`
- **solution**: point `--project-path` at the root Phase 0 wrote into, or run Phase 0 first — never heal code for this finding (auto-healing row `audit.nothing-inspected`).

### DEV-CORE-001 — All 6 per-app Core providers exist
- **Severity**: err (any of the 6 missing), ok (all present)
- Check (CLI): under `src/{AppCode}.Infrastructure/Persistence/Seeding/Applications/{AppPascal}/Core/`,
  the 6 files `{AppPascal}{Suffix}.cs` for every suffix of `CORE_PROVIDER_SUFFIXES`
  (Navigation, TenantApplications, Roles, Permissions, RolePermissions, TestUsers).
- **ok**: label=`DEV_CORE_001_ok`, params=`{ app, count: 6 }`
- **err**: label=`DEV_CORE_001_err`, params=`{ app, missing: "<comma-separated file names>", expectedDir }`
- **err (legacy)**: label=`DEV_CORE_001_legacy_layout`, params=`{ app, legacyFiles }` — all 6 missing AND
  unprefixed `Persistence/Seeding/Core/Core*SeedDataProvider.cs` files present: the pre-per-app bundle.
- **warn**: label=`DEV_CORE_001_legacy_leftover`, params=`{ legacyFiles }` — legacy files NEXT TO a migrated
  bundle: if their block is still registered the seed runs twice; delete them once this audit is green.
- **fixSkill**: `backend-core-seed`, **fixPhaseKey**: `core`
- **solution** (mandatory on err): "Re-run Phase 0 (`scaffold-core-seed --spec-file <spec> --outdir <projectPath>`):
  idempotent and additive. Do NOT hand-edit the providers."

### DEV-CORE-002 — Every BA module appears in the navigation provider (prose)
- **Severity**: err (any module missing nav), ok (all covered)
- Check: for each module code in `--- BA MODULES (expected) ---`, `Grep` the
  owning app's `{AppPascal}CoreNavigationSeedDataProvider.cs` for a line matching
  `NavigationModule.Create\s*\(\s*[^)]*code\s*:\s*"<moduleCode>"`. This is the
  regression the Budget-empty bug exhibited in TestV2/ba-002: 9 entities +
  2 controllers but `SeedNavigationAsync` returned `Task.CompletedTask`.
  The gates.md Phase 0 pre-entry check 1 runs this grep per module.
- **ok**: label=`DEV_CORE_002_ok`, params=`{ count: <number> }`
- **err**: label=`DEV_CORE_002_err`, params=`{ missing: "<comma-separated module codes>" }`
- **fixSkill**: `backend-core-seed`, **fixPhaseKey**: `core`
- **solution** (mandatory on err): "The CoreSeedSpec built upstream is
  missing these modules. Verify the BA `applications/modules` tables
  include them, then rebuild the spec and re-run scaffold-core-seed."

### DEV-CORE-003 — At least one role for the application (prose)
- **Severity**: err (if no roles), ok (if ≥ 1 role)
- Check: `Grep` `{AppPascal}CoreRolesSeedDataProvider.cs` for
  `Role\.Create\s*\(` — the generated form is
  `Role.Create(… applicationId: navApp_<role>.Id …)` (the application is
  resolved at runtime by code, never a `{Pascal}ApplicationId` constant).
  Without any role, every login resolves to zero permissions.
  `scaffold-core-seed`'s `validate.ts` already refuses an application without roles.
- **ok**: label=`DEV_CORE_003_ok`, params=`{ count: <number> }`
- **err**: label=`DEV_CORE_003_err`, params=`{ applicationCode: "<value>" }`
- **fixSkill**: `backend-core-seed`, **fixPhaseKey**: `core`
- **solution** (mandatory on err): "The BA `actors` table for this
  analysis is empty. Run `create-actors` to declare at least one actor
  per application before re-running Phase 0."

### DEV-CORE-004 — Every nav node carries its full permission FLOOR (prose)
- **Severity**: err (any floor row missing), warn (pre-floor 3-field provider —
  project not yet regenerated), ok (floor complete)
- The floor per grain (canonical in `lib/permission-actions.ts`, drift-locked):

  <!-- permission-floor:v1 — drift-tested against lib/permission-actions.ts (edit ALL carriers or the suite fails) -->
  | Grain | Node | Floor permissions |
  |---|---|---|
  | Application | `{app}` | `access` |
  | Module | `{app}.{module}` | `access` |
  | Section | `{app}.{module}.{section}` | `access` `lookup` `read` `create` `update` `delete` `execute` |
  | Resource | `{app}.{module}.{section}.{resource}` | `access` `lookup` `read` `create` `update` `delete` `execute` |
  <!-- /permission-floor:v1 -->

- Check: `Read` `{AppPascal}CorePermissionsSeedDataProvider.cs` and parse the 4-field
  permission tuples `("{path}", "{action}", "{level}", "{nodeCode}")`. Build
  the expected node set from the SAME app's
  `{AppPascal}CoreNavigationSeedDataProvider.cs` (app + modules + sections + resources)
  and verify every node carries its full floor paths (extend-built-in slices
  skip the application-grain row). A provider still on the pre-floor 3-field
  tuple `(Path, Action, SectionCode)` → **warn** (regenerate Phase 0), never
  a per-row err avalanche. The gates.md Phase 0 pre-entry check 2 runs this.
- **ok**: label=`DEV_CORE_004_ok`
- **warn**: label=`DEV_CORE_004_warn`, params=`{ reason: "pre-floor-provider" }`
- **err**: label=`DEV_CORE_004_err`, params=`{ missingFloorPaths: "<path list>" }`
- **fixSkill**: `backend-core-seed`, **fixPhaseKey**: `core`
- **solution** (mandatory on err): "The permission floor is DERIVED from the
  nav tree by scaffold-core-seed's build-spec — a missing floor row means a
  hand-written spec or a stale provider. Rebuild the CoreSeedSpec and re-run
  Phase 0 (scaffold-core-seed); never hand-add the rows."

### DEV-CORE-005 — Per-app DI registration block present and complete
- **Severity**: err (no DI host, no per-app markers, an empty block, or a PARTIAL block — 1-5 of the 6 lines, a manual edit or a merge between the markers), ok (6 lines)
- > Until 2026-09-24 a partial block was a **warn**: the blocking After-Phase-0 gate exited 0 on a
  > bundle that boots without one seed — the merge that drops the `CorePermissionsSeedDataProvider`
  > line leaves every role without permissions, 403 everywhere. A partial block is an **err**
  > (`reason: "partial-block"`, `missing` names the providers) — user decision 2026-09-24 (QC lot 13).
- Check (CLI): the DI host (`DependencyInjection.cs`, else `ServiceCollectionExtensions.cs`,
  else `InfrastructureModule.cs`, under `src/{AppCode}.Infrastructure/`) carries
  `// <<< CORE-SEED-DI-{AppPascal} BEGIN >>>` … `// <<< CORE-SEED-DI-{AppPascal} END >>>`;
  between them, one `services.AddScoped<IClientSeedDataProvider, global::<ns>.{AppPascal}{Suffix}>();`
  per suffix of `CORE_PROVIDER_SUFFIXES` (6). The legacy app-less
  `<<< CORE-SEED-DI BEGIN >>>` pair is `legacy-layout` (err), never « no markers ».
- **ok**: label=`DEV_CORE_005_ok`, params=`{ app, found: 6, file }`
- **err**: label=`DEV_CORE_005_err`, params=`{ app, reason: "no-di-host" | "no-markers" | "empty-block" | "partial-block", … }` —
  `partial-block` carries `{ found, missing: "<class names, generator order>", file }` (no `warn` exists for this rule since 2026-09-24)
- **err (legacy)**: label=`DEV_CORE_005_legacy_layout`, params=`{ app, reason: "legacy-markers", file }`
- **fixSkill**: `backend-core-seed`, **fixPhaseKey**: `core`
- **solution** (mandatory on err): "Re-run scaffold-core-seed. The CLI
  patches the DI host idempotently between the per-app markers. Without the
  registrations, the providers compile but never run at startup."

### DEV-CORE-006 — Test users are CREATED, one per role
- **Severity**: err (the test-users provider file is absent — `reason: "provider-missing"`), warn (zero users, or fewer users than roles), ok (≥ 1 user and ≥ roles)
- > Since 2026-09-24 (QC G2) this rule errs ON ITS OWN when its provider is absent: it used to return
  > nothing and leave DEV-CORE-001 to name the file, so `--rules DEV-CORE-006` alone answered `valid:true`
  > on a bundle 001 refuses. In the default run 001 AND 006 both err on the same absent file — deliberate.
- Check (CLI): `{AppPascal}CoreTestUsersSeedDataProvider.cs` — count the
  `await EnsureTestUserAsync(` calls against the `Role.Create(` calls of
  `{AppPascal}CoreRolesSeedDataProvider.cs`. A provider without
  `User.CreateForDevelopment` is a STUB generated before the provider created
  users (`reason: "stub-provider"`). Phase 5 UI tests log in with these users
  (`tests/ui-test/test-users.json`); missing users surface as 401s.
- **ok**: label=`DEV_CORE_006_ok`, params=`{ app, users, roles, skipped }`
- **warn**: label=`DEV_CORE_006_warn`, params=`{ app, users, roles, skipped, reason?: "stub-provider" | "no-test-users" }`
- **err**: label=`DEV_CORE_006_err`, params=`{ app, reason: "provider-missing", expected: "<provider path>" }`
- **fixSkill**: `backend-core-seed`, **fixPhaseKey**: `core`
- **solution** (mandatory on warn): "The CoreSeedSpec.testUsers[] is
  short (or the provider predates user creation). Add one entry per actor
  (default: emailLocalPart = roleCode) and re-run scaffold-core-seed."

### DEV-CORE-007 — Providers are deterministic (no hand-edits) (prose)
- **Severity**: warn (any provider modified outside the documented
  shape), ok (pristine)
- Check: each Core provider should contain ONLY the four `Seed*Async`
  methods, a single `Order` property, its `static readonly` GUID fields and
  (test users) the private `EnsureTestUserAsync` helper. `Grep` for any
  extra public method or property declaration; warn if found. Hand-edits
  are lost on the next Phase 0 re-run.
- **ok**: label=`DEV_CORE_007_ok`
- **warn**: label=`DEV_CORE_007_warn`, params=`{ files: "<file:extra-members list>" }`
- **solution** (mandatory on warn): "Move custom logic out of the Core
  providers into a SEPARATE module-scoped seed under
  `Persistence/Seeding/Applications/{AppPascal}/Modules/{ModuleCode}/`. The
  Core providers are regenerated on every Phase 0 run; any addition there
  will disappear."

### DEV-CORE-008 — Desired-state snapshot present per application
- **Severity**: err (missing, not a core-seed state file, empty `specHash`, or snapshotting another application), ok
- Check (CLI): `.smartstack/core-seed/{app}.state.json` (the scaffolder's
  `stateFilePath`) parses through the scaffolder's own `parseCoreSeedState`
  and its `application` is the audited app. This snapshot is the artefact the
  prod reconciliation pipeline diffs at release time (derive-seed-delta) and
  the gitflow pr gate keys on — without it, a seed change ships to prod with
  NO data migration and the additive boot seed silently ignores every rename /
  update / removal.
- **ok**: label=`DEV_CORE_008_ok`, params=`{ app, specHash, file }`
- **err**: label=`DEV_CORE_008_err`, params=`{ app, reason: "missing" | "invalid" | "application-mismatch", … }`
- **fixSkill**: `backend-core-seed`, **fixPhaseKey**: `core`
- **solution** (mandatory on err): "Re-run scaffold-core-seed (Phase 0) — it
  emits the state snapshot alongside the providers. Commit the state file
  with the project."

### DEV-CORE-009 — CoreSeedScriptRunner present, registered and embedded
- **Severity**: err (runner file missing, not at `Order => 5`, or DI registration missing), warn (only the csproj EmbeddedResource block missing), ok
- Check (CLI), per project:
  1. `src/{AppCode}.Infrastructure/Persistence/Seeding/Scripts/CoreSeedScriptRunner.cs`
     exists and declares `Order => 5` (it must run BEFORE the additive
     providers, Order 10+).
  2. The DI host contains the `<<< CORE-SEED-SCRIPT-RUNNER BEGIN/END >>>`
     block with one `AddScoped<IClientSeedDataProvider, …CoreSeedScriptRunner>()`.
  3. `{AppCode}.Infrastructure.csproj` contains the
     `<<< CORE-SEED-SCRIPTS BEGIN/END >>>` block with
     `<EmbeddedResource Include="Persistence\Seeding\Scripts\**\*.sql" />`
     (without it the delta scripts compile out of the assembly and are
     silently never applied at boot).
- **ok**: label=`DEV_CORE_009_ok`, params=`{ runner, diHost, csproj }`
- **warn**: label=`DEV_CORE_009_warn`, params=`{ missing: "csproj-embedded-resource" }`
- **err**: label=`DEV_CORE_009_err`, params=`{ missing: "<runner | runner-order-5 | di-registration | csproj-embedded-resource, …>" }`
- **fixSkill**: `backend-core-seed`, **fixPhaseKey**: `core`
- **solution** (mandatory on err/warn): "Re-run scaffold-core-seed — it emits
  the runner, patches the DI host and the csproj idempotently."

### DEV-CORE-010 — Seed-owned RolePermissions carry the AssignedBy marker
- **Severity**: err (the role-permissions provider file is absent — `reason: "provider-missing"`, since 2026-09-24 / QC G2: the rule errs on its own so `--rules DEV-CORE-010` alone never passes a bundle DEV-CORE-001 refuses; in the default run 001 and 010 both err on that file), warn (marker absent), ok
- Check (CLI): `{AppPascal}CoreRolePermissionsSeedDataProvider.cs` calls
  `RolePermission.Create(role.Id, permission.Id, "scaffold-core-seed:{app}")`
  (the scaffolder's `seedAssignedBy(app)`). The marker is what lets the
  release delta scripts revoke ONLY seed-owned mappings
  (`AssignedBy IS NULL OR LIKE 'scaffold-core-seed%'`) and never an admin
  grant. A provider with no mapping at all is ok (`mappings: 0`).
- **ok**: label=`DEV_CORE_010_ok`, params=`{ app, marker, file }`
- **warn**: label=`DEV_CORE_010_warn`, params=`{ app, expectedMarker, file }`
- **err**: label=`DEV_CORE_010_err`, params=`{ app, reason: "provider-missing", expected: "<provider path>" }`
- **fixSkill**: `backend-core-seed`, **fixPhaseKey**: `core`
- **solution** (mandatory on warn): "Re-run scaffold-core-seed to regenerate
  the RolePermissions providers with the AssignedBy marker."

### DEV-CORE-011 — Seeded grants match the BA RBAC matrices, both directions (external CLI)
- **Severity**: err (missing-grant / extra-grant / unmatched-actor),
  warn (no state file to compare), ok
- Check (deterministic — run the colocated create-rbac CLI in check mode and
  read its envelope; NEVER re-derive by hand; this pack's CLI answers
  `--rules DEV-CORE-011` with an explicit `external` stub):
  ```bash
  npx --prefer-offline tsx skills/business-analyse/create-rbac/cli/derive-rbac-grants/index.ts \
    --spec '{"baRoot":".smartstack/ba","mode":"check","projectPath":"<projectPath>"}'
  ```
  The CLI re-derives the grants from every module's `rbac.md` (human rows
  app-prefixed + derived-lookups block verbatim + v3.62 `.access`
  grandfathering, floor skipped) and compares them to
  `.smartstack/core-seed/*.state.json` in BOTH directions:
  - `missingGrants[]` — a BA-specified right absent from the seed: the role
    silently cannot do what the BA specified (every downstream gate stays
    green without this rule — the UAT even EXPECTS the resulting 403);
  - `extraGrants[]` — a seeded right that neither the matrix, the derived
    block nor the grandfathering explains: an invented right (`rbac.md`
    records no ✗ cell, so denial is only enforceable as "everything else is
    an extra");
  - `unmatchedActors[]` — an actor holding grants that matches no seeded role
    (`actorMatchesRole` legs: label⇔name, label⇔code, code⇔code, slug).
  Drift is DATA in the CLI (exit 0) — THIS rule is the enforcement.
- **ok**: label=`DEV_CORE_011_ok`, params=`{ grants: <n> }`
- **err**: label=`DEV_CORE_011_err`,
  params=`{ missing: "<roleCode|path,…>", extra: "<roleCode|path,…>", unmatchedActors: "<…>" }`
- **warn**: label=`DEV_CORE_011_warn`, params=`{ reason: "no-state" }`
- **fixSkill**: `backend-core-seed`, **fixPhaseKey**: `core`
- **solution** (mandatory on err): "Re-run Phase 0 with the derive-rbac-grants
  fragment (mode=derive) pasted verbatim into the CoreSeedSpec slices —
  missing grants mean the transcription lost matrix rows; extra grants mean
  the seed carries rights the BA never specified: either author the missing
  rbac.md row (the BA is the SSOT) or regenerate the seed without them.
  `needsResolution[]` entries (unknown actor, invalid path, role-code
  collision) are fixed in the BA docs, never worked around."

## Output

Emit EXACTLY ONE JSON code block matching the standard `auditReport`
envelope: the CLI's `auditReport` (its findings verbatim — `dimension`
`devCore`, one `ok` per passing rule), plus one finding per prose rule
(002/003/004/007) and the DEV-CORE-011 verdict read from `derive-rbac-grants`.

```json
{
  "auditReport": {
    "scope": "devCore",
    "applicationCode": "<from BA APPLICATION CODE>",
    "moduleCode": null,
    "findings": [
      {
        "dimension": "devCore",
        "code": "DEV-CORE-005",
        "severity": "err",
        "label": "DEV_CORE_005_err",
        "params": { "app": "crm", "reason": "no-markers" },
        "solution": "Re-run scaffold-core-seed. The CLI patches the DI host idempotently between the per-app markers.",
        "fixSkill": "backend-core-seed",
        "fixPhaseKey": "core"
      }
    ]
  }
}
```

Stop immediately after the JSON block. Do not narrate.
