newt-core 0.8.2

Newt-Agent core types, errors, and the NeMoCode-style tier router
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
use super::*;

/// Working directories are resolved before the confined command executes.
#[test]
fn resolve_exec_cwd_confines_to_workspace() {
    // #1159: relative cwd joins under the workspace; absolute passes through
    // (the fs fence rejects escapes); empty/None → workspace root.
    assert_eq!(resolve_exec_cwd("/ws", None), "/ws");
    assert_eq!(resolve_exec_cwd("/ws", Some("")), "/ws");
    assert_eq!(resolve_exec_cwd("/ws", Some("  ")), "/ws");
    // Relative join uses the platform separator (correct — the cwd feeds
    // the confined shell on this OS); compare against the same join.
    let joined = std::path::Path::new("/ws")
        .join("crates/foo")
        .to_string_lossy()
        .into_owned();
    assert_eq!(resolve_exec_cwd("/ws", Some("crates/foo")), joined);
    // An absolute path passes through verbatim (platform-appropriate).
    let abs = if cfg!(windows) {
        "C:\\ws\\sub"
    } else {
        "/ws/sub"
    };
    assert_eq!(resolve_exec_cwd("/ws", Some(abs)), abs);
    // The dispatch args carry it verbatim.
    let a = confined_dispatch_args("ls", &joined);
    assert_eq!(a["cwd"], joined);
}

#[test]
fn command_cwd_preserves_nonblank_literal_path_whitespace() {
    let relative = " directory ";
    let expected = std::path::Path::new("/workspace").join(relative);
    assert_eq!(
        resolve_exec_cwd("/workspace", Some(relative)),
        expected.to_string_lossy()
    );
    let absolute = if cfg!(windows) {
        r"C:\workspace\directory "
    } else {
        "/workspace/directory "
    };
    assert_eq!(resolve_exec_cwd("/workspace", Some(absolute)), absolute);
    for blank in ["", " ", "\t\n"] {
        assert_eq!(resolve_exec_cwd("/workspace", Some(blank)), "/workspace");
    }
}

#[test]
fn split_leading_cd_folds_the_habitual_cd_prefix() {
    // The reported failure: `cd <workspace> && git checkout -b …` tried to
    // exec the `cd` builtin. Fold it: cwd = the path, run the real command.
    let (path, rest) = split_leading_cd("cd /ws/newt-agent && git checkout -b x");
    assert_eq!(path.as_deref(), Some("/ws/newt-agent"));
    assert_eq!(rest, "git checkout -b x");

    // `;` connective folds the same way.
    let (path, rest) = split_leading_cd("cd sub ; ls -la");
    assert_eq!(path.as_deref(), Some("sub"));
    assert_eq!(rest, "ls -la");

    // A quoted path with spaces is returned unquoted; the remainder is kept.
    let (path, rest) = split_leading_cd("cd \"/a b/c\" && cargo test");
    assert_eq!(path.as_deref(), Some("/a b/c"));
    assert_eq!(rest, "cargo test");

    // Only the FIRST cd is folded — a second cd stays in the remainder for
    // the shell engine (we don't chase chdir chains).
    let (path, rest) = split_leading_cd("cd a && cd b && ls");
    assert_eq!(path.as_deref(), Some("a"));
    assert_eq!(rest, "cd b && ls");

    // A bare `cd <path>` folds to an empty remainder (the caller turns this
    // into a guidance note — nothing to exec).
    let (path, rest) = split_leading_cd("cd /somewhere");
    assert_eq!(path.as_deref(), Some("/somewhere"));
    assert!(rest.is_empty());
}

#[test]
fn split_leading_cd_leaves_non_cd_and_ambiguous_commands_whole() {
    // Not a cd at all → unchanged.
    assert_eq!(split_leading_cd("git status"), (None, "git status".into()));
    // `cd` as a substring of another word is not a match.
    assert_eq!(split_leading_cd("cding foo"), (None, "cding foo".into()));
    // `cd <path>` followed by something other than a sequential connective
    // (a pipe here) is left whole — we only fold the safe `&&`/`;` shapes.
    assert_eq!(
        split_leading_cd("cd x | grep y"),
        (None, "cd x | grep y".into())
    );
}

