[pitboss/grind] deferred session-0014 (20260516T052512Z-20f8)

This commit is contained in:
pitboss 2026-05-16 08:30:39 -05:00
parent a2cc5f7700
commit 6a169f51b8
23 changed files with 737 additions and 29 deletions

View file

@ -336,6 +336,7 @@ fn chain_step(prev_output: Option<&[u8]>) -> ChainStepHarness {
)]
})
.unwrap_or_default(),
extra_files: Vec::new(),
}
}

View file

@ -308,6 +308,7 @@ fn chain_step(prev_output: Option<&[u8]>) -> ChainStepHarness {
)]
})
.unwrap_or_default(),
extra_files: Vec::new(),
}
}

View file

@ -111,6 +111,7 @@ fn chain_step(prev_output: Option<&[u8]>) -> ChainStepHarness {
)]
})
.unwrap_or_default(),
extra_files: Vec::new(),
}
}

View file

@ -116,6 +116,7 @@ fn chain_step(prev_output: Option<&[u8]>) -> ChainStepHarness {
)]
})
.unwrap_or_default(),
extra_files: Vec::new(),
}
}

View file

@ -278,6 +278,35 @@ function __nyx_stub_sql_record(query, detail) {
// best-effort: stub recorder write failure is non-fatal.
}
}
// Phase 10 (Track D.3) HTTP recording helper. When the verifier spawned an
// HttpStub it publishes the side-channel log path through NYX_HTTP_LOG; a
// sink call site whose outbound request never reaches the on-the-wire
// listener (DNS-mocked, network-isolated sandbox, pre-flight check) can
// call this helper to surface the attempted call. Format matches the SQL
// helper so the host-side merger parses both streams identically.
function __nyx_stub_http_record(method, url, body, detail) {
const _p = process.env.NYX_HTTP_LOG;
if (!_p) return;
const _fs = require('fs');
try {
let _buf = '';
_buf += '# method: ' + String(method) + '\n';
_buf += '# url: ' + String(url) + '\n';
if (body !== undefined && body !== null) {
_buf += '# body: ' + String(body) + '\n';
}
if (detail && typeof detail === 'object') {
for (const _k of Object.keys(detail)) {
_buf += '# ' + String(_k) + ': ' + String(detail[_k]) + '\n';
}
}
_buf += String(method) + ' ' + String(url) + '\n';
_fs.appendFileSync(_p, _buf);
} catch (e) {
// best-effort: stub recorder write failure is non-fatal.
}
}
"#
}
@ -465,6 +494,7 @@ pub fn chain_step(prev_output: Option<&[u8]>, is_typescript: bool) -> ChainStepH
)]
})
.unwrap_or_default(),
extra_files: Vec::new(),
}
}
@ -1074,4 +1104,17 @@ mod tests {
"stub recorder must append to the log file"
);
}
#[test]
fn probe_shim_publishes_stub_http_recorder() {
let shim = probe_shim();
assert!(
shim.contains("function __nyx_stub_http_record"),
"Node probe shim must define __nyx_stub_http_record"
);
assert!(
shim.contains("NYX_HTTP_LOG"),
"stub recorder must read NYX_HTTP_LOG"
);
}
}

View file

@ -66,6 +66,14 @@ pub struct ChainStepHarness {
pub filename: String,
pub command: Vec<String>,
pub extra_env: Vec<(String, String)>,
/// Companion files staged alongside [`Self::source`] in the chain
/// step's workdir. Each entry is `(relative_path, content)`;
/// subdirectories in `relative_path` are created automatically.
/// Mirrors [`HarnessSource::extra_files`] so an emitter whose chain
/// step needs a build manifest (Rust's `Cargo.toml`, future
/// `pom.xml`, etc.) can ship it without smuggling everything into
/// `source`.
pub extra_files: Vec<(String, String)>,
}
impl ChainStepHarness {
@ -156,6 +164,7 @@ pub fn default_chain_step(prev_output: Option<&[u8]>) -> ChainStepHarness {
)]
})
.unwrap_or_default(),
extra_files: Vec::new(),
}
}

View file

