@clear-capabilities/agentic-security-scanner 0.144.0 → 0.147.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 (209) hide show
  1. package/CHANGELOG.md +390 -0
  2. package/bin/agentic-security.js +3813 -83
  3. package/dist/1122.index.js +702 -0
  4. package/dist/{301.index.js → 1301.index.js} +2 -2
  5. package/dist/1379.index.js +591 -0
  6. package/dist/{444.index.js → 1444.index.js} +13 -4
  7. package/dist/{660.index.js → 1660.index.js} +2 -2
  8. package/dist/{700.index.js → 1700.index.js} +2 -2
  9. package/dist/{905.index.js → 1905.index.js} +3 -3
  10. package/dist/{920.index.js → 1920.index.js} +3 -3
  11. package/dist/{238.index.js → 2238.index.js} +3 -3
  12. package/dist/2271.index.js +165 -0
  13. package/dist/{985.index.js → 2376.index.js} +1260 -340
  14. package/dist/2432.index.js +793 -0
  15. package/dist/2659.index.js +93 -0
  16. package/dist/{826.index.js → 2826.index.js} +2 -2
  17. package/dist/{830.index.js → 2830.index.js} +2 -2
  18. package/dist/2923.index.js +298 -0
  19. package/dist/{1.index.js → 3001.index.js} +5 -5
  20. package/dist/{117.index.js → 3117.index.js} +3 -3
  21. package/dist/3180.index.js +307 -0
  22. package/dist/3276.index.js +117 -0
  23. package/dist/{415.index.js → 3415.index.js} +2 -2
  24. package/dist/{499.index.js → 3499.index.js} +2 -2
  25. package/dist/3518.index.js +450 -0
  26. package/dist/{526.index.js → 3526.index.js} +14 -6
  27. package/dist/{736.index.js → 3736.index.js} +4 -4
  28. package/dist/{839.index.js → 3839.index.js} +4 -4
  29. package/dist/{113.index.js → 4113.index.js} +14 -6
  30. package/dist/{265.index.js → 4265.index.js} +2 -2
  31. package/dist/{384.index.js → 4384.index.js} +3 -3
  32. package/dist/4547.index.js +268 -0
  33. package/dist/4863.index.js +422 -0
  34. package/dist/{970.index.js → 4970.index.js} +67 -3
  35. package/dist/5051.index.js +770 -0
  36. package/dist/{144.index.js → 5144.index.js} +5 -5
  37. package/dist/{333.index.js → 5333.index.js} +3 -3
  38. package/dist/5343.index.js +185 -0
  39. package/dist/5350.index.js +866 -0
  40. package/dist/5561.index.js +436 -0
  41. package/dist/{637.index.js → 5637.index.js} +29 -7
  42. package/dist/{449.index.js → 5830.index.js} +78 -14
  43. package/dist/6626.index.js +532 -0
  44. package/dist/6662.index.js +297 -0
  45. package/dist/{675.index.js → 6675.index.js} +5 -5
  46. package/dist/{730.index.js → 6730.index.js} +6 -6
  47. package/dist/6829.index.js +225 -0
  48. package/dist/6944.index.js +130 -0
  49. package/dist/{178.index.js → 7178.index.js} +26 -8
  50. package/dist/{227.index.js → 7227.index.js} +2 -2
  51. package/dist/7310.index.js +520 -0
  52. package/dist/{552.index.js → 7552.index.js} +4 -4
  53. package/dist/7709.index.js +78 -0
  54. package/dist/8218.index.js +160 -0
  55. package/dist/{476.index.js → 8476.index.js} +4 -4
  56. package/dist/{513.index.js → 8513.index.js} +5 -5
  57. package/dist/{520.index.js → 8520.index.js} +2 -2
  58. package/dist/{718.index.js → 8718.index.js} +2 -2
  59. package/dist/{752.index.js → 8752.index.js} +2 -2
  60. package/dist/8846.index.js +100 -0
  61. package/dist/{435.index.js → 9091.index.js} +580 -187
  62. package/dist/{207.index.js → 9207.index.js} +2 -2
  63. package/dist/{220.index.js → 9220.index.js} +2 -2
  64. package/dist/9390.index.js +163 -0
  65. package/dist/{503.index.js → 9503.index.js} +2 -2
  66. package/dist/{801.index.js → 9801.index.js} +2 -2
  67. package/dist/{824.index.js → 9824.index.js} +2 -2
  68. package/dist/agentic-security.mjs +16 -16
  69. package/dist/agentic-security.mjs.sha256 +1 -1
  70. package/dist/compliance-frameworks/hipaa-security-rule.json +3 -2
  71. package/package.json +23 -10
  72. package/src/compare.js +6 -1
  73. package/src/dataflow/CLAUDE.md +2 -2
  74. package/src/dataflow/catalog.js +42 -0
  75. package/src/dataflow/orm-write-catalog.js +175 -0
  76. package/src/engine.js +580 -30
  77. package/src/fix/apply-fix-service.js +1 -0
  78. package/src/history-scan.js +22 -5
  79. package/src/ir/CLAUDE.md +2 -1
  80. package/src/ir/chrome-probe.mjs +150 -0
  81. package/src/ir/parser-js.js +94 -7
  82. package/src/lineage/CLAUDE.md +1203 -0
  83. package/src/lineage/DESIGN_DESTINATION_RESOLVER.md +156 -0
  84. package/src/lineage/DESIGN_GRAPH_BUILDER.md +938 -0
  85. package/src/lineage/DESIGN_HANDLING_ANALYZER.md +355 -0
  86. package/src/lineage/DESIGN_INTRAPROCEDURAL.md +628 -0
  87. package/src/lineage/DESIGN_PATH_PROVENANCE.md +3451 -0
  88. package/src/lineage/DESIGN_QUEUE_DETAIL.md +120 -0
  89. package/src/lineage/DESIGN_REGISTRIES.md +880 -0
  90. package/src/lineage/DESIGN_STORE_DETAIL.md +143 -0
  91. package/src/lineage/DESIGN_TRANSIT_PROTECTION.md +245 -0
  92. package/src/lineage/classification.js +56 -0
  93. package/src/lineage/coverage.js +658 -0
  94. package/src/lineage/cross-repo-link.js +107 -0
  95. package/src/lineage/dataflow-graph.schema.json +184 -0
  96. package/src/lineage/decision-story.js +206 -0
  97. package/src/lineage/drift-policy.js +279 -0
  98. package/src/lineage/driver.js +135 -0
  99. package/src/lineage/engine.js +992 -0
  100. package/src/lineage/export-briefing.js +628 -0
  101. package/src/lineage/export-csv.js +62 -0
  102. package/src/lineage/export-json.js +238 -0
  103. package/src/lineage/export-privacy.js +258 -0
  104. package/src/lineage/federation-loader.js +111 -0
  105. package/src/lineage/field-identity.js +78 -0
  106. package/src/lineage/fixtures/build-flagship-fixture.mjs +272 -0
  107. package/src/lineage/fixtures/flagship-graph.json +1453 -0
  108. package/src/lineage/flow-grade.js +221 -0
  109. package/src/lineage/governance-edit.js +169 -0
  110. package/src/lineage/graph-builder.js +1114 -0
  111. package/src/lineage/graph-diff.js +431 -0
  112. package/src/lineage/graph-snapshot.js +180 -0
  113. package/src/lineage/handling-analyzer.js +168 -0
  114. package/src/lineage/ids.js +349 -0
  115. package/src/lineage/impact-assessment.js +76 -0
  116. package/src/lineage/impact-engine.js +268 -0
  117. package/src/lineage/index.js +281 -0
  118. package/src/lineage/language-coverage-tiers.js +58 -0
  119. package/src/lineage/obligation-mapping.js +126 -0
  120. package/src/lineage/obligation-predicates.js +235 -0
  121. package/src/lineage/observation-adapters.js +282 -0
  122. package/src/lineage/observation-correlation.js +622 -0
  123. package/src/lineage/observation-store.js +497 -0
  124. package/src/lineage/path-query.js +410 -0
  125. package/src/lineage/path-store.js +400 -0
  126. package/src/lineage/protection.js +53 -0
  127. package/src/lineage/recipient-profile.js +192 -0
  128. package/src/lineage/recipient-registry.js +394 -0
  129. package/src/lineage/redact-graph.js +224 -0
  130. package/src/lineage/remediation.js +417 -0
  131. package/src/lineage/resolve-destination.js +91 -0
  132. package/src/lineage/runtime-observation.js +464 -0
  133. package/src/lineage/scenario-diff.js +84 -0
  134. package/src/lineage/scenario-engine.js +251 -0
  135. package/src/lineage/scenario.js +101 -0
  136. package/src/lineage/schema.js +167 -0
  137. package/src/lineage/sink-registry.js +427 -0
  138. package/src/lineage/source-registry.js +357 -0
  139. package/src/lineage/source-seeding.js +212 -0
  140. package/src/lineage/summaries.js +590 -0
  141. package/src/lineage/transform-catalog.js +397 -0
  142. package/src/lineage/transit-protection.js +150 -0
  143. package/src/lineage/validate.js +285 -0
  144. package/src/lsp/server.js +49 -2
  145. package/src/mcp/CLAUDE.md +7 -1
  146. package/src/mcp/dataflow-tools.js +160 -0
  147. package/src/mcp/server.js +1 -1
  148. package/src/mcp/tools.js +22 -1
  149. package/src/pipeline/assurance-mode.js +64 -1
  150. package/src/pipeline/finding-schema.js +8 -1
  151. package/src/pipeline/scan-health.js +19 -1
  152. package/src/posture/CLAUDE.md +140 -0
  153. package/src/posture/accuracy-scorecard.js +60 -0
  154. package/src/posture/artifact-registry.js +76 -0
  155. package/src/posture/auditor-walkthrough.js +192 -13
  156. package/src/posture/compliance-frameworks/hipaa-security-rule.json +3 -2
  157. package/src/posture/compliance-policy.js +12 -2
  158. package/src/posture/cross-repo-memory.js +7 -2
  159. package/src/posture/fix-history.js +25 -2
  160. package/src/posture/fix-verify.js +9 -1
  161. package/src/posture/fleet.js +0 -0
  162. package/src/posture/git-history.js +13 -5
  163. package/src/posture/material-change.js +21 -2
  164. package/src/posture/mttr.js +75 -12
  165. package/src/posture/obligation-evidence-pack.js +202 -0
  166. package/src/posture/pre-incident-archaeology.js +39 -7
  167. package/src/posture/privacy-framework.js +14 -0
  168. package/src/posture/provenance/ai-authorship.js +68 -0
  169. package/src/posture/provenance/branch-entry.js +80 -0
  170. package/src/posture/provenance/cache.js +143 -0
  171. package/src/posture/provenance/confidence.js +36 -0
  172. package/src/posture/provenance/coordinator.js +786 -0
  173. package/src/posture/provenance/dag-walk.js +249 -0
  174. package/src/posture/provenance/evidence-attribution.js +59 -0
  175. package/src/posture/provenance/git-evidence.js +310 -0
  176. package/src/posture/provenance/lifecycle.js +208 -0
  177. package/src/posture/provenance/missing-control-resolver.js +137 -0
  178. package/src/posture/provenance/origin-resolver.js +342 -0
  179. package/src/posture/provenance/predicate-replay.js +133 -0
  180. package/src/posture/provenance/providers/config.js +39 -0
  181. package/src/posture/provenance/providers/github.js +62 -0
  182. package/src/posture/provenance/providers/gitlab.js +58 -0
  183. package/src/posture/provenance/repo-lineage.js +74 -0
  184. package/src/posture/provenance/sca-origin.js +139 -0
  185. package/src/posture/provenance/schema.js +255 -0
  186. package/src/posture/provenance/transitive-sca.js +147 -0
  187. package/src/posture/provenance/validate.js +30 -0
  188. package/src/posture/provenance-evidence-bundle.js +144 -0
  189. package/src/posture/remediation-ledger.js +337 -0
  190. package/src/posture/sbom-diff.js +15 -2
  191. package/src/posture/secret-history.js +10 -2
  192. package/src/posture/state-dir.js +38 -14
  193. package/src/posture/vuln-archaeology.js +8 -2
  194. package/src/pr-delta.js +25 -4
  195. package/src/report/index.js +197 -3
  196. package/src/runScan.js +34 -5
  197. package/src/sast/rate-limit.js +33 -3
  198. package/src/server/CLAUDE.md +47 -0
  199. package/src/server/graph-loader.js +141 -0
  200. package/src/server/http-server.js +325 -0
  201. package/src/server/routes.js +129 -0
  202. package/src/server/security.js +111 -0
  203. package/src/server/static-assets.js +139 -0
  204. package/src/util/git-hardening.js +128 -0
  205. package/dist/11.index.js +0 -353
  206. package/dist/259.index.js +0 -975
  207. package/dist/317.index.js +0 -300
  208. package/dist/609.index.js +0 -741
  209. package/dist/838.index.js +0 -152
