@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.
- package/CHANGELOG.md +22 -0
- package/README.md +12 -10
- package/architecture.md +154 -150
- package/cli-contract.md +138 -141
- package/conformance/README.md +9 -9
- package/conformance/coverage.md +5 -5
- package/db-schema.md +72 -72
- package/index.json +19 -18
- package/interfaces/security-scanner.md +25 -25
- package/job-events.md +43 -43
- package/job-lifecycle.md +32 -36
- package/package.json +2 -1
- package/plugin-author-guide.md +97 -125
- package/plugin-kv-api.md +22 -23
- package/plugin-quickstart.md +96 -0
- package/prompt-preamble.md +6 -6
- package/schemas/extensions/action.schema.json +6 -0
- package/schemas/project-config.schema.json +4 -0
- package/telemetry.md +120 -136
- package/versioning.md +12 -12
package/plugin-kv-api.md
CHANGED
|
@@ -5,7 +5,7 @@ Normative contract for plugin-accessible persistence. Two modes exist (see [`db-
|
|
|
5
5
|
- **Mode A, KV**: plugin uses the kernel-provided `ctx.store.*` accessor. Backed by the shared `state_plugin_kvs` table.
|
|
6
6
|
- **Mode B, Dedicated**: plugin owns its own tables with the `plugin_<normalizedId>_` prefix, migrated by the kernel.
|
|
7
7
|
|
|
8
|
-
This document defines mode A in full and
|
|
8
|
+
This document defines mode A in full and the boundary with mode B. Implementations MUST expose this API to every plugin that declares `"storage": { "mode": "kv" }` in its manifest.
|
|
9
9
|
|
|
10
10
|
---
|
|
11
11
|
|
|
@@ -19,7 +19,7 @@ A plugin extension receives a `ctx` object at construction time. `ctx.store` is
|
|
|
19
19
|
| `mode: "kv"` | `KvStore` (this document). |
|
|
20
20
|
| `mode: "dedicated"` | `DedicatedStore` (scoped Database wrapper). See mode B below. |
|
|
21
21
|
|
|
22
|
-
Plugins SHOULD pick the minimum mode they need. Mode A is simpler
|
|
22
|
+
Plugins SHOULD pick the minimum mode they need. Mode A is simpler and requires no migrations. Mode B is for plugins that need relational shape, indexes, or cross-row queries.
|
|
23
23
|
|
|
24
24
|
---
|
|
25
25
|
|
|
@@ -47,25 +47,25 @@ Implementations in other languages MUST expose the same semantic surface.
|
|
|
47
47
|
|
|
48
48
|
### Scoping
|
|
49
49
|
|
|
50
|
-
Every operation is scoped by the caller's `pluginId`. The plugin cannot specify, override, or observe another plugin's `pluginId`.
|
|
50
|
+
Every operation is scoped by the caller's `pluginId`. The plugin cannot specify, override, or observe another plugin's `pluginId`. The kernel enforces this when constructing `ctx.store`: the `pluginId` is captured at registration time and is not an argument.
|
|
51
51
|
|
|
52
52
|
Operations MAY be additionally scoped by `nodePath`:
|
|
53
53
|
|
|
54
54
|
- **Global KV (no `nodePath`)**: `{pluginId, nodePath: null, key}`. One row per plugin + key.
|
|
55
55
|
- **Node-scoped KV (with `nodePath`)**: `{pluginId, nodePath: "<path>", key}`. One row per plugin + node + key.
|
|
56
56
|
|
|
57
|
-
Both scopes share the
|
|
57
|
+
Both scopes share the `state_plugin_kvs` table (see [`db-schema.md`](./db-schema.md)). The `nodePath` column is nullable; implementations MUST use a sentinel empty string internally when the backing engine rejects NULL in composite primary keys.
|
|
58
58
|
|
|
59
59
|
### Semantics
|
|
60
60
|
|
|
61
61
|
| Operation | Behaviour |
|
|
62
62
|
|---|---|
|
|
63
63
|
| `get(key, { nodePath })` | Returns the stored value (JSON-decoded) or `null` if no row exists. Never throws for "missing". |
|
|
64
|
-
| `set(key, value, { nodePath })` | Upsert. Replaces any existing value
|
|
64
|
+
| `set(key, value, { nodePath })` | Upsert. Replaces any existing value, updates `updatedAt`. The kernel JSON-encodes the value; it MUST be JSON-serializable. Cyclic or non-serializable values MUST be rejected with a typed error. |
|
|
65
65
|
| `delete(key, { nodePath })` | Deletes the row if present. Returns `true` if a row was deleted, `false` otherwise. Idempotent. |
|
|
66
66
|
| `list({ nodePath, prefix })` | Returns all entries matching the scope. `nodePath` omitted: returns global entries (`nodePath IS NULL`). `nodePath: null` (explicit): same as omitted. `nodePath: "<path>"`: returns entries for that node. `prefix`: filters keys starting with the given string. |
|
|
67
67
|
|
|
68
|
-
Return order of `list` is NOT specified
|
|
68
|
+
Return order of `list` is NOT specified; consumers MUST NOT rely on ordering. Implementations SHOULD order by `key ASC` for developer ergonomics.
|
|
69
69
|
|
|
70
70
|
### Key constraints
|
|
71
71
|
|
|
@@ -83,7 +83,7 @@ Return order of `list` is NOT specified by this spec; consumers MUST NOT rely on
|
|
|
83
83
|
|
|
84
84
|
The `KvStore` operations are individually atomic. There is NO multi-operation transaction in mode A, plugins that need transactional semantics across several rows MUST use mode B.
|
|
85
85
|
|
|
86
|
-
Implementations MUST NOT expose a `transaction()` method on `KvStore` in mode A. The shape is
|
|
86
|
+
Implementations MUST NOT expose a `transaction()` method on `KvStore` in mode A. The shape is minimal to keep the backing table simple.
|
|
87
87
|
|
|
88
88
|
### Errors
|
|
89
89
|
|
|
@@ -129,7 +129,7 @@ interface DedicatedStore {
|
|
|
129
129
|
`DedicatedStore.db` is a wrapper, NOT a raw handle. Every query passes through a validator that rejects:
|
|
130
130
|
|
|
131
131
|
- References to tables whose name doesn't start with this plugin's prefix.
|
|
132
|
-
- DDL statements (`CREATE`, `ALTER`, `DROP`, `TRUNCATE`). Mode B DDL is runtime-immutable after migrations; plugins change shape via a new migration
|
|
132
|
+
- DDL statements (`CREATE`, `ALTER`, `DROP`, `TRUNCATE`). Mode B DDL is runtime-immutable after migrations; plugins change shape via a new migration.
|
|
133
133
|
- `ATTACH DATABASE` statements.
|
|
134
134
|
- `PRAGMA` statements that aren't scoped to the plugin's own tables.
|
|
135
135
|
|
|
@@ -143,8 +143,7 @@ Mode B plugins MAY call `db.transaction(async (tx) => { ... })`. The kernel prov
|
|
|
143
143
|
|
|
144
144
|
- Location: `<plugin-dir>/migrations/NNN_snake_case.sql`.
|
|
145
145
|
- Applied in order after kernel migrations on boot.
|
|
146
|
-
- Prefix injection: the kernel rewrites `CREATE TABLE <name>` into `CREATE TABLE plugin_<id>_<name>` if the prefix is missing.
|
|
147
|
-
- Index and constraint prefixes are similarly injected.
|
|
146
|
+
- Prefix injection: the kernel rewrites `CREATE TABLE <name>` into `CREATE TABLE plugin_<id>_<name>` if the prefix is missing. Index and constraint prefixes are similarly injected.
|
|
148
147
|
- A failing plugin migration disables only that plugin (`status: load-error`); other plugins and the kernel continue.
|
|
149
148
|
|
|
150
149
|
See [`db-schema.md`](./db-schema.md) for the normative migration analyzers.
|
|
@@ -153,7 +152,7 @@ See [`db-schema.md`](./db-schema.md) for the normative migration analyzers.
|
|
|
153
152
|
|
|
154
153
|
## Mode selection guidance
|
|
155
154
|
|
|
156
|
-
Non-normative
|
|
155
|
+
Non-normative guidance for plugin authors.
|
|
157
156
|
|
|
158
157
|
**Prefer mode A when**:
|
|
159
158
|
|
|
@@ -165,27 +164,27 @@ Non-normative; descriptive guidance for plugin authors.
|
|
|
165
164
|
|
|
166
165
|
- You need indexes beyond `(pluginId, nodePath, key)`.
|
|
167
166
|
- You need to `JOIN` rows, aggregate, or do relational queries.
|
|
168
|
-
- Your data model is
|
|
167
|
+
- Your data model is tabular (cache with TTL, observation log, provider registry).
|
|
169
168
|
- You are willing to own migrations forever.
|
|
170
169
|
|
|
171
|
-
A plugin MUST declare **exactly one** storage mode
|
|
170
|
+
A plugin MUST declare **exactly one** storage mode; mixing modes is forbidden. [`plugins-registry.schema.json`](./schemas/plugins-registry.schema.json) enforces this at the manifest level (`storage` is a `oneOf` between `kv` and `dedicated`), and at runtime `ctx.store` exposes either the `KvStore` or the `DedicatedStore` shape, never both. A plugin that needs both KV-like and relational access MUST use mode B and implement KV-style rows as a dedicated table.
|
|
172
171
|
|
|
173
172
|
---
|
|
174
173
|
|
|
175
174
|
## Visibility analyzers
|
|
176
175
|
|
|
177
176
|
- A plugin MUST NOT read or write rows outside its scope. Mode A: the accessor is scoped. Mode B: the validator enforces the prefix.
|
|
178
|
-
- The kernel MAY expose read-only introspection for diagnostics (e.g., `sm plugins show <id> --storage` lists key counts).
|
|
179
|
-
- `sm db shell` can read any table.
|
|
177
|
+
- The kernel MAY expose read-only introspection for diagnostics (e.g., `sm plugins show <id> --storage` lists key counts). Authoritative, not a plugin-level API.
|
|
178
|
+
- `sm db shell` can read any table. Operator-level escape hatch; plugins MUST NOT rely on it.
|
|
180
179
|
|
|
181
180
|
---
|
|
182
181
|
|
|
183
182
|
## Backup and retention
|
|
184
183
|
|
|
185
|
-
- Mode A rows
|
|
186
|
-
- Mode B rows live in the plugin's dedicated tables
|
|
187
|
-
- `sm plugins disable <id>` does NOT drop the plugin's data
|
|
188
|
-
- `sm db reset` (no modifier) drops only `scan_*`. Plugin KV rows (mode A) and plugin-dedicated tables (mode B) are **preserved
|
|
184
|
+
- Mode A rows live in `state_plugin_kvs` and are backed up with `sm db backup`.
|
|
185
|
+
- Mode B rows live in the plugin's dedicated tables (prefixed `plugin_<id>_`) and are likewise backed up.
|
|
186
|
+
- `sm plugins disable <id>` does NOT drop the plugin's data; disabled plugins keep their KV rows and dedicated tables. (`scan_contributions` rows ARE purged eagerly on disable, see `db-schema.md` § `scan_contributions`, because those are scan-derived and would otherwise keep rendering in the UI until the next scan. KV / dedicated-table data is plugin-managed and survives toggle cycles so re-enabling restores state.) `sm plugins forget <id>` (deferred to post-`v1.0`) wipes everything.
|
|
187
|
+
- `sm db reset` (no modifier) drops only `scan_*`. Plugin KV rows (mode A) and plugin-dedicated tables (mode B) are **preserved** (non-destructive to plugin storage).
|
|
189
188
|
- `sm db reset --state` drops `state_*` AND every `plugin_<normalized_id>_*` table, which includes `state_plugin_kvs` (mode A) AND the plugin-dedicated tables (mode B). The CLI MUST require interactive confirmation unless `--yes` is passed.
|
|
190
189
|
- `sm db reset --hard` deletes the DB file entirely, destroying all plugin storage regardless of mode.
|
|
191
190
|
|
|
@@ -193,11 +192,11 @@ A plugin MUST declare **exactly one** storage mode. Mixing modes in the same plu
|
|
|
193
192
|
|
|
194
193
|
## Honest note on isolation
|
|
195
194
|
|
|
196
|
-
Mode A is
|
|
195
|
+
Mode A is isolated at the row level: the accessor physically cannot see another plugin's rows.
|
|
197
196
|
|
|
198
|
-
Mode B is **isolated against accidents, not hostile code**. The scoped `Database` wrapper rejects cross-namespace queries at runtime
|
|
197
|
+
Mode B is **isolated against accidents, not hostile code**. The scoped `Database` wrapper rejects cross-namespace queries at runtime, but a malicious plugin in the same JavaScript process can bypass it by importing raw engine bindings directly. Plugins are user-placed code; the kernel trusts the user's judgement at install time.
|
|
199
198
|
|
|
200
|
-
Post-v1.0 work: signed manifest, sandboxed worker-thread isolation, per-plugin DB file. None
|
|
199
|
+
Post-v1.0 work: signed manifest, sandboxed worker-thread isolation, per-plugin DB file. None land before `v0.5.0`.
|
|
201
200
|
|
|
202
201
|
---
|
|
203
202
|
|
|
@@ -211,7 +210,7 @@ Post-v1.0 work: signed manifest, sandboxed worker-thread isolation, per-plugin D
|
|
|
211
210
|
## Stability
|
|
212
211
|
|
|
213
212
|
- The `KvStore` interface (method names, options, return shapes) is **stable** as of spec v1.0.0.
|
|
214
|
-
- Adding a method to `KvStore` is a minor bump; removing or changing signature is a major bump.
|
|
213
|
+
- Adding a method to `KvStore` is a minor bump; removing or changing a signature is a major bump.
|
|
215
214
|
- Mode names (`kv`, `dedicated`) are **stable**. Adding a third mode is a minor bump.
|
|
216
215
|
- Key and value size limits are implementation-defined and MAY change without a spec bump; implementations MUST document their limits in their own changelog.
|
|
217
216
|
- Error class names are **stable**; adding a new error class is a minor bump.
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
# Plugin quickstart
|
|
2
|
+
|
|
3
|
+
A working `skill-map` plugin in three steps, plus the map of where each kind fits. For the full contract (manifest, the six kinds, storage, view contributions, testing) see the [Plugin author guide](./plugin-author-guide.md); the schemas under [`schemas/`](./schemas/) are the source of truth.
|
|
4
|
+
|
|
5
|
+
## Where each kind fits
|
|
6
|
+
|
|
7
|
+
A plugin is one or more of **six extension kinds**. Each plugs into one point of skill-map's lifecycle; pick the one that matches what you want to do.
|
|
8
|
+
|
|
9
|
+
```text
|
|
10
|
+
THE DETERMINISTIC FLOW ( the scan: fast · reproducible · offline )
|
|
11
|
+
═══════════════════════════════════════════════════════════════════
|
|
12
|
+
|
|
13
|
+
files on disk
|
|
14
|
+
│
|
|
15
|
+
▼
|
|
16
|
+
┌────────────┐
|
|
17
|
+
│ PROVIDER │ decides what counts as a node, and under which lens
|
|
18
|
+
└─────┬──────┘ e.g. .claude/skills/foo/SKILL.md → a Claude skill
|
|
19
|
+
▼
|
|
20
|
+
┌────────────┐
|
|
21
|
+
│ EXTRACTOR │ reads one node and pulls out its references and signals
|
|
22
|
+
└─────┬──────┘ e.g. an @architect mention → a link to that agent
|
|
23
|
+
▼
|
|
24
|
+
┌────────────┐
|
|
25
|
+
│ ANALYZER │ looks across the whole graph and flags problems
|
|
26
|
+
└─────┬──────┘ e.g. a link to a missing file → an Issue
|
|
27
|
+
▼
|
|
28
|
+
┌────────────┐
|
|
29
|
+
│ ACTION │ acts on a node (still on the deterministic flow); can
|
|
30
|
+
└─────┬──────┘ also run as an LLM job. e.g. Bump · Summarize (LLM)
|
|
31
|
+
▼
|
|
32
|
+
┌────────────┐
|
|
33
|
+
│ FORMATTER │ turns the finished graph into an output format
|
|
34
|
+
└────────────┘ e.g. the whole graph → an ASCII tree ( sm graph )
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
Off to the side, reacting to the whole lifecycle (never blocks it):
|
|
38
|
+
|
|
39
|
+
┌────────────┐
|
|
40
|
+
│ HOOK │ watches events and reacts with a side effect
|
|
41
|
+
└────────────┘ e.g. after a scan finishes → notify Slack
|
|
42
|
+
fires on: boot · scan · extractor/analyzer/action · job · shutdown
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
(Same diagram as the [author guide](./plugin-author-guide.md#plugin-lifecycle-at-a-glance), copied here so the quickstart stands alone.)
|
|
46
|
+
|
|
47
|
+
## 1. Scaffold
|
|
48
|
+
|
|
49
|
+
```bash
|
|
50
|
+
sm plugins create extractor my-plugin
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
Writes a loader-clean plugin under `.skill-map/plugins/my-plugin/`: a `plugin.json` plus a working stub for the chosen kind. The first positional is the kind, the second the plugin id. (Details in [§Scaffolder](./plugin-author-guide.md#scaffolder).)
|
|
54
|
+
|
|
55
|
+
## 2. Fill the stub
|
|
56
|
+
|
|
57
|
+
Open the generated `index.js` and write your logic. An extractor emits its findings through callbacks on `ctx`:
|
|
58
|
+
|
|
59
|
+
```javascript
|
|
60
|
+
export default {
|
|
61
|
+
version: '1.0.0',
|
|
62
|
+
description: 'Link any node that mentions ROADMAP.md.',
|
|
63
|
+
scope: 'body',
|
|
64
|
+
extract(ctx) {
|
|
65
|
+
if (ctx.body.includes('ROADMAP.md')) {
|
|
66
|
+
ctx.emitLink({
|
|
67
|
+
source: ctx.node.path,
|
|
68
|
+
target: 'ROADMAP.md',
|
|
69
|
+
kind: 'references',
|
|
70
|
+
sources: ['my-plugin'],
|
|
71
|
+
});
|
|
72
|
+
}
|
|
73
|
+
},
|
|
74
|
+
};
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
The method name and `ctx` shape differ per kind; each has an example in [§The six extension kinds](./plugin-author-guide.md#the-six-extension-kinds).
|
|
78
|
+
|
|
79
|
+
## 3. Load and run
|
|
80
|
+
|
|
81
|
+
```bash
|
|
82
|
+
sm plugins list # confirm it loaded (status should be green)
|
|
83
|
+
sm scan # run it over your project
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
A non-green status? [§Diagnostics](./plugin-author-guide.md#diagnostics) lists every status and how to fix it.
|
|
87
|
+
|
|
88
|
+
## Then go deeper
|
|
89
|
+
|
|
90
|
+
Whatever you need next is one section away in the [Plugin author guide](./plugin-author-guide.md):
|
|
91
|
+
|
|
92
|
+
- [Manifest fields](./plugin-author-guide.md#manifest) and the [`specCompat` strategy](./plugin-author-guide.md#speccompat-strategy)
|
|
93
|
+
- [The six extension kinds](./plugin-author-guide.md#the-six-extension-kinds), in full, with an example each
|
|
94
|
+
- [Storage](./plugin-author-guide.md#storage): a KV bag or a dedicated table
|
|
95
|
+
- [View contributions](./plugin-author-guide.md#view-contributions): chips, badges, and buttons in the UI
|
|
96
|
+
- [Testing your plugin](./plugin-author-guide.md#testing-your-plugin) against the kernel's public types
|
package/prompt-preamble.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Prompt preamble
|
|
2
2
|
|
|
3
|
-
Canonical text the kernel prepends to every rendered job content blob before the action-specific template
|
|
3
|
+
Canonical text the kernel prepends to every rendered job content blob, before the action-specific template, to mitigate prompt injection from user-authored node content. This document defines:
|
|
4
4
|
|
|
5
5
|
1. The **delimiter contract** that wraps user content.
|
|
6
6
|
2. The **verbatim preamble text** (the only normative text in the spec).
|
|
@@ -32,7 +32,7 @@ An action template that violates rule 4 (e.g., interpolates user text outside `<
|
|
|
32
32
|
|
|
33
33
|
## The preamble text
|
|
34
34
|
|
|
35
|
-
The following text is **normative and verbatim
|
|
35
|
+
The following text is **normative and verbatim**, byte-for-byte reproducible. Included in the `contentHash` computation (via `promptTemplateHash`, which hashes the preamble + action template concatenation).
|
|
36
36
|
|
|
37
37
|
```
|
|
38
38
|
You are operating inside skill-map, a deterministic tool that runs actions
|
|
@@ -102,7 +102,7 @@ The preamble establishes a promise from the model:
|
|
|
102
102
|
- `safety` MUST conform to [`schemas/report-base.schema.json`](./schemas/report-base.schema.json)`#/properties/safety`.
|
|
103
103
|
- `confidence` MUST be a number in `[0.0, 1.0]`.
|
|
104
104
|
|
|
105
|
-
The kernel validates every report against the action's declared schema (which MUST extend [`report-base.schema.json`](./schemas/report-base.schema.json)). A report
|
|
105
|
+
The kernel validates every report against the action's declared schema (which MUST extend [`report-base.schema.json`](./schemas/report-base.schema.json)). A report lacking `safety` or `confidence`, or with wrong-shape values, is rejected; the job transitions to `failed` with reason `report-invalid` (see [`job-lifecycle.md`](./job-lifecycle.md)).
|
|
106
106
|
|
|
107
107
|
Implementations MUST NOT tolerate the absence of `safety`. If a model returns a report without it, the failure is the runner's problem to surface, not the kernel's to tolerate.
|
|
108
108
|
|
|
@@ -116,10 +116,10 @@ On `sm job submit`:
|
|
|
116
116
|
2. The kernel validates that the template does not interpolate user text outside of `<user-content>` blocks.
|
|
117
117
|
3. The kernel prepends the verbatim preamble text above.
|
|
118
118
|
4. The kernel renders the template by interpolating the node content, wrapping it in `<user-content>`.
|
|
119
|
-
5. The kernel stores the result in `state_job_contents` keyed by `contentHash` (content-addressed:
|
|
119
|
+
5. The kernel stores the result in `state_job_contents` keyed by `contentHash` (content-addressed: jobs resolving to the same `contentHash` share one row). No canonical filesystem artifact: `sm job preview` and `sm job claim --json` read directly from this table. Subprocess runners that need a file (e.g., `claude -p` reading stdin from a path) materialize a temp file from the DB row and remove it after spawn; it is operationally ephemeral, not part of the contract.
|
|
120
120
|
6. The kernel computes `contentHash` over (among other things) the concatenation of preamble + template. A changed preamble (e.g., spec bump) MUST produce a different hash and therefore MUST NOT collide with prior jobs.
|
|
121
121
|
|
|
122
|
-
Implementations MUST NOT modify the preamble text at runtime (e.g., based on locale, model, or config)
|
|
122
|
+
Implementations MUST NOT modify the preamble text at runtime (e.g., based on locale, model, or config): it is universal and invariant.
|
|
123
123
|
|
|
124
124
|
---
|
|
125
125
|
|
|
@@ -143,7 +143,7 @@ This preamble is a **mitigation**, not a guarantee. A determined attacker can st
|
|
|
143
143
|
2. It gives the model a structured place to report suspected injections, so consumers can act (flag the node, re-run with a different model, refuse to summarize).
|
|
144
144
|
3. It makes injection attempts visible (via the `safety` field in reports) so that deterministic rules can surface patterns over the graph.
|
|
145
145
|
|
|
146
|
-
Defense-in-depth: the deterministic analyzer `injection-pattern` (
|
|
146
|
+
Defense-in-depth: the deterministic analyzer `injection-pattern` (a built-in in the default plugin pack) scans node bodies for known injection patterns independently of the LLM. Neither layer is sufficient alone.
|
|
147
147
|
|
|
148
148
|
---
|
|
149
149
|
|
|
@@ -18,6 +18,12 @@
|
|
|
18
18
|
"minimum": 1,
|
|
19
19
|
"description": "Best-effort estimate of wall-clock duration when `mode=probabilistic`. Drives TTL (`ttl = max(probExpectedDurationSeconds × graceMultiplier, minimumTtlSeconds)`). Required for `probabilistic`; ignored otherwise. Renamed from `expectedDurationSeconds` with the structure-as-truth refactor, the `prob` prefix makes it clear at a glance which mode the field belongs to."
|
|
20
20
|
},
|
|
21
|
+
"writes": {
|
|
22
|
+
"type": "array",
|
|
23
|
+
"uniqueItems": true,
|
|
24
|
+
"items": { "type": "string", "enum": ["sidecar"] },
|
|
25
|
+
"description": "Declares the kinds of persistent writes this Action's `invoke()` may emit (mirrors `IActionResult.writes[].kind`). Today the only kind is `sidecar`: the Action creates or modifies a `.sm` annotation sidecar next to a source file. An Action whose `invoke()` returns a write of a given kind MUST declare that kind here; the declaration is the source of truth consumers gate on. When the `allowSidecarWriters: false` project policy is set, every Action that declares `sidecar` is dropped from the scan composer (its `inspector.action.button` never renders) and the sidecar store refuses the write. Absent = the Action performs no persistent writes (read-only / report-only)."
|
|
26
|
+
},
|
|
21
27
|
"precondition": {
|
|
22
28
|
"type": "object",
|
|
23
29
|
"additionalProperties": false,
|
|
@@ -139,6 +139,10 @@
|
|
|
139
139
|
"allowEditSmFiles": {
|
|
140
140
|
"type": "boolean",
|
|
141
141
|
"description": "**Project-local only** (per `core/config/helper:PROJECT_LOCAL_ONLY_KEYS`). Grants this project permission to create / modify `.sm` annotation sidecars next to source files. Default `false`. The first time a verb or BFF route attempts a `.sm` write while this is `false`, the kernel raises `EConsentRequiredError`. The CLI surfaces it as an interactive `confirm()` prompt (or `--yes` bypass); the BFF returns 412 `confirm-required` so the UI can open a `ConfirmationService` dialog. On accept the flag is persisted to `<cwd>/.skill-map/settings.local.json` (gitignored, per-checkout) and never asked again. On decline the operation aborts WITHOUT persisting the rejection, the next attempt re-asks. **Stripped with a warning when found in the committed `project` layer** (`<cwd>/.skill-map/settings.json`), each developer consents independently."
|
|
142
|
+
},
|
|
143
|
+
"allowSidecarWriters": {
|
|
144
|
+
"type": "boolean",
|
|
145
|
+
"description": "**Project policy, team-shared** (committed in `<cwd>/.skill-map/settings.json`, NOT project-local). Default `true`. When `false`, every extension whose manifest declares `writes: ['sidecar']` (the built-in `core/node-bump`, `core/node-set-tags`, `core/node-set-stability`, plus any external action) is dropped from the scan composer so its `inspector.action.button` never renders, and the sidecar store refuses the write with `ESidecarWritersForbiddenError`. This is a HARD gate that wins over the per-machine `allowEditSmFiles` consent: a developer cannot re-enable sidecar writes locally, and `--yes` does not bypass it. Reads of existing `.sm` sidecars (annotation orphan fields) are unaffected, the policy governs writes / generation only. Unlike `allowEditSmFiles` this key is meant to travel via the shared repo, so it is NOT stripped from the committed `project` layer."
|
|
142
146
|
}
|
|
143
147
|
}
|
|
144
148
|
}
|