@opus-magnum/rubedo 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,3 @@
1
+ export { RubedoForge } from "./v1/RubedoForge";
2
+ export { stableStringify, canonicalizeJson, type Json } from "./v1/canonical-json";
3
+ export { RUBEDO_CONTRACT_V1 } from "./v1/contract";
package/dist/index.js ADDED
@@ -0,0 +1,3 @@
1
+ export { RubedoForge } from "./v1/RubedoForge";
2
+ export { stableStringify, canonicalizeJson } from "./v1/canonical-json";
3
+ export { RUBEDO_CONTRACT_V1 } from "./v1/contract";
@@ -0,0 +1,4 @@
1
+ import type { IRubedoForge, RubedoArtifact, RubedoInput } from "../../../interfaces/RUBEDO/v1/IRubedoForge";
2
+ export declare class RubedoForge implements IRubedoForge {
3
+ forge(input: RubedoInput): RubedoArtifact;
4
+ }
@@ -0,0 +1,19 @@
1
+ import crypto from "node:crypto";
2
+ import { stableStringify } from "./canonical-json";
3
+ function sha256Hex(s) {
4
+ return crypto.createHash("sha256").update(s, "utf-8").digest("hex");
5
+ }
6
+ export class RubedoForge {
7
+ forge(input) {
8
+ const synopsis = [...input.synopsis].slice().sort((a, b) => (a.id < b.id ? -1 : a.id > b.id ? 1 : 0));
9
+ const payload = { synopsis };
10
+ const canonical = stableStringify(payload);
11
+ const fingerprint = sha256Hex(canonical);
12
+ return {
13
+ artifact_id: "RUBEDO::v1::" + fingerprint.slice(0, 12),
14
+ fingerprint,
15
+ payload,
16
+ stats: { count: input.synopsis.length }
17
+ };
18
+ }
19
+ }
@@ -0,0 +1,6 @@
1
+ /**
2
+ * Canonical JSON helpers
3
+ * Goal: stable structural representation independent of input key order.
4
+ */
5
+ export declare function canonicalizeJson(value: unknown): unknown;
6
+ export declare function stableStringify(value: unknown): string;
@@ -0,0 +1,29 @@
1
+ /**
2
+ * Canonical JSON helpers
3
+ * Goal: stable structural representation independent of input key order.
4
+ */
5
+ export function canonicalizeJson(value) {
6
+ if (value === null)
7
+ return null;
8
+ const t = typeof value;
9
+ // primitives
10
+ if (t === "string" || t === "number" || t === "boolean")
11
+ return value;
12
+ // arrays
13
+ if (Array.isArray(value))
14
+ return value.map(v => canonicalizeJson(v));
15
+ // objects
16
+ if (t === "object") {
17
+ const obj = value;
18
+ const keys = Object.keys(obj).sort((a, b) => (a < b ? -1 : a > b ? 1 : 0));
19
+ const out = {};
20
+ for (const k of keys)
21
+ out[k] = canonicalizeJson(obj[k]);
22
+ return out;
23
+ }
24
+ // non-JSON types (undefined, bigint, function, symbol) — deterministic fallback
25
+ return null;
26
+ }
27
+ export function stableStringify(value) {
28
+ return JSON.stringify(canonicalizeJson(value));
29
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,25 @@
1
+ import { RUBEDO_CONTRACT_V1 } from "./contract.js";
2
+ import { RubedoForge } from "./RubedoForge.js";
3
+ function assertEq(a, b, msg) {
4
+ if (a !== b)
5
+ throw new Error(msg + `: ${String(a)} !== ${String(b)}`);
6
+ }
7
+ console.log(`[RUBEDO_CONTRACT] version=${RUBEDO_CONTRACT_V1.version}`);
8
+ const f = new RubedoForge();
9
+ const fpOf = (synopsis) => f.forge({ synopsis }).fingerprint;
10
+ // Key order independence (value object different key order must be same fingerprint)
11
+ const s1 = [{ id: "key:a", kind: "KEY", value: { a: 1, b: 2, c: { y: 9, z: 0 } } }];
12
+ const s2 = [{ id: "key:a", kind: "KEY", value: { b: 2, a: 1, c: { z: 0, y: 9 } } }];
13
+ assertEq(fpOf(s1), fpOf(s2), "KEY_ORDER_INDEPENDENCE");
14
+ // Synopsis order independence
15
+ const t1 = [
16
+ { id: "k:1", kind: "KEY", value: 1 },
17
+ { id: "k:2", kind: "KEY", value: 2 }
18
+ ];
19
+ const t2 = [
20
+ { id: "k:2", kind: "KEY", value: 2 },
21
+ { id: "k:1", kind: "KEY", value: 1 }
22
+ ];
23
+ assertEq(fpOf(t1), fpOf(t2), "SYNOPSIS_ORDER_INDEPENDENCE");
24
+ console.log("[PASS] RUBEDO package contract invariants satisfied");
25
+ process.exit(0);
@@ -0,0 +1,22 @@
1
+ /**
2
+ * RUBEDO CONTRACT v1
3
+ * Immutable invariants for fingerprint generation.
4
+ */
5
+ export declare const RUBEDO_CONTRACT_V1: {
6
+ readonly version: 1;
7
+ readonly fingerprint: {
8
+ readonly algorithm: "sha256";
9
+ readonly encoding: "hex";
10
+ readonly source: "canonical-json";
11
+ readonly deterministic: true;
12
+ readonly orderIndependent: true;
13
+ };
14
+ readonly payload: {
15
+ readonly fields: readonly ["synopsis"];
16
+ readonly synopsis: {
17
+ readonly required: true;
18
+ readonly orderedBy: "id";
19
+ };
20
+ };
21
+ readonly invariants: readonly ["Same semantic input MUST produce identical fingerprint", "Key order MUST NOT affect fingerprint", "Synopsis order MUST NOT affect fingerprint", "Fingerprint MUST be reproducible offline"];
22
+ };
@@ -0,0 +1,27 @@
1
+ /**
2
+ * RUBEDO CONTRACT v1
3
+ * Immutable invariants for fingerprint generation.
4
+ */
5
+ export const RUBEDO_CONTRACT_V1 = {
6
+ version: 1,
7
+ fingerprint: {
8
+ algorithm: "sha256",
9
+ encoding: "hex",
10
+ source: "canonical-json",
11
+ deterministic: true,
12
+ orderIndependent: true
13
+ },
14
+ payload: {
15
+ fields: ["synopsis"],
16
+ synopsis: {
17
+ required: true,
18
+ orderedBy: "id"
19
+ }
20
+ },
21
+ invariants: [
22
+ "Same semantic input MUST produce identical fingerprint",
23
+ "Key order MUST NOT affect fingerprint",
24
+ "Synopsis order MUST NOT affect fingerprint",
25
+ "Fingerprint MUST be reproducible offline"
26
+ ]
27
+ };
package/package.json ADDED
@@ -0,0 +1,21 @@
1
+ {
2
+ "name": "@opus-magnum/rubedo",
3
+ "version": "1.0.0",
4
+ "type": "module",
5
+ "main": "dist/index.js",
6
+ "types": "dist/index.d.ts",
7
+ "exports": {
8
+ ".": {
9
+ "types": "./dist/index.d.ts",
10
+ "default": "./dist/index.js"
11
+ }
12
+ },
13
+ "scripts": {
14
+ "build": "npx tsc -p tsconfig.build.json",
15
+ "test:contract": "npm run -s build >/dev/null && node dist/v1/contract-test.js"
16
+ },
17
+ "devDependencies": {
18
+ "typescript": "^5.9.3",
19
+ "@types/node": "^22.19.3"
20
+ }
21
+ }
package/src/index.ts ADDED
@@ -0,0 +1,3 @@
1
+ export { RubedoForge } from "./v1/RubedoForge";
2
+ export { stableStringify, canonicalizeJson, type Json } from "./v1/canonical-json";
3
+ export { RUBEDO_CONTRACT_V1 } from "./v1/contract";
@@ -0,0 +1,26 @@
1
+ import crypto from "node:crypto";
2
+ import type { IRubedoForge, RubedoArtifact, RubedoInput } from "../../../interfaces/RUBEDO/v1/IRubedoForge";
3
+ import { stableStringify } from "./canonical-json";
4
+
5
+ function sha256Hex(s: string): string {
6
+ return crypto.createHash("sha256").update(s, "utf-8").digest("hex");
7
+ }
8
+
9
+ export class RubedoForge implements IRubedoForge {
10
+
11
+ forge(input: RubedoInput): RubedoArtifact {
12
+ const synopsis = [...input.synopsis].slice().sort((a, b) => (a.id < b.id ? -1 : a.id > b.id ? 1 : 0));
13
+ const payload = { synopsis };
14
+
15
+ const canonical = stableStringify(payload);
16
+
17
+ const fingerprint = sha256Hex(canonical);
18
+
19
+ return {
20
+ artifact_id: "RUBEDO::v1::" + fingerprint.slice(0, 12),
21
+ fingerprint,
22
+ payload,
23
+ stats: { count: input.synopsis.length }
24
+ };
25
+ }
26
+ }
@@ -0,0 +1,32 @@
1
+ /**
2
+ * Canonical JSON helpers
3
+ * Goal: stable structural representation independent of input key order.
4
+ */
5
+
6
+ export function canonicalizeJson(value: unknown): unknown {
7
+ if (value === null) return null;
8
+
9
+ const t = typeof value;
10
+
11
+ // primitives
12
+ if (t === "string" || t === "number" || t === "boolean") return value;
13
+
14
+ // arrays
15
+ if (Array.isArray(value)) return value.map(v => canonicalizeJson(v));
16
+
17
+ // objects
18
+ if (t === "object") {
19
+ const obj = value as Record<string, unknown>;
20
+ const keys = Object.keys(obj).sort((a, b) => (a < b ? -1 : a > b ? 1 : 0));
21
+ const out: Record<string, unknown> = {};
22
+ for (const k of keys) out[k] = canonicalizeJson(obj[k]);
23
+ return out;
24
+ }
25
+
26
+ // non-JSON types (undefined, bigint, function, symbol) — deterministic fallback
27
+ return null;
28
+ }
29
+
30
+ export function stableStringify(value: unknown): string {
31
+ return JSON.stringify(canonicalizeJson(value));
32
+ }
@@ -0,0 +1,30 @@
1
+ import { RUBEDO_CONTRACT_V1 } from "./contract.js";
2
+ import { RubedoForge } from "./RubedoForge.js";
3
+
4
+ function assertEq(a: any, b: any, msg: string) {
5
+ if (a !== b) throw new Error(msg + `: ${String(a)} !== ${String(b)}`);
6
+ }
7
+
8
+ console.log(`[RUBEDO_CONTRACT] version=${RUBEDO_CONTRACT_V1.version}`);
9
+
10
+ const f = new RubedoForge();
11
+ const fpOf = (synopsis: any[]) => f.forge({ synopsis }).fingerprint;
12
+
13
+ // Key order independence (value object different key order must be same fingerprint)
14
+ const s1 = [{ id: "key:a", kind: "KEY", value: { a: 1, b: 2, c: { y: 9, z: 0 } } }];
15
+ const s2 = [{ id: "key:a", kind: "KEY", value: { b: 2, a: 1, c: { z: 0, y: 9 } } }];
16
+ assertEq(fpOf(s1), fpOf(s2), "KEY_ORDER_INDEPENDENCE");
17
+
18
+ // Synopsis order independence
19
+ const t1 = [
20
+ { id: "k:1", kind: "KEY", value: 1 },
21
+ { id: "k:2", kind: "KEY", value: 2 }
22
+ ];
23
+ const t2 = [
24
+ { id: "k:2", kind: "KEY", value: 2 },
25
+ { id: "k:1", kind: "KEY", value: 1 }
26
+ ];
27
+ assertEq(fpOf(t1), fpOf(t2), "SYNOPSIS_ORDER_INDEPENDENCE");
28
+
29
+ console.log("[PASS] RUBEDO package contract invariants satisfied");
30
+ process.exit(0);
@@ -0,0 +1,31 @@
1
+ /**
2
+ * RUBEDO CONTRACT v1
3
+ * Immutable invariants for fingerprint generation.
4
+ */
5
+
6
+ export const RUBEDO_CONTRACT_V1 = {
7
+ version: 1 as const,
8
+
9
+ fingerprint: {
10
+ algorithm: "sha256",
11
+ encoding: "hex",
12
+ source: "canonical-json",
13
+ deterministic: true,
14
+ orderIndependent: true
15
+ },
16
+
17
+ payload: {
18
+ fields: ["synopsis"],
19
+ synopsis: {
20
+ required: true,
21
+ orderedBy: "id"
22
+ }
23
+ },
24
+
25
+ invariants: [
26
+ "Same semantic input MUST produce identical fingerprint",
27
+ "Key order MUST NOT affect fingerprint",
28
+ "Synopsis order MUST NOT affect fingerprint",
29
+ "Fingerprint MUST be reproducible offline"
30
+ ]
31
+ } as const;
@@ -0,0 +1,13 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "ES2022",
4
+ "module": "ES2022",
5
+ "moduleResolution": "Bundler",
6
+ "declaration": true,
7
+ "outDir": "dist",
8
+ "rootDir": "src",
9
+ "strict": true,
10
+ "skipLibCheck": true
11
+ },
12
+ "include": ["src/**/*"]
13
+ }