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

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .gitattributes
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,5 @@
web/src/generated/** text eol=lf
# The module is binary: no line-ending conversion.
web/src/generated/wasm/*.wasm binary
# Preserve the pinned unified diff bytes, including required blank context lines.
web/engine-patches/local-zone-purge.patch -text -whitespace
2 changes: 2 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@ jobs:
cache-dependency-path: web/package-lock.json
- run: npm ci
- run: node scripts/verify-engine.mjs
- name: Test engine build provenance
run: node --test scripts/engine-source.test.mjs
- name: Check foundation
if: hashFiles('web/index.html') == ''
run: npm run check:data
Expand Down
14 changes: 14 additions & 0 deletions docs/architecture.md
Original file line number Diff line number Diff line change
Expand Up @@ -72,3 +72,17 @@ matching close-request acknowledgement. The WebView profile remains separate
from browser profiles; builds do not migrate or erase it. This is an unsigned
local build, with no installer or public release qualification. See
[Windows build and boundaries](windows.md).

## Local content domain foundation

The library and collections domains add versioned, encrypted local records for
metadata/history, Study, Clips and a separately protected Journal vault. Local
source references and search projections validate their account/epoch ownership.
These contracts and adapters are independent of the workspace presentation.

Whole-workspace erase uses the pinned engine's reviewed local-zone purge
adaptation to remove content keys and cached uploads before staging fresh empty
records atomically. Account identity, device trust, approval checkpoints and
unrelated zones remain. Failed writes preserve the prior snapshot. This is local
erasure, not remote deletion or memory zeroization. Preserve profiles and exports
when reverting: reverting code does not downgrade already-written versioned data.
1,222 changes: 1,222 additions & 0 deletions web/engine-patches/local-zone-purge.patch

Large diffs are not rendered by default.

26 changes: 19 additions & 7 deletions web/engine-pin.json
Original file line number Diff line number Diff line change
@@ -1,14 +1,26 @@
{
"engineRepository": "https://github.com/orbyn-dev/orbyn-engine",
"engineCommit": "f61b16de7ffa23bce98aae57a6781b6e732f925b",
"artifactRepository": "https://github.com/orbyn-dev/orbyn-planner",
"artifactCommit": "c2d57f8ce673f3f9fcfcfb60ce3dde9aae1d0d16",
"artifactPath": "web/src/generated/wasm",
"license": "MIT OR Apache-2.0",
"sha256": {
"orbyn_wasm.js": "c6391fc3e3898c277615bc32d7ded270b35e78be44d69d69ecf45e6a91a3fc15",
"orbyn_wasm.d.ts": "deb9009a1d83d351b7a3b0c45dda37bb7c25f3689bd3e122d4bfa30c3d58e06b",
"orbyn_wasm_bg.wasm": "b5cf49fb1aff449a8e320fe6e8824f49532cbd29a46823219f66cdf42c0343c5",
"orbyn_wasm_bg.wasm.d.ts": "5a3b393bf533006638e9e4b9df120dc1c7f43d4c1720c808d39fc36ee18c8a23"
"orbyn_wasm.js": "bdfeec85768336b5400faa8c412d13c9d389b1629b513b7d1130263b86abe7ec",
"orbyn_wasm.d.ts": "73fb363678725e45127abe3cc33b290754e9966a73d790bad6a6ed86e314bd47",
"orbyn_wasm_bg.wasm": "7015f110011e70cd9e830012e1d8e1b3d76698ac588dda76086b58592a7c49bb",
"orbyn_wasm_bg.wasm.d.ts": "d0c8bfe530013c5182a0333fbf07b612961eb9b969479e366a3721a78d74f031"
},
"enginePatch": {
"path": "engine-patches/local-zone-purge.patch",
"sha256": "d77ee7f2fc78fe58640a9dd26c4162246b1333ae391b2c24c0db25be8f94629f",
"files": [
"crates/orbyn-client/src/lib.rs",
"crates/orbyn-wasm/README.md",
"crates/orbyn-wasm/src/lib.rs",
"crates/orbyn-wasm/src/tests.rs",
"docs/architecture.md"
]
},
"build": {
"rustc": "rustc 1.99.1 (58cf8f9c3 2026-10-13)",
"wasmBindgen": "0.2.130"
}
}
39 changes: 39 additions & 0 deletions web/public/licenses/Unicode-3.0.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
UNICODE LICENSE V3

COPYRIGHT AND PERMISSION NOTICE

Copyright © 1991-2026 Unicode, Inc.

NOTICE TO USER: Carefully read the following legal agreement. BY
DOWNLOADING, INSTALLING, COPYING OR OTHERWISE USING DATA FILES, AND/OR
SOFTWARE, YOU UNEQUIVOCALLY ACCEPT, AND AGREE TO BE BOUND BY, ALL OF THE
TERMS AND CONDITIONS OF THIS AGREEMENT. IF YOU DO NOT AGREE, DO NOT
DOWNLOAD, INSTALL, COPY, DISTRIBUTE OR USE THE DATA FILES OR SOFTWARE.

Permission is hereby granted, free of charge, to any person obtaining a
copy of data files and any associated documentation (the "Data Files") or
software and any associated documentation (the "Software") to deal in the
Data Files or Software without restriction, including without limitation
the rights to use, copy, modify, merge, publish, distribute, and/or sell
copies of the Data Files or Software, and to permit persons to whom the
Data Files or Software are furnished to do so, provided that either (a)
this copyright and permission notice appear with all copies of the Data
Files or Software, or (b) this copyright and permission notice appear in
associated Documentation.

THE DATA FILES AND SOFTWARE ARE PROVIDED "AS IS", WITHOUT WARRANTY OF ANY
KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT OF
THIRD PARTY RIGHTS.

IN NO EVENT SHALL THE COPYRIGHT HOLDER OR HOLDERS INCLUDED IN THIS NOTICE
BE LIABLE FOR ANY CLAIM, OR ANY SPECIAL INDIRECT OR CONSEQUENTIAL DAMAGES,
OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS,
WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION,
ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THE DATA
FILES OR SOFTWARE.

Except as contained in this notice, the name of a copyright holder shall
not be used in advertising or otherwise to promote the sale, use or other
dealings in these Data Files or Software without prior written
authorization of the copyright holder.
163 changes: 121 additions & 42 deletions web/scripts/build-engine.mjs
Original file line number Diff line number Diff line change
@@ -1,51 +1,130 @@
import { readFile } from "node:fs/promises";
import { fileURLToPath } from "node:url";
import { fileURLToPath, pathToFileURL } from "node:url";
import { spawnSync } from "node:child_process";
const root = new URL("../", import.meta.url);
const pin = JSON.parse(
await readFile(new URL("engine-pin.json", root), "utf8"),
);
const source = process.argv[2];
if (!source)
throw new Error(
"Usage: node scripts/build-engine.mjs <pinned-engine-checkout> (requires Cargo and matching wasm-bindgen).",
);
const check = spawnSync("git", ["rev-parse", "HEAD"], {
cwd: source,
encoding: "utf8",
});
if (check.status !== 0 || check.stdout.trim() !== pin.engineCommit)
throw new Error("Engine checkout must match engine-pin.json.");
for (const [command, args] of [
[
"cargo",
import { resolve, join } from "node:path";
import { homedir } from "node:os";
import { verifyEngineSource } from "./engine-source.mjs";
export async function buildEngine({
source,
root,
pin,
environment = process.env,
run = spawnSync,
}) {
await verifyEngineSource(source, root, pin);
const bindgen = environment.ORBYN_WASM_BINDGEN ?? "wasm-bindgen";
const cargo = environment.ORBYN_CARGO ?? "cargo";
const compiler = environment.ORBYN_RUSTC ?? environment.RUSTC ?? "rustc";
if (environment.RUSTC_WRAPPER || environment.RUSTC_WORKSPACE_WRAPPER)
throw new Error("Engine build does not accept compiler wrappers.");
// Cargo gives encoded flags priority over other environment flags. Refuse
// ambiguous inputs instead of silently changing caller-supplied build options.
for (const [key, value] of Object.entries(environment)) {
if (
value &&
(key === "RUSTFLAGS" ||
key === "CARGO_ENCODED_RUSTFLAGS" ||
key === "CARGO_BUILD_RUSTFLAGS" ||
/^CARGO_TARGET_.+_RUSTFLAGS$/.test(key))
)
throw new Error(
"Engine build does not accept externally supplied Rust flags.",
);
}
const userHome = environment.USERPROFILE || environment.HOME || homedir();
const roots = [
[
resolve(source, environment.CARGO_HOME || join(userHome, ".cargo")),
"/orbyn-build/cargo",
],
[
resolve(source, environment.RUSTUP_HOME || join(userHome, ".rustup")),
"/orbyn-build/rustup",
],
[resolve(source), "/orbyn-build/engine"],
];
const remaps = [];
for (const [path, replacement] of roots) {
if (/[=\x1f]/.test(path))
throw new Error(
"Engine build paths cannot contain '=' or the flags separator.",
);
for (const spelling of new Set([path, path.replaceAll("\\", "/")]))
remaps.push(`--remap-path-prefix=${spelling}=${replacement}`);
}
// Cargo must receive the same compiler that was checked, regardless of its own toolchain selection.
const env = {
...environment,
RUSTC: compiler,
CARGO_ENCODED_RUSTFLAGS: remaps.join("\x1f"),
};
if (pin.build) {
for (const [command, expected] of [
[bindgen, `wasm-bindgen ${pin.build.wasmBindgen}`],
[compiler, pin.build.rustc],
]) {
const result = run(command, ["--version"], {
cwd: source,
encoding: "utf8",
env,
});
if (
result.error ||
result.status !== 0 ||
result.stdout.trim() !== expected
)
throw new Error("Engine build tool version differs from reviewed pin.");
}
}
const target = environment.CARGO_TARGET_DIR
? resolve(source, environment.CARGO_TARGET_DIR)
: resolve(source, "target");
for (const [command, args] of [
[
"build",
"--locked",
"-p",
"orbyn-wasm",
"--release",
"--target",
"wasm32-unknown-unknown",
cargo,
[
"build",
"--locked",
"-p",
"orbyn-wasm",
"--release",
"--target",
"wasm32-unknown-unknown",
],
],
],
[
"wasm-bindgen",
[
"--target",
"web",
"--out-dir",
fileURLToPath(new URL("src/generated/wasm/", root)),
"target/wasm32-unknown-unknown/release/orbyn_wasm.wasm",
bindgen,
[
"--target",
"web",
"--out-dir",
resolve(root, "src/generated/wasm"),
resolve(target, "wasm32-unknown-unknown/release/orbyn_wasm.wasm"),
],
],
],
]) {
const result = spawnSync(command, args, { cwd: source, stdio: "inherit" });
if (result.error || result.status !== 0)
]) {
const result = run(command, args, { cwd: source, stdio: "inherit", env });
if (result.error || result.status !== 0)
throw new Error(
`${command} failed; verify installed Rust target and the wasm-bindgen version in Cargo.lock.`,
);
}
}
if (
process.argv[1] &&
import.meta.url === pathToFileURL(resolve(process.argv[1])).href
) {
const root = fileURLToPath(new URL("../", import.meta.url));
const pin = JSON.parse(
await readFile(resolve(root, "engine-pin.json"), "utf8"),
);
const source = process.argv[2];
if (!source)
throw new Error(
`${command} failed; verify installed Rust target and the wasm-bindgen version in Cargo.lock.`,
"Usage: node scripts/build-engine.mjs <pinned-engine-checkout> (requires Cargo and matching wasm-bindgen).",
);
await buildEngine({ source, root, pin });
console.log(
"Built engine artifacts. Review the generated diff and update verified hashes deliberately; do not assume byte-for-byte reproducibility across Rust versions.",
);
}
console.log(
"Built engine artifacts. Review the generated diff and update verified hashes deliberately; do not assume byte-for-byte reproducibility across Rust versions.",
);
71 changes: 71 additions & 0 deletions web/scripts/engine-source.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
import { readFile } from "node:fs/promises";
import { createHash } from "node:crypto";
import { spawnSync } from "node:child_process";
import { resolve, relative, isAbsolute } from "node:path";
export const sha256 = (bytes) =>
createHash("sha256").update(bytes).digest("hex");
export async function readEnginePatch(root, pin) {
if (!pin.enginePatch) return null;
const patch = pin.enginePatch;
if (
typeof patch.path !== "string" ||
!/^[a-f0-9]{64}$/.test(patch.sha256) ||
!Array.isArray(patch.files) ||
!patch.files.length ||
patch.files.some((file) => typeof file !== "string")
)
throw new Error("Invalid engine patch manifest.");
const path = resolve(root, patch.path),
local = relative(root, path);
if (local.startsWith("..") || isAbsolute(local))
throw new Error("Engine patch must be inside the web package.");
const bytes = await readFile(path);
if (sha256(bytes) !== patch.sha256)
throw new Error("Engine source patch hash mismatch.");
return bytes;
}
export async function verifyEngineSource(source, root, pin) {
const git = (args) => {
const result = spawnSync("git", args, {
cwd: source,
maxBuffer: 16 * 1024 * 1024,
});
if (result.error || result.status !== 0)
throw new Error("Cannot verify engine source checkout.");
return result.stdout;
};
if (git(["rev-parse", "HEAD"]).toString().trim() !== pin.engineCommit)
throw new Error("Engine checkout must match engine-pin.json.");
// Ignore no source files: untracked inputs could influence a build even when the tracked diff matches.
if (git(["ls-files", "--others", "--exclude-standard"]).length)
throw new Error(
"Engine source has untracked inputs. Use a clean isolated checkout.",
);
const diff = git([
"-c",
"core.autocrlf=false",
"diff",
"--binary",
"--no-ext-diff",
"--no-textconv",
"HEAD",
"--",
]);
const patch = await readEnginePatch(root, pin);
if (patch ? !diff.equals(patch) : diff.length !== 0)
throw new Error(
"Engine source changes must exactly match the reviewed patch.",
);
if (patch) {
const files = git(["diff", "--name-only", "-z", "HEAD", "--"])
.toString()
.split("\0")
.filter(Boolean)
.sort();
if (
JSON.stringify(files) !==
JSON.stringify([...pin.enginePatch.files].sort())
)
throw new Error("Engine patch file allowlist mismatch.");
}
}
Loading
Loading