aienvmp 0.1.58 → 0.1.60
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/BUGFIXES.md +12 -0
- package/CHANGELOG.md +17 -0
- package/README.md +2 -1
- package/package.json +1 -1
- package/src/commands/sbom.js +7 -0
- package/src/commands/summary.js +3 -1
- package/src/contract.js +5 -3
- package/src/dependencies.js +17 -8
- package/src/enforcement.js +21 -0
- package/src/preflight.js +1 -0
- package/src/render.js +3 -2
package/BUGFIXES.md
CHANGED
|
@@ -142,6 +142,18 @@ Short record of bugs, fixes, and follow-up checks.
|
|
|
142
142
|
- Fix: manifest generation now includes `lightSbom.aiDependencyReview`; the dashboard Light SBOM card and `summary.md` show the AI dependency review status and commands.
|
|
143
143
|
- Verification: regression tests cover generation, dashboard rendering, SBOM artifact reuse, and summary output; local sync smoke confirmed dashboard and SBOM artifacts include the block.
|
|
144
144
|
|
|
145
|
+
### Scanner-off SBOM state could look safer than intended
|
|
146
|
+
|
|
147
|
+
- Issue: AI agents could read `aiDependencyReview.status: ready` while the security scanner was off, making the state look more verified than it was.
|
|
148
|
+
- Fix: `aiDependencyReview` now includes `statusReason` and `securityConfidence`, and generation reuses the computed risk summary instead of recalculating a weaker review signal.
|
|
149
|
+
- Verification: regression tests cover scanner-off confidence, dashboard/summary rendering, schema metadata, and standalone SBOM fallback behavior; local sync smoke confirmed `scanner-off` appears in generated artifacts.
|
|
150
|
+
|
|
151
|
+
### Strict mode guidance required too much inference
|
|
152
|
+
|
|
153
|
+
- Issue: AI and CI consumers could see scoped strict results, but still had to infer which `doctor --strict` command should be used without disrupting local operation.
|
|
154
|
+
- Fix: `strictPlan` now provides local advisory command, recommended strict scope, CI command, all-scope command, scope statuses, and the strict-use rule.
|
|
155
|
+
- Verification: regression tests cover enforcement advice, doctor JSON, status/context, summary, dashboard, and schema outputs; local sync smoke confirmed summary emits the CI strict command.
|
|
156
|
+
|
|
145
157
|
## Template
|
|
146
158
|
|
|
147
159
|
### Title
|
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,22 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.1.60
|
|
4
|
+
|
|
5
|
+
- Added `enforcement.strictPlan` to help AI agents and CI choose the narrowest explicit strict scope.
|
|
6
|
+
- Added `preflight.enforcementProfile.strictPlan` so status/context consumers see the same strict guidance.
|
|
7
|
+
- Added CI strict command output to `.aienvmp/summary.md`.
|
|
8
|
+
- Added strict-plan CI command and rule display to the dashboard Enforcement Mode card.
|
|
9
|
+
- Documented strict-plan consumption in `schema --json` and README.
|
|
10
|
+
- Added regression tests for enforcement advice, doctor JSON, status/context, summary, dashboard, and schema outputs.
|
|
11
|
+
|
|
12
|
+
## 0.1.59
|
|
13
|
+
|
|
14
|
+
- Added `aiDependencyReview.statusReason` so AI agents can distinguish actual review risk from scanner-off uncertainty.
|
|
15
|
+
- Added `aiDependencyReview.securityConfidence` across generated light SBOM, standalone SBOM, dashboard, summary, and schema metadata.
|
|
16
|
+
- Reused the computed `riskSummary`, package-manager policy, and dependency hints when building `aiDependencyReview` to avoid drift between artifacts.
|
|
17
|
+
- Updated dashboard and summary surfaces to show dependency-review confidence.
|
|
18
|
+
- Updated README and regression tests for scanner-off dependency review behavior.
|
|
19
|
+
|
|
3
20
|
## 0.1.58
|
|
4
21
|
|
|
5
22
|
- Added `lightSbom.aiDependencyReview` during manifest generation so all generated artifacts share the same dependency-review block.
|
package/README.md
CHANGED
|
@@ -53,7 +53,7 @@ AIENV.md # Markdown env map for AI agents
|
|
|
53
53
|
- `aiReadiness` gives a one-field ready/review signal for AI continuation.
|
|
54
54
|
- `dependencyReadSet` lists manifests and lockfiles before package or security changes.
|
|
55
55
|
- `sbomRisk` gives AI a compact light-SBOM risk level, signals, and next command.
|
|
56
|
-
- `sbom.json.aiDependencyReview` connects SBOM risk
|
|
56
|
+
- `sbom.json.aiDependencyReview` connects SBOM risk, scanner confidence, and safe dependency-change steps.
|
|
57
57
|
- `coordination.conflictTargets` shows where multiple agents are planning changes.
|
|
58
58
|
- `agentActivity.multiActorTargets` shows where multiple agents actually recorded env changes.
|
|
59
59
|
- `followUps` shows records that still need `sync`, `status`, or `handoff`.
|
|
@@ -98,6 +98,7 @@ aienvmp doctor --strict security|policy|coordination|all
|
|
|
98
98
|
## CI
|
|
99
99
|
|
|
100
100
|
The GitHub Action writes status, summary, schema, doctor, plan, SBOM, and dashboard artifacts. `strict: "off"` reports warnings without failing the job.
|
|
101
|
+
Use `doctor --json` or `context --json` to read `strictPlan` and choose the narrowest CI strict scope.
|
|
101
102
|
|
|
102
103
|
```yaml
|
|
103
104
|
- uses: soovwv/aienvmp@main
|
package/package.json
CHANGED
package/src/commands/sbom.js
CHANGED
|
@@ -55,8 +55,15 @@ function aiDependencyReview(lightSbom = {}) {
|
|
|
55
55
|
? risk.reviewTargets
|
|
56
56
|
: hints.map((item) => item.manifest).filter(Boolean);
|
|
57
57
|
const review = ["urgent", "high", "medium"].includes(level) || policy.status === "review-required";
|
|
58
|
+
const scannerOff = risk.scanner === "off" || lightSbom.source?.vulnerabilities === "not scanned" || lightSbom.confidence?.vulnerabilities === "not-scanned";
|
|
58
59
|
return {
|
|
59
60
|
status: review ? "review" : "ready",
|
|
61
|
+
statusReason: review
|
|
62
|
+
? "SBOM risk or package manager policy requires dependency review before changes."
|
|
63
|
+
: scannerOff
|
|
64
|
+
? "No scanned vulnerability finding is present because the security scanner is off; run read-only security scan before security decisions."
|
|
65
|
+
: "No light SBOM signal requires dependency review.",
|
|
66
|
+
securityConfidence: scannerOff ? "scanner-off" : "scanner-summary",
|
|
60
67
|
mode: "advisory",
|
|
61
68
|
readFirst: ["riskSummary", "dependencyChangeHints", "packageManagerPolicy", "topRisk"],
|
|
62
69
|
reviewTargets: [...new Set(reviewTargets)].slice(0, 8),
|
package/src/commands/summary.js
CHANGED
|
@@ -56,6 +56,7 @@ export function renderSummary(status = {}, manifest = {}) {
|
|
|
56
56
|
const aiSignals = toList(aiReadiness.signals).slice(0, 5);
|
|
57
57
|
const aiNext = aiReadiness.next || "Run aienvmp context --json for details.";
|
|
58
58
|
const aiDependencyReview = manifest.lightSbom?.aiDependencyReview || {};
|
|
59
|
+
const strictPlan = status.enforcementProfile?.strictPlan || status.enforcement?.strictPlan || {};
|
|
59
60
|
|
|
60
61
|
return [
|
|
61
62
|
"# aienvmp summary",
|
|
@@ -66,6 +67,7 @@ export function renderSummary(status = {}, manifest = {}) {
|
|
|
66
67
|
`- AI safe local work: ${toList(aiReadiness.safeProjectLocalActions)[0] || "read artifacts and avoid environment changes until reviewed"}`,
|
|
67
68
|
`- AI read first: ${readFirst}, then ${detail}`,
|
|
68
69
|
`- mode: advisory by default; strict is opt-in with ${strict}`,
|
|
70
|
+
`- CI strict: ${strictPlan.ciCommand || `${strict} --json`}`,
|
|
69
71
|
"",
|
|
70
72
|
`- state: ${status.state || "unknown"}`,
|
|
71
73
|
`- workspace: ${workspace}`,
|
|
@@ -86,7 +88,7 @@ export function renderSummary(status = {}, manifest = {}) {
|
|
|
86
88
|
"",
|
|
87
89
|
`- source: ${manifest.lightSbom?.source?.dependencies || "project manifests"}`,
|
|
88
90
|
`- confidence: transitive ${manifest.lightSbom?.confidence?.transitiveDependencies || "not-resolved"}`,
|
|
89
|
-
`- AI dependency review: ${aiDependencyReview.status || "unknown"} / ${aiDependencyReview.beforeDependencyChange?.[0] || "aienvmp sbom --json"}`,
|
|
91
|
+
`- AI dependency review: ${aiDependencyReview.status || "unknown"} / ${aiDependencyReview.securityConfidence || "unknown"} / ${aiDependencyReview.beforeDependencyChange?.[0] || "aienvmp sbom --json"}`,
|
|
90
92
|
`- signals: ${riskSignals.length ? riskSignals.join("; ") : "none"}`,
|
|
91
93
|
`- verify: ${sbomRisk.next || "Use a dedicated scanner for security decisions."}`,
|
|
92
94
|
"",
|
package/src/contract.js
CHANGED
|
@@ -29,7 +29,7 @@ export function schemaContract() {
|
|
|
29
29
|
},
|
|
30
30
|
context: {
|
|
31
31
|
command: "aienvmp context --json",
|
|
32
|
-
rootFields: ["status", "preflight", "aiReadiness", "coordination", "agentPointers", "decision", "recommendedActions", "workspace", "dependencySnapshot", "lightSbom", "warnings"]
|
|
32
|
+
rootFields: ["status", "preflight", "aiReadiness", "coordination", "agentPointers", "decision", "enforcement", "recommendedActions", "workspace", "dependencySnapshot", "lightSbom", "warnings"]
|
|
33
33
|
},
|
|
34
34
|
handoff: {
|
|
35
35
|
command: "aienvmp handoff --json",
|
|
@@ -42,7 +42,8 @@ export function schemaContract() {
|
|
|
42
42
|
sbom: {
|
|
43
43
|
file: ".aienvmp/sbom.json",
|
|
44
44
|
command: "aienvmp sbom --json",
|
|
45
|
-
rootFields: ["schemaVersion", "schemaName", "workspace", "summary", "riskSummary", "topRisk", "packageManagerPolicy", "dependencyChangeHints", "aiDependencyReview"]
|
|
45
|
+
rootFields: ["schemaVersion", "schemaName", "workspace", "summary", "riskSummary", "topRisk", "packageManagerPolicy", "dependencyChangeHints", "aiDependencyReview"],
|
|
46
|
+
aiDependencyReviewFields: ["status", "statusReason", "securityConfidence", "readFirst", "reviewTargets", "beforeDependencyChange", "afterDependencyChange", "rule"]
|
|
46
47
|
},
|
|
47
48
|
cyclonedxLite: {
|
|
48
49
|
file: ".aienvmp/sbom.cdx.json",
|
|
@@ -54,7 +55,8 @@ export function schemaContract() {
|
|
|
54
55
|
stability: "additive",
|
|
55
56
|
consumerRule: "Ignore unknown fields. Do not require optional fields unless listed in requiredFields.",
|
|
56
57
|
localBehavior: "read-only; this command does not scan, install, update, or lock anything.",
|
|
57
|
-
aiReadinessRule: "When aiReadiness.level is review, project-local code work may still continue if aiReadiness.projectLocalWork is allowed; environment changes should follow intent/review guidance."
|
|
58
|
+
aiReadinessRule: "When aiReadiness.level is review, project-local code work may still continue if aiReadiness.projectLocalWork is allowed; environment changes should follow intent/review guidance.",
|
|
59
|
+
strictPlanRule: "Use enforcement.strictPlan or preflight.enforcementProfile.strictPlan to choose the narrowest explicit strict scope for CI."
|
|
58
60
|
}
|
|
59
61
|
};
|
|
60
62
|
}
|
package/src/dependencies.js
CHANGED
|
@@ -75,6 +75,9 @@ export function buildLightSbom(snapshot = {}, security = {}) {
|
|
|
75
75
|
fixAvailable: pkg.fixAvailable === true || Boolean(pkg.fixVersions?.length),
|
|
76
76
|
fixVersions: (pkg.fixVersions || []).slice(0, 3)
|
|
77
77
|
}));
|
|
78
|
+
const riskSummary = lightSbomRiskSummary({ packages, security, directVulnerable, transitiveOrUnmatched, topRisk, lockfiles: snapshot.lockfiles || [] });
|
|
79
|
+
const pmPolicy = packageManagerPolicy(snapshot.lockfiles || []);
|
|
80
|
+
const changeHints = dependencyChangeHints(packages, topRisk, snapshot.lockfiles || []);
|
|
78
81
|
return {
|
|
79
82
|
schemaVersion: 1,
|
|
80
83
|
mode: "light-sbom",
|
|
@@ -108,10 +111,10 @@ export function buildLightSbom(snapshot = {}, security = {}) {
|
|
|
108
111
|
transitiveOrUnmatchedVulnerablePackages: transitiveOrUnmatched.length
|
|
109
112
|
},
|
|
110
113
|
topRisk,
|
|
111
|
-
riskSummary
|
|
112
|
-
packageManagerPolicy:
|
|
113
|
-
dependencyChangeHints:
|
|
114
|
-
aiDependencyReview: aiDependencyReview({
|
|
114
|
+
riskSummary,
|
|
115
|
+
packageManagerPolicy: pmPolicy,
|
|
116
|
+
dependencyChangeHints: changeHints,
|
|
117
|
+
aiDependencyReview: aiDependencyReview({ riskSummary, packageManagerPolicy: pmPolicy, security }),
|
|
115
118
|
aiUse: {
|
|
116
119
|
beforeDependencyChanges: "Read lightSbom.summary and lightSbom.topRisk before changing dependencies.",
|
|
117
120
|
securityMode: security.enabled ? "scanner-summary" : "scanner-off",
|
|
@@ -121,14 +124,20 @@ export function buildLightSbom(snapshot = {}, security = {}) {
|
|
|
121
124
|
};
|
|
122
125
|
}
|
|
123
126
|
|
|
124
|
-
function aiDependencyReview({
|
|
125
|
-
const
|
|
126
|
-
const
|
|
127
|
+
function aiDependencyReview({ riskSummary = {}, packageManagerPolicy = {}, security = {} } = {}) {
|
|
128
|
+
const review = ["urgent", "high", "medium"].includes(riskSummary.level) || packageManagerPolicy.status === "review-required";
|
|
129
|
+
const scannerOff = security.enabled !== true;
|
|
127
130
|
return {
|
|
128
131
|
status: review ? "review" : "ready",
|
|
132
|
+
statusReason: review
|
|
133
|
+
? "SBOM risk or package manager policy requires dependency review before changes."
|
|
134
|
+
: scannerOff
|
|
135
|
+
? "No scanned vulnerability finding is present because the security scanner is off; run read-only security scan before security decisions."
|
|
136
|
+
: "No light SBOM signal requires dependency review.",
|
|
137
|
+
securityConfidence: scannerOff ? "scanner-off" : "scanner-summary",
|
|
129
138
|
mode: "advisory",
|
|
130
139
|
readFirst: ["riskSummary", "dependencyChangeHints", "packageManagerPolicy", "topRisk"],
|
|
131
|
-
reviewTargets:
|
|
140
|
+
reviewTargets: riskSummary.reviewTargets || [],
|
|
132
141
|
safeActions: [
|
|
133
142
|
"read SBOM, status, summary, context, and dependency manifests before dependency changes",
|
|
134
143
|
"plan remediation without installing, upgrading, downgrading, or switching package managers",
|
package/src/enforcement.js
CHANGED
|
@@ -33,6 +33,7 @@ export function enforcementAdvice(warnings = []) {
|
|
|
33
33
|
gate: enforcementGate(""),
|
|
34
34
|
suggestedStrictScopes,
|
|
35
35
|
scopes: scopeResults,
|
|
36
|
+
strictPlan: strictScopePlan(suggestedStrictScopes, scopeResults),
|
|
36
37
|
recommendedCommand: suggestedStrictScopes.length
|
|
37
38
|
? `aienvmp doctor --strict ${suggestedStrictScopes[0]}`
|
|
38
39
|
: "aienvmp doctor --strict all",
|
|
@@ -40,6 +41,26 @@ export function enforcementAdvice(warnings = []) {
|
|
|
40
41
|
};
|
|
41
42
|
}
|
|
42
43
|
|
|
44
|
+
export function strictScopePlan(suggestedStrictScopes = [], scopeResults = []) {
|
|
45
|
+
const firstScope = suggestedStrictScopes[0] || "all";
|
|
46
|
+
return {
|
|
47
|
+
mode: "advisory-local-strict-ci",
|
|
48
|
+
localDefault: "aienvmp doctor --json",
|
|
49
|
+
recommendedStrictScope: firstScope,
|
|
50
|
+
recommendedStrictCommand: `aienvmp doctor --strict ${firstScope}`,
|
|
51
|
+
ciCommand: `aienvmp doctor --strict ${firstScope} --json`,
|
|
52
|
+
allScopesCommand: "aienvmp doctor --strict all --json",
|
|
53
|
+
scopeStatuses: scopeResults.map((item) => ({
|
|
54
|
+
scope: item.scope,
|
|
55
|
+
status: item.status,
|
|
56
|
+
matchedWarningCodes: item.matchedWarningCodes || []
|
|
57
|
+
})),
|
|
58
|
+
rule: suggestedStrictScopes.length
|
|
59
|
+
? "Use the narrowest failing strict scope first; keep local operation advisory unless CI or the user explicitly requests failure."
|
|
60
|
+
: "No scope currently fails; use --strict all only for explicit CI health checks."
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
|
|
43
64
|
export function enforcementGate(scope = "") {
|
|
44
65
|
const strictScope = normalizeStrictScope(scope);
|
|
45
66
|
return {
|
package/src/preflight.js
CHANGED
|
@@ -36,6 +36,7 @@ export function buildPreflight(manifest = {}, warnings = [], intents = [], timel
|
|
|
36
36
|
gate: enforcementGate(""),
|
|
37
37
|
reason: "Avoid disrupting shared servers or developer machines while still making drift visible.",
|
|
38
38
|
recommendedStrictCommand: enforcement.recommendedCommand,
|
|
39
|
+
strictPlan: enforcement.strictPlan,
|
|
39
40
|
strictCommands: [
|
|
40
41
|
"aienvmp doctor --strict security",
|
|
41
42
|
"aienvmp doctor --strict policy",
|
package/src/render.js
CHANGED
|
@@ -434,7 +434,7 @@ const aiDependencyReview=lightSbom.aiDependencyReview||{};
|
|
|
434
434
|
const dependencyHintsHtml=dependencyHints.length?'<div class="timeline">'+dependencyHints.slice(0,5).map(h=>\`<div class="event"><time>\${esc(h.ecosystem||'deps')}</time><div><b>\${esc(h.manifest)}</b> <code>\${esc(h.manager||'unknown')}</code> \${esc(h.packages||0)} packages\${h.riskPackages?.length?\`<div class="path">risk: \${esc(h.riskPackages.map(p=>p.name).join(', '))}</div>\`:''}<div class="path">\${esc((h.groups||[]).join(', ')||'no groups')}\${h.lockfiles?.length?\` / lockfiles: \${esc(h.lockfiles.map(l=>l.file).join(', '))}\`:''}</div></div></div>\`).join('')+'</div>':'<div class="okline">No dependency change hints available.</div>';
|
|
435
435
|
const pmPolicyHtml='<table><tr><th>Status</th><td><code>'+esc(pmPolicy.status||'no-lockfile')+'</code></td></tr><tr><th>Guidance</th><td>'+esc(pmPolicy.guidance||'No lockfile policy detected.')+'</td></tr></table>';
|
|
436
436
|
const riskSummaryHtml=riskSummary.level?\`<table><tr><th>Level</th><td><code>\${esc(riskSummary.level)}</code> \${esc(riskSummary.score||0)}</td></tr><tr><th>Scanner</th><td><code>\${esc(riskSummary.scanner||'unknown')}</code></td></tr><tr><th>Next</th><td>\${esc(riskSummary.next||'No SBOM action required.')}</td></tr><tr><th>Targets</th><td>\${esc((riskSummary.reviewTargets||[]).join(', ')||'none')}</td></tr></table>\${riskSummary.signals?.length?'<div class="timeline">'+riskSummary.signals.slice(0,5).map(s=>\`<div class="event"><time>risk</time><div>\${esc(s)}</div></div>\`).join('')+'</div>':''}\`:'<div class="okline">No risk summary available.</div>';
|
|
437
|
-
const aiDependencyReviewHtml=aiDependencyReview.status?\`<table><tr><th>Status</th><td><code>\${esc(aiDependencyReview.status)}</code> \${esc(aiDependencyReview.mode||'advisory')}</td></tr><tr><th>Read first</th><td>\${esc((aiDependencyReview.readFirst||[]).join(', ')||'riskSummary')}</td></tr><tr><th>Targets</th><td>\${esc((aiDependencyReview.reviewTargets||[]).join(', ')||'none')}</td></tr><tr><th>Before</th><td><code>\${esc((aiDependencyReview.beforeDependencyChange||[]).slice(0,3).join(' -> ')||'aienvmp intent --actor agent:id --action dependency-review --target dependency')}</code></td></tr><tr><th>After</th><td><code>\${esc((aiDependencyReview.afterDependencyChange||[]).slice(-1)[0]||'aienvmp checkpoint --actor agent:id --summary dependency-change --target dependency')}</code></td></tr></table><div class="path">\${esc(aiDependencyReview.rule||'Dependency review is advisory and non-blocking.')}</div>\`:'<div class="okline">No AI dependency review block available. Run <code>aienvmp sync</code>.</div>';
|
|
437
|
+
const aiDependencyReviewHtml=aiDependencyReview.status?\`<table><tr><th>Status</th><td><code>\${esc(aiDependencyReview.status)}</code> \${esc(aiDependencyReview.mode||'advisory')}</td></tr><tr><th>Reason</th><td>\${esc(aiDependencyReview.statusReason||'No dependency review reason provided.')}</td></tr><tr><th>Security confidence</th><td><code>\${esc(aiDependencyReview.securityConfidence||'unknown')}</code></td></tr><tr><th>Read first</th><td>\${esc((aiDependencyReview.readFirst||[]).join(', ')||'riskSummary')}</td></tr><tr><th>Targets</th><td>\${esc((aiDependencyReview.reviewTargets||[]).join(', ')||'none')}</td></tr><tr><th>Before</th><td><code>\${esc((aiDependencyReview.beforeDependencyChange||[]).slice(0,3).join(' -> ')||'aienvmp intent --actor agent:id --action dependency-review --target dependency')}</code></td></tr><tr><th>After</th><td><code>\${esc((aiDependencyReview.afterDependencyChange||[]).slice(-1)[0]||'aienvmp checkpoint --actor agent:id --summary dependency-change --target dependency')}</code></td></tr></table><div class="path">\${esc(aiDependencyReview.rule||'Dependency review is advisory and non-blocking.')}</div>\`:'<div class="okline">No AI dependency review block available. Run <code>aienvmp sync</code>.</div>';
|
|
438
438
|
const lightSbomHtml=\`<table><tr><th>Packages</th><td><code>\${esc(lightSbomSummary.packages||0)}</code></td></tr><tr><th>Vulnerabilities</th><td><code>\${esc(lightSbomSummary.vulnerabilities||0)}</code></td></tr><tr><th>Direct vulnerable</th><td><code>\${esc(lightSbomSummary.directVulnerablePackages||0)}</code></td></tr><tr><th>Manifests</th><td><code>\${esc((lightSbomSummary.manifests||[]).join(', ')||'none')}</code></td></tr><tr><th>Lockfiles</th><td><code>\${esc((lightSbomSummary.lockfiles||[]).map(l=>l.file).join(', ')||'none')}</code></td></tr></table><h3 style="margin-top:12px">AI Dependency Review</h3>\${aiDependencyReviewHtml}<h3 style="margin-top:12px">Risk summary</h3>\${riskSummaryHtml}<h3 style="margin-top:12px">Package manager policy</h3>\${pmPolicyHtml}\${topRisk.length?'<div class="timeline">'+topRisk.slice(0,5).map(p=>\`<div class="event"><time>\${esc(p.priority)}</time><div><b>\${esc(p.name)}</b> \${esc(p.severity)} \${p.directDependency?'<code>direct</code>':'<code>transitive</code>'}<div class="path">\${esc(p.manifest||p.ecosystem)} \${esc(p.version||'')}</div></div></div>\`).join('')+'</div>':'<div class="okline">No high-risk package summary in the current light SBOM.</div>'}<h3 style="margin-top:12px">Dependency change hints</h3>\${dependencyHintsHtml}\`;
|
|
439
439
|
const sec=manifest.security||{};
|
|
440
440
|
const secSummary=sec.summary||{total:0,critical:0,high:0,moderate:0,low:0,info:0};
|
|
@@ -473,7 +473,8 @@ const ciReadinessHtml=ciReadiness.length?'<table>'+ciReadiness.map(s=>\`<tr><th>
|
|
|
473
473
|
const enforcementProfile=manifest.preflight?.enforcementProfile||{};
|
|
474
474
|
const strictCommands=enforcementProfile.strictCommands||[];
|
|
475
475
|
const gate=enforcementProfile.gate||{};
|
|
476
|
-
const
|
|
476
|
+
const strictPlan=enforcementProfile.strictPlan||{};
|
|
477
|
+
const enforcementHtml=\`<table><tr><th>Default</th><td><code>\${esc(gate.defaultMode||enforcementProfile.defaultMode||'advisory')}</code> \${esc(gate.localDefault||'warn-only')}</td></tr><tr><th>Strict</th><td><code>\${esc(gate.strictMode||'off')}</code></td></tr><tr><th>Fail</th><td>\${esc(gate.failCondition||'never in default mode')}</td></tr><tr><th>Exit</th><td>\${esc(gate.exitCode||'0 unless the command itself errors')}</td></tr><tr><th>Recommended</th><td><code>\${esc(strictPlan.recommendedStrictCommand||enforcementProfile.recommendedStrictCommand||'aienvmp doctor --strict all')}</code></td></tr><tr><th>CI</th><td><code>\${esc(strictPlan.ciCommand||'aienvmp doctor --strict all --json')}</code></td></tr></table><div class="timeline">\${strictCommands.slice(0,4).map(cmd=>\`<div class="event"><time>CI</time><div><code>\${esc(cmd)}</code></div></div>\`).join('')}</div><div class="path">\${esc(strictPlan.rule||gate.rule||enforcementProfile.reason||'Warnings stay advisory unless strict mode is requested.')}</div>\`;
|
|
477
478
|
const contract=manifest.preflight?.contract||{};
|
|
478
479
|
const contractHtml=contract.name?\`<table><tr><th>Name</th><td><code>\${esc(contract.name)}</code></td></tr><tr><th>Version</th><td><code>\${esc(contract.version||1)}</code></td></tr><tr><th>Stability</th><td><code>\${esc(contract.stability||'additive')}</code></td></tr><tr><th>AI fields</th><td>\${esc((contract.aiEntryFields||[]).join(', ')||'none')}</td></tr></table><div class="path">\${esc(contract.rule||'Ignore unknown fields.')}</div>\`:'<div class="okline">Run <code>aienvmp status --write</code> to include AI contract metadata.</div>';
|
|
479
480
|
const intentTargets=manifest.preflight?.intentTargets||[];
|