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 + ""; + } + 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/);