@lamentis/naome 1.3.2 → 1.3.4

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 (33) hide show
  1. package/Cargo.lock +2 -2
  2. package/crates/naome-cli/Cargo.toml +1 -1
  3. package/crates/naome-cli/src/main.rs +1 -0
  4. package/crates/naome-cli/src/workflow_commands.rs +30 -4
  5. package/crates/naome-core/Cargo.toml +1 -1
  6. package/crates/naome-core/src/information_architecture.rs +179 -0
  7. package/crates/naome-core/src/lib.rs +6 -0
  8. package/crates/naome-core/src/route/quality_gate_config.rs +18 -0
  9. package/crates/naome-core/src/task_ledger/render.rs +50 -16
  10. package/crates/naome-core/src/task_state/diff.rs +2 -2
  11. package/crates/naome-core/src/task_state/git_io.rs +5 -2
  12. package/crates/naome-core/src/task_state/types.rs +4 -0
  13. package/crates/naome-core/src/verification_contract.rs +3 -1
  14. package/crates/naome-core/src/verification_contract_policy.rs +52 -0
  15. package/crates/naome-core/tests/information_architecture.rs +58 -0
  16. package/crates/naome-core/tests/repo_support/mod.rs +2 -1
  17. package/crates/naome-core/tests/repo_support/verification.rs +28 -42
  18. package/crates/naome-core/tests/repo_support/verification_values.rs +13 -0
  19. package/crates/naome-core/tests/route_user_diff.rs +52 -4
  20. package/crates/naome-core/tests/task_ledger.rs +62 -0
  21. package/crates/naome-core/tests/task_state.rs +6 -12
  22. package/crates/naome-core/tests/verification_contract.rs +37 -1
  23. package/native/darwin-arm64/naome +0 -0
  24. package/native/linux-x64/naome +0 -0
  25. package/package.json +1 -1
  26. package/templates/naome-root/.naome/bin/check-harness-health.js +3 -3
  27. package/templates/naome-root/.naome/bin/check-task-state.js +3 -3
  28. package/templates/naome-root/.naome/manifest.json +3 -3
  29. package/templates/naome-root/docs/naome/agent-workflow.md +7 -4
  30. package/templates/naome-root/docs/naome/architecture.md +11 -2
  31. package/templates/naome-root/docs/naome/first-run.md +1 -1
  32. package/templates/naome-root/docs/naome/task-ledger.md +13 -1
  33. package/templates/naome-root/docs/naome/testing.md +1 -1
package/Cargo.lock CHANGED
@@ -76,7 +76,7 @@ checksum = "f8ca58f447f06ed17d5fc4043ce1b10dd205e060fb3ce5b979b8ed8e59ff3f79"
76
76
 
77
77
  [[package]]
78
78
  name = "naome-cli"
