@skill-map/spec 0.53.0 → 0.54.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,16 +1,16 @@
1
1
  # Security scanner interface
2
2
 
3
- Normative contract for third-party security-scanning plugins (Snyk, Socket, custom rulesets, and similar). A security scanner is NOT a new extension kind, it is a **convention over the existing `Action` kind**. This document defines the convention so that:
3
+ Normative contract for third-party security-scanning plugins (Snyk, Socket, custom rulesets, similar). A security scanner is NOT a new extension kind, it is a **convention over the existing `Action` kind**, defined so that:
4
4
 
5
5
  - Multiple vendors can ship interoperable scanners.
6
6
  - `sm findings` can aggregate findings across scanners uniformly.
7
- - The UI can present a single "Security" panel regardless of which scanners are installed.
7
+ - The UI can present a single "Security" panel regardless of which scanners are present.
8
8
 
9
9
  ---
10
10
 
11
11
  ## Why a convention, not a new kind
12
12
 
13
- The six extension kinds are locked ([`architecture.md`](../architecture.md)). Adding a seventh for "security" would conflate concerns: scanners are really actions that produce a specialized report. A convention lets any Action opt into the scanner surface without kernel changes.
13
+ The six extension kinds are locked ([`architecture.md`](../architecture.md)). A seventh for "security" would conflate concerns: scanners are actions that produce a specialized report. A convention lets any Action opt into the scanner surface with no kernel changes.
14
14
 
15
15
  ---
16
16
 
@@ -33,13 +33,13 @@ Example manifest:
33
33
  }
34
34
  ```
35
35
 
36
- The kernel does NOT enforce the `security-` prefix, any Action may produce findings that conform to this schema. But `sm findings --security` and the UI's Security panel filter by prefix **OR** the `tags` label.
36
+ The kernel does NOT enforce the `security-` prefix, any Action may produce findings conforming to this schema. But `sm findings --security` and the UI's Security panel filter by prefix **OR** the `tags` label.
37
37
 
38
38
  ---
39
39
 
40
40
  ## Input
41
41
 
42
- The Action receives a standard invocation: a single node, or (via `--all`) a set of nodes matching the Action's `preconditions`. Scanners typically set:
42
+ The Action receives a standard invocation: a single node, or (via `--all`) the set of nodes matching the Action's `preconditions`. Scanners typically set:
43
43
 
44
44
  ```json
45
45
  {
@@ -47,15 +47,15 @@ The Action receives a standard invocation: a single node, or (via `--all`) a set
47
47
  }
48
48
  ```
49
49
 
50
- i.e. applies to every node. A scanner MAY narrow to specific kinds if the vendor's check only applies to, for example, shell-hook content.
50
+ i.e. applies to every node. A scanner MAY narrow to specific kinds if the vendor's check only applies to, e.g., shell-hook content.
51
51
 
52
- Scanners are **deterministic-mode** Actions by default: no LLM involvement. The Action runs its own logic (HTTP request to a vendor API, local regex scan, dependency check) and writes a report. Scanners MAY also be `probabilistic` Actions if the scanner relies on model analysis, the same report shape applies.
52
+ Scanners are **deterministic-mode** Actions by default: no LLM involvement. The Action runs its own logic (HTTP request to a vendor API, local regex scan, dependency check) and writes a report. Scanners MAY also be `probabilistic` Actions if they rely on model analysis; the same report shape applies.
53
53
 
54
54
  ---
55
55
 
56
56
  ## Output: the `SecurityReport` shape
57
57
 
58
- Every scanner MUST produce a report conforming to this shape. It extends [`report-base.schema.json`](../schemas/report-base.schema.json) with scanner-specific fields.
58
+ Every scanner MUST produce a report conforming to this shape, extending [`report-base.schema.json`](../schemas/report-base.schema.json) with scanner-specific fields.
59
59
 
60
60
  ```jsonc
