# case (root) — JSON Implementation

Cross-cutting direct-JSON rules live in [`case-editing-operations.md`](../../case-editing-operations.md).

## Purpose

Create the full project on disk in a single plugin invocation — 5 scaffold files + `caseplan.json`. Runs exactly once per project, as the first build step. Two sections:

1. **§ Scaffold** — write the 5 boilerplate files (`project.uiproj`, `operate.json`, `entry-points.json`, `bindings_v2.json`, `package-descriptor.json`) directly.
2. **§ Write caseplan.json** — write the root case skeleton (`root` + empty `nodes: []` + empty `edges: []`).

Solution setup (`uip solution init`) and project registration (`uip solution projects add`) are CLI — see [implementation.md Step 6](../../implementation.md). Edit-after-create is out of scope (SKILL regenerates from scratch — see SKILL.md Rule 6); this recipe writes all case fields directly into the initial `caseplan.json`.

**No trigger emitted at T01.** The primary trigger is created by the triggers plugin at T02 via direct JSON write.

## Input spec (from `sdd.md`)

| Field | Required | Notes |
|---|---|---|
| `file` | yes | Target path. Literal filename MUST be `caseplan.json`. |
| `name` | yes | Human-readable case name. |
| `case-identifier` | no | Defaults to `name`. |
| `identifier-type` | no | `constant` \| `external`. Defaults to `constant`. |
| `case-app-enabled` | no | Boolean. Defaults to `false`. |
| `directly-pass-task-outputs` | no | Boolean. Defaults to `true`. Set `false` only when sdd.md requests it. |
| `description` | no | Defaults to empty string. Always emitted so downstream consumers read a consistent shape. |

See [`planning.md`](planning.md) for how these fields are sourced from `sdd.md`.

## § Scaffold — write project boilerplate

Runs before § Write caseplan.json. Writes 5 static JSON files directly. All substitution is name-for-name — no subprocess.

### Pre-flight

1. **Solution exists.** `<SolutionDir>/<SolutionName>.uipx` must exist (created by `uip solution init` — Step 6.0).
2. **Project dir is a distinct child of the solution dir.** The target is always `<SolutionDir>/<ProjectName>/`, never `<SolutionDir>/` itself. `<ProjectName>` equal to `<SolutionName>` is normal and still nests — `Foo/Foo/`. Never collapse the two because the names match.
3. **Target dir is clean.** None of the 5 scaffold files may already exist in `<SolutionDir>/<ProjectName>/`. If any is present, **hard-fail** with:
   ```
   <SolutionDir>/<ProjectName>/<file> already exists. Remove <SolutionDir>/<ProjectName>/ before re-scaffolding. No --force equivalent in the JSON path.
   ```
   Do not overwrite. Do not merge.
4. **Create directory.** `mkdir -p <SolutionDir>/<ProjectName>` via Bash.

### Generate one UUID for `operate.json.projectId`

Bash + `node -e` (stdout only — no file I/O inside the subprocess):

```bash
node -e "console.log(crypto.randomUUID())"
```

Capture the printed UUID; inject it at `<PROJECT_ID>` below.

### Files to write

Use the Write tool for each. All 5 files go directly into `<SolutionDir>/<ProjectName>/` — **flat layout, no `content/` directory on disk**.

#### `project.uiproj`

```json
{
  "Name": "<ProjectName>",
  "ProjectType": "CaseManagement"
}
```

#### `operate.json`

```json
{
  "$schema": "https://cloud.uipath.com/draft/2024-12/operate",
  "projectId": "<PROJECT_ID>",
  "contentType": "CaseManagement",
  "targetFramework": "Portable",
  "runtimeOptions": {
    "requiresUserInteraction": false,
    "isAttended": false
  }
}
```

#### `entry-points.json`

```json
{
  "$schema": "https://cloud.uipath.com/draft/2024-12/entry-point",
  "$id": "entry-points.json",
  "entryPoints": []
}
```

> Emit `entryPoints: []` empty. The triggers plugin owns every `entryPoints[]` insertion starting at T02 — that way the path fragment always matches the real primary-trigger ID.

#### `bindings_v2.json`

```json
{
  "version": "2.0",
  "resources": []
}
```

