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

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 15 additions & 0 deletions contracts/jedit/echo-text.graphql
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,14 @@ type BufferWorldline {
lineCount: Int!
}

type RopeCheckpointDeclaration {
checkpointId: ID!
bufferId: ID!
basisHeadId: ID!
basisByteLength: Int!
Comment thread
coderabbitai[bot] marked this conversation as resolved.
reason: String!
Comment thread
coderabbitai[bot] marked this conversation as resolved.
}

type TextWindowLine {
lineNumber: Int!
startByte: Int!
Expand Down Expand Up @@ -38,6 +46,12 @@ input ReplaceRangeAsTickInput {
insertText: String!
}

input DeclareCheckpointInput {
bufferId: ID!
basisHeadId: ID!
reason: String!
}

input TextWindowInput {
bufferId: ID!
basisHeadId: ID!
Expand All @@ -49,6 +63,7 @@ input TextWindowInput {
type Mutation {
createBufferWorldline(input: CreateBufferWorldlineInput!): BufferWorldline!
replaceRangeAsTick(input: ReplaceRangeAsTickInput!): BufferWorldline!
declareCheckpoint(input: DeclareCheckpointInput!): RopeCheckpointDeclaration!
}

type Query {
Expand Down
29 changes: 21 additions & 8 deletions docs/BEARING.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ claims belong in git history and design retrospectives.
linked to Echo. There is no TypeScript text runtime or generic WASM facade in
the product text path.
- `contracts/jedit/echo-text.graphql` declares buffer creation, single-range
replacement, and bounded text-window observation.
replacement, checkpoint declaration, and bounded text-window observation.
- Echo's `echo-wesley-gen --contract-host` output supplies canonical EINT
codecs, operation identities, registry evidence, generated rules, and the
query observer plan under `native/jedit-echo-host/src/generated/`.
Expand All @@ -29,12 +29,24 @@ claims belong in git history and design retrospectives.
coordinate branding, and disposable UI projections. It does not construct
Echo identities, admission evidence, receipts, graph patches, or scheduler
outcomes.
- Buffer open/create, insert, replace, delete, and bounded text-window reads are
implemented. Multi-range edit, checkpoint, save/export, `:why`, causal
line-diff, and undo/redo return typed obstructions.
- Buffer open/create, insert, replace, delete, checkpoint declaration, and
bounded text-window reads are implemented. Multi-range edit, save/export,
`:why`, causal line-diff, and undo/redo return typed obstructions.
- Jim owns `RopeCheckpointDeclared` semantics. Echo admits and schedules the
generated declaration, persists its Jim fact in the graph/WAL history, and
returns the opaque receipt and tick identities. Declaration validates that
its basis head belongs to the stated Jim buffer worldline and does not mint a
text head, rewrite, diff, or causal anchor.
- A Jim checkpoint declaration and an Echo causal anchor remain separate
propositions. No anchor association is created automatically.
- The current package is explicitly `0.1.0-wesley-compat`. Its authored Rust
operation law and process invocation seam are transitional until Edict can
install and invoke the corresponding generated operations.
- Checkpoint reason remains a transitional GraphQL `String` because the pinned
Wesley contract-host enum emitter produces Rust that does not compile for
this enum shape. The installed native Jim contract still admits exactly
`manual-save`, `autosave`, `retention-boundary`, `export`, and `import`; an
arbitrary string is not an admitted checkpoint proposition.
- The full-snapshot runtime, local graph-rope executor, local admission loop,
locally manufactured receipts, fake production transports, and optimistic
text mutation path remain deleted.
Expand Down Expand Up @@ -76,15 +88,16 @@ or support-policy logic.

## Immediate Roadmap

1. Keep the narrow create/replace/read corridor green against Echo-owned WAL,
admission, scheduling, graph state, receipts, and restart recovery.
1. Keep the narrow create/replace/checkpoint/read corridor green against
Echo-owned WAL, admission, scheduling, graph state, receipts, and restart
recovery.
2. Avoid restoring broad editor feature parity through transitional APIs.
3. Have Echo and Edict establish one natively installed generated operation.
4. Migrate `ReplaceRange` to the generated Edict client and operation.
5. Make the Wesley/Rust replacement path unreachable, then delete it.
6. Migrate create/open and bounded text-window observation.
7. Add checkpoint declaration and optional causal-anchor association as
separate propositions.
7. Add optional causal-anchor association as a proposition separate from
checkpoint declaration only when a concrete consumer requires it.
8. Add save/export through generated operations, then derive undo/redo
candidates from retained Echo history and invoke generated inverse
operations through basis-pinned Echo observations.
Expand Down
110 changes: 104 additions & 6 deletions native/jedit-echo-host/src/contract.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,22 +6,30 @@ use warp_core::{
};

use crate::generated::contract::__echo_wesley_generated::{
CreateBufferWorldlineVars, ReplaceRangeAsTickVars, TextWindowVars,
CreateBufferWorldlineVars, DeclareCheckpointVars, ReplaceRangeAsTickVars, TextWindowVars,
};
use crate::generated::contract::{
create_buffer_worldline_contract_rule,
create_buffer_worldline_contract_runtime_ingress_footprint,
create_buffer_worldline_contract_vars, replace_range_as_tick_contract_rule,
replace_range_as_tick_contract_runtime_ingress_footprint, replace_range_as_tick_contract_vars,
text_window_query_observer, CONTRACT_HOST_HELPER_API_VERSION, ECHO_CONTRACT_ABI_VERSION,
GENERATED_RUST_ARTIFACT_HASH, OP_CREATE_BUFFER_WORLDLINE, OP_REPLACE_RANGE_AS_TICK, REGISTRY,
create_buffer_worldline_contract_vars, declare_checkpoint_contract_rule,
declare_checkpoint_contract_runtime_ingress_footprint, declare_checkpoint_contract_vars,
replace_range_as_tick_contract_rule, replace_range_as_tick_contract_runtime_ingress_footprint,
replace_range_as_tick_contract_vars, text_window_query_observer,
CONTRACT_HOST_HELPER_API_VERSION, ECHO_CONTRACT_ABI_VERSION, GENERATED_RUST_ARTIFACT_HASH,
OP_CREATE_BUFFER_WORLDLINE, OP_DECLARE_CHECKPOINT, OP_REPLACE_RANGE_AS_TICK, REGISTRY,
REGISTRY_VERSION, SCHEMA_SHA256, WESLEY_GENERATOR_VERSION,
};
use crate::identity::parse_node_id;
use crate::rope::{plan_create, plan_replace, read_window};
use crate::records::CheckpointReason;
use crate::rope::{plan_checkpoint, plan_create, plan_replace, read_window};

const PACKAGE_NAME: &str = "jedit-echo-text";
const PACKAGE_VERSION: &str = "0.1.0-wesley-compat";
const CHECKPOINT_REASON_MANUAL_SAVE: &str = "manual-save";
const CHECKPOINT_REASON_AUTOSAVE: &str = "autosave";
const CHECKPOINT_REASON_RETENTION_BOUNDARY: &str = "retention-boundary";
const CHECKPOINT_REASON_EXPORT: &str = "export";
const CHECKPOINT_REASON_IMPORT: &str = "import";

pub fn installed_package() -> InstalledContractPackage<'static> {
InstalledContractPackage {
Expand Down Expand Up @@ -56,6 +64,13 @@ pub fn installed_package() -> InstalledContractPackage<'static> {
replace_range_footprint,
),
},
ContractMutationHandler {
op_id: OP_DECLARE_CHECKPOINT,
rule: declare_checkpoint_contract_rule(
declare_checkpoint_executor,
declare_checkpoint_footprint,
),
},
],
inverse_handlers: vec![],
query_observers: vec![text_window_query_observer(observe_text_window)],
Expand Down Expand Up @@ -126,6 +141,60 @@ fn replace_plan(
)
}

fn declare_checkpoint_executor(view: GraphView<'_>, scope: &NodeId, delta: &mut TickDelta) {
let Some(vars) = declare_checkpoint_contract_vars(view, scope) else {
return;
};
if let Ok(plan) = declare_checkpoint_plan(view, vars) {
plan.emit(delta);
}
}

fn declare_checkpoint_footprint(view: GraphView<'_>, scope: &NodeId) -> Footprint {
let mut footprint = declare_checkpoint_contract_runtime_ingress_footprint(view, scope);
if let Some(vars) = declare_checkpoint_contract_vars(view, scope) {
if let Ok(plan) = declare_checkpoint_plan(view, vars) {
plan.extend_footprint(&mut footprint);
}
}
footprint
}

fn declare_checkpoint_plan(
view: GraphView<'_>,
vars: DeclareCheckpointVars,
) -> crate::error::HostResult<crate::rope::CheckpointPlan> {
plan_checkpoint(
&view,
parse_node_id(&vars.input.bufferId)?,
parse_node_id(&vars.input.basisHeadId)?,
checkpoint_reason(&vars.input.reason)?,
)
}

fn checkpoint_reason(reason: &str) -> crate::error::HostResult<CheckpointReason> {
match reason {
CHECKPOINT_REASON_MANUAL_SAVE => Ok(CheckpointReason::ManualSave),
CHECKPOINT_REASON_AUTOSAVE => Ok(CheckpointReason::Autosave),
CHECKPOINT_REASON_RETENTION_BOUNDARY => Ok(CheckpointReason::RetentionBoundary),
CHECKPOINT_REASON_EXPORT => Ok(CheckpointReason::Export),
CHECKPOINT_REASON_IMPORT => Ok(CheckpointReason::Import),
unsupported => Err(crate::error::HostError::InvalidRequest(format!(
"unsupported checkpoint reason: {unsupported}"
))),
}
}

pub fn generated_checkpoint_reason(reason: CheckpointReason) -> &'static str {
match reason {
CheckpointReason::ManualSave => CHECKPOINT_REASON_MANUAL_SAVE,
CheckpointReason::Autosave => CHECKPOINT_REASON_AUTOSAVE,
CheckpointReason::RetentionBoundary => CHECKPOINT_REASON_RETENTION_BOUNDARY,
CheckpointReason::Export => CHECKPOINT_REASON_EXPORT,
CheckpointReason::Import => CHECKPOINT_REASON_IMPORT,
}
}