79
- version = "1.3.2"
79
+ version = "1.3.4"
80
80
  dependencies = [
81
81
  "naome-core",
82
82
  "serde_json",
@@ -84,7 +84,7 @@ dependencies = [
84
84
 
85
85
  [[package]]
86
86
  name = "naome-core"
87
- version = "1.3.2"
87
+ version = "1.3.4"
88
88
  dependencies = [
89
89
  "serde",
90
90
  "serde_json",
@@ -1,6 +1,6 @@
1
1
  [package]
2
2
  name = "naome-cli"
3
- version = "1.3.2"
3
+ version = "1.3.4"
4
4
  edition.workspace = true
5
5
  license.workspace = true
6
6
  repository.workspace = true
@@ -69,6 +69,7 @@ const HELP: &str = r#"Usage:
69
69
  naome workflow phases [--json]
70
70
  naome workflow processes [--json]
71
71
  naome workflow mutations --path <path> [path...] [--json]
72
+ naome workflow information [--path <path>] [--json]
72
73
  naome check-harness-health [--root <path>] [--allow-missing-archive]
73
74
  naome check-task-state [--root <path>] [--admission|--progress|--commit-gate|--push-gate] [--allow-missing-archive]
74
75
  naome validate-verification [--root <path>]"#;
@@ -1,9 +1,10 @@
1
1
  use std::path::Path;
2
2
 
3
3
  use naome_core::{
4
- agent_run_plan, classify_mutations, context_delta_report, decision_gate, doctor_report,
5
- edit_session_watchdog, proof_plan_for_task, refresh_integrity, repository_capability_graph,
6
- safe_rg_args, summarize_command_output, tracked_process_report, validate_search_command,
4
+ agent_run_plan, classify_information_path, classify_mutations, context_delta_report,
5
+ decision_gate, doctor_report, edit_session_watchdog, information_architecture_report,
6
+ proof_plan_for_task, refresh_integrity, repository_capability_graph, safe_rg_args,
7
+ summarize_command_output, tracked_process_report, validate_search_command,
7
8
  verification_phase_plan,
8
9
  };
9
10
 
@@ -32,7 +33,7 @@ pub fn run_workflow_command(
32
33
  args: &[String],
33
34
  ) -> Result<(), Box<dyn std::error::Error>> {
34
35
  let Some(subcommand) = args.get(1).map(String::as_str) else {
35
- return Err("naome workflow requires agent-plan, context-delta, proof-plan, capabilities, edit-watchdog, decision-gate, digest, search-profile, check-search, phases, processes, or mutations.".into());
36
+ return Err("naome workflow requires agent-plan, context-delta, proof-plan, capabilities, edit-watchdog, decision-gate, digest, search-profile, check-search, phases, processes, mutations, or information.".into());
36
37
  };
37
38
  let json = args.iter().any(|arg| arg == "--json");
38
39
 
@@ -103,6 +104,7 @@ pub fn run_workflow_command(
103
104
  }
104
105
  }
105
106
  "mutations" => run_mutations(root, args, json)?,
107
+ "information" => run_information(args, json)?,
106
108
  _ => return Err(format!("unknown naome workflow command: {subcommand}").into()),
107
109
  }
108
110
  Ok(())
@@ -179,6 +181,30 @@ fn run_mutations(
179
181
  Ok(())
180
182
  }
181
183
 
184
+ fn run_information(args: &[String], json: bool) -> Result<(), Box<dyn std::error::Error>> {
185
+ if let Some(path) = option_value(args, "--path") {
186
+ let classification = classify_information_path(&path);
187
+ if json {
188
+ println!("{}", serde_json::to_string_pretty(&classification)?);
189
+ } else {
190
+ println!(
191
+ "{}: {} ({}, {})",
192
+ classification.path,
193
+ classification.class,
194
+ classification.commit_policy,
195
+ classification.restore_policy
196
+ );
197
+ }
198
+ return Ok(());
199
+ }
200
+
201
+ print_json_or_label(
202
+ serde_json::to_value(information_architecture_report())?,
203
+ json,
204
+ "NAOME information architecture ready.",
205
+ )
206
+ }
207
+
182
208
  fn run_context_delta(
183
209
  root: &Path,
184
210
  args: &[String],
@@ -1,6 +1,6 @@
1
1
  [package]
2
2
  name = "naome-core"
3
- version = "1.3.2"
3
+ version = "1.3.4"
4
4
  edition.workspace = true
5
5
  license.workspace = true
6
6
  repository.workspace = true
@@ -0,0 +1,179 @@
1
+ use serde::Serialize;
2
+
3
+ #[derive(Debug, Clone, PartialEq, Eq, Serialize)]
4
+ pub struct InformationArchitectureReport {
5
+ pub schema: &'static str,
6
+ pub version: u8,
7
+ pub classes: Vec<InformationClass>,
8
+ pub rules: Vec<InformationPathRule>,
9
+ }
10
+ #[derive(Debug, Clone, PartialEq, Eq, Serialize)]
11
+ pub struct InformationClass {
12
+ pub id: &'static str,
13
+ pub description: &'static str,
14
+ pub commit_policy: &'static str,
15
+ pub restore_policy: &'static str,
16
+ }
17
+ #[derive(Debug, Clone, PartialEq, Eq, Serialize)]
18
+ pub struct InformationPathRule {
19
+ pub path_pattern: &'static str,
20
+ pub class: &'static str,
21
+ pub commit_policy: &'static str,
22
+ pub restore_policy: &'static str,
23
+ }
24
+ #[derive(Debug, Clone, PartialEq, Eq, Serialize)]
25
+ pub struct InformationPathClassification {
26
+ pub path: String,
27
+ pub class: &'static str,
28
+ pub commit_policy: &'static str,
29
+ pub restore_policy: &'static str,
30
+ }
31
+
32
+ const CLASSES: &[(&str, &str, &str, &str)] = &[
33
+ (
34
+ "durable_project_state",
35
+ "Committed configuration, policy, and templates required to reconstruct the harness.",
36
+ "commit",
37
+ "restore_from_repository_or_package",
38
+ ),
39
+ (
40
+ "durable_task_ledger",
41
+ "Committed task intent, scope, and lifecycle records.",
42
+ "commit",
43
+ "restore_from_repository",
44
+ ),
45
+ (
46
+ "generated_projection",
47
+ "Compatibility views generated from durable state.",
48
+ "commit_when_required_by_compatibility",
49
+ "regenerate_from_durable_state",
50
+ ),
51
+ (
52
+ "local_runtime_state",
53
+ "Machine-local prompts, caches, temporary files, and runtime state.",
54
+ "never_commit",
55
+ "recreate_on_demand",
56
+ ),
57
+ (
58
+ "run_evidence",
59
+ "Verification outputs from a specific run.",
60
+ "prefer_compact_release_evidence",
61
+ "restore_from_ci_or_task_ledger_when_audited",
62
+ ),
63
+ (
64
+ "product_source",
65
+ "Repository source, package metadata, tests, scripts, and documentation.",
66
+ "commit_when_in_task_scope",
67
+ "restore_from_repository",
68
+ ),
69
+ ];
70
+ struct RuleSeed {
71
+ path_pattern: &'static str,
72
+ class: &'static str,
73
+ }
74
+
75
+ const RULES: &[RuleSeed] = &[
76
+ RuleSeed { path_pattern: ".naome/tmp/**", class: "local_runtime_state" },
77
+ RuleSeed { path_pattern: ".naome/task-state.json", class: "generated_projection" },
78
+ RuleSeed { path_pattern: ".naome/tasks/active.json", class: "durable_task_ledger" },
79
+ RuleSeed { path_pattern: ".naome/tasks/*/task.json", class: "durable_task_ledger" },
80
+ RuleSeed { path_pattern: ".naome/tasks/*/events.jsonl", class: "durable_task_ledger" },
81
+ RuleSeed { path_pattern: ".naome/tasks/*/proofs/*.json", class: "run_evidence" },
82
+ RuleSeed { path_pattern: ".naome/manifest.json", class: "durable_project_state" },
83
+ RuleSeed { path_pattern: ".naome/verification.json", class: "durable_project_state" },
84
+ RuleSeed { path_pattern: ".naome/repository-quality*.json", class: "durable_project_state" },
85
+ RuleSeed { path_pattern: "docs/naome/**", class: "durable_project_state" },
86
+ RuleSeed { path_pattern: "packages/naome/templates/naome-root/**", class: "durable_project_state" },
87
+ ];
88
+ pub fn information_architecture_report() -> InformationArchitectureReport {
89
+ InformationArchitectureReport {
90
+ schema: "naome.information-architecture.v1",
91
+ version: 1,
92
+ classes: CLASSES
93
+ .iter()
94
+ .map(|entry| class_from_tuple(*entry))
95
+ .collect(),
96
+ rules: RULES
97
+ .iter()
98
+ .map(|rule| rule_from_class(rule.path_pattern, rule.class))
99
+ .collect(),
100
+ }
101
+ }
102
+
103
+ pub fn classify_information_path(path: &str) -> InformationPathClassification {
104
+ let path = normalize_path(path);
105
+ let class_id = RULES
106
+ .iter()
107
+ .find(|rule| matches_information_rule(&path, rule.path_pattern))
108
+ .map(|rule| rule.class)
109
+ .unwrap_or("product_source");
110
+ let class = class_by_id(class_id);
111
+ InformationPathClassification {
112
+ path,
113
+ class: class.id,
114
+ commit_policy: class.commit_policy,
115
+ restore_policy: class.restore_policy,
116
+ }
117
+ }
118
+
119
+ fn rule_from_class(path_pattern: &'static str, class_id: &'static str) -> InformationPathRule {
120
+ let class = class_by_id(class_id);
121
+ InformationPathRule {
122
+ path_pattern,
123
+ class: class.id,
124
+ commit_policy: class.commit_policy,
125
+ restore_policy: class.restore_policy,
126
+ }
127
+ }
128
+
129
+ fn class_by_id(id: &str) -> InformationClass {
130
+ CLASSES
131
+ .iter()
132
+ .find(|(class_id, _, _, _)| *class_id == id)
133
+ .map(|entry| class_from_tuple(*entry))
134
+ .expect("information class rule must reference a known class")
135
+ }
136
+
137
+ fn class_from_tuple(
138
+ entry: (&'static str, &'static str, &'static str, &'static str),
139
+ ) -> InformationClass {
140
+ InformationClass {
141
+ id: entry.0,
142
+ description: entry.1,
143
+ commit_policy: entry.2,
144
+ restore_policy: entry.3,
145
+ }
146
+ }
147
+
148
+ fn normalize_path(path: &str) -> String {
149
+ path.replace('\\', "/")
150
+ .trim_start_matches("./")
151
+ .trim_end_matches('/')
152
+ .to_string()
153
+ }
154
+
155
+ fn matches_information_rule(path: &str, pattern: &str) -> bool {
156
+ if let Some(prefix) = pattern.strip_suffix("/**") {
157
+ return path == prefix || path.starts_with(&format!("{prefix}/"));
158
+ }
159
+ if let Some(prefix) = pattern
160
+ .strip_suffix("*.json")
161
+ .filter(|prefix| !prefix.contains('*'))
162
+ {
163
+ return path.starts_with(prefix) && path.ends_with(".json");
164
+ }
165
+
166
+ let path_parts: Vec<&str> = path.split('/').collect();
167
+ let pattern_parts: Vec<&str> = pattern.split('/').collect();
168
+ path_parts.len() == pattern_parts.len()
169
+ && path_parts
170
+ .iter()
171
+ .zip(pattern_parts.iter())
172
+ .all(|(path_part, pattern_part)| {
173
+ *pattern_part == "*"
174
+ || *path_part == *pattern_part
175
+ || pattern_part
176
+ .strip_prefix('*')
177
+ .is_some_and(|suffix| path_part.ends_with(suffix))
178
+ })
179
+ }
@@ -2,6 +2,7 @@ mod context;
2
2
  mod decision;
3
3
  mod git;
4
4
  mod harness_health;
5
+ mod information_architecture;
5
6
  mod install_plan;
6
7
  mod intent;
7
8
  mod journal;
@@ -14,6 +15,7 @@ mod task_ledger;
14
15
  mod task_state;
15
16
  mod verification;
16
17
  mod verification_contract;
18
+ mod verification_contract_policy;
17
19
  mod workflow;
18
20
 
19
21
  pub use context::{
@@ -22,6 +24,10 @@ pub use context::{
22
24
  };
23
25
  pub use decision::{evaluate_decision, format_decision, EvaluationOptions};
24
26
  pub use harness_health::{validate_harness_health, HarnessHealthOptions};
27
+ pub use information_architecture::{
28
+ classify_information_path, information_architecture_report, InformationArchitectureReport,
29
+ InformationClass, InformationPathClassification, InformationPathRule,
30
+ };
25
31
  pub use install_plan::{install_plan, InstallPlan};
26
32
  pub use install_plan::{MACHINE_OWNED_PATHS, PROJECT_OWNED_PATHS};
27
33
  pub use intent::{evaluate_intent, format_intent, IntentDecision, PromptEvidence};
@@ -98,6 +98,7 @@ pub(super) fn user_diff_check_ids(
98
98
  "No quality checks are configured for these changed paths.",
99
99
  ));
100
100
  }
101
+ enforce_repository_semantic_companion(&mut ids, checks)?;
101
102
 
102
103
  Ok(ids)
103
104
  }
@@ -124,3 +125,20 @@ fn push_unique_string(values: &mut Vec<String>, value: &str) {
124
125
  values.push(value.to_string());
125
126
  }
126
127
  }
