# 0005 — The D8 result-vocabulary re-code and its legacy read mapping

## Context

Before P1b's D8 vocabulary re-code, a task's result `target.kind` used
`"prompt"` for a prepared command/agent-LLM result and one shared
`"command"` for every native (shell/script) result, which conflated two
materially different execution shapes under one label and mislabeled an
LLM-routed dispatch as a literal "prompt". D8 renamed the written
vocabulary; the harder problem was that `task_history` is a durable,
already-populated table — existing rows on disk still carry the OLD labels,
and `readTaskHistory` has to keep returning a coherent shape for both
generations of rows forever.

## Decision

Moved verbatim from `src/tasks/run/task-result.ts`'s module header:

> `TaskRunResult` — the shape every dispatch arm returns — plus the small
> cluster of helpers that build one directly: `preparedResultTarget` (the D8
> result-vocabulary projection of a freshly prepared execution),
> `finishDisabledTask` (the disabled-task short-circuit), and
> `exitCodeForStatus` (the OS-scheduler exit-code mapping). `RunTaskOptions`
> — the public options bag `runTask()`, `load-task.ts`, and every dispatch
> arm read from — lives here too, alongside the other public-surface types
> the compat shim (`src/tasks/runner.ts`) re-exports.
>
> D8 (§5.3, §6 F-2): `preparedResultTarget`'s prepared-command arm now
> returns `{kind:"command", engine}` (formerly `{kind:"prompt", engine}`);
> its native arm now returns the bare `{kind:"shell"}` / `{kind:"script"}`
> (formerly one shared `{kind:"command"}`) — the arm-specific `cmd` is
> added by `run-native-task.ts` once it has actually built the argv,
> mirroring the pre-P1b shape where the bare disabled-task projection never
> carried `cmd` either.

And from `src/tasks/run/task-history.ts`'s module header (the WRITE half —
the READ half's mapping rule is kept in the code itself, not moved; see
below):

> The `task_history` read/write boundary: `appendHistory` (write) and
> `readTaskHistory` / `taskHistoryRowToResult` (read).
>
> D8 (spec §5.3, §6 F-2) result-vocabulary re-code, implemented entirely at
> this read/write boundary. WRITE: every row `appendHistory` writes now
> carries `targetVocab: 2` in its metadata, and the new target_kind strings
> ("command" for a prepared command/agent-LLM result, "shell", "script",
> "workflow" unchanged).

## Consequences

- Every NEW row written after D8 is unambiguous — `targetVocab: 2` plus the
  new `target_kind` strings mean a reader never has to guess which
  generation a row belongs to.
- Every OLD row (written before D8, no `targetVocab` marker at all) must
  keep reading correctly forever — this is not a migration that runs once
  and finishes; there is no "convert task_history in place" step, because
  the table is an append-only historical log, not authoring state. The
  read-side mapping rule is therefore a PERMANENT part of the codebase
  (`docs/plans/specs/p4-deletions-closeout.md` row B-51: "it reads old rows
  forever. Deleting it is a review-blocking violation.").
- The exact legacy mapping — kept in `src/tasks/run/task-history.ts` itself
  as a short invariant comment, not summarized here, because a maintainer
  reading `taskHistoryRowToResult` needs it right there — is: a legacy row
  (no `targetVocab` marker) maps `"prompt"` → `{kind:"command", engine}`,
  `"command"` → `{kind:"shell"}`, `"workflow"` unchanged, and anything else
  (including the new vocabulary's own strings written WITHOUT a marker,
  which no production writer ever does) → `"unknown"`. The P0-pinned null
  fallbacks survive: a workflow row's `ref` falls back to `""`, the
  command/prompt arm's `engine` falls back to `null`.
- `SAFE_TASK_ATTEMPT_ERROR_CODES` (`src/tasks/run/attempt-lifecycle.ts`) is a
  related but separate allowlist — it decides which error CODES are safe to
  surface verbatim in a `detail.error` column, not which result-kind
  strings are legal. Do not conflate the two when reading either file.
- **Removed 2026-08-28** (review finding against this phase's own close-out
  spec, `docs/plans/specs/p4-deletions-closeout.md`): `finishDisabledTask`
  and `preparedResultTarget`, quoted above as part of the moved essay, are no
  longer in `src/tasks/run/task-result.ts`. `finishDisabledTask`'s only
  caller — `run-task.ts`'s `shouldSkipUnactivatedTask` — was deleted by the
  SAME phase's P4-N6 (task source v4 has no document-level `enabled` to skip
  at fire time), which left `finishDisabledTask` fully dead; `preparedResultTarget`
  was in turn `finishDisabledTask`'s only caller, so it went dead with it.
  Deleting them also retired the `"disabled"` `TaskRunStatus` member
  `finishDisabledTask` was the sole producer of, and the now-unreachable
  `result.status === "disabled"` branch in `src/commands/tasks/tasks.ts`.
  This does not touch the D8 vocabulary or the legacy read mapping this ADR
  is about — every OTHER dispatch arm still builds its own
  `TaskRunResult.target` per the Decision above, and `task-history.ts`'s read
  side is unchanged.
- **Superseded 2026-09-01**: the "PERMANENT part of the codebase" ruling
  above (row B-51) is overturned. `task_history` is DB-owned data, and the
  legacy->current remap is deterministic and total, so it belongs in a
  one-time schema migration, not three permanently-recurring read-side
  branches (the mapping had been independently re-implemented at
  `src/tasks/run/task-history.ts`, `src/commands/health/improve-metrics.ts`'s
  `isAgentTaskHistoryRow`, and `src/commands/health/windows.ts`). State
  migration `025-task-history-vocabulary-backfill`
  (`src/core/state/migrations.ts`) now rewrites every legacy row's
  `target_kind` (and stamps `targetVocab: 2`) the first time a pre-migration
  state.db is opened by this or a later release; all three read sites were
  deleted and now read `target_kind` directly in the current vocabulary.

## Provenance

- Source: `src/tasks/run/task-result.ts` module header;
  `src/tasks/run/task-history.ts` module header (WRITE half only — the READ
  half's mapping rule stays in the code as a permanent invariant).
- Spec: `docs/plans/specs/p1b-model-extraction.md` §5.3, §6 F-2.
- Extracted: P4 (`docs/plans/specs/p4-deletions-closeout.md` §4.2), 2026-08-27.