#[test]
fn run_command_redirect_lets_git_network_ops_through() {
    // Ops the embedded git tool cannot do faithfully → fall through (None).
    for cmd in [
        "git push origin fix/foo",
        "git push",
        "git fetch origin",
        "git pull",
        "git clone https://example.com/r.git",
        "git rm src/cockpit.rs",
    ] {
        assert_eq!(run_command_redirect(cmd), None, "{cmd} must fall through");
    }
    // Local ops the embedded git tool handles → still redirect.
    for cmd in [
        "git status",
        "git log --oneline",
        "git add .",
        "git commit -m x",
    ] {
        assert_eq!(
            run_command_redirect(cmd),
            None,
            "{cmd} must retain native command semantics"
        );
    }
    // Other direct tools still redirect; plain shell commands run as-is.
    assert_eq!(run_command_redirect("read_file foo.txt"), Some("read_file"));
    assert_eq!(run_command_redirect("list_dir ."), Some("list_dir"));
    assert_eq!(run_command_redirect("cargo test"), None);
    assert_eq!(run_command_redirect("gh pr create --fill"), None);
    assert_eq!(run_command_redirect(""), None);
}

/// #1262: a command with shell COMPOSITION is a real shell program the
/// embedded tools cannot serve — it must never be redirected (the diagnosed
/// session's legitimate pipeline was bounced + miscounted as a corrected
/// hallucination). Bare servable forms keep redirecting.
#[test]
fn run_command_redirect_passes_composed_commands_through() {
    // The exact diagnosed pipeline.
    assert_eq!(
        run_command_redirect(
            "find . -name \"*.rs\" -type f -print0 | xargs -0 du -k | sort -rn | head 20"
        ),
        None,
        "a pipeline leading with `find` is not a misdirected find call"
    );
    // Redirects and sequencing are composition too.
    assert_eq!(run_command_redirect("find . -name '*.log' > out.txt"), None);
    assert_eq!(run_command_redirect("git status && git diff"), None);
    assert_eq!(run_command_redirect("list_dir . ; echo done"), None);
    assert_eq!(run_command_redirect("read_file $(pick_file)"), None);
    assert_eq!(run_command_redirect("read_file `pick_file`"), None);
    // Bare servable forms still redirect (the true positives hold).
    assert_eq!(run_command_redirect("find . -name \"*.rs\""), Some("find"));
    // The embedded `grep` tool: a regex search that needs no shell spawn, so
    // it survives where `find` does and where run_command spawn is refused.
    assert_eq!(run_command_redirect("grep error ."), Some("grep"));
    assert_eq!(run_command_redirect("list_dir src"), Some("list_dir"));
    assert_eq!(run_command_redirect("git status"), None);
}

/// The old refusal forced a smaller Git dialect on the model. The native
/// command keeps revisions, output flags, and pathspecs unchanged.
#[test]
fn git_commands_are_never_redirected_to_a_special_tool() {
    for command in [
        "git log A..B",
        "git diff --stat -- src",
        "git show HEAD",
        "git status --porcelain=v1",
    ] {
        assert_eq!(run_command_redirect(command), None, "{command}");
    }
}

/// #1709 family: a COMPOSED `run_command` that creates a git commit bypasses
/// `LocalGitTool::finalize_commit_message` and would land an unattributed
/// commit. The guard `run_command_creates_shell_git_commit` detects it so the
/// run_command arm can refuse predictably and direct the model to the
/// first-class `git` tool. A bare `git commit` is already bounced by
/// `run_command_redirect` (covered above); these are the composed forms that
/// fall through.
#[test]
fn run_command_creates_shell_git_commit_detects_composed_commit_forms() {
    // Sequencing + commit.
    assert!(run_command_creates_shell_git_commit(
        "git add . && git commit -m \"fix the parser\""
    ));
    assert!(run_command_creates_shell_git_commit(
        "git add . ; git commit -m x"
    ));
    // Pipeline commit (e.g. message from stdin).
    assert!(run_command_creates_shell_git_commit(
        "echo \"msg\" | git commit -F -"
    ));
    // Redirect is composition.
    assert!(run_command_creates_shell_git_commit(
        "git commit -m x > commit.log"
    ));
    // Global option with a value before the subcommand.
    assert!(run_command_creates_shell_git_commit(
        "git -c user.email=evil@example.com commit -m x"
    ));
    assert!(run_command_creates_shell_git_commit(
        "git -C /repo commit -m x"
    ));
    // `--git-dir=<path>` carries its value in-token, so the next token IS
    // the subcommand.
    assert!(run_command_creates_shell_git_commit(
        "git --git-dir=/repo/.git commit -m x"
    ));
    // A bare flag global option before the subcommand.
    assert!(run_command_creates_shell_git_commit(
        "git --no-pager commit -m x"
    ));
    // `--amend` is still the `commit` subcommand.
    assert!(run_command_creates_shell_git_commit(
        "git add . && git commit --amend -m x"
    ));
    // Qualified binary path (the model often uses /usr/bin/git).
    assert!(run_command_creates_shell_git_commit(
        "/usr/bin/git -C /repo commit -m x"
    ));
    // Env-assignment prefix forging commit identity.
    assert!(run_command_creates_shell_git_commit(
        "GIT_AUTHOR_NAME=evil GIT_AUTHOR_EMAIL=evil@example.com git commit -m x"
    ));
    // Command substitution / backtick wrapping.
    assert!(run_command_creates_shell_git_commit("$(git commit -m x)"));
    assert!(run_command_creates_shell_git_commit("`git commit -m x`"));
}