128
+
129
+ fn enforce_repository_semantic_companion(
130
+ ids: &mut Vec<String>,
131
+ checks: &HashMap<&str, QualityCheck>,
132
+ ) -> Result<(), NaomeError> {
133
+ if !ids.iter().any(|item| item == "repository-quality-check") {
134
+ return Ok(());
135
+ }
136
+ if checks.contains_key("repository-semantic-check") {
137
+ push_unique_string(ids, "repository-semantic-check");
138
+ return Ok(());
139
+ }
140
+
141
+ Err(NaomeError::new(
142
+ "repository-quality-check requires repository-semantic-check so semantic findings are mandatory quality-gate failures.",
143
+ ))
144
+ }
@@ -1,6 +1,6 @@
1
1
  use std::path::Path;
2
2
 
3
- use serde_json::{json, Value};
3
+ use serde_json::{json, Map, Value};
4
4
 
5
5
  use crate::models::NaomeError;
6
6
 
@@ -28,28 +28,62 @@ pub(super) fn render_from_ledger(root: &Path) -> Result<Option<TaskLedgerProject
28
28
  }
29
29
 
30
30
  fn render_active_task(spec: Value, proof_results: Vec<Value>) -> Value {
31
- json!({
32
- "id": spec.get("id").cloned().unwrap_or(Value::Null),
33
- "request": spec.get("request").cloned().unwrap_or(Value::Null),
34
- "userPrompt": spec.get("userPrompt").cloned().unwrap_or(Value::Null),
35
- "admission": spec.get("admission").cloned().unwrap_or(Value::Null),
36
- "allowedPaths": spec.get("allowedPaths").cloned().unwrap_or_else(|| json!([])),
37
- "declaredChangeTypes": spec
38
- .get("declaredChangeTypes")
31
+ let mut active_task = Map::new();
32
+ active_task.insert(
33
+ "id".to_string(),
34
+ spec.get("id").cloned().unwrap_or(Value::Null),
35
+ );
36
+ active_task.insert(
37
+ "request".to_string(),
38
+ spec.get("request").cloned().unwrap_or(Value::Null),
39
+ );
40
+ active_task.insert(
41
+ "userPrompt".to_string(),
42
+ spec.get("userPrompt").cloned().unwrap_or(Value::Null),
43
+ );
44
+ active_task.insert(
45
+ "admission".to_string(),
46
+ spec.get("admission").cloned().unwrap_or(Value::Null),
47
+ );
48
+ active_task.insert(
49
+ "allowedPaths".to_string(),
50
+ spec.get("allowedPaths")
39
51
  .cloned()
40
52
  .unwrap_or_else(|| json!([])),
41
- "requiredCheckIds": spec
42
- .get("requiredCheckIds")
53
+ );
54
+ active_task.insert(
55
+ "declaredChangeTypes".to_string(),
56
+ spec.get("declaredChangeTypes")
43
57
  .cloned()
44
58
  .unwrap_or_else(|| json!([])),
45
- "proofResults": proof_results,
46
- "revisions": spec.get("revisions").cloned().unwrap_or_else(|| json!([])),
47
- "humanReview": spec.get("humanReview").cloned().unwrap_or_else(|| {
59
+ );
60
+ active_task.insert(
61
+ "requiredCheckIds".to_string(),
62
+ spec.get("requiredCheckIds")
63
+ .cloned()
64
+ .unwrap_or_else(|| json!([])),
65
+ );
66
+ active_task.insert("proofResults".to_string(), Value::Array(proof_results));
67
+ if let Some(path_sets) = spec.get("proofPathSets") {
68
+ active_task.insert("proofPathSets".to_string(), path_sets.clone());
69
+ }
70
+ if let Some(batches) = spec.get("proofBatches") {
71
+ active_task.insert("proofBatches".to_string(), batches.clone());
72
+ }
73
+ active_task.insert(
74
+ "revisions".to_string(),
75
+ spec.get("revisions").cloned().unwrap_or_else(|| json!([])),
76
+ );
77
+ active_task.insert(
78
+ "humanReview".to_string(),
79
+ spec.get("humanReview").cloned().unwrap_or_else(|| {
48
80
  json!({
49
81
  "required": false,
50
82
  "approved": false,
51
83
  "reason": null
52
84
  })
53
- })
54
- })
85
+ }),
86
+ );
87
+
88
+ Value::Object(active_task)
55
89
  }
@@ -6,7 +6,7 @@ use serde_json::Value;
6
6
  use crate::models::NaomeError;
7
7
 
8
8
  use super::git_io::read_git_changed_entries;
9
- use super::types::{is_control_state_path, ChangedEntry, TaskDiff};
9
+ use super::types::{is_control_state_path, is_local_runtime_path, ChangedEntry, TaskDiff};
10
10
  use super::util::{matches_any_pattern, normalize_path, string_array};
