From cb6993c15eb164209885b4f0af1da097fe5552ad Mon Sep 17 00:00:00 2001 From: James Ross Date: Thu, 15 Oct 2026 19:33:05 -0700 Subject: [PATCH 01/50] docs(changelog): merge duplicated Unreleased subsections Unreleased carried two Added, two Removed, two Changed, and two Fixed subsections, which markdownlint reports as MD024 duplicate headings on main and which blocks any commit that touches CHANGELOG.md. Move each second group's entries under the first heading of the same name. The change is a pure reordering: sorted line content is identical except for the four removed duplicate headings and four blank lines. --- CHANGELOG.md | 818 +++++++++++++++++++++++++-------------------------- 1 file changed, 405 insertions(+), 413 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 700dfb50..0a2631bc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -33,6 +33,66 @@ combined booleans its caller supplied and checked nothing; the gate list is tracked in #858. +- Removed the provisional caller-authoritative causal-anchor API: + `CausalAnchorRequest`, `CausalAnchorFact::from_request`, public admitted-fact + fields, and raw admitted-identity constructors. Applications now submit a + `CausalAnchorAdmissionRequest` and treat the returned Echo fact, receipt, and + identities as opaque. This is an intentional breaking Rust API correction; + preserving the old surface would let callers manufacture Echo authority. +- Removed the abandoned Method system: its workspace crate and `xtask` + commands, checked-in backlog, cycles, retrospectives, status ledgers, process + manuals, and generated task graphs. Current architecture now lives in + canonical topics/specs/invariants, durable decisions live in ADRs, and live + work/status lives in GitHub. +- Removed the superseded `docs/design/` packet corpus and the retired TTD + counterfactual-creation invariant after promoting current doctrine into + canonical topics, ADRs, architecture documents, specifications, and tests. +- Removed the remaining architecture drift/future packets and dated benchmark + reports after preserving the retained-reading storage and proof boundary in + ADR 0020. Benchmark instructions now keep methodology in source docs and + measurements in generated artifacts and pull requests. +- Deleted zombie crates with no consumers (recoverable from git history): + `echo-wasm-bindings` (browser demo kernel), `echo-ttd`, `ttd-protocol-rs`, + and `echo-session-proto` (the TTD stack now lives with `warp-ttd`), plus + `echo-config-fs` (desktop app-shell fossil) and the + `packages/ttd-protocol-ts` generated consumer. +- Deleted `echo-app-core` (desktop app-shell fossil: toasts, prefs, config + ports) and the `echo-dry-tests` in-memory config fake that existed only to + test its trait; no other crate consumed either. +- Removed the unused `echo-wasm-abi::ttd` module and its public `PrivacyMask`, + `SessionToken`, and `TtdError` exports after the owning TTD/session stack was + retired. This is a breaking Rust API removal for unknown external consumers. +- Deleted `cargo xtask wesley` (existed only to sync the removed TTD + protocol consumer artifacts). +- Deleted stale point-in-time audit reports under `docs/audit/` and + `docs/audits/`. +- Removed the deleted crates from workspace members, CI clippy lanes, + `det-policy.yaml`, `scripts/verify-local.sh`, and the unordered-ABI + allowlist. +- Removed the VitePress docs-site toolchain from active repo tooling: npm + scripts, Make targets, tracked docs-site config, the dead browser-open helper, + and VitePress/Mermaid dependencies are gone. +- Removed the broken `warp-core/serde` feature and the gated `Serializable*` + wrapper exports. `warp-core` no longer declares direct `serde`, `serde-value`, + or `ciborium` dependencies; authoritative core serialization must stay in + explicit canonical boundary encoders rather than general serde derives. +- Removed the legacy `ttd-browser` crate from Echo's active workspace. The + release browser/runtime boundary now stays centered on `warp-wasm` and + `echo-wasm-abi`; debugger session semantics and browser delivery adapters + belong in `warp-ttd`. +- `docs/METHODOLOGY.md` — 5x Duty Model (never practiced; see graveyard). +- 17 unimplemented future spec files (see graveyard). +- `docs/march-16.plan.md` — stale planning scratchpad. +- `docs/plans/parallel-merge-and-footprint-optimizations.md` — superseded + by design review. +- Old plans, book (LaTeX), and research artifacts. All remain recoverable from + Git history. +- **`warp-ffi` crate deleted:** The C ABI integration path (`crates/warp-ffi`) + has been removed. The C ABI approach was abandoned in favor of Rust plugin + extension via `RewriteRule` trait registration and Rhai scripting. See + TASKS-DAG.md #26 (Graveyard). This is a **BREAKING CHANGE** for any + downstream code that depended on the C FFI surface. + ### Changed - `IngressEnvelope::to_retained_bytes_v2` is now `to_retained_bytes` (#880). @@ -103,6 +163,159 @@ than cumulative counts for the thread. `recover_filesystem_store` counts nothing. +- `ExecItem` constructors require an explicit canonical evidence sequence. + Manually assembled work units no longer silently reuse a per-rule match + index as the execution sequence. Engine callers pass the existing batch-wide + enumeration directly, preserving scheduler order. + +- Echo and Edict share exact `bunny-num` 0.6.0 for the canonical signed Q32.32 + foundation. `warp-math` exposes Bunny's checked type and delegates existing + `DFix64` arithmetic and motion conversions while preserving saturation, + ties-to-even, raw payload bytes, and the separate legacy ABI truncation policy. + This does not add fixed-point language syntax or runtime operation profiles. +- The general Rust toolchain and numerical consumer MSRVs are 1.96.0. Independent + leaves and authenticated provider component producers retain their explicit + 1.90.0 policy; the outer component driver uses 1.96.0. The MSRV guard also + validates a final policy row without a newline and reads package MSRVs only + from `[package]`, excluding metadata. The standalone host witness selects + Rust 1.96.0 locally, and the guard rejects a missing or stale nested pin. + +- Public installed-contract evidence fields on runtime ingress, receipt + correlation, outcome, and WAL state-delta carriers now use + `Option` instead of + `Option`. This is an intentional pre-1.0 source + compatibility change: legacy callers wrap with `LegacyContract` or `.into()` + and inspect with `legacy_contract()`, while provider callers inspect + `provider_v1()`. The enum is non-exhaustive to match the extensible wire-tag + family, so direct downstream matches require a wildcard arm. The legacy tag-1 + WAL encoding remains byte-identical. +- The public `RuntimeError` enum now includes + `InstalledContractIntentKindMismatch` and + `UnsupportedInstalledProviderContractMutation`, and the public + `ContractInverseObstruction` enum now includes `ProviderTargetUnsupported`. + These are intentional pre-1.0 source-compatibility additions: downstream + exhaustive matches must handle the new variants (or use an appropriate + wildcard arm). +- `echo-wesley-gen` now follows Wesley's operation-neutral adapter names: + `import_runtime_optic_artifact(...)` accepts `OperationArtifact`, and + `import_registration_descriptor(...)` accepts + `OperationRegistrationDescriptor`. Source consumers using Wesley's former + `OpticArtifact` or `OpticRegistrationDescriptor` names must update when they + adopt the corresponding Wesley 0.3 prerelease; Echo's runtime-local optic + artifact types and handles are unchanged. +- Provider semantic-source, generation-input, and contract-pack `Display` + diagnostics now render explicit kebab-case failure labels instead of Rust + `Debug` variant spellings. Typed error enums remain the programmatic + contract, while human/CLI diagnostics no longer move when Rust variants are + refactored. +- Restored the durable ADR 0001–0011 namespace and moved the post-Method + decisions to ADR 0012–0019, preserving the meaning of existing source + citations. CI now rejects missing, duplicate, non-contiguous, unindexed, or + collided ADR identifiers. +- Recorded the WSC, CAS, semantic reading identity, and optional proof boundary + as ADR 0020 without carrying forward the source packet's implementation + roadmap or application-specific checkpoint design. +- Replaced the opaque numeric determinism claim-pack generator with an honest + gate over upstream job results and exact artifact payload presence. The + executable suites remain the authority for what passed. +- Replaced the frozen root architecture and advanced guides with explicit + supersession signposts to the living no-graph architecture, topics, + specifications, invariants, and ADRs. +- Echo 1.0 release eligibility now depends only on Continuum participant + conformance, networked causal suffix exchange, and release integrity. Edict, + `jedit`, and any particular generated package remain downstream compatibility + work and no longer gate the Echo release. +- Echo 1.0 planning references now point at the cross-repository Continuum + Stack Convergence Project instead of the retired Echo-only Project. +- Local `scripts/verify-local.sh full` now treats broad Cargo test lanes as + GitHub Actions-owned by default, while keeping a maintainer opt-in through + `VERIFY_LOCAL_FULL_TESTS=1` for intentional local full-suite runs. +- `warp-core` renamed the generated contract package host API from + `install_contract_package(...)` to `register_contract_package(...)` so the + trusted-runtime boundary reads as explicit runtime-owned registration instead + of process-global installation. +- `warp-core` sealed braid member lookup now requires authority-bound sealed + query material, redacts non-public blinding material from debug output, and + keeps hidden-member commitments stable across parent frontier movement. +- `warp-core` settlement planning now rejects non-`Shared` strands before + producing import candidates. Author-only/debugger strand suffixes can remain + real causal work, but they cannot enter base shared history without an + explicit shared admission posture. Settlement compare remains local + revelation/inspection only: it can inspect a locally held strand suffix + without promoting, planning, admitting, or settling it. +- `warp-core` settlement plural artifacts and retained braid shells now carry + the source strand posture instead of hard-coding author-only posture for + shared settlement records. +- Local determinism tooling now fails closed around + `scripts/check-warp-core-serialization-boundaries.sh`. The serialization + boundary guard is mandatory, runs through `bash` rather than executable mode, + works without `rg`, rejects table-form serde/ciborium dependencies, and + blocks direct, grouped, multiline, or aliased canonical ABI serialization + imports outside explicit `warp-core` boundary modules. +- The nondeterminism guard now shares the same `rg`/Perl fallback scanner, + supports `DETERMINISM_FORCE_NO_RG=1` regression coverage, catches namespace + imports and alias calls for `std::env`, `std::fs`, and `std::process`, bans + `std::thread::available_parallelism`, and replaces file-wide allowlists with + rule-scoped waivers for build/native filesystem boundaries and test fixtures. +- `warp-core` engine and scheduler state now use ordered `BTreeMap`/`BTreeSet` + storage instead of `HashMap`, `HashSet`, or `FxHashMap` in deterministic core + paths. WAL filesystem tests also recreate stale deterministic fixture roots + before use so previous local residue cannot change the test outcome. +- `warp-core` engine construction now defaults to deterministic serial + execution instead of reading `ECHO_WORKERS` or host CPU availability. Callers + can still opt into parallel execution explicitly with + `EngineBuilder::workers(...)` or worker-count constructors. +- `echo-cli wal submission-posture` now exposes generic read-only recovery JSON + for one submission id and canonical envelope digest. The output reports retry + posture, recovered submission posture, receipt digest, and ticket digest + without importing any application nouns into Echo. +- Runtime WAL-backed scheduler ticks now roll back all tick WAL evidence from a + failed multi-head scheduler pass, treat missing or mismatched receipt + correlation evidence as an invariant error instead of a normal obstruction, + and bind runtime recovery certificates to rebuilt submission and receipt + indexes. +- `warp-wasm` no longer contains the legacy Stack Witness 0001 + `createBuffer`/`replaceRange`/`textWindow` shortcut. QueryView requests now + route through the generic installed contract observer boundary only; without + an installed observer, WASM `observe(...)` returns `UNSUPPORTED_QUERY` + instead of materializing hardcoded text bytes. +- `warp-core` structured binding tests no longer use rope/text-shaped fixture + names. The executable examples now exercise generic authored scopes, heads, + segments, and markers so core test fixtures do not imply built-in editor + semantics. +- The canonical pre-push hook now runs the narrowest changed-file Rust witness + instead of reusing the broader local PR gate. Rust module edits map to exact + `cargo test -p --lib ::tests` slices, integration-test edits + map to exact `--test ` invocations, and tooling edits stay on local + shell smoke checks. Broader clippy, rustdoc, package, and workspace coverage + remains available through `make verify-pr`, `make verify-full`, and CI. The + selector now avoids fake zero-test module filters for source files without + inline tests, maps `src/bin/*.rs` edits to exact binary targets, and preserves + required features for gated integration tests. +- Deterministic math now lives in a slim `warp-math` workspace crate. + `warp-core` keeps the existing `warp_core::math::*` compatibility surface as + a re-export, while `warp-geom` depends directly on `warp-math` instead of + pulling in all of `warp-core`. Math-only integration tests, deterministic + trig golden vectors, PRNG golden regression, and the LUT generator moved with + the math crate. +- CI and local verification now split broad clippy coverage into explicit + library, binary, and selected integration-test lanes instead of invoking one + monolithic cargo pass. The `warp-core` runtime inbox test target is now an + explicit lint lane with the same `native_rule_bootstrap,host_test` features + used by the ticketed-ingress regression suite. +- Local verification now treats rustdoc warnings as CI-owned by default. + `scripts/verify-local.sh` skips local rustdoc lanes unless + `VERIFY_LOCAL_RUSTDOC=1` is set, keeping pre-push and full local gates focused + on faster edit-loop witnesses while preserving CI rustdoc coverage. +- Tooling-only full verification now selects focused hook regression scripts by + changed file family, so a `scripts/verify-local.sh` edit runs the verify-local + hook regression without also running unrelated runtime-schema, PR-status, or + timing hook tests. +- Local hook regression tests are now CI-owned by default. The local full gate + skips hook tests unless `VERIFY_LOCAL_HOOK_TESTS=1` is set, and the opt-in + hook-test lane clears verifier override variables before launching nested + hook regressions. + ### Added - Echo preserves Continuum occurrence identity at admission (#880). @@ -230,208 +443,22 @@ - A fallible complete-object CAS port stages and verifies exact bytes before atomic destination promotion. Memory and disk adapters share conformance checks; existing APIs remain compatible and no durability or authenticated absence is claimed. -### Fixed +- Generic source-owned Edict functions execute through both private pure and + bounded-read evaluation with fresh lexical frames, ordered argument evaluation, + ordered local bindings, and one cumulative budget. Independent provider + judgments validate the complete source/imported closure, supported types, + totality, combined runtime depth, and conservative whole-operation costs. + Providers and runtime reject source functions that shadow authenticated + imported effects, including otherwise valid unused definitions. Bounded-read + call lookup distinguishes source and imported functions by exact membership, + including disjoint names under one package prefix. + Independent provider checks retain nominal type identity for source-function + compatibility while deriving runtime costs from the underlying representation. -- A tick that decides a submission no rule matched now commits and recovers - under the WAL (#873). The WAL tick decision gains `NoMatchingRule` (code 4), - recovery maps it to `RecoveredSubmissionPosture::DecidedNoMatchingRule`, and - a retry reports `SubmissionRetryPosture::AlreadyDecidedNoMatchingRule`. - Before, such a tick failed with `TickOutcomeUnavailable` and could not be - recorded at all. Older WALs never contain the code. -- `Footprint::independent` no longer treats an unpopulated `factor_mask` as - partition evidence. The mask fast path now applies only when both masks are - non-zero; a zero placeholder on either side falls through to the node, edge, - attachment, and port set checks. Previously two zero-mask footprints with - conflicting writes compared as independent. Nothing in the engine calls it - today (the scheduler never consults the mask), but it is public API. A - union of footprints (`union_assign`) also stays unpopulated when either side - with claims is unpopulated, so a composite cannot look fully masked. -- A tick with the in-memory WAL no longer costs time in proportion to the - whole log. The host copied the in-memory log, its catalog and its indexes - before every tick so it could undo a failed one; at 8,000 updates a tick - took 13.5 ms. It now marks the log's end and, only if a tick fails, - truncates back to the mark and rebuilds what is derived from the log. In - `wal_record_cost` the in-memory update now costs about 0.22 ms at 500 and at - 8,000 updates of history. - -- A commit record whose storage sync failed is no longer acknowledged. The - record can be readable without being durable; a trusted host used to - re-read the log after the error and treat what it found as committed, for - submissions, scheduler ticks, causal anchors, operation installations and - operation ticks. The filesystem store now refuses further writes after any - failure while a commit record is written or synced, and the host takes the - work back and refuses further WAL work with `StorageFailed` until the WAL - is reopened, as batched submission already did. An error that arrives after - a successful sync is still reconciled from the log. - -- A trusted host takes the filesystem WAL writer lease before it runs - writable recovery. It used to recover first and take the lease afterwards, - so a second host that opened a log with a live writer could drop that - writer's in-progress tail by rewriting its segment files, and only then be - refused. The second host is now refused at the lease and modifies nothing. - -- The canonical CBOR decoder in `echo-wasm-abi` no longer reserves memory in - proportion to a length its input claims, and no longer recurses without - bound. A header that claims more array elements, map entries or string - bytes than the input has left is refused with `Incomplete`, and the - comparison is made before the length is narrowed, so 32-bit and 64-bit - targets refuse the same inputs. A container reserves at most a few slots - before its elements are decoded, so nested headers cannot multiply the - memory a request asks for. Nesting deeper than - `MAX_CANONICAL_NESTING_DEPTH` (128) is refused, by the encoder as well, so - bytes it returns always decode. Before, a nine-byte request could abort the - process. - -- Writable WAL recovery no longer removes committed records in order to drop - an uncommitted tail. It rebuilt the whole log by deleting every segment - file and re-appending what it kept, so a crash in between lost all - committed history; it also synced the WAL root instead of the directory - that holds the segments, and collapsed rotated segments into the first. - Each segment that loses records is now rebuilt in a scratch file and - swapped in by one rename, a segment with nothing to drop is not touched, - an emptied trailing segment is removed, and the containing directory is - synced. A crash at any step leaves the same committed history. - -- A worldline frontier retains the state root computed by its last executable-operation Action commit and returns it without hashing until the graph is next accessed mutably. An Action tick after the first now hashes the whole reachable state once instead of twice. Debug builds hash again to check each retained root, so they do not get the saving. The graph field of `WorldlineState` is private to its module so that every mutation goes through the accessor that drops the retained root. - -- A filesystem WAL writer that takes over an existing log syncs its segment files before reading them as recovered history. A record that an earlier writer wrote and never synced was readable after reopen and was reported as a durable acceptance; a later power loss could still lose it. - -- A scheduler pass that commits executable-operation Actions hashes the worldline's whole reachable state twice instead of three times. The root before the commit is the root the pass already computed for the frontier basis; the commit no longer computes it again. - -- A scheduler pass resolves a writer head's frontier basis once instead of once per admitted executable-operation Action. Resolving it hashes the whole reachable state, so a tick's cost grew with Actions times graph size. The bases produced are unchanged. - -- A scheduler pass builds and digests its retained WAL state delta once per commit instead of once per admitted Action. The WAL bytes are unchanged. - -- A scheduler pass that commits executable-operation Actions no longer copies the worldline's graph. The commit wrote its patch to a copy of the graph and then replaced the original, and the pass's rollback marker held a second copy. The patch is now written in place and each replaced value is journaled before its write; rollback restores from the journal. This applies to patches of node upserts and non-portal attachment writes. A patch that changes graph structure, and a pass that runs native rules through the engine, still retains one copy. Debug builds keep a copy to check the journal. - -- A worldline's initial boundary is shared instead of copied. It never changes after construction, but every scheduler rollback marker, replay checkpoint and replay base cloned the whole graph it holds. Tick cost on a large graph drops by roughly a fifth; the remaining graph-proportional copies are the live state itself. - -- Scheduler rollback checkpoints no longer copy a worldline's committed tick history or committed-ingress ledger on every SuperTick. A checkpoint records the history length and the attempt journals the ingress ids it adds; rollback truncates and removes them. The trusted host no longer clones the runtime, provenance and retained Action outcomes before every tick, or the runtime before every WAL-acknowledged submission: a completed scheduler pass returns an undo handle, and Action staging and submissions are journaled. Rollback results are unchanged; tick and submission cost no longer grow with committed history. - -- A strict filesystem WAL commit no longer rewrites and syncs the writer-epoch ledger and its directory after syncing the commit record. The commit record remains the durability boundary; reopening already derives each epoch's final commit from the segment. Each transaction now forces one sync instead of three. The on-disk formats are unchanged. - -- Package admission rejects result projections whose expected-digest binding conflicts with the selected mutation program, before they can occupy an installed operation coordinate. Recovery applies the same rule to retained installations: a WAL that holds such a hand-built package is refused rather than reinstalled. Echo's providers never emitted that shape, and no invocation of it could be admitted. Admission likewise refuses an expected-digest path that is empty or overlaps the key or replacement path. - -- Both executable mutation providers refuse transformed effect arguments instead of silently projecting the caller input into a different mutation. Direct application-input arguments remain supported; the structurally separate verifier enforces the same boundary. - -- Retained local fork retries reconcile durable topology after uncertain appends, preserving the original basis and preventing duplicate fork records. - -- Observation slots preflight their canonical byte bound and charge execution reads before copying Atom payloads. - -- Private Edict runtime decoding resolves named integer types through their declared width, matching source-function provider acceptance. - -- Filesystem writer takeover preserves unused LSNs after empty epochs and refuses unreconciled tails before changing the epoch ledger. - -- Incremental snapshot state roots now include the existing v1 domain separator and agree with canonical snapshot hashing. Previously emitted incorrect accumulator roots are not migrated. - -- The SPDX checker preserves Markdown frontmatter when it checks or repairs license headers. - -- Action WAL recovery reuses ordered verified replay cursors instead of retaining a full state for each basis tick. It preserves basis, Tick, result, obstruction and conflict checks, including delayed stale bases. - -- The generic operation runner names typed obstruction kinds, footprint conflicts, and missing outcomes in bounded summaries. It omits raw outcome records and invocation data on both Action error paths. - -- `xtask run-edict-operation` runs outside Git and resolves relative artifact and WAL paths from the caller directory. Repository maintenance commands retain their Git-root behavior. - -- `WorldlineState::state_root` documentation now states its reachable-state boundary. Detached create-if-absent writes remain bound by patch and commit identities. Hash bytes are unchanged. - -- `ReadView::at` validates the aperture and derives the selected current frontier - basis with one state hash. `ReadView::new` still checks an independently - selected expected basis. Both retain the same aperture and trust boundaries. - -- Public bounded-read compiler timeouts retain partial stdout/stderr and the - original timeout as the failure cause. - -- Bounded-read lowering recognizes only the exact unsigned primitive coordinates. - Named structural types starting with `U` now reach ordinary type lookup, as - they already do in independent verification and runtime parsing. - -- Private read evaluation identifies the input by its verified `arg.0` identity. - Reordering a package's local declarations no longer changes input selection. - -- Read-provider lawpack validation remains separate from the pure profile's - empty-effect requirement, preserving read acceptance and pure-effect refusal - after integration of the strengthened pure-provider checks. - -- The bounded-read public compiler witness preserves return status and raw - streams when malformed or non-object JSONL prevents structured validation. - -- Bounded-read evaluator tests now require `trusted_runtime` in Cargo and run - in feature-enabled CI and opt-in local routes, including shared parser edits. - -- Removed the bounded-read witness's unguarded image/entrypoint recipe. Public - compiler and fresh-output runtime witnesses require a continuously guarded - reusable worker; pre/post measurements alone are not budget enforcement. - -- Generated provider helpers bind the bounded-read schema publication, including - its exact schema identity in the helper contract and derivation materials. - -- Byte-equality fixture reproduction guidance now requires guarded reusable - compiler storage and preserves the limits of its frozen provider evidence. - -- Byte-equality evaluator tests now require `trusted_runtime` in Cargo and run - in the feature-enabled CI and opt-in local test routes. Selecting the target - without the feature can no longer produce a zero-test success. - -- The ordered-publication compiler witness retains the return code and raw - streams when unexpected output violates JSONL, instead of hiding the compiler - result behind a JSON decoding or non-object error. - -- Removed the unguarded ordered-publication image-build recipe. Its test-only - witness requires a guarded reusable worker with copied sources and accounted - compiler output; the supported runner remains separate follow-up work. - -- Provider artifact generation rejects a contract publication whose exact - schema or manifest differs from the generation input. Cross-publication calls - can no longer emit artifacts with misleading input provenance. - -- The filesystem WAL writer lease now attempts an explicit unlock when its - owning guard drops. A retained duplicate descriptor no longer extends a - successfully released lease and blocks immediate writer takeover. Live-writer - exclusion and the successor's independent lease remain enforced. - -- Native pure executable-package providers now enforce the compiler-owned - projection artifact, node, path, text, and exact-field bounds. The pure output - budget remains Core-declared; the anchored route's fixed output ceiling is - not applied to pure programs. Both independently reproduced checked components - include these bounds. -- Native pure executable-package providers now require an adapter profile with - empty `semanticEffects` and a nonempty `budgetObligation` before constructing - or accepting a no-effects package. Effectful profiles remain unsupported on - this route. Both checked components include this validation. -- The native pure executable-package lowerer and verifier now validate source - artifacts before dispatch: the source reference must match Core's coordinate, - and its canonical value must be a UTF-8 byte string. Rebinding an invalid - source's digest no longer bypasses these checks. Both checked components - include this validation. -- The pure executable-package verifier now compares the application - result projection with the authored Core result and its output contract. - A digest-bound projection could previously select another field or binding, - omit a field, or change its output type or budget and still be accepted. - The independently reproduced checked component includes this check. -- The Edict executable-package verifier now refuses pure-v1 Target IR - that changes Core let bindings, results, input constraints, or basis, or - introduces extra intents or requirements. Exact package reconstruction alone - could accept an independently rebound but semantically inconsistent pair. - The native verifier and checked WASM package carry the same relation check. - -### Added - -- Generic source-owned Edict functions execute through both private pure and - bounded-read evaluation with fresh lexical frames, ordered argument evaluation, - ordered local bindings, and one cumulative budget. Independent provider - judgments validate the complete source/imported closure, supported types, - totality, combined runtime depth, and conservative whole-operation costs. - Providers and runtime reject source functions that shadow authenticated - imported effects, including otherwise valid unused definitions. Bounded-read - call lookup distinguishes source and imported functions by exact membership, - including disjoint names under one package prefix. - Independent provider checks retain nominal type identity for source-function - compatibility while deriving runtime costs from the underlying representation. - -- Bounded pure evaluation supports generic `core.bytes.concat` with independent - operand byte bounds, checked maximum composition, and combined-byte work and - result storage charged before allocation/copy. Application names and text - encoding do not affect concatenation behavior or costs. +- Bounded pure evaluation supports generic `core.bytes.concat` with independent + operand byte bounds, checked maximum composition, and combined-byte work and + result storage charged before allocation/copy. Application names and text + encoding do not affect concatenation behavior or costs. - Bounded pure evaluation supports generic `core.bytes.slice` with half-open U64 ranges, runtime byte/range validation, and deterministic selected-byte @@ -1924,224 +1951,189 @@ Applied, Rejected, Obstructed}` with receipt evidence and typed contract - Three graveyard entries (BOAW naming, 5x Duty Model, unimplemented future specs). -### Removed +### Fixed -- Removed the provisional caller-authoritative causal-anchor API: - `CausalAnchorRequest`, `CausalAnchorFact::from_request`, public admitted-fact - fields, and raw admitted-identity constructors. Applications now submit a - `CausalAnchorAdmissionRequest` and treat the returned Echo fact, receipt, and - identities as opaque. This is an intentional breaking Rust API correction; - preserving the old surface would let callers manufacture Echo authority. -- Removed the abandoned Method system: its workspace crate and `xtask` - commands, checked-in backlog, cycles, retrospectives, status ledgers, process - manuals, and generated task graphs. Current architecture now lives in - canonical topics/specs/invariants, durable decisions live in ADRs, and live - work/status lives in GitHub. -- Removed the superseded `docs/design/` packet corpus and the retired TTD - counterfactual-creation invariant after promoting current doctrine into - canonical topics, ADRs, architecture documents, specifications, and tests. -- Removed the remaining architecture drift/future packets and dated benchmark - reports after preserving the retained-reading storage and proof boundary in - ADR 0020. Benchmark instructions now keep methodology in source docs and - measurements in generated artifacts and pull requests. -- Deleted zombie crates with no consumers (recoverable from git history): - `echo-wasm-bindings` (browser demo kernel), `echo-ttd`, `ttd-protocol-rs`, - and `echo-session-proto` (the TTD stack now lives with `warp-ttd`), plus - `echo-config-fs` (desktop app-shell fossil) and the - `packages/ttd-protocol-ts` generated consumer. -- Deleted `echo-app-core` (desktop app-shell fossil: toasts, prefs, config - ports) and the `echo-dry-tests` in-memory config fake that existed only to - test its trait; no other crate consumed either. -- Removed the unused `echo-wasm-abi::ttd` module and its public `PrivacyMask`, - `SessionToken`, and `TtdError` exports after the owning TTD/session stack was - retired. This is a breaking Rust API removal for unknown external consumers. -- Deleted `cargo xtask wesley` (existed only to sync the removed TTD - protocol consumer artifacts). -- Deleted stale point-in-time audit reports under `docs/audit/` and - `docs/audits/`. -- Removed the deleted crates from workspace members, CI clippy lanes, - `det-policy.yaml`, `scripts/verify-local.sh`, and the unordered-ABI - allowlist. -- Removed the VitePress docs-site toolchain from active repo tooling: npm - scripts, Make targets, tracked docs-site config, the dead browser-open helper, - and VitePress/Mermaid dependencies are gone. -- Removed the broken `warp-core/serde` feature and the gated `Serializable*` - wrapper exports. `warp-core` no longer declares direct `serde`, `serde-value`, - or `ciborium` dependencies; authoritative core serialization must stay in - explicit canonical boundary encoders rather than general serde derives. -- Removed the legacy `ttd-browser` crate from Echo's active workspace. The - release browser/runtime boundary now stays centered on `warp-wasm` and - `echo-wasm-abi`; debugger session semantics and browser delivery adapters - belong in `warp-ttd`. -- `docs/METHODOLOGY.md` — 5x Duty Model (never practiced; see graveyard). -- 17 unimplemented future spec files (see graveyard). -- `docs/march-16.plan.md` — stale planning scratchpad. -- `docs/plans/parallel-merge-and-footprint-optimizations.md` — superseded - by design review. -- Old plans, book (LaTeX), and research artifacts. All remain recoverable from - Git history. -- **`warp-ffi` crate deleted:** The C ABI integration path (`crates/warp-ffi`) - has been removed. The C ABI approach was abandoned in favor of Rust plugin - extension via `RewriteRule` trait registration and Rhai scripting. See - TASKS-DAG.md #26 (Graveyard). This is a **BREAKING CHANGE** for any - downstream code that depended on the C FFI surface. +- A tick that decides a submission no rule matched now commits and recovers + under the WAL (#873). The WAL tick decision gains `NoMatchingRule` (code 4), + recovery maps it to `RecoveredSubmissionPosture::DecidedNoMatchingRule`, and + a retry reports `SubmissionRetryPosture::AlreadyDecidedNoMatchingRule`. + Before, such a tick failed with `TickOutcomeUnavailable` and could not be + recorded at all. Older WALs never contain the code. +- `Footprint::independent` no longer treats an unpopulated `factor_mask` as + partition evidence. The mask fast path now applies only when both masks are + non-zero; a zero placeholder on either side falls through to the node, edge, + attachment, and port set checks. Previously two zero-mask footprints with + conflicting writes compared as independent. Nothing in the engine calls it + today (the scheduler never consults the mask), but it is public API. A + union of footprints (`union_assign`) also stays unpopulated when either side + with claims is unpopulated, so a composite cannot look fully masked. +- A tick with the in-memory WAL no longer costs time in proportion to the + whole log. The host copied the in-memory log, its catalog and its indexes + before every tick so it could undo a failed one; at 8,000 updates a tick + took 13.5 ms. It now marks the log's end and, only if a tick fails, + truncates back to the mark and rebuilds what is derived from the log. In + `wal_record_cost` the in-memory update now costs about 0.22 ms at 500 and at + 8,000 updates of history. -### Changed +- A commit record whose storage sync failed is no longer acknowledged. The + record can be readable without being durable; a trusted host used to + re-read the log after the error and treat what it found as committed, for + submissions, scheduler ticks, causal anchors, operation installations and + operation ticks. The filesystem store now refuses further writes after any + failure while a commit record is written or synced, and the host takes the + work back and refuses further WAL work with `StorageFailed` until the WAL + is reopened, as batched submission already did. An error that arrives after + a successful sync is still reconciled from the log. -- `ExecItem` constructors require an explicit canonical evidence sequence. - Manually assembled work units no longer silently reuse a per-rule match - index as the execution sequence. Engine callers pass the existing batch-wide - enumeration directly, preserving scheduler order. +- A trusted host takes the filesystem WAL writer lease before it runs + writable recovery. It used to recover first and take the lease afterwards, + so a second host that opened a log with a live writer could drop that + writer's in-progress tail by rewriting its segment files, and only then be + refused. The second host is now refused at the lease and modifies nothing. -- Echo and Edict share exact `bunny-num` 0.6.0 for the canonical signed Q32.32 - foundation. `warp-math` exposes Bunny's checked type and delegates existing - `DFix64` arithmetic and motion conversions while preserving saturation, - ties-to-even, raw payload bytes, and the separate legacy ABI truncation policy. - This does not add fixed-point language syntax or runtime operation profiles. -- The general Rust toolchain and numerical consumer MSRVs are 1.96.0. Independent - leaves and authenticated provider component producers retain their explicit - 1.90.0 policy; the outer component driver uses 1.96.0. The MSRV guard also - validates a final policy row without a newline and reads package MSRVs only - from `[package]`, excluding metadata. The standalone host witness selects - Rust 1.96.0 locally, and the guard rejects a missing or stale nested pin. +- The canonical CBOR decoder in `echo-wasm-abi` no longer reserves memory in + proportion to a length its input claims, and no longer recurses without + bound. A header that claims more array elements, map entries or string + bytes than the input has left is refused with `Incomplete`, and the + comparison is made before the length is narrowed, so 32-bit and 64-bit + targets refuse the same inputs. A container reserves at most a few slots + before its elements are decoded, so nested headers cannot multiply the + memory a request asks for. Nesting deeper than + `MAX_CANONICAL_NESTING_DEPTH` (128) is refused, by the encoder as well, so + bytes it returns always decode. Before, a nine-byte request could abort the + process. -- Public installed-contract evidence fields on runtime ingress, receipt - correlation, outcome, and WAL state-delta carriers now use - `Option` instead of - `Option`. This is an intentional pre-1.0 source - compatibility change: legacy callers wrap with `LegacyContract` or `.into()` - and inspect with `legacy_contract()`, while provider callers inspect - `provider_v1()`. The enum is non-exhaustive to match the extensible wire-tag - family, so direct downstream matches require a wildcard arm. The legacy tag-1 - WAL encoding remains byte-identical. -- The public `RuntimeError` enum now includes - `InstalledContractIntentKindMismatch` and - `UnsupportedInstalledProviderContractMutation`, and the public - `ContractInverseObstruction` enum now includes `ProviderTargetUnsupported`. - These are intentional pre-1.0 source-compatibility additions: downstream - exhaustive matches must handle the new variants (or use an appropriate - wildcard arm). -- `echo-wesley-gen` now follows Wesley's operation-neutral adapter names: - `import_runtime_optic_artifact(...)` accepts `OperationArtifact`, and - `import_registration_descriptor(...)` accepts - `OperationRegistrationDescriptor`. Source consumers using Wesley's former - `OpticArtifact` or `OpticRegistrationDescriptor` names must update when they - adopt the corresponding Wesley 0.3 prerelease; Echo's runtime-local optic - artifact types and handles are unchanged. -- Provider semantic-source, generation-input, and contract-pack `Display` - diagnostics now render explicit kebab-case failure labels instead of Rust - `Debug` variant spellings. Typed error enums remain the programmatic - contract, while human/CLI diagnostics no longer move when Rust variants are - refactored. -- Restored the durable ADR 0001–0011 namespace and moved the post-Method - decisions to ADR 0012–0019, preserving the meaning of existing source - citations. CI now rejects missing, duplicate, non-contiguous, unindexed, or - collided ADR identifiers. -- Recorded the WSC, CAS, semantic reading identity, and optional proof boundary - as ADR 0020 without carrying forward the source packet's implementation - roadmap or application-specific checkpoint design. -- Replaced the opaque numeric determinism claim-pack generator with an honest - gate over upstream job results and exact artifact payload presence. The - executable suites remain the authority for what passed. -- Replaced the frozen root architecture and advanced guides with explicit - supersession signposts to the living no-graph architecture, topics, - specifications, invariants, and ADRs. -- Echo 1.0 release eligibility now depends only on Continuum participant - conformance, networked causal suffix exchange, and release integrity. Edict, - `jedit`, and any particular generated package remain downstream compatibility - work and no longer gate the Echo release. -- Echo 1.0 planning references now point at the cross-repository Continuum - Stack Convergence Project instead of the retired Echo-only Project. -- Local `scripts/verify-local.sh full` now treats broad Cargo test lanes as - GitHub Actions-owned by default, while keeping a maintainer opt-in through - `VERIFY_LOCAL_FULL_TESTS=1` for intentional local full-suite runs. -- `warp-core` renamed the generated contract package host API from - `install_contract_package(...)` to `register_contract_package(...)` so the - trusted-runtime boundary reads as explicit runtime-owned registration instead - of process-global installation. -- `warp-core` sealed braid member lookup now requires authority-bound sealed - query material, redacts non-public blinding material from debug output, and - keeps hidden-member commitments stable across parent frontier movement. -- `warp-core` settlement planning now rejects non-`Shared` strands before - producing import candidates. Author-only/debugger strand suffixes can remain - real causal work, but they cannot enter base shared history without an - explicit shared admission posture. Settlement compare remains local - revelation/inspection only: it can inspect a locally held strand suffix - without promoting, planning, admitting, or settling it. -- `warp-core` settlement plural artifacts and retained braid shells now carry - the source strand posture instead of hard-coding author-only posture for - shared settlement records. -- Local determinism tooling now fails closed around - `scripts/check-warp-core-serialization-boundaries.sh`. The serialization - boundary guard is mandatory, runs through `bash` rather than executable mode, - works without `rg`, rejects table-form serde/ciborium dependencies, and - blocks direct, grouped, multiline, or aliased canonical ABI serialization - imports outside explicit `warp-core` boundary modules. -- The nondeterminism guard now shares the same `rg`/Perl fallback scanner, - supports `DETERMINISM_FORCE_NO_RG=1` regression coverage, catches namespace - imports and alias calls for `std::env`, `std::fs`, and `std::process`, bans - `std::thread::available_parallelism`, and replaces file-wide allowlists with - rule-scoped waivers for build/native filesystem boundaries and test fixtures. -- `warp-core` engine and scheduler state now use ordered `BTreeMap`/`BTreeSet` - storage instead of `HashMap`, `HashSet`, or `FxHashMap` in deterministic core - paths. WAL filesystem tests also recreate stale deterministic fixture roots - before use so previous local residue cannot change the test outcome. -- `warp-core` engine construction now defaults to deterministic serial - execution instead of reading `ECHO_WORKERS` or host CPU availability. Callers - can still opt into parallel execution explicitly with - `EngineBuilder::workers(...)` or worker-count constructors. -- `echo-cli wal submission-posture` now exposes generic read-only recovery JSON - for one submission id and canonical envelope digest. The output reports retry - posture, recovered submission posture, receipt digest, and ticket digest - without importing any application nouns into Echo. -- Runtime WAL-backed scheduler ticks now roll back all tick WAL evidence from a - failed multi-head scheduler pass, treat missing or mismatched receipt - correlation evidence as an invariant error instead of a normal obstruction, - and bind runtime recovery certificates to rebuilt submission and receipt - indexes. -- `warp-wasm` no longer contains the legacy Stack Witness 0001 - `createBuffer`/`replaceRange`/`textWindow` shortcut. QueryView requests now - route through the generic installed contract observer boundary only; without - an installed observer, WASM `observe(...)` returns `UNSUPPORTED_QUERY` - instead of materializing hardcoded text bytes. -- `warp-core` structured binding tests no longer use rope/text-shaped fixture - names. The executable examples now exercise generic authored scopes, heads, - segments, and markers so core test fixtures do not imply built-in editor - semantics. -- The canonical pre-push hook now runs the narrowest changed-file Rust witness - instead of reusing the broader local PR gate. Rust module edits map to exact - `cargo test -p --lib ::tests` slices, integration-test edits - map to exact `--test ` invocations, and tooling edits stay on local - shell smoke checks. Broader clippy, rustdoc, package, and workspace coverage - remains available through `make verify-pr`, `make verify-full`, and CI. The - selector now avoids fake zero-test module filters for source files without - inline tests, maps `src/bin/*.rs` edits to exact binary targets, and preserves - required features for gated integration tests. -- Deterministic math now lives in a slim `warp-math` workspace crate. - `warp-core` keeps the existing `warp_core::math::*` compatibility surface as - a re-export, while `warp-geom` depends directly on `warp-math` instead of - pulling in all of `warp-core`. Math-only integration tests, deterministic - trig golden vectors, PRNG golden regression, and the LUT generator moved with - the math crate. -- CI and local verification now split broad clippy coverage into explicit - library, binary, and selected integration-test lanes instead of invoking one - monolithic cargo pass. The `warp-core` runtime inbox test target is now an - explicit lint lane with the same `native_rule_bootstrap,host_test` features - used by the ticketed-ingress regression suite. -- Local verification now treats rustdoc warnings as CI-owned by default. - `scripts/verify-local.sh` skips local rustdoc lanes unless - `VERIFY_LOCAL_RUSTDOC=1` is set, keeping pre-push and full local gates focused - on faster edit-loop witnesses while preserving CI rustdoc coverage. -- Tooling-only full verification now selects focused hook regression scripts by - changed file family, so a `scripts/verify-local.sh` edit runs the verify-local - hook regression without also running unrelated runtime-schema, PR-status, or - timing hook tests. -- Local hook regression tests are now CI-owned by default. The local full gate - skips hook tests unless `VERIFY_LOCAL_HOOK_TESTS=1` is set, and the opt-in - hook-test lane clears verifier override variables before launching nested - hook regressions. +- Writable WAL recovery no longer removes committed records in order to drop + an uncommitted tail. It rebuilt the whole log by deleting every segment + file and re-appending what it kept, so a crash in between lost all + committed history; it also synced the WAL root instead of the directory + that holds the segments, and collapsed rotated segments into the first. + Each segment that loses records is now rebuilt in a scratch file and + swapped in by one rename, a segment with nothing to drop is not touched, + an emptied trailing segment is removed, and the containing directory is + synced. A crash at any step leaves the same committed history. -### Fixed +- A worldline frontier retains the state root computed by its last executable-operation Action commit and returns it without hashing until the graph is next accessed mutably. An Action tick after the first now hashes the whole reachable state once instead of twice. Debug builds hash again to check each retained root, so they do not get the saving. The graph field of `WorldlineState` is private to its module so that every mutation goes through the accessor that drops the retained root. + +- A filesystem WAL writer that takes over an existing log syncs its segment files before reading them as recovered history. A record that an earlier writer wrote and never synced was readable after reopen and was reported as a durable acceptance; a later power loss could still lose it. + +- A scheduler pass that commits executable-operation Actions hashes the worldline's whole reachable state twice instead of three times. The root before the commit is the root the pass already computed for the frontier basis; the commit no longer computes it again. + +- A scheduler pass resolves a writer head's frontier basis once instead of once per admitted executable-operation Action. Resolving it hashes the whole reachable state, so a tick's cost grew with Actions times graph size. The bases produced are unchanged. + +- A scheduler pass builds and digests its retained WAL state delta once per commit instead of once per admitted Action. The WAL bytes are unchanged. + +- A scheduler pass that commits executable-operation Actions no longer copies the worldline's graph. The commit wrote its patch to a copy of the graph and then replaced the original, and the pass's rollback marker held a second copy. The patch is now written in place and each replaced value is journaled before its write; rollback restores from the journal. This applies to patches of node upserts and non-portal attachment writes. A patch that changes graph structure, and a pass that runs native rules through the engine, still retains one copy. Debug builds keep a copy to check the journal. + +- A worldline's initial boundary is shared instead of copied. It never changes after construction, but every scheduler rollback marker, replay checkpoint and replay base cloned the whole graph it holds. Tick cost on a large graph drops by roughly a fifth; the remaining graph-proportional copies are the live state itself. + +- Scheduler rollback checkpoints no longer copy a worldline's committed tick history or committed-ingress ledger on every SuperTick. A checkpoint records the history length and the attempt journals the ingress ids it adds; rollback truncates and removes them. The trusted host no longer clones the runtime, provenance and retained Action outcomes before every tick, or the runtime before every WAL-acknowledged submission: a completed scheduler pass returns an undo handle, and Action staging and submissions are journaled. Rollback results are unchanged; tick and submission cost no longer grow with committed history. + +- A strict filesystem WAL commit no longer rewrites and syncs the writer-epoch ledger and its directory after syncing the commit record. The commit record remains the durability boundary; reopening already derives each epoch's final commit from the segment. Each transaction now forces one sync instead of three. The on-disk formats are unchanged. + +- Package admission rejects result projections whose expected-digest binding conflicts with the selected mutation program, before they can occupy an installed operation coordinate. Recovery applies the same rule to retained installations: a WAL that holds such a hand-built package is refused rather than reinstalled. Echo's providers never emitted that shape, and no invocation of it could be admitted. Admission likewise refuses an expected-digest path that is empty or overlaps the key or replacement path. + +- Both executable mutation providers refuse transformed effect arguments instead of silently projecting the caller input into a different mutation. Direct application-input arguments remain supported; the structurally separate verifier enforces the same boundary. + +- Retained local fork retries reconcile durable topology after uncertain appends, preserving the original basis and preventing duplicate fork records. + +- Observation slots preflight their canonical byte bound and charge execution reads before copying Atom payloads. + +- Private Edict runtime decoding resolves named integer types through their declared width, matching source-function provider acceptance. + +- Filesystem writer takeover preserves unused LSNs after empty epochs and refuses unreconciled tails before changing the epoch ledger. + +- Incremental snapshot state roots now include the existing v1 domain separator and agree with canonical snapshot hashing. Previously emitted incorrect accumulator roots are not migrated. + +- The SPDX checker preserves Markdown frontmatter when it checks or repairs license headers. + +- Action WAL recovery reuses ordered verified replay cursors instead of retaining a full state for each basis tick. It preserves basis, Tick, result, obstruction and conflict checks, including delayed stale bases. + +- The generic operation runner names typed obstruction kinds, footprint conflicts, and missing outcomes in bounded summaries. It omits raw outcome records and invocation data on both Action error paths. + +- `xtask run-edict-operation` runs outside Git and resolves relative artifact and WAL paths from the caller directory. Repository maintenance commands retain their Git-root behavior. + +- `WorldlineState::state_root` documentation now states its reachable-state boundary. Detached create-if-absent writes remain bound by patch and commit identities. Hash bytes are unchanged. + +- `ReadView::at` validates the aperture and derives the selected current frontier + basis with one state hash. `ReadView::new` still checks an independently + selected expected basis. Both retain the same aperture and trust boundaries. + +- Public bounded-read compiler timeouts retain partial stdout/stderr and the + original timeout as the failure cause. + +- Bounded-read lowering recognizes only the exact unsigned primitive coordinates. + Named structural types starting with `U` now reach ordinary type lookup, as + they already do in independent verification and runtime parsing. + +- Private read evaluation identifies the input by its verified `arg.0` identity. + Reordering a package's local declarations no longer changes input selection. + +- Read-provider lawpack validation remains separate from the pure profile's + empty-effect requirement, preserving read acceptance and pure-effect refusal + after integration of the strengthened pure-provider checks. + +- The bounded-read public compiler witness preserves return status and raw + streams when malformed or non-object JSONL prevents structured validation. + +- Bounded-read evaluator tests now require `trusted_runtime` in Cargo and run + in feature-enabled CI and opt-in local routes, including shared parser edits. + +- Removed the bounded-read witness's unguarded image/entrypoint recipe. Public + compiler and fresh-output runtime witnesses require a continuously guarded + reusable worker; pre/post measurements alone are not budget enforcement. + +- Generated provider helpers bind the bounded-read schema publication, including + its exact schema identity in the helper contract and derivation materials. + +- Byte-equality fixture reproduction guidance now requires guarded reusable + compiler storage and preserves the limits of its frozen provider evidence. + +- Byte-equality evaluator tests now require `trusted_runtime` in Cargo and run + in the feature-enabled CI and opt-in local test routes. Selecting the target + without the feature can no longer produce a zero-test success. + +- The ordered-publication compiler witness retains the return code and raw + streams when unexpected output violates JSONL, instead of hiding the compiler + result behind a JSON decoding or non-object error. + +- Removed the unguarded ordered-publication image-build recipe. Its test-only + witness requires a guarded reusable worker with copied sources and accounted + compiler output; the supported runner remains separate follow-up work. + +- Provider artifact generation rejects a contract publication whose exact + schema or manifest differs from the generation input. Cross-publication calls + can no longer emit artifacts with misleading input provenance. + +- The filesystem WAL writer lease now attempts an explicit unlock when its + owning guard drops. A retained duplicate descriptor no longer extends a + successfully released lease and blocks immediate writer takeover. Live-writer + exclusion and the successor's independent lease remain enforced. + +- Native pure executable-package providers now enforce the compiler-owned + projection artifact, node, path, text, and exact-field bounds. The pure output + budget remains Core-declared; the anchored route's fixed output ceiling is + not applied to pure programs. Both independently reproduced checked components + include these bounds. +- Native pure executable-package providers now require an adapter profile with + empty `semanticEffects` and a nonempty `budgetObligation` before constructing + or accepting a no-effects package. Effectful profiles remain unsupported on + this route. Both checked components include this validation. +- The native pure executable-package lowerer and verifier now validate source + artifacts before dispatch: the source reference must match Core's coordinate, + and its canonical value must be a UTF-8 byte string. Rebinding an invalid + source's digest no longer bypasses these checks. Both checked components + include this validation. +- The pure executable-package verifier now compares the application + result projection with the authored Core result and its output contract. + A digest-bound projection could previously select another field or binding, + omit a field, or change its output type or budget and still be accepted. + The independently reproduced checked component includes this check. +- The Edict executable-package verifier now refuses pure-v1 Target IR + that changes Core let bindings, results, input constraints, or basis, or + introduces extra intents or requirements. Exact package reconstruction alone + could accept an independently rebound but semantically inconsistent pair. + The native verifier and checked WASM package carry the same relation check. - Filesystem writer leases explicitly unlock when their owner leaves scope, so a descriptor briefly inherited by a concurrent child process cannot keep From 0973026adda69ec9ffa58e32d27f13d3e24936de Mon Sep 17 00:00:00 2001 From: James Ross Date: Thu, 15 Oct 2026 19:33:17 -0700 Subject: [PATCH 02/50] docs: propose Edict-authored rope law and an effect bridge Record a proposed decision, in the topic that owns executable-operation authorship, that Jedit's ReplaceRange rope law is authored in Jedit-owned Edict source. It supersedes only ADR 0023's "Jedit rope-law closure" section and its selection under "Alternatives for Expressing the Rope Law"; every other ADR 0023 provision still governs. ADR 0023 now links the supersession in its header, the affected section, and the alternatives text, and keeps its original rationale. The new evidence: the declarative resource route was never implemented; Edict source and Echo's evaluators have grown since 2026-07-18; and both routes are gated by the same missing piece, an admitted path from an evaluated Edict operation to graph writes. Add a proposed design, Authored Operation Effects, for that piece: evaluation proposes typed node and edge effects, Echo witnesses the read set and validates writes against a declared footprint law, and the whole proposal commits atomically in one decided Tick or obstructs with no patch. It adds no patch authority, no application vocabulary, and no second persistence path. Both documents are proposals for review and change no runtime behavior. Refs flyingrobots/jedit#296, flyingrobots/jedit#302 --- CHANGELOG.md | 7 + docs/README.md | 1 + ...-admitted-executable-operation-packages.md | 21 +- .../authored-operation-effects.md | 190 ++++++++++++++++++ docs/topics/GeneratedRules.md | 71 +++++++ 5 files changed, 287 insertions(+), 3 deletions(-) create mode 100644 docs/architecture/authored-operation-effects.md diff --git a/CHANGELOG.md b/CHANGELOG.md index 0a2631bc..cac61cb9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -95,6 +95,13 @@ ### Changed +- Proposed, for review, that Jedit's rope law be authored in Edict source, + superseding ADR 0023's declarative rope-law resource choice + ([Generated Rule Authorship](docs/topics/GeneratedRules.md#application-operation-semantics-authored-in-edict-source)), + and a design for turning evaluated Edict operations into validated, atomic + graph effects + ([Authored Operation Effects](docs/architecture/authored-operation-effects.md)). + Documentation only; no runtime behavior changed. - `IngressEnvelope::to_retained_bytes_v2` is now `to_retained_bytes` (#880). It writes `EINGR002`, byte for byte as before, for an envelope without a Continuum occurrence, and `EINGR003` for one with an occurrence. diff --git a/docs/README.md b/docs/README.md index 0bfd2fd0..e5f5198e 100644 --- a/docs/README.md +++ b/docs/README.md @@ -20,6 +20,7 @@ causal history. Git history is the archive; GitHub owns live work and status. - [There Is No Graph](architecture/there-is-no-graph.md) - [Sliced readings and copy-on-write strands](architecture/sliced-readings-and-copy-on-write-strands.md) (accepted design, not implemented) +- [Authored operation effects](architecture/authored-operation-effects.md) (proposed design, not accepted) - [Batched strict submission](architecture/batched-strict-submission.md) - [Participant history as a braid](architecture/participant-history-as-a-braid.md) (accepted design, not implemented) - [Continuum transport](architecture/continuum-transport.md) diff --git a/docs/adr/0023-admitted-executable-operation-packages.md b/docs/adr/0023-admitted-executable-operation-packages.md index cc41e986..4cb0b86f 100644 --- a/docs/adr/0023-admitted-executable-operation-packages.md +++ b/docs/adr/0023-admitted-executable-operation-packages.md @@ -10,6 +10,10 @@ - **Superseded in part by:** the [Action validity across ticks](../topics/GeneratedRules.md#action-validity-across-ticks) decision of 2026-10-14 (#837), for every exact-basis requirement below as it applies to Actions that meet that decision's conditions +- **Superseded in part by:** the + [Application operation semantics authored in Edict source](../topics/GeneratedRules.md#application-operation-semantics-authored-in-edict-source) + decision of 2026-10-15, for the "Jedit rope-law closure" section and the + selection under "Alternatives for Expressing the Rope Law" > **Scheduler lifecycle amendment:** This ADR established exact package, > invocation, private-evaluation, and recovery semantics. ADR 0025 supersedes @@ -291,6 +295,13 @@ admission posture without adding broad authorization policy to this decision. ### Jedit rope-law closure +> **Superseded in part (2026-10-15):** The rope law is now authored in +> Jedit-owned Edict source rather than imported as a declarative executable +> resource. See +> [Application operation semantics authored in Edict source](../topics/GeneratedRules.md#application-operation-semantics-authored-in-edict-source). +> The schema-declaration requirements and the prohibited forms below still +> govern. + For the first `ReplaceRange` vertical, Jedit will use a digest-locked canonical declarative executable semantic resource imported through a Jedit-owned Edict lawpack. This decision does not require Edict source to express the complete @@ -371,7 +382,8 @@ and complete obstruction control. This is the preferable long-term authoring experience if repeated operations demonstrate that those constructs belong in Edict. It is rejected for the first vertical because the current compiler's initial lowerable subset is much -smaller. Implementing the entire language surface before any hook-free +smaller. (This route became the selected direction on 2026-10-15; see the +superseding decision linked above.) Implementing the entire language surface before any hook-free operation runs would turn the first witness into a broad language and runtime design effort and would make `ReplaceRange` define unproven universal syntax. @@ -383,8 +395,11 @@ identity through Core and target lowering, and generate canonical client and package artifacts. The persistent-rope execution law lives in declarative program bytes rather than native code. -This route is selected. It is the smallest route that closes the execution -proof chain without broad Edict expansion. It does not establish a permanent +This route was selected on 2026-07-18 and was superseded on 2026-10-15 before +it was implemented; see +[Application operation semantics authored in Edict source](../topics/GeneratedRules.md#application-operation-semantics-authored-in-edict-source). +The original rationale follows. It is the smallest route that closes the +execution proof chain without broad Edict expansion. It does not establish a permanent rule that complex Edict operations must be authored as external resources. Later Edict syntax may compile to the same target program boundary. diff --git a/docs/architecture/authored-operation-effects.md b/docs/architecture/authored-operation-effects.md new file mode 100644 index 00000000..1702c124 --- /dev/null +++ b/docs/architecture/authored-operation-effects.md @@ -0,0 +1,190 @@ + + + +# Authored Operation Effects + +- **Status:** Proposed design for review. **Not accepted. Not implemented.** + The section [Current implementation](#current-implementation) states what + the runtime does today. +- **Proposed:** 2026-10-15, for review in + [flyingrobots/jedit#302](https://github.com/flyingrobots/jedit/pull/302) +- **Depends on:** [Application operation semantics authored in Edict source](../topics/GeneratedRules.md#application-operation-semantics-authored-in-edict-source) +- **Refines:** [ADR 0023](../adr/0023-admitted-executable-operation-packages.md) + and [ADR 0025](../adr/0025-scheduler-owned-executable-operation-actions.md) + for Actions whose program is evaluated Edict rather than a fixed + single-node profile +- **Related:** [Sliced Readings and Copy-on-Write Strands](sliced-readings-and-copy-on-write-strands.md), + [Generated Rule Authorship](../topics/GeneratedRules.md), + [WAL](../topics/WAL.md) + +## Decision + +An admitted executable operation whose program is compiled Edict may produce a +bounded set of graph effects. Evaluation proposes those effects. Echo witnesses +what the evaluation actually read and what it proposes to write, validates both +against the admitted contract and the basis, and commits all of the effects in +one Tick or none of them. + +Three invariants govern the design: + +1. **Evaluation is not commitment.** A successful evaluation yields a + proposal. Nothing it computes is authoritative until the scheduler commits + the Tick that contains it. +2. **The footprint is witnessed, not claimed.** The evaluator records every + slot it actually reads. The proposal states every slot it writes. Echo + checks both against the footprint law the package declares. A declaration + bounds the footprint; it never substitutes for it. +3. **Multi-node mutation is atomic.** A proposal commits whole within one Tick + and its decided-Tick WAL record, or it obstructs with no parent-visible + patch. A failed operation never leaves part of its effect in history. + +Echo learns no application vocabulary. A proposal is nodes, edges, and atoms. + +## Current implementation + +- Echo main admits two executable programs: anchored single-node alpha-atom + compare-and-set and create-if-absent. Both write bytes supplied by the + invocation (`crates/warp-core/src/echo_operation.rs`). +- `edict_pure` and `edict_read` evaluate compiled Edict values, but no admitted + path turns their results into writes. `edict_read` reads alpha atoms by + explicit node key; it cannot follow edges. +- Create-if-absent obstructs when the node already exists, even with identical + content. +- A Tick holds at most 128 graph operations. +- The state root is a full walk of the reachable graph on every commit. See the + open state-root decision in + [Sliced Readings and Copy-on-Write Strands](sliced-readings-and-copy-on-write-strands.md#open-decisions-that-bound-these-laws). + +## Lifecycle + +```text +admitted package + invocation at an exact basis +-> scheduler selects the Action while constructing a Tick +-> bounded evaluation over a sliced reading of that basis + records: every slot read, steps, allocation + yields: a canonical EffectProposal, or a typed obstruction +-> validation against the package's footprint law, budget, authority, + identity law, and the basis +-> composition with other Actions under the existing independence rule +-> one decided Tick: patch, receipt, decided-Tick WAL record +``` + +Evaluation runs inside Tick construction, as ADR 0025 already requires for +Actions. Proposals are not persisted on their own. Recovery replays decided +Ticks. A pending Action is re-evaluated against its basis after recovery, which +is sound because evaluation is deterministic over a fixed basis. + +## EffectProposal + +A proposal is a canonical value with a domain-separated identity: + +```text +EffectProposal { + basis exact parent coordinate evaluated against + readSet slots read: node, alpha atom, outgoing edges by type + effects [ in canonical order + EnsureNode { node, type, atom } create, or require identical + CreateNode { node, type, atom } require absent + ReplaceAtom { node, expectedAtomDigest, atom } + EnsureEdge { edge, from, to, type } create, or require identical + ReplaceEdge { edge, expectedTo, to } retarget an existing edge + ] + result compiler-declared application result bytes + cost steps, allocated bytes, read bytes, write bytes +} +``` + +Rules: + +- **Effects are the only writes.** The proposal contains no prepared patch. + Echo derives the patch from the effects. +- **Ensure is idempotent only on identity.** An `EnsureNode` whose node exists + with identical type and atom adds nothing to the patch and counts as a read. + An existing node with different content is a typed + `ContentIdentityConflict` obstruction. The same holds for `EnsureEdge`. This + is the idempotent create profile that Edict's I-022 requires before + duplicate content-addressed creation can be lawful. +- **Compare-before-replace.** `ReplaceAtom` and `ReplaceEdge` carry the value + they expect. A mismatch at commit is a stale-basis obstruction, not a merge. +- **No deletes in this profile.** Persistent structures do not need them. + Deletion needs its own design. +- **Identity derivation is the program's.** Echo checks that each node and edge + id matches the identity law the package declares, so ids cannot be chosen to + collide with unrelated nodes. Echo does not invent ids. + +## Footprint law + +The package declares a footprint law: the node types, edge types, and maximum +counts an Action may read and write, relative to its invocation. At validation +Echo checks: + +- the witnessed read set and the proposal's write set lie inside the law; +- counts do not exceed the declared maxima; +- the derived patch's actual footprint equals the proposal's write set. + +Any violation is a typed obstruction with no patch. Independence between +Actions in one Tick uses the witnessed footprints, under the existing rule: +write against read or write conflicts, read against read does not. + +Reading a large shared subtree costs footprint entries but never conflicts. + +## Basis, budget, retries + +- **Basis.** Reads are taken from the exact basis. At commit, every + `expected…` value and every read slot that a co-scheduled Action wrote must + still hold; otherwise the Action obstructs as stale. Action validity across + ticks keeps its existing rules. +- **Budget.** Evaluation is metered as `edict_pure` already meters it. Each + effect adds a fixed write cost. Exhausting any limit obstructs with + `BudgetExceeded`, no proposal, and no patch. A proposal that would exceed the + Tick's operation cap obstructs the same way rather than splitting. +- **Retries.** An Action is identified by package, invocation, and basis. + Re-evaluating the same Action at the same basis yields the same proposal. + Content-addressed `Ensure` effects make a retried, already-committed + operation a no-op where the program says so; the operation's own `Replace` + guard decides whether a retry is a stale basis. + +## Evidence + +The receipt binds, in addition to its current fields: + +- the proposal identity; +- the witnessed read-set digest and the write-set digest; +- the derived patch digest; +- consumed budget. + +The decided-Tick WAL record is the only durability mechanism. This design adds +no second persistence path. + +## What this design does not do + +- It does not create an opaque patch authority. A program cannot submit a + patch; it can only propose typed effects that Echo validates. +- It does not add application intrinsics, a rope primitive, or a + general-purpose virtual machine. +- It does not decide the state-root definition, and it does not make + admission cost independent of history size. + +## Required tests + +Each behaviour below needs a test before implementation: + +| Property | Test | +| ------------------- | ------------------------------------------------------------------------------------------------------------------------ | +| Atomicity | A failure on the last effect of a multi-node proposal leaves the parent state root and patch unchanged. | +| Ensure identity | Identical existing node: no patch entry. Different existing node: `ContentIdentityConflict`. | +| Witnessed footprint | A program that reads a slot outside its law obstructs, even if its writes are in bounds. | +| Stale basis | `ReplaceEdge` with a moved `expectedTo` obstructs with no patch. | +| Budget | Exhausting steps, bytes, or the Tick operation cap obstructs with no proposal committed. | +| Determinism | Same package, invocation, and basis give byte-identical proposals and receipts. | +| Recovery | Crash before and after the decided-Tick WAL record; fresh-host recovery reproduces the same state root and receipts. | +| Composition | Two Actions with disjoint witnessed write sets on one basis commit in one Tick; overlapping ones obstruct the later one. | + +## Open questions + +- How `edict_read` exposes outgoing edges by type, and how a bounded closure + read guided by a pure Edict selector is expressed and budgeted. +- Whether the footprint law is per-invocation or per-package with parameters. +- Which domain-separated hash Edict source can call to derive ids. +- Whether proposals larger than one Tick's operation cap should ever be + representable. This design says no. diff --git a/docs/topics/GeneratedRules.md b/docs/topics/GeneratedRules.md index 8b6c4ba7..5b0b1973 100644 --- a/docs/topics/GeneratedRules.md +++ b/docs/topics/GeneratedRules.md @@ -319,6 +319,77 @@ boundary. The host/WAL shell must be separated from `native_rule_bootstrap` before Jedit can delete the legacy feature without also losing executable operations. +#### Application operation semantics authored in Edict source + +**Decision 2026-10-15 (proposed for review in +[flyingrobots/jedit#302](https://github.com/flyingrobots/jedit/pull/302); tracks +[flyingrobots/jedit#296](https://github.com/flyingrobots/jedit/issues/296)), +superseding in part [ADR 0023](../adr/0023-admitted-executable-operation-packages.md): +its "Jedit rope-law closure" section and its selection under "Alternatives for +Expressing the Rope Law". Every other provision of ADR 0023 continues to +govern.** + +Jedit's persistent-rope `ReplaceRange` law is authored in Jedit-owned Edict +source and compiled by Edict. It is not imported as a declarative executable +resource through a Jedit lawpack, and it is not delegated to a native or +nominally generic sequence-splice capability. + +What does not change: + +- Echo still learns no editor, text, or rope vocabulary. +- The prohibited forms listed under ADR 0023's rope-law closure remain + prohibited: Rust function pointers, callback coordinates, host trait + implementations, Jedit-supplied `MutationPlan`s, a native `replace-range` + intrinsic, and assertions that ambient code implements an operation. +- Jedit must still publish one versioned canonical text-schema declaration + that binds the fact, edge, and attachment propositions, codecs, identity law, + and migration posture, and the package must bind it. +- The evidence-grade table and the handwritten planner's role as test-only + differential evidence are unchanged. + +Evidence for the change, which ADR 0023 did not have on 2026-07-18: + +- The declarative resource route was never implemented. No crate in this + repository decodes or evaluates such a resource, so the route that was chosen + for speed has not delivered a witness. +- Edict source has since grown on its main branch: proven unsigned subtraction, + bounded byte length, slice, and concatenation, and first-order source + functions compiled into authenticated Core. Echo now evaluates those + functions in its pure and bounded-read evaluators (#753). The source route is + no longer the empty-handed alternative ADR 0023 rejected. +- Neither route can produce a committed rope today, for the same reason: no + admitted path turns an evaluated Edict operation into graph writes. The + admitted programs remain single-node compare-and-set and create-if-absent of + caller-supplied bytes. That gap is generic, and it gates every route equally. +- A resource format rich enough to carry rope splitting, joining, balancing, + and obstruction control would itself be an unreviewed second law language. + Authoring in Edict keeps one language for application behavior. + +Consequences: + +- The generic evaluation-to-effect boundary must be designed and reviewed + before it is implemented; the proposal is + [Authored Operation Effects](../architecture/authored-operation-effects.md). + Evaluation proposes effects; it never performs + them. Echo witnesses the actual read and write dependencies, validates basis, + identity, budget, and authority, and commits all proposed effects atomically + within the existing WAL and recovery law, or none. This decision does not + approve an opaque patch authority. +- Edict language features are added only when complete authored source + demonstrates the need. Isolated probes do not justify a feature. +- A native sequence-splice capability is rejected unless the complete authored + implementation demonstrates either a fundamental expressiveness gap that no + small, general language feature closes, or a measured resource problem that + no generic runtime optimization solves without changing authored semantics. + Even then, the logical splice contract must be separated from its + implementation strategy, and the capability requires its own design review. +- Rope completion requires two acceptance targets. The first is semantic + correctness: an Edict-authored `ReplaceRange`, possibly over an inefficient + representation, commits correct bytes with canonical evidence. The second is + persistent-rope correctness: path copying, retained identity for unaffected + subtrees, preserved invariants, and bounded work. The first does not complete + the rope. + ## Footprint Honesty Generated footprints are compile-time claims. Runtime footprint checking in From 791e2b56c76302f670338ef2487a055f839993d5 Mon Sep 17 00:00:00 2001 From: James Ross Date: Thu, 15 Oct 2026 20:07:46 -0700 Subject: [PATCH 03/50] docs: harden the authored-effects design for review Add invariant 4: no committed coordinate contains an edge to an absent node, so a retargeted head is never observable before everything it reaches. Validation rejects dangling endpoints with DanglingReference, because GraphStore::upsert_edge_record does not check them, and commit makes partial visibility impossible by binding all effects to one Tick published only after its decided WAL record. State that reads are witnessed through a recording view over a bounded slice rather than declared, and that today's edict_read only counts reads, so this is new instrumentation. State the composition rule for shared structure: an Ensure of an identical existing node is a read, and two Actions creating the same new node conflict under the existing set test, with no new merge rule; a retry sees the node and reads it. Add three concrete execution traces for Jim's reference ReplaceRange: committed, obstructed by a stale basis at evaluation, and rejected at composition. Extend the required tests accordingly. Refs flyingrobots/jedit#302 --- .../authored-operation-effects.md | 165 ++++++++++++++++-- 1 file changed, 154 insertions(+), 11 deletions(-) diff --git a/docs/architecture/authored-operation-effects.md b/docs/architecture/authored-operation-effects.md index 1702c124..1b187886 100644 --- a/docs/architecture/authored-operation-effects.md +++ b/docs/architecture/authored-operation-effects.md @@ -37,6 +37,10 @@ Three invariants govern the design: 3. **Multi-node mutation is atomic.** A proposal commits whole within one Tick and its decided-Tick WAL record, or it obstructs with no parent-visible patch. A failed operation never leaves part of its effect in history. +4. **Nothing references what is not realized.** No committed coordinate + contains an edge whose endpoint is absent. In particular, a head that a + proposal retargets is never observable before every node and edge it + reaches has been validated and committed in the same Tick. Echo learns no application vocabulary. A proposal is nodes, edges, and atoms. @@ -47,9 +51,16 @@ Echo learns no application vocabulary. A proposal is nodes, edges, and atoms. invocation (`crates/warp-core/src/echo_operation.rs`). - `edict_pure` and `edict_read` evaluate compiled Edict values, but no admitted path turns their results into writes. `edict_read` reads alpha atoms by - explicit node key; it cannot follow edges. + explicit node key; it cannot follow edges. It counts reads against a limit + but does not record which slots it read + (`crates/warp-core/src/edict_read/evaluate.rs`). - Create-if-absent obstructs when the node already exists, even with identical content. +- `GraphStore::upsert_edge_record` does not check that an edge's endpoints + exist (`crates/warp-core/src/graph.rs`). +- Footprint independence is a set test: any write overlapping another Action's + read or write conflicts, including two identical writes + (`crates/warp-core/src/footprint.rs`, `Footprint::independent`). - A Tick holds at most 128 graph operations. - The state root is a full walk of the reachable graph on every commit. See the open state-root decision in @@ -112,6 +123,21 @@ Rules: id matches the identity law the package declares, so ids cannot be chosen to collide with unrelated nodes. Echo does not invent ids. +## Witnessed reads over a bounded slice + +The program never declares what it read. Evaluation reaches state only through +a recording view over the basis, and the read set is exactly the set of slots +that view resolved: node records, alpha atoms, and outgoing edges by type. A +program has no other path to state, so the record is complete by construction. +This needs new instrumentation; today's `edict_read` only counts reads. + +The view resolves each slot on demand from the basis, as +[Sliced Readings and Copy-on-Write Strands](sliced-readings-and-copy-on-write-strands.md) +law 1 requires. The contract never depends on a complete materialized graph: +the read limit and the footprint law bound the slice, and reaching past either +obstructs. A first implementation may still sit on today's fully materialized +worldline state, but nothing in this contract may rely on that. + ## Footprint law The package declares a footprint law: the node types, edge types, and maximum @@ -128,6 +154,31 @@ write against read or write conflicts, read against read does not. Reading a large shared subtree costs footprint entries but never conflicts. +An `Ensure` of a node or edge that already exists identically in the basis is +a read, not a write, so Actions that share existing structure do not conflict. +Two Actions in one Tick that both create the same new content-addressed node +do write the same slot, and the existing rule makes the later one obstruct. +This design adds no merge rule for identical writes. On retry the node exists +in the new basis, the `Ensure` becomes a read, and the conflict disappears. + +## Visibility and referential integrity + +Validation enforces invariant 4 before a proposal can join a Tick: + +- every `EnsureEdge` and `ReplaceEdge` endpoint exists in the basis or is + created by an `EnsureNode` or `CreateNode` in the same proposal; +- every edge a `ReplaceEdge` retargets exists in the basis with the expected + target. + +A violation is a typed `DanglingReference` obstruction with no patch. + +Commit then makes partial visibility impossible by construction. All of an +Action's effects belong to one Tick. Observers, readings, and later Actions bind +to committed Tick coordinates, and a Tick is published only after its decided +WAL record. The order of operations inside the Tick's patch is therefore never +observable: the coordinate before the Tick shows the old head, and the +coordinate after shows the new head with everything it reaches. + ## Basis, budget, retries - **Basis.** Reads are taken from the exact basis. At commit, every @@ -156,6 +207,94 @@ The receipt binds, in addition to its current fields: The decided-Tick WAL record is the only durability mechanism. This design adds no second persistence path. +## Execution traces + +These traces follow Jim's flat-text reference `ReplaceRange` (the first +acceptance target in flyingrobots/jedit#302). The application vocabulary below +belongs to Jim's package; Echo sees only nodes, edges, and atoms. + +Basis coordinate `c` (Tick `t`): + +```text +Buffer B atom {bufferKey "a.txt", version 7} + -canonicalHead-> H1 atom {byteLength 11, lineCount 1, sequence 4} +H1 -root-> T1 atom {bytes "hello world", lineBreaks 0} +H1 -anchor-> I atom {fileCoordinate "a.txt", hostDigest d0} +``` + +Invocation: `{bufferId B, basisHeadId H1, startByte 6, endByte 11, +replacement "there"}` at basis `c`. + +### Committed + +1. **Admission.** The invocation is admitted at `c` under the package's + authority and delegated budget, retained before acknowledgement. +2. **Selection.** The scheduler selects the Action while constructing Tick + `t+1` and opens a recording view over `c`. +3. **Evaluation reads**, recorded by the view: `B` atom; `B` `canonicalHead` + edge (to `H1`); `H1` atom; `H1` `root` edge (to `T1`); `H1` `anchor` edge + (to `I`); `T1` atom. Six slots. +4. **Pure computation.** The guard `canonicalHead == H1` holds; range and + boundaries check; the new bytes are `"hello there"`; the program derives ids + `T2` and `H2` under its identity law. +5. **Proposal**, in canonical order: + - `EnsureNode T2 {bytes "hello there", lineBreaks 0}` + - `EnsureNode H2 {byteLength 11, lineCount 1, sequence 5}` + - `EnsureEdge H2 -root-> T2` + - `EnsureEdge H2 -anchor-> I` + - `ReplaceEdge B -canonicalHead-> expected H1, now H2` + - `ReplaceAtom B expected digest(version 7), now {version 8}` +6. **Validation.** The read and write sets lie inside the footprint law. Echo + recomputes `T2` and `H2` from their atoms and children under the declared + identity law. Endpoints check: `T2` and `H2` are created here, and `I` and + `B` exist at `c`. The expected `H1` and version-7 digest hold at `c`. The + budget holds. +7. **Composition.** The write set is `{T2, H2, H2.root, H2.anchor, +B.canonicalHead, B.atom}`. No co-scheduled Action reads or writes these. +8. **Patch**, derived by Echo: `UpsertNode T2`, `SetAttachment T2`, + `UpsertNode H2`, `SetAttachment H2`, `UpsertEdge H2.root`, + `UpsertEdge H2.anchor`, `UpsertEdge B.canonicalHead` (same edge id, new + target), `SetAttachment B`. Eight operations. +9. **Decision and durability.** The receipt binds the proposal identity, the + read-set and write-set digests, the patch digest, and the consumed budget. + The decided Tick is written to the WAL, then published. +10. **Visibility.** Coordinate `t` shows `B → H1 → T1`. Coordinate `t+1` shows + `B → H2 → T2`, with `H2 → I`. No coordinate shows `H2` without `T2`. `H1` + and `T1` are no longer reachable from `B`; they leave the state root, and + whether their bytes are kept is the retention policy's decision. + +A crash before the WAL record loses the Tick entirely; on recovery the Action +is pending again and re-evaluates against `c` to the same proposal. A crash +after it recovers Tick `t+1` from the WAL, with the same state root and +receipt. + +### Obstructed: stale basis at evaluation + +Another edit has already advanced `B` to head `H3`, so at `c` the +`canonicalHead` edge points to `H3`. + +1. Steps 1–2 as above. +2. Evaluation reads the `B` atom and the `canonicalHead` edge, then the guard + `canonicalHead == H1` fails. +3. The outcome is the typed obstruction `BasisNotCanonical`. There is no + proposal and no patch. The receipt records the obstruction and the two-slot + read set, so the refusal is evidence of what the program saw. +4. The state root at `t+1` is unchanged for this Action. The client must issue + a new invocation against `H3`. + +### Obstructed: conflict at composition + +Two Actions, both evaluated against `H1`, are selected for the same Tick. Both +propose `ReplaceEdge B -canonicalHead-> expected H1`. + +1. Each evaluates and validates on its own. +2. Their write sets overlap on `B.canonicalHead`. In deterministic order the + first joins the Tick; the second gets `RejectedFootprintConflict`, with no + patch. +3. If the second is retried in a later Tick, its basis now shows `H2`, its + guard fails, and it obstructs with `BasisNotCanonical`. Echo never merges + the two edits; reconciling them is the application's decision. + ## What this design does not do - It does not create an opaque patch authority. A program cannot submit a @@ -169,16 +308,20 @@ no second persistence path. Each behaviour below needs a test before implementation: -| Property | Test | -| ------------------- | ------------------------------------------------------------------------------------------------------------------------ | -| Atomicity | A failure on the last effect of a multi-node proposal leaves the parent state root and patch unchanged. | -| Ensure identity | Identical existing node: no patch entry. Different existing node: `ContentIdentityConflict`. | -| Witnessed footprint | A program that reads a slot outside its law obstructs, even if its writes are in bounds. | -| Stale basis | `ReplaceEdge` with a moved `expectedTo` obstructs with no patch. | -| Budget | Exhausting steps, bytes, or the Tick operation cap obstructs with no proposal committed. | -| Determinism | Same package, invocation, and basis give byte-identical proposals and receipts. | -| Recovery | Crash before and after the decided-Tick WAL record; fresh-host recovery reproduces the same state root and receipts. | -| Composition | Two Actions with disjoint witnessed write sets on one basis commit in one Tick; overlapping ones obstruct the later one. | +| Property | Test | +| --------------------- | ------------------------------------------------------------------------------------------------------------------------ | +| Atomicity | A failure on the last effect of a multi-node proposal leaves the parent state root and patch unchanged. | +| Ensure identity | Identical existing node: no patch entry. Different existing node: `ContentIdentityConflict`. | +| Witnessed footprint | A program that reads a slot outside its law obstructs, even if its writes are in bounds. | +| Stale basis | `ReplaceEdge` with a moved `expectedTo` obstructs with no patch. | +| Budget | Exhausting steps, bytes, or the Tick operation cap obstructs with no proposal committed. | +| Determinism | Same package, invocation, and basis give byte-identical proposals and receipts. | +| Recovery | Crash before and after the decided-Tick WAL record; fresh-host recovery reproduces the same state root and receipts. | +| Composition | Two Actions with disjoint witnessed write sets on one basis commit in one Tick; overlapping ones obstruct the later one. | +| Shared structure | Two Actions that `Ensure` the same node already present in the basis both commit in one Tick. | +| Referential integrity | An edge to a node neither in the basis nor created by the proposal obstructs with `DanglingReference`. | +| Visibility | No published coordinate shows a retargeted head without every node and edge it reaches. | +| Witnessed reads | The receipt's read set equals the slots the view resolved, including for a program that reads and then obstructs. | ## Open questions From 81b549a5e198131b8f6f779711d31e0d19766d07 Mon Sep 17 00:00:00 2001 From: James Ross Date: Thu, 15 Oct 2026 20:12:12 -0700 Subject: [PATCH 04/50] docs: witness absence and keep decided outcomes final Record observed absence as a dependency (missing node or atom, empty or specific outgoing-edge set) against the basis, so a co-scheduled Action that creates the slot invalidates the reader. Align dependency kinds with the Action validity across ticks decision (#837): value and single-key absence are supported; reading edges by type is a topology dependency that keeps the whole-frontier rule until sound edge-set tracking exists there. Make retry identity explicit: a decided outcome, receipt, and attribution never change; a retry is a new submission with its own identity; Echo does not retry on its own; and a never-decided Action recovered after a crash is the same submission, not a retry. Update the composition trace and the required tests accordingly. Refs flyingrobots/jedit#302 --- .../authored-operation-effects.md | 71 ++++++++++++++----- 1 file changed, 55 insertions(+), 16 deletions(-) diff --git a/docs/architecture/authored-operation-effects.md b/docs/architecture/authored-operation-effects.md index 1b187886..a58a4522 100644 --- a/docs/architecture/authored-operation-effects.md +++ b/docs/architecture/authored-operation-effects.md @@ -131,6 +131,28 @@ that view resolved: node records, alpha atoms, and outgoing edges by type. A program has no other path to state, so the record is complete by construction. This needs new instrumentation; today's `edict_read` only counts reads. +Absence is a dependency too. Every resolution is recorded with its outcome, +against the basis it was resolved at: + +| Resolution | Recorded dependency | +| -------------------------------------- | ---------------------------------------------------------------------- | +| node or atom present | the slot and a digest of its value | +| node or atom absent | the slot, observed absent | +| outgoing edges of one type from a node | the source node and type, with the target set observed, empty included | + +An `Ensure` that finds no existing node depends on that absence, so a +co-scheduled Action that creates the slot invalidates it like any other +changed read. + +These dependency kinds follow the +[Action validity across ticks](../topics/GeneratedRules.md#action-validity-across-ticks) +decision (#837). Value and single-key absence dependencies are the kinds that +decision supports. A read of outgoing edges by type is a topology dependency, +which that decision does not yet support, so an Action that reads edges keeps +the whole-frontier rule: it commits only onto the exact basis it was evaluated +against. Widening that requires sound edge-set tracking under #837, not a +looser rule here. + The view resolves each slot on demand from the basis, as [Sliced Readings and Copy-on-Write Strands](sliced-readings-and-copy-on-write-strands.md) law 1 requires. The contract never depends on a complete materialized graph: @@ -158,8 +180,9 @@ An `Ensure` of a node or edge that already exists identically in the basis is a read, not a write, so Actions that share existing structure do not conflict. Two Actions in one Tick that both create the same new content-addressed node do write the same slot, and the existing rule makes the later one obstruct. -This design adds no merge rule for identical writes. On retry the node exists -in the new basis, the `Ensure` becomes a read, and the conflict disappears. +This design adds no merge rule for identical writes. The rejected Action's +outcome is final (see Retries); a new submission evaluated later finds the +node in its basis, so its `Ensure` is a read and does not conflict. ## Visibility and referential integrity @@ -181,19 +204,27 @@ coordinate after shows the new head with everything it reaches. ## Basis, budget, retries -- **Basis.** Reads are taken from the exact basis. At commit, every - `expected…` value and every read slot that a co-scheduled Action wrote must - still hold; otherwise the Action obstructs as stale. Action validity across - ticks keeps its existing rules. +- **Basis.** Reads, including observed absences, are taken from the exact + basis. At commit, every `expected…` value and every recorded dependency that + a co-scheduled Action wrote must still hold; otherwise the Action obstructs + as stale. Action validity across ticks keeps its existing rules, and an + Action is never re-stamped as if it had been evaluated against a later basis. - **Budget.** Evaluation is metered as `edict_pure` already meters it. Each effect adds a fixed write cost. Exhausting any limit obstructs with `BudgetExceeded`, no proposal, and no patch. A proposal that would exceed the Tick's operation cap obstructs the same way rather than splitting. -- **Retries.** An Action is identified by package, invocation, and basis. - Re-evaluating the same Action at the same basis yields the same proposal. - Content-addressed `Ensure` effects make a retried, already-committed - operation a no-op where the program says so; the operation's own `Replace` - guard decides whether a retry is a stale basis. +- **Retries.** A decided outcome is final. An Action is identified by its + submission; once it is decided (committed, obstructed, or + `RejectedFootprintConflict`), its outcome, receipt, and attribution never + change, and a later success is never written over an earlier conflict. A + retry is a new submission with its own identity and attribution, evaluated + against whatever basis is current when the scheduler selects it, and it may + reach a different outcome. Echo does not retry on its own; whether to submit + again is the application's decision. +- **Pending is not retried.** An Action that was never decided, for example + one whose Tick was lost in a crash before its WAL record, remains the same + submission. Recovery re-evaluates it; that is not a retry, because no + outcome was ever recorded for it. ## Evidence @@ -263,8 +294,9 @@ B.canonicalHead, B.atom}`. No co-scheduled Action reads or writes these. and `T1` are no longer reachable from `B`; they leave the state root, and whether their bytes are kept is the retention policy's decision. -A crash before the WAL record loses the Tick entirely; on recovery the Action -is pending again and re-evaluates against `c` to the same proposal. A crash +A crash before the WAL record loses the Tick entirely; the Action was never +decided, so on recovery it is the same pending submission and re-evaluates +against `c` to the same proposal. A crash after it recovers Tick `t+1` from the WAL, with the same state root and receipt. @@ -291,9 +323,12 @@ propose `ReplaceEdge B -canonicalHead-> expected H1`. 2. Their write sets overlap on `B.canonicalHead`. In deterministic order the first joins the Tick; the second gets `RejectedFootprintConflict`, with no patch. -3. If the second is retried in a later Tick, its basis now shows `H2`, its - guard fails, and it obstructs with `BasisNotCanonical`. Echo never merges - the two edits; reconciling them is the application's decision. +3. The second Action's `RejectedFootprintConflict` is its final outcome. If + the application submits the edit again, that is a new submission with its + own identity; evaluated at `t+1`, its basis shows `H2`, its guard fails, and + it obstructs with `BasisNotCanonical`. Echo never merges the two edits and + never rewrites the first rejection; reconciling them is the application's + decision. ## What this design does not do @@ -322,6 +357,10 @@ Each behaviour below needs a test before implementation: | Referential integrity | An edge to a node neither in the basis nor created by the proposal obstructs with `DanglingReference`. | | Visibility | No published coordinate shows a retargeted head without every node and edge it reaches. | | Witnessed reads | The receipt's read set equals the slots the view resolved, including for a program that reads and then obstructs. | +| Negative reads | An Action that observed a node, atom, or edge set absent obstructs when a co-scheduled Action creates it. | +| Edge-read basis | An Action that read edges commits only onto its exact evaluation basis. | +| Final outcomes | A rejected Action's outcome, receipt, and attribution are unchanged after a later submission of the same edit succeeds. | +| Pending recovery | An Action lost with its Tick before the WAL record is recovered as the same submission, not a new one. | ## Open questions From 210eb3945a31a6defc9aba8e07e2c2d7f7a08dab Mon Sep 17 00:00:00 2001 From: James Ross Date: Thu, 15 Oct 2026 20:19:03 -0700 Subject: [PATCH 05/50] docs: tie the effect bridge to Echo's identity, attribution, and WAL contracts Add an identity chain: submission (Submission Attribution and canonical inbox sequencing), Action (ADR 0023/0025), proposal (new, evidence only, deterministic per Action and basis), and decision (recorded once by submission_id). The bridge removes no admission check, adds no identity root, and persists nothing outside the decided-Tick WAL record. Retries now rest on Echo's existing intake idempotence: identical content returns the existing submission as Duplicate with its original outcome; a real new attempt must differ, which makes it a new submission. The new receipt-entry fields change an encoding that commit hash v3 binds, so they need a new entry format version under Submission Attribution's format rules. Recovery replays decided Ticks and checks the patch digest and commit hash without re-evaluating; scoped-validity Actions follow #837's recomputation; undecided Actions re-evaluate as the same submission. Extend the required tests accordingly. Refs flyingrobots/jedit#302 --- .../authored-operation-effects.md | 108 +++++++++++++----- 1 file changed, 79 insertions(+), 29 deletions(-) diff --git a/docs/architecture/authored-operation-effects.md b/docs/architecture/authored-operation-effects.md index a58a4522..1a467cfe 100644 --- a/docs/architecture/authored-operation-effects.md +++ b/docs/architecture/authored-operation-effects.md @@ -214,29 +214,75 @@ coordinate after shows the new head with everything it reaches. `BudgetExceeded`, no proposal, and no patch. A proposal that would exceed the Tick's operation cap obstructs the same way rather than splitting. - **Retries.** A decided outcome is final. An Action is identified by its - submission; once it is decided (committed, obstructed, or - `RejectedFootprintConflict`), its outcome, receipt, and attribution never - change, and a later success is never written over an earlier conflict. A - retry is a new submission with its own identity and attribution, evaluated - against whatever basis is current when the scheduler selects it, and it may - reach a different outcome. Echo does not retry on its own; whether to submit - again is the application's decision. + submission (see Identity and existing contracts); once it is decided + (committed, obstructed, or `RejectedFootprintConflict`), its outcome, + receipt, and attribution never change, and a later success is never written + over an earlier conflict. Echo's intake already enforces this: resubmitting + identical content on the same head returns the existing submission as a + `Duplicate`, with its original outcome and no new Tick. A real new attempt + must differ, for example an invocation rebased onto the new head, or a + causal parent citing the earlier outcome; that difference makes it a new + submission with its own identity and attribution, evaluated against whatever + basis is current when the scheduler selects it. Echo does not retry on its + own; whether to submit again is the application's decision. - **Pending is not retried.** An Action that was never decided, for example one whose Tick was lost in a crash before its WAL record, remains the same submission. Recovery re-evaluates it; that is not a retry, because no outcome was ever recorded for it. +## Identity and existing contracts + +The bridge adds evidence, not a new identity root or a new authority path. Each +step below already exists except the proposal, which is evidence inside one +decision: + +| Object | Identity | Owner | Status | +| ---------- | ------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------- | ------------------ | +| Submission | `submission_id = H("echo.intent-submission.v0", worldline, head, ingress_id)`; a Continuum occurrence key is hashed into `ingress_id` | [Submission Attribution](../topics/SubmissionAttribution.md), [canonical inbox sequencing](../spec/canonical-inbox-sequencing.md) | existing | +| Action | admitted package and projected invocation | ADR 0023, ADR 0025 | existing | +| Proposal | `H(Action, evaluation basis, canonical EffectProposal)`; deterministic, so re-evaluating an undecided Action reproduces it | this design | new, evidence only | +| Decision | committed, obstructed, or `RejectedFootprintConflict`, recorded once in the Tick's attribution section by `submission_id` | Submission Attribution | existing | + +Consequences: + +- **Admission** is unchanged: the bridge runs inside scheduler-owned Tick + construction after ordinary admission, and adds checks; it removes none. +- **Attribution** is unchanged: an Action batch already attributes each + candidate to its own receipt entry. The proposal identity and digests below + ride in that entry; the attribution section still names only submissions. +- **Durability** is unchanged: the decided-Tick WAL record (`EPRV0003`) is the + only persistence. Proposals are never retained apart from their Tick. + ## Evidence -The receipt binds, in addition to its current fields: +Each Action's receipt entry binds, in addition to its current fields: - the proposal identity; -- the witnessed read-set digest and the write-set digest; +- the witnessed read-set digest, including observed absences, and the + write-set digest; - the derived patch digest; - consumed budget. -The decided-Tick WAL record is the only durability mechanism. This design adds -no second persistence path. +These fields change the receipt entry's encoding, which commit hash v3 binds. +They therefore need a new receipt entry format version, introduced under the +same rules as Submission Attribution's format table: existing formats and +retained legacy ticks stay readable and unchanged, and a Tick written with the +new format says so. + +Recovery: + +- A decided Tick is recovered from its WAL record by replaying its patch. + Echo then checks the patch digest and the commit hash, which binds the + receipt and its attribution section, so the recovered evidence is exactly + the evidence that was committed. +- Recovery does not re-evaluate decided Actions. Their read sets and proposals + are evidence about a past evaluation, not inputs to replay. +- An Action admitted under the scoped validity of + [Action validity across ticks](../topics/GeneratedRules.md#action-validity-across-ticks) + follows that decision: recovery recomputes its validation at the recorded + position from retained history and refuses a history whose result differs. +- An undecided Action is re-evaluated as the same submission, and determinism + makes it reproduce the same proposal. ## Execution traces @@ -343,24 +389,28 @@ propose `ReplaceEdge B -canonicalHead-> expected H1`. Each behaviour below needs a test before implementation: -| Property | Test | -| --------------------- | ------------------------------------------------------------------------------------------------------------------------ | -| Atomicity | A failure on the last effect of a multi-node proposal leaves the parent state root and patch unchanged. | -| Ensure identity | Identical existing node: no patch entry. Different existing node: `ContentIdentityConflict`. | -| Witnessed footprint | A program that reads a slot outside its law obstructs, even if its writes are in bounds. | -| Stale basis | `ReplaceEdge` with a moved `expectedTo` obstructs with no patch. | -| Budget | Exhausting steps, bytes, or the Tick operation cap obstructs with no proposal committed. | -| Determinism | Same package, invocation, and basis give byte-identical proposals and receipts. | -| Recovery | Crash before and after the decided-Tick WAL record; fresh-host recovery reproduces the same state root and receipts. | -| Composition | Two Actions with disjoint witnessed write sets on one basis commit in one Tick; overlapping ones obstruct the later one. | -| Shared structure | Two Actions that `Ensure` the same node already present in the basis both commit in one Tick. | -| Referential integrity | An edge to a node neither in the basis nor created by the proposal obstructs with `DanglingReference`. | -| Visibility | No published coordinate shows a retargeted head without every node and edge it reaches. | -| Witnessed reads | The receipt's read set equals the slots the view resolved, including for a program that reads and then obstructs. | -| Negative reads | An Action that observed a node, atom, or edge set absent obstructs when a co-scheduled Action creates it. | -| Edge-read basis | An Action that read edges commits only onto its exact evaluation basis. | -| Final outcomes | A rejected Action's outcome, receipt, and attribution are unchanged after a later submission of the same edit succeeds. | -| Pending recovery | An Action lost with its Tick before the WAL record is recovered as the same submission, not a new one. | +| Property | Test | +| ---------------------- | -------------------------------------------------------------------------------------------------------------------------- | +| Atomicity | A failure on the last effect of a multi-node proposal leaves the parent state root and patch unchanged. | +| Ensure identity | Identical existing node: no patch entry. Different existing node: `ContentIdentityConflict`. | +| Witnessed footprint | A program that reads a slot outside its law obstructs, even if its writes are in bounds. | +| Stale basis | `ReplaceEdge` with a moved `expectedTo` obstructs with no patch. | +| Budget | Exhausting steps, bytes, or the Tick operation cap obstructs with no proposal committed. | +| Determinism | Same package, invocation, and basis give byte-identical proposals and receipts. | +| Recovery | Crash before and after the decided-Tick WAL record; fresh-host recovery reproduces the same state root and receipts. | +| Composition | Two Actions with disjoint witnessed write sets on one basis commit in one Tick; overlapping ones obstruct the later one. | +| Shared structure | Two Actions that `Ensure` the same node already present in the basis both commit in one Tick. | +| Referential integrity | An edge to a node neither in the basis nor created by the proposal obstructs with `DanglingReference`. | +| Visibility | No published coordinate shows a retargeted head without every node and edge it reaches. | +| Witnessed reads | The receipt's read set equals the slots the view resolved, including for a program that reads and then obstructs. | +| Negative reads | An Action that observed a node, atom, or edge set absent obstructs when a co-scheduled Action creates it. | +| Edge-read basis | An Action that read edges commits only onto its exact evaluation basis. | +| Final outcomes | A rejected Action's outcome, receipt, and attribution are unchanged after a later submission of the same edit succeeds. | +| Duplicate resubmission | Resubmitting a decided Action's identical content returns `Duplicate` with the original outcome and commits no Tick. | +| Attribution | Every Action in a bridge Tick appears once in the attribution section, mapped to its own receipt entry. | +| Receipt format | Ticks written before the new entry format recover unchanged; new Ticks declare the new format. | +| Recovered evidence | A recovered Tick's proposal identity, read-set, write-set, and patch digests equal those committed, without re-evaluation. | +| Pending recovery | An Action lost with its Tick before the WAL record is recovered as the same submission, not a new one. | ## Open questions From c05cf3efcd6a97e49b4dbdb4b80795d246d794a7 Mon Sep 17 00:00:00 2001 From: James Ross Date: Thu, 15 Oct 2026 22:06:22 -0700 Subject: [PATCH 06/50] docs(changelog): drop the unshipped authored-effects proposal entry The changelog records shipped behavior. The decision and design in this branch are documentation and change no runtime behavior. --- CHANGELOG.md | 7 ------- 1 file changed, 7 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index cac61cb9..0a2631bc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -95,13 +95,6 @@ ### Changed -- Proposed, for review, that Jedit's rope law be authored in Edict source, - superseding ADR 0023's declarative rope-law resource choice - ([Generated Rule Authorship](docs/topics/GeneratedRules.md#application-operation-semantics-authored-in-edict-source)), - and a design for turning evaluated Edict operations into validated, atomic - graph effects - ([Authored Operation Effects](docs/architecture/authored-operation-effects.md)). - Documentation only; no runtime behavior changed. - `IngressEnvelope::to_retained_bytes_v2` is now `to_retained_bytes` (#880). It writes `EINGR002`, byte for byte as before, for an envelope without a Continuum occurrence, and `EINGR003` for one with an occurrence. From b26714f242070d05c5314a382ee864ac5ebb84f3 Mon Sep 17 00:00:00 2001 From: James Ross Date: Thu, 15 Oct 2026 22:06:27 -0700 Subject: [PATCH 07/50] docs: accept authored effects on merge and correct the bridge design Acceptance: the decision and the design are accepted effective when #889 merges to main; before that they govern nothing and ADR 0023 is unchanged. ADR 0023's rope-law closure banner now enumerates exactly which resource requirements are superseded and which still govern, and keeps the original text for its reasoning. Acceptance covers the contract, not an implementation. Design corrections from review: - keep recovery's preparation re-derivation and identity comparison, extended to the new evidence; verification is not re-admission - scope the 128-operation limit to Action batches, not Ticks - give receipts an explicit NoProposal posture for evaluations that obstruct before proposing - scope referential integrity to edges a proposal adds or retargets - rewrite the stale-basis trace at a later evaluation coordinate - compare proposals, not receipts, across Tick contexts - test write/read as well as write/write composition conflicts - state that the bridge implies no Continuum transport or replica import --- docs/README.md | 2 +- ...-admitted-executable-operation-packages.md | 38 ++- .../authored-operation-effects.md | 219 ++++++++++++------ docs/topics/GeneratedRules.md | 18 +- 4 files changed, 186 insertions(+), 91 deletions(-) diff --git a/docs/README.md b/docs/README.md index e5f5198e..87844298 100644 --- a/docs/README.md +++ b/docs/README.md @@ -20,7 +20,7 @@ causal history. Git history is the archive; GitHub owns live work and status. - [There Is No Graph](architecture/there-is-no-graph.md) - [Sliced readings and copy-on-write strands](architecture/sliced-readings-and-copy-on-write-strands.md) (accepted design, not implemented) -- [Authored operation effects](architecture/authored-operation-effects.md) (proposed design, not accepted) +- [Authored operation effects](architecture/authored-operation-effects.md) (accepted design on merge of #889, not implemented) - [Batched strict submission](architecture/batched-strict-submission.md) - [Participant history as a braid](architecture/participant-history-as-a-braid.md) (accepted design, not implemented) - [Continuum transport](architecture/continuum-transport.md) diff --git a/docs/adr/0023-admitted-executable-operation-packages.md b/docs/adr/0023-admitted-executable-operation-packages.md index 4cb0b86f..087a8795 100644 --- a/docs/adr/0023-admitted-executable-operation-packages.md +++ b/docs/adr/0023-admitted-executable-operation-packages.md @@ -12,8 +12,11 @@ applies to Actions that meet that decision's conditions - **Superseded in part by:** the [Application operation semantics authored in Edict source](../topics/GeneratedRules.md#application-operation-semantics-authored-in-edict-source) - decision of 2026-10-15, for the "Jedit rope-law closure" section and the - selection under "Alternatives for Expressing the Rope Law" + decision, accepted 2026-10-15 and effective when + [flyingrobots/echo#889](https://github.com/flyingrobots/echo/pull/889) merges + to `main`, for the declarative-resource requirements of the "Jedit rope-law + closure" section (enumerated in its banner) and the selection under + "Alternatives for Expressing the Rope Law" > **Scheduler lifecycle amendment:** This ADR established exact package, > invocation, private-evaluation, and recovery semantics. ADR 0025 supersedes @@ -295,12 +298,25 @@ admission posture without adding broad authorization policy to this decision. ### Jedit rope-law closure -> **Superseded in part (2026-10-15):** The rope law is now authored in -> Jedit-owned Edict source rather than imported as a declarative executable -> resource. See +> **Superseded in part,** effective when +> [flyingrobots/echo#889](https://github.com/flyingrobots/echo/pull/889) merges +> to `main`. The rope law is authored in Jedit-owned Edict source rather than +> imported as a declarative executable resource. See > [Application operation semantics authored in Edict source](../topics/GeneratedRules.md#application-operation-semantics-authored-in-edict-source). -> The schema-declaration requirements and the prohibited forms below still -> govern. +> The text below is kept as written, for its reasoning. +> +> **No longer required:** the declarative resource and lawpack named in the +> first paragraph; the fifth required binding (the rope-law resource +> coordinate and digest); the lawpack and `EchoOperationProgramV1` steps of the +> binding chain and the paragraph after it; and the paragraphs on what makes +> the resource acceptable and who owns it. +> +> **Still governing:** the versioned canonical text-schema declaration and its +> first four required bindings; the package's binding of that declaration's +> coordinate and digest, with the typed schema-closure refusal, applied to the +> package's compiled Edict program in place of the lawpack and subordinate +> program; the prohibited forms, which the compiled Edict program may not +> contain or resolve to; and the handwritten planner's test-only role. For the first `ReplaceRange` vertical, Jedit will use a digest-locked canonical declarative executable semantic resource imported through a Jedit-owned Edict @@ -382,8 +398,8 @@ and complete obstruction control. This is the preferable long-term authoring experience if repeated operations demonstrate that those constructs belong in Edict. It is rejected for the first vertical because the current compiler's initial lowerable subset is much -smaller. (This route became the selected direction on 2026-10-15; see the -superseding decision linked above.) Implementing the entire language surface before any hook-free +smaller. (The superseding decision linked above selects this route, effective +when flyingrobots/echo#889 merges.) Implementing the entire language surface before any hook-free operation runs would turn the first witness into a broad language and runtime design effort and would make `ReplaceRange` define unproven universal syntax. @@ -395,8 +411,8 @@ identity through Core and target lowering, and generate canonical client and package artifacts. The persistent-rope execution law lives in declarative program bytes rather than native code. -This route was selected on 2026-07-18 and was superseded on 2026-10-15 before -it was implemented; see +This route was selected on 2026-07-18 and was never implemented. The selection +is superseded, effective when flyingrobots/echo#889 merges; see [Application operation semantics authored in Edict source](../topics/GeneratedRules.md#application-operation-semantics-authored-in-edict-source). The original rationale follows. It is the smallest route that closes the execution proof chain without broad Edict expansion. It does not establish a permanent diff --git a/docs/architecture/authored-operation-effects.md b/docs/architecture/authored-operation-effects.md index 1a467cfe..5fe1f906 100644 --- a/docs/architecture/authored-operation-effects.md +++ b/docs/architecture/authored-operation-effects.md @@ -3,10 +3,16 @@ # Authored Operation Effects -- **Status:** Proposed design for review. **Not accepted. Not implemented.** - The section [Current implementation](#current-implementation) states what - the runtime does today. -- **Proposed:** 2026-10-15, for review in +- **Status:** Accepted design, effective when + [flyingrobots/echo#889](https://github.com/flyingrobots/echo/pull/889) merges + to `main`. Before that merge it is a proposal and governs nothing. + **Not implemented.** Acceptance establishes the contract; it does not claim + the bridge exists. The section + [Current implementation](#current-implementation) states what the runtime + does today. +- **Proposed:** 2026-10-15 in + [flyingrobots/echo#889](https://github.com/flyingrobots/echo/pull/889); + application context in [flyingrobots/jedit#302](https://github.com/flyingrobots/jedit/pull/302) - **Depends on:** [Application operation semantics authored in Edict source](../topics/GeneratedRules.md#application-operation-semantics-authored-in-edict-source) - **Refines:** [ADR 0023](../adr/0023-admitted-executable-operation-packages.md) @@ -37,10 +43,15 @@ Three invariants govern the design: 3. **Multi-node mutation is atomic.** A proposal commits whole within one Tick and its decided-Tick WAL record, or it obstructs with no parent-visible patch. A failed operation never leaves part of its effect in history. -4. **Nothing references what is not realized.** No committed coordinate - contains an edge whose endpoint is absent. In particular, a head that a - proposal retargets is never observable before every node and edge it - reaches has been validated and committed in the same Tick. +4. **A proposal introduces no dangling reference.** Every edge a proposal + adds or retargets has endpoints that exist in the basis or are created in + the same proposal. In particular, a head that a proposal retargets is never + observable before every node and edge the proposal makes it reach has been + validated and committed in the same Tick. This invariant covers only the + edges a proposal introduces or retargets. It does not claim that the basis + is free of dangling edges: other writers do not check endpoints today (see + [Current implementation](#current-implementation)), and this design does not + validate the rest of the graph. Echo learns no application vocabulary. A proposal is nodes, edges, and atoms. @@ -61,7 +72,19 @@ Echo learns no application vocabulary. A proposal is nodes, edges, and atoms. - Footprint independence is a set test: any write overlapping another Action's read or write conflicts, including two identical writes (`crates/warp-core/src/footprint.rs`, `Footprint::independent`). -- A Tick holds at most 128 graph operations. +- An executable-operation Action batch selects at most 64 candidates + (`ACTION_BATCH_CANDIDATE_LIMIT_V1`) and accepts at most 128 patch operations + summed over its accepted Actions (`ACTION_BATCH_OPERATION_LIMIT_V1`); a + candidate that would exceed the operation limit is obstructed rather than + accepted (`crates/warp-core/src/echo_operation.rs`). These are Action-batch + composition limits, not Tick limits: `WarpTickPatchV1::new` accepts an + operation vector of any length. +- Recovery re-derives each committed and footprint-conflicted Action's + preparation from the recovered parent state and refuses history whose + package, invocation, basis, private-evaluation, patch, result, footprint, or + preparation identity differs from the recorded outcome; obstructed Actions + are re-evaluated and their pre-decision evidence compared + (`crates/warp-core/src/trusted_runtime_host.rs`, ADR 0025). - The state root is a full walk of the reachable graph on every commit. See the open state-root decision in [Sliced Readings and Copy-on-Write Strands](sliced-readings-and-copy-on-write-strands.md#open-decisions-that-bound-these-laws). @@ -82,8 +105,10 @@ admitted package + invocation at an exact basis Evaluation runs inside Tick construction, as ADR 0025 already requires for Actions. Proposals are not persisted on their own. Recovery replays decided -Ticks. A pending Action is re-evaluated against its basis after recovery, which -is sound because evaluation is deterministic over a fixed basis. +Ticks and re-derives each decided Action's evaluation to verify the recorded +evidence (see [Recovery](#recovery)). A pending Action is re-evaluated after +recovery when the scheduler selects it. Both are sound because evaluation is +deterministic over a fixed basis. ## EffectProposal @@ -195,6 +220,11 @@ Validation enforces invariant 4 before a proposal can join a Tick: A violation is a typed `DanglingReference` obstruction with no patch. +Validation examines only the edges the proposal adds or retargets, and the +endpoints those edges name. An existing dangling edge elsewhere in the basis +neither obstructs an unrelated proposal nor is repaired by it. Whether Echo +should reject dangling edges from every writer is a separate decision. + Commit then makes partial visibility impossible by construction. All of an Action's effects belong to one Tick. Observers, readings, and later Actions bind to committed Tick coordinates, and a Tick is published only after its decided @@ -210,9 +240,13 @@ coordinate after shows the new head with everything it reaches. as stale. Action validity across ticks keeps its existing rules, and an Action is never re-stamped as if it had been evaluated against a later basis. - **Budget.** Evaluation is metered as `edict_pure` already meters it. Each - effect adds a fixed write cost. Exhausting any limit obstructs with - `BudgetExceeded`, no proposal, and no patch. A proposal that would exceed the - Tick's operation cap obstructs the same way rather than splitting. + effect adds a fixed write cost. Exhausting any evaluation limit obstructs + with `BudgetExceeded`, no proposal, and no patch. A proposal whose derived + patch alone exceeds the Action-batch operation limit + (`ACTION_BATCH_OPERATION_LIMIT_V1`) obstructs the same way rather than + splitting across Ticks. A proposal that fits alone but not beside the + Actions already accepted into the batch is handled by the existing batch + rule, which obstructs that candidate in this batch. - **Retries.** A decided outcome is final. An Action is identified by its submission (see Identity and existing contracts); once it is decided (committed, obstructed, or `RejectedFootprintConflict`), its outcome, @@ -227,8 +261,12 @@ coordinate after shows the new head with everything it reaches. own; whether to submit again is the application's decision. - **Pending is not retried.** An Action that was never decided, for example one whose Tick was lost in a crash before its WAL record, remains the same - submission. Recovery re-evaluates it; that is not a retry, because no - outcome was ever recorded for it. + submission. It is evaluated again when the scheduler selects it; that is not + a retry, because no outcome was ever recorded for it. +- **Verification is not re-admission.** Recovery re-evaluates decided Actions + only to verify their recorded evidence (see [Recovery](#recovery)). It never + re-decides them, never stamps a new outcome, and never applies a re-derived + proposal. ## Identity and existing contracts @@ -236,12 +274,12 @@ The bridge adds evidence, not a new identity root or a new authority path. Each step below already exists except the proposal, which is evidence inside one decision: -| Object | Identity | Owner | Status | -| ---------- | ------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------- | ------------------ | -| Submission | `submission_id = H("echo.intent-submission.v0", worldline, head, ingress_id)`; a Continuum occurrence key is hashed into `ingress_id` | [Submission Attribution](../topics/SubmissionAttribution.md), [canonical inbox sequencing](../spec/canonical-inbox-sequencing.md) | existing | -| Action | admitted package and projected invocation | ADR 0023, ADR 0025 | existing | -| Proposal | `H(Action, evaluation basis, canonical EffectProposal)`; deterministic, so re-evaluating an undecided Action reproduces it | this design | new, evidence only | -| Decision | committed, obstructed, or `RejectedFootprintConflict`, recorded once in the Tick's attribution section by `submission_id` | Submission Attribution | existing | +| Object | Identity | Owner | Status | +| ---------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------- | ------------------ | +| Submission | `submission_id = H("echo.intent-submission.v0", worldline, head, ingress_id)`; a Continuum occurrence key is hashed into `ingress_id` | [Submission Attribution](../topics/SubmissionAttribution.md), [canonical inbox sequencing](../spec/canonical-inbox-sequencing.md) | existing | +| Action | admitted package and projected invocation | ADR 0023, ADR 0025 | existing | +| Proposal | `H(Action, evaluation basis, canonical EffectProposal)` when evaluation produced a proposal; otherwise none (see Evidence); deterministic, so re-evaluation over the same basis reproduces it | this design | new, evidence only | +| Decision | committed, obstructed, or `RejectedFootprintConflict`, recorded once in the Tick's attribution section by `submission_id` | Submission Attribution | existing | Consequences: @@ -257,32 +295,55 @@ Consequences: Each Action's receipt entry binds, in addition to its current fields: -- the proposal identity; -- the witnessed read-set digest, including observed absences, and the - write-set digest; -- the derived patch digest; +- the proposal posture, a tagged value with exactly two variants: + - `NoProposal`: evaluation obstructed before it produced a proposal, for + example `BudgetExceeded` or an authored guard such as + `BasisNotCanonical`. No proposal identity, write-set digest, or patch + digest exists, and none is manufactured; + - `Proposed { proposal identity, write-set digest, derived patch digest }`: + evaluation produced a proposal, whether it then committed, obstructed at + validation, or was rejected at composition; +- the witnessed read-set digest, including observed absences, in both + variants, because a refusal is still evidence of what the program saw; - consumed budget. +The submission identity is present in both variants. An identified submission +whose evaluation produced no proposal is a different evidence state from an +evaluated proposal, and the receipt keeps them distinct. + These fields change the receipt entry's encoding, which commit hash v3 binds. They therefore need a new receipt entry format version, introduced under the same rules as Submission Attribution's format table: existing formats and retained legacy ticks stay readable and unchanged, and a Tick written with the new format says so. -Recovery: +### Recovery + +The bridge keeps Echo's existing recovery verification and extends it to the +new evidence. Verification is not re-admission. - A decided Tick is recovered from its WAL record by replaying its patch. Echo then checks the patch digest and the commit hash, which binds the receipt and its attribution section, so the recovered evidence is exactly - the evidence that was committed. -- Recovery does not re-evaluate decided Actions. Their read sets and proposals - are evidence about a past evaluation, not inputs to replay. + the evidence that was committed. Recovered state comes from the committed + patch, never from a re-derived proposal. +- As recovery already does for committed and footprint-conflicted Actions + (`trusted_runtime_host.rs`, ADR 0025), it re-derives each decided Action's + evaluation over the recovered parent state and compares the result with the + recorded receipt entry: the proposal posture and, where present, the + proposal identity, the read-set and write-set digests, and the derived patch + digest, in addition to the identities compared today. An obstructed Action's + pre-decision evidence is compared the same way. Any disagreement refuses the + history. +- The comparison never changes a decision. The recorded outcome, receipt, and + attribution stand as written; a re-derived proposal is checked against them + and then discarded. - An Action admitted under the scoped validity of [Action validity across ticks](../topics/GeneratedRules.md#action-validity-across-ticks) follows that decision: recovery recomputes its validation at the recorded position from retained history and refuses a history whose result differs. -- An undecided Action is re-evaluated as the same submission, and determinism - makes it reproduce the same proposal. +- An undecided Action stays the same pending submission and is evaluated when + the scheduler next selects it. ## Execution traces @@ -332,8 +393,9 @@ B.canonicalHead, B.atom}`. No co-scheduled Action reads or writes these. `UpsertNode H2`, `SetAttachment H2`, `UpsertEdge H2.root`, `UpsertEdge H2.anchor`, `UpsertEdge B.canonicalHead` (same edge id, new target), `SetAttachment B`. Eight operations. -9. **Decision and durability.** The receipt binds the proposal identity, the - read-set and write-set digests, the patch digest, and the consumed budget. +9. **Decision and durability.** The receipt binds the proposal posture + `Proposed` with the proposal identity, the write-set and patch digests, the + read-set digest, and the consumed budget. The decided Tick is written to the WAL, then published. 10. **Visibility.** Coordinate `t` shows `B → H1 → T1`. Coordinate `t+1` shows `B → H2 → T2`, with `H2 → I`. No coordinate shows `H2` without `T2`. `H1` @@ -341,24 +403,30 @@ B.canonicalHead, B.atom}`. No co-scheduled Action reads or writes these. whether their bytes are kept is the retention policy's decision. A crash before the WAL record loses the Tick entirely; the Action was never -decided, so on recovery it is the same pending submission and re-evaluates -against `c` to the same proposal. A crash -after it recovers Tick `t+1` from the WAL, with the same state root and -receipt. +decided, so after recovery it is the same pending submission, evaluated again +when the scheduler selects it. Over the unchanged basis `c` it reproduces the +same proposal. A crash after the WAL record recovers Tick `t+1` by replaying +its patch, re-derives the Action's evaluation over `c` to verify the recorded +proposal posture and digests, and yields the same state root and receipt. ### Obstructed: stale basis at evaluation -Another edit has already advanced `B` to head `H3`, so at `c` the -`canonicalHead` edge points to `H3`. +After coordinate `c`, another edit commits at Tick `u > t` and moves `B` to +head `H3`. Coordinate `c` still shows `H1`; the later coordinate `c'` (Tick +`u`) shows `B -canonicalHead-> H3`. A client that is still displaying `H1` +submits the same invocation, `basisHeadId H1`, and it is admitted at `c'`. -1. Steps 1–2 as above. -2. Evaluation reads the `B` atom and the `canonicalHead` edge, then the guard - `canonicalHead == H1` fails. -3. The outcome is the typed obstruction `BasisNotCanonical`. There is no - proposal and no patch. The receipt records the obstruction and the two-slot - read set, so the refusal is evidence of what the program saw. -4. The state root at `t+1` is unchanged for this Action. The client must issue - a new invocation against `H3`. +1. **Admission** at `c'`, as in the committed trace. +2. **Selection.** The scheduler selects the Action while constructing Tick + `u+1` and opens a recording view over `c'`. +3. **Evaluation** reads the `B` atom and the `canonicalHead` edge (to `H3`), + then the authored guard `canonicalHead == basisHeadId` fails. +4. The outcome is the typed obstruction `BasisNotCanonical` with proposal + posture `NoProposal`. There is no proposal identity and no patch. The + receipt records the obstruction and the two-slot read set, so the refusal + is evidence of what the program saw. +5. The state root at `u+1` is unchanged by this Action. The client must issue + a new invocation naming `H3`. ### Obstructed: conflict at composition @@ -384,33 +452,40 @@ propose `ReplaceEdge B -canonicalHead-> expected H1`. general-purpose virtual machine. - It does not decide the state-root definition, and it does not make admission cost independent of history size. +- It does not implement or imply Continuum transport, comparable-basis + transport, replica import, or distributed settlement. It defines generic + authored effects inside one Echo's existing admission, scheduling, WAL, and + recovery architecture. That Echo can commit an Edict-authored proposal says + nothing about whether a remote history can be imported; those are separate + contracts with their own reviews. ## Required tests Each behaviour below needs a test before implementation: -| Property | Test | -| ---------------------- | -------------------------------------------------------------------------------------------------------------------------- | -| Atomicity | A failure on the last effect of a multi-node proposal leaves the parent state root and patch unchanged. | -| Ensure identity | Identical existing node: no patch entry. Different existing node: `ContentIdentityConflict`. | -| Witnessed footprint | A program that reads a slot outside its law obstructs, even if its writes are in bounds. | -| Stale basis | `ReplaceEdge` with a moved `expectedTo` obstructs with no patch. | -| Budget | Exhausting steps, bytes, or the Tick operation cap obstructs with no proposal committed. | -| Determinism | Same package, invocation, and basis give byte-identical proposals and receipts. | -| Recovery | Crash before and after the decided-Tick WAL record; fresh-host recovery reproduces the same state root and receipts. | -| Composition | Two Actions with disjoint witnessed write sets on one basis commit in one Tick; overlapping ones obstruct the later one. | -| Shared structure | Two Actions that `Ensure` the same node already present in the basis both commit in one Tick. | -| Referential integrity | An edge to a node neither in the basis nor created by the proposal obstructs with `DanglingReference`. | -| Visibility | No published coordinate shows a retargeted head without every node and edge it reaches. | -| Witnessed reads | The receipt's read set equals the slots the view resolved, including for a program that reads and then obstructs. | -| Negative reads | An Action that observed a node, atom, or edge set absent obstructs when a co-scheduled Action creates it. | -| Edge-read basis | An Action that read edges commits only onto its exact evaluation basis. | -| Final outcomes | A rejected Action's outcome, receipt, and attribution are unchanged after a later submission of the same edit succeeds. | -| Duplicate resubmission | Resubmitting a decided Action's identical content returns `Duplicate` with the original outcome and commits no Tick. | -| Attribution | Every Action in a bridge Tick appears once in the attribution section, mapped to its own receipt entry. | -| Receipt format | Ticks written before the new entry format recover unchanged; new Ticks declare the new format. | -| Recovered evidence | A recovered Tick's proposal identity, read-set, write-set, and patch digests equal those committed, without re-evaluation. | -| Pending recovery | An Action lost with its Tick before the WAL record is recovered as the same submission, not a new one. | +| Property | Test | +| ---------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| Atomicity | A failure on the last effect of a multi-node proposal leaves the parent state root and patch unchanged. | +| Ensure identity | Identical existing node: no patch entry. Different existing node: `ContentIdentityConflict`. | +| Witnessed footprint | A program that reads a slot outside its law obstructs, even if its writes are in bounds. | +| Stale basis | `ReplaceEdge` with a moved `expectedTo` obstructs with no patch. | +| Budget | Exhausting steps or bytes obstructs with `NoProposal`; a proposal whose patch alone exceeds the Action-batch operation limit obstructs with no patch. | +| Determinism | Same package, invocation, and evaluation basis give byte-identical proposals, proposal identities, read-set and write-set digests, and derived patches, whether the Action is evaluated alone or beside other Actions. Receipts and composite patches are compared only for identical Tick contexts. | +| No-proposal evidence | An evaluation that obstructs before proposing records `NoProposal` with its read-set digest and no proposal, write-set, or patch digest. | +| Recovery | Crash before and after the decided-Tick WAL record; fresh-host recovery reproduces the same state root and receipts. | +| Composition | Two Actions with independent witnessed footprints (neither writes a slot the other reads or writes) commit in one Tick. A write/write overlap obstructs the later one, and so does a write/read overlap: A reads X and writes Y, B writes X and Z. | +| Shared structure | Two Actions that `Ensure` the same node already present in the basis both commit in one Tick. | +| Referential integrity | An edge to a node neither in the basis nor created by the proposal obstructs with `DanglingReference`; a pre-existing dangling edge elsewhere in the basis does not obstruct an unrelated proposal. | +| Visibility | No published coordinate shows a retargeted head without every node and edge it reaches. | +| Witnessed reads | The receipt's read set equals the slots the view resolved, including for a program that reads and then obstructs. | +| Negative reads | An Action that observed a node, atom, or edge set absent obstructs when a co-scheduled Action creates it. | +| Edge-read basis | An Action that read edges commits only onto its exact evaluation basis. | +| Final outcomes | A rejected Action's outcome, receipt, and attribution are unchanged after a later submission of the same edit succeeds. | +| Duplicate resubmission | Resubmitting a decided Action's identical content returns `Duplicate` with the original outcome and commits no Tick. | +| Attribution | Every Action in a bridge Tick appears once in the attribution section, mapped to its own receipt entry. | +| Receipt format | Ticks written before the new entry format recover unchanged; new Ticks declare the new format. | +| Recovered evidence | Recovery replays the committed patch, re-derives each decided Action's evaluation, and accepts the history only when the proposal posture, proposal identity, read-set, write-set, and patch digests equal those recorded; a tampered digest refuses the history, and no recorded outcome changes. | +| Pending recovery | An Action lost with its Tick before the WAL record is recovered as the same submission, not a new one. | ## Open questions @@ -418,5 +493,5 @@ Each behaviour below needs a test before implementation: read guided by a pure Edict selector is expressed and budgeted. - Whether the footprint law is per-invocation or per-package with parameters. - Which domain-separated hash Edict source can call to derive ids. -- Whether proposals larger than one Tick's operation cap should ever be - representable. This design says no. +- Whether proposals larger than the Action-batch operation limit should ever + be representable. This design says no. diff --git a/docs/topics/GeneratedRules.md b/docs/topics/GeneratedRules.md index 5b0b1973..918f2fab 100644 --- a/docs/topics/GeneratedRules.md +++ b/docs/topics/GeneratedRules.md @@ -321,13 +321,15 @@ can delete the legacy feature without also losing executable operations. #### Application operation semantics authored in Edict source -**Decision 2026-10-15 (proposed for review in -[flyingrobots/jedit#302](https://github.com/flyingrobots/jedit/pull/302); tracks -[flyingrobots/jedit#296](https://github.com/flyingrobots/jedit/issues/296)), -superseding in part [ADR 0023](../adr/0023-admitted-executable-operation-packages.md): -its "Jedit rope-law closure" section and its selection under "Alternatives for +**Decision 2026-10-15, accepted, effective when +[flyingrobots/echo#889](https://github.com/flyingrobots/echo/pull/889) merges to +`main`; until that merge it is a proposal and ADR 0023 governs unchanged. Tracks +[flyingrobots/jedit#296](https://github.com/flyingrobots/jedit/issues/296). +Supersedes in part [ADR 0023](../adr/0023-admitted-executable-operation-packages.md): +the declarative-resource requirements of its "Jedit rope-law closure" section, +enumerated in that section's banner, and its selection under "Alternatives for Expressing the Rope Law". Every other provision of ADR 0023 continues to -govern.** +govern, and its original reasoning is kept.** Jedit's persistent-rope `ReplaceRange` law is authored in Jedit-owned Edict source and compiled by Edict. It is not imported as a declarative executable @@ -367,8 +369,10 @@ Evidence for the change, which ADR 0023 did not have on 2026-07-18: Consequences: +- This decision accepts an architectural contract, not an implementation. No + evaluation-to-effect bridge exists until it is built and witnessed. - The generic evaluation-to-effect boundary must be designed and reviewed - before it is implemented; the proposal is + before it is implemented; the design is [Authored Operation Effects](../architecture/authored-operation-effects.md). Evaluation proposes effects; it never performs them. Echo witnesses the actual read and write dependencies, validates basis, From b1192d96d78f0cf461d014b0ff24a613745e342f Mon Sep 17 00:00:00 2001 From: James Ross Date: Thu, 15 Oct 2026 23:19:17 -0700 Subject: [PATCH 08/50] docs: mark remaining resource-route clauses and recover by outcome class Re-review found three gaps: - An oversized proposal obstructed by the Action-batch operation limit keeps posture Proposed with its digests; only evaluation-budget exhaustion is NoProposal. - Current recovery verifies by outcome class: BasisChanged obstructions are not re-evaluated, and a scheduler-assigned BudgetExceeded is verified through batch reconstruction. The bridge now adds its evidence to each class's existing check instead of claiming uniform re-evaluation. - ADR 0023's evidence-grade lawpack row, Jurisdiction, and Consequences still assumed the resource route; each is now marked superseded in part where it appears and listed in the rope-law closure banner. The 2026-07-18 plan points at the superseding decision. --- ...-admitted-executable-operation-packages.md | 24 +++- .../authored-operation-effects.md | 106 +++++++++++------- ...im-edict-echo-executable-warp-semantics.md | 5 + 3 files changed, 89 insertions(+), 46 deletions(-) diff --git a/docs/adr/0023-admitted-executable-operation-packages.md b/docs/adr/0023-admitted-executable-operation-packages.md index 087a8795..3260c118 100644 --- a/docs/adr/0023-admitted-executable-operation-packages.md +++ b/docs/adr/0023-admitted-executable-operation-packages.md @@ -308,8 +308,11 @@ admission posture without adding broad authorization policy to this decision. > **No longer required:** the declarative resource and lawpack named in the > first paragraph; the fifth required binding (the rope-law resource > coordinate and digest); the lawpack and `EchoOperationProgramV1` steps of the -> binding chain and the paragraph after it; and the paragraphs on what makes -> the resource acceptable and who owns it. +> binding chain and the paragraph after it; the paragraphs on what makes +> the resource acceptable and who owns it; and, outside this section, the +> lawpack-resource binding in the evidence-grade table, the rope-law resource +> and generic resource binding in Jurisdiction, and the executable-resource +> binding in Consequences, each marked where it appears. > > **Still governing:** the versioned canonical text-schema declaration and its > first four required bindings; the package's binding of that declaration's @@ -440,6 +443,12 @@ the following grades. | Repeated evaluation by the same Echo evaluator | Deterministic self-validation | The implementation is repeatable for the tested basis and inputs; it is not an independent implementation. | | Receipt/WAL round-trip and fresh-host reconstruction | Deterministic self-validation | Retained bytes reconstruct the same installed and execution identities without callbacks. | +> **Superseded in part,** effective when flyingrobots/echo#889 merges: the +> "lawpack resource" in the target-verifier row above. The row's check now +> covers the admitted package and Target IR to the program compiled from +> Jedit's Edict source, with the same evidence grade. See the +> [Jedit rope-law closure](#jedit-rope-law-closure) banner. + A separate verifier crate is not, by itself, independently implemented conformance evidence. This decision does not claim formal refinement, complete semantic equivalence, a clean-room Echo interpreter, or a proof that all @@ -453,6 +462,11 @@ by comparing two wrappers around the same algorithm. ## Jurisdiction +> **Superseded in part,** effective when flyingrobots/echo#889 merges: Jedit +> owns the `ReplaceRange` law as Edict source rather than a rope-law resource, +> and Edict's generic resource binding is no longer part of this operation's +> route. See the [Jedit rope-law closure](#jedit-rope-law-closure) banner. + - **Jedit** owns `ReplaceRange.edict`, the rope-law resource, fact schemas, codec/identity law, operation semantics, and the differential oracle corpus. - **Edict** owns source parsing, typing, canonical meaning, Core, generic @@ -500,7 +514,11 @@ by comparing two wrappers around the same algorithm. category rather than a callback-bearing provider revision. - Edict needs fixed-width and explicit-basis operation support plus exact executable-resource binding; this decision does not require a general - recursive language. + recursive language. (Superseded in part, effective when + flyingrobots/echo#889 merges: the rope law no longer needs + executable-resource binding; Edict gains language features only when the + authored source demonstrates the need. See the + [Jedit rope-law closure](#jedit-rope-law-closure) banner.) - The first program format preserves contained-execution obligations without claiming durable child lanes, child ticks, wormholes, or universal WARP wire status. diff --git a/docs/architecture/authored-operation-effects.md b/docs/architecture/authored-operation-effects.md index 5fe1f906..3aa2e4f2 100644 --- a/docs/architecture/authored-operation-effects.md +++ b/docs/architecture/authored-operation-effects.md @@ -79,12 +79,24 @@ Echo learns no application vocabulary. A proposal is nodes, edges, and atoms. accepted (`crates/warp-core/src/echo_operation.rs`). These are Action-batch composition limits, not Tick limits: `WarpTickPatchV1::new` accepts an operation vector of any length. -- Recovery re-derives each committed and footprint-conflicted Action's - preparation from the recovered parent state and refuses history whose - package, invocation, basis, private-evaluation, patch, result, footprint, or - preparation identity differs from the recorded outcome; obstructed Actions - are re-evaluated and their pre-decision evidence compared - (`crates/warp-core/src/trusted_runtime_host.rs`, ADR 0025). +- Recovery verifies each decided Action by outcome class + (`crates/warp-core/src/trusted_runtime_host.rs`, ADR 0025). First, for + every class, it recomputes the Action's basis posture against the scheduler + Tick's parent and refuses history where the recorded posture disagrees. + Then: + - **Committed and `RejectedFootprintConflict`:** it reconstructs the + preparation from the recovered parent state and refuses history whose + package, installed-operation, invocation, basis, private-evaluation, + patch, result, footprint, or preparation identity differs from the + recorded evidence. + - **Obstructed `BasisChanged`:** it verifies the invocation-admission + evidence and the recomputed basis posture. It does not re-evaluate the + Action. + - **Other obstructions:** it re-evaluates the Action and requires the same + pre-decision evidence, except that a scheduler-assigned `BudgetExceeded` + (an Action-batch limit, decided after a successful evaluation) is + accepted as a reconstructed preparation and verified when recovery + reconstructs the whole Action batch's composition. - The state root is a full walk of the reachable graph on every commit. See the open state-root decision in [Sliced Readings and Copy-on-Write Strands](sliced-readings-and-copy-on-write-strands.md#open-decisions-that-bound-these-laws). @@ -241,12 +253,15 @@ coordinate after shows the new head with everything it reaches. Action is never re-stamped as if it had been evaluated against a later basis. - **Budget.** Evaluation is metered as `edict_pure` already meters it. Each effect adds a fixed write cost. Exhausting any evaluation limit obstructs - with `BudgetExceeded`, no proposal, and no patch. A proposal whose derived - patch alone exceeds the Action-batch operation limit - (`ACTION_BATCH_OPERATION_LIMIT_V1`) obstructs the same way rather than - splitting across Ticks. A proposal that fits alone but not beside the - Actions already accepted into the batch is handled by the existing batch - rule, which obstructs that candidate in this batch. + with `BudgetExceeded`, posture `NoProposal`, and no patch. A proposal whose + derived patch alone exceeds the Action-batch operation limit + (`ACTION_BATCH_OPERATION_LIMIT_V1`) also obstructs with `BudgetExceeded` and + no committed patch, rather than splitting across Ticks, but its posture is + `Proposed`: the proposal exists, so its identity, write-set digest, and + derived patch digest are recorded, not discarded. A proposal that fits alone + but not beside the Actions already accepted into the batch is handled by the + existing batch rule, which obstructs that candidate in this batch, likewise + with posture `Proposed`. - **Retries.** A decided outcome is final. An Action is identified by its submission (see Identity and existing contracts); once it is decided (committed, obstructed, or `RejectedFootprintConflict`), its outcome, @@ -327,14 +342,19 @@ new evidence. Verification is not re-admission. receipt and its attribution section, so the recovered evidence is exactly the evidence that was committed. Recovered state comes from the committed patch, never from a re-derived proposal. -- As recovery already does for committed and footprint-conflicted Actions - (`trusted_runtime_host.rs`, ADR 0025), it re-derives each decided Action's - evaluation over the recovered parent state and compares the result with the - recorded receipt entry: the proposal posture and, where present, the - proposal identity, the read-set and write-set digests, and the derived patch - digest, in addition to the identities compared today. An obstructed Action's - pre-decision evidence is compared the same way. Any disagreement refuses the - history. +- Recovery keeps the per-class verification described in + [Current implementation](#current-implementation) and adds the new evidence + to whatever each class already compares: + - where recovery reconstructs a preparation (committed and + `RejectedFootprintConflict`), or accepts one for a scheduler-assigned + `BudgetExceeded` and verifies it through the batch composition, it also + compares posture `Proposed`, the proposal identity, the read-set and + write-set digests, and the derived patch digest; + - where it re-evaluates an obstruction, it also compares the posture and + the read-set digest, and for `Proposed` the proposal digests; + - where it does not re-evaluate (`BasisChanged`), it verifies only the + evidence that class verifies today, and adds no re-evaluation. + Any disagreement refuses the history. - The comparison never changes a decision. The recorded outcome, receipt, and attribution stand as written; a re-derived proposal is checked against them and then discarded. @@ -463,29 +483,29 @@ propose `ReplaceEdge B -canonicalHead-> expected H1`. Each behaviour below needs a test before implementation: -| Property | Test | -| ---------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| Atomicity | A failure on the last effect of a multi-node proposal leaves the parent state root and patch unchanged. | -| Ensure identity | Identical existing node: no patch entry. Different existing node: `ContentIdentityConflict`. | -| Witnessed footprint | A program that reads a slot outside its law obstructs, even if its writes are in bounds. | -| Stale basis | `ReplaceEdge` with a moved `expectedTo` obstructs with no patch. | -| Budget | Exhausting steps or bytes obstructs with `NoProposal`; a proposal whose patch alone exceeds the Action-batch operation limit obstructs with no patch. | -| Determinism | Same package, invocation, and evaluation basis give byte-identical proposals, proposal identities, read-set and write-set digests, and derived patches, whether the Action is evaluated alone or beside other Actions. Receipts and composite patches are compared only for identical Tick contexts. | -| No-proposal evidence | An evaluation that obstructs before proposing records `NoProposal` with its read-set digest and no proposal, write-set, or patch digest. | -| Recovery | Crash before and after the decided-Tick WAL record; fresh-host recovery reproduces the same state root and receipts. | -| Composition | Two Actions with independent witnessed footprints (neither writes a slot the other reads or writes) commit in one Tick. A write/write overlap obstructs the later one, and so does a write/read overlap: A reads X and writes Y, B writes X and Z. | -| Shared structure | Two Actions that `Ensure` the same node already present in the basis both commit in one Tick. | -| Referential integrity | An edge to a node neither in the basis nor created by the proposal obstructs with `DanglingReference`; a pre-existing dangling edge elsewhere in the basis does not obstruct an unrelated proposal. | -| Visibility | No published coordinate shows a retargeted head without every node and edge it reaches. | -| Witnessed reads | The receipt's read set equals the slots the view resolved, including for a program that reads and then obstructs. | -| Negative reads | An Action that observed a node, atom, or edge set absent obstructs when a co-scheduled Action creates it. | -| Edge-read basis | An Action that read edges commits only onto its exact evaluation basis. | -| Final outcomes | A rejected Action's outcome, receipt, and attribution are unchanged after a later submission of the same edit succeeds. | -| Duplicate resubmission | Resubmitting a decided Action's identical content returns `Duplicate` with the original outcome and commits no Tick. | -| Attribution | Every Action in a bridge Tick appears once in the attribution section, mapped to its own receipt entry. | -| Receipt format | Ticks written before the new entry format recover unchanged; new Ticks declare the new format. | -| Recovered evidence | Recovery replays the committed patch, re-derives each decided Action's evaluation, and accepts the history only when the proposal posture, proposal identity, read-set, write-set, and patch digests equal those recorded; a tampered digest refuses the history, and no recorded outcome changes. | -| Pending recovery | An Action lost with its Tick before the WAL record is recovered as the same submission, not a new one. | +| Property | Test | +| ---------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| Atomicity | A failure on the last effect of a multi-node proposal leaves the parent state root and patch unchanged. | +| Ensure identity | Identical existing node: no patch entry. Different existing node: `ContentIdentityConflict`. | +| Witnessed footprint | A program that reads a slot outside its law obstructs, even if its writes are in bounds. | +| Stale basis | `ReplaceEdge` with a moved `expectedTo` obstructs with no patch. | +| Budget | Exhausting steps or bytes obstructs with `BudgetExceeded` and `NoProposal`; a proposal whose patch alone exceeds the Action-batch operation limit obstructs with `BudgetExceeded`, posture `Proposed` with its digests, and no committed patch. | +| Determinism | Same package, invocation, and evaluation basis give byte-identical proposals, proposal identities, read-set and write-set digests, and derived patches, whether the Action is evaluated alone or beside other Actions. Receipts and composite patches are compared only for identical Tick contexts. | +| No-proposal evidence | An evaluation that obstructs before proposing records `NoProposal` with its read-set digest and no proposal, write-set, or patch digest. | +| Recovery | Crash before and after the decided-Tick WAL record; fresh-host recovery reproduces the same state root and receipts. | +| Composition | Two Actions with independent witnessed footprints (neither writes a slot the other reads or writes) commit in one Tick. A write/write overlap obstructs the later one, and so does a write/read overlap: A reads X and writes Y, B writes X and Z. | +| Shared structure | Two Actions that `Ensure` the same node already present in the basis both commit in one Tick. | +| Referential integrity | An edge to a node neither in the basis nor created by the proposal obstructs with `DanglingReference`; a pre-existing dangling edge elsewhere in the basis does not obstruct an unrelated proposal. | +| Visibility | No published coordinate shows a retargeted head without every node and edge it reaches. | +| Witnessed reads | The receipt's read set equals the slots the view resolved, including for a program that reads and then obstructs. | +| Negative reads | An Action that observed a node, atom, or edge set absent obstructs when a co-scheduled Action creates it. | +| Edge-read basis | An Action that read edges commits only onto its exact evaluation basis. | +| Final outcomes | A rejected Action's outcome, receipt, and attribution are unchanged after a later submission of the same edit succeeds. | +| Duplicate resubmission | Resubmitting a decided Action's identical content returns `Duplicate` with the original outcome and commits no Tick. | +| Attribution | Every Action in a bridge Tick appears once in the attribution section, mapped to its own receipt entry. | +| Receipt format | Ticks written before the new entry format recover unchanged; new Ticks declare the new format. | +| Recovered evidence | For each outcome class, recovery performs that class's existing verification plus the new comparisons (posture, proposal identity, read-set, write-set, and patch digests where the class reconstructs or re-evaluates); a tampered digest refuses the history, a `BasisChanged` obstruction is not re-evaluated, and no recorded outcome changes. | +| Pending recovery | An Action lost with its Tick before the WAL record is recovered as the same submission, not a new one. | ## Open questions diff --git a/docs/plans/2026-07-18-jim-edict-echo-executable-warp-semantics.md b/docs/plans/2026-07-18-jim-edict-echo-executable-warp-semantics.md index 25fe4a1c..df3891bf 100644 --- a/docs/plans/2026-07-18-jim-edict-echo-executable-warp-semantics.md +++ b/docs/plans/2026-07-18-jim-edict-echo-executable-warp-semantics.md @@ -13,6 +13,11 @@ differ from the accepted executable-operation boundary. The accepted campaign is recorded in section 13; the inventory of broader Jim and Echo capabilities does not place those capabilities in scope for the first vertical. +ADR 0023's Jedit lawpack-resource route is itself superseded in part, +effective when flyingrobots/echo#889 merges, by +[Application operation semantics authored in Edict source](../topics/GeneratedRules.md#application-operation-semantics-authored-in-edict-source). +This inventory's lawpack-resource steps are kept as historical evidence. + ## Source basis This report is based on source code, not repository documentation, at these From e8ae67df38b89099b7534da46416af381c8cf31e Mon Sep 17 00:00:00 2001 From: James Ross Date: Fri, 16 Oct 2026 01:32:41 -0700 Subject: [PATCH 09/50] docs: bind every proposal identity to its exact effects Add invariant 5: an outcome that carries a proposal identity binds it to the exact evaluated effects, committed or rejected, so a rejected proposal is never ambiguous on replay or audit. Add a recovery matrix naming, per outcome class, the posture, the existing check, and what the bridge adds, and record that BasisChanged is decided before evaluation (NoProposal, empty read set). Correct the invariant count. --- .../authored-operation-effects.md | 47 ++++++++++++++----- 1 file changed, 35 insertions(+), 12 deletions(-) diff --git a/docs/architecture/authored-operation-effects.md b/docs/architecture/authored-operation-effects.md index 3aa2e4f2..d485385f 100644 --- a/docs/architecture/authored-operation-effects.md +++ b/docs/architecture/authored-operation-effects.md @@ -31,7 +31,7 @@ what the evaluation actually read and what it proposes to write, validates both against the admitted contract and the basis, and commits all of the effects in one Tick or none of them. -Three invariants govern the design: +Five invariants govern the design: 1. **Evaluation is not commitment.** A successful evaluation yields a proposal. Nothing it computes is authoritative until the scheduler commits @@ -52,6 +52,12 @@ Three invariants govern the design: is free of dangling edges: other writers do not check endpoints today (see [Current implementation](#current-implementation)), and this design does not validate the rest of the graph. +5. **A proposal identity names exactly one evaluated proposal.** Every outcome + that carries a proposal identity binds it to the exact effects the + evaluation produced, whether those effects committed or were rejected. A + rejected proposal is never ambiguous on replay or audit: re-deriving it over + the recorded basis must reproduce effects that hash to the recorded + identity and derive the recorded patch digest. Echo learns no application vocabulary. A proposal is nodes, edges, and atoms. @@ -326,6 +332,17 @@ The submission identity is present in both variants. An identified submission whose evaluation produced no proposal is a different evidence state from an evaluated proposal, and the receipt keeps them distinct. +The proposal identity is `H(Action, evaluation basis, canonical +EffectProposal)`, and the canonical `EffectProposal` contains every effect in +canonical order, so the identity binds the exact evaluated effects +(invariant 5). The write-set and derived patch digests are derived from those +same effects. Proposals are not retained apart from their Tick; the binding is +checked by re-deriving the proposal, as the recovery matrix below requires for +every class that carries one. + +An Action obstructed with `BasisChanged` is decided before evaluation runs +(`echo_operation.rs`), so it has posture `NoProposal` and an empty read set. + These fields change the receipt entry's encoding, which commit hash v3 binds. They therefore need a new receipt entry format version, introduced under the same rules as Submission Attribution's format table: existing formats and @@ -344,17 +361,8 @@ new evidence. Verification is not re-admission. patch, never from a re-derived proposal. - Recovery keeps the per-class verification described in [Current implementation](#current-implementation) and adds the new evidence - to whatever each class already compares: - - where recovery reconstructs a preparation (committed and - `RejectedFootprintConflict`), or accepts one for a scheduler-assigned - `BudgetExceeded` and verifies it through the batch composition, it also - compares posture `Proposed`, the proposal identity, the read-set and - write-set digests, and the derived patch digest; - - where it re-evaluates an obstruction, it also compares the posture and - the read-set digest, and for `Proposed` the proposal digests; - - where it does not re-evaluate (`BasisChanged`), it verifies only the - evidence that class verifies today, and adds no re-evaluation. - Any disagreement refuses the history. + to whatever each class already compares, as the matrix below states. Any + disagreement refuses the history. - The comparison never changes a decision. The recorded outcome, receipt, and attribution stand as written; a re-derived proposal is checked against them and then discarded. @@ -365,6 +373,20 @@ new evidence. Verification is not re-admission. - An undecided Action stays the same pending submission and is evaluated when the scheduler next selects it. +Recovery matrix. "Existing check" is what recovery verifies today; "Added" is +what the bridge adds. "Effects bound" means the re-derived effects must hash +to the recorded proposal identity and derive the recorded write-set and patch +digests (invariant 5). + +| Outcome | Posture | Existing check | Added | +| ------------------------------------------------------------------------------------------------------ | ------------ | --------------------------------------------------------------------- | ---------------------------------------------------------- | +| Committed | `Proposed` | replay patch; reconstruct preparation; compare identities | effects bound; read-set digest; patch digest equals replay | +| `RejectedFootprintConflict` | `Proposed` | reconstruct preparation; compare identities | effects bound; read-set digest | +| Obstructed at validation (`DanglingReference`, `ContentIdentityConflict`, footprint law, stale expect) | `Proposed` | re-evaluate; compare pre-decision evidence | effects bound; read-set digest; obstruction kind | +| `BudgetExceeded`, scheduler-assigned (Action-batch limit) | `Proposed` | accept reconstructed preparation; verify through batch reconstruction | effects bound; read-set digest | +| `BudgetExceeded` during evaluation, or an authored guard | `NoProposal` | re-evaluate; compare pre-decision evidence | re-evaluation again yields no proposal; read-set digest | +| `BasisChanged` | `NoProposal` | admission evidence; recomputed basis posture; no re-evaluation | none; posture must be `NoProposal` with an empty read set | + ## Execution traces These traces follow Jim's flat-text reference `ReplaceRange` (the first @@ -491,6 +513,7 @@ Each behaviour below needs a test before implementation: | Stale basis | `ReplaceEdge` with a moved `expectedTo` obstructs with no patch. | | Budget | Exhausting steps or bytes obstructs with `BudgetExceeded` and `NoProposal`; a proposal whose patch alone exceeds the Action-batch operation limit obstructs with `BudgetExceeded`, posture `Proposed` with its digests, and no committed patch. | | Determinism | Same package, invocation, and evaluation basis give byte-identical proposals, proposal identities, read-set and write-set digests, and derived patches, whether the Action is evaluated alone or beside other Actions. Receipts and composite patches are compared only for identical Tick contexts. | +| Proposal binding | For every `Proposed` outcome class in the recovery matrix, including rejected ones, altering one recorded effect-derived digest or the proposal identity refuses the history. | | No-proposal evidence | An evaluation that obstructs before proposing records `NoProposal` with its read-set digest and no proposal, write-set, or patch digest. | | Recovery | Crash before and after the decided-Tick WAL record; fresh-host recovery reproduces the same state root and receipts. | | Composition | Two Actions with independent witnessed footprints (neither writes a slot the other reads or writes) commit in one Tick. A write/write overlap obstructs the later one, and so does a write/read overlap: A reads X and writes Y, B writes X and Z. | From bf5f2d6626e9d1c3a37f1bf5722188729a97f1d5 Mon Sep 17 00:00:00 2001 From: James Ross Date: Fri, 16 Oct 2026 01:53:16 -0700 Subject: [PATCH 10/50] docs: close the third review round on the authored-effects bridge - One effect per slot: two effects on one slot obstruct with DuplicateEffectTarget, since WarpTickPatchV1::new keeps the last operation per key and would drop effects the identity names. - Outgoing-edge range keys (source, edge type): a range read and any edge write with that source and type conflict, closing a same-Tick phantom that disjoint edge-id sets cannot see. - Proposed carries a patch tag: Derived with its digest, or NotDerived when validation refused the proposal before any patch existed. - Basis change splits into whole-frontier (before evaluation, NoProposal, empty read set) and dependency-scoped (after evaluation at the origin, keeping the proposal and read set). - New evidence is retained only as EPRV0004; the EPRV0003 decoder and legacy Ticks stay unchanged. - Recovery's batch reconstruction, outcome equality, and blocker recomputation are stated as common checks; the lifecycle summary and visibility test are scoped correctly. - ADR 0023's package binding direction and obligations bind a lawpack only for programs imported from one. --- ...-admitted-executable-operation-packages.md | 25 +- .../authored-operation-effects.md | 223 ++++++++++++------ docs/topics/GeneratedRules.md | 9 +- 3 files changed, 180 insertions(+), 77 deletions(-) diff --git a/docs/adr/0023-admitted-executable-operation-packages.md b/docs/adr/0023-admitted-executable-operation-packages.md index 3260c118..0279f133 100644 --- a/docs/adr/0023-admitted-executable-operation-packages.md +++ b/docs/adr/0023-admitted-executable-operation-packages.md @@ -14,9 +14,11 @@ [Application operation semantics authored in Edict source](../topics/GeneratedRules.md#application-operation-semantics-authored-in-edict-source) decision, accepted 2026-10-15 and effective when [flyingrobots/echo#889](https://github.com/flyingrobots/echo/pull/889) merges - to `main`, for the declarative-resource requirements of the "Jedit rope-law - closure" section (enumerated in its banner) and the selection under - "Alternatives for Expressing the Rope Law" + to `main`, for the declarative-resource and lawpack requirements that + assume the rope law is an imported resource, in this ADR's rope-law + closure, package binding, package obligations, evidence grades, + Jurisdiction, and Consequences (enumerated in the "Jedit rope-law closure" + banner), and the selection under "Alternatives for Expressing the Rope Law" > **Scheduler lifecycle amendment:** This ADR established exact package, > invocation, private-evaluation, and recovery semantics. ADR 0025 supersedes @@ -108,6 +110,13 @@ Edict intent / admitted executable-operation package → binds the exact EchoOperationProgramV1 bytes and digest ``` +> **Superseded in part,** effective when flyingrobots/echo#889 merges: the +> lawpack step applies only to a program whose executable meaning is imported +> from a lawpack resource. A program compiled from Edict source, such as +> Jedit's `ReplaceRange`, goes from the canonical semantic closure directly to +> the exact program bytes and digest, and binds no lawpack resource. See the +> [Jedit rope-law closure](#jedit-rope-law-closure) banner. + The admitted Edict operation supplies the public operation coordinate, invocation and result schemas, basis contract, budget and footprint contract, authority requirements, result and obstruction interpretation, and the @@ -143,6 +152,13 @@ Every `ExecutableOperationPackageV1` must canonically bind: - result and obstruction interpretation; - one evaluator ABI and intrinsic-profile identity. +> **Superseded in part,** effective when flyingrobots/echo#889 merges: the +> lawpack resource coordinate and digest are bound only by a package whose +> program is imported from a lawpack resource. A package whose program is +> compiled from Edict source binds no lawpack resource; every other +> obligation above still applies to it. See the +> [Jedit rope-law closure](#jedit-rope-law-closure) banner. + Every bound `EchoOperationProgramV1` must canonically bind its executable meaning: @@ -310,6 +326,9 @@ admission posture without adding broad authorization policy to this decision. > coordinate and digest); the lawpack and `EchoOperationProgramV1` steps of the > binding chain and the paragraph after it; the paragraphs on what makes > the resource acceptable and who owns it; and, outside this section, the +> lawpack step of the package binding direction and the lawpack obligation in +> "First-version execution obligations" (both now apply only to a program +> imported from a lawpack resource), the > lawpack-resource binding in the evidence-grade table, the rope-law resource > and generic resource binding in Jurisdiction, and the executable-resource > binding in Consequences, each marked where it appears. diff --git a/docs/architecture/authored-operation-effects.md b/docs/architecture/authored-operation-effects.md index d485385f..1ce7dca0 100644 --- a/docs/architecture/authored-operation-effects.md +++ b/docs/architecture/authored-operation-effects.md @@ -57,7 +57,9 @@ Five invariants govern the design: evaluation produced, whether those effects committed or were rejected. A rejected proposal is never ambiguous on replay or audit: re-deriving it over the recorded basis must reproduce effects that hash to the recorded - identity and derive the recorded patch digest. + identity, the recorded write-set digest, and, where a patch was derived, + the recorded patch digest. Each slot is the target of at most one effect, + so the derived patch applies every effect the identity names. Echo learns no application vocabulary. A proposal is nodes, edges, and atoms. @@ -77,7 +79,15 @@ Echo learns no application vocabulary. A proposal is nodes, edges, and atoms. exist (`crates/warp-core/src/graph.rs`). - Footprint independence is a set test: any write overlapping another Action's read or write conflicts, including two identical writes - (`crates/warp-core/src/footprint.rs`, `Footprint::independent`). + (`crates/warp-core/src/footprint.rs`, `Footprint::independent`). Its key + sets are nodes, edges, attachments, and ports; it has no key for a range + such as "all outgoing edges of one type from a node". +- `WarpTickPatchV1::new` keeps one operation per canonical sort key, the last + one supplied, and silently drops earlier operations with the same key + (`crates/warp-core/src/tick_patch.rs`). +- The retained provenance record `EPRV0003` writes each receipt entry as rule, + scope hash, scope, disposition, and blockers, with no per-entry length or + version (`crates/warp-core/src/provenance_codec.rs`). - An executable-operation Action batch selects at most 64 candidates (`ACTION_BATCH_CANDIDATE_LIMIT_V1`) and accepts at most 128 patch operations summed over its accepted Actions (`ACTION_BATCH_OPERATION_LIMIT_V1`); a @@ -101,8 +111,13 @@ Echo learns no application vocabulary. A proposal is nodes, edges, and atoms. - **Other obstructions:** it re-evaluates the Action and requires the same pre-decision evidence, except that a scheduler-assigned `BudgetExceeded` (an Action-batch limit, decided after a successful evaluation) is - accepted as a reconstructed preparation and verified when recovery - reconstructs the whole Action batch's composition. + accepted as a reconstructed preparation and verified through the batch + reconstruction below. + Finally, for every Action Tick and every outcome class, it reconstructs the + whole Action batch's composition over the recovered parent state and refuses + history where any reconstructed outcome differs from the recorded one; for + each `RejectedFootprintConflict` it also recomputes the blocking earlier + Actions and requires them to equal the recorded blockers. - The state root is a full walk of the reachable graph on every commit. See the open state-root decision in [Sliced Readings and Copy-on-Write Strands](sliced-readings-and-copy-on-write-strands.md#open-decisions-that-bound-these-laws). @@ -123,10 +138,12 @@ admitted package + invocation at an exact basis Evaluation runs inside Tick construction, as ADR 0025 already requires for Actions. Proposals are not persisted on their own. Recovery replays decided -Ticks and re-derives each decided Action's evaluation to verify the recorded -evidence (see [Recovery](#recovery)). A pending Action is re-evaluated after -recovery when the scheduler selects it. Both are sound because evaluation is -deterministic over a fixed basis. +Ticks and verifies each decided Action by its outcome class: it re-derives the +evaluation for the classes whose check does so, and verifies a `BasisChanged` +decided before evaluation without re-evaluating it (see [Recovery](#recovery)). +A pending Action is evaluated after recovery when the scheduler selects it. +Re-derivation is sound because evaluation is deterministic over a fixed +basis. ## EffectProposal @@ -152,6 +169,13 @@ Rules: - **Effects are the only writes.** The proposal contains no prepared patch. Echo derives the patch from the effects. +- **One effect per slot.** No two effects in one proposal may target the same + slot: one node record, one node's atom, or one edge. `EnsureNode` and + `CreateNode` target the node record and its atom, so an `EnsureNode` plus a + `ReplaceAtom` of the same node collide. A collision is a typed + `DuplicateEffectTarget` obstruction before any patch is derived. Without + this rule `WarpTickPatchV1::new` would keep only the last operation per key + and silently drop effects the proposal identity names. - **Ensure is idempotent only on identity.** An `EnsureNode` whose node exists with identical type and atom adds nothing to the patch and counts as a read. An existing node with different content is a typed @@ -187,6 +211,17 @@ An `Ensure` that finds no existing node depends on that absence, so a co-scheduled Action that creates the slot invalidates it like any other changed read. +A read of the outgoing edges of one type from a node is a range, not a set of +known edge ids, so it needs its own footprint key. Today's `Footprint` has +none: if Action A observes no `canonicalHead` edge from `B` and co-scheduled +Action B creates a fresh one, their edge-id sets are disjoint and the existing +test calls them independent, although A's observed absence no longer holds. +The bridge therefore adds an outgoing-edge range key `(source node, edge +type)`. A range read claims it as a read. Every effect that creates or +retargets an edge with that source and type claims it as a write, in addition +to the edge id. Independence treats range keys like every other key: a write +against a read or write conflicts. + These dependency kinds follow the [Action validity across ticks](../topics/GeneratedRules.md#action-validity-across-ticks) decision (#837). Value and single-key absence dependencies are the kinds that @@ -214,8 +249,9 @@ Echo checks: - the derived patch's actual footprint equals the proposal's write set. Any violation is a typed obstruction with no patch. Independence between -Actions in one Tick uses the witnessed footprints, under the existing rule: -write against read or write conflicts, read against read does not. +Actions in one Tick uses the witnessed footprints, including outgoing-edge +range keys, under the existing rule: write against read or write conflicts, +read against read does not. Reading a large shared subtree costs footprint entries but never conflicts. @@ -248,7 +284,9 @@ Action's effects belong to one Tick. Observers, readings, and later Actions bind to committed Tick coordinates, and a Tick is published only after its decided WAL record. The order of operations inside the Tick's patch is therefore never observable: the coordinate before the Tick shows the old head, and the -coordinate after shows the new head with everything it reaches. +coordinate after shows the new head with every node and edge the proposal +introduced. Pre-existing structure the head now reaches is whatever the basis +held; this design validates only what the proposal adds or retargets. ## Basis, budget, retries @@ -257,14 +295,30 @@ coordinate after shows the new head with everything it reaches. a co-scheduled Action wrote must still hold; otherwise the Action obstructs as stale. Action validity across ticks keeps its existing rules, and an Action is never re-stamped as if it had been evaluated against a later basis. +- **Two kinds of basis change.** Today's executable programs have fixed, + statically known reads, so Echo decides dependency-scoped validity before + evaluation. A bridge Action's reads are known only by evaluating it, so the + bridge splits basis change in two: + - **Whole-frontier `BasisChanged`,** decided before evaluation: the Action + is not eligible for dependency-scoped validity (for example because its + program can read edges) and its evaluation basis is no longer the commit + parent. Posture `NoProposal`, empty read set, no evaluation. + - **Dependency-scoped,** decided after evaluation: the Action is eligible, + so Echo evaluates it at its recorded origin basis, then checks each + witnessed read, presence and absence, against the history between that + origin and the commit parent. If one changed, the Action obstructs with + `BasisChanged` but keeps the evidence it has: posture `Proposed` when + evaluation produced a proposal, otherwise `NoProposal`, and in both + cases its witnessed read set. Echo never validates a delayed Action + without its actual dependencies. - **Budget.** Evaluation is metered as `edict_pure` already meters it. Each effect adds a fixed write cost. Exhausting any evaluation limit obstructs with `BudgetExceeded`, posture `NoProposal`, and no patch. A proposal whose derived patch alone exceeds the Action-batch operation limit (`ACTION_BATCH_OPERATION_LIMIT_V1`) also obstructs with `BudgetExceeded` and no committed patch, rather than splitting across Ticks, but its posture is - `Proposed`: the proposal exists, so its identity, write-set digest, and - derived patch digest are recorded, not discarded. A proposal that fits alone + `Proposed` with a derived patch: the proposal exists, so its identity, + write-set digest, and derived patch digest are recorded, not discarded. A proposal that fits alone but not beside the Actions already accepted into the batch is handled by the existing batch rule, which obstructs that candidate in this batch, likewise with posture `Proposed`. @@ -309,23 +363,33 @@ Consequences: - **Attribution** is unchanged: an Action batch already attributes each candidate to its own receipt entry. The proposal identity and digests below ride in that entry; the attribution section still names only submissions. -- **Durability** is unchanged: the decided-Tick WAL record (`EPRV0003`) is the - only persistence. Proposals are never retained apart from their Tick. +- **Durability** is unchanged in kind: the decided-Tick WAL record is the only + persistence. Proposals are never retained apart from their Tick. The record + gains a new layout version for the new evidence (see Evidence). ## Evidence Each Action's receipt entry binds, in addition to its current fields: - the proposal posture, a tagged value with exactly two variants: - - `NoProposal`: evaluation obstructed before it produced a proposal, for - example `BudgetExceeded` or an authored guard such as - `BasisNotCanonical`. No proposal identity, write-set digest, or patch - digest exists, and none is manufactured; - - `Proposed { proposal identity, write-set digest, derived patch digest }`: - evaluation produced a proposal, whether it then committed, obstructed at - validation, or was rejected at composition; -- the witnessed read-set digest, including observed absences, in both - variants, because a refusal is still evidence of what the program saw; + - `NoProposal`: no proposal exists, because evaluation obstructed before + producing one (for example `BudgetExceeded` or an authored guard such + as `BasisNotCanonical`) or never ran (whole-frontier `BasisChanged`). + No proposal identity, write-set digest, or patch digest exists, and + none is manufactured; + - `Proposed { proposal identity, write-set digest, patch }`: evaluation + produced a proposal. `patch` is itself tagged: + - `Derived { patch digest }` when Echo derived a patch: the proposal + committed, was rejected at composition, or was obstructed by an + Action-batch limit or a dependency-scoped `BasisChanged`; + - `NotDerived` when validation refused the proposal before any patch + existed (`DanglingReference`, `ContentIdentityConflict`, + `DuplicateEffectTarget`, a footprint-law violation, or a stale + expected value). No patch digest is manufactured; an overwrite + patch that `Ensure` forbids is never hashed; +- the witnessed read-set digest, including observed absences, for every + outcome that evaluated, because a refusal is still evidence of what the + program saw; it is the empty set's digest when evaluation never ran; - consumed budget. The submission identity is present in both variants. An identified submission @@ -335,19 +399,25 @@ evaluated proposal, and the receipt keeps them distinct. The proposal identity is `H(Action, evaluation basis, canonical EffectProposal)`, and the canonical `EffectProposal` contains every effect in canonical order, so the identity binds the exact evaluated effects -(invariant 5). The write-set and derived patch digests are derived from those -same effects. Proposals are not retained apart from their Tick; the binding is +(invariant 5). The write-set digest and, when one was derived, the patch +digest are derived from those same effects. Proposals are not retained apart from their Tick; the binding is checked by re-deriving the proposal, as the recovery matrix below requires for every class that carries one. -An Action obstructed with `BasisChanged` is decided before evaluation runs -(`echo_operation.rs`), so it has posture `NoProposal` and an empty read set. - -These fields change the receipt entry's encoding, which commit hash v3 binds. -They therefore need a new receipt entry format version, introduced under the -same rules as Submission Attribution's format table: existing formats and -retained legacy ticks stay readable and unchanged, and a Tick written with the -new format says so. +Today every `BasisChanged` is decided before evaluation runs +(`echo_operation.rs`). For bridge Actions that remains true only of the +whole-frontier case; a dependency-scoped `BasisChanged` is decided after +evaluation and keeps its evidence (see Basis, budget, retries). + +These fields change the receipt entry's encoding, which commit hash v3 binds, +and its retained layout. `EPRV0003` has no per-entry length or version, so a +legacy decoder would read the new fields as blocker data. The new evidence is +therefore written only under a new retained provenance record version +(`EPRV0004`) with its own receipt-entry layout and receipt format number, +introduced under the same rules as Submission Attribution's format table. The +`EPRV0003` decoder stays unchanged, retained legacy Ticks recover through it +byte for byte, a mixed history decodes each record by its own magic, and an +`EPRV0003` record never carries the new fields. ### Recovery @@ -373,19 +443,28 @@ new evidence. Verification is not re-admission. - An undecided Action stays the same pending submission and is evaluated when the scheduler next selects it. -Recovery matrix. "Existing check" is what recovery verifies today; "Added" is -what the bridge adds. "Effects bound" means the re-derived effects must hash -to the recorded proposal identity and derive the recorded write-set and patch -digests (invariant 5). - -| Outcome | Posture | Existing check | Added | -| ------------------------------------------------------------------------------------------------------ | ------------ | --------------------------------------------------------------------- | ---------------------------------------------------------- | -| Committed | `Proposed` | replay patch; reconstruct preparation; compare identities | effects bound; read-set digest; patch digest equals replay | -| `RejectedFootprintConflict` | `Proposed` | reconstruct preparation; compare identities | effects bound; read-set digest | -| Obstructed at validation (`DanglingReference`, `ContentIdentityConflict`, footprint law, stale expect) | `Proposed` | re-evaluate; compare pre-decision evidence | effects bound; read-set digest; obstruction kind | -| `BudgetExceeded`, scheduler-assigned (Action-batch limit) | `Proposed` | accept reconstructed preparation; verify through batch reconstruction | effects bound; read-set digest | -| `BudgetExceeded` during evaluation, or an authored guard | `NoProposal` | re-evaluate; compare pre-decision evidence | re-evaluation again yields no proposal; read-set digest | -| `BasisChanged` | `NoProposal` | admission evidence; recomputed basis posture; no re-evaluation | none; posture must be `NoProposal` with an empty read set | +Recovery matrix. "Existing check" is what recovery verifies today for that +class; "Added" is what the bridge adds. "Effects bound" means the re-derived +effects must hash to the recorded proposal identity and reproduce the +recorded write-set digest and patch tag, with the patch digest when it is +`Derived` (invariant 5). + +Two checks apply to every row and are not repeated in it, because recovery +already performs them for every Action Tick: the basis posture is recomputed +against the scheduler Tick's parent, and the whole Action batch's composition +is reconstructed and every reconstructed outcome must equal the recorded one. +The bridge keeps both and adds the outgoing-edge range keys to the +reconstructed composition. + +| Outcome | Posture | Existing check | Added | +| --------------------------------------------------------------------------------------------------------------------------------------- | -------------------------- | ------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------- | +| Committed | `Proposed`, `Derived` | replay patch; reconstruct preparation; compare identities | effects bound; read-set digest; patch digest equals the replayed patch | +| `RejectedFootprintConflict` | `Proposed`, `Derived` | reconstruct preparation; compare identities; recompute recorded blockers | effects bound; read-set digest; blockers include range-key conflicts | +| Obstructed at validation (`DanglingReference`, `ContentIdentityConflict`, `DuplicateEffectTarget`, footprint law, stale expected value) | `Proposed`, `NotDerived` | re-evaluate; compare pre-decision evidence | effects bound; read-set digest; obstruction kind; no patch digest present | +| `BudgetExceeded`, scheduler-assigned (Action-batch limit) | `Proposed`, `Derived` | accept reconstructed preparation; verify through the batch reconstruction | effects bound; read-set digest | +| `BudgetExceeded` during evaluation, or an authored guard | `NoProposal` | re-evaluate; compare pre-decision evidence | re-evaluation again yields no proposal; read-set digest | +| `BasisChanged`, whole-frontier (before evaluation) | `NoProposal` | admission evidence; recomputed basis posture; no re-evaluation | none; posture must be `NoProposal` with an empty read set | +| `BasisChanged`, dependency-scoped (after evaluation) | `Proposed` or `NoProposal` | none today: current programs decide this before evaluation | re-evaluate at the origin; effects bound if `Proposed`; read-set digest; recompute which witnessed read changed | ## Execution traces @@ -505,30 +584,32 @@ propose `ReplaceEdge B -canonicalHead-> expected H1`. Each behaviour below needs a test before implementation: -| Property | Test | -| ---------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| Atomicity | A failure on the last effect of a multi-node proposal leaves the parent state root and patch unchanged. | -| Ensure identity | Identical existing node: no patch entry. Different existing node: `ContentIdentityConflict`. | -| Witnessed footprint | A program that reads a slot outside its law obstructs, even if its writes are in bounds. | -| Stale basis | `ReplaceEdge` with a moved `expectedTo` obstructs with no patch. | -| Budget | Exhausting steps or bytes obstructs with `BudgetExceeded` and `NoProposal`; a proposal whose patch alone exceeds the Action-batch operation limit obstructs with `BudgetExceeded`, posture `Proposed` with its digests, and no committed patch. | -| Determinism | Same package, invocation, and evaluation basis give byte-identical proposals, proposal identities, read-set and write-set digests, and derived patches, whether the Action is evaluated alone or beside other Actions. Receipts and composite patches are compared only for identical Tick contexts. | -| Proposal binding | For every `Proposed` outcome class in the recovery matrix, including rejected ones, altering one recorded effect-derived digest or the proposal identity refuses the history. | -| No-proposal evidence | An evaluation that obstructs before proposing records `NoProposal` with its read-set digest and no proposal, write-set, or patch digest. | -| Recovery | Crash before and after the decided-Tick WAL record; fresh-host recovery reproduces the same state root and receipts. | -| Composition | Two Actions with independent witnessed footprints (neither writes a slot the other reads or writes) commit in one Tick. A write/write overlap obstructs the later one, and so does a write/read overlap: A reads X and writes Y, B writes X and Z. | -| Shared structure | Two Actions that `Ensure` the same node already present in the basis both commit in one Tick. | -| Referential integrity | An edge to a node neither in the basis nor created by the proposal obstructs with `DanglingReference`; a pre-existing dangling edge elsewhere in the basis does not obstruct an unrelated proposal. | -| Visibility | No published coordinate shows a retargeted head without every node and edge it reaches. | -| Witnessed reads | The receipt's read set equals the slots the view resolved, including for a program that reads and then obstructs. | -| Negative reads | An Action that observed a node, atom, or edge set absent obstructs when a co-scheduled Action creates it. | -| Edge-read basis | An Action that read edges commits only onto its exact evaluation basis. | -| Final outcomes | A rejected Action's outcome, receipt, and attribution are unchanged after a later submission of the same edit succeeds. | -| Duplicate resubmission | Resubmitting a decided Action's identical content returns `Duplicate` with the original outcome and commits no Tick. | -| Attribution | Every Action in a bridge Tick appears once in the attribution section, mapped to its own receipt entry. | -| Receipt format | Ticks written before the new entry format recover unchanged; new Ticks declare the new format. | -| Recovered evidence | For each outcome class, recovery performs that class's existing verification plus the new comparisons (posture, proposal identity, read-set, write-set, and patch digests where the class reconstructs or re-evaluates); a tampered digest refuses the history, a `BasisChanged` obstruction is not re-evaluated, and no recorded outcome changes. | -| Pending recovery | An Action lost with its Tick before the WAL record is recovered as the same submission, not a new one. | +| Property | Test | +| ---------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| Atomicity | A failure on the last effect of a multi-node proposal leaves the parent state root and patch unchanged. | +| Ensure identity | Identical existing node: no patch entry. Different existing node: `ContentIdentityConflict`. | +| Witnessed footprint | A program that reads a slot outside its law obstructs, even if its writes are in bounds. | +| Stale basis | `ReplaceEdge` with a moved `expectedTo` obstructs with no patch. | +| Budget | Exhausting steps or bytes obstructs with `BudgetExceeded` and `NoProposal`; a proposal whose patch alone exceeds the Action-batch operation limit obstructs with `BudgetExceeded`, posture `Proposed` with its digests, and no committed patch. | +| Determinism | Same package, invocation, and evaluation basis give byte-identical proposals, proposal identities, read-set and write-set digests, and derived patches, whether the Action is evaluated alone or beside other Actions. Receipts and composite patches are compared only for identical Tick contexts. | +| Proposal binding | For every `Proposed` outcome class in the recovery matrix, including rejected ones, altering one recorded effect-derived digest or the proposal identity refuses the history. | +| No-proposal evidence | An evaluation that obstructs before proposing records `NoProposal` with its read-set digest and no proposal, write-set, or patch digest. | +| Recovery | Crash before and after the decided-Tick WAL record; fresh-host recovery reproduces the same state root and receipts. | +| Composition | Two Actions with independent witnessed footprints (neither writes a slot the other reads or writes) commit in one Tick. A write/write overlap obstructs the later one, and so does a write/read overlap: A reads X and writes Y, B writes X and Z. | +| Shared structure | Two Actions that `Ensure` the same node already present in the basis both commit in one Tick. | +| Referential integrity | An edge to a node neither in the basis nor created by the proposal obstructs with `DanglingReference`; a pre-existing dangling edge elsewhere in the basis does not obstruct an unrelated proposal. | +| Visibility | No published coordinate shows a retargeted head without every node and edge the proposal introduced; pre-existing structure the head reaches is not validated. | +| Witnessed reads | The receipt's read set equals the slots the view resolved, including for a program that reads and then obstructs. | +| Negative reads | An Action that observed a node, atom, or edge set absent obstructs when a co-scheduled Action creates it, including a fresh edge of the observed source and type with a new edge id (outgoing-edge range key). | +| Edge-read basis | An Action that read edges commits only onto its exact evaluation basis. | +| Final outcomes | A rejected Action's outcome, receipt, and attribution are unchanged after a later submission of the same edit succeeds. | +| Duplicate resubmission | Resubmitting a decided Action's identical content returns `Duplicate` with the original outcome and commits no Tick. | +| Attribution | Every Action in a bridge Tick appears once in the attribution section, mapped to its own receipt entry. | +| One effect per slot | Two effects on one slot (two `ReplaceAtom`, or `EnsureNode` plus `ReplaceAtom` of the same node) obstruct with `DuplicateEffectTarget`, posture `Proposed`, `NotDerived`. | +| Scoped basis change | A dependency-scoped Action evaluated at its origin obstructs with `BasisChanged` and keeps its proposal and read set when a witnessed read changed; an unchanged read set lets it commit. | +| Receipt format | `EPRV0003` records recover byte for byte through the unchanged decoder; new Ticks are written as `EPRV0004`; a history mixing both recovers. | +| Recovered evidence | For each outcome class, recovery performs that class's existing verification plus the new comparisons (posture, proposal identity, read-set, write-set, and patch digests where the class reconstructs or re-evaluates); a tampered digest refuses the history, a whole-frontier `BasisChanged` is not re-evaluated, the batch composition is reconstructed for every class, and no recorded outcome changes. | +| Pending recovery | An Action lost with its Tick before the WAL record is recovered as the same submission, not a new one. | ## Open questions diff --git a/docs/topics/GeneratedRules.md b/docs/topics/GeneratedRules.md index 918f2fab..dfcd3df6 100644 --- a/docs/topics/GeneratedRules.md +++ b/docs/topics/GeneratedRules.md @@ -326,9 +326,12 @@ can delete the legacy feature without also losing executable operations. `main`; until that merge it is a proposal and ADR 0023 governs unchanged. Tracks [flyingrobots/jedit#296](https://github.com/flyingrobots/jedit/issues/296). Supersedes in part [ADR 0023](../adr/0023-admitted-executable-operation-packages.md): -the declarative-resource requirements of its "Jedit rope-law closure" section, -enumerated in that section's banner, and its selection under "Alternatives for -Expressing the Rope Law". Every other provision of ADR 0023 continues to +every requirement that assumes the rope law is an imported declarative +resource or lawpack, wherever it appears in ADR 0023 (the rope-law closure, +the package binding direction and obligations, the evidence grades, +Jurisdiction, and Consequences), each marked where it appears and enumerated +in the "Jedit rope-law closure" banner, and its selection under "Alternatives +for Expressing the Rope Law". Every other provision of ADR 0023 continues to govern, and its original reasoning is kept.** Jedit's persistent-rope `ReplaceRange` law is authored in Jedit-owned Edict From 50e6a7eeb0905d65c785139e2820aeebbf23f97b Mon Sep 17 00:00:00 2001 From: James Ross Date: Fri, 16 Oct 2026 03:24:27 -0700 Subject: [PATCH 11/50] docs: define target sets, scoped preconditions, and receipt digest v5 Fifth review round, each a concrete gap in the stated contract: - Proposed records a target-set digest that the effects alone define; the write set is known only after validation, so it moves into the Derived patch tag and is never manufactured for NotDerived. - Footprint validation splits into FootprintLawViolation (before derivation, NotDerived) and FootprintMismatch (after, Derived). - Dependency-scoped validity checks every effect precondition as well as the witnessed reads, and a changed-then-restored value still counts as changed. - A retry is evaluated at its recorded basis, never at selection time. - Receipt digest version 5 binds the bridge fields alongside EPRV0004, with the format-table row the implementation adds. - Duplicate targets compare canonical slot identity, and an observed empty edge set conflicts with an insertion, each with a test row. --- .../authored-operation-effects.md | 150 ++++++++++++------ 1 file changed, 98 insertions(+), 52 deletions(-) diff --git a/docs/architecture/authored-operation-effects.md b/docs/architecture/authored-operation-effects.md index 1ce7dca0..d56184ba 100644 --- a/docs/architecture/authored-operation-effects.md +++ b/docs/architecture/authored-operation-effects.md @@ -57,9 +57,10 @@ Five invariants govern the design: evaluation produced, whether those effects committed or were rejected. A rejected proposal is never ambiguous on replay or audit: re-deriving it over the recorded basis must reproduce effects that hash to the recorded - identity, the recorded write-set digest, and, where a patch was derived, - the recorded patch digest. Each slot is the target of at most one effect, - so the derived patch applies every effect the identity names. + identity and the recorded target-set digest, and, where a patch was + derived, the recorded write-set and patch digests. Each slot is the target + of at most one effect, so the derived patch applies every effect the + identity names. Echo learns no application vocabulary. A proposal is nodes, edges, and atoms. @@ -170,12 +171,19 @@ Rules: - **Effects are the only writes.** The proposal contains no prepared patch. Echo derives the patch from the effects. - **One effect per slot.** No two effects in one proposal may target the same - slot: one node record, one node's atom, or one edge. `EnsureNode` and - `CreateNode` target the node record and its atom, so an `EnsureNode` plus a - `ReplaceAtom` of the same node collide. A collision is a typed - `DuplicateEffectTarget` obstruction before any patch is derived. Without - this rule `WarpTickPatchV1::new` would keep only the last operation per key - and silently drop effects the proposal identity names. + slot: one node record, one node's atom, or one edge. Targets are compared + by canonical slot identity (the node id, the node id's atom, or the edge + id), never by the effect's kind or form, so differently expressed effects + cannot reach one slot undetected. `EnsureNode` and `CreateNode` target the + node record and its atom, so an `EnsureNode` plus a `ReplaceAtom` of the + same node collide, and an `EnsureEdge` plus a `ReplaceEdge` of the same + edge id collide. A collision is a typed `DuplicateEffectTarget` obstruction + before any patch is derived. Without this rule `WarpTickPatchV1::new` would + keep only the last operation per key and silently drop effects the proposal + identity names. +- **Target set.** The proposal's target set is the canonical set of slots its + effects target. It is a function of the effects alone, defined for every + proposal before validation, and its digest is part of the evidence. - **Ensure is idempotent only on identity.** An `EnsureNode` whose node exists with identical type and atom adds nothing to the patch and counts as a read. An existing node with different content is a typed @@ -244,11 +252,17 @@ The package declares a footprint law: the node types, edge types, and maximum counts an Action may read and write, relative to its invocation. At validation Echo checks: -- the witnessed read set and the proposal's write set lie inside the law; +- the witnessed read set and the proposal's target set lie inside the law; - counts do not exceed the declared maxima; -- the derived patch's actual footprint equals the proposal's write set. - -Any violation is a typed obstruction with no patch. Independence between +- the derived patch's actual footprint equals the proposal's write set: the + target set less the `Ensure` targets that already exist identically, which + are reads. + +The first two checks run before any patch is derived; a violation is a typed +`FootprintLawViolation` obstruction with patch tag `NotDerived`. The third can +only run after derivation; a mismatch is a typed `FootprintMismatch` +obstruction with patch tag `Derived`, recording the patch digest it compared. +Neither commits a patch. Independence between Actions in one Tick uses the witnessed footprints, including outgoing-edge range keys, under the existing rule: write against read or write conflicts, read against read does not. @@ -304,9 +318,15 @@ held; this design validates only what the proposal adds or retargets. program can read edges) and its evaluation basis is no longer the commit parent. Posture `NoProposal`, empty read set, no evaluation. - **Dependency-scoped,** decided after evaluation: the Action is eligible, - so Echo evaluates it at its recorded origin basis, then checks each - witnessed read, presence and absence, against the history between that - origin and the commit parent. If one changed, the Action obstructs with + so Echo evaluates it at its recorded origin basis, then checks its + dependency set against the history between that origin and the commit + parent. The dependency set is every witnessed read, presence and + absence, plus every effect precondition, whether or not the program + read it: each `ReplaceAtom` expected atom digest, each `ReplaceEdge` + expected target, each `CreateNode` absence, and each `Ensure` target's + presence-with-identical-content or absence. As the Action validity + across ticks decision requires, a dependency that changed in that + history counts as changed even if it was later restored. If one changed, the Action obstructs with `BasisChanged` but keeps the evidence it has: posture `Proposed` when evaluation produced a proposal, otherwise `NoProposal`, and in both cases its witnessed read set. Echo never validates a delayed Action @@ -318,7 +338,7 @@ held; this design validates only what the proposal adds or retargets. (`ACTION_BATCH_OPERATION_LIMIT_V1`) also obstructs with `BudgetExceeded` and no committed patch, rather than splitting across Ticks, but its posture is `Proposed` with a derived patch: the proposal exists, so its identity, - write-set digest, and derived patch digest are recorded, not discarded. A proposal that fits alone + target-set, write-set, and patch digests are recorded, not discarded. A proposal that fits alone but not beside the Actions already accepted into the batch is handled by the existing batch rule, which obstructs that candidate in this batch, likewise with posture `Proposed`. @@ -331,9 +351,12 @@ held; this design validates only what the proposal adds or retargets. `Duplicate`, with its original outcome and no new Tick. A real new attempt must differ, for example an invocation rebased onto the new head, or a causal parent citing the earlier outcome; that difference makes it a new - submission with its own identity and attribution, evaluated against whatever - basis is current when the scheduler selects it. Echo does not retry on its - own; whether to submit again is the application's decision. + submission with its own identity, attribution, and recorded evaluation + basis. Like any Action, it is evaluated at that recorded basis, never + re-stamped onto whatever state is current when the scheduler selects it, + and then committed under the whole-frontier or dependency-scoped rule. + Echo does not retry on its own; whether to submit again is the + application's decision. - **Pending is not retried.** An Action that was never decided, for example one whose Tick was lost in a crash before its WAL record, remains the same submission. It is evaluated again when the scheduler selects it; that is not @@ -375,18 +398,22 @@ Each Action's receipt entry binds, in addition to its current fields: - `NoProposal`: no proposal exists, because evaluation obstructed before producing one (for example `BudgetExceeded` or an authored guard such as `BasisNotCanonical`) or never ran (whole-frontier `BasisChanged`). - No proposal identity, write-set digest, or patch digest exists, and - none is manufactured; - - `Proposed { proposal identity, write-set digest, patch }`: evaluation - produced a proposal. `patch` is itself tagged: - - `Derived { patch digest }` when Echo derived a patch: the proposal - committed, was rejected at composition, or was obstructed by an - Action-batch limit or a dependency-scoped `BasisChanged`; + No proposal identity, target-set, write-set, or patch digest exists, + and none is manufactured; + - `Proposed { proposal identity, target-set digest, patch }`: evaluation + produced a proposal. The target-set digest is always defined, because + the target set depends only on the effects. `patch` is itself tagged: + - `Derived { write-set digest, patch digest }` when Echo derived a + patch: the proposal committed, was rejected at composition, or was + obstructed by `FootprintMismatch`, an Action-batch limit, or a + dependency-scoped `BasisChanged`. The write set is known only once + validation has resolved each `Ensure` against the basis, so it is + recorded only here; - `NotDerived` when validation refused the proposal before any patch existed (`DanglingReference`, `ContentIdentityConflict`, - `DuplicateEffectTarget`, a footprint-law violation, or a stale - expected value). No patch digest is manufactured; an overwrite - patch that `Ensure` forbids is never hashed; + `DuplicateEffectTarget`, `FootprintLawViolation`, or a stale + expected value). No write-set or patch digest is manufactured; an + overwrite patch that `Ensure` forbids is never hashed; - the witnessed read-set digest, including observed absences, for every outcome that evaluated, because a refusal is still evidence of what the program saw; it is the empty set's digest when evaluation never ran; @@ -399,8 +426,8 @@ evaluated proposal, and the receipt keeps them distinct. The proposal identity is `H(Action, evaluation basis, canonical EffectProposal)`, and the canonical `EffectProposal` contains every effect in canonical order, so the identity binds the exact evaluated effects -(invariant 5). The write-set digest and, when one was derived, the patch -digest are derived from those same effects. Proposals are not retained apart from their Tick; the binding is +(invariant 5). The target-set digest and, when a patch was derived, the +write-set and patch digests are derived from those same effects. Proposals are not retained apart from their Tick; the binding is checked by re-deriving the proposal, as the recovery matrix below requires for every class that carries one. @@ -412,12 +439,24 @@ evaluation and keeps its evidence (see Basis, budget, retries). These fields change the receipt entry's encoding, which commit hash v3 binds, and its retained layout. `EPRV0003` has no per-entry length or version, so a legacy decoder would read the new fields as blocker data. The new evidence is -therefore written only under a new retained provenance record version -(`EPRV0004`) with its own receipt-entry layout and receipt format number, -introduced under the same rules as Submission Attribution's format table. The -`EPRV0003` decoder stays unchanged, retained legacy Ticks recover through it -byte for byte, a mixed history decodes each record by its own magic, and an -`EPRV0003` record never carries the new fields. +therefore written only under a new retained provenance record version, +`EPRV0004`, and a new receipt digest version, 5. The retained magic alone is +not enough, because it sits outside the receipt digest that commit hash v3 +binds: receipt digest version 5 puts the proposal posture, target-set digest, +patch tag with its digests, read-set digest, and consumed budget into the +digest's preimage, so a version-4 receipt identity never changes meaning. +The implementation adds this row to +[Submission Attribution's format table](../topics/SubmissionAttribution.md#format-binding-and-legacy) +when it lands: + +| Receipt | Receipt digest | Retained record | Commit hash | +| ----------------------------------------- | -------------- | --------------- | ----------- | +| Commit-bound section with bridge evidence | version 5 | `EPRV0004` | v3 | + +The `EPRV0003` decoder and receipt digest version 4 stay unchanged, retained +legacy Ticks recover through them byte for byte, a mixed history decodes each +record by its own magic, and an `EPRV0003` record never carries the new +fields. ### Recovery @@ -446,8 +485,8 @@ new evidence. Verification is not re-admission. Recovery matrix. "Existing check" is what recovery verifies today for that class; "Added" is what the bridge adds. "Effects bound" means the re-derived effects must hash to the recorded proposal identity and reproduce the -recorded write-set digest and patch tag, with the patch digest when it is -`Derived` (invariant 5). +recorded target-set digest and patch tag, with the write-set and patch +digests when it is `Derived` (invariant 5). Two checks apply to every row and are not repeated in it, because recovery already performs them for every Action Tick: the basis posture is recomputed @@ -456,15 +495,16 @@ is reconstructed and every reconstructed outcome must equal the recorded one. The bridge keeps both and adds the outgoing-edge range keys to the reconstructed composition. -| Outcome | Posture | Existing check | Added | -| --------------------------------------------------------------------------------------------------------------------------------------- | -------------------------- | ------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------- | -| Committed | `Proposed`, `Derived` | replay patch; reconstruct preparation; compare identities | effects bound; read-set digest; patch digest equals the replayed patch | -| `RejectedFootprintConflict` | `Proposed`, `Derived` | reconstruct preparation; compare identities; recompute recorded blockers | effects bound; read-set digest; blockers include range-key conflicts | -| Obstructed at validation (`DanglingReference`, `ContentIdentityConflict`, `DuplicateEffectTarget`, footprint law, stale expected value) | `Proposed`, `NotDerived` | re-evaluate; compare pre-decision evidence | effects bound; read-set digest; obstruction kind; no patch digest present | -| `BudgetExceeded`, scheduler-assigned (Action-batch limit) | `Proposed`, `Derived` | accept reconstructed preparation; verify through the batch reconstruction | effects bound; read-set digest | -| `BudgetExceeded` during evaluation, or an authored guard | `NoProposal` | re-evaluate; compare pre-decision evidence | re-evaluation again yields no proposal; read-set digest | -| `BasisChanged`, whole-frontier (before evaluation) | `NoProposal` | admission evidence; recomputed basis posture; no re-evaluation | none; posture must be `NoProposal` with an empty read set | -| `BasisChanged`, dependency-scoped (after evaluation) | `Proposed` or `NoProposal` | none today: current programs decide this before evaluation | re-evaluate at the origin; effects bound if `Proposed`; read-set digest; recompute which witnessed read changed | +| Outcome | Posture | Existing check | Added | +| ------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------- | ------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------- | +| Committed | `Proposed`, `Derived` | replay patch; reconstruct preparation; compare identities | effects bound; read-set digest; patch digest equals the replayed patch | +| `RejectedFootprintConflict` | `Proposed`, `Derived` | reconstruct preparation; compare identities; recompute recorded blockers | effects bound; read-set digest; blockers include range-key conflicts | +| Obstructed at validation (`DanglingReference`, `ContentIdentityConflict`, `DuplicateEffectTarget`, `FootprintLawViolation`, stale expected value) | `Proposed`, `NotDerived` | re-evaluate; compare pre-decision evidence | effects bound; read-set digest; obstruction kind; no write-set or patch digest present | +| Obstructed by `FootprintMismatch` (after derivation) | `Proposed`, `Derived` | re-evaluate; compare pre-decision evidence | effects bound; read-set digest; re-derived patch reproduces the mismatch | +| `BudgetExceeded`, scheduler-assigned (Action-batch limit) | `Proposed`, `Derived` | accept reconstructed preparation; verify through the batch reconstruction | effects bound; read-set digest | +| `BudgetExceeded` during evaluation, or an authored guard | `NoProposal` | re-evaluate; compare pre-decision evidence | re-evaluation again yields no proposal; read-set digest | +| `BasisChanged`, whole-frontier (before evaluation) | `NoProposal` | admission evidence; recomputed basis posture; no re-evaluation | none; posture must be `NoProposal` with an empty read set | +| `BasisChanged`, dependency-scoped (after evaluation) | `Proposed` or `NoProposal` | none today: current programs decide this before evaluation | re-evaluate at the origin; effects bound if `Proposed`; read-set digest; recompute which witnessed read changed | ## Execution traces @@ -515,8 +555,9 @@ B.canonicalHead, B.atom}`. No co-scheduled Action reads or writes these. `UpsertEdge H2.anchor`, `UpsertEdge B.canonicalHead` (same edge id, new target), `SetAttachment B`. Eight operations. 9. **Decision and durability.** The receipt binds the proposal posture - `Proposed` with the proposal identity, the write-set and patch digests, the - read-set digest, and the consumed budget. + `Proposed` with the proposal identity, the target-set digest, the patch tag + `Derived` with the write-set and patch digests, the read-set digest, and + the consumed budget. The decided Tick is written to the WAL, then published. 10. **Visibility.** Coordinate `t` shows `B → H1 → T1`. Coordinate `t+1` shows `B → H2 → T2`, with `H2 → I`. No coordinate shows `H2` without `T2`. `H1` @@ -592,6 +633,11 @@ Each behaviour below needs a test before implementation: | Stale basis | `ReplaceEdge` with a moved `expectedTo` obstructs with no patch. | | Budget | Exhausting steps or bytes obstructs with `BudgetExceeded` and `NoProposal`; a proposal whose patch alone exceeds the Action-batch operation limit obstructs with `BudgetExceeded`, posture `Proposed` with its digests, and no committed patch. | | Determinism | Same package, invocation, and evaluation basis give byte-identical proposals, proposal identities, read-set and write-set digests, and derived patches, whether the Action is evaluated alone or beside other Actions. Receipts and composite patches are compared only for identical Tick contexts. | +| Canonical slots | Differently expressed effects on one slot (`EnsureEdge` and `ReplaceEdge` of one edge id; `CreateNode` and `ReplaceAtom` of one node) obstruct with `DuplicateEffectTarget`. | +| Footprint stages | A target outside the footprint law obstructs with `FootprintLawViolation` and `NotDerived`; a derived patch whose footprint differs from the write set obstructs with `FootprintMismatch` and `Derived`. | +| Scoped preconditions | A dependency-scoped Action whose `ReplaceAtom` expected value changed and was restored between its origin and the commit parent obstructs with `BasisChanged`, though the program never read that atom. | +| Retry basis | A new submission selected after the head advanced is evaluated at its recorded basis, not at the selection-time state. | +| Receipt digest v5 | A version-5 receipt digest changes when any bridge field changes; version-4 receipt identities are unchanged. | | Proposal binding | For every `Proposed` outcome class in the recovery matrix, including rejected ones, altering one recorded effect-derived digest or the proposal identity refuses the history. | | No-proposal evidence | An evaluation that obstructs before proposing records `NoProposal` with its read-set digest and no proposal, write-set, or patch digest. | | Recovery | Crash before and after the decided-Tick WAL record; fresh-host recovery reproduces the same state root and receipts. | @@ -600,7 +646,7 @@ Each behaviour below needs a test before implementation: | Referential integrity | An edge to a node neither in the basis nor created by the proposal obstructs with `DanglingReference`; a pre-existing dangling edge elsewhere in the basis does not obstruct an unrelated proposal. | | Visibility | No published coordinate shows a retargeted head without every node and edge the proposal introduced; pre-existing structure the head reaches is not validated. | | Witnessed reads | The receipt's read set equals the slots the view resolved, including for a program that reads and then obstructs. | -| Negative reads | An Action that observed a node, atom, or edge set absent obstructs when a co-scheduled Action creates it, including a fresh edge of the observed source and type with a new edge id (outgoing-edge range key). | +| Negative reads | An Action that observed a node, atom, or edge set absent obstructs when a co-scheduled Action creates it, including a fresh edge of the observed source and type with a new edge id when the observed set was empty (outgoing-edge range key). | | Edge-read basis | An Action that read edges commits only onto its exact evaluation basis. | | Final outcomes | A rejected Action's outcome, receipt, and attribution are unchanged after a later submission of the same edit succeeds. | | Duplicate resubmission | Resubmitting a decided Action's identical content returns `Duplicate` with the original outcome and commits no Tick. | From 16fd7d107c12dd5541fe9cb2fd9f87c245aa8111 Mon Sep 17 00:00:00 2001 From: James Ross Date: Fri, 16 Oct 2026 06:06:49 -0700 Subject: [PATCH 12/50] docs: close the fourth review round on the authored-effects bridge - One canonical slot model (node record, atom, edge, outgoing-edge range key) for the target set, write set, read set, and footprint. The write set adds the range key of every created or retargeted edge, and the derived patch's actual footprint is computed in the same model, so the committed trace's EnsureEdge and ReplaceEdge pass the equality check. The trace's write set now lists the atom slots and range keys. - Effect preconditions, edge endpoints included, are defined once. They are footprint reads, and they belong to a dependency-scoped Action's dependency set, so a changed-and-restored endpoint obstructs. - A dependency-scoped history check runs after evaluation and before validation. Its BasisChanged records Proposed with NotDerived, so a proposal that would also fail validation still has one encodable outcome. - IdentityLawViolation is a typed Proposed/NotDerived obstruction, with matrix and test rows. - Receipt entries gain a NotBridge posture for fixed-profile and other entries, so mixed Ticks encode in EPRV0004 without fabricated evidence. - Bridge entries bind their validity rule. A Scoped entry binds origin, validation position, dependency descriptors with comparison modes, compared values, and the changed dependency, in both EPRV0004 and receipt digest v5. Recovery recomputes the complete dependency set. Co-Authored-By: Claude Opus 5.5 --- .../authored-operation-effects.md | 220 ++++++++++++------ 1 file changed, 151 insertions(+), 69 deletions(-) diff --git a/docs/architecture/authored-operation-effects.md b/docs/architecture/authored-operation-effects.md index d56184ba..ceeb9caa 100644 --- a/docs/architecture/authored-operation-effects.md +++ b/docs/architecture/authored-operation-effects.md @@ -181,9 +181,38 @@ Rules: before any patch is derived. Without this rule `WarpTickPatchV1::new` would keep only the last operation per key and silently drop effects the proposal identity names. +- **Slots.** One canonical slot model serves the target set, the write set, + the read set, and the footprint. A slot is a node record, a node's atom + (its alpha attachment), an edge, or an outgoing-edge range key + `(source node, edge type)` (see + [Witnessed reads](#witnessed-reads-over-a-bounded-slice)). - **Target set.** The proposal's target set is the canonical set of slots its - effects target. It is a function of the effects alone, defined for every - proposal before validation, and its digest is part of the evidence. + effects target: for `EnsureNode` and `CreateNode` the node record and its + atom, for `ReplaceAtom` the atom, and for `EnsureEdge` and `ReplaceEdge` the + edge. It is a function of the effects alone, defined for every proposal + before validation, and its digest is part of the evidence. +- **Write set.** The proposal's write set is the target set less the slots of + each `Ensure` whose target already exists identically in the basis (those + are reads), plus the outgoing-edge range key of every edge the proposal + creates or retargets: the `(from, type)` of a creating `EnsureEdge`, and the + source and type of the basis edge a `ReplaceEdge` retargets. Range keys + depend on the basis, so the write set is known only once validation has + resolved each effect against it. +- **Effect preconditions.** Each effect states what must hold for it to + apply, whether or not the program read it: + - `CreateNode`: the node is absent; + - `EnsureNode` and `EnsureEdge`: the target is absent, or present with + identical content; + - `ReplaceAtom`: the node's atom has the expected digest; + - `ReplaceEdge`: the edge exists with the expected target; + - `EnsureEdge` and `ReplaceEdge`: each endpoint the edge will name + (`from` and `to`, or the retargeted edge's source and the new `to`) + exists, unless the same proposal creates it. + + Every precondition slot is a read in the Action's footprint, is checked by + validation, and belongs to the dependency set of a dependency-scoped Action + (see [Basis, budget, retries](#basis-budget-retries)). + - **Ensure is idempotent only on identity.** An `EnsureNode` whose node exists with identical type and atom adds nothing to the patch and counts as a read. An existing node with different content is a typed @@ -196,7 +225,9 @@ Rules: Deletion needs its own design. - **Identity derivation is the program's.** Echo checks that each node and edge id matches the identity law the package declares, so ids cannot be chosen to - collide with unrelated nodes. Echo does not invent ids. + collide with unrelated nodes. A node or edge id that does not match is a + typed `IdentityLawViolation` obstruction before any patch is derived. Echo + does not invent ids. ## Witnessed reads over a bounded slice @@ -252,11 +283,14 @@ The package declares a footprint law: the node types, edge types, and maximum counts an Action may read and write, relative to its invocation. At validation Echo checks: -- the witnessed read set and the proposal's target set lie inside the law; +- the witnessed read set, the precondition slots, and the proposal's target + set lie inside the law; - counts do not exceed the declared maxima; -- the derived patch's actual footprint equals the proposal's write set: the - target set less the `Ensure` targets that already exist identically, which - are reads. +- the derived patch's actual footprint equals the proposal's write set (see + [EffectProposal](#effectproposal)). The actual footprint is computed in the + same slot model: each node upsert writes the node record, each attachment + set writes the node's atom, and each edge upsert writes the edge and the + range key of its source and type. The first two checks run before any patch is derived; a violation is a typed `FootprintLawViolation` obstruction with patch tag `NotDerived`. The third can @@ -279,14 +313,18 @@ node in its basis, so its `Ensure` is a read and does not conflict. ## Visibility and referential integrity -Validation enforces invariant 4 before a proposal can join a Tick: +Validation enforces invariant 4 before a proposal can join a Tick, through +the endpoint preconditions defined in [EffectProposal](#effectproposal): - every `EnsureEdge` and `ReplaceEdge` endpoint exists in the basis or is created by an `EnsureNode` or `CreateNode` in the same proposal; - every edge a `ReplaceEdge` retargets exists in the basis with the expected target. -A violation is a typed `DanglingReference` obstruction with no patch. +A missing endpoint is a typed `DanglingReference` obstruction with no patch. +Because endpoints are precondition reads, a co-scheduled Action that writes +one conflicts, and a delayed Action whose endpoint changed in the intervening +history obstructs with `BasisChanged`, even if the endpoint was restored. Validation examines only the edges the proposal adds or retargets, and the endpoints those edges name. An existing dangling edge elsewhere in the basis @@ -321,16 +359,22 @@ held; this design validates only what the proposal adds or retargets. so Echo evaluates it at its recorded origin basis, then checks its dependency set against the history between that origin and the commit parent. The dependency set is every witnessed read, presence and - absence, plus every effect precondition, whether or not the program - read it: each `ReplaceAtom` expected atom digest, each `ReplaceEdge` - expected target, each `CreateNode` absence, and each `Ensure` target's - presence-with-identical-content or absence. As the Action validity - across ticks decision requires, a dependency that changed in that - history counts as changed even if it was later restored. If one changed, the Action obstructs with - `BasisChanged` but keeps the evidence it has: posture `Proposed` when - evaluation produced a proposal, otherwise `NoProposal`, and in both - cases its witnessed read set. Echo never validates a delayed Action - without its actual dependencies. + absence, plus every effect precondition defined in + [EffectProposal](#effectproposal), whether or not the program read it, + including each edge endpoint. As the Action validity across ticks + decision requires, a dependency that changed in that history counts as + changed even if it was later restored. + - **Order.** The history check runs immediately after evaluation and + before any validation of the proposal. If a dependency changed, the + Action obstructs with `BasisChanged` and keeps the evidence it has: + posture `Proposed` with patch tag `NotDerived` when evaluation produced + a proposal, otherwise `NoProposal`, and in both cases its witnessed read + set and the dependency it names as changed. No patch is derived from a + proposal whose dependencies changed, so a proposal that would also have + failed validation (for example `DuplicateEffectTarget`) still has one + encodable outcome. Only when every dependency holds does validation run, + at the Action's validation position. Echo never validates a delayed + Action without its actual dependencies. - **Budget.** Evaluation is metered as `edict_pure` already meters it. Each effect adds a fixed write cost. Exhausting any evaluation limit obstructs with `BudgetExceeded`, posture `NoProposal`, and no patch. A proposal whose @@ -394,7 +438,12 @@ Consequences: Each Action's receipt entry binds, in addition to its current fields: -- the proposal posture, a tagged value with exactly two variants: +- the proposal posture, a tagged value with exactly three variants: + - `NotBridge`: the entry is not a bridge Action. Fixed-profile Actions and + every other existing receipt entry take this variant. It carries none of + the bridge fields below, and its existing fields are encoded and hashed + exactly as under receipt digest version 4, after the tag. No bridge + evidence is manufactured for it; - `NoProposal`: no proposal exists, because evaluation obstructed before producing one (for example `BudgetExceeded` or an authored guard such as `BasisNotCanonical`) or never ran (whole-frontier `BasisChanged`). @@ -405,21 +454,34 @@ Each Action's receipt entry binds, in addition to its current fields: the target set depends only on the effects. `patch` is itself tagged: - `Derived { write-set digest, patch digest }` when Echo derived a patch: the proposal committed, was rejected at composition, or was - obstructed by `FootprintMismatch`, an Action-batch limit, or a - dependency-scoped `BasisChanged`. The write set is known only once - validation has resolved each `Ensure` against the basis, so it is - recorded only here; - - `NotDerived` when validation refused the proposal before any patch - existed (`DanglingReference`, `ContentIdentityConflict`, - `DuplicateEffectTarget`, `FootprintLawViolation`, or a stale - expected value). No write-set or patch digest is manufactured; an - overwrite patch that `Ensure` forbids is never hashed; -- the witnessed read-set digest, including observed absences, for every - outcome that evaluated, because a refusal is still evidence of what the - program saw; it is the empty set's digest when evaluation never ran; -- consumed budget. - -The submission identity is present in both variants. An identified submission + obstructed by `FootprintMismatch` or an Action-batch limit. The + write set is known only once validation has resolved each effect + against the basis, so it is recorded only here; + - `NotDerived` when no patch was derived: validation refused the + proposal (`DanglingReference`, `ContentIdentityConflict`, + `DuplicateEffectTarget`, `FootprintLawViolation`, + `IdentityLawViolation`, or a stale expected value), or a + dependency-scoped `BasisChanged` was decided before validation. No + write-set or patch digest is manufactured; an overwrite patch that + `Ensure` forbids is never hashed; +- for `NoProposal` and `Proposed`, the witnessed read-set digest, including + observed absences, for every outcome that evaluated, because a refusal is + still evidence of what the program saw; it is the empty set's digest when + evaluation never ran; +- for `NoProposal` and `Proposed`, the validity rule, a tagged value: + - `WholeFrontier`: the Action commits only onto its exact evaluation + basis; the recorded basis posture is its evidence; + - `Scoped`: the evidence the + [Action validity across ticks](../topics/GeneratedRules.md#action-validity-across-ticks) + decision requires, namely the origin basis, the validation position, a + dependency-set digest over each dependency's descriptor and comparison + mode, a compared-values digest over the values those dependencies were + compared against at the validation position (the commit parent plus the + writes of every earlier accepted Action), and, for a `BasisChanged`, the + first changed dependency in canonical order; +- for `NoProposal` and `Proposed`, consumed budget. + +The submission identity is present in every variant. An identified submission whose evaluation produced no proposal is a different evidence state from an evaluated proposal, and the receipt keeps them distinct. @@ -427,9 +489,10 @@ The proposal identity is `H(Action, evaluation basis, canonical EffectProposal)`, and the canonical `EffectProposal` contains every effect in canonical order, so the identity binds the exact evaluated effects (invariant 5). The target-set digest and, when a patch was derived, the -write-set and patch digests are derived from those same effects. Proposals are not retained apart from their Tick; the binding is -checked by re-deriving the proposal, as the recovery matrix below requires for -every class that carries one. +write-set and patch digests are derived from those same effects. Proposals +are not retained apart from their Tick; the binding is checked by re-deriving +the proposal, as the recovery matrix below requires for every class that +carries one. Today every `BasisChanged` is decided before evaluation runs (`echo_operation.rs`). For bridge Actions that remains true only of the @@ -442,9 +505,13 @@ legacy decoder would read the new fields as blocker data. The new evidence is therefore written only under a new retained provenance record version, `EPRV0004`, and a new receipt digest version, 5. The retained magic alone is not enough, because it sits outside the receipt digest that commit hash v3 -binds: receipt digest version 5 puts the proposal posture, target-set digest, -patch tag with its digests, read-set digest, and consumed budget into the -digest's preimage, so a version-4 receipt identity never changes meaning. +binds: receipt digest version 5 puts every entry's proposal posture and, for +bridge entries, the target-set digest, patch tag with its digests, read-set +digest, validity rule with its scoped evidence, and consumed budget into the +digest's preimage. Two validations that compared different values therefore +have different receipt identities, and a version-4 receipt identity never +changes meaning. Ticks that mix bridge and `NotBridge` entries encode in +`EPRV0004` without fabricated evidence. The implementation adds this row to [Submission Attribution's format table](../topics/SubmissionAttribution.md#format-binding-and-legacy) when it lands: @@ -475,10 +542,13 @@ new evidence. Verification is not re-admission. - The comparison never changes a decision. The recorded outcome, receipt, and attribution stand as written; a re-derived proposal is checked against them and then discarded. -- An Action admitted under the scoped validity of - [Action validity across ticks](../topics/GeneratedRules.md#action-validity-across-ticks) - follows that decision: recovery recomputes its validation at the recorded - position from retained history and refuses a history whose result differs. +- An Action recorded as `Scoped` follows + [Action validity across ticks](../topics/GeneratedRules.md#action-validity-across-ticks): + whatever its outcome, recovery recomputes its complete dependency set + (every witnessed read and every effect precondition) and its validation at + the recorded position from retained history, and refuses a history whose + dependency-set digest, compared-values digest, changed dependency, or result + differs. - An undecided Action stays the same pending submission and is evaluated when the scheduler next selects it. @@ -492,19 +562,21 @@ Two checks apply to every row and are not repeated in it, because recovery already performs them for every Action Tick: the basis posture is recomputed against the scheduler Tick's parent, and the whole Action batch's composition is reconstructed and every reconstructed outcome must equal the recorded one. -The bridge keeps both and adds the outgoing-edge range keys to the -reconstructed composition. - -| Outcome | Posture | Existing check | Added | -| ------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------- | ------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------- | -| Committed | `Proposed`, `Derived` | replay patch; reconstruct preparation; compare identities | effects bound; read-set digest; patch digest equals the replayed patch | -| `RejectedFootprintConflict` | `Proposed`, `Derived` | reconstruct preparation; compare identities; recompute recorded blockers | effects bound; read-set digest; blockers include range-key conflicts | -| Obstructed at validation (`DanglingReference`, `ContentIdentityConflict`, `DuplicateEffectTarget`, `FootprintLawViolation`, stale expected value) | `Proposed`, `NotDerived` | re-evaluate; compare pre-decision evidence | effects bound; read-set digest; obstruction kind; no write-set or patch digest present | -| Obstructed by `FootprintMismatch` (after derivation) | `Proposed`, `Derived` | re-evaluate; compare pre-decision evidence | effects bound; read-set digest; re-derived patch reproduces the mismatch | -| `BudgetExceeded`, scheduler-assigned (Action-batch limit) | `Proposed`, `Derived` | accept reconstructed preparation; verify through the batch reconstruction | effects bound; read-set digest | -| `BudgetExceeded` during evaluation, or an authored guard | `NoProposal` | re-evaluate; compare pre-decision evidence | re-evaluation again yields no proposal; read-set digest | -| `BasisChanged`, whole-frontier (before evaluation) | `NoProposal` | admission evidence; recomputed basis posture; no re-evaluation | none; posture must be `NoProposal` with an empty read set | -| `BasisChanged`, dependency-scoped (after evaluation) | `Proposed` or `NoProposal` | none today: current programs decide this before evaluation | re-evaluate at the origin; effects bound if `Proposed`; read-set digest; recompute which witnessed read changed | +The bridge keeps both and adds the outgoing-edge range keys and precondition +reads to the reconstructed composition. The scoped-validity recomputation +above likewise applies to every row recorded as `Scoped`. + +| Outcome | Posture | Existing check | Added | +| ------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------- | ------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | +| Committed | `Proposed`, `Derived` | replay patch; reconstruct preparation; compare identities | effects bound; read-set digest; patch digest equals the replayed patch | +| `RejectedFootprintConflict` | `Proposed`, `Derived` | reconstruct preparation; compare identities; recompute recorded blockers | effects bound; read-set digest; blockers include range-key conflicts | +| Obstructed at validation (`DanglingReference`, `ContentIdentityConflict`, `DuplicateEffectTarget`, `FootprintLawViolation`, `IdentityLawViolation`, stale expected value) | `Proposed`, `NotDerived` | re-evaluate; compare pre-decision evidence | effects bound; read-set digest; obstruction kind; no write-set or patch digest present | +| Obstructed by `FootprintMismatch` (after derivation) | `Proposed`, `Derived` | re-evaluate; compare pre-decision evidence | effects bound; read-set digest; re-derived patch reproduces the mismatch | +| `BudgetExceeded`, scheduler-assigned (Action-batch limit) | `Proposed`, `Derived` | accept reconstructed preparation; verify through the batch reconstruction | effects bound; read-set digest | +| `BudgetExceeded` during evaluation, or an authored guard | `NoProposal` | re-evaluate; compare pre-decision evidence | re-evaluation again yields no proposal; read-set digest | +| `BasisChanged`, whole-frontier (before evaluation) | `NoProposal` | admission evidence; recomputed basis posture; no re-evaluation | none; posture must be `NoProposal` with an empty read set | +| `BasisChanged`, dependency-scoped (after evaluation, before validation) | `Proposed`, `NotDerived`, or `NoProposal` | none today: current programs decide this before evaluation | re-evaluate at the origin; effects bound if `Proposed`; read-set digest; recompute the complete dependency set, witnessed reads and effect preconditions, and the changed dependency | +| Any outcome of a `NotBridge` entry | `NotBridge` | unchanged | none; no bridge field present | ## Execution traces @@ -545,19 +617,27 @@ replacement "there"}` at basis `c`. - `ReplaceAtom B expected digest(version 7), now {version 8}` 6. **Validation.** The read and write sets lie inside the footprint law. Echo recomputes `T2` and `H2` from their atoms and children under the declared - identity law. Endpoints check: `T2` and `H2` are created here, and `I` and + identity law; a mismatch would obstruct with `IdentityLawViolation`. + Endpoint preconditions check: `T2` and `H2` are created here, and `I` and `B` exist at `c`. The expected `H1` and version-7 digest hold at `c`. The - budget holds. -7. **Composition.** The write set is `{T2, H2, H2.root, H2.anchor, -B.canonicalHead, B.atom}`. No co-scheduled Action reads or writes these. + budget holds. The Action reads edges, so its validity rule is + `WholeFrontier`. +7. **Composition.** The write set is the eight target slots `T2`, `T2.atom`, + `H2`, `H2.atom`, `H2.root`, `H2.anchor`, `B.canonicalHead`, and `B.atom`, + plus the range keys `(H2, root)`, `(H2, anchor)`, and `(B, canonicalHead)` + of the edges it creates or retargets. Its reads add the precondition slots: + endpoints `I` and `B` exist, and `B.atom` and `B.canonicalHead` hold their + expected values. No co-scheduled Action reads or writes these. 8. **Patch**, derived by Echo: `UpsertNode T2`, `SetAttachment T2`, `UpsertNode H2`, `SetAttachment H2`, `UpsertEdge H2.root`, `UpsertEdge H2.anchor`, `UpsertEdge B.canonicalHead` (same edge id, new - target), `SetAttachment B`. Eight operations. + target), `SetAttachment B`. Eight operations, whose actual footprint is + exactly the write set above: each `SetAttachment` writes an atom slot and + each `UpsertEdge` writes its edge and range key. 9. **Decision and durability.** The receipt binds the proposal posture `Proposed` with the proposal identity, the target-set digest, the patch tag - `Derived` with the write-set and patch digests, the read-set digest, and - the consumed budget. + `Derived` with the write-set and patch digests, the read-set digest, the + validity rule `WholeFrontier`, and the consumed budget. The decided Tick is written to the WAL, then published. 10. **Visibility.** Coordinate `t` shows `B → H1 → T1`. Coordinate `t+1` shows `B → H2 → T2`, with `H2 → I`. No coordinate shows `H2` without `T2`. `H1` @@ -634,10 +714,12 @@ Each behaviour below needs a test before implementation: | Budget | Exhausting steps or bytes obstructs with `BudgetExceeded` and `NoProposal`; a proposal whose patch alone exceeds the Action-batch operation limit obstructs with `BudgetExceeded`, posture `Proposed` with its digests, and no committed patch. | | Determinism | Same package, invocation, and evaluation basis give byte-identical proposals, proposal identities, read-set and write-set digests, and derived patches, whether the Action is evaluated alone or beside other Actions. Receipts and composite patches are compared only for identical Tick contexts. | | Canonical slots | Differently expressed effects on one slot (`EnsureEdge` and `ReplaceEdge` of one edge id; `CreateNode` and `ReplaceAtom` of one node) obstruct with `DuplicateEffectTarget`. | +| Write set | The derived patch's actual footprint equals the write set, including the atom slots of created nodes and the range key of every created or retargeted edge, so a committed `EnsureEdge` and `ReplaceEdge` pass the footprint check. | +| Identity law | A proposed node or edge id that does not match the package's identity law obstructs with `IdentityLawViolation`, posture `Proposed`, `NotDerived`, and recovery reproduces it. | | Footprint stages | A target outside the footprint law obstructs with `FootprintLawViolation` and `NotDerived`; a derived patch whose footprint differs from the write set obstructs with `FootprintMismatch` and `Derived`. | -| Scoped preconditions | A dependency-scoped Action whose `ReplaceAtom` expected value changed and was restored between its origin and the commit parent obstructs with `BasisChanged`, though the program never read that atom. | +| Scoped preconditions | A dependency-scoped Action whose `ReplaceAtom` expected value, or one of whose edge endpoints, changed and was restored between its origin and the commit parent obstructs with `BasisChanged`, though the program never read that slot; recovery reproduces the outcome and its changed dependency. | | Retry basis | A new submission selected after the head advanced is evaluated at its recorded basis, not at the selection-time state. | -| Receipt digest v5 | A version-5 receipt digest changes when any bridge field changes; version-4 receipt identities are unchanged. | +| Receipt digest v5 | A version-5 receipt digest changes when any bridge field changes, including the validation position, a dependency's comparison mode, or a compared value; version-4 receipt identities are unchanged. | | Proposal binding | For every `Proposed` outcome class in the recovery matrix, including rejected ones, altering one recorded effect-derived digest or the proposal identity refuses the history. | | No-proposal evidence | An evaluation that obstructs before proposing records `NoProposal` with its read-set digest and no proposal, write-set, or patch digest. | | Recovery | Crash before and after the decided-Tick WAL record; fresh-host recovery reproduces the same state root and receipts. | @@ -652,8 +734,8 @@ Each behaviour below needs a test before implementation: | Duplicate resubmission | Resubmitting a decided Action's identical content returns `Duplicate` with the original outcome and commits no Tick. | | Attribution | Every Action in a bridge Tick appears once in the attribution section, mapped to its own receipt entry. | | One effect per slot | Two effects on one slot (two `ReplaceAtom`, or `EnsureNode` plus `ReplaceAtom` of the same node) obstruct with `DuplicateEffectTarget`, posture `Proposed`, `NotDerived`. | -| Scoped basis change | A dependency-scoped Action evaluated at its origin obstructs with `BasisChanged` and keeps its proposal and read set when a witnessed read changed; an unchanged read set lets it commit. | -| Receipt format | `EPRV0003` records recover byte for byte through the unchanged decoder; new Ticks are written as `EPRV0004`; a history mixing both recovers. | +| Scoped basis change | A dependency-scoped Action evaluated at its origin obstructs with `BasisChanged`, posture `Proposed` with `NotDerived`, and keeps its proposal, read set, and changed dependency when a dependency changed, even if its proposal would also fail validation; an unchanged dependency set lets it commit. | +| Receipt format | `EPRV0003` records recover byte for byte through the unchanged decoder; new Ticks are written as `EPRV0004`; a Tick mixing a bridge Action with fixed-profile Actions encodes the latter as `NotBridge`; a history mixing both formats recovers. | | Recovered evidence | For each outcome class, recovery performs that class's existing verification plus the new comparisons (posture, proposal identity, read-set, write-set, and patch digests where the class reconstructs or re-evaluates); a tampered digest refuses the history, a whole-frontier `BasisChanged` is not re-evaluated, the batch composition is reconstructed for every class, and no recorded outcome changes. | | Pending recovery | An Action lost with its Tick before the WAL record is recovered as the same submission, not a new one. | From 522e2eaa5e7791c37baff7cceb4f95b093e48698 Mon Sep 17 00:00:00 2001 From: James Ross Date: Fri, 16 Oct 2026 06:08:47 -0700 Subject: [PATCH 13/50] docs: name the evidence that detects a restored dependency Value comparison cannot see A -> B -> A. The dependency-scoped history check compares slots instead, reusing echo_operation_written_slots_v1, which collects every slot the retained replay patches wrote between origin and commit parent, and falls back to the whole-frontier rule when a patch is missing. Adds an ABA test row. --- docs/architecture/authored-operation-effects.md | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/docs/architecture/authored-operation-effects.md b/docs/architecture/authored-operation-effects.md index ceeb9caa..06c3b3de 100644 --- a/docs/architecture/authored-operation-effects.md +++ b/docs/architecture/authored-operation-effects.md @@ -364,6 +364,17 @@ held; this design validates only what the proposal adds or retargets. including each edge endpoint. As the Action validity across ticks decision requires, a dependency that changed in that history counts as changed even if it was later restored. + - **How a restored value is detected.** Comparing current values cannot + see a change from A to B and back to A. The history check therefore + compares slots, not values: it reuses Echo's existing evidence, + `echo_operation_written_slots_v1` (`echo_operation.rs`), which collects + every slot written by the retained replay patches from the origin to + the commit parent. A dependency whose slot is in that set has changed, + whatever its value now. If any Tick in that interval has no retained + replay patch, the written set is unknown and the Action falls back to + the whole-frontier rule. Value comparison is used only at the + validation position, against the commit parent plus the writes of + earlier Actions in the same Tick. No new versioning system is added. - **Order.** The history check runs immediately after evaluation and before any validation of the proposal. If a dependency changed, the Action obstructs with `BasisChanged` and keeps the evidence it has: @@ -717,6 +728,7 @@ Each behaviour below needs a test before implementation: | Write set | The derived patch's actual footprint equals the write set, including the atom slots of created nodes and the range key of every created or retargeted edge, so a committed `EnsureEdge` and `ReplaceEdge` pass the footprint check. | | Identity law | A proposed node or edge id that does not match the package's identity law obstructs with `IdentityLawViolation`, posture `Proposed`, `NotDerived`, and recovery reproduces it. | | Footprint stages | A target outside the footprint law obstructs with `FootprintLawViolation` and `NotDerived`; a derived patch whose footprint differs from the write set obstructs with `FootprintMismatch` and `Derived`. | +| Restored value (ABA) | A dependency written A to B and back to A between origin and commit parent is found through the written-slot set and obstructs with `BasisChanged`; with one replay patch in the interval missing, the Action falls back to the whole-frontier rule. | | Scoped preconditions | A dependency-scoped Action whose `ReplaceAtom` expected value, or one of whose edge endpoints, changed and was restored between its origin and the commit parent obstructs with `BasisChanged`, though the program never read that slot; recovery reproduces the outcome and its changed dependency. | | Retry basis | A new submission selected after the head advanced is evaluated at its recorded basis, not at the selection-time state. | | Receipt digest v5 | A version-5 receipt digest changes when any bridge field changes, including the validation position, a dependency's comparison mode, or a compared value; version-4 receipt identities are unchanged. | From fa9ef3bd1952ebc2e99d87f367c55b5811c993d2 Mon Sep 17 00:00:00 2001 From: James Ross Date: Fri, 16 Oct 2026 09:50:25 -0700 Subject: [PATCH 14/50] docs: close the fifth review round on the authored-effects bridge - A wrong expected value is PreconditionMismatch (Proposed, NotDerived), the kind today's compare-and-set uses, distinct from BasisChanged. - CreateNode needs the node record and atom both absent (else PreconditionMismatch). EnsureNode needs both absent or both identical (else ContentIdentityConflict). An orphan slot is never overwritten. - Effects have a canonical order: primary-slot class, then id bytes, with a kind and encoding tie-break. Echo sorts; emission order never reaches the identity. - The resolved write set, range keys included, is checked against the footprint law before derivation. - Write sets resolve against the Tick's commit parent, so two Actions creating the same node still conflict. - Written slots for restored-value detection come from replay operations, not declared out_slots. Current implementation now records that WarpTickPatchV1::new does not check out_slots against operations. - Unknown history (a missing replay patch) is decided before evaluation as a whole-frontier BasisChanged with NoProposal. - Evaluation obstructions keep their outcome. Only a proposal is reclassified as a dependency-scoped BasisChanged. Co-Authored-By: Claude Opus 5.5 --- .../authored-operation-effects.md | 209 +++++++++++------- 1 file changed, 134 insertions(+), 75 deletions(-) diff --git a/docs/architecture/authored-operation-effects.md b/docs/architecture/authored-operation-effects.md index 06c3b3de..636f8126 100644 --- a/docs/architecture/authored-operation-effects.md +++ b/docs/architecture/authored-operation-effects.md @@ -119,6 +119,11 @@ Echo learns no application vocabulary. A proposal is nodes, edges, and atoms. history where any reconstructed outcome differs from the recorded one; for each `RejectedFootprintConflict` it also recomputes the blocking earlier Actions and requires them to equal the recorded blockers. +- `echo_operation_written_slots_v1` collects the slots written between an + origin and a commit parent by unioning each retained replay patch's + `out_slots`. `WarpTickPatchV1::new` accepts `out_slots` independently of + its operations, and only debug builds or `footprint_enforce_release` check + that a rewrite stays inside its declared footprint. - The state root is a full walk of the reachable graph on every commit. See the open state-root decision in [Sliced Readings and Copy-on-Write Strands](sliced-readings-and-copy-on-write-strands.md#open-decisions-that-bound-these-laws). @@ -170,6 +175,18 @@ Rules: - **Effects are the only writes.** The proposal contains no prepared patch. Echo derives the patch from the effects. +- **Canonical order.** Echo sorts the effects the evaluator emits; the order + in which evaluation produced them never reaches the identity. Each effect + has a primary slot: the node record for `EnsureNode` and `CreateNode`, the + atom for `ReplaceAtom`, and the edge for `EnsureEdge` and `ReplaceEdge`. + Effects sort by the primary slot's class (node record, then atom, then + edge), then by its id bytes, compared lexicographically. A tie can occur + only in a proposal that obstructs with `DuplicateEffectTarget`; it breaks + by effect kind (`EnsureNode`, `CreateNode`, `ReplaceAtom`, `EnsureEdge`, + `ReplaceEdge`), then by the effect's canonical encoding bytes: a kind tag, + fixed-width ids, and length-prefixed type and atom bytes. Sorting never + removes an effect, so duplicates stay visible to the one-effect-per-slot + rule. - **One effect per slot.** No two effects in one proposal may target the same slot: one node record, one node's atom, or one edge. Targets are compared by canonical slot identity (the node id, the node id's atom, or the edge @@ -200,9 +217,15 @@ Rules: resolved each effect against it. - **Effect preconditions.** Each effect states what must hold for it to apply, whether or not the program read it: - - `CreateNode`: the node is absent; - - `EnsureNode` and `EnsureEdge`: the target is absent, or present with - identical content; + - `CreateNode`: the node record and its atom are both absent. If either + exists, including an atom without a node record, the effect obstructs + with `PreconditionMismatch`, as today's create-if-absent does; + - `EnsureNode`: the node record and its atom are both absent, or both + present with identical type and atom. Any other state, including a + record without an atom or an atom without a record, is + `ContentIdentityConflict`; + - `EnsureEdge`: the edge is absent, or present with identical endpoints + and type; - `ReplaceAtom`: the node's atom has the expected digest; - `ReplaceEdge`: the edge exists with the expected target; - `EnsureEdge` and `ReplaceEdge`: each endpoint the edge will name @@ -220,7 +243,11 @@ Rules: is the idempotent create profile that Edict's I-022 requires before duplicate content-addressed creation can be lawful. - **Compare-before-replace.** `ReplaceAtom` and `ReplaceEdge` carry the value - they expect. A mismatch at commit is a stale-basis obstruction, not a merge. + they expect. A mismatch is a typed `PreconditionMismatch` obstruction, the + kind today's compare-and-set uses, with posture `Proposed`, `NotDerived`. + It is never a merge. It is distinct from `BasisChanged`, which reports a + dependency that changed after the origin (see + [Basis, budget, retries](#basis-budget-retries)). - **No deletes in this profile.** Persistent structures do not need them. Deletion needs its own design. - **Identity derivation is the program's.** Echo checks that each node and edge @@ -286,13 +313,17 @@ Echo checks: - the witnessed read set, the precondition slots, and the proposal's target set lie inside the law; - counts do not exceed the declared maxima; +- once validation has resolved each effect against the basis, the complete + write set, range keys included, and its counts lie inside the law. A range + key is known only after resolution: a `ReplaceEdge` whose basis edge has a + source or type outside the law fails here, before any patch exists; - the derived patch's actual footprint equals the proposal's write set (see [EffectProposal](#effectproposal)). The actual footprint is computed in the same slot model: each node upsert writes the node record, each attachment set writes the node's atom, and each edge upsert writes the edge and the range key of its source and type. -The first two checks run before any patch is derived; a violation is a typed +The first three checks run before any patch is derived; a violation is a typed `FootprintLawViolation` obstruction with patch tag `NotDerived`. The third can only run after derivation; a mismatch is a typed `FootprintMismatch` obstruction with patch tag `Derived`, recording the patch digest it compared. @@ -307,7 +338,12 @@ An `Ensure` of a node or edge that already exists identically in the basis is a read, not a write, so Actions that share existing structure do not conflict. Two Actions in one Tick that both create the same new content-addressed node do write the same slot, and the existing rule makes the later one obstruct. -This design adds no merge rule for identical writes. The rejected Action's +This design adds no merge rule for identical writes. Every Action's write +set, and so whether its `Ensure` is a read, is resolved against the Tick's +commit parent, never against the writes of Actions accepted earlier in the +same Tick. A later Action therefore cannot turn an earlier Action's new node +into a read of its own, even under dependency-scoped validity, whose value +comparison runs at the validation position. The rejected Action's outcome is final (see Retries); a new submission evaluated later finds the node in its basis, so its `Ensure` is a read and does not conflict. @@ -366,21 +402,35 @@ held; this design validates only what the proposal adds or retargets. changed even if it was later restored. - **How a restored value is detected.** Comparing current values cannot see a change from A to B and back to A. The history check therefore - compares slots, not values: it reuses Echo's existing evidence, - `echo_operation_written_slots_v1` (`echo_operation.rs`), which collects - every slot written by the retained replay patches from the origin to - the commit parent. A dependency whose slot is in that set has changed, - whatever its value now. If any Tick in that interval has no retained - replay patch, the written set is unknown and the Action falls back to - the whole-frontier rule. Value comparison is used only at the - validation position, against the commit parent plus the writes of - earlier Actions in the same Tick. No new versioning system is added. - - **Order.** The history check runs immediately after evaluation and - before any validation of the proposal. If a dependency changed, the - Action obstructs with `BasisChanged` and keeps the evidence it has: - posture `Proposed` with patch tag `NotDerived` when evaluation produced - a proposal, otherwise `NoProposal`, and in both cases its witnessed read - set and the dependency it names as changed. No patch is derived from a + compares slots, not values: it uses the set of slots written by the + retained replay patches from the origin to the commit parent. A + dependency whose slot is in that set has changed, whatever its value + now. The set is derived from each patch's replay operations, in the + slot model of the actual footprint, never from its declared + `out_slots`, which a release build does not check against the + operations (see [Current implementation](#current-implementation)). + `echo_operation_written_slots_v1` must therefore project operations, or + verify that `out_slots` covers them exactly and treat any mismatch as + unknown. Value comparison is used only at the validation position, + against the commit parent plus the writes of earlier Actions in the + same Tick. No new versioning system is added. + - **Unknown history is decided before evaluation.** The written set + depends only on the origin and the commit parent, so Echo computes it + before evaluating the Action. If any Tick in the interval has no + retained replay patch, or its writes cannot be derived, the set is + unknown and the Action takes the whole-frontier rule at once: since the + origin is not the commit parent, it obstructs with whole-frontier + `BasisChanged`, posture `NoProposal`, empty read set, no evaluation. + - **Evaluation obstructions stand.** An evaluation that obstructs before + producing a proposal (`BudgetExceeded`, or an authored guard) keeps + that obstruction with posture `NoProposal`. Its later reads never ran + and it has no effects, so its dependency set is incomplete, and it is + never reclassified as `BasisChanged`. It commits nothing either way. + - **Order.** For a proposal, the history check runs immediately after + evaluation and before any validation. If a dependency changed, the + Action obstructs with `BasisChanged`, posture `Proposed` with patch tag + `NotDerived`, and keeps its witnessed read set and the dependency it + names as changed. No patch is derived from a proposal whose dependencies changed, so a proposal that would also have failed validation (for example `DuplicateEffectTarget`) still has one encodable outcome. Only when every dependency holds does validation run, @@ -471,7 +521,7 @@ Each Action's receipt entry binds, in addition to its current fields: - `NotDerived` when no patch was derived: validation refused the proposal (`DanglingReference`, `ContentIdentityConflict`, `DuplicateEffectTarget`, `FootprintLawViolation`, - `IdentityLawViolation`, or a stale expected value), or a + `IdentityLawViolation`, or `PreconditionMismatch`), or a dependency-scoped `BasisChanged` was decided before validation. No write-set or patch digest is manufactured; an overwrite patch that `Ensure` forbids is never hashed; @@ -489,7 +539,9 @@ Each Action's receipt entry binds, in addition to its current fields: mode, a compared-values digest over the values those dependencies were compared against at the validation position (the commit parent plus the writes of every earlier accepted Action), and, for a `BasisChanged`, the - first changed dependency in canonical order; + first changed dependency in canonical order. An evaluation obstruction + runs no dependency comparison, so its `Scoped` evidence is the origin + basis and validation position only; - for `NoProposal` and `Proposed`, consumed budget. The submission identity is present in every variant. An identified submission @@ -555,11 +607,12 @@ new evidence. Verification is not re-admission. and then discarded. - An Action recorded as `Scoped` follows [Action validity across ticks](../topics/GeneratedRules.md#action-validity-across-ticks): - whatever its outcome, recovery recomputes its complete dependency set - (every witnessed read and every effect precondition) and its validation at - the recorded position from retained history, and refuses a history whose - dependency-set digest, compared-values digest, changed dependency, or result - differs. + for every `Proposed` outcome, recovery recomputes its complete dependency + set (every witnessed read and every effect precondition), the written-slot + set from the replay operations, and its validation at the recorded + position from retained history, and refuses a history whose dependency-set + digest, compared-values digest, changed dependency, or result differs. For + an evaluation obstruction it re-evaluates at the origin. - An undecided Action stays the same pending submission and is evaluated when the scheduler next selects it. @@ -577,17 +630,17 @@ The bridge keeps both and adds the outgoing-edge range keys and precondition reads to the reconstructed composition. The scoped-validity recomputation above likewise applies to every row recorded as `Scoped`. -| Outcome | Posture | Existing check | Added | -| ------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------- | ------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | -| Committed | `Proposed`, `Derived` | replay patch; reconstruct preparation; compare identities | effects bound; read-set digest; patch digest equals the replayed patch | -| `RejectedFootprintConflict` | `Proposed`, `Derived` | reconstruct preparation; compare identities; recompute recorded blockers | effects bound; read-set digest; blockers include range-key conflicts | -| Obstructed at validation (`DanglingReference`, `ContentIdentityConflict`, `DuplicateEffectTarget`, `FootprintLawViolation`, `IdentityLawViolation`, stale expected value) | `Proposed`, `NotDerived` | re-evaluate; compare pre-decision evidence | effects bound; read-set digest; obstruction kind; no write-set or patch digest present | -| Obstructed by `FootprintMismatch` (after derivation) | `Proposed`, `Derived` | re-evaluate; compare pre-decision evidence | effects bound; read-set digest; re-derived patch reproduces the mismatch | -| `BudgetExceeded`, scheduler-assigned (Action-batch limit) | `Proposed`, `Derived` | accept reconstructed preparation; verify through the batch reconstruction | effects bound; read-set digest | -| `BudgetExceeded` during evaluation, or an authored guard | `NoProposal` | re-evaluate; compare pre-decision evidence | re-evaluation again yields no proposal; read-set digest | -| `BasisChanged`, whole-frontier (before evaluation) | `NoProposal` | admission evidence; recomputed basis posture; no re-evaluation | none; posture must be `NoProposal` with an empty read set | -| `BasisChanged`, dependency-scoped (after evaluation, before validation) | `Proposed`, `NotDerived`, or `NoProposal` | none today: current programs decide this before evaluation | re-evaluate at the origin; effects bound if `Proposed`; read-set digest; recompute the complete dependency set, witnessed reads and effect preconditions, and the changed dependency | -| Any outcome of a `NotBridge` entry | `NotBridge` | unchanged | none; no bridge field present | +| Outcome | Posture | Existing check | Added | +| --------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------ | ------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| Committed | `Proposed`, `Derived` | replay patch; reconstruct preparation; compare identities | effects bound; read-set digest; patch digest equals the replayed patch | +| `RejectedFootprintConflict` | `Proposed`, `Derived` | reconstruct preparation; compare identities; recompute recorded blockers | effects bound; read-set digest; blockers include range-key conflicts | +| Obstructed at validation (`DanglingReference`, `ContentIdentityConflict`, `DuplicateEffectTarget`, `FootprintLawViolation`, `IdentityLawViolation`, `PreconditionMismatch`) | `Proposed`, `NotDerived` | re-evaluate; compare pre-decision evidence | effects bound; read-set digest; obstruction kind; no write-set or patch digest present | +| Obstructed by `FootprintMismatch` (after derivation) | `Proposed`, `Derived` | re-evaluate; compare pre-decision evidence | effects bound; read-set digest; re-derived patch reproduces the mismatch | +| `BudgetExceeded`, scheduler-assigned (Action-batch limit) | `Proposed`, `Derived` | accept reconstructed preparation; verify through the batch reconstruction | effects bound; read-set digest | +| `BudgetExceeded` during evaluation, or an authored guard | `NoProposal` | re-evaluate; compare pre-decision evidence | re-evaluation, at the origin for a `Scoped` Action, again yields no proposal and the same obstruction; read-set digest | +| `BasisChanged`, whole-frontier (before evaluation, including unknown history) | `NoProposal` | admission evidence; recomputed basis posture; no re-evaluation | none; posture must be `NoProposal` with an empty read set | +| `BasisChanged`, dependency-scoped (after evaluation, before validation) | `Proposed`, `NotDerived` | none today: current programs decide this before evaluation | re-evaluate at the origin; effects bound; read-set digest; recompute the written-slot set from replay operations, the complete dependency set (witnessed reads and effect preconditions), and the changed dependency | +| Any outcome of a `NotBridge` entry | `NotBridge` | unchanged | none; no bridge field present | ## Execution traces @@ -619,13 +672,15 @@ replacement "there"}` at basis `c`. 4. **Pure computation.** The guard `canonicalHead == H1` holds; range and boundaries check; the new bytes are `"hello there"`; the program derives ids `T2` and `H2` under its identity law. -5. **Proposal**, in canonical order: +5. **Proposal**, in canonical order: node records, then atoms, then edges. + Within a class the order follows id bytes, which these symbolic names do + not show. - `EnsureNode T2 {bytes "hello there", lineBreaks 0}` - `EnsureNode H2 {byteLength 11, lineCount 1, sequence 5}` + - `ReplaceAtom B expected digest(version 7), now {version 8}` - `EnsureEdge H2 -root-> T2` - `EnsureEdge H2 -anchor-> I` - `ReplaceEdge B -canonicalHead-> expected H1, now H2` - - `ReplaceAtom B expected digest(version 7), now {version 8}` 6. **Validation.** The read and write sets lie inside the footprint law. Echo recomputes `T2` and `H2` from their atoms and children under the declared identity law; a mismatch would obstruct with `IdentityLawViolation`. @@ -716,40 +771,44 @@ propose `ReplaceEdge B -canonicalHead-> expected H1`. Each behaviour below needs a test before implementation: -| Property | Test | -| ---------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| Atomicity | A failure on the last effect of a multi-node proposal leaves the parent state root and patch unchanged. | -| Ensure identity | Identical existing node: no patch entry. Different existing node: `ContentIdentityConflict`. | -| Witnessed footprint | A program that reads a slot outside its law obstructs, even if its writes are in bounds. | -| Stale basis | `ReplaceEdge` with a moved `expectedTo` obstructs with no patch. | -| Budget | Exhausting steps or bytes obstructs with `BudgetExceeded` and `NoProposal`; a proposal whose patch alone exceeds the Action-batch operation limit obstructs with `BudgetExceeded`, posture `Proposed` with its digests, and no committed patch. | -| Determinism | Same package, invocation, and evaluation basis give byte-identical proposals, proposal identities, read-set and write-set digests, and derived patches, whether the Action is evaluated alone or beside other Actions. Receipts and composite patches are compared only for identical Tick contexts. | -| Canonical slots | Differently expressed effects on one slot (`EnsureEdge` and `ReplaceEdge` of one edge id; `CreateNode` and `ReplaceAtom` of one node) obstruct with `DuplicateEffectTarget`. | -| Write set | The derived patch's actual footprint equals the write set, including the atom slots of created nodes and the range key of every created or retargeted edge, so a committed `EnsureEdge` and `ReplaceEdge` pass the footprint check. | -| Identity law | A proposed node or edge id that does not match the package's identity law obstructs with `IdentityLawViolation`, posture `Proposed`, `NotDerived`, and recovery reproduces it. | -| Footprint stages | A target outside the footprint law obstructs with `FootprintLawViolation` and `NotDerived`; a derived patch whose footprint differs from the write set obstructs with `FootprintMismatch` and `Derived`. | -| Restored value (ABA) | A dependency written A to B and back to A between origin and commit parent is found through the written-slot set and obstructs with `BasisChanged`; with one replay patch in the interval missing, the Action falls back to the whole-frontier rule. | -| Scoped preconditions | A dependency-scoped Action whose `ReplaceAtom` expected value, or one of whose edge endpoints, changed and was restored between its origin and the commit parent obstructs with `BasisChanged`, though the program never read that slot; recovery reproduces the outcome and its changed dependency. | -| Retry basis | A new submission selected after the head advanced is evaluated at its recorded basis, not at the selection-time state. | -| Receipt digest v5 | A version-5 receipt digest changes when any bridge field changes, including the validation position, a dependency's comparison mode, or a compared value; version-4 receipt identities are unchanged. | -| Proposal binding | For every `Proposed` outcome class in the recovery matrix, including rejected ones, altering one recorded effect-derived digest or the proposal identity refuses the history. | -| No-proposal evidence | An evaluation that obstructs before proposing records `NoProposal` with its read-set digest and no proposal, write-set, or patch digest. | -| Recovery | Crash before and after the decided-Tick WAL record; fresh-host recovery reproduces the same state root and receipts. | -| Composition | Two Actions with independent witnessed footprints (neither writes a slot the other reads or writes) commit in one Tick. A write/write overlap obstructs the later one, and so does a write/read overlap: A reads X and writes Y, B writes X and Z. | -| Shared structure | Two Actions that `Ensure` the same node already present in the basis both commit in one Tick. | -| Referential integrity | An edge to a node neither in the basis nor created by the proposal obstructs with `DanglingReference`; a pre-existing dangling edge elsewhere in the basis does not obstruct an unrelated proposal. | -| Visibility | No published coordinate shows a retargeted head without every node and edge the proposal introduced; pre-existing structure the head reaches is not validated. | -| Witnessed reads | The receipt's read set equals the slots the view resolved, including for a program that reads and then obstructs. | -| Negative reads | An Action that observed a node, atom, or edge set absent obstructs when a co-scheduled Action creates it, including a fresh edge of the observed source and type with a new edge id when the observed set was empty (outgoing-edge range key). | -| Edge-read basis | An Action that read edges commits only onto its exact evaluation basis. | -| Final outcomes | A rejected Action's outcome, receipt, and attribution are unchanged after a later submission of the same edit succeeds. | -| Duplicate resubmission | Resubmitting a decided Action's identical content returns `Duplicate` with the original outcome and commits no Tick. | -| Attribution | Every Action in a bridge Tick appears once in the attribution section, mapped to its own receipt entry. | -| One effect per slot | Two effects on one slot (two `ReplaceAtom`, or `EnsureNode` plus `ReplaceAtom` of the same node) obstruct with `DuplicateEffectTarget`, posture `Proposed`, `NotDerived`. | -| Scoped basis change | A dependency-scoped Action evaluated at its origin obstructs with `BasisChanged`, posture `Proposed` with `NotDerived`, and keeps its proposal, read set, and changed dependency when a dependency changed, even if its proposal would also fail validation; an unchanged dependency set lets it commit. | -| Receipt format | `EPRV0003` records recover byte for byte through the unchanged decoder; new Ticks are written as `EPRV0004`; a Tick mixing a bridge Action with fixed-profile Actions encodes the latter as `NotBridge`; a history mixing both formats recovers. | -| Recovered evidence | For each outcome class, recovery performs that class's existing verification plus the new comparisons (posture, proposal identity, read-set, write-set, and patch digests where the class reconstructs or re-evaluates); a tampered digest refuses the history, a whole-frontier `BasisChanged` is not re-evaluated, the batch composition is reconstructed for every class, and no recorded outcome changes. | -| Pending recovery | An Action lost with its Tick before the WAL record is recovered as the same submission, not a new one. | +| Property | Test | +| ----------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| Atomicity | A failure on the last effect of a multi-node proposal leaves the parent state root and patch unchanged. | +| Ensure identity | Identical existing node: no patch entry. Different existing node: `ContentIdentityConflict`. | +| Partial occupancy | A node record without its atom, or an atom without its record: `CreateNode` obstructs with `PreconditionMismatch` and `EnsureNode` with `ContentIdentityConflict`; neither overwrites the orphan slot. | +| Witnessed footprint | A program that reads a slot outside its law obstructs, even if its writes are in bounds. | +| Stale basis | `ReplaceAtom` with a changed expected digest, or `ReplaceEdge` with a moved `expectedTo`, obstructs with `PreconditionMismatch`, posture `Proposed`, `NotDerived`, and no patch. | +| Budget | Exhausting steps or bytes obstructs with `BudgetExceeded` and `NoProposal`; a proposal whose patch alone exceeds the Action-batch operation limit obstructs with `BudgetExceeded`, posture `Proposed` with its digests, and no committed patch. | +| Determinism | Same package, invocation, and evaluation basis give byte-identical proposals, proposal identities, read-set and write-set digests, and derived patches, whether the Action is evaluated alone or beside other Actions. Receipts and composite patches are compared only for identical Tick contexts. | +| Canonical order | Emitting the same effects in different orders yields the same canonical `EffectProposal` bytes and proposal identity. | +| Canonical slots | Differently expressed effects on one slot (`EnsureEdge` and `ReplaceEdge` of one edge id; `CreateNode` and `ReplaceAtom` of one node) obstruct with `DuplicateEffectTarget`. | +| Write set | The derived patch's actual footprint equals the write set, including the atom slots of created nodes and the range key of every created or retargeted edge, so a committed `EnsureEdge` and `ReplaceEdge` pass the footprint check. | +| Identity law | A proposed node or edge id that does not match the package's identity law obstructs with `IdentityLawViolation`, posture `Proposed`, `NotDerived`, and recovery reproduces it. | +| Footprint stages | A target outside the footprint law obstructs with `FootprintLawViolation` and `NotDerived`, and so does a `ReplaceEdge` whose basis edge's range key lies outside the law; a derived patch whose footprint differs from the write set obstructs with `FootprintMismatch` and `Derived`. | +| Restored value (ABA) | A dependency written A to B and back to A between origin and commit parent is found through the written-slot set and obstructs with `BasisChanged`, including when the intervening patch under-declared its `out_slots`; with one replay patch in the interval missing, the Action obstructs before evaluation as a whole-frontier `BasisChanged`, posture `NoProposal`, with an empty read set. | +| Scoped preconditions | A dependency-scoped Action whose `ReplaceAtom` expected value, or one of whose edge endpoints, changed and was restored between its origin and the commit parent obstructs with `BasisChanged`, though the program never read that slot; recovery reproduces the outcome and its changed dependency. | +| Retry basis | A new submission selected after the head advanced is evaluated at its recorded basis, not at the selection-time state. | +| Receipt digest v5 | A version-5 receipt digest changes when any bridge field changes, including the validation position, a dependency's comparison mode, or a compared value; version-4 receipt identities are unchanged. | +| Proposal binding | For every `Proposed` outcome class in the recovery matrix, including rejected ones, altering one recorded effect-derived digest or the proposal identity refuses the history. | +| No-proposal evidence | An evaluation that obstructs before proposing records `NoProposal` with its read-set digest and no proposal, write-set, or patch digest. | +| Recovery | Crash before and after the decided-Tick WAL record; fresh-host recovery reproduces the same state root and receipts. | +| Composition | Two Actions with independent witnessed footprints (neither writes a slot the other reads or writes) commit in one Tick. A write/write overlap obstructs the later one, and so does a write/read overlap: A reads X and writes Y, B writes X and Z. | +| Shared structure | Two Actions that `Ensure` the same node already present in the basis both commit in one Tick. | +| Same-Tick identical create | Two Actions, dependency-scoped included, that both `Ensure` the same node absent at the commit parent: the later obstructs with `RejectedFootprintConflict` instead of reading the earlier one's write. | +| Referential integrity | An edge to a node neither in the basis nor created by the proposal obstructs with `DanglingReference`; a pre-existing dangling edge elsewhere in the basis does not obstruct an unrelated proposal. | +| Visibility | No published coordinate shows a retargeted head without every node and edge the proposal introduced; pre-existing structure the head reaches is not validated. | +| Witnessed reads | The receipt's read set equals the slots the view resolved, including for a program that reads and then obstructs. | +| Negative reads | An Action that observed a node, atom, or edge set absent obstructs when a co-scheduled Action creates it, including a fresh edge of the observed source and type with a new edge id when the observed set was empty (outgoing-edge range key). | +| Edge-read basis | An Action that read edges commits only onto its exact evaluation basis. | +| Final outcomes | A rejected Action's outcome, receipt, and attribution are unchanged after a later submission of the same edit succeeds. | +| Duplicate resubmission | Resubmitting a decided Action's identical content returns `Duplicate` with the original outcome and commits no Tick. | +| Attribution | Every Action in a bridge Tick appears once in the attribution section, mapped to its own receipt entry. | +| One effect per slot | Two effects on one slot (two `ReplaceAtom`, or `EnsureNode` plus `ReplaceAtom` of the same node) obstruct with `DuplicateEffectTarget`, posture `Proposed`, `NotDerived`. | +| Scoped basis change | A dependency-scoped Action evaluated at its origin obstructs with `BasisChanged`, posture `Proposed` with `NotDerived`, and keeps its proposal, read set, and changed dependency when a dependency changed, even if its proposal would also fail validation; an unchanged dependency set lets it commit. | +| Scoped evaluation obstruction | A dependency-scoped Action whose evaluation exhausts its budget, or fails an authored guard, keeps that obstruction with `NoProposal` even when one of its reads changed after the origin; recovery reproduces it. | +| Receipt format | `EPRV0003` records recover byte for byte through the unchanged decoder; new Ticks are written as `EPRV0004`; a Tick mixing a bridge Action with fixed-profile Actions encodes the latter as `NotBridge`; a history mixing both formats recovers. | +| Recovered evidence | For each outcome class, recovery performs that class's existing verification plus the new comparisons (posture, proposal identity, read-set, write-set, and patch digests where the class reconstructs or re-evaluates); a tampered digest refuses the history, a whole-frontier `BasisChanged` is not re-evaluated, the batch composition is reconstructed for every class, and no recorded outcome changes. | +| Pending recovery | An Action lost with its Tick before the WAL record is recovered as the same submission, not a new one. | ## Open questions From 168b91a5b2a68e1c398420d741731ee3bd0a9d32 Mon Sep 17 00:00:00 2001 From: James Ross Date: Fri, 16 Oct 2026 10:01:12 -0700 Subject: [PATCH 15/50] docs: close the sixth review round on the authored-effects bridge - Record that the design supersedes, for bridge Actions only, ADR 0023's prepared-evaluation outcome. Bridge evaluation yields a proposal, not a patch; the receipt posture replaces PreparedEchoOperationOutcomeV1; and some obstructions record an uncommitted derived patch digest. Atomic visibility still governs. The supersession is marked in the design header, in ADR 0023's header and at both affected passages, and in the GeneratedRules decision's scope sentence. - The effect tie-break encoding includes every effect field, expected values included. - ReplaceAtom requires the node record (NodeMissing), so an orphan atom never satisfies it. - The post-derivation footprint check is the fourth, not the third. - Define the dependency descriptor, its order and its encoding, so the recorded first changed dependency is deterministic. Co-Authored-By: Claude Opus 5.5 --- ...-admitted-executable-operation-packages.md | 31 ++++++++++ .../authored-operation-effects.md | 58 +++++++++++++------ docs/topics/GeneratedRules.md | 7 ++- 3 files changed, 77 insertions(+), 19 deletions(-) diff --git a/docs/adr/0023-admitted-executable-operation-packages.md b/docs/adr/0023-admitted-executable-operation-packages.md index 0279f133..1c6b2880 100644 --- a/docs/adr/0023-admitted-executable-operation-packages.md +++ b/docs/adr/0023-admitted-executable-operation-packages.md @@ -19,6 +19,13 @@ closure, package binding, package obligations, evidence grades, Jurisdiction, and Consequences (enumerated in the "Jedit rope-law closure" banner), and the selection under "Alternatives for Expressing the Rope Law" +- **Superseded in part by:** + [Authored Operation Effects](../architecture/authored-operation-effects.md), + effective when [flyingrobots/echo#889](https://github.com/flyingrobots/echo/pull/889) + merges to `main`, for bridge Actions only: the prepared-evaluation outcome + (a successful evaluation exposes one patch; the closed + `PreparedEchoOperationOutcomeV1`; no patch digest on `Obstructed`), marked + where it appears > **Scheduler lifecycle amendment:** This ADR established exact package, > invocation, private-evaluation, and recovery semantics. ADR 0025 supersedes @@ -182,6 +189,18 @@ exposes one normal Echo patch; an obstructed evaluation exposes no parent mutation. Version 1 retains an execution or trace identity sufficient to bind later evidence without claiming that a durable child worldline already exists. +> **Superseded in part,** effective when flyingrobots/echo#889 merges: for +> bridge Actions, whose compiled Edict program proposes effects, the +> evaluation and outcome contract here is replaced by +> [Authored Operation Effects](../architecture/authored-operation-effects.md). +> A bridge evaluation yields an `EffectProposal` or a typed obstruction rather +> than a patch, Echo derives the patch afterwards in the same Tick +> construction, and the receipt posture defined there replaces +> `PreparedEchoOperationOutcomeV1`; some obstructed postures record a derived +> patch digest that was never committed. Atomic visibility, one parent patch +> or one obstruction, still governs. The original text is preserved and still +> governs every other Action. + The program may use generic primitives for typed graph access, canonical scalar operations, checked `u64` arithmetic, bounded byte manipulation, UTF-8 validation, canonical hashing, content-address derivation, and graph-delta @@ -243,6 +262,18 @@ An `Obstructed` outcome never enters parent commit or scheduler composition and never carries a patch digest. It may produce retained typed execution evidence, but it does not claim a committed consequence. +> **Superseded in part,** effective when flyingrobots/echo#889 merges: for +> bridge Actions, whose compiled Edict program proposes effects, the +> evaluation and outcome contract here is replaced by +> [Authored Operation Effects](../architecture/authored-operation-effects.md). +> A bridge evaluation yields an `EffectProposal` or a typed obstruction rather +> than a patch, Echo derives the patch afterwards in the same Tick +> construction, and the receipt posture defined there replaces +> `PreparedEchoOperationOutcomeV1`; some obstructed postures record a derived +> patch digest that was never committed. Atomic visibility, one parent patch +> or one obstruction, still governs. The original text is preserved and still +> governs every other Action. +> > **Superseded in part (2026-10-14, #837):** for Actions that meet the > conditions of [Action validity across ticks](../topics/GeneratedRules.md#action-validity-across-ticks), > the exact-basis requirement here is replaced by dependency-scoped validity. diff --git a/docs/architecture/authored-operation-effects.md b/docs/architecture/authored-operation-effects.md index 636f8126..99b64c02 100644 --- a/docs/architecture/authored-operation-effects.md +++ b/docs/architecture/authored-operation-effects.md @@ -19,6 +19,18 @@ and [ADR 0025](../adr/0025-scheduler-owned-executable-operation-actions.md) for Actions whose program is evaluated Edict rather than a fixed single-node profile +- **Supersedes in part:** for bridge Actions only, ADR 0023's + prepared-evaluation outcome: that a successful evaluation exposes one + normal Echo patch, that every evaluation yields a closed + `PreparedEchoOperationOutcomeV1` (`Committable` or `Obstructed`), and that + an `Obstructed` outcome never carries a patch digest. A bridge evaluation + yields an `EffectProposal` or a typed obstruction; Echo derives the patch + afterwards in the same Tick construction, and the receipt posture in + [Evidence](#evidence) replaces the closed outcome. Some obstructed postures + record the digest of a derived patch that was never committed. ADR 0023's + atomic visibility (one parent patch or one obstruction) still governs, and + every other Action keeps ADR 0023 unchanged. Each superseded passage is + marked in ADR 0023. - **Related:** [Sliced Readings and Copy-on-Write Strands](sliced-readings-and-copy-on-write-strands.md), [Generated Rule Authorship](../topics/GeneratedRules.md), [WAL](../topics/WAL.md) @@ -183,8 +195,10 @@ Rules: edge), then by its id bytes, compared lexicographically. A tie can occur only in a proposal that obstructs with `DuplicateEffectTarget`; it breaks by effect kind (`EnsureNode`, `CreateNode`, `ReplaceAtom`, `EnsureEdge`, - `ReplaceEdge`), then by the effect's canonical encoding bytes: a kind tag, - fixed-width ids, and length-prefixed type and atom bytes. Sorting never + `ReplaceEdge`), then by the effect's canonical encoding bytes. That + encoding includes every field of the effect, expected values included: a + kind tag, then each field in the order the effect lists it, with ids and + digests at fixed width and type and atom bytes length-prefixed. Sorting never removes an effect, so duplicates stay visible to the one-effect-per-slot rule. - **One effect per slot.** No two effects in one proposal may target the same @@ -226,7 +240,10 @@ Rules: `ContentIdentityConflict`; - `EnsureEdge`: the edge is absent, or present with identical endpoints and type; - - `ReplaceAtom`: the node's atom has the expected digest; + - `ReplaceAtom`: the node record exists, else `NodeMissing` (as today's + compare-and-set reports), and its atom has the expected digest, else + `PreconditionMismatch`. An orphan atom without its record therefore + never satisfies a `ReplaceAtom`; - `ReplaceEdge`: the edge exists with the expected target; - `EnsureEdge` and `ReplaceEdge`: each endpoint the edge will name (`from` and `to`, or the retargeted edge's source and the new `to`) @@ -324,7 +341,7 @@ Echo checks: range key of its source and type. The first three checks run before any patch is derived; a violation is a typed -`FootprintLawViolation` obstruction with patch tag `NotDerived`. The third can +`FootprintLawViolation` obstruction with patch tag `NotDerived`. The fourth can only run after derivation; a mismatch is a typed `FootprintMismatch` obstruction with patch tag `Derived`, recording the patch digest it compared. Neither commits a patch. Independence between @@ -521,7 +538,7 @@ Each Action's receipt entry binds, in addition to its current fields: - `NotDerived` when no patch was derived: validation refused the proposal (`DanglingReference`, `ContentIdentityConflict`, `DuplicateEffectTarget`, `FootprintLawViolation`, - `IdentityLawViolation`, or `PreconditionMismatch`), or a + `IdentityLawViolation`, `NodeMissing`, or `PreconditionMismatch`), or a dependency-scoped `BasisChanged` was decided before validation. No write-set or patch digest is manufactured; an overwrite patch that `Ensure` forbids is never hashed; @@ -539,7 +556,14 @@ Each Action's receipt entry binds, in addition to its current fields: mode, a compared-values digest over the values those dependencies were compared against at the validation position (the commit parent plus the writes of every earlier accepted Action), and, for a `BasisChanged`, the - first changed dependency in canonical order. An evaluation obstruction + first changed dependency in canonical order. A dependency descriptor is + its slot (class and id), its kind (value present, observed absent, + expected value, or endpoint existence), and its comparison mode. + Descriptors sort by slot class (node record, atom, edge, then range + key), then slot id bytes, then kind in the order listed, then + comparison mode; each is encoded as a kind tag, the slot class tag and + fixed-width id, and the comparison-mode tag, and the dependency-set + digest hashes them in that order. An evaluation obstruction runs no dependency comparison, so its `Scoped` evidence is the origin basis and validation position only; - for `NoProposal` and `Proposed`, consumed budget. @@ -630,17 +654,17 @@ The bridge keeps both and adds the outgoing-edge range keys and precondition reads to the reconstructed composition. The scoped-validity recomputation above likewise applies to every row recorded as `Scoped`. -| Outcome | Posture | Existing check | Added | -| --------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------ | ------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| Committed | `Proposed`, `Derived` | replay patch; reconstruct preparation; compare identities | effects bound; read-set digest; patch digest equals the replayed patch | -| `RejectedFootprintConflict` | `Proposed`, `Derived` | reconstruct preparation; compare identities; recompute recorded blockers | effects bound; read-set digest; blockers include range-key conflicts | -| Obstructed at validation (`DanglingReference`, `ContentIdentityConflict`, `DuplicateEffectTarget`, `FootprintLawViolation`, `IdentityLawViolation`, `PreconditionMismatch`) | `Proposed`, `NotDerived` | re-evaluate; compare pre-decision evidence | effects bound; read-set digest; obstruction kind; no write-set or patch digest present | -| Obstructed by `FootprintMismatch` (after derivation) | `Proposed`, `Derived` | re-evaluate; compare pre-decision evidence | effects bound; read-set digest; re-derived patch reproduces the mismatch | -| `BudgetExceeded`, scheduler-assigned (Action-batch limit) | `Proposed`, `Derived` | accept reconstructed preparation; verify through the batch reconstruction | effects bound; read-set digest | -| `BudgetExceeded` during evaluation, or an authored guard | `NoProposal` | re-evaluate; compare pre-decision evidence | re-evaluation, at the origin for a `Scoped` Action, again yields no proposal and the same obstruction; read-set digest | -| `BasisChanged`, whole-frontier (before evaluation, including unknown history) | `NoProposal` | admission evidence; recomputed basis posture; no re-evaluation | none; posture must be `NoProposal` with an empty read set | -| `BasisChanged`, dependency-scoped (after evaluation, before validation) | `Proposed`, `NotDerived` | none today: current programs decide this before evaluation | re-evaluate at the origin; effects bound; read-set digest; recompute the written-slot set from replay operations, the complete dependency set (witnessed reads and effect preconditions), and the changed dependency | -| Any outcome of a `NotBridge` entry | `NotBridge` | unchanged | none; no bridge field present | +| Outcome | Posture | Existing check | Added | +| ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | ------------------------ | ------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| Committed | `Proposed`, `Derived` | replay patch; reconstruct preparation; compare identities | effects bound; read-set digest; patch digest equals the replayed patch | +| `RejectedFootprintConflict` | `Proposed`, `Derived` | reconstruct preparation; compare identities; recompute recorded blockers | effects bound; read-set digest; blockers include range-key conflicts | +| Obstructed at validation (`DanglingReference`, `ContentIdentityConflict`, `DuplicateEffectTarget`, `FootprintLawViolation`, `IdentityLawViolation`, `NodeMissing`, `PreconditionMismatch`) | `Proposed`, `NotDerived` | re-evaluate; compare pre-decision evidence | effects bound; read-set digest; obstruction kind; no write-set or patch digest present | +| Obstructed by `FootprintMismatch` (after derivation) | `Proposed`, `Derived` | re-evaluate; compare pre-decision evidence | effects bound; read-set digest; re-derived patch reproduces the mismatch | +| `BudgetExceeded`, scheduler-assigned (Action-batch limit) | `Proposed`, `Derived` | accept reconstructed preparation; verify through the batch reconstruction | effects bound; read-set digest | +| `BudgetExceeded` during evaluation, or an authored guard | `NoProposal` | re-evaluate; compare pre-decision evidence | re-evaluation, at the origin for a `Scoped` Action, again yields no proposal and the same obstruction; read-set digest | +| `BasisChanged`, whole-frontier (before evaluation, including unknown history) | `NoProposal` | admission evidence; recomputed basis posture; no re-evaluation | none; posture must be `NoProposal` with an empty read set | +| `BasisChanged`, dependency-scoped (after evaluation, before validation) | `Proposed`, `NotDerived` | none today: current programs decide this before evaluation | re-evaluate at the origin; effects bound; read-set digest; recompute the written-slot set from replay operations, the complete dependency set (witnessed reads and effect preconditions), and the changed dependency | +| Any outcome of a `NotBridge` entry | `NotBridge` | unchanged | none; no bridge field present | ## Execution traces diff --git a/docs/topics/GeneratedRules.md b/docs/topics/GeneratedRules.md index dfcd3df6..913a4745 100644 --- a/docs/topics/GeneratedRules.md +++ b/docs/topics/GeneratedRules.md @@ -331,8 +331,11 @@ resource or lawpack, wherever it appears in ADR 0023 (the rope-law closure, the package binding direction and obligations, the evidence grades, Jurisdiction, and Consequences), each marked where it appears and enumerated in the "Jedit rope-law closure" banner, and its selection under "Alternatives -for Expressing the Rope Law". Every other provision of ADR 0023 continues to -govern, and its original reasoning is kept.** +for Expressing the Rope Law". For bridge Actions, +[Authored Operation Effects](../architecture/authored-operation-effects.md) +also supersedes ADR 0023's prepared-evaluation outcome, as marked there. Every +other provision of ADR 0023 continues to govern, and its original reasoning is +kept.** Jedit's persistent-rope `ReplaceRange` law is authored in Jedit-owned Edict source and compiled by Edict. It is not imported as a declarative executable From 4c809ff43c6e7c7cc3151590f751f4c94faf7111 Mon Sep 17 00:00:00 2001 From: James Ross Date: Fri, 16 Oct 2026 10:14:54 -0700 Subject: [PATCH 16/50] docs: close the seventh review round on the authored-effects bridge - Within one Tick, every check before composition runs against the commit parent. Composition reserves each candidate's complete footprint, and any overlap with an earlier accepted Action is RejectedFootprintConflict, as the within-one-tick rule requires. A later Action expecting a value an earlier one replaced is a conflict, never a PreconditionMismatch. For a non-conflicting Action, values at the validation position equal those at the commit parent. - Endpoint checks apply only to a creating EnsureEdge and to ReplaceEdge. An identical EnsureEdge is a read and checks no endpoints, matching invariant 4. - Record that the design supersedes, for bridge Actions only, ADR 0025's prepared candidate on success and its lawpack-qualified obstruction. The supersession appears in the design header, ADR 0025's header, and a banner at the clause. Co-Authored-By: Claude Opus 5.5 --- ...uler-owned-executable-operation-actions.md | 15 +++++++ .../authored-operation-effects.md | 43 +++++++++++++++---- 2 files changed, 49 insertions(+), 9 deletions(-) diff --git a/docs/adr/0025-scheduler-owned-executable-operation-actions.md b/docs/adr/0025-scheduler-owned-executable-operation-actions.md index 62307203..ebfc23cb 100644 --- a/docs/adr/0025-scheduler-owned-executable-operation-actions.md +++ b/docs/adr/0025-scheduler-owned-executable-operation-actions.md @@ -9,6 +9,12 @@ - **Superseded in part by:** the [Action validity across ticks](../topics/GeneratedRules.md#action-validity-across-ticks) decision of 2026-10-14 (#837), for the shared parent basis of Actions in one Tick +- **Superseded in part by:** + [Authored Operation Effects](../architecture/authored-operation-effects.md), + effective when [flyingrobots/echo#889](https://github.com/flyingrobots/echo/pull/889) + merges to `main`, for bridge Actions only: the prepared candidate returned + on success and the lawpack qualification of an evaluation obstruction, + marked where it appears ## Context @@ -74,6 +80,15 @@ worldline: and no parent-visible operations; - neither result mutates the parent state. +> **Superseded in part,** effective when flyingrobots/echo#889 merges: for +> bridge Actions, whose program is compiled from Edict source and binds no +> lawpack resource, an obstruction is qualified by the admitted package and +> installed-operation identities instead of a lawpack, and success returns an +> `EffectProposal` rather than a prepared candidate, as +> [Authored Operation Effects](../architecture/authored-operation-effects.md) +> defines. The original text is preserved and still governs every other +> Action. + The direct `prepare_echo_operation_v1` and `commit_prepared_echo_operation_v1` methods remain publicly reachable on `TrustedRuntimeHost` as explicitly transitional, documentation-hidden diff --git a/docs/architecture/authored-operation-effects.md b/docs/architecture/authored-operation-effects.md index 99b64c02..54715e57 100644 --- a/docs/architecture/authored-operation-effects.md +++ b/docs/architecture/authored-operation-effects.md @@ -31,6 +31,15 @@ atomic visibility (one parent patch or one obstruction) still governs, and every other Action keeps ADR 0023 unchanged. Each superseded passage is marked in ADR 0023. +- **Supersedes in part:** for bridge Actions only, ADR 0025's requirements + that a successful evaluation return one complete prepared candidate and + that an evaluation obstruction return a lawpack-qualified coordinate. A + bridge evaluation returns an `EffectProposal`. Its program is compiled + from Edict source and binds no lawpack resource, so its typed obstruction + is qualified by the admitted package and installed-operation identities + instead. ADR 0025's other requirements, + including that neither result mutates the parent, still govern. The + superseded clause is marked in ADR 0025. - **Related:** [Sliced Readings and Copy-on-Write Strands](sliced-readings-and-copy-on-write-strands.md), [Generated Rule Authorship](../topics/GeneratedRules.md), [WAL](../topics/WAL.md) @@ -245,9 +254,13 @@ Rules: `PreconditionMismatch`. An orphan atom without its record therefore never satisfies a `ReplaceAtom`; - `ReplaceEdge`: the edge exists with the expected target; - - `EnsureEdge` and `ReplaceEdge`: each endpoint the edge will name - (`from` and `to`, or the retargeted edge's source and the new `to`) - exists, unless the same proposal creates it. + - a creating `EnsureEdge` and every `ReplaceEdge`: each endpoint the + edge will name (`from` and `to`, or the retargeted edge's source and + the new `to`) exists, unless the same proposal creates it. An + `EnsureEdge` that finds an identical edge is a read; it adds and + retargets nothing, so its endpoints are not checked, as invariant 4 + and [Visibility and referential integrity](#visibility-and-referential-integrity) + limit endpoint checks to edges the proposal introduces or retargets; Every precondition slot is a read in the Action's footprint, is checked by validation, and belongs to the dependency set of a dependency-scoped Action @@ -428,9 +441,8 @@ held; this design validates only what the proposal adds or retargets. operations (see [Current implementation](#current-implementation)). `echo_operation_written_slots_v1` must therefore project operations, or verify that `out_slots` covers them exactly and treat any mismatch as - unknown. Value comparison is used only at the validation position, - against the commit parent plus the writes of earlier Actions in the - same Tick. No new versioning system is added. + unknown. Value comparison is used only at validation, as the next + bullet orders it. No new versioning system is added. - **Unknown history is decided before evaluation.** The written set depends only on the origin and the commit parent, so Echo computes it before evaluating the Action. If any Tick in the interval has no @@ -450,9 +462,22 @@ held; this design validates only what the proposal adds or retargets. names as changed. No patch is derived from a proposal whose dependencies changed, so a proposal that would also have failed validation (for example `DuplicateEffectTarget`) still has one - encodable outcome. Only when every dependency holds does validation run, - at the Action's validation position. Echo never validates a delayed - Action without its actual dependencies. + encodable outcome. Only when every dependency holds does validation run. + Echo never validates a delayed Action without its actual dependencies. + - **Within one Tick, footprints before earlier writes.** Every check + before composition, preconditions and expected values included, runs + against the Tick's commit parent, never against writes accepted + earlier in the same Tick. Composition then reserves each candidate's + complete footprint (witnessed reads, precondition reads, and write + set) in canonical order, and any overlap with an earlier accepted + Action is `RejectedFootprintConflict` with its blockers, as the + [within-one-tick rule](../topics/GeneratedRules.md#action-validity-across-ticks) + requires. A candidate that overlaps nothing depends on no slot an + earlier Action wrote, so its values at the validation position (the + commit parent plus earlier accepted writes) equal its values at the + commit parent. A later Action that expects a value an earlier Action + replaced is therefore a footprint conflict, never a + `PreconditionMismatch`. - **Budget.** Evaluation is metered as `edict_pure` already meters it. Each effect adds a fixed write cost. Exhausting any evaluation limit obstructs with `BudgetExceeded`, posture `NoProposal`, and no patch. A proposal whose From 174fb8c6f1e132ed537c511f150ed194c0f82c00 Mon Sep 17 00:00:00 2001 From: James Ross Date: Fri, 16 Oct 2026 10:23:45 -0700 Subject: [PATCH 17/50] docs: close the eighth review round on the authored-effects bridge - Same-Tick composition conflicts only on overlaps in which either Action writes the slot. Read against read never conflicts, keeping shared structure lawful. - Define the canonical encoding and order of the read-set, target-set, write-set, and dependency-set digests, including observed range targets, so live execution and recovery hash identical bytes. - An oversized proposal is representable as Proposed/Derived evidence but never committable. The open question now agrees with the budget rule. Co-Authored-By: Claude Opus 5.5 --- .../authored-operation-effects.md | 37 ++++++++++++++++--- 1 file changed, 31 insertions(+), 6 deletions(-) diff --git a/docs/architecture/authored-operation-effects.md b/docs/architecture/authored-operation-effects.md index 54715e57..2e1df6e1 100644 --- a/docs/architecture/authored-operation-effects.md +++ b/docs/architecture/authored-operation-effects.md @@ -470,12 +470,14 @@ held; this design validates only what the proposal adds or retargets. earlier in the same Tick. Composition then reserves each candidate's complete footprint (witnessed reads, precondition reads, and write set) in canonical order, and any overlap with an earlier accepted - Action is `RejectedFootprintConflict` with its blockers, as the + Action in which either of the two writes the slot is + `RejectedFootprintConflict` with its blockers, as the [within-one-tick rule](../topics/GeneratedRules.md#action-validity-across-ticks) - requires. A candidate that overlaps nothing depends on no slot an - earlier Action wrote, so its values at the validation position (the - commit parent plus earlier accepted writes) equal its values at the - commit parent. A later Action that expects a value an earlier Action + requires. Read against read never conflicts, so Actions that share + existing structure both proceed. A candidate with no such overlap + depends on no slot an earlier Action wrote, so its values at the + validation position (the commit parent plus earlier accepted writes) + equal its values at the commit parent. A later Action that expects a value an earlier Action replaced is therefore a footprint conflict, never a `PreconditionMismatch`. - **Budget.** Evaluation is metered as `edict_pure` already meters it. Each @@ -593,6 +595,26 @@ Each Action's receipt entry binds, in addition to its current fields: basis and validation position only; - for `NoProposal` and `Proposed`, consumed budget. +**Canonical set encoding.** The read-set, target-set, write-set, and +dependency-set digests each hash a set of slot entries, never a collection in +discovery order. Entries sort by slot class (node record, atom, edge, then +range key), then by id bytes compared lexicographically; a range key's id is +its source node id followed by its edge-type id. Each entry encodes as a slot +class tag and its fixed-width id or ids, followed by what that set records +for the slot: + +- read set: an outcome tag (present or absent) and, when present, the 32-byte + digest of the value read; a range read records the observed edges as a + count-prefixed list of edge id and target node id pairs, sorted by edge id; +- target set and write set: the slot alone; +- dependency set: the descriptor encoding defined under the `Scoped` evidence + above. + +Each digest is BLAKE3 over a domain tag naming the set and its version, the +entry count as a little-endian `u64`, and the entries in order. Live +execution and recovery therefore produce identical bytes whatever order an +implementation visits slots in. + The submission identity is present in every variant. An identified submission whose evaluation produced no proposal is a different evidence state from an evaluated proposal, and the receipt keeps them distinct. @@ -866,4 +888,7 @@ Each behaviour below needs a test before implementation: - Whether the footprint law is per-invocation or per-package with parameters. - Which domain-separated hash Edict source can call to derive ids. - Whether proposals larger than the Action-batch operation limit should ever - be representable. This design says no. + be committable. This design says no: such a proposal is still representable + as evidence (`BudgetExceeded`, posture `Proposed` with a `Derived` patch, as + [Basis, budget, retries](#basis-budget-retries) states), but it never + commits and is never split across Ticks. From 64c3eb7fd57d81b87d885e5e4f0be572be00fea2 Mon Sep 17 00:00:00 2001 From: James Ross Date: Fri, 16 Oct 2026 10:33:27 -0700 Subject: [PATCH 18/50] docs: close the ninth review round on the authored-effects bridge - Every node and edge in an effect is instance-scoped (NodeKey, EdgeKey). Endpoints are local ids in the edge's own warp, as GraphStore holds one warp. Slots, the sort order and every digest encode the warp id, so equal local ids in different warps never collide. - An effect's atom is an AtomPayload { type_id, bytes }, separate from the node type. Two-plane law L3 puts type_id in every encoding and digest. ReplaceAtom replaces both, and its expected digest covers both. - Define the compared-values digest: one entry per dependency in canonical descriptor order, with a presence tag and the value digest. Co-Authored-By: Claude Opus 5.5 --- .../authored-operation-effects.md | 102 +++++++++++------- 1 file changed, 63 insertions(+), 39 deletions(-) diff --git a/docs/architecture/authored-operation-effects.md b/docs/architecture/authored-operation-effects.md index 2e1df6e1..b18d27b2 100644 --- a/docs/architecture/authored-operation-effects.md +++ b/docs/architecture/authored-operation-effects.md @@ -192,6 +192,17 @@ EffectProposal { } ``` +Every `node` is an instance-scoped `NodeKey { warp_id, local_id }` and every +`edge` an `EdgeKey`. An edge's `from`, `to`, and `expectedTo` are local node +ids in the edge's own warp, because a `GraphStore` holds one warp and an +`EdgeRecord`'s endpoints are local `NodeId`s within it. `type` is the node +record's type. `atom` is an `AtomPayload { type_id, bytes }`: its `type_id` +is a separate proposition from the node type, and the +[two-plane law](../invariants/warp-two-plane-law.md) (L3) requires it in +every encoding and digest that includes the bytes. `ReplaceAtom` replaces +both the payload type and the bytes, and its `expectedAtomDigest` covers +both. + Rules: - **Effects are the only writes.** The proposal contains no prepared patch. @@ -201,13 +212,16 @@ Rules: has a primary slot: the node record for `EnsureNode` and `CreateNode`, the atom for `ReplaceAtom`, and the edge for `EnsureEdge` and `ReplaceEdge`. Effects sort by the primary slot's class (node record, then atom, then - edge), then by its id bytes, compared lexicographically. A tie can occur + edge), then by its warp id bytes, then its local id bytes, compared + lexicographically. A tie can occur only in a proposal that obstructs with `DuplicateEffectTarget`; it breaks by effect kind (`EnsureNode`, `CreateNode`, `ReplaceAtom`, `EnsureEdge`, `ReplaceEdge`), then by the effect's canonical encoding bytes. That encoding includes every field of the effect, expected values included: a - kind tag, then each field in the order the effect lists it, with ids and - digests at fixed width and type and atom bytes length-prefixed. Sorting never + kind tag, then each field in the order the effect lists it, with each + instance-scoped key as its warp id then its local id, ids, type ids, and + digests at fixed width, and atom bytes length-prefixed after the atom's + `type_id`. Sorting never removes an effect, so duplicates stay visible to the one-effect-per-slot rule. - **One effect per slot.** No two effects in one proposal may target the same @@ -224,7 +238,10 @@ Rules: - **Slots.** One canonical slot model serves the target set, the write set, the read set, and the footprint. A slot is a node record, a node's atom (its alpha attachment), an edge, or an outgoing-edge range key - `(source node, edge type)` (see + `(source node, edge type)`. Every slot is instance-scoped: a node record or + atom by its `NodeKey`, an edge by its `EdgeKey`, and a range key by its + source `NodeKey`, so equal local ids in different warps are different + slots (see [Witnessed reads](#witnessed-reads-over-a-bounded-slice)). - **Target set.** The proposal's target set is the canonical set of slots its effects target: for `EnsureNode` and `CreateNode` the node record and its @@ -244,9 +261,9 @@ Rules: exists, including an atom without a node record, the effect obstructs with `PreconditionMismatch`, as today's create-if-absent does; - `EnsureNode`: the node record and its atom are both absent, or both - present with identical type and atom. Any other state, including a - record without an atom or an atom without a record, is - `ContentIdentityConflict`; + present with identical node type, atom `type_id`, and atom bytes. Any + other state, including a record without an atom or an atom without a + record, is `ContentIdentityConflict`; - `EnsureEdge`: the edge is absent, or present with identical endpoints and type; - `ReplaceAtom`: the node record exists, else `NodeMissing` (as today's @@ -464,33 +481,33 @@ held; this design validates only what the proposal adds or retargets. failed validation (for example `DuplicateEffectTarget`) still has one encodable outcome. Only when every dependency holds does validation run. Echo never validates a delayed Action without its actual dependencies. - - **Within one Tick, footprints before earlier writes.** Every check - before composition, preconditions and expected values included, runs - against the Tick's commit parent, never against writes accepted - earlier in the same Tick. Composition then reserves each candidate's - complete footprint (witnessed reads, precondition reads, and write - set) in canonical order, and any overlap with an earlier accepted - Action in which either of the two writes the slot is - `RejectedFootprintConflict` with its blockers, as the + - **Within one Tick, footprints before earlier writes.** Every check before + composition, preconditions and expected values included, runs against the + Tick's commit parent, never against writes accepted earlier in the same + Tick. Composition then reserves each candidate's complete footprint + (witnessed reads, precondition reads, and write set) in canonical order, + and any overlap with an earlier accepted Action in which either of the two + writes the slot is `RejectedFootprintConflict` with its blockers, as the [within-one-tick rule](../topics/GeneratedRules.md#action-validity-across-ticks) - requires. Read against read never conflicts, so Actions that share - existing structure both proceed. A candidate with no such overlap - depends on no slot an earlier Action wrote, so its values at the - validation position (the commit parent plus earlier accepted writes) - equal its values at the commit parent. A later Action that expects a value an earlier Action + requires. + Read against read never conflicts, so Actions that share existing + structure both proceed. A candidate with no such overlap depends on no + slot an earlier Action wrote, so its values at the validation position + (the commit parent plus earlier accepted writes) equal its values at the + commit parent. A later Action that expects a value an earlier Action replaced is therefore a footprint conflict, never a `PreconditionMismatch`. - **Budget.** Evaluation is metered as `edict_pure` already meters it. Each - effect adds a fixed write cost. Exhausting any evaluation limit obstructs - with `BudgetExceeded`, posture `NoProposal`, and no patch. A proposal whose - derived patch alone exceeds the Action-batch operation limit + effect adds a fixed write cost. Exhausting any evaluation limit obstructs with + `BudgetExceeded`, posture `NoProposal`, and no patch. A proposal whose derived + patch alone exceeds the Action-batch operation limit (`ACTION_BATCH_OPERATION_LIMIT_V1`) also obstructs with `BudgetExceeded` and no committed patch, rather than splitting across Ticks, but its posture is `Proposed` with a derived patch: the proposal exists, so its identity, - target-set, write-set, and patch digests are recorded, not discarded. A proposal that fits alone - but not beside the Actions already accepted into the batch is handled by the - existing batch rule, which obstructs that candidate in this batch, likewise - with posture `Proposed`. + target-set, write-set, and patch digests are recorded, not discarded. A + proposal that fits alone but not beside the Actions already accepted into the + batch is handled by the existing batch rule, which obstructs that candidate in + this batch, likewise with posture `Proposed`. - **Retries.** A decided outcome is final. An Action is identified by its submission (see Identity and existing contracts); once it is decided (committed, obstructed, or `RejectedFootprintConflict`), its outcome, @@ -587,28 +604,35 @@ Each Action's receipt entry binds, in addition to its current fields: its slot (class and id), its kind (value present, observed absent, expected value, or endpoint existence), and its comparison mode. Descriptors sort by slot class (node record, atom, edge, then range - key), then slot id bytes, then kind in the order listed, then - comparison mode; each is encoded as a kind tag, the slot class tag and - fixed-width id, and the comparison-mode tag, and the dependency-set + key), then slot identity bytes as the canonical set encoding below + defines them, then kind in the order listed, then comparison mode; + each is encoded as a kind tag, the slot encoding, and the + comparison-mode tag, and the dependency-set digest hashes them in that order. An evaluation obstruction runs no dependency comparison, so its `Scoped` evidence is the origin basis and validation position only; - for `NoProposal` and `Proposed`, consumed budget. -**Canonical set encoding.** The read-set, target-set, write-set, and -dependency-set digests each hash a set of slot entries, never a collection in -discovery order. Entries sort by slot class (node record, atom, edge, then -range key), then by id bytes compared lexicographically; a range key's id is -its source node id followed by its edge-type id. Each entry encodes as a slot -class tag and its fixed-width id or ids, followed by what that set records -for the slot: +**Canonical set encoding.** The read-set, target-set, write-set, +dependency-set, and compared-values digests each hash a set of slot entries, +never a collection in discovery order. A slot encodes as its class tag followed +by its identity: the warp id and local id of its `NodeKey` or `EdgeKey`, and +for a range key the source `NodeKey` followed by the edge-type id, all at fixed +width. Entries sort by slot class (node record, atom, edge, then range key), +then by those identity bytes compared lexicographically. Each entry is the slot +encoding followed by what that set records for the slot: - read set: an outcome tag (present or absent) and, when present, the 32-byte - digest of the value read; a range read records the observed edges as a + digest of the value read (for an atom, over its `type_id` and bytes); a + range read records the observed edges as a count-prefixed list of edge id and target node id pairs, sorted by edge id; - target set and write set: the slot alone; - dependency set: the descriptor encoding defined under the `Scoped` evidence - above. + above; +- compared values: one entry per dependency, in canonical descriptor order, + each the descriptor encoding, a presence tag (present or absent at the + validation position), and, when present, the 32-byte digest of the value + compared, encoded as for the read set. Each digest is BLAKE3 over a domain tag naming the set and its version, the entry count as a little-endian `u64`, and the entries in order. Live From 93972b5c6c5a8d7ca984451931c4276e276c5afb Mon Sep 17 00:00:00 2001 From: James Ross Date: Fri, 16 Oct 2026 10:42:31 -0700 Subject: [PATCH 19/50] docs: close the tenth review round on the authored-effects bridge - Visibility checks endpoints only for a creating EnsureEdge and for ReplaceEdge, matching the precondition rule. - Define value digests for node records, atoms (type_id and bytes; the expectedAtomDigest) and edges, plus the domain tags of every set digest and the slot-class tags. - Enumerate the dependency kinds and the two supported comparison modes (ValueDigest, Presence), with their tags, order and encoding. - Specify the complete proposal-identity encoding: domain, Action identities, basis identity, read-set digest, counted effects with kind tags, result, and cost. - Node, atom and edge types an effect writes must be admitted by the package's schema, else SchemaViolation (Proposed, NotDerived). Adds schema and encoding-vector test rows. Co-Authored-By: Claude Opus 5.5 --- .../authored-operation-effects.md | 112 +++++++++++++----- 1 file changed, 80 insertions(+), 32 deletions(-) diff --git a/docs/architecture/authored-operation-effects.md b/docs/architecture/authored-operation-effects.md index b18d27b2..253d223a 100644 --- a/docs/architecture/authored-operation-effects.md +++ b/docs/architecture/authored-operation-effects.md @@ -357,6 +357,12 @@ The package declares a footprint law: the node types, edge types, and maximum counts an Action may read and write, relative to its invocation. At validation Echo checks: +- every node type, atom `type_id`, and edge type an effect writes is admitted + by the package's typed graph and attachment schema, which ADR 0023 requires + the package to bind, including which atom types a node type may carry. A + violation is a typed `SchemaViolation` obstruction, posture `Proposed`, + `NotDerived`; + - the witnessed read set, the precondition slots, and the proposal's target set lie inside the law; - counts do not exceed the declared maxima; @@ -399,7 +405,8 @@ node in its basis, so its `Ensure` is a read and does not conflict. Validation enforces invariant 4 before a proposal can join a Tick, through the endpoint preconditions defined in [EffectProposal](#effectproposal): -- every `EnsureEdge` and `ReplaceEdge` endpoint exists in the basis or is +- every creating `EnsureEdge` and every `ReplaceEdge` endpoint exists in the + basis or is created by an `EnsureNode` or `CreateNode` in the same proposal; - every edge a `ReplaceEdge` retargets exists in the basis with the expected target. @@ -582,7 +589,8 @@ Each Action's receipt entry binds, in addition to its current fields: - `NotDerived` when no patch was derived: validation refused the proposal (`DanglingReference`, `ContentIdentityConflict`, `DuplicateEffectTarget`, `FootprintLawViolation`, - `IdentityLawViolation`, `NodeMissing`, or `PreconditionMismatch`), or a + `IdentityLawViolation`, `SchemaViolation`, `NodeMissing`, or + `PreconditionMismatch`), or a dependency-scoped `BasisChanged` was decided before validation. No write-set or patch digest is manufactured; an overwrite patch that `Ensure` forbids is never hashed; @@ -600,17 +608,25 @@ Each Action's receipt entry binds, in addition to its current fields: mode, a compared-values digest over the values those dependencies were compared against at the validation position (the commit parent plus the writes of every earlier accepted Action), and, for a `BasisChanged`, the - first changed dependency in canonical order. A dependency descriptor is - its slot (class and id), its kind (value present, observed absent, - expected value, or endpoint existence), and its comparison mode. - Descriptors sort by slot class (node record, atom, edge, then range - key), then slot identity bytes as the canonical set encoding below - defines them, then kind in the order listed, then comparison mode; - each is encoded as a kind tag, the slot encoding, and the - comparison-mode tag, and the dependency-set - digest hashes them in that order. An evaluation obstruction + first changed dependency in canonical order. An evaluation obstruction runs no dependency comparison, so its `Scoped` evidence is the origin - basis and validation position only; + basis and validation position only. A dependency descriptor is its + slot, its kind, and its comparison mode, each a one-byte tag: + - kind: value present `0`, observed absent `1`, expected value `2`, + endpoint existence `3`; + - comparison mode: `ValueDigest` `0`, which compares the slot's value + digest (value present, expected value), and `Presence` `1`, which + compares only presence (observed absent, endpoint existence). These + are the two modes the Action validity decision supports; + revision-sensitive and identity-sensitive dependencies are refused + at admission and have no tag. + - order and encoding: a scoped Action reads no edge ranges (an edge + read keeps the whole-frontier rule), so its dependency slots are + node records, atoms, and edges. Descriptors sort by slot encoding + (defined in the canonical set encoding below), then kind tag, then + mode tag; each encodes as kind tag, slot encoding, mode tag; and + the dependency-set digest hashes them in that order; + - for `NoProposal` and `Proposed`, consumed budget. **Canonical set encoding.** The read-set, target-set, write-set, @@ -622,8 +638,8 @@ width. Entries sort by slot class (node record, atom, edge, then range key), then by those identity bytes compared lexicographically. Each entry is the slot encoding followed by what that set records for the slot: -- read set: an outcome tag (present or absent) and, when present, the 32-byte - digest of the value read (for an atom, over its `type_id` and bytes); a +- read set: an outcome tag (`0` present, `1` absent) and, when present, the + 32-byte value digest defined below; a range read records the observed edges as a count-prefixed list of edge id and target node id pairs, sorted by edge id; - target set and write set: the slot alone; @@ -639,18 +655,48 @@ entry count as a little-endian `u64`, and the entries in order. Live execution and recovery therefore produce identical bytes whatever order an implementation visits slots in. +Slot class tags are node record `0`, atom `1`, edge `2`, and range key `3`. +Ids, type ids, and warp ids are 32 bytes each. A present slot's value digest +is BLAKE3 over a class-specific domain tag and its canonical fields: + +- node record: `echo.bridge.node-value.v1\0`, then the node type id; +- atom: `echo.bridge.atom-value.v1\0`, then the `type_id`, the byte length as + a little-endian `u64`, and the bytes. `expectedAtomDigest` is this digest; +- edge: `echo.bridge.edge-value.v1\0`, then the edge type id and the local + `from` and `to` node ids. + +The set digests use the domain tags `echo.bridge.read-set.v1\0`, +`echo.bridge.target-set.v1\0`, `echo.bridge.write-set.v1\0`, +`echo.bridge.dependency-set.v1\0`, and `echo.bridge.compared-values.v1\0`. + The submission identity is present in every variant. An identified submission whose evaluation produced no proposal is a different evidence state from an evaluated proposal, and the receipt keeps them distinct. +**Proposal encoding.** The proposal identity is BLAKE3 over +`echo.bridge.effect-proposal.v1\0` followed by: + +1. the Action: its installed-operation identity and its canonical invocation + digest, as ADR 0023 binds them; +2. the evaluation basis: the `EchoOperationEvaluationBasisV1` identity; +3. the read-set digest, which stands for `readSet`; +4. the effect count as a little-endian `u64`, then each effect's canonical + encoding (see [EffectProposal](#effectproposal)) in canonical order; +5. the result: its length as a little-endian `u64`, then its bytes; +6. the cost: steps, allocated bytes, read bytes, and write bytes, each a + little-endian `u64`. + +Effect kind tags are `EnsureNode` `0`, `CreateNode` `1`, `ReplaceAtom` `2`, +`EnsureEdge` `3`, and `ReplaceEdge` `4`. + The proposal identity is `H(Action, evaluation basis, canonical -EffectProposal)`, and the canonical `EffectProposal` contains every effect in -canonical order, so the identity binds the exact evaluated effects -(invariant 5). The target-set digest and, when a patch was derived, the -write-set and patch digests are derived from those same effects. Proposals -are not retained apart from their Tick; the binding is checked by re-deriving -the proposal, as the recovery matrix below requires for every class that -carries one. +EffectProposal)` in this encoding, and the canonical `EffectProposal` contains +every effect in canonical order, so the identity binds the exact evaluated +effects (invariant 5). The target-set digest and, when a patch was derived, the +write-set and patch digests are derived from those same effects. Proposals are +not retained apart from their Tick; the binding is checked by re-deriving the +proposal, as the recovery matrix below requires for every class that carries +one. Today every `BasisChanged` is decided before evaluation runs (`echo_operation.rs`). For bridge Actions that remains true only of the @@ -725,17 +771,17 @@ The bridge keeps both and adds the outgoing-edge range keys and precondition reads to the reconstructed composition. The scoped-validity recomputation above likewise applies to every row recorded as `Scoped`. -| Outcome | Posture | Existing check | Added | -| ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | ------------------------ | ------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| Committed | `Proposed`, `Derived` | replay patch; reconstruct preparation; compare identities | effects bound; read-set digest; patch digest equals the replayed patch | -| `RejectedFootprintConflict` | `Proposed`, `Derived` | reconstruct preparation; compare identities; recompute recorded blockers | effects bound; read-set digest; blockers include range-key conflicts | -| Obstructed at validation (`DanglingReference`, `ContentIdentityConflict`, `DuplicateEffectTarget`, `FootprintLawViolation`, `IdentityLawViolation`, `NodeMissing`, `PreconditionMismatch`) | `Proposed`, `NotDerived` | re-evaluate; compare pre-decision evidence | effects bound; read-set digest; obstruction kind; no write-set or patch digest present | -| Obstructed by `FootprintMismatch` (after derivation) | `Proposed`, `Derived` | re-evaluate; compare pre-decision evidence | effects bound; read-set digest; re-derived patch reproduces the mismatch | -| `BudgetExceeded`, scheduler-assigned (Action-batch limit) | `Proposed`, `Derived` | accept reconstructed preparation; verify through the batch reconstruction | effects bound; read-set digest | -| `BudgetExceeded` during evaluation, or an authored guard | `NoProposal` | re-evaluate; compare pre-decision evidence | re-evaluation, at the origin for a `Scoped` Action, again yields no proposal and the same obstruction; read-set digest | -| `BasisChanged`, whole-frontier (before evaluation, including unknown history) | `NoProposal` | admission evidence; recomputed basis posture; no re-evaluation | none; posture must be `NoProposal` with an empty read set | -| `BasisChanged`, dependency-scoped (after evaluation, before validation) | `Proposed`, `NotDerived` | none today: current programs decide this before evaluation | re-evaluate at the origin; effects bound; read-set digest; recompute the written-slot set from replay operations, the complete dependency set (witnessed reads and effect preconditions), and the changed dependency | -| Any outcome of a `NotBridge` entry | `NotBridge` | unchanged | none; no bridge field present | +| Outcome | Posture | Existing check | Added | +| ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------ | ------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| Committed | `Proposed`, `Derived` | replay patch; reconstruct preparation; compare identities | effects bound; read-set digest; patch digest equals the replayed patch | +| `RejectedFootprintConflict` | `Proposed`, `Derived` | reconstruct preparation; compare identities; recompute recorded blockers | effects bound; read-set digest; blockers include range-key conflicts | +| Obstructed at validation (`DanglingReference`, `ContentIdentityConflict`, `DuplicateEffectTarget`, `FootprintLawViolation`, `IdentityLawViolation`, `SchemaViolation`, `NodeMissing`, `PreconditionMismatch`) | `Proposed`, `NotDerived` | re-evaluate; compare pre-decision evidence | effects bound; read-set digest; obstruction kind; no write-set or patch digest present | +| Obstructed by `FootprintMismatch` (after derivation) | `Proposed`, `Derived` | re-evaluate; compare pre-decision evidence | effects bound; read-set digest; re-derived patch reproduces the mismatch | +| `BudgetExceeded`, scheduler-assigned (Action-batch limit) | `Proposed`, `Derived` | accept reconstructed preparation; verify through the batch reconstruction | effects bound; read-set digest | +| `BudgetExceeded` during evaluation, or an authored guard | `NoProposal` | re-evaluate; compare pre-decision evidence | re-evaluation, at the origin for a `Scoped` Action, again yields no proposal and the same obstruction; read-set digest | +| `BasisChanged`, whole-frontier (before evaluation, including unknown history) | `NoProposal` | admission evidence; recomputed basis posture; no re-evaluation | none; posture must be `NoProposal` with an empty read set | +| `BasisChanged`, dependency-scoped (after evaluation, before validation) | `Proposed`, `NotDerived` | none today: current programs decide this before evaluation | re-evaluate at the origin; effects bound; read-set digest; recompute the written-slot set from replay operations, the complete dependency set (witnessed reads and effect preconditions), and the changed dependency | +| Any outcome of a `NotBridge` entry | `NotBridge` | unchanged | none; no bridge field present | ## Execution traces @@ -876,9 +922,11 @@ Each behaviour below needs a test before implementation: | Budget | Exhausting steps or bytes obstructs with `BudgetExceeded` and `NoProposal`; a proposal whose patch alone exceeds the Action-batch operation limit obstructs with `BudgetExceeded`, posture `Proposed` with its digests, and no committed patch. | | Determinism | Same package, invocation, and evaluation basis give byte-identical proposals, proposal identities, read-set and write-set digests, and derived patches, whether the Action is evaluated alone or beside other Actions. Receipts and composite patches are compared only for identical Tick contexts. | | Canonical order | Emitting the same effects in different orders yields the same canonical `EffectProposal` bytes and proposal identity. | +| Encoding vectors | Fixed byte vectors for the proposal encoding, every value digest, and every set digest, including a descriptor list with both comparison modes, reproduce the specified bytes. | | Canonical slots | Differently expressed effects on one slot (`EnsureEdge` and `ReplaceEdge` of one edge id; `CreateNode` and `ReplaceAtom` of one node) obstruct with `DuplicateEffectTarget`. | | Write set | The derived patch's actual footprint equals the write set, including the atom slots of created nodes and the range key of every created or retargeted edge, so a committed `EnsureEdge` and `ReplaceEdge` pass the footprint check. | | Identity law | A proposed node or edge id that does not match the package's identity law obstructs with `IdentityLawViolation`, posture `Proposed`, `NotDerived`, and recovery reproduces it. | +| Schema | An effect whose node type, atom `type_id`, or edge type the package's schema does not admit obstructs with `SchemaViolation`, posture `Proposed`, `NotDerived`, and recovery reproduces it. | | Footprint stages | A target outside the footprint law obstructs with `FootprintLawViolation` and `NotDerived`, and so does a `ReplaceEdge` whose basis edge's range key lies outside the law; a derived patch whose footprint differs from the write set obstructs with `FootprintMismatch` and `Derived`. | | Restored value (ABA) | A dependency written A to B and back to A between origin and commit parent is found through the written-slot set and obstructs with `BasisChanged`, including when the intervening patch under-declared its `out_slots`; with one replay patch in the interval missing, the Action obstructs before evaluation as a whole-frontier `BasisChanged`, posture `NoProposal`, with an empty read set. | | Scoped preconditions | A dependency-scoped Action whose `ReplaceAtom` expected value, or one of whose edge endpoints, changed and was restored between its origin and the commit parent obstructs with `BasisChanged`, though the program never read that slot; recovery reproduces the outcome and its changed dependency. | From fba27a21ca90bdccccccba62fdf9f91de36b56b3 Mon Sep 17 00:00:00 2001 From: James Ross Date: Fri, 16 Oct 2026 10:52:22 -0700 Subject: [PATCH 20/50] docs: close the eleventh review round on the authored-effects bridge - Generalize the atom slot class to attachments (AttachmentKey: owner and plane), since a node's alpha slot holds either an atom or a portal. For effects and reads in a descended warp, every portal on the root-to-target chain is a read, a footprint entry and a ValueDigest dependency, as GeneratedRules requires for descended targets. Effects never write a portal. Attachment value digests distinguish an atom from a portal. - Recovery repeats live execution's order for scoped Actions: history check, then pre-composition checks at the commit parent, then composition. A RejectedFootprintConflict is never revalidated at its validation position. - Reindex the footprint-check summary: schema, law x3, then the post-derivation equality. - The conflict trace's rejected Action has a Derived but uncommitted patch. - The nested range-list count is a little-endian u64. - EnsureEdge's type is the edge record's type, not a node type. - Add portal-chain and scoped-conflict-recovery test rows. Co-Authored-By: Claude Opus 5.5 --- .../authored-operation-effects.md | 110 +++++++++++------- 1 file changed, 70 insertions(+), 40 deletions(-) diff --git a/docs/architecture/authored-operation-effects.md b/docs/architecture/authored-operation-effects.md index 253d223a..b74cabf0 100644 --- a/docs/architecture/authored-operation-effects.md +++ b/docs/architecture/authored-operation-effects.md @@ -195,13 +195,15 @@ EffectProposal { Every `node` is an instance-scoped `NodeKey { warp_id, local_id }` and every `edge` an `EdgeKey`. An edge's `from`, `to`, and `expectedTo` are local node ids in the edge's own warp, because a `GraphStore` holds one warp and an -`EdgeRecord`'s endpoints are local `NodeId`s within it. `type` is the node -record's type. `atom` is an `AtomPayload { type_id, bytes }`: its `type_id` -is a separate proposition from the node type, and the -[two-plane law](../invariants/warp-two-plane-law.md) (L3) requires it in -every encoding and digest that includes the bytes. `ReplaceAtom` replaces -both the payload type and the bytes, and its `expectedAtomDigest` covers -both. +`EdgeRecord`'s endpoints are local `NodeId`s within it. For `EnsureNode` and +`CreateNode`, `type` is the node record's type; for `EnsureEdge` it is the edge +record's type (`EdgeRecord.ty`), validated against the edge types the schema +admits, never against node types. `atom` is an +`AtomPayload { type_id, bytes }`: its `type_id` is a separate proposition from +the node type, and the +[two-plane law](../invariants/warp-two-plane-law.md) (L3) requires it in every +encoding and digest that includes the bytes. `ReplaceAtom` replaces both the +payload type and the bytes, and its `expectedAtomDigest` covers both. Rules: @@ -211,7 +213,7 @@ Rules: in which evaluation produced them never reaches the identity. Each effect has a primary slot: the node record for `EnsureNode` and `CreateNode`, the atom for `ReplaceAtom`, and the edge for `EnsureEdge` and `ReplaceEdge`. - Effects sort by the primary slot's class (node record, then atom, then + Effects sort by the primary slot's class (node record, then attachment, then edge), then by its warp id bytes, then its local id bytes, compared lexicographically. A tie can occur only in a proposal that obstructs with `DuplicateEffectTarget`; it breaks @@ -236,13 +238,26 @@ Rules: keep only the last operation per key and silently drop effects the proposal identity names. - **Slots.** One canonical slot model serves the target set, the write set, - the read set, and the footprint. A slot is a node record, a node's atom - (its alpha attachment), an edge, or an outgoing-edge range key - `(source node, edge type)`. Every slot is instance-scoped: a node record or - atom by its `NodeKey`, an edge by its `EdgeKey`, and a range key by its - source `NodeKey`, so equal local ids in different warps are different - slots (see - [Witnessed reads](#witnessed-reads-over-a-bounded-slice)). + the read set, and the footprint. A slot is a node record, an attachment, an + edge, or an outgoing-edge range key `(source node, edge type)` (see + [Witnessed reads](#witnessed-reads-over-a-bounded-slice)). An attachment + slot is an `AttachmentKey`: an owner node or edge and a plane. A node's + atom is its alpha attachment. The same slot can instead hold a portal + (`AttachmentValue::Descend`), so a write of an atom and a read of a portal + on one node meet in one slot and conflict. Every slot is instance-scoped: + node records and node attachments by `NodeKey`, edges and edge attachments + by `EdgeKey`, and a range key by its source `NodeKey`, so equal local ids + in different warps are different slots. +- **Portal chain.** When an effect or a read names a key in a descended + warp, the read set, the footprint, and a scoped Action's dependency set + include every portal attachment on the validated root-to-target + reachability chain, as + [Generated Rule Authorship](../topics/GeneratedRules.md) requires for + descended targets and as Echo's existing descent check already reads + them. Each is a `ValueDigest` dependency on its `Descend` target. A + co-scheduled write to a portal therefore conflicts, and a portal that + changed between origin and commit parent, even if restored, obstructs with + `BasisChanged`. Effects never write a portal. - **Target set.** The proposal's target set is the canonical set of slots its effects target: for `EnsureNode` and `CreateNode` the node record and its atom, for `ReplaceAtom` the atom, and for `EnsureEdge` and `ReplaceEdge` the @@ -376,14 +391,14 @@ Echo checks: set writes the node's atom, and each edge upsert writes the edge and the range key of its source and type. -The first three checks run before any patch is derived; a violation is a typed -`FootprintLawViolation` obstruction with patch tag `NotDerived`. The fourth can -only run after derivation; a mismatch is a typed `FootprintMismatch` -obstruction with patch tag `Derived`, recording the patch digest it compared. -Neither commits a patch. Independence between -Actions in one Tick uses the witnessed footprints, including outgoing-edge -range keys, under the existing rule: write against read or write conflicts, -read against read does not. +The first check yields a typed `SchemaViolation` obstruction; the second, +third, and fourth yield a typed `FootprintLawViolation`. All four run before +any patch is derived, with patch tag `NotDerived`. The fifth can only run after +derivation; a mismatch is a typed `FootprintMismatch` obstruction with patch +tag `Derived`, recording the patch digest it compared. Neither commits a patch. +Independence between Actions in one Tick uses the witnessed footprints, +including outgoing-edge range keys, under the existing rule: write against read +or write conflicts, read against read does not. Reading a large shared subtree costs footprint entries but never conflicts. @@ -622,7 +637,8 @@ Each Action's receipt entry binds, in addition to its current fields: at admission and have no tag. - order and encoding: a scoped Action reads no edge ranges (an edge read keeps the whole-frontier rule), so its dependency slots are - node records, atoms, and edges. Descriptors sort by slot encoding + node records, attachments (atoms and portals), and edges. + Descriptors sort by slot encoding (defined in the canonical set encoding below), then kind tag, then mode tag; each encodes as kind tag, slot encoding, mode tag; and the dependency-set digest hashes them in that order; @@ -632,16 +648,18 @@ Each Action's receipt entry binds, in addition to its current fields: **Canonical set encoding.** The read-set, target-set, write-set, dependency-set, and compared-values digests each hash a set of slot entries, never a collection in discovery order. A slot encodes as its class tag followed -by its identity: the warp id and local id of its `NodeKey` or `EdgeKey`, and -for a range key the source `NodeKey` followed by the edge-type id, all at fixed -width. Entries sort by slot class (node record, atom, edge, then range key), -then by those identity bytes compared lexicographically. Each entry is the slot -encoding followed by what that set records for the slot: +by its identity, all at fixed width: for a node record, its `NodeKey` (warp id, +then local id); for an edge, its `EdgeKey`; for an attachment, an owner tag +(`0` node, `1` edge), the owner's key, and a plane tag (`0` alpha, `1` beta); +for a range key, the source `NodeKey` followed by the edge-type id. Entries +sort by slot class tag, then by those identity bytes compared +lexicographically. Each entry is the slot encoding followed by what that set +records for the slot: - read set: an outcome tag (`0` present, `1` absent) and, when present, the - 32-byte value digest defined below; a - range read records the observed edges as a - count-prefixed list of edge id and target node id pairs, sorted by edge id; + 32-byte value digest defined below; a range read records the observed edges + as a list of edge id and target node id pairs, sorted by edge id and + preceded by its count as a little-endian `u64`; - target set and write set: the slot alone; - dependency set: the descriptor encoding defined under the `Scoped` evidence above; @@ -655,13 +673,16 @@ entry count as a little-endian `u64`, and the entries in order. Live execution and recovery therefore produce identical bytes whatever order an implementation visits slots in. -Slot class tags are node record `0`, atom `1`, edge `2`, and range key `3`. +Slot class tags are node record `0`, attachment `1`, edge `2`, and range key +`3`. Ids, type ids, and warp ids are 32 bytes each. A present slot's value digest is BLAKE3 over a class-specific domain tag and its canonical fields: - node record: `echo.bridge.node-value.v1\0`, then the node type id; -- atom: `echo.bridge.atom-value.v1\0`, then the `type_id`, the byte length as - a little-endian `u64`, and the bytes. `expectedAtomDigest` is this digest; +- attachment: `echo.bridge.attachment-value.v1\0`, then for an atom tag `0`, + its `type_id`, its byte length as a little-endian `u64`, and its bytes, or + for a portal tag `1` and its target warp id. `expectedAtomDigest` is this + digest of the expected atom; - edge: `echo.bridge.edge-value.v1\0`, then the edge type id and the local `from` and `to` node ids. @@ -749,9 +770,15 @@ new evidence. Verification is not re-admission. - An Action recorded as `Scoped` follows [Action validity across ticks](../topics/GeneratedRules.md#action-validity-across-ticks): for every `Proposed` outcome, recovery recomputes its complete dependency - set (every witnessed read and every effect precondition), the written-slot - set from the replay operations, and its validation at the recorded - position from retained history, and refuses a history whose dependency-set + set (every witnessed read and every effect precondition) and the + written-slot set from the replay operations. It then repeats what live + execution did, in the same order: the history check, the pre-composition + checks against the commit parent, and the batch composition. Only for an + accepted Action does it compare values at the recorded validation + position, where they equal the commit parent's. A + `RejectedFootprintConflict` is verified through its preparation against + the commit parent and its recomputed blockers, never by validating it at + its validation position. Recovery refuses a history whose dependency-set digest, compared-values digest, changed dependency, or result differs. For an evaluation obstruction it re-evaluates at the origin. - An undecided Action stays the same pending submission and is evaluated when @@ -884,8 +911,9 @@ propose `ReplaceEdge B -canonicalHead-> expected H1`. 1. Each evaluates and validates on its own. 2. Their write sets overlap on `B.canonicalHead`. In deterministic order the - first joins the Tick; the second gets `RejectedFootprintConflict`, with no - patch. + first joins the Tick; the second gets `RejectedFootprintConflict`. Its + posture is `Proposed` with a `Derived` patch, whose write-set and patch + digests are recorded, but no patch of it is committed. 3. The second Action's `RejectedFootprintConflict` is its final outcome. If the application submits the edit again, that is a new submission with its own identity; evaluated at `t+1`, its basis shows `H2`, its guard fails, and @@ -930,6 +958,7 @@ Each behaviour below needs a test before implementation: | Footprint stages | A target outside the footprint law obstructs with `FootprintLawViolation` and `NotDerived`, and so does a `ReplaceEdge` whose basis edge's range key lies outside the law; a derived patch whose footprint differs from the write set obstructs with `FootprintMismatch` and `Derived`. | | Restored value (ABA) | A dependency written A to B and back to A between origin and commit parent is found through the written-slot set and obstructs with `BasisChanged`, including when the intervening patch under-declared its `out_slots`; with one replay patch in the interval missing, the Action obstructs before evaluation as a whole-frontier `BasisChanged`, posture `NoProposal`, with an empty read set. | | Scoped preconditions | A dependency-scoped Action whose `ReplaceAtom` expected value, or one of whose edge endpoints, changed and was restored between its origin and the commit parent obstructs with `BasisChanged`, though the program never read that slot; recovery reproduces the outcome and its changed dependency. | +| Scoped conflict recovery | A scoped Action rejected because an earlier Action in its Tick replaced a value it expects recovers as `RejectedFootprintConflict`, not `PreconditionMismatch`. | | Retry basis | A new submission selected after the head advanced is evaluated at its recorded basis, not at the selection-time state. | | Receipt digest v5 | A version-5 receipt digest changes when any bridge field changes, including the validation position, a dependency's comparison mode, or a compared value; version-4 receipt identities are unchanged. | | Proposal binding | For every `Proposed` outcome class in the recovery matrix, including rejected ones, altering one recorded effect-derived digest or the proposal identity refuses the history. | @@ -939,6 +968,7 @@ Each behaviour below needs a test before implementation: | Shared structure | Two Actions that `Ensure` the same node already present in the basis both commit in one Tick. | | Same-Tick identical create | Two Actions, dependency-scoped included, that both `Ensure` the same node absent at the commit parent: the later obstructs with `RejectedFootprintConflict` instead of reading the earlier one's write. | | Referential integrity | An edge to a node neither in the basis nor created by the proposal obstructs with `DanglingReference`; a pre-existing dangling edge elsewhere in the basis does not obstruct an unrelated proposal. | +| Portal chain | An effect in a descended warp records every portal on its root-to-target chain as a read; a co-scheduled portal retarget conflicts with it, and a portal changed and restored between origin and commit parent obstructs a scoped Action with `BasisChanged`. | | Visibility | No published coordinate shows a retargeted head without every node and edge the proposal introduced; pre-existing structure the head reaches is not validated. | | Witnessed reads | The receipt's read set equals the slots the view resolved, including for a program that reads and then obstructs. | | Negative reads | An Action that observed a node, atom, or edge set absent obstructs when a co-scheduled Action creates it, including a fresh edge of the observed source and type with a new edge id when the observed set was empty (outgoing-edge range key). | From 7a241eaaad2179b6b00e7d71335ceb19d8af9185 Mon Sep 17 00:00:00 2001 From: James Ross Date: Fri, 16 Oct 2026 11:04:40 -0700 Subject: [PATCH 21/50] docs: close the twelfth review round on the authored-effects bridge - Specify the EPRV0004 bridge section and the receipt-v5 preimage: length-prefixed section with tagged posture, patch, validity and evidence variants, fixed-width digests, and LE u64 integers. The decoder refuses length mismatches and trailing bytes. - Define one global validation order (history check, duplicates, identity, reachability, schema, footprint law, per-effect preconditions, resolved write set, derivation, batch limit, composition), so a multiply-invalid proposal has exactly one recorded obstruction. - Effects naming a key in a missing or unreachable warp obstruct with UnreachableWarp (Proposed, NotDerived). - Atom bytes in effect encodings are prefixed by a little-endian u64. - Atom payload bytes are charged against write_bytes. - A whole-frontier BasisChanged records zero consumed budget, and recovery verifies it. - Adds unreachable-warp, validation-order and atom-write-bytes test rows, and extends the encoding vectors to EPRV0004 entries. Co-Authored-By: Claude Opus 5.5 --- .../authored-operation-effects.md | 105 ++++++++++++++---- 1 file changed, 86 insertions(+), 19 deletions(-) diff --git a/docs/architecture/authored-operation-effects.md b/docs/architecture/authored-operation-effects.md index b74cabf0..51e4b858 100644 --- a/docs/architecture/authored-operation-effects.md +++ b/docs/architecture/authored-operation-effects.md @@ -222,8 +222,8 @@ Rules: encoding includes every field of the effect, expected values included: a kind tag, then each field in the order the effect lists it, with each instance-scoped key as its warp id then its local id, ids, type ids, and - digests at fixed width, and atom bytes length-prefixed after the atom's - `type_id`. Sorting never + digests at fixed width, and an atom as its `type_id`, its byte length as a + little-endian `u64`, then its bytes. Sorting never removes an effect, so duplicates stay visible to the one-effect-per-slot rule. - **One effect per slot.** No two effects in one proposal may target the same @@ -257,7 +257,11 @@ Rules: them. Each is a `ValueDigest` dependency on its `Descend` target. A co-scheduled write to a portal therefore conflicts, and a portal that changed between origin and commit parent, even if restored, obstructs with - `BasisChanged`. Effects never write a portal. + `BasisChanged`. Effects never write a portal. Every key an effect names + must lie in the root warp or in a warp reached by a valid root-to-target + chain at the basis; a key in a missing or unreachable warp is a typed + `UnreachableWarp` obstruction, posture `Proposed`, `NotDerived`, decided + before any patch is derived. - **Target set.** The proposal's target set is the canonical set of slots its effects target: for `EnsureNode` and `CreateNode` the node record and its atom, for `ReplaceAtom` the atom, and for `EnsureEdge` and `ReplaceEdge` the @@ -415,6 +419,35 @@ comparison runs at the validation position. The rejected Action's outcome is final (see Retries); a new submission evaluated later finds the node in its basis, so its `Ensure` is a read and does not conflict. +## Validation order + +Validation of a proposal runs in one fixed order, and the first failing step +decides the outcome, so a proposal that breaks several rules has exactly one +recorded obstruction: + +1. the dependency-scoped history check, for a scoped Action + (`BasisChanged`); +2. one effect per slot (`DuplicateEffectTarget`); +3. the identity law (`IdentityLawViolation`); +4. warp reachability of every effect key (`UnreachableWarp`); +5. the schema (`SchemaViolation`); +6. the footprint law over the witnessed read set, the precondition slots, + the target set, and the counts (`FootprintLawViolation`); +7. effect preconditions, effect by effect in canonical order, and within an + effect in the order its precondition list gives (`NodeMissing`, + `PreconditionMismatch`, `ContentIdentityConflict`, `DanglingReference`); +8. the footprint law over the resolved write set + (`FootprintLawViolation`); +9. patch derivation and the actual-footprint equality + (`FootprintMismatch`); +10. the Action-batch operation limit (`BudgetExceeded`); +11. composition (`RejectedFootprintConflict`). + +Steps 2 through 8 yield `NotDerived`, and steps 9 through 11 yield `Derived`. +Within a step that examines several effects, the first failing effect in +canonical order decides; since one kind belongs to each step except step 7, +only step 7 depends on that tie-break for its kind. + ## Visibility and referential integrity Validation enforces invariant 4 before a proposal can join a Tick, through @@ -520,7 +553,10 @@ held; this design validates only what the proposal adds or retargets. replaced is therefore a footprint conflict, never a `PreconditionMismatch`. - **Budget.** Evaluation is metered as `edict_pure` already meters it. Each - effect adds a fixed write cost. Exhausting any evaluation limit obstructs with + effect adds a fixed write cost, and an effect carrying an atom also charges + the atom's byte length against `write_bytes`, so a large payload cannot fit + a budget that would not cover its bytes. Exhausting any evaluation limit + obstructs with `BudgetExceeded`, posture `NoProposal`, and no patch. A proposal whose derived patch alone exceeds the Action-batch operation limit (`ACTION_BATCH_OPERATION_LIMIT_V1`) also obstructs with `BudgetExceeded` and @@ -604,8 +640,8 @@ Each Action's receipt entry binds, in addition to its current fields: - `NotDerived` when no patch was derived: validation refused the proposal (`DanglingReference`, `ContentIdentityConflict`, `DuplicateEffectTarget`, `FootprintLawViolation`, - `IdentityLawViolation`, `SchemaViolation`, `NodeMissing`, or - `PreconditionMismatch`), or a + `IdentityLawViolation`, `SchemaViolation`, `UnreachableWarp`, + `NodeMissing`, or `PreconditionMismatch`), or a dependency-scoped `BasisChanged` was decided before validation. No write-set or patch digest is manufactured; an overwrite patch that `Ensure` forbids is never hashed; @@ -643,7 +679,9 @@ Each Action's receipt entry binds, in addition to its current fields: mode tag; each encodes as kind tag, slot encoding, mode tag; and the dependency-set digest hashes them in that order; -- for `NoProposal` and `Proposed`, consumed budget. +- for `NoProposal` and `Proposed`, consumed budget. When evaluation never + ran (whole-frontier `BasisChanged`, including unknown history), it is zero + on every axis, and recovery verifies that. **Canonical set encoding.** The read-set, target-set, write-set, dependency-set, and compared-values digests each hash a set of slot entries, @@ -737,6 +775,32 @@ digest's preimage. Two validations that compared different values therefore have different receipt identities, and a version-4 receipt identity never changes meaning. Ticks that mix bridge and `NotBridge` entries encode in `EPRV0004` without fabricated evidence. + +**Receipt layout.** An `EPRV0004` entry is the `EPRV0003` entry fields, +unchanged, followed by a bridge section preceded by its byte length as a +little-endian `u64`. Integers are little-endian `u64` and digests and +identities are 32 bytes. The section is: + +1. the posture tag: `NotBridge` `0`, `NoProposal` `1`, `Proposed` `2`. A + `NotBridge` section ends here; +2. for `Proposed`: the proposal identity, the target-set digest, and the + patch tag (`NotDerived` `0`, `Derived` `1`), followed for `Derived` by + the write-set digest and the patch digest; +3. the read-set digest; +4. the validity tag (`WholeFrontier` `0`, `Scoped` `1`). For `Scoped`: the + origin basis identity, the validation position (the Action's index in + the Tick's canonical candidate order), and an evidence tag + (`OriginOnly` `0` for an evaluation obstruction, `Compared` `1`). For + `Compared`: the dependency-set digest, the compared-values digest, and an + option tag (`0` none, `1` some) followed for some by the changed + dependency's descriptor encoding; +5. the consumed budget: steps, allocated bytes, read bytes, write bytes. + +Every tag is one byte. Receipt digest version 5 hashes, in place of each +version-4 entry, that entry's version-4 bytes followed by its bridge section +exactly as `EPRV0004` retains it, length included, under the version-5 +domain. An `EPRV0004` decoder refuses a section whose length disagrees with +its contents or that carries trailing bytes. The implementation adds this row to [Submission Attribution's format table](../topics/SubmissionAttribution.md#format-binding-and-legacy) when it lands: @@ -798,17 +862,17 @@ The bridge keeps both and adds the outgoing-edge range keys and precondition reads to the reconstructed composition. The scoped-validity recomputation above likewise applies to every row recorded as `Scoped`. -| Outcome | Posture | Existing check | Added | -| ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------ | ------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| Committed | `Proposed`, `Derived` | replay patch; reconstruct preparation; compare identities | effects bound; read-set digest; patch digest equals the replayed patch | -| `RejectedFootprintConflict` | `Proposed`, `Derived` | reconstruct preparation; compare identities; recompute recorded blockers | effects bound; read-set digest; blockers include range-key conflicts | -| Obstructed at validation (`DanglingReference`, `ContentIdentityConflict`, `DuplicateEffectTarget`, `FootprintLawViolation`, `IdentityLawViolation`, `SchemaViolation`, `NodeMissing`, `PreconditionMismatch`) | `Proposed`, `NotDerived` | re-evaluate; compare pre-decision evidence | effects bound; read-set digest; obstruction kind; no write-set or patch digest present | -| Obstructed by `FootprintMismatch` (after derivation) | `Proposed`, `Derived` | re-evaluate; compare pre-decision evidence | effects bound; read-set digest; re-derived patch reproduces the mismatch | -| `BudgetExceeded`, scheduler-assigned (Action-batch limit) | `Proposed`, `Derived` | accept reconstructed preparation; verify through the batch reconstruction | effects bound; read-set digest | -| `BudgetExceeded` during evaluation, or an authored guard | `NoProposal` | re-evaluate; compare pre-decision evidence | re-evaluation, at the origin for a `Scoped` Action, again yields no proposal and the same obstruction; read-set digest | -| `BasisChanged`, whole-frontier (before evaluation, including unknown history) | `NoProposal` | admission evidence; recomputed basis posture; no re-evaluation | none; posture must be `NoProposal` with an empty read set | -| `BasisChanged`, dependency-scoped (after evaluation, before validation) | `Proposed`, `NotDerived` | none today: current programs decide this before evaluation | re-evaluate at the origin; effects bound; read-set digest; recompute the written-slot set from replay operations, the complete dependency set (witnessed reads and effect preconditions), and the changed dependency | -| Any outcome of a `NotBridge` entry | `NotBridge` | unchanged | none; no bridge field present | +| Outcome | Posture | Existing check | Added | +| -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------ | ------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| Committed | `Proposed`, `Derived` | replay patch; reconstruct preparation; compare identities | effects bound; read-set digest; patch digest equals the replayed patch | +| `RejectedFootprintConflict` | `Proposed`, `Derived` | reconstruct preparation; compare identities; recompute recorded blockers | effects bound; read-set digest; blockers include range-key conflicts | +| Obstructed at validation (`DanglingReference`, `ContentIdentityConflict`, `DuplicateEffectTarget`, `FootprintLawViolation`, `IdentityLawViolation`, `SchemaViolation`, `UnreachableWarp`, `NodeMissing`, `PreconditionMismatch`) | `Proposed`, `NotDerived` | re-evaluate; compare pre-decision evidence | effects bound; read-set digest; obstruction kind; no write-set or patch digest present | +| Obstructed by `FootprintMismatch` (after derivation) | `Proposed`, `Derived` | re-evaluate; compare pre-decision evidence | effects bound; read-set digest; re-derived patch reproduces the mismatch | +| `BudgetExceeded`, scheduler-assigned (Action-batch limit) | `Proposed`, `Derived` | accept reconstructed preparation; verify through the batch reconstruction | effects bound; read-set digest | +| `BudgetExceeded` during evaluation, or an authored guard | `NoProposal` | re-evaluate; compare pre-decision evidence | re-evaluation, at the origin for a `Scoped` Action, again yields no proposal and the same obstruction; read-set digest | +| `BasisChanged`, whole-frontier (before evaluation, including unknown history) | `NoProposal` | admission evidence; recomputed basis posture; no re-evaluation | none; posture must be `NoProposal` with an empty read set and zero consumed budget | +| `BasisChanged`, dependency-scoped (after evaluation, before validation) | `Proposed`, `NotDerived` | none today: current programs decide this before evaluation | re-evaluate at the origin; effects bound; read-set digest; recompute the written-slot set from replay operations, the complete dependency set (witnessed reads and effect preconditions), and the changed dependency | +| Any outcome of a `NotBridge` entry | `NotBridge` | unchanged | none; no bridge field present | ## Execution traces @@ -948,13 +1012,16 @@ Each behaviour below needs a test before implementation: | Witnessed footprint | A program that reads a slot outside its law obstructs, even if its writes are in bounds. | | Stale basis | `ReplaceAtom` with a changed expected digest, or `ReplaceEdge` with a moved `expectedTo`, obstructs with `PreconditionMismatch`, posture `Proposed`, `NotDerived`, and no patch. | | Budget | Exhausting steps or bytes obstructs with `BudgetExceeded` and `NoProposal`; a proposal whose patch alone exceeds the Action-batch operation limit obstructs with `BudgetExceeded`, posture `Proposed` with its digests, and no committed patch. | +| Atom write bytes | A proposal whose atom bytes exceed the delegated `write_bytes` obstructs with `BudgetExceeded` and `NoProposal`; a whole-frontier `BasisChanged` records zero consumed budget. | | Determinism | Same package, invocation, and evaluation basis give byte-identical proposals, proposal identities, read-set and write-set digests, and derived patches, whether the Action is evaluated alone or beside other Actions. Receipts and composite patches are compared only for identical Tick contexts. | | Canonical order | Emitting the same effects in different orders yields the same canonical `EffectProposal` bytes and proposal identity. | -| Encoding vectors | Fixed byte vectors for the proposal encoding, every value digest, and every set digest, including a descriptor list with both comparison modes, reproduce the specified bytes. | +| Encoding vectors | Fixed byte vectors for the proposal encoding, every value digest, every set digest (including a descriptor list with both comparison modes), and an `EPRV0004` entry of each posture, validity, and evidence tag reproduce the specified bytes and receipt-v5 digests. | | Canonical slots | Differently expressed effects on one slot (`EnsureEdge` and `ReplaceEdge` of one edge id; `CreateNode` and `ReplaceAtom` of one node) obstruct with `DuplicateEffectTarget`. | | Write set | The derived patch's actual footprint equals the write set, including the atom slots of created nodes and the range key of every created or retargeted edge, so a committed `EnsureEdge` and `ReplaceEdge` pass the footprint check. | | Identity law | A proposed node or edge id that does not match the package's identity law obstructs with `IdentityLawViolation`, posture `Proposed`, `NotDerived`, and recovery reproduces it. | +| Unreachable warp | An effect naming a key in a missing warp, or in a warp no valid portal chain reaches, obstructs with `UnreachableWarp`, posture `Proposed`, `NotDerived`. | | Schema | An effect whose node type, atom `type_id`, or edge type the package's schema does not admit obstructs with `SchemaViolation`, posture `Proposed`, `NotDerived`, and recovery reproduces it. | +| Validation order | A proposal that breaks several rules (for example a duplicate target, an unadmitted type, and a missing endpoint) records the obstruction of the earliest step in the validation order, live and on recovery. | | Footprint stages | A target outside the footprint law obstructs with `FootprintLawViolation` and `NotDerived`, and so does a `ReplaceEdge` whose basis edge's range key lies outside the law; a derived patch whose footprint differs from the write set obstructs with `FootprintMismatch` and `Derived`. | | Restored value (ABA) | A dependency written A to B and back to A between origin and commit parent is found through the written-slot set and obstructs with `BasisChanged`, including when the intervening patch under-declared its `out_slots`; with one replay patch in the interval missing, the Action obstructs before evaluation as a whole-frontier `BasisChanged`, posture `NoProposal`, with an empty read set. | | Scoped preconditions | A dependency-scoped Action whose `ReplaceAtom` expected value, or one of whose edge endpoints, changed and was restored between its origin and the commit parent obstructs with `BasisChanged`, though the program never read that slot; recovery reproduces the outcome and its changed dependency. | From 9318d2cab9544e3a5b6e95e673630cacaeca1fa4 Mon Sep 17 00:00:00 2001 From: James Ross Date: Fri, 16 Oct 2026 11:15:37 -0700 Subject: [PATCH 22/50] docs: close the thirteenth review round on the authored-effects bridge - Scoped evidence has three forms, each recording only a comparison that ran: OriginOnly (evaluation obstruction), HistoryChanged (dependency descriptors plus the changed dependency; no values compared), and Compared (commit-parent values read by the pre-composition checks). A RejectedFootprintConflict is never compared at its validation position. The EPRV0004 tags and recovery are updated to match. - The delegated budget stays EchoOperationBudgetV1 (steps, read bytes, write bytes). Allocation is bounded by edict_pure's existing max_allocated_bytes (host ceiling intersected with the program maximum) and recorded as evidence, not as a delegated axis. - Validation reads the program did not perform (for example hashing the current atom for ReplaceAtom) are charged to read_bytes before hashing, as compare-and-set does. Exceeding the budget is BudgetExceeded (Proposed, NotDerived) at that validation step. - Move the dependency-descriptor definition into its own paragraph, and add validation-read-budget and evidence-form test rows. Co-Authored-By: Claude Opus 5.5 --- .../authored-operation-effects.md | 156 +++++++++++------- 1 file changed, 94 insertions(+), 62 deletions(-) diff --git a/docs/architecture/authored-operation-effects.md b/docs/architecture/authored-operation-effects.md index 51e4b858..eda2c5e7 100644 --- a/docs/architecture/authored-operation-effects.md +++ b/docs/architecture/authored-operation-effects.md @@ -188,7 +188,7 @@ EffectProposal { ReplaceEdge { edge, expectedTo, to } retarget an existing edge ] result compiler-declared application result bytes - cost steps, allocated bytes, read bytes, write bytes + cost steps, read bytes, write bytes; allocated bytes } ``` @@ -435,7 +435,8 @@ recorded obstruction: the target set, and the counts (`FootprintLawViolation`); 7. effect preconditions, effect by effect in canonical order, and within an effect in the order its precondition list gives (`NodeMissing`, - `PreconditionMismatch`, `ContentIdentityConflict`, `DanglingReference`); + `PreconditionMismatch`, `ContentIdentityConflict`, `DanglingReference`, + or `BudgetExceeded` when a validation read exceeds the remaining budget); 8. the footprint law over the resolved write set (`FootprintLawViolation`); 9. patch derivation and the actual-footprint equality @@ -552,12 +553,27 @@ held; this design validates only what the proposal adds or retargets. commit parent. A later Action that expects a value an earlier Action replaced is therefore a footprint conflict, never a `PreconditionMismatch`. -- **Budget.** Evaluation is metered as `edict_pure` already meters it. Each - effect adds a fixed write cost, and an effect carrying an atom also charges - the atom's byte length against `write_bytes`, so a large payload cannot fit - a budget that would not cover its bytes. Exhausting any evaluation limit - obstructs with - `BudgetExceeded`, posture `NoProposal`, and no patch. A proposal whose derived +- **Budget.** The delegated budget stays `EchoOperationBudgetV1`, with its + three axes: steps, read bytes, and write bytes. Evaluation is metered as + `edict_pure` already meters it. Each effect adds a fixed write cost, and an + effect carrying an atom also charges the atom's byte length against + `write_bytes`, so a large payload cannot fit a budget that would not cover + its bytes. Exhausting any evaluation limit obstructs with + `BudgetExceeded`, posture `NoProposal`, and no patch. Allocation is not a + delegated axis: `edict_pure` already bounds it by its own + `max_allocated_bytes`, the host ceiling intersected with the program's + declared maximum, and exceeding that is likewise `BudgetExceeded` with + `NoProposal`. The allocated bytes are recorded as evidence, never compared + against the delegated budget. +- **Validation reads are charged.** A precondition check that reads a value + the program did not read charges that value's length against + `read_bytes` before hashing it, as today's compare-and-set charges the + current atom: a `ReplaceAtom` or `EnsureNode` hashes the current atom, and + an `EnsureEdge` or `ReplaceEdge` reads a fixed-size edge record. Charges + accrue in validation order, against the budget left after evaluation. A + check that would exceed it obstructs with `BudgetExceeded`, posture + `Proposed`, `NotDerived`, at that step of the validation order, before the + value is hashed. A proposal whose derived patch alone exceeds the Action-batch operation limit (`ACTION_BATCH_OPERATION_LIMIT_V1`) also obstructs with `BudgetExceeded` and no committed patch, rather than splitting across Ticks, but its posture is @@ -641,7 +657,8 @@ Each Action's receipt entry binds, in addition to its current fields: proposal (`DanglingReference`, `ContentIdentityConflict`, `DuplicateEffectTarget`, `FootprintLawViolation`, `IdentityLawViolation`, `SchemaViolation`, `UnreachableWarp`, - `NodeMissing`, or `PreconditionMismatch`), or a + `NodeMissing`, `PreconditionMismatch`, or a `BudgetExceeded` + charged by a validation read), or a dependency-scoped `BasisChanged` was decided before validation. No write-set or patch digest is manufactured; an overwrite patch that `Ensure` forbids is never hashed; @@ -654,35 +671,46 @@ Each Action's receipt entry binds, in addition to its current fields: basis; the recorded basis posture is its evidence; - `Scoped`: the evidence the [Action validity across ticks](../topics/GeneratedRules.md#action-validity-across-ticks) - decision requires, namely the origin basis, the validation position, a - dependency-set digest over each dependency's descriptor and comparison - mode, a compared-values digest over the values those dependencies were - compared against at the validation position (the commit parent plus the - writes of every earlier accepted Action), and, for a `BasisChanged`, the - first changed dependency in canonical order. An evaluation obstruction - runs no dependency comparison, so its `Scoped` evidence is the origin - basis and validation position only. A dependency descriptor is its - slot, its kind, and its comparison mode, each a one-byte tag: - - kind: value present `0`, observed absent `1`, expected value `2`, - endpoint existence `3`; - - comparison mode: `ValueDigest` `0`, which compares the slot's value - digest (value present, expected value), and `Presence` `1`, which - compares only presence (observed absent, endpoint existence). These - are the two modes the Action validity decision supports; - revision-sensitive and identity-sensitive dependencies are refused - at admission and have no tag. - - order and encoding: a scoped Action reads no edge ranges (an edge - read keeps the whole-frontier rule), so its dependency slots are - node records, attachments (atoms and portals), and edges. - Descriptors sort by slot encoding - (defined in the canonical set encoding below), then kind tag, then - mode tag; each encodes as kind tag, slot encoding, mode tag; and - the dependency-set digest hashes them in that order; + decision requires: the origin basis, the validation position, and one + of three evidence forms, each recording only a comparison that actually + ran: + - `OriginOnly`, for an evaluation obstruction, which runs no + dependency comparison; + - `HistoryChanged`, for a dependency-scoped `BasisChanged`: the + dependency-set digest over each dependency's descriptor and + comparison mode, and the first changed dependency in canonical + order. Validation never ran, so no values were compared; + - `Compared`, for every Action that passed the history check, + whatever its later outcome: the dependency-set digest and a + compared-values digest over the dependency values the + pre-composition checks read at the commit parent. For an accepted + Action these equal its values at the validation position (the + commit parent plus the writes of every earlier accepted Action), + because composition admits no overlap; a + `RejectedFootprintConflict` is never compared at its validation + position; - for `NoProposal` and `Proposed`, consumed budget. When evaluation never ran (whole-frontier `BasisChanged`, including unknown history), it is zero on every axis, and recovery verifies that. +**Dependency descriptors.** A dependency descriptor is its slot, its kind, +and its comparison mode, each kind and mode a one-byte tag: + +- kind: value present `0`, observed absent `1`, expected value `2`, endpoint + existence `3`; +- comparison mode: `ValueDigest` `0`, which compares the slot's value digest + (value present, expected value), and `Presence` `1`, which compares only + presence (observed absent, endpoint existence). These are the two modes the + Action validity decision supports; revision-sensitive and + identity-sensitive dependencies are refused at admission and have no tag. + +A scoped Action reads no edge ranges (an edge read keeps the whole-frontier +rule), so its dependency slots are node records, attachments (atoms and +portals), and edges. Descriptors sort by slot encoding (defined below), then +kind tag, then mode tag; each encodes as kind tag, slot encoding, mode tag; +and the dependency-set digest hashes them in that order. + **Canonical set encoding.** The read-set, target-set, write-set, dependency-set, and compared-values digests each hash a set of slot entries, never a collection in discovery order. A slot encodes as its class tag followed @@ -699,12 +727,12 @@ records for the slot: as a list of edge id and target node id pairs, sorted by edge id and preceded by its count as a little-endian `u64`; - target set and write set: the slot alone; -- dependency set: the descriptor encoding defined under the `Scoped` evidence - above; +- dependency set: the descriptor encoding defined under + [Dependency descriptors](#evidence) above; - compared values: one entry per dependency, in canonical descriptor order, each the descriptor encoding, a presence tag (present or absent at the - validation position), and, when present, the 32-byte digest of the value - compared, encoded as for the read set. + commit parent, as the pre-composition checks read it), and, when present, + the 32-byte digest of the value compared, encoded as for the read set. Each digest is BLAKE3 over a domain tag naming the set and its version, the entry count as a little-endian `u64`, and the entries in order. Live @@ -742,7 +770,7 @@ evaluated proposal, and the receipt keeps them distinct. 4. the effect count as a little-endian `u64`, then each effect's canonical encoding (see [EffectProposal](#effectproposal)) in canonical order; 5. the result: its length as a little-endian `u64`, then its bytes; -6. the cost: steps, allocated bytes, read bytes, and write bytes, each a +6. the cost: steps, read bytes, write bytes, and allocated bytes, each a little-endian `u64`. Effect kind tags are `EnsureNode` `0`, `CreateNode` `1`, `ReplaceAtom` `2`, @@ -790,11 +818,13 @@ identities are 32 bytes. The section is: 4. the validity tag (`WholeFrontier` `0`, `Scoped` `1`). For `Scoped`: the origin basis identity, the validation position (the Action's index in the Tick's canonical candidate order), and an evidence tag - (`OriginOnly` `0` for an evaluation obstruction, `Compared` `1`). For - `Compared`: the dependency-set digest, the compared-values digest, and an - option tag (`0` none, `1` some) followed for some by the changed - dependency's descriptor encoding; -5. the consumed budget: steps, allocated bytes, read bytes, write bytes. + (`OriginOnly` `0`, `HistoryChanged` `1`, `Compared` `2`). For + `HistoryChanged`: the dependency-set digest and the changed dependency's + descriptor encoding. For `Compared`: the dependency-set digest and the + compared-values digest; +5. the consumed budget against the delegated `EchoOperationBudgetV1` axes, + steps, read bytes, and write bytes, followed by the evaluator's allocated + bytes. Every tag is one byte. Receipt digest version 5 hashes, in place of each version-4 entry, that entry's version-4 bytes followed by its bridge section @@ -837,14 +867,14 @@ new evidence. Verification is not re-admission. set (every witnessed read and every effect precondition) and the written-slot set from the replay operations. It then repeats what live execution did, in the same order: the history check, the pre-composition - checks against the commit parent, and the batch composition. Only for an - accepted Action does it compare values at the recorded validation - position, where they equal the commit parent's. A - `RejectedFootprintConflict` is verified through its preparation against - the commit parent and its recomputed blockers, never by validating it at - its validation position. Recovery refuses a history whose dependency-set - digest, compared-values digest, changed dependency, or result differs. For - an evaluation obstruction it re-evaluates at the origin. + checks against the commit parent, and the batch composition, and it + rebuilds the recorded evidence form from the comparisons those steps + actually make. A `RejectedFootprintConflict` is verified through its + preparation against the commit parent and its recomputed blockers, never + by validating it at its validation position. Recovery refuses a history + whose evidence form, dependency-set digest, compared-values digest, + changed dependency, or result differs. For an evaluation obstruction it + re-evaluates at the origin. - An undecided Action stays the same pending submission and is evaluated when the scheduler next selects it. @@ -862,17 +892,17 @@ The bridge keeps both and adds the outgoing-edge range keys and precondition reads to the reconstructed composition. The scoped-validity recomputation above likewise applies to every row recorded as `Scoped`. -| Outcome | Posture | Existing check | Added | -| -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------ | ------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| Committed | `Proposed`, `Derived` | replay patch; reconstruct preparation; compare identities | effects bound; read-set digest; patch digest equals the replayed patch | -| `RejectedFootprintConflict` | `Proposed`, `Derived` | reconstruct preparation; compare identities; recompute recorded blockers | effects bound; read-set digest; blockers include range-key conflicts | -| Obstructed at validation (`DanglingReference`, `ContentIdentityConflict`, `DuplicateEffectTarget`, `FootprintLawViolation`, `IdentityLawViolation`, `SchemaViolation`, `UnreachableWarp`, `NodeMissing`, `PreconditionMismatch`) | `Proposed`, `NotDerived` | re-evaluate; compare pre-decision evidence | effects bound; read-set digest; obstruction kind; no write-set or patch digest present | -| Obstructed by `FootprintMismatch` (after derivation) | `Proposed`, `Derived` | re-evaluate; compare pre-decision evidence | effects bound; read-set digest; re-derived patch reproduces the mismatch | -| `BudgetExceeded`, scheduler-assigned (Action-batch limit) | `Proposed`, `Derived` | accept reconstructed preparation; verify through the batch reconstruction | effects bound; read-set digest | -| `BudgetExceeded` during evaluation, or an authored guard | `NoProposal` | re-evaluate; compare pre-decision evidence | re-evaluation, at the origin for a `Scoped` Action, again yields no proposal and the same obstruction; read-set digest | -| `BasisChanged`, whole-frontier (before evaluation, including unknown history) | `NoProposal` | admission evidence; recomputed basis posture; no re-evaluation | none; posture must be `NoProposal` with an empty read set and zero consumed budget | -| `BasisChanged`, dependency-scoped (after evaluation, before validation) | `Proposed`, `NotDerived` | none today: current programs decide this before evaluation | re-evaluate at the origin; effects bound; read-set digest; recompute the written-slot set from replay operations, the complete dependency set (witnessed reads and effect preconditions), and the changed dependency | -| Any outcome of a `NotBridge` entry | `NotBridge` | unchanged | none; no bridge field present | +| Outcome | Posture | Existing check | Added | +| ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | ------------------------ | ------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| Committed | `Proposed`, `Derived` | replay patch; reconstruct preparation; compare identities | effects bound; read-set digest; patch digest equals the replayed patch | +| `RejectedFootprintConflict` | `Proposed`, `Derived` | reconstruct preparation; compare identities; recompute recorded blockers | effects bound; read-set digest; blockers include range-key conflicts | +| Obstructed at validation (`DanglingReference`, `ContentIdentityConflict`, `DuplicateEffectTarget`, `FootprintLawViolation`, `IdentityLawViolation`, `SchemaViolation`, `UnreachableWarp`, `NodeMissing`, `PreconditionMismatch`, validation-read `BudgetExceeded`) | `Proposed`, `NotDerived` | re-evaluate; compare pre-decision evidence | effects bound; read-set digest; obstruction kind; no write-set or patch digest present | +| Obstructed by `FootprintMismatch` (after derivation) | `Proposed`, `Derived` | re-evaluate; compare pre-decision evidence | effects bound; read-set digest; re-derived patch reproduces the mismatch | +| `BudgetExceeded`, scheduler-assigned (Action-batch limit) | `Proposed`, `Derived` | accept reconstructed preparation; verify through the batch reconstruction | effects bound; read-set digest | +| `BudgetExceeded` during evaluation, or an authored guard | `NoProposal` | re-evaluate; compare pre-decision evidence | re-evaluation, at the origin for a `Scoped` Action, again yields no proposal and the same obstruction; read-set digest | +| `BasisChanged`, whole-frontier (before evaluation, including unknown history) | `NoProposal` | admission evidence; recomputed basis posture; no re-evaluation | none; posture must be `NoProposal` with an empty read set and zero consumed budget | +| `BasisChanged`, dependency-scoped (after evaluation, before validation) | `Proposed`, `NotDerived` | none today: current programs decide this before evaluation | re-evaluate at the origin; effects bound; read-set digest; recompute the written-slot set from replay operations, the complete dependency set (witnessed reads and effect preconditions), and the changed dependency | +| Any outcome of a `NotBridge` entry | `NotBridge` | unchanged | none; no bridge field present | ## Execution traces @@ -1013,6 +1043,7 @@ Each behaviour below needs a test before implementation: | Stale basis | `ReplaceAtom` with a changed expected digest, or `ReplaceEdge` with a moved `expectedTo`, obstructs with `PreconditionMismatch`, posture `Proposed`, `NotDerived`, and no patch. | | Budget | Exhausting steps or bytes obstructs with `BudgetExceeded` and `NoProposal`; a proposal whose patch alone exceeds the Action-batch operation limit obstructs with `BudgetExceeded`, posture `Proposed` with its digests, and no committed patch. | | Atom write bytes | A proposal whose atom bytes exceed the delegated `write_bytes` obstructs with `BudgetExceeded` and `NoProposal`; a whole-frontier `BasisChanged` records zero consumed budget. | +| Validation read budget | A `ReplaceAtom` of a stored atom larger than the remaining `read_bytes` obstructs with `BudgetExceeded`, posture `Proposed`, `NotDerived`, before hashing it; allocation beyond `edict_pure`'s `max_allocated_bytes` obstructs with `BudgetExceeded` and `NoProposal`. | | Determinism | Same package, invocation, and evaluation basis give byte-identical proposals, proposal identities, read-set and write-set digests, and derived patches, whether the Action is evaluated alone or beside other Actions. Receipts and composite patches are compared only for identical Tick contexts. | | Canonical order | Emitting the same effects in different orders yields the same canonical `EffectProposal` bytes and proposal identity. | | Encoding vectors | Fixed byte vectors for the proposal encoding, every value digest, every set digest (including a descriptor list with both comparison modes), and an `EPRV0004` entry of each posture, validity, and evidence tag reproduce the specified bytes and receipt-v5 digests. | @@ -1026,6 +1057,7 @@ Each behaviour below needs a test before implementation: | Restored value (ABA) | A dependency written A to B and back to A between origin and commit parent is found through the written-slot set and obstructs with `BasisChanged`, including when the intervening patch under-declared its `out_slots`; with one replay patch in the interval missing, the Action obstructs before evaluation as a whole-frontier `BasisChanged`, posture `NoProposal`, with an empty read set. | | Scoped preconditions | A dependency-scoped Action whose `ReplaceAtom` expected value, or one of whose edge endpoints, changed and was restored between its origin and the commit parent obstructs with `BasisChanged`, though the program never read that slot; recovery reproduces the outcome and its changed dependency. | | Scoped conflict recovery | A scoped Action rejected because an earlier Action in its Tick replaced a value it expects recovers as `RejectedFootprintConflict`, not `PreconditionMismatch`. | +| Scoped evidence forms | An evaluation obstruction records `OriginOnly`, a dependency-scoped `BasisChanged` records `HistoryChanged` with no compared values, and every other scoped outcome records `Compared` over commit-parent values; none is compared at a validation position that includes an earlier overlapping write. | | Retry basis | A new submission selected after the head advanced is evaluated at its recorded basis, not at the selection-time state. | | Receipt digest v5 | A version-5 receipt digest changes when any bridge field changes, including the validation position, a dependency's comparison mode, or a compared value; version-4 receipt identities are unchanged. | | Proposal binding | For every `Proposed` outcome class in the recovery matrix, including rejected ones, altering one recorded effect-derived digest or the proposal identity refuses the history. | From 8facc1321dc0ffa37ab43eeb1f165e5df004832d Mon Sep 17 00:00:00 2001 From: James Ross Date: Fri, 16 Oct 2026 11:27:21 -0700 Subject: [PATCH 23/50] docs: close the fourteenth review round on the authored-effects bridge - Compared-values entries gain a "not compared" presence tag. A dependency the checks never reached (an earlier failing step, or a validation read that exhausted the budget) is recorded without a value, so no evidence requires hashing past a budget stop. - Bridge Actions retain their outcome as EOACT004: the EOACT003 layout with the obstruction kind replaced by an outcome coordinate (bridge kind code, or package-qualified authored obstruction identity). The coordinate is also bound in the EPRV0004 bridge section. EOACT003 stays unchanged for fixed-profile Actions. - GeneratedRules' bound-evidence bullet now points to its own within-one-tick rule: a footprint-conflict rejection is not validated, and recovery recomputes the conflict. - README now distinguishes lawpack-imported packages from source-compiled bridge packages, and no longer describes the pending Jedit route as a rope lawpack. Co-Authored-By: Claude Opus 5.5 --- README.md | 16 +++++--- .../authored-operation-effects.md | 37 ++++++++++++++++--- docs/topics/GeneratedRules.md | 4 +- 3 files changed, 46 insertions(+), 11 deletions(-) diff --git a/README.md b/README.md index d4355ad4..ce885a9a 100644 --- a/README.md +++ b/README.md @@ -186,15 +186,21 @@ result, composition, and terminal outcome. Runtime-control installation and scheduler Tick records permit callback-free fresh-host recovery. Program bytes explicitly bind the interpreter and intrinsic profiles, while Tick evidence binds each admitted installation. A program digest cannot confer operation -identity, invocability, or authority. Each package also retains the canonical -lawpack obstruction coordinate resolved from the authored source-local import; -the scheduler reports that exact typed coordinate without interpreting -application vocabulary. +identity, invocability, or authority. Each package whose program is imported +from a lawpack also retains the canonical lawpack obstruction coordinate +resolved from the authored source-local import; the scheduler reports that +exact typed coordinate without interpreting application vocabulary. A package +compiled from Edict source binds no lawpack: under the accepted +[Authored Operation Effects](docs/architecture/authored-operation-effects.md) +design, which is not yet implemented, its obstructions are qualified by the +admitted package and installed operation. That generic runtime witness is not yet the Jim/Jedit vertical. A standalone application-owned Edict operation now crosses it through a compiler-produced package and structurally separate accepted verification report, but no Jedit -rope lawpack or `ReplaceRange` operation uses it. It does not yet claim +`ReplaceRange` operation uses it. Under the accepted Edict-source decision, the +rope law will be compiled from Jedit-owned Edict source rather than imported as +a lawpack. It does not yet claim cross-category scheduler composition or independently implemented semantic conformance. It also temporarily reuses `TrustedRuntimeHost`'s joint `native_rule_bootstrap` and `trusted_runtime` feature gate. The program itself diff --git a/docs/architecture/authored-operation-effects.md b/docs/architecture/authored-operation-effects.md index eda2c5e7..95cd6bd0 100644 --- a/docs/architecture/authored-operation-effects.md +++ b/docs/architecture/authored-operation-effects.md @@ -683,7 +683,11 @@ Each Action's receipt entry binds, in addition to its current fields: - `Compared`, for every Action that passed the history check, whatever its later outcome: the dependency-set digest and a compared-values digest over the dependency values the - pre-composition checks read at the commit parent. For an accepted + pre-composition checks read at the commit parent. A dependency + those checks never reached, because validation stopped at an + earlier step or a validation read exhausted the budget before + hashing its value, is recorded as not compared, with no value. For + an accepted Action these equal its values at the validation position (the commit parent plus the writes of every earlier accepted Action), because composition admits no overlap; a @@ -730,9 +734,10 @@ records for the slot: - dependency set: the descriptor encoding defined under [Dependency descriptors](#evidence) above; - compared values: one entry per dependency, in canonical descriptor order, - each the descriptor encoding, a presence tag (present or absent at the - commit parent, as the pre-composition checks read it), and, when present, - the 32-byte digest of the value compared, encoded as for the read set. + each the descriptor encoding, a presence tag (`0` present, `1` absent at + the commit parent as the pre-composition checks read it, or `2` not + compared), and, for `0`, the 32-byte digest of the value compared, encoded + as for the read set. Each digest is BLAKE3 over a domain tag naming the set and its version, the entry count as a little-endian `u64`, and the entries in order. Live @@ -824,13 +829,33 @@ identities are 32 bytes. The section is: compared-values digest; 5. the consumed budget against the delegated `EchoOperationBudgetV1` axes, steps, read bytes, and write bytes, followed by the evaluator's allocated - bytes. + bytes; +6. the outcome coordinate: `0` none (committed, or + `RejectedFootprintConflict`), `1` a bridge obstruction kind followed by + its one-byte code, or `2` an authored obstruction (such as + `BasisNotCanonical`) followed by its 32-byte package-declared obstruction + identity, qualified by the package and installed operation as the ADR + 0025 supersession states. Bridge kind codes are `BasisChanged` `0`, + `BudgetExceeded` `1`, `DuplicateEffectTarget` `2`, `IdentityLawViolation` + `3`, `UnreachableWarp` `4`, `SchemaViolation` `5`, `FootprintLawViolation` + `6`, `NodeMissing` `7`, `PreconditionMismatch` `8`, + `ContentIdentityConflict` `9`, `DanglingReference` `10`, and + `FootprintMismatch` `11`. Every tag is one byte. Receipt digest version 5 hashes, in place of each version-4 entry, that entry's version-4 bytes followed by its bridge section exactly as `EPRV0004` retains it, length included, under the version-5 domain. An `EPRV0004` decoder refuses a section whose length disagrees with its contents or that carries trailing bytes. + +**Retained Action outcome.** Today's `EOACT003` record writes an obstruction +as a code from `EchoOperationObstructionKindV1`, which has none of the +bridge kinds and no authored obstruction. A bridge Action's retained outcome +is therefore written as `EOACT004`: the `EOACT003` layout with its one-byte +obstruction kind replaced by the outcome coordinate above. Fixed-profile +Actions keep `EOACT003`, its decoder is unchanged, and each record decodes +by its own magic. Recovery requires the `EOACT004` coordinate to equal the +receipt's bridge-section coordinate. The implementation adds this row to [Submission Attribution's format table](../topics/SubmissionAttribution.md#format-binding-and-legacy) when it lands: @@ -1044,6 +1069,7 @@ Each behaviour below needs a test before implementation: | Budget | Exhausting steps or bytes obstructs with `BudgetExceeded` and `NoProposal`; a proposal whose patch alone exceeds the Action-batch operation limit obstructs with `BudgetExceeded`, posture `Proposed` with its digests, and no committed patch. | | Atom write bytes | A proposal whose atom bytes exceed the delegated `write_bytes` obstructs with `BudgetExceeded` and `NoProposal`; a whole-frontier `BasisChanged` records zero consumed budget. | | Validation read budget | A `ReplaceAtom` of a stored atom larger than the remaining `read_bytes` obstructs with `BudgetExceeded`, posture `Proposed`, `NotDerived`, before hashing it; allocation beyond `edict_pure`'s `max_allocated_bytes` obstructs with `BudgetExceeded` and `NoProposal`. | +| Budget-stopped evidence | A scoped proposal whose validation read exhausts the budget records `Compared` with that dependency, and every later one, as not compared; no value past the stop is hashed. | | Determinism | Same package, invocation, and evaluation basis give byte-identical proposals, proposal identities, read-set and write-set digests, and derived patches, whether the Action is evaluated alone or beside other Actions. Receipts and composite patches are compared only for identical Tick contexts. | | Canonical order | Emitting the same effects in different orders yields the same canonical `EffectProposal` bytes and proposal identity. | | Encoding vectors | Fixed byte vectors for the proposal encoding, every value digest, every set digest (including a descriptor list with both comparison modes), and an `EPRV0004` entry of each posture, validity, and evidence tag reproduce the specified bytes and receipt-v5 digests. | @@ -1079,6 +1105,7 @@ Each behaviour below needs a test before implementation: | Scoped basis change | A dependency-scoped Action evaluated at its origin obstructs with `BasisChanged`, posture `Proposed` with `NotDerived`, and keeps its proposal, read set, and changed dependency when a dependency changed, even if its proposal would also fail validation; an unchanged dependency set lets it commit. | | Scoped evaluation obstruction | A dependency-scoped Action whose evaluation exhausts its budget, or fails an authored guard, keeps that obstruction with `NoProposal` even when one of its reads changed after the origin; recovery reproduces it. | | Receipt format | `EPRV0003` records recover byte for byte through the unchanged decoder; new Ticks are written as `EPRV0004`; a Tick mixing a bridge Action with fixed-profile Actions encodes the latter as `NotBridge`; a history mixing both formats recovers. | +| Outcome record | A bridge obstruction, including an authored one such as `BasisNotCanonical`, is retained as `EOACT004` with the same coordinate as its receipt; fixed-profile Actions still write `EOACT003`, and mixed histories recover. | | Recovered evidence | For each outcome class, recovery performs that class's existing verification plus the new comparisons (posture, proposal identity, read-set, write-set, and patch digests where the class reconstructs or re-evaluates); a tampered digest refuses the history, a whole-frontier `BasisChanged` is not re-evaluated, the batch composition is reconstructed for every class, and no recorded outcome changes. | | Pending recovery | An Action lost with its Tick before the WAL record is recovered as the same submission, not a new one. | diff --git a/docs/topics/GeneratedRules.md b/docs/topics/GeneratedRules.md index 913a4745..38108dba 100644 --- a/docs/topics/GeneratedRules.md +++ b/docs/topics/GeneratedRules.md @@ -239,7 +239,9 @@ all of the following hold: identity, origin basis, commit parent, validation position, each dependency descriptor and comparison mode, and a digest of the compared values. Recovery recomputes the validation at that position and refuses a history whose - result differs; it never trusts the evidence field alone. + result differs; it never trusts the evidence field alone. An Action rejected + as a footprint conflict under the within-one-tick rule above is not + validated, so recovery recomputes its conflict instead. An Action is never re-stamped as if evaluated against its commit parent. A dependency failure names the dependency that changed. The per-tick limit of 64 From 83999a86e420c0c5a5b8afb853be76cbacbdaf6c Mon Sep 17 00:00:00 2001 From: James Ross Date: Fri, 16 Oct 2026 11:36:41 -0700 Subject: [PATCH 24/50] docs: close the fifteenth review round on the authored-effects bridge - Check warp reachability at the evaluation basis, which for a scoped Action is its origin. A warp unreachable there obstructs even if a later Tick reaches it, and the portal chain of a reachable warp is already a dependency. - The schema step checks the types an effect carries. ReplaceAtom's owner-type/atom-type relation is checked at the precondition step, after the node record is read and charged. - Composition precedes the batch operation limit, which is tested only for a candidate with no blockers, as today's scheduler does. - Qualify the stale-basis test: a wrong expected value at the evaluation basis is PreconditionMismatch, while a value changed after the origin is BasisChanged. Co-Authored-By: Claude Opus 5.5 --- .../authored-operation-effects.md | 28 ++++++++++++++----- 1 file changed, 21 insertions(+), 7 deletions(-) diff --git a/docs/architecture/authored-operation-effects.md b/docs/architecture/authored-operation-effects.md index 95cd6bd0..fb4183b0 100644 --- a/docs/architecture/authored-operation-effects.md +++ b/docs/architecture/authored-operation-effects.md @@ -429,20 +429,31 @@ recorded obstruction: (`BasisChanged`); 2. one effect per slot (`DuplicateEffectTarget`); 3. the identity law (`IdentityLawViolation`); -4. warp reachability of every effect key (`UnreachableWarp`); -5. the schema (`SchemaViolation`); +4. warp reachability of every effect key at the evaluation basis + (`UnreachableWarp`). For a scoped Action that basis is its origin: a warp + unreachable there obstructs even if a later Tick made it reachable, and a + reachable warp's portal chain is already in its dependency set, so step 1 + catches any change to it; +5. the schema, over the types each effect itself carries: node types, edge + types, and atom `type_id`s must be admitted (`SchemaViolation`). The + relation between a node's type and the atom types it may carry is checked + here for `EnsureNode` and `CreateNode`, which carry both; 6. the footprint law over the witnessed read set, the precondition slots, the target set, and the counts (`FootprintLawViolation`); 7. effect preconditions, effect by effect in canonical order, and within an effect in the order its precondition list gives (`NodeMissing`, `PreconditionMismatch`, `ContentIdentityConflict`, `DanglingReference`, - or `BudgetExceeded` when a validation read exceeds the remaining budget); + or `BudgetExceeded` when a validation read exceeds the remaining budget). + For a `ReplaceAtom`, the node record is read and charged first; once it + exists, the relation between its type and the new atom's `type_id` is + checked (`SchemaViolation`) before the expected digest; 8. the footprint law over the resolved write set (`FootprintLawViolation`); 9. patch derivation and the actual-footprint equality (`FootprintMismatch`); -10. the Action-batch operation limit (`BudgetExceeded`); -11. composition (`RejectedFootprintConflict`). +10. composition (`RejectedFootprintConflict`); +11. the Action-batch operation limit (`BudgetExceeded`), tested only for a + candidate with no blockers, as today's scheduler does. Steps 2 through 8 yield `NotDerived`, and steps 9 through 11 yield `Derived`. Within a step that examines several effects, the first failing effect in @@ -540,7 +551,8 @@ held; this design validates only what the proposal adds or retargets. - **Within one Tick, footprints before earlier writes.** Every check before composition, preconditions and expected values included, runs against the Tick's commit parent, never against writes accepted earlier in the same - Tick. Composition then reserves each candidate's complete footprint + Tick. The one exception is warp reachability, which is checked at the + evaluation basis (see [Validation order](#validation-order)). Composition then reserves each candidate's complete footprint (witnessed reads, precondition reads, and write set) in canonical order, and any overlap with an earlier accepted Action in which either of the two writes the slot is `RejectedFootprintConflict` with its blockers, as the @@ -1065,7 +1077,7 @@ Each behaviour below needs a test before implementation: | Ensure identity | Identical existing node: no patch entry. Different existing node: `ContentIdentityConflict`. | | Partial occupancy | A node record without its atom, or an atom without its record: `CreateNode` obstructs with `PreconditionMismatch` and `EnsureNode` with `ContentIdentityConflict`; neither overwrites the orphan slot. | | Witnessed footprint | A program that reads a slot outside its law obstructs, even if its writes are in bounds. | -| Stale basis | `ReplaceAtom` with a changed expected digest, or `ReplaceEdge` with a moved `expectedTo`, obstructs with `PreconditionMismatch`, posture `Proposed`, `NotDerived`, and no patch. | +| Stale basis | A `ReplaceAtom` or `ReplaceEdge` whose expected value already disagrees with the value at its evaluation basis obstructs with `PreconditionMismatch`, posture `Proposed`, `NotDerived`, and no patch; when the value instead changed after the origin, a whole-frontier Action obstructs with `BasisChanged` before evaluation and a scoped one at the history check. | | Budget | Exhausting steps or bytes obstructs with `BudgetExceeded` and `NoProposal`; a proposal whose patch alone exceeds the Action-batch operation limit obstructs with `BudgetExceeded`, posture `Proposed` with its digests, and no committed patch. | | Atom write bytes | A proposal whose atom bytes exceed the delegated `write_bytes` obstructs with `BudgetExceeded` and `NoProposal`; a whole-frontier `BasisChanged` records zero consumed budget. | | Validation read budget | A `ReplaceAtom` of a stored atom larger than the remaining `read_bytes` obstructs with `BudgetExceeded`, posture `Proposed`, `NotDerived`, before hashing it; allocation beyond `edict_pure`'s `max_allocated_bytes` obstructs with `BudgetExceeded` and `NoProposal`. | @@ -1077,8 +1089,10 @@ Each behaviour below needs a test before implementation: | Write set | The derived patch's actual footprint equals the write set, including the atom slots of created nodes and the range key of every created or retargeted edge, so a committed `EnsureEdge` and `ReplaceEdge` pass the footprint check. | | Identity law | A proposed node or edge id that does not match the package's identity law obstructs with `IdentityLawViolation`, posture `Proposed`, `NotDerived`, and recovery reproduces it. | | Unreachable warp | An effect naming a key in a missing warp, or in a warp no valid portal chain reaches, obstructs with `UnreachableWarp`, posture `Proposed`, `NotDerived`. | +| Origin reachability | A scoped Action naming a warp unreachable at its origin obstructs with `UnreachableWarp` even when an intervening Tick created the portal that reaches it. | | Schema | An effect whose node type, atom `type_id`, or edge type the package's schema does not admit obstructs with `SchemaViolation`, posture `Proposed`, `NotDerived`, and recovery reproduces it. | | Validation order | A proposal that breaks several rules (for example a duplicate target, an unadmitted type, and a missing endpoint) records the obstruction of the earliest step in the validation order, live and on recovery. | +| Conflict before batch limit | A candidate that both overlaps an earlier accepted Action and would exceed the batch operation limit records `RejectedFootprintConflict`, as today's scheduler does. | | Footprint stages | A target outside the footprint law obstructs with `FootprintLawViolation` and `NotDerived`, and so does a `ReplaceEdge` whose basis edge's range key lies outside the law; a derived patch whose footprint differs from the write set obstructs with `FootprintMismatch` and `Derived`. | | Restored value (ABA) | A dependency written A to B and back to A between origin and commit parent is found through the written-slot set and obstructs with `BasisChanged`, including when the intervening patch under-declared its `out_slots`; with one replay patch in the interval missing, the Action obstructs before evaluation as a whole-frontier `BasisChanged`, posture `NoProposal`, with an empty read set. | | Scoped preconditions | A dependency-scoped Action whose `ReplaceAtom` expected value, or one of whose edge endpoints, changed and was restored between its origin and the commit parent obstructs with `BasisChanged`, though the program never read that slot; recovery reproduces the outcome and its changed dependency. | From aac6b807ba2dd3e2595140600eb029aa231a6b8d Mon Sep 17 00:00:00 2001 From: James Ross Date: Fri, 16 Oct 2026 11:45:51 -0700 Subject: [PATCH 25/50] docs: close the sixteenth review round on the authored-effects bridge - The validation order gains a resolution step. Each ReplaceAtom's node record and each ReplaceEdge's edge record are read and charged before the schema and footprint checks that need them. A ReplaceEdge's preserved edge type and a ReplaceAtom's owner/atom relation are checked as resolved schema, and the footprint law runs once, after resolution, over every precondition slot (including a ReplaceEdge's resolved source) and the resolved write set. - Exact per-effect charges at emission: one step, plus write_bytes of 64 and the atom bytes for node and atom effects (today's compare-and-set charge), or 128 for edge effects. - Source-compiled packages use ExecutableOperationPackageV2 and EchoOperationSemanticClosureV2, with the lawpack binding as a tagged option. There is no sentinel lawpack, V2 identities are distinct, and V1 is unchanged. - Adds resolved-schema, resolved-footprint, effect-charge and package-version test rows. Co-Authored-By: Claude Opus 5.5 --- .../authored-operation-effects.md | 90 ++++++++++++------- 1 file changed, 57 insertions(+), 33 deletions(-) diff --git a/docs/architecture/authored-operation-effects.md b/docs/architecture/authored-operation-effects.md index fb4183b0..ac06101d 100644 --- a/docs/architecture/authored-operation-effects.md +++ b/docs/architecture/authored-operation-effects.md @@ -395,11 +395,13 @@ Echo checks: set writes the node's atom, and each edge upsert writes the edge and the range key of its source and type. -The first check yields a typed `SchemaViolation` obstruction; the second, -third, and fourth yield a typed `FootprintLawViolation`. All four run before -any patch is derived, with patch tag `NotDerived`. The fifth can only run after -derivation; a mismatch is a typed `FootprintMismatch` obstruction with patch -tag `Derived`, recording the patch digest it compared. Neither commits a patch. +The schema check yields a typed `SchemaViolation` obstruction and the law +checks a typed `FootprintLawViolation`; both run before any patch is derived, +with patch tag `NotDerived`, at the steps [Validation order](#validation-order) +gives them, after the facts an effect does not carry have been resolved. The +patch equality can only run after derivation; a mismatch is a typed +`FootprintMismatch` obstruction with patch tag `Derived`, recording the patch +digest it compared. Neither commits a patch. Independence between Actions in one Tick uses the witnessed footprints, including outgoing-edge range keys, under the existing rule: write against read or write conflicts, read against read does not. @@ -435,30 +437,35 @@ recorded obstruction: reachable warp's portal chain is already in its dependency set, so step 1 catches any change to it; 5. the schema, over the types each effect itself carries: node types, edge - types, and atom `type_id`s must be admitted (`SchemaViolation`). The - relation between a node's type and the atom types it may carry is checked - here for `EnsureNode` and `CreateNode`, which carry both; -6. the footprint law over the witnessed read set, the precondition slots, - the target set, and the counts (`FootprintLawViolation`); -7. effect preconditions, effect by effect in canonical order, and within an - effect in the order its precondition list gives (`NodeMissing`, - `PreconditionMismatch`, `ContentIdentityConflict`, `DanglingReference`, - or `BudgetExceeded` when a validation read exceeds the remaining budget). - For a `ReplaceAtom`, the node record is read and charged first; once it - exists, the relation between its type and the new atom's `type_id` is - checked (`SchemaViolation`) before the expected digest; -8. the footprint law over the resolved write set - (`FootprintLawViolation`); -9. patch derivation and the actual-footprint equality - (`FootprintMismatch`); -10. composition (`RejectedFootprintConflict`); -11. the Action-batch operation limit (`BudgetExceeded`), tested only for a + types, and atom `type_id`s must be admitted, and for `EnsureNode` and + `CreateNode`, which carry both, the node type must admit the atom type + (`SchemaViolation`); +6. resolution: in canonical effect order, read and charge each + `ReplaceAtom`'s node record and each `ReplaceEdge`'s edge record, the + facts those effects do not carry (`NodeMissing` for a missing node, + `PreconditionMismatch` for a missing edge, or `BudgetExceeded` when the + read exceeds the remaining budget); +7. the schema over resolved types: a `ReplaceEdge`'s preserved edge type + must be admitted, and a `ReplaceAtom`'s owner node type must admit the + new atom type (`SchemaViolation`); +8. the footprint law, now that every slot is known: the witnessed read set, + every precondition slot (including a `ReplaceEdge`'s resolved source), + the target set, the resolved write set with its range keys, and their + counts (`FootprintLawViolation`); +9. the remaining effect preconditions, effect by effect in canonical order, + and within an effect in the order its precondition list gives + (`PreconditionMismatch`, `ContentIdentityConflict`, `DanglingReference`, + or `BudgetExceeded` when a validation read exceeds the remaining budget); +10. patch derivation and the actual-footprint equality + (`FootprintMismatch`); +11. composition (`RejectedFootprintConflict`); +12. the Action-batch operation limit (`BudgetExceeded`), tested only for a candidate with no blockers, as today's scheduler does. -Steps 2 through 8 yield `NotDerived`, and steps 9 through 11 yield `Derived`. -Within a step that examines several effects, the first failing effect in -canonical order decides; since one kind belongs to each step except step 7, -only step 7 depends on that tie-break for its kind. +Steps 2 through 9 yield `NotDerived`, and steps 10 through 12 yield +`Derived`. Within a step that examines several effects, the first failing +effect in canonical order decides; only steps 6 and 9 can yield more than +one kind, so only they depend on that tie-break for their kind. ## Visibility and referential integrity @@ -565,12 +572,14 @@ held; this design validates only what the proposal adds or retargets. commit parent. A later Action that expects a value an earlier Action replaced is therefore a footprint conflict, never a `PreconditionMismatch`. -- **Budget.** The delegated budget stays `EchoOperationBudgetV1`, with its - three axes: steps, read bytes, and write bytes. Evaluation is metered as - `edict_pure` already meters it. Each effect adds a fixed write cost, and an - effect carrying an atom also charges the atom's byte length against - `write_bytes`, so a large payload cannot fit a budget that would not cover - its bytes. Exhausting any evaluation limit obstructs with +- **Budget.** The delegated budget stays `EchoOperationBudgetV1`, with its three + axes: steps, read bytes, and write bytes. Evaluation is metered as + `edict_pure` already meters it. Each effect is charged when emitted, whatever + validation later resolves: one step, plus `write_bytes` of 64 and the atom's + byte length for `EnsureNode`, `CreateNode`, and `ReplaceAtom` (the charge + today's compare-and-set makes for its replacement), or 128 for `EnsureEdge` + and `ReplaceEdge`. A large payload therefore cannot fit a budget that would + not cover its bytes. Exhausting any evaluation limit obstructs with `BudgetExceeded`, posture `NoProposal`, and no patch. Allocation is not a delegated axis: `edict_pure` already bounds it by its own `max_allocated_bytes`, the host ceiling intersected with the program's @@ -641,6 +650,17 @@ Consequences: - **Durability** is unchanged in kind: the decided-Tick WAL record is the only persistence. Proposals are never retained apart from their Tick. The record gains a new layout version for the new evidence (see Evidence). +- **Package version.** `ExecutableOperationPackageV1` embeds + `EchoOperationSemanticClosureV1`, which requires a lawpack coordinate and + binds `lawpack_identity` into its canonical bytes and identities. A + source-compiled bridge package therefore uses new versions, + `ExecutableOperationPackageV2` and `EchoOperationSemanticClosureV2`. They + are identical to V1 except that the lawpack binding is a tagged option: + `0` absent, or `1` followed by the V1 lawpack coordinate and identity. A + bridge package writes `0`; no sentinel lawpack is fabricated. V2 canonical + bytes carry their own version domain tags, so V2 package, + installed-operation, and semantic-closure identities never coincide with + V1 identities. V1 encoding, validation, and identities are unchanged. ## Evidence @@ -1079,6 +1099,7 @@ Each behaviour below needs a test before implementation: | Witnessed footprint | A program that reads a slot outside its law obstructs, even if its writes are in bounds. | | Stale basis | A `ReplaceAtom` or `ReplaceEdge` whose expected value already disagrees with the value at its evaluation basis obstructs with `PreconditionMismatch`, posture `Proposed`, `NotDerived`, and no patch; when the value instead changed after the origin, a whole-frontier Action obstructs with `BasisChanged` before evaluation and a scoped one at the history check. | | Budget | Exhausting steps or bytes obstructs with `BudgetExceeded` and `NoProposal`; a proposal whose patch alone exceeds the Action-batch operation limit obstructs with `BudgetExceeded`, posture `Proposed` with its digests, and no committed patch. | +| Effect charges | Each effect is charged one step and its fixed `write_bytes` (64 plus atom bytes for node and atom effects, 128 for edge effects) at emission; a budget one byte short of a proposal's total obstructs with `BudgetExceeded` and `NoProposal`. | | Atom write bytes | A proposal whose atom bytes exceed the delegated `write_bytes` obstructs with `BudgetExceeded` and `NoProposal`; a whole-frontier `BasisChanged` records zero consumed budget. | | Validation read budget | A `ReplaceAtom` of a stored atom larger than the remaining `read_bytes` obstructs with `BudgetExceeded`, posture `Proposed`, `NotDerived`, before hashing it; allocation beyond `edict_pure`'s `max_allocated_bytes` obstructs with `BudgetExceeded` and `NoProposal`. | | Budget-stopped evidence | A scoped proposal whose validation read exhausts the budget records `Compared` with that dependency, and every later one, as not compared; no value past the stop is hashed. | @@ -1091,9 +1112,11 @@ Each behaviour below needs a test before implementation: | Unreachable warp | An effect naming a key in a missing warp, or in a warp no valid portal chain reaches, obstructs with `UnreachableWarp`, posture `Proposed`, `NotDerived`. | | Origin reachability | A scoped Action naming a warp unreachable at its origin obstructs with `UnreachableWarp` even when an intervening Tick created the portal that reaches it. | | Schema | An effect whose node type, atom `type_id`, or edge type the package's schema does not admit obstructs with `SchemaViolation`, posture `Proposed`, `NotDerived`, and recovery reproduces it. | +| Resolved schema | A `ReplaceEdge` whose basis edge type the schema does not admit, or a `ReplaceAtom` whose owner node type does not admit the new atom type, obstructs with `SchemaViolation` after its record is read and charged. | | Validation order | A proposal that breaks several rules (for example a duplicate target, an unadmitted type, and a missing endpoint) records the obstruction of the earliest step in the validation order, live and on recovery. | | Conflict before batch limit | A candidate that both overlaps an earlier accepted Action and would exceed the batch operation limit records `RejectedFootprintConflict`, as today's scheduler does. | | Footprint stages | A target outside the footprint law obstructs with `FootprintLawViolation` and `NotDerived`, and so does a `ReplaceEdge` whose basis edge's range key lies outside the law; a derived patch whose footprint differs from the write set obstructs with `FootprintMismatch` and `Derived`. | +| Resolved footprint | A `ReplaceEdge` whose resolved source node lies outside the footprint law obstructs with `FootprintLawViolation`. | | Restored value (ABA) | A dependency written A to B and back to A between origin and commit parent is found through the written-slot set and obstructs with `BasisChanged`, including when the intervening patch under-declared its `out_slots`; with one replay patch in the interval missing, the Action obstructs before evaluation as a whole-frontier `BasisChanged`, posture `NoProposal`, with an empty read set. | | Scoped preconditions | A dependency-scoped Action whose `ReplaceAtom` expected value, or one of whose edge endpoints, changed and was restored between its origin and the commit parent obstructs with `BasisChanged`, though the program never read that slot; recovery reproduces the outcome and its changed dependency. | | Scoped conflict recovery | A scoped Action rejected because an earlier Action in its Tick replaced a value it expects recovers as `RejectedFootprintConflict`, not `PreconditionMismatch`. | @@ -1119,6 +1142,7 @@ Each behaviour below needs a test before implementation: | Scoped basis change | A dependency-scoped Action evaluated at its origin obstructs with `BasisChanged`, posture `Proposed` with `NotDerived`, and keeps its proposal, read set, and changed dependency when a dependency changed, even if its proposal would also fail validation; an unchanged dependency set lets it commit. | | Scoped evaluation obstruction | A dependency-scoped Action whose evaluation exhausts its budget, or fails an authored guard, keeps that obstruction with `NoProposal` even when one of its reads changed after the origin; recovery reproduces it. | | Receipt format | `EPRV0003` records recover byte for byte through the unchanged decoder; new Ticks are written as `EPRV0004`; a Tick mixing a bridge Action with fixed-profile Actions encodes the latter as `NotBridge`; a history mixing both formats recovers. | +| Package version | A source-compiled package encodes as `ExecutableOperationPackageV2` with lawpack tag `0`; V1 packages keep their bytes and identities, and no V2 identity equals a V1 identity. | | Outcome record | A bridge obstruction, including an authored one such as `BasisNotCanonical`, is retained as `EOACT004` with the same coordinate as its receipt; fixed-profile Actions still write `EOACT003`, and mixed histories recover. | | Recovered evidence | For each outcome class, recovery performs that class's existing verification plus the new comparisons (posture, proposal identity, read-set, write-set, and patch digests where the class reconstructs or re-evaluates); a tampered digest refuses the history, a whole-frontier `BasisChanged` is not re-evaluated, the batch composition is reconstructed for every class, and no recorded outcome changes. | | Pending recovery | An Action lost with its Tick before the WAL record is recovered as the same submission, not a new one. | From 002a114db8a54dae9203a1790645b60c72509888 Mon Sep 17 00:00:00 2001 From: James Ross Date: Fri, 16 Oct 2026 18:57:26 -0700 Subject: [PATCH 26/50] docs: close the seventeenth review round on the authored-effects bridge - Package V2 carries an EchoOperationProgramV2 tagged union. Tags 0 and 1 are today's fixed profiles, unchanged. Tag 2 is EdictEffectProgram: length-prefixed compiled bytes, Target IR, evaluator ABI and intrinsic profile, identity-law and schema identities, and a length-prefixed footprint law. The evaluator dispatches on the tag. - Resolution of ReplaceAtom node records and ReplaceEdge edge records now runs first, at the evaluation basis (a scoped Action's origin), and adds the resolved records and a ReplaceEdge's source to the dependency set before the history check. A deleted and recreated source is caught. - A range-key read-set entry has no outcome tag. It is the slot, a u64 LE count, then 32-byte (edge, target) pairs; an empty range is a count of zero. - The receipt-v5 preimage is the v4 preimage with its leading LE u16 version tag set to 5, and each entry followed by its bridge section. Co-Authored-By: Claude Opus 5.5 --- .../authored-operation-effects.md | 97 ++++++++++++------- 1 file changed, 61 insertions(+), 36 deletions(-) diff --git a/docs/architecture/authored-operation-effects.md b/docs/architecture/authored-operation-effects.md index ac06101d..8337a852 100644 --- a/docs/architecture/authored-operation-effects.md +++ b/docs/architecture/authored-operation-effects.md @@ -427,24 +427,26 @@ Validation of a proposal runs in one fixed order, and the first failing step decides the outcome, so a proposal that breaks several rules has exactly one recorded obstruction: -1. the dependency-scoped history check, for a scoped Action +1. resolution at the evaluation basis (for a scoped Action, its origin): in + canonical effect order, read and charge each `ReplaceAtom`'s node record + and each `ReplaceEdge`'s edge record, the facts those effects do not + carry (`NodeMissing` for a missing node, `PreconditionMismatch` for a + missing edge, or `BudgetExceeded` when the read exceeds the remaining + budget). The resolved records, and a `ReplaceEdge`'s source endpoint, + join the dependency set, so the history check sees them; +2. the dependency-scoped history check, for a scoped Action (`BasisChanged`); -2. one effect per slot (`DuplicateEffectTarget`); -3. the identity law (`IdentityLawViolation`); -4. warp reachability of every effect key at the evaluation basis - (`UnreachableWarp`). For a scoped Action that basis is its origin: a warp - unreachable there obstructs even if a later Tick made it reachable, and a - reachable warp's portal chain is already in its dependency set, so step 1 - catches any change to it; -5. the schema, over the types each effect itself carries: node types, edge +3. one effect per slot (`DuplicateEffectTarget`); +4. the identity law (`IdentityLawViolation`); +5. warp reachability of every effect key at the evaluation basis + (`UnreachableWarp`). A warp unreachable at a scoped Action's origin + obstructs even if a later Tick made it reachable, and a reachable warp's + portal chain is already in its dependency set, so step 2 catches any + change to it; +6. the schema, over the types each effect itself carries: node types, edge types, and atom `type_id`s must be admitted, and for `EnsureNode` and `CreateNode`, which carry both, the node type must admit the atom type (`SchemaViolation`); -6. resolution: in canonical effect order, read and charge each - `ReplaceAtom`'s node record and each `ReplaceEdge`'s edge record, the - facts those effects do not carry (`NodeMissing` for a missing node, - `PreconditionMismatch` for a missing edge, or `BudgetExceeded` when the - read exceeds the remaining budget); 7. the schema over resolved types: a `ReplaceEdge`'s preserved edge type must be admitted, and a `ReplaceAtom`'s owner node type must admit the new atom type (`SchemaViolation`); @@ -462,9 +464,9 @@ recorded obstruction: 12. the Action-batch operation limit (`BudgetExceeded`), tested only for a candidate with no blockers, as today's scheduler does. -Steps 2 through 9 yield `NotDerived`, and steps 10 through 12 yield +Steps 1 through 9 yield `NotDerived`, and steps 10 through 12 yield `Derived`. Within a step that examines several effects, the first failing -effect in canonical order decides; only steps 6 and 9 can yield more than +effect in canonical order decides; only steps 1 and 9 can yield more than one kind, so only they depend on that tie-break for their kind. ## Visibility and referential integrity @@ -547,7 +549,9 @@ held; this design validates only what the proposal adds or retargets. and it has no effects, so its dependency set is incomplete, and it is never reclassified as `BasisChanged`. It commits nothing either way. - **Order.** For a proposal, the history check runs immediately after - evaluation and before any validation. If a dependency changed, the + evaluation and the origin resolution of step 1 of the + [Validation order](#validation-order), and before any other + validation. If a dependency changed, the Action obstructs with `BasisChanged`, posture `Proposed` with patch tag `NotDerived`, and keeps its witnessed read set and the dependency it names as changed. No patch is derived from a @@ -558,8 +562,8 @@ held; this design validates only what the proposal adds or retargets. - **Within one Tick, footprints before earlier writes.** Every check before composition, preconditions and expected values included, runs against the Tick's commit parent, never against writes accepted earlier in the same - Tick. The one exception is warp reachability, which is checked at the - evaluation basis (see [Validation order](#validation-order)). Composition then reserves each candidate's complete footprint + Tick. The exceptions are resolution and warp reachability, which run + at the evaluation basis (see [Validation order](#validation-order)). Composition then reserves each candidate's complete footprint (witnessed reads, precondition reads, and write set) in canonical order, and any overlap with an earlier accepted Action in which either of the two writes the slot is `RejectedFootprintConflict` with its blockers, as the @@ -655,12 +659,27 @@ Consequences: binds `lawpack_identity` into its canonical bytes and identities. A source-compiled bridge package therefore uses new versions, `ExecutableOperationPackageV2` and `EchoOperationSemanticClosureV2`. They - are identical to V1 except that the lawpack binding is a tagged option: - `0` absent, or `1` followed by the V1 lawpack coordinate and identity. A - bridge package writes `0`; no sentinel lawpack is fabricated. V2 canonical - bytes carry their own version domain tags, so V2 package, - installed-operation, and semantic-closure identities never coincide with - V1 identities. V1 encoding, validation, and identities are unchanged. + are identical to V1 except in two fields: + - the lawpack binding is a tagged option: `0` absent, or `1` followed by + the V1 lawpack coordinate and identity. A bridge package writes `0`; + no sentinel lawpack is fabricated; + - the program is an `EchoOperationProgramV2`, a tagged union, because + `EchoOperationProgramV1` has only the two fixed profiles. Tags `0` + and `1` are those profiles, with their V1 fields in their V1 order. + Tag `2` is `EdictEffectProgram`, followed by: the compiled program + bytes, preceded by their length as a little-endian `u64`; the + compiler's Target IR identity; the evaluator ABI and + intrinsic-profile identity; the identity-law identity; the schema + identity; and the footprint law's canonical bytes, preceded by their + length as a little-endian `u64`. Identities are 32 bytes each. The + evaluator dispatches on the tag: `0` and `1` run today's profiles + unchanged, and `2` runs the compiled program through the bridge this + document defines. + + V2 canonical bytes carry their own version domain tags, so V2 program, + package, installed-operation, and semantic-closure identities never + coincide with V1 identities. V1 encoding, validation, and identities are + unchanged. ## Evidence @@ -758,10 +777,13 @@ sort by slot class tag, then by those identity bytes compared lexicographically. Each entry is the slot encoding followed by what that set records for the slot: -- read set: an outcome tag (`0` present, `1` absent) and, when present, the - 32-byte value digest defined below; a range read records the observed edges - as a list of edge id and target node id pairs, sorted by edge id and - preceded by its count as a little-endian `u64`; +- read set: for a node record, attachment, or edge slot, an outcome tag + (`0` present, `1` absent) and, when present, the 32-byte value digest + defined below. A range-key slot has no outcome tag and no value digest: + its entry is the slot encoding, then the observed edge count as a + little-endian `u64`, then that many pairs of edge local id and target node + local id, 32 bytes each, sorted by edge id. An empty range is a count of + zero, never an absent outcome; - target set and write set: the slot alone; - dependency set: the descriptor encoding defined under [Dependency descriptors](#evidence) above; @@ -874,11 +896,12 @@ identities are 32 bytes. The section is: `ContentIdentityConflict` `9`, `DanglingReference` `10`, and `FootprintMismatch` `11`. -Every tag is one byte. Receipt digest version 5 hashes, in place of each -version-4 entry, that entry's version-4 bytes followed by its bridge section -exactly as `EPRV0004` retains it, length included, under the version-5 -domain. An `EPRV0004` decoder refuses a section whose length disagrees with -its contents or that carries trailing bytes. +Every tag is one byte. The receipt digest version 5 preimage is the version-4 +preimage with two changes: its leading version tag, a little-endian `u16` in +the same position as versions 2 through 4, is `5`; and each entry's version-4 +bytes are followed by its bridge section exactly as `EPRV0004` retains it, +length included. An `EPRV0004` decoder refuses a section whose length disagrees +with its contents or that carries trailing bytes. **Retained Action outcome.** Today's `EOACT003` record writes an obstruction as a code from `EchoOperationObstructionKindV1`, which has none of the @@ -1106,6 +1129,7 @@ Each behaviour below needs a test before implementation: | Determinism | Same package, invocation, and evaluation basis give byte-identical proposals, proposal identities, read-set and write-set digests, and derived patches, whether the Action is evaluated alone or beside other Actions. Receipts and composite patches are compared only for identical Tick contexts. | | Canonical order | Emitting the same effects in different orders yields the same canonical `EffectProposal` bytes and proposal identity. | | Encoding vectors | Fixed byte vectors for the proposal encoding, every value digest, every set digest (including a descriptor list with both comparison modes), and an `EPRV0004` entry of each posture, validity, and evidence tag reproduce the specified bytes and receipt-v5 digests. | +| Range-read entries | An empty observed range encodes as a range entry with count zero, never as an absent outcome, and a nonempty range's entry matches the specified layout byte for byte. | | Canonical slots | Differently expressed effects on one slot (`EnsureEdge` and `ReplaceEdge` of one edge id; `CreateNode` and `ReplaceAtom` of one node) obstruct with `DuplicateEffectTarget`. | | Write set | The derived patch's actual footprint equals the write set, including the atom slots of created nodes and the range key of every created or retargeted edge, so a committed `EnsureEdge` and `ReplaceEdge` pass the footprint check. | | Identity law | A proposed node or edge id that does not match the package's identity law obstructs with `IdentityLawViolation`, posture `Proposed`, `NotDerived`, and recovery reproduces it. | @@ -1119,10 +1143,11 @@ Each behaviour below needs a test before implementation: | Resolved footprint | A `ReplaceEdge` whose resolved source node lies outside the footprint law obstructs with `FootprintLawViolation`. | | Restored value (ABA) | A dependency written A to B and back to A between origin and commit parent is found through the written-slot set and obstructs with `BasisChanged`, including when the intervening patch under-declared its `out_slots`; with one replay patch in the interval missing, the Action obstructs before evaluation as a whole-frontier `BasisChanged`, posture `NoProposal`, with an empty read set. | | Scoped preconditions | A dependency-scoped Action whose `ReplaceAtom` expected value, or one of whose edge endpoints, changed and was restored between its origin and the commit parent obstructs with `BasisChanged`, though the program never read that slot; recovery reproduces the outcome and its changed dependency. | +| Origin resolution | A scoped `ReplaceEdge` whose program never read the edge, and whose source node is deleted and recreated between origin and commit parent while the edge is unchanged, obstructs with `BasisChanged`, because resolution at the origin put the source in its dependency set. | | Scoped conflict recovery | A scoped Action rejected because an earlier Action in its Tick replaced a value it expects recovers as `RejectedFootprintConflict`, not `PreconditionMismatch`. | | Scoped evidence forms | An evaluation obstruction records `OriginOnly`, a dependency-scoped `BasisChanged` records `HistoryChanged` with no compared values, and every other scoped outcome records `Compared` over commit-parent values; none is compared at a validation position that includes an earlier overlapping write. | | Retry basis | A new submission selected after the head advanced is evaluated at its recorded basis, not at the selection-time state. | -| Receipt digest v5 | A version-5 receipt digest changes when any bridge field changes, including the validation position, a dependency's comparison mode, or a compared value; version-4 receipt identities are unchanged. | +| Receipt digest v5 | A version-5 receipt digest begins with the little-endian `u16` `5` and changes when any bridge field changes, including the validation position, a dependency's comparison mode, or a compared value; version-4 receipt identities are unchanged. | | Proposal binding | For every `Proposed` outcome class in the recovery matrix, including rejected ones, altering one recorded effect-derived digest or the proposal identity refuses the history. | | No-proposal evidence | An evaluation that obstructs before proposing records `NoProposal` with its read-set digest and no proposal, write-set, or patch digest. | | Recovery | Crash before and after the decided-Tick WAL record; fresh-host recovery reproduces the same state root and receipts. | @@ -1142,7 +1167,7 @@ Each behaviour below needs a test before implementation: | Scoped basis change | A dependency-scoped Action evaluated at its origin obstructs with `BasisChanged`, posture `Proposed` with `NotDerived`, and keeps its proposal, read set, and changed dependency when a dependency changed, even if its proposal would also fail validation; an unchanged dependency set lets it commit. | | Scoped evaluation obstruction | A dependency-scoped Action whose evaluation exhausts its budget, or fails an authored guard, keeps that obstruction with `NoProposal` even when one of its reads changed after the origin; recovery reproduces it. | | Receipt format | `EPRV0003` records recover byte for byte through the unchanged decoder; new Ticks are written as `EPRV0004`; a Tick mixing a bridge Action with fixed-profile Actions encodes the latter as `NotBridge`; a history mixing both formats recovers. | -| Package version | A source-compiled package encodes as `ExecutableOperationPackageV2` with lawpack tag `0`; V1 packages keep their bytes and identities, and no V2 identity equals a V1 identity. | +| Package version | A source-compiled package encodes as `ExecutableOperationPackageV2` with lawpack tag `0` and an `EdictEffectProgram` (program tag `2`) that the evaluator dispatches to the bridge; a V2 package carrying a fixed profile runs it unchanged; V1 packages keep their bytes and identities, and no V2 identity equals a V1 identity. | | Outcome record | A bridge obstruction, including an authored one such as `BasisNotCanonical`, is retained as `EOACT004` with the same coordinate as its receipt; fixed-profile Actions still write `EOACT003`, and mixed histories recover. | | Recovered evidence | For each outcome class, recovery performs that class's existing verification plus the new comparisons (posture, proposal identity, read-set, write-set, and patch digests where the class reconstructs or re-evaluates); a tampered digest refuses the history, a whole-frontier `BasisChanged` is not re-evaluated, the batch composition is reconstructed for every class, and no recorded outcome changes. | | Pending recovery | An Action lost with its Tick before the WAL record is recovered as the same submission, not a new one. | From 6f9d4b59104249c81629109bcbaf85e2a4a2f688 Mon Sep 17 00:00:00 2001 From: James Ross Date: Fri, 16 Oct 2026 19:09:34 -0700 Subject: [PATCH 27/50] docs: specify bridge records as zero-copy images and an arena-only hot path Owner direction: Echo owns its memory, and bridge records are read in place rather than hydrated. - Replace the inline byte layouts with an "Encoding and memory" section in the WSC style. Records are #[repr(C)] bytemuck Pod rows (SlotRow, ReadRow, EdgePairRow, EffectRow, DependencyRow, OutcomeRow, BridgeEntryRow): little-endian, 8-byte aligned, explicit zero padding, with variable bytes in a packed blob addressed by WSC Ranges. Each value has exactly one canonical image; digests are BLAKE3 over exact image bytes; readers validate in place before trusting a view. - EPRV0004 appends an indexable BridgeEntryRow table; receipt v5 hashes it after the v4 preimage under u16 version 5; EOACT004 carries an OutcomeRow. - Memory: one reused per-scheduler arena, reset per Tick. Records are built in final form, and capacity is fixed from declared maxima before evaluation, so evaluation, validation, derivation and composition never allocate. Arena capacity never affects decisions; a growth failure is a host fault. - Review round 18: OriginOnly now covers resolution failures as well as evaluation obstructions, so every outcome decided before the history check has evidence. - Adds in-place validation, no-hot-path-allocation, arena-independence and resolution-failure test rows. Co-Authored-By: Claude Opus 5.5 --- .../authored-operation-effects.md | 258 ++++++++++-------- 1 file changed, 141 insertions(+), 117 deletions(-) diff --git a/docs/architecture/authored-operation-effects.md b/docs/architecture/authored-operation-effects.md index 8337a852..f95bc188 100644 --- a/docs/architecture/authored-operation-effects.md +++ b/docs/architecture/authored-operation-effects.md @@ -218,12 +218,9 @@ Rules: lexicographically. A tie can occur only in a proposal that obstructs with `DuplicateEffectTarget`; it breaks by effect kind (`EnsureNode`, `CreateNode`, `ReplaceAtom`, `EnsureEdge`, - `ReplaceEdge`), then by the effect's canonical encoding bytes. That - encoding includes every field of the effect, expected values included: a - kind tag, then each field in the order the effect lists it, with each - instance-scoped key as its warp id then its local id, ids, type ids, and - digests at fixed width, and an atom as its `type_id`, its byte length as a - little-endian `u64`, then its bytes. Sorting never + `ReplaceEdge`), then by the effect's `EffectRow` bytes, then by its atom + bytes (see [Encoding and memory](#encoding-and-memory)); every field of + the effect, expected values included, is in those bytes. Sorting never removes an effect, so duplicates stay visible to the one-effect-per-slot rule. - **One effect per slot.** No two effects in one proposal may target the same @@ -725,8 +722,10 @@ Each Action's receipt entry binds, in addition to its current fields: decision requires: the origin basis, the validation position, and one of three evidence forms, each recording only a comparison that actually ran: - - `OriginOnly`, for an evaluation obstruction, which runs no - dependency comparison; + - `OriginOnly`, for every outcome decided before the history check: + an evaluation obstruction, or a failure of resolution (step 1 of + the [Validation order](#validation-order)). No dependency + comparison ran; - `HistoryChanged`, for a dependency-scoped `BasisChanged`: the dependency-set digest over each dependency's descriptor and comparison mode, and the first changed dependency in canonical @@ -749,8 +748,8 @@ Each Action's receipt entry binds, in addition to its current fields: ran (whole-frontier `BasisChanged`, including unknown history), it is zero on every axis, and recovery verifies that. -**Dependency descriptors.** A dependency descriptor is its slot, its kind, -and its comparison mode, each kind and mode a one-byte tag: +**Dependency kinds and modes.** A dependency is its slot, its kind, and its +comparison mode: - kind: value present `0`, observed absent `1`, expected value `2`, endpoint existence `3`; @@ -762,46 +761,10 @@ and its comparison mode, each kind and mode a one-byte tag: A scoped Action reads no edge ranges (an edge read keeps the whole-frontier rule), so its dependency slots are node records, attachments (atoms and -portals), and edges. Descriptors sort by slot encoding (defined below), then -kind tag, then mode tag; each encodes as kind tag, slot encoding, mode tag; -and the dependency-set digest hashes them in that order. - -**Canonical set encoding.** The read-set, target-set, write-set, -dependency-set, and compared-values digests each hash a set of slot entries, -never a collection in discovery order. A slot encodes as its class tag followed -by its identity, all at fixed width: for a node record, its `NodeKey` (warp id, -then local id); for an edge, its `EdgeKey`; for an attachment, an owner tag -(`0` node, `1` edge), the owner's key, and a plane tag (`0` alpha, `1` beta); -for a range key, the source `NodeKey` followed by the edge-type id. Entries -sort by slot class tag, then by those identity bytes compared -lexicographically. Each entry is the slot encoding followed by what that set -records for the slot: - -- read set: for a node record, attachment, or edge slot, an outcome tag - (`0` present, `1` absent) and, when present, the 32-byte value digest - defined below. A range-key slot has no outcome tag and no value digest: - its entry is the slot encoding, then the observed edge count as a - little-endian `u64`, then that many pairs of edge local id and target node - local id, 32 bytes each, sorted by edge id. An empty range is a count of - zero, never an absent outcome; -- target set and write set: the slot alone; -- dependency set: the descriptor encoding defined under - [Dependency descriptors](#evidence) above; -- compared values: one entry per dependency, in canonical descriptor order, - each the descriptor encoding, a presence tag (`0` present, `1` absent at - the commit parent as the pre-composition checks read it, or `2` not - compared), and, for `0`, the 32-byte digest of the value compared, encoded - as for the read set. - -Each digest is BLAKE3 over a domain tag naming the set and its version, the -entry count as a little-endian `u64`, and the entries in order. Live -execution and recovery therefore produce identical bytes whatever order an -implementation visits slots in. - -Slot class tags are node record `0`, attachment `1`, edge `2`, and range key -`3`. -Ids, type ids, and warp ids are 32 bytes each. A present slot's value digest -is BLAKE3 over a class-specific domain tag and its canonical fields: +portals), and edges. + +**Value digests.** A present slot's value digest is BLAKE3 over a +class-specific domain tag and its canonical fields: - node record: `echo.bridge.node-value.v1\0`, then the node type id; - attachment: `echo.bridge.attachment-value.v1\0`, then for an atom tag `0`, @@ -811,29 +774,17 @@ is BLAKE3 over a class-specific domain tag and its canonical fields: - edge: `echo.bridge.edge-value.v1\0`, then the edge type id and the local `from` and `to` node ids. -The set digests use the domain tags `echo.bridge.read-set.v1\0`, -`echo.bridge.target-set.v1\0`, `echo.bridge.write-set.v1\0`, -`echo.bridge.dependency-set.v1\0`, and `echo.bridge.compared-values.v1\0`. +The read-set, target-set, write-set, dependency-set, and compared-values +digests, and the byte images they hash, are defined in +[Encoding and memory](#encoding-and-memory). The submission identity is present in every variant. An identified submission whose evaluation produced no proposal is a different evidence state from an evaluated proposal, and the receipt keeps them distinct. -**Proposal encoding.** The proposal identity is BLAKE3 over -`echo.bridge.effect-proposal.v1\0` followed by: - -1. the Action: its installed-operation identity and its canonical invocation - digest, as ADR 0023 binds them; -2. the evaluation basis: the `EchoOperationEvaluationBasisV1` identity; -3. the read-set digest, which stands for `readSet`; -4. the effect count as a little-endian `u64`, then each effect's canonical - encoding (see [EffectProposal](#effectproposal)) in canonical order; -5. the result: its length as a little-endian `u64`, then its bytes; -6. the cost: steps, read bytes, write bytes, and allocated bytes, each a - little-endian `u64`. - -Effect kind tags are `EnsureNode` `0`, `CreateNode` `1`, `ReplaceAtom` `2`, -`EnsureEdge` `3`, and `ReplaceEdge` `4`. +**Proposal identity.** The proposal identity is BLAKE3 over +`echo.bridge.effect-proposal.v1\0` followed by the proposal's canonical +image, defined in [Encoding and memory](#encoding-and-memory). The proposal identity is `H(Action, evaluation basis, canonical EffectProposal)` in this encoding, and the canonical `EffectProposal` contains @@ -863,54 +814,12 @@ have different receipt identities, and a version-4 receipt identity never changes meaning. Ticks that mix bridge and `NotBridge` entries encode in `EPRV0004` without fabricated evidence. -**Receipt layout.** An `EPRV0004` entry is the `EPRV0003` entry fields, -unchanged, followed by a bridge section preceded by its byte length as a -little-endian `u64`. Integers are little-endian `u64` and digests and -identities are 32 bytes. The section is: - -1. the posture tag: `NotBridge` `0`, `NoProposal` `1`, `Proposed` `2`. A - `NotBridge` section ends here; -2. for `Proposed`: the proposal identity, the target-set digest, and the - patch tag (`NotDerived` `0`, `Derived` `1`), followed for `Derived` by - the write-set digest and the patch digest; -3. the read-set digest; -4. the validity tag (`WholeFrontier` `0`, `Scoped` `1`). For `Scoped`: the - origin basis identity, the validation position (the Action's index in - the Tick's canonical candidate order), and an evidence tag - (`OriginOnly` `0`, `HistoryChanged` `1`, `Compared` `2`). For - `HistoryChanged`: the dependency-set digest and the changed dependency's - descriptor encoding. For `Compared`: the dependency-set digest and the - compared-values digest; -5. the consumed budget against the delegated `EchoOperationBudgetV1` axes, - steps, read bytes, and write bytes, followed by the evaluator's allocated - bytes; -6. the outcome coordinate: `0` none (committed, or - `RejectedFootprintConflict`), `1` a bridge obstruction kind followed by - its one-byte code, or `2` an authored obstruction (such as - `BasisNotCanonical`) followed by its 32-byte package-declared obstruction - identity, qualified by the package and installed operation as the ADR - 0025 supersession states. Bridge kind codes are `BasisChanged` `0`, - `BudgetExceeded` `1`, `DuplicateEffectTarget` `2`, `IdentityLawViolation` - `3`, `UnreachableWarp` `4`, `SchemaViolation` `5`, `FootprintLawViolation` - `6`, `NodeMissing` `7`, `PreconditionMismatch` `8`, - `ContentIdentityConflict` `9`, `DanglingReference` `10`, and - `FootprintMismatch` `11`. - -Every tag is one byte. The receipt digest version 5 preimage is the version-4 -preimage with two changes: its leading version tag, a little-endian `u16` in -the same position as versions 2 through 4, is `5`; and each entry's version-4 -bytes are followed by its bridge section exactly as `EPRV0004` retains it, -length included. An `EPRV0004` decoder refuses a section whose length disagrees -with its contents or that carries trailing bytes. - -**Retained Action outcome.** Today's `EOACT003` record writes an obstruction -as a code from `EchoOperationObstructionKindV1`, which has none of the -bridge kinds and no authored obstruction. A bridge Action's retained outcome -is therefore written as `EOACT004`: the `EOACT003` layout with its one-byte -obstruction kind replaced by the outcome coordinate above. Fixed-profile -Actions keep `EOACT003`, its decoder is unchanged, and each record decodes -by its own magic. Recovery requires the `EOACT004` coordinate to equal the -receipt's bridge-section coordinate. +**Receipt and outcome records.** The `EPRV0004` bridge table, the receipt +digest version 5 preimage, and the `EOACT004` retained Action outcome are +defined in [Encoding and memory](#encoding-and-memory). Fixed-profile +Actions keep `EOACT003`, its decoder is unchanged, and each record decodes by +its own magic. Recovery requires the `EOACT004` outcome coordinate to equal +the receipt's bridge-row coordinate. The implementation adds this row to [Submission Attribution's format table](../topics/SubmissionAttribution.md#format-binding-and-legacy) when it lands: @@ -984,6 +893,117 @@ above likewise applies to every row recorded as `Scoped`. | `BasisChanged`, dependency-scoped (after evaluation, before validation) | `Proposed`, `NotDerived` | none today: current programs decide this before evaluation | re-evaluate at the origin; effects bound; read-set digest; recompute the written-slot set from replay operations, the complete dependency set (witnessed reads and effect preconditions), and the changed dependency | | Any outcome of a `NotBridge` entry | `NotBridge` | unchanged | none; no bridge field present | +## Encoding and memory + +Every bridge record is a zero-copy image in the style of Echo's WSC snapshot +format (`crates/warp-core/src/wsc/`). It is read in place through a view that +a validator has checked, and it is never hydrated into owned structures. The +rules: + +- **Rows.** Every fixed-size row is `#[repr(C)]` and `bytemuck` `Pod` and + `Zeroable`, with explicit padding fields. Integers are little-endian, ids + and digests are 32 bytes, and every row size is a multiple of 8 bytes. +- **Images.** An image is an 8-byte magic, a fixed header, and sections of + rows, each section starting 8-byte aligned. Variable-length bytes live in + a trailing blob section and are addressed by a 16-byte `Range` + (`start_le`, `len_le`), WSC's range row. +- **One canonical image per value.** Rows appear in the sort order given + below. Padding, and every field a row's tags make unused, are zero. Blob + bytes are packed in row order, each run starting 8-byte aligned, with zero + bytes between runs and none after the last. Two images of one value are + therefore byte-identical, and every digest below is BLAKE3 over a domain + tag and exact image bytes. +- **Validation before trust.** A reader checks the magic, the sizes, + alignment, tags, zero padding and zero unused fields, sort order, range + bounds, and blob packing before using a view. Any failure refuses the + history. Validation runs in place and allocates nothing. + +**Rows.** + +| Row | Bytes | Fields, in order | +| ---------------- | ----- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `SlotRow` | 104 | `class` u8 (node record `0`, attachment `1`, edge `2`, range key `3`); `owner` u8 (attachment: node `0`, edge `1`); `plane` u8 (attachment: alpha `0`, beta `1`); 5 padding bytes; `warp_id`; `local_id` (the node, edge, attachment owner, or range source); `edge_type` (range key only) | +| `ReadRow` | 160 | `slot` (`SlotRow`); `outcome` u8 (present `0`, absent `1`, range `2`); 7 padding bytes; `value_digest` (present only); `pairs` (`Range` into the pair section; range only) | +| `EdgePairRow` | 64 | `edge_local_id`; `target_local_id` | +| `EffectRow` | 280 | `kind` u8 (`EnsureNode` `0`, `CreateNode` `1`, `ReplaceAtom` `2`, `EnsureEdge` `3`, `ReplaceEdge` `4`); 7 padding bytes; `key_warp_id`; `key_local_id`; `type_id` (node type, or `EnsureEdge`'s edge type); `atom_type_id`; `atom` (`Range` into the blob); `expected_digest` (`ReplaceAtom`); `from_local_id` (`EnsureEdge`); `to_local_id` (`EnsureEdge`, and `ReplaceEdge`'s new target); `expected_to_local_id` (`ReplaceEdge`) | +| `DependencyRow` | 152 | `slot` (`SlotRow`); `kind` u8; `mode` u8; 6 padding bytes, which together with `slot` form the 112-byte descriptor; `compared` u8 (present `0`, absent `1`, not compared `2`); 7 padding bytes; `compared_digest` (present only) | +| `OutcomeRow` | 40 | `outcome` u8 (none `0`, bridge kind `1`, authored `2`); `code` u8 (the bridge kind code); 6 padding bytes; `authored_id` (authored only) | +| `BridgeEntryRow` | 448 | `posture`, `patch`, `validity`, and `evidence` tags, u8 each; `outcome` and `code` as in `OutcomeRow`; 2 padding bytes; `proposal_id`; `target_set`; `write_set`; `patch_digest`; `read_set`; `origin_basis`; `dependency_set`; `compared_values`; `changed_dependency` (a 112-byte descriptor); `authored_id`; `validation_position` u64; `steps`, `read_bytes`, `write_bytes`, and `allocated_bytes`, u64 each | + +Tags not defined above use the values given in [Evidence](#evidence): +posture `NotBridge` `0`, `NoProposal` `1`, `Proposed` `2`; patch `NotDerived` +`0`, `Derived` `1`; validity `WholeFrontier` `0`, `Scoped` `1`; evidence +`OriginOnly` `0`, `HistoryChanged` `1`, `Compared` `2`. Bridge kind codes are +`BasisChanged` `0`, `BudgetExceeded` `1`, `DuplicateEffectTarget` `2`, +`IdentityLawViolation` `3`, `UnreachableWarp` `4`, `SchemaViolation` `5`, +`FootprintLawViolation` `6`, `NodeMissing` `7`, `PreconditionMismatch` `8`, +`ContentIdentityConflict` `9`, `DanglingReference` `10`, and +`FootprintMismatch` `11`. A `NotBridge` row is all zero after its posture. + +**Sort orders.** `SlotRow`s sort by `class`, `warp_id`, `local_id`, +`edge_type`, `owner`, then `plane`, so a set's rows are in slot order. +`ReadRow`s and `DependencyRow`s sort by their slot, and `DependencyRow`s with +one slot by `kind`, then `mode`. A range's `EdgePairRow`s are contiguous, +sorted by `edge_local_id`, and the ranges follow `ReadRow` order. `EffectRow`s +follow the canonical effect order in [EffectProposal](#effectproposal). + +**Images and digests.** + +- **Proposal** (magic `EBPRP001`): after the magic, a 192-byte header holding + the installed-operation identity, the canonical invocation digest, the + `EchoOperationEvaluationBasisV1` identity, the read-set digest, the cost + (`steps`, `read_bytes`, `write_bytes`, `allocated_bytes`), the result as a + `Range` into the blob, the effect count, and the blob length; then the + `EffectRow`s; then the blob, holding the atoms in effect order and then the + result. The proposal identity is BLAKE3 over + `echo.bridge.effect-proposal.v1\0` and the image. +- **Sets.** The read-set digest is BLAKE3 over `echo.bridge.read-set.v1\0`, + the `ReadRow` count as a little-endian `u64`, the `ReadRow`s, the + `EdgePairRow` count, and the `EdgePairRow`s. The target-set and write-set + digests hash their domain tags (`echo.bridge.target-set.v1\0`, + `echo.bridge.write-set.v1\0`), the count, and the `SlotRow`s. The + dependency-set digest hashes `echo.bridge.dependency-set.v1\0`, the count, + and each `DependencyRow`'s 112-byte descriptor. The compared-values digest + hashes `echo.bridge.compared-values.v1\0`, the count, and the whole + `DependencyRow`s. +- **`EPRV0004`**: the `EPRV0003` record, unchanged, followed by an 8-byte + aligned bridge table of one `BridgeEntryRow` per receipt entry, in entry + order. The table is indexable in place. +- **Receipt digest version 5**: the version-4 preimage with its leading + little-endian `u16` version tag set to `5`, in the same position as + versions 2 through 4, followed by the bridge table bytes exactly as + `EPRV0004` retains them. +- **`EOACT004`**: the `EOACT003` layout with its one-byte obstruction kind + replaced by an `OutcomeRow`. + +**Memory.** The bridge allocates nothing on its hot path: + +- **One reused arena per scheduler.** The scheduler owns a bridge arena that + is reset, never freed, at each Tick boundary, as it already reuses its + radix `scratch` buffer. Every working structure is a typed region in it: + the recording view's `ReadRow`s and `EdgePairRow`s, the emitted + `EffectRow`s and blob, the `DependencyRow`s and `SlotRow`s, the proposal + images, and the bridge table. +- **Built in final form.** Records are written in place as their final + images. Hashing, the WAL append, and receipt construction read those bytes + directly; nothing is copied into intermediate structures. +- **Capacity fixed before evaluation.** Each region's size is a pure + function of the selected candidates' declared footprint-law maxima and + delegated budgets, bounded by the existing batch limits (64 candidates, + 128 operations). The arena grows, if at all, after selection and before + evaluation. Evaluation, validation, derivation, and composition never + allocate. +- **No host-dependent decisions.** Arena capacity never changes which + Actions a Tick contains or how they are decided. If the host cannot grow + the arena, the Tick fails as a host fault and nothing is decided, as on a + crash before the WAL record, rather than obstructing an Action. +- **Recovery reads views.** Recovery validates retained images in place and + reads them through views; it rebuilds proposals in the same arena to + compare them, and allocates nothing per Action. + +`allocated_bytes` remains `edict_pure`'s metered evaluator allocation, which +is evidence about the program, not about host memory. + ## Execution traces These traces follow Jim's flat-text reference `ReplaceRange` (the first @@ -1128,7 +1148,10 @@ Each behaviour below needs a test before implementation: | Budget-stopped evidence | A scoped proposal whose validation read exhausts the budget records `Compared` with that dependency, and every later one, as not compared; no value past the stop is hashed. | | Determinism | Same package, invocation, and evaluation basis give byte-identical proposals, proposal identities, read-set and write-set digests, and derived patches, whether the Action is evaluated alone or beside other Actions. Receipts and composite patches are compared only for identical Tick contexts. | | Canonical order | Emitting the same effects in different orders yields the same canonical `EffectProposal` bytes and proposal identity. | -| Encoding vectors | Fixed byte vectors for the proposal encoding, every value digest, every set digest (including a descriptor list with both comparison modes), and an `EPRV0004` entry of each posture, validity, and evidence tag reproduce the specified bytes and receipt-v5 digests. | +| Encoding vectors | Fixed byte vectors for every row, the proposal image, every set digest, a bridge table with each posture, validity, evidence, and outcome tag, and `EOACT004` reproduce the specified bytes, sizes, and receipt-v5 digests. | +| In-place validation | Each image validates and reads through a view without heap allocation; an image with nonzero padding, a nonzero unused field, an unsorted section, an out-of-bounds range, or loosely packed blob bytes is refused. | +| No hot-path allocation | With an allocation-counting allocator, a warmed-up Tick of bridge Actions performs zero heap allocations during evaluation, validation, derivation, and composition, and recovery of that Tick allocates nothing per Action. | +| Arena independence | The same Ticks decided on hosts with different initial arena capacities produce byte-identical receipts, WAL records, and state roots. | | Range-read entries | An empty observed range encodes as a range entry with count zero, never as an absent outcome, and a nonempty range's entry matches the specified layout byte for byte. | | Canonical slots | Differently expressed effects on one slot (`EnsureEdge` and `ReplaceEdge` of one edge id; `CreateNode` and `ReplaceAtom` of one node) obstruct with `DuplicateEffectTarget`. | | Write set | The derived patch's actual footprint equals the write set, including the atom slots of created nodes and the range key of every created or retargeted edge, so a committed `EnsureEdge` and `ReplaceEdge` pass the footprint check. | @@ -1146,6 +1169,7 @@ Each behaviour below needs a test before implementation: | Origin resolution | A scoped `ReplaceEdge` whose program never read the edge, and whose source node is deleted and recreated between origin and commit parent while the edge is unchanged, obstructs with `BasisChanged`, because resolution at the origin put the source in its dependency set. | | Scoped conflict recovery | A scoped Action rejected because an earlier Action in its Tick replaced a value it expects recovers as `RejectedFootprintConflict`, not `PreconditionMismatch`. | | Scoped evidence forms | An evaluation obstruction records `OriginOnly`, a dependency-scoped `BasisChanged` records `HistoryChanged` with no compared values, and every other scoped outcome records `Compared` over commit-parent values; none is compared at a validation position that includes an earlier overlapping write. | +| Resolution failure evidence | A scoped `ReplaceEdge` whose edge is missing at its origin records `OriginOnly` with `PreconditionMismatch`; recovery reproduces it without running the history check. | | Retry basis | A new submission selected after the head advanced is evaluated at its recorded basis, not at the selection-time state. | | Receipt digest v5 | A version-5 receipt digest begins with the little-endian `u16` `5` and changes when any bridge field changes, including the validation position, a dependency's comparison mode, or a compared value; version-4 receipt identities are unchanged. | | Proposal binding | For every `Proposed` outcome class in the recovery matrix, including rejected ones, altering one recorded effect-derived digest or the proposal identity refuses the history. | From ef5989db617b0b3395229cd30f08746a60aded2f Mon Sep 17 00:00:00 2001 From: James Ross Date: Fri, 16 Oct 2026 19:20:47 -0700 Subject: [PATCH 28/50] docs: close the nineteenth review round on the authored-effects bridge - ADR archive contract: restore ADR 0023's original sentences verbatim. Each supersession note is now a separate banner, as docs/adr/README.md requires (preserve historical text; record supersession in both places). The ADR diff now deletes no original line. - Endpoint existence is a ValueDigest dependency on the endpoint's node record. The second mode is Absence (single-key absence), so only the two kinds the Action validity decision admits are used. - Define range-read metering: count first, one step and 64 read_bytes per edge, and a law-maximum check, all before materializing anything; otherwise BudgetExceeded with NoProposal. - The effect tie-break ignores blob ranges, which are assigned after sorting. - Arena capacity includes max_allocated_bytes, which bounds the result. - Package V2 adds EchoOperationInvocationV2 with an EdictInput kind, validated against the input schema, and admission dispatches by program tag. - Exact EPRV0004 and EOACT004 magics, and the per-variant EOACT004 layout (the outcome coordinate is always encoded). Co-Authored-By: Claude Opus 5.5 --- ...-admitted-executable-operation-packages.md | 30 +++--- .../authored-operation-effects.md | 97 +++++++++++++------ 2 files changed, 85 insertions(+), 42 deletions(-) diff --git a/docs/adr/0023-admitted-executable-operation-packages.md b/docs/adr/0023-admitted-executable-operation-packages.md index 1c6b2880..8a9d18ea 100644 --- a/docs/adr/0023-admitted-executable-operation-packages.md +++ b/docs/adr/0023-admitted-executable-operation-packages.md @@ -451,11 +451,14 @@ and complete obstruction control. This is the preferable long-term authoring experience if repeated operations demonstrate that those constructs belong in Edict. It is rejected for the first vertical because the current compiler's initial lowerable subset is much -smaller. (The superseding decision linked above selects this route, effective -when flyingrobots/echo#889 merges.) Implementing the entire language surface before any hook-free +smaller. Implementing the entire language surface before any hook-free operation runs would turn the first witness into a broad language and runtime design effort and would make `ReplaceRange` define unproven universal syntax. +> **Superseded in part,** effective when flyingrobots/echo#889 merges: the +> superseding decision linked in this record's header selects this route, +> Edict source, for the rope law. The rejection above is kept as written. + ### Bind a canonical declarative executable resource from a Jedit lawpack This route adds only the Edict prerequisites needed to type the real operation, @@ -464,14 +467,16 @@ identity through Core and target lowering, and generate canonical client and package artifacts. The persistent-rope execution law lives in declarative program bytes rather than native code. -This route was selected on 2026-07-18 and was never implemented. The selection -is superseded, effective when flyingrobots/echo#889 merges; see -[Application operation semantics authored in Edict source](../topics/GeneratedRules.md#application-operation-semantics-authored-in-edict-source). -The original rationale follows. It is the smallest route that closes the -execution proof chain without broad Edict expansion. It does not establish a permanent +This route is selected. It is the smallest route that closes the execution +proof chain without broad Edict expansion. It does not establish a permanent rule that complex Edict operations must be authored as external resources. Later Edict syntax may compile to the same target program boundary. +> **Superseded in part,** effective when flyingrobots/echo#889 merges: this +> selection was never implemented and is superseded by +> [Application operation semantics authored in Edict source](../topics/GeneratedRules.md#application-operation-semantics-authored-in-edict-source). +> The selection and its rationale above are kept as written. + ## Verification Evidence Grades The word "independent" is reserved for an implementation that does not merely @@ -564,11 +569,7 @@ by comparing two wrappers around the same algorithm. category rather than a callback-bearing provider revision. - Edict needs fixed-width and explicit-basis operation support plus exact executable-resource binding; this decision does not require a general - recursive language. (Superseded in part, effective when - flyingrobots/echo#889 merges: the rope law no longer needs - executable-resource binding; Edict gains language features only when the - authored source demonstrates the need. See the - [Jedit rope-law closure](#jedit-rope-law-closure) banner.) + recursive language. - The first program format preserves contained-execution obligations without claiming durable child lanes, child ticks, wormholes, or universal WARP wire status. @@ -584,6 +585,11 @@ by comparing two wrappers around the same algorithm. - The existing Jedit planner becomes migration evidence rather than production execution authority. +> **Superseded in part,** effective when flyingrobots/echo#889 merges: the +> rope law no longer needs executable-resource binding, and Edict gains +> language features only when the authored source demonstrates the need. See +> the [Jedit rope-law closure](#jedit-rope-law-closure) banner. + ## Non-Goals - Cyber Kitten syntax or runtime; diff --git a/docs/architecture/authored-operation-effects.md b/docs/architecture/authored-operation-effects.md index f95bc188..150de537 100644 --- a/docs/architecture/authored-operation-effects.md +++ b/docs/architecture/authored-operation-effects.md @@ -209,20 +209,20 @@ Rules: - **Effects are the only writes.** The proposal contains no prepared patch. Echo derives the patch from the effects. -- **Canonical order.** Echo sorts the effects the evaluator emits; the order - in which evaluation produced them never reaches the identity. Each effect - has a primary slot: the node record for `EnsureNode` and `CreateNode`, the - atom for `ReplaceAtom`, and the edge for `EnsureEdge` and `ReplaceEdge`. - Effects sort by the primary slot's class (node record, then attachment, then - edge), then by its warp id bytes, then its local id bytes, compared - lexicographically. A tie can occur - only in a proposal that obstructs with `DuplicateEffectTarget`; it breaks - by effect kind (`EnsureNode`, `CreateNode`, `ReplaceAtom`, `EnsureEdge`, - `ReplaceEdge`), then by the effect's `EffectRow` bytes, then by its atom - bytes (see [Encoding and memory](#encoding-and-memory)); every field of - the effect, expected values included, is in those bytes. Sorting never - removes an effect, so duplicates stay visible to the one-effect-per-slot - rule. +- **Canonical order.** Echo sorts the effects the evaluator emits; the order in + which evaluation produced them never reaches the identity. Each effect has a + primary slot: the node record for `EnsureNode` and `CreateNode`, the atom for + `ReplaceAtom`, and the edge for `EnsureEdge` and `ReplaceEdge`. Effects sort + by the primary slot's class (node record, then attachment, then edge), then by + its warp id bytes, then its local id bytes, compared lexicographically. A tie + can occur only in a proposal that obstructs with `DuplicateEffectTarget`; it + breaks by effect kind (`EnsureNode`, `CreateNode`, `ReplaceAtom`, + `EnsureEdge`, `ReplaceEdge`), then by the effect's `EffectRow` bytes with its + `atom` range zeroed, then by its atom bytes (see [Encoding and + memory](#encoding-and-memory)); every semantic field of the effect, expected + values included, is in those bytes. Blob ranges are assigned only after + sorting, so emission order never reaches them. Sorting never removes an + effect, so duplicates stay visible to the one-effect-per-slot rule. - **One effect per slot.** No two effects in one proposal may target the same slot: one node record, one node's atom, or one edge. Targets are compared by canonical slot identity (the node id, the node id's atom, or the edge @@ -351,6 +351,16 @@ retargets an edge with that source and type claims it as a write, in addition to the edge id. Independence treats range keys like every other key: a write against a read or write conflicts. +A range read is metered before it materializes anything. The view first +takes the range's edge count from the source node's bucket, without copying +an edge, and charges one step and 64 `read_bytes` per edge (one edge local id +and one target local id). If the count exceeds the footprint law's maximum +for that edge type, or the charge exceeds the remaining budget, evaluation +obstructs with `BudgetExceeded`, posture `NoProposal`, before any edge is +copied. Otherwise the view writes the range's `EdgePairRow`s directly into +the arena. Consumed cost and the read set therefore depend only on the basis, +and a high-degree node cannot force an allocation the budget did not cover. + These dependency kinds follow the [Action validity across ticks](../topics/GeneratedRules.md#action-validity-across-ticks) decision (#837). Value and single-key absence dependencies are the kinds that @@ -671,7 +681,18 @@ Consequences: length as a little-endian `u64`. Identities are 32 bytes each. The evaluator dispatches on the tag: `0` and `1` run today's profiles unchanged, and `2` runs the compiled program through the bridge this - document defines. + document defines; + - the invocation is an `EchoOperationInvocationV2`, the V1 canonical + CBOR map with its kind extended to a tagged union: tags `0` and `1` + are the V1 compare-and-set and create-if-absent kinds with their V1 + fields, and tag `2` is `EdictInput`, which carries no anchored node or + replacement and instead requires `application_input_bytes`. Those are + the program's input in `edict.canonical-cbor/v1`, validated at + admission against the package's input schema identity. Admission + dispatches on the program tag: a tag `2` program admits only tag `2` + invocations and the fixed profiles only their own kinds, so no + invocation crosses between them. The V2 invocation identity has its own + domain. V2 canonical bytes carry their own version domain tags, so V2 program, package, installed-operation, and semantic-closure identities never @@ -753,10 +774,13 @@ comparison mode: - kind: value present `0`, observed absent `1`, expected value `2`, endpoint existence `3`; -- comparison mode: `ValueDigest` `0`, which compares the slot's value digest - (value present, expected value), and `Presence` `1`, which compares only - presence (observed absent, endpoint existence). These are the two modes the - Action validity decision supports; revision-sensitive and +- comparison mode: `ValueDigest` `0`, a payload-value dependency that + compares the slot's value digest, and `Absence` `1`, a single-key absence + dependency. These are exactly the two kinds the Action validity decision + admits. Value present, expected value, and endpoint existence use + `ValueDigest`: an endpoint's dependency is on its node record's value, so + it holds only while the node exists with that value. Observed absent uses + `Absence`. Positive presence alone is not a mode. Revision-sensitive and identity-sensitive dependencies are refused at admission and have no tag. A scoped Action reads no edge ranges (an edge read keeps the whole-frontier @@ -966,15 +990,23 @@ follow the canonical effect order in [EffectProposal](#effectproposal). and each `DependencyRow`'s 112-byte descriptor. The compared-values digest hashes `echo.bridge.compared-values.v1\0`, the count, and the whole `DependencyRow`s. -- **`EPRV0004`**: the `EPRV0003` record, unchanged, followed by an 8-byte - aligned bridge table of one `BridgeEntryRow` per receipt entry, in entry - order. The table is indexable in place. +- **`EPRV0004`**: the eight ASCII bytes `EPRV0004` as the record magic, then + the `EPRV0003` payload that follows that record's own magic, byte for byte, + then zero padding to an 8-byte boundary, then a bridge table of one + `BridgeEntryRow` per receipt entry, in entry order. Only the magic differs + from `EPRV0003` before the table, so the unchanged `EPRV0003` decoder never + sees an `EPRV0004` record. The table is indexable in place. - **Receipt digest version 5**: the version-4 preimage with its leading little-endian `u16` version tag set to `5`, in the same position as versions 2 through 4, followed by the bridge table bytes exactly as `EPRV0004` retains them. -- **`EOACT004`**: the `EOACT003` layout with its one-byte obstruction kind - replaced by an `OutcomeRow`. +- **`EOACT004`**: the eight ASCII bytes `EOACT004`, then the `EOACT003` + layout with one change per variant. Every variant writes an `OutcomeRow` + immediately after its one-byte variant tag (committed `1`, obstructed `2`, + conflict `3`). For committed and conflict, it is the all-zero row, outcome + none; for obstructed, it replaces `EOACT003`'s one-byte obstruction kind. + The coordinate is always encoded, never derived, and recovery requires + the decoder to read the zero row for committed and conflict. **Memory.** The bridge allocates nothing on its hot path: @@ -987,10 +1019,11 @@ follow the canonical effect order in [EffectProposal](#effectproposal). - **Built in final form.** Records are written in place as their final images. Hashing, the WAL append, and receipt construction read those bytes directly; nothing is copied into intermediate structures. -- **Capacity fixed before evaluation.** Each region's size is a pure - function of the selected candidates' declared footprint-law maxima and - delegated budgets, bounded by the existing batch limits (64 candidates, - 128 operations). The arena grows, if at all, after selection and before +- **Capacity fixed before evaluation.** Each region's size is a pure function of + the selected candidates' declared footprint-law maxima, delegated budgets, and + the program's `max_allocated_bytes`, which bounds the result bytes the blob + must also hold, all bounded by the existing batch limits (64 candidates, 128 + operations). The arena grows, if at all, after selection and before evaluation. Evaluation, validation, derivation, and composition never allocate. - **No host-dependent decisions.** Arena capacity never changes which @@ -1148,6 +1181,7 @@ Each behaviour below needs a test before implementation: | Budget-stopped evidence | A scoped proposal whose validation read exhausts the budget records `Compared` with that dependency, and every later one, as not compared; no value past the stop is hashed. | | Determinism | Same package, invocation, and evaluation basis give byte-identical proposals, proposal identities, read-set and write-set digests, and derived patches, whether the Action is evaluated alone or beside other Actions. Receipts and composite patches are compared only for identical Tick contexts. | | Canonical order | Emitting the same effects in different orders yields the same canonical `EffectProposal` bytes and proposal identity. | +| Blob-independent order | Two same-kind duplicate effects on one slot with different atoms produce the same canonical image and proposal identity whichever order they were emitted in. | | Encoding vectors | Fixed byte vectors for every row, the proposal image, every set digest, a bridge table with each posture, validity, evidence, and outcome tag, and `EOACT004` reproduce the specified bytes, sizes, and receipt-v5 digests. | | In-place validation | Each image validates and reads through a view without heap allocation; an image with nonzero padding, a nonzero unused field, an unsorted section, an out-of-bounds range, or loosely packed blob bytes is refused. | | No hot-path allocation | With an allocation-counting allocator, a warmed-up Tick of bridge Actions performs zero heap allocations during evaluation, validation, derivation, and composition, and recovery of that Tick allocates nothing per Action. | @@ -1183,6 +1217,7 @@ Each behaviour below needs a test before implementation: | Visibility | No published coordinate shows a retargeted head without every node and edge the proposal introduced; pre-existing structure the head reaches is not validated. | | Witnessed reads | The receipt's read set equals the slots the view resolved, including for a program that reads and then obstructs. | | Negative reads | An Action that observed a node, atom, or edge set absent obstructs when a co-scheduled Action creates it, including a fresh edge of the observed source and type with a new edge id when the observed set was empty (outgoing-edge range key). | +| Range metering | A range read over a node with more edges than the law's maximum, or than the remaining budget covers, obstructs with `BudgetExceeded` and `NoProposal` before copying any edge; an admissible range charges one step and 64 read bytes per edge. | | Edge-read basis | An Action that read edges commits only onto its exact evaluation basis. | | Final outcomes | A rejected Action's outcome, receipt, and attribution are unchanged after a later submission of the same edit succeeds. | | Duplicate resubmission | Resubmitting a decided Action's identical content returns `Duplicate` with the original outcome and commits no Tick. | @@ -1192,14 +1227,16 @@ Each behaviour below needs a test before implementation: | Scoped evaluation obstruction | A dependency-scoped Action whose evaluation exhausts its budget, or fails an authored guard, keeps that obstruction with `NoProposal` even when one of its reads changed after the origin; recovery reproduces it. | | Receipt format | `EPRV0003` records recover byte for byte through the unchanged decoder; new Ticks are written as `EPRV0004`; a Tick mixing a bridge Action with fixed-profile Actions encodes the latter as `NotBridge`; a history mixing both formats recovers. | | Package version | A source-compiled package encodes as `ExecutableOperationPackageV2` with lawpack tag `0` and an `EdictEffectProgram` (program tag `2`) that the evaluator dispatches to the bridge; a V2 package carrying a fixed profile runs it unchanged; V1 packages keep their bytes and identities, and no V2 identity equals a V1 identity. | +| Invocation version | An `EdictInput` (tag `2`) invocation is admitted only for an `EdictEffectProgram` package and is refused for a fixed profile, and a fixed-profile kind is refused for a tag `2` program. | | Outcome record | A bridge obstruction, including an authored one such as `BasisNotCanonical`, is retained as `EOACT004` with the same coordinate as its receipt; fixed-profile Actions still write `EOACT003`, and mixed histories recover. | | Recovered evidence | For each outcome class, recovery performs that class's existing verification plus the new comparisons (posture, proposal identity, read-set, write-set, and patch digests where the class reconstructs or re-evaluates); a tampered digest refuses the history, a whole-frontier `BasisChanged` is not re-evaluated, the batch composition is reconstructed for every class, and no recorded outcome changes. | | Pending recovery | An Action lost with its Tick before the WAL record is recovered as the same submission, not a new one. | ## Open questions -- How `edict_read` exposes outgoing edges by type, and how a bounded closure - read guided by a pure Edict selector is expressed and budgeted. +- How `edict_read` exposes outgoing edges by type to Edict source, and how a + bounded closure read guided by a pure Edict selector is expressed and + budgeted. The metering of a single range read is defined above. - Whether the footprint law is per-invocation or per-package with parameters. - Which domain-separated hash Edict source can call to derive ids. - Whether proposals larger than the Action-batch operation limit should ever From 1d11a1a236553663cd03fbd81199a164ca66e75c Mon Sep 17 00:00:00 2001 From: James Ross Date: Fri, 16 Oct 2026 19:34:46 -0700 Subject: [PATCH 29/50] docs: close the twentieth review round on the authored-effects bridge - Define the executable identity law ContentAddressV1 (package identity_law = 1). Its edge id is BLAKE3 over from, type and to. Its node id is BLAKE3 over type, atom digest and the proposal-created children sorted by (type, to). Unknown laws are refused. This resolves the open question on which hash derives ids. - Package V2 is described as edict.canonical-cbor/v1 maps, like V1, replacing hand-built length prefixes. A fixed-profile program requires a lawpack entry and an EdictEffectProgram forbids one; admission refuses other combinations. - Resolution charges are exact: a node record costs 1 step + 64 read bytes, an edge record 1 step + 128. Resolution also reads endpoint node records, and the resolved schema step enforces source/edge/target type relations. Co-Authored-By: Claude Opus 5.5 --- .../authored-operation-effects.md | 87 ++++++++++++++----- 1 file changed, 63 insertions(+), 24 deletions(-) diff --git a/docs/architecture/authored-operation-effects.md b/docs/architecture/authored-operation-effects.md index 150de537..30d96f19 100644 --- a/docs/architecture/authored-operation-effects.md +++ b/docs/architecture/authored-operation-effects.md @@ -319,6 +319,29 @@ Rules: typed `IdentityLawViolation` obstruction before any patch is derived. Echo does not invent ids. +### Identity laws + +A package names its identity law by number, and Echo recomputes ids under it; +a digest alone could not be executed. One law is defined: + +- **`1`, `ContentAddressV1`.** It covers the ids a proposal creates: the node + of each `EnsureNode` and `CreateNode`, and the edge of each `EnsureEdge`. + `ReplaceAtom` and `ReplaceEdge` name existing ids and are not recomputed. + - An edge id is BLAKE3 over `echo.bridge.edge-id.v1\0`, the warp id, the + `from` local id, the edge type id, and the `to` local id. + - A node id is BLAKE3 over `echo.bridge.node-id.v1\0`, the warp id, the + node type id, the attachment value digest of its atom (see + [Evidence](#evidence)), the number of its outgoing edges that the + proposal's `EnsureEdge` effects give it as a little-endian `u64`, and, + for each such edge sorted by edge type id and then `to` local id, the + edge type id and the `to` local id. + + All ids are 32 bytes. A node's id therefore commits to its content and to + the children the same proposal links it to, as Jim's rope requires, and an + edge's id commits to its endpoints and type. A program computes the same + BLAKE3 inputs; Edict must expose them before a tag `2` package can be + produced. + ## Witnessed reads over a bounded slice The program never declares what it read. Evaluation reaches state only through @@ -435,12 +458,17 @@ decides the outcome, so a proposal that breaks several rules has exactly one recorded obstruction: 1. resolution at the evaluation basis (for a scoped Action, its origin): in - canonical effect order, read and charge each `ReplaceAtom`'s node record - and each `ReplaceEdge`'s edge record, the facts those effects do not - carry (`NodeMissing` for a missing node, `PreconditionMismatch` for a - missing edge, or `BudgetExceeded` when the read exceeds the remaining - budget). The resolved records, and a `ReplaceEdge`'s source endpoint, - join the dependency set, so the history check sees them; + canonical effect order, read and charge the facts the effects do not + carry. That means each `ReplaceAtom`'s node record, each `ReplaceEdge`'s + edge record, and the node record of each endpoint of a creating + `EnsureEdge` or a `ReplaceEdge` that the proposal does not create. Each + node-record read costs one step and 64 `read_bytes`, and each edge-record + read one step and 128 `read_bytes`, the sizes of WSC's node and edge rows. + Failures are `NodeMissing` for a `ReplaceAtom`'s missing node, + `PreconditionMismatch` for a missing edge, or `BudgetExceeded` when a + read exceeds the remaining budget. A missing endpoint is recorded as + absent and reported as `DanglingReference` at step 9. The resolved + records join the dependency set, so the history check sees them; 2. the dependency-scoped history check, for a scoped Action (`BasisChanged`); 3. one effect per slot (`DuplicateEffectTarget`); @@ -455,8 +483,11 @@ recorded obstruction: `CreateNode`, which carry both, the node type must admit the atom type (`SchemaViolation`); 7. the schema over resolved types: a `ReplaceEdge`'s preserved edge type - must be admitted, and a `ReplaceAtom`'s owner node type must admit the - new atom type (`SchemaViolation`); + must be admitted; a `ReplaceAtom`'s owner node type must admit the new + atom type; and for every creating `EnsureEdge` and every `ReplaceEdge`, + the schema must admit the edge between its source and target node types, + taken from the resolved records or from the proposal's own node effects + (`SchemaViolation`); 8. the footprint law, now that every slot is known: the witnessed read set, every precondition slot (including a `ReplaceEdge`'s resolved source), the target set, the resolved write set with its range keys, and their @@ -667,21 +698,25 @@ Consequences: source-compiled bridge package therefore uses new versions, `ExecutableOperationPackageV2` and `EchoOperationSemanticClosureV2`. They are identical to V1 except in two fields: - - the lawpack binding is a tagged option: `0` absent, or `1` followed by - the V1 lawpack coordinate and identity. A bridge package writes `0`; - no sentinel lawpack is fabricated; - - the program is an `EchoOperationProgramV2`, a tagged union, because - `EchoOperationProgramV1` has only the two fixed profiles. Tags `0` - and `1` are those profiles, with their V1 fields in their V1 order. - Tag `2` is `EdictEffectProgram`, followed by: the compiled program - bytes, preceded by their length as a little-endian `u64`; the - compiler's Target IR identity; the evaluator ABI and - intrinsic-profile identity; the identity-law identity; the schema - identity; and the footprint law's canonical bytes, preceded by their - length as a little-endian `u64`. Identities are 32 bytes each. The - evaluator dispatches on the tag: `0` and `1` run today's profiles - unchanged, and `2` runs the compiled program through the bridge this - document defines; + - the lawpack binding is optional: the semantic closure's map carries + its `lawpack` entry (the V1 coordinate and identity) or omits it. A + fixed-profile program (tags `0` and `1` below) requires the entry, as + ADR 0023 requires today; an `EdictEffectProgram` (tag `2`) requires its + absence. Admission refuses any other combination, so no sentinel + lawpack is fabricated and no fixed profile loses its lawpack; + - the program is an `EchoOperationProgramV2`, because + `EchoOperationProgramV1` has only the two fixed profiles. Like every + package value it is an `edict.canonical-cbor/v1` map with a `tag` + entry. Tags `0` and `1` are those profiles, with their V1 entries. Tag + `2` is `EdictEffectProgram`, whose entries are `compiled_program` (the + compiled bytes), `target_ir_identity`, + `evaluator_profile_identity` (the evaluator ABI and intrinsic + profile), `identity_law` (an unsigned integer naming an + [identity law](#identity-laws); unknown values are refused), + `schema_identity`, and `footprint_law` (its canonical bytes). + Identities are 32-byte byte strings. The evaluator dispatches on the + tag: `0` and `1` run today's profiles unchanged, and `2` runs the + compiled program through the bridge this document defines; - the invocation is an `EchoOperationInvocationV2`, the V1 canonical CBOR map with its kind extended to a tagged union: tags `0` and `1` are the V1 compare-and-set and create-if-absent kinds with their V1 @@ -1190,10 +1225,12 @@ Each behaviour below needs a test before implementation: | Canonical slots | Differently expressed effects on one slot (`EnsureEdge` and `ReplaceEdge` of one edge id; `CreateNode` and `ReplaceAtom` of one node) obstruct with `DuplicateEffectTarget`. | | Write set | The derived patch's actual footprint equals the write set, including the atom slots of created nodes and the range key of every created or retargeted edge, so a committed `EnsureEdge` and `ReplaceEdge` pass the footprint check. | | Identity law | A proposed node or edge id that does not match the package's identity law obstructs with `IdentityLawViolation`, posture `Proposed`, `NotDerived`, and recovery reproduces it. | +| Content address | Under `ContentAddressV1`, a node id equal to the specified BLAKE3 over its type, atom, and proposal-created children, and an edge id over its endpoints and type, pass; any other id obstructs with `IdentityLawViolation`; fixed vectors pin both derivations. | | Unreachable warp | An effect naming a key in a missing warp, or in a warp no valid portal chain reaches, obstructs with `UnreachableWarp`, posture `Proposed`, `NotDerived`. | | Origin reachability | A scoped Action naming a warp unreachable at its origin obstructs with `UnreachableWarp` even when an intervening Tick created the portal that reaches it. | | Schema | An effect whose node type, atom `type_id`, or edge type the package's schema does not admit obstructs with `SchemaViolation`, posture `Proposed`, `NotDerived`, and recovery reproduces it. | | Resolved schema | A `ReplaceEdge` whose basis edge type the schema does not admit, or a `ReplaceAtom` whose owner node type does not admit the new atom type, obstructs with `SchemaViolation` after its record is read and charged. | +| Endpoint types | A creating `EnsureEdge` or a `ReplaceEdge` whose source or target node type the schema does not admit for that edge type obstructs with `SchemaViolation`, after its endpoint records are read and charged. | | Validation order | A proposal that breaks several rules (for example a duplicate target, an unadmitted type, and a missing endpoint) records the obstruction of the earliest step in the validation order, live and on recovery. | | Conflict before batch limit | A candidate that both overlaps an earlier accepted Action and would exceed the batch operation limit records `RejectedFootprintConflict`, as today's scheduler does. | | Footprint stages | A target outside the footprint law obstructs with `FootprintLawViolation` and `NotDerived`, and so does a `ReplaceEdge` whose basis edge's range key lies outside the law; a derived patch whose footprint differs from the write set obstructs with `FootprintMismatch` and `Derived`. | @@ -1227,6 +1264,7 @@ Each behaviour below needs a test before implementation: | Scoped evaluation obstruction | A dependency-scoped Action whose evaluation exhausts its budget, or fails an authored guard, keeps that obstruction with `NoProposal` even when one of its reads changed after the origin; recovery reproduces it. | | Receipt format | `EPRV0003` records recover byte for byte through the unchanged decoder; new Ticks are written as `EPRV0004`; a Tick mixing a bridge Action with fixed-profile Actions encodes the latter as `NotBridge`; a history mixing both formats recovers. | | Package version | A source-compiled package encodes as `ExecutableOperationPackageV2` with lawpack tag `0` and an `EdictEffectProgram` (program tag `2`) that the evaluator dispatches to the bridge; a V2 package carrying a fixed profile runs it unchanged; V1 packages keep their bytes and identities, and no V2 identity equals a V1 identity. | +| Lawpack by program | A V2 package whose fixed-profile program omits the lawpack, or whose `EdictEffectProgram` carries one, is refused at admission. | | Invocation version | An `EdictInput` (tag `2`) invocation is admitted only for an `EdictEffectProgram` package and is refused for a fixed profile, and a fixed-profile kind is refused for a tag `2` program. | | Outcome record | A bridge obstruction, including an authored one such as `BasisNotCanonical`, is retained as `EOACT004` with the same coordinate as its receipt; fixed-profile Actions still write `EOACT003`, and mixed histories recover. | | Recovered evidence | For each outcome class, recovery performs that class's existing verification plus the new comparisons (posture, proposal identity, read-set, write-set, and patch digests where the class reconstructs or re-evaluates); a tampered digest refuses the history, a whole-frontier `BasisChanged` is not re-evaluated, the batch composition is reconstructed for every class, and no recorded outcome changes. | @@ -1238,7 +1276,8 @@ Each behaviour below needs a test before implementation: bounded closure read guided by a pure Edict selector is expressed and budgeted. The metering of a single range read is defined above. - Whether the footprint law is per-invocation or per-package with parameters. -- Which domain-separated hash Edict source can call to derive ids. +- How Edict source exposes the `ContentAddressV1` inputs; the law itself + is defined above. - Whether proposals larger than the Action-batch operation limit should ever be committable. This design says no: such a proposal is still representable as evidence (`BudgetExceeded`, posture `Proposed` with a `Derived` patch, as From 8fb04a6f32f571e24c71f8f561d4ec6eaf1105b7 Mon Sep 17 00:00:00 2001 From: James Ross Date: Fri, 16 Oct 2026 19:57:11 -0700 Subject: [PATCH 30/50] docs: close the twenty-first review round on the authored-effects bridge - A record created under ContentAddressV1 is immutable. Replacing one is an IdentityLawViolation; mutable anchors (non-content-address ids) may still be replaced. - Warp reachability is now validation step 1, before resolution, so a key in a missing warp is never reported as a missing record. - Scoped eligibility is static: a package whose footprint law admits any edge-range read is always whole-frontier. - The footprint law is per package with no invocation parameters, with its canonical CBOR entries defined; the open question is closed. - The V2 program carries executable schema bytes (node/atom and source/edge/target relations). Admission requires the Target IR, evaluator profile and schema digest to match the closure and package. - Arena capacity is sized per candidate from its own maxima, so an oversized patch is derived in place and rejected. - The lawpack test row matches the normative "entry omitted" encoding. Co-Authored-By: Claude Opus 5.5 --- .../authored-operation-effects.md | 117 +++++++++++------- 1 file changed, 74 insertions(+), 43 deletions(-) diff --git a/docs/architecture/authored-operation-effects.md b/docs/architecture/authored-operation-effects.md index 30d96f19..c5204dc7 100644 --- a/docs/architecture/authored-operation-effects.md +++ b/docs/architecture/authored-operation-effects.md @@ -327,6 +327,13 @@ a digest alone could not be executed. One law is defined: - **`1`, `ContentAddressV1`.** It covers the ids a proposal creates: the node of each `EnsureNode` and `CreateNode`, and the edge of each `EnsureEdge`. `ReplaceAtom` and `ReplaceEdge` name existing ids and are not recomputed. + A record created under this law is immutable: replacing a node's atom or + an edge's target would leave an id that no longer addresses its content. + So a `ReplaceAtom` or `ReplaceEdge` whose target's id equals + `ContentAddressV1` of its current content obstructs with + `IdentityLawViolation`. Mutable records, such as an application's anchor + node (the buffer `B` in the trace), have ids that are not content + addresses and may be replaced. - An edge id is BLAKE3 over `echo.bridge.edge-id.v1\0`, the warp id, the `from` local id, the edge type id, and the `to` local id. - A node id is BLAKE3 over `echo.bridge.node-id.v1\0`, the warp id, the @@ -386,11 +393,14 @@ and a high-degree node cannot force an allocation the budget did not cover. These dependency kinds follow the [Action validity across ticks](../topics/GeneratedRules.md#action-validity-across-ticks) -decision (#837). Value and single-key absence dependencies are the kinds that -decision supports. A read of outgoing edges by type is a topology dependency, -which that decision does not yet support, so an Action that reads edges keeps -the whole-frontier rule: it commits only onto the exact basis it was evaluated -against. Widening that requires sound edge-set tracking under #837, not a +decision (#837). Value and single-key absence dependencies are the kinds that decision +supports. A read of outgoing edges by type is a topology dependency, which that +decision does not yet support. Eligibility is decided statically at admission, +never by the branch an evaluation takes: an Action whose package's footprint law +admits any outgoing-edge range read keeps the whole-frontier rule, whether or +not this invocation reads an edge, and commits only onto the exact basis it was +evaluated against. Every other bridge Action is eligible for dependency-scoped +validity. Widening that requires sound edge-set tracking under #837, not a looser rule here. The view resolves each slot on demand from the basis, as @@ -402,9 +412,14 @@ worldline state, but nothing in this contract may rely on that. ## Footprint law -The package declares a footprint law: the node types, edge types, and maximum -counts an Action may read and write, relative to its invocation. At validation -Echo checks: +The package declares a footprint law, fixed per package with no invocation +parameters. It is an `edict.canonical-cbor/v1` map with these entries: +`read_node_types` and `write_node_types` (sets of node type ids), +`atom_types` (the atom `type_id`s it may read or write), `range_edge_types` +(edge types it may read as outgoing ranges; empty makes it eligible for +scoped validity), `write_edge_types`, and the unsigned counts `max_reads`, +`max_writes`, and `max_range_edges` (per range read). Its canonical bytes are +part of the program's identity. At validation Echo checks: - every node type, atom `type_id`, and edge type an effect writes is admitted by the package's typed graph and attachment schema, which ADR 0023 requires @@ -457,7 +472,13 @@ Validation of a proposal runs in one fixed order, and the first failing step decides the outcome, so a proposal that breaks several rules has exactly one recorded obstruction: -1. resolution at the evaluation basis (for a scoped Action, its origin): in +1. warp reachability of every effect key at the evaluation basis + (`UnreachableWarp`). A warp unreachable at a scoped Action's origin + obstructs even if a later Tick made it reachable, and a reachable warp's + portal chain is already in its dependency set, so step 3 catches any + change to it. It runs first, so a key in a missing warp is never + reported as a missing record; +2. resolution at the evaluation basis (for a scoped Action, its origin): in canonical effect order, read and charge the facts the effects do not carry. That means each `ReplaceAtom`'s node record, each `ReplaceEdge`'s edge record, and the node record of each endpoint of a creating @@ -469,15 +490,10 @@ recorded obstruction: read exceeds the remaining budget. A missing endpoint is recorded as absent and reported as `DanglingReference` at step 9. The resolved records join the dependency set, so the history check sees them; -2. the dependency-scoped history check, for a scoped Action +3. the dependency-scoped history check, for a scoped Action (`BasisChanged`); -3. one effect per slot (`DuplicateEffectTarget`); -4. the identity law (`IdentityLawViolation`); -5. warp reachability of every effect key at the evaluation basis - (`UnreachableWarp`). A warp unreachable at a scoped Action's origin - obstructs even if a later Tick made it reachable, and a reachable warp's - portal chain is already in its dependency set, so step 2 catches any - change to it; +4. one effect per slot (`DuplicateEffectTarget`); +5. the identity law (`IdentityLawViolation`); 6. the schema, over the types each effect itself carries: node types, edge types, and atom `type_id`s must be admitted, and for `EnsureNode` and `CreateNode`, which carry both, the node type must admit the atom type @@ -504,7 +520,7 @@ recorded obstruction: Steps 1 through 9 yield `NotDerived`, and steps 10 through 12 yield `Derived`. Within a step that examines several effects, the first failing -effect in canonical order decides; only steps 1 and 9 can yield more than +effect in canonical order decides; only steps 2 and 9 can yield more than one kind, so only they depend on that tie-break for their kind. ## Visibility and referential integrity @@ -549,9 +565,11 @@ held; this design validates only what the proposal adds or retargets. evaluation. A bridge Action's reads are known only by evaluating it, so the bridge splits basis change in two: - **Whole-frontier `BasisChanged`,** decided before evaluation: the Action - is not eligible for dependency-scoped validity (for example because its - program can read edges) and its evaluation basis is no longer the commit - parent. Posture `NoProposal`, empty read set, no evaluation. + is not eligible for dependency-scoped validity (its footprint law admits + an edge-range read; see + [Witnessed reads](#witnessed-reads-over-a-bounded-slice)) and its evaluation basis is + no longer the commit parent. Posture `NoProposal`, empty read set, no + evaluation. - **Dependency-scoped,** decided after evaluation: the Action is eligible, so Echo evaluates it at its recorded origin basis, then checks its dependency set against the history between that origin and the commit @@ -587,16 +605,16 @@ held; this design validates only what the proposal adds or retargets. and it has no effects, so its dependency set is incomplete, and it is never reclassified as `BasisChanged`. It commits nothing either way. - **Order.** For a proposal, the history check runs immediately after - evaluation and the origin resolution of step 1 of the - [Validation order](#validation-order), and before any other - validation. If a dependency changed, the - Action obstructs with `BasisChanged`, posture `Proposed` with patch tag - `NotDerived`, and keeps its witnessed read set and the dependency it - names as changed. No patch is derived from a - proposal whose dependencies changed, so a proposal that would also have - failed validation (for example `DuplicateEffectTarget`) still has one - encodable outcome. Only when every dependency holds does validation run. - Echo never validates a delayed Action without its actual dependencies. + evaluation and the reachability and origin resolution of steps 1 and 2 of + the [Validation order](#validation-order), and before any other + validation. If a dependency changed, the Action obstructs with + `BasisChanged`, posture `Proposed` with patch tag `NotDerived`, and keeps + its witnessed read set and the dependency it names as changed. No patch is + derived from a proposal whose dependencies changed, so a proposal that + would also have failed validation (for example `DuplicateEffectTarget`) + still has one encodable outcome. Only when every dependency holds does + validation run. Echo never validates a delayed Action without its actual + dependencies. - **Within one Tick, footprints before earlier writes.** Every check before composition, preconditions and expected values included, runs against the Tick's commit parent, never against writes accepted earlier in the same @@ -713,8 +731,20 @@ Consequences: `evaluator_profile_identity` (the evaluator ABI and intrinsic profile), `identity_law` (an unsigned integer naming an [identity law](#identity-laws); unknown values are refused), - `schema_identity`, and `footprint_law` (its canonical bytes). - Identities are 32-byte byte strings. The evaluator dispatches on the + `schema` (canonical bytes, below), and `footprint_law` (its canonical + bytes). Identities are 32-byte byte strings. The `schema` is an + `edict.canonical-cbor/v1` map that Echo executes, not only a digest: + `node_types` maps each admitted node type id to the set of atom + `type_id`s it may carry, and `edge_types` maps each admitted edge type + id to the set of admitted (source node type, target node type) pairs. + Validation steps 6 and 7 read these relations directly. + Admission refuses a package unless the program's entries agree with the + rest of the package: `target_ir_identity` must equal the semantic + closure's Target IR identity, `evaluator_profile_identity` must equal + the identity of the package's interpreter and intrinsic profiles, and + the digest of `schema` under `echo.operation-schema/v1` must equal the + closure's schema digest. No artifact is attested under one identity and + executed under another. The evaluator dispatches on the tag: `0` and `1` run today's profiles unchanged, and `2` runs the compiled program through the bridge this document defines; - the invocation is an `EchoOperationInvocationV2`, the V1 canonical @@ -778,10 +808,10 @@ Each Action's receipt entry binds, in addition to its current fields: decision requires: the origin basis, the validation position, and one of three evidence forms, each recording only a comparison that actually ran: - - `OriginOnly`, for every outcome decided before the history check: - an evaluation obstruction, or a failure of resolution (step 1 of - the [Validation order](#validation-order)). No dependency - comparison ran; + - `OriginOnly`, for every outcome decided before the history check: an + evaluation obstruction, or a failure of reachability or resolution + (steps 1 and 2 of the [Validation order](#validation-order)). No + dependency comparison ran; - `HistoryChanged`, for a dependency-scoped `BasisChanged`: the dependency-set digest over each dependency's descriptor and comparison mode, and the first changed dependency in canonical @@ -1057,10 +1087,12 @@ follow the canonical effect order in [EffectProposal](#effectproposal). - **Capacity fixed before evaluation.** Each region's size is a pure function of the selected candidates' declared footprint-law maxima, delegated budgets, and the program's `max_allocated_bytes`, which bounds the result bytes the blob - must also hold, all bounded by the existing batch limits (64 candidates, 128 - operations). The arena grows, if at all, after selection and before - evaluation. Evaluation, validation, derivation, and composition never - allocate. + must also hold, sized per candidate from its own declared maxima, never from + the 128-operation batch limit, so a candidate whose patch exceeds that limit + is still derived in place and then rejected with `BudgetExceeded`. The + 64-candidate limit bounds how many such candidates a Tick selects. The arena + grows, if at all, after selection and before evaluation. Evaluation, + validation, derivation, and composition never allocate. - **No host-dependent decisions.** Arena capacity never changes which Actions a Tick contains or how they are decided. If the host cannot grow the arena, the Tick fails as a host fault and nothing is decided, as on a @@ -1263,7 +1295,7 @@ Each behaviour below needs a test before implementation: | Scoped basis change | A dependency-scoped Action evaluated at its origin obstructs with `BasisChanged`, posture `Proposed` with `NotDerived`, and keeps its proposal, read set, and changed dependency when a dependency changed, even if its proposal would also fail validation; an unchanged dependency set lets it commit. | | Scoped evaluation obstruction | A dependency-scoped Action whose evaluation exhausts its budget, or fails an authored guard, keeps that obstruction with `NoProposal` even when one of its reads changed after the origin; recovery reproduces it. | | Receipt format | `EPRV0003` records recover byte for byte through the unchanged decoder; new Ticks are written as `EPRV0004`; a Tick mixing a bridge Action with fixed-profile Actions encodes the latter as `NotBridge`; a history mixing both formats recovers. | -| Package version | A source-compiled package encodes as `ExecutableOperationPackageV2` with lawpack tag `0` and an `EdictEffectProgram` (program tag `2`) that the evaluator dispatches to the bridge; a V2 package carrying a fixed profile runs it unchanged; V1 packages keep their bytes and identities, and no V2 identity equals a V1 identity. | +| Package version | A source-compiled package encodes as `ExecutableOperationPackageV2` with no `lawpack` entry and an `EdictEffectProgram` (program tag `2`) that the evaluator dispatches to the bridge; a V2 package carrying a fixed profile runs it unchanged; V1 packages keep their bytes and identities, and no V2 identity equals a V1 identity. | | Lawpack by program | A V2 package whose fixed-profile program omits the lawpack, or whose `EdictEffectProgram` carries one, is refused at admission. | | Invocation version | An `EdictInput` (tag `2`) invocation is admitted only for an `EdictEffectProgram` package and is refused for a fixed profile, and a fixed-profile kind is refused for a tag `2` program. | | Outcome record | A bridge obstruction, including an authored one such as `BasisNotCanonical`, is retained as `EOACT004` with the same coordinate as its receipt; fixed-profile Actions still write `EOACT003`, and mixed histories recover. | @@ -1275,7 +1307,6 @@ Each behaviour below needs a test before implementation: - How `edict_read` exposes outgoing edges by type to Edict source, and how a bounded closure read guided by a pure Edict selector is expressed and budgeted. The metering of a single range read is defined above. -- Whether the footprint law is per-invocation or per-package with parameters. - How Edict source exposes the `ContentAddressV1` inputs; the law itself is defined above. - Whether proposals larger than the Action-batch operation limit should ever From 4a2c724b25432a7549d53f598a9c15aecd2e4dcd Mon Sep 17 00:00:00 2001 From: James Ross Date: Fri, 16 Oct 2026 20:07:54 -0700 Subject: [PATCH 31/50] docs: close the twenty-second review round on the authored-effects bridge - Immutability is decided by node type, which the schema marks as content or mutable, so no creation history is needed. Content-addressed nodes are created by EnsureNode and never replaced; mutable nodes are created by CreateNode and may be replaced. - The footprint law gains portal_owner_types. Crossing a portal owned by any other type is a FootprintLawViolation. - Each portal crossed during reachability costs one step and 64 read bytes, charged before reading; exhausting the budget is BudgetExceeded. - Admission checks only canonical CBOR and max_input_bytes for an EdictInput. The input's shape is the program's to check, and a malformed input obstructs with InputRejected / NoProposal. Co-Authored-By: Claude Opus 5.5 --- .../authored-operation-effects.md | 76 +++++++++++++------ 1 file changed, 51 insertions(+), 25 deletions(-) diff --git a/docs/architecture/authored-operation-effects.md b/docs/architecture/authored-operation-effects.md index c5204dc7..0b07b883 100644 --- a/docs/architecture/authored-operation-effects.md +++ b/docs/architecture/authored-operation-effects.md @@ -132,14 +132,14 @@ Echo learns no application vocabulary. A proposal is nodes, edges, and atoms. Action. - **Other obstructions:** it re-evaluates the Action and requires the same pre-decision evidence, except that a scheduler-assigned `BudgetExceeded` - (an Action-batch limit, decided after a successful evaluation) is - accepted as a reconstructed preparation and verified through the batch - reconstruction below. - Finally, for every Action Tick and every outcome class, it reconstructs the - whole Action batch's composition over the recovered parent state and refuses - history where any reconstructed outcome differs from the recorded one; for - each `RejectedFootprintConflict` it also recomputes the blocking earlier - Actions and requires them to equal the recorded blockers. + (an Action-batch limit, decided after a successful evaluation) is accepted + as a reconstructed preparation and verified through the batch + reconstruction below. Finally, for every Action Tick and every outcome + class, it reconstructs the whole Action batch's composition over the + recovered parent state and refuses history where any reconstructed outcome + differs from the recorded one; for each `RejectedFootprintConflict` it + also recomputes the blocking earlier Actions and requires them to equal + the recorded blockers. - `echo_operation_written_slots_v1` collects the slots written between an origin and a commit parent by unioning each retained replay patch's `out_slots`. `WarpTickPatchV1::new` accepts `out_slots` independently of @@ -327,13 +327,19 @@ a digest alone could not be executed. One law is defined: - **`1`, `ContentAddressV1`.** It covers the ids a proposal creates: the node of each `EnsureNode` and `CreateNode`, and the edge of each `EnsureEdge`. `ReplaceAtom` and `ReplaceEdge` name existing ids and are not recomputed. - A record created under this law is immutable: replacing a node's atom or - an edge's target would leave an id that no longer addresses its content. - So a `ReplaceAtom` or `ReplaceEdge` whose target's id equals - `ContentAddressV1` of its current content obstructs with - `IdentityLawViolation`. Mutable records, such as an application's anchor - node (the buffer `B` in the trace), have ids that are not content - addresses and may be replaced. + The law applies by node type, as the package's schema marks it. Each node + type is either content-addressed or mutable, and the record's type, which + every record carries, decides; no creation history is needed. + - A node of a content-addressed type must be created through + `EnsureNode` with its `ContentAddressV1` id, and it is immutable. A + `ReplaceAtom` of it, or a `ReplaceEdge` of an edge whose source is of + such a type, obstructs with `IdentityLawViolation`, since its id would + no longer address its content. + - A node of a mutable type, such as an application's anchor (the buffer + `B` in the trace), has a program-chosen id. It may only be created by + `CreateNode`, which requires the id to be absent, and it may be + replaced. An `EnsureNode` of a mutable type is an + `IdentityLawViolation`. - An edge id is BLAKE3 over `echo.bridge.edge-id.v1\0`, the warp id, the `from` local id, the edge type id, and the `to` local id. - A node id is BLAKE3 over `echo.bridge.node-id.v1\0`, the warp id, the @@ -393,7 +399,8 @@ and a high-degree node cannot force an allocation the budget did not cover. These dependency kinds follow the [Action validity across ticks](../topics/GeneratedRules.md#action-validity-across-ticks) -decision (#837). Value and single-key absence dependencies are the kinds that decision +decision (#837). Value and single-key absence dependencies are the kinds that +decision supports. A read of outgoing edges by type is a topology dependency, which that decision does not yet support. Eligibility is decided statically at admission, never by the branch an evaluation takes: an Action whose package's footprint law @@ -418,8 +425,12 @@ parameters. It is an `edict.canonical-cbor/v1` map with these entries: `atom_types` (the atom `type_id`s it may read or write), `range_edge_types` (edge types it may read as outgoing ranges; empty makes it eligible for scoped validity), `write_edge_types`, and the unsigned counts `max_reads`, -`max_writes`, and `max_range_edges` (per range read). Its canonical bytes are -part of the program's identity. At validation Echo checks: +`max_writes`, and `max_range_edges` (per range read), and `portal_owner_types` +(node types whose alpha portals an effect in a descended warp may cross; empty +confines the package to its root warp). Each portal on a validated chain must +be owned by a node of one of these types, or validation obstructs with +`FootprintLawViolation`. Its canonical bytes are part of the program's +identity. At validation Echo checks: - every node type, atom `type_id`, and edge type an effect writes is admitted by the package's typed graph and attachment schema, which ADR 0023 requires @@ -476,7 +487,10 @@ recorded obstruction: (`UnreachableWarp`). A warp unreachable at a scoped Action's origin obstructs even if a later Tick made it reachable, and a reachable warp's portal chain is already in its dependency set, so step 3 catches any - change to it. It runs first, so a key in a missing warp is never + change to it. Each portal crossed costs one step and 64 `read_bytes`, + charged before it is read, in canonical effect order and root first; a + crossing the remaining budget cannot cover obstructs with + `BudgetExceeded`. It runs first, so a key in a missing warp is never reported as a missing record; 2. resolution at the evaluation basis (for a scoped Action, its origin): in canonical effect order, read and charge the facts the effects do not @@ -731,11 +745,14 @@ Consequences: `evaluator_profile_identity` (the evaluator ABI and intrinsic profile), `identity_law` (an unsigned integer naming an [identity law](#identity-laws); unknown values are refused), - `schema` (canonical bytes, below), and `footprint_law` (its canonical - bytes). Identities are 32-byte byte strings. The `schema` is an + `schema` (canonical bytes, below), `footprint_law` (its canonical + bytes), and `max_input_bytes`. Identities are 32-byte byte strings. The + `schema` is an `edict.canonical-cbor/v1` map that Echo executes, not only a digest: - `node_types` maps each admitted node type id to the set of atom - `type_id`s it may carry, and `edge_types` maps each admitted edge type + `node_types` maps each admitted node type id to its `addressing` + (`content` or `mutable`, which the [identity law](#identity-laws) + reads) and the set of atom `type_id`s it may carry, and `edge_types` maps + each admitted edge type id to the set of admitted (source node type, target node type) pairs. Validation steps 6 and 7 read these relations directly. Admission refuses a package unless the program's entries agree with the @@ -752,8 +769,14 @@ Consequences: are the V1 compare-and-set and create-if-absent kinds with their V1 fields, and tag `2` is `EdictInput`, which carries no anchored node or replacement and instead requires `application_input_bytes`. Those are - the program's input in `edict.canonical-cbor/v1`, validated at - admission against the package's input schema identity. Admission + the program's input in `edict.canonical-cbor/v1`. Admission checks + only what it can check without a schema language: that the bytes are + canonical `edict.canonical-cbor/v1` and no longer than the program's + `max_input_bytes` entry. The input's shape is the compiled program's to + check, as its first act; a malformed input obstructs evaluation with + the authored `InputRejected`, posture `NoProposal`. The package's input + schema identity remains evidence of the intended shape, not an + admission check. Admission dispatches on the program tag: a tag `2` program admits only tag `2` invocations and the fixed profiles only their own kinds, so no invocation crosses between them. The V2 invocation identity has its own @@ -1258,8 +1281,10 @@ Each behaviour below needs a test before implementation: | Write set | The derived patch's actual footprint equals the write set, including the atom slots of created nodes and the range key of every created or retargeted edge, so a committed `EnsureEdge` and `ReplaceEdge` pass the footprint check. | | Identity law | A proposed node or edge id that does not match the package's identity law obstructs with `IdentityLawViolation`, posture `Proposed`, `NotDerived`, and recovery reproduces it. | | Content address | Under `ContentAddressV1`, a node id equal to the specified BLAKE3 over its type, atom, and proposal-created children, and an edge id over its endpoints and type, pass; any other id obstructs with `IdentityLawViolation`; fixed vectors pin both derivations. | +| Addressing by type | A `ReplaceAtom` of a node whose type the schema marks content-addressed, or an `EnsureNode` of a mutable type, obstructs with `IdentityLawViolation`; a `ReplaceAtom` of a mutable-type node commits. | | Unreachable warp | An effect naming a key in a missing warp, or in a warp no valid portal chain reaches, obstructs with `UnreachableWarp`, posture `Proposed`, `NotDerived`. | | Origin reachability | A scoped Action naming a warp unreachable at its origin obstructs with `UnreachableWarp` even when an intervening Tick created the portal that reaches it. | +| Portal permission and cost | An effect in a descended warp whose chain crosses a portal owned by a type outside `portal_owner_types` obstructs with `FootprintLawViolation`; a chain whose crossings exceed the remaining budget obstructs with `BudgetExceeded` at step 1. | | Schema | An effect whose node type, atom `type_id`, or edge type the package's schema does not admit obstructs with `SchemaViolation`, posture `Proposed`, `NotDerived`, and recovery reproduces it. | | Resolved schema | A `ReplaceEdge` whose basis edge type the schema does not admit, or a `ReplaceAtom` whose owner node type does not admit the new atom type, obstructs with `SchemaViolation` after its record is read and charged. | | Endpoint types | A creating `EnsureEdge` or a `ReplaceEdge` whose source or target node type the schema does not admit for that edge type obstructs with `SchemaViolation`, after its endpoint records are read and charged. | @@ -1298,6 +1323,7 @@ Each behaviour below needs a test before implementation: | Package version | A source-compiled package encodes as `ExecutableOperationPackageV2` with no `lawpack` entry and an `EdictEffectProgram` (program tag `2`) that the evaluator dispatches to the bridge; a V2 package carrying a fixed profile runs it unchanged; V1 packages keep their bytes and identities, and no V2 identity equals a V1 identity. | | Lawpack by program | A V2 package whose fixed-profile program omits the lawpack, or whose `EdictEffectProgram` carries one, is refused at admission. | | Invocation version | An `EdictInput` (tag `2`) invocation is admitted only for an `EdictEffectProgram` package and is refused for a fixed profile, and a fixed-profile kind is refused for a tag `2` program. | +| Input bounds | An `EdictInput` that is not canonical CBOR, or longer than `max_input_bytes`, is refused at admission; a well-formed input of the wrong shape obstructs evaluation with `InputRejected` and `NoProposal`. | | Outcome record | A bridge obstruction, including an authored one such as `BasisNotCanonical`, is retained as `EOACT004` with the same coordinate as its receipt; fixed-profile Actions still write `EOACT003`, and mixed histories recover. | | Recovered evidence | For each outcome class, recovery performs that class's existing verification plus the new comparisons (posture, proposal identity, read-set, write-set, and patch digests where the class reconstructs or re-evaluates); a tampered digest refuses the history, a whole-frontier `BasisChanged` is not re-evaluated, the batch composition is reconstructed for every class, and no recorded outcome changes. | | Pending recovery | An Action lost with its Tick before the WAL record is recovered as the same submission, not a new one. | From 461af4ada8487899235e58a8ed522c20863daad5 Mon Sep 17 00:00:00 2001 From: James Ross Date: Fri, 16 Oct 2026 20:23:39 -0700 Subject: [PATCH 32/50] docs: consolidate the authored-effects design after a whole-document review An independent read of the whole document against its own rules found 26 internal inconsistencies, alongside six open Codex findings. They are fixed here in one pass against one head: - Validation order: schema membership and relations now run as one step, before the identity law that reads the schema's addressing (11 steps). Step 1 owns all portal outcomes (permission, metering, reachability). Absent endpoints are skipped by the schema step. References and traces are renumbered. - Identity: ContentAddressV1 applies by node type, as before. Content nodes get all their edges at creation and are immutable. Edges from mutable nodes take a stable slot id over (from, type), so a ReplaceEdge retarget keeps a valid id; this fixes the main trace's buffer edge. CreateNode of a content type is refused. - Evidence: NotBridge entries are all-zero bridge rows. Only Ticks with a bridge Action use receipt v5 / EPRV0004. validation_position is defined, with a per-tag field-usage table and Range units. Dependency kinds gain resolved record (4), portal (5) and required absence (6). Compared covers precondition and resolved-record values; witnessed reads are checked by the written-slot history test. - Budget: each validated slot is charged once, at the first step that reads it. The receipt's consumed budget includes validation; allocated_bytes is evaluation only. - Reads: portals are not in the read set. Absent probes name their expected type so the footprint law can authorize them. Range overruns stop evaluation; other out-of-law reads are caught at step 7. - Records: EOACT004 is a sequential record with OutcomeRow plus BridgeEntryRow, with no V1 receipt or prepared candidate. OriginOnly outcomes recover by replaying only the pre-history failure. - Traces, tests and wording are aligned to all of the above. Co-Authored-By: Claude Opus 5.5 --- .../authored-operation-effects.md | 339 +++++++++++------- 1 file changed, 210 insertions(+), 129 deletions(-) diff --git a/docs/architecture/authored-operation-effects.md b/docs/architecture/authored-operation-effects.md index 0b07b883..de3f4e01 100644 --- a/docs/architecture/authored-operation-effects.md +++ b/docs/architecture/authored-operation-effects.md @@ -246,9 +246,10 @@ Rules: by `EdgeKey`, and a range key by its source `NodeKey`, so equal local ids in different warps are different slots. - **Portal chain.** When an effect or a read names a key in a descended - warp, the read set, the footprint, and a scoped Action's dependency set + warp, the footprint and a scoped Action's dependency set (kind `5`) include every portal attachment on the validated root-to-target - reachability chain, as + reachability chain. Portals an effect's chain crosses are not part of the + read set, which holds exactly what the program's view resolved, as [Generated Rule Authorship](../topics/GeneratedRules.md) requires for descended targets and as Echo's existing descent check already reads them. Each is a `ValueDigest` dependency on its `Descend` target. A @@ -324,36 +325,41 @@ Rules: A package names its identity law by number, and Echo recomputes ids under it; a digest alone could not be executed. One law is defined: -- **`1`, `ContentAddressV1`.** It covers the ids a proposal creates: the node - of each `EnsureNode` and `CreateNode`, and the edge of each `EnsureEdge`. - `ReplaceAtom` and `ReplaceEdge` name existing ids and are not recomputed. - The law applies by node type, as the package's schema marks it. Each node - type is either content-addressed or mutable, and the record's type, which - every record carries, decides; no creation history is needed. - - A node of a content-addressed type must be created through - `EnsureNode` with its `ContentAddressV1` id, and it is immutable. A - `ReplaceAtom` of it, or a `ReplaceEdge` of an edge whose source is of - such a type, obstructs with `IdentityLawViolation`, since its id would - no longer address its content. - - A node of a mutable type, such as an application's anchor (the buffer - `B` in the trace), has a program-chosen id. It may only be created by +- **`1`, `ContentAddressV1`.** It applies by node type: the package's + schema marks each node type `content` or `mutable`, and a record's type, + which every record carries, decides, so no creation history is needed. An + edge follows its source node's type. + - A **content** node is created only by an `EnsureNode` whose id is its + content address, together with every outgoing edge it will ever have, + each by an `EnsureEdge` in the same proposal. It is immutable + afterwards: a `ReplaceAtom` of it, a `ReplaceEdge` of an edge from it, + a creating `EnsureEdge` from it in a later proposal, or a `CreateNode` + of a content type is `IdentityLawViolation`. + - A **mutable** node, such as an application's anchor (the buffer `B` + in the trace), has a program-chosen id. It is created only by a `CreateNode`, which requires the id to be absent, and it may be - replaced. An `EnsureNode` of a mutable type is an - `IdentityLawViolation`. - - An edge id is BLAKE3 over `echo.bridge.edge-id.v1\0`, the warp id, the - `from` local id, the edge type id, and the `to` local id. - - A node id is BLAKE3 over `echo.bridge.node-id.v1\0`, the warp id, the - node type id, the attachment value digest of its atom (see - [Evidence](#evidence)), the number of its outgoing edges that the - proposal's `EnsureEdge` effects give it as a little-endian `u64`, and, - for each such edge sorted by edge type id and then `to` local id, the - edge type id and the `to` local id. - - All ids are 32 bytes. A node's id therefore commits to its content and to - the children the same proposal links it to, as Jim's rope requires, and an - edge's id commits to its endpoints and type. A program computes the same - BLAKE3 inputs; Edict must expose them before a tag `2` package can be - produced. + replaced. An `EnsureNode` of a mutable type is `IdentityLawViolation`. + - A content node's id is BLAKE3 over `echo.bridge.node-id.v1\0`, the + warp id, the node type id, the attachment value digest of its atom (see + [Evidence](#evidence)), the number of its outgoing edges as a + little-endian `u64`, and, for each edge sorted by edge type id and then + `to` local id, the edge type id and the `to` local id. Those are exactly + the edges its creating proposal's `EnsureEdge` effects give it. + - An edge from a content node has the content id BLAKE3 over + `echo.bridge.edge-id.v1\0`, the warp id, the `from` local id, the edge + type id, and the `to` local id. It is immutable, like its source. + - An edge from a mutable node has the slot id BLAKE3 over + `echo.bridge.slot-edge-id.v1\0`, the warp id, the `from` local id, and + the edge type id: one edge per source and type, whose id stays the same + when a `ReplaceEdge` retargets it. A creating `EnsureEdge` of it must + carry that id. + + Echo recomputes every id a proposal creates and refuses a mismatch with + `IdentityLawViolation`; `ReplaceAtom` and `ReplaceEdge` name existing ids. + All ids are 32 bytes. A content node's id therefore commits to its content + and its children, as Jim's rope requires, and no later effect can make it + stale. A program computes the same BLAKE3 inputs; Edict must expose them + before a tag `2` package can be produced. ## Witnessed reads over a bounded slice @@ -390,8 +396,8 @@ against a read or write conflicts. A range read is metered before it materializes anything. The view first takes the range's edge count from the source node's bucket, without copying an edge, and charges one step and 64 `read_bytes` per edge (one edge local id -and one target local id). If the count exceeds the footprint law's maximum -for that edge type, or the charge exceeds the remaining budget, evaluation +and one target local id). If the count exceeds the footprint law's +`max_range_edges`, or the charge exceeds the remaining budget, evaluation obstructs with `BudgetExceeded`, posture `NoProposal`, before any edge is copied. Otherwise the view writes the range's `EdgePairRow`s directly into the arena. Consumed cost and the read set therefore depend only on the basis, @@ -413,8 +419,17 @@ looser rule here. The view resolves each slot on demand from the basis, as [Sliced Readings and Copy-on-Write Strands](sliced-readings-and-copy-on-write-strands.md) law 1 requires. The contract never depends on a complete materialized graph: -the read limit and the footprint law bound the slice, and reaching past either -obstructs. A first implementation may still sit on today's fully materialized +the read limit and the footprint law bound the slice. A range read beyond +`max_range_edges` or the budget obstructs evaluation itself (`BudgetExceeded`, +`NoProposal`), since it would materialize data; any other read outside the +footprint law is found at validation step 7 (`FootprintLawViolation`, +`Proposed`, `NotDerived`). + +A read that may find nothing names the node type it expects: the program asks +for a node or atom of that type. The view records the expected type with an +absent outcome (in the `ReadRow`'s `value_digest` field), so the footprint law's +`read_node_types` decides whether the probe is permitted, live and on recovery +alike. A first implementation may still sit on today's fully materialized worldline state, but nothing in this contract may rely on that. ## Footprint law @@ -483,59 +498,59 @@ Validation of a proposal runs in one fixed order, and the first failing step decides the outcome, so a proposal that breaks several rules has exactly one recorded obstruction: -1. warp reachability of every effect key at the evaluation basis - (`UnreachableWarp`). A warp unreachable at a scoped Action's origin - obstructs even if a later Tick made it reachable, and a reachable warp's - portal chain is already in its dependency set, so step 3 catches any - change to it. Each portal crossed costs one step and 64 `read_bytes`, - charged before it is read, in canonical effect order and root first; a - crossing the remaining budget cannot cover obstructs with - `BudgetExceeded`. It runs first, so a key in a missing warp is never - reported as a missing record; +1. warp reachability of every effect key at the evaluation basis, root + first and in canonical effect order. Each portal crossed costs one step + and 64 `read_bytes`, charged before it is read; a crossing the remaining + budget cannot cover is `BudgetExceeded`. A portal owned by a node whose + type is not in the footprint law's `portal_owner_types` is + `FootprintLawViolation`. A key in a missing or unreachable warp is + `UnreachableWarp`, decided before any record is looked up. A warp + unreachable at a scoped Action's origin obstructs even if a later Tick made + it reachable; a reachable warp's portals are dependencies (kind `5`), so + step 3 catches any change to them; 2. resolution at the evaluation basis (for a scoped Action, its origin): in - canonical effect order, read and charge the facts the effects do not - carry. That means each `ReplaceAtom`'s node record, each `ReplaceEdge`'s - edge record, and the node record of each endpoint of a creating - `EnsureEdge` or a `ReplaceEdge` that the proposal does not create. Each - node-record read costs one step and 64 `read_bytes`, and each edge-record - read one step and 128 `read_bytes`, the sizes of WSC's node and edge rows. - Failures are `NodeMissing` for a `ReplaceAtom`'s missing node, - `PreconditionMismatch` for a missing edge, or `BudgetExceeded` when a - read exceeds the remaining budget. A missing endpoint is recorded as - absent and reported as `DanglingReference` at step 9. The resolved - records join the dependency set, so the history check sees them; + canonical effect order, read the facts the effects do not carry. That is + each `ReplaceAtom`'s node record, each `ReplaceEdge`'s edge record, and + the node record of each endpoint of a creating `EnsureEdge` or a + `ReplaceEdge` that the proposal does not create. Charges follow + [Validation reads](#basis-budget-retries). Failures are `NodeMissing` for + a `ReplaceAtom`'s missing node, `PreconditionMismatch` for a missing edge, + or `BudgetExceeded`. A missing endpoint is recorded as absent and reported + as `DanglingReference` at step 8. The resolved records are dependencies + (kind `4`), so the history check sees them; 3. the dependency-scoped history check, for a scoped Action (`BasisChanged`); 4. one effect per slot (`DuplicateEffectTarget`); -5. the identity law (`IdentityLawViolation`); -6. the schema, over the types each effect itself carries: node types, edge - types, and atom `type_id`s must be admitted, and for `EnsureNode` and - `CreateNode`, which carry both, the node type must admit the atom type - (`SchemaViolation`); -7. the schema over resolved types: a `ReplaceEdge`'s preserved edge type - must be admitted; a `ReplaceAtom`'s owner node type must admit the new - atom type; and for every creating `EnsureEdge` and every `ReplaceEdge`, - the schema must admit the edge between its source and target node types, - taken from the resolved records or from the proposal's own node effects - (`SchemaViolation`); -8. the footprint law, now that every slot is known: the witnessed read set, +5. the schema (`SchemaViolation`), before the identity law, which reads the + schema's node-type addressing. Every node type, edge type, and atom + `type_id` an effect carries or step 2 resolved must be admitted; a node + type must admit its atom type (for `EnsureNode`, `CreateNode`, and a + `ReplaceAtom`'s resolved owner); a `ReplaceEdge`'s preserved edge type + must be admitted; and for every creating `EnsureEdge` and every + `ReplaceEdge`, the schema must admit the edge between its source and + target node types, taken from the resolved records or from the + proposal's own node effects. An endpoint step 2 found absent has no type + and is skipped here; +6. the identity law (`IdentityLawViolation`); +7. the footprint law, now that every slot is known: the witnessed read set, every precondition slot (including a `ReplaceEdge`'s resolved source), the target set, the resolved write set with its range keys, and their counts (`FootprintLawViolation`); -9. the remaining effect preconditions, effect by effect in canonical order, +8. the remaining effect preconditions, effect by effect in canonical order, and within an effect in the order its precondition list gives (`PreconditionMismatch`, `ContentIdentityConflict`, `DanglingReference`, or `BudgetExceeded` when a validation read exceeds the remaining budget); -10. patch derivation and the actual-footprint equality - (`FootprintMismatch`); -11. composition (`RejectedFootprintConflict`); -12. the Action-batch operation limit (`BudgetExceeded`), tested only for a +9. patch derivation and the actual-footprint equality + (`FootprintMismatch`); +10. composition (`RejectedFootprintConflict`); +11. the Action-batch operation limit (`BudgetExceeded`), tested only for a candidate with no blockers, as today's scheduler does. -Steps 1 through 9 yield `NotDerived`, and steps 10 through 12 yield +Steps 1 through 8 yield `NotDerived`, and steps 9 through 11 yield `Derived`. Within a step that examines several effects, the first failing -effect in canonical order decides; only steps 2 and 9 can yield more than -one kind, so only they depend on that tie-break for their kind. +effect in canonical order decides. Steps 1, 2, and 8 can yield more than one +kind; within step 1 a crossing is charged, then its owner checked, then the +next portal read. ## Visibility and referential integrity @@ -570,9 +585,10 @@ held; this design validates only what the proposal adds or retargets. ## Basis, budget, retries - **Basis.** Reads, including observed absences, are taken from the exact - basis. At commit, every `expected…` value and every recorded dependency that - a co-scheduled Action wrote must still hold; otherwise the Action obstructs - as stale. Action validity across ticks keeps its existing rules, and an + basis. At commit, every `expected…` value and every recorded dependency that a + co-scheduled Action wrote must still hold; an overlap with an earlier accepted + Action in the same Tick is `RejectedFootprintConflict` (see **Within one + Tick** below). Action validity across ticks keeps its existing rules, and an Action is never re-stamped as if it had been evaluated against a later basis. - **Two kinds of basis change.** Today's executable programs have fixed, statically known reads, so Echo decides dependency-scoped validity before @@ -590,8 +606,8 @@ held; this design validates only what the proposal adds or retargets. parent. The dependency set is every witnessed read, presence and absence, plus every effect precondition defined in [EffectProposal](#effectproposal), whether or not the program read it, - including each edge endpoint. As the Action validity across ticks - decision requires, a dependency that changed in that history counts as + including each edge endpoint. Consistent with the Action validity across + ticks decision, a dependency that changed in that history counts as changed even if it was later restored. - **How a restored value is detected.** Comparing current values cannot see a change from A to B and back to A. The history check therefore @@ -604,15 +620,17 @@ held; this design validates only what the proposal adds or retargets. operations (see [Current implementation](#current-implementation)). `echo_operation_written_slots_v1` must therefore project operations, or verify that `out_slots` covers them exactly and treat any mismatch as - unknown. Value comparison is used only at validation, as the next - bullet orders it. No new versioning system is added. + unknown. Value comparison runs only at validation, against the commit + parent, as the **Order** bullet below places it. No new versioning system + is added. - **Unknown history is decided before evaluation.** The written set depends only on the origin and the commit parent, so Echo computes it before evaluating the Action. If any Tick in the interval has no retained replay patch, or its writes cannot be derived, the set is unknown and the Action takes the whole-frontier rule at once: since the origin is not the commit parent, it obstructs with whole-frontier - `BasisChanged`, posture `NoProposal`, empty read set, no evaluation. + `BasisChanged`, posture `NoProposal`, validity `WholeFrontier`, empty + read set, zero consumed budget, no evaluation. - **Evaluation obstructions stand.** An evaluation that obstructs before producing a proposal (`BudgetExceeded`, or an authored guard) keeps that obstruction with posture `NoProposal`. Its later reads never ran @@ -660,15 +678,21 @@ held; this design validates only what the proposal adds or retargets. declared maximum, and exceeding that is likewise `BudgetExceeded` with `NoProposal`. The allocated bytes are recorded as evidence, never compared against the delegated budget. -- **Validation reads are charged.** A precondition check that reads a value - the program did not read charges that value's length against - `read_bytes` before hashing it, as today's compare-and-set charges the - current atom: a `ReplaceAtom` or `EnsureNode` hashes the current atom, and - an `EnsureEdge` or `ReplaceEdge` reads a fixed-size edge record. Charges - accrue in validation order, against the budget left after evaluation. A - check that would exceed it obstructs with `BudgetExceeded`, posture - `Proposed`, `NotDerived`, at that step of the validation order, before the - value is hashed. A proposal whose derived +- **Validation reads are charged.** Every slot validation reads is charged + once per Action, before it is read, at the first step that reads it, and + never again: a node record or an atom's presence costs one step and 64 + `read_bytes`, an edge record one step and 128 `read_bytes`, and hashing an + atom's value adds its byte length. A slot the program's view already read + is not charged again, since its value is known. So step 1 charges portals, + step 2 charges each resolved record, and step 8 charges the remaining + precondition slots: the absence checks of `CreateNode` and a creating + `EnsureNode` or `EnsureEdge`, and the atom hashed for `ReplaceAtom`'s + expected digest or an `EnsureNode`'s identity check. Charges come from the + budget left after evaluation. A read the remaining budget cannot cover + obstructs with `BudgetExceeded`, posture `Proposed`, `NotDerived`, at that + step. The receipt's consumed budget is evaluation and validation together; + `allocated_bytes` is evaluation alone. +- **Batch limit.** A proposal whose derived patch alone exceeds the Action-batch operation limit (`ACTION_BATCH_OPERATION_LIMIT_V1`) also obstructs with `BudgetExceeded` and no committed patch, rather than splitting across Ticks, but its posture is @@ -729,7 +753,7 @@ Consequences: binds `lawpack_identity` into its canonical bytes and identities. A source-compiled bridge package therefore uses new versions, `ExecutableOperationPackageV2` and `EchoOperationSemanticClosureV2`. They - are identical to V1 except in two fields: + are identical to V1 except in three respects: - the lawpack binding is optional: the semantic closure's map carries its `lawpack` entry (the V1 coordinate and identity) or omits it. A fixed-profile program (tags `0` and `1` below) requires the entry, as @@ -754,7 +778,7 @@ Consequences: reads) and the set of atom `type_id`s it may carry, and `edge_types` maps each admitted edge type id to the set of admitted (source node type, target node type) pairs. - Validation steps 6 and 7 read these relations directly. + Validation step 5 reads these relations directly. Admission refuses a package unless the program's entries agree with the rest of the package: `target_ir_identity` must equal the semantic closure's Target IR identity, `evaluator_profile_identity` must equal @@ -793,9 +817,9 @@ Each Action's receipt entry binds, in addition to its current fields: - the proposal posture, a tagged value with exactly three variants: - `NotBridge`: the entry is not a bridge Action. Fixed-profile Actions and - every other existing receipt entry take this variant. It carries none of - the bridge fields below, and its existing fields are encoded and hashed - exactly as under receipt digest version 4, after the tag. No bridge + every other existing receipt entry take this variant. Its existing + fields are encoded and hashed exactly as under receipt digest version + 4, and its row in the bridge table is all zero (posture `0`). No bridge evidence is manufactured for it; - `NoProposal`: no proposal exists, because evaluation obstructed before producing one (for example `BudgetExceeded` or an authored guard such @@ -841,8 +865,11 @@ Each Action's receipt entry binds, in addition to its current fields: order. Validation never ran, so no values were compared; - `Compared`, for every Action that passed the history check, whatever its later outcome: the dependency-set digest and a - compared-values digest over the dependency values the - pre-composition checks read at the commit parent. A dependency + compared-values digest. The pre-composition checks compare, at the + commit parent, the value of every effect-precondition and + resolved-record dependency. Witnessed reads are verified by the + history check's written-slot test, not by value, and are recorded + as not compared. A precondition or resolved-record dependency those checks never reached, because validation stopped at an earlier step or a validation read exhausted the budget before hashing its value, is recorded as not compared, with no value. For @@ -853,23 +880,27 @@ Each Action's receipt entry binds, in addition to its current fields: `RejectedFootprintConflict` is never compared at its validation position; -- for `NoProposal` and `Proposed`, consumed budget. When evaluation never - ran (whole-frontier `BasisChanged`, including unknown history), it is zero - on every axis, and recovery verifies that. +- for `NoProposal` and `Proposed`, consumed budget: evaluation and validation + charges together, with `allocated_bytes` from evaluation alone. When + evaluation never ran (whole-frontier `BasisChanged`, including unknown + history), it is zero on every axis, and recovery verifies that. **Dependency kinds and modes.** A dependency is its slot, its kind, and its comparison mode: - kind: value present `0`, observed absent `1`, expected value `2`, endpoint - existence `3`; + existence `3`, resolved record `4` (a record step 2 resolved), portal `5` + (a portal on an effect's chain), and required absence `6` (a slot a + `CreateNode` or creating `EnsureNode` or `EnsureEdge` requires absent); - comparison mode: `ValueDigest` `0`, a payload-value dependency that compares the slot's value digest, and `Absence` `1`, a single-key absence dependency. These are exactly the two kinds the Action validity decision - admits. Value present, expected value, and endpoint existence use - `ValueDigest`: an endpoint's dependency is on its node record's value, so - it holds only while the node exists with that value. Observed absent uses - `Absence`. Positive presence alone is not a mode. Revision-sensitive and - identity-sensitive dependencies are refused at admission and have no tag. + admits. Value present, expected value, endpoint existence, resolved record, + and portal use `ValueDigest`: an endpoint's dependency is on its node record's + value, so it holds only while the node exists with that value. Observed absent + and required absence use `Absence`. Positive presence alone is not a mode. + Revision-sensitive and identity-sensitive dependencies are refused at + admission and have no tag. A scoped Action reads no edge ranges (an edge read keeps the whole-frontier rule), so its dependency slots are node records, attachments (atoms and @@ -940,6 +971,10 @@ when it lands: | ----------------------------------------- | -------------- | --------------- | ----------- | | Commit-bound section with bridge evidence | version 5 | `EPRV0004` | v3 | +Only a Tick that contains a bridge Action uses receipt digest version 5 and +`EPRV0004`; a Tick of fixed-profile Actions alone keeps version 4 and +`EPRV0003`, byte for byte. + The `EPRV0003` decoder and receipt digest version 4 stay unchanged, retained legacy Ticks recover through them byte for byte, a mixed history decodes each record by its own magic, and an `EPRV0003` record never carries the new @@ -964,8 +999,12 @@ new evidence. Verification is not re-admission. and then discarded. - An Action recorded as `Scoped` follows [Action validity across ticks](../topics/GeneratedRules.md#action-validity-across-ticks): - for every `Proposed` outcome, recovery recomputes its complete dependency - set (every witnessed read and every effect precondition) and the + for a `Proposed` outcome recorded as `OriginOnly`, recovery replays only + the pre-history failure (reachability or resolution) and requires the same + obstruction, without reconstructing a dependency set that live execution + never built. For one recorded as `HistoryChanged` or `Compared`, it + recomputes the complete dependency set (every witnessed read and every + effect precondition) and the written-slot set from the replay operations. It then repeats what live execution did, in the same order: the history check, the pre-composition checks against the commit parent, and the batch composition, and it @@ -1003,7 +1042,7 @@ above likewise applies to every row recorded as `Scoped`. | `BudgetExceeded` during evaluation, or an authored guard | `NoProposal` | re-evaluate; compare pre-decision evidence | re-evaluation, at the origin for a `Scoped` Action, again yields no proposal and the same obstruction; read-set digest | | `BasisChanged`, whole-frontier (before evaluation, including unknown history) | `NoProposal` | admission evidence; recomputed basis posture; no re-evaluation | none; posture must be `NoProposal` with an empty read set and zero consumed budget | | `BasisChanged`, dependency-scoped (after evaluation, before validation) | `Proposed`, `NotDerived` | none today: current programs decide this before evaluation | re-evaluate at the origin; effects bound; read-set digest; recompute the written-slot set from replay operations, the complete dependency set (witnessed reads and effect preconditions), and the changed dependency | -| Any outcome of a `NotBridge` entry | `NotBridge` | unchanged | none; no bridge field present | +| Any outcome of a `NotBridge` entry | `NotBridge` | unchanged | none; its bridge row is all zero | ## Encoding and memory @@ -1052,6 +1091,24 @@ posture `NotBridge` `0`, `NoProposal` `1`, `Proposed` `2`; patch `NotDerived` `ContentIdentityConflict` `9`, `DanglingReference` `10`, and `FootprintMismatch` `11`. A `NotBridge` row is all zero after its posture. +`validation_position` is the Action's zero-based index among the Tick's +selected candidates in canonical order, counting every candidate whatever its +outcome; it is `0` when the validity tag is `WholeFrontier`. Each tag sets +exactly these `BridgeEntryRow` fields; every other field is zero: + +| Tag value | Fields it sets | +| ------------------------- | ----------------------------------------------------------------------------- | +| posture `NotBridge` | none | +| posture `NoProposal` | `read_set`, the budget fields, `validity`, `outcome`, `code` or `authored_id` | +| posture `Proposed` | as `NoProposal`, plus `proposal_id`, `target_set`, `patch` | +| patch `Derived` | `write_set`, `patch_digest` | +| validity `Scoped` | `origin_basis`, `validation_position`, `evidence` | +| evidence `HistoryChanged` | `dependency_set`, `changed_dependency` | +| evidence `Compared` | `dependency_set`, `compared_values` | + +A `Range` addresses its section in that section's units, from the section's +start: the blob in bytes, and the `EdgePairRow` section in rows. + **Sort orders.** `SlotRow`s sort by `class`, `warp_id`, `local_id`, `edge_type`, `owner`, then `plane`, so a set's rows are in slot order. `ReadRow`s and `DependencyRow`s sort by their slot, and `DependencyRow`s with @@ -1088,13 +1145,19 @@ follow the canonical effect order in [EffectProposal](#effectproposal). little-endian `u16` version tag set to `5`, in the same position as versions 2 through 4, followed by the bridge table bytes exactly as `EPRV0004` retains them. -- **`EOACT004`**: the eight ASCII bytes `EOACT004`, then the `EOACT003` - layout with one change per variant. Every variant writes an `OutcomeRow` - immediately after its one-byte variant tag (committed `1`, obstructed `2`, - conflict `3`). For committed and conflict, it is the all-zero row, outcome - none; for obstructed, it replaces `EOACT003`'s one-byte obstruction kind. - The coordinate is always encoded, never derived, and recovery requires - the decoder to read the zero row for committed and conflict. +- **`EOACT004`**: a bridge Action's retained outcome. It is a sequential + record like `EOACT003`, not one of the zero-copy images above, because it + is read once at recovery: the eight ASCII bytes `EOACT004`, the submission + id and ingress id (32 bytes each), a one-byte variant tag (committed `1`, + obstructed `2`, conflict `3`), the 40-byte `OutcomeRow`, and the Action's + 448-byte `BridgeEntryRow`. A conflict then adds its blocker count as a + little-endian `u32` and each blocker's candidate index as a little-endian + `u32`. A bridge Action has no `EchoOperationReceiptV1` and no prepared + candidate: its evidence is the `BridgeEntryRow` that the receipt digest + already binds. Committed and conflict variants carry the all-zero + `OutcomeRow`. Recovery requires the `OutcomeRow` and the `BridgeEntryRow` + to equal the receipt's, byte for byte. Fixed-profile Actions keep + `EOACT003`. **Memory.** The bridge allocates nothing on its hot path: @@ -1166,13 +1229,27 @@ replacement "there"}` at basis `c`. - `EnsureEdge H2 -root-> T2` - `EnsureEdge H2 -anchor-> I` - `ReplaceEdge B -canonicalHead-> expected H1, now H2` -6. **Validation.** The read and write sets lie inside the footprint law. Echo - recomputes `T2` and `H2` from their atoms and children under the declared - identity law; a mismatch would obstruct with `IdentityLawViolation`. - Endpoint preconditions check: `T2` and `H2` are created here, and `I` and - `B` exist at `c`. The expected `H1` and version-7 digest hold at `c`. The - budget holds. The Action reads edges, so its validity rule is - `WholeFrontier`. +6. **Validation**, in [Validation order](#validation-order): + 1. every key is in the root warp, so no portal is crossed; + 2. resolution reads and charges `B`'s node record (for `ReplaceAtom B`), + the `B.canonicalHead` edge record (for `ReplaceEdge`), and the + endpoint `I`'s node record (for `EnsureEdge H2 -anchor-> I`); + 3. the Action is whole-frontier, so no history check runs; + 4. no two effects share a slot; + 5. the schema admits every type: `T2` and `H2` are content types, `B` is + mutable, and each edge's source and target types are admitted; + 6. Echo recomputes `T2` and `H2` as content addresses over their atoms + and the edges this proposal gives them, recomputes the content ids of + `H2.root` and `H2.anchor`, and confirms that `B.canonicalHead` keeps + its slot id, which does not depend on its target; + 7. the read set, precondition slots, and write set lie inside the + footprint law; + 8. the expected `H1` and version-7 digest hold at `c`, and every endpoint + exists or is created here. + + Its package's footprint law admits range reads, so its validity rule is + `WholeFrontier` whatever this evaluation read. + 7. **Composition.** The write set is the eight target slots `T2`, `T2.atom`, `H2`, `H2.atom`, `H2.root`, `H2.anchor`, `B.canonicalHead`, and `B.atom`, plus the range keys `(H2, root)`, `(H2, anchor)`, and `(B, canonicalHead)` @@ -1280,11 +1357,13 @@ Each behaviour below needs a test before implementation: | Canonical slots | Differently expressed effects on one slot (`EnsureEdge` and `ReplaceEdge` of one edge id; `CreateNode` and `ReplaceAtom` of one node) obstruct with `DuplicateEffectTarget`. | | Write set | The derived patch's actual footprint equals the write set, including the atom slots of created nodes and the range key of every created or retargeted edge, so a committed `EnsureEdge` and `ReplaceEdge` pass the footprint check. | | Identity law | A proposed node or edge id that does not match the package's identity law obstructs with `IdentityLawViolation`, posture `Proposed`, `NotDerived`, and recovery reproduces it. | -| Content address | Under `ContentAddressV1`, a node id equal to the specified BLAKE3 over its type, atom, and proposal-created children, and an edge id over its endpoints and type, pass; any other id obstructs with `IdentityLawViolation`; fixed vectors pin both derivations. | +| Node resolution | A `ReplaceAtom` of a node absent at the evaluation basis obstructs at step 2 with `NodeMissing`, posture `Proposed`, `NotDerived`. | +| Content address | Under `ContentAddressV1`, a node id equal to the specified BLAKE3 over its type, atom, and the edges its creating proposal's `EnsureEdge` effects give it, and an edge id over its endpoints and type, pass; any other id obstructs with `IdentityLawViolation`; fixed vectors pin both derivations. | | Addressing by type | A `ReplaceAtom` of a node whose type the schema marks content-addressed, or an `EnsureNode` of a mutable type, obstructs with `IdentityLawViolation`; a `ReplaceAtom` of a mutable-type node commits. | +| Edge addressing | An edge from a mutable node keeps its slot id when a `ReplaceEdge` retargets it; a creating `EnsureEdge` from an existing content node, a `CreateNode` of a content type, and a `ReplaceEdge` of an edge from a content node each obstruct with `IdentityLawViolation`. | | Unreachable warp | An effect naming a key in a missing warp, or in a warp no valid portal chain reaches, obstructs with `UnreachableWarp`, posture `Proposed`, `NotDerived`. | | Origin reachability | A scoped Action naming a warp unreachable at its origin obstructs with `UnreachableWarp` even when an intervening Tick created the portal that reaches it. | -| Portal permission and cost | An effect in a descended warp whose chain crosses a portal owned by a type outside `portal_owner_types` obstructs with `FootprintLawViolation`; a chain whose crossings exceed the remaining budget obstructs with `BudgetExceeded` at step 1. | +| Portal permission and cost | At validation step 1, an effect in a descended warp whose chain crosses a portal owned by a type outside `portal_owner_types` obstructs with `FootprintLawViolation`, and a chain whose crossings exceed the remaining budget obstructs with `BudgetExceeded`. | | Schema | An effect whose node type, atom `type_id`, or edge type the package's schema does not admit obstructs with `SchemaViolation`, posture `Proposed`, `NotDerived`, and recovery reproduces it. | | Resolved schema | A `ReplaceEdge` whose basis edge type the schema does not admit, or a `ReplaceAtom` whose owner node type does not admit the new atom type, obstructs with `SchemaViolation` after its record is read and charged. | | Endpoint types | A creating `EnsureEdge` or a `ReplaceEdge` whose source or target node type the schema does not admit for that edge type obstructs with `SchemaViolation`, after its endpoint records are read and charged. | @@ -1297,6 +1376,7 @@ Each behaviour below needs a test before implementation: | Origin resolution | A scoped `ReplaceEdge` whose program never read the edge, and whose source node is deleted and recreated between origin and commit parent while the edge is unchanged, obstructs with `BasisChanged`, because resolution at the origin put the source in its dependency set. | | Scoped conflict recovery | A scoped Action rejected because an earlier Action in its Tick replaced a value it expects recovers as `RejectedFootprintConflict`, not `PreconditionMismatch`. | | Scoped evidence forms | An evaluation obstruction records `OriginOnly`, a dependency-scoped `BasisChanged` records `HistoryChanged` with no compared values, and every other scoped outcome records `Compared` over commit-parent values; none is compared at a validation position that includes an earlier overlapping write. | +| OriginOnly recovery | A scoped proposal that fails reachability or resolution is recovered by replaying only that failure, with no dependency set rebuilt. | | Resolution failure evidence | A scoped `ReplaceEdge` whose edge is missing at its origin records `OriginOnly` with `PreconditionMismatch`; recovery reproduces it without running the history check. | | Retry basis | A new submission selected after the head advanced is evaluated at its recorded basis, not at the selection-time state. | | Receipt digest v5 | A version-5 receipt digest begins with the little-endian `u16` `5` and changes when any bridge field changes, including the validation position, a dependency's comparison mode, or a compared value; version-4 receipt identities are unchanged. | @@ -1312,14 +1392,15 @@ Each behaviour below needs a test before implementation: | Witnessed reads | The receipt's read set equals the slots the view resolved, including for a program that reads and then obstructs. | | Negative reads | An Action that observed a node, atom, or edge set absent obstructs when a co-scheduled Action creates it, including a fresh edge of the observed source and type with a new edge id when the observed set was empty (outgoing-edge range key). | | Range metering | A range read over a node with more edges than the law's maximum, or than the remaining budget covers, obstructs with `BudgetExceeded` and `NoProposal` before copying any edge; an admissible range charges one step and 64 read bytes per edge. | -| Edge-read basis | An Action that read edges commits only onto its exact evaluation basis. | +| Absent probes | A read that finds nothing records the node type it expected; a probe for a type outside `read_node_types` obstructs at step 7 with `FootprintLawViolation`, and recovery reproduces the decision from the read set alone. | +| Edge-read basis | An Action whose package's footprint law admits a range read commits only onto its exact evaluation basis, including when this invocation reads no edge. | | Final outcomes | A rejected Action's outcome, receipt, and attribution are unchanged after a later submission of the same edit succeeds. | | Duplicate resubmission | Resubmitting a decided Action's identical content returns `Duplicate` with the original outcome and commits no Tick. | | Attribution | Every Action in a bridge Tick appears once in the attribution section, mapped to its own receipt entry. | | One effect per slot | Two effects on one slot (two `ReplaceAtom`, or `EnsureNode` plus `ReplaceAtom` of the same node) obstruct with `DuplicateEffectTarget`, posture `Proposed`, `NotDerived`. | | Scoped basis change | A dependency-scoped Action evaluated at its origin obstructs with `BasisChanged`, posture `Proposed` with `NotDerived`, and keeps its proposal, read set, and changed dependency when a dependency changed, even if its proposal would also fail validation; an unchanged dependency set lets it commit. | | Scoped evaluation obstruction | A dependency-scoped Action whose evaluation exhausts its budget, or fails an authored guard, keeps that obstruction with `NoProposal` even when one of its reads changed after the origin; recovery reproduces it. | -| Receipt format | `EPRV0003` records recover byte for byte through the unchanged decoder; new Ticks are written as `EPRV0004`; a Tick mixing a bridge Action with fixed-profile Actions encodes the latter as `NotBridge`; a history mixing both formats recovers. | +| Receipt format | `EPRV0003` records recover byte for byte through the unchanged decoder; a Tick containing a bridge Action is written as `EPRV0004`, with its fixed-profile Actions as all-zero `NotBridge` rows; a Tick of fixed-profile Actions alone stays `EPRV0003` and receipt digest version 4; a history mixing both formats recovers. | | Package version | A source-compiled package encodes as `ExecutableOperationPackageV2` with no `lawpack` entry and an `EdictEffectProgram` (program tag `2`) that the evaluator dispatches to the bridge; a V2 package carrying a fixed profile runs it unchanged; V1 packages keep their bytes and identities, and no V2 identity equals a V1 identity. | | Lawpack by program | A V2 package whose fixed-profile program omits the lawpack, or whose `EdictEffectProgram` carries one, is refused at admission. | | Invocation version | An `EdictInput` (tag `2`) invocation is admitted only for an `EdictEffectProgram` package and is refused for a fixed profile, and a fixed-profile kind is refused for a tag `2` program. | From b93603b3d97eb51a21a976450f3a9bed8a3cc56d Mon Sep 17 00:00:00 2001 From: James Ross Date: Fri, 16 Oct 2026 20:37:38 -0700 Subject: [PATCH 33/50] docs: resolve Ensure targets early; retain bridge results; canonical sets Round 24 on the authored-effects bridge: - Step 2 now resolves every Ensure and Create target, so the footprint law at step 7 sees the true write set. Every resolved record is a kind-4 dependency: ValueDigest when present, Absence when absent. That covers missing endpoints, which had no legal mode. - Portal owner records are read, charged, and recorded as kind-5 dependencies, so an owner whose type changes is caught by the history check. - ContentAddressV1 now also recomputes the id of every basis edge that a ReplaceEdge or an identical EnsureEdge resolves. - An absent ReadRow carries its expected node type in a `subject` field defined per outcome. - Every set in a footprint law or schema is a strictly ascending CBOR array. Admission refuses unsorted or duplicated sets, and `addressing` is encoded as an unsigned integer. - A committed bridge Action's result is retained as ADR 0025 requires: - `result_identity` and a result range in `BridgeEntryRow` (now 496 bytes); - a result blob in EPRV0004 and receipt digest v5; - the exact bytes in EOACT004. - The basis is the replay-determined materialized state, including unreachable records, which leave only through a patch. Retention governs only earlier history. - Eight tests and the main trace are updated to match. Co-Authored-By: Claude Opus 5.5 --- .../authored-operation-effects.md | 322 +++++++++++------- 1 file changed, 192 insertions(+), 130 deletions(-) diff --git a/docs/architecture/authored-operation-effects.md b/docs/architecture/authored-operation-effects.md index de3f4e01..c30a626a 100644 --- a/docs/architecture/authored-operation-effects.md +++ b/docs/architecture/authored-operation-effects.md @@ -354,8 +354,13 @@ a digest alone could not be executed. One law is defined: when a `ReplaceEdge` retargets it. A creating `EnsureEdge` of it must carry that id. - Echo recomputes every id a proposal creates and refuses a mismatch with - `IdentityLawViolation`; `ReplaceAtom` and `ReplaceEdge` name existing ids. + Echo recomputes every id a proposal creates, and the id of every edge a + `ReplaceEdge` or an identical `EnsureEdge` resolves from the basis (its + slot or content id from the resolved record's source, type, and, for a + content edge, target), and refuses a mismatch with `IdentityLawViolation`. + A basis edge written by a non-bridge writer under some other id is + therefore never adopted. `ReplaceAtom` names an existing mutable node, + whose id is program-chosen. All ids are 32 bytes. A content node's id therefore commits to its content and its children, as Jim's rope requires, and no later effect can make it stale. A program computes the same BLAKE3 inputs; Edict must expose them @@ -427,15 +432,29 @@ footprint law is found at validation step 7 (`FootprintLawViolation`, A read that may find nothing names the node type it expects: the program asks for a node or atom of that type. The view records the expected type with an -absent outcome (in the `ReadRow`'s `value_digest` field), so the footprint law's +absent outcome (in the `ReadRow`'s `subject` field), so the footprint law's `read_node_types` decides whether the probe is permitted, live and on recovery alike. A first implementation may still sit on today's fully materialized worldline state, but nothing in this contract may rely on that. ## Footprint law +### Set encoding + +Every set-valued field in a footprint law or a schema is a CBOR array of +distinct elements in strictly ascending order. An id is a 32-byte byte string, +ordered bytewise. A pair, such as a schema's (source node type, target node +type), is a two-element array ordered by its first element, then its second. A +map keyed by ids, such as the schema's `node_types` and `edge_types`, is a +CBOR map, whose keys `edict.canonical-cbor/v1` already orders. Admission +refuses a law or schema with an unsorted or duplicated set, rather than +normalizing it, so each law and schema has exactly one encoding and one +identity. `addressing` is the unsigned integer `0` for `content` and `1` for +`mutable`; counts are unsigned integers. + The package declares a footprint law, fixed per package with no invocation -parameters. It is an `edict.canonical-cbor/v1` map with these entries: +parameters. It is an `edict.canonical-cbor/v1` map, with the +[set encoding](#set-encoding) below, and these entries: `read_node_types` and `write_node_types` (sets of node type ids), `atom_types` (the atom `type_id`s it may read or write), `range_edge_types` (edge types it may read as outgoing ranges; empty makes it eligible for @@ -503,21 +522,28 @@ recorded obstruction: and 64 `read_bytes`, charged before it is read; a crossing the remaining budget cannot cover is `BudgetExceeded`. A portal owned by a node whose type is not in the footprint law's `portal_owner_types` is - `FootprintLawViolation`. A key in a missing or unreachable warp is - `UnreachableWarp`, decided before any record is looked up. A warp - unreachable at a scoped Action's origin obstructs even if a later Tick made - it reachable; a reachable warp's portals are dependencies (kind `5`), so - step 3 catches any change to them; + `FootprintLawViolation`; reading the owner's node record to check that + costs one more step and 64 `read_bytes`. A key in a missing or unreachable + warp is `UnreachableWarp`, decided before any record is looked up. A warp + unreachable at a scoped Action's origin obstructs even if a later Tick + made it reachable. Each portal attachment and each portal owner's node + record are dependencies (kind `5`), so step 3 catches a change to either, + including an owner whose type changed; 2. resolution at the evaluation basis (for a scoped Action, its origin): in - canonical effect order, read the facts the effects do not carry. That is - each `ReplaceAtom`'s node record, each `ReplaceEdge`'s edge record, and - the node record of each endpoint of a creating `EnsureEdge` or a - `ReplaceEdge` that the proposal does not create. Charges follow + canonical effect order, read every record validation needs. That is the + target record of each `EnsureNode`, `CreateNode`, and `EnsureEdge`, + present or absent, which decides whether the effect writes or only + confirms; each `ReplaceAtom`'s node record; each `ReplaceEdge`'s edge + record; and the node record of each endpoint of a creating `EnsureEdge` + or a `ReplaceEdge` that the proposal does not create. Charges follow [Validation reads](#basis-budget-retries). Failures are `NodeMissing` for a `ReplaceAtom`'s missing node, `PreconditionMismatch` for a missing edge, - or `BudgetExceeded`. A missing endpoint is recorded as absent and reported - as `DanglingReference` at step 8. The resolved records are dependencies - (kind `4`), so the history check sees them; + or `BudgetExceeded`; whether a present target matches is decided at + step 8, and a missing endpoint is reported as `DanglingReference` there. + Every record read here is a dependency (kind `4`): `ValueDigest` when + present and `Absence` when absent, so step 3 catches a target or endpoint + that an intervening Tick created, changed, or removed. After this step the + complete write set is known; 3. the dependency-scoped history check, for a scoped Action (`BasisChanged`); 4. one effect per slot (`DuplicateEffectTarget`); @@ -584,12 +610,19 @@ held; this design validates only what the proposal adds or retargets. ## Basis, budget, retries -- **Basis.** Reads, including observed absences, are taken from the exact - basis. At commit, every `expected…` value and every recorded dependency that a - co-scheduled Action wrote must still hold; an overlap with an earlier accepted - Action in the same Tick is `RejectedFootprintConflict` (see **Within one - Tick** below). Action validity across ticks keeps its existing rules, and an - Action is never re-stamped as if it had been evaluated against a later basis. +- **Basis.** Reads, including observed absences, are taken from the exact basis: + the materialized state at that coordinate, which replaying the worldline's + patches determines exactly, including records no longer reachable from a root. + A record leaves that state only through a patch that deletes it. Retention + governs only the history before a coordinate, never the records of the current + state, so no host may drop an unreachable record outside a Tick. The state + root does not commit to unreachable records; a keyed read of one is bound by + the coordinate and replay, which every host reproduces identically. At commit, + every `expected…` value and every recorded dependency that a co-scheduled + Action wrote must still hold; an overlap with an earlier accepted Action in + the same Tick is `RejectedFootprintConflict` (see **Within one Tick** below). + Action validity across ticks keeps its existing rules, and an Action is never + re-stamped as if it had been evaluated against a later basis. - **Two kinds of basis change.** Today's executable programs have fixed, statically known reads, so Echo decides dependency-scoped validity before evaluation. A bridge Action's reads are known only by evaluating it, so the @@ -683,11 +716,11 @@ held; this design validates only what the proposal adds or retargets. never again: a node record or an atom's presence costs one step and 64 `read_bytes`, an edge record one step and 128 `read_bytes`, and hashing an atom's value adds its byte length. A slot the program's view already read - is not charged again, since its value is known. So step 1 charges portals, - step 2 charges each resolved record, and step 8 charges the remaining - precondition slots: the absence checks of `CreateNode` and a creating - `EnsureNode` or `EnsureEdge`, and the atom hashed for `ReplaceAtom`'s - expected digest or an `EnsureNode`'s identity check. Charges come from the + is not charged again, since its value is known. So step 1 charges portals + and their owners' records, step 2 charges each resolved record, including + every Ensure and Create target, and step 8 charges only the atoms it hashes, + for `ReplaceAtom`'s expected digest and an existing `EnsureNode`'s + identical-content check. Charges come from the budget left after evaluation. A read the remaining budget cannot cover obstructs with `BudgetExceeded`, posture `Proposed`, `NotDerived`, at that step. The receipt's consumed budget is evaluation and validation together; @@ -773,6 +806,7 @@ Consequences: bytes), and `max_input_bytes`. Identities are 32-byte byte strings. The `schema` is an `edict.canonical-cbor/v1` map that Echo executes, not only a digest: + with the [set encoding](#set-encoding) above; `node_types` maps each admitted node type id to its `addressing` (`content` or `mutable`, which the [identity law](#identity-laws) reads) and the set of atom `type_id`s it may carry, and `edge_types` maps @@ -889,16 +923,17 @@ Each Action's receipt entry binds, in addition to its current fields: comparison mode: - kind: value present `0`, observed absent `1`, expected value `2`, endpoint - existence `3`, resolved record `4` (a record step 2 resolved), portal `5` - (a portal on an effect's chain), and required absence `6` (a slot a - `CreateNode` or creating `EnsureNode` or `EnsureEdge` requires absent); + existence `3`, resolved record `4` (a record step 2 resolved, including an + Ensure or Create target and an endpoint), and portal `5` (a portal + attachment on an effect's chain, or its owner's node record); - comparison mode: `ValueDigest` `0`, a payload-value dependency that compares the slot's value digest, and `Absence` `1`, a single-key absence dependency. These are exactly the two kinds the Action validity decision - admits. Value present, expected value, endpoint existence, resolved record, - and portal use `ValueDigest`: an endpoint's dependency is on its node record's - value, so it holds only while the node exists with that value. Observed absent - and required absence use `Absence`. Positive presence alone is not a mode. + admits. Value present, expected value, endpoint existence, and portal use + `ValueDigest`: an endpoint's dependency is on its node record's value, so it + holds only while the node exists with that value. Observed absent uses + `Absence`. A resolved record uses `ValueDigest` when step 2 found it and + `Absence` when it did not. Positive presence alone is not a mode. Revision-sensitive and identity-sensitive dependencies are refused at admission and have no tag. @@ -1071,15 +1106,15 @@ rules: **Rows.** -| Row | Bytes | Fields, in order | -| ---------------- | ----- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `SlotRow` | 104 | `class` u8 (node record `0`, attachment `1`, edge `2`, range key `3`); `owner` u8 (attachment: node `0`, edge `1`); `plane` u8 (attachment: alpha `0`, beta `1`); 5 padding bytes; `warp_id`; `local_id` (the node, edge, attachment owner, or range source); `edge_type` (range key only) | -| `ReadRow` | 160 | `slot` (`SlotRow`); `outcome` u8 (present `0`, absent `1`, range `2`); 7 padding bytes; `value_digest` (present only); `pairs` (`Range` into the pair section; range only) | -| `EdgePairRow` | 64 | `edge_local_id`; `target_local_id` | -| `EffectRow` | 280 | `kind` u8 (`EnsureNode` `0`, `CreateNode` `1`, `ReplaceAtom` `2`, `EnsureEdge` `3`, `ReplaceEdge` `4`); 7 padding bytes; `key_warp_id`; `key_local_id`; `type_id` (node type, or `EnsureEdge`'s edge type); `atom_type_id`; `atom` (`Range` into the blob); `expected_digest` (`ReplaceAtom`); `from_local_id` (`EnsureEdge`); `to_local_id` (`EnsureEdge`, and `ReplaceEdge`'s new target); `expected_to_local_id` (`ReplaceEdge`) | -| `DependencyRow` | 152 | `slot` (`SlotRow`); `kind` u8; `mode` u8; 6 padding bytes, which together with `slot` form the 112-byte descriptor; `compared` u8 (present `0`, absent `1`, not compared `2`); 7 padding bytes; `compared_digest` (present only) | -| `OutcomeRow` | 40 | `outcome` u8 (none `0`, bridge kind `1`, authored `2`); `code` u8 (the bridge kind code); 6 padding bytes; `authored_id` (authored only) | -| `BridgeEntryRow` | 448 | `posture`, `patch`, `validity`, and `evidence` tags, u8 each; `outcome` and `code` as in `OutcomeRow`; 2 padding bytes; `proposal_id`; `target_set`; `write_set`; `patch_digest`; `read_set`; `origin_basis`; `dependency_set`; `compared_values`; `changed_dependency` (a 112-byte descriptor); `authored_id`; `validation_position` u64; `steps`, `read_bytes`, `write_bytes`, and `allocated_bytes`, u64 each | +| Row | Bytes | Fields, in order | +| ---------------- | ----- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `SlotRow` | 104 | `class` u8 (node record `0`, attachment `1`, edge `2`, range key `3`); `owner` u8 (attachment: node `0`, edge `1`); `plane` u8 (attachment: alpha `0`, beta `1`); 5 padding bytes; `warp_id`; `local_id` (the node, edge, attachment owner, or range source); `edge_type` (range key only) | +| `ReadRow` | 160 | `slot` (`SlotRow`); `outcome` u8 (present `0`, absent `1`, range `2`); 7 padding bytes; `subject`, 32 bytes: the value digest when present, the expected node type id when absent, zero for a range; `pairs` (`Range` into the pair section; range only) | +| `EdgePairRow` | 64 | `edge_local_id`; `target_local_id` | +| `EffectRow` | 280 | `kind` u8 (`EnsureNode` `0`, `CreateNode` `1`, `ReplaceAtom` `2`, `EnsureEdge` `3`, `ReplaceEdge` `4`); 7 padding bytes; `key_warp_id`; `key_local_id`; `type_id` (node type, or `EnsureEdge`'s edge type); `atom_type_id`; `atom` (`Range` into the blob); `expected_digest` (`ReplaceAtom`); `from_local_id` (`EnsureEdge`); `to_local_id` (`EnsureEdge`, and `ReplaceEdge`'s new target); `expected_to_local_id` (`ReplaceEdge`) | +| `DependencyRow` | 152 | `slot` (`SlotRow`); `kind` u8; `mode` u8; 6 padding bytes, which together with `slot` form the 112-byte descriptor; `compared` u8 (present `0`, absent `1`, not compared `2`); 7 padding bytes; `compared_digest` (present only) | +| `OutcomeRow` | 40 | `outcome` u8 (none `0`, bridge kind `1`, authored `2`); `code` u8 (the bridge kind code); 6 padding bytes; `authored_id` (authored only) | +| `BridgeEntryRow` | 496 | `posture`, `patch`, `validity`, and `evidence` tags, u8 each; `outcome` and `code` as in `OutcomeRow`; 2 padding bytes; `proposal_id`; `target_set`; `write_set`; `patch_digest`; `read_set`; `origin_basis`; `dependency_set`; `compared_values`; `changed_dependency` (a 112-byte descriptor); `authored_id`; `validation_position` u64; `steps`, `read_bytes`, `write_bytes`, and `allocated_bytes`, u64 each; `result_identity`; `result` (`Range` into the result blob) | Tags not defined above use the values given in [Evidence](#evidence): posture `NotBridge` `0`, `NoProposal` `1`, `Proposed` `2`; patch `NotDerived` @@ -1105,6 +1140,15 @@ exactly these `BridgeEntryRow` fields; every other field is zero: | validity `Scoped` | `origin_basis`, `validation_position`, `evidence` | | evidence `HistoryChanged` | `dependency_set`, `changed_dependency` | | evidence `Compared` | `dependency_set`, `compared_values` | +| outcome committed | `result_identity`, `result` | + +`result_identity` is BLAKE3 over `echo.bridge.result.v1\0`, the program +identity, the length of the program's declared output type coordinate as a +little-endian `u64` and its UTF-8 bytes, and the length of the result bytes as +a little-endian `u64` and the bytes: the propositions ADR 0025 binds for an +applied Action. An obstruction or conflict carries no result, and both fields +are zero. Recovery re-evaluates the Action and requires its result bytes to +equal the retained ones before it publishes the outcome. A `Range` addresses its section in that section's units, from the section's start: the blob in bytes, and the `EdgePairRow` section in rows. @@ -1138,19 +1182,25 @@ follow the canonical effect order in [EffectProposal](#effectproposal). - **`EPRV0004`**: the eight ASCII bytes `EPRV0004` as the record magic, then the `EPRV0003` payload that follows that record's own magic, byte for byte, then zero padding to an 8-byte boundary, then a bridge table of one - `BridgeEntryRow` per receipt entry, in entry order. Only the magic differs - from `EPRV0003` before the table, so the unchanged `EPRV0003` decoder never - sees an `EPRV0004` record. The table is indexable in place. + `BridgeEntryRow` per receipt entry, in entry order, then the result blob: + its length as a little-endian `u64`, then each committed bridge Action's + exact result bytes in entry order, then zero padding to an 8-byte + boundary. Each row's `result` range addresses the blob, which carries the + result bytes ADR 0025 requires in the Receipt. Only the magic differs from + `EPRV0003` before the table, so the unchanged `EPRV0003` decoder never sees + an `EPRV0004` record. The table is indexable in place. - **Receipt digest version 5**: the version-4 preimage with its leading little-endian `u16` version tag set to `5`, in the same position as - versions 2 through 4, followed by the bridge table bytes exactly as - `EPRV0004` retains them. + versions 2 through 4, followed by the bridge table and result blob bytes + exactly as `EPRV0004` retains them. - **`EOACT004`**: a bridge Action's retained outcome. It is a sequential record like `EOACT003`, not one of the zero-copy images above, because it is read once at recovery: the eight ASCII bytes `EOACT004`, the submission id and ingress id (32 bytes each), a one-byte variant tag (committed `1`, obstructed `2`, conflict `3`), the 40-byte `OutcomeRow`, and the Action's - 448-byte `BridgeEntryRow`. A conflict then adds its blocker count as a + 496-byte `BridgeEntryRow`, then, for a committed Action, the exact result + bytes its row's `result` range names, as a little-endian `u64` length and + the bytes. A conflict then adds its blocker count as a little-endian `u32` and each blocker's candidate index as a little-endian `u32`. A bridge Action has no `EchoOperationReceiptV1` and no prepared candidate: its evidence is the `BridgeEntryRow` that the receipt digest @@ -1231,17 +1281,20 @@ replacement "there"}` at basis `c`. - `ReplaceEdge B -canonicalHead-> expected H1, now H2` 6. **Validation**, in [Validation order](#validation-order): 1. every key is in the root warp, so no portal is crossed; - 2. resolution reads and charges `B`'s node record (for `ReplaceAtom B`), - the `B.canonicalHead` edge record (for `ReplaceEdge`), and the - endpoint `I`'s node record (for `EnsureEdge H2 -anchor-> I`); + 2. resolution reads and charges the targets of `EnsureNode T2`, + `EnsureNode H2`, `EnsureEdge H2.root`, and `EnsureEdge H2.anchor` + (all absent, so all four create), `B`'s node record (for + `ReplaceAtom B`), the `B.canonicalHead` edge record (for + `ReplaceEdge`), and the endpoint `I`'s node record (for + `EnsureEdge H2 -anchor-> I`); 3. the Action is whole-frontier, so no history check runs; 4. no two effects share a slot; 5. the schema admits every type: `T2` and `H2` are content types, `B` is mutable, and each edge's source and target types are admitted; 6. Echo recomputes `T2` and `H2` as content addresses over their atoms and the edges this proposal gives them, recomputes the content ids of - `H2.root` and `H2.anchor`, and confirms that `B.canonicalHead` keeps - its slot id, which does not depend on its target; + `H2.root` and `H2.anchor`, and recomputes the slot id of the resolved + `B.canonicalHead` from its source and type, which the retarget keeps; 7. the read set, precondition slots, and write set lie inside the footprint law; 8. the expected `H1` and version-7 digest hold at `c`, and every endpoint @@ -1269,8 +1322,9 @@ replacement "there"}` at basis `c`. The decided Tick is written to the WAL, then published. 10. **Visibility.** Coordinate `t` shows `B → H1 → T1`. Coordinate `t+1` shows `B → H2 → T2`, with `H2 → I`. No coordinate shows `H2` without `T2`. `H1` - and `T1` are no longer reachable from `B`; they leave the state root, and - whether their bytes are kept is the retention policy's decision. + and `T1` are no longer reachable from `B`, so they leave the state root, + but they stay records of the state until a patch deletes them (see + [Basis](#basis-budget-retries)). A crash before the WAL record loses the Tick entirely; the Action was never decided, so after recovery it is the same pending submission, evaluated again @@ -1334,80 +1388,88 @@ propose `ReplaceEdge B -canonicalHead-> expected H1`. Each behaviour below needs a test before implementation: -| Property | Test | -| ----------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| Atomicity | A failure on the last effect of a multi-node proposal leaves the parent state root and patch unchanged. | -| Ensure identity | Identical existing node: no patch entry. Different existing node: `ContentIdentityConflict`. | -| Partial occupancy | A node record without its atom, or an atom without its record: `CreateNode` obstructs with `PreconditionMismatch` and `EnsureNode` with `ContentIdentityConflict`; neither overwrites the orphan slot. | -| Witnessed footprint | A program that reads a slot outside its law obstructs, even if its writes are in bounds. | -| Stale basis | A `ReplaceAtom` or `ReplaceEdge` whose expected value already disagrees with the value at its evaluation basis obstructs with `PreconditionMismatch`, posture `Proposed`, `NotDerived`, and no patch; when the value instead changed after the origin, a whole-frontier Action obstructs with `BasisChanged` before evaluation and a scoped one at the history check. | -| Budget | Exhausting steps or bytes obstructs with `BudgetExceeded` and `NoProposal`; a proposal whose patch alone exceeds the Action-batch operation limit obstructs with `BudgetExceeded`, posture `Proposed` with its digests, and no committed patch. | -| Effect charges | Each effect is charged one step and its fixed `write_bytes` (64 plus atom bytes for node and atom effects, 128 for edge effects) at emission; a budget one byte short of a proposal's total obstructs with `BudgetExceeded` and `NoProposal`. | -| Atom write bytes | A proposal whose atom bytes exceed the delegated `write_bytes` obstructs with `BudgetExceeded` and `NoProposal`; a whole-frontier `BasisChanged` records zero consumed budget. | -| Validation read budget | A `ReplaceAtom` of a stored atom larger than the remaining `read_bytes` obstructs with `BudgetExceeded`, posture `Proposed`, `NotDerived`, before hashing it; allocation beyond `edict_pure`'s `max_allocated_bytes` obstructs with `BudgetExceeded` and `NoProposal`. | -| Budget-stopped evidence | A scoped proposal whose validation read exhausts the budget records `Compared` with that dependency, and every later one, as not compared; no value past the stop is hashed. | -| Determinism | Same package, invocation, and evaluation basis give byte-identical proposals, proposal identities, read-set and write-set digests, and derived patches, whether the Action is evaluated alone or beside other Actions. Receipts and composite patches are compared only for identical Tick contexts. | -| Canonical order | Emitting the same effects in different orders yields the same canonical `EffectProposal` bytes and proposal identity. | -| Blob-independent order | Two same-kind duplicate effects on one slot with different atoms produce the same canonical image and proposal identity whichever order they were emitted in. | -| Encoding vectors | Fixed byte vectors for every row, the proposal image, every set digest, a bridge table with each posture, validity, evidence, and outcome tag, and `EOACT004` reproduce the specified bytes, sizes, and receipt-v5 digests. | -| In-place validation | Each image validates and reads through a view without heap allocation; an image with nonzero padding, a nonzero unused field, an unsorted section, an out-of-bounds range, or loosely packed blob bytes is refused. | -| No hot-path allocation | With an allocation-counting allocator, a warmed-up Tick of bridge Actions performs zero heap allocations during evaluation, validation, derivation, and composition, and recovery of that Tick allocates nothing per Action. | -| Arena independence | The same Ticks decided on hosts with different initial arena capacities produce byte-identical receipts, WAL records, and state roots. | -| Range-read entries | An empty observed range encodes as a range entry with count zero, never as an absent outcome, and a nonempty range's entry matches the specified layout byte for byte. | -| Canonical slots | Differently expressed effects on one slot (`EnsureEdge` and `ReplaceEdge` of one edge id; `CreateNode` and `ReplaceAtom` of one node) obstruct with `DuplicateEffectTarget`. | -| Write set | The derived patch's actual footprint equals the write set, including the atom slots of created nodes and the range key of every created or retargeted edge, so a committed `EnsureEdge` and `ReplaceEdge` pass the footprint check. | -| Identity law | A proposed node or edge id that does not match the package's identity law obstructs with `IdentityLawViolation`, posture `Proposed`, `NotDerived`, and recovery reproduces it. | -| Node resolution | A `ReplaceAtom` of a node absent at the evaluation basis obstructs at step 2 with `NodeMissing`, posture `Proposed`, `NotDerived`. | -| Content address | Under `ContentAddressV1`, a node id equal to the specified BLAKE3 over its type, atom, and the edges its creating proposal's `EnsureEdge` effects give it, and an edge id over its endpoints and type, pass; any other id obstructs with `IdentityLawViolation`; fixed vectors pin both derivations. | -| Addressing by type | A `ReplaceAtom` of a node whose type the schema marks content-addressed, or an `EnsureNode` of a mutable type, obstructs with `IdentityLawViolation`; a `ReplaceAtom` of a mutable-type node commits. | -| Edge addressing | An edge from a mutable node keeps its slot id when a `ReplaceEdge` retargets it; a creating `EnsureEdge` from an existing content node, a `CreateNode` of a content type, and a `ReplaceEdge` of an edge from a content node each obstruct with `IdentityLawViolation`. | -| Unreachable warp | An effect naming a key in a missing warp, or in a warp no valid portal chain reaches, obstructs with `UnreachableWarp`, posture `Proposed`, `NotDerived`. | -| Origin reachability | A scoped Action naming a warp unreachable at its origin obstructs with `UnreachableWarp` even when an intervening Tick created the portal that reaches it. | -| Portal permission and cost | At validation step 1, an effect in a descended warp whose chain crosses a portal owned by a type outside `portal_owner_types` obstructs with `FootprintLawViolation`, and a chain whose crossings exceed the remaining budget obstructs with `BudgetExceeded`. | -| Schema | An effect whose node type, atom `type_id`, or edge type the package's schema does not admit obstructs with `SchemaViolation`, posture `Proposed`, `NotDerived`, and recovery reproduces it. | -| Resolved schema | A `ReplaceEdge` whose basis edge type the schema does not admit, or a `ReplaceAtom` whose owner node type does not admit the new atom type, obstructs with `SchemaViolation` after its record is read and charged. | -| Endpoint types | A creating `EnsureEdge` or a `ReplaceEdge` whose source or target node type the schema does not admit for that edge type obstructs with `SchemaViolation`, after its endpoint records are read and charged. | -| Validation order | A proposal that breaks several rules (for example a duplicate target, an unadmitted type, and a missing endpoint) records the obstruction of the earliest step in the validation order, live and on recovery. | -| Conflict before batch limit | A candidate that both overlaps an earlier accepted Action and would exceed the batch operation limit records `RejectedFootprintConflict`, as today's scheduler does. | -| Footprint stages | A target outside the footprint law obstructs with `FootprintLawViolation` and `NotDerived`, and so does a `ReplaceEdge` whose basis edge's range key lies outside the law; a derived patch whose footprint differs from the write set obstructs with `FootprintMismatch` and `Derived`. | -| Resolved footprint | A `ReplaceEdge` whose resolved source node lies outside the footprint law obstructs with `FootprintLawViolation`. | -| Restored value (ABA) | A dependency written A to B and back to A between origin and commit parent is found through the written-slot set and obstructs with `BasisChanged`, including when the intervening patch under-declared its `out_slots`; with one replay patch in the interval missing, the Action obstructs before evaluation as a whole-frontier `BasisChanged`, posture `NoProposal`, with an empty read set. | -| Scoped preconditions | A dependency-scoped Action whose `ReplaceAtom` expected value, or one of whose edge endpoints, changed and was restored between its origin and the commit parent obstructs with `BasisChanged`, though the program never read that slot; recovery reproduces the outcome and its changed dependency. | -| Origin resolution | A scoped `ReplaceEdge` whose program never read the edge, and whose source node is deleted and recreated between origin and commit parent while the edge is unchanged, obstructs with `BasisChanged`, because resolution at the origin put the source in its dependency set. | -| Scoped conflict recovery | A scoped Action rejected because an earlier Action in its Tick replaced a value it expects recovers as `RejectedFootprintConflict`, not `PreconditionMismatch`. | -| Scoped evidence forms | An evaluation obstruction records `OriginOnly`, a dependency-scoped `BasisChanged` records `HistoryChanged` with no compared values, and every other scoped outcome records `Compared` over commit-parent values; none is compared at a validation position that includes an earlier overlapping write. | -| OriginOnly recovery | A scoped proposal that fails reachability or resolution is recovered by replaying only that failure, with no dependency set rebuilt. | -| Resolution failure evidence | A scoped `ReplaceEdge` whose edge is missing at its origin records `OriginOnly` with `PreconditionMismatch`; recovery reproduces it without running the history check. | -| Retry basis | A new submission selected after the head advanced is evaluated at its recorded basis, not at the selection-time state. | -| Receipt digest v5 | A version-5 receipt digest begins with the little-endian `u16` `5` and changes when any bridge field changes, including the validation position, a dependency's comparison mode, or a compared value; version-4 receipt identities are unchanged. | -| Proposal binding | For every `Proposed` outcome class in the recovery matrix, including rejected ones, altering one recorded effect-derived digest or the proposal identity refuses the history. | -| No-proposal evidence | An evaluation that obstructs before proposing records `NoProposal` with its read-set digest and no proposal, write-set, or patch digest. | -| Recovery | Crash before and after the decided-Tick WAL record; fresh-host recovery reproduces the same state root and receipts. | -| Composition | Two Actions with independent witnessed footprints (neither writes a slot the other reads or writes) commit in one Tick. A write/write overlap obstructs the later one, and so does a write/read overlap: A reads X and writes Y, B writes X and Z. | -| Shared structure | Two Actions that `Ensure` the same node already present in the basis both commit in one Tick. | -| Same-Tick identical create | Two Actions, dependency-scoped included, that both `Ensure` the same node absent at the commit parent: the later obstructs with `RejectedFootprintConflict` instead of reading the earlier one's write. | -| Referential integrity | An edge to a node neither in the basis nor created by the proposal obstructs with `DanglingReference`; a pre-existing dangling edge elsewhere in the basis does not obstruct an unrelated proposal. | -| Portal chain | An effect in a descended warp records every portal on its root-to-target chain as a read; a co-scheduled portal retarget conflicts with it, and a portal changed and restored between origin and commit parent obstructs a scoped Action with `BasisChanged`. | -| Visibility | No published coordinate shows a retargeted head without every node and edge the proposal introduced; pre-existing structure the head reaches is not validated. | -| Witnessed reads | The receipt's read set equals the slots the view resolved, including for a program that reads and then obstructs. | -| Negative reads | An Action that observed a node, atom, or edge set absent obstructs when a co-scheduled Action creates it, including a fresh edge of the observed source and type with a new edge id when the observed set was empty (outgoing-edge range key). | -| Range metering | A range read over a node with more edges than the law's maximum, or than the remaining budget covers, obstructs with `BudgetExceeded` and `NoProposal` before copying any edge; an admissible range charges one step and 64 read bytes per edge. | -| Absent probes | A read that finds nothing records the node type it expected; a probe for a type outside `read_node_types` obstructs at step 7 with `FootprintLawViolation`, and recovery reproduces the decision from the read set alone. | -| Edge-read basis | An Action whose package's footprint law admits a range read commits only onto its exact evaluation basis, including when this invocation reads no edge. | -| Final outcomes | A rejected Action's outcome, receipt, and attribution are unchanged after a later submission of the same edit succeeds. | -| Duplicate resubmission | Resubmitting a decided Action's identical content returns `Duplicate` with the original outcome and commits no Tick. | -| Attribution | Every Action in a bridge Tick appears once in the attribution section, mapped to its own receipt entry. | -| One effect per slot | Two effects on one slot (two `ReplaceAtom`, or `EnsureNode` plus `ReplaceAtom` of the same node) obstruct with `DuplicateEffectTarget`, posture `Proposed`, `NotDerived`. | -| Scoped basis change | A dependency-scoped Action evaluated at its origin obstructs with `BasisChanged`, posture `Proposed` with `NotDerived`, and keeps its proposal, read set, and changed dependency when a dependency changed, even if its proposal would also fail validation; an unchanged dependency set lets it commit. | -| Scoped evaluation obstruction | A dependency-scoped Action whose evaluation exhausts its budget, or fails an authored guard, keeps that obstruction with `NoProposal` even when one of its reads changed after the origin; recovery reproduces it. | -| Receipt format | `EPRV0003` records recover byte for byte through the unchanged decoder; a Tick containing a bridge Action is written as `EPRV0004`, with its fixed-profile Actions as all-zero `NotBridge` rows; a Tick of fixed-profile Actions alone stays `EPRV0003` and receipt digest version 4; a history mixing both formats recovers. | -| Package version | A source-compiled package encodes as `ExecutableOperationPackageV2` with no `lawpack` entry and an `EdictEffectProgram` (program tag `2`) that the evaluator dispatches to the bridge; a V2 package carrying a fixed profile runs it unchanged; V1 packages keep their bytes and identities, and no V2 identity equals a V1 identity. | -| Lawpack by program | A V2 package whose fixed-profile program omits the lawpack, or whose `EdictEffectProgram` carries one, is refused at admission. | -| Invocation version | An `EdictInput` (tag `2`) invocation is admitted only for an `EdictEffectProgram` package and is refused for a fixed profile, and a fixed-profile kind is refused for a tag `2` program. | -| Input bounds | An `EdictInput` that is not canonical CBOR, or longer than `max_input_bytes`, is refused at admission; a well-formed input of the wrong shape obstructs evaluation with `InputRejected` and `NoProposal`. | -| Outcome record | A bridge obstruction, including an authored one such as `BasisNotCanonical`, is retained as `EOACT004` with the same coordinate as its receipt; fixed-profile Actions still write `EOACT003`, and mixed histories recover. | -| Recovered evidence | For each outcome class, recovery performs that class's existing verification plus the new comparisons (posture, proposal identity, read-set, write-set, and patch digests where the class reconstructs or re-evaluates); a tampered digest refuses the history, a whole-frontier `BasisChanged` is not re-evaluated, the batch composition is reconstructed for every class, and no recorded outcome changes. | -| Pending recovery | An Action lost with its Tick before the WAL record is recovered as the same submission, not a new one. | +| Property | Test | +| ----------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | +| Atomicity | A failure on the last effect of a multi-node proposal leaves the parent state root and patch unchanged. | +| Ensure identity | Identical existing node: no patch entry. Different existing node: `ContentIdentityConflict`. | +| Partial occupancy | A node record without its atom, or an atom without its record: `CreateNode` obstructs with `PreconditionMismatch` and `EnsureNode` with `ContentIdentityConflict`; neither overwrites the orphan slot. | +| Witnessed footprint | A program that reads a slot outside its law obstructs, even if its writes are in bounds. | +| Stale basis | A `ReplaceAtom` or `ReplaceEdge` whose expected value already disagrees with the value at its evaluation basis obstructs with `PreconditionMismatch`, posture `Proposed`, `NotDerived`, and no patch; when the value instead changed after the origin, a whole-frontier Action obstructs with `BasisChanged` before evaluation and a scoped one at the history check. | +| Budget | Exhausting steps or bytes obstructs with `BudgetExceeded` and `NoProposal`; a proposal whose patch alone exceeds the Action-batch operation limit obstructs with `BudgetExceeded`, posture `Proposed` with its digests, and no committed patch. | +| Effect charges | Each effect is charged one step and its fixed `write_bytes` (64 plus atom bytes for node and atom effects, 128 for edge effects) at emission; a budget one byte short of a proposal's total obstructs with `BudgetExceeded` and `NoProposal`. | +| Atom write bytes | A proposal whose atom bytes exceed the delegated `write_bytes` obstructs with `BudgetExceeded` and `NoProposal`; a whole-frontier `BasisChanged` records zero consumed budget. | +| Validation read budget | A `ReplaceAtom` of a stored atom larger than the remaining `read_bytes` obstructs with `BudgetExceeded`, posture `Proposed`, `NotDerived`, before hashing it; allocation beyond `edict_pure`'s `max_allocated_bytes` obstructs with `BudgetExceeded` and `NoProposal`. | +| Budget-stopped evidence | A scoped proposal whose validation read exhausts the budget records `Compared` with that dependency, and every later one, as not compared; no value past the stop is hashed. | +| Determinism | Same package, invocation, and evaluation basis give byte-identical proposals, proposal identities, read-set and write-set digests, and derived patches, whether the Action is evaluated alone or beside other Actions. Receipts and composite patches are compared only for identical Tick contexts. | +| Canonical order | Emitting the same effects in different orders yields the same canonical `EffectProposal` bytes and proposal identity. | +| Blob-independent order | Two same-kind duplicate effects on one slot with different atoms produce the same canonical image and proposal identity whichever order they were emitted in. | +| Encoding vectors | Fixed byte vectors for every row, the proposal image, every set digest, a bridge table with each posture, validity, evidence, and outcome tag, and `EOACT004` reproduce the specified bytes, sizes, and receipt-v5 digests. | +| In-place validation | Each image validates and reads through a view without heap allocation; an image with nonzero padding, a nonzero unused field, an unsorted section, an out-of-bounds range, or loosely packed blob bytes is refused. | +| No hot-path allocation | With an allocation-counting allocator, a warmed-up Tick of bridge Actions performs zero heap allocations during evaluation, validation, derivation, and composition, and recovery of that Tick allocates nothing per Action. | +| Arena independence | The same Ticks decided on hosts with different initial arena capacities produce byte-identical receipts, WAL records, and state roots. | +| Range-read entries | An empty observed range encodes as a range entry with count zero, never as an absent outcome, and a nonempty range's entry matches the specified layout byte for byte. | +| Canonical slots | Differently expressed effects on one slot (`EnsureEdge` and `ReplaceEdge` of one edge id; `CreateNode` and `ReplaceAtom` of one node) obstruct with `DuplicateEffectTarget`. | +| Write set | The derived patch's actual footprint equals the write set, including the atom slots of created nodes and the range key of every created or retargeted edge, so a committed `EnsureEdge` and `ReplaceEdge` pass the footprint check. | +| Identity law | A proposed node or edge id that does not match the package's identity law obstructs with `IdentityLawViolation`, posture `Proposed`, `NotDerived`, and recovery reproduces it. | +| Node resolution | A `ReplaceAtom` of a node absent at the evaluation basis obstructs at step 2 with `NodeMissing`, posture `Proposed`, `NotDerived`. | +| Content address | Under `ContentAddressV1`, a node id equal to the specified BLAKE3 over its type, atom, and the edges its creating proposal's `EnsureEdge` effects give it, and an edge id over its endpoints and type, pass; any other id obstructs with `IdentityLawViolation`; fixed vectors pin both derivations. | +| Addressing by type | A `ReplaceAtom` of a node whose type the schema marks content-addressed, or an `EnsureNode` of a mutable type, obstructs with `IdentityLawViolation`; a `ReplaceAtom` of a mutable-type node commits. | +| Edge addressing | An edge from a mutable node keeps its slot id when a `ReplaceEdge` retargets it; a creating `EnsureEdge` from an existing content node, a `CreateNode` of a content type, and a `ReplaceEdge` of an edge from a content node each obstruct with `IdentityLawViolation`. | +| Unreachable warp | An effect naming a key in a missing warp, or in a warp no valid portal chain reaches, obstructs with `UnreachableWarp`, posture `Proposed`, `NotDerived`. | +| Origin reachability | A scoped Action naming a warp unreachable at its origin obstructs with `UnreachableWarp` even when an intervening Tick created the portal that reaches it. | +| Portal permission and cost | At validation step 1, an effect in a descended warp whose chain crosses a portal owned by a type outside `portal_owner_types` obstructs with `FootprintLawViolation`, and a chain whose crossings exceed the remaining budget obstructs with `BudgetExceeded`. | +| Schema | An effect whose node type, atom `type_id`, or edge type the package's schema does not admit obstructs with `SchemaViolation`, posture `Proposed`, `NotDerived`, and recovery reproduces it. | +| Resolved schema | A `ReplaceEdge` whose basis edge type the schema does not admit, or a `ReplaceAtom` whose owner node type does not admit the new atom type, obstructs with `SchemaViolation` after its record is read and charged. | +| Endpoint types | A creating `EnsureEdge` or a `ReplaceEdge` whose source or target node type the schema does not admit for that edge type obstructs with `SchemaViolation`, after its endpoint records are read and charged. | +| Validation order | A proposal that breaks several rules (for example a duplicate target, an unadmitted type, and a missing endpoint) records the obstruction of the earliest step in the validation order, live and on recovery. | +| Conflict before batch limit | A candidate that both overlaps an earlier accepted Action and would exceed the batch operation limit records `RejectedFootprintConflict`, as today's scheduler does. | +| Footprint stages | A target outside the footprint law obstructs with `FootprintLawViolation` and `NotDerived`, and so does a `ReplaceEdge` whose basis edge's range key lies outside the law; a derived patch whose footprint differs from the write set obstructs with `FootprintMismatch` and `Derived`. | +| Resolved footprint | A `ReplaceEdge` whose resolved source node lies outside the footprint law obstructs with `FootprintLawViolation`. | +| Restored value (ABA) | A dependency written A to B and back to A between origin and commit parent is found through the written-slot set and obstructs with `BasisChanged`, including when the intervening patch under-declared its `out_slots`; with one replay patch in the interval missing, the Action obstructs before evaluation as a whole-frontier `BasisChanged`, posture `NoProposal`, with an empty read set. | +| Scoped preconditions | A dependency-scoped Action whose `ReplaceAtom` expected value, or one of whose edge endpoints, changed and was restored between its origin and the commit parent obstructs with `BasisChanged`, though the program never read that slot; recovery reproduces the outcome and its changed dependency. | +| Origin resolution | A scoped `ReplaceEdge` whose program never read the edge, and whose source node is deleted and recreated between origin and commit parent while the edge is unchanged, obstructs with `BasisChanged`, because resolution at the origin put the source in its dependency set. | +| Scoped conflict recovery | A scoped Action rejected because an earlier Action in its Tick replaced a value it expects recovers as `RejectedFootprintConflict`, not `PreconditionMismatch`. | +| Scoped evidence forms | An evaluation obstruction records `OriginOnly`, a dependency-scoped `BasisChanged` records `HistoryChanged` with no compared values, and every other scoped outcome records `Compared` over commit-parent values; none is compared at a validation position that includes an earlier overlapping write. | +| OriginOnly recovery | A scoped proposal that fails reachability or resolution is recovered by replaying only that failure, with no dependency set rebuilt. | +| Resolution failure evidence | A scoped `ReplaceEdge` whose edge is missing at its origin records `OriginOnly` with `PreconditionMismatch`; recovery reproduces it without running the history check. | +| Retry basis | A new submission selected after the head advanced is evaluated at its recorded basis, not at the selection-time state. | +| Receipt digest v5 | A version-5 receipt digest begins with the little-endian `u16` `5` and changes when any bridge field changes, including the validation position, a dependency's comparison mode, or a compared value; version-4 receipt identities are unchanged. | +| Proposal binding | For every `Proposed` outcome class in the recovery matrix, including rejected ones, altering one recorded effect-derived digest or the proposal identity refuses the history. | +| No-proposal evidence | An evaluation that obstructs before proposing records `NoProposal` with its read-set digest and no proposal, write-set, or patch digest. | +| Recovery | Crash before and after the decided-Tick WAL record; fresh-host recovery reproduces the same state root and receipts. | +| Composition | Two Actions with independent witnessed footprints (neither writes a slot the other reads or writes) commit in one Tick. A write/write overlap obstructs the later one, and so does a write/read overlap: A reads X and writes Y, B writes X and Z. | +| Shared structure | Two Actions that `Ensure` the same node already present in the basis both commit in one Tick. | +| Same-Tick identical create | Two Actions, dependency-scoped included, that both `Ensure` the same node absent at the commit parent: the later obstructs with `RejectedFootprintConflict` instead of reading the earlier one's write. | +| Referential integrity | An edge to a node neither in the basis nor created by the proposal obstructs with `DanglingReference`; a pre-existing dangling edge elsewhere in the basis does not obstruct an unrelated proposal. | +| Portal chain | An effect in a descended warp records every portal on its root-to-target chain as a read; a co-scheduled portal retarget conflicts with it, and a portal changed and restored between origin and commit parent obstructs a scoped Action with `BasisChanged`. | +| Visibility | No published coordinate shows a retargeted head without every node and edge the proposal introduced; pre-existing structure the head reaches is not validated. | +| Witnessed reads | The receipt's read set equals the slots the view resolved, including for a program that reads and then obstructs. | +| Negative reads | An Action that observed a node, atom, or edge set absent obstructs when a co-scheduled Action creates it, including a fresh edge of the observed source and type with a new edge id when the observed set was empty (outgoing-edge range key). | +| Range metering | A range read over a node with more edges than the law's maximum, or than the remaining budget covers, obstructs with `BudgetExceeded` and `NoProposal` before copying any edge; an admissible range charges one step and 64 read bytes per edge. | +| Absent probes | A read that finds nothing records the node type it expected; a probe for a type outside `read_node_types` obstructs at step 7 with `FootprintLawViolation`, and recovery reproduces the decision from the read set alone. | +| Edge-read basis | An Action whose package's footprint law admits a range read commits only onto its exact evaluation basis, including when this invocation reads no edge. | +| Final outcomes | A rejected Action's outcome, receipt, and attribution are unchanged after a later submission of the same edit succeeds. | +| Duplicate resubmission | Resubmitting a decided Action's identical content returns `Duplicate` with the original outcome and commits no Tick. | +| Attribution | Every Action in a bridge Tick appears once in the attribution section, mapped to its own receipt entry. | +| One effect per slot | Two effects on one slot (two `ReplaceAtom`, or `EnsureNode` plus `ReplaceAtom` of the same node) obstruct with `DuplicateEffectTarget`, posture `Proposed`, `NotDerived`. | +| Scoped basis change | A dependency-scoped Action evaluated at its origin obstructs with `BasisChanged`, posture `Proposed` with `NotDerived`, and keeps its proposal, read set, and changed dependency when a dependency changed, even if its proposal would also fail validation; an unchanged dependency set lets it commit. | +| Scoped evaluation obstruction | A dependency-scoped Action whose evaluation exhausts its budget, or fails an authored guard, keeps that obstruction with `NoProposal` even when one of its reads changed after the origin; recovery reproduces it. | +| Receipt format | `EPRV0003` records recover byte for byte through the unchanged decoder; a Tick containing a bridge Action is written as `EPRV0004`, with its fixed-profile Actions as all-zero `NotBridge` rows; a Tick of fixed-profile Actions alone stays `EPRV0003` and receipt digest version 4; a history mixing both formats recovers. | +| Package version | A source-compiled package encodes as `ExecutableOperationPackageV2` with no `lawpack` entry and an `EdictEffectProgram` (program tag `2`) that the evaluator dispatches to the bridge; a V2 package carrying a fixed profile runs it unchanged; V1 packages keep their bytes and identities, and no V2 identity equals a V1 identity. | +| Lawpack by program | A V2 package whose fixed-profile program omits the lawpack, or whose `EdictEffectProgram` carries one, is refused at admission. | +| Invocation version | An `EdictInput` (tag `2`) invocation is admitted only for an `EdictEffectProgram` package and is refused for a fixed profile, and a fixed-profile kind is refused for a tag `2` program. | +| Input bounds | An `EdictInput` that is not canonical CBOR, or longer than `max_input_bytes`, is refused at admission; a well-formed input of the wrong shape obstructs evaluation with `InputRejected` and `NoProposal`. | +| Outcome record | A bridge obstruction, including an authored one such as `BasisNotCanonical`, is retained as `EOACT004` with the same coordinate as its receipt; fixed-profile Actions still write `EOACT003`, and mixed histories recover. | +| ----------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| Pending recovery | An Action lost with its Tick before the WAL record is recovered as the same submission, not a new one. | +| Ensure resolution | An `EnsureNode` whose identical target already exists is a read, not a write, and the footprint law checks the write set without it; its target read is charged at step 2. | +| Absent endpoint history | A scoped Action whose edge endpoint was absent at its origin and created by an intervening Tick obstructs with `BasisChanged`, through the endpoint's `Absence` dependency. | +| Bridge result | A committed bridge Action's exact result bytes are retained in `EPRV0004` and `EOACT004` and bound by `result_identity`; recovery refuses re-evaluated bytes that differ. | +| Absent probe row | An absent `ReadRow` carries its expected node type in `subject`, and the image validator accepts it. | +| Set encoding | A footprint law or schema with an unsorted or duplicated set is refused at admission; two packages declaring the same sets have identical bytes. | +| Portal owner dependency | A delayed scoped Action whose portal owner's node type changed in an intervening Tick obstructs with `BasisChanged`; the owner read is charged at step 1. | +| Existing edge identity | A `ReplaceEdge` of a basis edge whose id is not its slot id under `ContentAddressV1` obstructs with `IdentityLawViolation`. | +| Unreachable records | A keyed read of a record that a prior Tick made unreachable resolves identically live and after recovery, because the record stays until a patch deletes it. | ## Open questions From 55a20083908851624c51b273ce0eee25a4f981a2 Mon Sep 17 00:00:00 2001 From: James Ross Date: Fri, 16 Oct 2026 20:51:32 -0700 Subject: [PATCH 34/50] docs: complete resolution, exact schema shapes, V2 domains; keyed-read semantics Round 25 on the authored-effects bridge: - Step 2 resolves everything later steps decide on: - both the node record and the alpha slot of each Ensure/Create target; - the source record of every EnsureEdge and ReplaceEdge, whose type selects the edge-id rule; - for an existing content node, its outgoing bucket, so its id is recomputed before adoption. - Step 1 covers descended read keys as well as effect keys, so their portals and owners are dependencies and portal_owner_types applies to reads. - Range reads: an undeclared range type obstructs evaluation at once, so scoped eligibility holds. The view charges every edge in the source bucket and compares the bucket length with max_range_edges, because no per-type index exists. - An absent atom probe records its expected atom type_id. - The footprint law and schema have exact CDDL shapes with required text keys and no extras. - V2 names its exact schema strings and hash domains (the v1 suffix becomes v2 for the Echo-owned ones). The semantic closure keeps its producer's identity. - Keyed reads of unreachable records are stated as observable, versioned semantics. Any future reclamation must preserve them or ship a new semantics version. - Nine tests are added and the trace is updated. Co-Authored-By: Claude Opus 5.5 --- .../authored-operation-effects.md | 165 ++++++++++++------ 1 file changed, 114 insertions(+), 51 deletions(-) diff --git a/docs/architecture/authored-operation-effects.md b/docs/architecture/authored-operation-effects.md index c30a626a..4b12eaee 100644 --- a/docs/architecture/authored-operation-effects.md +++ b/docs/architecture/authored-operation-effects.md @@ -354,6 +354,12 @@ a digest alone could not be executed. One law is defined: when a `ReplaceEdge` retargets it. A creating `EnsureEdge` of it must carry that id. + An `EnsureNode` whose content-typed target already exists adopts it only + if the existing node's record, atom, and complete outgoing edge set equal + what the proposal gives it. Echo then recomputes the id from those, so a + node written under some other id is never adopted; a mismatch is + `IdentityLawViolation`. + Echo recomputes every id a proposal creates, and the id of every edge a `ReplaceEdge` or an identical `EnsureEdge` resolves from the basis (its slot or content id from the resolved record's source, type, and, for a @@ -398,15 +404,23 @@ retargets an edge with that source and type claims it as a write, in addition to the edge id. Independence treats range keys like every other key: a write against a read or write conflicts. -A range read is metered before it materializes anything. The view first -takes the range's edge count from the source node's bucket, without copying -an edge, and charges one step and 64 `read_bytes` per edge (one edge local id -and one target local id). If the count exceeds the footprint law's -`max_range_edges`, or the charge exceeds the remaining budget, evaluation -obstructs with `BudgetExceeded`, posture `NoProposal`, before any edge is -copied. Otherwise the view writes the range's `EdgePairRow`s directly into -the arena. Consumed cost and the read set therefore depend only on the basis, -and a high-degree node cannot force an allocation the budget did not cover. +A range read is checked and metered before it materializes anything. A +range read of an edge type outside the footprint law's `range_edge_types` +obstructs evaluation at once (`FootprintLawViolation`, posture `NoProposal`), +before it reads anything, so a package whose law admits no range read can +never produce a proposal that depends on one, and its static scoped +eligibility holds. For an admitted type, the view takes the length of the +source node's whole outgoing bucket, without copying an edge. `GraphStore` +keeps one bucket per source, not per type, so finding the matching edges +inspects every edge in it; the view therefore charges one step and 64 +`read_bytes` for every edge in the bucket, matching or not, and compares the +bucket length, not the match count, with `max_range_edges`. If the length +exceeds `max_range_edges`, or the charge exceeds the remaining budget, +evaluation obstructs with `BudgetExceeded`, posture `NoProposal`, before any +edge is inspected. Otherwise the view writes the matching edges' +`EdgePairRow`s directly into the arena. Consumed cost and the read set +therefore depend only on the basis, and a high-degree node cannot force work +or allocation the budget did not cover. These dependency kinds follow the [Action validity across ticks](../topics/GeneratedRules.md#action-validity-across-ticks) @@ -430,12 +444,12 @@ the read limit and the footprint law bound the slice. A range read beyond footprint law is found at validation step 7 (`FootprintLawViolation`, `Proposed`, `NotDerived`). -A read that may find nothing names the node type it expects: the program asks -for a node or atom of that type. The view records the expected type with an -absent outcome (in the `ReadRow`'s `subject` field), so the footprint law's -`read_node_types` decides whether the probe is permitted, live and on recovery -alike. A first implementation may still sit on today's fully materialized -worldline state, but nothing in this contract may rely on that. +A read that may find nothing names the type it expects: a node read names a node +type, and an atom read names an atom `type_id`. The view records that type with +an absent outcome (in the `ReadRow`'s `subject` field), so the footprint law's +`read_node_types` or `atom_types` decides whether the probe is permitted, live +and on recovery alike. A first implementation may still sit on today's fully +materialized worldline state, but nothing in this contract may rely on that. ## Footprint law @@ -443,18 +457,33 @@ worldline state, but nothing in this contract may rely on that. Every set-valued field in a footprint law or a schema is a CBOR array of distinct elements in strictly ascending order. An id is a 32-byte byte string, -ordered bytewise. A pair, such as a schema's (source node type, target node -type), is a two-element array ordered by its first element, then its second. A -map keyed by ids, such as the schema's `node_types` and `edge_types`, is a -CBOR map, whose keys `edict.canonical-cbor/v1` already orders. Admission -refuses a law or schema with an unsorted or duplicated set, rather than -normalizing it, so each law and schema has exactly one encoding and one -identity. `addressing` is the unsigned integer `0` for `content` and `1` for -`mutable`; counts are unsigned integers. +ordered bytewise. A pair is a two-element array ordered by its first element, +then its second. Admission refuses a law or schema with an unsorted or +duplicated set, rather than normalizing it. Map keys are text strings, ordered +as `edict.canonical-cbor/v1` orders them. The complete shapes, in CDDL, are: + +```text +footprint-law = { + "read_node_types": [* id], "write_node_types": [* id], + "atom_types": [* id], "range_edge_types": [* id], + "write_edge_types": [* id], "portal_owner_types": [* id], + "max_reads": uint, "max_writes": uint, "max_range_edges": uint, +} +schema = { + "node_types": { * id => node-type }, + "edge_types": { * id => [* endpoint-pair] }, +} +node-type = { "addressing": 0 / 1, "atom_types": [* id] } ; 0 content, 1 mutable +endpoint-pair = [source: id, target: id] +id = bstr .size 32 +``` + +No other key may appear, and every key shown is required, so each law and +schema has exactly one encoding and one identity. The package declares a footprint law, fixed per package with no invocation -parameters. It is an `edict.canonical-cbor/v1` map, with the -[set encoding](#set-encoding) below, and these entries: +parameters. It is an `edict.canonical-cbor/v1` map, in the exact shape that +[Set encoding](#set-encoding) gives, with these entries: `read_node_types` and `write_node_types` (sets of node type ids), `atom_types` (the atom `type_id`s it may read or write), `range_edge_types` (edge types it may read as outgoing ranges; empty makes it eligible for @@ -517,8 +546,10 @@ Validation of a proposal runs in one fixed order, and the first failing step decides the outcome, so a proposal that breaks several rules has exactly one recorded obstruction: -1. warp reachability of every effect key at the evaluation basis, root - first and in canonical effect order. Each portal crossed costs one step +1. warp reachability, at the evaluation basis, of every effect key and every + key in the witnessed read set that lies in a descended warp: root first, + effect keys in canonical effect order and then read keys in slot order, + each chain crossed once. Each portal crossed costs one step and 64 `read_bytes`, charged before it is read; a crossing the remaining budget cannot cover is `BudgetExceeded`. A portal owned by a node whose type is not in the footprint law's `portal_owner_types` is @@ -531,11 +562,16 @@ recorded obstruction: including an owner whose type changed; 2. resolution at the evaluation basis (for a scoped Action, its origin): in canonical effect order, read every record validation needs. That is the - target record of each `EnsureNode`, `CreateNode`, and `EnsureEdge`, + target of each `EnsureNode` and `CreateNode`, both its node record and + its alpha atom slot, and the target edge record of each `EnsureEdge`, present or absent, which decides whether the effect writes or only - confirms; each `ReplaceAtom`'s node record; each `ReplaceEdge`'s edge - record; and the node record of each endpoint of a creating `EnsureEdge` - or a `ReplaceEdge` that the proposal does not create. Charges follow + confirms; for an `EnsureNode` whose content-typed target exists, that + node's whole outgoing bucket, charged per edge like a range read, which + the identity law needs; each `ReplaceAtom`'s node record; each + `ReplaceEdge`'s edge record; the source node record of every `EnsureEdge` + and `ReplaceEdge`, whose type selects the edge-id rule; and the target + node record of a creating `EnsureEdge` or a `ReplaceEdge`, each unless the + proposal creates that node. Charges follow [Validation reads](#basis-budget-retries). Failures are `NodeMissing` for a `ReplaceAtom`'s missing node, `PreconditionMismatch` for a missing edge, or `BudgetExceeded`; whether a present target matches is decided at @@ -610,14 +646,18 @@ held; this design validates only what the proposal adds or retargets. ## Basis, budget, retries -- **Basis.** Reads, including observed absences, are taken from the exact basis: - the materialized state at that coordinate, which replaying the worldline's - patches determines exactly, including records no longer reachable from a root. - A record leaves that state only through a patch that deletes it. Retention - governs only the history before a coordinate, never the records of the current - state, so no host may drop an unreachable record outside a Tick. The state - root does not commit to unreachable records; a keyed read of one is bound by - the coordinate and replay, which every host reproduces identically. At commit, +- **Basis.** Reads, including observed absences, are taken from the exact + basis: the materialized state at that coordinate, which replaying the + worldline's patches determines exactly. That state includes records no + longer reachable from a root, and a keyed read resolves them. This is + observable bridge semantics, not a storage detail: a record leaves the + state only through a patch that deletes it, and no host may drop one + outside a Tick. Retention governs only the history before a coordinate. + The state root does not commit to unreachable records, so a keyed read of + one is bound by the coordinate and replay. Any future reclamation of + unreachable records must either preserve what a keyed read observes, for + example by deleting them in a Tick's patch, or ship as a new version of + these semantics; it can never silently change what an Action reads. At commit, every `expected…` value and every recorded dependency that a co-scheduled Action wrote must still hold; an overlap with an earlier accepted Action in the same Tick is `RejectedFootprintConflict` (see **Within one Tick** below). @@ -717,8 +757,10 @@ held; this design validates only what the proposal adds or retargets. `read_bytes`, an edge record one step and 128 `read_bytes`, and hashing an atom's value adds its byte length. A slot the program's view already read is not charged again, since its value is known. So step 1 charges portals - and their owners' records, step 2 charges each resolved record, including - every Ensure and Create target, and step 8 charges only the atoms it hashes, + and their owners' records, for effect and read keys alike, step 2 charges + each resolved record, including every Ensure and Create target with its + alpha slot, every edge source, and the outgoing bucket of an existing + content node, per edge, and step 8 charges only the atoms it hashes, for `ReplaceAtom`'s expected digest and an existing `EnsureNode`'s identical-content check. Charges come from the budget left after evaluation. A read the remaining budget cannot cover @@ -840,9 +882,20 @@ Consequences: invocation crosses between them. The V2 invocation identity has its own domain. - V2 canonical bytes carry their own version domain tags, so V2 program, - package, installed-operation, and semantic-closure identities never - coincide with V1 identities. V1 encoding, validation, and identities are + V2 changes only the version suffix of each Echo-owned schema string and + hash domain. The canonical maps carry the schema strings + `echo.operation-package/v2`, `echo.operation-program/v2`, and + `echo.operation-invocation/v2`. Identities hash the domains + `echo:operation-package:v2\0`, `echo:operation-program:v2\0`, + `echo:operation-invocation:v2\0`, `echo:operation-invocation-bytes:v2\0`, + `echo:operation-installation:v2\0`, + `echo:operation-package-admission:v2\0`, and + `echo:operation-invocation-admission:v2\0`, in place of the V1 domains + of the same names. Every other domain is unchanged. The semantic closure + is the producer's artifact and keeps the identity its producer defines; + a V2 package binds it exactly as a V1 package does. So V2 program, + package, invocation, and installed-operation identities never coincide + with V1 identities, and V1 encoding, validation, and identities are unchanged. ## Evidence @@ -1109,7 +1162,7 @@ rules: | Row | Bytes | Fields, in order | | ---------------- | ----- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `SlotRow` | 104 | `class` u8 (node record `0`, attachment `1`, edge `2`, range key `3`); `owner` u8 (attachment: node `0`, edge `1`); `plane` u8 (attachment: alpha `0`, beta `1`); 5 padding bytes; `warp_id`; `local_id` (the node, edge, attachment owner, or range source); `edge_type` (range key only) | -| `ReadRow` | 160 | `slot` (`SlotRow`); `outcome` u8 (present `0`, absent `1`, range `2`); 7 padding bytes; `subject`, 32 bytes: the value digest when present, the expected node type id when absent, zero for a range; `pairs` (`Range` into the pair section; range only) | +| `ReadRow` | 160 | `slot` (`SlotRow`); `outcome` u8 (present `0`, absent `1`, range `2`); 7 padding bytes; `subject`, 32 bytes: the value digest when present, when absent, the expected node type id for a node-record slot or the expected atom `type_id` for an attachment slot; zero for a range; `pairs` (`Range` into the pair section; range only) | | `EdgePairRow` | 64 | `edge_local_id`; `target_local_id` | | `EffectRow` | 280 | `kind` u8 (`EnsureNode` `0`, `CreateNode` `1`, `ReplaceAtom` `2`, `EnsureEdge` `3`, `ReplaceEdge` `4`); 7 padding bytes; `key_warp_id`; `key_local_id`; `type_id` (node type, or `EnsureEdge`'s edge type); `atom_type_id`; `atom` (`Range` into the blob); `expected_digest` (`ReplaceAtom`); `from_local_id` (`EnsureEdge`); `to_local_id` (`EnsureEdge`, and `ReplaceEdge`'s new target); `expected_to_local_id` (`ReplaceEdge`) | | `DependencyRow` | 152 | `slot` (`SlotRow`); `kind` u8; `mode` u8; 6 padding bytes, which together with `slot` form the 112-byte descriptor; `compared` u8 (present `0`, absent `1`, not compared `2`); 7 padding bytes; `compared_digest` (present only) | @@ -1281,12 +1334,13 @@ replacement "there"}` at basis `c`. - `ReplaceEdge B -canonicalHead-> expected H1, now H2` 6. **Validation**, in [Validation order](#validation-order): 1. every key is in the root warp, so no portal is crossed; - 2. resolution reads and charges the targets of `EnsureNode T2`, - `EnsureNode H2`, `EnsureEdge H2.root`, and `EnsureEdge H2.anchor` - (all absent, so all four create), `B`'s node record (for - `ReplaceAtom B`), the `B.canonicalHead` edge record (for - `ReplaceEdge`), and the endpoint `I`'s node record (for - `EnsureEdge H2 -anchor-> I`); + 2. resolution reads and charges the node records and alpha slots of the + `EnsureNode T2` and `EnsureNode H2` targets and the edge records of + the `EnsureEdge H2.root` and `H2.anchor` targets (all absent, so all + four create); `B`'s node record, for `ReplaceAtom B` and as the + source of `ReplaceEdge`, charged once; the `B.canonicalHead` edge + record; and the endpoint `I`'s node record. `H2`, the source of both + Ensures, is created here and is not read; 3. the Action is whole-frontier, so no history check runs; 4. no two effects share a slot; 5. the schema admits every type: `T2` and `H2` are content types, `B` is @@ -1470,6 +1524,15 @@ Each behaviour below needs a test before implementation: | Portal owner dependency | A delayed scoped Action whose portal owner's node type changed in an intervening Tick obstructs with `BasisChanged`; the owner read is charged at step 1. | | Existing edge identity | A `ReplaceEdge` of a basis edge whose id is not its slot id under `ContentAddressV1` obstructs with `IdentityLawViolation`. | | Unreachable records | A keyed read of a record that a prior Tick made unreachable resolves identically live and after recovery, because the record stays until a patch deletes it. | +| Alpha slot dependency | A scoped `EnsureNode` whose target's alpha slot changed in an intervening Tick, with its node record unchanged, obstructs with `BasisChanged`. | +| Absent atom probe | An absent atom read records its expected atom `type_id`; a probe for a type outside `atom_types` obstructs at step 7 with `FootprintLawViolation`. | +| Content-node adoption | An `EnsureNode` of an existing content node is adopted only when that node's record, atom, and outgoing edges recompute to its id; a node stored under another id obstructs with `IdentityLawViolation`. | +| Schema shape | A footprint law or schema with an unknown, missing, or reordered key, or a node-type entry of any other shape, is refused at admission. | +| Descended read reachability | A scoped Action that only reads a descended warp records that warp's portals and owners as dependencies, enforces `portal_owner_types`, and obstructs with `BasisChanged` when an intervening Tick changes them. | +| Undeclared range read | A range read of an edge type outside `range_edge_types` obstructs evaluation with `FootprintLawViolation`, posture `NoProposal`, before reading anything. | +| Range inspection metering | A range read over a bucket of many edges of other types is charged for every edge in the bucket and obstructs when the bucket length exceeds `max_range_edges`, even when no edge matches. | +| Existing edge source | An identical `EnsureEdge` whose source exists reads and charges the source record, and the edge id is recomputed under the rule its type selects. | +| V2 domains | V2 package, program, invocation, and installed-operation identities use the `v2` schema strings and domains, and differ from V1 identities over the same content. | ## Open questions From 097936c9d0effe8c3c271eff73b011474d5a2697 Mon Sep 17 00:00:00 2001 From: James Ross Date: Fri, 16 Oct 2026 21:03:06 -0700 Subject: [PATCH 35/50] docs: topology writes keep whole-frontier validity; graph_schema key; beta portals Round 26 on the authored-effects bridge: - Scoped eligibility is now refused, statically, to any package whose law admits a range read, an edge write, or a content-node write. Each of these depends on a whole outgoing bucket. That is a topology dependency, which the unrestricted UpsertEdge path can change without touching any slot a scoped Action recorded. - Creating or retargeting an edge from a mutable source reads the source's bucket and refuses an alias: another edge of the same type under any id. - The bridge does not traverse edge-owned (beta) portals; a chain crossing one is FootprintLawViolation at step 1. - The executable graph schema moves to the key `graph_schema`, so `schema` keeps its V1 role as the format discriminator. The exact tag-2 program map is given in CDDL. - Four tests are added. Co-Authored-By: Claude Opus 5.5 --- .../authored-operation-effects.md | 101 ++++++++++++------ 1 file changed, 66 insertions(+), 35 deletions(-) diff --git a/docs/architecture/authored-operation-effects.md b/docs/architecture/authored-operation-effects.md index 4b12eaee..2af69f04 100644 --- a/docs/architecture/authored-operation-effects.md +++ b/docs/architecture/authored-operation-effects.md @@ -352,7 +352,11 @@ a digest alone could not be executed. One law is defined: `echo.bridge.slot-edge-id.v1\0`, the warp id, the `from` local id, and the edge type id: one edge per source and type, whose id stays the same when a `ReplaceEdge` retargets it. A creating `EnsureEdge` of it must - carry that id. + carry that id. Because a non-bridge writer can store an edge under any + id, step 2 reads the source's whole outgoing bucket, charged per edge + like a range read, for every `EnsureEdge` and `ReplaceEdge` from a + mutable source. Any other edge of the same type from that source is an + alias, and the effect obstructs with `IdentityLawViolation`. An `EnsureNode` whose content-typed target already exists adopts it only if the existing node's record, atom, and complete outgoing edge set equal @@ -428,12 +432,23 @@ decision (#837). Value and single-key absence dependencies are the kinds that decision supports. A read of outgoing edges by type is a topology dependency, which that decision does not yet support. Eligibility is decided statically at admission, -never by the branch an evaluation takes: an Action whose package's footprint law -admits any outgoing-edge range read keeps the whole-frontier rule, whether or -not this invocation reads an edge, and commits only onto the exact basis it was -evaluated against. Every other bridge Action is eligible for dependency-scoped -validity. Widening that requires sound edge-set tracking under #837, not a -looser rule here. +never by the branch an evaluation takes. An Action keeps the whole-frontier +rule, whatever this invocation does, and commits only onto the exact basis it +was evaluated against, when its package's footprint law admits any of: + +- an outgoing-edge range read (a non-empty `range_edge_types`); +- an edge write (a non-empty `write_edge_types`). Creating or retargeting an + edge from a mutable source requires that no other edge of its type leave + that source, and that depends on the source's whole bucket; +- a write of a content-addressed node type. Adopting an existing content node + requires its whole outgoing edge set to match. + +Each of these is a topology dependency: a write from outside the bridge (the +unrestricted `UpsertEdge` path) can add an edge to a bucket without changing any +single slot a scoped Action recorded. Every other bridge Action is eligible for +dependency-scoped validity, and its writes are node records and atoms of mutable +types. Widening that requires sound edge-set tracking under #837, not a looser +rule here. The view resolves each slot on demand from the basis, as [Sliced Readings and Copy-on-Write Strands](sliced-readings-and-copy-on-write-strands.md) @@ -469,12 +484,18 @@ footprint-law = { "write_edge_types": [* id], "portal_owner_types": [* id], "max_reads": uint, "max_writes": uint, "max_range_edges": uint, } -schema = { +graph-schema = { "node_types": { * id => node-type }, "edge_types": { * id => [* endpoint-pair] }, } node-type = { "addressing": 0 / 1, "atom_types": [* id] } ; 0 content, 1 mutable endpoint-pair = [source: id, target: id] +program-v2-effect = { + "schema": "echo.operation-program/v2", "tag": 2, + "compiled_program": bstr, "target_ir_identity": id, + "evaluator_profile_identity": id, "identity_law": uint, + "graph_schema": bstr, "footprint_law": bstr, "max_input_bytes": uint, +} id = bstr .size 32 ``` @@ -489,10 +510,13 @@ parameters. It is an `edict.canonical-cbor/v1` map, in the exact shape that (edge types it may read as outgoing ranges; empty makes it eligible for scoped validity), `write_edge_types`, and the unsigned counts `max_reads`, `max_writes`, and `max_range_edges` (per range read), and `portal_owner_types` -(node types whose alpha portals an effect in a descended warp may cross; empty -confines the package to its root warp). Each portal on a validated chain must -be owned by a node of one of these types, or validation obstructs with -`FootprintLawViolation`. Its canonical bytes are part of the program's +(node types whose alpha portals an effect or read in a descended warp may +cross; empty confines the package to its root warp). Each portal on a +validated chain must be owned by a node of one of these types, or validation +obstructs with `FootprintLawViolation`. The bridge does not traverse +edge-owned (beta) portals: an edge has no node type for this law to admit, so +a chain crossing one is `FootprintLawViolation` at step 1, before any owner +record is read. Its canonical bytes are part of the program's identity. At validation Echo checks: - every node type, atom `type_id`, and edge type an effect writes is admitted @@ -562,16 +586,17 @@ recorded obstruction: including an owner whose type changed; 2. resolution at the evaluation basis (for a scoped Action, its origin): in canonical effect order, read every record validation needs. That is the - target of each `EnsureNode` and `CreateNode`, both its node record and - its alpha atom slot, and the target edge record of each `EnsureEdge`, - present or absent, which decides whether the effect writes or only - confirms; for an `EnsureNode` whose content-typed target exists, that - node's whole outgoing bucket, charged per edge like a range read, which - the identity law needs; each `ReplaceAtom`'s node record; each - `ReplaceEdge`'s edge record; the source node record of every `EnsureEdge` - and `ReplaceEdge`, whose type selects the edge-id rule; and the target - node record of a creating `EnsureEdge` or a `ReplaceEdge`, each unless the - proposal creates that node. Charges follow + target of each `EnsureNode` and `CreateNode`, both its node record and its + alpha atom slot, and the target edge record of each `EnsureEdge`, present or + absent, which decides whether the effect writes or only confirms; for an + `EnsureNode` whose content-typed target exists, that node's whole outgoing + bucket, charged per edge like a range read, which the identity law needs; + each `ReplaceAtom`'s node record; each `ReplaceEdge`'s edge record; the + source node record of every `EnsureEdge` and `ReplaceEdge`, whose type + selects the edge-id rule, and, when that source is mutable, its whole + outgoing bucket for the alias check; and the target node record of a creating + `EnsureEdge` or a `ReplaceEdge`, each unless the proposal creates that node. + Charges follow [Validation reads](#basis-budget-retries). Failures are `NodeMissing` for a `ReplaceAtom`'s missing node, `PreconditionMismatch` for a missing edge, or `BudgetExceeded`; whether a present target matches is decided at @@ -669,7 +694,7 @@ held; this design validates only what the proposal adds or retargets. bridge splits basis change in two: - **Whole-frontier `BasisChanged`,** decided before evaluation: the Action is not eligible for dependency-scoped validity (its footprint law admits - an edge-range read; see + a range read, an edge write, or a content-node write; see [Witnessed reads](#witnessed-reads-over-a-bounded-slice)) and its evaluation basis is no longer the commit parent. Posture `NoProposal`, empty read set, no evaluation. @@ -844,26 +869,28 @@ Consequences: `evaluator_profile_identity` (the evaluator ABI and intrinsic profile), `identity_law` (an unsigned integer naming an [identity law](#identity-laws); unknown values are refused), - `schema` (canonical bytes, below), `footprint_law` (its canonical - bytes), and `max_input_bytes`. Identities are 32-byte byte strings. The - `schema` is an + `graph_schema` (canonical bytes, below), `footprint_law` (its + canonical bytes), and `max_input_bytes`. The key `schema` keeps its V1 + role as the format discriminator, with the value + `echo.operation-program/v2`; the exact map is `program-v2-effect` in + [Set encoding](#set-encoding), and no other key may appear. + Identities are 32-byte byte strings. The `graph_schema` is an `edict.canonical-cbor/v1` map that Echo executes, not only a digest: with the [set encoding](#set-encoding) above; `node_types` maps each admitted node type id to its `addressing` (`content` or `mutable`, which the [identity law](#identity-laws) reads) and the set of atom `type_id`s it may carry, and `edge_types` maps - each admitted edge type - id to the set of admitted (source node type, target node type) pairs. - Validation step 5 reads these relations directly. + each admitted edge type id to the set of admitted (source node type, + target node type) pairs. Validation step 5 reads these relations directly. Admission refuses a package unless the program's entries agree with the rest of the package: `target_ir_identity` must equal the semantic - closure's Target IR identity, `evaluator_profile_identity` must equal - the identity of the package's interpreter and intrinsic profiles, and - the digest of `schema` under `echo.operation-schema/v1` must equal the + closure's Target IR identity, `evaluator_profile_identity` must equal the + identity of the package's interpreter and intrinsic profiles, and the + digest of `graph_schema` under `echo.operation-schema/v1` must equal the closure's schema digest. No artifact is attested under one identity and - executed under another. The evaluator dispatches on the - tag: `0` and `1` run today's profiles unchanged, and `2` runs the - compiled program through the bridge this document defines; + executed under another. The evaluator dispatches on the tag: `0` and `1` + run today's profiles unchanged, and `2` runs the compiled program through + the bridge this document defines; - the invocation is an `EchoOperationInvocationV2`, the V1 canonical CBOR map with its kind extended to a tagged union: tags `0` and `1` are the V1 compare-and-set and create-if-absent kinds with their V1 @@ -1533,6 +1560,10 @@ Each behaviour below needs a test before implementation: | Range inspection metering | A range read over a bucket of many edges of other types is charged for every edge in the bucket and obstructs when the bucket length exceeds `max_range_edges`, even when no edge matches. | | Existing edge source | An identical `EnsureEdge` whose source exists reads and charges the source record, and the edge id is recomputed under the rule its type selects. | | V2 domains | V2 package, program, invocation, and installed-operation identities use the `v2` schema strings and domains, and differ from V1 identities over the same content. | +| Topology eligibility | An Action whose package's law admits an edge write or a content-node write commits only onto its exact evaluation basis, including when this invocation writes only atoms. | +| Mutable-source alias | A creating `EnsureEdge` or a `ReplaceEdge` from a mutable source that already has another edge of the same type, under any id, obstructs with `IdentityLawViolation`. | +| Beta portal | An effect or read reached only through an edge-owned portal obstructs at step 1 with `FootprintLawViolation`. | +| Program map | A tag `2` program map with `schema` set to anything but `echo.operation-program/v2`, or with any key outside the exact map, is refused at admission. | ## Open questions From debe4d8f8159348ce8830129b95f9c551ce0a429 Mon Sep 17 00:00:00 2001 From: James Ross Date: Fri, 16 Oct 2026 21:22:50 -0700 Subject: [PATCH 36/50] docs: exact V2 package/closure/program/invocation maps; bridge patch constructor Round 27 on the authored-effects bridge, checked against the V1 encoders in echo_operation.rs: - V2 now matches V1's real shapes. V1 discriminates programs by a text `kind` and invocations by their `schema` string, and has separate lawpack_coordinate/lawpack_identity keys; there is no tag and no `lawpack` entry. Fixed-profile programs and invocations stay V1 byte for byte. V2 adds only: - closure-v2 (the V1 closure without the lawpack keys); - program-v2-effect, carrying interpreter_profile_identity and intrinsic_profile_identity as V1 programs do, with no combined evaluator identity; - invocation-v2-effect. All are given in exact CDDL. - The bridge patch constructor is specified exactly: WarpTickPatchV1::new with the scheduler policy id, the installed-operation identity as the rule pack, Committed, the slots, and per-effect ops, as the fixed-profile path builds its patch. - Step 7 authorizes precondition and resolution slots by the write permission of the effect that implies them, so an absent endpoint needs no expected type and is DanglingReference at step 8. - The EPRV0004 result blob follows the image blob rule (runs aligned, zero between, none after the last). The length counts the inner padding, and the record ends with the blob. Co-Authored-By: Claude Opus 5.5 --- .../authored-operation-effects.md | 219 ++++++++++-------- 1 file changed, 125 insertions(+), 94 deletions(-) diff --git a/docs/architecture/authored-operation-effects.md b/docs/architecture/authored-operation-effects.md index 2af69f04..7dc0899f 100644 --- a/docs/architecture/authored-operation-effects.md +++ b/docs/architecture/authored-operation-effects.md @@ -209,6 +209,27 @@ Rules: - **Effects are the only writes.** The proposal contains no prepared patch. Echo derives the patch from the effects. +- **Patch constructor.** The derived patch is built by + `WarpTickPatchV1::new`, as the fixed-profile path builds its patch, with + these arguments: + - `policy_id`: the scheduler's policy id for the Tick; + - `rule_pack_id`: the installed-operation identity; + - `commit_status`: `Committed`; + - `in_slots`: every node, edge, and attachment slot in the read set, the + precondition and resolution slots, and the portal chains. A range key + has no `SlotId` form, so it is not listed here; the write-set and + read-set digests cover it; + - `out_slots`: the write set's node, edge, and attachment slots; + - `ops`, in canonical effect order: `UpsertNode` then `SetAttachment` for + a creating `EnsureNode` or `CreateNode`, and nothing for an identical + `EnsureNode`; `SetAttachment` for `ReplaceAtom`; `UpsertEdge` for a + creating `EnsureEdge`, and nothing for an identical one; and + `UpsertEdge` with the same id and the new target for `ReplaceEdge`. + + The constructor sorts and deduplicates slots and operations itself. The + patch digest is `WarpTickPatchV1::digest` of that value, which hashes every + argument above. + - **Canonical order.** Echo sorts the effects the evaluator emits; the order in which evaluation produced them never reaches the identity. Each effect has a primary slot: the node record for `EnsureNode` and `CreateNode`, the atom for @@ -374,7 +395,7 @@ a digest alone could not be executed. One law is defined: All ids are 32 bytes. A content node's id therefore commits to its content and its children, as Jim's rope requires, and no later effect can make it stale. A program computes the same BLAKE3 inputs; Edict must expose them - before a tag `2` package can be produced. + before an effect package can be produced. ## Witnessed reads over a bounded slice @@ -490,12 +511,6 @@ graph-schema = { } node-type = { "addressing": 0 / 1, "atom_types": [* id] } ; 0 content, 1 mutable endpoint-pair = [source: id, target: id] -program-v2-effect = { - "schema": "echo.operation-program/v2", "tag": 2, - "compiled_program": bstr, "target_ir_identity": id, - "evaluator_profile_identity": id, "identity_law": uint, - "graph_schema": bstr, "footprint_law": bstr, "max_input_bytes": uint, -} id = bstr .size 32 ``` @@ -619,10 +634,15 @@ recorded obstruction: proposal's own node effects. An endpoint step 2 found absent has no type and is skipped here; 6. the identity law (`IdentityLawViolation`); -7. the footprint law, now that every slot is known: the witnessed read set, - every precondition slot (including a `ReplaceEdge`'s resolved source), - the target set, the resolved write set with its range keys, and their - counts (`FootprintLawViolation`); +7. the footprint law, now that every slot is known (`FootprintLawViolation`). + The witnessed read set must lie inside the law's read permissions, by the + type each read records. The target set and the resolved write set, with + its range keys, must lie inside its write permissions. The counts must not + exceed its maxima. Precondition and resolution slots (targets, endpoints, + sources, and buckets that step 2 read) are implied by an effect, not read + by the program. Each is authorized by the write permission of the effect + that implies it, never by `read_node_types`, so an absent endpoint needs + no expected type: it passes here and is `DanglingReference` at step 8; 8. the remaining effect preconditions, effect by effect in canonical order, and within an effect in the order its precondition list gives (`PreconditionMismatch`, `ContentIdentityConflict`, `DanglingReference`, @@ -849,81 +869,89 @@ Consequences: persistence. Proposals are never retained apart from their Tick. The record gains a new layout version for the new evidence (see Evidence). - **Package version.** `ExecutableOperationPackageV1` embeds - `EchoOperationSemanticClosureV1`, which requires a lawpack coordinate and - binds `lawpack_identity` into its canonical bytes and identities. A - source-compiled bridge package therefore uses new versions, - `ExecutableOperationPackageV2` and `EchoOperationSemanticClosureV2`. They - are identical to V1 except in three respects: - - the lawpack binding is optional: the semantic closure's map carries - its `lawpack` entry (the V1 coordinate and identity) or omits it. A - fixed-profile program (tags `0` and `1` below) requires the entry, as - ADR 0023 requires today; an `EdictEffectProgram` (tag `2`) requires its - absence. Admission refuses any other combination, so no sentinel - lawpack is fabricated and no fixed profile loses its lawpack; - - the program is an `EchoOperationProgramV2`, because - `EchoOperationProgramV1` has only the two fixed profiles. Like every - package value it is an `edict.canonical-cbor/v1` map with a `tag` - entry. Tags `0` and `1` are those profiles, with their V1 entries. Tag - `2` is `EdictEffectProgram`, whose entries are `compiled_program` (the - compiled bytes), `target_ir_identity`, - `evaluator_profile_identity` (the evaluator ABI and intrinsic - profile), `identity_law` (an unsigned integer naming an - [identity law](#identity-laws); unknown values are refused), - `graph_schema` (canonical bytes, below), `footprint_law` (its - canonical bytes), and `max_input_bytes`. The key `schema` keeps its V1 - role as the format discriminator, with the value - `echo.operation-program/v2`; the exact map is `program-v2-effect` in - [Set encoding](#set-encoding), and no other key may appear. - Identities are 32-byte byte strings. The `graph_schema` is an - `edict.canonical-cbor/v1` map that Echo executes, not only a digest: - with the [set encoding](#set-encoding) above; - `node_types` maps each admitted node type id to its `addressing` - (`content` or `mutable`, which the [identity law](#identity-laws) - reads) and the set of atom `type_id`s it may carry, and `edge_types` maps - each admitted edge type id to the set of admitted (source node type, - target node type) pairs. Validation step 5 reads these relations directly. - Admission refuses a package unless the program's entries agree with the - rest of the package: `target_ir_identity` must equal the semantic - closure's Target IR identity, `evaluator_profile_identity` must equal the - identity of the package's interpreter and intrinsic profiles, and the - digest of `graph_schema` under `echo.operation-schema/v1` must equal the - closure's schema digest. No artifact is attested under one identity and - executed under another. The evaluator dispatches on the tag: `0` and `1` - run today's profiles unchanged, and `2` runs the compiled program through - the bridge this document defines; - - the invocation is an `EchoOperationInvocationV2`, the V1 canonical - CBOR map with its kind extended to a tagged union: tags `0` and `1` - are the V1 compare-and-set and create-if-absent kinds with their V1 - fields, and tag `2` is `EdictInput`, which carries no anchored node or - replacement and instead requires `application_input_bytes`. Those are - the program's input in `edict.canonical-cbor/v1`. Admission checks - only what it can check without a schema language: that the bytes are - canonical `edict.canonical-cbor/v1` and no longer than the program's - `max_input_bytes` entry. The input's shape is the compiled program's to - check, as its first act; a malformed input obstructs evaluation with - the authored `InputRejected`, posture `NoProposal`. The package's input - schema identity remains evidence of the intended shape, not an - admission check. Admission - dispatches on the program tag: a tag `2` program admits only tag `2` - invocations and the fixed profiles only their own kinds, so no - invocation crosses between them. The V2 invocation identity has its own - domain. - - V2 changes only the version suffix of each Echo-owned schema string and - hash domain. The canonical maps carry the schema strings - `echo.operation-package/v2`, `echo.operation-program/v2`, and - `echo.operation-invocation/v2`. Identities hash the domains - `echo:operation-package:v2\0`, `echo:operation-program:v2\0`, - `echo:operation-invocation:v2\0`, `echo:operation-invocation-bytes:v2\0`, - `echo:operation-installation:v2\0`, - `echo:operation-package-admission:v2\0`, and - `echo:operation-invocation-admission:v2\0`, in place of the V1 domains - of the same names. Every other domain is unchanged. The semantic closure - is the producer's artifact and keeps the identity its producer defines; - a V2 package binds it exactly as a V1 package does. So V2 program, - package, invocation, and installed-operation identities never coincide - with V1 identities, and V1 encoding, validation, and identities are - unchanged. + `EchoOperationSemanticClosureV1`, which requires `lawpack_coordinate` and + `lawpack_identity`, and `EchoOperationProgramV1`, which has only the two + fixed profiles. A source-compiled bridge package therefore uses + `ExecutableOperationPackageV2`. Fixed-profile programs and invocations are + not re-encoded: they keep their V1 maps, schema strings, and identities + byte for byte. V2 adds exactly this: + - **Package.** The V1 package map with `schema` set to + `echo.operation-package/v2`, `semantic_closure` set to a + `closure-v2`, and `program` set to an effect program. Every other key, + and its value shape, is as in V1. + - **Semantic closure.** The V1 closure map, with `lawpack_coordinate` and + `lawpack_identity` both omitted. A V2 package carries only an effect + program, which requires their absence, so no sentinel lawpack is + fabricated. A fixed-profile program stays in a V1 package with its + lawpack, as ADR 0023 requires today. The closure has no identity of its + own; its bytes are part of the package's. + - **Effect program.** A new map, `program-v2-effect` below. Like a V1 + program, it carries `interpreter_profile_identity` and + `intrinsic_profile_identity`, and admission requires each to equal the + package's value of the same name. Its other entries are: + - `compiled_program`, the compiled bytes; + - `target_ir_identity`, which must equal the closure's; + - `identity_law`, an unsigned integer naming an + [identity law](#identity-laws); unknown values are refused; + - `graph_schema`, the canonical bytes of a `graph-schema`; + - `footprint_law`, the canonical bytes of a `footprint-law`; + - `max_input_bytes`. + + The digest of `graph_schema` under `echo.operation-schema/v1` must + equal the closure's `application_schema_identity`. The `graph_schema` + is a map that Echo executes, not only a digest. `node_types` maps each + admitted node type to its addressing, which the + [identity law](#identity-laws) reads, and its atom types. `edge_types` + maps each admitted edge type to its (source node type, target node + type) pairs. Validation step 5 reads these relations directly. No + artifact is attested under one identity and executed under another. + + - **Effect invocation.** A new map, `invocation-v2-effect` below. It + carries the V1 invocation's common entries other than the anchored + node and replacement (`authority_grant_identity`, `delegated_budget`, + `evaluation_basis`, `operation_coordinate`, `package_id`), plus the + required `application_input_bytes`, the program's input in + `edict.canonical-cbor/v1`. Admission checks only what it can check + without a schema language: that the bytes are canonical and no longer + than the program's `max_input_bytes`. The input's shape is the compiled + program's to check, as its first act; a malformed input obstructs + evaluation with the authored `InputRejected`, posture `NoProposal`. An + effect program admits only effect invocations, and a fixed profile only + its own V1 invocations, so no invocation crosses between them. + + ```text + closure-v2 = { + "application_schema_coordinate": tstr, "application_schema_identity": id, + "canonical_meaning_identity": id, "core_identity": id, + "edict_source_identity": id, "target_ir_identity": id, + } + program-v2-effect = { + "schema": "echo.operation-program/v2", "kind": "edict-effect-program/v1", + "interpreter_profile_identity": id, "intrinsic_profile_identity": id, + "compiled_program": bstr, "target_ir_identity": id, + "identity_law": uint, "graph_schema": bstr, "footprint_law": bstr, + "max_input_bytes": uint, + } + invocation-v2-effect = { + "schema": "echo.operation-invocation/v2", + "authority_grant_identity": id, "delegated_budget": budget-v1, + "evaluation_basis": evaluation-basis-v1, "operation_coordinate": tstr, + "package_id": id, "application_input_bytes": bstr, + } + ; budget-v1 and evaluation-basis-v1 are the V1 values, unchanged. + ``` + + No other key may appear in any of these maps. A V2 package, effect program, + and effect invocation hash their identities under + `echo:operation-package:v2\0`, `echo:operation-program:v2\0`, and + `echo:operation-invocation:v2\0`. Their invocation-bytes digest, package + admission, invocation admission, and installation identities use + `echo:operation-invocation-bytes:v2\0`, + `echo:operation-package-admission:v2\0`, + `echo:operation-invocation-admission:v2\0`, and + `echo:operation-installation:v2\0`. Every other domain is unchanged. So V2 + identities never coincide with V1 identities, and V1 encoding, validation, + and identities are unchanged. ## Evidence @@ -1263,9 +1291,12 @@ follow the canonical effect order in [EffectProposal](#effectproposal). the `EPRV0003` payload that follows that record's own magic, byte for byte, then zero padding to an 8-byte boundary, then a bridge table of one `BridgeEntryRow` per receipt entry, in entry order, then the result blob: - its length as a little-endian `u64`, then each committed bridge Action's - exact result bytes in entry order, then zero padding to an 8-byte - boundary. Each row's `result` range addresses the blob, which carries the + its length as a little-endian `u64`, then the blob. The blob follows the + image rule above: each committed bridge Action's exact result bytes, in + entry order, each run starting 8-byte aligned from the blob's start, with + zero bytes between runs and none after the last. The length counts the + padding between runs. The record ends with the blob. Each row's `result` + range addresses the blob, which carries the result bytes ADR 0025 requires in the Receipt. Only the magic differs from `EPRV0003` before the table, so the unchanged `EPRV0003` decoder never sees an `EPRV0004` record. The table is indexable in place. @@ -1536,10 +1567,10 @@ Each behaviour below needs a test before implementation: | Scoped basis change | A dependency-scoped Action evaluated at its origin obstructs with `BasisChanged`, posture `Proposed` with `NotDerived`, and keeps its proposal, read set, and changed dependency when a dependency changed, even if its proposal would also fail validation; an unchanged dependency set lets it commit. | | Scoped evaluation obstruction | A dependency-scoped Action whose evaluation exhausts its budget, or fails an authored guard, keeps that obstruction with `NoProposal` even when one of its reads changed after the origin; recovery reproduces it. | | Receipt format | `EPRV0003` records recover byte for byte through the unchanged decoder; a Tick containing a bridge Action is written as `EPRV0004`, with its fixed-profile Actions as all-zero `NotBridge` rows; a Tick of fixed-profile Actions alone stays `EPRV0003` and receipt digest version 4; a history mixing both formats recovers. | -| Package version | A source-compiled package encodes as `ExecutableOperationPackageV2` with no `lawpack` entry and an `EdictEffectProgram` (program tag `2`) that the evaluator dispatches to the bridge; a V2 package carrying a fixed profile runs it unchanged; V1 packages keep their bytes and identities, and no V2 identity equals a V1 identity. | -| Lawpack by program | A V2 package whose fixed-profile program omits the lawpack, or whose `EdictEffectProgram` carries one, is refused at admission. | -| Invocation version | An `EdictInput` (tag `2`) invocation is admitted only for an `EdictEffectProgram` package and is refused for a fixed profile, and a fixed-profile kind is refused for a tag `2` program. | -| Input bounds | An `EdictInput` that is not canonical CBOR, or longer than `max_input_bytes`, is refused at admission; a well-formed input of the wrong shape obstructs evaluation with `InputRejected` and `NoProposal`. | +| Package version | A source-compiled package encodes as `ExecutableOperationPackageV2` with a `closure-v2` (no lawpack keys) and a `program-v2-effect`, and its identities use the `v2` domains; a fixed-profile package keeps its V1 bytes and identities exactly. | +| Lawpack by program | A V2 package whose closure carries `lawpack_coordinate` or `lawpack_identity`, or whose program is a fixed profile, is refused at admission. | +| Invocation version | An effect invocation (`invocation-v2-effect`) is admitted only for an effect-program package, and a V1 invocation only for its own fixed profile. | +| Input bounds | An effect invocation whose `application_input_bytes` is not canonical CBOR, or is longer than `max_input_bytes`, is refused at admission; a well-formed one of the wrong shape obstructs evaluation with `InputRejected`. | | Outcome record | A bridge obstruction, including an authored one such as `BasisNotCanonical`, is retained as `EOACT004` with the same coordinate as its receipt; fixed-profile Actions still write `EOACT003`, and mixed histories recover. | | ----------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | Pending recovery | An Action lost with its Tick before the WAL record is recovered as the same submission, not a new one. | @@ -1563,7 +1594,7 @@ Each behaviour below needs a test before implementation: | Topology eligibility | An Action whose package's law admits an edge write or a content-node write commits only onto its exact evaluation basis, including when this invocation writes only atoms. | | Mutable-source alias | A creating `EnsureEdge` or a `ReplaceEdge` from a mutable source that already has another edge of the same type, under any id, obstructs with `IdentityLawViolation`. | | Beta portal | An effect or read reached only through an edge-owned portal obstructs at step 1 with `FootprintLawViolation`. | -| Program map | A tag `2` program map with `schema` set to anything but `echo.operation-program/v2`, or with any key outside the exact map, is refused at admission. | +| Program map | An effect program map whose `schema` or `kind` differs from `program-v2-effect`, that has any other key, or whose profile identities differ from the package's, is refused at admission. | ## Open questions From 21a4147ba661fa4702abe58487a3dc9bd7b1e8d8 Mon Sep 17 00:00:00 2001 From: James Ross Date: Fri, 16 Oct 2026 21:35:57 -0700 Subject: [PATCH 37/50] docs: candidate vs composite patches; EOACT004 decision coordinate; identities Round 28 on the authored-effects bridge, checked against echo_operation.rs: - A row's patch_digest is the Action's own candidate patch. The Tick's replay patch is the batch composition, the union of accepted candidates under the batch rule-pack id, as for fixed-profile Actions. Recovery verifies both. - Every EOACT004 variant carries the 152-byte decision coordinate exactly as EOACT003 retains it. Conflict blockers use EOACT003's encoding (u64 count, u32 indexes). - authored_id has an exact recipe: the program identity, a code, and canonical detail bytes. - program-v2-effect carries `output_type`, which result_identity hashes. - The graph schema is bound by the program identity, separate from the closure's application_schema_identity, which keeps naming the application's declaration (ADR 0023). - The inherited footprint_contract_identity must be the v2 digest of the footprint_law bytes. - Step 6 skips an edge whose source is absent and not created here; it is DanglingReference at step 8. - max_reads and max_writes count distinct slots after deduplication and Ensure resolution. - Eight tests are added. Co-Authored-By: Claude Opus 5.5 --- .../authored-operation-effects.md | 311 ++++++++++-------- 1 file changed, 178 insertions(+), 133 deletions(-) diff --git a/docs/architecture/authored-operation-effects.md b/docs/architecture/authored-operation-effects.md index 7dc0899f..c630d64d 100644 --- a/docs/architecture/authored-operation-effects.md +++ b/docs/architecture/authored-operation-effects.md @@ -518,21 +518,25 @@ No other key may appear, and every key shown is required, so each law and schema has exactly one encoding and one identity. The package declares a footprint law, fixed per package with no invocation -parameters. It is an `edict.canonical-cbor/v1` map, in the exact shape that -[Set encoding](#set-encoding) gives, with these entries: -`read_node_types` and `write_node_types` (sets of node type ids), -`atom_types` (the atom `type_id`s it may read or write), `range_edge_types` -(edge types it may read as outgoing ranges; empty makes it eligible for -scoped validity), `write_edge_types`, and the unsigned counts `max_reads`, -`max_writes`, and `max_range_edges` (per range read), and `portal_owner_types` -(node types whose alpha portals an effect or read in a descended warp may -cross; empty confines the package to its root warp). Each portal on a -validated chain must be owned by a node of one of these types, or validation -obstructs with `FootprintLawViolation`. The bridge does not traverse -edge-owned (beta) portals: an edge has no node type for this law to admit, so -a chain crossing one is `FootprintLawViolation` at step 1, before any owner -record is read. Its canonical bytes are part of the program's -identity. At validation Echo checks: +parameters. It is an `edict.canonical-cbor/v1` map, in the exact shape that [Set +encoding](#set-encoding) gives, with these entries: `read_node_types` and +`write_node_types` (sets of node type ids), `atom_types` (the atom `type_id`s it +may read or write), `range_edge_types` (edge types it may read as outgoing +ranges; empty makes it eligible for scoped validity), `write_edge_types`, the +unsigned counts `max_reads`, `max_writes`, and `max_range_edges` (per range +read), and `portal_owner_types` (node types whose alpha portals an effect or +read in a descended warp may cross; empty confines the package to its root +warp). Each portal on a validated chain must be owned by a node of one of these +types, or validation obstructs with `FootprintLawViolation`. The bridge does not +traverse edge-owned (beta) portals: an edge has no node type for this law to +admit, so a chain crossing one is `FootprintLawViolation` at step 1, before any +owner record is read. The counts are over distinct slots, after deduplication: +`max_reads` bounds the witnessed read set, where one range read is one slot (its +range key) and its edges are bounded by `max_range_edges`; `max_writes` bounds +the resolved write set after Ensure resolution, range keys included, so an +identical Ensure adds nothing. Validation reads are bounded by the budget, not +by `max_reads`. The law's canonical bytes are part of the program's identity. At +validation Echo checks: - every node type, atom `type_id`, and edge type an effect writes is admitted by the package's typed graph and attachment schema, which ADR 0023 requires @@ -633,7 +637,10 @@ recorded obstruction: target node types, taken from the resolved records or from the proposal's own node effects. An endpoint step 2 found absent has no type and is skipped here; -6. the identity law (`IdentityLawViolation`); +6. the identity law (`IdentityLawViolation`). An edge whose source step 2 + found absent, and the proposal does not create, has no source type to + select its id rule, so this step skips it; it is `DanglingReference` at + step 8, as an absent endpoint is at step 5; 7. the footprint law, now that every slot is known (`FootprintLawViolation`). The witnessed read set must lie inside the law's read permissions, by the type each read records. The target set and the resolved write set, with @@ -895,11 +902,21 @@ Consequences: [identity law](#identity-laws); unknown values are refused; - `graph_schema`, the canonical bytes of a `graph-schema`; - `footprint_law`, the canonical bytes of a `footprint-law`; - - `max_input_bytes`. - - The digest of `graph_schema` under `echo.operation-schema/v1` must - equal the closure's `application_schema_identity`. The `graph_schema` - is a map that Echo executes, not only a digest. `node_types` maps each + - `max_input_bytes`; + - `output_type`, the compiler-declared output type coordinate of the + program's application result, which `result_identity` hashes. + + The graph schema is the executable graph contract, a different + artifact from the application's own schema declaration. The closure's + `application_schema_identity` keeps naming that declaration, with its + codecs and migration posture, as ADR 0023 requires. The graph schema + is bound by the program identity, whose bytes contain it, so no + equality between the two is required. The package's inherited + `footprint_contract_identity` must equal BLAKE3 over + `echo:operation-footprint-law:v2\0` and the `footprint_law` bytes, so + the attested footprint contract is exactly the law validation + executes. The `graph_schema` is a map that Echo executes, not only a + digest. `node_types` maps each admitted node type to its addressing, which the [identity law](#identity-laws) reads, and its atom types. `edge_types` maps each admitted edge type to its (source node type, target node @@ -930,7 +947,7 @@ Consequences: "interpreter_profile_identity": id, "intrinsic_profile_identity": id, "compiled_program": bstr, "target_ir_identity": id, "identity_law": uint, "graph_schema": bstr, "footprint_law": bstr, - "max_input_bytes": uint, + "max_input_bytes": uint, "output_type": tstr, } invocation-v2-effect = { "schema": "echo.operation-invocation/v2", @@ -1177,7 +1194,7 @@ above likewise applies to every row recorded as `Scoped`. | Outcome | Posture | Existing check | Added | | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | ------------------------ | ------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| Committed | `Proposed`, `Derived` | replay patch; reconstruct preparation; compare identities | effects bound; read-set digest; patch digest equals the replayed patch | +| Committed | `Proposed`, `Derived` | replay patch; reconstruct preparation; compare identities | effects bound; read-set digest; the row's `patch_digest` equals this Action's re-derived candidate patch; the Tick's replay patch equals the batch composition of every accepted candidate | | `RejectedFootprintConflict` | `Proposed`, `Derived` | reconstruct preparation; compare identities; recompute recorded blockers | effects bound; read-set digest; blockers include range-key conflicts | | Obstructed at validation (`DanglingReference`, `ContentIdentityConflict`, `DuplicateEffectTarget`, `FootprintLawViolation`, `IdentityLawViolation`, `SchemaViolation`, `UnreachableWarp`, `NodeMissing`, `PreconditionMismatch`, validation-read `BudgetExceeded`) | `Proposed`, `NotDerived` | re-evaluate; compare pre-decision evidence | effects bound; read-set digest; obstruction kind; no write-set or patch digest present | | Obstructed by `FootprintMismatch` (after derivation) | `Proposed`, `Derived` | re-evaluate; compare pre-decision evidence | effects bound; read-set digest; re-derived patch reproduces the mismatch | @@ -1221,7 +1238,7 @@ rules: | `EdgePairRow` | 64 | `edge_local_id`; `target_local_id` | | `EffectRow` | 280 | `kind` u8 (`EnsureNode` `0`, `CreateNode` `1`, `ReplaceAtom` `2`, `EnsureEdge` `3`, `ReplaceEdge` `4`); 7 padding bytes; `key_warp_id`; `key_local_id`; `type_id` (node type, or `EnsureEdge`'s edge type); `atom_type_id`; `atom` (`Range` into the blob); `expected_digest` (`ReplaceAtom`); `from_local_id` (`EnsureEdge`); `to_local_id` (`EnsureEdge`, and `ReplaceEdge`'s new target); `expected_to_local_id` (`ReplaceEdge`) | | `DependencyRow` | 152 | `slot` (`SlotRow`); `kind` u8; `mode` u8; 6 padding bytes, which together with `slot` form the 112-byte descriptor; `compared` u8 (present `0`, absent `1`, not compared `2`); 7 padding bytes; `compared_digest` (present only) | -| `OutcomeRow` | 40 | `outcome` u8 (none `0`, bridge kind `1`, authored `2`); `code` u8 (the bridge kind code); 6 padding bytes; `authored_id` (authored only) | +| `OutcomeRow` | 40 | `outcome` u8 (none `0`, bridge kind `1`, authored `2`); `code` u8 (the bridge kind code); 6 padding bytes; `authored_id` (authored only; see below) | | `BridgeEntryRow` | 496 | `posture`, `patch`, `validity`, and `evidence` tags, u8 each; `outcome` and `code` as in `OutcomeRow`; 2 padding bytes; `proposal_id`; `target_set`; `write_set`; `patch_digest`; `read_set`; `origin_basis`; `dependency_set`; `compared_values`; `changed_dependency` (a 112-byte descriptor); `authored_id`; `validation_position` u64; `steps`, `read_bytes`, `write_bytes`, and `allocated_bytes`, u64 each; `result_identity`; `result` (`Range` into the result blob) | Tags not defined above use the values given in [Evidence](#evidence): @@ -1250,8 +1267,17 @@ exactly these `BridgeEntryRow` fields; every other field is zero: | evidence `Compared` | `dependency_set`, `compared_values` | | outcome committed | `result_identity`, `result` | +`authored_id` identifies an authored obstruction. The program reports one as +a code, a UTF-8 string of at most 1,024 bytes such as `InputRejected` or +`BasisNotCanonical`, and detail bytes in `edict.canonical-cbor/v1`, bounded +by the program's `max_allocated_bytes`. `authored_id` is BLAKE3 over +`echo.bridge.authored-obstruction.v1\0`, the program identity, the code's +length as a little-endian `u64` and its bytes, and the detail's length and +bytes. Recovery re-evaluates the Action at its origin and requires the same +identity. + `result_identity` is BLAKE3 over `echo.bridge.result.v1\0`, the program -identity, the length of the program's declared output type coordinate as a +identity, the length of the program's `output_type` as a little-endian `u64` and its UTF-8 bytes, and the length of the result bytes as a little-endian `u64` and the bytes: the propositions ADR 0025 binds for an applied Action. An obstruction or conflict carries no result, and both fields @@ -1306,19 +1332,30 @@ follow the canonical effect order in [EffectProposal](#effectproposal). exactly as `EPRV0004` retains them. - **`EOACT004`**: a bridge Action's retained outcome. It is a sequential record like `EOACT003`, not one of the zero-copy images above, because it - is read once at recovery: the eight ASCII bytes `EOACT004`, the submission - id and ingress id (32 bytes each), a one-byte variant tag (committed `1`, - obstructed `2`, conflict `3`), the 40-byte `OutcomeRow`, and the Action's - 496-byte `BridgeEntryRow`, then, for a committed Action, the exact result - bytes its row's `result` range names, as a little-endian `u64` length and - the bytes. A conflict then adds its blocker count as a - little-endian `u32` and each blocker's candidate index as a little-endian - `u32`. A bridge Action has no `EchoOperationReceiptV1` and no prepared - candidate: its evidence is the `BridgeEntryRow` that the receipt digest - already binds. Committed and conflict variants carry the all-zero - `OutcomeRow`. Recovery requires the `OutcomeRow` and the `BridgeEntryRow` - to equal the receipt's, byte for byte. Fixed-profile Actions keep - `EOACT003`. + is read once at recovery: + - the eight ASCII bytes `EOACT004`, the submission id and ingress id (32 + bytes each), and a one-byte variant tag (committed `1`, obstructed `2`, + conflict `3`); + - the 40-byte `OutcomeRow` and the Action's 496-byte `BridgeEntryRow`; + - the 152-byte decision coordinate, exactly as `EOACT003` retains it + (`retain_action_decision_coordinate_v1`): writer-head worldline id and + head id, `worldline_tick_after` and `commit_global_tick` as + little-endian `u64`s, commit id, Tick receipt digest, and member index + as a little-endian `u64`. Every variant carries it, because a bridge + Action is decided by a sealed Tick whether it commits, obstructs, or + conflicts; + - for a committed Action, its exact result bytes, as a little-endian + `u64` length and the bytes; + - for a conflict, the blocker count as a little-endian `u64` and each + blocker's candidate index as a little-endian `u32`, as `EOACT003` does. + + A bridge Action has no `EchoOperationReceiptV1` and no prepared candidate: + its evidence is the `BridgeEntryRow`, which the receipt digest already + binds. Committed and conflict variants carry the all-zero `OutcomeRow`. + Recovery requires the `OutcomeRow` and `BridgeEntryRow` to equal the + receipt's byte for byte, and the decision coordinate to name the sealed + Tick that holds that receipt entry at that member index. Fixed-profile + Actions keep `EOACT003`. **Memory.** The bridge allocates nothing on its hot path: @@ -1500,101 +1537,109 @@ propose `ReplaceEdge B -canonicalHead-> expected H1`. Each behaviour below needs a test before implementation: -| Property | Test | -| ----------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | -| Atomicity | A failure on the last effect of a multi-node proposal leaves the parent state root and patch unchanged. | -| Ensure identity | Identical existing node: no patch entry. Different existing node: `ContentIdentityConflict`. | -| Partial occupancy | A node record without its atom, or an atom without its record: `CreateNode` obstructs with `PreconditionMismatch` and `EnsureNode` with `ContentIdentityConflict`; neither overwrites the orphan slot. | -| Witnessed footprint | A program that reads a slot outside its law obstructs, even if its writes are in bounds. | -| Stale basis | A `ReplaceAtom` or `ReplaceEdge` whose expected value already disagrees with the value at its evaluation basis obstructs with `PreconditionMismatch`, posture `Proposed`, `NotDerived`, and no patch; when the value instead changed after the origin, a whole-frontier Action obstructs with `BasisChanged` before evaluation and a scoped one at the history check. | -| Budget | Exhausting steps or bytes obstructs with `BudgetExceeded` and `NoProposal`; a proposal whose patch alone exceeds the Action-batch operation limit obstructs with `BudgetExceeded`, posture `Proposed` with its digests, and no committed patch. | -| Effect charges | Each effect is charged one step and its fixed `write_bytes` (64 plus atom bytes for node and atom effects, 128 for edge effects) at emission; a budget one byte short of a proposal's total obstructs with `BudgetExceeded` and `NoProposal`. | -| Atom write bytes | A proposal whose atom bytes exceed the delegated `write_bytes` obstructs with `BudgetExceeded` and `NoProposal`; a whole-frontier `BasisChanged` records zero consumed budget. | -| Validation read budget | A `ReplaceAtom` of a stored atom larger than the remaining `read_bytes` obstructs with `BudgetExceeded`, posture `Proposed`, `NotDerived`, before hashing it; allocation beyond `edict_pure`'s `max_allocated_bytes` obstructs with `BudgetExceeded` and `NoProposal`. | -| Budget-stopped evidence | A scoped proposal whose validation read exhausts the budget records `Compared` with that dependency, and every later one, as not compared; no value past the stop is hashed. | -| Determinism | Same package, invocation, and evaluation basis give byte-identical proposals, proposal identities, read-set and write-set digests, and derived patches, whether the Action is evaluated alone or beside other Actions. Receipts and composite patches are compared only for identical Tick contexts. | -| Canonical order | Emitting the same effects in different orders yields the same canonical `EffectProposal` bytes and proposal identity. | -| Blob-independent order | Two same-kind duplicate effects on one slot with different atoms produce the same canonical image and proposal identity whichever order they were emitted in. | -| Encoding vectors | Fixed byte vectors for every row, the proposal image, every set digest, a bridge table with each posture, validity, evidence, and outcome tag, and `EOACT004` reproduce the specified bytes, sizes, and receipt-v5 digests. | -| In-place validation | Each image validates and reads through a view without heap allocation; an image with nonzero padding, a nonzero unused field, an unsorted section, an out-of-bounds range, or loosely packed blob bytes is refused. | -| No hot-path allocation | With an allocation-counting allocator, a warmed-up Tick of bridge Actions performs zero heap allocations during evaluation, validation, derivation, and composition, and recovery of that Tick allocates nothing per Action. | -| Arena independence | The same Ticks decided on hosts with different initial arena capacities produce byte-identical receipts, WAL records, and state roots. | -| Range-read entries | An empty observed range encodes as a range entry with count zero, never as an absent outcome, and a nonempty range's entry matches the specified layout byte for byte. | -| Canonical slots | Differently expressed effects on one slot (`EnsureEdge` and `ReplaceEdge` of one edge id; `CreateNode` and `ReplaceAtom` of one node) obstruct with `DuplicateEffectTarget`. | -| Write set | The derived patch's actual footprint equals the write set, including the atom slots of created nodes and the range key of every created or retargeted edge, so a committed `EnsureEdge` and `ReplaceEdge` pass the footprint check. | -| Identity law | A proposed node or edge id that does not match the package's identity law obstructs with `IdentityLawViolation`, posture `Proposed`, `NotDerived`, and recovery reproduces it. | -| Node resolution | A `ReplaceAtom` of a node absent at the evaluation basis obstructs at step 2 with `NodeMissing`, posture `Proposed`, `NotDerived`. | -| Content address | Under `ContentAddressV1`, a node id equal to the specified BLAKE3 over its type, atom, and the edges its creating proposal's `EnsureEdge` effects give it, and an edge id over its endpoints and type, pass; any other id obstructs with `IdentityLawViolation`; fixed vectors pin both derivations. | -| Addressing by type | A `ReplaceAtom` of a node whose type the schema marks content-addressed, or an `EnsureNode` of a mutable type, obstructs with `IdentityLawViolation`; a `ReplaceAtom` of a mutable-type node commits. | -| Edge addressing | An edge from a mutable node keeps its slot id when a `ReplaceEdge` retargets it; a creating `EnsureEdge` from an existing content node, a `CreateNode` of a content type, and a `ReplaceEdge` of an edge from a content node each obstruct with `IdentityLawViolation`. | -| Unreachable warp | An effect naming a key in a missing warp, or in a warp no valid portal chain reaches, obstructs with `UnreachableWarp`, posture `Proposed`, `NotDerived`. | -| Origin reachability | A scoped Action naming a warp unreachable at its origin obstructs with `UnreachableWarp` even when an intervening Tick created the portal that reaches it. | -| Portal permission and cost | At validation step 1, an effect in a descended warp whose chain crosses a portal owned by a type outside `portal_owner_types` obstructs with `FootprintLawViolation`, and a chain whose crossings exceed the remaining budget obstructs with `BudgetExceeded`. | -| Schema | An effect whose node type, atom `type_id`, or edge type the package's schema does not admit obstructs with `SchemaViolation`, posture `Proposed`, `NotDerived`, and recovery reproduces it. | -| Resolved schema | A `ReplaceEdge` whose basis edge type the schema does not admit, or a `ReplaceAtom` whose owner node type does not admit the new atom type, obstructs with `SchemaViolation` after its record is read and charged. | -| Endpoint types | A creating `EnsureEdge` or a `ReplaceEdge` whose source or target node type the schema does not admit for that edge type obstructs with `SchemaViolation`, after its endpoint records are read and charged. | -| Validation order | A proposal that breaks several rules (for example a duplicate target, an unadmitted type, and a missing endpoint) records the obstruction of the earliest step in the validation order, live and on recovery. | -| Conflict before batch limit | A candidate that both overlaps an earlier accepted Action and would exceed the batch operation limit records `RejectedFootprintConflict`, as today's scheduler does. | -| Footprint stages | A target outside the footprint law obstructs with `FootprintLawViolation` and `NotDerived`, and so does a `ReplaceEdge` whose basis edge's range key lies outside the law; a derived patch whose footprint differs from the write set obstructs with `FootprintMismatch` and `Derived`. | -| Resolved footprint | A `ReplaceEdge` whose resolved source node lies outside the footprint law obstructs with `FootprintLawViolation`. | -| Restored value (ABA) | A dependency written A to B and back to A between origin and commit parent is found through the written-slot set and obstructs with `BasisChanged`, including when the intervening patch under-declared its `out_slots`; with one replay patch in the interval missing, the Action obstructs before evaluation as a whole-frontier `BasisChanged`, posture `NoProposal`, with an empty read set. | -| Scoped preconditions | A dependency-scoped Action whose `ReplaceAtom` expected value, or one of whose edge endpoints, changed and was restored between its origin and the commit parent obstructs with `BasisChanged`, though the program never read that slot; recovery reproduces the outcome and its changed dependency. | -| Origin resolution | A scoped `ReplaceEdge` whose program never read the edge, and whose source node is deleted and recreated between origin and commit parent while the edge is unchanged, obstructs with `BasisChanged`, because resolution at the origin put the source in its dependency set. | -| Scoped conflict recovery | A scoped Action rejected because an earlier Action in its Tick replaced a value it expects recovers as `RejectedFootprintConflict`, not `PreconditionMismatch`. | -| Scoped evidence forms | An evaluation obstruction records `OriginOnly`, a dependency-scoped `BasisChanged` records `HistoryChanged` with no compared values, and every other scoped outcome records `Compared` over commit-parent values; none is compared at a validation position that includes an earlier overlapping write. | -| OriginOnly recovery | A scoped proposal that fails reachability or resolution is recovered by replaying only that failure, with no dependency set rebuilt. | -| Resolution failure evidence | A scoped `ReplaceEdge` whose edge is missing at its origin records `OriginOnly` with `PreconditionMismatch`; recovery reproduces it without running the history check. | -| Retry basis | A new submission selected after the head advanced is evaluated at its recorded basis, not at the selection-time state. | -| Receipt digest v5 | A version-5 receipt digest begins with the little-endian `u16` `5` and changes when any bridge field changes, including the validation position, a dependency's comparison mode, or a compared value; version-4 receipt identities are unchanged. | -| Proposal binding | For every `Proposed` outcome class in the recovery matrix, including rejected ones, altering one recorded effect-derived digest or the proposal identity refuses the history. | -| No-proposal evidence | An evaluation that obstructs before proposing records `NoProposal` with its read-set digest and no proposal, write-set, or patch digest. | -| Recovery | Crash before and after the decided-Tick WAL record; fresh-host recovery reproduces the same state root and receipts. | -| Composition | Two Actions with independent witnessed footprints (neither writes a slot the other reads or writes) commit in one Tick. A write/write overlap obstructs the later one, and so does a write/read overlap: A reads X and writes Y, B writes X and Z. | -| Shared structure | Two Actions that `Ensure` the same node already present in the basis both commit in one Tick. | -| Same-Tick identical create | Two Actions, dependency-scoped included, that both `Ensure` the same node absent at the commit parent: the later obstructs with `RejectedFootprintConflict` instead of reading the earlier one's write. | -| Referential integrity | An edge to a node neither in the basis nor created by the proposal obstructs with `DanglingReference`; a pre-existing dangling edge elsewhere in the basis does not obstruct an unrelated proposal. | -| Portal chain | An effect in a descended warp records every portal on its root-to-target chain as a read; a co-scheduled portal retarget conflicts with it, and a portal changed and restored between origin and commit parent obstructs a scoped Action with `BasisChanged`. | -| Visibility | No published coordinate shows a retargeted head without every node and edge the proposal introduced; pre-existing structure the head reaches is not validated. | -| Witnessed reads | The receipt's read set equals the slots the view resolved, including for a program that reads and then obstructs. | -| Negative reads | An Action that observed a node, atom, or edge set absent obstructs when a co-scheduled Action creates it, including a fresh edge of the observed source and type with a new edge id when the observed set was empty (outgoing-edge range key). | -| Range metering | A range read over a node with more edges than the law's maximum, or than the remaining budget covers, obstructs with `BudgetExceeded` and `NoProposal` before copying any edge; an admissible range charges one step and 64 read bytes per edge. | -| Absent probes | A read that finds nothing records the node type it expected; a probe for a type outside `read_node_types` obstructs at step 7 with `FootprintLawViolation`, and recovery reproduces the decision from the read set alone. | -| Edge-read basis | An Action whose package's footprint law admits a range read commits only onto its exact evaluation basis, including when this invocation reads no edge. | -| Final outcomes | A rejected Action's outcome, receipt, and attribution are unchanged after a later submission of the same edit succeeds. | -| Duplicate resubmission | Resubmitting a decided Action's identical content returns `Duplicate` with the original outcome and commits no Tick. | -| Attribution | Every Action in a bridge Tick appears once in the attribution section, mapped to its own receipt entry. | -| One effect per slot | Two effects on one slot (two `ReplaceAtom`, or `EnsureNode` plus `ReplaceAtom` of the same node) obstruct with `DuplicateEffectTarget`, posture `Proposed`, `NotDerived`. | -| Scoped basis change | A dependency-scoped Action evaluated at its origin obstructs with `BasisChanged`, posture `Proposed` with `NotDerived`, and keeps its proposal, read set, and changed dependency when a dependency changed, even if its proposal would also fail validation; an unchanged dependency set lets it commit. | -| Scoped evaluation obstruction | A dependency-scoped Action whose evaluation exhausts its budget, or fails an authored guard, keeps that obstruction with `NoProposal` even when one of its reads changed after the origin; recovery reproduces it. | -| Receipt format | `EPRV0003` records recover byte for byte through the unchanged decoder; a Tick containing a bridge Action is written as `EPRV0004`, with its fixed-profile Actions as all-zero `NotBridge` rows; a Tick of fixed-profile Actions alone stays `EPRV0003` and receipt digest version 4; a history mixing both formats recovers. | -| Package version | A source-compiled package encodes as `ExecutableOperationPackageV2` with a `closure-v2` (no lawpack keys) and a `program-v2-effect`, and its identities use the `v2` domains; a fixed-profile package keeps its V1 bytes and identities exactly. | -| Lawpack by program | A V2 package whose closure carries `lawpack_coordinate` or `lawpack_identity`, or whose program is a fixed profile, is refused at admission. | -| Invocation version | An effect invocation (`invocation-v2-effect`) is admitted only for an effect-program package, and a V1 invocation only for its own fixed profile. | -| Input bounds | An effect invocation whose `application_input_bytes` is not canonical CBOR, or is longer than `max_input_bytes`, is refused at admission; a well-formed one of the wrong shape obstructs evaluation with `InputRejected`. | -| Outcome record | A bridge obstruction, including an authored one such as `BasisNotCanonical`, is retained as `EOACT004` with the same coordinate as its receipt; fixed-profile Actions still write `EOACT003`, and mixed histories recover. | -| ----------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| Pending recovery | An Action lost with its Tick before the WAL record is recovered as the same submission, not a new one. | -| Ensure resolution | An `EnsureNode` whose identical target already exists is a read, not a write, and the footprint law checks the write set without it; its target read is charged at step 2. | -| Absent endpoint history | A scoped Action whose edge endpoint was absent at its origin and created by an intervening Tick obstructs with `BasisChanged`, through the endpoint's `Absence` dependency. | -| Bridge result | A committed bridge Action's exact result bytes are retained in `EPRV0004` and `EOACT004` and bound by `result_identity`; recovery refuses re-evaluated bytes that differ. | -| Absent probe row | An absent `ReadRow` carries its expected node type in `subject`, and the image validator accepts it. | -| Set encoding | A footprint law or schema with an unsorted or duplicated set is refused at admission; two packages declaring the same sets have identical bytes. | -| Portal owner dependency | A delayed scoped Action whose portal owner's node type changed in an intervening Tick obstructs with `BasisChanged`; the owner read is charged at step 1. | -| Existing edge identity | A `ReplaceEdge` of a basis edge whose id is not its slot id under `ContentAddressV1` obstructs with `IdentityLawViolation`. | -| Unreachable records | A keyed read of a record that a prior Tick made unreachable resolves identically live and after recovery, because the record stays until a patch deletes it. | -| Alpha slot dependency | A scoped `EnsureNode` whose target's alpha slot changed in an intervening Tick, with its node record unchanged, obstructs with `BasisChanged`. | -| Absent atom probe | An absent atom read records its expected atom `type_id`; a probe for a type outside `atom_types` obstructs at step 7 with `FootprintLawViolation`. | -| Content-node adoption | An `EnsureNode` of an existing content node is adopted only when that node's record, atom, and outgoing edges recompute to its id; a node stored under another id obstructs with `IdentityLawViolation`. | -| Schema shape | A footprint law or schema with an unknown, missing, or reordered key, or a node-type entry of any other shape, is refused at admission. | -| Descended read reachability | A scoped Action that only reads a descended warp records that warp's portals and owners as dependencies, enforces `portal_owner_types`, and obstructs with `BasisChanged` when an intervening Tick changes them. | -| Undeclared range read | A range read of an edge type outside `range_edge_types` obstructs evaluation with `FootprintLawViolation`, posture `NoProposal`, before reading anything. | -| Range inspection metering | A range read over a bucket of many edges of other types is charged for every edge in the bucket and obstructs when the bucket length exceeds `max_range_edges`, even when no edge matches. | -| Existing edge source | An identical `EnsureEdge` whose source exists reads and charges the source record, and the edge id is recomputed under the rule its type selects. | -| V2 domains | V2 package, program, invocation, and installed-operation identities use the `v2` schema strings and domains, and differ from V1 identities over the same content. | -| Topology eligibility | An Action whose package's law admits an edge write or a content-node write commits only onto its exact evaluation basis, including when this invocation writes only atoms. | -| Mutable-source alias | A creating `EnsureEdge` or a `ReplaceEdge` from a mutable source that already has another edge of the same type, under any id, obstructs with `IdentityLawViolation`. | -| Beta portal | An effect or read reached only through an edge-owned portal obstructs at step 1 with `FootprintLawViolation`. | -| Program map | An effect program map whose `schema` or `kind` differs from `program-v2-effect`, that has any other key, or whose profile identities differ from the package's, is refused at admission. | +| Property | Test | +| ------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | +| Atomicity | A failure on the last effect of a multi-node proposal leaves the parent state root and patch unchanged. | +| Ensure identity | Identical existing node: no patch entry. Different existing node: `ContentIdentityConflict`. | +| Partial occupancy | A node record without its atom, or an atom without its record: `CreateNode` obstructs with `PreconditionMismatch` and `EnsureNode` with `ContentIdentityConflict`; neither overwrites the orphan slot. | +| Witnessed footprint | A program that reads a slot outside its law obstructs, even if its writes are in bounds. | +| Stale basis | A `ReplaceAtom` or `ReplaceEdge` whose expected value already disagrees with the value at its evaluation basis obstructs with `PreconditionMismatch`, posture `Proposed`, `NotDerived`, and no patch; when the value instead changed after the origin, a whole-frontier Action obstructs with `BasisChanged` before evaluation and a scoped one at the history check. | +| Budget | Exhausting steps or bytes obstructs with `BudgetExceeded` and `NoProposal`; a proposal whose patch alone exceeds the Action-batch operation limit obstructs with `BudgetExceeded`, posture `Proposed` with its digests, and no committed patch. | +| Effect charges | Each effect is charged one step and its fixed `write_bytes` (64 plus atom bytes for node and atom effects, 128 for edge effects) at emission; a budget one byte short of a proposal's total obstructs with `BudgetExceeded` and `NoProposal`. | +| Atom write bytes | A proposal whose atom bytes exceed the delegated `write_bytes` obstructs with `BudgetExceeded` and `NoProposal`; a whole-frontier `BasisChanged` records zero consumed budget. | +| Validation read budget | A `ReplaceAtom` of a stored atom larger than the remaining `read_bytes` obstructs with `BudgetExceeded`, posture `Proposed`, `NotDerived`, before hashing it; allocation beyond `edict_pure`'s `max_allocated_bytes` obstructs with `BudgetExceeded` and `NoProposal`. | +| Budget-stopped evidence | A scoped proposal whose validation read exhausts the budget records `Compared` with that dependency, and every later one, as not compared; no value past the stop is hashed. | +| Determinism | Same package, invocation, and evaluation basis give byte-identical proposals, proposal identities, read-set and write-set digests, and derived patches, whether the Action is evaluated alone or beside other Actions. Receipts and composite patches are compared only for identical Tick contexts. | +| Canonical order | Emitting the same effects in different orders yields the same canonical `EffectProposal` bytes and proposal identity. | +| Blob-independent order | Two same-kind duplicate effects on one slot with different atoms produce the same canonical image and proposal identity whichever order they were emitted in. | +| Encoding vectors | Fixed byte vectors for every row, the proposal image, every set digest, a bridge table with each posture, validity, evidence, and outcome tag, and `EOACT004` reproduce the specified bytes, sizes, and receipt-v5 digests. | +| In-place validation | Each image validates and reads through a view without heap allocation; an image with nonzero padding, a nonzero unused field, an unsorted section, an out-of-bounds range, or loosely packed blob bytes is refused. | +| No hot-path allocation | With an allocation-counting allocator, a warmed-up Tick of bridge Actions performs zero heap allocations during evaluation, validation, derivation, and composition, and recovery of that Tick allocates nothing per Action. | +| Arena independence | The same Ticks decided on hosts with different initial arena capacities produce byte-identical receipts, WAL records, and state roots. | +| Range-read entries | An empty observed range encodes as a range entry with count zero, never as an absent outcome, and a nonempty range's entry matches the specified layout byte for byte. | +| Canonical slots | Differently expressed effects on one slot (`EnsureEdge` and `ReplaceEdge` of one edge id; `CreateNode` and `ReplaceAtom` of one node) obstruct with `DuplicateEffectTarget`. | +| Write set | The derived patch's actual footprint equals the write set, including the atom slots of created nodes and the range key of every created or retargeted edge, so a committed `EnsureEdge` and `ReplaceEdge` pass the footprint check. | +| Identity law | A proposed node or edge id that does not match the package's identity law obstructs with `IdentityLawViolation`, posture `Proposed`, `NotDerived`, and recovery reproduces it. | +| Node resolution | A `ReplaceAtom` of a node absent at the evaluation basis obstructs at step 2 with `NodeMissing`, posture `Proposed`, `NotDerived`. | +| Content address | Under `ContentAddressV1`, a node id equal to the specified BLAKE3 over its type, atom, and the edges its creating proposal's `EnsureEdge` effects give it, and an edge id over its endpoints and type, pass; any other id obstructs with `IdentityLawViolation`; fixed vectors pin both derivations. | +| Addressing by type | A `ReplaceAtom` of a node whose type the schema marks content-addressed, or an `EnsureNode` of a mutable type, obstructs with `IdentityLawViolation`; a `ReplaceAtom` of a mutable-type node commits. | +| Edge addressing | An edge from a mutable node keeps its slot id when a `ReplaceEdge` retargets it; a creating `EnsureEdge` from an existing content node, a `CreateNode` of a content type, and a `ReplaceEdge` of an edge from a content node each obstruct with `IdentityLawViolation`. | +| Unreachable warp | An effect naming a key in a missing warp, or in a warp no valid portal chain reaches, obstructs with `UnreachableWarp`, posture `Proposed`, `NotDerived`. | +| Origin reachability | A scoped Action naming a warp unreachable at its origin obstructs with `UnreachableWarp` even when an intervening Tick created the portal that reaches it. | +| Portal permission and cost | At validation step 1, an effect in a descended warp whose chain crosses a portal owned by a type outside `portal_owner_types` obstructs with `FootprintLawViolation`, and a chain whose crossings exceed the remaining budget obstructs with `BudgetExceeded`. | +| Schema | An effect whose node type, atom `type_id`, or edge type the package's schema does not admit obstructs with `SchemaViolation`, posture `Proposed`, `NotDerived`, and recovery reproduces it. | +| Resolved schema | A `ReplaceEdge` whose basis edge type the schema does not admit, or a `ReplaceAtom` whose owner node type does not admit the new atom type, obstructs with `SchemaViolation` after its record is read and charged. | +| Endpoint types | A creating `EnsureEdge` or a `ReplaceEdge` whose source or target node type the schema does not admit for that edge type obstructs with `SchemaViolation`, after its endpoint records are read and charged. | +| Validation order | A proposal that breaks several rules (for example a duplicate target, an unadmitted type, and a missing endpoint) records the obstruction of the earliest step in the validation order, live and on recovery. | +| Conflict before batch limit | A candidate that both overlaps an earlier accepted Action and would exceed the batch operation limit records `RejectedFootprintConflict`, as today's scheduler does. | +| Footprint stages | A target outside the footprint law obstructs with `FootprintLawViolation` and `NotDerived`, and so does a `ReplaceEdge` whose basis edge's range key lies outside the law; a derived patch whose footprint differs from the write set obstructs with `FootprintMismatch` and `Derived`. | +| Resolved footprint | A `ReplaceEdge` whose resolved source node lies outside the footprint law obstructs with `FootprintLawViolation`. | +| Restored value (ABA) | A dependency written A to B and back to A between origin and commit parent is found through the written-slot set and obstructs with `BasisChanged`, including when the intervening patch under-declared its `out_slots`; with one replay patch in the interval missing, the Action obstructs before evaluation as a whole-frontier `BasisChanged`, posture `NoProposal`, with an empty read set. | +| Scoped preconditions | A dependency-scoped Action whose `ReplaceAtom` expected value, or one of whose edge endpoints, changed and was restored between its origin and the commit parent obstructs with `BasisChanged`, though the program never read that slot; recovery reproduces the outcome and its changed dependency. | +| Origin resolution | A scoped `ReplaceEdge` whose program never read the edge, and whose source node is deleted and recreated between origin and commit parent while the edge is unchanged, obstructs with `BasisChanged`, because resolution at the origin put the source in its dependency set. | +| Scoped conflict recovery | A scoped Action rejected because an earlier Action in its Tick replaced a value it expects recovers as `RejectedFootprintConflict`, not `PreconditionMismatch`. | +| Scoped evidence forms | An evaluation obstruction records `OriginOnly`, a dependency-scoped `BasisChanged` records `HistoryChanged` with no compared values, and every other scoped outcome records `Compared` over commit-parent values; none is compared at a validation position that includes an earlier overlapping write. | +| OriginOnly recovery | A scoped proposal that fails reachability or resolution is recovered by replaying only that failure, with no dependency set rebuilt. | +| Resolution failure evidence | A scoped `ReplaceEdge` whose edge is missing at its origin records `OriginOnly` with `PreconditionMismatch`; recovery reproduces it without running the history check. | +| Retry basis | A new submission selected after the head advanced is evaluated at its recorded basis, not at the selection-time state. | +| Receipt digest v5 | A version-5 receipt digest begins with the little-endian `u16` `5` and changes when any bridge field changes, including the validation position, a dependency's comparison mode, or a compared value; version-4 receipt identities are unchanged. | +| Proposal binding | For every `Proposed` outcome class in the recovery matrix, including rejected ones, altering one recorded effect-derived digest or the proposal identity refuses the history. | +| No-proposal evidence | An evaluation that obstructs before proposing records `NoProposal` with its read-set digest and no proposal, write-set, or patch digest. | +| Recovery | Crash before and after the decided-Tick WAL record; fresh-host recovery reproduces the same state root and receipts. | +| Composition | Two Actions with independent witnessed footprints (neither writes a slot the other reads or writes) commit in one Tick. A write/write overlap obstructs the later one, and so does a write/read overlap: A reads X and writes Y, B writes X and Z. | +| Shared structure | Two Actions that `Ensure` the same node already present in the basis both commit in one Tick. | +| Same-Tick identical create | Two Actions, dependency-scoped included, that both `Ensure` the same node absent at the commit parent: the later obstructs with `RejectedFootprintConflict` instead of reading the earlier one's write. | +| Referential integrity | An edge to a node neither in the basis nor created by the proposal obstructs with `DanglingReference`; a pre-existing dangling edge elsewhere in the basis does not obstruct an unrelated proposal. | +| Portal chain | An effect in a descended warp records every portal on its root-to-target chain as a read; a co-scheduled portal retarget conflicts with it, and a portal changed and restored between origin and commit parent obstructs a scoped Action with `BasisChanged`. | +| Visibility | No published coordinate shows a retargeted head without every node and edge the proposal introduced; pre-existing structure the head reaches is not validated. | +| Witnessed reads | The receipt's read set equals the slots the view resolved, including for a program that reads and then obstructs. | +| Negative reads | An Action that observed a node, atom, or edge set absent obstructs when a co-scheduled Action creates it, including a fresh edge of the observed source and type with a new edge id when the observed set was empty (outgoing-edge range key). | +| Range metering | A range read over a node with more edges than the law's maximum, or than the remaining budget covers, obstructs with `BudgetExceeded` and `NoProposal` before copying any edge; an admissible range charges one step and 64 read bytes per edge. | +| Absent probes | A read that finds nothing records the node type it expected; a probe for a type outside `read_node_types` obstructs at step 7 with `FootprintLawViolation`, and recovery reproduces the decision from the read set alone. | +| Edge-read basis | An Action whose package's footprint law admits a range read commits only onto its exact evaluation basis, including when this invocation reads no edge. | +| Final outcomes | A rejected Action's outcome, receipt, and attribution are unchanged after a later submission of the same edit succeeds. | +| Duplicate resubmission | Resubmitting a decided Action's identical content returns `Duplicate` with the original outcome and commits no Tick. | +| Attribution | Every Action in a bridge Tick appears once in the attribution section, mapped to its own receipt entry. | +| One effect per slot | Two effects on one slot (two `ReplaceAtom`, or `EnsureNode` plus `ReplaceAtom` of the same node) obstruct with `DuplicateEffectTarget`, posture `Proposed`, `NotDerived`. | +| Scoped basis change | A dependency-scoped Action evaluated at its origin obstructs with `BasisChanged`, posture `Proposed` with `NotDerived`, and keeps its proposal, read set, and changed dependency when a dependency changed, even if its proposal would also fail validation; an unchanged dependency set lets it commit. | +| Scoped evaluation obstruction | A dependency-scoped Action whose evaluation exhausts its budget, or fails an authored guard, keeps that obstruction with `NoProposal` even when one of its reads changed after the origin; recovery reproduces it. | +| Receipt format | `EPRV0003` records recover byte for byte through the unchanged decoder; a Tick containing a bridge Action is written as `EPRV0004`, with its fixed-profile Actions as all-zero `NotBridge` rows; a Tick of fixed-profile Actions alone stays `EPRV0003` and receipt digest version 4; a history mixing both formats recovers. | +| Package version | A source-compiled package encodes as `ExecutableOperationPackageV2` with a `closure-v2` (no lawpack keys) and a `program-v2-effect`, and its identities use the `v2` domains; a fixed-profile package keeps its V1 bytes and identities exactly. | +| Lawpack by program | A V2 package whose closure carries `lawpack_coordinate` or `lawpack_identity`, or whose program is a fixed profile, is refused at admission. | +| Invocation version | An effect invocation (`invocation-v2-effect`) is admitted only for an effect-program package, and a V1 invocation only for its own fixed profile. | +| Input bounds | An effect invocation whose `application_input_bytes` is not canonical CBOR, or is longer than `max_input_bytes`, is refused at admission; a well-formed one of the wrong shape obstructs evaluation with `InputRejected`. | +| Outcome record | A bridge obstruction, including an authored one such as `BasisNotCanonical`, is retained as `EOACT004` with the same coordinate as its receipt; fixed-profile Actions still write `EOACT003`, and mixed histories recover. | +| ------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| Pending recovery | An Action lost with its Tick before the WAL record is recovered as the same submission, not a new one. | +| Ensure resolution | An `EnsureNode` whose identical target already exists is a read, not a write, and the footprint law checks the write set without it; its target read is charged at step 2. | +| Absent endpoint history | A scoped Action whose edge endpoint was absent at its origin and created by an intervening Tick obstructs with `BasisChanged`, through the endpoint's `Absence` dependency. | +| Bridge result | A committed bridge Action's exact result bytes are retained in `EPRV0004` and `EOACT004` and bound by `result_identity`; recovery refuses re-evaluated bytes that differ. | +| Absent probe row | An absent `ReadRow` carries its expected node type in `subject`, and the image validator accepts it. | +| Set encoding | A footprint law or schema with an unsorted or duplicated set is refused at admission; two packages declaring the same sets have identical bytes. | +| Portal owner dependency | A delayed scoped Action whose portal owner's node type changed in an intervening Tick obstructs with `BasisChanged`; the owner read is charged at step 1. | +| Existing edge identity | A `ReplaceEdge` of a basis edge whose id is not its slot id under `ContentAddressV1` obstructs with `IdentityLawViolation`. | +| Unreachable records | A keyed read of a record that a prior Tick made unreachable resolves identically live and after recovery, because the record stays until a patch deletes it. | +| Alpha slot dependency | A scoped `EnsureNode` whose target's alpha slot changed in an intervening Tick, with its node record unchanged, obstructs with `BasisChanged`. | +| Absent atom probe | An absent atom read records its expected atom `type_id`; a probe for a type outside `atom_types` obstructs at step 7 with `FootprintLawViolation`. | +| Content-node adoption | An `EnsureNode` of an existing content node is adopted only when that node's record, atom, and outgoing edges recompute to its id; a node stored under another id obstructs with `IdentityLawViolation`. | +| Schema shape | A footprint law or schema with an unknown, missing, or reordered key, or a node-type entry of any other shape, is refused at admission. | +| Descended read reachability | A scoped Action that only reads a descended warp records that warp's portals and owners as dependencies, enforces `portal_owner_types`, and obstructs with `BasisChanged` when an intervening Tick changes them. | +| Undeclared range read | A range read of an edge type outside `range_edge_types` obstructs evaluation with `FootprintLawViolation`, posture `NoProposal`, before reading anything. | +| Range inspection metering | A range read over a bucket of many edges of other types is charged for every edge in the bucket and obstructs when the bucket length exceeds `max_range_edges`, even when no edge matches. | +| Existing edge source | An identical `EnsureEdge` whose source exists reads and charges the source record, and the edge id is recomputed under the rule its type selects. | +| V2 domains | V2 package, program, invocation, and installed-operation identities use the `v2` schema strings and domains, and differ from V1 identities over the same content. | +| Topology eligibility | An Action whose package's law admits an edge write or a content-node write commits only onto its exact evaluation basis, including when this invocation writes only atoms. | +| Mutable-source alias | A creating `EnsureEdge` or a `ReplaceEdge` from a mutable source that already has another edge of the same type, under any id, obstructs with `IdentityLawViolation`. | +| Beta portal | An effect or read reached only through an edge-owned portal obstructs at step 1 with `FootprintLawViolation`. | +| Program map | An effect program map whose `schema` or `kind` differs from `program-v2-effect`, that has any other key, or whose profile identities differ from the package's, is refused at admission. | +| Dangling source | A creating `EnsureEdge` whose source exists neither at the basis nor in the proposal skips the identity law and obstructs at step 8 with `DanglingReference`. | +| Graph schema binding | A V2 package whose closure names the application's own schema declaration is admitted with a different `graph_schema`; changing `graph_schema` changes the program identity. | +| Output type | A committed result's `result_identity` hashes the program's `output_type`; a program map without `output_type` is refused at admission. | +| Footprint contract | A V2 package whose `footprint_contract_identity` is not the `v2` digest of its `footprint_law` bytes is refused at admission. | +| Authored obstruction | An authored obstruction's `authored_id` follows the specified recipe from its code and canonical detail, and recovery re-derives the same identity. | +| Candidate and composite patches | In a Tick with two accepted bridge Actions, each row's `patch_digest` is its own candidate patch, and the replay patch is their batch composition; recovery verifies both. | +| Bridge decision coordinate | Every `EOACT004` variant carries the 152-byte decision coordinate, and recovery refuses one that does not name the sealed Tick and member index of its receipt entry. | +| Footprint maxima | `max_reads` and `max_writes` count distinct slots after deduplication and Ensure resolution; an identical `EnsureNode` adds no write, and one range read counts as one read. | ## Open questions From f2885d3d2d54fc2b8561fee8236a512211e3e236 Mon Sep 17 00:00:00 2001 From: James Ross Date: Fri, 16 Oct 2026 21:50:21 -0700 Subject: [PATCH 38/50] docs: fix the effect package's profile identities and basis; bind OutcomeRow Round 29 on the authored-effects bridge, checked against the V1 admission code in echo_operation.rs: - A V2 package fixes each identity field V1 derives from its program, using V1's profile_digest. target_profile_identity is the digest of echo.operation-target.edict-effect/v1, application_basis_schema_identity is the digest of echo.operation.basis.edict-effect/v1, and footprint_contract_identity is the v2 law digest. Admission requires each equality. - An effect invocation's evaluation_basis resolves the frontier as in V1. Its application basis is the input itself: the value identity is a domain-separated digest of application_input_bytes, which admission recomputes. - An EOACT004 OutcomeRow must equal the projection of the receipt-bound BridgeEntryRow, and recovery refuses a mismatch. - Three tests are added. Co-Authored-By: Claude Opus 5.5 --- .../authored-operation-effects.md | 35 +++++++++++++++---- 1 file changed, 29 insertions(+), 6 deletions(-) diff --git a/docs/architecture/authored-operation-effects.md b/docs/architecture/authored-operation-effects.md index c630d64d..6fd242aa 100644 --- a/docs/architecture/authored-operation-effects.md +++ b/docs/architecture/authored-operation-effects.md @@ -885,7 +885,16 @@ Consequences: - **Package.** The V1 package map with `schema` set to `echo.operation-package/v2`, `semantic_closure` set to a `closure-v2`, and `program` set to an effect program. Every other key, - and its value shape, is as in V1. + and its value shape, is as in V1. Where V1 derives a package identity + field from its program, V2 fixes it for the effect program, using V1's + `profile_digest` (BLAKE3 over `echo:operation-profile:v1\0` and the + length-prefixed label), and admission requires each equality: + - `target_profile_identity` is the profile digest of + `echo.operation-target.edict-effect/v1`; + - `application_basis_schema_identity` is the profile digest of + `echo.operation.basis.edict-effect/v1`; + - `footprint_contract_identity` is the `v2` footprint-law digest + below. - **Semantic closure.** The V1 closure map, with `lawpack_coordinate` and `lawpack_identity` both omitted. A V2 package carries only an effect program, which requires their absence, so no sentinel lawpack is @@ -928,7 +937,15 @@ Consequences: node and replacement (`authority_grant_identity`, `delegated_budget`, `evaluation_basis`, `operation_coordinate`, `package_id`), plus the required `application_input_bytes`, the program's input in - `edict.canonical-cbor/v1`. Admission checks only what it can check + `edict.canonical-cbor/v1`. Its `evaluation_basis` names + the frontier exactly as V1 does, and V1's basis check resolves it + there. Its application basis is the input itself: + `application_basis_schema_identity` is the package's value above, and + `application_basis_value_identity` is BLAKE3 over + `echo:operation-edict-effect-basis:v1\0`, the input's length as a + little-endian `u64`, and the input bytes. Admission recomputes both and + refuses a mismatch, so the basis evidence is bound to the input the + program evaluates. Admission checks only what it can check without a schema language: that the bytes are canonical and no longer than the program's `max_input_bytes`. The input's shape is the compiled program's to check, as its first act; a malformed input obstructs @@ -1352,10 +1369,13 @@ follow the canonical effect order in [EffectProposal](#effectproposal). A bridge Action has no `EchoOperationReceiptV1` and no prepared candidate: its evidence is the `BridgeEntryRow`, which the receipt digest already binds. Committed and conflict variants carry the all-zero `OutcomeRow`. - Recovery requires the `OutcomeRow` and `BridgeEntryRow` to equal the - receipt's byte for byte, and the decision coordinate to name the sealed - Tick that holds that receipt entry at that member index. Fixed-profile - Actions keep `EOACT003`. + The `OutcomeRow` is not separately bound by the receipt: it must equal the + projection of the receipt-bound `BridgeEntryRow` (its `outcome`, `code`, and + `authored_id`, with zero padding). Recovery derives that projection and + refuses a record whose `OutcomeRow` differs before it publishes the outcome. + Recovery also requires the `BridgeEntryRow` to equal the receipt's byte for + byte, and the decision coordinate to name the sealed Tick that holds that + receipt entry at that member index. Fixed-profile Actions keep `EOACT003`. **Memory.** The bridge allocates nothing on its hot path: @@ -1640,6 +1660,9 @@ Each behaviour below needs a test before implementation: | Candidate and composite patches | In a Tick with two accepted bridge Actions, each row's `patch_digest` is its own candidate patch, and the replay patch is their batch composition; recovery verifies both. | | Bridge decision coordinate | Every `EOACT004` variant carries the 152-byte decision coordinate, and recovery refuses one that does not name the sealed Tick and member index of its receipt entry. | | Footprint maxima | `max_reads` and `max_writes` count distinct slots after deduplication and Ensure resolution; an identical `EnsureNode` adds no write, and one range read counts as one read. | +| Effect profiles | A V2 package whose `target_profile_identity` or `application_basis_schema_identity` is not the specified profile digest is refused at admission. | +| Effect basis binding | An effect invocation whose `application_basis_value_identity` is not the digest of its own `application_input_bytes` is refused at admission. | +| Outcome row projection | An `EOACT004` record whose `OutcomeRow` differs from the projection of its receipt-bound `BridgeEntryRow` is refused by recovery. | ## Open questions From 9d50a31cea6af48c0669628f3c4d5371a33329f9 Mon Sep 17 00:00:00 2001 From: James Ross Date: Fri, 16 Oct 2026 22:08:05 -0700 Subject: [PATCH 39/50] docs: pin every inherited identity; evaluation read charges; typed reads Round 30 on the authored-effects bridge, checked against V1 admission: - Every identity field a V2 package inherits now has a fixed value, using V1's profile_digest per label as V1 derives them from its program: input, result and obstruction schemas, both interpretations, and the evaluation basis. - program-v2-effect carries max_allocated_bytes, the program's allocation ceiling, which evaluation intersects with the host's ceiling. - Evaluation reads have exact charges: node and atom reads cost one step and 64 read_bytes, plus the atom length when present, charged before the read. An uncoverable read obstructs uncharged. - ReadRow (now 192 bytes) carries read_type, the type that authorizes the read: the record's actual type when present, the named type when absent. - An identical EnsureEdge still requires its source to exist, because the identity law reads the source's type. - Five tests are added. Co-Authored-By: Claude Opus 5.5 --- .../authored-operation-effects.md | 68 +++++++++++++++---- 1 file changed, 54 insertions(+), 14 deletions(-) diff --git a/docs/architecture/authored-operation-effects.md b/docs/architecture/authored-operation-effects.md index 6fd242aa..be5c7b31 100644 --- a/docs/architecture/authored-operation-effects.md +++ b/docs/architecture/authored-operation-effects.md @@ -313,9 +313,13 @@ Rules: edge will name (`from` and `to`, or the retargeted edge's source and the new `to`) exists, unless the same proposal creates it. An `EnsureEdge` that finds an identical edge is a read; it adds and - retargets nothing, so its endpoints are not checked, as invariant 4 - and [Visibility and referential integrity](#visibility-and-referential-integrity) - limit endpoint checks to edges the proposal introduces or retargets; + retargets nothing, so its target is not checked, as invariant 4 and + [Visibility and referential integrity](#visibility-and-referential-integrity) + limit endpoint checks to edges the proposal introduces or retargets. + Its source must still exist, because the identity law reads the + source's type to check the edge's id; a missing source is + `DanglingReference` at step 8, for an identical `EnsureEdge` as for a + creating one; Every precondition slot is a read in the Action's footprint, is checked by validation, and belongs to the dependency set of a dependency-scoped Action @@ -481,11 +485,13 @@ footprint law is found at validation step 7 (`FootprintLawViolation`, `Proposed`, `NotDerived`). A read that may find nothing names the type it expects: a node read names a node -type, and an atom read names an atom `type_id`. The view records that type with -an absent outcome (in the `ReadRow`'s `subject` field), so the footprint law's -`read_node_types` or `atom_types` decides whether the probe is permitted, live -and on recovery alike. A first implementation may still sit on today's fully -materialized worldline state, but nothing in this contract may rely on that. +type, and an atom read names an atom `type_id`. The view records the authorizing +type in every node and atom `ReadRow`'s `read_type` field: the record's actual +type when the read finds one, and the type the read named when it finds nothing. +The footprint law's `read_node_types` or `atom_types` decides from that field +whether each read is permitted, live and on recovery alike. A first +implementation may still sit on today's fully materialized worldline state, but +nothing in this contract may rely on that. ## Footprint law @@ -799,10 +805,19 @@ held; this design validates only what the proposal adds or retargets. not cover its bytes. Exhausting any evaluation limit obstructs with `BudgetExceeded`, posture `NoProposal`, and no patch. Allocation is not a delegated axis: `edict_pure` already bounds it by its own - `max_allocated_bytes`, the host ceiling intersected with the program's - declared maximum, and exceeding that is likewise `BudgetExceeded` with - `NoProposal`. The allocated bytes are recorded as evidence, never compared - against the delegated budget. + `max_allocated_bytes`, the host ceiling intersected with the + `max_allocated_bytes` the program map declares, and exceeding that is likewise + `BudgetExceeded` with `NoProposal`. The allocated bytes are recorded as + evidence, never compared against the delegated budget. +- **Evaluation reads are charged.** The recording view charges each read + before it resolves it, in the order the program makes them. A node-record + read costs one step and 64 `read_bytes`, present or absent. An atom read + costs one step and 64 `read_bytes`, plus the atom's byte length when it is + present, so a large atom cannot be read beyond the budget. Range reads are + charged as [Witnessed reads](#witnessed-reads-over-a-bounded-slice) states. + A read the remaining budget cannot cover obstructs with `BudgetExceeded`, + posture `NoProposal`, before anything is read; its charge is not added to + the consumed budget, which records every charge made before it. - **Validation reads are charged.** Every slot validation reads is charged once per Action, before it is read, at the first step that reads it, and never again: a node record or an atom's presence costs one step and 64 @@ -893,8 +908,24 @@ Consequences: `echo.operation-target.edict-effect/v1`; - `application_basis_schema_identity` is the profile digest of `echo.operation.basis.edict-effect/v1`; + - `input_schema_identity`, `result_schema_identity`, and + `obstruction_schema_identity` are the profile digests of + `echo.operation.input.edict-effect/v1`, + `echo.operation.result.edict-effect/v1`, and + `echo.operation.obstruction.edict-effect/v1`; + - `result_interpretation_identity` and + `obstruction_interpretation_identity` are the profile digests of + `echo.operation.result-interpretation.edict-effect/v1` and + `echo.operation.obstruction-interpretation.edict-effect/v1`; + - `evaluation_basis_schema_identity` is the profile digest of + `echo.operation.evaluation-basis/v1`, as in V1; - `footprint_contract_identity` is the `v2` footprint-law digest below. + + Together with the profile identities the program carries, this fixes + every identity field of the package. `obstruction_coordinate` and + `operation_coordinate` remain the application's text, as in V1. + - **Semantic closure.** The V1 closure map, with `lawpack_coordinate` and `lawpack_identity` both omitted. A V2 package carries only an effect program, which requires their absence, so no sentinel lawpack is @@ -912,6 +943,9 @@ Consequences: - `graph_schema`, the canonical bytes of a `graph-schema`; - `footprint_law`, the canonical bytes of a `footprint-law`; - `max_input_bytes`; + - `max_allocated_bytes`, the program's declared allocation ceiling, + which evaluation intersects with the host's ceiling and which sizes + the scheduler's evaluation arena; - `output_type`, the compiler-declared output type coordinate of the program's application result, which `result_identity` hashes. @@ -964,7 +998,8 @@ Consequences: "interpreter_profile_identity": id, "intrinsic_profile_identity": id, "compiled_program": bstr, "target_ir_identity": id, "identity_law": uint, "graph_schema": bstr, "footprint_law": bstr, - "max_input_bytes": uint, "output_type": tstr, + "max_input_bytes": uint, "max_allocated_bytes": uint, + "output_type": tstr, } invocation-v2-effect = { "schema": "echo.operation-invocation/v2", @@ -1251,7 +1286,7 @@ rules: | Row | Bytes | Fields, in order | | ---------------- | ----- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `SlotRow` | 104 | `class` u8 (node record `0`, attachment `1`, edge `2`, range key `3`); `owner` u8 (attachment: node `0`, edge `1`); `plane` u8 (attachment: alpha `0`, beta `1`); 5 padding bytes; `warp_id`; `local_id` (the node, edge, attachment owner, or range source); `edge_type` (range key only) | -| `ReadRow` | 160 | `slot` (`SlotRow`); `outcome` u8 (present `0`, absent `1`, range `2`); 7 padding bytes; `subject`, 32 bytes: the value digest when present, when absent, the expected node type id for a node-record slot or the expected atom `type_id` for an attachment slot; zero for a range; `pairs` (`Range` into the pair section; range only) | +| `ReadRow` | 192 | `slot` (`SlotRow`); `outcome` u8 (present `0`, absent `1`, range `2`); 7 padding bytes; `read_type`, 32 bytes: the type that authorizes the read, the record's node type or atom `type_id` when present and the type the read named when absent, zero for a range; `subject`, 32 bytes: the value digest when present, zero otherwise; `pairs` (`Range` into the pair section; range only) | | `EdgePairRow` | 64 | `edge_local_id`; `target_local_id` | | `EffectRow` | 280 | `kind` u8 (`EnsureNode` `0`, `CreateNode` `1`, `ReplaceAtom` `2`, `EnsureEdge` `3`, `ReplaceEdge` `4`); 7 padding bytes; `key_warp_id`; `key_local_id`; `type_id` (node type, or `EnsureEdge`'s edge type); `atom_type_id`; `atom` (`Range` into the blob); `expected_digest` (`ReplaceAtom`); `from_local_id` (`EnsureEdge`); `to_local_id` (`EnsureEdge`, and `ReplaceEdge`'s new target); `expected_to_local_id` (`ReplaceEdge`) | | `DependencyRow` | 152 | `slot` (`SlotRow`); `kind` u8; `mode` u8; 6 padding bytes, which together with `slot` form the 112-byte descriptor; `compared` u8 (present `0`, absent `1`, not compared `2`); 7 padding bytes; `compared_digest` (present only) | @@ -1663,6 +1698,11 @@ Each behaviour below needs a test before implementation: | Effect profiles | A V2 package whose `target_profile_identity` or `application_basis_schema_identity` is not the specified profile digest is refused at admission. | | Effect basis binding | An effect invocation whose `application_basis_value_identity` is not the digest of its own `application_input_bytes` is refused at admission. | | Outcome row projection | An `EOACT004` record whose `OutcomeRow` differs from the projection of its receipt-bound `BridgeEntryRow` is refused by recovery. | +| Identical edge source | An identical `EnsureEdge` whose source no longer exists obstructs at step 8 with `DanglingReference`. | +| Allocation ceiling | An effect program map without `max_allocated_bytes` is refused; evaluation uses its intersection with the host ceiling. | +| Inherited identities | A V2 package whose input, result, obstruction, interpretation, or evaluation-basis identity differs from its specified profile digest is refused at admission. | +| Evaluation read charges | Node, absent, and atom reads charge one step and 64 `read_bytes`, plus the atom length when present; a read the budget cannot cover obstructs before reading, uncharged. | +| Read types | A present read is authorized by the record's actual type in `read_type`, an absent one by the type it named; recovery reproduces both decisions from the read set. | ## Open questions From d02feb060752f030fc9d01f1351e015be89dbd6b Mon Sep 17 00:00:00 2001 From: James Ross Date: Fri, 16 Oct 2026 22:21:21 -0700 Subject: [PATCH 40/50] docs: orphan buckets, pinned profiles, id-keyed maps, typed read rows, V2 installs Round 31 on the authored-effects bridge, checked against V1 code: - Creating a node requires its outgoing bucket to be empty, because a non-bridge writer can leave edges under an absent id. Node creation is therefore a topology dependency, and laws admitting it keep the whole-frontier rule. Scoped Actions now write only atoms of existing mutable nodes, and six tests that described scoped edge or creation writes are restated to match. - Effect programs must name the profiles Echo implements (echo.operation-interpreter.edict-effect/v1 and echo.operation-intrinsics.edict-effect/v1), as V1 pins its own. - The text-key rule covers record maps only. node_types and edge_types have 32-byte id keys, ordered bytewise. - One row per named type for a repeated absent read, so a permitted type cannot mask a forbidden probe. max_reads counts rows. - A V2 installation record is V1's map with schema echo.operation-installation/v2, and recovery dispatches on it. - One canonical bucket-scan charge: a full edge record per edge, preflighted for the whole bucket. - The absent-probe test now names read_type. Co-Authored-By: Claude Opus 5.5 --- .../authored-operation-effects.md | 101 ++++++++++++------ 1 file changed, 69 insertions(+), 32 deletions(-) diff --git a/docs/architecture/authored-operation-effects.md b/docs/architecture/authored-operation-effects.md index be5c7b31..22cbf649 100644 --- a/docs/architecture/authored-operation-effects.md +++ b/docs/architecture/authored-operation-effects.md @@ -374,14 +374,14 @@ a digest alone could not be executed. One law is defined: `echo.bridge.edge-id.v1\0`, the warp id, the `from` local id, the edge type id, and the `to` local id. It is immutable, like its source. - An edge from a mutable node has the slot id BLAKE3 over - `echo.bridge.slot-edge-id.v1\0`, the warp id, the `from` local id, and - the edge type id: one edge per source and type, whose id stays the same - when a `ReplaceEdge` retargets it. A creating `EnsureEdge` of it must - carry that id. Because a non-bridge writer can store an edge under any - id, step 2 reads the source's whole outgoing bucket, charged per edge - like a range read, for every `EnsureEdge` and `ReplaceEdge` from a - mutable source. Any other edge of the same type from that source is an - alias, and the effect obstructs with `IdentityLawViolation`. + `echo.bridge.slot-edge-id.v1\0`, the warp id, the `from` local id, and the + edge type id: one edge per source and type, whose id stays the same when a + `ReplaceEdge` retargets it. A creating `EnsureEdge` of it must carry that + id. Because a non-bridge writer can store an edge under any id, step 2 + scans the source's whole outgoing bucket (see [Validation + reads](#basis-budget-retries)) for every `EnsureEdge` and `ReplaceEdge` + from a mutable source. Any other edge of the same type from that source is + an alias, and the effect obstructs with `IdentityLawViolation`. An `EnsureNode` whose content-typed target already exists adopts it only if the existing node's record, atom, and complete outgoing edge set equal @@ -466,14 +466,16 @@ was evaluated against, when its package's footprint law admits any of: edge from a mutable source requires that no other edge of its type leave that source, and that depends on the source's whole bucket; - a write of a content-addressed node type. Adopting an existing content node - requires its whole outgoing edge set to match. + requires its whole outgoing edge set to match; +- node creation (a non-empty `write_node_types`). Creating a node requires its + outgoing bucket to be empty, which depends on the whole bucket. Each of these is a topology dependency: a write from outside the bridge (the unrestricted `UpsertEdge` path) can add an edge to a bucket without changing any single slot a scoped Action recorded. Every other bridge Action is eligible for -dependency-scoped validity, and its writes are node records and atoms of mutable -types. Widening that requires sound edge-set tracking under #837, not a looser -rule here. +dependency-scoped validity; its writes are atoms of existing mutable nodes. +Widening that requires sound edge-set tracking under #837, not a looser rule +here. The view resolves each slot on demand from the basis, as [Sliced Readings and Copy-on-Write Strands](sliced-readings-and-copy-on-write-strands.md) @@ -501,8 +503,12 @@ Every set-valued field in a footprint law or a schema is a CBOR array of distinct elements in strictly ascending order. An id is a 32-byte byte string, ordered bytewise. A pair is a two-element array ordered by its first element, then its second. Admission refuses a law or schema with an unsorted or -duplicated set, rather than normalizing it. Map keys are text strings, ordered -as `edict.canonical-cbor/v1` orders them. The complete shapes, in CDDL, are: +duplicated set, rather than normalizing it. Record maps (`footprint-law`, +`graph-schema`, `node-type`) have the fixed text keys shown. The id-keyed maps +`node_types` and `edge_types` have 32-byte byte-string keys, each id at most +once. `edict.canonical-cbor/v1` orders map keys by their encoded bytes, which +for these equal-length keys is bytewise order of the ids. The complete shapes, +in CDDL, are: ```text footprint-law = { @@ -536,8 +542,10 @@ warp). Each portal on a validated chain must be owned by a node of one of these types, or validation obstructs with `FootprintLawViolation`. The bridge does not traverse edge-owned (beta) portals: an edge has no node type for this law to admit, so a chain crossing one is `FootprintLawViolation` at step 1, before any -owner record is read. The counts are over distinct slots, after deduplication: -`max_reads` bounds the witnessed read set, where one range read is one slot (its +owner record is read. The counts are taken after deduplication: +`max_reads` bounds the rows of the witnessed read set (one per slot, or one per +named type for an absent slot read under several types; see the sort orders in +[Encoding and memory](#encoding-and-memory)), where one range read is one row (its range key) and its edges are bounded by `max_range_edges`; `max_writes` bounds the resolved write set after Ensure resolution, range keys included, so an identical Ensure adds nothing. Validation reads are bounded by the budget, not @@ -614,8 +622,11 @@ recorded obstruction: target of each `EnsureNode` and `CreateNode`, both its node record and its alpha atom slot, and the target edge record of each `EnsureEdge`, present or absent, which decides whether the effect writes or only confirms; for an - `EnsureNode` whose content-typed target exists, that node's whole outgoing - bucket, charged per edge like a range read, which the identity law needs; + `EnsureNode` whose content-typed target exists, a scan of that node's whole + outgoing bucket, which the identity law needs; for every node the proposal + creates, its outgoing bucket's length, which must be zero, since a node + cannot be created over edges a non-bridge writer left under its id + (`PreconditionMismatch` otherwise); each `ReplaceAtom`'s node record; each `ReplaceEdge`'s edge record; the source node record of every `EnsureEdge` and `ReplaceEdge`, whose type selects the edge-id rule, and, when that source is mutable, its whole @@ -823,7 +834,12 @@ held; this design validates only what the proposal adds or retargets. never again: a node record or an atom's presence costs one step and 64 `read_bytes`, an edge record one step and 128 `read_bytes`, and hashing an atom's value adds its byte length. A slot the program's view already read - is not charged again, since its value is known. So step 1 charges portals + is not charged again, since its value is known. A bucket scan reads every + edge in the bucket as a full edge record, one step and 128 `read_bytes` + each, and is charged for the whole bucket before any edge is inspected; a + bucket length read alone costs one step and 64 `read_bytes`. A scan the + remaining budget cannot cover obstructs before inspecting anything, and + its charge is not added. So step 1 charges portals and their owners' records, for effect and read keys alike, step 2 charges each resolved record, including every Ensure and Create target with its alpha slot, every edge source, and the outgoing bucket of an existing @@ -934,8 +950,12 @@ Consequences: own; its bytes are part of the package's. - **Effect program.** A new map, `program-v2-effect` below. Like a V1 program, it carries `interpreter_profile_identity` and - `intrinsic_profile_identity`, and admission requires each to equal the - package's value of the same name. Its other entries are: + `intrinsic_profile_identity`. Admission requires each to equal the + package's value of the same name and the profile Echo implements for + effect programs: the profile digests of + `echo.operation-interpreter.edict-effect/v1` and + `echo.operation-intrinsics.edict-effect/v1`. Any other value is + refused as an unsupported profile, as in V1. Its other entries are: - `compiled_program`, the compiled bytes; - `target_ir_identity`, which must equal the closure's; - `identity_law`, an unsigned integer naming an @@ -1018,9 +1038,15 @@ Consequences: `echo:operation-invocation-bytes:v2\0`, `echo:operation-package-admission:v2\0`, `echo:operation-invocation-admission:v2\0`, and - `echo:operation-installation:v2\0`. Every other domain is unchanged. So V2 - identities never coincide with V1 identities, and V1 encoding, validation, - and identities are unchanged. + `echo:operation-installation:v2\0`. Every other domain is unchanged. The + retained installation record of a V2 package is the V1 installation map + (`admission_policy`, `admission_policy_id`, `installed_operation_id`, + `package_admission_id`, `package_bytes`, `package_id`, `schema`) with + `schema` set to `echo.operation-installation/v2`. Recovery dispatches on + `schema`: `v1` decodes `package_bytes` with the V1 package decoder and + identity recipes, and `v2` with the V2 ones; any other value is refused. So + V2 identities never coincide with V1 identities, and V1 encoding, + validation, and identities are unchanged. ## Evidence @@ -1341,7 +1367,12 @@ start: the blob in bytes, and the `EdgePairRow` section in rows. **Sort orders.** `SlotRow`s sort by `class`, `warp_id`, `local_id`, `edge_type`, `owner`, then `plane`, so a set's rows are in slot order. -`ReadRow`s and `DependencyRow`s sort by their slot, and `DependencyRow`s with +`ReadRow`s sort by their slot, then by `read_type`. A slot the program reads +more than once under different named types while it is absent has one row per +named type, each authorized on its own, so a permitted type cannot mask a +forbidden probe; repeated reads with the same type are one row. A present slot +has one row, under its actual type. `max_reads` counts rows. `DependencyRow`s +sort by their slot, and `DependencyRow`s with one slot by `kind`, then `mode`. A range's `EdgePairRow`s are contiguous, sorted by `edge_local_id`, and the ranges follow `ReadRow` order. `EffectRow`s follow the canonical effect order in [EffectProposal](#effectproposal). @@ -1630,12 +1661,12 @@ Each behaviour below needs a test before implementation: | Footprint stages | A target outside the footprint law obstructs with `FootprintLawViolation` and `NotDerived`, and so does a `ReplaceEdge` whose basis edge's range key lies outside the law; a derived patch whose footprint differs from the write set obstructs with `FootprintMismatch` and `Derived`. | | Resolved footprint | A `ReplaceEdge` whose resolved source node lies outside the footprint law obstructs with `FootprintLawViolation`. | | Restored value (ABA) | A dependency written A to B and back to A between origin and commit parent is found through the written-slot set and obstructs with `BasisChanged`, including when the intervening patch under-declared its `out_slots`; with one replay patch in the interval missing, the Action obstructs before evaluation as a whole-frontier `BasisChanged`, posture `NoProposal`, with an empty read set. | -| Scoped preconditions | A dependency-scoped Action whose `ReplaceAtom` expected value, or one of whose edge endpoints, changed and was restored between its origin and the commit parent obstructs with `BasisChanged`, though the program never read that slot; recovery reproduces the outcome and its changed dependency. | -| Origin resolution | A scoped `ReplaceEdge` whose program never read the edge, and whose source node is deleted and recreated between origin and commit parent while the edge is unchanged, obstructs with `BasisChanged`, because resolution at the origin put the source in its dependency set. | +| Scoped preconditions | A dependency-scoped Action whose `ReplaceAtom` expected value changed and was restored between its origin and the commit parent obstructs with `BasisChanged`, though the program never read it. | +| Origin resolution | A scoped `ReplaceAtom` whose program never read its node, and whose node is deleted and recreated between origin and commit parent with the same atom, obstructs with `BasisChanged`, because resolution at the origin recorded the node record as a dependency. | | Scoped conflict recovery | A scoped Action rejected because an earlier Action in its Tick replaced a value it expects recovers as `RejectedFootprintConflict`, not `PreconditionMismatch`. | | Scoped evidence forms | An evaluation obstruction records `OriginOnly`, a dependency-scoped `BasisChanged` records `HistoryChanged` with no compared values, and every other scoped outcome records `Compared` over commit-parent values; none is compared at a validation position that includes an earlier overlapping write. | | OriginOnly recovery | A scoped proposal that fails reachability or resolution is recovered by replaying only that failure, with no dependency set rebuilt. | -| Resolution failure evidence | A scoped `ReplaceEdge` whose edge is missing at its origin records `OriginOnly` with `PreconditionMismatch`; recovery reproduces it without running the history check. | +| Resolution failure evidence | A scoped `ReplaceAtom` whose node is missing at its origin records `OriginOnly` with `NodeMissing`; recovery reproduces it without running the history check. | | Retry basis | A new submission selected after the head advanced is evaluated at its recorded basis, not at the selection-time state. | | Receipt digest v5 | A version-5 receipt digest begins with the little-endian `u16` `5` and changes when any bridge field changes, including the validation position, a dependency's comparison mode, or a compared value; version-4 receipt identities are unchanged. | | Proposal binding | For every `Proposed` outcome class in the recovery matrix, including rejected ones, altering one recorded effect-derived digest or the proposal identity refuses the history. | @@ -1643,7 +1674,7 @@ Each behaviour below needs a test before implementation: | Recovery | Crash before and after the decided-Tick WAL record; fresh-host recovery reproduces the same state root and receipts. | | Composition | Two Actions with independent witnessed footprints (neither writes a slot the other reads or writes) commit in one Tick. A write/write overlap obstructs the later one, and so does a write/read overlap: A reads X and writes Y, B writes X and Z. | | Shared structure | Two Actions that `Ensure` the same node already present in the basis both commit in one Tick. | -| Same-Tick identical create | Two Actions, dependency-scoped included, that both `Ensure` the same node absent at the commit parent: the later obstructs with `RejectedFootprintConflict` instead of reading the earlier one's write. | +| Same-Tick identical create | Two Actions that both `Ensure` the same node absent at the commit parent: the later obstructs with `RejectedFootprintConflict` instead of reading the earlier one's write. | | Referential integrity | An edge to a node neither in the basis nor created by the proposal obstructs with `DanglingReference`; a pre-existing dangling edge elsewhere in the basis does not obstruct an unrelated proposal. | | Portal chain | An effect in a descended warp records every portal on its root-to-target chain as a read; a co-scheduled portal retarget conflicts with it, and a portal changed and restored between origin and commit parent obstructs a scoped Action with `BasisChanged`. | | Visibility | No published coordinate shows a retargeted head without every node and edge the proposal introduced; pre-existing structure the head reaches is not validated. | @@ -1667,14 +1698,14 @@ Each behaviour below needs a test before implementation: | ------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | Pending recovery | An Action lost with its Tick before the WAL record is recovered as the same submission, not a new one. | | Ensure resolution | An `EnsureNode` whose identical target already exists is a read, not a write, and the footprint law checks the write set without it; its target read is charged at step 2. | -| Absent endpoint history | A scoped Action whose edge endpoint was absent at its origin and created by an intervening Tick obstructs with `BasisChanged`, through the endpoint's `Absence` dependency. | +| Absent endpoint history | An Action whose package admits edge writes keeps the whole-frontier rule, so an endpoint absent at its evaluation basis and created by a later Tick never lets it commit onto that later basis; it obstructs with whole-frontier `BasisChanged`. | | Bridge result | A committed bridge Action's exact result bytes are retained in `EPRV0004` and `EOACT004` and bound by `result_identity`; recovery refuses re-evaluated bytes that differ. | -| Absent probe row | An absent `ReadRow` carries its expected node type in `subject`, and the image validator accepts it. | +| Absent probe row | An absent `ReadRow` carries its expected type in `read_type` and a zero `subject`, and the image validator accepts it. | | Set encoding | A footprint law or schema with an unsorted or duplicated set is refused at admission; two packages declaring the same sets have identical bytes. | | Portal owner dependency | A delayed scoped Action whose portal owner's node type changed in an intervening Tick obstructs with `BasisChanged`; the owner read is charged at step 1. | | Existing edge identity | A `ReplaceEdge` of a basis edge whose id is not its slot id under `ContentAddressV1` obstructs with `IdentityLawViolation`. | | Unreachable records | A keyed read of a record that a prior Tick made unreachable resolves identically live and after recovery, because the record stays until a patch deletes it. | -| Alpha slot dependency | A scoped `EnsureNode` whose target's alpha slot changed in an intervening Tick, with its node record unchanged, obstructs with `BasisChanged`. | +| Alpha slot dependency | A scoped `ReplaceAtom` whose owner node record changed in an intervening Tick, with its atom unchanged, obstructs with `BasisChanged` through the resolved node-record dependency. | | Absent atom probe | An absent atom read records its expected atom `type_id`; a probe for a type outside `atom_types` obstructs at step 7 with `FootprintLawViolation`. | | Content-node adoption | An `EnsureNode` of an existing content node is adopted only when that node's record, atom, and outgoing edges recompute to its id; a node stored under another id obstructs with `IdentityLawViolation`. | | Schema shape | A footprint law or schema with an unknown, missing, or reordered key, or a node-type entry of any other shape, is refused at admission. | @@ -1703,6 +1734,12 @@ Each behaviour below needs a test before implementation: | Inherited identities | A V2 package whose input, result, obstruction, interpretation, or evaluation-basis identity differs from its specified profile digest is refused at admission. | | Evaluation read charges | Node, absent, and atom reads charge one step and 64 `read_bytes`, plus the atom length when present; a read the budget cannot cover obstructs before reading, uncharged. | | Read types | A present read is authorized by the record's actual type in `read_type`, an absent one by the type it named; recovery reproduces both decisions from the read set. | +| Orphan bucket | Creating a node whose id already has an outgoing bucket left by a non-bridge writer obstructs with `PreconditionMismatch`; a law admitting node creation keeps the whole-frontier rule. | +| Supported profiles | An effect program and package that agree on any interpreter or intrinsic profile other than the specified effect profiles are refused at admission. | +| Id-keyed maps | A graph schema whose `node_types` or `edge_types` keys are out of bytewise order, or repeat an id, is refused at admission. | +| Repeated absent reads | Reading one absent slot under a permitted and a forbidden type yields two rows and `FootprintLawViolation`; reading it twice under one type yields one row. | +| V2 installation record | A retained V2 installation uses schema `echo.operation-installation/v2` and recovers through the V2 decoder; a record with any other schema is refused. | +| Bucket scan charge | A bucket scan charges one step and 128 `read_bytes` per edge for the whole bucket before inspecting any; a scan the budget cannot cover obstructs uncharged. | ## Open questions From 08cfaf36d0eb4fe173edd94eb53de10e6a6fbea8 Mon Sep 17 00:00:00 2001 From: James Ross Date: Fri, 16 Oct 2026 22:33:10 -0700 Subject: [PATCH 41/50] docs: bridge Tick scope, mixed-batch digests, evaluation-only cost, portal reads Round 32 on the authored-effects bridge, checked against the batch code in echo_operation.rs: - A bridge receipt entry gets a canonical synthetic scope (the root warp plus the invocation identity) and V1's scope_hash, recomputed on recovery. - Batch rewrites and composition digests: a Tick of fixed-profile Actions alone keeps V1 byte for byte. A Tick with a bridge Action uses v2 domains and a member kind byte. A bridge member's tuple is its proposal identity, candidate patch digest, result identity, evaluation-basis identity, and write-set digest. - The proposal header's cost is evaluation-only, so proposal identity does not depend on how far validation ran. - Allocation: the program's declared ceiling bounds evaluation, capped at admission by a fixed profile ceiling (16 MiB). A host short of it fails as a host fault, never an obstruction. - An evaluation-time FootprintLawViolation joins the NoProposal recovery row. - An atom read of a portal has its own outcome (3), with the portal digest, authorized through portal_owner_types. - Six tests are added. Co-Authored-By: Claude Opus 5.5 --- .../authored-operation-effects.md | 66 +++++++++++++++---- 1 file changed, 52 insertions(+), 14 deletions(-) diff --git a/docs/architecture/authored-operation-effects.md b/docs/architecture/authored-operation-effects.md index 22cbf649..19a74219 100644 --- a/docs/architecture/authored-operation-effects.md +++ b/docs/architecture/authored-operation-effects.md @@ -491,7 +491,12 @@ type, and an atom read names an atom `type_id`. The view records the authorizing type in every node and atom `ReadRow`'s `read_type` field: the record's actual type when the read finds one, and the type the read named when it finds nothing. The footprint law's `read_node_types` or `atom_types` decides from that field -whether each read is permitted, live and on recovery alike. A first +whether each read is permitted, live and on recovery alike. An atom read of an +alpha slot that holds a portal (`Descend`) has the outcome `portal`: the +program sees the portal, never an atom. Its row has a zero `read_type` and the +portal's attachment value digest as `subject`. It is authorized when the slot's +owner node type is in `portal_owner_types`, and it is a `ValueDigest` +dependency like any present value. A first implementation may still sit on today's fully materialized worldline state, but nothing in this contract may rely on that. @@ -815,11 +820,15 @@ held; this design validates only what the proposal adds or retargets. and `ReplaceEdge`. A large payload therefore cannot fit a budget that would not cover its bytes. Exhausting any evaluation limit obstructs with `BudgetExceeded`, posture `NoProposal`, and no patch. Allocation is not a - delegated axis: `edict_pure` already bounds it by its own - `max_allocated_bytes`, the host ceiling intersected with the - `max_allocated_bytes` the program map declares, and exceeding that is likewise - `BudgetExceeded` with `NoProposal`. The allocated bytes are recorded as - evidence, never compared against the delegated budget. + delegated axis: evaluation is bounded by the `max_allocated_bytes` the program + map declares, and exceeding it is likewise `BudgetExceeded` with `NoProposal`. + Admission refuses a program that declares more than the effect profile's fixed + ceiling, `EFFECT_ALLOCATION_CEILING_V1` (16 MiB), which every host + implementing the profile must provide. So no host's own limit enters an + outcome: a host that cannot provide the declared bytes fails as a host fault + and decides nothing, as **No host-dependent decisions** below requires. The + allocated bytes are recorded as evidence, never compared against the delegated + budget. - **Evaluation reads are charged.** The recording view charges each read before it resolves it, in the order the program makes them. A node-record read costs one step and 64 `read_bytes`, present or absent. An atom read @@ -903,6 +912,26 @@ Consequences: - **Attribution** is unchanged: an Action batch already attributes each candidate to its own receipt entry. The proposal identity and digests below ride in that entry; the attribution section still names only submissions. +- **Tick scope.** Every `TickReceiptEntry` needs a `scope` and its + `scope_hash`, which `action_batch_plan_digest` hashes. A fixed-profile + Action's scope is its anchored node. A bridge Action has none, so its scope + is synthetic and canonical: the worldline's root warp id, with the + invocation identity's 32 bytes as the local id. Its `scope_hash` is + `scope_hash(installed_operation_id, scope)`, as for fixed profiles. It names + no graph node, is never read, and recovery recomputes it from the retained + invocation. +- **Batch digests.** The batch rewrites and composition digests hash one + identity tuple per applied member. A Tick of fixed-profile Actions alone + keeps today's recipes and `v1` domains byte for byte. A Tick that contains + a bridge Action uses `echo:operation-action-batch-rewrites:v2\0` and + `echo:operation-action-batch-composition:v2\0`, with a one-byte member kind + before each member (fixed `1`, bridge `2`), so the two kinds of tuple can + never alias. A fixed member contributes exactly its V1 tuple. A bridge + member contributes its proposal identity where a fixed member has its + `preparation_id`, then its candidate patch digest, its `result_identity`, + its invocation's evaluation-basis identity, and its write-set digest as + its actual footprint. The rewrites digest takes the first element of each + tuple, as V1 takes `preparation_id`. - **Durability** is unchanged in kind: the decided-Tick WAL record is the only persistence. Proposals are never retained apart from their Tick. The record gains a new layout version for the new evidence (see Evidence). @@ -964,8 +993,8 @@ Consequences: - `footprint_law`, the canonical bytes of a `footprint-law`; - `max_input_bytes`; - `max_allocated_bytes`, the program's declared allocation ceiling, - which evaluation intersects with the host's ceiling and which sizes - the scheduler's evaluation arena; + at most `EFFECT_ALLOCATION_CEILING_V1`, which bounds evaluation and + sizes the scheduler's evaluation arena; - `output_type`, the compiler-declared output type coordinate of the program's application result, which `result_identity` hashes. @@ -1277,7 +1306,7 @@ above likewise applies to every row recorded as `Scoped`. | Obstructed at validation (`DanglingReference`, `ContentIdentityConflict`, `DuplicateEffectTarget`, `FootprintLawViolation`, `IdentityLawViolation`, `SchemaViolation`, `UnreachableWarp`, `NodeMissing`, `PreconditionMismatch`, validation-read `BudgetExceeded`) | `Proposed`, `NotDerived` | re-evaluate; compare pre-decision evidence | effects bound; read-set digest; obstruction kind; no write-set or patch digest present | | Obstructed by `FootprintMismatch` (after derivation) | `Proposed`, `Derived` | re-evaluate; compare pre-decision evidence | effects bound; read-set digest; re-derived patch reproduces the mismatch | | `BudgetExceeded`, scheduler-assigned (Action-batch limit) | `Proposed`, `Derived` | accept reconstructed preparation; verify through the batch reconstruction | effects bound; read-set digest | -| `BudgetExceeded` during evaluation, or an authored guard | `NoProposal` | re-evaluate; compare pre-decision evidence | re-evaluation, at the origin for a `Scoped` Action, again yields no proposal and the same obstruction; read-set digest | +| `BudgetExceeded` during evaluation, an evaluation-time `FootprintLawViolation` (an undeclared range type), or an authored guard | `NoProposal` | re-evaluate; compare pre-decision evidence | re-evaluation, at the origin for a `Scoped` Action, again yields no proposal and the same obstruction; read-set digest | | `BasisChanged`, whole-frontier (before evaluation, including unknown history) | `NoProposal` | admission evidence; recomputed basis posture; no re-evaluation | none; posture must be `NoProposal` with an empty read set and zero consumed budget | | `BasisChanged`, dependency-scoped (after evaluation, before validation) | `Proposed`, `NotDerived` | none today: current programs decide this before evaluation | re-evaluate at the origin; effects bound; read-set digest; recompute the written-slot set from replay operations, the complete dependency set (witnessed reads and effect preconditions), and the changed dependency | | Any outcome of a `NotBridge` entry | `NotBridge` | unchanged | none; its bridge row is all zero | @@ -1312,7 +1341,7 @@ rules: | Row | Bytes | Fields, in order | | ---------------- | ----- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `SlotRow` | 104 | `class` u8 (node record `0`, attachment `1`, edge `2`, range key `3`); `owner` u8 (attachment: node `0`, edge `1`); `plane` u8 (attachment: alpha `0`, beta `1`); 5 padding bytes; `warp_id`; `local_id` (the node, edge, attachment owner, or range source); `edge_type` (range key only) | -| `ReadRow` | 192 | `slot` (`SlotRow`); `outcome` u8 (present `0`, absent `1`, range `2`); 7 padding bytes; `read_type`, 32 bytes: the type that authorizes the read, the record's node type or atom `type_id` when present and the type the read named when absent, zero for a range; `subject`, 32 bytes: the value digest when present, zero otherwise; `pairs` (`Range` into the pair section; range only) | +| `ReadRow` | 192 | `slot` (`SlotRow`); `outcome` u8 (present `0`, absent `1`, range `2`, portal `3`); 7 padding bytes; `read_type`, 32 bytes: the type that authorizes the read, the record's node type or atom `type_id` when present and the type the read named when absent, zero for a range or a portal; `subject`, 32 bytes: the value digest when present or a portal, zero otherwise; `pairs` (`Range` into the pair section; range only) | | `EdgePairRow` | 64 | `edge_local_id`; `target_local_id` | | `EffectRow` | 280 | `kind` u8 (`EnsureNode` `0`, `CreateNode` `1`, `ReplaceAtom` `2`, `EnsureEdge` `3`, `ReplaceEdge` `4`); 7 padding bytes; `key_warp_id`; `key_local_id`; `type_id` (node type, or `EnsureEdge`'s edge type); `atom_type_id`; `atom` (`Range` into the blob); `expected_digest` (`ReplaceAtom`); `from_local_id` (`EnsureEdge`); `to_local_id` (`EnsureEdge`, and `ReplaceEdge`'s new target); `expected_to_local_id` (`ReplaceEdge`) | | `DependencyRow` | 152 | `slot` (`SlotRow`); `kind` u8; `mode` u8; 6 padding bytes, which together with `slot` form the 112-byte descriptor; `compared` u8 (present `0`, absent `1`, not compared `2`); 7 padding bytes; `compared_digest` (present only) | @@ -1381,8 +1410,11 @@ follow the canonical effect order in [EffectProposal](#effectproposal). - **Proposal** (magic `EBPRP001`): after the magic, a 192-byte header holding the installed-operation identity, the canonical invocation digest, the - `EchoOperationEvaluationBasisV1` identity, the read-set digest, the cost - (`steps`, `read_bytes`, `write_bytes`, `allocated_bytes`), the result as a + `EchoOperationEvaluationBasisV1` identity, the read-set digest, the + evaluation cost alone (`steps`, `read_bytes`, `write_bytes`, + `allocated_bytes` as evaluation consumed them, never validation charges, so + the proposal identity does not depend on how far validation ran), the + result as a `Range` into the blob, the effect count, and the blob length; then the `EffectRow`s; then the blob, holding the atoms in effect order and then the result. The proposal identity is BLAKE3 over @@ -1695,7 +1727,7 @@ Each behaviour below needs a test before implementation: | Invocation version | An effect invocation (`invocation-v2-effect`) is admitted only for an effect-program package, and a V1 invocation only for its own fixed profile. | | Input bounds | An effect invocation whose `application_input_bytes` is not canonical CBOR, or is longer than `max_input_bytes`, is refused at admission; a well-formed one of the wrong shape obstructs evaluation with `InputRejected`. | | Outcome record | A bridge obstruction, including an authored one such as `BasisNotCanonical`, is retained as `EOACT004` with the same coordinate as its receipt; fixed-profile Actions still write `EOACT003`, and mixed histories recover. | -| ------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| ------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | Pending recovery | An Action lost with its Tick before the WAL record is recovered as the same submission, not a new one. | | Ensure resolution | An `EnsureNode` whose identical target already exists is a read, not a write, and the footprint law checks the write set without it; its target read is charged at step 2. | | Absent endpoint history | An Action whose package admits edge writes keeps the whole-frontier rule, so an endpoint absent at its evaluation basis and created by a later Tick never lets it commit onto that later basis; it obstructs with whole-frontier `BasisChanged`. | @@ -1729,7 +1761,7 @@ Each behaviour below needs a test before implementation: | Effect profiles | A V2 package whose `target_profile_identity` or `application_basis_schema_identity` is not the specified profile digest is refused at admission. | | Effect basis binding | An effect invocation whose `application_basis_value_identity` is not the digest of its own `application_input_bytes` is refused at admission. | | Outcome row projection | An `EOACT004` record whose `OutcomeRow` differs from the projection of its receipt-bound `BridgeEntryRow` is refused by recovery. | -| Identical edge source | An identical `EnsureEdge` whose source no longer exists obstructs at step 8 with `DanglingReference`. | +| ----------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | Allocation ceiling | An effect program map without `max_allocated_bytes` is refused; evaluation uses its intersection with the host ceiling. | | Inherited identities | A V2 package whose input, result, obstruction, interpretation, or evaluation-basis identity differs from its specified profile digest is refused at admission. | | Evaluation read charges | Node, absent, and atom reads charge one step and 64 `read_bytes`, plus the atom length when present; a read the budget cannot cover obstructs before reading, uncharged. | @@ -1740,6 +1772,12 @@ Each behaviour below needs a test before implementation: | Repeated absent reads | Reading one absent slot under a permitted and a forbidden type yields two rows and `FootprintLawViolation`; reading it twice under one type yields one row. | | V2 installation record | A retained V2 installation uses schema `echo.operation-installation/v2` and recovers through the V2 decoder; a record with any other schema is refused. | | Bucket scan charge | A bucket scan charges one step and 128 `read_bytes` per edge for the whole bucket before inspecting any; a scan the budget cannot cover obstructs uncharged. | +| Bridge scope | A bridge Action's receipt entry has the synthetic scope (root warp, invocation identity) and its `scope_hash`; recovery recomputes both from the retained invocation. | +| Mixed batch digests | A Tick of fixed-profile Actions alone keeps its V1 rewrites and composition digests byte for byte; a Tick with a bridge Action uses the `v2` recipes with member kinds, and recovery reproduces them. | +| Evaluation-only proposal cost | The same evaluation at the same origin has the same proposal identity whether validation stops at the history check or runs to the end. | +| Allocation ceiling profile | A program declaring more than `EFFECT_ALLOCATION_CEILING_V1` is refused at admission; a host that cannot provide the declared bytes fails the Tick as a host fault, never obstructing the Action. | +| Range-type refusal recovery | An evaluation-time `FootprintLawViolation` for an undeclared range type recovers as `NoProposal` by re-evaluation, with the same read set. | +| Portal atom read | An atom read of a slot holding a portal records outcome `portal` with the portal's digest; it is refused unless the owner's type is in `portal_owner_types`. | ## Open questions From 5ac35cde50df76a0a36ecc9f7069da353873cc67 Mon Sep 17 00:00:00 2001 From: James Ross Date: Fri, 16 Oct 2026 22:43:31 -0700 Subject: [PATCH 42/50] docs: portal reads bind the owner type; inherited I/O ceilings; no result projection Round 33 on the authored-effects bridge: - A portal atom read now also reads and charges the slot owner's node record, and records the owner's type as read_type, so portal_owner_types is decided from the row alone. The owner record is a resolved-record dependency. - program-v2-effect carries max_input_bytes and max_result_bytes, each capped at 65,536 as ADR 0025 requires. A longer result is BudgetExceeded with NoProposal. - A V2 package must omit application_result_projection: an effect program's result is its own output, bound by result_identity. - Two tests are added and the portal test is updated. Co-Authored-By: Claude Opus 5.5 --- .../authored-operation-effects.md | 32 ++++++++++++------- 1 file changed, 21 insertions(+), 11 deletions(-) diff --git a/docs/architecture/authored-operation-effects.md b/docs/architecture/authored-operation-effects.md index 19a74219..f6a47712 100644 --- a/docs/architecture/authored-operation-effects.md +++ b/docs/architecture/authored-operation-effects.md @@ -492,11 +492,13 @@ type in every node and atom `ReadRow`'s `read_type` field: the record's actual type when the read finds one, and the type the read named when it finds nothing. The footprint law's `read_node_types` or `atom_types` decides from that field whether each read is permitted, live and on recovery alike. An atom read of an -alpha slot that holds a portal (`Descend`) has the outcome `portal`: the -program sees the portal, never an atom. Its row has a zero `read_type` and the -portal's attachment value digest as `subject`. It is authorized when the slot's -owner node type is in `portal_owner_types`, and it is a `ValueDigest` -dependency like any present value. A first +alpha slot that holds a portal (`Descend`) has the outcome `portal`: the program +sees the portal, never an atom. The view also reads the slot owner's node +record, charged as a node-record read, and records the owner's node type as the +row's `read_type`, with the portal's attachment value digest as `subject`. The +read is authorized when that type is in `portal_owner_types`, decided from the +row alone, live and on recovery. The portal is a `ValueDigest` dependency, and +the owner record is a resolved-record dependency, like any present value. A first implementation may still sit on today's fully materialized worldline state, but nothing in this contract may rely on that. @@ -945,7 +947,10 @@ Consequences: - **Package.** The V1 package map with `schema` set to `echo.operation-package/v2`, `semantic_closure` set to a `closure-v2`, and `program` set to an effect program. Every other key, - and its value shape, is as in V1. Where V1 derives a package identity + and its value shape, is as in V1, except that the optional + `application_result_projection` must be absent: an effect program's + application result is its own output, which `result_identity` binds, + so no projection can compete with it. Where V1 derives a package identity field from its program, V2 fixes it for the effect program, using V1's `profile_digest` (BLAKE3 over `echo:operation-profile:v1\0` and the length-prefixed label), and admission requires each equality: @@ -991,7 +996,10 @@ Consequences: [identity law](#identity-laws); unknown values are refused; - `graph_schema`, the canonical bytes of a `graph-schema`; - `footprint_law`, the canonical bytes of a `footprint-law`; - - `max_input_bytes`; + - `max_input_bytes` and `max_result_bytes`, each at most 65,536, the + application input and result ceilings ADR 0025 sets. A result + longer than `max_result_bytes` obstructs evaluation with + `BudgetExceeded`, posture `NoProposal`; - `max_allocated_bytes`, the program's declared allocation ceiling, at most `EFFECT_ALLOCATION_CEILING_V1`, which bounds evaluation and sizes the scheduler's evaluation arena; @@ -1047,8 +1055,8 @@ Consequences: "interpreter_profile_identity": id, "intrinsic_profile_identity": id, "compiled_program": bstr, "target_ir_identity": id, "identity_law": uint, "graph_schema": bstr, "footprint_law": bstr, - "max_input_bytes": uint, "max_allocated_bytes": uint, - "output_type": tstr, + "max_input_bytes": uint, "max_result_bytes": uint, + "max_allocated_bytes": uint, "output_type": tstr, } invocation-v2-effect = { "schema": "echo.operation-invocation/v2", @@ -1341,7 +1349,7 @@ rules: | Row | Bytes | Fields, in order | | ---------------- | ----- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `SlotRow` | 104 | `class` u8 (node record `0`, attachment `1`, edge `2`, range key `3`); `owner` u8 (attachment: node `0`, edge `1`); `plane` u8 (attachment: alpha `0`, beta `1`); 5 padding bytes; `warp_id`; `local_id` (the node, edge, attachment owner, or range source); `edge_type` (range key only) | -| `ReadRow` | 192 | `slot` (`SlotRow`); `outcome` u8 (present `0`, absent `1`, range `2`, portal `3`); 7 padding bytes; `read_type`, 32 bytes: the type that authorizes the read, the record's node type or atom `type_id` when present and the type the read named when absent, zero for a range or a portal; `subject`, 32 bytes: the value digest when present or a portal, zero otherwise; `pairs` (`Range` into the pair section; range only) | +| `ReadRow` | 192 | `slot` (`SlotRow`); `outcome` u8 (present `0`, absent `1`, range `2`, portal `3`); 7 padding bytes; `read_type`, 32 bytes: the type that authorizes the read, the record's node type or atom `type_id` when present and the type the read named when absent, the owner's node type for a portal, and zero for a range; `subject`, 32 bytes: the value digest when present or a portal, zero otherwise; `pairs` (`Range` into the pair section; range only) | | `EdgePairRow` | 64 | `edge_local_id`; `target_local_id` | | `EffectRow` | 280 | `kind` u8 (`EnsureNode` `0`, `CreateNode` `1`, `ReplaceAtom` `2`, `EnsureEdge` `3`, `ReplaceEdge` `4`); 7 padding bytes; `key_warp_id`; `key_local_id`; `type_id` (node type, or `EnsureEdge`'s edge type); `atom_type_id`; `atom` (`Range` into the blob); `expected_digest` (`ReplaceAtom`); `from_local_id` (`EnsureEdge`); `to_local_id` (`EnsureEdge`, and `ReplaceEdge`'s new target); `expected_to_local_id` (`ReplaceEdge`) | | `DependencyRow` | 152 | `slot` (`SlotRow`); `kind` u8; `mode` u8; 6 padding bytes, which together with `slot` form the 112-byte descriptor; `compared` u8 (present `0`, absent `1`, not compared `2`); 7 padding bytes; `compared_digest` (present only) | @@ -1777,7 +1785,9 @@ Each behaviour below needs a test before implementation: | Evaluation-only proposal cost | The same evaluation at the same origin has the same proposal identity whether validation stops at the history check or runs to the end. | | Allocation ceiling profile | A program declaring more than `EFFECT_ALLOCATION_CEILING_V1` is refused at admission; a host that cannot provide the declared bytes fails the Tick as a host fault, never obstructing the Action. | | Range-type refusal recovery | An evaluation-time `FootprintLawViolation` for an undeclared range type recovers as `NoProposal` by re-evaluation, with the same read set. | -| Portal atom read | An atom read of a slot holding a portal records outcome `portal` with the portal's digest; it is refused unless the owner's type is in `portal_owner_types`. | +| Portal atom read | An atom read of a slot holding a portal records outcome `portal`, the owner's node type as `read_type`, and the portal's digest; it is refused unless that type is in `portal_owner_types`, decided from the row alone. | +| Inherited ceilings | An effect program declaring `max_input_bytes` or `max_result_bytes` above 65,536 is refused at admission; a longer result obstructs with `BudgetExceeded`, `NoProposal`. | +| No result projection | A V2 package carrying `application_result_projection` is refused at admission. | ## Open questions From 56896dd47c6b9d1497c7e61615280b4f20577aa8 Mon Sep 17 00:00:00 2001 From: James Ross Date: Fri, 16 Oct 2026 22:55:39 -0700 Subject: [PATCH 43/50] docs: authorize every read before its value reaches the program Round 34 on the authored-effects bridge: - The recording view authorizes each node, atom and portal read (its read_type against the law's read permissions, and max_reads) before the program sees the value. An out-of-law read obstructs evaluation at once with FootprintLawViolation and NoProposal, so a forbidden value can never shape an authored guard or a BudgetExceeded exit, which previously skipped step 7. Step 7 rechecks the recorded rows, as recovery does, and the NoProposal recovery row covers this case. - Tests now match the normative rules: max_reads counts read-set rows (typed absent probes count separately), and the allocation test no longer intersects with a host ceiling. Co-Authored-By: Claude Opus 5.5 --- .../authored-operation-effects.md | 28 +++++++++++-------- 1 file changed, 17 insertions(+), 11 deletions(-) diff --git a/docs/architecture/authored-operation-effects.md b/docs/architecture/authored-operation-effects.md index f6a47712..8df7315c 100644 --- a/docs/architecture/authored-operation-effects.md +++ b/docs/architecture/authored-operation-effects.md @@ -482,9 +482,13 @@ The view resolves each slot on demand from the basis, as law 1 requires. The contract never depends on a complete materialized graph: the read limit and the footprint law bound the slice. A range read beyond `max_range_edges` or the budget obstructs evaluation itself (`BudgetExceeded`, -`NoProposal`), since it would materialize data; any other read outside the -footprint law is found at validation step 7 (`FootprintLawViolation`, -`Proposed`, `NotDerived`). +`NoProposal`), since it would materialize data. Every other read is authorized +by the view before its value reaches the program: a node, atom, or portal read +whose type (as `read_type` records it) is outside the law's read permissions, +or that would take the read set past `max_reads`, obstructs evaluation at once +with `FootprintLawViolation`, posture `NoProposal`, and the program never sees +the value. So a forbidden value can never shape an evaluation obstruction, +whatever exit the program takes after the read. A read that may find nothing names the type it expects: a node read names a node type, and an atom read names an atom `type_id`. The view records the authorizing @@ -666,8 +670,9 @@ recorded obstruction: select its id rule, so this step skips it; it is `DanglingReference` at step 8, as an absent endpoint is at step 5; 7. the footprint law, now that every slot is known (`FootprintLawViolation`). - The witnessed read set must lie inside the law's read permissions, by the - type each read records. The target set and the resolved write set, with + The witnessed read set was authorized read by read during evaluation; this + step rechecks it from the recorded rows, as recovery does. The target set + and the resolved write set, with its range keys, must lie inside its write permissions. The counts must not exceed its maxima. Precondition and resolution slots (targets, endpoints, sources, and buckets that step 2 read) are implied by an effect, not read @@ -1314,7 +1319,7 @@ above likewise applies to every row recorded as `Scoped`. | Obstructed at validation (`DanglingReference`, `ContentIdentityConflict`, `DuplicateEffectTarget`, `FootprintLawViolation`, `IdentityLawViolation`, `SchemaViolation`, `UnreachableWarp`, `NodeMissing`, `PreconditionMismatch`, validation-read `BudgetExceeded`) | `Proposed`, `NotDerived` | re-evaluate; compare pre-decision evidence | effects bound; read-set digest; obstruction kind; no write-set or patch digest present | | Obstructed by `FootprintMismatch` (after derivation) | `Proposed`, `Derived` | re-evaluate; compare pre-decision evidence | effects bound; read-set digest; re-derived patch reproduces the mismatch | | `BudgetExceeded`, scheduler-assigned (Action-batch limit) | `Proposed`, `Derived` | accept reconstructed preparation; verify through the batch reconstruction | effects bound; read-set digest | -| `BudgetExceeded` during evaluation, an evaluation-time `FootprintLawViolation` (an undeclared range type), or an authored guard | `NoProposal` | re-evaluate; compare pre-decision evidence | re-evaluation, at the origin for a `Scoped` Action, again yields no proposal and the same obstruction; read-set digest | +| `BudgetExceeded` during evaluation, an evaluation-time `FootprintLawViolation` (an out-of-law read, an undeclared range type, or `max_reads` exceeded), or an authored guard | `NoProposal` | re-evaluate; compare pre-decision evidence | re-evaluation, at the origin for a `Scoped` Action, again yields no proposal and the same obstruction; read-set digest | | `BasisChanged`, whole-frontier (before evaluation, including unknown history) | `NoProposal` | admission evidence; recomputed basis posture; no re-evaluation | none; posture must be `NoProposal` with an empty read set and zero consumed budget | | `BasisChanged`, dependency-scoped (after evaluation, before validation) | `Proposed`, `NotDerived` | none today: current programs decide this before evaluation | re-evaluate at the origin; effects bound; read-set digest; recompute the written-slot set from replay operations, the complete dependency set (witnessed reads and effect preconditions), and the changed dependency | | Any outcome of a `NotBridge` entry | `NotBridge` | unchanged | none; its bridge row is all zero | @@ -1721,7 +1726,7 @@ Each behaviour below needs a test before implementation: | Witnessed reads | The receipt's read set equals the slots the view resolved, including for a program that reads and then obstructs. | | Negative reads | An Action that observed a node, atom, or edge set absent obstructs when a co-scheduled Action creates it, including a fresh edge of the observed source and type with a new edge id when the observed set was empty (outgoing-edge range key). | | Range metering | A range read over a node with more edges than the law's maximum, or than the remaining budget covers, obstructs with `BudgetExceeded` and `NoProposal` before copying any edge; an admissible range charges one step and 64 read bytes per edge. | -| Absent probes | A read that finds nothing records the node type it expected; a probe for a type outside `read_node_types` obstructs at step 7 with `FootprintLawViolation`, and recovery reproduces the decision from the read set alone. | +| Absent probes | A read that finds nothing records the type it named in `read_type`; a probe for a type outside the law's read permissions obstructs evaluation at once with `FootprintLawViolation`, `NoProposal`, and recovery reproduces the decision from the read set alone. | | Edge-read basis | An Action whose package's footprint law admits a range read commits only onto its exact evaluation basis, including when this invocation reads no edge. | | Final outcomes | A rejected Action's outcome, receipt, and attribution are unchanged after a later submission of the same edit succeeds. | | Duplicate resubmission | Resubmitting a decided Action's identical content returns `Duplicate` with the original outcome and commits no Tick. | @@ -1735,7 +1740,7 @@ Each behaviour below needs a test before implementation: | Invocation version | An effect invocation (`invocation-v2-effect`) is admitted only for an effect-program package, and a V1 invocation only for its own fixed profile. | | Input bounds | An effect invocation whose `application_input_bytes` is not canonical CBOR, or is longer than `max_input_bytes`, is refused at admission; a well-formed one of the wrong shape obstructs evaluation with `InputRejected`. | | Outcome record | A bridge obstruction, including an authored one such as `BasisNotCanonical`, is retained as `EOACT004` with the same coordinate as its receipt; fixed-profile Actions still write `EOACT003`, and mixed histories recover. | -| ------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | +| ------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | Pending recovery | An Action lost with its Tick before the WAL record is recovered as the same submission, not a new one. | | Ensure resolution | An `EnsureNode` whose identical target already exists is a read, not a write, and the footprint law checks the write set without it; its target read is charged at step 2. | | Absent endpoint history | An Action whose package admits edge writes keeps the whole-frontier rule, so an endpoint absent at its evaluation basis and created by a later Tick never lets it commit onto that later basis; it obstructs with whole-frontier `BasisChanged`. | @@ -1765,12 +1770,12 @@ Each behaviour below needs a test before implementation: | Authored obstruction | An authored obstruction's `authored_id` follows the specified recipe from its code and canonical detail, and recovery re-derives the same identity. | | Candidate and composite patches | In a Tick with two accepted bridge Actions, each row's `patch_digest` is its own candidate patch, and the replay patch is their batch composition; recovery verifies both. | | Bridge decision coordinate | Every `EOACT004` variant carries the 152-byte decision coordinate, and recovery refuses one that does not name the sealed Tick and member index of its receipt entry. | -| Footprint maxima | `max_reads` and `max_writes` count distinct slots after deduplication and Ensure resolution; an identical `EnsureNode` adds no write, and one range read counts as one read. | +| Footprint maxima | `max_reads` counts read-set rows, one per slot, or one per named type for an absent slot probed under several types, so two typed probes of one absent slot exceed `max_reads = 1`; `max_writes` counts distinct resolved write slots after Ensure resolution, so an identical `EnsureNode` adds no write; one range read is one row. | | Effect profiles | A V2 package whose `target_profile_identity` or `application_basis_schema_identity` is not the specified profile digest is refused at admission. | | Effect basis binding | An effect invocation whose `application_basis_value_identity` is not the digest of its own `application_input_bytes` is refused at admission. | | Outcome row projection | An `EOACT004` record whose `OutcomeRow` differs from the projection of its receipt-bound `BridgeEntryRow` is refused by recovery. | -| ----------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| Allocation ceiling | An effect program map without `max_allocated_bytes` is refused; evaluation uses its intersection with the host ceiling. | +| ----------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| Allocation ceiling | An effect program map without `max_allocated_bytes` is refused; evaluation is bounded by that declaration alone, never by a host's own ceiling. | | Inherited identities | A V2 package whose input, result, obstruction, interpretation, or evaluation-basis identity differs from its specified profile digest is refused at admission. | | Evaluation read charges | Node, absent, and atom reads charge one step and 64 `read_bytes`, plus the atom length when present; a read the budget cannot cover obstructs before reading, uncharged. | | Read types | A present read is authorized by the record's actual type in `read_type`, an absent one by the type it named; recovery reproduces both decisions from the read set. | @@ -1788,6 +1793,7 @@ Each behaviour below needs a test before implementation: | Portal atom read | An atom read of a slot holding a portal records outcome `portal`, the owner's node type as `read_type`, and the portal's digest; it is refused unless that type is in `portal_owner_types`, decided from the row alone. | | Inherited ceilings | An effect program declaring `max_input_bytes` or `max_result_bytes` above 65,536 is refused at admission; a longer result obstructs with `BudgetExceeded`, `NoProposal`. | | No result projection | A V2 package carrying `application_result_projection` is refused at admission. | +| Reads authorized before use | A program that reads a node outside its law, then fails an authored guard or exhausts its budget, obstructs with `FootprintLawViolation` at that read; the forbidden value never reaches its obstruction or `authored_id`. | ## Open questions From 5ce3a98e592820ec818a20114b7c62ddd7540f89 Mon Sep 17 00:00:00 2001 From: James Ross Date: Fri, 16 Oct 2026 23:09:21 -0700 Subject: [PATCH 44/50] docs: reads resolve their chain first; result capacity; content hash charge; minimum budget Round 35 on the authored-effects bridge: - A read in a descended warp resolves and authorizes its root-to-target chain before its value is visible: portal charges, owner types, and UnreachableWarp, all at evaluation with NoProposal. No inaccessible value can shape an evaluation exit. - The arena's result capacity comes from max_result_bytes as well as max_allocated_bytes, since a result can carry bytes the program never allocated. - Adopting an existing content node hashes and charges its atom at step 2, before the step-6 identity law needs it. - An effect program's minimum budget is fixed at one step and zero bytes, applied at admission exactly as V1 applies minimum_budget(). - Four tests are added. Co-Authored-By: Claude Opus 5.5 --- .../authored-operation-effects.md | 98 +++++++++++-------- 1 file changed, 56 insertions(+), 42 deletions(-) diff --git a/docs/architecture/authored-operation-effects.md b/docs/architecture/authored-operation-effects.md index 8df7315c..357bd792 100644 --- a/docs/architecture/authored-operation-effects.md +++ b/docs/architecture/authored-operation-effects.md @@ -487,8 +487,13 @@ by the view before its value reaches the program: a node, atom, or portal read whose type (as `read_type` records it) is outside the law's read permissions, or that would take the read set past `max_reads`, obstructs evaluation at once with `FootprintLawViolation`, posture `NoProposal`, and the program never sees -the value. So a forbidden value can never shape an evaluation obstruction, -whatever exit the program takes after the read. +the value. A read of a key in a descended warp first resolves its +root-to-target chain, as validation step 1 does for effect keys: each portal +crossed is charged, its owner authorized against `portal_owner_types`, and an +unreachable warp obstructs evaluation with `UnreachableWarp`, posture +`NoProposal`, before anything in it is read. So a forbidden or unreachable +value can never shape an evaluation obstruction, whatever exit the program takes +after the read. A read that may find nothing names the type it expects: a node read names a node type, and an atom read names an atom `type_id`. The view records the authorizing @@ -634,24 +639,23 @@ recorded obstruction: alpha atom slot, and the target edge record of each `EnsureEdge`, present or absent, which decides whether the effect writes or only confirms; for an `EnsureNode` whose content-typed target exists, a scan of that node's whole - outgoing bucket, which the identity law needs; for every node the proposal - creates, its outgoing bucket's length, which must be zero, since a node - cannot be created over edges a non-bridge writer left under its id - (`PreconditionMismatch` otherwise); - each `ReplaceAtom`'s node record; each `ReplaceEdge`'s edge record; the - source node record of every `EnsureEdge` and `ReplaceEdge`, whose type - selects the edge-id rule, and, when that source is mutable, its whole - outgoing bucket for the alias check; and the target node record of a creating - `EnsureEdge` or a `ReplaceEdge`, each unless the proposal creates that node. - Charges follow - [Validation reads](#basis-budget-retries). Failures are `NodeMissing` for - a `ReplaceAtom`'s missing node, `PreconditionMismatch` for a missing edge, - or `BudgetExceeded`; whether a present target matches is decided at - step 8, and a missing endpoint is reported as `DanglingReference` there. - Every record read here is a dependency (kind `4`): `ValueDigest` when - present and `Absence` when absent, so step 3 catches a target or endpoint - that an intervening Tick created, changed, or removed. After this step the - complete write set is known; + outgoing bucket and a hash of its atom, which the identity law needs; for + every node the proposal creates, its outgoing bucket's length, which must be + zero, since a node cannot be created over edges a non-bridge writer left + under its id (`PreconditionMismatch` otherwise); each `ReplaceAtom`'s node + record; each `ReplaceEdge`'s edge record; the source node record of every + `EnsureEdge` and `ReplaceEdge`, whose type selects the edge-id rule, and, + when that source is mutable, its whole outgoing bucket for the alias check; + and the target node record of a creating `EnsureEdge` or a `ReplaceEdge`, + each unless the proposal creates that node. Charges follow [Validation + reads](#basis-budget-retries). Failures are `NodeMissing` for a + `ReplaceAtom`'s missing node, `PreconditionMismatch` for a missing edge, or + `BudgetExceeded`; whether a present target matches is decided at step 8, and + a missing endpoint is reported as `DanglingReference` there. Every record + read here is a dependency (kind `4`): `ValueDigest` when present and + `Absence` when absent, so step 3 catches a target or endpoint that an + intervening Tick created, changed, or removed. After this step the complete + write set is known; 3. the dependency-scoped history check, for a scoped Action (`BasisChanged`); 4. one effect per slot (`DuplicateEffectTarget`); @@ -845,24 +849,23 @@ held; this design validates only what the proposal adds or retargets. A read the remaining budget cannot cover obstructs with `BudgetExceeded`, posture `NoProposal`, before anything is read; its charge is not added to the consumed budget, which records every charge made before it. -- **Validation reads are charged.** Every slot validation reads is charged - once per Action, before it is read, at the first step that reads it, and - never again: a node record or an atom's presence costs one step and 64 - `read_bytes`, an edge record one step and 128 `read_bytes`, and hashing an - atom's value adds its byte length. A slot the program's view already read - is not charged again, since its value is known. A bucket scan reads every - edge in the bucket as a full edge record, one step and 128 `read_bytes` - each, and is charged for the whole bucket before any edge is inspected; a - bucket length read alone costs one step and 64 `read_bytes`. A scan the - remaining budget cannot cover obstructs before inspecting anything, and - its charge is not added. So step 1 charges portals - and their owners' records, for effect and read keys alike, step 2 charges - each resolved record, including every Ensure and Create target with its - alpha slot, every edge source, and the outgoing bucket of an existing - content node, per edge, and step 8 charges only the atoms it hashes, - for `ReplaceAtom`'s expected digest and an existing `EnsureNode`'s - identical-content check. Charges come from the - budget left after evaluation. A read the remaining budget cannot cover +- **Validation reads are charged.** Every slot validation reads is charged once + per Action, before it is read, at the first step that reads it, and never + again: a node record or an atom's presence costs one step and 64 `read_bytes`, + an edge record one step and 128 `read_bytes`, and hashing an atom's value adds + its byte length. A slot the program's view already read is not charged again, + since its value is known. A bucket scan reads every edge in the bucket as a + full edge record, one step and 128 `read_bytes` each, and is charged for the + whole bucket before any edge is inspected; a bucket length read alone costs + one step and 64 `read_bytes`. A scan the remaining budget cannot cover + obstructs before inspecting anything, and its charge is not added. So step 1 + charges portals and their owners' records, for effect and read keys alike, + step 2 charges each resolved record, including every Ensure and Create target + with its alpha slot, every edge source, and the outgoing bucket of an existing + content node, per edge, with that node's atom, hashed at step 2 because the + identity law at step 6 recomputes the node's id from it; and step 8 charges + only the atom it hashes for `ReplaceAtom`'s expected digest. Charges come from + the budget left after evaluation. A read the remaining budget cannot cover obstructs with `BudgetExceeded`, posture `Proposed`, `NotDerived`, at that step. The receipt's consumed budget is evaluation and validation together; `allocated_bytes` is evaluation alone. @@ -915,7 +918,12 @@ decision: Consequences: - **Admission** is unchanged: the bridge runs inside scheduler-owned Tick - construction after ordinary admission, and adds checks; it removes none. + construction after ordinary admission, and adds checks; it removes none. V1 + admission refuses a package ceiling or delegated budget below the program's + `minimum_budget()`. An effect program's minimum budget is fixed: one step and + zero bytes on each byte axis, the least that can evaluate anything. Admission + applies it exactly as V1 does, and everything above it is decided by + evaluation (`BudgetExceeded`), never by admission. - **Attribution** is unchanged: an Action batch already attributes each candidate to its own receipt entry. The proposal identity and digests below ride in that entry; the attribution section still names only submissions. @@ -1500,9 +1508,11 @@ follow the canonical effect order in [EffectProposal](#effectproposal). images. Hashing, the WAL append, and receipt construction read those bytes directly; nothing is copied into intermediate structures. - **Capacity fixed before evaluation.** Each region's size is a pure function of - the selected candidates' declared footprint-law maxima, delegated budgets, and - the program's `max_allocated_bytes`, which bounds the result bytes the blob - must also hold, sized per candidate from its own declared maxima, never from + the selected candidates' declared footprint-law maxima, delegated budgets, the + program's `max_allocated_bytes`, and its `max_result_bytes`, which bounds the + result bytes the blob must also hold independently of allocation (a result + can carry bytes the program never allocated, such as its input). Each is + sized per candidate from its own declared maxima, never from the 128-operation batch limit, so a candidate whose patch exceeds that limit is still derived in place and then rejected with `BudgetExceeded`. The 64-candidate limit bounds how many such candidates a Tick selects. The arena @@ -1794,6 +1804,10 @@ Each behaviour below needs a test before implementation: | Inherited ceilings | An effect program declaring `max_input_bytes` or `max_result_bytes` above 65,536 is refused at admission; a longer result obstructs with `BudgetExceeded`, `NoProposal`. | | No result projection | A V2 package carrying `application_result_projection` is refused at admission. | | Reads authorized before use | A program that reads a node outside its law, then fails an authored guard or exhausts its budget, obstructs with `FootprintLawViolation` at that read; the forbidden value never reaches its obstruction or `authored_id`. | +| Result capacity | A program with a small `max_allocated_bytes` returning input bytes up to `max_result_bytes` fits the pre-sized arena; evaluation never grows it. | +| Read reachability | A read through an unreachable warp, or a chain with a forbidden portal owner, obstructs evaluation at that read with `UnreachableWarp` or `FootprintLawViolation`, `NoProposal`, before the value is visible. | +| Content hash charge | Adopting an existing content node charges its atom hash at step 2; a budget that cannot cover it obstructs there, before the identity law runs. | +| Effect minimum budget | An effect package ceiling or delegated budget below one step is refused at admission; any budget of at least one step is admitted, and shortfalls are `BudgetExceeded` at evaluation. | ## Open questions From 5d2850aaca26f3255716d11a8b1c88c81654b70c Mon Sep 17 00:00:00 2001 From: James Ross Date: Fri, 16 Oct 2026 23:19:10 -0700 Subject: [PATCH 45/50] docs: footprint-law ceilings; ReplaceAtom never removes a portal; read recovery Round 36 on the authored-effects bridge: - Footprint laws have effect-profile ceilings, checked at admission: max_reads 4,096, max_writes 256, max_range_edges 65,536. The arena is sized from each maximum clamped to what the delegated budget can pay for, so no law can overflow sizing or force host faults. - ReplaceAtom requires its alpha slot to hold an atom. A portal there is PreconditionMismatch before derivation, never a portal silently removed by the patch. - An evaluation-time UnreachableWarp (a read through an unreachable warp) joins the NoProposal recovery row. - output_type must be nonempty UTF-8 of at most 1,024 bytes, as V1 bounds result coordinates. - The absent-atom-probe test now requires evaluation-time refusal. Co-Authored-By: Claude Opus 5.5 --- .../authored-operation-effects.md | 29 ++++++++++++++----- 1 file changed, 21 insertions(+), 8 deletions(-) diff --git a/docs/architecture/authored-operation-effects.md b/docs/architecture/authored-operation-effects.md index 357bd792..c61c593c 100644 --- a/docs/architecture/authored-operation-effects.md +++ b/docs/architecture/authored-operation-effects.md @@ -305,9 +305,12 @@ Rules: - `EnsureEdge`: the edge is absent, or present with identical endpoints and type; - `ReplaceAtom`: the node record exists, else `NodeMissing` (as today's - compare-and-set reports), and its atom has the expected digest, else - `PreconditionMismatch`. An orphan atom without its record therefore - never satisfies a `ReplaceAtom`; + compare-and-set reports), and its alpha slot holds an atom + (`AttachmentValue::Atom`) with the expected digest, else + `PreconditionMismatch`. A slot holding a portal is therefore + `PreconditionMismatch`, decided before any patch is derived, never a + portal removed by the patch. An orphan atom without its record never + satisfies a `ReplaceAtom` either; - `ReplaceEdge`: the edge exists with the expected target; - a creating `EnsureEdge` and every `ReplaceEdge`: each endpoint the edge will name (`from` and `to`, or the retargeted edge's source and @@ -551,8 +554,10 @@ encoding](#set-encoding) gives, with these entries: `read_node_types` and `write_node_types` (sets of node type ids), `atom_types` (the atom `type_id`s it may read or write), `range_edge_types` (edge types it may read as outgoing ranges; empty makes it eligible for scoped validity), `write_edge_types`, the -unsigned counts `max_reads`, `max_writes`, and `max_range_edges` (per range -read), and `portal_owner_types` (node types whose alpha portals an effect or +unsigned counts `max_reads` (at most 4,096), `max_writes` (at most 256), and +`max_range_edges` (per range read, at most 65,536), which admission checks +against these effect-profile ceilings so no law can overflow the arena's sizing +arithmetic, and `portal_owner_types` (node types whose alpha portals an effect or read in a descended warp may cross; empty confines the package to its root warp). Each portal on a validated chain must be owned by a node of one of these types, or validation obstructs with `FootprintLawViolation`. The bridge does not @@ -1018,6 +1023,8 @@ Consequences: sizes the scheduler's evaluation arena; - `output_type`, the compiler-declared output type coordinate of the program's application result, which `result_identity` hashes. + Admission requires it to be nonempty UTF-8 of at most 1,024 bytes, + the bound V1 applies to result coordinates. The graph schema is the executable graph contract, a different artifact from the application's own schema declaration. The closure's @@ -1327,7 +1334,7 @@ above likewise applies to every row recorded as `Scoped`. | Obstructed at validation (`DanglingReference`, `ContentIdentityConflict`, `DuplicateEffectTarget`, `FootprintLawViolation`, `IdentityLawViolation`, `SchemaViolation`, `UnreachableWarp`, `NodeMissing`, `PreconditionMismatch`, validation-read `BudgetExceeded`) | `Proposed`, `NotDerived` | re-evaluate; compare pre-decision evidence | effects bound; read-set digest; obstruction kind; no write-set or patch digest present | | Obstructed by `FootprintMismatch` (after derivation) | `Proposed`, `Derived` | re-evaluate; compare pre-decision evidence | effects bound; read-set digest; re-derived patch reproduces the mismatch | | `BudgetExceeded`, scheduler-assigned (Action-batch limit) | `Proposed`, `Derived` | accept reconstructed preparation; verify through the batch reconstruction | effects bound; read-set digest | -| `BudgetExceeded` during evaluation, an evaluation-time `FootprintLawViolation` (an out-of-law read, an undeclared range type, or `max_reads` exceeded), or an authored guard | `NoProposal` | re-evaluate; compare pre-decision evidence | re-evaluation, at the origin for a `Scoped` Action, again yields no proposal and the same obstruction; read-set digest | +| `BudgetExceeded` during evaluation, an evaluation-time `FootprintLawViolation` (an out-of-law read, an undeclared range type, or `max_reads` exceeded), an evaluation-time `UnreachableWarp` (a read through an unreachable warp), or an authored guard | `NoProposal` | re-evaluate; compare pre-decision evidence | re-evaluation, at the origin for a `Scoped` Action, again yields no proposal and the same obstruction; read-set digest | | `BasisChanged`, whole-frontier (before evaluation, including unknown history) | `NoProposal` | admission evidence; recomputed basis posture; no re-evaluation | none; posture must be `NoProposal` with an empty read set and zero consumed budget | | `BasisChanged`, dependency-scoped (after evaluation, before validation) | `Proposed`, `NotDerived` | none today: current programs decide this before evaluation | re-evaluate at the origin; effects bound; read-set digest; recompute the written-slot set from replay operations, the complete dependency set (witnessed reads and effect preconditions), and the changed dependency | | Any outcome of a `NotBridge` entry | `NotBridge` | unchanged | none; its bridge row is all zero | @@ -1508,7 +1515,9 @@ follow the canonical effect order in [EffectProposal](#effectproposal). images. Hashing, the WAL append, and receipt construction read those bytes directly; nothing is copied into intermediate structures. - **Capacity fixed before evaluation.** Each region's size is a pure function of - the selected candidates' declared footprint-law maxima, delegated budgets, the + the selected candidates' footprint-law maxima, each clamped to what its + delegated budget can pay for (a read costs at least 64 `read_bytes`, a range + edge 64, a write 64 `write_bytes`), delegated budgets, the program's `max_allocated_bytes`, and its `max_result_bytes`, which bounds the result bytes the blob must also hold independently of allocation (a result can carry bytes the program never allocated, such as its input). Each is @@ -1761,7 +1770,7 @@ Each behaviour below needs a test before implementation: | Existing edge identity | A `ReplaceEdge` of a basis edge whose id is not its slot id under `ContentAddressV1` obstructs with `IdentityLawViolation`. | | Unreachable records | A keyed read of a record that a prior Tick made unreachable resolves identically live and after recovery, because the record stays until a patch deletes it. | | Alpha slot dependency | A scoped `ReplaceAtom` whose owner node record changed in an intervening Tick, with its atom unchanged, obstructs with `BasisChanged` through the resolved node-record dependency. | -| Absent atom probe | An absent atom read records its expected atom `type_id`; a probe for a type outside `atom_types` obstructs at step 7 with `FootprintLawViolation`. | +| Absent atom probe | An absent atom read records its expected atom `type_id`; a probe for a type outside `atom_types` obstructs evaluation at once with `FootprintLawViolation`, `NoProposal`, before the program sees the result. | | Content-node adoption | An `EnsureNode` of an existing content node is adopted only when that node's record, atom, and outgoing edges recompute to its id; a node stored under another id obstructs with `IdentityLawViolation`. | | Schema shape | A footprint law or schema with an unknown, missing, or reordered key, or a node-type entry of any other shape, is refused at admission. | | Descended read reachability | A scoped Action that only reads a descended warp records that warp's portals and owners as dependencies, enforces `portal_owner_types`, and obstructs with `BasisChanged` when an intervening Tick changes them. | @@ -1808,6 +1817,10 @@ Each behaviour below needs a test before implementation: | Read reachability | A read through an unreachable warp, or a chain with a forbidden portal owner, obstructs evaluation at that read with `UnreachableWarp` or `FootprintLawViolation`, `NoProposal`, before the value is visible. | | Content hash charge | Adopting an existing content node charges its atom hash at step 2; a budget that cannot cover it obstructs there, before the identity law runs. | | Effect minimum budget | An effect package ceiling or delegated budget below one step is refused at admission; any budget of at least one step is admitted, and shortfalls are `BudgetExceeded` at evaluation. | +| Unreachable read recovery | A read through an unreachable warp recovers as an evaluation-time `UnreachableWarp`, `NoProposal`, by re-evaluation at the origin with the same read set. | +| Footprint law ceilings | A footprint law with `max_reads` above 4,096, `max_writes` above 256, or `max_range_edges` above 65,536 is refused at admission; the arena is sized from the maxima clamped by the delegated budget. | +| ReplaceAtom over a portal | A `ReplaceAtom` whose alpha slot holds a portal obstructs with `PreconditionMismatch` before derivation, even when its expected digest names the portal. | +| Output type coordinate | An effect program whose `output_type` is empty or longer than 1,024 bytes is refused at admission. | ## Open questions From 510546eb81f629733417e06dbc1c3a1824437c36 Mon Sep 17 00:00:00 2001 From: James Ross Date: Fri, 16 Oct 2026 23:30:58 -0700 Subject: [PATCH 46/50] docs: effect budget ceilings, chains charged once, a per-effect permission table Round 37 on the authored-effects bridge: - Effect-profile budget ceilings at admission (1,048,576 steps, 64 MiB read, 16 MiB write) and at most 1,024 effects per proposal, counted at emission. Every budget- or count-sized arena region is now bounded, including against idempotent Ensures that write nothing. - A portal chain the recording view resolved for a read is not charged again at step 1, under the once-per-Action rule. - Step 7 gives an exact per-effect permission table for writes and their implied reads. Present implied node records need read_node_types, so an atom-only scoped Action needs no write_node_types. - The portal-chain test records effect-chain portals as footprint and dependency entries, not witnessed reads. - Three tests are added. Co-Authored-By: Claude Opus 5.5 --- .../authored-operation-effects.md | 38 +++++++++++++++---- 1 file changed, 30 insertions(+), 8 deletions(-) diff --git a/docs/architecture/authored-operation-effects.md b/docs/architecture/authored-operation-effects.md index c61c593c..6b756d20 100644 --- a/docs/architecture/authored-operation-effects.md +++ b/docs/architecture/authored-operation-effects.md @@ -683,11 +683,21 @@ recorded obstruction: step rechecks it from the recorded rows, as recovery does. The target set and the resolved write set, with its range keys, must lie inside its write permissions. The counts must not - exceed its maxima. Precondition and resolution slots (targets, endpoints, - sources, and buckets that step 2 read) are implied by an effect, not read - by the program. Each is authorized by the write permission of the effect - that implies it, never by `read_node_types`, so an absent endpoint needs - no expected type: it passes here and is `DanglingReference` at step 8; + exceed its maxima. Each effect is permitted, and the slots it implies are + authorized, exactly as this table gives; an implied slot found absent needs + no type and passes here (an absent endpoint is `DanglingReference` at step + 8): + + | Effect | Its write requires | Each present implied record requires | + | -------------------------- | ---------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------ | + | `EnsureNode`, `CreateNode` | node type in `write_node_types`; atom type in `atom_types` | the existing target's node type in `read_node_types`; its atom type in `atom_types` | + | `ReplaceAtom` | new atom type in `atom_types` | owner node type in `read_node_types`; current atom type in `atom_types` | + | `EnsureEdge` | edge type in `write_edge_types` | source and target node types in `read_node_types`; bucket edges' types in `write_edge_types` or `range_edge_types` | + | `ReplaceEdge` | edge type in `write_edge_types` | source, old and new target node types in `read_node_types`; bucket edges as for `EnsureEdge` | + + So an Action that only replaces atoms of existing nodes needs no + `write_node_types`, as dependency-scoped validity requires; + 8. the remaining effect preconditions, effect by effect in canonical order, and within an effect in the order its precondition list gives (`PreconditionMismatch`, `ContentIdentityConflict`, `DanglingReference`, @@ -828,7 +838,14 @@ held; this design validates only what the proposal adds or retargets. replaced is therefore a footprint conflict, never a `PreconditionMismatch`. - **Budget.** The delegated budget stays `EchoOperationBudgetV1`, with its three - axes: steps, read bytes, and write bytes. Evaluation is metered as + axes: steps, read bytes, and write bytes. For an effect program, admission + refuses a package ceiling or delegated budget above the effect profile's + fixed `EFFECT_BUDGET_CEILING_V1`: 1,048,576 steps, 64 MiB of `read_bytes`, and + 16 MiB of `write_bytes`. A proposal also holds at most 1,024 effects + (`EFFECT_COUNT_CEILING_V1`), counted at emission, whether or not they write; + the 1,025th obstructs evaluation with `BudgetExceeded`, posture `NoProposal`. + Every region the arena sizes from a budget or a count is therefore bounded, + and sizing arithmetic cannot overflow. Evaluation is metered as `edict_pure` already meters it. Each effect is charged when emitted, whatever validation later resolves: one step, plus `write_bytes` of 64 and the atom's byte length for `EnsureNode`, `CreateNode`, and `ReplaceAtom` (the charge @@ -864,7 +881,9 @@ held; this design validates only what the proposal adds or retargets. whole bucket before any edge is inspected; a bucket length read alone costs one step and 64 `read_bytes`. A scan the remaining budget cannot cover obstructs before inspecting anything, and its charge is not added. So step 1 - charges portals and their owners' records, for effect and read keys alike, + charges the portals and owners' records of effect-key chains the view did + not already walk; a chain the view resolved for a read was charged then and is + reused without another charge, step 2 charges each resolved record, including every Ensure and Create target with its alpha slot, every edge source, and the outgoing bucket of an existing content node, per edge, with that node's atom, hashed at step 2 because the @@ -1740,7 +1759,7 @@ Each behaviour below needs a test before implementation: | Shared structure | Two Actions that `Ensure` the same node already present in the basis both commit in one Tick. | | Same-Tick identical create | Two Actions that both `Ensure` the same node absent at the commit parent: the later obstructs with `RejectedFootprintConflict` instead of reading the earlier one's write. | | Referential integrity | An edge to a node neither in the basis nor created by the proposal obstructs with `DanglingReference`; a pre-existing dangling edge elsewhere in the basis does not obstruct an unrelated proposal. | -| Portal chain | An effect in a descended warp records every portal on its root-to-target chain as a read; a co-scheduled portal retarget conflicts with it, and a portal changed and restored between origin and commit parent obstructs a scoped Action with `BasisChanged`. | +| Portal chain | An effect in a descended warp records every portal on its root-to-target chain as a footprint and dependency entry, never as a witnessed read; a co-scheduled portal retarget conflicts with it, and a portal changed and restored between origin and commit parent obstructs a scoped Action with `BasisChanged`. | | Visibility | No published coordinate shows a retargeted head without every node and edge the proposal introduced; pre-existing structure the head reaches is not validated. | | Witnessed reads | The receipt's read set equals the slots the view resolved, including for a program that reads and then obstructs. | | Negative reads | An Action that observed a node, atom, or edge set absent obstructs when a co-scheduled Action creates it, including a fresh edge of the observed source and type with a new edge id when the observed set was empty (outgoing-edge range key). | @@ -1821,6 +1840,9 @@ Each behaviour below needs a test before implementation: | Footprint law ceilings | A footprint law with `max_reads` above 4,096, `max_writes` above 256, or `max_range_edges` above 65,536 is refused at admission; the arena is sized from the maxima clamped by the delegated budget. | | ReplaceAtom over a portal | A `ReplaceAtom` whose alpha slot holds a portal obstructs with `PreconditionMismatch` before derivation, even when its expected digest names the portal. | | Output type coordinate | An effect program whose `output_type` is empty or longer than 1,024 bytes is refused at admission. | +| Effect budget ceilings | A package ceiling or delegated budget above `EFFECT_BUDGET_CEILING_V1` is refused at admission; a program emitting a 1,025th effect, even an identical `Ensure`, obstructs with `BudgetExceeded`, `NoProposal`. | +| Chain charged once | A read whose portal chain the view resolved and charged is not charged again at step 1; a budget covering one traversal suffices. | +| Implied-read permissions | A `ReplaceAtom` whose owner's node type is in `read_node_types` but not `write_node_types` passes the footprint law; one whose owner type is in neither obstructs with `FootprintLawViolation`. | ## Open questions From 58ff64cdc3b2abcbd6d62c7adaad27dc0b2a84f7 Mon Sep 17 00:00:00 2001 From: James Ross Date: Sat, 17 Oct 2026 10:50:08 -0700 Subject: [PATCH 47/50] docs: content mismatch before identity; resolve what the permission table checks Round 38 on the authored-effects bridge: - An existing content node's type, atom and outgoing edges are compared with the proposal at step 2. A mismatch is ContentIdentityConflict there, so the step-6 identity law only sees matching content and the Ensure contract's outcome holds. - The permission table authorizes every edge a content-node bucket scan reads, by type, against write_edge_types or range_edge_types. - ReplaceAtom's current alpha value is read, hashed and charged at step 2, so step 7 can check its type; step 8 compares it without a second charge. - ReplaceEdge no longer requires the old target's type: that node is never read. - Three tests are added. Co-Authored-By: Claude Opus 5.5 --- .../authored-operation-effects.md | 55 +++++++++++-------- 1 file changed, 32 insertions(+), 23 deletions(-) diff --git a/docs/architecture/authored-operation-effects.md b/docs/architecture/authored-operation-effects.md index 6b756d20..f993e5f9 100644 --- a/docs/architecture/authored-operation-effects.md +++ b/docs/architecture/authored-operation-effects.md @@ -644,21 +644,26 @@ recorded obstruction: alpha atom slot, and the target edge record of each `EnsureEdge`, present or absent, which decides whether the effect writes or only confirms; for an `EnsureNode` whose content-typed target exists, a scan of that node's whole - outgoing bucket and a hash of its atom, which the identity law needs; for - every node the proposal creates, its outgoing bucket's length, which must be - zero, since a node cannot be created over edges a non-bridge writer left - under its id (`PreconditionMismatch` otherwise); each `ReplaceAtom`'s node - record; each `ReplaceEdge`'s edge record; the source node record of every - `EnsureEdge` and `ReplaceEdge`, whose type selects the edge-id rule, and, - when that source is mutable, its whole outgoing bucket for the alias check; - and the target node record of a creating `EnsureEdge` or a `ReplaceEdge`, - each unless the proposal creates that node. Charges follow [Validation - reads](#basis-budget-retries). Failures are `NodeMissing` for a - `ReplaceAtom`'s missing node, `PreconditionMismatch` for a missing edge, or - `BudgetExceeded`; whether a present target matches is decided at step 8, and - a missing endpoint is reported as `DanglingReference` there. Every record - read here is a dependency (kind `4`): `ValueDigest` when present and - `Absence` when absent, so step 3 catches a target or endpoint that an + outgoing bucket and a hash of its atom. Those are compared with the proposal + here: an existing node whose type, atom, or outgoing edges differ from what + the proposal gives it is `ContentIdentityConflict` at this step, so the + identity law at step 6 sees only content that matches. For every node the + proposal creates, its outgoing bucket's length, which must be zero, since a + node cannot be created over edges a non-bridge writer left under its id + (`PreconditionMismatch` otherwise); each `ReplaceAtom`'s node record and + current alpha value, hashed so its type and digest are known before the + footprint law; each `ReplaceEdge`'s edge record; the source node record of + every `EnsureEdge` and `ReplaceEdge`, whose type selects the edge-id rule, + and, when that source is mutable, its whole outgoing bucket for the alias + check; and the target node record of a creating `EnsureEdge` or a + `ReplaceEdge`, each unless the proposal creates that node. Charges follow + [Validation reads](#basis-budget-retries). Failures are `NodeMissing` for a + `ReplaceAtom`'s missing node, `PreconditionMismatch` for a missing edge, + `ContentIdentityConflict` for an existing content node that differs from the + proposal, or `BudgetExceeded`; whether a present target matches is decided at + step 8, and a missing endpoint is reported as `DanglingReference` there. + Every record read here is a dependency (kind `4`): `ValueDigest` when present + and `Absence` when absent, so step 3 catches a target or endpoint that an intervening Tick created, changed, or removed. After this step the complete write set is known; 3. the dependency-scoped history check, for a scoped Action @@ -688,12 +693,12 @@ recorded obstruction: no type and passes here (an absent endpoint is `DanglingReference` at step 8): - | Effect | Its write requires | Each present implied record requires | - | -------------------------- | ---------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------ | - | `EnsureNode`, `CreateNode` | node type in `write_node_types`; atom type in `atom_types` | the existing target's node type in `read_node_types`; its atom type in `atom_types` | - | `ReplaceAtom` | new atom type in `atom_types` | owner node type in `read_node_types`; current atom type in `atom_types` | - | `EnsureEdge` | edge type in `write_edge_types` | source and target node types in `read_node_types`; bucket edges' types in `write_edge_types` or `range_edge_types` | - | `ReplaceEdge` | edge type in `write_edge_types` | source, old and new target node types in `read_node_types`; bucket edges as for `EnsureEdge` | + | Effect | Its write requires | Each present implied record requires | + | -------------------------- | ---------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------- | + | `EnsureNode`, `CreateNode` | node type in `write_node_types`; atom type in `atom_types` | the existing target's node type in `read_node_types`; its atom type in `atom_types`; each scanned bucket edge's type in `write_edge_types` or `range_edge_types` | + | `ReplaceAtom` | new atom type in `atom_types` | owner node type in `read_node_types`; current atom type in `atom_types` | + | `EnsureEdge` | edge type in `write_edge_types` | source and target node types in `read_node_types`; bucket edges' types in `write_edge_types` or `range_edge_types` | + | `ReplaceEdge` | edge type in `write_edge_types` | source and new target node types in `read_node_types` (the old target is never read); bucket edges as for `EnsureEdge` | So an Action that only replaces atoms of existing nodes needs no `write_node_types`, as dependency-scoped validity requires; @@ -887,8 +892,9 @@ held; this design validates only what the proposal adds or retargets. step 2 charges each resolved record, including every Ensure and Create target with its alpha slot, every edge source, and the outgoing bucket of an existing content node, per edge, with that node's atom, hashed at step 2 because the - identity law at step 6 recomputes the node's id from it; and step 8 charges - only the atom it hashes for `ReplaceAtom`'s expected digest. Charges come from + identity law at step 6 recomputes the node's id from it, and each + `ReplaceAtom`'s current alpha value, whose digest step 8 then compares + without charging again; step 8 charges nothing of its own. Charges come from the budget left after evaluation. A read the remaining budget cannot cover obstructs with `BudgetExceeded`, posture `Proposed`, `NotDerived`, at that step. The receipt's consumed budget is evaluation and validation together; @@ -1843,6 +1849,9 @@ Each behaviour below needs a test before implementation: | Effect budget ceilings | A package ceiling or delegated budget above `EFFECT_BUDGET_CEILING_V1` is refused at admission; a program emitting a 1,025th effect, even an identical `Ensure`, obstructs with `BudgetExceeded`, `NoProposal`. | | Chain charged once | A read whose portal chain the view resolved and charged is not charged again at step 1; a budget covering one traversal suffices. | | Implied-read permissions | A `ReplaceAtom` whose owner's node type is in `read_node_types` but not `write_node_types` passes the footprint law; one whose owner type is in neither obstructs with `FootprintLawViolation`. | +| Content-scan permissions | Adopting an existing content node whose bucket holds an edge type outside `write_edge_types` and `range_edge_types` obstructs with `FootprintLawViolation`. | +| Content mismatch first | An `EnsureNode` of an existing content node whose atom differs from the proposal's obstructs at step 2 with `ContentIdentityConflict`, never `IdentityLawViolation`. | +| ReplaceAtom current atom | A `ReplaceAtom` whose current atom type is outside `atom_types` obstructs at step 7 with `FootprintLawViolation`; its alpha value is read and charged at step 2. | ## Open questions From 537c153c565789e07555aebe631512195aad3533 Mon Sep 17 00:00:00 2001 From: James Ross Date: Mon, 19 Oct 2026 17:44:34 -0700 Subject: [PATCH 48/50] docs: accept the architecture, authorize a bounded first profile, keep details provisional Reconciles #889 with the maintainer decision of 2026-10-17 (https://github.com/flyingrobots/echo/pull/889#issuecomment-6241449977): merging must not ratify the whole provisional specification. - authored-operation-effects.md gains "What merging accepts", which separates three strengths: accepted architecture (Edict authorship, the ownership boundary, the five invariants, recovery as verification, deterministic charging and bounded capacity, scratch never a semantic input, and unreachable records readable by key, acknowledged with its storage and reclamation cost); the authorized first profile (five typed effects, exact-basis validity with dependency-scoped validity deferred for this profile only, witnessed reads including absence and ranges, canonical validation, Echo-derived patches, atomic publication, receipt-bound evidence, verifying recovery; a flat-text first witness; the decisive adversarial witnesses); and the provisional specification (layouts, encodings, digests, codes, step order, metering, recovery variants, scoped and delayed validity, physical layouts), which become obligations only alongside executable checks. - Status line, Basis, Encoding and memory, Required tests, and Open questions now point at that boundary. Adds the Exact parent, Scratch independence, and Recovered outcomes test rows, and removes two stray separator rows that rendered as rows of dashes. - ADR 0023 and ADR 0025 supersession banners for bridge Actions, the GeneratedRules decision banner, and the docs index say the merge accepts the architecture while the detailed bridge contract stays provisional. --- docs/README.md | 2 +- ...-admitted-executable-operation-packages.md | 16 +- ...uler-owned-executable-operation-actions.md | 9 +- .../authored-operation-effects.md | 143 +++++++++++++++++- docs/topics/GeneratedRules.md | 11 +- 5 files changed, 165 insertions(+), 16 deletions(-) diff --git a/docs/README.md b/docs/README.md index 87844298..e84dd7a9 100644 --- a/docs/README.md +++ b/docs/README.md @@ -20,7 +20,7 @@ causal history. Git history is the archive; GitHub owns live work and status. - [There Is No Graph](architecture/there-is-no-graph.md) - [Sliced readings and copy-on-write strands](architecture/sliced-readings-and-copy-on-write-strands.md) (accepted design, not implemented) -- [Authored operation effects](architecture/authored-operation-effects.md) (accepted design on merge of #889, not implemented) +- [Authored operation effects](architecture/authored-operation-effects.md) (architecture accepted on merge of #889, detailed specification provisional, not implemented) - [Batched strict submission](architecture/batched-strict-submission.md) - [Participant history as a braid](architecture/participant-history-as-a-braid.md) (accepted design, not implemented) - [Continuum transport](architecture/continuum-transport.md) diff --git a/docs/adr/0023-admitted-executable-operation-packages.md b/docs/adr/0023-admitted-executable-operation-packages.md index 8a9d18ea..004a0495 100644 --- a/docs/adr/0023-admitted-executable-operation-packages.md +++ b/docs/adr/0023-admitted-executable-operation-packages.md @@ -25,7 +25,9 @@ merges to `main`, for bridge Actions only: the prepared-evaluation outcome (a successful evaluation exposes one patch; the closed `PreparedEchoOperationOutcomeV1`; no patch digest on `Obstructed`), marked - where it appears + where it appears. The merge accepts this as architecture; the detailed + bridge contract stays provisional until executable evidence, per + [What merging accepts](../architecture/authored-operation-effects.md#what-merging-accepts) > **Scheduler lifecycle amendment:** This ADR established exact package, > invocation, private-evaluation, and recovery semantics. ADR 0025 supersedes @@ -199,7 +201,11 @@ later evidence without claiming that a durable child worldline already exists. > `PreparedEchoOperationOutcomeV1`; some obstructed postures record a derived > patch digest that was never committed. Atomic visibility, one parent patch > or one obstruction, still governs. The original text is preserved and still -> governs every other Action. +> governs every other Action. The merge accepts this replacement as +> architecture. The posture, record, and digest details stay provisional +> until executable evidence fixes them, as +> [What merging accepts](../architecture/authored-operation-effects.md#what-merging-accepts) +> states, and no bridge Action exists until the first bridge profile ships. The program may use generic primitives for typed graph access, canonical scalar operations, checked `u64` arithmetic, bounded byte manipulation, UTF-8 @@ -272,7 +278,11 @@ but it does not claim a committed consequence. > `PreparedEchoOperationOutcomeV1`; some obstructed postures record a derived > patch digest that was never committed. Atomic visibility, one parent patch > or one obstruction, still governs. The original text is preserved and still -> governs every other Action. +> governs every other Action. The merge accepts this replacement as +> architecture. The posture, record, and digest details stay provisional +> until executable evidence fixes them, as +> [What merging accepts](../architecture/authored-operation-effects.md#what-merging-accepts) +> states, and no bridge Action exists until the first bridge profile ships. > > **Superseded in part (2026-10-14, #837):** for Actions that meet the > conditions of [Action validity across ticks](../topics/GeneratedRules.md#action-validity-across-ticks), diff --git a/docs/adr/0025-scheduler-owned-executable-operation-actions.md b/docs/adr/0025-scheduler-owned-executable-operation-actions.md index ebfc23cb..6b89177c 100644 --- a/docs/adr/0025-scheduler-owned-executable-operation-actions.md +++ b/docs/adr/0025-scheduler-owned-executable-operation-actions.md @@ -14,7 +14,9 @@ effective when [flyingrobots/echo#889](https://github.com/flyingrobots/echo/pull/889) merges to `main`, for bridge Actions only: the prepared candidate returned on success and the lawpack qualification of an evaluation obstruction, - marked where it appears + marked where it appears. The merge accepts this as architecture; the detailed + bridge contract stays provisional until executable evidence, per + [What merging accepts](../architecture/authored-operation-effects.md#what-merging-accepts) ## Context @@ -87,7 +89,10 @@ worldline: > `EffectProposal` rather than a prepared candidate, as > [Authored Operation Effects](../architecture/authored-operation-effects.md) > defines. The original text is preserved and still governs every other -> Action. +> Action. The merge accepts this replacement as architecture. The detailed +> bridge contract stays provisional until executable evidence fixes it, as +> [What merging accepts](../architecture/authored-operation-effects.md#what-merging-accepts) +> states, and no bridge Action exists until the first bridge profile ships. The direct `prepare_echo_operation_v1` and `commit_prepared_echo_operation_v1` methods remain publicly reachable on diff --git a/docs/architecture/authored-operation-effects.md b/docs/architecture/authored-operation-effects.md index f993e5f9..b61c5740 100644 --- a/docs/architecture/authored-operation-effects.md +++ b/docs/architecture/authored-operation-effects.md @@ -3,11 +3,14 @@ # Authored Operation Effects -- **Status:** Accepted design, effective when +- **Status:** Architecture accepted, and a bounded first implementation + authorized, effective when [flyingrobots/echo#889](https://github.com/flyingrobots/echo/pull/889) merges - to `main`. Before that merge it is a proposal and governs nothing. - **Not implemented.** Acceptance establishes the contract; it does not claim - the bridge exists. The section + to `main`. The detailed specification stays provisional until executable + evidence demonstrates it. [What merging accepts](#what-merging-accepts) + draws the line. Before that merge the whole document is a proposal and + governs nothing. **Not implemented.** Acceptance establishes the + architecture; it does not claim the bridge exists. The section [Current implementation](#current-implementation) states what the runtime does today. - **Proposed:** 2026-10-15 in @@ -44,6 +47,105 @@ [Generated Rule Authorship](../topics/GeneratedRules.md), [WAL](../topics/WAL.md) +## What merging accepts + +The maintainer decision of 2026-10-17 +([flyingrobots/echo#889](https://github.com/flyingrobots/echo/pull/889#issuecomment-6241449977)) +accepts this document at three strengths. Merging accepts the first two. It +does not ratify the third: those details become compatibility obligations +only alongside executable evidence. + +**1. Accepted architecture.** These govern from the merge: + +- Application operation semantics are authored in application-owned Edict + source; for Jedit's rope law, see + [Application operation semantics authored in Edict source](../topics/GeneratedRules.md#application-operation-semantics-authored-in-edict-source). +- The ownership boundary. The application owns behavior. Echo owns admission, + generic execution, witnessed reads, validation, derivation of the + authoritative patch, atomic publication, history, and recovery. Echo learns + no application vocabulary. No application callback, caller-supplied patch, + `MutationPlan`, or editor-specific intrinsic enters Echo. +- The five invariants under [Decision](#decision). Evaluation proposes typed + node, edge, and atom effects; it never performs them. +- Recovery verifies the original decision and never makes a new admission + decision. +- Deterministic resource behavior is correctness, not tuning. Evaluation is + charged deterministically against declared budgets. Every working structure + has a bounded capacity fixed before evaluation. A host allocation failure is + a host fault and never becomes an Action obstruction or any other recorded + outcome. Scratch history never becomes a semantic input: cold and warm + scratch produce identical evidence. +- **Unreachable records remain readable by key.** This is accepted + deliberately, with its cost: it constrains storage and reclamation. A + record that no root reaches any longer, such as a superseded rope chunk, + cannot be discarded for that reason alone. Reclamation must preserve what a + keyed read observes, or change it through an explicit, versioned semantic + mechanism. See [Basis, budget, retries](#basis-budget-retries). + +**2. Authorized first implementation.** The first bridge profile is bounded +as follows. Existing executable-operation profiles keep their current +behavior. + +- The five typed effects of [EffectProposal](#effectproposal). +- **Exact-basis validity.** A bridge Action is evaluated against the exact + commit parent. An Action whose evaluation basis is not the commit parent + obstructs with whole-frontier `BasisChanged` before evaluation. + **Dependency-scoped validity is deferred for this profile.** The text below + that specifies it (scoped eligibility, origin evaluation, the written-slot + history check, `HistoryChanged`, `OriginOnly`, and the scoped rows of + [Required tests](#required-tests)) is retained design for a later profile. + It is not an obligation of the first one. Jedit's structural edits already + need whole-frontier validity under this design, so deferring scoped + validity does not block the motivating rope operation. +- Witnessed reads, including observed absence and outgoing-edge ranges. +- Canonical validation with one specified obstruction precedence. +- Echo-derived patches, atomic publication, and receipt-bound evidence. +- Recovery that verifies the original decision. + +Exact-basis validity does not remove same-Tick conflicts. Two Actions +evaluated against the same parent can still invalidate each other's +observations, for example when one observes an empty outgoing-edge range and +the other inserts into it. Wherever the first profile allows concurrent +composition, range dependencies and complete conflict footprints remain +required. + +The first end-to-end witness is an authored edit that creates content, links +it, and moves a head atomically, and that a fresh engine then recovers. A +flat-text representation is acceptable for this milestone. It meets the first +acceptance target in the +[authorship decision](../topics/GeneratedRules.md#application-operation-semantics-authored-in-edict-source) +and does not complete the persistent rope. + +These witnesses decide whether the first profile is accepted. Each takes its +expected result from an independent source or an adversarial case, never from +a second implementation that shares the first one's assumptions: + +- several simultaneous violations produce the specified first obstruction + (Validation order); +- duplicate targets cannot disappear through patch deduplication (One effect + per slot); +- a forbidden read never reaches authored computation (Reads authorized + before use); +- a same-Tick edge insertion conflicts with a relevant range observation + (Negative reads); +- a failure on the last effect leaves no partial mutation (Atomicity); +- a stale exact parent produces no application mutation (Exact parent); +- cold and warm scratch produce identical evidence (Scratch independence); +- crash recovery reproduces committed, obstructed, and conflicting outcomes + without a new admission decision (Recovered outcomes). + +**3. Provisional specification.** Everything else here is a reviewed proposal, +not a compatibility obligation. That covers exact record layouts and byte +encodings, digest constructions, numeric codes, the specific order of the +validation steps, metering constants and charges, recovery variants, the +dependency-scoped and delayed-validity rules, and the physical zero-copy and +arena layouts. Each detail becomes an obligation when an executable check that +exercises it lands with independent expected results. Until then it may change +without a new decision. Bytes that enter an identity, a digest, or retained +evidence must be fixed and witnessed before the profile that writes them +ships. Physical layouts that no identity depends on can stay provisional +after that. + ## Decision An admitted executable operation whose program is compiled Edict may produce a @@ -768,7 +870,10 @@ held; this design validates only what the proposal adds or retargets. the same Tick is `RejectedFootprintConflict` (see **Within one Tick** below). Action validity across ticks keeps its existing rules, and an Action is never re-stamped as if it had been evaluated against a later basis. -- **Two kinds of basis change.** Today's executable programs have fixed, +- **Two kinds of basis change.** The first bridge profile implements only the + whole-frontier kind; dependency-scoped validity is deferred for it (see + [What merging accepts](#what-merging-accepts)), and the scoped text here is + retained design for a later profile. Today's executable programs have fixed, statically known reads, so Echo decides dependency-scoped validity before evaluation. A bridge Action's reads are known only by evaluating it, so the bridge splits basis change in two: @@ -1366,6 +1471,12 @@ above likewise applies to every row recorded as `Scoped`. ## Encoding and memory +The layouts in this section are provisional (see +[What merging accepts](#what-merging-accepts)). Deterministic charging, +bounded capacity, and the rule that host allocation failure never becomes an +Action outcome are accepted. Any bytes that enter an identity, a digest, or +retained evidence must be fixed before the profile that writes them ships. + Every bridge record is a zero-copy image in the style of Echo's WSC snapshot format (`crates/warp-core/src/wsc/`). It is read in place through a view that a validator has checked, and it is never hydrated into owned structures. The @@ -1710,7 +1821,15 @@ propose `ReplaceEdge B -canonicalHead-> expected H1`. ## Required tests -Each behaviour below needs a test before implementation: +Each behaviour below needs a test before the profile that implements it ships. +The first bridge profile must pass the witnesses named in +[What merging accepts](#what-merging-accepts) and every row its scope +exercises. The rows for dependency-scoped validity (Restored value, Scoped +preconditions, Origin resolution, Scoped conflict recovery, Scoped evidence +forms, `OriginOnly` recovery, Scoped basis change, Scoped evaluation +obstruction, Origin reachability) apply only to a later profile that +implements it. A row that pins an exact layout, code, or constant turns that +provisional detail into an obligation when its test lands. | Property | Test | | ------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | @@ -1731,6 +1850,7 @@ Each behaviour below needs a test before implementation: | In-place validation | Each image validates and reads through a view without heap allocation; an image with nonzero padding, a nonzero unused field, an unsorted section, an out-of-bounds range, or loosely packed blob bytes is refused. | | No hot-path allocation | With an allocation-counting allocator, a warmed-up Tick of bridge Actions performs zero heap allocations during evaluation, validation, derivation, and composition, and recovery of that Tick allocates nothing per Action. | | Arena independence | The same Ticks decided on hosts with different initial arena capacities produce byte-identical receipts, WAL records, and state roots. | +| Scratch independence | The same Ticks decided with a freshly allocated (cold) arena and with an arena reused after unrelated earlier Ticks (warm) produce byte-identical receipts, WAL records, retained evidence, and state roots. | | Range-read entries | An empty observed range encodes as a range entry with count zero, never as an absent outcome, and a nonempty range's entry matches the specified layout byte for byte. | | Canonical slots | Differently expressed effects on one slot (`EnsureEdge` and `ReplaceEdge` of one edge id; `CreateNode` and `ReplaceAtom` of one node) obstruct with `DuplicateEffectTarget`. | | Write set | The derived patch's actual footprint equals the write set, including the atom slots of created nodes and the range key of every created or retargeted edge, so a committed `EnsureEdge` and `ReplaceEdge` pass the footprint check. | @@ -1761,6 +1881,7 @@ Each behaviour below needs a test before implementation: | Proposal binding | For every `Proposed` outcome class in the recovery matrix, including rejected ones, altering one recorded effect-derived digest or the proposal identity refuses the history. | | No-proposal evidence | An evaluation that obstructs before proposing records `NoProposal` with its read-set digest and no proposal, write-set, or patch digest. | | Recovery | Crash before and after the decided-Tick WAL record; fresh-host recovery reproduces the same state root and receipts. | +| Recovered outcomes | A fresh engine recovering a history that holds a committed bridge Action, an obstructed one, and a `RejectedFootprintConflict` reproduces each recorded outcome, receipt, and state root by verification, and records no new admission decision for any of them. | | Composition | Two Actions with independent witnessed footprints (neither writes a slot the other reads or writes) commit in one Tick. A write/write overlap obstructs the later one, and so does a write/read overlap: A reads X and writes Y, B writes X and Z. | | Shared structure | Two Actions that `Ensure` the same node already present in the basis both commit in one Tick. | | Same-Tick identical create | Two Actions that both `Ensure` the same node absent at the commit parent: the later obstructs with `RejectedFootprintConflict` instead of reading the earlier one's write. | @@ -1772,6 +1893,7 @@ Each behaviour below needs a test before implementation: | Range metering | A range read over a node with more edges than the law's maximum, or than the remaining budget covers, obstructs with `BudgetExceeded` and `NoProposal` before copying any edge; an admissible range charges one step and 64 read bytes per edge. | | Absent probes | A read that finds nothing records the type it named in `read_type`; a probe for a type outside the law's read permissions obstructs evaluation at once with `FootprintLawViolation`, `NoProposal`, and recovery reproduces the decision from the read set alone. | | Edge-read basis | An Action whose package's footprint law admits a range read commits only onto its exact evaluation basis, including when this invocation reads no edge. | +| Exact parent | In the first bridge profile, an Action whose evaluation basis is not the commit parent obstructs with whole-frontier `BasisChanged`, posture `NoProposal`, before evaluation; it adds no patch entry and changes no state root, live or on recovery. | | Final outcomes | A rejected Action's outcome, receipt, and attribution are unchanged after a later submission of the same edit succeeds. | | Duplicate resubmission | Resubmitting a decided Action's identical content returns `Duplicate` with the original outcome and commits no Tick. | | Attribution | Every Action in a bridge Tick appears once in the attribution section, mapped to its own receipt entry. | @@ -1784,7 +1906,6 @@ Each behaviour below needs a test before implementation: | Invocation version | An effect invocation (`invocation-v2-effect`) is admitted only for an effect-program package, and a V1 invocation only for its own fixed profile. | | Input bounds | An effect invocation whose `application_input_bytes` is not canonical CBOR, or is longer than `max_input_bytes`, is refused at admission; a well-formed one of the wrong shape obstructs evaluation with `InputRejected`. | | Outcome record | A bridge obstruction, including an authored one such as `BasisNotCanonical`, is retained as `EOACT004` with the same coordinate as its receipt; fixed-profile Actions still write `EOACT003`, and mixed histories recover. | -| ------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | Pending recovery | An Action lost with its Tick before the WAL record is recovered as the same submission, not a new one. | | Ensure resolution | An `EnsureNode` whose identical target already exists is a read, not a write, and the footprint law checks the write set without it; its target read is charged at step 2. | | Absent endpoint history | An Action whose package admits edge writes keeps the whole-frontier rule, so an endpoint absent at its evaluation basis and created by a later Tick never lets it commit onto that later basis; it obstructs with whole-frontier `BasisChanged`. | @@ -1818,7 +1939,6 @@ Each behaviour below needs a test before implementation: | Effect profiles | A V2 package whose `target_profile_identity` or `application_basis_schema_identity` is not the specified profile digest is refused at admission. | | Effect basis binding | An effect invocation whose `application_basis_value_identity` is not the digest of its own `application_input_bytes` is refused at admission. | | Outcome row projection | An `EOACT004` record whose `OutcomeRow` differs from the projection of its receipt-bound `BridgeEntryRow` is refused by recovery. | -| ----------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | Allocation ceiling | An effect program map without `max_allocated_bytes` is refused; evaluation is bounded by that declaration alone, never by a host's own ceiling. | | Inherited identities | A V2 package whose input, result, obstruction, interpretation, or evaluation-basis identity differs from its specified profile digest is refused at admission. | | Evaluation read charges | Node, absent, and atom reads charge one step and 64 `read_bytes`, plus the atom length when present; a read the budget cannot cover obstructs before reading, uncharged. | @@ -1865,3 +1985,10 @@ Each behaviour below needs a test before implementation: as evidence (`BudgetExceeded`, posture `Proposed` with a `Derived` patch, as [Basis, budget, retries](#basis-budget-retries) states), but it never commits and is never split across Ticks. +- When a later bridge profile should adopt dependency-scoped validity. It is + deferred for the first profile; adopting it needs its own executable + evidence, at least the restored-value (ABA) and scoped-precondition + witnesses, and must leave the first profile's recorded outcomes verifiable. +- Which reclamation mechanism, if any, should change what a keyed read of an + unreachable record observes. Until one ships as an explicit, versioned + semantic change, unreachable records stay readable by key. diff --git a/docs/topics/GeneratedRules.md b/docs/topics/GeneratedRules.md index 38108dba..9a507ee9 100644 --- a/docs/topics/GeneratedRules.md +++ b/docs/topics/GeneratedRules.md @@ -337,7 +337,11 @@ for Expressing the Rope Law". For bridge Actions, [Authored Operation Effects](../architecture/authored-operation-effects.md) also supersedes ADR 0023's prepared-evaluation outcome, as marked there. Every other provision of ADR 0023 continues to govern, and its original reasoning is -kept.** +kept. The maintainer decision of 2026-10-17 accepts this authorship decision +and the core architecture of that design on merge, authorizes a bounded +first implementation, and keeps the design's detailed specification +provisional until executable evidence demonstrates it; see +[What merging accepts](../architecture/authored-operation-effects.md#what-merging-accepts).** Jedit's persistent-rope `ReplaceRange` law is authored in Jedit-owned Edict source and compiled by Edict. It is not imported as a declarative executable @@ -397,7 +401,10 @@ Consequences: implementation strategy, and the capability requires its own design review. - Rope completion requires two acceptance targets. The first is semantic correctness: an Edict-authored `ReplaceRange`, possibly over an inefficient - representation, commits correct bytes with canonical evidence. The second is + representation (flat text is acceptable), commits correct bytes with + canonical evidence. Its witness creates content, links it, and moves the + head in one atomic Tick, then recovers that result on a fresh engine. The + second is persistent-rope correctness: path copying, retained identity for unaffected subtrees, preserved invariants, and bounded work. The first does not complete the rope. From 51b6df4d09724caf412ff340a00825ffcf162e6b Mon Sep 17 00:00:00 2001 From: James Ross Date: Mon, 19 Oct 2026 18:04:01 -0700 Subject: [PATCH 49/50] docs: describe #917's outgoing-edge range keys and their limits The Current implementation section, the range-read rationale, and the recovery reconstruction text still said Footprint had no range key. Since #917 it has OutgoingEdgeRange claims (r_read, r_write) in footprint and patch identities, retained through scheduling, provenance, and admission. The text now says so and states what remains: claims are declared rather than witnessed, no accessor or edge writer records them, source presence needs its own node dependency, and witnessed reads, complete effect write claims, exact-parent validation, and fresh-engine recovery are still to be built. --- .../authored-operation-effects.md | 46 +++++++++++++------ 1 file changed, 33 insertions(+), 13 deletions(-) diff --git a/docs/architecture/authored-operation-effects.md b/docs/architecture/authored-operation-effects.md index b61c5740..ec5be14f 100644 --- a/docs/architecture/authored-operation-effects.md +++ b/docs/architecture/authored-operation-effects.md @@ -204,8 +204,23 @@ Echo learns no application vocabulary. A proposal is nodes, edges, and atoms. - Footprint independence is a set test: any write overlapping another Action's read or write conflicts, including two identical writes (`crates/warp-core/src/footprint.rs`, `Footprint::independent`). Its key - sets are nodes, edges, attachments, and ports; it has no key for a range - such as "all outgoing edges of one type from a node". + sets are nodes, edges, attachments, ports, and, since + [flyingrobots/echo#917](https://github.com/flyingrobots/echo/pull/917), + outgoing-edge ranges: an `OutgoingEdgeRange` names one source node + (including its WARP) and one edge type, and a footprint claims ranges read + (`r_read`, including an observed empty range) and ranges written + (`r_write`). A write to a range conflicts with another Action's read or + write of it, so a declared empty range conflicts with an insertion into it + under a fresh edge id. Range keys take part in footprint and patch + identities (`SlotId::OutgoingEdges` in `crates/warp-core/src/tick_patch.rs`) + and survive scheduling, retained patches, provenance, and admission, and a + rejected Action records the exact blocking range. The limits: range claims + are declared, not witnessed. No graph accessor or edge writer records them + automatically. A range claim does not cover the source node's presence, + which needs its own node dependency. No Edict program gains graph mutation + authority from it. The automatic witnessed reads, complete effect write + claims, exact-parent effect validation, and fresh-engine recovery that this + design requires remain to be built. - `WarpTickPatchV1::new` keeps one operation per canonical sort key, the last one supplied, and silently drops earlier operations with the same key (`crates/warp-core/src/tick_patch.rs`). @@ -528,15 +543,18 @@ co-scheduled Action that creates the slot invalidates it like any other changed read. A read of the outgoing edges of one type from a node is a range, not a set of -known edge ids, so it needs its own footprint key. Today's `Footprint` has -none: if Action A observes no `canonicalHead` edge from `B` and co-scheduled -Action B creates a fresh one, their edge-id sets are disjoint and the existing -test calls them independent, although A's observed absence no longer holds. -The bridge therefore adds an outgoing-edge range key `(source node, edge -type)`. A range read claims it as a read. Every effect that creates or -retargets an edge with that source and type claims it as a write, in addition -to the edge id. Independence treats range keys like every other key: a write -against a read or write conflicts. +known edge ids, so it needs its own footprint key. With edge ids alone, if +Action A observes no `canonicalHead` edge from `B` and co-scheduled Action B +creates a fresh one, their edge-id sets are disjoint and the test calls them +independent, although A's observed absence no longer holds. Echo's +`Footprint` has carried that key since +[flyingrobots/echo#917](https://github.com/flyingrobots/echo/pull/917): an +`OutgoingEdgeRange` `(source node, edge type)`, claimed in `r_read` and +`r_write`. Independence treats range keys like every other key: a write +against a read or write conflicts. What #917 does not do, and the bridge +must, is make the claims witnessed rather than declared. A range read claims +its range as a read. Every effect that creates or retargets an edge with that +source and type claims it as a write, in addition to the edge id. A range read is checked and metered before it materializes anything. A range read of an edge type outside the footprint law's `range_edge_types` @@ -1453,8 +1471,10 @@ Two checks apply to every row and are not repeated in it, because recovery already performs them for every Action Tick: the basis posture is recomputed against the scheduler Tick's parent, and the whole Action batch's composition is reconstructed and every reconstructed outcome must equal the recorded one. -The bridge keeps both and adds the outgoing-edge range keys and precondition -reads to the reconstructed composition. The scoped-validity recomputation +The bridge keeps both. The reconstruction already compares declared +outgoing-edge range keys and recomputes range blockers (#917); the bridge +supplies the range keys that its witnessed reads and effects claim, and its +precondition reads, to that reconstructed composition. The scoped-validity recomputation above likewise applies to every row recorded as `Scoped`. | Outcome | Posture | Existing check | Added | From be82442f7531c602124093c63777de2000c24f94 Mon Sep 17 00:00:00 2001 From: James Ross Date: Mon, 19 Oct 2026 20:07:02 -0700 Subject: [PATCH 50/50] docs: blockers are earlier-Action indices, not ranges Range conflicts participate in blocker computation, but a rejected Action records the indices of the earlier Actions that blocked it (engine_impl.rs pushes reserved_entry_indices after a boolean footprint-conflict check), not the exact conflicting range. Corrects Current implementation and the recovery-reconstruction sentence. --- docs/architecture/authored-operation-effects.md | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/docs/architecture/authored-operation-effects.md b/docs/architecture/authored-operation-effects.md index ec5be14f..1d838ac8 100644 --- a/docs/architecture/authored-operation-effects.md +++ b/docs/architecture/authored-operation-effects.md @@ -213,8 +213,10 @@ Echo learns no application vocabulary. A proposal is nodes, edges, and atoms. write of it, so a declared empty range conflicts with an insertion into it under a fresh edge id. Range keys take part in footprint and patch identities (`SlotId::OutgoingEdges` in `crates/warp-core/src/tick_patch.rs`) - and survive scheduling, retained patches, provenance, and admission, and a - rejected Action records the exact blocking range. The limits: range claims + and survive scheduling, retained patches, provenance, and admission. Range + conflicts participate in blocker computation: a rejected Action records the + indices of the earlier Actions that blocked it, not the exact conflicting + range. The limits: range claims are declared, not witnessed. No graph accessor or edge writer records them automatically. A range claim does not cover the source node's presence, which needs its own node dependency. No Edict program gains graph mutation @@ -1472,7 +1474,8 @@ already performs them for every Action Tick: the basis posture is recomputed against the scheduler Tick's parent, and the whole Action batch's composition is reconstructed and every reconstructed outcome must equal the recorded one. The bridge keeps both. The reconstruction already compares declared -outgoing-edge range keys and recomputes range blockers (#917); the bridge +outgoing-edge range keys and recomputes blockers, as earlier-Action indices +(#917); the bridge supplies the range keys that its witnessed reads and effects claim, and its precondition reads, to that reconstructed composition. The scoped-validity recomputation above likewise applies to every row recorded as `Scoped`.