#### `package-descriptor.json`

```json
{
  "$schema": "https://cloud.uipath.com/draft/2024-12/package-descriptor",
  "files": {
    "operate.json": "content/operate.json",
    "entry-points.json": "content/entry-points.json",
    "bindings.json": "content/bindings_v2.json",
    "caseplan.json": "content/caseplan.json",
    "caseplan.json.bpmn": "content/caseplan.json.bpmn"
  }
}
```

> `content/` prefix here describes the **packed** layout inside the eventual `.nupkg` — NOT the on-disk layout. On disk every file is flat under `<ProjectName>/`. `caseplan.json.bpmn` is generated by downstream tooling (debug / case pack) and need not exist at scaffold time. — Phase 7 runs `case pack` before `solution pack` for exactly this reason ([phased-execution.md § Why `case pack` is mandatory](../../phased-execution.md#why-case-pack-is-mandatory)).
>
> **`bindings.json` key maps to `content/bindings_v2.json`.** The packed entry's key is `bindings.json` (bare) while the on-disk file stays `bindings_v2.json`. Do not create a `bindings.json` file on disk — write only `bindings_v2.json` per the block above.

### Atomicity

Hard-fail on the first write error — no rollback, no staging directory. Partial state is the user's cleanup problem (matches the pre-flight hard-fail policy).

### Post-scaffold check

- `<SolutionDir>/<ProjectName>/project.uiproj` exists and parses as JSON.
- `<SolutionDir>/<ProjectName>/operate.json` contains a non-empty `projectId` string.
- `<SolutionDir>/<ProjectName>/entry-points.json` parses as JSON and its `entryPoints` field is `[]`.
- **No `content/` dir on disk.** Case file is flat at `<SolutionDir>/<ProjectName>/caseplan.json`; if nested under `content/`, layout is wrong — halt. `validate`/`debug` resolve only the flat root path (an ad-hoc validate against the nested path passes, but real project-dir resolution fails).
- **Project dir is not the solution dir.** `<SolutionName>.uipx` and `caseplan.json` must NOT be siblings — `<SolutionDir>/<ProjectName>/<SolutionName>.uipx` must not exist. Nothing downstream catches this: `validate` passes on any path given, and `uip solution projects add <SolutionDir> …` registers the solution directory as its own project without error. But `debug` walks up from the project dir for the enclosing `.uipx`, so a collapsed layout overshoots it and fails with `no .uipx file was found in <workingRoot>`. Halt; move the 6 project files into `<SolutionDir>/<ProjectName>/`.

If any check fails, halt and report.

## § Write caseplan.json — Pre-write checks

1. **Scaffold has run.** The 5 files listed in § Scaffold must exist in `<SolutionDir>/<ProjectName>/`. They were written earlier in this same plugin invocation; if missing, halt (bug — re-run the plugin from the start).
2. **Collision behavior: overwrite.** If `caseplan.json` already exists, overwrite it. When absent, create it. Skill Phase 2 re-runs regenerate `caseplan.json` from scratch per SKILL.md Rule 6, so a collision here means a genuine re-run and overwriting is correct.

## ID generation

- Top-level `id` is generated: prefix `case-` + 10 chars from `[A-Za-z0-9]` (per `case-editing-operations.md` § ID Generation algorithm). Example: `case-aBcDeFgHiJ`.
- **No trigger ID emitted at T01.** The triggers plugin owns primary-trigger creation at T02.

Record in `id-map.json`:

```json
{
  "T01": { "kind": "case", "id": "case-aBcDeFgHiJ" }
}
```

The `id` value mirrors the actual top-level `id` written into `caseplan.json` — debug breadcrumb of reality.

## Recipe — Skeleton (no trigger)

Pure skeleton: top-level fields + `metadata` block + empty `bindings: []` + empty `variables` + empty `nodes: []` + empty `edges: []` + empty `layout: {}`. Primary trigger is the triggers plugin's responsibility at T02.

### Minimal variant (no description)

```json
{
    "id": "case-aBcDeFgHiJ",
    "version": "27.0.0",
    "name": "<name>",
    "metadata": {
        "caseIdentifier": "<case-identifier — defaults to <name>>",
        "caseIdentifierType": "<constant|external — defaults to constant>",
        "caseAppEnabled": <true|false — defaults to false>,
        "publishVersion": 2,
        "caseUnifiedSchemaEnabled": true,
        "caseDirectlyPassTaskOutputs": <true|false — defaults to true>,
        "intsvcActivityConfig": "v2"
    },
    "bindings": [],
    "variables": {
        "inputs": [],
        "outputs": [],
        "inputOutputs": []
    },
    "nodes": [],
    "edges": [],
    "layout": {}
}
```

### With description

Adds top-level `description` field (NOT inside `metadata`):

```json
{
    "id": "case-aBcDeFgHiJ",
    "version": "27.0.0",
    "name": "<name>",
    "description": "<description>",
    "metadata": {
        "caseIdentifier": "<case-identifier>",
        "caseIdentifierType": "<constant|external>",
        "caseAppEnabled": <true|false>,
        "publishVersion": 2,
        "caseUnifiedSchemaEnabled": true,
        "caseDirectlyPassTaskOutputs": <true|false — defaults to true>,
        "intsvcActivityConfig": "v2"
    },
    "bindings": [],
    "variables": {
        "inputs": [],
        "outputs": [],
        "inputOutputs": []
    },
    "nodes": [],
    "edges": [],
    "layout": {}
}
```

> **`intsvcActivityConfig` always emitted** — set `metadata.intsvcActivityConfig: "v2"` on every caseplan.
>
> **`caseDirectlyPassTaskOutputs` always emitted** — write `metadata.caseDirectlyPassTaskOutputs` on every caseplan, value from the T01 `directly-pass-task-outputs` field (defaults to `true` when sdd.md is silent). When `true`, task outputs pass directly through messages instead of shared variables, fixing race conditions on task outputs in cases with parallel tasks. Emit `false` only when sdd.md explicitly requests it.

## caseIdentifier — constant vs external

Set `caseIdentifierType` from the T01 `identifier-type` (default `constant`); lives under `metadata.*`.

- **`constant`** — write the literal prefix from sdd.md (`"caseIdentifier": "LOAN"`).
- **`external`** — copy the T01 `case-identifier` expression **verbatim** into `caseIdentifier` (e.g. `"=vars.poNumber"` or `` "=js:`${metadata.InstanceId}-${vars.region}`" ``). Do NOT transform or `=js:`-wrap it — unlike task-input sinks ([bindings-and-expressions.md](../../bindings-and-expressions.md)), this value is written as authored. Valid forms + variable eligibility: [planning.md § External identifier value](planning.md).

## Formatting

- Indent: 4 spaces.
- Trailing newline: single `\n` at end of file.
- Key order: `id, version, name, description, metadata, bindings, variables, nodes, edges, layout`.

Use the Write tool. File did not exist before — Edit does not apply.

## Post-write validation

Cheap sanity checks only — full validation runs after all plugins are done, per SKILL.md Anti-patterns ("Do NOT validate after each command").

1. **File parses.** `JSON.parse(readFile('caseplan.json'))` succeeds.
2. **Top-level shape.**
   - `id` matches `^case-[A-Za-z0-9]{10}$`
   - `version === "27.0.0"`
   - `metadata.caseUnifiedSchemaEnabled === true`
   - `metadata.publishVersion === 2`
   - `metadata.intsvcActivityConfig === "v2"`
   - `typeof metadata.caseDirectlyPassTaskOutputs === "boolean"` (present; `true` unless sdd.md requested `false`)
   - `bindings` is an array of length 0
   - `variables.inputs`, `variables.outputs`, `variables.inputOutputs` are all arrays of length 0
3. **Empty node/edge arrays + layout.**
   - `nodes` is an array of length 0
   - `edges` is an array of length 0
   - `layout` is an object (may be `{}`)

If any check fails, halt and report — do not proceed to downstream plugins.

**Do NOT run `uip maestro case validate` here.** A case-only caseplan will fail validation by design (no stage nodes, so the case cannot be entered). Validation runs once after the full build (SKILL.md Anti-patterns — "Do NOT validate after each command"). Pre-build validate is informational only, regardless of schema.

<!-- END: impl-json.md -->
