@wenathlan/foundry 2.0.94 → 2.0.118
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.
- package/README.md +0 -0
- package/docs/.gitkeep +0 -0
- package/package.json +35 -6
- package/tests/.gitkeep +0 -0
- package/tests/artifact.test.ts +106 -0
- package/tests/design.test.ts +177 -0
- package/tests/family.test.ts +104 -0
- package/tests/imagestore.test.ts +109 -0
- package/tests/sandbox.test.ts +162 -0
package/README.md
CHANGED
|
File without changes
|
package/docs/.gitkeep
CHANGED
|
File without changes
|
package/package.json
CHANGED
|
@@ -1,18 +1,26 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@wenathlan/foundry",
|
|
3
|
-
"version": "2.0.
|
|
3
|
+
"version": "2.0.118",
|
|
4
4
|
"description": "foundry is the e2b and docker clone interface of the wenathlan family: it drives sandboxes and images while the engine lives in saddle, and ships the documentation set and the tests directory as one package",
|
|
5
5
|
"type": "module",
|
|
6
|
-
"
|
|
6
|
+
"repository": {
|
|
7
|
+
"type": "git",
|
|
8
|
+
"url": "https://github.com/wenathlan/devthink"
|
|
9
|
+
},
|
|
7
10
|
"license": "GPL-3.0-only",
|
|
8
11
|
"author": "Wenathlan",
|
|
9
12
|
"sideEffects": false,
|
|
10
13
|
"scripts": {
|
|
11
14
|
"dev": "vite --host",
|
|
12
15
|
"build": "vite build",
|
|
13
|
-
"
|
|
16
|
+
"check": "tsc --noEmit",
|
|
17
|
+
"lint": "biome check .",
|
|
14
18
|
"preview": "vite preview --host",
|
|
15
|
-
"
|
|
19
|
+
"restore:entry": "node -e \"const{readFileSync}=require('node:fs');const{execSync}=require('node:child_process');const script=/script[^>]*App\\.tsx/;const triad=/family=Inter[\\s\\S]*Geist\\+Mono/;const face=(h)=>script.test(h)&&triad.test(h);if(face(readFileSync('index.html','utf8'))){console.log('index.html already rides the source face');process.exit(0)}if(!face(execSync('git show HEAD:./index.html',{encoding:'utf8'}))){console.error('refusing: HEAD index.html is not the canonical source face yet (script App.tsx + the font triad) - land the source-face fix first; nothing was touched');process.exit(1)}execSync('git checkout -- index.html',{stdio:'inherit'});console.log('index.html restored from HEAD (the source face)')\"",
|
|
20
|
+
"start": "vite preview --host",
|
|
21
|
+
"test": "node --test \"tests/*.test.ts\"",
|
|
22
|
+
"test:watch": "node --test --watch \"tests/*.test.ts\"",
|
|
23
|
+
"typecheck": "tsc --noEmit"
|
|
16
24
|
},
|
|
17
25
|
"dependencies": {
|
|
18
26
|
"@hono/node-server": "^2.1.3",
|
|
@@ -35,7 +43,22 @@
|
|
|
35
43
|
"LICENSE"
|
|
36
44
|
],
|
|
37
45
|
"engines": {
|
|
38
|
-
"
|
|
46
|
+
"bare": "^1.18.0",
|
|
47
|
+
"bun": "^1.4.2",
|
|
48
|
+
"chrome": "^16.0.912",
|
|
49
|
+
"cnpm": "^9.4.0",
|
|
50
|
+
"corepack": "^0.35.0",
|
|
51
|
+
"deno": "^2.9.5",
|
|
52
|
+
"ecmascript": "^5.0.0",
|
|
53
|
+
"firefox": ">=0.8.0",
|
|
54
|
+
"iojs": "^1.0.0",
|
|
55
|
+
"node": "^26.8.2",
|
|
56
|
+
"npm": "^12.1.0",
|
|
57
|
+
"pnpm": "^12.6.0",
|
|
58
|
+
"teleport": ">=0.2.0",
|
|
59
|
+
"typescript": "^5.8.0",
|
|
60
|
+
"vscode": "^1.118.0",
|
|
61
|
+
"yarn": "^4.17.1"
|
|
39
62
|
},
|
|
40
63
|
"publishConfig": {
|
|
41
64
|
"access": "public"
|
|
@@ -46,6 +69,7 @@
|
|
|
46
69
|
"@capacitor/cli": "^8.5.2",
|
|
47
70
|
"@capacitor/core": "^8.5.2",
|
|
48
71
|
"@capacitor/ios": "^8.5.2",
|
|
72
|
+
"@fluentui/react-icons": "^2.0.344",
|
|
49
73
|
"@hookform/resolvers": "^5.9.1",
|
|
50
74
|
"@libsql/client": "^0.18.0",
|
|
51
75
|
"@opencode-ai/sdk": "^1.18.34",
|
|
@@ -129,5 +153,10 @@
|
|
|
129
153
|
"yaml": "^2.9.1",
|
|
130
154
|
"zod": "^4.6.5"
|
|
131
155
|
},
|
|
132
|
-
"packageManager": "pnpm@12.3.4"
|
|
156
|
+
"packageManager": "pnpm@12.3.4",
|
|
157
|
+
"pnpm": {
|
|
158
|
+
"neverBuiltDependencies": [
|
|
159
|
+
"ssh2"
|
|
160
|
+
]
|
|
161
|
+
}
|
|
133
162
|
}
|
package/tests/.gitkeep
CHANGED
|
File without changes
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
// # artifact.test — the checksum engine of the /artifacts page, runnable
|
|
2
|
+
// with the node built-in runner (no dependencies, no install):
|
|
3
|
+
// node --test tests/artifact.test.ts
|
|
4
|
+
// The seal is the proof: fnv-1a (32-bit) over the canonical identity string,
|
|
5
|
+
// rendered as 8 hex, byte-stable in node and in the browser — pinned here
|
|
6
|
+
// against the canonical FNV-1a vectors.
|
|
7
|
+
import assert from "node:assert/strict";
|
|
8
|
+
import { describe, it } from "node:test";
|
|
9
|
+
import {
|
|
10
|
+
type Artifact,
|
|
11
|
+
artifactIdentity,
|
|
12
|
+
artifactLine,
|
|
13
|
+
fnv1a,
|
|
14
|
+
fnv1aHex,
|
|
15
|
+
manifest,
|
|
16
|
+
sealArtifact,
|
|
17
|
+
seedArtifacts,
|
|
18
|
+
utf8Bytes,
|
|
19
|
+
verifyArtifact,
|
|
20
|
+
} from "../artifact.ts";
|
|
21
|
+
|
|
22
|
+
describe("fnv-1a", () => {
|
|
23
|
+
it("answers the canonical vectors", () => {
|
|
24
|
+
assert.equal(fnv1a(""), 0x811c9dc5);
|
|
25
|
+
assert.equal(fnv1a("a"), 0xe40c292c);
|
|
26
|
+
assert.equal(fnv1a("foobar"), 0xbf9cf968);
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
it("renders the 8-hex digest zero-padded", () => {
|
|
30
|
+
assert.equal(fnv1aHex(""), "811c9dc5");
|
|
31
|
+
assert.equal(fnv1aHex("a"), "e40c292c");
|
|
32
|
+
assert.match(fnv1aHex("any identity"), /^[0-9a-f]{8}$/);
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
it("encodes utf-8 bytes (the multibyte path stays byte-exact)", () => {
|
|
36
|
+
assert.deepEqual(utf8Bytes("a"), [0x61]);
|
|
37
|
+
assert.deepEqual(utf8Bytes("ã"), [0xc3, 0xa3]);
|
|
38
|
+
assert.deepEqual(utf8Bytes("★"), [0xe2, 0x98, 0x85]);
|
|
39
|
+
assert.deepEqual(utf8Bytes("𝄞"), [0xf0, 0x9d, 0x84, 0x9e]);
|
|
40
|
+
assert.equal(fnv1a("ã"), fnv1a("ã")); // stable across calls
|
|
41
|
+
assert.notEqual(fnv1a("ã"), fnv1a("a"));
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
it("differs for differing identities (spot check over 24 inputs)", () => {
|
|
45
|
+
const digests = new Set<string>();
|
|
46
|
+
for (let i = 0; i < 24; i++) digests.add(fnv1aHex(`artifact-${i}`));
|
|
47
|
+
assert.ok(digests.size >= 23, `too many collisions: ${digests.size}`);
|
|
48
|
+
});
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
describe("the seal", () => {
|
|
52
|
+
it("builds the canonical identity string", () => {
|
|
53
|
+
assert.equal(artifactIdentity("base", "tzst", 4096, "rev-1"), "base · .tzst · 4096 · rev-1");
|
|
54
|
+
assert.equal(artifactIdentity("runtime", "targz", 10, ""), "runtime · .tar.gz · 10 · ");
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
it("seals the same artifact twice byte-equal (deterministic)", () => {
|
|
58
|
+
const a = sealArtifact("base-01", "tzst", 40_000_000, "rev-2026-02-14");
|
|
59
|
+
const b = sealArtifact("base-01", "tzst", 40_000_000, "rev-2026-02-14");
|
|
60
|
+
assert.deepEqual(a, b);
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
it("seals differing payloads into differing checksums", () => {
|
|
64
|
+
const a = sealArtifact("base-01", "tzst", 40_000_000, "rev-1");
|
|
65
|
+
const b = sealArtifact("base-01", "tzst", 40_000_000, "rev-2");
|
|
66
|
+
assert.notEqual(a.checksum, b.checksum);
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
it("derives the id from the checksum", () => {
|
|
70
|
+
const artifact = sealArtifact("pack-09", "tarxz", 1_000_000);
|
|
71
|
+
assert.equal(artifact.id, `art-${artifact.checksum}`);
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
it("re-verifies a sealed artifact (the seal is the proof)", () => {
|
|
75
|
+
const artifact = sealArtifact("tool-02", "targz", 88_000_000, "rev-x");
|
|
76
|
+
assert.equal(verifyArtifact(artifact), true);
|
|
77
|
+
const tampered: Artifact = { ...artifact, checksum: "deadbeef" };
|
|
78
|
+
assert.equal(verifyArtifact(tampered), false);
|
|
79
|
+
});
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
describe("the packaging line", () => {
|
|
83
|
+
it("replays the same line from the same seed", () => {
|
|
84
|
+
assert.deepEqual(seedArtifacts(804613), seedArtifacts(804613));
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
it("seals one artifact per herd format", () => {
|
|
88
|
+
const line = seedArtifacts(804613);
|
|
89
|
+
assert.equal(line.length, 3);
|
|
90
|
+
assert.deepEqual(
|
|
91
|
+
line.map((artifact) => artifact.format),
|
|
92
|
+
["tzst", "targz", "tarxz"],
|
|
93
|
+
);
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
it("renders the manifest line and joins the manifest", () => {
|
|
97
|
+
const line = seedArtifacts(804613);
|
|
98
|
+
const rendered = artifactLine(line[0]);
|
|
99
|
+
assert.match(rendered, /^base-\w+\.tzst · [\d,]+ b · fnv-1a [0-9a-f]{8}$/);
|
|
100
|
+
assert.equal(manifest(line), line.map((artifact) => artifactLine(artifact)).join("\n"));
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
it("answers an empty manifest for an empty line", () => {
|
|
104
|
+
assert.equal(manifest([]), "");
|
|
105
|
+
});
|
|
106
|
+
});
|
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
// # design.test — the design contract of the foundry surface, runnable with
|
|
2
|
+
// the node built-in runner (no dependencies, no install):
|
|
3
|
+
// node --test tests/design.test.ts
|
|
4
|
+
// The file reads the shipped sources (css, entry face, brand mark, app
|
|
5
|
+
// identity) and pins the family laws of the reform wave: no banned hue, no
|
|
6
|
+
// pill, no transition:all, the trylle type triad, the source-face entry,
|
|
7
|
+
// the azul identity under the windows hands and the honest page set of the
|
|
8
|
+
// application identity.
|
|
9
|
+
import assert from "node:assert/strict";
|
|
10
|
+
import { readFileSync } from "node:fs";
|
|
11
|
+
import { describe, it } from "node:test";
|
|
12
|
+
|
|
13
|
+
function read(path: string): string {
|
|
14
|
+
return readFileSync(new URL(path, import.meta.url), "utf8");
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
const CSS = read("../Sol/sol.css");
|
|
18
|
+
const ENTRY = read("../index.html");
|
|
19
|
+
const MARK = read("../Sol/shell/BrandMark.tsx");
|
|
20
|
+
const SHELL = read("../Sol/shell/Shell.tsx");
|
|
21
|
+
const TOML = read("../devthink.toml");
|
|
22
|
+
const SOL = read("../Sol/Sol.tsx");
|
|
23
|
+
|
|
24
|
+
const BANNED = [/3b82f6/i, /2563eb/i, /f97316/i];
|
|
25
|
+
|
|
26
|
+
/** strips the windows pin keyframe — the one sanctioned scale(0) entrance —
|
|
27
|
+
* so the floor law can be asserted on everything else. */
|
|
28
|
+
function cssWithoutPinIntro(source: string): string {
|
|
29
|
+
return source.replace(/@keyframes dtPopIntro \{[\s\S]*?\n\}/, "");
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
describe("design contract", () => {
|
|
33
|
+
it("ships no banned hue in the stylesheet, the entry or the mark", () => {
|
|
34
|
+
for (const source of [CSS, ENTRY, MARK]) {
|
|
35
|
+
for (const banned of BANNED) {
|
|
36
|
+
assert.ok(!banned.test(source), `banned hue ${banned} found`);
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
it("keeps the azul identity and the windows accent in their lanes", () => {
|
|
42
|
+
assert.match(CSS, /--sol-primary: #4d8edd/, "the azul identity token stands");
|
|
43
|
+
assert.match(CSS, /--win-accent: #4cc2ff/, "the windows accent governs focus and the bar cta");
|
|
44
|
+
assert.match(CSS, /--win-accent: #0067c0/, "the light theme follows the windows light law");
|
|
45
|
+
assert.match(MARK, /#2fb8b5/, "the drawn mark keeps its têmpera story");
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
it("retires the abandoned pipeline blue and the molten amber identities", () => {
|
|
49
|
+
assert.ok(!/1e6ff2/i.test(CSS + ENTRY), "the pipeline blue is gone");
|
|
50
|
+
assert.ok(!/d97706|b45309|f59e0b/i.test(CSS + ENTRY), "the molten amber is gone");
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
it("wears the têmpera identity on the icons and the manifest", () => {
|
|
54
|
+
const FAVICON = read("../favicon.svg");
|
|
55
|
+
const ICON = read("../icon.svg");
|
|
56
|
+
const MANIFEST = read("../manifest.webmanifest");
|
|
57
|
+
for (const [name, source] of [
|
|
58
|
+
["favicon.svg", FAVICON],
|
|
59
|
+
["icon.svg", ICON],
|
|
60
|
+
] as const) {
|
|
61
|
+
assert.ok(!/1e6ff2|1447a8/.test(source), `${name} still rides the pipeline blue`);
|
|
62
|
+
}
|
|
63
|
+
assert.match(ICON, /#2fb8b5|#4fd6d2|#1f7d7b/, "the icon rides the têmpera family");
|
|
64
|
+
assert.match(FAVICON, /#2fb8b5/, "the favicon rides the têmpera family");
|
|
65
|
+
assert.match(MANIFEST, /"theme_color": "#2fb8b5"/, "the manifest theme color is the têmpera");
|
|
66
|
+
assert.match(MANIFEST, /"background_color": "#0a1210"/, "the manifest background is the fornalha base");
|
|
67
|
+
assert.match(ENTRY, /theme-color" content="#0a0a0b"/, "the entry theme color is the trylle canvas");
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
it("keeps the pill out of the stylesheet (no 999px, no stadium radius)", () => {
|
|
71
|
+
assert.ok(!CSS.includes("999px"), "the pill radius is retired");
|
|
72
|
+
assert.ok(!CSS.includes("--r-pill"), "no pill token survives");
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
it("never ships transition: all", () => {
|
|
76
|
+
assert.ok(!/transition:\s*all/.test(CSS), "transition: all is a reprovação");
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
it("never enters from scale(0) outside the windows pin law", () => {
|
|
80
|
+
// the ONE sanctioned scale(0) entrance is the dtPopIntro pin bounce —
|
|
81
|
+
// the windows recipe (0 → 1.125 → 0.725 → 1); everything else floors
|
|
82
|
+
assert.ok(/@keyframes dtPopIntro/.test(CSS), "the windows pin entrance stands");
|
|
83
|
+
assert.ok(!/scale\(0\)/.test(cssWithoutPinIntro(CSS)), "scale(0) outside dtPopIntro is a reprovação");
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
it("declares the trylle type triad (Inter Variable surfaces, Geist Mono numbers)", () => {
|
|
87
|
+
assert.match(CSS, /"Inter Variable"/);
|
|
88
|
+
assert.match(CSS, /"Geist Mono Variable"/);
|
|
89
|
+
assert.match(ENTRY, /family=Inter/);
|
|
90
|
+
assert.match(ENTRY, /Geist\+Mono/);
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
it("keeps the entry on the source face (./App.tsx, no hashed ghosts)", () => {
|
|
94
|
+
assert.match(ENTRY, /script[^>]*\.\/App\.tsx/);
|
|
95
|
+
assert.ok(!/-[0-9a-f]{8}\.(js|css)/.test(ENTRY), "no hashed bundle rides the entry");
|
|
96
|
+
assert.match(ENTRY, /href="\.\/favicon\.svg"/);
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
it("guards the reduced-motion stop on every layer", () => {
|
|
100
|
+
const blocks = CSS.match(/@media \(prefers-reduced-motion: reduce\)/g) ?? [];
|
|
101
|
+
assert.ok(blocks.length >= 4, `expected the hard stop on every layer, found ${blocks.length}`);
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
it("wears the windows hands on the shell (press law, pin entrance, resize, inactive)", () => {
|
|
105
|
+
assert.match(CSS, /dtPopIntro/, "the pin entrance keyframe stands");
|
|
106
|
+
assert.match(SHELL, /RESIZE_EDGES/, "the eight resize handles mount");
|
|
107
|
+
assert.match(CSS, /win-rz\[data-edge="se"\]/, "the resize handles ride the frame edges");
|
|
108
|
+
assert.match(CSS, /grayscale\(80%\)/, "the inactive window law stands");
|
|
109
|
+
assert.match(CSS, /rgb\(232 17 35\)/, "the close caption wears the windows red");
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
it("declares the honest page set (home, sandboxes, images, artifacts, notfound)", () => {
|
|
113
|
+
assert.match(TOML, /pages = \["home", "sandboxes", "images", "artifacts", "notfound"\]/);
|
|
114
|
+
for (const route of ['"/sandboxes"', '"/images"', '"/artifacts"', '"/404"']) {
|
|
115
|
+
assert.ok(SOL.includes(`path=${route}`), `route ${route} must mount`);
|
|
116
|
+
}
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
it("mounts the flow law (intro and onboarding open the route table)", () => {
|
|
120
|
+
assert.match(SOL, /path="\/intro"/);
|
|
121
|
+
assert.match(SOL, /path="\/onboarding"/);
|
|
122
|
+
assert.ok(SOL.indexOf('"/intro"') < SOL.indexOf('"/"'), "the entry flow opens the route table");
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
it("carries the flow contract sources (the entry gate behind the home anchor)", () => {
|
|
126
|
+
const GATE = read("../Sol/home/entrygate.tsx");
|
|
127
|
+
const HOME = read("../Sol/home/home.tsx");
|
|
128
|
+
const INTRO = read("../Sol/intro/intro.tsx");
|
|
129
|
+
const ONBOARDING = read("../Sol/onboarding/onboarding.tsx");
|
|
130
|
+
assert.match(GATE, /navigate\("\/intro"/, "an unseen intro navigates to the splash");
|
|
131
|
+
assert.match(GATE, /navigate\("\/onboarding"/, "an unseen onboarding navigates to the walk");
|
|
132
|
+
assert.match(HOME, /EntryGate/, "the home anchor consults the gate");
|
|
133
|
+
assert.match(INTRO, /markIntroSeen/, "the intro marks introseen");
|
|
134
|
+
assert.match(INTRO, /\/onboarding/, "the intro hands DIRECTLY to the walk");
|
|
135
|
+
assert.match(ONBOARDING, /markOnboardingSeen/, "the onboarding marks onboardingseen");
|
|
136
|
+
});
|
|
137
|
+
|
|
138
|
+
it("catalogues the seen state on the database, not just the browser", () => {
|
|
139
|
+
const PRISMA = read("../schema.prisma");
|
|
140
|
+
const DB = read("../db.ts");
|
|
141
|
+
const STATE = read("../onboardingstate.ts");
|
|
142
|
+
assert.match(PRISMA, /model OnboardingState \{/, "the prisma model stands");
|
|
143
|
+
assert.match(PRISMA, /introseen\s+Boolean/);
|
|
144
|
+
assert.match(PRISMA, /onboardingseen\s+Boolean/);
|
|
145
|
+
assert.match(DB, /onboarding_states/, "the sqlite mirror exists");
|
|
146
|
+
assert.match(DB, /getOnboardingState/, "the reader accessor exists");
|
|
147
|
+
assert.match(DB, /setOnboardingStage/, "the writer accessor exists");
|
|
148
|
+
assert.doesNotMatch(DB, /["']\s*\+\s*(visitor|stage)\b/, "no concatenated sql rides the accessors");
|
|
149
|
+
assert.match(STATE, /foundry\.onboarding\.seen/, "the localStorage cache mirrors the db");
|
|
150
|
+
assert.match(STATE, /\/api\/v1\/onboarding/, "the accessor rides the platform route");
|
|
151
|
+
});
|
|
152
|
+
|
|
153
|
+
it("keeps tabular numerals on the platform numbers", () => {
|
|
154
|
+
assert.match(CSS, /font-variant-numeric:\s*tabular-nums/);
|
|
155
|
+
});
|
|
156
|
+
|
|
157
|
+
it("rests the title-bar mark on the landing (logo discipline)", () => {
|
|
158
|
+
assert.match(CSS, /data-landing/);
|
|
159
|
+
assert.ok(SHELL.includes("data-landing"), "the shell carries the landing attribute");
|
|
160
|
+
});
|
|
161
|
+
|
|
162
|
+
it("runs the family flow signature inside the mark strokes", () => {
|
|
163
|
+
assert.match(MARK, /fdMarkFlow/, "the flow layer exists on the foundry mark");
|
|
164
|
+
assert.match(MARK, /fdm-flow/, "the flow gradient is declared");
|
|
165
|
+
assert.match(
|
|
166
|
+
MARK,
|
|
167
|
+
/prefers-reduced-motion: reduce[\s\S]*\.fdMarkFlow \{ opacity: 0/,
|
|
168
|
+
"the flow hard-stops on reduced motion",
|
|
169
|
+
);
|
|
170
|
+
});
|
|
171
|
+
|
|
172
|
+
it("links the family row from the one table (zero drift between chrome and landing)", () => {
|
|
173
|
+
assert.match(SHELL, /from "\.\.\/\.\.\/family"/, "the shell reads family.ts");
|
|
174
|
+
const HOME = read("../Sol/home/home.tsx");
|
|
175
|
+
assert.match(HOME, /familyrow/, "the landing footer reads the same table");
|
|
176
|
+
});
|
|
177
|
+
});
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
// # family.test — the family table the chrome rides, runnable with the node
|
|
2
|
+
// built-in runner (no dependencies, no install):
|
|
3
|
+
// node --test tests/family.test.ts
|
|
4
|
+
// Every test watches the promise the row makes: the nine siblings beside
|
|
5
|
+
// foundry are listed once, every slug resolves through the familyurl
|
|
6
|
+
// contract into a climb-one-level url, every member carries the spec §12
|
|
7
|
+
// accent with its own dot ink, and no accent ever collides with the banned
|
|
8
|
+
// palette.
|
|
9
|
+
import assert from "node:assert/strict";
|
|
10
|
+
import { describe, it } from "node:test";
|
|
11
|
+
import { FAMILY_ACCENTS, FAMILY_SLUGS, familyAccentOf, familyrow } from "../family.ts";
|
|
12
|
+
import { familyurl } from "../familyurl.ts";
|
|
13
|
+
|
|
14
|
+
const BANNED = new Set(["#3b82f6", "#2563eb", "#f97316"]);
|
|
15
|
+
|
|
16
|
+
describe("family table", () => {
|
|
17
|
+
it("lists the nine siblings beside foundry, never foundry itself", () => {
|
|
18
|
+
assert.deepEqual(FAMILY_SLUGS, [
|
|
19
|
+
"devthink",
|
|
20
|
+
"cadria",
|
|
21
|
+
"stealthhead",
|
|
22
|
+
"debonair",
|
|
23
|
+
"argan",
|
|
24
|
+
"saddle",
|
|
25
|
+
"forge",
|
|
26
|
+
"vault",
|
|
27
|
+
"getry",
|
|
28
|
+
]);
|
|
29
|
+
assert.ok(!FAMILY_SLUGS.includes("foundry"));
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
it("answers an accent for every listed sibling", () => {
|
|
33
|
+
for (const slug of FAMILY_SLUGS) {
|
|
34
|
+
const member = FAMILY_ACCENTS[slug];
|
|
35
|
+
assert.ok(member, `${slug} must carry an accent`);
|
|
36
|
+
assert.equal(member.slug, slug);
|
|
37
|
+
assert.match(member.accent, /^#[0-9a-fA-F]{6}$/);
|
|
38
|
+
assert.match(member.dotInk, /^#[0-9a-fA-F]{6}$/);
|
|
39
|
+
}
|
|
40
|
+
assert.deepEqual(Object.keys(FAMILY_ACCENTS).sort(), [
|
|
41
|
+
"argan",
|
|
42
|
+
"cadria",
|
|
43
|
+
"debonair",
|
|
44
|
+
"devthink",
|
|
45
|
+
"forge",
|
|
46
|
+
"foundry",
|
|
47
|
+
"getry",
|
|
48
|
+
"saddle",
|
|
49
|
+
"stealthhead",
|
|
50
|
+
"vault",
|
|
51
|
+
]);
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
it("keeps the spec §12 accents stable, verbatim", () => {
|
|
55
|
+
assert.equal(FAMILY_ACCENTS.devthink.accent, "#F59E0B");
|
|
56
|
+
assert.equal(FAMILY_ACCENTS.argan.accent, "#34d8a8");
|
|
57
|
+
assert.equal(FAMILY_ACCENTS.cadria.accent, "#f472b6");
|
|
58
|
+
assert.equal(FAMILY_ACCENTS.debonair.accent, "#d9962e");
|
|
59
|
+
assert.equal(FAMILY_ACCENTS.forge.accent, "#a2cb3a");
|
|
60
|
+
assert.equal(FAMILY_ACCENTS.foundry.accent, "#2fb8b5");
|
|
61
|
+
assert.equal(FAMILY_ACCENTS.getry.accent, "#8b5cf6");
|
|
62
|
+
assert.equal(FAMILY_ACCENTS.saddle.accent, "#e86f2d");
|
|
63
|
+
assert.equal(FAMILY_ACCENTS.stealthhead.accent, "#e8563f");
|
|
64
|
+
assert.equal(FAMILY_ACCENTS.vault.accent, "#a3d7e6");
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
it("keeps the spec accent-ink column stable", () => {
|
|
68
|
+
assert.equal(FAMILY_ACCENTS.forge.dotInk, "#161d03");
|
|
69
|
+
assert.equal(FAMILY_ACCENTS.foundry.dotInk, "#03211f");
|
|
70
|
+
assert.equal(FAMILY_ACCENTS.vault.dotInk, "#072330");
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
it("resolves every member through the familyurl contract", () => {
|
|
74
|
+
for (const slug of FAMILY_SLUGS) {
|
|
75
|
+
assert.equal(familyurl(slug), `../${slug}/`);
|
|
76
|
+
}
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
it("never ships a banned hue in the row or the table", () => {
|
|
80
|
+
for (const member of [...familyrow(), ...Object.values(FAMILY_ACCENTS)]) {
|
|
81
|
+
assert.ok(!BANNED.has(member.accent.toLowerCase()), `${member.slug} rides a banned accent`);
|
|
82
|
+
}
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
it("builds the rail row in slug order without gaps", () => {
|
|
86
|
+
const row = familyrow();
|
|
87
|
+
assert.equal(row.length, FAMILY_SLUGS.length);
|
|
88
|
+
assert.deepEqual(
|
|
89
|
+
row.map((member) => member.slug),
|
|
90
|
+
[...FAMILY_SLUGS],
|
|
91
|
+
);
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
it("answers familyAccentOf for the house and falls back for unknowns", () => {
|
|
95
|
+
assert.equal(familyAccentOf("foundry")?.accent, "#2fb8b5");
|
|
96
|
+
assert.equal(familyAccentOf("forge")?.accent, "#a2cb3a");
|
|
97
|
+
assert.equal(familyAccentOf("nope"), undefined);
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
it("keeps the familyurl contract on the climb-one-level shape", () => {
|
|
101
|
+
assert.equal(familyurl("cadria"), "../cadria/");
|
|
102
|
+
assert.equal(familyurl("/cadria/"), "../cadria/");
|
|
103
|
+
});
|
|
104
|
+
});
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
// # imagestore.test — the herd format engine of the /images page, runnable
|
|
2
|
+
// with the node built-in runner (no dependencies, no install):
|
|
3
|
+
// node --test tests/imagestore.test.ts
|
|
4
|
+
// The three real clone formats (.tzst / .tar.gz / .tar.xz), the honest
|
|
5
|
+
// conversion plans (always through the tar layer) and the size estimates.
|
|
6
|
+
import assert from "node:assert/strict";
|
|
7
|
+
import { describe, it } from "node:test";
|
|
8
|
+
import {
|
|
9
|
+
convertPlan,
|
|
10
|
+
estimateSize,
|
|
11
|
+
FORMATS,
|
|
12
|
+
formatBytes,
|
|
13
|
+
formatFromExt,
|
|
14
|
+
formatOf,
|
|
15
|
+
pourOrder,
|
|
16
|
+
seedImages,
|
|
17
|
+
} from "../imagestore.ts";
|
|
18
|
+
|
|
19
|
+
describe("the formats", () => {
|
|
20
|
+
it("answers the three real clone formats in floor order", () => {
|
|
21
|
+
assert.deepEqual(
|
|
22
|
+
FORMATS.map((format) => format.ext),
|
|
23
|
+
[".tzst", ".tar.gz", ".tar.xz"],
|
|
24
|
+
);
|
|
25
|
+
assert.deepEqual(pourOrder(), FORMATS);
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
it("resolves formats by id and by extension", () => {
|
|
29
|
+
assert.equal(formatOf("tzst")?.ext, ".tzst");
|
|
30
|
+
assert.equal(formatOf("tarxz")?.label, "the release herd");
|
|
31
|
+
assert.equal(formatFromExt(".tar.gz")?.id, "targz");
|
|
32
|
+
assert.equal(formatFromExt(".TAR.GZ")?.id, "targz");
|
|
33
|
+
assert.equal(formatOf("nope"), undefined);
|
|
34
|
+
assert.equal(formatFromExt(".zip"), undefined);
|
|
35
|
+
});
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
describe("the conversion plan", () => {
|
|
39
|
+
it("always goes through the tar layer, never a direct recompress", () => {
|
|
40
|
+
const plan = convertPlan("tzst", "tarxz");
|
|
41
|
+
assert.equal(plan.from.id, "tzst");
|
|
42
|
+
assert.equal(plan.to.id, "tarxz");
|
|
43
|
+
assert.match(plan.steps[0], /unpack the \.tzst layer down to the tar stream/);
|
|
44
|
+
assert.match(plan.steps[1], /recompress the tar stream into \.tar\.xz/);
|
|
45
|
+
assert.match(plan.steps[2], /checksum/);
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
it("answers a one-line no-op plan for the same format", () => {
|
|
49
|
+
const plan = convertPlan("targz", "targz");
|
|
50
|
+
assert.equal(plan.steps.length, 1);
|
|
51
|
+
assert.match(plan.steps[0], /no conversion/);
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
it("builds a plan for every ordered pair of distinct formats", () => {
|
|
55
|
+
for (const from of FORMATS) {
|
|
56
|
+
for (const to of FORMATS) {
|
|
57
|
+
const plan = convertPlan(from.id, to.id);
|
|
58
|
+
assert.ok(plan.steps.length >= 1);
|
|
59
|
+
if (from.id !== to.id) assert.equal(plan.steps.length, 3);
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
});
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
describe("the estimates", () => {
|
|
66
|
+
it("orders the compress shares: xz smallest, gzip the middle", () => {
|
|
67
|
+
const bytes = 1_000_000_000;
|
|
68
|
+
const tzst = estimateSize(bytes, "tzst");
|
|
69
|
+
const targz = estimateSize(bytes, "targz");
|
|
70
|
+
const tarxz = estimateSize(bytes, "tarxz");
|
|
71
|
+
assert.ok(tarxz < tzst, "xz rides smaller than zstd");
|
|
72
|
+
assert.ok(tzst < targz, "zstd rides smaller than gzip");
|
|
73
|
+
assert.ok(tarxz > 0 && targz < bytes);
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
it("answers zero for empty and negative streams", () => {
|
|
77
|
+
assert.equal(estimateSize(0, "tzst"), 0);
|
|
78
|
+
assert.equal(estimateSize(-5, "tarxz"), 0);
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
it("scales linearly with the stream size", () => {
|
|
82
|
+
assert.equal(estimateSize(2_000_000, "tzst"), estimateSize(1_000_000, "tzst") * 2);
|
|
83
|
+
});
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
describe("the shelf", () => {
|
|
87
|
+
it("replays the same shelf from the same seed", () => {
|
|
88
|
+
assert.deepEqual(seedImages(314159, 6), seedImages(314159, 6));
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
it("shelves unique ids on real formats with positive sizes", () => {
|
|
92
|
+
const shelf = seedImages(8, 12);
|
|
93
|
+
assert.equal(new Set(shelf.map((image) => image.id)).size, 12);
|
|
94
|
+
for (const image of shelf) {
|
|
95
|
+
assert.ok(FORMATS.some((format) => format.id === image.format));
|
|
96
|
+
assert.ok(image.tarBytes > 0);
|
|
97
|
+
}
|
|
98
|
+
});
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
describe("the byte counts", () => {
|
|
102
|
+
it("renders the tabular human units", () => {
|
|
103
|
+
assert.equal(formatBytes(512), "512 B");
|
|
104
|
+
assert.equal(formatBytes(1024), "1.0 KB");
|
|
105
|
+
assert.equal(formatBytes(1_500_000), "1.4 MB");
|
|
106
|
+
assert.equal(formatBytes(2_300_000_000), "2.1 GB");
|
|
107
|
+
assert.equal(formatBytes(0), "0 B");
|
|
108
|
+
});
|
|
109
|
+
});
|
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
// # sandbox.test — the sandbox state machine of the /sandboxes page,
|
|
2
|
+
// runnable with the node built-in runner (no dependencies, no install):
|
|
3
|
+
// node --test tests/sandbox.test.ts
|
|
4
|
+
// The machine never lies: the ladder is cold → warm → hot with the cooling
|
|
5
|
+
// paths back, every refusal is an honest string, the herd replays from the
|
|
6
|
+
// seed and the census counts exactly.
|
|
7
|
+
import assert from "node:assert/strict";
|
|
8
|
+
import { describe, it } from "node:test";
|
|
9
|
+
import {
|
|
10
|
+
canTransition,
|
|
11
|
+
drive,
|
|
12
|
+
herdCensus,
|
|
13
|
+
mulberry32,
|
|
14
|
+
refusalFor,
|
|
15
|
+
SANDBOX_STATES,
|
|
16
|
+
type Sandbox,
|
|
17
|
+
seedHerd,
|
|
18
|
+
step,
|
|
19
|
+
} from "../sandbox.ts";
|
|
20
|
+
|
|
21
|
+
/** one hand-built sandbox */
|
|
22
|
+
function boxOf(patch: Partial<Sandbox>): Sandbox {
|
|
23
|
+
return { id: "fdy-test-01", name: "sandbox-test", state: "cold", image: "base.tzst", boots: 0, steps: 0, ...patch };
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
describe("the machine", () => {
|
|
27
|
+
it("keeps the three-state ladder cold → warm → hot", () => {
|
|
28
|
+
assert.deepEqual(SANDBOX_STATES, ["cold", "warm", "hot"]);
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
it("allows the boot path and the cooling paths", () => {
|
|
32
|
+
assert.equal(canTransition("cold", "warm"), true);
|
|
33
|
+
assert.equal(canTransition("warm", "hot"), true);
|
|
34
|
+
assert.equal(canTransition("warm", "cold"), true);
|
|
35
|
+
assert.equal(canTransition("hot", "warm"), true);
|
|
36
|
+
assert.equal(canTransition("hot", "cold"), true);
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
it("refuses the impossible moves", () => {
|
|
40
|
+
assert.equal(canTransition("cold", "hot"), false);
|
|
41
|
+
assert.equal(canTransition("cold", "cold"), false);
|
|
42
|
+
assert.equal(canTransition("hot", "hot"), false);
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
it("answers honest refusals with the reason", () => {
|
|
46
|
+
assert.match(refusalFor("cold", "hot"), /warm it first/);
|
|
47
|
+
assert.match(refusalFor("warm", "warm"), /already warm/);
|
|
48
|
+
assert.match(refusalFor("hot", "hot"), /already hot/);
|
|
49
|
+
});
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
describe("drive", () => {
|
|
53
|
+
it("warms a cold sandbox and counts the boot", () => {
|
|
54
|
+
const box = boxOf({});
|
|
55
|
+
const answer = drive(box, "warm");
|
|
56
|
+
assert.equal(answer.refusal, undefined);
|
|
57
|
+
assert.equal(answer.sandbox.state, "warm");
|
|
58
|
+
assert.equal(answer.sandbox.boots, 1);
|
|
59
|
+
assert.equal(answer.sandbox.steps, 0);
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
it("refuses to warm an already warm sandbox (no double boot)", () => {
|
|
63
|
+
const box = boxOf({ state: "warm", boots: 2 });
|
|
64
|
+
const answer = drive(box, "warm");
|
|
65
|
+
assert.match(answer.refusal ?? "", /already warm/);
|
|
66
|
+
assert.equal(answer.sandbox.boots, 2);
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
it("heats a warm sandbox into hot", () => {
|
|
70
|
+
const answer = drive(boxOf({ state: "warm" }), "heat");
|
|
71
|
+
assert.equal(answer.sandbox.state, "hot");
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
it("cools a hot sandbox back to warm (working set kept)", () => {
|
|
75
|
+
const box = boxOf({ state: "hot", boots: 3, steps: 12 });
|
|
76
|
+
const answer = drive(box, "cool");
|
|
77
|
+
assert.equal(answer.sandbox.state, "warm");
|
|
78
|
+
assert.equal(answer.sandbox.boots, 3);
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
it("freezes back to cold and tears the working set down", () => {
|
|
82
|
+
const box = boxOf({ state: "hot", boots: 3, steps: 12 });
|
|
83
|
+
const answer = drive(box, "freeze");
|
|
84
|
+
assert.equal(answer.sandbox.state, "cold");
|
|
85
|
+
assert.equal(answer.sandbox.boots, 0);
|
|
86
|
+
assert.equal(answer.sandbox.steps, 0);
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
it("never mutates the input sandbox", () => {
|
|
90
|
+
const box = boxOf({ state: "warm" });
|
|
91
|
+
const frozen = JSON.parse(JSON.stringify(box)) as Sandbox;
|
|
92
|
+
drive(box, "heat");
|
|
93
|
+
drive(box, "freeze");
|
|
94
|
+
assert.deepEqual(box, frozen);
|
|
95
|
+
});
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
describe("step", () => {
|
|
99
|
+
it("counts a step only on a hot sandbox", () => {
|
|
100
|
+
const hot = boxOf({ state: "hot", steps: 5 });
|
|
101
|
+
assert.equal(step(hot).sandbox.steps, 6);
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
it("refuses steps on cold and warm with the honest line", () => {
|
|
105
|
+
assert.match(step(boxOf({ state: "cold" })).refusal ?? "", /heat it first/);
|
|
106
|
+
assert.match(step(boxOf({ state: "warm" })).refusal ?? "", /only a hot sandbox/);
|
|
107
|
+
});
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
describe("the rehearsal herd", () => {
|
|
111
|
+
it("replays the same herd from the same seed", () => {
|
|
112
|
+
assert.deepEqual(seedHerd(620221, 7), seedHerd(620221, 7));
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
it("herds unique ids with valid states and images", () => {
|
|
116
|
+
const herd = seedHerd(9, 30);
|
|
117
|
+
assert.equal(new Set(herd.map((box) => box.id)).size, 30);
|
|
118
|
+
for (const box of herd) {
|
|
119
|
+
assert.ok(SANDBOX_STATES.includes(box.state));
|
|
120
|
+
assert.match(box.image, /\.(tzst|tar\.gz|tar\.xz)$/);
|
|
121
|
+
assert.ok(box.boots >= 0 && box.steps >= 0);
|
|
122
|
+
}
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
it("keeps cold boxes honest: no boots, no steps", () => {
|
|
126
|
+
for (const box of seedHerd(1234, 60).filter((entry) => entry.state === "cold")) {
|
|
127
|
+
assert.equal(box.boots, 0);
|
|
128
|
+
assert.equal(box.steps, 0);
|
|
129
|
+
}
|
|
130
|
+
});
|
|
131
|
+
});
|
|
132
|
+
|
|
133
|
+
describe("the census", () => {
|
|
134
|
+
it("counts the ladder exactly and derives the warm share", () => {
|
|
135
|
+
const herd = [
|
|
136
|
+
boxOf({ id: "a", state: "cold" }),
|
|
137
|
+
boxOf({ id: "b", state: "warm" }),
|
|
138
|
+
boxOf({ id: "c", state: "warm" }),
|
|
139
|
+
boxOf({ id: "d", state: "hot" }),
|
|
140
|
+
];
|
|
141
|
+
const census = herdCensus(herd);
|
|
142
|
+
assert.equal(census.total, 4);
|
|
143
|
+
assert.equal(census.byState.cold, 1);
|
|
144
|
+
assert.equal(census.byState.warm, 2);
|
|
145
|
+
assert.equal(census.byState.hot, 1);
|
|
146
|
+
assert.equal(census.warmShare, 0.75);
|
|
147
|
+
});
|
|
148
|
+
|
|
149
|
+
it("answers zero shares on an empty room", () => {
|
|
150
|
+
const census = herdCensus([]);
|
|
151
|
+
assert.equal(census.total, 0);
|
|
152
|
+
assert.equal(census.warmShare, 0);
|
|
153
|
+
});
|
|
154
|
+
});
|
|
155
|
+
|
|
156
|
+
describe("prng", () => {
|
|
157
|
+
it("mulberry32 is deterministic across calls", () => {
|
|
158
|
+
const a = mulberry32(620221);
|
|
159
|
+
const b = mulberry32(620221);
|
|
160
|
+
for (let i = 0; i < 16; i++) assert.equal(a(), b());
|
|
161
|
+
});
|
|
162
|
+
});
|