61
61
  {
@@ -113,7 +113,7 @@ Every scanner MUST produce a report conforming to this shape. It extends [`repor
113
113
  | `version` | string (semver) | Scanner version at run time. |
114
114
  | `vendor` | string | Human-readable vendor name. |
115
115
  | `ranAt` | integer | Unix ms. |
116
- | `durationMs` | integer | How long the scan took. |
116
+ | `durationMs` | integer | Scan duration. |
117
117
 
118
118
  **Finding** (`findings[]`), ZERO OR MORE. Each finding MUST include:
119
119
 
@@ -121,14 +121,14 @@ Every scanner MUST produce a report conforming to this shape. It extends [`repor
121
121
  |---|---|---|
122
122
  | `id` | string | Globally unique finding id. Convention: `<scannerId>:<vendorFindingId>`. |
123
123
  | `severity` | enum | `error` / `warn` / `info`. Maps to deterministic issue severity for aggregation. |
124
- | `category` | string | One of the normative categories below, or a vendor-specific string prefixed `vendor:`. |
124
+ | `category` | string | A normative category below, or a vendor-specific string prefixed `vendor:`. |
125
125
  | `title` | string | Short human-readable summary. |
126
126
  | `description` | string | Longer explanation; markdown-friendly. |
127
127
  | `nodePath` | string | The `node.path` this finding references. |
128
128
  | `locations` | array\|null | Optional in-file locations. Each has `line` (required), `column`, `length`, `raw`. |
129
129
  | `references` | array\|null | External URLs (CVE, advisory, blog post). |
130
130
  | `remediation` | object\|null | `summary` (string), `autofixable` (boolean). Autofix is advisory, the kernel does not invoke it. |
131
- | `meta` | object\|null | Vendor-specific free-form. CVSS, CWE, CPE, etc. |
131
+ | `meta` | object\|null | Vendor-specific free-form: CVSS, CWE, CPE, etc. |
132
132
 
133
133
  **Stats** (`stats.*`), REQUIRED summary:
134
134
 
@@ -149,34 +149,34 @@ A `category` value SHOULD be one of these for interoperability:
149
149
  - `outdated`, version pinned well below current, not exploited but due for upgrade.
150
150
  - `policy-violation`, organization-level analyzer (naming, banned words, required disclaimer).
151
151
 
152
- Vendors MAY introduce their own category with the prefix `vendor:<slug>` (e.g. `vendor:socket:supply-chain`). Consumers that don't understand a vendor category MUST treat it as opaque but still display it.
152
+ Vendors MAY introduce their own category prefixed `vendor:<slug>` (e.g. `vendor:socket:supply-chain`). Consumers that don't understand a vendor category MUST treat it as opaque but still display it.
153
153
 
154
154
  ---
155
155
 
156
156
  ## Runtime model
157
157
 
158
158
  - Scanners are invoked through the standard job system: `sm job submit security-snyk -n <node.path>` or `sm job submit security-snyk --all`.
159
- - The report is persisted through the normal action report mechanism ([`state_executions`](../db-schema.md)`.report_path` points to the JSON file).
159
+ - The report is persisted through the normal action report mechanism ([`state_executions`](../db-schema.md)`.report_path` points to the JSON).
160
160
  - `sm findings --security` aggregates findings from reports whose action id starts with `security-`, merging across scanners, deduplicating by `finding.id`.
161
- - Implementations MAY also surface findings at scan time via a companion Analyzer (e.g. `security-findings-stale` flags nodes whose last security scan is older than a threshold). This is recommended but not normative.
161
+ - Implementations MAY also surface findings at scan time via a companion Analyzer (e.g. `security-findings-stale` flags nodes whose last scan exceeds a threshold). Recommended but not normative.
162
162
 
163
163
  ---
164
164
 
165
165
  ## Deduplication
166
166
 
167
- Finding ids MUST be stable: re-running the same scanner against unchanged input MUST produce the same `finding.id` values. This allows:
167
+ Finding ids MUST be stable: re-running the same scanner against unchanged input MUST produce the same `finding.id` values. This lets:
168
168
 
169
- - `sm findings --since <date>` to show only new findings.
170
- - The UI to diff scan-to-scan.
171
- - Aggregators to dedupe identical reports from multiple provider instances.
169
+ - `sm findings --since <date>` show only new findings.
170
+ - The UI diff scan-to-scan.
171
+ - Aggregators dedupe identical reports from multiple provider instances.
172
172
 
173
- The convention `<scannerId>:<vendorFindingId>` ensures cross-scanner uniqueness while staying human-readable.
173
+ The convention `<scannerId>:<vendorFindingId>` ensures cross-scanner uniqueness while staying readable.
174
174
 
175
175
  ---
176
176
 
177
177
  ## Aggregation into `sm findings`
178
178
 
179
- When a consumer calls `sm findings --security`, the kernel:
179
+ On `sm findings --security`, the kernel:
180
180
 
181
181
  1. Queries `state_executions` for actions whose id starts with `security-`.
182
182
  2. For each, loads the most recent report (per `(actionId, nodeId)`).
@@ -184,7 +184,7 @@ When a consumer calls `sm findings --security`, the kernel:
184
184
  4. Emits a normalized list: each entry includes `scanner`, `finding`, and `lastRanAt`.
185
185
  5. Applies optional filters: `--severity`, `--category`, `--node`, `--since`.
186
186
 
187
- The consumer sees a flat list of findings regardless of how many scanners produced them.
187
+ The consumer sees a flat list regardless of how many scanners produced it.
188
188
 
189
189
  ---
190
190
 
@@ -202,15 +202,15 @@ The Web UI's Security panel:
202
202
 
203
203
  ## Schema file location
204
204
 
205
- The JSON Schema for `SecurityReport` lives at `spec/schemas/summaries/security.schema.json` once Step 4 of the spec bootstrap completes. Until then, this document is the normative source and vendors SHOULD derive their own validator from it.
205
+ The JSON Schema for `SecurityReport` lives at `spec/schemas/summaries/security.schema.json` once Step 4 of the spec bootstrap completes. Until then, this document is the normative source and vendors SHOULD derive their validator from it.
206
206
 
207
- This is the only `summaries/*` schema that does NOT correspond to a node kind; it corresponds to an action category instead.
207
+ This is the only `summaries/*` schema that does NOT correspond to a node kind; it corresponds to an action category.
208
208
 
209
209
  ---
210
210
 
211
211
  ## Compliance
212
212
 
213
- A scanner that produces a report NOT conforming to `SecurityReport` is still a valid Action, but it does NOT show up in `sm findings --security` or the UI Security panel. Conforming is what unlocks the aggregation surface.
213
+ A scanner whose report does NOT conform to `SecurityReport` is still a valid Action, but does NOT show up in `sm findings --security` or the UI Security panel; conforming is what unlocks the aggregation surface.
214
214
 
215
215
  `sm plugins doctor` MAY emit a warning for Actions prefixed `security-` whose most recent report does not parse as `SecurityReport`.
216
216
 
@@ -221,7 +221,7 @@ A scanner that produces a report NOT conforming to `SecurityReport` is still a v
221
221
  - [`../architecture.md`](../architecture.md), extension kinds (Action) and the kernel contract.
222
222
  - [`../job-lifecycle.md`](../job-lifecycle.md), job submit/claim/record flow for scanner invocations.
223
223
  - [`../prompt-preamble.md`](../prompt-preamble.md), `report-base` shape (safety + confidence) that scanner reports extend.
224
- - [`../db-schema.md`](../db-schema.md), `state_executions` where scanner reports are persisted.
224
+ - [`../db-schema.md`](../db-schema.md), `state_executions`, where scanner reports are persisted.
225
225
 
226
226
  ---
227
227
 
package/job-events.md CHANGED
@@ -1,22 +1,22 @@
1
1
  # Job events
2
2
 
3
- Canonical event stream emitted during job execution. Every implementation MUST emit these events in the order described, with the shapes defined below. Consumers include the CLI pretty printer, the `--json` ndjson output, the Server's WebSocket broadcaster, and any third-party integration.
3
+ Canonical event stream emitted during job execution. Every implementation MUST emit these events in the order described, with the shapes below. Consumers: the CLI pretty printer, the `--json` ndjson output, the Server's WebSocket broadcaster, any third-party integration.
4
4
 
5
- This document is **normative**. The set of event types, their payload shapes, and their ordering analyzers are stable contracts.
5
+ This document is **normative**. The event types, payload shapes, and ordering analyzers are stable contracts.
6
6
 
7
7
  ---
8
8
 
9
9
  ## Transport
10
10
 
11
- Events are records produced by the kernel through `ProgressEmitterPort` (see [`architecture.md`](./architecture.md)). An implementation MUST provide three output adapters:
11
+ Events are records the kernel produces through `ProgressEmitterPort` (see [`architecture.md`](./architecture.md)). An implementation MUST provide three output adapters:
12
12
 
13
13
  | Adapter | Purpose | Format |
14
14
  |---|---|---|
15
15
  | `pretty` | Default TTY output. Human-readable, colored, line-based progress. | Free-form; not normative. |
16
16
  | `stream-output` | Pretty + model tokens inline. Debugging mode. | Free-form; not normative. |
17
- | `json` | Machine-readable ndjson. One event per line; each line is a complete JSON object. | **Normative.** Matches the shapes below. |
17
+ | `json` | Machine-readable ndjson. One event per line, each a complete JSON object. | **Normative.** Matches the shapes below. |
18
18
 
19
- The Server exposes the same events over WebSocket (`/ws`) using the same JSON shapes; each event is a single WebSocket text frame.
19
+ The Server exposes the same events over WebSocket (`/ws`) using the same JSON shapes; each event is a single WS text frame.
20
20
 
21
21
  ---
22
22
 
@@ -38,11 +38,11 @@ Every event is a JSON object with this envelope:
38
38
  |---|---|---|
39
39
  | `type` | always | One of the canonical event types below. |
40
40
  | `timestamp` | always | Unix milliseconds when the event was emitted. |
41
- | `runId` | always | Identifier of the invocation that emitted the event. CLI runner loops use `r-YYYYMMDD-HHMMSS-XXXX`; synthetic or non-job runs use one optional mode segment: `r-<mode>-YYYYMMDD-HHMMSS-XXXX`. Canonical modes are `ext` (external Skill claims), `scan` (scan runs), and `check` (standalone issue recomputations). |
41
+ | `runId` | always | Identifier of the invocation that emitted the event. CLI runner loops use `r-YYYYMMDD-HHMMSS-XXXX`; synthetic or non-job runs add one mode segment: `r-<mode>-YYYYMMDD-HHMMSS-XXXX`. Canonical modes: `ext` (external Skill claims), `scan` (scan runs), `check` (standalone issue recomputations). |
42
42
  | `jobId` | when job-scoped | The job the event refers to. Null for run-level events (`run.*`). |
43
- | `data` | per-event | Event-specific payload, shape defined below. |
43
+ | `data` | per-event | Event-specific payload, shape below. |
44
44
 
45
- Implementations MUST include every envelope field in every event, even if `jobId` is null. This simplifies consumers.
45
+ Implementations MUST include every envelope field in every event, even if `jobId` is null.
46
46
 
47
47
  Unknown fields in `data` MUST be ignored by consumers (forward compatibility).
48
48
 
@@ -50,11 +50,11 @@ Unknown fields in `data` MUST be ignored by consumers (forward compatibility).
50
50
 
51
51
  ## Event catalog
52
52
 
53
- Emitted in roughly this order during a `sm job run --all` invocation. The exact sequence may interleave for parallel runs (deferred to post-`v1.0`).
53
+ Emitted in roughly this order during `sm job run --all`. The sequence may interleave for parallel runs (deferred to post-`v1.0`).
54
54
 
55
55
  ### `run.started`
56
56
 
57
- Emitted once at the start of every `sm job run` invocation.
57
+ Emitted once at the start of every `sm job run`.
58
58
 
59
59
  ```json
60
60
  {
@@ -72,7 +72,7 @@ Emitted once at the start of every `sm job run` invocation.
72
72
 
73
73
  - `mode`: what the runner was asked to do.
74
74
  - `maxJobs`: cap on concurrent drain (`--max N` or null).
75
- - `filter`: resolved filter predicate, free-form object.
75
+ - `filter`: resolved filter predicate (free-form object).
76
76
 
77
77
  ### `run.reap.started`
78
78
 
@@ -105,7 +105,7 @@ Emitted after auto-reap finishes.
105
105
  }
106
106
  ```
107
107
 
108
- - `reapedIds` lists the jobs transitioned from `running` to `failed`. May be empty.
108
+ - `reapedIds` lists jobs transitioned from `running` to `failed`. May be empty.
109
109
 
110
110
  ### `job.claimed`
111
111
 
@@ -161,13 +161,13 @@ Emitted when the runner is about to execute the job content.
161
161
  }
162
162
  ```
163
163
 
164
- `command` is implementation-defined free-form; it is descriptive, not invokable. `contentHash` references the row in `state_job_contents` the runner is about to execute against, useful for downstream observers that want to correlate the spawn with the rendered content (which is in DB, not on disk).
164
+ `command` is implementation-defined free-form; descriptive, not invokable. `contentHash` references the `state_job_contents` row the runner is about to execute, letting observers correlate the spawn with the rendered content (in DB, not on disk).
165
165
 
166
- > **Hookable**, see [`architecture.md` §Hook · curated trigger set](./architecture.md#hook--curated-trigger-set). Plugins MAY subscribe a `hook` extension to this event for pre-flight checks or audit logging. Reactions only, hooks cannot block the spawn.
166
+ > **Hookable**, see [`architecture.md` §Hook · curated trigger set](./architecture.md#hook--curated-trigger-set). Plugins MAY subscribe a `hook` extension for pre-flight checks or audit logging. Reactions only, hooks cannot block the spawn.
167
167
 
168
168
  ### `model.delta`
169
169
 
170
- Emitted in `stream-output` mode only. Carries incremental model output.
170
+ Emitted in `stream-output` mode only; carries incremental model output.
171
171
 
172
172
  ```json
173
173
  {
@@ -182,7 +182,7 @@ Emitted in `stream-output` mode only. Carries incremental model output.
182
182
  }
183
183
  ```
184
184
 
185
- Consumers of the canonical `json` output MAY receive these events if the runner chose to emit them. `pretty` and `json` adapters MAY drop `model.delta` events for brevity.
185
+ Consumers of the canonical `json` output MAY receive these events if the runner emitted them. `pretty` and `json` adapters MAY drop `model.delta` events for brevity.
186
186
 
187
187
  ### `job.callback.received`
188
188
 
@@ -202,11 +202,11 @@ Emitted inside `sm record` when the callback arrives and passes nonce validation
202
202
  }
203
203
  ```
204
204
 
205
- `executionId` references the just-written `state_executions` row whose `report_json` carries the report payload. Consumers that need the content fetch it via `sm history --json` or directly from the DB; the event itself stays small.
205
+ `executionId` references the just-written `state_executions` row whose `report_json` carries the report payload. Consumers needing the content fetch it via `sm history --json` or the DB; the event stays small.
206
206
 
207
- `runId` on this event is the run that originally claimed the job. If the record is called from outside a CLI run, the canonical case being a Skill agent that called `sm job claim` + `sm record` without ever entering `sm job run`, the kernel MUST synthesize a `runId` of the form `r-ext-YYYYMMDD-HHMMSS-XXXX` (same timestamp + 4-hex shape as real run ids, with the `r-ext-` prefix reserved for externally-driven claims).
207
+ `runId` is the run that originally claimed the job. If `record` is called from outside a CLI run (canonical case: a Skill agent that called `sm job claim` + `sm record` without entering `sm job run`), the kernel MUST synthesize a `runId` of the form `r-ext-YYYYMMDD-HHMMSS-XXXX` (same timestamp + 4-hex shape as real run ids, `r-ext-` prefix reserved for externally-driven claims).
208
208
 
209
- Synthetic-run envelope: when a Skill agent claims a job, the kernel MUST emit, on the server's WebSocket and in the `--json` ndjson stream if active, a full envelope covering that claim:
209
+ Synthetic-run envelope: when a Skill agent claims a job, the kernel MUST emit a full envelope covering that claim, on the server's WebSocket and in the `--json` ndjson stream if active:
210
210
 
211
211
  ```
212
212
  run.started (mode="external")
@@ -217,7 +217,7 @@ run.started (mode="external")
217
217
  → run.summary
218
218
  ```
219
219
 
220
- The `run.started.data.mode` carries the literal string `external` so UI consumers can render skill-driven work differently from CLI-driven work. `run.summary` closes the synthetic run as soon as the callback is processed; one synthetic run always wraps exactly one job. This keeps the WebSocket broadcaster's contract ("every job event lives inside a run envelope") intact across both runner paths.
220
+ `run.started.data.mode` carries the literal `external` so UI consumers can render skill-driven work differently from CLI-driven work. `run.summary` closes the synthetic run as soon as the callback is processed; one synthetic run always wraps exactly one job. This keeps the WebSocket broadcaster's contract ("every job event lives inside a run envelope") intact across both runner paths.
221
221
 
222
222
  ### `job.completed`
223
223
 
@@ -239,9 +239,9 @@ Emitted when a job transitions to `completed`.
239
239
  }
