PROCITY/tools/qa/r41_denylist.mjs
m3ultra d14cf7fa6e Lane F R41 §41.6: 18 gates green — the deny-list, the wardrobe switch, and no byte-hash goldens
qa.sh --strict: 18 passed · 0 failed · 0 warn · 0 skipped. selfcheck 157,647/157,647,
fingerprint 0x5f76e76 unmoved. No tag — John's playtest session rules the epoch.

THE FIVE ASKS. [C] wardrobe wired via exported WARDROBE_BASE + stockBaseFor(shop), preloaded
inside the existing STOCK_REAL gate: ON under ?stock=real (52 pack items, 69 garment ids on
rendered meshes, 1 atlas, exactly 2 requests) and CHEAPER (59->55 draws, 111->100 textures);
OFF is what ships by default, A/B'd against a reverted control tree — default arm identical
on every counter (160 URLs same hash, 131/18,018 street, 59/28,505 interior). [D] three gates
wired (r41_shots.py writes to TMPDIR so gate runs never rewrite committed shots). [E]
clips_verify wired — 46 clips / 6 groups / 3,498,124 B.

[B's bookmark finding] CONFIRMED AND WIDER: street_noon, shopfront_detail, crossroads_busy
AND market_square all give 3 distinct hashes across 3 boots; only night_neon is stable.
RULING: no gate pins a screenshot byte-hash on any bookmark — everything R41 asserts is a
counter. Filed to B: one await document.fonts.ready in buildings.js.

[C's drawSweep] CONFIRMED INDEPENDENTLY with F's own sweep (never calls C's): GLB-on worst
123 @ dept/auto. The phantom control (one stale room left in scene) reads 197 — +74 on every
reading — decomposing R39/R40's '188' as 116 real + ~72 phantom.

NEW GATES, controls demonstrated. r41_denylist.mjs (ruling 3): 738 files / 197.3 MiB scanned
by path AND bytes, 7 banned names, 0 hits; the control plants a banned manifest row every run
-> RED on 2 names -> removed -> GREEN. r41_integration.py: wardrobe both arms · interior <=350
on both instruments · ?noassets=1 zero across six fetch classes over a 3-shop walk (control
fetches 5/6) · ?clips=0 142 URLs and ?classic=1 125 URLs with zero R41 cargo.

BUDGETS. Street: noon 282 · NIGHT 291/120,093 — margin 9, INTACT · classic 269 byte-exact ·
?clips=0 NIGHT 291, delta 0. No lane spent a street draw. Interior: true pre-R41 116, R41 123
@ dept/auto, margin 227; over 33 real shops worst 110; pub 49 quiet / 77 gig night.

FILED BACK: flags_check's _enter_record_shop went RED on 8 smokes because §41.4's bins put
every one of the first six record-shop counters inside 2.6 m — widened to the whole open set,
verified not a game defect. smoke_djdance read _actions[0] and D's per-instance clip swapping
made the mixer multi-action, so a healthy dancer was called a stall — now follows the clip by
name. D's r41_shots.py browse arm is deterministically red here (re-enters the winner after a
~3 s/candidate scan, by which time the occupant has left) — wired WARN-level with the reason
at the call site, fix handed to D. F's own r39_transmission.py had an unmeasured 30 s goto
default that flaked once under a full strict run; pinned to 90 s.

11 proof frames in docs/shots/laneF_r41/ with sidecars: street_postures (5 rigs, 3 clips
playing) against its ?clips=0 control (1 clip) · street_lean · browse_interior · pub_furnished
+ the honest kit before/after pair · record_dj_booth · opshop_wardrobe on/off · credits_panel
with ODbL on screen. Every humanoid frame carries its R10 human-sized line.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-07 18:11:58 +10:00

147 lines
8.8 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#!/usr/bin/env node
// PROCITY Lane F — R41 §41.6 THE DENY-LIST GATE (ruling 3 becomes repo law, enforced).
//
// node tools/qa/r41_denylist.mjs [--verbose]
//
// ROUND41 ruling 3, verbatim: these never enter `web/` under any circumstance —
// • character_kit_modular/exports/bodies/** (44 bodies, 30 named -nsfw, 465k tris, broken bboxes)
// • any anatomy/ dir
// • elsa_coronation_hair_wig_kh3.glb (Disney/Square Enix IP rip)
// • daphne_sexy.glb (unverified rip)
// • ~/Documents/mocaponline/** (vendor demo packs, licence unread — includes Epic's
// EULA-bound SK_Mannequin.fbx, named in the ruling)
//
// This is a LICENCE/IP gate, so it is deliberately blunt in two directions at once:
//
// 1. PATHS — no file under web/ may sit at, or under, a banned path component.
// 2. BYTES — no file under web/ may CONTAIN a banned name, in any encoding this repo uses. The scan
// reads every byte of every file, binaries included, and that is the point: a GLB carries its
// source mesh/node names inside the JSON chunk, and a manifest carries the path it was published
// from. "Nothing references it" is a claim about text; "nothing contains it" is a claim about the
// bytes that ship, and the second is the one a licence audit asks.
//
// THE CONTROL RUNS EVERY TIME. A deny-list that has never fired is indistinguishable from a deny-list
// with a typo in its regex — the R25 vacuous-gate law. So the gate plants a real banned reference in a
// real file under web/, asserts the scan goes RED on it, removes it, and asserts the scan goes GREEN
// again. Both arms print. The plant is removed in a `finally`, and its name is asserted absent before
// the run starts (so a crashed earlier run cannot poison the tree silently).
//
// Exit 0 = clean · 1 = a banned reference is in web/, or the control failed to fire.
import fs from 'node:fs';
import path from 'node:path';
import { fileURLToPath } from 'node:url';
const ROOT = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..', '..');
const WEB = path.join(ROOT, 'web');
const VERBOSE = process.argv.includes('--verbose');
const C = { red: '\x1b[31m', grn: '\x1b[32m', yel: '\x1b[33m', dim: '\x1b[2m', bold: '\x1b[1m', off: '\x1b[0m' };
let fails = 0;
const OK = (m) => console.log(` ${C.grn}${C.off} ${m}`);
const FAIL = (m) => { console.log(` ${C.red}${C.off} ${m}`); fails++; };
const NOTE = (m) => console.log(` ${C.yel}·${C.off} ${m}`);
const head = (m) => console.log(`\n${C.bold}${m}${C.off}`);
// ── the list ─────────────────────────────────────────────────────────────────────────────────────
// `token`: a byte sequence that must not appear in any file under web/ (matched case-insensitively).
// `component`: a path component that must not appear in any path under web/.
const BANNED = [
{ token: 'character_kit_modular', why: 'ruling 3 — the 44 bodies (30 -nsfw, 465k tris, 0x0x0 bboxes)' },
{ token: 'exports/bodies/', why: 'ruling 3 — the bodies export dir, by its own path' },
{ token: 'elsa_coronation_hair_wig_kh3', why: 'ruling 3 — Disney/Square Enix IP rip' },
{ token: 'daphne_sexy', why: 'ruling 3 — unverified rip' },
{ token: 'mocaponline', why: 'ruling 3 — vendor demo packs, licence unread' },
{ token: 'SK_Mannequin', why: "ruling 3 — Epic's EULA-bound mannequin, named in the mocaponline clause" },
{ token: 'anatomy/', why: 'ruling 3 — any anatomy/ dir' },
];
const BANNED_COMPONENTS = ['anatomy', 'character_kit_modular', 'mocaponline'];
// ── the scan ─────────────────────────────────────────────────────────────────────────────────────
function walk(dir, out = []) {
for (const e of fs.readdirSync(dir, { withFileTypes: true })) {
const p = path.join(dir, e.name);
if (e.isSymbolicLink()) { out.push({ p, link: true }); continue; }
if (e.isDirectory()) walk(p, out);
else if (e.isFile()) out.push({ p, link: false });
}
return out;
}
// Case-insensitive byte search. Tokens are pure ASCII, so lower-casing the haystack bytes in place is
// exact — no encoding assumption, works on UTF-8 text and on GLB/webp/jpg binaries alike.
const needles = BANNED.map((b) => ({ ...b, bytes: Buffer.from(b.token.toLowerCase(), 'latin1') }));
function scanBytes(buf) {
const lower = Buffer.from(buf); // copy, then fold A-Z → a-z in place
for (let i = 0; i < lower.length; i++) { const c = lower[i]; if (c >= 65 && c <= 90) lower[i] = c + 32; }
const hits = [];
for (const n of needles) if (lower.includes(n.bytes)) hits.push(n);
return hits;
}
function scan() {
const files = walk(WEB);
const hits = [];
let bytes = 0, links = 0;
for (const f of files) {
const rel = path.relative(ROOT, f.p);
const comps = rel.split(path.sep);
for (const c of BANNED_COMPONENTS) {
if (comps.some((x) => x.toLowerCase() === c)) hits.push({ rel, kind: 'path', token: c, why: `banned path component "${c}"` });
}
if (f.link) { // a symlink is a path, not bytes — its TARGET is the risk
links++;
let t = ''; try { t = fs.readlinkSync(f.p); } catch { /* dangling */ }
for (const h of scanBytes(Buffer.from(t, 'utf8'))) hits.push({ rel, kind: 'symlink', token: h.token, why: `${h.why} (symlink → ${t})` });
continue;
}
let buf; try { buf = fs.readFileSync(f.p); } catch { continue; }
bytes += buf.length;
for (const h of scanBytes(buf)) hits.push({ rel, kind: 'bytes', token: h.token, why: h.why });
}
return { files: files.length, bytes, links, hits };
}
// ── run ──────────────────────────────────────────────────────────────────────────────────────────
console.log(`${C.bold}PROCITY R41 §41.6 — DENY-LIST COMPLIANCE (ruling 3)${C.off} ${C.dim}web/ · paths + bytes${C.off}`);
head('1. THE TREE — every file under web/, every byte, against the seven banned names');
const CONTROL_FILE = path.join(WEB, 'assets', '.r41_denylist_control.tmp.json');
if (fs.existsSync(CONTROL_FILE)) { fs.rmSync(CONTROL_FILE); NOTE(`removed a stale control plant from an earlier crashed run: ${path.relative(ROOT, CONTROL_FILE)}`); }
const t0 = Date.now();
const clean = scan();
const ms = Date.now() - t0;
NOTE(`${clean.files} files · ${(clean.bytes / 1048576).toFixed(1)} MiB · ${clean.links} symlink(s) · ${ms} ms`);
if (clean.hits.length === 0) {
OK(`0 banned references under web/ — ${BANNED.length} names × ${clean.files} files, paths AND bytes`);
} else {
for (const h of clean.hits) FAIL(`${h.rel}${h.kind}: "${h.token}" (${h.why})`);
}
if (VERBOSE) for (const b of BANNED) console.log(` ${C.dim}· ${b.token}${b.why}${C.off}`);
head('2. THE CONTROL — plant a real banned reference, the gate must go RED, then GREEN again');
let planted = false;
try {
fs.mkdirSync(path.dirname(CONTROL_FILE), { recursive: true });
// A plausible plant, not a strawman: this is exactly the shape a wildcard copy would leave behind —
// a manifest row naming the source it was published from.
fs.writeFileSync(CONTROL_FILE, JSON.stringify({
_control: 'R41 deny-list gate control plant — deleted by the gate that wrote it',
assets: [{ file: 'body_07.glb', source: '~/Documents/character_kit_modular/exports/bodies/body_07.glb' }],
}, null, 1));
planted = true;
const red = scan();
const caught = red.hits.filter((h) => h.rel.includes('.r41_denylist_control.tmp'));
if (caught.length >= 2) OK(`RED arm fired: the plant is caught on ${caught.length} names (${caught.map((h) => `"${h.token}"`).join(', ')})`);
else FAIL(`RED arm did NOT fire as expected — ${caught.length} hit(s) on the plant; the scan is vacuous`);
} finally {
if (planted) fs.rmSync(CONTROL_FILE, { force: true });
}
const after = scan();
if (after.hits.length === clean.hits.length) OK(`GREEN arm restored: ${after.hits.length} hit(s), identical to the pre-control scan — the plant left nothing behind`);
else FAIL(`the tree did not return to its pre-control state (${clean.hits.length}${after.hits.length} hits)`);
console.log('');
if (fails === 0) { console.log(`${C.grn}● PASS${C.off} — web/ is deny-list clean, and the deny-list is not vacuous`); process.exit(0); }
console.log(`${C.red}● FAIL${C.off}${fails} problem(s) above`); process.exit(1);