Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
147 changes: 147 additions & 0 deletions workers/godlock-uk/factor-detail.test.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,147 @@
import { describe, it } from "node:test";
import assert from "node:assert/strict";
import { scoreDebateText } from "./src/debateScore.js";
import { factorDetail, storedFactorView, factorDetailSealed, NOT_STORED, NOT_SEALED_LABEL, FACTOR_KEYS } from "./src/factorDetail.js";
import { hashPayload, canonicalJson, ZERO } from "./src/ledger.js";
import { receiptLedgerAndProof, _resetProofCache, HEAD_SQL, TAIL_SQL, BY_RECEIPT_SQL } from "./src/receiptProof.js";
import { threadView, threadExpandScript } from "./src/ui.js";

const TEXT = "Functionally specified digital information joined to a translation reader is the steel class. Pretty spirals are not a proof. Probability given evidence suggests design.";

describe("factor detail at write time", () => {
const review = scoreDebateText(TEXT, "");
const fd = factorDetail(review);
it("values are exactly the checker's factors", () => {
for (const k of fd.cycle_factors) assert.equal(fd.factors[k].value, review.triad.factors[k]);
assert.equal(fd.combined, review.triad.combined);
});
it("pairing products reproduce combined as the mean", () => {
const n = fd.cycle_factors.length;
assert.equal(fd.pairings.length, n * n);
assert.equal(fd.pairings.length, review.triad.pairing_count);
const mean = fd.pairings.reduce((a, p) => a + p.product, 0) / fd.pairings.length;
assert.ok(Math.abs(mean - fd.combined) < 0.00005, mean + " vs " + fd.combined);
});
it("checker reasons are copied verbatim, not invented", () => {
assert.deepEqual(fd.factors.spre.checker.factors, review.spre.factors);
assert.equal(fd.factors.spre.checker.band, review.spre.band);
assert.deepEqual(fd.factors.truth_formula.checker.factors, review.truth_formula.factors);
assert.equal(fd.factors.spre.applicability_reason, review.applicability.spre.reason);
});
it("every omitted factor carries the checker's reason and is not scored as 0", () => {
const omittedKeys = fd.omitted.map((o) => o.factor);
for (const k of FACTOR_KEYS) assert.ok(fd.cycle_factors.includes(k) !== omittedKeys.includes(k));
const clce = fd.omitted.find((o) => o.factor === "clce");
assert.equal(clce.reason, review.applicability.clce.reason);
assert.match(fd.omitted_note, /not scored as 0/);
});
it("no triad means no detail", () => assert.equal(factorDetail({}), null));
});

describe("stored factor view (read time, no recompute)", () => {
it("old receipt without stored detail says so", () => {
const v = storedFactorView([{ reason: "initial", triad_json: JSON.stringify({ factors: { spre: 0.1 } }) }]);
assert.equal(v.factor_detail, null); assert.equal(v.factor_detail_note, NOT_STORED);
});
it("rescore rows show which factors changed and by how much", () => {
const v = storedFactorView([
{ reason: "initial", triad_json: JSON.stringify({ factors: { spre: 0.15, bayesian: 0.5, clce: null } }) },
{ reason: "supersedes", created_utc: "t2", triad_json: JSON.stringify({ factors: { spre: 0.3, bayesian: 0.5, clce: 0.2 } }) },
]);
assert.deepEqual(v.rescore_factor_changes[0].changes, [
{ factor: "clce", before: null, after: 0.2, delta: null },
{ factor: "spre", before: 0.15, after: 0.3, delta: 0.15 },
]);
});
it("rescore without stored factors is not guessed", () => {
const v = storedFactorView([{ reason: "initial", triad_json: null }, { reason: "x", triad_json: JSON.stringify({ factors: { spre: 1 } }) }]);
assert.equal(v.rescore_factor_changes[0].changes, null);
assert.equal(v.rescore_factor_changes[0].note, NOT_STORED);
});
});