240
240
  ```
241
241
 
242
- `executionId` references the `state_executions` row that holds the report payload (in `report_json`). The full report is intentionally NOT inlined in the event, keep events small and let consumers query the row when they want the body.
242
+ `executionId` references the `state_executions` row holding the report payload (in `report_json`). The full report is intentionally NOT inlined; events stay small, consumers query the row.
243
243
 
244
- > **Hookable**, see [`architecture.md` §Hook · curated trigger set](./architecture.md#hook--curated-trigger-set). The most common hookable event: notification, billing, downstream dispatch.
244
+ > **Hookable**, see [`architecture.md` §Hook · curated trigger set](./architecture.md#hook--curated-trigger-set). Most common hookable event: notification, billing.
245
245
 
246
246
  ### `job.failed`
247
247
 
@@ -262,9 +262,9 @@ Emitted when a job transitions to `failed` by any path.
262
262
  }
263
263
  ```
264
264
 
265
- `reason` enum matches [`execution-record.schema.json`](./schemas/execution-record.schema.json) `failureReason`. `message` is human-readable free-form; MAY be truncated for display.
265
+ `reason` enum matches [`execution-record.schema.json`](./schemas/execution-record.schema.json) `failureReason`. `message` is human-readable free-form, MAY be truncated for display.
266
266
 
