# description
Wrapper shape for REST responses under `/api/*` (Step 14.2). Five variants distinguished by the `kind` discriminator and which payload field is present (`items` for list kinds AND `'annotations.registered'`, `item` for single-resource kinds, `value` for `kind: 'config'` and the action-result kinds). The `/api/scan` and `/api/health` responses are exempt, they carry the underlying `ScanResult` / `IHealthResponse` shape directly. The `/api/graph` response is also exempt, it returns the formatter's native textual output (text/plain or text/markdown). Step 14.5.d adds the required `kindRegistry` field on every payload-bearing list / single / config variant so the UI can render Provider-declared kinds (label, color, icon) without hardcoding visuals; the sibling `providerRegistry` field carries the registered Providers' own identity (label, color, chip visibility) so the UI renders the active-lens dropdown and the per-node provider chip from the real Provider set instead of a hardcoded list. Sentinel kinds (`health`, `scan`, `graph`) stay exempt because they don't carry an envelope payload. Step 9.6 closes the action-result (R7) and `'annotations.registered'` (R7) gaps, both are payload-bearing but carry their own variant shapes (action-result: `value` + `elapsedMs`, no `filters`/`counts`/`kindRegistry`; annotations.registered: `items` + `counts.total`, no `filters`/`kindRegistry`) because they project read-only kernel surfaces orthogonal to the kindRegistry. Step 16 piece 1 (the findings workbench, inspector half) adds three kinds: `findings` rides the list shape with a REQUIRED `counts.dismissedExcluded` / `counts.fixedExcluded` honesty pair (stale rows ride `items` inline with their derived `stale` flag) and a locked per-item finding-row shape, `node.prob-extensions` rides the single shape (`item` = the finders / fixers / standalone launcher catalog), and `job.submitted` rides the action-result shape (`value` + `elapsedMs`, no registries). The change keeps `schemaVersion` at `'1'`, the BFF is greenfield (no released consumers depend on the prior shape), so a versioned migration buys nothing.

# properties.schemaVersion.description
Envelope shape version. Bumped only on breaking changes. The Step 14.5.d addition of `kindRegistry` keeps the version at `'1'` because there are no released consumers in the wild.

