phase 1.6 BIPA: scrum-driven fixes

Per 2026-05-03 phase_1_6_bipa_gates scrum (13 findings, 0 convergent).
1 BLOCK verified false positive, 4 real fixes shipped:

False positive (verified):
- opus BLOCK on attest:55 — claimed `set -uo pipefail` without `-e`
  makes the post-python3 `if [ $? -ne 0 ]` check unreachable. Verified
  WRONG: `X=$(false); echo $?` prints 1. Bash propagates command-
  substitution exit through $? on the assignment line. The check IS
  the python3 exit gate. Inline comment added to the script noting
  the false positive so future scrums don't re-flag.

Real fixes:
1. opus WARN attestation:18 — schema fingerprint hashed names ONLY,
   missing column-type changes. A column repurposed to hold base64
   photo bytes under its existing name would pass undetected. Now
   hashes "name<TAB>type<TAB>nullable=bool" per row. Re-run produced
   evidence SHA-256 1fdcc9f1... (vs old 230fffeb..., reflecting the
   broader fingerprint scope).

2. opus WARN gate_4_test:60 — definition regex didn't catch
   object-literal property forms (`const t = { FEMALE_NAMES: [...] }`)
   or TypeScript class fields (`class L { public NAMES_X: string[] = [] }`).
   Added two new patterns + a regression test
   (Gate 4: object-literal and class-field bypasses are caught) that
   exercises 5 bypass forms. 4/4 tests green; 1 minor regex tweak
   needed mid-fix to handle single-line class bodies.

3. kimi WARN python3-reliance — script assumed pyarrow installed and
   would emit a stack trace into the attestation if not. Added
   `python3 -c "import pyarrow"` gate at top with clean install
   instructions on failure.

4. opus INFO PHASE_1_6:200 — item 7 (training) silently dropped from
   blocking set with bare "deferred" rationale. Now explicitly states
   the deferral is conditional on small operator population (J + 1-2
   named ops); item 7 re-promotes to blocking if population grows.
   ⚖ COUNSEL marker added.

Skipped (acceptable as ⚖ COUNSEL placeholders by design):
- kimi WARN consent template:30-day-SLA (counsel decides number)
- kimi WARN consent template:email-placeholder (counsel supplies)
- kimi WARN parquet absence (env override exists; redeployment-aware)
- kimi INFO runbook manual-erasure (marked TODO when /erase ships)
- qwen INFO doc path/status nits (already addressed by file moves)

Tests: 4/4 Gate 4 absence test (incl. new bypass-coverage), 3/3
attestation evidence checks pass on live data.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
root
2026-05-03 04:43:17 -05:00
co-authored by Claude Opus 4.7
parent 4708717f6b
commit c7aa607ae4
4 changed files with 92 additions and 23 deletions
+41 -1
View File
@@ -65,6 +65,12 @@ function* walkSource(dir: string): Generator<string> {
// definitionPatternsFor: returns regexes that match common DEFINITION
// forms in JS/TS/HTML embedded scripts. A bare reference inside a
// comment is intentionally NOT matched.
//
// 2026-05-03 opus scrum WARN (gate_4_test:60) added object-literal +
// class-field patterns: a developer wrapping the lookup tables in
// `const tables = { FEMALE_NAMES: [...] }` or a TypeScript class
// field `FEMALE_NAMES: string[] = [...]` would have bypassed the
// original 4 patterns silently.
function definitionPatternsFor(symbol: string): RegExp[] {
return [
// var / const / let SYMBOL =
@@ -73,8 +79,16 @@ function definitionPatternsFor(symbol: string): RegExp[] {
new RegExp(`\\bfunction\\s+${symbol}\\s*\\(`),
// SYMBOL = function( OR SYMBOL = (...) =>
new RegExp(`\\b${symbol}\\s*=\\s*(?:function\\s*\\(|\\([^)]*\\)\\s*=>|async\\s*(?:\\(|function))`),
// class member: SYMBOL(...) { (a method declaration)
// class method: SYMBOL(...) {
new RegExp(`^\\s*${symbol}\\s*\\([^)]*\\)\\s*\\{`, "m"),
// object-literal property assigned to an array OR object value:
// { SYMBOL: [...] } or SYMBOL: {...} or SYMBOL: new Set(...)
new RegExp(`(?:^|[,{])\\s*${symbol}\\s*:\\s*(?:\\[|\\{|new\\s+(?:Set|Map|Array)\\b)`, "m"),
// TypeScript / class field with type annotation. The boundary
// before SYMBOL is start-of-line OR `{`/`;`/`}` so single-line
// class bodies (`class L { public NAMES_X: string[] = []; }`)
// are caught alongside multi-line ones.
new RegExp(`(?:^|[{};])\\s*(?:public|private|protected|readonly|static\\s+)*\\s*${symbol}\\s*:\\s*[^=;{]+=\\s*[\\[\\{]`, "m"),
];
}
@@ -128,3 +142,29 @@ test("Gate 4: regex catches a synthetic positive (defense in depth)", () => {
expect(offenders.some((o) => o.includes("NAMES_HISPANIC"))).toBe(true);
expect(offenders.some((o) => o.includes("guessEthnicityFromFirstName"))).toBe(true);
});
// 2026-05-03 opus scrum WARN regression: the bypass forms a developer
// might use to wrap the lookup tables without tripping the original
// four patterns. All of these MUST trip a definition regex.
test("Gate 4: object-literal and class-field bypasses are caught", () => {
const bypassForms = [
// Inline object-literal property → array
`const tables = { FEMALE_NAMES: ["Maria"] };`,
// Object property → Set/Map constructor
`const lookups = { NAMES_BLACK: new Set(["X"]) };`,
// Multi-line object literal
`const all = {\n NAMES_HISPANIC: [...],\n};`,
// TypeScript class field with type annotation + initializer
`class Lookup {\n SURNAMES_BLACK: string[] = ["X"];\n}`,
// TypeScript public field
`class L { public NAMES_EAST_ASIAN: string[] = []; }`,
];
for (const synthetic of bypassForms) {
const offenders = findOffenders("bypass_synthetic", synthetic);
if (offenders.length === 0) {
throw new Error(
`Gate 4 bypass not caught — pattern would slip past:\n ${synthetic}`,
);
}
}
});