@skill-map/spec 0.1.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.
Files changed (43) hide show
  1. package/CHANGELOG.md +96 -0
  2. package/README.md +105 -0
  3. package/architecture.md +218 -0
  4. package/cli-contract.md +336 -0
  5. package/conformance/README.md +140 -0
  6. package/conformance/cases/basic-scan.json +17 -0
  7. package/conformance/cases/kernel-empty-boot.json +24 -0
  8. package/conformance/fixtures/minimal-claude/agents/reviewer.md +16 -0
  9. package/conformance/fixtures/minimal-claude/commands/status.md +17 -0
  10. package/conformance/fixtures/minimal-claude/hooks/pre-commit.md +13 -0
  11. package/conformance/fixtures/minimal-claude/notes/architecture.md +11 -0
  12. package/conformance/fixtures/minimal-claude/skills/hello.md +22 -0
  13. package/conformance/fixtures/preamble-v1.txt +54 -0
  14. package/db-schema.md +359 -0
  15. package/dispatch-lifecycle.md +213 -0
  16. package/index.json +205 -0
  17. package/interfaces/security-scanner.md +233 -0
  18. package/job-events.md +322 -0
  19. package/package.json +49 -0
  20. package/plugin-kv-api.md +208 -0
  21. package/prompt-preamble.md +152 -0
  22. package/schemas/conformance-case.schema.json +185 -0
  23. package/schemas/execution-record.schema.json +88 -0
  24. package/schemas/frontmatter/agent.schema.json +22 -0
  25. package/schemas/frontmatter/base.schema.json +136 -0
  26. package/schemas/frontmatter/command.schema.json +39 -0
  27. package/schemas/frontmatter/hook.schema.json +29 -0
  28. package/schemas/frontmatter/note.schema.json +11 -0
  29. package/schemas/frontmatter/skill.schema.json +37 -0
  30. package/schemas/issue.schema.json +54 -0
  31. package/schemas/job.schema.json +75 -0
  32. package/schemas/link.schema.json +66 -0
  33. package/schemas/node.schema.json +95 -0
  34. package/schemas/plugins-registry.schema.json +99 -0
  35. package/schemas/project-config.schema.json +87 -0
  36. package/schemas/report-base.schema.json +41 -0
  37. package/schemas/scan-result.schema.json +71 -0
  38. package/schemas/summaries/agent.schema.json +46 -0
  39. package/schemas/summaries/command.schema.json +50 -0
  40. package/schemas/summaries/hook.schema.json +43 -0
  41. package/schemas/summaries/note.schema.json +37 -0
  42. package/schemas/summaries/skill.schema.json +57 -0
  43. package/versioning.md +94 -0
