Goal
Add Rust/WASM acceleration coverage for every eligible tsb core function while keeping the existing TypeScript implementations as the default-compatible fallback.
Current Status
This goal is not complete unless the deterministic gate below passes on the exact branch/commit being marked complete.
As of the 2026-07-02 audit:
Completion Contract
The Goal workflow should add goal-completed and remove goal only when all of the following are true:
- The repository has a Rust/WASM implementation layer that builds to WebAssembly and can be loaded from a Node/Bun environment.
- Existing TypeScript core implementations remain present and usable as fallbacks; the public
tsb API remains backward compatible for current callers.
- Every public value export from
src/core/index.ts is represented in a machine-checkable Rust/WASM coverage manifest.
- Every top-level value export from
src/index.ts whose source is under src/core/** is represented in that manifest, including direct top-level core exports that bypass src/core/index.ts.
- Every public class/prototype method on exported core classes is represented individually as
Class.method or through a machine-checkable per-class methods[] inventory. A parent class entry alone never covers its methods.
- The coverage manifest classifies each export or method as either:
rust-wasm: implemented in Rust, exported through WASM, wired into the Node/Bun-accessible accelerated path, covered by parity tests, and covered by benchmarks or a documented benchmark group; or
ts-only-ineligible: explicitly not suitable for Rust/WASM with a concrete reason, such as arbitrary JS callback semantics, JS object identity behavior, JS runtime mutation, external I/O, JS Date/Intl dependence, or another specific incompatibility.
ts-only-ineligible is not a catch-all for work that is merely unported. CPU-heavy pure operations such as numeric reductions, search/sort helpers, align/reindex/index operations, and vectorized scalar transforms should be treated as eligible unless a specific method-level JS semantic blocker is documented.
- There are no
todo, unknown, unclassified, placeholder, or missing entries in the coverage manifest.
- Every
rust-wasm entry has parity tests showing the Rust/WASM path returns the same observable result as the existing TypeScript path for representative normal, edge, missing-value, and dtype cases.
- The accelerated path can be selected from Node/Bun without replacing the TypeScript implementation. A caller should be able to use the TypeScript path and the Rust/WASM path in the same environment for comparison and fallback.
- Benchmark coverage is added for every
rust-wasm entry or for a documented benchmark group that covers that entry, and the benchmark output compares TypeScript tsb against Rust/WASM tsb.
- Benchmark results are written to a stable JSON artifact, and any Rust/WASM case that is slower than the TypeScript baseline is explicitly identified in the output or in the coverage report with a short explanation.
- The TypeScript test suite, strict typecheck, lint, Rust tests, WASM build, WASM parity tests, Rust/WASM coverage check, Rust/WASM benchmark command, and the deterministic completion gate below all pass on the same commit.
Deterministic Completion Gate
A run must not add goal-completed unless this gate passes from the repository root on the exact branch/commit being marked complete. The completion comment must paste this gate's output. If this gate fails, the issue remains active even if bun run wasm:coverage, tests, benchmarks, or the older evidence snippet pass.
python3 - <<'PY'
import json
import re
from pathlib import Path
ROOT = Path.cwd()
MANIFEST = ROOT / "wasm-coverage.json"
CORE_INDEX = ROOT / "src/core/index.ts"
TOP_INDEX = ROOT / "src/index.ts"
manifest = json.loads(MANIFEST.read_text())
entries = manifest.get("entries")
if not isinstance(entries, list) or not entries:
raise SystemExit("wasm-coverage.json must contain a non-empty entries array")
by_name = {}
for entry in entries:
name = entry.get("name")
status = entry.get("status")
if not isinstance(name, str) or not name:
raise SystemExit("manifest entry has invalid name: %r" % (entry,))
if name in by_name:
raise SystemExit("duplicate manifest entry: %s" % name)
if status not in {"rust-wasm", "ts-only-ineligible"}:
raise SystemExit("%s: invalid status %r" % (name, status))
if status == "rust-wasm":
for key in ("wasm_function", "parity_test", "benchmark_group"):
value = entry.get(key)
if not isinstance(value, str) or not value.strip():
raise SystemExit("%s: rust-wasm entry missing %s" % (name, key))
if status == "ts-only-ineligible":
reason = entry.get("reason")
if not isinstance(reason, str) or len(reason.strip()) < 30:
raise SystemExit("%s: ts-only-ineligible entry needs a concrete reason" % name)
lowered = reason.lower()
banned = ("todo", "unknown", "unclassified", "not yet", "not ported", "future work", "same as series", "same as dataframe")
if any(token in lowered for token in banned):
raise SystemExit("%s: reason looks like a placeholder/catch-all: %r" % (name, reason))
by_name[name] = entry
summary = manifest.get("summary", {})
if summary.get("unclassified") != 0:
raise SystemExit("summary.unclassified must be 0")
if summary.get("eligible_missing") != 0:
raise SystemExit("summary.eligible_missing must be 0")
if summary.get("total_core_entries") != len(entries):
raise SystemExit("summary.total_core_entries must equal len(entries)")
if summary.get("rust_wasm") != sum(1 for e in entries if e.get("status") == "rust-wasm"):
raise SystemExit("summary.rust_wasm count is wrong")
if summary.get("ts_only_ineligible") != sum(1 for e in entries if e.get("status") == "ts-only-ineligible"):
raise SystemExit("summary.ts_only_ineligible count is wrong")
export_block_re = re.compile(r'^export\s+(type\s+)?\{([\s\S]*?)\}\s*from\s*["\']([^"\']+)["\']', re.M)
def exported_name(token):
token = re.sub(r'//.*$', '', token).strip()
if not token:
return None
parts = re.split(r'\s+as\s+', token)
return parts[-1].strip()
def value_exports(source, source_filter=None):
out = {}
for match in export_block_re.finditer(source):
if match.group(1):
continue
from_path = match.group(3)
if source_filter and not source_filter(from_path):
continue
for raw in match.group(2).split(','):
name = exported_name(raw)
if name:
out[name] = from_path
return out
core_exports = value_exports(CORE_INDEX.read_text())
top_core_exports = value_exports(TOP_INDEX.read_text(), lambda p: p.startswith("./core/"))
required_exports = set(core_exports) | set(top_core_exports)
missing_exports = sorted(required_exports - set(by_name))
if missing_exports:
raise SystemExit("missing manifest export entries: " + ", ".join(missing_exports[:50]))
# Prefer concrete implementation source from src/core/index.ts for duplicate names.
export_sources = dict(top_core_exports)
export_sources.update(core_exports)
def resolve_source(from_path, base_dir):
if from_path.startswith("./core/"):
return ROOT / "src" / from_path[2:]
if from_path.startswith("./"):
return base_dir / from_path[2:]
return None
def find_class_file(name, from_path, base_dir, seen=None):
if seen is None:
seen = set()
path = resolve_source(from_path, base_dir)
if path is None or not path.is_file() or path in seen:
return None
seen.add(path)
text = path.read_text()
if re.search(r'(^|\n)export\s+(?:abstract\s+)?class\s+' + re.escape(name) + r'\b', text) or re.search(r'(^|\n)(?:abstract\s+)?class\s+' + re.escape(name) + r'\b', text):
return path
for exported, nested_from in value_exports(text).items():
if exported == name:
found = find_class_file(name, nested_from, path.parent, seen)
if found is not None:
return found
return None
class_file = {}
for name, from_path in export_sources.items():
base_dir = ROOT / "src/core" if not from_path.startswith("./core/") else ROOT / "src"
found = find_class_file(name, from_path, base_dir)
if found is not None:
class_file[name] = found
method_head_re = re.compile(r'^\s{2}(?:public\s+)?(?:async\s+)?(?:get\s+|set\s+)?([A-Za-z_$][\w$]*)\s*(?:<[^>{}]+>)?\s*\(', re.M)
privateish = {"constructor"}
def class_body(text, class_name):
match = re.search(r'(?:export\s+)?(?:abstract\s+)?class\s+' + re.escape(class_name) + r'\b[^{}]*\{', text)
if not match:
return ""
start = match.end()
depth = 1
i = start
while i < len(text):
ch = text[i]
if ch == "{":
depth += 1
elif ch == "}":
depth -= 1
if depth == 0:
return text[start:i]
i += 1
raise SystemExit("could not parse class body for %s" % class_name)
required_methods = []
for class_name, path in sorted(class_file.items()):
body = class_body(path.read_text(), class_name)
methods = sorted(set(m.group(1) for m in method_head_re.finditer(body)) - privateish)
for method in methods:
required_methods.append("%s.%s" % (class_name, method))
def method_covered(method_name):
if method_name in by_name:
return True
class_name, method = method_name.split('.', 1)
parent = by_name.get(class_name, {})
methods = parent.get("methods")
if isinstance(methods, list):
for item in methods:
if not isinstance(item, dict) or item.get("name") != method:
continue
status = item.get("status")
if status not in {"rust-wasm", "ts-only-ineligible"}:
raise SystemExit("%s: invalid method status %r" % (method_name, status))
if status == "rust-wasm" and not item.get("wasm_function"):
raise SystemExit("%s: rust-wasm method missing wasm_function" % method_name)
if status == "ts-only-ineligible":
reason = item.get("reason")
if not isinstance(reason, str) or len(reason.strip()) < 30:
raise SystemExit("%s: ts-only-ineligible method needs a concrete reason" % method_name)
return True
return False
missing_methods = [name for name in required_methods if not method_covered(name)]
if missing_methods:
raise SystemExit(
"missing public method coverage entries (use Class.method entries or a per-class methods[] inventory): "
+ ", ".join(missing_methods[:80])
)
priority_methods = [
"Series.add", "Series.sub", "Series.mul", "Series.div", "Series.sum", "Series.mean", "Series.min", "Series.max", "Series.std", "Series.var", "Series.median", "Series.sortValues",
"DataFrame.sum", "DataFrame.mean", "DataFrame.min", "DataFrame.max", "DataFrame.std", "DataFrame.corr", "DataFrame.cov", "DataFrame.sortValues",
"DataFrameRolling.mean", "DataFrameRolling.sum", "DataFrameRolling.std", "DataFrameRolling.var", "DataFrameRolling.min", "DataFrameRolling.max", "DataFrameRolling.median",
"DataFrameExpanding.mean", "DataFrameExpanding.sum", "DataFrameExpanding.std", "DataFrameExpanding.var", "DataFrameExpanding.min", "DataFrameExpanding.max", "DataFrameExpanding.median",
]
missing_priority = [name for name in priority_methods if name in required_methods and not method_covered(name)]
if missing_priority:
raise SystemExit("missing priority CPU-heavy method entries: " + ", ".join(missing_priority))
print("deterministic Rust/WASM completion gate: PASS")
print("manifest entries: %d" % len(entries))
print("live value exports covered: %d" % len(required_exports))
print("exported core classes audited for public methods: %d" % len(class_file))
print("public methods covered: %d" % len(required_methods))
print("rust-wasm entries: %d" % sum(1 for e in entries if e.get("status") == "rust-wasm"))
print("ts-only-ineligible entries: %d" % sum(1 for e in entries if e.get("status") == "ts-only-ineligible"))
PY
Minimum completion interpretation:
- If the gate reports any missing
Class.method coverage, the goal is incomplete.
- If
Series, DataFrame, DataFrameRolling, DataFrameExpanding, or similar high-level classes are marked ts-only-ineligible, their public methods still need individual entries or a machine-checkable per-class methods[] inventory.
- CPU-heavy pure methods must not be dismissed by a parent class blocker. Each one must be
rust-wasm or have its own concrete method-level incompatibility reason.
- Completion requires this gate plus the full Bun/Rust/WASM evidence script below to pass on the same commit.
Evidence / Verification
At completion, this script must pass from the repository root on the same commit as the deterministic gate:
set -euo pipefail
bun install
bun run typecheck
bun run lint
bun test ./tests/core/
bun run wasm:build
bun run wasm:test
bun run wasm:coverage
BENCHMARK_WORKERS=2 BENCHMARK_TIMEOUT=60 bun run bench:wasm-core
python3 - <<'PY'
import json
from pathlib import Path
coverage_path = Path("benchmarks/results-wasm-core.json")
if not coverage_path.is_file():
raise SystemExit("benchmarks/results-wasm-core.json was not written")
results = json.loads(coverage_path.read_text())
benchmarks = results.get("benchmarks")
if not isinstance(benchmarks, list) or not benchmarks:
raise SystemExit("Rust/WASM benchmark output has no benchmark entries")
missing = [b for b in benchmarks if "tsb" not in b or "tsb_wasm" not in b]
if missing:
raise SystemExit("Benchmark entries missing tsb/tsb_wasm comparison: %r" % (missing[:5],))
coverage_summary = results.get("coverage")
if not isinstance(coverage_summary, dict):
raise SystemExit("Benchmark output must include a coverage summary")
if coverage_summary.get("unclassified", 1) != 0:
raise SystemExit("Rust/WASM coverage still has unclassified entries")
if coverage_summary.get("eligible_missing", 1) != 0:
raise SystemExit("Rust/WASM coverage still has eligible functions without implementations")
if coverage_summary.get("total_core_entries", 0) <= 0:
raise SystemExit("Rust/WASM coverage summary did not count core entries")
PY
Completion also requires the final PR body or issue comment to include:
- The path to the coverage manifest.
- The number of total core entries,
rust-wasm entries, and ts-only-ineligible entries.
- The number of public methods audited and covered by the deterministic gate.
- The Rust/WASM benchmark artifact path.
- A short summary of fastest wins and any slower-than-TypeScript cases.
Scope and Constraints
The workflow may change:
Cargo.toml, Cargo.lock, Rust crate files, and Rust/WASM build configuration.
- New Rust/WASM source and generated bindings under a clearly named path such as
rust/, wasm/, src/wasm/, or src/core/wasm/.
- TypeScript glue code needed to expose the accelerated path from Node/Bun while preserving the current TypeScript behavior.
src/core/** only where needed for optional dispatch/fallback glue.
tests/core/** and new Rust/WASM parity tests.
benchmarks/run_benchmarks.sh, new benchmarks/tsb-wasm/** files, and benchmark result/runner files needed for Rust/WASM comparisons.
package.json scripts needed for wasm:build, wasm:test, wasm:coverage, and bench:wasm-core.
- Documentation or machine-readable manifests that explain Rust/WASM eligibility and coverage.
The workflow must not change:
README.md.
.autoloop/programs/**.
- The public
tsb API in a breaking way.
- Existing TypeScript implementations by deleting or replacing them outright.
- Core runtime JS dependencies. Any new JS dependency must be build/test tooling only and justified.
- Strict TypeScript rules: no
any, no as casts, no @ts-ignore, and no escape hatches.
Context To Read First
AGENTS.md
package.json
src/core/index.ts
src/index.ts
src/core/**
tests/core/**
benchmarks/run_benchmarks.sh
benchmarks/tsb/**
- Existing benchmark result shape in
benchmarks/results.json
Iteration Policy
Work in small checkpoints. This goal must advance piecemeal across multiple Goal runs.
The next checkpoint should make completion measurable again:
- Add the deterministic gate as a checked-in script, or update
scripts/wasm-coverage-check.ts so it enforces the same public-method inventory rules.
- Update
wasm-coverage.json to include Class.method entries or per-class methods[] inventories.
- Run the deterministic gate and report its output.
After the method inventory is measurable, port one coherent eligible group per run, prioritizing CPU-heavy core operations such as numeric reductions, search/sort helpers, align/reindex/index operations, and vectorized scalar transforms.
For every run:
- Keep the change set small enough to review as a single checkpoint.
- Execute the narrowest relevant parity tests and benchmark subset before broadening to the full evidence script.
- Report what changed, what was verified, what remains, and whether any functions/methods were marked
ts-only-ineligible.
- Do not mark the goal complete from intent, partial coverage, manifest self-consistency, or class-level ineligibility.
Blocked Stop Condition
Stop substantive work and comment instead of guessing if:
- The Rust/WASM toolchain cannot be installed or used in the GitHub Actions environment.
- The workflow cannot load the generated WASM from Node/Bun without adding supported build/test tooling only.
- A core function or method's eligibility cannot be decided from the code and tests alone.
- Meeting the coverage contract would require deleting the TypeScript implementation or breaking the public API.
- The benchmark harness cannot produce stable TypeScript vs Rust/WASM comparisons after reasonable retries.
The blocked comment should include the exact command output, the functions/methods or files affected, what is known, and the smallest maintainer decision or environment change needed to continue.
Goal
Add Rust/WASM acceleration coverage for every eligible
tsbcore function while keeping the existing TypeScript implementations as the default-compatible fallback.Current Status
This goal is not complete unless the deterministic gate below passes on the exact branch/commit being marked complete.
As of the 2026-07-02 audit:
Series,DataFrame,DataFrameRolling, or another high-level class does not cover public methods such asSeries.sum,Series.mean,Series.add,DataFrame.sum,DataFrame.mean, or rolling/expanding reductions.Completion Contract
The Goal workflow should add
goal-completedand removegoalonly when all of the following are true:tsbAPI remains backward compatible for current callers.src/core/index.tsis represented in a machine-checkable Rust/WASM coverage manifest.src/index.tswhose source is undersrc/core/**is represented in that manifest, including direct top-level core exports that bypasssrc/core/index.ts.Class.methodor through a machine-checkable per-classmethods[]inventory. A parent class entry alone never covers its methods.rust-wasm: implemented in Rust, exported through WASM, wired into the Node/Bun-accessible accelerated path, covered by parity tests, and covered by benchmarks or a documented benchmark group; orts-only-ineligible: explicitly not suitable for Rust/WASM with a concrete reason, such as arbitrary JS callback semantics, JS object identity behavior, JS runtime mutation, external I/O, JS Date/Intl dependence, or another specific incompatibility.ts-only-ineligibleis not a catch-all for work that is merely unported. CPU-heavy pure operations such as numeric reductions, search/sort helpers, align/reindex/index operations, and vectorized scalar transforms should be treated as eligible unless a specific method-level JS semantic blocker is documented.todo,unknown,unclassified, placeholder, or missing entries in the coverage manifest.rust-wasmentry has parity tests showing the Rust/WASM path returns the same observable result as the existing TypeScript path for representative normal, edge, missing-value, and dtype cases.rust-wasmentry or for a documented benchmark group that covers that entry, and the benchmark output compares TypeScripttsbagainst Rust/WASMtsb.Deterministic Completion Gate
A run must not add
goal-completedunless this gate passes from the repository root on the exact branch/commit being marked complete. The completion comment must paste this gate's output. If this gate fails, the issue remains active even ifbun run wasm:coverage, tests, benchmarks, or the older evidence snippet pass.Minimum completion interpretation:
Class.methodcoverage, the goal is incomplete.Series,DataFrame,DataFrameRolling,DataFrameExpanding, or similar high-level classes are markedts-only-ineligible, their public methods still need individual entries or a machine-checkable per-classmethods[]inventory.rust-wasmor have its own concrete method-level incompatibility reason.Evidence / Verification
At completion, this script must pass from the repository root on the same commit as the deterministic gate:
Completion also requires the final PR body or issue comment to include:
rust-wasmentries, andts-only-ineligibleentries.Scope and Constraints
The workflow may change:
Cargo.toml,Cargo.lock, Rust crate files, and Rust/WASM build configuration.rust/,wasm/,src/wasm/, orsrc/core/wasm/.src/core/**only where needed for optional dispatch/fallback glue.tests/core/**and new Rust/WASM parity tests.benchmarks/run_benchmarks.sh, newbenchmarks/tsb-wasm/**files, and benchmark result/runner files needed for Rust/WASM comparisons.package.jsonscripts needed forwasm:build,wasm:test,wasm:coverage, andbench:wasm-core.The workflow must not change:
README.md..autoloop/programs/**.tsbAPI in a breaking way.any, noascasts, no@ts-ignore, and no escape hatches.Context To Read First
AGENTS.mdpackage.jsonsrc/core/index.tssrc/index.tssrc/core/**tests/core/**benchmarks/run_benchmarks.shbenchmarks/tsb/**benchmarks/results.jsonIteration Policy
Work in small checkpoints. This goal must advance piecemeal across multiple Goal runs.
The next checkpoint should make completion measurable again:
scripts/wasm-coverage-check.tsso it enforces the same public-method inventory rules.wasm-coverage.jsonto includeClass.methodentries or per-classmethods[]inventories.After the method inventory is measurable, port one coherent eligible group per run, prioritizing CPU-heavy core operations such as numeric reductions, search/sort helpers, align/reindex/index operations, and vectorized scalar transforms.
For every run:
ts-only-ineligible.Blocked Stop Condition
Stop substantive work and comment instead of guessing if:
The blocked comment should include the exact command output, the functions/methods or files affected, what is known, and the smallest maintainer decision or environment change needed to continue.