@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.
- package/Cargo.lock +2 -2
- package/crates/naome-cli/Cargo.toml +1 -1
- package/crates/naome-cli/src/main.rs +1 -0
- package/crates/naome-cli/src/workflow_commands.rs +30 -4
- package/crates/naome-core/Cargo.toml +1 -1
- package/crates/naome-core/src/information_architecture.rs +179 -0
- package/crates/naome-core/src/lib.rs +6 -0
- package/crates/naome-core/src/route/quality_gate_config.rs +18 -0
- package/crates/naome-core/src/task_ledger/render.rs +50 -16
- package/crates/naome-core/src/task_state/diff.rs +2 -2
- package/crates/naome-core/src/task_state/git_io.rs +5 -2
- package/crates/naome-core/src/task_state/types.rs +4 -0
- package/crates/naome-core/src/verification_contract.rs +3 -1
- package/crates/naome-core/src/verification_contract_policy.rs +52 -0
- package/crates/naome-core/tests/information_architecture.rs +58 -0
- package/crates/naome-core/tests/repo_support/mod.rs +2 -1
- package/crates/naome-core/tests/repo_support/verification.rs +28 -42
- package/crates/naome-core/tests/repo_support/verification_values.rs +13 -0
- package/crates/naome-core/tests/route_user_diff.rs +52 -4
- package/crates/naome-core/tests/task_ledger.rs +62 -0
- package/crates/naome-core/tests/task_state.rs +6 -12
- package/crates/naome-core/tests/verification_contract.rs +37 -1
- package/native/darwin-arm64/naome +0 -0
- package/native/linux-x64/naome +0 -0
- package/package.json +1 -1
- package/templates/naome-root/.naome/bin/check-harness-health.js +3 -3
- package/templates/naome-root/.naome/bin/check-task-state.js +3 -3
- package/templates/naome-root/.naome/manifest.json +3 -3
- package/templates/naome-root/docs/naome/agent-workflow.md +7 -4
- package/templates/naome-root/docs/naome/architecture.md +11 -2
- package/templates/naome-root/docs/naome/first-run.md +1 -1
- package/templates/naome-root/docs/naome/task-ledger.md +13 -1
- 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.
|
|
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.
|
|
87
|
+
version = "1.3.4"
|
|
88
88
|
dependencies = [
|
|
89
89
|
"serde",
|
|
90
90
|
"serde_json",
|
|
@@ -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,
|
|
5
|
-
|
|
6
|
-
|
|
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
|
|
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],
|
|
@@ -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
|
-
|
|
32
|
-
|
|
33
|
-
"
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
"
|
|
38
|
-
|
|
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
|
-
|
|
42
|
-
|
|
53
|
+
);
|
|
54
|
+
active_task.insert(
|
|
55
|
+
"declaredChangeTypes".to_string(),
|
|
56
|
+
spec.get("declaredChangeTypes")
|
|
43
57
|
.cloned()
|
|
44
58
|
.unwrap_or_else(|| json!([])),
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
"
|
|
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 =
|
|
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,
|
|
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,
|
|
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
|
-
|
|
40
|
-
"
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
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
|
-
|
|
63
|
-
"
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
"
|
|
71
|
-
"
|
|
72
|
-
"
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
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
|
|
77
|
-
let repo = TestRepo::new("route-
|
|
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("
|
|
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!["
|
|
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
|
-
|
|
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":
|
|
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
|
package/native/linux-x64/naome
CHANGED
|
Binary file
|
package/package.json
CHANGED
|
@@ -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:
|
|
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:
|
|
21
|
+
"docs/naome/first-run.md": "sha256:1466ce8c65e19a1514885f917db14e8a772350e3f6d1c03a66326963365919e1",
|
|
22
22
|
"docs/naome/index.md": "sha256:07ef776f49130319a5280bdb3ae38af22141708253f38eb983a4336fbae1b25a",
|
|
23
|
-
"docs/naome/task-ledger.md": "sha256:
|
|
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:
|
|
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:
|
|
21
|
+
"docs/naome/first-run.md": "sha256:1466ce8c65e19a1514885f917db14e8a772350e3f6d1c03a66326963365919e1",
|
|
22
22
|
"docs/naome/index.md": "sha256:07ef776f49130319a5280bdb3ae38af22141708253f38eb983a4336fbae1b25a",
|
|
23
|
-
"docs/naome/task-ledger.md": "sha256:
|
|
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:
|
|
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:
|
|
14
|
+
"docs/naome/first-run.md": "sha256:1466ce8c65e19a1514885f917db14e8a772350e3f6d1c03a66326963365919e1",
|
|
15
15
|
"docs/naome/index.md": "sha256:07ef776f49130319a5280bdb3ae38af22141708253f38eb983a4336fbae1b25a",
|
|
16
|
-
"docs/naome/task-ledger.md": "sha256:
|
|
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
|
|
118
|
-
|
|
119
|
-
`.naome/task-
|
|
120
|
-
|
|
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
|
-
-
|
|
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
|
-
-
|
|
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
|
|
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 +
|
|
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
|
|
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
|