11
11
  pub(super) fn validate_changed_paths(
12
12
  active_task: &Value,
@@ -80,7 +80,7 @@ pub(super) fn task_diff_from_entries(active_task: &Value, entries: &[ChangedEntr
80
80
  let diff_paths: Vec<String> = entries
81
81
  .iter()
82
82
  .map(|entry| entry.path.clone())
83
- .filter(|path| !is_control_state_path(path))
83
+ .filter(|path| !is_control_state_path(path) && !is_local_runtime_path(path))
84
84
  .collect();
85
85
  let outside_paths = diff_paths
86
86
  .iter()
@@ -6,7 +6,7 @@ use crate::models::NaomeError;
6
6
 
7
7
  pub(super) use super::git_parse::{parse_name_status_output, split_nul, upsert_changed_entry};
8
8
  pub(super) use super::git_refs::{command_output, git_commit_exists, read_git_head, run_git};
9
- use super::types::ChangedEntry;
9
+ use super::types::{is_local_runtime_path, ChangedEntry};
10
10
  use super::util::normalize_path;
11
11
  pub(super) fn read_git_changed_paths(root: &Path) -> Result<Vec<String>, NaomeError> {
12
12
  Ok(read_git_changed_entries(root)?
@@ -55,6 +55,9 @@ pub(super) fn read_git_changed_entries(root: &Path) -> Result<Vec<ChangedEntry>,
55
55
  }
56
56
 
57
57
  for entry in parse_name_status_output(&output.stdout) {
58
+ if is_local_runtime_path(&entry.path) {
59
+ continue;
60
+ }
58
61
  upsert_changed_entry(&mut entries, entry);
59
62
  }
60
63
  }
@@ -69,7 +72,7 @@ pub(super) fn read_git_changed_entries(root: &Path) -> Result<Vec<ChangedEntry>,
69
72
 
70
73
  for token in split_nul(&untracked.stdout) {
71
74
  let path = normalize_path(token.trim());
72
- if !path.is_empty() {
75
+ if !path.is_empty() && !is_local_runtime_path(&path) {
73
76
  upsert_changed_entry(
74
77
  &mut entries,
75
78
  ChangedEntry {
@@ -97,6 +97,10 @@ pub(super) fn is_control_state_path(path: &str) -> bool {
97
97
  path == CONTROL_STATE_PATH || path.starts_with(TASK_LEDGER_PATH_PREFIX)
98
98
  }
99
99
 
100
+ pub(super) fn is_local_runtime_path(path: &str) -> bool {
101
+ path == ".naome/tmp" || path.starts_with(".naome/tmp/")
102
+ }
103
+
100
104
  pub(super) fn read_complete_active_task(root: &Path) -> Result<Option<(Value, Value)>, NaomeError> {
101
105
  let Some(task_state) = read_task_state_projection(root)? else {
102
106
  return Ok(None);
@@ -5,6 +5,7 @@ use std::path::Path;
5
5
  use serde_json::Value;
6
6
 
7
7
  use crate::models::NaomeError;
8
+ use crate::verification_contract_policy::validate_semantic_quality_gate_policy;
8
9
 
9
10
  const REQUIRED_TESTING_HEADINGS: &[&str] = &[
10
11
  "# Testing And Verification",
@@ -29,7 +30,7 @@ const ALLOWED_TOP_LEVEL_KEYS: &[&str] = &[
29
30
  const MAX_CHECKS: usize = 20;
30
31
  const MAX_PHASES: usize = 8;
31
32
  const MAX_CHANGE_TYPES: usize = 12;
32
- const MAX_RELEASE_GATES: usize = 10;
33
+ const MAX_RELEASE_GATES: usize = 12;
33
34
 
34
35
  pub fn validate_verification_contract(root: &Path) -> Result<Vec<String>, NaomeError> {
35
36
  let mut errors = Vec::new();
@@ -135,6 +136,7 @@ fn validate_contract_shape(contract: &Value, errors: &mut Vec<String>) {
135
136
  }
136
137
  validate_change_types(change_types, &check_ids, errors);
137
138
  validate_release_gates(release_gates, &check_ids, errors);
139
+ validate_semantic_quality_gate_policy(change_types, release_gates, &check_ids, errors);
138
140
 
139
141
  if status == Some("ready") && contains_example_placeholders(contract) {
140
142
  errors.push(
@@ -0,0 +1,52 @@
1
+ use std::collections::HashSet;
2
+
3
+ use serde_json::Value;
4
+
5
+ pub(crate) fn validate_semantic_quality_gate_policy(
6
+ change_types: &[Value],
7
+ release_gates: &[Value],
8
+ check_ids: &HashSet<String>,
9
+ errors: &mut Vec<String>,
10
+ ) {
11
+ if check_ids.contains("repository-quality-check")
12
+ && !check_ids.contains("repository-semantic-check")
13
+ {
14
+ errors.push(
15
+ "checks must include repository-semantic-check whenever repository-quality-check is configured.".to_string(),
16
+ );
17
+ }
18
+
19
+ for (index, change_type) in change_types.iter().enumerate() {
20
+ let Some(required_checks) = change_type
21
+ .get("requiredChecks")
22
+ .and_then(Value::as_array)
23
+ else {
24
+ continue;
25
+ };
26
+ let has_quality = contains_string(required_checks, "repository-quality-check");
27
+ let has_semantic = contains_string(required_checks, "repository-semantic-check");
28
+ if has_quality && !has_semantic {
29
+ errors.push(format!(
30
+ "changeTypes[{index}].requiredChecks must include repository-semantic-check whenever repository-quality-check is required."
31
+ ));
32
+ }
33
+ }
34
+
35
+ let has_quality_release_gate = release_gates.iter().any(|gate| {
36
+ gate.get("checkId").and_then(Value::as_str) == Some("repository-quality-check")
37
+ });
38
+ let has_semantic_release_gate = release_gates.iter().any(|gate| {
39
+ gate.get("checkId").and_then(Value::as_str) == Some("repository-semantic-check")
40
+ });
41
+ if has_quality_release_gate && !has_semantic_release_gate {
42
+ errors.push(
43
+ "releaseGates must include repository-semantic-check whenever repository-quality-check is a release gate.".to_string(),
44
+ );
45
+ }
46
+ }
47
+
48
+ fn contains_string(values: &[Value], expected: &str) -> bool {
49
+ values
50
+ .iter()
51
+ .any(|value| value.as_str() == Some(expected))
52
+ }
@@ -0,0 +1,58 @@
1
+ use naome_core::{classify_information_path, information_architecture_report};
2
+
3
+ #[test]
4
+ fn classifies_naome_information_by_restore_source() {
5
+ let durable = classify_information_path(".naome/verification.json");
6
+ assert_eq!(durable.class, "durable_project_state");
7
+ assert_eq!(durable.commit_policy, "commit");
8
+ assert_eq!(durable.restore_policy, "restore_from_repository_or_package");
9
+
10
+ let projection = classify_information_path(".naome/task-state.json");
11
+ assert_eq!(projection.class, "generated_projection");
12
+ assert_eq!(
13
+ projection.commit_policy,
14
+ "commit_when_required_by_compatibility"
15
+ );
16
+ assert_eq!(projection.restore_policy, "regenerate_from_durable_state");
17
+
18
+ let local = classify_information_path(".naome/tmp/route.prompt");
19
+ assert_eq!(local.class, "local_runtime_state");
20
+ assert_eq!(local.commit_policy, "never_commit");
21
+ assert_eq!(local.restore_policy, "recreate_on_demand");
22
+
23
+ let proof = classify_information_path(".naome/tasks/readme-task/proofs/diff-check.json");
24
+ assert_eq!(proof.class, "run_evidence");
25
+ assert_eq!(proof.commit_policy, "prefer_compact_release_evidence");
26
+ assert_eq!(
27
+ proof.restore_policy,
28
+ "restore_from_ci_or_task_ledger_when_audited"
29
+ );
30
+
31
+ let task = classify_information_path(".naome/tasks/readme-task/task.json");
32
+ assert_eq!(task.class, "durable_task_ledger");
33
+ assert_eq!(task.commit_policy, "commit");
34
+ assert_eq!(task.restore_policy, "restore_from_repository");
35
+ }
36
+
37
+ #[test]
38
+ fn reports_information_architecture_contract_for_agents() {
39
+ let report = information_architecture_report();
40
+
41
+ assert_eq!(report.schema, "naome.information-architecture.v1");
42
+ assert!(report
43
+ .classes
44
+ .iter()
45
+ .any(|class| class.id == "durable_project_state"));
46
+ assert!(report
47
+ .classes
48
+ .iter()
49
+ .any(|class| class.id == "generated_projection"));
50
+ assert!(report
51
+ .classes
52
+ .iter()
53
+ .any(|class| class.id == "run_evidence"));
54
+ assert!(report
55
+ .rules
56
+ .iter()
57
+ .any(|rule| rule.path_pattern == ".naome/tasks/*/proofs/*.json"));
58
+ }
@@ -17,6 +17,7 @@ pub use verification_values::{
17
17
  change_type, check_missing_last_verified_fixture, diff_check,
18
18
  placeholder_verification_contract_fixture, quality_check, repo_docs_verification_fixture,
19
19
  repository_quality_config_source,
20
- repository_quality_config_value, semantic_repository_quality_fixture_source,
20
+ repository_quality_config_value, repository_semantic_check,
21
+ semantic_repository_quality_fixture_source,
21
22
  verification_value,
22
23
  };
@@ -2,7 +2,8 @@ use serde_json::{json, Value};
2
2
 
3
3
  use super::repo::TestRepo;
4
4
  use super::verification_values::{
5
- change_type, diff_check, mutating_diff_check, repository_quality_check, verification_value,
5
+ change_type, diff_check, mutating_diff_check, repository_quality_check,
6
+ repository_semantic_check, verification_value,
6
7
  };
7
8
 
8
9
  impl TestRepo {
@@ -33,54 +34,39 @@ impl TestRepo {
33
34
  ) {
34
35
  let mut checks = vec![diff_check(vec!["README.md"])];
35
36
  checks.extend(extra_checks);
36
-
37
37
  self.write_naome_json(
38
38
  "verification.json",
39
- json!({
40
- "schema": "naome.verification.v1",
41
- "version": 1,
42
- "status": "ready",
43
- "checks": checks,
44
- "changeTypes": [
45
- {
46
- "id": "readme",
47
- "description": "README changes.",
48
- "paths": ["README.md"],
49
- "requiredChecks": required_checks,
50
- "recommendedChecks": [],
51
- "humanReview": false
52
- }
53
- ],
54
- "releaseGates": []
55
- }),
39
+ verification_value(
40
+ "ready",
41
+ checks,
42
+ vec![change_type(
43
+ "readme",
44
+ "README changes.",
45
+ vec!["README.md"],
46
+ required_checks,
47
+ )],
48
+ ),
56
49
  );
57
50
  }
58
51
 
59
52
  pub fn write_product_quality_verification(&self) {
60
53
  self.write_naome_json(
61
54
  "verification.json",
62
- json!({
63
- "schema": "naome.verification.v1",
64
- "version": 1,
65
- "status": "ready",
66
- "checks": product_quality_checks(),
67
- "changeTypes": [
68
- {
69
- "id": "product-installer-or-template",
70
- "description": "NAOME product changes.",
71
- "paths": ["packages/naome/**", "scripts/**"],
72
- "requiredChecks": [
73
- "installer-tests",
74
- "rust-build",
75
- "decision-engine-tests",
76
- "package-dry-run"
77
- ],
78
- "recommendedChecks": [],
79
- "humanReview": false
80
- }
81
- ],
82
- "releaseGates": []
83
- }),
55
+ verification_value(
56
+ "ready",
57
+ product_quality_checks(),
58
+ vec![change_type(
59
+ "product-installer-or-template",
60
+ "NAOME product changes.",
61
+ vec!["packages/naome/**", "scripts/**"],
62
+ vec![
63
+ "installer-tests",
64
+ "rust-build",
65
+ "decision-engine-tests",
66
+ "package-dry-run",
67
+ ],
68
+ )],
69
+ ),
84
70
  );
85
71
  }
86
72
 
@@ -89,7 +75,7 @@ impl TestRepo {
89
75
  "verification.json",
90
76
  verification_value(
91
77
  "ready",
92
- vec![repository_quality_check()],
78
+ vec![repository_quality_check(), repository_semantic_check()],
93
79
  vec![change_type(
94
80
  "source",
95
81
  "Source changes.",
@@ -242,6 +242,19 @@ pub fn repository_quality_check() -> serde_json::Value {
242
242
  })
243
243
  }
244
244
 
245
+ pub fn repository_semantic_check() -> serde_json::Value {
246
+ json!({
247
+ "id": "repository-semantic-check",
248
+ "command": "naome semantic check --changed",
249
+ "cwd": ".",
250
+ "purpose": "Validate changed files against repository semantic rules.",
251
+ "cost": "fast",
252
+ "source": "NAOME built-in",
253
+ "evidence": ["src/**"],
254
+ "lastVerified": null
255
+ })
256
+ }
257
+
245
258
  pub fn semantic_repository_quality_fixture_source(name: &str) -> String {
246
259
  [
247
260
  format!("const {name} = {{"),
@@ -73,14 +73,14 @@ fn execute_route_runs_repository_quality_check_without_shelling_to_repo_command(
73
73
  }
74
74
 
75
75
  #[test]
76
- fn execute_route_refuses_semantic_changed_findings_through_repository_quality_gate() {
77
- let repo = TestRepo::new("route-semantic-quality-gate");
76
+ fn execute_route_requires_semantic_gate_with_repository_quality_gate() {
77
+ let repo = TestRepo::new("route-repository-quality-requires-semantic");
78
78
  repo.init_git();
79
79
  repo.write_file(
80
80
  ".naome/repository-quality.json",
81
81
  &repository_quality_config_source(),
82
82
  );
83
- repo.write_file("scripts/baseline.test.js", "const ok = { value: 1 };\n");
83
+ repo.write_file("src/clean.js", "export const clean = true;\n");
84
84
  repo.write_base_naome_state(json!({ "status": "idle", "activeTask": null }));
85
85
  repo.write_naome_json(
86
86
  "verification.json",
@@ -91,8 +91,56 @@ fn execute_route_refuses_semantic_changed_findings_through_repository_quality_ga
91
91
  "naome quality check --changed",
92
92
  "Validate changed files against repository quality rules.",
93
93
  "fast",
94
- vec!["scripts/**"],
94
+ vec!["src/**"],
95
95
  )],
96
+ vec![change_type(
97
+ "source",
98
+ "Source changes.",
99
+ vec!["src/**"],
100
+ vec!["repository-quality-check"],
101
+ )],
102
+ ),
103
+ );
104
+ repo.commit_all("baseline");
105
+ repo.write_file("src/clean.js", "export const clean = false;\n");
106
+ let before_head = repo.git_stdout(&["rev-parse", "HEAD"]);
107
+
108
+ let route = route_commit_request(&repo);
109
+
110
+ assert_quality_gate_blocked(&repo, &route, before_head, "src/clean.js");
111
+ assert!(route.user_message.contains("repository-semantic-check"));
112
+ }
113
+
114
+ #[test]
115
+ fn execute_route_refuses_semantic_changed_findings_through_repository_quality_gate() {
116
+ let repo = TestRepo::new("route-semantic-quality-gate");
117
+ repo.init_git();
118
+ repo.write_file(
119
+ ".naome/repository-quality.json",
120
+ &repository_quality_config_source(),
121
+ );
122
+ repo.write_file("scripts/baseline.test.js", "const ok = { value: 1 };\n");
123
+ repo.write_base_naome_state(json!({ "status": "idle", "activeTask": null }));
124
+ repo.write_naome_json(
125
+ "verification.json",
126
+ verification_value(
127
+ "ready",
128
+ vec![
129
+ quality_check(
130
+ "repository-quality-check",
131
+ "naome quality check --changed",
132
+ "Validate changed files against repository quality rules.",
133
+ "fast",
134
+ vec!["scripts/**"],
135
+ ),
136
+ quality_check(
137
+ "repository-semantic-check",
138
+ "naome semantic check --changed",
139
+ "Validate changed files against repository semantic rules.",
140
+ "fast",
141
+ vec!["scripts/**"],
142
+ ),
143
+ ],
96
144
  vec![change_type(
97
145
  "scripts",
98
146
  "Script changes.",
@@ -119,6 +119,57 @@ fn task_state_v2_can_migrate_to_ledger_without_losing_compact_proof_data() {
119
119
  assert!(report.errors.is_empty(), "{:?}", report.errors);
120
120
  }
121
121
 
122
+ #[test]
123
+ fn ledger_projection_preserves_compact_proof_batches_from_task_spec() {
124
+ let repo = TestRepo::new("compact-ledger");
125
+ repo.init_git();
126
+ repo.write_file("README.md", "# Baseline\n");
127
+ repo.write_base_harness(Some(idle_state()));
128
+ repo.git(&["add", "."]);
129
+ repo.git(&["commit", "-m", "baseline"]);
130
+ let head = repo.git_stdout(&["rev-parse", "HEAD"]);
131
+
132
+ repo.write_file(
133
+ ".naome/tasks/active.json",
134
+ &format_json(object([
135
+ ("schema", json!("naome.task-ledger-active.v1")),
136
+ ("version", json!(1)),
137
+ ("primaryTaskId", json!("readme-task")),
138
+ (
139
+ "worklanes",
140
+ json!([{ "id": "default", "taskId": "readme-task", "status": "active" }]),
141
+ ),
142
+ ])),
143
+ );
144
+ repo.write_file(
145
+ ".naome/tasks/readme-task/task.json",
146
+ &format_json(compact_task_spec_record(&head)),
147
+ );
148
+ repo.write_file(
149
+ ".naome/tasks/readme-task/events.jsonl",
150
+ concat!(
151
+ "{\"schema\":\"naome.task-ledger-event.v1\",\"type\":\"status\",\"status\":\"implementing\",\"recordedAt\":\"2026-05-08T00:00:01.000Z\"}\n",
152
+ "{\"schema\":\"naome.task-ledger-event.v1\",\"type\":\"status\",\"status\":\"complete\",\"recordedAt\":\"2026-05-08T00:00:02.000Z\"}\n"
153
+ ),
154
+ );
155
+ repo.write_file("README.md", "# Changed\n");
156
+
157
+ let projected = read_task_state_projection(repo.path()).unwrap().unwrap();
158
+ assert_eq!(projected["activeTask"]["proofResults"], json!([]));
159
+ assert_eq!(
160
+ projected["activeTask"]["proofPathSets"]["changed"],
161
+ json!(["README.md"])
162
+ );
163
+ assert_eq!(
164
+ projected["activeTask"]["proofBatches"][0]["proofs"][0]["checkId"],
165
+ "diff-check"
166
+ );
167
+
168
+ render_task_state_from_ledger(repo.path(), true).unwrap();
169
+ let report = validate_task_state(repo.path(), TaskStateOptions::default()).unwrap();
170
+ assert!(report.errors.is_empty(), "{:?}", report.errors);
171
+ }
172
+
122
173
  #[test]
123
174
  fn task_state_validation_reports_stale_rendered_projection_when_ledger_exists() {
124
175
  let repo = TestRepo::new("stale-projection");
@@ -261,6 +312,17 @@ fn task_spec_record(admission_head: &str) -> Value {
261
312
  ])
262
313
  }
263
314
 
315
+ fn compact_task_spec_record(admission_head: &str) -> Value {
316
+ let mut task = task_spec_record(admission_head);
317
+ let task_object = task.as_object_mut().unwrap();
318
+ task_object.insert(
319
+ "proofPathSets".to_string(),
320
+ object([("changed", json!(["README.md"]))]),
321
+ );
322
+ task_object.insert("proofBatches".to_string(), json!([compact_proof_batch()]));
323
+ task
324
+ }
325
+
264
326
  fn diff_proof_record() -> Value {
265
327
  object([
266
328
  ("schema", json!("naome.task-ledger-proof.v1")),
@@ -410,26 +410,20 @@ fn progress_report(repo: &TaskFixture) -> TaskStateReport {
410
410
  }
411
411
 
412
412
  fn implementing_readme_task_fixture() -> serde_json::Value {
413
- json!({
414
- "schema": "naome.task-state.v1",
415
- "version": 1,
416
- "status": "implementing",
417
- "activeTask": active_task(json!({
418
- "allowedPaths": ["README.md"],
419
- "proofResults": []
420
- })),
421
- "blocker": null,
422
- "updatedAt": "2026-05-04T12:00:00.000Z"
423
- })
413
+ implementing_task_fixture(vec!["README.md"])
424
414
  }
425
415
 
426
416
  fn implementing_package_task() -> serde_json::Value {
417
+ implementing_task_fixture(vec!["Package.swift"])
418
+ }
419
+
420
+ fn implementing_task_fixture(allowed_paths: Vec<&str>) -> serde_json::Value {
427
421
  json!({
428
422
  "schema": "naome.task-state.v1",
429
423
  "version": 1,
430
424
  "status": "implementing",
431
425
  "activeTask": active_task(json!({
432
- "allowedPaths": ["Package.swift"],
426
+ "allowedPaths": allowed_paths,
433
427
  "proofResults": []
434
428
  })),
435
429
  "blocker": null,
@@ -5,7 +5,7 @@ mod repo_support;
5
5
 
6
6
  use repo_support::{
7
7
  change_type, check_missing_last_verified_fixture, diff_check,
8
- placeholder_verification_contract_fixture, verification_value, TestRepo,
8
+ placeholder_verification_contract_fixture, quality_check, verification_value, TestRepo,
9
9
  };
10
10
 
11
11
  #[test]
@@ -52,6 +52,42 @@ fn rejects_missing_check_references_and_ready_placeholders() {
52
52
  assert!(joined.contains("must not contain example placeholders"));
53
53
  }
54
54
 
55
+ #[test]
56
+ fn rejects_repository_quality_without_semantic_required_check() {
57
+ let repo = TestRepo::new("verification-contract-semantic-required");
58
+ repo.write_testing_markdown(default_testing_markdown());
59
+ repo.write_naome_json("verification.json", {
60
+ let mut value = verification_value(
61
+ "ready",
62
+ vec![quality_check(
63
+ "repository-quality-check",
64
+ "node .naome/bin/naome.js quality check --changed",
65
+ "Validate changed files against repository quality rules.",
66
+ "fast",
67
+ vec![".naome/repository-quality.json"],
68
+ )],
69
+ vec![change_type(
70
+ "code",
71
+ "Code changes.",
72
+ vec!["src/**"],
73
+ vec!["repository-quality-check"],
74
+ )],
75
+ );
76
+ value["lastUpdated"] = json!("2026-05-11");
77
+ value["releaseGates"] = json!([{
78
+ "checkId": "repository-quality-check",
79
+ "requiredWhen": "Before release."
80
+ }]);
81
+ value
82
+ });
83
+
84
+ let errors = validate_verification_contract(repo.path()).unwrap();
85
+ let joined = errors.join("\n");
86
+
87
+ assert!(joined.contains("repository-semantic-check"));
88
+ assert!(joined.contains("changeTypes[0].requiredChecks"));
89
+ }
90
+
55
91
  #[test]
56
92
  fn rejects_checks_without_explicit_last_verified_value() {
57
93
  let repo = TestRepo::new("verification-contract-last-verified");
Binary file
Binary file
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lamentis/naome",
3
- "version": "1.3.2",
3
+ "version": "1.3.4",
4
4
  "description": "Native-first CLI for the NAOME agent harness.",
5
5
  "license": "Apache-2.0",
6
6
  "type": "module",
@@ -15,12 +15,12 @@ const expectedMachineOwnedIntegrity = Object.freeze({
15
15
  ".naome/package.json": "sha256:8005a3491db7d92f36ac66369861589f9c47123d3a7c71e643fc2c06168cd45a",
16
16
  ".naome/task-contract.schema.json": "sha256:1b3b62350328d0d6d660e36d1d1baaa2b88718530db774f9ab2a9e2fcba369c8",
17
17
  "AGENTS.md": "sha256:e8b2fc786c1c72b69ba8f2b2ffce4f459e799c7453ce9ff4a9f6448a8f9e6b4f",
18
- "docs/naome/agent-workflow.md": "sha256:78faeb4eed157b60f0b60bc43da36c713fc4a3436b93bd963ce5f3c12dc91f22",
18
+ "docs/naome/agent-workflow.md": "sha256:2fd1fd02eb6849133b9e8227421914580b1c469a60388a063c1b6ed48016b48d",
19
19
  "docs/naome/context-economy.md": "sha256:3ed5075815ecf4ada46a5e65438769310307c35759fcd46b13dc0b96e02bebd9",
20
20
  "docs/naome/execution.md": "sha256:bfc5d55838942ec8e3d790b59e3c634ff5bf6a2298265cef3dca9788a097eafb",
21
- "docs/naome/first-run.md": "sha256:f1a2412f1b542e61c79b5ba65a3fdaa810b0f95cf79d9f734256418cdcfb19aa",
21
+ "docs/naome/first-run.md": "sha256:1466ce8c65e19a1514885f917db14e8a772350e3f6d1c03a66326963365919e1",
22
22
  "docs/naome/index.md": "sha256:07ef776f49130319a5280bdb3ae38af22141708253f38eb983a4336fbae1b25a",
23
- "docs/naome/task-ledger.md": "sha256:1e524085a2f88811941fd9f2f48ca826bc3e0e4816039d07e795637847714cbd",
23
+ "docs/naome/task-ledger.md": "sha256:ac637a31abdd13eee15a49086594e63f5c88fe12a5cf621b227310788ae7e583",
24
24
  "docs/naome/upgrade.md": "sha256:2c60f0441bbd98bd528d109b30a7ded4b0ad55d61ffb9f52edac9e93b7999cb1"
25
25
  });
26
26
 
@@ -15,12 +15,12 @@ const expectedMachineOwnedIntegrity = Object.freeze({
15
15
  ".naome/package.json": "sha256:8005a3491db7d92f36ac66369861589f9c47123d3a7c71e643fc2c06168cd45a",
16
16
  ".naome/task-contract.schema.json": "sha256:1b3b62350328d0d6d660e36d1d1baaa2b88718530db774f9ab2a9e2fcba369c8",
17
17
  "AGENTS.md": "sha256:e8b2fc786c1c72b69ba8f2b2ffce4f459e799c7453ce9ff4a9f6448a8f9e6b4f",
18
- "docs/naome/agent-workflow.md": "sha256:78faeb4eed157b60f0b60bc43da36c713fc4a3436b93bd963ce5f3c12dc91f22",
18
+ "docs/naome/agent-workflow.md": "sha256:2fd1fd02eb6849133b9e8227421914580b1c469a60388a063c1b6ed48016b48d",
19
19
  "docs/naome/context-economy.md": "sha256:3ed5075815ecf4ada46a5e65438769310307c35759fcd46b13dc0b96e02bebd9",
20
20
  "docs/naome/execution.md": "sha256:bfc5d55838942ec8e3d790b59e3c634ff5bf6a2298265cef3dca9788a097eafb",
21
- "docs/naome/first-run.md": "sha256:f1a2412f1b542e61c79b5ba65a3fdaa810b0f95cf79d9f734256418cdcfb19aa",
21
+ "docs/naome/first-run.md": "sha256:1466ce8c65e19a1514885f917db14e8a772350e3f6d1c03a66326963365919e1",
22
22
  "docs/naome/index.md": "sha256:07ef776f49130319a5280bdb3ae38af22141708253f38eb983a4336fbae1b25a",
23
- "docs/naome/task-ledger.md": "sha256:1e524085a2f88811941fd9f2f48ca826bc3e0e4816039d07e795637847714cbd",
23
+ "docs/naome/task-ledger.md": "sha256:ac637a31abdd13eee15a49086594e63f5c88fe12a5cf621b227310788ae7e583",
24
24
  "docs/naome/upgrade.md": "sha256:2c60f0441bbd98bd528d109b30a7ded4b0ad55d61ffb9f52edac9e93b7999cb1"
25
25
  });
26
26
 
@@ -8,12 +8,12 @@
8
8
  ".naome/package.json": "sha256:8005a3491db7d92f36ac66369861589f9c47123d3a7c71e643fc2c06168cd45a",
9
9
  ".naome/task-contract.schema.json": "sha256:1b3b62350328d0d6d660e36d1d1baaa2b88718530db774f9ab2a9e2fcba369c8",
10
10
  "AGENTS.md": "sha256:e8b2fc786c1c72b69ba8f2b2ffce4f459e799c7453ce9ff4a9f6448a8f9e6b4f",
11
- "docs/naome/agent-workflow.md": "sha256:78faeb4eed157b60f0b60bc43da36c713fc4a3436b93bd963ce5f3c12dc91f22",
11
+ "docs/naome/agent-workflow.md": "sha256:2fd1fd02eb6849133b9e8227421914580b1c469a60388a063c1b6ed48016b48d",
12
12
  "docs/naome/context-economy.md": "sha256:3ed5075815ecf4ada46a5e65438769310307c35759fcd46b13dc0b96e02bebd9",
13
13
  "docs/naome/execution.md": "sha256:bfc5d55838942ec8e3d790b59e3c634ff5bf6a2298265cef3dca9788a097eafb",
14
- "docs/naome/first-run.md": "sha256:f1a2412f1b542e61c79b5ba65a3fdaa810b0f95cf79d9f734256418cdcfb19aa",
14
+ "docs/naome/first-run.md": "sha256:1466ce8c65e19a1514885f917db14e8a772350e3f6d1c03a66326963365919e1",
15
15
  "docs/naome/index.md": "sha256:07ef776f49130319a5280bdb3ae38af22141708253f38eb983a4336fbae1b25a",
16
- "docs/naome/task-ledger.md": "sha256:1e524085a2f88811941fd9f2f48ca826bc3e0e4816039d07e795637847714cbd",
16
+ "docs/naome/task-ledger.md": "sha256:ac637a31abdd13eee15a49086594e63f5c88fe12a5cf621b227310788ae7e583",
17
17
  "docs/naome/upgrade.md": "sha256:2c60f0441bbd98bd528d109b30a7ded4b0ad55d61ffb9f52edac9e93b7999cb1"
18
18
  },
19
19
  "machineOwned": [
@@ -114,10 +114,13 @@ Use this workflow after first-run intake is complete.
114
114
  task-state automatically; if a legacy-only active task is still present, run
115
115
  `node .naome/bin/naome.js task migrate-ledger --write --json` before adding
116
116
  completion proof.
117
- 5. Record proof in `.naome/tasks/<task-id>/proofs/`. If legacy compatibility
118
- work is unavoidable, use compact `proofPathSets` and `proofBatches` in
119
- `.naome/task-state.json` and include every changed in-scope path reported by
120
- git in expanded proof evidence.
117
+ 5. Record proof as compact `proofPathSets` and `proofBatches` in
118
+ `.naome/tasks/<task-id>/task.json` when many checks share evidence paths.
119
+ Use `.naome/tasks/<task-id>/proofs/` only when separate per-check files are
120
+ needed for parallel writers or durable audit detail. If legacy compatibility
121
+ work is unavoidable, use the same compact fields in `.naome/task-state.json`
122
+ and include every changed in-scope path reported by git in expanded proof
123
+ evidence.
121
124
  6. Run `node .naome/bin/naome.js task render-state --write --json` before
122
125
  external compatibility checks. Do not hand-edit the rendered projection when
123
126
  `.naome/tasks/active.json` exists.
@@ -8,7 +8,16 @@ Status: Uninitialized
8
8
 
9
9
  ## Known Boundaries
10
10
 
11
- - Unknown.
11
+ - NAOME information falls into durable project state, durable task ledger,
12
+ generated projection, local runtime state, run evidence, and product source.
13
+ - Durable project state is committed repository or package state needed to
14
+ reinstall or reconstruct the harness.
15
+ - `.naome/task-state.json` is a generated compatibility projection when
16
+ `.naome/tasks/active.json` exists. Render it from the ledger instead of
17
+ hand-editing it.
18
+ - `.naome/tmp/` is local runtime state and must not be committed.
19
+ - Per-check proof files are run evidence. Prefer compact ledger proof batches
20
+ when many release checks share the same evidence paths.
12
21
 
13
22
  ## Assumed Boundaries
14
23
 
@@ -20,7 +29,7 @@ Status: Uninitialized
20
29
 
21
30
  ## Generated Or External Code
22
31
 
23
- - Unknown.
32
+ - Generated NAOME projections must be reproducible from durable state.
24
33
 
25
34
  ## Evidence
26
35
 
@@ -102,7 +102,7 @@ Rules:
102
102
  real check.
103
103
  - This JSON is machine state, not prose context. It is exempt from the
104
104
  200-line instruction-file budget, but must stay bounded: at most 20 checks, 12
105
- change types, and 10 release gates.
105
+ change types, and 12 release gates.
106
106
  - Do not read `.naome/verification.json` as long-form context during normal
107
107
  work. Select the relevant change type or check id, then read only the matching
108
108
  entries needed for proof.
@@ -26,6 +26,9 @@ new deterministic task tooling can use `.naome/tasks/` as the source of truth.
26
26
  - `mutations.json` records touched paths and mutation ownership.
27
27
  - `proofs/<check-id>.json` stores one verification result per check so parallel
28
28
  checks do not rewrite the same file.
29
+ - `task.json` may also contain compact `proofPathSets` and `proofBatches`.
30
+ Prefer that release shape when many checks share evidence paths and detailed
31
+ command logs are already retained by CI or the local run history.
29
32
 
30
33
  ## Compatibility
31
34
 
@@ -60,7 +63,7 @@ whether product changes stay inside `allowedPaths`.
60
63
  The intended long-term model is:
61
64
 
62
65
  ```text
63
- task spec + events + mutations + proofs + verification config
66
+ task spec + events + mutations + compact proof batches + verification config
64
67
  => canonical task model
65
68
  => generated task-state projection
66
69
  => gates / route / commit / completion
@@ -69,3 +72,12 @@ task spec + events + mutations + proofs + verification config
69
72
  This keeps old repositories backward-compatible while making future parallel
70
73
  agents safer: separate agents and checks can write separate event/proof files
71
74
  instead of all competing for `.naome/task-state.json`.
75
+
76
+ ## Information Policy
77
+
78
+ Use `node .naome/bin/naome.js workflow information --path <path> --json` before
79
+ changing NAOME control files whose retention is unclear. Durable configuration,
80
+ templates, task specs, and task events are restored from repository or package
81
+ state. `.naome/task-state.json` is generated from durable state. `.naome/tmp/`
82
+ is local runtime state. Per-check proof files are run evidence; compact release
83
+ proof batches keep release diffs readable without losing verification coverage.
@@ -67,7 +67,7 @@ phase is failing or missing.
67
67
  - Use dates as `YYYY-MM-DD` or `null`.
68
68
  - Keep instruction files under 200 lines. `.naome/verification.json` is machine
69
69
  state instead; keep it schema-valid and bounded to 20 checks, 12 change types,
70
- and 10 release gates.
70
+ and 12 release gates.
71
71
  - Store long command output as a compact summary that preserves command, cwd,
72
72
  exit code, relevant lines, affected paths, and artifacts.
73
73
  - When intake defines change types, include `repository-quality-check` and