# description
Intermediate Representation (IR) emitted by extractors during a scan. A Signal is a *candidate* detection: zero, one, or many interpretations of the same piece of source text or structured data. The kernel's resolver phase consumes `Signal[]` and produces final `Link[]` by selecting a winning candidate per Signal (or rejecting all and emitting none) using the active Provider's resolution rules. Opt-in for plugin authors: an extractor MAY emit `Signal`s via `ctx.emitSignal()` when the detection carries genuine ambiguity (multiple plausible kinds, multiple plausible targets, byte-range awareness for collision detection), OR continue calling `ctx.emitLink()` directly when its detection is unambiguous. The two paths coexist; resolved Link rows look identical regardless of origin. Stability: experimental.

# properties.source.description
`node.path` of the originating node (the file or virtual entity in which the signal was detected).

# properties.scope.description
Where in the source the signal was detected. `body` = markdown body or equivalent prose payload. `frontmatter` = parsed metadata block at the top of the file. `sidecar` = co-located `.sm` overlay. Extractors specialized for non-prose sources (config files, AGENTS.md cascade) emit either `body` (if they treat the whole file as prose) or `frontmatter` (if they read structured fields).

# properties.range.description
Byte-range location within the source. Required for `scope: 'body'`, optional otherwise. Powers collision detection between detectors (two extractors emitting Signals with overlapping ranges) and code-block awareness (the orchestrator can mark ranges that fall inside code spans).

# properties.range.properties.start.description
Inclusive byte offset of the first character.

# properties.range.properties.end.description
Exclusive byte offset one past the last character.

# properties.range.properties.line.description
Optional 1-indexed FILE-absolute line number containing `start`, counting the frontmatter block, so it matches the author's editor. Extractors emit body-relative lines (via `computeLineStarts` / `lineFor` over the body they receive); the orchestrator adds the parser-owned `bodyLineOffset` (the frontmatter block's line count) to every body-scoped Signal at emit time. The offset is 0, and the line stays body-relative, when no absolute mapping exists (a `bodyField` provider whose prose lives inside a frontmatter field). The resolver preserves the value into `link.location.line` without re-walking the body; absent when the extractor does not track lines, the resolver falls back to `1`. Note `start` / `end` remain BODY byte offsets (they power collision detection over the body text); only `line` is file-absolute.

# properties.fieldPath.description
Structured-data location within `frontmatter` or `sidecar` scopes. Each entry is a step of the path: object keys are strings, array indices are integers serialized as strings. Example: `['tools', '0']` points to the first entry of the `tools` array. Null when the signal is body-scoped or when the extractor doesn't track field locations.

# properties.raw.description
Verbatim matched text (for body scope) or stringified value (for frontmatter / sidecar scope). Used for debugging, UI tooltips, and collision-key dedup.

# properties.context.description
Provider-determined surface context. `code-block` = inside a fenced code block (most providers ignore these). `inline-code` = inside backticks. `escaped` = preceded by `\` or otherwise marked literal. Null when the signal is in normal prose or when the context concept doesn't apply (frontmatter / sidecar scopes). Drives extraction filtering, the resolver's confidence weighting, and the post-walk resolution gate for code-region triggers (an unresolved `mentions` / `invokes` link whose every occurrence carries a code-region context is pruned; see architecture.md §Extractor · code-region triggers).

# properties.candidates.description
One or more alternative interpretations of the same signal. The resolver picks ONE as the winner (becomes a Link) or rejects all (no Link emitted). Multiple candidates from the same `extractorId` are allowed (e.g. one detector may emit both a `references` and a `mentions` hypothesis for the same `@token` and let the resolver decide).

# properties.candidates.items.properties.extractorId.description
Id of the extractor that contributed this candidate.

# properties.candidates.items.properties.kind.description
Proposed link kind, matching `link.schema.json#/properties/kind/enum`. Closed enum in v1; provider-specific kinds wait until a concrete need emerges.

# properties.candidates.items.properties.target.description
Proposed `node.path` of the destination. MAY refer to a missing node (the resolver does not validate existence); the `broken-ref` analyzer reports the gap downstream.

# properties.candidates.items.properties.confidence.description
Extractor's self-assessed probability that this interpretation is the correct one. Reference scoring (guideline, not contract): `1.0` = structured input (sidecar annotation, parsed JSON pointer), `0.95` = unambiguous syntax (`[text](file.md)`, `https://...`), `0.85` = strong signal with one degree of inference (`@file.md` with known extension), `0.5` = genuine ambiguity (`@bare-handle` could be agent, file, or generic mention). Drives UI edge opacity downstream.

# properties.candidates.items.properties.rationale.description
Optional human-readable explanation of WHY this candidate has the assigned confidence. Surfaced in inspector tooltips and debug output. Keep it short, e.g. `'ends in .md'`, `'no extension, no path prefix'`, `'declared in tools[] array'`.

# properties.candidates.items.properties.trigger.description
Trigger-style metadata when this candidate represents a textual invocation (`@x`, `/y`). Null otherwise. Mirrors `link.schema.json#/properties/trigger`.

# properties.resolution.description
Resolver outcome annotation, populated by the kernel resolver phase after `resolveSignals` runs. Absent before the resolver fires (raw extractor output). When `outcome` is `materialised`, `winnerIndex` points into `candidates[]` and a corresponding `Link` was emitted. When `outcome` is `rejected`, `rejectedBy` carries the reason (a cross-extractor range-overlap collision). Both materialised and rejected Signals remain accessible to analyzers via `IAnalyzerContext.signals` so the `core/extractor-collision` analyzer can surface losers as `warn` issues.

# properties.resolution.properties.outcome.description
Whether the resolver materialised this Signal's winning candidate as a `Link` (`materialised`) or rejected the whole Signal (`rejected`).

# properties.resolution.properties.winnerIndex.description
Index into `candidates[]` of the winning candidate when `outcome === 'materialised'`. Absent on rejection.

# properties.resolution.properties.rejectedBy.description
Set when the Signal lost a cross-extractor range-overlap collision against another Signal at the same source. Names the winner so an analyzer (or the operator drilling into the sidecar) can see WHO won and WHY.

# properties.resolution.properties.rejectedBy.properties.source.description
`node.path` of the winning Signal. Always equal to this Signal's `source` today, the field is explicit so future cross-node collision detection can populate it without a schema migration.

# properties.resolution.properties.rejectedBy.properties.range.description
Byte-range of the winning Signal. Mirrors the shape of `Signal.range`.

# properties.resolution.properties.rejectedBy.properties.extractorId.description
Qualified id (`<plugin>/<extractor>`) of the winning candidate's extractor.

# properties.resolution.properties.rejectedBy.properties.reason.description
Which tiebreak rule decided the winner. The four rules apply in this order: 1) `kind-priority` (provider `resolverRules.kindPriority`), 2) `higher-confidence` (numeric confidence DESC), 3) `longer-range` (`end - start` DESC), 4) `earlier-declaration` (extractor registration order).