9.8 kB 122 lines
e68889be
⋯
persona/audits/opus_audit_phase3_prep_v3.md
download raw history
at main Markdown

opus audit v3: phase 3 prep fix verification #

read-only · auditor: opus-verify-phase3-v3 · scope: the 9 items in audits/opus_audit_phase3_prep_v2.md §fix list. the DB was opened mode=ro, probes ran in-kernel with sys.dont_write_bytecode=True, and no __pycache__/*.pyc exists anywhere in the repo afterwards. the only file written is this report.

verdict: NOT PASS. one new major false positive in the M2' hypothesis path. items 1–4 and 6–9 pass.

the new causal-clause regex PRE_TOOL_HYPOTHESIS_CLAUSE_RE (prepare_phase3_sft_data.py L83) has no word boundary before since|because|as|so that, and it treats bare as as causal. so any pre-tool sentence that contains the letters as␠ (was, has, alias, as root, as well) gets cut mid-word, and only the garbage tail is kept as the "hypothesis". this hits exactly the falsifier ("any false positive on M2' probes").

self-tests (python3 -B) #

script exit time
mine_llm_isms_from_transcripts.py --self-test 0, PASS 0.07 s
scripts/prepare_phase3_sft_data.py --self-test 0, PASS 0.08 s
scripts/phase3_lora_and_transplant.py --self-test 0, PASS 0.71 s

1 & 2. emoji strip / layout (B2', B3'): PASS #

  • the global (?<=\S)[ \t]{2,}(?=\S) collapse and the [ \t]+([.!?,;:]) punctuation regex are both gone. _strip_emoji_preserving_layout (L181) only touches the [ \t]* captured right next to each emoji match, and fences are split out and passed through untouched (L361–368).
  • live data (fresh run, 658 text turns): code_fences_altered = 0 / 140 fenced turns. that's from both the script's own check and my own findall comparison. there are 0 unclosed-fence turns and 0 ~~~ fences.
  • stricter check: on turns with no emoji and nothing stripped, the whitespace skeleton is identical in 100% of cases. the only indented lines that changed (2 turns) are nested bullet prose being lowercased by design, not code.
  • the committed results/phase3_speech_act_surgery_report.json says code_fences_altered: 0 / 140 (657 turns, since the DB has grown by one since). the seed JSONL equals a fresh build_synthetic_phase3_seed_examples().
  • probes, all with an emoji stripped in the same reply:
    • an aligned fence (x = 1, print(a, b)), a padded table (| name | kld |) and make -j8 all all come through byte-identical.
    • ( ; ω ; ) and `a ; b` survive, while 🚀 ✅ 🇯🇵 ⭐ are stripped.
    • "fixed it 🚀\n" → "fixed it\n", so there's no trailing space anymore.
  • nits (not blocking):
    • (a) an emoji inside inline code is stripped: `echo 🚀` → `echo `. it happens in 1 of 658 live turns.
    • (b) a table cell padded around an emoji loses its padding: | name 🚀 | → | name |. that's only at the emoji position, which the v2 spec allowed.
    • (c) the final .strip() (L385) eats the indentation of a reply whose first line is 4-space code.
    • (d) ~~~ fences aren't protected from emoji stripping or lowercasing. there are 0 in the live data.
    • (e) hello🚀world → helloworld.

3. ABSTENTION_TEMPLATES: PASS #

  • none of the 6 templates contains anything from banned_ngrams (32), phase3_speech_act_ngrams (25), phase4_lexical_ftpo_ngrams (7) or the 576 surface_variants. I checked both word-boundary and raw substring matches.
  • there's no ? and no want me anywhere, and every template passes through surgery unchanged.
  • the self-test asserts against banned_ngrams, which is a superset of phase3 ∪ phase4 (I checked), so the docstring's claim holds.

4. closing guard (M1'): PASS (false negatives noted) #

  • can(?!'t|\u2019t|not) plus OFFER_ACTION_VERB_PAT are in place (L108–125). CHOICE_FORK_QUESTION_RE (L142) with (?!or\b) handles the leading Or.

  • TECHNICAL_TOKEN_IN_SENT_RE was deleted, which is acceptable ("wire up or delete").

  • kept (0 false positives in 14 probes):

    • I can confirm the seed…, I can't help much…, I cannot…, I can not…
    • Should I write the parser in Rust or Go?, Would you like to keep the old schema or migrate everything now?
    • Would you like Postgres or SQLite…?, Do you want the cache in Redis or in-process?, Want the tables sharded by user or by tenant?
    • If you want lower latency, use…, If you want the details, the root cause…, If you want to reproduce it, run…
    • If you'd rather avoid swap, I can see why…, Should I be worried about…?
  • stripped as intended:

    • Do you want me to apply…?, Or do you want me to revert it instead? (sentence-initial Or), If you want, I can apply the fix now.
    • If you want, I'll push it., Want me to run the tests?, Let me know if…, Shall I commit this?, Should I go ahead and commit this?, Happy to help with anything else!
  • live data: the fork exemption keeps 3 final sentences. 2 are real forks (branch vs main, extend-login vs trace-wire-format). 1 is borderline (…or were you just checking that permissions work?).

  • false negatives (minor, not the falsifier): the fork regex matches any or, so binary permission asks survive:

    • Do you want me to push this or not?
    • Should I commit this, or not?
    • Would you like me to fix this or anything else?
    • Would you like me to apply the patch, or would you rather review it first? (the classic menu-offer)

    suggestion: don't count or (not|anything else|something else|would you rather|should i|do you want) as a fork. separately, If you want, I can apply it to main.py. survives because [^.!?\n] stops at the . in main.py.

5. pre-tool hypothesis / questions (M2'): FAIL (new false positive) #

  • the v2 probes now pass:
    • …because the OOM killer usually targets the biggest cgroup. → the OOM killer usually targets the biggest cgroup.
    • I'll test this hypothesis: the fd leaks… → the fd leaks…
    • …first, then we can talk about the race. → we can talk about the race.
    • Let me see if I understand: you want the cache cleared on boot? is kept whole (the ? guard is at L256 and L311).
    • Let me check the config? It might be stale. is kept.
  • new false positives. each is a single pre-tool sentence with has_following_tool_call=True:
input output
Let me check whether this was the cause. the cause.
Let me check what the service has to say in the journal. to say in the journal.
Let me look at the alias table. table.
Let me verify the alias works. works.
Let me run the tests as root. root.
Let me check the logs as well. well.
Let me check the logs so that we know which worker died. we know which worker died. (purpose clause, not a hypothesis)
  • cause: in L83 (?:,?\s*(?:since|because|as|so\s+that)\s+|:\s+|,\s*then\s+), the \s* can match zero characters, so w·as␠, h·as␠ and ali·as␠ match. as is also ambiguous even as a whole word.
  • these sentences should either be deleted whole (pure narration) or kept verbatim. a mid-word fragment becomes a broken SFT target.
  • live impact today is ~0: only 1 pre-tool strip on 4 text+tool turns, and that one is correct ("no, i haven't. let me look." → "no, i haven't."). but this path will run over all teacher-generated phase 3 data.
  • fix:
    • require (?:,\s*|\s+)\b(?:since|because)\b\s+ (or ,\s*as\s+ only after a comma), and drop so that.
    • add the 7 probes above to self-test §5, asserting the output is either "" or the unchanged input.

6. R-Tuning aliases + naming: PASS #

  • a flat list is always any-of (L428–431). ["42","forty-two"] → known_keep_teacher at 1.0 (self-tested), and the teacher answer "forty-two" alone also passes.
  • nested groups require every group: [["42","forty-two"],["seconds","sec"]] gives 0.75 when one sample says 42 s, which is correct.
  • match_mode="all" is explicit opt-in.
  • _word_boundary_おそらく is gone everywhere. only _word_boundary_match remains.

7. transplant: PASS (nits) #

  • the (ggml_type:int, bytes) tuple is mandatory (L273), a bare-bytes call is self-tested to raise ValueError (L505), and the type is checked against the header (L278).
  • all 4 regexes are used via .fullmatch (L113–128).
  • the directory is fsync'd after os.replace (L312–316).
  • PLAN §2.2 L60 now reads 12 attn_output + 36 ssm_out + 48 ffn_down_shexp = 96 writers, plus 96 routers = 192, with the SC117 144 explained.
  • nits:
    • (a) the L93 comment still says "144 writers + 96 router gates = 240 tensors". it's stale, make it 96 + 96 = 192.
    • (b) isinstance(item[0], int) accepts bool, so (True, buf) passes as type 1 = F16. harmless in practice, but type(item[0]) is int is stricter.

8. Q2_0 compiler fallback + PLAN wording: PASS #

  • shutil.which("clang") or which("gcc") or which("cc") (L436), with a clear RuntimeError if none is found.
  • PLAN #28 L271 now says "compiled C reference transcribed from ggml-quants.c".
  • nit: _Float16 needs GCC ≥ 12 on x86-64. older Colab images would fail to compile instead of skipping.

9. miner docstring: PASS #

L18–21 now describe the 85/15 mixture (0.85 * p_indep + 0.15 * p_unif, no self-pooled term).


remaining fix list #

  1. (major) PRE_TOOL_HYPOTHESIS_CLAUSE_RE L83: add \b and require a real separator before since|because, drop bare as (or require , as) and so that. add the 7 probes from §5 to the self-test.
  2. (minor) CHOICE_FORK_QUESTION_RE: don't exempt or not / or anything else / or would you rather permission asks.
  3. (nit) L93 comment 144/240 → 96/96/192; type(...) is int; skip emoji stripping inside inline code; protect ~~~ fences.