@ -98,6 +98,7 @@ fn chain_step(prev_output: Option<&[u8]>) -> ChainStepHarness {
)]
})
.unwrap_or_default(),
extra_files: Vec::new(),
}
}
@ -352,6 +353,28 @@ function __nyx_stub_sql_record($query, array $detail = []): void {
if (substr($q, -1) !== "\n") $buf .= "\n";
@file_put_contents($p, $buf, FILE_APPEND);
}
// Phase 10 (Track D.3) HTTP recording helper. When the verifier spawned an
// HttpStub it publishes the side-channel log path through NYX_HTTP_LOG; a
// sink call site whose outbound request never reaches the on-the-wire
// listener (DNS-mocked, network-isolated sandbox, pre-flight check) can
// call this helper to surface the attempted call. Format matches the SQL
// helper so the host-side merger parses both streams identically.
function __nyx_stub_http_record($method, $url, $body = null, array $detail = []): void {
$p = getenv('NYX_HTTP_LOG');
if ($p === false || $p === '') return;
$buf = '';
$buf .= '# method: ' . (string)$method . "\n";
$buf .= '# url: ' . (string)$url . "\n";
if ($body !== null) {
$buf .= '# body: ' . (string)$body . "\n";
}
foreach ($detail as $k => $v) {
$buf .= '# ' . (string)$k . ': ' . (string)$v . "\n";
}
$buf .= (string)$method . ' ' . (string)$url . "\n";
@file_put_contents($p, $buf, FILE_APPEND);
}
"#
}
@ -751,6 +774,19 @@ mod tests {
);
}
#[test]
fn probe_shim_publishes_stub_http_recorder() {
let shim = probe_shim();
assert!(
shim.contains("function __nyx_stub_http_record"),
"PHP probe shim must define __nyx_stub_http_record"
);
assert!(
shim.contains("NYX_HTTP_LOG"),
"stub recorder must read NYX_HTTP_LOG"
);
}
#[test]
fn chain_step_splices_probe_shim_for_composite_reverify() {
let step = chain_step(Some(b"<prev>"));

View file

@ -92,6 +92,7 @@ fn chain_step(prev_output: Option<&[u8]>) -> ChainStepHarness {
)]
})
.unwrap_or_default(),
extra_files: Vec::new(),
}
}
@ -382,6 +383,29 @@ def __nyx_stub_sql_record(query, **detail):
_f.write('\n')
except OSError:
pass
# Phase 10 (Track D.3) HTTP recording helper. When the verifier spawned an
# HttpStub it publishes the side-channel log path through NYX_HTTP_LOG; a
# sink call site whose outbound request never reaches the on-the-wire
# listener (DNS-mocked, network-isolated sandbox, pre-flight check) can
# call this helper to surface the attempted call. Format matches the SQL
# helper so the host-side merger parses both streams identically.
def __nyx_stub_http_record(method, url, body=None, **detail):
import os
p = os.environ.get("NYX_HTTP_LOG")
if not p:
return
try:
with open(p, "a") as _f:
_f.write('# method: %s\n' % str(method))
_f.write('# url: %s\n' % str(url))
if body is not None:
_f.write('# body: %s\n' % str(body))
for k, v in detail.items():
_f.write('# %s: %s\n' % (str(k), str(v)))
_f.write('%s %s\n' % (str(method), str(url)))
except OSError:
pass
"#
}

View file

@ -93,6 +93,7 @@ fn chain_step(prev_output: Option<&[u8]>) -> ChainStepHarness {
)]
})
.unwrap_or_default(),
extra_files: Vec::new(),
}
}

View file

