@lamentis/naome 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/Cargo.lock +199 -0
- package/Cargo.toml +11 -0
- package/LICENSE +21 -0
- package/README.md +6 -0
- package/bin/naome-node.js +1424 -0
- package/bin/naome.js +129 -0
- package/crates/naome-cli/Cargo.toml +14 -0
- package/crates/naome-cli/src/main.rs +341 -0
- package/crates/naome-core/Cargo.toml +11 -0
- package/crates/naome-core/src/decision.rs +432 -0
- package/crates/naome-core/src/git.rs +70 -0
- package/crates/naome-core/src/harness_health.rs +557 -0
- package/crates/naome-core/src/install_plan.rs +82 -0
- package/crates/naome-core/src/lib.rs +17 -0
- package/crates/naome-core/src/models.rs +99 -0
- package/crates/naome-core/src/paths.rs +72 -0
- package/crates/naome-core/src/task_state.rs +1859 -0
- package/crates/naome-core/src/verification.rs +217 -0
- package/crates/naome-core/src/verification_contract.rs +406 -0
- package/crates/naome-core/tests/decision.rs +297 -0
- package/crates/naome-core/tests/harness_health.rs +232 -0
- package/crates/naome-core/tests/install_plan.rs +35 -0
- package/crates/naome-core/tests/task_state.rs +588 -0
- package/crates/naome-core/tests/verification.rs +165 -0
- package/crates/naome-core/tests/verification_contract.rs +181 -0
- package/native/darwin-arm64/naome +0 -0
- package/package.json +44 -0
- package/templates/naome-root/.naome/bin/check-harness-health.js +163 -0
- package/templates/naome-root/.naome/bin/check-task-state.js +180 -0
- package/templates/naome-root/.naome/bin/naome.js +306 -0
- package/templates/naome-root/.naome/init-state.json +13 -0
- package/templates/naome-root/.naome/manifest.json +45 -0
- package/templates/naome-root/.naome/package.json +3 -0
- package/templates/naome-root/.naome/task-contract.schema.json +174 -0
- package/templates/naome-root/.naome/task-state.json +8 -0
- package/templates/naome-root/.naome/upgrade-state.json +7 -0
- package/templates/naome-root/.naome/verification.json +45 -0
- package/templates/naome-root/.naomeignore +4 -0
- package/templates/naome-root/AGENTS.md +77 -0
- package/templates/naome-root/docs/naome/agent-workflow.md +82 -0
- package/templates/naome-root/docs/naome/architecture.md +37 -0
- package/templates/naome-root/docs/naome/decisions.md +18 -0
- package/templates/naome-root/docs/naome/execution.md +192 -0
- package/templates/naome-root/docs/naome/first-run.md +135 -0
- package/templates/naome-root/docs/naome/index.md +67 -0
- package/templates/naome-root/docs/naome/repo-profile.md +51 -0
- package/templates/naome-root/docs/naome/security.md +60 -0
- package/templates/naome-root/docs/naome/testing.md +51 -0
- package/templates/naome-root/docs/naome/upgrade.md +20 -0
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
use std::fs;
|
|
2
|
+
use std::path::{Path, PathBuf};
|
|
3
|
+
use std::time::{SystemTime, UNIX_EPOCH};
|
|
4
|
+
|
|
5
|
+
use naome_core::seed_builtin_verification_checks;
|
|
6
|
+
use serde_json::json;
|
|
7
|
+
|
|
8
|
+
#[test]
|
|
9
|
+
fn seeds_builtin_checks_without_removing_repo_specific_checks() {
|
|
10
|
+
let repo = TestRepo::new("verification-seed");
|
|
11
|
+
repo.write_naome_json(
|
|
12
|
+
"verification.json",
|
|
13
|
+
json!({
|
|
14
|
+
"schema": "naome.verification.v1",
|
|
15
|
+
"version": 1,
|
|
16
|
+
"status": "ready",
|
|
17
|
+
"lastUpdated": "2026-05-05",
|
|
18
|
+
"checks": [
|
|
19
|
+
{
|
|
20
|
+
"id": "repo-docs",
|
|
21
|
+
"command": "npm run docs:test",
|
|
22
|
+
"cwd": ".",
|
|
23
|
+
"purpose": "Validate repository documentation.",
|
|
24
|
+
"cost": "fast",
|
|
25
|
+
"source": "repository intake",
|
|
26
|
+
"evidence": ["package.json"],
|
|
27
|
+
"lastVerified": null
|
|
28
|
+
}
|
|
29
|
+
],
|
|
30
|
+
"changeTypes": [
|
|
31
|
+
{
|
|
32
|
+
"id": "repo-docs",
|
|
33
|
+
"description": "Repository documentation changes.",
|
|
34
|
+
"paths": ["docs/**"],
|
|
35
|
+
"requiredChecks": ["repo-docs"],
|
|
36
|
+
"recommendedChecks": [],
|
|
37
|
+
"humanReview": false
|
|
38
|
+
}
|
|
39
|
+
],
|
|
40
|
+
"releaseGates": [
|
|
41
|
+
{
|
|
42
|
+
"checkId": "repo-docs",
|
|
43
|
+
"requiredWhen": "Before merging repository documentation changes."
|
|
44
|
+
}
|
|
45
|
+
]
|
|
46
|
+
}),
|
|
47
|
+
);
|
|
48
|
+
|
|
49
|
+
let changed = seed_builtin_verification_checks(repo.path()).unwrap();
|
|
50
|
+
|
|
51
|
+
assert!(changed);
|
|
52
|
+
let verification = repo.read_naome_json("verification.json");
|
|
53
|
+
assert_eq!(
|
|
54
|
+
check_command(&verification, "diff-check"),
|
|
55
|
+
Some("git diff --check")
|
|
56
|
+
);
|
|
57
|
+
assert_eq!(
|
|
58
|
+
check_command(&verification, "naome-harness-health"),
|
|
59
|
+
Some("node .naome/bin/check-harness-health.js")
|
|
60
|
+
);
|
|
61
|
+
assert_eq!(
|
|
62
|
+
check_command(&verification, "naome-task-state"),
|
|
63
|
+
Some("node .naome/bin/check-task-state.js")
|
|
64
|
+
);
|
|
65
|
+
assert_eq!(
|
|
66
|
+
check_command(&verification, "repo-docs"),
|
|
67
|
+
Some("npm run docs:test")
|
|
68
|
+
);
|
|
69
|
+
|
|
70
|
+
let changed_again = seed_builtin_verification_checks(repo.path()).unwrap();
|
|
71
|
+
assert!(!changed_again);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
#[test]
|
|
75
|
+
fn seeding_builtin_checks_preserves_existing_verification_layout() {
|
|
76
|
+
let repo = TestRepo::new("verification-layout");
|
|
77
|
+
repo.write_naome_file(
|
|
78
|
+
"verification.json",
|
|
79
|
+
r#"{
|
|
80
|
+
"schema": "naome.verification.v1",
|
|
81
|
+
"version": 1,
|
|
82
|
+
"status": "ready",
|
|
83
|
+
"lastUpdated": "2026-05-06",
|
|
84
|
+
"checks": [
|
|
85
|
+
{
|
|
86
|
+
"id": "repo-docs",
|
|
87
|
+
"command": "npm run docs:test",
|
|
88
|
+
"cwd": ".",
|
|
89
|
+
"purpose": "Validate repository documentation.",
|
|
90
|
+
"cost": "fast",
|
|
91
|
+
"source": "repository intake",
|
|
92
|
+
"evidence": [
|
|
93
|
+
"package.json"
|
|
94
|
+
],
|
|
95
|
+
"lastVerified": null
|
|
96
|
+
}
|
|
97
|
+
],
|
|
98
|
+
"changeTypes": [],
|
|
99
|
+
"releaseGates": []
|
|
100
|
+
}
|
|
101
|
+
"#,
|
|
102
|
+
);
|
|
103
|
+
|
|
104
|
+
let changed = seed_builtin_verification_checks(repo.path()).unwrap();
|
|
105
|
+
|
|
106
|
+
assert!(changed);
|
|
107
|
+
let content = repo.read_naome_file("verification.json");
|
|
108
|
+
assert!(content.find("\"schema\"").unwrap() < content.find("\"checks\"").unwrap());
|
|
109
|
+
assert!(content.find("\"checks\"").unwrap() < content.find("\"changeTypes\"").unwrap());
|
|
110
|
+
assert!(
|
|
111
|
+
content.find("\"id\": \"repo-docs\"").unwrap()
|
|
112
|
+
< content.find("\"command\": \"npm run docs:test\"").unwrap()
|
|
113
|
+
);
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
fn check_command<'a>(verification: &'a serde_json::Value, check_id: &str) -> Option<&'a str> {
|
|
117
|
+
verification
|
|
118
|
+
.get("checks")?
|
|
119
|
+
.as_array()?
|
|
120
|
+
.iter()
|
|
121
|
+
.find(|check| check.get("id").and_then(serde_json::Value::as_str) == Some(check_id))?
|
|
122
|
+
.get("command")?
|
|
123
|
+
.as_str()
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
struct TestRepo {
|
|
127
|
+
root: PathBuf,
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
impl TestRepo {
|
|
131
|
+
fn new(name: &str) -> Self {
|
|
132
|
+
let nonce = SystemTime::now()
|
|
133
|
+
.duration_since(UNIX_EPOCH)
|
|
134
|
+
.unwrap()
|
|
135
|
+
.as_nanos();
|
|
136
|
+
let root = std::env::temp_dir().join(format!("naome-core-{name}-{nonce}"));
|
|
137
|
+
fs::create_dir_all(root.join(".naome")).unwrap();
|
|
138
|
+
Self { root }
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
fn path(&self) -> &Path {
|
|
142
|
+
&self.root
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
fn write_naome_json(&self, file_name: &str, value: serde_json::Value) {
|
|
146
|
+
fs::write(
|
|
147
|
+
self.root.join(".naome").join(file_name),
|
|
148
|
+
format!("{}\n", serde_json::to_string_pretty(&value).unwrap()),
|
|
149
|
+
)
|
|
150
|
+
.unwrap();
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
fn write_naome_file(&self, file_name: &str, content: &str) {
|
|
154
|
+
fs::write(self.root.join(".naome").join(file_name), content).unwrap();
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
fn read_naome_json(&self, file_name: &str) -> serde_json::Value {
|
|
158
|
+
serde_json::from_str(&fs::read_to_string(self.root.join(".naome").join(file_name)).unwrap())
|
|
159
|
+
.unwrap()
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
fn read_naome_file(&self, file_name: &str) -> String {
|
|
163
|
+
fs::read_to_string(self.root.join(".naome").join(file_name)).unwrap()
|
|
164
|
+
}
|
|
165
|
+
}
|
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
use std::fs;
|
|
2
|
+
use std::path::{Path, PathBuf};
|
|
3
|
+
use std::time::{SystemTime, UNIX_EPOCH};
|
|
4
|
+
|
|
5
|
+
use naome_core::validate_verification_contract;
|
|
6
|
+
use serde_json::json;
|
|
7
|
+
|
|
8
|
+
#[test]
|
|
9
|
+
fn accepts_valid_contract_and_testing_map() {
|
|
10
|
+
let repo = TestRepo::new("verification-contract-valid");
|
|
11
|
+
repo.write_testing_markdown(default_testing_markdown());
|
|
12
|
+
repo.write_naome_json(
|
|
13
|
+
"verification.json",
|
|
14
|
+
json!({
|
|
15
|
+
"schema": "naome.verification.v1",
|
|
16
|
+
"version": 1,
|
|
17
|
+
"status": "uninitialized",
|
|
18
|
+
"lastUpdated": null,
|
|
19
|
+
"checks": [
|
|
20
|
+
{
|
|
21
|
+
"id": "diff-check",
|
|
22
|
+
"command": "git diff --check",
|
|
23
|
+
"cwd": ".",
|
|
24
|
+
"purpose": "Reject whitespace errors.",
|
|
25
|
+
"cost": "fast",
|
|
26
|
+
"source": "git",
|
|
27
|
+
"evidence": [],
|
|
28
|
+
"lastVerified": null
|
|
29
|
+
}
|
|
30
|
+
],
|
|
31
|
+
"changeTypes": [
|
|
32
|
+
{
|
|
33
|
+
"id": "docs",
|
|
34
|
+
"description": "Documentation changes.",
|
|
35
|
+
"paths": ["docs/**"],
|
|
36
|
+
"requiredChecks": ["diff-check"],
|
|
37
|
+
"recommendedChecks": [],
|
|
38
|
+
"humanReview": false
|
|
39
|
+
}
|
|
40
|
+
],
|
|
41
|
+
"releaseGates": [
|
|
42
|
+
{
|
|
43
|
+
"checkId": "diff-check",
|
|
44
|
+
"requiredWhen": "Before release when docs changed."
|
|
45
|
+
}
|
|
46
|
+
]
|
|
47
|
+
}),
|
|
48
|
+
);
|
|
49
|
+
|
|
50
|
+
let errors = validate_verification_contract(repo.path()).unwrap();
|
|
51
|
+
|
|
52
|
+
assert!(errors.is_empty(), "{errors:#?}");
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
#[test]
|
|
56
|
+
fn rejects_missing_check_references_and_ready_placeholders() {
|
|
57
|
+
let repo = TestRepo::new("verification-contract-invalid");
|
|
58
|
+
repo.write_testing_markdown(default_testing_markdown());
|
|
59
|
+
repo.write_naome_json(
|
|
60
|
+
"verification.json",
|
|
61
|
+
json!({
|
|
62
|
+
"schema": "naome.verification.v1",
|
|
63
|
+
"version": 1,
|
|
64
|
+
"status": "ready",
|
|
65
|
+
"lastUpdated": "2026-05-05",
|
|
66
|
+
"checks": [
|
|
67
|
+
{
|
|
68
|
+
"id": "example-check",
|
|
69
|
+
"command": "replace with real command",
|
|
70
|
+
"cwd": ".",
|
|
71
|
+
"purpose": "Replace with what this proves.",
|
|
72
|
+
"cost": "fast",
|
|
73
|
+
"source": "replace with evidence source",
|
|
74
|
+
"evidence": [],
|
|
75
|
+
"lastVerified": null
|
|
76
|
+
}
|
|
77
|
+
],
|
|
78
|
+
"changeTypes": [
|
|
79
|
+
{
|
|
80
|
+
"id": "docs",
|
|
81
|
+
"description": "Documentation changes.",
|
|
82
|
+
"paths": ["docs/**"],
|
|
83
|
+
"requiredChecks": ["missing-check"],
|
|
84
|
+
"recommendedChecks": [],
|
|
85
|
+
"humanReview": false
|
|
86
|
+
}
|
|
87
|
+
],
|
|
88
|
+
"releaseGates": []
|
|
89
|
+
}),
|
|
90
|
+
);
|
|
91
|
+
|
|
92
|
+
let errors = validate_verification_contract(repo.path()).unwrap();
|
|
93
|
+
let joined = errors.join("\n");
|
|
94
|
+
|
|
95
|
+
assert!(joined.contains("unknown check id: missing-check"));
|
|
96
|
+
assert!(joined.contains("must not contain example placeholders"));
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
#[test]
|
|
100
|
+
fn rejects_checks_without_explicit_last_verified_value() {
|
|
101
|
+
let repo = TestRepo::new("verification-contract-last-verified");
|
|
102
|
+
repo.write_testing_markdown(default_testing_markdown());
|
|
103
|
+
repo.write_naome_json(
|
|
104
|
+
"verification.json",
|
|
105
|
+
json!({
|
|
106
|
+
"schema": "naome.verification.v1",
|
|
107
|
+
"version": 1,
|
|
108
|
+
"status": "uninitialized",
|
|
109
|
+
"lastUpdated": null,
|
|
110
|
+
"checks": [
|
|
111
|
+
{
|
|
112
|
+
"id": "diff-check",
|
|
113
|
+
"command": "git diff --check",
|
|
114
|
+
"cwd": ".",
|
|
115
|
+
"purpose": "Reject whitespace errors.",
|
|
116
|
+
"cost": "fast",
|
|
117
|
+
"source": "git",
|
|
118
|
+
"evidence": []
|
|
119
|
+
}
|
|
120
|
+
],
|
|
121
|
+
"changeTypes": [],
|
|
122
|
+
"releaseGates": []
|
|
123
|
+
}),
|
|
124
|
+
);
|
|
125
|
+
|
|
126
|
+
let errors = validate_verification_contract(repo.path()).unwrap();
|
|
127
|
+
let joined = errors.join("\n");
|
|
128
|
+
|
|
129
|
+
assert!(joined.contains("checks[0].lastVerified must be YYYY-MM-DD or null."));
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
fn default_testing_markdown() -> &'static str {
|
|
133
|
+
"# Testing And Verification\n\n\
|
|
134
|
+
## Verification Map\n\n\
|
|
135
|
+
| Change type | Required proof | Command | Notes |\n|---|---|---|---|\n\n\
|
|
136
|
+
## Known Checks\n\n\
|
|
137
|
+
| Check id | Command | Cwd | Cost | Last verified |\n|---|---|---|---|---|\n\n\
|
|
138
|
+
## Change Type Rules\n\n\
|
|
139
|
+
| Change type | Paths | Required checks |\n|---|---|---|\n\n\
|
|
140
|
+
## Release Gates\n\n\
|
|
141
|
+
| Check id | Required when |\n|---|---|\n\n\
|
|
142
|
+
## Evidence\n\n\
|
|
143
|
+
- Test fixture.\n"
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
struct TestRepo {
|
|
147
|
+
root: PathBuf,
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
impl TestRepo {
|
|
151
|
+
fn new(name: &str) -> Self {
|
|
152
|
+
let nonce = SystemTime::now()
|
|
153
|
+
.duration_since(UNIX_EPOCH)
|
|
154
|
+
.unwrap()
|
|
155
|
+
.as_nanos();
|
|
156
|
+
let root = std::env::temp_dir().join(format!("naome-core-{name}-{nonce}"));
|
|
157
|
+
fs::create_dir_all(root.join(".naome")).unwrap();
|
|
158
|
+
fs::create_dir_all(root.join("docs").join("naome")).unwrap();
|
|
159
|
+
Self { root }
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
fn path(&self) -> &Path {
|
|
163
|
+
&self.root
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
fn write_naome_json(&self, file_name: &str, value: serde_json::Value) {
|
|
167
|
+
fs::write(
|
|
168
|
+
self.root.join(".naome").join(file_name),
|
|
169
|
+
format!("{}\n", serde_json::to_string_pretty(&value).unwrap()),
|
|
170
|
+
)
|
|
171
|
+
.unwrap();
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
fn write_testing_markdown(&self, content: &str) {
|
|
175
|
+
fs::write(
|
|
176
|
+
self.root.join("docs").join("naome").join("testing.md"),
|
|
177
|
+
content,
|
|
178
|
+
)
|
|
179
|
+
.unwrap();
|
|
180
|
+
}
|
|
181
|
+
}
|
|
Binary file
|
package/package.json
ADDED
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@lamentis/naome",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Native-first CLI for the NAOME agent harness.",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"type": "module",
|
|
7
|
+
"keywords": [
|
|
8
|
+
"agent",
|
|
9
|
+
"agents",
|
|
10
|
+
"ai",
|
|
11
|
+
"harness",
|
|
12
|
+
"repository",
|
|
13
|
+
"codex",
|
|
14
|
+
"claude"
|
|
15
|
+
],
|
|
16
|
+
"repository": {
|
|
17
|
+
"type": "git",
|
|
18
|
+
"url": "git+https://github.com/Lamentis-O/naome.git",
|
|
19
|
+
"directory": "packages/naome"
|
|
20
|
+
},
|
|
21
|
+
"bugs": {
|
|
22
|
+
"url": "https://github.com/Lamentis-O/naome/issues"
|
|
23
|
+
},
|
|
24
|
+
"homepage": "https://github.com/Lamentis-O/naome#readme",
|
|
25
|
+
"bin": {
|
|
26
|
+
"naome": "bin/naome.js"
|
|
27
|
+
},
|
|
28
|
+
"files": [
|
|
29
|
+
"bin/naome.js",
|
|
30
|
+
"bin/naome-node.js",
|
|
31
|
+
"Cargo.lock",
|
|
32
|
+
"Cargo.toml",
|
|
33
|
+
"crates",
|
|
34
|
+
"native",
|
|
35
|
+
"templates",
|
|
36
|
+
"README.md"
|
|
37
|
+
],
|
|
38
|
+
"engines": {
|
|
39
|
+
"node": ">=18"
|
|
40
|
+
},
|
|
41
|
+
"publishConfig": {
|
|
42
|
+
"access": "public"
|
|
43
|
+
}
|
|
44
|
+
}
|
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
const { existsSync, readFileSync } = require("node:fs");
|
|
4
|
+
const { createHash } = require("node:crypto");
|
|
5
|
+
const { dirname, join, resolve } = require("node:path");
|
|
6
|
+
const { spawnSync } = require("node:child_process");
|
|
7
|
+
|
|
8
|
+
const nativeBinaryRelativePath = process.platform === "win32" ? ".naome/bin/naome-rust.exe" : ".naome/bin/naome-rust";
|
|
9
|
+
const nativeBinaryName = process.platform === "win32" ? "naome.exe" : "naome";
|
|
10
|
+
|
|
11
|
+
const expectedMachineOwnedIntegrity = Object.freeze({
|
|
12
|
+
"AGENTS.md": "sha256:ebe589ce85b43d4aa23014d8a26a49bd33dc52ef1bba68e4f63a7e86640be06a",
|
|
13
|
+
".naome/package.json": "sha256:8005a3491db7d92f36ac66369861589f9c47123d3a7c71e643fc2c06168cd45a",
|
|
14
|
+
".naome/bin/naome.js": "sha256:1b63729dc55730a826193d35cf78e755a991b356ac81b45f4aa3fcf24c8152fa",
|
|
15
|
+
".naome/bin/check-task-state.js": "sha256:43c02868072d0d13499aefba2e9a5ec9517d59539fd19ff0f11e3e4623a51b44",
|
|
16
|
+
".naome/bin/check-harness-health.js": "sha256:dce2a380022dd63d86bd762869debafbc635ab3d7e8fae985e2d429d8ee437ad",
|
|
17
|
+
".naome/task-contract.schema.json": "sha256:c806416d4944eaed6dc48b3760fd0dd5b9b5a7c9ab895697fe36b54c41c1332f",
|
|
18
|
+
"docs/naome/index.md": "sha256:75c4cdf9edcd46c83a619cbfc551e96cae35bcff694d955ebab4624125ae7f12",
|
|
19
|
+
"docs/naome/first-run.md": "sha256:a1dd0bd17ec9d71955a473cd2c4a615538e89a7d81e8f4e1015a50ab9efe3558",
|
|
20
|
+
"docs/naome/agent-workflow.md": "sha256:5e5b26e7896f95a9154abcd133f08b33f84fddc679757ed15c043a91e319fb4e",
|
|
21
|
+
"docs/naome/execution.md": "sha256:3d67fd9922a66a66f79f920adbb24dcd47eefdfde835a5351a27ffed932dcfea",
|
|
22
|
+
"docs/naome/upgrade.md": "sha256:2c60f0441bbd98bd528d109b30a7ded4b0ad55d61ffb9f52edac9e93b7999cb1"
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
function main() {
|
|
26
|
+
const root = findHarnessRoot(process.cwd()) || resolve(__dirname, "..", "..");
|
|
27
|
+
const nativeBinary = resolveNativeDecisionBinary(root);
|
|
28
|
+
|
|
29
|
+
const result = spawnSync(nativeBinary, ["check-harness-health", "--root", root], {
|
|
30
|
+
cwd: root,
|
|
31
|
+
encoding: "utf8",
|
|
32
|
+
env: {
|
|
33
|
+
...process.env,
|
|
34
|
+
NAOME_EXPECTED_INTEGRITY_JSON: JSON.stringify(expectedMachineOwnedIntegrity)
|
|
35
|
+
},
|
|
36
|
+
stdio: "inherit"
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
process.exit(result.status === null ? 1 : result.status);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function resolveNativeDecisionBinary(root) {
|
|
43
|
+
const expectedIntegrity = expectedNativeIntegrity(root);
|
|
44
|
+
const candidates = [
|
|
45
|
+
process.env.NAOME_NATIVE_BIN,
|
|
46
|
+
join(root, nativeBinaryRelativePath),
|
|
47
|
+
join(root, "packages", "naome", "target", "release", nativeBinaryName),
|
|
48
|
+
join(root, "packages", "naome", "target", "debug", nativeBinaryName)
|
|
49
|
+
].filter(Boolean);
|
|
50
|
+
|
|
51
|
+
for (const candidate of candidates) {
|
|
52
|
+
if (isUsableNativeBinary(candidate, root, expectedIntegrity)) {
|
|
53
|
+
return candidate;
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
if (expectedIntegrity) {
|
|
58
|
+
fail(`No runnable NAOME native harness health binary matched ${expectedIntegrity}. Run naome sync again.`);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
const built = buildSourceNativeBinary(root);
|
|
62
|
+
if (built && isUsableNativeBinary(built, root, null)) {
|
|
63
|
+
return built;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
fail("NAOME native harness health binary is missing or incompatible. Run naome sync again.");
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
function expectedNativeIntegrity(root) {
|
|
70
|
+
if (isIntegrityHash(process.env.NAOME_EXPECTED_NATIVE_INTEGRITY)) {
|
|
71
|
+
return process.env.NAOME_EXPECTED_NATIVE_INTEGRITY;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
const manifestPath = join(root, ".naome", "manifest.json");
|
|
75
|
+
if (!existsSync(manifestPath)) {
|
|
76
|
+
return null;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
let manifest;
|
|
80
|
+
try {
|
|
81
|
+
manifest = JSON.parse(readFileSync(manifestPath, "utf8"));
|
|
82
|
+
} catch {
|
|
83
|
+
return null;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
const expected = manifest.integrity?.[nativeBinaryRelativePath];
|
|
87
|
+
if (!isIntegrityHash(expected)) {
|
|
88
|
+
return null;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
return expected;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
function isUsableNativeBinary(candidate, root, expectedIntegrity) {
|
|
95
|
+
if (!candidate || !existsSync(candidate)) {
|
|
96
|
+
return false;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
if (expectedIntegrity) {
|
|
100
|
+
const actual = `sha256:${createHash("sha256").update(readFileSync(candidate)).digest("hex")}`;
|
|
101
|
+
if (actual !== expectedIntegrity) {
|
|
102
|
+
return false;
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
const probe = spawnSync(candidate, [], {
|
|
107
|
+
cwd: root,
|
|
108
|
+
encoding: "utf8",
|
|
109
|
+
stdio: "ignore"
|
|
110
|
+
});
|
|
111
|
+
return !probe.error && probe.status !== null;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
function buildSourceNativeBinary(root) {
|
|
115
|
+
const manifestPath = join(root, "packages", "naome", "Cargo.toml");
|
|
116
|
+
if (!existsSync(manifestPath)) {
|
|
117
|
+
return null;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
const result = spawnSync("cargo", ["build", "--release", "--manifest-path", manifestPath, "-p", "naome-cli"], {
|
|
121
|
+
cwd: root,
|
|
122
|
+
encoding: "utf8",
|
|
123
|
+
stdio: "ignore"
|
|
124
|
+
});
|
|
125
|
+
if (result.status !== 0) {
|
|
126
|
+
return null;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
const builtPath = join(root, "packages", "naome", "target", "release", nativeBinaryName);
|
|
130
|
+
return existsSync(builtPath) ? builtPath : null;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
function findHarnessRoot(startPath) {
|
|
134
|
+
let current = resolve(startPath);
|
|
135
|
+
|
|
136
|
+
while (true) {
|
|
137
|
+
if (existsSync(join(current, ".naome", "manifest.json"))) {
|
|
138
|
+
return current;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
const parent = dirname(current);
|
|
142
|
+
if (parent === current) {
|
|
143
|
+
return null;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
current = parent;
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
function isIntegrityHash(value) {
|
|
151
|
+
return typeof value === "string" && /^sha256:[a-f0-9]{64}$/.test(value);
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
function fail(message) {
|
|
155
|
+
console.error("NAOME harness health check failed.");
|
|
156
|
+
console.error(`- ${message}`);
|
|
157
|
+
console.error("");
|
|
158
|
+
console.error("Repair options: repair_harness, review_harness_diff, cancel_repair_baseline");
|
|
159
|
+
console.error("Run naome sync to repair machine-owned files after review.");
|
|
160
|
+
process.exit(1);
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
main();
|