Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 23 additions & 0 deletions dsl/test/claim/filesystem_write_closure_scale_witness.dag
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
module test.claim.filesystem_write_closure_scale_witness

import extdeps.filesystem.filesystem_io
import std.resources { ResourceHandle }

data filesystem_write_closure_scale_anchor: ResourceHandle = ResourceHandle {
type: "filesystem",
resource_id: "closure-scale-witness",
key: "scale",
cap: ""
}

func witness_filesystem_write_survives_name_collision() -> Bool {
let target = "/tmp/gunbc_fs_closure_scale_witness.txt"
let payload = "filesystem service survives the std.resources name collision\n"
let w = Filesystem.Write(path: target, content: payload)
let r = Filesystem.Read(path: target)
return w.success && (w.path == target) && r.success && (r.content == payload)
}

func filesystem_write_closure_scale_holds() -> Bool {
return witness_filesystem_write_survives_name_collision()
}
41 changes: 22 additions & 19 deletions src/v1/stage0/src/v1_interpreter.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ use im_rc::Vector as RrbVector;
use crate::std_syntax::BinOp;
use crate::std_syntax::LiteralValue;
use crate::v1_compiler_emit::{extract_string_interp_parts, has_mock_prefix};
use crate::v1_compiler_infer_items::{ItemInfo, ItemKind, ResolvedGraph, TypedModule};
use crate::v1_compiler_infer_items::{item_kind, ItemInfo, ItemKind, ResolvedGraph, TypedModule};
use crate::v1_rt;
use crate::v1_rt::{
rc_empty_set as empty_set, rc_set_insert as set_insert, rc_set_union as set_union, set_contains,
Expand Down Expand Up @@ -1007,25 +1007,28 @@ impl InterpContext {
if !name.is_empty() {
fn_nodes.insert(name.clone(), item.clone());
}
if let Some(info) = graph.item_registry.get(&name) {
if info.kind == ItemKind::ServiceItem {
for op in item.children.iter() {
let op_name = authored_name_at(source_indices.clone(), op.clone());
if !op_name.is_empty() {
let key = format!("{}.{}", name, op_name);
service_ops.insert(key, (item.clone(), op.clone()));
}
// Service-item detection is node-local: the item node carries the
// `transport` that *defines* it as a service, so `item_kind` of the
// node itself is the single authority. Do NOT gate on a name-keyed
// `item_registry` lookup — two top-level items can share one authored
// name (the `std.resources` `resource Filesystem` is an OtherItem;
// the `extdeps.filesystem` `service Filesystem` is a ServiceItem), and
// once both land in the same import closure the non-service entry can
// win the registry merge and poison the lookup, silently dropping the
// service's operations (-> "unknown service operation" at runtime).
if item_kind(item.clone()) == ItemKind::ServiceItem {
for op in item.children.iter() {
let op_name = authored_name_at(source_indices.clone(), op.clone());
if op_name.is_empty() {
continue;
}
}
}
if let Some(info) = graph.item_registry.get(&item.name) {
if info.kind == ItemKind::ServiceItem && !item.name.is_empty() {
for op in item.children.iter() {
let op_name = authored_name_at(source_indices.clone(), op.clone());
if !op_name.is_empty() {
let key = format!("{}.{}", item.name, op_name);
service_ops.insert(key, (item.clone(), op.clone()));
}
if !name.is_empty() {
let key = format!("{}.{}", name, op_name);
service_ops.insert(key, (item.clone(), op.clone()));
}
if !item.name.is_empty() && item.name != name {
let key = format!("{}.{}", item.name, op_name);
service_ops.insert(key, (item.clone(), op.clone()));
}
}
}
Expand Down
69 changes: 69 additions & 0 deletions src/v1/tests/src/interp_recorded_fixture_test.rs
Original file line number Diff line number Diff line change
Expand Up @@ -131,6 +131,75 @@ fn filesystem_write_witness_record_then_hermetic_replay_holds() {
);
}

// Regression: a large import closure that pulls BOTH the `extdeps.filesystem`
// `service Filesystem` and the `std.resources` `resource Filesystem` used to drop
// the service's operations at runtime (-> "unknown service operation:
// Filesystem.Write"), because runtime service-op registration gated on a
// name-keyed `item_registry` lookup that the same-named non-service entry won the
// merge of. The record phase below is wet (live I/O) and exercises that
// registration; pre-fix it fails at record time, post-fix it records then replays.
fn closure_scale_witness_entry(ws: &Path, scratch: &Path) -> PathBuf {
let src =
fs::read_to_string(ws.join("dsl/test/claim/filesystem_write_closure_scale_witness.dag"))
.expect("read closure-scale witness dag");
let live = scratch.join("fs_closure_scale_witness.txt");
let rewritten = src.replace(
"/tmp/gunbc_fs_closure_scale_witness.txt",
live.to_str().expect("utf8 scratch path"),
);
let entry = scratch.join("filesystem_write_closure_scale_witness.dag");
fs::write(&entry, rewritten).expect("write rewritten closure-scale witness dag");
entry
}

#[test]
fn filesystem_write_closure_scale_record_then_hermetic_replay_holds() {
let ws = workspace_root();
let store_dir = fixture_store_dir("fs-closure-scale-record-replay");
fs::create_dir_all(&store_dir).expect("fixture dir");
let entry = closure_scale_witness_entry(&ws, &store_dir);

let record = run_claim_batch(&[
"--source-root",
ws.to_str().expect("workspace"),
"--source-root",
ws.join("dsl").to_str().expect("dsl root"),
"--entry",
entry.to_str().expect("entry"),
"--function",
"filesystem_write_closure_scale_holds",
"--record",
"--fixture-store",
store_dir.to_str().expect("store path"),
]);
assert!(
record.status.success(),
"closure-scale record capture must pass: the Filesystem service must register \
even when std.resources' resource Filesystem is in the same import closure; stderr={}",
String::from_utf8_lossy(&record.stderr)
);

let hermetic = run_claim_batch(&[
"--source-root",
ws.to_str().expect("workspace"),
"--source-root",
ws.join("dsl").to_str().expect("dsl root"),
"--entry",
entry.to_str().expect("entry"),
"--function",
"filesystem_write_closure_scale_holds",
"--hermetic",
"--fixture-store",
store_dir.to_str().expect("store path"),
]);
let _ = fs::remove_dir_all(&store_dir);
assert!(
hermetic.status.success(),
"hermetic replay of the closure-scale witness must pass from recorded fixtures; stderr={}",
String::from_utf8_lossy(&hermetic.stderr)
);
}

#[test]
fn hermetic_fixture_staleness_fails_closed() {
let ws = workspace_root();
Expand Down
Loading