/// #1709 family: the guard must NOT fire on legitimate read-only git, git
/// network ops, unrelated commands, or the abort/quit forms of the
/// commit-producing subcommands — the bypass closure is narrow to commit
/// creation only. (`git merge`/`cherry-pick`/`revert`/`rebase` themselves
/// ARE blocked now — see `run_command_creates_shell_git_commit_detects_other_commit_forms`.)
#[test]
fn run_command_creates_shell_git_commit_preserves_readonly_and_unrelated() {
    // Read-only composed git.
    assert!(!run_command_creates_shell_git_commit(
        "git status && git diff"
    ));
    assert!(!run_command_creates_shell_git_commit(
        "git log | grep commit"
    ));
    assert!(!run_command_creates_shell_git_commit(
        "git log --grep=commit --oneline"
    ));
    // Git network passthrough ops.
    assert!(!run_command_creates_shell_git_commit(
        "git add . && git push origin fix/foo"
    ));
    assert!(!run_command_creates_shell_git_commit("git fetch"));
    // `commit` appearing as an ARGUMENT, not a subcommand.
    assert!(!run_command_creates_shell_git_commit(
        "echo git commit > notes.txt"
    ));
    assert!(!run_command_creates_shell_git_commit("cat commit_log.txt"));
    // Non-git commands.
    assert!(!run_command_creates_shell_git_commit("cargo test"));
    assert!(!run_command_creates_shell_git_commit("just check"));
    assert!(!run_command_creates_shell_git_commit(""));
    // Abort/quit forms create NO commit — preserved (fall through). These
    // back out of an in-progress op without creating a commit.
    assert!(!run_command_creates_shell_git_commit("git rebase --abort"));
    assert!(!run_command_creates_shell_git_commit("git rebase --quit"));
    assert!(!run_command_creates_shell_git_commit(
        "git cherry-pick --abort"
    ));
    assert!(!run_command_creates_shell_git_commit("git revert --quit"));
    assert!(!run_command_creates_shell_git_commit("git merge --abort"));
    assert!(!run_command_creates_shell_git_commit("git merge --quit"));
    // `--skip`/`--continue` DO create commits (they advance the operation),
    // so they are NOT abort forms — covered as blocked below.
}

/// #1709 family (audit req 7/8): the other audit-identified commit-producing
/// shell forms — `git merge`, `git cherry-pick`, `git revert`, `git rebase`
/// — are now BLOCKED (route or deny), bare AND composed, while their
/// `--abort`/`--quit` forms pass through (above). `--skip`/`--continue`
/// create commits and stay blocked.
#[test]
fn run_command_creates_shell_git_commit_detects_other_commit_forms() {
    // Bare commit-producing subcommands.
    assert!(run_command_creates_shell_git_commit("git merge feature/x"));
    assert!(run_command_creates_shell_git_commit(
        "git cherry-pick abc123"
    ));
    assert!(run_command_creates_shell_git_commit("git revert abc123"));
    assert!(run_command_creates_shell_git_commit("git rebase main"));
    // Composed with a preceding `git add`.
    assert!(run_command_creates_shell_git_commit(
        "git add . && git merge feature/x"
    ));
    // Qualified binary path.
    assert!(run_command_creates_shell_git_commit(
        "/usr/bin/git -C /repo cherry-pick abc123"
    ));
    // Env-assignment prefix.
    assert!(run_command_creates_shell_git_commit(
        "GIT_AUTHOR_NAME=evil git revert abc123"
    ));
    // `--skip` and `--continue` advance the operation and CREATE commits —
    // they are NOT abort forms, so they stay blocked.
    assert!(run_command_creates_shell_git_commit("git rebase --skip"));
    assert!(run_command_creates_shell_git_commit(
        "git cherry-pick --continue"
    ));
    assert!(run_command_creates_shell_git_commit(
        "git rebase --continue"
    ));
    // An abort flag ANYWHERE in the args exempts a non-commit subcommand
    // (a real abort never creates a commit).
    assert!(!run_command_creates_shell_git_commit(
        "git rebase --abort --continue"
    ));
}