package/index.json ADDED
@@ -0,0 +1,205 @@
1
+ {
2
+ "specVersion": "0.0.1",
3
+ "dialect": "https://json-schema.org/draft/2020-12/schema",
4
+ "canonicalUrlPrefix": "https://skill-map.dev/spec/v0",
5
+ "schemas": {
6
+ "topLevel": [
7
+ {
8
+ "id": "node",
9
+ "path": "schemas/node.schema.json"
10
+ },
11
+ {
12
+ "id": "link",
13
+ "path": "schemas/link.schema.json"
14
+ },
15
+ {
16
+ "id": "issue",
17
+ "path": "schemas/issue.schema.json"
18
+ },
19
+ {
20
+ "id": "scan-result",
21
+ "path": "schemas/scan-result.schema.json"
22
+ },
23
+ {
24
+ "id": "execution-record",
25
+ "path": "schemas/execution-record.schema.json"
26
+ },
27
+ {
28
+ "id": "project-config",
29
+ "path": "schemas/project-config.schema.json"
30
+ },
31
+ {
32
+ "id": "plugins-registry",
33
+ "path": "schemas/plugins-registry.schema.json"
34
+ },
35
+ {
36
+ "id": "job",
37
+ "path": "schemas/job.schema.json"
38
+ },
39
+ {
40
+ "id": "report-base",
41
+ "path": "schemas/report-base.schema.json"
42
+ },
43
+ {
44
+ "id": "conformance-case",
45
+ "path": "schemas/conformance-case.schema.json"
46
+ }
47
+ ],
48
+ "frontmatter": [
49
+ {
50
+ "id": "frontmatter/base",
51
+ "path": "schemas/frontmatter/base.schema.json"
52
+ },
53
+ {
54
+ "id": "frontmatter/skill",
55
+ "path": "schemas/frontmatter/skill.schema.json"
56
+ },
57
+ {
58
+ "id": "frontmatter/agent",
59
+ "path": "schemas/frontmatter/agent.schema.json"
60
+ },
61
+ {
62
+ "id": "frontmatter/command",
63
+ "path": "schemas/frontmatter/command.schema.json"
64
+ },
65
+ {
66
+ "id": "frontmatter/hook",
67
+ "path": "schemas/frontmatter/hook.schema.json"
68
+ },
69
+ {
70
+ "id": "frontmatter/note",
71
+ "path": "schemas/frontmatter/note.schema.json"
72
+ }
73
+ ],
74
+ "summaries": [
75
+ {
76
+ "id": "summaries/skill",
77
+ "path": "schemas/summaries/skill.schema.json"
78
+ },
79
+ {
80
+ "id": "summaries/agent",
81
+ "path": "schemas/summaries/agent.schema.json"
82
+ },
83
+ {
84
+ "id": "summaries/command",
85
+ "path": "schemas/summaries/command.schema.json"
86
+ },
87
+ {
88
+ "id": "summaries/hook",
89
+ "path": "schemas/summaries/hook.schema.json"
90
+ },
91
+ {
92
+ "id": "summaries/note",
93
+ "path": "schemas/summaries/note.schema.json"
94
+ }
95
+ ]
96
+ },
97
+ "prose": [
98
+ {
99
+ "file": "README.md",
100
+ "title": "README"
101
+ },
102
+ {
103
+ "file": "versioning.md",
104
+ "title": "Versioning policy"
105
+ },
106
+ {
107
+ "file": "CHANGELOG.md",
108
+ "title": "Spec changelog"
109
+ },
110
+ {
111
+ "file": "architecture.md",
112
+ "title": "Architecture"
113
+ },
114
+ {
115
+ "file": "cli-contract.md",
116
+ "title": "CLI contract"
117
+ },
118
+ {
119
+ "file": "dispatch-lifecycle.md",
120
+ "title": "Dispatch lifecycle"
121
+ },
122
+ {
123
+ "file": "job-events.md",
124
+ "title": "Job events"
125
+ },
126
+ {
127
+ "file": "prompt-preamble.md",
128
+ "title": "Prompt preamble"
129
+ },
130
+ {
131
+ "file": "db-schema.md",
132
+ "title": "Database schema"
133
+ },
134
+ {
135
+ "file": "plugin-kv-api.md",
136
+ "title": "Plugin KV API"
137
+ }
138
+ ],
139
+ "interfaces": [
140
+ {
141
+ "file": "interfaces/security-scanner.md",
142
+ "title": "Security scanner interface"
143
+ }
144
+ ],
145
+ "conformance": {
146
+ "casesDir": "conformance/cases",
147
+ "fixturesDir": "conformance/fixtures",
148
+ "cases": [
149
+ {
150
+ "id": "basic-scan",
151
+ "file": "conformance/cases/basic-scan.json"
152
+ },
153
+ {
154
+ "id": "kernel-empty-boot",
155
+ "file": "conformance/cases/kernel-empty-boot.json"
156
+ }
157
+ ]
158
+ },
159
+ "integrity": {
160
+ "algorithm": "sha256",
161
+ "files": {
162
+ "CHANGELOG.md": "30d0d0c2e9fe7cf781067b55c37eeacbbb9ea5e83c1acaf4a0e3bdb32c9fbc53",
163
+ "README.md": "d2b4c6debb9b7c4fdd34488e237672d8aff7687715744cf2c455977d5bc9813d",
164
+ "architecture.md": "fdb1ed50f12bf7b7fb89663c651277b7ba5ed729bd3dc02f3963c51e4d480b12",
165
+ "cli-contract.md": "ddb7d62f45398866dba2f25da544f8376d76582170712cb5a0010bb826ffb46a",
166
+ "conformance/README.md": "1f576362cb43a475b104e398ab3f9d2ac7451ac931ac067a5deaebb9e076f596",
167
+ "conformance/cases/basic-scan.json": "24623da0cad8c8c54b3ff9b09820ea1276fe8b8f0fc680bf6e8abeb4edb8e424",
168
+ "conformance/cases/kernel-empty-boot.json": "175524674b14d993d29f10080d7697074b3a2eee25b359ff903344d73c6acc98",
169
+ "conformance/fixtures/minimal-claude/agents/reviewer.md": "d0dd681ba63838301e480116aa09825329f01832b0116de5c5476fdd8a5dcf54",
170
+ "conformance/fixtures/minimal-claude/commands/status.md": "3f36e053fd1c059ffd902f84a55be8a458c26072f97cb37dd7e97314ae2a9bf5",
171
+ "conformance/fixtures/minimal-claude/hooks/pre-commit.md": "ec9cec8ac4ce34d40ec055ffd90e8f06ea3e5764d6ec3ee84e0d97de71b930c7",
172
+ "conformance/fixtures/minimal-claude/notes/architecture.md": "5a7e6fdbb1556733dacebad63758057dc1e19090b5a983292c0c65e90b98bcf1",
173
+ "conformance/fixtures/minimal-claude/skills/hello.md": "8598074020430f294ff1eac39876302448f004b6c48446d453092159319bcbee",
174
+ "conformance/fixtures/preamble-v1.txt": "1e0aeef224b64477bdc13a949c3ad402e68249caf499ecdba1302371677c068b",
175
+ "db-schema.md": "490e539f771fb9781fc97afef4f16d86b19eb9ecd9080eca9fcb8bc9605ae0a4",
176
+ "dispatch-lifecycle.md": "e2367bb51c638a9fbb20eec35c0603b2c4adb3668add50fec7be676521798f16",
177
+ "interfaces/security-scanner.md": "468f8ee412594b14b389c36cefa9ca75a5dec652adbf03ab8bbc7f57ca980253",
178
+ "job-events.md": "947d2124acc29f78df8f493549f0d1bdff8159caf621fe8307423c90d7d05f58",
179
+ "plugin-kv-api.md": "fb7cbaea6e18ad696e108fd6ec7a118dc9be6ffef4cf08cc92e511feafa9be30",
180
+ "prompt-preamble.md": "12f7489b5d9886070cfa43cf1f3871b46f2b478f3ead0db292f13e46239b417b",
181
+ "schemas/conformance-case.schema.json": "2740874e00269de6d8121300339401d0283197b6d97dcd77538ec5d108b14de2",
182
+ "schemas/execution-record.schema.json": "ec0f3acf1d0ce099c059d73eb434936bfd1bcf12023693bd572efb2a7352faa6",
183
+ "schemas/frontmatter/agent.schema.json": "3c7ff5e485c1edd40d643ba0e0669fb920a511dfc174b91caff63473585b993d",
184
+ "schemas/frontmatter/base.schema.json": "d5cb9460e855a9a1e5e6afb509e0d44165e2fbe02cd2cfc8f4a437906260f980",
185
+ "schemas/frontmatter/command.schema.json": "7b8463ce9c83edd2e3073dd4cd1bbeec4b42e53b03b48bc9a59e540136c2de89",
186
+ "schemas/frontmatter/hook.schema.json": "4f935bb2a94c6b08795e7e10a473d5185e5abaa8c215152d41036291835b7aad",
187
+ "schemas/frontmatter/note.schema.json": "9806b371193c802803638682f9a625f8277152ad3ef68939eb5f05fff2ef65f4",
188
+ "schemas/frontmatter/skill.schema.json": "b99b8ab23bee01333b4a04946cd9fc13d373d827ef6ddfc7d058daf637f2f80b",
189
+ "schemas/issue.schema.json": "40f6f8abadcce0fd8eac9df27ffcc20b2fc9fda6970142ddb8e7e56b1760b9b1",
190
+ "schemas/job.schema.json": "6e6e791b63cf72591a5a9c72f469b803a69d9bdb6687d780d797ee1f47f8b9f4",
191
+ "schemas/link.schema.json": "3e92f5c9def61a857a2c7b22846d82b988157de083463615144ddc92403a489e",
192
+ "schemas/node.schema.json": "14f345fac450f5728c895d1b878e0015eabb9d72ba9da4a8d2236c82933d3fcf",
193
+ "schemas/plugins-registry.schema.json": "69388548a51496f2597145154911a208bef12d5c805ed71901e236525dccadf9",
194
+ "schemas/project-config.schema.json": "a2aa639f123f303b086683d41f350ccf3929eb23d7f7c9de296da5fbe96d3e0e",
195
+ "schemas/report-base.schema.json": "a1021e9a59b4df9f99cd92454d797e88469766e7d49f52d231c4645ffdfdad8f",
196
+ "schemas/scan-result.schema.json": "5efe9b1954c5e729c4b55dbc4dd51263d97967d16c0b3cea398877ace74d37b7",
197
+ "schemas/summaries/agent.schema.json": "3d22558eeb170e00c4fc32018a810d27333cc632c9e528ff386100cfdfded087",
198
+ "schemas/summaries/command.schema.json": "2bffd606b24f7df9ccd13890af8725adfbfb8a2d7782fee1e0ac5250b9059117",
199
+ "schemas/summaries/hook.schema.json": "36f876f3b1a60d45be97a0848c79fd18744b434dfdcefc366f033b253d56268c",
200
+ "schemas/summaries/note.schema.json": "ae510f3ee1b6092c1061625e425c9bb7de9c9caa3f3774770c148f658d505753",
201
+ "schemas/summaries/skill.schema.json": "f01bab92c51d64ee23e61587e42cf0dc5b37a2f518f5b12b3d1d456390338aa8",
202
+ "versioning.md": "c526fb0068672016411a6660fe4a0ec5ef78086da96ba5e8277ce4e6adfec4bc"
203
+ }
204
+ }
205
+ }
@@ -0,0 +1,233 @@
1
+ # Security scanner interface
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:
4
+
5
+ - Multiple vendors can ship interoperable scanners.
6
+ - `sm findings` can aggregate findings across scanners uniformly.
7
+ - The UI can present a single "Security" panel regardless of which scanners are installed.
8
+
9
+ ---
10
+
11
+ ## Why a convention, not a new kind
12
+
13
+ The six extension kinds are locked (`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.
14
+
15
+ ---
16
+
17
+ ## Identifying a scanner
18
+
19
+ A plugin-provided Action is treated as a security scanner when both:
20
+
21
+ 1. Its `id` starts with `security-` (lowercase kebab-case).
22
+ 2. Its manifest declares `"kind": "scanner"` under `"tags"` (future-proof label; non-normative today but RECOMMENDED).
23
+
24
+ Example manifest:
25
+
26
+ ```json
27
+ {
28
+ "id": "security-snyk",
29
+ "version": "1.0.0",
30
+ "specCompat": "^1.0.0",
31
+ "extensions": ["extensions/snyk.action.mjs"],
32
+ "tags": ["kind:scanner", "vendor:snyk"]
33
+ }
34
+ ```
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.
37
+
38
+ ---
39
+
40
+ ## Input
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:
43
+
44
+ ```json
45
+ {
46
+ "preconditions": { "kind": ["skill", "agent", "command", "hook", "note"] }
47
+ }
48
+ ```
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.
51
+
52
+ Scanners are **local-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 `invocation-template` Actions if the scanner relies on model analysis — the same report shape applies.
53
+
54
+ ---
55
+
56
+ ## Output: the `SecurityReport` shape
57
+
58
+ Every scanner MUST produce a report conforming to this shape. It extends `report-base.schema.json` with scanner-specific fields.
59
+
60
+ ```jsonc
61
+ {
62
+ "confidence": 0.9,
63
+ "safety": {
64
+ "injectionDetected": false,
65
+ "contentQuality": "clean"
66
+ },
67
+
68
+ "scanner": {
69
+ "id": "security-snyk",
70
+ "version": "1.0.0",
71
+ "vendor": "Snyk",
72
+ "ranAt": 1745159465000,
73
+ "durationMs": 240
74
+ },
75
+
76
+ "findings": [
77
+ {
78
+ "id": "security-snyk:SNYK-JS-LODASH-567746",
79
+ "severity": "error",
80
+ "category": "vulnerability",
81
+ "title": "Prototype Pollution in lodash",
82
+ "description": "...",
83
+ "nodePath": "skills/my-skill.md",
84
+ "locations": [
85
+ { "line": 42, "column": 5, "length": 12, "raw": "lodash@4.17.15" }
86
+ ],
87
+ "references": [
88
+ "https://snyk.io/vuln/SNYK-JS-LODASH-567746",
89
+ "https://github.com/advisories/GHSA-..."
90
+ ],
91
+ "remediation": {
92
+ "summary": "Upgrade to lodash >= 4.17.21.",
93
+ "autofixable": false
94
+ },
95
+ "meta": { "cvss": 7.3, "cwe": "CWE-1321" }
96
+ }
97
+ ],
98
+
99
+ "stats": {
100
+ "totalFindings": 1,
101
+ "bySeverity": { "error": 1, "warn": 0, "info": 0 }
102
+ }
103
+ }
104
+ ```
105
+
106
+ ### Field reference
107
+
108
+ **Scanner envelope** (`scanner.*`) — REQUIRED:
109
+
110
+ | Field | Type | Meaning |
111
+ |---|---|---|
112
+ | `id` | string | Matches the Action's id. |
113
+ | `version` | string (semver) | Scanner version at run time. |
114
+ | `vendor` | string | Human-readable vendor name. |
115
+ | `ranAt` | integer | Unix ms. |
116
+ | `durationMs` | integer | How long the scan took. |
117
+
118
+ **Finding** (`findings[]`) — ZERO OR MORE. Each finding MUST include:
119
+
120
+ | Field | Type | Meaning |
121
+ |---|---|---|
122
+ | `id` | string | Globally unique finding id. Convention: `<scannerId>:<vendorFindingId>`. |
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:`. |
125
+ | `title` | string | Short human-readable summary. |
126
+ | `description` | string | Longer explanation; markdown-friendly. |
127
+ | `nodePath` | string | The `node.path` this finding references. |
128
+ | `locations` | array\|null | Optional in-file locations. Each has `line` (required), `column`, `length`, `raw`. |
129
+ | `references` | array\|null | External URLs (CVE, advisory, blog post). |
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. |
132
+
133
+ **Stats** (`stats.*`) — REQUIRED summary:
134
+
135
+ | Field | Type | Meaning |
136
+ |---|---|---|
137
+ | `totalFindings` | integer | MUST equal `findings.length`. |
138
+ | `bySeverity` | object | Map `severity → count`. All three severities MUST be present even if zero. |
139
+
140
+ ### Normative finding categories
141
+
142
+ A `category` value SHOULD be one of these for interoperability:
143
+
144
+ - `vulnerability` — known CVE, dependency advisory, version range with known exploit.
145
+ - `misconfiguration` — insecure default, exposed secret, weak permission, missing header.
146
+ - `credential-leak` — secret material (API key, token, password) detected in content.
147
+ - `injection-risk` — pattern likely to enable prompt injection, SQL injection, command injection.
148
+ - `license-violation` — incompatible license terms for a dependency or referenced asset.
149
+ - `outdated` — version pinned well below current, not exploited but due for upgrade.
150
+ - `policy-violation` — organization-level rule (naming, banned words, required disclaimer).
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.
153
+
154
+ ---
155
+
156
+ ## Runtime model
157
+
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.report_path` points to the JSON file).
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 Rule (e.g. `security-findings-stale` flags nodes whose last security scan is older than a threshold). This is recommended but not normative.
162
+
163
+ ---
164
+
165
+ ## Deduplication
166
+
167
+ Finding ids MUST be stable: re-running the same scanner against unchanged input MUST produce the same `finding.id` values. This allows:
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.
172
+
173
+ The convention `<scannerId>:<vendorFindingId>` ensures cross-scanner uniqueness while staying human-readable.
174
+
175
+ ---
176
+
177
+ ## Aggregation into `sm findings`
178
+
179
+ When a consumer calls `sm findings --security`, the kernel:
180
+
181
+ 1. Queries `state_executions` for actions whose id starts with `security-`.
182
+ 2. For each, loads the most recent report (per `(actionId, nodeId)`).
183
+ 3. Merges finding arrays.
184
+ 4. Emits a normalized list: each entry includes `scanner`, `finding`, and `lastRanAt`.
185
+ 5. Applies optional filters: `--severity`, `--category`, `--node`, `--since`.
186
+
187
+ The consumer sees a flat list of findings regardless of how many scanners produced them.
188
+
189
+ ---
190
+
191
+ ## UI surface
192
+
193
+ The Web UI's Security panel:
194
+
195
+ - Groups findings by `severity` first, then by `category`.
196
+ - Displays `scanner.vendor` as the provenance line.
197
+ - Links `references[]` inline.
198
+ - Exposes `remediation.summary` when present.
199
+ - Does NOT auto-run scanners. Invocation is user-initiated.
200
+
201
+ ---
202
+
203
+ ## Schema file location
204
+
205
+ The JSON Schema for `SecurityReport` lives at `spec/schemas/summaries/security.schema.json` once Step 3 of the spec bootstrap completes. Until then, this document is the normative source and vendors SHOULD derive their own validator from it.
206
+
207
+ This is the only `summaries/*` schema that does NOT correspond to a node kind; it corresponds to an action category instead.
208
+
209
+ ---
210
+
211
+ ## Compliance
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.
214
+
215
+ `sm plugins doctor` MAY emit a warning for Actions prefixed `security-` whose most recent report does not parse as `SecurityReport`.
216
+
217
+ ---
218
+
219
+ ## Stability
220
+
221
+ **Stability: experimental** as of spec v0.x. Field names and conventions MAY tighten before v1.0 once real scanner implementations (Snyk, Socket, custom) ship and reveal shape needs.
222
+
223
+ Locked for v0:
224
+
225
+ - The report envelope (`scanner`, `findings`, `stats`).
226
+ - The required fields on `scanner` and on each finding.
227
+ - The severity enum (`error` / `warn` / `info`).
228
+
229
+ Open (may change pre-v1.0):
230
+
231
+ - The exact category enum — may grow or consolidate.
232
+ - Whether `tags: ["kind:scanner"]` in the manifest becomes normative (vs. just recommended).
233
+ - Whether scanners gain a dedicated CLI verb (`sm security scan`) in addition to `sm job submit security-<id>`.