---
name: audit-dev-core
description: Audit code generated by Phase 0 (Core Foundation Seed) — every PRD module must have nav + roles + permissions in the 6 Core providers, DI registration must be in place, the providers must be deterministic (no hand-edits), and the seeded role→permission grants must match the BA rbac.md matrices in BOTH directions (DEV-CORE-011 via derive-rbac-grants --mode check).
group: D
phase: devCore
kind: audit
audit_only: true
section_label: 'AUDIT-DEV-CORE (rules to apply against the 6 Core/*SeedDataProvider.cs files 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 to write 6 `IClientSeedDataProvider` classes
under `src/{AppCode}.Infrastructure/Persistence/Seeding/Core/`:

- `CoreNavigationSeedDataProvider.cs` — Application + every Module + every Section + every Resource
- `CoreTenantApplicationsSeedDataProvider.cs` — tenant↔app links (`core.tenant_TenantApplications`); 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.
- `CoreRolesSeedDataProvider.cs` — One Role per actor × applicationCode
- `CorePermissionsSeedDataProvider.cs` — One NavigationPermission per `structured.permissions[]`
- `CoreRolePermissionsSeedDataProvider.cs` — Role → permission mappings
- `CoreTestUsersSeedDataProvider.cs` — Dev test users (gated by `IHostEnvironment.IsDevelopment()`)

You receive in the system prompt:

