# description
Row in `state_jobs`. Non-terminal state until it reaches `completed` or `failed`, at which point an `ExecutionRecord` is also written. The queue is kind-agnostic: `extensionId` names a probabilistic Action OR a probabilistic Analyzer (the columns were renamed from `action_id` / `action_version` when Analyzers joined the queue; `state_executions.extension_id` set the naming precedent).

# properties.extensionId.description
Qualified id of the submitted probabilistic extension (Action or Analyzer), e.g. `core/ai-summarizer-action`.

# properties.extensionVersion.description
Semver of the extension implementation at submit time. Frozen for this job.

# properties.extensionKind.description
Extension kind resolved at submit time, frozen like the version. `sm record` routes on this value (analyzer report → findings write-through; action report → summaries/enrichments conventions), keeping a plugin that ships both kinds under one extension id unambiguous end-to-end.

# properties.nodeId.description
Target `node.path`. `--all` submissions fan out into one job per node.

# properties.contentHash.description
sha256 over the NUL-joined (0x00) tuple (extensionId, extensionVersion, node.path, bodyHash, frontmatterHash, promptTemplateHash). Duplicate detection key.

# properties.nonce.description
Unique per-job token, the SOLE record credential: `sm record` requires it to close the job, and a mismatch rejects with exit code 4. NOT required by this root schema, because the shape most consumers see is the nonce-less projection (`#/$defs/PublicJob`); only the two credentialed surfaces carry it (`#/$defs/CredentialedJob`). Validate against the `$defs` that matches your surface rather than this root, which is deliberately permissive about the field so both projections can extend it.

# properties.findingIds.description
Finding-subset targeting for FIXER jobs, frozen at submit (`job-lifecycle.md` §Findings injection for fixers · Finding-subset targeting): the `state_findings` ids this job resolves; the findings injection narrows to them and the supersede/duplicate/running gates apply per set OVERLAP. `null` (or absent) = whole-node targeting, every open finding of the fixer's analyzers injects. Meaningless on non-fixer jobs.

# properties.autoFix.description
Per-job auto-fix opt-in, frozen at submit like `extensionKind`. When `true` on a finder job (`extensionKind = analyzer`), `sm record` chains the finder's fixers on completion: it resolves the Actions whose `precondition.analyzerIds` name this finder and submits each (see `job-lifecycle.md` §Auto-fix chain). Independent of the global `core/auto-fix` hook (they coexist; a double submit is a harmless supersede/duplicate). Ignored for Action jobs and when the finder recorded no findings. Default `false`.

# properties.status.description
Lifecycle state. Three terminal states: `completed`, `failed`, `cancelled`. `cancelled` (via `sm jobs cancel`) is a distinct terminal state, NOT a `failed` sub-reason. See `job-lifecycle.md` for allowed transitions.

# properties.failureReason.description
Populated ONLY when `status = failed`; `null` for `completed`, `queued`, `running`, and `cancelled` (a cancellation is self-explanatory and carries no reason). `user-failed` marks a job the operator forced to `failed` via `sm jobs fail` (symmetric to `sm jobs cancel`, which instead moves the job to the `cancelled` state). `job-file-missing` (legacy name preserved across the disk-to-DB shift) covers DB corruption where `state_jobs.content_hash` no longer resolves in `state_job_contents`, the runtime invariant should keep this state unreachable; the enum value exists as a defensive failure-mode label.

# properties.ttlSeconds.description
Optional TTL resolved at submit time from explicit operator sources only (`--ttl` flag, `jobs.perExtensionTtl`, `jobs.ttlSeconds`; see `job-lifecycle.md` §TTL resolution). NULL (the default) = the job never expires; the reaper skips it and `sm doctor`'s `jobs-overdue` check advises instead. Frozen.

# properties.expiresAt.description
Derived = claimedAt + ttlSeconds × 1000 for TTL-armed jobs. Used by auto-reap. Null while queued, and null for the life of a TTL-less job (never reaped).

# properties.submittedBy.description
Free-form: username, CI job id, or `skill:<skill-id>`. Optional provenance.

# $defs.PublicJob.description
The nonce-less projection, and the shape EVERY read surface MUST emit: `sm jobs list --json`, `sm jobs show --json`, `GET /api/jobs`, and the MCP job tools (`job-lifecycle.md` §Nonce exposure). The nonce is the sole record credential, so a passive reader of the queue must not be able to forge callbacks for jobs it never claimed. This is the shape those four surfaces already called "the public-job shape" in prose before it existed as a definition; the root schema used to REQUIRE the nonce, which made every one of them unsatisfiable. Absence is asserted, not merely permitted: a projection that leaks the credential fails here.

# $defs.CredentialedJob.description
The projection that DOES carry the record credential, emitted by exactly two surfaces: `sm jobs submit --json` (the creator's envelope) and the stored `state_jobs` row itself. `sm jobs claim --json` hands the credential over too, but in its own `{ id, nonce, content }` handover shape rather than as a Job. Requiring the nonce here (instead of on the root) keeps the guarantee that a credentialed surface cannot silently drop it while the public projections stay valid.