@windyroad/risk-scorer 0.14.0 → 0.14.1-preview.774
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/agents/pipeline.md
CHANGED
|
@@ -442,4 +442,4 @@ They outrank the technical assessment.
|
|
|
442
442
|
| 4 Significant | 4 | 8 | 12 | 16 | 20 |
|
|
443
443
|
| 5 Severe | 5 | 10 | 15 | 20 | 25 |
|
|
444
444
|
|
|
445
|
-
Label Bands: 1-2 Very Low, 3-
|
|
445
|
+
Label Bands (ADR-086, supersedes ADR-065): 1-2 Very Low, 3-5 Low, 6-9 Medium, 10-16 High, 17-25 Very High. Only `score = 5` differs from the prior bands — now Low (was Medium), restoring feasibility for severe-but-rare residuals.
|
package/hooks/lib/risk-gate.sh
CHANGED
|
@@ -101,23 +101,26 @@ check_risk_gate() {
|
|
|
101
101
|
fi
|
|
102
102
|
|
|
103
103
|
# 5. Threshold check — block when the score EXCEEDS the project's
|
|
104
|
-
# RISK-POLICY.md risk appetite (P007 / ADR-065). The
|
|
105
|
-
# adopter's documented appetite, not a code constant: a
|
|
106
|
-
# policy sets a higher appetite (e.g. "exceeds 9") must not
|
|
107
|
-
# within-appetite changes gate-rejected.
|
|
104
|
+
# RISK-POLICY.md risk appetite (P007 / ADR-086 supersedes ADR-065). The
|
|
105
|
+
# threshold is the adopter's documented appetite, not a code constant: a
|
|
106
|
+
# project whose policy sets a higher appetite (e.g. "exceeds 9") must not
|
|
107
|
+
# have its within-appetite changes gate-rejected.
|
|
108
108
|
# Precedence: RISK_APPETITE env override > RISK-POLICY.md § Risk Appetite
|
|
109
|
-
# parse > default
|
|
110
|
-
#
|
|
111
|
-
#
|
|
112
|
-
#
|
|
113
|
-
#
|
|
109
|
+
# parse > default 5 (ADR-086). Default 5 tracks the new Low ceiling so
|
|
110
|
+
# the no-policy adopter's default fallback admits residual=5 (the
|
|
111
|
+
# Impact=5/Likelihood=1 floor for severe-but-rare risks). The normal
|
|
112
|
+
# path is /wr-risk-scorer:update-policy interviewing the user to build
|
|
113
|
+
# the policy; the default fallback is defensive.
|
|
114
|
+
# The parse is tolerant of the phrasings "Threshold: N", "exceeds N",
|
|
115
|
+
# and "N/Low appetite", scoped to the "## Risk Appetite" section.
|
|
116
|
+
# Cost ~3-8ms/invocation (ADR-065 § Consequences carries forward).
|
|
114
117
|
local DECISION
|
|
115
118
|
DECISION=$(RISK_SCORE_VAL="$SCORE" RISK_APPETITE_ENV="${RISK_APPETITE:-}" python3 -c "
|
|
116
119
|
import os, re, sys
|
|
117
120
|
try:
|
|
118
121
|
score = float(os.environ['RISK_SCORE_VAL'])
|
|
119
122
|
except Exception:
|
|
120
|
-
print('no
|
|
123
|
+
print('no 5'); sys.exit(0)
|
|
121
124
|
N = None
|
|
122
125
|
override = os.environ.get('RISK_APPETITE_ENV', '').strip()
|
|
123
126
|
if override.isdigit():
|
|
@@ -137,9 +140,9 @@ else:
|
|
|
137
140
|
if m:
|
|
138
141
|
N = int(m.group(1)); break
|
|
139
142
|
if N is None:
|
|
140
|
-
N =
|
|
143
|
+
N = 5
|
|
141
144
|
print(('yes' if score > N else 'no') + ' ' + str(N))
|
|
142
|
-
" 2>/dev/null || echo "no
|
|
145
|
+
" 2>/dev/null || echo "no 5")
|
|
143
146
|
local DENIED="${DECISION%% *}"
|
|
144
147
|
local APPETITE="${DECISION##* }"
|
|
145
148
|
|
|
@@ -237,10 +237,12 @@ _write_matching_hash() {
|
|
|
237
237
|
|
|
238
238
|
# ---------------------------------------------------------------------------
|
|
239
239
|
# P007 / ADR-065 — the block threshold is the project's RISK-POLICY.md risk
|
|
240
|
-
# appetite (block when score > N), not a hardcoded 5. Default N=
|
|
241
|
-
#
|
|
242
|
-
#
|
|
243
|
-
#
|
|
240
|
+
# appetite (block when score > N), not a hardcoded 5. Default N=5 per ADR-086
|
|
241
|
+
# (was 4 under superseded ADR-065) when the policy is absent or unparseable,
|
|
242
|
+
# which tracks the new Low ceiling so the default fallback admits residual=5
|
|
243
|
+
# (the Impact=5/Likelihood=1 floor for severe-but-rare risks). Precedence:
|
|
244
|
+
# RISK_APPETITE env > RISK-POLICY.md parse > default 5. (Behavioural fixtures
|
|
245
|
+
# per ADR-052.)
|
|
244
246
|
# ---------------------------------------------------------------------------
|
|
245
247
|
|
|
246
248
|
@test "appetite 9 (exceeds 9): score 7 within the 5-9 band PASSES" {
|
|
@@ -275,29 +277,38 @@ _write_matching_hash() {
|
|
|
275
277
|
assert_gate_denies "$TEST_SESSION" "commit" "appetite of 4/25"
|
|
276
278
|
}
|
|
277
279
|
|
|
278
|
-
@test "absent RISK-POLICY.md: defaults to appetite
|
|
280
|
+
@test "absent RISK-POLICY.md: defaults to appetite 5 per ADR-086 (5 PASSES, 6 FAILS)" {
|
|
279
281
|
_use_policy '' # no RISK-POLICY.md in the temp dir
|
|
280
282
|
rm -f "$HASH_FILE"
|
|
281
|
-
printf '4' > "$SCORE_FILE"
|
|
282
|
-
assert_gate_allows "$TEST_SESSION" "commit"
|
|
283
283
|
printf '5' > "$SCORE_FILE"
|
|
284
|
-
|
|
284
|
+
assert_gate_allows "$TEST_SESSION" "commit"
|
|
285
|
+
printf '6' > "$SCORE_FILE"
|
|
286
|
+
assert_gate_denies "$TEST_SESSION" "commit" "appetite of 5/25"
|
|
285
287
|
}
|
|
286
288
|
|
|
287
|
-
@test "unparseable RISK-POLICY.md (no appetite integer): defaults to appetite
|
|
289
|
+
@test "unparseable RISK-POLICY.md (no appetite integer): defaults to appetite 5 per ADR-086" {
|
|
288
290
|
_use_policy 'We are conservative about risk but state no number here.'
|
|
289
291
|
rm -f "$HASH_FILE"
|
|
290
|
-
printf '4' > "$SCORE_FILE"
|
|
291
|
-
assert_gate_allows "$TEST_SESSION" "commit"
|
|
292
292
|
printf '5' > "$SCORE_FILE"
|
|
293
|
-
|
|
293
|
+
assert_gate_allows "$TEST_SESSION" "commit"
|
|
294
|
+
printf '6' > "$SCORE_FILE"
|
|
295
|
+
assert_gate_denies "$TEST_SESSION" "commit" "appetite of 5/25"
|
|
294
296
|
}
|
|
295
297
|
|
|
296
|
-
@test "fractional score
|
|
298
|
+
@test "fractional score 5.5 FAILS under default appetite 5 (5.5 > 5) per ADR-086" {
|
|
297
299
|
_use_policy ''
|
|
298
300
|
rm -f "$HASH_FILE"
|
|
299
|
-
printf '
|
|
300
|
-
assert_gate_denies "$TEST_SESSION" "commit" "
|
|
301
|
+
printf '5.5' > "$SCORE_FILE"
|
|
302
|
+
assert_gate_denies "$TEST_SESSION" "commit" "5.5/25"
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
@test "appetite via 'Threshold: 5' phrasing (ADR-086 new Low ceiling): score 5 PASSES, score 6 FAILS" {
|
|
306
|
+
_use_policy '**Threshold: 5 (Low)**'
|
|
307
|
+
rm -f "$HASH_FILE"
|
|
308
|
+
printf '5' > "$SCORE_FILE"
|
|
309
|
+
assert_gate_allows "$TEST_SESSION" "commit"
|
|
310
|
+
printf '6' > "$SCORE_FILE"
|
|
311
|
+
assert_gate_denies "$TEST_SESSION" "commit" "appetite of 5/25"
|
|
301
312
|
}
|
|
302
313
|
|
|
303
314
|
@test "RISK_APPETITE env override takes precedence over RISK-POLICY.md parse" {
|
package/package.json
CHANGED
|
@@ -113,7 +113,7 @@ Use the Risk Matrix from `RISK-POLICY.md`:
|
|
|
113
113
|
|
|
114
114
|
- **Inherent Score** = Impact × Likelihood
|
|
115
115
|
- **Residual Score** = Impact × Likelihood (after controls)
|
|
116
|
-
- **Band** (for each) per the Label Bands table: 1-2 Very Low · 3-
|
|
116
|
+
- **Band** (for each) per the Label Bands table (ADR-086): 1-2 Very Low · 3-5 Low · 6-9 Medium · 10-16 High · 17-25 Very High
|
|
117
117
|
- **Within appetite?** = residual score ≤ `RISK-POLICY.md`'s appetite threshold (read the threshold at runtime; do not hardcode)
|
|
118
118
|
|
|
119
119
|
### 5. Write the risk file
|
|
@@ -108,20 +108,23 @@ Reference specific product features, user workflows, and infrastructure by name.
|
|
|
108
108
|
- **Likely (4)**: High complexity, many code paths, or limited controls. Expected to occur without intervention.
|
|
109
109
|
- **Almost certain (5)**: Known gap, no controls in place, or previously observed failure mode.
|
|
110
110
|
|
|
111
|
-
**Risk matrix**: Include the Impact × Likelihood multiplication table (5×5 = scores 1-25) and the label bands:
|
|
111
|
+
**Risk matrix**: Include the Impact × Likelihood multiplication table (5×5 = scores 1-25) and the label bands per ADR-086 (supersedes ADR-065):
|
|
112
112
|
|
|
113
113
|
| Score Range | Label |
|
|
114
114
|
|-------------|-------|
|
|
115
|
-
| 1-
|
|
116
|
-
| 5
|
|
117
|
-
|
|
|
118
|
-
|
|
|
115
|
+
| 1-2 | Very Low |
|
|
116
|
+
| 3-5 | Low |
|
|
117
|
+
| 6-9 | Medium |
|
|
118
|
+
| 10-16 | High |
|
|
119
|
+
| 17-25 | Very High |
|
|
120
|
+
|
|
121
|
+
The bands match the risk-scorer agent's authoritative source at `packages/risk-scorer/agents/pipeline.md` and the create-risk SKILL — single source of truth across the plugin. The Low ceiling at 5 admits residual=5 (Impact=5×Likelihood=1, the floor for severe-but-rare risks) within Low so an appetite of 5 is reachable for that class.
|
|
119
122
|
|
|
120
123
|
The risk matrix is used by both the **risk-scorer agent** (pipeline risk assessment) and the **problem management process** (problem severity via `/problem` skill).
|
|
121
124
|
|
|
122
125
|
### 6. Confirm with the user
|
|
123
126
|
|
|
124
|
-
You MUST use the AskUserQuestion tool (not plain text output) to collect user confirmation. Do not proceed to step 7 until you have received answers via AskUserQuestion.
|
|
127
|
+
You MUST use the AskUserQuestion tool (not plain text output) to collect user confirmation. Do not proceed to step 6a or step 7 until you have received answers via AskUserQuestion.
|
|
125
128
|
|
|
126
129
|
Call AskUserQuestion with a single message that presents:
|
|
127
130
|
|
|
@@ -129,6 +132,28 @@ Call AskUserQuestion with a single message that presents:
|
|
|
129
132
|
2. The risk appetite threshold -- present the label bands from the agent contract (step 1) and recommend a threshold based on project maturity. Ask the user to confirm or adjust. A prototype with no real users may tolerate higher risk than a production system with paying users or compliance requirements
|
|
130
133
|
3. Whether any business context is missing (e.g., compliance requirements, SLAs, user base size)
|
|
131
134
|
|
|
135
|
+
### 6a. Tight-appetite warning when threshold < 5 (ADR-086)
|
|
136
|
+
|
|
137
|
+
If the user picked an appetite threshold below 5 in step 6, fire a second `AskUserQuestion` confirm-with-warning before proceeding. The Low band's ceiling under ADR-086 is 5; an appetite below 5 means a class of risks (those with Impact=5/Severe and no impact-reducing control available) can never be within appetite — the policy is mathematically infeasible for that class. The user can still set the tighter threshold (some domains genuinely want to prohibit severe-impact activities), but the consequence must be a conscious choice, not a quiet trap.
|
|
138
|
+
|
|
139
|
+
**Build the warning's example list** (cite concrete activity-classes the user is about to prohibit):
|
|
140
|
+
|
|
141
|
+
1. Glob `docs/risks/R*.active.md`. For each file, read the `## Inherent Risk` and `## Residual Risk` sections and extract the numeric `Impact` value. Collect entries whose Inherent Impact = 5 OR Residual Impact = 5.
|
|
142
|
+
2. If 1+ entries match: pick up to three. Format each as a kebab-case activity-class derived from the entry's filename slug (the part after `R<NNN>-` and before `.active.md`), followed by its R-ID as the audit pointer. Brief-before-ID per P350 — activity-class FIRST, R-ID in parentheses, never the R-ID alone as the carrier of meaning.
|
|
143
|
+
3. If 0 entries match (empty register OR no Impact=5 entries): fall back to citing the policy's own Impact=5 row from the Impact Levels table just drafted — the user has it in working memory and it's the right surface for "what kind of thing would be prohibited."
|
|
144
|
+
|
|
145
|
+
**The warning question**:
|
|
146
|
+
|
|
147
|
+
> *"You've chosen appetite N. Under the rebalanced label bands (ADR-086) the Low ceiling is 5, so an appetite of N means residual-5 risks like `<activity-class>` ({R-ID}) and `<activity-class>` ({R-ID}) can never be within appetite under this policy — those activities become effectively prohibited (no amount of control work brings their residual below 5 because Impact=5 is fixed). Continue with appetite N, or revise?"*
|
|
148
|
+
|
|
149
|
+
(Substitute the register-derived example list or the policy-row fallback as appropriate.)
|
|
150
|
+
|
|
151
|
+
**Options**:
|
|
152
|
+
- **Confirm: appetite N, with severe-impact activities prohibited** — the user explicitly accepts the trade-off. Proceed to step 7.
|
|
153
|
+
- **Revise the appetite** — return to step 6 with the appetite question only.
|
|
154
|
+
|
|
155
|
+
**Non-interactive (AFK) fallback** per ADR-013 Rule 6: if `AskUserQuestion` is unavailable, do NOT proceed — the warning is load-bearing (prohibits an entire risk class) and silently consuming it would normalise the prohibition. Halt with a clear "appetite < 5 selected interactively required" message for the orchestrator to drain later.
|
|
156
|
+
|
|
132
157
|
### 7. Validate draft with risk-scorer agent
|
|
133
158
|
|
|
134
159
|
Before writing the policy file, invoke the risk-scorer agent to validate the draft. This is the gate -- the enforce hook will only allow writes to RISK-POLICY.md after the scorer returns PASS.
|