From 62c734f5b262ac50ae557ed7c5801e0fcd05a31e Mon Sep 17 00:00:00 2001 From: Libby Date: Thu, 5 Nov 2026 10:49:52 +0000 Subject: [PATCH 01/76] tools/sprite-silhouette: sprite alpha silhouette to a CSS shape-outside polygon Zero-dependency Node tool: PNG alpha, threshold 0.35, outer contour of the largest 8-connected shape, Douglas-Peucker to a pixel tolerance, percent coordinates keyed by cast-art pose id. Ten tests, including every pose in the shared catalogue. Co-Authored-By: Claude Sonnet 5.5 --- tools/sprite-silhouette/README.md | 68 ++++++++++ tools/sprite-silhouette/png.mjs | 82 ++++++++++++ tools/sprite-silhouette/silhouette.mjs | 61 +++++++++ tools/sprite-silhouette/silhouette.test.mjs | 133 ++++++++++++++++++++ tools/sprite-silhouette/trace.mjs | 109 ++++++++++++++++ 5 files changed, 453 insertions(+) create mode 100644 tools/sprite-silhouette/README.md create mode 100644 tools/sprite-silhouette/png.mjs create mode 100644 tools/sprite-silhouette/silhouette.mjs create mode 100644 tools/sprite-silhouette/silhouette.test.mjs create mode 100644 tools/sprite-silhouette/trace.mjs diff --git a/tools/sprite-silhouette/README.md b/tools/sprite-silhouette/README.md new file mode 100644 index 00000000..b08e4aa4 --- /dev/null +++ b/tools/sprite-silhouette/README.md @@ -0,0 +1,68 @@ +# sprite-silhouette + +Turns a sprite's alpha channel into a simplified polygon in percent coordinates, for CSS +`shape-outside: polygon()`, so text wraps around the figure instead of its rectangle. Zero +dependencies: Node 18+ (`node:zlib` does the PNG inflate). Entry point: `silhouette.mjs`. +Live and tested; there is no packaging or release for it, it is a repo tool. + +## First call + +``` +node tools/sprite-silhouette/silhouette.mjs /opt/commons/cast-art/poses --out silhouettes.json +node --test tools/sprite-silhouette/silhouette.test.mjs +``` + +The first argument is a poses directory holding `catalogue.json` (the cast-art catalogue); the +tool reads it and every PNG it names, and writes nothing there. Output is a JSON object keyed by +pose id, `mind/label` in catalogue order (`void/neutral`, `eureka/probe`, ...): + +```json +{ "void/neutral": { "width": 577, "height": 579, + "points": [[42.98, 6.72], [49.59, 8.24], "..."], + "css": "polygon(42.98% 6.72%, 49.59% 8.24%, ...)" } } +``` + +Use the `css` string as the value of `shape-outside` on a floated image of the same aspect ratio +(percentages are of the image's box): + +```css +img.pose { float: left; shape-outside: polygon(42.98% 6.72%, 49.59% 8.24%, ...); shape-margin: 0.5em; } +``` + +An empty sprite (nothing at or above the threshold) gets `points: []` and `css: "none"`. + +From code: `silhouette(pngBuffer, { threshold, tolerance })` in `silhouette.mjs`. + +## Options + +- `--threshold 0.35`: a pixel is inside when `alpha/255 >= threshold` (0.35 is the Void mock-up's value). +- `--tolerance 1.5`: Douglas-Peucker distance in source pixels; the simplified polygon stays within + this of the traced edge in both directions (tested). 0 keeps every corner. +- `--out file`: write there instead of stdout. + +## What it does and does not trace + +Threshold, then the outer boundary of the largest 8-connected shape, by walking the pixel edges +(so vertices sit on pixel corners and the walk is exact), then simplification of that closed ring. +Holes are ignored (wrapping text goes around a figure, not through its gaps), and smaller +disconnected islands (a floating hand, a stray sparkle) are dropped: `shape-outside` takes one +polygon. The contour is a staircase of pixel edges, so below about 1 px the tolerance only +reproduces the staircase; there is no sub-pixel interpolation of the alpha ramp. + +Input: 8-bit non-interlaced PNG (RGBA, grey+alpha, palette with tRNS, or opaque RGB/grey, which is +a rectangle). Anything else is refused with its name. + +## Prior art + +- **Marching squares** (Lorensen and Cline's cubes, 2D case) interpolates along the alpha ramp for + sub-pixel contours and one pass emits every contour, holes included. Not used: the pose art is + anti-aliased over a pixel or two, a 1.5 px tolerance swallows that, and we want exactly one outer ring. +- **Potrace** (Selinger, 2003) fits optimal polygons and then Bezier curves to a bitmap. CSS + `polygon()` has no curves, so only its idea of an optimal polygon applies; this tool keeps the + simpler greedy Douglas-Peucker and measures its error directly. +- **Douglas-Peucker** (1973; Ramer 1972) is the simplifier: keep the vertex farthest from the chord + if it is beyond the tolerance, recurse. Its guarantee (every dropped vertex within the tolerance + of the result) is what the tests assert. It is not topology-preserving; on all 99 current poses at + the defaults no output self-intersects (checked when this was written, not asserted by the suite). +- **Boundary following** (Moore-neighbour / square tracing) is the contour walk, with the + 8-connected rule so diagonally touching pixels stay one shape. diff --git a/tools/sprite-silhouette/png.mjs b/tools/sprite-silhouette/png.mjs new file mode 100644 index 00000000..280e80ab --- /dev/null +++ b/tools/sprite-silhouette/png.mjs @@ -0,0 +1,82 @@ +// Minimal PNG reader: the alpha plane only. 8-bit, non-interlaced (every pose in cast-art is RGBA8). +// Anything else is refused by name, never guessed at. +import { inflateSync } from "node:zlib"; + +const SIGNATURE = Buffer.from([0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a]); +const CHANNELS = { 0: 1, 2: 3, 3: 1, 4: 2, 6: 4 }; + +/** @returns {{ width: number, height: number, alpha: Uint8Array }} alpha is row-major, 0..255. */ +export function decodeAlpha(buffer) { + if (buffer.length < 8 || !buffer.subarray(0, 8).equals(SIGNATURE)) throw new Error("not a PNG"); + let header = null; + let palette = null; + let trns = null; + const idat = []; + for (let at = 8; at + 8 <= buffer.length; ) { + const length = buffer.readUInt32BE(at); + const type = buffer.toString("latin1", at + 4, at + 8); + const body = buffer.subarray(at + 8, at + 8 + length); + if (body.length !== length) throw new Error(`truncated ${type} chunk`); + at += 12 + length; + if (type === "IHDR") { + header = { width: body.readUInt32BE(0), height: body.readUInt32BE(4), depth: body[8], color: body[9], interlace: body[12] }; + } else if (type === "PLTE") palette = body; + else if (type === "tRNS") trns = body; + else if (type === "IDAT") idat.push(body); + else if (type === "IEND") break; + } + if (!header) throw new Error("PNG has no IHDR"); + const { width, height, depth, color, interlace } = header; + if (depth !== 8) throw new Error(`unsupported PNG bit depth ${depth} (8 only)`); + if (interlace !== 0) throw new Error("unsupported PNG: interlaced"); + const channels = CHANNELS[color]; + if (!channels) throw new Error(`unsupported PNG color type ${color}`); + if (color === 3 && !palette) throw new Error("indexed PNG has no PLTE"); + + const raw = inflateSync(Buffer.concat(idat)); + const stride = width * channels; + if (raw.length !== (stride + 1) * height) throw new Error("PNG pixel data has the wrong length"); + + const alpha = new Uint8Array(width * height); + let prev = new Uint8Array(stride); + for (let y = 0; y < height; y++) { + const filter = raw[y * (stride + 1)]; + const line = Uint8Array.from(raw.subarray(y * (stride + 1) + 1, (y + 1) * (stride + 1))); + unfilter(filter, line, prev, channels); + for (let x = 0; x < width; x++) { + const p = x * channels; + let a = 255; + if (color === 6) a = line[p + 3]; + else if (color === 4) a = line[p + 1]; + else if (color === 3) a = trns && line[p] < trns.length ? trns[line[p]] : 255; + else if (trns && color === 0 && line[p] === trns.readUInt16BE(0)) a = 0; + else if (trns && color === 2 && line[p] === trns.readUInt16BE(0) && line[p + 1] === trns.readUInt16BE(2) && line[p + 2] === trns.readUInt16BE(4)) a = 0; + alpha[y * width + x] = a; + } + prev = line; + } + return { width, height, alpha }; +} + +function unfilter(filter, line, prev, bpp) { + for (let i = 0; i < line.length; i++) { + const a = i >= bpp ? line[i - bpp] : 0; + const b = prev[i]; + const c = i >= bpp ? prev[i - bpp] : 0; + let add; + switch (filter) { + case 0: add = 0; break; + case 1: add = a; break; + case 2: add = b; break; + case 3: add = (a + b) >> 1; break; + case 4: { + const p = a + b - c; + const pa = Math.abs(p - a), pb = Math.abs(p - b), pc = Math.abs(p - c); + add = pa <= pb && pa <= pc ? a : pb <= pc ? b : c; + break; + } + default: throw new Error(`bad PNG filter ${filter}`); + } + line[i] = (line[i] + add) & 255; + } +} diff --git a/tools/sprite-silhouette/silhouette.mjs b/tools/sprite-silhouette/silhouette.mjs new file mode 100644 index 00000000..92cb56d7 --- /dev/null +++ b/tools/sprite-silhouette/silhouette.mjs @@ -0,0 +1,61 @@ +#!/usr/bin/env node +// Sprite alpha silhouette -> simplified polygon in percent coordinates for CSS shape-outside: polygon(). +// First call: node tools/sprite-silhouette/silhouette.mjs /opt/commons/cast-art/poses > silhouettes.json +import { readFileSync, writeFileSync } from "node:fs"; +import { basename, join } from "node:path"; +import { pathToFileURL } from "node:url"; +import { decodeAlpha } from "./png.mjs"; +import { largestContour, mask, simplifyRing } from "./trace.mjs"; + +export const DEFAULTS = { threshold: 0.35, tolerance: 1.5 }; + +const pct = (v, total) => Math.round((v / total) * 10000) / 100; + +/** @returns {{ width: number, height: number, points: number[][], css: string }} points are [x%, y%]; css is "none" for an empty sprite. */ +export function silhouette(png, { threshold = DEFAULTS.threshold, tolerance = DEFAULTS.tolerance } = {}) { + const { width, height, alpha } = decodeAlpha(png); + const ring = largestContour(mask(alpha, threshold), width, height); + if (!ring) return { width, height, points: [], css: "none" }; + const points = simplifyRing(ring, tolerance).map(([x, y]) => [pct(x, width), pct(y, height)]); + const css = `polygon(${points.map(([x, y]) => `${x}% ${y}%`).join(", ")})`; + return { width, height, points, css }; +} + +/** Pose ids are `mind/label`, in catalogue order; the value is the path under the poses root. */ +export function poseFiles(catalogue) { + const files = {}; + for (const [mind, entry] of Object.entries(catalogue.minds)) { + for (const group of [entry.shared, entry.signatures]) { + for (const [label, path] of Object.entries(group ?? {})) files[`${mind}/${label}`] = path; + } + } + return files; +} + +export function silhouettes(root, options) { + const catalogue = JSON.parse(readFileSync(join(root, "catalogue.json"), "utf8")); + const out = {}; + for (const [id, path] of Object.entries(poseFiles(catalogue))) out[id] = silhouette(readFileSync(join(root, path)), options); + return out; +} + +function main(argv) { + const options = { ...DEFAULTS }; + let out = null, root = null; + for (let i = 0; i < argv.length; i++) { + if (argv[i] === "--threshold") options.threshold = Number(argv[++i]); + else if (argv[i] === "--tolerance") options.tolerance = Number(argv[++i]); + else if (argv[i] === "--out") out = argv[++i]; + else if (argv[i].startsWith("--")) throw new Error(`unknown option ${argv[i]}`); + else root = argv[i]; + } + if (!root || !(options.threshold > 0 && options.threshold <= 1) || !(options.tolerance >= 0)) { + console.error("usage: silhouette.mjs [--threshold 0.35] [--tolerance 1.5] [--out file.json]"); + process.exit(2); + } + const text = JSON.stringify(silhouettes(root, options), null, 1) + "\n"; + if (out) writeFileSync(out, text); + else process.stdout.write(text); +} + +if (import.meta.url === pathToFileURL(process.argv[1] ?? "").href) main(process.argv.slice(2)); diff --git a/tools/sprite-silhouette/silhouette.test.mjs b/tools/sprite-silhouette/silhouette.test.mjs new file mode 100644 index 00000000..c8b4faca --- /dev/null +++ b/tools/sprite-silhouette/silhouette.test.mjs @@ -0,0 +1,133 @@ +import assert from "node:assert/strict"; +import { existsSync } from "node:fs"; +import test from "node:test"; +import { deflateSync } from "node:zlib"; +import { decodeAlpha } from "./png.mjs"; +import { silhouette, silhouettes } from "./silhouette.mjs"; +import { largestContour, mask, maxDistanceToRing, simplifyRing } from "./trace.mjs"; + +// An RGBA8 PNG built from an alpha function, every row encoded with the given PNG filter. +function crc32(buf) { + let c, crc = ~0; + for (let i = 0; i < buf.length; i++) { + c = (crc ^ buf[i]) & 255; + for (let k = 0; k < 8; k++) c = c & 1 ? (c >>> 1) ^ 0xedb88320 : c >>> 1; + crc = (crc >>> 8) ^ c; + } + return ~crc >>> 0; +} +function chunk(type, body) { + const head = Buffer.alloc(8); + head.writeUInt32BE(body.length, 0); + head.write(type, 4, "latin1"); + const tail = Buffer.alloc(4); + tail.writeUInt32BE(crc32(Buffer.concat([head.subarray(4), body])), 0); + return Buffer.concat([head, body, tail]); +} +function png(width, height, alphaAt, { filter = 0 } = {}) { + const rows = []; + let prev = Buffer.alloc(width * 4); + for (let y = 0; y < height; y++) { + const line = Buffer.alloc(width * 4); + for (let x = 0; x < width; x++) line.set([200, 10 * (x % 20), 30, alphaAt(x, y)], x * 4); + const out = Buffer.alloc(width * 4 + 1); + out[0] = filter; + for (let i = 0; i < line.length; i++) { + const a = i >= 4 ? line[i - 4] : 0, b = prev[i], c = i >= 4 ? prev[i - 4] : 0; + const p = a + b - c, pa = Math.abs(p - a), pb = Math.abs(p - b), pc = Math.abs(p - c); + const pred = [0, a, b, (a + b) >> 1, pa <= pb && pa <= pc ? a : pb <= pc ? b : c][filter]; + out[i + 1] = (line[i] - pred) & 255; + } + rows.push(out); + prev = line; + } + const ihdr = Buffer.alloc(13); + ihdr.writeUInt32BE(width, 0); ihdr.writeUInt32BE(height, 4); ihdr[8] = 8; ihdr[9] = 6; + return Buffer.concat([ + Buffer.from([0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a]), + chunk("IHDR", ihdr), chunk("IDAT", deflateSync(Buffer.concat(rows))), chunk("IEND", Buffer.alloc(0)), + ]); +} +const disc = (cx, cy, r) => (x, y) => ((x + 0.5 - cx) ** 2 + (y + 0.5 - cy) ** 2 <= r * r ? 255 : 0); + +test("decodes alpha through every PNG row filter", () => { + const f = (x, y) => (x * 7 + y * 13) % 256; + for (const filter of [0, 1, 2, 3, 4]) { + const { width, height, alpha } = decodeAlpha(png(9, 7, f, { filter })); + assert.deepEqual([width, height], [9, 7]); + for (let y = 0; y < 7; y++) for (let x = 0; x < 9; x++) assert.equal(alpha[y * 9 + x], f(x, y), `filter ${filter} at ${x},${y}`); + } +}); + +test("refuses what it cannot read", () => { + assert.throws(() => decodeAlpha(Buffer.from("nope")), /not a PNG/); + const bad = png(2, 2, () => 255); + bad[24] = 16; // IHDR bit depth + assert.throws(() => decodeAlpha(bad), /bit depth 16/); +}); + +test("a fully transparent sprite has no shape", () => { + const s = silhouette(png(8, 8, () => 0)); + assert.deepEqual(s.points, []); + assert.equal(s.css, "none"); +}); + +test("a fully opaque sprite is its own bounding box", () => { + const s = silhouette(png(8, 4, () => 255)); + assert.deepEqual(s.points, [[0, 0], [100, 0], [100, 100], [0, 100]]); + assert.equal(s.css, "polygon(0% 0%, 100% 0%, 100% 100%, 0% 100%)"); +}); + +test("threshold is alpha/255 >= 0.35: 89 is out, 90 is in", () => { + assert.deepEqual(silhouette(png(4, 4, () => 89)).points, []); + assert.equal(silhouette(png(4, 4, () => 90)).points.length, 4); + assert.deepEqual(silhouette(png(4, 4, () => 89), { threshold: 0.3 }).points.length, 4); +}); + +test("simplified shape stays within tolerance of the traced edge, both ways, and is smaller", () => { + const { width, height, alpha } = decodeAlpha(png(120, 100, disc(60, 50, 40))); + const ring = largestContour(mask(alpha, 0.35), width, height); + for (const tolerance of [0.5, 1.5, 4]) { + const simple = simplifyRing(ring, tolerance); + assert.ok(simple.length < ring.length && simple.length >= 4); + assert.ok(maxDistanceToRing(ring, simple) <= tolerance + 1e-9, `ring to simplified at ${tolerance}`); + assert.ok(maxDistanceToRing(simple, ring) <= tolerance + 1e-9, `simplified to ring at ${tolerance}`); + } + // And the traced edge is the true edge to within a pixel: every ring vertex sits within 1px of the disc's circle. + for (const [x, y] of ring) assert.ok(Math.abs(Math.hypot(x - 60, y - 50) - 40) <= 1.0, `${x},${y}`); +}); + +test("only the outside is traced: a hole is ignored", () => { + const ring = (x, y) => (Math.max(Math.abs(x - 10), Math.abs(y - 10)) <= 8 && Math.max(Math.abs(x - 10), Math.abs(y - 10)) > 3 ? 255 : 0); + const s = silhouette(png(20, 20, ring), { tolerance: 0 }); + const xs = s.points.map((p) => p[0]), ys = s.points.map((p) => p[1]); + assert.deepEqual([Math.min(...xs), Math.max(...xs), Math.min(...ys), Math.max(...ys)], [10, 95, 10, 95]); + assert.equal(s.points.length, 4); +}); + +test("pixels that only touch diagonally are one shape; a smaller island is dropped", () => { + const dots = (x, y) => (x === y && x < 4 ? 255 : 0); + const diag = silhouette(png(8, 8, dots), { tolerance: 0 }); + const xs = diag.points.map((p) => p[0]); + assert.equal(Math.max(...xs), 50); // x 0..4 of 8 + const island = (x, y) => ((x < 5 && y < 5) || (x === 8 && y === 8) ? 255 : 0); + const s = silhouette(png(10, 10, island)); + assert.deepEqual(s.points, [[0, 0], [50, 0], [50, 50], [0, 50]]); +}); + +test("deterministic: same bytes, same polygon; coordinates are percent within the image", () => { + const bytes = png(90, 70, disc(45, 35, 30)); + const a = silhouette(bytes), b = silhouette(Buffer.from(bytes)); + assert.deepEqual(a, b); + for (const [x, y] of a.points) assert.ok(x >= 0 && x <= 100 && y >= 0 && y <= 100); + assert.match(a.css, /^polygon\((\d+(\.\d+)?% \d+(\.\d+)?%)(, \d+(\.\d+)?% \d+(\.\d+)?%){3,}\)$/); +}); + +const POSES = "/opt/commons/cast-art/poses"; +test("every pose in the shared cast-art catalogue gets a polygon", { skip: !existsSync(`${POSES}/catalogue.json`) }, () => { + const all = silhouettes(POSES); + const ids = Object.keys(all); + assert.ok(ids.length > 90 && ids.includes("void/neutral")); + for (const id of ids) assert.ok(all[id].points.length >= 4 && all[id].points.length < 400, id); + assert.deepEqual(silhouettes(POSES), all); +}); diff --git a/tools/sprite-silhouette/trace.mjs b/tools/sprite-silhouette/trace.mjs new file mode 100644 index 00000000..49594a5b --- /dev/null +++ b/tools/sprite-silhouette/trace.mjs @@ -0,0 +1,109 @@ +// Silhouette of an alpha plane: threshold -> outer contour of the largest 8-connected shape -> Douglas-Peucker. +// Coordinates are pixel-corner lattice points: x in 0..width, y in 0..height. + +/** A pixel is inside when alpha/255 >= threshold. */ +export function mask(alpha, threshold) { + const m = new Uint8Array(alpha.length); + for (let i = 0; i < alpha.length; i++) m[i] = alpha[i] / 255 >= threshold ? 1 : 0; + return m; +} + +/** Outer contour (lattice vertices at turns, clockwise on screen) of the 8-connected component with the most pixels, or null. */ +export function largestContour(m, width, height) { + const seen = new Uint8Array(m.length); + let best = null; + for (let start = 0; start < m.length; start++) { + if (!m[start] || seen[start]) continue; + // Raster order: `start` is the component's first pixel, so the pixels above and up-left of it are empty + // or belong to another component. + let count = 0; + const stack = [start]; + seen[start] = 1; + while (stack.length) { + const p = stack.pop(); + count++; + const px = p % width, py = (p / width) | 0; + for (let dy = -1; dy <= 1; dy++) for (let dx = -1; dx <= 1; dx++) { + const x = px + dx, y = py + dy; + if (x < 0 || y < 0 || x >= width || y >= height) continue; + const q = y * width + x; + if (m[q] && !seen[q]) { seen[q] = 1; stack.push(q); } + } + } + if (!best || count > best.count) best = { count, start }; + } + return best && traceOuter(m, width, height, best.start % width, (best.start / width) | 0); +} + +// Directions: 0 east, 1 south, 2 west, 3 north. The shape is kept on the walker's right, so an +// outer boundary comes out clockwise; at each corner a filled left-ahead pixel turns left (which +// joins pixels that only touch diagonally), else a filled right-ahead pixel goes straight, else right. +const DX = [1, 0, -1, 0], DY = [0, 1, 0, -1]; +const AHEAD_LEFT = [[0, -1], [0, 0], [-1, 0], [-1, -1]]; +const AHEAD_RIGHT = [[0, 0], [-1, 0], [-1, -1], [0, -1]]; + +function traceOuter(m, width, height, px, py) { + const at = (x, y) => x >= 0 && y >= 0 && x < width && y < height && m[y * width + x] === 1; + const vertices = []; + let x = px, y = py, dir = 3, first = true; // arrive at the start corner heading north, so it is counted as a turn + for (;;) { + const left = at(x + AHEAD_LEFT[dir][0], y + AHEAD_LEFT[dir][1]); + const right = at(x + AHEAD_RIGHT[dir][0], y + AHEAD_RIGHT[dir][1]); + const turn = left ? 3 : right ? 0 : 1; + const next = (dir + turn) & 3; + // Back at the start corner and about to leave east: the ring is closed. + if (!first && x === px && y === py && next === 0) break; + first = false; + if (turn !== 0) vertices.push([x, y]); + dir = next; + x += DX[dir]; y += DY[dir]; + } + return vertices; +} + +/** Douglas-Peucker on a closed ring: split at the vertex farthest from the first, simplify both halves. */ +export function simplifyRing(ring, tolerance) { + if (ring.length <= 3 || tolerance <= 0) return ring.slice(); + let far = 0, best = -1; + for (let i = 1; i < ring.length; i++) { + const d = (ring[i][0] - ring[0][0]) ** 2 + (ring[i][1] - ring[0][1]) ** 2; + if (d > best) { best = d; far = i; } + } + const a = dp(ring.slice(0, far + 1), tolerance); + const b = dp(ring.slice(far).concat([ring[0]]), tolerance); + return a.slice(0, -1).concat(b.slice(0, -1)); +} + +function dp(points, tolerance) { + const keep = new Uint8Array(points.length); + keep[0] = keep[points.length - 1] = 1; + const stack = [[0, points.length - 1]]; + while (stack.length) { + const [lo, hi] = stack.pop(); + let worst = -1, at = -1; + for (let i = lo + 1; i < hi; i++) { + const d = distanceToSegment(points[i], points[lo], points[hi]); + if (d > worst) { worst = d; at = i; } + } + if (worst > tolerance) { keep[at] = 1; stack.push([lo, at], [at, hi]); } + } + return points.filter((_, i) => keep[i]); +} + +export function distanceToSegment(p, a, b) { + const vx = b[0] - a[0], vy = b[1] - a[1]; + const len2 = vx * vx + vy * vy; + const t = len2 === 0 ? 0 : Math.max(0, Math.min(1, ((p[0] - a[0]) * vx + (p[1] - a[1]) * vy) / len2)); + return Math.hypot(p[0] - (a[0] + t * vx), p[1] - (a[1] + t * vy)); +} + +/** Largest distance from any point of ring `a`'s edges' vertices to ring `b`'s edges. */ +export function maxDistanceToRing(points, ring) { + let worst = 0; + for (const p of points) { + let d = Infinity; + for (let i = 0; i < ring.length; i++) d = Math.min(d, distanceToSegment(p, ring[i], ring[(i + 1) % ring.length])); + worst = Math.max(worst, d); + } + return worst; +} From 86faf53f8637b3b6944a950c254b0d69a8549292 Mon Sep 17 00:00:00 2001 From: Libby Date: Thu, 5 Nov 2026 10:50:08 +0000 Subject: [PATCH 02/76] tools/sprite-silhouette: README sample uses the real void/neutral size Co-Authored-By: Claude Sonnet 5.5 --- tools/sprite-silhouette/README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tools/sprite-silhouette/README.md b/tools/sprite-silhouette/README.md index b08e4aa4..a959e193 100644 --- a/tools/sprite-silhouette/README.md +++ b/tools/sprite-silhouette/README.md @@ -2,7 +2,7 @@ Turns a sprite's alpha channel into a simplified polygon in percent coordinates, for CSS `shape-outside: polygon()`, so text wraps around the figure instead of its rectangle. Zero -dependencies: Node 18+ (`node:zlib` does the PNG inflate). Entry point: `silhouette.mjs`. +dependencies: Node (tested on 24; `node:zlib` does the PNG inflate). Entry point: `silhouette.mjs`. Live and tested; there is no packaging or release for it, it is a repo tool. ## First call @@ -17,7 +17,7 @@ tool reads it and every PNG it names, and writes nothing there. Output is a JSON pose id, `mind/label` in catalogue order (`void/neutral`, `eureka/probe`, ...): ```json -{ "void/neutral": { "width": 577, "height": 579, +{ "void/neutral": { "width": 605, "height": 461, "points": [[42.98, 6.72], [49.59, 8.24], "..."], "css": "polygon(42.98% 6.72%, 49.59% 8.24%, ...)" } } ``` From cbb336f501ac781afa83bf87c6ece79390377897 Mon Sep 17 00:00:00 2001 From: Libby Date: Sun, 8 Nov 2026 22:03:27 +0000 Subject: [PATCH 03/76] cultmesh-kotlin: decode write_admitted.v0 and error.v1, content hash, WriteTracker, vector replay Strict decode of the two receipt frames (and error.v0), hello capability test, the dedupe content hash and the client settle rules, ported from cultnet-ts. write-receipt-vectors.json replays 38/38 (constants, contentHash, wire, schemaInvalid, scenarios, v0 limit_reached); dedupe and blankFields are server inputs and not run. Co-Authored-By: Claude Sonnet 5.5 --- packages/cultmesh-kotlin/README.md | 11 + packages/cultmesh-kotlin/build.ps1 | 3 + .../kotlin/org/gamecult/cultmesh/CultMesh.kt | 1 + .../org/gamecult/cultmesh/WriteReceipt.kt | 232 ++++++++++++++++++ .../gamecult/cultmesh/WriteReceiptVectors.kt | 224 +++++++++++++++++ 5 files changed, 471 insertions(+) create mode 100644 packages/cultmesh-kotlin/src/main/kotlin/org/gamecult/cultmesh/WriteReceipt.kt create mode 100644 packages/cultmesh-kotlin/src/main/kotlin/org/gamecult/cultmesh/WriteReceiptVectors.kt diff --git a/packages/cultmesh-kotlin/README.md b/packages/cultmesh-kotlin/README.md index 3da68679..ff69c346 100644 --- a/packages/cultmesh-kotlin/README.md +++ b/packages/cultmesh-kotlin/README.md @@ -474,3 +474,14 @@ powershell -ExecutionPolicy Bypass -File .\packages\cultmesh-kotlin\build.ps1 The build writes `artifacts/cultmesh-kotlin/cultmesh-kotlin.jar`. It also runs the built-in RUDP packet fixture and localhost UDP socket self-test. + +## Write Receipt + +`WriteReceipt.kt` decodes `cultnet.write_admitted.v0` and `cultnet.error.v1` (and `error.v0`) strictly against their +closed schemas (`decodeWriteAdmitted`, `decodeErrorFrame`), computes the dedupe `writeContentHash`, tests a hello with +`helloAdvertisesWriteReceipts`, and settles sent writes as admitted, refused or unconfirmed in `WriteTracker` +(pure state machine, abstract clock; the caller owns transport and timers). Kotlin has no write client yet, so nothing +sends a put and waits on this. `WriteReceiptVectors.kt` replays +`contracts/cultnet/interop/write-receipt-vectors.json` (constants, contentHash, wire, schemaInvalid, scenarios; the +server-side `dedupe` and `blankFields` sections are not run); `build.ps1` runs it after the self-test, or run +`CultMeshKt write-receipt-vectors `. diff --git a/packages/cultmesh-kotlin/build.ps1 b/packages/cultmesh-kotlin/build.ps1 index aabacbcd..42c22358 100644 --- a/packages/cultmesh-kotlin/build.ps1 +++ b/packages/cultmesh-kotlin/build.ps1 @@ -26,4 +26,7 @@ $sources = Get-ChildItem $src -Recurse -Filter *.kt | Select-Object -ExpandPrope if ($LASTEXITCODE -ne 0) { throw "kotlinc failed with exit code $LASTEXITCODE" } & $java -cp "$jarPath;$kotlinStdlib" org.gamecult.cultmesh.CultMeshKt if ($LASTEXITCODE -ne 0) { throw "cultmesh-kotlin self-test failed with exit code $LASTEXITCODE" } +$vectors = Join-Path $repoRoot "contracts\cultnet\interop\write-receipt-vectors.json" +& $java -cp "$jarPath;$kotlinStdlib" org.gamecult.cultmesh.CultMeshKt write-receipt-vectors $vectors +if ($LASTEXITCODE -ne 0) { throw "write-receipt vector replay failed with exit code $LASTEXITCODE" } Write-Host "Built $jarPath" diff --git a/packages/cultmesh-kotlin/src/main/kotlin/org/gamecult/cultmesh/CultMesh.kt b/packages/cultmesh-kotlin/src/main/kotlin/org/gamecult/cultmesh/CultMesh.kt index 7d052785..d9cdfad7 100644 --- a/packages/cultmesh-kotlin/src/main/kotlin/org/gamecult/cultmesh/CultMesh.kt +++ b/packages/cultmesh-kotlin/src/main/kotlin/org/gamecult/cultmesh/CultMesh.kt @@ -3424,6 +3424,7 @@ fun main(args: Array) { "rudp-dial-message-once" -> rudpDialSchemaMessageOnce(options) "serve" -> interopServe(options) "dial" -> interopDial(options) + "write-receipt-vectors" -> if (!replayWriteReceiptVectors(args.getOrNull(1) ?: error("usage: write-receipt-vectors "))) kotlin.system.exitProcess(1) else -> error("Unknown mode ${args[0]}") } } diff --git a/packages/cultmesh-kotlin/src/main/kotlin/org/gamecult/cultmesh/WriteReceipt.kt b/packages/cultmesh-kotlin/src/main/kotlin/org/gamecult/cultmesh/WriteReceipt.kt new file mode 100644 index 00000000..4060f08a --- /dev/null +++ b/packages/cultmesh-kotlin/src/main/kotlin/org/gamecult/cultmesh/WriteReceipt.kt @@ -0,0 +1,232 @@ +package org.gamecult.cultmesh + +// The write receipt (contracts/cultnet/interop/write-receipt.md): the two frames, their strict decode, the dedupe +// content hash, the hello capability test and the client settle rules. Mirrors packages/cultnet-ts (contracts.ts, +// write-tracker.ts) and packages/cultnet-rs (write_content_hash). The replay of write-receipt-vectors.json lives in +// WriteReceiptVectors.kt. Server rules (dedupe, limits, blank fields) are not a decoder's business and are not here. + +import java.io.IOException +import java.security.MessageDigest + +const val WRITE_ADMITTED_VERSION = "cultnet.write_admitted.v0" +const val ERROR_V1_VERSION = "cultnet.error.v1" +const val ERROR_V0_VERSION = "cultnet.error.v0" + +/** A connection is receipt-capable only when its hello lists both frames. */ +val CULTNET_WRITE_RECEIPT_MESSAGE_VERSIONS: List = listOf(WRITE_ADMITTED_VERSION, ERROR_V1_VERSION) + +const val RECEIPT_RESULT_WINDOW_MS = 5000L +const val LEGACY_RESULT_WINDOW_MS = 500L + +fun resultWindowMs(serverAdvertisesReceipts: Boolean): Long = + if (serverAdvertisesReceipts) RECEIPT_RESULT_WINDOW_MS else LEGACY_RESULT_WINDOW_MS + +/** The closed refusal-code set of error.v0; error.v1 adds `limit_reached`. */ +val CULTNET_ERROR_V0_CODES: Set = setOf( + "selection_invalid", "cursor_stale", "cursor_invalid", "reference_outside_target", "variant_unsupported", + "unowned_schema", "write_not_authorized", "unsupported_schema_version", "message_invalid", +) +val CULTNET_ERROR_V1_CODES: Set = CULTNET_ERROR_V0_CODES + "limit_reached" + +fun helloAdvertisesWriteReceipts(hello: CultNetMessage): Boolean { + val versions = hello.body["supportedMessageVersions"] as? List<*> ?: return false + return CULTNET_WRITE_RECEIPT_MESSAGE_VERSIONS.all { it in versions } +} + +data class CultNetWriteAdmitted(val messageId: String, val operation: String, val schemaId: String, val recordKey: String) { + fun toMessage() = CultNetMessage( + WRITE_ADMITTED_VERSION, + linkedMapOf("messageId" to messageId, "operation" to operation, "schemaId" to schemaId, "recordKey" to recordKey), + ) +} + +data class CultNetErrorDetails(val field: String?, val value: String?, val asOf: Long?, val current: Long?) + +/** error.v0 (`messageId` always null, `version` 0) or error.v1 (`version` 1). */ +data class CultNetErrorFrame( + val version: Int, + val error: String, + val routingHint: Map?, + val code: String?, + val details: CultNetErrorDetails?, + val messageId: String?, +) { + fun toMessage(): CultNetMessage { + val body = linkedMapOf( + "error" to error, + "routingHint" to routingHint, + "code" to code, + "details" to details?.let { + linkedMapOf("field" to it.field, "value" to it.value, "asOf" to it.asOf, "current" to it.current) + }, + ) + if (version == 1) body["messageId"] = messageId + return CultNetMessage(if (version == 1) ERROR_V1_VERSION else ERROR_V0_VERSION, body) + } +} + +/** + * Strict decode of a write_admitted.v0 message against its closed schema: exactly five keys, a non-empty string each, + * operation put|delete. + */ +fun decodeWriteAdmitted(message: CultNetMessage): CultNetWriteAdmitted { + if (message.schemaVersion != WRITE_ADMITTED_VERSION) throw IOException("not a $WRITE_ADMITTED_VERSION message") + closedKeys(message, setOf("messageId", "operation", "schemaId", "recordKey")) + val operation = nonEmpty(message, "operation") + if (operation != "put" && operation != "delete") throw IOException("operation must be put or delete") + return CultNetWriteAdmitted(nonEmpty(message, "messageId"), operation, nonEmpty(message, "schemaId"), nonEmpty(message, "recordKey")) +} + +/** Strict decode of error.v0 (no messageId, no limit_reached) or error.v1 (messageId required, string or nil, never empty). */ +fun decodeErrorFrame(message: CultNetMessage): CultNetErrorFrame { + val version = when (message.schemaVersion) { + ERROR_V1_VERSION -> 1 + ERROR_V0_VERSION -> 0 + else -> throw IOException("not an error message") + } + closedKeys(message, setOf("error", "routingHint", "code", "details") + if (version == 1) setOf("messageId") else emptySet()) + val error = nonEmpty(message, "error") + val codeValue = message.body["code"] + if (codeValue != null && (codeValue !is String || codeValue !in (if (version == 1) CULTNET_ERROR_V1_CODES else CULTNET_ERROR_V0_CODES))) { + throw IOException("unknown error code") + } + var messageId: String? = null + if (version == 1) { + val id = message.body["messageId"] + if (id != null && (id !is String || id.isEmpty())) throw IOException("messageId must be a non-empty string or nil") + messageId = id as String? + } + return CultNetErrorFrame(version, error, routingHintOf(message.body["routingHint"]), codeValue as String?, detailsOf(message.body["details"]), messageId) +} + +private fun closedKeys(message: CultNetMessage, required: Set) { + if (!message.body.keys.containsAll(required)) throw IOException("${message.schemaVersion} is missing ${required - message.body.keys}") + val unknown = message.body.keys - required + if (unknown.isNotEmpty()) throw IOException("${message.schemaVersion} carries unknown keys $unknown") +} + +private fun nonEmpty(message: CultNetMessage, key: String): String { + val value = message.body[key] + if (value !is String || value.isEmpty()) throw IOException("$key must be a non-empty string") + return value +} + +private fun routingHintOf(value: Any?): Map? { + if (value == null) return null + if (value !is Map<*, *> || value.keys != setOf("shard", "reason")) throw IOException("routingHint must be nil or {shard, reason}") + if (value["shard"] != null && value["shard"] !is Map<*, *>) throw IOException("routingHint.shard must be an object or nil") + if (value["reason"] !is String) throw IOException("routingHint.reason must be a string") + @Suppress("UNCHECKED_CAST") + return value as Map +} + +private fun detailsOf(value: Any?): CultNetErrorDetails? { + if (value == null) return null + if (value !is Map<*, *> || value.keys != setOf("field", "value", "asOf", "current")) { + throw IOException("details must be nil or {field, value, asOf, current}") + } + fun text(key: String): String? = value[key].let { if (it == null || it is String) it as String? else throw IOException("details.$key must be a string or nil") } + fun cursor(key: String): Long? = value[key].let { + if (it == null) null else if (it is Long && it >= 0) it else throw IOException("details.$key must be a non-negative integer or nil") + } + return CultNetErrorDetails(text("field"), text("value"), cursor("asOf"), cursor("current")) +} + +/** sha256 of the MessagePack array [operation, schemaId, recordKey, payload]: payload a bin for a put, nil for a delete. Lowercase hex. */ +fun writeContentHash(operation: String, schemaId: String, recordKey: String, payload: ByteArray?): String { + val writer = MessagePackWriter().array(4).string(operation).string(schemaId).string(recordKey) + if (payload == null) writer.nil() else writer.binary(payload) + return MessageDigest.getInstance("SHA-256").digest(writer.toByteArray()).joinToString("") { "%02x".format(it) } +} + +/** Why nothing settled a write. */ +enum class WriteUnconfirmedReason(val wire: String) { WINDOW_ELAPSED("window_elapsed"), SESSION_ENDED("session_ended"), LOCAL_SEND_FAILED("local_send_failed") } + +/** What a late answer is recorded as on a write that already settled unconfirmed. */ +enum class WriteRecorded(val wire: String) { ADMITTED_LATE("admitted_late"), REFUSED_LATE("refused_late") } + +/** What the caller was told. Settled once; never changed by a later frame. */ +sealed class WriteSettlement { + object Admitted : WriteSettlement() + data class Refused(val code: String?, val error: String, val response: CultNetErrorFrame) : WriteSettlement() + data class Unconfirmed(val reason: WriteUnconfirmedReason) : WriteSettlement() +} + +/** + * The client half of the receipt rules: a pure state machine with an abstract clock, one decision, how each sent write + * settles. Transport, timers and decoding belong to the caller; `frame` takes a message and decodes it strictly. + */ +class WriteTracker(private val windowMs: Long, private val onSettle: (String, WriteSettlement) -> Unit = { _, _ -> }) { + private class Entry(val sentAtMs: Long) { + var settlement: WriteSettlement? = null + var recorded: WriteRecorded? = null + } + + private val writes = linkedMapOf() + + /** Registers a write sent at [atMs]. Re-sending a known id is the same write and does not restart its window. */ + fun send(messageId: String, atMs: Long) { + tick(atMs) + writes.getOrPut(messageId) { Entry(atMs) } + } + + /** Feeds one received message. Only write_admitted, error.v1 and error.v0 answer; anything else is ignored. */ + fun frame(message: CultNetMessage, atMs: Long) { + tick(atMs) + when (message.schemaVersion) { + WRITE_ADMITTED_VERSION -> { + answer(decodeWriteAdmitted(message).messageId, WriteSettlement.Admitted, WriteRecorded.ADMITTED_LATE) + return + } + ERROR_V1_VERSION, ERROR_V0_VERSION -> { + val error = decodeErrorFrame(message) + val refusal = WriteSettlement.Refused(error.code, error.error, error) + if (error.messageId != null) { + answer(error.messageId, refusal, WriteRecorded.REFUSED_LATE) + return + } + // An error that names no write is attributable only when exactly one write is unanswered. + val unanswered = writes.filterValues { it.settlement == null } + if (unanswered.size == 1) settle(unanswered.keys.first(), unanswered.values.first(), refusal) + } + else -> return + } + } + + /** Every unanswered write whose window, counted from its own send, has run settles unconfirmed. */ + fun tick(atMs: Long) { + for ((id, entry) in writes) { + if (entry.settlement == null && atMs - entry.sentAtMs >= windowMs) settle(id, entry, WriteSettlement.Unconfirmed(WriteUnconfirmedReason.WINDOW_ELAPSED)) + } + } + + fun sessionEnd(atMs: Long) { + tick(atMs) + for ((id, entry) in writes) if (entry.settlement == null) settle(id, entry, WriteSettlement.Unconfirmed(WriteUnconfirmedReason.SESSION_ENDED)) + } + + /** This client's own send of [messageId] failed: that write settles unconfirmed; the others are untouched. */ + fun sendFailed(messageId: String, atMs: Long) { + val entry = writes[messageId] + if (entry != null && entry.settlement == null) settle(messageId, entry, WriteSettlement.Unconfirmed(WriteUnconfirmedReason.LOCAL_SEND_FAILED)) + tick(atMs) + } + + /** The settlement told to the caller, or null while unanswered or unknown. */ + fun settlement(messageId: String): WriteSettlement? = writes[messageId]?.settlement + + /** The late answer recorded on an unconfirmed write. */ + fun recorded(messageId: String): WriteRecorded? = writes[messageId]?.recorded + + private fun answer(messageId: String, settlement: WriteSettlement, late: WriteRecorded) { + val entry = writes[messageId] ?: return // names a write that is not in flight: settles nothing + val current = entry.settlement + if (current == null) settle(messageId, entry, settlement) + else if (current is WriteSettlement.Unconfirmed && entry.recorded == null) entry.recorded = late + } + + private fun settle(messageId: String, entry: Entry, settlement: WriteSettlement) { + entry.settlement = settlement + onSettle(messageId, settlement) + } +} diff --git a/packages/cultmesh-kotlin/src/main/kotlin/org/gamecult/cultmesh/WriteReceiptVectors.kt b/packages/cultmesh-kotlin/src/main/kotlin/org/gamecult/cultmesh/WriteReceiptVectors.kt new file mode 100644 index 00000000..033ef8fd --- /dev/null +++ b/packages/cultmesh-kotlin/src/main/kotlin/org/gamecult/cultmesh/WriteReceiptVectors.kt @@ -0,0 +1,224 @@ +package org.gamecult.cultmesh + +// Replays contracts/cultnet/interop/write-receipt-vectors.json against this runtime: constants, contentHash, every +// `wire` frame (decode to the vector's fields, re-encode to its bytes), every `schemaInvalid` frame (must be refused), +// and every `scenarios` event replay through WriteTracker. `dedupe` and `blankFields` are server inputs and are not +// run here. Mode: `java -cp ... org.gamecult.cultmesh.CultMeshKt write-receipt-vectors `. + +import java.io.File +import java.io.IOException + +fun replayWriteReceiptVectors(path: String): Boolean { + @Suppress("UNCHECKED_CAST") + val vectors = JsonReader(File(path).readText(Charsets.UTF_8)).readDocument() as Map + val results = linkedMapOf>() + fun check(name: String, run: () -> List) { + results[name] = try { run() } catch (e: Throwable) { listOf("threw: $e") } + } + + val constants = obj(vectors["constants"]) + check("constants") { + listOfNotNull( + "RECEIPT window".takeIf { constants["RECEIPT_RESULT_WINDOW_MS"] != RECEIPT_RESULT_WINDOW_MS }, + "LEGACY window".takeIf { constants["LEGACY_RESULT_WINDOW_MS"] != LEGACY_RESULT_WINDOW_MS }, + ) + } + + for (case in list(obj(vectors["contentHash"])["cases"])) { + val c = obj(case) + check("contentHash/${c["name"]}") { + val got = writeContentHash(c["operation"] as String, c["schemaId"] as String, c["recordKey"] as String, (c["payloadHex"] as String?)?.let(::unhex)) + if (got == c["sha256"]) emptyList() else listOf("$got != ${c["sha256"]}") + } + } + + for (case in list(vectors["wire"])) { + val c = obj(case) + check("wire/${c["name"]}") { + val bytes = unhex(c["messagePackHex"] as String) + val problems = mutableListOf() + val message = decodeReceiptFrame(parseCultNetMessage(bytes)) + if (message.toWireMap() != c["message"]) problems += "decoded ${message.toWireMap()} != ${c["message"]}" + val again = message.toBytes().joinToString("") { "%02X".format(it) } + if (again != (c["messagePackHex"] as String).uppercase()) problems += "re-encoded bytes $again != captured ${c["messagePackHex"]}" + problems + } + } + + for (case in list(vectors["schemaInvalid"])) { + val c = obj(case) + check("schemaInvalid/${c["name"]}") { + try { + decodeReceiptFrame(parseCultNetMessage(unhex(c["messagePackHex"] as String))) + listOf("accepted a frame that must be refused (${c["why"]})") + } catch (_: IOException) { + emptyList() + } + } + } + + // Not a vector: `limit_reached` is in error.v1's enum only (write-receipt.md), as the TS and Rust suites also pin. + check("error_v0_refuses_the_v1_only_limit_reached_code") { + val body = linkedMapOf("error" to "x", "routingHint" to null, "code" to "limit_reached", "details" to null) + val v1 = decodeErrorFrame(CultNetMessage(ERROR_V1_VERSION, body + ("messageId" to null))).code + try { + decodeErrorFrame(CultNetMessage(ERROR_V0_VERSION, body)) + listOf("error.v0 accepted limit_reached") + } catch (_: IOException) { + if (v1 == "limit_reached") emptyList() else listOf("error.v1 lost limit_reached") + } + } + + for (case in list(obj(vectors["scenarios"])["cases"])) { + val scenario = obj(case) + check("scenario/${scenario["name"]}") { + val problems = mutableListOf() + // The fake server states its capability the only way a real one does: in its hello. + val hello = cultNetHello( + "fake-server", "fake", + supportedMessageVersions = if (scenario["server"] == "receipt") CULTNET_WRITE_RECEIPT_MESSAGE_VERSIONS else emptyList(), + ) + val windowMs = resultWindowMs(helloAdvertisesWriteReceipts(hello)) + if (windowMs != constants[scenario["window"] as String]) problems += "window $windowMs != ${scenario["window"]}" + val told = linkedMapOf() + val tracker = WriteTracker(windowMs) { id, settlement -> + if (id in told) problems += "$id settled twice" + told[id] = settlement + } + for (event in list(scenario["events"]).map(::obj).sortedBy { it["atMs"] as Long }) { + val at = event["atMs"] as Long + when { + "send" in event -> tracker.send(event["send"] as String, at) + // A frame reaches the client as bytes: the fake server encodes it, the client decodes it. + "frame" in event -> tracker.frame(parseCultNetMessage(MessagePackWriter().value(event["frame"]).toByteArray()), at) + "tick" in event -> tracker.tick(at) + "sessionEnd" in event -> tracker.sessionEnd(at) + // transportAck: delivery is not an answer; the tracker has no such input and must not need one. + } + } + val expect = obj(scenario["expect"]) + for ((id, wantAny) in expect) { + val want = obj(wantAny) + val got = told[id] + if (got == null) { problems += "$id: never settled, wanted ${want["outcome"]}"; continue } + val (outcome, code, reason) = when (got) { + is WriteSettlement.Admitted -> Triple("admitted", null, null) + is WriteSettlement.Refused -> Triple("refused", got.code, null) + is WriteSettlement.Unconfirmed -> Triple("unconfirmed", null, got.reason.wire) + } + if (outcome != want["outcome"]) problems += "$id: told $outcome, wanted ${want["outcome"]}" + if ("code" in want && code != want["code"]) problems += "$id: code $code, wanted ${want["code"]}" + if ("reason" in want && reason != want["reason"]) problems += "$id: reason $reason, wanted ${want["reason"]}" + if (tracker.recorded(id)?.wire != want["recorded"]) problems += "$id: recorded ${tracker.recorded(id)?.wire}, wanted ${want["recorded"]}" + } + for (id in told.keys) if (id !in expect) problems += "$id: settled but the scenario expects nothing for it" + problems + } + } + + for ((name, problems) in results) { + println(if (problems.isEmpty()) "PASS $name" else "FAIL $name") + problems.forEach { println(" $it") } + } + val failed = results.values.count { it.isNotEmpty() } + println("${results.size - failed}/${results.size} passed") + return failed == 0 +} + +/** Strict decode of whichever receipt-family frame this is, returned as the message its typed form encodes; anything else is refused. */ +private fun decodeReceiptFrame(message: CultNetMessage): CultNetMessage = when (message.schemaVersion) { + WRITE_ADMITTED_VERSION -> decodeWriteAdmitted(message).toMessage() + ERROR_V1_VERSION, ERROR_V0_VERSION -> decodeErrorFrame(message).toMessage() + else -> throw IOException("not a receipt frame: ${message.schemaVersion}") +} + +@Suppress("UNCHECKED_CAST") +private fun obj(value: Any?): Map = value as Map +private fun list(value: Any?): List = value as List + +private fun unhex(text: String): ByteArray = ByteArray(text.length / 2) { text.substring(it * 2, it * 2 + 2).toInt(16).toByte() } + +/** A small JSON reader for the vector file: objects, arrays, strings, integers, true/false/null. */ +private class JsonReader(private val text: String) { + private var at = 0 + + fun readDocument(): Any? = readValue().also { skipSpace(); if (at != text.length) throw IOException("trailing JSON") } + + private fun skipSpace() { while (at < text.length && text[at].isWhitespace()) at++ } + + private fun readValue(): Any? { + skipSpace() + return when (val c = text[at]) { + '{' -> { + at++ + val map = linkedMapOf() + skipSpace() + if (text[at] == '}') { at++; return map } + do { + skipSpace() + val key = readString() + skipSpace(); expect(':') + map[key] = readValue() + skipSpace() + } while (closes('}')) + map + } + '[' -> { + at++ + val items = mutableListOf() + skipSpace() + if (text[at] == ']') { at++; return items } + do { + items += readValue() + skipSpace() + } while (closes(']')) + items + } + '"' -> readString() + 't' -> literal("true", true) + 'f' -> literal("false", false) + 'n' -> literal("null", null) + else -> { + if (c != '-' && !c.isDigit()) throw IOException("unexpected '$c' at $at") + val start = at++ + while (at < text.length && text[at].isDigit()) at++ + text.substring(start, at).toLong() + } + } + } + + /** After an element: false when [close] ends the container, true after a comma. */ + private fun closes(close: Char): Boolean { + val c = text[at++] + if (c == close) return false + if (c != ',') throw IOException("expected , or $close at ${at - 1}") + return true + } + + private fun literal(word: String, value: Any?): Any? { + if (!text.startsWith(word, at)) throw IOException("bad literal at $at") + at += word.length + return value + } + + private fun expect(c: Char) { if (text[at++] != c) throw IOException("expected $c at ${at - 1}") } + + private fun readString(): String { + expect('"') + val out = StringBuilder() + while (true) { + val c = text[at++] + when (c) { + '"' -> return out.toString() + '\\' -> when (val e = text[at++]) { + 'n' -> out.append('\n'); 't' -> out.append('\t'); 'r' -> out.append('\r') + 'b' -> out.append('\b'); 'f' -> out.append('\u000c') + 'u' -> { out.append(text.substring(at, at + 4).toInt(16).toChar()); at += 4 } + '"', '\\', '/' -> out.append(e) + else -> throw IOException("bad escape \\$e") + } + else -> out.append(c) + } + } + } +} From f6c61b1ebf2aa7723362d30cf565ab9f188952f0 Mon Sep 17 00:00:00 2001 From: Libby Date: Sun, 8 Nov 2026 22:06:08 +0000 Subject: [PATCH 04/76] cultnet-py: decode cultnet.write_admitted.v0 and cultnet.error.v1 against the write-receipt vectors Strict closed-schema decode, content hash helper, hello capability; vectors contentHash, wire and schemaInvalid replayed. Co-Authored-By: Claude Sonnet 5.5 --- packages/cultnet-py/CHANGELOG.md | 1 + .../cultnet-py/src/cultnet_py/__init__.py | 8 + .../cultnet-py/src/cultnet_py/messages.py | 90 +++++-- .../cultnet-py/tests/test_write_receipt.py | 241 ++++++++++++++++++ 4 files changed, 317 insertions(+), 23 deletions(-) create mode 100644 packages/cultnet-py/tests/test_write_receipt.py diff --git a/packages/cultnet-py/CHANGELOG.md b/packages/cultnet-py/CHANGELOG.md index b6bfa92b..5e4b0cfa 100644 --- a/packages/cultnet-py/CHANGELOG.md +++ b/packages/cultnet-py/CHANGELOG.md @@ -2,6 +2,7 @@ ## Unreleased (breaking; version 0.x, so the next minor) +- Decodes the write receipt frames of `contracts/cultnet/interop/write-receipt.md`: `parse_message` validates `cultnet.write_admitted.v0` and `cultnet.error.v1` against their closed schemas (unknown keys, empty or non-string ids, an unknown operation or code are refused; `error.v1` requires `messageId`, string or null; `limit_reached` is `error.v1` only). Added `CULTNET_ERROR_V1_CODES`, `CULTNET_WRITE_RECEIPT_MESSAGE_VERSIONS`, `write_content_hash` and `hello_advertises_write_receipts`; `hello` takes `supported_message_versions`. `tests/test_write_receipt.py` replays the vectors' `contentHash`, `wire` and `schemaInvalid` sections. Not implemented here: the client write tracker (`scenarios`) and the server rules (`dedupe`, `blankFields`); this package has no receipt server and no write client. - Added `message_invalid` to `CULTNET_ERROR_CODES` and `message_invalid_error(field, reason)`, byte-identical to the C# `ForMessageInvalid`. `cultmesh-py` refuses a malformed put or delete with it instead of the out-of-schema `malformed_document_put` and `malformed_document_delete`, which are removed. - Added `CultNetRudpSocketTransportServer` (`cultnet_py.rudp_server`): one UDP socket serving many RUDP peers, one session per remote endpoint, ported from the C# `CultNetRudpSocketTransportServer` and held to its bytes by diff --git a/packages/cultnet-py/src/cultnet_py/__init__.py b/packages/cultnet-py/src/cultnet_py/__init__.py index bcf2fcf0..e4a22779 100644 --- a/packages/cultnet-py/src/cultnet_py/__init__.py +++ b/packages/cultnet-py/src/cultnet_py/__init__.py @@ -39,6 +39,8 @@ from .cultmesh_contracts import ( from .framing import decode_frame, encode_frame, read_frame, write_frame from .messages import ( CULTNET_ERROR_CODES, + CULTNET_ERROR_V1_CODES, + CULTNET_WRITE_RECEIPT_MESSAGE_VERSIONS, CultNetMessage, CultNetWitnessArtifactBundle, compute_simulation_claim_hash, @@ -51,7 +53,9 @@ from .messages import ( error_message, message_invalid_error, unsupported_schema_version_error, + write_content_hash, hello, + hello_advertises_write_receipts, login, login_success, parse_message, @@ -176,6 +180,8 @@ __all__ = [ "verse_catalog_request", "verse_from_wire", "CULTNET_ERROR_CODES", + "CULTNET_ERROR_V1_CODES", + "CULTNET_WRITE_RECEIPT_MESSAGE_VERSIONS", "CultNetMessage", "CultNetWitnessArtifactBundle", "CultNetDatabaseSubscription", @@ -266,6 +272,8 @@ __all__ = [ "message_invalid_error", "unsupported_schema_version_error", "hello", + "hello_advertises_write_receipts", + "write_content_hash", "INTEROP_WIRE_CONTRACT", "login", "login_success", diff --git a/packages/cultnet-py/src/cultnet_py/messages.py b/packages/cultnet-py/src/cultnet_py/messages.py index 1a1f8e88..f2d89296 100644 --- a/packages/cultnet-py/src/cultnet_py/messages.py +++ b/packages/cultnet-py/src/cultnet_py/messages.py @@ -29,7 +29,11 @@ def parse_message(payload: bytes) -> CultNetMessage: body = dict(decoded) del body["schemaVersion"] if schema_version == "cultnet.error.v0": - _validate_error_body(body) + _validate_error_body(body, "cultnet.error.v0", CULTNET_ERROR_CODES) + elif schema_version == "cultnet.error.v1": + _validate_error_body(body, "cultnet.error.v1", CULTNET_ERROR_V1_CODES) + elif schema_version == "cultnet.write_admitted.v0": + _validate_write_admitted_body(body) return CultNetMessage(schema_version=schema_version, body=body) @@ -47,6 +51,13 @@ CULTNET_ERROR_CODES: tuple[str, ...] = ( "message_invalid", ) +# cultnet.error.v1 (contracts/cultnet/interop/cultnet.error.v1.schema.json): v0's codes plus limit_reached. +CULTNET_ERROR_V1_CODES: tuple[str, ...] = (*CULTNET_ERROR_CODES, "limit_reached") + +# The two frames a receipt-capable hello lists in supportedMessageVersions; a peer lists both or neither +# (contracts/cultnet/interop/write-receipt.md, "Capability: opting in"). +CULTNET_WRITE_RECEIPT_MESSAGE_VERSIONS: tuple[str, ...] = ("cultnet.write_admitted.v0", "cultnet.error.v1") + _ERROR_DETAILS_KEYS = ("field", "value", "asOf", "current") @@ -54,16 +65,48 @@ def _is_uint(value: Any) -> bool: return isinstance(value, int) and not isinstance(value, bool) and value >= 0 -def _validate_error_body(body: dict[str, Any]) -> None: - """Refuse a cultnet.error.v0 body that is not the full-key shape the schema requires.""" - expected = ("error", "routingHint", "code", "details") +def _is_name(value: Any) -> bool: + return isinstance(value, str) and value != "" + + +def _validate_write_admitted_body(body: dict[str, Any]) -> None: + """Refuse a cultnet.write_admitted.v0 body that is not exactly the five keys of its closed schema.""" + expected = ("messageId", "operation", "schemaId", "recordKey") if set(body) != set(expected): - raise ValueError(f"cultnet.error.v0 must carry exactly {list(expected)}, got {sorted(body)}") + raise ValueError(f"cultnet.write_admitted.v0 must carry exactly {list(expected)}, got {sorted(body)}") + for key in ("messageId", "schemaId", "recordKey"): + if not _is_name(body[key]): + raise ValueError(f"cultnet.write_admitted.v0 {key} must be a non-empty string") + if body["operation"] not in ("put", "delete"): + raise ValueError("cultnet.write_admitted.v0 operation must be put or delete") + + +def write_content_hash(operation: str, schema_id: str, record_key: str, payload: bytes | None) -> bytes: + """SHA-256 of the MessagePack array [operation, schemaId, recordKey, payload] (write-receipt.md, rule 5). + + payload is the raw document payload, packed as a MessagePack bin for a put and nil for a delete. + """ + return hashlib.sha256(msgpack.packb([operation, schema_id, record_key, payload], use_bin_type=True)).digest() + + +def hello_advertises_write_receipts(hello_message: CultNetMessage) -> bool: + """True when a hello lists both receipt frames: the only thing that makes a connection receipt-capable.""" + listed = hello_message.body.get("supportedMessageVersions") + return isinstance(listed, list) and all(version in listed for version in CULTNET_WRITE_RECEIPT_MESSAGE_VERSIONS) + + +def _validate_error_body(body: dict[str, Any], version: str, codes: tuple[str, ...]) -> None: + """Refuse a cultnet.error body that is not the full-key shape its closed schema requires.""" + expected = ("error", "routingHint", "code", "details") + (("messageId",) if version == "cultnet.error.v1" else ()) + if set(body) != set(expected): + raise ValueError(f"{version} must carry exactly {list(expected)}, got {sorted(body)}") if not isinstance(body["error"], str) or not body["error"]: - raise ValueError("cultnet.error.v0 error must be a non-empty string") + raise ValueError(f"{version} error must be a non-empty string") code = body["code"] - if code is not None and (not isinstance(code, str) or code not in CULTNET_ERROR_CODES): - raise ValueError(f"cultnet.error.v0 code {code!r} is not a recognized refusal code") + if code is not None and (not isinstance(code, str) or code not in codes): + raise ValueError(f"{version} code {code!r} is not a recognized refusal code") + if "messageId" in body and body["messageId"] is not None and not _is_name(body["messageId"]): + raise ValueError(f"{version} messageId must be a non-empty string or null") hint = body["routingHint"] if hint is not None: if ( @@ -72,17 +115,17 @@ def _validate_error_body(body: dict[str, Any]) -> None: or not isinstance(hint["reason"], str) or not (hint["shard"] is None or isinstance(hint["shard"], dict)) ): - raise ValueError("cultnet.error.v0 routingHint must be null or {shard, reason}") + raise ValueError(f"{version} routingHint must be null or {{shard, reason}}") details = body["details"] if details is not None: if not isinstance(details, dict) or set(details) != set(_ERROR_DETAILS_KEYS): - raise ValueError(f"cultnet.error.v0 details must be null or carry exactly {list(_ERROR_DETAILS_KEYS)}") + raise ValueError(f"{version} details must be null or carry exactly {list(_ERROR_DETAILS_KEYS)}") for key in ("field", "value"): if details[key] is not None and not isinstance(details[key], str): - raise ValueError(f"cultnet.error.v0 details.{key} must be a string or null") + raise ValueError(f"{version} details.{key} must be a string or null") for key in ("asOf", "current"): if details[key] is not None and not _is_uint(details[key]): - raise ValueError(f"cultnet.error.v0 details.{key} must be a non-negative integer or null") + raise ValueError(f"{version} details.{key} must be a non-negative integer or null") def hello( @@ -92,17 +135,18 @@ def hello( supported_schema_versions: list[str] | None = None, supported_mutation_contracts: list[dict[str, Any]] | None = None, transport_profiles: list[dict[str, Any]] | None = None, + supported_message_versions: list[str] | None = None, ) -> CultNetMessage: - return CultNetMessage( - "cultnet.hello.v0", - { - "runtimeId": runtime_id, - "supportedWireContracts": supported_wire_contracts or ["cultnet.schema.v0"], - "supportedSchemaVersions": supported_schema_versions or [], - "supportedMutationContracts": supported_mutation_contracts or [], - "transportProfiles": transport_profiles or [], - }, - ) + body: dict[str, Any] = { + "runtimeId": runtime_id, + "supportedWireContracts": supported_wire_contracts or ["cultnet.schema.v0"], + "supportedSchemaVersions": supported_schema_versions or [], + "supportedMutationContracts": supported_mutation_contracts or [], + "transportProfiles": transport_profiles or [], + } + if supported_message_versions is not None: + body["supportedMessageVersions"] = supported_message_versions + return CultNetMessage("cultnet.hello.v0", body) def login( @@ -172,7 +216,7 @@ def error_message( } if details is not None and set(details) - set(_ERROR_DETAILS_KEYS): raise ValueError(f"cultnet.error.v0 details has unknown keys {sorted(set(details) - set(_ERROR_DETAILS_KEYS))}") - _validate_error_body(body) + _validate_error_body(body, "cultnet.error.v0", CULTNET_ERROR_CODES) return CultNetMessage("cultnet.error.v0", body) diff --git a/packages/cultnet-py/tests/test_write_receipt.py b/packages/cultnet-py/tests/test_write_receipt.py new file mode 100644 index 00000000..32c5f03a --- /dev/null +++ b/packages/cultnet-py/tests/test_write_receipt.py @@ -0,0 +1,241 @@ +from __future__ import annotations + +import json +import unittest +from pathlib import Path +from typing import Any + +import msgpack + +from cultnet_py import ( + CULTNET_ERROR_CODES, + CULTNET_ERROR_V1_CODES, + CULTNET_WRITE_RECEIPT_MESSAGE_VERSIONS, + error_message, + hello, + hello_advertises_write_receipts, + parse_message, + write_content_hash, +) + +# Decoder-side conformance with contracts/cultnet/interop/write-receipt-vectors.json: `contentHash`, `wire` and +# `schemaInvalid`. `dedupe` and `blankFields` are server obligations and `scenarios` is the client write tracker; +# this runtime has neither a receipt server nor a write client, so it replays none of them. +_INTEROP = Path(__file__).resolve().parents[3] / "contracts/cultnet/interop" +_VECTORS = json.loads((_INTEROP / "write-receipt-vectors.json").read_text(encoding="utf-8")) +_ERROR_V1_SCHEMA = json.loads((_INTEROP / "cultnet.error.v1.schema.json").read_text(encoding="utf-8")) + + +def _pack(wire: dict[str, Any]) -> bytes: + return msgpack.packb(wire, use_bin_type=True) + + +def _admitted(**override: Any) -> dict[str, Any]: + return { + "schemaVersion": "cultnet.write_admitted.v0", + "messageId": "put-1", + "operation": "put", + "schemaId": "note.v0", + "recordKey": "note:1", + **override, + } + + +def _refused(**override: Any) -> dict[str, Any]: + return { + "schemaVersion": "cultnet.error.v1", + "error": "write_not_authorized: no", + "routingHint": None, + "code": "write_not_authorized", + "details": None, + "messageId": "put-1", + **override, + } + + +class WriteReceiptVectorTests(unittest.TestCase): + def test_vector_sections_are_the_ones_this_file_accounts_for(self) -> None: + self.assertEqual( + set(_VECTORS) - {"provenance", "specification"}, + {"constants", "contentHash", "dedupe", "wire", "blankFields", "schemaInvalid", "scenarios"}, + ) + self.assertEqual(len(_VECTORS["contentHash"]["cases"]), 4) + self.assertEqual(len(_VECTORS["wire"]), 7) + self.assertEqual(len(_VECTORS["schemaInvalid"]), 7) + + def test_content_hash_matches_every_vector(self) -> None: + for case in _VECTORS["contentHash"]["cases"]: + payload = None if case["payloadHex"] is None else bytes.fromhex(case["payloadHex"]) + digest = write_content_hash(case["operation"], case["schemaId"], case["recordKey"], payload) + self.assertEqual(digest.hex(), case["sha256"], case["name"]) + + def test_wire_vectors_decode_to_their_fields_in_order_and_reencode_byte_identical(self) -> None: + for vector in _VECTORS["wire"]: + raw = bytes.fromhex(vector["messagePackHex"]) + message = parse_message(raw) + self.assertEqual(message.to_wire(), vector["message"], vector["name"]) + self.assertEqual(list(message.to_wire()), list(vector["message"]), vector["name"]) + self.assertEqual(message.to_bytes(), raw, vector["name"]) + + def test_schema_invalid_vectors_are_refused(self) -> None: + for vector in _VECTORS["schemaInvalid"]: + with self.assertRaises(ValueError, msg=vector["name"]): + parse_message(bytes.fromhex(vector["messagePackHex"])) + + def test_a_payload_that_is_not_bytes_changes_the_hash(self) -> None: + # The payload is a bin, not an str and not a re-parsed document. + as_bin = write_content_hash("put", "note.v0", "note:1", b"\x81\xa2id\xa11") + self.assertNotEqual(as_bin, write_content_hash("put", "note.v0", "note:1", None)) + self.assertEqual(len(as_bin), 32) + + +class ErrorV1Tests(unittest.TestCase): + def test_code_set_equals_the_schema_enum_and_v0_is_unchanged(self) -> None: + schema_codes = [c for c in _ERROR_V1_SCHEMA["properties"]["code"]["enum"] if c is not None] + self.assertEqual(sorted(CULTNET_ERROR_V1_CODES), sorted(schema_codes)) + self.assertNotIn("limit_reached", CULTNET_ERROR_CODES) + self.assertEqual(CULTNET_ERROR_V1_CODES[-1], "limit_reached") + + def test_every_v1_code_decodes_and_an_unknown_one_does_not(self) -> None: + for code in CULTNET_ERROR_V1_CODES: + self.assertEqual(parse_message(_pack(_refused(code=code))).body["code"], code) + parse_message(_pack(_refused(code=None))) + with self.assertRaises(ValueError): + parse_message(_pack(_refused(code="invented"))) + with self.assertRaises(ValueError): + parse_message(_pack(_refused(code=7))) + + def test_limit_reached_is_v1_only(self) -> None: + v0 = {k: v for k, v in _refused(code="limit_reached", schemaVersion="cultnet.error.v0").items() if k != "messageId"} + with self.assertRaises(ValueError): + parse_message(_pack(v0)) + with self.assertRaises(ValueError): + error_message("x", code="limit_reached") + + def test_message_id_is_required_and_is_a_nonempty_string_or_null(self) -> None: + self.assertEqual(parse_message(_pack(_refused(messageId="w-é 1"))).body["messageId"], "w-é 1") + self.assertIsNone(parse_message(_pack(_refused(messageId=None))).body["messageId"]) + omitted = {k: v for k, v in _refused().items() if k != "messageId"} + with self.assertRaises(ValueError): + parse_message(_pack(omitted)) + for bad in ("", 5, b"put-1", ["put-1"], True): + with self.assertRaises(ValueError, msg=repr(bad)): + parse_message(_pack(_refused(messageId=bad))) + + def test_v0_stays_closed_to_message_id(self) -> None: + v0 = _refused(schemaVersion="cultnet.error.v0", code="message_invalid") + with self.assertRaises(ValueError): + parse_message(_pack(v0)) + del v0["messageId"] + self.assertEqual(parse_message(_pack(v0)).schema_version, "cultnet.error.v0") + + def test_v1_is_closed_to_unknown_keys_and_keeps_the_shape_rules_of_v0(self) -> None: + for bad in ( + _refused(extra=1), + _refused(error=""), + _refused(error=3), + _refused(routingHint={"shard": None}), + _refused(routingHint={"shard": 1, "reason": "r"}), + _refused(routingHint={"shard": None, "reason": 1}), + _refused(details={"field": None, "value": None, "asOf": None}), + _refused(details={"field": 1, "value": None, "asOf": None, "current": None}), + _refused(details={"field": None, "value": None, "asOf": -1, "current": None}), + _refused(details={"field": None, "value": None, "asOf": None, "current": True}), + ): + with self.assertRaises(ValueError, msg=str(bad)): + parse_message(_pack(bad)) + for good in ( + _refused(routingHint={"shard": None, "reason": "r"}), + _refused(details={"field": "messageId", "value": "x", "asOf": 1, "current": 2}), + ): + parse_message(_pack(good)) + + def test_the_refusal_reason_names_its_version(self) -> None: + with self.assertRaisesRegex(ValueError, "cultnet.error.v1 code"): + parse_message(_pack(_refused(code="invented"))) + with self.assertRaisesRegex(ValueError, r"routingHint must be null or \{shard, reason\}"): + parse_message(_pack(_refused(routingHint={"shard": None}))) + + +class RefusalTextTests(unittest.TestCase): + """A refusal names its frame version and the rule it broke; a caller reads this text in a log.""" + + def assertRefused(self, wire: dict[str, Any], pattern: str) -> None: + with self.assertRaisesRegex(ValueError, pattern): + parse_message(_pack(wire)) + + def test_error_frames_name_their_own_version(self) -> None: + v0 = {k: v for k, v in _refused(schemaVersion="cultnet.error.v0").items() if k != "messageId"} + self.assertRefused({**v0, "code": "nope"}, r"^cultnet\.error\.v0 code") + self.assertRefused({**v0, "extra": 1}, r"^cultnet\.error\.v0 must carry exactly") + self.assertRefused(_refused(extra=1), r"^cultnet\.error\.v1 must carry exactly") + self.assertRefused(_refused(error=""), r"^cultnet\.error\.v1 error must be") + self.assertRefused(_refused(messageId=""), r"^cultnet\.error\.v1 messageId must be") + self.assertRefused(_refused(details={"field": None}), r"^cultnet\.error\.v1 details must be null or carry exactly") + self.assertRefused( + _refused(details={"field": 1, "value": None, "asOf": None, "current": None}), r"^cultnet\.error\.v1 details\.field must be" + ) + self.assertRefused( + _refused(details={"field": None, "value": None, "asOf": -1, "current": None}), r"^cultnet\.error\.v1 details\.asOf must be" + ) + with self.assertRaisesRegex(ValueError, r"^cultnet\.error\.v0 code"): + error_message("x", code="bogus") + + def test_write_admitted_names_the_broken_rule(self) -> None: + self.assertRefused(_admitted(extra=1), r"^cultnet\.write_admitted\.v0 must carry exactly") + self.assertRefused(_admitted(recordKey=""), r"^cultnet\.write_admitted\.v0 recordKey must be a non-empty string") + self.assertRefused(_admitted(operation="upsert"), r"^cultnet\.write_admitted\.v0 operation must be put or delete$") + + +class WriteAdmittedTests(unittest.TestCase): + def test_both_operations_decode(self) -> None: + for operation in ("put", "delete"): + self.assertEqual(parse_message(_pack(_admitted(operation=operation))).body["operation"], operation) + + def test_every_key_is_required_and_no_other_is_allowed(self) -> None: + for key in ("messageId", "operation", "schemaId", "recordKey"): + with self.assertRaises(ValueError, msg=key): + parse_message(_pack({k: v for k, v in _admitted().items() if k != key})) + for extra in ("error", "code", "details"): + with self.assertRaises(ValueError, msg=extra): + parse_message(_pack(_admitted(**{extra: "x"}))) + + def test_each_string_is_nonempty_and_a_string(self) -> None: + for key in ("messageId", "schemaId", "recordKey"): + for bad in ("", None, 3, b"x"): + with self.assertRaises(ValueError, msg=f"{key}={bad!r}"): + parse_message(_pack(_admitted(**{key: bad}))) + + def test_operation_is_put_or_delete(self) -> None: + for bad in ("upsert", "PUT", "", None, 1): + with self.assertRaises(ValueError, msg=repr(bad)): + parse_message(_pack(_admitted(operation=bad))) + + def test_message_id_is_echoed_byte_for_byte(self) -> None: + self.assertEqual(parse_message(_pack(_admitted(messageId=" w é 1 "))).body["messageId"], " w é 1 ") + + +class HelloCapabilityTests(unittest.TestCase): + def test_capability_needs_both_frames(self) -> None: + both = list(CULTNET_WRITE_RECEIPT_MESSAGE_VERSIONS) + self.assertEqual(both, ["cultnet.write_admitted.v0", "cultnet.error.v1"]) + self.assertTrue(hello_advertises_write_receipts(hello(runtime_id="r", supported_message_versions=both))) + self.assertTrue(hello_advertises_write_receipts(hello(runtime_id="r", supported_message_versions=[*both, "x"]))) + self.assertFalse(hello_advertises_write_receipts(hello(runtime_id="r", supported_message_versions=both[:1]))) + self.assertFalse(hello_advertises_write_receipts(hello(runtime_id="r", supported_message_versions=both[1:]))) + self.assertFalse(hello_advertises_write_receipts(hello(runtime_id="r", supported_message_versions=[]))) + self.assertFalse(hello_advertises_write_receipts(hello(runtime_id="r"))) + + def test_a_non_list_listing_is_not_a_capability(self) -> None: + wire = hello(runtime_id="r").to_wire() + wire["supportedMessageVersions"] = "cultnet.write_admitted.v0 cultnet.error.v1" + self.assertFalse(hello_advertises_write_receipts(parse_message(_pack(wire)))) + + def test_hello_writes_the_listing_only_when_given_and_round_trips(self) -> None: + self.assertNotIn("supportedMessageVersions", hello(runtime_id="r").body) + listed = hello(runtime_id="r", supported_message_versions=list(CULTNET_WRITE_RECEIPT_MESSAGE_VERSIONS)) + self.assertEqual(parse_message(listed.to_bytes()), listed) + + +if __name__ == "__main__": + unittest.main() From 4f6ee0db835aa1e6b7a5ce6d7e175ea463e75792 Mon Sep 17 00:00:00 2001 From: Libby Date: Sun, 8 Nov 2026 21:22:55 +0000 Subject: [PATCH 05/76] Rust RUDP server: pin the refusal of blank schemaId/recordKey on receipt sessions heimdall-low-rust-keys-untested. Co-Authored-By: Claude Sonnet 5.5 --- .../cultmesh-rs/tests/rudp_document_server.rs | 69 +++++++++++++++++++ 1 file changed, 69 insertions(+) diff --git a/packages/cultmesh-rs/tests/rudp_document_server.rs b/packages/cultmesh-rs/tests/rudp_document_server.rs index be667e72..e37ce9d6 100644 --- a/packages/cultmesh-rs/tests/rudp_document_server.rs +++ b/packages/cultmesh-rs/tests/rudp_document_server.rs @@ -2983,6 +2983,75 @@ fn only_reliable_non_sequenced_writes_are_accepted_on_a_receipt_session() -> Res Ok(()) } +/// `bytes` with the MessagePack fixstr `from` replaced by the fixstr `to`, which may be blank. +fn swap_fixstr(bytes: &[u8], from: &str, to: &str) -> Vec { + assert!(from.len() < 32 && to.len() < 32); + let mut needle = vec![0xa0 | from.len() as u8]; + needle.extend_from_slice(from.as_bytes()); + let at = bytes.windows(needle.len()).position(|window| window == needle).expect("marker is in the frame"); + let mut out = bytes[..at].to_vec(); + out.push(0xa0 | to.len() as u8); + out.extend_from_slice(to.as_bytes()); + out.extend_from_slice(&bytes[at + needle.len()..]); + out +} + +/// A `schemaId` or `recordKey` that is present but empty or only whitespace names nothing: the write is refused +/// `message_invalid` like a frame that does not read (field "message", the write's id), never reaches the store, +/// and the session goes on (write-receipt.md, rule 5; vector `error_v1_empty_schema_id_is_unreadable_and_names_the_write`). +#[test] +fn a_write_whose_schema_id_or_record_key_names_nothing_is_refused_and_never_handed_to_the_store() -> Result<()> { + let blanks = ["", " ", "\t \n"]; + let mut connection_id = 300; + for blank in blanks { + for blank_field in ["schemaId", "recordKey"] { + for operation in ["put", "delete"] { + connection_id += 1; + let (schema_id, record_key) = match blank_field { + "schemaId" => (blank, "note:1"), + _ => ("test.raw.v1", blank), + }; + // The encoder refuses a blank field, so encode a marker and swap the blank into the bytes. + let bad = if operation == "put" { + let mut record = document("zz-key-zz", vec![1, 2, 3]); + record.schema_id = "zz-schema-zz".into(); + encode_cultnet_message_to_vec( + &CultNetMessage::DocumentPutRaw { message_id: "bad-1".into(), document: record }, + CultNetWireContract::CultNetSchemaV0, + )? + } else { + encode_cultnet_message_to_vec( + &CultNetMessage::DocumentDelete { + message_id: "bad-1".into(), + schema_id: "zz-schema-zz".into(), + record_key: "zz-key-zz".into(), + }, + CultNetWireContract::CultNetSchemaV0, + )? + }; + let bad = swap_fixstr(&bad, "zz-schema-zz", schema_id); + let bad = swap_fixstr(&bad, "zz-key-zz", record_key); + let label = format!("{operation} with {blank_field} {blank:?}"); + let sink = Sink::default(); + let mut server = server(Default::default(), Clock::new(70_000), sink.clone(), Source::default())?; + let mut peer = client(server.local_addr()?, connection_id)?; + connect(&mut server, &mut [&mut peer])?; + send_reliable(&mut peer, &receipt_hello())?; + peer.send_reliable("schema", bad)?; + send_reliable(&mut peer, &put("good-1", "note:2", vec![1]))?; + let answers = answers_after(&mut server, &mut peer, 2)?; + assert_eq!(refusal_of(&answers[0]), (Some(CultNetErrorCode::MessageInvalid), Some("bad-1".into())), "{label}"); + assert_eq!(error_text_and_field(&answers[0]).1, Some("message".into()), "{label}"); + assert_eq!(answers[1], admitted("good-1", "note:2"), "{label}: the session goes on"); + let state = sink.0.lock().unwrap(); + assert_eq!(state.authorized, vec!["good-1".to_string()], "{label}: the store was never asked about the blank write"); + assert_eq!(state.receipts.len(), 1, "{label}: only the good write was applied"); + } + } + } + Ok(()) +} + /// A refused write is traffic: the peer that sends it is not idle, so its session outlives the idle timeout measured /// from the refusal and not from the packet before it. #[test] From bd2aff8ed0cdd76d6acc673dff5a0fea8fc10d72 Mon Sep 17 00:00:00 2001 From: Libby Date: Sun, 8 Nov 2026 21:23:07 +0000 Subject: [PATCH 06/76] Rust RUDP server: decide the sequenced-channel refusal per frame A sequenced packet's arrival can release held ordered frames in the same delivery; refuse only the packet's own frame. heimdall-low-refuse-sequenced-per-frame. Co-Authored-By: Claude Sonnet 5.5 --- .../cultmesh-rs/src/rudp_document_server.rs | 4 +- .../cultmesh-rs/tests/rudp_document_server.rs | 45 +++++++++++++++++++ 2 files changed, 48 insertions(+), 1 deletion(-) diff --git a/packages/cultmesh-rs/src/rudp_document_server.rs b/packages/cultmesh-rs/src/rudp_document_server.rs index f97b2578..12fdf3e6 100644 --- a/packages/cultmesh-rs/src/rudp_document_server.rs +++ b/packages/cultmesh-rs/src/rudp_document_server.rs @@ -825,7 +825,9 @@ where if frame.channel_id != "schema" { continue; } - if refuse_sequenced { + // `delivered` also holds the ordered frames this packet's arrival released from the hold; those were + // not sent on the sequenced channel. They sit above the packet's own sequence, its own frame at or below. + if refuse_sequenced && frame.sequence <= packet.sequence { if let CultMeshRudpPollOutcome::ApplicationRejected(rejection) = self.refuse_unreliable_write(key, &frame.payload)? { diff --git a/packages/cultmesh-rs/tests/rudp_document_server.rs b/packages/cultmesh-rs/tests/rudp_document_server.rs index e37ce9d6..b6663396 100644 --- a/packages/cultmesh-rs/tests/rudp_document_server.rs +++ b/packages/cultmesh-rs/tests/rudp_document_server.rs @@ -3052,6 +3052,51 @@ fn a_write_whose_schema_id_or_record_key_names_nothing_is_refused_and_never_hand Ok(()) } +/// The sequenced-channel refusal belongs to the frame that arrived sequenced. A reliable-sequenced packet that +/// fills the gap an earlier ordered packet was held behind delivers both frames in one go: the sequenced write is +/// refused, the ordered write it released is admitted. +#[test] +fn a_sequenced_packet_releasing_a_held_ordered_write_refuses_only_its_own() -> Result<()> { + let sink = Sink::default(); + let mut server = server(Default::default(), Clock::new(70_000), sink.clone(), Source::default())?; + let mut peer = RawPeer::connect(&mut server, 250)?; + peer.send(&receipt_hello())?; + server.poll_once()?; + let mut packet_for = |peer: &mut RawPeer, id: &str, ordered: bool, sequenced: bool| -> Result> { + let payload = encode_cultnet_message_to_vec(&put(id, &format!("note:{id}"), vec![1]), CultNetWireContract::CultNetSchemaV0)?; + peer.session.send_many( + "schema", + payload, + cultnet_rs::CultNetRudpSendOptions { reliable: true, ordered, sequenced, now_ms: 3, reliable_expire_after_ms: None }, + Some(1200), + ) + }; + let sequenced = packet_for(&mut peer, "s1", false, true)?; + let ordered = packet_for(&mut peer, "o1", true, false)?; + // The ordered write arrives first, one sequence ahead of the gap, and is held. + for packet in &ordered { + peer.send_packet(packet)?; + } + server.poll_once()?; + assert_eq!(sink.0.lock().unwrap().receipts.len(), 0, "the ordered write waits behind the gap"); + for packet in &sequenced { + peer.send_packet(packet)?; + } + server.poll_once()?; + let mut answers = Vec::new(); + for packet in peer.drain() { + for frame in peer.session.receive(&packet, 4)?.delivered { + answers.push(decode_cultnet_message_from_slice(&frame.payload, CultNetWireContract::CultNetSchemaV0)?); + } + } + let refusals: Vec<_> = answers.iter().filter(|a| matches!(a, CultNetMessage::ErrorV1 { .. })).collect(); + assert_eq!(refusals.len(), 1, "only the sequenced write is refused: {answers:?}"); + assert_eq!(refusal_of(refusals[0]), (Some(CultNetErrorCode::MessageInvalid), Some("s1".into()))); + assert!(answers.contains(&admitted("o1", "note:o1")), "the released ordered write is admitted: {answers:?}"); + assert_eq!(sink.0.lock().unwrap().receipts.len(), 1, "and applied"); + Ok(()) +} + /// A refused write is traffic: the peer that sends it is not idle, so its session outlives the idle timeout measured /// from the refusal and not from the packet before it. #[test] From 4e89f07bff194851d518c7cb921b27a8f3fe375a Mon Sep 17 00:00:00 2001 From: Libby Date: Sun, 8 Nov 2026 22:13:24 +0000 Subject: [PATCH 07/76] Rust RUDP server: answer a superseded sequenced write; order frame and packet by serial arithmetic Superseded packet: a reliable-sequenced packet that arrives after a newer one delivers no frame, yet is acknowledged and never resent, so its write went unanswered. The test (red first: only "new" was refused, "old" got nothing) showed the drop happens in the session below frame decoding, but an unfragmented packet carries its whole frame, so the server can read the id from the packet payload. Chosen: refuse it from the packet payload. A superseded fragment of a larger write has no readable id in the packet, so write-receipt.md now says that write may go unanswered and the client's unconfirmed outcome covers it. Ordering: frame.sequence <= packet.sequence is replaced by sequence_at_or_before, serial arithmetic modulo 2^32. Deviation from the brief: cultnet-rs' at_or_before carries a 4096-sequence receive window made for duplicate detection; a frame's first fragment may lie further back than that, so reusing it would leave such a sequenced write unrefused and applied. The new test pins that, and a hand mutant with the window fails it. A wrapped sequence cannot reach this compare through the session today (it is judged a duplicate), so the unit test pins the helper, not an end-to-end wrap. Co-Authored-By: Claude Sonnet 5.5 --- contracts/cultnet/interop/write-receipt.md | 2 +- .../cultmesh-rs/src/rudp_document_server.rs | 33 +++++- .../cultmesh-rs/tests/rudp_document_server.rs | 102 ++++++++++++++++++ 3 files changed, 135 insertions(+), 2 deletions(-) diff --git a/contracts/cultnet/interop/write-receipt.md b/contracts/cultnet/interop/write-receipt.md index d3dc2614..b74411ea 100644 --- a/contracts/cultnet/interop/write-receipt.md +++ b/contracts/cultnet/interop/write-receipt.md @@ -38,7 +38,7 @@ A connection is receipt-capable when its `cultnet.hello.v0` (sent before its fir - **Limit refusals are remembered.** A `limit_reached` refusal is remembered like any other: a resend of the same id and content hash replays `limit_reached`. No slot is freed mid-session, so an id refused at a bound never becomes admitted later in that session (a server at the cap keeps the refused digest pair in a bounded refusal set of at most 1024 per session, outside the entry budget; past it, a further new write is refused `limit_reached` without being remembered, and the session refuses every id it has not seen for the rest of its life, even if a server-wide budget has since freed, so a refusal that was not remembered is never followed by an admission of the same id). The client mints a new id on a new session. - **Required fields.** A put or delete whose document or `schemaId` is null or missing is refused `message_invalid` with `details.field` `"document"` or `"schemaId"` (the first missing in that order) and fixed text; the write is not applied. A put or delete that is present but names nothing is not "missing": the write is refused `message_invalid` exactly as a frame that does not read, with `details.field` `"message"`, the fixed unreadable text and the write's id (vector `error_v1_empty_schema_id_is_unreadable_and_names_the_write`), and is never applied; a server must not hand it to its store. Names nothing means, on a put or delete, a `schemaId` or `recordKey` that is blank; on a put's document, also a `storedAt` that is blank, a `payload` of zero bytes (a MessagePack value is at least one byte, so an empty payload is no document), a `sourceRuntimeId`, `sourceAgentId` or `sourceRole` that is present and blank, and a `tags` entry that is blank. **Blank** means empty or made only of Unicode White_Space code points (the PropList property) and nothing else, in every runtime, whatever its platform's own whitespace test says. The set is exactly U+0009 to U+000D, U+0020, U+0085, U+00A0, U+1680, U+2000 to U+200A, U+2028, U+2029, U+202F, U+205F and U+3000. U+200B, U+200C, U+200D, U+2060, U+FEFF, U+180E and U+001C to U+001F are not White_Space (some platforms' whitespace tests accept U+001C to U+001F or U+180E, for example Java's `Character.isWhitespace` and older .NET, so a runtime must check the list, not its platform test): a string of them names something. The vectors list both sets (`whiteSpaceCodePoints`, `notWhiteSpaceCodePoints`) and a runtime checks every code point in every covered field. Absent or nil source ids and tags, and an empty `tags` list, are valid. The rule holds on every connection, receipt-capable or not (a legacy connection is refused with the `error.v0` of the same code, details and text); the vectors' `blankFields` section lists each case. - **Fixed texts.** The `error` string of these refusals is fixed, so vectors can compare it: `message_invalid: messageId exceeds 256 bytes`, `message_invalid: document is required`, `message_invalid: schemaId is required`, `message_invalid: the message could not be read` (a frame that does not decode, or a write that names nothing; the text is the same for both and never carries a decoder's own wording), `message_invalid: receipt sessions require the reliable channel`, `limit_reached: the session's remembered-write limit is full`. - - **Reliable channel only.** A write is accepted on a receipt session only on a channel that is reliable and not sequenced (reliable ordered and reliable unordered both qualify; a sequenced channel drops superseded packets, so a write on it can vanish without trace). A write arriving on any other channel is refused `message_invalid` with `details.field: "channel"` (the transport profile's word for it) and fixed text. Receipts and refusals travel the reliable channel; a write that could be lost without trace cannot be answered once. + - **Reliable channel only.** A write is accepted on a receipt session only on a channel that is reliable and not sequenced (reliable ordered and reliable unordered both qualify; a sequenced channel drops superseded packets, so a write on it can vanish without trace). A write arriving on any other channel is refused `message_invalid` with `details.field: "channel"` (the transport profile's word for it) and fixed text. Receipts and refusals travel the reliable channel; a write that could be lost without trace cannot be answered once. The server refuses a sequenced write whenever it can read the write's id: from the delivered frame, and from a superseded packet that carries its whole frame. A write sent as several fragments whose frame is superseded is dropped whole and may go unanswered; the client's unconfirmed outcome covers it. - Past the session (or a server's longer retention, if it keeps one) a resend is a new write. 6. **Forwarded writes.** A server that forwards a non-primary write answers the original peer only with what the primary answered: the primary's receipt becomes `write_admitted` with the original id, its refusal becomes `error.v1` with the original id. If the forward gets no answer the server relays nothing: no receipt, no error, no synthesized refusal. The client's own rule below then reports the write as unconfirmed. A forwarder never turns an absent or ambiguous primary answer into an admission. 7. **Looks alike.** An admitted and a refused write differ in `schemaVersion`; a receipt has no `error`, an `error.v1` has no admission. A transport ack is neither. diff --git a/packages/cultmesh-rs/src/rudp_document_server.rs b/packages/cultmesh-rs/src/rudp_document_server.rs index 12fdf3e6..a0ab51ac 100644 --- a/packages/cultmesh-rs/src/rudp_document_server.rs +++ b/packages/cultmesh-rs/src/rudp_document_server.rs @@ -821,13 +821,26 @@ where } let mut kept_rejection = None; + // A superseded packet delivers no frame, yet it was acknowledged and will not be resent, so its write would + // go unanswered. A packet that carries its whole frame is answered from its own payload. A superseded + // fragment cannot be: the write's id may lie in a fragment this packet does not hold. + if refuse_sequenced + && packet.fragment_count == 0 + && !result.delivered.iter().any(|frame| frame.channel_id == "schema" && frame.sequence == packet.sequence) + { + if let CultMeshRudpPollOutcome::ApplicationRejected(rejection) = + self.refuse_unreliable_write(key, &packet.payload)? + { + kept_rejection = Some(rejection); + } + } for frame in result.delivered { if frame.channel_id != "schema" { continue; } // `delivered` also holds the ordered frames this packet's arrival released from the hold; those were // not sent on the sequenced channel. They sit above the packet's own sequence, its own frame at or below. - if refuse_sequenced && frame.sequence <= packet.sequence { + if refuse_sequenced && sequence_at_or_before(frame.sequence, packet.sequence) { if let CultMeshRudpPollOutcome::ApplicationRejected(rejection) = self.refuse_unreliable_write(key, &frame.payload)? { @@ -1816,6 +1829,13 @@ where #[cfg(test)] mod send_failure_tests; +/// Whether `sequence` is at or before `mark` in serial order (RFC 1982): the 32-bit space is compared modulo 2^32, +/// so a frame that began before the wrap and a packet after it still order correctly. The receive window of +/// `cultnet_rs`' duplicate test does not belong here: a frame's first fragment may lie any distance back. +fn sequence_at_or_before(sequence: u32, mark: u32) -> bool { + mark.wrapping_sub(sequence) < 1 << 31 +} + fn validate_options(options: &CultMeshRudpDocumentServerOptions) -> Result<()> { if options.max_sessions == 0 { return Err(anyhow!("max_sessions must be greater than zero")); @@ -1931,4 +1951,15 @@ mod tests { .unwrap(); assert_eq!(hex(&bytes), MESSAGE_INVALID_HEX); } + + #[test] + fn sequence_order_holds_across_the_wrap_and_beyond_the_receive_window() { + assert!(sequence_at_or_before(7, 7)); + assert!(sequence_at_or_before(100, 5_200), "further back than the receive window"); + assert!(!sequence_at_or_before(5_200, 100)); + assert!(sequence_at_or_before(u32::MAX - 1, 1), "begun before the wrap, ended after it"); + assert!(!sequence_at_or_before(1, u32::MAX - 1), "after the wrap is later than before it"); + assert!(sequence_at_or_before(u32::MAX, 0), "the wrap itself"); + assert!(!sequence_at_or_before(0, u32::MAX)); + } } diff --git a/packages/cultmesh-rs/tests/rudp_document_server.rs b/packages/cultmesh-rs/tests/rudp_document_server.rs index b6663396..d9b42abb 100644 --- a/packages/cultmesh-rs/tests/rudp_document_server.rs +++ b/packages/cultmesh-rs/tests/rudp_document_server.rs @@ -3097,6 +3097,108 @@ fn a_sequenced_packet_releasing_a_held_ordered_write_refuses_only_its_own() -> R Ok(()) } +/// The refusal of a write on a sequenced channel does not depend on the packet being the newest. A reliable-sequenced +/// packet that arrives after a newer one on its channel is superseded: the session delivers no frame for it, yet +/// the packet was acknowledged and will not be resent, so the server answers it from the packet's own payload. +#[test] +fn a_superseded_sequenced_write_is_still_refused() -> Result<()> { + let sink = Sink::default(); + let mut server = server(Default::default(), Clock::new(70_000), sink.clone(), Source::default())?; + let mut peer = RawPeer::connect(&mut server, 253)?; + peer.send(&receipt_hello())?; + server.poll_once()?; + let packet_for = |peer: &mut RawPeer, id: &str| -> Result> { + let payload = encode_cultnet_message_to_vec(&put(id, &format!("note:{id}"), vec![1]), CultNetWireContract::CultNetSchemaV0)?; + peer.session.send_many( + "schema", + payload, + cultnet_rs::CultNetRudpSendOptions { reliable: true, ordered: false, sequenced: true, now_ms: 3, reliable_expire_after_ms: None }, + Some(1200), + ) + }; + let older = packet_for(&mut peer, "old")?; + let newer = packet_for(&mut peer, "new")?; + let mut refused_ids = Vec::new(); + for packets in [&newer, &older] { + for packet in packets { + peer.send_packet(packet)?; + } + server.poll_once()?; + for packet in peer.drain() { + for frame in peer.session.receive(&packet, 4)?.delivered { + let answer = decode_cultnet_message_from_slice(&frame.payload, CultNetWireContract::CultNetSchemaV0)?; + if matches!(answer, CultNetMessage::ErrorV1 { .. }) { + refused_ids.push(refusal_of(&answer).1.expect("a refusal names its write")); + } + } + } + } + assert_eq!(refused_ids, vec!["new".to_string(), "old".to_string()], "the superseded write is answered too"); + assert_eq!(sink.0.lock().unwrap().receipts.len(), 0, "and nothing was applied"); + Ok(()) +} + +/// The refusal reaches a sequenced write however far apart its fragments' sequences sit: other traffic can take the +/// sequences between the first fragment and the last, further back than the duplicate test's receive window. +#[test] +fn a_sequenced_write_whose_fragments_span_more_than_the_receive_window_is_refused() -> Result<()> { + let sink = Sink::default(); + let mut server = server(Default::default(), Clock::new(70_000), sink.clone(), Source::default())?; + let mut peer = RawPeer::connect(&mut server, 254)?; + peer.send(&receipt_hello())?; + server.poll_once()?; + let payload = encode_cultnet_message_to_vec(&put("far", "note:far", vec![1; 64]), CultNetWireContract::CultNetSchemaV0)?; + let mut packets = peer.session.send_many( + "schema", + payload, + cultnet_rs::CultNetRudpSendOptions { reliable: true, ordered: false, sequenced: true, now_ms: 3, reliable_expire_after_ms: None }, + Some(32), + )?; + let last = packets.pop().expect("the write is fragmented"); + assert!(!packets.is_empty()); + let mut next = last.sequence; + let mut refused_ids = Vec::new(); + for packet in &packets { + peer.send_packet(packet)?; + server.poll_once()?; + } + // 5,000 reliable packets on another channel, in order, take the sequences between the fragments. + for count in 0..5_000 { + let mut filler = packets[0].clone(); + filler.channel_id = "filler".into(); + filler.sequenced = false; + filler.fragment_id = 0; + filler.fragment_index = 0; + filler.fragment_count = 0; + filler.payload = vec![0]; + filler.sequence = next; + next += 1; + peer.send_packet(&filler)?; + server.poll_once()?; + if count % 100 == 99 { + // Read what the server sent, so the socket buffer never holds back the refusal. + for packet in peer.drain() { + peer.session.receive(&packet, 4)?; + } + } + } + let mut last = last; + last.sequence = next; + peer.send_packet(&last)?; + server.poll_once()?; + for packet in peer.drain() { + for frame in peer.session.receive(&packet, 4)?.delivered { + let answer = decode_cultnet_message_from_slice(&frame.payload, CultNetWireContract::CultNetSchemaV0)?; + if matches!(answer, CultNetMessage::ErrorV1 { .. }) { + refused_ids.push(refusal_of(&answer).1.expect("a refusal names its write")); + } + } + } + assert_eq!(refused_ids, vec!["far".to_string()]); + assert_eq!(sink.0.lock().unwrap().receipts.len(), 0, "and nothing was applied"); + Ok(()) +} + /// A refused write is traffic: the peer that sends it is not idle, so its session outlives the idle timeout measured /// from the refusal and not from the packet before it. #[test] From 34cceedf9841b57f9bc01c7bad5e43199ec7e2ff Mon Sep 17 00:00:00 2001 From: Libby Date: Sun, 8 Nov 2026 22:15:34 +0000 Subject: [PATCH 08/76] Rust RUDP server: pin the superseded-write refusal against a releasing packet and the half-space bound cargo-mutants on the previous commit missed two mutants: `&&` to `||` in the superseded-packet check (a packet that also releases another write hid it) and `<` to `<=` at the half-space bound of sequence_at_or_before. A test and two assertions kill them; the rerun catches 14 of 15 (1 unviable). Co-Authored-By: Claude Sonnet 5.5 --- .../cultmesh-rs/src/rudp_document_server.rs | 2 + .../cultmesh-rs/tests/rudp_document_server.rs | 44 +++++++++++++++++++ 2 files changed, 46 insertions(+) diff --git a/packages/cultmesh-rs/src/rudp_document_server.rs b/packages/cultmesh-rs/src/rudp_document_server.rs index a0ab51ac..1e729407 100644 --- a/packages/cultmesh-rs/src/rudp_document_server.rs +++ b/packages/cultmesh-rs/src/rudp_document_server.rs @@ -1961,5 +1961,7 @@ mod tests { assert!(!sequence_at_or_before(1, u32::MAX - 1), "after the wrap is later than before it"); assert!(sequence_at_or_before(u32::MAX, 0), "the wrap itself"); assert!(!sequence_at_or_before(0, u32::MAX)); + assert!(sequence_at_or_before(0, (1 << 31) - 1), "just inside half the space"); + assert!(!sequence_at_or_before(0, 1 << 31), "half the space away is not before"); } } diff --git a/packages/cultmesh-rs/tests/rudp_document_server.rs b/packages/cultmesh-rs/tests/rudp_document_server.rs index d9b42abb..6ca0007a 100644 --- a/packages/cultmesh-rs/tests/rudp_document_server.rs +++ b/packages/cultmesh-rs/tests/rudp_document_server.rs @@ -3138,6 +3138,50 @@ fn a_superseded_sequenced_write_is_still_refused() -> Result<()> { Ok(()) } +/// A superseded sequenced packet is answered even when its arrival fills a gap and releases another write: the +/// frames that arrive with it do not stand in for its own. +#[test] +fn a_superseded_sequenced_write_is_refused_while_it_releases_a_held_ordered_write() -> Result<()> { + let sink = Sink::default(); + let mut server = server(Default::default(), Clock::new(70_000), sink.clone(), Source::default())?; + let mut peer = RawPeer::connect(&mut server, 255)?; + peer.send(&receipt_hello())?; + server.poll_once()?; + let packet_for = |peer: &mut RawPeer, id: &str, ordered: bool, sequenced: bool| -> Result> { + let payload = encode_cultnet_message_to_vec(&put(id, &format!("note:{id}"), vec![1]), CultNetWireContract::CultNetSchemaV0)?; + peer.session.send_many( + "schema", + payload, + cultnet_rs::CultNetRudpSendOptions { reliable: true, ordered, sequenced, now_ms: 3, reliable_expire_after_ms: None }, + Some(1200), + ) + }; + let older = packet_for(&mut peer, "old", false, true)?; + let ordered = packet_for(&mut peer, "o1", true, false)?; + let newer = packet_for(&mut peer, "new", false, true)?; + let mut answers = Vec::new(); + for packets in [&newer, &ordered, &older] { + for packet in packets { + peer.send_packet(packet)?; + } + server.poll_once()?; + for packet in peer.drain() { + for frame in peer.session.receive(&packet, 4)?.delivered { + answers.push(decode_cultnet_message_from_slice(&frame.payload, CultNetWireContract::CultNetSchemaV0)?); + } + } + } + let mut refused: Vec = answers + .iter() + .filter(|a| matches!(a, CultNetMessage::ErrorV1 { .. })) + .map(|a| refusal_of(a).1.expect("a refusal names its write")) + .collect(); + refused.sort(); + assert_eq!(refused, vec!["new".to_string(), "old".to_string()], "{answers:?}"); + assert!(answers.contains(&admitted("o1", "note:o1")), "the released ordered write is admitted: {answers:?}"); + Ok(()) +} + /// The refusal reaches a sequenced write however far apart its fragments' sequences sit: other traffic can take the /// sequences between the first fragment and the last, further back than the duplicate test's receive window. #[test] From 6c441d1cda7780a90ecbff2dacaa3f0e7ac7e542 Mon Sep 17 00:00:00 2001 From: Libby Date: Mon, 9 Nov 2026 09:12:59 +0000 Subject: [PATCH 09/76] Rust RUDP server: answer a sequenced write once, not once per send A repeat of a packet the session already took, and a packet too far ahead to be remembered, delivered no frame and so drew the superseded-write refusal again on every send, breaking write-receipt rule 1. The refusal now applies only to the first packet the session takes for a sequence; CultNetRudpSession::has_received exposes that test. Co-Authored-By: Claude Sonnet 5.5 --- .../cultmesh-rs/src/rudp_document_server.rs | 9 +++ .../cultmesh-rs/tests/rudp_document_server.rs | 58 +++++++++++++++++++ packages/cultnet-rs/src/rudp.rs | 6 ++ 3 files changed, 73 insertions(+) diff --git a/packages/cultmesh-rs/src/rudp_document_server.rs b/packages/cultmesh-rs/src/rudp_document_server.rs index 1e729407..9aae5be9 100644 --- a/packages/cultmesh-rs/src/rudp_document_server.rs +++ b/packages/cultmesh-rs/src/rudp_document_server.rs @@ -780,6 +780,13 @@ where return Ok(CultMeshRudpPollOutcome::Handled); } + // Whether this packet is the first the session takes for its sequence: a repeat of an answered write, or a + // packet too far ahead to be remembered, is not, and the write is not answered for it again. + let first_receipt = packet.reliable + && self + .sessions + .get(&key) + .is_some_and(|entry| !entry.session.has_received(packet.sequence)); let result = { let entry = self .sessions @@ -825,6 +832,8 @@ where // go unanswered. A packet that carries its whole frame is answered from its own payload. A superseded // fragment cannot be: the write's id may lie in a fragment this packet does not hold. if refuse_sequenced + && first_receipt + && self.sessions.get(&key).is_some_and(|entry| entry.session.has_received(packet.sequence)) && packet.fragment_count == 0 && !result.delivered.iter().any(|frame| frame.channel_id == "schema" && frame.sequence == packet.sequence) { diff --git a/packages/cultmesh-rs/tests/rudp_document_server.rs b/packages/cultmesh-rs/tests/rudp_document_server.rs index 6ca0007a..60596ee5 100644 --- a/packages/cultmesh-rs/tests/rudp_document_server.rs +++ b/packages/cultmesh-rs/tests/rudp_document_server.rs @@ -3138,6 +3138,64 @@ fn a_superseded_sequenced_write_is_still_refused() -> Result<()> { Ok(()) } +/// One answer per write (write-receipt.md, rule 1): a sequenced packet the server already answered is answered +/// once, however often the peer sends it again, and a packet too far ahead to be received draws nothing until it +/// is received (the peer resends it, and the answer comes then). +#[test] +fn a_repeated_or_far_ahead_sequenced_write_draws_one_answer_not_one_per_send() -> Result<()> { + let sink = Sink::default(); + let mut server = server(Default::default(), Clock::new(70_000), sink.clone(), Source::default())?; + let mut peer = RawPeer::connect(&mut server, 256)?; + peer.send(&receipt_hello())?; + server.poll_once()?; + let packet_for = |peer: &mut RawPeer, id: &str| -> Result> { + let payload = encode_cultnet_message_to_vec(&put(id, &format!("note:{id}"), vec![1]), CultNetWireContract::CultNetSchemaV0)?; + peer.session.send_many( + "schema", + payload, + cultnet_rs::CultNetRudpSendOptions { reliable: true, ordered: false, sequenced: true, now_ms: 3, reliable_expire_after_ms: None }, + Some(1200), + ) + }; + let refusals_of = |peer: &mut RawPeer| -> Result> { + let mut ids = Vec::new(); + for packet in peer.drain() { + for frame in peer.session.receive(&packet, 4)?.delivered { + let answer = decode_cultnet_message_from_slice(&frame.payload, CultNetWireContract::CultNetSchemaV0)?; + if matches!(answer, CultNetMessage::ErrorV1 { .. }) { + ids.push(refusal_of(&answer).1.expect("a refusal names its write")); + } + } + } + Ok(ids) + }; + // The same packet, sent three times (a resend whose acknowledgement was lost), is refused once. + let once = packet_for(&mut peer, "dup")?; + let mut refused = Vec::new(); + for _ in 0..3 { + for packet in &once { + peer.send_packet(packet)?; + } + server.poll_once()?; + refused.extend(refusals_of(&mut peer)?); + } + assert_eq!(refused, vec!["dup".to_string()], "a repeat of an answered write is not answered again"); + + // A packet far past the receive window is not received: no answer yet, and none per resend. + let ahead = packet_for(&mut peer, "ahead")?; + let mut far = ahead[0].clone(); + far.sequence = once[0].sequence + 5_000; + let mut refused = Vec::new(); + for _ in 0..3 { + peer.send_packet(&far)?; + server.poll_once()?; + refused.extend(refusals_of(&mut peer)?); + } + assert_eq!(refused, Vec::::new(), "an unreceived packet draws no answer"); + assert_eq!(sink.0.lock().unwrap().receipts.len(), 0); + Ok(()) +} + /// A superseded sequenced packet is answered even when its arrival fills a gap and releases another write: the /// frames that arrive with it do not stand in for its own. #[test] diff --git a/packages/cultnet-rs/src/rudp.rs b/packages/cultnet-rs/src/rudp.rs index eae7a2ee..427a38c1 100644 --- a/packages/cultnet-rs/src/rudp.rs +++ b/packages/cultnet-rs/src/rudp.rs @@ -1277,6 +1277,12 @@ impl CultNetRudpSession { } } + /// Whether `receive` has taken this reliable sequence: delivered, held, superseded or a duplicate. A packet + /// `receive` refused unremembered (too far ahead, or before the handshake) is not, and its sender resends it. + pub fn has_received(&self, sequence: u32) -> bool { + self.was_received(sequence) + } + /// The one duplicate test: true for a sequence in the window that was /// received, for one below the window, and for one at or before the /// watermark. The watermark starts at the handshake's seed, so a frame the From 636b387cf10550fcb335344f679f3f1879d6e8c8 Mon Sep 17 00:00:00 2001 From: Libby Date: Mon, 9 Nov 2026 09:13:31 +0000 Subject: [PATCH 10/76] write-receipt spec: a repeated or unreceived sequenced packet draws no second answer Co-Authored-By: Claude Sonnet 5.5 --- contracts/cultnet/interop/write-receipt.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contracts/cultnet/interop/write-receipt.md b/contracts/cultnet/interop/write-receipt.md index b74411ea..234fdf93 100644 --- a/contracts/cultnet/interop/write-receipt.md +++ b/contracts/cultnet/interop/write-receipt.md @@ -38,7 +38,7 @@ A connection is receipt-capable when its `cultnet.hello.v0` (sent before its fir - **Limit refusals are remembered.** A `limit_reached` refusal is remembered like any other: a resend of the same id and content hash replays `limit_reached`. No slot is freed mid-session, so an id refused at a bound never becomes admitted later in that session (a server at the cap keeps the refused digest pair in a bounded refusal set of at most 1024 per session, outside the entry budget; past it, a further new write is refused `limit_reached` without being remembered, and the session refuses every id it has not seen for the rest of its life, even if a server-wide budget has since freed, so a refusal that was not remembered is never followed by an admission of the same id). The client mints a new id on a new session. - **Required fields.** A put or delete whose document or `schemaId` is null or missing is refused `message_invalid` with `details.field` `"document"` or `"schemaId"` (the first missing in that order) and fixed text; the write is not applied. A put or delete that is present but names nothing is not "missing": the write is refused `message_invalid` exactly as a frame that does not read, with `details.field` `"message"`, the fixed unreadable text and the write's id (vector `error_v1_empty_schema_id_is_unreadable_and_names_the_write`), and is never applied; a server must not hand it to its store. Names nothing means, on a put or delete, a `schemaId` or `recordKey` that is blank; on a put's document, also a `storedAt` that is blank, a `payload` of zero bytes (a MessagePack value is at least one byte, so an empty payload is no document), a `sourceRuntimeId`, `sourceAgentId` or `sourceRole` that is present and blank, and a `tags` entry that is blank. **Blank** means empty or made only of Unicode White_Space code points (the PropList property) and nothing else, in every runtime, whatever its platform's own whitespace test says. The set is exactly U+0009 to U+000D, U+0020, U+0085, U+00A0, U+1680, U+2000 to U+200A, U+2028, U+2029, U+202F, U+205F and U+3000. U+200B, U+200C, U+200D, U+2060, U+FEFF, U+180E and U+001C to U+001F are not White_Space (some platforms' whitespace tests accept U+001C to U+001F or U+180E, for example Java's `Character.isWhitespace` and older .NET, so a runtime must check the list, not its platform test): a string of them names something. The vectors list both sets (`whiteSpaceCodePoints`, `notWhiteSpaceCodePoints`) and a runtime checks every code point in every covered field. Absent or nil source ids and tags, and an empty `tags` list, are valid. The rule holds on every connection, receipt-capable or not (a legacy connection is refused with the `error.v0` of the same code, details and text); the vectors' `blankFields` section lists each case. - **Fixed texts.** The `error` string of these refusals is fixed, so vectors can compare it: `message_invalid: messageId exceeds 256 bytes`, `message_invalid: document is required`, `message_invalid: schemaId is required`, `message_invalid: the message could not be read` (a frame that does not decode, or a write that names nothing; the text is the same for both and never carries a decoder's own wording), `message_invalid: receipt sessions require the reliable channel`, `limit_reached: the session's remembered-write limit is full`. - - **Reliable channel only.** A write is accepted on a receipt session only on a channel that is reliable and not sequenced (reliable ordered and reliable unordered both qualify; a sequenced channel drops superseded packets, so a write on it can vanish without trace). A write arriving on any other channel is refused `message_invalid` with `details.field: "channel"` (the transport profile's word for it) and fixed text. Receipts and refusals travel the reliable channel; a write that could be lost without trace cannot be answered once. The server refuses a sequenced write whenever it can read the write's id: from the delivered frame, and from a superseded packet that carries its whole frame. A write sent as several fragments whose frame is superseded is dropped whole and may go unanswered; the client's unconfirmed outcome covers it. + - **Reliable channel only.** A write is accepted on a receipt session only on a channel that is reliable and not sequenced (reliable ordered and reliable unordered both qualify; a sequenced channel drops superseded packets, so a write on it can vanish without trace). A write arriving on any other channel is refused `message_invalid` with `details.field: "channel"` (the transport profile's word for it) and fixed text. Receipts and refusals travel the reliable channel; a write that could be lost without trace cannot be answered once. The server refuses a sequenced write whenever it can read the write's id: from the delivered frame, and from a superseded packet that carries its whole frame. A write sent as several fragments whose frame is superseded is dropped whole and may go unanswered; the client's unconfirmed outcome covers it. A repeat of a packet the server already took, or a packet too far ahead to be taken, draws no further answer (rule 1). - Past the session (or a server's longer retention, if it keeps one) a resend is a new write. 6. **Forwarded writes.** A server that forwards a non-primary write answers the original peer only with what the primary answered: the primary's receipt becomes `write_admitted` with the original id, its refusal becomes `error.v1` with the original id. If the forward gets no answer the server relays nothing: no receipt, no error, no synthesized refusal. The client's own rule below then reports the write as unconfirmed. A forwarder never turns an absent or ambiguous primary answer into an admission. 7. **Looks alike.** An admitted and a refused write differ in `schemaVersion`; a receipt has no `error`, an `error.v1` has no admission. A transport ack is neither. From 6ca9c455b71c07591603e7b07fd08dfc762dae7b Mon Sep 17 00:00:00 2001 From: Libby Date: Mon, 9 Nov 2026 10:37:48 +0000 Subject: [PATCH 11/76] cultmesh-kotlin: sendFailed vectors, a tracker that ends with its session, White_Space blank The replay runner handles the sendFailed event, fails on an event key it does not know and on a vector count the file did not declare. WriteTracker settles a write sent after sessionEnd as session_ended. Source ids on a raw document record are dropped when blank by Unicode White_Space (isBlankText), not Kotlin's isNullOrBlank, which keeps U+0085 and drops U+001C to U+001F; the vector file's JSON reader skips JSON whitespace only. Co-Authored-By: Claude Sonnet 5.5 --- .../kotlin/org/gamecult/cultmesh/CultMesh.kt | 7 ++--- .../org/gamecult/cultmesh/WriteReceipt.kt | 27 ++++++++++++++++++- .../gamecult/cultmesh/WriteReceiptVectors.kt | 21 ++++++++++++++- 3 files changed, 50 insertions(+), 5 deletions(-) diff --git a/packages/cultmesh-kotlin/src/main/kotlin/org/gamecult/cultmesh/CultMesh.kt b/packages/cultmesh-kotlin/src/main/kotlin/org/gamecult/cultmesh/CultMesh.kt index d9cdfad7..4a06d7f5 100644 --- a/packages/cultmesh-kotlin/src/main/kotlin/org/gamecult/cultmesh/CultMesh.kt +++ b/packages/cultmesh-kotlin/src/main/kotlin/org/gamecult/cultmesh/CultMesh.kt @@ -1100,9 +1100,9 @@ data class CultNetRawDocumentRecord( "payloadEncoding" to payloadEncoding, "payload" to payload.copyOf(), ) - if (!sourceRuntimeId.isNullOrBlank()) wire["sourceRuntimeId"] = sourceRuntimeId - if (!sourceAgentId.isNullOrBlank()) wire["sourceAgentId"] = sourceAgentId - if (!sourceRole.isNullOrBlank()) wire["sourceRole"] = sourceRole + if (!isBlankText(sourceRuntimeId)) wire["sourceRuntimeId"] = sourceRuntimeId + if (!isBlankText(sourceAgentId)) wire["sourceAgentId"] = sourceAgentId + if (!isBlankText(sourceRole)) wire["sourceRole"] = sourceRole if (tags.isNotEmpty()) wire["tags"] = tags return wire } @@ -3413,6 +3413,7 @@ fun main(args: Array) { rudpClientBindsLoopbackOnlyForALoopbackEndpoint() rudpSocketTransportHandshakesAndCarriesReliableOrderedSchemaFrames() rudpSocketTransportCarriesFragmentedReliableOrderedSchemaFrames() + rawDocumentRecordDropsOnlyWhiteSpaceSourceIds() return } diff --git a/packages/cultmesh-kotlin/src/main/kotlin/org/gamecult/cultmesh/WriteReceipt.kt b/packages/cultmesh-kotlin/src/main/kotlin/org/gamecult/cultmesh/WriteReceipt.kt index 4060f08a..4b38d942 100644 --- a/packages/cultmesh-kotlin/src/main/kotlin/org/gamecult/cultmesh/WriteReceipt.kt +++ b/packages/cultmesh-kotlin/src/main/kotlin/org/gamecult/cultmesh/WriteReceipt.kt @@ -163,11 +163,16 @@ class WriteTracker(private val windowMs: Long, private val onSettle: (String, Wr } private val writes = linkedMapOf() + private var ended = false /** Registers a write sent at [atMs]. Re-sending a known id is the same write and does not restart its window. */ fun send(messageId: String, atMs: Long) { tick(atMs) - writes.getOrPut(messageId) { Entry(atMs) } + if (messageId in writes) return + val entry = Entry(atMs) + writes[messageId] = entry + // On a session that already ended nothing can answer this write. + if (ended) settle(messageId, entry, WriteSettlement.Unconfirmed(WriteUnconfirmedReason.SESSION_ENDED)) } /** Feeds one received message. Only write_admitted, error.v1 and error.v0 answer; anything else is ignored. */ @@ -200,8 +205,10 @@ class WriteTracker(private val windowMs: Long, private val onSettle: (String, Wr } } + /** The session ended: every unanswered write settles unconfirmed, and so does any write sent afterwards. */ fun sessionEnd(atMs: Long) { tick(atMs) + ended = true for ((id, entry) in writes) if (entry.settlement == null) settle(id, entry, WriteSettlement.Unconfirmed(WriteUnconfirmedReason.SESSION_ENDED)) } @@ -230,3 +237,21 @@ class WriteTracker(private val windowMs: Long, private val onSettle: (String, Wr onSettle(messageId, settlement) } } + +/** Unicode White_Space (PropList), the one definition of "blank" in write-receipt.md rule 5; Kotlin's own isBlank takes U+001C to U+001F. */ +private val WHITE_SPACE_CODE_POINTS: Set = (0x0009..0x000D).toSet() + 0x0020 + 0x0085 + 0x00A0 + 0x1680 + + (0x2000..0x200A).toSet() + 0x2028 + 0x2029 + 0x202F + 0x205F + 0x3000 + +/** True for null, empty, or only White_Space code points. */ +fun isBlankText(text: String?): Boolean = text == null || text.codePoints().allMatch { it in WHITE_SPACE_CODE_POINTS } + +/** Self-test: blank is White_Space, not Kotlin's isBlank (rule 5): U+001C to U+001F name something, U+00A0 and U+0085 do not. */ +fun rawDocumentRecordDropsOnlyWhiteSpaceSourceIds() { + fun wireKeys(id: String) = CultNetRawDocumentRecord("note.v0", "note:1", "2026-11-08T00:00:00Z", payload = byteArrayOf(1), sourceRuntimeId = id, sourceAgentId = id, sourceRole = id).toWireMap().keys + for (blank in listOf("", "\u0085", "\u00a0\u2003", "\u3000", "\t\r\n ")) { + check(isBlankText(blank) && "sourceRuntimeId" !in wireKeys(blank)) { "blank ${blank.map { it.code }} must be dropped" } + } + for (named in listOf("\u001c", "\u001f", "\u200b", "\ufeff", "\u180e", "a")) { + check(!isBlankText(named) && "sourceRuntimeId" in wireKeys(named) && "sourceRole" in wireKeys(named)) { "${named.map { it.code }} names something and must be kept" } + } +} diff --git a/packages/cultmesh-kotlin/src/main/kotlin/org/gamecult/cultmesh/WriteReceiptVectors.kt b/packages/cultmesh-kotlin/src/main/kotlin/org/gamecult/cultmesh/WriteReceiptVectors.kt index 033ef8fd..e527692d 100644 --- a/packages/cultmesh-kotlin/src/main/kotlin/org/gamecult/cultmesh/WriteReceiptVectors.kt +++ b/packages/cultmesh-kotlin/src/main/kotlin/org/gamecult/cultmesh/WriteReceiptVectors.kt @@ -8,6 +8,9 @@ package org.gamecult.cultmesh import java.io.File import java.io.IOException +// The event actions scenarios.semantics names; anything else is refused, not skipped. +private val EVENT_KEYS = listOf("send", "frame", "tick", "sessionEnd", "sendFailed", "transportAck") + fun replayWriteReceiptVectors(path: String): Boolean { @Suppress("UNCHECKED_CAST") val vectors = JsonReader(File(path).readText(Charsets.UTF_8)).readDocument() as Map @@ -87,12 +90,15 @@ fun replayWriteReceiptVectors(path: String): Boolean { } for (event in list(scenario["events"]).map(::obj).sortedBy { it["atMs"] as Long }) { val at = event["atMs"] as Long + val actions = event.keys - "atMs" + if (actions.size != 1 || actions.first() !in EVENT_KEYS) { problems += "event at $at has keys $actions, wanted atMs and exactly one of $EVENT_KEYS"; continue } when { "send" in event -> tracker.send(event["send"] as String, at) // A frame reaches the client as bytes: the fake server encodes it, the client decodes it. "frame" in event -> tracker.frame(parseCultNetMessage(MessagePackWriter().value(event["frame"]).toByteArray()), at) "tick" in event -> tracker.tick(at) "sessionEnd" in event -> tracker.sessionEnd(at) + "sendFailed" in event -> tracker.sendFailed(event["sendFailed"] as String, at) // transportAck: delivery is not an answer; the tracker has no such input and must not need one. } } @@ -116,6 +122,19 @@ fun replayWriteReceiptVectors(path: String): Boolean { } } + // A runner that read fewer vectors than the file declares has silently skipped some. + check("counts") { + val declared = obj(vectors["counts"]) + fun ran(prefix: String) = results.keys.count { it.startsWith(prefix) } + val loaded = mapOf( + "wire" to list(vectors["wire"]).size, "schemaInvalid" to list(vectors["schemaInvalid"]).size, + "contentHashCases" to list(obj(vectors["contentHash"])["cases"]).size, "scenarioCases" to list(obj(vectors["scenarios"])["cases"]).size, + ) + val executed = mapOf("wire" to ran("wire/"), "schemaInvalid" to ran("schemaInvalid/"), "contentHashCases" to ran("contentHash/"), "scenarioCases" to ran("scenario/")) + loaded.keys.filter { loaded[it] != (declared[it] as Long).toInt() || executed[it] != loaded[it] } + .map { "$it: declared ${declared[it]}, loaded ${loaded[it]}, ran ${executed[it]}" } + } + for ((name, problems) in results) { println(if (problems.isEmpty()) "PASS $name" else "FAIL $name") problems.forEach { println(" $it") } @@ -144,7 +163,7 @@ private class JsonReader(private val text: String) { fun readDocument(): Any? = readValue().also { skipSpace(); if (at != text.length) throw IOException("trailing JSON") } - private fun skipSpace() { while (at < text.length && text[at].isWhitespace()) at++ } + private fun skipSpace() { while (at < text.length && text[at] in " \t\r\n") at++ } private fun readValue(): Any? { skipSpace() From 8c31933e108b00240708a32a9b9db12a4644e0c3 Mon Sep 17 00:00:00 2001 From: Libby Date: Mon, 9 Nov 2026 10:38:29 +0000 Subject: [PATCH 12/76] cultnet-py: the write-receipt vector sections include the file's counts Co-Authored-By: Claude Sonnet 5.5 --- packages/cultnet-py/tests/test_write_receipt.py | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/packages/cultnet-py/tests/test_write_receipt.py b/packages/cultnet-py/tests/test_write_receipt.py index 32c5f03a..98e74287 100644 --- a/packages/cultnet-py/tests/test_write_receipt.py +++ b/packages/cultnet-py/tests/test_write_receipt.py @@ -19,7 +19,7 @@ from cultnet_py import ( ) # Decoder-side conformance with contracts/cultnet/interop/write-receipt-vectors.json: `contentHash`, `wire` and -# `schemaInvalid`. `dedupe` and `blankFields` are server obligations and `scenarios` is the client write tracker; +# `schemaInvalid`. `dedupe` and `blankFields` are server obligations and `scenarios` is the client write tracker; `counts` is the file's own tally; # this runtime has neither a receipt server nor a write client, so it replays none of them. _INTEROP = Path(__file__).resolve().parents[3] / "contracts/cultnet/interop" _VECTORS = json.loads((_INTEROP / "write-receipt-vectors.json").read_text(encoding="utf-8")) @@ -57,11 +57,17 @@ class WriteReceiptVectorTests(unittest.TestCase): def test_vector_sections_are_the_ones_this_file_accounts_for(self) -> None: self.assertEqual( set(_VECTORS) - {"provenance", "specification"}, - {"constants", "contentHash", "dedupe", "wire", "blankFields", "schemaInvalid", "scenarios"}, + {"constants", "contentHash", "dedupe", "wire", "blankFields", "schemaInvalid", "scenarios", "counts"}, ) self.assertEqual(len(_VECTORS["contentHash"]["cases"]), 4) self.assertEqual(len(_VECTORS["wire"]), 7) self.assertEqual(len(_VECTORS["schemaInvalid"]), 7) + # The file declares how many of each it holds; a loader that drops entries disagrees with it. + counts = _VECTORS["counts"] + self.assertEqual(counts["wire"], len(_VECTORS["wire"])) + self.assertEqual(counts["schemaInvalid"], len(_VECTORS["schemaInvalid"])) + self.assertEqual(counts["contentHashCases"], len(_VECTORS["contentHash"]["cases"])) + self.assertEqual(counts["scenarioCases"], len(_VECTORS["scenarios"]["cases"])) def test_content_hash_matches_every_vector(self) -> None: for case in _VECTORS["contentHash"]["cases"]: From 3e6ec8567c6aa0da8d410e6b9d894f8e325d70a9 Mon Sep 17 00:00:00 2001 From: Libby Date: Mon, 9 Nov 2026 13:30:05 +0000 Subject: [PATCH 13/76] CI: forge runner label is docker, not ubuntu-latest Co-Authored-By: Claude Sonnet 5.5 --- .github/workflows/cultmesh-portability.yml | 4 ++-- .github/workflows/publish-packages.yml | 4 ++-- .github/workflows/quic-native.yml | 6 +++--- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/.github/workflows/cultmesh-portability.yml b/.github/workflows/cultmesh-portability.yml index 19bf1b34..22619240 100644 --- a/.github/workflows/cultmesh-portability.yml +++ b/.github/workflows/cultmesh-portability.yml @@ -16,8 +16,8 @@ jobs: strategy: fail-fast: false matrix: - # The forge runner is Ubuntu-only: Windows legs run on workflow_dispatch (GitHub or a Windows runner), Ubuntu gates push and PR. - os: ${{ fromJSON(github.event_name == 'workflow_dispatch' && '["windows-latest", "ubuntu-latest"]' || '["ubuntu-latest"]') }} + # The forge runner (label `docker`) is Linux-only: Windows legs run on workflow_dispatch (GitHub or a Windows runner), Ubuntu gates push and PR. + os: ${{ fromJSON(github.event_name == 'workflow_dispatch' && '["windows-latest", "docker"]' || '["docker"]') }} steps: - name: Check out CultLib diff --git a/.github/workflows/publish-packages.yml b/.github/workflows/publish-packages.yml index caa24336..f4026e41 100644 --- a/.github/workflows/publish-packages.yml +++ b/.github/workflows/publish-packages.yml @@ -50,7 +50,7 @@ jobs: cultcache-ts: name: "@gamecult/cultcache-ts to npm" if: startsWith(github.ref, 'refs/tags/cultcache-ts-v') || inputs.package == 'cultcache-ts' - runs-on: ubuntu-latest + runs-on: docker timeout-minutes: 20 steps: # fetch-depth: 0 so the semver check in the release step can see every previously @@ -103,7 +103,7 @@ jobs: fail-fast: false matrix: package: [cultcache-py, cultnet-py, cultmesh-py] - runs-on: ubuntu-latest + runs-on: docker timeout-minutes: 20 environment: pypi permissions: diff --git a/.github/workflows/quic-native.yml b/.github/workflows/quic-native.yml index 9b16cbeb..574db198 100644 --- a/.github/workflows/quic-native.yml +++ b/.github/workflows/quic-native.yml @@ -31,7 +31,7 @@ jobs: github.event_name == 'pull_request' || (github.event_name == 'push' && (github.ref == 'refs/heads/main' || startsWith(github.ref, 'refs/tags/cultmesh-quic-native-v'))) - runs-on: ubuntu-latest + runs-on: docker timeout-minutes: 5 steps: - name: Check out CultLib @@ -89,7 +89,7 @@ jobs: if: >- github.event_name == 'workflow_dispatch' || (github.event_name == 'push' && startsWith(github.ref, 'refs/tags/cultmesh-quic-native-v')) - runs-on: ubuntu-latest + runs-on: docker # Q6 A: this pinned Debian 13 container is the only permitted Linux # release builder. The digest is the pin; the `debian:13` tag is only a # label. The artifact links against this image's glibc (2.41); a bare @@ -133,7 +133,7 @@ jobs: name: Release native binaries needs: [build-win32-x64, build-linux-x64] if: github.event_name == 'push' && startsWith(github.ref, 'refs/tags/cultmesh-quic-native-v') - runs-on: ubuntu-latest + runs-on: docker timeout-minutes: 10 permissions: contents: write From fc0f83a385663df5bcd895958b9f0176360dd048 Mon Sep 17 00:00:00 2001 From: Libby Date: Mon, 9 Nov 2026 13:32:41 +0000 Subject: [PATCH 14/76] CI: Windows jobs are workflow_dispatch only (no Windows runner exists) Co-Authored-By: Claude Sonnet 5.5 --- .github/workflows/cultmesh-portability.yml | 2 +- .github/workflows/cultnet-interop.yml | 2 +- .github/workflows/quic-native.yml | 7 ++++--- 3 files changed, 6 insertions(+), 5 deletions(-) diff --git a/.github/workflows/cultmesh-portability.yml b/.github/workflows/cultmesh-portability.yml index 22619240..b5787fd4 100644 --- a/.github/workflows/cultmesh-portability.yml +++ b/.github/workflows/cultmesh-portability.yml @@ -16,7 +16,7 @@ jobs: strategy: fail-fast: false matrix: - # The forge runner (label `docker`) is Linux-only: Windows legs run on workflow_dispatch (GitHub or a Windows runner), Ubuntu gates push and PR. + # The forge runner (label `docker`) is Linux-only and no Windows runner exists: the Windows leg runs on workflow_dispatch only (GitHub or a Windows runner), the docker leg gates push and PR. Windows push/PR triggers return in the PR that registers a Windows runner. os: ${{ fromJSON(github.event_name == 'workflow_dispatch' && '["windows-latest", "docker"]' || '["docker"]') }} steps: diff --git a/.github/workflows/cultnet-interop.yml b/.github/workflows/cultnet-interop.yml index 118b496f..8f85506e 100644 --- a/.github/workflows/cultnet-interop.yml +++ b/.github/workflows/cultnet-interop.yml @@ -1,7 +1,7 @@ name: CultNet cross-runtime interop # workflow_dispatch only: this job is Windows-only (pwsh Kotlin build, setup steps), and the forge -# runner is Ubuntu-only, so a push or pull_request trigger would queue a job that can never run. +# runner is Ubuntu-only (no Windows runner exists; push and pull_request return in the PR that registers one), so a push or pull_request trigger would queue a job that can never run. # Run it from GitHub or any Windows runner. on: workflow_dispatch: diff --git a/.github/workflows/quic-native.yml b/.github/workflows/quic-native.yml index 574db198..7d4dd2be 100644 --- a/.github/workflows/quic-native.yml +++ b/.github/workflows/quic-native.yml @@ -49,11 +49,12 @@ jobs: # workflow_dispatch and a cultmesh-quic-native-v* tag run the two builds. # A dry (workflow_dispatch) run uploads artifacts and creates no release. + # No Windows runner is registered on the forge, so this job is workflow_dispatch only + # (run it on GitHub or a Windows runner). The tag trigger returns in the PR that registers one. + # Until then a tag push skips the release job, which needs this build. build-win32-x64: name: Build win32-x64 - if: >- - github.event_name == 'workflow_dispatch' || - (github.event_name == 'push' && startsWith(github.ref, 'refs/tags/cultmesh-quic-native-v')) + if: github.event_name == 'workflow_dispatch' runs-on: windows-latest timeout-minutes: 20 steps: From 041a7694759218350c14901d3f1d527d1b3b1fa4 Mon Sep 17 00:00:00 2001 From: Libby Date: Mon, 9 Nov 2026 13:40:39 +0000 Subject: [PATCH 15/76] CI: quic-native glibc comment no longer names ubuntu-latest as the runner Co-Authored-By: Claude Sonnet 5.5 --- .github/workflows/quic-native.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/quic-native.yml b/.github/workflows/quic-native.yml index 7d4dd2be..eb72975c 100644 --- a/.github/workflows/quic-native.yml +++ b/.github/workflows/quic-native.yml @@ -93,8 +93,8 @@ jobs: runs-on: docker # Q6 A: this pinned Debian 13 container is the only permitted Linux # release builder. The digest is the pin; the `debian:13` tag is only a - # label. The artifact links against this image's glibc (2.41); a bare - # ubuntu-latest runner carries glibc 2.39, which is not the deploy host's + # label. The artifact links against this image's glibc (2.41); + # a bare runner (label `docker`) has no pinned glibc (a GitHub ubuntu-latest image carried 2.39), which is not the deploy host's # OS (docs/typescript-quic-realtime-cut.md:2192-2197). container: image: debian:13@sha256:f324c7ff54321e8d9c588493a20244965938ce0aa50bbd1022d38010e9ffc4b1 From 4571a0d4e7f1e9b0da9ddfec962c0dc7a7fa1366 Mon Sep 17 00:00:00 2001 From: soul Date: Mon, 9 Nov 2026 14:39:51 +0000 Subject: [PATCH 16/76] tests first: write_admitted messageId rule vectors (failing) Pins Heimdall's rule: a write_admitted messageId needs a Unicode letter or number and no Default_Ignorable_Code_Point, compared raw. Shared vectors plus a runner test in TS, Python, Kotlin, C# and Rust. No decoder changed; the refuse vectors fail. Co-Authored-By: Claude Sonnet 5.5 --- .../interop/write-receipt-vectors.json | 277 +++++++++++++++++- contracts/cultnet/interop/write-receipt.md | 4 +- .../gamecult/cultmesh/WriteReceiptVectors.kt | 23 +- .../cultnet-py/tests/test_write_receipt.py | 27 +- packages/cultnet-rs/tests/write_receipt.rs | 48 +++ .../cultnet-ts/test/write-receipt.test.ts | 21 ++ .../CultNetWriteAdmittedMessageIdTests.cs | 58 ++++ 7 files changed, 454 insertions(+), 4 deletions(-) create mode 100644 tests/GameCult.Networking.Tests/CultNetWriteAdmittedMessageIdTests.cs diff --git a/contracts/cultnet/interop/write-receipt-vectors.json b/contracts/cultnet/interop/write-receipt-vectors.json index 397fbad6..a37d9660 100644 --- a/contracts/cultnet/interop/write-receipt-vectors.json +++ b/contracts/cultnet/interop/write-receipt-vectors.json @@ -149,6 +149,280 @@ { "name": "empty_tag_list", "set": { "tags": [] }, "outcome": "admitted" } ] }, + "admittedMessageId": { + "semantics": "Decoder-side rule for a `cultnet.write_admitted.v0` frame's `messageId` (Heimdall's ruling, write-receipt.md). Valid only if it contains at least one Unicode letter (\\p{L}) or number (\\p{N}) AND no Default_Ignorable_Code_Point (DerivedCoreProperties). The compare is RAW: the id is echoed byte for byte, so a decoder neither trims nor normalizes. Each case's id is given as `codePoints` (hex, in order), repeated `repeat` times (default 1), so no lone surrogate appears in this file. A runner builds the frame {schemaVersion write_admitted.v0, messageId, operation put, schemaId note.v0, recordKey note:1} with its own encoder and decodes it with the write_admitted decoder; `refuse` means the decoder refuses the frame the way it refuses an empty or ill-typed messageId, `accept` means it decodes and the id comes back identical. A case with `wireHex` carries the whole frame in MessagePack hex, for an id a language string cannot hold: the lone surrogate U+D800 is not valid UTF-8, so the vector's messageId is a MessagePack str whose bytes are ED A0 80, and Rust, C#, Kotlin and Python build the frame from those bytes and must refuse it. Scope: write_admitted only; error.v1 is untouched.", + "cases": [ + { + "name": "refuse_braille_blank_2800", + "codePoints": [ + "2800" + ], + "expect": "refuse", + "why": "U+2800 is Default_Ignorable and not a letter or number" + }, + { + "name": "refuse_hangul_filler_3164", + "codePoints": [ + "3164" + ], + "expect": "refuse", + "why": "U+3164 Default_Ignorable (a letter by category Lo, so DI decides)" + }, + { + "name": "refuse_hangul_choseong_filler_115F", + "codePoints": [ + "115F" + ], + "expect": "refuse", + "why": "U+115F Default_Ignorable (category Lo)" + }, + { + "name": "refuse_halfwidth_hangul_filler_FFA0", + "codePoints": [ + "FFA0" + ], + "expect": "refuse", + "why": "U+FFA0 Default_Ignorable (category Lo)" + }, + { + "name": "refuse_combining_grapheme_joiner_034F", + "codePoints": [ + "034F" + ], + "expect": "refuse", + "why": "U+034F Default_Ignorable" + }, + { + "name": "refuse_combining_mark_alone_0300", + "codePoints": [ + "0300" + ], + "expect": "refuse", + "why": "a combining mark is not a letter or number" + }, + { + "name": "refuse_private_use_E000", + "codePoints": [ + "E000" + ], + "expect": "refuse", + "why": "private use is neither a letter nor a number" + }, + { + "name": "refuse_unassigned_0378", + "codePoints": [ + "0378" + ], + "expect": "refuse", + "why": "unassigned since Unicode 1.1, neither a letter nor a number" + }, + { + "name": "refuse_lone_surrogate_D800", + "codePoints": [ + "D800" + ], + "expect": "refuse", + "why": "not a Unicode scalar value; on the wire it is the invalid UTF-8 bytes ED A0 80 in a str, so the decoder must refuse the frame. Rust, C#, Kotlin and Python build the frame from wireHex; a runtime whose strings can hold a lone surrogate (TypeScript) may also build it from the code point", + "wireHex": "85AD736368656D6156657273696F6EB963756C746E65742E77726974655F61646D69747465642E7630A96D6573736167654964A3EDA080A96F7065726174696F6EA3707574A8736368656D614964A76E6F74652E7630A97265636F72644B6579A66E6F74653A31" + }, + { + "name": "refuse_control_nul_0000", + "codePoints": [ + "0000" + ], + "expect": "refuse", + "why": "a control character is neither a letter nor a number" + }, + { + "name": "refuse_control_bel_0007", + "codePoints": [ + "0007" + ], + "expect": "refuse", + "why": "a control character is neither a letter nor a number" + }, + { + "name": "refuse_control_unit_separator_001F", + "codePoints": [ + "001F" + ], + "expect": "refuse", + "why": "a control character is neither a letter nor a number" + }, + { + "name": "refuse_control_delete_007F", + "codePoints": [ + "007F" + ], + "expect": "refuse", + "why": "a control character is neither a letter nor a number" + }, + { + "name": "refuse_control_next_line_0085", + "codePoints": [ + "0085" + ], + "expect": "refuse", + "why": "a control character is neither a letter nor a number" + }, + { + "name": "refuse_empty", + "codePoints": [], + "expect": "refuse", + "why": "empty" + }, + { + "name": "refuse_whitespace_only_space_0020", + "codePoints": [ + "0020" + ], + "expect": "refuse", + "why": "white space only" + }, + { + "name": "refuse_whitespace_only_nbsp_00A0", + "codePoints": [ + "00A0" + ], + "expect": "refuse", + "why": "white space only" + }, + { + "name": "refuse_whitespace_only_ideographic_space_3000", + "codePoints": [ + "3000" + ], + "expect": "refuse", + "why": "white space only" + }, + { + "name": "refuse_letter_zwsp_letter", + "codePoints": [ + "0061", + "200B", + "0062" + ], + "expect": "refuse", + "why": "a Default_Ignorable (U+200B) beside letters" + }, + { + "name": "refuse_letter_cgj", + "codePoints": [ + "0061", + "034F" + ], + "expect": "refuse", + "why": "a Default_Ignorable (U+034F) after a letter" + }, + { + "name": "refuse_letter_soft_hyphen", + "codePoints": [ + "0061", + "00AD" + ], + "expect": "refuse", + "why": "U+00AD is Default_Ignorable" + }, + { + "name": "refuse_letter_zwj", + "codePoints": [ + "0061", + "200D" + ], + "expect": "refuse", + "why": "U+200D is Default_Ignorable" + }, + { + "name": "refuse_digit_mongolian_vowel_separator", + "codePoints": [ + "0031", + "180E" + ], + "expect": "refuse", + "why": "U+180E is Default_Ignorable since Unicode 6.3" + }, + { + "name": "refuse_digit_word_joiner_digit", + "codePoints": [ + "0031", + "2060", + "0032" + ], + "expect": "refuse", + "why": "U+2060 is Default_Ignorable" + }, + { + "name": "accept_abc", + "codePoints": [ + "0061", + "0062", + "0063" + ], + "expect": "accept", + "why": "letters" + }, + { + "name": "accept_digit_1", + "codePoints": [ + "0031" + ], + "expect": "accept", + "why": "a number" + }, + { + "name": "accept_padded_a_raw_compare", + "codePoints": [ + "0020", + "0061", + "0020" + ], + "expect": "accept", + "why": "raw compare: the id is echoed byte for byte, never trimmed, so the padding stays and the letter makes it valid" + }, + { + "name": "accept_e_acute_precomposed", + "codePoints": [ + "00E9" + ], + "expect": "accept", + "why": "a letter" + }, + { + "name": "accept_arabic_indic_digit_three", + "codePoints": [ + "0663" + ], + "expect": "accept", + "why": "category Nd, a number" + }, + { + "name": "accept_cjk_zhong", + "codePoints": [ + "4E2D" + ], + "expect": "accept", + "why": "a letter (Lo, not Default_Ignorable)" + }, + { + "name": "accept_letter_then_combining_acute", + "codePoints": [ + "0065", + "0301" + ], + "expect": "accept", + "why": "a combining mark after a letter is allowed" + }, + { + "name": "accept_256_ascii_letters", + "codePoints": [ + "0061" + ], + "expect": "accept", + "why": "256 bytes is the size limit and is allowed", + "repeat": 256 + } + ] + }, "schemaInvalid": [ { "name": "write_admitted_with_an_error_key", @@ -1182,7 +1456,8 @@ "wire": 7, "schemaInvalid": 7, "contentHashCases": 4, - "scenarioCases": 25 + "scenarioCases": 25, + "admittedMessageIdCases": 32 }, "contentHash": { "definition": "sha256(msgpack([operation, schemaId, recordKey, payload])), payload a msgpack bin for a put and nil for a delete; hex lowercase", diff --git a/contracts/cultnet/interop/write-receipt.md b/contracts/cultnet/interop/write-receipt.md index dc138452..150c10f8 100644 --- a/contracts/cultnet/interop/write-receipt.md +++ b/contracts/cultnet/interop/write-receipt.md @@ -12,6 +12,8 @@ A put or delete today has two answers on the wire: nothing (admitted, or the ser **`cultnet.write_admitted.v0`**: `{ schemaVersion, messageId, operation: "put"|"delete", schemaId, recordKey }`, every key written, none nil. Sent once a put or delete is admitted. The id echoes the write's `messageId` byte for byte; `operation`, `schemaId`, `recordKey` echo the write. +**A `write_admitted` `messageId` is valid only if it contains at least one Unicode letter (`\p{L}`) or number (`\p{N}`) and no Default_Ignorable_Code_Point (DerivedCoreProperties).** The compare is raw: the id is echoed byte for byte, so a decoder never trims or normalizes it (`" a "` is valid; U+2800 is not, and neither is `a` followed by U+200B). Unlike the Required fields rule, this is not a White_Space test. A decoder refuses an invalid id the way it refuses an empty or ill-typed one; the rule is for `write_admitted` only and `error.v1` is unchanged. + **`cultnet.error.v1`**: `cultnet.error.v0` plus a required `messageId` (string or nil). The code enum is v0's plus `limit_reached` (see Remembered-write limits); `cultnet.error.v1.schema.json` is the authority for the closed set. Sent in place of `error.v0` to a receipt-capable connection. `messageId` is the id of the request the error answers; nil only when the frame was unreadable or carried no id. Why a new error version and not an extra key on v0: the v0 schema is closed (`additionalProperties: false`) and the TypeScript decoder (Ajv over the closed schemas) rejects unknown keys and unknown `schemaVersion`s, and `parseCultNetMessage` throws on an unknown version; Rust's decoder errors on an unknown version too. Unknown-key tolerance of the Rust, Python and Kotlin error decoders was not checked, so none is assumed, so a surprise key or frame breaks an old client's whole receive path. Old clients are therefore never sent either new frame. This reverses the "old clients ignore it" assumption, which is false for today's decoders. @@ -65,7 +67,7 @@ Those clients complete *successfully* when the window ends with no refusal. Unde ## Vectors -`write-receipt-vectors.json`: `constants` (the named windows and retention), `contentHash` (worked dedupe hashes), `dedupe` (server replay cases for rule 5), `wire` (MessagePack hex plus the decoded message for each new frame; the bytes are spec-authored with msgpack-python and every runtime's first conformance PR re-captures them with its own encoder and must match), `schemaInvalid` (frames that must be refused by a validator, each with why), and `blankFields` (server input cases for the Required fields rule: which document fields make a put unreadable, which are accepted), `scenarios` (event replays with the outcome each write must settle as; the clock is abstract; each case names its window constant). A conformant client passes every scenario; a conformant server's answers for the same inputs satisfy server rules 1 to 7. +`write-receipt-vectors.json`: `constants` (the named windows and retention), `contentHash` (worked dedupe hashes), `dedupe` (server replay cases for rule 5), `wire` (MessagePack hex plus the decoded message for each new frame; the bytes are spec-authored with msgpack-python and every runtime's first conformance PR re-captures them with its own encoder and must match), `schemaInvalid` (frames that must be refused by a validator, each with why), and `blankFields` (server input cases for the Required fields rule: which document fields make a put unreadable, which are accepted), `admittedMessageId` (the `write_admitted` id rule above: code-point lists, each marked refuse or accept; the lone surrogate U+D800 is carried as a whole frame in `wireHex`, because it is not valid UTF-8 and only some runtimes' strings can hold it), `scenarios` (event replays with the outcome each write must settle as; the clock is abstract; each case names its window constant). A conformant client passes every scenario; a conformant server's answers for the same inputs satisfy server rules 1 to 7. ## Conformance status diff --git a/packages/cultmesh-kotlin/src/main/kotlin/org/gamecult/cultmesh/WriteReceiptVectors.kt b/packages/cultmesh-kotlin/src/main/kotlin/org/gamecult/cultmesh/WriteReceiptVectors.kt index e527692d..8955c52d 100644 --- a/packages/cultmesh-kotlin/src/main/kotlin/org/gamecult/cultmesh/WriteReceiptVectors.kt +++ b/packages/cultmesh-kotlin/src/main/kotlin/org/gamecult/cultmesh/WriteReceiptVectors.kt @@ -48,6 +48,26 @@ fun replayWriteReceiptVectors(path: String): Boolean { } } + // admittedMessageId: a write_admitted messageId needs a letter or number and no Default_Ignorable code point, compared + // raw. The frame is built with this runtime's own encoder; a case with `wireHex` (the lone surrogate, invalid UTF-8) + // supplies the whole frame. + for (case in list(obj(vectors["admittedMessageId"])["cases"])) { + val c = obj(case) + check("admittedMessageId/${c["name"]}") { + val id = list(c["codePoints"]).joinToString("") { String(Character.toChars((it as String).toInt(16))) }.repeat(((c["repeat"] as Long?) ?: 1L).toInt()) + val bytes = (c["wireHex"] as String?)?.let(::unhex) ?: MessagePackWriter().value(linkedMapOf( + "schemaVersion" to WRITE_ADMITTED_VERSION, "messageId" to id, "operation" to "put", "schemaId" to "note.v0", "recordKey" to "note:1", + )).toByteArray() + val decoded = try { decodeReceiptFrame(parseCultNetMessage(bytes)) } catch (_: IOException) { null } + when { + c["expect"] == "refuse" -> if (decoded == null) emptyList() else listOf("accepted an id that must be refused (${c["why"]})") + decoded == null -> listOf("refused an id that must be accepted (${c["why"]})") + decoded.toWireMap()["messageId"] != id -> listOf("id not echoed raw: ${decoded.toWireMap()["messageId"]}") + else -> emptyList() + } + } + } + for (case in list(vectors["schemaInvalid"])) { val c = obj(case) check("schemaInvalid/${c["name"]}") { @@ -129,8 +149,9 @@ fun replayWriteReceiptVectors(path: String): Boolean { val loaded = mapOf( "wire" to list(vectors["wire"]).size, "schemaInvalid" to list(vectors["schemaInvalid"]).size, "contentHashCases" to list(obj(vectors["contentHash"])["cases"]).size, "scenarioCases" to list(obj(vectors["scenarios"])["cases"]).size, + "admittedMessageIdCases" to list(obj(vectors["admittedMessageId"])["cases"]).size, ) - val executed = mapOf("wire" to ran("wire/"), "schemaInvalid" to ran("schemaInvalid/"), "contentHashCases" to ran("contentHash/"), "scenarioCases" to ran("scenario/")) + val executed = mapOf("wire" to ran("wire/"), "schemaInvalid" to ran("schemaInvalid/"), "contentHashCases" to ran("contentHash/"), "scenarioCases" to ran("scenario/"), "admittedMessageIdCases" to ran("admittedMessageId/")) loaded.keys.filter { loaded[it] != (declared[it] as Long).toInt() || executed[it] != loaded[it] } .map { "$it: declared ${declared[it]}, loaded ${loaded[it]}, ran ${executed[it]}" } } diff --git a/packages/cultnet-py/tests/test_write_receipt.py b/packages/cultnet-py/tests/test_write_receipt.py index 98e74287..0c42a6af 100644 --- a/packages/cultnet-py/tests/test_write_receipt.py +++ b/packages/cultnet-py/tests/test_write_receipt.py @@ -57,7 +57,7 @@ class WriteReceiptVectorTests(unittest.TestCase): def test_vector_sections_are_the_ones_this_file_accounts_for(self) -> None: self.assertEqual( set(_VECTORS) - {"provenance", "specification"}, - {"constants", "contentHash", "dedupe", "wire", "blankFields", "schemaInvalid", "scenarios", "counts"}, + {"constants", "contentHash", "dedupe", "wire", "blankFields", "schemaInvalid", "scenarios", "counts", "admittedMessageId"}, ) self.assertEqual(len(_VECTORS["contentHash"]["cases"]), 4) self.assertEqual(len(_VECTORS["wire"]), 7) @@ -68,6 +68,31 @@ class WriteReceiptVectorTests(unittest.TestCase): self.assertEqual(counts["schemaInvalid"], len(_VECTORS["schemaInvalid"])) self.assertEqual(counts["contentHashCases"], len(_VECTORS["contentHash"]["cases"])) self.assertEqual(counts["scenarioCases"], len(_VECTORS["scenarios"]["cases"])) + self.assertEqual(counts["admittedMessageIdCases"], len(_VECTORS["admittedMessageId"]["cases"])) + + def test_admitted_message_id_vectors_are_refused_or_accepted_raw(self) -> None: + # `admittedMessageId`: a write_admitted messageId needs a letter or number and no Default_Ignorable code point, + # compared raw (never trimmed). A lone surrogate is not valid UTF-8, so its vector carries the whole frame. + cases = _VECTORS["admittedMessageId"]["cases"] + failures = [] + for case in cases: + raw = ( + bytes.fromhex(case["wireHex"]) + if "wireHex" in case + else _pack(_admitted(messageId="".join(chr(int(cp, 16)) for cp in case["codePoints"]) * case.get("repeat", 1))) + ) + try: + message = parse_message(raw) + except Exception: # noqa: BLE001 - any refusal counts + refused = True + else: + refused = False + wanted = "".join(chr(int(cp, 16)) for cp in case["codePoints"]) * case.get("repeat", 1) + if message.body["messageId"] != wanted: + failures.append(f"{case['name']}: id not echoed raw") + if refused != (case["expect"] == "refuse"): + failures.append(f"{case['name']}: expected {case['expect']}") + self.assertEqual(failures, []) def test_content_hash_matches_every_vector(self) -> None: for case in _VECTORS["contentHash"]["cases"]: diff --git a/packages/cultnet-rs/tests/write_receipt.rs b/packages/cultnet-rs/tests/write_receipt.rs index 3d233a61..8dccf996 100644 --- a/packages/cultnet-rs/tests/write_receipt.rs +++ b/packages/cultnet-rs/tests/write_receipt.rs @@ -153,3 +153,51 @@ fn error_v0_refuses_the_v1_only_limit_reached_code() { as_v0[at + 15] = b'0'; assert!(decode_cultnet_message_from_slice(&as_v0, CultNetWireContract::CultNetSchemaV0).is_err()); } + +/// Vectors `admittedMessageId`: a write_admitted messageId needs a letter or number and no Default_Ignorable code +/// point, compared raw (never trimmed). The frame is the spec's `write_admitted_put` bytes with the id swapped in; a +/// case with `wireHex` (the lone surrogate, invalid UTF-8) supplies the whole frame. +#[test] +fn write_admitted_message_id_vectors_are_refused_or_accepted_raw() { + let path = concat!(env!("CARGO_MANIFEST_DIR"), "/../../contracts/cultnet/interop/write-receipt-vectors.json"); + let vectors: serde_json::Value = serde_json::from_str(&std::fs::read_to_string(path).unwrap()).unwrap(); + let cases = vectors["admittedMessageId"]["cases"].as_array().unwrap(); + assert_eq!(vectors["counts"]["admittedMessageIdCases"].as_u64().unwrap() as usize, cases.len()); + let template = unhex("85AD736368656D6156657273696F6EB963756C746E65742E77726974655F61646D69747465642E7630A96D6573736167654964A57075742D31A96F7065726174696F6EA3707574A8736368656D614964A76E6F74652E7630A97265636F72644B6579A66E6F74653A31"); + let old_id_at = template.windows(5).position(|w| w == b"put-1").unwrap() - 1; + let mut failures = Vec::new(); + for case in cases { + let name = case["name"].as_str().unwrap(); + let id: String = case["codePoints"] + .as_array() + .unwrap() + .iter() + .filter_map(|cp| char::from_u32(u32::from_str_radix(cp.as_str().unwrap(), 16).unwrap())) + .collect::() + .repeat(case["repeat"].as_u64().unwrap_or(1) as usize); + let frame = match case["wireHex"].as_str() { + Some(hex) => unhex(hex), + None => { + let len = id.len(); + let header: Vec = if len < 32 { + vec![0xA0 | len as u8] + } else if len < 256 { + vec![0xD9, len as u8] + } else { + vec![0xDA, (len >> 8) as u8, len as u8] + }; + [&template[..old_id_at], &header[..], id.as_bytes(), &template[old_id_at + 6..]].concat() + } + }; + let decoded = decode_cultnet_message_from_slice(&frame, CultNetWireContract::CultNetSchemaV0); + match (case["expect"].as_str().unwrap(), decoded) { + ("refuse", Ok(_)) => failures.push(format!("{name}: expected refuse")), + ("accept", Err(error)) => failures.push(format!("{name}: expected accept, got {error}")), + ("accept", Ok(CultNetMessage::WriteAdmitted { message_id, .. })) if message_id != id => { + failures.push(format!("{name}: id not echoed raw")) + } + _ => {} + } + } + assert!(failures.is_empty(), "{} failing:\n{}", failures.len(), failures.join("\n")); +} diff --git a/packages/cultnet-ts/test/write-receipt.test.ts b/packages/cultnet-ts/test/write-receipt.test.ts index 6acb79cf..fbc8b6f0 100644 --- a/packages/cultnet-ts/test/write-receipt.test.ts +++ b/packages/cultnet-ts/test/write-receipt.test.ts @@ -280,3 +280,24 @@ test("a blank messageId, record key or tag is exactly Unicode White_Space, not t parseCultNetMessage(put({ tags: [named] })); } }); + +// `admittedMessageId` vectors: a write_admitted messageId needs a letter or number and no Default_Ignorable code point, +// compared raw (never trimmed). JavaScript strings hold a lone surrogate, so the D800 vector is built from its code point. +test("write_admitted messageId: every admittedMessageId vector is refused or accepted raw", () => { + const vectorsPath = join(__dirname, "..", "..", "..", "..", "contracts", "cultnet", "interop", "write-receipt-vectors.json"); + const vectors = JSON.parse(readFileSync(vectorsPath, "utf8")); + assert.equal(vectors.counts.admittedMessageIdCases, vectors.admittedMessageId.cases.length); + const failures: string[] = []; + for (const vector of vectors.admittedMessageId.cases as { name: string; codePoints: string[]; repeat?: number; expect: "refuse" | "accept" }[]) { + const id = vector.codePoints.map((cp) => String.fromCodePoint(parseInt(cp, 16))).join("").repeat(vector.repeat ?? 1); + let decodedId: string | undefined; + try { + decodedId = (parseCultNetMessage(admitted(id)) as CultNetWriteAdmittedMessage).messageId; + } catch { + decodedId = undefined; + } + if ((decodedId === undefined) !== (vector.expect === "refuse")) failures.push(`${vector.name}: expected ${vector.expect}`); + else if (decodedId !== undefined && decodedId !== id) failures.push(`${vector.name}: id not echoed raw`); + } + assert.deepEqual(failures, []); +}); diff --git a/tests/GameCult.Networking.Tests/CultNetWriteAdmittedMessageIdTests.cs b/tests/GameCult.Networking.Tests/CultNetWriteAdmittedMessageIdTests.cs new file mode 100644 index 00000000..4080f0d3 --- /dev/null +++ b/tests/GameCult.Networking.Tests/CultNetWriteAdmittedMessageIdTests.cs @@ -0,0 +1,58 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text.Json; +using NUnit.Framework; + +namespace GameCult.Networking.Tests +{ + // contracts/cultnet/interop/write-receipt-vectors.json `admittedMessageId`: a write_admitted messageId needs a letter or + // number and no Default_Ignorable code point, compared raw (never trimmed). The frame is built with this runtime's own + // encoder; the case with `wireHex` (a lone surrogate is invalid UTF-8) supplies the whole frame. + public sealed class CultNetWriteAdmittedMessageIdTests + { + private static JsonElement Vectors() + { + var directory = new DirectoryInfo(TestContext.CurrentContext.TestDirectory); + while (directory != null && !File.Exists(Path.Combine(directory.FullName, "CultLib.sln"))) + directory = directory.Parent; + var path = Path.Combine(directory!.FullName, "contracts", "cultnet", "interop", "write-receipt-vectors.json"); + return JsonDocument.Parse(File.ReadAllText(path)).RootElement.Clone(); + } + + public static IEnumerable Cases() => + Vectors().GetProperty("admittedMessageId").GetProperty("cases").EnumerateArray().Select(c => c.GetProperty("name").GetString()!); + + [Test] + public void TheFileDeclaresHowManyCasesThisRunnerMustRun() => + Assert.That(Cases().Count(), Is.EqualTo(Vectors().GetProperty("counts").GetProperty("admittedMessageIdCases").GetInt32())); + + [TestCaseSource(nameof(Cases))] + public void AWriteAdmittedMessageIdIsRefusedOrAcceptedRaw(string name) + { + var vector = Vectors().GetProperty("admittedMessageId").GetProperty("cases").EnumerateArray() + .Single(c => c.GetProperty("name").GetString() == name); + var id = string.Concat(Enumerable.Repeat( + string.Concat(vector.GetProperty("codePoints").EnumerateArray() + .Select(cp => char.ConvertFromUtf32(Convert.ToInt32(cp.GetString(), 16)))), + vector.TryGetProperty("repeat", out var repeat) ? repeat.GetInt32() : 1)); + var bytes = vector.TryGetProperty("wireHex", out var wire) + ? Convert.FromHexString(wire.GetString()!) + : CultNetSchemaMessageSerialization.Serialize(new CultNetWriteAdmittedMessage + { MessageId = id, Operation = "put", SchemaId = "note.v0", RecordKey = "note:1" }); + + CultNetWriteAdmittedMessage? decoded = null; + try { decoded = (CultNetWriteAdmittedMessage)CultNetSchemaMessageSerialization.Deserialize(bytes); } + catch (Exception) { /* a refusal */ } + + if (vector.GetProperty("expect").GetString() == "refuse") + Assert.That(decoded, Is.Null, vector.GetProperty("why").GetString()); + else + { + Assert.That(decoded, Is.Not.Null, vector.GetProperty("why").GetString()); + Assert.That(decoded!.MessageId, Is.EqualTo(id)); + } + } + } +} From 70f8135d2c713d33d203ecf0f6a7d41b2b59c674 Mon Sep 17 00:00:00 2001 From: soul Date: Mon, 9 Nov 2026 14:41:11 +0000 Subject: [PATCH 17/76] vector: write_admitted messageId accepts a NUL between letters (rule: letter present, no Default_Ignorable) Co-Authored-By: Claude Sonnet 5.5 --- contracts/cultnet/interop/write-receipt-vectors.json | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/contracts/cultnet/interop/write-receipt-vectors.json b/contracts/cultnet/interop/write-receipt-vectors.json index a37d9660..7fa2e2fa 100644 --- a/contracts/cultnet/interop/write-receipt-vectors.json +++ b/contracts/cultnet/interop/write-receipt-vectors.json @@ -412,6 +412,16 @@ "expect": "accept", "why": "a combining mark after a letter is allowed" }, + { + "name": "accept_letter_nul_letter", + "codePoints": [ + "0061", + "0000", + "0062" + ], + "expect": "accept", + "why": "U+0000 is not Default_Ignorable and a letter is present, so the rule accepts; raw compare keeps the NUL" + }, { "name": "accept_256_ascii_letters", "codePoints": [ @@ -1457,7 +1467,7 @@ "schemaInvalid": 7, "contentHashCases": 4, "scenarioCases": 25, - "admittedMessageIdCases": 32 + "admittedMessageIdCases": 33 }, "contentHash": { "definition": "sha256(msgpack([operation, schemaId, recordKey, payload])), payload a msgpack bin for a put and nil for a delete; hex lowercase", From 4c1537885dd61f13628651ecf4633bc97fc10095 Mon Sep 17 00:00:00 2001 From: soul Date: Mon, 9 Nov 2026 14:42:01 +0000 Subject: [PATCH 18/76] cultnet-ts: refuse a write_admitted messageId without a letter or number or with a Default_Ignorable code point Raw compare, no trim. The platform's regex property tables carry both properties; no table embedded. D800 vector now driven from wireHex. Co-Authored-By: Claude Sonnet 5.5 --- packages/cultnet-ts/src/contracts.ts | 13 +++++++++++++ packages/cultnet-ts/test/write-receipt.test.ts | 8 +++++--- 2 files changed, 18 insertions(+), 3 deletions(-) diff --git a/packages/cultnet-ts/src/contracts.ts b/packages/cultnet-ts/src/contracts.ts index 3cc87dcd..a05a26df 100644 --- a/packages/cultnet-ts/src/contracts.ts +++ b/packages/cultnet-ts/src/contracts.ts @@ -950,6 +950,10 @@ export function parseCultNetMessage(input: unknown, wireContract: CultNetWireCon return input as CultNetDatabaseChangeRawMessage; } + if (schemaVersion === "cultnet.write_admitted.v0" && !isValidAdmittedMessageId((input as { messageId?: unknown }).messageId)) { + throw new Error("Validation failed for cultnet.write_admitted.v0: /messageId: must contain a letter or number and no Default_Ignorable code point"); + } + if (!validator(input)) { const keys = Object.keys(input as Record).join(","); throw new Error(`${renderValidationErrors(schemaVersion, validator)}; keys=${keys}`); @@ -1460,6 +1464,15 @@ function validateRawDocumentRecord( requireOptionalStringArray(candidate.tags, `${instancePath}/tags`); } +/** + * A write_admitted messageId is valid iff it holds a Unicode letter or number and no Default_Ignorable_Code_Point, compared raw + * (write-receipt.md; the id is echoed byte for byte, so nothing is trimmed). The platform's regex tables carry both properties, + * so this runtime embeds no table; the Kotlin, Python, C# and Rust decoders embed DerivedCoreProperties ranges. + */ +function isValidAdmittedMessageId(value: unknown): boolean { + return typeof value === "string" && /[\p{L}\p{N}]/u.test(value) && !/\p{Default_Ignorable_Code_Point}/u.test(value); +} + /** Blank is empty or only Unicode White_Space (write-receipt.md, rule 5), not `trim()`: JS trims U+FEFF and keeps U+0085. */ function isBlank(value: string): boolean { return /^\p{White_Space}*$/u.test(value); diff --git a/packages/cultnet-ts/test/write-receipt.test.ts b/packages/cultnet-ts/test/write-receipt.test.ts index fbc8b6f0..c447367a 100644 --- a/packages/cultnet-ts/test/write-receipt.test.ts +++ b/packages/cultnet-ts/test/write-receipt.test.ts @@ -5,6 +5,7 @@ import { mkdtempSync, readFileSync, writeFileSync } from "node:fs"; import { tmpdir } from "node:os"; import { join } from "node:path"; +import { decode } from "@msgpack/msgpack"; import { CULTNET_ERROR_CODES, CULTNET_ERROR_V1_CODES, @@ -282,17 +283,18 @@ test("a blank messageId, record key or tag is exactly Unicode White_Space, not t }); // `admittedMessageId` vectors: a write_admitted messageId needs a letter or number and no Default_Ignorable code point, -// compared raw (never trimmed). JavaScript strings hold a lone surrogate, so the D800 vector is built from its code point. +// compared raw (never trimmed). The D800 vector is the frame's bytes (wireHex), decoded by the msgpack library like the others. test("write_admitted messageId: every admittedMessageId vector is refused or accepted raw", () => { const vectorsPath = join(__dirname, "..", "..", "..", "..", "contracts", "cultnet", "interop", "write-receipt-vectors.json"); const vectors = JSON.parse(readFileSync(vectorsPath, "utf8")); assert.equal(vectors.counts.admittedMessageIdCases, vectors.admittedMessageId.cases.length); const failures: string[] = []; - for (const vector of vectors.admittedMessageId.cases as { name: string; codePoints: string[]; repeat?: number; expect: "refuse" | "accept" }[]) { + for (const vector of vectors.admittedMessageId.cases as { name: string; codePoints: string[]; repeat?: number; wireHex?: string; expect: "refuse" | "accept" }[]) { const id = vector.codePoints.map((cp) => String.fromCodePoint(parseInt(cp, 16))).join("").repeat(vector.repeat ?? 1); let decodedId: string | undefined; try { - decodedId = (parseCultNetMessage(admitted(id)) as CultNetWriteAdmittedMessage).messageId; + const frame = vector.wireHex ? decode(Uint8Array.from(Buffer.from(vector.wireHex, "hex"))) : admitted(id); + decodedId = (parseCultNetMessage(frame) as CultNetWriteAdmittedMessage).messageId; } catch { decodedId = undefined; } From bf55c10199d98bf9180b2ca3a041a36409c44c8a Mon Sep 17 00:00:00 2001 From: soul Date: Mon, 9 Nov 2026 14:42:44 +0000 Subject: [PATCH 19/76] vector: write_admitted messageId refuses U+FEFF before a letter (kills a trim-before-check decoder) Co-Authored-By: Claude Sonnet 5.5 --- contracts/cultnet/interop/write-receipt-vectors.json | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/contracts/cultnet/interop/write-receipt-vectors.json b/contracts/cultnet/interop/write-receipt-vectors.json index 7fa2e2fa..2958990b 100644 --- a/contracts/cultnet/interop/write-receipt-vectors.json +++ b/contracts/cultnet/interop/write-receipt-vectors.json @@ -351,6 +351,15 @@ "expect": "refuse", "why": "U+2060 is Default_Ignorable" }, + { + "name": "refuse_bom_before_letter", + "codePoints": [ + "FEFF", + "0061" + ], + "expect": "refuse", + "why": "U+FEFF is Default_Ignorable; JavaScript trim() would strip it and accept the rest, but the compare is raw" + }, { "name": "accept_abc", "codePoints": [ @@ -1467,7 +1476,7 @@ "schemaInvalid": 7, "contentHashCases": 4, "scenarioCases": 25, - "admittedMessageIdCases": 33 + "admittedMessageIdCases": 34 }, "contentHash": { "definition": "sha256(msgpack([operation, schemaId, recordKey, payload])), payload a msgpack bin for a put and nil for a delete; hex lowercase", From 688562c3164d2b68dfda4037fad25c6371092c74 Mon Sep 17 00:00:00 2001 From: soul Date: Mon, 9 Nov 2026 14:43:26 +0000 Subject: [PATCH 20/76] cultnet-py: refuse a write_admitted messageId without a letter or number or with a Default_Ignorable code point Raw compare. unicodedata for L/N, an embedded Default_Ignorable range table (Unicode 16.0). Co-Authored-By: Claude Sonnet 5.5 --- .../cultnet-py/src/cultnet_py/messages.py | 28 +++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/packages/cultnet-py/src/cultnet_py/messages.py b/packages/cultnet-py/src/cultnet_py/messages.py index f2d89296..8ece7f6e 100644 --- a/packages/cultnet-py/src/cultnet_py/messages.py +++ b/packages/cultnet-py/src/cultnet_py/messages.py @@ -1,6 +1,7 @@ from __future__ import annotations import hashlib +import unicodedata from dataclasses import dataclass from typing import Any @@ -69,6 +70,31 @@ def _is_name(value: Any) -> bool: return isinstance(value, str) and value != "" +# Default_Ignorable_Code_Point ranges, DerivedCoreProperties.txt, Unicode 16.0 (unchanged since 15.1). This runtime has no +# platform table for the property, so it embeds it; the Kotlin, C# and Rust decoders embed the same ranges, TypeScript uses its +# regex engine's. Letters and numbers come from unicodedata (categories L* and N*); a code point the running Python's Unicode +# data does not assign (U+0378 in every version) is Cn, so it is refused either way. +_DEFAULT_IGNORABLE = ( + (0x00AD, 0x00AD), (0x034F, 0x034F), (0x061C, 0x061C), (0x115F, 0x1160), (0x17B4, 0x17B5), (0x180B, 0x180F), + (0x200B, 0x200F), (0x202A, 0x202E), (0x2060, 0x206F), (0x3164, 0x3164), (0xFE00, 0xFE0F), (0xFEFF, 0xFEFF), + (0xFFA0, 0xFFA0), (0xFFF0, 0xFFF8), (0x1BCA0, 0x1BCA3), (0x1D173, 0x1D17A), (0xE0000, 0xE0FFF), +) + + +def _is_admitted_message_id(value: Any) -> bool: + """write-receipt.md: a letter or number somewhere, no Default_Ignorable code point anywhere, compared raw (never stripped).""" + if not isinstance(value, str): + return False + has_letter_or_number = False + for ch in value: + point = ord(ch) + if any(low <= point <= high for low, high in _DEFAULT_IGNORABLE): + return False + if unicodedata.category(ch)[0] in ("L", "N"): + has_letter_or_number = True + return has_letter_or_number + + def _validate_write_admitted_body(body: dict[str, Any]) -> None: """Refuse a cultnet.write_admitted.v0 body that is not exactly the five keys of its closed schema.""" expected = ("messageId", "operation", "schemaId", "recordKey") @@ -77,6 +103,8 @@ def _validate_write_admitted_body(body: dict[str, Any]) -> None: for key in ("messageId", "schemaId", "recordKey"): if not _is_name(body[key]): raise ValueError(f"cultnet.write_admitted.v0 {key} must be a non-empty string") + if not _is_admitted_message_id(body["messageId"]): + raise ValueError("cultnet.write_admitted.v0 messageId must contain a letter or number and no Default_Ignorable code point") if body["operation"] not in ("put", "delete"): raise ValueError("cultnet.write_admitted.v0 operation must be put or delete") From d1a4ebdd6860defc13ebd7aa03fbf4ed5c2ff1fc Mon Sep 17 00:00:00 2001 From: soul Date: Mon, 9 Nov 2026 14:45:37 +0000 Subject: [PATCH 21/76] cultmesh-kotlin: refuse a write_admitted messageId without a letter or number or with a Default_Ignorable code point Raw compare. JDK Character categories for L/N, an embedded Default_Ignorable range table (Unicode 16.0). The MessagePack string reader now refuses invalid UTF-8 instead of substituting U+FFFD. Co-Authored-By: Claude Sonnet 5.5 --- .../kotlin/org/gamecult/cultmesh/CultMesh.kt | 5 +++- .../org/gamecult/cultmesh/WriteReceipt.kt | 27 ++++++++++++++++++- 2 files changed, 30 insertions(+), 2 deletions(-) diff --git a/packages/cultmesh-kotlin/src/main/kotlin/org/gamecult/cultmesh/CultMesh.kt b/packages/cultmesh-kotlin/src/main/kotlin/org/gamecult/cultmesh/CultMesh.kt index 4a06d7f5..2337448d 100644 --- a/packages/cultmesh-kotlin/src/main/kotlin/org/gamecult/cultmesh/CultMesh.kt +++ b/packages/cultmesh-kotlin/src/main/kotlin/org/gamecult/cultmesh/CultMesh.kt @@ -19,6 +19,7 @@ import java.net.Socket import java.net.SocketTimeoutException import java.net.URI import java.nio.ByteBuffer +import java.nio.charset.CodingErrorAction import java.nio.charset.StandardCharsets import java.security.SecureRandom import java.security.MessageDigest @@ -6306,7 +6307,9 @@ class MessagePackReader(payload: ByteArray) { code == 0xdb -> input.readInt() else -> throw IOException("expected string") } - return String(readPayload(length), StandardCharsets.UTF_8) + // Strict: bytes that are not UTF-8 (a lone surrogate's ED A0 80) refuse the frame instead of becoming U+FFFD. + return StandardCharsets.UTF_8.newDecoder().onMalformedInput(CodingErrorAction.REPORT).onUnmappableCharacter(CodingErrorAction.REPORT) + .decode(ByteBuffer.wrap(readPayload(length))).toString() } private fun readBinaryAfterCode(code: Int): ByteArray { diff --git a/packages/cultmesh-kotlin/src/main/kotlin/org/gamecult/cultmesh/WriteReceipt.kt b/packages/cultmesh-kotlin/src/main/kotlin/org/gamecult/cultmesh/WriteReceipt.kt index 4b38d942..523dfc2e 100644 --- a/packages/cultmesh-kotlin/src/main/kotlin/org/gamecult/cultmesh/WriteReceipt.kt +++ b/packages/cultmesh-kotlin/src/main/kotlin/org/gamecult/cultmesh/WriteReceipt.kt @@ -74,7 +74,32 @@ fun decodeWriteAdmitted(message: CultNetMessage): CultNetWriteAdmitted { closedKeys(message, setOf("messageId", "operation", "schemaId", "recordKey")) val operation = nonEmpty(message, "operation") if (operation != "put" && operation != "delete") throw IOException("operation must be put or delete") - return CultNetWriteAdmitted(nonEmpty(message, "messageId"), operation, nonEmpty(message, "schemaId"), nonEmpty(message, "recordKey")) + return CultNetWriteAdmitted(admittedMessageId(message), operation, nonEmpty(message, "schemaId"), nonEmpty(message, "recordKey")) +} + +// Default_Ignorable_Code_Point ranges, DerivedCoreProperties.txt, Unicode 16.0 (unchanged since 15.1). The JDK has no such +// property, so this runtime embeds it; the Python, C# and Rust decoders embed the same ranges, TypeScript uses its regex +// engine's. Letters and numbers come from the JDK's Character categories (L*, and Nd, Nl, No); a code point the running JDK +// does not assign (U+0378 in every version) has no such category, so it is refused either way. +private val DEFAULT_IGNORABLE: List = listOf( + 0x00AD..0x00AD, 0x034F..0x034F, 0x061C..0x061C, 0x115F..0x1160, 0x17B4..0x17B5, 0x180B..0x180F, + 0x200B..0x200F, 0x202A..0x202E, 0x2060..0x206F, 0x3164..0x3164, 0xFE00..0xFE0F, 0xFEFF..0xFEFF, + 0xFFA0..0xFFA0, 0xFFF0..0xFFF8, 0x1BCA0..0x1BCA3, 0x1D173..0x1D17A, 0xE0000..0xE0FFF, +) + +/** write-receipt.md: a letter or number somewhere, no Default_Ignorable code point anywhere, compared raw (never trimmed). */ +private fun admittedMessageId(message: CultNetMessage): String { + val id = nonEmpty(message, "messageId") + var hasLetterOrNumber = false + id.codePoints().forEach { cp -> + if (DEFAULT_IGNORABLE.any { cp in it }) throw IOException("messageId holds a Default_Ignorable code point") + when (Character.getType(cp).toByte()) { + Character.UPPERCASE_LETTER, Character.LOWERCASE_LETTER, Character.TITLECASE_LETTER, Character.MODIFIER_LETTER, + Character.OTHER_LETTER, Character.DECIMAL_DIGIT_NUMBER, Character.LETTER_NUMBER, Character.OTHER_NUMBER -> hasLetterOrNumber = true + } + } + if (!hasLetterOrNumber) throw IOException("messageId must contain a letter or number") + return id } /** Strict decode of error.v0 (no messageId, no limit_reached) or error.v1 (messageId required, string or nil, never empty). */ From d546ef9c7fb2dcdfc8ce1336a5ffd1277f333d66 Mon Sep 17 00:00:00 2001 From: soul Date: Mon, 9 Nov 2026 14:49:50 +0000 Subject: [PATCH 22/76] GameCult.Networking: refuse a write_admitted messageId without a letter or number, with a Default_Ignorable code point, or with invalid UTF-8 Raw compare. .NET Unicode categories for L/N, an embedded Default_Ignorable range table (Unicode 16.0). Co-Authored-By: Claude Sonnet 5.5 --- .../CultNetSchemaMessageSerialization.cs | 69 +++++++++++++++++++ .../CultNetWriteAdmittedMessageIdTests.cs | 2 +- 2 files changed, 70 insertions(+), 1 deletion(-) diff --git a/src/GameCult.Networking/CultNetSchemaMessageSerialization.cs b/src/GameCult.Networking/CultNetSchemaMessageSerialization.cs index cdcca912..9789e05e 100644 --- a/src/GameCult.Networking/CultNetSchemaMessageSerialization.cs +++ b/src/GameCult.Networking/CultNetSchemaMessageSerialization.cs @@ -1,4 +1,6 @@ using System; +using System.Buffers; +using System.Globalization; using MessagePack; namespace GameCult.Networking @@ -29,6 +31,7 @@ namespace GameCult.Networking public static T Deserialize(byte[] payload) where T : ICultNetSchemaMessage { if (payload == null) throw new ArgumentNullException(nameof(payload)); + if (typeof(T) == typeof(CultNetWriteAdmittedMessage)) CultNetAdmittedMessageId.Require(payload); return MessagePackSerializer.Deserialize(payload, Options); } @@ -98,4 +101,70 @@ namespace GameCult.Networking [Key("schemaVersion")] public string SchemaVersion { get; set; } = string.Empty; } } + + /// + /// The write_admitted messageId rule (write-receipt.md): a Unicode letter or number somewhere, no Default_Ignorable code + /// point anywhere, wire bytes that are valid UTF-8, compared raw (never trimmed). Letters and numbers come from the + /// runtime's Unicode categories (L*, Nd, Nl, No); a code point it does not assign (U+0378 in every version) has neither. + /// .NET has no Default_Ignorable property, so the ranges are embedded from DerivedCoreProperties.txt, Unicode 16.0 + /// (unchanged since 15.1); the Kotlin, Python and Rust decoders embed the same ranges, TypeScript uses its regex engine's. + /// + internal static class CultNetAdmittedMessageId + { + private static readonly (int Low, int High)[] DefaultIgnorable = + { + (0x00AD, 0x00AD), (0x034F, 0x034F), (0x061C, 0x061C), (0x115F, 0x1160), (0x17B4, 0x17B5), (0x180B, 0x180F), + (0x200B, 0x200F), (0x202A, 0x202E), (0x2060, 0x206F), (0x3164, 0x3164), (0xFE00, 0xFE0F), (0xFEFF, 0xFEFF), + (0xFFA0, 0xFFA0), (0xFFF0, 0xFFF8), (0x1BCA0, 0x1BCA3), (0x1D173, 0x1D17A), (0xE0000, 0xE0FFF), + }; + + private static readonly System.Text.UTF8Encoding StrictUtf8 = new(false, true); + + /// Throws unless the frame's top-level messageId is a valid write_admitted id; other keys are left to the serializer. + public static void Require(byte[] payload) + { + var reader = new MessagePackReader(payload); + var entries = reader.ReadMapHeader(); + for (var index = 0; index < entries; index++) + { + var key = reader.NextMessagePackType == MessagePackType.String ? reader.ReadString() : null; + if (key == "messageId" && reader.NextMessagePackType == MessagePackType.String) + { + string id; + try { id = StrictUtf8.GetString(reader.ReadStringSequence()!.Value.ToArray()); } + catch (System.Text.DecoderFallbackException) { throw new MessagePackSerializationException("cultnet.write_admitted.v0 messageId is not valid UTF-8."); } + if (!IsValid(id)) throw new MessagePackSerializationException("cultnet.write_admitted.v0 messageId must contain a letter or number and no Default_Ignorable code point."); + return; + } + + if (key == null) reader.Skip(); + reader.Skip(); + } + } + + public static bool IsValid(string id) + { + var hasLetterOrNumber = false; + for (var at = 0; at < id.Length; at += char.IsSurrogatePair(id, at) ? 2 : 1) + { + var point = char.IsSurrogatePair(id, at) ? char.ConvertToUtf32(id, at) : id[at]; + if (Array.Exists(DefaultIgnorable, range => point >= range.Low && point <= range.High)) return false; + switch (CharUnicodeInfo.GetUnicodeCategory(point)) + { + case UnicodeCategory.UppercaseLetter: + case UnicodeCategory.LowercaseLetter: + case UnicodeCategory.TitlecaseLetter: + case UnicodeCategory.ModifierLetter: + case UnicodeCategory.OtherLetter: + case UnicodeCategory.DecimalDigitNumber: + case UnicodeCategory.LetterNumber: + case UnicodeCategory.OtherNumber: + hasLetterOrNumber = true; + break; + } + } + + return hasLetterOrNumber; + } + } } diff --git a/tests/GameCult.Networking.Tests/CultNetWriteAdmittedMessageIdTests.cs b/tests/GameCult.Networking.Tests/CultNetWriteAdmittedMessageIdTests.cs index 4080f0d3..f5cac528 100644 --- a/tests/GameCult.Networking.Tests/CultNetWriteAdmittedMessageIdTests.cs +++ b/tests/GameCult.Networking.Tests/CultNetWriteAdmittedMessageIdTests.cs @@ -35,7 +35,7 @@ namespace GameCult.Networking.Tests .Single(c => c.GetProperty("name").GetString() == name); var id = string.Concat(Enumerable.Repeat( string.Concat(vector.GetProperty("codePoints").EnumerateArray() - .Select(cp => char.ConvertFromUtf32(Convert.ToInt32(cp.GetString(), 16)))), + .Select(cp => Convert.ToInt32(cp.GetString(), 16)).Where(point => point < 0xD800 || point > 0xDFFF).Select(char.ConvertFromUtf32)), vector.TryGetProperty("repeat", out var repeat) ? repeat.GetInt32() : 1)); var bytes = vector.TryGetProperty("wireHex", out var wire) ? Convert.FromHexString(wire.GetString()!) From ba548d192df76bb533ec8bcdb119088693a1381e Mon Sep 17 00:00:00 2001 From: soul Date: Mon, 9 Nov 2026 14:49:50 +0000 Subject: [PATCH 23/76] cultnet-rs: refuse a write_admitted messageId without a letter or number or with a Default_Ignorable code point Raw compare. std is_numeric for N; embedded letter (Unicode 15.1) and Default_Ignorable (16.0) range tables. Co-Authored-By: Claude Sonnet 5.5 --- packages/cultnet-rs/src/contracts.rs | 162 ++++++++++++++++++++++++++- 1 file changed, 161 insertions(+), 1 deletion(-) diff --git a/packages/cultnet-rs/src/contracts.rs b/packages/cultnet-rs/src/contracts.rs index 0ea2aa53..201632a2 100644 --- a/packages/cultnet-rs/src/contracts.rs +++ b/packages/cultnet-rs/src/contracts.rs @@ -680,7 +680,7 @@ fn validate_message(message: &CultNetMessage) -> Result<()> { } } CultNetMessage::WriteAdmitted { message_id, operation, schema_id, record_key } => { - require_non_empty(message_id, "messageId")?; + require_admitted_message_id(message_id)?; if operation != "put" && operation != "delete" { return Err(anyhow!("operation must be put or delete")); } @@ -934,6 +934,166 @@ fn require_non_empty(value: &str, field: &str) -> Result<()> { Ok(()) } +// write-receipt.md: a write_admitted messageId is valid iff it holds a Unicode letter (\p{L}) or number (\p{N}) and no +// Default_Ignorable_Code_Point, compared raw (never trimmed). std has `char::is_numeric` (N) but no letter category +// (`is_alphabetic` is the Alphabetic property, which also takes combining marks), and no Default_Ignorable, so both tables +// are embedded: DerivedCoreProperties.txt ranges, Unicode 16.0 (unchanged since 15.1), and the general category L ranges of +// Unicode 15.1 (U+0378 is unassigned in every version, so it is refused either way). The Kotlin, Python and C# decoders embed +// the same Default_Ignorable ranges and take L/N from their platform; TypeScript uses its regex engine's tables. +// Invalid UTF-8 never reaches this check: the MessagePack str decode refuses it. +const DEFAULT_IGNORABLE: &[(u32, u32)] = &[ + (0x00AD, 0x00AD), (0x034F, 0x034F), (0x061C, 0x061C), (0x115F, 0x1160), (0x17B4, 0x17B5), (0x180B, 0x180F), + (0x200B, 0x200F), (0x202A, 0x202E), (0x2060, 0x206F), (0x3164, 0x3164), (0xFE00, 0xFE0F), (0xFEFF, 0xFEFF), + (0xFFA0, 0xFFA0), (0xFFF0, 0xFFF8), (0x1BCA0, 0x1BCA3), (0x1D173, 0x1D17A), (0xE0000, 0xE0FFF), +]; + +const LETTERS: &[(u32, u32)] = &[ + (0x41, 0x5A), (0x61, 0x7A), (0xAA, 0xAA), (0xB5, 0xB5), (0xBA, 0xBA), (0xC0, 0xD6), (0xD8, 0xF6), (0xF8, 0x2C1), + (0x2C6, 0x2D1), (0x2E0, 0x2E4), (0x2EC, 0x2EC), (0x2EE, 0x2EE), (0x370, 0x374), (0x376, 0x377), (0x37A, 0x37D), + (0x37F, 0x37F), (0x386, 0x386), (0x388, 0x38A), (0x38C, 0x38C), (0x38E, 0x3A1), (0x3A3, 0x3F5), (0x3F7, 0x481), + (0x48A, 0x52F), (0x531, 0x556), (0x559, 0x559), (0x560, 0x588), (0x5D0, 0x5EA), (0x5EF, 0x5F2), (0x620, 0x64A), + (0x66E, 0x66F), (0x671, 0x6D3), (0x6D5, 0x6D5), (0x6E5, 0x6E6), (0x6EE, 0x6EF), (0x6FA, 0x6FC), (0x6FF, 0x6FF), + (0x710, 0x710), (0x712, 0x72F), (0x74D, 0x7A5), (0x7B1, 0x7B1), (0x7CA, 0x7EA), (0x7F4, 0x7F5), (0x7FA, 0x7FA), + (0x800, 0x815), (0x81A, 0x81A), (0x824, 0x824), (0x828, 0x828), (0x840, 0x858), (0x860, 0x86A), (0x870, 0x887), + (0x889, 0x88E), (0x8A0, 0x8C9), (0x904, 0x939), (0x93D, 0x93D), (0x950, 0x950), (0x958, 0x961), (0x971, 0x980), + (0x985, 0x98C), (0x98F, 0x990), (0x993, 0x9A8), (0x9AA, 0x9B0), (0x9B2, 0x9B2), (0x9B6, 0x9B9), (0x9BD, 0x9BD), + (0x9CE, 0x9CE), (0x9DC, 0x9DD), (0x9DF, 0x9E1), (0x9F0, 0x9F1), (0x9FC, 0x9FC), (0xA05, 0xA0A), (0xA0F, 0xA10), + (0xA13, 0xA28), (0xA2A, 0xA30), (0xA32, 0xA33), (0xA35, 0xA36), (0xA38, 0xA39), (0xA59, 0xA5C), (0xA5E, 0xA5E), + (0xA72, 0xA74), (0xA85, 0xA8D), (0xA8F, 0xA91), (0xA93, 0xAA8), (0xAAA, 0xAB0), (0xAB2, 0xAB3), (0xAB5, 0xAB9), + (0xABD, 0xABD), (0xAD0, 0xAD0), (0xAE0, 0xAE1), (0xAF9, 0xAF9), (0xB05, 0xB0C), (0xB0F, 0xB10), (0xB13, 0xB28), + (0xB2A, 0xB30), (0xB32, 0xB33), (0xB35, 0xB39), (0xB3D, 0xB3D), (0xB5C, 0xB5D), (0xB5F, 0xB61), (0xB71, 0xB71), + (0xB83, 0xB83), (0xB85, 0xB8A), (0xB8E, 0xB90), (0xB92, 0xB95), (0xB99, 0xB9A), (0xB9C, 0xB9C), (0xB9E, 0xB9F), + (0xBA3, 0xBA4), (0xBA8, 0xBAA), (0xBAE, 0xBB9), (0xBD0, 0xBD0), (0xC05, 0xC0C), (0xC0E, 0xC10), (0xC12, 0xC28), + (0xC2A, 0xC39), (0xC3D, 0xC3D), (0xC58, 0xC5A), (0xC5D, 0xC5D), (0xC60, 0xC61), (0xC80, 0xC80), (0xC85, 0xC8C), + (0xC8E, 0xC90), (0xC92, 0xCA8), (0xCAA, 0xCB3), (0xCB5, 0xCB9), (0xCBD, 0xCBD), (0xCDD, 0xCDE), (0xCE0, 0xCE1), + (0xCF1, 0xCF2), (0xD04, 0xD0C), (0xD0E, 0xD10), (0xD12, 0xD3A), (0xD3D, 0xD3D), (0xD4E, 0xD4E), (0xD54, 0xD56), + (0xD5F, 0xD61), (0xD7A, 0xD7F), (0xD85, 0xD96), (0xD9A, 0xDB1), (0xDB3, 0xDBB), (0xDBD, 0xDBD), (0xDC0, 0xDC6), + (0xE01, 0xE30), (0xE32, 0xE33), (0xE40, 0xE46), (0xE81, 0xE82), (0xE84, 0xE84), (0xE86, 0xE8A), (0xE8C, 0xEA3), + (0xEA5, 0xEA5), (0xEA7, 0xEB0), (0xEB2, 0xEB3), (0xEBD, 0xEBD), (0xEC0, 0xEC4), (0xEC6, 0xEC6), (0xEDC, 0xEDF), + (0xF00, 0xF00), (0xF40, 0xF47), (0xF49, 0xF6C), (0xF88, 0xF8C), (0x1000, 0x102A), (0x103F, 0x103F), + (0x1050, 0x1055), (0x105A, 0x105D), (0x1061, 0x1061), (0x1065, 0x1066), (0x106E, 0x1070), (0x1075, 0x1081), + (0x108E, 0x108E), (0x10A0, 0x10C5), (0x10C7, 0x10C7), (0x10CD, 0x10CD), (0x10D0, 0x10FA), (0x10FC, 0x1248), + (0x124A, 0x124D), (0x1250, 0x1256), (0x1258, 0x1258), (0x125A, 0x125D), (0x1260, 0x1288), (0x128A, 0x128D), + (0x1290, 0x12B0), (0x12B2, 0x12B5), (0x12B8, 0x12BE), (0x12C0, 0x12C0), (0x12C2, 0x12C5), (0x12C8, 0x12D6), + (0x12D8, 0x1310), (0x1312, 0x1315), (0x1318, 0x135A), (0x1380, 0x138F), (0x13A0, 0x13F5), (0x13F8, 0x13FD), + (0x1401, 0x166C), (0x166F, 0x167F), (0x1681, 0x169A), (0x16A0, 0x16EA), (0x16F1, 0x16F8), (0x1700, 0x1711), + (0x171F, 0x1731), (0x1740, 0x1751), (0x1760, 0x176C), (0x176E, 0x1770), (0x1780, 0x17B3), (0x17D7, 0x17D7), + (0x17DC, 0x17DC), (0x1820, 0x1878), (0x1880, 0x1884), (0x1887, 0x18A8), (0x18AA, 0x18AA), (0x18B0, 0x18F5), + (0x1900, 0x191E), (0x1950, 0x196D), (0x1970, 0x1974), (0x1980, 0x19AB), (0x19B0, 0x19C9), (0x1A00, 0x1A16), + (0x1A20, 0x1A54), (0x1AA7, 0x1AA7), (0x1B05, 0x1B33), (0x1B45, 0x1B4C), (0x1B83, 0x1BA0), (0x1BAE, 0x1BAF), + (0x1BBA, 0x1BE5), (0x1C00, 0x1C23), (0x1C4D, 0x1C4F), (0x1C5A, 0x1C7D), (0x1C80, 0x1C88), (0x1C90, 0x1CBA), + (0x1CBD, 0x1CBF), (0x1CE9, 0x1CEC), (0x1CEE, 0x1CF3), (0x1CF5, 0x1CF6), (0x1CFA, 0x1CFA), (0x1D00, 0x1DBF), + (0x1E00, 0x1F15), (0x1F18, 0x1F1D), (0x1F20, 0x1F45), (0x1F48, 0x1F4D), (0x1F50, 0x1F57), (0x1F59, 0x1F59), + (0x1F5B, 0x1F5B), (0x1F5D, 0x1F5D), (0x1F5F, 0x1F7D), (0x1F80, 0x1FB4), (0x1FB6, 0x1FBC), (0x1FBE, 0x1FBE), + (0x1FC2, 0x1FC4), (0x1FC6, 0x1FCC), (0x1FD0, 0x1FD3), (0x1FD6, 0x1FDB), (0x1FE0, 0x1FEC), (0x1FF2, 0x1FF4), + (0x1FF6, 0x1FFC), (0x2071, 0x2071), (0x207F, 0x207F), (0x2090, 0x209C), (0x2102, 0x2102), (0x2107, 0x2107), + (0x210A, 0x2113), (0x2115, 0x2115), (0x2119, 0x211D), (0x2124, 0x2124), (0x2126, 0x2126), (0x2128, 0x2128), + (0x212A, 0x212D), (0x212F, 0x2139), (0x213C, 0x213F), (0x2145, 0x2149), (0x214E, 0x214E), (0x2183, 0x2184), + (0x2C00, 0x2CE4), (0x2CEB, 0x2CEE), (0x2CF2, 0x2CF3), (0x2D00, 0x2D25), (0x2D27, 0x2D27), (0x2D2D, 0x2D2D), + (0x2D30, 0x2D67), (0x2D6F, 0x2D6F), (0x2D80, 0x2D96), (0x2DA0, 0x2DA6), (0x2DA8, 0x2DAE), (0x2DB0, 0x2DB6), + (0x2DB8, 0x2DBE), (0x2DC0, 0x2DC6), (0x2DC8, 0x2DCE), (0x2DD0, 0x2DD6), (0x2DD8, 0x2DDE), (0x2E2F, 0x2E2F), + (0x3005, 0x3006), (0x3031, 0x3035), (0x303B, 0x303C), (0x3041, 0x3096), (0x309D, 0x309F), (0x30A1, 0x30FA), + (0x30FC, 0x30FF), (0x3105, 0x312F), (0x3131, 0x318E), (0x31A0, 0x31BF), (0x31F0, 0x31FF), (0x3400, 0x4DBF), + (0x4E00, 0xA48C), (0xA4D0, 0xA4FD), (0xA500, 0xA60C), (0xA610, 0xA61F), (0xA62A, 0xA62B), (0xA640, 0xA66E), + (0xA67F, 0xA69D), (0xA6A0, 0xA6E5), (0xA717, 0xA71F), (0xA722, 0xA788), (0xA78B, 0xA7CA), (0xA7D0, 0xA7D1), + (0xA7D3, 0xA7D3), (0xA7D5, 0xA7D9), (0xA7F2, 0xA801), (0xA803, 0xA805), (0xA807, 0xA80A), (0xA80C, 0xA822), + (0xA840, 0xA873), (0xA882, 0xA8B3), (0xA8F2, 0xA8F7), (0xA8FB, 0xA8FB), (0xA8FD, 0xA8FE), (0xA90A, 0xA925), + (0xA930, 0xA946), (0xA960, 0xA97C), (0xA984, 0xA9B2), (0xA9CF, 0xA9CF), (0xA9E0, 0xA9E4), (0xA9E6, 0xA9EF), + (0xA9FA, 0xA9FE), (0xAA00, 0xAA28), (0xAA40, 0xAA42), (0xAA44, 0xAA4B), (0xAA60, 0xAA76), (0xAA7A, 0xAA7A), + (0xAA7E, 0xAAAF), (0xAAB1, 0xAAB1), (0xAAB5, 0xAAB6), (0xAAB9, 0xAABD), (0xAAC0, 0xAAC0), (0xAAC2, 0xAAC2), + (0xAADB, 0xAADD), (0xAAE0, 0xAAEA), (0xAAF2, 0xAAF4), (0xAB01, 0xAB06), (0xAB09, 0xAB0E), (0xAB11, 0xAB16), + (0xAB20, 0xAB26), (0xAB28, 0xAB2E), (0xAB30, 0xAB5A), (0xAB5C, 0xAB69), (0xAB70, 0xABE2), (0xAC00, 0xD7A3), + (0xD7B0, 0xD7C6), (0xD7CB, 0xD7FB), (0xF900, 0xFA6D), (0xFA70, 0xFAD9), (0xFB00, 0xFB06), (0xFB13, 0xFB17), + (0xFB1D, 0xFB1D), (0xFB1F, 0xFB28), (0xFB2A, 0xFB36), (0xFB38, 0xFB3C), (0xFB3E, 0xFB3E), (0xFB40, 0xFB41), + (0xFB43, 0xFB44), (0xFB46, 0xFBB1), (0xFBD3, 0xFD3D), (0xFD50, 0xFD8F), (0xFD92, 0xFDC7), (0xFDF0, 0xFDFB), + (0xFE70, 0xFE74), (0xFE76, 0xFEFC), (0xFF21, 0xFF3A), (0xFF41, 0xFF5A), (0xFF66, 0xFFBE), (0xFFC2, 0xFFC7), + (0xFFCA, 0xFFCF), (0xFFD2, 0xFFD7), (0xFFDA, 0xFFDC), (0x10000, 0x1000B), (0x1000D, 0x10026), (0x10028, 0x1003A), + (0x1003C, 0x1003D), (0x1003F, 0x1004D), (0x10050, 0x1005D), (0x10080, 0x100FA), (0x10280, 0x1029C), + (0x102A0, 0x102D0), (0x10300, 0x1031F), (0x1032D, 0x10340), (0x10342, 0x10349), (0x10350, 0x10375), + (0x10380, 0x1039D), (0x103A0, 0x103C3), (0x103C8, 0x103CF), (0x10400, 0x1049D), (0x104B0, 0x104D3), + (0x104D8, 0x104FB), (0x10500, 0x10527), (0x10530, 0x10563), (0x10570, 0x1057A), (0x1057C, 0x1058A), + (0x1058C, 0x10592), (0x10594, 0x10595), (0x10597, 0x105A1), (0x105A3, 0x105B1), (0x105B3, 0x105B9), + (0x105BB, 0x105BC), (0x10600, 0x10736), (0x10740, 0x10755), (0x10760, 0x10767), (0x10780, 0x10785), + (0x10787, 0x107B0), (0x107B2, 0x107BA), (0x10800, 0x10805), (0x10808, 0x10808), (0x1080A, 0x10835), + (0x10837, 0x10838), (0x1083C, 0x1083C), (0x1083F, 0x10855), (0x10860, 0x10876), (0x10880, 0x1089E), + (0x108E0, 0x108F2), (0x108F4, 0x108F5), (0x10900, 0x10915), (0x10920, 0x10939), (0x10980, 0x109B7), + (0x109BE, 0x109BF), (0x10A00, 0x10A00), (0x10A10, 0x10A13), (0x10A15, 0x10A17), (0x10A19, 0x10A35), + (0x10A60, 0x10A7C), (0x10A80, 0x10A9C), (0x10AC0, 0x10AC7), (0x10AC9, 0x10AE4), (0x10B00, 0x10B35), + (0x10B40, 0x10B55), (0x10B60, 0x10B72), (0x10B80, 0x10B91), (0x10C00, 0x10C48), (0x10C80, 0x10CB2), + (0x10CC0, 0x10CF2), (0x10D00, 0x10D23), (0x10E80, 0x10EA9), (0x10EB0, 0x10EB1), (0x10F00, 0x10F1C), + (0x10F27, 0x10F27), (0x10F30, 0x10F45), (0x10F70, 0x10F81), (0x10FB0, 0x10FC4), (0x10FE0, 0x10FF6), + (0x11003, 0x11037), (0x11071, 0x11072), (0x11075, 0x11075), (0x11083, 0x110AF), (0x110D0, 0x110E8), + (0x11103, 0x11126), (0x11144, 0x11144), (0x11147, 0x11147), (0x11150, 0x11172), (0x11176, 0x11176), + (0x11183, 0x111B2), (0x111C1, 0x111C4), (0x111DA, 0x111DA), (0x111DC, 0x111DC), (0x11200, 0x11211), + (0x11213, 0x1122B), (0x1123F, 0x11240), (0x11280, 0x11286), (0x11288, 0x11288), (0x1128A, 0x1128D), + (0x1128F, 0x1129D), (0x1129F, 0x112A8), (0x112B0, 0x112DE), (0x11305, 0x1130C), (0x1130F, 0x11310), + (0x11313, 0x11328), (0x1132A, 0x11330), (0x11332, 0x11333), (0x11335, 0x11339), (0x1133D, 0x1133D), + (0x11350, 0x11350), (0x1135D, 0x11361), (0x11400, 0x11434), (0x11447, 0x1144A), (0x1145F, 0x11461), + (0x11480, 0x114AF), (0x114C4, 0x114C5), (0x114C7, 0x114C7), (0x11580, 0x115AE), (0x115D8, 0x115DB), + (0x11600, 0x1162F), (0x11644, 0x11644), (0x11680, 0x116AA), (0x116B8, 0x116B8), (0x11700, 0x1171A), + (0x11740, 0x11746), (0x11800, 0x1182B), (0x118A0, 0x118DF), (0x118FF, 0x11906), (0x11909, 0x11909), + (0x1190C, 0x11913), (0x11915, 0x11916), (0x11918, 0x1192F), (0x1193F, 0x1193F), (0x11941, 0x11941), + (0x119A0, 0x119A7), (0x119AA, 0x119D0), (0x119E1, 0x119E1), (0x119E3, 0x119E3), (0x11A00, 0x11A00), + (0x11A0B, 0x11A32), (0x11A3A, 0x11A3A), (0x11A50, 0x11A50), (0x11A5C, 0x11A89), (0x11A9D, 0x11A9D), + (0x11AB0, 0x11AF8), (0x11C00, 0x11C08), (0x11C0A, 0x11C2E), (0x11C40, 0x11C40), (0x11C72, 0x11C8F), + (0x11D00, 0x11D06), (0x11D08, 0x11D09), (0x11D0B, 0x11D30), (0x11D46, 0x11D46), (0x11D60, 0x11D65), + (0x11D67, 0x11D68), (0x11D6A, 0x11D89), (0x11D98, 0x11D98), (0x11EE0, 0x11EF2), (0x11F02, 0x11F02), + (0x11F04, 0x11F10), (0x11F12, 0x11F33), (0x11FB0, 0x11FB0), (0x12000, 0x12399), (0x12480, 0x12543), + (0x12F90, 0x12FF0), (0x13000, 0x1342F), (0x13441, 0x13446), (0x14400, 0x14646), (0x16800, 0x16A38), + (0x16A40, 0x16A5E), (0x16A70, 0x16ABE), (0x16AD0, 0x16AED), (0x16B00, 0x16B2F), (0x16B40, 0x16B43), + (0x16B63, 0x16B77), (0x16B7D, 0x16B8F), (0x16E40, 0x16E7F), (0x16F00, 0x16F4A), (0x16F50, 0x16F50), + (0x16F93, 0x16F9F), (0x16FE0, 0x16FE1), (0x16FE3, 0x16FE3), (0x17000, 0x187F7), (0x18800, 0x18CD5), + (0x18D00, 0x18D08), (0x1AFF0, 0x1AFF3), (0x1AFF5, 0x1AFFB), (0x1AFFD, 0x1AFFE), (0x1B000, 0x1B122), + (0x1B132, 0x1B132), (0x1B150, 0x1B152), (0x1B155, 0x1B155), (0x1B164, 0x1B167), (0x1B170, 0x1B2FB), + (0x1BC00, 0x1BC6A), (0x1BC70, 0x1BC7C), (0x1BC80, 0x1BC88), (0x1BC90, 0x1BC99), (0x1D400, 0x1D454), + (0x1D456, 0x1D49C), (0x1D49E, 0x1D49F), (0x1D4A2, 0x1D4A2), (0x1D4A5, 0x1D4A6), (0x1D4A9, 0x1D4AC), + (0x1D4AE, 0x1D4B9), (0x1D4BB, 0x1D4BB), (0x1D4BD, 0x1D4C3), (0x1D4C5, 0x1D505), (0x1D507, 0x1D50A), + (0x1D50D, 0x1D514), (0x1D516, 0x1D51C), (0x1D51E, 0x1D539), (0x1D53B, 0x1D53E), (0x1D540, 0x1D544), + (0x1D546, 0x1D546), (0x1D54A, 0x1D550), (0x1D552, 0x1D6A5), (0x1D6A8, 0x1D6C0), (0x1D6C2, 0x1D6DA), + (0x1D6DC, 0x1D6FA), (0x1D6FC, 0x1D714), (0x1D716, 0x1D734), (0x1D736, 0x1D74E), (0x1D750, 0x1D76E), + (0x1D770, 0x1D788), (0x1D78A, 0x1D7A8), (0x1D7AA, 0x1D7C2), (0x1D7C4, 0x1D7CB), (0x1DF00, 0x1DF1E), + (0x1DF25, 0x1DF2A), (0x1E030, 0x1E06D), (0x1E100, 0x1E12C), (0x1E137, 0x1E13D), (0x1E14E, 0x1E14E), + (0x1E290, 0x1E2AD), (0x1E2C0, 0x1E2EB), (0x1E4D0, 0x1E4EB), (0x1E7E0, 0x1E7E6), (0x1E7E8, 0x1E7EB), + (0x1E7ED, 0x1E7EE), (0x1E7F0, 0x1E7FE), (0x1E800, 0x1E8C4), (0x1E900, 0x1E943), (0x1E94B, 0x1E94B), + (0x1EE00, 0x1EE03), (0x1EE05, 0x1EE1F), (0x1EE21, 0x1EE22), (0x1EE24, 0x1EE24), (0x1EE27, 0x1EE27), + (0x1EE29, 0x1EE32), (0x1EE34, 0x1EE37), (0x1EE39, 0x1EE39), (0x1EE3B, 0x1EE3B), (0x1EE42, 0x1EE42), + (0x1EE47, 0x1EE47), (0x1EE49, 0x1EE49), (0x1EE4B, 0x1EE4B), (0x1EE4D, 0x1EE4F), (0x1EE51, 0x1EE52), + (0x1EE54, 0x1EE54), (0x1EE57, 0x1EE57), (0x1EE59, 0x1EE59), (0x1EE5B, 0x1EE5B), (0x1EE5D, 0x1EE5D), + (0x1EE5F, 0x1EE5F), (0x1EE61, 0x1EE62), (0x1EE64, 0x1EE64), (0x1EE67, 0x1EE6A), (0x1EE6C, 0x1EE72), + (0x1EE74, 0x1EE77), (0x1EE79, 0x1EE7C), (0x1EE7E, 0x1EE7E), (0x1EE80, 0x1EE89), (0x1EE8B, 0x1EE9B), + (0x1EEA1, 0x1EEA3), (0x1EEA5, 0x1EEA9), (0x1EEAB, 0x1EEBB), (0x20000, 0x2A6DF), (0x2A700, 0x2B739), + (0x2B740, 0x2B81D), (0x2B820, 0x2CEA1), (0x2CEB0, 0x2EBE0), (0x2EBF0, 0x2EE5D), (0x2F800, 0x2FA1D), + (0x30000, 0x3134A), (0x31350, 0x323AF), +]; + +fn in_ranges(ranges: &[(u32, u32)], point: u32) -> bool { + ranges.binary_search_by(|&(low, high)| { + if point < low { + std::cmp::Ordering::Greater + } else if point > high { + std::cmp::Ordering::Less + } else { + std::cmp::Ordering::Equal + } + }) + .is_ok() +} + +fn require_admitted_message_id(value: &str) -> Result<()> { + let mut has_letter_or_number = false; + for ch in value.chars() { + let point = ch as u32; + if in_ranges(DEFAULT_IGNORABLE, point) { + return Err(anyhow!("CultNet field messageId must not hold a Default_Ignorable code point")); + } + has_letter_or_number |= ch.is_numeric() || in_ranges(LETTERS, point); + } + if !has_letter_or_number { + return Err(anyhow!("CultNet field messageId must contain a letter or number")); + } + Ok(()) +} + fn require_exact(value: &str, expected: &str, field: &str) -> Result<()> { if value != expected { return Err(anyhow!("{field} must be {expected}")); From cc931932fc0bf32adaecac74345db84edf4314e1 Mon Sep 17 00:00:00 2001 From: soul Date: Mon, 9 Nov 2026 15:00:54 +0000 Subject: [PATCH 24/76] tests first: Unicode 16.0 pin vectors (failing) Failing before the fix: Rust 1 (16100), Python 2 (16100, 10D40), Kotlin 4 (16100, 10D40, 2EBF0, 2EE5D). TypeScript and C# already pass: their platform data is 16.0 or later. Co-Authored-By: Claude Sonnet 5.5 --- .../interop/write-receipt-vectors.json | 52 ++++++++++++++++++- 1 file changed, 50 insertions(+), 2 deletions(-) diff --git a/contracts/cultnet/interop/write-receipt-vectors.json b/contracts/cultnet/interop/write-receipt-vectors.json index 2958990b..c7f47738 100644 --- a/contracts/cultnet/interop/write-receipt-vectors.json +++ b/contracts/cultnet/interop/write-receipt-vectors.json @@ -150,7 +150,7 @@ ] }, "admittedMessageId": { - "semantics": "Decoder-side rule for a `cultnet.write_admitted.v0` frame's `messageId` (Heimdall's ruling, write-receipt.md). Valid only if it contains at least one Unicode letter (\\p{L}) or number (\\p{N}) AND no Default_Ignorable_Code_Point (DerivedCoreProperties). The compare is RAW: the id is echoed byte for byte, so a decoder neither trims nor normalizes. Each case's id is given as `codePoints` (hex, in order), repeated `repeat` times (default 1), so no lone surrogate appears in this file. A runner builds the frame {schemaVersion write_admitted.v0, messageId, operation put, schemaId note.v0, recordKey note:1} with its own encoder and decodes it with the write_admitted decoder; `refuse` means the decoder refuses the frame the way it refuses an empty or ill-typed messageId, `accept` means it decodes and the id comes back identical. A case with `wireHex` carries the whole frame in MessagePack hex, for an id a language string cannot hold: the lone surrogate U+D800 is not valid UTF-8, so the vector's messageId is a MessagePack str whose bytes are ED A0 80, and Rust, C#, Kotlin and Python build the frame from those bytes and must refuse it. Scope: write_admitted only; error.v1 is untouched.", + "semantics": "Decoder-side rule for a `cultnet.write_admitted.v0` frame's `messageId` (Heimdall's ruling, write-receipt.md). Valid only if it contains at least one Unicode letter (\\p{L}) or number (\\p{N}) AND no Default_Ignorable_Code_Point (DerivedCoreProperties). The compare is RAW: the id is echoed byte for byte, so a decoder neither trims nor normalizes. Each case's id is given as `codePoints` (hex, in order), repeated `repeat` times (default 1), so no lone surrogate appears in this file. A runner builds the frame {schemaVersion write_admitted.v0, messageId, operation put, schemaId note.v0, recordKey note:1} with its own encoder and decodes it with the write_admitted decoder; `refuse` means the decoder refuses the frame the way it refuses an empty or ill-typed messageId, `accept` means it decodes and the id comes back identical. A case with `wireHex` carries the whole frame in MessagePack hex, for an id a language string cannot hold: the lone surrogate U+D800 is not valid UTF-8, so the vector's messageId is a MessagePack str whose bytes are ED A0 80, and Rust, C#, Kotlin and Python build the frame from those bytes and must refuse it. Letters, numbers and Default_Ignorable are those of Unicode 16.0 in every runtime, from embedded tables (no platform category lookup), so a runtime's own Unicode version cannot change a result. Scope: write_admitted only; error.v1 is untouched.", "cases": [ { "name": "refuse_braille_blank_2800", @@ -360,6 +360,54 @@ "expect": "refuse", "why": "U+FEFF is Default_Ignorable; JavaScript trim() would strip it and accept the rest, but the compare is raw" }, + { + "name": "accept_unicode16_gurung_khema_letter_16100", + "codePoints": [ + "16100" + ], + "expect": "accept", + "why": "U+16100 GURUNG KHEMA LETTER A is Lo and new in Unicode 16.0 (unassigned in 15.1); the rule is pinned to 16.0 in every runtime" + }, + { + "name": "accept_unicode16_garay_digit_zero_10D40", + "codePoints": [ + "10D40" + ], + "expect": "accept", + "why": "U+10D40 GARAY DIGIT ZERO is Nd and new in Unicode 16.0" + }, + { + "name": "accept_unicode15_1_cjk_ext_i_2EBF0", + "codePoints": [ + "2EBF0" + ], + "expect": "accept", + "why": "U+2EBF0, first of CJK Unified Ideographs Extension I, is Lo and new in Unicode 15.1" + }, + { + "name": "accept_last_cjk_ext_i_2EE5D", + "codePoints": [ + "2EE5D" + ], + "expect": "accept", + "why": "U+2EE5D is the last assigned code point of Extension I (Lo)" + }, + { + "name": "refuse_after_cjk_ext_i_2EE5E", + "codePoints": [ + "2EE5E" + ], + "expect": "refuse", + "why": "U+2EE5E is unassigned in Unicode 16.0, so neither a letter nor a number" + }, + { + "name": "refuse_unassigned_in_16_0_10D90", + "codePoints": [ + "10D90" + ], + "expect": "refuse", + "why": "U+10D90 follows the Garay block and is unassigned in Unicode 16.0" + }, { "name": "accept_abc", "codePoints": [ @@ -1476,7 +1524,7 @@ "schemaInvalid": 7, "contentHashCases": 4, "scenarioCases": 25, - "admittedMessageIdCases": 34 + "admittedMessageIdCases": 40 }, "contentHash": { "definition": "sha256(msgpack([operation, schemaId, recordKey, payload])), payload a msgpack bin for a put and nil for a delete; hex lowercase", From 3d37fda49638c4171ab654b1a72388aaee175449 Mon Sep 17 00:00:00 2001 From: soul Date: Mon, 9 Nov 2026 15:04:59 +0000 Subject: [PATCH 25/76] write_admitted messageId: pin Unicode 16.0, one generator for the embedded tables scripts/gen-unicode-messageid-tables.py emits each runtime's Letter-or-Number and Default_Ignorable tables from the two UCD 16.0 files; --check fails on a stale or hand-edited table. Co-Authored-By: Claude Sonnet 5.5 --- contracts/cultnet/interop/write-receipt.md | 2 +- scripts/gen-unicode-messageid-tables.py | 111 +++++++++++++++++++++ 2 files changed, 112 insertions(+), 1 deletion(-) create mode 100644 scripts/gen-unicode-messageid-tables.py diff --git a/contracts/cultnet/interop/write-receipt.md b/contracts/cultnet/interop/write-receipt.md index 150c10f8..10f42a6a 100644 --- a/contracts/cultnet/interop/write-receipt.md +++ b/contracts/cultnet/interop/write-receipt.md @@ -12,7 +12,7 @@ A put or delete today has two answers on the wire: nothing (admitted, or the ser **`cultnet.write_admitted.v0`**: `{ schemaVersion, messageId, operation: "put"|"delete", schemaId, recordKey }`, every key written, none nil. Sent once a put or delete is admitted. The id echoes the write's `messageId` byte for byte; `operation`, `schemaId`, `recordKey` echo the write. -**A `write_admitted` `messageId` is valid only if it contains at least one Unicode letter (`\p{L}`) or number (`\p{N}`) and no Default_Ignorable_Code_Point (DerivedCoreProperties).** The compare is raw: the id is echoed byte for byte, so a decoder never trims or normalizes it (`" a "` is valid; U+2800 is not, and neither is `a` followed by U+200B). Unlike the Required fields rule, this is not a White_Space test. A decoder refuses an invalid id the way it refuses an empty or ill-typed one; the rule is for `write_admitted` only and `error.v1` is unchanged. +**A `write_admitted` `messageId` is valid only if it contains at least one Unicode letter (`\p{L}`) or number (`\p{N}`) and no Default_Ignorable_Code_Point (DerivedCoreProperties).** The compare is raw: the id is echoed byte for byte, so a decoder never trims or normalizes it (`" a "` is valid; U+2800 is not, and neither is `a` followed by U+200B). Unlike the Required fields rule, this is not a White_Space test. The letters, numbers and Default_Ignorable code points are those of **Unicode 16.0** in every runtime, taken from tables embedded in each runtime and generated by `scripts/gen-unicode-messageid-tables.py` from `DerivedGeneralCategory.txt` (Lu Ll Lt Lm Lo Nd Nl No) and `DerivedCoreProperties.txt`; no runtime asks its platform's Unicode data, so a runtime's own Unicode version cannot change a result. Bumping the version is one coordinated change: regenerate the five tables and update the vectors that pin the version in the same commit. A decoder refuses an invalid id the way it refuses an empty or ill-typed one; the rule is for `write_admitted` only and `error.v1` is unchanged. **`cultnet.error.v1`**: `cultnet.error.v0` plus a required `messageId` (string or nil). The code enum is v0's plus `limit_reached` (see Remembered-write limits); `cultnet.error.v1.schema.json` is the authority for the closed set. Sent in place of `error.v0` to a receipt-capable connection. `messageId` is the id of the request the error answers; nil only when the frame was unreadable or carried no id. diff --git a/scripts/gen-unicode-messageid-tables.py b/scripts/gen-unicode-messageid-tables.py new file mode 100644 index 00000000..96d1a205 --- /dev/null +++ b/scripts/gen-unicode-messageid-tables.py @@ -0,0 +1,111 @@ +#!/usr/bin/env python3 +"""Generate the Unicode 16.0 tables of the write_admitted messageId rule for the five CultNet runtimes. + +The rule (contracts/cultnet/interop/write-receipt.md): a messageId is valid iff it holds a letter (Lu Ll Lt Lm Lo) or a number +(Nd Nl No) and no Default_Ignorable_Code_Point, all as of Unicode 16.0. The tables are embedded in each runtime so that no +platform's own Unicode version can change a result. Bumping the version is one coordinated change: run this script on the new +UCD files, commit the five regenerated files and the vectors that pin the new version, together. + + gen-unicode-messageid-tables.py DerivedGeneralCategory.txt DerivedCoreProperties.txt [--check] + +The inputs are https://www.unicode.org/Public/16.0.0/ucd/extracted/DerivedGeneralCategory.txt and +https://www.unicode.org/Public/16.0.0/ucd/DerivedCoreProperties.txt. With --check nothing is written; the exit status is 1 if +any generated file differs from what these inputs produce (a stale or hand-edited table). Standard library only. +""" +import hashlib +import re +import sys +from pathlib import Path + +VERSION = "16.0.0" +ROOT = Path(__file__).resolve().parent.parent +LETTER_OR_NUMBER_CATEGORIES = {"Lu", "Ll", "Lt", "Lm", "Lo", "Nd", "Nl", "No"} + +TARGETS = { + "ts": ROOT / "packages/cultnet-ts/src/unicode-messageid-tables.ts", + "py": ROOT / "packages/cultnet-py/src/cultnet_py/unicode_messageid_tables.py", + "kt": ROOT / "packages/cultmesh-kotlin/src/main/kotlin/org/gamecult/cultmesh/UnicodeMessageIdTables.kt", + "cs": ROOT / "src/GameCult.Networking/CultNetUnicodeMessageIdTables.cs", + "rs": ROOT / "packages/cultnet-rs/src/unicode_messageid_tables.rs", +} + + +def parse(path, wanted): + """Ranges (low, high) of the lines `LOW[..HIGH] ; VALUE`, VALUE accepted by [wanted], merged and sorted.""" + points = [] + for line in Path(path).read_text(encoding="utf-8").splitlines(): + m = re.match(r"([0-9A-F]+)(?:\.\.([0-9A-F]+))?\s*;\s*(\S+)", line) + if m and wanted(m.group(3)): + points.append((int(m.group(1), 16), int(m.group(2) or m.group(1), 16))) + points.sort() + merged = [] + for low, high in points: + if merged and low <= merged[-1][1] + 1: + merged[-1] = (merged[-1][0], max(merged[-1][1], high)) + else: + merged.append((low, high)) + return merged + + +def flat(ranges): + return [value for pair in ranges for value in pair] + + +def wrapped(items, indent, render, width=118): + lines, current = [], indent + for item in items: + text = render(item) + ", " + if len(current) + len(text) > width: + lines.append(current.rstrip()) + current = indent + current += text + lines.append(current.rstrip()) + return "\n".join(lines) + + +def emit(target, letters, ignorable, header): + num = lambda v: f"0x{v:X}" + if target == "ts": + body = lambda name, r: f"export const {name}: readonly number[] = [\n{wrapped(flat(r), ' ', num)}\n];\n" + return f"// {header}\n\n// [low, high] pairs, sorted.\n{body('LETTER_OR_NUMBER', letters)}\n{body('DEFAULT_IGNORABLE', ignorable)}" + if target == "py": + body = lambda name, r: f"{name}: tuple[int, ...] = (\n{wrapped(flat(r), ' ', num)}\n)\n" + return f"# {header}\n\n# (low, high) pairs, flattened and sorted.\n{body('LETTER_OR_NUMBER', letters)}\n{body('DEFAULT_IGNORABLE', ignorable)}" + if target == "kt": + body = lambda name, r: f"internal val {name}: IntArray = intArrayOf(\n{wrapped(flat(r), ' ', num)}\n)\n" + return f"// {header}\n\npackage org.gamecult.cultmesh\n\n// (low, high) pairs, flattened and sorted.\n{body('LETTER_OR_NUMBER', letters)}\n{body('DEFAULT_IGNORABLE', ignorable)}" + if target == "cs": + body = lambda name, r: f" public static readonly int[] {name} =\n {{\n{wrapped(flat(r), ' ', num)}\n }};\n" + return f"// {header}\n\nnamespace GameCult.Networking\n{{\n internal static class CultNetUnicodeMessageIdTables\n {{\n // (low, high) pairs, flattened and sorted.\n{body('LetterOrNumber', letters)}\n{body('DefaultIgnorable', ignorable)} }}\n}}\n" + body = lambda name, r: f"pub const {name}: &[(u32, u32)] = &[\n{wrapped(r, ' ', lambda p: f'({num(p[0])}, {num(p[1])})')}\n];\n" + return f"// {header}\n\n// (low, high) pairs, sorted.\n{body('LETTER_OR_NUMBER', letters)}\n{body('DEFAULT_IGNORABLE', ignorable)}" + + +def main(): + args = [a for a in sys.argv[1:] if a != "--check"] + check = "--check" in sys.argv[1:] + if len(args) != 2: + sys.exit(__doc__) + gc, dcp = args + for path, marker in ((gc, f"DerivedGeneralCategory-{VERSION}"), (dcp, f"DerivedCoreProperties-{VERSION}")): + if marker not in Path(path).read_text(encoding="utf-8").splitlines()[0]: + sys.exit(f"{path} is not {marker}.txt") + letters = parse(gc, lambda v: v in LETTER_OR_NUMBER_CATEGORIES) + ignorable = parse(dcp, lambda v: v == "Default_Ignorable_Code_Point") + digest = hashlib.sha256(b"".join(Path(p).read_bytes() for p in (gc, dcp))).hexdigest()[:16] + header = f"generated, Unicode {VERSION}, by scripts/gen-unicode-messageid-tables.py (inputs sha256 {digest}); do not edit" + stale = [] + for target, path in TARGETS.items(): + text = emit(target, letters, ignorable, header) + if check: + if not path.exists() or path.read_text(encoding="utf-8") != text: + stale.append(str(path.relative_to(ROOT))) + else: + path.write_text(text, encoding="utf-8") + if stale: + sys.exit("stale or hand-edited: " + ", ".join(stale)) + print(f"{'checked' if check else 'wrote'} {len(TARGETS)} tables: {len(letters)} letter-or-number ranges, {len(ignorable)} default-ignorable ranges") + + +if __name__ == "__main__": + main() From de25b50abdb4ac90b0512d7436aa59605ad9c678 Mon Sep 17 00:00:00 2001 From: soul Date: Mon, 9 Nov 2026 15:04:59 +0000 Subject: [PATCH 26/76] cultnet-ts: write_admitted messageId from embedded Unicode 16.0 tables, not the regex engine's Co-Authored-By: Claude Sonnet 5.5 --- packages/cultnet-ts/src/contracts.ts | 38 +++++- .../src/unicode-messageid-tables.ts | 126 ++++++++++++++++++ 2 files changed, 160 insertions(+), 4 deletions(-) create mode 100644 packages/cultnet-ts/src/unicode-messageid-tables.ts diff --git a/packages/cultnet-ts/src/contracts.ts b/packages/cultnet-ts/src/contracts.ts index a05a26df..09d5d823 100644 --- a/packages/cultnet-ts/src/contracts.ts +++ b/packages/cultnet-ts/src/contracts.ts @@ -10,6 +10,7 @@ import verifySchema from "../contracts/cultnet/cultnet.verify.schema.json"; import loginSuccessSchema from "../contracts/cultnet/cultnet.login-success.schema.json"; import errorSchema from "../contracts/cultnet/cultnet.error.schema.json"; import errorV1Schema from "../contracts/cultnet/interop/cultnet.error.v1.schema.json"; +import { DEFAULT_IGNORABLE, LETTER_OR_NUMBER } from "./unicode-messageid-tables"; import writeAdmittedSchema from "../contracts/cultnet/interop/cultnet.write-admitted.schema.json"; import sampleChangeNameSchema from "../contracts/cultnet/cultnet.sample-change-name.schema.json"; import sampleChatSchema from "../contracts/cultnet/cultnet.sample-chat.schema.json"; @@ -1465,12 +1466,41 @@ function validateRawDocumentRecord( } /** - * A write_admitted messageId is valid iff it holds a Unicode letter or number and no Default_Ignorable_Code_Point, compared raw - * (write-receipt.md; the id is echoed byte for byte, so nothing is trimmed). The platform's regex tables carry both properties, - * so this runtime embeds no table; the Kotlin, Python, C# and Rust decoders embed DerivedCoreProperties ranges. + * A write_admitted messageId is valid iff it holds a Unicode letter (Lu Ll Lt Lm Lo) or number (Nd Nl No) and no + * Default_Ignorable_Code_Point, as of Unicode 16.0, compared raw (write-receipt.md; the id is echoed byte for byte, so nothing + * is trimmed). The tables are embedded (unicode-messageid-tables.ts, generated by scripts/gen-unicode-messageid-tables.py), not + * the regex engine's, so the running Node's Unicode version changes no result; the other four runtimes embed the same tables. */ function isValidAdmittedMessageId(value: unknown): boolean { - return typeof value === "string" && /[\p{L}\p{N}]/u.test(value) && !/\p{Default_Ignorable_Code_Point}/u.test(value); + if (typeof value !== "string") { + return false; + } + let hasLetterOrNumber = false; + for (const ch of value) { + const point = ch.codePointAt(0) as number; + if (inRanges(DEFAULT_IGNORABLE, point)) { + return false; + } + hasLetterOrNumber = hasLetterOrNumber || inRanges(LETTER_OR_NUMBER, point); + } + return hasLetterOrNumber; +} + +/** Whether [point] lies in the flattened, sorted [low, high] pairs. */ +function inRanges(ranges: readonly number[], point: number): boolean { + let low = 0; + let high = ranges.length / 2 - 1; + while (low <= high) { + const mid = (low + high) >> 1; + if (point < ranges[2 * mid]) { + high = mid - 1; + } else if (point > ranges[2 * mid + 1]) { + low = mid + 1; + } else { + return true; + } + } + return false; } /** Blank is empty or only Unicode White_Space (write-receipt.md, rule 5), not `trim()`: JS trims U+FEFF and keeps U+0085. */ diff --git a/packages/cultnet-ts/src/unicode-messageid-tables.ts b/packages/cultnet-ts/src/unicode-messageid-tables.ts new file mode 100644 index 00000000..3374a011 --- /dev/null +++ b/packages/cultnet-ts/src/unicode-messageid-tables.ts @@ -0,0 +1,126 @@ +// generated, Unicode 16.0.0, by scripts/gen-unicode-messageid-tables.py (inputs sha256 24a07ef9fabf71e3); do not edit + +// [low, high] pairs, sorted. +export const LETTER_OR_NUMBER: readonly number[] = [ + 0x30, 0x39, 0x41, 0x5A, 0x61, 0x7A, 0xAA, 0xAA, 0xB2, 0xB3, 0xB5, 0xB5, 0xB9, 0xBA, 0xBC, 0xBE, 0xC0, 0xD6, 0xD8, + 0xF6, 0xF8, 0x2C1, 0x2C6, 0x2D1, 0x2E0, 0x2E4, 0x2EC, 0x2EC, 0x2EE, 0x2EE, 0x370, 0x374, 0x376, 0x377, 0x37A, + 0x37D, 0x37F, 0x37F, 0x386, 0x386, 0x388, 0x38A, 0x38C, 0x38C, 0x38E, 0x3A1, 0x3A3, 0x3F5, 0x3F7, 0x481, 0x48A, + 0x52F, 0x531, 0x556, 0x559, 0x559, 0x560, 0x588, 0x5D0, 0x5EA, 0x5EF, 0x5F2, 0x620, 0x64A, 0x660, 0x669, 0x66E, + 0x66F, 0x671, 0x6D3, 0x6D5, 0x6D5, 0x6E5, 0x6E6, 0x6EE, 0x6FC, 0x6FF, 0x6FF, 0x710, 0x710, 0x712, 0x72F, 0x74D, + 0x7A5, 0x7B1, 0x7B1, 0x7C0, 0x7EA, 0x7F4, 0x7F5, 0x7FA, 0x7FA, 0x800, 0x815, 0x81A, 0x81A, 0x824, 0x824, 0x828, + 0x828, 0x840, 0x858, 0x860, 0x86A, 0x870, 0x887, 0x889, 0x88E, 0x8A0, 0x8C9, 0x904, 0x939, 0x93D, 0x93D, 0x950, + 0x950, 0x958, 0x961, 0x966, 0x96F, 0x971, 0x980, 0x985, 0x98C, 0x98F, 0x990, 0x993, 0x9A8, 0x9AA, 0x9B0, 0x9B2, + 0x9B2, 0x9B6, 0x9B9, 0x9BD, 0x9BD, 0x9CE, 0x9CE, 0x9DC, 0x9DD, 0x9DF, 0x9E1, 0x9E6, 0x9F1, 0x9F4, 0x9F9, 0x9FC, + 0x9FC, 0xA05, 0xA0A, 0xA0F, 0xA10, 0xA13, 0xA28, 0xA2A, 0xA30, 0xA32, 0xA33, 0xA35, 0xA36, 0xA38, 0xA39, 0xA59, + 0xA5C, 0xA5E, 0xA5E, 0xA66, 0xA6F, 0xA72, 0xA74, 0xA85, 0xA8D, 0xA8F, 0xA91, 0xA93, 0xAA8, 0xAAA, 0xAB0, 0xAB2, + 0xAB3, 0xAB5, 0xAB9, 0xABD, 0xABD, 0xAD0, 0xAD0, 0xAE0, 0xAE1, 0xAE6, 0xAEF, 0xAF9, 0xAF9, 0xB05, 0xB0C, 0xB0F, + 0xB10, 0xB13, 0xB28, 0xB2A, 0xB30, 0xB32, 0xB33, 0xB35, 0xB39, 0xB3D, 0xB3D, 0xB5C, 0xB5D, 0xB5F, 0xB61, 0xB66, + 0xB6F, 0xB71, 0xB77, 0xB83, 0xB83, 0xB85, 0xB8A, 0xB8E, 0xB90, 0xB92, 0xB95, 0xB99, 0xB9A, 0xB9C, 0xB9C, 0xB9E, + 0xB9F, 0xBA3, 0xBA4, 0xBA8, 0xBAA, 0xBAE, 0xBB9, 0xBD0, 0xBD0, 0xBE6, 0xBF2, 0xC05, 0xC0C, 0xC0E, 0xC10, 0xC12, + 0xC28, 0xC2A, 0xC39, 0xC3D, 0xC3D, 0xC58, 0xC5A, 0xC5D, 0xC5D, 0xC60, 0xC61, 0xC66, 0xC6F, 0xC78, 0xC7E, 0xC80, + 0xC80, 0xC85, 0xC8C, 0xC8E, 0xC90, 0xC92, 0xCA8, 0xCAA, 0xCB3, 0xCB5, 0xCB9, 0xCBD, 0xCBD, 0xCDD, 0xCDE, 0xCE0, + 0xCE1, 0xCE6, 0xCEF, 0xCF1, 0xCF2, 0xD04, 0xD0C, 0xD0E, 0xD10, 0xD12, 0xD3A, 0xD3D, 0xD3D, 0xD4E, 0xD4E, 0xD54, + 0xD56, 0xD58, 0xD61, 0xD66, 0xD78, 0xD7A, 0xD7F, 0xD85, 0xD96, 0xD9A, 0xDB1, 0xDB3, 0xDBB, 0xDBD, 0xDBD, 0xDC0, + 0xDC6, 0xDE6, 0xDEF, 0xE01, 0xE30, 0xE32, 0xE33, 0xE40, 0xE46, 0xE50, 0xE59, 0xE81, 0xE82, 0xE84, 0xE84, 0xE86, + 0xE8A, 0xE8C, 0xEA3, 0xEA5, 0xEA5, 0xEA7, 0xEB0, 0xEB2, 0xEB3, 0xEBD, 0xEBD, 0xEC0, 0xEC4, 0xEC6, 0xEC6, 0xED0, + 0xED9, 0xEDC, 0xEDF, 0xF00, 0xF00, 0xF20, 0xF33, 0xF40, 0xF47, 0xF49, 0xF6C, 0xF88, 0xF8C, 0x1000, 0x102A, 0x103F, + 0x1049, 0x1050, 0x1055, 0x105A, 0x105D, 0x1061, 0x1061, 0x1065, 0x1066, 0x106E, 0x1070, 0x1075, 0x1081, 0x108E, + 0x108E, 0x1090, 0x1099, 0x10A0, 0x10C5, 0x10C7, 0x10C7, 0x10CD, 0x10CD, 0x10D0, 0x10FA, 0x10FC, 0x1248, 0x124A, + 0x124D, 0x1250, 0x1256, 0x1258, 0x1258, 0x125A, 0x125D, 0x1260, 0x1288, 0x128A, 0x128D, 0x1290, 0x12B0, 0x12B2, + 0x12B5, 0x12B8, 0x12BE, 0x12C0, 0x12C0, 0x12C2, 0x12C5, 0x12C8, 0x12D6, 0x12D8, 0x1310, 0x1312, 0x1315, 0x1318, + 0x135A, 0x1369, 0x137C, 0x1380, 0x138F, 0x13A0, 0x13F5, 0x13F8, 0x13FD, 0x1401, 0x166C, 0x166F, 0x167F, 0x1681, + 0x169A, 0x16A0, 0x16EA, 0x16EE, 0x16F8, 0x1700, 0x1711, 0x171F, 0x1731, 0x1740, 0x1751, 0x1760, 0x176C, 0x176E, + 0x1770, 0x1780, 0x17B3, 0x17D7, 0x17D7, 0x17DC, 0x17DC, 0x17E0, 0x17E9, 0x17F0, 0x17F9, 0x1810, 0x1819, 0x1820, + 0x1878, 0x1880, 0x1884, 0x1887, 0x18A8, 0x18AA, 0x18AA, 0x18B0, 0x18F5, 0x1900, 0x191E, 0x1946, 0x196D, 0x1970, + 0x1974, 0x1980, 0x19AB, 0x19B0, 0x19C9, 0x19D0, 0x19DA, 0x1A00, 0x1A16, 0x1A20, 0x1A54, 0x1A80, 0x1A89, 0x1A90, + 0x1A99, 0x1AA7, 0x1AA7, 0x1B05, 0x1B33, 0x1B45, 0x1B4C, 0x1B50, 0x1B59, 0x1B83, 0x1BA0, 0x1BAE, 0x1BE5, 0x1C00, + 0x1C23, 0x1C40, 0x1C49, 0x1C4D, 0x1C7D, 0x1C80, 0x1C8A, 0x1C90, 0x1CBA, 0x1CBD, 0x1CBF, 0x1CE9, 0x1CEC, 0x1CEE, + 0x1CF3, 0x1CF5, 0x1CF6, 0x1CFA, 0x1CFA, 0x1D00, 0x1DBF, 0x1E00, 0x1F15, 0x1F18, 0x1F1D, 0x1F20, 0x1F45, 0x1F48, + 0x1F4D, 0x1F50, 0x1F57, 0x1F59, 0x1F59, 0x1F5B, 0x1F5B, 0x1F5D, 0x1F5D, 0x1F5F, 0x1F7D, 0x1F80, 0x1FB4, 0x1FB6, + 0x1FBC, 0x1FBE, 0x1FBE, 0x1FC2, 0x1FC4, 0x1FC6, 0x1FCC, 0x1FD0, 0x1FD3, 0x1FD6, 0x1FDB, 0x1FE0, 0x1FEC, 0x1FF2, + 0x1FF4, 0x1FF6, 0x1FFC, 0x2070, 0x2071, 0x2074, 0x2079, 0x207F, 0x2089, 0x2090, 0x209C, 0x2102, 0x2102, 0x2107, + 0x2107, 0x210A, 0x2113, 0x2115, 0x2115, 0x2119, 0x211D, 0x2124, 0x2124, 0x2126, 0x2126, 0x2128, 0x2128, 0x212A, + 0x212D, 0x212F, 0x2139, 0x213C, 0x213F, 0x2145, 0x2149, 0x214E, 0x214E, 0x2150, 0x2189, 0x2460, 0x249B, 0x24EA, + 0x24FF, 0x2776, 0x2793, 0x2C00, 0x2CE4, 0x2CEB, 0x2CEE, 0x2CF2, 0x2CF3, 0x2CFD, 0x2CFD, 0x2D00, 0x2D25, 0x2D27, + 0x2D27, 0x2D2D, 0x2D2D, 0x2D30, 0x2D67, 0x2D6F, 0x2D6F, 0x2D80, 0x2D96, 0x2DA0, 0x2DA6, 0x2DA8, 0x2DAE, 0x2DB0, + 0x2DB6, 0x2DB8, 0x2DBE, 0x2DC0, 0x2DC6, 0x2DC8, 0x2DCE, 0x2DD0, 0x2DD6, 0x2DD8, 0x2DDE, 0x2E2F, 0x2E2F, 0x3005, + 0x3007, 0x3021, 0x3029, 0x3031, 0x3035, 0x3038, 0x303C, 0x3041, 0x3096, 0x309D, 0x309F, 0x30A1, 0x30FA, 0x30FC, + 0x30FF, 0x3105, 0x312F, 0x3131, 0x318E, 0x3192, 0x3195, 0x31A0, 0x31BF, 0x31F0, 0x31FF, 0x3220, 0x3229, 0x3248, + 0x324F, 0x3251, 0x325F, 0x3280, 0x3289, 0x32B1, 0x32BF, 0x3400, 0x4DBF, 0x4E00, 0xA48C, 0xA4D0, 0xA4FD, 0xA500, + 0xA60C, 0xA610, 0xA62B, 0xA640, 0xA66E, 0xA67F, 0xA69D, 0xA6A0, 0xA6EF, 0xA717, 0xA71F, 0xA722, 0xA788, 0xA78B, + 0xA7CD, 0xA7D0, 0xA7D1, 0xA7D3, 0xA7D3, 0xA7D5, 0xA7DC, 0xA7F2, 0xA801, 0xA803, 0xA805, 0xA807, 0xA80A, 0xA80C, + 0xA822, 0xA830, 0xA835, 0xA840, 0xA873, 0xA882, 0xA8B3, 0xA8D0, 0xA8D9, 0xA8F2, 0xA8F7, 0xA8FB, 0xA8FB, 0xA8FD, + 0xA8FE, 0xA900, 0xA925, 0xA930, 0xA946, 0xA960, 0xA97C, 0xA984, 0xA9B2, 0xA9CF, 0xA9D9, 0xA9E0, 0xA9E4, 0xA9E6, + 0xA9FE, 0xAA00, 0xAA28, 0xAA40, 0xAA42, 0xAA44, 0xAA4B, 0xAA50, 0xAA59, 0xAA60, 0xAA76, 0xAA7A, 0xAA7A, 0xAA7E, + 0xAAAF, 0xAAB1, 0xAAB1, 0xAAB5, 0xAAB6, 0xAAB9, 0xAABD, 0xAAC0, 0xAAC0, 0xAAC2, 0xAAC2, 0xAADB, 0xAADD, 0xAAE0, + 0xAAEA, 0xAAF2, 0xAAF4, 0xAB01, 0xAB06, 0xAB09, 0xAB0E, 0xAB11, 0xAB16, 0xAB20, 0xAB26, 0xAB28, 0xAB2E, 0xAB30, + 0xAB5A, 0xAB5C, 0xAB69, 0xAB70, 0xABE2, 0xABF0, 0xABF9, 0xAC00, 0xD7A3, 0xD7B0, 0xD7C6, 0xD7CB, 0xD7FB, 0xF900, + 0xFA6D, 0xFA70, 0xFAD9, 0xFB00, 0xFB06, 0xFB13, 0xFB17, 0xFB1D, 0xFB1D, 0xFB1F, 0xFB28, 0xFB2A, 0xFB36, 0xFB38, + 0xFB3C, 0xFB3E, 0xFB3E, 0xFB40, 0xFB41, 0xFB43, 0xFB44, 0xFB46, 0xFBB1, 0xFBD3, 0xFD3D, 0xFD50, 0xFD8F, 0xFD92, + 0xFDC7, 0xFDF0, 0xFDFB, 0xFE70, 0xFE74, 0xFE76, 0xFEFC, 0xFF10, 0xFF19, 0xFF21, 0xFF3A, 0xFF41, 0xFF5A, 0xFF66, + 0xFFBE, 0xFFC2, 0xFFC7, 0xFFCA, 0xFFCF, 0xFFD2, 0xFFD7, 0xFFDA, 0xFFDC, 0x10000, 0x1000B, 0x1000D, 0x10026, + 0x10028, 0x1003A, 0x1003C, 0x1003D, 0x1003F, 0x1004D, 0x10050, 0x1005D, 0x10080, 0x100FA, 0x10107, 0x10133, + 0x10140, 0x10178, 0x1018A, 0x1018B, 0x10280, 0x1029C, 0x102A0, 0x102D0, 0x102E1, 0x102FB, 0x10300, 0x10323, + 0x1032D, 0x1034A, 0x10350, 0x10375, 0x10380, 0x1039D, 0x103A0, 0x103C3, 0x103C8, 0x103CF, 0x103D1, 0x103D5, + 0x10400, 0x1049D, 0x104A0, 0x104A9, 0x104B0, 0x104D3, 0x104D8, 0x104FB, 0x10500, 0x10527, 0x10530, 0x10563, + 0x10570, 0x1057A, 0x1057C, 0x1058A, 0x1058C, 0x10592, 0x10594, 0x10595, 0x10597, 0x105A1, 0x105A3, 0x105B1, + 0x105B3, 0x105B9, 0x105BB, 0x105BC, 0x105C0, 0x105F3, 0x10600, 0x10736, 0x10740, 0x10755, 0x10760, 0x10767, + 0x10780, 0x10785, 0x10787, 0x107B0, 0x107B2, 0x107BA, 0x10800, 0x10805, 0x10808, 0x10808, 0x1080A, 0x10835, + 0x10837, 0x10838, 0x1083C, 0x1083C, 0x1083F, 0x10855, 0x10858, 0x10876, 0x10879, 0x1089E, 0x108A7, 0x108AF, + 0x108E0, 0x108F2, 0x108F4, 0x108F5, 0x108FB, 0x1091B, 0x10920, 0x10939, 0x10980, 0x109B7, 0x109BC, 0x109CF, + 0x109D2, 0x10A00, 0x10A10, 0x10A13, 0x10A15, 0x10A17, 0x10A19, 0x10A35, 0x10A40, 0x10A48, 0x10A60, 0x10A7E, + 0x10A80, 0x10A9F, 0x10AC0, 0x10AC7, 0x10AC9, 0x10AE4, 0x10AEB, 0x10AEF, 0x10B00, 0x10B35, 0x10B40, 0x10B55, + 0x10B58, 0x10B72, 0x10B78, 0x10B91, 0x10BA9, 0x10BAF, 0x10C00, 0x10C48, 0x10C80, 0x10CB2, 0x10CC0, 0x10CF2, + 0x10CFA, 0x10D23, 0x10D30, 0x10D39, 0x10D40, 0x10D65, 0x10D6F, 0x10D85, 0x10E60, 0x10E7E, 0x10E80, 0x10EA9, + 0x10EB0, 0x10EB1, 0x10EC2, 0x10EC4, 0x10F00, 0x10F27, 0x10F30, 0x10F45, 0x10F51, 0x10F54, 0x10F70, 0x10F81, + 0x10FB0, 0x10FCB, 0x10FE0, 0x10FF6, 0x11003, 0x11037, 0x11052, 0x1106F, 0x11071, 0x11072, 0x11075, 0x11075, + 0x11083, 0x110AF, 0x110D0, 0x110E8, 0x110F0, 0x110F9, 0x11103, 0x11126, 0x11136, 0x1113F, 0x11144, 0x11144, + 0x11147, 0x11147, 0x11150, 0x11172, 0x11176, 0x11176, 0x11183, 0x111B2, 0x111C1, 0x111C4, 0x111D0, 0x111DA, + 0x111DC, 0x111DC, 0x111E1, 0x111F4, 0x11200, 0x11211, 0x11213, 0x1122B, 0x1123F, 0x11240, 0x11280, 0x11286, + 0x11288, 0x11288, 0x1128A, 0x1128D, 0x1128F, 0x1129D, 0x1129F, 0x112A8, 0x112B0, 0x112DE, 0x112F0, 0x112F9, + 0x11305, 0x1130C, 0x1130F, 0x11310, 0x11313, 0x11328, 0x1132A, 0x11330, 0x11332, 0x11333, 0x11335, 0x11339, + 0x1133D, 0x1133D, 0x11350, 0x11350, 0x1135D, 0x11361, 0x11380, 0x11389, 0x1138B, 0x1138B, 0x1138E, 0x1138E, + 0x11390, 0x113B5, 0x113B7, 0x113B7, 0x113D1, 0x113D1, 0x113D3, 0x113D3, 0x11400, 0x11434, 0x11447, 0x1144A, + 0x11450, 0x11459, 0x1145F, 0x11461, 0x11480, 0x114AF, 0x114C4, 0x114C5, 0x114C7, 0x114C7, 0x114D0, 0x114D9, + 0x11580, 0x115AE, 0x115D8, 0x115DB, 0x11600, 0x1162F, 0x11644, 0x11644, 0x11650, 0x11659, 0x11680, 0x116AA, + 0x116B8, 0x116B8, 0x116C0, 0x116C9, 0x116D0, 0x116E3, 0x11700, 0x1171A, 0x11730, 0x1173B, 0x11740, 0x11746, + 0x11800, 0x1182B, 0x118A0, 0x118F2, 0x118FF, 0x11906, 0x11909, 0x11909, 0x1190C, 0x11913, 0x11915, 0x11916, + 0x11918, 0x1192F, 0x1193F, 0x1193F, 0x11941, 0x11941, 0x11950, 0x11959, 0x119A0, 0x119A7, 0x119AA, 0x119D0, + 0x119E1, 0x119E1, 0x119E3, 0x119E3, 0x11A00, 0x11A00, 0x11A0B, 0x11A32, 0x11A3A, 0x11A3A, 0x11A50, 0x11A50, + 0x11A5C, 0x11A89, 0x11A9D, 0x11A9D, 0x11AB0, 0x11AF8, 0x11BC0, 0x11BE0, 0x11BF0, 0x11BF9, 0x11C00, 0x11C08, + 0x11C0A, 0x11C2E, 0x11C40, 0x11C40, 0x11C50, 0x11C6C, 0x11C72, 0x11C8F, 0x11D00, 0x11D06, 0x11D08, 0x11D09, + 0x11D0B, 0x11D30, 0x11D46, 0x11D46, 0x11D50, 0x11D59, 0x11D60, 0x11D65, 0x11D67, 0x11D68, 0x11D6A, 0x11D89, + 0x11D98, 0x11D98, 0x11DA0, 0x11DA9, 0x11EE0, 0x11EF2, 0x11F02, 0x11F02, 0x11F04, 0x11F10, 0x11F12, 0x11F33, + 0x11F50, 0x11F59, 0x11FB0, 0x11FB0, 0x11FC0, 0x11FD4, 0x12000, 0x12399, 0x12400, 0x1246E, 0x12480, 0x12543, + 0x12F90, 0x12FF0, 0x13000, 0x1342F, 0x13441, 0x13446, 0x13460, 0x143FA, 0x14400, 0x14646, 0x16100, 0x1611D, + 0x16130, 0x16139, 0x16800, 0x16A38, 0x16A40, 0x16A5E, 0x16A60, 0x16A69, 0x16A70, 0x16ABE, 0x16AC0, 0x16AC9, + 0x16AD0, 0x16AED, 0x16B00, 0x16B2F, 0x16B40, 0x16B43, 0x16B50, 0x16B59, 0x16B5B, 0x16B61, 0x16B63, 0x16B77, + 0x16B7D, 0x16B8F, 0x16D40, 0x16D6C, 0x16D70, 0x16D79, 0x16E40, 0x16E96, 0x16F00, 0x16F4A, 0x16F50, 0x16F50, + 0x16F93, 0x16F9F, 0x16FE0, 0x16FE1, 0x16FE3, 0x16FE3, 0x17000, 0x187F7, 0x18800, 0x18CD5, 0x18CFF, 0x18D08, + 0x1AFF0, 0x1AFF3, 0x1AFF5, 0x1AFFB, 0x1AFFD, 0x1AFFE, 0x1B000, 0x1B122, 0x1B132, 0x1B132, 0x1B150, 0x1B152, + 0x1B155, 0x1B155, 0x1B164, 0x1B167, 0x1B170, 0x1B2FB, 0x1BC00, 0x1BC6A, 0x1BC70, 0x1BC7C, 0x1BC80, 0x1BC88, + 0x1BC90, 0x1BC99, 0x1CCF0, 0x1CCF9, 0x1D2C0, 0x1D2D3, 0x1D2E0, 0x1D2F3, 0x1D360, 0x1D378, 0x1D400, 0x1D454, + 0x1D456, 0x1D49C, 0x1D49E, 0x1D49F, 0x1D4A2, 0x1D4A2, 0x1D4A5, 0x1D4A6, 0x1D4A9, 0x1D4AC, 0x1D4AE, 0x1D4B9, + 0x1D4BB, 0x1D4BB, 0x1D4BD, 0x1D4C3, 0x1D4C5, 0x1D505, 0x1D507, 0x1D50A, 0x1D50D, 0x1D514, 0x1D516, 0x1D51C, + 0x1D51E, 0x1D539, 0x1D53B, 0x1D53E, 0x1D540, 0x1D544, 0x1D546, 0x1D546, 0x1D54A, 0x1D550, 0x1D552, 0x1D6A5, + 0x1D6A8, 0x1D6C0, 0x1D6C2, 0x1D6DA, 0x1D6DC, 0x1D6FA, 0x1D6FC, 0x1D714, 0x1D716, 0x1D734, 0x1D736, 0x1D74E, + 0x1D750, 0x1D76E, 0x1D770, 0x1D788, 0x1D78A, 0x1D7A8, 0x1D7AA, 0x1D7C2, 0x1D7C4, 0x1D7CB, 0x1D7CE, 0x1D7FF, + 0x1DF00, 0x1DF1E, 0x1DF25, 0x1DF2A, 0x1E030, 0x1E06D, 0x1E100, 0x1E12C, 0x1E137, 0x1E13D, 0x1E140, 0x1E149, + 0x1E14E, 0x1E14E, 0x1E290, 0x1E2AD, 0x1E2C0, 0x1E2EB, 0x1E2F0, 0x1E2F9, 0x1E4D0, 0x1E4EB, 0x1E4F0, 0x1E4F9, + 0x1E5D0, 0x1E5ED, 0x1E5F0, 0x1E5FA, 0x1E7E0, 0x1E7E6, 0x1E7E8, 0x1E7EB, 0x1E7ED, 0x1E7EE, 0x1E7F0, 0x1E7FE, + 0x1E800, 0x1E8C4, 0x1E8C7, 0x1E8CF, 0x1E900, 0x1E943, 0x1E94B, 0x1E94B, 0x1E950, 0x1E959, 0x1EC71, 0x1ECAB, + 0x1ECAD, 0x1ECAF, 0x1ECB1, 0x1ECB4, 0x1ED01, 0x1ED2D, 0x1ED2F, 0x1ED3D, 0x1EE00, 0x1EE03, 0x1EE05, 0x1EE1F, + 0x1EE21, 0x1EE22, 0x1EE24, 0x1EE24, 0x1EE27, 0x1EE27, 0x1EE29, 0x1EE32, 0x1EE34, 0x1EE37, 0x1EE39, 0x1EE39, + 0x1EE3B, 0x1EE3B, 0x1EE42, 0x1EE42, 0x1EE47, 0x1EE47, 0x1EE49, 0x1EE49, 0x1EE4B, 0x1EE4B, 0x1EE4D, 0x1EE4F, + 0x1EE51, 0x1EE52, 0x1EE54, 0x1EE54, 0x1EE57, 0x1EE57, 0x1EE59, 0x1EE59, 0x1EE5B, 0x1EE5B, 0x1EE5D, 0x1EE5D, + 0x1EE5F, 0x1EE5F, 0x1EE61, 0x1EE62, 0x1EE64, 0x1EE64, 0x1EE67, 0x1EE6A, 0x1EE6C, 0x1EE72, 0x1EE74, 0x1EE77, + 0x1EE79, 0x1EE7C, 0x1EE7E, 0x1EE7E, 0x1EE80, 0x1EE89, 0x1EE8B, 0x1EE9B, 0x1EEA1, 0x1EEA3, 0x1EEA5, 0x1EEA9, + 0x1EEAB, 0x1EEBB, 0x1F100, 0x1F10C, 0x1FBF0, 0x1FBF9, 0x20000, 0x2A6DF, 0x2A700, 0x2B739, 0x2B740, 0x2B81D, + 0x2B820, 0x2CEA1, 0x2CEB0, 0x2EBE0, 0x2EBF0, 0x2EE5D, 0x2F800, 0x2FA1D, 0x30000, 0x3134A, 0x31350, 0x323AF, +]; + +export const DEFAULT_IGNORABLE: readonly number[] = [ + 0xAD, 0xAD, 0x34F, 0x34F, 0x61C, 0x61C, 0x115F, 0x1160, 0x17B4, 0x17B5, 0x180B, 0x180F, 0x200B, 0x200F, 0x202A, + 0x202E, 0x2060, 0x206F, 0x3164, 0x3164, 0xFE00, 0xFE0F, 0xFEFF, 0xFEFF, 0xFFA0, 0xFFA0, 0xFFF0, 0xFFF8, 0x1BCA0, + 0x1BCA3, 0x1D173, 0x1D17A, 0xE0000, 0xE0FFF, +]; From 87f53498ed1db7709ade57241bc9b47bf05619e0 Mon Sep 17 00:00:00 2001 From: soul Date: Mon, 9 Nov 2026 15:04:59 +0000 Subject: [PATCH 27/76] cultnet-py: write_admitted messageId from embedded Unicode 16.0 tables, not unicodedata Co-Authored-By: Claude Sonnet 5.5 --- .../cultnet-py/src/cultnet_py/messages.py | 34 +++-- .../cultnet_py/unicode_messageid_tables.py | 127 ++++++++++++++++++ 2 files changed, 147 insertions(+), 14 deletions(-) create mode 100644 packages/cultnet-py/src/cultnet_py/unicode_messageid_tables.py diff --git a/packages/cultnet-py/src/cultnet_py/messages.py b/packages/cultnet-py/src/cultnet_py/messages.py index 8ece7f6e..d5e26b7a 100644 --- a/packages/cultnet-py/src/cultnet_py/messages.py +++ b/packages/cultnet-py/src/cultnet_py/messages.py @@ -1,12 +1,13 @@ from __future__ import annotations import hashlib -import unicodedata from dataclasses import dataclass from typing import Any import msgpack +from .unicode_messageid_tables import DEFAULT_IGNORABLE, LETTER_OR_NUMBER + @dataclass(frozen=True) class CultNetMessage: @@ -70,28 +71,33 @@ def _is_name(value: Any) -> bool: return isinstance(value, str) and value != "" -# Default_Ignorable_Code_Point ranges, DerivedCoreProperties.txt, Unicode 16.0 (unchanged since 15.1). This runtime has no -# platform table for the property, so it embeds it; the Kotlin, C# and Rust decoders embed the same ranges, TypeScript uses its -# regex engine's. Letters and numbers come from unicodedata (categories L* and N*); a code point the running Python's Unicode -# data does not assign (U+0378 in every version) is Cn, so it is refused either way. -_DEFAULT_IGNORABLE = ( - (0x00AD, 0x00AD), (0x034F, 0x034F), (0x061C, 0x061C), (0x115F, 0x1160), (0x17B4, 0x17B5), (0x180B, 0x180F), - (0x200B, 0x200F), (0x202A, 0x202E), (0x2060, 0x206F), (0x3164, 0x3164), (0xFE00, 0xFE0F), (0xFEFF, 0xFEFF), - (0xFFA0, 0xFFA0), (0xFFF0, 0xFFF8), (0x1BCA0, 0x1BCA3), (0x1D173, 0x1D17A), (0xE0000, 0xE0FFF), -) +# write-receipt.md: a letter (Lu Ll Lt Lm Lo) or number (Nd Nl No) somewhere, no Default_Ignorable code point anywhere, as of +# Unicode 16.0, compared raw (never stripped). The tables are embedded (unicode_messageid_tables.py, generated by +# scripts/gen-unicode-messageid-tables.py), not unicodedata's, so the running Python's Unicode version changes no result; +# the other four runtimes embed the same generated tables. +def _in_ranges(ranges: tuple[int, ...], point: int) -> bool: + """Whether [point] lies in the flattened, sorted (low, high) pairs.""" + low, high = 0, len(ranges) // 2 - 1 + while low <= high: + mid = (low + high) // 2 + if point < ranges[2 * mid]: + high = mid - 1 + elif point > ranges[2 * mid + 1]: + low = mid + 1 + else: + return True + return False def _is_admitted_message_id(value: Any) -> bool: - """write-receipt.md: a letter or number somewhere, no Default_Ignorable code point anywhere, compared raw (never stripped).""" if not isinstance(value, str): return False has_letter_or_number = False for ch in value: point = ord(ch) - if any(low <= point <= high for low, high in _DEFAULT_IGNORABLE): + if _in_ranges(DEFAULT_IGNORABLE, point): return False - if unicodedata.category(ch)[0] in ("L", "N"): - has_letter_or_number = True + has_letter_or_number = has_letter_or_number or _in_ranges(LETTER_OR_NUMBER, point) return has_letter_or_number diff --git a/packages/cultnet-py/src/cultnet_py/unicode_messageid_tables.py b/packages/cultnet-py/src/cultnet_py/unicode_messageid_tables.py new file mode 100644 index 00000000..163e3c08 --- /dev/null +++ b/packages/cultnet-py/src/cultnet_py/unicode_messageid_tables.py @@ -0,0 +1,127 @@ +# generated, Unicode 16.0.0, by scripts/gen-unicode-messageid-tables.py (inputs sha256 24a07ef9fabf71e3); do not edit + +# (low, high) pairs, flattened and sorted. +LETTER_OR_NUMBER: tuple[int, ...] = ( + 0x30, 0x39, 0x41, 0x5A, 0x61, 0x7A, 0xAA, 0xAA, 0xB2, 0xB3, 0xB5, 0xB5, 0xB9, 0xBA, 0xBC, 0xBE, 0xC0, 0xD6, 0xD8, + 0xF6, 0xF8, 0x2C1, 0x2C6, 0x2D1, 0x2E0, 0x2E4, 0x2EC, 0x2EC, 0x2EE, 0x2EE, 0x370, 0x374, 0x376, 0x377, 0x37A, + 0x37D, 0x37F, 0x37F, 0x386, 0x386, 0x388, 0x38A, 0x38C, 0x38C, 0x38E, 0x3A1, 0x3A3, 0x3F5, 0x3F7, 0x481, 0x48A, + 0x52F, 0x531, 0x556, 0x559, 0x559, 0x560, 0x588, 0x5D0, 0x5EA, 0x5EF, 0x5F2, 0x620, 0x64A, 0x660, 0x669, 0x66E, + 0x66F, 0x671, 0x6D3, 0x6D5, 0x6D5, 0x6E5, 0x6E6, 0x6EE, 0x6FC, 0x6FF, 0x6FF, 0x710, 0x710, 0x712, 0x72F, 0x74D, + 0x7A5, 0x7B1, 0x7B1, 0x7C0, 0x7EA, 0x7F4, 0x7F5, 0x7FA, 0x7FA, 0x800, 0x815, 0x81A, 0x81A, 0x824, 0x824, 0x828, + 0x828, 0x840, 0x858, 0x860, 0x86A, 0x870, 0x887, 0x889, 0x88E, 0x8A0, 0x8C9, 0x904, 0x939, 0x93D, 0x93D, 0x950, + 0x950, 0x958, 0x961, 0x966, 0x96F, 0x971, 0x980, 0x985, 0x98C, 0x98F, 0x990, 0x993, 0x9A8, 0x9AA, 0x9B0, 0x9B2, + 0x9B2, 0x9B6, 0x9B9, 0x9BD, 0x9BD, 0x9CE, 0x9CE, 0x9DC, 0x9DD, 0x9DF, 0x9E1, 0x9E6, 0x9F1, 0x9F4, 0x9F9, 0x9FC, + 0x9FC, 0xA05, 0xA0A, 0xA0F, 0xA10, 0xA13, 0xA28, 0xA2A, 0xA30, 0xA32, 0xA33, 0xA35, 0xA36, 0xA38, 0xA39, 0xA59, + 0xA5C, 0xA5E, 0xA5E, 0xA66, 0xA6F, 0xA72, 0xA74, 0xA85, 0xA8D, 0xA8F, 0xA91, 0xA93, 0xAA8, 0xAAA, 0xAB0, 0xAB2, + 0xAB3, 0xAB5, 0xAB9, 0xABD, 0xABD, 0xAD0, 0xAD0, 0xAE0, 0xAE1, 0xAE6, 0xAEF, 0xAF9, 0xAF9, 0xB05, 0xB0C, 0xB0F, + 0xB10, 0xB13, 0xB28, 0xB2A, 0xB30, 0xB32, 0xB33, 0xB35, 0xB39, 0xB3D, 0xB3D, 0xB5C, 0xB5D, 0xB5F, 0xB61, 0xB66, + 0xB6F, 0xB71, 0xB77, 0xB83, 0xB83, 0xB85, 0xB8A, 0xB8E, 0xB90, 0xB92, 0xB95, 0xB99, 0xB9A, 0xB9C, 0xB9C, 0xB9E, + 0xB9F, 0xBA3, 0xBA4, 0xBA8, 0xBAA, 0xBAE, 0xBB9, 0xBD0, 0xBD0, 0xBE6, 0xBF2, 0xC05, 0xC0C, 0xC0E, 0xC10, 0xC12, + 0xC28, 0xC2A, 0xC39, 0xC3D, 0xC3D, 0xC58, 0xC5A, 0xC5D, 0xC5D, 0xC60, 0xC61, 0xC66, 0xC6F, 0xC78, 0xC7E, 0xC80, + 0xC80, 0xC85, 0xC8C, 0xC8E, 0xC90, 0xC92, 0xCA8, 0xCAA, 0xCB3, 0xCB5, 0xCB9, 0xCBD, 0xCBD, 0xCDD, 0xCDE, 0xCE0, + 0xCE1, 0xCE6, 0xCEF, 0xCF1, 0xCF2, 0xD04, 0xD0C, 0xD0E, 0xD10, 0xD12, 0xD3A, 0xD3D, 0xD3D, 0xD4E, 0xD4E, 0xD54, + 0xD56, 0xD58, 0xD61, 0xD66, 0xD78, 0xD7A, 0xD7F, 0xD85, 0xD96, 0xD9A, 0xDB1, 0xDB3, 0xDBB, 0xDBD, 0xDBD, 0xDC0, + 0xDC6, 0xDE6, 0xDEF, 0xE01, 0xE30, 0xE32, 0xE33, 0xE40, 0xE46, 0xE50, 0xE59, 0xE81, 0xE82, 0xE84, 0xE84, 0xE86, + 0xE8A, 0xE8C, 0xEA3, 0xEA5, 0xEA5, 0xEA7, 0xEB0, 0xEB2, 0xEB3, 0xEBD, 0xEBD, 0xEC0, 0xEC4, 0xEC6, 0xEC6, 0xED0, + 0xED9, 0xEDC, 0xEDF, 0xF00, 0xF00, 0xF20, 0xF33, 0xF40, 0xF47, 0xF49, 0xF6C, 0xF88, 0xF8C, 0x1000, 0x102A, + 0x103F, 0x1049, 0x1050, 0x1055, 0x105A, 0x105D, 0x1061, 0x1061, 0x1065, 0x1066, 0x106E, 0x1070, 0x1075, 0x1081, + 0x108E, 0x108E, 0x1090, 0x1099, 0x10A0, 0x10C5, 0x10C7, 0x10C7, 0x10CD, 0x10CD, 0x10D0, 0x10FA, 0x10FC, 0x1248, + 0x124A, 0x124D, 0x1250, 0x1256, 0x1258, 0x1258, 0x125A, 0x125D, 0x1260, 0x1288, 0x128A, 0x128D, 0x1290, 0x12B0, + 0x12B2, 0x12B5, 0x12B8, 0x12BE, 0x12C0, 0x12C0, 0x12C2, 0x12C5, 0x12C8, 0x12D6, 0x12D8, 0x1310, 0x1312, 0x1315, + 0x1318, 0x135A, 0x1369, 0x137C, 0x1380, 0x138F, 0x13A0, 0x13F5, 0x13F8, 0x13FD, 0x1401, 0x166C, 0x166F, 0x167F, + 0x1681, 0x169A, 0x16A0, 0x16EA, 0x16EE, 0x16F8, 0x1700, 0x1711, 0x171F, 0x1731, 0x1740, 0x1751, 0x1760, 0x176C, + 0x176E, 0x1770, 0x1780, 0x17B3, 0x17D7, 0x17D7, 0x17DC, 0x17DC, 0x17E0, 0x17E9, 0x17F0, 0x17F9, 0x1810, 0x1819, + 0x1820, 0x1878, 0x1880, 0x1884, 0x1887, 0x18A8, 0x18AA, 0x18AA, 0x18B0, 0x18F5, 0x1900, 0x191E, 0x1946, 0x196D, + 0x1970, 0x1974, 0x1980, 0x19AB, 0x19B0, 0x19C9, 0x19D0, 0x19DA, 0x1A00, 0x1A16, 0x1A20, 0x1A54, 0x1A80, 0x1A89, + 0x1A90, 0x1A99, 0x1AA7, 0x1AA7, 0x1B05, 0x1B33, 0x1B45, 0x1B4C, 0x1B50, 0x1B59, 0x1B83, 0x1BA0, 0x1BAE, 0x1BE5, + 0x1C00, 0x1C23, 0x1C40, 0x1C49, 0x1C4D, 0x1C7D, 0x1C80, 0x1C8A, 0x1C90, 0x1CBA, 0x1CBD, 0x1CBF, 0x1CE9, 0x1CEC, + 0x1CEE, 0x1CF3, 0x1CF5, 0x1CF6, 0x1CFA, 0x1CFA, 0x1D00, 0x1DBF, 0x1E00, 0x1F15, 0x1F18, 0x1F1D, 0x1F20, 0x1F45, + 0x1F48, 0x1F4D, 0x1F50, 0x1F57, 0x1F59, 0x1F59, 0x1F5B, 0x1F5B, 0x1F5D, 0x1F5D, 0x1F5F, 0x1F7D, 0x1F80, 0x1FB4, + 0x1FB6, 0x1FBC, 0x1FBE, 0x1FBE, 0x1FC2, 0x1FC4, 0x1FC6, 0x1FCC, 0x1FD0, 0x1FD3, 0x1FD6, 0x1FDB, 0x1FE0, 0x1FEC, + 0x1FF2, 0x1FF4, 0x1FF6, 0x1FFC, 0x2070, 0x2071, 0x2074, 0x2079, 0x207F, 0x2089, 0x2090, 0x209C, 0x2102, 0x2102, + 0x2107, 0x2107, 0x210A, 0x2113, 0x2115, 0x2115, 0x2119, 0x211D, 0x2124, 0x2124, 0x2126, 0x2126, 0x2128, 0x2128, + 0x212A, 0x212D, 0x212F, 0x2139, 0x213C, 0x213F, 0x2145, 0x2149, 0x214E, 0x214E, 0x2150, 0x2189, 0x2460, 0x249B, + 0x24EA, 0x24FF, 0x2776, 0x2793, 0x2C00, 0x2CE4, 0x2CEB, 0x2CEE, 0x2CF2, 0x2CF3, 0x2CFD, 0x2CFD, 0x2D00, 0x2D25, + 0x2D27, 0x2D27, 0x2D2D, 0x2D2D, 0x2D30, 0x2D67, 0x2D6F, 0x2D6F, 0x2D80, 0x2D96, 0x2DA0, 0x2DA6, 0x2DA8, 0x2DAE, + 0x2DB0, 0x2DB6, 0x2DB8, 0x2DBE, 0x2DC0, 0x2DC6, 0x2DC8, 0x2DCE, 0x2DD0, 0x2DD6, 0x2DD8, 0x2DDE, 0x2E2F, 0x2E2F, + 0x3005, 0x3007, 0x3021, 0x3029, 0x3031, 0x3035, 0x3038, 0x303C, 0x3041, 0x3096, 0x309D, 0x309F, 0x30A1, 0x30FA, + 0x30FC, 0x30FF, 0x3105, 0x312F, 0x3131, 0x318E, 0x3192, 0x3195, 0x31A0, 0x31BF, 0x31F0, 0x31FF, 0x3220, 0x3229, + 0x3248, 0x324F, 0x3251, 0x325F, 0x3280, 0x3289, 0x32B1, 0x32BF, 0x3400, 0x4DBF, 0x4E00, 0xA48C, 0xA4D0, 0xA4FD, + 0xA500, 0xA60C, 0xA610, 0xA62B, 0xA640, 0xA66E, 0xA67F, 0xA69D, 0xA6A0, 0xA6EF, 0xA717, 0xA71F, 0xA722, 0xA788, + 0xA78B, 0xA7CD, 0xA7D0, 0xA7D1, 0xA7D3, 0xA7D3, 0xA7D5, 0xA7DC, 0xA7F2, 0xA801, 0xA803, 0xA805, 0xA807, 0xA80A, + 0xA80C, 0xA822, 0xA830, 0xA835, 0xA840, 0xA873, 0xA882, 0xA8B3, 0xA8D0, 0xA8D9, 0xA8F2, 0xA8F7, 0xA8FB, 0xA8FB, + 0xA8FD, 0xA8FE, 0xA900, 0xA925, 0xA930, 0xA946, 0xA960, 0xA97C, 0xA984, 0xA9B2, 0xA9CF, 0xA9D9, 0xA9E0, 0xA9E4, + 0xA9E6, 0xA9FE, 0xAA00, 0xAA28, 0xAA40, 0xAA42, 0xAA44, 0xAA4B, 0xAA50, 0xAA59, 0xAA60, 0xAA76, 0xAA7A, 0xAA7A, + 0xAA7E, 0xAAAF, 0xAAB1, 0xAAB1, 0xAAB5, 0xAAB6, 0xAAB9, 0xAABD, 0xAAC0, 0xAAC0, 0xAAC2, 0xAAC2, 0xAADB, 0xAADD, + 0xAAE0, 0xAAEA, 0xAAF2, 0xAAF4, 0xAB01, 0xAB06, 0xAB09, 0xAB0E, 0xAB11, 0xAB16, 0xAB20, 0xAB26, 0xAB28, 0xAB2E, + 0xAB30, 0xAB5A, 0xAB5C, 0xAB69, 0xAB70, 0xABE2, 0xABF0, 0xABF9, 0xAC00, 0xD7A3, 0xD7B0, 0xD7C6, 0xD7CB, 0xD7FB, + 0xF900, 0xFA6D, 0xFA70, 0xFAD9, 0xFB00, 0xFB06, 0xFB13, 0xFB17, 0xFB1D, 0xFB1D, 0xFB1F, 0xFB28, 0xFB2A, 0xFB36, + 0xFB38, 0xFB3C, 0xFB3E, 0xFB3E, 0xFB40, 0xFB41, 0xFB43, 0xFB44, 0xFB46, 0xFBB1, 0xFBD3, 0xFD3D, 0xFD50, 0xFD8F, + 0xFD92, 0xFDC7, 0xFDF0, 0xFDFB, 0xFE70, 0xFE74, 0xFE76, 0xFEFC, 0xFF10, 0xFF19, 0xFF21, 0xFF3A, 0xFF41, 0xFF5A, + 0xFF66, 0xFFBE, 0xFFC2, 0xFFC7, 0xFFCA, 0xFFCF, 0xFFD2, 0xFFD7, 0xFFDA, 0xFFDC, 0x10000, 0x1000B, 0x1000D, + 0x10026, 0x10028, 0x1003A, 0x1003C, 0x1003D, 0x1003F, 0x1004D, 0x10050, 0x1005D, 0x10080, 0x100FA, 0x10107, + 0x10133, 0x10140, 0x10178, 0x1018A, 0x1018B, 0x10280, 0x1029C, 0x102A0, 0x102D0, 0x102E1, 0x102FB, 0x10300, + 0x10323, 0x1032D, 0x1034A, 0x10350, 0x10375, 0x10380, 0x1039D, 0x103A0, 0x103C3, 0x103C8, 0x103CF, 0x103D1, + 0x103D5, 0x10400, 0x1049D, 0x104A0, 0x104A9, 0x104B0, 0x104D3, 0x104D8, 0x104FB, 0x10500, 0x10527, 0x10530, + 0x10563, 0x10570, 0x1057A, 0x1057C, 0x1058A, 0x1058C, 0x10592, 0x10594, 0x10595, 0x10597, 0x105A1, 0x105A3, + 0x105B1, 0x105B3, 0x105B9, 0x105BB, 0x105BC, 0x105C0, 0x105F3, 0x10600, 0x10736, 0x10740, 0x10755, 0x10760, + 0x10767, 0x10780, 0x10785, 0x10787, 0x107B0, 0x107B2, 0x107BA, 0x10800, 0x10805, 0x10808, 0x10808, 0x1080A, + 0x10835, 0x10837, 0x10838, 0x1083C, 0x1083C, 0x1083F, 0x10855, 0x10858, 0x10876, 0x10879, 0x1089E, 0x108A7, + 0x108AF, 0x108E0, 0x108F2, 0x108F4, 0x108F5, 0x108FB, 0x1091B, 0x10920, 0x10939, 0x10980, 0x109B7, 0x109BC, + 0x109CF, 0x109D2, 0x10A00, 0x10A10, 0x10A13, 0x10A15, 0x10A17, 0x10A19, 0x10A35, 0x10A40, 0x10A48, 0x10A60, + 0x10A7E, 0x10A80, 0x10A9F, 0x10AC0, 0x10AC7, 0x10AC9, 0x10AE4, 0x10AEB, 0x10AEF, 0x10B00, 0x10B35, 0x10B40, + 0x10B55, 0x10B58, 0x10B72, 0x10B78, 0x10B91, 0x10BA9, 0x10BAF, 0x10C00, 0x10C48, 0x10C80, 0x10CB2, 0x10CC0, + 0x10CF2, 0x10CFA, 0x10D23, 0x10D30, 0x10D39, 0x10D40, 0x10D65, 0x10D6F, 0x10D85, 0x10E60, 0x10E7E, 0x10E80, + 0x10EA9, 0x10EB0, 0x10EB1, 0x10EC2, 0x10EC4, 0x10F00, 0x10F27, 0x10F30, 0x10F45, 0x10F51, 0x10F54, 0x10F70, + 0x10F81, 0x10FB0, 0x10FCB, 0x10FE0, 0x10FF6, 0x11003, 0x11037, 0x11052, 0x1106F, 0x11071, 0x11072, 0x11075, + 0x11075, 0x11083, 0x110AF, 0x110D0, 0x110E8, 0x110F0, 0x110F9, 0x11103, 0x11126, 0x11136, 0x1113F, 0x11144, + 0x11144, 0x11147, 0x11147, 0x11150, 0x11172, 0x11176, 0x11176, 0x11183, 0x111B2, 0x111C1, 0x111C4, 0x111D0, + 0x111DA, 0x111DC, 0x111DC, 0x111E1, 0x111F4, 0x11200, 0x11211, 0x11213, 0x1122B, 0x1123F, 0x11240, 0x11280, + 0x11286, 0x11288, 0x11288, 0x1128A, 0x1128D, 0x1128F, 0x1129D, 0x1129F, 0x112A8, 0x112B0, 0x112DE, 0x112F0, + 0x112F9, 0x11305, 0x1130C, 0x1130F, 0x11310, 0x11313, 0x11328, 0x1132A, 0x11330, 0x11332, 0x11333, 0x11335, + 0x11339, 0x1133D, 0x1133D, 0x11350, 0x11350, 0x1135D, 0x11361, 0x11380, 0x11389, 0x1138B, 0x1138B, 0x1138E, + 0x1138E, 0x11390, 0x113B5, 0x113B7, 0x113B7, 0x113D1, 0x113D1, 0x113D3, 0x113D3, 0x11400, 0x11434, 0x11447, + 0x1144A, 0x11450, 0x11459, 0x1145F, 0x11461, 0x11480, 0x114AF, 0x114C4, 0x114C5, 0x114C7, 0x114C7, 0x114D0, + 0x114D9, 0x11580, 0x115AE, 0x115D8, 0x115DB, 0x11600, 0x1162F, 0x11644, 0x11644, 0x11650, 0x11659, 0x11680, + 0x116AA, 0x116B8, 0x116B8, 0x116C0, 0x116C9, 0x116D0, 0x116E3, 0x11700, 0x1171A, 0x11730, 0x1173B, 0x11740, + 0x11746, 0x11800, 0x1182B, 0x118A0, 0x118F2, 0x118FF, 0x11906, 0x11909, 0x11909, 0x1190C, 0x11913, 0x11915, + 0x11916, 0x11918, 0x1192F, 0x1193F, 0x1193F, 0x11941, 0x11941, 0x11950, 0x11959, 0x119A0, 0x119A7, 0x119AA, + 0x119D0, 0x119E1, 0x119E1, 0x119E3, 0x119E3, 0x11A00, 0x11A00, 0x11A0B, 0x11A32, 0x11A3A, 0x11A3A, 0x11A50, + 0x11A50, 0x11A5C, 0x11A89, 0x11A9D, 0x11A9D, 0x11AB0, 0x11AF8, 0x11BC0, 0x11BE0, 0x11BF0, 0x11BF9, 0x11C00, + 0x11C08, 0x11C0A, 0x11C2E, 0x11C40, 0x11C40, 0x11C50, 0x11C6C, 0x11C72, 0x11C8F, 0x11D00, 0x11D06, 0x11D08, + 0x11D09, 0x11D0B, 0x11D30, 0x11D46, 0x11D46, 0x11D50, 0x11D59, 0x11D60, 0x11D65, 0x11D67, 0x11D68, 0x11D6A, + 0x11D89, 0x11D98, 0x11D98, 0x11DA0, 0x11DA9, 0x11EE0, 0x11EF2, 0x11F02, 0x11F02, 0x11F04, 0x11F10, 0x11F12, + 0x11F33, 0x11F50, 0x11F59, 0x11FB0, 0x11FB0, 0x11FC0, 0x11FD4, 0x12000, 0x12399, 0x12400, 0x1246E, 0x12480, + 0x12543, 0x12F90, 0x12FF0, 0x13000, 0x1342F, 0x13441, 0x13446, 0x13460, 0x143FA, 0x14400, 0x14646, 0x16100, + 0x1611D, 0x16130, 0x16139, 0x16800, 0x16A38, 0x16A40, 0x16A5E, 0x16A60, 0x16A69, 0x16A70, 0x16ABE, 0x16AC0, + 0x16AC9, 0x16AD0, 0x16AED, 0x16B00, 0x16B2F, 0x16B40, 0x16B43, 0x16B50, 0x16B59, 0x16B5B, 0x16B61, 0x16B63, + 0x16B77, 0x16B7D, 0x16B8F, 0x16D40, 0x16D6C, 0x16D70, 0x16D79, 0x16E40, 0x16E96, 0x16F00, 0x16F4A, 0x16F50, + 0x16F50, 0x16F93, 0x16F9F, 0x16FE0, 0x16FE1, 0x16FE3, 0x16FE3, 0x17000, 0x187F7, 0x18800, 0x18CD5, 0x18CFF, + 0x18D08, 0x1AFF0, 0x1AFF3, 0x1AFF5, 0x1AFFB, 0x1AFFD, 0x1AFFE, 0x1B000, 0x1B122, 0x1B132, 0x1B132, 0x1B150, + 0x1B152, 0x1B155, 0x1B155, 0x1B164, 0x1B167, 0x1B170, 0x1B2FB, 0x1BC00, 0x1BC6A, 0x1BC70, 0x1BC7C, 0x1BC80, + 0x1BC88, 0x1BC90, 0x1BC99, 0x1CCF0, 0x1CCF9, 0x1D2C0, 0x1D2D3, 0x1D2E0, 0x1D2F3, 0x1D360, 0x1D378, 0x1D400, + 0x1D454, 0x1D456, 0x1D49C, 0x1D49E, 0x1D49F, 0x1D4A2, 0x1D4A2, 0x1D4A5, 0x1D4A6, 0x1D4A9, 0x1D4AC, 0x1D4AE, + 0x1D4B9, 0x1D4BB, 0x1D4BB, 0x1D4BD, 0x1D4C3, 0x1D4C5, 0x1D505, 0x1D507, 0x1D50A, 0x1D50D, 0x1D514, 0x1D516, + 0x1D51C, 0x1D51E, 0x1D539, 0x1D53B, 0x1D53E, 0x1D540, 0x1D544, 0x1D546, 0x1D546, 0x1D54A, 0x1D550, 0x1D552, + 0x1D6A5, 0x1D6A8, 0x1D6C0, 0x1D6C2, 0x1D6DA, 0x1D6DC, 0x1D6FA, 0x1D6FC, 0x1D714, 0x1D716, 0x1D734, 0x1D736, + 0x1D74E, 0x1D750, 0x1D76E, 0x1D770, 0x1D788, 0x1D78A, 0x1D7A8, 0x1D7AA, 0x1D7C2, 0x1D7C4, 0x1D7CB, 0x1D7CE, + 0x1D7FF, 0x1DF00, 0x1DF1E, 0x1DF25, 0x1DF2A, 0x1E030, 0x1E06D, 0x1E100, 0x1E12C, 0x1E137, 0x1E13D, 0x1E140, + 0x1E149, 0x1E14E, 0x1E14E, 0x1E290, 0x1E2AD, 0x1E2C0, 0x1E2EB, 0x1E2F0, 0x1E2F9, 0x1E4D0, 0x1E4EB, 0x1E4F0, + 0x1E4F9, 0x1E5D0, 0x1E5ED, 0x1E5F0, 0x1E5FA, 0x1E7E0, 0x1E7E6, 0x1E7E8, 0x1E7EB, 0x1E7ED, 0x1E7EE, 0x1E7F0, + 0x1E7FE, 0x1E800, 0x1E8C4, 0x1E8C7, 0x1E8CF, 0x1E900, 0x1E943, 0x1E94B, 0x1E94B, 0x1E950, 0x1E959, 0x1EC71, + 0x1ECAB, 0x1ECAD, 0x1ECAF, 0x1ECB1, 0x1ECB4, 0x1ED01, 0x1ED2D, 0x1ED2F, 0x1ED3D, 0x1EE00, 0x1EE03, 0x1EE05, + 0x1EE1F, 0x1EE21, 0x1EE22, 0x1EE24, 0x1EE24, 0x1EE27, 0x1EE27, 0x1EE29, 0x1EE32, 0x1EE34, 0x1EE37, 0x1EE39, + 0x1EE39, 0x1EE3B, 0x1EE3B, 0x1EE42, 0x1EE42, 0x1EE47, 0x1EE47, 0x1EE49, 0x1EE49, 0x1EE4B, 0x1EE4B, 0x1EE4D, + 0x1EE4F, 0x1EE51, 0x1EE52, 0x1EE54, 0x1EE54, 0x1EE57, 0x1EE57, 0x1EE59, 0x1EE59, 0x1EE5B, 0x1EE5B, 0x1EE5D, + 0x1EE5D, 0x1EE5F, 0x1EE5F, 0x1EE61, 0x1EE62, 0x1EE64, 0x1EE64, 0x1EE67, 0x1EE6A, 0x1EE6C, 0x1EE72, 0x1EE74, + 0x1EE77, 0x1EE79, 0x1EE7C, 0x1EE7E, 0x1EE7E, 0x1EE80, 0x1EE89, 0x1EE8B, 0x1EE9B, 0x1EEA1, 0x1EEA3, 0x1EEA5, + 0x1EEA9, 0x1EEAB, 0x1EEBB, 0x1F100, 0x1F10C, 0x1FBF0, 0x1FBF9, 0x20000, 0x2A6DF, 0x2A700, 0x2B739, 0x2B740, + 0x2B81D, 0x2B820, 0x2CEA1, 0x2CEB0, 0x2EBE0, 0x2EBF0, 0x2EE5D, 0x2F800, 0x2FA1D, 0x30000, 0x3134A, 0x31350, + 0x323AF, +) + +DEFAULT_IGNORABLE: tuple[int, ...] = ( + 0xAD, 0xAD, 0x34F, 0x34F, 0x61C, 0x61C, 0x115F, 0x1160, 0x17B4, 0x17B5, 0x180B, 0x180F, 0x200B, 0x200F, 0x202A, + 0x202E, 0x2060, 0x206F, 0x3164, 0x3164, 0xFE00, 0xFE0F, 0xFEFF, 0xFEFF, 0xFFA0, 0xFFA0, 0xFFF0, 0xFFF8, 0x1BCA0, + 0x1BCA3, 0x1D173, 0x1D17A, 0xE0000, 0xE0FFF, +) From c4f6e768395e2467346320c7ecff270791265ce5 Mon Sep 17 00:00:00 2001 From: soul Date: Mon, 9 Nov 2026 15:04:59 +0000 Subject: [PATCH 28/76] cultmesh-kotlin: write_admitted messageId from embedded Unicode 16.0 tables, not the JDK's Co-Authored-By: Claude Sonnet 5.5 --- .../cultmesh/UnicodeMessageIdTables.kt | 129 ++++++++++++++++++ .../org/gamecult/cultmesh/WriteReceipt.kt | 34 +++-- 2 files changed, 148 insertions(+), 15 deletions(-) create mode 100644 packages/cultmesh-kotlin/src/main/kotlin/org/gamecult/cultmesh/UnicodeMessageIdTables.kt diff --git a/packages/cultmesh-kotlin/src/main/kotlin/org/gamecult/cultmesh/UnicodeMessageIdTables.kt b/packages/cultmesh-kotlin/src/main/kotlin/org/gamecult/cultmesh/UnicodeMessageIdTables.kt new file mode 100644 index 00000000..7bc4bdc8 --- /dev/null +++ b/packages/cultmesh-kotlin/src/main/kotlin/org/gamecult/cultmesh/UnicodeMessageIdTables.kt @@ -0,0 +1,129 @@ +// generated, Unicode 16.0.0, by scripts/gen-unicode-messageid-tables.py (inputs sha256 24a07ef9fabf71e3); do not edit + +package org.gamecult.cultmesh + +// (low, high) pairs, flattened and sorted. +internal val LETTER_OR_NUMBER: IntArray = intArrayOf( + 0x30, 0x39, 0x41, 0x5A, 0x61, 0x7A, 0xAA, 0xAA, 0xB2, 0xB3, 0xB5, 0xB5, 0xB9, 0xBA, 0xBC, 0xBE, 0xC0, 0xD6, 0xD8, + 0xF6, 0xF8, 0x2C1, 0x2C6, 0x2D1, 0x2E0, 0x2E4, 0x2EC, 0x2EC, 0x2EE, 0x2EE, 0x370, 0x374, 0x376, 0x377, 0x37A, + 0x37D, 0x37F, 0x37F, 0x386, 0x386, 0x388, 0x38A, 0x38C, 0x38C, 0x38E, 0x3A1, 0x3A3, 0x3F5, 0x3F7, 0x481, 0x48A, + 0x52F, 0x531, 0x556, 0x559, 0x559, 0x560, 0x588, 0x5D0, 0x5EA, 0x5EF, 0x5F2, 0x620, 0x64A, 0x660, 0x669, 0x66E, + 0x66F, 0x671, 0x6D3, 0x6D5, 0x6D5, 0x6E5, 0x6E6, 0x6EE, 0x6FC, 0x6FF, 0x6FF, 0x710, 0x710, 0x712, 0x72F, 0x74D, + 0x7A5, 0x7B1, 0x7B1, 0x7C0, 0x7EA, 0x7F4, 0x7F5, 0x7FA, 0x7FA, 0x800, 0x815, 0x81A, 0x81A, 0x824, 0x824, 0x828, + 0x828, 0x840, 0x858, 0x860, 0x86A, 0x870, 0x887, 0x889, 0x88E, 0x8A0, 0x8C9, 0x904, 0x939, 0x93D, 0x93D, 0x950, + 0x950, 0x958, 0x961, 0x966, 0x96F, 0x971, 0x980, 0x985, 0x98C, 0x98F, 0x990, 0x993, 0x9A8, 0x9AA, 0x9B0, 0x9B2, + 0x9B2, 0x9B6, 0x9B9, 0x9BD, 0x9BD, 0x9CE, 0x9CE, 0x9DC, 0x9DD, 0x9DF, 0x9E1, 0x9E6, 0x9F1, 0x9F4, 0x9F9, 0x9FC, + 0x9FC, 0xA05, 0xA0A, 0xA0F, 0xA10, 0xA13, 0xA28, 0xA2A, 0xA30, 0xA32, 0xA33, 0xA35, 0xA36, 0xA38, 0xA39, 0xA59, + 0xA5C, 0xA5E, 0xA5E, 0xA66, 0xA6F, 0xA72, 0xA74, 0xA85, 0xA8D, 0xA8F, 0xA91, 0xA93, 0xAA8, 0xAAA, 0xAB0, 0xAB2, + 0xAB3, 0xAB5, 0xAB9, 0xABD, 0xABD, 0xAD0, 0xAD0, 0xAE0, 0xAE1, 0xAE6, 0xAEF, 0xAF9, 0xAF9, 0xB05, 0xB0C, 0xB0F, + 0xB10, 0xB13, 0xB28, 0xB2A, 0xB30, 0xB32, 0xB33, 0xB35, 0xB39, 0xB3D, 0xB3D, 0xB5C, 0xB5D, 0xB5F, 0xB61, 0xB66, + 0xB6F, 0xB71, 0xB77, 0xB83, 0xB83, 0xB85, 0xB8A, 0xB8E, 0xB90, 0xB92, 0xB95, 0xB99, 0xB9A, 0xB9C, 0xB9C, 0xB9E, + 0xB9F, 0xBA3, 0xBA4, 0xBA8, 0xBAA, 0xBAE, 0xBB9, 0xBD0, 0xBD0, 0xBE6, 0xBF2, 0xC05, 0xC0C, 0xC0E, 0xC10, 0xC12, + 0xC28, 0xC2A, 0xC39, 0xC3D, 0xC3D, 0xC58, 0xC5A, 0xC5D, 0xC5D, 0xC60, 0xC61, 0xC66, 0xC6F, 0xC78, 0xC7E, 0xC80, + 0xC80, 0xC85, 0xC8C, 0xC8E, 0xC90, 0xC92, 0xCA8, 0xCAA, 0xCB3, 0xCB5, 0xCB9, 0xCBD, 0xCBD, 0xCDD, 0xCDE, 0xCE0, + 0xCE1, 0xCE6, 0xCEF, 0xCF1, 0xCF2, 0xD04, 0xD0C, 0xD0E, 0xD10, 0xD12, 0xD3A, 0xD3D, 0xD3D, 0xD4E, 0xD4E, 0xD54, + 0xD56, 0xD58, 0xD61, 0xD66, 0xD78, 0xD7A, 0xD7F, 0xD85, 0xD96, 0xD9A, 0xDB1, 0xDB3, 0xDBB, 0xDBD, 0xDBD, 0xDC0, + 0xDC6, 0xDE6, 0xDEF, 0xE01, 0xE30, 0xE32, 0xE33, 0xE40, 0xE46, 0xE50, 0xE59, 0xE81, 0xE82, 0xE84, 0xE84, 0xE86, + 0xE8A, 0xE8C, 0xEA3, 0xEA5, 0xEA5, 0xEA7, 0xEB0, 0xEB2, 0xEB3, 0xEBD, 0xEBD, 0xEC0, 0xEC4, 0xEC6, 0xEC6, 0xED0, + 0xED9, 0xEDC, 0xEDF, 0xF00, 0xF00, 0xF20, 0xF33, 0xF40, 0xF47, 0xF49, 0xF6C, 0xF88, 0xF8C, 0x1000, 0x102A, + 0x103F, 0x1049, 0x1050, 0x1055, 0x105A, 0x105D, 0x1061, 0x1061, 0x1065, 0x1066, 0x106E, 0x1070, 0x1075, 0x1081, + 0x108E, 0x108E, 0x1090, 0x1099, 0x10A0, 0x10C5, 0x10C7, 0x10C7, 0x10CD, 0x10CD, 0x10D0, 0x10FA, 0x10FC, 0x1248, + 0x124A, 0x124D, 0x1250, 0x1256, 0x1258, 0x1258, 0x125A, 0x125D, 0x1260, 0x1288, 0x128A, 0x128D, 0x1290, 0x12B0, + 0x12B2, 0x12B5, 0x12B8, 0x12BE, 0x12C0, 0x12C0, 0x12C2, 0x12C5, 0x12C8, 0x12D6, 0x12D8, 0x1310, 0x1312, 0x1315, + 0x1318, 0x135A, 0x1369, 0x137C, 0x1380, 0x138F, 0x13A0, 0x13F5, 0x13F8, 0x13FD, 0x1401, 0x166C, 0x166F, 0x167F, + 0x1681, 0x169A, 0x16A0, 0x16EA, 0x16EE, 0x16F8, 0x1700, 0x1711, 0x171F, 0x1731, 0x1740, 0x1751, 0x1760, 0x176C, + 0x176E, 0x1770, 0x1780, 0x17B3, 0x17D7, 0x17D7, 0x17DC, 0x17DC, 0x17E0, 0x17E9, 0x17F0, 0x17F9, 0x1810, 0x1819, + 0x1820, 0x1878, 0x1880, 0x1884, 0x1887, 0x18A8, 0x18AA, 0x18AA, 0x18B0, 0x18F5, 0x1900, 0x191E, 0x1946, 0x196D, + 0x1970, 0x1974, 0x1980, 0x19AB, 0x19B0, 0x19C9, 0x19D0, 0x19DA, 0x1A00, 0x1A16, 0x1A20, 0x1A54, 0x1A80, 0x1A89, + 0x1A90, 0x1A99, 0x1AA7, 0x1AA7, 0x1B05, 0x1B33, 0x1B45, 0x1B4C, 0x1B50, 0x1B59, 0x1B83, 0x1BA0, 0x1BAE, 0x1BE5, + 0x1C00, 0x1C23, 0x1C40, 0x1C49, 0x1C4D, 0x1C7D, 0x1C80, 0x1C8A, 0x1C90, 0x1CBA, 0x1CBD, 0x1CBF, 0x1CE9, 0x1CEC, + 0x1CEE, 0x1CF3, 0x1CF5, 0x1CF6, 0x1CFA, 0x1CFA, 0x1D00, 0x1DBF, 0x1E00, 0x1F15, 0x1F18, 0x1F1D, 0x1F20, 0x1F45, + 0x1F48, 0x1F4D, 0x1F50, 0x1F57, 0x1F59, 0x1F59, 0x1F5B, 0x1F5B, 0x1F5D, 0x1F5D, 0x1F5F, 0x1F7D, 0x1F80, 0x1FB4, + 0x1FB6, 0x1FBC, 0x1FBE, 0x1FBE, 0x1FC2, 0x1FC4, 0x1FC6, 0x1FCC, 0x1FD0, 0x1FD3, 0x1FD6, 0x1FDB, 0x1FE0, 0x1FEC, + 0x1FF2, 0x1FF4, 0x1FF6, 0x1FFC, 0x2070, 0x2071, 0x2074, 0x2079, 0x207F, 0x2089, 0x2090, 0x209C, 0x2102, 0x2102, + 0x2107, 0x2107, 0x210A, 0x2113, 0x2115, 0x2115, 0x2119, 0x211D, 0x2124, 0x2124, 0x2126, 0x2126, 0x2128, 0x2128, + 0x212A, 0x212D, 0x212F, 0x2139, 0x213C, 0x213F, 0x2145, 0x2149, 0x214E, 0x214E, 0x2150, 0x2189, 0x2460, 0x249B, + 0x24EA, 0x24FF, 0x2776, 0x2793, 0x2C00, 0x2CE4, 0x2CEB, 0x2CEE, 0x2CF2, 0x2CF3, 0x2CFD, 0x2CFD, 0x2D00, 0x2D25, + 0x2D27, 0x2D27, 0x2D2D, 0x2D2D, 0x2D30, 0x2D67, 0x2D6F, 0x2D6F, 0x2D80, 0x2D96, 0x2DA0, 0x2DA6, 0x2DA8, 0x2DAE, + 0x2DB0, 0x2DB6, 0x2DB8, 0x2DBE, 0x2DC0, 0x2DC6, 0x2DC8, 0x2DCE, 0x2DD0, 0x2DD6, 0x2DD8, 0x2DDE, 0x2E2F, 0x2E2F, + 0x3005, 0x3007, 0x3021, 0x3029, 0x3031, 0x3035, 0x3038, 0x303C, 0x3041, 0x3096, 0x309D, 0x309F, 0x30A1, 0x30FA, + 0x30FC, 0x30FF, 0x3105, 0x312F, 0x3131, 0x318E, 0x3192, 0x3195, 0x31A0, 0x31BF, 0x31F0, 0x31FF, 0x3220, 0x3229, + 0x3248, 0x324F, 0x3251, 0x325F, 0x3280, 0x3289, 0x32B1, 0x32BF, 0x3400, 0x4DBF, 0x4E00, 0xA48C, 0xA4D0, 0xA4FD, + 0xA500, 0xA60C, 0xA610, 0xA62B, 0xA640, 0xA66E, 0xA67F, 0xA69D, 0xA6A0, 0xA6EF, 0xA717, 0xA71F, 0xA722, 0xA788, + 0xA78B, 0xA7CD, 0xA7D0, 0xA7D1, 0xA7D3, 0xA7D3, 0xA7D5, 0xA7DC, 0xA7F2, 0xA801, 0xA803, 0xA805, 0xA807, 0xA80A, + 0xA80C, 0xA822, 0xA830, 0xA835, 0xA840, 0xA873, 0xA882, 0xA8B3, 0xA8D0, 0xA8D9, 0xA8F2, 0xA8F7, 0xA8FB, 0xA8FB, + 0xA8FD, 0xA8FE, 0xA900, 0xA925, 0xA930, 0xA946, 0xA960, 0xA97C, 0xA984, 0xA9B2, 0xA9CF, 0xA9D9, 0xA9E0, 0xA9E4, + 0xA9E6, 0xA9FE, 0xAA00, 0xAA28, 0xAA40, 0xAA42, 0xAA44, 0xAA4B, 0xAA50, 0xAA59, 0xAA60, 0xAA76, 0xAA7A, 0xAA7A, + 0xAA7E, 0xAAAF, 0xAAB1, 0xAAB1, 0xAAB5, 0xAAB6, 0xAAB9, 0xAABD, 0xAAC0, 0xAAC0, 0xAAC2, 0xAAC2, 0xAADB, 0xAADD, + 0xAAE0, 0xAAEA, 0xAAF2, 0xAAF4, 0xAB01, 0xAB06, 0xAB09, 0xAB0E, 0xAB11, 0xAB16, 0xAB20, 0xAB26, 0xAB28, 0xAB2E, + 0xAB30, 0xAB5A, 0xAB5C, 0xAB69, 0xAB70, 0xABE2, 0xABF0, 0xABF9, 0xAC00, 0xD7A3, 0xD7B0, 0xD7C6, 0xD7CB, 0xD7FB, + 0xF900, 0xFA6D, 0xFA70, 0xFAD9, 0xFB00, 0xFB06, 0xFB13, 0xFB17, 0xFB1D, 0xFB1D, 0xFB1F, 0xFB28, 0xFB2A, 0xFB36, + 0xFB38, 0xFB3C, 0xFB3E, 0xFB3E, 0xFB40, 0xFB41, 0xFB43, 0xFB44, 0xFB46, 0xFBB1, 0xFBD3, 0xFD3D, 0xFD50, 0xFD8F, + 0xFD92, 0xFDC7, 0xFDF0, 0xFDFB, 0xFE70, 0xFE74, 0xFE76, 0xFEFC, 0xFF10, 0xFF19, 0xFF21, 0xFF3A, 0xFF41, 0xFF5A, + 0xFF66, 0xFFBE, 0xFFC2, 0xFFC7, 0xFFCA, 0xFFCF, 0xFFD2, 0xFFD7, 0xFFDA, 0xFFDC, 0x10000, 0x1000B, 0x1000D, + 0x10026, 0x10028, 0x1003A, 0x1003C, 0x1003D, 0x1003F, 0x1004D, 0x10050, 0x1005D, 0x10080, 0x100FA, 0x10107, + 0x10133, 0x10140, 0x10178, 0x1018A, 0x1018B, 0x10280, 0x1029C, 0x102A0, 0x102D0, 0x102E1, 0x102FB, 0x10300, + 0x10323, 0x1032D, 0x1034A, 0x10350, 0x10375, 0x10380, 0x1039D, 0x103A0, 0x103C3, 0x103C8, 0x103CF, 0x103D1, + 0x103D5, 0x10400, 0x1049D, 0x104A0, 0x104A9, 0x104B0, 0x104D3, 0x104D8, 0x104FB, 0x10500, 0x10527, 0x10530, + 0x10563, 0x10570, 0x1057A, 0x1057C, 0x1058A, 0x1058C, 0x10592, 0x10594, 0x10595, 0x10597, 0x105A1, 0x105A3, + 0x105B1, 0x105B3, 0x105B9, 0x105BB, 0x105BC, 0x105C0, 0x105F3, 0x10600, 0x10736, 0x10740, 0x10755, 0x10760, + 0x10767, 0x10780, 0x10785, 0x10787, 0x107B0, 0x107B2, 0x107BA, 0x10800, 0x10805, 0x10808, 0x10808, 0x1080A, + 0x10835, 0x10837, 0x10838, 0x1083C, 0x1083C, 0x1083F, 0x10855, 0x10858, 0x10876, 0x10879, 0x1089E, 0x108A7, + 0x108AF, 0x108E0, 0x108F2, 0x108F4, 0x108F5, 0x108FB, 0x1091B, 0x10920, 0x10939, 0x10980, 0x109B7, 0x109BC, + 0x109CF, 0x109D2, 0x10A00, 0x10A10, 0x10A13, 0x10A15, 0x10A17, 0x10A19, 0x10A35, 0x10A40, 0x10A48, 0x10A60, + 0x10A7E, 0x10A80, 0x10A9F, 0x10AC0, 0x10AC7, 0x10AC9, 0x10AE4, 0x10AEB, 0x10AEF, 0x10B00, 0x10B35, 0x10B40, + 0x10B55, 0x10B58, 0x10B72, 0x10B78, 0x10B91, 0x10BA9, 0x10BAF, 0x10C00, 0x10C48, 0x10C80, 0x10CB2, 0x10CC0, + 0x10CF2, 0x10CFA, 0x10D23, 0x10D30, 0x10D39, 0x10D40, 0x10D65, 0x10D6F, 0x10D85, 0x10E60, 0x10E7E, 0x10E80, + 0x10EA9, 0x10EB0, 0x10EB1, 0x10EC2, 0x10EC4, 0x10F00, 0x10F27, 0x10F30, 0x10F45, 0x10F51, 0x10F54, 0x10F70, + 0x10F81, 0x10FB0, 0x10FCB, 0x10FE0, 0x10FF6, 0x11003, 0x11037, 0x11052, 0x1106F, 0x11071, 0x11072, 0x11075, + 0x11075, 0x11083, 0x110AF, 0x110D0, 0x110E8, 0x110F0, 0x110F9, 0x11103, 0x11126, 0x11136, 0x1113F, 0x11144, + 0x11144, 0x11147, 0x11147, 0x11150, 0x11172, 0x11176, 0x11176, 0x11183, 0x111B2, 0x111C1, 0x111C4, 0x111D0, + 0x111DA, 0x111DC, 0x111DC, 0x111E1, 0x111F4, 0x11200, 0x11211, 0x11213, 0x1122B, 0x1123F, 0x11240, 0x11280, + 0x11286, 0x11288, 0x11288, 0x1128A, 0x1128D, 0x1128F, 0x1129D, 0x1129F, 0x112A8, 0x112B0, 0x112DE, 0x112F0, + 0x112F9, 0x11305, 0x1130C, 0x1130F, 0x11310, 0x11313, 0x11328, 0x1132A, 0x11330, 0x11332, 0x11333, 0x11335, + 0x11339, 0x1133D, 0x1133D, 0x11350, 0x11350, 0x1135D, 0x11361, 0x11380, 0x11389, 0x1138B, 0x1138B, 0x1138E, + 0x1138E, 0x11390, 0x113B5, 0x113B7, 0x113B7, 0x113D1, 0x113D1, 0x113D3, 0x113D3, 0x11400, 0x11434, 0x11447, + 0x1144A, 0x11450, 0x11459, 0x1145F, 0x11461, 0x11480, 0x114AF, 0x114C4, 0x114C5, 0x114C7, 0x114C7, 0x114D0, + 0x114D9, 0x11580, 0x115AE, 0x115D8, 0x115DB, 0x11600, 0x1162F, 0x11644, 0x11644, 0x11650, 0x11659, 0x11680, + 0x116AA, 0x116B8, 0x116B8, 0x116C0, 0x116C9, 0x116D0, 0x116E3, 0x11700, 0x1171A, 0x11730, 0x1173B, 0x11740, + 0x11746, 0x11800, 0x1182B, 0x118A0, 0x118F2, 0x118FF, 0x11906, 0x11909, 0x11909, 0x1190C, 0x11913, 0x11915, + 0x11916, 0x11918, 0x1192F, 0x1193F, 0x1193F, 0x11941, 0x11941, 0x11950, 0x11959, 0x119A0, 0x119A7, 0x119AA, + 0x119D0, 0x119E1, 0x119E1, 0x119E3, 0x119E3, 0x11A00, 0x11A00, 0x11A0B, 0x11A32, 0x11A3A, 0x11A3A, 0x11A50, + 0x11A50, 0x11A5C, 0x11A89, 0x11A9D, 0x11A9D, 0x11AB0, 0x11AF8, 0x11BC0, 0x11BE0, 0x11BF0, 0x11BF9, 0x11C00, + 0x11C08, 0x11C0A, 0x11C2E, 0x11C40, 0x11C40, 0x11C50, 0x11C6C, 0x11C72, 0x11C8F, 0x11D00, 0x11D06, 0x11D08, + 0x11D09, 0x11D0B, 0x11D30, 0x11D46, 0x11D46, 0x11D50, 0x11D59, 0x11D60, 0x11D65, 0x11D67, 0x11D68, 0x11D6A, + 0x11D89, 0x11D98, 0x11D98, 0x11DA0, 0x11DA9, 0x11EE0, 0x11EF2, 0x11F02, 0x11F02, 0x11F04, 0x11F10, 0x11F12, + 0x11F33, 0x11F50, 0x11F59, 0x11FB0, 0x11FB0, 0x11FC0, 0x11FD4, 0x12000, 0x12399, 0x12400, 0x1246E, 0x12480, + 0x12543, 0x12F90, 0x12FF0, 0x13000, 0x1342F, 0x13441, 0x13446, 0x13460, 0x143FA, 0x14400, 0x14646, 0x16100, + 0x1611D, 0x16130, 0x16139, 0x16800, 0x16A38, 0x16A40, 0x16A5E, 0x16A60, 0x16A69, 0x16A70, 0x16ABE, 0x16AC0, + 0x16AC9, 0x16AD0, 0x16AED, 0x16B00, 0x16B2F, 0x16B40, 0x16B43, 0x16B50, 0x16B59, 0x16B5B, 0x16B61, 0x16B63, + 0x16B77, 0x16B7D, 0x16B8F, 0x16D40, 0x16D6C, 0x16D70, 0x16D79, 0x16E40, 0x16E96, 0x16F00, 0x16F4A, 0x16F50, + 0x16F50, 0x16F93, 0x16F9F, 0x16FE0, 0x16FE1, 0x16FE3, 0x16FE3, 0x17000, 0x187F7, 0x18800, 0x18CD5, 0x18CFF, + 0x18D08, 0x1AFF0, 0x1AFF3, 0x1AFF5, 0x1AFFB, 0x1AFFD, 0x1AFFE, 0x1B000, 0x1B122, 0x1B132, 0x1B132, 0x1B150, + 0x1B152, 0x1B155, 0x1B155, 0x1B164, 0x1B167, 0x1B170, 0x1B2FB, 0x1BC00, 0x1BC6A, 0x1BC70, 0x1BC7C, 0x1BC80, + 0x1BC88, 0x1BC90, 0x1BC99, 0x1CCF0, 0x1CCF9, 0x1D2C0, 0x1D2D3, 0x1D2E0, 0x1D2F3, 0x1D360, 0x1D378, 0x1D400, + 0x1D454, 0x1D456, 0x1D49C, 0x1D49E, 0x1D49F, 0x1D4A2, 0x1D4A2, 0x1D4A5, 0x1D4A6, 0x1D4A9, 0x1D4AC, 0x1D4AE, + 0x1D4B9, 0x1D4BB, 0x1D4BB, 0x1D4BD, 0x1D4C3, 0x1D4C5, 0x1D505, 0x1D507, 0x1D50A, 0x1D50D, 0x1D514, 0x1D516, + 0x1D51C, 0x1D51E, 0x1D539, 0x1D53B, 0x1D53E, 0x1D540, 0x1D544, 0x1D546, 0x1D546, 0x1D54A, 0x1D550, 0x1D552, + 0x1D6A5, 0x1D6A8, 0x1D6C0, 0x1D6C2, 0x1D6DA, 0x1D6DC, 0x1D6FA, 0x1D6FC, 0x1D714, 0x1D716, 0x1D734, 0x1D736, + 0x1D74E, 0x1D750, 0x1D76E, 0x1D770, 0x1D788, 0x1D78A, 0x1D7A8, 0x1D7AA, 0x1D7C2, 0x1D7C4, 0x1D7CB, 0x1D7CE, + 0x1D7FF, 0x1DF00, 0x1DF1E, 0x1DF25, 0x1DF2A, 0x1E030, 0x1E06D, 0x1E100, 0x1E12C, 0x1E137, 0x1E13D, 0x1E140, + 0x1E149, 0x1E14E, 0x1E14E, 0x1E290, 0x1E2AD, 0x1E2C0, 0x1E2EB, 0x1E2F0, 0x1E2F9, 0x1E4D0, 0x1E4EB, 0x1E4F0, + 0x1E4F9, 0x1E5D0, 0x1E5ED, 0x1E5F0, 0x1E5FA, 0x1E7E0, 0x1E7E6, 0x1E7E8, 0x1E7EB, 0x1E7ED, 0x1E7EE, 0x1E7F0, + 0x1E7FE, 0x1E800, 0x1E8C4, 0x1E8C7, 0x1E8CF, 0x1E900, 0x1E943, 0x1E94B, 0x1E94B, 0x1E950, 0x1E959, 0x1EC71, + 0x1ECAB, 0x1ECAD, 0x1ECAF, 0x1ECB1, 0x1ECB4, 0x1ED01, 0x1ED2D, 0x1ED2F, 0x1ED3D, 0x1EE00, 0x1EE03, 0x1EE05, + 0x1EE1F, 0x1EE21, 0x1EE22, 0x1EE24, 0x1EE24, 0x1EE27, 0x1EE27, 0x1EE29, 0x1EE32, 0x1EE34, 0x1EE37, 0x1EE39, + 0x1EE39, 0x1EE3B, 0x1EE3B, 0x1EE42, 0x1EE42, 0x1EE47, 0x1EE47, 0x1EE49, 0x1EE49, 0x1EE4B, 0x1EE4B, 0x1EE4D, + 0x1EE4F, 0x1EE51, 0x1EE52, 0x1EE54, 0x1EE54, 0x1EE57, 0x1EE57, 0x1EE59, 0x1EE59, 0x1EE5B, 0x1EE5B, 0x1EE5D, + 0x1EE5D, 0x1EE5F, 0x1EE5F, 0x1EE61, 0x1EE62, 0x1EE64, 0x1EE64, 0x1EE67, 0x1EE6A, 0x1EE6C, 0x1EE72, 0x1EE74, + 0x1EE77, 0x1EE79, 0x1EE7C, 0x1EE7E, 0x1EE7E, 0x1EE80, 0x1EE89, 0x1EE8B, 0x1EE9B, 0x1EEA1, 0x1EEA3, 0x1EEA5, + 0x1EEA9, 0x1EEAB, 0x1EEBB, 0x1F100, 0x1F10C, 0x1FBF0, 0x1FBF9, 0x20000, 0x2A6DF, 0x2A700, 0x2B739, 0x2B740, + 0x2B81D, 0x2B820, 0x2CEA1, 0x2CEB0, 0x2EBE0, 0x2EBF0, 0x2EE5D, 0x2F800, 0x2FA1D, 0x30000, 0x3134A, 0x31350, + 0x323AF, +) + +internal val DEFAULT_IGNORABLE: IntArray = intArrayOf( + 0xAD, 0xAD, 0x34F, 0x34F, 0x61C, 0x61C, 0x115F, 0x1160, 0x17B4, 0x17B5, 0x180B, 0x180F, 0x200B, 0x200F, 0x202A, + 0x202E, 0x2060, 0x206F, 0x3164, 0x3164, 0xFE00, 0xFE0F, 0xFEFF, 0xFEFF, 0xFFA0, 0xFFA0, 0xFFF0, 0xFFF8, 0x1BCA0, + 0x1BCA3, 0x1D173, 0x1D17A, 0xE0000, 0xE0FFF, +) diff --git a/packages/cultmesh-kotlin/src/main/kotlin/org/gamecult/cultmesh/WriteReceipt.kt b/packages/cultmesh-kotlin/src/main/kotlin/org/gamecult/cultmesh/WriteReceipt.kt index 523dfc2e..9cf9c402 100644 --- a/packages/cultmesh-kotlin/src/main/kotlin/org/gamecult/cultmesh/WriteReceipt.kt +++ b/packages/cultmesh-kotlin/src/main/kotlin/org/gamecult/cultmesh/WriteReceipt.kt @@ -77,26 +77,30 @@ fun decodeWriteAdmitted(message: CultNetMessage): CultNetWriteAdmitted { return CultNetWriteAdmitted(admittedMessageId(message), operation, nonEmpty(message, "schemaId"), nonEmpty(message, "recordKey")) } -// Default_Ignorable_Code_Point ranges, DerivedCoreProperties.txt, Unicode 16.0 (unchanged since 15.1). The JDK has no such -// property, so this runtime embeds it; the Python, C# and Rust decoders embed the same ranges, TypeScript uses its regex -// engine's. Letters and numbers come from the JDK's Character categories (L*, and Nd, Nl, No); a code point the running JDK -// does not assign (U+0378 in every version) has no such category, so it is refused either way. -private val DEFAULT_IGNORABLE: List = listOf( - 0x00AD..0x00AD, 0x034F..0x034F, 0x061C..0x061C, 0x115F..0x1160, 0x17B4..0x17B5, 0x180B..0x180F, - 0x200B..0x200F, 0x202A..0x202E, 0x2060..0x206F, 0x3164..0x3164, 0xFE00..0xFE0F, 0xFEFF..0xFEFF, - 0xFFA0..0xFFA0, 0xFFF0..0xFFF8, 0x1BCA0..0x1BCA3, 0x1D173..0x1D17A, 0xE0000..0xE0FFF, -) +// write-receipt.md: a letter (Lu Ll Lt Lm Lo) or number (Nd Nl No) somewhere, no Default_Ignorable code point anywhere, as of +// Unicode 16.0, compared raw (never trimmed). The tables are embedded (UnicodeMessageIdTables.kt, generated by +// scripts/gen-unicode-messageid-tables.py), not the JDK's, so the running JVM's Unicode version changes no result; the other +// four runtimes embed the same generated tables. +private fun inRanges(ranges: IntArray, point: Int): Boolean { + var low = 0 + var high = ranges.size / 2 - 1 + while (low <= high) { + val mid = (low + high) ushr 1 + when { + point < ranges[2 * mid] -> high = mid - 1 + point > ranges[2 * mid + 1] -> low = mid + 1 + else -> return true + } + } + return false +} -/** write-receipt.md: a letter or number somewhere, no Default_Ignorable code point anywhere, compared raw (never trimmed). */ private fun admittedMessageId(message: CultNetMessage): String { val id = nonEmpty(message, "messageId") var hasLetterOrNumber = false id.codePoints().forEach { cp -> - if (DEFAULT_IGNORABLE.any { cp in it }) throw IOException("messageId holds a Default_Ignorable code point") - when (Character.getType(cp).toByte()) { - Character.UPPERCASE_LETTER, Character.LOWERCASE_LETTER, Character.TITLECASE_LETTER, Character.MODIFIER_LETTER, - Character.OTHER_LETTER, Character.DECIMAL_DIGIT_NUMBER, Character.LETTER_NUMBER, Character.OTHER_NUMBER -> hasLetterOrNumber = true - } + if (inRanges(DEFAULT_IGNORABLE, cp)) throw IOException("messageId holds a Default_Ignorable code point") + if (inRanges(LETTER_OR_NUMBER, cp)) hasLetterOrNumber = true } if (!hasLetterOrNumber) throw IOException("messageId must contain a letter or number") return id From c2d2c1d126a719bd2ff9a38929350dfff31a249e Mon Sep 17 00:00:00 2001 From: soul Date: Mon, 9 Nov 2026 15:05:00 +0000 Subject: [PATCH 29/76] GameCult.Networking: write_admitted messageId from embedded Unicode 16.0 tables, not .NET's Co-Authored-By: Claude Sonnet 5.5 --- .../CultNetSchemaMessageSerialization.cs | 47 +++--- .../CultNetUnicodeMessageIdTables.cs | 144 ++++++++++++++++++ 2 files changed, 164 insertions(+), 27 deletions(-) create mode 100644 src/GameCult.Networking/CultNetUnicodeMessageIdTables.cs diff --git a/src/GameCult.Networking/CultNetSchemaMessageSerialization.cs b/src/GameCult.Networking/CultNetSchemaMessageSerialization.cs index 9789e05e..3069384a 100644 --- a/src/GameCult.Networking/CultNetSchemaMessageSerialization.cs +++ b/src/GameCult.Networking/CultNetSchemaMessageSerialization.cs @@ -1,6 +1,5 @@ using System; using System.Buffers; -using System.Globalization; using MessagePack; namespace GameCult.Networking @@ -103,21 +102,13 @@ namespace GameCult.Networking } /// - /// The write_admitted messageId rule (write-receipt.md): a Unicode letter or number somewhere, no Default_Ignorable code - /// point anywhere, wire bytes that are valid UTF-8, compared raw (never trimmed). Letters and numbers come from the - /// runtime's Unicode categories (L*, Nd, Nl, No); a code point it does not assign (U+0378 in every version) has neither. - /// .NET has no Default_Ignorable property, so the ranges are embedded from DerivedCoreProperties.txt, Unicode 16.0 - /// (unchanged since 15.1); the Kotlin, Python and Rust decoders embed the same ranges, TypeScript uses its regex engine's. + /// The write_admitted messageId rule (write-receipt.md): a letter (Lu Ll Lt Lm Lo) or number (Nd Nl No) somewhere, no + /// Default_Ignorable code point anywhere, as of Unicode 16.0, wire bytes that are valid UTF-8, compared raw (never trimmed). + /// The tables are embedded (CultNetUnicodeMessageIdTables.cs, generated by scripts/gen-unicode-messageid-tables.py), not + /// .NET's, so the running runtime's Unicode version changes no result; the other four runtimes embed the same tables. /// internal static class CultNetAdmittedMessageId { - private static readonly (int Low, int High)[] DefaultIgnorable = - { - (0x00AD, 0x00AD), (0x034F, 0x034F), (0x061C, 0x061C), (0x115F, 0x1160), (0x17B4, 0x17B5), (0x180B, 0x180F), - (0x200B, 0x200F), (0x202A, 0x202E), (0x2060, 0x206F), (0x3164, 0x3164), (0xFE00, 0xFE0F), (0xFEFF, 0xFEFF), - (0xFFA0, 0xFFA0), (0xFFF0, 0xFFF8), (0x1BCA0, 0x1BCA3), (0x1D173, 0x1D17A), (0xE0000, 0xE0FFF), - }; - private static readonly System.Text.UTF8Encoding StrictUtf8 = new(false, true); /// Throws unless the frame's top-level messageId is a valid write_admitted id; other keys are left to the serializer. @@ -148,23 +139,25 @@ namespace GameCult.Networking for (var at = 0; at < id.Length; at += char.IsSurrogatePair(id, at) ? 2 : 1) { var point = char.IsSurrogatePair(id, at) ? char.ConvertToUtf32(id, at) : id[at]; - if (Array.Exists(DefaultIgnorable, range => point >= range.Low && point <= range.High)) return false; - switch (CharUnicodeInfo.GetUnicodeCategory(point)) - { - case UnicodeCategory.UppercaseLetter: - case UnicodeCategory.LowercaseLetter: - case UnicodeCategory.TitlecaseLetter: - case UnicodeCategory.ModifierLetter: - case UnicodeCategory.OtherLetter: - case UnicodeCategory.DecimalDigitNumber: - case UnicodeCategory.LetterNumber: - case UnicodeCategory.OtherNumber: - hasLetterOrNumber = true; - break; - } + if (InRanges(CultNetUnicodeMessageIdTables.DefaultIgnorable, point)) return false; + hasLetterOrNumber |= InRanges(CultNetUnicodeMessageIdTables.LetterOrNumber, point); } return hasLetterOrNumber; } + + private static bool InRanges(int[] ranges, int point) + { + int low = 0, high = ranges.Length / 2 - 1; + while (low <= high) + { + var mid = (low + high) >> 1; + if (point < ranges[2 * mid]) high = mid - 1; + else if (point > ranges[2 * mid + 1]) low = mid + 1; + else return true; + } + + return false; + } } } diff --git a/src/GameCult.Networking/CultNetUnicodeMessageIdTables.cs b/src/GameCult.Networking/CultNetUnicodeMessageIdTables.cs new file mode 100644 index 00000000..45cedbc5 --- /dev/null +++ b/src/GameCult.Networking/CultNetUnicodeMessageIdTables.cs @@ -0,0 +1,144 @@ +// generated, Unicode 16.0.0, by scripts/gen-unicode-messageid-tables.py (inputs sha256 24a07ef9fabf71e3); do not edit + +namespace GameCult.Networking +{ + internal static class CultNetUnicodeMessageIdTables + { + // (low, high) pairs, flattened and sorted. + public static readonly int[] LetterOrNumber = + { + 0x30, 0x39, 0x41, 0x5A, 0x61, 0x7A, 0xAA, 0xAA, 0xB2, 0xB3, 0xB5, 0xB5, 0xB9, 0xBA, 0xBC, 0xBE, 0xC0, + 0xD6, 0xD8, 0xF6, 0xF8, 0x2C1, 0x2C6, 0x2D1, 0x2E0, 0x2E4, 0x2EC, 0x2EC, 0x2EE, 0x2EE, 0x370, 0x374, + 0x376, 0x377, 0x37A, 0x37D, 0x37F, 0x37F, 0x386, 0x386, 0x388, 0x38A, 0x38C, 0x38C, 0x38E, 0x3A1, 0x3A3, + 0x3F5, 0x3F7, 0x481, 0x48A, 0x52F, 0x531, 0x556, 0x559, 0x559, 0x560, 0x588, 0x5D0, 0x5EA, 0x5EF, 0x5F2, + 0x620, 0x64A, 0x660, 0x669, 0x66E, 0x66F, 0x671, 0x6D3, 0x6D5, 0x6D5, 0x6E5, 0x6E6, 0x6EE, 0x6FC, 0x6FF, + 0x6FF, 0x710, 0x710, 0x712, 0x72F, 0x74D, 0x7A5, 0x7B1, 0x7B1, 0x7C0, 0x7EA, 0x7F4, 0x7F5, 0x7FA, 0x7FA, + 0x800, 0x815, 0x81A, 0x81A, 0x824, 0x824, 0x828, 0x828, 0x840, 0x858, 0x860, 0x86A, 0x870, 0x887, 0x889, + 0x88E, 0x8A0, 0x8C9, 0x904, 0x939, 0x93D, 0x93D, 0x950, 0x950, 0x958, 0x961, 0x966, 0x96F, 0x971, 0x980, + 0x985, 0x98C, 0x98F, 0x990, 0x993, 0x9A8, 0x9AA, 0x9B0, 0x9B2, 0x9B2, 0x9B6, 0x9B9, 0x9BD, 0x9BD, 0x9CE, + 0x9CE, 0x9DC, 0x9DD, 0x9DF, 0x9E1, 0x9E6, 0x9F1, 0x9F4, 0x9F9, 0x9FC, 0x9FC, 0xA05, 0xA0A, 0xA0F, 0xA10, + 0xA13, 0xA28, 0xA2A, 0xA30, 0xA32, 0xA33, 0xA35, 0xA36, 0xA38, 0xA39, 0xA59, 0xA5C, 0xA5E, 0xA5E, 0xA66, + 0xA6F, 0xA72, 0xA74, 0xA85, 0xA8D, 0xA8F, 0xA91, 0xA93, 0xAA8, 0xAAA, 0xAB0, 0xAB2, 0xAB3, 0xAB5, 0xAB9, + 0xABD, 0xABD, 0xAD0, 0xAD0, 0xAE0, 0xAE1, 0xAE6, 0xAEF, 0xAF9, 0xAF9, 0xB05, 0xB0C, 0xB0F, 0xB10, 0xB13, + 0xB28, 0xB2A, 0xB30, 0xB32, 0xB33, 0xB35, 0xB39, 0xB3D, 0xB3D, 0xB5C, 0xB5D, 0xB5F, 0xB61, 0xB66, 0xB6F, + 0xB71, 0xB77, 0xB83, 0xB83, 0xB85, 0xB8A, 0xB8E, 0xB90, 0xB92, 0xB95, 0xB99, 0xB9A, 0xB9C, 0xB9C, 0xB9E, + 0xB9F, 0xBA3, 0xBA4, 0xBA8, 0xBAA, 0xBAE, 0xBB9, 0xBD0, 0xBD0, 0xBE6, 0xBF2, 0xC05, 0xC0C, 0xC0E, 0xC10, + 0xC12, 0xC28, 0xC2A, 0xC39, 0xC3D, 0xC3D, 0xC58, 0xC5A, 0xC5D, 0xC5D, 0xC60, 0xC61, 0xC66, 0xC6F, 0xC78, + 0xC7E, 0xC80, 0xC80, 0xC85, 0xC8C, 0xC8E, 0xC90, 0xC92, 0xCA8, 0xCAA, 0xCB3, 0xCB5, 0xCB9, 0xCBD, 0xCBD, + 0xCDD, 0xCDE, 0xCE0, 0xCE1, 0xCE6, 0xCEF, 0xCF1, 0xCF2, 0xD04, 0xD0C, 0xD0E, 0xD10, 0xD12, 0xD3A, 0xD3D, + 0xD3D, 0xD4E, 0xD4E, 0xD54, 0xD56, 0xD58, 0xD61, 0xD66, 0xD78, 0xD7A, 0xD7F, 0xD85, 0xD96, 0xD9A, 0xDB1, + 0xDB3, 0xDBB, 0xDBD, 0xDBD, 0xDC0, 0xDC6, 0xDE6, 0xDEF, 0xE01, 0xE30, 0xE32, 0xE33, 0xE40, 0xE46, 0xE50, + 0xE59, 0xE81, 0xE82, 0xE84, 0xE84, 0xE86, 0xE8A, 0xE8C, 0xEA3, 0xEA5, 0xEA5, 0xEA7, 0xEB0, 0xEB2, 0xEB3, + 0xEBD, 0xEBD, 0xEC0, 0xEC4, 0xEC6, 0xEC6, 0xED0, 0xED9, 0xEDC, 0xEDF, 0xF00, 0xF00, 0xF20, 0xF33, 0xF40, + 0xF47, 0xF49, 0xF6C, 0xF88, 0xF8C, 0x1000, 0x102A, 0x103F, 0x1049, 0x1050, 0x1055, 0x105A, 0x105D, + 0x1061, 0x1061, 0x1065, 0x1066, 0x106E, 0x1070, 0x1075, 0x1081, 0x108E, 0x108E, 0x1090, 0x1099, 0x10A0, + 0x10C5, 0x10C7, 0x10C7, 0x10CD, 0x10CD, 0x10D0, 0x10FA, 0x10FC, 0x1248, 0x124A, 0x124D, 0x1250, 0x1256, + 0x1258, 0x1258, 0x125A, 0x125D, 0x1260, 0x1288, 0x128A, 0x128D, 0x1290, 0x12B0, 0x12B2, 0x12B5, 0x12B8, + 0x12BE, 0x12C0, 0x12C0, 0x12C2, 0x12C5, 0x12C8, 0x12D6, 0x12D8, 0x1310, 0x1312, 0x1315, 0x1318, 0x135A, + 0x1369, 0x137C, 0x1380, 0x138F, 0x13A0, 0x13F5, 0x13F8, 0x13FD, 0x1401, 0x166C, 0x166F, 0x167F, 0x1681, + 0x169A, 0x16A0, 0x16EA, 0x16EE, 0x16F8, 0x1700, 0x1711, 0x171F, 0x1731, 0x1740, 0x1751, 0x1760, 0x176C, + 0x176E, 0x1770, 0x1780, 0x17B3, 0x17D7, 0x17D7, 0x17DC, 0x17DC, 0x17E0, 0x17E9, 0x17F0, 0x17F9, 0x1810, + 0x1819, 0x1820, 0x1878, 0x1880, 0x1884, 0x1887, 0x18A8, 0x18AA, 0x18AA, 0x18B0, 0x18F5, 0x1900, 0x191E, + 0x1946, 0x196D, 0x1970, 0x1974, 0x1980, 0x19AB, 0x19B0, 0x19C9, 0x19D0, 0x19DA, 0x1A00, 0x1A16, 0x1A20, + 0x1A54, 0x1A80, 0x1A89, 0x1A90, 0x1A99, 0x1AA7, 0x1AA7, 0x1B05, 0x1B33, 0x1B45, 0x1B4C, 0x1B50, 0x1B59, + 0x1B83, 0x1BA0, 0x1BAE, 0x1BE5, 0x1C00, 0x1C23, 0x1C40, 0x1C49, 0x1C4D, 0x1C7D, 0x1C80, 0x1C8A, 0x1C90, + 0x1CBA, 0x1CBD, 0x1CBF, 0x1CE9, 0x1CEC, 0x1CEE, 0x1CF3, 0x1CF5, 0x1CF6, 0x1CFA, 0x1CFA, 0x1D00, 0x1DBF, + 0x1E00, 0x1F15, 0x1F18, 0x1F1D, 0x1F20, 0x1F45, 0x1F48, 0x1F4D, 0x1F50, 0x1F57, 0x1F59, 0x1F59, 0x1F5B, + 0x1F5B, 0x1F5D, 0x1F5D, 0x1F5F, 0x1F7D, 0x1F80, 0x1FB4, 0x1FB6, 0x1FBC, 0x1FBE, 0x1FBE, 0x1FC2, 0x1FC4, + 0x1FC6, 0x1FCC, 0x1FD0, 0x1FD3, 0x1FD6, 0x1FDB, 0x1FE0, 0x1FEC, 0x1FF2, 0x1FF4, 0x1FF6, 0x1FFC, 0x2070, + 0x2071, 0x2074, 0x2079, 0x207F, 0x2089, 0x2090, 0x209C, 0x2102, 0x2102, 0x2107, 0x2107, 0x210A, 0x2113, + 0x2115, 0x2115, 0x2119, 0x211D, 0x2124, 0x2124, 0x2126, 0x2126, 0x2128, 0x2128, 0x212A, 0x212D, 0x212F, + 0x2139, 0x213C, 0x213F, 0x2145, 0x2149, 0x214E, 0x214E, 0x2150, 0x2189, 0x2460, 0x249B, 0x24EA, 0x24FF, + 0x2776, 0x2793, 0x2C00, 0x2CE4, 0x2CEB, 0x2CEE, 0x2CF2, 0x2CF3, 0x2CFD, 0x2CFD, 0x2D00, 0x2D25, 0x2D27, + 0x2D27, 0x2D2D, 0x2D2D, 0x2D30, 0x2D67, 0x2D6F, 0x2D6F, 0x2D80, 0x2D96, 0x2DA0, 0x2DA6, 0x2DA8, 0x2DAE, + 0x2DB0, 0x2DB6, 0x2DB8, 0x2DBE, 0x2DC0, 0x2DC6, 0x2DC8, 0x2DCE, 0x2DD0, 0x2DD6, 0x2DD8, 0x2DDE, 0x2E2F, + 0x2E2F, 0x3005, 0x3007, 0x3021, 0x3029, 0x3031, 0x3035, 0x3038, 0x303C, 0x3041, 0x3096, 0x309D, 0x309F, + 0x30A1, 0x30FA, 0x30FC, 0x30FF, 0x3105, 0x312F, 0x3131, 0x318E, 0x3192, 0x3195, 0x31A0, 0x31BF, 0x31F0, + 0x31FF, 0x3220, 0x3229, 0x3248, 0x324F, 0x3251, 0x325F, 0x3280, 0x3289, 0x32B1, 0x32BF, 0x3400, 0x4DBF, + 0x4E00, 0xA48C, 0xA4D0, 0xA4FD, 0xA500, 0xA60C, 0xA610, 0xA62B, 0xA640, 0xA66E, 0xA67F, 0xA69D, 0xA6A0, + 0xA6EF, 0xA717, 0xA71F, 0xA722, 0xA788, 0xA78B, 0xA7CD, 0xA7D0, 0xA7D1, 0xA7D3, 0xA7D3, 0xA7D5, 0xA7DC, + 0xA7F2, 0xA801, 0xA803, 0xA805, 0xA807, 0xA80A, 0xA80C, 0xA822, 0xA830, 0xA835, 0xA840, 0xA873, 0xA882, + 0xA8B3, 0xA8D0, 0xA8D9, 0xA8F2, 0xA8F7, 0xA8FB, 0xA8FB, 0xA8FD, 0xA8FE, 0xA900, 0xA925, 0xA930, 0xA946, + 0xA960, 0xA97C, 0xA984, 0xA9B2, 0xA9CF, 0xA9D9, 0xA9E0, 0xA9E4, 0xA9E6, 0xA9FE, 0xAA00, 0xAA28, 0xAA40, + 0xAA42, 0xAA44, 0xAA4B, 0xAA50, 0xAA59, 0xAA60, 0xAA76, 0xAA7A, 0xAA7A, 0xAA7E, 0xAAAF, 0xAAB1, 0xAAB1, + 0xAAB5, 0xAAB6, 0xAAB9, 0xAABD, 0xAAC0, 0xAAC0, 0xAAC2, 0xAAC2, 0xAADB, 0xAADD, 0xAAE0, 0xAAEA, 0xAAF2, + 0xAAF4, 0xAB01, 0xAB06, 0xAB09, 0xAB0E, 0xAB11, 0xAB16, 0xAB20, 0xAB26, 0xAB28, 0xAB2E, 0xAB30, 0xAB5A, + 0xAB5C, 0xAB69, 0xAB70, 0xABE2, 0xABF0, 0xABF9, 0xAC00, 0xD7A3, 0xD7B0, 0xD7C6, 0xD7CB, 0xD7FB, 0xF900, + 0xFA6D, 0xFA70, 0xFAD9, 0xFB00, 0xFB06, 0xFB13, 0xFB17, 0xFB1D, 0xFB1D, 0xFB1F, 0xFB28, 0xFB2A, 0xFB36, + 0xFB38, 0xFB3C, 0xFB3E, 0xFB3E, 0xFB40, 0xFB41, 0xFB43, 0xFB44, 0xFB46, 0xFBB1, 0xFBD3, 0xFD3D, 0xFD50, + 0xFD8F, 0xFD92, 0xFDC7, 0xFDF0, 0xFDFB, 0xFE70, 0xFE74, 0xFE76, 0xFEFC, 0xFF10, 0xFF19, 0xFF21, 0xFF3A, + 0xFF41, 0xFF5A, 0xFF66, 0xFFBE, 0xFFC2, 0xFFC7, 0xFFCA, 0xFFCF, 0xFFD2, 0xFFD7, 0xFFDA, 0xFFDC, 0x10000, + 0x1000B, 0x1000D, 0x10026, 0x10028, 0x1003A, 0x1003C, 0x1003D, 0x1003F, 0x1004D, 0x10050, 0x1005D, + 0x10080, 0x100FA, 0x10107, 0x10133, 0x10140, 0x10178, 0x1018A, 0x1018B, 0x10280, 0x1029C, 0x102A0, + 0x102D0, 0x102E1, 0x102FB, 0x10300, 0x10323, 0x1032D, 0x1034A, 0x10350, 0x10375, 0x10380, 0x1039D, + 0x103A0, 0x103C3, 0x103C8, 0x103CF, 0x103D1, 0x103D5, 0x10400, 0x1049D, 0x104A0, 0x104A9, 0x104B0, + 0x104D3, 0x104D8, 0x104FB, 0x10500, 0x10527, 0x10530, 0x10563, 0x10570, 0x1057A, 0x1057C, 0x1058A, + 0x1058C, 0x10592, 0x10594, 0x10595, 0x10597, 0x105A1, 0x105A3, 0x105B1, 0x105B3, 0x105B9, 0x105BB, + 0x105BC, 0x105C0, 0x105F3, 0x10600, 0x10736, 0x10740, 0x10755, 0x10760, 0x10767, 0x10780, 0x10785, + 0x10787, 0x107B0, 0x107B2, 0x107BA, 0x10800, 0x10805, 0x10808, 0x10808, 0x1080A, 0x10835, 0x10837, + 0x10838, 0x1083C, 0x1083C, 0x1083F, 0x10855, 0x10858, 0x10876, 0x10879, 0x1089E, 0x108A7, 0x108AF, + 0x108E0, 0x108F2, 0x108F4, 0x108F5, 0x108FB, 0x1091B, 0x10920, 0x10939, 0x10980, 0x109B7, 0x109BC, + 0x109CF, 0x109D2, 0x10A00, 0x10A10, 0x10A13, 0x10A15, 0x10A17, 0x10A19, 0x10A35, 0x10A40, 0x10A48, + 0x10A60, 0x10A7E, 0x10A80, 0x10A9F, 0x10AC0, 0x10AC7, 0x10AC9, 0x10AE4, 0x10AEB, 0x10AEF, 0x10B00, + 0x10B35, 0x10B40, 0x10B55, 0x10B58, 0x10B72, 0x10B78, 0x10B91, 0x10BA9, 0x10BAF, 0x10C00, 0x10C48, + 0x10C80, 0x10CB2, 0x10CC0, 0x10CF2, 0x10CFA, 0x10D23, 0x10D30, 0x10D39, 0x10D40, 0x10D65, 0x10D6F, + 0x10D85, 0x10E60, 0x10E7E, 0x10E80, 0x10EA9, 0x10EB0, 0x10EB1, 0x10EC2, 0x10EC4, 0x10F00, 0x10F27, + 0x10F30, 0x10F45, 0x10F51, 0x10F54, 0x10F70, 0x10F81, 0x10FB0, 0x10FCB, 0x10FE0, 0x10FF6, 0x11003, + 0x11037, 0x11052, 0x1106F, 0x11071, 0x11072, 0x11075, 0x11075, 0x11083, 0x110AF, 0x110D0, 0x110E8, + 0x110F0, 0x110F9, 0x11103, 0x11126, 0x11136, 0x1113F, 0x11144, 0x11144, 0x11147, 0x11147, 0x11150, + 0x11172, 0x11176, 0x11176, 0x11183, 0x111B2, 0x111C1, 0x111C4, 0x111D0, 0x111DA, 0x111DC, 0x111DC, + 0x111E1, 0x111F4, 0x11200, 0x11211, 0x11213, 0x1122B, 0x1123F, 0x11240, 0x11280, 0x11286, 0x11288, + 0x11288, 0x1128A, 0x1128D, 0x1128F, 0x1129D, 0x1129F, 0x112A8, 0x112B0, 0x112DE, 0x112F0, 0x112F9, + 0x11305, 0x1130C, 0x1130F, 0x11310, 0x11313, 0x11328, 0x1132A, 0x11330, 0x11332, 0x11333, 0x11335, + 0x11339, 0x1133D, 0x1133D, 0x11350, 0x11350, 0x1135D, 0x11361, 0x11380, 0x11389, 0x1138B, 0x1138B, + 0x1138E, 0x1138E, 0x11390, 0x113B5, 0x113B7, 0x113B7, 0x113D1, 0x113D1, 0x113D3, 0x113D3, 0x11400, + 0x11434, 0x11447, 0x1144A, 0x11450, 0x11459, 0x1145F, 0x11461, 0x11480, 0x114AF, 0x114C4, 0x114C5, + 0x114C7, 0x114C7, 0x114D0, 0x114D9, 0x11580, 0x115AE, 0x115D8, 0x115DB, 0x11600, 0x1162F, 0x11644, + 0x11644, 0x11650, 0x11659, 0x11680, 0x116AA, 0x116B8, 0x116B8, 0x116C0, 0x116C9, 0x116D0, 0x116E3, + 0x11700, 0x1171A, 0x11730, 0x1173B, 0x11740, 0x11746, 0x11800, 0x1182B, 0x118A0, 0x118F2, 0x118FF, + 0x11906, 0x11909, 0x11909, 0x1190C, 0x11913, 0x11915, 0x11916, 0x11918, 0x1192F, 0x1193F, 0x1193F, + 0x11941, 0x11941, 0x11950, 0x11959, 0x119A0, 0x119A7, 0x119AA, 0x119D0, 0x119E1, 0x119E1, 0x119E3, + 0x119E3, 0x11A00, 0x11A00, 0x11A0B, 0x11A32, 0x11A3A, 0x11A3A, 0x11A50, 0x11A50, 0x11A5C, 0x11A89, + 0x11A9D, 0x11A9D, 0x11AB0, 0x11AF8, 0x11BC0, 0x11BE0, 0x11BF0, 0x11BF9, 0x11C00, 0x11C08, 0x11C0A, + 0x11C2E, 0x11C40, 0x11C40, 0x11C50, 0x11C6C, 0x11C72, 0x11C8F, 0x11D00, 0x11D06, 0x11D08, 0x11D09, + 0x11D0B, 0x11D30, 0x11D46, 0x11D46, 0x11D50, 0x11D59, 0x11D60, 0x11D65, 0x11D67, 0x11D68, 0x11D6A, + 0x11D89, 0x11D98, 0x11D98, 0x11DA0, 0x11DA9, 0x11EE0, 0x11EF2, 0x11F02, 0x11F02, 0x11F04, 0x11F10, + 0x11F12, 0x11F33, 0x11F50, 0x11F59, 0x11FB0, 0x11FB0, 0x11FC0, 0x11FD4, 0x12000, 0x12399, 0x12400, + 0x1246E, 0x12480, 0x12543, 0x12F90, 0x12FF0, 0x13000, 0x1342F, 0x13441, 0x13446, 0x13460, 0x143FA, + 0x14400, 0x14646, 0x16100, 0x1611D, 0x16130, 0x16139, 0x16800, 0x16A38, 0x16A40, 0x16A5E, 0x16A60, + 0x16A69, 0x16A70, 0x16ABE, 0x16AC0, 0x16AC9, 0x16AD0, 0x16AED, 0x16B00, 0x16B2F, 0x16B40, 0x16B43, + 0x16B50, 0x16B59, 0x16B5B, 0x16B61, 0x16B63, 0x16B77, 0x16B7D, 0x16B8F, 0x16D40, 0x16D6C, 0x16D70, + 0x16D79, 0x16E40, 0x16E96, 0x16F00, 0x16F4A, 0x16F50, 0x16F50, 0x16F93, 0x16F9F, 0x16FE0, 0x16FE1, + 0x16FE3, 0x16FE3, 0x17000, 0x187F7, 0x18800, 0x18CD5, 0x18CFF, 0x18D08, 0x1AFF0, 0x1AFF3, 0x1AFF5, + 0x1AFFB, 0x1AFFD, 0x1AFFE, 0x1B000, 0x1B122, 0x1B132, 0x1B132, 0x1B150, 0x1B152, 0x1B155, 0x1B155, + 0x1B164, 0x1B167, 0x1B170, 0x1B2FB, 0x1BC00, 0x1BC6A, 0x1BC70, 0x1BC7C, 0x1BC80, 0x1BC88, 0x1BC90, + 0x1BC99, 0x1CCF0, 0x1CCF9, 0x1D2C0, 0x1D2D3, 0x1D2E0, 0x1D2F3, 0x1D360, 0x1D378, 0x1D400, 0x1D454, + 0x1D456, 0x1D49C, 0x1D49E, 0x1D49F, 0x1D4A2, 0x1D4A2, 0x1D4A5, 0x1D4A6, 0x1D4A9, 0x1D4AC, 0x1D4AE, + 0x1D4B9, 0x1D4BB, 0x1D4BB, 0x1D4BD, 0x1D4C3, 0x1D4C5, 0x1D505, 0x1D507, 0x1D50A, 0x1D50D, 0x1D514, + 0x1D516, 0x1D51C, 0x1D51E, 0x1D539, 0x1D53B, 0x1D53E, 0x1D540, 0x1D544, 0x1D546, 0x1D546, 0x1D54A, + 0x1D550, 0x1D552, 0x1D6A5, 0x1D6A8, 0x1D6C0, 0x1D6C2, 0x1D6DA, 0x1D6DC, 0x1D6FA, 0x1D6FC, 0x1D714, + 0x1D716, 0x1D734, 0x1D736, 0x1D74E, 0x1D750, 0x1D76E, 0x1D770, 0x1D788, 0x1D78A, 0x1D7A8, 0x1D7AA, + 0x1D7C2, 0x1D7C4, 0x1D7CB, 0x1D7CE, 0x1D7FF, 0x1DF00, 0x1DF1E, 0x1DF25, 0x1DF2A, 0x1E030, 0x1E06D, + 0x1E100, 0x1E12C, 0x1E137, 0x1E13D, 0x1E140, 0x1E149, 0x1E14E, 0x1E14E, 0x1E290, 0x1E2AD, 0x1E2C0, + 0x1E2EB, 0x1E2F0, 0x1E2F9, 0x1E4D0, 0x1E4EB, 0x1E4F0, 0x1E4F9, 0x1E5D0, 0x1E5ED, 0x1E5F0, 0x1E5FA, + 0x1E7E0, 0x1E7E6, 0x1E7E8, 0x1E7EB, 0x1E7ED, 0x1E7EE, 0x1E7F0, 0x1E7FE, 0x1E800, 0x1E8C4, 0x1E8C7, + 0x1E8CF, 0x1E900, 0x1E943, 0x1E94B, 0x1E94B, 0x1E950, 0x1E959, 0x1EC71, 0x1ECAB, 0x1ECAD, 0x1ECAF, + 0x1ECB1, 0x1ECB4, 0x1ED01, 0x1ED2D, 0x1ED2F, 0x1ED3D, 0x1EE00, 0x1EE03, 0x1EE05, 0x1EE1F, 0x1EE21, + 0x1EE22, 0x1EE24, 0x1EE24, 0x1EE27, 0x1EE27, 0x1EE29, 0x1EE32, 0x1EE34, 0x1EE37, 0x1EE39, 0x1EE39, + 0x1EE3B, 0x1EE3B, 0x1EE42, 0x1EE42, 0x1EE47, 0x1EE47, 0x1EE49, 0x1EE49, 0x1EE4B, 0x1EE4B, 0x1EE4D, + 0x1EE4F, 0x1EE51, 0x1EE52, 0x1EE54, 0x1EE54, 0x1EE57, 0x1EE57, 0x1EE59, 0x1EE59, 0x1EE5B, 0x1EE5B, + 0x1EE5D, 0x1EE5D, 0x1EE5F, 0x1EE5F, 0x1EE61, 0x1EE62, 0x1EE64, 0x1EE64, 0x1EE67, 0x1EE6A, 0x1EE6C, + 0x1EE72, 0x1EE74, 0x1EE77, 0x1EE79, 0x1EE7C, 0x1EE7E, 0x1EE7E, 0x1EE80, 0x1EE89, 0x1EE8B, 0x1EE9B, + 0x1EEA1, 0x1EEA3, 0x1EEA5, 0x1EEA9, 0x1EEAB, 0x1EEBB, 0x1F100, 0x1F10C, 0x1FBF0, 0x1FBF9, 0x20000, + 0x2A6DF, 0x2A700, 0x2B739, 0x2B740, 0x2B81D, 0x2B820, 0x2CEA1, 0x2CEB0, 0x2EBE0, 0x2EBF0, 0x2EE5D, + 0x2F800, 0x2FA1D, 0x30000, 0x3134A, 0x31350, 0x323AF, + }; + + public static readonly int[] DefaultIgnorable = + { + 0xAD, 0xAD, 0x34F, 0x34F, 0x61C, 0x61C, 0x115F, 0x1160, 0x17B4, 0x17B5, 0x180B, 0x180F, 0x200B, 0x200F, + 0x202A, 0x202E, 0x2060, 0x206F, 0x3164, 0x3164, 0xFE00, 0xFE0F, 0xFEFF, 0xFEFF, 0xFFA0, 0xFFA0, 0xFFF0, + 0xFFF8, 0x1BCA0, 0x1BCA3, 0x1D173, 0x1D17A, 0xE0000, 0xE0FFF, + }; + } +} From 8598fd3d12f588541fe5a2e34179cdb84e35c14f Mon Sep 17 00:00:00 2001 From: soul Date: Mon, 9 Nov 2026 15:05:00 +0000 Subject: [PATCH 30/76] cultnet-rs: write_admitted messageId from the generated Unicode 16.0 tables (letters and numbers now 16.0, not 15.1) Co-Authored-By: Claude Sonnet 5.5 --- packages/cultnet-rs/src/contracts.rs | 139 +---------------- packages/cultnet-rs/src/lib.rs | 1 + .../src/unicode_messageid_tables.rs | 147 ++++++++++++++++++ 3 files changed, 155 insertions(+), 132 deletions(-) create mode 100644 packages/cultnet-rs/src/unicode_messageid_tables.rs diff --git a/packages/cultnet-rs/src/contracts.rs b/packages/cultnet-rs/src/contracts.rs index 201632a2..bf907806 100644 --- a/packages/cultnet-rs/src/contracts.rs +++ b/packages/cultnet-rs/src/contracts.rs @@ -934,137 +934,12 @@ fn require_non_empty(value: &str, field: &str) -> Result<()> { Ok(()) } -// write-receipt.md: a write_admitted messageId is valid iff it holds a Unicode letter (\p{L}) or number (\p{N}) and no -// Default_Ignorable_Code_Point, compared raw (never trimmed). std has `char::is_numeric` (N) but no letter category -// (`is_alphabetic` is the Alphabetic property, which also takes combining marks), and no Default_Ignorable, so both tables -// are embedded: DerivedCoreProperties.txt ranges, Unicode 16.0 (unchanged since 15.1), and the general category L ranges of -// Unicode 15.1 (U+0378 is unassigned in every version, so it is refused either way). The Kotlin, Python and C# decoders embed -// the same Default_Ignorable ranges and take L/N from their platform; TypeScript uses its regex engine's tables. -// Invalid UTF-8 never reaches this check: the MessagePack str decode refuses it. -const DEFAULT_IGNORABLE: &[(u32, u32)] = &[ - (0x00AD, 0x00AD), (0x034F, 0x034F), (0x061C, 0x061C), (0x115F, 0x1160), (0x17B4, 0x17B5), (0x180B, 0x180F), - (0x200B, 0x200F), (0x202A, 0x202E), (0x2060, 0x206F), (0x3164, 0x3164), (0xFE00, 0xFE0F), (0xFEFF, 0xFEFF), - (0xFFA0, 0xFFA0), (0xFFF0, 0xFFF8), (0x1BCA0, 0x1BCA3), (0x1D173, 0x1D17A), (0xE0000, 0xE0FFF), -]; - -const LETTERS: &[(u32, u32)] = &[ - (0x41, 0x5A), (0x61, 0x7A), (0xAA, 0xAA), (0xB5, 0xB5), (0xBA, 0xBA), (0xC0, 0xD6), (0xD8, 0xF6), (0xF8, 0x2C1), - (0x2C6, 0x2D1), (0x2E0, 0x2E4), (0x2EC, 0x2EC), (0x2EE, 0x2EE), (0x370, 0x374), (0x376, 0x377), (0x37A, 0x37D), - (0x37F, 0x37F), (0x386, 0x386), (0x388, 0x38A), (0x38C, 0x38C), (0x38E, 0x3A1), (0x3A3, 0x3F5), (0x3F7, 0x481), - (0x48A, 0x52F), (0x531, 0x556), (0x559, 0x559), (0x560, 0x588), (0x5D0, 0x5EA), (0x5EF, 0x5F2), (0x620, 0x64A), - (0x66E, 0x66F), (0x671, 0x6D3), (0x6D5, 0x6D5), (0x6E5, 0x6E6), (0x6EE, 0x6EF), (0x6FA, 0x6FC), (0x6FF, 0x6FF), - (0x710, 0x710), (0x712, 0x72F), (0x74D, 0x7A5), (0x7B1, 0x7B1), (0x7CA, 0x7EA), (0x7F4, 0x7F5), (0x7FA, 0x7FA), - (0x800, 0x815), (0x81A, 0x81A), (0x824, 0x824), (0x828, 0x828), (0x840, 0x858), (0x860, 0x86A), (0x870, 0x887), - (0x889, 0x88E), (0x8A0, 0x8C9), (0x904, 0x939), (0x93D, 0x93D), (0x950, 0x950), (0x958, 0x961), (0x971, 0x980), - (0x985, 0x98C), (0x98F, 0x990), (0x993, 0x9A8), (0x9AA, 0x9B0), (0x9B2, 0x9B2), (0x9B6, 0x9B9), (0x9BD, 0x9BD), - (0x9CE, 0x9CE), (0x9DC, 0x9DD), (0x9DF, 0x9E1), (0x9F0, 0x9F1), (0x9FC, 0x9FC), (0xA05, 0xA0A), (0xA0F, 0xA10), - (0xA13, 0xA28), (0xA2A, 0xA30), (0xA32, 0xA33), (0xA35, 0xA36), (0xA38, 0xA39), (0xA59, 0xA5C), (0xA5E, 0xA5E), - (0xA72, 0xA74), (0xA85, 0xA8D), (0xA8F, 0xA91), (0xA93, 0xAA8), (0xAAA, 0xAB0), (0xAB2, 0xAB3), (0xAB5, 0xAB9), - (0xABD, 0xABD), (0xAD0, 0xAD0), (0xAE0, 0xAE1), (0xAF9, 0xAF9), (0xB05, 0xB0C), (0xB0F, 0xB10), (0xB13, 0xB28), - (0xB2A, 0xB30), (0xB32, 0xB33), (0xB35, 0xB39), (0xB3D, 0xB3D), (0xB5C, 0xB5D), (0xB5F, 0xB61), (0xB71, 0xB71), - (0xB83, 0xB83), (0xB85, 0xB8A), (0xB8E, 0xB90), (0xB92, 0xB95), (0xB99, 0xB9A), (0xB9C, 0xB9C), (0xB9E, 0xB9F), - (0xBA3, 0xBA4), (0xBA8, 0xBAA), (0xBAE, 0xBB9), (0xBD0, 0xBD0), (0xC05, 0xC0C), (0xC0E, 0xC10), (0xC12, 0xC28), - (0xC2A, 0xC39), (0xC3D, 0xC3D), (0xC58, 0xC5A), (0xC5D, 0xC5D), (0xC60, 0xC61), (0xC80, 0xC80), (0xC85, 0xC8C), - (0xC8E, 0xC90), (0xC92, 0xCA8), (0xCAA, 0xCB3), (0xCB5, 0xCB9), (0xCBD, 0xCBD), (0xCDD, 0xCDE), (0xCE0, 0xCE1), - (0xCF1, 0xCF2), (0xD04, 0xD0C), (0xD0E, 0xD10), (0xD12, 0xD3A), (0xD3D, 0xD3D), (0xD4E, 0xD4E), (0xD54, 0xD56), - (0xD5F, 0xD61), (0xD7A, 0xD7F), (0xD85, 0xD96), (0xD9A, 0xDB1), (0xDB3, 0xDBB), (0xDBD, 0xDBD), (0xDC0, 0xDC6), - (0xE01, 0xE30), (0xE32, 0xE33), (0xE40, 0xE46), (0xE81, 0xE82), (0xE84, 0xE84), (0xE86, 0xE8A), (0xE8C, 0xEA3), - (0xEA5, 0xEA5), (0xEA7, 0xEB0), (0xEB2, 0xEB3), (0xEBD, 0xEBD), (0xEC0, 0xEC4), (0xEC6, 0xEC6), (0xEDC, 0xEDF), - (0xF00, 0xF00), (0xF40, 0xF47), (0xF49, 0xF6C), (0xF88, 0xF8C), (0x1000, 0x102A), (0x103F, 0x103F), - (0x1050, 0x1055), (0x105A, 0x105D), (0x1061, 0x1061), (0x1065, 0x1066), (0x106E, 0x1070), (0x1075, 0x1081), - (0x108E, 0x108E), (0x10A0, 0x10C5), (0x10C7, 0x10C7), (0x10CD, 0x10CD), (0x10D0, 0x10FA), (0x10FC, 0x1248), - (0x124A, 0x124D), (0x1250, 0x1256), (0x1258, 0x1258), (0x125A, 0x125D), (0x1260, 0x1288), (0x128A, 0x128D), - (0x1290, 0x12B0), (0x12B2, 0x12B5), (0x12B8, 0x12BE), (0x12C0, 0x12C0), (0x12C2, 0x12C5), (0x12C8, 0x12D6), - (0x12D8, 0x1310), (0x1312, 0x1315), (0x1318, 0x135A), (0x1380, 0x138F), (0x13A0, 0x13F5), (0x13F8, 0x13FD), - (0x1401, 0x166C), (0x166F, 0x167F), (0x1681, 0x169A), (0x16A0, 0x16EA), (0x16F1, 0x16F8), (0x1700, 0x1711), - (0x171F, 0x1731), (0x1740, 0x1751), (0x1760, 0x176C), (0x176E, 0x1770), (0x1780, 0x17B3), (0x17D7, 0x17D7), - (0x17DC, 0x17DC), (0x1820, 0x1878), (0x1880, 0x1884), (0x1887, 0x18A8), (0x18AA, 0x18AA), (0x18B0, 0x18F5), - (0x1900, 0x191E), (0x1950, 0x196D), (0x1970, 0x1974), (0x1980, 0x19AB), (0x19B0, 0x19C9), (0x1A00, 0x1A16), - (0x1A20, 0x1A54), (0x1AA7, 0x1AA7), (0x1B05, 0x1B33), (0x1B45, 0x1B4C), (0x1B83, 0x1BA0), (0x1BAE, 0x1BAF), - (0x1BBA, 0x1BE5), (0x1C00, 0x1C23), (0x1C4D, 0x1C4F), (0x1C5A, 0x1C7D), (0x1C80, 0x1C88), (0x1C90, 0x1CBA), - (0x1CBD, 0x1CBF), (0x1CE9, 0x1CEC), (0x1CEE, 0x1CF3), (0x1CF5, 0x1CF6), (0x1CFA, 0x1CFA), (0x1D00, 0x1DBF), - (0x1E00, 0x1F15), (0x1F18, 0x1F1D), (0x1F20, 0x1F45), (0x1F48, 0x1F4D), (0x1F50, 0x1F57), (0x1F59, 0x1F59), - (0x1F5B, 0x1F5B), (0x1F5D, 0x1F5D), (0x1F5F, 0x1F7D), (0x1F80, 0x1FB4), (0x1FB6, 0x1FBC), (0x1FBE, 0x1FBE), - (0x1FC2, 0x1FC4), (0x1FC6, 0x1FCC), (0x1FD0, 0x1FD3), (0x1FD6, 0x1FDB), (0x1FE0, 0x1FEC), (0x1FF2, 0x1FF4), - (0x1FF6, 0x1FFC), (0x2071, 0x2071), (0x207F, 0x207F), (0x2090, 0x209C), (0x2102, 0x2102), (0x2107, 0x2107), - (0x210A, 0x2113), (0x2115, 0x2115), (0x2119, 0x211D), (0x2124, 0x2124), (0x2126, 0x2126), (0x2128, 0x2128), - (0x212A, 0x212D), (0x212F, 0x2139), (0x213C, 0x213F), (0x2145, 0x2149), (0x214E, 0x214E), (0x2183, 0x2184), - (0x2C00, 0x2CE4), (0x2CEB, 0x2CEE), (0x2CF2, 0x2CF3), (0x2D00, 0x2D25), (0x2D27, 0x2D27), (0x2D2D, 0x2D2D), - (0x2D30, 0x2D67), (0x2D6F, 0x2D6F), (0x2D80, 0x2D96), (0x2DA0, 0x2DA6), (0x2DA8, 0x2DAE), (0x2DB0, 0x2DB6), - (0x2DB8, 0x2DBE), (0x2DC0, 0x2DC6), (0x2DC8, 0x2DCE), (0x2DD0, 0x2DD6), (0x2DD8, 0x2DDE), (0x2E2F, 0x2E2F), - (0x3005, 0x3006), (0x3031, 0x3035), (0x303B, 0x303C), (0x3041, 0x3096), (0x309D, 0x309F), (0x30A1, 0x30FA), - (0x30FC, 0x30FF), (0x3105, 0x312F), (0x3131, 0x318E), (0x31A0, 0x31BF), (0x31F0, 0x31FF), (0x3400, 0x4DBF), - (0x4E00, 0xA48C), (0xA4D0, 0xA4FD), (0xA500, 0xA60C), (0xA610, 0xA61F), (0xA62A, 0xA62B), (0xA640, 0xA66E), - (0xA67F, 0xA69D), (0xA6A0, 0xA6E5), (0xA717, 0xA71F), (0xA722, 0xA788), (0xA78B, 0xA7CA), (0xA7D0, 0xA7D1), - (0xA7D3, 0xA7D3), (0xA7D5, 0xA7D9), (0xA7F2, 0xA801), (0xA803, 0xA805), (0xA807, 0xA80A), (0xA80C, 0xA822), - (0xA840, 0xA873), (0xA882, 0xA8B3), (0xA8F2, 0xA8F7), (0xA8FB, 0xA8FB), (0xA8FD, 0xA8FE), (0xA90A, 0xA925), - (0xA930, 0xA946), (0xA960, 0xA97C), (0xA984, 0xA9B2), (0xA9CF, 0xA9CF), (0xA9E0, 0xA9E4), (0xA9E6, 0xA9EF), - (0xA9FA, 0xA9FE), (0xAA00, 0xAA28), (0xAA40, 0xAA42), (0xAA44, 0xAA4B), (0xAA60, 0xAA76), (0xAA7A, 0xAA7A), - (0xAA7E, 0xAAAF), (0xAAB1, 0xAAB1), (0xAAB5, 0xAAB6), (0xAAB9, 0xAABD), (0xAAC0, 0xAAC0), (0xAAC2, 0xAAC2), - (0xAADB, 0xAADD), (0xAAE0, 0xAAEA), (0xAAF2, 0xAAF4), (0xAB01, 0xAB06), (0xAB09, 0xAB0E), (0xAB11, 0xAB16), - (0xAB20, 0xAB26), (0xAB28, 0xAB2E), (0xAB30, 0xAB5A), (0xAB5C, 0xAB69), (0xAB70, 0xABE2), (0xAC00, 0xD7A3), - (0xD7B0, 0xD7C6), (0xD7CB, 0xD7FB), (0xF900, 0xFA6D), (0xFA70, 0xFAD9), (0xFB00, 0xFB06), (0xFB13, 0xFB17), - (0xFB1D, 0xFB1D), (0xFB1F, 0xFB28), (0xFB2A, 0xFB36), (0xFB38, 0xFB3C), (0xFB3E, 0xFB3E), (0xFB40, 0xFB41), - (0xFB43, 0xFB44), (0xFB46, 0xFBB1), (0xFBD3, 0xFD3D), (0xFD50, 0xFD8F), (0xFD92, 0xFDC7), (0xFDF0, 0xFDFB), - (0xFE70, 0xFE74), (0xFE76, 0xFEFC), (0xFF21, 0xFF3A), (0xFF41, 0xFF5A), (0xFF66, 0xFFBE), (0xFFC2, 0xFFC7), - (0xFFCA, 0xFFCF), (0xFFD2, 0xFFD7), (0xFFDA, 0xFFDC), (0x10000, 0x1000B), (0x1000D, 0x10026), (0x10028, 0x1003A), - (0x1003C, 0x1003D), (0x1003F, 0x1004D), (0x10050, 0x1005D), (0x10080, 0x100FA), (0x10280, 0x1029C), - (0x102A0, 0x102D0), (0x10300, 0x1031F), (0x1032D, 0x10340), (0x10342, 0x10349), (0x10350, 0x10375), - (0x10380, 0x1039D), (0x103A0, 0x103C3), (0x103C8, 0x103CF), (0x10400, 0x1049D), (0x104B0, 0x104D3), - (0x104D8, 0x104FB), (0x10500, 0x10527), (0x10530, 0x10563), (0x10570, 0x1057A), (0x1057C, 0x1058A), - (0x1058C, 0x10592), (0x10594, 0x10595), (0x10597, 0x105A1), (0x105A3, 0x105B1), (0x105B3, 0x105B9), - (0x105BB, 0x105BC), (0x10600, 0x10736), (0x10740, 0x10755), (0x10760, 0x10767), (0x10780, 0x10785), - (0x10787, 0x107B0), (0x107B2, 0x107BA), (0x10800, 0x10805), (0x10808, 0x10808), (0x1080A, 0x10835), - (0x10837, 0x10838), (0x1083C, 0x1083C), (0x1083F, 0x10855), (0x10860, 0x10876), (0x10880, 0x1089E), - (0x108E0, 0x108F2), (0x108F4, 0x108F5), (0x10900, 0x10915), (0x10920, 0x10939), (0x10980, 0x109B7), - (0x109BE, 0x109BF), (0x10A00, 0x10A00), (0x10A10, 0x10A13), (0x10A15, 0x10A17), (0x10A19, 0x10A35), - (0x10A60, 0x10A7C), (0x10A80, 0x10A9C), (0x10AC0, 0x10AC7), (0x10AC9, 0x10AE4), (0x10B00, 0x10B35), - (0x10B40, 0x10B55), (0x10B60, 0x10B72), (0x10B80, 0x10B91), (0x10C00, 0x10C48), (0x10C80, 0x10CB2), - (0x10CC0, 0x10CF2), (0x10D00, 0x10D23), (0x10E80, 0x10EA9), (0x10EB0, 0x10EB1), (0x10F00, 0x10F1C), - (0x10F27, 0x10F27), (0x10F30, 0x10F45), (0x10F70, 0x10F81), (0x10FB0, 0x10FC4), (0x10FE0, 0x10FF6), - (0x11003, 0x11037), (0x11071, 0x11072), (0x11075, 0x11075), (0x11083, 0x110AF), (0x110D0, 0x110E8), - (0x11103, 0x11126), (0x11144, 0x11144), (0x11147, 0x11147), (0x11150, 0x11172), (0x11176, 0x11176), - (0x11183, 0x111B2), (0x111C1, 0x111C4), (0x111DA, 0x111DA), (0x111DC, 0x111DC), (0x11200, 0x11211), - (0x11213, 0x1122B), (0x1123F, 0x11240), (0x11280, 0x11286), (0x11288, 0x11288), (0x1128A, 0x1128D), - (0x1128F, 0x1129D), (0x1129F, 0x112A8), (0x112B0, 0x112DE), (0x11305, 0x1130C), (0x1130F, 0x11310), - (0x11313, 0x11328), (0x1132A, 0x11330), (0x11332, 0x11333), (0x11335, 0x11339), (0x1133D, 0x1133D), - (0x11350, 0x11350), (0x1135D, 0x11361), (0x11400, 0x11434), (0x11447, 0x1144A), (0x1145F, 0x11461), - (0x11480, 0x114AF), (0x114C4, 0x114C5), (0x114C7, 0x114C7), (0x11580, 0x115AE), (0x115D8, 0x115DB), - (0x11600, 0x1162F), (0x11644, 0x11644), (0x11680, 0x116AA), (0x116B8, 0x116B8), (0x11700, 0x1171A), - (0x11740, 0x11746), (0x11800, 0x1182B), (0x118A0, 0x118DF), (0x118FF, 0x11906), (0x11909, 0x11909), - (0x1190C, 0x11913), (0x11915, 0x11916), (0x11918, 0x1192F), (0x1193F, 0x1193F), (0x11941, 0x11941), - (0x119A0, 0x119A7), (0x119AA, 0x119D0), (0x119E1, 0x119E1), (0x119E3, 0x119E3), (0x11A00, 0x11A00), - (0x11A0B, 0x11A32), (0x11A3A, 0x11A3A), (0x11A50, 0x11A50), (0x11A5C, 0x11A89), (0x11A9D, 0x11A9D), - (0x11AB0, 0x11AF8), (0x11C00, 0x11C08), (0x11C0A, 0x11C2E), (0x11C40, 0x11C40), (0x11C72, 0x11C8F), - (0x11D00, 0x11D06), (0x11D08, 0x11D09), (0x11D0B, 0x11D30), (0x11D46, 0x11D46), (0x11D60, 0x11D65), - (0x11D67, 0x11D68), (0x11D6A, 0x11D89), (0x11D98, 0x11D98), (0x11EE0, 0x11EF2), (0x11F02, 0x11F02), - (0x11F04, 0x11F10), (0x11F12, 0x11F33), (0x11FB0, 0x11FB0), (0x12000, 0x12399), (0x12480, 0x12543), - (0x12F90, 0x12FF0), (0x13000, 0x1342F), (0x13441, 0x13446), (0x14400, 0x14646), (0x16800, 0x16A38), - (0x16A40, 0x16A5E), (0x16A70, 0x16ABE), (0x16AD0, 0x16AED), (0x16B00, 0x16B2F), (0x16B40, 0x16B43), - (0x16B63, 0x16B77), (0x16B7D, 0x16B8F), (0x16E40, 0x16E7F), (0x16F00, 0x16F4A), (0x16F50, 0x16F50), - (0x16F93, 0x16F9F), (0x16FE0, 0x16FE1), (0x16FE3, 0x16FE3), (0x17000, 0x187F7), (0x18800, 0x18CD5), - (0x18D00, 0x18D08), (0x1AFF0, 0x1AFF3), (0x1AFF5, 0x1AFFB), (0x1AFFD, 0x1AFFE), (0x1B000, 0x1B122), - (0x1B132, 0x1B132), (0x1B150, 0x1B152), (0x1B155, 0x1B155), (0x1B164, 0x1B167), (0x1B170, 0x1B2FB), - (0x1BC00, 0x1BC6A), (0x1BC70, 0x1BC7C), (0x1BC80, 0x1BC88), (0x1BC90, 0x1BC99), (0x1D400, 0x1D454), - (0x1D456, 0x1D49C), (0x1D49E, 0x1D49F), (0x1D4A2, 0x1D4A2), (0x1D4A5, 0x1D4A6), (0x1D4A9, 0x1D4AC), - (0x1D4AE, 0x1D4B9), (0x1D4BB, 0x1D4BB), (0x1D4BD, 0x1D4C3), (0x1D4C5, 0x1D505), (0x1D507, 0x1D50A), - (0x1D50D, 0x1D514), (0x1D516, 0x1D51C), (0x1D51E, 0x1D539), (0x1D53B, 0x1D53E), (0x1D540, 0x1D544), - (0x1D546, 0x1D546), (0x1D54A, 0x1D550), (0x1D552, 0x1D6A5), (0x1D6A8, 0x1D6C0), (0x1D6C2, 0x1D6DA), - (0x1D6DC, 0x1D6FA), (0x1D6FC, 0x1D714), (0x1D716, 0x1D734), (0x1D736, 0x1D74E), (0x1D750, 0x1D76E), - (0x1D770, 0x1D788), (0x1D78A, 0x1D7A8), (0x1D7AA, 0x1D7C2), (0x1D7C4, 0x1D7CB), (0x1DF00, 0x1DF1E), - (0x1DF25, 0x1DF2A), (0x1E030, 0x1E06D), (0x1E100, 0x1E12C), (0x1E137, 0x1E13D), (0x1E14E, 0x1E14E), - (0x1E290, 0x1E2AD), (0x1E2C0, 0x1E2EB), (0x1E4D0, 0x1E4EB), (0x1E7E0, 0x1E7E6), (0x1E7E8, 0x1E7EB), - (0x1E7ED, 0x1E7EE), (0x1E7F0, 0x1E7FE), (0x1E800, 0x1E8C4), (0x1E900, 0x1E943), (0x1E94B, 0x1E94B), - (0x1EE00, 0x1EE03), (0x1EE05, 0x1EE1F), (0x1EE21, 0x1EE22), (0x1EE24, 0x1EE24), (0x1EE27, 0x1EE27), - (0x1EE29, 0x1EE32), (0x1EE34, 0x1EE37), (0x1EE39, 0x1EE39), (0x1EE3B, 0x1EE3B), (0x1EE42, 0x1EE42), - (0x1EE47, 0x1EE47), (0x1EE49, 0x1EE49), (0x1EE4B, 0x1EE4B), (0x1EE4D, 0x1EE4F), (0x1EE51, 0x1EE52), - (0x1EE54, 0x1EE54), (0x1EE57, 0x1EE57), (0x1EE59, 0x1EE59), (0x1EE5B, 0x1EE5B), (0x1EE5D, 0x1EE5D), - (0x1EE5F, 0x1EE5F), (0x1EE61, 0x1EE62), (0x1EE64, 0x1EE64), (0x1EE67, 0x1EE6A), (0x1EE6C, 0x1EE72), - (0x1EE74, 0x1EE77), (0x1EE79, 0x1EE7C), (0x1EE7E, 0x1EE7E), (0x1EE80, 0x1EE89), (0x1EE8B, 0x1EE9B), - (0x1EEA1, 0x1EEA3), (0x1EEA5, 0x1EEA9), (0x1EEAB, 0x1EEBB), (0x20000, 0x2A6DF), (0x2A700, 0x2B739), - (0x2B740, 0x2B81D), (0x2B820, 0x2CEA1), (0x2CEB0, 0x2EBE0), (0x2EBF0, 0x2EE5D), (0x2F800, 0x2FA1D), - (0x30000, 0x3134A), (0x31350, 0x323AF), -]; +// write-receipt.md: a write_admitted messageId is valid iff it holds a Unicode letter (Lu Ll Lt Lm Lo) or number (Nd Nl No) and no +// Default_Ignorable_Code_Point, as of Unicode 16.0, compared raw (never trimmed). The tables are embedded +// (unicode_messageid_tables.rs, generated by scripts/gen-unicode-messageid-tables.py), not std's, so no platform's Unicode +// version changes a result; the other four runtimes embed the same generated tables. Invalid UTF-8 never reaches this check: +// the MessagePack str decode refuses it. +use crate::unicode_messageid_tables::{DEFAULT_IGNORABLE, LETTER_OR_NUMBER}; fn in_ranges(ranges: &[(u32, u32)], point: u32) -> bool { ranges.binary_search_by(|&(low, high)| { @@ -1086,7 +961,7 @@ fn require_admitted_message_id(value: &str) -> Result<()> { if in_ranges(DEFAULT_IGNORABLE, point) { return Err(anyhow!("CultNet field messageId must not hold a Default_Ignorable code point")); } - has_letter_or_number |= ch.is_numeric() || in_ranges(LETTERS, point); + has_letter_or_number |= in_ranges(LETTER_OR_NUMBER, point); } if !has_letter_or_number { return Err(anyhow!("CultNet field messageId must contain a letter or number")); diff --git a/packages/cultnet-rs/src/lib.rs b/packages/cultnet-rs/src/lib.rs index b35bd701..e008a2ba 100644 --- a/packages/cultnet-rs/src/lib.rs +++ b/packages/cultnet-rs/src/lib.rs @@ -3,6 +3,7 @@ mod contracts; mod idunn_deployment_brake; mod idunn_lifecycle_brake; mod media_fec; +mod unicode_messageid_tables; mod media_stream_contracts; mod media_stream_wire; mod provider_health_contracts; diff --git a/packages/cultnet-rs/src/unicode_messageid_tables.rs b/packages/cultnet-rs/src/unicode_messageid_tables.rs new file mode 100644 index 00000000..b5293f7a --- /dev/null +++ b/packages/cultnet-rs/src/unicode_messageid_tables.rs @@ -0,0 +1,147 @@ +// generated, Unicode 16.0.0, by scripts/gen-unicode-messageid-tables.py (inputs sha256 24a07ef9fabf71e3); do not edit + +// (low, high) pairs, sorted. +pub const LETTER_OR_NUMBER: &[(u32, u32)] = &[ + (0x30, 0x39), (0x41, 0x5A), (0x61, 0x7A), (0xAA, 0xAA), (0xB2, 0xB3), (0xB5, 0xB5), (0xB9, 0xBA), (0xBC, 0xBE), + (0xC0, 0xD6), (0xD8, 0xF6), (0xF8, 0x2C1), (0x2C6, 0x2D1), (0x2E0, 0x2E4), (0x2EC, 0x2EC), (0x2EE, 0x2EE), + (0x370, 0x374), (0x376, 0x377), (0x37A, 0x37D), (0x37F, 0x37F), (0x386, 0x386), (0x388, 0x38A), (0x38C, 0x38C), + (0x38E, 0x3A1), (0x3A3, 0x3F5), (0x3F7, 0x481), (0x48A, 0x52F), (0x531, 0x556), (0x559, 0x559), (0x560, 0x588), + (0x5D0, 0x5EA), (0x5EF, 0x5F2), (0x620, 0x64A), (0x660, 0x669), (0x66E, 0x66F), (0x671, 0x6D3), (0x6D5, 0x6D5), + (0x6E5, 0x6E6), (0x6EE, 0x6FC), (0x6FF, 0x6FF), (0x710, 0x710), (0x712, 0x72F), (0x74D, 0x7A5), (0x7B1, 0x7B1), + (0x7C0, 0x7EA), (0x7F4, 0x7F5), (0x7FA, 0x7FA), (0x800, 0x815), (0x81A, 0x81A), (0x824, 0x824), (0x828, 0x828), + (0x840, 0x858), (0x860, 0x86A), (0x870, 0x887), (0x889, 0x88E), (0x8A0, 0x8C9), (0x904, 0x939), (0x93D, 0x93D), + (0x950, 0x950), (0x958, 0x961), (0x966, 0x96F), (0x971, 0x980), (0x985, 0x98C), (0x98F, 0x990), (0x993, 0x9A8), + (0x9AA, 0x9B0), (0x9B2, 0x9B2), (0x9B6, 0x9B9), (0x9BD, 0x9BD), (0x9CE, 0x9CE), (0x9DC, 0x9DD), (0x9DF, 0x9E1), + (0x9E6, 0x9F1), (0x9F4, 0x9F9), (0x9FC, 0x9FC), (0xA05, 0xA0A), (0xA0F, 0xA10), (0xA13, 0xA28), (0xA2A, 0xA30), + (0xA32, 0xA33), (0xA35, 0xA36), (0xA38, 0xA39), (0xA59, 0xA5C), (0xA5E, 0xA5E), (0xA66, 0xA6F), (0xA72, 0xA74), + (0xA85, 0xA8D), (0xA8F, 0xA91), (0xA93, 0xAA8), (0xAAA, 0xAB0), (0xAB2, 0xAB3), (0xAB5, 0xAB9), (0xABD, 0xABD), + (0xAD0, 0xAD0), (0xAE0, 0xAE1), (0xAE6, 0xAEF), (0xAF9, 0xAF9), (0xB05, 0xB0C), (0xB0F, 0xB10), (0xB13, 0xB28), + (0xB2A, 0xB30), (0xB32, 0xB33), (0xB35, 0xB39), (0xB3D, 0xB3D), (0xB5C, 0xB5D), (0xB5F, 0xB61), (0xB66, 0xB6F), + (0xB71, 0xB77), (0xB83, 0xB83), (0xB85, 0xB8A), (0xB8E, 0xB90), (0xB92, 0xB95), (0xB99, 0xB9A), (0xB9C, 0xB9C), + (0xB9E, 0xB9F), (0xBA3, 0xBA4), (0xBA8, 0xBAA), (0xBAE, 0xBB9), (0xBD0, 0xBD0), (0xBE6, 0xBF2), (0xC05, 0xC0C), + (0xC0E, 0xC10), (0xC12, 0xC28), (0xC2A, 0xC39), (0xC3D, 0xC3D), (0xC58, 0xC5A), (0xC5D, 0xC5D), (0xC60, 0xC61), + (0xC66, 0xC6F), (0xC78, 0xC7E), (0xC80, 0xC80), (0xC85, 0xC8C), (0xC8E, 0xC90), (0xC92, 0xCA8), (0xCAA, 0xCB3), + (0xCB5, 0xCB9), (0xCBD, 0xCBD), (0xCDD, 0xCDE), (0xCE0, 0xCE1), (0xCE6, 0xCEF), (0xCF1, 0xCF2), (0xD04, 0xD0C), + (0xD0E, 0xD10), (0xD12, 0xD3A), (0xD3D, 0xD3D), (0xD4E, 0xD4E), (0xD54, 0xD56), (0xD58, 0xD61), (0xD66, 0xD78), + (0xD7A, 0xD7F), (0xD85, 0xD96), (0xD9A, 0xDB1), (0xDB3, 0xDBB), (0xDBD, 0xDBD), (0xDC0, 0xDC6), (0xDE6, 0xDEF), + (0xE01, 0xE30), (0xE32, 0xE33), (0xE40, 0xE46), (0xE50, 0xE59), (0xE81, 0xE82), (0xE84, 0xE84), (0xE86, 0xE8A), + (0xE8C, 0xEA3), (0xEA5, 0xEA5), (0xEA7, 0xEB0), (0xEB2, 0xEB3), (0xEBD, 0xEBD), (0xEC0, 0xEC4), (0xEC6, 0xEC6), + (0xED0, 0xED9), (0xEDC, 0xEDF), (0xF00, 0xF00), (0xF20, 0xF33), (0xF40, 0xF47), (0xF49, 0xF6C), (0xF88, 0xF8C), + (0x1000, 0x102A), (0x103F, 0x1049), (0x1050, 0x1055), (0x105A, 0x105D), (0x1061, 0x1061), (0x1065, 0x1066), + (0x106E, 0x1070), (0x1075, 0x1081), (0x108E, 0x108E), (0x1090, 0x1099), (0x10A0, 0x10C5), (0x10C7, 0x10C7), + (0x10CD, 0x10CD), (0x10D0, 0x10FA), (0x10FC, 0x1248), (0x124A, 0x124D), (0x1250, 0x1256), (0x1258, 0x1258), + (0x125A, 0x125D), (0x1260, 0x1288), (0x128A, 0x128D), (0x1290, 0x12B0), (0x12B2, 0x12B5), (0x12B8, 0x12BE), + (0x12C0, 0x12C0), (0x12C2, 0x12C5), (0x12C8, 0x12D6), (0x12D8, 0x1310), (0x1312, 0x1315), (0x1318, 0x135A), + (0x1369, 0x137C), (0x1380, 0x138F), (0x13A0, 0x13F5), (0x13F8, 0x13FD), (0x1401, 0x166C), (0x166F, 0x167F), + (0x1681, 0x169A), (0x16A0, 0x16EA), (0x16EE, 0x16F8), (0x1700, 0x1711), (0x171F, 0x1731), (0x1740, 0x1751), + (0x1760, 0x176C), (0x176E, 0x1770), (0x1780, 0x17B3), (0x17D7, 0x17D7), (0x17DC, 0x17DC), (0x17E0, 0x17E9), + (0x17F0, 0x17F9), (0x1810, 0x1819), (0x1820, 0x1878), (0x1880, 0x1884), (0x1887, 0x18A8), (0x18AA, 0x18AA), + (0x18B0, 0x18F5), (0x1900, 0x191E), (0x1946, 0x196D), (0x1970, 0x1974), (0x1980, 0x19AB), (0x19B0, 0x19C9), + (0x19D0, 0x19DA), (0x1A00, 0x1A16), (0x1A20, 0x1A54), (0x1A80, 0x1A89), (0x1A90, 0x1A99), (0x1AA7, 0x1AA7), + (0x1B05, 0x1B33), (0x1B45, 0x1B4C), (0x1B50, 0x1B59), (0x1B83, 0x1BA0), (0x1BAE, 0x1BE5), (0x1C00, 0x1C23), + (0x1C40, 0x1C49), (0x1C4D, 0x1C7D), (0x1C80, 0x1C8A), (0x1C90, 0x1CBA), (0x1CBD, 0x1CBF), (0x1CE9, 0x1CEC), + (0x1CEE, 0x1CF3), (0x1CF5, 0x1CF6), (0x1CFA, 0x1CFA), (0x1D00, 0x1DBF), (0x1E00, 0x1F15), (0x1F18, 0x1F1D), + (0x1F20, 0x1F45), (0x1F48, 0x1F4D), (0x1F50, 0x1F57), (0x1F59, 0x1F59), (0x1F5B, 0x1F5B), (0x1F5D, 0x1F5D), + (0x1F5F, 0x1F7D), (0x1F80, 0x1FB4), (0x1FB6, 0x1FBC), (0x1FBE, 0x1FBE), (0x1FC2, 0x1FC4), (0x1FC6, 0x1FCC), + (0x1FD0, 0x1FD3), (0x1FD6, 0x1FDB), (0x1FE0, 0x1FEC), (0x1FF2, 0x1FF4), (0x1FF6, 0x1FFC), (0x2070, 0x2071), + (0x2074, 0x2079), (0x207F, 0x2089), (0x2090, 0x209C), (0x2102, 0x2102), (0x2107, 0x2107), (0x210A, 0x2113), + (0x2115, 0x2115), (0x2119, 0x211D), (0x2124, 0x2124), (0x2126, 0x2126), (0x2128, 0x2128), (0x212A, 0x212D), + (0x212F, 0x2139), (0x213C, 0x213F), (0x2145, 0x2149), (0x214E, 0x214E), (0x2150, 0x2189), (0x2460, 0x249B), + (0x24EA, 0x24FF), (0x2776, 0x2793), (0x2C00, 0x2CE4), (0x2CEB, 0x2CEE), (0x2CF2, 0x2CF3), (0x2CFD, 0x2CFD), + (0x2D00, 0x2D25), (0x2D27, 0x2D27), (0x2D2D, 0x2D2D), (0x2D30, 0x2D67), (0x2D6F, 0x2D6F), (0x2D80, 0x2D96), + (0x2DA0, 0x2DA6), (0x2DA8, 0x2DAE), (0x2DB0, 0x2DB6), (0x2DB8, 0x2DBE), (0x2DC0, 0x2DC6), (0x2DC8, 0x2DCE), + (0x2DD0, 0x2DD6), (0x2DD8, 0x2DDE), (0x2E2F, 0x2E2F), (0x3005, 0x3007), (0x3021, 0x3029), (0x3031, 0x3035), + (0x3038, 0x303C), (0x3041, 0x3096), (0x309D, 0x309F), (0x30A1, 0x30FA), (0x30FC, 0x30FF), (0x3105, 0x312F), + (0x3131, 0x318E), (0x3192, 0x3195), (0x31A0, 0x31BF), (0x31F0, 0x31FF), (0x3220, 0x3229), (0x3248, 0x324F), + (0x3251, 0x325F), (0x3280, 0x3289), (0x32B1, 0x32BF), (0x3400, 0x4DBF), (0x4E00, 0xA48C), (0xA4D0, 0xA4FD), + (0xA500, 0xA60C), (0xA610, 0xA62B), (0xA640, 0xA66E), (0xA67F, 0xA69D), (0xA6A0, 0xA6EF), (0xA717, 0xA71F), + (0xA722, 0xA788), (0xA78B, 0xA7CD), (0xA7D0, 0xA7D1), (0xA7D3, 0xA7D3), (0xA7D5, 0xA7DC), (0xA7F2, 0xA801), + (0xA803, 0xA805), (0xA807, 0xA80A), (0xA80C, 0xA822), (0xA830, 0xA835), (0xA840, 0xA873), (0xA882, 0xA8B3), + (0xA8D0, 0xA8D9), (0xA8F2, 0xA8F7), (0xA8FB, 0xA8FB), (0xA8FD, 0xA8FE), (0xA900, 0xA925), (0xA930, 0xA946), + (0xA960, 0xA97C), (0xA984, 0xA9B2), (0xA9CF, 0xA9D9), (0xA9E0, 0xA9E4), (0xA9E6, 0xA9FE), (0xAA00, 0xAA28), + (0xAA40, 0xAA42), (0xAA44, 0xAA4B), (0xAA50, 0xAA59), (0xAA60, 0xAA76), (0xAA7A, 0xAA7A), (0xAA7E, 0xAAAF), + (0xAAB1, 0xAAB1), (0xAAB5, 0xAAB6), (0xAAB9, 0xAABD), (0xAAC0, 0xAAC0), (0xAAC2, 0xAAC2), (0xAADB, 0xAADD), + (0xAAE0, 0xAAEA), (0xAAF2, 0xAAF4), (0xAB01, 0xAB06), (0xAB09, 0xAB0E), (0xAB11, 0xAB16), (0xAB20, 0xAB26), + (0xAB28, 0xAB2E), (0xAB30, 0xAB5A), (0xAB5C, 0xAB69), (0xAB70, 0xABE2), (0xABF0, 0xABF9), (0xAC00, 0xD7A3), + (0xD7B0, 0xD7C6), (0xD7CB, 0xD7FB), (0xF900, 0xFA6D), (0xFA70, 0xFAD9), (0xFB00, 0xFB06), (0xFB13, 0xFB17), + (0xFB1D, 0xFB1D), (0xFB1F, 0xFB28), (0xFB2A, 0xFB36), (0xFB38, 0xFB3C), (0xFB3E, 0xFB3E), (0xFB40, 0xFB41), + (0xFB43, 0xFB44), (0xFB46, 0xFBB1), (0xFBD3, 0xFD3D), (0xFD50, 0xFD8F), (0xFD92, 0xFDC7), (0xFDF0, 0xFDFB), + (0xFE70, 0xFE74), (0xFE76, 0xFEFC), (0xFF10, 0xFF19), (0xFF21, 0xFF3A), (0xFF41, 0xFF5A), (0xFF66, 0xFFBE), + (0xFFC2, 0xFFC7), (0xFFCA, 0xFFCF), (0xFFD2, 0xFFD7), (0xFFDA, 0xFFDC), (0x10000, 0x1000B), (0x1000D, 0x10026), + (0x10028, 0x1003A), (0x1003C, 0x1003D), (0x1003F, 0x1004D), (0x10050, 0x1005D), (0x10080, 0x100FA), + (0x10107, 0x10133), (0x10140, 0x10178), (0x1018A, 0x1018B), (0x10280, 0x1029C), (0x102A0, 0x102D0), + (0x102E1, 0x102FB), (0x10300, 0x10323), (0x1032D, 0x1034A), (0x10350, 0x10375), (0x10380, 0x1039D), + (0x103A0, 0x103C3), (0x103C8, 0x103CF), (0x103D1, 0x103D5), (0x10400, 0x1049D), (0x104A0, 0x104A9), + (0x104B0, 0x104D3), (0x104D8, 0x104FB), (0x10500, 0x10527), (0x10530, 0x10563), (0x10570, 0x1057A), + (0x1057C, 0x1058A), (0x1058C, 0x10592), (0x10594, 0x10595), (0x10597, 0x105A1), (0x105A3, 0x105B1), + (0x105B3, 0x105B9), (0x105BB, 0x105BC), (0x105C0, 0x105F3), (0x10600, 0x10736), (0x10740, 0x10755), + (0x10760, 0x10767), (0x10780, 0x10785), (0x10787, 0x107B0), (0x107B2, 0x107BA), (0x10800, 0x10805), + (0x10808, 0x10808), (0x1080A, 0x10835), (0x10837, 0x10838), (0x1083C, 0x1083C), (0x1083F, 0x10855), + (0x10858, 0x10876), (0x10879, 0x1089E), (0x108A7, 0x108AF), (0x108E0, 0x108F2), (0x108F4, 0x108F5), + (0x108FB, 0x1091B), (0x10920, 0x10939), (0x10980, 0x109B7), (0x109BC, 0x109CF), (0x109D2, 0x10A00), + (0x10A10, 0x10A13), (0x10A15, 0x10A17), (0x10A19, 0x10A35), (0x10A40, 0x10A48), (0x10A60, 0x10A7E), + (0x10A80, 0x10A9F), (0x10AC0, 0x10AC7), (0x10AC9, 0x10AE4), (0x10AEB, 0x10AEF), (0x10B00, 0x10B35), + (0x10B40, 0x10B55), (0x10B58, 0x10B72), (0x10B78, 0x10B91), (0x10BA9, 0x10BAF), (0x10C00, 0x10C48), + (0x10C80, 0x10CB2), (0x10CC0, 0x10CF2), (0x10CFA, 0x10D23), (0x10D30, 0x10D39), (0x10D40, 0x10D65), + (0x10D6F, 0x10D85), (0x10E60, 0x10E7E), (0x10E80, 0x10EA9), (0x10EB0, 0x10EB1), (0x10EC2, 0x10EC4), + (0x10F00, 0x10F27), (0x10F30, 0x10F45), (0x10F51, 0x10F54), (0x10F70, 0x10F81), (0x10FB0, 0x10FCB), + (0x10FE0, 0x10FF6), (0x11003, 0x11037), (0x11052, 0x1106F), (0x11071, 0x11072), (0x11075, 0x11075), + (0x11083, 0x110AF), (0x110D0, 0x110E8), (0x110F0, 0x110F9), (0x11103, 0x11126), (0x11136, 0x1113F), + (0x11144, 0x11144), (0x11147, 0x11147), (0x11150, 0x11172), (0x11176, 0x11176), (0x11183, 0x111B2), + (0x111C1, 0x111C4), (0x111D0, 0x111DA), (0x111DC, 0x111DC), (0x111E1, 0x111F4), (0x11200, 0x11211), + (0x11213, 0x1122B), (0x1123F, 0x11240), (0x11280, 0x11286), (0x11288, 0x11288), (0x1128A, 0x1128D), + (0x1128F, 0x1129D), (0x1129F, 0x112A8), (0x112B0, 0x112DE), (0x112F0, 0x112F9), (0x11305, 0x1130C), + (0x1130F, 0x11310), (0x11313, 0x11328), (0x1132A, 0x11330), (0x11332, 0x11333), (0x11335, 0x11339), + (0x1133D, 0x1133D), (0x11350, 0x11350), (0x1135D, 0x11361), (0x11380, 0x11389), (0x1138B, 0x1138B), + (0x1138E, 0x1138E), (0x11390, 0x113B5), (0x113B7, 0x113B7), (0x113D1, 0x113D1), (0x113D3, 0x113D3), + (0x11400, 0x11434), (0x11447, 0x1144A), (0x11450, 0x11459), (0x1145F, 0x11461), (0x11480, 0x114AF), + (0x114C4, 0x114C5), (0x114C7, 0x114C7), (0x114D0, 0x114D9), (0x11580, 0x115AE), (0x115D8, 0x115DB), + (0x11600, 0x1162F), (0x11644, 0x11644), (0x11650, 0x11659), (0x11680, 0x116AA), (0x116B8, 0x116B8), + (0x116C0, 0x116C9), (0x116D0, 0x116E3), (0x11700, 0x1171A), (0x11730, 0x1173B), (0x11740, 0x11746), + (0x11800, 0x1182B), (0x118A0, 0x118F2), (0x118FF, 0x11906), (0x11909, 0x11909), (0x1190C, 0x11913), + (0x11915, 0x11916), (0x11918, 0x1192F), (0x1193F, 0x1193F), (0x11941, 0x11941), (0x11950, 0x11959), + (0x119A0, 0x119A7), (0x119AA, 0x119D0), (0x119E1, 0x119E1), (0x119E3, 0x119E3), (0x11A00, 0x11A00), + (0x11A0B, 0x11A32), (0x11A3A, 0x11A3A), (0x11A50, 0x11A50), (0x11A5C, 0x11A89), (0x11A9D, 0x11A9D), + (0x11AB0, 0x11AF8), (0x11BC0, 0x11BE0), (0x11BF0, 0x11BF9), (0x11C00, 0x11C08), (0x11C0A, 0x11C2E), + (0x11C40, 0x11C40), (0x11C50, 0x11C6C), (0x11C72, 0x11C8F), (0x11D00, 0x11D06), (0x11D08, 0x11D09), + (0x11D0B, 0x11D30), (0x11D46, 0x11D46), (0x11D50, 0x11D59), (0x11D60, 0x11D65), (0x11D67, 0x11D68), + (0x11D6A, 0x11D89), (0x11D98, 0x11D98), (0x11DA0, 0x11DA9), (0x11EE0, 0x11EF2), (0x11F02, 0x11F02), + (0x11F04, 0x11F10), (0x11F12, 0x11F33), (0x11F50, 0x11F59), (0x11FB0, 0x11FB0), (0x11FC0, 0x11FD4), + (0x12000, 0x12399), (0x12400, 0x1246E), (0x12480, 0x12543), (0x12F90, 0x12FF0), (0x13000, 0x1342F), + (0x13441, 0x13446), (0x13460, 0x143FA), (0x14400, 0x14646), (0x16100, 0x1611D), (0x16130, 0x16139), + (0x16800, 0x16A38), (0x16A40, 0x16A5E), (0x16A60, 0x16A69), (0x16A70, 0x16ABE), (0x16AC0, 0x16AC9), + (0x16AD0, 0x16AED), (0x16B00, 0x16B2F), (0x16B40, 0x16B43), (0x16B50, 0x16B59), (0x16B5B, 0x16B61), + (0x16B63, 0x16B77), (0x16B7D, 0x16B8F), (0x16D40, 0x16D6C), (0x16D70, 0x16D79), (0x16E40, 0x16E96), + (0x16F00, 0x16F4A), (0x16F50, 0x16F50), (0x16F93, 0x16F9F), (0x16FE0, 0x16FE1), (0x16FE3, 0x16FE3), + (0x17000, 0x187F7), (0x18800, 0x18CD5), (0x18CFF, 0x18D08), (0x1AFF0, 0x1AFF3), (0x1AFF5, 0x1AFFB), + (0x1AFFD, 0x1AFFE), (0x1B000, 0x1B122), (0x1B132, 0x1B132), (0x1B150, 0x1B152), (0x1B155, 0x1B155), + (0x1B164, 0x1B167), (0x1B170, 0x1B2FB), (0x1BC00, 0x1BC6A), (0x1BC70, 0x1BC7C), (0x1BC80, 0x1BC88), + (0x1BC90, 0x1BC99), (0x1CCF0, 0x1CCF9), (0x1D2C0, 0x1D2D3), (0x1D2E0, 0x1D2F3), (0x1D360, 0x1D378), + (0x1D400, 0x1D454), (0x1D456, 0x1D49C), (0x1D49E, 0x1D49F), (0x1D4A2, 0x1D4A2), (0x1D4A5, 0x1D4A6), + (0x1D4A9, 0x1D4AC), (0x1D4AE, 0x1D4B9), (0x1D4BB, 0x1D4BB), (0x1D4BD, 0x1D4C3), (0x1D4C5, 0x1D505), + (0x1D507, 0x1D50A), (0x1D50D, 0x1D514), (0x1D516, 0x1D51C), (0x1D51E, 0x1D539), (0x1D53B, 0x1D53E), + (0x1D540, 0x1D544), (0x1D546, 0x1D546), (0x1D54A, 0x1D550), (0x1D552, 0x1D6A5), (0x1D6A8, 0x1D6C0), + (0x1D6C2, 0x1D6DA), (0x1D6DC, 0x1D6FA), (0x1D6FC, 0x1D714), (0x1D716, 0x1D734), (0x1D736, 0x1D74E), + (0x1D750, 0x1D76E), (0x1D770, 0x1D788), (0x1D78A, 0x1D7A8), (0x1D7AA, 0x1D7C2), (0x1D7C4, 0x1D7CB), + (0x1D7CE, 0x1D7FF), (0x1DF00, 0x1DF1E), (0x1DF25, 0x1DF2A), (0x1E030, 0x1E06D), (0x1E100, 0x1E12C), + (0x1E137, 0x1E13D), (0x1E140, 0x1E149), (0x1E14E, 0x1E14E), (0x1E290, 0x1E2AD), (0x1E2C0, 0x1E2EB), + (0x1E2F0, 0x1E2F9), (0x1E4D0, 0x1E4EB), (0x1E4F0, 0x1E4F9), (0x1E5D0, 0x1E5ED), (0x1E5F0, 0x1E5FA), + (0x1E7E0, 0x1E7E6), (0x1E7E8, 0x1E7EB), (0x1E7ED, 0x1E7EE), (0x1E7F0, 0x1E7FE), (0x1E800, 0x1E8C4), + (0x1E8C7, 0x1E8CF), (0x1E900, 0x1E943), (0x1E94B, 0x1E94B), (0x1E950, 0x1E959), (0x1EC71, 0x1ECAB), + (0x1ECAD, 0x1ECAF), (0x1ECB1, 0x1ECB4), (0x1ED01, 0x1ED2D), (0x1ED2F, 0x1ED3D), (0x1EE00, 0x1EE03), + (0x1EE05, 0x1EE1F), (0x1EE21, 0x1EE22), (0x1EE24, 0x1EE24), (0x1EE27, 0x1EE27), (0x1EE29, 0x1EE32), + (0x1EE34, 0x1EE37), (0x1EE39, 0x1EE39), (0x1EE3B, 0x1EE3B), (0x1EE42, 0x1EE42), (0x1EE47, 0x1EE47), + (0x1EE49, 0x1EE49), (0x1EE4B, 0x1EE4B), (0x1EE4D, 0x1EE4F), (0x1EE51, 0x1EE52), (0x1EE54, 0x1EE54), + (0x1EE57, 0x1EE57), (0x1EE59, 0x1EE59), (0x1EE5B, 0x1EE5B), (0x1EE5D, 0x1EE5D), (0x1EE5F, 0x1EE5F), + (0x1EE61, 0x1EE62), (0x1EE64, 0x1EE64), (0x1EE67, 0x1EE6A), (0x1EE6C, 0x1EE72), (0x1EE74, 0x1EE77), + (0x1EE79, 0x1EE7C), (0x1EE7E, 0x1EE7E), (0x1EE80, 0x1EE89), (0x1EE8B, 0x1EE9B), (0x1EEA1, 0x1EEA3), + (0x1EEA5, 0x1EEA9), (0x1EEAB, 0x1EEBB), (0x1F100, 0x1F10C), (0x1FBF0, 0x1FBF9), (0x20000, 0x2A6DF), + (0x2A700, 0x2B739), (0x2B740, 0x2B81D), (0x2B820, 0x2CEA1), (0x2CEB0, 0x2EBE0), (0x2EBF0, 0x2EE5D), + (0x2F800, 0x2FA1D), (0x30000, 0x3134A), (0x31350, 0x323AF), +]; + +pub const DEFAULT_IGNORABLE: &[(u32, u32)] = &[ + (0xAD, 0xAD), (0x34F, 0x34F), (0x61C, 0x61C), (0x115F, 0x1160), (0x17B4, 0x17B5), (0x180B, 0x180F), + (0x200B, 0x200F), (0x202A, 0x202E), (0x2060, 0x206F), (0x3164, 0x3164), (0xFE00, 0xFE0F), (0xFEFF, 0xFEFF), + (0xFFA0, 0xFFA0), (0xFFF0, 0xFFF8), (0x1BCA0, 0x1BCA3), (0x1D173, 0x1D17A), (0xE0000, 0xE0FFF), +]; From f378d4fdb095dc7f902c243724d8d0f6faf18456 Mon Sep 17 00:00:00 2001 From: soul Date: Mon, 9 Nov 2026 15:11:15 +0000 Subject: [PATCH 31/76] tests first: duplicate messageId, ill-formed id, scoped UTF-8 strictness, every table range pinned (failing) Failing before the fix: C# 1 (duplicate messageId, second is U+200B), TypeScript 1 (a + lone surrogate), Kotlin 1 (a document payload string with a lone surrogate no longer decodes). Python and Rust already pass the new vectors. The range probes read the generated write-admitted-message-id-ranges.json and pass on the correct tables; they exist to fail when any range is deleted or narrowed. Co-Authored-By: Claude Sonnet 5.5 --- .../write-admitted-message-id-ranges.json | 1 + .../interop/write-receipt-vectors.json | 122 +++++++++++++++++- .../gamecult/cultmesh/WriteReceiptVectors.kt | 32 +++++ .../cultnet-py/tests/test_write_receipt.py | 26 ++++ packages/cultnet-rs/tests/write_receipt.rs | 37 ++++++ .../cultnet-ts/test/write-receipt.test.ts | 20 +++ scripts/gen-unicode-messageid-tables.py | 14 +- .../CultNetWriteAdmittedMessageIdTests.cs | 33 +++++ 8 files changed, 279 insertions(+), 6 deletions(-) create mode 100644 contracts/cultnet/interop/write-admitted-message-id-ranges.json diff --git a/contracts/cultnet/interop/write-admitted-message-id-ranges.json b/contracts/cultnet/interop/write-admitted-message-id-ranges.json new file mode 100644 index 00000000..98ccdec0 --- /dev/null +++ b/contracts/cultnet/interop/write-admitted-message-id-ranges.json @@ -0,0 +1 @@ +{"generated":"generated, Unicode 16.0.0, by scripts/gen-unicode-messageid-tables.py (inputs sha256 24a07ef9fabf71e3); do not edit","unicodeVersion":"16.0.0","letterOrNumber":[[48,57],[65,90],[97,122],[170,170],[178,179],[181,181],[185,186],[188,190],[192,214],[216,246],[248,705],[710,721],[736,740],[748,748],[750,750],[880,884],[886,887],[890,893],[895,895],[902,902],[904,906],[908,908],[910,929],[931,1013],[1015,1153],[1162,1327],[1329,1366],[1369,1369],[1376,1416],[1488,1514],[1519,1522],[1568,1610],[1632,1641],[1646,1647],[1649,1747],[1749,1749],[1765,1766],[1774,1788],[1791,1791],[1808,1808],[1810,1839],[1869,1957],[1969,1969],[1984,2026],[2036,2037],[2042,2042],[2048,2069],[2074,2074],[2084,2084],[2088,2088],[2112,2136],[2144,2154],[2160,2183],[2185,2190],[2208,2249],[2308,2361],[2365,2365],[2384,2384],[2392,2401],[2406,2415],[2417,2432],[2437,2444],[2447,2448],[2451,2472],[2474,2480],[2482,2482],[2486,2489],[2493,2493],[2510,2510],[2524,2525],[2527,2529],[2534,2545],[2548,2553],[2556,2556],[2565,2570],[2575,2576],[2579,2600],[2602,2608],[2610,2611],[2613,2614],[2616,2617],[2649,2652],[2654,2654],[2662,2671],[2674,2676],[2693,2701],[2703,2705],[2707,2728],[2730,2736],[2738,2739],[2741,2745],[2749,2749],[2768,2768],[2784,2785],[2790,2799],[2809,2809],[2821,2828],[2831,2832],[2835,2856],[2858,2864],[2866,2867],[2869,2873],[2877,2877],[2908,2909],[2911,2913],[2918,2927],[2929,2935],[2947,2947],[2949,2954],[2958,2960],[2962,2965],[2969,2970],[2972,2972],[2974,2975],[2979,2980],[2984,2986],[2990,3001],[3024,3024],[3046,3058],[3077,3084],[3086,3088],[3090,3112],[3114,3129],[3133,3133],[3160,3162],[3165,3165],[3168,3169],[3174,3183],[3192,3198],[3200,3200],[3205,3212],[3214,3216],[3218,3240],[3242,3251],[3253,3257],[3261,3261],[3293,3294],[3296,3297],[3302,3311],[3313,3314],[3332,3340],[3342,3344],[3346,3386],[3389,3389],[3406,3406],[3412,3414],[3416,3425],[3430,3448],[3450,3455],[3461,3478],[3482,3505],[3507,3515],[3517,3517],[3520,3526],[3558,3567],[3585,3632],[3634,3635],[3648,3654],[3664,3673],[3713,3714],[3716,3716],[3718,3722],[3724,3747],[3749,3749],[3751,3760],[3762,3763],[3773,3773],[3776,3780],[3782,3782],[3792,3801],[3804,3807],[3840,3840],[3872,3891],[3904,3911],[3913,3948],[3976,3980],[4096,4138],[4159,4169],[4176,4181],[4186,4189],[4193,4193],[4197,4198],[4206,4208],[4213,4225],[4238,4238],[4240,4249],[4256,4293],[4295,4295],[4301,4301],[4304,4346],[4348,4680],[4682,4685],[4688,4694],[4696,4696],[4698,4701],[4704,4744],[4746,4749],[4752,4784],[4786,4789],[4792,4798],[4800,4800],[4802,4805],[4808,4822],[4824,4880],[4882,4885],[4888,4954],[4969,4988],[4992,5007],[5024,5109],[5112,5117],[5121,5740],[5743,5759],[5761,5786],[5792,5866],[5870,5880],[5888,5905],[5919,5937],[5952,5969],[5984,5996],[5998,6000],[6016,6067],[6103,6103],[6108,6108],[6112,6121],[6128,6137],[6160,6169],[6176,6264],[6272,6276],[6279,6312],[6314,6314],[6320,6389],[6400,6430],[6470,6509],[6512,6516],[6528,6571],[6576,6601],[6608,6618],[6656,6678],[6688,6740],[6784,6793],[6800,6809],[6823,6823],[6917,6963],[6981,6988],[6992,7001],[7043,7072],[7086,7141],[7168,7203],[7232,7241],[7245,7293],[7296,7306],[7312,7354],[7357,7359],[7401,7404],[7406,7411],[7413,7414],[7418,7418],[7424,7615],[7680,7957],[7960,7965],[7968,8005],[8008,8013],[8016,8023],[8025,8025],[8027,8027],[8029,8029],[8031,8061],[8064,8116],[8118,8124],[8126,8126],[8130,8132],[8134,8140],[8144,8147],[8150,8155],[8160,8172],[8178,8180],[8182,8188],[8304,8305],[8308,8313],[8319,8329],[8336,8348],[8450,8450],[8455,8455],[8458,8467],[8469,8469],[8473,8477],[8484,8484],[8486,8486],[8488,8488],[8490,8493],[8495,8505],[8508,8511],[8517,8521],[8526,8526],[8528,8585],[9312,9371],[9450,9471],[10102,10131],[11264,11492],[11499,11502],[11506,11507],[11517,11517],[11520,11557],[11559,11559],[11565,11565],[11568,11623],[11631,11631],[11648,11670],[11680,11686],[11688,11694],[11696,11702],[11704,11710],[11712,11718],[11720,11726],[11728,11734],[11736,11742],[11823,11823],[12293,12295],[12321,12329],[12337,12341],[12344,12348],[12353,12438],[12445,12447],[12449,12538],[12540,12543],[12549,12591],[12593,12686],[12690,12693],[12704,12735],[12784,12799],[12832,12841],[12872,12879],[12881,12895],[12928,12937],[12977,12991],[13312,19903],[19968,42124],[42192,42237],[42240,42508],[42512,42539],[42560,42606],[42623,42653],[42656,42735],[42775,42783],[42786,42888],[42891,42957],[42960,42961],[42963,42963],[42965,42972],[42994,43009],[43011,43013],[43015,43018],[43020,43042],[43056,43061],[43072,43123],[43138,43187],[43216,43225],[43250,43255],[43259,43259],[43261,43262],[43264,43301],[43312,43334],[43360,43388],[43396,43442],[43471,43481],[43488,43492],[43494,43518],[43520,43560],[43584,43586],[43588,43595],[43600,43609],[43616,43638],[43642,43642],[43646,43695],[43697,43697],[43701,43702],[43705,43709],[43712,43712],[43714,43714],[43739,43741],[43744,43754],[43762,43764],[43777,43782],[43785,43790],[43793,43798],[43808,43814],[43816,43822],[43824,43866],[43868,43881],[43888,44002],[44016,44025],[44032,55203],[55216,55238],[55243,55291],[63744,64109],[64112,64217],[64256,64262],[64275,64279],[64285,64285],[64287,64296],[64298,64310],[64312,64316],[64318,64318],[64320,64321],[64323,64324],[64326,64433],[64467,64829],[64848,64911],[64914,64967],[65008,65019],[65136,65140],[65142,65276],[65296,65305],[65313,65338],[65345,65370],[65382,65470],[65474,65479],[65482,65487],[65490,65495],[65498,65500],[65536,65547],[65549,65574],[65576,65594],[65596,65597],[65599,65613],[65616,65629],[65664,65786],[65799,65843],[65856,65912],[65930,65931],[66176,66204],[66208,66256],[66273,66299],[66304,66339],[66349,66378],[66384,66421],[66432,66461],[66464,66499],[66504,66511],[66513,66517],[66560,66717],[66720,66729],[66736,66771],[66776,66811],[66816,66855],[66864,66915],[66928,66938],[66940,66954],[66956,66962],[66964,66965],[66967,66977],[66979,66993],[66995,67001],[67003,67004],[67008,67059],[67072,67382],[67392,67413],[67424,67431],[67456,67461],[67463,67504],[67506,67514],[67584,67589],[67592,67592],[67594,67637],[67639,67640],[67644,67644],[67647,67669],[67672,67702],[67705,67742],[67751,67759],[67808,67826],[67828,67829],[67835,67867],[67872,67897],[67968,68023],[68028,68047],[68050,68096],[68112,68115],[68117,68119],[68121,68149],[68160,68168],[68192,68222],[68224,68255],[68288,68295],[68297,68324],[68331,68335],[68352,68405],[68416,68437],[68440,68466],[68472,68497],[68521,68527],[68608,68680],[68736,68786],[68800,68850],[68858,68899],[68912,68921],[68928,68965],[68975,68997],[69216,69246],[69248,69289],[69296,69297],[69314,69316],[69376,69415],[69424,69445],[69457,69460],[69488,69505],[69552,69579],[69600,69622],[69635,69687],[69714,69743],[69745,69746],[69749,69749],[69763,69807],[69840,69864],[69872,69881],[69891,69926],[69942,69951],[69956,69956],[69959,69959],[69968,70002],[70006,70006],[70019,70066],[70081,70084],[70096,70106],[70108,70108],[70113,70132],[70144,70161],[70163,70187],[70207,70208],[70272,70278],[70280,70280],[70282,70285],[70287,70301],[70303,70312],[70320,70366],[70384,70393],[70405,70412],[70415,70416],[70419,70440],[70442,70448],[70450,70451],[70453,70457],[70461,70461],[70480,70480],[70493,70497],[70528,70537],[70539,70539],[70542,70542],[70544,70581],[70583,70583],[70609,70609],[70611,70611],[70656,70708],[70727,70730],[70736,70745],[70751,70753],[70784,70831],[70852,70853],[70855,70855],[70864,70873],[71040,71086],[71128,71131],[71168,71215],[71236,71236],[71248,71257],[71296,71338],[71352,71352],[71360,71369],[71376,71395],[71424,71450],[71472,71483],[71488,71494],[71680,71723],[71840,71922],[71935,71942],[71945,71945],[71948,71955],[71957,71958],[71960,71983],[71999,71999],[72001,72001],[72016,72025],[72096,72103],[72106,72144],[72161,72161],[72163,72163],[72192,72192],[72203,72242],[72250,72250],[72272,72272],[72284,72329],[72349,72349],[72368,72440],[72640,72672],[72688,72697],[72704,72712],[72714,72750],[72768,72768],[72784,72812],[72818,72847],[72960,72966],[72968,72969],[72971,73008],[73030,73030],[73040,73049],[73056,73061],[73063,73064],[73066,73097],[73112,73112],[73120,73129],[73440,73458],[73474,73474],[73476,73488],[73490,73523],[73552,73561],[73648,73648],[73664,73684],[73728,74649],[74752,74862],[74880,75075],[77712,77808],[77824,78895],[78913,78918],[78944,82938],[82944,83526],[90368,90397],[90416,90425],[92160,92728],[92736,92766],[92768,92777],[92784,92862],[92864,92873],[92880,92909],[92928,92975],[92992,92995],[93008,93017],[93019,93025],[93027,93047],[93053,93071],[93504,93548],[93552,93561],[93760,93846],[93952,94026],[94032,94032],[94099,94111],[94176,94177],[94179,94179],[94208,100343],[100352,101589],[101631,101640],[110576,110579],[110581,110587],[110589,110590],[110592,110882],[110898,110898],[110928,110930],[110933,110933],[110948,110951],[110960,111355],[113664,113770],[113776,113788],[113792,113800],[113808,113817],[118000,118009],[119488,119507],[119520,119539],[119648,119672],[119808,119892],[119894,119964],[119966,119967],[119970,119970],[119973,119974],[119977,119980],[119982,119993],[119995,119995],[119997,120003],[120005,120069],[120071,120074],[120077,120084],[120086,120092],[120094,120121],[120123,120126],[120128,120132],[120134,120134],[120138,120144],[120146,120485],[120488,120512],[120514,120538],[120540,120570],[120572,120596],[120598,120628],[120630,120654],[120656,120686],[120688,120712],[120714,120744],[120746,120770],[120772,120779],[120782,120831],[122624,122654],[122661,122666],[122928,122989],[123136,123180],[123191,123197],[123200,123209],[123214,123214],[123536,123565],[123584,123627],[123632,123641],[124112,124139],[124144,124153],[124368,124397],[124400,124410],[124896,124902],[124904,124907],[124909,124910],[124912,124926],[124928,125124],[125127,125135],[125184,125251],[125259,125259],[125264,125273],[126065,126123],[126125,126127],[126129,126132],[126209,126253],[126255,126269],[126464,126467],[126469,126495],[126497,126498],[126500,126500],[126503,126503],[126505,126514],[126516,126519],[126521,126521],[126523,126523],[126530,126530],[126535,126535],[126537,126537],[126539,126539],[126541,126543],[126545,126546],[126548,126548],[126551,126551],[126553,126553],[126555,126555],[126557,126557],[126559,126559],[126561,126562],[126564,126564],[126567,126570],[126572,126578],[126580,126583],[126585,126588],[126590,126590],[126592,126601],[126603,126619],[126625,126627],[126629,126633],[126635,126651],[127232,127244],[130032,130041],[131072,173791],[173824,177977],[177984,178205],[178208,183969],[183984,191456],[191472,192093],[194560,195101],[196608,201546],[201552,205743]],"defaultIgnorable":[[173,173],[847,847],[1564,1564],[4447,4448],[6068,6069],[6155,6159],[8203,8207],[8234,8238],[8288,8303],[12644,12644],[65024,65039],[65279,65279],[65440,65440],[65520,65528],[113824,113827],[119155,119162],[917504,921599]]} diff --git a/contracts/cultnet/interop/write-receipt-vectors.json b/contracts/cultnet/interop/write-receipt-vectors.json index c7f47738..f99265ed 100644 --- a/contracts/cultnet/interop/write-receipt-vectors.json +++ b/contracts/cultnet/interop/write-receipt-vectors.json @@ -150,7 +150,7 @@ ] }, "admittedMessageId": { - "semantics": "Decoder-side rule for a `cultnet.write_admitted.v0` frame's `messageId` (Heimdall's ruling, write-receipt.md). Valid only if it contains at least one Unicode letter (\\p{L}) or number (\\p{N}) AND no Default_Ignorable_Code_Point (DerivedCoreProperties). The compare is RAW: the id is echoed byte for byte, so a decoder neither trims nor normalizes. Each case's id is given as `codePoints` (hex, in order), repeated `repeat` times (default 1), so no lone surrogate appears in this file. A runner builds the frame {schemaVersion write_admitted.v0, messageId, operation put, schemaId note.v0, recordKey note:1} with its own encoder and decodes it with the write_admitted decoder; `refuse` means the decoder refuses the frame the way it refuses an empty or ill-typed messageId, `accept` means it decodes and the id comes back identical. A case with `wireHex` carries the whole frame in MessagePack hex, for an id a language string cannot hold: the lone surrogate U+D800 is not valid UTF-8, so the vector's messageId is a MessagePack str whose bytes are ED A0 80, and Rust, C#, Kotlin and Python build the frame from those bytes and must refuse it. Letters, numbers and Default_Ignorable are those of Unicode 16.0 in every runtime, from embedded tables (no platform category lookup), so a runtime's own Unicode version cannot change a result. Scope: write_admitted only; error.v1 is untouched.", + "semantics": "Decoder-side rule for a `cultnet.write_admitted.v0` frame's `messageId` (Heimdall's ruling, write-receipt.md). Valid only if it contains at least one Unicode letter (\\p{L}) or number (\\p{N}) AND no Default_Ignorable_Code_Point (DerivedCoreProperties). The compare is RAW: the id is echoed byte for byte, so a decoder neither trims nor normalizes. Each case's id is given as `codePoints` (hex, in order), repeated `repeat` times (default 1), so no lone surrogate appears in this file. Every messageId key of a frame must be valid. A runner builds the frame {schemaVersion write_admitted.v0, messageId, operation put, schemaId note.v0, recordKey note:1} with its own encoder and decodes it with the write_admitted decoder; `refuse` means the decoder refuses the frame the way it refuses an empty or ill-typed messageId, `accept` means it decodes and the id comes back identical. A case with `wireHex` carries the whole frame in MessagePack hex, for an id a language string or this file cannot hold (a lone surrogate is not valid UTF-8; the bytes are ED A0 80) or a frame that repeats a key. The rule is on the decoded string, which must be well-formed Unicode: a lone surrogate refuses. A runtime whose MessagePack reader rejects invalid UTF-8 refuses earlier; byte-level strictness is a property of the reader and is not required of a decoder. Scope: write_admitted only; error.v1 is untouched.", "cases": [ { "name": "refuse_braille_blank_2800", @@ -222,7 +222,7 @@ "D800" ], "expect": "refuse", - "why": "not a Unicode scalar value; on the wire it is the invalid UTF-8 bytes ED A0 80 in a str, so the decoder must refuse the frame. Rust, C#, Kotlin and Python build the frame from wireHex; a runtime whose strings can hold a lone surrogate (TypeScript) may also build it from the code point", + "why": "a lone surrogate is not a Unicode scalar value and has no letter or number; on the wire it is the invalid UTF-8 bytes ED A0 80 in a str. A reader that rejects invalid UTF-8 refuses the frame; one that substitutes or keeps the surrogate refuses the id", "wireHex": "85AD736368656D6156657273696F6EB963756C746E65742E77726974655F61646D69747465642E7630A96D6573736167654964A3EDA080A96F7065726174696F6EA3707574A8736368656D614964A76E6F74652E7630A97265636F72644B6579A66E6F74653A31" }, { @@ -408,6 +408,122 @@ "expect": "refuse", "why": "U+10D90 follows the Garay block and is unassigned in Unicode 16.0" }, + { + "name": "refuse_duplicate_message_id_second_is_default_ignorable", + "codePoints": ["0061"], + "expect": "refuse", + "why": "the frame carries messageId twice, \"a\" and then U+200B; every messageId key must be valid, and a reader that keeps the last one sees U+200B", + "wireHex": "86AD736368656D6156657273696F6EB963756C746E65742E77726974655F61646D69747465642E7630A96D6573736167654964A57075742D31A96F7065726174696F6EA3707574A8736368656D614964A76E6F74652E7630A97265636F72644B6579A66E6F74653A31A96D6573736167654964A3E2808B" + }, + { + "name": "refuse_letter_then_lone_surrogate_D800", + "codePoints": ["0061"], + "expect": "refuse", + "why": "\"a\" followed by a lone surrogate: the id as decoded is not well-formed Unicode. A reader that rejects invalid UTF-8 refuses the frame earlier; one that substitutes or keeps the surrogate must still refuse the id. Byte-level strictness is a property of the reader, not required of the decoder", + "wireHex": "85AD736368656D6156657273696F6EB963756C746E65742E77726974655F61646D69747465642E7630A96D6573736167654964A461EDA080A96F7065726174696F6EA3707574A8736368656D614964A76E6F74652E7630A97265636F72644B6579A66E6F74653A31" + }, + { + "name": "accept_astral_mathematical_bold_capital_a_1D400", + "codePoints": ["1D400"], + "expect": "accept", + "why": "an astral letter (Lu)" + }, + { + "name": "accept_astral_cjk_ext_b_20000", + "codePoints": ["20000"], + "expect": "accept", + "why": "an astral letter (Lo)" + }, + { + "name": "refuse_letter_then_default_ignorable_061C", + "codePoints": ["0061", "061C"], + "expect": "refuse", + "why": "U+061C is Default_Ignorable (an end of one of its ranges)" + }, + { + "name": "refuse_letter_then_default_ignorable_17B4", + "codePoints": ["0061", "17B4"], + "expect": "refuse", + "why": "U+17B4 is Default_Ignorable (an end of one of its ranges)" + }, + { + "name": "refuse_letter_then_default_ignorable_17B5", + "codePoints": ["0061", "17B5"], + "expect": "refuse", + "why": "U+17B5 is Default_Ignorable (an end of one of its ranges)" + }, + { + "name": "refuse_letter_then_default_ignorable_202A", + "codePoints": ["0061", "202A"], + "expect": "refuse", + "why": "U+202A is Default_Ignorable (an end of one of its ranges)" + }, + { + "name": "refuse_letter_then_default_ignorable_202E", + "codePoints": ["0061", "202E"], + "expect": "refuse", + "why": "U+202E is Default_Ignorable (an end of one of its ranges)" + }, + { + "name": "refuse_letter_then_default_ignorable_FE00", + "codePoints": ["0061", "FE00"], + "expect": "refuse", + "why": "U+FE00 is Default_Ignorable (an end of one of its ranges)" + }, + { + "name": "refuse_letter_then_default_ignorable_FE0F", + "codePoints": ["0061", "FE0F"], + "expect": "refuse", + "why": "U+FE0F is Default_Ignorable (an end of one of its ranges)" + }, + { + "name": "refuse_letter_then_default_ignorable_FFF0", + "codePoints": ["0061", "FFF0"], + "expect": "refuse", + "why": "U+FFF0 is Default_Ignorable (an end of one of its ranges)" + }, + { + "name": "refuse_letter_then_default_ignorable_FFF8", + "codePoints": ["0061", "FFF8"], + "expect": "refuse", + "why": "U+FFF8 is Default_Ignorable (an end of one of its ranges)" + }, + { + "name": "refuse_letter_then_default_ignorable_1BCA0", + "codePoints": ["0061", "1BCA0"], + "expect": "refuse", + "why": "U+1BCA0 is Default_Ignorable (an end of one of its ranges)" + }, + { + "name": "refuse_letter_then_default_ignorable_1BCA3", + "codePoints": ["0061", "1BCA3"], + "expect": "refuse", + "why": "U+1BCA3 is Default_Ignorable (an end of one of its ranges)" + }, + { + "name": "refuse_letter_then_default_ignorable_1D173", + "codePoints": ["0061", "1D173"], + "expect": "refuse", + "why": "U+1D173 is Default_Ignorable (an end of one of its ranges)" + }, + { + "name": "refuse_letter_then_default_ignorable_1D17A", + "codePoints": ["0061", "1D17A"], + "expect": "refuse", + "why": "U+1D17A is Default_Ignorable (an end of one of its ranges)" + }, + { + "name": "refuse_letter_then_default_ignorable_E0000", + "codePoints": ["0061", "E0000"], + "expect": "refuse", + "why": "U+E0000 is Default_Ignorable (an end of one of its ranges)" + }, + { + "name": "refuse_letter_then_default_ignorable_E0FFF", + "codePoints": ["0061", "E0FFF"], + "expect": "refuse", + "why": "U+E0FFF is Default_Ignorable (an end of one of its ranges)" + }, { "name": "accept_abc", "codePoints": [ @@ -1524,7 +1640,7 @@ "schemaInvalid": 7, "contentHashCases": 4, "scenarioCases": 25, - "admittedMessageIdCases": 40 + "admittedMessageIdCases": 59 }, "contentHash": { "definition": "sha256(msgpack([operation, schemaId, recordKey, payload])), payload a msgpack bin for a put and nil for a delete; hex lowercase", diff --git a/packages/cultmesh-kotlin/src/main/kotlin/org/gamecult/cultmesh/WriteReceiptVectors.kt b/packages/cultmesh-kotlin/src/main/kotlin/org/gamecult/cultmesh/WriteReceiptVectors.kt index 8955c52d..35e0136f 100644 --- a/packages/cultmesh-kotlin/src/main/kotlin/org/gamecult/cultmesh/WriteReceiptVectors.kt +++ b/packages/cultmesh-kotlin/src/main/kotlin/org/gamecult/cultmesh/WriteReceiptVectors.kt @@ -68,6 +68,38 @@ fun replayWriteReceiptVectors(path: String): Boolean { } } + // The generated ranges file is the independent copy of the embedded tables: first, last and the points either side of every + // range, so deleting or narrowing any range fails here. + check("unicodeTableRanges") { + @Suppress("UNCHECKED_CAST") + val ranges = JsonReader(File(File(path).parentFile, "write-admitted-message-id-ranges.json").readText(Charsets.UTF_8)).readDocument() as Map + fun pairs(key: String) = list(ranges[key]).map { list(it).map { n -> (n as Long).toInt() } } + val letters = pairs("letterOrNumber") + val ignorable = pairs("defaultIgnorable") + val problems = mutableListOf() + if (letters.size <= 700 || ignorable.size != 17) problems += "ranges file holds ${letters.size} and ${ignorable.size} ranges" + fun accepted(id: String): Boolean = try { + decodeReceiptFrame(parseCultNetMessage(MessagePackWriter().value(linkedMapOf( + "schemaVersion" to WRITE_ADMITTED_VERSION, "messageId" to id, "operation" to "put", "schemaId" to "note.v0", "recordKey" to "note:1", + )).toByteArray())) + true + } catch (_: IOException) { false } + for ((low, high) in letters) for ((point, want) in listOf(low to true, high to true, low - 1 to false, high + 1 to false)) { + if (accepted(String(Character.toChars(point))) != want) problems += "U+${point.toString(16)} alone should be ${if (want) "accepted" else "refused"}" + } + for ((low, high) in ignorable) for ((point, want) in listOf(low to false, high to false, low - 1 to true, high + 1 to true)) { + if (accepted("a" + String(Character.toChars(point))) != want) problems += "a + U+${point.toString(16)} should be ${if (want) "accepted" else "refused"}" + } + problems + } + + // A document payload string with a lone surrogate (ED A0 80) is not a write_admitted id: the shared MessagePack reader keeps + // reading it. Strictness is scoped to the write_admitted frame, never to the reader. + check("documentPayloadStringWithLoneSurrogateStillDecodes") { + val text = MessagePackReader(unhex("A3EDA080")).readString() + if (text.isNotEmpty()) emptyList() else listOf("decoded to an empty string") + } + for (case in list(vectors["schemaInvalid"])) { val c = obj(case) check("schemaInvalid/${c["name"]}") { diff --git a/packages/cultnet-py/tests/test_write_receipt.py b/packages/cultnet-py/tests/test_write_receipt.py index 0c42a6af..fce0d850 100644 --- a/packages/cultnet-py/tests/test_write_receipt.py +++ b/packages/cultnet-py/tests/test_write_receipt.py @@ -94,6 +94,32 @@ class WriteReceiptVectorTests(unittest.TestCase): failures.append(f"{case['name']}: expected {case['expect']}") self.assertEqual(failures, []) + def test_every_range_of_the_unicode_tables_is_pinned_at_both_ends(self) -> None: + # write-admitted-message-id-ranges.json is the generated, independent copy: for each letter-or-number range its first and + # last point are accepted and the points either side refused; for each Default_Ignorable range "a" beside its first and + # last point is refused and beside the points either side accepted. Deleting or narrowing any embedded range fails here. + ranges = json.loads((_INTEROP / "write-admitted-message-id-ranges.json").read_text(encoding="utf-8")) + self.assertGreater(len(ranges["letterOrNumber"]), 700) + self.assertEqual(len(ranges["defaultIgnorable"]), 17) + + def accepted(text: str) -> bool: + try: + parse_message(_pack(_admitted(messageId=text))) + return True + except ValueError: + return False + + failures = [] + for low, high in ranges["letterOrNumber"]: + for point, want in ((low, True), (high, True), (low - 1, False), (high + 1, False)): + if accepted(chr(point)) != want: + failures.append(f"U+{point:04X} alone should be {'accepted' if want else 'refused'}") + for low, high in ranges["defaultIgnorable"]: + for point, want in ((low, False), (high, False), (low - 1, True), (high + 1, True)): + if accepted("a" + chr(point)) != want: + failures.append(f"a + U+{point:04X} should be {'accepted' if want else 'refused'}") + self.assertEqual(failures, []) + def test_content_hash_matches_every_vector(self) -> None: for case in _VECTORS["contentHash"]["cases"]: payload = None if case["payloadHex"] is None else bytes.fromhex(case["payloadHex"]) diff --git a/packages/cultnet-rs/tests/write_receipt.rs b/packages/cultnet-rs/tests/write_receipt.rs index 8dccf996..10d7dcb8 100644 --- a/packages/cultnet-rs/tests/write_receipt.rs +++ b/packages/cultnet-rs/tests/write_receipt.rs @@ -201,3 +201,40 @@ fn write_admitted_message_id_vectors_are_refused_or_accepted_raw() { } assert!(failures.is_empty(), "{} failing:\n{}", failures.len(), failures.join("\n")); } + +/// The generated ranges file is the independent copy of the embedded tables: first, last and the points either side of every range. +#[test] +fn every_range_of_the_unicode_tables_is_pinned_at_both_ends() { + let path = concat!(env!("CARGO_MANIFEST_DIR"), "/../../contracts/cultnet/interop/write-admitted-message-id-ranges.json"); + let ranges: serde_json::Value = serde_json::from_str(&std::fs::read_to_string(path).unwrap()).unwrap(); + let letters = ranges["letterOrNumber"].as_array().unwrap(); + let ignorable = ranges["defaultIgnorable"].as_array().unwrap(); + assert!(letters.len() > 700); + assert_eq!(ignorable.len(), 17); + let template = unhex("85AD736368656D6156657273696F6EB963756C746E65742E77726974655F61646D69747465642E7630A96D6573736167654964A57075742D31A96F7065726174696F6EA3707574A8736368656D614964A76E6F74652E7630A97265636F72644B6579A66E6F74653A31"); + let old_id_at = template.windows(5).position(|w| w == b"put-1").unwrap() - 1; + let accepted = |id: &str| { + let header = vec![0xA0 | id.len() as u8]; + let frame = [&template[..old_id_at], &header[..], id.as_bytes(), &template[old_id_at + 6..]].concat(); + decode_cultnet_message_from_slice(&frame, CultNetWireContract::CultNetSchemaV0).is_ok() + }; + let point = |value: i64| char::from_u32(value as u32).unwrap().to_string(); + let mut failures = Vec::new(); + for range in letters { + let (low, high) = (range[0].as_i64().unwrap(), range[1].as_i64().unwrap()); + for (at, want) in [(low, true), (high, true), (low - 1, false), (high + 1, false)] { + if accepted(&point(at)) != want { + failures.push(format!("U+{at:X} alone should be {}", if want { "accepted" } else { "refused" })); + } + } + } + for range in ignorable { + let (low, high) = (range[0].as_i64().unwrap(), range[1].as_i64().unwrap()); + for (at, want) in [(low, false), (high, false), (low - 1, true), (high + 1, true)] { + if accepted(&format!("a{}", point(at))) != want { + failures.push(format!("a + U+{at:X} should be {}", if want { "accepted" } else { "refused" })); + } + } + } + assert!(failures.is_empty(), "{} failing:\n{}", failures.len(), failures.join("\n")); +} diff --git a/packages/cultnet-ts/test/write-receipt.test.ts b/packages/cultnet-ts/test/write-receipt.test.ts index c447367a..c654de1a 100644 --- a/packages/cultnet-ts/test/write-receipt.test.ts +++ b/packages/cultnet-ts/test/write-receipt.test.ts @@ -303,3 +303,23 @@ test("write_admitted messageId: every admittedMessageId vector is refused or acc } assert.deepEqual(failures, []); }); + +// The generated ranges file is the independent copy of the embedded tables: first, last and the points either side of every range. +test("write_admitted messageId: every range of the Unicode 16.0 tables is pinned at both ends", () => { + const ranges = JSON.parse(readFileSync(join(__dirname, "..", "..", "..", "..", "contracts", "cultnet", "interop", "write-admitted-message-id-ranges.json"), "utf8")); + assert.ok(ranges.letterOrNumber.length > 700); + assert.equal(ranges.defaultIgnorable.length, 17); + const accepted = (id: string) => { try { parseCultNetMessage(admitted(id)); return true; } catch { return false; } }; + const failures: string[] = []; + for (const [low, high] of ranges.letterOrNumber as [number, number][]) { + for (const [point, want] of [[low, true], [high, true], [low - 1, false], [high + 1, false]] as [number, boolean][]) { + if (accepted(String.fromCodePoint(point)) !== want) failures.push(`U+${point.toString(16)} alone should be ${want ? "accepted" : "refused"}`); + } + } + for (const [low, high] of ranges.defaultIgnorable as [number, number][]) { + for (const [point, want] of [[low, false], [high, false], [low - 1, true], [high + 1, true]] as [number, boolean][]) { + if (accepted("a" + String.fromCodePoint(point)) !== want) failures.push(`a + U+${point.toString(16)} should be ${want ? "accepted" : "refused"}`); + } + } + assert.deepEqual(failures, []); +}); diff --git a/scripts/gen-unicode-messageid-tables.py b/scripts/gen-unicode-messageid-tables.py index 96d1a205..4c119930 100644 --- a/scripts/gen-unicode-messageid-tables.py +++ b/scripts/gen-unicode-messageid-tables.py @@ -13,6 +13,7 @@ https://www.unicode.org/Public/16.0.0/ucd/DerivedCoreProperties.txt. With --chec any generated file differs from what these inputs produce (a stale or hand-edited table). Standard library only. """ import hashlib +import json import re import sys from pathlib import Path @@ -28,6 +29,9 @@ TARGETS = { "cs": ROOT / "src/GameCult.Networking/CultNetUnicodeMessageIdTables.cs", "rs": ROOT / "packages/cultnet-rs/src/unicode_messageid_tables.rs", } +# The independent copy the runtimes' tests probe (first, last, and the code point either side of every range), so that deleting +# or narrowing any range of any embedded table fails a test. +RANGES_JSON = ROOT / "contracts/cultnet/interop/write-admitted-message-id-ranges.json" def parse(path, wanted): @@ -95,8 +99,12 @@ def main(): digest = hashlib.sha256(b"".join(Path(p).read_bytes() for p in (gc, dcp))).hexdigest()[:16] header = f"generated, Unicode {VERSION}, by scripts/gen-unicode-messageid-tables.py (inputs sha256 {digest}); do not edit" stale = [] - for target, path in TARGETS.items(): - text = emit(target, letters, ignorable, header) + texts = {path: emit(target, letters, ignorable, header) for target, path in TARGETS.items()} + texts[RANGES_JSON] = json.dumps( + {"generated": header, "unicodeVersion": VERSION, "letterOrNumber": [list(r) for r in letters], "defaultIgnorable": [list(r) for r in ignorable]}, + separators=(",", ":"), + ) + "\n" + for path, text in texts.items(): if check: if not path.exists() or path.read_text(encoding="utf-8") != text: stale.append(str(path.relative_to(ROOT))) @@ -104,7 +112,7 @@ def main(): path.write_text(text, encoding="utf-8") if stale: sys.exit("stale or hand-edited: " + ", ".join(stale)) - print(f"{'checked' if check else 'wrote'} {len(TARGETS)} tables: {len(letters)} letter-or-number ranges, {len(ignorable)} default-ignorable ranges") + print(f"{'checked' if check else 'wrote'} {len(texts)} files: {len(letters)} letter-or-number ranges, {len(ignorable)} default-ignorable ranges") if __name__ == "__main__": diff --git a/tests/GameCult.Networking.Tests/CultNetWriteAdmittedMessageIdTests.cs b/tests/GameCult.Networking.Tests/CultNetWriteAdmittedMessageIdTests.cs index f5cac528..a4f1475c 100644 --- a/tests/GameCult.Networking.Tests/CultNetWriteAdmittedMessageIdTests.cs +++ b/tests/GameCult.Networking.Tests/CultNetWriteAdmittedMessageIdTests.cs @@ -54,5 +54,38 @@ namespace GameCult.Networking.Tests Assert.That(decoded!.MessageId, Is.EqualTo(id)); } } + + // The generated ranges file is the independent copy of the embedded tables: first, last and the points either side of every range. + [Test] + public void EveryRangeOfTheUnicodeTablesIsPinnedAtBothEnds() + { + var directory = new DirectoryInfo(TestContext.CurrentContext.TestDirectory); + while (directory != null && !File.Exists(Path.Combine(directory.FullName, "CultLib.sln"))) + directory = directory.Parent; + var ranges = JsonDocument.Parse(File.ReadAllText(Path.Combine(directory!.FullName, "contracts", "cultnet", "interop", "write-admitted-message-id-ranges.json"))).RootElement; + var letters = ranges.GetProperty("letterOrNumber").EnumerateArray().Select(r => (r[0].GetInt32(), r[1].GetInt32())).ToArray(); + var ignorable = ranges.GetProperty("defaultIgnorable").EnumerateArray().Select(r => (r[0].GetInt32(), r[1].GetInt32())).ToArray(); + Assert.That(letters.Length, Is.GreaterThan(700)); + Assert.That(ignorable.Length, Is.EqualTo(17)); + static bool Accepted(string id) + { + try + { + CultNetSchemaMessageSerialization.Deserialize(CultNetSchemaMessageSerialization.Serialize( + new CultNetWriteAdmittedMessage { MessageId = id, Operation = "put", SchemaId = "note.v0", RecordKey = "note:1" })); + return true; + } + catch (Exception) { return false; } + } + + var failures = new List(); + foreach (var (low, high) in letters) + foreach (var (point, want) in new[] { (low, true), (high, true), (low - 1, false), (high + 1, false) }) + if (Accepted(char.ConvertFromUtf32(point)) != want) failures.Add($"U+{point:X} alone should be {(want ? "accepted" : "refused")}"); + foreach (var (low, high) in ignorable) + foreach (var (point, want) in new[] { (low, false), (high, false), (low - 1, true), (high + 1, true) }) + if (Accepted("a" + char.ConvertFromUtf32(point)) != want) failures.Add($"a + U+{point:X} should be {(want ? "accepted" : "refused")}"); + Assert.That(failures, Is.Empty); + } } } From 2f372eb2f1658696f7115474bd4d7777246009fc Mon Sep 17 00:00:00 2001 From: soul Date: Mon, 9 Nov 2026 15:11:53 +0000 Subject: [PATCH 32/76] GameCult.Networking: check every messageId key of a write_admitted frame, not only the first Co-Authored-By: Claude Sonnet 5.5 --- src/GameCult.Networking/CultNetSchemaMessageSerialization.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/GameCult.Networking/CultNetSchemaMessageSerialization.cs b/src/GameCult.Networking/CultNetSchemaMessageSerialization.cs index 3069384a..cb33b707 100644 --- a/src/GameCult.Networking/CultNetSchemaMessageSerialization.cs +++ b/src/GameCult.Networking/CultNetSchemaMessageSerialization.cs @@ -111,7 +111,7 @@ namespace GameCult.Networking { private static readonly System.Text.UTF8Encoding StrictUtf8 = new(false, true); - /// Throws unless the frame's top-level messageId is a valid write_admitted id; other keys are left to the serializer. + /// Throws unless every top-level messageId key of the frame is a valid write_admitted id; other keys are left to the serializer. public static void Require(byte[] payload) { var reader = new MessagePackReader(payload); @@ -125,7 +125,7 @@ namespace GameCult.Networking try { id = StrictUtf8.GetString(reader.ReadStringSequence()!.Value.ToArray()); } catch (System.Text.DecoderFallbackException) { throw new MessagePackSerializationException("cultnet.write_admitted.v0 messageId is not valid UTF-8."); } if (!IsValid(id)) throw new MessagePackSerializationException("cultnet.write_admitted.v0 messageId must contain a letter or number and no Default_Ignorable code point."); - return; + continue; // a repeated messageId key is checked too: the serializer keeps the last, so every one must be valid } if (key == null) reader.Skip(); From 99273f468c5441ba67fa527c5834e3c2264ff7aa Mon Sep 17 00:00:00 2001 From: soul Date: Mon, 9 Nov 2026 15:12:07 +0000 Subject: [PATCH 33/76] cultnet-ts: refuse a write_admitted messageId that is not well-formed Unicode (lone surrogate) Co-Authored-By: Claude Sonnet 5.5 --- packages/cultnet-ts/src/contracts.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/cultnet-ts/src/contracts.ts b/packages/cultnet-ts/src/contracts.ts index 09d5d823..8dd37ced 100644 --- a/packages/cultnet-ts/src/contracts.ts +++ b/packages/cultnet-ts/src/contracts.ts @@ -1478,7 +1478,8 @@ function isValidAdmittedMessageId(value: unknown): boolean { let hasLetterOrNumber = false; for (const ch of value) { const point = ch.codePointAt(0) as number; - if (inRanges(DEFAULT_IGNORABLE, point)) { + // The decoded string must be well-formed Unicode: @msgpack/msgpack turns invalid UTF-8 (ED A0 80) into a lone surrogate. + if ((point >= 0xd800 && point <= 0xdfff) || inRanges(DEFAULT_IGNORABLE, point)) { return false; } hasLetterOrNumber = hasLetterOrNumber || inRanges(LETTER_OR_NUMBER, point); From 6c130581a1193a810584a18b279d6f1fdc51c3f7 Mon Sep 17 00:00:00 2001 From: soul Date: Mon, 9 Nov 2026 15:13:01 +0000 Subject: [PATCH 34/76] cultmesh-kotlin: UTF-8 strictness scoped to the write_admitted frame, not the shared MessagePack reader Co-Authored-By: Claude Sonnet 5.5 --- .../src/main/kotlin/org/gamecult/cultmesh/CultMesh.kt | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/packages/cultmesh-kotlin/src/main/kotlin/org/gamecult/cultmesh/CultMesh.kt b/packages/cultmesh-kotlin/src/main/kotlin/org/gamecult/cultmesh/CultMesh.kt index 2337448d..72c1d41d 100644 --- a/packages/cultmesh-kotlin/src/main/kotlin/org/gamecult/cultmesh/CultMesh.kt +++ b/packages/cultmesh-kotlin/src/main/kotlin/org/gamecult/cultmesh/CultMesh.kt @@ -2365,6 +2365,9 @@ fun parseCultNetMessage(payload: ByteArray): CultNetMessage { if (schemaVersion !is String || schemaVersion.isBlank()) { throw IOException("CultNet schema-v0 messages must declare schemaVersion") } + // A write_admitted id is echoed byte for byte, so that frame alone is read again with invalid UTF-8 refused; document payload + // strings elsewhere keep decoding as before. + if (schemaVersion == WRITE_ADMITTED_VERSION) MessagePackReader(payload, strictUtf8 = true).readAny() val body = linkedMapOf() for ((key, value) in decoded) { if (key !is String) throw IOException("CultNet schema-v0 message keys must be strings") @@ -6152,7 +6155,8 @@ private fun requireSchemaResponse(message: CultNetMessage?, expectedSchemaVersio return response } -class MessagePackReader(payload: ByteArray) { +/** [strictUtf8] makes a str whose bytes are not UTF-8 throw instead of becoming U+FFFD; only the write_admitted frame asks for it. */ +class MessagePackReader(payload: ByteArray, private val strictUtf8: Boolean = false) { private val input = DataInputStream(ByteArrayInputStream(payload)) private var pushed = -1 @@ -6307,9 +6311,10 @@ class MessagePackReader(payload: ByteArray) { code == 0xdb -> input.readInt() else -> throw IOException("expected string") } - // Strict: bytes that are not UTF-8 (a lone surrogate's ED A0 80) refuse the frame instead of becoming U+FFFD. + val bytes = readPayload(length) + if (!strictUtf8) return String(bytes, StandardCharsets.UTF_8) return StandardCharsets.UTF_8.newDecoder().onMalformedInput(CodingErrorAction.REPORT).onUnmappableCharacter(CodingErrorAction.REPORT) - .decode(ByteBuffer.wrap(readPayload(length))).toString() + .decode(ByteBuffer.wrap(bytes)).toString() } private fun readBinaryAfterCode(code: Int): ByteArray { From a97861945c914bb2d08cc95174aa65d2366f196c Mon Sep 17 00:00:00 2001 From: soul Date: Mon, 9 Nov 2026 15:13:09 +0000 Subject: [PATCH 35/76] write-receipt.md: the messageId rule is on the decoded string; byte-level UTF-8 strictness is the reader's, not required Co-Authored-By: Claude Sonnet 5.5 --- contracts/cultnet/interop/write-receipt.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/contracts/cultnet/interop/write-receipt.md b/contracts/cultnet/interop/write-receipt.md index 10f42a6a..4f6cb7d1 100644 --- a/contracts/cultnet/interop/write-receipt.md +++ b/contracts/cultnet/interop/write-receipt.md @@ -12,7 +12,7 @@ A put or delete today has two answers on the wire: nothing (admitted, or the ser **`cultnet.write_admitted.v0`**: `{ schemaVersion, messageId, operation: "put"|"delete", schemaId, recordKey }`, every key written, none nil. Sent once a put or delete is admitted. The id echoes the write's `messageId` byte for byte; `operation`, `schemaId`, `recordKey` echo the write. -**A `write_admitted` `messageId` is valid only if it contains at least one Unicode letter (`\p{L}`) or number (`\p{N}`) and no Default_Ignorable_Code_Point (DerivedCoreProperties).** The compare is raw: the id is echoed byte for byte, so a decoder never trims or normalizes it (`" a "` is valid; U+2800 is not, and neither is `a` followed by U+200B). Unlike the Required fields rule, this is not a White_Space test. The letters, numbers and Default_Ignorable code points are those of **Unicode 16.0** in every runtime, taken from tables embedded in each runtime and generated by `scripts/gen-unicode-messageid-tables.py` from `DerivedGeneralCategory.txt` (Lu Ll Lt Lm Lo Nd Nl No) and `DerivedCoreProperties.txt`; no runtime asks its platform's Unicode data, so a runtime's own Unicode version cannot change a result. Bumping the version is one coordinated change: regenerate the five tables and update the vectors that pin the version in the same commit. A decoder refuses an invalid id the way it refuses an empty or ill-typed one; the rule is for `write_admitted` only and `error.v1` is unchanged. +**A `write_admitted` `messageId` is valid only if it contains at least one Unicode letter (`\p{L}`) or number (`\p{N}`) and no Default_Ignorable_Code_Point (DerivedCoreProperties).** The compare is raw: the id is echoed byte for byte, so a decoder never trims or normalizes it (`" a "` is valid; U+2800 is not, and neither is `a` followed by U+200B). Unlike the Required fields rule, this is not a White_Space test. The letters, numbers and Default_Ignorable code points are those of **Unicode 16.0** in every runtime, taken from tables embedded in each runtime and generated by `scripts/gen-unicode-messageid-tables.py` from `DerivedGeneralCategory.txt` (Lu Ll Lt Lm Lo Nd Nl No) and `DerivedCoreProperties.txt`; no runtime asks its platform's Unicode data, so a runtime's own Unicode version cannot change a result. Bumping the version is one coordinated change: regenerate the five tables and update the vectors that pin the version in the same commit. The rule is on the decoded string, which must be well-formed Unicode: a lone surrogate refuses. A runtime whose MessagePack reader rejects invalid UTF-8 refuses the frame earlier; byte-level strictness is a property of the reader and is not required of a decoder. Every `messageId` key of a frame must be valid. A decoder refuses an invalid id the way it refuses an empty or ill-typed one; the rule is for `write_admitted` only and `error.v1` is unchanged. **`cultnet.error.v1`**: `cultnet.error.v0` plus a required `messageId` (string or nil). The code enum is v0's plus `limit_reached` (see Remembered-write limits); `cultnet.error.v1.schema.json` is the authority for the closed set. Sent in place of `error.v0` to a receipt-capable connection. `messageId` is the id of the request the error answers; nil only when the frame was unreadable or carried no id. @@ -67,7 +67,7 @@ Those clients complete *successfully* when the window ends with no refusal. Unde ## Vectors -`write-receipt-vectors.json`: `constants` (the named windows and retention), `contentHash` (worked dedupe hashes), `dedupe` (server replay cases for rule 5), `wire` (MessagePack hex plus the decoded message for each new frame; the bytes are spec-authored with msgpack-python and every runtime's first conformance PR re-captures them with its own encoder and must match), `schemaInvalid` (frames that must be refused by a validator, each with why), and `blankFields` (server input cases for the Required fields rule: which document fields make a put unreadable, which are accepted), `admittedMessageId` (the `write_admitted` id rule above: code-point lists, each marked refuse or accept; the lone surrogate U+D800 is carried as a whole frame in `wireHex`, because it is not valid UTF-8 and only some runtimes' strings can hold it), `scenarios` (event replays with the outcome each write must settle as; the clock is abstract; each case names its window constant). A conformant client passes every scenario; a conformant server's answers for the same inputs satisfy server rules 1 to 7. +`write-receipt-vectors.json`: `constants` (the named windows and retention), `contentHash` (worked dedupe hashes), `dedupe` (server replay cases for rule 5), `wire` (MessagePack hex plus the decoded message for each new frame; the bytes are spec-authored with msgpack-python and every runtime's first conformance PR re-captures them with its own encoder and must match), `schemaInvalid` (frames that must be refused by a validator, each with why), and `blankFields` (server input cases for the Required fields rule: which document fields make a put unreadable, which are accepted), `admittedMessageId` (the `write_admitted` id rule above: code-point lists, each marked refuse or accept; a lone surrogate, and a frame that repeats the key, are carried as whole frames in `wireHex`, because a lone surrogate is not valid UTF-8 and only some runtimes' strings can hold it; `write-admitted-message-id-ranges.json` lists every range of the embedded tables, and each runtime probes the first and last point of each range and the points either side), `scenarios` (event replays with the outcome each write must settle as; the clock is abstract; each case names its window constant). A conformant client passes every scenario; a conformant server's answers for the same inputs satisfy server rules 1 to 7. ## Conformance status From 5679417a826ef496ce30c4f58d74017ab79a34d1 Mon Sep 17 00:00:00 2001 From: soul Date: Mon, 9 Nov 2026 15:26:27 +0000 Subject: [PATCH 36/76] CI: back to ubuntu-latest, install PowerShell where a Linux job needs pwsh The forge has two labels: docker (node 22, py 3.11) and ubuntu-latest (act-latest, node 24, py 3.12); neither carries pwsh. Windows legs stay workflow_dispatch only. Co-Authored-By: Claude Sonnet 5.5 --- .github/workflows/cultmesh-portability.yml | 21 +++++++++++++++++++-- .github/workflows/publish-packages.yml | 4 ++-- .github/workflows/quic-native.yml | 8 ++++---- 3 files changed, 25 insertions(+), 8 deletions(-) diff --git a/.github/workflows/cultmesh-portability.yml b/.github/workflows/cultmesh-portability.yml index b5787fd4..b93aeeb0 100644 --- a/.github/workflows/cultmesh-portability.yml +++ b/.github/workflows/cultmesh-portability.yml @@ -16,8 +16,8 @@ jobs: strategy: fail-fast: false matrix: - # The forge runner (label `docker`) is Linux-only and no Windows runner exists: the Windows leg runs on workflow_dispatch only (GitHub or a Windows runner), the docker leg gates push and PR. Windows push/PR triggers return in the PR that registers a Windows runner. - os: ${{ fromJSON(github.event_name == 'workflow_dispatch' && '["windows-latest", "docker"]' || '["docker"]') }} + # The forge runner (label `ubuntu-latest`) is Linux-only and no Windows runner exists: the Windows leg runs on workflow_dispatch only (GitHub or a Windows runner), the ubuntu-latest leg gates push and PR. Windows push/PR triggers return in the PR that registers a Windows runner. + os: ${{ fromJSON(github.event_name == 'workflow_dispatch' && '["windows-latest", "ubuntu-latest"]' || '["ubuntu-latest"]') }} steps: - name: Check out CultLib @@ -45,6 +45,23 @@ jobs: - name: Install Python CultLib packages run: python -m pip install -e packages/cultcache-py -e packages/cultnet-py -e packages/cultmesh-py + # The forge runner images carry no pwsh; GitHub's Windows and hosted images do. + - name: Install PowerShell + if: runner.os == 'Linux' + shell: bash + run: | + set -euo pipefail + if command -v pwsh >/dev/null; then pwsh -Version; exit 0; fi + SUDO=""; [ "$(id -u)" -eq 0 ] || SUDO="sudo" + PWSH_VERSION=7.4.6 + curl -fsSL "https://github.com/PowerShell/PowerShell/releases/download/v${PWSH_VERSION}/powershell-${PWSH_VERSION}-linux-x64.tar.gz" -o "${RUNNER_TEMP:-/tmp}/powershell.tar.gz" + $SUDO mkdir -p /opt/microsoft/powershell/7 + $SUDO tar -xzf "${RUNNER_TEMP:-/tmp}/powershell.tar.gz" -C /opt/microsoft/powershell/7 + $SUDO chmod +x /opt/microsoft/powershell/7/pwsh + $SUDO ln -sf /opt/microsoft/powershell/7/pwsh /usr/local/bin/pwsh + echo "DOTNET_SYSTEM_GLOBALIZATION_INVARIANT=1" >> "$GITHUB_ENV" + DOTNET_SYSTEM_GLOBALIZATION_INVARIANT=1 pwsh -Version + - name: Verify explicit reactive scheduling across runtimes shell: pwsh run: ./scripts/verify-reactive-document-scaling.ps1 diff --git a/.github/workflows/publish-packages.yml b/.github/workflows/publish-packages.yml index f4026e41..caa24336 100644 --- a/.github/workflows/publish-packages.yml +++ b/.github/workflows/publish-packages.yml @@ -50,7 +50,7 @@ jobs: cultcache-ts: name: "@gamecult/cultcache-ts to npm" if: startsWith(github.ref, 'refs/tags/cultcache-ts-v') || inputs.package == 'cultcache-ts' - runs-on: docker + runs-on: ubuntu-latest timeout-minutes: 20 steps: # fetch-depth: 0 so the semver check in the release step can see every previously @@ -103,7 +103,7 @@ jobs: fail-fast: false matrix: package: [cultcache-py, cultnet-py, cultmesh-py] - runs-on: docker + runs-on: ubuntu-latest timeout-minutes: 20 environment: pypi permissions: diff --git a/.github/workflows/quic-native.yml b/.github/workflows/quic-native.yml index eb72975c..e9d6a3eb 100644 --- a/.github/workflows/quic-native.yml +++ b/.github/workflows/quic-native.yml @@ -31,7 +31,7 @@ jobs: github.event_name == 'pull_request' || (github.event_name == 'push' && (github.ref == 'refs/heads/main' || startsWith(github.ref, 'refs/tags/cultmesh-quic-native-v'))) - runs-on: docker + runs-on: ubuntu-latest timeout-minutes: 5 steps: - name: Check out CultLib @@ -90,11 +90,11 @@ jobs: if: >- github.event_name == 'workflow_dispatch' || (github.event_name == 'push' && startsWith(github.ref, 'refs/tags/cultmesh-quic-native-v')) - runs-on: docker + runs-on: ubuntu-latest # Q6 A: this pinned Debian 13 container is the only permitted Linux # release builder. The digest is the pin; the `debian:13` tag is only a # label. The artifact links against this image's glibc (2.41); - # a bare runner (label `docker`) has no pinned glibc (a GitHub ubuntu-latest image carried 2.39), which is not the deploy host's + # a bare runner's OS image has its own glibc (a GitHub ubuntu-latest image carried 2.39), which is not the deploy host's # OS (docs/typescript-quic-realtime-cut.md:2192-2197). container: image: debian:13@sha256:f324c7ff54321e8d9c588493a20244965938ce0aa50bbd1022d38010e9ffc4b1 @@ -134,7 +134,7 @@ jobs: name: Release native binaries needs: [build-win32-x64, build-linux-x64] if: github.event_name == 'push' && startsWith(github.ref, 'refs/tags/cultmesh-quic-native-v') - runs-on: docker + runs-on: ubuntu-latest timeout-minutes: 10 permissions: contents: write From b55521d50dc91c00587d48eab8d5cef323d1d2f9 Mon Sep 17 00:00:00 2001 From: soul Date: Mon, 9 Nov 2026 15:50:10 +0000 Subject: [PATCH 37/76] cultmesh-ts test: observe queued provider registers from creation so a client timeout is not an unhandled rejection Co-Authored-By: Claude Sonnet 5.5 --- .../cultmesh-ts/test/provider-rudp-transport.test.ts | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/packages/cultmesh-ts/test/provider-rudp-transport.test.ts b/packages/cultmesh-ts/test/provider-rudp-transport.test.ts index aa4db341..577acc8a 100644 --- a/packages/cultmesh-ts/test/provider-rudp-transport.test.ts +++ b/packages/cultmesh-ts/test/provider-rudp-transport.test.ts @@ -287,18 +287,21 @@ test("a replaced RUDP generation drops operations queued behind an active handle runtimeId: identity.serviceInstanceId, connectionId, sessionToken: "signed-provider-token", - operationTimeoutMs: 100, + operationTimeoutMs: 1, // expires long before the test ends: the settle-by-timeout path is always exercised socketFactory: () => socket, }).connect(identity, new AbortController().signal); try { + // The handler is held open and the closed generation never answers, so these two settle only by the + // client's operation timeout or by connection.close(). Observe them from the start: whichever fires + // first is a legitimate end of the call, and this test asserts on server-side handler calls, not on it. const first = connection.register( { identity, requestedLeaseDurationMs: 10_000 }, new AbortController().signal, - ); + ).then(() => undefined, () => undefined); const second = connection.register( { identity, requestedLeaseDurationMs: 10_000 }, new AbortController().signal, - ); + ).then(() => undefined, () => undefined); await waitFor(() => handlerCalls === 1, "first queued provider operation"); const replacement = new CultNetRudpSession({ connectionId }); @@ -318,7 +321,7 @@ test("a replaced RUDP generation drops operations queued behind an active handle assert.equal(handlerCalls, 1, "transport-queued work from the closed generation must be dropped"); connection.close(); - await Promise.allSettled([first, second]); + await Promise.all([first, second]); } finally { connection.close(); server.close(); From a04014e0b45c3bb9c0055153248a8c93175e1482 Mon Sep 17 00:00:00 2001 From: soul Date: Mon, 9 Nov 2026 15:50:27 +0000 Subject: [PATCH 38/76] ci: rerun 1 (RUDP flake evidence) Co-Authored-By: Claude Sonnet 5.5 From cc5a9cec9b446996859212625a0413b6cdd88b10 Mon Sep 17 00:00:00 2001 From: soul Date: Mon, 9 Nov 2026 16:04:58 +0000 Subject: [PATCH 39/76] tests first: repeated messageId key, each runtime's declared policy (C# failing) Vectors admittedMessageIdDuplicates carry [invalid,valid], [valid,invalid], [valid,valid]. Decoders that see the duplicate (C#, Rust) must refuse all three; map-collapsing decoders (Python, TS, Kotlin) judge the last key. Python, TS, Kotlin and Rust already behave as declared; C# fails: it accepted [valid,valid]. Co-Authored-By: Claude Sonnet 5.5 --- .../interop/write-receipt-vectors.json | 28 ++++++++++++++++++- .../gamecult/cultmesh/WriteReceiptVectors.kt | 14 ++++++++++ .../cultnet-py/tests/test_write_receipt.py | 14 +++++++++- packages/cultnet-rs/tests/write_receipt.rs | 14 ++++++++++ .../cultnet-ts/test/write-receipt.test.ts | 12 ++++++++ .../CultNetWriteAdmittedMessageIdTests.cs | 14 ++++++++++ 6 files changed, 94 insertions(+), 2 deletions(-) diff --git a/contracts/cultnet/interop/write-receipt-vectors.json b/contracts/cultnet/interop/write-receipt-vectors.json index f99265ed..e95c3180 100644 --- a/contracts/cultnet/interop/write-receipt-vectors.json +++ b/contracts/cultnet/interop/write-receipt-vectors.json @@ -606,6 +606,31 @@ } ] }, + "admittedMessageIdDuplicates": { + "semantics": "A frame with more than one messageId key is malformed. A decoder that can see the duplicate (C#, Rust: raw MessagePack reader or ordered pairs) refuses it, whatever the values (`seesDuplicate`). A decoder that collapses the map (Python, TypeScript, Kotlin) judges the value it ends up with, the last key, and refuses exactly when that value is invalid (`lastKeyWins`); where it accepts it echoes the last key's id (`lastKeyWinsEcho`). Neither behaviour ever echoes an invalid id. Each runtime declares which policy it has and its runner asserts that policy's column, so a change of a decoder's key policy fails a named test. The first key is ZWSP (U+200B, Default_Ignorable) in `invalid_then_valid`; the second key is the last entry of the map. Frames are MessagePack hex.", + "cases": [ + { + "name": "invalid_then_valid", + "wireHex": "86AD736368656D6156657273696F6EB963756C746E65742E77726974655F61646D69747465642E7630A96D6573736167654964A3E2808BA96F7065726174696F6EA3707574A8736368656D614964A76E6F74652E7630A97265636F72644B6579A66E6F74653A31A96D6573736167654964A161", + "seesDuplicate": "refuse", + "lastKeyWins": "accept", + "lastKeyWinsEcho": "a" + }, + { + "name": "valid_then_invalid", + "wireHex": "86AD736368656D6156657273696F6EB963756C746E65742E77726974655F61646D69747465642E7630A96D6573736167654964A161A96F7065726174696F6EA3707574A8736368656D614964A76E6F74652E7630A97265636F72644B6579A66E6F74653A31A96D6573736167654964A3E2808B", + "seesDuplicate": "refuse", + "lastKeyWins": "refuse" + }, + { + "name": "valid_then_valid", + "wireHex": "86AD736368656D6156657273696F6EB963756C746E65742E77726974655F61646D69747465642E7630A96D6573736167654964A161A96F7065726174696F6EA3707574A8736368656D614964A76E6F74652E7630A97265636F72644B6579A66E6F74653A31A96D6573736167654964A162", + "seesDuplicate": "refuse", + "lastKeyWins": "accept", + "lastKeyWinsEcho": "b" + } + ] + }, "schemaInvalid": [ { "name": "write_admitted_with_an_error_key", @@ -1640,7 +1665,8 @@ "schemaInvalid": 7, "contentHashCases": 4, "scenarioCases": 25, - "admittedMessageIdCases": 59 + "admittedMessageIdCases": 59, + "admittedMessageIdDuplicateCases": 3 }, "contentHash": { "definition": "sha256(msgpack([operation, schemaId, recordKey, payload])), payload a msgpack bin for a put and nil for a delete; hex lowercase", diff --git a/packages/cultmesh-kotlin/src/main/kotlin/org/gamecult/cultmesh/WriteReceiptVectors.kt b/packages/cultmesh-kotlin/src/main/kotlin/org/gamecult/cultmesh/WriteReceiptVectors.kt index 35e0136f..b7215a12 100644 --- a/packages/cultmesh-kotlin/src/main/kotlin/org/gamecult/cultmesh/WriteReceiptVectors.kt +++ b/packages/cultmesh-kotlin/src/main/kotlin/org/gamecult/cultmesh/WriteReceiptVectors.kt @@ -100,6 +100,20 @@ fun replayWriteReceiptVectors(path: String): Boolean { if (text.isNotEmpty()) emptyList() else listOf("decoded to an empty string") } + // admittedMessageIdDuplicates: Kotlin decodes the frame to a map, so it cannot see a repeated key: it judges the last one. + for (case in list(obj(vectors["admittedMessageIdDuplicates"])["cases"])) { + val c = obj(case) + check("admittedMessageIdDuplicates/${c["name"]}") { + val decoded = try { decodeReceiptFrame(parseCultNetMessage(unhex(c["wireHex"] as String))) } catch (_: IOException) { null } + when { + c["lastKeyWins"] == "refuse" -> if (decoded == null) emptyList() else listOf("accepted a frame whose last messageId key is invalid") + decoded == null -> listOf("refused a frame whose last messageId key is valid") + decoded.toWireMap()["messageId"] != c["lastKeyWinsEcho"] -> listOf("echoed ${decoded.toWireMap()["messageId"]}, wanted ${c["lastKeyWinsEcho"]}") + else -> emptyList() + } + } + } + for (case in list(vectors["schemaInvalid"])) { val c = obj(case) check("schemaInvalid/${c["name"]}") { diff --git a/packages/cultnet-py/tests/test_write_receipt.py b/packages/cultnet-py/tests/test_write_receipt.py index fce0d850..0ff1d017 100644 --- a/packages/cultnet-py/tests/test_write_receipt.py +++ b/packages/cultnet-py/tests/test_write_receipt.py @@ -57,7 +57,7 @@ class WriteReceiptVectorTests(unittest.TestCase): def test_vector_sections_are_the_ones_this_file_accounts_for(self) -> None: self.assertEqual( set(_VECTORS) - {"provenance", "specification"}, - {"constants", "contentHash", "dedupe", "wire", "blankFields", "schemaInvalid", "scenarios", "counts", "admittedMessageId"}, + {"constants", "contentHash", "dedupe", "wire", "blankFields", "schemaInvalid", "scenarios", "counts", "admittedMessageId", "admittedMessageIdDuplicates"}, ) self.assertEqual(len(_VECTORS["contentHash"]["cases"]), 4) self.assertEqual(len(_VECTORS["wire"]), 7) @@ -94,6 +94,18 @@ class WriteReceiptVectorTests(unittest.TestCase): failures.append(f"{case['name']}: expected {case['expect']}") self.assertEqual(failures, []) + def test_a_repeated_message_id_key_follows_the_last_key_wins_policy(self) -> None: + # Python decodes the frame to a dict, so it cannot see a duplicate: it judges the last key (admittedMessageIdDuplicates). + section = _VECTORS["admittedMessageIdDuplicates"] + self.assertEqual(_VECTORS["counts"]["admittedMessageIdDuplicateCases"], len(section["cases"])) + for case in section["cases"]: + raw = bytes.fromhex(case["wireHex"]) + if case["lastKeyWins"] == "refuse": + with self.assertRaises(ValueError, msg=case["name"]): + parse_message(raw) + else: + self.assertEqual(parse_message(raw).body["messageId"], case["lastKeyWinsEcho"], case["name"]) + def test_every_range_of_the_unicode_tables_is_pinned_at_both_ends(self) -> None: # write-admitted-message-id-ranges.json is the generated, independent copy: for each letter-or-number range its first and # last point are accepted and the points either side refused; for each Default_Ignorable range "a" beside its first and diff --git a/packages/cultnet-rs/tests/write_receipt.rs b/packages/cultnet-rs/tests/write_receipt.rs index 10d7dcb8..ee05bd72 100644 --- a/packages/cultnet-rs/tests/write_receipt.rs +++ b/packages/cultnet-rs/tests/write_receipt.rs @@ -238,3 +238,17 @@ fn every_range_of_the_unicode_tables_is_pinned_at_both_ends() { } assert!(failures.is_empty(), "{} failing:\n{}", failures.len(), failures.join("\n")); } + +/// Vectors `admittedMessageIdDuplicates`: Rust decodes a map it can see into, so a repeated messageId key is refused whatever the values. +#[test] +fn a_repeated_message_id_key_is_refused_whatever_the_values() { + let path = concat!(env!("CARGO_MANIFEST_DIR"), "/../../contracts/cultnet/interop/write-receipt-vectors.json"); + let vectors: serde_json::Value = serde_json::from_str(&std::fs::read_to_string(path).unwrap()).unwrap(); + let cases = vectors["admittedMessageIdDuplicates"]["cases"].as_array().unwrap(); + assert_eq!(vectors["counts"]["admittedMessageIdDuplicateCases"].as_u64().unwrap() as usize, cases.len()); + for case in cases { + assert_eq!(case["seesDuplicate"], "refuse"); + let decoded = decode_cultnet_message_from_slice(&unhex(case["wireHex"].as_str().unwrap()), CultNetWireContract::CultNetSchemaV0); + assert!(decoded.is_err(), "{}: a repeated messageId key must be refused", case["name"]); + } +} diff --git a/packages/cultnet-ts/test/write-receipt.test.ts b/packages/cultnet-ts/test/write-receipt.test.ts index c654de1a..1483fe31 100644 --- a/packages/cultnet-ts/test/write-receipt.test.ts +++ b/packages/cultnet-ts/test/write-receipt.test.ts @@ -323,3 +323,15 @@ test("write_admitted messageId: every range of the Unicode 16.0 tables is pinned } assert.deepEqual(failures, []); }); + +// TypeScript decodes the frame to an object, so it cannot see a duplicate key: it judges the last one (admittedMessageIdDuplicates). +test("write_admitted messageId: a repeated key follows the last-key-wins policy", () => { + const vectors = JSON.parse(readFileSync(join(__dirname, "..", "..", "..", "..", "contracts", "cultnet", "interop", "write-receipt-vectors.json"), "utf8")); + const section = vectors.admittedMessageIdDuplicates; + assert.equal(vectors.counts.admittedMessageIdDuplicateCases, section.cases.length); + for (const vector of section.cases as { name: string; wireHex: string; lastKeyWins: "accept" | "refuse"; lastKeyWinsEcho?: string }[]) { + const frame = decode(Uint8Array.from(Buffer.from(vector.wireHex, "hex"))); + if (vector.lastKeyWins === "refuse") assert.throws(() => parseCultNetMessage(frame), /./s, vector.name); + else assert.equal((parseCultNetMessage(frame) as CultNetWriteAdmittedMessage).messageId, vector.lastKeyWinsEcho, vector.name); + } +}); diff --git a/tests/GameCult.Networking.Tests/CultNetWriteAdmittedMessageIdTests.cs b/tests/GameCult.Networking.Tests/CultNetWriteAdmittedMessageIdTests.cs index a4f1475c..6c65734c 100644 --- a/tests/GameCult.Networking.Tests/CultNetWriteAdmittedMessageIdTests.cs +++ b/tests/GameCult.Networking.Tests/CultNetWriteAdmittedMessageIdTests.cs @@ -87,5 +87,19 @@ namespace GameCult.Networking.Tests if (Accepted("a" + char.ConvertFromUtf32(point)) != want) failures.Add($"a + U+{point:X} should be {(want ? "accepted" : "refused")}"); Assert.That(failures, Is.Empty); } + + // admittedMessageIdDuplicates: C# reads the raw map, so it sees a repeated messageId key and refuses it whatever the values. + [Test] + public void ARepeatedMessageIdKeyIsRefusedWhateverTheValues() + { + var cases = Vectors().GetProperty("admittedMessageIdDuplicates").GetProperty("cases").EnumerateArray().ToArray(); + Assert.That(cases.Length, Is.EqualTo(Vectors().GetProperty("counts").GetProperty("admittedMessageIdDuplicateCases").GetInt32())); + foreach (var vector in cases) + { + Assert.That(vector.GetProperty("seesDuplicate").GetString(), Is.EqualTo("refuse")); + Assert.That(() => CultNetSchemaMessageSerialization.Deserialize(Convert.FromHexString(vector.GetProperty("wireHex").GetString()!)), + Throws.Exception, vector.GetProperty("name").GetString()); + } + } } } From e08d5c47f4cfe45e978c2ac5b8399b4cdd63b6b3 Mon Sep 17 00:00:00 2001 From: soul Date: Mon, 9 Nov 2026 16:05:21 +0000 Subject: [PATCH 40/76] GameCult.Networking: a write_admitted frame with more than one messageId key is malformed Co-Authored-By: Claude Sonnet 5.5 --- .../CultNetSchemaMessageSerialization.cs | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/GameCult.Networking/CultNetSchemaMessageSerialization.cs b/src/GameCult.Networking/CultNetSchemaMessageSerialization.cs index cb33b707..1e7bdd08 100644 --- a/src/GameCult.Networking/CultNetSchemaMessageSerialization.cs +++ b/src/GameCult.Networking/CultNetSchemaMessageSerialization.cs @@ -111,21 +111,24 @@ namespace GameCult.Networking { private static readonly System.Text.UTF8Encoding StrictUtf8 = new(false, true); - /// Throws unless every top-level messageId key of the frame is a valid write_admitted id; other keys are left to the serializer. + /// Throws unless the frame's one top-level messageId key is a valid write_admitted id (a repeated key is malformed); other keys are left to the serializer. public static void Require(byte[] payload) { var reader = new MessagePackReader(payload); var entries = reader.ReadMapHeader(); + var seen = false; for (var index = 0; index < entries; index++) { var key = reader.NextMessagePackType == MessagePackType.String ? reader.ReadString() : null; if (key == "messageId" && reader.NextMessagePackType == MessagePackType.String) { + if (seen) throw new MessagePackSerializationException("cultnet.write_admitted.v0 carries messageId more than once."); + seen = true; string id; try { id = StrictUtf8.GetString(reader.ReadStringSequence()!.Value.ToArray()); } catch (System.Text.DecoderFallbackException) { throw new MessagePackSerializationException("cultnet.write_admitted.v0 messageId is not valid UTF-8."); } if (!IsValid(id)) throw new MessagePackSerializationException("cultnet.write_admitted.v0 messageId must contain a letter or number and no Default_Ignorable code point."); - continue; // a repeated messageId key is checked too: the serializer keeps the last, so every one must be valid + continue; } if (key == null) reader.Skip(); From 27d9fbca9414cdaadb214095f66b9f643854f95a Mon Sep 17 00:00:00 2001 From: soul Date: Mon, 9 Nov 2026 16:07:11 +0000 Subject: [PATCH 41/76] write-receipt.md: where a received messageId decides, and the single-parser assumption it relies on Co-Authored-By: Claude Sonnet 5.5 --- contracts/cultnet/interop/write-receipt.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/contracts/cultnet/interop/write-receipt.md b/contracts/cultnet/interop/write-receipt.md index 4f6cb7d1..5bb0a3e8 100644 --- a/contracts/cultnet/interop/write-receipt.md +++ b/contracts/cultnet/interop/write-receipt.md @@ -12,7 +12,9 @@ A put or delete today has two answers on the wire: nothing (admitted, or the ser **`cultnet.write_admitted.v0`**: `{ schemaVersion, messageId, operation: "put"|"delete", schemaId, recordKey }`, every key written, none nil. Sent once a put or delete is admitted. The id echoes the write's `messageId` byte for byte; `operation`, `schemaId`, `recordKey` echo the write. -**A `write_admitted` `messageId` is valid only if it contains at least one Unicode letter (`\p{L}`) or number (`\p{N}`) and no Default_Ignorable_Code_Point (DerivedCoreProperties).** The compare is raw: the id is echoed byte for byte, so a decoder never trims or normalizes it (`" a "` is valid; U+2800 is not, and neither is `a` followed by U+200B). Unlike the Required fields rule, this is not a White_Space test. The letters, numbers and Default_Ignorable code points are those of **Unicode 16.0** in every runtime, taken from tables embedded in each runtime and generated by `scripts/gen-unicode-messageid-tables.py` from `DerivedGeneralCategory.txt` (Lu Ll Lt Lm Lo Nd Nl No) and `DerivedCoreProperties.txt`; no runtime asks its platform's Unicode data, so a runtime's own Unicode version cannot change a result. Bumping the version is one coordinated change: regenerate the five tables and update the vectors that pin the version in the same commit. The rule is on the decoded string, which must be well-formed Unicode: a lone surrogate refuses. A runtime whose MessagePack reader rejects invalid UTF-8 refuses the frame earlier; byte-level strictness is a property of the reader and is not required of a decoder. Every `messageId` key of a frame must be valid. A decoder refuses an invalid id the way it refuses an empty or ill-typed one; the rule is for `write_admitted` only and `error.v1` is unchanged. +**A `write_admitted` `messageId` is valid only if it contains at least one Unicode letter (`\p{L}`) or number (`\p{N}`) and no Default_Ignorable_Code_Point (DerivedCoreProperties).** The compare is raw: the id is echoed byte for byte, so a decoder never trims or normalizes it (`" a "` is valid; U+2800 is not, and neither is `a` followed by U+200B). Unlike the Required fields rule, this is not a White_Space test. The letters, numbers and Default_Ignorable code points are those of **Unicode 16.0** in every runtime, taken from tables embedded in each runtime and generated by `scripts/gen-unicode-messageid-tables.py` from `DerivedGeneralCategory.txt` (Lu Ll Lt Lm Lo Nd Nl No) and `DerivedCoreProperties.txt`; no runtime asks its platform's Unicode data, so a runtime's own Unicode version cannot change a result. Bumping the version is one coordinated change: regenerate the five tables and update the vectors that pin the version in the same commit. The rule is on the decoded string, which must be well-formed Unicode: a lone surrogate refuses. A runtime whose MessagePack reader rejects invalid UTF-8 refuses the frame earlier; byte-level strictness is a property of the reader and is not required of a decoder. A frame with more than one `messageId` key is malformed: a decoder that can see the duplicate (the C# and Rust decoders) refuses it; a decoder that collapses the map (Python, TypeScript, Kotlin) judges the value it ends up with, the last key, and refuses if that value is invalid, so no decoder echoes an invalid id (vectors `admittedMessageIdDuplicates` declare each policy and every runner asserts its own). A decoder refuses an invalid id the way it refuses an empty or ill-typed one; the rule is for `write_admitted` only and `error.v1` is unchanged. + +**Where a received `messageId` decides, and what that relies on.** Beyond echoing it, a received `messageId` is used in exactly two places. On a server, the remembered-write key `(actor, messageId)` with the content hash, the per-session limit and the replay of rule 5 are taken from the id of the write frame the server itself parsed. On a client, the write tracker attributes a `write_admitted` or `error.v1` to a write by equality with the id of a write that client itself sent (a frame naming an id not in flight settles nothing), and attributes an id-less error by the count of unanswered writes, not by any id. Both rely on the receiver being the only parser of that frame: a decoder's `messageId` on a frame that carries more than one `messageId` key is not trusted for any authorization, dedupe, replay or ordering decision, and the map-collapsing runtimes (Python, TypeScript, Kotlin, and C#'s typed deserializer on write frames) cannot see the duplicate. If a frame is ever parsed by two runtimes in a chain (a relay, proxy or forwarder that re-reads it), or a consumer needs `messageId` for authorization, dedupe, replay or ordering beyond echo and equality with a self-sent id, all runtimes must first refuse a duplicate `messageId` key (a spec change plus byte-level duplicate detection in the runtimes that decode to a map) before that use lands. Today Rust refuses a repeated key on every raw schema frame, write frames included; C# does not on write frames (a duplicate-key write is deduped on its last id, while the id it names in a refusal for an unreadable frame is read from the first key), and the Python server (`cultmesh_py/server.py`) only echoes the id while the TypeScript and Kotlin code matches it by equality with a request it sent. **`cultnet.error.v1`**: `cultnet.error.v0` plus a required `messageId` (string or nil). The code enum is v0's plus `limit_reached` (see Remembered-write limits); `cultnet.error.v1.schema.json` is the authority for the closed set. Sent in place of `error.v0` to a receipt-capable connection. `messageId` is the id of the request the error answers; nil only when the frame was unreadable or carried no id. From 2aade7fdd32fd4ca5719c45d76365d545d546eba Mon Sep 17 00:00:00 2001 From: soul Date: Mon, 9 Nov 2026 16:25:50 +0000 Subject: [PATCH 42/76] Docs and test comments: correct the Rust duplicate-key citation, say what the TS and Rust duplicate tests pin Co-Authored-By: Claude Sonnet 5.5 --- contracts/cultnet/interop/write-receipt.md | 2 +- packages/cultnet-rs/tests/write_receipt.rs | 3 ++- packages/cultnet-ts/test/write-receipt.test.ts | 2 ++ 3 files changed, 5 insertions(+), 2 deletions(-) diff --git a/contracts/cultnet/interop/write-receipt.md b/contracts/cultnet/interop/write-receipt.md index 5bb0a3e8..bfcbbb86 100644 --- a/contracts/cultnet/interop/write-receipt.md +++ b/contracts/cultnet/interop/write-receipt.md @@ -14,7 +14,7 @@ A put or delete today has two answers on the wire: nothing (admitted, or the ser **A `write_admitted` `messageId` is valid only if it contains at least one Unicode letter (`\p{L}`) or number (`\p{N}`) and no Default_Ignorable_Code_Point (DerivedCoreProperties).** The compare is raw: the id is echoed byte for byte, so a decoder never trims or normalizes it (`" a "` is valid; U+2800 is not, and neither is `a` followed by U+200B). Unlike the Required fields rule, this is not a White_Space test. The letters, numbers and Default_Ignorable code points are those of **Unicode 16.0** in every runtime, taken from tables embedded in each runtime and generated by `scripts/gen-unicode-messageid-tables.py` from `DerivedGeneralCategory.txt` (Lu Ll Lt Lm Lo Nd Nl No) and `DerivedCoreProperties.txt`; no runtime asks its platform's Unicode data, so a runtime's own Unicode version cannot change a result. Bumping the version is one coordinated change: regenerate the five tables and update the vectors that pin the version in the same commit. The rule is on the decoded string, which must be well-formed Unicode: a lone surrogate refuses. A runtime whose MessagePack reader rejects invalid UTF-8 refuses the frame earlier; byte-level strictness is a property of the reader and is not required of a decoder. A frame with more than one `messageId` key is malformed: a decoder that can see the duplicate (the C# and Rust decoders) refuses it; a decoder that collapses the map (Python, TypeScript, Kotlin) judges the value it ends up with, the last key, and refuses if that value is invalid, so no decoder echoes an invalid id (vectors `admittedMessageIdDuplicates` declare each policy and every runner asserts its own). A decoder refuses an invalid id the way it refuses an empty or ill-typed one; the rule is for `write_admitted` only and `error.v1` is unchanged. -**Where a received `messageId` decides, and what that relies on.** Beyond echoing it, a received `messageId` is used in exactly two places. On a server, the remembered-write key `(actor, messageId)` with the content hash, the per-session limit and the replay of rule 5 are taken from the id of the write frame the server itself parsed. On a client, the write tracker attributes a `write_admitted` or `error.v1` to a write by equality with the id of a write that client itself sent (a frame naming an id not in flight settles nothing), and attributes an id-less error by the count of unanswered writes, not by any id. Both rely on the receiver being the only parser of that frame: a decoder's `messageId` on a frame that carries more than one `messageId` key is not trusted for any authorization, dedupe, replay or ordering decision, and the map-collapsing runtimes (Python, TypeScript, Kotlin, and C#'s typed deserializer on write frames) cannot see the duplicate. If a frame is ever parsed by two runtimes in a chain (a relay, proxy or forwarder that re-reads it), or a consumer needs `messageId` for authorization, dedupe, replay or ordering beyond echo and equality with a self-sent id, all runtimes must first refuse a duplicate `messageId` key (a spec change plus byte-level duplicate detection in the runtimes that decode to a map) before that use lands. Today Rust refuses a repeated key on every raw schema frame, write frames included; C# does not on write frames (a duplicate-key write is deduped on its last id, while the id it names in a refusal for an unreadable frame is read from the first key), and the Python server (`cultmesh_py/server.py`) only echoes the id while the TypeScript and Kotlin code matches it by equality with a request it sent. +**Where a received `messageId` decides, and what that relies on.** Beyond echoing it, a received `messageId` is used in exactly two places. On a server, the remembered-write key `(actor, messageId)` with the content hash, the per-session limit and the replay of rule 5 are taken from the id of the write frame the server itself parsed. On a client, the write tracker attributes a `write_admitted` or `error.v1` to a write by equality with the id of a write that client itself sent (a frame naming an id not in flight settles nothing), and attributes an id-less error by the count of unanswered writes, not by any id. Both rely on the receiver being the only parser of that frame: a decoder's `messageId` on a frame that carries more than one `messageId` key is not trusted for any authorization, dedupe, replay or ordering decision, and the map-collapsing runtimes (Python, TypeScript, Kotlin, and C#'s typed deserializer on write frames) cannot see the duplicate. If a frame is ever parsed by two runtimes in a chain (a relay, proxy or forwarder that re-reads it), or a consumer needs `messageId` for authorization, dedupe, replay or ordering beyond echo and equality with a self-sent id, all runtimes must first refuse a duplicate `messageId` key (a spec change plus byte-level duplicate detection in the runtimes that decode to a map) before that use lands. Today Rust refuses a repeated key on write frames and on `write_admitted` (serde's `duplicate field messageId`, and `refuse_duplicate_keys` for the raw-list frames such as `document_put_raw`), though its `readable_message_id` names the first key when it names an unreadable frame; C# does not on write frames (a duplicate-key write is deduped on its last id, while the id it names in a refusal for an unreadable frame is read from the first key), and the Python server (`cultmesh_py/server.py`) only echoes the id while the TypeScript and Kotlin code matches it by equality with a request it sent. **`cultnet.error.v1`**: `cultnet.error.v0` plus a required `messageId` (string or nil). The code enum is v0's plus `limit_reached` (see Remembered-write limits); `cultnet.error.v1.schema.json` is the authority for the closed set. Sent in place of `error.v0` to a receipt-capable connection. `messageId` is the id of the request the error answers; nil only when the frame was unreadable or carried no id. diff --git a/packages/cultnet-rs/tests/write_receipt.rs b/packages/cultnet-rs/tests/write_receipt.rs index ee05bd72..3dd02a76 100644 --- a/packages/cultnet-rs/tests/write_receipt.rs +++ b/packages/cultnet-rs/tests/write_receipt.rs @@ -239,7 +239,8 @@ fn every_range_of_the_unicode_tables_is_pinned_at_both_ends() { assert!(failures.is_empty(), "{} failing:\n{}", failures.len(), failures.join("\n")); } -/// Vectors `admittedMessageIdDuplicates`: Rust decodes a map it can see into, so a repeated messageId key is refused whatever the values. +/// Vectors `admittedMessageIdDuplicates`: a repeated messageId key is refused whatever the values. This pins the outcome, not the +/// cause (today serde's `duplicate field messageId` in the typed decode of write_admitted). #[test] fn a_repeated_message_id_key_is_refused_whatever_the_values() { let path = concat!(env!("CARGO_MANIFEST_DIR"), "/../../contracts/cultnet/interop/write-receipt-vectors.json"); diff --git a/packages/cultnet-ts/test/write-receipt.test.ts b/packages/cultnet-ts/test/write-receipt.test.ts index 1483fe31..23ef478e 100644 --- a/packages/cultnet-ts/test/write-receipt.test.ts +++ b/packages/cultnet-ts/test/write-receipt.test.ts @@ -325,6 +325,8 @@ test("write_admitted messageId: every range of the Unicode 16.0 tables is pinned }); // TypeScript decodes the frame to an object, so it cannot see a duplicate key: it judges the last one (admittedMessageIdDuplicates). +// This pins @msgpack/msgpack's last-key behaviour plus the id check on the decoded object, not a first-key or duplicate path in +// cultnet-ts source: there is none, so a refuse-on-first mutation in src would not be killed. test("write_admitted messageId: a repeated key follows the last-key-wins policy", () => { const vectors = JSON.parse(readFileSync(join(__dirname, "..", "..", "..", "..", "contracts", "cultnet", "interop", "write-receipt-vectors.json"), "utf8")); const section = vectors.admittedMessageIdDuplicates; From c9cdbc7edc35c095a1ba16a3779fa338bf3173de Mon Sep 17 00:00:00 2001 From: soul Date: Mon, 9 Nov 2026 16:29:33 +0000 Subject: [PATCH 43/76] ci: rerun 2 (RUDP flake evidence) Co-Authored-By: Claude Sonnet 5.5 From 073efd06e79716f7fdabe44a83cebbbafc9596af Mon Sep 17 00:00:00 2001 From: soul Date: Mon, 9 Nov 2026 16:53:27 +0000 Subject: [PATCH 44/76] ci: rerun 3 (RUDP flake evidence) Co-Authored-By: Claude Sonnet 5.5 From a56ae56b7d2ac21b6049274e04c0d650a5dee600 Mon Sep 17 00:00:00 2001 From: Libby Date: Mon, 9 Nov 2026 19:50:50 +0000 Subject: [PATCH 45/76] CI: install the Rust toolchain on forge Linux runners The act-latest image has no cargo; the cross-runtime CultCache store tests build the Rust interop example and failed with "cargo: not found" once the RUDP flake and pwsh were out of the way. Co-Authored-By: Claude Sonnet 5.5 --- .github/workflows/cultmesh-portability.yml | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/.github/workflows/cultmesh-portability.yml b/.github/workflows/cultmesh-portability.yml index b93aeeb0..b327b8c5 100644 --- a/.github/workflows/cultmesh-portability.yml +++ b/.github/workflows/cultmesh-portability.yml @@ -45,6 +45,17 @@ jobs: - name: Install Python CultLib packages run: python -m pip install -e packages/cultcache-py -e packages/cultnet-py -e packages/cultmesh-py + # The forge runner images carry no cargo either (the cross-runtime store tests build the Rust interop example); GitHub's hosted images do. + - name: Install Rust toolchain + if: runner.os == 'Linux' + shell: bash + run: | + set -euo pipefail + if ! command -v cargo >/dev/null && [ ! -x "$HOME/.cargo/bin/cargo" ]; then + curl -fsSL https://sh.rustup.rs | sh -s -- -y --profile minimal --default-toolchain stable + fi + echo "$HOME/.cargo/bin" >> "$GITHUB_PATH" + # The forge runner images carry no pwsh; GitHub's Windows and hosted images do. - name: Install PowerShell if: runner.os == 'Linux' From ef82811f2e41826aeb6115f8e9d075ffc4d56a96 Mon Sep 17 00:00:00 2001 From: Libby Date: Mon, 9 Nov 2026 20:03:46 +0000 Subject: [PATCH 46/76] CI: fetch tags in cultmesh-portability checkout scripts/test-declared-dependency-ranges.mjs asserts the clone has release tags; actions/checkout fetches none by default. Co-Authored-By: Claude Sonnet 5.5 --- .github/workflows/cultmesh-portability.yml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.github/workflows/cultmesh-portability.yml b/.github/workflows/cultmesh-portability.yml index b327b8c5..a346a410 100644 --- a/.github/workflows/cultmesh-portability.yml +++ b/.github/workflows/cultmesh-portability.yml @@ -22,6 +22,10 @@ jobs: steps: - name: Check out CultLib uses: actions/checkout@v4 + with: + # The declared-dependency-range test builds the admitted releases from their tags. + fetch-depth: 0 + fetch-tags: true - name: Set up .NET uses: actions/setup-dotnet@v4 From a2bb0bced471c8243ffbde3226d9a9c6b796a70c Mon Sep 17 00:00:00 2001 From: Libby Date: Mon, 9 Nov 2026 20:19:03 +0000 Subject: [PATCH 47/76] cultnet-ts test: wait for loopback datagrams instead of a fixed 100 ms sleep "server mode admits a pinned client that restarts on a new port" asserted the Accept arrived after sleep(100) and failed on the forge runner (ok(accept) undefined, run 894). It now polls for the packet or frame it asserts on, up to 2 s; no assertion changed. Co-Authored-By: Claude Sonnet 5.5 --- .../cultnet-ts/test/rudp-connect-generation.test.ts | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/packages/cultnet-ts/test/rudp-connect-generation.test.ts b/packages/cultnet-ts/test/rudp-connect-generation.test.ts index cea61a6d..883fe638 100644 --- a/packages/cultnet-ts/test/rudp-connect-generation.test.ts +++ b/packages/cultnet-ts/test/rudp-connect-generation.test.ts @@ -367,6 +367,10 @@ async function bind(): Promise { return socket; } const sleep = (ms: number) => new Promise((resolve) => setTimeout(resolve, ms)); +// Wait for a datagram-driven condition rather than guessing how long loopback delivery takes on a loaded runner. +const until = async (condition: () => boolean, limitMs = 2_000) => { + for (let waited = 0; !condition() && waited < limitMs; waited += 10) await sleep(10); +}; test("server mode admits a pinned client that restarts on a new port", async () => { const serverSocket = await bind(); @@ -385,24 +389,24 @@ test("server mode admits a pinned client that restarts on a new port", async () try { const first = session(1); a.send(encodeRudpPacket(first.createConnect(0, enc("join"))), port, "127.0.0.1"); - await sleep(100); + await until(() => atA.some((packet) => packet.packetType === "accept")); const accept = atA.find((packet) => packet.packetType === "accept"); assert.ok(accept); first.receive(accept, 0); a.send(encodeRudpPacket(first.createAckForReceived(accept.sequence)), port, "127.0.0.1"); a.send(encodeRudpPacket(send(first, "hello")), port, "127.0.0.1"); - await sleep(100); + await until(() => frames.length === 1); assert.deepEqual(frames, ["hello"]); const second = session(1); // the process restarted on a new port, pinned b.send(encodeRudpPacket(second.createConnect(0, enc("join"))), port, "127.0.0.1"); - await sleep(100); + await until(() => atB.some((packet) => packet.packetType === "accept")); const acceptB = atB.find((packet) => packet.packetType === "accept"); assert.ok(acceptB, `the restarted client on a new port was not admitted: ${JSON.stringify(atB.map((packet) => packet.packetType))}`); second.receive(acceptB, 1); assert.ok(second.connected); b.send(encodeRudpPacket(send(second, "after")), port, "127.0.0.1"); - await sleep(100); + await until(() => frames.length === 2); assert.deepEqual(frames, ["hello", "after"]); } finally { server.close(); From 356687b7174393c5d524bdfa39dd5ffff099f9af Mon Sep 17 00:00:00 2001 From: soul Date: Mon, 9 Nov 2026 22:01:25 +0000 Subject: [PATCH 48/76] cultmesh-ts test: result window outlasts the transport ack in the RUDP catalog publish test The put's flush is bounded by the result window, so a 25 ms flush and 100 ms window made a slow ack on a loaded forge runner surface as local_send_failed instead of the window_elapsed the test checks. Drop the flush override and give the window room. Co-Authored-By: Claude Sonnet 5.5 --- packages/cultmesh-ts/test/cultmesh.test.ts | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/packages/cultmesh-ts/test/cultmesh.test.ts b/packages/cultmesh-ts/test/cultmesh.test.ts index 68429969..10de21b9 100644 --- a/packages/cultmesh-ts/test/cultmesh.test.ts +++ b/packages/cultmesh-ts/test/cultmesh.test.ts @@ -2986,8 +2986,9 @@ test("CultMesh TS publishes one registered document to a RUDP catalog", async () maxFragmentBytes: 1024, maxPendingReliablePackets: 16, connectTimeoutMs: 1_000, - flushTimeoutMs: 25, - resultWindowMs: 100, + // The flush is bounded by the result window, so the window must outlast the transport ack on a loaded + // runner: a short one turns a slow ack into local_send_failed, which is not the path this test checks. + resultWindowMs: 750, sourceRole: "test-provider", }, ), From e6a279248c33f4e5dd8d21a40d0f88b9d6893ef2 Mon Sep 17 00:00:00 2001 From: Libby Date: Tue, 10 Nov 2026 17:01:43 +0000 Subject: [PATCH 49/76] CultNet governance contracts: vote API and fork-desk card (#72) --- .../cultnet/governance/fork-card-vectors.json | 14658 ++++++++++++++++ contracts/cultnet/governance/fork-card.md | 193 + .../cultnet/governance/fork-card.schema.json | 965 + .../cultnet/governance/vote-api-vectors.json | 1231 ++ contracts/cultnet/governance/vote-api.md | 107 + .../cultnet/governance/vote-api.schema.json | 420 + .../test/governance-vectors.test.ts | 465 + scripts/gen-governance-vectors.py | 551 + .../GovernanceVectorTests.cs | 647 + .../MiniJsonSchemaValidator.cs | 5 +- 10 files changed, 19240 insertions(+), 2 deletions(-) create mode 100644 contracts/cultnet/governance/fork-card-vectors.json create mode 100644 contracts/cultnet/governance/fork-card.md create mode 100644 contracts/cultnet/governance/fork-card.schema.json create mode 100644 contracts/cultnet/governance/vote-api-vectors.json create mode 100644 contracts/cultnet/governance/vote-api.md create mode 100644 contracts/cultnet/governance/vote-api.schema.json create mode 100644 packages/cultnet-ts/test/governance-vectors.test.ts create mode 100644 scripts/gen-governance-vectors.py create mode 100644 tests/GameCult.Networking.Tests/GovernanceVectorTests.cs diff --git a/contracts/cultnet/governance/fork-card-vectors.json b/contracts/cultnet/governance/fork-card-vectors.json new file mode 100644 index 00000000..1575e5c9 --- /dev/null +++ b/contracts/cultnet/governance/fork-card-vectors.json @@ -0,0 +1,14658 @@ +{ + "provenance": "Rule source: /opt/commons/handoff/fork-desk-answer-binding-20261110.md (Heimdall), amended by the Persona briefs of 2026-11-10 (Eureka's fit check). Bytes spec-authored with msgpack-python (scripts/gen-governance-vectors.py); every runtime re-encodes `message` and must match messagePackHex. options_hash follows fork-card.md 'Options hash'; the vectors named heimdall_* are that file's, with its digests.", + "optionsHash": [ + { + "name": "heimdall_A_five_members", + "heimdall": "A", + "title": "Slot mark consent", + "context": "Should a slot mark be shared?", + "options": [ + { + "id": "yes", + "text": "Share it." + }, + { + "id": "no", + "text": "Keep it local." + } + ], + "recommendation": "no", + "depends": [ + "gamecult-chat:question:file-zone-audience" + ], + "canonical": "{\"context\":\"Should a slot mark be shared?\",\"depends\":[\"gamecult-chat:question:file-zone-audience\"],\"options\":[{\"id\":\"yes\",\"text\":\"Share it.\"},{\"id\":\"no\",\"text\":\"Keep it local.\"}],\"recommendation\":\"no\",\"title\":\"Slot mark consent\"}", + "canonicalHex": "7B22636F6E74657874223A2253686F756C64206120736C6F74206D61726B206265207368617265643F222C22646570656E6473223A5B2267616D6563756C742D636861743A7175657374696F6E3A66696C652D7A6F6E652D61756469656E6365225D2C226F7074696F6E73223A5B7B226964223A22796573222C2274657874223A2253686172652069742E227D2C7B226964223A226E6F222C2274657874223A224B656570206974206C6F63616C2E227D5D2C227265636F6D6D656E646174696F6E223A226E6F222C227469746C65223A22536C6F74206D61726B20636F6E73656E74227D", + "sha256": "7d95fc87d532420611fe415c1def27a3447617bb68235f46e3af130aea361c48" + }, + { + "name": "heimdall_B_recommendation_changed", + "heimdall": "B", + "title": "Slot mark consent", + "context": "Should a slot mark be shared?", + "options": [ + { + "id": "yes", + "text": "Share it." + }, + { + "id": "no", + "text": "Keep it local." + } + ], + "recommendation": "yes", + "depends": [ + "gamecult-chat:question:file-zone-audience" + ], + "canonical": "{\"context\":\"Should a slot mark be shared?\",\"depends\":[\"gamecult-chat:question:file-zone-audience\"],\"options\":[{\"id\":\"yes\",\"text\":\"Share it.\"},{\"id\":\"no\",\"text\":\"Keep it local.\"}],\"recommendation\":\"yes\",\"title\":\"Slot mark consent\"}", + "canonicalHex": "7B22636F6E74657874223A2253686F756C64206120736C6F74206D61726B206265207368617265643F222C22646570656E6473223A5B2267616D6563756C742D636861743A7175657374696F6E3A66696C652D7A6F6E652D61756469656E6365225D2C226F7074696F6E73223A5B7B226964223A22796573222C2274657874223A2253686172652069742E227D2C7B226964223A226E6F222C2274657874223A224B656570206974206C6F63616C2E227D5D2C227265636F6D6D656E646174696F6E223A22796573222C227469746C65223A22536C6F74206D61726B20636F6E73656E74227D", + "sha256": "14b8ffbdb61106dbc06f69f3fc8e2072af7a205d766e4cc040e89733013d54dc" + }, + { + "name": "heimdall_C_option_order_changed", + "heimdall": "C", + "title": "Slot mark consent", + "context": "Should a slot mark be shared?", + "options": [ + { + "id": "no", + "text": "Keep it local." + }, + { + "id": "yes", + "text": "Share it." + } + ], + "recommendation": "no", + "depends": [ + "gamecult-chat:question:file-zone-audience" + ], + "canonical": "{\"context\":\"Should a slot mark be shared?\",\"depends\":[\"gamecult-chat:question:file-zone-audience\"],\"options\":[{\"id\":\"no\",\"text\":\"Keep it local.\"},{\"id\":\"yes\",\"text\":\"Share it.\"}],\"recommendation\":\"no\",\"title\":\"Slot mark consent\"}", + "canonicalHex": "7B22636F6E74657874223A2253686F756C64206120736C6F74206D61726B206265207368617265643F222C22646570656E6473223A5B2267616D6563756C742D636861743A7175657374696F6E3A66696C652D7A6F6E652D61756469656E6365225D2C226F7074696F6E73223A5B7B226964223A226E6F222C2274657874223A224B656570206974206C6F63616C2E227D2C7B226964223A22796573222C2274657874223A2253686172652069742E227D5D2C227265636F6D6D656E646174696F6E223A226E6F222C227469746C65223A22536C6F74206D61726B20636F6E73656E74227D", + "sha256": "ac67b41539f869f6203ac0908819df046e4328b27795743a919bd4ab390596b5" + }, + { + "name": "heimdall_D_nfc_escapes_astral", + "heimdall": "D", + "title": "Café “desk”", + "context": "Line one\nLine two — 😀", + "options": [ + { + "id": "a", + "text": "x" + }, + { + "id": "b", + "text": "y" + } + ], + "recommendation": "a", + "depends": [], + "canonical": "{\"context\":\"Line one\\nLine two — 😀\",\"depends\":[],\"options\":[{\"id\":\"a\",\"text\":\"x\"},{\"id\":\"b\",\"text\":\"y\"}],\"recommendation\":\"a\",\"title\":\"Café “desk”\"}", + "canonicalHex": "7B22636F6E74657874223A224C696E65206F6E655C6E4C696E652074776F20E2809420F09F9880222C22646570656E6473223A5B5D2C226F7074696F6E73223A5B7B226964223A2261222C2274657874223A2278227D2C7B226964223A2262222C2274657874223A2279227D5D2C227265636F6D6D656E646174696F6E223A2261222C227469746C65223A22436166C3A920E2809C6465736BE2809D227D", + "sha256": "71d5c413478ad06aaa1cbdd9e91c53f0cb4cef1dbd4c51bf3d473bccc6a5a272" + }, + { + "name": "heimdall_E_decomposed_equals_D", + "heimdall": "E", + "title": "Café “desk”", + "context": "Line one\nLine two — 😀", + "options": [ + { + "id": "a", + "text": "x" + }, + { + "id": "b", + "text": "y" + } + ], + "recommendation": "a", + "depends": [], + "canonical": "{\"context\":\"Line one\\nLine two — 😀\",\"depends\":[],\"options\":[{\"id\":\"a\",\"text\":\"x\"},{\"id\":\"b\",\"text\":\"y\"}],\"recommendation\":\"a\",\"title\":\"Café “desk”\"}", + "canonicalHex": "7B22636F6E74657874223A224C696E65206F6E655C6E4C696E652074776F20E2809420F09F9880222C22646570656E6473223A5B5D2C226F7074696F6E73223A5B7B226964223A2261222C2274657874223A2278227D2C7B226964223A2262222C2274657874223A2279227D5D2C227265636F6D6D656E646174696F6E223A2261222C227469746C65223A22436166C3A920E2809C6465736BE2809D227D", + "sha256": "71d5c413478ad06aaa1cbdd9e91c53f0cb4cef1dbd4c51bf3d473bccc6a5a272" + }, + { + "name": "heimdall_F_option_id_changed", + "heimdall": "F", + "title": "Slot mark consent", + "context": "Should a slot mark be shared?", + "options": [ + { + "id": "share", + "text": "Share it." + }, + { + "id": "no", + "text": "Keep it local." + } + ], + "recommendation": "no", + "depends": [ + "gamecult-chat:question:file-zone-audience" + ], + "canonical": "{\"context\":\"Should a slot mark be shared?\",\"depends\":[\"gamecult-chat:question:file-zone-audience\"],\"options\":[{\"id\":\"share\",\"text\":\"Share it.\"},{\"id\":\"no\",\"text\":\"Keep it local.\"}],\"recommendation\":\"no\",\"title\":\"Slot mark consent\"}", + "canonicalHex": "7B22636F6E74657874223A2253686F756C64206120736C6F74206D61726B206265207368617265643F222C22646570656E6473223A5B2267616D6563756C742D636861743A7175657374696F6E3A66696C652D7A6F6E652D61756469656E6365225D2C226F7074696F6E73223A5B7B226964223A227368617265222C2274657874223A2253686172652069742E227D2C7B226964223A226E6F222C2274657874223A224B656570206974206C6F63616C2E227D5D2C227265636F6D6D656E646174696F6E223A226E6F222C227469746C65223A22536C6F74206D61726B20636F6E73656E74227D", + "sha256": "fb53aa3a9f7e74029f755d774064eedd240ab90d8fc64723f97c31b3d2846d40" + }, + { + "name": "heimdall_G_depends_emptied", + "heimdall": "G", + "title": "Slot mark consent", + "context": "Should a slot mark be shared?", + "options": [ + { + "id": "yes", + "text": "Share it." + }, + { + "id": "no", + "text": "Keep it local." + } + ], + "recommendation": "no", + "depends": [], + "canonical": "{\"context\":\"Should a slot mark be shared?\",\"depends\":[],\"options\":[{\"id\":\"yes\",\"text\":\"Share it.\"},{\"id\":\"no\",\"text\":\"Keep it local.\"}],\"recommendation\":\"no\",\"title\":\"Slot mark consent\"}", + "canonicalHex": "7B22636F6E74657874223A2253686F756C64206120736C6F74206D61726B206265207368617265643F222C22646570656E6473223A5B5D2C226F7074696F6E73223A5B7B226964223A22796573222C2274657874223A2253686172652069742E227D2C7B226964223A226E6F222C2274657874223A224B656570206974206C6F63616C2E227D5D2C227265636F6D6D656E646174696F6E223A226E6F222C227469746C65223A22536C6F74206D61726B20636F6E73656E74227D", + "sha256": "f856edce121db4121b5f1af400b1c471ebbfa6579a042aca9a34ae31c15f5c86" + }, + { + "name": "option_text_changed", + "heimdall": null, + "title": "Slot mark consent", + "context": "Should a slot mark be shared?", + "options": [ + { + "id": "yes", + "text": "Share it!" + }, + { + "id": "no", + "text": "Keep it local." + } + ], + "recommendation": "no", + "depends": [ + "gamecult-chat:question:file-zone-audience" + ], + "canonical": "{\"context\":\"Should a slot mark be shared?\",\"depends\":[\"gamecult-chat:question:file-zone-audience\"],\"options\":[{\"id\":\"yes\",\"text\":\"Share it!\"},{\"id\":\"no\",\"text\":\"Keep it local.\"}],\"recommendation\":\"no\",\"title\":\"Slot mark consent\"}", + "canonicalHex": "7B22636F6E74657874223A2253686F756C64206120736C6F74206D61726B206265207368617265643F222C22646570656E6473223A5B2267616D6563756C742D636861743A7175657374696F6E3A66696C652D7A6F6E652D61756469656E6365225D2C226F7074696F6E73223A5B7B226964223A22796573222C2274657874223A22536861726520697421227D2C7B226964223A226E6F222C2274657874223A224B656570206974206C6F63616C2E227D5D2C227265636F6D6D656E646174696F6E223A226E6F222C227469746C65223A22536C6F74206D61726B20636F6E73656E74227D", + "sha256": "9ce0d2770497fdd2d5338e66f9f1ae1741d87301db4c793407c25762929392b6" + }, + { + "name": "title_changed", + "heimdall": null, + "title": "Slot mark consent?", + "context": "Should a slot mark be shared?", + "options": [ + { + "id": "yes", + "text": "Share it." + }, + { + "id": "no", + "text": "Keep it local." + } + ], + "recommendation": "no", + "depends": [ + "gamecult-chat:question:file-zone-audience" + ], + "canonical": "{\"context\":\"Should a slot mark be shared?\",\"depends\":[\"gamecult-chat:question:file-zone-audience\"],\"options\":[{\"id\":\"yes\",\"text\":\"Share it.\"},{\"id\":\"no\",\"text\":\"Keep it local.\"}],\"recommendation\":\"no\",\"title\":\"Slot mark consent?\"}", + "canonicalHex": "7B22636F6E74657874223A2253686F756C64206120736C6F74206D61726B206265207368617265643F222C22646570656E6473223A5B2267616D6563756C742D636861743A7175657374696F6E3A66696C652D7A6F6E652D61756469656E6365225D2C226F7074696F6E73223A5B7B226964223A22796573222C2274657874223A2253686172652069742E227D2C7B226964223A226E6F222C2274657874223A224B656570206974206C6F63616C2E227D5D2C227265636F6D6D656E646174696F6E223A226E6F222C227469746C65223A22536C6F74206D61726B20636F6E73656E743F227D", + "sha256": "30fe0c5bc88bd21d64592a495316c30e80fcee6cacf5c63fd9a818ca5cf87d0f" + }, + { + "name": "context_changed", + "heimdall": null, + "title": "Slot mark consent", + "context": "Should slot marks be shared?", + "options": [ + { + "id": "yes", + "text": "Share it." + }, + { + "id": "no", + "text": "Keep it local." + } + ], + "recommendation": "no", + "depends": [ + "gamecult-chat:question:file-zone-audience" + ], + "canonical": "{\"context\":\"Should slot marks be shared?\",\"depends\":[\"gamecult-chat:question:file-zone-audience\"],\"options\":[{\"id\":\"yes\",\"text\":\"Share it.\"},{\"id\":\"no\",\"text\":\"Keep it local.\"}],\"recommendation\":\"no\",\"title\":\"Slot mark consent\"}", + "canonicalHex": "7B22636F6E74657874223A2253686F756C6420736C6F74206D61726B73206265207368617265643F222C22646570656E6473223A5B2267616D6563756C742D636861743A7175657374696F6E3A66696C652D7A6F6E652D61756469656E6365225D2C226F7074696F6E73223A5B7B226964223A22796573222C2274657874223A2253686172652069742E227D2C7B226964223A226E6F222C2274657874223A224B656570206974206C6F63616C2E227D5D2C227265636F6D6D656E646174696F6E223A226E6F222C227469746C65223A22536C6F74206D61726B20636F6E73656E74227D", + "sha256": "716de65096e34fd89cc92848b667d90f5c27eeb66929da3d340cabb80155e173" + }, + { + "name": "depends_order_changed", + "heimdall": null, + "title": "Slot mark consent", + "context": "Should a slot mark be shared?", + "options": [ + { + "id": "yes", + "text": "Share it." + }, + { + "id": "no", + "text": "Keep it local." + } + ], + "recommendation": "no", + "depends": [ + "gamecult-chat:question:file-zone-audience", + "x:question:y" + ], + "canonical": "{\"context\":\"Should a slot mark be shared?\",\"depends\":[\"gamecult-chat:question:file-zone-audience\",\"x:question:y\"],\"options\":[{\"id\":\"yes\",\"text\":\"Share it.\"},{\"id\":\"no\",\"text\":\"Keep it local.\"}],\"recommendation\":\"no\",\"title\":\"Slot mark consent\"}", + "canonicalHex": "7B22636F6E74657874223A2253686F756C64206120736C6F74206D61726B206265207368617265643F222C22646570656E6473223A5B2267616D6563756C742D636861743A7175657374696F6E3A66696C652D7A6F6E652D61756469656E6365222C22783A7175657374696F6E3A79225D2C226F7074696F6E73223A5B7B226964223A22796573222C2274657874223A2253686172652069742E227D2C7B226964223A226E6F222C2274657874223A224B656570206974206C6F63616C2E227D5D2C227265636F6D6D656E646174696F6E223A226E6F222C227469746C65223A22536C6F74206D61726B20636F6E73656E74227D", + "sha256": "fc3f651c6f498385256fc1232d5db31ece9dcc542e391f97245d27bc9e385b3d" + }, + { + "name": "depends_order_changed_reversed", + "heimdall": null, + "title": "Slot mark consent", + "context": "Should a slot mark be shared?", + "options": [ + { + "id": "yes", + "text": "Share it." + }, + { + "id": "no", + "text": "Keep it local." + } + ], + "recommendation": "no", + "depends": [ + "x:question:y", + "gamecult-chat:question:file-zone-audience" + ], + "canonical": "{\"context\":\"Should a slot mark be shared?\",\"depends\":[\"x:question:y\",\"gamecult-chat:question:file-zone-audience\"],\"options\":[{\"id\":\"yes\",\"text\":\"Share it.\"},{\"id\":\"no\",\"text\":\"Keep it local.\"}],\"recommendation\":\"no\",\"title\":\"Slot mark consent\"}", + "canonicalHex": "7B22636F6E74657874223A2253686F756C64206120736C6F74206D61726B206265207368617265643F222C22646570656E6473223A5B22783A7175657374696F6E3A79222C2267616D6563756C742D636861743A7175657374696F6E3A66696C652D7A6F6E652D61756469656E6365225D2C226F7074696F6E73223A5B7B226964223A22796573222C2274657874223A2253686172652069742E227D2C7B226964223A226E6F222C2274657874223A224B656570206974206C6F63616C2E227D5D2C227265636F6D6D656E646174696F6E223A226E6F222C227469746C65223A22536C6F74206D61726B20636F6E73656E74227D", + "sha256": "b22642e30f8d68839e3a0df5bc23b83ff50a839fcecf0366a65fa6e3cf873796" + }, + { + "name": "hidden_members_do_not_move_it", + "heimdall": null, + "title": "Slot mark consent", + "context": "Should a slot mark be shared?", + "options": [ + { + "id": "yes", + "text": "Share it." + }, + { + "id": "no", + "text": "Keep it local." + } + ], + "recommendation": "no", + "depends": [ + "gamecult-chat:question:file-zone-audience" + ], + "canonical": "{\"context\":\"Should a slot mark be shared?\",\"depends\":[\"gamecult-chat:question:file-zone-audience\"],\"options\":[{\"id\":\"yes\",\"text\":\"Share it.\"},{\"id\":\"no\",\"text\":\"Keep it local.\"}],\"recommendation\":\"no\",\"title\":\"Slot mark consent\"}", + "canonicalHex": "7B22636F6E74657874223A2253686F756C64206120736C6F74206D61726B206265207368617265643F222C22646570656E6473223A5B2267616D6563756C742D636861743A7175657374696F6E3A66696C652D7A6F6E652D61756469656E6365225D2C226F7074696F6E73223A5B7B226964223A22796573222C2274657874223A2253686172652069742E227D2C7B226964223A226E6F222C2274657874223A224B656570206974206C6F63616C2E227D5D2C227265636F6D6D656E646174696F6E223A226E6F222C227469746C65223A22536C6F74206D61726B20636F6E73656E74227D", + "sha256": "7d95fc87d532420611fe415c1def27a3447617bb68235f46e3af130aea361c48" + }, + { + "name": "control_characters_and_markup_are_escaped_minimally", + "heimdall": null, + "title": "q \" b \\ t \t n \n bell \u0007 del ", + "context": " 
 &", + "options": [ + { + "id": "yes", + "text": "Share it." + }, + { + "id": "no", + "text": "Keep it local." + } + ], + "recommendation": "no", + "depends": [ + "gamecult-chat:question:file-zone-audience" + ], + "canonical": "{\"context\":\" 
 &\",\"depends\":[\"gamecult-chat:question:file-zone-audience\"],\"options\":[{\"id\":\"yes\",\"text\":\"Share it.\"},{\"id\":\"no\",\"text\":\"Keep it local.\"}],\"recommendation\":\"no\",\"title\":\"q \\\" b \\\\ t \\t n \\n bell \\u0007 del \"}", + "canonicalHex": "7B22636F6E74657874223A223C2F7363726970743E20E280A82026222C22646570656E6473223A5B2267616D6563756C742D636861743A7175657374696F6E3A66696C652D7A6F6E652D61756469656E6365225D2C226F7074696F6E73223A5B7B226964223A22796573222C2274657874223A2253686172652069742E227D2C7B226964223A226E6F222C2274657874223A224B656570206974206C6F63616C2E227D5D2C227265636F6D6D656E646174696F6E223A226E6F222C227469746C65223A2271205C222062205C5C2074205C74206E205C6E2062656C6C205C75303030372064656C207F227D", + "sha256": "f2d5a09513ce728129f6ca13f7123aa75b2bb77541b1de8946f39239634b40fe" + }, + { + "name": "astral_option_id_and_text", + "heimdall": null, + "title": "Slot mark consent", + "context": "Should a slot mark be shared?", + "options": [ + { + "id": "😀", + "text": "𝄞" + }, + { + "id": "no", + "text": "Keep it local." + } + ], + "recommendation": "no", + "depends": [ + "gamecult-chat:question:file-zone-audience" + ], + "canonical": "{\"context\":\"Should a slot mark be shared?\",\"depends\":[\"gamecult-chat:question:file-zone-audience\"],\"options\":[{\"id\":\"😀\",\"text\":\"𝄞\"},{\"id\":\"no\",\"text\":\"Keep it local.\"}],\"recommendation\":\"no\",\"title\":\"Slot mark consent\"}", + "canonicalHex": "7B22636F6E74657874223A2253686F756C64206120736C6F74206D61726B206265207368617265643F222C22646570656E6473223A5B2267616D6563756C742D636861743A7175657374696F6E3A66696C652D7A6F6E652D61756469656E6365225D2C226F7074696F6E73223A5B7B226964223A22F09F9880222C2274657874223A22F09D849E227D2C7B226964223A226E6F222C2274657874223A224B656570206974206C6F63616C2E227D5D2C227265636F6D6D656E646174696F6E223A226E6F222C227469746C65223A22536C6F74206D61726B20636F6E73656E74227D", + "sha256": "836fbfce16127132bae100aa773d328a85ad08d241ae9bf47ea7f84766762cac" + } + ], + "card": { + "valid": [ + { + "name": "operator_fork", + "message": { + "schemaVersion": "gamecult.fork.card.v0", + "fork_id": "gamecult-chat:question:slot-mark-consent", + "title": "Slot mark consent", + "context": "Should a slot mark be shared?", + "options": [ + { + "id": "yes", + "text": "Share it." + }, + { + "id": "no", + "text": "Keep it local." + } + ], + "recommendation": "no", + "depends": [ + "gamecult-chat:question:file-zone-audience" + ], + "leans": null, + "flag": null, + "raised_in": { + "kind": "CutSpec", + "id": "cultlib-gaps:cut_spec:cut-slot-tolerance.r4" + }, + "deciders": [ + "e0d1c2b3-a495-4687-8879-6a5b4c3d2e1f" + ], + "status": "open", + "ruling": null, + "ruled_from": [], + "order": 3, + "options_hash": "7d95fc87d532420611fe415c1def27a3447617bb68235f46e3af130aea361c48" + }, + "messagePackHex": "DE0010AD736368656D6156657273696F6EB567616D6563756C742E666F726B2E636172642E7630A7666F726B5F6964D92867616D6563756C742D636861743A7175657374696F6E3A736C6F742D6D61726B2D636F6E73656E74A57469746C65B1536C6F74206D61726B20636F6E73656E74A7636F6E74657874BD53686F756C64206120736C6F74206D61726B206265207368617265643FA76F7074696F6E739282A26964A3796573A474657874A953686172652069742E82A26964A26E6FA474657874AE4B656570206974206C6F63616C2EAE7265636F6D6D656E646174696F6EA26E6FA7646570656E647391D92967616D6563756C742D636861743A7175657374696F6E3A66696C652D7A6F6E652D61756469656E6365A56C65616E73C0A4666C6167C0A97261697365645F696E82A46B696E64A743757453706563A26964D92B63756C746C69622D676170733A6375745F737065633A6375742D736C6F742D746F6C6572616E63652E7234A8646563696465727391D92465306431633262332D613439352D343638372D383837392D366135623463336432653166A6737461747573A46F70656EA672756C696E67C0AA72756C65645F66726F6D90A56F7264657203AC6F7074696F6E735F68617368D94037643935666338376435333234323036313166653431356331646566323761333434373631376262363832333566343665336166313330616561333631633438" + }, + { + "name": "leans_flag_closed_two_deciders", + "message": { + "schemaVersion": "gamecult.fork.card.v0", + "fork_id": "gamecult-chat:question:slot-mark-consent", + "title": "Slot mark consent", + "context": "Should a slot mark be shared?", + "options": [ + { + "id": "yes", + "text": "Share it." + }, + { + "id": "no", + "text": "Keep it local." + } + ], + "recommendation": "no", + "depends": [], + "leans": "yes", + "flag": "provisional: Self expects this to move", + "raised_in": null, + "deciders": [ + "e0d1c2b3-a495-4687-8879-6a5b4c3d2e1f", + "9f8e7d6c-5b4a-4392-8170-0f1e2d3c4b5a" + ], + "status": "closed", + "ruling": "x:resolution:y", + "ruled_from": [ + "n1" + ], + "order": 0, + "options_hash": "f856edce121db4121b5f1af400b1c471ebbfa6579a042aca9a34ae31c15f5c86" + }, + "messagePackHex": "DE0010AD736368656D6156657273696F6EB567616D6563756C742E666F726B2E636172642E7630A7666F726B5F6964D92867616D6563756C742D636861743A7175657374696F6E3A736C6F742D6D61726B2D636F6E73656E74A57469746C65B1536C6F74206D61726B20636F6E73656E74A7636F6E74657874BD53686F756C64206120736C6F74206D61726B206265207368617265643FA76F7074696F6E739282A26964A3796573A474657874A953686172652069742E82A26964A26E6FA474657874AE4B656570206974206C6F63616C2EAE7265636F6D6D656E646174696F6EA26E6FA7646570656E647390A56C65616E73A3796573A4666C6167D92670726F766973696F6E616C3A2053656C662065787065637473207468697320746F206D6F7665A97261697365645F696EC0A8646563696465727392D92465306431633262332D613439352D343638372D383837392D366135623463336432653166D92439663865376436632D356234612D343339322D383137302D306631653264336334623561A6737461747573A6636C6F736564A672756C696E67AE783A7265736F6C7574696F6E3A79AA72756C65645F66726F6D91A26E31A56F7264657200AC6F7074696F6E735F68617368D94066383536656463653132316462343132316235663161663430306231633437316562626661363537396130343261636139613334616533316331356635633836" + }, + { + "name": "closed_without_a_ruling_the_question_was_withdrawn", + "message": { + "schemaVersion": "gamecult.fork.card.v0", + "fork_id": "gamecult-chat:question:slot-mark-consent", + "title": "Slot mark consent", + "context": "Should a slot mark be shared?", + "options": [ + { + "id": "yes", + "text": "Share it." + }, + { + "id": "no", + "text": "Keep it local." + } + ], + "recommendation": "no", + "depends": [ + "gamecult-chat:question:file-zone-audience" + ], + "leans": null, + "flag": null, + "raised_in": { + "kind": "CutSpec", + "id": "cultlib-gaps:cut_spec:cut-slot-tolerance.r4" + }, + "deciders": [ + "e0d1c2b3-a495-4687-8879-6a5b4c3d2e1f" + ], + "status": "closed", + "ruling": null, + "ruled_from": [], + "order": 3, + "options_hash": "7d95fc87d532420611fe415c1def27a3447617bb68235f46e3af130aea361c48" + }, + "messagePackHex": "DE0010AD736368656D6156657273696F6EB567616D6563756C742E666F726B2E636172642E7630A7666F726B5F6964D92867616D6563756C742D636861743A7175657374696F6E3A736C6F742D6D61726B2D636F6E73656E74A57469746C65B1536C6F74206D61726B20636F6E73656E74A7636F6E74657874BD53686F756C64206120736C6F74206D61726B206265207368617265643FA76F7074696F6E739282A26964A3796573A474657874A953686172652069742E82A26964A26E6FA474657874AE4B656570206974206C6F63616C2EAE7265636F6D6D656E646174696F6EA26E6FA7646570656E647391D92967616D6563756C742D636861743A7175657374696F6E3A66696C652D7A6F6E652D61756469656E6365A56C65616E73C0A4666C6167C0A97261697365645F696E82A46B696E64A743757453706563A26964D92B63756C746C69622D676170733A6375745F737065633A6375742D736C6F742D746F6C6572616E63652E7234A8646563696465727391D92465306431633262332D613439352D343638372D383837392D366135623463336432653166A6737461747573A6636C6F736564A672756C696E67C0AA72756C65645F66726F6D90A56F7264657203AC6F7074696F6E735F68617368D94037643935666338376435333234323036313166653431356331646566323761333434373631376262363832333566343665336166313330616561333631633438" + } + ], + "invalid": [ + { + "name": "one_option", + "why": "two or more options", + "message": { + "schemaVersion": "gamecult.fork.card.v0", + "fork_id": "gamecult-chat:question:slot-mark-consent", + "title": "Slot mark consent", + "context": "Should a slot mark be shared?", + "options": [ + { + "id": "yes", + "text": "Share it." + } + ], + "recommendation": "yes", + "depends": [ + "gamecult-chat:question:file-zone-audience" + ], + "leans": null, + "flag": null, + "raised_in": { + "kind": "CutSpec", + "id": "cultlib-gaps:cut_spec:cut-slot-tolerance.r4" + }, + "deciders": [ + "e0d1c2b3-a495-4687-8879-6a5b4c3d2e1f" + ], + "status": "open", + "ruling": null, + "ruled_from": [], + "order": 3, + "options_hash": "950f1ed77895e730a7e456e5d17801385b30b8b67d7185d977d0d0a6c6c6b5c2" + } + }, + { + "name": "no_deciders", + "why": "a fork with no decider cannot be answered", + "message": { + "schemaVersion": "gamecult.fork.card.v0", + "fork_id": "gamecult-chat:question:slot-mark-consent", + "title": "Slot mark consent", + "context": "Should a slot mark be shared?", + "options": [ + { + "id": "yes", + "text": "Share it." + }, + { + "id": "no", + "text": "Keep it local." + } + ], + "recommendation": "no", + "depends": [ + "gamecult-chat:question:file-zone-audience" + ], + "leans": null, + "flag": null, + "raised_in": { + "kind": "CutSpec", + "id": "cultlib-gaps:cut_spec:cut-slot-tolerance.r4" + }, + "deciders": [], + "status": "open", + "ruling": null, + "ruled_from": [], + "order": 3, + "options_hash": "7d95fc87d532420611fe415c1def27a3447617bb68235f46e3af130aea361c48" + } + }, + { + "name": "null_recommendation", + "why": "every card recommends an option", + "message": { + "schemaVersion": "gamecult.fork.card.v0", + "fork_id": "gamecult-chat:question:slot-mark-consent", + "title": "Slot mark consent", + "context": "Should a slot mark be shared?", + "options": [ + { + "id": "yes", + "text": "Share it." + }, + { + "id": "no", + "text": "Keep it local." + } + ], + "recommendation": null, + "depends": [ + "gamecult-chat:question:file-zone-audience" + ], + "leans": null, + "flag": null, + "raised_in": { + "kind": "CutSpec", + "id": "cultlib-gaps:cut_spec:cut-slot-tolerance.r4" + }, + "deciders": [ + "e0d1c2b3-a495-4687-8879-6a5b4c3d2e1f" + ], + "status": "open", + "ruling": null, + "ruled_from": [], + "order": 3, + "options_hash": "7d95fc87d532420611fe415c1def27a3447617bb68235f46e3af130aea361c48" + } + }, + { + "name": "unknown_key", + "why": "closed document", + "message": { + "schemaVersion": "gamecult.fork.card.v0", + "fork_id": "gamecult-chat:question:slot-mark-consent", + "title": "Slot mark consent", + "context": "Should a slot mark be shared?", + "options": [ + { + "id": "yes", + "text": "Share it." + }, + { + "id": "no", + "text": "Keep it local." + } + ], + "recommendation": "no", + "depends": [ + "gamecult-chat:question:file-zone-audience" + ], + "leans": null, + "flag": null, + "raised_in": { + "kind": "CutSpec", + "id": "cultlib-gaps:cut_spec:cut-slot-tolerance.r4" + }, + "deciders": [ + "e0d1c2b3-a495-4687-8879-6a5b4c3d2e1f" + ], + "status": "open", + "ruling": null, + "ruled_from": [], + "order": 3, + "options_hash": "7d95fc87d532420611fe415c1def27a3447617bb68235f46e3af130aea361c48", + "note": "x" + } + }, + { + "name": "option_with_a_label", + "why": "options are {id,text}; the old format's display label is not a member (fork-card.md, Old format)", + "message": { + "schemaVersion": "gamecult.fork.card.v0", + "fork_id": "gamecult-chat:question:slot-mark-consent", + "title": "Slot mark consent", + "context": "Should a slot mark be shared?", + "options": [ + { + "id": "yes", + "text": "Share it.", + "label": "Yes" + }, + { + "id": "no", + "text": "Keep it local." + } + ], + "recommendation": "no", + "depends": [ + "gamecult-chat:question:file-zone-audience" + ], + "leans": null, + "flag": null, + "raised_in": { + "kind": "CutSpec", + "id": "cultlib-gaps:cut_spec:cut-slot-tolerance.r4" + }, + "deciders": [ + "e0d1c2b3-a495-4687-8879-6a5b4c3d2e1f" + ], + "status": "open", + "ruling": null, + "ruled_from": [], + "order": 3, + "options_hash": "7d95fc87d532420611fe415c1def27a3447617bb68235f46e3af130aea361c48" + } + }, + { + "name": "status_unknown", + "why": "status is open or closed", + "message": { + "schemaVersion": "gamecult.fork.card.v0", + "fork_id": "gamecult-chat:question:slot-mark-consent", + "title": "Slot mark consent", + "context": "Should a slot mark be shared?", + "options": [ + { + "id": "yes", + "text": "Share it." + }, + { + "id": "no", + "text": "Keep it local." + } + ], + "recommendation": "no", + "depends": [ + "gamecult-chat:question:file-zone-audience" + ], + "leans": null, + "flag": null, + "raised_in": { + "kind": "CutSpec", + "id": "cultlib-gaps:cut_spec:cut-slot-tolerance.r4" + }, + "deciders": [ + "e0d1c2b3-a495-4687-8879-6a5b4c3d2e1f" + ], + "status": "provisional", + "ruling": null, + "ruled_from": [], + "order": 3, + "options_hash": "7d95fc87d532420611fe415c1def27a3447617bb68235f46e3af130aea361c48" + } + }, + { + "name": "options_hash_not_hex", + "why": "options_hash is 64 lowercase hex", + "message": { + "schemaVersion": "gamecult.fork.card.v0", + "fork_id": "gamecult-chat:question:slot-mark-consent", + "title": "Slot mark consent", + "context": "Should a slot mark be shared?", + "options": [ + { + "id": "yes", + "text": "Share it." + }, + { + "id": "no", + "text": "Keep it local." + } + ], + "recommendation": "no", + "depends": [ + "gamecult-chat:question:file-zone-audience" + ], + "leans": null, + "flag": null, + "raised_in": { + "kind": "CutSpec", + "id": "cultlib-gaps:cut_spec:cut-slot-tolerance.r4" + }, + "deciders": [ + "e0d1c2b3-a495-4687-8879-6a5b4c3d2e1f" + ], + "status": "open", + "ruling": null, + "ruled_from": [], + "order": 3, + "options_hash": "XYZ" + } + }, + { + "name": "duplicate_option_ids", + "why": "rule: option ids are unique (not expressible in the schema)", + "message": { + "schemaVersion": "gamecult.fork.card.v0", + "fork_id": "gamecult-chat:question:slot-mark-consent", + "title": "Slot mark consent", + "context": "Should a slot mark be shared?", + "options": [ + { + "id": "yes", + "text": "Share it." + }, + { + "id": "yes", + "text": "Keep it local." + } + ], + "recommendation": "no", + "depends": [ + "gamecult-chat:question:file-zone-audience" + ], + "leans": null, + "flag": null, + "raised_in": { + "kind": "CutSpec", + "id": "cultlib-gaps:cut_spec:cut-slot-tolerance.r4" + }, + "deciders": [ + "e0d1c2b3-a495-4687-8879-6a5b4c3d2e1f" + ], + "status": "open", + "ruling": null, + "ruled_from": [], + "order": 3, + "options_hash": "d5aaa1c4c0e464c2015044f907fea8f6b7100ff7181677d14ce0982e059aac40" + } + }, + { + "name": "recommendation_not_an_option", + "why": "rule: recommendation is an option id (not expressible in the schema)", + "message": { + "schemaVersion": "gamecult.fork.card.v0", + "fork_id": "gamecult-chat:question:slot-mark-consent", + "title": "Slot mark consent", + "context": "Should a slot mark be shared?", + "options": [ + { + "id": "yes", + "text": "Share it." + }, + { + "id": "no", + "text": "Keep it local." + } + ], + "recommendation": "maybe", + "depends": [ + "gamecult-chat:question:file-zone-audience" + ], + "leans": null, + "flag": null, + "raised_in": { + "kind": "CutSpec", + "id": "cultlib-gaps:cut_spec:cut-slot-tolerance.r4" + }, + "deciders": [ + "e0d1c2b3-a495-4687-8879-6a5b4c3d2e1f" + ], + "status": "open", + "ruling": null, + "ruled_from": [], + "order": 3, + "options_hash": "9077264ad75c31722641565acd76aab2dc248560aa8e90210973553414aba3f5" + } + }, + { + "name": "leans_not_an_option", + "why": "rule: leans is nil or an option id (not expressible in the schema)", + "message": { + "schemaVersion": "gamecult.fork.card.v0", + "fork_id": "gamecult-chat:question:slot-mark-consent", + "title": "Slot mark consent", + "context": "Should a slot mark be shared?", + "options": [ + { + "id": "yes", + "text": "Share it." + }, + { + "id": "no", + "text": "Keep it local." + } + ], + "recommendation": "no", + "depends": [ + "gamecult-chat:question:file-zone-audience" + ], + "leans": "maybe", + "flag": null, + "raised_in": { + "kind": "CutSpec", + "id": "cultlib-gaps:cut_spec:cut-slot-tolerance.r4" + }, + "deciders": [ + "e0d1c2b3-a495-4687-8879-6a5b4c3d2e1f" + ], + "status": "open", + "ruling": null, + "ruled_from": [], + "order": 3, + "options_hash": "7d95fc87d532420611fe415c1def27a3447617bb68235f46e3af130aea361c48" + } + }, + { + "name": "duplicate_deciders", + "why": "rule: deciders are unique (not expressible in the schema)", + "message": { + "schemaVersion": "gamecult.fork.card.v0", + "fork_id": "gamecult-chat:question:slot-mark-consent", + "title": "Slot mark consent", + "context": "Should a slot mark be shared?", + "options": [ + { + "id": "yes", + "text": "Share it." + }, + { + "id": "no", + "text": "Keep it local." + } + ], + "recommendation": "no", + "depends": [ + "gamecult-chat:question:file-zone-audience" + ], + "leans": null, + "flag": null, + "raised_in": { + "kind": "CutSpec", + "id": "cultlib-gaps:cut_spec:cut-slot-tolerance.r4" + }, + "deciders": [ + "e0d1c2b3-a495-4687-8879-6a5b4c3d2e1f", + "e0d1c2b3-a495-4687-8879-6a5b4c3d2e1f" + ], + "status": "open", + "ruling": null, + "ruled_from": [], + "order": 3, + "options_hash": "7d95fc87d532420611fe415c1def27a3447617bb68235f46e3af130aea361c48" + } + }, + { + "name": "ruled_from_on_an_open_card", + "why": "rule: a ruling that answers a fork closes it, so an open card cites no answer in ruled_from (not expressible in the schema)", + "message": { + "schemaVersion": "gamecult.fork.card.v0", + "fork_id": "gamecult-chat:question:slot-mark-consent", + "title": "Slot mark consent", + "context": "Should a slot mark be shared?", + "options": [ + { + "id": "yes", + "text": "Share it." + }, + { + "id": "no", + "text": "Keep it local." + } + ], + "recommendation": "no", + "depends": [ + "gamecult-chat:question:file-zone-audience" + ], + "leans": null, + "flag": null, + "raised_in": { + "kind": "CutSpec", + "id": "cultlib-gaps:cut_spec:cut-slot-tolerance.r4" + }, + "deciders": [ + "e0d1c2b3-a495-4687-8879-6a5b4c3d2e1f" + ], + "status": "open", + "ruling": null, + "ruled_from": [ + "n1" + ], + "order": 3, + "options_hash": "7d95fc87d532420611fe415c1def27a3447617bb68235f46e3af130aea361c48" + } + }, + { + "name": "open_card_names_a_ruling", + "why": "rule: only a closed card names a ruling (not expressible in the schema)", + "message": { + "schemaVersion": "gamecult.fork.card.v0", + "fork_id": "gamecult-chat:question:slot-mark-consent", + "title": "Slot mark consent", + "context": "Should a slot mark be shared?", + "options": [ + { + "id": "yes", + "text": "Share it." + }, + { + "id": "no", + "text": "Keep it local." + } + ], + "recommendation": "no", + "depends": [ + "gamecult-chat:question:file-zone-audience" + ], + "leans": null, + "flag": null, + "raised_in": { + "kind": "CutSpec", + "id": "cultlib-gaps:cut_spec:cut-slot-tolerance.r4" + }, + "deciders": [ + "e0d1c2b3-a495-4687-8879-6a5b4c3d2e1f" + ], + "status": "open", + "ruling": "cultlib-gaps:resolution:question.slot-mark-consent.n1", + "ruled_from": [], + "order": 3, + "options_hash": "7d95fc87d532420611fe415c1def27a3447617bb68235f46e3af130aea361c48" + } + }, + { + "name": "ruled_from_without_a_ruling", + "why": "rule: ruled_from lists answers a ruling was admitted from, so it needs the ruling (not expressible in the schema)", + "message": { + "schemaVersion": "gamecult.fork.card.v0", + "fork_id": "gamecult-chat:question:slot-mark-consent", + "title": "Slot mark consent", + "context": "Should a slot mark be shared?", + "options": [ + { + "id": "yes", + "text": "Share it." + }, + { + "id": "no", + "text": "Keep it local." + } + ], + "recommendation": "no", + "depends": [ + "gamecult-chat:question:file-zone-audience" + ], + "leans": null, + "flag": null, + "raised_in": { + "kind": "CutSpec", + "id": "cultlib-gaps:cut_spec:cut-slot-tolerance.r4" + }, + "deciders": [ + "e0d1c2b3-a495-4687-8879-6a5b4c3d2e1f" + ], + "status": "closed", + "ruling": null, + "ruled_from": [ + "n1" + ], + "order": 3, + "options_hash": "7d95fc87d532420611fe415c1def27a3447617bb68235f46e3af130aea361c48" + } + }, + { + "name": "options_hash_of_another_card", + "why": "rule: options_hash equals the hash of the shown members (not expressible in the schema)", + "message": { + "schemaVersion": "gamecult.fork.card.v0", + "fork_id": "gamecult-chat:question:slot-mark-consent", + "title": "Slot mark consent", + "context": "Should a slot mark be shared?", + "options": [ + { + "id": "yes", + "text": "Share it." + }, + { + "id": "no", + "text": "Keep it local." + } + ], + "recommendation": "no", + "depends": [ + "gamecult-chat:question:file-zone-audience" + ], + "leans": null, + "flag": null, + "raised_in": { + "kind": "CutSpec", + "id": "cultlib-gaps:cut_spec:cut-slot-tolerance.r4" + }, + "deciders": [ + "e0d1c2b3-a495-4687-8879-6a5b4c3d2e1f" + ], + "status": "open", + "ruling": null, + "ruled_from": [], + "order": 3, + "options_hash": "14b8ffbdb61106dbc06f69f3fc8e2072af7a205d766e4cc040e89733013d54dc" + } + } + ] + }, + "request": { + "valid": [ + { + "name": "choice_and_note", + "message": { + "schemaVersion": "gamecult.fork.answer_request.v0", + "fork_id": "gamecult-chat:question:slot-mark-consent", + "options_hash": "7d95fc87d532420611fe415c1def27a3447617bb68235f46e3af130aea361c48", + "answer": { + "choice": "no", + "note": "Keep it local: a slot mark is the author's call, and sharing it is a separate act." + }, + "retracted": false, + "retracts": null, + "answered_at": "2026-11-12T19:02:00Z", + "request_id": "00000001-0000-4000-8000-000000000001" + }, + "messagePackHex": "88AD736368656D6156657273696F6EBF67616D6563756C742E666F726B2E616E737765725F726571756573742E7630A7666F726B5F6964D92867616D6563756C742D636861743A7175657374696F6E3A736C6F742D6D61726B2D636F6E73656E74AC6F7074696F6E735F68617368D94037643935666338376435333234323036313166653431356331646566323761333434373631376262363832333566343665336166313330616561333631633438A6616E7377657282A663686F696365A26E6FA46E6F7465D9524B656570206974206C6F63616C3A206120736C6F74206D61726B2069732074686520617574686F7227732063616C6C2C20616E642073686172696E672069742069732061207365706172617465206163742EA9726574726163746564C2A87265747261637473C0AB616E7377657265645F6174B4323032362D31312D31325431393A30323A30305AAA726571756573745F6964D92430303030303030312D303030302D343030302D383030302D303030303030303030303031" + }, + { + "name": "choice_alone", + "message": { + "schemaVersion": "gamecult.fork.answer_request.v0", + "fork_id": "gamecult-chat:question:slot-mark-consent", + "options_hash": "7d95fc87d532420611fe415c1def27a3447617bb68235f46e3af130aea361c48", + "answer": { + "choice": "no", + "note": null + }, + "retracted": false, + "retracts": null, + "answered_at": "2026-11-12T19:02:00Z", + "request_id": "00000005-0000-4000-8000-000000000005" + }, + "messagePackHex": "88AD736368656D6156657273696F6EBF67616D6563756C742E666F726B2E616E737765725F726571756573742E7630A7666F726B5F6964D92867616D6563756C742D636861743A7175657374696F6E3A736C6F742D6D61726B2D636F6E73656E74AC6F7074696F6E735F68617368D94037643935666338376435333234323036313166653431356331646566323761333434373631376262363832333566343665336166313330616561333631633438A6616E7377657282A663686F696365A26E6FA46E6F7465C0A9726574726163746564C2A87265747261637473C0AB616E7377657265645F6174B4323032362D31312D31325431393A30323A30305AAA726571756573745F6964D92430303030303030352D303030302D343030302D383030302D303030303030303030303035" + }, + { + "name": "note_alone_clear_pick", + "message": { + "schemaVersion": "gamecult.fork.answer_request.v0", + "fork_id": "gamecult-chat:question:slot-mark-consent", + "options_hash": "7d95fc87d532420611fe415c1def27a3447617bb68235f46e3af130aea361c48", + "answer": { + "choice": null, + "note": "Keep it local: a slot mark is the author's call, and sharing it is a separate act." + }, + "retracted": false, + "retracts": null, + "answered_at": "2026-11-12T19:02:00Z", + "request_id": "00000006-0000-4000-8000-000000000006" + }, + "messagePackHex": "88AD736368656D6156657273696F6EBF67616D6563756C742E666F726B2E616E737765725F726571756573742E7630A7666F726B5F6964D92867616D6563756C742D636861743A7175657374696F6E3A736C6F742D6D61726B2D636F6E73656E74AC6F7074696F6E735F68617368D94037643935666338376435333234323036313166653431356331646566323761333434373631376262363832333566343665336166313330616561333631633438A6616E7377657282A663686F696365C0A46E6F7465D9524B656570206974206C6F63616C3A206120736C6F74206D61726B2069732074686520617574686F7227732063616C6C2C20616E642073686172696E672069742069732061207365706172617465206163742EA9726574726163746564C2A87265747261637473C0AB616E7377657265645F6174B4323032362D31312D31325431393A30323A30305AAA726571756573745F6964D92430303030303030362D303030302D343030302D383030302D303030303030303030303036" + }, + { + "name": "retraction", + "message": { + "schemaVersion": "gamecult.fork.answer_request.v0", + "fork_id": "gamecult-chat:question:slot-mark-consent", + "options_hash": "7d95fc87d532420611fe415c1def27a3447617bb68235f46e3af130aea361c48", + "answer": null, + "retracted": true, + "retracts": "n1", + "answered_at": "2026-11-12T19:02:00Z", + "request_id": "00000022-0000-4000-8000-000000000022" + }, + "messagePackHex": "88AD736368656D6156657273696F6EBF67616D6563756C742E666F726B2E616E737765725F726571756573742E7630A7666F726B5F6964D92867616D6563756C742D636861743A7175657374696F6E3A736C6F742D6D61726B2D636F6E73656E74AC6F7074696F6E735F68617368D94037643935666338376435333234323036313166653431356331646566323761333434373631376262363832333566343665336166313330616561333631633438A6616E73776572C0A9726574726163746564C3A87265747261637473A26E31AB616E7377657265645F6174B4323032362D31312D31325431393A30323A30305AAA726571756573745F6964D92430303030303032322D303030302D343030302D383030302D303030303030303030303232" + }, + { + "name": "note_4000_ascii_bytes", + "message": { + "schemaVersion": "gamecult.fork.answer_request.v0", + "fork_id": "gamecult-chat:question:slot-mark-consent", + "options_hash": "7d95fc87d532420611fe415c1def27a3447617bb68235f46e3af130aea361c48", + "answer": { + "choice": null, + "note": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" + }, + "retracted": false, + "retracts": null, + "answered_at": "2026-11-12T19:02:00Z", + "request_id": "00000023-0000-4000-8000-000000000023" + }, + "messagePackHex": "88AD736368656D6156657273696F6EBF67616D6563756C742E666F726B2E616E737765725F726571756573742E7630A7666F726B5F6964D92867616D6563756C742D636861743A7175657374696F6E3A736C6F742D6D61726B2D636F6E73656E74AC6F7074696F6E735F68617368D94037643935666338376435333234323036313166653431356331646566323761333434373631376262363832333566343665336166313330616561333631633438A6616E7377657282A663686F696365C0A46E6F7465DA0FA078787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878A9726574726163746564C2A87265747261637473C0AB616E7377657265645F6174B4323032362D31312D31325431393A30323A30305AAA726571756573745F6964D92430303030303032332D303030302D343030302D383030302D303030303030303030303233" + }, + { + "name": "note_astral_4000_bytes", + "message": { + "schemaVersion": "gamecult.fork.answer_request.v0", + "fork_id": "gamecult-chat:question:slot-mark-consent", + "options_hash": "7d95fc87d532420611fe415c1def27a3447617bb68235f46e3af130aea361c48", + "answer": { + "choice": null, + "note": "😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀" + }, + "retracted": false, + "retracts": null, + "answered_at": "2026-11-12T19:02:00Z", + "request_id": "00000024-0000-4000-8000-000000000024" + }, + "messagePackHex": "88AD736368656D6156657273696F6EBF67616D6563756C742E666F726B2E616E737765725F726571756573742E7630A7666F726B5F6964D92867616D6563756C742D636861743A7175657374696F6E3A736C6F742D6D61726B2D636F6E73656E74AC6F7074696F6E735F68617368D94037643935666338376435333234323036313166653431356331646566323761333434373631376262363832333566343665336166313330616561333631633438A6616E7377657282A663686F696365C0A46E6F7465DA0FA0F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880A9726574726163746564C2A87265747261637473C0AB616E7377657265645F6174B4323032362D31312D31325431393A30323A30305AAA726571756573745F6964D92430303030303032342D303030302D343030302D383030302D303030303030303030303234" + }, + { + "name": "note_combining_4000_bytes", + "message": { + "schemaVersion": "gamecult.fork.answer_request.v0", + "fork_id": "gamecult-chat:question:slot-mark-consent", + "options_hash": "7d95fc87d532420611fe415c1def27a3447617bb68235f46e3af130aea361c48", + "answer": { + "choice": null, + "note": "éééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééx" + }, + "retracted": false, + "retracts": null, + "answered_at": "2026-11-12T19:02:00Z", + "request_id": "00000025-0000-4000-8000-000000000025" + }, + "messagePackHex": "88AD736368656D6156657273696F6EBF67616D6563756C742E666F726B2E616E737765725F726571756573742E7630A7666F726B5F6964D92867616D6563756C742D636861743A7175657374696F6E3A736C6F742D6D61726B2D636F6E73656E74AC6F7074696F6E735F68617368D94037643935666338376435333234323036313166653431356331646566323761333434373631376262363832333566343665336166313330616561333631633438A6616E7377657282A663686F696365C0A46E6F7465DA0FA065CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8178A9726574726163746564C2A87265747261637473C0AB616E7377657265645F6174B4323032362D31312D31325431393A30323A30305AAA726571756573745F6964D92430303030303032352D303030302D343030302D383030302D303030303030303030303235" + } + ], + "invalid": [ + { + "name": "answer_with_neither", + "why": "an answer object holds a choice, a note, or both", + "message": { + "schemaVersion": "gamecult.fork.answer_request.v0", + "fork_id": "gamecult-chat:question:slot-mark-consent", + "options_hash": "7d95fc87d532420611fe415c1def27a3447617bb68235f46e3af130aea361c48", + "answer": { + "choice": null, + "note": null + }, + "retracted": false, + "retracts": null, + "answered_at": "2026-11-12T19:02:00Z", + "request_id": "00000026-0000-4000-8000-000000000026" + }, + "messagePackHex": "88AD736368656D6156657273696F6EBF67616D6563756C742E666F726B2E616E737765725F726571756573742E7630A7666F726B5F6964D92867616D6563756C742D636861743A7175657374696F6E3A736C6F742D6D61726B2D636F6E73656E74AC6F7074696F6E735F68617368D94037643935666338376435333234323036313166653431356331646566323761333434373631376262363832333566343665336166313330616561333631633438A6616E7377657282A663686F696365C0A46E6F7465C0A9726574726163746564C2A87265747261637473C0AB616E7377657265645F6174B4323032362D31312D31325431393A30323A30305AAA726571756573745F6964D92430303030303032362D303030302D343030302D383030302D303030303030303030303236" + }, + { + "name": "blank_note", + "why": "a blank note is no answer", + "message": { + "schemaVersion": "gamecult.fork.answer_request.v0", + "fork_id": "gamecult-chat:question:slot-mark-consent", + "options_hash": "7d95fc87d532420611fe415c1def27a3447617bb68235f46e3af130aea361c48", + "answer": { + "choice": null, + "note": "   " + }, + "retracted": false, + "retracts": null, + "answered_at": "2026-11-12T19:02:00Z", + "request_id": "00000027-0000-4000-8000-000000000027" + }, + "messagePackHex": "88AD736368656D6156657273696F6EBF67616D6563756C742E666F726B2E616E737765725F726571756573742E7630A7666F726B5F6964D92867616D6563756C742D636861743A7175657374696F6E3A736C6F742D6D61726B2D636F6E73656E74AC6F7074696F6E735F68617368D94037643935666338376435333234323036313166653431356331646566323761333434373631376262363832333566343665336166313330616561333631633438A6616E7377657282A663686F696365C0A46E6F7465A52020C2A020A9726574726163746564C2A87265747261637473C0AB616E7377657265645F6174B4323032362D31312D31325431393A30323A30305AAA726571756573745F6964D92430303030303032372D303030302D343030302D383030302D303030303030303030303237" + }, + { + "name": "request_id_missing", + "why": "map-mode writes every key; a retry needs the id", + "message": { + "schemaVersion": "gamecult.fork.answer_request.v0", + "fork_id": "gamecult-chat:question:slot-mark-consent", + "options_hash": "7d95fc87d532420611fe415c1def27a3447617bb68235f46e3af130aea361c48", + "answer": { + "choice": "no", + "note": "Keep it local: a slot mark is the author's call, and sharing it is a separate act." + }, + "retracted": false, + "retracts": null, + "answered_at": "2026-11-12T19:02:00Z" + }, + "messagePackHex": "87AD736368656D6156657273696F6EBF67616D6563756C742E666F726B2E616E737765725F726571756573742E7630A7666F726B5F6964D92867616D6563756C742D636861743A7175657374696F6E3A736C6F742D6D61726B2D636F6E73656E74AC6F7074696F6E735F68617368D94037643935666338376435333234323036313166653431356331646566323761333434373631376262363832333566343665336166313330616561333631633438A6616E7377657282A663686F696365A26E6FA46E6F7465D9524B656570206974206C6F63616C3A206120736C6F74206D61726B2069732074686520617574686F7227732063616C6C2C20616E642073686172696E672069742069732061207365706172617465206163742EA9726574726163746564C2A87265747261637473C0AB616E7377657265645F6174B4323032362D31312D31325431393A30323A30305A" + }, + { + "name": "request_id_uppercase", + "why": "request_id is lowercase", + "message": { + "schemaVersion": "gamecult.fork.answer_request.v0", + "fork_id": "gamecult-chat:question:slot-mark-consent", + "options_hash": "7d95fc87d532420611fe415c1def27a3447617bb68235f46e3af130aea361c48", + "answer": { + "choice": "no", + "note": "Keep it local: a slot mark is the author's call, and sharing it is a separate act." + }, + "retracted": false, + "retracts": null, + "answered_at": "2026-11-12T19:02:00Z", + "request_id": "0000000A-0000-4000-8000-00000000000B" + }, + "messagePackHex": "88AD736368656D6156657273696F6EBF67616D6563756C742E666F726B2E616E737765725F726571756573742E7630A7666F726B5F6964D92867616D6563756C742D636861743A7175657374696F6E3A736C6F742D6D61726B2D636F6E73656E74AC6F7074696F6E735F68617368D94037643935666338376435333234323036313166653431356331646566323761333434373631376262363832333566343665336166313330616561333631633438A6616E7377657282A663686F696365A26E6FA46E6F7465D9524B656570206974206C6F63616C3A206120736C6F74206D61726B2069732074686520617574686F7227732063616C6C2C20616E642073686172696E672069742069732061207365706172617465206163742EA9726574726163746564C2A87265747261637473C0AB616E7377657265645F6174B4323032362D31312D31325431393A30323A30305AAA726571756573745F6964D92430303030303030412D303030302D343030302D383030302D303030303030303030303042" + }, + { + "name": "request_id_not_a_uuid", + "why": "request_id is a lowercase D-format UUID", + "message": { + "schemaVersion": "gamecult.fork.answer_request.v0", + "fork_id": "gamecult-chat:question:slot-mark-consent", + "options_hash": "7d95fc87d532420611fe415c1def27a3447617bb68235f46e3af130aea361c48", + "answer": { + "choice": "no", + "note": "Keep it local: a slot mark is the author's call, and sharing it is a separate act." + }, + "retracted": false, + "retracts": null, + "answered_at": "2026-11-12T19:02:00Z", + "request_id": "retry-1" + }, + "messagePackHex": "88AD736368656D6156657273696F6EBF67616D6563756C742E666F726B2E616E737765725F726571756573742E7630A7666F726B5F6964D92867616D6563756C742D636861743A7175657374696F6E3A736C6F742D6D61726B2D636F6E73656E74AC6F7074696F6E735F68617368D94037643935666338376435333234323036313166653431356331646566323761333434373631376262363832333566343665336166313330616561333631633438A6616E7377657282A663686F696365A26E6FA46E6F7465D9524B656570206974206C6F63616C3A206120736C6F74206D61726B2069732074686520617574686F7227732063616C6C2C20616E642073686172696E672069742069732061207365706172617465206163742EA9726574726163746564C2A87265747261637473C0AB616E7377657265645F6174B4323032362D31312D31325431393A30323A30305AAA726571756573745F6964A772657472792D31" + }, + { + "name": "note_over_4000", + "why": "notes are at most 4000 UTF-8 bytes", + "message": { + "schemaVersion": "gamecult.fork.answer_request.v0", + "fork_id": "gamecult-chat:question:slot-mark-consent", + "options_hash": "7d95fc87d532420611fe415c1def27a3447617bb68235f46e3af130aea361c48", + "answer": { + "choice": null, + "note": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" + }, + "retracted": false, + "retracts": null, + "answered_at": "2026-11-12T19:02:00Z", + "request_id": "00000028-0000-4000-8000-000000000028" + }, + "messagePackHex": "88AD736368656D6156657273696F6EBF67616D6563756C742E666F726B2E616E737765725F726571756573742E7630A7666F726B5F6964D92867616D6563756C742D636861743A7175657374696F6E3A736C6F742D6D61726B2D636F6E73656E74AC6F7074696F6E735F68617368D94037643935666338376435333234323036313166653431356331646566323761333434373631376262363832333566343665336166313330616561333631633438A6616E7377657282A663686F696365C0A46E6F7465DA0FA17878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878787878A9726574726163746564C2A87265747261637473C0AB616E7377657265645F6174B4323032362D31312D31325431393A30323A30305AAA726571756573745F6964D92430303030303032382D303030302D343030302D383030302D303030303030303030303238" + }, + { + "name": "note_multibyte_4001_bytes", + "why": "a note is counted in UTF-8 bytes: 1999 e-acute is 3998 bytes, plus three ASCII is 4001 (rule: not expressible in the schema, 2002 characters)", + "message": { + "schemaVersion": "gamecult.fork.answer_request.v0", + "fork_id": "gamecult-chat:question:slot-mark-consent", + "options_hash": "7d95fc87d532420611fe415c1def27a3447617bb68235f46e3af130aea361c48", + "answer": { + "choice": null, + "note": "éééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééabc" + }, + "retracted": false, + "retracts": null, + "answered_at": "2026-11-12T19:02:00Z", + "request_id": "00000029-0000-4000-8000-000000000029" + }, + "messagePackHex": "88AD736368656D6156657273696F6EBF67616D6563756C742E666F726B2E616E737765725F726571756573742E7630A7666F726B5F6964D92867616D6563756C742D636861743A7175657374696F6E3A736C6F742D6D61726B2D636F6E73656E74AC6F7074696F6E735F68617368D94037643935666338376435333234323036313166653431356331646566323761333434373631376262363832333566343665336166313330616561333631633438A6616E7377657282A663686F696365C0A46E6F7465DA0FA1C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9C3A9616263A9726574726163746564C2A87265747261637473C0AB616E7377657265645F6174B4323032362D31312D31325431393A30323A30305AAA726571756573745F6964D92430303030303032392D303030302D343030302D383030302D303030303030303030303239" + }, + { + "name": "note_astral_4004_bytes", + "why": "1001 astral characters are 4004 bytes though only 1001 code points (rule: not expressible in the schema)", + "message": { + "schemaVersion": "gamecult.fork.answer_request.v0", + "fork_id": "gamecult-chat:question:slot-mark-consent", + "options_hash": "7d95fc87d532420611fe415c1def27a3447617bb68235f46e3af130aea361c48", + "answer": { + "choice": null, + "note": "😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀" + }, + "retracted": false, + "retracts": null, + "answered_at": "2026-11-12T19:02:00Z", + "request_id": "0000002a-0000-4000-8000-00000000002a" + }, + "messagePackHex": "88AD736368656D6156657273696F6EBF67616D6563756C742E666F726B2E616E737765725F726571756573742E7630A7666F726B5F6964D92867616D6563756C742D636861743A7175657374696F6E3A736C6F742D6D61726B2D636F6E73656E74AC6F7074696F6E735F68617368D94037643935666338376435333234323036313166653431356331646566323761333434373631376262363832333566343665336166313330616561333631633438A6616E7377657282A663686F696365C0A46E6F7465DA0FA4F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880F09F9880A9726574726163746564C2A87265747261637473C0AB616E7377657265645F6174B4323032362D31312D31325431393A30323A30305AAA726571756573745F6964D92430303030303032612D303030302D343030302D383030302D303030303030303030303261" + }, + { + "name": "note_combining_4002_bytes", + "why": "e plus a combining acute is 3 bytes: 1334 of them are 4002 (rule: not expressible in the schema)", + "message": { + "schemaVersion": "gamecult.fork.answer_request.v0", + "fork_id": "gamecult-chat:question:slot-mark-consent", + "options_hash": "7d95fc87d532420611fe415c1def27a3447617bb68235f46e3af130aea361c48", + "answer": { + "choice": null, + "note": "éééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééééé" + }, + "retracted": false, + "retracts": null, + "answered_at": "2026-11-12T19:02:00Z", + "request_id": "0000002b-0000-4000-8000-00000000002b" + }, + "messagePackHex": "88AD736368656D6156657273696F6EBF67616D6563756C742E666F726B2E616E737765725F726571756573742E7630A7666F726B5F6964D92867616D6563756C742D636861743A7175657374696F6E3A736C6F742D6D61726B2D636F6E73656E74AC6F7074696F6E735F68617368D94037643935666338376435333234323036313166653431356331646566323761333434373631376262363832333566343665336166313330616561333631633438A6616E7377657282A663686F696365C0A46E6F7465DA0FA265CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC8165CC81A9726574726163746564C2A87265747261637473C0AB616E7377657265645F6174B4323032362D31312D31325431393A30323A30305AAA726571756573745F6964D92430303030303032622D303030302D343030302D383030302D303030303030303030303262" + }, + { + "name": "options_hash_uppercase", + "why": "hash is lowercase hex", + "message": { + "schemaVersion": "gamecult.fork.answer_request.v0", + "fork_id": "gamecult-chat:question:slot-mark-consent", + "options_hash": "7D95FC87D532420611FE415C1DEF27A3447617BB68235F46E3AF130AEA361C48", + "answer": { + "choice": "no", + "note": null + }, + "retracted": false, + "retracts": null, + "answered_at": "2026-11-12T19:02:00Z", + "request_id": "0000002c-0000-4000-8000-00000000002c" + }, + "messagePackHex": "88AD736368656D6156657273696F6EBF67616D6563756C742E666F726B2E616E737765725F726571756573742E7630A7666F726B5F6964D92867616D6563756C742D636861743A7175657374696F6E3A736C6F742D6D61726B2D636F6E73656E74AC6F7074696F6E735F68617368D94037443935464338374435333234323036313146453431354331444546323741333434373631374242363832333546343645334146313330414541333631433438A6616E7377657282A663686F696365A26E6FA46E6F7465C0A9726574726163746564C2A87265747261637473C0AB616E7377657265645F6174B4323032362D31312D31325431393A30323A30305AAA726571756573745F6964D92430303030303032632D303030302D343030302D383030302D303030303030303030303263" + }, + { + "name": "answerer_key", + "why": "answerer is server-set; the door answers body_forbidden_field", + "message": { + "schemaVersion": "gamecult.fork.answer_request.v0", + "fork_id": "gamecult-chat:question:slot-mark-consent", + "options_hash": "7d95fc87d532420611fe415c1def27a3447617bb68235f46e3af130aea361c48", + "answer": { + "choice": "no", + "note": "Keep it local: a slot mark is the author's call, and sharing it is a separate act." + }, + "retracted": false, + "retracts": null, + "answered_at": "2026-11-12T19:02:00Z", + "request_id": "00000001-0000-4000-8000-000000000001", + "answerer": "e0d1c2b3-a495-4687-8879-6a5b4c3d2e1f" + }, + "messagePackHex": "89AD736368656D6156657273696F6EBF67616D6563756C742E666F726B2E616E737765725F726571756573742E7630A7666F726B5F6964D92867616D6563756C742D636861743A7175657374696F6E3A736C6F742D6D61726B2D636F6E73656E74AC6F7074696F6E735F68617368D94037643935666338376435333234323036313166653431356331646566323761333434373631376262363832333566343665336166313330616561333631633438A6616E7377657282A663686F696365A26E6FA46E6F7465D9524B656570206974206C6F63616C3A206120736C6F74206D61726B2069732074686520617574686F7227732063616C6C2C20616E642073686172696E672069742069732061207365706172617465206163742EA9726574726163746564C2A87265747261637473C0AB616E7377657265645F6174B4323032362D31312D31325431393A30323A30305AAA726571756573745F6964D92430303030303030312D303030302D343030302D383030302D303030303030303030303031A8616E737765726572D92465306431633262332D613439352D343638372D383837392D366135623463336432653166" + }, + { + "name": "authority_key", + "why": "authority is server-set; the door answers body_forbidden_field", + "message": { + "schemaVersion": "gamecult.fork.answer_request.v0", + "fork_id": "gamecult-chat:question:slot-mark-consent", + "options_hash": "7d95fc87d532420611fe415c1def27a3447617bb68235f46e3af130aea361c48", + "answer": { + "choice": "no", + "note": "Keep it local: a slot mark is the author's call, and sharing it is a separate act." + }, + "retracted": false, + "retracts": null, + "answered_at": "2026-11-12T19:02:00Z", + "request_id": "00000001-0000-4000-8000-000000000001", + "authority": "Operator" + }, + "messagePackHex": "89AD736368656D6156657273696F6EBF67616D6563756C742E666F726B2E616E737765725F726571756573742E7630A7666F726B5F6964D92867616D6563756C742D636861743A7175657374696F6E3A736C6F742D6D61726B2D636F6E73656E74AC6F7074696F6E735F68617368D94037643935666338376435333234323036313166653431356331646566323761333434373631376262363832333566343665336166313330616561333631633438A6616E7377657282A663686F696365A26E6FA46E6F7465D9524B656570206974206C6F63616C3A206120736C6F74206D61726B2069732074686520617574686F7227732063616C6C2C20616E642073686172696E672069742069732061207365706172617465206163742EA9726574726163746564C2A87265747261637473C0AB616E7377657265645F6174B4323032362D31312D31325431393A30323A30305AAA726571756573745F6964D92430303030303030312D303030302D343030302D383030302D303030303030303030303031A9617574686F72697479A84F70657261746F72" + }, + { + "name": "answer_object_with_a_key", + "why": "the answer object is closed", + "message": { + "schemaVersion": "gamecult.fork.answer_request.v0", + "fork_id": "gamecult-chat:question:slot-mark-consent", + "options_hash": "7d95fc87d532420611fe415c1def27a3447617bb68235f46e3af130aea361c48", + "answer": { + "choice": "no", + "note": null, + "weight": 1 + }, + "retracted": false, + "retracts": null, + "answered_at": "2026-11-12T19:02:00Z", + "request_id": "0000002d-0000-4000-8000-00000000002d" + }, + "messagePackHex": "88AD736368656D6156657273696F6EBF67616D6563756C742E666F726B2E616E737765725F726571756573742E7630A7666F726B5F6964D92867616D6563756C742D636861743A7175657374696F6E3A736C6F742D6D61726B2D636F6E73656E74AC6F7074696F6E735F68617368D94037643935666338376435333234323036313166653431356331646566323761333434373631376262363832333566343665336166313330616561333631633438A6616E7377657283A663686F696365A26E6FA46E6F7465C0A677656967687401A9726574726163746564C2A87265747261637473C0AB616E7377657265645F6174B4323032362D31312D31325431393A30323A30305AAA726571756573745F6964D92430303030303032642D303030302D343030302D383030302D303030303030303030303264" + }, + { + "name": "answered_at_without_zone", + "why": "instants are UTC with a Z", + "message": { + "schemaVersion": "gamecult.fork.answer_request.v0", + "fork_id": "gamecult-chat:question:slot-mark-consent", + "options_hash": "7d95fc87d532420611fe415c1def27a3447617bb68235f46e3af130aea361c48", + "answer": { + "choice": "no", + "note": null + }, + "retracted": false, + "retracts": null, + "answered_at": "2026-11-12T19:02:00", + "request_id": "0000002e-0000-4000-8000-00000000002e" + }, + "messagePackHex": "88AD736368656D6156657273696F6EBF67616D6563756C742E666F726B2E616E737765725F726571756573742E7630A7666F726B5F6964D92867616D6563756C742D636861743A7175657374696F6E3A736C6F742D6D61726B2D636F6E73656E74AC6F7074696F6E735F68617368D94037643935666338376435333234323036313166653431356331646566323761333434373631376262363832333566343665336166313330616561333631633438A6616E7377657282A663686F696365A26E6FA46E6F7465C0A9726574726163746564C2A87265747261637473C0AB616E7377657265645F6174B3323032362D31312D31325431393A30323A3030AA726571756573745F6964D92430303030303032652D303030302D343030302D383030302D303030303030303030303265" + }, + { + "name": "retracted_not_a_boolean", + "why": "retracted is a boolean", + "message": { + "schemaVersion": "gamecult.fork.answer_request.v0", + "fork_id": "gamecult-chat:question:slot-mark-consent", + "options_hash": "7d95fc87d532420611fe415c1def27a3447617bb68235f46e3af130aea361c48", + "answer": { + "choice": "no", + "note": "Keep it local: a slot mark is the author's call, and sharing it is a separate act." + }, + "retracted": "no", + "retracts": null, + "answered_at": "2026-11-12T19:02:00Z", + "request_id": "00000001-0000-4000-8000-000000000001" + }, + "messagePackHex": "88AD736368656D6156657273696F6EBF67616D6563756C742E666F726B2E616E737765725F726571756573742E7630A7666F726B5F6964D92867616D6563756C742D636861743A7175657374696F6E3A736C6F742D6D61726B2D636F6E73656E74AC6F7074696F6E735F68617368D94037643935666338376435333234323036313166653431356331646566323761333434373631376262363832333566343665336166313330616561333631633438A6616E7377657282A663686F696365A26E6FA46E6F7465D9524B656570206974206C6F63616C3A206120736C6F74206D61726B2069732074686520617574686F7227732063616C6C2C20616E642073686172696E672069742069732061207365706172617465206163742EA9726574726163746564A26E6FA87265747261637473C0AB616E7377657265645F6174B4323032362D31312D31325431393A30323A30305AAA726571756573745F6964D92430303030303030312D303030302D343030302D383030302D303030303030303030303031" + }, + { + "name": "fork_id_empty", + "why": "fork_id names a fork", + "message": { + "schemaVersion": "gamecult.fork.answer_request.v0", + "fork_id": "", + "options_hash": "7d95fc87d532420611fe415c1def27a3447617bb68235f46e3af130aea361c48", + "answer": { + "choice": "no", + "note": null + }, + "retracted": false, + "retracts": null, + "answered_at": "2026-11-12T19:02:00Z", + "request_id": "0000002f-0000-4000-8000-00000000002f" + }, + "messagePackHex": "88AD736368656D6156657273696F6EBF67616D6563756C742E666F726B2E616E737765725F726571756573742E7630A7666F726B5F6964A0AC6F7074696F6E735F68617368D94037643935666338376435333234323036313166653431356331646566323761333434373631376262363832333566343665336166313330616561333631633438A6616E7377657282A663686F696365A26E6FA46E6F7465C0A9726574726163746564C2A87265747261637473C0AB616E7377657265645F6174B4323032362D31312D31325431393A30323A30305AAA726571756573745F6964D92430303030303032662D303030302D343030302D383030302D303030303030303030303266" + } + ] + }, + "deskAnswer": { + "valid": [ + { + "name": "counted", + "message": { + "schemaVersion": "gamecult.desk_answer.v0", + "answer_id": "n1", + "fork_id": "gamecult-chat:question:slot-mark-consent", + "outcome": "Counted", + "of": null, + "retracts": null, + "code": null, + "options_hash": "7d95fc87d532420611fe415c1def27a3447617bb68235f46e3af130aea361c48", + "choice": "no", + "note": "Keep it local: a slot mark is the author's call, and sharing it is a separate act.", + "answered_at": "2026-11-12T19:02:00Z", + "received_at": "2026-11-12T19:02:05Z", + "claim_in_future": false, + "answerer": "e0d1c2b3-a495-4687-8879-6a5b4c3d2e1f", + "authority": "Operator", + "request_id": "00000001-0000-4000-8000-000000000001" + }, + "messagePackHex": "DE0010AD736368656D6156657273696F6EB767616D6563756C742E6465736B5F616E737765722E7630A9616E737765725F6964A26E31A7666F726B5F6964D92867616D6563756C742D636861743A7175657374696F6E3A736C6F742D6D61726B2D636F6E73656E74A76F7574636F6D65A7436F756E746564A26F66C0A87265747261637473C0A4636F6465C0AC6F7074696F6E735F68617368D94037643935666338376435333234323036313166653431356331646566323761333434373631376262363832333566343665336166313330616561333631633438A663686F696365A26E6FA46E6F7465D9524B656570206974206C6F63616C3A206120736C6F74206D61726B2069732074686520617574686F7227732063616C6C2C20616E642073686172696E672069742069732061207365706172617465206163742EAB616E7377657265645F6174B4323032362D31312D31325431393A30323A30305AAB72656365697665645F6174B4323032362D31312D31325431393A30323A30355AAF636C61696D5F696E5F667574757265C2A8616E737765726572D92465306431633262332D613439352D343638372D383837392D366135623463336432653166A9617574686F72697479A84F70657261746F72AA726571756573745F6964D92430303030303030312D303030302D343030302D383030302D303030303030303030303031" + }, + { + "name": "revision", + "message": { + "schemaVersion": "gamecult.desk_answer.v0", + "answer_id": "n2", + "fork_id": "gamecult-chat:question:slot-mark-consent", + "outcome": "Revision", + "of": "n1", + "retracts": null, + "code": null, + "options_hash": "7d95fc87d532420611fe415c1def27a3447617bb68235f46e3af130aea361c48", + "choice": "yes", + "note": "On reflection, share it.", + "answered_at": "2026-11-12T19:06:00Z", + "received_at": "2026-11-12T19:06:05Z", + "claim_in_future": false, + "answerer": "e0d1c2b3-a495-4687-8879-6a5b4c3d2e1f", + "authority": "Operator", + "request_id": "00000002-0000-4000-8000-000000000002" + }, + "messagePackHex": "DE0010AD736368656D6156657273696F6EB767616D6563756C742E6465736B5F616E737765722E7630A9616E737765725F6964A26E32A7666F726B5F6964D92867616D6563756C742D636861743A7175657374696F6E3A736C6F742D6D61726B2D636F6E73656E74A76F7574636F6D65A85265766973696F6EA26F66A26E31A87265747261637473C0A4636F6465C0AC6F7074696F6E735F68617368D94037643935666338376435333234323036313166653431356331646566323761333434373631376262363832333566343665336166313330616561333631633438A663686F696365A3796573A46E6F7465B84F6E207265666C656374696F6E2C2073686172652069742EAB616E7377657265645F6174B4323032362D31312D31325431393A30363A30305AAB72656365697665645F6174B4323032362D31312D31325431393A30363A30355AAF636C61696D5F696E5F667574757265C2A8616E737765726572D92465306431633262332D613439352D343638372D383837392D366135623463336432653166A9617574686F72697479A84F70657261746F72AA726571756573745F6964D92430303030303030322D303030302D343030302D383030302D303030303030303030303032" + }, + { + "name": "retraction", + "message": { + "schemaVersion": "gamecult.desk_answer.v0", + "answer_id": "n8", + "fork_id": "gamecult-chat:question:slot-mark-consent", + "outcome": "Retraction", + "of": null, + "retracts": "n1", + "code": null, + "options_hash": "7d95fc87d532420611fe415c1def27a3447617bb68235f46e3af130aea361c48", + "choice": null, + "note": null, + "answered_at": "2026-11-12T19:02:00Z", + "received_at": "2026-11-12T19:09:05Z", + "claim_in_future": false, + "answerer": "e0d1c2b3-a495-4687-8879-6a5b4c3d2e1f", + "authority": "Operator", + "request_id": "00000030-0000-4000-8000-000000000030" + }, + "messagePackHex": "DE0010AD736368656D6156657273696F6EB767616D6563756C742E6465736B5F616E737765722E7630A9616E737765725F6964A26E38A7666F726B5F6964D92867616D6563756C742D636861743A7175657374696F6E3A736C6F742D6D61726B2D636F6E73656E74A76F7574636F6D65AA52657472616374696F6EA26F66C0A87265747261637473A26E31A4636F6465C0AC6F7074696F6E735F68617368D94037643935666338376435333234323036313166653431356331646566323761333434373631376262363832333566343665336166313330616561333631633438A663686F696365C0A46E6F7465C0AB616E7377657265645F6174B4323032362D31312D31325431393A30323A30305AAB72656365697665645F6174B4323032362D31312D31325431393A30393A30355AAF636C61696D5F696E5F667574757265C2A8616E737765726572D92465306431633262332D613439352D343638372D383837392D366135623463336432653166A9617574686F72697479A84F70657261746F72AA726571756573745F6964D92430303030303033302D303030302D343030302D383030302D303030303030303030303330" + }, + { + "name": "refused_stale", + "message": { + "schemaVersion": "gamecult.desk_answer.v0", + "answer_id": "n1", + "fork_id": "gamecult-chat:question:slot-mark-consent", + "outcome": "Refused", + "of": null, + "retracts": null, + "code": "stale_fork", + "options_hash": "14b8ffbdb61106dbc06f69f3fc8e2072af7a205d766e4cc040e89733013d54dc", + "choice": "no", + "note": "Keep it local: a slot mark is the author's call, and sharing it is a separate act.", + "answered_at": "2026-11-12T19:02:00Z", + "received_at": "2026-11-12T19:02:05Z", + "claim_in_future": false, + "answerer": "e0d1c2b3-a495-4687-8879-6a5b4c3d2e1f", + "authority": "Operator", + "request_id": "00000031-0000-4000-8000-000000000031" + }, + "messagePackHex": "DE0010AD736368656D6156657273696F6EB767616D6563756C742E6465736B5F616E737765722E7630A9616E737765725F6964A26E31A7666F726B5F6964D92867616D6563756C742D636861743A7175657374696F6E3A736C6F742D6D61726B2D636F6E73656E74A76F7574636F6D65A752656675736564A26F66C0A87265747261637473C0A4636F6465AA7374616C655F666F726BAC6F7074696F6E735F68617368D94031346238666662646236313130366462633036663639663366633865323037326166376132303564373636653463633034306538393733333031336435346463A663686F696365A26E6FA46E6F7465D9524B656570206974206C6F63616C3A206120736C6F74206D61726B2069732074686520617574686F7227732063616C6C2C20616E642073686172696E672069742069732061207365706172617465206163742EAB616E7377657265645F6174B4323032362D31312D31325431393A30323A30305AAB72656365697665645F6174B4323032362D31312D31325431393A30323A30355AAF636C61696D5F696E5F667574757265C2A8616E737765726572D92465306431633262332D613439352D343638372D383837392D366135623463336432653166A9617574686F72697479A84F70657261746F72AA726571756573745F6964D92430303030303033312D303030302D343030302D383030302D303030303030303030303331" + }, + { + "name": "refused_closed_retraction_keeps_no_target", + "message": { + "schemaVersion": "gamecult.desk_answer.v0", + "answer_id": "n9", + "fork_id": "gamecult-chat:question:slot-mark-consent", + "outcome": "Refused", + "of": null, + "retracts": null, + "code": "fork_closed", + "options_hash": "7d95fc87d532420611fe415c1def27a3447617bb68235f46e3af130aea361c48", + "choice": null, + "note": null, + "answered_at": "2026-11-12T19:02:00Z", + "received_at": "2026-11-12T19:09:05Z", + "claim_in_future": false, + "answerer": "e0d1c2b3-a495-4687-8879-6a5b4c3d2e1f", + "authority": "Operator", + "request_id": "00000032-0000-4000-8000-000000000032" + }, + "messagePackHex": "DE0010AD736368656D6156657273696F6EB767616D6563756C742E6465736B5F616E737765722E7630A9616E737765725F6964A26E39A7666F726B5F6964D92867616D6563756C742D636861743A7175657374696F6E3A736C6F742D6D61726B2D636F6E73656E74A76F7574636F6D65A752656675736564A26F66C0A87265747261637473C0A4636F6465AB666F726B5F636C6F736564AC6F7074696F6E735F68617368D94037643935666338376435333234323036313166653431356331646566323761333434373631376262363832333566343665336166313330616561333631633438A663686F696365C0A46E6F7465C0AB616E7377657265645F6174B4323032362D31312D31325431393A30323A30305AAB72656365697665645F6174B4323032362D31312D31325431393A30393A30355AAF636C61696D5F696E5F667574757265C2A8616E737765726572D92465306431633262332D613439352D343638372D383837392D366135623463336432653166A9617574686F72697479A84F70657261746F72AA726571756573745F6964D92430303030303033322D303030302D343030302D383030302D303030303030303030303332" + } + ], + "invalid": [ + { + "name": "counted_with_a_code", + "why": "only Refused carries a code", + "message": { + "schemaVersion": "gamecult.desk_answer.v0", + "answer_id": "n1", + "fork_id": "gamecult-chat:question:slot-mark-consent", + "outcome": "Counted", + "of": null, + "retracts": null, + "code": "fork_closed", + "options_hash": "7d95fc87d532420611fe415c1def27a3447617bb68235f46e3af130aea361c48", + "choice": "no", + "note": "Keep it local: a slot mark is the author's call, and sharing it is a separate act.", + "answered_at": "2026-11-12T19:02:00Z", + "received_at": "2026-11-12T19:02:05Z", + "claim_in_future": false, + "answerer": "e0d1c2b3-a495-4687-8879-6a5b4c3d2e1f", + "authority": "Operator", + "request_id": "00000001-0000-4000-8000-000000000001" + }, + "messagePackHex": "DE0010AD736368656D6156657273696F6EB767616D6563756C742E6465736B5F616E737765722E7630A9616E737765725F6964A26E31A7666F726B5F6964D92867616D6563756C742D636861743A7175657374696F6E3A736C6F742D6D61726B2D636F6E73656E74A76F7574636F6D65A7436F756E746564A26F66C0A87265747261637473C0A4636F6465AB666F726B5F636C6F736564AC6F7074696F6E735F68617368D94037643935666338376435333234323036313166653431356331646566323761333434373631376262363832333566343665336166313330616561333631633438A663686F696365A26E6FA46E6F7465D9524B656570206974206C6F63616C3A206120736C6F74206D61726B2069732074686520617574686F7227732063616C6C2C20616E642073686172696E672069742069732061207365706172617465206163742EAB616E7377657265645F6174B4323032362D31312D31325431393A30323A30305AAB72656365697665645F6174B4323032362D31312D31325431393A30323A30355AAF636C61696D5F696E5F667574757265C2A8616E737765726572D92465306431633262332D613439352D343638372D383837392D366135623463336432653166A9617574686F72697479A84F70657261746F72AA726571756573745F6964D92430303030303030312D303030302D343030302D383030302D303030303030303030303031" + }, + { + "name": "refused_without_a_code", + "why": "Refused carries a reason code", + "message": { + "schemaVersion": "gamecult.desk_answer.v0", + "answer_id": "n5", + "fork_id": "gamecult-chat:question:slot-mark-consent", + "outcome": "Refused", + "of": null, + "retracts": null, + "code": null, + "options_hash": "7d95fc87d532420611fe415c1def27a3447617bb68235f46e3af130aea361c48", + "choice": "no", + "note": "Keep it local: a slot mark is the author's call, and sharing it is a separate act.", + "answered_at": "2026-11-12T19:02:00Z", + "received_at": "2026-11-12T19:07:05Z", + "claim_in_future": false, + "answerer": "e0d1c2b3-a495-4687-8879-6a5b4c3d2e1f", + "authority": "Operator", + "request_id": "00000001-0000-4000-8000-000000000001" + }, + "messagePackHex": "DE0010AD736368656D6156657273696F6EB767616D6563756C742E6465736B5F616E737765722E7630A9616E737765725F6964A26E35A7666F726B5F6964D92867616D6563756C742D636861743A7175657374696F6E3A736C6F742D6D61726B2D636F6E73656E74A76F7574636F6D65A752656675736564A26F66C0A87265747261637473C0A4636F6465C0AC6F7074696F6E735F68617368D94037643935666338376435333234323036313166653431356331646566323761333434373631376262363832333566343665336166313330616561333631633438A663686F696365A26E6FA46E6F7465D9524B656570206974206C6F63616C3A206120736C6F74206D61726B2069732074686520617574686F7227732063616C6C2C20616E642073686172696E672069742069732061207365706172617465206163742EAB616E7377657265645F6174B4323032362D31312D31325431393A30323A30305AAB72656365697665645F6174B4323032362D31312D31325431393A30373A30355AAF636C61696D5F696E5F667574757265C2A8616E737765726572D92465306431633262332D613439352D343638372D383837392D366135623463336432653166A9617574686F72697479A84F70657261746F72AA726571756573745F6964D92430303030303030312D303030302D343030302D383030302D303030303030303030303031" + }, + { + "name": "refused_with_an_unknown_code", + "why": "the code set is closed", + "message": { + "schemaVersion": "gamecult.desk_answer.v0", + "answer_id": "n5", + "fork_id": "gamecult-chat:question:slot-mark-consent", + "outcome": "Refused", + "of": null, + "retracts": null, + "code": "because", + "options_hash": "7d95fc87d532420611fe415c1def27a3447617bb68235f46e3af130aea361c48", + "choice": "no", + "note": "Keep it local: a slot mark is the author's call, and sharing it is a separate act.", + "answered_at": "2026-11-12T19:02:00Z", + "received_at": "2026-11-12T19:07:05Z", + "claim_in_future": false, + "answerer": "e0d1c2b3-a495-4687-8879-6a5b4c3d2e1f", + "authority": "Operator", + "request_id": "00000001-0000-4000-8000-000000000001" + }, + "messagePackHex": "DE0010AD736368656D6156657273696F6EB767616D6563756C742E6465736B5F616E737765722E7630A9616E737765725F6964A26E35A7666F726B5F6964D92867616D6563756C742D636861743A7175657374696F6E3A736C6F742D6D61726B2D636F6E73656E74A76F7574636F6D65A752656675736564A26F66C0A87265747261637473C0A4636F6465A762656361757365AC6F7074696F6E735F68617368D94037643935666338376435333234323036313166653431356331646566323761333434373631376262363832333566343665336166313330616561333631633438A663686F696365A26E6FA46E6F7465D9524B656570206974206C6F63616C3A206120736C6F74206D61726B2069732074686520617574686F7227732063616C6C2C20616E642073686172696E672069742069732061207365706172617465206163742EAB616E7377657265645F6174B4323032362D31312D31325431393A30323A30305AAB72656365697665645F6174B4323032362D31312D31325431393A30373A30355AAF636C61696D5F696E5F667574757265C2A8616E737765726572D92465306431633262332D613439352D343638372D383837392D366135623463336432653166A9617574686F72697479A84F70657261746F72AA726571756573745F6964D92430303030303030312D303030302D343030302D383030302D303030303030303030303031" + }, + { + "name": "counted_naming_another_answer", + "why": "Counted names none", + "message": { + "schemaVersion": "gamecult.desk_answer.v0", + "answer_id": "n1", + "fork_id": "gamecult-chat:question:slot-mark-consent", + "outcome": "Counted", + "of": "n0", + "retracts": null, + "code": null, + "options_hash": "7d95fc87d532420611fe415c1def27a3447617bb68235f46e3af130aea361c48", + "choice": "no", + "note": "Keep it local: a slot mark is the author's call, and sharing it is a separate act.", + "answered_at": "2026-11-12T19:02:00Z", + "received_at": "2026-11-12T19:02:05Z", + "claim_in_future": false, + "answerer": "e0d1c2b3-a495-4687-8879-6a5b4c3d2e1f", + "authority": "Operator", + "request_id": "00000001-0000-4000-8000-000000000001" + }, + "messagePackHex": "DE0010AD736368656D6156657273696F6EB767616D6563756C742E6465736B5F616E737765722E7630A9616E737765725F6964A26E31A7666F726B5F6964D92867616D6563756C742D636861743A7175657374696F6E3A736C6F742D6D61726B2D636F6E73656E74A76F7574636F6D65A7436F756E746564A26F66A26E30A87265747261637473C0A4636F6465C0AC6F7074696F6E735F68617368D94037643935666338376435333234323036313166653431356331646566323761333434373631376262363832333566343665336166313330616561333631633438A663686F696365A26E6FA46E6F7465D9524B656570206974206C6F63616C3A206120736C6F74206D61726B2069732074686520617574686F7227732063616C6C2C20616E642073686172696E672069742069732061207365706172617465206163742EAB616E7377657265645F6174B4323032362D31312D31325431393A30323A30305AAB72656365697665645F6174B4323032362D31312D31325431393A30323A30355AAF636C61696D5F696E5F667574757265C2A8616E737765726572D92465306431633262332D613439352D343638372D383837392D366135623463336432653166A9617574686F72697479A84F70657261746F72AA726571756573745F6964D92430303030303030312D303030302D343030302D383030302D303030303030303030303031" + }, + { + "name": "revision_naming_none", + "why": "a Revision names the counted answer", + "message": { + "schemaVersion": "gamecult.desk_answer.v0", + "answer_id": "n2", + "fork_id": "gamecult-chat:question:slot-mark-consent", + "outcome": "Revision", + "of": null, + "retracts": null, + "code": null, + "options_hash": "7d95fc87d532420611fe415c1def27a3447617bb68235f46e3af130aea361c48", + "choice": "yes", + "note": "On reflection, share it.", + "answered_at": "2026-11-12T19:06:00Z", + "received_at": "2026-11-12T19:06:05Z", + "claim_in_future": false, + "answerer": "e0d1c2b3-a495-4687-8879-6a5b4c3d2e1f", + "authority": "Operator", + "request_id": "00000002-0000-4000-8000-000000000002" + }, + "messagePackHex": "DE0010AD736368656D6156657273696F6EB767616D6563756C742E6465736B5F616E737765722E7630A9616E737765725F6964A26E32A7666F726B5F6964D92867616D6563756C742D636861743A7175657374696F6E3A736C6F742D6D61726B2D636F6E73656E74A76F7574636F6D65A85265766973696F6EA26F66C0A87265747261637473C0A4636F6465C0AC6F7074696F6E735F68617368D94037643935666338376435333234323036313166653431356331646566323761333434373631376262363832333566343665336166313330616561333631633438A663686F696365A3796573A46E6F7465B84F6E207265666C656374696F6E2C2073686172652069742EAB616E7377657265645F6174B4323032362D31312D31325431393A30363A30305AAB72656365697665645F6174B4323032362D31312D31325431393A30363A30355AAF636C61696D5F696E5F667574757265C2A8616E737765726572D92465306431633262332D613439352D343638372D383837392D366135623463336432653166A9617574686F72697479A84F70657261746F72AA726571756573745F6964D92430303030303030322D303030302D343030302D383030302D303030303030303030303032" + }, + { + "name": "revision_that_also_retracts", + "why": "a variant carries only its own link (a retraction with no target, or a target with no retraction, cannot exist)", + "message": { + "schemaVersion": "gamecult.desk_answer.v0", + "answer_id": "n2", + "fork_id": "gamecult-chat:question:slot-mark-consent", + "outcome": "Revision", + "of": "n1", + "retracts": "n1", + "code": null, + "options_hash": "7d95fc87d532420611fe415c1def27a3447617bb68235f46e3af130aea361c48", + "choice": "yes", + "note": "On reflection, share it.", + "answered_at": "2026-11-12T19:06:00Z", + "received_at": "2026-11-12T19:06:05Z", + "claim_in_future": false, + "answerer": "e0d1c2b3-a495-4687-8879-6a5b4c3d2e1f", + "authority": "Operator", + "request_id": "00000002-0000-4000-8000-000000000002" + }, + "messagePackHex": "DE0010AD736368656D6156657273696F6EB767616D6563756C742E6465736B5F616E737765722E7630A9616E737765725F6964A26E32A7666F726B5F6964D92867616D6563756C742D636861743A7175657374696F6E3A736C6F742D6D61726B2D636F6E73656E74A76F7574636F6D65A85265766973696F6EA26F66A26E31A87265747261637473A26E31A4636F6465C0AC6F7074696F6E735F68617368D94037643935666338376435333234323036313166653431356331646566323761333434373631376262363832333566343665336166313330616561333631633438A663686F696365A3796573A46E6F7465B84F6E207265666C656374696F6E2C2073686172652069742EAB616E7377657265645F6174B4323032362D31312D31325431393A30363A30305AAB72656365697665645F6174B4323032362D31312D31325431393A30363A30355AAF636C61696D5F696E5F667574757265C2A8616E737765726572D92465306431633262332D613439352D343638372D383837392D366135623463336432653166A9617574686F72697479A84F70657261746F72AA726571756573745F6964D92430303030303030322D303030302D343030302D383030302D303030303030303030303032" + }, + { + "name": "retraction_naming_no_target", + "why": "a Retraction names the answer it retracts", + "message": { + "schemaVersion": "gamecult.desk_answer.v0", + "answer_id": "n8", + "fork_id": "gamecult-chat:question:slot-mark-consent", + "outcome": "Retraction", + "of": null, + "retracts": null, + "code": null, + "options_hash": "7d95fc87d532420611fe415c1def27a3447617bb68235f46e3af130aea361c48", + "choice": null, + "note": null, + "answered_at": "2026-11-12T19:02:00Z", + "received_at": "2026-11-12T19:09:05Z", + "claim_in_future": false, + "answerer": "e0d1c2b3-a495-4687-8879-6a5b4c3d2e1f", + "authority": "Operator", + "request_id": "00000033-0000-4000-8000-000000000033" + }, + "messagePackHex": "DE0010AD736368656D6156657273696F6EB767616D6563756C742E6465736B5F616E737765722E7630A9616E737765725F6964A26E38A7666F726B5F6964D92867616D6563756C742D636861743A7175657374696F6E3A736C6F742D6D61726B2D636F6E73656E74A76F7574636F6D65AA52657472616374696F6EA26F66C0A87265747261637473C0A4636F6465C0AC6F7074696F6E735F68617368D94037643935666338376435333234323036313166653431356331646566323761333434373631376262363832333566343665336166313330616561333631633438A663686F696365C0A46E6F7465C0AB616E7377657265645F6174B4323032362D31312D31325431393A30323A30305AAB72656365697665645F6174B4323032362D31312D31325431393A30393A30355AAF636C61696D5F696E5F667574757265C2A8616E737765726572D92465306431633262332D613439352D343638372D383837392D366135623463336432653166A9617574686F72697479A84F70657261746F72AA726571756573745F6964D92430303030303033332D303030302D343030302D383030302D303030303030303030303333" + }, + { + "name": "refused_naming_a_target", + "why": "Refused carries only its code", + "message": { + "schemaVersion": "gamecult.desk_answer.v0", + "answer_id": "n5", + "fork_id": "gamecult-chat:question:slot-mark-consent", + "outcome": "Refused", + "of": null, + "retracts": "n1", + "code": "fork_closed", + "options_hash": "7d95fc87d532420611fe415c1def27a3447617bb68235f46e3af130aea361c48", + "choice": "no", + "note": "Keep it local: a slot mark is the author's call, and sharing it is a separate act.", + "answered_at": "2026-11-12T19:02:00Z", + "received_at": "2026-11-12T19:07:05Z", + "claim_in_future": false, + "answerer": "e0d1c2b3-a495-4687-8879-6a5b4c3d2e1f", + "authority": "Operator", + "request_id": "00000001-0000-4000-8000-000000000001" + }, + "messagePackHex": "DE0010AD736368656D6156657273696F6EB767616D6563756C742E6465736B5F616E737765722E7630A9616E737765725F6964A26E35A7666F726B5F6964D92867616D6563756C742D636861743A7175657374696F6E3A736C6F742D6D61726B2D636F6E73656E74A76F7574636F6D65A752656675736564A26F66C0A87265747261637473A26E31A4636F6465AB666F726B5F636C6F736564AC6F7074696F6E735F68617368D94037643935666338376435333234323036313166653431356331646566323761333434373631376262363832333566343665336166313330616561333631633438A663686F696365A26E6FA46E6F7465D9524B656570206974206C6F63616C3A206120736C6F74206D61726B2069732074686520617574686F7227732063616C6C2C20616E642073686172696E672069742069732061207365706172617465206163742EAB616E7377657265645F6174B4323032362D31312D31325431393A30323A30305AAB72656365697665645F6174B4323032362D31312D31325431393A30373A30355AAF636C61696D5F696E5F667574757265C2A8616E737765726572D92465306431633262332D613439352D343638372D383837392D366135623463336432653166A9617574686F72697479A84F70657261746F72AA726571756573745F6964D92430303030303030312D303030302D343030302D383030302D303030303030303030303031" + }, + { + "name": "retraction_carrying_a_choice", + "why": "a Retraction carries no choice or note", + "message": { + "schemaVersion": "gamecult.desk_answer.v0", + "answer_id": "n8", + "fork_id": "gamecult-chat:question:slot-mark-consent", + "outcome": "Retraction", + "of": null, + "retracts": "n1", + "code": null, + "options_hash": "7d95fc87d532420611fe415c1def27a3447617bb68235f46e3af130aea361c48", + "choice": "no", + "note": null, + "answered_at": "2026-11-12T19:02:00Z", + "received_at": "2026-11-12T19:09:05Z", + "claim_in_future": false, + "answerer": "e0d1c2b3-a495-4687-8879-6a5b4c3d2e1f", + "authority": "Operator", + "request_id": "00000034-0000-4000-8000-000000000034" + }, + "messagePackHex": "DE0010AD736368656D6156657273696F6EB767616D6563756C742E6465736B5F616E737765722E7630A9616E737765725F6964A26E38A7666F726B5F6964D92867616D6563756C742D636861743A7175657374696F6E3A736C6F742D6D61726B2D636F6E73656E74A76F7574636F6D65AA52657472616374696F6EA26F66C0A87265747261637473A26E31A4636F6465C0AC6F7074696F6E735F68617368D94037643935666338376435333234323036313166653431356331646566323761333434373631376262363832333566343665336166313330616561333631633438A663686F696365A26E6FA46E6F7465C0AB616E7377657265645F6174B4323032362D31312D31325431393A30323A30305AAB72656365697665645F6174B4323032362D31312D31325431393A30393A30355AAF636C61696D5F696E5F667574757265C2A8616E737765726572D92465306431633262332D613439352D343638372D383837392D366135623463336432653166A9617574686F72697479A84F70657261746F72AA726571756573745F6964D92430303030303033342D303030302D343030302D383030302D303030303030303030303334" + }, + { + "name": "outcome_lower_case", + "why": "outcome tags are Counted, Revision, Retraction, Refused", + "message": { + "schemaVersion": "gamecult.desk_answer.v0", + "answer_id": "n1", + "fork_id": "gamecult-chat:question:slot-mark-consent", + "outcome": "counted", + "of": null, + "retracts": null, + "code": null, + "options_hash": "7d95fc87d532420611fe415c1def27a3447617bb68235f46e3af130aea361c48", + "choice": "no", + "note": "Keep it local: a slot mark is the author's call, and sharing it is a separate act.", + "answered_at": "2026-11-12T19:02:00Z", + "received_at": "2026-11-12T19:02:05Z", + "claim_in_future": false, + "answerer": "e0d1c2b3-a495-4687-8879-6a5b4c3d2e1f", + "authority": "Operator", + "request_id": "00000001-0000-4000-8000-000000000001" + }, + "messagePackHex": "DE0010AD736368656D6156657273696F6EB767616D6563756C742E6465736B5F616E737765722E7630A9616E737765725F6964A26E31A7666F726B5F6964D92867616D6563756C742D636861743A7175657374696F6E3A736C6F742D6D61726B2D636F6E73656E74A76F7574636F6D65A7636F756E746564A26F66C0A87265747261637473C0A4636F6465C0AC6F7074696F6E735F68617368D94037643935666338376435333234323036313166653431356331646566323761333434373631376262363832333566343665336166313330616561333631633438A663686F696365A26E6FA46E6F7465D9524B656570206974206C6F63616C3A206120736C6F74206D61726B2069732074686520617574686F7227732063616C6C2C20616E642073686172696E672069742069732061207365706172617465206163742EAB616E7377657265645F6174B4323032362D31312D31325431393A30323A30305AAB72656365697665645F6174B4323032362D31312D31325431393A30323A30355AAF636C61696D5F696E5F667574757265C2A8616E737765726572D92465306431633262332D613439352D343638372D383837392D366135623463336432653166A9617574686F72697479A84F70657261746F72AA726571756573745F6964D92430303030303030312D303030302D343030302D383030302D303030303030303030303031" + }, + { + "name": "no_answerer", + "why": "answerer is stamped on every stored answer", + "message": { + "schemaVersion": "gamecult.desk_answer.v0", + "answer_id": "n1", + "fork_id": "gamecult-chat:question:slot-mark-consent", + "outcome": "Counted", + "of": null, + "retracts": null, + "code": null, + "options_hash": "7d95fc87d532420611fe415c1def27a3447617bb68235f46e3af130aea361c48", + "choice": "no", + "note": "Keep it local: a slot mark is the author's call, and sharing it is a separate act.", + "answered_at": "2026-11-12T19:02:00Z", + "received_at": "2026-11-12T19:02:05Z", + "claim_in_future": false, + "answerer": "", + "authority": "Operator", + "request_id": "00000001-0000-4000-8000-000000000001" + }, + "messagePackHex": "DE0010AD736368656D6156657273696F6EB767616D6563756C742E6465736B5F616E737765722E7630A9616E737765725F6964A26E31A7666F726B5F6964D92867616D6563756C742D636861743A7175657374696F6E3A736C6F742D6D61726B2D636F6E73656E74A76F7574636F6D65A7436F756E746564A26F66C0A87265747261637473C0A4636F6465C0AC6F7074696F6E735F68617368D94037643935666338376435333234323036313166653431356331646566323761333434373631376262363832333566343665336166313330616561333631633438A663686F696365A26E6FA46E6F7465D9524B656570206974206C6F63616C3A206120736C6F74206D61726B2069732074686520617574686F7227732063616C6C2C20616E642073686172696E672069742069732061207365706172617465206163742EAB616E7377657265645F6174B4323032362D31312D31325431393A30323A30305AAB72656365697665645F6174B4323032362D31312D31325431393A30323A30355AAF636C61696D5F696E5F667574757265C2A8616E737765726572A0A9617574686F72697479A84F70657261746F72AA726571756573745F6964D92430303030303030312D303030302D343030302D383030302D303030303030303030303031" + } + ] + }, + "results": [ + { + "name": "stale_fork", + "httpStatus": 409, + "message": { + "schemaVersion": "gamecult.fork.answer_result.v0", + "answer": { + "schemaVersion": "gamecult.desk_answer.v0", + "answer_id": "n1", + "fork_id": "gamecult-chat:question:slot-mark-consent", + "outcome": "Refused", + "of": null, + "retracts": null, + "code": "stale_fork", + "options_hash": "7d95fc87d532420611fe415c1def27a3447617bb68235f46e3af130aea361c48", + "choice": "no", + "note": "Keep it local: a slot mark is the author's call, and sharing it is a separate act.", + "answered_at": "2026-11-12T19:02:00Z", + "received_at": "2026-11-12T19:02:05Z", + "claim_in_future": false, + "answerer": "e0d1c2b3-a495-4687-8879-6a5b4c3d2e1f", + "authority": "Operator", + "request_id": "00000001-0000-4000-8000-000000000001" + }, + "card": { + "schemaVersion": "gamecult.fork.card.v0", + "fork_id": "gamecult-chat:question:slot-mark-consent", + "title": "Slot mark consent", + "context": "Should a slot mark be shared?", + "options": [ + { + "id": "yes", + "text": "Share it." + }, + { + "id": "no", + "text": "Keep it local." + } + ], + "recommendation": "no", + "depends": [ + "gamecult-chat:question:file-zone-audience" + ], + "leans": null, + "flag": null, + "raised_in": { + "kind": "CutSpec", + "id": "cultlib-gaps:cut_spec:cut-slot-tolerance.r4" + }, + "deciders": [ + "e0d1c2b3-a495-4687-8879-6a5b4c3d2e1f" + ], + "status": "open", + "ruling": null, + "ruled_from": [], + "order": 3, + "options_hash": "7d95fc87d532420611fe415c1def27a3447617bb68235f46e3af130aea361c48" + } + }, + "messagePackHex": "83AD736368656D6156657273696F6EBE67616D6563756C742E666F726B2E616E737765725F726573756C742E7630A6616E73776572DE0010AD736368656D6156657273696F6EB767616D6563756C742E6465736B5F616E737765722E7630A9616E737765725F6964A26E31A7666F726B5F6964D92867616D6563756C742D636861743A7175657374696F6E3A736C6F742D6D61726B2D636F6E73656E74A76F7574636F6D65A752656675736564A26F66C0A87265747261637473C0A4636F6465AA7374616C655F666F726BAC6F7074696F6E735F68617368D94037643935666338376435333234323036313166653431356331646566323761333434373631376262363832333566343665336166313330616561333631633438A663686F696365A26E6FA46E6F7465D9524B656570206974206C6F63616C3A206120736C6F74206D61726B2069732074686520617574686F7227732063616C6C2C20616E642073686172696E672069742069732061207365706172617465206163742EAB616E7377657265645F6174B4323032362D31312D31325431393A30323A30305AAB72656365697665645F6174B4323032362D31312D31325431393A30323A30355AAF636C61696D5F696E5F667574757265C2A8616E737765726572D92465306431633262332D613439352D343638372D383837392D366135623463336432653166A9617574686F72697479A84F70657261746F72AA726571756573745F6964D92430303030303030312D303030302D343030302D383030302D303030303030303030303031A463617264DE0010AD736368656D6156657273696F6EB567616D6563756C742E666F726B2E636172642E7630A7666F726B5F6964D92867616D6563756C742D636861743A7175657374696F6E3A736C6F742D6D61726B2D636F6E73656E74A57469746C65B1536C6F74206D61726B20636F6E73656E74A7636F6E74657874BD53686F756C64206120736C6F74206D61726B206265207368617265643FA76F7074696F6E739282A26964A3796573A474657874A953686172652069742E82A26964A26E6FA474657874AE4B656570206974206C6F63616C2EAE7265636F6D6D656E646174696F6EA26E6FA7646570656E647391D92967616D6563756C742D636861743A7175657374696F6E3A66696C652D7A6F6E652D61756469656E6365A56C65616E73C0A4666C6167C0A97261697365645F696E82A46B696E64A743757453706563A26964D92B63756C746C69622D676170733A6375745F737065633A6375742D736C6F742D746F6C6572616E63652E7234A8646563696465727391D92465306431633262332D613439352D343638372D383837392D366135623463336432653166A6737461747573A46F70656EA672756C696E67C0AA72756C65645F66726F6D90A56F7264657203AC6F7074696F6E735F68617368D94037643935666338376435333234323036313166653431356331646566323761333434373631376262363832333566343665336166313330616561333631633438" + }, + { + "name": "not_a_decider", + "httpStatus": 403, + "message": { + "schemaVersion": "gamecult.fork.answer_result.v0", + "answer": { + "schemaVersion": "gamecult.desk_answer.v0", + "answer_id": "n1", + "fork_id": "gamecult-chat:question:slot-mark-consent", + "outcome": "Refused", + "of": null, + "retracts": null, + "code": "not_a_decider", + "options_hash": "7d95fc87d532420611fe415c1def27a3447617bb68235f46e3af130aea361c48", + "choice": "no", + "note": "Keep it local: a slot mark is the author's call, and sharing it is a separate act.", + "answered_at": "2026-11-12T19:02:00Z", + "received_at": "2026-11-12T19:02:05Z", + "claim_in_future": false, + "answerer": "e0d1c2b3-a495-4687-8879-6a5b4c3d2e1f", + "authority": "Operator", + "request_id": "00000001-0000-4000-8000-000000000001" + }, + "card": null + }, + "messagePackHex": "83AD736368656D6156657273696F6EBE67616D6563756C742E666F726B2E616E737765725F726573756C742E7630A6616E73776572DE0010AD736368656D6156657273696F6EB767616D6563756C742E6465736B5F616E737765722E7630A9616E737765725F6964A26E31A7666F726B5F6964D92867616D6563756C742D636861743A7175657374696F6E3A736C6F742D6D61726B2D636F6E73656E74A76F7574636F6D65A752656675736564A26F66C0A87265747261637473C0A4636F6465AD6E6F745F615F64656369646572AC6F7074696F6E735F68617368D94037643935666338376435333234323036313166653431356331646566323761333434373631376262363832333566343665336166313330616561333631633438A663686F696365A26E6FA46E6F7465D9524B656570206974206C6F63616C3A206120736C6F74206D61726B2069732074686520617574686F7227732063616C6C2C20616E642073686172696E672069742069732061207365706172617465206163742EAB616E7377657265645F6174B4323032362D31312D31325431393A30323A30305AAB72656365697665645F6174B4323032362D31312D31325431393A30323A30355AAF636C61696D5F696E5F667574757265C2A8616E737765726572D92465306431633262332D613439352D343638372D383837392D366135623463336432653166A9617574686F72697479A84F70657261746F72AA726571756573745F6964D92430303030303030312D303030302D343030302D383030302D303030303030303030303031A463617264C0" + }, + { + "name": "fork_closed", + "httpStatus": 409, + "message": { + "schemaVersion": "gamecult.fork.answer_result.v0", + "answer": { + "schemaVersion": "gamecult.desk_answer.v0", + "answer_id": "n1", + "fork_id": "gamecult-chat:question:slot-mark-consent", + "outcome": "Refused", + "of": null, + "retracts": null, + "code": "fork_closed", + "options_hash": "7d95fc87d532420611fe415c1def27a3447617bb68235f46e3af130aea361c48", + "choice": "no", + "note": "Keep it local: a slot mark is the author's call, and sharing it is a separate act.", + "answered_at": "2026-11-12T19:02:00Z", + "received_at": "2026-11-12T19:02:05Z", + "claim_in_future": false, + "answerer": "e0d1c2b3-a495-4687-8879-6a5b4c3d2e1f", + "authority": "Operator", + "request_id": "00000001-0000-4000-8000-000000000001" + }, + "card": null + }, + "messagePackHex": "83AD736368656D6156657273696F6EBE67616D6563756C742E666F726B2E616E737765725F726573756C742E7630A6616E73776572DE0010AD736368656D6156657273696F6EB767616D6563756C742E6465736B5F616E737765722E7630A9616E737765725F6964A26E31A7666F726B5F6964D92867616D6563756C742D636861743A7175657374696F6E3A736C6F742D6D61726B2D636F6E73656E74A76F7574636F6D65A752656675736564A26F66C0A87265747261637473C0A4636F6465AB666F726B5F636C6F736564AC6F7074696F6E735F68617368D94037643935666338376435333234323036313166653431356331646566323761333434373631376262363832333566343665336166313330616561333631633438A663686F696365A26E6FA46E6F7465D9524B656570206974206C6F63616C3A206120736C6F74206D61726B2069732074686520617574686F7227732063616C6C2C20616E642073686172696E672069742069732061207365706172617465206163742EAB616E7377657265645F6174B4323032362D31312D31325431393A30323A30305AAB72656365697665645F6174B4323032362D31312D31325431393A30323A30355AAF636C61696D5F696E5F667574757265C2A8616E737765726572D92465306431633262332D613439352D343638372D383837392D366135623463336432653166A9617574686F72697479A84F70657261746F72AA726571756573745F6964D92430303030303030312D303030302D343030302D383030302D303030303030303030303031A463617264C0" + }, + { + "name": "choice_invalid", + "httpStatus": 400, + "message": { + "schemaVersion": "gamecult.fork.answer_result.v0", + "answer": { + "schemaVersion": "gamecult.desk_answer.v0", + "answer_id": "n1", + "fork_id": "gamecult-chat:question:slot-mark-consent", + "outcome": "Refused", + "of": null, + "retracts": null, + "code": "choice_invalid", + "options_hash": "7d95fc87d532420611fe415c1def27a3447617bb68235f46e3af130aea361c48", + "choice": "no", + "note": "Keep it local: a slot mark is the author's call, and sharing it is a separate act.", + "answered_at": "2026-11-12T19:02:00Z", + "received_at": "2026-11-12T19:02:05Z", + "claim_in_future": false, + "answerer": "e0d1c2b3-a495-4687-8879-6a5b4c3d2e1f", + "authority": "Operator", + "request_id": "00000001-0000-4000-8000-000000000001" + }, + "card": null + }, + "messagePackHex": "83AD736368656D6156657273696F6EBE67616D6563756C742E666F726B2E616E737765725F726573756C742E7630A6616E73776572DE0010AD736368656D6156657273696F6EB767616D6563756C742E6465736B5F616E737765722E7630A9616E737765725F6964A26E31A7666F726B5F6964D92867616D6563756C742D636861743A7175657374696F6E3A736C6F742D6D61726B2D636F6E73656E74A76F7574636F6D65A752656675736564A26F66C0A87265747261637473C0A4636F6465AE63686F6963655F696E76616C6964AC6F7074696F6E735F68617368D94037643935666338376435333234323036313166653431356331646566323761333434373631376262363832333566343665336166313330616561333631633438A663686F696365A26E6FA46E6F7465D9524B656570206974206C6F63616C3A206120736C6F74206D61726B2069732074686520617574686F7227732063616C6C2C20616E642073686172696E672069742069732061207365706172617465206163742EAB616E7377657265645F6174B4323032362D31312D31325431393A30323A30305AAB72656365697665645F6174B4323032362D31312D31325431393A30323A30355AAF636C61696D5F696E5F667574757265C2A8616E737765726572D92465306431633262332D613439352D343638372D383837392D366135623463336432653166A9617574686F72697479A84F70657261746F72AA726571756573745F6964D92430303030303030312D303030302D343030302D383030302D303030303030303030303031A463617264C0" + }, + { + "name": "bad_retraction", + "httpStatus": 400, + "message": { + "schemaVersion": "gamecult.fork.answer_result.v0", + "answer": { + "schemaVersion": "gamecult.desk_answer.v0", + "answer_id": "n1", + "fork_id": "gamecult-chat:question:slot-mark-consent", + "outcome": "Refused", + "of": null, + "retracts": null, + "code": "bad_retraction", + "options_hash": "7d95fc87d532420611fe415c1def27a3447617bb68235f46e3af130aea361c48", + "choice": "no", + "note": "Keep it local: a slot mark is the author's call, and sharing it is a separate act.", + "answered_at": "2026-11-12T19:02:00Z", + "received_at": "2026-11-12T19:02:05Z", + "claim_in_future": false, + "answerer": "e0d1c2b3-a495-4687-8879-6a5b4c3d2e1f", + "authority": "Operator", + "request_id": "00000001-0000-4000-8000-000000000001" + }, + "card": null + }, + "messagePackHex": "83AD736368656D6156657273696F6EBE67616D6563756C742E666F726B2E616E737765725F726573756C742E7630A6616E73776572DE0010AD736368656D6156657273696F6EB767616D6563756C742E6465736B5F616E737765722E7630A9616E737765725F6964A26E31A7666F726B5F6964D92867616D6563756C742D636861743A7175657374696F6E3A736C6F742D6D61726B2D636F6E73656E74A76F7574636F6D65A752656675736564A26F66C0A87265747261637473C0A4636F6465AE6261645F72657472616374696F6EAC6F7074696F6E735F68617368D94037643935666338376435333234323036313166653431356331646566323761333434373631376262363832333566343665336166313330616561333631633438A663686F696365A26E6FA46E6F7465D9524B656570206974206C6F63616C3A206120736C6F74206D61726B2069732074686520617574686F7227732063616C6C2C20616E642073686172696E672069742069732061207365706172617465206163742EAB616E7377657265645F6174B4323032362D31312D31325431393A30323A30305AAB72656365697665645F6174B4323032362D31312D31325431393A30323A30355AAF636C61696D5F696E5F667574757265C2A8616E737765726572D92465306431633262332D613439352D343638372D383837392D366135623463336432653166A9617574686F72697479A84F70657261746F72AA726571756573745F6964D92430303030303030312D303030302D343030302D383030302D303030303030303030303031A463617264C0" + }, + { + "name": "already_retracted", + "httpStatus": 409, + "message": { + "schemaVersion": "gamecult.fork.answer_result.v0", + "answer": { + "schemaVersion": "gamecult.desk_answer.v0", + "answer_id": "n1", + "fork_id": "gamecult-chat:question:slot-mark-consent", + "outcome": "Refused", + "of": null, + "retracts": null, + "code": "already_retracted", + "options_hash": "7d95fc87d532420611fe415c1def27a3447617bb68235f46e3af130aea361c48", + "choice": "no", + "note": "Keep it local: a slot mark is the author's call, and sharing it is a separate act.", + "answered_at": "2026-11-12T19:02:00Z", + "received_at": "2026-11-12T19:02:05Z", + "claim_in_future": false, + "answerer": "e0d1c2b3-a495-4687-8879-6a5b4c3d2e1f", + "authority": "Operator", + "request_id": "00000001-0000-4000-8000-000000000001" + }, + "card": null + }, + "messagePackHex": "83AD736368656D6156657273696F6EBE67616D6563756C742E666F726B2E616E737765725F726573756C742E7630A6616E73776572DE0010AD736368656D6156657273696F6EB767616D6563756C742E6465736B5F616E737765722E7630A9616E737765725F6964A26E31A7666F726B5F6964D92867616D6563756C742D636861743A7175657374696F6E3A736C6F742D6D61726B2D636F6E73656E74A76F7574636F6D65A752656675736564A26F66C0A87265747261637473C0A4636F6465B1616C72656164795F726574726163746564AC6F7074696F6E735F68617368D94037643935666338376435333234323036313166653431356331646566323761333434373631376262363832333566343665336166313330616561333631633438A663686F696365A26E6FA46E6F7465D9524B656570206974206C6F63616C3A206120736C6F74206D61726B2069732074686520617574686F7227732063616C6C2C20616E642073686172696E672069742069732061207365706172617465206163742EAB616E7377657265645F6174B4323032362D31312D31325431393A30323A30305AAB72656365697665645F6174B4323032362D31312D31325431393A30323A30355AAF636C61696D5F696E5F667574757265C2A8616E737765726572D92465306431633262332D613439352D343638372D383837392D366135623463336432653166A9617574686F72697479A84F70657261746F72AA726571756573745F6964D92430303030303030312D303030302D343030302D383030302D303030303030303030303031A463617264C0" + } + ], + "doorRefusals": [ + { + "name": "unauthenticated", + "httpStatus": 401, + "message": { + "schemaVersion": "gamecult.fork.refusal.v0", + "code": "unauthenticated", + "error": "unauthenticated: a verified session is required" + }, + "messagePackHex": "83AD736368656D6156657273696F6EB867616D6563756C742E666F726B2E7265667573616C2E7630A4636F6465AF756E61757468656E74696361746564A56572726F72D92F756E61757468656E746963617465643A20612076657269666965642073657373696F6E206973207265717569726564" + }, + { + "name": "body_forbidden_field", + "httpStatus": 400, + "message": { + "schemaVersion": "gamecult.fork.refusal.v0", + "code": "body_forbidden_field", + "error": "body_forbidden_field: answerer and authority are set by the receiver" + }, + "messagePackHex": "83AD736368656D6156657273696F6EB867616D6563756C742E666F726B2E7265667573616C2E7630A4636F6465B4626F64795F666F7262696464656E5F6669656C64A56572726F72D944626F64795F666F7262696464656E5F6669656C643A20616E73776572657220616E6420617574686F72697479206172652073657420627920746865207265636569766572" + }, + { + "name": "body_unknown_field", + "httpStatus": 400, + "message": { + "schemaVersion": "gamecult.fork.refusal.v0", + "code": "body_unknown_field", + "error": "body_unknown_field: the body is a closed shape" + }, + "messagePackHex": "83AD736368656D6156657273696F6EB867616D6563756C742E666F726B2E7265667573616C2E7630A4636F6465B2626F64795F756E6B6E6F776E5F6669656C64A56572726F72D92E626F64795F756E6B6E6F776E5F6669656C643A2074686520626F6479206973206120636C6F736564207368617065" + }, + { + "name": "body_invalid", + "httpStatus": 400, + "message": { + "schemaVersion": "gamecult.fork.refusal.v0", + "code": "body_invalid", + "error": "body_invalid: the body does not satisfy gamecult.fork.answer_request.v0" + }, + "messagePackHex": "83AD736368656D6156657273696F6EB867616D6563756C742E666F726B2E7265667573616C2E7630A4636F6465AC626F64795F696E76616C6964A56572726F72D947626F64795F696E76616C69643A2074686520626F647920646F6573206E6F7420736174697366792067616D6563756C742E666F726B2E616E737765725F726571756573742E7630" + }, + { + "name": "fork_unknown", + "httpStatus": 404, + "message": { + "schemaVersion": "gamecult.fork.refusal.v0", + "code": "fork_unknown", + "error": "fork_unknown: no such fork" + }, + "messagePackHex": "83AD736368656D6156657273696F6EB867616D6563756C742E666F726B2E7265667573616C2E7630A4636F6465AC666F726B5F756E6B6E6F776EA56572726F72BA666F726B5F756E6B6E6F776E3A206E6F207375636820666F726B" + }, + { + "name": "insufficient_scope", + "httpStatus": 403, + "message": { + "schemaVersion": "gamecult.fork.refusal.v0", + "code": "insufficient_scope", + "error": "insufficient_scope: the token lacks the scope this route needs" + }, + "messagePackHex": "83AD736368656D6156657273696F6EB867616D6563756C742E666F726B2E7265667573616C2E7630A4636F6465B2696E73756666696369656E745F73636F7065A56572726F72D93E696E73756666696369656E745F73636F70653A2074686520746F6B656E206C61636B73207468652073636F7065207468697320726F757465206E65656473" + }, + { + "name": "forbidden", + "httpStatus": 403, + "message": { + "schemaVersion": "gamecult.fork.refusal.v0", + "code": "forbidden", + "error": "forbidden: only Active members may use the desk" + }, + "messagePackHex": "83AD736368656D6156657273696F6EB867616D6563756C742E666F726B2E7265667573616C2E7630A4636F6465A9666F7262696464656EA56572726F72D92F666F7262696464656E3A206F6E6C7920416374697665206D656D62657273206D61792075736520746865206465736B" + }, + { + "name": "request_id_conflict", + "httpStatus": 409, + "message": { + "schemaVersion": "gamecult.fork.refusal.v0", + "code": "request_id_conflict", + "error": "request_id_conflict: this request_id was used for a different body" + }, + "messagePackHex": "83AD736368656D6156657273696F6EB867616D6563756C742E666F726B2E7265667573616C2E7630A4636F6465B3726571756573745F69645F636F6E666C696374A56572726F72D942726571756573745F69645F636F6E666C6963743A207468697320726571756573745F696420776173207573656420666F72206120646966666572656E7420626F6479" + }, + { + "name": "request_invalid", + "httpStatus": 400, + "message": { + "schemaVersion": "gamecult.fork.refusal.v0", + "code": "request_invalid", + "error": "request_invalid: the request does not satisfy its document" + }, + "messagePackHex": "83AD736368656D6156657273696F6EB867616D6563756C742E666F726B2E7265667573616C2E7630A4636F6465AF726571756573745F696E76616C6964A56572726F72D93A726571756573745F696E76616C69643A20746865207265717565737420646F6573206E6F7420736174697366792069747320646F63756D656E74" + } + ], + "unavailable": [ + { + "name": "second_door_down", + "httpStatus": 503, + "message": { + "schemaVersion": "gamecult.fork.unavailable.v0", + "code": "unavailable", + "error": "unavailable: the outcome is not confirmed, try again with the same request_id", + "retryAfterSeconds": 30 + }, + "messagePackHex": "84AD736368656D6156657273696F6EBC67616D6563756C742E666F726B2E756E617661696C61626C652E7630A4636F6465AB756E617661696C61626C65A56572726F72D94D756E617661696C61626C653A20746865206F7574636F6D65206973206E6F7420636F6E6669726D65642C2074727920616761696E2077697468207468652073616D6520726571756573745F6964B1726574727941667465725365636F6E64731E" + }, + { + "name": "second_door_down_no_hint", + "httpStatus": 503, + "message": { + "schemaVersion": "gamecult.fork.unavailable.v0", + "code": "unavailable", + "error": "unavailable: the outcome is not confirmed, try again with the same request_id", + "retryAfterSeconds": null + }, + "messagePackHex": "84AD736368656D6156657273696F6EBC67616D6563756C742E666F726B2E756E617661696C61626C652E7630A4636F6465AB756E617661696C61626C65A56572726F72D94D756E617661696C61626C653A20746865206F7574636F6D65206973206E6F7420636F6E6669726D65642C2074727920616761696E2077697468207468652073616D6520726571756573745F6964B1726574727941667465725365636F6E6473C0" + } + ], + "rulingCites": [ + { + "name": "operator_cites_a_counted_operator_answer", + "answers": [ + { + "schemaVersion": "gamecult.desk_answer.v0", + "answer_id": "n1", + "fork_id": "gamecult-chat:question:slot-mark-consent", + "outcome": "Counted", + "of": null, + "retracts": null, + "code": null, + "options_hash": "7d95fc87d532420611fe415c1def27a3447617bb68235f46e3af130aea361c48", + "choice": "no", + "note": "Keep it local: a slot mark is the author's call, and sharing it is a separate act.", + "answered_at": "2026-11-12T19:02:00Z", + "received_at": "2026-11-12T19:02:05Z", + "claim_in_future": false, + "answerer": "e0d1c2b3-a495-4687-8879-6a5b4c3d2e1f", + "authority": "Operator", + "request_id": "00000001-0000-4000-8000-000000000001" + } + ], + "message": { + "schemaVersion": "gamecult.fork.ruling_cite.v0", + "ruling_id": "cultlib-gaps:resolution:question.slot-mark-consent.n1", + "authority": "Operator", + "answer_id": "n1" + }, + "admit": true, + "messagePackHex": "84AD736368656D6156657273696F6EBC67616D6563756C742E666F726B2E72756C696E675F636974652E7630A972756C696E675F6964D93563756C746C69622D676170733A7265736F6C7574696F6E3A7175657374696F6E2E736C6F742D6D61726B2D636F6E73656E742E6E31A9617574686F72697479A84F70657261746F72A9616E737765725F6964A26E31" + }, + { + "name": "operator_cites_nothing", + "answers": [ + { + "schemaVersion": "gamecult.desk_answer.v0", + "answer_id": "n1", + "fork_id": "gamecult-chat:question:slot-mark-consent", + "outcome": "Counted", + "of": null, + "retracts": null, + "code": null, + "options_hash": "7d95fc87d532420611fe415c1def27a3447617bb68235f46e3af130aea361c48", + "choice": "no", + "note": "Keep it local: a slot mark is the author's call, and sharing it is a separate act.", + "answered_at": "2026-11-12T19:02:00Z", + "received_at": "2026-11-12T19:02:05Z", + "claim_in_future": false, + "answerer": "e0d1c2b3-a495-4687-8879-6a5b4c3d2e1f", + "authority": "Operator", + "request_id": "00000001-0000-4000-8000-000000000001" + } + ], + "message": { + "schemaVersion": "gamecult.fork.ruling_cite.v0", + "ruling_id": "cultlib-gaps:resolution:question.slot-mark-consent.n1", + "authority": "Operator", + "answer_id": null + }, + "admit": false, + "messagePackHex": "84AD736368656D6156657273696F6EBC67616D6563756C742E666F726B2E72756C696E675F636974652E7630A972756C696E675F6964D93563756C746C69622D676170733A7265736F6C7574696F6E3A7175657374696F6E2E736C6F742D6D61726B2D636F6E73656E742E6E31A9617574686F72697479A84F70657261746F72A9616E737765725F6964C0" + }, + { + "name": "operator_cites_a_revision", + "answers": [ + { + "schemaVersion": "gamecult.desk_answer.v0", + "answer_id": "n1", + "fork_id": "gamecult-chat:question:slot-mark-consent", + "outcome": "Counted", + "of": null, + "retracts": null, + "code": null, + "options_hash": "7d95fc87d532420611fe415c1def27a3447617bb68235f46e3af130aea361c48", + "choice": "no", + "note": "Keep it local: a slot mark is the author's call, and sharing it is a separate act.", + "answered_at": "2026-11-12T19:02:00Z", + "received_at": "2026-11-12T19:02:05Z", + "claim_in_future": false, + "answerer": "e0d1c2b3-a495-4687-8879-6a5b4c3d2e1f", + "authority": "Operator", + "request_id": "00000001-0000-4000-8000-000000000001" + }, + { + "schemaVersion": "gamecult.desk_answer.v0", + "answer_id": "n2", + "fork_id": "gamecult-chat:question:slot-mark-consent", + "outcome": "Revision", + "of": "n1", + "retracts": null, + "code": null, + "options_hash": "7d95fc87d532420611fe415c1def27a3447617bb68235f46e3af130aea361c48", + "choice": "yes", + "note": "On reflection, share it.", + "answered_at": "2026-11-12T19:06:00Z", + "received_at": "2026-11-12T19:06:05Z", + "claim_in_future": false, + "answerer": "e0d1c2b3-a495-4687-8879-6a5b4c3d2e1f", + "authority": "Operator", + "request_id": "00000002-0000-4000-8000-000000000002" + } + ], + "message": { + "schemaVersion": "gamecult.fork.ruling_cite.v0", + "ruling_id": "cultlib-gaps:resolution:question.slot-mark-consent.n1", + "authority": "Operator", + "answer_id": "n2" + }, + "admit": false, + "messagePackHex": "84AD736368656D6156657273696F6EBC67616D6563756C742E666F726B2E72756C696E675F636974652E7630A972756C696E675F6964D93563756C746C69622D676170733A7265736F6C7574696F6E3A7175657374696F6E2E736C6F742D6D61726B2D636F6E73656E742E6E31A9617574686F72697479A84F70657261746F72A9616E737765725F6964A26E32" + }, + { + "name": "operator_cites_a_refused_answer", + "answers": [ + { + "schemaVersion": "gamecult.desk_answer.v0", + "answer_id": "n5", + "fork_id": "gamecult-chat:question:slot-mark-consent", + "outcome": "Refused", + "of": null, + "retracts": null, + "code": "fork_closed", + "options_hash": "7d95fc87d532420611fe415c1def27a3447617bb68235f46e3af130aea361c48", + "choice": "no", + "note": "Keep it local: a slot mark is the author's call, and sharing it is a separate act.", + "answered_at": "2026-11-12T19:02:00Z", + "received_at": "2026-11-12T19:07:05Z", + "claim_in_future": false, + "answerer": "e0d1c2b3-a495-4687-8879-6a5b4c3d2e1f", + "authority": "Operator", + "request_id": "00000001-0000-4000-8000-000000000001" + } + ], + "message": { + "schemaVersion": "gamecult.fork.ruling_cite.v0", + "ruling_id": "cultlib-gaps:resolution:question.slot-mark-consent.n1", + "authority": "Operator", + "answer_id": "n5" + }, + "admit": false, + "messagePackHex": "84AD736368656D6156657273696F6EBC67616D6563756C742E666F726B2E72756C696E675F636974652E7630A972756C696E675F6964D93563756C746C69622D676170733A7265736F6C7574696F6E3A7175657374696F6E2E736C6F742D6D61726B2D636F6E73656E742E6E31A9617574686F72697479A84F70657261746F72A9616E737765725F6964A26E35" + }, + { + "name": "operator_cites_a_counted_answer_by_a_non_operator", + "answers": [ + { + "schemaVersion": "gamecult.desk_answer.v0", + "answer_id": "n4", + "fork_id": "gamecult-chat:question:slot-mark-consent", + "outcome": "Counted", + "of": null, + "retracts": null, + "code": null, + "options_hash": "7d95fc87d532420611fe415c1def27a3447617bb68235f46e3af130aea361c48", + "choice": "yes", + "note": "Share, please.", + "answered_at": "2026-11-12T19:02:00Z", + "received_at": "2026-11-12T19:03:05Z", + "claim_in_future": false, + "answerer": "11111111-2222-4333-8444-555555555555", + "authority": "Member", + "request_id": "00000004-0000-4000-8000-000000000004" + } + ], + "message": { + "schemaVersion": "gamecult.fork.ruling_cite.v0", + "ruling_id": "cultlib-gaps:resolution:question.slot-mark-consent.n1", + "authority": "Operator", + "answer_id": "n4" + }, + "admit": false, + "messagePackHex": "84AD736368656D6156657273696F6EBC67616D6563756C742E666F726B2E72756C696E675F636974652E7630A972756C696E675F6964D93563756C746C69622D676170733A7265736F6C7574696F6E3A7175657374696F6E2E736C6F742D6D61726B2D636F6E73656E742E6E31A9617574686F72697479A84F70657261746F72A9616E737765725F6964A26E34" + }, + { + "name": "operator_cites_an_answer_that_does_not_exist", + "answers": [ + { + "schemaVersion": "gamecult.desk_answer.v0", + "answer_id": "n1", + "fork_id": "gamecult-chat:question:slot-mark-consent", + "outcome": "Counted", + "of": null, + "retracts": null, + "code": null, + "options_hash": "7d95fc87d532420611fe415c1def27a3447617bb68235f46e3af130aea361c48", + "choice": "no", + "note": "Keep it local: a slot mark is the author's call, and sharing it is a separate act.", + "answered_at": "2026-11-12T19:02:00Z", + "received_at": "2026-11-12T19:02:05Z", + "claim_in_future": false, + "answerer": "e0d1c2b3-a495-4687-8879-6a5b4c3d2e1f", + "authority": "Operator", + "request_id": "00000001-0000-4000-8000-000000000001" + } + ], + "message": { + "schemaVersion": "gamecult.fork.ruling_cite.v0", + "ruling_id": "cultlib-gaps:resolution:question.slot-mark-consent.n1", + "authority": "Operator", + "answer_id": "n99" + }, + "admit": false, + "messagePackHex": "84AD736368656D6156657273696F6EBC67616D6563756C742E666F726B2E72756C696E675F636974652E7630A972756C696E675F6964D93563756C746C69622D676170733A7265736F6C7574696F6E3A7175657374696F6E2E736C6F742D6D61726B2D636F6E73656E742E6E31A9617574686F72697479A84F70657261746F72A9616E737765725F6964A36E3939" + }, + { + "name": "steward_needs_no_citation", + "answers": [], + "message": { + "schemaVersion": "gamecult.fork.ruling_cite.v0", + "ruling_id": "cultlib-gaps:resolution:question.slot-mark-consent.n1", + "authority": "Steward", + "answer_id": null + }, + "admit": true, + "messagePackHex": "84AD736368656D6156657273696F6EBC67616D6563756C742E666F726B2E72756C696E675F636974652E7630A972756C696E675F6964D93563756C746C69622D676170733A7265736F6C7574696F6E3A7175657374696F6E2E736C6F742D6D61726B2D636F6E73656E742E6E31A9617574686F72697479A753746577617264A9616E737765725F6964C0" + } + ], + "transport": { + "listRequest": { + "valid": [ + { + "name": "list_open_page", + "message": { + "schemaVersion": "gamecult.fork.list_request.v0", + "mind": "gamecult-chat", + "status": "open", + "cursor": null, + "limit": 2 + }, + "messagePackHex": "85AD736368656D6156657273696F6EBD67616D6563756C742E666F726B2E6C6973745F726571756573742E7630A46D696E64AD67616D6563756C742D63686174A6737461747573A46F70656EA6637572736F72C0A56C696D697402" + }, + { + "name": "list_next_page", + "message": { + "schemaVersion": "gamecult.fork.list_request.v0", + "mind": "gamecult-chat", + "status": "open", + "cursor": "c4", + "limit": 2 + }, + "messagePackHex": "85AD736368656D6156657273696F6EBD67616D6563756C742E666F726B2E6C6973745F726571756573742E7630A46D696E64AD67616D6563756C742D63686174A6737461747573A46F70656EA6637572736F72A26334A56C696D697402" + }, + { + "name": "list_count_only", + "message": { + "schemaVersion": "gamecult.fork.list_request.v0", + "mind": "gamecult-chat", + "status": null, + "cursor": null, + "limit": 0 + }, + "messagePackHex": "85AD736368656D6156657273696F6EBD67616D6563756C742E666F726B2E6C6973745F726571756573742E7630A46D696E64AD67616D6563756C742D63686174A6737461747573C0A6637572736F72C0A56C696D697400" + } + ], + "invalid": [ + { + "name": "limit_51", + "why": "a page is at most 50 forks", + "message": { + "schemaVersion": "gamecult.fork.list_request.v0", + "mind": "gamecult-chat", + "status": null, + "cursor": null, + "limit": 51 + }, + "messagePackHex": "85AD736368656D6156657273696F6EBD67616D6563756C742E666F726B2E6C6973745F726571756573742E7630A46D696E64AD67616D6563756C742D63686174A6737461747573C0A6637572736F72C0A56C696D697433" + }, + { + "name": "limit_negative", + "why": "limit is 0 to 50", + "message": { + "schemaVersion": "gamecult.fork.list_request.v0", + "mind": "gamecult-chat", + "status": null, + "cursor": null, + "limit": -1 + }, + "messagePackHex": "85AD736368656D6156657273696F6EBD67616D6563756C742E666F726B2E6C6973745F726571756573742E7630A46D696E64AD67616D6563756C742D63686174A6737461747573C0A6637572736F72C0A56C696D6974FF" + }, + { + "name": "status_all", + "why": "status is open, recorded or nil", + "message": { + "schemaVersion": "gamecult.fork.list_request.v0", + "mind": "gamecult-chat", + "status": "all", + "cursor": null, + "limit": 10 + }, + "messagePackHex": "85AD736368656D6156657273696F6EBD67616D6563756C742E666F726B2E6C6973745F726571756573742E7630A46D696E64AD67616D6563756C742D63686174A6737461747573A3616C6CA6637572736F72C0A56C696D69740A" + }, + { + "name": "cursor_malformed", + "why": "a cursor is c", + "message": { + "schemaVersion": "gamecult.fork.list_request.v0", + "mind": "gamecult-chat", + "status": null, + "cursor": "next", + "limit": 10 + }, + "messagePackHex": "85AD736368656D6156657273696F6EBD67616D6563756C742E666F726B2E6C6973745F726571756573742E7630A46D696E64AD67616D6563756C742D63686174A6737461747573C0A6637572736F72A46E657874A56C696D69740A" + }, + { + "name": "cursor_zero", + "why": "a cursor names a positive order", + "message": { + "schemaVersion": "gamecult.fork.list_request.v0", + "mind": "gamecult-chat", + "status": null, + "cursor": "c0", + "limit": 10 + }, + "messagePackHex": "85AD736368656D6156657273696F6EBD67616D6563756C742E666F726B2E6C6973745F726571756573742E7630A46D696E64AD67616D6563756C742D63686174A6737461747573C0A6637572736F72A26330A56C696D69740A" + }, + { + "name": "mind_empty", + "why": "a list names a Mind", + "message": { + "schemaVersion": "gamecult.fork.list_request.v0", + "mind": "", + "status": null, + "cursor": null, + "limit": 10 + }, + "messagePackHex": "85AD736368656D6156657273696F6EBD67616D6563756C742E666F726B2E6C6973745F726571756573742E7630A46D696E64A0A6737461747573C0A6637572736F72C0A56C696D69740A" + }, + { + "name": "unknown_key", + "why": "closed document", + "message": { + "schemaVersion": "gamecult.fork.list_request.v0", + "mind": "gamecult-chat", + "status": null, + "cursor": null, + "limit": 10, + "sort": "oldest" + }, + "messagePackHex": "86AD736368656D6156657273696F6EBD67616D6563756C742E666F726B2E6C6973745F726571756573742E7630A46D696E64AD67616D6563756C742D63686174A6737461747573C0A6637572736F72C0A56C696D69740AA4736F7274A66F6C64657374" + } + ] + }, + "readRequest": { + "valid": [ + { + "name": "read_one", + "message": { + "schemaVersion": "gamecult.fork.read_request.v0", + "mind": "gamecult-chat", + "fork_id": "gamecult-chat:question:slot-mark-consent" + }, + "messagePackHex": "83AD736368656D6156657273696F6EBD67616D6563756C742E666F726B2E726561645F726571756573742E7630A46D696E64AD67616D6563756C742D63686174A7666F726B5F6964D92867616D6563756C742D636861743A7175657374696F6E3A736C6F742D6D61726B2D636F6E73656E74" + } + ], + "invalid": [ + { + "name": "fork_id_empty", + "why": "a read names a fork", + "message": { + "schemaVersion": "gamecult.fork.read_request.v0", + "mind": "gamecult-chat", + "fork_id": "" + }, + "messagePackHex": "83AD736368656D6156657273696F6EBD67616D6563756C742E666F726B2E726561645F726571756573742E7630A46D696E64AD67616D6563756C742D63686174A7666F726B5F6964A0" + }, + { + "name": "mind_empty", + "why": "a read names a Mind", + "message": { + "schemaVersion": "gamecult.fork.read_request.v0", + "mind": "", + "fork_id": "gamecult-chat:question:slot-mark-consent" + }, + "messagePackHex": "83AD736368656D6156657273696F6EBD67616D6563756C742E666F726B2E726561645F726571756573742E7630A46D696E64A0A7666F726B5F6964D92867616D6563756C742D636861743A7175657374696F6E3A736C6F742D6D61726B2D636F6E73656E74" + }, + { + "name": "mind_missing", + "why": "map-mode writes every key; the Mind is the route's", + "message": { + "schemaVersion": "gamecult.fork.read_request.v0", + "fork_id": "gamecult-chat:question:slot-mark-consent" + }, + "messagePackHex": "82AD736368656D6156657273696F6EBD67616D6563756C742E666F726B2E726561645F726571756573742E7630A7666F726B5F6964D92867616D6563756C742D636861743A7175657374696F6E3A736C6F742D6D61726B2D636F6E73656E74" + } + ] + }, + "entry": { + "valid": [ + { + "name": "open_no_answers", + "message": { + "schemaVersion": "gamecult.fork.entry.v0", + "card": { + "schemaVersion": "gamecult.fork.card.v0", + "fork_id": "gamecult-chat:question:q4", + "title": "Fork 4", + "context": "Should a slot mark be shared?", + "options": [ + { + "id": "yes", + "text": "Share it." + }, + { + "id": "no", + "text": "Keep it local." + } + ], + "recommendation": "no", + "depends": [ + "gamecult-chat:question:file-zone-audience" + ], + "leans": null, + "flag": null, + "raised_in": { + "kind": "CutSpec", + "id": "cultlib-gaps:cut_spec:cut-slot-tolerance.r4" + }, + "deciders": [ + "e0d1c2b3-a495-4687-8879-6a5b4c3d2e1f" + ], + "status": "open", + "ruling": null, + "ruled_from": [], + "order": 4, + "options_hash": "a64604237f906b6fec9cb292863685d966abf8048f276afa24cd4f1e1c4c74aa" + }, + "record": { + "schemaVersion": "gamecult.fork.record.v0", + "fork_id": "gamecult-chat:question:q4", + "options_hash": "a64604237f906b6fec9cb292863685d966abf8048f276afa24cd4f1e1c4c74aa", + "counted_answer_id": null, + "answers": [] + } + }, + "messagePackHex": "83AD736368656D6156657273696F6EB667616D6563756C742E666F726B2E656E7472792E7630A463617264DE0010AD736368656D6156657273696F6EB567616D6563756C742E666F726B2E636172642E7630A7666F726B5F6964B967616D6563756C742D636861743A7175657374696F6E3A7134A57469746C65A6466F726B2034A7636F6E74657874BD53686F756C64206120736C6F74206D61726B206265207368617265643FA76F7074696F6E739282A26964A3796573A474657874A953686172652069742E82A26964A26E6FA474657874AE4B656570206974206C6F63616C2EAE7265636F6D6D656E646174696F6EA26E6FA7646570656E647391D92967616D6563756C742D636861743A7175657374696F6E3A66696C652D7A6F6E652D61756469656E6365A56C65616E73C0A4666C6167C0A97261697365645F696E82A46B696E64A743757453706563A26964D92B63756C746C69622D676170733A6375745F737065633A6375742D736C6F742D746F6C6572616E63652E7234A8646563696465727391D92465306431633262332D613439352D343638372D383837392D366135623463336432653166A6737461747573A46F70656EA672756C696E67C0AA72756C65645F66726F6D90A56F7264657204AC6F7074696F6E735F68617368D94061363436303432333766393036623666656339636232393238363336383564393636616266383034386632373661666132346364346631653163346337346161A67265636F726485AD736368656D6156657273696F6EB767616D6563756C742E666F726B2E7265636F72642E7630A7666F726B5F6964B967616D6563756C742D636861743A7175657374696F6E3A7134AC6F7074696F6E735F68617368D94061363436303432333766393036623666656339636232393238363336383564393636616266383034386632373661666132346364346631653163346337346161B1636F756E7465645F616E737765725F6964C0A7616E737765727390" + }, + { + "name": "open_counted", + "message": { + "schemaVersion": "gamecult.fork.entry.v0", + "card": { + "schemaVersion": "gamecult.fork.card.v0", + "fork_id": "gamecult-chat:question:q5", + "title": "Fork 5", + "context": "Should a slot mark be shared?", + "options": [ + { + "id": "yes", + "text": "Share it." + }, + { + "id": "no", + "text": "Keep it local." + } + ], + "recommendation": "no", + "depends": [ + "gamecult-chat:question:file-zone-audience" + ], + "leans": null, + "flag": null, + "raised_in": { + "kind": "CutSpec", + "id": "cultlib-gaps:cut_spec:cut-slot-tolerance.r4" + }, + "deciders": [ + "e0d1c2b3-a495-4687-8879-6a5b4c3d2e1f" + ], + "status": "open", + "ruling": null, + "ruled_from": [], + "order": 5, + "options_hash": "16d20a6dc44b6cc00afd378e9f0c8b51dfe5b07e869e1a2aa9588de1de587fab" + }, + "record": { + "schemaVersion": "gamecult.fork.record.v0", + "fork_id": "gamecult-chat:question:q5", + "options_hash": "16d20a6dc44b6cc00afd378e9f0c8b51dfe5b07e869e1a2aa9588de1de587fab", + "counted_answer_id": "n1", + "answers": [ + { + "schemaVersion": "gamecult.desk_answer.v0", + "answer_id": "n1", + "fork_id": "gamecult-chat:question:q5", + "outcome": "Counted", + "of": null, + "retracts": null, + "code": null, + "options_hash": "16d20a6dc44b6cc00afd378e9f0c8b51dfe5b07e869e1a2aa9588de1de587fab", + "choice": "no", + "note": "Keep it local: a slot mark is the author's call, and sharing it is a separate act.", + "answered_at": "2026-11-12T19:02:00Z", + "received_at": "2026-11-12T19:02:05Z", + "claim_in_future": false, + "answerer": "e0d1c2b3-a495-4687-8879-6a5b4c3d2e1f", + "authority": "Operator", + "request_id": "0000001f-0000-4000-8000-00000000001f" + } + ] + } + }, + "messagePackHex": "83AD736368656D6156657273696F6EB667616D6563756C742E666F726B2E656E7472792E7630A463617264DE0010AD736368656D6156657273696F6EB567616D6563756C742E666F726B2E636172642E7630A7666F726B5F6964B967616D6563756C742D636861743A7175657374696F6E3A7135A57469746C65A6466F726B2035A7636F6E74657874BD53686F756C64206120736C6F74206D61726B206265207368617265643FA76F7074696F6E739282A26964A3796573A474657874A953686172652069742E82A26964A26E6FA474657874AE4B656570206974206C6F63616C2EAE7265636F6D6D656E646174696F6EA26E6FA7646570656E647391D92967616D6563756C742D636861743A7175657374696F6E3A66696C652D7A6F6E652D61756469656E6365A56C65616E73C0A4666C6167C0A97261697365645F696E82A46B696E64A743757453706563A26964D92B63756C746C69622D676170733A6375745F737065633A6375742D736C6F742D746F6C6572616E63652E7234A8646563696465727391D92465306431633262332D613439352D343638372D383837392D366135623463336432653166A6737461747573A46F70656EA672756C696E67C0AA72756C65645F66726F6D90A56F7264657205AC6F7074696F6E735F68617368D94031366432306136646334346236636330306166643337386539663063386235316466653562303765383639653161326161393538386465316465353837666162A67265636F726485AD736368656D6156657273696F6EB767616D6563756C742E666F726B2E7265636F72642E7630A7666F726B5F6964B967616D6563756C742D636861743A7175657374696F6E3A7135AC6F7074696F6E735F68617368D94031366432306136646334346236636330306166643337386539663063386235316466653562303765383639653161326161393538386465316465353837666162B1636F756E7465645F616E737765725F6964A26E31A7616E737765727391DE0010AD736368656D6156657273696F6EB767616D6563756C742E6465736B5F616E737765722E7630A9616E737765725F6964A26E31A7666F726B5F6964B967616D6563756C742D636861743A7175657374696F6E3A7135A76F7574636F6D65A7436F756E746564A26F66C0A87265747261637473C0A4636F6465C0AC6F7074696F6E735F68617368D94031366432306136646334346236636330306166643337386539663063386235316466653562303765383639653161326161393538386465316465353837666162A663686F696365A26E6FA46E6F7465D9524B656570206974206C6F63616C3A206120736C6F74206D61726B2069732074686520617574686F7227732063616C6C2C20616E642073686172696E672069742069732061207365706172617465206163742EAB616E7377657265645F6174B4323032362D31312D31325431393A30323A30305AAB72656365697665645F6174B4323032362D31312D31325431393A30323A30355AAF636C61696D5F696E5F667574757265C2A8616E737765726572D92465306431633262332D613439352D343638372D383837392D366135623463336432653166A9617574686F72697479A84F70657261746F72AA726571756573745F6964D92430303030303031662D303030302D343030302D383030302D303030303030303030303166" + }, + { + "name": "closed_with_ruling_and_a_refusal", + "message": { + "schemaVersion": "gamecult.fork.entry.v0", + "card": { + "schemaVersion": "gamecult.fork.card.v0", + "fork_id": "gamecult-chat:question:q3", + "title": "Fork 3", + "context": "Should a slot mark be shared?", + "options": [ + { + "id": "yes", + "text": "Share it." + }, + { + "id": "no", + "text": "Keep it local." + } + ], + "recommendation": "no", + "depends": [ + "gamecult-chat:question:file-zone-audience" + ], + "leans": null, + "flag": null, + "raised_in": { + "kind": "CutSpec", + "id": "cultlib-gaps:cut_spec:cut-slot-tolerance.r4" + }, + "deciders": [ + "e0d1c2b3-a495-4687-8879-6a5b4c3d2e1f" + ], + "status": "closed", + "ruling": "cultlib-gaps:resolution:question.q3.n1", + "ruled_from": [ + "n1" + ], + "order": 3, + "options_hash": "ced892eb663c22e3d7ece602708043ee545f56b9d4e4421d7f5bfaa86c418ce2" + }, + "record": { + "schemaVersion": "gamecult.fork.record.v0", + "fork_id": "gamecult-chat:question:q3", + "options_hash": "ced892eb663c22e3d7ece602708043ee545f56b9d4e4421d7f5bfaa86c418ce2", + "counted_answer_id": "n1", + "answers": [ + { + "schemaVersion": "gamecult.desk_answer.v0", + "answer_id": "n1", + "fork_id": "gamecult-chat:question:q3", + "outcome": "Counted", + "of": null, + "retracts": null, + "code": null, + "options_hash": "ced892eb663c22e3d7ece602708043ee545f56b9d4e4421d7f5bfaa86c418ce2", + "choice": "yes", + "note": "Keep it local: a slot mark is the author's call, and sharing it is a separate act.", + "answered_at": "2026-11-12T19:02:00Z", + "received_at": "2026-11-12T19:02:05Z", + "claim_in_future": false, + "answerer": "e0d1c2b3-a495-4687-8879-6a5b4c3d2e1f", + "authority": "Operator", + "request_id": "00000020-0000-4000-8000-000000000020" + }, + { + "schemaVersion": "gamecult.desk_answer.v0", + "answer_id": "n2", + "fork_id": "gamecult-chat:question:q3", + "outcome": "Refused", + "of": null, + "retracts": null, + "code": "fork_closed", + "options_hash": "ced892eb663c22e3d7ece602708043ee545f56b9d4e4421d7f5bfaa86c418ce2", + "choice": "no", + "note": "Keep it local: a slot mark is the author's call, and sharing it is a separate act.", + "answered_at": "2026-11-12T19:02:00Z", + "received_at": "2026-11-12T19:04:05Z", + "claim_in_future": false, + "answerer": "9f8e7d6c-5b4a-4392-8170-0f1e2d3c4b5a", + "authority": "Steward", + "request_id": "00000021-0000-4000-8000-000000000021" + } + ] + } + }, + "messagePackHex": "83AD736368656D6156657273696F6EB667616D6563756C742E666F726B2E656E7472792E7630A463617264DE0010AD736368656D6156657273696F6EB567616D6563756C742E666F726B2E636172642E7630A7666F726B5F6964B967616D6563756C742D636861743A7175657374696F6E3A7133A57469746C65A6466F726B2033A7636F6E74657874BD53686F756C64206120736C6F74206D61726B206265207368617265643FA76F7074696F6E739282A26964A3796573A474657874A953686172652069742E82A26964A26E6FA474657874AE4B656570206974206C6F63616C2EAE7265636F6D6D656E646174696F6EA26E6FA7646570656E647391D92967616D6563756C742D636861743A7175657374696F6E3A66696C652D7A6F6E652D61756469656E6365A56C65616E73C0A4666C6167C0A97261697365645F696E82A46B696E64A743757453706563A26964D92B63756C746C69622D676170733A6375745F737065633A6375742D736C6F742D746F6C6572616E63652E7234A8646563696465727391D92465306431633262332D613439352D343638372D383837392D366135623463336432653166A6737461747573A6636C6F736564A672756C696E67D92663756C746C69622D676170733A7265736F6C7574696F6E3A7175657374696F6E2E71332E6E31AA72756C65645F66726F6D91A26E31A56F7264657203AC6F7074696F6E735F68617368D94063656438393265623636336332326533643765636536303237303830343365653534356635366239643465343432316437663562666161383663343138636532A67265636F726485AD736368656D6156657273696F6EB767616D6563756C742E666F726B2E7265636F72642E7630A7666F726B5F6964B967616D6563756C742D636861743A7175657374696F6E3A7133AC6F7074696F6E735F68617368D94063656438393265623636336332326533643765636536303237303830343365653534356635366239643465343432316437663562666161383663343138636532B1636F756E7465645F616E737765725F6964A26E31A7616E737765727392DE0010AD736368656D6156657273696F6EB767616D6563756C742E6465736B5F616E737765722E7630A9616E737765725F6964A26E31A7666F726B5F6964B967616D6563756C742D636861743A7175657374696F6E3A7133A76F7574636F6D65A7436F756E746564A26F66C0A87265747261637473C0A4636F6465C0AC6F7074696F6E735F68617368D94063656438393265623636336332326533643765636536303237303830343365653534356635366239643465343432316437663562666161383663343138636532A663686F696365A3796573A46E6F7465D9524B656570206974206C6F63616C3A206120736C6F74206D61726B2069732074686520617574686F7227732063616C6C2C20616E642073686172696E672069742069732061207365706172617465206163742EAB616E7377657265645F6174B4323032362D31312D31325431393A30323A30305AAB72656365697665645F6174B4323032362D31312D31325431393A30323A30355AAF636C61696D5F696E5F667574757265C2A8616E737765726572D92465306431633262332D613439352D343638372D383837392D366135623463336432653166A9617574686F72697479A84F70657261746F72AA726571756573745F6964D92430303030303032302D303030302D343030302D383030302D303030303030303030303230DE0010AD736368656D6156657273696F6EB767616D6563756C742E6465736B5F616E737765722E7630A9616E737765725F6964A26E32A7666F726B5F6964B967616D6563756C742D636861743A7175657374696F6E3A7133A76F7574636F6D65A752656675736564A26F66C0A87265747261637473C0A4636F6465AB666F726B5F636C6F736564AC6F7074696F6E735F68617368D94063656438393265623636336332326533643765636536303237303830343365653534356635366239643465343432316437663562666161383663343138636532A663686F696365A26E6FA46E6F7465D9524B656570206974206C6F63616C3A206120736C6F74206D61726B2069732074686520617574686F7227732063616C6C2C20616E642073686172696E672069742069732061207365706172617465206163742EAB616E7377657265645F6174B4323032362D31312D31325431393A30323A30305AAB72656365697665645F6174B4323032362D31312D31325431393A30343A30355AAF636C61696D5F696E5F667574757265C2A8616E737765726572D92439663865376436632D356234612D343339322D383137302D306631653264336334623561A9617574686F72697479A753746577617264AA726571756573745F6964D92430303030303032312D303030302D343030302D383030302D303030303030303030303231" + } + ], + "invalid": [ + { + "name": "entry_without_record", + "why": "an entry holds the card and its record", + "message": { + "schemaVersion": "gamecult.fork.entry.v0", + "card": { + "schemaVersion": "gamecult.fork.card.v0", + "fork_id": "gamecult-chat:question:q5", + "title": "Fork 5", + "context": "Should a slot mark be shared?", + "options": [ + { + "id": "yes", + "text": "Share it." + }, + { + "id": "no", + "text": "Keep it local." + } + ], + "recommendation": "no", + "depends": [ + "gamecult-chat:question:file-zone-audience" + ], + "leans": null, + "flag": null, + "raised_in": { + "kind": "CutSpec", + "id": "cultlib-gaps:cut_spec:cut-slot-tolerance.r4" + }, + "deciders": [ + "e0d1c2b3-a495-4687-8879-6a5b4c3d2e1f" + ], + "status": "open", + "ruling": null, + "ruled_from": [], + "order": 5, + "options_hash": "16d20a6dc44b6cc00afd378e9f0c8b51dfe5b07e869e1a2aa9588de1de587fab" + } + }, + "messagePackHex": "82AD736368656D6156657273696F6EB667616D6563756C742E666F726B2E656E7472792E7630A463617264DE0010AD736368656D6156657273696F6EB567616D6563756C742E666F726B2E636172642E7630A7666F726B5F6964B967616D6563756C742D636861743A7175657374696F6E3A7135A57469746C65A6466F726B2035A7636F6E74657874BD53686F756C64206120736C6F74206D61726B206265207368617265643FA76F7074696F6E739282A26964A3796573A474657874A953686172652069742E82A26964A26E6FA474657874AE4B656570206974206C6F63616C2EAE7265636F6D6D656E646174696F6EA26E6FA7646570656E647391D92967616D6563756C742D636861743A7175657374696F6E3A66696C652D7A6F6E652D61756469656E6365A56C65616E73C0A4666C6167C0A97261697365645F696E82A46B696E64A743757453706563A26964D92B63756C746C69622D676170733A6375745F737065633A6375742D736C6F742D746F6C6572616E63652E7234A8646563696465727391D92465306431633262332D613439352D343638372D383837392D366135623463336432653166A6737461747573A46F70656EA672756C696E67C0AA72756C65645F66726F6D90A56F7264657205AC6F7074696F6E735F68617368D94031366432306136646334346236636330306166643337386539663063386235316466653562303765383639653161326161393538386465316465353837666162" + }, + { + "name": "record_of_another_fork", + "why": "rule: the record names the card's fork (not expressible in the schema)", + "message": { + "schemaVersion": "gamecult.fork.entry.v0", + "card": { + "schemaVersion": "gamecult.fork.card.v0", + "fork_id": "gamecult-chat:question:q5", + "title": "Fork 5", + "context": "Should a slot mark be shared?", + "options": [ + { + "id": "yes", + "text": "Share it." + }, + { + "id": "no", + "text": "Keep it local." + } + ], + "recommendation": "no", + "depends": [ + "gamecult-chat:question:file-zone-audience" + ], + "leans": null, + "flag": null, + "raised_in": { + "kind": "CutSpec", + "id": "cultlib-gaps:cut_spec:cut-slot-tolerance.r4" + }, + "deciders": [ + "e0d1c2b3-a495-4687-8879-6a5b4c3d2e1f" + ], + "status": "open", + "ruling": null, + "ruled_from": [], + "order": 5, + "options_hash": "16d20a6dc44b6cc00afd378e9f0c8b51dfe5b07e869e1a2aa9588de1de587fab" + }, + "record": { + "schemaVersion": "gamecult.fork.record.v0", + "fork_id": "gamecult-chat:question:q9", + "options_hash": "16d20a6dc44b6cc00afd378e9f0c8b51dfe5b07e869e1a2aa9588de1de587fab", + "counted_answer_id": "n1", + "answers": [ + { + "schemaVersion": "gamecult.desk_answer.v0", + "answer_id": "n1", + "fork_id": "gamecult-chat:question:q5", + "outcome": "Counted", + "of": null, + "retracts": null, + "code": null, + "options_hash": "16d20a6dc44b6cc00afd378e9f0c8b51dfe5b07e869e1a2aa9588de1de587fab", + "choice": "no", + "note": "Keep it local: a slot mark is the author's call, and sharing it is a separate act.", + "answered_at": "2026-11-12T19:02:00Z", + "received_at": "2026-11-12T19:02:05Z", + "claim_in_future": false, + "answerer": "e0d1c2b3-a495-4687-8879-6a5b4c3d2e1f", + "authority": "Operator", + "request_id": "0000001f-0000-4000-8000-00000000001f" + } + ] + } + }, + "messagePackHex": "83AD736368656D6156657273696F6EB667616D6563756C742E666F726B2E656E7472792E7630A463617264DE0010AD736368656D6156657273696F6EB567616D6563756C742E666F726B2E636172642E7630A7666F726B5F6964B967616D6563756C742D636861743A7175657374696F6E3A7135A57469746C65A6466F726B2035A7636F6E74657874BD53686F756C64206120736C6F74206D61726B206265207368617265643FA76F7074696F6E739282A26964A3796573A474657874A953686172652069742E82A26964A26E6FA474657874AE4B656570206974206C6F63616C2EAE7265636F6D6D656E646174696F6EA26E6FA7646570656E647391D92967616D6563756C742D636861743A7175657374696F6E3A66696C652D7A6F6E652D61756469656E6365A56C65616E73C0A4666C6167C0A97261697365645F696E82A46B696E64A743757453706563A26964D92B63756C746C69622D676170733A6375745F737065633A6375742D736C6F742D746F6C6572616E63652E7234A8646563696465727391D92465306431633262332D613439352D343638372D383837392D366135623463336432653166A6737461747573A46F70656EA672756C696E67C0AA72756C65645F66726F6D90A56F7264657205AC6F7074696F6E735F68617368D94031366432306136646334346236636330306166643337386539663063386235316466653562303765383639653161326161393538386465316465353837666162A67265636F726485AD736368656D6156657273696F6EB767616D6563756C742E666F726B2E7265636F72642E7630A7666F726B5F6964B967616D6563756C742D636861743A7175657374696F6E3A7139AC6F7074696F6E735F68617368D94031366432306136646334346236636330306166643337386539663063386235316466653562303765383639653161326161393538386465316465353837666162B1636F756E7465645F616E737765725F6964A26E31A7616E737765727391DE0010AD736368656D6156657273696F6EB767616D6563756C742E6465736B5F616E737765722E7630A9616E737765725F6964A26E31A7666F726B5F6964B967616D6563756C742D636861743A7175657374696F6E3A7135A76F7574636F6D65A7436F756E746564A26F66C0A87265747261637473C0A4636F6465C0AC6F7074696F6E735F68617368D94031366432306136646334346236636330306166643337386539663063386235316466653562303765383639653161326161393538386465316465353837666162A663686F696365A26E6FA46E6F7465D9524B656570206974206C6F63616C3A206120736C6F74206D61726B2069732074686520617574686F7227732063616C6C2C20616E642073686172696E672069742069732061207365706172617465206163742EAB616E7377657265645F6174B4323032362D31312D31325431393A30323A30305AAB72656365697665645F6174B4323032362D31312D31325431393A30323A30355AAF636C61696D5F696E5F667574757265C2A8616E737765726572D92465306431633262332D613439352D343638372D383837392D366135623463336432653166A9617574686F72697479A84F70657261746F72AA726571756573745F6964D92430303030303031662D303030302D343030302D383030302D303030303030303030303166" + }, + { + "name": "record_under_another_hash", + "why": "rule: the record is under the card's options_hash (not expressible in the schema)", + "message": { + "schemaVersion": "gamecult.fork.entry.v0", + "card": { + "schemaVersion": "gamecult.fork.card.v0", + "fork_id": "gamecult-chat:question:q5", + "title": "Fork 5", + "context": "Should a slot mark be shared?", + "options": [ + { + "id": "yes", + "text": "Share it." + }, + { + "id": "no", + "text": "Keep it local." + } + ], + "recommendation": "no", + "depends": [ + "gamecult-chat:question:file-zone-audience" + ], + "leans": null, + "flag": null, + "raised_in": { + "kind": "CutSpec", + "id": "cultlib-gaps:cut_spec:cut-slot-tolerance.r4" + }, + "deciders": [ + "e0d1c2b3-a495-4687-8879-6a5b4c3d2e1f" + ], + "status": "open", + "ruling": null, + "ruled_from": [], + "order": 5, + "options_hash": "16d20a6dc44b6cc00afd378e9f0c8b51dfe5b07e869e1a2aa9588de1de587fab" + }, + "record": { + "schemaVersion": "gamecult.fork.record.v0", + "fork_id": "gamecult-chat:question:q5", + "options_hash": "14b8ffbdb61106dbc06f69f3fc8e2072af7a205d766e4cc040e89733013d54dc", + "counted_answer_id": "n1", + "answers": [ + { + "schemaVersion": "gamecult.desk_answer.v0", + "answer_id": "n1", + "fork_id": "gamecult-chat:question:q5", + "outcome": "Counted", + "of": null, + "retracts": null, + "code": null, + "options_hash": "16d20a6dc44b6cc00afd378e9f0c8b51dfe5b07e869e1a2aa9588de1de587fab", + "choice": "no", + "note": "Keep it local: a slot mark is the author's call, and sharing it is a separate act.", + "answered_at": "2026-11-12T19:02:00Z", + "received_at": "2026-11-12T19:02:05Z", + "claim_in_future": false, + "answerer": "e0d1c2b3-a495-4687-8879-6a5b4c3d2e1f", + "authority": "Operator", + "request_id": "0000001f-0000-4000-8000-00000000001f" + } + ] + } + }, + "messagePackHex": "83AD736368656D6156657273696F6EB667616D6563756C742E666F726B2E656E7472792E7630A463617264DE0010AD736368656D6156657273696F6EB567616D6563756C742E666F726B2E636172642E7630A7666F726B5F6964B967616D6563756C742D636861743A7175657374696F6E3A7135A57469746C65A6466F726B2035A7636F6E74657874BD53686F756C64206120736C6F74206D61726B206265207368617265643FA76F7074696F6E739282A26964A3796573A474657874A953686172652069742E82A26964A26E6FA474657874AE4B656570206974206C6F63616C2EAE7265636F6D6D656E646174696F6EA26E6FA7646570656E647391D92967616D6563756C742D636861743A7175657374696F6E3A66696C652D7A6F6E652D61756469656E6365A56C65616E73C0A4666C6167C0A97261697365645F696E82A46B696E64A743757453706563A26964D92B63756C746C69622D676170733A6375745F737065633A6375742D736C6F742D746F6C6572616E63652E7234A8646563696465727391D92465306431633262332D613439352D343638372D383837392D366135623463336432653166A6737461747573A46F70656EA672756C696E67C0AA72756C65645F66726F6D90A56F7264657205AC6F7074696F6E735F68617368D94031366432306136646334346236636330306166643337386539663063386235316466653562303765383639653161326161393538386465316465353837666162A67265636F726485AD736368656D6156657273696F6EB767616D6563756C742E666F726B2E7265636F72642E7630A7666F726B5F6964B967616D6563756C742D636861743A7175657374696F6E3A7135AC6F7074696F6E735F68617368D94031346238666662646236313130366462633036663639663366633865323037326166376132303564373636653463633034306538393733333031336435346463B1636F756E7465645F616E737765725F6964A26E31A7616E737765727391DE0010AD736368656D6156657273696F6EB767616D6563756C742E6465736B5F616E737765722E7630A9616E737765725F6964A26E31A7666F726B5F6964B967616D6563756C742D636861743A7175657374696F6E3A7135A76F7574636F6D65A7436F756E746564A26F66C0A87265747261637473C0A4636F6465C0AC6F7074696F6E735F68617368D94031366432306136646334346236636330306166643337386539663063386235316466653562303765383639653161326161393538386465316465353837666162A663686F696365A26E6FA46E6F7465D9524B656570206974206C6F63616C3A206120736C6F74206D61726B2069732074686520617574686F7227732063616C6C2C20616E642073686172696E672069742069732061207365706172617465206163742EAB616E7377657265645F6174B4323032362D31312D31325431393A30323A30305AAB72656365697665645F6174B4323032362D31312D31325431393A30323A30355AAF636C61696D5F696E5F667574757265C2A8616E737765726572D92465306431633262332D613439352D343638372D383837392D366135623463336432653166A9617574686F72697479A84F70657261746F72AA726571756573745F6964D92430303030303031662D303030302D343030302D383030302D303030303030303030303166" + }, + { + "name": "counted_answer_not_derived", + "why": "rule: counted_answer_id is the first live Counted or Revision answer under the card's hash (not expressible in the schema)", + "message": { + "schemaVersion": "gamecult.fork.entry.v0", + "card": { + "schemaVersion": "gamecult.fork.card.v0", + "fork_id": "gamecult-chat:question:q5", + "title": "Fork 5", + "context": "Should a slot mark be shared?", + "options": [ + { + "id": "yes", + "text": "Share it." + }, + { + "id": "no", + "text": "Keep it local." + } + ], + "recommendation": "no", + "depends": [ + "gamecult-chat:question:file-zone-audience" + ], + "leans": null, + "flag": null, + "raised_in": { + "kind": "CutSpec", + "id": "cultlib-gaps:cut_spec:cut-slot-tolerance.r4" + }, + "deciders": [ + "e0d1c2b3-a495-4687-8879-6a5b4c3d2e1f" + ], + "status": "open", + "ruling": null, + "ruled_from": [], + "order": 5, + "options_hash": "16d20a6dc44b6cc00afd378e9f0c8b51dfe5b07e869e1a2aa9588de1de587fab" + }, + "record": { + "schemaVersion": "gamecult.fork.record.v0", + "fork_id": "gamecult-chat:question:q5", + "options_hash": "16d20a6dc44b6cc00afd378e9f0c8b51dfe5b07e869e1a2aa9588de1de587fab", + "counted_answer_id": null, + "answers": [ + { + "schemaVersion": "gamecult.desk_answer.v0", + "answer_id": "n1", + "fork_id": "gamecult-chat:question:q5", + "outcome": "Counted", + "of": null, + "retracts": null, + "code": null, + "options_hash": "16d20a6dc44b6cc00afd378e9f0c8b51dfe5b07e869e1a2aa9588de1de587fab", + "choice": "no", + "note": "Keep it local: a slot mark is the author's call, and sharing it is a separate act.", + "answered_at": "2026-11-12T19:02:00Z", + "received_at": "2026-11-12T19:02:05Z", + "claim_in_future": false, + "answerer": "e0d1c2b3-a495-4687-8879-6a5b4c3d2e1f", + "authority": "Operator", + "request_id": "0000001f-0000-4000-8000-00000000001f" + } + ] + } + }, + "messagePackHex": "83AD736368656D6156657273696F6EB667616D6563756C742E666F726B2E656E7472792E7630A463617264DE0010AD736368656D6156657273696F6EB567616D6563756C742E666F726B2E636172642E7630A7666F726B5F6964B967616D6563756C742D636861743A7175657374696F6E3A7135A57469746C65A6466F726B2035A7636F6E74657874BD53686F756C64206120736C6F74206D61726B206265207368617265643FA76F7074696F6E739282A26964A3796573A474657874A953686172652069742E82A26964A26E6FA474657874AE4B656570206974206C6F63616C2EAE7265636F6D6D656E646174696F6EA26E6FA7646570656E647391D92967616D6563756C742D636861743A7175657374696F6E3A66696C652D7A6F6E652D61756469656E6365A56C65616E73C0A4666C6167C0A97261697365645F696E82A46B696E64A743757453706563A26964D92B63756C746C69622D676170733A6375745F737065633A6375742D736C6F742D746F6C6572616E63652E7234A8646563696465727391D92465306431633262332D613439352D343638372D383837392D366135623463336432653166A6737461747573A46F70656EA672756C696E67C0AA72756C65645F66726F6D90A56F7264657205AC6F7074696F6E735F68617368D94031366432306136646334346236636330306166643337386539663063386235316466653562303765383639653161326161393538386465316465353837666162A67265636F726485AD736368656D6156657273696F6EB767616D6563756C742E666F726B2E7265636F72642E7630A7666F726B5F6964B967616D6563756C742D636861743A7175657374696F6E3A7135AC6F7074696F6E735F68617368D94031366432306136646334346236636330306166643337386539663063386235316466653562303765383639653161326161393538386465316465353837666162B1636F756E7465645F616E737765725F6964C0A7616E737765727391DE0010AD736368656D6156657273696F6EB767616D6563756C742E6465736B5F616E737765722E7630A9616E737765725F6964A26E31A7666F726B5F6964B967616D6563756C742D636861743A7175657374696F6E3A7135A76F7574636F6D65A7436F756E746564A26F66C0A87265747261637473C0A4636F6465C0AC6F7074696F6E735F68617368D94031366432306136646334346236636330306166643337386539663063386235316466653562303765383639653161326161393538386465316465353837666162A663686F696365A26E6FA46E6F7465D9524B656570206974206C6F63616C3A206120736C6F74206D61726B2069732074686520617574686F7227732063616C6C2C20616E642073686172696E672069742069732061207365706172617465206163742EAB616E7377657265645F6174B4323032362D31312D31325431393A30323A30305AAB72656365697665645F6174B4323032362D31312D31325431393A30323A30355AAF636C61696D5F696E5F667574757265C2A8616E737765726572D92465306431633262332D613439352D343638372D383837392D366135623463336432653166A9617574686F72697479A84F70657261746F72AA726571756573745F6964D92430303030303031662D303030302D343030302D383030302D303030303030303030303166" + }, + { + "name": "card_hash_wrong", + "why": "rule: the server computes options_hash and a client recomputes it; a card with another hash is no card", + "message": { + "schemaVersion": "gamecult.fork.entry.v0", + "card": { + "schemaVersion": "gamecult.fork.card.v0", + "fork_id": "gamecult-chat:question:q4", + "title": "Fork 4", + "context": "Should a slot mark be shared?", + "options": [ + { + "id": "yes", + "text": "Share it." + }, + { + "id": "no", + "text": "Keep it local." + } + ], + "recommendation": "no", + "depends": [ + "gamecult-chat:question:file-zone-audience" + ], + "leans": null, + "flag": null, + "raised_in": { + "kind": "CutSpec", + "id": "cultlib-gaps:cut_spec:cut-slot-tolerance.r4" + }, + "deciders": [ + "e0d1c2b3-a495-4687-8879-6a5b4c3d2e1f" + ], + "status": "open", + "ruling": null, + "ruled_from": [], + "order": 4, + "options_hash": "14b8ffbdb61106dbc06f69f3fc8e2072af7a205d766e4cc040e89733013d54dc" + }, + "record": { + "schemaVersion": "gamecult.fork.record.v0", + "fork_id": "gamecult-chat:question:q4", + "options_hash": "14b8ffbdb61106dbc06f69f3fc8e2072af7a205d766e4cc040e89733013d54dc", + "counted_answer_id": null, + "answers": [] + } + }, + "messagePackHex": "83AD736368656D6156657273696F6EB667616D6563756C742E666F726B2E656E7472792E7630A463617264DE0010AD736368656D6156657273696F6EB567616D6563756C742E666F726B2E636172642E7630A7666F726B5F6964B967616D6563756C742D636861743A7175657374696F6E3A7134A57469746C65A6466F726B2034A7636F6E74657874BD53686F756C64206120736C6F74206D61726B206265207368617265643FA76F7074696F6E739282A26964A3796573A474657874A953686172652069742E82A26964A26E6FA474657874AE4B656570206974206C6F63616C2EAE7265636F6D6D656E646174696F6EA26E6FA7646570656E647391D92967616D6563756C742D636861743A7175657374696F6E3A66696C652D7A6F6E652D61756469656E6365A56C65616E73C0A4666C6167C0A97261697365645F696E82A46B696E64A743757453706563A26964D92B63756C746C69622D676170733A6375745F737065633A6375742D736C6F742D746F6C6572616E63652E7234A8646563696465727391D92465306431633262332D613439352D343638372D383837392D366135623463336432653166A6737461747573A46F70656EA672756C696E67C0AA72756C65645F66726F6D90A56F7264657204AC6F7074696F6E735F68617368D94031346238666662646236313130366462633036663639663366633865323037326166376132303564373636653463633034306538393733333031336435346463A67265636F726485AD736368656D6156657273696F6EB767616D6563756C742E666F726B2E7265636F72642E7630A7666F726B5F6964B967616D6563756C742D636861743A7175657374696F6E3A7134AC6F7074696F6E735F68617368D94031346238666662646236313130366462633036663639663366633865323037326166376132303564373636653463633034306538393733333031336435346463B1636F756E7465645F616E737765725F6964C0A7616E737765727390" + } + ] + }, + "listInvalid": [ + { + "name": "next_cursor_malformed", + "why": "a cursor is c", + "message": { + "schemaVersion": "gamecult.fork.list.v0", + "mind": "gamecult-chat", + "stamp": "s412", + "open_count": 3, + "recorded_count": 2, + "forks": [ + { + "schemaVersion": "gamecult.fork.entry.v0", + "card": { + "schemaVersion": "gamecult.fork.card.v0", + "fork_id": "gamecult-chat:question:q5", + "title": "Fork 5", + "context": "Should a slot mark be shared?", + "options": [ + { + "id": "yes", + "text": "Share it." + }, + { + "id": "no", + "text": "Keep it local." + } + ], + "recommendation": "no", + "depends": [ + "gamecult-chat:question:file-zone-audience" + ], + "leans": null, + "flag": null, + "raised_in": { + "kind": "CutSpec", + "id": "cultlib-gaps:cut_spec:cut-slot-tolerance.r4" + }, + "deciders": [ + "e0d1c2b3-a495-4687-8879-6a5b4c3d2e1f" + ], + "status": "open", + "ruling": null, + "ruled_from": [], + "order": 5, + "options_hash": "16d20a6dc44b6cc00afd378e9f0c8b51dfe5b07e869e1a2aa9588de1de587fab" + }, + "record": { + "schemaVersion": "gamecult.fork.record.v0", + "fork_id": "gamecult-chat:question:q5", + "options_hash": "16d20a6dc44b6cc00afd378e9f0c8b51dfe5b07e869e1a2aa9588de1de587fab", + "counted_answer_id": "n1", + "answers": [ + { + "schemaVersion": "gamecult.desk_answer.v0", + "answer_id": "n1", + "fork_id": "gamecult-chat:question:q5", + "outcome": "Counted", + "of": null, + "retracts": null, + "code": null, + "options_hash": "16d20a6dc44b6cc00afd378e9f0c8b51dfe5b07e869e1a2aa9588de1de587fab", + "choice": "no", + "note": "Keep it local: a slot mark is the author's call, and sharing it is a separate act.", + "answered_at": "2026-11-12T19:02:00Z", + "received_at": "2026-11-12T19:02:05Z", + "claim_in_future": false, + "answerer": "e0d1c2b3-a495-4687-8879-6a5b4c3d2e1f", + "authority": "Operator", + "request_id": "0000001f-0000-4000-8000-00000000001f" + } + ] + } + }, + { + "schemaVersion": "gamecult.fork.entry.v0", + "card": { + "schemaVersion": "gamecult.fork.card.v0", + "fork_id": "gamecult-chat:question:q4", + "title": "Fork 4", + "context": "Should a slot mark be shared?", + "options": [ + { + "id": "yes", + "text": "Share it." + }, + { + "id": "no", + "text": "Keep it local." + } + ], + "recommendation": "no", + "depends": [ + "gamecult-chat:question:file-zone-audience" + ], + "leans": null, + "flag": null, + "raised_in": { + "kind": "CutSpec", + "id": "cultlib-gaps:cut_spec:cut-slot-tolerance.r4" + }, + "deciders": [ + "e0d1c2b3-a495-4687-8879-6a5b4c3d2e1f" + ], + "status": "open", + "ruling": null, + "ruled_from": [], + "order": 4, + "options_hash": "a64604237f906b6fec9cb292863685d966abf8048f276afa24cd4f1e1c4c74aa" + }, + "record": { + "schemaVersion": "gamecult.fork.record.v0", + "fork_id": "gamecult-chat:question:q4", + "options_hash": "a64604237f906b6fec9cb292863685d966abf8048f276afa24cd4f1e1c4c74aa", + "counted_answer_id": null, + "answers": [] + } + } + ], + "next_cursor": "more" + }, + "messagePackHex": "87AD736368656D6156657273696F6EB567616D6563756C742E666F726B2E6C6973742E7630A46D696E64AD67616D6563756C742D63686174A57374616D70A473343132AA6F70656E5F636F756E7403AE7265636F726465645F636F756E7402A5666F726B739283AD736368656D6156657273696F6EB667616D6563756C742E666F726B2E656E7472792E7630A463617264DE0010AD736368656D6156657273696F6EB567616D6563756C742E666F726B2E636172642E7630A7666F726B5F6964B967616D6563756C742D636861743A7175657374696F6E3A7135A57469746C65A6466F726B2035A7636F6E74657874BD53686F756C64206120736C6F74206D61726B206265207368617265643FA76F7074696F6E739282A26964A3796573A474657874A953686172652069742E82A26964A26E6FA474657874AE4B656570206974206C6F63616C2EAE7265636F6D6D656E646174696F6EA26E6FA7646570656E647391D92967616D6563756C742D636861743A7175657374696F6E3A66696C652D7A6F6E652D61756469656E6365A56C65616E73C0A4666C6167C0A97261697365645F696E82A46B696E64A743757453706563A26964D92B63756C746C69622D676170733A6375745F737065633A6375742D736C6F742D746F6C6572616E63652E7234A8646563696465727391D92465306431633262332D613439352D343638372D383837392D366135623463336432653166A6737461747573A46F70656EA672756C696E67C0AA72756C65645F66726F6D90A56F7264657205AC6F7074696F6E735F68617368D94031366432306136646334346236636330306166643337386539663063386235316466653562303765383639653161326161393538386465316465353837666162A67265636F726485AD736368656D6156657273696F6EB767616D6563756C742E666F726B2E7265636F72642E7630A7666F726B5F6964B967616D6563756C742D636861743A7175657374696F6E3A7135AC6F7074696F6E735F68617368D94031366432306136646334346236636330306166643337386539663063386235316466653562303765383639653161326161393538386465316465353837666162B1636F756E7465645F616E737765725F6964A26E31A7616E737765727391DE0010AD736368656D6156657273696F6EB767616D6563756C742E6465736B5F616E737765722E7630A9616E737765725F6964A26E31A7666F726B5F6964B967616D6563756C742D636861743A7175657374696F6E3A7135A76F7574636F6D65A7436F756E746564A26F66C0A87265747261637473C0A4636F6465C0AC6F7074696F6E735F68617368D94031366432306136646334346236636330306166643337386539663063386235316466653562303765383639653161326161393538386465316465353837666162A663686F696365A26E6FA46E6F7465D9524B656570206974206C6F63616C3A206120736C6F74206D61726B2069732074686520617574686F7227732063616C6C2C20616E642073686172696E672069742069732061207365706172617465206163742EAB616E7377657265645F6174B4323032362D31312D31325431393A30323A30305AAB72656365697665645F6174B4323032362D31312D31325431393A30323A30355AAF636C61696D5F696E5F667574757265C2A8616E737765726572D92465306431633262332D613439352D343638372D383837392D366135623463336432653166A9617574686F72697479A84F70657261746F72AA726571756573745F6964D92430303030303031662D303030302D343030302D383030302D30303030303030303030316683AD736368656D6156657273696F6EB667616D6563756C742E666F726B2E656E7472792E7630A463617264DE0010AD736368656D6156657273696F6EB567616D6563756C742E666F726B2E636172642E7630A7666F726B5F6964B967616D6563756C742D636861743A7175657374696F6E3A7134A57469746C65A6466F726B2034A7636F6E74657874BD53686F756C64206120736C6F74206D61726B206265207368617265643FA76F7074696F6E739282A26964A3796573A474657874A953686172652069742E82A26964A26E6FA474657874AE4B656570206974206C6F63616C2EAE7265636F6D6D656E646174696F6EA26E6FA7646570656E647391D92967616D6563756C742D636861743A7175657374696F6E3A66696C652D7A6F6E652D61756469656E6365A56C65616E73C0A4666C6167C0A97261697365645F696E82A46B696E64A743757453706563A26964D92B63756C746C69622D676170733A6375745F737065633A6375742D736C6F742D746F6C6572616E63652E7234A8646563696465727391D92465306431633262332D613439352D343638372D383837392D366135623463336432653166A6737461747573A46F70656EA672756C696E67C0AA72756C65645F66726F6D90A56F7264657204AC6F7074696F6E735F68617368D94061363436303432333766393036623666656339636232393238363336383564393636616266383034386632373661666132346364346631653163346337346161A67265636F726485AD736368656D6156657273696F6EB767616D6563756C742E666F726B2E7265636F72642E7630A7666F726B5F6964B967616D6563756C742D636861743A7175657374696F6E3A7134AC6F7074696F6E735F68617368D94061363436303432333766393036623666656339636232393238363336383564393636616266383034386632373661666132346364346631653163346337346161B1636F756E7465645F616E737765725F6964C0A7616E737765727390AB6E6578745F637572736F72A46D6F7265" + }, + { + "name": "stamp_malformed", + "why": "a stamp is s", + "message": { + "schemaVersion": "gamecult.fork.list.v0", + "mind": "gamecult-chat", + "stamp": "412", + "open_count": 3, + "recorded_count": 2, + "forks": [ + { + "schemaVersion": "gamecult.fork.entry.v0", + "card": { + "schemaVersion": "gamecult.fork.card.v0", + "fork_id": "gamecult-chat:question:q5", + "title": "Fork 5", + "context": "Should a slot mark be shared?", + "options": [ + { + "id": "yes", + "text": "Share it." + }, + { + "id": "no", + "text": "Keep it local." + } + ], + "recommendation": "no", + "depends": [ + "gamecult-chat:question:file-zone-audience" + ], + "leans": null, + "flag": null, + "raised_in": { + "kind": "CutSpec", + "id": "cultlib-gaps:cut_spec:cut-slot-tolerance.r4" + }, + "deciders": [ + "e0d1c2b3-a495-4687-8879-6a5b4c3d2e1f" + ], + "status": "open", + "ruling": null, + "ruled_from": [], + "order": 5, + "options_hash": "16d20a6dc44b6cc00afd378e9f0c8b51dfe5b07e869e1a2aa9588de1de587fab" + }, + "record": { + "schemaVersion": "gamecult.fork.record.v0", + "fork_id": "gamecult-chat:question:q5", + "options_hash": "16d20a6dc44b6cc00afd378e9f0c8b51dfe5b07e869e1a2aa9588de1de587fab", + "counted_answer_id": "n1", + "answers": [ + { + "schemaVersion": "gamecult.desk_answer.v0", + "answer_id": "n1", + "fork_id": "gamecult-chat:question:q5", + "outcome": "Counted", + "of": null, + "retracts": null, + "code": null, + "options_hash": "16d20a6dc44b6cc00afd378e9f0c8b51dfe5b07e869e1a2aa9588de1de587fab", + "choice": "no", + "note": "Keep it local: a slot mark is the author's call, and sharing it is a separate act.", + "answered_at": "2026-11-12T19:02:00Z", + "received_at": "2026-11-12T19:02:05Z", + "claim_in_future": false, + "answerer": "e0d1c2b3-a495-4687-8879-6a5b4c3d2e1f", + "authority": "Operator", + "request_id": "0000001f-0000-4000-8000-00000000001f" + } + ] + } + }, + { + "schemaVersion": "gamecult.fork.entry.v0", + "card": { + "schemaVersion": "gamecult.fork.card.v0", + "fork_id": "gamecult-chat:question:q4", + "title": "Fork 4", + "context": "Should a slot mark be shared?", + "options": [ + { + "id": "yes", + "text": "Share it." + }, + { + "id": "no", + "text": "Keep it local." + } + ], + "recommendation": "no", + "depends": [ + "gamecult-chat:question:file-zone-audience" + ], + "leans": null, + "flag": null, + "raised_in": { + "kind": "CutSpec", + "id": "cultlib-gaps:cut_spec:cut-slot-tolerance.r4" + }, + "deciders": [ + "e0d1c2b3-a495-4687-8879-6a5b4c3d2e1f" + ], + "status": "open", + "ruling": null, + "ruled_from": [], + "order": 4, + "options_hash": "a64604237f906b6fec9cb292863685d966abf8048f276afa24cd4f1e1c4c74aa" + }, + "record": { + "schemaVersion": "gamecult.fork.record.v0", + "fork_id": "gamecult-chat:question:q4", + "options_hash": "a64604237f906b6fec9cb292863685d966abf8048f276afa24cd4f1e1c4c74aa", + "counted_answer_id": null, + "answers": [] + } + } + ], + "next_cursor": "c4" + }, + "messagePackHex": "87AD736368656D6156657273696F6EB567616D6563756C742E666F726B2E6C6973742E7630A46D696E64AD67616D6563756C742D63686174A57374616D70A3343132AA6F70656E5F636F756E7403AE7265636F726465645F636F756E7402A5666F726B739283AD736368656D6156657273696F6EB667616D6563756C742E666F726B2E656E7472792E7630A463617264DE0010AD736368656D6156657273696F6EB567616D6563756C742E666F726B2E636172642E7630A7666F726B5F6964B967616D6563756C742D636861743A7175657374696F6E3A7135A57469746C65A6466F726B2035A7636F6E74657874BD53686F756C64206120736C6F74206D61726B206265207368617265643FA76F7074696F6E739282A26964A3796573A474657874A953686172652069742E82A26964A26E6FA474657874AE4B656570206974206C6F63616C2EAE7265636F6D6D656E646174696F6EA26E6FA7646570656E647391D92967616D6563756C742D636861743A7175657374696F6E3A66696C652D7A6F6E652D61756469656E6365A56C65616E73C0A4666C6167C0A97261697365645F696E82A46B696E64A743757453706563A26964D92B63756C746C69622D676170733A6375745F737065633A6375742D736C6F742D746F6C6572616E63652E7234A8646563696465727391D92465306431633262332D613439352D343638372D383837392D366135623463336432653166A6737461747573A46F70656EA672756C696E67C0AA72756C65645F66726F6D90A56F7264657205AC6F7074696F6E735F68617368D94031366432306136646334346236636330306166643337386539663063386235316466653562303765383639653161326161393538386465316465353837666162A67265636F726485AD736368656D6156657273696F6EB767616D6563756C742E666F726B2E7265636F72642E7630A7666F726B5F6964B967616D6563756C742D636861743A7175657374696F6E3A7135AC6F7074696F6E735F68617368D94031366432306136646334346236636330306166643337386539663063386235316466653562303765383639653161326161393538386465316465353837666162B1636F756E7465645F616E737765725F6964A26E31A7616E737765727391DE0010AD736368656D6156657273696F6EB767616D6563756C742E6465736B5F616E737765722E7630A9616E737765725F6964A26E31A7666F726B5F6964B967616D6563756C742D636861743A7175657374696F6E3A7135A76F7574636F6D65A7436F756E746564A26F66C0A87265747261637473C0A4636F6465C0AC6F7074696F6E735F68617368D94031366432306136646334346236636330306166643337386539663063386235316466653562303765383639653161326161393538386465316465353837666162A663686F696365A26E6FA46E6F7465D9524B656570206974206C6F63616C3A206120736C6F74206D61726B2069732074686520617574686F7227732063616C6C2C20616E642073686172696E672069742069732061207365706172617465206163742EAB616E7377657265645F6174B4323032362D31312D31325431393A30323A30305AAB72656365697665645F6174B4323032362D31312D31325431393A30323A30355AAF636C61696D5F696E5F667574757265C2A8616E737765726572D92465306431633262332D613439352D343638372D383837392D366135623463336432653166A9617574686F72697479A84F70657261746F72AA726571756573745F6964D92430303030303031662D303030302D343030302D383030302D30303030303030303030316683AD736368656D6156657273696F6EB667616D6563756C742E666F726B2E656E7472792E7630A463617264DE0010AD736368656D6156657273696F6EB567616D6563756C742E666F726B2E636172642E7630A7666F726B5F6964B967616D6563756C742D636861743A7175657374696F6E3A7134A57469746C65A6466F726B2034A7636F6E74657874BD53686F756C64206120736C6F74206D61726B206265207368617265643FA76F7074696F6E739282A26964A3796573A474657874A953686172652069742E82A26964A26E6FA474657874AE4B656570206974206C6F63616C2EAE7265636F6D6D656E646174696F6EA26E6FA7646570656E647391D92967616D6563756C742D636861743A7175657374696F6E3A66696C652D7A6F6E652D61756469656E6365A56C65616E73C0A4666C6167C0A97261697365645F696E82A46B696E64A743757453706563A26964D92B63756C746C69622D676170733A6375745F737065633A6375742D736C6F742D746F6C6572616E63652E7234A8646563696465727391D92465306431633262332D613439352D343638372D383837392D366135623463336432653166A6737461747573A46F70656EA672756C696E67C0AA72756C65645F66726F6D90A56F7264657204AC6F7074696F6E735F68617368D94061363436303432333766393036623666656339636232393238363336383564393636616266383034386632373661666132346364346631653163346337346161A67265636F726485AD736368656D6156657273696F6EB767616D6563756C742E666F726B2E7265636F72642E7630A7666F726B5F6964B967616D6563756C742D636861743A7175657374696F6E3A7134AC6F7074696F6E735F68617368D94061363436303432333766393036623666656339636232393238363336383564393636616266383034386632373661666132346364346631653163346337346161B1636F756E7465645F616E737765725F6964C0A7616E737765727390AB6E6578745F637572736F72A26334" + }, + { + "name": "count_negative", + "why": "counts are not negative", + "message": { + "schemaVersion": "gamecult.fork.list.v0", + "mind": "gamecult-chat", + "stamp": "s412", + "open_count": -1, + "recorded_count": 2, + "forks": [ + { + "schemaVersion": "gamecult.fork.entry.v0", + "card": { + "schemaVersion": "gamecult.fork.card.v0", + "fork_id": "gamecult-chat:question:q5", + "title": "Fork 5", + "context": "Should a slot mark be shared?", + "options": [ + { + "id": "yes", + "text": "Share it." + }, + { + "id": "no", + "text": "Keep it local." + } + ], + "recommendation": "no", + "depends": [ + "gamecult-chat:question:file-zone-audience" + ], + "leans": null, + "flag": null, + "raised_in": { + "kind": "CutSpec", + "id": "cultlib-gaps:cut_spec:cut-slot-tolerance.r4" + }, + "deciders": [ + "e0d1c2b3-a495-4687-8879-6a5b4c3d2e1f" + ], + "status": "open", + "ruling": null, + "ruled_from": [], + "order": 5, + "options_hash": "16d20a6dc44b6cc00afd378e9f0c8b51dfe5b07e869e1a2aa9588de1de587fab" + }, + "record": { + "schemaVersion": "gamecult.fork.record.v0", + "fork_id": "gamecult-chat:question:q5", + "options_hash": "16d20a6dc44b6cc00afd378e9f0c8b51dfe5b07e869e1a2aa9588de1de587fab", + "counted_answer_id": "n1", + "answers": [ + { + "schemaVersion": "gamecult.desk_answer.v0", + "answer_id": "n1", + "fork_id": "gamecult-chat:question:q5", + "outcome": "Counted", + "of": null, + "retracts": null, + "code": null, + "options_hash": "16d20a6dc44b6cc00afd378e9f0c8b51dfe5b07e869e1a2aa9588de1de587fab", + "choice": "no", + "note": "Keep it local: a slot mark is the author's call, and sharing it is a separate act.", + "answered_at": "2026-11-12T19:02:00Z", + "received_at": "2026-11-12T19:02:05Z", + "claim_in_future": false, + "answerer": "e0d1c2b3-a495-4687-8879-6a5b4c3d2e1f", + "authority": "Operator", + "request_id": "0000001f-0000-4000-8000-00000000001f" + } + ] + } + }, + { + "schemaVersion": "gamecult.fork.entry.v0", + "card": { + "schemaVersion": "gamecult.fork.card.v0", + "fork_id": "gamecult-chat:question:q4", + "title": "Fork 4", + "context": "Should a slot mark be shared?", + "options": [ + { + "id": "yes", + "text": "Share it." + }, + { + "id": "no", + "text": "Keep it local." + } + ], + "recommendation": "no", + "depends": [ + "gamecult-chat:question:file-zone-audience" + ], + "leans": null, + "flag": null, + "raised_in": { + "kind": "CutSpec", + "id": "cultlib-gaps:cut_spec:cut-slot-tolerance.r4" + }, + "deciders": [ + "e0d1c2b3-a495-4687-8879-6a5b4c3d2e1f" + ], + "status": "open", + "ruling": null, + "ruled_from": [], + "order": 4, + "options_hash": "a64604237f906b6fec9cb292863685d966abf8048f276afa24cd4f1e1c4c74aa" + }, + "record": { + "schemaVersion": "gamecult.fork.record.v0", + "fork_id": "gamecult-chat:question:q4", + "options_hash": "a64604237f906b6fec9cb292863685d966abf8048f276afa24cd4f1e1c4c74aa", + "counted_answer_id": null, + "answers": [] + } + } + ], + "next_cursor": "c4" + }, + "messagePackHex": "87AD736368656D6156657273696F6EB567616D6563756C742E666F726B2E6C6973742E7630A46D696E64AD67616D6563756C742D63686174A57374616D70A473343132AA6F70656E5F636F756E74FFAE7265636F726465645F636F756E7402A5666F726B739283AD736368656D6156657273696F6EB667616D6563756C742E666F726B2E656E7472792E7630A463617264DE0010AD736368656D6156657273696F6EB567616D6563756C742E666F726B2E636172642E7630A7666F726B5F6964B967616D6563756C742D636861743A7175657374696F6E3A7135A57469746C65A6466F726B2035A7636F6E74657874BD53686F756C64206120736C6F74206D61726B206265207368617265643FA76F7074696F6E739282A26964A3796573A474657874A953686172652069742E82A26964A26E6FA474657874AE4B656570206974206C6F63616C2EAE7265636F6D6D656E646174696F6EA26E6FA7646570656E647391D92967616D6563756C742D636861743A7175657374696F6E3A66696C652D7A6F6E652D61756469656E6365A56C65616E73C0A4666C6167C0A97261697365645F696E82A46B696E64A743757453706563A26964D92B63756C746C69622D676170733A6375745F737065633A6375742D736C6F742D746F6C6572616E63652E7234A8646563696465727391D92465306431633262332D613439352D343638372D383837392D366135623463336432653166A6737461747573A46F70656EA672756C696E67C0AA72756C65645F66726F6D90A56F7264657205AC6F7074696F6E735F68617368D94031366432306136646334346236636330306166643337386539663063386235316466653562303765383639653161326161393538386465316465353837666162A67265636F726485AD736368656D6156657273696F6EB767616D6563756C742E666F726B2E7265636F72642E7630A7666F726B5F6964B967616D6563756C742D636861743A7175657374696F6E3A7135AC6F7074696F6E735F68617368D94031366432306136646334346236636330306166643337386539663063386235316466653562303765383639653161326161393538386465316465353837666162B1636F756E7465645F616E737765725F6964A26E31A7616E737765727391DE0010AD736368656D6156657273696F6EB767616D6563756C742E6465736B5F616E737765722E7630A9616E737765725F6964A26E31A7666F726B5F6964B967616D6563756C742D636861743A7175657374696F6E3A7135A76F7574636F6D65A7436F756E746564A26F66C0A87265747261637473C0A4636F6465C0AC6F7074696F6E735F68617368D94031366432306136646334346236636330306166643337386539663063386235316466653562303765383639653161326161393538386465316465353837666162A663686F696365A26E6FA46E6F7465D9524B656570206974206C6F63616C3A206120736C6F74206D61726B2069732074686520617574686F7227732063616C6C2C20616E642073686172696E672069742069732061207365706172617465206163742EAB616E7377657265645F6174B4323032362D31312D31325431393A30323A30305AAB72656365697665645F6174B4323032362D31312D31325431393A30323A30355AAF636C61696D5F696E5F667574757265C2A8616E737765726572D92465306431633262332D613439352D343638372D383837392D366135623463336432653166A9617574686F72697479A84F70657261746F72AA726571756573745F6964D92430303030303031662D303030302D343030302D383030302D30303030303030303030316683AD736368656D6156657273696F6EB667616D6563756C742E666F726B2E656E7472792E7630A463617264DE0010AD736368656D6156657273696F6EB567616D6563756C742E666F726B2E636172642E7630A7666F726B5F6964B967616D6563756C742D636861743A7175657374696F6E3A7134A57469746C65A6466F726B2034A7636F6E74657874BD53686F756C64206120736C6F74206D61726B206265207368617265643FA76F7074696F6E739282A26964A3796573A474657874A953686172652069742E82A26964A26E6FA474657874AE4B656570206974206C6F63616C2EAE7265636F6D6D656E646174696F6EA26E6FA7646570656E647391D92967616D6563756C742D636861743A7175657374696F6E3A66696C652D7A6F6E652D61756469656E6365A56C65616E73C0A4666C6167C0A97261697365645F696E82A46B696E64A743757453706563A26964D92B63756C746C69622D676170733A6375745F737065633A6375742D736C6F742D746F6C6572616E63652E7234A8646563696465727391D92465306431633262332D613439352D343638372D383837392D366135623463336432653166A6737461747573A46F70656EA672756C696E67C0AA72756C65645F66726F6D90A56F7264657204AC6F7074696F6E735F68617368D94061363436303432333766393036623666656339636232393238363336383564393636616266383034386632373661666132346364346631653163346337346161A67265636F726485AD736368656D6156657273696F6EB767616D6563756C742E666F726B2E7265636F72642E7630A7666F726B5F6964B967616D6563756C742D636861743A7175657374696F6E3A7134AC6F7074696F6E735F68617368D94061363436303432333766393036623666656339636232393238363336383564393636616266383034386632373661666132346364346631653163346337346161B1636F756E7465645F616E737765725F6964C0A7616E737765727390AB6E6578745F637572736F72A26334" + } + ], + "listScenarios": [ + { + "name": "newest_first_paged_open_only", + "ordinal": 412, + "entries": [ + { + "schemaVersion": "gamecult.fork.entry.v0", + "card": { + "schemaVersion": "gamecult.fork.card.v0", + "fork_id": "gamecult-chat:question:q5", + "title": "Fork 5", + "context": "Should a slot mark be shared?", + "options": [ + { + "id": "yes", + "text": "Share it." + }, + { + "id": "no", + "text": "Keep it local." + } + ], + "recommendation": "no", + "depends": [ + "gamecult-chat:question:file-zone-audience" + ], + "leans": null, + "flag": null, + "raised_in": { + "kind": "CutSpec", + "id": "cultlib-gaps:cut_spec:cut-slot-tolerance.r4" + }, + "deciders": [ + "e0d1c2b3-a495-4687-8879-6a5b4c3d2e1f" + ], + "status": "open", + "ruling": null, + "ruled_from": [], + "order": 5, + "options_hash": "16d20a6dc44b6cc00afd378e9f0c8b51dfe5b07e869e1a2aa9588de1de587fab" + }, + "record": { + "schemaVersion": "gamecult.fork.record.v0", + "fork_id": "gamecult-chat:question:q5", + "options_hash": "16d20a6dc44b6cc00afd378e9f0c8b51dfe5b07e869e1a2aa9588de1de587fab", + "counted_answer_id": "n1", + "answers": [ + { + "schemaVersion": "gamecult.desk_answer.v0", + "answer_id": "n1", + "fork_id": "gamecult-chat:question:q5", + "outcome": "Counted", + "of": null, + "retracts": null, + "code": null, + "options_hash": "16d20a6dc44b6cc00afd378e9f0c8b51dfe5b07e869e1a2aa9588de1de587fab", + "choice": "no", + "note": "Keep it local: a slot mark is the author's call, and sharing it is a separate act.", + "answered_at": "2026-11-12T19:02:00Z", + "received_at": "2026-11-12T19:02:05Z", + "claim_in_future": false, + "answerer": "e0d1c2b3-a495-4687-8879-6a5b4c3d2e1f", + "authority": "Operator", + "request_id": "0000001f-0000-4000-8000-00000000001f" + } + ] + } + }, + { + "schemaVersion": "gamecult.fork.entry.v0", + "card": { + "schemaVersion": "gamecult.fork.card.v0", + "fork_id": "gamecult-chat:question:q4", + "title": "Fork 4", + "context": "Should a slot mark be shared?", + "options": [ + { + "id": "yes", + "text": "Share it." + }, + { + "id": "no", + "text": "Keep it local." + } + ], + "recommendation": "no", + "depends": [ + "gamecult-chat:question:file-zone-audience" + ], + "leans": null, + "flag": null, + "raised_in": { + "kind": "CutSpec", + "id": "cultlib-gaps:cut_spec:cut-slot-tolerance.r4" + }, + "deciders": [ + "e0d1c2b3-a495-4687-8879-6a5b4c3d2e1f" + ], + "status": "open", + "ruling": null, + "ruled_from": [], + "order": 4, + "options_hash": "a64604237f906b6fec9cb292863685d966abf8048f276afa24cd4f1e1c4c74aa" + }, + "record": { + "schemaVersion": "gamecult.fork.record.v0", + "fork_id": "gamecult-chat:question:q4", + "options_hash": "a64604237f906b6fec9cb292863685d966abf8048f276afa24cd4f1e1c4c74aa", + "counted_answer_id": null, + "answers": [] + } + }, + { + "schemaVersion": "gamecult.fork.entry.v0", + "card": { + "schemaVersion": "gamecult.fork.card.v0", + "fork_id": "gamecult-chat:question:q3", + "title": "Fork 3", + "context": "Should a slot mark be shared?", + "options": [ + { + "id": "yes", + "text": "Share it." + }, + { + "id": "no", + "text": "Keep it local." + } + ], + "recommendation": "no", + "depends": [ + "gamecult-chat:question:file-zone-audience" + ], + "leans": null, + "flag": null, + "raised_in": { + "kind": "CutSpec", + "id": "cultlib-gaps:cut_spec:cut-slot-tolerance.r4" + }, + "deciders": [ + "e0d1c2b3-a495-4687-8879-6a5b4c3d2e1f" + ], + "status": "closed", + "ruling": "cultlib-gaps:resolution:question.q3.n1", + "ruled_from": [ + "n1" + ], + "order": 3, + "options_hash": "ced892eb663c22e3d7ece602708043ee545f56b9d4e4421d7f5bfaa86c418ce2" + }, + "record": { + "schemaVersion": "gamecult.fork.record.v0", + "fork_id": "gamecult-chat:question:q3", + "options_hash": "ced892eb663c22e3d7ece602708043ee545f56b9d4e4421d7f5bfaa86c418ce2", + "counted_answer_id": "n1", + "answers": [ + { + "schemaVersion": "gamecult.desk_answer.v0", + "answer_id": "n1", + "fork_id": "gamecult-chat:question:q3", + "outcome": "Counted", + "of": null, + "retracts": null, + "code": null, + "options_hash": "ced892eb663c22e3d7ece602708043ee545f56b9d4e4421d7f5bfaa86c418ce2", + "choice": "yes", + "note": "Keep it local: a slot mark is the author's call, and sharing it is a separate act.", + "answered_at": "2026-11-12T19:02:00Z", + "received_at": "2026-11-12T19:02:05Z", + "claim_in_future": false, + "answerer": "e0d1c2b3-a495-4687-8879-6a5b4c3d2e1f", + "authority": "Operator", + "request_id": "00000020-0000-4000-8000-000000000020" + }, + { + "schemaVersion": "gamecult.desk_answer.v0", + "answer_id": "n2", + "fork_id": "gamecult-chat:question:q3", + "outcome": "Refused", + "of": null, + "retracts": null, + "code": "fork_closed", + "options_hash": "ced892eb663c22e3d7ece602708043ee545f56b9d4e4421d7f5bfaa86c418ce2", + "choice": "no", + "note": "Keep it local: a slot mark is the author's call, and sharing it is a separate act.", + "answered_at": "2026-11-12T19:02:00Z", + "received_at": "2026-11-12T19:04:05Z", + "claim_in_future": false, + "answerer": "9f8e7d6c-5b4a-4392-8170-0f1e2d3c4b5a", + "authority": "Steward", + "request_id": "00000021-0000-4000-8000-000000000021" + } + ] + } + }, + { + "schemaVersion": "gamecult.fork.entry.v0", + "card": { + "schemaVersion": "gamecult.fork.card.v0", + "fork_id": "gamecult-chat:question:q2", + "title": "Fork 2", + "context": "Should a slot mark be shared?", + "options": [ + { + "id": "yes", + "text": "Share it." + }, + { + "id": "no", + "text": "Keep it local." + } + ], + "recommendation": "no", + "depends": [ + "gamecult-chat:question:file-zone-audience" + ], + "leans": null, + "flag": null, + "raised_in": { + "kind": "CutSpec", + "id": "cultlib-gaps:cut_spec:cut-slot-tolerance.r4" + }, + "deciders": [ + "e0d1c2b3-a495-4687-8879-6a5b4c3d2e1f" + ], + "status": "open", + "ruling": null, + "ruled_from": [], + "order": 2, + "options_hash": "5c7c2945e8e8689c6c928b11d7e0a033640b0c66afb7fcffb9e740c019d63e79" + }, + "record": { + "schemaVersion": "gamecult.fork.record.v0", + "fork_id": "gamecult-chat:question:q2", + "options_hash": "5c7c2945e8e8689c6c928b11d7e0a033640b0c66afb7fcffb9e740c019d63e79", + "counted_answer_id": null, + "answers": [] + } + }, + { + "schemaVersion": "gamecult.fork.entry.v0", + "card": { + "schemaVersion": "gamecult.fork.card.v0", + "fork_id": "gamecult-chat:question:q1", + "title": "Fork 1", + "context": "Should a slot mark be shared?", + "options": [ + { + "id": "yes", + "text": "Share it." + }, + { + "id": "no", + "text": "Keep it local." + } + ], + "recommendation": "no", + "depends": [ + "gamecult-chat:question:file-zone-audience" + ], + "leans": null, + "flag": null, + "raised_in": { + "kind": "CutSpec", + "id": "cultlib-gaps:cut_spec:cut-slot-tolerance.r4" + }, + "deciders": [ + "e0d1c2b3-a495-4687-8879-6a5b4c3d2e1f" + ], + "status": "closed", + "ruling": "cultlib-gaps:resolution:question.q1.n1", + "ruled_from": [], + "order": 1, + "options_hash": "61959964bd214ea1b961554c43ec46a9faf156dd547cc50c8c16bbdcd55f223a" + }, + "record": { + "schemaVersion": "gamecult.fork.record.v0", + "fork_id": "gamecult-chat:question:q1", + "options_hash": "61959964bd214ea1b961554c43ec46a9faf156dd547cc50c8c16bbdcd55f223a", + "counted_answer_id": null, + "answers": [] + } + } + ], + "steps": [ + { + "request": { + "schemaVersion": "gamecult.fork.list_request.v0", + "mind": "gamecult-chat", + "status": "open", + "cursor": null, + "limit": 2 + }, + "response": { + "schemaVersion": "gamecult.fork.list.v0", + "mind": "gamecult-chat", + "stamp": "s412", + "open_count": 3, + "recorded_count": 2, + "forks": [ + { + "schemaVersion": "gamecult.fork.entry.v0", + "card": { + "schemaVersion": "gamecult.fork.card.v0", + "fork_id": "gamecult-chat:question:q5", + "title": "Fork 5", + "context": "Should a slot mark be shared?", + "options": [ + { + "id": "yes", + "text": "Share it." + }, + { + "id": "no", + "text": "Keep it local." + } + ], + "recommendation": "no", + "depends": [ + "gamecult-chat:question:file-zone-audience" + ], + "leans": null, + "flag": null, + "raised_in": { + "kind": "CutSpec", + "id": "cultlib-gaps:cut_spec:cut-slot-tolerance.r4" + }, + "deciders": [ + "e0d1c2b3-a495-4687-8879-6a5b4c3d2e1f" + ], + "status": "open", + "ruling": null, + "ruled_from": [], + "order": 5, + "options_hash": "16d20a6dc44b6cc00afd378e9f0c8b51dfe5b07e869e1a2aa9588de1de587fab" + }, + "record": { + "schemaVersion": "gamecult.fork.record.v0", + "fork_id": "gamecult-chat:question:q5", + "options_hash": "16d20a6dc44b6cc00afd378e9f0c8b51dfe5b07e869e1a2aa9588de1de587fab", + "counted_answer_id": "n1", + "answers": [ + { + "schemaVersion": "gamecult.desk_answer.v0", + "answer_id": "n1", + "fork_id": "gamecult-chat:question:q5", + "outcome": "Counted", + "of": null, + "retracts": null, + "code": null, + "options_hash": "16d20a6dc44b6cc00afd378e9f0c8b51dfe5b07e869e1a2aa9588de1de587fab", + "choice": "no", + "note": "Keep it local: a slot mark is the author's call, and sharing it is a separate act.", + "answered_at": "2026-11-12T19:02:00Z", + "received_at": "2026-11-12T19:02:05Z", + "claim_in_future": false, + "answerer": "e0d1c2b3-a495-4687-8879-6a5b4c3d2e1f", + "authority": "Operator", + "request_id": "0000001f-0000-4000-8000-00000000001f" + } + ] + } + }, + { + "schemaVersion": "gamecult.fork.entry.v0", + "card": { + "schemaVersion": "gamecult.fork.card.v0", + "fork_id": "gamecult-chat:question:q4", + "title": "Fork 4", + "context": "Should a slot mark be shared?", + "options": [ + { + "id": "yes", + "text": "Share it." + }, + { + "id": "no", + "text": "Keep it local." + } + ], + "recommendation": "no", + "depends": [ + "gamecult-chat:question:file-zone-audience" + ], + "leans": null, + "flag": null, + "raised_in": { + "kind": "CutSpec", + "id": "cultlib-gaps:cut_spec:cut-slot-tolerance.r4" + }, + "deciders": [ + "e0d1c2b3-a495-4687-8879-6a5b4c3d2e1f" + ], + "status": "open", + "ruling": null, + "ruled_from": [], + "order": 4, + "options_hash": "a64604237f906b6fec9cb292863685d966abf8048f276afa24cd4f1e1c4c74aa" + }, + "record": { + "schemaVersion": "gamecult.fork.record.v0", + "fork_id": "gamecult-chat:question:q4", + "options_hash": "a64604237f906b6fec9cb292863685d966abf8048f276afa24cd4f1e1c4c74aa", + "counted_answer_id": null, + "answers": [] + } + } + ], + "next_cursor": "c4" + }, + "responseHex": "87AD736368656D6156657273696F6EB567616D6563756C742E666F726B2E6C6973742E7630A46D696E64AD67616D6563756C742D63686174A57374616D70A473343132AA6F70656E5F636F756E7403AE7265636F726465645F636F756E7402A5666F726B739283AD736368656D6156657273696F6EB667616D6563756C742E666F726B2E656E7472792E7630A463617264DE0010AD736368656D6156657273696F6EB567616D6563756C742E666F726B2E636172642E7630A7666F726B5F6964B967616D6563756C742D636861743A7175657374696F6E3A7135A57469746C65A6466F726B2035A7636F6E74657874BD53686F756C64206120736C6F74206D61726B206265207368617265643FA76F7074696F6E739282A26964A3796573A474657874A953686172652069742E82A26964A26E6FA474657874AE4B656570206974206C6F63616C2EAE7265636F6D6D656E646174696F6EA26E6FA7646570656E647391D92967616D6563756C742D636861743A7175657374696F6E3A66696C652D7A6F6E652D61756469656E6365A56C65616E73C0A4666C6167C0A97261697365645F696E82A46B696E64A743757453706563A26964D92B63756C746C69622D676170733A6375745F737065633A6375742D736C6F742D746F6C6572616E63652E7234A8646563696465727391D92465306431633262332D613439352D343638372D383837392D366135623463336432653166A6737461747573A46F70656EA672756C696E67C0AA72756C65645F66726F6D90A56F7264657205AC6F7074696F6E735F68617368D94031366432306136646334346236636330306166643337386539663063386235316466653562303765383639653161326161393538386465316465353837666162A67265636F726485AD736368656D6156657273696F6EB767616D6563756C742E666F726B2E7265636F72642E7630A7666F726B5F6964B967616D6563756C742D636861743A7175657374696F6E3A7135AC6F7074696F6E735F68617368D94031366432306136646334346236636330306166643337386539663063386235316466653562303765383639653161326161393538386465316465353837666162B1636F756E7465645F616E737765725F6964A26E31A7616E737765727391DE0010AD736368656D6156657273696F6EB767616D6563756C742E6465736B5F616E737765722E7630A9616E737765725F6964A26E31A7666F726B5F6964B967616D6563756C742D636861743A7175657374696F6E3A7135A76F7574636F6D65A7436F756E746564A26F66C0A87265747261637473C0A4636F6465C0AC6F7074696F6E735F68617368D94031366432306136646334346236636330306166643337386539663063386235316466653562303765383639653161326161393538386465316465353837666162A663686F696365A26E6FA46E6F7465D9524B656570206974206C6F63616C3A206120736C6F74206D61726B2069732074686520617574686F7227732063616C6C2C20616E642073686172696E672069742069732061207365706172617465206163742EAB616E7377657265645F6174B4323032362D31312D31325431393A30323A30305AAB72656365697665645F6174B4323032362D31312D31325431393A30323A30355AAF636C61696D5F696E5F667574757265C2A8616E737765726572D92465306431633262332D613439352D343638372D383837392D366135623463336432653166A9617574686F72697479A84F70657261746F72AA726571756573745F6964D92430303030303031662D303030302D343030302D383030302D30303030303030303030316683AD736368656D6156657273696F6EB667616D6563756C742E666F726B2E656E7472792E7630A463617264DE0010AD736368656D6156657273696F6EB567616D6563756C742E666F726B2E636172642E7630A7666F726B5F6964B967616D6563756C742D636861743A7175657374696F6E3A7134A57469746C65A6466F726B2034A7636F6E74657874BD53686F756C64206120736C6F74206D61726B206265207368617265643FA76F7074696F6E739282A26964A3796573A474657874A953686172652069742E82A26964A26E6FA474657874AE4B656570206974206C6F63616C2EAE7265636F6D6D656E646174696F6EA26E6FA7646570656E647391D92967616D6563756C742D636861743A7175657374696F6E3A66696C652D7A6F6E652D61756469656E6365A56C65616E73C0A4666C6167C0A97261697365645F696E82A46B696E64A743757453706563A26964D92B63756C746C69622D676170733A6375745F737065633A6375742D736C6F742D746F6C6572616E63652E7234A8646563696465727391D92465306431633262332D613439352D343638372D383837392D366135623463336432653166A6737461747573A46F70656EA672756C696E67C0AA72756C65645F66726F6D90A56F7264657204AC6F7074696F6E735F68617368D94061363436303432333766393036623666656339636232393238363336383564393636616266383034386632373661666132346364346631653163346337346161A67265636F726485AD736368656D6156657273696F6EB767616D6563756C742E666F726B2E7265636F72642E7630A7666F726B5F6964B967616D6563756C742D636861743A7175657374696F6E3A7134AC6F7074696F6E735F68617368D94061363436303432333766393036623666656339636232393238363336383564393636616266383034386632373661666132346364346631653163346337346161B1636F756E7465645F616E737765725F6964C0A7616E737765727390AB6E6578745F637572736F72A26334" + }, + { + "request": { + "schemaVersion": "gamecult.fork.list_request.v0", + "mind": "gamecult-chat", + "status": "open", + "cursor": "c4", + "limit": 2 + }, + "response": { + "schemaVersion": "gamecult.fork.list.v0", + "mind": "gamecult-chat", + "stamp": "s412", + "open_count": 3, + "recorded_count": 2, + "forks": [ + { + "schemaVersion": "gamecult.fork.entry.v0", + "card": { + "schemaVersion": "gamecult.fork.card.v0", + "fork_id": "gamecult-chat:question:q2", + "title": "Fork 2", + "context": "Should a slot mark be shared?", + "options": [ + { + "id": "yes", + "text": "Share it." + }, + { + "id": "no", + "text": "Keep it local." + } + ], + "recommendation": "no", + "depends": [ + "gamecult-chat:question:file-zone-audience" + ], + "leans": null, + "flag": null, + "raised_in": { + "kind": "CutSpec", + "id": "cultlib-gaps:cut_spec:cut-slot-tolerance.r4" + }, + "deciders": [ + "e0d1c2b3-a495-4687-8879-6a5b4c3d2e1f" + ], + "status": "open", + "ruling": null, + "ruled_from": [], + "order": 2, + "options_hash": "5c7c2945e8e8689c6c928b11d7e0a033640b0c66afb7fcffb9e740c019d63e79" + }, + "record": { + "schemaVersion": "gamecult.fork.record.v0", + "fork_id": "gamecult-chat:question:q2", + "options_hash": "5c7c2945e8e8689c6c928b11d7e0a033640b0c66afb7fcffb9e740c019d63e79", + "counted_answer_id": null, + "answers": [] + } + } + ], + "next_cursor": null + }, + "responseHex": "87AD736368656D6156657273696F6EB567616D6563756C742E666F726B2E6C6973742E7630A46D696E64AD67616D6563756C742D63686174A57374616D70A473343132AA6F70656E5F636F756E7403AE7265636F726465645F636F756E7402A5666F726B739183AD736368656D6156657273696F6EB667616D6563756C742E666F726B2E656E7472792E7630A463617264DE0010AD736368656D6156657273696F6EB567616D6563756C742E666F726B2E636172642E7630A7666F726B5F6964B967616D6563756C742D636861743A7175657374696F6E3A7132A57469746C65A6466F726B2032A7636F6E74657874BD53686F756C64206120736C6F74206D61726B206265207368617265643FA76F7074696F6E739282A26964A3796573A474657874A953686172652069742E82A26964A26E6FA474657874AE4B656570206974206C6F63616C2EAE7265636F6D6D656E646174696F6EA26E6FA7646570656E647391D92967616D6563756C742D636861743A7175657374696F6E3A66696C652D7A6F6E652D61756469656E6365A56C65616E73C0A4666C6167C0A97261697365645F696E82A46B696E64A743757453706563A26964D92B63756C746C69622D676170733A6375745F737065633A6375742D736C6F742D746F6C6572616E63652E7234A8646563696465727391D92465306431633262332D613439352D343638372D383837392D366135623463336432653166A6737461747573A46F70656EA672756C696E67C0AA72756C65645F66726F6D90A56F7264657202AC6F7074696F6E735F68617368D94035633763323934356538653836383963366339323862313164376530613033333634306230633636616662376663666662396537343063303139643633653739A67265636F726485AD736368656D6156657273696F6EB767616D6563756C742E666F726B2E7265636F72642E7630A7666F726B5F6964B967616D6563756C742D636861743A7175657374696F6E3A7132AC6F7074696F6E735F68617368D94035633763323934356538653836383963366339323862313164376530613033333634306230633636616662376663666662396537343063303139643633653739B1636F756E7465645F616E737765725F6964C0A7616E737765727390AB6E6578745F637572736F72C0" + } + ] + }, + { + "name": "all_forks_one_page", + "ordinal": 412, + "entries": [ + { + "schemaVersion": "gamecult.fork.entry.v0", + "card": { + "schemaVersion": "gamecult.fork.card.v0", + "fork_id": "gamecult-chat:question:q5", + "title": "Fork 5", + "context": "Should a slot mark be shared?", + "options": [ + { + "id": "yes", + "text": "Share it." + }, + { + "id": "no", + "text": "Keep it local." + } + ], + "recommendation": "no", + "depends": [ + "gamecult-chat:question:file-zone-audience" + ], + "leans": null, + "flag": null, + "raised_in": { + "kind": "CutSpec", + "id": "cultlib-gaps:cut_spec:cut-slot-tolerance.r4" + }, + "deciders": [ + "e0d1c2b3-a495-4687-8879-6a5b4c3d2e1f" + ], + "status": "open", + "ruling": null, + "ruled_from": [], + "order": 5, + "options_hash": "16d20a6dc44b6cc00afd378e9f0c8b51dfe5b07e869e1a2aa9588de1de587fab" + }, + "record": { + "schemaVersion": "gamecult.fork.record.v0", + "fork_id": "gamecult-chat:question:q5", + "options_hash": "16d20a6dc44b6cc00afd378e9f0c8b51dfe5b07e869e1a2aa9588de1de587fab", + "counted_answer_id": "n1", + "answers": [ + { + "schemaVersion": "gamecult.desk_answer.v0", + "answer_id": "n1", + "fork_id": "gamecult-chat:question:q5", + "outcome": "Counted", + "of": null, + "retracts": null, + "code": null, + "options_hash": "16d20a6dc44b6cc00afd378e9f0c8b51dfe5b07e869e1a2aa9588de1de587fab", + "choice": "no", + "note": "Keep it local: a slot mark is the author's call, and sharing it is a separate act.", + "answered_at": "2026-11-12T19:02:00Z", + "received_at": "2026-11-12T19:02:05Z", + "claim_in_future": false, + "answerer": "e0d1c2b3-a495-4687-8879-6a5b4c3d2e1f", + "authority": "Operator", + "request_id": "0000001f-0000-4000-8000-00000000001f" + } + ] + } + }, + { + "schemaVersion": "gamecult.fork.entry.v0", + "card": { + "schemaVersion": "gamecult.fork.card.v0", + "fork_id": "gamecult-chat:question:q4", + "title": "Fork 4", + "context": "Should a slot mark be shared?", + "options": [ + { + "id": "yes", + "text": "Share it." + }, + { + "id": "no", + "text": "Keep it local." + } + ], + "recommendation": "no", + "depends": [ + "gamecult-chat:question:file-zone-audience" + ], + "leans": null, + "flag": null, + "raised_in": { + "kind": "CutSpec", + "id": "cultlib-gaps:cut_spec:cut-slot-tolerance.r4" + }, + "deciders": [ + "e0d1c2b3-a495-4687-8879-6a5b4c3d2e1f" + ], + "status": "open", + "ruling": null, + "ruled_from": [], + "order": 4, + "options_hash": "a64604237f906b6fec9cb292863685d966abf8048f276afa24cd4f1e1c4c74aa" + }, + "record": { + "schemaVersion": "gamecult.fork.record.v0", + "fork_id": "gamecult-chat:question:q4", + "options_hash": "a64604237f906b6fec9cb292863685d966abf8048f276afa24cd4f1e1c4c74aa", + "counted_answer_id": null, + "answers": [] + } + }, + { + "schemaVersion": "gamecult.fork.entry.v0", + "card": { + "schemaVersion": "gamecult.fork.card.v0", + "fork_id": "gamecult-chat:question:q3", + "title": "Fork 3", + "context": "Should a slot mark be shared?", + "options": [ + { + "id": "yes", + "text": "Share it." + }, + { + "id": "no", + "text": "Keep it local." + } + ], + "recommendation": "no", + "depends": [ + "gamecult-chat:question:file-zone-audience" + ], + "leans": null, + "flag": null, + "raised_in": { + "kind": "CutSpec", + "id": "cultlib-gaps:cut_spec:cut-slot-tolerance.r4" + }, + "deciders": [ + "e0d1c2b3-a495-4687-8879-6a5b4c3d2e1f" + ], + "status": "closed", + "ruling": "cultlib-gaps:resolution:question.q3.n1", + "ruled_from": [ + "n1" + ], + "order": 3, + "options_hash": "ced892eb663c22e3d7ece602708043ee545f56b9d4e4421d7f5bfaa86c418ce2" + }, + "record": { + "schemaVersion": "gamecult.fork.record.v0", + "fork_id": "gamecult-chat:question:q3", + "options_hash": "ced892eb663c22e3d7ece602708043ee545f56b9d4e4421d7f5bfaa86c418ce2", + "counted_answer_id": "n1", + "answers": [ + { + "schemaVersion": "gamecult.desk_answer.v0", + "answer_id": "n1", + "fork_id": "gamecult-chat:question:q3", + "outcome": "Counted", + "of": null, + "retracts": null, + "code": null, + "options_hash": "ced892eb663c22e3d7ece602708043ee545f56b9d4e4421d7f5bfaa86c418ce2", + "choice": "yes", + "note": "Keep it local: a slot mark is the author's call, and sharing it is a separate act.", + "answered_at": "2026-11-12T19:02:00Z", + "received_at": "2026-11-12T19:02:05Z", + "claim_in_future": false, + "answerer": "e0d1c2b3-a495-4687-8879-6a5b4c3d2e1f", + "authority": "Operator", + "request_id": "00000020-0000-4000-8000-000000000020" + }, + { + "schemaVersion": "gamecult.desk_answer.v0", + "answer_id": "n2", + "fork_id": "gamecult-chat:question:q3", + "outcome": "Refused", + "of": null, + "retracts": null, + "code": "fork_closed", + "options_hash": "ced892eb663c22e3d7ece602708043ee545f56b9d4e4421d7f5bfaa86c418ce2", + "choice": "no", + "note": "Keep it local: a slot mark is the author's call, and sharing it is a separate act.", + "answered_at": "2026-11-12T19:02:00Z", + "received_at": "2026-11-12T19:04:05Z", + "claim_in_future": false, + "answerer": "9f8e7d6c-5b4a-4392-8170-0f1e2d3c4b5a", + "authority": "Steward", + "request_id": "00000021-0000-4000-8000-000000000021" + } + ] + } + }, + { + "schemaVersion": "gamecult.fork.entry.v0", + "card": { + "schemaVersion": "gamecult.fork.card.v0", + "fork_id": "gamecult-chat:question:q2", + "title": "Fork 2", + "context": "Should a slot mark be shared?", + "options": [ + { + "id": "yes", + "text": "Share it." + }, + { + "id": "no", + "text": "Keep it local." + } + ], + "recommendation": "no", + "depends": [ + "gamecult-chat:question:file-zone-audience" + ], + "leans": null, + "flag": null, + "raised_in": { + "kind": "CutSpec", + "id": "cultlib-gaps:cut_spec:cut-slot-tolerance.r4" + }, + "deciders": [ + "e0d1c2b3-a495-4687-8879-6a5b4c3d2e1f" + ], + "status": "open", + "ruling": null, + "ruled_from": [], + "order": 2, + "options_hash": "5c7c2945e8e8689c6c928b11d7e0a033640b0c66afb7fcffb9e740c019d63e79" + }, + "record": { + "schemaVersion": "gamecult.fork.record.v0", + "fork_id": "gamecult-chat:question:q2", + "options_hash": "5c7c2945e8e8689c6c928b11d7e0a033640b0c66afb7fcffb9e740c019d63e79", + "counted_answer_id": null, + "answers": [] + } + }, + { + "schemaVersion": "gamecult.fork.entry.v0", + "card": { + "schemaVersion": "gamecult.fork.card.v0", + "fork_id": "gamecult-chat:question:q1", + "title": "Fork 1", + "context": "Should a slot mark be shared?", + "options": [ + { + "id": "yes", + "text": "Share it." + }, + { + "id": "no", + "text": "Keep it local." + } + ], + "recommendation": "no", + "depends": [ + "gamecult-chat:question:file-zone-audience" + ], + "leans": null, + "flag": null, + "raised_in": { + "kind": "CutSpec", + "id": "cultlib-gaps:cut_spec:cut-slot-tolerance.r4" + }, + "deciders": [ + "e0d1c2b3-a495-4687-8879-6a5b4c3d2e1f" + ], + "status": "closed", + "ruling": "cultlib-gaps:resolution:question.q1.n1", + "ruled_from": [], + "order": 1, + "options_hash": "61959964bd214ea1b961554c43ec46a9faf156dd547cc50c8c16bbdcd55f223a" + }, + "record": { + "schemaVersion": "gamecult.fork.record.v0", + "fork_id": "gamecult-chat:question:q1", + "options_hash": "61959964bd214ea1b961554c43ec46a9faf156dd547cc50c8c16bbdcd55f223a", + "counted_answer_id": null, + "answers": [] + } + } + ], + "steps": [ + { + "request": { + "schemaVersion": "gamecult.fork.list_request.v0", + "mind": "gamecult-chat", + "status": null, + "cursor": null, + "limit": 50 + }, + "response": { + "schemaVersion": "gamecult.fork.list.v0", + "mind": "gamecult-chat", + "stamp": "s412", + "open_count": 3, + "recorded_count": 2, + "forks": [ + { + "schemaVersion": "gamecult.fork.entry.v0", + "card": { + "schemaVersion": "gamecult.fork.card.v0", + "fork_id": "gamecult-chat:question:q5", + "title": "Fork 5", + "context": "Should a slot mark be shared?", + "options": [ + { + "id": "yes", + "text": "Share it." + }, + { + "id": "no", + "text": "Keep it local." + } + ], + "recommendation": "no", + "depends": [ + "gamecult-chat:question:file-zone-audience" + ], + "leans": null, + "flag": null, + "raised_in": { + "kind": "CutSpec", + "id": "cultlib-gaps:cut_spec:cut-slot-tolerance.r4" + }, + "deciders": [ + "e0d1c2b3-a495-4687-8879-6a5b4c3d2e1f" + ], + "status": "open", + "ruling": null, + "ruled_from": [], + "order": 5, + "options_hash": "16d20a6dc44b6cc00afd378e9f0c8b51dfe5b07e869e1a2aa9588de1de587fab" + }, + "record": { + "schemaVersion": "gamecult.fork.record.v0", + "fork_id": "gamecult-chat:question:q5", + "options_hash": "16d20a6dc44b6cc00afd378e9f0c8b51dfe5b07e869e1a2aa9588de1de587fab", + "counted_answer_id": "n1", + "answers": [ + { + "schemaVersion": "gamecult.desk_answer.v0", + "answer_id": "n1", + "fork_id": "gamecult-chat:question:q5", + "outcome": "Counted", + "of": null, + "retracts": null, + "code": null, + "options_hash": "16d20a6dc44b6cc00afd378e9f0c8b51dfe5b07e869e1a2aa9588de1de587fab", + "choice": "no", + "note": "Keep it local: a slot mark is the author's call, and sharing it is a separate act.", + "answered_at": "2026-11-12T19:02:00Z", + "received_at": "2026-11-12T19:02:05Z", + "claim_in_future": false, + "answerer": "e0d1c2b3-a495-4687-8879-6a5b4c3d2e1f", + "authority": "Operator", + "request_id": "0000001f-0000-4000-8000-00000000001f" + } + ] + } + }, + { + "schemaVersion": "gamecult.fork.entry.v0", + "card": { + "schemaVersion": "gamecult.fork.card.v0", + "fork_id": "gamecult-chat:question:q4", + "title": "Fork 4", + "context": "Should a slot mark be shared?", + "options": [ + { + "id": "yes", + "text": "Share it." + }, + { + "id": "no", + "text": "Keep it local." + } + ], + "recommendation": "no", + "depends": [ + "gamecult-chat:question:file-zone-audience" + ], + "leans": null, + "flag": null, + "raised_in": { + "kind": "CutSpec", + "id": "cultlib-gaps:cut_spec:cut-slot-tolerance.r4" + }, + "deciders": [ + "e0d1c2b3-a495-4687-8879-6a5b4c3d2e1f" + ], + "status": "open", + "ruling": null, + "ruled_from": [], + "order": 4, + "options_hash": "a64604237f906b6fec9cb292863685d966abf8048f276afa24cd4f1e1c4c74aa" + }, + "record": { + "schemaVersion": "gamecult.fork.record.v0", + "fork_id": "gamecult-chat:question:q4", + "options_hash": "a64604237f906b6fec9cb292863685d966abf8048f276afa24cd4f1e1c4c74aa", + "counted_answer_id": null, + "answers": [] + } + }, + { + "schemaVersion": "gamecult.fork.entry.v0", + "card": { + "schemaVersion": "gamecult.fork.card.v0", + "fork_id": "gamecult-chat:question:q3", + "title": "Fork 3", + "context": "Should a slot mark be shared?", + "options": [ + { + "id": "yes", + "text": "Share it." + }, + { + "id": "no", + "text": "Keep it local." + } + ], + "recommendation": "no", + "depends": [ + "gamecult-chat:question:file-zone-audience" + ], + "leans": null, + "flag": null, + "raised_in": { + "kind": "CutSpec", + "id": "cultlib-gaps:cut_spec:cut-slot-tolerance.r4" + }, + "deciders": [ + "e0d1c2b3-a495-4687-8879-6a5b4c3d2e1f" + ], + "status": "closed", + "ruling": "cultlib-gaps:resolution:question.q3.n1", + "ruled_from": [ + "n1" + ], + "order": 3, + "options_hash": "ced892eb663c22e3d7ece602708043ee545f56b9d4e4421d7f5bfaa86c418ce2" + }, + "record": { + "schemaVersion": "gamecult.fork.record.v0", + "fork_id": "gamecult-chat:question:q3", + "options_hash": "ced892eb663c22e3d7ece602708043ee545f56b9d4e4421d7f5bfaa86c418ce2", + "counted_answer_id": "n1", + "answers": [ + { + "schemaVersion": "gamecult.desk_answer.v0", + "answer_id": "n1", + "fork_id": "gamecult-chat:question:q3", + "outcome": "Counted", + "of": null, + "retracts": null, + "code": null, + "options_hash": "ced892eb663c22e3d7ece602708043ee545f56b9d4e4421d7f5bfaa86c418ce2", + "choice": "yes", + "note": "Keep it local: a slot mark is the author's call, and sharing it is a separate act.", + "answered_at": "2026-11-12T19:02:00Z", + "received_at": "2026-11-12T19:02:05Z", + "claim_in_future": false, + "answerer": "e0d1c2b3-a495-4687-8879-6a5b4c3d2e1f", + "authority": "Operator", + "request_id": "00000020-0000-4000-8000-000000000020" + }, + { + "schemaVersion": "gamecult.desk_answer.v0", + "answer_id": "n2", + "fork_id": "gamecult-chat:question:q3", + "outcome": "Refused", + "of": null, + "retracts": null, + "code": "fork_closed", + "options_hash": "ced892eb663c22e3d7ece602708043ee545f56b9d4e4421d7f5bfaa86c418ce2", + "choice": "no", + "note": "Keep it local: a slot mark is the author's call, and sharing it is a separate act.", + "answered_at": "2026-11-12T19:02:00Z", + "received_at": "2026-11-12T19:04:05Z", + "claim_in_future": false, + "answerer": "9f8e7d6c-5b4a-4392-8170-0f1e2d3c4b5a", + "authority": "Steward", + "request_id": "00000021-0000-4000-8000-000000000021" + } + ] + } + }, + { + "schemaVersion": "gamecult.fork.entry.v0", + "card": { + "schemaVersion": "gamecult.fork.card.v0", + "fork_id": "gamecult-chat:question:q2", + "title": "Fork 2", + "context": "Should a slot mark be shared?", + "options": [ + { + "id": "yes", + "text": "Share it." + }, + { + "id": "no", + "text": "Keep it local." + } + ], + "recommendation": "no", + "depends": [ + "gamecult-chat:question:file-zone-audience" + ], + "leans": null, + "flag": null, + "raised_in": { + "kind": "CutSpec", + "id": "cultlib-gaps:cut_spec:cut-slot-tolerance.r4" + }, + "deciders": [ + "e0d1c2b3-a495-4687-8879-6a5b4c3d2e1f" + ], + "status": "open", + "ruling": null, + "ruled_from": [], + "order": 2, + "options_hash": "5c7c2945e8e8689c6c928b11d7e0a033640b0c66afb7fcffb9e740c019d63e79" + }, + "record": { + "schemaVersion": "gamecult.fork.record.v0", + "fork_id": "gamecult-chat:question:q2", + "options_hash": "5c7c2945e8e8689c6c928b11d7e0a033640b0c66afb7fcffb9e740c019d63e79", + "counted_answer_id": null, + "answers": [] + } + }, + { + "schemaVersion": "gamecult.fork.entry.v0", + "card": { + "schemaVersion": "gamecult.fork.card.v0", + "fork_id": "gamecult-chat:question:q1", + "title": "Fork 1", + "context": "Should a slot mark be shared?", + "options": [ + { + "id": "yes", + "text": "Share it." + }, + { + "id": "no", + "text": "Keep it local." + } + ], + "recommendation": "no", + "depends": [ + "gamecult-chat:question:file-zone-audience" + ], + "leans": null, + "flag": null, + "raised_in": { + "kind": "CutSpec", + "id": "cultlib-gaps:cut_spec:cut-slot-tolerance.r4" + }, + "deciders": [ + "e0d1c2b3-a495-4687-8879-6a5b4c3d2e1f" + ], + "status": "closed", + "ruling": "cultlib-gaps:resolution:question.q1.n1", + "ruled_from": [], + "order": 1, + "options_hash": "61959964bd214ea1b961554c43ec46a9faf156dd547cc50c8c16bbdcd55f223a" + }, + "record": { + "schemaVersion": "gamecult.fork.record.v0", + "fork_id": "gamecult-chat:question:q1", + "options_hash": "61959964bd214ea1b961554c43ec46a9faf156dd547cc50c8c16bbdcd55f223a", + "counted_answer_id": null, + "answers": [] + } + } + ], + "next_cursor": null + }, + "responseHex": "87AD736368656D6156657273696F6EB567616D6563756C742E666F726B2E6C6973742E7630A46D696E64AD67616D6563756C742D63686174A57374616D70A473343132AA6F70656E5F636F756E7403AE7265636F726465645F636F756E7402A5666F726B739583AD736368656D6156657273696F6EB667616D6563756C742E666F726B2E656E7472792E7630A463617264DE0010AD736368656D6156657273696F6EB567616D6563756C742E666F726B2E636172642E7630A7666F726B5F6964B967616D6563756C742D636861743A7175657374696F6E3A7135A57469746C65A6466F726B2035A7636F6E74657874BD53686F756C64206120736C6F74206D61726B206265207368617265643FA76F7074696F6E739282A26964A3796573A474657874A953686172652069742E82A26964A26E6FA474657874AE4B656570206974206C6F63616C2EAE7265636F6D6D656E646174696F6EA26E6FA7646570656E647391D92967616D6563756C742D636861743A7175657374696F6E3A66696C652D7A6F6E652D61756469656E6365A56C65616E73C0A4666C6167C0A97261697365645F696E82A46B696E64A743757453706563A26964D92B63756C746C69622D676170733A6375745F737065633A6375742D736C6F742D746F6C6572616E63652E7234A8646563696465727391D92465306431633262332D613439352D343638372D383837392D366135623463336432653166A6737461747573A46F70656EA672756C696E67C0AA72756C65645F66726F6D90A56F7264657205AC6F7074696F6E735F68617368D94031366432306136646334346236636330306166643337386539663063386235316466653562303765383639653161326161393538386465316465353837666162A67265636F726485AD736368656D6156657273696F6EB767616D6563756C742E666F726B2E7265636F72642E7630A7666F726B5F6964B967616D6563756C742D636861743A7175657374696F6E3A7135AC6F7074696F6E735F68617368D94031366432306136646334346236636330306166643337386539663063386235316466653562303765383639653161326161393538386465316465353837666162B1636F756E7465645F616E737765725F6964A26E31A7616E737765727391DE0010AD736368656D6156657273696F6EB767616D6563756C742E6465736B5F616E737765722E7630A9616E737765725F6964A26E31A7666F726B5F6964B967616D6563756C742D636861743A7175657374696F6E3A7135A76F7574636F6D65A7436F756E746564A26F66C0A87265747261637473C0A4636F6465C0AC6F7074696F6E735F68617368D94031366432306136646334346236636330306166643337386539663063386235316466653562303765383639653161326161393538386465316465353837666162A663686F696365A26E6FA46E6F7465D9524B656570206974206C6F63616C3A206120736C6F74206D61726B2069732074686520617574686F7227732063616C6C2C20616E642073686172696E672069742069732061207365706172617465206163742EAB616E7377657265645F6174B4323032362D31312D31325431393A30323A30305AAB72656365697665645F6174B4323032362D31312D31325431393A30323A30355AAF636C61696D5F696E5F667574757265C2A8616E737765726572D92465306431633262332D613439352D343638372D383837392D366135623463336432653166A9617574686F72697479A84F70657261746F72AA726571756573745F6964D92430303030303031662D303030302D343030302D383030302D30303030303030303030316683AD736368656D6156657273696F6EB667616D6563756C742E666F726B2E656E7472792E7630A463617264DE0010AD736368656D6156657273696F6EB567616D6563756C742E666F726B2E636172642E7630A7666F726B5F6964B967616D6563756C742D636861743A7175657374696F6E3A7134A57469746C65A6466F726B2034A7636F6E74657874BD53686F756C64206120736C6F74206D61726B206265207368617265643FA76F7074696F6E739282A26964A3796573A474657874A953686172652069742E82A26964A26E6FA474657874AE4B656570206974206C6F63616C2EAE7265636F6D6D656E646174696F6EA26E6FA7646570656E647391D92967616D6563756C742D636861743A7175657374696F6E3A66696C652D7A6F6E652D61756469656E6365A56C65616E73C0A4666C6167C0A97261697365645F696E82A46B696E64A743757453706563A26964D92B63756C746C69622D676170733A6375745F737065633A6375742D736C6F742D746F6C6572616E63652E7234A8646563696465727391D92465306431633262332D613439352D343638372D383837392D366135623463336432653166A6737461747573A46F70656EA672756C696E67C0AA72756C65645F66726F6D90A56F7264657204AC6F7074696F6E735F68617368D94061363436303432333766393036623666656339636232393238363336383564393636616266383034386632373661666132346364346631653163346337346161A67265636F726485AD736368656D6156657273696F6EB767616D6563756C742E666F726B2E7265636F72642E7630A7666F726B5F6964B967616D6563756C742D636861743A7175657374696F6E3A7134AC6F7074696F6E735F68617368D94061363436303432333766393036623666656339636232393238363336383564393636616266383034386632373661666132346364346631653163346337346161B1636F756E7465645F616E737765725F6964C0A7616E73776572739083AD736368656D6156657273696F6EB667616D6563756C742E666F726B2E656E7472792E7630A463617264DE0010AD736368656D6156657273696F6EB567616D6563756C742E666F726B2E636172642E7630A7666F726B5F6964B967616D6563756C742D636861743A7175657374696F6E3A7133A57469746C65A6466F726B2033A7636F6E74657874BD53686F756C64206120736C6F74206D61726B206265207368617265643FA76F7074696F6E739282A26964A3796573A474657874A953686172652069742E82A26964A26E6FA474657874AE4B656570206974206C6F63616C2EAE7265636F6D6D656E646174696F6EA26E6FA7646570656E647391D92967616D6563756C742D636861743A7175657374696F6E3A66696C652D7A6F6E652D61756469656E6365A56C65616E73C0A4666C6167C0A97261697365645F696E82A46B696E64A743757453706563A26964D92B63756C746C69622D676170733A6375745F737065633A6375742D736C6F742D746F6C6572616E63652E7234A8646563696465727391D92465306431633262332D613439352D343638372D383837392D366135623463336432653166A6737461747573A6636C6F736564A672756C696E67D92663756C746C69622D676170733A7265736F6C7574696F6E3A7175657374696F6E2E71332E6E31AA72756C65645F66726F6D91A26E31A56F7264657203AC6F7074696F6E735F68617368D94063656438393265623636336332326533643765636536303237303830343365653534356635366239643465343432316437663562666161383663343138636532A67265636F726485AD736368656D6156657273696F6EB767616D6563756C742E666F726B2E7265636F72642E7630A7666F726B5F6964B967616D6563756C742D636861743A7175657374696F6E3A7133AC6F7074696F6E735F68617368D94063656438393265623636336332326533643765636536303237303830343365653534356635366239643465343432316437663562666161383663343138636532B1636F756E7465645F616E737765725F6964A26E31A7616E737765727392DE0010AD736368656D6156657273696F6EB767616D6563756C742E6465736B5F616E737765722E7630A9616E737765725F6964A26E31A7666F726B5F6964B967616D6563756C742D636861743A7175657374696F6E3A7133A76F7574636F6D65A7436F756E746564A26F66C0A87265747261637473C0A4636F6465C0AC6F7074696F6E735F68617368D94063656438393265623636336332326533643765636536303237303830343365653534356635366239643465343432316437663562666161383663343138636532A663686F696365A3796573A46E6F7465D9524B656570206974206C6F63616C3A206120736C6F74206D61726B2069732074686520617574686F7227732063616C6C2C20616E642073686172696E672069742069732061207365706172617465206163742EAB616E7377657265645F6174B4323032362D31312D31325431393A30323A30305AAB72656365697665645F6174B4323032362D31312D31325431393A30323A30355AAF636C61696D5F696E5F667574757265C2A8616E737765726572D92465306431633262332D613439352D343638372D383837392D366135623463336432653166A9617574686F72697479A84F70657261746F72AA726571756573745F6964D92430303030303032302D303030302D343030302D383030302D303030303030303030303230DE0010AD736368656D6156657273696F6EB767616D6563756C742E6465736B5F616E737765722E7630A9616E737765725F6964A26E32A7666F726B5F6964B967616D6563756C742D636861743A7175657374696F6E3A7133A76F7574636F6D65A752656675736564A26F66C0A87265747261637473C0A4636F6465AB666F726B5F636C6F736564AC6F7074696F6E735F68617368D94063656438393265623636336332326533643765636536303237303830343365653534356635366239643465343432316437663562666161383663343138636532A663686F696365A26E6FA46E6F7465D9524B656570206974206C6F63616C3A206120736C6F74206D61726B2069732074686520617574686F7227732063616C6C2C20616E642073686172696E672069742069732061207365706172617465206163742EAB616E7377657265645F6174B4323032362D31312D31325431393A30323A30305AAB72656365697665645F6174B4323032362D31312D31325431393A30343A30355AAF636C61696D5F696E5F667574757265C2A8616E737765726572D92439663865376436632D356234612D343339322D383137302D306631653264336334623561A9617574686F72697479A753746577617264AA726571756573745F6964D92430303030303032312D303030302D343030302D383030302D30303030303030303030323183AD736368656D6156657273696F6EB667616D6563756C742E666F726B2E656E7472792E7630A463617264DE0010AD736368656D6156657273696F6EB567616D6563756C742E666F726B2E636172642E7630A7666F726B5F6964B967616D6563756C742D636861743A7175657374696F6E3A7132A57469746C65A6466F726B2032A7636F6E74657874BD53686F756C64206120736C6F74206D61726B206265207368617265643FA76F7074696F6E739282A26964A3796573A474657874A953686172652069742E82A26964A26E6FA474657874AE4B656570206974206C6F63616C2EAE7265636F6D6D656E646174696F6EA26E6FA7646570656E647391D92967616D6563756C742D636861743A7175657374696F6E3A66696C652D7A6F6E652D61756469656E6365A56C65616E73C0A4666C6167C0A97261697365645F696E82A46B696E64A743757453706563A26964D92B63756C746C69622D676170733A6375745F737065633A6375742D736C6F742D746F6C6572616E63652E7234A8646563696465727391D92465306431633262332D613439352D343638372D383837392D366135623463336432653166A6737461747573A46F70656EA672756C696E67C0AA72756C65645F66726F6D90A56F7264657202AC6F7074696F6E735F68617368D94035633763323934356538653836383963366339323862313164376530613033333634306230633636616662376663666662396537343063303139643633653739A67265636F726485AD736368656D6156657273696F6EB767616D6563756C742E666F726B2E7265636F72642E7630A7666F726B5F6964B967616D6563756C742D636861743A7175657374696F6E3A7132AC6F7074696F6E735F68617368D94035633763323934356538653836383963366339323862313164376530613033333634306230633636616662376663666662396537343063303139643633653739B1636F756E7465645F616E737765725F6964C0A7616E73776572739083AD736368656D6156657273696F6EB667616D6563756C742E666F726B2E656E7472792E7630A463617264DE0010AD736368656D6156657273696F6EB567616D6563756C742E666F726B2E636172642E7630A7666F726B5F6964B967616D6563756C742D636861743A7175657374696F6E3A7131A57469746C65A6466F726B2031A7636F6E74657874BD53686F756C64206120736C6F74206D61726B206265207368617265643FA76F7074696F6E739282A26964A3796573A474657874A953686172652069742E82A26964A26E6FA474657874AE4B656570206974206C6F63616C2EAE7265636F6D6D656E646174696F6EA26E6FA7646570656E647391D92967616D6563756C742D636861743A7175657374696F6E3A66696C652D7A6F6E652D61756469656E6365A56C65616E73C0A4666C6167C0A97261697365645F696E82A46B696E64A743757453706563A26964D92B63756C746C69622D676170733A6375745F737065633A6375742D736C6F742D746F6C6572616E63652E7234A8646563696465727391D92465306431633262332D613439352D343638372D383837392D366135623463336432653166A6737461747573A6636C6F736564A672756C696E67D92663756C746C69622D676170733A7265736F6C7574696F6E3A7175657374696F6E2E71312E6E31AA72756C65645F66726F6D90A56F7264657201AC6F7074696F6E735F68617368D94036313935393936346264323134656131623936313535346334336563343661396661663135366464353437636335306338633136626264636435356632323361A67265636F726485AD736368656D6156657273696F6EB767616D6563756C742E666F726B2E7265636F72642E7630A7666F726B5F6964B967616D6563756C742D636861743A7175657374696F6E3A7131AC6F7074696F6E735F68617368D94036313935393936346264323134656131623936313535346334336563343661396661663135366464353437636335306338633136626264636435356632323361B1636F756E7465645F616E737765725F6964C0A7616E737765727390AB6E6578745F637572736F72C0" + } + ] + }, + { + "name": "recorded_only_paged", + "ordinal": 412, + "entries": [ + { + "schemaVersion": "gamecult.fork.entry.v0", + "card": { + "schemaVersion": "gamecult.fork.card.v0", + "fork_id": "gamecult-chat:question:q5", + "title": "Fork 5", + "context": "Should a slot mark be shared?", + "options": [ + { + "id": "yes", + "text": "Share it." + }, + { + "id": "no", + "text": "Keep it local." + } + ], + "recommendation": "no", + "depends": [ + "gamecult-chat:question:file-zone-audience" + ], + "leans": null, + "flag": null, + "raised_in": { + "kind": "CutSpec", + "id": "cultlib-gaps:cut_spec:cut-slot-tolerance.r4" + }, + "deciders": [ + "e0d1c2b3-a495-4687-8879-6a5b4c3d2e1f" + ], + "status": "open", + "ruling": null, + "ruled_from": [], + "order": 5, + "options_hash": "16d20a6dc44b6cc00afd378e9f0c8b51dfe5b07e869e1a2aa9588de1de587fab" + }, + "record": { + "schemaVersion": "gamecult.fork.record.v0", + "fork_id": "gamecult-chat:question:q5", + "options_hash": "16d20a6dc44b6cc00afd378e9f0c8b51dfe5b07e869e1a2aa9588de1de587fab", + "counted_answer_id": "n1", + "answers": [ + { + "schemaVersion": "gamecult.desk_answer.v0", + "answer_id": "n1", + "fork_id": "gamecult-chat:question:q5", + "outcome": "Counted", + "of": null, + "retracts": null, + "code": null, + "options_hash": "16d20a6dc44b6cc00afd378e9f0c8b51dfe5b07e869e1a2aa9588de1de587fab", + "choice": "no", + "note": "Keep it local: a slot mark is the author's call, and sharing it is a separate act.", + "answered_at": "2026-11-12T19:02:00Z", + "received_at": "2026-11-12T19:02:05Z", + "claim_in_future": false, + "answerer": "e0d1c2b3-a495-4687-8879-6a5b4c3d2e1f", + "authority": "Operator", + "request_id": "0000001f-0000-4000-8000-00000000001f" + } + ] + } + }, + { + "schemaVersion": "gamecult.fork.entry.v0", + "card": { + "schemaVersion": "gamecult.fork.card.v0", + "fork_id": "gamecult-chat:question:q4", + "title": "Fork 4", + "context": "Should a slot mark be shared?", + "options": [ + { + "id": "yes", + "text": "Share it." + }, + { + "id": "no", + "text": "Keep it local." + } + ], + "recommendation": "no", + "depends": [ + "gamecult-chat:question:file-zone-audience" + ], + "leans": null, + "flag": null, + "raised_in": { + "kind": "CutSpec", + "id": "cultlib-gaps:cut_spec:cut-slot-tolerance.r4" + }, + "deciders": [ + "e0d1c2b3-a495-4687-8879-6a5b4c3d2e1f" + ], + "status": "open", + "ruling": null, + "ruled_from": [], + "order": 4, + "options_hash": "a64604237f906b6fec9cb292863685d966abf8048f276afa24cd4f1e1c4c74aa" + }, + "record": { + "schemaVersion": "gamecult.fork.record.v0", + "fork_id": "gamecult-chat:question:q4", + "options_hash": "a64604237f906b6fec9cb292863685d966abf8048f276afa24cd4f1e1c4c74aa", + "counted_answer_id": null, + "answers": [] + } + }, + { + "schemaVersion": "gamecult.fork.entry.v0", + "card": { + "schemaVersion": "gamecult.fork.card.v0", + "fork_id": "gamecult-chat:question:q3", + "title": "Fork 3", + "context": "Should a slot mark be shared?", + "options": [ + { + "id": "yes", + "text": "Share it." + }, + { + "id": "no", + "text": "Keep it local." + } + ], + "recommendation": "no", + "depends": [ + "gamecult-chat:question:file-zone-audience" + ], + "leans": null, + "flag": null, + "raised_in": { + "kind": "CutSpec", + "id": "cultlib-gaps:cut_spec:cut-slot-tolerance.r4" + }, + "deciders": [ + "e0d1c2b3-a495-4687-8879-6a5b4c3d2e1f" + ], + "status": "closed", + "ruling": "cultlib-gaps:resolution:question.q3.n1", + "ruled_from": [ + "n1" + ], + "order": 3, + "options_hash": "ced892eb663c22e3d7ece602708043ee545f56b9d4e4421d7f5bfaa86c418ce2" + }, + "record": { + "schemaVersion": "gamecult.fork.record.v0", + "fork_id": "gamecult-chat:question:q3", + "options_hash": "ced892eb663c22e3d7ece602708043ee545f56b9d4e4421d7f5bfaa86c418ce2", + "counted_answer_id": "n1", + "answers": [ + { + "schemaVersion": "gamecult.desk_answer.v0", + "answer_id": "n1", + "fork_id": "gamecult-chat:question:q3", + "outcome": "Counted", + "of": null, + "retracts": null, + "code": null, + "options_hash": "ced892eb663c22e3d7ece602708043ee545f56b9d4e4421d7f5bfaa86c418ce2", + "choice": "yes", + "note": "Keep it local: a slot mark is the author's call, and sharing it is a separate act.", + "answered_at": "2026-11-12T19:02:00Z", + "received_at": "2026-11-12T19:02:05Z", + "claim_in_future": false, + "answerer": "e0d1c2b3-a495-4687-8879-6a5b4c3d2e1f", + "authority": "Operator", + "request_id": "00000020-0000-4000-8000-000000000020" + }, + { + "schemaVersion": "gamecult.desk_answer.v0", + "answer_id": "n2", + "fork_id": "gamecult-chat:question:q3", + "outcome": "Refused", + "of": null, + "retracts": null, + "code": "fork_closed", + "options_hash": "ced892eb663c22e3d7ece602708043ee545f56b9d4e4421d7f5bfaa86c418ce2", + "choice": "no", + "note": "Keep it local: a slot mark is the author's call, and sharing it is a separate act.", + "answered_at": "2026-11-12T19:02:00Z", + "received_at": "2026-11-12T19:04:05Z", + "claim_in_future": false, + "answerer": "9f8e7d6c-5b4a-4392-8170-0f1e2d3c4b5a", + "authority": "Steward", + "request_id": "00000021-0000-4000-8000-000000000021" + } + ] + } + }, + { + "schemaVersion": "gamecult.fork.entry.v0", + "card": { + "schemaVersion": "gamecult.fork.card.v0", + "fork_id": "gamecult-chat:question:q2", + "title": "Fork 2", + "context": "Should a slot mark be shared?", + "options": [ + { + "id": "yes", + "text": "Share it." + }, + { + "id": "no", + "text": "Keep it local." + } + ], + "recommendation": "no", + "depends": [ + "gamecult-chat:question:file-zone-audience" + ], + "leans": null, + "flag": null, + "raised_in": { + "kind": "CutSpec", + "id": "cultlib-gaps:cut_spec:cut-slot-tolerance.r4" + }, + "deciders": [ + "e0d1c2b3-a495-4687-8879-6a5b4c3d2e1f" + ], + "status": "open", + "ruling": null, + "ruled_from": [], + "order": 2, + "options_hash": "5c7c2945e8e8689c6c928b11d7e0a033640b0c66afb7fcffb9e740c019d63e79" + }, + "record": { + "schemaVersion": "gamecult.fork.record.v0", + "fork_id": "gamecult-chat:question:q2", + "options_hash": "5c7c2945e8e8689c6c928b11d7e0a033640b0c66afb7fcffb9e740c019d63e79", + "counted_answer_id": null, + "answers": [] + } + }, + { + "schemaVersion": "gamecult.fork.entry.v0", + "card": { + "schemaVersion": "gamecult.fork.card.v0", + "fork_id": "gamecult-chat:question:q1", + "title": "Fork 1", + "context": "Should a slot mark be shared?", + "options": [ + { + "id": "yes", + "text": "Share it." + }, + { + "id": "no", + "text": "Keep it local." + } + ], + "recommendation": "no", + "depends": [ + "gamecult-chat:question:file-zone-audience" + ], + "leans": null, + "flag": null, + "raised_in": { + "kind": "CutSpec", + "id": "cultlib-gaps:cut_spec:cut-slot-tolerance.r4" + }, + "deciders": [ + "e0d1c2b3-a495-4687-8879-6a5b4c3d2e1f" + ], + "status": "closed", + "ruling": "cultlib-gaps:resolution:question.q1.n1", + "ruled_from": [], + "order": 1, + "options_hash": "61959964bd214ea1b961554c43ec46a9faf156dd547cc50c8c16bbdcd55f223a" + }, + "record": { + "schemaVersion": "gamecult.fork.record.v0", + "fork_id": "gamecult-chat:question:q1", + "options_hash": "61959964bd214ea1b961554c43ec46a9faf156dd547cc50c8c16bbdcd55f223a", + "counted_answer_id": null, + "answers": [] + } + } + ], + "steps": [ + { + "request": { + "schemaVersion": "gamecult.fork.list_request.v0", + "mind": "gamecult-chat", + "status": "recorded", + "cursor": null, + "limit": 1 + }, + "response": { + "schemaVersion": "gamecult.fork.list.v0", + "mind": "gamecult-chat", + "stamp": "s412", + "open_count": 3, + "recorded_count": 2, + "forks": [ + { + "schemaVersion": "gamecult.fork.entry.v0", + "card": { + "schemaVersion": "gamecult.fork.card.v0", + "fork_id": "gamecult-chat:question:q3", + "title": "Fork 3", + "context": "Should a slot mark be shared?", + "options": [ + { + "id": "yes", + "text": "Share it." + }, + { + "id": "no", + "text": "Keep it local." + } + ], + "recommendation": "no", + "depends": [ + "gamecult-chat:question:file-zone-audience" + ], + "leans": null, + "flag": null, + "raised_in": { + "kind": "CutSpec", + "id": "cultlib-gaps:cut_spec:cut-slot-tolerance.r4" + }, + "deciders": [ + "e0d1c2b3-a495-4687-8879-6a5b4c3d2e1f" + ], + "status": "closed", + "ruling": "cultlib-gaps:resolution:question.q3.n1", + "ruled_from": [ + "n1" + ], + "order": 3, + "options_hash": "ced892eb663c22e3d7ece602708043ee545f56b9d4e4421d7f5bfaa86c418ce2" + }, + "record": { + "schemaVersion": "gamecult.fork.record.v0", + "fork_id": "gamecult-chat:question:q3", + "options_hash": "ced892eb663c22e3d7ece602708043ee545f56b9d4e4421d7f5bfaa86c418ce2", + "counted_answer_id": "n1", + "answers": [ + { + "schemaVersion": "gamecult.desk_answer.v0", + "answer_id": "n1", + "fork_id": "gamecult-chat:question:q3", + "outcome": "Counted", + "of": null, + "retracts": null, + "code": null, + "options_hash": "ced892eb663c22e3d7ece602708043ee545f56b9d4e4421d7f5bfaa86c418ce2", + "choice": "yes", + "note": "Keep it local: a slot mark is the author's call, and sharing it is a separate act.", + "answered_at": "2026-11-12T19:02:00Z", + "received_at": "2026-11-12T19:02:05Z", + "claim_in_future": false, + "answerer": "e0d1c2b3-a495-4687-8879-6a5b4c3d2e1f", + "authority": "Operator", + "request_id": "00000020-0000-4000-8000-000000000020" + }, + { + "schemaVersion": "gamecult.desk_answer.v0", + "answer_id": "n2", + "fork_id": "gamecult-chat:question:q3", + "outcome": "Refused", + "of": null, + "retracts": null, + "code": "fork_closed", + "options_hash": "ced892eb663c22e3d7ece602708043ee545f56b9d4e4421d7f5bfaa86c418ce2", + "choice": "no", + "note": "Keep it local: a slot mark is the author's call, and sharing it is a separate act.", + "answered_at": "2026-11-12T19:02:00Z", + "received_at": "2026-11-12T19:04:05Z", + "claim_in_future": false, + "answerer": "9f8e7d6c-5b4a-4392-8170-0f1e2d3c4b5a", + "authority": "Steward", + "request_id": "00000021-0000-4000-8000-000000000021" + } + ] + } + } + ], + "next_cursor": "c3" + }, + "responseHex": "87AD736368656D6156657273696F6EB567616D6563756C742E666F726B2E6C6973742E7630A46D696E64AD67616D6563756C742D63686174A57374616D70A473343132AA6F70656E5F636F756E7403AE7265636F726465645F636F756E7402A5666F726B739183AD736368656D6156657273696F6EB667616D6563756C742E666F726B2E656E7472792E7630A463617264DE0010AD736368656D6156657273696F6EB567616D6563756C742E666F726B2E636172642E7630A7666F726B5F6964B967616D6563756C742D636861743A7175657374696F6E3A7133A57469746C65A6466F726B2033A7636F6E74657874BD53686F756C64206120736C6F74206D61726B206265207368617265643FA76F7074696F6E739282A26964A3796573A474657874A953686172652069742E82A26964A26E6FA474657874AE4B656570206974206C6F63616C2EAE7265636F6D6D656E646174696F6EA26E6FA7646570656E647391D92967616D6563756C742D636861743A7175657374696F6E3A66696C652D7A6F6E652D61756469656E6365A56C65616E73C0A4666C6167C0A97261697365645F696E82A46B696E64A743757453706563A26964D92B63756C746C69622D676170733A6375745F737065633A6375742D736C6F742D746F6C6572616E63652E7234A8646563696465727391D92465306431633262332D613439352D343638372D383837392D366135623463336432653166A6737461747573A6636C6F736564A672756C696E67D92663756C746C69622D676170733A7265736F6C7574696F6E3A7175657374696F6E2E71332E6E31AA72756C65645F66726F6D91A26E31A56F7264657203AC6F7074696F6E735F68617368D94063656438393265623636336332326533643765636536303237303830343365653534356635366239643465343432316437663562666161383663343138636532A67265636F726485AD736368656D6156657273696F6EB767616D6563756C742E666F726B2E7265636F72642E7630A7666F726B5F6964B967616D6563756C742D636861743A7175657374696F6E3A7133AC6F7074696F6E735F68617368D94063656438393265623636336332326533643765636536303237303830343365653534356635366239643465343432316437663562666161383663343138636532B1636F756E7465645F616E737765725F6964A26E31A7616E737765727392DE0010AD736368656D6156657273696F6EB767616D6563756C742E6465736B5F616E737765722E7630A9616E737765725F6964A26E31A7666F726B5F6964B967616D6563756C742D636861743A7175657374696F6E3A7133A76F7574636F6D65A7436F756E746564A26F66C0A87265747261637473C0A4636F6465C0AC6F7074696F6E735F68617368D94063656438393265623636336332326533643765636536303237303830343365653534356635366239643465343432316437663562666161383663343138636532A663686F696365A3796573A46E6F7465D9524B656570206974206C6F63616C3A206120736C6F74206D61726B2069732074686520617574686F7227732063616C6C2C20616E642073686172696E672069742069732061207365706172617465206163742EAB616E7377657265645F6174B4323032362D31312D31325431393A30323A30305AAB72656365697665645F6174B4323032362D31312D31325431393A30323A30355AAF636C61696D5F696E5F667574757265C2A8616E737765726572D92465306431633262332D613439352D343638372D383837392D366135623463336432653166A9617574686F72697479A84F70657261746F72AA726571756573745F6964D92430303030303032302D303030302D343030302D383030302D303030303030303030303230DE0010AD736368656D6156657273696F6EB767616D6563756C742E6465736B5F616E737765722E7630A9616E737765725F6964A26E32A7666F726B5F6964B967616D6563756C742D636861743A7175657374696F6E3A7133A76F7574636F6D65A752656675736564A26F66C0A87265747261637473C0A4636F6465AB666F726B5F636C6F736564AC6F7074696F6E735F68617368D94063656438393265623636336332326533643765636536303237303830343365653534356635366239643465343432316437663562666161383663343138636532A663686F696365A26E6FA46E6F7465D9524B656570206974206C6F63616C3A206120736C6F74206D61726B2069732074686520617574686F7227732063616C6C2C20616E642073686172696E672069742069732061207365706172617465206163742EAB616E7377657265645F6174B4323032362D31312D31325431393A30323A30305AAB72656365697665645F6174B4323032362D31312D31325431393A30343A30355AAF636C61696D5F696E5F667574757265C2A8616E737765726572D92439663865376436632D356234612D343339322D383137302D306631653264336334623561A9617574686F72697479A753746577617264AA726571756573745F6964D92430303030303032312D303030302D343030302D383030302D303030303030303030303231AB6E6578745F637572736F72A26333" + }, + { + "request": { + "schemaVersion": "gamecult.fork.list_request.v0", + "mind": "gamecult-chat", + "status": "recorded", + "cursor": "c3", + "limit": 1 + }, + "response": { + "schemaVersion": "gamecult.fork.list.v0", + "mind": "gamecult-chat", + "stamp": "s412", + "open_count": 3, + "recorded_count": 2, + "forks": [ + { + "schemaVersion": "gamecult.fork.entry.v0", + "card": { + "schemaVersion": "gamecult.fork.card.v0", + "fork_id": "gamecult-chat:question:q1", + "title": "Fork 1", + "context": "Should a slot mark be shared?", + "options": [ + { + "id": "yes", + "text": "Share it." + }, + { + "id": "no", + "text": "Keep it local." + } + ], + "recommendation": "no", + "depends": [ + "gamecult-chat:question:file-zone-audience" + ], + "leans": null, + "flag": null, + "raised_in": { + "kind": "CutSpec", + "id": "cultlib-gaps:cut_spec:cut-slot-tolerance.r4" + }, + "deciders": [ + "e0d1c2b3-a495-4687-8879-6a5b4c3d2e1f" + ], + "status": "closed", + "ruling": "cultlib-gaps:resolution:question.q1.n1", + "ruled_from": [], + "order": 1, + "options_hash": "61959964bd214ea1b961554c43ec46a9faf156dd547cc50c8c16bbdcd55f223a" + }, + "record": { + "schemaVersion": "gamecult.fork.record.v0", + "fork_id": "gamecult-chat:question:q1", + "options_hash": "61959964bd214ea1b961554c43ec46a9faf156dd547cc50c8c16bbdcd55f223a", + "counted_answer_id": null, + "answers": [] + } + } + ], + "next_cursor": null + }, + "responseHex": "87AD736368656D6156657273696F6EB567616D6563756C742E666F726B2E6C6973742E7630A46D696E64AD67616D6563756C742D63686174A57374616D70A473343132AA6F70656E5F636F756E7403AE7265636F726465645F636F756E7402A5666F726B739183AD736368656D6156657273696F6EB667616D6563756C742E666F726B2E656E7472792E7630A463617264DE0010AD736368656D6156657273696F6EB567616D6563756C742E666F726B2E636172642E7630A7666F726B5F6964B967616D6563756C742D636861743A7175657374696F6E3A7131A57469746C65A6466F726B2031A7636F6E74657874BD53686F756C64206120736C6F74206D61726B206265207368617265643FA76F7074696F6E739282A26964A3796573A474657874A953686172652069742E82A26964A26E6FA474657874AE4B656570206974206C6F63616C2EAE7265636F6D6D656E646174696F6EA26E6FA7646570656E647391D92967616D6563756C742D636861743A7175657374696F6E3A66696C652D7A6F6E652D61756469656E6365A56C65616E73C0A4666C6167C0A97261697365645F696E82A46B696E64A743757453706563A26964D92B63756C746C69622D676170733A6375745F737065633A6375742D736C6F742D746F6C6572616E63652E7234A8646563696465727391D92465306431633262332D613439352D343638372D383837392D366135623463336432653166A6737461747573A6636C6F736564A672756C696E67D92663756C746C69622D676170733A7265736F6C7574696F6E3A7175657374696F6E2E71312E6E31AA72756C65645F66726F6D90A56F7264657201AC6F7074696F6E735F68617368D94036313935393936346264323134656131623936313535346334336563343661396661663135366464353437636335306338633136626264636435356632323361A67265636F726485AD736368656D6156657273696F6EB767616D6563756C742E666F726B2E7265636F72642E7630A7666F726B5F6964B967616D6563756C742D636861743A7175657374696F6E3A7131AC6F7074696F6E735F68617368D94036313935393936346264323134656131623936313535346334336563343661396661663135366464353437636335306338633136626264636435356632323361B1636F756E7465645F616E737765725F6964C0A7616E737765727390AB6E6578745F637572736F72C0" + } + ] + }, + { + "name": "limit_zero_is_the_count_and_stamp_only", + "ordinal": 412, + "entries": [ + { + "schemaVersion": "gamecult.fork.entry.v0", + "card": { + "schemaVersion": "gamecult.fork.card.v0", + "fork_id": "gamecult-chat:question:q5", + "title": "Fork 5", + "context": "Should a slot mark be shared?", + "options": [ + { + "id": "yes", + "text": "Share it." + }, + { + "id": "no", + "text": "Keep it local." + } + ], + "recommendation": "no", + "depends": [ + "gamecult-chat:question:file-zone-audience" + ], + "leans": null, + "flag": null, + "raised_in": { + "kind": "CutSpec", + "id": "cultlib-gaps:cut_spec:cut-slot-tolerance.r4" + }, + "deciders": [ + "e0d1c2b3-a495-4687-8879-6a5b4c3d2e1f" + ], + "status": "open", + "ruling": null, + "ruled_from": [], + "order": 5, + "options_hash": "16d20a6dc44b6cc00afd378e9f0c8b51dfe5b07e869e1a2aa9588de1de587fab" + }, + "record": { + "schemaVersion": "gamecult.fork.record.v0", + "fork_id": "gamecult-chat:question:q5", + "options_hash": "16d20a6dc44b6cc00afd378e9f0c8b51dfe5b07e869e1a2aa9588de1de587fab", + "counted_answer_id": "n1", + "answers": [ + { + "schemaVersion": "gamecult.desk_answer.v0", + "answer_id": "n1", + "fork_id": "gamecult-chat:question:q5", + "outcome": "Counted", + "of": null, + "retracts": null, + "code": null, + "options_hash": "16d20a6dc44b6cc00afd378e9f0c8b51dfe5b07e869e1a2aa9588de1de587fab", + "choice": "no", + "note": "Keep it local: a slot mark is the author's call, and sharing it is a separate act.", + "answered_at": "2026-11-12T19:02:00Z", + "received_at": "2026-11-12T19:02:05Z", + "claim_in_future": false, + "answerer": "e0d1c2b3-a495-4687-8879-6a5b4c3d2e1f", + "authority": "Operator", + "request_id": "0000001f-0000-4000-8000-00000000001f" + } + ] + } + }, + { + "schemaVersion": "gamecult.fork.entry.v0", + "card": { + "schemaVersion": "gamecult.fork.card.v0", + "fork_id": "gamecult-chat:question:q4", + "title": "Fork 4", + "context": "Should a slot mark be shared?", + "options": [ + { + "id": "yes", + "text": "Share it." + }, + { + "id": "no", + "text": "Keep it local." + } + ], + "recommendation": "no", + "depends": [ + "gamecult-chat:question:file-zone-audience" + ], + "leans": null, + "flag": null, + "raised_in": { + "kind": "CutSpec", + "id": "cultlib-gaps:cut_spec:cut-slot-tolerance.r4" + }, + "deciders": [ + "e0d1c2b3-a495-4687-8879-6a5b4c3d2e1f" + ], + "status": "open", + "ruling": null, + "ruled_from": [], + "order": 4, + "options_hash": "a64604237f906b6fec9cb292863685d966abf8048f276afa24cd4f1e1c4c74aa" + }, + "record": { + "schemaVersion": "gamecult.fork.record.v0", + "fork_id": "gamecult-chat:question:q4", + "options_hash": "a64604237f906b6fec9cb292863685d966abf8048f276afa24cd4f1e1c4c74aa", + "counted_answer_id": null, + "answers": [] + } + }, + { + "schemaVersion": "gamecult.fork.entry.v0", + "card": { + "schemaVersion": "gamecult.fork.card.v0", + "fork_id": "gamecult-chat:question:q3", + "title": "Fork 3", + "context": "Should a slot mark be shared?", + "options": [ + { + "id": "yes", + "text": "Share it." + }, + { + "id": "no", + "text": "Keep it local." + } + ], + "recommendation": "no", + "depends": [ + "gamecult-chat:question:file-zone-audience" + ], + "leans": null, + "flag": null, + "raised_in": { + "kind": "CutSpec", + "id": "cultlib-gaps:cut_spec:cut-slot-tolerance.r4" + }, + "deciders": [ + "e0d1c2b3-a495-4687-8879-6a5b4c3d2e1f" + ], + "status": "closed", + "ruling": "cultlib-gaps:resolution:question.q3.n1", + "ruled_from": [ + "n1" + ], + "order": 3, + "options_hash": "ced892eb663c22e3d7ece602708043ee545f56b9d4e4421d7f5bfaa86c418ce2" + }, + "record": { + "schemaVersion": "gamecult.fork.record.v0", + "fork_id": "gamecult-chat:question:q3", + "options_hash": "ced892eb663c22e3d7ece602708043ee545f56b9d4e4421d7f5bfaa86c418ce2", + "counted_answer_id": "n1", + "answers": [ + { + "schemaVersion": "gamecult.desk_answer.v0", + "answer_id": "n1", + "fork_id": "gamecult-chat:question:q3", + "outcome": "Counted", + "of": null, + "retracts": null, + "code": null, + "options_hash": "ced892eb663c22e3d7ece602708043ee545f56b9d4e4421d7f5bfaa86c418ce2", + "choice": "yes", + "note": "Keep it local: a slot mark is the author's call, and sharing it is a separate act.", + "answered_at": "2026-11-12T19:02:00Z", + "received_at": "2026-11-12T19:02:05Z", + "claim_in_future": false, + "answerer": "e0d1c2b3-a495-4687-8879-6a5b4c3d2e1f", + "authority": "Operator", + "request_id": "00000020-0000-4000-8000-000000000020" + }, + { + "schemaVersion": "gamecult.desk_answer.v0", + "answer_id": "n2", + "fork_id": "gamecult-chat:question:q3", + "outcome": "Refused", + "of": null, + "retracts": null, + "code": "fork_closed", + "options_hash": "ced892eb663c22e3d7ece602708043ee545f56b9d4e4421d7f5bfaa86c418ce2", + "choice": "no", + "note": "Keep it local: a slot mark is the author's call, and sharing it is a separate act.", + "answered_at": "2026-11-12T19:02:00Z", + "received_at": "2026-11-12T19:04:05Z", + "claim_in_future": false, + "answerer": "9f8e7d6c-5b4a-4392-8170-0f1e2d3c4b5a", + "authority": "Steward", + "request_id": "00000021-0000-4000-8000-000000000021" + } + ] + } + }, + { + "schemaVersion": "gamecult.fork.entry.v0", + "card": { + "schemaVersion": "gamecult.fork.card.v0", + "fork_id": "gamecult-chat:question:q2", + "title": "Fork 2", + "context": "Should a slot mark be shared?", + "options": [ + { + "id": "yes", + "text": "Share it." + }, + { + "id": "no", + "text": "Keep it local." + } + ], + "recommendation": "no", + "depends": [ + "gamecult-chat:question:file-zone-audience" + ], + "leans": null, + "flag": null, + "raised_in": { + "kind": "CutSpec", + "id": "cultlib-gaps:cut_spec:cut-slot-tolerance.r4" + }, + "deciders": [ + "e0d1c2b3-a495-4687-8879-6a5b4c3d2e1f" + ], + "status": "open", + "ruling": null, + "ruled_from": [], + "order": 2, + "options_hash": "5c7c2945e8e8689c6c928b11d7e0a033640b0c66afb7fcffb9e740c019d63e79" + }, + "record": { + "schemaVersion": "gamecult.fork.record.v0", + "fork_id": "gamecult-chat:question:q2", + "options_hash": "5c7c2945e8e8689c6c928b11d7e0a033640b0c66afb7fcffb9e740c019d63e79", + "counted_answer_id": null, + "answers": [] + } + }, + { + "schemaVersion": "gamecult.fork.entry.v0", + "card": { + "schemaVersion": "gamecult.fork.card.v0", + "fork_id": "gamecult-chat:question:q1", + "title": "Fork 1", + "context": "Should a slot mark be shared?", + "options": [ + { + "id": "yes", + "text": "Share it." + }, + { + "id": "no", + "text": "Keep it local." + } + ], + "recommendation": "no", + "depends": [ + "gamecult-chat:question:file-zone-audience" + ], + "leans": null, + "flag": null, + "raised_in": { + "kind": "CutSpec", + "id": "cultlib-gaps:cut_spec:cut-slot-tolerance.r4" + }, + "deciders": [ + "e0d1c2b3-a495-4687-8879-6a5b4c3d2e1f" + ], + "status": "closed", + "ruling": "cultlib-gaps:resolution:question.q1.n1", + "ruled_from": [], + "order": 1, + "options_hash": "61959964bd214ea1b961554c43ec46a9faf156dd547cc50c8c16bbdcd55f223a" + }, + "record": { + "schemaVersion": "gamecult.fork.record.v0", + "fork_id": "gamecult-chat:question:q1", + "options_hash": "61959964bd214ea1b961554c43ec46a9faf156dd547cc50c8c16bbdcd55f223a", + "counted_answer_id": null, + "answers": [] + } + } + ], + "steps": [ + { + "request": { + "schemaVersion": "gamecult.fork.list_request.v0", + "mind": "gamecult-chat", + "status": null, + "cursor": null, + "limit": 0 + }, + "response": { + "schemaVersion": "gamecult.fork.list.v0", + "mind": "gamecult-chat", + "stamp": "s412", + "open_count": 3, + "recorded_count": 2, + "forks": [], + "next_cursor": null + }, + "responseHex": "87AD736368656D6156657273696F6EB567616D6563756C742E666F726B2E6C6973742E7630A46D696E64AD67616D6563756C742D63686174A57374616D70A473343132AA6F70656E5F636F756E7403AE7265636F726465645F636F756E7402A5666F726B7390AB6E6578745F637572736F72C0" + } + ] + }, + { + "name": "a_mind_with_no_forks", + "ordinal": 7, + "entries": [], + "steps": [ + { + "request": { + "schemaVersion": "gamecult.fork.list_request.v0", + "mind": "nibu", + "status": null, + "cursor": null, + "limit": 10 + }, + "response": { + "schemaVersion": "gamecult.fork.list.v0", + "mind": "nibu", + "stamp": "s7", + "open_count": 0, + "recorded_count": 0, + "forks": [], + "next_cursor": null + }, + "responseHex": "87AD736368656D6156657273696F6EB567616D6563756C742E666F726B2E6C6973742E7630A46D696E64A46E696275A57374616D70A27337AA6F70656E5F636F756E7400AE7265636F726465645F636F756E7400A5666F726B7390AB6E6578745F637572736F72C0" + } + ] + } + ], + "mindRoutes": { + "minds": { + "gamecult-chat": "huginn-gamecult-chat.sock", + "nibu": "huginn-nibu.sock" + }, + "forks": { + "gamecult-chat": [ + "gamecult-chat:question:q1", + "gamecult-chat:question:q2", + "gamecult-chat:question:slot-mark-consent" + ], + "nibu": [ + "nibu:question:glow-order" + ] + }, + "cases": [ + { + "name": "list_a_percent_encoded_configured_mind_is_decoded_once", + "route": "list", + "mind": "%6eibu", + "fork_id": null, + "token": { + "audience": "bifrost-desk", + "scopes": [ + "desk.read" + ], + "active": true + }, + "expect": "ok", + "httpStatus": 200, + "opened": "huginn-nibu.sock", + "stored_in": null + }, + { + "name": "list_a_configured_mind", + "route": "list", + "mind": "gamecult-chat", + "fork_id": null, + "token": { + "audience": "bifrost-desk", + "scopes": [ + "desk.read" + ], + "active": true + }, + "expect": "ok", + "httpStatus": 200, + "opened": "huginn-gamecult-chat.sock", + "stored_in": null + }, + { + "name": "list_another_configured_mind", + "route": "list", + "mind": "nibu", + "fork_id": null, + "token": { + "audience": "bifrost-desk", + "scopes": [ + "desk.read" + ], + "active": true + }, + "expect": "ok", + "httpStatus": 200, + "opened": "huginn-nibu.sock", + "stored_in": null + }, + { + "name": "read_a_fork_the_mind_holds", + "route": "read", + "mind": "gamecult-chat", + "fork_id": "gamecult-chat:question:slot-mark-consent", + "token": { + "audience": "bifrost-desk", + "scopes": [ + "desk.read" + ], + "active": true + }, + "expect": "ok", + "httpStatus": 200, + "opened": "huginn-gamecult-chat.sock", + "stored_in": null + }, + { + "name": "answer_a_fork_the_mind_holds_is_stored_in_that_mind", + "route": "answer", + "mind": "gamecult-chat", + "fork_id": "gamecult-chat:question:slot-mark-consent", + "token": { + "audience": "bifrost-desk", + "scopes": [ + "desk.answer" + ], + "active": true + }, + "expect": "ok", + "httpStatus": 200, + "opened": "huginn-gamecult-chat.sock", + "stored_in": "gamecult-chat" + }, + { + "name": "answer_on_the_other_mind", + "route": "answer", + "mind": "nibu", + "fork_id": "nibu:question:glow-order", + "token": { + "audience": "bifrost-desk", + "scopes": [ + "desk.answer" + ], + "active": true + }, + "expect": "ok", + "httpStatus": 200, + "opened": "huginn-nibu.sock", + "stored_in": "nibu" + }, + { + "name": "M1_list_dot_dot_is_unknown_and_opens_nothing", + "route": "list", + "mind": "../x", + "fork_id": null, + "token": { + "audience": "bifrost-desk", + "scopes": [ + "desk.read" + ], + "active": true + }, + "expect": "fork_unknown", + "httpStatus": 404, + "opened": null, + "stored_in": null + }, + { + "name": "M1_list_slash_is_unknown_and_opens_nothing", + "route": "list", + "mind": "a/b", + "fork_id": null, + "token": { + "audience": "bifrost-desk", + "scopes": [ + "desk.read" + ], + "active": true + }, + "expect": "fork_unknown", + "httpStatus": 404, + "opened": null, + "stored_in": null + }, + { + "name": "M1_list_encoded_slash_is_unknown_and_opens_nothing", + "route": "list", + "mind": "a%2fb", + "fork_id": null, + "token": { + "audience": "bifrost-desk", + "scopes": [ + "desk.read" + ], + "active": true + }, + "expect": "fork_unknown", + "httpStatus": 404, + "opened": null, + "stored_in": null + }, + { + "name": "M1_list_encoded_dot_dot_is_unknown_and_opens_nothing", + "route": "list", + "mind": "%2e%2e%2fx", + "fork_id": null, + "token": { + "audience": "bifrost-desk", + "scopes": [ + "desk.read" + ], + "active": true + }, + "expect": "fork_unknown", + "httpStatus": 404, + "opened": null, + "stored_in": null + }, + { + "name": "M1_list_nul_is_unknown_and_opens_nothing", + "route": "list", + "mind": "eureka%00", + "fork_id": null, + "token": { + "audience": "bifrost-desk", + "scopes": [ + "desk.read" + ], + "active": true + }, + "expect": "fork_unknown", + "httpStatus": 404, + "opened": null, + "stored_in": null + }, + { + "name": "M1_list_four_kb_is_unknown_and_opens_nothing", + "route": "list", + "mind": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", + "fork_id": null, + "token": { + "audience": "bifrost-desk", + "scopes": [ + "desk.read" + ], + "active": true + }, + "expect": "fork_unknown", + "httpStatus": 404, + "opened": null, + "stored_in": null + }, + { + "name": "M1_list_other_case_is_unknown_and_opens_nothing", + "route": "list", + "mind": "GAMECULT-CHAT", + "fork_id": null, + "token": { + "audience": "bifrost-desk", + "scopes": [ + "desk.read" + ], + "active": true + }, + "expect": "fork_unknown", + "httpStatus": 404, + "opened": null, + "stored_in": null + }, + { + "name": "M1_list_trailing_space_is_unknown_and_opens_nothing", + "route": "list", + "mind": "nibu%20", + "fork_id": null, + "token": { + "audience": "bifrost-desk", + "scopes": [ + "desk.read" + ], + "active": true + }, + "expect": "fork_unknown", + "httpStatus": 404, + "opened": null, + "stored_in": null + }, + { + "name": "M1_list_invalid_utf8_is_unknown_and_opens_nothing", + "route": "list", + "mind": "%ff", + "fork_id": null, + "token": { + "audience": "bifrost-desk", + "scopes": [ + "desk.read" + ], + "active": true + }, + "expect": "fork_unknown", + "httpStatus": 404, + "opened": null, + "stored_in": null + }, + { + "name": "M1_list_dot_is_unknown_and_opens_nothing", + "route": "list", + "mind": ".", + "fork_id": null, + "token": { + "audience": "bifrost-desk", + "scopes": [ + "desk.read" + ], + "active": true + }, + "expect": "fork_unknown", + "httpStatus": 404, + "opened": null, + "stored_in": null + }, + { + "name": "M1_list_dots_is_unknown_and_opens_nothing", + "route": "list", + "mind": "..", + "fork_id": null, + "token": { + "audience": "bifrost-desk", + "scopes": [ + "desk.read" + ], + "active": true + }, + "expect": "fork_unknown", + "httpStatus": 404, + "opened": null, + "stored_in": null + }, + { + "name": "M1_list_double_encoded_is_unknown_and_opens_nothing", + "route": "list", + "mind": "%256eibu", + "fork_id": null, + "token": { + "audience": "bifrost-desk", + "scopes": [ + "desk.read" + ], + "active": true + }, + "expect": "fork_unknown", + "httpStatus": 404, + "opened": null, + "stored_in": null + }, + { + "name": "M1_read_dot_dot_is_unknown_and_opens_nothing", + "route": "read", + "mind": "../x", + "fork_id": "gamecult-chat:question:slot-mark-consent", + "token": { + "audience": "bifrost-desk", + "scopes": [ + "desk.read" + ], + "active": true + }, + "expect": "fork_unknown", + "httpStatus": 404, + "opened": null, + "stored_in": null + }, + { + "name": "M1_read_slash_is_unknown_and_opens_nothing", + "route": "read", + "mind": "a/b", + "fork_id": "gamecult-chat:question:slot-mark-consent", + "token": { + "audience": "bifrost-desk", + "scopes": [ + "desk.read" + ], + "active": true + }, + "expect": "fork_unknown", + "httpStatus": 404, + "opened": null, + "stored_in": null + }, + { + "name": "M1_read_encoded_slash_is_unknown_and_opens_nothing", + "route": "read", + "mind": "a%2fb", + "fork_id": "gamecult-chat:question:slot-mark-consent", + "token": { + "audience": "bifrost-desk", + "scopes": [ + "desk.read" + ], + "active": true + }, + "expect": "fork_unknown", + "httpStatus": 404, + "opened": null, + "stored_in": null + }, + { + "name": "M1_read_encoded_dot_dot_is_unknown_and_opens_nothing", + "route": "read", + "mind": "%2e%2e%2fx", + "fork_id": "gamecult-chat:question:slot-mark-consent", + "token": { + "audience": "bifrost-desk", + "scopes": [ + "desk.read" + ], + "active": true + }, + "expect": "fork_unknown", + "httpStatus": 404, + "opened": null, + "stored_in": null + }, + { + "name": "M1_read_nul_is_unknown_and_opens_nothing", + "route": "read", + "mind": "eureka%00", + "fork_id": "gamecult-chat:question:slot-mark-consent", + "token": { + "audience": "bifrost-desk", + "scopes": [ + "desk.read" + ], + "active": true + }, + "expect": "fork_unknown", + "httpStatus": 404, + "opened": null, + "stored_in": null + }, + { + "name": "M1_read_four_kb_is_unknown_and_opens_nothing", + "route": "read", + "mind": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", + "fork_id": "gamecult-chat:question:slot-mark-consent", + "token": { + "audience": "bifrost-desk", + "scopes": [ + "desk.read" + ], + "active": true + }, + "expect": "fork_unknown", + "httpStatus": 404, + "opened": null, + "stored_in": null + }, + { + "name": "M1_read_other_case_is_unknown_and_opens_nothing", + "route": "read", + "mind": "GAMECULT-CHAT", + "fork_id": "gamecult-chat:question:slot-mark-consent", + "token": { + "audience": "bifrost-desk", + "scopes": [ + "desk.read" + ], + "active": true + }, + "expect": "fork_unknown", + "httpStatus": 404, + "opened": null, + "stored_in": null + }, + { + "name": "M1_read_trailing_space_is_unknown_and_opens_nothing", + "route": "read", + "mind": "nibu%20", + "fork_id": "gamecult-chat:question:slot-mark-consent", + "token": { + "audience": "bifrost-desk", + "scopes": [ + "desk.read" + ], + "active": true + }, + "expect": "fork_unknown", + "httpStatus": 404, + "opened": null, + "stored_in": null + }, + { + "name": "M1_read_invalid_utf8_is_unknown_and_opens_nothing", + "route": "read", + "mind": "%ff", + "fork_id": "gamecult-chat:question:slot-mark-consent", + "token": { + "audience": "bifrost-desk", + "scopes": [ + "desk.read" + ], + "active": true + }, + "expect": "fork_unknown", + "httpStatus": 404, + "opened": null, + "stored_in": null + }, + { + "name": "M1_read_dot_is_unknown_and_opens_nothing", + "route": "read", + "mind": ".", + "fork_id": "gamecult-chat:question:slot-mark-consent", + "token": { + "audience": "bifrost-desk", + "scopes": [ + "desk.read" + ], + "active": true + }, + "expect": "fork_unknown", + "httpStatus": 404, + "opened": null, + "stored_in": null + }, + { + "name": "M1_read_dots_is_unknown_and_opens_nothing", + "route": "read", + "mind": "..", + "fork_id": "gamecult-chat:question:slot-mark-consent", + "token": { + "audience": "bifrost-desk", + "scopes": [ + "desk.read" + ], + "active": true + }, + "expect": "fork_unknown", + "httpStatus": 404, + "opened": null, + "stored_in": null + }, + { + "name": "M1_read_double_encoded_is_unknown_and_opens_nothing", + "route": "read", + "mind": "%256eibu", + "fork_id": "gamecult-chat:question:slot-mark-consent", + "token": { + "audience": "bifrost-desk", + "scopes": [ + "desk.read" + ], + "active": true + }, + "expect": "fork_unknown", + "httpStatus": 404, + "opened": null, + "stored_in": null + }, + { + "name": "M1_answer_dot_dot_is_unknown_and_opens_nothing", + "route": "answer", + "mind": "../x", + "fork_id": "gamecult-chat:question:slot-mark-consent", + "token": { + "audience": "bifrost-desk", + "scopes": [ + "desk.answer" + ], + "active": true + }, + "expect": "fork_unknown", + "httpStatus": 404, + "opened": null, + "stored_in": null + }, + { + "name": "M1_answer_slash_is_unknown_and_opens_nothing", + "route": "answer", + "mind": "a/b", + "fork_id": "gamecult-chat:question:slot-mark-consent", + "token": { + "audience": "bifrost-desk", + "scopes": [ + "desk.answer" + ], + "active": true + }, + "expect": "fork_unknown", + "httpStatus": 404, + "opened": null, + "stored_in": null + }, + { + "name": "M1_answer_encoded_slash_is_unknown_and_opens_nothing", + "route": "answer", + "mind": "a%2fb", + "fork_id": "gamecult-chat:question:slot-mark-consent", + "token": { + "audience": "bifrost-desk", + "scopes": [ + "desk.answer" + ], + "active": true + }, + "expect": "fork_unknown", + "httpStatus": 404, + "opened": null, + "stored_in": null + }, + { + "name": "M1_answer_encoded_dot_dot_is_unknown_and_opens_nothing", + "route": "answer", + "mind": "%2e%2e%2fx", + "fork_id": "gamecult-chat:question:slot-mark-consent", + "token": { + "audience": "bifrost-desk", + "scopes": [ + "desk.answer" + ], + "active": true + }, + "expect": "fork_unknown", + "httpStatus": 404, + "opened": null, + "stored_in": null + }, + { + "name": "M1_answer_nul_is_unknown_and_opens_nothing", + "route": "answer", + "mind": "eureka%00", + "fork_id": "gamecult-chat:question:slot-mark-consent", + "token": { + "audience": "bifrost-desk", + "scopes": [ + "desk.answer" + ], + "active": true + }, + "expect": "fork_unknown", + "httpStatus": 404, + "opened": null, + "stored_in": null + }, + { + "name": "M1_answer_four_kb_is_unknown_and_opens_nothing", + "route": "answer", + "mind": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", + "fork_id": "gamecult-chat:question:slot-mark-consent", + "token": { + "audience": "bifrost-desk", + "scopes": [ + "desk.answer" + ], + "active": true + }, + "expect": "fork_unknown", + "httpStatus": 404, + "opened": null, + "stored_in": null + }, + { + "name": "M1_answer_other_case_is_unknown_and_opens_nothing", + "route": "answer", + "mind": "GAMECULT-CHAT", + "fork_id": "gamecult-chat:question:slot-mark-consent", + "token": { + "audience": "bifrost-desk", + "scopes": [ + "desk.answer" + ], + "active": true + }, + "expect": "fork_unknown", + "httpStatus": 404, + "opened": null, + "stored_in": null + }, + { + "name": "M1_answer_trailing_space_is_unknown_and_opens_nothing", + "route": "answer", + "mind": "nibu%20", + "fork_id": "gamecult-chat:question:slot-mark-consent", + "token": { + "audience": "bifrost-desk", + "scopes": [ + "desk.answer" + ], + "active": true + }, + "expect": "fork_unknown", + "httpStatus": 404, + "opened": null, + "stored_in": null + }, + { + "name": "M1_answer_invalid_utf8_is_unknown_and_opens_nothing", + "route": "answer", + "mind": "%ff", + "fork_id": "gamecult-chat:question:slot-mark-consent", + "token": { + "audience": "bifrost-desk", + "scopes": [ + "desk.answer" + ], + "active": true + }, + "expect": "fork_unknown", + "httpStatus": 404, + "opened": null, + "stored_in": null + }, + { + "name": "M1_answer_dot_is_unknown_and_opens_nothing", + "route": "answer", + "mind": ".", + "fork_id": "gamecult-chat:question:slot-mark-consent", + "token": { + "audience": "bifrost-desk", + "scopes": [ + "desk.answer" + ], + "active": true + }, + "expect": "fork_unknown", + "httpStatus": 404, + "opened": null, + "stored_in": null + }, + { + "name": "M1_answer_dots_is_unknown_and_opens_nothing", + "route": "answer", + "mind": "..", + "fork_id": "gamecult-chat:question:slot-mark-consent", + "token": { + "audience": "bifrost-desk", + "scopes": [ + "desk.answer" + ], + "active": true + }, + "expect": "fork_unknown", + "httpStatus": 404, + "opened": null, + "stored_in": null + }, + { + "name": "M1_answer_double_encoded_is_unknown_and_opens_nothing", + "route": "answer", + "mind": "%256eibu", + "fork_id": "gamecult-chat:question:slot-mark-consent", + "token": { + "audience": "bifrost-desk", + "scopes": [ + "desk.answer" + ], + "active": true + }, + "expect": "fork_unknown", + "httpStatus": 404, + "opened": null, + "stored_in": null + }, + { + "name": "M2_read_unknown_mind_known_fork", + "route": "read", + "mind": "ghost", + "fork_id": "gamecult-chat:question:slot-mark-consent", + "token": { + "audience": "bifrost-desk", + "scopes": [ + "desk.read" + ], + "active": true + }, + "expect": "fork_unknown", + "httpStatus": 404, + "opened": null, + "stored_in": null + }, + { + "name": "M2_read_known_mind_unknown_fork", + "route": "read", + "mind": "gamecult-chat", + "fork_id": "gamecult-chat:question:ghost", + "token": { + "audience": "bifrost-desk", + "scopes": [ + "desk.read" + ], + "active": true + }, + "expect": "fork_unknown", + "httpStatus": 404, + "opened": "huginn-gamecult-chat.sock", + "stored_in": null + }, + { + "name": "M2_answer_unknown_mind_known_fork", + "route": "answer", + "mind": "ghost", + "fork_id": "gamecult-chat:question:slot-mark-consent", + "token": { + "audience": "bifrost-desk", + "scopes": [ + "desk.answer" + ], + "active": true + }, + "expect": "fork_unknown", + "httpStatus": 404, + "opened": null, + "stored_in": null + }, + { + "name": "M2_answer_known_mind_unknown_fork", + "route": "answer", + "mind": "gamecult-chat", + "fork_id": "gamecult-chat:question:ghost", + "token": { + "audience": "bifrost-desk", + "scopes": [ + "desk.answer" + ], + "active": true + }, + "expect": "fork_unknown", + "httpStatus": 404, + "opened": "huginn-gamecult-chat.sock", + "stored_in": null + }, + { + "name": "M2_list_unknown_mind", + "route": "list", + "mind": "ghost", + "fork_id": null, + "token": { + "audience": "bifrost-desk", + "scopes": [ + "desk.read" + ], + "active": true + }, + "expect": "fork_unknown", + "httpStatus": 404, + "opened": null, + "stored_in": null + }, + { + "name": "M3_answer_on_nibu_with_a_fork_gamecult_chat_holds", + "route": "answer", + "mind": "nibu", + "fork_id": "gamecult-chat:question:slot-mark-consent", + "token": { + "audience": "bifrost-desk", + "scopes": [ + "desk.answer" + ], + "active": true + }, + "expect": "fork_unknown", + "httpStatus": 404, + "opened": "huginn-nibu.sock", + "stored_in": null + }, + { + "name": "M3_answer_on_gamecult_chat_with_a_fork_nibu_holds", + "route": "answer", + "mind": "gamecult-chat", + "fork_id": "nibu:question:glow-order", + "token": { + "audience": "bifrost-desk", + "scopes": [ + "desk.answer" + ], + "active": true + }, + "expect": "fork_unknown", + "httpStatus": 404, + "opened": "huginn-gamecult-chat.sock", + "stored_in": null + }, + { + "name": "M4_desk_read_lists_every_mind", + "route": "list", + "mind": "nibu", + "fork_id": null, + "token": { + "audience": "bifrost-desk", + "scopes": [ + "desk.read" + ], + "active": true + }, + "expect": "ok", + "httpStatus": 200, + "opened": "huginn-nibu.sock", + "stored_in": null + }, + { + "name": "M4_desk_read_reads_every_mind", + "route": "read", + "mind": "nibu", + "fork_id": "nibu:question:glow-order", + "token": { + "audience": "bifrost-desk", + "scopes": [ + "desk.read" + ], + "active": true + }, + "expect": "ok", + "httpStatus": 200, + "opened": "huginn-nibu.sock", + "stored_in": null + }, + { + "name": "M4_desk_answer_cannot_list_any_mind", + "route": "list", + "mind": "gamecult-chat", + "fork_id": null, + "token": { + "audience": "bifrost-desk", + "scopes": [ + "desk.answer" + ], + "active": true + }, + "expect": "insufficient_scope", + "httpStatus": 403, + "opened": null, + "stored_in": null + }, + { + "name": "M4_desk_answer_cannot_list_the_other_mind", + "route": "list", + "mind": "nibu", + "fork_id": null, + "token": { + "audience": "bifrost-desk", + "scopes": [ + "desk.answer" + ], + "active": true + }, + "expect": "insufficient_scope", + "httpStatus": 403, + "opened": null, + "stored_in": null + }, + { + "name": "M4_desk_read_cannot_answer_on_any_mind", + "route": "answer", + "mind": "nibu", + "fork_id": "nibu:question:glow-order", + "token": { + "audience": "bifrost-desk", + "scopes": [ + "desk.read" + ], + "active": true + }, + "expect": "insufficient_scope", + "httpStatus": 403, + "opened": null, + "stored_in": null + }, + { + "name": "auth_comes_before_the_mind_lookup_no_bearer", + "route": "list", + "mind": "ghost", + "fork_id": null, + "token": null, + "expect": "unauthenticated", + "httpStatus": 401, + "opened": null, + "stored_in": null + }, + { + "name": "auth_comes_before_the_mind_lookup_scope", + "route": "answer", + "mind": "ghost", + "fork_id": "gamecult-chat:question:slot-mark-consent", + "token": { + "audience": "bifrost-desk", + "scopes": [ + "desk.read" + ], + "active": true + }, + "expect": "insufficient_scope", + "httpStatus": 403, + "opened": null, + "stored_in": null + }, + { + "name": "auth_comes_before_the_mind_lookup_non_active", + "route": "read", + "mind": "ghost", + "fork_id": "gamecult-chat:question:slot-mark-consent", + "token": { + "audience": "bifrost-desk", + "scopes": [ + "desk.read" + ], + "active": false + }, + "expect": "forbidden", + "httpStatus": 403, + "opened": null, + "stored_in": null + } + ], + "sameResponse": [ + [ + "M2_read_unknown_mind_known_fork", + "M2_read_known_mind_unknown_fork" + ], + [ + "M2_answer_unknown_mind_known_fork", + "M2_answer_known_mind_unknown_fork" + ], + [ + "M2_list_unknown_mind", + "M2_read_known_mind_unknown_fork" + ], + [ + "M3_answer_on_nibu_with_a_fork_gamecult_chat_holds", + "M2_answer_known_mind_unknown_fork" + ] + ] + }, + "authCases": [ + { + "name": "list_with_desk_read", + "route": "list", + "token": { + "audience": "bifrost-desk", + "scopes": [ + "desk.read" + ], + "active": true + }, + "expect": "ok", + "huginn_down": false + }, + { + "name": "read_with_desk_read", + "route": "read", + "token": { + "audience": "bifrost-desk", + "scopes": [ + "desk.read" + ], + "active": true + }, + "expect": "ok", + "huginn_down": false + }, + { + "name": "answer_with_desk_answer", + "route": "answer", + "token": { + "audience": "bifrost-desk", + "scopes": [ + "desk.answer" + ], + "active": true + }, + "expect": "ok", + "huginn_down": false + }, + { + "name": "answer_with_both_scopes", + "route": "answer", + "token": { + "audience": "bifrost-desk", + "scopes": [ + "desk.read", + "desk.answer" + ], + "active": true + }, + "expect": "ok", + "huginn_down": false + }, + { + "name": "no_bearer_list", + "route": "list", + "token": null, + "expect": "unauthenticated", + "huginn_down": false + }, + { + "name": "no_bearer_answer", + "route": "answer", + "token": null, + "expect": "unauthenticated", + "huginn_down": false + }, + { + "name": "the_MAS_token_is_refused", + "route": "list", + "token": { + "audience": "matrix-authentication-service", + "scopes": [ + "openid", + "profile" + ], + "active": true + }, + "expect": "unauthenticated", + "huginn_down": false + }, + { + "name": "a_votes_token_cannot_list", + "route": "list", + "token": { + "audience": "bifrost-votes", + "scopes": [ + "votes.read" + ], + "active": true + }, + "expect": "unauthenticated", + "huginn_down": false + }, + { + "name": "a_votes_token_cannot_read", + "route": "read", + "token": { + "audience": "bifrost-votes", + "scopes": [ + "votes.read" + ], + "active": true + }, + "expect": "unauthenticated", + "huginn_down": false + }, + { + "name": "a_votes_token_cannot_answer", + "route": "answer", + "token": { + "audience": "bifrost-votes", + "scopes": [ + "votes.cast" + ], + "active": true + }, + "expect": "unauthenticated", + "huginn_down": false + }, + { + "name": "desk_audience_with_a_votes_scope_only", + "route": "answer", + "token": { + "audience": "bifrost-desk", + "scopes": [ + "votes.cast" + ], + "active": true + }, + "expect": "insufficient_scope", + "huginn_down": false + }, + { + "name": "desk_read_cannot_answer", + "route": "answer", + "token": { + "audience": "bifrost-desk", + "scopes": [ + "desk.read" + ], + "active": true + }, + "expect": "insufficient_scope", + "huginn_down": false + }, + { + "name": "desk_answer_cannot_list", + "route": "list", + "token": { + "audience": "bifrost-desk", + "scopes": [ + "desk.answer" + ], + "active": true + }, + "expect": "insufficient_scope", + "huginn_down": false + }, + { + "name": "a_non_active_member_cannot_list", + "route": "list", + "token": { + "audience": "bifrost-desk", + "scopes": [ + "desk.read" + ], + "active": false + }, + "expect": "forbidden", + "huginn_down": false + }, + { + "name": "a_non_active_member_cannot_read", + "route": "read", + "token": { + "audience": "bifrost-desk", + "scopes": [ + "desk.read" + ], + "active": false + }, + "expect": "forbidden", + "huginn_down": false + }, + { + "name": "a_non_active_member_cannot_answer", + "route": "answer", + "token": { + "audience": "bifrost-desk", + "scopes": [ + "desk.answer" + ], + "active": false + }, + "expect": "forbidden", + "huginn_down": false + }, + { + "name": "scope_is_judged_before_membership", + "route": "answer", + "token": { + "audience": "bifrost-desk", + "scopes": [ + "desk.read" + ], + "active": false + }, + "expect": "insufficient_scope", + "huginn_down": false + }, + { + "name": "huginn_down_list", + "route": "list", + "token": { + "audience": "bifrost-desk", + "scopes": [ + "desk.read" + ], + "active": true + }, + "expect": "unavailable", + "huginn_down": true + }, + { + "name": "huginn_down_read", + "route": "read", + "token": { + "audience": "bifrost-desk", + "scopes": [ + "desk.read" + ], + "active": true + }, + "expect": "unavailable", + "huginn_down": true + }, + { + "name": "huginn_down_answer", + "route": "answer", + "token": { + "audience": "bifrost-desk", + "scopes": [ + "desk.answer" + ], + "active": true + }, + "expect": "unavailable", + "huginn_down": true + }, + { + "name": "auth_is_judged_before_huginn_is_asked", + "route": "list", + "token": null, + "expect": "unauthenticated", + "huginn_down": true + }, + { + "name": "membership_is_judged_before_huginn_is_asked", + "route": "read", + "token": { + "audience": "bifrost-desk", + "scopes": [ + "desk.read" + ], + "active": false + }, + "expect": "forbidden", + "huginn_down": true + } + ] + }, + "scenarios": [ + { + "name": "operator_answers_and_is_counted", + "card": { + "schemaVersion": "gamecult.fork.card.v0", + "fork_id": "gamecult-chat:question:slot-mark-consent", + "title": "Slot mark consent", + "context": "Should a slot mark be shared?", + "options": [ + { + "id": "yes", + "text": "Share it." + }, + { + "id": "no", + "text": "Keep it local." + } + ], + "recommendation": "no", + "depends": [ + "gamecult-chat:question:file-zone-audience" + ], + "leans": null, + "flag": null, + "raised_in": { + "kind": "CutSpec", + "id": "cultlib-gaps:cut_spec:cut-slot-tolerance.r4" + }, + "deciders": [ + "e0d1c2b3-a495-4687-8879-6a5b4c3d2e1f" + ], + "status": "open", + "ruling": null, + "ruled_from": [], + "order": 3, + "options_hash": "7d95fc87d532420611fe415c1def27a3447617bb68235f46e3af130aea361c48" + }, + "optionsHash": "7d95fc87d532420611fe415c1def27a3447617bb68235f46e3af130aea361c48", + "steps": [ + { + "kind": "answer", + "body": { + "schemaVersion": "gamecult.fork.answer_request.v0", + "fork_id": "gamecult-chat:question:slot-mark-consent", + "options_hash": "7d95fc87d532420611fe415c1def27a3447617bb68235f46e3af130aea361c48", + "answer": { + "choice": "no", + "note": "Keep it local: a slot mark is the author's call, and sharing it is a separate act." + }, + "retracted": false, + "retracts": null, + "answered_at": "2026-11-12T19:02:00Z", + "request_id": "00000001-0000-4000-8000-000000000001" + }, + "server": { + "answer_id": "n1", + "answerer": "e0d1c2b3-a495-4687-8879-6a5b4c3d2e1f", + "authority": "Operator", + "received_at": "2026-11-12T19:02:05Z" + }, + "response": { + "schemaVersion": "gamecult.fork.answer_result.v0", + "answer": { + "schemaVersion": "gamecult.desk_answer.v0", + "answer_id": "n1", + "fork_id": "gamecult-chat:question:slot-mark-consent", + "outcome": "Counted", + "of": null, + "retracts": null, + "code": null, + "options_hash": "7d95fc87d532420611fe415c1def27a3447617bb68235f46e3af130aea361c48", + "choice": "no", + "note": "Keep it local: a slot mark is the author's call, and sharing it is a separate act.", + "answered_at": "2026-11-12T19:02:00Z", + "received_at": "2026-11-12T19:02:05Z", + "claim_in_future": false, + "answerer": "e0d1c2b3-a495-4687-8879-6a5b4c3d2e1f", + "authority": "Operator", + "request_id": "00000001-0000-4000-8000-000000000001" + }, + "card": null + } + } + ], + "record": { + "schemaVersion": "gamecult.fork.record.v0", + "fork_id": "gamecult-chat:question:slot-mark-consent", + "options_hash": "7d95fc87d532420611fe415c1def27a3447617bb68235f46e3af130aea361c48", + "counted_answer_id": "n1", + "answers": [ + { + "schemaVersion": "gamecult.desk_answer.v0", + "answer_id": "n1", + "fork_id": "gamecult-chat:question:slot-mark-consent", + "outcome": "Counted", + "of": null, + "retracts": null, + "code": null, + "options_hash": "7d95fc87d532420611fe415c1def27a3447617bb68235f46e3af130aea361c48", + "choice": "no", + "note": "Keep it local: a slot mark is the author's call, and sharing it is a separate act.", + "answered_at": "2026-11-12T19:02:00Z", + "received_at": "2026-11-12T19:02:05Z", + "claim_in_future": false, + "answerer": "e0d1c2b3-a495-4687-8879-6a5b4c3d2e1f", + "authority": "Operator", + "request_id": "00000001-0000-4000-8000-000000000001" + } + ] + }, + "recordHex": "85AD736368656D6156657273696F6EB767616D6563756C742E666F726B2E7265636F72642E7630A7666F726B5F6964D92867616D6563756C742D636861743A7175657374696F6E3A736C6F742D6D61726B2D636F6E73656E74AC6F7074696F6E735F68617368D94037643935666338376435333234323036313166653431356331646566323761333434373631376262363832333566343665336166313330616561333631633438B1636F756E7465645F616E737765725F6964A26E31A7616E737765727391DE0010AD736368656D6156657273696F6EB767616D6563756C742E6465736B5F616E737765722E7630A9616E737765725F6964A26E31A7666F726B5F6964D92867616D6563756C742D636861743A7175657374696F6E3A736C6F742D6D61726B2D636F6E73656E74A76F7574636F6D65A7436F756E746564A26F66C0A87265747261637473C0A4636F6465C0AC6F7074696F6E735F68617368D94037643935666338376435333234323036313166653431356331646566323761333434373631376262363832333566343665336166313330616561333631633438A663686F696365A26E6FA46E6F7465D9524B656570206974206C6F63616C3A206120736C6F74206D61726B2069732074686520617574686F7227732063616C6C2C20616E642073686172696E672069742069732061207365706172617465206163742EAB616E7377657265645F6174B4323032362D31312D31325431393A30323A30305AAB72656365697665645F6174B4323032362D31312D31325431393A30323A30355AAF636C61696D5F696E5F667574757265C2A8616E737765726572D92465306431633262332D613439352D343638372D383837392D366135623463336432653166A9617574686F72697479A84F70657261746F72AA726571756573745F6964D92430303030303030312D303030302D343030302D383030302D303030303030303030303031" + }, + { + "name": "choice_alone_is_counted", + "card": { + "schemaVersion": "gamecult.fork.card.v0", + "fork_id": "gamecult-chat:question:slot-mark-consent", + "title": "Slot mark consent", + "context": "Should a slot mark be shared?", + "options": [ + { + "id": "yes", + "text": "Share it." + }, + { + "id": "no", + "text": "Keep it local." + } + ], + "recommendation": "no", + "depends": [ + "gamecult-chat:question:file-zone-audience" + ], + "leans": null, + "flag": null, + "raised_in": { + "kind": "CutSpec", + "id": "cultlib-gaps:cut_spec:cut-slot-tolerance.r4" + }, + "deciders": [ + "e0d1c2b3-a495-4687-8879-6a5b4c3d2e1f" + ], + "status": "open", + "ruling": null, + "ruled_from": [], + "order": 3, + "options_hash": "7d95fc87d532420611fe415c1def27a3447617bb68235f46e3af130aea361c48" + }, + "optionsHash": "7d95fc87d532420611fe415c1def27a3447617bb68235f46e3af130aea361c48", + "steps": [ + { + "kind": "answer", + "body": { + "schemaVersion": "gamecult.fork.answer_request.v0", + "fork_id": "gamecult-chat:question:slot-mark-consent", + "options_hash": "7d95fc87d532420611fe415c1def27a3447617bb68235f46e3af130aea361c48", + "answer": { + "choice": "no", + "note": null + }, + "retracted": false, + "retracts": null, + "answered_at": "2026-11-12T19:02:00Z", + "request_id": "00000005-0000-4000-8000-000000000005" + }, + "server": { + "answer_id": "n1", + "answerer": "e0d1c2b3-a495-4687-8879-6a5b4c3d2e1f", + "authority": "Operator", + "received_at": "2026-11-12T19:02:05Z" + }, + "response": { + "schemaVersion": "gamecult.fork.answer_result.v0", + "answer": { + "schemaVersion": "gamecult.desk_answer.v0", + "answer_id": "n1", + "fork_id": "gamecult-chat:question:slot-mark-consent", + "outcome": "Counted", + "of": null, + "retracts": null, + "code": null, + "options_hash": "7d95fc87d532420611fe415c1def27a3447617bb68235f46e3af130aea361c48", + "choice": "no", + "note": null, + "answered_at": "2026-11-12T19:02:00Z", + "received_at": "2026-11-12T19:02:05Z", + "claim_in_future": false, + "answerer": "e0d1c2b3-a495-4687-8879-6a5b4c3d2e1f", + "authority": "Operator", + "request_id": "00000005-0000-4000-8000-000000000005" + }, + "card": null + } + } + ], + "record": { + "schemaVersion": "gamecult.fork.record.v0", + "fork_id": "gamecult-chat:question:slot-mark-consent", + "options_hash": "7d95fc87d532420611fe415c1def27a3447617bb68235f46e3af130aea361c48", + "counted_answer_id": "n1", + "answers": [ + { + "schemaVersion": "gamecult.desk_answer.v0", + "answer_id": "n1", + "fork_id": "gamecult-chat:question:slot-mark-consent", + "outcome": "Counted", + "of": null, + "retracts": null, + "code": null, + "options_hash": "7d95fc87d532420611fe415c1def27a3447617bb68235f46e3af130aea361c48", + "choice": "no", + "note": null, + "answered_at": "2026-11-12T19:02:00Z", + "received_at": "2026-11-12T19:02:05Z", + "claim_in_future": false, + "answerer": "e0d1c2b3-a495-4687-8879-6a5b4c3d2e1f", + "authority": "Operator", + "request_id": "00000005-0000-4000-8000-000000000005" + } + ] + }, + "recordHex": "85AD736368656D6156657273696F6EB767616D6563756C742E666F726B2E7265636F72642E7630A7666F726B5F6964D92867616D6563756C742D636861743A7175657374696F6E3A736C6F742D6D61726B2D636F6E73656E74AC6F7074696F6E735F68617368D94037643935666338376435333234323036313166653431356331646566323761333434373631376262363832333566343665336166313330616561333631633438B1636F756E7465645F616E737765725F6964A26E31A7616E737765727391DE0010AD736368656D6156657273696F6EB767616D6563756C742E6465736B5F616E737765722E7630A9616E737765725F6964A26E31A7666F726B5F6964D92867616D6563756C742D636861743A7175657374696F6E3A736C6F742D6D61726B2D636F6E73656E74A76F7574636F6D65A7436F756E746564A26F66C0A87265747261637473C0A4636F6465C0AC6F7074696F6E735F68617368D94037643935666338376435333234323036313166653431356331646566323761333434373631376262363832333566343665336166313330616561333631633438A663686F696365A26E6FA46E6F7465C0AB616E7377657265645F6174B4323032362D31312D31325431393A30323A30305AAB72656365697665645F6174B4323032362D31312D31325431393A30323A30355AAF636C61696D5F696E5F667574757265C2A8616E737765726572D92465306431633262332D613439352D343638372D383837392D366135623463336432653166A9617574686F72697479A84F70657261746F72AA726571756573745F6964D92430303030303030352D303030302D343030302D383030302D303030303030303030303035" + }, + { + "name": "note_alone_is_counted_a_clear_pick", + "card": { + "schemaVersion": "gamecult.fork.card.v0", + "fork_id": "gamecult-chat:question:slot-mark-consent", + "title": "Slot mark consent", + "context": "Should a slot mark be shared?", + "options": [ + { + "id": "yes", + "text": "Share it." + }, + { + "id": "no", + "text": "Keep it local." + } + ], + "recommendation": "no", + "depends": [ + "gamecult-chat:question:file-zone-audience" + ], + "leans": null, + "flag": null, + "raised_in": { + "kind": "CutSpec", + "id": "cultlib-gaps:cut_spec:cut-slot-tolerance.r4" + }, + "deciders": [ + "e0d1c2b3-a495-4687-8879-6a5b4c3d2e1f" + ], + "status": "open", + "ruling": null, + "ruled_from": [], + "order": 3, + "options_hash": "7d95fc87d532420611fe415c1def27a3447617bb68235f46e3af130aea361c48" + }, + "optionsHash": "7d95fc87d532420611fe415c1def27a3447617bb68235f46e3af130aea361c48", + "steps": [ + { + "kind": "answer", + "body": { + "schemaVersion": "gamecult.fork.answer_request.v0", + "fork_id": "gamecult-chat:question:slot-mark-consent", + "options_hash": "7d95fc87d532420611fe415c1def27a3447617bb68235f46e3af130aea361c48", + "answer": { + "choice": null, + "note": "Keep it local: a slot mark is the author's call, and sharing it is a separate act." + }, + "retracted": false, + "retracts": null, + "answered_at": "2026-11-12T19:02:00Z", + "request_id": "00000006-0000-4000-8000-000000000006" + }, + "server": { + "answer_id": "n1", + "answerer": "e0d1c2b3-a495-4687-8879-6a5b4c3d2e1f", + "authority": "Operator", + "received_at": "2026-11-12T19:02:05Z" + }, + "response": { + "schemaVersion": "gamecult.fork.answer_result.v0", + "answer": { + "schemaVersion": "gamecult.desk_answer.v0", + "answer_id": "n1", + "fork_id": "gamecult-chat:question:slot-mark-consent", + "outcome": "Counted", + "of": null, + "retracts": null, + "code": null, + "options_hash": "7d95fc87d532420611fe415c1def27a3447617bb68235f46e3af130aea361c48", + "choice": null, + "note": "Keep it local: a slot mark is the author's call, and sharing it is a separate act.", + "answered_at": "2026-11-12T19:02:00Z", + "received_at": "2026-11-12T19:02:05Z", + "claim_in_future": false, + "answerer": "e0d1c2b3-a495-4687-8879-6a5b4c3d2e1f", + "authority": "Operator", + "request_id": "00000006-0000-4000-8000-000000000006" + }, + "card": null + } + } + ], + "record": { + "schemaVersion": "gamecult.fork.record.v0", + "fork_id": "gamecult-chat:question:slot-mark-consent", + "options_hash": "7d95fc87d532420611fe415c1def27a3447617bb68235f46e3af130aea361c48", + "counted_answer_id": "n1", + "answers": [ + { + "schemaVersion": "gamecult.desk_answer.v0", + "answer_id": "n1", + "fork_id": "gamecult-chat:question:slot-mark-consent", + "outcome": "Counted", + "of": null, + "retracts": null, + "code": null, + "options_hash": "7d95fc87d532420611fe415c1def27a3447617bb68235f46e3af130aea361c48", + "choice": null, + "note": "Keep it local: a slot mark is the author's call, and sharing it is a separate act.", + "answered_at": "2026-11-12T19:02:00Z", + "received_at": "2026-11-12T19:02:05Z", + "claim_in_future": false, + "answerer": "e0d1c2b3-a495-4687-8879-6a5b4c3d2e1f", + "authority": "Operator", + "request_id": "00000006-0000-4000-8000-000000000006" + } + ] + }, + "recordHex": "85AD736368656D6156657273696F6EB767616D6563756C742E666F726B2E7265636F72642E7630A7666F726B5F6964D92867616D6563756C742D636861743A7175657374696F6E3A736C6F742D6D61726B2D636F6E73656E74AC6F7074696F6E735F68617368D94037643935666338376435333234323036313166653431356331646566323761333434373631376262363832333566343665336166313330616561333631633438B1636F756E7465645F616E737765725F6964A26E31A7616E737765727391DE0010AD736368656D6156657273696F6EB767616D6563756C742E6465736B5F616E737765722E7630A9616E737765725F6964A26E31A7666F726B5F6964D92867616D6563756C742D636861743A7175657374696F6E3A736C6F742D6D61726B2D636F6E73656E74A76F7574636F6D65A7436F756E746564A26F66C0A87265747261637473C0A4636F6465C0AC6F7074696F6E735F68617368D94037643935666338376435333234323036313166653431356331646566323761333434373631376262363832333566343665336166313330616561333631633438A663686F696365C0A46E6F7465D9524B656570206974206C6F63616C3A206120736C6F74206D61726B2069732074686520617574686F7227732063616C6C2C20616E642073686172696E672069742069732061207365706172617465206163742EAB616E7377657265645F6174B4323032362D31312D31325431393A30323A30305AAB72656365697665645F6174B4323032362D31312D31325431393A30323A30355AAF636C61696D5F696E5F667574757265C2A8616E737765726572D92465306431633262332D613439352D343638372D383837392D366135623463336432653166A9617574686F72697479A84F70657261746F72AA726571756573745F6964D92430303030303030362D303030302D343030302D383030302D303030303030303030303036" + }, + { + "name": "V1_answerer_in_the_body_is_refused_at_the_door_and_stores_nothing", + "card": { + "schemaVersion": "gamecult.fork.card.v0", + "fork_id": "gamecult-chat:question:slot-mark-consent", + "title": "Slot mark consent", + "context": "Should a slot mark be shared?", + "options": [ + { + "id": "yes", + "text": "Share it." + }, + { + "id": "no", + "text": "Keep it local." + } + ], + "recommendation": "no", + "depends": [ + "gamecult-chat:question:file-zone-audience" + ], + "leans": null, + "flag": null, + "raised_in": { + "kind": "CutSpec", + "id": "cultlib-gaps:cut_spec:cut-slot-tolerance.r4" + }, + "deciders": [ + "e0d1c2b3-a495-4687-8879-6a5b4c3d2e1f" + ], + "status": "open", + "ruling": null, + "ruled_from": [], + "order": 3, + "options_hash": "7d95fc87d532420611fe415c1def27a3447617bb68235f46e3af130aea361c48" + }, + "optionsHash": "7d95fc87d532420611fe415c1def27a3447617bb68235f46e3af130aea361c48", + "steps": [ + { + "kind": "answer", + "body": { + "schemaVersion": "gamecult.fork.answer_request.v0", + "fork_id": "gamecult-chat:question:slot-mark-consent", + "options_hash": "7d95fc87d532420611fe415c1def27a3447617bb68235f46e3af130aea361c48", + "answer": { + "choice": "no", + "note": "Keep it local: a slot mark is the author's call, and sharing it is a separate act." + }, + "retracted": false, + "retracts": null, + "answered_at": "2026-11-12T19:02:00Z", + "request_id": "00000001-0000-4000-8000-000000000001", + "answerer": "x" + }, + "server": { + "answer_id": "n1", + "answerer": "e0d1c2b3-a495-4687-8879-6a5b4c3d2e1f", + "authority": "Operator", + "received_at": "2026-11-12T19:02:05Z" + }, + "response": { + "schemaVersion": "gamecult.fork.refusal.v0", + "code": "body_forbidden_field", + "error": "body_forbidden_field: answerer and authority are set by the receiver" + } + } + ], + "record": { + "schemaVersion": "gamecult.fork.record.v0", + "fork_id": "gamecult-chat:question:slot-mark-consent", + "options_hash": "7d95fc87d532420611fe415c1def27a3447617bb68235f46e3af130aea361c48", + "counted_answer_id": null, + "answers": [] + }, + "recordHex": "85AD736368656D6156657273696F6EB767616D6563756C742E666F726B2E7265636F72642E7630A7666F726B5F6964D92867616D6563756C742D636861743A7175657374696F6E3A736C6F742D6D61726B2D636F6E73656E74AC6F7074696F6E735F68617368D94037643935666338376435333234323036313166653431356331646566323761333434373631376262363832333566343665336166313330616561333631633438B1636F756E7465645F616E737765725F6964C0A7616E737765727390" + }, + { + "name": "V1_authority_nested_in_answer_is_refused", + "card": { + "schemaVersion": "gamecult.fork.card.v0", + "fork_id": "gamecult-chat:question:slot-mark-consent", + "title": "Slot mark consent", + "context": "Should a slot mark be shared?", + "options": [ + { + "id": "yes", + "text": "Share it." + }, + { + "id": "no", + "text": "Keep it local." + } + ], + "recommendation": "no", + "depends": [ + "gamecult-chat:question:file-zone-audience" + ], + "leans": null, + "flag": null, + "raised_in": { + "kind": "CutSpec", + "id": "cultlib-gaps:cut_spec:cut-slot-tolerance.r4" + }, + "deciders": [ + "e0d1c2b3-a495-4687-8879-6a5b4c3d2e1f" + ], + "status": "open", + "ruling": null, + "ruled_from": [], + "order": 3, + "options_hash": "7d95fc87d532420611fe415c1def27a3447617bb68235f46e3af130aea361c48" + }, + "optionsHash": "7d95fc87d532420611fe415c1def27a3447617bb68235f46e3af130aea361c48", + "steps": [ + { + "kind": "answer", + "body": { + "schemaVersion": "gamecult.fork.answer_request.v0", + "fork_id": "gamecult-chat:question:slot-mark-consent", + "options_hash": "7d95fc87d532420611fe415c1def27a3447617bb68235f46e3af130aea361c48", + "answer": { + "choice": "no", + "note": null, + "authority": "Operator" + }, + "retracted": false, + "retracts": null, + "answered_at": "2026-11-12T19:02:00Z", + "request_id": "00000001-0000-4000-8000-000000000001" + }, + "server": { + "answer_id": "n1", + "answerer": "e0d1c2b3-a495-4687-8879-6a5b4c3d2e1f", + "authority": "Operator", + "received_at": "2026-11-12T19:02:05Z" + }, + "response": { + "schemaVersion": "gamecult.fork.refusal.v0", + "code": "body_forbidden_field", + "error": "body_forbidden_field: answerer and authority are set by the receiver" + } + } + ], + "record": { + "schemaVersion": "gamecult.fork.record.v0", + "fork_id": "gamecult-chat:question:slot-mark-consent", + "options_hash": "7d95fc87d532420611fe415c1def27a3447617bb68235f46e3af130aea361c48", + "counted_answer_id": null, + "answers": [] + }, + "recordHex": "85AD736368656D6156657273696F6EB767616D6563756C742E666F726B2E7265636F72642E7630A7666F726B5F6964D92867616D6563756C742D636861743A7175657374696F6E3A736C6F742D6D61726B2D636F6E73656E74AC6F7074696F6E735F68617368D94037643935666338376435333234323036313166653431356331646566323761333434373631376262363832333566343665336166313330616561333631633438B1636F756E7465645F616E737765725F6964C0A7616E737765727390" + }, + { + "name": "V1_both_are_refused_not_stripped", + "card": { + "schemaVersion": "gamecult.fork.card.v0", + "fork_id": "gamecult-chat:question:slot-mark-consent", + "title": "Slot mark consent", + "context": "Should a slot mark be shared?", + "options": [ + { + "id": "yes", + "text": "Share it." + }, + { + "id": "no", + "text": "Keep it local." + } + ], + "recommendation": "no", + "depends": [ + "gamecult-chat:question:file-zone-audience" + ], + "leans": null, + "flag": null, + "raised_in": { + "kind": "CutSpec", + "id": "cultlib-gaps:cut_spec:cut-slot-tolerance.r4" + }, + "deciders": [ + "e0d1c2b3-a495-4687-8879-6a5b4c3d2e1f" + ], + "status": "open", + "ruling": null, + "ruled_from": [], + "order": 3, + "options_hash": "7d95fc87d532420611fe415c1def27a3447617bb68235f46e3af130aea361c48" + }, + "optionsHash": "7d95fc87d532420611fe415c1def27a3447617bb68235f46e3af130aea361c48", + "steps": [ + { + "kind": "answer", + "body": { + "schemaVersion": "gamecult.fork.answer_request.v0", + "fork_id": "gamecult-chat:question:slot-mark-consent", + "options_hash": "7d95fc87d532420611fe415c1def27a3447617bb68235f46e3af130aea361c48", + "answer": { + "choice": "no", + "note": "Keep it local: a slot mark is the author's call, and sharing it is a separate act." + }, + "retracted": false, + "retracts": null, + "answered_at": "2026-11-12T19:02:00Z", + "request_id": "00000001-0000-4000-8000-000000000001", + "answerer": "e0d1c2b3-a495-4687-8879-6a5b4c3d2e1f", + "authority": "Operator" + }, + "server": { + "answer_id": "n1", + "answerer": "e0d1c2b3-a495-4687-8879-6a5b4c3d2e1f", + "authority": "Operator", + "received_at": "2026-11-12T19:02:05Z" + }, + "response": { + "schemaVersion": "gamecult.fork.refusal.v0", + "code": "body_forbidden_field", + "error": "body_forbidden_field: answerer and authority are set by the receiver" + } + } + ], + "record": { + "schemaVersion": "gamecult.fork.record.v0", + "fork_id": "gamecult-chat:question:slot-mark-consent", + "options_hash": "7d95fc87d532420611fe415c1def27a3447617bb68235f46e3af130aea361c48", + "counted_answer_id": null, + "answers": [] + }, + "recordHex": "85AD736368656D6156657273696F6EB767616D6563756C742E666F726B2E7265636F72642E7630A7666F726B5F6964D92867616D6563756C742D636861743A7175657374696F6E3A736C6F742D6D61726B2D636F6E73656E74AC6F7074696F6E735F68617368D94037643935666338376435333234323036313166653431356331646566323761333434373631376262363832333566343665336166313330616561333631633438B1636F756E7465645F616E737765725F6964C0A7616E737765727390" + }, + { + "name": "M5_a_body_naming_deciders_is_an_unknown_key_the_decider_list_is_never_the_bodys", + "card": { + "schemaVersion": "gamecult.fork.card.v0", + "fork_id": "gamecult-chat:question:slot-mark-consent", + "title": "Slot mark consent", + "context": "Should a slot mark be shared?", + "options": [ + { + "id": "yes", + "text": "Share it." + }, + { + "id": "no", + "text": "Keep it local." + } + ], + "recommendation": "no", + "depends": [ + "gamecult-chat:question:file-zone-audience" + ], + "leans": null, + "flag": null, + "raised_in": { + "kind": "CutSpec", + "id": "cultlib-gaps:cut_spec:cut-slot-tolerance.r4" + }, + "deciders": [ + "e0d1c2b3-a495-4687-8879-6a5b4c3d2e1f" + ], + "status": "open", + "ruling": null, + "ruled_from": [], + "order": 3, + "options_hash": "7d95fc87d532420611fe415c1def27a3447617bb68235f46e3af130aea361c48" + }, + "optionsHash": "7d95fc87d532420611fe415c1def27a3447617bb68235f46e3af130aea361c48", + "steps": [ + { + "kind": "answer", + "body": { + "schemaVersion": "gamecult.fork.answer_request.v0", + "fork_id": "gamecult-chat:question:slot-mark-consent", + "options_hash": "7d95fc87d532420611fe415c1def27a3447617bb68235f46e3af130aea361c48", + "answer": { + "choice": "no", + "note": "Keep it local: a slot mark is the author's call, and sharing it is a separate act." + }, + "retracted": false, + "retracts": null, + "answered_at": "2026-11-12T19:02:00Z", + "request_id": "00000001-0000-4000-8000-000000000001", + "deciders": [ + "11111111-2222-4333-8444-555555555555" + ] + }, + "server": { + "answer_id": "n1", + "answerer": "e0d1c2b3-a495-4687-8879-6a5b4c3d2e1f", + "authority": "Operator", + "received_at": "2026-11-12T19:02:05Z" + }, + "response": { + "schemaVersion": "gamecult.fork.refusal.v0", + "code": "body_unknown_field", + "error": "body_unknown_field: the body is a closed shape" + } + } + ], + "record": { + "schemaVersion": "gamecult.fork.record.v0", + "fork_id": "gamecult-chat:question:slot-mark-consent", + "options_hash": "7d95fc87d532420611fe415c1def27a3447617bb68235f46e3af130aea361c48", + "counted_answer_id": null, + "answers": [] + }, + "recordHex": "85AD736368656D6156657273696F6EB767616D6563756C742E666F726B2E7265636F72642E7630A7666F726B5F6964D92867616D6563756C742D636861743A7175657374696F6E3A736C6F742D6D61726B2D636F6E73656E74AC6F7074696F6E735F68617368D94037643935666338376435333234323036313166653431356331646566323761333434373631376262363832333566343665336166313330616561333631633438B1636F756E7465645F616E737765725F6964C0A7616E737765727390" + }, + { + "name": "M1_a_body_naming_a_mind_is_an_unknown_key_the_mind_is_the_routes", + "card": { + "schemaVersion": "gamecult.fork.card.v0", + "fork_id": "gamecult-chat:question:slot-mark-consent", + "title": "Slot mark consent", + "context": "Should a slot mark be shared?", + "options": [ + { + "id": "yes", + "text": "Share it." + }, + { + "id": "no", + "text": "Keep it local." + } + ], + "recommendation": "no", + "depends": [ + "gamecult-chat:question:file-zone-audience" + ], + "leans": null, + "flag": null, + "raised_in": { + "kind": "CutSpec", + "id": "cultlib-gaps:cut_spec:cut-slot-tolerance.r4" + }, + "deciders": [ + "e0d1c2b3-a495-4687-8879-6a5b4c3d2e1f" + ], + "status": "open", + "ruling": null, + "ruled_from": [], + "order": 3, + "options_hash": "7d95fc87d532420611fe415c1def27a3447617bb68235f46e3af130aea361c48" + }, + "optionsHash": "7d95fc87d532420611fe415c1def27a3447617bb68235f46e3af130aea361c48", + "steps": [ + { + "kind": "answer", + "body": { + "schemaVersion": "gamecult.fork.answer_request.v0", + "fork_id": "gamecult-chat:question:slot-mark-consent", + "options_hash": "7d95fc87d532420611fe415c1def27a3447617bb68235f46e3af130aea361c48", + "answer": { + "choice": "no", + "note": "Keep it local: a slot mark is the author's call, and sharing it is a separate act." + }, + "retracted": false, + "retracts": null, + "answered_at": "2026-11-12T19:02:00Z", + "request_id": "00000001-0000-4000-8000-000000000001", + "mind": "nibu" + }, + "server": { + "answer_id": "n1", + "answerer": "e0d1c2b3-a495-4687-8879-6a5b4c3d2e1f", + "authority": "Operator", + "received_at": "2026-11-12T19:02:05Z" + }, + "response": { + "schemaVersion": "gamecult.fork.refusal.v0", + "code": "body_unknown_field", + "error": "body_unknown_field: the body is a closed shape" + } + } + ], + "record": { + "schemaVersion": "gamecult.fork.record.v0", + "fork_id": "gamecult-chat:question:slot-mark-consent", + "options_hash": "7d95fc87d532420611fe415c1def27a3447617bb68235f46e3af130aea361c48", + "counted_answer_id": null, + "answers": [] + }, + "recordHex": "85AD736368656D6156657273696F6EB767616D6563756C742E666F726B2E7265636F72642E7630A7666F726B5F6964D92867616D6563756C742D636861743A7175657374696F6E3A736C6F742D6D61726B2D636F6E73656E74AC6F7074696F6E735F68617368D94037643935666338376435333234323036313166653431356331646566323761333434373631376262363832333566343665336166313330616561333631633438B1636F756E7465645F616E737765725F6964C0A7616E737765727390" + }, + { + "name": "an_unknown_key_is_refused_the_body_is_closed", + "card": { + "schemaVersion": "gamecult.fork.card.v0", + "fork_id": "gamecult-chat:question:slot-mark-consent", + "title": "Slot mark consent", + "context": "Should a slot mark be shared?", + "options": [ + { + "id": "yes", + "text": "Share it." + }, + { + "id": "no", + "text": "Keep it local." + } + ], + "recommendation": "no", + "depends": [ + "gamecult-chat:question:file-zone-audience" + ], + "leans": null, + "flag": null, + "raised_in": { + "kind": "CutSpec", + "id": "cultlib-gaps:cut_spec:cut-slot-tolerance.r4" + }, + "deciders": [ + "e0d1c2b3-a495-4687-8879-6a5b4c3d2e1f" + ], + "status": "open", + "ruling": null, + "ruled_from": [], + "order": 3, + "options_hash": "7d95fc87d532420611fe415c1def27a3447617bb68235f46e3af130aea361c48" + }, + "optionsHash": "7d95fc87d532420611fe415c1def27a3447617bb68235f46e3af130aea361c48", + "steps": [ + { + "kind": "answer", + "body": { + "schemaVersion": "gamecult.fork.answer_request.v0", + "fork_id": "gamecult-chat:question:slot-mark-consent", + "options_hash": "7d95fc87d532420611fe415c1def27a3447617bb68235f46e3af130aea361c48", + "answer": { + "choice": "no", + "note": "Keep it local: a slot mark is the author's call, and sharing it is a separate act." + }, + "retracted": false, + "retracts": null, + "answered_at": "2026-11-12T19:02:00Z", + "request_id": "00000001-0000-4000-8000-000000000001", + "mood": "happy" + }, + "server": { + "answer_id": "n1", + "answerer": "e0d1c2b3-a495-4687-8879-6a5b4c3d2e1f", + "authority": "Operator", + "received_at": "2026-11-12T19:02:05Z" + }, + "response": { + "schemaVersion": "gamecult.fork.refusal.v0", + "code": "body_unknown_field", + "error": "body_unknown_field: the body is a closed shape" + } + } + ], + "record": { + "schemaVersion": "gamecult.fork.record.v0", + "fork_id": "gamecult-chat:question:slot-mark-consent", + "options_hash": "7d95fc87d532420611fe415c1def27a3447617bb68235f46e3af130aea361c48", + "counted_answer_id": null, + "answers": [] + }, + "recordHex": "85AD736368656D6156657273696F6EB767616D6563756C742E666F726B2E7265636F72642E7630A7666F726B5F6964D92867616D6563756C742D636861743A7175657374696F6E3A736C6F742D6D61726B2D636F6E73656E74AC6F7074696F6E735F68617368D94037643935666338376435333234323036313166653431356331646566323761333434373631376262363832333566343665336166313330616561333631633438B1636F756E7465645F616E737765725F6964C0A7616E737765727390" + }, + { + "name": "an_answer_with_neither_choice_nor_note_is_invalid", + "card": { + "schemaVersion": "gamecult.fork.card.v0", + "fork_id": "gamecult-chat:question:slot-mark-consent", + "title": "Slot mark consent", + "context": "Should a slot mark be shared?", + "options": [ + { + "id": "yes", + "text": "Share it." + }, + { + "id": "no", + "text": "Keep it local." + } + ], + "recommendation": "no", + "depends": [ + "gamecult-chat:question:file-zone-audience" + ], + "leans": null, + "flag": null, + "raised_in": { + "kind": "CutSpec", + "id": "cultlib-gaps:cut_spec:cut-slot-tolerance.r4" + }, + "deciders": [ + "e0d1c2b3-a495-4687-8879-6a5b4c3d2e1f" + ], + "status": "open", + "ruling": null, + "ruled_from": [], + "order": 3, + "options_hash": "7d95fc87d532420611fe415c1def27a3447617bb68235f46e3af130aea361c48" + }, + "optionsHash": "7d95fc87d532420611fe415c1def27a3447617bb68235f46e3af130aea361c48", + "steps": [ + { + "kind": "answer", + "body": { + "schemaVersion": "gamecult.fork.answer_request.v0", + "fork_id": "gamecult-chat:question:slot-mark-consent", + "options_hash": "7d95fc87d532420611fe415c1def27a3447617bb68235f46e3af130aea361c48", + "answer": { + "choice": null, + "note": null + }, + "retracted": false, + "retracts": null, + "answered_at": "2026-11-12T19:02:00Z", + "request_id": "00000008-0000-4000-8000-000000000008" + }, + "server": { + "answer_id": "n1", + "answerer": "e0d1c2b3-a495-4687-8879-6a5b4c3d2e1f", + "authority": "Operator", + "received_at": "2026-11-12T19:02:05Z" + }, + "response": { + "schemaVersion": "gamecult.fork.refusal.v0", + "code": "body_invalid", + "error": "body_invalid: the body does not satisfy gamecult.fork.answer_request.v0" + } + } + ], + "record": { + "schemaVersion": "gamecult.fork.record.v0", + "fork_id": "gamecult-chat:question:slot-mark-consent", + "options_hash": "7d95fc87d532420611fe415c1def27a3447617bb68235f46e3af130aea361c48", + "counted_answer_id": null, + "answers": [] + }, + "recordHex": "85AD736368656D6156657273696F6EB767616D6563756C742E666F726B2E7265636F72642E7630A7666F726B5F6964D92867616D6563756C742D636861743A7175657374696F6E3A736C6F742D6D61726B2D636F6E73656E74AC6F7074696F6E735F68617368D94037643935666338376435333234323036313166653431356331646566323761333434373631376262363832333566343665336166313330616561333631633438B1636F756E7465645F616E737765725F6964C0A7616E737765727390" + }, + { + "name": "a_non_retraction_without_an_answer_is_invalid", + "card": { + "schemaVersion": "gamecult.fork.card.v0", + "fork_id": "gamecult-chat:question:slot-mark-consent", + "title": "Slot mark consent", + "context": "Should a slot mark be shared?", + "options": [ + { + "id": "yes", + "text": "Share it." + }, + { + "id": "no", + "text": "Keep it local." + } + ], + "recommendation": "no", + "depends": [ + "gamecult-chat:question:file-zone-audience" + ], + "leans": null, + "flag": null, + "raised_in": { + "kind": "CutSpec", + "id": "cultlib-gaps:cut_spec:cut-slot-tolerance.r4" + }, + "deciders": [ + "e0d1c2b3-a495-4687-8879-6a5b4c3d2e1f" + ], + "status": "open", + "ruling": null, + "ruled_from": [], + "order": 3, + "options_hash": "7d95fc87d532420611fe415c1def27a3447617bb68235f46e3af130aea361c48" + }, + "optionsHash": "7d95fc87d532420611fe415c1def27a3447617bb68235f46e3af130aea361c48", + "steps": [ + { + "kind": "answer", + "body": { + "schemaVersion": "gamecult.fork.answer_request.v0", + "fork_id": "gamecult-chat:question:slot-mark-consent", + "options_hash": "7d95fc87d532420611fe415c1def27a3447617bb68235f46e3af130aea361c48", + "answer": null, + "retracted": false, + "retracts": null, + "answered_at": "2026-11-12T19:02:00Z", + "request_id": "00000009-0000-4000-8000-000000000009" + }, + "server": { + "answer_id": "n1", + "answerer": "e0d1c2b3-a495-4687-8879-6a5b4c3d2e1f", + "authority": "Operator", + "received_at": "2026-11-12T19:02:05Z" + }, + "response": { + "schemaVersion": "gamecult.fork.refusal.v0", + "code": "body_invalid", + "error": "body_invalid: the body does not satisfy gamecult.fork.answer_request.v0" + } + } + ], + "record": { + "schemaVersion": "gamecult.fork.record.v0", + "fork_id": "gamecult-chat:question:slot-mark-consent", + "options_hash": "7d95fc87d532420611fe415c1def27a3447617bb68235f46e3af130aea361c48", + "counted_answer_id": null, + "answers": [] + }, + "recordHex": "85AD736368656D6156657273696F6EB767616D6563756C742E666F726B2E7265636F72642E7630A7666F726B5F6964D92867616D6563756C742D636861743A7175657374696F6E3A736C6F742D6D61726B2D636F6E73656E74AC6F7074696F6E735F68617368D94037643935666338376435333234323036313166653431356331646566323761333434373631376262363832333566343665336166313330616561333631633438B1636F756E7465645F616E737765725F6964C0A7616E737765727390" + }, + { + "name": "unavailable_before_huginn_stored_nothing_is_retried_into_a_Counted_answer", + "card": { + "schemaVersion": "gamecult.fork.card.v0", + "fork_id": "gamecult-chat:question:slot-mark-consent", + "title": "Slot mark consent", + "context": "Should a slot mark be shared?", + "options": [ + { + "id": "yes", + "text": "Share it." + }, + { + "id": "no", + "text": "Keep it local." + } + ], + "recommendation": "no", + "depends": [ + "gamecult-chat:question:file-zone-audience" + ], + "leans": null, + "flag": null, + "raised_in": { + "kind": "CutSpec", + "id": "cultlib-gaps:cut_spec:cut-slot-tolerance.r4" + }, + "deciders": [ + "e0d1c2b3-a495-4687-8879-6a5b4c3d2e1f" + ], + "status": "open", + "ruling": null, + "ruled_from": [], + "order": 3, + "options_hash": "7d95fc87d532420611fe415c1def27a3447617bb68235f46e3af130aea361c48" + }, + "optionsHash": "7d95fc87d532420611fe415c1def27a3447617bb68235f46e3af130aea361c48", + "steps": [ + { + "kind": "answer", + "body": { + "schemaVersion": "gamecult.fork.answer_request.v0", + "fork_id": "gamecult-chat:question:slot-mark-consent", + "options_hash": "7d95fc87d532420611fe415c1def27a3447617bb68235f46e3af130aea361c48", + "answer": { + "choice": "no", + "note": "Keep it local: a slot mark is the author's call, and sharing it is a separate act." + }, + "retracted": false, + "retracts": null, + "answered_at": "2026-11-12T19:02:00Z", + "request_id": "00000001-0000-4000-8000-000000000001" + }, + "server": { + "answer_id": "n9", + "answerer": "e0d1c2b3-a495-4687-8879-6a5b4c3d2e1f", + "authority": "Operator", + "received_at": "2026-11-12T19:02:05Z", + "door_down": true, + "retry_after_seconds": 30 + }, + "response": { + "schemaVersion": "gamecult.fork.unavailable.v0", + "code": "unavailable", + "error": "unavailable: the outcome is not confirmed, try again with the same request_id", + "retryAfterSeconds": 30 + } + }, + { + "kind": "answer", + "body": { + "schemaVersion": "gamecult.fork.answer_request.v0", + "fork_id": "gamecult-chat:question:slot-mark-consent", + "options_hash": "7d95fc87d532420611fe415c1def27a3447617bb68235f46e3af130aea361c48", + "answer": { + "choice": "no", + "note": "Keep it local: a slot mark is the author's call, and sharing it is a separate act." + }, + "retracted": false, + "retracts": null, + "answered_at": "2026-11-12T19:02:00Z", + "request_id": "00000001-0000-4000-8000-000000000001" + }, + "server": { + "answer_id": "n1", + "answerer": "e0d1c2b3-a495-4687-8879-6a5b4c3d2e1f", + "authority": "Operator", + "received_at": "2026-11-12T19:02:05Z" + }, + "response": { + "schemaVersion": "gamecult.fork.answer_result.v0", + "answer": { + "schemaVersion": "gamecult.desk_answer.v0", + "answer_id": "n1", + "fork_id": "gamecult-chat:question:slot-mark-consent", + "outcome": "Counted", + "of": null, + "retracts": null, + "code": null, + "options_hash": "7d95fc87d532420611fe415c1def27a3447617bb68235f46e3af130aea361c48", + "choice": "no", + "note": "Keep it local: a slot mark is the author's call, and sharing it is a separate act.", + "answered_at": "2026-11-12T19:02:00Z", + "received_at": "2026-11-12T19:02:05Z", + "claim_in_future": false, + "answerer": "e0d1c2b3-a495-4687-8879-6a5b4c3d2e1f", + "authority": "Operator", + "request_id": "00000001-0000-4000-8000-000000000001" + }, + "card": null + } + } + ], + "record": { + "schemaVersion": "gamecult.fork.record.v0", + "fork_id": "gamecult-chat:question:slot-mark-consent", + "options_hash": "7d95fc87d532420611fe415c1def27a3447617bb68235f46e3af130aea361c48", + "counted_answer_id": "n1", + "answers": [ + { + "schemaVersion": "gamecult.desk_answer.v0", + "answer_id": "n1", + "fork_id": "gamecult-chat:question:slot-mark-consent", + "outcome": "Counted", + "of": null, + "retracts": null, + "code": null, + "options_hash": "7d95fc87d532420611fe415c1def27a3447617bb68235f46e3af130aea361c48", + "choice": "no", + "note": "Keep it local: a slot mark is the author's call, and sharing it is a separate act.", + "answered_at": "2026-11-12T19:02:00Z", + "received_at": "2026-11-12T19:02:05Z", + "claim_in_future": false, + "answerer": "e0d1c2b3-a495-4687-8879-6a5b4c3d2e1f", + "authority": "Operator", + "request_id": "00000001-0000-4000-8000-000000000001" + } + ] + }, + "recordHex": "85AD736368656D6156657273696F6EB767616D6563756C742E666F726B2E7265636F72642E7630A7666F726B5F6964D92867616D6563756C742D636861743A7175657374696F6E3A736C6F742D6D61726B2D636F6E73656E74AC6F7074696F6E735F68617368D94037643935666338376435333234323036313166653431356331646566323761333434373631376262363832333566343665336166313330616561333631633438B1636F756E7465645F616E737765725F6964A26E31A7616E737765727391DE0010AD736368656D6156657273696F6EB767616D6563756C742E6465736B5F616E737765722E7630A9616E737765725F6964A26E31A7666F726B5F6964D92867616D6563756C742D636861743A7175657374696F6E3A736C6F742D6D61726B2D636F6E73656E74A76F7574636F6D65A7436F756E746564A26F66C0A87265747261637473C0A4636F6465C0AC6F7074696F6E735F68617368D94037643935666338376435333234323036313166653431356331646566323761333434373631376262363832333566343665336166313330616561333631633438A663686F696365A26E6FA46E6F7465D9524B656570206974206C6F63616C3A206120736C6F74206D61726B2069732074686520617574686F7227732063616C6C2C20616E642073686172696E672069742069732061207365706172617465206163742EAB616E7377657265645F6174B4323032362D31312D31325431393A30323A30305AAB72656365697665645F6174B4323032362D31312D31325431393A30323A30355AAF636C61696D5F696E5F667574757265C2A8616E737765726572D92465306431633262332D613439352D343638372D383837392D366135623463336432653166A9617574686F72697479A84F70657261746F72AA726571756573745F6964D92430303030303030312D303030302D343030302D383030302D303030303030303030303031" + }, + { + "name": "unavailable_after_huginn_stored_the_retry_with_the_same_request_id_returns_the_stored_outcome", + "card": { + "schemaVersion": "gamecult.fork.card.v0", + "fork_id": "gamecult-chat:question:slot-mark-consent", + "title": "Slot mark consent", + "context": "Should a slot mark be shared?", + "options": [ + { + "id": "yes", + "text": "Share it." + }, + { + "id": "no", + "text": "Keep it local." + } + ], + "recommendation": "no", + "depends": [ + "gamecult-chat:question:file-zone-audience" + ], + "leans": null, + "flag": null, + "raised_in": { + "kind": "CutSpec", + "id": "cultlib-gaps:cut_spec:cut-slot-tolerance.r4" + }, + "deciders": [ + "e0d1c2b3-a495-4687-8879-6a5b4c3d2e1f" + ], + "status": "open", + "ruling": null, + "ruled_from": [], + "order": 3, + "options_hash": "7d95fc87d532420611fe415c1def27a3447617bb68235f46e3af130aea361c48" + }, + "optionsHash": "7d95fc87d532420611fe415c1def27a3447617bb68235f46e3af130aea361c48", + "steps": [ + { + "kind": "answer", + "body": { + "schemaVersion": "gamecult.fork.answer_request.v0", + "fork_id": "gamecult-chat:question:slot-mark-consent", + "options_hash": "7d95fc87d532420611fe415c1def27a3447617bb68235f46e3af130aea361c48", + "answer": { + "choice": "no", + "note": "Keep it local: a slot mark is the author's call, and sharing it is a separate act." + }, + "retracted": false, + "retracts": null, + "answered_at": "2026-11-12T19:02:00Z", + "request_id": "00000001-0000-4000-8000-000000000001" + }, + "server": { + "answer_id": "n1", + "answerer": "e0d1c2b3-a495-4687-8879-6a5b4c3d2e1f", + "authority": "Operator", + "received_at": "2026-11-12T19:02:05Z", + "door_down": true, + "stored": true, + "retry_after_seconds": 30 + }, + "response": { + "schemaVersion": "gamecult.fork.unavailable.v0", + "code": "unavailable", + "error": "unavailable: the outcome is not confirmed, try again with the same request_id", + "retryAfterSeconds": 30 + } + }, + { + "kind": "answer", + "body": { + "schemaVersion": "gamecult.fork.answer_request.v0", + "fork_id": "gamecult-chat:question:slot-mark-consent", + "options_hash": "7d95fc87d532420611fe415c1def27a3447617bb68235f46e3af130aea361c48", + "answer": { + "choice": "no", + "note": "Keep it local: a slot mark is the author's call, and sharing it is a separate act." + }, + "retracted": false, + "retracts": null, + "answered_at": "2026-11-12T19:02:00Z", + "request_id": "00000001-0000-4000-8000-000000000001" + }, + "server": { + "answer_id": "n9", + "answerer": "e0d1c2b3-a495-4687-8879-6a5b4c3d2e1f", + "authority": "Operator", + "received_at": "2026-11-12T19:02:05Z" + }, + "response": { + "schemaVersion": "gamecult.fork.answer_result.v0", + "answer": { + "schemaVersion": "gamecult.desk_answer.v0", + "answer_id": "n1", + "fork_id": "gamecult-chat:question:slot-mark-consent", + "outcome": "Counted", + "of": null, + "retracts": null, + "code": null, + "options_hash": "7d95fc87d532420611fe415c1def27a3447617bb68235f46e3af130aea361c48", + "choice": "no", + "note": "Keep it local: a slot mark is the author's call, and sharing it is a separate act.", + "answered_at": "2026-11-12T19:02:00Z", + "received_at": "2026-11-12T19:02:05Z", + "claim_in_future": false, + "answerer": "e0d1c2b3-a495-4687-8879-6a5b4c3d2e1f", + "authority": "Operator", + "request_id": "00000001-0000-4000-8000-000000000001" + }, + "card": null + } + } + ], + "record": { + "schemaVersion": "gamecult.fork.record.v0", + "fork_id": "gamecult-chat:question:slot-mark-consent", + "options_hash": "7d95fc87d532420611fe415c1def27a3447617bb68235f46e3af130aea361c48", + "counted_answer_id": "n1", + "answers": [ + { + "schemaVersion": "gamecult.desk_answer.v0", + "answer_id": "n1", + "fork_id": "gamecult-chat:question:slot-mark-consent", + "outcome": "Counted", + "of": null, + "retracts": null, + "code": null, + "options_hash": "7d95fc87d532420611fe415c1def27a3447617bb68235f46e3af130aea361c48", + "choice": "no", + "note": "Keep it local: a slot mark is the author's call, and sharing it is a separate act.", + "answered_at": "2026-11-12T19:02:00Z", + "received_at": "2026-11-12T19:02:05Z", + "claim_in_future": false, + "answerer": "e0d1c2b3-a495-4687-8879-6a5b4c3d2e1f", + "authority": "Operator", + "request_id": "00000001-0000-4000-8000-000000000001" + } + ] + }, + "recordHex": "85AD736368656D6156657273696F6EB767616D6563756C742E666F726B2E7265636F72642E7630A7666F726B5F6964D92867616D6563756C742D636861743A7175657374696F6E3A736C6F742D6D61726B2D636F6E73656E74AC6F7074696F6E735F68617368D94037643935666338376435333234323036313166653431356331646566323761333434373631376262363832333566343665336166313330616561333631633438B1636F756E7465645F616E737765725F6964A26E31A7616E737765727391DE0010AD736368656D6156657273696F6EB767616D6563756C742E6465736B5F616E737765722E7630A9616E737765725F6964A26E31A7666F726B5F6964D92867616D6563756C742D636861743A7175657374696F6E3A736C6F742D6D61726B2D636F6E73656E74A76F7574636F6D65A7436F756E746564A26F66C0A87265747261637473C0A4636F6465C0AC6F7074696F6E735F68617368D94037643935666338376435333234323036313166653431356331646566323761333434373631376262363832333566343665336166313330616561333631633438A663686F696365A26E6FA46E6F7465D9524B656570206974206C6F63616C3A206120736C6F74206D61726B2069732074686520617574686F7227732063616C6C2C20616E642073686172696E672069742069732061207365706172617465206163742EAB616E7377657265645F6174B4323032362D31312D31325431393A30323A30305AAB72656365697665645F6174B4323032362D31312D31325431393A30323A30355AAF636C61696D5F696E5F667574757265C2A8616E737765726572D92465306431633262332D613439352D343638372D383837392D366135623463336432653166A9617574686F72697479A84F70657261746F72AA726571756573745F6964D92430303030303030312D303030302D343030302D383030302D303030303030303030303031" + }, + { + "name": "a_retry_never_adds_a_Revision", + "card": { + "schemaVersion": "gamecult.fork.card.v0", + "fork_id": "gamecult-chat:question:slot-mark-consent", + "title": "Slot mark consent", + "context": "Should a slot mark be shared?", + "options": [ + { + "id": "yes", + "text": "Share it." + }, + { + "id": "no", + "text": "Keep it local." + } + ], + "recommendation": "no", + "depends": [ + "gamecult-chat:question:file-zone-audience" + ], + "leans": null, + "flag": null, + "raised_in": { + "kind": "CutSpec", + "id": "cultlib-gaps:cut_spec:cut-slot-tolerance.r4" + }, + "deciders": [ + "e0d1c2b3-a495-4687-8879-6a5b4c3d2e1f" + ], + "status": "open", + "ruling": null, + "ruled_from": [], + "order": 3, + "options_hash": "7d95fc87d532420611fe415c1def27a3447617bb68235f46e3af130aea361c48" + }, + "optionsHash": "7d95fc87d532420611fe415c1def27a3447617bb68235f46e3af130aea361c48", + "steps": [ + { + "kind": "answer", + "body": { + "schemaVersion": "gamecult.fork.answer_request.v0", + "fork_id": "gamecult-chat:question:slot-mark-consent", + "options_hash": "7d95fc87d532420611fe415c1def27a3447617bb68235f46e3af130aea361c48", + "answer": { + "choice": "no", + "note": "Keep it local: a slot mark is the author's call, and sharing it is a separate act." + }, + "retracted": false, + "retracts": null, + "answered_at": "2026-11-12T19:02:00Z", + "request_id": "00000001-0000-4000-8000-000000000001" + }, + "server": { + "answer_id": "n1", + "answerer": "e0d1c2b3-a495-4687-8879-6a5b4c3d2e1f", + "authority": "Operator", + "received_at": "2026-11-12T19:02:05Z" + }, + "response": { + "schemaVersion": "gamecult.fork.answer_result.v0", + "answer": { + "schemaVersion": "gamecult.desk_answer.v0", + "answer_id": "n1", + "fork_id": "gamecult-chat:question:slot-mark-consent", + "outcome": "Counted", + "of": null, + "retracts": null, + "code": null, + "options_hash": "7d95fc87d532420611fe415c1def27a3447617bb68235f46e3af130aea361c48", + "choice": "no", + "note": "Keep it local: a slot mark is the author's call, and sharing it is a separate act.", + "answered_at": "2026-11-12T19:02:00Z", + "received_at": "2026-11-12T19:02:05Z", + "claim_in_future": false, + "answerer": "e0d1c2b3-a495-4687-8879-6a5b4c3d2e1f", + "authority": "Operator", + "request_id": "00000001-0000-4000-8000-000000000001" + }, + "card": null + } + }, + { + "kind": "answer", + "body": { + "schemaVersion": "gamecult.fork.answer_request.v0", + "fork_id": "gamecult-chat:question:slot-mark-consent", + "options_hash": "7d95fc87d532420611fe415c1def27a3447617bb68235f46e3af130aea361c48", + "answer": { + "choice": "no", + "note": "Keep it local: a slot mark is the author's call, and sharing it is a separate act." + }, + "retracted": false, + "retracts": null, + "answered_at": "2026-11-12T19:02:00Z", + "request_id": "00000001-0000-4000-8000-000000000001" + }, + "server": { + "answer_id": "n9", + "answerer": "e0d1c2b3-a495-4687-8879-6a5b4c3d2e1f", + "authority": "Operator", + "received_at": "2026-11-12T19:02:05Z" + }, + "response": { + "schemaVersion": "gamecult.fork.answer_result.v0", + "answer": { + "schemaVersion": "gamecult.desk_answer.v0", + "answer_id": "n1", + "fork_id": "gamecult-chat:question:slot-mark-consent", + "outcome": "Counted", + "of": null, + "retracts": null, + "code": null, + "options_hash": "7d95fc87d532420611fe415c1def27a3447617bb68235f46e3af130aea361c48", + "choice": "no", + "note": "Keep it local: a slot mark is the author's call, and sharing it is a separate act.", + "answered_at": "2026-11-12T19:02:00Z", + "received_at": "2026-11-12T19:02:05Z", + "claim_in_future": false, + "answerer": "e0d1c2b3-a495-4687-8879-6a5b4c3d2e1f", + "authority": "Operator", + "request_id": "00000001-0000-4000-8000-000000000001" + }, + "card": null + } + } + ], + "record": { + "schemaVersion": "gamecult.fork.record.v0", + "fork_id": "gamecult-chat:question:slot-mark-consent", + "options_hash": "7d95fc87d532420611fe415c1def27a3447617bb68235f46e3af130aea361c48", + "counted_answer_id": "n1", + "answers": [ + { + "schemaVersion": "gamecult.desk_answer.v0", + "answer_id": "n1", + "fork_id": "gamecult-chat:question:slot-mark-consent", + "outcome": "Counted", + "of": null, + "retracts": null, + "code": null, + "options_hash": "7d95fc87d532420611fe415c1def27a3447617bb68235f46e3af130aea361c48", + "choice": "no", + "note": "Keep it local: a slot mark is the author's call, and sharing it is a separate act.", + "answered_at": "2026-11-12T19:02:00Z", + "received_at": "2026-11-12T19:02:05Z", + "claim_in_future": false, + "answerer": "e0d1c2b3-a495-4687-8879-6a5b4c3d2e1f", + "authority": "Operator", + "request_id": "00000001-0000-4000-8000-000000000001" + } + ] + }, + "recordHex": "85AD736368656D6156657273696F6EB767616D6563756C742E666F726B2E7265636F72642E7630A7666F726B5F6964D92867616D6563756C742D636861743A7175657374696F6E3A736C6F742D6D61726B2D636F6E73656E74AC6F7074696F6E735F68617368D94037643935666338376435333234323036313166653431356331646566323761333434373631376262363832333566343665336166313330616561333631633438B1636F756E7465645F616E737765725F6964A26E31A7616E737765727391DE0010AD736368656D6156657273696F6EB767616D6563756C742E6465736B5F616E737765722E7630A9616E737765725F6964A26E31A7666F726B5F6964D92867616D6563756C742D636861743A7175657374696F6E3A736C6F742D6D61726B2D636F6E73656E74A76F7574636F6D65A7436F756E746564A26F66C0A87265747261637473C0A4636F6465C0AC6F7074696F6E735F68617368D94037643935666338376435333234323036313166653431356331646566323761333434373631376262363832333566343665336166313330616561333631633438A663686F696365A26E6FA46E6F7465D9524B656570206974206C6F63616C3A206120736C6F74206D61726B2069732074686520617574686F7227732063616C6C2C20616E642073686172696E672069742069732061207365706172617465206163742EAB616E7377657265645F6174B4323032362D31312D31325431393A30323A30305AAB72656365697665645F6174B4323032362D31312D31325431393A30323A30355AAF636C61696D5F696E5F667574757265C2A8616E737765726572D92465306431633262332D613439352D343638372D383837392D366135623463336432653166A9617574686F72697479A84F70657261746F72AA726571756573745F6964D92430303030303030312D303030302D343030302D383030302D303030303030303030303031" + }, + { + "name": "Q1_a_replayed_stale_fork_returns_the_CURRENT_card_and_stores_nothing_new", + "card": { + "schemaVersion": "gamecult.fork.card.v0", + "fork_id": "gamecult-chat:question:slot-mark-consent", + "title": "Slot mark consent", + "context": "Should a slot mark be shared?", + "options": [ + { + "id": "yes", + "text": "Share it." + }, + { + "id": "no", + "text": "Keep it local." + } + ], + "recommendation": "no", + "depends": [ + "gamecult-chat:question:file-zone-audience" + ], + "leans": null, + "flag": null, + "raised_in": { + "kind": "CutSpec", + "id": "cultlib-gaps:cut_spec:cut-slot-tolerance.r4" + }, + "deciders": [ + "e0d1c2b3-a495-4687-8879-6a5b4c3d2e1f" + ], + "status": "open", + "ruling": null, + "ruled_from": [], + "order": 3, + "options_hash": "7d95fc87d532420611fe415c1def27a3447617bb68235f46e3af130aea361c48" + }, + "optionsHash": "7d95fc87d532420611fe415c1def27a3447617bb68235f46e3af130aea361c48", + "steps": [ + { + "kind": "answer", + "body": { + "schemaVersion": "gamecult.fork.answer_request.v0", + "fork_id": "gamecult-chat:question:slot-mark-consent", + "options_hash": "14b8ffbdb61106dbc06f69f3fc8e2072af7a205d766e4cc040e89733013d54dc", + "answer": { + "choice": "no", + "note": "Keep it local: a slot mark is the author's call, and sharing it is a separate act." + }, + "retracted": false, + "retracts": null, + "answered_at": "2026-11-12T19:02:00Z", + "request_id": "00000007-0000-4000-8000-000000000007" + }, + "server": { + "answer_id": "n1", + "answerer": "e0d1c2b3-a495-4687-8879-6a5b4c3d2e1f", + "authority": "Operator", + "received_at": "2026-11-12T19:02:05Z" + }, + "response": { + "schemaVersion": "gamecult.fork.answer_result.v0", + "answer": { + "schemaVersion": "gamecult.desk_answer.v0", + "answer_id": "n1", + "fork_id": "gamecult-chat:question:slot-mark-consent", + "outcome": "Refused", + "of": null, + "retracts": null, + "code": "stale_fork", + "options_hash": "14b8ffbdb61106dbc06f69f3fc8e2072af7a205d766e4cc040e89733013d54dc", + "choice": "no", + "note": "Keep it local: a slot mark is the author's call, and sharing it is a separate act.", + "answered_at": "2026-11-12T19:02:00Z", + "received_at": "2026-11-12T19:02:05Z", + "claim_in_future": false, + "answerer": "e0d1c2b3-a495-4687-8879-6a5b4c3d2e1f", + "authority": "Operator", + "request_id": "00000007-0000-4000-8000-000000000007" + }, + "card": { + "schemaVersion": "gamecult.fork.card.v0", + "fork_id": "gamecult-chat:question:slot-mark-consent", + "title": "Slot mark consent", + "context": "Should a slot mark be shared?", + "options": [ + { + "id": "yes", + "text": "Share it." + }, + { + "id": "no", + "text": "Keep it local." + } + ], + "recommendation": "no", + "depends": [ + "gamecult-chat:question:file-zone-audience" + ], + "leans": null, + "flag": null, + "raised_in": { + "kind": "CutSpec", + "id": "cultlib-gaps:cut_spec:cut-slot-tolerance.r4" + }, + "deciders": [ + "e0d1c2b3-a495-4687-8879-6a5b4c3d2e1f" + ], + "status": "open", + "ruling": null, + "ruled_from": [], + "order": 3, + "options_hash": "7d95fc87d532420611fe415c1def27a3447617bb68235f46e3af130aea361c48" + } + } + }, + { + "kind": "card", + "body": null, + "server": null, + "response": null, + "card": { + "schemaVersion": "gamecult.fork.card.v0", + "fork_id": "gamecult-chat:question:slot-mark-consent", + "title": "Slot mark consent (revised)", + "context": "Should a slot mark be shared?", + "options": [ + { + "id": "yes", + "text": "Share it." + }, + { + "id": "no", + "text": "Keep it local." + } + ], + "recommendation": "no", + "depends": [ + "gamecult-chat:question:file-zone-audience" + ], + "leans": null, + "flag": null, + "raised_in": { + "kind": "CutSpec", + "id": "cultlib-gaps:cut_spec:cut-slot-tolerance.r4" + }, + "deciders": [ + "e0d1c2b3-a495-4687-8879-6a5b4c3d2e1f" + ], + "status": "open", + "ruling": null, + "ruled_from": [], + "order": 3, + "options_hash": "e7696ae1d487f78b5dc99fb8ccbd684d6e6f1ea7d7e190c21f3c6b4d8c42aaf7" + } + }, + { + "kind": "answer", + "body": { + "schemaVersion": "gamecult.fork.answer_request.v0", + "fork_id": "gamecult-chat:question:slot-mark-consent", + "options_hash": "14b8ffbdb61106dbc06f69f3fc8e2072af7a205d766e4cc040e89733013d54dc", + "answer": { + "choice": "no", + "note": "Keep it local: a slot mark is the author's call, and sharing it is a separate act." + }, + "retracted": false, + "retracts": null, + "answered_at": "2026-11-12T19:02:00Z", + "request_id": "00000007-0000-4000-8000-000000000007" + }, + "server": { + "answer_id": "n9", + "answerer": "e0d1c2b3-a495-4687-8879-6a5b4c3d2e1f", + "authority": "Operator", + "received_at": "2026-11-12T19:02:05Z" + }, + "response": { + "schemaVersion": "gamecult.fork.answer_result.v0", + "answer": { + "schemaVersion": "gamecult.desk_answer.v0", + "answer_id": "n1", + "fork_id": "gamecult-chat:question:slot-mark-consent", + "outcome": "Refused", + "of": null, + "retracts": null, + "code": "stale_fork", + "options_hash": "14b8ffbdb61106dbc06f69f3fc8e2072af7a205d766e4cc040e89733013d54dc", + "choice": "no", + "note": "Keep it local: a slot mark is the author's call, and sharing it is a separate act.", + "answered_at": "2026-11-12T19:02:00Z", + "received_at": "2026-11-12T19:02:05Z", + "claim_in_future": false, + "answerer": "e0d1c2b3-a495-4687-8879-6a5b4c3d2e1f", + "authority": "Operator", + "request_id": "00000007-0000-4000-8000-000000000007" + }, + "card": { + "schemaVersion": "gamecult.fork.card.v0", + "fork_id": "gamecult-chat:question:slot-mark-consent", + "title": "Slot mark consent (revised)", + "context": "Should a slot mark be shared?", + "options": [ + { + "id": "yes", + "text": "Share it." + }, + { + "id": "no", + "text": "Keep it local." + } + ], + "recommendation": "no", + "depends": [ + "gamecult-chat:question:file-zone-audience" + ], + "leans": null, + "flag": null, + "raised_in": { + "kind": "CutSpec", + "id": "cultlib-gaps:cut_spec:cut-slot-tolerance.r4" + }, + "deciders": [ + "e0d1c2b3-a495-4687-8879-6a5b4c3d2e1f" + ], + "status": "open", + "ruling": null, + "ruled_from": [], + "order": 3, + "options_hash": "e7696ae1d487f78b5dc99fb8ccbd684d6e6f1ea7d7e190c21f3c6b4d8c42aaf7" + } + } + } + ], + "record": { + "schemaVersion": "gamecult.fork.record.v0", + "fork_id": "gamecult-chat:question:slot-mark-consent", + "options_hash": "e7696ae1d487f78b5dc99fb8ccbd684d6e6f1ea7d7e190c21f3c6b4d8c42aaf7", + "counted_answer_id": null, + "answers": [ + { + "schemaVersion": "gamecult.desk_answer.v0", + "answer_id": "n1", + "fork_id": "gamecult-chat:question:slot-mark-consent", + "outcome": "Refused", + "of": null, + "retracts": null, + "code": "stale_fork", + "options_hash": "14b8ffbdb61106dbc06f69f3fc8e2072af7a205d766e4cc040e89733013d54dc", + "choice": "no", + "note": "Keep it local: a slot mark is the author's call, and sharing it is a separate act.", + "answered_at": "2026-11-12T19:02:00Z", + "received_at": "2026-11-12T19:02:05Z", + "claim_in_future": false, + "answerer": "e0d1c2b3-a495-4687-8879-6a5b4c3d2e1f", + "authority": "Operator", + "request_id": "00000007-0000-4000-8000-000000000007" + } + ] + }, + "recordHex": "85AD736368656D6156657273696F6EB767616D6563756C742E666F726B2E7265636F72642E7630A7666F726B5F6964D92867616D6563756C742D636861743A7175657374696F6E3A736C6F742D6D61726B2D636F6E73656E74AC6F7074696F6E735F68617368D94065373639366165316434383766373862356463393966623863636264363834643665366631656137643765313930633231663363366234643863343261616637B1636F756E7465645F616E737765725F6964C0A7616E737765727391DE0010AD736368656D6156657273696F6EB767616D6563756C742E6465736B5F616E737765722E7630A9616E737765725F6964A26E31A7666F726B5F6964D92867616D6563756C742D636861743A7175657374696F6E3A736C6F742D6D61726B2D636F6E73656E74A76F7574636F6D65A752656675736564A26F66C0A87265747261637473C0A4636F6465AA7374616C655F666F726BAC6F7074696F6E735F68617368D94031346238666662646236313130366462633036663639663366633865323037326166376132303564373636653463633034306538393733333031336435346463A663686F696365A26E6FA46E6F7465D9524B656570206974206C6F63616C3A206120736C6F74206D61726B2069732074686520617574686F7227732063616C6C2C20616E642073686172696E672069742069732061207365706172617465206163742EAB616E7377657265645F6174B4323032362D31312D31325431393A30323A30305AAB72656365697665645F6174B4323032362D31312D31325431393A30323A30355AAF636C61696D5F696E5F667574757265C2A8616E737765726572D92465306431633262332D613439352D343638372D383837392D366135623463336432653166A9617574686F72697479A84F70657261746F72AA726571756573745F6964D92430303030303030372D303030302D343030302D383030302D303030303030303030303037" + }, + { + "name": "V13_only_answered_at_changed_is_request_id_conflict", + "card": { + "schemaVersion": "gamecult.fork.card.v0", + "fork_id": "gamecult-chat:question:slot-mark-consent", + "title": "Slot mark consent", + "context": "Should a slot mark be shared?", + "options": [ + { + "id": "yes", + "text": "Share it." + }, + { + "id": "no", + "text": "Keep it local." + } + ], + "recommendation": "no", + "depends": [ + "gamecult-chat:question:file-zone-audience" + ], + "leans": null, + "flag": null, + "raised_in": { + "kind": "CutSpec", + "id": "cultlib-gaps:cut_spec:cut-slot-tolerance.r4" + }, + "deciders": [ + "e0d1c2b3-a495-4687-8879-6a5b4c3d2e1f" + ], + "status": "open", + "ruling": null, + "ruled_from": [], + "order": 3, + "options_hash": "7d95fc87d532420611fe415c1def27a3447617bb68235f46e3af130aea361c48" + }, + "optionsHash": "7d95fc87d532420611fe415c1def27a3447617bb68235f46e3af130aea361c48", + "steps": [ + { + "kind": "answer", + "body": { + "schemaVersion": "gamecult.fork.answer_request.v0", + "fork_id": "gamecult-chat:question:slot-mark-consent", + "options_hash": "7d95fc87d532420611fe415c1def27a3447617bb68235f46e3af130aea361c48", + "answer": { + "choice": "no", + "note": "Keep it local: a slot mark is the author's call, and sharing it is a separate act." + }, + "retracted": false, + "retracts": null, + "answered_at": "2026-11-12T19:02:00Z", + "request_id": "00000001-0000-4000-8000-000000000001" + }, + "server": { + "answer_id": "n1", + "answerer": "e0d1c2b3-a495-4687-8879-6a5b4c3d2e1f", + "authority": "Operator", + "received_at": "2026-11-12T19:02:05Z" + }, + "response": { + "schemaVersion": "gamecult.fork.answer_result.v0", + "answer": { + "schemaVersion": "gamecult.desk_answer.v0", + "answer_id": "n1", + "fork_id": "gamecult-chat:question:slot-mark-consent", + "outcome": "Counted", + "of": null, + "retracts": null, + "code": null, + "options_hash": "7d95fc87d532420611fe415c1def27a3447617bb68235f46e3af130aea361c48", + "choice": "no", + "note": "Keep it local: a slot mark is the author's call, and sharing it is a separate act.", + "answered_at": "2026-11-12T19:02:00Z", + "received_at": "2026-11-12T19:02:05Z", + "claim_in_future": false, + "answerer": "e0d1c2b3-a495-4687-8879-6a5b4c3d2e1f", + "authority": "Operator", + "request_id": "00000001-0000-4000-8000-000000000001" + }, + "card": null + } + }, + { + "kind": "answer", + "body": { + "schemaVersion": "gamecult.fork.answer_request.v0", + "fork_id": "gamecult-chat:question:slot-mark-consent", + "options_hash": "7d95fc87d532420611fe415c1def27a3447617bb68235f46e3af130aea361c48", + "answer": { + "choice": "no", + "note": "Keep it local: a slot mark is the author's call, and sharing it is a separate act." + }, + "retracted": false, + "retracts": null, + "answered_at": "2026-11-12T19:03:00Z", + "request_id": "00000001-0000-4000-8000-000000000001" + }, + "server": { + "answer_id": "n9", + "answerer": "e0d1c2b3-a495-4687-8879-6a5b4c3d2e1f", + "authority": "Operator", + "received_at": "2026-11-12T19:02:05Z" + }, + "response": { + "schemaVersion": "gamecult.fork.refusal.v0", + "code": "request_id_conflict", + "error": "request_id_conflict: this request_id was used for a different body" + } + } + ], + "record": { + "schemaVersion": "gamecult.fork.record.v0", + "fork_id": "gamecult-chat:question:slot-mark-consent", + "options_hash": "7d95fc87d532420611fe415c1def27a3447617bb68235f46e3af130aea361c48", + "counted_answer_id": "n1", + "answers": [ + { + "schemaVersion": "gamecult.desk_answer.v0", + "answer_id": "n1", + "fork_id": "gamecult-chat:question:slot-mark-consent", + "outcome": "Counted", + "of": null, + "retracts": null, + "code": null, + "options_hash": "7d95fc87d532420611fe415c1def27a3447617bb68235f46e3af130aea361c48", + "choice": "no", + "note": "Keep it local: a slot mark is the author's call, and sharing it is a separate act.", + "answered_at": "2026-11-12T19:02:00Z", + "received_at": "2026-11-12T19:02:05Z", + "claim_in_future": false, + "answerer": "e0d1c2b3-a495-4687-8879-6a5b4c3d2e1f", + "authority": "Operator", + "request_id": "00000001-0000-4000-8000-000000000001" + } + ] + }, + "recordHex": "85AD736368656D6156657273696F6EB767616D6563756C742E666F726B2E7265636F72642E7630A7666F726B5F6964D92867616D6563756C742D636861743A7175657374696F6E3A736C6F742D6D61726B2D636F6E73656E74AC6F7074696F6E735F68617368D94037643935666338376435333234323036313166653431356331646566323761333434373631376262363832333566343665336166313330616561333631633438B1636F756E7465645F616E737765725F6964A26E31A7616E737765727391DE0010AD736368656D6156657273696F6EB767616D6563756C742E6465736B5F616E737765722E7630A9616E737765725F6964A26E31A7666F726B5F6964D92867616D6563756C742D636861743A7175657374696F6E3A736C6F742D6D61726B2D636F6E73656E74A76F7574636F6D65A7436F756E746564A26F66C0A87265747261637473C0A4636F6465C0AC6F7074696F6E735F68617368D94037643935666338376435333234323036313166653431356331646566323761333434373631376262363832333566343665336166313330616561333631633438A663686F696365A26E6FA46E6F7465D9524B656570206974206C6F63616C3A206120736C6F74206D61726B2069732074686520617574686F7227732063616C6C2C20616E642073686172696E672069742069732061207365706172617465206163742EAB616E7377657265645F6174B4323032362D31312D31325431393A30323A30305AAB72656365697665645F6174B4323032362D31312D31325431393A30323A30355AAF636C61696D5F696E5F667574757265C2A8616E737765726572D92465306431633262332D613439352D343638372D383837392D366135623463336432653166A9617574686F72697479A84F70657261746F72AA726571756573745F6964D92430303030303030312D303030302D343030302D383030302D303030303030303030303031" + }, + { + "name": "V14_an_identical_body_from_another_account_gets_its_own_Counted_and_never_the_first_accounts_outcome", + "card": { + "schemaVersion": "gamecult.fork.card.v0", + "fork_id": "gamecult-chat:question:slot-mark-consent", + "title": "Slot mark consent", + "context": "Should a slot mark be shared?", + "options": [ + { + "id": "yes", + "text": "Share it." + }, + { + "id": "no", + "text": "Keep it local." + } + ], + "recommendation": "no", + "depends": [ + "gamecult-chat:question:file-zone-audience" + ], + "leans": null, + "flag": null, + "raised_in": { + "kind": "CutSpec", + "id": "cultlib-gaps:cut_spec:cut-slot-tolerance.r4" + }, + "deciders": [ + "e0d1c2b3-a495-4687-8879-6a5b4c3d2e1f", + "9f8e7d6c-5b4a-4392-8170-0f1e2d3c4b5a" + ], + "status": "open", + "ruling": null, + "ruled_from": [], + "order": 3, + "options_hash": "7d95fc87d532420611fe415c1def27a3447617bb68235f46e3af130aea361c48" + }, + "optionsHash": "7d95fc87d532420611fe415c1def27a3447617bb68235f46e3af130aea361c48", + "steps": [ + { + "kind": "answer", + "body": { + "schemaVersion": "gamecult.fork.answer_request.v0", + "fork_id": "gamecult-chat:question:slot-mark-consent", + "options_hash": "7d95fc87d532420611fe415c1def27a3447617bb68235f46e3af130aea361c48", + "answer": { + "choice": "no", + "note": "Keep it local: a slot mark is the author's call, and sharing it is a separate act." + }, + "retracted": false, + "retracts": null, + "answered_at": "2026-11-12T19:02:00Z", + "request_id": "00000001-0000-4000-8000-000000000001" + }, + "server": { + "answer_id": "n1", + "answerer": "11111111-2222-4333-8444-555555555555", + "authority": "Member", + "received_at": "2026-11-12T19:02:05Z" + }, + "response": { + "schemaVersion": "gamecult.fork.answer_result.v0", + "answer": { + "schemaVersion": "gamecult.desk_answer.v0", + "answer_id": "n1", + "fork_id": "gamecult-chat:question:slot-mark-consent", + "outcome": "Refused", + "of": null, + "retracts": null, + "code": "not_a_decider", + "options_hash": "7d95fc87d532420611fe415c1def27a3447617bb68235f46e3af130aea361c48", + "choice": "no", + "note": "Keep it local: a slot mark is the author's call, and sharing it is a separate act.", + "answered_at": "2026-11-12T19:02:00Z", + "received_at": "2026-11-12T19:02:05Z", + "claim_in_future": false, + "answerer": "11111111-2222-4333-8444-555555555555", + "authority": "Member", + "request_id": "00000001-0000-4000-8000-000000000001" + }, + "card": null + } + }, + { + "kind": "answer", + "body": { + "schemaVersion": "gamecult.fork.answer_request.v0", + "fork_id": "gamecult-chat:question:slot-mark-consent", + "options_hash": "7d95fc87d532420611fe415c1def27a3447617bb68235f46e3af130aea361c48", + "answer": { + "choice": "no", + "note": "Keep it local: a slot mark is the author's call, and sharing it is a separate act." + }, + "retracted": false, + "retracts": null, + "answered_at": "2026-11-12T19:02:00Z", + "request_id": "00000001-0000-4000-8000-000000000001" + }, + "server": { + "answer_id": "n2", + "answerer": "9f8e7d6c-5b4a-4392-8170-0f1e2d3c4b5a", + "authority": "Steward", + "received_at": "2026-11-12T19:03:05Z" + }, + "response": { + "schemaVersion": "gamecult.fork.answer_result.v0", + "answer": { + "schemaVersion": "gamecult.desk_answer.v0", + "answer_id": "n2", + "fork_id": "gamecult-chat:question:slot-mark-consent", + "outcome": "Counted", + "of": null, + "retracts": null, + "code": null, + "options_hash": "7d95fc87d532420611fe415c1def27a3447617bb68235f46e3af130aea361c48", + "choice": "no", + "note": "Keep it local: a slot mark is the author's call, and sharing it is a separate act.", + "answered_at": "2026-11-12T19:02:00Z", + "received_at": "2026-11-12T19:03:05Z", + "claim_in_future": false, + "answerer": "9f8e7d6c-5b4a-4392-8170-0f1e2d3c4b5a", + "authority": "Steward", + "request_id": "00000001-0000-4000-8000-000000000001" + }, + "card": null + } + }, + { + "kind": "answer", + "body": { + "schemaVersion": "gamecult.fork.answer_request.v0", + "fork_id": "gamecult-chat:question:slot-mark-consent", + "options_hash": "7d95fc87d532420611fe415c1def27a3447617bb68235f46e3af130aea361c48", + "answer": { + "choice": "no", + "note": "Keep it local: a slot mark is the author's call, and sharing it is a separate act." + }, + "retracted": false, + "retracts": null, + "answered_at": "2026-11-12T19:02:00Z", + "request_id": "00000001-0000-4000-8000-000000000001" + }, + "server": { + "answer_id": "n9", + "answerer": "11111111-2222-4333-8444-555555555555", + "authority": "Member", + "received_at": "2026-11-12T19:04:05Z" + }, + "response": { + "schemaVersion": "gamecult.fork.answer_result.v0", + "answer": { + "schemaVersion": "gamecult.desk_answer.v0", + "answer_id": "n1", + "fork_id": "gamecult-chat:question:slot-mark-consent", + "outcome": "Refused", + "of": null, + "retracts": null, + "code": "not_a_decider", + "options_hash": "7d95fc87d532420611fe415c1def27a3447617bb68235f46e3af130aea361c48", + "choice": "no", + "note": "Keep it local: a slot mark is the author's call, and sharing it is a separate act.", + "answered_at": "2026-11-12T19:02:00Z", + "received_at": "2026-11-12T19:02:05Z", + "claim_in_future": false, + "answerer": "11111111-2222-4333-8444-555555555555", + "authority": "Member", + "request_id": "00000001-0000-4000-8000-000000000001" + }, + "card": null + } + } + ], + "record": { + "schemaVersion": "gamecult.fork.record.v0", + "fork_id": "gamecult-chat:question:slot-mark-consent", + "options_hash": "7d95fc87d532420611fe415c1def27a3447617bb68235f46e3af130aea361c48", + "counted_answer_id": "n2", + "answers": [ + { + "schemaVersion": "gamecult.desk_answer.v0", + "answer_id": "n1", + "fork_id": "gamecult-chat:question:slot-mark-consent", + "outcome": "Refused", + "of": null, + "retracts": null, + "code": "not_a_decider", + "options_hash": "7d95fc87d532420611fe415c1def27a3447617bb68235f46e3af130aea361c48", + "choice": "no", + "note": "Keep it local: a slot mark is the author's call, and sharing it is a separate act.", + "answered_at": "2026-11-12T19:02:00Z", + "received_at": "2026-11-12T19:02:05Z", + "claim_in_future": false, + "answerer": "11111111-2222-4333-8444-555555555555", + "authority": "Member", + "request_id": "00000001-0000-4000-8000-000000000001" + }, + { + "schemaVersion": "gamecult.desk_answer.v0", + "answer_id": "n2", + "fork_id": "gamecult-chat:question:slot-mark-consent", + "outcome": "Counted", + "of": null, + "retracts": null, + "code": null, + "options_hash": "7d95fc87d532420611fe415c1def27a3447617bb68235f46e3af130aea361c48", + "choice": "no", + "note": "Keep it local: a slot mark is the author's call, and sharing it is a separate act.", + "answered_at": "2026-11-12T19:02:00Z", + "received_at": "2026-11-12T19:03:05Z", + "claim_in_future": false, + "answerer": "9f8e7d6c-5b4a-4392-8170-0f1e2d3c4b5a", + "authority": "Steward", + "request_id": "00000001-0000-4000-8000-000000000001" + } + ] + }, + "recordHex": "85AD736368656D6156657273696F6EB767616D6563756C742E666F726B2E7265636F72642E7630A7666F726B5F6964D92867616D6563756C742D636861743A7175657374696F6E3A736C6F742D6D61726B2D636F6E73656E74AC6F7074696F6E735F68617368D94037643935666338376435333234323036313166653431356331646566323761333434373631376262363832333566343665336166313330616561333631633438B1636F756E7465645F616E737765725F6964A26E32A7616E737765727392DE0010AD736368656D6156657273696F6EB767616D6563756C742E6465736B5F616E737765722E7630A9616E737765725F6964A26E31A7666F726B5F6964D92867616D6563756C742D636861743A7175657374696F6E3A736C6F742D6D61726B2D636F6E73656E74A76F7574636F6D65A752656675736564A26F66C0A87265747261637473C0A4636F6465AD6E6F745F615F64656369646572AC6F7074696F6E735F68617368D94037643935666338376435333234323036313166653431356331646566323761333434373631376262363832333566343665336166313330616561333631633438A663686F696365A26E6FA46E6F7465D9524B656570206974206C6F63616C3A206120736C6F74206D61726B2069732074686520617574686F7227732063616C6C2C20616E642073686172696E672069742069732061207365706172617465206163742EAB616E7377657265645F6174B4323032362D31312D31325431393A30323A30305AAB72656365697665645F6174B4323032362D31312D31325431393A30323A30355AAF636C61696D5F696E5F667574757265C2A8616E737765726572D92431313131313131312D323232322D343333332D383434342D353535353535353535353535A9617574686F72697479A64D656D626572AA726571756573745F6964D92430303030303030312D303030302D343030302D383030302D303030303030303030303031DE0010AD736368656D6156657273696F6EB767616D6563756C742E6465736B5F616E737765722E7630A9616E737765725F6964A26E32A7666F726B5F6964D92867616D6563756C742D636861743A7175657374696F6E3A736C6F742D6D61726B2D636F6E73656E74A76F7574636F6D65A7436F756E746564A26F66C0A87265747261637473C0A4636F6465C0AC6F7074696F6E735F68617368D94037643935666338376435333234323036313166653431356331646566323761333434373631376262363832333566343665336166313330616561333631633438A663686F696365A26E6FA46E6F7465D9524B656570206974206C6F63616C3A206120736C6F74206D61726B2069732074686520617574686F7227732063616C6C2C20616E642073686172696E672069742069732061207365706172617465206163742EAB616E7377657265645F6174B4323032362D31312D31325431393A30323A30305AAB72656365697665645F6174B4323032362D31312D31325431393A30333A30355AAF636C61696D5F696E5F667574757265C2A8616E737765726572D92439663865376436632D356234612D343339322D383137302D306631653264336334623561A9617574686F72697479A753746577617264AA726571756573745F6964D92430303030303030312D303030302D343030302D383030302D303030303030303030303031" + }, + { + "name": "V14_an_identical_body_from_another_decider_is_its_own_Revision_and_the_first_retry_still_returns_n1", + "card": { + "schemaVersion": "gamecult.fork.card.v0", + "fork_id": "gamecult-chat:question:slot-mark-consent", + "title": "Slot mark consent", + "context": "Should a slot mark be shared?", + "options": [ + { + "id": "yes", + "text": "Share it." + }, + { + "id": "no", + "text": "Keep it local." + } + ], + "recommendation": "no", + "depends": [ + "gamecult-chat:question:file-zone-audience" + ], + "leans": null, + "flag": null, + "raised_in": { + "kind": "CutSpec", + "id": "cultlib-gaps:cut_spec:cut-slot-tolerance.r4" + }, + "deciders": [ + "e0d1c2b3-a495-4687-8879-6a5b4c3d2e1f", + "9f8e7d6c-5b4a-4392-8170-0f1e2d3c4b5a" + ], + "status": "open", + "ruling": null, + "ruled_from": [], + "order": 3, + "options_hash": "7d95fc87d532420611fe415c1def27a3447617bb68235f46e3af130aea361c48" + }, + "optionsHash": "7d95fc87d532420611fe415c1def27a3447617bb68235f46e3af130aea361c48", + "steps": [ + { + "kind": "answer", + "body": { + "schemaVersion": "gamecult.fork.answer_request.v0", + "fork_id": "gamecult-chat:question:slot-mark-consent", + "options_hash": "7d95fc87d532420611fe415c1def27a3447617bb68235f46e3af130aea361c48", + "answer": { + "choice": "no", + "note": "Keep it local: a slot mark is the author's call, and sharing it is a separate act." + }, + "retracted": false, + "retracts": null, + "answered_at": "2026-11-12T19:02:00Z", + "request_id": "00000001-0000-4000-8000-000000000001" + }, + "server": { + "answer_id": "n1", + "answerer": "e0d1c2b3-a495-4687-8879-6a5b4c3d2e1f", + "authority": "Operator", + "received_at": "2026-11-12T19:02:05Z" + }, + "response": { + "schemaVersion": "gamecult.fork.answer_result.v0", + "answer": { + "schemaVersion": "gamecult.desk_answer.v0", + "answer_id": "n1", + "fork_id": "gamecult-chat:question:slot-mark-consent", + "outcome": "Counted", + "of": null, + "retracts": null, + "code": null, + "options_hash": "7d95fc87d532420611fe415c1def27a3447617bb68235f46e3af130aea361c48", + "choice": "no", + "note": "Keep it local: a slot mark is the author's call, and sharing it is a separate act.", + "answered_at": "2026-11-12T19:02:00Z", + "received_at": "2026-11-12T19:02:05Z", + "claim_in_future": false, + "answerer": "e0d1c2b3-a495-4687-8879-6a5b4c3d2e1f", + "authority": "Operator", + "request_id": "00000001-0000-4000-8000-000000000001" + }, + "card": null + } + }, + { + "kind": "answer", + "body": { + "schemaVersion": "gamecult.fork.answer_request.v0", + "fork_id": "gamecult-chat:question:slot-mark-consent", + "options_hash": "7d95fc87d532420611fe415c1def27a3447617bb68235f46e3af130aea361c48", + "answer": { + "choice": "no", + "note": "Keep it local: a slot mark is the author's call, and sharing it is a separate act." + }, + "retracted": false, + "retracts": null, + "answered_at": "2026-11-12T19:02:00Z", + "request_id": "00000001-0000-4000-8000-000000000001" + }, + "server": { + "answer_id": "n3", + "answerer": "9f8e7d6c-5b4a-4392-8170-0f1e2d3c4b5a", + "authority": "Steward", + "received_at": "2026-11-12T19:05:05Z" + }, + "response": { + "schemaVersion": "gamecult.fork.answer_result.v0", + "answer": { + "schemaVersion": "gamecult.desk_answer.v0", + "answer_id": "n3", + "fork_id": "gamecult-chat:question:slot-mark-consent", + "outcome": "Revision", + "of": "n1", + "retracts": null, + "code": null, + "options_hash": "7d95fc87d532420611fe415c1def27a3447617bb68235f46e3af130aea361c48", + "choice": "no", + "note": "Keep it local: a slot mark is the author's call, and sharing it is a separate act.", + "answered_at": "2026-11-12T19:02:00Z", + "received_at": "2026-11-12T19:05:05Z", + "claim_in_future": false, + "answerer": "9f8e7d6c-5b4a-4392-8170-0f1e2d3c4b5a", + "authority": "Steward", + "request_id": "00000001-0000-4000-8000-000000000001" + }, + "card": null + } + }, + { + "kind": "answer", + "body": { + "schemaVersion": "gamecult.fork.answer_request.v0", + "fork_id": "gamecult-chat:question:slot-mark-consent", + "options_hash": "7d95fc87d532420611fe415c1def27a3447617bb68235f46e3af130aea361c48", + "answer": { + "choice": "no", + "note": "Keep it local: a slot mark is the author's call, and sharing it is a separate act." + }, + "retracted": false, + "retracts": null, + "answered_at": "2026-11-12T19:02:00Z", + "request_id": "00000001-0000-4000-8000-000000000001" + }, + "server": { + "answer_id": "n9", + "answerer": "e0d1c2b3-a495-4687-8879-6a5b4c3d2e1f", + "authority": "Operator", + "received_at": "2026-11-12T19:02:05Z" + }, + "response": { + "schemaVersion": "gamecult.fork.answer_result.v0", + "answer": { + "schemaVersion": "gamecult.desk_answer.v0", + "answer_id": "n1", + "fork_id": "gamecult-chat:question:slot-mark-consent", + "outcome": "Counted", + "of": null, + "retracts": null, + "code": null, + "options_hash": "7d95fc87d532420611fe415c1def27a3447617bb68235f46e3af130aea361c48", + "choice": "no", + "note": "Keep it local: a slot mark is the author's call, and sharing it is a separate act.", + "answered_at": "2026-11-12T19:02:00Z", + "received_at": "2026-11-12T19:02:05Z", + "claim_in_future": false, + "answerer": "e0d1c2b3-a495-4687-8879-6a5b4c3d2e1f", + "authority": "Operator", + "request_id": "00000001-0000-4000-8000-000000000001" + }, + "card": null + } + } + ], + "record": { + "schemaVersion": "gamecult.fork.record.v0", + "fork_id": "gamecult-chat:question:slot-mark-consent", + "options_hash": "7d95fc87d532420611fe415c1def27a3447617bb68235f46e3af130aea361c48", + "counted_answer_id": "n1", + "answers": [ + { + "schemaVersion": "gamecult.desk_answer.v0", + "answer_id": "n1", + "fork_id": "gamecult-chat:question:slot-mark-consent", + "outcome": "Counted", + "of": null, + "retracts": null, + "code": null, + "options_hash": "7d95fc87d532420611fe415c1def27a3447617bb68235f46e3af130aea361c48", + "choice": "no", + "note": "Keep it local: a slot mark is the author's call, and sharing it is a separate act.", + "answered_at": "2026-11-12T19:02:00Z", + "received_at": "2026-11-12T19:02:05Z", + "claim_in_future": false, + "answerer": "e0d1c2b3-a495-4687-8879-6a5b4c3d2e1f", + "authority": "Operator", + "request_id": "00000001-0000-4000-8000-000000000001" + }, + { + "schemaVersion": "gamecult.desk_answer.v0", + "answer_id": "n3", + "fork_id": "gamecult-chat:question:slot-mark-consent", + "outcome": "Revision", + "of": "n1", + "retracts": null, + "code": null, + "options_hash": "7d95fc87d532420611fe415c1def27a3447617bb68235f46e3af130aea361c48", + "choice": "no", + "note": "Keep it local: a slot mark is the author's call, and sharing it is a separate act.", + "answered_at": "2026-11-12T19:02:00Z", + "received_at": "2026-11-12T19:05:05Z", + "claim_in_future": false, + "answerer": "9f8e7d6c-5b4a-4392-8170-0f1e2d3c4b5a", + "authority": "Steward", + "request_id": "00000001-0000-4000-8000-000000000001" + } + ] + }, + "recordHex": "85AD736368656D6156657273696F6EB767616D6563756C742E666F726B2E7265636F72642E7630A7666F726B5F6964D92867616D6563756C742D636861743A7175657374696F6E3A736C6F742D6D61726B2D636F6E73656E74AC6F7074696F6E735F68617368D94037643935666338376435333234323036313166653431356331646566323761333434373631376262363832333566343665336166313330616561333631633438B1636F756E7465645F616E737765725F6964A26E31A7616E737765727392DE0010AD736368656D6156657273696F6EB767616D6563756C742E6465736B5F616E737765722E7630A9616E737765725F6964A26E31A7666F726B5F6964D92867616D6563756C742D636861743A7175657374696F6E3A736C6F742D6D61726B2D636F6E73656E74A76F7574636F6D65A7436F756E746564A26F66C0A87265747261637473C0A4636F6465C0AC6F7074696F6E735F68617368D94037643935666338376435333234323036313166653431356331646566323761333434373631376262363832333566343665336166313330616561333631633438A663686F696365A26E6FA46E6F7465D9524B656570206974206C6F63616C3A206120736C6F74206D61726B2069732074686520617574686F7227732063616C6C2C20616E642073686172696E672069742069732061207365706172617465206163742EAB616E7377657265645F6174B4323032362D31312D31325431393A30323A30305AAB72656365697665645F6174B4323032362D31312D31325431393A30323A30355AAF636C61696D5F696E5F667574757265C2A8616E737765726572D92465306431633262332D613439352D343638372D383837392D366135623463336432653166A9617574686F72697479A84F70657261746F72AA726571756573745F6964D92430303030303030312D303030302D343030302D383030302D303030303030303030303031DE0010AD736368656D6156657273696F6EB767616D6563756C742E6465736B5F616E737765722E7630A9616E737765725F6964A26E33A7666F726B5F6964D92867616D6563756C742D636861743A7175657374696F6E3A736C6F742D6D61726B2D636F6E73656E74A76F7574636F6D65A85265766973696F6EA26F66A26E31A87265747261637473C0A4636F6465C0AC6F7074696F6E735F68617368D94037643935666338376435333234323036313166653431356331646566323761333434373631376262363832333566343665336166313330616561333631633438A663686F696365A26E6FA46E6F7465D9524B656570206974206C6F63616C3A206120736C6F74206D61726B2069732074686520617574686F7227732063616C6C2C20616E642073686172696E672069742069732061207365706172617465206163742EAB616E7377657265645F6174B4323032362D31312D31325431393A30323A30305AAB72656365697665645F6174B4323032362D31312D31325431393A30353A30355AAF636C61696D5F696E5F667574757265C2A8616E737765726572D92439663865376436632D356234612D343339322D383137302D306631653264336334623561A9617574686F72697479A753746577617264AA726571756573745F6964D92430303030303030312D303030302D343030302D383030302D303030303030303030303031" + }, + { + "name": "V16_Q5_a_forbidden_request_binds_no_request_id", + "card": { + "schemaVersion": "gamecult.fork.card.v0", + "fork_id": "gamecult-chat:question:slot-mark-consent", + "title": "Slot mark consent", + "context": "Should a slot mark be shared?", + "options": [ + { + "id": "yes", + "text": "Share it." + }, + { + "id": "no", + "text": "Keep it local." + } + ], + "recommendation": "no", + "depends": [ + "gamecult-chat:question:file-zone-audience" + ], + "leans": null, + "flag": null, + "raised_in": { + "kind": "CutSpec", + "id": "cultlib-gaps:cut_spec:cut-slot-tolerance.r4" + }, + "deciders": [ + "e0d1c2b3-a495-4687-8879-6a5b4c3d2e1f" + ], + "status": "open", + "ruling": null, + "ruled_from": [], + "order": 3, + "options_hash": "7d95fc87d532420611fe415c1def27a3447617bb68235f46e3af130aea361c48" + }, + "optionsHash": "7d95fc87d532420611fe415c1def27a3447617bb68235f46e3af130aea361c48", + "steps": [ + { + "kind": "answer", + "body": { + "schemaVersion": "gamecult.fork.answer_request.v0", + "fork_id": "gamecult-chat:question:slot-mark-consent", + "options_hash": "7d95fc87d532420611fe415c1def27a3447617bb68235f46e3af130aea361c48", + "answer": { + "choice": "no", + "note": "Keep it local: a slot mark is the author's call, and sharing it is a separate act." + }, + "retracted": false, + "retracts": null, + "answered_at": "2026-11-12T19:02:00Z", + "request_id": "00000001-0000-4000-8000-000000000001" + }, + "server": { + "answer_id": "n9", + "answerer": "e0d1c2b3-a495-4687-8879-6a5b4c3d2e1f", + "authority": "Operator", + "received_at": "2026-11-12T19:02:05Z", + "active": false + }, + "response": { + "schemaVersion": "gamecult.fork.refusal.v0", + "code": "forbidden", + "error": "forbidden: only Active members may use the desk" + } + }, + { + "kind": "answer", + "body": { + "schemaVersion": "gamecult.fork.answer_request.v0", + "fork_id": "gamecult-chat:question:slot-mark-consent", + "options_hash": "7d95fc87d532420611fe415c1def27a3447617bb68235f46e3af130aea361c48", + "answer": { + "choice": "no", + "note": "Keep it local: a slot mark is the author's call, and sharing it is a separate act." + }, + "retracted": false, + "retracts": null, + "answered_at": "2026-11-12T19:02:00Z", + "request_id": "00000001-0000-4000-8000-000000000001" + }, + "server": { + "answer_id": "n1", + "answerer": "e0d1c2b3-a495-4687-8879-6a5b4c3d2e1f", + "authority": "Operator", + "received_at": "2026-11-12T19:02:05Z" + }, + "response": { + "schemaVersion": "gamecult.fork.answer_result.v0", + "answer": { + "schemaVersion": "gamecult.desk_answer.v0", + "answer_id": "n1", + "fork_id": "gamecult-chat:question:slot-mark-consent", + "outcome": "Counted", + "of": null, + "retracts": null, + "code": null, + "options_hash": "7d95fc87d532420611fe415c1def27a3447617bb68235f46e3af130aea361c48", + "choice": "no", + "note": "Keep it local: a slot mark is the author's call, and sharing it is a separate act.", + "answered_at": "2026-11-12T19:02:00Z", + "received_at": "2026-11-12T19:02:05Z", + "claim_in_future": false, + "answerer": "e0d1c2b3-a495-4687-8879-6a5b4c3d2e1f", + "authority": "Operator", + "request_id": "00000001-0000-4000-8000-000000000001" + }, + "card": null + } + } + ], + "record": { + "schemaVersion": "gamecult.fork.record.v0", + "fork_id": "gamecult-chat:question:slot-mark-consent", + "options_hash": "7d95fc87d532420611fe415c1def27a3447617bb68235f46e3af130aea361c48", + "counted_answer_id": "n1", + "answers": [ + { + "schemaVersion": "gamecult.desk_answer.v0", + "answer_id": "n1", + "fork_id": "gamecult-chat:question:slot-mark-consent", + "outcome": "Counted", + "of": null, + "retracts": null, + "code": null, + "options_hash": "7d95fc87d532420611fe415c1def27a3447617bb68235f46e3af130aea361c48", + "choice": "no", + "note": "Keep it local: a slot mark is the author's call, and sharing it is a separate act.", + "answered_at": "2026-11-12T19:02:00Z", + "received_at": "2026-11-12T19:02:05Z", + "claim_in_future": false, + "answerer": "e0d1c2b3-a495-4687-8879-6a5b4c3d2e1f", + "authority": "Operator", + "request_id": "00000001-0000-4000-8000-000000000001" + } + ] + }, + "recordHex": "85AD736368656D6156657273696F6EB767616D6563756C742E666F726B2E7265636F72642E7630A7666F726B5F6964D92867616D6563756C742D636861743A7175657374696F6E3A736C6F742D6D61726B2D636F6E73656E74AC6F7074696F6E735F68617368D94037643935666338376435333234323036313166653431356331646566323761333434373631376262363832333566343665336166313330616561333631633438B1636F756E7465645F616E737765725F6964A26E31A7616E737765727391DE0010AD736368656D6156657273696F6EB767616D6563756C742E6465736B5F616E737765722E7630A9616E737765725F6964A26E31A7666F726B5F6964D92867616D6563756C742D636861743A7175657374696F6E3A736C6F742D6D61726B2D636F6E73656E74A76F7574636F6D65A7436F756E746564A26F66C0A87265747261637473C0A4636F6465C0AC6F7074696F6E735F68617368D94037643935666338376435333234323036313166653431356331646566323761333434373631376262363832333566343665336166313330616561333631633438A663686F696365A26E6FA46E6F7465D9524B656570206974206C6F63616C3A206120736C6F74206D61726B2069732074686520617574686F7227732063616C6C2C20616E642073686172696E672069742069732061207365706172617465206163742EAB616E7377657265645F6174B4323032362D31312D31325431393A30323A30305AAB72656365697665645F6174B4323032362D31312D31325431393A30323A30355AAF636C61696D5F696E5F667574757265C2A8616E737765726572D92465306431633262332D613439352D343638372D383837392D366135623463336432653166A9617574686F72697479A84F70657261746F72AA726571756573745F6964D92430303030303030312D303030302D343030302D383030302D303030303030303030303031" + }, + { + "name": "V17_Q6_a_body_refused_at_the_door_binds_no_request_id", + "card": { + "schemaVersion": "gamecult.fork.card.v0", + "fork_id": "gamecult-chat:question:slot-mark-consent", + "title": "Slot mark consent", + "context": "Should a slot mark be shared?", + "options": [ + { + "id": "yes", + "text": "Share it." + }, + { + "id": "no", + "text": "Keep it local." + } + ], + "recommendation": "no", + "depends": [ + "gamecult-chat:question:file-zone-audience" + ], + "leans": null, + "flag": null, + "raised_in": { + "kind": "CutSpec", + "id": "cultlib-gaps:cut_spec:cut-slot-tolerance.r4" + }, + "deciders": [ + "e0d1c2b3-a495-4687-8879-6a5b4c3d2e1f" + ], + "status": "open", + "ruling": null, + "ruled_from": [], + "order": 3, + "options_hash": "7d95fc87d532420611fe415c1def27a3447617bb68235f46e3af130aea361c48" + }, + "optionsHash": "7d95fc87d532420611fe415c1def27a3447617bb68235f46e3af130aea361c48", + "steps": [ + { + "kind": "answer", + "body": { + "schemaVersion": "gamecult.fork.answer_request.v0", + "fork_id": "gamecult-chat:question:slot-mark-consent", + "options_hash": "7d95fc87d532420611fe415c1def27a3447617bb68235f46e3af130aea361c48", + "answer": { + "choice": "no", + "note": "Keep it local: a slot mark is the author's call, and sharing it is a separate act." + }, + "retracted": false, + "retracts": null, + "answered_at": "2026-11-12T19:02:00Z", + "request_id": "00000001-0000-4000-8000-000000000001", + "mood": "happy" + }, + "server": { + "answer_id": "n9", + "answerer": "e0d1c2b3-a495-4687-8879-6a5b4c3d2e1f", + "authority": "Operator", + "received_at": "2026-11-12T19:02:05Z" + }, + "response": { + "schemaVersion": "gamecult.fork.refusal.v0", + "code": "body_unknown_field", + "error": "body_unknown_field: the body is a closed shape" + } + }, + { + "kind": "answer", + "body": { + "schemaVersion": "gamecult.fork.answer_request.v0", + "fork_id": "gamecult-chat:question:slot-mark-consent", + "options_hash": "7d95fc87d532420611fe415c1def27a3447617bb68235f46e3af130aea361c48", + "answer": { + "choice": "no", + "note": "Keep it local: a slot mark is the author's call, and sharing it is a separate act." + }, + "retracted": false, + "retracts": null, + "answered_at": "2026-11-12T19:02:00Z", + "request_id": "00000001-0000-4000-8000-000000000001" + }, + "server": { + "answer_id": "n1", + "answerer": "e0d1c2b3-a495-4687-8879-6a5b4c3d2e1f", + "authority": "Operator", + "received_at": "2026-11-12T19:02:05Z" + }, + "response": { + "schemaVersion": "gamecult.fork.answer_result.v0", + "answer": { + "schemaVersion": "gamecult.desk_answer.v0", + "answer_id": "n1", + "fork_id": "gamecult-chat:question:slot-mark-consent", + "outcome": "Counted", + "of": null, + "retracts": null, + "code": null, + "options_hash": "7d95fc87d532420611fe415c1def27a3447617bb68235f46e3af130aea361c48", + "choice": "no", + "note": "Keep it local: a slot mark is the author's call, and sharing it is a separate act.", + "answered_at": "2026-11-12T19:02:00Z", + "received_at": "2026-11-12T19:02:05Z", + "claim_in_future": false, + "answerer": "e0d1c2b3-a495-4687-8879-6a5b4c3d2e1f", + "authority": "Operator", + "request_id": "00000001-0000-4000-8000-000000000001" + }, + "card": null + } + } + ], + "record": { + "schemaVersion": "gamecult.fork.record.v0", + "fork_id": "gamecult-chat:question:slot-mark-consent", + "options_hash": "7d95fc87d532420611fe415c1def27a3447617bb68235f46e3af130aea361c48", + "counted_answer_id": "n1", + "answers": [ + { + "schemaVersion": "gamecult.desk_answer.v0", + "answer_id": "n1", + "fork_id": "gamecult-chat:question:slot-mark-consent", + "outcome": "Counted", + "of": null, + "retracts": null, + "code": null, + "options_hash": "7d95fc87d532420611fe415c1def27a3447617bb68235f46e3af130aea361c48", + "choice": "no", + "note": "Keep it local: a slot mark is the author's call, and sharing it is a separate act.", + "answered_at": "2026-11-12T19:02:00Z", + "received_at": "2026-11-12T19:02:05Z", + "claim_in_future": false, + "answerer": "e0d1c2b3-a495-4687-8879-6a5b4c3d2e1f", + "authority": "Operator", + "request_id": "00000001-0000-4000-8000-000000000001" + } + ] + }, + "recordHex": "85AD736368656D6156657273696F6EB767616D6563756C742E666F726B2E7265636F72642E7630A7666F726B5F6964D92867616D6563756C742D636861743A7175657374696F6E3A736C6F742D6D61726B2D636F6E73656E74AC6F7074696F6E735F68617368D94037643935666338376435333234323036313166653431356331646566323761333434373631376262363832333566343665336166313330616561333631633438B1636F756E7465645F616E737765725F6964A26E31A7616E737765727391DE0010AD736368656D6156657273696F6EB767616D6563756C742E6465736B5F616E737765722E7630A9616E737765725F6964A26E31A7666F726B5F6964D92867616D6563756C742D636861743A7175657374696F6E3A736C6F742D6D61726B2D636F6E73656E74A76F7574636F6D65A7436F756E746564A26F66C0A87265747261637473C0A4636F6465C0AC6F7074696F6E735F68617368D94037643935666338376435333234323036313166653431356331646566323761333434373631376262363832333566343665336166313330616561333631633438A663686F696365A26E6FA46E6F7465D9524B656570206974206C6F63616C3A206120736C6F74206D61726B2069732074686520617574686F7227732063616C6C2C20616E642073686172696E672069742069732061207365706172617465206163742EAB616E7377657265645F6174B4323032362D31312D31325431393A30323A30305AAB72656365697665645F6174B4323032362D31312D31325431393A30323A30355AAF636C61696D5F696E5F667574757265C2A8616E737765726572D92465306431633262332D613439352D343638372D383837392D366135623463336432653166A9617574686F72697479A84F70657261746F72AA726571756573745F6964D92430303030303030312D303030302D343030302D383030302D303030303030303030303031" + }, + { + "name": "V17_Q6_an_unknown_fork_binds_no_request_id", + "card": { + "schemaVersion": "gamecult.fork.card.v0", + "fork_id": "gamecult-chat:question:slot-mark-consent", + "title": "Slot mark consent", + "context": "Should a slot mark be shared?", + "options": [ + { + "id": "yes", + "text": "Share it." + }, + { + "id": "no", + "text": "Keep it local." + } + ], + "recommendation": "no", + "depends": [ + "gamecult-chat:question:file-zone-audience" + ], + "leans": null, + "flag": null, + "raised_in": { + "kind": "CutSpec", + "id": "cultlib-gaps:cut_spec:cut-slot-tolerance.r4" + }, + "deciders": [ + "e0d1c2b3-a495-4687-8879-6a5b4c3d2e1f" + ], + "status": "open", + "ruling": null, + "ruled_from": [], + "order": 3, + "options_hash": "7d95fc87d532420611fe415c1def27a3447617bb68235f46e3af130aea361c48" + }, + "optionsHash": "7d95fc87d532420611fe415c1def27a3447617bb68235f46e3af130aea361c48", + "steps": [ + { + "kind": "answer", + "body": { + "schemaVersion": "gamecult.fork.answer_request.v0", + "fork_id": "gamecult-chat:question:nope", + "options_hash": "7d95fc87d532420611fe415c1def27a3447617bb68235f46e3af130aea361c48", + "answer": { + "choice": "no", + "note": "Keep it local: a slot mark is the author's call, and sharing it is a separate act." + }, + "retracted": false, + "retracts": null, + "answered_at": "2026-11-12T19:02:00Z", + "request_id": "00000001-0000-4000-8000-000000000001" + }, + "server": { + "answer_id": "n9", + "answerer": "e0d1c2b3-a495-4687-8879-6a5b4c3d2e1f", + "authority": "Operator", + "received_at": "2026-11-12T19:02:05Z" + }, + "response": { + "schemaVersion": "gamecult.fork.refusal.v0", + "code": "fork_unknown", + "error": "fork_unknown: no such fork" + } + }, + { + "kind": "answer", + "body": { + "schemaVersion": "gamecult.fork.answer_request.v0", + "fork_id": "gamecult-chat:question:slot-mark-consent", + "options_hash": "7d95fc87d532420611fe415c1def27a3447617bb68235f46e3af130aea361c48", + "answer": { + "choice": "no", + "note": "Keep it local: a slot mark is the author's call, and sharing it is a separate act." + }, + "retracted": false, + "retracts": null, + "answered_at": "2026-11-12T19:02:00Z", + "request_id": "00000001-0000-4000-8000-000000000001" + }, + "server": { + "answer_id": "n1", + "answerer": "e0d1c2b3-a495-4687-8879-6a5b4c3d2e1f", + "authority": "Operator", + "received_at": "2026-11-12T19:02:05Z" + }, + "response": { + "schemaVersion": "gamecult.fork.answer_result.v0", + "answer": { + "schemaVersion": "gamecult.desk_answer.v0", + "answer_id": "n1", + "fork_id": "gamecult-chat:question:slot-mark-consent", + "outcome": "Counted", + "of": null, + "retracts": null, + "code": null, + "options_hash": "7d95fc87d532420611fe415c1def27a3447617bb68235f46e3af130aea361c48", + "choice": "no", + "note": "Keep it local: a slot mark is the author's call, and sharing it is a separate act.", + "answered_at": "2026-11-12T19:02:00Z", + "received_at": "2026-11-12T19:02:05Z", + "claim_in_future": false, + "answerer": "e0d1c2b3-a495-4687-8879-6a5b4c3d2e1f", + "authority": "Operator", + "request_id": "00000001-0000-4000-8000-000000000001" + }, + "card": null + } + } + ], + "record": { + "schemaVersion": "gamecult.fork.record.v0", + "fork_id": "gamecult-chat:question:slot-mark-consent", + "options_hash": "7d95fc87d532420611fe415c1def27a3447617bb68235f46e3af130aea361c48", + "counted_answer_id": "n1", + "answers": [ + { + "schemaVersion": "gamecult.desk_answer.v0", + "answer_id": "n1", + "fork_id": "gamecult-chat:question:slot-mark-consent", + "outcome": "Counted", + "of": null, + "retracts": null, + "code": null, + "options_hash": "7d95fc87d532420611fe415c1def27a3447617bb68235f46e3af130aea361c48", + "choice": "no", + "note": "Keep it local: a slot mark is the author's call, and sharing it is a separate act.", + "answered_at": "2026-11-12T19:02:00Z", + "received_at": "2026-11-12T19:02:05Z", + "claim_in_future": false, + "answerer": "e0d1c2b3-a495-4687-8879-6a5b4c3d2e1f", + "authority": "Operator", + "request_id": "00000001-0000-4000-8000-000000000001" + } + ] + }, + "recordHex": "85AD736368656D6156657273696F6EB767616D6563756C742E666F726B2E7265636F72642E7630A7666F726B5F6964D92867616D6563756C742D636861743A7175657374696F6E3A736C6F742D6D61726B2D636F6E73656E74AC6F7074696F6E735F68617368D94037643935666338376435333234323036313166653431356331646566323761333434373631376262363832333566343665336166313330616561333631633438B1636F756E7465645F616E737765725F6964A26E31A7616E737765727391DE0010AD736368656D6156657273696F6EB767616D6563756C742E6465736B5F616E737765722E7630A9616E737765725F6964A26E31A7666F726B5F6964D92867616D6563756C742D636861743A7175657374696F6E3A736C6F742D6D61726B2D636F6E73656E74A76F7574636F6D65A7436F756E746564A26F66C0A87265747261637473C0A4636F6465C0AC6F7074696F6E735F68617368D94037643935666338376435333234323036313166653431356331646566323761333434373631376262363832333566343665336166313330616561333631633438A663686F696365A26E6FA46E6F7465D9524B656570206974206C6F63616C3A206120736C6F74206D61726B2069732074686520617574686F7227732063616C6C2C20616E642073686172696E672069742069732061207365706172617465206163742EAB616E7377657265645F6174B4323032362D31312D31325431393A30323A30305AAB72656365697665645F6174B4323032362D31312D31325431393A30323A30355AAF636C61696D5F696E5F667574757265C2A8616E737765726572D92465306431633262332D613439352D343638372D383837392D366135623463336432653166A9617574686F72697479A84F70657261746F72AA726571756573745F6964D92430303030303030312D303030302D343030302D383030302D303030303030303030303031" + }, + { + "name": "the_same_request_id_with_another_body_is_request_id_conflict", + "card": { + "schemaVersion": "gamecult.fork.card.v0", + "fork_id": "gamecult-chat:question:slot-mark-consent", + "title": "Slot mark consent", + "context": "Should a slot mark be shared?", + "options": [ + { + "id": "yes", + "text": "Share it." + }, + { + "id": "no", + "text": "Keep it local." + } + ], + "recommendation": "no", + "depends": [ + "gamecult-chat:question:file-zone-audience" + ], + "leans": null, + "flag": null, + "raised_in": { + "kind": "CutSpec", + "id": "cultlib-gaps:cut_spec:cut-slot-tolerance.r4" + }, + "deciders": [ + "e0d1c2b3-a495-4687-8879-6a5b4c3d2e1f" + ], + "status": "open", + "ruling": null, + "ruled_from": [], + "order": 3, + "options_hash": "7d95fc87d532420611fe415c1def27a3447617bb68235f46e3af130aea361c48" + }, + "optionsHash": "7d95fc87d532420611fe415c1def27a3447617bb68235f46e3af130aea361c48", + "steps": [ + { + "kind": "answer", + "body": { + "schemaVersion": "gamecult.fork.answer_request.v0", + "fork_id": "gamecult-chat:question:slot-mark-consent", + "options_hash": "7d95fc87d532420611fe415c1def27a3447617bb68235f46e3af130aea361c48", + "answer": { + "choice": "no", + "note": "Keep it local: a slot mark is the author's call, and sharing it is a separate act." + }, + "retracted": false, + "retracts": null, + "answered_at": "2026-11-12T19:02:00Z", + "request_id": "00000001-0000-4000-8000-000000000001" + }, + "server": { + "answer_id": "n1", + "answerer": "e0d1c2b3-a495-4687-8879-6a5b4c3d2e1f", + "authority": "Operator", + "received_at": "2026-11-12T19:02:05Z" + }, + "response": { + "schemaVersion": "gamecult.fork.answer_result.v0", + "answer": { + "schemaVersion": "gamecult.desk_answer.v0", + "answer_id": "n1", + "fork_id": "gamecult-chat:question:slot-mark-consent", + "outcome": "Counted", + "of": null, + "retracts": null, + "code": null, + "options_hash": "7d95fc87d532420611fe415c1def27a3447617bb68235f46e3af130aea361c48", + "choice": "no", + "note": "Keep it local: a slot mark is the author's call, and sharing it is a separate act.", + "answered_at": "2026-11-12T19:02:00Z", + "received_at": "2026-11-12T19:02:05Z", + "claim_in_future": false, + "answerer": "e0d1c2b3-a495-4687-8879-6a5b4c3d2e1f", + "authority": "Operator", + "request_id": "00000001-0000-4000-8000-000000000001" + }, + "card": null + } + }, + { + "kind": "answer", + "body": { + "schemaVersion": "gamecult.fork.answer_request.v0", + "fork_id": "gamecult-chat:question:slot-mark-consent", + "options_hash": "7d95fc87d532420611fe415c1def27a3447617bb68235f46e3af130aea361c48", + "answer": { + "choice": "yes", + "note": null + }, + "retracted": false, + "retracts": null, + "answered_at": "2026-11-12T19:02:00Z", + "request_id": "00000001-0000-4000-8000-000000000001" + }, + "server": { + "answer_id": "n9", + "answerer": "e0d1c2b3-a495-4687-8879-6a5b4c3d2e1f", + "authority": "Operator", + "received_at": "2026-11-12T19:02:05Z" + }, + "response": { + "schemaVersion": "gamecult.fork.refusal.v0", + "code": "request_id_conflict", + "error": "request_id_conflict: this request_id was used for a different body" + } + } + ], + "record": { + "schemaVersion": "gamecult.fork.record.v0", + "fork_id": "gamecult-chat:question:slot-mark-consent", + "options_hash": "7d95fc87d532420611fe415c1def27a3447617bb68235f46e3af130aea361c48", + "counted_answer_id": "n1", + "answers": [ + { + "schemaVersion": "gamecult.desk_answer.v0", + "answer_id": "n1", + "fork_id": "gamecult-chat:question:slot-mark-consent", + "outcome": "Counted", + "of": null, + "retracts": null, + "code": null, + "options_hash": "7d95fc87d532420611fe415c1def27a3447617bb68235f46e3af130aea361c48", + "choice": "no", + "note": "Keep it local: a slot mark is the author's call, and sharing it is a separate act.", + "answered_at": "2026-11-12T19:02:00Z", + "received_at": "2026-11-12T19:02:05Z", + "claim_in_future": false, + "answerer": "e0d1c2b3-a495-4687-8879-6a5b4c3d2e1f", + "authority": "Operator", + "request_id": "00000001-0000-4000-8000-000000000001" + } + ] + }, + "recordHex": "85AD736368656D6156657273696F6EB767616D6563756C742E666F726B2E7265636F72642E7630A7666F726B5F6964D92867616D6563756C742D636861743A7175657374696F6E3A736C6F742D6D61726B2D636F6E73656E74AC6F7074696F6E735F68617368D94037643935666338376435333234323036313166653431356331646566323761333434373631376262363832333566343665336166313330616561333631633438B1636F756E7465645F616E737765725F6964A26E31A7616E737765727391DE0010AD736368656D6156657273696F6EB767616D6563756C742E6465736B5F616E737765722E7630A9616E737765725F6964A26E31A7666F726B5F6964D92867616D6563756C742D636861743A7175657374696F6E3A736C6F742D6D61726B2D636F6E73656E74A76F7574636F6D65A7436F756E746564A26F66C0A87265747261637473C0A4636F6465C0AC6F7074696F6E735F68617368D94037643935666338376435333234323036313166653431356331646566323761333434373631376262363832333566343665336166313330616561333631633438A663686F696365A26E6FA46E6F7465D9524B656570206974206C6F63616C3A206120736C6F74206D61726B2069732074686520617574686F7227732063616C6C2C20616E642073686172696E672069742069732061207365706172617465206163742EAB616E7377657265645F6174B4323032362D31312D31325431393A30323A30305AAB72656365697665645F6174B4323032362D31312D31325431393A30323A30355AAF636C61696D5F696E5F667574757265C2A8616E737765726572D92465306431633262332D613439352D343638372D383837392D366135623463336432653166A9617574686F72697479A84F70657261746F72AA726571756573745F6964D92430303030303030312D303030302D343030302D383030302D303030303030303030303031" + }, + { + "name": "the_same_request_id_from_another_account_is_another_request", + "card": { + "schemaVersion": "gamecult.fork.card.v0", + "fork_id": "gamecult-chat:question:slot-mark-consent", + "title": "Slot mark consent", + "context": "Should a slot mark be shared?", + "options": [ + { + "id": "yes", + "text": "Share it." + }, + { + "id": "no", + "text": "Keep it local." + } + ], + "recommendation": "no", + "depends": [ + "gamecult-chat:question:file-zone-audience" + ], + "leans": null, + "flag": null, + "raised_in": { + "kind": "CutSpec", + "id": "cultlib-gaps:cut_spec:cut-slot-tolerance.r4" + }, + "deciders": [ + "e0d1c2b3-a495-4687-8879-6a5b4c3d2e1f", + "9f8e7d6c-5b4a-4392-8170-0f1e2d3c4b5a" + ], + "status": "open", + "ruling": null, + "ruled_from": [], + "order": 3, + "options_hash": "7d95fc87d532420611fe415c1def27a3447617bb68235f46e3af130aea361c48" + }, + "optionsHash": "7d95fc87d532420611fe415c1def27a3447617bb68235f46e3af130aea361c48", + "steps": [ + { + "kind": "answer", + "body": { + "schemaVersion": "gamecult.fork.answer_request.v0", + "fork_id": "gamecult-chat:question:slot-mark-consent", + "options_hash": "7d95fc87d532420611fe415c1def27a3447617bb68235f46e3af130aea361c48", + "answer": { + "choice": "no", + "note": "Keep it local: a slot mark is the author's call, and sharing it is a separate act." + }, + "retracted": false, + "retracts": null, + "answered_at": "2026-11-12T19:02:00Z", + "request_id": "00000001-0000-4000-8000-000000000001" + }, + "server": { + "answer_id": "n1", + "answerer": "e0d1c2b3-a495-4687-8879-6a5b4c3d2e1f", + "authority": "Operator", + "received_at": "2026-11-12T19:02:05Z" + }, + "response": { + "schemaVersion": "gamecult.fork.answer_result.v0", + "answer": { + "schemaVersion": "gamecult.desk_answer.v0", + "answer_id": "n1", + "fork_id": "gamecult-chat:question:slot-mark-consent", + "outcome": "Counted", + "of": null, + "retracts": null, + "code": null, + "options_hash": "7d95fc87d532420611fe415c1def27a3447617bb68235f46e3af130aea361c48", + "choice": "no", + "note": "Keep it local: a slot mark is the author's call, and sharing it is a separate act.", + "answered_at": "2026-11-12T19:02:00Z", + "received_at": "2026-11-12T19:02:05Z", + "claim_in_future": false, + "answerer": "e0d1c2b3-a495-4687-8879-6a5b4c3d2e1f", + "authority": "Operator", + "request_id": "00000001-0000-4000-8000-000000000001" + }, + "card": null + } + }, + { + "kind": "answer", + "body": { + "schemaVersion": "gamecult.fork.answer_request.v0", + "fork_id": "gamecult-chat:question:slot-mark-consent", + "options_hash": "7d95fc87d532420611fe415c1def27a3447617bb68235f46e3af130aea361c48", + "answer": { + "choice": "yes", + "note": "Share it, the audience is settled." + }, + "retracted": false, + "retracts": null, + "answered_at": "2026-11-12T19:05:00Z", + "request_id": "00000001-0000-4000-8000-000000000001" + }, + "server": { + "answer_id": "n3", + "answerer": "9f8e7d6c-5b4a-4392-8170-0f1e2d3c4b5a", + "authority": "Steward", + "received_at": "2026-11-12T19:05:05Z" + }, + "response": { + "schemaVersion": "gamecult.fork.answer_result.v0", + "answer": { + "schemaVersion": "gamecult.desk_answer.v0", + "answer_id": "n3", + "fork_id": "gamecult-chat:question:slot-mark-consent", + "outcome": "Revision", + "of": "n1", + "retracts": null, + "code": null, + "options_hash": "7d95fc87d532420611fe415c1def27a3447617bb68235f46e3af130aea361c48", + "choice": "yes", + "note": "Share it, the audience is settled.", + "answered_at": "2026-11-12T19:05:00Z", + "received_at": "2026-11-12T19:05:05Z", + "claim_in_future": false, + "answerer": "9f8e7d6c-5b4a-4392-8170-0f1e2d3c4b5a", + "authority": "Steward", + "request_id": "00000001-0000-4000-8000-000000000001" + }, + "card": null + } + } + ], + "record": { + "schemaVersion": "gamecult.fork.record.v0", + "fork_id": "gamecult-chat:question:slot-mark-consent", + "options_hash": "7d95fc87d532420611fe415c1def27a3447617bb68235f46e3af130aea361c48", + "counted_answer_id": "n1", + "answers": [ + { + "schemaVersion": "gamecult.desk_answer.v0", + "answer_id": "n1", + "fork_id": "gamecult-chat:question:slot-mark-consent", + "outcome": "Counted", + "of": null, + "retracts": null, + "code": null, + "options_hash": "7d95fc87d532420611fe415c1def27a3447617bb68235f46e3af130aea361c48", + "choice": "no", + "note": "Keep it local: a slot mark is the author's call, and sharing it is a separate act.", + "answered_at": "2026-11-12T19:02:00Z", + "received_at": "2026-11-12T19:02:05Z", + "claim_in_future": false, + "answerer": "e0d1c2b3-a495-4687-8879-6a5b4c3d2e1f", + "authority": "Operator", + "request_id": "00000001-0000-4000-8000-000000000001" + }, + { + "schemaVersion": "gamecult.desk_answer.v0", + "answer_id": "n3", + "fork_id": "gamecult-chat:question:slot-mark-consent", + "outcome": "Revision", + "of": "n1", + "retracts": null, + "code": null, + "options_hash": "7d95fc87d532420611fe415c1def27a3447617bb68235f46e3af130aea361c48", + "choice": "yes", + "note": "Share it, the audience is settled.", + "answered_at": "2026-11-12T19:05:00Z", + "received_at": "2026-11-12T19:05:05Z", + "claim_in_future": false, + "answerer": "9f8e7d6c-5b4a-4392-8170-0f1e2d3c4b5a", + "authority": "Steward", + "request_id": "00000001-0000-4000-8000-000000000001" + } + ] + }, + "recordHex": "85AD736368656D6156657273696F6EB767616D6563756C742E666F726B2E7265636F72642E7630A7666F726B5F6964D92867616D6563756C742D636861743A7175657374696F6E3A736C6F742D6D61726B2D636F6E73656E74AC6F7074696F6E735F68617368D94037643935666338376435333234323036313166653431356331646566323761333434373631376262363832333566343665336166313330616561333631633438B1636F756E7465645F616E737765725F6964A26E31A7616E737765727392DE0010AD736368656D6156657273696F6EB767616D6563756C742E6465736B5F616E737765722E7630A9616E737765725F6964A26E31A7666F726B5F6964D92867616D6563756C742D636861743A7175657374696F6E3A736C6F742D6D61726B2D636F6E73656E74A76F7574636F6D65A7436F756E746564A26F66C0A87265747261637473C0A4636F6465C0AC6F7074696F6E735F68617368D94037643935666338376435333234323036313166653431356331646566323761333434373631376262363832333566343665336166313330616561333631633438A663686F696365A26E6FA46E6F7465D9524B656570206974206C6F63616C3A206120736C6F74206D61726B2069732074686520617574686F7227732063616C6C2C20616E642073686172696E672069742069732061207365706172617465206163742EAB616E7377657265645F6174B4323032362D31312D31325431393A30323A30305AAB72656365697665645F6174B4323032362D31312D31325431393A30323A30355AAF636C61696D5F696E5F667574757265C2A8616E737765726572D92465306431633262332D613439352D343638372D383837392D366135623463336432653166A9617574686F72697479A84F70657261746F72AA726571756573745F6964D92430303030303030312D303030302D343030302D383030302D303030303030303030303031DE0010AD736368656D6156657273696F6EB767616D6563756C742E6465736B5F616E737765722E7630A9616E737765725F6964A26E33A7666F726B5F6964D92867616D6563756C742D636861743A7175657374696F6E3A736C6F742D6D61726B2D636F6E73656E74A76F7574636F6D65A85265766973696F6EA26F66A26E31A87265747261637473C0A4636F6465C0AC6F7074696F6E735F68617368D94037643935666338376435333234323036313166653431356331646566323761333434373631376262363832333566343665336166313330616561333631633438A663686F696365A3796573A46E6F7465D92253686172652069742C207468652061756469656E636520697320736574746C65642EAB616E7377657265645F6174B4323032362D31312D31325431393A30353A30305AAB72656365697665645F6174B4323032362D31312D31325431393A30353A30355AAF636C61696D5F696E5F667574757265C2A8616E737765726572D92439663865376436632D356234612D343339322D383137302D306631653264336334623561A9617574686F72697479A753746577617264AA726571756573745F6964D92430303030303030312D303030302D343030302D383030302D303030303030303030303031" + }, + { + "name": "a_non_active_member_is_forbidden_before_the_body_is_read", + "card": { + "schemaVersion": "gamecult.fork.card.v0", + "fork_id": "gamecult-chat:question:slot-mark-consent", + "title": "Slot mark consent", + "context": "Should a slot mark be shared?", + "options": [ + { + "id": "yes", + "text": "Share it." + }, + { + "id": "no", + "text": "Keep it local." + } + ], + "recommendation": "no", + "depends": [ + "gamecult-chat:question:file-zone-audience" + ], + "leans": null, + "flag": null, + "raised_in": { + "kind": "CutSpec", + "id": "cultlib-gaps:cut_spec:cut-slot-tolerance.r4" + }, + "deciders": [ + "e0d1c2b3-a495-4687-8879-6a5b4c3d2e1f" + ], + "status": "open", + "ruling": null, + "ruled_from": [], + "order": 3, + "options_hash": "7d95fc87d532420611fe415c1def27a3447617bb68235f46e3af130aea361c48" + }, + "optionsHash": "7d95fc87d532420611fe415c1def27a3447617bb68235f46e3af130aea361c48", + "steps": [ + { + "kind": "answer", + "body": { + "schemaVersion": "gamecult.fork.answer_request.v0", + "fork_id": "gamecult-chat:question:slot-mark-consent", + "options_hash": "7d95fc87d532420611fe415c1def27a3447617bb68235f46e3af130aea361c48", + "answer": { + "choice": "no", + "note": "Keep it local: a slot mark is the author's call, and sharing it is a separate act." + }, + "retracted": false, + "retracts": null, + "answered_at": "2026-11-12T19:02:00Z", + "request_id": "00000001-0000-4000-8000-000000000001", + "answerer": "x" + }, + "server": { + "answer_id": "n1", + "answerer": "e0d1c2b3-a495-4687-8879-6a5b4c3d2e1f", + "authority": "Operator", + "received_at": "2026-11-12T19:02:05Z", + "active": false + }, + "response": { + "schemaVersion": "gamecult.fork.refusal.v0", + "code": "forbidden", + "error": "forbidden: only Active members may use the desk" + } + }, + { + "kind": "answer", + "body": { + "schemaVersion": "gamecult.fork.answer_request.v0", + "fork_id": "gamecult-chat:question:slot-mark-consent", + "options_hash": "7d95fc87d532420611fe415c1def27a3447617bb68235f46e3af130aea361c48", + "answer": { + "choice": "no", + "note": "Keep it local: a slot mark is the author's call, and sharing it is a separate act." + }, + "retracted": false, + "retracts": null, + "answered_at": "2026-11-12T19:02:00Z", + "request_id": "00000001-0000-4000-8000-000000000001" + }, + "server": { + "answer_id": "n1", + "answerer": "e0d1c2b3-a495-4687-8879-6a5b4c3d2e1f", + "authority": "Operator", + "received_at": "2026-11-12T19:02:05Z", + "active": false + }, + "response": { + "schemaVersion": "gamecult.fork.refusal.v0", + "code": "forbidden", + "error": "forbidden: only Active members may use the desk" + } + }, + { + "kind": "answer", + "body": { + "schemaVersion": "gamecult.fork.answer_request.v0", + "fork_id": "gamecult-chat:question:slot-mark-consent", + "options_hash": "7d95fc87d532420611fe415c1def27a3447617bb68235f46e3af130aea361c48", + "answer": { + "choice": "no", + "note": "Keep it local: a slot mark is the author's call, and sharing it is a separate act." + }, + "retracted": false, + "retracts": null, + "answered_at": "2026-11-12T19:02:00Z", + "request_id": "00000001-0000-4000-8000-000000000001", + "mood": "happy" + }, + "server": { + "answer_id": "n1", + "answerer": "e0d1c2b3-a495-4687-8879-6a5b4c3d2e1f", + "authority": "Operator", + "received_at": "2026-11-12T19:02:05Z", + "active": false, + "door_down": true, + "retry_after_seconds": 30 + }, + "response": { + "schemaVersion": "gamecult.fork.refusal.v0", + "code": "forbidden", + "error": "forbidden: only Active members may use the desk" + } + } + ], + "record": { + "schemaVersion": "gamecult.fork.record.v0", + "fork_id": "gamecult-chat:question:slot-mark-consent", + "options_hash": "7d95fc87d532420611fe415c1def27a3447617bb68235f46e3af130aea361c48", + "counted_answer_id": null, + "answers": [] + }, + "recordHex": "85AD736368656D6156657273696F6EB767616D6563756C742E666F726B2E7265636F72642E7630A7666F726B5F6964D92867616D6563756C742D636861743A7175657374696F6E3A736C6F742D6D61726B2D636F6E73656E74AC6F7074696F6E735F68617368D94037643935666338376435333234323036313166653431356331646566323761333434373631376262363832333566343665336166313330616561333631633438B1636F756E7465645F616E737765725F6964C0A7616E737765727390" + }, + { + "name": "no_session_is_unauthenticated", + "card": { + "schemaVersion": "gamecult.fork.card.v0", + "fork_id": "gamecult-chat:question:slot-mark-consent", + "title": "Slot mark consent", + "context": "Should a slot mark be shared?", + "options": [ + { + "id": "yes", + "text": "Share it." + }, + { + "id": "no", + "text": "Keep it local." + } + ], + "recommendation": "no", + "depends": [ + "gamecult-chat:question:file-zone-audience" + ], + "leans": null, + "flag": null, + "raised_in": { + "kind": "CutSpec", + "id": "cultlib-gaps:cut_spec:cut-slot-tolerance.r4" + }, + "deciders": [ + "e0d1c2b3-a495-4687-8879-6a5b4c3d2e1f" + ], + "status": "open", + "ruling": null, + "ruled_from": [], + "order": 3, + "options_hash": "7d95fc87d532420611fe415c1def27a3447617bb68235f46e3af130aea361c48" + }, + "optionsHash": "7d95fc87d532420611fe415c1def27a3447617bb68235f46e3af130aea361c48", + "steps": [ + { + "kind": "answer", + "body": { + "schemaVersion": "gamecult.fork.answer_request.v0", + "fork_id": "gamecult-chat:question:slot-mark-consent", + "options_hash": "7d95fc87d532420611fe415c1def27a3447617bb68235f46e3af130aea361c48", + "answer": { + "choice": "no", + "note": "Keep it local: a slot mark is the author's call, and sharing it is a separate act." + }, + "retracted": false, + "retracts": null, + "answered_at": "2026-11-12T19:02:00Z", + "request_id": "00000001-0000-4000-8000-000000000001" + }, + "server": { + "answer_id": "n1", + "answerer": null, + "authority": null, + "received_at": "2026-11-12T19:02:05Z" + }, + "response": { + "schemaVersion": "gamecult.fork.refusal.v0", + "code": "unauthenticated", + "error": "unauthenticated: a verified session is required" + } + } + ], + "record": { + "schemaVersion": "gamecult.fork.record.v0", + "fork_id": "gamecult-chat:question:slot-mark-consent", + "options_hash": "7d95fc87d532420611fe415c1def27a3447617bb68235f46e3af130aea361c48", + "counted_answer_id": null, + "answers": [] + }, + "recordHex": "85AD736368656D6156657273696F6EB767616D6563756C742E666F726B2E7265636F72642E7630A7666F726B5F6964D92867616D6563756C742D636861743A7175657374696F6E3A736C6F742D6D61726B2D636F6E73656E74AC6F7074696F6E735F68617368D94037643935666338376435333234323036313166653431356331646566323761333434373631376262363832333566343665336166313330616561333631633438B1636F756E7465645F616E737765725F6964C0A7616E737765727390" + }, + { + "name": "an_unknown_fork_is_refused_at_the_door", + "card": { + "schemaVersion": "gamecult.fork.card.v0", + "fork_id": "gamecult-chat:question:slot-mark-consent", + "title": "Slot mark consent", + "context": "Should a slot mark be shared?", + "options": [ + { + "id": "yes", + "text": "Share it." + }, + { + "id": "no", + "text": "Keep it local." + } + ], + "recommendation": "no", + "depends": [ + "gamecult-chat:question:file-zone-audience" + ], + "leans": null, + "flag": null, + "raised_in": { + "kind": "CutSpec", + "id": "cultlib-gaps:cut_spec:cut-slot-tolerance.r4" + }, + "deciders": [ + "e0d1c2b3-a495-4687-8879-6a5b4c3d2e1f" + ], + "status": "open", + "ruling": null, + "ruled_from": [], + "order": 3, + "options_hash": "7d95fc87d532420611fe415c1def27a3447617bb68235f46e3af130aea361c48" + }, + "optionsHash": "7d95fc87d532420611fe415c1def27a3447617bb68235f46e3af130aea361c48", + "steps": [ + { + "kind": "answer", + "body": { + "schemaVersion": "gamecult.fork.answer_request.v0", + "fork_id": "gamecult-chat:question:nope", + "options_hash": "7d95fc87d532420611fe415c1def27a3447617bb68235f46e3af130aea361c48", + "answer": { + "choice": "no", + "note": "Keep it local: a slot mark is the author's call, and sharing it is a separate act." + }, + "retracted": false, + "retracts": null, + "answered_at": "2026-11-12T19:02:00Z", + "request_id": "0000000a-0000-4000-8000-00000000000a" + }, + "server": { + "answer_id": "n1", + "answerer": "e0d1c2b3-a495-4687-8879-6a5b4c3d2e1f", + "authority": "Operator", + "received_at": "2026-11-12T19:02:05Z" + }, + "response": { + "schemaVersion": "gamecult.fork.refusal.v0", + "code": "fork_unknown", + "error": "fork_unknown: no such fork" + } + } + ], + "record": { + "schemaVersion": "gamecult.fork.record.v0", + "fork_id": "gamecult-chat:question:slot-mark-consent", + "options_hash": "7d95fc87d532420611fe415c1def27a3447617bb68235f46e3af130aea361c48", + "counted_answer_id": null, + "answers": [] + }, + "recordHex": "85AD736368656D6156657273696F6EB767616D6563756C742E666F726B2E7265636F72642E7630A7666F726B5F6964D92867616D6563756C742D636861743A7175657374696F6E3A736C6F742D6D61726B2D636F6E73656E74AC6F7074696F6E735F68617368D94037643935666338376435333234323036313166653431356331646566323761333434373631376262363832333566343665336166313330616561333631633438B1636F756E7465645F616E737765725F6964C0A7616E737765727390" + }, + { + "name": "V2_stale_hash_is_refused_not_counted_and_stored_as_Refused", + "card": { + "schemaVersion": "gamecult.fork.card.v0", + "fork_id": "gamecult-chat:question:slot-mark-consent", + "title": "Slot mark consent", + "context": "Should a slot mark be shared?", + "options": [ + { + "id": "yes", + "text": "Share it." + }, + { + "id": "no", + "text": "Keep it local." + } + ], + "recommendation": "no", + "depends": [ + "gamecult-chat:question:file-zone-audience" + ], + "leans": null, + "flag": null, + "raised_in": { + "kind": "CutSpec", + "id": "cultlib-gaps:cut_spec:cut-slot-tolerance.r4" + }, + "deciders": [ + "e0d1c2b3-a495-4687-8879-6a5b4c3d2e1f" + ], + "status": "open", + "ruling": null, + "ruled_from": [], + "order": 3, + "options_hash": "7d95fc87d532420611fe415c1def27a3447617bb68235f46e3af130aea361c48" + }, + "optionsHash": "7d95fc87d532420611fe415c1def27a3447617bb68235f46e3af130aea361c48", + "steps": [ + { + "kind": "answer", + "body": { + "schemaVersion": "gamecult.fork.answer_request.v0", + "fork_id": "gamecult-chat:question:slot-mark-consent", + "options_hash": "14b8ffbdb61106dbc06f69f3fc8e2072af7a205d766e4cc040e89733013d54dc", + "answer": { + "choice": "no", + "note": "Keep it local: a slot mark is the author's call, and sharing it is a separate act." + }, + "retracted": false, + "retracts": null, + "answered_at": "2026-11-12T19:02:00Z", + "request_id": "00000007-0000-4000-8000-000000000007" + }, + "server": { + "answer_id": "n1", + "answerer": "e0d1c2b3-a495-4687-8879-6a5b4c3d2e1f", + "authority": "Operator", + "received_at": "2026-11-12T19:02:05Z" + }, + "response": { + "schemaVersion": "gamecult.fork.answer_result.v0", + "answer": { + "schemaVersion": "gamecult.desk_answer.v0", + "answer_id": "n1", + "fork_id": "gamecult-chat:question:slot-mark-consent", + "outcome": "Refused", + "of": null, + "retracts": null, + "code": "stale_fork", + "options_hash": "14b8ffbdb61106dbc06f69f3fc8e2072af7a205d766e4cc040e89733013d54dc", + "choice": "no", + "note": "Keep it local: a slot mark is the author's call, and sharing it is a separate act.", + "answered_at": "2026-11-12T19:02:00Z", + "received_at": "2026-11-12T19:02:05Z", + "claim_in_future": false, + "answerer": "e0d1c2b3-a495-4687-8879-6a5b4c3d2e1f", + "authority": "Operator", + "request_id": "00000007-0000-4000-8000-000000000007" + }, + "card": { + "schemaVersion": "gamecult.fork.card.v0", + "fork_id": "gamecult-chat:question:slot-mark-consent", + "title": "Slot mark consent", + "context": "Should a slot mark be shared?", + "options": [ + { + "id": "yes", + "text": "Share it." + }, + { + "id": "no", + "text": "Keep it local." + } + ], + "recommendation": "no", + "depends": [ + "gamecult-chat:question:file-zone-audience" + ], + "leans": null, + "flag": null, + "raised_in": { + "kind": "CutSpec", + "id": "cultlib-gaps:cut_spec:cut-slot-tolerance.r4" + }, + "deciders": [ + "e0d1c2b3-a495-4687-8879-6a5b4c3d2e1f" + ], + "status": "open", + "ruling": null, + "ruled_from": [], + "order": 3, + "options_hash": "7d95fc87d532420611fe415c1def27a3447617bb68235f46e3af130aea361c48" + } + } + } + ], + "record": { + "schemaVersion": "gamecult.fork.record.v0", + "fork_id": "gamecult-chat:question:slot-mark-consent", + "options_hash": "7d95fc87d532420611fe415c1def27a3447617bb68235f46e3af130aea361c48", + "counted_answer_id": null, + "answers": [ + { + "schemaVersion": "gamecult.desk_answer.v0", + "answer_id": "n1", + "fork_id": "gamecult-chat:question:slot-mark-consent", + "outcome": "Refused", + "of": null, + "retracts": null, + "code": "stale_fork", + "options_hash": "14b8ffbdb61106dbc06f69f3fc8e2072af7a205d766e4cc040e89733013d54dc", + "choice": "no", + "note": "Keep it local: a slot mark is the author's call, and sharing it is a separate act.", + "answered_at": "2026-11-12T19:02:00Z", + "received_at": "2026-11-12T19:02:05Z", + "claim_in_future": false, + "answerer": "e0d1c2b3-a495-4687-8879-6a5b4c3d2e1f", + "authority": "Operator", + "request_id": "00000007-0000-4000-8000-000000000007" + } + ] + }, + "recordHex": "85AD736368656D6156657273696F6EB767616D6563756C742E666F726B2E7265636F72642E7630A7666F726B5F6964D92867616D6563756C742D636861743A7175657374696F6E3A736C6F742D6D61726B2D636F6E73656E74AC6F7074696F6E735F68617368D94037643935666338376435333234323036313166653431356331646566323761333434373631376262363832333566343665336166313330616561333631633438B1636F756E7465645F616E737765725F6964C0A7616E737765727391DE0010AD736368656D6156657273696F6EB767616D6563756C742E6465736B5F616E737765722E7630A9616E737765725F6964A26E31A7666F726B5F6964D92867616D6563756C742D636861743A7175657374696F6E3A736C6F742D6D61726B2D636F6E73656E74A76F7574636F6D65A752656675736564A26F66C0A87265747261637473C0A4636F6465AA7374616C655F666F726BAC6F7074696F6E735F68617368D94031346238666662646236313130366462633036663639663366633865323037326166376132303564373636653463633034306538393733333031336435346463A663686F696365A26E6FA46E6F7465D9524B656570206974206C6F63616C3A206120736C6F74206D61726B2069732074686520617574686F7227732063616C6C2C20616E642073686172696E672069742069732061207365706172617465206163742EAB616E7377657265645F6174B4323032362D31312D31325431393A30323A30305AAB72656365697665645F6174B4323032362D31312D31325431393A30323A30355AAF636C61696D5F696E5F667574757265C2A8616E737765726572D92465306431633262332D613439352D343638372D383837392D366135623463336432653166A9617574686F72697479A84F70657261746F72AA726571756573745F6964D92430303030303030372D303030302D343030302D383030302D303030303030303030303037" + }, + { + "name": "V2_a_stale_answer_then_a_fresh_one_the_fresh_one_is_counted", + "card": { + "schemaVersion": "gamecult.fork.card.v0", + "fork_id": "gamecult-chat:question:slot-mark-consent", + "title": "Slot mark consent", + "context": "Should a slot mark be shared?", + "options": [ + { + "id": "yes", + "text": "Share it." + }, + { + "id": "no", + "text": "Keep it local." + } + ], + "recommendation": "no", + "depends": [ + "gamecult-chat:question:file-zone-audience" + ], + "leans": null, + "flag": null, + "raised_in": { + "kind": "CutSpec", + "id": "cultlib-gaps:cut_spec:cut-slot-tolerance.r4" + }, + "deciders": [ + "e0d1c2b3-a495-4687-8879-6a5b4c3d2e1f" + ], + "status": "open", + "ruling": null, + "ruled_from": [], + "order": 3, + "options_hash": "7d95fc87d532420611fe415c1def27a3447617bb68235f46e3af130aea361c48" + }, + "optionsHash": "7d95fc87d532420611fe415c1def27a3447617bb68235f46e3af130aea361c48", + "steps": [ + { + "kind": "answer", + "body": { + "schemaVersion": "gamecult.fork.answer_request.v0", + "fork_id": "gamecult-chat:question:slot-mark-consent", + "options_hash": "14b8ffbdb61106dbc06f69f3fc8e2072af7a205d766e4cc040e89733013d54dc", + "answer": { + "choice": "no", + "note": "Keep it local: a slot mark is the author's call, and sharing it is a separate act." + }, + "retracted": false, + "retracts": null, + "answered_at": "2026-11-12T19:02:00Z", + "request_id": "0000000b-0000-4000-8000-00000000000b" + }, + "server": { + "answer_id": "n1", + "answerer": "e0d1c2b3-a495-4687-8879-6a5b4c3d2e1f", + "authority": "Operator", + "received_at": "2026-11-12T19:02:05Z" + }, + "response": { + "schemaVersion": "gamecult.fork.answer_result.v0", + "answer": { + "schemaVersion": "gamecult.desk_answer.v0", + "answer_id": "n1", + "fork_id": "gamecult-chat:question:slot-mark-consent", + "outcome": "Refused", + "of": null, + "retracts": null, + "code": "stale_fork", + "options_hash": "14b8ffbdb61106dbc06f69f3fc8e2072af7a205d766e4cc040e89733013d54dc", + "choice": "no", + "note": "Keep it local: a slot mark is the author's call, and sharing it is a separate act.", + "answered_at": "2026-11-12T19:02:00Z", + "received_at": "2026-11-12T19:02:05Z", + "claim_in_future": false, + "answerer": "e0d1c2b3-a495-4687-8879-6a5b4c3d2e1f", + "authority": "Operator", + "request_id": "0000000b-0000-4000-8000-00000000000b" + }, + "card": { + "schemaVersion": "gamecult.fork.card.v0", + "fork_id": "gamecult-chat:question:slot-mark-consent", + "title": "Slot mark consent", + "context": "Should a slot mark be shared?", + "options": [ + { + "id": "yes", + "text": "Share it." + }, + { + "id": "no", + "text": "Keep it local." + } + ], + "recommendation": "no", + "depends": [ + "gamecult-chat:question:file-zone-audience" + ], + "leans": null, + "flag": null, + "raised_in": { + "kind": "CutSpec", + "id": "cultlib-gaps:cut_spec:cut-slot-tolerance.r4" + }, + "deciders": [ + "e0d1c2b3-a495-4687-8879-6a5b4c3d2e1f" + ], + "status": "open", + "ruling": null, + "ruled_from": [], + "order": 3, + "options_hash": "7d95fc87d532420611fe415c1def27a3447617bb68235f46e3af130aea361c48" + } + } + }, + { + "kind": "answer", + "body": { + "schemaVersion": "gamecult.fork.answer_request.v0", + "fork_id": "gamecult-chat:question:slot-mark-consent", + "options_hash": "7d95fc87d532420611fe415c1def27a3447617bb68235f46e3af130aea361c48", + "answer": { + "choice": "no", + "note": "Keep it local: a slot mark is the author's call, and sharing it is a separate act." + }, + "retracted": false, + "retracts": null, + "answered_at": "2026-11-12T19:02:00Z", + "request_id": "00000001-0000-4000-8000-000000000001" + }, + "server": { + "answer_id": "n2", + "answerer": "e0d1c2b3-a495-4687-8879-6a5b4c3d2e1f", + "authority": "Operator", + "received_at": "2026-11-12T19:06:05Z" + }, + "response": { + "schemaVersion": "gamecult.fork.answer_result.v0", + "answer": { + "schemaVersion": "gamecult.desk_answer.v0", + "answer_id": "n2", + "fork_id": "gamecult-chat:question:slot-mark-consent", + "outcome": "Counted", + "of": null, + "retracts": null, + "code": null, + "options_hash": "7d95fc87d532420611fe415c1def27a3447617bb68235f46e3af130aea361c48", + "choice": "no", + "note": "Keep it local: a slot mark is the author's call, and sharing it is a separate act.", + "answered_at": "2026-11-12T19:02:00Z", + "received_at": "2026-11-12T19:06:05Z", + "claim_in_future": false, + "answerer": "e0d1c2b3-a495-4687-8879-6a5b4c3d2e1f", + "authority": "Operator", + "request_id": "00000001-0000-4000-8000-000000000001" + }, + "card": null + } + } + ], + "record": { + "schemaVersion": "gamecult.fork.record.v0", + "fork_id": "gamecult-chat:question:slot-mark-consent", + "options_hash": "7d95fc87d532420611fe415c1def27a3447617bb68235f46e3af130aea361c48", + "counted_answer_id": "n2", + "answers": [ + { + "schemaVersion": "gamecult.desk_answer.v0", + "answer_id": "n1", + "fork_id": "gamecult-chat:question:slot-mark-consent", + "outcome": "Refused", + "of": null, + "retracts": null, + "code": "stale_fork", + "options_hash": "14b8ffbdb61106dbc06f69f3fc8e2072af7a205d766e4cc040e89733013d54dc", + "choice": "no", + "note": "Keep it local: a slot mark is the author's call, and sharing it is a separate act.", + "answered_at": "2026-11-12T19:02:00Z", + "received_at": "2026-11-12T19:02:05Z", + "claim_in_future": false, + "answerer": "e0d1c2b3-a495-4687-8879-6a5b4c3d2e1f", + "authority": "Operator", + "request_id": "0000000b-0000-4000-8000-00000000000b" + }, + { + "schemaVersion": "gamecult.desk_answer.v0", + "answer_id": "n2", + "fork_id": "gamecult-chat:question:slot-mark-consent", + "outcome": "Counted", + "of": null, + "retracts": null, + "code": null, + "options_hash": "7d95fc87d532420611fe415c1def27a3447617bb68235f46e3af130aea361c48", + "choice": "no", + "note": "Keep it local: a slot mark is the author's call, and sharing it is a separate act.", + "answered_at": "2026-11-12T19:02:00Z", + "received_at": "2026-11-12T19:06:05Z", + "claim_in_future": false, + "answerer": "e0d1c2b3-a495-4687-8879-6a5b4c3d2e1f", + "authority": "Operator", + "request_id": "00000001-0000-4000-8000-000000000001" + } + ] + }, + "recordHex": "85AD736368656D6156657273696F6EB767616D6563756C742E666F726B2E7265636F72642E7630A7666F726B5F6964D92867616D6563756C742D636861743A7175657374696F6E3A736C6F742D6D61726B2D636F6E73656E74AC6F7074696F6E735F68617368D94037643935666338376435333234323036313166653431356331646566323761333434373631376262363832333566343665336166313330616561333631633438B1636F756E7465645F616E737765725F6964A26E32A7616E737765727392DE0010AD736368656D6156657273696F6EB767616D6563756C742E6465736B5F616E737765722E7630A9616E737765725F6964A26E31A7666F726B5F6964D92867616D6563756C742D636861743A7175657374696F6E3A736C6F742D6D61726B2D636F6E73656E74A76F7574636F6D65A752656675736564A26F66C0A87265747261637473C0A4636F6465AA7374616C655F666F726BAC6F7074696F6E735F68617368D94031346238666662646236313130366462633036663639663366633865323037326166376132303564373636653463633034306538393733333031336435346463A663686F696365A26E6FA46E6F7465D9524B656570206974206C6F63616C3A206120736C6F74206D61726B2069732074686520617574686F7227732063616C6C2C20616E642073686172696E672069742069732061207365706172617465206163742EAB616E7377657265645F6174B4323032362D31312D31325431393A30323A30305AAB72656365697665645F6174B4323032362D31312D31325431393A30323A30355AAF636C61696D5F696E5F667574757265C2A8616E737765726572D92465306431633262332D613439352D343638372D383837392D366135623463336432653166A9617574686F72697479A84F70657261746F72AA726571756573745F6964D92430303030303030622D303030302D343030302D383030302D303030303030303030303062DE0010AD736368656D6156657273696F6EB767616D6563756C742E6465736B5F616E737765722E7630A9616E737765725F6964A26E32A7666F726B5F6964D92867616D6563756C742D636861743A7175657374696F6E3A736C6F742D6D61726B2D636F6E73656E74A76F7574636F6D65A7436F756E746564A26F66C0A87265747261637473C0A4636F6465C0AC6F7074696F6E735F68617368D94037643935666338376435333234323036313166653431356331646566323761333434373631376262363832333566343665336166313330616561333631633438A663686F696365A26E6FA46E6F7465D9524B656570206974206C6F63616C3A206120736C6F74206D61726B2069732074686520617574686F7227732063616C6C2C20616E642073686172696E672069742069732061207365706172617465206163742EAB616E7377657265645F6174B4323032362D31312D31325431393A30323A30305AAB72656365697665645F6174B4323032362D31312D31325431393A30363A30355AAF636C61696D5F696E5F667574757265C2A8616E737765726572D92465306431633262332D613439352D343638372D383837392D366135623463336432653166A9617574686F72697479A84F70657261746F72AA726571756573745F6964D92430303030303030312D303030302D343030302D383030302D303030303030303030303031" + }, + { + "name": "V3_an_answer_off_the_decider_list_is_Refused_not_a_decider_and_stored", + "card": { + "schemaVersion": "gamecult.fork.card.v0", + "fork_id": "gamecult-chat:question:slot-mark-consent", + "title": "Slot mark consent", + "context": "Should a slot mark be shared?", + "options": [ + { + "id": "yes", + "text": "Share it." + }, + { + "id": "no", + "text": "Keep it local." + } + ], + "recommendation": "no", + "depends": [ + "gamecult-chat:question:file-zone-audience" + ], + "leans": null, + "flag": null, + "raised_in": { + "kind": "CutSpec", + "id": "cultlib-gaps:cut_spec:cut-slot-tolerance.r4" + }, + "deciders": [ + "e0d1c2b3-a495-4687-8879-6a5b4c3d2e1f" + ], + "status": "open", + "ruling": null, + "ruled_from": [], + "order": 3, + "options_hash": "7d95fc87d532420611fe415c1def27a3447617bb68235f46e3af130aea361c48" + }, + "optionsHash": "7d95fc87d532420611fe415c1def27a3447617bb68235f46e3af130aea361c48", + "steps": [ + { + "kind": "answer", + "body": { + "schemaVersion": "gamecult.fork.answer_request.v0", + "fork_id": "gamecult-chat:question:slot-mark-consent", + "options_hash": "7d95fc87d532420611fe415c1def27a3447617bb68235f46e3af130aea361c48", + "answer": { + "choice": "yes", + "note": "Share, please." + }, + "retracted": false, + "retracts": null, + "answered_at": "2026-11-12T19:02:00Z", + "request_id": "00000004-0000-4000-8000-000000000004" + }, + "server": { + "answer_id": "n4", + "answerer": "11111111-2222-4333-8444-555555555555", + "authority": "Member", + "received_at": "2026-11-12T19:03:05Z" + }, + "response": { + "schemaVersion": "gamecult.fork.answer_result.v0", + "answer": { + "schemaVersion": "gamecult.desk_answer.v0", + "answer_id": "n4", + "fork_id": "gamecult-chat:question:slot-mark-consent", + "outcome": "Refused", + "of": null, + "retracts": null, + "code": "not_a_decider", + "options_hash": "7d95fc87d532420611fe415c1def27a3447617bb68235f46e3af130aea361c48", + "choice": "yes", + "note": "Share, please.", + "answered_at": "2026-11-12T19:02:00Z", + "received_at": "2026-11-12T19:03:05Z", + "claim_in_future": false, + "answerer": "11111111-2222-4333-8444-555555555555", + "authority": "Member", + "request_id": "00000004-0000-4000-8000-000000000004" + }, + "card": null + } + } + ], + "record": { + "schemaVersion": "gamecult.fork.record.v0", + "fork_id": "gamecult-chat:question:slot-mark-consent", + "options_hash": "7d95fc87d532420611fe415c1def27a3447617bb68235f46e3af130aea361c48", + "counted_answer_id": null, + "answers": [ + { + "schemaVersion": "gamecult.desk_answer.v0", + "answer_id": "n4", + "fork_id": "gamecult-chat:question:slot-mark-consent", + "outcome": "Refused", + "of": null, + "retracts": null, + "code": "not_a_decider", + "options_hash": "7d95fc87d532420611fe415c1def27a3447617bb68235f46e3af130aea361c48", + "choice": "yes", + "note": "Share, please.", + "answered_at": "2026-11-12T19:02:00Z", + "received_at": "2026-11-12T19:03:05Z", + "claim_in_future": false, + "answerer": "11111111-2222-4333-8444-555555555555", + "authority": "Member", + "request_id": "00000004-0000-4000-8000-000000000004" + } + ] + }, + "recordHex": "85AD736368656D6156657273696F6EB767616D6563756C742E666F726B2E7265636F72642E7630A7666F726B5F6964D92867616D6563756C742D636861743A7175657374696F6E3A736C6F742D6D61726B2D636F6E73656E74AC6F7074696F6E735F68617368D94037643935666338376435333234323036313166653431356331646566323761333434373631376262363832333566343665336166313330616561333631633438B1636F756E7465645F616E737765725F6964C0A7616E737765727391DE0010AD736368656D6156657273696F6EB767616D6563756C742E6465736B5F616E737765722E7630A9616E737765725F6964A26E34A7666F726B5F6964D92867616D6563756C742D636861743A7175657374696F6E3A736C6F742D6D61726B2D636F6E73656E74A76F7574636F6D65A752656675736564A26F66C0A87265747261637473C0A4636F6465AD6E6F745F615F64656369646572AC6F7074696F6E735F68617368D94037643935666338376435333234323036313166653431356331646566323761333434373631376262363832333566343665336166313330616561333631633438A663686F696365A3796573A46E6F7465AE53686172652C20706C656173652EAB616E7377657265645F6174B4323032362D31312D31325431393A30323A30305AAB72656365697665645F6174B4323032362D31312D31325431393A30333A30355AAF636C61696D5F696E5F667574757265C2A8616E737765726572D92431313131313131312D323232322D343333332D383434342D353535353535353535353535A9617574686F72697479A64D656D626572AA726571756573745F6964D92430303030303030342D303030302D343030302D383030302D303030303030303030303034" + }, + { + "name": "a_non_decider_with_a_stale_hash_is_stale_first", + "card": { + "schemaVersion": "gamecult.fork.card.v0", + "fork_id": "gamecult-chat:question:slot-mark-consent", + "title": "Slot mark consent", + "context": "Should a slot mark be shared?", + "options": [ + { + "id": "yes", + "text": "Share it." + }, + { + "id": "no", + "text": "Keep it local." + } + ], + "recommendation": "no", + "depends": [ + "gamecult-chat:question:file-zone-audience" + ], + "leans": null, + "flag": null, + "raised_in": { + "kind": "CutSpec", + "id": "cultlib-gaps:cut_spec:cut-slot-tolerance.r4" + }, + "deciders": [ + "e0d1c2b3-a495-4687-8879-6a5b4c3d2e1f" + ], + "status": "open", + "ruling": null, + "ruled_from": [], + "order": 3, + "options_hash": "7d95fc87d532420611fe415c1def27a3447617bb68235f46e3af130aea361c48" + }, + "optionsHash": "7d95fc87d532420611fe415c1def27a3447617bb68235f46e3af130aea361c48", + "steps": [ + { + "kind": "answer", + "body": { + "schemaVersion": "gamecult.fork.answer_request.v0", + "fork_id": "gamecult-chat:question:slot-mark-consent", + "options_hash": "14b8ffbdb61106dbc06f69f3fc8e2072af7a205d766e4cc040e89733013d54dc", + "answer": { + "choice": "yes", + "note": "Share, please." + }, + "retracted": false, + "retracts": null, + "answered_at": "2026-11-12T19:02:00Z", + "request_id": "00000004-0000-4000-8000-000000000004" + }, + "server": { + "answer_id": "n4", + "answerer": "11111111-2222-4333-8444-555555555555", + "authority": "Member", + "received_at": "2026-11-12T19:03:05Z" + }, + "response": { + "schemaVersion": "gamecult.fork.answer_result.v0", + "answer": { + "schemaVersion": "gamecult.desk_answer.v0", + "answer_id": "n4", + "fork_id": "gamecult-chat:question:slot-mark-consent", + "outcome": "Refused", + "of": null, + "retracts": null, + "code": "stale_fork", + "options_hash": "14b8ffbdb61106dbc06f69f3fc8e2072af7a205d766e4cc040e89733013d54dc", + "choice": "yes", + "note": "Share, please.", + "answered_at": "2026-11-12T19:02:00Z", + "received_at": "2026-11-12T19:03:05Z", + "claim_in_future": false, + "answerer": "11111111-2222-4333-8444-555555555555", + "authority": "Member", + "request_id": "00000004-0000-4000-8000-000000000004" + }, + "card": { + "schemaVersion": "gamecult.fork.card.v0", + "fork_id": "gamecult-chat:question:slot-mark-consent", + "title": "Slot mark consent", + "context": "Should a slot mark be shared?", + "options": [ + { + "id": "yes", + "text": "Share it." + }, + { + "id": "no", + "text": "Keep it local." + } + ], + "recommendation": "no", + "depends": [ + "gamecult-chat:question:file-zone-audience" + ], + "leans": null, + "flag": null, + "raised_in": { + "kind": "CutSpec", + "id": "cultlib-gaps:cut_spec:cut-slot-tolerance.r4" + }, + "deciders": [ + "e0d1c2b3-a495-4687-8879-6a5b4c3d2e1f" + ], + "status": "open", + "ruling": null, + "ruled_from": [], + "order": 3, + "options_hash": "7d95fc87d532420611fe415c1def27a3447617bb68235f46e3af130aea361c48" + } + } + } + ], + "record": { + "schemaVersion": "gamecult.fork.record.v0", + "fork_id": "gamecult-chat:question:slot-mark-consent", + "options_hash": "7d95fc87d532420611fe415c1def27a3447617bb68235f46e3af130aea361c48", + "counted_answer_id": null, + "answers": [ + { + "schemaVersion": "gamecult.desk_answer.v0", + "answer_id": "n4", + "fork_id": "gamecult-chat:question:slot-mark-consent", + "outcome": "Refused", + "of": null, + "retracts": null, + "code": "stale_fork", + "options_hash": "14b8ffbdb61106dbc06f69f3fc8e2072af7a205d766e4cc040e89733013d54dc", + "choice": "yes", + "note": "Share, please.", + "answered_at": "2026-11-12T19:02:00Z", + "received_at": "2026-11-12T19:03:05Z", + "claim_in_future": false, + "answerer": "11111111-2222-4333-8444-555555555555", + "authority": "Member", + "request_id": "00000004-0000-4000-8000-000000000004" + } + ] + }, + "recordHex": "85AD736368656D6156657273696F6EB767616D6563756C742E666F726B2E7265636F72642E7630A7666F726B5F6964D92867616D6563756C742D636861743A7175657374696F6E3A736C6F742D6D61726B2D636F6E73656E74AC6F7074696F6E735F68617368D94037643935666338376435333234323036313166653431356331646566323761333434373631376262363832333566343665336166313330616561333631633438B1636F756E7465645F616E737765725F6964C0A7616E737765727391DE0010AD736368656D6156657273696F6EB767616D6563756C742E6465736B5F616E737765722E7630A9616E737765725F6964A26E34A7666F726B5F6964D92867616D6563756C742D636861743A7175657374696F6E3A736C6F742D6D61726B2D636F6E73656E74A76F7574636F6D65A752656675736564A26F66C0A87265747261637473C0A4636F6465AA7374616C655F666F726BAC6F7074696F6E735F68617368D94031346238666662646236313130366462633036663639663366633865323037326166376132303564373636653463633034306538393733333031336435346463A663686F696365A3796573A46E6F7465AE53686172652C20706C656173652EAB616E7377657265645F6174B4323032362D31312D31325431393A30323A30305AAB72656365697665645F6174B4323032362D31312D31325431393A30333A30355AAF636C61696D5F696E5F667574757265C2A8616E737765726572D92431313131313131312D323232322D343333332D383434342D353535353535353535353535A9617574686F72697479A64D656D626572AA726571756573745F6964D92430303030303030342D303030302D343030302D383030302D303030303030303030303034" + }, + { + "name": "V4_a_closed_fork_refuses_and_stores_the_refusal", + "card": { + "schemaVersion": "gamecult.fork.card.v0", + "fork_id": "gamecult-chat:question:slot-mark-consent", + "title": "Slot mark consent", + "context": "Should a slot mark be shared?", + "options": [ + { + "id": "yes", + "text": "Share it." + }, + { + "id": "no", + "text": "Keep it local." + } + ], + "recommendation": "no", + "depends": [ + "gamecult-chat:question:file-zone-audience" + ], + "leans": null, + "flag": null, + "raised_in": { + "kind": "CutSpec", + "id": "cultlib-gaps:cut_spec:cut-slot-tolerance.r4" + }, + "deciders": [ + "e0d1c2b3-a495-4687-8879-6a5b4c3d2e1f" + ], + "status": "closed", + "ruling": "cultlib-gaps:resolution:question.slot-mark-consent.n1", + "ruled_from": [], + "order": 3, + "options_hash": "7d95fc87d532420611fe415c1def27a3447617bb68235f46e3af130aea361c48" + }, + "optionsHash": "7d95fc87d532420611fe415c1def27a3447617bb68235f46e3af130aea361c48", + "steps": [ + { + "kind": "answer", + "body": { + "schemaVersion": "gamecult.fork.answer_request.v0", + "fork_id": "gamecult-chat:question:slot-mark-consent", + "options_hash": "7d95fc87d532420611fe415c1def27a3447617bb68235f46e3af130aea361c48", + "answer": { + "choice": "no", + "note": "Keep it local: a slot mark is the author's call, and sharing it is a separate act." + }, + "retracted": false, + "retracts": null, + "answered_at": "2026-11-12T19:02:00Z", + "request_id": "00000001-0000-4000-8000-000000000001" + }, + "server": { + "answer_id": "n1", + "answerer": "e0d1c2b3-a495-4687-8879-6a5b4c3d2e1f", + "authority": "Operator", + "received_at": "2026-11-12T19:02:05Z" + }, + "response": { + "schemaVersion": "gamecult.fork.answer_result.v0", + "answer": { + "schemaVersion": "gamecult.desk_answer.v0", + "answer_id": "n1", + "fork_id": "gamecult-chat:question:slot-mark-consent", + "outcome": "Refused", + "of": null, + "retracts": null, + "code": "fork_closed", + "options_hash": "7d95fc87d532420611fe415c1def27a3447617bb68235f46e3af130aea361c48", + "choice": "no", + "note": "Keep it local: a slot mark is the author's call, and sharing it is a separate act.", + "answered_at": "2026-11-12T19:02:00Z", + "received_at": "2026-11-12T19:02:05Z", + "claim_in_future": false, + "answerer": "e0d1c2b3-a495-4687-8879-6a5b4c3d2e1f", + "authority": "Operator", + "request_id": "00000001-0000-4000-8000-000000000001" + }, + "card": null + } + } + ], + "record": { + "schemaVersion": "gamecult.fork.record.v0", + "fork_id": "gamecult-chat:question:slot-mark-consent", + "options_hash": "7d95fc87d532420611fe415c1def27a3447617bb68235f46e3af130aea361c48", + "counted_answer_id": null, + "answers": [ + { + "schemaVersion": "gamecult.desk_answer.v0", + "answer_id": "n1", + "fork_id": "gamecult-chat:question:slot-mark-consent", + "outcome": "Refused", + "of": null, + "retracts": null, + "code": "fork_closed", + "options_hash": "7d95fc87d532420611fe415c1def27a3447617bb68235f46e3af130aea361c48", + "choice": "no", + "note": "Keep it local: a slot mark is the author's call, and sharing it is a separate act.", + "answered_at": "2026-11-12T19:02:00Z", + "received_at": "2026-11-12T19:02:05Z", + "claim_in_future": false, + "answerer": "e0d1c2b3-a495-4687-8879-6a5b4c3d2e1f", + "authority": "Operator", + "request_id": "00000001-0000-4000-8000-000000000001" + } + ] + }, + "recordHex": "85AD736368656D6156657273696F6EB767616D6563756C742E666F726B2E7265636F72642E7630A7666F726B5F6964D92867616D6563756C742D636861743A7175657374696F6E3A736C6F742D6D61726B2D636F6E73656E74AC6F7074696F6E735F68617368D94037643935666338376435333234323036313166653431356331646566323761333434373631376262363832333566343665336166313330616561333631633438B1636F756E7465645F616E737765725F6964C0A7616E737765727391DE0010AD736368656D6156657273696F6EB767616D6563756C742E6465736B5F616E737765722E7630A9616E737765725F6964A26E31A7666F726B5F6964D92867616D6563756C742D636861743A7175657374696F6E3A736C6F742D6D61726B2D636F6E73656E74A76F7574636F6D65A752656675736564A26F66C0A87265747261637473C0A4636F6465AB666F726B5F636C6F736564AC6F7074696F6E735F68617368D94037643935666338376435333234323036313166653431356331646566323761333434373631376262363832333566343665336166313330616561333631633438A663686F696365A26E6FA46E6F7465D9524B656570206974206C6F63616C3A206120736C6F74206D61726B2069732074686520617574686F7227732063616C6C2C20616E642073686172696E672069742069732061207365706172617465206163742EAB616E7377657265645F6174B4323032362D31312D31325431393A30323A30305AAB72656365697665645F6174B4323032362D31312D31325431393A30323A30355AAF636C61696D5F696E5F667574757265C2A8616E737765726572D92465306431633262332D613439352D343638372D383837392D366135623463336432653166A9617574686F72697479A84F70657261746F72AA726571756573745F6964D92430303030303030312D303030302D343030302D383030302D303030303030303030303031" + }, + { + "name": "V5_second_answer_from_one_decider_is_a_Revision_the_first_stays_counted", + "card": { + "schemaVersion": "gamecult.fork.card.v0", + "fork_id": "gamecult-chat:question:slot-mark-consent", + "title": "Slot mark consent", + "context": "Should a slot mark be shared?", + "options": [ + { + "id": "yes", + "text": "Share it." + }, + { + "id": "no", + "text": "Keep it local." + } + ], + "recommendation": "no", + "depends": [ + "gamecult-chat:question:file-zone-audience" + ], + "leans": null, + "flag": null, + "raised_in": { + "kind": "CutSpec", + "id": "cultlib-gaps:cut_spec:cut-slot-tolerance.r4" + }, + "deciders": [ + "e0d1c2b3-a495-4687-8879-6a5b4c3d2e1f" + ], + "status": "open", + "ruling": null, + "ruled_from": [], + "order": 3, + "options_hash": "7d95fc87d532420611fe415c1def27a3447617bb68235f46e3af130aea361c48" + }, + "optionsHash": "7d95fc87d532420611fe415c1def27a3447617bb68235f46e3af130aea361c48", + "steps": [ + { + "kind": "answer", + "body": { + "schemaVersion": "gamecult.fork.answer_request.v0", + "fork_id": "gamecult-chat:question:slot-mark-consent", + "options_hash": "7d95fc87d532420611fe415c1def27a3447617bb68235f46e3af130aea361c48", + "answer": { + "choice": "no", + "note": "Keep it local: a slot mark is the author's call, and sharing it is a separate act." + }, + "retracted": false, + "retracts": null, + "answered_at": "2026-11-12T19:02:00Z", + "request_id": "00000001-0000-4000-8000-000000000001" + }, + "server": { + "answer_id": "n1", + "answerer": "e0d1c2b3-a495-4687-8879-6a5b4c3d2e1f", + "authority": "Operator", + "received_at": "2026-11-12T19:02:05Z" + }, + "response": { + "schemaVersion": "gamecult.fork.answer_result.v0", + "answer": { + "schemaVersion": "gamecult.desk_answer.v0", + "answer_id": "n1", + "fork_id": "gamecult-chat:question:slot-mark-consent", + "outcome": "Counted", + "of": null, + "retracts": null, + "code": null, + "options_hash": "7d95fc87d532420611fe415c1def27a3447617bb68235f46e3af130aea361c48", + "choice": "no", + "note": "Keep it local: a slot mark is the author's call, and sharing it is a separate act.", + "answered_at": "2026-11-12T19:02:00Z", + "received_at": "2026-11-12T19:02:05Z", + "claim_in_future": false, + "answerer": "e0d1c2b3-a495-4687-8879-6a5b4c3d2e1f", + "authority": "Operator", + "request_id": "00000001-0000-4000-8000-000000000001" + }, + "card": null + } + }, + { + "kind": "answer", + "body": { + "schemaVersion": "gamecult.fork.answer_request.v0", + "fork_id": "gamecult-chat:question:slot-mark-consent", + "options_hash": "7d95fc87d532420611fe415c1def27a3447617bb68235f46e3af130aea361c48", + "answer": { + "choice": "yes", + "note": "On reflection, share it." + }, + "retracted": false, + "retracts": null, + "answered_at": "2026-11-12T19:06:00Z", + "request_id": "00000002-0000-4000-8000-000000000002" + }, + "server": { + "answer_id": "n2", + "answerer": "e0d1c2b3-a495-4687-8879-6a5b4c3d2e1f", + "authority": "Operator", + "received_at": "2026-11-12T19:06:05Z" + }, + "response": { + "schemaVersion": "gamecult.fork.answer_result.v0", + "answer": { + "schemaVersion": "gamecult.desk_answer.v0", + "answer_id": "n2", + "fork_id": "gamecult-chat:question:slot-mark-consent", + "outcome": "Revision", + "of": "n1", + "retracts": null, + "code": null, + "options_hash": "7d95fc87d532420611fe415c1def27a3447617bb68235f46e3af130aea361c48", + "choice": "yes", + "note": "On reflection, share it.", + "answered_at": "2026-11-12T19:06:00Z", + "received_at": "2026-11-12T19:06:05Z", + "claim_in_future": false, + "answerer": "e0d1c2b3-a495-4687-8879-6a5b4c3d2e1f", + "authority": "Operator", + "request_id": "00000002-0000-4000-8000-000000000002" + }, + "card": null + } + } + ], + "record": { + "schemaVersion": "gamecult.fork.record.v0", + "fork_id": "gamecult-chat:question:slot-mark-consent", + "options_hash": "7d95fc87d532420611fe415c1def27a3447617bb68235f46e3af130aea361c48", + "counted_answer_id": "n1", + "answers": [ + { + "schemaVersion": "gamecult.desk_answer.v0", + "answer_id": "n1", + "fork_id": "gamecult-chat:question:slot-mark-consent", + "outcome": "Counted", + "of": null, + "retracts": null, + "code": null, + "options_hash": "7d95fc87d532420611fe415c1def27a3447617bb68235f46e3af130aea361c48", + "choice": "no", + "note": "Keep it local: a slot mark is the author's call, and sharing it is a separate act.", + "answered_at": "2026-11-12T19:02:00Z", + "received_at": "2026-11-12T19:02:05Z", + "claim_in_future": false, + "answerer": "e0d1c2b3-a495-4687-8879-6a5b4c3d2e1f", + "authority": "Operator", + "request_id": "00000001-0000-4000-8000-000000000001" + }, + { + "schemaVersion": "gamecult.desk_answer.v0", + "answer_id": "n2", + "fork_id": "gamecult-chat:question:slot-mark-consent", + "outcome": "Revision", + "of": "n1", + "retracts": null, + "code": null, + "options_hash": "7d95fc87d532420611fe415c1def27a3447617bb68235f46e3af130aea361c48", + "choice": "yes", + "note": "On reflection, share it.", + "answered_at": "2026-11-12T19:06:00Z", + "received_at": "2026-11-12T19:06:05Z", + "claim_in_future": false, + "answerer": "e0d1c2b3-a495-4687-8879-6a5b4c3d2e1f", + "authority": "Operator", + "request_id": "00000002-0000-4000-8000-000000000002" + } + ] + }, + "recordHex": "85AD736368656D6156657273696F6EB767616D6563756C742E666F726B2E7265636F72642E7630A7666F726B5F6964D92867616D6563756C742D636861743A7175657374696F6E3A736C6F742D6D61726B2D636F6E73656E74AC6F7074696F6E735F68617368D94037643935666338376435333234323036313166653431356331646566323761333434373631376262363832333566343665336166313330616561333631633438B1636F756E7465645F616E737765725F6964A26E31A7616E737765727392DE0010AD736368656D6156657273696F6EB767616D6563756C742E6465736B5F616E737765722E7630A9616E737765725F6964A26E31A7666F726B5F6964D92867616D6563756C742D636861743A7175657374696F6E3A736C6F742D6D61726B2D636F6E73656E74A76F7574636F6D65A7436F756E746564A26F66C0A87265747261637473C0A4636F6465C0AC6F7074696F6E735F68617368D94037643935666338376435333234323036313166653431356331646566323761333434373631376262363832333566343665336166313330616561333631633438A663686F696365A26E6FA46E6F7465D9524B656570206974206C6F63616C3A206120736C6F74206D61726B2069732074686520617574686F7227732063616C6C2C20616E642073686172696E672069742069732061207365706172617465206163742EAB616E7377657265645F6174B4323032362D31312D31325431393A30323A30305AAB72656365697665645F6174B4323032362D31312D31325431393A30323A30355AAF636C61696D5F696E5F667574757265C2A8616E737765726572D92465306431633262332D613439352D343638372D383837392D366135623463336432653166A9617574686F72697479A84F70657261746F72AA726571756573745F6964D92430303030303030312D303030302D343030302D383030302D303030303030303030303031DE0010AD736368656D6156657273696F6EB767616D6563756C742E6465736B5F616E737765722E7630A9616E737765725F6964A26E32A7666F726B5F6964D92867616D6563756C742D636861743A7175657374696F6E3A736C6F742D6D61726B2D636F6E73656E74A76F7574636F6D65A85265766973696F6EA26F66A26E31A87265747261637473C0A4636F6465C0AC6F7074696F6E735F68617368D94037643935666338376435333234323036313166653431356331646566323761333434373631376262363832333566343665336166313330616561333631633438A663686F696365A3796573A46E6F7465B84F6E207265666C656374696F6E2C2073686172652069742EAB616E7377657265645F6174B4323032362D31312D31325431393A30363A30305AAB72656365697665645F6174B4323032362D31312D31325431393A30363A30355AAF636C61696D5F696E5F667574757265C2A8616E737765726572D92465306431633262332D613439352D343638372D383837392D366135623463336432653166A9617574686F72697479A84F70657261746F72AA726571756573745F6964D92430303030303030322D303030302D343030302D383030302D303030303030303030303032" + }, + { + "name": "V5_receiver_order_decides_not_answered_at", + "card": { + "schemaVersion": "gamecult.fork.card.v0", + "fork_id": "gamecult-chat:question:slot-mark-consent", + "title": "Slot mark consent", + "context": "Should a slot mark be shared?", + "options": [ + { + "id": "yes", + "text": "Share it." + }, + { + "id": "no", + "text": "Keep it local." + } + ], + "recommendation": "no", + "depends": [ + "gamecult-chat:question:file-zone-audience" + ], + "leans": null, + "flag": null, + "raised_in": { + "kind": "CutSpec", + "id": "cultlib-gaps:cut_spec:cut-slot-tolerance.r4" + }, + "deciders": [ + "e0d1c2b3-a495-4687-8879-6a5b4c3d2e1f" + ], + "status": "open", + "ruling": null, + "ruled_from": [], + "order": 3, + "options_hash": "7d95fc87d532420611fe415c1def27a3447617bb68235f46e3af130aea361c48" + }, + "optionsHash": "7d95fc87d532420611fe415c1def27a3447617bb68235f46e3af130aea361c48", + "steps": [ + { + "kind": "answer", + "body": { + "schemaVersion": "gamecult.fork.answer_request.v0", + "fork_id": "gamecult-chat:question:slot-mark-consent", + "options_hash": "7d95fc87d532420611fe415c1def27a3447617bb68235f46e3af130aea361c48", + "answer": { + "choice": "yes", + "note": "On reflection, share it." + }, + "retracted": false, + "retracts": null, + "answered_at": "2026-11-12T19:06:00Z", + "request_id": "00000002-0000-4000-8000-000000000002" + }, + "server": { + "answer_id": "n1", + "answerer": "e0d1c2b3-a495-4687-8879-6a5b4c3d2e1f", + "authority": "Operator", + "received_at": "2026-11-12T19:02:05Z" + }, + "response": { + "schemaVersion": "gamecult.fork.answer_result.v0", + "answer": { + "schemaVersion": "gamecult.desk_answer.v0", + "answer_id": "n1", + "fork_id": "gamecult-chat:question:slot-mark-consent", + "outcome": "Counted", + "of": null, + "retracts": null, + "code": null, + "options_hash": "7d95fc87d532420611fe415c1def27a3447617bb68235f46e3af130aea361c48", + "choice": "yes", + "note": "On reflection, share it.", + "answered_at": "2026-11-12T19:06:00Z", + "received_at": "2026-11-12T19:02:05Z", + "claim_in_future": false, + "answerer": "e0d1c2b3-a495-4687-8879-6a5b4c3d2e1f", + "authority": "Operator", + "request_id": "00000002-0000-4000-8000-000000000002" + }, + "card": null + } + }, + { + "kind": "answer", + "body": { + "schemaVersion": "gamecult.fork.answer_request.v0", + "fork_id": "gamecult-chat:question:slot-mark-consent", + "options_hash": "7d95fc87d532420611fe415c1def27a3447617bb68235f46e3af130aea361c48", + "answer": { + "choice": "no", + "note": "Keep it local: a slot mark is the author's call, and sharing it is a separate act." + }, + "retracted": false, + "retracts": null, + "answered_at": "2026-11-12T19:02:00Z", + "request_id": "00000001-0000-4000-8000-000000000001" + }, + "server": { + "answer_id": "n2", + "answerer": "e0d1c2b3-a495-4687-8879-6a5b4c3d2e1f", + "authority": "Operator", + "received_at": "2026-11-12T19:03:05Z" + }, + "response": { + "schemaVersion": "gamecult.fork.answer_result.v0", + "answer": { + "schemaVersion": "gamecult.desk_answer.v0", + "answer_id": "n2", + "fork_id": "gamecult-chat:question:slot-mark-consent", + "outcome": "Revision", + "of": "n1", + "retracts": null, + "code": null, + "options_hash": "7d95fc87d532420611fe415c1def27a3447617bb68235f46e3af130aea361c48", + "choice": "no", + "note": "Keep it local: a slot mark is the author's call, and sharing it is a separate act.", + "answered_at": "2026-11-12T19:02:00Z", + "received_at": "2026-11-12T19:03:05Z", + "claim_in_future": false, + "answerer": "e0d1c2b3-a495-4687-8879-6a5b4c3d2e1f", + "authority": "Operator", + "request_id": "00000001-0000-4000-8000-000000000001" + }, + "card": null + } + } + ], + "record": { + "schemaVersion": "gamecult.fork.record.v0", + "fork_id": "gamecult-chat:question:slot-mark-consent", + "options_hash": "7d95fc87d532420611fe415c1def27a3447617bb68235f46e3af130aea361c48", + "counted_answer_id": "n1", + "answers": [ + { + "schemaVersion": "gamecult.desk_answer.v0", + "answer_id": "n1", + "fork_id": "gamecult-chat:question:slot-mark-consent", + "outcome": "Counted", + "of": null, + "retracts": null, + "code": null, + "options_hash": "7d95fc87d532420611fe415c1def27a3447617bb68235f46e3af130aea361c48", + "choice": "yes", + "note": "On reflection, share it.", + "answered_at": "2026-11-12T19:06:00Z", + "received_at": "2026-11-12T19:02:05Z", + "claim_in_future": false, + "answerer": "e0d1c2b3-a495-4687-8879-6a5b4c3d2e1f", + "authority": "Operator", + "request_id": "00000002-0000-4000-8000-000000000002" + }, + { + "schemaVersion": "gamecult.desk_answer.v0", + "answer_id": "n2", + "fork_id": "gamecult-chat:question:slot-mark-consent", + "outcome": "Revision", + "of": "n1", + "retracts": null, + "code": null, + "options_hash": "7d95fc87d532420611fe415c1def27a3447617bb68235f46e3af130aea361c48", + "choice": "no", + "note": "Keep it local: a slot mark is the author's call, and sharing it is a separate act.", + "answered_at": "2026-11-12T19:02:00Z", + "received_at": "2026-11-12T19:03:05Z", + "claim_in_future": false, + "answerer": "e0d1c2b3-a495-4687-8879-6a5b4c3d2e1f", + "authority": "Operator", + "request_id": "00000001-0000-4000-8000-000000000001" + } + ] + }, + "recordHex": "85AD736368656D6156657273696F6EB767616D6563756C742E666F726B2E7265636F72642E7630A7666F726B5F6964D92867616D6563756C742D636861743A7175657374696F6E3A736C6F742D6D61726B2D636F6E73656E74AC6F7074696F6E735F68617368D94037643935666338376435333234323036313166653431356331646566323761333434373631376262363832333566343665336166313330616561333631633438B1636F756E7465645F616E737765725F6964A26E31A7616E737765727392DE0010AD736368656D6156657273696F6EB767616D6563756C742E6465736B5F616E737765722E7630A9616E737765725F6964A26E31A7666F726B5F6964D92867616D6563756C742D636861743A7175657374696F6E3A736C6F742D6D61726B2D636F6E73656E74A76F7574636F6D65A7436F756E746564A26F66C0A87265747261637473C0A4636F6465C0AC6F7074696F6E735F68617368D94037643935666338376435333234323036313166653431356331646566323761333434373631376262363832333566343665336166313330616561333631633438A663686F696365A3796573A46E6F7465B84F6E207265666C656374696F6E2C2073686172652069742EAB616E7377657265645F6174B4323032362D31312D31325431393A30363A30305AAB72656365697665645F6174B4323032362D31312D31325431393A30323A30355AAF636C61696D5F696E5F667574757265C2A8616E737765726572D92465306431633262332D613439352D343638372D383837392D366135623463336432653166A9617574686F72697479A84F70657261746F72AA726571756573745F6964D92430303030303030322D303030302D343030302D383030302D303030303030303030303032DE0010AD736368656D6156657273696F6EB767616D6563756C742E6465736B5F616E737765722E7630A9616E737765725F6964A26E32A7666F726B5F6964D92867616D6563756C742D636861743A7175657374696F6E3A736C6F742D6D61726B2D636F6E73656E74A76F7574636F6D65A85265766973696F6EA26F66A26E31A87265747261637473C0A4636F6465C0AC6F7074696F6E735F68617368D94037643935666338376435333234323036313166653431356331646566323761333434373631376262363832333566343665336166313330616561333631633438A663686F696365A26E6FA46E6F7465D9524B656570206974206C6F63616C3A206120736C6F74206D61726B2069732074686520617574686F7227732063616C6C2C20616E642073686172696E672069742069732061207365706172617465206163742EAB616E7377657265645F6174B4323032362D31312D31325431393A30323A30305AAB72656365697665645F6174B4323032362D31312D31325431393A30333A30355AAF636C61696D5F696E5F667574757265C2A8616E737765726572D92465306431633262332D613439352D343638372D383837392D366135623463336432653166A9617574686F72697479A84F70657261746F72AA726571756573745F6964D92430303030303030312D303030302D343030302D383030302D303030303030303030303031" + }, + { + "name": "V5_reversed_arrival_reverses_the_result", + "card": { + "schemaVersion": "gamecult.fork.card.v0", + "fork_id": "gamecult-chat:question:slot-mark-consent", + "title": "Slot mark consent", + "context": "Should a slot mark be shared?", + "options": [ + { + "id": "yes", + "text": "Share it." + }, + { + "id": "no", + "text": "Keep it local." + } + ], + "recommendation": "no", + "depends": [ + "gamecult-chat:question:file-zone-audience" + ], + "leans": null, + "flag": null, + "raised_in": { + "kind": "CutSpec", + "id": "cultlib-gaps:cut_spec:cut-slot-tolerance.r4" + }, + "deciders": [ + "e0d1c2b3-a495-4687-8879-6a5b4c3d2e1f" + ], + "status": "open", + "ruling": null, + "ruled_from": [], + "order": 3, + "options_hash": "7d95fc87d532420611fe415c1def27a3447617bb68235f46e3af130aea361c48" + }, + "optionsHash": "7d95fc87d532420611fe415c1def27a3447617bb68235f46e3af130aea361c48", + "steps": [ + { + "kind": "answer", + "body": { + "schemaVersion": "gamecult.fork.answer_request.v0", + "fork_id": "gamecult-chat:question:slot-mark-consent", + "options_hash": "7d95fc87d532420611fe415c1def27a3447617bb68235f46e3af130aea361c48", + "answer": { + "choice": "no", + "note": "Keep it local: a slot mark is the author's call, and sharing it is a separate act." + }, + "retracted": false, + "retracts": null, + "answered_at": "2026-11-12T19:02:00Z", + "request_id": "00000001-0000-4000-8000-000000000001" + }, + "server": { + "answer_id": "n1", + "answerer": "e0d1c2b3-a495-4687-8879-6a5b4c3d2e1f", + "authority": "Operator", + "received_at": "2026-11-12T19:02:05Z" + }, + "response": { + "schemaVersion": "gamecult.fork.answer_result.v0", + "answer": { + "schemaVersion": "gamecult.desk_answer.v0", + "answer_id": "n1", + "fork_id": "gamecult-chat:question:slot-mark-consent", + "outcome": "Counted", + "of": null, + "retracts": null, + "code": null, + "options_hash": "7d95fc87d532420611fe415c1def27a3447617bb68235f46e3af130aea361c48", + "choice": "no", + "note": "Keep it local: a slot mark is the author's call, and sharing it is a separate act.", + "answered_at": "2026-11-12T19:02:00Z", + "received_at": "2026-11-12T19:02:05Z", + "claim_in_future": false, + "answerer": "e0d1c2b3-a495-4687-8879-6a5b4c3d2e1f", + "authority": "Operator", + "request_id": "00000001-0000-4000-8000-000000000001" + }, + "card": null + } + }, + { + "kind": "answer", + "body": { + "schemaVersion": "gamecult.fork.answer_request.v0", + "fork_id": "gamecult-chat:question:slot-mark-consent", + "options_hash": "7d95fc87d532420611fe415c1def27a3447617bb68235f46e3af130aea361c48", + "answer": { + "choice": "yes", + "note": "On reflection, share it." + }, + "retracted": false, + "retracts": null, + "answered_at": "2026-11-12T19:06:00Z", + "request_id": "00000002-0000-4000-8000-000000000002" + }, + "server": { + "answer_id": "n2", + "answerer": "e0d1c2b3-a495-4687-8879-6a5b4c3d2e1f", + "authority": "Operator", + "received_at": "2026-11-12T19:03:05Z" + }, + "response": { + "schemaVersion": "gamecult.fork.answer_result.v0", + "answer": { + "schemaVersion": "gamecult.desk_answer.v0", + "answer_id": "n2", + "fork_id": "gamecult-chat:question:slot-mark-consent", + "outcome": "Revision", + "of": "n1", + "retracts": null, + "code": null, + "options_hash": "7d95fc87d532420611fe415c1def27a3447617bb68235f46e3af130aea361c48", + "choice": "yes", + "note": "On reflection, share it.", + "answered_at": "2026-11-12T19:06:00Z", + "received_at": "2026-11-12T19:03:05Z", + "claim_in_future": false, + "answerer": "e0d1c2b3-a495-4687-8879-6a5b4c3d2e1f", + "authority": "Operator", + "request_id": "00000002-0000-4000-8000-000000000002" + }, + "card": null + } + } + ], + "record": { + "schemaVersion": "gamecult.fork.record.v0", + "fork_id": "gamecult-chat:question:slot-mark-consent", + "options_hash": "7d95fc87d532420611fe415c1def27a3447617bb68235f46e3af130aea361c48", + "counted_answer_id": "n1", + "answers": [ + { + "schemaVersion": "gamecult.desk_answer.v0", + "answer_id": "n1", + "fork_id": "gamecult-chat:question:slot-mark-consent", + "outcome": "Counted", + "of": null, + "retracts": null, + "code": null, + "options_hash": "7d95fc87d532420611fe415c1def27a3447617bb68235f46e3af130aea361c48", + "choice": "no", + "note": "Keep it local: a slot mark is the author's call, and sharing it is a separate act.", + "answered_at": "2026-11-12T19:02:00Z", + "received_at": "2026-11-12T19:02:05Z", + "claim_in_future": false, + "answerer": "e0d1c2b3-a495-4687-8879-6a5b4c3d2e1f", + "authority": "Operator", + "request_id": "00000001-0000-4000-8000-000000000001" + }, + { + "schemaVersion": "gamecult.desk_answer.v0", + "answer_id": "n2", + "fork_id": "gamecult-chat:question:slot-mark-consent", + "outcome": "Revision", + "of": "n1", + "retracts": null, + "code": null, + "options_hash": "7d95fc87d532420611fe415c1def27a3447617bb68235f46e3af130aea361c48", + "choice": "yes", + "note": "On reflection, share it.", + "answered_at": "2026-11-12T19:06:00Z", + "received_at": "2026-11-12T19:03:05Z", + "claim_in_future": false, + "answerer": "e0d1c2b3-a495-4687-8879-6a5b4c3d2e1f", + "authority": "Operator", + "request_id": "00000002-0000-4000-8000-000000000002" + } + ] + }, + "recordHex": "85AD736368656D6156657273696F6EB767616D6563756C742E666F726B2E7265636F72642E7630A7666F726B5F6964D92867616D6563756C742D636861743A7175657374696F6E3A736C6F742D6D61726B2D636F6E73656E74AC6F7074696F6E735F68617368D94037643935666338376435333234323036313166653431356331646566323761333434373631376262363832333566343665336166313330616561333631633438B1636F756E7465645F616E737765725F6964A26E31A7616E737765727392DE0010AD736368656D6156657273696F6EB767616D6563756C742E6465736B5F616E737765722E7630A9616E737765725F6964A26E31A7666F726B5F6964D92867616D6563756C742D636861743A7175657374696F6E3A736C6F742D6D61726B2D636F6E73656E74A76F7574636F6D65A7436F756E746564A26F66C0A87265747261637473C0A4636F6465C0AC6F7074696F6E735F68617368D94037643935666338376435333234323036313166653431356331646566323761333434373631376262363832333566343665336166313330616561333631633438A663686F696365A26E6FA46E6F7465D9524B656570206974206C6F63616C3A206120736C6F74206D61726B2069732074686520617574686F7227732063616C6C2C20616E642073686172696E672069742069732061207365706172617465206163742EAB616E7377657265645F6174B4323032362D31312D31325431393A30323A30305AAB72656365697665645F6174B4323032362D31312D31325431393A30323A30355AAF636C61696D5F696E5F667574757265C2A8616E737765726572D92465306431633262332D613439352D343638372D383837392D366135623463336432653166A9617574686F72697479A84F70657261746F72AA726571756573745F6964D92430303030303030312D303030302D343030302D383030302D303030303030303030303031DE0010AD736368656D6156657273696F6EB767616D6563756C742E6465736B5F616E737765722E7630A9616E737765725F6964A26E32A7666F726B5F6964D92867616D6563756C742D636861743A7175657374696F6E3A736C6F742D6D61726B2D636F6E73656E74A76F7574636F6D65A85265766973696F6EA26F66A26E31A87265747261637473C0A4636F6465C0AC6F7074696F6E735F68617368D94037643935666338376435333234323036313166653431356331646566323761333434373631376262363832333566343665336166313330616561333631633438A663686F696365A3796573A46E6F7465B84F6E207265666C656374696F6E2C2073686172652069742EAB616E7377657265645F6174B4323032362D31312D31325431393A30363A30305AAB72656365697665645F6174B4323032362D31312D31325431393A30333A30355AAF636C61696D5F696E5F667574757265C2A8616E737765726572D92465306431633262332D613439352D343638372D383837392D366135623463336432653166A9617574686F72697479A84F70657261746F72AA726571756573745F6964D92430303030303030322D303030302D343030302D383030302D303030303030303030303032" + }, + { + "name": "a_second_decider_answer_is_recorded_as_a_Revision", + "card": { + "schemaVersion": "gamecult.fork.card.v0", + "fork_id": "gamecult-chat:question:slot-mark-consent", + "title": "Slot mark consent", + "context": "Should a slot mark be shared?", + "options": [ + { + "id": "yes", + "text": "Share it." + }, + { + "id": "no", + "text": "Keep it local." + } + ], + "recommendation": "no", + "depends": [ + "gamecult-chat:question:file-zone-audience" + ], + "leans": null, + "flag": null, + "raised_in": { + "kind": "CutSpec", + "id": "cultlib-gaps:cut_spec:cut-slot-tolerance.r4" + }, + "deciders": [ + "e0d1c2b3-a495-4687-8879-6a5b4c3d2e1f", + "9f8e7d6c-5b4a-4392-8170-0f1e2d3c4b5a" + ], + "status": "open", + "ruling": null, + "ruled_from": [], + "order": 3, + "options_hash": "7d95fc87d532420611fe415c1def27a3447617bb68235f46e3af130aea361c48" + }, + "optionsHash": "7d95fc87d532420611fe415c1def27a3447617bb68235f46e3af130aea361c48", + "steps": [ + { + "kind": "answer", + "body": { + "schemaVersion": "gamecult.fork.answer_request.v0", + "fork_id": "gamecult-chat:question:slot-mark-consent", + "options_hash": "7d95fc87d532420611fe415c1def27a3447617bb68235f46e3af130aea361c48", + "answer": { + "choice": "no", + "note": "Keep it local: a slot mark is the author's call, and sharing it is a separate act." + }, + "retracted": false, + "retracts": null, + "answered_at": "2026-11-12T19:02:00Z", + "request_id": "00000001-0000-4000-8000-000000000001" + }, + "server": { + "answer_id": "n1", + "answerer": "e0d1c2b3-a495-4687-8879-6a5b4c3d2e1f", + "authority": "Operator", + "received_at": "2026-11-12T19:02:05Z" + }, + "response": { + "schemaVersion": "gamecult.fork.answer_result.v0", + "answer": { + "schemaVersion": "gamecult.desk_answer.v0", + "answer_id": "n1", + "fork_id": "gamecult-chat:question:slot-mark-consent", + "outcome": "Counted", + "of": null, + "retracts": null, + "code": null, + "options_hash": "7d95fc87d532420611fe415c1def27a3447617bb68235f46e3af130aea361c48", + "choice": "no", + "note": "Keep it local: a slot mark is the author's call, and sharing it is a separate act.", + "answered_at": "2026-11-12T19:02:00Z", + "received_at": "2026-11-12T19:02:05Z", + "claim_in_future": false, + "answerer": "e0d1c2b3-a495-4687-8879-6a5b4c3d2e1f", + "authority": "Operator", + "request_id": "00000001-0000-4000-8000-000000000001" + }, + "card": null + } + }, + { + "kind": "answer", + "body": { + "schemaVersion": "gamecult.fork.answer_request.v0", + "fork_id": "gamecult-chat:question:slot-mark-consent", + "options_hash": "7d95fc87d532420611fe415c1def27a3447617bb68235f46e3af130aea361c48", + "answer": { + "choice": "yes", + "note": "Share it, the audience is settled." + }, + "retracted": false, + "retracts": null, + "answered_at": "2026-11-12T19:05:00Z", + "request_id": "00000003-0000-4000-8000-000000000003" + }, + "server": { + "answer_id": "n3", + "answerer": "9f8e7d6c-5b4a-4392-8170-0f1e2d3c4b5a", + "authority": "Steward", + "received_at": "2026-11-12T19:05:05Z" + }, + "response": { + "schemaVersion": "gamecult.fork.answer_result.v0", + "answer": { + "schemaVersion": "gamecult.desk_answer.v0", + "answer_id": "n3", + "fork_id": "gamecult-chat:question:slot-mark-consent", + "outcome": "Revision", + "of": "n1", + "retracts": null, + "code": null, + "options_hash": "7d95fc87d532420611fe415c1def27a3447617bb68235f46e3af130aea361c48", + "choice": "yes", + "note": "Share it, the audience is settled.", + "answered_at": "2026-11-12T19:05:00Z", + "received_at": "2026-11-12T19:05:05Z", + "claim_in_future": false, + "answerer": "9f8e7d6c-5b4a-4392-8170-0f1e2d3c4b5a", + "authority": "Steward", + "request_id": "00000003-0000-4000-8000-000000000003" + }, + "card": null + } + } + ], + "record": { + "schemaVersion": "gamecult.fork.record.v0", + "fork_id": "gamecult-chat:question:slot-mark-consent", + "options_hash": "7d95fc87d532420611fe415c1def27a3447617bb68235f46e3af130aea361c48", + "counted_answer_id": "n1", + "answers": [ + { + "schemaVersion": "gamecult.desk_answer.v0", + "answer_id": "n1", + "fork_id": "gamecult-chat:question:slot-mark-consent", + "outcome": "Counted", + "of": null, + "retracts": null, + "code": null, + "options_hash": "7d95fc87d532420611fe415c1def27a3447617bb68235f46e3af130aea361c48", + "choice": "no", + "note": "Keep it local: a slot mark is the author's call, and sharing it is a separate act.", + "answered_at": "2026-11-12T19:02:00Z", + "received_at": "2026-11-12T19:02:05Z", + "claim_in_future": false, + "answerer": "e0d1c2b3-a495-4687-8879-6a5b4c3d2e1f", + "authority": "Operator", + "request_id": "00000001-0000-4000-8000-000000000001" + }, + { + "schemaVersion": "gamecult.desk_answer.v0", + "answer_id": "n3", + "fork_id": "gamecult-chat:question:slot-mark-consent", + "outcome": "Revision", + "of": "n1", + "retracts": null, + "code": null, + "options_hash": "7d95fc87d532420611fe415c1def27a3447617bb68235f46e3af130aea361c48", + "choice": "yes", + "note": "Share it, the audience is settled.", + "answered_at": "2026-11-12T19:05:00Z", + "received_at": "2026-11-12T19:05:05Z", + "claim_in_future": false, + "answerer": "9f8e7d6c-5b4a-4392-8170-0f1e2d3c4b5a", + "authority": "Steward", + "request_id": "00000003-0000-4000-8000-000000000003" + } + ] + }, + "recordHex": "85AD736368656D6156657273696F6EB767616D6563756C742E666F726B2E7265636F72642E7630A7666F726B5F6964D92867616D6563756C742D636861743A7175657374696F6E3A736C6F742D6D61726B2D636F6E73656E74AC6F7074696F6E735F68617368D94037643935666338376435333234323036313166653431356331646566323761333434373631376262363832333566343665336166313330616561333631633438B1636F756E7465645F616E737765725F6964A26E31A7616E737765727392DE0010AD736368656D6156657273696F6EB767616D6563756C742E6465736B5F616E737765722E7630A9616E737765725F6964A26E31A7666F726B5F6964D92867616D6563756C742D636861743A7175657374696F6E3A736C6F742D6D61726B2D636F6E73656E74A76F7574636F6D65A7436F756E746564A26F66C0A87265747261637473C0A4636F6465C0AC6F7074696F6E735F68617368D94037643935666338376435333234323036313166653431356331646566323761333434373631376262363832333566343665336166313330616561333631633438A663686F696365A26E6FA46E6F7465D9524B656570206974206C6F63616C3A206120736C6F74206D61726B2069732074686520617574686F7227732063616C6C2C20616E642073686172696E672069742069732061207365706172617465206163742EAB616E7377657265645F6174B4323032362D31312D31325431393A30323A30305AAB72656365697665645F6174B4323032362D31312D31325431393A30323A30355AAF636C61696D5F696E5F667574757265C2A8616E737765726572D92465306431633262332D613439352D343638372D383837392D366135623463336432653166A9617574686F72697479A84F70657261746F72AA726571756573745F6964D92430303030303030312D303030302D343030302D383030302D303030303030303030303031DE0010AD736368656D6156657273696F6EB767616D6563756C742E6465736B5F616E737765722E7630A9616E737765725F6964A26E33A7666F726B5F6964D92867616D6563756C742D636861743A7175657374696F6E3A736C6F742D6D61726B2D636F6E73656E74A76F7574636F6D65A85265766973696F6EA26F66A26E31A87265747261637473C0A4636F6465C0AC6F7074696F6E735F68617368D94037643935666338376435333234323036313166653431356331646566323761333434373631376262363832333566343665336166313330616561333631633438A663686F696365A3796573A46E6F7465D92253686172652069742C207468652061756469656E636520697320736574746C65642EAB616E7377657265645F6174B4323032362D31312D31325431393A30353A30305AAB72656365697665645F6174B4323032362D31312D31325431393A30353A30355AAF636C61696D5F696E5F667574757265C2A8616E737765726572D92439663865376436632D356234612D343339322D383137302D306631653264336334623561A9617574686F72697479A753746577617264AA726571756573745F6964D92430303030303030332D303030302D343030302D383030302D303030303030303030303033" + }, + { + "name": "a_claim_exactly_five_minutes_ahead_is_not_flagged", + "card": { + "schemaVersion": "gamecult.fork.card.v0", + "fork_id": "gamecult-chat:question:slot-mark-consent", + "title": "Slot mark consent", + "context": "Should a slot mark be shared?", + "options": [ + { + "id": "yes", + "text": "Share it." + }, + { + "id": "no", + "text": "Keep it local." + } + ], + "recommendation": "no", + "depends": [ + "gamecult-chat:question:file-zone-audience" + ], + "leans": null, + "flag": null, + "raised_in": { + "kind": "CutSpec", + "id": "cultlib-gaps:cut_spec:cut-slot-tolerance.r4" + }, + "deciders": [ + "e0d1c2b3-a495-4687-8879-6a5b4c3d2e1f" + ], + "status": "open", + "ruling": null, + "ruled_from": [], + "order": 3, + "options_hash": "7d95fc87d532420611fe415c1def27a3447617bb68235f46e3af130aea361c48" + }, + "optionsHash": "7d95fc87d532420611fe415c1def27a3447617bb68235f46e3af130aea361c48", + "steps": [ + { + "kind": "answer", + "body": { + "schemaVersion": "gamecult.fork.answer_request.v0", + "fork_id": "gamecult-chat:question:slot-mark-consent", + "options_hash": "7d95fc87d532420611fe415c1def27a3447617bb68235f46e3af130aea361c48", + "answer": { + "choice": "no", + "note": "Keep it local: a slot mark is the author's call, and sharing it is a separate act." + }, + "retracted": false, + "retracts": null, + "answered_at": "2026-11-12T19:07:05Z", + "request_id": "0000000c-0000-4000-8000-00000000000c" + }, + "server": { + "answer_id": "n1", + "answerer": "e0d1c2b3-a495-4687-8879-6a5b4c3d2e1f", + "authority": "Operator", + "received_at": "2026-11-12T19:02:05Z" + }, + "response": { + "schemaVersion": "gamecult.fork.answer_result.v0", + "answer": { + "schemaVersion": "gamecult.desk_answer.v0", + "answer_id": "n1", + "fork_id": "gamecult-chat:question:slot-mark-consent", + "outcome": "Counted", + "of": null, + "retracts": null, + "code": null, + "options_hash": "7d95fc87d532420611fe415c1def27a3447617bb68235f46e3af130aea361c48", + "choice": "no", + "note": "Keep it local: a slot mark is the author's call, and sharing it is a separate act.", + "answered_at": "2026-11-12T19:07:05Z", + "received_at": "2026-11-12T19:02:05Z", + "claim_in_future": false, + "answerer": "e0d1c2b3-a495-4687-8879-6a5b4c3d2e1f", + "authority": "Operator", + "request_id": "0000000c-0000-4000-8000-00000000000c" + }, + "card": null + } + } + ], + "record": { + "schemaVersion": "gamecult.fork.record.v0", + "fork_id": "gamecult-chat:question:slot-mark-consent", + "options_hash": "7d95fc87d532420611fe415c1def27a3447617bb68235f46e3af130aea361c48", + "counted_answer_id": "n1", + "answers": [ + { + "schemaVersion": "gamecult.desk_answer.v0", + "answer_id": "n1", + "fork_id": "gamecult-chat:question:slot-mark-consent", + "outcome": "Counted", + "of": null, + "retracts": null, + "code": null, + "options_hash": "7d95fc87d532420611fe415c1def27a3447617bb68235f46e3af130aea361c48", + "choice": "no", + "note": "Keep it local: a slot mark is the author's call, and sharing it is a separate act.", + "answered_at": "2026-11-12T19:07:05Z", + "received_at": "2026-11-12T19:02:05Z", + "claim_in_future": false, + "answerer": "e0d1c2b3-a495-4687-8879-6a5b4c3d2e1f", + "authority": "Operator", + "request_id": "0000000c-0000-4000-8000-00000000000c" + } + ] + }, + "recordHex": "85AD736368656D6156657273696F6EB767616D6563756C742E666F726B2E7265636F72642E7630A7666F726B5F6964D92867616D6563756C742D636861743A7175657374696F6E3A736C6F742D6D61726B2D636F6E73656E74AC6F7074696F6E735F68617368D94037643935666338376435333234323036313166653431356331646566323761333434373631376262363832333566343665336166313330616561333631633438B1636F756E7465645F616E737765725F6964A26E31A7616E737765727391DE0010AD736368656D6156657273696F6EB767616D6563756C742E6465736B5F616E737765722E7630A9616E737765725F6964A26E31A7666F726B5F6964D92867616D6563756C742D636861743A7175657374696F6E3A736C6F742D6D61726B2D636F6E73656E74A76F7574636F6D65A7436F756E746564A26F66C0A87265747261637473C0A4636F6465C0AC6F7074696F6E735F68617368D94037643935666338376435333234323036313166653431356331646566323761333434373631376262363832333566343665336166313330616561333631633438A663686F696365A26E6FA46E6F7465D9524B656570206974206C6F63616C3A206120736C6F74206D61726B2069732074686520617574686F7227732063616C6C2C20616E642073686172696E672069742069732061207365706172617465206163742EAB616E7377657265645F6174B4323032362D31312D31325431393A30373A30355AAB72656365697665645F6174B4323032362D31312D31325431393A30323A30355AAF636C61696D5F696E5F667574757265C2A8616E737765726572D92465306431633262332D613439352D343638372D383837392D366135623463336432653166A9617574686F72697479A84F70657261746F72AA726571756573745F6964D92430303030303030632D303030302D343030302D383030302D303030303030303030303063" + }, + { + "name": "a_claim_five_minutes_and_half_a_second_ahead_is_flagged_from_the_stored_received_at", + "card": { + "schemaVersion": "gamecult.fork.card.v0", + "fork_id": "gamecult-chat:question:slot-mark-consent", + "title": "Slot mark consent", + "context": "Should a slot mark be shared?", + "options": [ + { + "id": "yes", + "text": "Share it." + }, + { + "id": "no", + "text": "Keep it local." + } + ], + "recommendation": "no", + "depends": [ + "gamecult-chat:question:file-zone-audience" + ], + "leans": null, + "flag": null, + "raised_in": { + "kind": "CutSpec", + "id": "cultlib-gaps:cut_spec:cut-slot-tolerance.r4" + }, + "deciders": [ + "e0d1c2b3-a495-4687-8879-6a5b4c3d2e1f" + ], + "status": "open", + "ruling": null, + "ruled_from": [], + "order": 3, + "options_hash": "7d95fc87d532420611fe415c1def27a3447617bb68235f46e3af130aea361c48" + }, + "optionsHash": "7d95fc87d532420611fe415c1def27a3447617bb68235f46e3af130aea361c48", + "steps": [ + { + "kind": "answer", + "body": { + "schemaVersion": "gamecult.fork.answer_request.v0", + "fork_id": "gamecult-chat:question:slot-mark-consent", + "options_hash": "7d95fc87d532420611fe415c1def27a3447617bb68235f46e3af130aea361c48", + "answer": { + "choice": "no", + "note": "Keep it local: a slot mark is the author's call, and sharing it is a separate act." + }, + "retracted": false, + "retracts": null, + "answered_at": "2026-11-12T19:07:05.500Z", + "request_id": "0000000d-0000-4000-8000-00000000000d" + }, + "server": { + "answer_id": "n1", + "answerer": "e0d1c2b3-a495-4687-8879-6a5b4c3d2e1f", + "authority": "Operator", + "received_at": "2026-11-12T19:02:05Z" + }, + "response": { + "schemaVersion": "gamecult.fork.answer_result.v0", + "answer": { + "schemaVersion": "gamecult.desk_answer.v0", + "answer_id": "n1", + "fork_id": "gamecult-chat:question:slot-mark-consent", + "outcome": "Counted", + "of": null, + "retracts": null, + "code": null, + "options_hash": "7d95fc87d532420611fe415c1def27a3447617bb68235f46e3af130aea361c48", + "choice": "no", + "note": "Keep it local: a slot mark is the author's call, and sharing it is a separate act.", + "answered_at": "2026-11-12T19:07:05.500Z", + "received_at": "2026-11-12T19:02:05Z", + "claim_in_future": true, + "answerer": "e0d1c2b3-a495-4687-8879-6a5b4c3d2e1f", + "authority": "Operator", + "request_id": "0000000d-0000-4000-8000-00000000000d" + }, + "card": null + } + } + ], + "record": { + "schemaVersion": "gamecult.fork.record.v0", + "fork_id": "gamecult-chat:question:slot-mark-consent", + "options_hash": "7d95fc87d532420611fe415c1def27a3447617bb68235f46e3af130aea361c48", + "counted_answer_id": "n1", + "answers": [ + { + "schemaVersion": "gamecult.desk_answer.v0", + "answer_id": "n1", + "fork_id": "gamecult-chat:question:slot-mark-consent", + "outcome": "Counted", + "of": null, + "retracts": null, + "code": null, + "options_hash": "7d95fc87d532420611fe415c1def27a3447617bb68235f46e3af130aea361c48", + "choice": "no", + "note": "Keep it local: a slot mark is the author's call, and sharing it is a separate act.", + "answered_at": "2026-11-12T19:07:05.500Z", + "received_at": "2026-11-12T19:02:05Z", + "claim_in_future": true, + "answerer": "e0d1c2b3-a495-4687-8879-6a5b4c3d2e1f", + "authority": "Operator", + "request_id": "0000000d-0000-4000-8000-00000000000d" + } + ] + }, + "recordHex": "85AD736368656D6156657273696F6EB767616D6563756C742E666F726B2E7265636F72642E7630A7666F726B5F6964D92867616D6563756C742D636861743A7175657374696F6E3A736C6F742D6D61726B2D636F6E73656E74AC6F7074696F6E735F68617368D94037643935666338376435333234323036313166653431356331646566323761333434373631376262363832333566343665336166313330616561333631633438B1636F756E7465645F616E737765725F6964A26E31A7616E737765727391DE0010AD736368656D6156657273696F6EB767616D6563756C742E6465736B5F616E737765722E7630A9616E737765725F6964A26E31A7666F726B5F6964D92867616D6563756C742D636861743A7175657374696F6E3A736C6F742D6D61726B2D636F6E73656E74A76F7574636F6D65A7436F756E746564A26F66C0A87265747261637473C0A4636F6465C0AC6F7074696F6E735F68617368D94037643935666338376435333234323036313166653431356331646566323761333434373631376262363832333566343665336166313330616561333631633438A663686F696365A26E6FA46E6F7465D9524B656570206974206C6F63616C3A206120736C6F74206D61726B2069732074686520617574686F7227732063616C6C2C20616E642073686172696E672069742069732061207365706172617465206163742EAB616E7377657265645F6174B8323032362D31312D31325431393A30373A30352E3530305AAB72656365697665645F6174B4323032362D31312D31325431393A30323A30355AAF636C61696D5F696E5F667574757265C3A8616E737765726572D92465306431633262332D613439352D343638372D383837392D366135623463336432653166A9617574686F72697479A84F70657261746F72AA726571756573745F6964D92430303030303030642D303030302D343030302D383030302D303030303030303030303064" + }, + { + "name": "a_claim_more_than_five_minutes_ahead_is_accepted_and_flagged", + "card": { + "schemaVersion": "gamecult.fork.card.v0", + "fork_id": "gamecult-chat:question:slot-mark-consent", + "title": "Slot mark consent", + "context": "Should a slot mark be shared?", + "options": [ + { + "id": "yes", + "text": "Share it." + }, + { + "id": "no", + "text": "Keep it local." + } + ], + "recommendation": "no", + "depends": [ + "gamecult-chat:question:file-zone-audience" + ], + "leans": null, + "flag": null, + "raised_in": { + "kind": "CutSpec", + "id": "cultlib-gaps:cut_spec:cut-slot-tolerance.r4" + }, + "deciders": [ + "e0d1c2b3-a495-4687-8879-6a5b4c3d2e1f" + ], + "status": "open", + "ruling": null, + "ruled_from": [], + "order": 3, + "options_hash": "7d95fc87d532420611fe415c1def27a3447617bb68235f46e3af130aea361c48" + }, + "optionsHash": "7d95fc87d532420611fe415c1def27a3447617bb68235f46e3af130aea361c48", + "steps": [ + { + "kind": "answer", + "body": { + "schemaVersion": "gamecult.fork.answer_request.v0", + "fork_id": "gamecult-chat:question:slot-mark-consent", + "options_hash": "7d95fc87d532420611fe415c1def27a3447617bb68235f46e3af130aea361c48", + "answer": { + "choice": "no", + "note": "Keep it local: a slot mark is the author's call, and sharing it is a separate act." + }, + "retracted": false, + "retracts": null, + "answered_at": "2026-11-12T19:09:00Z", + "request_id": "0000000e-0000-4000-8000-00000000000e" + }, + "server": { + "answer_id": "n1", + "answerer": "e0d1c2b3-a495-4687-8879-6a5b4c3d2e1f", + "authority": "Operator", + "received_at": "2026-11-12T19:02:05Z" + }, + "response": { + "schemaVersion": "gamecult.fork.answer_result.v0", + "answer": { + "schemaVersion": "gamecult.desk_answer.v0", + "answer_id": "n1", + "fork_id": "gamecult-chat:question:slot-mark-consent", + "outcome": "Counted", + "of": null, + "retracts": null, + "code": null, + "options_hash": "7d95fc87d532420611fe415c1def27a3447617bb68235f46e3af130aea361c48", + "choice": "no", + "note": "Keep it local: a slot mark is the author's call, and sharing it is a separate act.", + "answered_at": "2026-11-12T19:09:00Z", + "received_at": "2026-11-12T19:02:05Z", + "claim_in_future": true, + "answerer": "e0d1c2b3-a495-4687-8879-6a5b4c3d2e1f", + "authority": "Operator", + "request_id": "0000000e-0000-4000-8000-00000000000e" + }, + "card": null + } + } + ], + "record": { + "schemaVersion": "gamecult.fork.record.v0", + "fork_id": "gamecult-chat:question:slot-mark-consent", + "options_hash": "7d95fc87d532420611fe415c1def27a3447617bb68235f46e3af130aea361c48", + "counted_answer_id": "n1", + "answers": [ + { + "schemaVersion": "gamecult.desk_answer.v0", + "answer_id": "n1", + "fork_id": "gamecult-chat:question:slot-mark-consent", + "outcome": "Counted", + "of": null, + "retracts": null, + "code": null, + "options_hash": "7d95fc87d532420611fe415c1def27a3447617bb68235f46e3af130aea361c48", + "choice": "no", + "note": "Keep it local: a slot mark is the author's call, and sharing it is a separate act.", + "answered_at": "2026-11-12T19:09:00Z", + "received_at": "2026-11-12T19:02:05Z", + "claim_in_future": true, + "answerer": "e0d1c2b3-a495-4687-8879-6a5b4c3d2e1f", + "authority": "Operator", + "request_id": "0000000e-0000-4000-8000-00000000000e" + } + ] + }, + "recordHex": "85AD736368656D6156657273696F6EB767616D6563756C742E666F726B2E7265636F72642E7630A7666F726B5F6964D92867616D6563756C742D636861743A7175657374696F6E3A736C6F742D6D61726B2D636F6E73656E74AC6F7074696F6E735F68617368D94037643935666338376435333234323036313166653431356331646566323761333434373631376262363832333566343665336166313330616561333631633438B1636F756E7465645F616E737765725F6964A26E31A7616E737765727391DE0010AD736368656D6156657273696F6EB767616D6563756C742E6465736B5F616E737765722E7630A9616E737765725F6964A26E31A7666F726B5F6964D92867616D6563756C742D636861743A7175657374696F6E3A736C6F742D6D61726B2D636F6E73656E74A76F7574636F6D65A7436F756E746564A26F66C0A87265747261637473C0A4636F6465C0AC6F7074696F6E735F68617368D94037643935666338376435333234323036313166653431356331646566323761333434373631376262363832333566343665336166313330616561333631633438A663686F696365A26E6FA46E6F7465D9524B656570206974206C6F63616C3A206120736C6F74206D61726B2069732074686520617574686F7227732063616C6C2C20616E642073686172696E672069742069732061207365706172617465206163742EAB616E7377657265645F6174B4323032362D31312D31325431393A30393A30305AAB72656365697665645F6174B4323032362D31312D31325431393A30323A30355AAF636C61696D5F696E5F667574757265C3A8616E737765726572D92465306431633262332D613439352D343638372D383837392D366135623463336432653166A9617574686F72697479A84F70657261746F72AA726571756573745F6964D92430303030303030652D303030302D343030302D383030302D303030303030303030303065" + }, + { + "name": "V6_a_matrix_edit_redaction_or_reaction_changes_nothing", + "card": { + "schemaVersion": "gamecult.fork.card.v0", + "fork_id": "gamecult-chat:question:slot-mark-consent", + "title": "Slot mark consent", + "context": "Should a slot mark be shared?", + "options": [ + { + "id": "yes", + "text": "Share it." + }, + { + "id": "no", + "text": "Keep it local." + } + ], + "recommendation": "no", + "depends": [ + "gamecult-chat:question:file-zone-audience" + ], + "leans": null, + "flag": null, + "raised_in": { + "kind": "CutSpec", + "id": "cultlib-gaps:cut_spec:cut-slot-tolerance.r4" + }, + "deciders": [ + "e0d1c2b3-a495-4687-8879-6a5b4c3d2e1f" + ], + "status": "open", + "ruling": null, + "ruled_from": [], + "order": 3, + "options_hash": "7d95fc87d532420611fe415c1def27a3447617bb68235f46e3af130aea361c48" + }, + "optionsHash": "7d95fc87d532420611fe415c1def27a3447617bb68235f46e3af130aea361c48", + "steps": [ + { + "kind": "answer", + "body": { + "schemaVersion": "gamecult.fork.answer_request.v0", + "fork_id": "gamecult-chat:question:slot-mark-consent", + "options_hash": "7d95fc87d532420611fe415c1def27a3447617bb68235f46e3af130aea361c48", + "answer": { + "choice": "no", + "note": "Keep it local: a slot mark is the author's call, and sharing it is a separate act." + }, + "retracted": false, + "retracts": null, + "answered_at": "2026-11-12T19:02:00Z", + "request_id": "00000001-0000-4000-8000-000000000001" + }, + "server": { + "answer_id": "n1", + "answerer": "e0d1c2b3-a495-4687-8879-6a5b4c3d2e1f", + "authority": "Operator", + "received_at": "2026-11-12T19:02:05Z" + }, + "response": { + "schemaVersion": "gamecult.fork.answer_result.v0", + "answer": { + "schemaVersion": "gamecult.desk_answer.v0", + "answer_id": "n1", + "fork_id": "gamecult-chat:question:slot-mark-consent", + "outcome": "Counted", + "of": null, + "retracts": null, + "code": null, + "options_hash": "7d95fc87d532420611fe415c1def27a3447617bb68235f46e3af130aea361c48", + "choice": "no", + "note": "Keep it local: a slot mark is the author's call, and sharing it is a separate act.", + "answered_at": "2026-11-12T19:02:00Z", + "received_at": "2026-11-12T19:02:05Z", + "claim_in_future": false, + "answerer": "e0d1c2b3-a495-4687-8879-6a5b4c3d2e1f", + "authority": "Operator", + "request_id": "00000001-0000-4000-8000-000000000001" + }, + "card": null + } + }, + { + "kind": "matrix_event", + "body": null, + "server": null, + "response": null, + "matrix_kind": "m.replace", + "choice": "yes" + }, + { + "kind": "matrix_event", + "body": null, + "server": null, + "response": null, + "matrix_kind": "m.redaction", + "choice": null + }, + { + "kind": "matrix_event", + "body": null, + "server": null, + "response": null, + "matrix_kind": "m.reaction", + "choice": "yes" + } + ], + "record": { + "schemaVersion": "gamecult.fork.record.v0", + "fork_id": "gamecult-chat:question:slot-mark-consent", + "options_hash": "7d95fc87d532420611fe415c1def27a3447617bb68235f46e3af130aea361c48", + "counted_answer_id": "n1", + "answers": [ + { + "schemaVersion": "gamecult.desk_answer.v0", + "answer_id": "n1", + "fork_id": "gamecult-chat:question:slot-mark-consent", + "outcome": "Counted", + "of": null, + "retracts": null, + "code": null, + "options_hash": "7d95fc87d532420611fe415c1def27a3447617bb68235f46e3af130aea361c48", + "choice": "no", + "note": "Keep it local: a slot mark is the author's call, and sharing it is a separate act.", + "answered_at": "2026-11-12T19:02:00Z", + "received_at": "2026-11-12T19:02:05Z", + "claim_in_future": false, + "answerer": "e0d1c2b3-a495-4687-8879-6a5b4c3d2e1f", + "authority": "Operator", + "request_id": "00000001-0000-4000-8000-000000000001" + } + ] + }, + "recordHex": "85AD736368656D6156657273696F6EB767616D6563756C742E666F726B2E7265636F72642E7630A7666F726B5F6964D92867616D6563756C742D636861743A7175657374696F6E3A736C6F742D6D61726B2D636F6E73656E74AC6F7074696F6E735F68617368D94037643935666338376435333234323036313166653431356331646566323761333434373631376262363832333566343665336166313330616561333631633438B1636F756E7465645F616E737765725F6964A26E31A7616E737765727391DE0010AD736368656D6156657273696F6EB767616D6563756C742E6465736B5F616E737765722E7630A9616E737765725F6964A26E31A7666F726B5F6964D92867616D6563756C742D636861743A7175657374696F6E3A736C6F742D6D61726B2D636F6E73656E74A76F7574636F6D65A7436F756E746564A26F66C0A87265747261637473C0A4636F6465C0AC6F7074696F6E735F68617368D94037643935666338376435333234323036313166653431356331646566323761333434373631376262363832333566343665336166313330616561333631633438A663686F696365A26E6FA46E6F7465D9524B656570206974206C6F63616C3A206120736C6F74206D61726B2069732074686520617574686F7227732063616C6C2C20616E642073686172696E672069742069732061207365706172617465206163742EAB616E7377657265645F6174B4323032362D31312D31325431393A30323A30305AAB72656365697665645F6174B4323032362D31312D31325431393A30323A30355AAF636C61696D5F696E5F667574757265C2A8616E737765726572D92465306431633262332D613439352D343638372D383837392D366135623463336432653166A9617574686F72697479A84F70657261746F72AA726571756573745F6964D92430303030303030312D303030302D343030302D383030302D303030303030303030303031" + }, + { + "name": "V7_a_choice_that_is_not_an_option_is_Refused_choice_invalid", + "card": { + "schemaVersion": "gamecult.fork.card.v0", + "fork_id": "gamecult-chat:question:slot-mark-consent", + "title": "Slot mark consent", + "context": "Should a slot mark be shared?", + "options": [ + { + "id": "yes", + "text": "Share it." + }, + { + "id": "no", + "text": "Keep it local." + } + ], + "recommendation": "no", + "depends": [ + "gamecult-chat:question:file-zone-audience" + ], + "leans": null, + "flag": null, + "raised_in": { + "kind": "CutSpec", + "id": "cultlib-gaps:cut_spec:cut-slot-tolerance.r4" + }, + "deciders": [ + "e0d1c2b3-a495-4687-8879-6a5b4c3d2e1f" + ], + "status": "open", + "ruling": null, + "ruled_from": [], + "order": 3, + "options_hash": "7d95fc87d532420611fe415c1def27a3447617bb68235f46e3af130aea361c48" + }, + "optionsHash": "7d95fc87d532420611fe415c1def27a3447617bb68235f46e3af130aea361c48", + "steps": [ + { + "kind": "answer", + "body": { + "schemaVersion": "gamecult.fork.answer_request.v0", + "fork_id": "gamecult-chat:question:slot-mark-consent", + "options_hash": "7d95fc87d532420611fe415c1def27a3447617bb68235f46e3af130aea361c48", + "answer": { + "choice": "maybe", + "note": null + }, + "retracted": false, + "retracts": null, + "answered_at": "2026-11-12T19:02:00Z", + "request_id": "0000000f-0000-4000-8000-00000000000f" + }, + "server": { + "answer_id": "n1", + "answerer": "e0d1c2b3-a495-4687-8879-6a5b4c3d2e1f", + "authority": "Operator", + "received_at": "2026-11-12T19:02:05Z" + }, + "response": { + "schemaVersion": "gamecult.fork.answer_result.v0", + "answer": { + "schemaVersion": "gamecult.desk_answer.v0", + "answer_id": "n1", + "fork_id": "gamecult-chat:question:slot-mark-consent", + "outcome": "Refused", + "of": null, + "retracts": null, + "code": "choice_invalid", + "options_hash": "7d95fc87d532420611fe415c1def27a3447617bb68235f46e3af130aea361c48", + "choice": "maybe", + "note": null, + "answered_at": "2026-11-12T19:02:00Z", + "received_at": "2026-11-12T19:02:05Z", + "claim_in_future": false, + "answerer": "e0d1c2b3-a495-4687-8879-6a5b4c3d2e1f", + "authority": "Operator", + "request_id": "0000000f-0000-4000-8000-00000000000f" + }, + "card": null + } + } + ], + "record": { + "schemaVersion": "gamecult.fork.record.v0", + "fork_id": "gamecult-chat:question:slot-mark-consent", + "options_hash": "7d95fc87d532420611fe415c1def27a3447617bb68235f46e3af130aea361c48", + "counted_answer_id": null, + "answers": [ + { + "schemaVersion": "gamecult.desk_answer.v0", + "answer_id": "n1", + "fork_id": "gamecult-chat:question:slot-mark-consent", + "outcome": "Refused", + "of": null, + "retracts": null, + "code": "choice_invalid", + "options_hash": "7d95fc87d532420611fe415c1def27a3447617bb68235f46e3af130aea361c48", + "choice": "maybe", + "note": null, + "answered_at": "2026-11-12T19:02:00Z", + "received_at": "2026-11-12T19:02:05Z", + "claim_in_future": false, + "answerer": "e0d1c2b3-a495-4687-8879-6a5b4c3d2e1f", + "authority": "Operator", + "request_id": "0000000f-0000-4000-8000-00000000000f" + } + ] + }, + "recordHex": "85AD736368656D6156657273696F6EB767616D6563756C742E666F726B2E7265636F72642E7630A7666F726B5F6964D92867616D6563756C742D636861743A7175657374696F6E3A736C6F742D6D61726B2D636F6E73656E74AC6F7074696F6E735F68617368D94037643935666338376435333234323036313166653431356331646566323761333434373631376262363832333566343665336166313330616561333631633438B1636F756E7465645F616E737765725F6964C0A7616E737765727391DE0010AD736368656D6156657273696F6EB767616D6563756C742E6465736B5F616E737765722E7630A9616E737765725F6964A26E31A7666F726B5F6964D92867616D6563756C742D636861743A7175657374696F6E3A736C6F742D6D61726B2D636F6E73656E74A76F7574636F6D65A752656675736564A26F66C0A87265747261637473C0A4636F6465AE63686F6963655F696E76616C6964AC6F7074696F6E735F68617368D94037643935666338376435333234323036313166653431356331646566323761333434373631376262363832333566343665336166313330616561333631633438A663686F696365A56D61796265A46E6F7465C0AB616E7377657265645F6174B4323032362D31312D31325431393A30323A30305AAB72656365697665645F6174B4323032362D31312D31325431393A30323A30355AAF636C61696D5F696E5F667574757265C2A8616E737765726572D92465306431633262332D613439352D343638372D383837392D366135623463336432653166A9617574686F72697479A84F70657261746F72AA726571756573745F6964D92430303030303030662D303030302D343030302D383030302D303030303030303030303066" + }, + { + "name": "a_changed_card_stops_the_old_answers_counting_and_a_new_answer_is_counted", + "card": { + "schemaVersion": "gamecult.fork.card.v0", + "fork_id": "gamecult-chat:question:slot-mark-consent", + "title": "Slot mark consent", + "context": "Should a slot mark be shared?", + "options": [ + { + "id": "yes", + "text": "Share it." + }, + { + "id": "no", + "text": "Keep it local." + } + ], + "recommendation": "no", + "depends": [ + "gamecult-chat:question:file-zone-audience" + ], + "leans": null, + "flag": null, + "raised_in": { + "kind": "CutSpec", + "id": "cultlib-gaps:cut_spec:cut-slot-tolerance.r4" + }, + "deciders": [ + "e0d1c2b3-a495-4687-8879-6a5b4c3d2e1f" + ], + "status": "open", + "ruling": null, + "ruled_from": [], + "order": 3, + "options_hash": "7d95fc87d532420611fe415c1def27a3447617bb68235f46e3af130aea361c48" + }, + "optionsHash": "7d95fc87d532420611fe415c1def27a3447617bb68235f46e3af130aea361c48", + "steps": [ + { + "kind": "answer", + "body": { + "schemaVersion": "gamecult.fork.answer_request.v0", + "fork_id": "gamecult-chat:question:slot-mark-consent", + "options_hash": "7d95fc87d532420611fe415c1def27a3447617bb68235f46e3af130aea361c48", + "answer": { + "choice": "no", + "note": "Keep it local: a slot mark is the author's call, and sharing it is a separate act." + }, + "retracted": false, + "retracts": null, + "answered_at": "2026-11-12T19:02:00Z", + "request_id": "00000001-0000-4000-8000-000000000001" + }, + "server": { + "answer_id": "n1", + "answerer": "e0d1c2b3-a495-4687-8879-6a5b4c3d2e1f", + "authority": "Operator", + "received_at": "2026-11-12T19:02:05Z" + }, + "response": { + "schemaVersion": "gamecult.fork.answer_result.v0", + "answer": { + "schemaVersion": "gamecult.desk_answer.v0", + "answer_id": "n1", + "fork_id": "gamecult-chat:question:slot-mark-consent", + "outcome": "Counted", + "of": null, + "retracts": null, + "code": null, + "options_hash": "7d95fc87d532420611fe415c1def27a3447617bb68235f46e3af130aea361c48", + "choice": "no", + "note": "Keep it local: a slot mark is the author's call, and sharing it is a separate act.", + "answered_at": "2026-11-12T19:02:00Z", + "received_at": "2026-11-12T19:02:05Z", + "claim_in_future": false, + "answerer": "e0d1c2b3-a495-4687-8879-6a5b4c3d2e1f", + "authority": "Operator", + "request_id": "00000001-0000-4000-8000-000000000001" + }, + "card": null + } + }, + { + "kind": "card", + "body": null, + "server": null, + "response": null, + "card": { + "schemaVersion": "gamecult.fork.card.v0", + "fork_id": "gamecult-chat:question:slot-mark-consent", + "title": "Slot mark consent (revised)", + "context": "Should a slot mark be shared?", + "options": [ + { + "id": "yes", + "text": "Share it." + }, + { + "id": "no", + "text": "Keep it local." + } + ], + "recommendation": "no", + "depends": [ + "gamecult-chat:question:file-zone-audience" + ], + "leans": null, + "flag": null, + "raised_in": { + "kind": "CutSpec", + "id": "cultlib-gaps:cut_spec:cut-slot-tolerance.r4" + }, + "deciders": [ + "e0d1c2b3-a495-4687-8879-6a5b4c3d2e1f" + ], + "status": "open", + "ruling": null, + "ruled_from": [], + "order": 3, + "options_hash": "e7696ae1d487f78b5dc99fb8ccbd684d6e6f1ea7d7e190c21f3c6b4d8c42aaf7" + } + }, + { + "kind": "answer", + "body": { + "schemaVersion": "gamecult.fork.answer_request.v0", + "fork_id": "gamecult-chat:question:slot-mark-consent", + "options_hash": "e7696ae1d487f78b5dc99fb8ccbd684d6e6f1ea7d7e190c21f3c6b4d8c42aaf7", + "answer": { + "choice": "no", + "note": "Keep it local: a slot mark is the author's call, and sharing it is a separate act." + }, + "retracted": false, + "retracts": null, + "answered_at": "2026-11-12T19:02:00Z", + "request_id": "00000010-0000-4000-8000-000000000010" + }, + "server": { + "answer_id": "n2", + "answerer": "e0d1c2b3-a495-4687-8879-6a5b4c3d2e1f", + "authority": "Operator", + "received_at": "2026-11-12T19:06:05Z" + }, + "response": { + "schemaVersion": "gamecult.fork.answer_result.v0", + "answer": { + "schemaVersion": "gamecult.desk_answer.v0", + "answer_id": "n2", + "fork_id": "gamecult-chat:question:slot-mark-consent", + "outcome": "Counted", + "of": null, + "retracts": null, + "code": null, + "options_hash": "e7696ae1d487f78b5dc99fb8ccbd684d6e6f1ea7d7e190c21f3c6b4d8c42aaf7", + "choice": "no", + "note": "Keep it local: a slot mark is the author's call, and sharing it is a separate act.", + "answered_at": "2026-11-12T19:02:00Z", + "received_at": "2026-11-12T19:06:05Z", + "claim_in_future": false, + "answerer": "e0d1c2b3-a495-4687-8879-6a5b4c3d2e1f", + "authority": "Operator", + "request_id": "00000010-0000-4000-8000-000000000010" + }, + "card": null + } + } + ], + "record": { + "schemaVersion": "gamecult.fork.record.v0", + "fork_id": "gamecult-chat:question:slot-mark-consent", + "options_hash": "e7696ae1d487f78b5dc99fb8ccbd684d6e6f1ea7d7e190c21f3c6b4d8c42aaf7", + "counted_answer_id": "n2", + "answers": [ + { + "schemaVersion": "gamecult.desk_answer.v0", + "answer_id": "n1", + "fork_id": "gamecult-chat:question:slot-mark-consent", + "outcome": "Counted", + "of": null, + "retracts": null, + "code": null, + "options_hash": "7d95fc87d532420611fe415c1def27a3447617bb68235f46e3af130aea361c48", + "choice": "no", + "note": "Keep it local: a slot mark is the author's call, and sharing it is a separate act.", + "answered_at": "2026-11-12T19:02:00Z", + "received_at": "2026-11-12T19:02:05Z", + "claim_in_future": false, + "answerer": "e0d1c2b3-a495-4687-8879-6a5b4c3d2e1f", + "authority": "Operator", + "request_id": "00000001-0000-4000-8000-000000000001" + }, + { + "schemaVersion": "gamecult.desk_answer.v0", + "answer_id": "n2", + "fork_id": "gamecult-chat:question:slot-mark-consent", + "outcome": "Counted", + "of": null, + "retracts": null, + "code": null, + "options_hash": "e7696ae1d487f78b5dc99fb8ccbd684d6e6f1ea7d7e190c21f3c6b4d8c42aaf7", + "choice": "no", + "note": "Keep it local: a slot mark is the author's call, and sharing it is a separate act.", + "answered_at": "2026-11-12T19:02:00Z", + "received_at": "2026-11-12T19:06:05Z", + "claim_in_future": false, + "answerer": "e0d1c2b3-a495-4687-8879-6a5b4c3d2e1f", + "authority": "Operator", + "request_id": "00000010-0000-4000-8000-000000000010" + } + ] + }, + "recordHex": "85AD736368656D6156657273696F6EB767616D6563756C742E666F726B2E7265636F72642E7630A7666F726B5F6964D92867616D6563756C742D636861743A7175657374696F6E3A736C6F742D6D61726B2D636F6E73656E74AC6F7074696F6E735F68617368D94065373639366165316434383766373862356463393966623863636264363834643665366631656137643765313930633231663363366234643863343261616637B1636F756E7465645F616E737765725F6964A26E32A7616E737765727392DE0010AD736368656D6156657273696F6EB767616D6563756C742E6465736B5F616E737765722E7630A9616E737765725F6964A26E31A7666F726B5F6964D92867616D6563756C742D636861743A7175657374696F6E3A736C6F742D6D61726B2D636F6E73656E74A76F7574636F6D65A7436F756E746564A26F66C0A87265747261637473C0A4636F6465C0AC6F7074696F6E735F68617368D94037643935666338376435333234323036313166653431356331646566323761333434373631376262363832333566343665336166313330616561333631633438A663686F696365A26E6FA46E6F7465D9524B656570206974206C6F63616C3A206120736C6F74206D61726B2069732074686520617574686F7227732063616C6C2C20616E642073686172696E672069742069732061207365706172617465206163742EAB616E7377657265645F6174B4323032362D31312D31325431393A30323A30305AAB72656365697665645F6174B4323032362D31312D31325431393A30323A30355AAF636C61696D5F696E5F667574757265C2A8616E737765726572D92465306431633262332D613439352D343638372D383837392D366135623463336432653166A9617574686F72697479A84F70657261746F72AA726571756573745F6964D92430303030303030312D303030302D343030302D383030302D303030303030303030303031DE0010AD736368656D6156657273696F6EB767616D6563756C742E6465736B5F616E737765722E7630A9616E737765725F6964A26E32A7666F726B5F6964D92867616D6563756C742D636861743A7175657374696F6E3A736C6F742D6D61726B2D636F6E73656E74A76F7574636F6D65A7436F756E746564A26F66C0A87265747261637473C0A4636F6465C0AC6F7074696F6E735F68617368D94065373639366165316434383766373862356463393966623863636264363834643665366631656137643765313930633231663363366234643863343261616637A663686F696365A26E6FA46E6F7465D9524B656570206974206C6F63616C3A206120736C6F74206D61726B2069732074686520617574686F7227732063616C6C2C20616E642073686172696E672069742069732061207365706172617465206163742EAB616E7377657265645F6174B4323032362D31312D31325431393A30323A30305AAB72656365697665645F6174B4323032362D31312D31325431393A30363A30355AAF636C61696D5F696E5F667574757265C2A8616E737765726572D92465306431633262332D613439352D343638372D383837392D366135623463336432653166A9617574686F72697479A84F70657261746F72AA726571756573745F6964D92430303030303031302D303030302D343030302D383030302D303030303030303030303130" + }, + { + "name": "a_changed_card_leaves_no_counted_answer_until_one_arrives", + "card": { + "schemaVersion": "gamecult.fork.card.v0", + "fork_id": "gamecult-chat:question:slot-mark-consent", + "title": "Slot mark consent", + "context": "Should a slot mark be shared?", + "options": [ + { + "id": "yes", + "text": "Share it." + }, + { + "id": "no", + "text": "Keep it local." + } + ], + "recommendation": "no", + "depends": [ + "gamecult-chat:question:file-zone-audience" + ], + "leans": null, + "flag": null, + "raised_in": { + "kind": "CutSpec", + "id": "cultlib-gaps:cut_spec:cut-slot-tolerance.r4" + }, + "deciders": [ + "e0d1c2b3-a495-4687-8879-6a5b4c3d2e1f" + ], + "status": "open", + "ruling": null, + "ruled_from": [], + "order": 3, + "options_hash": "7d95fc87d532420611fe415c1def27a3447617bb68235f46e3af130aea361c48" + }, + "optionsHash": "7d95fc87d532420611fe415c1def27a3447617bb68235f46e3af130aea361c48", + "steps": [ + { + "kind": "answer", + "body": { + "schemaVersion": "gamecult.fork.answer_request.v0", + "fork_id": "gamecult-chat:question:slot-mark-consent", + "options_hash": "7d95fc87d532420611fe415c1def27a3447617bb68235f46e3af130aea361c48", + "answer": { + "choice": "no", + "note": "Keep it local: a slot mark is the author's call, and sharing it is a separate act." + }, + "retracted": false, + "retracts": null, + "answered_at": "2026-11-12T19:02:00Z", + "request_id": "00000001-0000-4000-8000-000000000001" + }, + "server": { + "answer_id": "n1", + "answerer": "e0d1c2b3-a495-4687-8879-6a5b4c3d2e1f", + "authority": "Operator", + "received_at": "2026-11-12T19:02:05Z" + }, + "response": { + "schemaVersion": "gamecult.fork.answer_result.v0", + "answer": { + "schemaVersion": "gamecult.desk_answer.v0", + "answer_id": "n1", + "fork_id": "gamecult-chat:question:slot-mark-consent", + "outcome": "Counted", + "of": null, + "retracts": null, + "code": null, + "options_hash": "7d95fc87d532420611fe415c1def27a3447617bb68235f46e3af130aea361c48", + "choice": "no", + "note": "Keep it local: a slot mark is the author's call, and sharing it is a separate act.", + "answered_at": "2026-11-12T19:02:00Z", + "received_at": "2026-11-12T19:02:05Z", + "claim_in_future": false, + "answerer": "e0d1c2b3-a495-4687-8879-6a5b4c3d2e1f", + "authority": "Operator", + "request_id": "00000001-0000-4000-8000-000000000001" + }, + "card": null + } + }, + { + "kind": "card", + "body": null, + "server": null, + "response": null, + "card": { + "schemaVersion": "gamecult.fork.card.v0", + "fork_id": "gamecult-chat:question:slot-mark-consent", + "title": "Slot mark consent (revised)", + "context": "Should a slot mark be shared?", + "options": [ + { + "id": "yes", + "text": "Share it." + }, + { + "id": "no", + "text": "Keep it local." + } + ], + "recommendation": "no", + "depends": [ + "gamecult-chat:question:file-zone-audience" + ], + "leans": null, + "flag": null, + "raised_in": { + "kind": "CutSpec", + "id": "cultlib-gaps:cut_spec:cut-slot-tolerance.r4" + }, + "deciders": [ + "e0d1c2b3-a495-4687-8879-6a5b4c3d2e1f" + ], + "status": "open", + "ruling": null, + "ruled_from": [], + "order": 3, + "options_hash": "e7696ae1d487f78b5dc99fb8ccbd684d6e6f1ea7d7e190c21f3c6b4d8c42aaf7" + } + } + ], + "record": { + "schemaVersion": "gamecult.fork.record.v0", + "fork_id": "gamecult-chat:question:slot-mark-consent", + "options_hash": "e7696ae1d487f78b5dc99fb8ccbd684d6e6f1ea7d7e190c21f3c6b4d8c42aaf7", + "counted_answer_id": null, + "answers": [ + { + "schemaVersion": "gamecult.desk_answer.v0", + "answer_id": "n1", + "fork_id": "gamecult-chat:question:slot-mark-consent", + "outcome": "Counted", + "of": null, + "retracts": null, + "code": null, + "options_hash": "7d95fc87d532420611fe415c1def27a3447617bb68235f46e3af130aea361c48", + "choice": "no", + "note": "Keep it local: a slot mark is the author's call, and sharing it is a separate act.", + "answered_at": "2026-11-12T19:02:00Z", + "received_at": "2026-11-12T19:02:05Z", + "claim_in_future": false, + "answerer": "e0d1c2b3-a495-4687-8879-6a5b4c3d2e1f", + "authority": "Operator", + "request_id": "00000001-0000-4000-8000-000000000001" + } + ] + }, + "recordHex": "85AD736368656D6156657273696F6EB767616D6563756C742E666F726B2E7265636F72642E7630A7666F726B5F6964D92867616D6563756C742D636861743A7175657374696F6E3A736C6F742D6D61726B2D636F6E73656E74AC6F7074696F6E735F68617368D94065373639366165316434383766373862356463393966623863636264363834643665366631656137643765313930633231663363366234643863343261616637B1636F756E7465645F616E737765725F6964C0A7616E737765727391DE0010AD736368656D6156657273696F6EB767616D6563756C742E6465736B5F616E737765722E7630A9616E737765725F6964A26E31A7666F726B5F6964D92867616D6563756C742D636861743A7175657374696F6E3A736C6F742D6D61726B2D636F6E73656E74A76F7574636F6D65A7436F756E746564A26F66C0A87265747261637473C0A4636F6465C0AC6F7074696F6E735F68617368D94037643935666338376435333234323036313166653431356331646566323761333434373631376262363832333566343665336166313330616561333631633438A663686F696365A26E6FA46E6F7465D9524B656570206974206C6F63616C3A206120736C6F74206D61726B2069732074686520617574686F7227732063616C6C2C20616E642073686172696E672069742069732061207365706172617465206163742EAB616E7377657265645F6174B4323032362D31312D31325431393A30323A30305AAB72656365697665645F6174B4323032362D31312D31325431393A30323A30355AAF636C61696D5F696E5F667574757265C2A8616E737765726572D92465306431633262332D613439352D343638372D383837392D366135623463336432653166A9617574686F72697479A84F70657261746F72AA726571756573745F6964D92430303030303030312D303030302D343030302D383030302D303030303030303030303031" + }, + { + "name": "V8_retraction_with_an_answer_is_bad", + "card": { + "schemaVersion": "gamecult.fork.card.v0", + "fork_id": "gamecult-chat:question:slot-mark-consent", + "title": "Slot mark consent", + "context": "Should a slot mark be shared?", + "options": [ + { + "id": "yes", + "text": "Share it." + }, + { + "id": "no", + "text": "Keep it local." + } + ], + "recommendation": "no", + "depends": [ + "gamecult-chat:question:file-zone-audience" + ], + "leans": null, + "flag": null, + "raised_in": { + "kind": "CutSpec", + "id": "cultlib-gaps:cut_spec:cut-slot-tolerance.r4" + }, + "deciders": [ + "e0d1c2b3-a495-4687-8879-6a5b4c3d2e1f" + ], + "status": "open", + "ruling": null, + "ruled_from": [], + "order": 3, + "options_hash": "7d95fc87d532420611fe415c1def27a3447617bb68235f46e3af130aea361c48" + }, + "optionsHash": "7d95fc87d532420611fe415c1def27a3447617bb68235f46e3af130aea361c48", + "steps": [ + { + "kind": "answer", + "body": { + "schemaVersion": "gamecult.fork.answer_request.v0", + "fork_id": "gamecult-chat:question:slot-mark-consent", + "options_hash": "7d95fc87d532420611fe415c1def27a3447617bb68235f46e3af130aea361c48", + "answer": { + "choice": "no", + "note": "Keep it local: a slot mark is the author's call, and sharing it is a separate act." + }, + "retracted": false, + "retracts": null, + "answered_at": "2026-11-12T19:02:00Z", + "request_id": "00000001-0000-4000-8000-000000000001" + }, + "server": { + "answer_id": "n1", + "answerer": "e0d1c2b3-a495-4687-8879-6a5b4c3d2e1f", + "authority": "Operator", + "received_at": "2026-11-12T19:02:05Z" + }, + "response": { + "schemaVersion": "gamecult.fork.answer_result.v0", + "answer": { + "schemaVersion": "gamecult.desk_answer.v0", + "answer_id": "n1", + "fork_id": "gamecult-chat:question:slot-mark-consent", + "outcome": "Counted", + "of": null, + "retracts": null, + "code": null, + "options_hash": "7d95fc87d532420611fe415c1def27a3447617bb68235f46e3af130aea361c48", + "choice": "no", + "note": "Keep it local: a slot mark is the author's call, and sharing it is a separate act.", + "answered_at": "2026-11-12T19:02:00Z", + "received_at": "2026-11-12T19:02:05Z", + "claim_in_future": false, + "answerer": "e0d1c2b3-a495-4687-8879-6a5b4c3d2e1f", + "authority": "Operator", + "request_id": "00000001-0000-4000-8000-000000000001" + }, + "card": null + } + }, + { + "kind": "answer", + "body": { + "schemaVersion": "gamecult.fork.answer_request.v0", + "fork_id": "gamecult-chat:question:slot-mark-consent", + "options_hash": "7d95fc87d532420611fe415c1def27a3447617bb68235f46e3af130aea361c48", + "answer": { + "choice": "no", + "note": null + }, + "retracted": true, + "retracts": "n1", + "answered_at": "2026-11-12T19:02:00Z", + "request_id": "00000011-0000-4000-8000-000000000011" + }, + "server": { + "answer_id": "n9", + "answerer": "e0d1c2b3-a495-4687-8879-6a5b4c3d2e1f", + "authority": "Operator", + "received_at": "2026-11-12T19:09:05Z" + }, + "response": { + "schemaVersion": "gamecult.fork.answer_result.v0", + "answer": { + "schemaVersion": "gamecult.desk_answer.v0", + "answer_id": "n9", + "fork_id": "gamecult-chat:question:slot-mark-consent", + "outcome": "Refused", + "of": null, + "retracts": null, + "code": "bad_retraction", + "options_hash": "7d95fc87d532420611fe415c1def27a3447617bb68235f46e3af130aea361c48", + "choice": "no", + "note": null, + "answered_at": "2026-11-12T19:02:00Z", + "received_at": "2026-11-12T19:09:05Z", + "claim_in_future": false, + "answerer": "e0d1c2b3-a495-4687-8879-6a5b4c3d2e1f", + "authority": "Operator", + "request_id": "00000011-0000-4000-8000-000000000011" + }, + "card": null + } + } + ], + "record": { + "schemaVersion": "gamecult.fork.record.v0", + "fork_id": "gamecult-chat:question:slot-mark-consent", + "options_hash": "7d95fc87d532420611fe415c1def27a3447617bb68235f46e3af130aea361c48", + "counted_answer_id": "n1", + "answers": [ + { + "schemaVersion": "gamecult.desk_answer.v0", + "answer_id": "n1", + "fork_id": "gamecult-chat:question:slot-mark-consent", + "outcome": "Counted", + "of": null, + "retracts": null, + "code": null, + "options_hash": "7d95fc87d532420611fe415c1def27a3447617bb68235f46e3af130aea361c48", + "choice": "no", + "note": "Keep it local: a slot mark is the author's call, and sharing it is a separate act.", + "answered_at": "2026-11-12T19:02:00Z", + "received_at": "2026-11-12T19:02:05Z", + "claim_in_future": false, + "answerer": "e0d1c2b3-a495-4687-8879-6a5b4c3d2e1f", + "authority": "Operator", + "request_id": "00000001-0000-4000-8000-000000000001" + }, + { + "schemaVersion": "gamecult.desk_answer.v0", + "answer_id": "n9", + "fork_id": "gamecult-chat:question:slot-mark-consent", + "outcome": "Refused", + "of": null, + "retracts": null, + "code": "bad_retraction", + "options_hash": "7d95fc87d532420611fe415c1def27a3447617bb68235f46e3af130aea361c48", + "choice": "no", + "note": null, + "answered_at": "2026-11-12T19:02:00Z", + "received_at": "2026-11-12T19:09:05Z", + "claim_in_future": false, + "answerer": "e0d1c2b3-a495-4687-8879-6a5b4c3d2e1f", + "authority": "Operator", + "request_id": "00000011-0000-4000-8000-000000000011" + } + ] + }, + "recordHex": "85AD736368656D6156657273696F6EB767616D6563756C742E666F726B2E7265636F72642E7630A7666F726B5F6964D92867616D6563756C742D636861743A7175657374696F6E3A736C6F742D6D61726B2D636F6E73656E74AC6F7074696F6E735F68617368D94037643935666338376435333234323036313166653431356331646566323761333434373631376262363832333566343665336166313330616561333631633438B1636F756E7465645F616E737765725F6964A26E31A7616E737765727392DE0010AD736368656D6156657273696F6EB767616D6563756C742E6465736B5F616E737765722E7630A9616E737765725F6964A26E31A7666F726B5F6964D92867616D6563756C742D636861743A7175657374696F6E3A736C6F742D6D61726B2D636F6E73656E74A76F7574636F6D65A7436F756E746564A26F66C0A87265747261637473C0A4636F6465C0AC6F7074696F6E735F68617368D94037643935666338376435333234323036313166653431356331646566323761333434373631376262363832333566343665336166313330616561333631633438A663686F696365A26E6FA46E6F7465D9524B656570206974206C6F63616C3A206120736C6F74206D61726B2069732074686520617574686F7227732063616C6C2C20616E642073686172696E672069742069732061207365706172617465206163742EAB616E7377657265645F6174B4323032362D31312D31325431393A30323A30305AAB72656365697665645F6174B4323032362D31312D31325431393A30323A30355AAF636C61696D5F696E5F667574757265C2A8616E737765726572D92465306431633262332D613439352D343638372D383837392D366135623463336432653166A9617574686F72697479A84F70657261746F72AA726571756573745F6964D92430303030303030312D303030302D343030302D383030302D303030303030303030303031DE0010AD736368656D6156657273696F6EB767616D6563756C742E6465736B5F616E737765722E7630A9616E737765725F6964A26E39A7666F726B5F6964D92867616D6563756C742D636861743A7175657374696F6E3A736C6F742D6D61726B2D636F6E73656E74A76F7574636F6D65A752656675736564A26F66C0A87265747261637473C0A4636F6465AE6261645F72657472616374696F6EAC6F7074696F6E735F68617368D94037643935666338376435333234323036313166653431356331646566323761333434373631376262363832333566343665336166313330616561333631633438A663686F696365A26E6FA46E6F7465C0AB616E7377657265645F6174B4323032362D31312D31325431393A30323A30305AAB72656365697665645F6174B4323032362D31312D31325431393A30393A30355AAF636C61696D5F696E5F667574757265C2A8616E737765726572D92465306431633262332D613439352D343638372D383837392D366135623463336432653166A9617574686F72697479A84F70657261746F72AA726571756573745F6964D92430303030303031312D303030302D343030302D383030302D303030303030303030303131" + }, + { + "name": "V8_retraction_without_retracts_is_bad", + "card": { + "schemaVersion": "gamecult.fork.card.v0", + "fork_id": "gamecult-chat:question:slot-mark-consent", + "title": "Slot mark consent", + "context": "Should a slot mark be shared?", + "options": [ + { + "id": "yes", + "text": "Share it." + }, + { + "id": "no", + "text": "Keep it local." + } + ], + "recommendation": "no", + "depends": [ + "gamecult-chat:question:file-zone-audience" + ], + "leans": null, + "flag": null, + "raised_in": { + "kind": "CutSpec", + "id": "cultlib-gaps:cut_spec:cut-slot-tolerance.r4" + }, + "deciders": [ + "e0d1c2b3-a495-4687-8879-6a5b4c3d2e1f" + ], + "status": "open", + "ruling": null, + "ruled_from": [], + "order": 3, + "options_hash": "7d95fc87d532420611fe415c1def27a3447617bb68235f46e3af130aea361c48" + }, + "optionsHash": "7d95fc87d532420611fe415c1def27a3447617bb68235f46e3af130aea361c48", + "steps": [ + { + "kind": "answer", + "body": { + "schemaVersion": "gamecult.fork.answer_request.v0", + "fork_id": "gamecult-chat:question:slot-mark-consent", + "options_hash": "7d95fc87d532420611fe415c1def27a3447617bb68235f46e3af130aea361c48", + "answer": { + "choice": "no", + "note": "Keep it local: a slot mark is the author's call, and sharing it is a separate act." + }, + "retracted": false, + "retracts": null, + "answered_at": "2026-11-12T19:02:00Z", + "request_id": "00000001-0000-4000-8000-000000000001" + }, + "server": { + "answer_id": "n1", + "answerer": "e0d1c2b3-a495-4687-8879-6a5b4c3d2e1f", + "authority": "Operator", + "received_at": "2026-11-12T19:02:05Z" + }, + "response": { + "schemaVersion": "gamecult.fork.answer_result.v0", + "answer": { + "schemaVersion": "gamecult.desk_answer.v0", + "answer_id": "n1", + "fork_id": "gamecult-chat:question:slot-mark-consent", + "outcome": "Counted", + "of": null, + "retracts": null, + "code": null, + "options_hash": "7d95fc87d532420611fe415c1def27a3447617bb68235f46e3af130aea361c48", + "choice": "no", + "note": "Keep it local: a slot mark is the author's call, and sharing it is a separate act.", + "answered_at": "2026-11-12T19:02:00Z", + "received_at": "2026-11-12T19:02:05Z", + "claim_in_future": false, + "answerer": "e0d1c2b3-a495-4687-8879-6a5b4c3d2e1f", + "authority": "Operator", + "request_id": "00000001-0000-4000-8000-000000000001" + }, + "card": null + } + }, + { + "kind": "answer", + "body": { + "schemaVersion": "gamecult.fork.answer_request.v0", + "fork_id": "gamecult-chat:question:slot-mark-consent", + "options_hash": "7d95fc87d532420611fe415c1def27a3447617bb68235f46e3af130aea361c48", + "answer": null, + "retracted": true, + "retracts": null, + "answered_at": "2026-11-12T19:02:00Z", + "request_id": "00000012-0000-4000-8000-000000000012" + }, + "server": { + "answer_id": "n9", + "answerer": "e0d1c2b3-a495-4687-8879-6a5b4c3d2e1f", + "authority": "Operator", + "received_at": "2026-11-12T19:09:05Z" + }, + "response": { + "schemaVersion": "gamecult.fork.answer_result.v0", + "answer": { + "schemaVersion": "gamecult.desk_answer.v0", + "answer_id": "n9", + "fork_id": "gamecult-chat:question:slot-mark-consent", + "outcome": "Refused", + "of": null, + "retracts": null, + "code": "bad_retraction", + "options_hash": "7d95fc87d532420611fe415c1def27a3447617bb68235f46e3af130aea361c48", + "choice": null, + "note": null, + "answered_at": "2026-11-12T19:02:00Z", + "received_at": "2026-11-12T19:09:05Z", + "claim_in_future": false, + "answerer": "e0d1c2b3-a495-4687-8879-6a5b4c3d2e1f", + "authority": "Operator", + "request_id": "00000012-0000-4000-8000-000000000012" + }, + "card": null + } + } + ], + "record": { + "schemaVersion": "gamecult.fork.record.v0", + "fork_id": "gamecult-chat:question:slot-mark-consent", + "options_hash": "7d95fc87d532420611fe415c1def27a3447617bb68235f46e3af130aea361c48", + "counted_answer_id": "n1", + "answers": [ + { + "schemaVersion": "gamecult.desk_answer.v0", + "answer_id": "n1", + "fork_id": "gamecult-chat:question:slot-mark-consent", + "outcome": "Counted", + "of": null, + "retracts": null, + "code": null, + "options_hash": "7d95fc87d532420611fe415c1def27a3447617bb68235f46e3af130aea361c48", + "choice": "no", + "note": "Keep it local: a slot mark is the author's call, and sharing it is a separate act.", + "answered_at": "2026-11-12T19:02:00Z", + "received_at": "2026-11-12T19:02:05Z", + "claim_in_future": false, + "answerer": "e0d1c2b3-a495-4687-8879-6a5b4c3d2e1f", + "authority": "Operator", + "request_id": "00000001-0000-4000-8000-000000000001" + }, + { + "schemaVersion": "gamecult.desk_answer.v0", + "answer_id": "n9", + "fork_id": "gamecult-chat:question:slot-mark-consent", + "outcome": "Refused", + "of": null, + "retracts": null, + "code": "bad_retraction", + "options_hash": "7d95fc87d532420611fe415c1def27a3447617bb68235f46e3af130aea361c48", + "choice": null, + "note": null, + "answered_at": "2026-11-12T19:02:00Z", + "received_at": "2026-11-12T19:09:05Z", + "claim_in_future": false, + "answerer": "e0d1c2b3-a495-4687-8879-6a5b4c3d2e1f", + "authority": "Operator", + "request_id": "00000012-0000-4000-8000-000000000012" + } + ] + }, + "recordHex": "85AD736368656D6156657273696F6EB767616D6563756C742E666F726B2E7265636F72642E7630A7666F726B5F6964D92867616D6563756C742D636861743A7175657374696F6E3A736C6F742D6D61726B2D636F6E73656E74AC6F7074696F6E735F68617368D94037643935666338376435333234323036313166653431356331646566323761333434373631376262363832333566343665336166313330616561333631633438B1636F756E7465645F616E737765725F6964A26E31A7616E737765727392DE0010AD736368656D6156657273696F6EB767616D6563756C742E6465736B5F616E737765722E7630A9616E737765725F6964A26E31A7666F726B5F6964D92867616D6563756C742D636861743A7175657374696F6E3A736C6F742D6D61726B2D636F6E73656E74A76F7574636F6D65A7436F756E746564A26F66C0A87265747261637473C0A4636F6465C0AC6F7074696F6E735F68617368D94037643935666338376435333234323036313166653431356331646566323761333434373631376262363832333566343665336166313330616561333631633438A663686F696365A26E6FA46E6F7465D9524B656570206974206C6F63616C3A206120736C6F74206D61726B2069732074686520617574686F7227732063616C6C2C20616E642073686172696E672069742069732061207365706172617465206163742EAB616E7377657265645F6174B4323032362D31312D31325431393A30323A30305AAB72656365697665645F6174B4323032362D31312D31325431393A30323A30355AAF636C61696D5F696E5F667574757265C2A8616E737765726572D92465306431633262332D613439352D343638372D383837392D366135623463336432653166A9617574686F72697479A84F70657261746F72AA726571756573745F6964D92430303030303030312D303030302D343030302D383030302D303030303030303030303031DE0010AD736368656D6156657273696F6EB767616D6563756C742E6465736B5F616E737765722E7630A9616E737765725F6964A26E39A7666F726B5F6964D92867616D6563756C742D636861743A7175657374696F6E3A736C6F742D6D61726B2D636F6E73656E74A76F7574636F6D65A752656675736564A26F66C0A87265747261637473C0A4636F6465AE6261645F72657472616374696F6EAC6F7074696F6E735F68617368D94037643935666338376435333234323036313166653431356331646566323761333434373631376262363832333566343665336166313330616561333631633438A663686F696365C0A46E6F7465C0AB616E7377657265645F6174B4323032362D31312D31325431393A30323A30305AAB72656365697665645F6174B4323032362D31312D31325431393A30393A30355AAF636C61696D5F696E5F667574757265C2A8616E737765726572D92465306431633262332D613439352D343638372D383837392D366135623463336432653166A9617574686F72697479A84F70657261746F72AA726571756573745F6964D92430303030303031322D303030302D343030302D383030302D303030303030303030303132" + }, + { + "name": "V8_retracts_on_a_plain_answer_is_bad", + "card": { + "schemaVersion": "gamecult.fork.card.v0", + "fork_id": "gamecult-chat:question:slot-mark-consent", + "title": "Slot mark consent", + "context": "Should a slot mark be shared?", + "options": [ + { + "id": "yes", + "text": "Share it." + }, + { + "id": "no", + "text": "Keep it local." + } + ], + "recommendation": "no", + "depends": [ + "gamecult-chat:question:file-zone-audience" + ], + "leans": null, + "flag": null, + "raised_in": { + "kind": "CutSpec", + "id": "cultlib-gaps:cut_spec:cut-slot-tolerance.r4" + }, + "deciders": [ + "e0d1c2b3-a495-4687-8879-6a5b4c3d2e1f" + ], + "status": "open", + "ruling": null, + "ruled_from": [], + "order": 3, + "options_hash": "7d95fc87d532420611fe415c1def27a3447617bb68235f46e3af130aea361c48" + }, + "optionsHash": "7d95fc87d532420611fe415c1def27a3447617bb68235f46e3af130aea361c48", + "steps": [ + { + "kind": "answer", + "body": { + "schemaVersion": "gamecult.fork.answer_request.v0", + "fork_id": "gamecult-chat:question:slot-mark-consent", + "options_hash": "7d95fc87d532420611fe415c1def27a3447617bb68235f46e3af130aea361c48", + "answer": { + "choice": "no", + "note": "Keep it local: a slot mark is the author's call, and sharing it is a separate act." + }, + "retracted": false, + "retracts": null, + "answered_at": "2026-11-12T19:02:00Z", + "request_id": "00000001-0000-4000-8000-000000000001" + }, + "server": { + "answer_id": "n1", + "answerer": "e0d1c2b3-a495-4687-8879-6a5b4c3d2e1f", + "authority": "Operator", + "received_at": "2026-11-12T19:02:05Z" + }, + "response": { + "schemaVersion": "gamecult.fork.answer_result.v0", + "answer": { + "schemaVersion": "gamecult.desk_answer.v0", + "answer_id": "n1", + "fork_id": "gamecult-chat:question:slot-mark-consent", + "outcome": "Counted", + "of": null, + "retracts": null, + "code": null, + "options_hash": "7d95fc87d532420611fe415c1def27a3447617bb68235f46e3af130aea361c48", + "choice": "no", + "note": "Keep it local: a slot mark is the author's call, and sharing it is a separate act.", + "answered_at": "2026-11-12T19:02:00Z", + "received_at": "2026-11-12T19:02:05Z", + "claim_in_future": false, + "answerer": "e0d1c2b3-a495-4687-8879-6a5b4c3d2e1f", + "authority": "Operator", + "request_id": "00000001-0000-4000-8000-000000000001" + }, + "card": null + } + }, + { + "kind": "answer", + "body": { + "schemaVersion": "gamecult.fork.answer_request.v0", + "fork_id": "gamecult-chat:question:slot-mark-consent", + "options_hash": "7d95fc87d532420611fe415c1def27a3447617bb68235f46e3af130aea361c48", + "answer": { + "choice": "yes", + "note": "On reflection, share it." + }, + "retracted": false, + "retracts": "n1", + "answered_at": "2026-11-12T19:06:00Z", + "request_id": "00000002-0000-4000-8000-000000000002" + }, + "server": { + "answer_id": "n9", + "answerer": "e0d1c2b3-a495-4687-8879-6a5b4c3d2e1f", + "authority": "Operator", + "received_at": "2026-11-12T19:09:05Z" + }, + "response": { + "schemaVersion": "gamecult.fork.answer_result.v0", + "answer": { + "schemaVersion": "gamecult.desk_answer.v0", + "answer_id": "n9", + "fork_id": "gamecult-chat:question:slot-mark-consent", + "outcome": "Refused", + "of": null, + "retracts": null, + "code": "bad_retraction", + "options_hash": "7d95fc87d532420611fe415c1def27a3447617bb68235f46e3af130aea361c48", + "choice": "yes", + "note": "On reflection, share it.", + "answered_at": "2026-11-12T19:06:00Z", + "received_at": "2026-11-12T19:09:05Z", + "claim_in_future": false, + "answerer": "e0d1c2b3-a495-4687-8879-6a5b4c3d2e1f", + "authority": "Operator", + "request_id": "00000002-0000-4000-8000-000000000002" + }, + "card": null + } + } + ], + "record": { + "schemaVersion": "gamecult.fork.record.v0", + "fork_id": "gamecult-chat:question:slot-mark-consent", + "options_hash": "7d95fc87d532420611fe415c1def27a3447617bb68235f46e3af130aea361c48", + "counted_answer_id": "n1", + "answers": [ + { + "schemaVersion": "gamecult.desk_answer.v0", + "answer_id": "n1", + "fork_id": "gamecult-chat:question:slot-mark-consent", + "outcome": "Counted", + "of": null, + "retracts": null, + "code": null, + "options_hash": "7d95fc87d532420611fe415c1def27a3447617bb68235f46e3af130aea361c48", + "choice": "no", + "note": "Keep it local: a slot mark is the author's call, and sharing it is a separate act.", + "answered_at": "2026-11-12T19:02:00Z", + "received_at": "2026-11-12T19:02:05Z", + "claim_in_future": false, + "answerer": "e0d1c2b3-a495-4687-8879-6a5b4c3d2e1f", + "authority": "Operator", + "request_id": "00000001-0000-4000-8000-000000000001" + }, + { + "schemaVersion": "gamecult.desk_answer.v0", + "answer_id": "n9", + "fork_id": "gamecult-chat:question:slot-mark-consent", + "outcome": "Refused", + "of": null, + "retracts": null, + "code": "bad_retraction", + "options_hash": "7d95fc87d532420611fe415c1def27a3447617bb68235f46e3af130aea361c48", + "choice": "yes", + "note": "On reflection, share it.", + "answered_at": "2026-11-12T19:06:00Z", + "received_at": "2026-11-12T19:09:05Z", + "claim_in_future": false, + "answerer": "e0d1c2b3-a495-4687-8879-6a5b4c3d2e1f", + "authority": "Operator", + "request_id": "00000002-0000-4000-8000-000000000002" + } + ] + }, + "recordHex": "85AD736368656D6156657273696F6EB767616D6563756C742E666F726B2E7265636F72642E7630A7666F726B5F6964D92867616D6563756C742D636861743A7175657374696F6E3A736C6F742D6D61726B2D636F6E73656E74AC6F7074696F6E735F68617368D94037643935666338376435333234323036313166653431356331646566323761333434373631376262363832333566343665336166313330616561333631633438B1636F756E7465645F616E737765725F6964A26E31A7616E737765727392DE0010AD736368656D6156657273696F6EB767616D6563756C742E6465736B5F616E737765722E7630A9616E737765725F6964A26E31A7666F726B5F6964D92867616D6563756C742D636861743A7175657374696F6E3A736C6F742D6D61726B2D636F6E73656E74A76F7574636F6D65A7436F756E746564A26F66C0A87265747261637473C0A4636F6465C0AC6F7074696F6E735F68617368D94037643935666338376435333234323036313166653431356331646566323761333434373631376262363832333566343665336166313330616561333631633438A663686F696365A26E6FA46E6F7465D9524B656570206974206C6F63616C3A206120736C6F74206D61726B2069732074686520617574686F7227732063616C6C2C20616E642073686172696E672069742069732061207365706172617465206163742EAB616E7377657265645F6174B4323032362D31312D31325431393A30323A30305AAB72656365697665645F6174B4323032362D31312D31325431393A30323A30355AAF636C61696D5F696E5F667574757265C2A8616E737765726572D92465306431633262332D613439352D343638372D383837392D366135623463336432653166A9617574686F72697479A84F70657261746F72AA726571756573745F6964D92430303030303030312D303030302D343030302D383030302D303030303030303030303031DE0010AD736368656D6156657273696F6EB767616D6563756C742E6465736B5F616E737765722E7630A9616E737765725F6964A26E39A7666F726B5F6964D92867616D6563756C742D636861743A7175657374696F6E3A736C6F742D6D61726B2D636F6E73656E74A76F7574636F6D65A752656675736564A26F66C0A87265747261637473C0A4636F6465AE6261645F72657472616374696F6EAC6F7074696F6E735F68617368D94037643935666338376435333234323036313166653431356331646566323761333434373631376262363832333566343665336166313330616561333631633438A663686F696365A3796573A46E6F7465B84F6E207265666C656374696F6E2C2073686172652069742EAB616E7377657265645F6174B4323032362D31312D31325431393A30363A30305AAB72656365697665645F6174B4323032362D31312D31325431393A30393A30355AAF636C61696D5F696E5F667574757265C2A8616E737765726572D92465306431633262332D613439352D343638372D383837392D366135623463336432653166A9617574686F72697479A84F70657261746F72AA726571756573745F6964D92430303030303030322D303030302D343030302D383030302D303030303030303030303032" + }, + { + "name": "V8_retraction_naming_an_unknown_answer_is_bad", + "card": { + "schemaVersion": "gamecult.fork.card.v0", + "fork_id": "gamecult-chat:question:slot-mark-consent", + "title": "Slot mark consent", + "context": "Should a slot mark be shared?", + "options": [ + { + "id": "yes", + "text": "Share it." + }, + { + "id": "no", + "text": "Keep it local." + } + ], + "recommendation": "no", + "depends": [ + "gamecult-chat:question:file-zone-audience" + ], + "leans": null, + "flag": null, + "raised_in": { + "kind": "CutSpec", + "id": "cultlib-gaps:cut_spec:cut-slot-tolerance.r4" + }, + "deciders": [ + "e0d1c2b3-a495-4687-8879-6a5b4c3d2e1f" + ], + "status": "open", + "ruling": null, + "ruled_from": [], + "order": 3, + "options_hash": "7d95fc87d532420611fe415c1def27a3447617bb68235f46e3af130aea361c48" + }, + "optionsHash": "7d95fc87d532420611fe415c1def27a3447617bb68235f46e3af130aea361c48", + "steps": [ + { + "kind": "answer", + "body": { + "schemaVersion": "gamecult.fork.answer_request.v0", + "fork_id": "gamecult-chat:question:slot-mark-consent", + "options_hash": "7d95fc87d532420611fe415c1def27a3447617bb68235f46e3af130aea361c48", + "answer": { + "choice": "no", + "note": "Keep it local: a slot mark is the author's call, and sharing it is a separate act." + }, + "retracted": false, + "retracts": null, + "answered_at": "2026-11-12T19:02:00Z", + "request_id": "00000001-0000-4000-8000-000000000001" + }, + "server": { + "answer_id": "n1", + "answerer": "e0d1c2b3-a495-4687-8879-6a5b4c3d2e1f", + "authority": "Operator", + "received_at": "2026-11-12T19:02:05Z" + }, + "response": { + "schemaVersion": "gamecult.fork.answer_result.v0", + "answer": { + "schemaVersion": "gamecult.desk_answer.v0", + "answer_id": "n1", + "fork_id": "gamecult-chat:question:slot-mark-consent", + "outcome": "Counted", + "of": null, + "retracts": null, + "code": null, + "options_hash": "7d95fc87d532420611fe415c1def27a3447617bb68235f46e3af130aea361c48", + "choice": "no", + "note": "Keep it local: a slot mark is the author's call, and sharing it is a separate act.", + "answered_at": "2026-11-12T19:02:00Z", + "received_at": "2026-11-12T19:02:05Z", + "claim_in_future": false, + "answerer": "e0d1c2b3-a495-4687-8879-6a5b4c3d2e1f", + "authority": "Operator", + "request_id": "00000001-0000-4000-8000-000000000001" + }, + "card": null + } + }, + { + "kind": "answer", + "body": { + "schemaVersion": "gamecult.fork.answer_request.v0", + "fork_id": "gamecult-chat:question:slot-mark-consent", + "options_hash": "7d95fc87d532420611fe415c1def27a3447617bb68235f46e3af130aea361c48", + "answer": null, + "retracted": true, + "retracts": "n99", + "answered_at": "2026-11-12T19:02:00Z", + "request_id": "00000013-0000-4000-8000-000000000013" + }, + "server": { + "answer_id": "n9", + "answerer": "e0d1c2b3-a495-4687-8879-6a5b4c3d2e1f", + "authority": "Operator", + "received_at": "2026-11-12T19:09:05Z" + }, + "response": { + "schemaVersion": "gamecult.fork.answer_result.v0", + "answer": { + "schemaVersion": "gamecult.desk_answer.v0", + "answer_id": "n9", + "fork_id": "gamecult-chat:question:slot-mark-consent", + "outcome": "Refused", + "of": null, + "retracts": null, + "code": "bad_retraction", + "options_hash": "7d95fc87d532420611fe415c1def27a3447617bb68235f46e3af130aea361c48", + "choice": null, + "note": null, + "answered_at": "2026-11-12T19:02:00Z", + "received_at": "2026-11-12T19:09:05Z", + "claim_in_future": false, + "answerer": "e0d1c2b3-a495-4687-8879-6a5b4c3d2e1f", + "authority": "Operator", + "request_id": "00000013-0000-4000-8000-000000000013" + }, + "card": null + } + } + ], + "record": { + "schemaVersion": "gamecult.fork.record.v0", + "fork_id": "gamecult-chat:question:slot-mark-consent", + "options_hash": "7d95fc87d532420611fe415c1def27a3447617bb68235f46e3af130aea361c48", + "counted_answer_id": "n1", + "answers": [ + { + "schemaVersion": "gamecult.desk_answer.v0", + "answer_id": "n1", + "fork_id": "gamecult-chat:question:slot-mark-consent", + "outcome": "Counted", + "of": null, + "retracts": null, + "code": null, + "options_hash": "7d95fc87d532420611fe415c1def27a3447617bb68235f46e3af130aea361c48", + "choice": "no", + "note": "Keep it local: a slot mark is the author's call, and sharing it is a separate act.", + "answered_at": "2026-11-12T19:02:00Z", + "received_at": "2026-11-12T19:02:05Z", + "claim_in_future": false, + "answerer": "e0d1c2b3-a495-4687-8879-6a5b4c3d2e1f", + "authority": "Operator", + "request_id": "00000001-0000-4000-8000-000000000001" + }, + { + "schemaVersion": "gamecult.desk_answer.v0", + "answer_id": "n9", + "fork_id": "gamecult-chat:question:slot-mark-consent", + "outcome": "Refused", + "of": null, + "retracts": null, + "code": "bad_retraction", + "options_hash": "7d95fc87d532420611fe415c1def27a3447617bb68235f46e3af130aea361c48", + "choice": null, + "note": null, + "answered_at": "2026-11-12T19:02:00Z", + "received_at": "2026-11-12T19:09:05Z", + "claim_in_future": false, + "answerer": "e0d1c2b3-a495-4687-8879-6a5b4c3d2e1f", + "authority": "Operator", + "request_id": "00000013-0000-4000-8000-000000000013" + } + ] + }, + "recordHex": "85AD736368656D6156657273696F6EB767616D6563756C742E666F726B2E7265636F72642E7630A7666F726B5F6964D92867616D6563756C742D636861743A7175657374696F6E3A736C6F742D6D61726B2D636F6E73656E74AC6F7074696F6E735F68617368D94037643935666338376435333234323036313166653431356331646566323761333434373631376262363832333566343665336166313330616561333631633438B1636F756E7465645F616E737765725F6964A26E31A7616E737765727392DE0010AD736368656D6156657273696F6EB767616D6563756C742E6465736B5F616E737765722E7630A9616E737765725F6964A26E31A7666F726B5F6964D92867616D6563756C742D636861743A7175657374696F6E3A736C6F742D6D61726B2D636F6E73656E74A76F7574636F6D65A7436F756E746564A26F66C0A87265747261637473C0A4636F6465C0AC6F7074696F6E735F68617368D94037643935666338376435333234323036313166653431356331646566323761333434373631376262363832333566343665336166313330616561333631633438A663686F696365A26E6FA46E6F7465D9524B656570206974206C6F63616C3A206120736C6F74206D61726B2069732074686520617574686F7227732063616C6C2C20616E642073686172696E672069742069732061207365706172617465206163742EAB616E7377657265645F6174B4323032362D31312D31325431393A30323A30305AAB72656365697665645F6174B4323032362D31312D31325431393A30323A30355AAF636C61696D5F696E5F667574757265C2A8616E737765726572D92465306431633262332D613439352D343638372D383837392D366135623463336432653166A9617574686F72697479A84F70657261746F72AA726571756573745F6964D92430303030303030312D303030302D343030302D383030302D303030303030303030303031DE0010AD736368656D6156657273696F6EB767616D6563756C742E6465736B5F616E737765722E7630A9616E737765725F6964A26E39A7666F726B5F6964D92867616D6563756C742D636861743A7175657374696F6E3A736C6F742D6D61726B2D636F6E73656E74A76F7574636F6D65A752656675736564A26F66C0A87265747261637473C0A4636F6465AE6261645F72657472616374696F6EAC6F7074696F6E735F68617368D94037643935666338376435333234323036313166653431356331646566323761333434373631376262363832333566343665336166313330616561333631633438A663686F696365C0A46E6F7465C0AB616E7377657265645F6174B4323032362D31312D31325431393A30323A30305AAB72656365697665645F6174B4323032362D31312D31325431393A30393A30355AAF636C61696D5F696E5F667574757265C2A8616E737765726572D92465306431633262332D613439352D343638372D383837392D366135623463336432653166A9617574686F72697479A84F70657261746F72AA726571756573745F6964D92430303030303031332D303030302D343030302D383030302D303030303030303030303133" + }, + { + "name": "V8_retraction_naming_a_Refused_answer_is_bad", + "card": { + "schemaVersion": "gamecult.fork.card.v0", + "fork_id": "gamecult-chat:question:slot-mark-consent", + "title": "Slot mark consent", + "context": "Should a slot mark be shared?", + "options": [ + { + "id": "yes", + "text": "Share it." + }, + { + "id": "no", + "text": "Keep it local." + } + ], + "recommendation": "no", + "depends": [ + "gamecult-chat:question:file-zone-audience" + ], + "leans": null, + "flag": null, + "raised_in": { + "kind": "CutSpec", + "id": "cultlib-gaps:cut_spec:cut-slot-tolerance.r4" + }, + "deciders": [ + "e0d1c2b3-a495-4687-8879-6a5b4c3d2e1f" + ], + "status": "open", + "ruling": null, + "ruled_from": [], + "order": 3, + "options_hash": "7d95fc87d532420611fe415c1def27a3447617bb68235f46e3af130aea361c48" + }, + "optionsHash": "7d95fc87d532420611fe415c1def27a3447617bb68235f46e3af130aea361c48", + "steps": [ + { + "kind": "answer", + "body": { + "schemaVersion": "gamecult.fork.answer_request.v0", + "fork_id": "gamecult-chat:question:slot-mark-consent", + "options_hash": "14b8ffbdb61106dbc06f69f3fc8e2072af7a205d766e4cc040e89733013d54dc", + "answer": { + "choice": "no", + "note": "Keep it local: a slot mark is the author's call, and sharing it is a separate act." + }, + "retracted": false, + "retracts": null, + "answered_at": "2026-11-12T19:02:00Z", + "request_id": "00000014-0000-4000-8000-000000000014" + }, + "server": { + "answer_id": "n1", + "answerer": "e0d1c2b3-a495-4687-8879-6a5b4c3d2e1f", + "authority": "Operator", + "received_at": "2026-11-12T19:02:05Z" + }, + "response": { + "schemaVersion": "gamecult.fork.answer_result.v0", + "answer": { + "schemaVersion": "gamecult.desk_answer.v0", + "answer_id": "n1", + "fork_id": "gamecult-chat:question:slot-mark-consent", + "outcome": "Refused", + "of": null, + "retracts": null, + "code": "stale_fork", + "options_hash": "14b8ffbdb61106dbc06f69f3fc8e2072af7a205d766e4cc040e89733013d54dc", + "choice": "no", + "note": "Keep it local: a slot mark is the author's call, and sharing it is a separate act.", + "answered_at": "2026-11-12T19:02:00Z", + "received_at": "2026-11-12T19:02:05Z", + "claim_in_future": false, + "answerer": "e0d1c2b3-a495-4687-8879-6a5b4c3d2e1f", + "authority": "Operator", + "request_id": "00000014-0000-4000-8000-000000000014" + }, + "card": { + "schemaVersion": "gamecult.fork.card.v0", + "fork_id": "gamecult-chat:question:slot-mark-consent", + "title": "Slot mark consent", + "context": "Should a slot mark be shared?", + "options": [ + { + "id": "yes", + "text": "Share it." + }, + { + "id": "no", + "text": "Keep it local." + } + ], + "recommendation": "no", + "depends": [ + "gamecult-chat:question:file-zone-audience" + ], + "leans": null, + "flag": null, + "raised_in": { + "kind": "CutSpec", + "id": "cultlib-gaps:cut_spec:cut-slot-tolerance.r4" + }, + "deciders": [ + "e0d1c2b3-a495-4687-8879-6a5b4c3d2e1f" + ], + "status": "open", + "ruling": null, + "ruled_from": [], + "order": 3, + "options_hash": "7d95fc87d532420611fe415c1def27a3447617bb68235f46e3af130aea361c48" + } + } + }, + { + "kind": "answer", + "body": { + "schemaVersion": "gamecult.fork.answer_request.v0", + "fork_id": "gamecult-chat:question:slot-mark-consent", + "options_hash": "7d95fc87d532420611fe415c1def27a3447617bb68235f46e3af130aea361c48", + "answer": null, + "retracted": true, + "retracts": "n1", + "answered_at": "2026-11-12T19:02:00Z", + "request_id": "00000015-0000-4000-8000-000000000015" + }, + "server": { + "answer_id": "n9", + "answerer": "e0d1c2b3-a495-4687-8879-6a5b4c3d2e1f", + "authority": "Operator", + "received_at": "2026-11-12T19:09:05Z" + }, + "response": { + "schemaVersion": "gamecult.fork.answer_result.v0", + "answer": { + "schemaVersion": "gamecult.desk_answer.v0", + "answer_id": "n9", + "fork_id": "gamecult-chat:question:slot-mark-consent", + "outcome": "Refused", + "of": null, + "retracts": null, + "code": "bad_retraction", + "options_hash": "7d95fc87d532420611fe415c1def27a3447617bb68235f46e3af130aea361c48", + "choice": null, + "note": null, + "answered_at": "2026-11-12T19:02:00Z", + "received_at": "2026-11-12T19:09:05Z", + "claim_in_future": false, + "answerer": "e0d1c2b3-a495-4687-8879-6a5b4c3d2e1f", + "authority": "Operator", + "request_id": "00000015-0000-4000-8000-000000000015" + }, + "card": null + } + } + ], + "record": { + "schemaVersion": "gamecult.fork.record.v0", + "fork_id": "gamecult-chat:question:slot-mark-consent", + "options_hash": "7d95fc87d532420611fe415c1def27a3447617bb68235f46e3af130aea361c48", + "counted_answer_id": null, + "answers": [ + { + "schemaVersion": "gamecult.desk_answer.v0", + "answer_id": "n1", + "fork_id": "gamecult-chat:question:slot-mark-consent", + "outcome": "Refused", + "of": null, + "retracts": null, + "code": "stale_fork", + "options_hash": "14b8ffbdb61106dbc06f69f3fc8e2072af7a205d766e4cc040e89733013d54dc", + "choice": "no", + "note": "Keep it local: a slot mark is the author's call, and sharing it is a separate act.", + "answered_at": "2026-11-12T19:02:00Z", + "received_at": "2026-11-12T19:02:05Z", + "claim_in_future": false, + "answerer": "e0d1c2b3-a495-4687-8879-6a5b4c3d2e1f", + "authority": "Operator", + "request_id": "00000014-0000-4000-8000-000000000014" + }, + { + "schemaVersion": "gamecult.desk_answer.v0", + "answer_id": "n9", + "fork_id": "gamecult-chat:question:slot-mark-consent", + "outcome": "Refused", + "of": null, + "retracts": null, + "code": "bad_retraction", + "options_hash": "7d95fc87d532420611fe415c1def27a3447617bb68235f46e3af130aea361c48", + "choice": null, + "note": null, + "answered_at": "2026-11-12T19:02:00Z", + "received_at": "2026-11-12T19:09:05Z", + "claim_in_future": false, + "answerer": "e0d1c2b3-a495-4687-8879-6a5b4c3d2e1f", + "authority": "Operator", + "request_id": "00000015-0000-4000-8000-000000000015" + } + ] + }, + "recordHex": "85AD736368656D6156657273696F6EB767616D6563756C742E666F726B2E7265636F72642E7630A7666F726B5F6964D92867616D6563756C742D636861743A7175657374696F6E3A736C6F742D6D61726B2D636F6E73656E74AC6F7074696F6E735F68617368D94037643935666338376435333234323036313166653431356331646566323761333434373631376262363832333566343665336166313330616561333631633438B1636F756E7465645F616E737765725F6964C0A7616E737765727392DE0010AD736368656D6156657273696F6EB767616D6563756C742E6465736B5F616E737765722E7630A9616E737765725F6964A26E31A7666F726B5F6964D92867616D6563756C742D636861743A7175657374696F6E3A736C6F742D6D61726B2D636F6E73656E74A76F7574636F6D65A752656675736564A26F66C0A87265747261637473C0A4636F6465AA7374616C655F666F726BAC6F7074696F6E735F68617368D94031346238666662646236313130366462633036663639663366633865323037326166376132303564373636653463633034306538393733333031336435346463A663686F696365A26E6FA46E6F7465D9524B656570206974206C6F63616C3A206120736C6F74206D61726B2069732074686520617574686F7227732063616C6C2C20616E642073686172696E672069742069732061207365706172617465206163742EAB616E7377657265645F6174B4323032362D31312D31325431393A30323A30305AAB72656365697665645F6174B4323032362D31312D31325431393A30323A30355AAF636C61696D5F696E5F667574757265C2A8616E737765726572D92465306431633262332D613439352D343638372D383837392D366135623463336432653166A9617574686F72697479A84F70657261746F72AA726571756573745F6964D92430303030303031342D303030302D343030302D383030302D303030303030303030303134DE0010AD736368656D6156657273696F6EB767616D6563756C742E6465736B5F616E737765722E7630A9616E737765725F6964A26E39A7666F726B5F6964D92867616D6563756C742D636861743A7175657374696F6E3A736C6F742D6D61726B2D636F6E73656E74A76F7574636F6D65A752656675736564A26F66C0A87265747261637473C0A4636F6465AE6261645F72657472616374696F6EAC6F7074696F6E735F68617368D94037643935666338376435333234323036313166653431356331646566323761333434373631376262363832333566343665336166313330616561333631633438A663686F696365C0A46E6F7465C0AB616E7377657265645F6174B4323032362D31312D31325431393A30323A30305AAB72656365697665645F6174B4323032362D31312D31325431393A30393A30355AAF636C61696D5F696E5F667574757265C2A8616E737765726572D92465306431633262332D613439352D343638372D383837392D366135623463336432653166A9617574686F72697479A84F70657261746F72AA726571756573745F6964D92430303030303031352D303030302D343030302D383030302D303030303030303030303135" + }, + { + "name": "V8_retracting_twice_is_already_retracted", + "card": { + "schemaVersion": "gamecult.fork.card.v0", + "fork_id": "gamecult-chat:question:slot-mark-consent", + "title": "Slot mark consent", + "context": "Should a slot mark be shared?", + "options": [ + { + "id": "yes", + "text": "Share it." + }, + { + "id": "no", + "text": "Keep it local." + } + ], + "recommendation": "no", + "depends": [ + "gamecult-chat:question:file-zone-audience" + ], + "leans": null, + "flag": null, + "raised_in": { + "kind": "CutSpec", + "id": "cultlib-gaps:cut_spec:cut-slot-tolerance.r4" + }, + "deciders": [ + "e0d1c2b3-a495-4687-8879-6a5b4c3d2e1f" + ], + "status": "open", + "ruling": null, + "ruled_from": [], + "order": 3, + "options_hash": "7d95fc87d532420611fe415c1def27a3447617bb68235f46e3af130aea361c48" + }, + "optionsHash": "7d95fc87d532420611fe415c1def27a3447617bb68235f46e3af130aea361c48", + "steps": [ + { + "kind": "answer", + "body": { + "schemaVersion": "gamecult.fork.answer_request.v0", + "fork_id": "gamecult-chat:question:slot-mark-consent", + "options_hash": "7d95fc87d532420611fe415c1def27a3447617bb68235f46e3af130aea361c48", + "answer": { + "choice": "no", + "note": "Keep it local: a slot mark is the author's call, and sharing it is a separate act." + }, + "retracted": false, + "retracts": null, + "answered_at": "2026-11-12T19:02:00Z", + "request_id": "00000001-0000-4000-8000-000000000001" + }, + "server": { + "answer_id": "n1", + "answerer": "e0d1c2b3-a495-4687-8879-6a5b4c3d2e1f", + "authority": "Operator", + "received_at": "2026-11-12T19:02:05Z" + }, + "response": { + "schemaVersion": "gamecult.fork.answer_result.v0", + "answer": { + "schemaVersion": "gamecult.desk_answer.v0", + "answer_id": "n1", + "fork_id": "gamecult-chat:question:slot-mark-consent", + "outcome": "Counted", + "of": null, + "retracts": null, + "code": null, + "options_hash": "7d95fc87d532420611fe415c1def27a3447617bb68235f46e3af130aea361c48", + "choice": "no", + "note": "Keep it local: a slot mark is the author's call, and sharing it is a separate act.", + "answered_at": "2026-11-12T19:02:00Z", + "received_at": "2026-11-12T19:02:05Z", + "claim_in_future": false, + "answerer": "e0d1c2b3-a495-4687-8879-6a5b4c3d2e1f", + "authority": "Operator", + "request_id": "00000001-0000-4000-8000-000000000001" + }, + "card": null + } + }, + { + "kind": "answer", + "body": { + "schemaVersion": "gamecult.fork.answer_request.v0", + "fork_id": "gamecult-chat:question:slot-mark-consent", + "options_hash": "7d95fc87d532420611fe415c1def27a3447617bb68235f46e3af130aea361c48", + "answer": null, + "retracted": true, + "retracts": "n1", + "answered_at": "2026-11-12T19:09:00Z", + "request_id": "00000016-0000-4000-8000-000000000016" + }, + "server": { + "answer_id": "n8", + "answerer": "e0d1c2b3-a495-4687-8879-6a5b4c3d2e1f", + "authority": "Operator", + "received_at": "2026-11-12T19:09:05Z" + }, + "response": { + "schemaVersion": "gamecult.fork.answer_result.v0", + "answer": { + "schemaVersion": "gamecult.desk_answer.v0", + "answer_id": "n8", + "fork_id": "gamecult-chat:question:slot-mark-consent", + "outcome": "Retraction", + "of": null, + "retracts": "n1", + "code": null, + "options_hash": "7d95fc87d532420611fe415c1def27a3447617bb68235f46e3af130aea361c48", + "choice": null, + "note": null, + "answered_at": "2026-11-12T19:09:00Z", + "received_at": "2026-11-12T19:09:05Z", + "claim_in_future": false, + "answerer": "e0d1c2b3-a495-4687-8879-6a5b4c3d2e1f", + "authority": "Operator", + "request_id": "00000016-0000-4000-8000-000000000016" + }, + "card": null + } + }, + { + "kind": "answer", + "body": { + "schemaVersion": "gamecult.fork.answer_request.v0", + "fork_id": "gamecult-chat:question:slot-mark-consent", + "options_hash": "7d95fc87d532420611fe415c1def27a3447617bb68235f46e3af130aea361c48", + "answer": null, + "retracted": true, + "retracts": "n1", + "answered_at": "2026-11-12T19:09:00Z", + "request_id": "00000017-0000-4000-8000-000000000017" + }, + "server": { + "answer_id": "n9", + "answerer": "e0d1c2b3-a495-4687-8879-6a5b4c3d2e1f", + "authority": "Operator", + "received_at": "2026-11-12T19:10:05Z" + }, + "response": { + "schemaVersion": "gamecult.fork.answer_result.v0", + "answer": { + "schemaVersion": "gamecult.desk_answer.v0", + "answer_id": "n9", + "fork_id": "gamecult-chat:question:slot-mark-consent", + "outcome": "Refused", + "of": null, + "retracts": null, + "code": "already_retracted", + "options_hash": "7d95fc87d532420611fe415c1def27a3447617bb68235f46e3af130aea361c48", + "choice": null, + "note": null, + "answered_at": "2026-11-12T19:09:00Z", + "received_at": "2026-11-12T19:10:05Z", + "claim_in_future": false, + "answerer": "e0d1c2b3-a495-4687-8879-6a5b4c3d2e1f", + "authority": "Operator", + "request_id": "00000017-0000-4000-8000-000000000017" + }, + "card": null + } + } + ], + "record": { + "schemaVersion": "gamecult.fork.record.v0", + "fork_id": "gamecult-chat:question:slot-mark-consent", + "options_hash": "7d95fc87d532420611fe415c1def27a3447617bb68235f46e3af130aea361c48", + "counted_answer_id": null, + "answers": [ + { + "schemaVersion": "gamecult.desk_answer.v0", + "answer_id": "n1", + "fork_id": "gamecult-chat:question:slot-mark-consent", + "outcome": "Counted", + "of": null, + "retracts": null, + "code": null, + "options_hash": "7d95fc87d532420611fe415c1def27a3447617bb68235f46e3af130aea361c48", + "choice": "no", + "note": "Keep it local: a slot mark is the author's call, and sharing it is a separate act.", + "answered_at": "2026-11-12T19:02:00Z", + "received_at": "2026-11-12T19:02:05Z", + "claim_in_future": false, + "answerer": "e0d1c2b3-a495-4687-8879-6a5b4c3d2e1f", + "authority": "Operator", + "request_id": "00000001-0000-4000-8000-000000000001" + }, + { + "schemaVersion": "gamecult.desk_answer.v0", + "answer_id": "n8", + "fork_id": "gamecult-chat:question:slot-mark-consent", + "outcome": "Retraction", + "of": null, + "retracts": "n1", + "code": null, + "options_hash": "7d95fc87d532420611fe415c1def27a3447617bb68235f46e3af130aea361c48", + "choice": null, + "note": null, + "answered_at": "2026-11-12T19:09:00Z", + "received_at": "2026-11-12T19:09:05Z", + "claim_in_future": false, + "answerer": "e0d1c2b3-a495-4687-8879-6a5b4c3d2e1f", + "authority": "Operator", + "request_id": "00000016-0000-4000-8000-000000000016" + }, + { + "schemaVersion": "gamecult.desk_answer.v0", + "answer_id": "n9", + "fork_id": "gamecult-chat:question:slot-mark-consent", + "outcome": "Refused", + "of": null, + "retracts": null, + "code": "already_retracted", + "options_hash": "7d95fc87d532420611fe415c1def27a3447617bb68235f46e3af130aea361c48", + "choice": null, + "note": null, + "answered_at": "2026-11-12T19:09:00Z", + "received_at": "2026-11-12T19:10:05Z", + "claim_in_future": false, + "answerer": "e0d1c2b3-a495-4687-8879-6a5b4c3d2e1f", + "authority": "Operator", + "request_id": "00000017-0000-4000-8000-000000000017" + } + ] + }, + "recordHex": "85AD736368656D6156657273696F6EB767616D6563756C742E666F726B2E7265636F72642E7630A7666F726B5F6964D92867616D6563756C742D636861743A7175657374696F6E3A736C6F742D6D61726B2D636F6E73656E74AC6F7074696F6E735F68617368D94037643935666338376435333234323036313166653431356331646566323761333434373631376262363832333566343665336166313330616561333631633438B1636F756E7465645F616E737765725F6964C0A7616E737765727393DE0010AD736368656D6156657273696F6EB767616D6563756C742E6465736B5F616E737765722E7630A9616E737765725F6964A26E31A7666F726B5F6964D92867616D6563756C742D636861743A7175657374696F6E3A736C6F742D6D61726B2D636F6E73656E74A76F7574636F6D65A7436F756E746564A26F66C0A87265747261637473C0A4636F6465C0AC6F7074696F6E735F68617368D94037643935666338376435333234323036313166653431356331646566323761333434373631376262363832333566343665336166313330616561333631633438A663686F696365A26E6FA46E6F7465D9524B656570206974206C6F63616C3A206120736C6F74206D61726B2069732074686520617574686F7227732063616C6C2C20616E642073686172696E672069742069732061207365706172617465206163742EAB616E7377657265645F6174B4323032362D31312D31325431393A30323A30305AAB72656365697665645F6174B4323032362D31312D31325431393A30323A30355AAF636C61696D5F696E5F667574757265C2A8616E737765726572D92465306431633262332D613439352D343638372D383837392D366135623463336432653166A9617574686F72697479A84F70657261746F72AA726571756573745F6964D92430303030303030312D303030302D343030302D383030302D303030303030303030303031DE0010AD736368656D6156657273696F6EB767616D6563756C742E6465736B5F616E737765722E7630A9616E737765725F6964A26E38A7666F726B5F6964D92867616D6563756C742D636861743A7175657374696F6E3A736C6F742D6D61726B2D636F6E73656E74A76F7574636F6D65AA52657472616374696F6EA26F66C0A87265747261637473A26E31A4636F6465C0AC6F7074696F6E735F68617368D94037643935666338376435333234323036313166653431356331646566323761333434373631376262363832333566343665336166313330616561333631633438A663686F696365C0A46E6F7465C0AB616E7377657265645F6174B4323032362D31312D31325431393A30393A30305AAB72656365697665645F6174B4323032362D31312D31325431393A30393A30355AAF636C61696D5F696E5F667574757265C2A8616E737765726572D92465306431633262332D613439352D343638372D383837392D366135623463336432653166A9617574686F72697479A84F70657261746F72AA726571756573745F6964D92430303030303031362D303030302D343030302D383030302D303030303030303030303136DE0010AD736368656D6156657273696F6EB767616D6563756C742E6465736B5F616E737765722E7630A9616E737765725F6964A26E39A7666F726B5F6964D92867616D6563756C742D636861743A7175657374696F6E3A736C6F742D6D61726B2D636F6E73656E74A76F7574636F6D65A752656675736564A26F66C0A87265747261637473C0A4636F6465B1616C72656164795F726574726163746564AC6F7074696F6E735F68617368D94037643935666338376435333234323036313166653431356331646566323761333434373631376262363832333566343665336166313330616561333631633438A663686F696365C0A46E6F7465C0AB616E7377657265645F6174B4323032362D31312D31325431393A30393A30305AAB72656365697665645F6174B4323032362D31312D31325431393A31303A30355AAF636C61696D5F696E5F667574757265C2A8616E737765726572D92465306431633262332D613439352D343638372D383837392D366135623463336432653166A9617574686F72697479A84F70657261746F72AA726571756573745F6964D92430303030303031372D303030302D343030302D383030302D303030303030303030303137" + }, + { + "name": "V8_retraction_under_a_stale_hash_is_stale_fork", + "card": { + "schemaVersion": "gamecult.fork.card.v0", + "fork_id": "gamecult-chat:question:slot-mark-consent", + "title": "Slot mark consent", + "context": "Should a slot mark be shared?", + "options": [ + { + "id": "yes", + "text": "Share it." + }, + { + "id": "no", + "text": "Keep it local." + } + ], + "recommendation": "no", + "depends": [ + "gamecult-chat:question:file-zone-audience" + ], + "leans": null, + "flag": null, + "raised_in": { + "kind": "CutSpec", + "id": "cultlib-gaps:cut_spec:cut-slot-tolerance.r4" + }, + "deciders": [ + "e0d1c2b3-a495-4687-8879-6a5b4c3d2e1f" + ], + "status": "open", + "ruling": null, + "ruled_from": [], + "order": 3, + "options_hash": "7d95fc87d532420611fe415c1def27a3447617bb68235f46e3af130aea361c48" + }, + "optionsHash": "7d95fc87d532420611fe415c1def27a3447617bb68235f46e3af130aea361c48", + "steps": [ + { + "kind": "answer", + "body": { + "schemaVersion": "gamecult.fork.answer_request.v0", + "fork_id": "gamecult-chat:question:slot-mark-consent", + "options_hash": "7d95fc87d532420611fe415c1def27a3447617bb68235f46e3af130aea361c48", + "answer": { + "choice": "no", + "note": "Keep it local: a slot mark is the author's call, and sharing it is a separate act." + }, + "retracted": false, + "retracts": null, + "answered_at": "2026-11-12T19:02:00Z", + "request_id": "00000001-0000-4000-8000-000000000001" + }, + "server": { + "answer_id": "n1", + "answerer": "e0d1c2b3-a495-4687-8879-6a5b4c3d2e1f", + "authority": "Operator", + "received_at": "2026-11-12T19:02:05Z" + }, + "response": { + "schemaVersion": "gamecult.fork.answer_result.v0", + "answer": { + "schemaVersion": "gamecult.desk_answer.v0", + "answer_id": "n1", + "fork_id": "gamecult-chat:question:slot-mark-consent", + "outcome": "Counted", + "of": null, + "retracts": null, + "code": null, + "options_hash": "7d95fc87d532420611fe415c1def27a3447617bb68235f46e3af130aea361c48", + "choice": "no", + "note": "Keep it local: a slot mark is the author's call, and sharing it is a separate act.", + "answered_at": "2026-11-12T19:02:00Z", + "received_at": "2026-11-12T19:02:05Z", + "claim_in_future": false, + "answerer": "e0d1c2b3-a495-4687-8879-6a5b4c3d2e1f", + "authority": "Operator", + "request_id": "00000001-0000-4000-8000-000000000001" + }, + "card": null + } + }, + { + "kind": "answer", + "body": { + "schemaVersion": "gamecult.fork.answer_request.v0", + "fork_id": "gamecult-chat:question:slot-mark-consent", + "options_hash": "14b8ffbdb61106dbc06f69f3fc8e2072af7a205d766e4cc040e89733013d54dc", + "answer": null, + "retracted": true, + "retracts": "n1", + "answered_at": "2026-11-12T19:02:00Z", + "request_id": "00000018-0000-4000-8000-000000000018" + }, + "server": { + "answer_id": "n9", + "answerer": "e0d1c2b3-a495-4687-8879-6a5b4c3d2e1f", + "authority": "Operator", + "received_at": "2026-11-12T19:09:05Z" + }, + "response": { + "schemaVersion": "gamecult.fork.answer_result.v0", + "answer": { + "schemaVersion": "gamecult.desk_answer.v0", + "answer_id": "n9", + "fork_id": "gamecult-chat:question:slot-mark-consent", + "outcome": "Refused", + "of": null, + "retracts": null, + "code": "stale_fork", + "options_hash": "14b8ffbdb61106dbc06f69f3fc8e2072af7a205d766e4cc040e89733013d54dc", + "choice": null, + "note": null, + "answered_at": "2026-11-12T19:02:00Z", + "received_at": "2026-11-12T19:09:05Z", + "claim_in_future": false, + "answerer": "e0d1c2b3-a495-4687-8879-6a5b4c3d2e1f", + "authority": "Operator", + "request_id": "00000018-0000-4000-8000-000000000018" + }, + "card": { + "schemaVersion": "gamecult.fork.card.v0", + "fork_id": "gamecult-chat:question:slot-mark-consent", + "title": "Slot mark consent", + "context": "Should a slot mark be shared?", + "options": [ + { + "id": "yes", + "text": "Share it." + }, + { + "id": "no", + "text": "Keep it local." + } + ], + "recommendation": "no", + "depends": [ + "gamecult-chat:question:file-zone-audience" + ], + "leans": null, + "flag": null, + "raised_in": { + "kind": "CutSpec", + "id": "cultlib-gaps:cut_spec:cut-slot-tolerance.r4" + }, + "deciders": [ + "e0d1c2b3-a495-4687-8879-6a5b4c3d2e1f" + ], + "status": "open", + "ruling": null, + "ruled_from": [], + "order": 3, + "options_hash": "7d95fc87d532420611fe415c1def27a3447617bb68235f46e3af130aea361c48" + } + } + } + ], + "record": { + "schemaVersion": "gamecult.fork.record.v0", + "fork_id": "gamecult-chat:question:slot-mark-consent", + "options_hash": "7d95fc87d532420611fe415c1def27a3447617bb68235f46e3af130aea361c48", + "counted_answer_id": "n1", + "answers": [ + { + "schemaVersion": "gamecult.desk_answer.v0", + "answer_id": "n1", + "fork_id": "gamecult-chat:question:slot-mark-consent", + "outcome": "Counted", + "of": null, + "retracts": null, + "code": null, + "options_hash": "7d95fc87d532420611fe415c1def27a3447617bb68235f46e3af130aea361c48", + "choice": "no", + "note": "Keep it local: a slot mark is the author's call, and sharing it is a separate act.", + "answered_at": "2026-11-12T19:02:00Z", + "received_at": "2026-11-12T19:02:05Z", + "claim_in_future": false, + "answerer": "e0d1c2b3-a495-4687-8879-6a5b4c3d2e1f", + "authority": "Operator", + "request_id": "00000001-0000-4000-8000-000000000001" + }, + { + "schemaVersion": "gamecult.desk_answer.v0", + "answer_id": "n9", + "fork_id": "gamecult-chat:question:slot-mark-consent", + "outcome": "Refused", + "of": null, + "retracts": null, + "code": "stale_fork", + "options_hash": "14b8ffbdb61106dbc06f69f3fc8e2072af7a205d766e4cc040e89733013d54dc", + "choice": null, + "note": null, + "answered_at": "2026-11-12T19:02:00Z", + "received_at": "2026-11-12T19:09:05Z", + "claim_in_future": false, + "answerer": "e0d1c2b3-a495-4687-8879-6a5b4c3d2e1f", + "authority": "Operator", + "request_id": "00000018-0000-4000-8000-000000000018" + } + ] + }, + "recordHex": "85AD736368656D6156657273696F6EB767616D6563756C742E666F726B2E7265636F72642E7630A7666F726B5F6964D92867616D6563756C742D636861743A7175657374696F6E3A736C6F742D6D61726B2D636F6E73656E74AC6F7074696F6E735F68617368D94037643935666338376435333234323036313166653431356331646566323761333434373631376262363832333566343665336166313330616561333631633438B1636F756E7465645F616E737765725F6964A26E31A7616E737765727392DE0010AD736368656D6156657273696F6EB767616D6563756C742E6465736B5F616E737765722E7630A9616E737765725F6964A26E31A7666F726B5F6964D92867616D6563756C742D636861743A7175657374696F6E3A736C6F742D6D61726B2D636F6E73656E74A76F7574636F6D65A7436F756E746564A26F66C0A87265747261637473C0A4636F6465C0AC6F7074696F6E735F68617368D94037643935666338376435333234323036313166653431356331646566323761333434373631376262363832333566343665336166313330616561333631633438A663686F696365A26E6FA46E6F7465D9524B656570206974206C6F63616C3A206120736C6F74206D61726B2069732074686520617574686F7227732063616C6C2C20616E642073686172696E672069742069732061207365706172617465206163742EAB616E7377657265645F6174B4323032362D31312D31325431393A30323A30305AAB72656365697665645F6174B4323032362D31312D31325431393A30323A30355AAF636C61696D5F696E5F667574757265C2A8616E737765726572D92465306431633262332D613439352D343638372D383837392D366135623463336432653166A9617574686F72697479A84F70657261746F72AA726571756573745F6964D92430303030303030312D303030302D343030302D383030302D303030303030303030303031DE0010AD736368656D6156657273696F6EB767616D6563756C742E6465736B5F616E737765722E7630A9616E737765725F6964A26E39A7666F726B5F6964D92867616D6563756C742D636861743A7175657374696F6E3A736C6F742D6D61726B2D636F6E73656E74A76F7574636F6D65A752656675736564A26F66C0A87265747261637473C0A4636F6465AA7374616C655F666F726BAC6F7074696F6E735F68617368D94031346238666662646236313130366462633036663639663366633865323037326166376132303564373636653463633034306538393733333031336435346463A663686F696365C0A46E6F7465C0AB616E7377657265645F6174B4323032362D31312D31325431393A30323A30305AAB72656365697665645F6174B4323032362D31312D31325431393A30393A30355AAF636C61696D5F696E5F667574757265C2A8616E737765726572D92465306431633262332D613439352D343638372D383837392D366135623463336432653166A9617574686F72697479A84F70657261746F72AA726571756573745F6964D92430303030303031382D303030302D343030302D383030302D303030303030303030303138" + }, + { + "name": "V9_another_decider_retracts_and_the_answer_stops_counting", + "card": { + "schemaVersion": "gamecult.fork.card.v0", + "fork_id": "gamecult-chat:question:slot-mark-consent", + "title": "Slot mark consent", + "context": "Should a slot mark be shared?", + "options": [ + { + "id": "yes", + "text": "Share it." + }, + { + "id": "no", + "text": "Keep it local." + } + ], + "recommendation": "no", + "depends": [ + "gamecult-chat:question:file-zone-audience" + ], + "leans": null, + "flag": null, + "raised_in": { + "kind": "CutSpec", + "id": "cultlib-gaps:cut_spec:cut-slot-tolerance.r4" + }, + "deciders": [ + "e0d1c2b3-a495-4687-8879-6a5b4c3d2e1f", + "9f8e7d6c-5b4a-4392-8170-0f1e2d3c4b5a" + ], + "status": "open", + "ruling": null, + "ruled_from": [], + "order": 3, + "options_hash": "7d95fc87d532420611fe415c1def27a3447617bb68235f46e3af130aea361c48" + }, + "optionsHash": "7d95fc87d532420611fe415c1def27a3447617bb68235f46e3af130aea361c48", + "steps": [ + { + "kind": "answer", + "body": { + "schemaVersion": "gamecult.fork.answer_request.v0", + "fork_id": "gamecult-chat:question:slot-mark-consent", + "options_hash": "7d95fc87d532420611fe415c1def27a3447617bb68235f46e3af130aea361c48", + "answer": { + "choice": "no", + "note": "Keep it local: a slot mark is the author's call, and sharing it is a separate act." + }, + "retracted": false, + "retracts": null, + "answered_at": "2026-11-12T19:02:00Z", + "request_id": "00000001-0000-4000-8000-000000000001" + }, + "server": { + "answer_id": "n1", + "answerer": "e0d1c2b3-a495-4687-8879-6a5b4c3d2e1f", + "authority": "Operator", + "received_at": "2026-11-12T19:02:05Z" + }, + "response": { + "schemaVersion": "gamecult.fork.answer_result.v0", + "answer": { + "schemaVersion": "gamecult.desk_answer.v0", + "answer_id": "n1", + "fork_id": "gamecult-chat:question:slot-mark-consent", + "outcome": "Counted", + "of": null, + "retracts": null, + "code": null, + "options_hash": "7d95fc87d532420611fe415c1def27a3447617bb68235f46e3af130aea361c48", + "choice": "no", + "note": "Keep it local: a slot mark is the author's call, and sharing it is a separate act.", + "answered_at": "2026-11-12T19:02:00Z", + "received_at": "2026-11-12T19:02:05Z", + "claim_in_future": false, + "answerer": "e0d1c2b3-a495-4687-8879-6a5b4c3d2e1f", + "authority": "Operator", + "request_id": "00000001-0000-4000-8000-000000000001" + }, + "card": null + } + }, + { + "kind": "answer", + "body": { + "schemaVersion": "gamecult.fork.answer_request.v0", + "fork_id": "gamecult-chat:question:slot-mark-consent", + "options_hash": "7d95fc87d532420611fe415c1def27a3447617bb68235f46e3af130aea361c48", + "answer": null, + "retracted": true, + "retracts": "n1", + "answered_at": "2026-11-12T19:09:00Z", + "request_id": "00000019-0000-4000-8000-000000000019" + }, + "server": { + "answer_id": "n7", + "answerer": "9f8e7d6c-5b4a-4392-8170-0f1e2d3c4b5a", + "authority": "Steward", + "received_at": "2026-11-12T19:09:05Z" + }, + "response": { + "schemaVersion": "gamecult.fork.answer_result.v0", + "answer": { + "schemaVersion": "gamecult.desk_answer.v0", + "answer_id": "n7", + "fork_id": "gamecult-chat:question:slot-mark-consent", + "outcome": "Retraction", + "of": null, + "retracts": "n1", + "code": null, + "options_hash": "7d95fc87d532420611fe415c1def27a3447617bb68235f46e3af130aea361c48", + "choice": null, + "note": null, + "answered_at": "2026-11-12T19:09:00Z", + "received_at": "2026-11-12T19:09:05Z", + "claim_in_future": false, + "answerer": "9f8e7d6c-5b4a-4392-8170-0f1e2d3c4b5a", + "authority": "Steward", + "request_id": "00000019-0000-4000-8000-000000000019" + }, + "card": null + } + } + ], + "record": { + "schemaVersion": "gamecult.fork.record.v0", + "fork_id": "gamecult-chat:question:slot-mark-consent", + "options_hash": "7d95fc87d532420611fe415c1def27a3447617bb68235f46e3af130aea361c48", + "counted_answer_id": null, + "answers": [ + { + "schemaVersion": "gamecult.desk_answer.v0", + "answer_id": "n1", + "fork_id": "gamecult-chat:question:slot-mark-consent", + "outcome": "Counted", + "of": null, + "retracts": null, + "code": null, + "options_hash": "7d95fc87d532420611fe415c1def27a3447617bb68235f46e3af130aea361c48", + "choice": "no", + "note": "Keep it local: a slot mark is the author's call, and sharing it is a separate act.", + "answered_at": "2026-11-12T19:02:00Z", + "received_at": "2026-11-12T19:02:05Z", + "claim_in_future": false, + "answerer": "e0d1c2b3-a495-4687-8879-6a5b4c3d2e1f", + "authority": "Operator", + "request_id": "00000001-0000-4000-8000-000000000001" + }, + { + "schemaVersion": "gamecult.desk_answer.v0", + "answer_id": "n7", + "fork_id": "gamecult-chat:question:slot-mark-consent", + "outcome": "Retraction", + "of": null, + "retracts": "n1", + "code": null, + "options_hash": "7d95fc87d532420611fe415c1def27a3447617bb68235f46e3af130aea361c48", + "choice": null, + "note": null, + "answered_at": "2026-11-12T19:09:00Z", + "received_at": "2026-11-12T19:09:05Z", + "claim_in_future": false, + "answerer": "9f8e7d6c-5b4a-4392-8170-0f1e2d3c4b5a", + "authority": "Steward", + "request_id": "00000019-0000-4000-8000-000000000019" + } + ] + }, + "recordHex": "85AD736368656D6156657273696F6EB767616D6563756C742E666F726B2E7265636F72642E7630A7666F726B5F6964D92867616D6563756C742D636861743A7175657374696F6E3A736C6F742D6D61726B2D636F6E73656E74AC6F7074696F6E735F68617368D94037643935666338376435333234323036313166653431356331646566323761333434373631376262363832333566343665336166313330616561333631633438B1636F756E7465645F616E737765725F6964C0A7616E737765727392DE0010AD736368656D6156657273696F6EB767616D6563756C742E6465736B5F616E737765722E7630A9616E737765725F6964A26E31A7666F726B5F6964D92867616D6563756C742D636861743A7175657374696F6E3A736C6F742D6D61726B2D636F6E73656E74A76F7574636F6D65A7436F756E746564A26F66C0A87265747261637473C0A4636F6465C0AC6F7074696F6E735F68617368D94037643935666338376435333234323036313166653431356331646566323761333434373631376262363832333566343665336166313330616561333631633438A663686F696365A26E6FA46E6F7465D9524B656570206974206C6F63616C3A206120736C6F74206D61726B2069732074686520617574686F7227732063616C6C2C20616E642073686172696E672069742069732061207365706172617465206163742EAB616E7377657265645F6174B4323032362D31312D31325431393A30323A30305AAB72656365697665645F6174B4323032362D31312D31325431393A30323A30355AAF636C61696D5F696E5F667574757265C2A8616E737765726572D92465306431633262332D613439352D343638372D383837392D366135623463336432653166A9617574686F72697479A84F70657261746F72AA726571756573745F6964D92430303030303030312D303030302D343030302D383030302D303030303030303030303031DE0010AD736368656D6156657273696F6EB767616D6563756C742E6465736B5F616E737765722E7630A9616E737765725F6964A26E37A7666F726B5F6964D92867616D6563756C742D636861743A7175657374696F6E3A736C6F742D6D61726B2D636F6E73656E74A76F7574636F6D65AA52657472616374696F6EA26F66C0A87265747261637473A26E31A4636F6465C0AC6F7074696F6E735F68617368D94037643935666338376435333234323036313166653431356331646566323761333434373631376262363832333566343665336166313330616561333631633438A663686F696365C0A46E6F7465C0AB616E7377657265645F6174B4323032362D31312D31325431393A30393A30305AAB72656365697665645F6174B4323032362D31312D31325431393A30393A30355AAF636C61696D5F696E5F667574757265C2A8616E737765726572D92439663865376436632D356234612D343339322D383137302D306631653264336334623561A9617574686F72697479A753746577617264AA726571756573745F6964D92430303030303031392D303030302D343030302D383030302D303030303030303030303139" + }, + { + "name": "V9_an_account_off_the_list_cannot_retract", + "card": { + "schemaVersion": "gamecult.fork.card.v0", + "fork_id": "gamecult-chat:question:slot-mark-consent", + "title": "Slot mark consent", + "context": "Should a slot mark be shared?", + "options": [ + { + "id": "yes", + "text": "Share it." + }, + { + "id": "no", + "text": "Keep it local." + } + ], + "recommendation": "no", + "depends": [ + "gamecult-chat:question:file-zone-audience" + ], + "leans": null, + "flag": null, + "raised_in": { + "kind": "CutSpec", + "id": "cultlib-gaps:cut_spec:cut-slot-tolerance.r4" + }, + "deciders": [ + "e0d1c2b3-a495-4687-8879-6a5b4c3d2e1f" + ], + "status": "open", + "ruling": null, + "ruled_from": [], + "order": 3, + "options_hash": "7d95fc87d532420611fe415c1def27a3447617bb68235f46e3af130aea361c48" + }, + "optionsHash": "7d95fc87d532420611fe415c1def27a3447617bb68235f46e3af130aea361c48", + "steps": [ + { + "kind": "answer", + "body": { + "schemaVersion": "gamecult.fork.answer_request.v0", + "fork_id": "gamecult-chat:question:slot-mark-consent", + "options_hash": "7d95fc87d532420611fe415c1def27a3447617bb68235f46e3af130aea361c48", + "answer": { + "choice": "no", + "note": "Keep it local: a slot mark is the author's call, and sharing it is a separate act." + }, + "retracted": false, + "retracts": null, + "answered_at": "2026-11-12T19:02:00Z", + "request_id": "00000001-0000-4000-8000-000000000001" + }, + "server": { + "answer_id": "n1", + "answerer": "e0d1c2b3-a495-4687-8879-6a5b4c3d2e1f", + "authority": "Operator", + "received_at": "2026-11-12T19:02:05Z" + }, + "response": { + "schemaVersion": "gamecult.fork.answer_result.v0", + "answer": { + "schemaVersion": "gamecult.desk_answer.v0", + "answer_id": "n1", + "fork_id": "gamecult-chat:question:slot-mark-consent", + "outcome": "Counted", + "of": null, + "retracts": null, + "code": null, + "options_hash": "7d95fc87d532420611fe415c1def27a3447617bb68235f46e3af130aea361c48", + "choice": "no", + "note": "Keep it local: a slot mark is the author's call, and sharing it is a separate act.", + "answered_at": "2026-11-12T19:02:00Z", + "received_at": "2026-11-12T19:02:05Z", + "claim_in_future": false, + "answerer": "e0d1c2b3-a495-4687-8879-6a5b4c3d2e1f", + "authority": "Operator", + "request_id": "00000001-0000-4000-8000-000000000001" + }, + "card": null + } + }, + { + "kind": "answer", + "body": { + "schemaVersion": "gamecult.fork.answer_request.v0", + "fork_id": "gamecult-chat:question:slot-mark-consent", + "options_hash": "7d95fc87d532420611fe415c1def27a3447617bb68235f46e3af130aea361c48", + "answer": null, + "retracted": true, + "retracts": "n1", + "answered_at": "2026-11-12T19:09:00Z", + "request_id": "0000001a-0000-4000-8000-00000000001a" + }, + "server": { + "answer_id": "n6", + "answerer": "11111111-2222-4333-8444-555555555555", + "authority": "Member", + "received_at": "2026-11-12T19:09:05Z" + }, + "response": { + "schemaVersion": "gamecult.fork.answer_result.v0", + "answer": { + "schemaVersion": "gamecult.desk_answer.v0", + "answer_id": "n6", + "fork_id": "gamecult-chat:question:slot-mark-consent", + "outcome": "Refused", + "of": null, + "retracts": null, + "code": "not_a_decider", + "options_hash": "7d95fc87d532420611fe415c1def27a3447617bb68235f46e3af130aea361c48", + "choice": null, + "note": null, + "answered_at": "2026-11-12T19:09:00Z", + "received_at": "2026-11-12T19:09:05Z", + "claim_in_future": false, + "answerer": "11111111-2222-4333-8444-555555555555", + "authority": "Member", + "request_id": "0000001a-0000-4000-8000-00000000001a" + }, + "card": null + } + } + ], + "record": { + "schemaVersion": "gamecult.fork.record.v0", + "fork_id": "gamecult-chat:question:slot-mark-consent", + "options_hash": "7d95fc87d532420611fe415c1def27a3447617bb68235f46e3af130aea361c48", + "counted_answer_id": "n1", + "answers": [ + { + "schemaVersion": "gamecult.desk_answer.v0", + "answer_id": "n1", + "fork_id": "gamecult-chat:question:slot-mark-consent", + "outcome": "Counted", + "of": null, + "retracts": null, + "code": null, + "options_hash": "7d95fc87d532420611fe415c1def27a3447617bb68235f46e3af130aea361c48", + "choice": "no", + "note": "Keep it local: a slot mark is the author's call, and sharing it is a separate act.", + "answered_at": "2026-11-12T19:02:00Z", + "received_at": "2026-11-12T19:02:05Z", + "claim_in_future": false, + "answerer": "e0d1c2b3-a495-4687-8879-6a5b4c3d2e1f", + "authority": "Operator", + "request_id": "00000001-0000-4000-8000-000000000001" + }, + { + "schemaVersion": "gamecult.desk_answer.v0", + "answer_id": "n6", + "fork_id": "gamecult-chat:question:slot-mark-consent", + "outcome": "Refused", + "of": null, + "retracts": null, + "code": "not_a_decider", + "options_hash": "7d95fc87d532420611fe415c1def27a3447617bb68235f46e3af130aea361c48", + "choice": null, + "note": null, + "answered_at": "2026-11-12T19:09:00Z", + "received_at": "2026-11-12T19:09:05Z", + "claim_in_future": false, + "answerer": "11111111-2222-4333-8444-555555555555", + "authority": "Member", + "request_id": "0000001a-0000-4000-8000-00000000001a" + } + ] + }, + "recordHex": "85AD736368656D6156657273696F6EB767616D6563756C742E666F726B2E7265636F72642E7630A7666F726B5F6964D92867616D6563756C742D636861743A7175657374696F6E3A736C6F742D6D61726B2D636F6E73656E74AC6F7074696F6E735F68617368D94037643935666338376435333234323036313166653431356331646566323761333434373631376262363832333566343665336166313330616561333631633438B1636F756E7465645F616E737765725F6964A26E31A7616E737765727392DE0010AD736368656D6156657273696F6EB767616D6563756C742E6465736B5F616E737765722E7630A9616E737765725F6964A26E31A7666F726B5F6964D92867616D6563756C742D636861743A7175657374696F6E3A736C6F742D6D61726B2D636F6E73656E74A76F7574636F6D65A7436F756E746564A26F66C0A87265747261637473C0A4636F6465C0AC6F7074696F6E735F68617368D94037643935666338376435333234323036313166653431356331646566323761333434373631376262363832333566343665336166313330616561333631633438A663686F696365A26E6FA46E6F7465D9524B656570206974206C6F63616C3A206120736C6F74206D61726B2069732074686520617574686F7227732063616C6C2C20616E642073686172696E672069742069732061207365706172617465206163742EAB616E7377657265645F6174B4323032362D31312D31325431393A30323A30305AAB72656365697665645F6174B4323032362D31312D31325431393A30323A30355AAF636C61696D5F696E5F667574757265C2A8616E737765726572D92465306431633262332D613439352D343638372D383837392D366135623463336432653166A9617574686F72697479A84F70657261746F72AA726571756573745F6964D92430303030303030312D303030302D343030302D383030302D303030303030303030303031DE0010AD736368656D6156657273696F6EB767616D6563756C742E6465736B5F616E737765722E7630A9616E737765725F6964A26E36A7666F726B5F6964D92867616D6563756C742D636861743A7175657374696F6E3A736C6F742D6D61726B2D636F6E73656E74A76F7574636F6D65A752656675736564A26F66C0A87265747261637473C0A4636F6465AD6E6F745F615F64656369646572AC6F7074696F6E735F68617368D94037643935666338376435333234323036313166653431356331646566323761333434373631376262363832333566343665336166313330616561333631633438A663686F696365C0A46E6F7465C0AB616E7377657265645F6174B4323032362D31312D31325431393A30393A30305AAB72656365697665645F6174B4323032362D31312D31325431393A30393A30355AAF636C61696D5F696E5F667574757265C2A8616E737765726572D92431313131313131312D323232322D343333332D383434342D353535353535353535353535A9617574686F72697479A64D656D626572AA726571756573745F6964D92430303030303031612D303030302D343030302D383030302D303030303030303030303161" + }, + { + "name": "V10a_a_ruling_that_closed_the_fork_makes_a_retraction_fork_closed", + "card": { + "schemaVersion": "gamecult.fork.card.v0", + "fork_id": "gamecult-chat:question:slot-mark-consent", + "title": "Slot mark consent", + "context": "Should a slot mark be shared?", + "options": [ + { + "id": "yes", + "text": "Share it." + }, + { + "id": "no", + "text": "Keep it local." + } + ], + "recommendation": "no", + "depends": [ + "gamecult-chat:question:file-zone-audience" + ], + "leans": null, + "flag": null, + "raised_in": { + "kind": "CutSpec", + "id": "cultlib-gaps:cut_spec:cut-slot-tolerance.r4" + }, + "deciders": [ + "e0d1c2b3-a495-4687-8879-6a5b4c3d2e1f" + ], + "status": "open", + "ruling": null, + "ruled_from": [], + "order": 3, + "options_hash": "7d95fc87d532420611fe415c1def27a3447617bb68235f46e3af130aea361c48" + }, + "optionsHash": "7d95fc87d532420611fe415c1def27a3447617bb68235f46e3af130aea361c48", + "steps": [ + { + "kind": "answer", + "body": { + "schemaVersion": "gamecult.fork.answer_request.v0", + "fork_id": "gamecult-chat:question:slot-mark-consent", + "options_hash": "7d95fc87d532420611fe415c1def27a3447617bb68235f46e3af130aea361c48", + "answer": { + "choice": "no", + "note": "Keep it local: a slot mark is the author's call, and sharing it is a separate act." + }, + "retracted": false, + "retracts": null, + "answered_at": "2026-11-12T19:02:00Z", + "request_id": "00000001-0000-4000-8000-000000000001" + }, + "server": { + "answer_id": "n1", + "answerer": "e0d1c2b3-a495-4687-8879-6a5b4c3d2e1f", + "authority": "Operator", + "received_at": "2026-11-12T19:02:05Z" + }, + "response": { + "schemaVersion": "gamecult.fork.answer_result.v0", + "answer": { + "schemaVersion": "gamecult.desk_answer.v0", + "answer_id": "n1", + "fork_id": "gamecult-chat:question:slot-mark-consent", + "outcome": "Counted", + "of": null, + "retracts": null, + "code": null, + "options_hash": "7d95fc87d532420611fe415c1def27a3447617bb68235f46e3af130aea361c48", + "choice": "no", + "note": "Keep it local: a slot mark is the author's call, and sharing it is a separate act.", + "answered_at": "2026-11-12T19:02:00Z", + "received_at": "2026-11-12T19:02:05Z", + "claim_in_future": false, + "answerer": "e0d1c2b3-a495-4687-8879-6a5b4c3d2e1f", + "authority": "Operator", + "request_id": "00000001-0000-4000-8000-000000000001" + }, + "card": null + } + }, + { + "kind": "card", + "body": null, + "server": null, + "response": null, + "card": { + "schemaVersion": "gamecult.fork.card.v0", + "fork_id": "gamecult-chat:question:slot-mark-consent", + "title": "Slot mark consent", + "context": "Should a slot mark be shared?", + "options": [ + { + "id": "yes", + "text": "Share it." + }, + { + "id": "no", + "text": "Keep it local." + } + ], + "recommendation": "no", + "depends": [ + "gamecult-chat:question:file-zone-audience" + ], + "leans": null, + "flag": null, + "raised_in": { + "kind": "CutSpec", + "id": "cultlib-gaps:cut_spec:cut-slot-tolerance.r4" + }, + "deciders": [ + "e0d1c2b3-a495-4687-8879-6a5b4c3d2e1f" + ], + "status": "closed", + "ruling": "cultlib-gaps:resolution:question.slot-mark-consent.n1", + "ruled_from": [ + "n1" + ], + "order": 3, + "options_hash": "7d95fc87d532420611fe415c1def27a3447617bb68235f46e3af130aea361c48" + } + }, + { + "kind": "answer", + "body": { + "schemaVersion": "gamecult.fork.answer_request.v0", + "fork_id": "gamecult-chat:question:slot-mark-consent", + "options_hash": "7d95fc87d532420611fe415c1def27a3447617bb68235f46e3af130aea361c48", + "answer": null, + "retracted": true, + "retracts": "n1", + "answered_at": "2026-11-12T19:09:00Z", + "request_id": "0000001b-0000-4000-8000-00000000001b" + }, + "server": { + "answer_id": "n9", + "answerer": "e0d1c2b3-a495-4687-8879-6a5b4c3d2e1f", + "authority": "Operator", + "received_at": "2026-11-12T19:09:05Z" + }, + "response": { + "schemaVersion": "gamecult.fork.answer_result.v0", + "answer": { + "schemaVersion": "gamecult.desk_answer.v0", + "answer_id": "n9", + "fork_id": "gamecult-chat:question:slot-mark-consent", + "outcome": "Refused", + "of": null, + "retracts": null, + "code": "fork_closed", + "options_hash": "7d95fc87d532420611fe415c1def27a3447617bb68235f46e3af130aea361c48", + "choice": null, + "note": null, + "answered_at": "2026-11-12T19:09:00Z", + "received_at": "2026-11-12T19:09:05Z", + "claim_in_future": false, + "answerer": "e0d1c2b3-a495-4687-8879-6a5b4c3d2e1f", + "authority": "Operator", + "request_id": "0000001b-0000-4000-8000-00000000001b" + }, + "card": null + } + } + ], + "record": { + "schemaVersion": "gamecult.fork.record.v0", + "fork_id": "gamecult-chat:question:slot-mark-consent", + "options_hash": "7d95fc87d532420611fe415c1def27a3447617bb68235f46e3af130aea361c48", + "counted_answer_id": "n1", + "answers": [ + { + "schemaVersion": "gamecult.desk_answer.v0", + "answer_id": "n1", + "fork_id": "gamecult-chat:question:slot-mark-consent", + "outcome": "Counted", + "of": null, + "retracts": null, + "code": null, + "options_hash": "7d95fc87d532420611fe415c1def27a3447617bb68235f46e3af130aea361c48", + "choice": "no", + "note": "Keep it local: a slot mark is the author's call, and sharing it is a separate act.", + "answered_at": "2026-11-12T19:02:00Z", + "received_at": "2026-11-12T19:02:05Z", + "claim_in_future": false, + "answerer": "e0d1c2b3-a495-4687-8879-6a5b4c3d2e1f", + "authority": "Operator", + "request_id": "00000001-0000-4000-8000-000000000001" + }, + { + "schemaVersion": "gamecult.desk_answer.v0", + "answer_id": "n9", + "fork_id": "gamecult-chat:question:slot-mark-consent", + "outcome": "Refused", + "of": null, + "retracts": null, + "code": "fork_closed", + "options_hash": "7d95fc87d532420611fe415c1def27a3447617bb68235f46e3af130aea361c48", + "choice": null, + "note": null, + "answered_at": "2026-11-12T19:09:00Z", + "received_at": "2026-11-12T19:09:05Z", + "claim_in_future": false, + "answerer": "e0d1c2b3-a495-4687-8879-6a5b4c3d2e1f", + "authority": "Operator", + "request_id": "0000001b-0000-4000-8000-00000000001b" + } + ] + }, + "recordHex": "85AD736368656D6156657273696F6EB767616D6563756C742E666F726B2E7265636F72642E7630A7666F726B5F6964D92867616D6563756C742D636861743A7175657374696F6E3A736C6F742D6D61726B2D636F6E73656E74AC6F7074696F6E735F68617368D94037643935666338376435333234323036313166653431356331646566323761333434373631376262363832333566343665336166313330616561333631633438B1636F756E7465645F616E737765725F6964A26E31A7616E737765727392DE0010AD736368656D6156657273696F6EB767616D6563756C742E6465736B5F616E737765722E7630A9616E737765725F6964A26E31A7666F726B5F6964D92867616D6563756C742D636861743A7175657374696F6E3A736C6F742D6D61726B2D636F6E73656E74A76F7574636F6D65A7436F756E746564A26F66C0A87265747261637473C0A4636F6465C0AC6F7074696F6E735F68617368D94037643935666338376435333234323036313166653431356331646566323761333434373631376262363832333566343665336166313330616561333631633438A663686F696365A26E6FA46E6F7465D9524B656570206974206C6F63616C3A206120736C6F74206D61726B2069732074686520617574686F7227732063616C6C2C20616E642073686172696E672069742069732061207365706172617465206163742EAB616E7377657265645F6174B4323032362D31312D31325431393A30323A30305AAB72656365697665645F6174B4323032362D31312D31325431393A30323A30355AAF636C61696D5F696E5F667574757265C2A8616E737765726572D92465306431633262332D613439352D343638372D383837392D366135623463336432653166A9617574686F72697479A84F70657261746F72AA726571756573745F6964D92430303030303030312D303030302D343030302D383030302D303030303030303030303031DE0010AD736368656D6156657273696F6EB767616D6563756C742E6465736B5F616E737765722E7630A9616E737765725F6964A26E39A7666F726B5F6964D92867616D6563756C742D636861743A7175657374696F6E3A736C6F742D6D61726B2D636F6E73656E74A76F7574636F6D65A752656675736564A26F66C0A87265747261637473C0A4636F6465AB666F726B5F636C6F736564AC6F7074696F6E735F68617368D94037643935666338376435333234323036313166653431356331646566323761333434373631376262363832333566343665336166313330616561333631633438A663686F696365C0A46E6F7465C0AB616E7377657265645F6174B4323032362D31312D31325431393A30393A30305AAB72656365697665645F6174B4323032362D31312D31325431393A30393A30355AAF636C61696D5F696E5F667574757265C2A8616E737765726572D92465306431633262332D613439352D343638372D383837392D366135623463336432653166A9617574686F72697479A84F70657261746F72AA726571756573745F6964D92430303030303031622D303030302D343030302D383030302D303030303030303030303162" + }, + { + "name": "V11_retracting_the_counted_answer_makes_the_next_decider_answer_counted", + "card": { + "schemaVersion": "gamecult.fork.card.v0", + "fork_id": "gamecult-chat:question:slot-mark-consent", + "title": "Slot mark consent", + "context": "Should a slot mark be shared?", + "options": [ + { + "id": "yes", + "text": "Share it." + }, + { + "id": "no", + "text": "Keep it local." + } + ], + "recommendation": "no", + "depends": [ + "gamecult-chat:question:file-zone-audience" + ], + "leans": null, + "flag": null, + "raised_in": { + "kind": "CutSpec", + "id": "cultlib-gaps:cut_spec:cut-slot-tolerance.r4" + }, + "deciders": [ + "e0d1c2b3-a495-4687-8879-6a5b4c3d2e1f", + "9f8e7d6c-5b4a-4392-8170-0f1e2d3c4b5a" + ], + "status": "open", + "ruling": null, + "ruled_from": [], + "order": 3, + "options_hash": "7d95fc87d532420611fe415c1def27a3447617bb68235f46e3af130aea361c48" + }, + "optionsHash": "7d95fc87d532420611fe415c1def27a3447617bb68235f46e3af130aea361c48", + "steps": [ + { + "kind": "answer", + "body": { + "schemaVersion": "gamecult.fork.answer_request.v0", + "fork_id": "gamecult-chat:question:slot-mark-consent", + "options_hash": "7d95fc87d532420611fe415c1def27a3447617bb68235f46e3af130aea361c48", + "answer": { + "choice": "no", + "note": "Keep it local: a slot mark is the author's call, and sharing it is a separate act." + }, + "retracted": false, + "retracts": null, + "answered_at": "2026-11-12T19:02:00Z", + "request_id": "00000001-0000-4000-8000-000000000001" + }, + "server": { + "answer_id": "n1", + "answerer": "e0d1c2b3-a495-4687-8879-6a5b4c3d2e1f", + "authority": "Operator", + "received_at": "2026-11-12T19:02:05Z" + }, + "response": { + "schemaVersion": "gamecult.fork.answer_result.v0", + "answer": { + "schemaVersion": "gamecult.desk_answer.v0", + "answer_id": "n1", + "fork_id": "gamecult-chat:question:slot-mark-consent", + "outcome": "Counted", + "of": null, + "retracts": null, + "code": null, + "options_hash": "7d95fc87d532420611fe415c1def27a3447617bb68235f46e3af130aea361c48", + "choice": "no", + "note": "Keep it local: a slot mark is the author's call, and sharing it is a separate act.", + "answered_at": "2026-11-12T19:02:00Z", + "received_at": "2026-11-12T19:02:05Z", + "claim_in_future": false, + "answerer": "e0d1c2b3-a495-4687-8879-6a5b4c3d2e1f", + "authority": "Operator", + "request_id": "00000001-0000-4000-8000-000000000001" + }, + "card": null + } + }, + { + "kind": "answer", + "body": { + "schemaVersion": "gamecult.fork.answer_request.v0", + "fork_id": "gamecult-chat:question:slot-mark-consent", + "options_hash": "7d95fc87d532420611fe415c1def27a3447617bb68235f46e3af130aea361c48", + "answer": { + "choice": "yes", + "note": "Share it, the audience is settled." + }, + "retracted": false, + "retracts": null, + "answered_at": "2026-11-12T19:05:00Z", + "request_id": "00000003-0000-4000-8000-000000000003" + }, + "server": { + "answer_id": "n3", + "answerer": "9f8e7d6c-5b4a-4392-8170-0f1e2d3c4b5a", + "authority": "Steward", + "received_at": "2026-11-12T19:05:05Z" + }, + "response": { + "schemaVersion": "gamecult.fork.answer_result.v0", + "answer": { + "schemaVersion": "gamecult.desk_answer.v0", + "answer_id": "n3", + "fork_id": "gamecult-chat:question:slot-mark-consent", + "outcome": "Revision", + "of": "n1", + "retracts": null, + "code": null, + "options_hash": "7d95fc87d532420611fe415c1def27a3447617bb68235f46e3af130aea361c48", + "choice": "yes", + "note": "Share it, the audience is settled.", + "answered_at": "2026-11-12T19:05:00Z", + "received_at": "2026-11-12T19:05:05Z", + "claim_in_future": false, + "answerer": "9f8e7d6c-5b4a-4392-8170-0f1e2d3c4b5a", + "authority": "Steward", + "request_id": "00000003-0000-4000-8000-000000000003" + }, + "card": null + } + }, + { + "kind": "answer", + "body": { + "schemaVersion": "gamecult.fork.answer_request.v0", + "fork_id": "gamecult-chat:question:slot-mark-consent", + "options_hash": "7d95fc87d532420611fe415c1def27a3447617bb68235f46e3af130aea361c48", + "answer": null, + "retracted": true, + "retracts": "n1", + "answered_at": "2026-11-12T19:09:00Z", + "request_id": "0000001c-0000-4000-8000-00000000001c" + }, + "server": { + "answer_id": "n8", + "answerer": "e0d1c2b3-a495-4687-8879-6a5b4c3d2e1f", + "authority": "Operator", + "received_at": "2026-11-12T19:09:05Z" + }, + "response": { + "schemaVersion": "gamecult.fork.answer_result.v0", + "answer": { + "schemaVersion": "gamecult.desk_answer.v0", + "answer_id": "n8", + "fork_id": "gamecult-chat:question:slot-mark-consent", + "outcome": "Retraction", + "of": null, + "retracts": "n1", + "code": null, + "options_hash": "7d95fc87d532420611fe415c1def27a3447617bb68235f46e3af130aea361c48", + "choice": null, + "note": null, + "answered_at": "2026-11-12T19:09:00Z", + "received_at": "2026-11-12T19:09:05Z", + "claim_in_future": false, + "answerer": "e0d1c2b3-a495-4687-8879-6a5b4c3d2e1f", + "authority": "Operator", + "request_id": "0000001c-0000-4000-8000-00000000001c" + }, + "card": null + } + } + ], + "record": { + "schemaVersion": "gamecult.fork.record.v0", + "fork_id": "gamecult-chat:question:slot-mark-consent", + "options_hash": "7d95fc87d532420611fe415c1def27a3447617bb68235f46e3af130aea361c48", + "counted_answer_id": "n3", + "answers": [ + { + "schemaVersion": "gamecult.desk_answer.v0", + "answer_id": "n1", + "fork_id": "gamecult-chat:question:slot-mark-consent", + "outcome": "Counted", + "of": null, + "retracts": null, + "code": null, + "options_hash": "7d95fc87d532420611fe415c1def27a3447617bb68235f46e3af130aea361c48", + "choice": "no", + "note": "Keep it local: a slot mark is the author's call, and sharing it is a separate act.", + "answered_at": "2026-11-12T19:02:00Z", + "received_at": "2026-11-12T19:02:05Z", + "claim_in_future": false, + "answerer": "e0d1c2b3-a495-4687-8879-6a5b4c3d2e1f", + "authority": "Operator", + "request_id": "00000001-0000-4000-8000-000000000001" + }, + { + "schemaVersion": "gamecult.desk_answer.v0", + "answer_id": "n3", + "fork_id": "gamecult-chat:question:slot-mark-consent", + "outcome": "Revision", + "of": "n1", + "retracts": null, + "code": null, + "options_hash": "7d95fc87d532420611fe415c1def27a3447617bb68235f46e3af130aea361c48", + "choice": "yes", + "note": "Share it, the audience is settled.", + "answered_at": "2026-11-12T19:05:00Z", + "received_at": "2026-11-12T19:05:05Z", + "claim_in_future": false, + "answerer": "9f8e7d6c-5b4a-4392-8170-0f1e2d3c4b5a", + "authority": "Steward", + "request_id": "00000003-0000-4000-8000-000000000003" + }, + { + "schemaVersion": "gamecult.desk_answer.v0", + "answer_id": "n8", + "fork_id": "gamecult-chat:question:slot-mark-consent", + "outcome": "Retraction", + "of": null, + "retracts": "n1", + "code": null, + "options_hash": "7d95fc87d532420611fe415c1def27a3447617bb68235f46e3af130aea361c48", + "choice": null, + "note": null, + "answered_at": "2026-11-12T19:09:00Z", + "received_at": "2026-11-12T19:09:05Z", + "claim_in_future": false, + "answerer": "e0d1c2b3-a495-4687-8879-6a5b4c3d2e1f", + "authority": "Operator", + "request_id": "0000001c-0000-4000-8000-00000000001c" + } + ] + }, + "recordHex": "85AD736368656D6156657273696F6EB767616D6563756C742E666F726B2E7265636F72642E7630A7666F726B5F6964D92867616D6563756C742D636861743A7175657374696F6E3A736C6F742D6D61726B2D636F6E73656E74AC6F7074696F6E735F68617368D94037643935666338376435333234323036313166653431356331646566323761333434373631376262363832333566343665336166313330616561333631633438B1636F756E7465645F616E737765725F6964A26E33A7616E737765727393DE0010AD736368656D6156657273696F6EB767616D6563756C742E6465736B5F616E737765722E7630A9616E737765725F6964A26E31A7666F726B5F6964D92867616D6563756C742D636861743A7175657374696F6E3A736C6F742D6D61726B2D636F6E73656E74A76F7574636F6D65A7436F756E746564A26F66C0A87265747261637473C0A4636F6465C0AC6F7074696F6E735F68617368D94037643935666338376435333234323036313166653431356331646566323761333434373631376262363832333566343665336166313330616561333631633438A663686F696365A26E6FA46E6F7465D9524B656570206974206C6F63616C3A206120736C6F74206D61726B2069732074686520617574686F7227732063616C6C2C20616E642073686172696E672069742069732061207365706172617465206163742EAB616E7377657265645F6174B4323032362D31312D31325431393A30323A30305AAB72656365697665645F6174B4323032362D31312D31325431393A30323A30355AAF636C61696D5F696E5F667574757265C2A8616E737765726572D92465306431633262332D613439352D343638372D383837392D366135623463336432653166A9617574686F72697479A84F70657261746F72AA726571756573745F6964D92430303030303030312D303030302D343030302D383030302D303030303030303030303031DE0010AD736368656D6156657273696F6EB767616D6563756C742E6465736B5F616E737765722E7630A9616E737765725F6964A26E33A7666F726B5F6964D92867616D6563756C742D636861743A7175657374696F6E3A736C6F742D6D61726B2D636F6E73656E74A76F7574636F6D65A85265766973696F6EA26F66A26E31A87265747261637473C0A4636F6465C0AC6F7074696F6E735F68617368D94037643935666338376435333234323036313166653431356331646566323761333434373631376262363832333566343665336166313330616561333631633438A663686F696365A3796573A46E6F7465D92253686172652069742C207468652061756469656E636520697320736574746C65642EAB616E7377657265645F6174B4323032362D31312D31325431393A30353A30305AAB72656365697665645F6174B4323032362D31312D31325431393A30353A30355AAF636C61696D5F696E5F667574757265C2A8616E737765726572D92439663865376436632D356234612D343339322D383137302D306631653264336334623561A9617574686F72697479A753746577617264AA726571756573745F6964D92430303030303030332D303030302D343030302D383030302D303030303030303030303033DE0010AD736368656D6156657273696F6EB767616D6563756C742E6465736B5F616E737765722E7630A9616E737765725F6964A26E38A7666F726B5F6964D92867616D6563756C742D636861743A7175657374696F6E3A736C6F742D6D61726B2D636F6E73656E74A76F7574636F6D65AA52657472616374696F6EA26F66C0A87265747261637473A26E31A4636F6465C0AC6F7074696F6E735F68617368D94037643935666338376435333234323036313166653431356331646566323761333434373631376262363832333566343665336166313330616561333631633438A663686F696365C0A46E6F7465C0AB616E7377657265645F6174B4323032362D31312D31325431393A30393A30305AAB72656365697665645F6174B4323032362D31312D31325431393A30393A30355AAF636C61696D5F696E5F667574757265C2A8616E737765726572D92465306431633262332D613439352D343638372D383837392D366135623463336432653166A9617574686F72697479A84F70657261746F72AA726571756573745F6964D92430303030303031632D303030302D343030302D383030302D303030303030303030303163" + }, + { + "name": "V11_retracting_an_answer_that_was_not_counted_changes_nothing", + "card": { + "schemaVersion": "gamecult.fork.card.v0", + "fork_id": "gamecult-chat:question:slot-mark-consent", + "title": "Slot mark consent", + "context": "Should a slot mark be shared?", + "options": [ + { + "id": "yes", + "text": "Share it." + }, + { + "id": "no", + "text": "Keep it local." + } + ], + "recommendation": "no", + "depends": [ + "gamecult-chat:question:file-zone-audience" + ], + "leans": null, + "flag": null, + "raised_in": { + "kind": "CutSpec", + "id": "cultlib-gaps:cut_spec:cut-slot-tolerance.r4" + }, + "deciders": [ + "e0d1c2b3-a495-4687-8879-6a5b4c3d2e1f", + "9f8e7d6c-5b4a-4392-8170-0f1e2d3c4b5a" + ], + "status": "open", + "ruling": null, + "ruled_from": [], + "order": 3, + "options_hash": "7d95fc87d532420611fe415c1def27a3447617bb68235f46e3af130aea361c48" + }, + "optionsHash": "7d95fc87d532420611fe415c1def27a3447617bb68235f46e3af130aea361c48", + "steps": [ + { + "kind": "answer", + "body": { + "schemaVersion": "gamecult.fork.answer_request.v0", + "fork_id": "gamecult-chat:question:slot-mark-consent", + "options_hash": "7d95fc87d532420611fe415c1def27a3447617bb68235f46e3af130aea361c48", + "answer": { + "choice": "no", + "note": "Keep it local: a slot mark is the author's call, and sharing it is a separate act." + }, + "retracted": false, + "retracts": null, + "answered_at": "2026-11-12T19:02:00Z", + "request_id": "00000001-0000-4000-8000-000000000001" + }, + "server": { + "answer_id": "n1", + "answerer": "e0d1c2b3-a495-4687-8879-6a5b4c3d2e1f", + "authority": "Operator", + "received_at": "2026-11-12T19:02:05Z" + }, + "response": { + "schemaVersion": "gamecult.fork.answer_result.v0", + "answer": { + "schemaVersion": "gamecult.desk_answer.v0", + "answer_id": "n1", + "fork_id": "gamecult-chat:question:slot-mark-consent", + "outcome": "Counted", + "of": null, + "retracts": null, + "code": null, + "options_hash": "7d95fc87d532420611fe415c1def27a3447617bb68235f46e3af130aea361c48", + "choice": "no", + "note": "Keep it local: a slot mark is the author's call, and sharing it is a separate act.", + "answered_at": "2026-11-12T19:02:00Z", + "received_at": "2026-11-12T19:02:05Z", + "claim_in_future": false, + "answerer": "e0d1c2b3-a495-4687-8879-6a5b4c3d2e1f", + "authority": "Operator", + "request_id": "00000001-0000-4000-8000-000000000001" + }, + "card": null + } + }, + { + "kind": "answer", + "body": { + "schemaVersion": "gamecult.fork.answer_request.v0", + "fork_id": "gamecult-chat:question:slot-mark-consent", + "options_hash": "7d95fc87d532420611fe415c1def27a3447617bb68235f46e3af130aea361c48", + "answer": { + "choice": "yes", + "note": "Share it, the audience is settled." + }, + "retracted": false, + "retracts": null, + "answered_at": "2026-11-12T19:05:00Z", + "request_id": "00000003-0000-4000-8000-000000000003" + }, + "server": { + "answer_id": "n3", + "answerer": "9f8e7d6c-5b4a-4392-8170-0f1e2d3c4b5a", + "authority": "Steward", + "received_at": "2026-11-12T19:05:05Z" + }, + "response": { + "schemaVersion": "gamecult.fork.answer_result.v0", + "answer": { + "schemaVersion": "gamecult.desk_answer.v0", + "answer_id": "n3", + "fork_id": "gamecult-chat:question:slot-mark-consent", + "outcome": "Revision", + "of": "n1", + "retracts": null, + "code": null, + "options_hash": "7d95fc87d532420611fe415c1def27a3447617bb68235f46e3af130aea361c48", + "choice": "yes", + "note": "Share it, the audience is settled.", + "answered_at": "2026-11-12T19:05:00Z", + "received_at": "2026-11-12T19:05:05Z", + "claim_in_future": false, + "answerer": "9f8e7d6c-5b4a-4392-8170-0f1e2d3c4b5a", + "authority": "Steward", + "request_id": "00000003-0000-4000-8000-000000000003" + }, + "card": null + } + }, + { + "kind": "answer", + "body": { + "schemaVersion": "gamecult.fork.answer_request.v0", + "fork_id": "gamecult-chat:question:slot-mark-consent", + "options_hash": "7d95fc87d532420611fe415c1def27a3447617bb68235f46e3af130aea361c48", + "answer": null, + "retracted": true, + "retracts": "n3", + "answered_at": "2026-11-12T19:09:00Z", + "request_id": "0000001d-0000-4000-8000-00000000001d" + }, + "server": { + "answer_id": "n8", + "answerer": "e0d1c2b3-a495-4687-8879-6a5b4c3d2e1f", + "authority": "Operator", + "received_at": "2026-11-12T19:09:05Z" + }, + "response": { + "schemaVersion": "gamecult.fork.answer_result.v0", + "answer": { + "schemaVersion": "gamecult.desk_answer.v0", + "answer_id": "n8", + "fork_id": "gamecult-chat:question:slot-mark-consent", + "outcome": "Retraction", + "of": null, + "retracts": "n3", + "code": null, + "options_hash": "7d95fc87d532420611fe415c1def27a3447617bb68235f46e3af130aea361c48", + "choice": null, + "note": null, + "answered_at": "2026-11-12T19:09:00Z", + "received_at": "2026-11-12T19:09:05Z", + "claim_in_future": false, + "answerer": "e0d1c2b3-a495-4687-8879-6a5b4c3d2e1f", + "authority": "Operator", + "request_id": "0000001d-0000-4000-8000-00000000001d" + }, + "card": null + } + } + ], + "record": { + "schemaVersion": "gamecult.fork.record.v0", + "fork_id": "gamecult-chat:question:slot-mark-consent", + "options_hash": "7d95fc87d532420611fe415c1def27a3447617bb68235f46e3af130aea361c48", + "counted_answer_id": "n1", + "answers": [ + { + "schemaVersion": "gamecult.desk_answer.v0", + "answer_id": "n1", + "fork_id": "gamecult-chat:question:slot-mark-consent", + "outcome": "Counted", + "of": null, + "retracts": null, + "code": null, + "options_hash": "7d95fc87d532420611fe415c1def27a3447617bb68235f46e3af130aea361c48", + "choice": "no", + "note": "Keep it local: a slot mark is the author's call, and sharing it is a separate act.", + "answered_at": "2026-11-12T19:02:00Z", + "received_at": "2026-11-12T19:02:05Z", + "claim_in_future": false, + "answerer": "e0d1c2b3-a495-4687-8879-6a5b4c3d2e1f", + "authority": "Operator", + "request_id": "00000001-0000-4000-8000-000000000001" + }, + { + "schemaVersion": "gamecult.desk_answer.v0", + "answer_id": "n3", + "fork_id": "gamecult-chat:question:slot-mark-consent", + "outcome": "Revision", + "of": "n1", + "retracts": null, + "code": null, + "options_hash": "7d95fc87d532420611fe415c1def27a3447617bb68235f46e3af130aea361c48", + "choice": "yes", + "note": "Share it, the audience is settled.", + "answered_at": "2026-11-12T19:05:00Z", + "received_at": "2026-11-12T19:05:05Z", + "claim_in_future": false, + "answerer": "9f8e7d6c-5b4a-4392-8170-0f1e2d3c4b5a", + "authority": "Steward", + "request_id": "00000003-0000-4000-8000-000000000003" + }, + { + "schemaVersion": "gamecult.desk_answer.v0", + "answer_id": "n8", + "fork_id": "gamecult-chat:question:slot-mark-consent", + "outcome": "Retraction", + "of": null, + "retracts": "n3", + "code": null, + "options_hash": "7d95fc87d532420611fe415c1def27a3447617bb68235f46e3af130aea361c48", + "choice": null, + "note": null, + "answered_at": "2026-11-12T19:09:00Z", + "received_at": "2026-11-12T19:09:05Z", + "claim_in_future": false, + "answerer": "e0d1c2b3-a495-4687-8879-6a5b4c3d2e1f", + "authority": "Operator", + "request_id": "0000001d-0000-4000-8000-00000000001d" + } + ] + }, + "recordHex": "85AD736368656D6156657273696F6EB767616D6563756C742E666F726B2E7265636F72642E7630A7666F726B5F6964D92867616D6563756C742D636861743A7175657374696F6E3A736C6F742D6D61726B2D636F6E73656E74AC6F7074696F6E735F68617368D94037643935666338376435333234323036313166653431356331646566323761333434373631376262363832333566343665336166313330616561333631633438B1636F756E7465645F616E737765725F6964A26E31A7616E737765727393DE0010AD736368656D6156657273696F6EB767616D6563756C742E6465736B5F616E737765722E7630A9616E737765725F6964A26E31A7666F726B5F6964D92867616D6563756C742D636861743A7175657374696F6E3A736C6F742D6D61726B2D636F6E73656E74A76F7574636F6D65A7436F756E746564A26F66C0A87265747261637473C0A4636F6465C0AC6F7074696F6E735F68617368D94037643935666338376435333234323036313166653431356331646566323761333434373631376262363832333566343665336166313330616561333631633438A663686F696365A26E6FA46E6F7465D9524B656570206974206C6F63616C3A206120736C6F74206D61726B2069732074686520617574686F7227732063616C6C2C20616E642073686172696E672069742069732061207365706172617465206163742EAB616E7377657265645F6174B4323032362D31312D31325431393A30323A30305AAB72656365697665645F6174B4323032362D31312D31325431393A30323A30355AAF636C61696D5F696E5F667574757265C2A8616E737765726572D92465306431633262332D613439352D343638372D383837392D366135623463336432653166A9617574686F72697479A84F70657261746F72AA726571756573745F6964D92430303030303030312D303030302D343030302D383030302D303030303030303030303031DE0010AD736368656D6156657273696F6EB767616D6563756C742E6465736B5F616E737765722E7630A9616E737765725F6964A26E33A7666F726B5F6964D92867616D6563756C742D636861743A7175657374696F6E3A736C6F742D6D61726B2D636F6E73656E74A76F7574636F6D65A85265766973696F6EA26F66A26E31A87265747261637473C0A4636F6465C0AC6F7074696F6E735F68617368D94037643935666338376435333234323036313166653431356331646566323761333434373631376262363832333566343665336166313330616561333631633438A663686F696365A3796573A46E6F7465D92253686172652069742C207468652061756469656E636520697320736574746C65642EAB616E7377657265645F6174B4323032362D31312D31325431393A30353A30305AAB72656365697665645F6174B4323032362D31312D31325431393A30353A30355AAF636C61696D5F696E5F667574757265C2A8616E737765726572D92439663865376436632D356234612D343339322D383137302D306631653264336334623561A9617574686F72697479A753746577617264AA726571756573745F6964D92430303030303030332D303030302D343030302D383030302D303030303030303030303033DE0010AD736368656D6156657273696F6EB767616D6563756C742E6465736B5F616E737765722E7630A9616E737765725F6964A26E38A7666F726B5F6964D92867616D6563756C742D636861743A7175657374696F6E3A736C6F742D6D61726B2D636F6E73656E74A76F7574636F6D65AA52657472616374696F6EA26F66C0A87265747261637473A26E33A4636F6465C0AC6F7074696F6E735F68617368D94037643935666338376435333234323036313166653431356331646566323761333434373631376262363832333566343665336166313330616561333631633438A663686F696365C0A46E6F7465C0AB616E7377657265645F6174B4323032362D31312D31325431393A30393A30305AAB72656365697665645F6174B4323032362D31312D31325431393A30393A30355AAF636C61696D5F696E5F667574757265C2A8616E737765726572D92465306431633262332D613439352D343638372D383837392D366135623463336432653166A9617574686F72697479A84F70657261746F72AA726571756573745F6964D92430303030303031642D303030302D343030302D383030302D303030303030303030303164" + }, + { + "name": "after_every_answer_is_retracted_a_fresh_answer_is_Counted_again", + "card": { + "schemaVersion": "gamecult.fork.card.v0", + "fork_id": "gamecult-chat:question:slot-mark-consent", + "title": "Slot mark consent", + "context": "Should a slot mark be shared?", + "options": [ + { + "id": "yes", + "text": "Share it." + }, + { + "id": "no", + "text": "Keep it local." + } + ], + "recommendation": "no", + "depends": [ + "gamecult-chat:question:file-zone-audience" + ], + "leans": null, + "flag": null, + "raised_in": { + "kind": "CutSpec", + "id": "cultlib-gaps:cut_spec:cut-slot-tolerance.r4" + }, + "deciders": [ + "e0d1c2b3-a495-4687-8879-6a5b4c3d2e1f" + ], + "status": "open", + "ruling": null, + "ruled_from": [], + "order": 3, + "options_hash": "7d95fc87d532420611fe415c1def27a3447617bb68235f46e3af130aea361c48" + }, + "optionsHash": "7d95fc87d532420611fe415c1def27a3447617bb68235f46e3af130aea361c48", + "steps": [ + { + "kind": "answer", + "body": { + "schemaVersion": "gamecult.fork.answer_request.v0", + "fork_id": "gamecult-chat:question:slot-mark-consent", + "options_hash": "7d95fc87d532420611fe415c1def27a3447617bb68235f46e3af130aea361c48", + "answer": { + "choice": "no", + "note": "Keep it local: a slot mark is the author's call, and sharing it is a separate act." + }, + "retracted": false, + "retracts": null, + "answered_at": "2026-11-12T19:02:00Z", + "request_id": "00000001-0000-4000-8000-000000000001" + }, + "server": { + "answer_id": "n1", + "answerer": "e0d1c2b3-a495-4687-8879-6a5b4c3d2e1f", + "authority": "Operator", + "received_at": "2026-11-12T19:02:05Z" + }, + "response": { + "schemaVersion": "gamecult.fork.answer_result.v0", + "answer": { + "schemaVersion": "gamecult.desk_answer.v0", + "answer_id": "n1", + "fork_id": "gamecult-chat:question:slot-mark-consent", + "outcome": "Counted", + "of": null, + "retracts": null, + "code": null, + "options_hash": "7d95fc87d532420611fe415c1def27a3447617bb68235f46e3af130aea361c48", + "choice": "no", + "note": "Keep it local: a slot mark is the author's call, and sharing it is a separate act.", + "answered_at": "2026-11-12T19:02:00Z", + "received_at": "2026-11-12T19:02:05Z", + "claim_in_future": false, + "answerer": "e0d1c2b3-a495-4687-8879-6a5b4c3d2e1f", + "authority": "Operator", + "request_id": "00000001-0000-4000-8000-000000000001" + }, + "card": null + } + }, + { + "kind": "answer", + "body": { + "schemaVersion": "gamecult.fork.answer_request.v0", + "fork_id": "gamecult-chat:question:slot-mark-consent", + "options_hash": "7d95fc87d532420611fe415c1def27a3447617bb68235f46e3af130aea361c48", + "answer": null, + "retracted": true, + "retracts": "n1", + "answered_at": "2026-11-12T19:09:00Z", + "request_id": "0000001e-0000-4000-8000-00000000001e" + }, + "server": { + "answer_id": "n8", + "answerer": "e0d1c2b3-a495-4687-8879-6a5b4c3d2e1f", + "authority": "Operator", + "received_at": "2026-11-12T19:09:05Z" + }, + "response": { + "schemaVersion": "gamecult.fork.answer_result.v0", + "answer": { + "schemaVersion": "gamecult.desk_answer.v0", + "answer_id": "n8", + "fork_id": "gamecult-chat:question:slot-mark-consent", + "outcome": "Retraction", + "of": null, + "retracts": "n1", + "code": null, + "options_hash": "7d95fc87d532420611fe415c1def27a3447617bb68235f46e3af130aea361c48", + "choice": null, + "note": null, + "answered_at": "2026-11-12T19:09:00Z", + "received_at": "2026-11-12T19:09:05Z", + "claim_in_future": false, + "answerer": "e0d1c2b3-a495-4687-8879-6a5b4c3d2e1f", + "authority": "Operator", + "request_id": "0000001e-0000-4000-8000-00000000001e" + }, + "card": null + } + }, + { + "kind": "answer", + "body": { + "schemaVersion": "gamecult.fork.answer_request.v0", + "fork_id": "gamecult-chat:question:slot-mark-consent", + "options_hash": "7d95fc87d532420611fe415c1def27a3447617bb68235f46e3af130aea361c48", + "answer": { + "choice": "yes", + "note": "On reflection, share it." + }, + "retracted": false, + "retracts": null, + "answered_at": "2026-11-12T19:06:00Z", + "request_id": "00000002-0000-4000-8000-000000000002" + }, + "server": { + "answer_id": "n2", + "answerer": "e0d1c2b3-a495-4687-8879-6a5b4c3d2e1f", + "authority": "Operator", + "received_at": "2026-11-12T19:06:05Z" + }, + "response": { + "schemaVersion": "gamecult.fork.answer_result.v0", + "answer": { + "schemaVersion": "gamecult.desk_answer.v0", + "answer_id": "n2", + "fork_id": "gamecult-chat:question:slot-mark-consent", + "outcome": "Counted", + "of": null, + "retracts": null, + "code": null, + "options_hash": "7d95fc87d532420611fe415c1def27a3447617bb68235f46e3af130aea361c48", + "choice": "yes", + "note": "On reflection, share it.", + "answered_at": "2026-11-12T19:06:00Z", + "received_at": "2026-11-12T19:06:05Z", + "claim_in_future": false, + "answerer": "e0d1c2b3-a495-4687-8879-6a5b4c3d2e1f", + "authority": "Operator", + "request_id": "00000002-0000-4000-8000-000000000002" + }, + "card": null + } + } + ], + "record": { + "schemaVersion": "gamecult.fork.record.v0", + "fork_id": "gamecult-chat:question:slot-mark-consent", + "options_hash": "7d95fc87d532420611fe415c1def27a3447617bb68235f46e3af130aea361c48", + "counted_answer_id": "n2", + "answers": [ + { + "schemaVersion": "gamecult.desk_answer.v0", + "answer_id": "n1", + "fork_id": "gamecult-chat:question:slot-mark-consent", + "outcome": "Counted", + "of": null, + "retracts": null, + "code": null, + "options_hash": "7d95fc87d532420611fe415c1def27a3447617bb68235f46e3af130aea361c48", + "choice": "no", + "note": "Keep it local: a slot mark is the author's call, and sharing it is a separate act.", + "answered_at": "2026-11-12T19:02:00Z", + "received_at": "2026-11-12T19:02:05Z", + "claim_in_future": false, + "answerer": "e0d1c2b3-a495-4687-8879-6a5b4c3d2e1f", + "authority": "Operator", + "request_id": "00000001-0000-4000-8000-000000000001" + }, + { + "schemaVersion": "gamecult.desk_answer.v0", + "answer_id": "n8", + "fork_id": "gamecult-chat:question:slot-mark-consent", + "outcome": "Retraction", + "of": null, + "retracts": "n1", + "code": null, + "options_hash": "7d95fc87d532420611fe415c1def27a3447617bb68235f46e3af130aea361c48", + "choice": null, + "note": null, + "answered_at": "2026-11-12T19:09:00Z", + "received_at": "2026-11-12T19:09:05Z", + "claim_in_future": false, + "answerer": "e0d1c2b3-a495-4687-8879-6a5b4c3d2e1f", + "authority": "Operator", + "request_id": "0000001e-0000-4000-8000-00000000001e" + }, + { + "schemaVersion": "gamecult.desk_answer.v0", + "answer_id": "n2", + "fork_id": "gamecult-chat:question:slot-mark-consent", + "outcome": "Counted", + "of": null, + "retracts": null, + "code": null, + "options_hash": "7d95fc87d532420611fe415c1def27a3447617bb68235f46e3af130aea361c48", + "choice": "yes", + "note": "On reflection, share it.", + "answered_at": "2026-11-12T19:06:00Z", + "received_at": "2026-11-12T19:06:05Z", + "claim_in_future": false, + "answerer": "e0d1c2b3-a495-4687-8879-6a5b4c3d2e1f", + "authority": "Operator", + "request_id": "00000002-0000-4000-8000-000000000002" + } + ] + }, + "recordHex": "85AD736368656D6156657273696F6EB767616D6563756C742E666F726B2E7265636F72642E7630A7666F726B5F6964D92867616D6563756C742D636861743A7175657374696F6E3A736C6F742D6D61726B2D636F6E73656E74AC6F7074696F6E735F68617368D94037643935666338376435333234323036313166653431356331646566323761333434373631376262363832333566343665336166313330616561333631633438B1636F756E7465645F616E737765725F6964A26E32A7616E737765727393DE0010AD736368656D6156657273696F6EB767616D6563756C742E6465736B5F616E737765722E7630A9616E737765725F6964A26E31A7666F726B5F6964D92867616D6563756C742D636861743A7175657374696F6E3A736C6F742D6D61726B2D636F6E73656E74A76F7574636F6D65A7436F756E746564A26F66C0A87265747261637473C0A4636F6465C0AC6F7074696F6E735F68617368D94037643935666338376435333234323036313166653431356331646566323761333434373631376262363832333566343665336166313330616561333631633438A663686F696365A26E6FA46E6F7465D9524B656570206974206C6F63616C3A206120736C6F74206D61726B2069732074686520617574686F7227732063616C6C2C20616E642073686172696E672069742069732061207365706172617465206163742EAB616E7377657265645F6174B4323032362D31312D31325431393A30323A30305AAB72656365697665645F6174B4323032362D31312D31325431393A30323A30355AAF636C61696D5F696E5F667574757265C2A8616E737765726572D92465306431633262332D613439352D343638372D383837392D366135623463336432653166A9617574686F72697479A84F70657261746F72AA726571756573745F6964D92430303030303030312D303030302D343030302D383030302D303030303030303030303031DE0010AD736368656D6156657273696F6EB767616D6563756C742E6465736B5F616E737765722E7630A9616E737765725F6964A26E38A7666F726B5F6964D92867616D6563756C742D636861743A7175657374696F6E3A736C6F742D6D61726B2D636F6E73656E74A76F7574636F6D65AA52657472616374696F6EA26F66C0A87265747261637473A26E31A4636F6465C0AC6F7074696F6E735F68617368D94037643935666338376435333234323036313166653431356331646566323761333434373631376262363832333566343665336166313330616561333631633438A663686F696365C0A46E6F7465C0AB616E7377657265645F6174B4323032362D31312D31325431393A30393A30305AAB72656365697665645F6174B4323032362D31312D31325431393A30393A30355AAF636C61696D5F696E5F667574757265C2A8616E737765726572D92465306431633262332D613439352D343638372D383837392D366135623463336432653166A9617574686F72697479A84F70657261746F72AA726571756573745F6964D92430303030303031652D303030302D343030302D383030302D303030303030303030303165DE0010AD736368656D6156657273696F6EB767616D6563756C742E6465736B5F616E737765722E7630A9616E737765725F6964A26E32A7666F726B5F6964D92867616D6563756C742D636861743A7175657374696F6E3A736C6F742D6D61726B2D636F6E73656E74A76F7574636F6D65A7436F756E746564A26F66C0A87265747261637473C0A4636F6465C0AC6F7074696F6E735F68617368D94037643935666338376435333234323036313166653431356331646566323761333434373631376262363832333566343665336166313330616561333631633438A663686F696365A3796573A46E6F7465B84F6E207265666C656374696F6E2C2073686172652069742EAB616E7377657265645F6174B4323032362D31312D31325431393A30363A30305AAB72656365697665645F6174B4323032362D31312D31325431393A30363A30355AAF636C61696D5F696E5F667574757265C2A8616E737765726572D92465306431633262332D613439352D343638372D383837392D366135623463336432653166A9617574686F72697479A84F70657261746F72AA726571756573745F6964D92430303030303030322D303030302D343030302D383030302D303030303030303030303032" + } + ] +} diff --git a/contracts/cultnet/governance/fork-card.md b/contracts/cultnet/governance/fork-card.md new file mode 100644 index 00000000..0edfd650 --- /dev/null +++ b/contracts/cultnet/governance/fork-card.md @@ -0,0 +1,193 @@ +# CultNet fork-desk contract v0 (specification) + +Status: specification, drafted for Heimdall (Bifrost receiver), Huginn (the second door) and Void (Ratatoskr desk). No runtime implements it yet. +Rule source: `/opt/commons/handoff/fork-desk-answer-binding-20261110.md` (Heimdall; sections 1.1 to 1.5, 1.3a to 1.3d), amended by the Persona briefs of 2026-11-10 (Eureka's fit check). Where this page differs from that file it says so under "Differences from the binding file". +Format source: the fork desk artifact in `/opt/commons/handoff/fork-desk-original/` (ruling `eureka-substrate:ruling:fork-desk-nibu-format`). +Files here: `fork-card.schema.json` (one schema, a `$defs` entry per document), `fork-card-vectors.json`. Conformance: `tests/GameCult.Networking.Tests/GovernanceVectorTests.cs` (C#) and `packages/cultnet-ts/test/governance-vectors.test.ts` (TypeScript), both generated against vectors written by `scripts/gen-governance-vectors.py`. Each suite computes every hash with its own code and replays every scenario through a reference judge written from this page. + +All documents are CultNet map-mode: every key written, nil where absent, closed schemas. Field names are snake_case as in the binding file and the mind's records; `schemaVersion` keeps CultNet's spelling (open choice 1). The bytes of every document are pinned in the vectors as `messagePackHex`. + +## Documents + +| schema id | what | who writes it | +|---|---|---| +| `gamecult.fork.card.v0` | the fork as shown | the publisher (a Mind's card projection) | +| `gamecult.fork.answer_request.v0` | the answer a decider sends | the client; Bifrost forwards it unchanged to Huginn's second door | +| `gamecult.desk_answer.v0` | the stored answer with one tagged outcome | Huginn only | +| `gamecult.fork.answer_result.v0` | what the sender gets back for a stored answer | Bifrost, from the stored `desk_answer` | +| `gamecult.fork.refusal.v0` | a body refused at the door; nothing stored | Bifrost | +| `gamecult.fork.record.v0` | a fork's answers plus the derived counted answer | a projection of the stored `desk_answer`s | +| `gamecult.fork.ruling_cite.v0` | the citation a ruling carries | the Mind admitting the ruling | +| `gamecult.fork.list_request.v0` | list a Mind's forks (Transport) | the desk | +| `gamecult.fork.list.v0` | a page of entries, counts and a stamp | Bifrost, projected from Huginn | +| `gamecult.fork.read_request.v0` | read one fork (Transport) | the desk | +| `gamecult.fork.entry.v0` | one card with its record | Bifrost, projected from Huginn | +| `gamecult.fork.unavailable.v0` | the outcome is not confirmed (the second door or Huginn did not answer); retry with the same `request_id` | Bifrost | + +### card + +`{schemaVersion, fork_id, title, context, options[{id, text}], recommendation, depends[], leans, flag, raised_in, deciders[], status, ruling, ruled_from[], order, options_hash}`. + +- `fork_id`: the mind's `question` key, `:question: