From 1fd571e92c8e447943b57fd9a522f63a24da909a Mon Sep 17 00:00:00 2001
From: Trent Slade <73952179+EmergentMonk@users.noreply.github.com>
Date: Sun, 6 Sep 2026 01:48:41 +0930
Subject: [PATCH 01/19] Add offline calculator protocol core
---
web/heresy-core.js | 305 +++++++++++++++++++++++++++++++++++++++++++++
1 file changed, 305 insertions(+)
create mode 100644 web/heresy-core.js
diff --git a/web/heresy-core.js b/web/heresy-core.js
new file mode 100644
index 0000000..1dcc47c
--- /dev/null
+++ b/web/heresy-core.js
@@ -0,0 +1,305 @@
+(function (root, factory) {
+ "use strict";
+ const api = factory();
+ if (typeof module === "object" && module.exports) {
+ module.exports = api;
+ }
+ root.HeresyCore = api;
+})(typeof globalThis !== "undefined" ? globalThis : this, function () {
+ "use strict";
+
+ const INT32_MIN = -2147483648n;
+ const INT32_MAX = 2147483647n;
+ const LOST_KEY = "LOST-IN-.env.old.backup.final2";
+
+ const OPERATIONS = Object.freeze({
+ add: Object.freeze({ slug: "add", code: "ADD", method: "Add", field: "add", opcode: 1, symbol: "+" }),
+ subtract: Object.freeze({ slug: "subtract", code: "SUB", method: "Subtract", field: "subtract", opcode: 2, symbol: "-" }),
+ multiply: Object.freeze({ slug: "multiply", code: "MUL", method: "Multiply", field: "multiply", opcode: 3, symbol: "×" }),
+ divide: Object.freeze({ slug: "divide", code: "DIV", method: "Divide", field: "divide", opcode: 4, symbol: "÷" }),
+ });
+
+ const STYLES = Object.freeze({
+ "punch-card": Object.freeze({
+ name: "Punch Card / Fixed Width",
+ era: "1940s-1960s",
+ punchline: "80 columns because storage used to arrive with furniture.",
+ value: "A rigid physical record layout that operators and programs can agree on.",
+ caveat: "Fixed width wastes space and becomes brittle when the record shape changes.",
+ authenticated: false,
+ }),
+ "magnetic-tape": Object.freeze({
+ name: "Magnetic Tape Record",
+ era: "1950s-1980s",
+ punchline: "Sequential access. Please rewind your microservice.",
+ value: "Simple durable sequential records that are easy to append and batch.",
+ caveat: "Random access and low-latency request/response are not the tape's hobbies.",
+ authenticated: false,
+ }),
+ "raw-tcp": Object.freeze({
+ name: "Raw TCP-style Frame",
+ era: "1970s-1980s",
+ punchline: "The documentation has become frighteningly short.",
+ value: "Reliable ordered delivery plus explicit application message framing.",
+ caveat: "TCP is a byte stream, not an API design. Versioning, auth, timeouts, and observability remain yours.",
+ authenticated: false,
+ }),
+ udp: Object.freeze({
+ name: "UDP-style Datagram",
+ era: "1970s-1980s",
+ punchline: "The packet can simply leave. No architecture review calendar invite required.",
+ value: "Message boundaries and tiny application framing for freshness-first workloads.",
+ caveat: "Delivery, ordering, retransmission, authentication, and congestion semantics may need to be supplied elsewhere.",
+ authenticated: false,
+ }),
+ "ftp-batch": Object.freeze({
+ name: "FTP / Batch File",
+ era: "1970s-1990s",
+ punchline: "Real-time, if tomorrow morning counts as a latency target.",
+ value: "Inspectable, archivable, replayable, checksum-friendly asynchronous exchange.",
+ caveat: "Plain FTP is unencrypted, and batch is the wrong tool when latency actually matters.",
+ authenticated: false,
+ }),
+ rpc: Object.freeze({
+ name: "RPC-ish Invocation",
+ era: "1980s-1990s",
+ punchline: "The network is a local function call until packet loss files an objection.",
+ value: "Typed operation names and generated client/server calling conventions.",
+ caveat: "Remote calls still fail in remote ways. Pretending otherwise creates exciting outages.",
+ authenticated: false,
+ }),
+ corba: Object.freeze({
+ name: "CORBA / IDL Theatre",
+ era: "1990s",
+ punchline: "The arithmetic has been referred to the Object Request Broker.",
+ value: "Language-neutral interface contracts and distributed-object interoperability.",
+ caveat: "This is deliberately GIOP-ish theatre. IDL is normally a build-time contract, not resent on every call.",
+ authenticated: false,
+ }),
+ soap: Object.freeze({
+ name: "SOAP-ish XML",
+ era: "late 1990s-2000s",
+ punchline: "The integer has entered the XML cathedral and acquired a namespace.",
+ value: "Explicit envelopes, extensible headers, typed contracts, and enterprise tooling.",
+ caveat: "The wider WS-* universe can add more ceremony in exchange for security, reliability, and governance.",
+ authenticated: false,
+ }),
+ rest: Object.freeze({
+ name: "REST-ish HTTP + JSON",
+ era: "modern web",
+ punchline: "Human-readable bureaucracy. Dave has lost the API key again.",
+ value: "Ubiquitous HTTP tooling, intermediaries, debuggability, caches, and loose coupling.",
+ caveat: "HTTP and JSON overhead can be disproportionate for tiny machine-to-machine messages.",
+ authenticated: true,
+ }),
+ graphql: Object.freeze({
+ name: "GraphQL-ish HTTP",
+ era: "modern web",
+ punchline: "Ask precisely for what you need after introducing yourself to the syntax tree.",
+ value: "Client-selected response shape, schema introspection, and aggregation behind one endpoint.",
+ caveat: "Query flexibility shifts complexity into schema design, resolvers, authorization, cost controls, and caching.",
+ authenticated: true,
+ }),
+ "agent-tool": Object.freeze({
+ name: "Agent Tool Call",
+ era: "current AI stack",
+ punchline: "Tiny arithmetic escorted by a schema, descriptions, arguments, and several responsible adults.",
+ value: "Machine-readable affordances that let a model choose and call tools with validation.",
+ caveat: "Tool schemas can consume substantial context repeatedly. Compact contracts and reuse matter at scale.",
+ authenticated: true,
+ }),
+ });
+
+ const STYLE_ORDER = Object.freeze([
+ "punch-card",
+ "magnetic-tape",
+ "raw-tcp",
+ "udp",
+ "ftp-batch",
+ "rpc",
+ "corba",
+ "soap",
+ "rest",
+ "graphql",
+ "agent-tool",
+ ]);
+
+ function parseInt32(value, label) {
+ const text = String(value).trim();
+ if (!/^-?\d+$/.test(text)) {
+ throw new Error(label + " must be a whole signed 32-bit integer.");
+ }
+ const parsed = BigInt(text);
+ if (parsed < INT32_MIN || parsed > INT32_MAX) {
+ throw new Error(label + " must be between -2147483648 and 2147483647.");
+ }
+ return parsed;
+ }
+
+ function exactResult(left, right, operation) {
+ if (operation.slug === "add") return (left + right).toString();
+ if (operation.slug === "subtract") return (left - right).toString();
+ if (operation.slug === "multiply") return (left * right).toString();
+ if (right === 0n) throw new Error("Division by zero has been escalated to the architecture committee.");
+ if (left % right === 0n) return (left / right).toString();
+ let numerator = left;
+ let denominator = right;
+ if (denominator < 0n) {
+ numerator = -numerator;
+ denominator = -denominator;
+ }
+ return numerator.toString() + "/" + denominator.toString();
+ }
+
+ function asciiBytes(text) {
+ const bytes = new Uint8Array(text.length);
+ for (let index = 0; index < text.length; index += 1) {
+ const code = text.charCodeAt(index);
+ if (code > 127) throw new Error("Internal exhibit payload escaped ASCII containment.");
+ bytes[index] = code;
+ }
+ return bytes;
+ }
+
+ function hex(bytes) {
+ return Array.from(bytes, (value) => value.toString(16).padStart(2, "0")).join("");
+ }
+
+ function binaryMessage(operation, left, right) {
+ const bytes = new Uint8Array(9);
+ const view = new DataView(bytes.buffer);
+ view.setUint8(0, operation.opcode);
+ view.setInt32(1, Number(left), false);
+ view.setInt32(5, Number(right), false);
+ return bytes;
+ }
+
+ function tcpFrame(message) {
+ const bytes = new Uint8Array(11);
+ const view = new DataView(bytes.buffer);
+ view.setUint16(0, message.length, false);
+ bytes.set(message, 2);
+ return bytes;
+ }
+
+ function signed11(value) {
+ const sign = value < 0n ? "-" : "+";
+ const digits = (value < 0n ? -value : value).toString().padStart(10, "0");
+ return sign + digits;
+ }
+
+ function httpRequest(path, body) {
+ return (
+ "POST " + path + " HTTP/1.1\r\n" +
+ "Host: example.invalid\r\n" +
+ "Content-Type: application/json\r\n" +
+ "Accept: application/json\r\n" +
+ "X-Request-ID: 0042\r\n" +
+ "X-API-Key: " + LOST_KEY + "\r\n" +
+ "Content-Length: " + asciiBytes(body).length + "\r\n\r\n" +
+ body
+ );
+ }
+
+ function buildPayload(styleSlug, operation, left, right, intent) {
+ const leftText = left.toString();
+ const rightText = right.toString();
+ const message = binaryMessage(operation, left, right);
+
+ if (styleSlug === "punch-card") {
+ return (operation.code.padEnd(8) + leftText.padStart(11) + rightText.padStart(11)).padEnd(80);
+ }
+ if (styleSlug === "magnetic-tape") {
+ return operation.code + "|" + signed11(left) + "|" + signed11(right) + "\n";
+ }
+ if (styleSlug === "raw-tcp") return tcpFrame(message);
+ if (styleSlug === "udp") return message;
+ if (styleSlug === "ftp-batch") {
+ return "HDR|JOB=0042|COUNT=1\n" + intent + "\nEOF|COUNT=1|CHECK=HUMAN-EYEBALL\n";
+ }
+ if (styleSlug === "rpc") {
+ return "ArithmeticService." + operation.method + "(i32:" + leftText + ",i32:" + rightText + ")";
+ }
+ if (styleSlug === "corba") {
+ return "GIOP-ISH|IDL:heresy/Arithmetic:1.0|operation=" + operation.field + "|a=" + leftText + "|b=" + rightText + "|request_id=0042";
+ }
+ if (styleSlug === "soap") {
+ return "0042<" + operation.method + " xmlns=\"urn:heresy:arithmetic\">" + leftText + "" + rightText + "" + operation.method + ">";
+ }
+ if (styleSlug === "rest") {
+ const body = JSON.stringify({ operation: operation.field, arguments: { a: Number(left), b: Number(right) } });
+ return httpRequest("/v1/arithmetic/" + operation.field, body);
+ }
+ if (styleSlug === "graphql") {
+ const query = "mutation " + operation.method + "($a:Int!,$b:Int!){" + operation.field + "(a:$a,b:$b)}";
+ const body = JSON.stringify({ query: query, variables: { a: Number(left), b: Number(right) } });
+ return httpRequest("/graphql", body);
+ }
+ if (styleSlug === "agent-tool") {
+ return JSON.stringify({
+ authentication: { scheme: "api_key", status: "lost_again", last_known_location: ".env.old.backup.final2" },
+ tool: {
+ name: operation.field + "_two_integers",
+ description: operation.method + " exactly two signed integers and return the mathematical result.",
+ input_schema: {
+ type: "object",
+ properties: {
+ a: { type: "integer", minimum: -2147483648, maximum: 2147483647 },
+ b: { type: "integer", minimum: -2147483648, maximum: 2147483647 },
+ },
+ required: ["a", "b"],
+ additionalProperties: false,
+ },
+ },
+ arguments: { a: Number(left), b: Number(right) },
+ });
+ }
+ throw new Error("Unknown interface style: " + styleSlug);
+ }
+
+ function evaluate(leftValue, rightValue, operationSlug, styleSlug) {
+ const operation = OPERATIONS[operationSlug];
+ const style = STYLES[styleSlug];
+ if (!operation) throw new Error("Unknown arithmetic operation: " + operationSlug);
+ if (!style) throw new Error("Unknown interface style: " + styleSlug);
+
+ const left = parseInt32(leftValue, "Left operand");
+ const right = parseInt32(rightValue, "Right operand");
+ const result = exactResult(left, right, operation);
+ const intent = operation.code + " " + left.toString() + " " + right.toString();
+ const usefulBytes = asciiBytes(intent).length;
+ const payload = buildPayload(styleSlug, operation, left, right, intent);
+ const payloadBytes = typeof payload === "string" ? asciiBytes(payload).length : payload.length;
+ const authenticated = style.authenticated;
+
+ return Object.freeze({
+ result: result,
+ intent: intent,
+ usefulBytes: usefulBytes,
+ payloadBytes: payloadBytes,
+ ceremonyBytes: payloadBytes - usefulBytes,
+ estimatedTokens: Math.ceil(payloadBytes / 4),
+ ceremonyRatio: payloadBytes / usefulBytes,
+ payloadDisplay: typeof payload === "string" ? payload : "hex:" + hex(payload),
+ styleName: style.name,
+ era: style.era,
+ punchline: style.punchline,
+ valuePurchased: style.value,
+ caveat: style.caveat,
+ apiKeyStatus: authenticated ? "FOUND IN .env.old.backup.final2" : "NOT REQUIRED",
+ authenticationStatus: authenticated ? "401" : "NOT APPLICABLE",
+ remediation: authenticated ? "rewind tape" : "continue offending the abstraction layer",
+ });
+ }
+
+ return Object.freeze({
+ INT32_MIN: INT32_MIN,
+ INT32_MAX: INT32_MAX,
+ LOST_KEY: LOST_KEY,
+ OPERATIONS: OPERATIONS,
+ STYLES: STYLES,
+ STYLE_ORDER: STYLE_ORDER,
+ parseInt32: parseInt32,
+ evaluate: evaluate,
+ });
+});
From 1405e0a888fa066c6223f0a8cab5210e4ada8f87 Mon Sep 17 00:00:00 2001
From: Trent Slade <73952179+EmergentMonk@users.noreply.github.com>
Date: Sun, 6 Sep 2026 01:49:03 +0930
Subject: [PATCH 02/19] Add offline HERESY API Calculator shell
---
web/index.html | 144 +++++++++++++++++++++++++++++++++++++++++++++++++
1 file changed, 144 insertions(+)
create mode 100644 web/index.html
diff --git a/web/index.html b/web/index.html
new file mode 100644
index 0000000..6c87f5d
--- /dev/null
+++ b/web/index.html
@@ -0,0 +1,144 @@
+
+
+
+
+
+
+ HERESY API Calculator
+
+
+
+
+
+ QSOL-IMC SOFTWARE HERESY DIVISION
+ HERESY API Calculator
+ Do tiny arithmetic through an interface architecture wildly disproportionate to the task.
+ OFFLINE CAPABLE · NO BACKEND · NO API KEY FOR DAVE TO LOSE
+
+
+
+
+
1. Specify the dangerous mathematics
+
+
+
+
+
+
+
+
+
+
+
2. Receive the architecture
+
+
+
+
+
+ Mathematics
+ 4
+
+
+ Useful intent
+ ADD 2 2
+
+
+ Interface
+ Punch Card / Fixed Width
+
+
+
+
+
+ Useful bytes
+ 7
+
+
+ Payload bytes
+ 80
+
+
+ Approx. tokens
+ 20
+
+
+ Ceremony Ratio
+ 11.43×
+
+
+
+
+
+ FAKE WIRE REPRESENTATION
+ +73 ceremony bytes
+
+
+
+
+
+
+ Punchline
+
+
+
+ What the ceremony buys
+
+
+
+ Engineering caveat
+
+
+
+
+
+
+
+
3. Modern authentication ceremony
+
+
+ - API key status
- NOT REQUIRED
+ - Authentication status
- NOT APPLICABLE
+ - Recommended remediation
- continue offending the abstraction layer
+
+ provision → store safely → forget where → search .env variants → find somewhere unsafe → rotate → 401 → consider magnetic tape
+
+
+
+
+
+
+
+
+
From b9735975c3753315263208a46c0dbeb85d31ab2b Mon Sep 17 00:00:00 2001
From: Trent Slade <73952179+EmergentMonk@users.noreply.github.com>
Date: Sun, 6 Sep 2026 01:49:33 +0930
Subject: [PATCH 03/19] Style the offline HERESY API Calculator
---
web/style.css | 367 ++++++++++++++++++++++++++++++++++++++++++++++++++
1 file changed, 367 insertions(+)
create mode 100644 web/style.css
diff --git a/web/style.css b/web/style.css
new file mode 100644
index 0000000..1ab6900
--- /dev/null
+++ b/web/style.css
@@ -0,0 +1,367 @@
+:root {
+ color-scheme: dark;
+ --bg: #0c0e0f;
+ --panel: #141719;
+ --panel-2: #1a1f22;
+ --line: #30373b;
+ --text: #e8ecec;
+ --muted: #9fa9ad;
+ --accent: #e2b857;
+ --accent-soft: rgba(226, 184, 87, 0.14);
+ --danger: #ff8a65;
+ --ok: #a6d189;
+ --radius: 16px;
+ font-family: Inter, ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
+}
+
+* {
+ box-sizing: border-box;
+}
+
+body {
+ margin: 0;
+ min-height: 100vh;
+ background:
+ linear-gradient(rgba(255, 255, 255, 0.018) 1px, transparent 1px),
+ linear-gradient(90deg, rgba(255, 255, 255, 0.018) 1px, transparent 1px),
+ var(--bg);
+ background-size: 28px 28px;
+ color: var(--text);
+}
+
+button,
+input,
+select {
+ font: inherit;
+}
+
+.shell {
+ width: min(1180px, calc(100% - 32px));
+ margin: 0 auto;
+ padding: 56px 0 48px;
+}
+
+.hero {
+ max-width: 880px;
+ margin-bottom: 28px;
+}
+
+.eyebrow,
+.offline-badge,
+.metric-label,
+.wire-heading,
+.era {
+ font-family: "SFMono-Regular", Consolas, "Liberation Mono", monospace;
+ letter-spacing: 0.08em;
+ text-transform: uppercase;
+}
+
+.eyebrow {
+ margin: 0 0 10px;
+ color: var(--accent);
+ font-size: 0.78rem;
+}
+
+h1,
+h2,
+h3,
+p {
+ margin-top: 0;
+}
+
+h1 {
+ margin-bottom: 12px;
+ font-size: clamp(2.4rem, 7vw, 5.6rem);
+ line-height: 0.94;
+ letter-spacing: -0.05em;
+}
+
+.lede {
+ max-width: 720px;
+ margin-bottom: 18px;
+ color: var(--muted);
+ font-size: clamp(1rem, 2.2vw, 1.3rem);
+ line-height: 1.55;
+}
+
+.offline-badge {
+ display: inline-block;
+ margin: 0;
+ padding: 9px 12px;
+ border: 1px solid rgba(226, 184, 87, 0.45);
+ border-radius: 999px;
+ background: var(--accent-soft);
+ color: var(--accent);
+ font-size: 0.72rem;
+}
+
+.panel {
+ margin: 18px 0;
+ padding: clamp(20px, 4vw, 34px);
+ border: 1px solid var(--line);
+ border-radius: var(--radius);
+ background: linear-gradient(145deg, rgba(255,255,255,0.025), transparent 45%), var(--panel);
+ box-shadow: 0 18px 50px rgba(0, 0, 0, 0.22);
+}
+
+.panel-heading {
+ display: flex;
+ gap: 16px;
+ align-items: center;
+ justify-content: space-between;
+ margin-bottom: 24px;
+}
+
+.panel-heading h2 {
+ margin: 0;
+ font-size: clamp(1.15rem, 2.5vw, 1.65rem);
+}
+
+.status-light {
+ width: 11px;
+ height: 11px;
+ flex: 0 0 auto;
+ border-radius: 50%;
+ background: var(--ok);
+ box-shadow: 0 0 18px rgba(166, 209, 137, 0.65);
+}
+
+.equation-grid {
+ display: grid;
+ grid-template-columns: minmax(0, 1fr) minmax(150px, 0.7fr) minmax(0, 1fr);
+ gap: 14px;
+}
+
+label {
+ display: grid;
+ gap: 8px;
+ color: var(--muted);
+ font-size: 0.88rem;
+}
+
+input,
+select {
+ width: 100%;
+ min-height: 52px;
+ border: 1px solid var(--line);
+ border-radius: 10px;
+ background: #0f1213;
+ color: var(--text);
+ padding: 0 14px;
+ outline: none;
+}
+
+input:focus,
+select:focus,
+button:focus-visible,
+pre:focus-visible {
+ border-color: var(--accent);
+ box-shadow: 0 0 0 3px rgba(226, 184, 87, 0.14);
+}
+
+.style-picker {
+ margin-top: 16px;
+}
+
+button {
+ width: 100%;
+ min-height: 54px;
+ margin-top: 18px;
+ border: 1px solid #f1ca72;
+ border-radius: 10px;
+ background: var(--accent);
+ color: #14110a;
+ font-weight: 800;
+ letter-spacing: 0.04em;
+ cursor: pointer;
+}
+
+button:hover {
+ filter: brightness(1.06);
+}
+
+.error {
+ margin: 16px 0 0;
+ padding: 12px 14px;
+ border-left: 3px solid var(--danger);
+ background: rgba(255, 138, 101, 0.08);
+ color: #ffc2ae;
+}
+
+.era {
+ color: var(--accent);
+ font-size: 0.72rem;
+ text-align: right;
+}
+
+.result-strip,
+.metrics,
+.curatorial-grid {
+ display: grid;
+ gap: 12px;
+}
+
+.result-strip {
+ grid-template-columns: 0.6fr 1fr 1.2fr;
+ margin-bottom: 12px;
+}
+
+.result-strip > div,
+.metrics article,
+.curatorial-grid article,
+.authentication dl > div {
+ border: 1px solid var(--line);
+ border-radius: 12px;
+ background: var(--panel-2);
+}
+
+.result-strip > div,
+.metrics article {
+ padding: 16px;
+}
+
+.result-strip strong,
+.metrics strong {
+ display: block;
+ margin-top: 6px;
+ overflow-wrap: anywhere;
+ font-family: "SFMono-Regular", Consolas, "Liberation Mono", monospace;
+}
+
+.result-strip strong {
+ font-size: clamp(1.05rem, 2.5vw, 1.45rem);
+}
+
+.metrics {
+ grid-template-columns: repeat(4, 1fr);
+ margin: 12px 0;
+}
+
+.metric-label {
+ color: var(--muted);
+ font-size: 0.68rem;
+}
+
+.metrics strong {
+ font-size: 1.45rem;
+ color: var(--accent);
+}
+
+.wire-block {
+ margin-top: 12px;
+ overflow: hidden;
+ border: 1px solid var(--line);
+ border-radius: 12px;
+ background: #090b0c;
+}
+
+.wire-heading {
+ display: flex;
+ gap: 12px;
+ justify-content: space-between;
+ padding: 11px 14px;
+ border-bottom: 1px solid var(--line);
+ color: var(--muted);
+ font-size: 0.66rem;
+}
+
+pre {
+ max-height: 360px;
+ margin: 0;
+ padding: 18px;
+ overflow: auto;
+ white-space: pre-wrap;
+ overflow-wrap: anywhere;
+ color: #d9dedf;
+ font: 0.82rem/1.55 "SFMono-Regular", Consolas, "Liberation Mono", monospace;
+}
+
+.curatorial-grid {
+ grid-template-columns: repeat(3, 1fr);
+ margin-top: 12px;
+}
+
+.curatorial-grid article {
+ padding: 18px;
+}
+
+.curatorial-grid h3 {
+ margin-bottom: 9px;
+ color: var(--accent);
+ font-size: 0.9rem;
+}
+
+.curatorial-grid p {
+ margin-bottom: 0;
+ color: var(--muted);
+ line-height: 1.55;
+}
+
+.authentication dl {
+ display: grid;
+ grid-template-columns: repeat(3, 1fr);
+ gap: 12px;
+ margin: 0 0 16px;
+}
+
+.authentication dl > div {
+ padding: 16px;
+}
+
+.authentication dt {
+ margin-bottom: 7px;
+ color: var(--muted);
+ font-size: 0.76rem;
+}
+
+.authentication dd {
+ margin: 0;
+ overflow-wrap: anywhere;
+ font-family: "SFMono-Regular", Consolas, "Liberation Mono", monospace;
+ font-weight: 700;
+}
+
+.lifecycle {
+ margin: 0;
+ padding: 12px 14px;
+ border-radius: 10px;
+ background: #0e1112;
+ color: var(--muted);
+ font: 0.78rem/1.6 "SFMono-Regular", Consolas, "Liberation Mono", monospace;
+}
+
+footer {
+ padding: 14px 4px 0;
+ color: var(--muted);
+ font-size: 0.82rem;
+ line-height: 1.55;
+}
+
+footer code {
+ color: var(--accent);
+}
+
+@media (max-width: 760px) {
+ .shell {
+ width: min(100% - 20px, 1180px);
+ padding-top: 30px;
+ }
+
+ .equation-grid,
+ .result-strip,
+ .metrics,
+ .curatorial-grid,
+ .authentication dl {
+ grid-template-columns: 1fr;
+ }
+
+ .panel-heading,
+ .wire-heading {
+ align-items: flex-start;
+ flex-direction: column;
+ }
+
+ .era {
+ text-align: left;
+ }
+}
From 86901b5f47e2e2f68213a8dc037b3eeeff37c7ee Mon Sep 17 00:00:00 2001
From: Trent Slade <73952179+EmergentMonk@users.noreply.github.com>
Date: Sun, 6 Sep 2026 01:49:46 +0930
Subject: [PATCH 04/19] Wire calculator UI to the offline protocol core
---
web/app.js | 85 ++++++++++++++++++++++++++++++++++++++++++++++++++++++
1 file changed, 85 insertions(+)
create mode 100644 web/app.js
diff --git a/web/app.js b/web/app.js
new file mode 100644
index 0000000..c680954
--- /dev/null
+++ b/web/app.js
@@ -0,0 +1,85 @@
+(function () {
+ "use strict";
+
+ const core = window.HeresyCore;
+ const form = document.getElementById("calculator-form");
+ const styleSelect = document.getElementById("style");
+ const error = document.getElementById("error");
+
+ const fields = {
+ result: document.getElementById("result"),
+ intent: document.getElementById("intent"),
+ styleName: document.getElementById("style-name"),
+ era: document.getElementById("era"),
+ usefulBytes: document.getElementById("useful-bytes"),
+ payloadBytes: document.getElementById("payload-bytes"),
+ tokens: document.getElementById("tokens"),
+ ceremonyRatio: document.getElementById("ceremony-ratio"),
+ ceremonyBytes: document.getElementById("ceremony-bytes"),
+ payload: document.getElementById("payload"),
+ punchline: document.getElementById("punchline"),
+ valuePurchased: document.getElementById("value-purchased"),
+ caveat: document.getElementById("caveat"),
+ apiKeyStatus: document.getElementById("api-key-status"),
+ authStatus: document.getElementById("auth-status"),
+ remediation: document.getElementById("remediation"),
+ };
+
+ function populateStyles() {
+ core.STYLE_ORDER.forEach(function (slug) {
+ const style = core.STYLES[slug];
+ const option = document.createElement("option");
+ option.value = slug;
+ option.textContent = style.era + " · " + style.name;
+ styleSelect.appendChild(option);
+ });
+ }
+
+ function render(report) {
+ fields.result.textContent = report.result;
+ fields.intent.textContent = report.intent;
+ fields.styleName.textContent = report.styleName;
+ fields.era.textContent = report.era;
+ fields.usefulBytes.textContent = String(report.usefulBytes);
+ fields.payloadBytes.textContent = String(report.payloadBytes);
+ fields.tokens.textContent = String(report.estimatedTokens);
+ fields.ceremonyRatio.textContent = report.ceremonyRatio.toFixed(2) + "×";
+ fields.ceremonyBytes.textContent = (report.ceremonyBytes >= 0 ? "+" : "") + report.ceremonyBytes + " ceremony bytes";
+ fields.payload.textContent = report.payloadDisplay;
+ fields.punchline.textContent = report.punchline;
+ fields.valuePurchased.textContent = report.valuePurchased;
+ fields.caveat.textContent = report.caveat;
+ fields.apiKeyStatus.textContent = report.apiKeyStatus;
+ fields.authStatus.textContent = report.authenticationStatus;
+ fields.remediation.textContent = report.remediation;
+ }
+
+ function calculate() {
+ error.hidden = true;
+ error.textContent = "";
+ try {
+ const report = core.evaluate(
+ document.getElementById("left").value,
+ document.getElementById("right").value,
+ document.getElementById("operation").value,
+ styleSelect.value
+ );
+ render(report);
+ } catch (problem) {
+ error.textContent = problem instanceof Error ? problem.message : String(problem);
+ error.hidden = false;
+ }
+ }
+
+ form.addEventListener("submit", function (event) {
+ event.preventDefault();
+ calculate();
+ });
+
+ ["left", "right", "operation", "style"].forEach(function (id) {
+ document.getElementById(id).addEventListener("change", calculate);
+ });
+
+ populateStyles();
+ calculate();
+})();
From 1e420673df630999c0abc2fbc1eb4dafd3c570c2 Mon Sep 17 00:00:00 2001
From: Trent Slade <73952179+EmergentMonk@users.noreply.github.com>
Date: Sun, 6 Sep 2026 01:50:02 +0930
Subject: [PATCH 05/19] Keep Pages deployment purely static
---
web/.nojekyll | 0
1 file changed, 0 insertions(+), 0 deletions(-)
create mode 100644 web/.nojekyll
diff --git a/web/.nojekyll b/web/.nojekyll
new file mode 100644
index 0000000..e69de29
From f0f7b68b979a38bbd97484a77b86e82f5c45f15e Mon Sep 17 00:00:00 2001
From: Trent Slade <73952179+EmergentMonk@users.noreply.github.com>
Date: Sun, 6 Sep 2026 01:50:14 +0930
Subject: [PATCH 06/19] Add executable offline calculator smoke tests
---
tests/web_smoke.js | 53 ++++++++++++++++++++++++++++++++++++++++++++++
1 file changed, 53 insertions(+)
create mode 100644 tests/web_smoke.js
diff --git a/tests/web_smoke.js b/tests/web_smoke.js
new file mode 100644
index 0000000..a7bab51
--- /dev/null
+++ b/tests/web_smoke.js
@@ -0,0 +1,53 @@
+"use strict";
+
+const assert = require("assert");
+const core = require("../web/heresy-core.js");
+
+assert.strictEqual(core.STYLE_ORDER.length, 11);
+assert.deepStrictEqual(Object.keys(core.OPERATIONS), ["add", "subtract", "multiply", "divide"]);
+
+const punch = core.evaluate("2", "2", "add", "punch-card");
+assert.strictEqual(punch.result, "4");
+assert.strictEqual(punch.intent, "ADD 2 2");
+assert.strictEqual(punch.usefulBytes, 7);
+assert.strictEqual(punch.payloadBytes, 80);
+
+const udp = core.evaluate("2", "2", "add", "udp");
+const tcp = core.evaluate("2", "2", "add", "raw-tcp");
+assert.strictEqual(udp.payloadBytes, 9);
+assert.strictEqual(tcp.payloadBytes, 11);
+assert.ok(udp.payloadDisplay.startsWith("hex:01"));
+assert.ok(tcp.payloadDisplay.startsWith("hex:0009"));
+
+const rest = core.evaluate("17", "-3", "subtract", "rest");
+assert.strictEqual(rest.result, "20");
+assert.strictEqual(rest.authenticationStatus, "401");
+assert.strictEqual(rest.apiKeyStatus, "FOUND IN .env.old.backup.final2");
+assert.ok(rest.payloadDisplay.includes("X-API-Key: LOST-IN-.env.old.backup.final2"));
+
+const agent = core.evaluate("2147483647", "-2147483648", "add", "agent-tool");
+const agentPayload = JSON.parse(agent.payloadDisplay);
+for (const name of ["a", "b"]) {
+ assert.strictEqual(agentPayload.tool.input_schema.properties[name].minimum, -2147483648);
+ assert.strictEqual(agentPayload.tool.input_schema.properties[name].maximum, 2147483647);
+}
+
+assert.strictEqual(core.evaluate("6", "3", "divide", "rpc").result, "2");
+assert.strictEqual(core.evaluate("1", "3", "divide", "rpc").result, "1/3");
+assert.strictEqual(core.evaluate("-1", "-3", "divide", "rpc").result, "1/3");
+assert.throws(() => core.evaluate("1", "0", "divide", "rpc"), /architecture committee/);
+assert.throws(() => core.parseInt32("2147483648", "operand"), /between/);
+assert.throws(() => core.parseInt32("3.14", "operand"), /whole/);
+
+for (const style of core.STYLE_ORDER) {
+ const first = core.evaluate("9", "4", "multiply", style);
+ const second = core.evaluate("9", "4", "multiply", style);
+ assert.deepStrictEqual(first, second);
+ assert.ok(first.payloadBytes > 0);
+ assert.ok(first.estimatedTokens > 0);
+ assert.ok(first.punchline.length > 0);
+ assert.ok(first.valuePurchased.length > 0);
+ assert.ok(first.caveat.length > 0);
+}
+
+console.log("HERESY web core: deterministic, offline, and still needlessly ceremonial.");
From 06174a3c57e46b98fd37f3cf170dfbedc4d38eb0 Mon Sep 17 00:00:00 2001
From: Trent Slade <73952179+EmergentMonk@users.noreply.github.com>
Date: Sun, 6 Sep 2026 01:50:25 +0930
Subject: [PATCH 07/19] Assert calculator remains offline-first
---
tests/test_web.py | 56 +++++++++++++++++++++++++++++++++++++++++++++++
1 file changed, 56 insertions(+)
create mode 100644 tests/test_web.py
diff --git a/tests/test_web.py b/tests/test_web.py
new file mode 100644
index 0000000..13bd447
--- /dev/null
+++ b/tests/test_web.py
@@ -0,0 +1,56 @@
+from pathlib import Path
+import unittest
+
+
+ROOT = Path(__file__).resolve().parents[1]
+WEB = ROOT / "web"
+
+
+class OfflineCalculatorTests(unittest.TestCase):
+ def test_static_app_has_local_entrypoint_and_assets(self) -> None:
+ index = (WEB / "index.html").read_text(encoding="utf-8")
+ self.assertIn('href="./style.css"', index)
+ self.assertIn('src="./heresy-core.js"', index)
+ self.assertIn('src="./app.js"', index)
+ for name in ("index.html", "style.css", "heresy-core.js", "app.js", ".nojekyll"):
+ self.assertTrue((WEB / name).exists(), name)
+
+ def test_static_app_has_no_remote_runtime_dependency(self) -> None:
+ combined = "\n".join(
+ (WEB / name).read_text(encoding="utf-8")
+ for name in ("index.html", "style.css", "heresy-core.js", "app.js")
+ )
+ self.assertNotIn("http://", combined.lower())
+ self.assertNotIn("https://", combined.lower())
+ self.assertNotIn("fetch(", combined)
+ self.assertNotIn("XMLHttpRequest", combined)
+ self.assertNotIn("WebSocket", combined)
+
+ def test_ui_retains_required_joke_and_measurement_surfaces(self) -> None:
+ index = (WEB / "index.html").read_text(encoding="utf-8")
+ for marker in (
+ 'id="result"',
+ 'id="intent"',
+ 'id="payload"',
+ 'id="payload-bytes"',
+ 'id="tokens"',
+ 'id="ceremony-ratio"',
+ 'id="punchline"',
+ 'id="api-key-status"',
+ 'id="auth-status"',
+ "NO API KEY FOR DAVE TO LOSE",
+ "rewind tape",
+ ):
+ self.assertIn(marker, index)
+
+ def test_pages_workflow_publishes_only_static_web_directory(self) -> None:
+ workflow = (ROOT / ".github" / "workflows" / "pages.yml").read_text(encoding="utf-8")
+ self.assertIn("actions/upload-pages-artifact", workflow)
+ self.assertIn("actions/deploy-pages", workflow)
+ self.assertIn("path: web", workflow)
+ self.assertIn("pages: write", workflow)
+ self.assertIn("id-token: write", workflow)
+
+
+if __name__ == "__main__":
+ unittest.main()
From 01a1c1f5b89c9e2c83eff4ae7cb6759731b510d0 Mon Sep 17 00:00:00 2001
From: Trent Slade <73952179+EmergentMonk@users.noreply.github.com>
Date: Sun, 6 Sep 2026 01:50:32 +0930
Subject: [PATCH 08/19] Deploy offline calculator to GitHub Pages
---
.github/workflows/pages.yml | 41 +++++++++++++++++++++++++++++++++++++
1 file changed, 41 insertions(+)
create mode 100644 .github/workflows/pages.yml
diff --git a/.github/workflows/pages.yml b/.github/workflows/pages.yml
new file mode 100644
index 0000000..b59e12a
--- /dev/null
+++ b/.github/workflows/pages.yml
@@ -0,0 +1,41 @@
+name: deploy-pages
+
+on:
+ push:
+ branches: [main]
+ paths:
+ - "web/**"
+ - ".github/workflows/pages.yml"
+ workflow_dispatch:
+
+permissions:
+ contents: read
+ pages: write
+ id-token: write
+
+concurrency:
+ group: pages
+ cancel-in-progress: true
+
+jobs:
+ deploy:
+ environment:
+ name: github-pages
+ url: ${{ steps.deployment.outputs.page_url }}
+ runs-on: ubuntu-latest
+ timeout-minutes: 5
+ steps:
+ - name: Checkout
+ uses: actions/checkout@v4
+
+ - name: Configure Pages
+ uses: actions/configure-pages@v5
+
+ - name: Upload static calculator
+ uses: actions/upload-pages-artifact@v3
+ with:
+ path: web
+
+ - name: Deploy to GitHub Pages
+ id: deployment
+ uses: actions/deploy-pages@v4
From a2383fc51f16cad32b9df81cf0337feecd2635da Mon Sep 17 00:00:00 2001
From: Trent Slade <73952179+EmergentMonk@users.noreply.github.com>
Date: Sun, 6 Sep 2026 01:50:46 +0930
Subject: [PATCH 09/19] Validate offline calculator in CI
---
.github/workflows/test.yml | 6 +++++-
1 file changed, 5 insertions(+), 1 deletion(-)
diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml
index e590b90..34209f9 100644
--- a/.github/workflows/test.yml
+++ b/.github/workflows/test.yml
@@ -31,7 +31,7 @@ jobs:
with:
python-version: "3.13"
- - name: Validate Time Machine
+ - name: Validate Time Machine and offline calculator
run: |
python -m compileall -q heresy.py tests
python -m unittest discover -s tests -v
@@ -40,3 +40,7 @@ jobs:
python heresy.py --left 17 --right -3 --style udp --json >/dev/null
python heresy.py --list-styles >/dev/null
python heresy.py --old-school-defense >/dev/null
+ node --check web/heresy-core.js
+ node --check web/app.js
+ node --check tests/web_smoke.js
+ node tests/web_smoke.js
From 33f3b790bf7e85c59eb467435e967903bfedab52 Mon Sep 17 00:00:00 2001
From: Trent Slade <73952179+EmergentMonk@users.noreply.github.com>
Date: Sun, 6 Sep 2026 01:51:11 +0930
Subject: [PATCH 10/19] Mark offline calculator contract implemented
---
ROADMAP.md | 85 +++++++++++++++++++-----------------------------------
1 file changed, 29 insertions(+), 56 deletions(-)
diff --git a/ROADMAP.md b/ROADMAP.md
index 37500b1..0221d44 100644
--- a/ROADMAP.md
+++ b/ROADMAP.md
@@ -10,9 +10,9 @@ Established the dependency-free museum, canonical `ADD 2 2` experiment, Ceremony
## PR #2 — API TIME MACHINE
-**Status:** implemented by PR #2.
+**Status:** implemented and merged.
-Turn the protocol museum into a dynamic historical comparison engine for the same tiny semantic intent.
+Turned the protocol museum into a dynamic historical comparison engine for the same tiny semantic intent.
### Delivered contract
@@ -34,77 +34,50 @@ Turn the protocol museum into a dynamic historical comparison engine for the sam
- detailed curatorial dossier view
- deterministic JSON output
-Every exhibit reports:
-
-- useful semantic intent
-- useful bytes
-- application payload bytes
-- ceremony bytes relative to useful intent
-- deliberately crude approximate token count
-- Ceremony Ratio
-- protocol-specific punchline
-- what the extra ceremony buys
-- honest engineering caveat
-- authentication status where applicable
-
-### Running gag
-
-Modern authenticated modes retain the API-key lifecycle:
-
-```text
-provision -> store safely -> forget where -> search .env variants -> find somewhere unsafe -> rotate -> 401 -> consider magnetic tape
-```
+Every exhibit reports useful semantics, payload metrics, approximate token ceremony, Ceremony Ratio, a punchline, the value purchased by the abstraction, an engineering caveat, and authentication status where applicable.
## PR #3 — HERESY API CALCULATOR
-**Status:** next.
+**Status:** implemented by PR #3.
-Build an offline-first HTML/CSS/JavaScript joke calculator and publish the same static app through GitHub Pages.
+Build an offline-first HTML/CSS/JavaScript joke calculator and publish the exact same static app through GitHub Pages.
-### Hard requirements
+### Delivered contract
-- plain HTML, CSS, and JavaScript
+- plain HTML, CSS, and JavaScript under `web/`
- no runtime framework
- no backend
-- no external API dependency
-- no API key required, because Dave would lose it
-- works when opened directly from local disk
-- works when served through GitHub Pages
-- deterministic calculations
+- no package installation
+- no external API or network dependency
+- no API key required to run the calculator, because Dave would lose it
+- opens directly from `web/index.html` on local disk
+- deploys the same `web/` directory through GitHub Pages
+- deterministic signed-int32 operands
+- four basic operations: add, subtract, multiply, and divide
+- exact fractional display for non-integral division results
+- all eleven API Time Machine interface styles
+- fake wire representation for the selected style
+- useful bytes, payload bytes, approximate tokens, ceremony bytes, and Ceremony Ratio
+- protocol-specific punchline, engineering value, and caveat
+- synthetic lost-key / 401 gag for REST, GraphQL, and agent-tool modes
- responsive desktop/mobile layout
+- executable JavaScript core regressions plus offline/static CI checks
-### Joke interaction
-
-The user enters a tiny arithmetic operation, for example `2 + 2`, then selects an unnecessarily elaborate interface style.
-
-The calculator shows:
-
-1. the actual mathematical answer
-2. the useful semantic request
-3. the fake wire representation for the chosen era/style
-4. payload bytes
-5. approximate token ceremony
-6. Ceremony Ratio
-7. an era-specific punchline
-8. a modern-authentication failure gag where appropriate
+### Running gag
-Example outcome:
+Modern authenticated modes retain the lifecycle:
```text
-Mathematics: 4
-Useful intent: ADD 2 2
-Useful bytes: 7
-Enterprise ceremony: 487
-API key status: FOUND IN .env.old.backup.final2
-Authentication status: 401
-Recommended remediation: rewind tape
+provision -> store safely -> forget where -> search .env variants -> find somewhere unsafe -> rotate -> 401 -> consider magnetic tape
```
-### GitHub Pages
+The calculator itself needs no credential. The fake modern API exhibits are the only things irresponsible enough to entrust Dave with one.
+
+### GitHub Pages invariant
-The Pages deployment should publish exactly the static offline-capable application. GitHub Pages is a distribution surface, not a runtime dependency.
+GitHub Pages publishes exactly `web/` as a static artifact. It is a distribution surface, not a runtime dependency.
-If the internet disappears after loading the repository, the calculator should still be able to insult APIs locally.
+If the internet disappears, opening `web/index.html` locally must still provide the same arithmetic, protocol payloads, metrics, and jokes.
## Later gallery wings
From d0da711b1485d8d3aa8c5f5405023dff90bfc8a1 Mon Sep 17 00:00:00 2001
From: Trent Slade <73952179+EmergentMonk@users.noreply.github.com>
Date: Sun, 6 Sep 2026 01:51:37 +0930
Subject: [PATCH 11/19] Document offline HERESY API Calculator
---
README.md | 79 ++++++++++++++++++++++++++++++++++++++++++++-----------
1 file changed, 64 insertions(+), 15 deletions(-)
diff --git a/README.md b/README.md
index aba1b99..a491564 100644
--- a/README.md
+++ b/README.md
@@ -6,11 +6,62 @@ HERESY-API asks a deeply irresponsible question:
**What if modern API ceremony had to justify itself in a museum beside punch cards, magnetic tape, raw TCP frames, UDP datagrams, batch files, CORBA, SOAP, JSON, GraphQL, and agent tool schemas?**
-The answer is the **API Time Machine**.
+The answer now comes in two forms: the Python **API Time Machine** and the browser-based **HERESY API Calculator**.
+
+## HERESY API Calculator
+
+The calculator is a plain HTML/CSS/JavaScript static app under [`web/`](web/).
+
+Open it locally with no server at all:
+
+```text
+web/index.html
+```
+
+Or use the GitHub Pages deployment after PR #3 lands:
+
+```text
+https://qsolkcb.github.io/HERESY-API/
+```
+
+GitHub Pages is only the display case. The exact same files work directly from local disk.
+
+### Calculator contract
+
+- no framework
+- no backend
+- no package install
+- no external API
+- no network dependency
+- no API key required, because Dave would lose it
+- signed 32-bit integer operands
+- add, subtract, multiply, and divide
+- exact fractional display for non-integral division
+- all eleven Time Machine interface styles
+- fake wire representation for the selected era
+- useful bytes, payload bytes, approximate tokens, ceremony bytes, and Ceremony Ratio
+- protocol-specific punchline
+- what the abstraction actually buys
+- honest engineering caveat
+- synthetic 401 authentication tragedy for modern modes
+
+Example conceptual output:
+
+```text
+Mathematics: 4
+Useful intent: ADD 2 2
+Useful bytes: 7
+Interface: Agent Tool Call
+API key status: FOUND IN .env.old.backup.final2
+Authentication status: 401
+Recommended remediation: rewind tape
+```
+
+The app uses only relative local assets. If the internet disappears, it retains enough functionality to continue insulting APIs.
## API Time Machine
-The default experiment remains magnificently difficult:
+The canonical command-line experiment remains magnificently difficult:
```text
ADD 2 2
@@ -58,7 +109,7 @@ Produce machine-readable metadata about our joke about machine-readable metadata
python3 heresy.py --left 10 --right -7 --style graphql --json
```
-The operands are intentionally restricted to signed 32-bit integers so the binary TCP/UDP exhibits remain deterministic and structurally comparable.
+The Python Time Machine operands are restricted to signed 32-bit integers so the binary TCP/UDP exhibits remain deterministic and structurally comparable.
## Current timeline
@@ -70,7 +121,7 @@ The operands are intentionally restricted to signed 32-bit integers so the binar
| UDP-style Datagram | 1970s-1980s | The packet can simply leave. No architecture review calendar invite required. |
| FTP / Batch File | 1970s-1990s | Real-time, if tomorrow morning counts as a latency target. |
| RPC-ish Invocation | 1980s-1990s | The network is local until packet loss files an objection. |
-| CORBA / IDL Theatre | 1990s | The addition has been referred to the Object Request Broker. |
+| CORBA / IDL Theatre | 1990s | The arithmetic has been referred to the Object Request Broker. |
| SOAP-ish XML | late 1990s-2000s | The integer has entered the XML cathedral and acquired a namespace. |
| REST-ish HTTP + JSON | modern web | Human-readable bureaucracy. Dave has lost the API key again. |
| GraphQL-ish HTTP | modern web | Ask precisely for what you need after introducing yourself to the syntax tree. |
@@ -78,15 +129,15 @@ The operands are intentionally restricted to signed 32-bit integers so the binar
## Ceremony Ratio
-For any Time Machine request:
+For any exhibit:
```text
Ceremony Ratio = application payload bytes / useful semantic intent bytes
```
-For the canonical `ADD 2 2`, useful intent is seven ASCII bytes.
+For canonical `ADD 2 2`, useful intent is seven ASCII bytes.
-The approximate token meter remains deliberately crude:
+The approximate token meter is deliberately crude:
```text
estimated tokens = ceil(application payload bytes / 4)
@@ -94,7 +145,7 @@ estimated tokens = ceil(application payload bytes / 4)
It is a joke-meter, not a tokenizer, benchmark, protocol-efficiency paper, or invoice predictor.
-Network headers, TLS, Ethernet, filesystem overhead, physical-media overhead, server implementation cost, latency, reliability, maintenance, observability, governance, and all the other things capable of ruining a simple chart are intentionally outside this metric.
+Network headers outside the represented application payload, TLS, Ethernet, filesystem overhead, physical-media overhead, server implementation cost, latency, reliability, maintenance, observability, governance, and all the other things capable of ruining a simple chart are intentionally outside this metric.
## The Heresies
@@ -107,6 +158,7 @@ Network headers, TLS, Ethernet, filesystem overhead, physical-media overhead, se
7. **An old mechanism can be unsuitable for modern production and still make a devastating comparison at a deliberately tiny scale.**
8. **An API key stored safely is apparently indistinguishable from an API key lost forever.**
9. **If an abstraction adds bytes, the Time Machine is allowed to ask what those bytes purchased.**
+10. **A calculator that needs the cloud to add two integers has volunteered as an exhibit.**
## Why the heretics occasionally have a point
@@ -138,7 +190,7 @@ For **MOVE THIS FILE OVER THERE**, a durable file can be wonderfully literal: in
The CORBA stop is deliberately **GIOP-ish theatre**, not a byte-accurate packet capture. IDL is normally a build-time interface contract and is not resent on every call.
-That caveat is part of the joke's constitution: HERESY-API may exaggerate bureaucracy, but it does not get to falsify where the bureaucracy lives.
+HERESY-API may exaggerate bureaucracy, but it does not get to falsify where the bureaucracy lives.
## Modern authentication ceremony
@@ -171,25 +223,22 @@ For the technically annoying version of the joke:
python3 heresy.py --old-school-defense
```
-This prints the narrow cases for TCP, UDP, and batch files together with the bill each one leaves on the table.
-
## Project sequence
See [`ROADMAP.md`](ROADMAP.md).
- **PR #1 — Protocol Museum:** foundation, curatorial rules, metrics, and first exhibits.
- **PR #2 — API Time Machine:** dynamic operands, full historical timeline, filtering, detailed dossiers, engineering value/caveats, UDP, CORBA, and persistent API-key tragedy.
-- **PR #3 — HERESY API CALCULATOR:** offline-first plain HTML/CSS/JavaScript calculator distributed through GitHub Pages, with no backend and therefore no API key for Dave to lose.
-
-GitHub Pages will be the display case, not a runtime dependency.
+- **PR #3 — HERESY API Calculator:** offline-first HTML/CSS/JavaScript calculator, with GitHub Pages as a static distribution surface only.
## Run the tests
```bash
python3 -m unittest discover -s tests -v
+node tests/web_smoke.js
```
-No runtime dependencies are required.
+No runtime dependencies are required by the project itself.
## Important lack of seriousness
From f2ec9aabca8b9404e5f6b175efcef3e3d8ce083e Mon Sep 17 00:00:00 2001
From: Trent Slade <73952179+EmergentMonk@users.noreply.github.com>
Date: Sun, 6 Sep 2026 01:52:58 +0930
Subject: [PATCH 12/19] Test generated authentication remediation in offline
core
---
tests/test_web.py | 4 +++-
1 file changed, 3 insertions(+), 1 deletion(-)
diff --git a/tests/test_web.py b/tests/test_web.py
index 13bd447..f53f67e 100644
--- a/tests/test_web.py
+++ b/tests/test_web.py
@@ -28,6 +28,7 @@ def test_static_app_has_no_remote_runtime_dependency(self) -> None:
def test_ui_retains_required_joke_and_measurement_surfaces(self) -> None:
index = (WEB / "index.html").read_text(encoding="utf-8")
+ core = (WEB / "heresy-core.js").read_text(encoding="utf-8")
for marker in (
'id="result"',
'id="intent"',
@@ -39,9 +40,10 @@ def test_ui_retains_required_joke_and_measurement_surfaces(self) -> None:
'id="api-key-status"',
'id="auth-status"',
"NO API KEY FOR DAVE TO LOSE",
- "rewind tape",
):
self.assertIn(marker, index)
+ self.assertIn('authenticationStatus: authenticated ? "401"', core)
+ self.assertIn('remediation: authenticated ? "rewind tape"', core)
def test_pages_workflow_publishes_only_static_web_directory(self) -> None:
workflow = (ROOT / ".github" / "workflows" / "pages.yml").read_text(encoding="utf-8")
From 3cc1c7e5d0b9d2c6c55cefee27ebc5f3e717964d Mon Sep 17 00:00:00 2001
From: Trent Slade <73952179+EmergentMonk@users.noreply.github.com>
Date: Sun, 6 Sep 2026 01:54:14 +0930
Subject: [PATCH 13/19] Move CI actions onto Node 24 compatible majors
---
.github/workflows/test.yml | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml
index 34209f9..a116dbe 100644
--- a/.github/workflows/test.yml
+++ b/.github/workflows/test.yml
@@ -24,10 +24,10 @@ jobs:
timeout-minutes: 5
steps:
- name: Checkout
- uses: actions/checkout@v4
+ uses: actions/checkout@v6
- name: Set up Python
- uses: actions/setup-python@v5
+ uses: actions/setup-python@v7
with:
python-version: "3.13"
From d022cb54fadd8d8fd1188f389fd37c24808d6465 Mon Sep 17 00:00:00 2001
From: Trent Slade <73952179+EmergentMonk@users.noreply.github.com>
Date: Sun, 6 Sep 2026 01:54:27 +0930
Subject: [PATCH 14/19] Refresh Pages workflow action majors
---
.github/workflows/pages.yml | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/.github/workflows/pages.yml b/.github/workflows/pages.yml
index b59e12a..165ecf0 100644
--- a/.github/workflows/pages.yml
+++ b/.github/workflows/pages.yml
@@ -26,13 +26,13 @@ jobs:
timeout-minutes: 5
steps:
- name: Checkout
- uses: actions/checkout@v4
+ uses: actions/checkout@v6
- name: Configure Pages
uses: actions/configure-pages@v5
- name: Upload static calculator
- uses: actions/upload-pages-artifact@v3
+ uses: actions/upload-pages-artifact@v4
with:
path: web
From 446680c7e1be927b37c045bfd6ef98f020f8cc19 Mon Sep 17 00:00:00 2001
From: Trent Slade <73952179+EmergentMonk@users.noreply.github.com>
Date: Sun, 6 Sep 2026 02:02:17 +0930
Subject: [PATCH 15/19] Clear stale calculator output on validation errors
---
web/app.js | 7 +++++++
1 file changed, 7 insertions(+)
diff --git a/web/app.js b/web/app.js
index c680954..21d5b09 100644
--- a/web/app.js
+++ b/web/app.js
@@ -35,6 +35,12 @@
});
}
+ function clearReport() {
+ Object.keys(fields).forEach(function (name) {
+ fields[name].textContent = "";
+ });
+ }
+
function render(report) {
fields.result.textContent = report.result;
fields.intent.textContent = report.intent;
@@ -66,6 +72,7 @@
);
render(report);
} catch (problem) {
+ clearReport();
error.textContent = problem instanceof Error ? problem.message : String(problem);
error.hidden = false;
}
From 5563022c3baf4d34bfd192d9e232a33eca2a7a9f Mon Sep 17 00:00:00 2001
From: Trent Slade <73952179+EmergentMonk@users.noreply.github.com>
Date: Sun, 6 Sep 2026 02:02:40 +0930
Subject: [PATCH 16/19] Add DOM regression for stale calculator output
---
tests/app_dom_smoke.js | 104 +++++++++++++++++++++++++++++++++++++++++
1 file changed, 104 insertions(+)
create mode 100644 tests/app_dom_smoke.js
diff --git a/tests/app_dom_smoke.js b/tests/app_dom_smoke.js
new file mode 100644
index 0000000..e26726f
--- /dev/null
+++ b/tests/app_dom_smoke.js
@@ -0,0 +1,104 @@
+"use strict";
+
+const assert = require("assert");
+const core = require("../web/heresy-core.js");
+
+function element(initial) {
+ return {
+ value: initial && Object.prototype.hasOwnProperty.call(initial, "value") ? initial.value : "",
+ textContent: initial && Object.prototype.hasOwnProperty.call(initial, "textContent") ? initial.textContent : "",
+ hidden: initial && Object.prototype.hasOwnProperty.call(initial, "hidden") ? initial.hidden : false,
+ listeners: {},
+ children: [],
+ addEventListener(type, handler) {
+ this.listeners[type] = handler;
+ },
+ appendChild(child) {
+ this.children.push(child);
+ if (!this.value && child.value) {
+ this.value = child.value;
+ }
+ },
+ };
+}
+
+const ids = [
+ "calculator-form",
+ "style",
+ "error",
+ "result",
+ "intent",
+ "style-name",
+ "era",
+ "useful-bytes",
+ "payload-bytes",
+ "tokens",
+ "ceremony-ratio",
+ "ceremony-bytes",
+ "payload",
+ "punchline",
+ "value-purchased",
+ "caveat",
+ "api-key-status",
+ "auth-status",
+ "remediation",
+ "left",
+ "right",
+ "operation",
+];
+
+const elements = Object.fromEntries(ids.map((id) => [id, element()]));
+elements.left.value = "2";
+elements.right.value = "2";
+elements.operation.value = "add";
+elements.error.hidden = true;
+
+const documentStub = {
+ getElementById(id) {
+ return elements[id];
+ },
+ createElement() {
+ return element();
+ },
+};
+
+global.window = { HeresyCore: core };
+global.document = documentStub;
+
+require("../web/app.js");
+
+assert.strictEqual(elements.result.textContent, "4");
+assert.strictEqual(elements.intent.textContent, "ADD 2 2");
+assert.ok(elements.payload.textContent.length > 0);
+assert.strictEqual(elements.error.hidden, true);
+
+// Force a failed calculation after a successful one.
+elements.operation.value = "divide";
+elements.right.value = "0";
+elements.right.listeners.change();
+
+assert.strictEqual(elements.error.hidden, false);
+assert.match(elements.error.textContent, /architecture committee/);
+
+for (const id of [
+ "result",
+ "intent",
+ "style-name",
+ "era",
+ "useful-bytes",
+ "payload-bytes",
+ "tokens",
+ "ceremony-ratio",
+ "ceremony-bytes",
+ "payload",
+ "punchline",
+ "value-purchased",
+ "caveat",
+ "api-key-status",
+ "auth-status",
+ "remediation",
+]) {
+ assert.strictEqual(elements[id].textContent, "", id + " retained stale output");
+}
+
+console.log("HERESY DOM adapter: failed calculations clear stale museum output.");
From 4f647c742c32c55f24a3b6dfe8a11b35d3851fcc Mon Sep 17 00:00:00 2001
From: Trent Slade <73952179+EmergentMonk@users.noreply.github.com>
Date: Sun, 6 Sep 2026 02:02:54 +0930
Subject: [PATCH 17/19] Run DOM stale-state regression in CI
---
.github/workflows/test.yml | 2 ++
1 file changed, 2 insertions(+)
diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml
index a116dbe..7ffb164 100644
--- a/.github/workflows/test.yml
+++ b/.github/workflows/test.yml
@@ -43,4 +43,6 @@ jobs:
node --check web/heresy-core.js
node --check web/app.js
node --check tests/web_smoke.js
+ node --check tests/app_dom_smoke.js
node tests/web_smoke.js
+ node tests/app_dom_smoke.js
From a1dfce6cac97100abfa0b30b1dd5e6f6ffb3e408 Mon Sep 17 00:00:00 2001
From: Trent Slade <73952179+EmergentMonk@users.noreply.github.com>
Date: Sun, 6 Sep 2026 02:13:40 +0930
Subject: [PATCH 18/19] Normalize exact fractional division results
---
web/heresy-core.js | 14 ++++++++++++++
1 file changed, 14 insertions(+)
diff --git a/web/heresy-core.js b/web/heresy-core.js
index 1dcc47c..525d892 100644
--- a/web/heresy-core.js
+++ b/web/heresy-core.js
@@ -136,6 +136,17 @@
return parsed;
}
+ function greatestCommonDivisor(left, right) {
+ let a = left < 0n ? -left : left;
+ let b = right < 0n ? -right : right;
+ while (b !== 0n) {
+ const remainder = a % b;
+ a = b;
+ b = remainder;
+ }
+ return a;
+ }
+
function exactResult(left, right, operation) {
if (operation.slug === "add") return (left + right).toString();
if (operation.slug === "subtract") return (left - right).toString();
@@ -148,6 +159,9 @@
numerator = -numerator;
denominator = -denominator;
}
+ const divisor = greatestCommonDivisor(numerator, denominator);
+ numerator /= divisor;
+ denominator /= divisor;
return numerator.toString() + "/" + denominator.toString();
}
From 1428ccb99d815f6acfd6c393c1882849da0d86e5 Mon Sep 17 00:00:00 2001
From: Trent Slade <73952179+EmergentMonk@users.noreply.github.com>
Date: Sun, 6 Sep 2026 02:13:55 +0930
Subject: [PATCH 19/19] Cover reduced exact fractions
---
tests/web_smoke.js | 5 +++++
1 file changed, 5 insertions(+)
diff --git a/tests/web_smoke.js b/tests/web_smoke.js
index a7bab51..2bc3fdc 100644
--- a/tests/web_smoke.js
+++ b/tests/web_smoke.js
@@ -34,6 +34,11 @@ for (const name of ["a", "b"]) {
assert.strictEqual(core.evaluate("6", "3", "divide", "rpc").result, "2");
assert.strictEqual(core.evaluate("1", "3", "divide", "rpc").result, "1/3");
+assert.strictEqual(core.evaluate("2", "4", "divide", "rpc").result, "1/2");
+assert.strictEqual(core.evaluate("-2", "4", "divide", "rpc").result, "-1/2");
+assert.strictEqual(core.evaluate("2", "-4", "divide", "rpc").result, "-1/2");
+assert.strictEqual(core.evaluate("-2", "-4", "divide", "rpc").result, "1/2");
+assert.strictEqual(core.evaluate("6", "-4", "divide", "rpc").result, "-3/2");
assert.strictEqual(core.evaluate("-1", "-3", "divide", "rpc").result, "1/3");
assert.throws(() => core.evaluate("1", "0", "divide", "rpc"), /architecture committee/);
assert.throws(() => core.parseInt32("2147483648", "operand"), /between/);