mindforge-cc 11.5.0 → 11.5.1

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,11 +1,11 @@
1
1
  {
2
- "version": "11.5.0",
2
+ "version": "11.5.1",
3
3
  "environment": "development",
4
4
  "governance": {
5
5
  "drift_threshold": 0.75,
6
6
  "critical_drift_threshold": 0.5,
7
7
  "res_threshold": 0.8,
8
- "active_did": "did:mindforge:f5ee24cf-f359-475e-bd76-c83e3d6fce02"
8
+ "active_did": "did:mindforge:7dffeae7-eb70-45c9-9001-701fba7809d2"
9
9
  },
10
10
  "revops": {
11
11
  "market_registry": {
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "pattern-library.jsonl": {
3
- "lastSync": "2026-06-11T08:14:04.496Z",
3
+ "lastSync": "2026-06-11T11:41:34.054Z",
4
4
  "localCount": 1
5
5
  }
6
6
  }
package/CHANGELOG.md CHANGED
@@ -1,5 +1,50 @@
1
1
  # Changelog
2
2
 
3
+ ## [11.5.1] - 2026-06-11 — Robustness + governance-gate patch (Wave 8)
4
+
5
+ A fast-follow patch from a fresh adversarial audit of the shipped v11.5.0 tree.
6
+ Hardens crash-prone JSON parsing in the autonomous/memory pipelines, closes a
7
+ CI governance-gate gap, and tightens two security surfaces. No new features.
8
+
9
+ ### Fixed
10
+
11
+ - **Crash-proof AUDIT.jsonl parsing** (`bin/memory/pillar-health-tracker.js`) —
12
+ `summarizePhase()` parsed every audit line with an unguarded `JSON.parse`, so a
13
+ single malformed/torn line crashed the knowledge-capture pipeline. Now parses
14
+ per-line in try/catch and skips bad lines.
15
+ - **Crash-proof compaction capture** (`bin/memory/knowledge-capture.js`) — a
16
+ malformed `handoff.json` no longer throws out of `captureFromCompaction()`; it
17
+ logs and returns `[]`, mirroring the missing-file path.
18
+ - **Resilient federated-sync stats** (`bin/memory/federated-sync.js`) — the two
19
+ unguarded `JSON.parse` calls on `sync-stats.json` (`handleSyncFailure`,
20
+ `resetFailures`) now fall back to `{failures:0}` on corruption, matching the
21
+ sibling `getLastSyncTimestamp` pattern.
22
+
23
+ ### Security
24
+
25
+ - **CI Tier-3 governance gate now validates content** (`.github/workflows/control-plane.yml`)
26
+ — the gate counted approval files but never checked them; it now requires at
27
+ least one approval with `identity_verification.verified === true` and a
28
+ signature, and rejects any unverified/empty file. Completes the Wave-6
29
+ fail-closed `approve.js` work (a hand-committed empty approval no longer passes).
30
+ - **Dashboard approval attribution** (`bin/dashboard/api-router.js`) —
31
+ `POST /api/approve/:id` no longer records the client-supplied `approver`
32
+ (forgeable audit identity); it attributes the action to a fixed authenticated
33
+ actor. The dashboard remains localhost-bound + token-gated.
34
+ - **Destructive-command detector blocks Unix `truncate`** (`bin/security/trust-boundaries.js`)
35
+ — the SQL-only `truncate table` pattern missed `truncate -s 0 <path>` (in-place
36
+ file zeroing). Added a size-flag pattern so it is gated; benign uses stay allowed.
37
+ - **CI Tier-3 gate accepts an explicitly-acknowledged unverified approval**
38
+ (`.github/workflows/control-plane.yml`, `bin/governance/approve.js`) — since this
39
+ repo has no GPG signing infra, the gate accepts an approval that is either
40
+ GPG-verified OR an opted-in `unverified_ack` record (`approve.js` under
41
+ `MINDFORGE_ALLOW_UNVERIFIED_APPROVAL=1`), while still rejecting bare/stale
42
+ `verified:false` files. Replaced the stale v11.4.0 approval with a fresh one.
43
+ - **`uuid` dependency removed from `ads-engine`** (`bin/review/ads-engine.js`) — it
44
+ required the uninstalled `uuid` package, making `ads-engine` and (transitively)
45
+ `federated-sync` un-loadable in a clean install. Swapped to the built-in
46
+ `crypto.randomUUID()` (zero-native-deps); both modules now load.
47
+
3
48
  ## [11.5.0] - 2026-06-11 — Governance hardening + autonomous-engine repair (Waves 4–7)
4
49
 
5
50
  This release bundles four waves of work: orchestration primitives, an **inert** manifest
package/MINDFORGE.md CHANGED
@@ -1,12 +1,12 @@
1
- # MINDFORGE.md — Parameter Registry (v11.5.0)
1
+ # MINDFORGE.md — Parameter Registry (v11.5.1)
2
2
 
3
3
  ## 1. IDENTITY & VERSIONING
4
4
 
5
5
  [NAME] = MindForge
6
- [VERSION] = 11.5.0
6
+ [VERSION] = 11.5.1
7
7
  [STABLE] = true
8
8
  [MODE] = "Platform Sovereign"
9
- [REQUIRED_CORE_VERSION] = 11.5.0
9
+ [REQUIRED_CORE_VERSION] = 11.5.1
10
10
  [SOVEREIGN_IDENTITY] = true
11
11
  [SRE_LAYER_ENABLED] = true
12
12
 
package/README.md CHANGED
@@ -501,4 +501,3 @@ MindForge never stores credentials in files. Review:
501
501
 
502
502
  ## ⚖️ License
503
503
  MIT © 2026 MindForge Team
504
- eam
package/RELEASENOTES.md CHANGED
@@ -1,5 +1,50 @@
1
1
  # Release Notes
2
2
 
3
+ ## v11.5.1 — Robustness + governance-gate patch
4
+
5
+ **Release Date**: 2026-06-11
6
+ **Type**: Patch (no API changes; one CI-gate behavior change)
7
+ **Upgrade Path**: `npx mindforge-cc@latest`
8
+
9
+ A fast-follow patch driven by a fresh adversarial audit of the shipped v11.5.0
10
+ tree. It hardens crash-prone JSON parsing in the autonomous/memory pipelines,
11
+ closes a governance-gate gap left by the v11.5.0 approval work, and tightens two
12
+ security surfaces. No features, no API changes.
13
+
14
+ ### Robustness — no more crashes on a torn JSONL line
15
+
16
+ Three pipelines parsed JSON without guards, so one malformed/partially-written
17
+ line could crash them:
18
+
19
+ - `summarizePhase()` (pillar-health) parsed every `AUDIT.jsonl` line unguarded —
20
+ it now skips bad lines and keeps the valid ones.
21
+ - `captureFromCompaction()` (knowledge-capture) now returns `[]` on a malformed
22
+ `handoff.json` instead of throwing.
23
+ - `federated-sync` now tolerates a corrupted `sync-stats.json` in both
24
+ `handleSyncFailure` and `resetFailures` (falls back to `{failures:0}`).
25
+
26
+ ### Security & governance
27
+
28
+ - **The CI Tier-3 gate now actually validates approvals.** Previously it only
29
+ counted approval files; a hand-committed empty file would pass. It now requires
30
+ each approval to carry a signature and be EITHER GPG-verified
31
+ (`verified: true`) OR an explicitly opted-in unverified approval
32
+ (`unverified_ack`, minted by `approve.js` under
33
+ `MINDFORGE_ALLOW_UNVERIFIED_APPROVAL=1` for repos without GPG infra). Bare or
34
+ stale `verified:false` files are still rejected — completing the v11.5.0
35
+ fail-closed `approve.js` work.
36
+ - **Dashboard approvals can't forge an identity.** `POST /api/approve/:id` no
37
+ longer records a client-supplied `approver` into the audit trail; it attributes
38
+ the action to a fixed authenticated actor. (The dashboard is already
39
+ localhost-bound and Bearer-token gated, so this is attribution hardening.)
40
+ - **The destructive-command guard now blocks Unix `truncate -s`.** In-place file
41
+ zeroing (`truncate -s 0 <path>`) was missed by the SQL-only pattern; it is now
42
+ gated, with benign uses unaffected.
43
+ - **A shipped module that couldn't load is fixed.** `bin/review/ads-engine.js`
44
+ required the uninstalled `uuid` package — so it (and the `federated-sync` that
45
+ imports it) threw on load in a clean install. Swapped to the built-in
46
+ `crypto.randomUUID()`; no new dependency.
47
+
3
48
  ## v11.5.0 — Governance hardening + autonomous-engine repair
4
49
 
5
50
  **Release Date**: 2026-06-11
@@ -75,12 +75,21 @@ function register(app) {
75
75
  app.post('/api/approve/:id', (req, res) => {
76
76
  try {
77
77
  const { id } = req.params;
78
- const { decision, comment, approver } = req.body || {};
78
+ const { decision, comment } = req.body || {};
79
79
 
80
80
  if (!decision) {
81
81
  return res.status(400).json({ error: 'Missing "decision" field (approve|reject)' });
82
82
  }
83
83
 
84
+ // SECURITY (v11.5.1): do NOT trust a client-supplied `approver` for the
85
+ // recorded identity — it is forgeable and would let any caller write a
86
+ // false approval audit trail (e.g. resolved_by: 'admin'). requireAuth
87
+ // (server.js) proves the caller holds the owner-only dashboard token but
88
+ // exposes no named principal, so we attribute the action to a FIXED
89
+ // trusted actor. (A future RBAC pass can map a Bearer token -> DID and
90
+ // record the real principal; until then, never echo req.body.approver.)
91
+ const approver = 'dashboard-authenticated';
92
+
84
93
  const result = Approval.processDecision(id, decision, comment, approver);
85
94
 
86
95
  if (!result.success) {
@@ -59,7 +59,11 @@ function verifyApproverIdentity(approver) {
59
59
  }
60
60
  console.warn('[GOVERNANCE] No GPG key — minting an UNVERIFIED approval (MINDFORGE_ALLOW_UNVERIFIED_APPROVAL=1). ' +
61
61
  'git identity is spoofable; this approval is NOT cryptographically attributed.');
62
- return { verified: false, method: 'git_identity_unverified', identity: approver };
62
+ // unverified_ack=true is the EXPLICIT, audited override the CI Tier-3 gate
63
+ // looks for. A bare verified:false record WITHOUT this marker (e.g. a stale
64
+ // pre-fail-closed file or a hand-forged empty one) is still rejected by the
65
+ // gate — only a deliberately opted-in unverified approval is accepted.
66
+ return { verified: false, method: 'git_identity_unverified', identity: approver, unverified_ack: true };
63
67
  }
64
68
 
65
69
  return { verified: true, method: 'gpg_key', identity: approver, keyId: gpgKey };
@@ -58,7 +58,11 @@ class FederatedSync {
58
58
  const statsPath = path.join(this.localStore.getPaths().MEMORY_DIR, 'sync-stats.json');
59
59
  let stats = { failures: 0 };
60
60
  if (fs.existsSync(statsPath)) {
61
- stats = JSON.parse(fs.readFileSync(statsPath, 'utf8'));
61
+ try {
62
+ stats = JSON.parse(fs.readFileSync(statsPath, 'utf8'));
63
+ } catch {
64
+ stats = { failures: 0 };
65
+ }
62
66
  }
63
67
  stats.failures = (stats.failures || 0) + 1;
64
68
  stats.last_error = err.message;
@@ -122,7 +126,12 @@ class FederatedSync {
122
126
  resetFailures() {
123
127
  const statsPath = path.join(this.localStore.getPaths().MEMORY_DIR, 'sync-stats.json');
124
128
  if (fs.existsSync(statsPath)) {
125
- const stats = JSON.parse(fs.readFileSync(statsPath, 'utf8'));
129
+ let stats = { failures: 0 };
130
+ try {
131
+ stats = JSON.parse(fs.readFileSync(statsPath, 'utf8'));
132
+ } catch {
133
+ stats = { failures: 0 };
134
+ }
126
135
  stats.failures = 0;
127
136
  fs.writeFileSync(statsPath, JSON.stringify(stats, null, 2));
128
137
  }
@@ -238,7 +238,16 @@ function captureFromPhaseCompletion(phaseNum) {
238
238
  function captureFromCompaction(handoffPath) {
239
239
  if (!fs.existsSync(handoffPath)) return [];
240
240
 
241
- const handoff = JSON.parse(fs.readFileSync(handoffPath, 'utf8'));
241
+ let handoff;
242
+ try {
243
+ handoff = JSON.parse(fs.readFileSync(handoffPath, 'utf8'));
244
+ } catch (err) {
245
+ // Malformed handoff.json must not crash the capture pipeline — mirror the
246
+ // missing-file path and return [] after logging the parse failure.
247
+ console.error(`[knowledge-capture] Failed to parse handoff file ${handoffPath}: ${err.message}`);
248
+ return [];
249
+ }
250
+
242
251
  const items = handoff.implicit_knowledge || [];
243
252
  const project = getProjectName();
244
253
  const captured = [];
@@ -18,7 +18,15 @@ class PillarHealthTracker {
18
18
  if (!fs.existsSync(auditPath)) return null;
19
19
 
20
20
  const lines = fs.readFileSync(auditPath, 'utf8').trim().split('\n');
21
- const events = lines.map(l => JSON.parse(l));
21
+ const events = lines
22
+ .map(l => {
23
+ try {
24
+ return JSON.parse(l);
25
+ } catch {
26
+ return null; // Skip malformed lines rather than crashing the pipeline.
27
+ }
28
+ })
29
+ .filter(Boolean);
22
30
 
23
31
  // 1. RSA (Mission Fidelity) Analysis
24
32
  const rsaEvents = events.filter(e => e.type === 'mission_fidelity' || e.event === 'scs_homing_injected');
@@ -8,7 +8,7 @@ const fs = require('fs');
8
8
  const path = require('path');
9
9
  const ModelClient = require('../models/model-client');
10
10
  const { calculateSoulScore, parseMetrics, synthesizeADSPlan } = require('./ads-synthesizer');
11
- const { v4: uuidv4 } = require('uuid');
11
+ const crypto = require('crypto');
12
12
 
13
13
  async function runADSSynthesis(params) {
14
14
  const {
@@ -89,7 +89,7 @@ Finalize the PLAN.md. Include the [ADS_VERDICT]: [MERGED|BLUE|RED] (Score: X.XXX
89
89
  process.stdout.write('done.\n');
90
90
 
91
91
  // Finalize outputs
92
- const adsUuid = uuidv4();
92
+ const adsUuid = crypto.randomUUID();
93
93
  const adrDir = path.join(process.cwd(), '.planning', 'decisions');
94
94
  if (!fs.existsSync(adrDir)) fs.mkdirSync(adrDir, { recursive: true });
95
95
 
@@ -124,6 +124,11 @@ function isHighImpact(command) {
124
124
  /git\s+reset\s+--hard/i,
125
125
  /delete\s+from/i,
126
126
  /truncate\s+table/i,
127
+ // Unix `truncate -s <size> <path>` zeroes/shrinks a file in place — a
128
+ // destructive data-loss op the SQL-only `truncate table` pattern above
129
+ // misses. Match the size flag (-s, -s0, --size) so `truncate -s 0 <path>`
130
+ // is gated; benign words ("truncated output") and the SQL form are not.
131
+ /\btruncate\s+(-{1,2}s\w*|--size)\b/i,
127
132
  /\bmkfs(\.\w+)?\s+\/dev\//i,
128
133
  // #11: any dd write target, not just /dev/ (dd if=... of=important.db).
129
134
  // Original /dev/-only check is a subset of this, so it stays covered.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "mindforge-cc",
3
- "version": "11.5.0",
3
+ "version": "11.5.1",
4
4
  "description": "MindForge \u2014 Sovereign Agentic Intelligence Framework. Sovereign Stability: Production-Hardened Agentic Intelligence (v11)",
5
5
  "bin": {
6
6
  "mindforge-cc": "bin/install.js",