@lamentis/naome 1.0.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 (49) hide show
  1. package/Cargo.lock +199 -0
  2. package/Cargo.toml +11 -0
  3. package/LICENSE +21 -0
  4. package/README.md +6 -0
  5. package/bin/naome-node.js +1424 -0
  6. package/bin/naome.js +129 -0
  7. package/crates/naome-cli/Cargo.toml +14 -0
  8. package/crates/naome-cli/src/main.rs +341 -0
  9. package/crates/naome-core/Cargo.toml +11 -0
  10. package/crates/naome-core/src/decision.rs +432 -0
  11. package/crates/naome-core/src/git.rs +70 -0
  12. package/crates/naome-core/src/harness_health.rs +557 -0
  13. package/crates/naome-core/src/install_plan.rs +82 -0
  14. package/crates/naome-core/src/lib.rs +17 -0
  15. package/crates/naome-core/src/models.rs +99 -0
  16. package/crates/naome-core/src/paths.rs +72 -0
  17. package/crates/naome-core/src/task_state.rs +1859 -0
  18. package/crates/naome-core/src/verification.rs +217 -0
  19. package/crates/naome-core/src/verification_contract.rs +406 -0
  20. package/crates/naome-core/tests/decision.rs +297 -0
  21. package/crates/naome-core/tests/harness_health.rs +232 -0
  22. package/crates/naome-core/tests/install_plan.rs +35 -0
  23. package/crates/naome-core/tests/task_state.rs +588 -0
  24. package/crates/naome-core/tests/verification.rs +165 -0
  25. package/crates/naome-core/tests/verification_contract.rs +181 -0
  26. package/native/darwin-arm64/naome +0 -0
  27. package/package.json +44 -0
  28. package/templates/naome-root/.naome/bin/check-harness-health.js +163 -0
  29. package/templates/naome-root/.naome/bin/check-task-state.js +180 -0
  30. package/templates/naome-root/.naome/bin/naome.js +306 -0
  31. package/templates/naome-root/.naome/init-state.json +13 -0
  32. package/templates/naome-root/.naome/manifest.json +45 -0
  33. package/templates/naome-root/.naome/package.json +3 -0
  34. package/templates/naome-root/.naome/task-contract.schema.json +174 -0
  35. package/templates/naome-root/.naome/task-state.json +8 -0
  36. package/templates/naome-root/.naome/upgrade-state.json +7 -0
  37. package/templates/naome-root/.naome/verification.json +45 -0
  38. package/templates/naome-root/.naomeignore +4 -0
  39. package/templates/naome-root/AGENTS.md +77 -0
  40. package/templates/naome-root/docs/naome/agent-workflow.md +82 -0
  41. package/templates/naome-root/docs/naome/architecture.md +37 -0
  42. package/templates/naome-root/docs/naome/decisions.md +18 -0
  43. package/templates/naome-root/docs/naome/execution.md +192 -0
  44. package/templates/naome-root/docs/naome/first-run.md +135 -0
  45. package/templates/naome-root/docs/naome/index.md +67 -0
  46. package/templates/naome-root/docs/naome/repo-profile.md +51 -0
  47. package/templates/naome-root/docs/naome/security.md +60 -0
  48. package/templates/naome-root/docs/naome/testing.md +51 -0
  49. package/templates/naome-root/docs/naome/upgrade.md +20 -0
