Public dry run dryrun-f1e9b7b784b4

Source commit: f1e9b7b784b4a4dd28799719681535e2a4ef44c9
This commit is contained in:
Michel Paulissen 2026-07-03 18:54:07 +02:00
parent 20c72e6066
commit 8c0c336ae9
5 changed files with 1829 additions and 20 deletions

View file

@ -1,7 +1,7 @@
{ {
"kind": "disasmer-filtered-public-tree", "kind": "disasmer-filtered-public-tree",
"source_commit": "1714a9eedd5b1e30c6c9aceb7a999f2f695ba83c", "source_commit": "f1e9b7b784b4a4dd28799719681535e2a4ef44c9",
"release_name": "dryrun-1714a9eedd5b", "release_name": "dryrun-f1e9b7b784b4",
"filtered_out": [ "filtered_out": [
"private/**", "private/**",
"experiments/**", "experiments/**",

File diff suppressed because it is too large Load diff

View file

@ -305,7 +305,7 @@ fn launch_threads(entry: &str) -> BTreeMap<i64, VirtualThread> {
thread(MAIN_THREAD, "main", &format!("{entry} virtual process"), 12), thread(MAIN_THREAD, "main", &format!("{entry} virtual process"), 12),
thread(LINUX_THREAD, "compile-linux", "compile linux", 42), thread(LINUX_THREAD, "compile-linux", "compile linux", 42),
thread(WINDOWS_THREAD, "compile-windows", "compile windows", 52), thread(WINDOWS_THREAD, "compile-windows", "compile windows", 52),
thread(PACKAGE_THREAD, "package", "package artifacts", 64), thread(PACKAGE_THREAD, "package-release", "package artifacts", 64),
] ]
.into_iter() .into_iter()
.map(|thread| (thread.id, thread)) .map(|thread| (thread.id, thread))
@ -1428,6 +1428,7 @@ fn parse_let_binding_name(line: &str) -> Option<String> {
#[derive(Clone, Debug, PartialEq, Eq)] #[derive(Clone, Debug, PartialEq, Eq)]
enum SourceLocalRuntimeValue { enum SourceLocalRuntimeValue {
SourceSnapshot(String),
TaskHandle { TaskHandle {
task: TaskId, task: TaskId,
thread_id: i64, thread_id: i64,
@ -1441,6 +1442,9 @@ enum SourceLocalRuntimeValue {
impl SourceLocalRuntimeValue { impl SourceLocalRuntimeValue {
fn display(&self, _state: &AdapterState) -> String { fn display(&self, _state: &AdapterState) -> String {
match self { match self {
SourceLocalRuntimeValue::SourceSnapshot(digest) => {
format!("SourceSnapshot {{ digest = \"{digest}\" }}")
}
SourceLocalRuntimeValue::TaskHandle { SourceLocalRuntimeValue::TaskHandle {
task, task,
thread_id, thread_id,
@ -1462,7 +1466,18 @@ fn infer_disasmer_source_local_value(
statement: &str, statement: &str,
runtime_values: &BTreeMap<String, SourceLocalRuntimeValue>, runtime_values: &BTreeMap<String, SourceLocalRuntimeValue>,
) -> Option<SourceLocalRuntimeValue> { ) -> Option<SourceLocalRuntimeValue> {
if let Some(task_function) = extract_call_argument(statement, "disasmer::spawn::task(") { if statement.contains("prepare_source_snapshot()") {
return Some(SourceLocalRuntimeValue::SourceSnapshot(
source_snapshot_digest_from_project(state).unwrap_or_else(|| {
format!(
"source://local-checkout/{}",
project_snapshot_suffix(&state.project)
)
}),
));
}
if let Some(task_function) = extract_spawn_task_function(statement) {
let task_name = extract_string_argument(statement, ".name("); let task_name = extract_string_argument(statement, ".name(");
let spawned_thread = let spawned_thread =
thread_for_spawn_statement(state, &task_function, task_name.as_deref())?; thread_for_spawn_statement(state, &task_function, task_name.as_deref())?;
@ -1498,12 +1513,83 @@ fn infer_disasmer_source_local_value(
None None
} }
fn extract_call_argument(statement: &str, marker: &str) -> Option<String> { fn extract_spawn_task_function(statement: &str) -> Option<String> {
if let Some(args) = extract_call_arguments(statement, "disasmer::spawn::task(") {
return args.first().cloned();
}
let args = extract_call_arguments(statement, "disasmer::spawn::task_with_arg(")?;
args.get(1).cloned()
}
fn source_snapshot_digest_from_project(state: &AdapterState) -> Option<String> {
let source =
fs::read_to_string(resolve_source_path(&state.project, &state.source_path)).ok()?;
let digest_marker = source.find("digest:")?;
let after_digest = &source[digest_marker..];
let first_quote = after_digest.find('"')? + 1;
let rest = &after_digest[first_quote..];
let end_quote = rest.find('"')?;
Some(rest[..end_quote].to_owned())
}
fn extract_call_arguments(statement: &str, marker: &str) -> Option<Vec<String>> {
let start = statement.find(marker)? + marker.len(); let start = statement.find(marker)? + marker.len();
let rest = &statement[start..]; let rest = &statement[start..];
let end = rest.find(')')?; let mut args = Vec::new();
let value = rest[..end].trim(); let mut current = String::new();
(!value.is_empty()).then_some(value.to_owned()) let mut depth = 0_i32;
let mut in_string = false;
let mut escaped = false;
for ch in rest.chars() {
if in_string {
current.push(ch);
if escaped {
escaped = false;
} else if ch == '\\' {
escaped = true;
} else if ch == '"' {
in_string = false;
}
continue;
}
match ch {
'"' => {
in_string = true;
current.push(ch);
}
'(' | '[' | '{' => {
depth += 1;
current.push(ch);
}
')' => {
if depth == 0 {
let value = current.trim();
if !value.is_empty() {
args.push(value.to_owned());
}
return (!args.is_empty()).then_some(args);
}
depth -= 1;
current.push(ch);
}
']' | '}' => {
depth -= 1;
current.push(ch);
}
',' if depth == 0 => {
let value = current.trim();
if !value.is_empty() {
args.push(value.to_owned());
}
current.clear();
}
_ => current.push(ch),
}
}
None
} }
fn extract_string_argument(statement: &str, marker: &str) -> Option<String> { fn extract_string_argument(statement: &str, marker: &str) -> Option<String> {
@ -1518,9 +1604,15 @@ fn extract_env_name(statement: &str) -> Option<String> {
if statement.contains("windows_env") || statement.contains("env!(\"windows\")") { if statement.contains("windows_env") || statement.contains("env!(\"windows\")") {
return Some("windows".to_owned()); return Some("windows".to_owned());
} }
if statement.contains("linux_command_env") || statement.contains("env!(\"linux-command\")") {
return Some("linux-command".to_owned());
}
if statement.contains("linux_env") || statement.contains("env!(\"linux\")") { if statement.contains("linux_env") || statement.contains("env!(\"linux\")") {
return Some("linux".to_owned()); return Some("linux".to_owned());
} }
if statement.contains("coordinator_env") || statement.contains("env!(\"coordinator\")") {
return Some("coordinator".to_owned());
}
None None
} }
@ -1552,25 +1644,27 @@ fn thread_for_spawn_statement<'a>(
} }
} }
let normalized = task_function.replace('_', "-"); let normalized = task_function.replace('_', "-");
state state.threads.values().find(|thread| {
.threads thread.task.as_str() == normalized
.values() || (thread.id == PACKAGE_THREAD && normalized == "package-release")
.find(|thread| thread.task.as_str() == normalized) })
} }
fn task_environment(thread: &VirtualThread) -> &'static str { fn task_environment(thread: &VirtualThread) -> &'static str {
match thread.id { match thread.id {
WINDOWS_THREAD => "windows", WINDOWS_THREAD => "windows",
MAIN_THREAD => "coordinator", MAIN_THREAD => "coordinator",
PACKAGE_THREAD => "coordinator",
_ => "linux", _ => "linux",
} }
} }
fn task_arguments_value(thread: &VirtualThread) -> String { fn task_arguments_value(thread: &VirtualThread) -> String {
if thread.task.as_str() == "task_add_one" { match thread.task.as_str() {
"[input: i32]".to_owned() "compile-linux" => "[source: SourceSnapshot]".to_owned(),
} else { "package-release" => "[inputs: Vec<Artifact>]".to_owned(),
"[]".to_owned() "task_add_one" => "[input: i32]".to_owned(),
_ => "[]".to_owned(),
} }
} }
@ -2224,6 +2318,7 @@ fn read_message<R: BufRead>(reader: &mut R) -> Result<Option<Value>> {
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use std::fs;
use std::io::Cursor; use std::io::Cursor;
use serde_json::json; use serde_json::json;
@ -2349,7 +2444,7 @@ mod tests {
let error = freeze_all(&mut state, LINUX_THREAD, None).unwrap_err(); let error = freeze_all(&mut state, LINUX_THREAD, None).unwrap_err();
assert_eq!(error.thread_id, PACKAGE_THREAD); assert_eq!(error.thread_id, PACKAGE_THREAD);
assert_eq!(error.task, TaskId::from("package")); assert_eq!(error.task, TaskId::from("package-release"));
assert!(error.message().contains("could not freeze")); assert!(error.message().contains("could not freeze"));
assert_eq!(state.epoch, 0); assert_eq!(state.epoch, 0);
assert!(state assert!(state
@ -2541,6 +2636,119 @@ mod tests {
})); }));
} }
#[test]
fn source_locals_infer_task_with_arg_values_from_runtime_state() {
let project =
std::env::temp_dir().join(format!("disasmer-dap-task-with-arg-{}", std::process::id()));
let src = project.join("src");
fs::create_dir_all(&src).unwrap();
fs::write(
src.join("main.rs"),
r#"use disasmer::{Artifact, EnvRef, SourceSnapshot};
async fn build_release() -> Result<(), disasmer::TaskArgError> {
let source = prepare_source_snapshot();
let compile = disasmer::spawn::task_with_arg(source.clone(), compile_linux)
.name("compile linux")
.env(linux_command_env())
.start()
.await?;
let compile_thread = compile.virtual_thread_id();
let linux_artifact = compile.join().await;
let package = disasmer::spawn::task_with_arg(vec![linux_artifact.clone()], package_release)
.name("package release")
.env(coordinator_env())
.start()
.await?;
let package_thread = package.virtual_thread_id();
let release_artifact = package.join().await;
Ok(())
}
fn prepare_source_snapshot() -> SourceSnapshot {
SourceSnapshot {
digest: "source://coordinator/quick-test-checkout".to_owned(),
}
}
fn linux_command_env() -> EnvRef {
disasmer::env!("linux-command")
}
fn coordinator_env() -> EnvRef {
disasmer::env!("coordinator")
}
fn compile_linux(source: SourceSnapshot) -> Artifact {
Artifact { id: source.digest }
}
fn package_release(inputs: Vec<Artifact>) -> Artifact {
inputs.into_iter().next().unwrap()
}
"#,
)
.unwrap();
let mut state = AdapterState::default();
state.project = project.to_string_lossy().into_owned();
state.source_path = "src/main.rs".to_owned();
state.threads.get_mut(&MAIN_THREAD).unwrap().line = 23;
let thread = state.threads[&MAIN_THREAD].clone();
let locals = variables_response(&state, thread.locals_ref);
let locals = locals["variables"].as_array().unwrap();
assert!(locals.iter().any(|variable| {
variable["name"] == "source"
&& variable["value"]
.as_str()
.is_some_and(|value| value.contains("source://coordinator/quick-test-checkout"))
}));
assert!(locals.iter().any(|variable| {
variable["name"] == "compile"
&& variable["value"].as_str().is_some_and(|value| {
value.contains("TaskHandle")
&& value.contains("compile-linux")
&& value.contains("virtual_thread_id = 2")
&& value.contains("linux-command")
})
}));
assert!(locals
.iter()
.any(|variable| variable["name"] == "compile_thread" && variable["value"] == "2"));
assert!(locals.iter().any(|variable| {
variable["name"] == "linux_artifact"
&& variable["value"]
.as_str()
.is_some_and(|value| value.contains("Artifact"))
}));
assert!(locals.iter().any(|variable| {
variable["name"] == "package"
&& variable["value"].as_str().is_some_and(|value| {
value.contains("TaskHandle")
&& value.contains("package-release")
&& value.contains("virtual_thread_id = 4")
&& value.contains("coordinator")
})
}));
assert!(locals
.iter()
.any(|variable| variable["name"] == "package_thread" && variable["value"] == "4"));
assert!(locals.iter().any(|variable| {
variable["name"] == "release_artifact"
&& variable["value"]
.as_str()
.is_some_and(|value| value.contains("Artifact"))
}));
let _ = fs::remove_dir_all(project);
}
#[test] #[test]
fn wasm_frame_locals_expose_wasmtime_runtime_values() { fn wasm_frame_locals_expose_wasmtime_runtime_values() {
let mut state = AdapterState::default(); let mut state = AdapterState::default();

View file

@ -0,0 +1,109 @@
#!/usr/bin/env node
const assert = require("assert");
const fs = require("fs");
const path = require("path");
const repo = path.resolve(__dirname, "..");
function read(relativePath) {
const fullPath = path.join(repo, relativePath);
if (!fs.existsSync(fullPath)) {
if (fs.existsSync(path.join(repo, "DISASMER_PUBLIC_TREE.json"))) {
console.log(
`CLI-first contract smoke skipped: ${relativePath} is filtered from this public tree`
);
process.exit(0);
}
throw new Error(`${relativePath} is missing`);
}
return fs.readFileSync(fullPath, "utf8");
}
function criterionLines(source) {
return source
.split(/\r?\n/)
.filter((line) => /^- \[[ x]\] \*\*/.test(line));
}
function expect(source, name, pattern) {
assert.match(source, pattern, `missing CLI-first contract evidence: ${name}`);
}
const criteria = read("cli_acceptance_criteria.md");
const cli = read("crates/disasmer-cli/src/main.rs");
expect(criteria, "header", /^# Disasmer CLI-First MVP Acceptance Criteria/m);
expect(
criteria,
"addendum status",
/\*\*Status:\*\* CLI-first addendum to `acceptance_criteria\.md` and `acceptance_criteria_phase2\.md`/
);
expect(
criteria,
"website exception",
/hosted account creation as the only intentional private website exception/
);
expect(
criteria,
"no duplicate work note",
/does not automatically mean new product code, a new feature, or even actual implementation work is required/
);
expect(
criteria,
"future hosted business non-goal",
/billing, full support tooling, broad moderation consoles, and durable account\/business-process management are intentionally outside this CLI-first MVP slice/
);
const lines = criterionLines(criteria);
assert(lines.length > 0, "CLI-first criteria must contain criteria lines");
for (const line of lines) {
assert.match(
line,
/^- \[[ x]\] \*\*(Passed|Partial|Open)(?: \([^)]+\))?:\*\*/,
`CLI-first criterion lacks an explicit status prefix: ${line}`
);
}
const openCriteria = lines.filter((line) => /\*\*Open(?::| \()/.test(line));
assert.deepStrictEqual(
openCriteria,
[
"- [ ] **Open:** Every criterion in this document is validated end-to-end or with integration-style coverage, preferably against a realistic NixOS deployment that can pretend to be live hosted infrastructure.",
],
"only the full CLI-first verification gate should remain Open while command/API facts are Partial"
);
for (const [name, pattern] of [
["top-level version metadata", /#\[command\(name = "disasmer", version, arg_required_else_help = true\)\]/],
["doctor command", /Doctor\(DoctorArgs\)/],
["auth status command", /enum AuthCommands[\s\S]*Status\(AuthStatusArgs\)/],
["auth logout command", /enum AuthCommands[\s\S]*Logout\(AuthLogoutArgs\)/],
["key lifecycle commands", /enum KeyCommands[\s\S]*Add\(KeyAddArgs\)[\s\S]*List\(KeyListArgs\)[\s\S]*Revoke\(KeyRevokeArgs\)/],
["project commands", /enum ProjectCommands[\s\S]*Init\(ProjectInitArgs\)[\s\S]*Status\(ProjectStatusArgs\)[\s\S]*List\(ProjectListArgs\)[\s\S]*Select\(ProjectSelectArgs\)/],
["inspect command", /Inspect\(BundleInspectArgs\)/],
["build command", /Build\(BuildArgs\)/],
["node lifecycle commands", /enum NodeCommands[\s\S]*Attach\(AttachArgs\)[\s\S]*Enroll\(NodeEnrollArgs\)[\s\S]*List\(NodeListArgs\)[\s\S]*Status\(NodeStatusArgs\)[\s\S]*Revoke\(NodeRevokeArgs\)/],
["process commands", /enum ProcessCommands[\s\S]*Status\(ProcessStatusArgs\)[\s\S]*Restart\(ProcessRestartArgs\)[\s\S]*Cancel\(ProcessCancelArgs\)/],
["task list command", /enum TaskCommands[\s\S]*List\(TaskListArgs\)/],
["logs command", /Logs\(LogsArgs\)/],
["artifact commands", /enum ArtifactCommands[\s\S]*List\(ArtifactListArgs\)[\s\S]*Download\(ArtifactDownloadArgs\)[\s\S]*Export\(ArtifactExportArgs\)/],
["DAP command", /Dap\(DapArgs\)/],
["debug attach command", /enum DebugCommands[\s\S]*Attach\(DebugAttachArgs\)/],
["quota command", /enum QuotaCommands[\s\S]*Status\(QuotaStatusArgs\)/],
["admin commands", /enum AdminCommands[\s\S]*Status\(AdminStatusArgs\)[\s\S]*Bootstrap\(AdminBootstrapArgs\)[\s\S]*RevokeNode\(NodeRevokeArgs\)[\s\S]*StopProcess\(ProcessCancelArgs\)[\s\S]*SuspendTenant\(AdminSuspendTenantArgs\)/],
]) {
expect(cli, name, pattern);
}
for (const [name, pattern] of [
["CLI parse coverage", /fn cli_first_mvp_command_surface_parses\(\)/],
["CLI version coverage", /fn top_level_version_is_available\(\)/],
["project local config coverage", /fn project_init_select_and_status_use_local_project_config\(\)/],
["build no full repo upload coverage", /fn build_command_reuses_bundle_inspection_without_full_repo_upload\(\)/],
["safe coordinator-required plans", /fn node_enroll_and_process_commands_have_safe_plan_without_coordinator\(\)/],
]) {
expect(cli, name, pattern);
}
console.log("CLI-first contract smoke passed");

View file

@ -0,0 +1,93 @@
#!/usr/bin/env node
const assert = require("assert");
const fs = require("fs");
const path = require("path");
const repo = path.resolve(__dirname, "..");
const inventoryPath = path.join(repo, "website_mvp_inventory.md");
if (!fs.existsSync(inventoryPath)) {
if (fs.existsSync(path.join(repo, "DISASMER_PUBLIC_TREE.json"))) {
console.log(
"Website inventory contract smoke skipped: website_mvp_inventory.md is filtered from this public tree"
);
process.exit(0);
}
throw new Error("website_mvp_inventory.md is missing");
}
const source = fs.readFileSync(inventoryPath, "utf8");
function expect(name, pattern) {
assert.match(source, pattern, `missing website inventory evidence: ${name}`);
}
function criterionLines() {
return source
.split(/\r?\n/)
.filter((line) => /^- \[[ x]\] \*\*/.test(line));
}
expect("header", /^# Disasmer Minimal Website Inventory/m);
expect(
"acceptance-style status",
/\*\*Status:\*\* working MVP website inventory aligned with `acceptance_criteria\.md`, `acceptance_criteria_phase2\.md`, and `cli_acceptance_criteria\.md`/
);
expect(
"hosted signup exception",
/except hosted Authentik account creation/
);
expect(
"no duplicate work note",
/does not automatically mean new product code, a new feature, or even actual implementation work is required/
);
expect(
"barebones no CSS",
/barebones functional HTML with no CSS/
);
expect(
"future hosted business scope",
/Billing, checkout, hosted support tooling, a full hosted admin console, broad moderation workflows/
);
expect(
"billing is not MVP website work",
/Do not build billing\/upgrade flows into the minimal website for this MVP/
);
assert.doesNotMatch(source, /community-tier/, "use `community tier`, not `community-tier`");
const lines = criterionLines();
assert(lines.length > 0, "website inventory must contain status-prefixed checklist items");
for (const line of lines) {
assert.match(
line,
/^- \[[ x]\] \*\*(Passed|Partial|Open)(?: \([^)]+\))?:\*\*/,
`website inventory item lacks an explicit status prefix: ${line}`
);
}
for (const [name, pattern] of [
["CLI login command", /`disasmer login \[--browser\] \[--coordinator <url>\]`/],
["CLI auth status command", /`disasmer auth status`/],
["CLI key command", /`disasmer key add --public-key <pubkey>`/],
["CLI project init command", /`disasmer project init`/],
["CLI node enroll command", /`disasmer node enroll --coordinator <url> --ttl-seconds <seconds>`/],
["CLI process status command", /`disasmer process status`/],
["CLI task list command", /`disasmer task list`/],
["CLI DAP command", /`disasmer dap`/],
["CLI quota command", /`disasmer quota status`/],
["CLI admin status command", /`disasmer admin status`/],
]) {
expect(name, pattern);
}
for (const [name, pattern] of [
["website default project is open", /- \[ \] \*\*Open:\*\* land in their default project\./],
["website node attach is open", /- \[ \] \*\*Open:\*\* copy a node attach command\./],
["website process view is open", /- \[ \] \*\*Open:\*\* see the single current virtual process\./],
["website logs are open", /- \[ \] \*\*Open:\*\* see recent bounded logs\./],
["website artifact download is open", /- \[ \] \*\*Open:\*\* securely download an available best-effort retained artifact\./],
["website keys are partial through CLI", /- \[ \] \*\*Partial:\*\* manage\/revoke public keys or know the CLI command to do so\./],
]) {
expect(name, pattern);
}
console.log("Website inventory contract smoke passed");