@ -71,24 +71,31 @@ impl LangEmitter for RustEmitter {
/// Phase 26 — Rust chain-step harness.
///
/// Emits a minimal `step.rs` file that reads `NYX_PREV_OUTPUT` and writes
/// it on stdout. The chain composer drives the step with `rustc step.rs`
/// (single-file build) — full Cargo crate scaffolding is reserved for
/// chain members whose underlying finding already produced a HarnessSpec
/// via the standard emit path.
/// Splices the Rust probe shim ([`probe_shim`]) in front of a minimal
/// driver that reads `NYX_PREV_OUTPUT` and writes it on stdout. The
/// shim references `libc::*` from its `__nyx_install_crash_guard`
/// definition, so a single-file `rustc step.rs` build cannot resolve
/// the symbols. Instead the step ships a companion `Cargo.toml`
/// pinning `libc = "0.2"` via [`ChainStepHarness::extra_files`] and
/// drives the build through `cargo run --quiet`.
fn chain_step(prev_output: Option<&[u8]>) -> ChainStepHarness {
let source = "use std::env;\nuse std::io::{self, Write};\n\nfn main() {\n let prev = env::var(\"NYX_PREV_OUTPUT\").unwrap_or_default();\n let _ = io::stdout().write_all(prev.as_bytes());\n}\n".to_owned();
// Shell-wrap build + run so the step actually executes the compiled binary.
// `ChainStepHarness.command` models a single process; without the wrap the
// step ends after `rustc` exits and the next chain member sees no output.
let shim = probe_shim();
let driver = "use std::env;\nuse std::io::{self, Write};\n\nfn main() {\n let prev = env::var(\"NYX_PREV_OUTPUT\").unwrap_or_default();\n let _ = io::stdout().write_all(prev.as_bytes());\n}\n";
let source = format!("{shim}\n{driver}");
let cargo_toml = "[package]\n\
name = \"nyx-chain-step\"\n\
version = \"0.0.1\"\n\
edition = \"2021\"\n\n\
[[bin]]\n\
name = \"step\"\n\
path = \"step.rs\"\n\n\
[dependencies]\n\
libc = \"0.2\"\n"
.to_owned();
ChainStepHarness {
source,
filename: "step.rs".to_owned(),
command: vec![
"sh".to_owned(),
"-c".to_owned(),
"rustc step.rs -o step && ./step".to_owned(),
],
command: vec!["cargo".to_owned(), "run".to_owned(), "--quiet".to_owned()],
extra_env: prev_output
.map(|bytes| {
vec![(
@ -97,6 +104,7 @@ fn chain_step(prev_output: Option<&[u8]>) -> ChainStepHarness {
)]
})
.unwrap_or_default(),
extra_files: vec![("Cargo.toml".to_owned(), cargo_toml)],
}
}
@ -878,4 +886,67 @@ mod tests {
let _ = generate_cargo_toml(Cap::CODE_EXEC);
let _ = generate_cargo_toml(Cap::SSRF);
}
#[test]
fn chain_step_splices_probe_shim_for_composite_reverify() {
// Phase 26 follow-up: Rust chain_step now splices the probe
// shim ahead of the driver so a chain step that terminates at
// a sink can drive the `__nyx_probe` channel directly. The
// shim references `libc::*` so the step also ships a companion
// `Cargo.toml` via `extra_files` and drives the build through
// `cargo run --quiet` rather than single-file `rustc`.
let step = chain_step(Some(b"prev-output"));
assert!(
step.source.contains("__nyx_probe shim (Phase 06"),
"probe_shim banner missing from chain step source",
);
assert!(
step.source.contains("fn __nyx_install_crash_guard("),
"install_crash_guard missing from chain step source",
);
let shim_pos = step
.source
.find("__nyx_probe shim (Phase 06")
.expect("shim banner");
let main_pos = step.source.find("fn main()").expect("main fn");
assert!(
shim_pos < main_pos,
"shim must be spliced before fn main(): shim={shim_pos} main={main_pos}",
);
assert_eq!(step.filename, "step.rs");
assert_eq!(
step.command,
vec!["cargo".to_owned(), "run".to_owned(), "--quiet".to_owned()],
);
assert!(
step.extra_env
.iter()
.any(|(k, v)| k == ChainStepHarness::PREV_OUTPUT_ENV && v == "prev-output"),
"prev_output must be threaded through extra_env, got {:?}",
step.extra_env,
);
}
#[test]
fn chain_step_emits_cargo_toml_with_libc_dep() {
let step = chain_step(None);
let cargo = step
.extra_files
.iter()
.find(|(n, _)| n == "Cargo.toml")
.expect("Cargo.toml must be in extra_files for cargo run");
let body = &cargo.1;
assert!(
body.contains("libc = \"0.2\""),
"Cargo.toml must pin libc for the probe shim's sigaction path, got: {body}",
);
assert!(
body.contains("path = \"step.rs\""),
"[[bin]] must point at step.rs so cargo run picks it up, got: {body}",
);
assert!(
body.contains("edition = \"2021\""),
"Cargo.toml must declare edition 2021, got: {body}",
);
}
}