267
- > **Hookable**, see [`architecture.md` §Hook · curated trigger set](./architecture.md#hook--curated-trigger-set). Hook subscribers commonly use this event for alerting and retry triggers. Filter by `data.reason` to narrow to a specific failure mode.
267
+ > **Hookable**, see [`architecture.md` §Hook · curated trigger set](./architecture.md#hook--curated-trigger-set). Common use: alerting and retry triggers. Filter by `data.reason` to narrow to a specific failure mode.
268
268
 
269
269
  ### `run.summary`
270
270
 
@@ -310,15 +310,15 @@ run.started
310
310
 
311
311
  A parallel implementation MAY interleave per-job sequences across different `jobId` values, but MUST preserve ordering within a single `jobId`.
312
312
 
313
- `job.failed` with reason `abandoned` MAY appear without a matching `job.claimed` in the current run, it refers to a job claimed in a previous run that expired before the next reap.
313
+ `job.failed` with reason `abandoned` MAY appear without a matching `job.claimed` in the current run: it refers to a job claimed in a previous run that expired before the next reap.
314
314
 
315
315
  ---
316
316
 
317
317
  ## Non-job events (Stability: experimental)
318
318
 
319
- These event families cover kernel activity other than job execution. They share the common envelope (`type`, `timestamp`, `runId`, `jobId`, `data`). For non-job events `jobId` is always `null`; `runId` identifies the invocation that produced the event, a scan gets an `r-scan-YYYYMMDD-HHMMSS-XXXX` id, an issue recomputation outside a scan gets an `r-check-...` id, following the same `r-<mode>-...` shape as the external-Skill synthetic envelope (`r-ext-...`).
319
+ These event families cover kernel activity other than job execution. They share the common envelope (`type`, `timestamp`, `runId`, `jobId`, `data`). For non-job events `jobId` is always `null`; `runId` identifies the invocation: a scan gets an `r-scan-YYYYMMDD-HHMMSS-XXXX` id, an issue recomputation outside a scan an `r-check-...` id, following the same `r-<mode>-...` shape as the external-Skill envelope (`r-ext-...`).
320
320
 
321
- The **shapes below are experimental through spec v0.x**. The reference impl starts emitting them at Step 13 alongside the WebSocket broadcaster; once real consumers exercise the stream, the fields lock. Bumping them to `stable` is a minor spec bump; changes to field shapes before `stable` are allowed without a major bump (per [`versioning.md`](./versioning.md) §Pre-1.0).
321
+ The **shapes below are experimental through spec v0.x**. The reference impl starts emitting them at Step 13 alongside the WebSocket broadcaster; once real consumers exercise the stream, the fields lock. Bumping to `stable` is a minor spec bump; field-shape changes before `stable` are allowed without a major bump (per [`versioning.md`](./versioning.md) §Pre-1.0).
322
322
 
323
323
  ### Scan events
324
324
 
@@ -379,11 +379,11 @@ Emitted once at scan end.
379
379
  }
380
380
  ```
381
381
 
382
- > **Hookable**, see [`architecture.md` §Hook · curated trigger set](./architecture.md#hook--curated-trigger-set). Post-scan reaction (Slack notification, CI gate, summary email).
382
+ > **Hookable**, see [`architecture.md` §Hook · curated trigger set](./architecture.md#hook--curated-trigger-set). Post-scan reaction (Slack notification, CI gate).
383
383
 
384
384
  #### `extractor.completed`
385
385
 
386
- Emitted once per registered Extractor, after the full walk completes. Aggregated, NOT per-node, per-node fan-out lives in `scan.progress`, which is intentionally not hookable.
386
+ Emitted once per registered Extractor, after the full walk. Aggregated, NOT per-node; per-node fan-out lives in `scan.progress`, which is intentionally not hookable.
387
387
 
388
388
  ```json
389
389
  {
@@ -399,11 +399,11 @@ Emitted once per registered Extractor, after the full walk completes. Aggregated
399
399
 
400
400
  `extractorId` is the qualified extension id (`<plugin-id>/<id>`).
401
401
 
402
- > **Hookable**, see [`architecture.md` §Hook · curated trigger set](./architecture.md#hook--curated-trigger-set). Per-Extractor metrics, audit. Filter by `data.extractorId` to scope to a single Extractor.
402
+ > **Hookable**, see [`architecture.md` §Hook · curated trigger set](./architecture.md#hook--curated-trigger-set). Per-Extractor metrics. Filter by `data.extractorId` to scope to one Extractor.
403
403
 
404
404
  #### `analyzer.completed`
405
405
 
406
- Emitted once per registered Analyzer, after every issue has been validated.
406
+ Emitted once per registered Analyzer, after every issue is validated.
407
407
 
408
408
  ```json
409
409
  {
@@ -419,11 +419,11 @@ Emitted once per registered Analyzer, after every issue has been validated.
419
419
 
420
420
  `analyzerId` is the qualified extension id.
421
421
 
422
- > **Hookable**, see [`architecture.md` §Hook · curated trigger set](./architecture.md#hook--curated-trigger-set). Per-Analyzer alerting, downstream tooling. Filter by `data.analyzerId`.
422
+ > **Hookable**, see [`architecture.md` §Hook · curated trigger set](./architecture.md#hook--curated-trigger-set). Per-Analyzer alerting. Filter by `data.analyzerId`.
423
423
 
424
424
  #### `action.completed`
425
425
 
426
- Emitted once per Action invocation, after the report has been recorded.
426
+ Emitted once per Action invocation, after the report is recorded.
427
427
 
428
428
  ```json
429
429
  {
@@ -439,13 +439,13 @@ Emitted once per Action invocation, after the report has been recorded.
439
439
  }
440
440
  ```
441
441
 
442
- `actionId` is the qualified extension id; `node` carries the target node summary (full `Node` shape per [`schemas/node.schema.json`](./schemas/node.schema.json) is forward-compatible). Lands alongside the job subsystem at Step 10.
442
+ `actionId` is the qualified extension id; `node` carries the target node summary (full `Node` shape per [`schemas/node.schema.json`](./schemas/node.schema.json) is forward-compatible). Lands at Step 10 with the job subsystem.
443
443
 
444
- > **Hookable**, see [`architecture.md` §Hook · curated trigger set](./architecture.md#hook--curated-trigger-set). Per-Action notification, integration glue. Filter by `data.actionId`.
444
+ > **Hookable**, see [`architecture.md` §Hook · curated trigger set](./architecture.md#hook--curated-trigger-set). Per-Action notification. Filter by `data.actionId`.
445
445
 
446
446
  ### Issue events
447
447
 
448
- Emitted by the scan after `scan.completed` when the new scan's issue set differs from the previous one. Enables a UI "issue inbox" to update incrementally without re-fetching the full list.
448
+ Emitted by the scan after `scan.completed` when the new scan's issue set differs from the previous. Lets a UI "issue inbox" update incrementally without re-fetching the full list.
449
449
 
450
450
  #### `issue.added`
451
451
 
@@ -466,7 +466,7 @@ Emitted by the scan after `scan.completed` when the new scan's issue set differs
466
466
 
467
467
  #### `issue.resolved`
468
468
 
469
- Emitted when an issue present in the previous scan is absent from the new one.
469
+ Emitted when an issue present in the previous scan is absent from the new.
470
470
 
471
471
  ```json
472
472
  {
@@ -481,7 +481,7 @@ Emitted when an issue present in the previous scan is absent from the new one.
481
481
  }
482
482
  ```
483
483
 
484
- Issue diffing is keyed on `(analyzerId, nodeIds sorted, message)`, same key → same issue. A payload change on the same key emits no event; consumers re-read full issue detail from `sm check` when needed.
484
+ Issue diffing is keyed on `(analyzerId, nodeIds sorted, message)`: same key → same issue. A payload change on the same key emits no event; consumers re-read full issue detail from `sm check`.
485
485
 
486
486
  ---
487
487
 
@@ -501,7 +501,7 @@ If an event payload cannot be serialized (internal bug), the implementation MUST
501
501
  }
502
502
  ```
503
503
 
504
- Consumers MAY treat `emitter.error` as a soft failure (log and continue). Implementations MUST NOT crash the run because of a serialization failure.
504
+ Consumers MAY treat `emitter.error` as a soft failure (log and continue). Implementations MUST NOT crash the run on a serialization failure.
505
505
 
506
506
  ---
507
507
 
@@ -515,14 +515,14 @@ Consumers MAY treat `emitter.error` as a soft failure (log and continue). Implem
515
515
 
516
516
  ## Stability
517
517
 
518
- The **job event type list** (`run.*`, `job.*`, `model.delta`, `emitter.error`) is stable as of spec v1.0.0. Adding a new event type is a minor bump. Removing or renaming one is a major bump.
518
+ The **job event type list** (`run.*`, `job.*`, `model.delta`, `emitter.error`) is stable as of spec v1.0.0. Adding a new event type is a minor bump; removing or renaming one is a major bump.
519
519
 
520
- **Adding** fields to `data` is a minor bump. Changing a field's type or removing a field is a major bump.
520
+ **Adding** fields to `data` is a minor bump; changing a field's type or removing a field is a major bump.
521
521
 
522
522
  Consumers MUST ignore unknown fields (forward compatibility).
523
523
 
524
524
  The envelope (`type`, `timestamp`, `runId`, `jobId`, `data`) is stable. Adding an envelope field is a major bump because every consumer would need to handle it.
525
525
 
526
- The **non-job event families** (`scan.*`, `issue.*`, `extractor.completed`, `analyzer.completed`, `action.completed`) are marked **experimental** across spec v0.x. They ship alongside the WebSocket broadcaster at Step 13 of the reference impl; shapes may tighten before a stable tag lands. Once promoted to `stable` (a minor spec bump), the same add/remove/rename semantics as the job events apply.
526
+ The **non-job event families** (`scan.*`, `issue.*`, `extractor.completed`, `analyzer.completed`, `action.completed`) are **experimental** across spec v0.x. They ship alongside the WebSocket broadcaster at Step 13 of the reference impl; shapes may tighten before a stable tag lands. Once promoted to `stable` (a minor spec bump), the same add/remove/rename semantics as the job events apply.
527
527
 
528
- The **Hook curated trigger set** (eight hookable lifecycle events; see [`architecture.md` §Hook · curated trigger set](./architecture.md#hook--curated-trigger-set)) is itself stable as of the same minor in which it lands: adding a hookable trigger is a minor bump, removing or renaming one is a major bump. The curation policy ("a hook subscribes only to a deliberately small set") is normative, surface noise reduction is the entire point.
528
+ The **Hook curated trigger set** (eight hookable lifecycle events; see [`architecture.md` §Hook · curated trigger set](./architecture.md#hook--curated-trigger-set)) is stable as of the minor in which it lands: adding a hookable trigger is a minor bump, removing or renaming one is a major bump. The curation policy ("a hook subscribes only to a deliberately small set") is normative; surface noise reduction is the point.