diff --git a/CHANGELOG.md b/CHANGELOG.md index 6232acea..e6c99f23 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,11 @@ ### 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 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 @@ -49,6 +54,23 @@ ### 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. +- 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) - Added regression coverage that rejects trailing whitespace in the committed diff --git a/Cargo.lock b/Cargo.lock index 7da1da26..a5f6d0ed 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/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/Cargo.toml b/crates/warp-wasm/Cargo.toml index 3dac4896..2a8b232e 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 } @@ -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/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..c2f5fcfe 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, @@ -118,6 +122,63 @@ pub struct WarpKernel { next_run_id: RunId, /// Registry metadata (injected at construction, immutable after). registry: RegistryInfo, + stack_witness_create_buffer_admitted_at: Option, + stack_witness_replace_range_admitted_at: Option, +} + +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_FIXTURE_ARTIFACT_ID: &str = "fixture-file-history-v0"; +#[cfg(test)] +const STACK_WITNESS_FIXTURE_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"; +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 { + 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, + } +} + +fn 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() } impl WarpKernel { @@ -200,6 +261,8 @@ impl WarpKernel { }, next_run_id: RunId::from_raw(1), registry, + stack_witness_create_buffer_admitted_at: None, + stack_witness_replace_range_admitted_at: None, }) } @@ -634,6 +697,148 @@ 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(), + }); + } + 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) { + 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 { + max_payload_bytes, + max_witness_refs, + } => AbiReadingBudgetPosture::Bounded { + max_payload_bytes, + payload_bytes, + 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: 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 { + 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 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 if vars == STACK_WITNESS_CREATE_BUFFER_VARS => { + self.stack_witness_create_buffer_admitted_at = Some(admitted_at); + } + STACK_WITNESS_REPLACE_RANGE_OP_ID if vars == STACK_WITNESS_REPLACE_RANGE_VARS => { + self.stack_witness_replace_range_admitted_at = Some(admitted_at); + } + _ => {} + } + } + pub(crate) fn current_head(&self) -> Result { let request = ObservationRequest::builtin_one_shot( ObservationCoordinate { @@ -830,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}", @@ -880,6 +1085,18 @@ impl KernelPort for WarpKernel { })?; } + 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!( + "contract artifact is not installed for Stack Witness 0001 op id {op_id}" + ), + }); + } + Self::validate_stack_witness_fixture_vars(op_id, vars)?; + let envelope = IngressEnvelope::local_intent( IngressTarget::DefaultWriter { worldline_id: self.default_worldline, @@ -891,6 +1108,10 @@ impl KernelPort for WarpKernel { match self.runtime.ingest(envelope) { Ok(disposition) => { let accepted = matches!(disposition, IngressDisposition::Accepted { .. }); + if accepted { + 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 { accepted, @@ -942,6 +1163,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()) } @@ -1089,6 +1313,234 @@ mod tests { } } + const STACK_WITNESS_UNKNOWN_OP_ID: u32 = 0x5357_00ff; + 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", + "fixture_vars_bytes", + "declared_footprint", + ]; + const STACK_WITNESS_HELPER_QUERY_FIELDS: &[&str] = &[ + "contract_artifact_id", + "query_id", + "fixture_vars_bytes", + "reading_envelope", + "query_bytes", + ]; + const STACK_WITNESS_FORBIDDEN_FOOTPRINTS: &[&str] = + &["AstState", "Diagnostics", "GitWitness", "UiState"]; + + fn stack_witness_create_buffer_vars() -> Vec { + STACK_WITNESS_CREATE_BUFFER_VARS.to_vec() + } + + fn stack_witness_replace_range_vars() -> Vec { + STACK_WITNESS_REPLACE_RANGE_VARS.to_vec() + } + + fn stack_witness_text_window_vars() -> Vec { + STACK_WITNESS_TEXT_WINDOW_VARS.to_vec() + } + + #[test] + fn stack_witness_fixture_vectors_match_wesley_artifact_shape() { + let vectors = include_str!("../test/fixtures/stack-witness-0001-vectors.json"); + + 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, + "fixtureVarsEncoding", + STACK_WITNESS_FIXTURE_VARS_ENCODING, + ); + assert_string_field(vectors, "targetCodec", STACK_WITNESS_TARGET_CODEC); + + 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", + fixture_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( + 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", + fixture_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( + vectors, + StackWitnessVectorExpectation { + name: "textWindow", + operation_type: "QUERY", + op_id: STACK_WITNESS_TEXT_WINDOW_QUERY_ID, + helper_kind: "QueryView", + helper_frame: "QueryView", + helper_entrypoint: "observe", + fixture_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, + }, + ); + + 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), + ); + } + + struct StackWitnessVectorExpectation { + name: &'static str, + operation_type: &'static str, + op_id: u32, + helper_kind: &'static str, + helper_frame: &'static str, + helper_entrypoint: &'static str, + fixture_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(vectors: &str, expected: StackWitnessVectorExpectation) { + let vector = stack_witness_vector(vectors, expected.name); + 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, + "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); + 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_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| 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] + 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); @@ -1615,6 +2067,268 @@ 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_UNKNOWN_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_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 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, + &stack_witness_create_buffer_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(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(kernel, Some(4)); + assert_eq!( + replace_run.scheduler_status.last_run_completion, + Some(RunCompletion::Quiesced) + ); + } + + fn admit_stack_witness_fixture_history(kernel: &mut WarpKernel) { + admit_stack_witness_create_buffer(kernel); + admit_stack_witness_replace_range(kernel); + } + + 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, + }, + AbiObservationFrame::QueryView, + AbiObservationProjection::Query { + query_id: STACK_WITNESS_TEXT_WINDOW_QUERY_ID, + vars_bytes: stack_witness_text_window_vars(), + }, + ) + } + + 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()); + 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_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(); + 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) + .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_eq!( + artifact.artifact_hash, + expected_stack_witness_text_window_artifact_hash() + ); + } + #[test] fn import_suffix_intent_rejects_malformed_payload_without_ingress() { let mut kernel = WarpKernel::new().unwrap(); @@ -2020,14 +2734,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/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..c2c5997a --- /dev/null +++ b/crates/warp-wasm/test/fixtures/stack-witness-0001-vectors.json @@ -0,0 +1,88 @@ +{ + "artifact": { + "familyId": "stack-witness-0001.file-history", + "schemaId": "stack-witness-0001.file-history.v0", + "artifactId": "fixture-file-history-v0", + "version": "0" + }, + "fixtureVarsEncoding": "utf8-semicolon-kv/v0", + "targetCodec": "wesley-binary/v0", + "operations": [ + { + "name": "createBuffer", + "operationType": "MUTATION", + "opIdHex": "0x53570001", + "opIdDecimal": 1398210561, + "helperKind": "EINT", + "fixtureVarsBytes": "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", + "fixture_vars_bytes", + "declared_footprint" + ] + } + }, + { + "name": "replaceRange", + "operationType": "MUTATION", + "opIdHex": "0x53570002", + "opIdDecimal": 1398210562, + "helperKind": "EINT", + "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"], + "creates": ["Tick", "Receipt"], + "forbids": ["AstState", "Diagnostics", "GitWitness", "UiState"] + }, + "helperShape": { + "frame": "EINT", + "entrypoint": "dispatch_intent", + "fields": [ + "contract_artifact_id", + "operation_id", + "fixture_vars_bytes", + "declared_footprint" + ] + } + }, + { + "name": "textWindow", + "operationType": "QUERY", + "opIdHex": "0x53571001", + "opIdDecimal": 1398214657, + "helperKind": "QueryView", + "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", + "declaredFootprint": { + "reads": ["Buffer", "Tick", "Receipt"], + "writes": [], + "creates": [], + "forbids": ["AstState", "Diagnostics", "GitWitness", "UiState"] + }, + "helperShape": { + "frame": "QueryView", + "entrypoint": "observe", + "fields": [ + "contract_artifact_id", + "query_id", + "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 new file mode 100644 index 00000000..1136ce06 --- /dev/null +++ b/docs/design/stack-witness-0001-jedit-file-history.md @@ -0,0 +1,298 @@ + + + +# Stack Witness 0001 - jedit File History Walking Skeleton + +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 +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; +- 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` + +Creates the file-like contract object. + +Minimum variables: + +- `name = "demo.txt"`; +- contract artifact id; +- operation id; +- fixture vars bytes for this witness, later Wesley-generated binary codec + 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 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. + +## 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 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, 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 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. + +Targeted command: + +```sh +cargo test -p warp-wasm --features engine stack_witness_ +``` + +Current result: + +```text +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. 8 passed; 0 failed; 0 ignored; 0 measured; 44 filtered out +``` + +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; +- fixture vars bytes; +- `targetCodec: wesley-binary/v0`; +- footprints; +- EINT helpers; +- QueryView helper; +- artifact identity. + +## 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, 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. +- 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.