Arborix -> Arboricx rename

This commit is contained in:
2026-05-08 09:12:20 -05:00
parent e3117e3ac8
commit 343ecbf4c4
29 changed files with 315 additions and 324 deletions

View File

@@ -1,17 +1,17 @@
{
"name": "arborix-runtime",
"name": "arboricx-runtime",
"version": "0.1.0",
"description": "Arborix portable bundle runtime — JavaScript reference implementation",
"description": "Arboricx portable bundle runtime — JavaScript reference implementation",
"type": "module",
"main": "src/bundle.js",
"bin": {
"arborix-run": "src/cli.js"
"arboricx-run": "src/cli.js"
},
"scripts": {
"test": "node --test test/*.test.js",
"inspect": "node src/cli.js inspect",
"run": "node src/cli.js run"
},
"keywords": ["arborix", "tree-calculus", "trie", "runtime"],
"keywords": ["arboricx", "tree-calculus", "trie", "runtime"],
"license": "MIT"
}

View File

@@ -1,9 +1,9 @@
/**
* bundle.js — Parse an Arborix portable bundle binary into a JavaScript object.
* bundle.js — Parse an Arboricx portable bundle binary into a JavaScript object.
*
* Format (v1):
* Header (32 bytes):
* Magic 8B "ARBORIX\0"
* Magic 8B "ARBORICX"
* Major 2B u16 BE (must be 1)
* Minor 2B u16 BE
* SectionCount 4B u32 BE
@@ -27,7 +27,7 @@ import { decodeCbor } from "./cbor.js";
// ── Constants ───────────────────────────────────────────────────────────────
const MAGIC = Buffer.from([0x41, 0x52, 0x42, 0x4f, 0x52, 0x49, 0x58, 0x00]); // "ARBORIX\0"
const MAGIC = Buffer.from([0x41, 0x52, 0x42, 0x4f, 0x52, 0x49, 0x43, 0x58]); // "ARBORICX"
const HEADER_LENGTH = 32;
const SECTION_ENTRY_LENGTH = 60;
const SECTION_MANIFEST = 1;
@@ -69,7 +69,7 @@ export function parseBundle(buffer) {
// Check magic
if (!buffer.slice(0, 8).equals(MAGIC)) {
throw new Error("invalid magic: expected ARBORIX\\0");
throw new Error("invalid magic: expected ARBORICX");
}
// Parse header

View File

@@ -1,5 +1,5 @@
/**
* cbor.js — Minimal CBOR decoder for the Arborix manifest format.
* cbor.js — Minimal CBOR decoder for the Arboricx manifest format.
*
* Decodes the canonical CBOR produced by the Haskell cborg library:
* - Maps: major type 5 (0xa0 + length)

View File

@@ -1,6 +1,6 @@
#!/usr/bin/env node
/**
* cli.js — Minimal CLI for inspecting and running Arborix bundles.
* cli.js — Minimal CLI for inspecting and running Arboricx bundles.
*
* Usage:
* node cli.js inspect <bundle>
@@ -240,7 +240,7 @@ switch (command) {
break;
}
default:
console.log("Arborix JS Runtime");
console.log("Arboricx JS Runtime");
console.log("");
console.log("Usage:");
console.log(" node cli.js inspect <bundle>");

View File

@@ -13,7 +13,7 @@
* Throws on violation.
*/
export function validateManifest(manifest) {
if (manifest.schema !== "arborix.bundle.manifest.v1") {
if (manifest.schema !== "arboricx.bundle.manifest.v1") {
throw new Error(
`unsupported manifest schema: ${manifest.schema}`
);
@@ -33,12 +33,12 @@ export function validateManifest(manifest) {
`unsupported node hash algorithm: ${tree.nodeHash.algorithm}`
);
}
if (tree.nodeHash.domain !== "arborix.merkle.node.v1") {
if (tree.nodeHash.domain !== "arboricx.merkle.node.v1") {
throw new Error(
`unsupported node hash domain: ${tree.nodeHash.domain}`
);
}
if (tree.nodePayload !== "arborix.merkle.payload.v1") {
if (tree.nodePayload !== "arboricx.merkle.payload.v1") {
throw new Error(`unsupported node payload: ${tree.nodePayload}`);
}
@@ -46,7 +46,7 @@ export function validateManifest(manifest) {
if (runtime.semantics !== "tree-calculus.v1") {
throw new Error(`unsupported runtime semantics: ${runtime.semantics}`);
}
if (runtime.abi !== "arborix.abi.tree.v1") {
if (runtime.abi !== "arboricx.abi.tree.v1") {
throw new Error(`unsupported runtime ABI: ${runtime.abi}`);
}
if (runtime.capabilities && runtime.capabilities.length > 0) {

View File

@@ -7,14 +7,14 @@
* Fork: 0x02 || left_hash (32 bytes raw) || right_hash (32 bytes raw)
*
* Hash computation:
* hash = SHA256( "arborix.merkle.node.v1" || 0x00 || node_payload )
* hash = SHA256( "arboricx.merkle.node.v1" || 0x00 || node_payload )
*/
import { createHash } from "node:crypto";
// ── Constants ───────────────────────────────────────────────────────────────
const DOMAIN_TAG = "arborix.merkle.node.v1";
const DOMAIN_TAG = "arboricx.merkle.node.v1";
const HASH_LENGTH = 32; // raw hash bytes
const HEX_LENGTH = 64; // hex-encoded hash length

View File

@@ -19,7 +19,7 @@ const fixtureDir = "../../test/fixtures";
describe("bundle parsing", () => {
it("valid bundle parses header and sections", () => {
const bundle = parseBundle(
readFileSync(`${fixtureDir}/id.arborix`)
readFileSync(`${fixtureDir}/id.arboricx`)
);
strictEqual(bundle.version, "1.0");
strictEqual(bundle.sectionCount, 2);
@@ -29,23 +29,23 @@ describe("bundle parsing", () => {
it("parseManifest returns valid manifest", () => {
const manifest = parseManifest(
readFileSync(`${fixtureDir}/id.arborix`)
readFileSync(`${fixtureDir}/id.arboricx`)
);
strictEqual(manifest.schema, "arborix.bundle.manifest.v1");
strictEqual(manifest.schema, "arboricx.bundle.manifest.v1");
strictEqual(manifest.bundleType, "tree-calculus-executable-object");
strictEqual(manifest.closure, "complete");
strictEqual(manifest.tree.calculus, "tree-calculus.v1");
strictEqual(manifest.tree.nodeHash.algorithm, "sha256");
strictEqual(manifest.tree.nodeHash.domain, "arborix.merkle.node.v1");
strictEqual(manifest.tree.nodeHash.domain, "arboricx.merkle.node.v1");
strictEqual(manifest.runtime.semantics, "tree-calculus.v1");
strictEqual(manifest.runtime.abi, "arborix.abi.tree.v1");
strictEqual(manifest.runtime.abi, "arboricx.abi.tree.v1");
});
});
describe("hash verification", () => {
it("valid bundle nodes verify", () => {
const data = bundleParseNodeSection(
readFileSync(`${fixtureDir}/id.arborix`)
readFileSync(`${fixtureDir}/id.arboricx`)
);
const { nodeMap } = parseNodes(data);
const { verified } = verifyNodeHashes(nodeMap);
@@ -62,20 +62,20 @@ describe("errors", () => {
it("unsupported version fails", () => {
const buf = Buffer.alloc(32, 0);
buf.write("ARBORIX\0", 0, 8);
buf.write("ARBORICX", 0, 8);
buf.writeUInt16BE(2, 8); // major version 2
throws(() => parseBundle(buf), /unsupported bundle major version/);
});
it("bad section digest fails", () => {
const buf = readFileSync(`${fixtureDir}/id.arborix`);
const buf = readFileSync(`${fixtureDir}/id.arboricx`);
// Corrupt one byte in the manifest section
buf[152] ^= 0x01;
throws(() => parseBundle(buf), /digest mismatch/);
});
it("truncated bundle fails", () => {
const buf = readFileSync(`${fixtureDir}/id.arborix`);
const buf = readFileSync(`${fixtureDir}/id.arboricx`);
const truncated = buf.slice(0, 40);
throws(() => parseBundle(truncated), /truncated/);
});
@@ -83,33 +83,33 @@ describe("errors", () => {
it("missing nodes section fails", () => {
// Build a bundle with only manifest entry in the directory (1 section instead of 2)
const header = Buffer.alloc(32, 0);
header.write("ARBORIX\0", 0, 8);
header.write("ARBORICX", 0, 8);
header.writeUInt16BE(1, 8); // major version
header.writeUInt16BE(0, 10); // minor version
header.writeUInt32BE(1, 12); // 1 section
// Build a manifest JSON
const manifestObj = {
schema: "arborix.bundle.manifest.v1",
schema: "arboricx.bundle.manifest.v1",
bundleType: "tree-calculus-executable-object",
tree: {
calculus: "tree-calculus.v1",
nodeHash: {
algorithm: "sha256",
domain: "arborix.merkle.node.v1"
domain: "arboricx.merkle.node.v1"
},
nodePayload: "arborix.merkle.payload.v1"
nodePayload: "arboricx.merkle.payload.v1"
},
runtime: {
semantics: "tree-calculus.v1",
evaluation: "normal-order",
abi: "arborix.abi.tree.v1",
abi: "arboricx.abi.tree.v1",
capabilities: []
},
closure: "complete",
roots: [{ hash: Buffer.alloc(32).toString("hex"), role: "default" }],
exports: [{ name: "root", root: Buffer.alloc(32).toString("hex"), kind: "term", abi: "arborix.abi.tree.v1" }],
metadata: { createdBy: "arborix" }
exports: [{ name: "root", root: Buffer.alloc(32).toString("hex"), kind: "term", abi: "arboricx.abi.tree.v1" }],
metadata: { createdBy: "arboricx" }
};
const manifestJson = JSON.stringify(manifestObj);
const manifestBytes = Buffer.from(manifestJson);

View File

@@ -51,35 +51,35 @@ describe("merkle — computeNodeHash", () => {
strictEqual(hash.length, 64);
});
it("Leaf hash matches expected Arborix domain", () => {
it("Leaf hash matches expected Arboricx domain", () => {
const leaf = { type: "leaf" };
const hash = computeNodeHash(leaf);
strictEqual(hash, "e54db458aa8e94782f7c61ad6c1f19a1c0c6fca7ffe53674f0d2bc5ff7ab02ff");
strictEqual(hash, "92b8a9796dbeafbcd36757535876256392170d137bf36b319d77f11a37112158");
});
});
describe("merkle — node section parsing", () => {
const fixtureDir = "../../test/fixtures";
it("parses id.arborix with correct node count", () => {
it("parses id.arboricx with correct node count", () => {
const data = bundleParseNodeSection(
readFileSync(`${fixtureDir}/id.arborix`)
readFileSync(`${fixtureDir}/id.arboricx`)
);
const { nodeMap } = parseNodeSection(data);
strictEqual(nodeMap.size, 4);
});
it("parses true.arborix with correct node count", () => {
it("parses true.arboricx with correct node count", () => {
const data = bundleParseNodeSection(
readFileSync(`${fixtureDir}/true.arborix`)
readFileSync(`${fixtureDir}/true.arboricx`)
);
const { nodeMap } = parseNodeSection(data);
strictEqual(nodeMap.size, 2);
});
it("parses false.arborix with correct node count", () => {
it("parses false.arboricx with correct node count", () => {
const data = bundleParseNodeSection(
readFileSync(`${fixtureDir}/false.arborix`)
readFileSync(`${fixtureDir}/false.arboricx`)
);
const { nodeMap } = parseNodeSection(data);
strictEqual(nodeMap.size, 1);
@@ -89,29 +89,29 @@ describe("merkle — node section parsing", () => {
describe("merkle — hash verification", () => {
const fixtureDir = "../../test/fixtures";
it("id.arborix nodes all verify", () => {
it("id.arboricx nodes all verify", () => {
const data = bundleParseNodeSection(
readFileSync(`${fixtureDir}/id.arborix`)
readFileSync(`${fixtureDir}/id.arboricx`)
);
const { nodeMap } = parseNodeSection(data);
const { verified, mismatches } = verifyNodeHashes(nodeMap);
ok(verified, "id.arborix node hashes should verify");
ok(verified, "id.arboricx node hashes should verify");
strictEqual(mismatches.length, 0);
});
it("true.arborix nodes all verify", () => {
it("true.arboricx nodes all verify", () => {
const data = bundleParseNodeSection(
readFileSync(`${fixtureDir}/true.arborix`)
readFileSync(`${fixtureDir}/true.arboricx`)
);
const { nodeMap } = parseNodeSection(data);
const { verified, mismatches } = verifyNodeHashes(nodeMap);
ok(verified, "true.arborix node hashes should verify");
ok(verified, "true.arboricx node hashes should verify");
strictEqual(mismatches.length, 0);
});
it("corrupted node payload fails hash verification", () => {
const data = bundleParseNodeSection(
readFileSync(`${fixtureDir}/id.arborix`)
readFileSync(`${fixtureDir}/id.arboricx`)
);
const { nodeMap } = parseNodeSection(data);
// Find a stem node to corrupt
@@ -137,23 +137,23 @@ describe("merkle — hash verification", () => {
describe("merkle — closure verification", () => {
const fixtureDir = "../../test/fixtures";
it("id.arborix has complete closure", () => {
it("id.arboricx has complete closure", () => {
const data = bundleParseNodeSection(
readFileSync(`${fixtureDir}/id.arborix`)
readFileSync(`${fixtureDir}/id.arboricx`)
);
const { nodeMap } = parseNodeSection(data);
const { complete, missing } = verifyClosure(nodeMap);
ok(complete, "id.arborix should have complete closure");
ok(complete, "id.arboricx should have complete closure");
strictEqual(missing.length, 0);
});
it("verifyRootClosure checks transitive reachability", () => {
const data = bundleParseNodeSection(
readFileSync(`${fixtureDir}/id.arborix`)
readFileSync(`${fixtureDir}/id.arboricx`)
);
const { nodeMap } = parseNodeSection(data);
// Use the actual root hash from the fixture's manifest
const manifest = parseManifest(readFileSync(`${fixtureDir}/id.arborix`));
const manifest = parseManifest(readFileSync(`${fixtureDir}/id.arboricx`));
const rootHash = manifest.exports[0].root;
const { complete, missingRoots } = verifyRootClosure(nodeMap, rootHash);
ok(complete, "root should be reachable");
@@ -162,7 +162,7 @@ describe("merkle — closure verification", () => {
it("parseNodeSection returns correct node count", () => {
const data = bundleParseNodeSection(
readFileSync(`${fixtureDir}/id.arborix`)
readFileSync(`${fixtureDir}/id.arboricx`)
);
const result = parseNodeSection(data);
strictEqual(result.count, 4);

View File

@@ -9,8 +9,8 @@ import { buildTreeFromNodeMap } from "../src/cli.js";
const fixtureDir = "../../test/fixtures";
describe("run bundle — id.arborix", () => {
const bundle = readFileSync(`${fixtureDir}/id.arborix`);
describe("run bundle — id.arboricx", () => {
const bundle = readFileSync(`${fixtureDir}/id.arboricx`);
const manifest = parseManifest(bundle);
const nodeSectionData = bundleParseNodeSection(bundle);
const { nodeMap } = parseNodes(nodeSectionData);
@@ -37,8 +37,8 @@ describe("run bundle — id.arborix", () => {
});
});
describe("run bundle — true.arborix", () => {
const bundle = readFileSync(`${fixtureDir}/true.arborix`);
describe("run bundle — true.arboricx", () => {
const bundle = readFileSync(`${fixtureDir}/true.arboricx`);
const manifest = parseManifest(bundle);
const nodeSectionData = bundleParseNodeSection(bundle);
const { nodeMap } = parseNodes(nodeSectionData);
@@ -61,8 +61,8 @@ describe("run bundle — true.arborix", () => {
});
});
describe("run bundle — false.arborix", () => {
const bundle = readFileSync(`${fixtureDir}/false.arborix`);
describe("run bundle — false.arboricx", () => {
const bundle = readFileSync(`${fixtureDir}/false.arboricx`);
const manifest = parseManifest(bundle);
const nodeSectionData = bundleParseNodeSection(bundle);
const { nodeMap } = parseNodes(nodeSectionData);
@@ -83,8 +83,8 @@ describe("run bundle — false.arborix", () => {
});
});
describe("run bundle — notQ.arborix", () => {
const bundle = readFileSync(`${fixtureDir}/notQ.arborix`);
describe("run bundle — notQ.arboricx", () => {
const bundle = readFileSync(`${fixtureDir}/notQ.arboricx`);
const manifest = parseManifest(bundle);
const nodeSectionData = bundleParseNodeSection(bundle);
const { nodeMap } = parseNodes(nodeSectionData);
@@ -100,7 +100,7 @@ describe("run bundle — notQ.arborix", () => {
});
describe("run bundle — missing export", () => {
const bundle = readFileSync(`${fixtureDir}/id.arborix`);
const bundle = readFileSync(`${fixtureDir}/id.arboricx`);
const manifest = parseManifest(bundle);
it("nonexistent export fails clearly", () => {
@@ -109,8 +109,8 @@ describe("run bundle — missing export", () => {
});
describe("run bundle — auto-select", () => {
// true.arborix has only one export, should auto-select
const bundle = readFileSync(`${fixtureDir}/true.arborix`);
// true.arboricx has only one export, should auto-select
const bundle = readFileSync(`${fixtureDir}/true.arboricx`);
const manifest = parseManifest(bundle);
it("single export auto-selects", () => {