/// #1709 family (req 13): a model cannot escape harness-managed attribution
/// by shelling out `git commit` with its OWN hand-written trailer or forged
/// identity — the guard blocks the commit attempt ENTIRELY, so the model is
/// forced onto the `git` tool, whose finalizer owns attribution. The model's
/// text never reaches a real `git commit`.
#[test]
fn run_command_creates_shell_git_commit_blocks_model_forged_attribution() {
    // Model hand-writes a trailer to impersonate harness attribution.
    assert!(run_command_creates_shell_git_commit(
        "git commit -m \"fix the parser\n\nCo-authored-by: fake (newt-agent v9.9) <x@y>\""
    ));
    // Model forges the author identity via -c.
    assert!(run_command_creates_shell_git_commit(
        "git -c user.name='newt-agent' -c user.email='x@y' commit -m x"
    ));
    // Model forges identity via env prefix.
    assert!(run_command_creates_shell_git_commit(
        "GIT_AUTHOR_NAME=newt-agent GIT_AUTHOR_EMAIL=x@y git commit -m x"
    ));
    // Model tries to suppress attribution by emptying the message via a file.
    assert!(run_command_creates_shell_git_commit(
        "printf '' | git commit -F -"
    ));
}

/// Attribution integration remains a migration prerequisite for native
/// commits. The guard, not an embedded-tool redirect, owns this restriction.
#[test]
fn bare_git_commit_uses_the_attribution_guard_without_a_redirect() {
    assert_eq!(run_command_redirect("git commit"), None);
    assert_eq!(run_command_redirect("git commit --amend -m x"), None);
    assert!(run_command_creates_shell_git_commit("git commit"));
}

/// #1262: the loop's `hallucination_count` increments exactly on
/// `is_hallucination` (mod.rs call classification), so this pure pin IS the
/// turn-metrics behavior: the diagnosed pipeline counts ZERO hallucinations;
/// a bare misdirected call still counts one.
#[test]
fn pipeline_is_never_counted_as_a_hallucination() {
    assert!(!is_hallucination(
        "run_command",
        &serde_json::json!({"command":
                "find . -name \"*.rs\" -type f -print0 | xargs -0 du -k | sort -rn | head 20"})
    ));
    assert!(!is_hallucination(
        "run_command",
        &serde_json::json!({"command": "git status && git diff"})
    ));
    // The true positive holds: a bare misdirected call still counts.
    assert!(is_hallucination(
        "run_command",
        &serde_json::json!({"command": "list_dir ."})
    ));
}

/// #898 regression: a real `git push` at run_command must NOT be counted as
/// a hallucination. Local Git commands are equally ordinary shell invocations.
#[test]
fn is_hallucination_allows_git_network_ops() {
    assert!(!is_hallucination(
        "run_command",
        &serde_json::json!({"command": "git push origin fix/foo"})
    ));
    assert!(!is_hallucination(
        "run_command",
        &serde_json::json!({"command": "git fetch"})
    ));
    assert!(!is_hallucination(
        "run_command",
        &serde_json::json!({"command": "git rm src/cockpit.rs"})
    ));
    assert!(!is_hallucination(
        "run_command",
        &serde_json::json!({"command": "git status"})
    ));
}

/// #2905 round 2: the review must not hinge on the lexical commit detector.
/// A script that commits (`./ship.sh`, a Makefile target, an alias) is
/// invisible to it, yet the commit it lands is still reviewed.
#[test]
fn commit_review_covers_a_commit_the_detector_cannot_see() {
    struct Moves(std::sync::atomic::AtomicBool);
    impl crate::commit_review::CommitReader for Moves {
        fn head(&self) -> std::result::Result<Option<String>, crate::commit_review::HeadError> {
            Ok(Some(
                if self.0.load(std::sync::atomic::Ordering::SeqCst) {
                    "oid-new"
                } else {
                    "oid-base"
                }
                .into(),
            ))
        }
        fn landed(&self, _: Option<&str>, _: &str, _: usize) -> Vec<(String, String)> {
            vec![(
                "oid-new".into(),
                "x\n\nHi from /home/someone/secret\n".into(),
            )]
        }
        fn stat(&self, _: &str) -> String {
            String::new()
        }
    }
    let script = "./scripts/ship.sh";
    assert!(!run_command_creates_shell_git_commit(script));
    let repo = Moves(std::sync::atomic::AtomicBool::new(false));
    let pending = start_commit_review(true, &repo, None).expect("snapshot taken");
    repo.0.store(true, std::sync::atomic::Ordering::SeqCst);
    let report = pending.finish(&repo).0.expect("landed commit reviewed");
    assert!(report.contains("oid-new"), "{report}");
    // No read authority over the cwd: no snapshot at all.
    assert!(start_commit_review(false, &repo, None).is_none());
}