@@ -0,0 +1,588 @@
1
+ use std::collections::HashMap;
2
+ use std::fs;
3
+ use std::path::{Path, PathBuf};
4
+ use std::process::Command;
5
+ use std::sync::atomic::{AtomicU64, Ordering};
6
+ use std::time::{SystemTime, UNIX_EPOCH};
7
+
8
+ use naome_core::{validate_task_state, HarnessHealthOptions, TaskStateMode, TaskStateOptions};
9
+ use serde_json::{json, Value};
10
+ use sha2::{Digest, Sha256};
11
+
12
+ static FIXTURE_COUNTER: AtomicU64 = AtomicU64::new(0);
13
+
14
+ const MACHINE_OWNED_PATHS: &[&str] = &[
15
+ "AGENTS.md",
16
+ ".naome/package.json",
17
+ ".naome/bin/naome.js",
18
+ ".naome/bin/check-task-state.js",
19
+ ".naome/bin/check-harness-health.js",
20
+ ".naome/task-contract.schema.json",
21
+ "docs/naome/index.md",
22
+ "docs/naome/first-run.md",
23
+ "docs/naome/agent-workflow.md",
24
+ "docs/naome/execution.md",
25
+ "docs/naome/upgrade.md",
26
+ ];
27
+
28
+ const PROJECT_OWNED_PATHS: &[&str] = &[
29
+ ".naomeignore",
30
+ ".naome/init-state.json",
31
+ ".naome/manifest.json",
32
+ ".naome/task-state.json",
33
+ ".naome/upgrade-state.json",
34
+ ".naome/verification.json",
35
+ "docs/naome/architecture.md",
36
+ "docs/naome/decisions.md",
37
+ "docs/naome/repo-profile.md",
38
+ "docs/naome/security.md",
39
+ "docs/naome/testing.md",
40
+ ];
41
+
42
+ #[test]
43
+ fn accepts_idle_task_state_template() {
44
+ let repo = TaskFixture::new(idle_task_state());
45
+
46
+ let report = validate_task_state(repo.path(), TaskStateOptions::default()).unwrap();
47
+
48
+ assert!(report.errors.is_empty(), "{:#?}", report.errors);
49
+ }
50
+
51
+ #[test]
52
+ fn admission_blocks_dirty_idle_worktree() {
53
+ let repo = TaskFixture::new(idle_task_state());
54
+ repo.init_git();
55
+ repo.write("README.md", "# Dirty baseline\n");
56
+
57
+ let report = validate_task_state(
58
+ repo.path(),
59
+ TaskStateOptions {
60
+ mode: TaskStateMode::Admission,
61
+ ..TaskStateOptions::default()
62
+ },
63
+ )
64
+ .unwrap();
65
+ let joined = report.errors.join("\n");
66
+
67
+ assert!(joined.contains("Task admission requires a clean git diff"));
68
+ assert!(joined.contains("README.md"));
69
+ assert!(joined.contains("review_task_diff"));
70
+ }
71
+
72
+ #[test]
73
+ fn admission_blocks_clean_repo_when_harness_is_unhealthy() {
74
+ let repo = TaskFixture::new(idle_task_state());
75
+ let integrity = repo.install_healthy_harness();
76
+ repo.init_git();
77
+ repo.write("docs/naome/execution.md", "# Tampered\n");
78
+ repo.git(["add", "docs/naome/execution.md"]);
79
+ repo.git(["commit", "-m", "tamper harness"]);
80
+
81
+ let report = validate_task_state(
82
+ repo.path(),
83
+ TaskStateOptions {
84
+ mode: TaskStateMode::Admission,
85
+ harness_health: Some(strict_health_options(integrity)),
86
+ },
87
+ )
88
+ .unwrap();
89
+ let joined = report.errors.join("\n");
90
+
91
+ assert!(joined.contains("Harness health failed"));
92
+ assert!(joined.contains("docs/naome/execution.md"));
93
+ assert!(joined.contains("repair_harness"));
94
+ assert!(joined.contains("review_harness_health"));
95
+ }
96
+
97
+ #[test]
98
+ fn accepts_complete_task_with_scoped_diff_and_notice() {
99
+ let repo = TaskFixture::new(complete_task_state(json!({
100
+ "allowedPaths": ["README.md"],
101
+ "proofResults": [successful_proof(json!({ "evidence": ["README.md"] }))]
102
+ })));
103
+ repo.init_git();
104
+ repo.write("README.md", "# Test\n");
105
+
106
+ let report = validate_task_state(repo.path(), TaskStateOptions::default()).unwrap();
107
+
108
+ assert!(report.errors.is_empty(), "{:#?}", report.errors);
109
+ assert!(report
110
+ .notices
111
+ .join("\n")
112
+ .contains("Next task admission is blocked"));
113
+ }
114
+
115
+ #[test]
116
+ fn commit_gate_blocks_normal_task_commit_when_harness_is_unhealthy() {
117
+ let repo = TaskFixture::new(complete_task_state(json!({
118
+ "allowedPaths": ["README.md"],
119
+ "proofResults": [successful_proof(json!({ "evidence": ["README.md"] }))]
120
+ })));
121
+ let integrity = repo.install_healthy_harness();
122
+ repo.init_git();
123
+ repo.write("docs/naome/execution.md", "# Tampered\n");
124
+ repo.git(["add", "docs/naome/execution.md"]);
125
+ repo.git(["commit", "-m", "tamper harness"]);
126
+ repo.write("README.md", "# Task result\n");
127
+
128
+ let report = validate_task_state(
129
+ repo.path(),
130
+ TaskStateOptions {
131
+ mode: TaskStateMode::CommitGate,
132
+ harness_health: Some(strict_health_options(integrity)),
133
+ },
134
+ )
135
+ .unwrap();
136
+ let joined = report.errors.join("\n");
137
+
138
+ assert!(joined.contains("Harness health failed"));
139
+ assert!(joined.contains("docs/naome/execution.md"));
140
+ assert!(joined.contains("repair_harness"));
141
+ }
142
+
143
+ #[test]
144
+ fn commit_gate_allows_install_baseline_with_gitignore_and_project_owned_paths() {
145
+ let repo = TaskFixture::new(idle_task_state());
146
+ repo.write_json(
147
+ ".naome/init-state.json",
148
+ json!({
149
+ "initialized": false,
150
+ "intakeStatus": "not_started",
151
+ "initializedAt": null,
152
+ "initializedBy": null,
153
+ "blockedReason": null,
154
+ "requiredDocs": []
155
+ }),
156
+ );
157
+ repo.init_git();
158
+ repo.write(
159
+ ".gitignore",
160
+ "# NAOME local machine-owned harness files.\n.naome/archive/\n",
161
+ );
162
+ repo.write("AGENTS.md", "# Agent Instructions\n");
163
+ repo.write("docs/naome/architecture.md", "# Architecture\n");
164
+ repo.write("docs/naome/testing.md", "# Testing\n");
165
+ repo.write_json(
166
+ ".naome/verification.json",
167
+ json!({
168
+ "schema": "naome.verification.v1",
169
+ "version": 1,
170
+ "status": "ready",
171
+ "lastUpdated": "2026-05-05",
172
+ "checks": [],
173
+ "changeTypes": [],
174
+ "releaseGates": []
175
+ }),
176
+ );
177
+
178
+ let report = validate_task_state(
179
+ repo.path(),
180
+ TaskStateOptions {
181
+ mode: TaskStateMode::CommitGate,
182
+ ..TaskStateOptions::default()
183
+ },
184
+ )
185
+ .unwrap();
186
+
187
+ assert!(report.errors.is_empty(), "{:#?}", report.errors);
188
+ }
189
+
190
+ #[test]
191
+ fn progress_accepts_implementing_task_with_scoped_diff_without_proof() {
192
+ let repo = TaskFixture::new(json!({
193
+ "schema": "naome.task-state.v1",
194
+ "version": 1,
195
+ "status": "implementing",
196
+ "activeTask": active_task(json!({
197
+ "allowedPaths": ["README.md"],
198
+ "proofResults": []
199
+ })),
200
+ "blocker": null,
201
+ "updatedAt": "2026-05-04T12:00:00.000Z"
202
+ }));
203
+ repo.init_git();
204
+ repo.write("README.md", "# In progress\n");
205
+
206
+ let report = validate_task_state(
207
+ repo.path(),
208
+ TaskStateOptions {
209
+ mode: TaskStateMode::Progress,
210
+ ..TaskStateOptions::default()
211
+ },
212
+ )
213
+ .unwrap();
214
+
215
+ assert!(report.errors.is_empty(), "{:#?}", report.errors);
216
+ }
217
+
218
+ #[test]
219
+ fn progress_rejects_in_flight_work_outside_scope() {
220
+ let repo = TaskFixture::new(json!({
221
+ "schema": "naome.task-state.v1",
222
+ "version": 1,
223
+ "status": "implementing",
224
+ "activeTask": active_task(json!({
225
+ "allowedPaths": ["README.md"],
226
+ "proofResults": []
227
+ })),
228
+ "blocker": null,
229
+ "updatedAt": "2026-05-04T12:00:00.000Z"
230
+ }));
231
+ repo.init_git();
232
+ repo.write("src/session.js", "export const session = null;\n");
233
+
234
+ let report = validate_task_state(
235
+ repo.path(),
236
+ TaskStateOptions {
237
+ mode: TaskStateMode::Progress,
238
+ ..TaskStateOptions::default()
239
+ },
240
+ )
241
+ .unwrap();
242
+ let joined = report.errors.join("\n");
243
+
244
+ assert!(joined.contains("Changed files outside allowedPaths"));
245
+ assert!(joined.contains("src/session.js"));
246
+ }
247
+
248
+ #[test]
249
+ fn rejects_complete_task_when_evidence_omits_changed_allowed_path() {
250
+ let repo = TaskFixture::new(complete_task_state(json!({
251
+ "allowedPaths": ["packages/next/README.md"],
252
+ "proofResults": [successful_proof(json!({ "evidence": ["README.md"] }))]
253
+ })));
254
+ repo.write("README.md", "# Root README\n");
255
+ repo.write("packages/next/README.md", "# Package README\n");
256
+ repo.init_git();
257
+ repo.write("packages/next/README.md", "# Package README\n\nSmoke.\n");
258
+
259
+ let report = validate_task_state(repo.path(), TaskStateOptions::default()).unwrap();
260
+ let joined = report.errors.join("\n");
261
+
262
+ assert!(joined.contains("evidence missing changed allowed paths"));
263
+ assert!(joined.contains("packages/next/README.md"));
264
+ }
265
+
266
+ #[test]
267
+ fn accepts_committed_deleted_file_evidence_after_task_baseline_is_clean() {
268
+ let repo = TaskFixture::new(idle_task_state());
269
+ repo.write(
270
+ "docs/naome/deleted-file-evidence.tmp",
271
+ "temporary fixture\n",
272
+ );
273
+ repo.init_git();
274
+ let admission_head = repo.git(["rev-parse", "HEAD"]).trim().to_string();
275
+ fs::remove_file(repo.path().join("docs/naome/deleted-file-evidence.tmp")).unwrap();
276
+ repo.write_json(
277
+ ".naome/task-state.json",
278
+ complete_task_state(json!({
279
+ "admission": successful_admission(json!({ "gitHead": admission_head })),
280
+ "allowedPaths": ["docs/naome/deleted-file-evidence.tmp"],
281
+ "proofResults": [
282
+ successful_proof(json!({
283
+ "evidence": [
284
+ {
285
+ "path": "docs/naome/deleted-file-evidence.tmp",
286
+ "status": "deleted"
287
+ }
288
+ ]
289
+ }))
290
+ ]
291
+ })),
292
+ );
293
+ repo.git(["add", "-A"]);
294
+ repo.git(["commit", "-m", "baseline deleted evidence"]);
295
+
296
+ let report = validate_task_state(repo.path(), TaskStateOptions::default()).unwrap();
297
+
298
+ assert!(report.errors.is_empty(), "{:#?}", report.errors);
299
+ }
300
+
301
+ struct TaskFixture {
302
+ root: PathBuf,
303
+ }
304
+
305
+ impl TaskFixture {
306
+ fn new(task_state: Value) -> Self {
307
+ let nonce = SystemTime::now()
308
+ .duration_since(UNIX_EPOCH)
309
+ .unwrap()
310
+ .as_nanos();
311
+ let counter = FIXTURE_COUNTER.fetch_add(1, Ordering::Relaxed);
312
+ let root = std::env::temp_dir().join(format!(
313
+ "naome-task-state-rust-{}-{nonce}-{counter}",
314
+ std::process::id()
315
+ ));
316
+ fs::create_dir_all(root.join(".naome")).unwrap();
317
+ write_json(&root, ".naome/task-state.json", task_state);
318
+ write_json(
319
+ &root,
320
+ ".naome/init-state.json",
321
+ json!({
322
+ "initialized": true,
323
+ "intakeStatus": "complete",
324
+ "initializedAt": "2026-05-04T12:00:00.000Z",
325
+ "initializedBy": "naome-test",
326
+ "blockedReason": null,
327
+ "requiredDocs": []
328
+ }),
329
+ );
330
+ write_json(
331
+ &root,
332
+ ".naome/upgrade-state.json",
333
+ json!({
334
+ "status": "complete",
335
+ "fromVersion": null,
336
+ "toVersion": "0.6.1",
337
+ "pending": [],
338
+ "completed": []
339
+ }),
340
+ );
341
+ write_json(
342
+ &root,
343
+ ".naome/verification.json",
344
+ json!({
345
+ "schema": "naome.verification.v1",
346
+ "version": 1,
347
+ "status": "ready",
348
+ "lastUpdated": "2026-05-04",
349
+ "checks": [
350
+ {
351
+ "id": "diff-check",
352
+ "command": "git diff --check",
353
+ "cwd": ".",
354
+ "purpose": "Detect whitespace and patch formatting issues.",
355
+ "cost": "fast",
356
+ "source": "git",
357
+ "evidence": ["README.md"],
358
+ "lastVerified": "2026-05-04"
359
+ }
360
+ ],
361
+ "changeTypes": [],
362
+ "releaseGates": []
363
+ }),
364
+ );
365
+ Self { root }
366
+ }
367
+
368
+ fn path(&self) -> &Path {
369
+ &self.root
370
+ }
371
+
372
+ fn write(&self, relative_path: &str, content: &str) {
373
+ let path = self.root.join(relative_path);
374
+ fs::create_dir_all(path.parent().unwrap()).unwrap();
375
+ fs::write(path, content).unwrap();
376
+ }
377
+
378
+ fn write_json(&self, relative_path: &str, value: Value) {
379
+ write_json(&self.root, relative_path, value);
380
+ }
381
+
382
+ fn install_healthy_harness(&self) -> HashMap<String, String> {
383
+ let mut integrity = HashMap::new();
384
+
385
+ for relative_path in MACHINE_OWNED_PATHS {
386
+ let content = machine_content(relative_path);
387
+ self.write(relative_path, &content);
388
+ integrity.insert(
389
+ (*relative_path).to_string(),
390
+ format!("sha256:{}", sha256(&content)),
391
+ );
392
+ }
393
+
394
+ for relative_path in PROJECT_OWNED_PATHS {
395
+ let path = self.root.join(relative_path);
396
+ if *relative_path == ".naome/manifest.json" || path.exists() {
397
+ continue;
398
+ }
399
+ let content = if *relative_path == ".naomeignore" {
400
+ ".naome/archive/\n".to_string()
401
+ } else {
402
+ format!("# {relative_path}\n")
403
+ };
404
+ self.write(relative_path, &content);
405
+ }
406
+
407
+ fs::create_dir_all(self.root.join(".naome/archive")).unwrap();
408
+ self.write_json(
409
+ ".naome/manifest.json",
410
+ json!({
411
+ "name": "naome",
412
+ "harnessVersion": "0.6.1",
413
+ "profile": "standard",
414
+ "installedAt": "2026-05-05T00:00:00.000Z",
415
+ "machineOwned": MACHINE_OWNED_PATHS,
416
+ "projectOwned": PROJECT_OWNED_PATHS,
417
+ "integrity": integrity
418
+ }),
419
+ );
420
+
421
+ integrity
422
+ }
423
+
424
+ fn init_git(&self) {
425
+ self.git(["init"]);
426
+ self.git(["config", "user.email", "naome@example.com"]);
427
+ self.git(["config", "user.name", "NAOME Test"]);
428
+ self.git(["add", "."]);
429
+ self.git(["commit", "-m", "baseline"]);
430
+ self.refresh_admission_head();
431
+ }
432
+
433
+ fn refresh_admission_head(&self) {
434
+ let path = self.root.join(".naome/task-state.json");
435
+ let mut task_state: Value =
436
+ serde_json::from_str(&fs::read_to_string(&path).unwrap()).unwrap();
437
+ let Some(active_task) = task_state.get_mut("activeTask") else {
438
+ return;
439
+ };
440
+ let Some(admission) = active_task.get_mut("admission") else {
441
+ return;
442
+ };
443
+
444
+ admission["gitHead"] = Value::String(self.git(["rev-parse", "HEAD"]).trim().to_string());
445
+ fs::write(
446
+ path,
447
+ format!("{}\n", serde_json::to_string_pretty(&task_state).unwrap()),
448
+ )
449
+ .unwrap();
450
+ }
451
+
452
+ fn git<const N: usize>(&self, args: [&str; N]) -> String {
453
+ let output = Command::new("git")
454
+ .args(args)
455
+ .current_dir(&self.root)
456
+ .output()
457
+ .unwrap();
458
+ assert!(
459
+ output.status.success(),
460
+ "{}\n{}",
461
+ String::from_utf8_lossy(&output.stdout),
462
+ String::from_utf8_lossy(&output.stderr)
463
+ );
464
+ String::from_utf8_lossy(&output.stdout).to_string()
465
+ }
466
+ }
467
+
468
+ fn strict_health_options(expected_integrity: HashMap<String, String>) -> HarnessHealthOptions {
469
+ HarnessHealthOptions {
470
+ expected_integrity,
471
+ allow_missing_integrity: false,
472
+ allow_missing_archive: false,
473
+ }
474
+ }
475
+
476
+ fn machine_content(relative_path: &str) -> String {
477
+ match relative_path {
478
+ "AGENTS.md" => [
479
+ "# Agent Instructions",
480
+ "Run node .naome/bin/check-harness-health.js before feature work.",
481
+ "Run node .naome/bin/check-task-state.js --admission before task work.",
482
+ "",
483
+ ]
484
+ .join("\n"),
485
+ ".naome/package.json" => format!("{}\n", json!({ "type": "commonjs" })),
486
+ _ => format!("# {relative_path}\n"),
487
+ }
488
+ }
489
+
490
+ fn sha256(content: &str) -> String {
491
+ let mut hasher = Sha256::new();
492
+ hasher.update(content.as_bytes());
493
+ format!("{:x}", hasher.finalize())
494
+ }
495
+
496
+ fn write_json(root: &Path, relative_path: &str, value: Value) {
497
+ let path = root.join(relative_path);
498
+ fs::create_dir_all(path.parent().unwrap()).unwrap();
499
+ fs::write(
500
+ path,
501
+ format!("{}\n", serde_json::to_string_pretty(&value).unwrap()),
502
+ )
503
+ .unwrap();
504
+ }
505
+
506
+ fn idle_task_state() -> Value {
507
+ json!({
508
+ "schema": "naome.task-state.v1",
509
+ "version": 1,
510
+ "status": "idle",
511
+ "activeTask": null,
512
+ "blocker": null,
513
+ "updatedAt": null
514
+ })
515
+ }
516
+
517
+ fn complete_task_state(overrides: Value) -> Value {
518
+ json!({
519
+ "schema": "naome.task-state.v1",
520
+ "version": 1,
521
+ "status": "complete",
522
+ "activeTask": active_task(overrides),
523
+ "blocker": null,
524
+ "updatedAt": "2026-05-04T12:00:00.000Z"
525
+ })
526
+ }
527
+
528
+ fn active_task(overrides: Value) -> Value {
529
+ let mut task = json!({
530
+ "id": "readme-test",
531
+ "request": "Update the README.",
532
+ "userPrompt": {
533
+ "receivedAt": "2026-05-04T12:00:00.000Z",
534
+ "text": "Update the README."
535
+ },
536
+ "admission": successful_admission(json!({})),
537
+ "allowedPaths": ["README.md"],
538
+ "declaredChangeTypes": ["docs"],
539
+ "requiredCheckIds": ["diff-check"],
540
+ "proofResults": [],
541
+ "humanReview": {
542
+ "required": false,
543
+ "approved": false,
544
+ "reason": null
545
+ }
546
+ });
547
+ merge_object(&mut task, overrides);
548
+ task
549
+ }
550
+
551
+ fn successful_admission(overrides: Value) -> Value {
552
+ let mut admission = json!({
553
+ "command": "node .naome/bin/check-task-state.js --admission",
554
+ "cwd": ".",
555
+ "exitCode": 0,
556
+ "checkedAt": "2026-05-04T12:00:00.000Z",
557
+ "gitHead": "pending-test-head",
558
+ "changedPaths": []
559
+ });
560
+ merge_object(&mut admission, overrides);
561
+ admission
562
+ }
563
+
564
+ fn successful_proof(overrides: Value) -> Value {
565
+ let mut proof = json!({
566
+ "checkId": "diff-check",
567
+ "command": "git diff --check",
568
+ "cwd": ".",
569
+ "exitCode": 0,
570
+ "checkedAt": "2026-05-04T12:00:00.000Z",
571
+ "evidence": ["README.md"]
572
+ });
573
+ merge_object(&mut proof, overrides);
574
+ proof
575
+ }
576
+
577
+ fn merge_object(target: &mut Value, overrides: Value) {
578
+ let Some(target_object) = target.as_object_mut() else {
579
+ return;
580
+ };
581
+ let Some(overrides_object) = overrides.as_object() else {
582
+ return;
583
+ };
584
+
585
+ for (key, value) in overrides_object {
586
+ target_object.insert(key.clone(), value.clone());
587
+ }
588
+ }