T-820W-yaml-splice-wasm-ts-binding
Status: closed/done · Impact: high · Complexity: large
Expose the pure yaml-splice Rust crate to TypeScript over WebAssembly, per
D-W4SM-yaml-splice-wasm-typescript-binding, so the SDLC frontmatter writers (and later
markdown vault tooling) get byte-identical edit_yaml semantics from one implementation.
A spike proved this is low-difficulty; this task lands it in the tree: a wrapper crate, a
typed TS package, a shared conformance corpus, and a drift gate. Unblocks the whole-block
writer migrations in M-2VYE-yaml-splice-extraction-and-writer-migrations.
| Location | Role today |
|---|---|
packages/rust/yaml-splice/ | the pure Rust crate — edit_yaml, FieldEdit, EditOutcome, typed EditError. Compiles to wasm32-unknown-unknown unchanged. No TS surface. |
packages/rust/yaml-splice/tests/conformance.rs | the 37-case §8 corpus, inline in Rust — not reachable from another language |
packages/ts/ | TS workspace packages (intersect, agent-plugin, easy-git) consumed via workspace:*; no yaml-splice package |
apps/sdlc/lib/model/ops/_update.ts | _update.ts-class whole-block writer that reserializes via dumpFrontmatter — the eventual consumer, cannot call Rust today |
Proposed
Section titled “Proposed”Three new pieces plus a gate: packages/rust/yaml-splice-wasm (a cdylib wasm-bindgen
wrapper), packages/ts/yaml-splice (@sksizer/yaml-splice — committed wasm artifact +
typed façade), a language-neutral JSON conformance corpus both Rust and TS run, and a CI
conformance gate that runs the corpus against the committed artifact. import { editYaml } from '@sksizer/yaml-splice' returns a plain { bytes, changed } or throws a typed EditError,
byte-identical to the Rust crate.
Approach
Section titled “Approach”- Wrapper crate. Create
packages/rust/yaml-splice-wasm(crate-type = ["cdylib"],yaml-splice = { workspace = true }, pinnedwasm-bindgen). ExposeeditYaml(source: &[u8], edits_json: &str) -> Result<EditResult, JsError>; parse the edits JSON intoVec<FieldEdit>through the crate’sFrom<&serde_json::Value>value bridge; mapEditErrorto a thrownErrorcarryingname(variant) +span. (Reference the proven spike wrapper — ~50 LoC.) - Build pipeline. A committed build script drives
cargo build --release --target wasm32-unknown-unknown→ the pinnedwasm-bindgenCLI (nodejs target) →wasm-opt -Oz, emitting the glue +.wasmintopackages/ts/yaml-splice. Pin the wasm-bindgen crate and CLI to the same version. - TS package.
packages/ts/yaml-splice(@sksizer/yaml-splice,workspace:*) with a hand-writtenindex.tsfaçade: typedFieldEditunion /EditOutcome/EditError; read{bytes, changed}into a plain object andfree()the bindgen handle; stringify edits internally. nodejs (sync) default entry. - Shared corpus. Move the §8 fixtures to
{source, edits, expected_bytes | expected_error}JSON under a shared location; pointyaml-splice’sconformance.rsat it and add a TS test that runs the same fixtures against the wasm façade. - Conformance gate. A CI check that runs the shared corpus against the committed
artifact (
bun test packages/ts/yaml-splice) and confirms the wrapper compiles to wasm32 — behavioral, not a cross-OS byte-diff (wasm runs identically everywhere but does not build byte-identically across hosts).
Files to touch
Section titled “Files to touch”| Location | Kind | Change |
|---|---|---|
packages/rust/yaml-splice-wasm/Cargo.toml | new | cdylib; deps yaml-splice, pinned wasm-bindgen, serde_json |
packages/rust/yaml-splice-wasm/src/lib.rs | new | the wasm-bindgen editYaml wrapper + error mapping |
packages/rust/yaml-splice-wasm/build.sh | new | cargo → wasm-bindgen (pinned) → wasm-opt into the TS package |
packages/ts/yaml-splice/package.json | new | @sksizer/yaml-splice, bun/exports, nodejs entry |
packages/ts/yaml-splice/index.ts | new | typed façade over the raw bindgen |
packages/ts/yaml-splice/wasm/ | new | committed generated glue + .wasm |
packages/ts/yaml-splice/index.test.ts | new | TS run of the shared conformance corpus |
packages/rust/yaml-splice/tests/fixtures/conformance.json | new | language-neutral corpus |
packages/rust/yaml-splice/tests/conformance.rs | modify | load the shared JSON fixtures |
Cargo.toml | modify | (auto via packages/rust/* glob; add wasm-bindgen workspace dep if needed) |
.github/workflows/ | new | drift gate: rebuild the artifact and diff |
Acceptance criteria
Section titled “Acceptance criteria”- AC-1:
packages/rust/yaml-splice-wasmbuilds towasm32-unknown-unknown, and the pinnedwasm-bindgen+wasm-optpipeline regenerates the committedpackages/ts/yaml-splice/wasm/artifact deterministically. - AC-2:
import { editYaml } from '@sksizer/yaml-splice'in a bun/node test does a value change that preserves quoting byte-for-byte, and a semantic no-op that returns byte-identical input withchanged === false. - AC-3: an
EditErrorfrom the Rust core surfaces as a typed TS error (name= variant,span); the façade exposes a typedFieldEditunion and never leaks the raw bindgen class (handle freed). - AC-4: the conformance corpus is language-neutral JSON;
cargo test -p yaml-spliceand the TS package test both load and pass the same fixtures (byte + changed + error-code parity). - AC-5: a CI conformance gate runs the shared §8 corpus against the committed wasm artifact and confirms the wrapper source compiles to wasm32 — behavioral, not a cross-OS byte-diff (wasm runs identically everywhere but does not build byte-identically across hosts).
- AC-6:
yaml-splice’s own three-dependency purity is unchanged —wasm-bindgenlives only inyaml-splice-wasm, not the core crate.
Out of scope
Section titled “Out of scope”- Migrating the SDLC writers onto the package — the consuming half of M-2VYE-yaml-splice-extraction-and-writer-migrations.
- Publishing
@sksizer/yaml-spliceto npm (tier-2 promotion, later). - A production browser/bundler build beyond a stub entry.
- Replacing
saphyr-parserwith the lossless CST — M-C6XN-lossless-cst-core.
Dependencies
Section titled “Dependencies”- D-W4SM-yaml-splice-wasm-typescript-binding accepted (currently
open/proposed). - Builds on the merged
yaml-splicecrate (T-3MZD, closed).
Discovery context
Section titled “Discovery context”- A time-boxed WASM spike (2026-07-21) proved the binding low-difficulty: compiles to wasm32 unchanged, ~50-line wrapper, both round-trips pass, ~271 KB optimized. This task productionizes that spike per D-W4SM-yaml-splice-wasm-typescript-binding.