@lamentis/naome 1.3.3 → 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 +5 -0
- package/crates/naome-core/src/task_ledger/render.rs +50 -16
- package/crates/naome-core/tests/information_architecture.rs +58 -0
- package/crates/naome-core/tests/task_ledger.rs +62 -0
- 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 +2 -2
- package/templates/naome-root/.naome/bin/check-task-state.js +2 -2
- package/templates/naome-root/.naome/manifest.json +2 -2
- 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/task-ledger.md +13 -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;
|
|
@@ -23,6 +24,10 @@ pub use context::{
|
|
|
23
24
|
};
|
|
24
25
|
pub use decision::{evaluate_decision, format_decision, EvaluationOptions};
|
|
25
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
|
+
};
|
|
26
31
|
pub use install_plan::{install_plan, InstallPlan};
|
|
27
32
|
pub use install_plan::{MACHINE_OWNED_PATHS, PROJECT_OWNED_PATHS};
|
|
28
33
|
pub use intent::{evaluate_intent, format_intent, IntentDecision, PromptEvidence};
|
|
@@ -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
|
}
|
|
@@ -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
|
+
}
|
|
@@ -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")),
|
|
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
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
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
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
|
|
|
@@ -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.
|