# properties.kind.description
Discriminator. List kinds (`nodes`, `links`, `issues`, `plugins`) carry `items` + `filters` + `counts` + `kindRegistry`. The `node` kind carries `item` + `kindRegistry`. The `config` kind carries `value` + `kindRegistry`. The `action.applied` kind (Step 17, `POST /api/actions/:pluginId/:actionId`) carries `value` + `elapsedMs` (no `filters` / `counts` / `kindRegistry`, it's an action-result projection orthogonal to the kindRegistry surface). The `annotations.registered` kind (Step 9.6.6, BFF half) carries `items` + `counts.total` (no `filters` / `kindRegistry`, pure read-only catalog projection). Step 16 piece 1 kinds: `findings` (list shape, `GET /api/nodes/:pathB64/findings`, `counts` additionally requires `dismissedExcluded` / `fixedExcluded`), `node.prob-extensions` (single shape, `GET /api/nodes/:pathB64/prob-extensions`), `job.submitted` (action-result shape, `POST /api/nodes/:pathB64/jobs`). The `jobs` kind (`GET /api/jobs`, the cross-corpus job list) rides a registry-less list shape (`items` + `filters` + `counts`, no `kindRegistry` / `providerRegistry` / `contributionsRegistry`). The `health` / `scan` / `graph` values are reserved for documentation parity with the routes that DON'T use this envelope.

# properties.items.description
Present when `kind` is one of the list kinds (`nodes`, `links`, `issues`, `plugins`). Empty array is valid and means the filter matched zero rows.

# properties.item.description
Present when `kind` is `'node'` or `'node.prob-extensions'`. Single-resource envelope payload; each variant below constrains the shape (the `node` variant `$ref`s `node.schema.json`, so the decorated node validates in full rather than as a bare object).

# properties.links.properties.incoming.description
Links whose `target` resolves to this node.

# properties.links.description
Present ONLY when `kind` is `'node'`, a sibling of `item` rather than a field inside it. The split is deliberate: `item` IS the node and validates against `node.schema.json`, so folding the relations in would turn it into a bundle and forfeit that validation. Both arrays are always present; empty means the node has no link in that direction.

# properties.issues.description
Present ONLY when `kind` is `'node'`, sibling of `item` for the same reason as `links`. Every persisted issue whose `nodeIds` include this node. Empty array is valid and means the node is clean.

# properties.value.description
Present when `kind` is `'config'`, `'action.applied'`, or `'job.submitted'`. For `'config'`, carries the merged effective config object. For `'action.applied'`, carries `{ actionId, nodePath, report }` (the generic Action-result payload from `POST /api/actions/:pluginId/:actionId`, `core/node-bump` included). For `'job.submitted'`, carries `{ jobId, nodePath, extensionId, supersededIds }` (the submit result from `POST /api/nodes/:pathB64/jobs`).

# properties.elapsedMs.description
Wall-clock milliseconds the BFF spent serving the request. Present on action-result envelopes (`kind: 'action.applied'`, `'job.submitted'`); absent elsewhere.

# properties.filters.description
Echo of the URL filters the server applied, normalized into a JSON-friendly shape (arrays for multi-value filters, `null` for absent ones). Helps the client correlate the response with the request.

# properties.kindRegistry.description
Catalog of node kinds active in the current scope, keyed by kind name. Built once per server boot from every enabled Provider's `kinds` map and embedded into every payload-bearing envelope so the UI can render kind tags / palette swatches / graph nodes against Provider-declared visuals (label, color, icon) without ever hardcoding a closed kind enum. Sentinel envelopes (`health`, `scan`, `graph`) are exempt. Each entry MAY carry contributions from multiple Providers when several declare the same kind name (e.g. Claude `agent` and OpenAI `agent`); the `providers` map keeps every contribution and `primaryProviderId` points at the one whose visuals drive the kind's primary CSS var. The kernel separately surfaces `provider-ambiguous` issues for files matched by more than one Provider; the UI may still receive the merged registry during the conflict window.

# properties.kindRegistry.additionalProperties.properties.primaryProviderId.description
Id of the Provider whose visuals drive the kind's primary CSS var (`--sm-kind-<kind>`). Set on first registration (first Provider in iteration order); subsequent contributors append to `providers` without overwriting the primary.

# properties.kindRegistry.additionalProperties.properties.providers.description
Per-provider visuals for this kind name. Keyed by Provider id (e.g. `'claude'`, `'codex'`). Lets the UI render a node painted with its own Provider's color via `entry.providers[node.provider]` when the kind name is shared across Providers.

# properties.contributionsRegistry.description
Catalog of registered view contributions active in the current scope, keyed by qualified contribution id `<pluginId>/<extensionId>/<contributionId>`. Built once per server boot from every enabled extension's `viewContributions` map and embedded into every payload-bearing envelope so the UI can fetch the catalog without a separate request. Sentinel envelopes (`health`, `scan`, `graph`), action-result envelopes (`action.applied`, `job.submitted`), and the catalog envelopes themselves (`annotations.registered`, `contributions.registered`) are exempt. Mirror of `kindRegistry`, parallel surface. Each entry references a slot by name from the closed catalog at `view-slots.schema.json#/$defs/SlotName`; the slot fixes both the renderer and the payload shape. Per-node payloads are delivered separately on `node` envelopes (single via `item.contributions`) or list envelopes (`items[].contributions`, only when `limit ≤ bff.maxBulkContributions`, default 200).

# properties.contributionsRegistry.additionalProperties.properties.priority.description
Optional ordering hint (default 100 when omitted). Slots whose `order` is `'priority'` sort contributions ASC by this value with alphabetical tie-break by qualified id. Mirror of `IViewContribution.priority` in `view-slots.schema.json#/$defs/ViewContribution`; propagated so the UI can apply the manifest-declared order without a second round-trip.

# properties.contributionsRegistry.additionalProperties.properties.pluginOrder.description
Optional inspector-only ordering hint, denormalised from the owning plugin's `plugin.json` `order` field (default 100). The inspector groups `inspector.body.panel.*` contributions into one collapsible section per plugin and sorts those sections ASC by this value, tie-break by plugin id. Every contribution of a given plugin carries the same value.

# properties.contributionsRegistry.additionalProperties.properties.extensionOrder.description
Optional inspector-only ordering hint, denormalised from the owning extension's `order` manifest field (default 100). Inside a plugin's inspector section, bricks are sorted ASC by this value, tie-break by `priority` then qualified id. Every contribution of a given extension carries the same value.

# properties.providerRegistry.description
Catalog of Providers registered in the current scope, keyed by Provider id. Built once per server boot from every registered Provider's `ui` block and embedded into every payload-bearing envelope so the UI renders the active-lens dropdown, the topbar lens chip, and the per-node provider chip from the real Provider set instead of a hardcoded list. Sentinel envelopes (`health`, `scan`, `graph`), action-result envelopes (`action.applied`, `job.submitted`), and the catalog envelopes (`annotations.registered`, `contributions.registered`) are exempt. Mirror of `kindRegistry`, parallel surface. The active lens itself (current value + filesystem-detected candidates + the enabled `selectable` set) is NOT here; it is served by `GET /api/active-provider`, a per-project dynamic surface orthogonal to this static boot catalog.

# properties.providerRegistry.additionalProperties.properties.isLens.description
True when this Provider is a selectable lens, projected from its `gatedByActiveLens` flag. The active-lens dropdown lists only `isLens: true` entries; the non-gated `core/markdown` base is `false` and never appears there. Independent of `selectable` (which marks which lenses are ENABLED right now): a disabled lens stays `isLens: true` and renders greyed.

# properties.providerRegistry.additionalProperties.properties.hideChip.description
When `true`, the UI suppresses this Provider's per-card chip (reserved for the universal `markdown` base). Combined with `isLens: false` the base appears on no lens surface at all.

# properties.providerRegistry.additionalProperties.properties.bodyField.description
Name of the parsed-frontmatter field that carries this Provider's node body, projected from its `read.bodyField` (see `provider.schema.json`). Present only for Providers whose prompt lives inside structured frontmatter rather than after a fence (OpenAI Codex sub-agents are pure TOML whose markdown prompt is `developer_instructions`). The UI uses it to render that field as the node body and to omit it from the metadata dump; absent for ordinary frontmatter-fence Providers.

# properties.providerRegistry.additionalProperties.properties.invocationSigil.description
Single glyph this lens's runtime uses to invoke a skill / command, projected from its `presentation.invocationSigil` (see `provider.schema.json`). The UI joins it against the active lens to paint the `invokes` edge-kind glyph (and tooltip example) in the link-kind palette: `/` for `claude` / `antigravity`, `$` for `codex`. Absent for lenses with no invocation channel (`agent-skills`, `markdown`), under which no `invokes` edge arises.

# properties.providerRegistry.additionalProperties.properties.mcpRegister.description
How an operator registers skill-map's MCP server with this lens's runtime, projected VERBATIM from the Provider's `mcpRegister` block (see `provider.schema.json` for the two flavours and the `{{url}}` placeholder). Travels on the wire so the Copy affordance is driven by the registered Provider set instead of a client-side catalog, which is what let an external Provider's lens fall back to a bare URL while the built-ins had a real command. Absent for a Provider that declares no recipe; the UI then copies the endpoint URL alone.

# properties.counts.properties.total.description
Total rows after filtering, before pagination is applied.

# properties.counts.properties.returned.description
Rows actually carried in `items` (≤ `limit`). Present on list-kind envelopes (`nodes`, `links`, `issues`, `plugins`, `findings`); absent on `'annotations.registered'` (no pagination, no filters, the catalog ships in its entirety in every response).

# properties.counts.properties.dismissedExcluded.description
Findings the default view held back because their (extension, type) class matches an ACTIVE sidecar suppression (`sm findings dismiss`, the read-time dismissal lens; top precedence over fixed and stale). REQUIRED on `kind: 'findings'` envelopes, absent elsewhere; 0 under an explicit `?dismissed=1` / `?fixed=1` / `?stale=1` bucket filter, mirroring `sm findings --json`.

# properties.counts.properties.fixedExcluded.description
Findings the default view held back because their `resolution` is `'fixed'` (already handled; a row that is BOTH fixed and stale counts here, state precedence; a suppressed row counts as dismissed instead). REQUIRED on `kind: 'findings'` envelopes, absent elsewhere. The default-view honesty device mirrored from `sm findings --json`: an empty `items` with a non-zero excluded count never claims a clean node. Always 0 when an explicit bucket filter is active (an explicit bucket view holds nothing back to report).

# properties.counts.properties.page.description
Pagination window. Present when the endpoint paginates (today: `/api/nodes` only).

# properties.counts.description
Tally + paging info. Present on every list / single envelope; absent on `health` / `scan` / `graph` responses (which don't use this envelope). The list variants additionally require `returned`; the `'annotations.registered'` variant only requires `total`; the `'findings'` variant requires `total` + `returned` + the `dismissedExcluded` / `fixedExcluded` honesty pair.

# oneOf[].description
List envelope, `items` payload + `filters` + `counts` (with `returned`) + `kindRegistry` + `providerRegistry` + `contributionsRegistry`. Used by `/api/nodes`, `/api/links`, `/api/issues`, `/api/plugins`.

# oneOf[].description
Findings list envelope, the per-node judgment tray served by `GET /api/nodes/:pathB64/findings` (Step 16 piece 1). Rides the list shape (`items` + `filters` + `counts` + the three registries) with two deltas locked here: `counts` additionally REQUIRES the default-view honesty pair `dismissedExcluded` / `fixedExcluded` (what the default view held back under the same filters, both 0 under an explicit `?dismissed=1` / `?fixed=1` / `?stale=1` bucket filter, mirroring `sm findings`; dismissed = the class matches an active sidecar suppression, the read-time lens, top precedence; stale rows ride `items` inline since 2026-07-20, flagged per row, never held back), and each item is one `state_findings` row projection (camelCase, the `sm findings --json` row shape plus the derived `stale` boolean; the internal `bodyHashAtGeneration` is NOT exposed).

# oneOf[].properties.items.items.properties.id.description
`state_findings.id`, the handle for `sm findings resolve <id>` / `sm findings dismiss <id>`.

# oneOf[].properties.items.items.properties.origin.description
`extension` = finder lane (the judgment a probabilistic finder Analyzer recorded); `kernel` = safety lane (kernel-synthesized reserved slugs).

# oneOf[].properties.items.items.properties.model.description
The recording agent's self-reported model id; `null` when undeclared.

# oneOf[].properties.items.items.properties.resolution.description
Lifecycle state; `null` = open (see `db-schema.md` §state_findings).

# oneOf[].properties.items.items.properties.resolutionActor.description
Who decided a `fixed` row; `null` for `human-decision` / open rows.

# oneOf[].properties.items.items.properties.resolutionBy.description
The fixer's qualified extension id; `null` for a purely human resolution.

# oneOf[].properties.items.items.properties.stale.description
Derived: the node body changed since the judgment (or the node left the scan).

# oneOf[].description
Cross-corpus job-queue list envelope served by `GET /api/jobs` (the read side of the UI queue inspector; HTTP face of `sm jobs list`). A REGISTRY-LESS list shape: `items` (each a public `Job` projection: every `job.schema.json` field EXCEPT the `nonce` record credential, which `job-lifecycle.md` §Atomic claim · Nonce exposure forbids on read surfaces) + `filters` (echo of the applied `status` / `extension` / `node`, `null` when absent) + `counts` (`total` = `returned` = `items.length`; the endpoint does not paginate). The kind / provider / contribution registries are intentionally absent, the queue projection is orthogonal to those catalogs (same rationale as the action-result and annotation / contribution-catalog variants), the SPA already holds them cached from a prior list call, and dropping them keeps the route on a narrow read-only deps bag. Rows are newest-first (`createdAt DESC, id DESC`).

# oneOf[].properties.items.description
Each item is the nonce-less public projection. Was an inlined COPY of the job shape, kept in sync by hand with `job.schema.json`; now a `$ref`, so the credential-omission rule has exactly one definition and cannot drift between the two files.

# oneOf[].description
Single-resource envelope, `item` payload + its `links` / `issues` siblings + the three registries, no `counts` / `filters`. Used by `/api/nodes/:pathB64`. The relations ride BESIDE `item`, not inside it, so `item` stays a Node and validates against `node.schema.json` (the shape was flipped to this at Step 14.5.a; the schema caught up later).

# oneOf[].description
Probabilistic-launcher envelope served by `GET /api/nodes/:pathB64/prob-extensions` (Step 16). Rides the single shape (`item` + the three registries, no `counts` / `filters`); the `item` is the node's launcher catalog for the inspector's finder buttons (ROADMAP §Step 16): `finders` (probabilistic Analyzers whose precondition matches the node AND that have at least one matching fixer, i.e. `fixerIds` non-empty; the UI renders each as a Detect button, Detect+Fix when the automatic toggle is on, DISABLED while `hasOpenFindings` is true since 2026-07-20, the fix lives on each finding row), `standalone` (probabilistic Analyzers matching the node with NO fixer, PLUS probabilistic Actions without `analyzerIds`; single-action buttons, `fixerIds` empty), and `issueFixers` (probabilistic Actions whose `analyzerIds` resolve to a DETERMINISTIC analyzer, such as `core/ai-reference-action` over `core/reference-broken`, listed ONLY while the node carries at least one matching open Issue; the UI renders each as a fix button ON the matching deterministic issue rows, never as a launcher button, user decision 2026-07-22 replacing the former standalone placement). A fixer paired with a probabilistic finder is never listed on its own: it surfaces through its finder's `fixerIds` and the tray's per-finding fix button submits it; the former per-finder-fixer split and the `fixers` bucket stay retired. The OPTIONAL fourth bucket `skills` carries the skill-action catalog (`skill-actions.md`): one entry per skill installed under `.skill-map/.agents/skills/`, on every node, deterministically; ABSENT is not empty (an implementation predating skill actions omits the field and stays conforming, an empty catalog emits `[]`, same rule as `findingsMaxSeverity`). Entry shapes in `#/$defs/ProbExtensionEntry` (finders / standalone), `#/$defs/IssueFixerEntry` (issueFixers) and `#/$defs/SkillActionEntry` (skills).

# oneOf[].properties.item.properties.skills.description
OPTIONAL skill-action bucket (`skill-actions.md`). Absent is NOT empty: absent means the implementation does not report skill actions at all, `[]` means an empty catalog.

# oneOf[].description
Value envelope, `value` payload + `kindRegistry` + `providerRegistry` + `contributionsRegistry`, no `counts` / `filters`. Used by `/api/config` and by `GET /api/config/resolution` (kind `config.resolution`), whose `value.rows[]` is the flattened effective config: one entry per leaf key as `{ key, value, layer, secret }`, `layer` naming the winning config layer (`defaults` / `project` / `project-local` / `override`, the loader's per-key provenance) and `secret: true` meaning the value was MASKED server-side (a plugin-extension setting declared `type: 'secret'` never reaches the wire in clear).

# oneOf[].description
Action-result envelope, `value` + `elapsedMs` siblings, no `filters` / `counts` / `kindRegistry` / `providerRegistry` / `contributionsRegistry`. Used by `POST /api/actions/:pluginId/:actionId` (`action.applied`, carries `{ actionId, nodePath, report }` plus the wall-clock duration). The registries are intentionally absent, the action result is orthogonal to every catalog and the SPA already has them cached from a prior list call.

# oneOf[].description
Job-submitted action-result envelope from `POST /api/nodes/:pathB64/jobs` (Step 16 piece 1), shaped like the `action.applied` variant: `value` + `elapsedMs`, no `filters` / `counts` / registries. `value` locks to `{ jobId, nodePath, extensionId, supersededIds }`; a non-empty `supersededIds` reports the fixer-supersede cancellations applied in the same transaction. The `nonce` record credential NEVER travels here, the processing agent claims it via `sm jobs claim --json` (`job-lifecycle.md` §Atomic claim · Nonce exposure).

# oneOf[].properties.value.properties.extensionId.description
The qualified id the submit resolved to, which may differ from the (possibly bare) id the request body carried.

# oneOf[].properties.value.properties.supersededIds.description
Stale queued sibling ids a FIXER submit cancelled in the same transaction (`job-lifecycle.md` §Findings injection for fixers · Supersede). Empty for non-fixer submits and for fixers with nothing to supersede.

# oneOf[].description
Annotation-catalog envelope, `items` + `counts.total` only, no `filters` / `kindRegistry` / `providerRegistry` / `contributionsRegistry` / `returned`. Used by `GET /api/annotations/registered` (Step 9.6.6, BFF half). The catalog is small (typically 0–50 entries), ships in its entirety on every response, and does not paginate; `counts.total` doubles as `items.length`.

# oneOf[].description
View-contributions-catalog envelope, `items` + `counts.total` only, no `filters` / `kindRegistry` / `providerRegistry` / `contributionsRegistry` / `returned`. Used by `GET /api/contributions/registered`. Mirror of `annotations.registered`. The catalog ships in entirety; `counts.total` doubles as `items.length`. Each item is an `IRegisteredViewContribution` shape: `{ pluginId, extensionId, contributionId, slot, label?, tooltip?, icon?, emptyText?, emitWhenEmpty? }`.

# oneOf[].description
Sentinel kinds, reserved for routes that do NOT carry an envelope payload at the wire level (`health`, `scan`, `graph`). They do not carry `kindRegistry`, `providerRegistry`, or `contributionsRegistry` either; clients that need any of them must call a payload-bearing endpoint at boot.

# allOf[].description
`links` / `issues` are exclusive to the single-node envelope. Expressed as ONE conditional rather than ten repetitions of `not: { anyOf: [...] }` (the idiom the sibling payload fields use per variant), because the same negation copied into every other variant is exactly the shape that goes stale when a variant is added: a new `kind` would silently be allowed to carry relations. The `if` matches the node envelope and adds nothing; every other kind takes the `else` and must omit both.

# $defs.ProbExtensionEntry.description
One launcher entry of the `node.prob-extensions` catalog (the inspector's finder buttons). `state` is the live queue state from `state_jobs` for the BUTTON's active job (`queued` / `running` when one exists, else `idle`). Because the tray's per-finding fix button submits the finder's fixers, the state is computed over the union of `{id} ∪ fixerIds` on this node, so a queued/running FIXER lights the finder's button (running wins over queued); `jobId` is that active job's id. `lastJudged` is the latest recorded execution for the finder itself (`null` when never judged). `fixerIds` is the finder's matching fixers (empty for standalone entries and for finders with none); `hasOpenFindings` DISABLES the finder button (open findings are handled from their rows; the button re-enables once none is open). `findingsMaxSeverity` drives the launcher's verdict mark: the highest severity among the extension's stored `state_findings` rows for this node. See the launcher-envelope variant for the `finders` / `standalone` classification.

# $defs.ProbExtensionEntry.properties.id.description
Qualified extension id (`<plugin>/<extension>`), the value `POST /api/nodes/:pathB64/jobs` accepts as `extension`.

# $defs.ProbExtensionEntry.properties.description.description
The extension manifest's required `description`, rendered as the launcher tooltip / subtitle.

# $defs.ProbExtensionEntry.properties.lastJudged.oneOf[].properties.at.description
Epoch milliseconds of the latest recorded execution for this (node, extension) pair.

# $defs.ProbExtensionEntry.properties.lastJudged.oneOf[].properties.model.description
The recording agent's self-reported model id at that execution; `null` when undeclared.

# $defs.ProbExtensionEntry.properties.fixerIds.description
Qualified ids of the fixer Actions whose `precondition.analyzerIds` name this finder (the inverse Modelo B lookup). Non-empty ONLY on `finders`-bucket entries; empty for standalone entries (finders with no fixer, and Actions without `analyzerIds`). In manual mode the button's Fix state submits each of these; in automatic mode the finder is submitted with `autoFix: true` and the kernel chains them.

# $defs.ProbExtensionEntry.properties.hasOpenFindings.description
True when the node currently carries at least one UNRESOLVED (not `fixed`), non-stale finding emitted by THIS finder's extension id. Drives the two-state button: `false` → Detect state (submit the finder), `true` → Fix state (submit the fixerIds). Always `false` for standalone entries.

# $defs.ProbExtensionEntry.properties.findingsMaxSeverity.description
OPTIONAL. Highest severity among the findings a read surface LISTS for this extension on the node, both origins: unresolved (neither `fixed` nor `dismissed`, so a `human-decision` row still counts) and not hidden by the class-suppression lens. STALE rows count, because they are listed (marked inline, `db-schema.md` §state_findings Stale rule); this differs from `hasOpenFindings`, which additionally excludes them because a stale row awaits a re-run rather than a fix. `null` when nothing is listed: every finding resolved, the last run found nothing, or the pair was never judged (`lastJudged` disambiguates). Drives the launcher's verdict mark (severity glyph while rows are listed, clean check when `null` with a `lastJudged`), so the mark always agrees with what the operator sees in the findings surface, and resolving the last row flips it to the check on the next read. ABSENT means the server does not report the verdict at all, which a client MUST distinguish from `null`: it renders no mark rather than a clean one. Optional so an implementation predating the field stays conforming; the reference implementation always emits it.

# $defs.ProbExtensionEntry.properties.jobId.description
The ACTIVE queued/running job's id for this button (the finder OR any of its `fixerIds` on this node, see `state`), the handle the UI's stop affordance cancels via POST /api/jobs/:jobId/cancel. `null` when idle.

# $defs.ProbExtensionEntry.properties.fixerBusy.properties.all.description
True when a whole-node fixer job (no frozen findingIds) is active: every row's fix affordance is busy.

# $defs.ProbExtensionEntry.properties.fixerBusy.properties.findingIds.description
Union of the ACTIVE subset fixer jobs' frozen finding ids (sorted). A row whose finding id appears here is busy.

# $defs.ProbExtensionEntry.properties.fixerBusy.description
Frozen finding targets of the ACTIVE fixer jobs for this finder (cli-contract.md, GET /api/nodes/:pathB64/prob-extensions): the tray derives each row's fix-button busy state from it so fixing one finding no longer spins every row. `null` when no fixer job is active.

# $defs.SkillActionEntry.description
One `skills` entry of the `node.prob-extensions` catalog: an operator-installed skill from the private `.skill-map/.agents/skills/` catalog (`skill-actions.md`), offered on every node. `state` / `jobId` mirror `ProbExtensionEntry` over the SKILL's own active jobs only (no fixerIds union; running wins over queued); `lastJudged` is its latest recorded execution for this node. Submitting posts the entry's `id` verbatim as the `extension` body field of `POST /api/nodes/:pathB64/jobs`.

# $defs.SkillActionEntry.properties.id.description
The `skill:<name>` submit target, `<name>` being the catalog subdirectory name verbatim.

# $defs.SkillActionEntry.properties.name.description
The skill's frontmatter `name`, the launcher label (skill ids carry no `<plugin>/` segment to shorten).

# $defs.SkillActionEntry.properties.description.description
The skill's frontmatter `description`, rendered as the launcher tooltip / subtitle.

# $defs.SkillActionEntry.properties.version.description
Resolved catalog version: frontmatter `version` when a string, else `metadata.version` when a string, else `0.0.0`. Informational; dedup correctness rides the body hashing into `promptTemplateHash`.

# $defs.SkillActionEntry.properties.lastJudged.oneOf[].properties.at.description
Epoch milliseconds of the latest recorded execution for this (node, skill) pair.

# $defs.SkillActionEntry.properties.lastJudged.oneOf[].properties.model.description
The recording agent's self-reported model id at that execution; `null` when undeclared.

# $defs.SkillActionEntry.properties.jobId.description
The ACTIVE queued/running job's id for this skill on this node, the handle the UI's stop affordance cancels via POST /api/jobs/:jobId/cancel. `null` when idle.

# $defs.IssueFixerEntry.description
One `issueFixers` entry of the `node.prob-extensions` catalog: a probabilistic Action whose `precondition.analyzerIds` resolve to a DETERMINISTIC analyzer (e.g. `core/ai-reference-action` over `core/reference-broken`), listed only while the node carries at least one matching open Issue. The UI renders it as a fix button ON each matching deterministic issue row (matched via `analyzerIds`), never as a launcher button (user decision 2026-07-22). `state` / `jobId` mirror `ProbExtensionEntry` but cover the ACTION's own jobs only (no fixerIds union); submitting fixes EVERY matching issue of the node in one job, so all matching rows share the busy state.

# $defs.IssueFixerEntry.properties.id.description
Qualified action id (`<plugin>/<extension>`), the value `POST /api/nodes/:pathB64/jobs` accepts as `extension`.

# $defs.IssueFixerEntry.properties.description.description
The action manifest's required `description`, rendered as the fix button's tooltip.

# $defs.IssueFixerEntry.properties.lastJudged.oneOf[].properties.at.description
Epoch milliseconds of the latest recorded execution for this (node, action) pair.

# $defs.IssueFixerEntry.properties.lastJudged.oneOf[].properties.model.description
The recording agent's self-reported model id at that execution; `null` when undeclared.

# $defs.IssueFixerEntry.properties.jobId.description
The ACTIVE queued/running job's id for this action on this node, the handle the UI's stop affordance cancels via POST /api/jobs/:jobId/cancel. `null` when idle.

# $defs.IssueFixerEntry.properties.analyzerIds.description
SHORT analyzer ids (as persisted on `scan_issues.analyzerId`, plugin prefix and `:sub-id` stripped from the action's declared `precondition.analyzerIds`), the row-match key: the UI shows this entry's fix button on every deterministic issue row whose `analyzerId` appears here.