describe("drop-down view", () => {
const fd = factorDetail(scoreDebateText(TEXT, ""));
const base = { ok: true, proof: { status: "missing" } };
it("renders values, pairings, combined and omitted reasons", () => {
const v = threadView({ ...base, receipt: { summary: "S", factor_detail: fd, rescore_factor_changes: [] } });
const m = new Map(v.factors);
assert.equal(m.get("Factor spre"), String(fd.factors.spre.value));
assert.ok([...m.keys()].some((k) => k.startsWith("Pairing bayesian × spre")));
assert.match(m.get("Omitted clce"), /Not scored as 0/);
assert.equal(v.note, "Hash-linked and sealed. Not signed.");
});
it("old receipts show the not-stored line", () => {
const v = threadView({ ...base, receipt: { summary: "S" } });
assert.deepEqual(v.factors, [["Seal coverage", NOT_SEALED_LABEL], ["Per-factor", "per-factor detail not stored for this receipt"]]);
});
it("rescore changes render with signed deltas", () => {
const v = threadView({ ...base, receipt: { factor_detail: null, rescore_factor_changes: [{ created_utc: "t2", changes: [{ factor: "spre", before: 0.15, after: 0.3, delta: 0.15 }] }] } });
assert.deepEqual(v.factors[2], ["Rescore t2", "spre 0.15 → 0.3 (+0.15)"]);
});
it("script uses textContent only and still parses", () => {
const js = threadExpandScript();
assert.doesNotMatch(js, /innerHTML|insertAdjacentHTML|document\.write/);
assert.doesNotThrow(() => new Function(js.replace(/^<script>|<\/script>$/g, "")));
});
});

function fakeDb(rows) {
return { prepare(sql) {
let args = [];
const run = () => {
if (sql === HEAD_SQL) return rows[rows.length - 1];
if (sql === TAIL_SQL) return rows.filter((r) => r.sequence > args[0]);
if (sql === BY_RECEIPT_SQL) return rows.filter((r) => JSON.parse(r.payload_json).receipt_id === args[0]);
return null;
};
const st = { bind(...a) { args = a; return st; }, async first() { return run(); }, async all() { return { results: run() }; }, async run() { return {}; } };
return st;
} };
}
function ledgerWith(payloads) {
const rows = []; let prev = ZERO;
payloads.forEach(([action, payload], i) => {
const e = { sequence: i + 1, timestamp_utc: "t" + i, action, payload, previous_hash: prev };
const entry_hash = hashPayload(e);
rows.push({ ...e, payload_json: canonicalJson(payload), entry_hash }); prev = entry_hash;
});
return rows;
}