fn observe_text_window(
context: &ContractQueryObserverContext<'_>,
vars: TextWindowVars,
Expand Down Expand Up @@ -166,3 +235,32 @@ fn observer_host_error(error: crate::error::HostError) -> ContractQueryObserverE
fn observer_error(message: impl Into<String>) -> ContractQueryObserverError {
ContractQueryObserverError::failed(crate::generated::contract::OP_TEXT_WINDOW, message)
}

#[cfg(test)]
mod tests {
use super::*;

#[test]
fn checkpoint_reason_scalar_has_an_exact_native_domain() {
let supported = [
(CHECKPOINT_REASON_MANUAL_SAVE, CheckpointReason::ManualSave),
(CHECKPOINT_REASON_AUTOSAVE, CheckpointReason::Autosave),
(
CHECKPOINT_REASON_RETENTION_BOUNDARY,
CheckpointReason::RetentionBoundary,
),
(CHECKPOINT_REASON_EXPORT, CheckpointReason::Export),
(CHECKPOINT_REASON_IMPORT, CheckpointReason::Import),
];

for (encoded, reason) in supported {
assert_eq!(checkpoint_reason(encoded).expect("reason should decode"), reason);
assert_eq!(generated_checkpoint_reason(reason), encoded);
}

assert!(matches!(
checkpoint_reason("initial"),
Err(crate::error::HostError::InvalidRequest(_))
));
}
}
2 changes: 2 additions & 0 deletions native/jedit-echo-host/src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ pub enum HostError {
InvalidRequest(String),
#[error("Echo did not apply the generated operation: {0}")]
IntentNotApplied(String),
#[error("generated installed operation is unavailable: {0}")]
GeneratedOperationUnavailable(String),
#[error("Echo observation failed: {0}")]
Observation(String),
#[error("host protocol failed: {0}")]
Expand Down
Loading
Loading