Skip to content

Commit 361dae1

Browse files
committed
fix(warp-core): refuse create-from-absence against an orphaned attachment
Two review findings, same root cause: GraphStore::set_node_attachment never requires the owning node to exist, so an attachment present with no NodeRecord (an orphan) is a real reachable state, not hypothetical. Neither admission nor evaluation independently checked for it when the node itself was absent. - current_application_basis (Codex, P2): derived "absent" from node presence, so it never inspected the attachment when the node was missing. A create-from-absence invocation dishonestly claiming absence against a real orphan was admitted. Fixed by deriving absence from the attachment slot alone -- the only thing this primitive actually mutates -- so an orphan corroborates as present, using its real value, exactly like the ordinary case. - prepare_operation_v1's create branch (CodeRabbit, Major): built the atomic create ops without checking whether the attachment slot was already occupied, since node absence doesn't imply attachment absence. A caller with a basis honestly reflecting an orphan's real present value (passing the now-fixed admission check) could still reach evaluation and silently overwrite it. Fixed by checking store.node_attachment(&node.local_id).is_some() before proceeding, refusing with PreconditionMismatch -- creation is atomic over both slots or it refuses, per ADR 0024. New fixture_host_with_orphan_attachment helper backs two tests, one per layer: admission refuses a dishonest absence claim with BasisMismatch; evaluation refuses (and leaves the orphan untouched) when the claimed basis honestly reflects the orphan's real value, isolating the evaluation-time check specifically. The two fixes had to land in that order -- before the admission fix, the evaluation test's premise (an honest "present" basis matching what admission computes) wasn't reachable, since old admission unconditionally computed "absent" whenever the node was missing. 22 tests pass (was 20); full pipeline suite green.
1 parent eadf13b commit 361dae1

2 files changed

Lines changed: 226 additions & 14 deletions

File tree

crates/warp-core/src/echo_operation.rs