- `--- BA MODULES (expected) ---` — Markdown listing every module code declared in the BA tables for the active analysis (the `coreExpectation.modules` set the runtime gate uses).
- `--- BA APPLICATION CODE ---` — the lowercase application code (e.g. `crm`).
- `--- PROJECT INVENTORY (core) ---` — deterministic scan listing every existing `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 rule checks,
and `Bash` ONLY to invoke the deterministic check CLIs (DEV-CORE-011).
**No `Edit` / `Write`** — you are read-only on the project.

Apply every rule below across the whole app scope. Emit `ok` findings for
passing rules so the UI shows green checks. Emit `err` findings only when
the spec is unambiguous and the code is clearly wrong (missing module nav,
empty roles, < 2 permissions per module). Use `warn` for optional artefacts
(missing test users, DI marker block formatting drift) that the user should
review but won't block by themselves.

## Rules

### DEV-CORE-001 — All 6 Core providers exist
- **Severity**: err (if any of the 6 missing), ok (if all present)
- Check: under `src/{AppCode}.Infrastructure/Persistence/Seeding/Core/`,
  the 6 files must be present:
  - `CoreNavigationSeedDataProvider.cs`
  - `CoreTenantApplicationsSeedDataProvider.cs`
  - `CoreRolesSeedDataProvider.cs`
  - `CorePermissionsSeedDataProvider.cs`
  - `CoreRolePermissionsSeedDataProvider.cs`
  - `CoreTestUsersSeedDataProvider.cs`
- **ok**: label=`DEV_CORE_001_ok`, params=`{ count: 6 }`
- **err**: label=`DEV_CORE_001_err`, params=`{ missing: "<comma-separated file names>" }`
- **fixSkill**: `backend-core-seed`, **fixPhaseKey**: `core`
- **solution** (mandatory on err): "Re-run Phase 0. Invoke
  `scaffold-core-seed --spec-file <path> --outdir <project>` with the
  spec file built by the orchestrator. Do NOT hand-edit the providers."

### DEV-CORE-002 — Every BA module appears in CoreNavigationSeedDataProvider
- **Severity**: err (if any module missing nav), ok (if all covered)
- Check: for each module code in `--- BA MODULES (expected) ---`,
  `Grep` `CoreNavigationSeedDataProvider.cs` for a line matching
  `NavigationModule.Create\s*\(\s*[^)]*code\s*:\s*"<moduleCode>"`.
  This is the regression that the Budget-empty bug exhibited in
  TestV2/ba-002: 9 entities + 2 controllers but `SeedNavigationAsync`
  returned `Task.CompletedTask`.
- **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
- **Severity**: err (if no roles), ok (if ≥ 1 role)
- Check: `Grep` `CoreRolesSeedDataProvider.cs` for a line matching
  `Role\.Create\s*\([^)]*<PascalApplicationCode>ApplicationId`. Without
  any role, every login resolves to zero permissions.
- **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
- **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` `CorePermissionsSeedDataProvider.cs` and parse the 4-field
  permission tuples `("{path}", "{action}", "{level}", "{nodeCode}")`. Build
  the expected node set from the SAME provider app's
  `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.
- **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 — DI registration block present and well-formed
- **Severity**: err (if no DI block), warn (if marker block is malformed), ok (if 5 lines between markers)
- Check: locate `DependencyInjection.cs` under `src/{AppCode}.Infrastructure/`
  (or `ServiceCollectionExtensions.cs` / `*Module.cs`). `Grep` for the
  literal markers `<<< CORE-SEED-DI BEGIN >>>` and `<<< CORE-SEED-DI END >>>`.
  Between them, count lines matching
  `services\.AddScoped<IClientSeedDataProvider,\s*Core\w+SeedDataProvider>\(\)`.
  - 5 lines → ok
  - 1-4 lines → warn (manual edit between markers)
  - 0 lines or no markers → err
- **ok**: label=`DEV_CORE_005_ok`
- **warn**: label=`DEV_CORE_005_warn`, params=`{ found: <number> }`
- **err**: label=`DEV_CORE_005_err`
- **fixSkill**: `backend-core-seed`, **fixPhaseKey**: `core`
- **solution** (mandatory on err): "Re-run scaffold-core-seed. The CLI
  patches DependencyInjection.cs idempotently between
  `<<< CORE-SEED-DI BEGIN/END >>>` markers. Without the registrations,
  the providers compile but never run at startup."

### DEV-CORE-006 — Test users present (one per role)
- **Severity**: warn (if zero or < roles count), ok (if at least one)
- Check: `Read` `CoreTestUsersSeedDataProvider.cs` and count
  `EnsureTestUserAsync` calls. Compare against role count in
  `CoreRolesSeedDataProvider.cs`. Phase 5 UI tests need at least one
  user per role to validate RBAC; missing users surface as 401s in
  test runs.
- **ok**: label=`DEV_CORE_006_ok`
- **warn**: label=`DEV_CORE_006_warn`, params=`{ users: <number>, roles: <number> }`
- **fixSkill**: `backend-core-seed`, **fixPhaseKey**: `core`
- **solution** (mandatory on warn): "The CoreSeedSpec.testUsers[] is
  short. Add one entry per actor (default: emailLocalPart = roleCode)
  and re-run scaffold-core-seed."

### DEV-CORE-007 — Providers are deterministic (no hand-edits)
- **Severity**: warn (if any provider modified outside the documented
  shape), ok (if pristine)
- Check: each Core provider should contain ONLY the methods documented
  in the SKILL (`SeedNavigationAsync` / `SeedRolesAsync` /
  `SeedPermissionsAsync` / `SeedRolePermissionsAsync`) and a single
  `Order` property. `Grep` for any extra public method or property
  declaration; warn if found. Hand-edits will be 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/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 (if missing for any app), ok (if present)
- Check: for each application code, `.smartstack/core-seed/{appCode}.state.json`
  must exist and be valid JSON with `"$schema": "smartstack/core-seed-state"`
  and a non-empty `"specHash"`. 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=`{ apps: "<comma-separated app codes>" }`
- **err**: label=`DEV_CORE_008_err`, params=`{ missing: "<comma-separated app codes>" }`
- **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 or DI registration missing), warn (csproj
  EmbeddedResource block missing), ok
- Check:
  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 >>>`
     marker block with one `AddScoped<IClientSeedDataProvider, …CoreSeedScriptRunner>()`.
  3. The 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`
- **warn**: label=`DEV_CORE_009_warn`, params=`{ missing: "csproj EmbeddedResource block" }`
- **err**: label=`DEV_CORE_009_err`, params=`{ missing: "<runner|di-registration>" }`
- **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**: warn (marker absent), ok
- Check: `Grep` each `{AppPascal}CoreRolePermissionsSeedDataProvider.cs` for
  `RolePermission.Create(role.Id, permission.Id, "scaffold-core-seed:`.
  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 generated before this convention still works but
  its new mappings are indistinguishable from legacy rows.
- **ok**: label=`DEV_CORE_010_ok`
- **warn**: label=`DEV_CORE_010_warn`, params=`{ files: "<providers without the marker>" }`
- **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
- **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):
  ```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. Set `dimension` to `devCore` on every finding. Include one
`ok` finding per passing rule (so the UI lights up green) and one
finding per failing rule.

```json
{
  "auditReport": {
    "scope": "devCore",
    "applicationCode": "<from BA APPLICATION CODE>",
    "moduleCode": null,
    "findings": [
      {
        "dimension": "devCore",
        "code": "DEV-CORE-002",
        "severity": "err",
        "label": "DEV_CORE_002_err",
        "params": { "missing": "budgeting" },
        "solution": "The CoreSeedSpec built upstream is missing this module. Verify BA tables and rebuild.",
        "fixSkill": "backend-core-seed",
        "fixPhaseKey": "core"
      }
    ]
  }
}
```

Stop immediately after the JSON block. Do not narrate.
