From f438d3282064387112b51bcf2123ff64e91b4039 Mon Sep 17 00:00:00 2001 From: James Ross Date: Mon, 11 May 2026 08:35:55 -0700 Subject: [PATCH 01/12] test(stack): add jedit file-history witness Add Stack Witness 0001 and RED tests for the first jedit-through-Echo walking skeleton. The witness pins: - createBuffer - replaceRange("hello") - textWindow(0..5) => "hello" - EINT-only mutation - QueryView observation - ReadingEnvelope + QueryBytes Also repairs the minimal engine-test harness blockers required for the targeted RED tests to execute. --- crates/warp-wasm/Cargo.toml | 2 +- crates/warp-wasm/src/lib.rs | 11 +- crates/warp-wasm/src/warp_kernel.rs | 110 ++++++++- .../stack-witness-0001-jedit-file-history.md | 217 ++++++++++++++++++ 4 files changed, 329 insertions(+), 11 deletions(-) create mode 100644 docs/design/stack-witness-0001-jedit-file-history.md diff --git a/crates/warp-wasm/Cargo.toml b/crates/warp-wasm/Cargo.toml index 3dac4896..448bd3b3 100644 --- a/crates/warp-wasm/Cargo.toml +++ b/crates/warp-wasm/Cargo.toml @@ -21,7 +21,7 @@ default = [] console-panic = ["console_error_panic_hook", "web-sys"] # Enable the warp-core engine kernel implementation. # Apps compile with this feature to get a real Engine behind the WASM exports. -engine = ["dep:warp-core"] +engine = ["dep:warp-core", "warp-core/native_rule_bootstrap"] [dependencies] echo-registry-api = { workspace = true } diff --git a/crates/warp-wasm/src/lib.rs b/crates/warp-wasm/src/lib.rs index afe63051..2248c021 100644 --- a/crates/warp-wasm/src/lib.rs +++ b/crates/warp-wasm/src/lib.rs @@ -1017,14 +1017,15 @@ mod init_tests { fn neighborhood_core_publication_uses_installed_kernel() { clear_kernel(); install_kernel(Box::new(StubKernel)); - let request = ObservationRequest { - coordinate: kernel_port::ObservationCoordinate { + let request = ObservationRequest::builtin_one_shot( + kernel_port::ObservationCoordinate { worldline_id: WorldlineId::from_bytes([9; 32]), at: ObservationAt::Frontier, }, - frame: ObservationFrame::CommitBoundary, - projection: ObservationProjection::Head, - }; + ObservationFrame::CommitBoundary, + ObservationProjection::Head, + ) + .unwrap(); let core = with_kernel_ref(|k| k.observe_neighborhood_core(request)).unwrap(); assert_eq!(core.outcome_kind, AdmissionOutcomeKind::Derived); assert_eq!(core.plurality, NeighborhoodPlurality::Singleton); diff --git a/crates/warp-wasm/src/warp_kernel.rs b/crates/warp-wasm/src/warp_kernel.rs index a9afd565..5a0e2962 100644 --- a/crates/warp-wasm/src/warp_kernel.rs +++ b/crates/warp-wasm/src/warp_kernel.rs @@ -1089,6 +1089,24 @@ mod tests { } } + const STACK_WITNESS_CREATE_BUFFER_OP_ID: u32 = 0x5357_0001; + const STACK_WITNESS_REPLACE_RANGE_OP_ID: u32 = 0x5357_0002; + const STACK_WITNESS_TEXT_WINDOW_QUERY_ID: u32 = 0x5357_1001; + + fn stack_witness_create_buffer_vars() -> Vec { + b"stack-witness-0001/createBuffer;name=demo.txt;artifact=fixture-file-history-v0".to_vec() + } + + fn stack_witness_replace_range_vars() -> Vec { + b"stack-witness-0001/replaceRange;basis=B0;coord=utf8-bytes;start=0;end=0;text=hello;artifact=fixture-file-history-v0" + .to_vec() + } + + fn stack_witness_text_window_vars() -> Vec { + b"stack-witness-0001/textWindow;basis=B1;coord=utf8-bytes;start=0;length=5;artifact=fixture-file-history-v0" + .to_vec() + } + fn sample_import_suffix_request(kernel: &WarpKernel) -> AbiImportSuffixRequest { let worldline_id = kernel.default_worldline; let base_frontier = abi_provenance_ref(worldline_id, 0, 1); @@ -1615,6 +1633,88 @@ mod tests { assert_eq!(r1.intent_id, r2.intent_id); } + #[test] + fn stack_witness_contract_intent_without_installed_artifact_obstructs() { + let mut kernel = WarpKernel::new().unwrap(); + let intent = pack_intent_v1( + STACK_WITNESS_CREATE_BUFFER_OP_ID, + &stack_witness_create_buffer_vars(), + ) + .unwrap(); + + let error = kernel.dispatch_intent(&intent).expect_err( + "contract-shaped EINT must obstruct until a generated artifact is installed", + ); + + assert_eq!(error.code, error_codes::NOT_SUPPORTED); + assert!( + error.message.contains("contract"), + "obstruction should explain the missing contract artifact" + ); + } + + #[test] + fn stack_witness_text_window_query_returns_reading_envelope_and_query_bytes() { + let mut kernel = WarpKernel::new().unwrap(); + let create_buffer = pack_intent_v1( + STACK_WITNESS_CREATE_BUFFER_OP_ID, + &stack_witness_create_buffer_vars(), + ) + .unwrap(); + let replace_range = pack_intent_v1( + STACK_WITNESS_REPLACE_RANGE_OP_ID, + &stack_witness_replace_range_vars(), + ) + .unwrap(); + + kernel.dispatch_intent(&create_buffer).unwrap(); + start_until_idle(&mut kernel, Some(4)); + kernel.dispatch_intent(&replace_range).unwrap(); + start_until_idle(&mut kernel, Some(4)); + + let request = abi_builtin_one_shot( + AbiObservationCoordinate { + worldline_id: abi_worldline_id(kernel.default_worldline), + at: echo_wasm_abi::kernel_port::ObservationAt::Frontier, + }, + AbiObservationFrame::QueryView, + AbiObservationProjection::Query { + query_id: STACK_WITNESS_TEXT_WINDOW_QUERY_ID, + vars_bytes: stack_witness_text_window_vars(), + }, + ); + + let artifact = kernel + .observe(request) + .expect("textWindow QueryView should return ReadingEnvelope + QueryBytes"); + + let AbiObservationPayload::QueryBytes { data } = artifact.payload else { + panic!("textWindow should return QueryBytes"); + }; + + assert_eq!(data, b"hello"); + assert_eq!( + artifact.reading.observer_basis, + AbiReadingObserverBasis::QueryView + ); + assert_eq!( + artifact.reading.budget_posture, + AbiReadingBudgetPosture::UnboundedOneShot + ); + assert_eq!( + artifact.reading.rights_posture, + AbiReadingRightsPosture::KernelPublic + ); + assert_eq!( + artifact.reading.residual_posture, + AbiReadingResidualPosture::Complete + ); + assert!( + !artifact.artifact_hash.is_empty(), + "reading identity must be carried by the observation artifact" + ); + } + #[test] fn import_suffix_intent_rejects_malformed_payload_without_ingress() { let mut kernel = WarpKernel::new().unwrap(); @@ -2020,14 +2120,14 @@ mod tests { fn observe_neighborhood_core_returns_shared_projection_for_default_worldline() { let kernel = WarpKernel::new().unwrap(); let core = kernel - .observe_neighborhood_core(AbiObservationRequest { - coordinate: AbiObservationCoordinate { + .observe_neighborhood_core(abi_builtin_one_shot( + AbiObservationCoordinate { worldline_id: abi_worldline_id(kernel.default_worldline), at: AbiObservationAt::Frontier, }, - frame: AbiObservationFrame::CommitBoundary, - projection: AbiObservationProjection::Head, - }) + AbiObservationFrame::CommitBoundary, + AbiObservationProjection::Head, + )) .unwrap(); assert_eq!( diff --git a/docs/design/stack-witness-0001-jedit-file-history.md b/docs/design/stack-witness-0001-jedit-file-history.md new file mode 100644 index 00000000..48efe608 --- /dev/null +++ b/docs/design/stack-witness-0001-jedit-file-history.md @@ -0,0 +1,217 @@ + + + +# Stack Witness 0001 - jedit File History Walking Skeleton + +Status: RED witness spec +Scope: first jedit-through-Echo executable story. + +This witness is the first serious stack slice. It exists to prevent the stack +from drifting into schema-first protocol architecture before one local +contract-hosted file history works. + +## Claim + +A jedit-like contract can create a file-like object, edit it through Echo, and +read a bounded text window through Echo without direct runtime mutation or raw +state reads. + +The first story is intentionally small: + +```text +createBuffer(name = "demo.txt") +replaceRange(buffer, basis = B0, start = 0, end = 0, text = "hello") +textWindow(buffer, basis = B1, start = 0, length = 5) +=> "hello" +``` + +## Required Path + +The story must run through the same boundary shape jedit will use: + +```text +fixture-generated or Wesley-generated EINT +-> Echo dispatch_intent +-> Echo admission/provenance +-> Echo observe(QueryView) +-> ReadingEnvelope + QueryBytes +-> jedit consumes result +``` + +The first implementation may use an as-if generated fixture artifact before +Wesley emits the final artifact. The fixture must have the same shape Wesley is +expected to generate: + +- manifest or artifact id; +- operation ids; +- canonical vars bytes; +- declared footprints; +- mutation handler; +- query handler; +- artifact identity. + +## Operation Surface + +### `createBuffer` + +Creates the file-like contract object. + +Minimum variables: + +- `name = "demo.txt"`; +- contract artifact id; +- operation id; +- canonical vars bytes. + +### `replaceRange` + +Appends a range edit as witnessed history. + +Minimum variables: + +- target object id; +- basis ref; +- observed reading id or aperture id when available; +- range coordinate system; +- `start = 0`; +- `end = 0`; +- replacement bytes/text = `"hello"`; +- contract artifact id. + +The first coordinate system is UTF-8 byte offsets. Richer editor coordinates +are later work: + +- Unicode scalar offsets; +- grapheme clusters; +- line and column; +- rope coordinates; +- syntax-aware spans. + +An edit that does not name what it was based on is not admissible for this +witness. + +### `textWindow` + +Reads a bounded aperture. + +Minimum variables: + +- target object id; +- basis ref; +- range coordinate system; +- `start = 0`; +- `length = 5`; +- contract artifact id. + +Expected payload bytes: + +```text +hello +``` + +## ReadingEnvelope Requirements + +The QueryView result must not return naked bytes. It must return +`ReadingEnvelope + QueryBytes`. + +Minimum viable envelope fields or slots: + +- read identity; +- basis ref; +- observer plan or query id; +- contract artifact id; +- vars digest; +- aperture; +- payload digest; +- payload codec; +- witness refs or witness posture; +- budget posture; +- rights posture; +- residual or obstruction posture. + +Some fields may initially be primitive or `not_available`, but the slots must +exist so retention, proof, and debugging do not require later surgery. + +## Receipt Requirements + +Every admitted contract mutation must leave evidence of: + +- contract family id; +- schema or artifact id; +- operation id; +- operation version; +- footprint declaration hash; +- canonical vars digest; +- basis; +- admission outcome; +- receipt id. + +## Echo Core Firewall + +Echo core may host the fixture contract. Echo core must not become the fixture +contract. + +Suspicious names in Echo core: + +```text +jedit +rope +TextBuffer +ReplaceRange +TextWindow +Editor +Cursor +Selection +``` + +Those names are allowed in tests or fixtures only when clearly marked as +fixture vocabulary, for example: + +```text +fixture_jedit_contract +test_text_window_contract +``` + +## Initial RED Witnesses + +The first RED tests should prove: + +1. Contract-shaped EINT must not silently enter Echo as an unauthenticated + generic inbox event when no generated contract artifact is installed. +2. QueryView/textWindow must return `ReadingEnvelope + QueryBytes`, not + `UnsupportedQuery` or naked payload bytes. + +Suggested targeted commands: + +```sh +cargo test -p warp-wasm stack_witness_contract_intent_without_installed_artifact_obstructs +cargo test -p warp-wasm stack_witness_text_window_query_returns_reading_envelope_and_query_bytes +``` + +## Golden Trace Shape + +The witness should eventually produce one stack trace: + +```text +001 createBuffer request +002 createBuffer receipt +003 replaceRange request +004 replaceRange receipt +005 textWindow request +006 ReadingEnvelope +007 QueryBytes +``` + +Each repo can consume or produce part of this trace: + +- Wesley produces generated helpers, canonical vars, operation ids, footprint + hashes, and fixture vectors. +- Echo admits intents, retains provenance, observes QueryView, and emits the + reading artifact. +- jedit consumes generated helpers and renders the bounded payload. +- warp-ttd explains the receipt, basis, law identity, read identity, and payload + digest. +- Continuum later publishes the proven boundary as shared protocol family. + +Continuum should codify this seam after the local proof exists. It should not +invent the seam before Echo and jedit prove it. From d772ca6bde067f4f62576358d58d35f4262c3e38 Mon Sep 17 00:00:00 2001 From: James Ross Date: Mon, 11 May 2026 08:37:55 -0700 Subject: [PATCH 02/12] fix(stack): obstruct missing contract artifact --- crates/warp-wasm/src/warp_kernel.rs | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/crates/warp-wasm/src/warp_kernel.rs b/crates/warp-wasm/src/warp_kernel.rs index 5a0e2962..328ca1d3 100644 --- a/crates/warp-wasm/src/warp_kernel.rs +++ b/crates/warp-wasm/src/warp_kernel.rs @@ -120,6 +120,13 @@ pub struct WarpKernel { registry: RegistryInfo, } +const STACK_WITNESS_CONTRACT_OP_ID_MASK: u32 = 0xffff_0000; +const STACK_WITNESS_CONTRACT_OP_ID_PREFIX: u32 = 0x5357_0000; + +fn is_stack_witness_contract_op_id(op_id: u32) -> bool { + op_id & STACK_WITNESS_CONTRACT_OP_ID_MASK == STACK_WITNESS_CONTRACT_OP_ID_PREFIX +} + impl WarpKernel { /// Create a new kernel with a minimal empty engine. /// @@ -880,6 +887,15 @@ impl KernelPort for WarpKernel { })?; } + if is_stack_witness_contract_op_id(op_id) { + return Err(AbiError { + code: error_codes::NOT_SUPPORTED, + message: format!( + "contract artifact is not installed for Stack Witness 0001 op id {op_id}" + ), + }); + } + let envelope = IngressEnvelope::local_intent( IngressTarget::DefaultWriter { worldline_id: self.default_worldline, From 48cd87063feb6f7cda1b2f3fa0a16970f9eeb6af Mon Sep 17 00:00:00 2001 From: James Ross Date: Mon, 11 May 2026 08:39:35 -0700 Subject: [PATCH 03/12] feat(stack): add fixture contract registry --- crates/warp-wasm/src/warp_kernel.rs | 73 +++++++++++++++++++++++++++-- 1 file changed, 69 insertions(+), 4 deletions(-) diff --git a/crates/warp-wasm/src/warp_kernel.rs b/crates/warp-wasm/src/warp_kernel.rs index 328ca1d3..c552453f 100644 --- a/crates/warp-wasm/src/warp_kernel.rs +++ b/crates/warp-wasm/src/warp_kernel.rs @@ -122,11 +122,41 @@ pub struct WarpKernel { const STACK_WITNESS_CONTRACT_OP_ID_MASK: u32 = 0xffff_0000; const STACK_WITNESS_CONTRACT_OP_ID_PREFIX: u32 = 0x5357_0000; +const STACK_WITNESS_CREATE_BUFFER_OP_ID: u32 = 0x5357_0001; +const STACK_WITNESS_REPLACE_RANGE_OP_ID: u32 = 0x5357_0002; fn is_stack_witness_contract_op_id(op_id: u32) -> bool { op_id & STACK_WITNESS_CONTRACT_OP_ID_MASK == STACK_WITNESS_CONTRACT_OP_ID_PREFIX } +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +enum FixtureContractOperationKind { + Mutation, +} + +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +struct FixtureContractOperation { + op_id: u32, + name: &'static str, + kind: FixtureContractOperationKind, +} + +fn stack_witness_fixture_operation(op_id: u32) -> Option { + match op_id { + STACK_WITNESS_CREATE_BUFFER_OP_ID => Some(FixtureContractOperation { + op_id, + name: "createBuffer", + kind: FixtureContractOperationKind::Mutation, + }), + STACK_WITNESS_REPLACE_RANGE_OP_ID => Some(FixtureContractOperation { + op_id, + name: "replaceRange", + kind: FixtureContractOperationKind::Mutation, + }), + _ => None, + } +} + impl WarpKernel { /// Create a new kernel with a minimal empty engine. /// @@ -887,7 +917,9 @@ impl KernelPort for WarpKernel { })?; } - if is_stack_witness_contract_op_id(op_id) { + if is_stack_witness_contract_op_id(op_id) + && stack_witness_fixture_operation(op_id).is_none() + { return Err(AbiError { code: error_codes::NOT_SUPPORTED, message: format!( @@ -896,6 +928,16 @@ impl KernelPort for WarpKernel { }); } + if let Some(operation) = stack_witness_fixture_operation(op_id) { + return Err(AbiError { + code: error_codes::NOT_SUPPORTED, + message: format!( + "contract artifact is registered for Stack Witness 0001 operation {}, but its fixture handler is not installed", + operation.name + ), + }); + } + let envelope = IngressEnvelope::local_intent( IngressTarget::DefaultWriter { worldline_id: self.default_worldline, @@ -1105,8 +1147,7 @@ mod tests { } } - const STACK_WITNESS_CREATE_BUFFER_OP_ID: u32 = 0x5357_0001; - const STACK_WITNESS_REPLACE_RANGE_OP_ID: u32 = 0x5357_0002; + const STACK_WITNESS_UNKNOWN_OP_ID: u32 = 0x5357_00ff; const STACK_WITNESS_TEXT_WINDOW_QUERY_ID: u32 = 0x5357_1001; fn stack_witness_create_buffer_vars() -> Vec { @@ -1123,6 +1164,30 @@ mod tests { .to_vec() } + #[test] + fn stack_witness_fixture_registry_names_mutations() { + assert_eq!( + stack_witness_fixture_operation(STACK_WITNESS_CREATE_BUFFER_OP_ID), + Some(FixtureContractOperation { + op_id: STACK_WITNESS_CREATE_BUFFER_OP_ID, + name: "createBuffer", + kind: FixtureContractOperationKind::Mutation, + }) + ); + assert_eq!( + stack_witness_fixture_operation(STACK_WITNESS_REPLACE_RANGE_OP_ID), + Some(FixtureContractOperation { + op_id: STACK_WITNESS_REPLACE_RANGE_OP_ID, + name: "replaceRange", + kind: FixtureContractOperationKind::Mutation, + }) + ); + assert_eq!( + stack_witness_fixture_operation(STACK_WITNESS_UNKNOWN_OP_ID), + None + ); + } + fn sample_import_suffix_request(kernel: &WarpKernel) -> AbiImportSuffixRequest { let worldline_id = kernel.default_worldline; let base_frontier = abi_provenance_ref(worldline_id, 0, 1); @@ -1653,7 +1718,7 @@ mod tests { fn stack_witness_contract_intent_without_installed_artifact_obstructs() { let mut kernel = WarpKernel::new().unwrap(); let intent = pack_intent_v1( - STACK_WITNESS_CREATE_BUFFER_OP_ID, + STACK_WITNESS_UNKNOWN_OP_ID, &stack_witness_create_buffer_vars(), ) .unwrap(); From 94ff70fdf8fc588f39dda3f649e8ea358f3316ff Mon Sep 17 00:00:00 2001 From: James Ross Date: Mon, 11 May 2026 08:40:46 -0700 Subject: [PATCH 04/12] feat(stack): admit fixture edit intents --- crates/warp-wasm/src/warp_kernel.rs | 43 ++++++++++++++++++++++------- 1 file changed, 33 insertions(+), 10 deletions(-) diff --git a/crates/warp-wasm/src/warp_kernel.rs b/crates/warp-wasm/src/warp_kernel.rs index c552453f..f742d3d6 100644 --- a/crates/warp-wasm/src/warp_kernel.rs +++ b/crates/warp-wasm/src/warp_kernel.rs @@ -928,16 +928,6 @@ impl KernelPort for WarpKernel { }); } - if let Some(operation) = stack_witness_fixture_operation(op_id) { - return Err(AbiError { - code: error_codes::NOT_SUPPORTED, - message: format!( - "contract artifact is registered for Stack Witness 0001 operation {}, but its fixture handler is not installed", - operation.name - ), - }); - } - let envelope = IngressEnvelope::local_intent( IngressTarget::DefaultWriter { worldline_id: self.default_worldline, @@ -1734,6 +1724,39 @@ mod tests { ); } + #[test] + fn stack_witness_create_buffer_and_replace_range_enter_dispatch_intent() { + let mut kernel = WarpKernel::new().unwrap(); + let create_buffer = pack_intent_v1( + STACK_WITNESS_CREATE_BUFFER_OP_ID, + &stack_witness_create_buffer_vars(), + ) + .unwrap(); + let replace_range = pack_intent_v1( + STACK_WITNESS_REPLACE_RANGE_OP_ID, + &stack_witness_replace_range_vars(), + ) + .unwrap(); + + let create = kernel.dispatch_intent(&create_buffer).unwrap(); + assert!(create.accepted); + assert_eq!(create.intent_id.len(), 32); + let create_run = start_until_idle(&mut kernel, Some(4)); + assert_eq!( + create_run.scheduler_status.last_run_completion, + Some(RunCompletion::Quiesced) + ); + + let replace = kernel.dispatch_intent(&replace_range).unwrap(); + assert!(replace.accepted); + assert_eq!(replace.intent_id.len(), 32); + let replace_run = start_until_idle(&mut kernel, Some(4)); + assert_eq!( + replace_run.scheduler_status.last_run_completion, + Some(RunCompletion::Quiesced) + ); + } + #[test] fn stack_witness_text_window_query_returns_reading_envelope_and_query_bytes() { let mut kernel = WarpKernel::new().unwrap(); From 2ce8a0d891a18580bd1b4a97f88567fe660811cb Mon Sep 17 00:00:00 2001 From: James Ross Date: Mon, 11 May 2026 08:42:49 -0700 Subject: [PATCH 05/12] feat(stack): route fixture text window query --- crates/warp-wasm/src/warp_kernel.rs | 88 +++++++++++++++++++++++++++-- 1 file changed, 84 insertions(+), 4 deletions(-) diff --git a/crates/warp-wasm/src/warp_kernel.rs b/crates/warp-wasm/src/warp_kernel.rs index f742d3d6..c8b41388 100644 --- a/crates/warp-wasm/src/warp_kernel.rs +++ b/crates/warp-wasm/src/warp_kernel.rs @@ -18,15 +18,19 @@ use echo_wasm_abi::kernel_port::{ HeadEligibility as AbiHeadEligibility, HeadId as AbiHeadId, HeadInfo, KernelPort, NeighborhoodCore as AbiNeighborhoodCore, NeighborhoodSite as AbiNeighborhoodSite, ObservationArtifact as AbiObservationArtifact, ObservationFrame as AbiObservationFrame, - ObservationProjection as AbiObservationProjection, + ObservationPayload as AbiObservationPayload, ObservationProjection as AbiObservationProjection, ObservationReadBudget as AbiObservationReadBudget, ObservationRequest as AbiObservationRequest, ObservationRights as AbiObservationRights, ObserveOpticRequest as AbiObserveOpticRequest, ObserveOpticResult as AbiObserveOpticResult, ObserverInstanceRef as AbiObserverInstanceRef, OpticAperture as AbiOpticAperture, OpticApertureShape as AbiOpticApertureShape, OpticFocus as AbiOpticFocus, ProjectionVersion as AbiProjectionVersion, - ReadingObserverPlan as AbiReadingObserverPlan, ReducerVersion as AbiReducerVersion, - RegistryInfo, RetainedReadingKey as AbiRetainedReadingKey, RunCompletion, RunId as AbiRunId, - SchedulerMode, SchedulerState, SchedulerStatus, SettlementDelta as AbiSettlementDelta, + ReadingBudgetPosture as AbiReadingBudgetPosture, + ReadingObserverBasis as AbiReadingObserverBasis, ReadingObserverPlan as AbiReadingObserverPlan, + ReadingResidualPosture as AbiReadingResidualPosture, + ReadingRightsPosture as AbiReadingRightsPosture, ReducerVersion as AbiReducerVersion, + RegistryInfo, ResolvedObservationCoordinate as AbiResolvedObservationCoordinate, + RetainedReadingKey as AbiRetainedReadingKey, RunCompletion, RunId as AbiRunId, SchedulerMode, + SchedulerState, SchedulerStatus, SettlementDelta as AbiSettlementDelta, SettlementPlan as AbiSettlementPlan, SettlementRequest as AbiSettlementRequest, SettlementResult as AbiSettlementResult, WorkState, WorldlineId as AbiWorldlineId, WorldlineTick as AbiWorldlineTick, WriterHeadKey as AbiWriterHeadKey, ABI_VERSION, @@ -124,6 +128,9 @@ const STACK_WITNESS_CONTRACT_OP_ID_MASK: u32 = 0xffff_0000; const STACK_WITNESS_CONTRACT_OP_ID_PREFIX: u32 = 0x5357_0000; const STACK_WITNESS_CREATE_BUFFER_OP_ID: u32 = 0x5357_0001; const STACK_WITNESS_REPLACE_RANGE_OP_ID: u32 = 0x5357_0002; +const STACK_WITNESS_TEXT_WINDOW_QUERY_ID: u32 = 0x5357_1001; +const STACK_WITNESS_TEXT_WINDOW_VARS: &[u8] = b"stack-witness-0001/textWindow;basis=B1;coord=utf8-bytes;start=0;length=5;artifact=fixture-file-history-v0"; +const STACK_WITNESS_TEXT_WINDOW_BYTES: &[u8] = b"hello"; fn is_stack_witness_contract_op_id(op_id: u32) -> bool { op_id & STACK_WITNESS_CONTRACT_OP_ID_MASK == STACK_WITNESS_CONTRACT_OP_ID_PREFIX @@ -671,6 +678,76 @@ impl WarpKernel { .map_err(Self::map_observation_error) } + fn observe_stack_witness_fixture( + &self, + request: &AbiObservationRequest, + ) -> Result, AbiError> { + let AbiObservationProjection::Query { + query_id, + vars_bytes, + } = &request.projection + else { + return Ok(None); + }; + if request.frame != AbiObservationFrame::QueryView + || *query_id != STACK_WITNESS_TEXT_WINDOW_QUERY_ID + || vars_bytes.as_slice() != STACK_WITNESS_TEXT_WINDOW_VARS + { + return Ok(None); + } + + if !matches!(request.rights, AbiObservationRights::KernelPublic) { + return Err(AbiError { + code: error_codes::UNSUPPORTED_OBSERVATION_RIGHTS, + message: "Stack Witness 0001 fixture observer requires kernel-public rights".into(), + }); + } + + let head = self.current_head()?; + let budget_posture = match request.budget { + AbiObservationReadBudget::UnboundedOneShot => AbiReadingBudgetPosture::UnboundedOneShot, + AbiObservationReadBudget::Bounded { + max_payload_bytes, + max_witness_refs, + } => AbiReadingBudgetPosture::Bounded { + max_payload_bytes, + payload_bytes: STACK_WITNESS_TEXT_WINDOW_BYTES.len() as u64, + max_witness_refs, + witness_refs: 0, + }, + }; + + Ok(Some(AbiObservationArtifact { + resolved: AbiResolvedObservationCoordinate { + observation_version: 2, + worldline_id: request.coordinate.worldline_id, + requested_at: request.coordinate.at.clone(), + resolved_worldline_tick: head.worldline_tick, + commit_global_tick: head.commit_global_tick, + observed_after_global_tick: head.commit_global_tick, + state_root: head.state_root, + commit_hash: head.commit_id, + }, + reading: echo_wasm_abi::kernel_port::ReadingEnvelope { + observer_plan: request.observer_plan.clone(), + observer_instance: request.observer_instance.clone(), + observer_basis: AbiReadingObserverBasis::QueryView, + witness_refs: Vec::new(), + parent_basis_posture: + echo_wasm_abi::kernel_port::ObservationBasisPosture::Worldline, + budget_posture, + rights_posture: AbiReadingRightsPosture::KernelPublic, + residual_posture: AbiReadingResidualPosture::Complete, + }, + frame: request.frame.clone(), + projection: request.projection.clone(), + artifact_hash: vec![0x51; 32], + payload: AbiObservationPayload::QueryBytes { + data: STACK_WITNESS_TEXT_WINDOW_BYTES.to_vec(), + }, + })) + } + pub(crate) fn current_head(&self) -> Result { let request = ObservationRequest::builtin_one_shot( ObservationCoordinate { @@ -990,6 +1067,9 @@ impl KernelPort for WarpKernel { } fn observe(&self, request: AbiObservationRequest) -> Result { + if let Some(artifact) = self.observe_stack_witness_fixture(&request)? { + return Ok(artifact); + } let request = Self::to_core_request(request)?; Ok(self.observe_core(request)?.to_abi()) } From 47355602bc6f4fd7ea985135ad25a999bc9949b1 Mon Sep 17 00:00:00 2001 From: James Ross Date: Mon, 11 May 2026 08:52:12 -0700 Subject: [PATCH 06/12] docs(stack): record Stack Witness 0001 green state --- .../stack-witness-0001-jedit-file-history.md | 46 +++++++++++++++++-- 1 file changed, 42 insertions(+), 4 deletions(-) diff --git a/docs/design/stack-witness-0001-jedit-file-history.md b/docs/design/stack-witness-0001-jedit-file-history.md index 48efe608..493bbbb9 100644 --- a/docs/design/stack-witness-0001-jedit-file-history.md +++ b/docs/design/stack-witness-0001-jedit-file-history.md @@ -174,20 +174,58 @@ test_text_window_contract ## Initial RED Witnesses -The first RED tests should prove: +The first RED tests proved: 1. Contract-shaped EINT must not silently enter Echo as an unauthenticated generic inbox event when no generated contract artifact is installed. 2. QueryView/textWindow must return `ReadingEnvelope + QueryBytes`, not `UnsupportedQuery` or naked payload bytes. -Suggested targeted commands: +## Current GREEN State + +As of branch `wip/stack-witness-0001`, Echo has a fixture-backed walking +skeleton for this witness: + +- a static/as-if-generated fixture registry exists for `createBuffer` and + `replaceRange`; +- unknown Stack Witness contract op ids obstruct with a contract/missing + artifact error; +- `createBuffer` and `replaceRange("hello")` enter through `dispatch_intent` + and the existing scheduler path; +- the Stack Witness `textWindow` QueryView routes to a fixture observer; +- the fixture observer returns `ReadingEnvelope + QueryBytes("hello")`; +- Echo core still does not expose public jedit, editor, rope, buffer, cursor, + or selection APIs. + +Targeted command: ```sh -cargo test -p warp-wasm stack_witness_contract_intent_without_installed_artifact_obstructs -cargo test -p warp-wasm stack_witness_text_window_query_returns_reading_envelope_and_query_bytes +cargo test -p warp-wasm --features engine stack_witness_ +``` + +Current result: + +```text +running 4 tests +test warp_kernel::tests::stack_witness_fixture_registry_names_mutations ... ok +test warp_kernel::tests::stack_witness_contract_intent_without_installed_artifact_obstructs ... ok +test warp_kernel::tests::stack_witness_create_buffer_and_replace_range_enter_dispatch_intent ... ok +test warp_kernel::tests::stack_witness_text_window_query_returns_reading_envelope_and_query_bytes ... ok + +test result: ok. 4 passed; 0 failed; 0 ignored; 0 measured; 44 filtered out ``` +This is intentionally fixture scaffolding. Do not generalize Echo further from +this state. The next stack move is for Wesley to replace the cardboard cutout +with a generated fixture artifact shape: + +- operation ids; +- canonical vars; +- footprints; +- EINT helpers; +- QueryView helper; +- artifact identity. + ## Golden Trace Shape The witness should eventually produce one stack trace: From c4571c7b4855304a674fe78bb62b5514f86de7b6 Mon Sep 17 00:00:00 2001 From: James Ross Date: Mon, 11 May 2026 09:18:22 -0700 Subject: [PATCH 07/12] test(stack): lock Echo fixture shape to Wesley vectors --- Cargo.lock | 1 + crates/warp-wasm/Cargo.toml | 1 + crates/warp-wasm/src/warp_kernel.rs | 124 ++++++++++++++++++ .../fixtures/stack-witness-0001-vectors.json | 86 ++++++++++++ 4 files changed, 212 insertions(+) create mode 100644 crates/warp-wasm/test/fixtures/stack-witness-0001-vectors.json diff --git a/Cargo.lock b/Cargo.lock index 7da1da26..6012fa30 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2083,6 +2083,7 @@ dependencies = [ "js-sys", "serde", "serde-value", + "serde_json", "warp-core", "wasm-bindgen", "web-sys", diff --git a/crates/warp-wasm/Cargo.toml b/crates/warp-wasm/Cargo.toml index 448bd3b3..364e4ed8 100644 --- a/crates/warp-wasm/Cargo.toml +++ b/crates/warp-wasm/Cargo.toml @@ -35,6 +35,7 @@ serde = { version = "1.0", features = ["derive"] } [dev-dependencies] warp-core = { workspace = true } +serde_json = "1.0" serde-value = "0.7" [package.metadata.wasm-pack.profile.release] diff --git a/crates/warp-wasm/src/warp_kernel.rs b/crates/warp-wasm/src/warp_kernel.rs index c8b41388..13e114a0 100644 --- a/crates/warp-wasm/src/warp_kernel.rs +++ b/crates/warp-wasm/src/warp_kernel.rs @@ -129,6 +129,15 @@ const STACK_WITNESS_CONTRACT_OP_ID_PREFIX: u32 = 0x5357_0000; const STACK_WITNESS_CREATE_BUFFER_OP_ID: u32 = 0x5357_0001; const STACK_WITNESS_REPLACE_RANGE_OP_ID: u32 = 0x5357_0002; const STACK_WITNESS_TEXT_WINDOW_QUERY_ID: u32 = 0x5357_1001; +#[cfg(test)] +const STACK_WITNESS_FIXTURE_ARTIFACT_ID: &str = "fixture-file-history-v0"; +#[cfg(test)] +const STACK_WITNESS_CANONICAL_VARS_ENCODING: &str = "utf8-semicolon-kv/v0"; +#[cfg(test)] +const STACK_WITNESS_CREATE_BUFFER_VARS: &[u8] = + b"stack-witness-0001/createBuffer;name=demo.txt;artifact=fixture-file-history-v0"; +#[cfg(test)] +const STACK_WITNESS_REPLACE_RANGE_VARS: &[u8] = b"stack-witness-0001/replaceRange;basis=B0;coord=utf8-bytes;start=0;end=0;text=hello;artifact=fixture-file-history-v0"; const STACK_WITNESS_TEXT_WINDOW_VARS: &[u8] = b"stack-witness-0001/textWindow;basis=B1;coord=utf8-bytes;start=0;length=5;artifact=fixture-file-history-v0"; const STACK_WITNESS_TEXT_WINDOW_BYTES: &[u8] = b"hello"; @@ -1234,6 +1243,121 @@ mod tests { .to_vec() } + #[test] + fn stack_witness_fixture_vectors_match_wesley_artifact_shape() { + let vectors: serde_json::Value = serde_json::from_str(include_str!( + "../test/fixtures/stack-witness-0001-vectors.json" + )) + .expect("Stack Witness fixture vectors should parse"); + + assert_eq!( + vectors["artifact"]["artifactId"], + serde_json::json!(STACK_WITNESS_FIXTURE_ARTIFACT_ID) + ); + assert_eq!( + vectors["canonicalVarsEncoding"], + serde_json::json!(STACK_WITNESS_CANONICAL_VARS_ENCODING) + ); + + assert_stack_witness_vector( + &vectors, + StackWitnessVectorExpectation { + name: "createBuffer", + operation_type: "MUTATION", + op_id: STACK_WITNESS_CREATE_BUFFER_OP_ID, + helper_kind: "EINT", + helper_frame: "EINT", + helper_entrypoint: "dispatch_intent", + canonical_vars: STACK_WITNESS_CREATE_BUFFER_VARS, + }, + ); + assert_stack_witness_vector( + &vectors, + StackWitnessVectorExpectation { + name: "replaceRange", + operation_type: "MUTATION", + op_id: STACK_WITNESS_REPLACE_RANGE_OP_ID, + helper_kind: "EINT", + helper_frame: "EINT", + helper_entrypoint: "dispatch_intent", + canonical_vars: STACK_WITNESS_REPLACE_RANGE_VARS, + }, + ); + assert_stack_witness_vector( + &vectors, + StackWitnessVectorExpectation { + name: "textWindow", + operation_type: "QUERY", + op_id: STACK_WITNESS_TEXT_WINDOW_QUERY_ID, + helper_kind: "QueryView", + helper_frame: "QueryView", + helper_entrypoint: "observe", + canonical_vars: STACK_WITNESS_TEXT_WINDOW_VARS, + }, + ); + + let text_window = stack_witness_vector(&vectors, "textWindow"); + assert_eq!(text_window["payloadCodec"], serde_json::json!("QueryBytes")); + assert_eq!( + text_window["envelope"], + serde_json::json!("ReadingEnvelope") + ); + } + + struct StackWitnessVectorExpectation { + name: &'static str, + operation_type: &'static str, + op_id: u32, + helper_kind: &'static str, + helper_frame: &'static str, + helper_entrypoint: &'static str, + canonical_vars: &'static [u8], + } + + fn assert_stack_witness_vector( + vectors: &serde_json::Value, + expected: StackWitnessVectorExpectation, + ) { + let vector = stack_witness_vector(vectors, expected.name); + assert_eq!( + vector["operationType"], + serde_json::json!(expected.operation_type) + ); + assert_eq!(vector["opIdDecimal"], serde_json::json!(expected.op_id)); + assert_eq!( + vector["opIdHex"], + serde_json::json!(format!("0x{:08x}", expected.op_id)) + ); + assert_eq!( + vector["helperKind"], + serde_json::json!(expected.helper_kind) + ); + assert_eq!( + vector["helperShape"]["frame"], + serde_json::json!(expected.helper_frame) + ); + assert_eq!( + vector["helperShape"]["entrypoint"], + serde_json::json!(expected.helper_entrypoint) + ); + assert_eq!( + vector["canonicalVarsBytes"].as_str().map(str::as_bytes), + Some(expected.canonical_vars) + ); + } + + fn stack_witness_vector<'a>( + vectors: &'a serde_json::Value, + name: &str, + ) -> &'a serde_json::Value { + vectors["operations"] + .as_array() + .expect("Stack Witness operations should be an array") + .iter() + .find(|operation| operation["name"].as_str() == Some(name)) + .expect("Stack Witness operation vector should exist") + } + #[test] fn stack_witness_fixture_registry_names_mutations() { assert_eq!( diff --git a/crates/warp-wasm/test/fixtures/stack-witness-0001-vectors.json b/crates/warp-wasm/test/fixtures/stack-witness-0001-vectors.json new file mode 100644 index 00000000..eefd5030 --- /dev/null +++ b/crates/warp-wasm/test/fixtures/stack-witness-0001-vectors.json @@ -0,0 +1,86 @@ +{ + "artifact": { + "familyId": "stack-witness-0001.file-history", + "schemaId": "stack-witness-0001.file-history.v0", + "artifactId": "fixture-file-history-v0", + "version": "0" + }, + "canonicalVarsEncoding": "utf8-semicolon-kv/v0", + "operations": [ + { + "name": "createBuffer", + "operationType": "MUTATION", + "opIdHex": "0x53570001", + "opIdDecimal": 1398210561, + "helperKind": "EINT", + "canonicalVarsBytes": "stack-witness-0001/createBuffer;name=demo.txt;artifact=fixture-file-history-v0", + "declaredFootprint": { + "reads": [], + "writes": [], + "creates": ["Buffer"], + "forbids": ["AstState", "Diagnostics", "GitWitness", "UiState"] + }, + "helperShape": { + "frame": "EINT", + "entrypoint": "dispatch_intent", + "fields": [ + "contract_artifact_id", + "operation_id", + "canonical_vars_bytes", + "declared_footprint" + ] + } + }, + { + "name": "replaceRange", + "operationType": "MUTATION", + "opIdHex": "0x53570002", + "opIdDecimal": 1398210562, + "helperKind": "EINT", + "canonicalVarsBytes": "stack-witness-0001/replaceRange;basis=B0;coord=utf8-bytes;start=0;end=0;text=hello;artifact=fixture-file-history-v0", + "declaredFootprint": { + "reads": ["Buffer"], + "writes": ["Buffer"], + "creates": ["Tick", "Receipt"], + "forbids": ["AstState", "Diagnostics", "GitWitness", "UiState"] + }, + "helperShape": { + "frame": "EINT", + "entrypoint": "dispatch_intent", + "fields": [ + "contract_artifact_id", + "operation_id", + "canonical_vars_bytes", + "declared_footprint" + ] + } + }, + { + "name": "textWindow", + "operationType": "QUERY", + "opIdHex": "0x53571001", + "opIdDecimal": 1398214657, + "helperKind": "QueryView", + "canonicalVarsBytes": "stack-witness-0001/textWindow;basis=B1;coord=utf8-bytes;start=0;length=5;artifact=fixture-file-history-v0", + "payloadCodec": "QueryBytes", + "envelope": "ReadingEnvelope", + "declaredFootprint": { + "reads": ["Buffer", "Tick", "Receipt"], + "writes": [], + "creates": [], + "forbids": ["AstState", "Diagnostics", "GitWitness", "UiState"] + }, + "helperShape": { + "frame": "QueryView", + "entrypoint": "observe", + "fields": [ + "contract_artifact_id", + "query_id", + "canonical_vars_bytes", + "reading_envelope", + "query_bytes" + ] + } + } + ] +} From e51a27c4b89eb9a53320055643f087b6ccbbe4a7 Mon Sep 17 00:00:00 2001 From: James Ross Date: Mon, 11 May 2026 09:32:06 -0700 Subject: [PATCH 08/12] test(stack): harden Echo Wesley vector drift lock --- CHANGELOG.md | 4 ++++ crates/warp-wasm/src/warp_kernel.rs | 16 ++++++++++++---- .../fixtures/stack-witness-0001-vectors.json | 5 +++-- .../stack-witness-0001-jedit-file-history.md | 3 +++ 4 files changed, 22 insertions(+), 6 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6232acea..97d83681 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,10 @@ ### Added +- Stack Witness 0001 drift lock — `warp-wasm` now mirrors Wesley's fixture + vectors for the jedit-through-Echo walking skeleton and verifies Echo's + fixture op ids, buffer-inclusive canonical vars, helper entrypoints, and + expected `QueryBytes("hello")` payload bytes against that vector. - `echo-registry-api::verify_contract_artifact(...)` — generic load-time verification for Wesley-generated registries, including schema/codec/layout checks, expected footprint certificate hashes, optional generated artifact diff --git a/crates/warp-wasm/src/warp_kernel.rs b/crates/warp-wasm/src/warp_kernel.rs index 13e114a0..c3d163ce 100644 --- a/crates/warp-wasm/src/warp_kernel.rs +++ b/crates/warp-wasm/src/warp_kernel.rs @@ -137,8 +137,8 @@ const STACK_WITNESS_CANONICAL_VARS_ENCODING: &str = "utf8-semicolon-kv/v0"; const STACK_WITNESS_CREATE_BUFFER_VARS: &[u8] = b"stack-witness-0001/createBuffer;name=demo.txt;artifact=fixture-file-history-v0"; #[cfg(test)] -const STACK_WITNESS_REPLACE_RANGE_VARS: &[u8] = b"stack-witness-0001/replaceRange;basis=B0;coord=utf8-bytes;start=0;end=0;text=hello;artifact=fixture-file-history-v0"; -const STACK_WITNESS_TEXT_WINDOW_VARS: &[u8] = b"stack-witness-0001/textWindow;basis=B1;coord=utf8-bytes;start=0;length=5;artifact=fixture-file-history-v0"; +const STACK_WITNESS_REPLACE_RANGE_VARS: &[u8] = b"stack-witness-0001/replaceRange;bufferId=demo.txt;basis=B0;coord=utf8-bytes;start=0;end=0;text=hello;artifact=fixture-file-history-v0"; +const STACK_WITNESS_TEXT_WINDOW_VARS: &[u8] = b"stack-witness-0001/textWindow;bufferId=demo.txt;basis=B1;coord=utf8-bytes;start=0;length=5;artifact=fixture-file-history-v0"; const STACK_WITNESS_TEXT_WINDOW_BYTES: &[u8] = b"hello"; fn is_stack_witness_contract_op_id(op_id: u32) -> bool { @@ -1234,12 +1234,12 @@ mod tests { } fn stack_witness_replace_range_vars() -> Vec { - b"stack-witness-0001/replaceRange;basis=B0;coord=utf8-bytes;start=0;end=0;text=hello;artifact=fixture-file-history-v0" + b"stack-witness-0001/replaceRange;bufferId=demo.txt;basis=B0;coord=utf8-bytes;start=0;end=0;text=hello;artifact=fixture-file-history-v0" .to_vec() } fn stack_witness_text_window_vars() -> Vec { - b"stack-witness-0001/textWindow;basis=B1;coord=utf8-bytes;start=0;length=5;artifact=fixture-file-history-v0" + b"stack-witness-0001/textWindow;bufferId=demo.txt;basis=B1;coord=utf8-bytes;start=0;length=5;artifact=fixture-file-history-v0" .to_vec() } @@ -1302,6 +1302,10 @@ mod tests { text_window["envelope"], serde_json::json!("ReadingEnvelope") ); + assert_eq!( + text_window["expectedQueryBytesHex"], + serde_json::json!(lower_hex(STACK_WITNESS_TEXT_WINDOW_BYTES)) + ); } struct StackWitnessVectorExpectation { @@ -1358,6 +1362,10 @@ mod tests { .expect("Stack Witness operation vector should exist") } + fn lower_hex(bytes: &[u8]) -> String { + bytes.iter().map(|byte| format!("{byte:02x}")).collect() + } + #[test] fn stack_witness_fixture_registry_names_mutations() { assert_eq!( diff --git a/crates/warp-wasm/test/fixtures/stack-witness-0001-vectors.json b/crates/warp-wasm/test/fixtures/stack-witness-0001-vectors.json index eefd5030..27fe2b66 100644 --- a/crates/warp-wasm/test/fixtures/stack-witness-0001-vectors.json +++ b/crates/warp-wasm/test/fixtures/stack-witness-0001-vectors.json @@ -37,7 +37,7 @@ "opIdHex": "0x53570002", "opIdDecimal": 1398210562, "helperKind": "EINT", - "canonicalVarsBytes": "stack-witness-0001/replaceRange;basis=B0;coord=utf8-bytes;start=0;end=0;text=hello;artifact=fixture-file-history-v0", + "canonicalVarsBytes": "stack-witness-0001/replaceRange;bufferId=demo.txt;basis=B0;coord=utf8-bytes;start=0;end=0;text=hello;artifact=fixture-file-history-v0", "declaredFootprint": { "reads": ["Buffer"], "writes": ["Buffer"], @@ -61,9 +61,10 @@ "opIdHex": "0x53571001", "opIdDecimal": 1398214657, "helperKind": "QueryView", - "canonicalVarsBytes": "stack-witness-0001/textWindow;basis=B1;coord=utf8-bytes;start=0;length=5;artifact=fixture-file-history-v0", + "canonicalVarsBytes": "stack-witness-0001/textWindow;bufferId=demo.txt;basis=B1;coord=utf8-bytes;start=0;length=5;artifact=fixture-file-history-v0", "payloadCodec": "QueryBytes", "envelope": "ReadingEnvelope", + "expectedQueryBytesHex": "68656c6c6f", "declaredFootprint": { "reads": ["Buffer", "Tick", "Receipt"], "writes": [], diff --git a/docs/design/stack-witness-0001-jedit-file-history.md b/docs/design/stack-witness-0001-jedit-file-history.md index 493bbbb9..78ee67f7 100644 --- a/docs/design/stack-witness-0001-jedit-file-history.md +++ b/docs/design/stack-witness-0001-jedit-file-history.md @@ -194,6 +194,9 @@ skeleton for this witness: and the existing scheduler path; - the Stack Witness `textWindow` QueryView routes to a fixture observer; - the fixture observer returns `ReadingEnvelope + QueryBytes("hello")`; +- Echo mirrors Wesley's Stack Witness 0001 fixture vector and verifies op ids, + helper entrypoints, buffer-inclusive canonical vars, and expected query bytes + against that artifact shape; - Echo core still does not expose public jedit, editor, rope, buffer, cursor, or selection APIs. From 753395866cc9ac0040fd482e8515e27e4fd51665 Mon Sep 17 00:00:00 2001 From: James Ross Date: Mon, 11 May 2026 09:56:40 -0700 Subject: [PATCH 09/12] fix(stack): enforce Stack Witness fixture causality --- CHANGELOG.md | 11 + Cargo.lock | 1 + crates/warp-wasm/Cargo.toml | 1 + crates/warp-wasm/src/warp_kernel.rs | 265 +++++++++++++++--- .../stack-witness-0001-jedit-file-history.md | 42 ++- 5 files changed, 272 insertions(+), 48 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 97d83681..2fcacf93 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -53,6 +53,17 @@ ### Changed +### Fixed + +- Stack Witness 0001 fixture observations now require the fixture + `createBuffer` and `replaceRange("hello")` history to be admitted and + materialized before `textWindow` can return `QueryBytes("hello")`. +- Stack Witness 0001 `textWindow` observations now fail closed when the bounded + read budget is smaller than the returned payload and carry a deterministic + BLAKE3 artifact hash instead of a dummy reading identity. +- Strengthened Echo's Wesley fixture-vector drift lock to cover artifact family, + schema, version, declared footprints, and generated helper field shapes. + ### Fixed (PR #326 follow-up) - Added regression coverage that rejects trailing whitespace in the committed diff --git a/Cargo.lock b/Cargo.lock index 6012fa30..bd98c43e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2077,6 +2077,7 @@ dependencies = [ name = "warp-wasm" version = "0.1.0" dependencies = [ + "blake3", "console_error_panic_hook", "echo-registry-api", "echo-wasm-abi", diff --git a/crates/warp-wasm/Cargo.toml b/crates/warp-wasm/Cargo.toml index 364e4ed8..66784b0a 100644 --- a/crates/warp-wasm/Cargo.toml +++ b/crates/warp-wasm/Cargo.toml @@ -32,6 +32,7 @@ js-sys = "0.3.83" web-sys = { version = "0.3.83", optional = true, features = ["console"] } console_error_panic_hook = { version = "0.1.7", optional = true } serde = { version = "1.0", features = ["derive"] } +blake3 = "1.0" [dev-dependencies] warp-core = { workspace = true } diff --git a/crates/warp-wasm/src/warp_kernel.rs b/crates/warp-wasm/src/warp_kernel.rs index c3d163ce..875ccd53 100644 --- a/crates/warp-wasm/src/warp_kernel.rs +++ b/crates/warp-wasm/src/warp_kernel.rs @@ -122,6 +122,8 @@ pub struct WarpKernel { next_run_id: RunId, /// Registry metadata (injected at construction, immutable after). registry: RegistryInfo, + stack_witness_create_buffer_admitted: bool, + stack_witness_replace_range_admitted: bool, } const STACK_WITNESS_CONTRACT_OP_ID_MASK: u32 = 0xffff_0000; @@ -129,7 +131,6 @@ const STACK_WITNESS_CONTRACT_OP_ID_PREFIX: u32 = 0x5357_0000; const STACK_WITNESS_CREATE_BUFFER_OP_ID: u32 = 0x5357_0001; const STACK_WITNESS_REPLACE_RANGE_OP_ID: u32 = 0x5357_0002; const STACK_WITNESS_TEXT_WINDOW_QUERY_ID: u32 = 0x5357_1001; -#[cfg(test)] const STACK_WITNESS_FIXTURE_ARTIFACT_ID: &str = "fixture-file-history-v0"; #[cfg(test)] const STACK_WITNESS_CANONICAL_VARS_ENCODING: &str = "utf8-semicolon-kv/v0"; @@ -173,6 +174,15 @@ fn stack_witness_fixture_operation(op_id: u32) -> Option Vec { + let mut hasher = blake3::Hasher::new(); + hasher.update(STACK_WITNESS_FIXTURE_ARTIFACT_ID.as_bytes()); + hasher.update(&STACK_WITNESS_TEXT_WINDOW_QUERY_ID.to_le_bytes()); + hasher.update(STACK_WITNESS_TEXT_WINDOW_VARS); + hasher.update(STACK_WITNESS_TEXT_WINDOW_BYTES); + hasher.finalize().as_bytes().to_vec() +} + impl WarpKernel { /// Create a new kernel with a minimal empty engine. /// @@ -253,6 +263,8 @@ impl WarpKernel { }, next_run_id: RunId::from_raw(1), registry, + stack_witness_create_buffer_admitted: false, + stack_witness_replace_range_admitted: false, }) } @@ -713,6 +725,28 @@ impl WarpKernel { } let head = self.current_head()?; + if !self.stack_witness_fixture_history_is_materialized(&head) { + return Err(AbiError { + code: error_codes::OBSERVATION_UNAVAILABLE, + message: "Stack Witness 0001 textWindow requires admitted createBuffer and replaceRange history".into(), + }); + } + + let payload_bytes = STACK_WITNESS_TEXT_WINDOW_BYTES.len() as u64; + if let AbiObservationReadBudget::Bounded { + max_payload_bytes, .. + } = request.budget + { + if payload_bytes > max_payload_bytes { + return Err(AbiError { + code: error_codes::OBSERVATION_BUDGET_EXCEEDED, + message: format!( + "Stack Witness 0001 textWindow payload requires {payload_bytes} bytes but budget allows {max_payload_bytes}" + ), + }); + } + } + let budget_posture = match request.budget { AbiObservationReadBudget::UnboundedOneShot => AbiReadingBudgetPosture::UnboundedOneShot, AbiObservationReadBudget::Bounded { @@ -720,7 +754,7 @@ impl WarpKernel { max_witness_refs, } => AbiReadingBudgetPosture::Bounded { max_payload_bytes, - payload_bytes: STACK_WITNESS_TEXT_WINDOW_BYTES.len() as u64, + payload_bytes, max_witness_refs, witness_refs: 0, }, @@ -750,13 +784,31 @@ impl WarpKernel { }, frame: request.frame.clone(), projection: request.projection.clone(), - artifact_hash: vec![0x51; 32], + artifact_hash: stack_witness_text_window_artifact_hash(), payload: AbiObservationPayload::QueryBytes { data: STACK_WITNESS_TEXT_WINDOW_BYTES.to_vec(), }, })) } + fn stack_witness_fixture_history_is_materialized(&self, head: &HeadInfo) -> bool { + self.stack_witness_create_buffer_admitted + && self.stack_witness_replace_range_admitted + && head.worldline_tick.0 >= 2 + } + + fn record_stack_witness_fixture_admission(&mut self, op_id: u32) { + match op_id { + STACK_WITNESS_CREATE_BUFFER_OP_ID => { + self.stack_witness_create_buffer_admitted = true; + } + STACK_WITNESS_REPLACE_RANGE_OP_ID => { + self.stack_witness_replace_range_admitted = true; + } + _ => {} + } + } + pub(crate) fn current_head(&self) -> Result { let request = ObservationRequest::builtin_one_shot( ObservationCoordinate { @@ -1025,6 +1077,9 @@ impl KernelPort for WarpKernel { match self.runtime.ingest(envelope) { Ok(disposition) => { let accepted = matches!(disposition, IngressDisposition::Accepted { .. }); + if accepted { + self.record_stack_witness_fixture_admission(op_id); + } self.refresh_scheduler_status(); Ok(DispatchResponse { accepted, @@ -1227,20 +1282,35 @@ mod tests { } const STACK_WITNESS_UNKNOWN_OP_ID: u32 = 0x5357_00ff; - const STACK_WITNESS_TEXT_WINDOW_QUERY_ID: u32 = 0x5357_1001; + const STACK_WITNESS_FIXTURE_FAMILY_ID: &str = "stack-witness-0001.file-history"; + const STACK_WITNESS_FIXTURE_SCHEMA_ID: &str = "stack-witness-0001.file-history.v0"; + const STACK_WITNESS_FIXTURE_VERSION: &str = "0"; + const STACK_WITNESS_HELPER_MUTATION_FIELDS: &[&str] = &[ + "contract_artifact_id", + "operation_id", + "canonical_vars_bytes", + "declared_footprint", + ]; + const STACK_WITNESS_HELPER_QUERY_FIELDS: &[&str] = &[ + "contract_artifact_id", + "query_id", + "canonical_vars_bytes", + "reading_envelope", + "query_bytes", + ]; + const STACK_WITNESS_FORBIDDEN_FOOTPRINTS: &[&str] = + &["AstState", "Diagnostics", "GitWitness", "UiState"]; fn stack_witness_create_buffer_vars() -> Vec { - b"stack-witness-0001/createBuffer;name=demo.txt;artifact=fixture-file-history-v0".to_vec() + STACK_WITNESS_CREATE_BUFFER_VARS.to_vec() } fn stack_witness_replace_range_vars() -> Vec { - b"stack-witness-0001/replaceRange;bufferId=demo.txt;basis=B0;coord=utf8-bytes;start=0;end=0;text=hello;artifact=fixture-file-history-v0" - .to_vec() + STACK_WITNESS_REPLACE_RANGE_VARS.to_vec() } fn stack_witness_text_window_vars() -> Vec { - b"stack-witness-0001/textWindow;bufferId=demo.txt;basis=B1;coord=utf8-bytes;start=0;length=5;artifact=fixture-file-history-v0" - .to_vec() + STACK_WITNESS_TEXT_WINDOW_VARS.to_vec() } #[test] @@ -1250,10 +1320,22 @@ mod tests { )) .expect("Stack Witness fixture vectors should parse"); + assert_eq!( + vectors["artifact"]["familyId"], + serde_json::json!(STACK_WITNESS_FIXTURE_FAMILY_ID) + ); + assert_eq!( + vectors["artifact"]["schemaId"], + serde_json::json!(STACK_WITNESS_FIXTURE_SCHEMA_ID) + ); assert_eq!( vectors["artifact"]["artifactId"], serde_json::json!(STACK_WITNESS_FIXTURE_ARTIFACT_ID) ); + assert_eq!( + vectors["artifact"]["version"], + serde_json::json!(STACK_WITNESS_FIXTURE_VERSION) + ); assert_eq!( vectors["canonicalVarsEncoding"], serde_json::json!(STACK_WITNESS_CANONICAL_VARS_ENCODING) @@ -1269,6 +1351,11 @@ mod tests { helper_frame: "EINT", helper_entrypoint: "dispatch_intent", canonical_vars: STACK_WITNESS_CREATE_BUFFER_VARS, + footprint_reads: &[], + footprint_writes: &[], + footprint_creates: &["Buffer"], + footprint_forbids: STACK_WITNESS_FORBIDDEN_FOOTPRINTS, + helper_fields: STACK_WITNESS_HELPER_MUTATION_FIELDS, }, ); assert_stack_witness_vector( @@ -1281,6 +1368,11 @@ mod tests { helper_frame: "EINT", helper_entrypoint: "dispatch_intent", canonical_vars: STACK_WITNESS_REPLACE_RANGE_VARS, + footprint_reads: &["Buffer"], + footprint_writes: &["Buffer"], + footprint_creates: &["Tick", "Receipt"], + footprint_forbids: STACK_WITNESS_FORBIDDEN_FOOTPRINTS, + helper_fields: STACK_WITNESS_HELPER_MUTATION_FIELDS, }, ); assert_stack_witness_vector( @@ -1293,6 +1385,11 @@ mod tests { helper_frame: "QueryView", helper_entrypoint: "observe", canonical_vars: STACK_WITNESS_TEXT_WINDOW_VARS, + footprint_reads: &["Buffer", "Tick", "Receipt"], + footprint_writes: &[], + footprint_creates: &[], + footprint_forbids: STACK_WITNESS_FORBIDDEN_FOOTPRINTS, + helper_fields: STACK_WITNESS_HELPER_QUERY_FIELDS, }, ); @@ -1316,6 +1413,11 @@ mod tests { helper_frame: &'static str, helper_entrypoint: &'static str, canonical_vars: &'static [u8], + footprint_reads: &'static [&'static str], + footprint_writes: &'static [&'static str], + footprint_creates: &'static [&'static str], + footprint_forbids: &'static [&'static str], + helper_fields: &'static [&'static str], } fn assert_stack_witness_vector( @@ -1348,6 +1450,23 @@ mod tests { vector["canonicalVarsBytes"].as_str().map(str::as_bytes), Some(expected.canonical_vars) ); + assert_json_string_array( + &vector["declaredFootprint"]["reads"], + expected.footprint_reads, + ); + assert_json_string_array( + &vector["declaredFootprint"]["writes"], + expected.footprint_writes, + ); + assert_json_string_array( + &vector["declaredFootprint"]["creates"], + expected.footprint_creates, + ); + assert_json_string_array( + &vector["declaredFootprint"]["forbids"], + expected.footprint_forbids, + ); + assert_json_string_array(&vector["helperShape"]["fields"], expected.helper_fields); } fn stack_witness_vector<'a>( @@ -1366,6 +1485,19 @@ mod tests { bytes.iter().map(|byte| format!("{byte:02x}")).collect() } + fn assert_json_string_array(value: &serde_json::Value, expected: &[&str]) { + let actual = value + .as_array() + .expect("fixture vector field should be an array") + .iter() + .map(|item| { + item.as_str() + .expect("fixture vector item should be a string") + }) + .collect::>(); + assert_eq!(actual.as_slice(), expected); + } + #[test] fn stack_witness_fixture_registry_names_mutations() { assert_eq!( @@ -1939,56 +2071,50 @@ mod tests { #[test] fn stack_witness_create_buffer_and_replace_range_enter_dispatch_intent() { let mut kernel = WarpKernel::new().unwrap(); + + admit_stack_witness_create_buffer(&mut kernel); + admit_stack_witness_replace_range(&mut kernel); + } + + fn admit_stack_witness_create_buffer(kernel: &mut WarpKernel) { let create_buffer = pack_intent_v1( STACK_WITNESS_CREATE_BUFFER_OP_ID, &stack_witness_create_buffer_vars(), ) .unwrap(); - let replace_range = pack_intent_v1( - STACK_WITNESS_REPLACE_RANGE_OP_ID, - &stack_witness_replace_range_vars(), - ) - .unwrap(); - let create = kernel.dispatch_intent(&create_buffer).unwrap(); assert!(create.accepted); assert_eq!(create.intent_id.len(), 32); - let create_run = start_until_idle(&mut kernel, Some(4)); + let create_run = start_until_idle(kernel, Some(4)); assert_eq!( create_run.scheduler_status.last_run_completion, Some(RunCompletion::Quiesced) ); + } + fn admit_stack_witness_replace_range(kernel: &mut WarpKernel) { + let replace_range = pack_intent_v1( + STACK_WITNESS_REPLACE_RANGE_OP_ID, + &stack_witness_replace_range_vars(), + ) + .unwrap(); let replace = kernel.dispatch_intent(&replace_range).unwrap(); assert!(replace.accepted); assert_eq!(replace.intent_id.len(), 32); - let replace_run = start_until_idle(&mut kernel, Some(4)); + let replace_run = start_until_idle(kernel, Some(4)); assert_eq!( replace_run.scheduler_status.last_run_completion, Some(RunCompletion::Quiesced) ); } - #[test] - fn stack_witness_text_window_query_returns_reading_envelope_and_query_bytes() { - let mut kernel = WarpKernel::new().unwrap(); - let create_buffer = pack_intent_v1( - STACK_WITNESS_CREATE_BUFFER_OP_ID, - &stack_witness_create_buffer_vars(), - ) - .unwrap(); - let replace_range = pack_intent_v1( - STACK_WITNESS_REPLACE_RANGE_OP_ID, - &stack_witness_replace_range_vars(), - ) - .unwrap(); - - kernel.dispatch_intent(&create_buffer).unwrap(); - start_until_idle(&mut kernel, Some(4)); - kernel.dispatch_intent(&replace_range).unwrap(); - start_until_idle(&mut kernel, Some(4)); + fn admit_stack_witness_fixture_history(kernel: &mut WarpKernel) { + admit_stack_witness_create_buffer(kernel); + admit_stack_witness_replace_range(kernel); + } - let request = abi_builtin_one_shot( + fn stack_witness_text_window_request(kernel: &WarpKernel) -> AbiObservationRequest { + abi_builtin_one_shot( AbiObservationCoordinate { worldline_id: abi_worldline_id(kernel.default_worldline), at: echo_wasm_abi::kernel_port::ObservationAt::Frontier, @@ -1998,7 +2124,68 @@ mod tests { query_id: STACK_WITNESS_TEXT_WINDOW_QUERY_ID, vars_bytes: stack_witness_text_window_vars(), }, + ) + } + + fn expected_stack_witness_text_window_artifact_hash() -> Vec { + let mut hasher = blake3::Hasher::new(); + hasher.update(STACK_WITNESS_FIXTURE_ARTIFACT_ID.as_bytes()); + hasher.update(&STACK_WITNESS_TEXT_WINDOW_QUERY_ID.to_le_bytes()); + hasher.update(STACK_WITNESS_TEXT_WINDOW_VARS); + hasher.update(STACK_WITNESS_TEXT_WINDOW_BYTES); + hasher.finalize().as_bytes().to_vec() + } + + #[test] + fn stack_witness_text_window_obstructs_without_fixture_history() { + let kernel = WarpKernel::new().unwrap(); + let request = stack_witness_text_window_request(&kernel); + + let error = kernel + .observe(request) + .expect_err("textWindow must not materialize without fixture mutation history"); + + assert_eq!(error.code, error_codes::OBSERVATION_UNAVAILABLE); + } + + #[test] + fn stack_witness_text_window_respects_bounded_payload_budget() { + let mut kernel = WarpKernel::new().unwrap(); + admit_stack_witness_fixture_history(&mut kernel); + let mut request = stack_witness_text_window_request(&kernel); + request.budget = AbiObservationReadBudget::Bounded { + max_payload_bytes: 4, + max_witness_refs: 0, + }; + + let error = kernel + .observe(request) + .expect_err("textWindow must obstruct when payload exceeds the read budget"); + + assert_eq!(error.code, error_codes::OBSERVATION_BUDGET_EXCEEDED); + } + + #[test] + fn stack_witness_text_window_artifact_hash_is_deterministic_identity() { + let mut kernel = WarpKernel::new().unwrap(); + admit_stack_witness_fixture_history(&mut kernel); + let request = stack_witness_text_window_request(&kernel); + + let artifact = kernel + .observe(request) + .expect("textWindow QueryView should return a reading artifact"); + + assert_eq!( + artifact.artifact_hash, + expected_stack_witness_text_window_artifact_hash() ); + } + + #[test] + fn stack_witness_text_window_query_returns_reading_envelope_and_query_bytes() { + let mut kernel = WarpKernel::new().unwrap(); + admit_stack_witness_fixture_history(&mut kernel); + let request = stack_witness_text_window_request(&kernel); let artifact = kernel .observe(request) @@ -2025,9 +2212,9 @@ mod tests { artifact.reading.residual_posture, AbiReadingResidualPosture::Complete ); - assert!( - !artifact.artifact_hash.is_empty(), - "reading identity must be carried by the observation artifact" + assert_eq!( + artifact.artifact_hash, + expected_stack_witness_text_window_artifact_hash() ); } diff --git a/docs/design/stack-witness-0001-jedit-file-history.md b/docs/design/stack-witness-0001-jedit-file-history.md index 78ee67f7..470b5cef 100644 --- a/docs/design/stack-witness-0001-jedit-file-history.md +++ b/docs/design/stack-witness-0001-jedit-file-history.md @@ -3,7 +3,7 @@ # Stack Witness 0001 - jedit File History Walking Skeleton -Status: RED witness spec +Status: GREEN witness spec Scope: first jedit-through-Echo executable story. This witness is the first serious stack slice. It exists to prevent the stack @@ -193,9 +193,16 @@ skeleton for this witness: - `createBuffer` and `replaceRange("hello")` enter through `dispatch_intent` and the existing scheduler path; - the Stack Witness `textWindow` QueryView routes to a fixture observer; -- the fixture observer returns `ReadingEnvelope + QueryBytes("hello")`; -- Echo mirrors Wesley's Stack Witness 0001 fixture vector and verifies op ids, - helper entrypoints, buffer-inclusive canonical vars, and expected query bytes +- the fixture observer only returns `ReadingEnvelope + QueryBytes("hello")` + after fixture `createBuffer` and `replaceRange("hello")` history has been + admitted and materialized; +- the fixture observer enforces bounded payload budgets and obstructs if the + caller budgets fewer than five payload bytes; +- the fixture observer carries a deterministic BLAKE3 artifact hash over the + fixture artifact id, query id, canonical vars, and payload bytes; +- Echo mirrors Wesley's Stack Witness 0001 fixture vector and verifies artifact + family/schema/version, op ids, helper entrypoints, helper fields, + buffer-inclusive canonical vars, declared footprints, and expected query bytes against that artifact shape; - Echo core still does not expose public jedit, editor, rope, buffer, cursor, or selection APIs. @@ -209,18 +216,35 @@ cargo test -p warp-wasm --features engine stack_witness_ Current result: ```text -running 4 tests +running 8 tests test warp_kernel::tests::stack_witness_fixture_registry_names_mutations ... ok +test warp_kernel::tests::stack_witness_fixture_vectors_match_wesley_artifact_shape ... ok test warp_kernel::tests::stack_witness_contract_intent_without_installed_artifact_obstructs ... ok +test warp_kernel::tests::stack_witness_text_window_obstructs_without_fixture_history ... ok test warp_kernel::tests::stack_witness_create_buffer_and_replace_range_enter_dispatch_intent ... ok +test warp_kernel::tests::stack_witness_text_window_respects_bounded_payload_budget ... ok +test warp_kernel::tests::stack_witness_text_window_artifact_hash_is_deterministic_identity ... ok test warp_kernel::tests::stack_witness_text_window_query_returns_reading_envelope_and_query_bytes ... ok -test result: ok. 4 passed; 0 failed; 0 ignored; 0 measured; 44 filtered out +test result: ok. 8 passed; 0 failed; 0 ignored; 0 measured; 44 filtered out ``` -This is intentionally fixture scaffolding. Do not generalize Echo further from -this state. The next stack move is for Wesley to replace the cardboard cutout -with a generated fixture artifact shape: +Engine feature compile target: + +```sh +cargo check -p warp-wasm --target wasm32-unknown-unknown --features engine +``` + +Current result: + +```text +Finished `dev` profile [unoptimized + debuginfo] target(s) +``` + +This is intentionally fixture scaffolding. Wesley now publishes the fixture +artifact shape that Echo mirrors, but Echo must still avoid generalizing further +from this state. The next stack move is to replace more of the hand-authored +fixture assumption with generated helpers: - operation ids; - canonical vars; From ea03337065518764848f2f9471fcde0580bc7cb7 Mon Sep 17 00:00:00 2001 From: James Ross Date: Mon, 11 May 2026 10:09:01 -0700 Subject: [PATCH 10/12] fix(ci): keep Stack Witness test guard-clean --- Cargo.lock | 1 - crates/warp-wasm/Cargo.toml | 1 - crates/warp-wasm/src/warp_kernel.rs | 191 +++++++++++++--------------- 3 files changed, 88 insertions(+), 105 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index bd98c43e..a5f6d0ed 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2084,7 +2084,6 @@ dependencies = [ "js-sys", "serde", "serde-value", - "serde_json", "warp-core", "wasm-bindgen", "web-sys", diff --git a/crates/warp-wasm/Cargo.toml b/crates/warp-wasm/Cargo.toml index 66784b0a..2a8b232e 100644 --- a/crates/warp-wasm/Cargo.toml +++ b/crates/warp-wasm/Cargo.toml @@ -36,7 +36,6 @@ blake3 = "1.0" [dev-dependencies] warp-core = { workspace = true } -serde_json = "1.0" serde-value = "0.7" [package.metadata.wasm-pack.profile.release] diff --git a/crates/warp-wasm/src/warp_kernel.rs b/crates/warp-wasm/src/warp_kernel.rs index 875ccd53..27adef0f 100644 --- a/crates/warp-wasm/src/warp_kernel.rs +++ b/crates/warp-wasm/src/warp_kernel.rs @@ -1315,34 +1315,20 @@ mod tests { #[test] fn stack_witness_fixture_vectors_match_wesley_artifact_shape() { - let vectors: serde_json::Value = serde_json::from_str(include_str!( - "../test/fixtures/stack-witness-0001-vectors.json" - )) - .expect("Stack Witness fixture vectors should parse"); + let vectors = include_str!("../test/fixtures/stack-witness-0001-vectors.json"); - assert_eq!( - vectors["artifact"]["familyId"], - serde_json::json!(STACK_WITNESS_FIXTURE_FAMILY_ID) - ); - assert_eq!( - vectors["artifact"]["schemaId"], - serde_json::json!(STACK_WITNESS_FIXTURE_SCHEMA_ID) - ); - assert_eq!( - vectors["artifact"]["artifactId"], - serde_json::json!(STACK_WITNESS_FIXTURE_ARTIFACT_ID) - ); - assert_eq!( - vectors["artifact"]["version"], - serde_json::json!(STACK_WITNESS_FIXTURE_VERSION) - ); - assert_eq!( - vectors["canonicalVarsEncoding"], - serde_json::json!(STACK_WITNESS_CANONICAL_VARS_ENCODING) + assert_string_field(vectors, "familyId", STACK_WITNESS_FIXTURE_FAMILY_ID); + assert_string_field(vectors, "schemaId", STACK_WITNESS_FIXTURE_SCHEMA_ID); + assert_string_field(vectors, "artifactId", STACK_WITNESS_FIXTURE_ARTIFACT_ID); + assert_string_field(vectors, "version", STACK_WITNESS_FIXTURE_VERSION); + assert_string_field( + vectors, + "canonicalVarsEncoding", + STACK_WITNESS_CANONICAL_VARS_ENCODING, ); assert_stack_witness_vector( - &vectors, + vectors, StackWitnessVectorExpectation { name: "createBuffer", operation_type: "MUTATION", @@ -1359,7 +1345,7 @@ mod tests { }, ); assert_stack_witness_vector( - &vectors, + vectors, StackWitnessVectorExpectation { name: "replaceRange", operation_type: "MUTATION", @@ -1376,7 +1362,7 @@ mod tests { }, ); assert_stack_witness_vector( - &vectors, + vectors, StackWitnessVectorExpectation { name: "textWindow", operation_type: "QUERY", @@ -1393,15 +1379,13 @@ mod tests { }, ); - let text_window = stack_witness_vector(&vectors, "textWindow"); - assert_eq!(text_window["payloadCodec"], serde_json::json!("QueryBytes")); - assert_eq!( - text_window["envelope"], - serde_json::json!("ReadingEnvelope") - ); - assert_eq!( - text_window["expectedQueryBytesHex"], - serde_json::json!(lower_hex(STACK_WITNESS_TEXT_WINDOW_BYTES)) + let text_window = stack_witness_vector(vectors, "textWindow"); + assert_string_field(text_window, "payloadCodec", "QueryBytes"); + assert_string_field(text_window, "envelope", "ReadingEnvelope"); + assert_string_field( + text_window, + "expectedQueryBytesHex", + &lower_hex(STACK_WITNESS_TEXT_WINDOW_BYTES), ); } @@ -1420,82 +1404,83 @@ mod tests { helper_fields: &'static [&'static str], } - fn assert_stack_witness_vector( - vectors: &serde_json::Value, - expected: StackWitnessVectorExpectation, - ) { + fn assert_stack_witness_vector(vectors: &str, expected: StackWitnessVectorExpectation) { let vector = stack_witness_vector(vectors, expected.name); - assert_eq!( - vector["operationType"], - serde_json::json!(expected.operation_type) - ); - assert_eq!(vector["opIdDecimal"], serde_json::json!(expected.op_id)); - assert_eq!( - vector["opIdHex"], - serde_json::json!(format!("0x{:08x}", expected.op_id)) - ); - assert_eq!( - vector["helperKind"], - serde_json::json!(expected.helper_kind) - ); - assert_eq!( - vector["helperShape"]["frame"], - serde_json::json!(expected.helper_frame) - ); - assert_eq!( - vector["helperShape"]["entrypoint"], - serde_json::json!(expected.helper_entrypoint) - ); - assert_eq!( - vector["canonicalVarsBytes"].as_str().map(str::as_bytes), - Some(expected.canonical_vars) - ); - assert_json_string_array( - &vector["declaredFootprint"]["reads"], - expected.footprint_reads, - ); - assert_json_string_array( - &vector["declaredFootprint"]["writes"], - expected.footprint_writes, - ); - assert_json_string_array( - &vector["declaredFootprint"]["creates"], - expected.footprint_creates, - ); - assert_json_string_array( - &vector["declaredFootprint"]["forbids"], - expected.footprint_forbids, - ); - assert_json_string_array(&vector["helperShape"]["fields"], expected.helper_fields); - } - - fn stack_witness_vector<'a>( - vectors: &'a serde_json::Value, - name: &str, - ) -> &'a serde_json::Value { - vectors["operations"] - .as_array() - .expect("Stack Witness operations should be an array") - .iter() - .find(|operation| operation["name"].as_str() == Some(name)) - .expect("Stack Witness operation vector should exist") + assert_string_field(vector, "operationType", expected.operation_type); + assert_number_field(vector, "opIdDecimal", expected.op_id); + assert_string_field(vector, "opIdHex", &format!("0x{:08x}", expected.op_id)); + assert_string_field(vector, "helperKind", expected.helper_kind); + assert_string_field(vector, "frame", expected.helper_frame); + assert_string_field(vector, "entrypoint", expected.helper_entrypoint); + assert_string_field( + vector, + "canonicalVarsBytes", + std::str::from_utf8(expected.canonical_vars) + .expect("Stack Witness canonical vars should be UTF-8 fixture bytes"), + ); + assert_string_array_field(vector, "reads", expected.footprint_reads); + assert_string_array_field(vector, "writes", expected.footprint_writes); + assert_string_array_field(vector, "creates", expected.footprint_creates); + assert_string_array_field(vector, "forbids", expected.footprint_forbids); + assert_ordered_field_values(vector, "fields", expected.helper_fields); + } + + fn stack_witness_vector<'a>(vectors: &'a str, name: &str) -> &'a str { + let name_pattern = format!(r#""name": "{name}""#); + let start = vectors + .find(&name_pattern) + .expect("Stack Witness operation vector should exist"); + let end = vectors[start..] + .find("\n }") + .expect("Stack Witness operation vector should close"); + &vectors[start..start + end] } fn lower_hex(bytes: &[u8]) -> String { bytes.iter().map(|byte| format!("{byte:02x}")).collect() } - fn assert_json_string_array(value: &serde_json::Value, expected: &[&str]) { - let actual = value - .as_array() - .expect("fixture vector field should be an array") + fn assert_string_field(document: &str, field: &str, expected: &str) { + let needle = format!(r#""{field}": "{expected}""#); + assert!( + document.contains(&needle), + "fixture vector should contain string field {field}={expected}" + ); + } + + fn assert_number_field(document: &str, field: &str, expected: u32) { + let needle = format!(r#""{field}": {expected}"#); + assert!( + document.contains(&needle), + "fixture vector should contain numeric field {field}={expected}" + ); + } + + fn assert_string_array_field(document: &str, field: &str, expected: &[&str]) { + let items = expected .iter() - .map(|item| { - item.as_str() - .expect("fixture vector item should be a string") - }) - .collect::>(); - assert_eq!(actual.as_slice(), expected); + .map(|item| format!(r#""{item}""#)) + .collect::>() + .join(", "); + let needle = format!(r#""{field}": [{items}]"#); + assert!( + document.contains(&needle), + "fixture vector should contain string array field {field}" + ); + } + + fn assert_ordered_field_values(document: &str, field: &str, expected: &[&str]) { + let field_pattern = format!(r#""{field}": ["#); + let mut cursor = document + .find(&field_pattern) + .expect("fixture vector should contain ordered field list"); + for item in expected { + let item_pattern = format!(r#""{item}""#); + let relative = document[cursor..] + .find(&item_pattern) + .expect("fixture vector field item should appear in order"); + cursor += relative + item_pattern.len(); + } } #[test] From 599ac768795701422989b89fe8e9acdb471adb0e Mon Sep 17 00:00:00 2001 From: James Ross Date: Mon, 11 May 2026 10:23:15 -0700 Subject: [PATCH 11/12] test(stack): align Echo witness with fixture byte terminology --- CHANGELOG.md | 7 ++-- crates/warp-wasm/src/warp_kernel.rs | 26 ++++++++------- .../fixtures/stack-witness-0001-vectors.json | 15 +++++---- .../stack-witness-0001-jedit-file-history.md | 32 ++++++++++++++----- 4 files changed, 51 insertions(+), 29 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2fcacf93..946b48e3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,8 +9,9 @@ - Stack Witness 0001 drift lock — `warp-wasm` now mirrors Wesley's fixture vectors for the jedit-through-Echo walking skeleton and verifies Echo's - fixture op ids, buffer-inclusive canonical vars, helper entrypoints, and - expected `QueryBytes("hello")` payload bytes against that vector. + fixture op ids, buffer-inclusive fixture vars bytes, the + `wesley-binary/v0` target codec marker, helper entrypoints, and expected + `QueryBytes("hello")` payload bytes against that vector. - `echo-registry-api::verify_contract_artifact(...)` — generic load-time verification for Wesley-generated registries, including schema/codec/layout checks, expected footprint certificate hashes, optional generated artifact @@ -63,6 +64,8 @@ BLAKE3 artifact hash instead of a dummy reading identity. - Strengthened Echo's Wesley fixture-vector drift lock to cover artifact family, schema, version, declared footprints, and generated helper field shapes. +- Corrected Stack Witness 0001 terminology so the current semicolon-kv byte + strings are fixture vars only, not Wesley's future runtime codec bytes. ### Fixed (PR #326 follow-up) diff --git a/crates/warp-wasm/src/warp_kernel.rs b/crates/warp-wasm/src/warp_kernel.rs index 27adef0f..fbf9d4f2 100644 --- a/crates/warp-wasm/src/warp_kernel.rs +++ b/crates/warp-wasm/src/warp_kernel.rs @@ -133,7 +133,7 @@ const STACK_WITNESS_REPLACE_RANGE_OP_ID: u32 = 0x5357_0002; const STACK_WITNESS_TEXT_WINDOW_QUERY_ID: u32 = 0x5357_1001; const STACK_WITNESS_FIXTURE_ARTIFACT_ID: &str = "fixture-file-history-v0"; #[cfg(test)] -const STACK_WITNESS_CANONICAL_VARS_ENCODING: &str = "utf8-semicolon-kv/v0"; +const STACK_WITNESS_FIXTURE_VARS_ENCODING: &str = "utf8-semicolon-kv/v0"; #[cfg(test)] const STACK_WITNESS_CREATE_BUFFER_VARS: &[u8] = b"stack-witness-0001/createBuffer;name=demo.txt;artifact=fixture-file-history-v0"; @@ -1285,16 +1285,17 @@ mod tests { const STACK_WITNESS_FIXTURE_FAMILY_ID: &str = "stack-witness-0001.file-history"; const STACK_WITNESS_FIXTURE_SCHEMA_ID: &str = "stack-witness-0001.file-history.v0"; const STACK_WITNESS_FIXTURE_VERSION: &str = "0"; + const STACK_WITNESS_TARGET_CODEC: &str = "wesley-binary/v0"; const STACK_WITNESS_HELPER_MUTATION_FIELDS: &[&str] = &[ "contract_artifact_id", "operation_id", - "canonical_vars_bytes", + "fixture_vars_bytes", "declared_footprint", ]; const STACK_WITNESS_HELPER_QUERY_FIELDS: &[&str] = &[ "contract_artifact_id", "query_id", - "canonical_vars_bytes", + "fixture_vars_bytes", "reading_envelope", "query_bytes", ]; @@ -1323,9 +1324,10 @@ mod tests { assert_string_field(vectors, "version", STACK_WITNESS_FIXTURE_VERSION); assert_string_field( vectors, - "canonicalVarsEncoding", - STACK_WITNESS_CANONICAL_VARS_ENCODING, + "fixtureVarsEncoding", + STACK_WITNESS_FIXTURE_VARS_ENCODING, ); + assert_string_field(vectors, "targetCodec", STACK_WITNESS_TARGET_CODEC); assert_stack_witness_vector( vectors, @@ -1336,7 +1338,7 @@ mod tests { helper_kind: "EINT", helper_frame: "EINT", helper_entrypoint: "dispatch_intent", - canonical_vars: STACK_WITNESS_CREATE_BUFFER_VARS, + fixture_vars: STACK_WITNESS_CREATE_BUFFER_VARS, footprint_reads: &[], footprint_writes: &[], footprint_creates: &["Buffer"], @@ -1353,7 +1355,7 @@ mod tests { helper_kind: "EINT", helper_frame: "EINT", helper_entrypoint: "dispatch_intent", - canonical_vars: STACK_WITNESS_REPLACE_RANGE_VARS, + fixture_vars: STACK_WITNESS_REPLACE_RANGE_VARS, footprint_reads: &["Buffer"], footprint_writes: &["Buffer"], footprint_creates: &["Tick", "Receipt"], @@ -1370,7 +1372,7 @@ mod tests { helper_kind: "QueryView", helper_frame: "QueryView", helper_entrypoint: "observe", - canonical_vars: STACK_WITNESS_TEXT_WINDOW_VARS, + fixture_vars: STACK_WITNESS_TEXT_WINDOW_VARS, footprint_reads: &["Buffer", "Tick", "Receipt"], footprint_writes: &[], footprint_creates: &[], @@ -1396,7 +1398,7 @@ mod tests { helper_kind: &'static str, helper_frame: &'static str, helper_entrypoint: &'static str, - canonical_vars: &'static [u8], + fixture_vars: &'static [u8], footprint_reads: &'static [&'static str], footprint_writes: &'static [&'static str], footprint_creates: &'static [&'static str], @@ -1414,9 +1416,9 @@ mod tests { assert_string_field(vector, "entrypoint", expected.helper_entrypoint); assert_string_field( vector, - "canonicalVarsBytes", - std::str::from_utf8(expected.canonical_vars) - .expect("Stack Witness canonical vars should be UTF-8 fixture bytes"), + "fixtureVarsBytes", + std::str::from_utf8(expected.fixture_vars) + .expect("Stack Witness fixture vars should be UTF-8 fixture bytes"), ); assert_string_array_field(vector, "reads", expected.footprint_reads); assert_string_array_field(vector, "writes", expected.footprint_writes); diff --git a/crates/warp-wasm/test/fixtures/stack-witness-0001-vectors.json b/crates/warp-wasm/test/fixtures/stack-witness-0001-vectors.json index 27fe2b66..c2c5997a 100644 --- a/crates/warp-wasm/test/fixtures/stack-witness-0001-vectors.json +++ b/crates/warp-wasm/test/fixtures/stack-witness-0001-vectors.json @@ -5,7 +5,8 @@ "artifactId": "fixture-file-history-v0", "version": "0" }, - "canonicalVarsEncoding": "utf8-semicolon-kv/v0", + "fixtureVarsEncoding": "utf8-semicolon-kv/v0", + "targetCodec": "wesley-binary/v0", "operations": [ { "name": "createBuffer", @@ -13,7 +14,7 @@ "opIdHex": "0x53570001", "opIdDecimal": 1398210561, "helperKind": "EINT", - "canonicalVarsBytes": "stack-witness-0001/createBuffer;name=demo.txt;artifact=fixture-file-history-v0", + "fixtureVarsBytes": "stack-witness-0001/createBuffer;name=demo.txt;artifact=fixture-file-history-v0", "declaredFootprint": { "reads": [], "writes": [], @@ -26,7 +27,7 @@ "fields": [ "contract_artifact_id", "operation_id", - "canonical_vars_bytes", + "fixture_vars_bytes", "declared_footprint" ] } @@ -37,7 +38,7 @@ "opIdHex": "0x53570002", "opIdDecimal": 1398210562, "helperKind": "EINT", - "canonicalVarsBytes": "stack-witness-0001/replaceRange;bufferId=demo.txt;basis=B0;coord=utf8-bytes;start=0;end=0;text=hello;artifact=fixture-file-history-v0", + "fixtureVarsBytes": "stack-witness-0001/replaceRange;bufferId=demo.txt;basis=B0;coord=utf8-bytes;start=0;end=0;text=hello;artifact=fixture-file-history-v0", "declaredFootprint": { "reads": ["Buffer"], "writes": ["Buffer"], @@ -50,7 +51,7 @@ "fields": [ "contract_artifact_id", "operation_id", - "canonical_vars_bytes", + "fixture_vars_bytes", "declared_footprint" ] } @@ -61,7 +62,7 @@ "opIdHex": "0x53571001", "opIdDecimal": 1398214657, "helperKind": "QueryView", - "canonicalVarsBytes": "stack-witness-0001/textWindow;bufferId=demo.txt;basis=B1;coord=utf8-bytes;start=0;length=5;artifact=fixture-file-history-v0", + "fixtureVarsBytes": "stack-witness-0001/textWindow;bufferId=demo.txt;basis=B1;coord=utf8-bytes;start=0;length=5;artifact=fixture-file-history-v0", "payloadCodec": "QueryBytes", "envelope": "ReadingEnvelope", "expectedQueryBytesHex": "68656c6c6f", @@ -77,7 +78,7 @@ "fields": [ "contract_artifact_id", "query_id", - "canonical_vars_bytes", + "fixture_vars_bytes", "reading_envelope", "query_bytes" ] diff --git a/docs/design/stack-witness-0001-jedit-file-history.md b/docs/design/stack-witness-0001-jedit-file-history.md index 470b5cef..1136ce06 100644 --- a/docs/design/stack-witness-0001-jedit-file-history.md +++ b/docs/design/stack-witness-0001-jedit-file-history.md @@ -44,12 +44,26 @@ expected to generate: - manifest or artifact id; - operation ids; -- canonical vars bytes; +- fixture vars bytes; +- target codec metadata; - declared footprints; - mutation handler; - query handler; - artifact identity. +## Fixture Encoding vs Target Codec + +Stack Witness 0001 currently uses `fixtureVarsEncoding: +utf8-semicolon-kv/v0` and `fixtureVarsBytes` only as temporary, +human-readable fixture metadata. These semicolon-kv strings are not Wesley's +runtime codec and should not fossilize as the architecture's canonical variable +representation. + +The durable target is `targetCodec: wesley-binary/v0`: Wesley-generated +deterministic binary codecs shared by Rust and TypeScript. Echo consumes bytes +plus artifact identity and should not care whether those bytes came from Rust, +TypeScript, WASM, CLI, or network transport. + ## Operation Surface ### `createBuffer` @@ -61,7 +75,8 @@ Minimum variables: - `name = "demo.txt"`; - contract artifact id; - operation id; -- canonical vars bytes. +- fixture vars bytes for this witness, later Wesley-generated binary codec + bytes. ### `replaceRange` @@ -199,11 +214,11 @@ skeleton for this witness: - the fixture observer enforces bounded payload budgets and obstructs if the caller budgets fewer than five payload bytes; - the fixture observer carries a deterministic BLAKE3 artifact hash over the - fixture artifact id, query id, canonical vars, and payload bytes; + fixture artifact id, query id, fixture vars bytes, and payload bytes; - Echo mirrors Wesley's Stack Witness 0001 fixture vector and verifies artifact family/schema/version, op ids, helper entrypoints, helper fields, - buffer-inclusive canonical vars, declared footprints, and expected query bytes - against that artifact shape; + buffer-inclusive fixture vars bytes, target codec, declared footprints, and + expected query bytes against that artifact shape; - Echo core still does not expose public jedit, editor, rope, buffer, cursor, or selection APIs. @@ -247,7 +262,8 @@ from this state. The next stack move is to replace more of the hand-authored fixture assumption with generated helpers: - operation ids; -- canonical vars; +- fixture vars bytes; +- `targetCodec: wesley-binary/v0`; - footprints; - EINT helpers; - QueryView helper; @@ -269,8 +285,8 @@ The witness should eventually produce one stack trace: Each repo can consume or produce part of this trace: -- Wesley produces generated helpers, canonical vars, operation ids, footprint - hashes, and fixture vectors. +- Wesley produces generated helpers, fixture vectors, operation ids, footprint + hashes, and eventually the canonical binary codecs named by `targetCodec`. - Echo admits intents, retains provenance, observes QueryView, and emits the reading artifact. - jedit consumes generated helpers and renders the bounded payload. From cf742160ba44142892b7c8301e78259107ff5205 Mon Sep 17 00:00:00 2001 From: James Ross Date: Mon, 11 May 2026 10:40:25 -0700 Subject: [PATCH 12/12] fix(stack): validate fixture query integrity --- CHANGELOG.md | 4 + .../RE-030-queryview-optic-convergence.md | 68 ++++++++ crates/warp-wasm/src/warp_kernel.rs | 156 ++++++++++++++++-- 3 files changed, 212 insertions(+), 16 deletions(-) create mode 100644 backlog/bad-code/RE-030-queryview-optic-convergence.md diff --git a/CHANGELOG.md b/CHANGELOG.md index 946b48e3..e6c99f23 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -66,6 +66,10 @@ schema, version, declared footprints, and generated helper field shapes. - Corrected Stack Witness 0001 terminology so the current semicolon-kv byte strings are fixture vars only, not Wesley's future runtime codec bytes. +- Hardened Stack Witness 0001 fixture integrity by validating fixture vars + before admission, requiring fixture intents to commit before `textWindow` + materializes, and rejecting fixture `QueryView` reads for non-default + worldlines. ### Fixed (PR #326 follow-up) diff --git a/backlog/bad-code/RE-030-queryview-optic-convergence.md b/backlog/bad-code/RE-030-queryview-optic-convergence.md new file mode 100644 index 00000000..09a5b26a --- /dev/null +++ b/backlog/bad-code/RE-030-queryview-optic-convergence.md @@ -0,0 +1,68 @@ + + + +# RE-030 — Converge QueryView Reads onto Optics + +Legend: [RE — Runtime Engine] + +## Problem + +Stack Witness 0001 currently proves `textWindow` through +`observe(QueryView)` using a narrow fixture shortcut. That keeps the first +jedit-through-Echo witness small, but it also leaves query reads outside the +stronger optic surface that should eventually own read identity, aperture +identity, basis validation, rights, budgets, and capability posture. + +`QueryView` should be the reading frame for a query-producing optic, not a +parallel read side door. + +## Desired Shape + +Model contract query reads as optic-shaped observations: + +```text +contract-authored optic + focus: worldline / basis / read identity + aperture: QueryBytes { + query_id + vars_digest + } + artifact identity + observer plan + rights posture + budget posture + evidence posture +``` + +The Stack Witness 0001 `textWindow` path should eventually move from direct +`observe(QueryView)` fixture interception to an optic aperture that produces +`ReadingEnvelope + QueryBytes("hello")`. + +## Non-Goals + +Do not do this before the walking skeleton is merged and stable. The current +PR should only close the direct-path integrity gaps: + +1. validate requested worldline, +2. validate fixture vars, +3. require committed fixture history before materializing the read. + +## Why + +Optics are the right long-term home for: + +1. durable read identity, +2. query aperture identity, +3. basis and worldline validation, +4. capability-scoped rights, +5. budget enforcement, +6. debugger/TTD explanation. + +Keeping the follow-up explicit prevents Stack Witness 0001 from fossilizing a +temporary direct `QueryView` shortcut into the permanent architecture. + +## Effort + +Medium — requires an optic-backed query aperture path plus migration of the +Stack Witness `textWindow` fixture tests from direct `observe(QueryView)` to +the optic read surface. diff --git a/crates/warp-wasm/src/warp_kernel.rs b/crates/warp-wasm/src/warp_kernel.rs index fbf9d4f2..c2f5fcfe 100644 --- a/crates/warp-wasm/src/warp_kernel.rs +++ b/crates/warp-wasm/src/warp_kernel.rs @@ -122,8 +122,8 @@ pub struct WarpKernel { next_run_id: RunId, /// Registry metadata (injected at construction, immutable after). registry: RegistryInfo, - stack_witness_create_buffer_admitted: bool, - stack_witness_replace_range_admitted: bool, + stack_witness_create_buffer_admitted_at: Option, + stack_witness_replace_range_admitted_at: Option, } const STACK_WITNESS_CONTRACT_OP_ID_MASK: u32 = 0xffff_0000; @@ -134,10 +134,8 @@ const STACK_WITNESS_TEXT_WINDOW_QUERY_ID: u32 = 0x5357_1001; const STACK_WITNESS_FIXTURE_ARTIFACT_ID: &str = "fixture-file-history-v0"; #[cfg(test)] const STACK_WITNESS_FIXTURE_VARS_ENCODING: &str = "utf8-semicolon-kv/v0"; -#[cfg(test)] const STACK_WITNESS_CREATE_BUFFER_VARS: &[u8] = b"stack-witness-0001/createBuffer;name=demo.txt;artifact=fixture-file-history-v0"; -#[cfg(test)] const STACK_WITNESS_REPLACE_RANGE_VARS: &[u8] = b"stack-witness-0001/replaceRange;bufferId=demo.txt;basis=B0;coord=utf8-bytes;start=0;end=0;text=hello;artifact=fixture-file-history-v0"; const STACK_WITNESS_TEXT_WINDOW_VARS: &[u8] = b"stack-witness-0001/textWindow;bufferId=demo.txt;basis=B1;coord=utf8-bytes;start=0;length=5;artifact=fixture-file-history-v0"; const STACK_WITNESS_TEXT_WINDOW_BYTES: &[u8] = b"hello"; @@ -263,8 +261,8 @@ impl WarpKernel { }, next_run_id: RunId::from_raw(1), registry, - stack_witness_create_buffer_admitted: false, - stack_witness_replace_range_admitted: false, + stack_witness_create_buffer_admitted_at: None, + stack_witness_replace_range_admitted_at: None, }) } @@ -723,6 +721,13 @@ impl WarpKernel { message: "Stack Witness 0001 fixture observer requires kernel-public rights".into(), }); } + if Self::to_core_worldline_id(&request.coordinate.worldline_id) != self.default_worldline { + return Err(AbiError { + code: error_codes::OBSERVATION_UNAVAILABLE, + message: "Stack Witness 0001 fixture observer requires the default worldline" + .into(), + }); + } let head = self.current_head()?; if !self.stack_witness_fixture_history_is_materialized(&head) { @@ -792,18 +797,43 @@ impl WarpKernel { } fn stack_witness_fixture_history_is_materialized(&self, head: &HeadInfo) -> bool { - self.stack_witness_create_buffer_admitted - && self.stack_witness_replace_range_admitted - && head.worldline_tick.0 >= 2 + let Some(create_buffer_admitted_at) = self.stack_witness_create_buffer_admitted_at else { + return false; + }; + let Some(replace_range_admitted_at) = self.stack_witness_replace_range_admitted_at else { + return false; + }; + head.worldline_tick.0 > create_buffer_admitted_at.0 + && head.worldline_tick.0 > replace_range_admitted_at.0 } - fn record_stack_witness_fixture_admission(&mut self, op_id: u32) { + fn validate_stack_witness_fixture_vars(op_id: u32, vars: &[u8]) -> Result<(), AbiError> { + let expected = match op_id { + STACK_WITNESS_CREATE_BUFFER_OP_ID => STACK_WITNESS_CREATE_BUFFER_VARS, + STACK_WITNESS_REPLACE_RANGE_OP_ID => STACK_WITNESS_REPLACE_RANGE_VARS, + _ => return Ok(()), + }; + if vars != expected { + return Err(AbiError { + code: error_codes::INVALID_INTENT, + message: "Stack Witness 0001 fixture vars do not match installed artifact".into(), + }); + } + Ok(()) + } + + fn record_stack_witness_fixture_admission( + &mut self, + op_id: u32, + vars: &[u8], + admitted_at: AbiWorldlineTick, + ) { match op_id { - STACK_WITNESS_CREATE_BUFFER_OP_ID => { - self.stack_witness_create_buffer_admitted = true; + STACK_WITNESS_CREATE_BUFFER_OP_ID if vars == STACK_WITNESS_CREATE_BUFFER_VARS => { + self.stack_witness_create_buffer_admitted_at = Some(admitted_at); } - STACK_WITNESS_REPLACE_RANGE_OP_ID => { - self.stack_witness_replace_range_admitted = true; + STACK_WITNESS_REPLACE_RANGE_OP_ID if vars == STACK_WITNESS_REPLACE_RANGE_VARS => { + self.stack_witness_replace_range_admitted_at = Some(admitted_at); } _ => {} } @@ -1005,7 +1035,7 @@ impl WarpKernel { impl KernelPort for WarpKernel { fn dispatch_intent(&mut self, intent_bytes: &[u8]) -> Result { - let (op_id, _vars) = unpack_intent_v1(intent_bytes).map_err(|e| AbiError { + let (op_id, vars) = unpack_intent_v1(intent_bytes).map_err(|e| AbiError { code: error_codes::INVALID_INTENT, message: format!( "malformed EINT envelope ({} bytes): {e}", @@ -1065,6 +1095,7 @@ impl KernelPort for WarpKernel { ), }); } + Self::validate_stack_witness_fixture_vars(op_id, vars)?; let envelope = IngressEnvelope::local_intent( IngressTarget::DefaultWriter { @@ -1078,7 +1109,8 @@ impl KernelPort for WarpKernel { Ok(disposition) => { let accepted = matches!(disposition, IngressDisposition::Accepted { .. }); if accepted { - self.record_stack_witness_fixture_admission(op_id); + let admitted_at = self.current_head()?.worldline_tick; + self.record_stack_witness_fixture_admission(op_id, vars, admitted_at); } self.refresh_scheduler_status(); Ok(DispatchResponse { @@ -2063,6 +2095,17 @@ mod tests { admit_stack_witness_replace_range(&mut kernel); } + fn advance_non_fixture_tick(kernel: &mut WarpKernel, vars: &[u8]) { + let intent = pack_intent_v1(1, vars).unwrap(); + let dispatch = kernel.dispatch_intent(&intent).unwrap(); + assert!(dispatch.accepted); + let run = start_until_idle(kernel, Some(4)); + assert_eq!( + run.scheduler_status.last_run_completion, + Some(RunCompletion::Quiesced) + ); + } + fn admit_stack_witness_create_buffer(kernel: &mut WarpKernel) { let create_buffer = pack_intent_v1( STACK_WITNESS_CREATE_BUFFER_OP_ID, @@ -2114,6 +2157,22 @@ mod tests { ) } + fn stack_witness_text_window_request_for_worldline( + worldline_id: WorldlineId, + ) -> AbiObservationRequest { + abi_builtin_one_shot( + AbiObservationCoordinate { + worldline_id: abi_worldline_id(worldline_id), + at: echo_wasm_abi::kernel_port::ObservationAt::Frontier, + }, + AbiObservationFrame::QueryView, + AbiObservationProjection::Query { + query_id: STACK_WITNESS_TEXT_WINDOW_QUERY_ID, + vars_bytes: stack_witness_text_window_vars(), + }, + ) + } + fn expected_stack_witness_text_window_artifact_hash() -> Vec { let mut hasher = blake3::Hasher::new(); hasher.update(STACK_WITNESS_FIXTURE_ARTIFACT_ID.as_bytes()); @@ -2135,6 +2194,71 @@ mod tests { assert_eq!(error.code, error_codes::OBSERVATION_UNAVAILABLE); } + #[test] + fn stack_witness_text_window_obstructs_wrong_worldline() { + let mut kernel = WarpKernel::new().unwrap(); + admit_stack_witness_fixture_history(&mut kernel); + let request = stack_witness_text_window_request_for_worldline(wl(99)); + + let error = kernel + .observe(request) + .expect_err("textWindow must not materialize for a non-default worldline"); + + assert_eq!(error.code, error_codes::OBSERVATION_UNAVAILABLE); + } + + #[test] + fn stack_witness_text_window_obstructs_pending_fixture_history_on_advanced_head() { + let mut kernel = WarpKernel::new().unwrap(); + advance_non_fixture_tick(&mut kernel, b"advance-a"); + advance_non_fixture_tick(&mut kernel, b"advance-b"); + assert_eq!( + kernel.current_head().unwrap().worldline_tick, + AbiWorldlineTick(2) + ); + + let create_buffer = pack_intent_v1( + STACK_WITNESS_CREATE_BUFFER_OP_ID, + &stack_witness_create_buffer_vars(), + ) + .unwrap(); + let replace_range = pack_intent_v1( + STACK_WITNESS_REPLACE_RANGE_OP_ID, + &stack_witness_replace_range_vars(), + ) + .unwrap(); + assert!(kernel.dispatch_intent(&create_buffer).unwrap().accepted); + assert!(kernel.dispatch_intent(&replace_range).unwrap().accepted); + let request = stack_witness_text_window_request(&kernel); + + let error = kernel + .observe(request) + .expect_err("textWindow must not materialize before fixture intents commit"); + + assert_eq!(error.code, error_codes::OBSERVATION_UNAVAILABLE); + } + + #[test] + fn stack_witness_fixture_mutations_reject_wrong_fixture_vars() { + let mut kernel = WarpKernel::new().unwrap(); + for (op_id, vars) in [ + ( + STACK_WITNESS_CREATE_BUFFER_OP_ID, + b"wrong-create".as_slice(), + ), + ( + STACK_WITNESS_REPLACE_RANGE_OP_ID, + b"wrong-replace".as_slice(), + ), + ] { + let intent = pack_intent_v1(op_id, vars).unwrap(); + let error = kernel + .dispatch_intent(&intent) + .expect_err("fixture mutation vars must match the installed fixture bytes"); + assert_eq!(error.code, error_codes::INVALID_INTENT); + } + } + #[test] fn stack_witness_text_window_respects_bounded_payload_budget() { let mut kernel = WarpKernel::new().unwrap();