describe("factor detail seal coverage", () => {
const fd = factorDetail(scoreDebateText(TEXT, ""));
const stored = () => [{ reason: "initial", triad_json: JSON.stringify({ factors: {}, factor_detail: fd }) }];
const rows = () => ledgerWith([["SUBMIT", { receipt_id: "r1", factor_detail_sha256: hashPayload(fd) }], ["SCORE", { receipt_id: "r1" }]]);
it("covered when the row matches the checkpoint and the sha matches", async () => {
_resetProofCache();
const { rows: lr, proof } = await receiptLedgerAndProof({ DB: fakeDb(rows()) }, "r1");
assert.equal(proof.status, "verified");
assert.equal(factorDetailSealed(storedFactorView(stored()).factor_detail, lr, proof), true);
const v = threadView({ ok: true, proof, receipt: { factor_detail: fd, factor_detail_sealed: true } });
assert.match(v.factors[0][1], /covered by the seal/); assert.doesNotMatch(v.factors[0][1], /not covered/);
});
it("triad_json edited after the write: not covered, sealed false", async () => {
_resetProofCache();
const { rows: lr, proof } = await receiptLedgerAndProof({ DB: fakeDb(rows()) }, "r1");
const s = stored(); const snap = JSON.parse(s[0].triad_json);
snap.factor_detail.factors.spre.value = 0.99; s[0].triad_json = JSON.stringify(snap);
const sealed = factorDetailSealed(storedFactorView(s).factor_detail, lr, proof);
assert.equal(sealed, false);
const v = threadView({ ok: true, proof, receipt: { factor_detail: snap.factor_detail, factor_detail_sealed: sealed } });
assert.equal(v.factors[0][1], NOT_SEALED_LABEL);
});
it("old SUBMIT without factor_detail_sha256 is not covered", async () => {
_resetProofCache();
const old = ledgerWith([["SUBMIT", { receipt_id: "r1" }]]);
const { rows: lr, proof } = await receiptLedgerAndProof({ DB: fakeDb(old) }, "r1");
assert.equal(factorDetailSealed(fd, lr, proof), false);
});
it("unverified proof is never covered", () => {
assert.equal(factorDetailSealed(fd, rows(), { status: "unverified", entries: [] }), false);
});
it("pairing products display rounded to 4 places", () => {
const v = threadView({ ok: true, proof: {}, receipt: { factor_detail: { cycle_factors: [], factors: {}, pairings: [{ i: "a", j: "b", product: 0.123456789 }], omitted: [] } } });
assert.deepEqual(v.factors.find((x) => x[0] === "Pairing a × b"), ["Pairing a × b", "0.1235"]);
});
});
2 changes: 1 addition & 1 deletion workers/godlock-uk/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
"license": "Apache-2.0",
"type": "module",
"scripts": {
"test": "node --test steer.test.mjs presence.test.mjs pages.test.mjs engine.test.mjs runtime.test.mjs mesh.test.mjs site-presence.test.mjs donate.test.mjs receipts.test.mjs identity.test.mjs ingest.test.mjs shelves.test.mjs redline.test.mjs submit.test.mjs survival.test.mjs help.test.mjs softwares-ssot.test.mjs sot-outlet.test.mjs debate-score.test.mjs indexnow.test.mjs thread-expand.test.mjs"
"test": "node --test steer.test.mjs presence.test.mjs pages.test.mjs engine.test.mjs runtime.test.mjs mesh.test.mjs site-presence.test.mjs donate.test.mjs receipts.test.mjs identity.test.mjs ingest.test.mjs shelves.test.mjs redline.test.mjs submit.test.mjs survival.test.mjs help.test.mjs softwares-ssot.test.mjs sot-outlet.test.mjs debate-score.test.mjs indexnow.test.mjs thread-expand.test.mjs factor-detail.test.mjs"
},
"dependencies": {
"wrangler": "^4.127.1"
Expand Down
145 changes: 145 additions & 0 deletions workers/godlock-uk/src/factorDetail.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,145 @@
/**
* Per-factor detail for a receipt, captured at write time from the checker's
* own output (reviewDocument / TRIAD_V3). Nothing here calls a model or
* invents a reason: every value and reason is copied from what the checker
* returned. Products are the arithmetic factor_i × factor_j of those values.
* Stored inside receipt_rescores.triad_json, which is not part of any receipt
* or ledger hash, so existing hashes are untouched.
* Author: Aziel Eliab.
*/
import { hashPayload } from "./ledger.js";

export const FACTOR_DETAIL_SCHEMA = "godlock.factor_detail.v1";
export const FACTOR_KEYS = ["physics", "linguistics", "bayesian", "truth_formula", "clce", "spre"];
export const NOT_STORED = "per-factor detail not stored for this receipt";
const NO_REASON = "checker returned no reason";

function pick(obj, keys) {
const out = {};
if (!obj || typeof obj !== "object") return out;
for (const k of keys) {
if (obj[k] !== undefined) out[k] = obj[k];
}
return out;
}

/** Checker output for one factor, verbatim, limited to the fields that explain the value. */
function checkerOutput(review, key) {
const r = review || {};
switch (key) {
case "spre": return { engine: "SPRE", ...pick(r.spre, ["pc", "band", "factors", "audit_flags", "kid_plain"]) };
case "clce": return { engine: "CLCE", ...pick(r.clce, ["structural", "band", "claim_count", "evidence_span_count", "headings_present", "verified_file", "kid_plain"]) };
case "physics": return { engine: "PLR", ...pick(r.plr, ["physics_coherence", "status", "lights", "flags", "kid_plain"]) };
case "linguistics": return { engine: "PLR", ...pick(r.plr, ["linguistic_neutrality", "status", "lights", "flags", "kid_plain"]) };
case "truth_formula": return { engine: "TRUTH_FORMULA", ...pick(r.truth_formula, ["score", "mode", "factors", "formula"]) };
case "bayesian": return { engine: "BAYESIAN", ...pick(r.bayesian, ["posterior", "applied", "hits", "hypotheses", "note"]) };
default: return {};
}
}

function applicabilityReason(review, key) {
const a = review && review.applicability;
if (!a) return NO_REASON;
const src = key === "physics" || key === "linguistics" ? a.plr : a[key];
return src && typeof src.reason === "string" && src.reason ? src.reason : NO_REASON;
}

/** Build at write time. Returns null when the checker produced no triad. */
export function factorDetail(review) {
const triad = review && review.triad;
if (!triad || !triad.factors) return null;
const flags = (review.applicability && review.applicability.flags) || {};
const cycle = Array.isArray(triad.cycle_factors) ? triad.cycle_factors.slice() : [];
const factors = {};
const omitted = [];
for (const key of FACTOR_KEYS) {
const value = triad.factors[key] === undefined ? null : triad.factors[key];
const applicable = cycle.includes(key);
if (applicable) {
factors[key] = { value, applicability_reason: applicabilityReason(review, key), checker: checkerOutput(review, key) };
} else {
omitted.push({ factor: key, reason: applicabilityReason(review, key), flag: flags[key] === undefined ? null : flags[key] });
}
}
const pairings = [];
for (const i of cycle) {
for (const j of cycle) {
pairings.push({ i, j, product: Number(triad.factors[i]) * Number(triad.factors[j]) });
}
}
return {
schema: FACTOR_DETAIL_SCHEMA,
triad_schema: triad.schema || null,
combined: triad.combined === undefined ? null : triad.combined,
display: triad.display === undefined ? null : triad.display,
cycle_factors: cycle,
factors,
pairings,
omitted,
omitted_note: "Omitted factors are not scored as 0.",
};
}

function parse(json) {
if (!json) return null;
try { return JSON.parse(json); } catch { return null; }
}

/**
* Read time, from stored rows only (no recompute). rows = receipt_rescores for
* this receipt in created order, each with triad_json.
*/
export function storedFactorView(rows) {
const list = Array.isArray(rows) ? rows : [];
const initial = list.find((r) => r && r.reason === "initial") || null;
const snapInitial = initial ? parse(initial.triad_json) : null;
const detail = snapInitial && snapInitial.factor_detail && snapInitial.factor_detail.schema === FACTOR_DETAIL_SCHEMA
? snapInitial.factor_detail
: null;
const changes = [];
let prev = snapInitial;
for (const r of list) {
if (!r || r === initial) continue;
const snap = parse(r.triad_json);
const entry = { created_utc: r.created_utc, reason: r.reason, changes: null, note: null };
if (!prev || !prev.factors || !snap || !snap.factors) {
entry.note = NOT_STORED;
} else {
entry.changes = [];
for (const key of FACTOR_KEYS) {
const a = prev.factors[key] == null ? null : Number(prev.factors[key]);
const b = snap.factors[key] == null ? null : Number(snap.factors[key]);
if (a === b) continue;
entry.changes.push({ factor: key, before: a, after: b, delta: a != null && b != null ? Math.round((b - a) * 10000) / 10000 : null });
}
}
changes.push(entry);
if (snap) prev = snap;
}
return { factor_detail: detail, factor_detail_note: detail ? null : NOT_STORED, rescore_factor_changes: changes };
}

export const NOT_SEALED_LABEL = "Per-factor detail is stored data. It is not covered by the seal.";
export const SEALED_LABEL = "Per-factor detail is covered by the seal: its sha256 matches factor_detail_sha256 in the sealed SUBMIT entry.";

/**
* True only when: proof is verified, the SUBMIT ledger row for this receipt
* matches the verified checkpoint, it carries factor_detail_sha256, and the
* sha256 of the stored factor_detail (canonical JSON) equals it. Else false.
*/
export function factorDetailSealed(detail, ledgerRows, proof) {
if (!detail || !proof || proof.status !== "verified") return false;
const entries = Array.isArray(proof.entries) ? proof.entries : [];
const rows = Array.isArray(ledgerRows) ? ledgerRows : [];
for (const row of rows) {
if (String(row.action) !== "SUBMIT") continue;
let payload;
try { payload = JSON.parse(row.payload_json || "{}"); } catch { continue; }
const want = payload && payload.factor_detail_sha256;
if (typeof want !== "string" || want.length !== 64) continue;
const e = entries.find((x) => Number(x.sequence) === Number(row.sequence) && x.entry_hash === row.entry_hash);
if (!e || !e.hash_ok || !e.in_verified_range || !e.matches_checkpoint) return false;
return hashPayload(detail) === want;
}
return false;
}
Loading
Loading