@@ -1,5 +1,5 @@
1
- export const id = 144;
2
- export const ids = [144];
1
+ export const id = 5144;
2
+ export const ids = [5144];
3
3
  export const modules = {
4
4
 
5
5
  /***/ 5144:
@@ -65,7 +65,7 @@ const LEGAL_HOLD_FILE = 'legal-holds.json';
65
65
 
66
66
  function _loadRaw(scanRoot) {
67
67
  let fp;
68
- try { fp = (0,_state_dir_js__WEBPACK_IMPORTED_MODULE_1__/* .statePath */ .BQ)(scanRoot, LEGAL_HOLD_FILE); } catch { return []; }
68
+ try { fp = (0,_state_dir_js__WEBPACK_IMPORTED_MODULE_1__.statePath)(scanRoot, LEGAL_HOLD_FILE); } catch { return []; }
69
69
  let raw;
70
70
  try { raw = node_fs__WEBPACK_IMPORTED_MODULE_0__.readFileSync(fp, 'utf8'); } catch { return []; }
71
71
  try {
@@ -135,7 +135,7 @@ function addLegalHold(scanRoot, { artifact, owner, reason, expires_at } = {}) {
135
135
  const hold = { artifact, owner, reason, expires_at: expires_at || null, created_at: new Date().toISOString() };
136
136
  const holds = _loadRaw(scanRoot);
137
137
  holds.push(hold);
138
- const fp = (0,_state_dir_js__WEBPACK_IMPORTED_MODULE_1__/* .statePath */ .BQ)(scanRoot, LEGAL_HOLD_FILE);
138
+ const fp = (0,_state_dir_js__WEBPACK_IMPORTED_MODULE_1__.statePath)(scanRoot, LEGAL_HOLD_FILE);
139
139
  if (!(0,_state_dir_js__WEBPACK_IMPORTED_MODULE_1__/* .safeWriteState */ .Ep)(fp, JSON.stringify(holds, null, 2) + '\n')) {
140
140
  return { ok: false, reason: 'state writes are disabled (--no-state) or this is not a safe state directory' };
141
141
  }
@@ -151,7 +151,7 @@ function removeLegalHold(scanRoot, artifact) {
151
151
  const remaining = holds.filter(h => !(h && h.artifact === artifact));
152
152
  const removedCount = holds.length - remaining.length;
153
153
  if (removedCount > 0) {
154
- const fp = (0,_state_dir_js__WEBPACK_IMPORTED_MODULE_1__/* .statePath */ .BQ)(scanRoot, LEGAL_HOLD_FILE);
154
+ const fp = (0,_state_dir_js__WEBPACK_IMPORTED_MODULE_1__.statePath)(scanRoot, LEGAL_HOLD_FILE);
155
155
  (0,_state_dir_js__WEBPACK_IMPORTED_MODULE_1__/* .safeWriteState */ .Ep)(fp, JSON.stringify(remaining, null, 2) + '\n');
156
156
  }
157
157
  return removedCount;
@@ -1,5 +1,5 @@
1
- export const id = 333;
2
- export const ids = [333];
1
+ export const id = 5333;
2
+ export const ids = [5333];
3
3
  export const modules = {
4
4
 
5
5
  /***/ 5333:
@@ -182,7 +182,7 @@ async function applyScaUpgrade({ scanRoot, finding, runTests = true }) {
182
182
  // we can restore on test failure. node_modules / vendor dirs are NOT
183
183
  // backed up (too big); they'll be rebuilt by re-running the install on
184
184
  // restore.
185
- const stateDir = (0,_state_dir_js__WEBPACK_IMPORTED_MODULE_6__/* .statePath */ .BQ)(scanRoot, 'sca-upgrade-history');
185
+ const stateDir = (0,_state_dir_js__WEBPACK_IMPORTED_MODULE_6__.statePath)(scanRoot, 'sca-upgrade-history');
186
186
  node_fs__WEBPACK_IMPORTED_MODULE_0__.mkdirSync(stateDir, { recursive: true });
187
187
  const upgradeId = node_crypto__WEBPACK_IMPORTED_MODULE_3__.randomBytes(8).toString('hex');
188
188
  const backups = {};
@@ -0,0 +1,185 @@
1
+ export const id = 5343;
2
+ export const ids = [5343];
3
+ export const modules = {
4
+
5
+ /***/ 5343:
6
+ /***/ ((__unused_webpack___webpack_module__, __webpack_exports__, __webpack_require__) => {
7
+
8
+ /* harmony export */ __webpack_require__.d(__webpack_exports__, {
9
+ /* harmony export */ proposeGovernanceEdit: () => (/* binding */ proposeGovernanceEdit)
10
+ /* harmony export */ });
11
+ /* harmony import */ var _recipient_registry_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(6703);
12
+ // governance-edit.js — M5 deliverable #5 (PRD line 1324's 5-part write
13
+ // contract: preview, validation, backup/version guard, confirmation,
14
+ // audit event). A CLI-only workflow for proposing a validated,
15
+ // reviewable edit to recipient-profiles.json — the one governance
16
+ // config file this codebase already has real, tested per-entry
17
+ // validation for (isValidRecipientConfigEntry, recipient-registry.js).
18
+ //
19
+ // Deliberately narrower than the PRD's own richer "interactive review/
20
+ // approve UI" vision — the HTTP-server-side interactive write surface
21
+ // (new routes, CSRF protection, a write-authorization mechanism beyond
22
+ // the existing read-only session token) is real, separately-scoped
23
+ // future work, not attempted here. See this sub-project's own scoping
24
+ // doc for the full reasoning: no PRD acceptance criterion gates this
25
+ // deliverable at all, and every M4/M5 deliverable this session has
26
+ // shipped has been CLI-first with zero UI/HTTP-write work.
27
+ //
28
+ // This module is pure — no file I/O, no fs access. The CLI layer
29
+ // (bin/agentic-security.js's cmdGovernancePropose) owns reading the
30
+ // current file, writing the backup, writing the new content, and
31
+ // calling auditCall.
32
+
33
+
34
+
35
+ // Validates the top-level `{recipients: {...}}` container shape — used
36
+ // for BOTH the current on-disk config and the --patch file. A patch is
37
+ // user input the command's whole job is to validate, so it gets no
38
+ // tolerant degradation (fixes I2/I3: previously a patch missing
39
+ // `recipients` entirely, or with `recipients` as an array, degraded
40
+ // silently to an empty no-op write). The CURRENT config gets the same
41
+ // check for a different reason: if its top-level shape is unrecognized
42
+ // (e.g. a typo'd `Recipients` key), the tool must refuse rather than
43
+ // silently treating "no recognizable data" as "start from empty" and
44
+ // overwriting whatever WAS there (this was B1's second live repro —
45
+ // the whole file being replaced under a misspelled key).
46
+ function _validateContainerShape(recipients, label) {
47
+ if (recipients === undefined) {
48
+ return [{ key: '(top-level)', message: `${label} is missing a "recipients" object` }];
49
+ }
50
+ if (recipients === null || typeof recipients !== 'object' || Array.isArray(recipients)) {
51
+ return [{ key: '(top-level)', message: `${label}'s "recipients" must be a plain object, not ${Array.isArray(recipients) ? 'an array' : recipients === null ? 'null' : typeof recipients}` }];
52
+ }
53
+ const errors = [];
54
+ for (const key of Object.keys(recipients)) {
55
+ if (key.length === 0) errors.push({ key, message: `${label} contains an empty-string recipient key, which is never valid` });
56
+ if (key === '__proto__') errors.push({ key, message: `${label} contains a "__proto__" recipient key, which is never valid` });
57
+ }
58
+ return errors;
59
+ }
60
+
61
+ function _validateEntries(patchRecipients) {
62
+ const errors = [];
63
+ for (const [key, entry] of Object.entries(patchRecipients)) {
64
+ if (entry === null) continue; // explicit deletion marker, not a config entry
65
+ if (!(0,_recipient_registry_js__WEBPACK_IMPORTED_MODULE_0__/* .isValidRecipientConfigEntry */ .cw)(entry)) {
66
+ errors.push({ key, message: `recipient "${key}" is not a valid recipient-profile-shaped config entry` });
67
+ }
68
+ }
69
+ return errors;
70
+ }
71
+
72
+ // RFC-7396-style merge patch at the recipient-key level: a non-null
73
+ // patch value REPLACES that key's entire entry; a null value DELETES
74
+ // it; a key the patch never mentions is left untouched. This is the
75
+ // fix for a real data-loss bug (found by this task's own review,
76
+ // live-reproduced): the prior design treated `patch.recipients` as
77
+ // the ENTIRE new recipients object, so omitting a key silently
78
+ // deleted it — a reasonable operator adding one vendor had no reason
79
+ // to expect every other vendor's compliance facts to vanish.
80
+ function _mergeRecipients(currentRecipients, patchRecipients) {
81
+ const merged = { ...currentRecipients };
82
+ for (const [key, value] of Object.entries(patchRecipients)) {
83
+ if (value === null) delete merged[key];
84
+ else merged[key] = value;
85
+ }
86
+ return merged;
87
+ }
88
+
89
+ // Canonical (key-order-independent) JSON serialization for change
90
+ // detection — mirrors ids.js's own _canon precedent. A hand-authored
91
+ // patch file has no reason to preserve the stored config's own key
92
+ // order, so a raw JSON.stringify comparison would spuriously flag a
93
+ // semantically-unchanged recipient as "changed" (found by the task
94
+ // review, reproduced live). Arrays keep their own order (order is
95
+ // semantically meaningful there — e.g. subprocessorChain); only OBJECT
96
+ // key order is normalized.
97
+ function _canonicalize(value) {
98
+ if (Array.isArray(value)) return value.map(_canonicalize);
99
+ if (value && typeof value === 'object') {
100
+ const sorted = {};
101
+ for (const key of Object.keys(value).sort()) sorted[key] = _canonicalize(value[key]);
102
+ return sorted;
103
+ }
104
+ return value;
105
+ }
106
+
107
+ function _diffRecipients(currentRecipients, patchRecipients) {
108
+ const added = [];
109
+ const removed = [];
110
+ const changed = [];
111
+ const currentKeys = new Set(Object.keys(currentRecipients));
112
+ for (const [key, value] of Object.entries(patchRecipients)) {
113
+ if (value === null) {
114
+ if (currentKeys.has(key)) removed.push(key);
115
+ // deleting a key that never existed is a no-op, not reported
116
+ continue;
117
+ }
118
+ if (!currentKeys.has(key)) { added.push(key); continue; }
119
+ if (JSON.stringify(_canonicalize(currentRecipients[key])) !== JSON.stringify(_canonicalize(value))) {
120
+ changed.push({ key, before: currentRecipients[key], after: value });
121
+ }
122
+ }
123
+ added.sort();
124
+ removed.sort();
125
+ changed.sort((a, b) => (a.key < b.key ? -1 : a.key > b.key ? 1 : 0));
126
+ return { added, removed, changed };
127
+ }
128
+
129
+ /**
130
+ * Propose a patch to a recipient-profiles.json-shaped config. Pure,
131
+ * never throws, never touches the filesystem. `currentConfig`/`patch`
132
+ * are both expected to be `{recipients: {...}}`-shaped — a malformed
133
+ * top-level shape on EITHER side (missing `recipients`, `recipients`
134
+ * not a plain object, or an empty-string/`__proto__` recipient key) is
135
+ * a validation failure (`valid: false`, `merged: null`), never a
136
+ * silent empty-object fallback. The stored config gets this treatment
137
+ * so an unrecognized shape (e.g. a typo'd top-level key) refuses to
138
+ * write rather than being silently treated as "nothing here yet" and
139
+ * overwritten; the patch gets it because it is user input the command's
140
+ * whole job is to validate.
141
+ *
142
+ * `patch.recipients` is an RFC-7396-style JSON MERGE PATCH against
143
+ * `currentConfig.recipients`, keyed at the recipient level — never a
144
+ * full replacement of the whole object: a key present with a non-null
145
+ * value REPLACES that key's entire entry (never deep-merged within
146
+ * itself); a key present with value `null` DELETES it (the only way to
147
+ * remove a recipient); a key the patch never mentions is left
148
+ * untouched in the merged result. Every OTHER top-level key on the
149
+ * current config (e.g. `$schema`, `version`) is preserved verbatim into
150
+ * `merged` — only `recipients` itself is merged.
151
+ *
152
+ * Returns `{valid, errors, diff, merged}` — `diff`/`merged` are always
153
+ * computed when both container shapes are valid, even when `valid` is
154
+ * false due to a per-entry error, so an operator can see what they
155
+ * attempted before fixing a validation error. `merged` is `null` only
156
+ * when there is no safe merge base to compute (a container-shape
157
+ * failure on either side). `merged`, when non-null, is the full
158
+ * config-shaped result the caller should write — never the raw patch.
159
+ */
160
+ function proposeGovernanceEdit(currentConfig, patch) {
161
+ const currentContainerErrors = _validateContainerShape(currentConfig?.recipients, 'the current config file');
162
+ const patchContainerErrors = _validateContainerShape(patch?.recipients, 'the --patch file');
163
+ if (currentContainerErrors.length || patchContainerErrors.length) {
164
+ return {
165
+ valid: false,
166
+ errors: [...currentContainerErrors, ...patchContainerErrors],
167
+ diff: { added: [], removed: [], changed: [] },
168
+ merged: null,
169
+ };
170
+ }
171
+ const currentRecipients = currentConfig.recipients;
172
+ const patchRecipients = patch.recipients;
173
+ const entryErrors = _validateEntries(patchRecipients);
174
+ const diff = _diffRecipients(currentRecipients, patchRecipients);
175
+ const mergedRecipients = _mergeRecipients(currentRecipients, patchRecipients);
176
+ // Preserve every OTHER top-level key from the current config verbatim
177
+ // (e.g. $schema, version) — only `recipients` itself is merged.
178
+ const merged = { ...(currentConfig && typeof currentConfig === 'object' ? currentConfig : {}), recipients: mergedRecipients };
179
+ return { valid: entryErrors.length === 0, errors: entryErrors, diff, merged };
180
+ }
181
+
182
+
183
+ /***/ })
184
+
185
+ };