Lines changed: 29 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -2029,20 +2029,25 @@ fn current_application_basis(
20292029
let store = state
20302030
.store(&invocation.node.warp_id)
20312031
.ok_or_else(|| basis_mismatch("application-basis warp is unavailable"))?;
2032-
// A node that does not exist, or that exists without its alpha
2033-
// attachment, corroborates as absence (ADR 0024): a
2034-
// create-from-absence invocation's claimed application basis is
2035-
// then judged, like any other, by the equality check in
2036-
// `admit_invocation_v1` -- a claim of absence matches only real
2037-
// absence, exactly as a claim of a value matches only that exact
2038-
// value. The atomic-or-refuse distinction between "node absent"
2039-
// and "node exists without its attachment" belongs to
2040-
// `prepare_operation_v1`, not to this coarse admission check.
2041-
let attachment = match store.node(&invocation.node.local_id) {
2042-
None => None,
2043-
Some(_) => store.node_attachment(&invocation.node.local_id),
2044-
};
2045-
let Some(attachment) = attachment else {
2032+
// Absence corroborates from the attachment slot alone, never from
2033+
// node presence (ADR 0024, PR #686 review finding #2): a node
2034+
// that does not exist, or that exists without its alpha
2035+
// attachment, both corroborate as absence, but an *attachment*
2036+
// that exists -- even orphaned, with no owning `NodeRecord`,
2037+
// which `GraphStore::set_node_attachment` never forbids -- must
2038+
// corroborate as present, using its real value, exactly like the
2039+
// ordinary case below. Deriving "absent" from node presence
2040+
// instead of attachment presence would let a claimed-absent
2041+
// invocation match an orphaned attachment's slot without ever
2042+
// inspecting it. A create-from-absence invocation's claimed
2043+
// application basis is then judged, like any other, by the
2044+
// equality check in `admit_invocation_v1` -- a claim of absence
2045+
// matches only real absence, exactly as a claim of a value
2046+
// matches only that exact value. The atomic-or-refuse
2047+
// distinction between "node absent" and "node exists without its
2048+
// attachment" belongs to `prepare_operation_v1`, not to this
2049+
// coarse admission check.
2050+
let Some(attachment) = store.node_attachment(&invocation.node.local_id) else {
20462051
// No local budget guard is needed here: `admit_invocation_v1`
20472052
// already requires `invocation.delegated_budget.read_bytes >=
20482053
// installed.program.minimum_budget().read_bytes` (64 for this
@@ -2399,6 +2404,16 @@ pub(crate) fn prepare_operation_v1(
23992404
return obstruction(EchoOperationObstructionKindV1::BudgetExceeded);
24002405
}
24012406
actual_footprint.a_read.insert(slot);
2407+
// PR #686 review finding #2 (CodeRabbit): the node being
2408+
// absent does not prove the attachment slot is absent
2409+
// too -- `GraphStore::set_node_attachment` never requires
2410+
// the owning node to exist, so an orphaned attachment is
2411+
// a real reachable state, not hypothetical. Creation is
2412+
// atomic over both slots (ADR 0024); refuse rather than
2413+
// silently overwrite an orphan's real value.
2414+
if store.node_attachment(&node.local_id).is_some() {
2415+
return obstruction(EchoOperationObstructionKindV1::PreconditionMismatch);
2416+
}
24022417
let Some(write_bytes) = 32_u64.checked_add(replacement_len) else {
24032418
return obstruction(EchoOperationObstructionKindV1::BudgetExceeded);
24042419
};

crates/warp-core/tests/executable_operation_pipeline_tests.rs

Lines changed: 197 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -230,6 +230,84 @@ fn fixture_host_with_bare_node(
230230
(host, head_key, node, second_node)
231231
}
232232

233+
/// Like [`fixture_host`], but the warp-scoped store also contains an
234+
/// *orphaned* alpha attachment -- present at `orphan_local_id` with no
235+
/// corresponding `NodeRecord` -- reachable because
236+
/// `GraphStore::set_node_attachment` does not require the owning node to
237+
/// exist. Used to exercise create-from-absence's PR #686 review finding #2
238+
/// (CodeRabbit + Codex): a node that is absent must not be treated as fully
239+
/// absent when its attachment slot is independently occupied.
240+
fn fixture_host_with_orphan_attachment(
241+
orphan_local_id: warp_core::NodeId,
242+
orphan_attachment_type: warp_core::TypeId,
243+
orphan_bytes: &[u8],
244+
) -> (TrustedRuntimeHost, WriterHeadKey, NodeKey, NodeKey) {
245+
let warp_id = make_warp_id("operation-fixture");
246+
let node_id = make_node_id("operation-fixture-root");
247+
let node_type = make_type_id("operation-fixture-node");
248+
let attachment_type = make_type_id("operation-fixture-atom");
249+
let node = NodeKey {
250+
warp_id,
251+
local_id: node_id,
252+
};
253+
let orphan_node = NodeKey {
254+
warp_id,
255+
local_id: orphan_local_id,
256+
};
257+
let mut store = GraphStore::new(warp_id);
258+
store.insert_node(node_id, NodeRecord { ty: node_type });
259+
store.set_node_attachment(
260+
node_id,
261+
Some(AttachmentValue::Atom(AtomPayload::new(
262+
attachment_type,
263+
Bytes::from_static(b"before"),
264+
))),
265+
);
266+
store.set_node_attachment(
267+
orphan_local_id,
268+
Some(AttachmentValue::Atom(AtomPayload::new(
269+
orphan_attachment_type,
270+
Bytes::copy_from_slice(orphan_bytes),
271+
))),
272+
);
273+
let state = WorldlineState::from_root_store(store, node_id)
274+
.expect("the fixture state has one lawful root");
275+
let worldline_id = WorldlineId::from_bytes(digest("operation-fixture-worldline"));
276+
let head_key = WriterHeadKey {
277+
worldline_id,
278+
head_id: make_head_id("operation-fixture-writer"),
279+
};
280+
let mut runtime = WorldlineRuntime::new();
281+
runtime
282+
.register_worldline(worldline_id, state)
283+
.expect("the fixture worldline registers");
284+
runtime
285+
.register_writer_head(WriterHead::with_routing(
286+
head_key,
287+
PlaybackMode::Play,
288+
InboxPolicy::AcceptAll,
289+
None,
290+
true,
291+
))
292+
.expect("the fixture writer registers");
293+
294+
let mut engine_store = GraphStore::default();
295+
let engine_root = make_node_id("root");
296+
engine_store.insert_node(
297+
engine_root,
298+
NodeRecord {
299+
ty: make_type_id("world"),
300+
},
301+
);
302+
let engine = EngineBuilder::new(engine_store, engine_root)
303+
.scheduler(SchedulerKind::Radix)
304+
.workers(1)
305+
.build();
306+
let host = TrustedRuntimeHost::new(runtime, engine)
307+
.expect("the trusted Echo runtime host initializes");
308+
(host, head_key, node, orphan_node)
309+
}
310+
233311
fn semantic_closure() -> EchoOperationSemanticClosureV1 {
234312
EchoOperationSemanticClosureV1::new(
235313
digest("fixture-edict-source"),
@@ -2156,3 +2234,122 @@ fn filesystem_wal_recovers_a_create_from_absence_commit() {
21562234
)))
21572235
);
21582236
}
2237+
2238+
/// Regression for PR #686 review finding #2 (CodeRabbit, P0): create-from-
2239+
/// absence must not silently overwrite an orphaned attachment (present with
2240+
/// no owning `NodeRecord` -- reachable because `GraphStore::set_node_attachment`
2241+
/// never requires the node to exist). Exercised with a basis that honestly
2242+
/// reflects the orphan's real present value, so admission passes and the
2243+
/// evaluation-time check in `prepare_operation_v1` is isolated as the thing
2244+
/// under test.
2245+
#[test]
2246+
fn create_from_absence_refuses_when_the_attachment_exists_without_its_node() {
2247+
let orphan_local_id = make_node_id("operation-fixture-orphan-attachment");
2248+
let orphan_attachment_type = make_type_id("operation-fixture-atom");
2249+
let (mut host, head_key, _existing_node, orphan_node) =
2250+
fixture_host_with_orphan_attachment(orphan_local_id, orphan_attachment_type, b"orphaned");
2251+
let installed = install_fixture_operation(&mut host);
2252+
2253+
let application_basis = warp_core::echo_operation_anchored_node_application_basis_v1(
2254+
orphan_node,
2255+
orphan_attachment_type,
2256+
b"orphaned",
2257+
);
2258+
let evaluation_basis = host
2259+
.echo_operation_evaluation_basis_v1(head_key, application_basis)
2260+
.expect("Echo resolves the exact current parent basis");
2261+
let invocation = EchoOperationInvocationV1::anchored_node_attachment_compare_and_set(
2262+
installed.package_id(),
2263+
installed.operation_coordinate(),
2264+
evaluation_basis,
2265+
digest("fixture-authority-grant"),
2266+
EchoOperationBudgetV1::new(16, 4_096, 4_096),
2267+
orphan_node,
2268+
None,
2269+
b"clobbered".to_vec(),
2270+
);
2271+
let invocation_bytes = invocation
2272+
.to_canonical_bytes()
2273+
.expect("the create-from-absence invocation is canonical");
2274+
let invocation_policy = EchoOperationInvocationAdmissionPolicyV1::new(
2275+
digest("fixture-authority-profile"),
2276+
digest("fixture-authority-grant"),
2277+
EchoOperationBudgetV1::new(16, 4_096, 4_096),
2278+
);
2279+
let admitted_invocation = host
2280+
.admit_echo_operation_invocation_v1(&invocation_policy, &invocation_bytes)
2281+
.expect(
2282+
"Echo admits the invocation -- the orphaned attachment honestly \
2283+
corroborates as present, matching this invocation's claimed basis",
2284+
);
2285+
let preparation = host.prepare_echo_operation_v1(admitted_invocation);
2286+
let EchoOperationPreparationV1::Obstructed(obstruction) = preparation else {
2287+
panic!("create-from-absence against an orphaned attachment must not prepare a patch");
2288+
};
2289+
assert_eq!(
2290+
obstruction.kind(),
2291+
EchoOperationObstructionKindV1::PreconditionMismatch
2292+
);
2293+
2294+
let state = host
2295+
.runtime()
2296+
.worldlines()
2297+
.get(&head_key.worldline_id)
2298+
.expect("the untouched worldline remains registered")
2299+
.state();
2300+
assert_eq!(
2301+
state
2302+
.store(&orphan_node.warp_id)
2303+
.and_then(|store| store.node_attachment(&orphan_node.local_id)),
2304+
Some(&AttachmentValue::Atom(AtomPayload::new(
2305+
orphan_attachment_type,
2306+
Bytes::from_static(b"orphaned"),
2307+
))),
2308+
"an obstructed create-from-absence must leave the orphaned attachment untouched"
2309+
);
2310+
}
2311+
2312+
/// Regression for PR #686 review finding #2 (Codex, P2): admission's
2313+
/// independent application-basis corroboration must not treat a node with an
2314+
/// orphaned attachment as absent. A create-from-absence invocation that
2315+
/// (incorrectly) claims absence against a real orphan must be refused at
2316+
/// admission with `BasisMismatch`, not let through to evaluation.
2317+
#[test]
2318+
fn create_from_absence_invocation_refuses_at_admission_against_an_orphaned_attachment() {
2319+
let orphan_local_id = make_node_id("operation-fixture-orphan-admission");
2320+
let orphan_attachment_type = make_type_id("operation-fixture-atom");
2321+
let (mut host, head_key, _existing_node, orphan_node) =
2322+
fixture_host_with_orphan_attachment(orphan_local_id, orphan_attachment_type, b"orphaned");
2323+
let installed = install_fixture_operation(&mut host);
2324+
2325+
let claimed_absent_basis =
2326+
warp_core::echo_operation_anchored_node_absent_application_basis_v1(orphan_node);
2327+
let evaluation_basis = host
2328+
.echo_operation_evaluation_basis_v1(head_key, claimed_absent_basis)
2329+
.expect("Echo resolves a basis for the claimed proposition");
2330+
let invocation = EchoOperationInvocationV1::anchored_node_attachment_compare_and_set(
2331+
installed.package_id(),
2332+
installed.operation_coordinate(),
2333+
evaluation_basis,
2334+
digest("fixture-authority-grant"),
2335+
EchoOperationBudgetV1::new(16, 4_096, 4_096),
2336+
orphan_node,
2337+
None,
2338+
b"clobbered".to_vec(),
2339+
);
2340+
let invocation_bytes = invocation
2341+
.to_canonical_bytes()
2342+
.expect("the create-from-absence invocation is canonical");
2343+
let invocation_policy = EchoOperationInvocationAdmissionPolicyV1::new(
2344+
digest("fixture-authority-profile"),
2345+
digest("fixture-authority-grant"),
2346+
EchoOperationBudgetV1::new(16, 4_096, 4_096),
2347+
);
2348+
let error = host
2349+
.admit_echo_operation_invocation_v1(&invocation_policy, &invocation_bytes)
2350+
.expect_err("a dishonest absence claim against a real orphaned attachment must refuse");
2351+
assert_eq!(
2352+
error.kind(),
2353+
EchoOperationInvocationAdmissionErrorKindV1::BasisMismatch
2354+
);
2355+
}

0 commit comments

Comments
 (0)