Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
59 commits
Select commit Hold shift + click to select a range
9e90abd
docs(experiments): audit published numbers, retract three claims
Oct 6, 2026
5222967
experiments(crystal-delivery): freeze act list and ordering rule
Oct 6, 2026
6478657
experiments(crystal-delivery): D8 results - both changes behave as do…
Oct 6, 2026
897186e
docs(experiments): Exp 28 - D8 delivery check with maximal re-verific…
Oct 6, 2026
ee42297
docs(experiments): Exp 29 - trap bundle audit, design clean, headline…
Oct 6, 2026
a724ffe
feat(gates): delivery telemetry out of temp with per-evaluation decis…
Oct 6, 2026
070b8f7
docs(experiments): Exp 30 second wave - D9 telemetry, red team fixes,…
Oct 6, 2026
8f70f07
experiments(note-tone): freeze M4 voices and red-team, smoke scope only
Oct 6, 2026
0f88778
feat(gates): MSCODEBASE_ADVISORY_TEXT override for controlled voice e…
Oct 6, 2026
d9725ba
fix(gates): loader-safe plugin entry, length-matched M4 voices amendment
Oct 6, 2026
6196ea8
fix(gates): entry exports only default, helpers moved out of loader path
Oct 6, 2026
a762679
fix(gates): hooks never throw on malformed input, loader-proof test
Oct 6, 2026
7f7d736
fix(gates): helpers out of plugin dir, loader auto-loads every file
Oct 6, 2026
009f51a
experiments(note-tone): probe2 loader fixed, gate cli needs explicit …
Oct 6, 2026
7730211
experiments(note-tone): smoke 5 runs, rig works, no verdicts, measure…
Oct 7, 2026
4342259
fix(stale-detector): exclude frozen/ evidence from version drift
Oct 8, 2026
7d1913b
experiments(note-tone): reachability probe, 4 runs, scaling blocked
Oct 8, 2026
ac5945a
fix(gate): gate commits that use git global options
Oct 8, 2026
cb729d5
experiments(note-tone): v4 choice design, note steers agent 1.000 vs …
Oct 10, 2026
eb22241
experiments(note-tone): v5 phrasing contrast, no detectable effect
Oct 10, 2026
6bf42c6
experiments(tom-e1e2): independent re-derivation, 25/25 hashes, 400/4…
Oct 10, 2026
bfa8f80
experiments(tom-e1e2): deterministic delivery+order check, 30/30
Oct 10, 2026
a248c35
experiments(tom-e1e2): red team, 5 findings, history ledger
Oct 10, 2026
ef9d404
experiments: corrections after self-audit, overlap 416/416
Oct 10, 2026
0658efb
experiments(tom-e1e2): red team-2, five blocks from the TZ
Oct 10, 2026
5bf53ab
experiments(tom-e1e2): redteam-2 corrections after self-audit
Oct 10, 2026
78c5fad
experiments(note-tone): preserve full-30 raw evidence and leftover rigs
Oct 10, 2026
29bed60
experiments(tom-e1e2): red team-3 vectors + independent verification
Oct 10, 2026
7450c56
experiments(replication): harness fixes from pilot
Oct 10, 2026
d90015b
fix(rigs): kill process tree on timeout + incident diary
Oct 10, 2026
48a1b06
experiments(replication): sidefx merge, t3a exclusion, kill-tree
Oct 10, 2026
6cd1432
experiments(replication): R1 null, 44 runs, HELP 0.600 vs SHAM 0.529
Oct 10, 2026
1110d5c
experiments(behavior): #2-#5 results, classifier fix, artifact rule
Oct 10, 2026
5d8ad6f
experiments(replication): prohibitive arm, no effect, circuit closed
Oct 10, 2026
25864c5
experiments(note-tone): backfill artifact verdicts into arm/phrase rows
Oct 10, 2026
439e709
experiments(replication): all 68 run artifacts
Oct 10, 2026
578d057
docs(tom-e1e2): complete documentation, index, Tom comment draft
Oct 10, 2026
b95a20a
docs(tom-e1e2): scope section in Tom comment draft
Oct 10, 2026
e7dfab5
docs(tom-e1e2): name t3a exclusion and vacuous blind in draft
Oct 10, 2026
364ff0f
docs(tom-e1e2): scale variance qualifier in draft
Oct 10, 2026
0853377
docs(tom-e1e2): nobody-claims chapter, three novel mechanisms
Oct 10, 2026
8d33ad9
experiments(asym): preference vs trap framing, gate fired honestly
Oct 10, 2026
2a90ba1
docs(tom-e1e2): draft additions from audit round
Oct 10, 2026
ac32a6c
docs(tom-e1e2): staleness audit of new docs commits
Oct 10, 2026
c953f02
docs(tom-e1e2): staleness mechanism verified, draft completed
Oct 10, 2026
6143305
docs(tom-e1e2): inventory chapter, all experiments accounted
Oct 10, 2026
c963554
docs(tom-e1e2): boundaries paragraph in draft caveats
Oct 10, 2026
1a71131
docs(tom-e1e2): restructure draft per review, verify literature
Oct 10, 2026
681acce
docs(tom-e1e2): comment posted to thread
Oct 10, 2026
4eb0ba5
docs(tom-e1e2): methodology priority record
Oct 10, 2026
07cef8d
docs(tom-e1e2): week threads sweep + paired re-analysis
Oct 10, 2026
5dc030c
docs(tom-e1e2): Tom's three replies (11.10) dissected
Oct 11, 2026
bb21b6a
docs(tom-e1e2): scale forensics - Defender A/B, retract our match number
Oct 11, 2026
16e4f69
fix(tom-e1e2): re-measure all timing experiments with Defender OFF
Oct 11, 2026
54d31e3
test(tom-e1e2): re-derive the lost overlap check from scratch (416/416)
Oct 11, 2026
f40f064
docs(tom-e1e2): performance follow-up draft (Defender correction)
Oct 11, 2026
96e9bf2
docs(tom-e1e2): expand performance follow-up draft with full tables a…
Oct 11, 2026
70c9f60
test(tom-e1e2): independently confirm Tom's key-matching fix (15/15)
Oct 11, 2026
f45c6d5
Merge origin/main into fix/audit-number-corrections
Oct 22, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
176 changes: 104 additions & 72 deletions .opencode/plugin/mscodebase_gate.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,124 +9,156 @@
// (объективный) и явный MSCODEBASE_ISOLATION_GATE=block.
//
// Переключатели (env): MSCODEBASE_PY, MSCODEBASE_STALE_GATE=block|off,
// MSCODEBASE_ISOLATION_GATE=advisory|block|off, MSCODEBASE_GATE_LOG.
// MSCODEBASE_ISOLATION_GATE=advisory|block|off,
// MSCODEBASE_GATE_LOG (явный путь к JSONL),
// MSCODEBASE_ADVISORY_TEXT (M4 override: фиксированный текст
// advisory-заметки вместо вывода гейта; additive, default-off),
// MSCODEBASE_DATA_DIR / BASE_INDEX_DIR (корень данных для пути по умолчанию).
// Хук никогда не роняет хост: любые сбои → лог, без throw (кроме намеренного блока).
//
// Телеметрия: на КАЖДУЮ оценку гейта — РОВНО ОДНА JSON-строка в gate_telemetry.jsonl
// (data dir проекта, см. src/core/artifact_paths.get_gate_log_path — НЕ %TEMP%):
// {t, gate: stale|isolation, mode, act, drift|count, delivered, reason, restraint?, latency_ms, refusal_len?}
// reason ∈ fired_block | fired_advisory | suppressed_restraint | suppressed_zero_findings
// | skipped_off | error_unavailable | error_exception.
// Правило: та же ветка, что РЕШАЕТ (throw/stash/suppress), и ПИШЕТ строку —
// второго кодового пути нет (комментарий у каждой эмиссии называет зеркалимое решение).
// В лог НЕ пишутся тела findings/выводы CLI (только refusal_len) — секретоподобный
// текст в принципе не попадает в файл (вывод CLI и так проходит redact.py на стороне src/cli.py).
//
// ВАЖНО: вызов python — через node:child_process, НЕ через `$` из контекста плагина: в opencode
// 1.18.23 контекст фабрики не содержит `$` (API дрейфанул) → `TypeError: $ is not a function`.
//
// LOADER CONTRACT (opencode ≥1.16): legacy plugin loader итерирует
// Object.values(module) и ВЫЗЫВАЕТ каждый function-экспорт как фабрику плагина
// (см. opencode issues #41234/#31575/#42451). Поэтому этот входной модуль обязан
// экспортировать ТОЛЬКО default-фабрику — никаких других runtime-экспортов
// (включая const). Все хелперы живут в ../../tools/gate_helpers.ts; тесты хелперов
// импортируют gate_helpers.ts напрямую, а loader-safety тест импортирует ЭТОТ
// модуль и проверяет Object.keys(entry) === ["default"].

import type { Plugin } from "@opencode-ai/plugin"
import { appendFileSync, mkdirSync } from "node:fs"
import { execFile } from "node:child_process"
import { dirname, join } from "node:path"
import { tmpdir } from "node:os"
import { dirname } from "node:path"
import {
resolveGateLog,
makeRow,
parseStale,
parseIsolation,
decideStale,
decideIsolation,
buildAdvisoryNote,
isolationRowExtra,
cli,
actOf,
isGitCommit,
} from "../../tools/gate_helpers.ts"
import type { GateRow } from "../../tools/gate_helpers.ts"

// Module-local config + stash (identical lines to the pre-split module):
// the entry must export ONLY default, so values the factory closes over live here,
// not as helper exports (opencode ≥1.16 legacy loader would invoke function
// exports and choke on non-function ones — issues #41234/#31575).
// Project-local venv by default; MSCODEBASE_PY overrides. No machine-specific path.
const LOG = process.env.MSCODEBASE_GATE_LOG ?? join(tmpdir(), "opencode", "mscodebase_gate.log")
const STALE_MODE = process.env.MSCODEBASE_STALE_GATE ?? "block"
const ISO_MODE = process.env.MSCODEBASE_ISOLATION_GATE ?? "advisory"

const STALE_ARGS = "{}"
const ISO_ARGS = '{"action":"isolation","kwargs":{"restraint":true}}'

function log(rec: Record<string, unknown>) {
try {
mkdirSync(dirname(LOG), { recursive: true })
appendFileSync(LOG, JSON.stringify({ t: Date.now(), ...rec }) + "\n")
} catch {
// never break the host
}
}

function cli(directory: string, tool: string, argsJson: string): Promise<string> {
const py = process.env.MSCODEBASE_PY ?? join(directory, "venv", "Scripts", "python.exe")
return new Promise((resolve) => {
try {
execFile(
py,
["-m", "src.cli", tool, argsJson],
{ cwd: directory, timeout: 90000, windowsHide: true, maxBuffer: 4 * 1024 * 1024 },
(_err, stdout) => resolve(stdout ?? ""),
)
} catch (e) {
log({ kind: "cli_spawn_error", err: String(e).slice(0, 200) })
resolve("")
}
})
}

function staleDrift(out: string): number {
const m = /Total drift instances:\s*(\d+)/i.exec(out)
return m ? parseInt(m[1], 10) : 0
}

function isolationCount(out: string): number {
const r = /removed_last_caller:\s*(\d+)/i.exec(out)
const n = /new_orphan:\s*(\d+)/i.exec(out)
return (r ? parseInt(r[1], 10) : 0) + (n ? parseInt(n[1], 10) : 0)
}

function isolationSuppressed(out: string): boolean {
return /deliver:\s*✗/i.test(out) || /deliver:\s*false/i.test(out)
}

const stash = new Map<string, string>()

export default (async ({ directory }) => {
log({ kind: "init", directory, stale: STALE_MODE, isolation: ISO_MODE })
const LOG = resolveGateLog(directory ?? "")
const emit = (rec: Record<string, unknown>) => {
try {
mkdirSync(dirname(LOG), { recursive: true })
// единый конструктор строки (makeRow): вся телеметрия идёт через эту точку
appendFileSync(LOG, JSON.stringify(makeRow(rec as GateRow)) + "\n")
} catch {
// never break the host
}
}
emit({ kind: "init", directory, stale: STALE_MODE, isolation: ISO_MODE, log: LOG })
return {
"tool.execute.before": async (input: any, output: any) => {
// Контракт хоста: хук никогда не бросает. opencode ≥1.18 вызывает хуки с
// формами входа, которые мы не контролируем (undefined input/output) —
// непроверенный доступ ронял весь сервер (M4 smoke, попытки 2–3).
if (!input || typeof input !== "object" || !output || typeof output !== "object") return
if (input.tool !== "bash") return
const cmd = String(output.args?.command ?? "")
if (!/\bgit\s+commit\b/.test(cmd)) return
// Tokenizer, not /\bgit\s+commit\b/: global options such as `git -C <dir> commit`
// are the same command and must be gated too (M4 full-30 run 11 slipped through).
if (!isGitCommit(cmd)) return
const act = actOf(output)

// ── Gate 1: stale_detector (block) ──
if (STALE_MODE !== "off") {
// ── Gate 1: stale_detector ──
if (STALE_MODE === "off") {
// mirrors the skip branch: gate off → no CLI call, nothing delivered
emit({ gate: "stale", mode: STALE_MODE, act, drift: 0, delivered: false, reason: "skipped_off", latency_ms: 0 })
} else {
const t0 = Date.now()
try {
const out = await cli(directory, "stale_detector", STALE_ARGS)
const drift = staleDrift(out)
log({ kind: "stale", drift, out: out.slice(0, 400) })
if (STALE_MODE === "block" && drift > 0) {
log({ kind: "stale_block", drift })
throw new Error(
`CRYSTAL-GATE: ${drift} doc-drift instance(s). Docs assert a version the code no longer is.\n${out.slice(0, 500)}`,
const parsed = parseStale(out)
const latency_ms = Date.now() - t0
if (STALE_MODE === "block" && parsed.drift > 0) {
const err = new Error(
`CRYSTAL-GATE: ${parsed.drift} doc-drift instance(s). Docs assert a version the code no longer is.\n${out.slice(0, 500)}`,
)
// mirrors the throw below: refusal length, never the text
emit({ gate: "stale", mode: STALE_MODE, act, drift: parsed.drift, delivered: true, reason: "fired_block", refusal_len: String(err).length, latency_ms })
throw err
}
// mirrors the fall-through branch: no block → nothing delivered, reason from the same parse
const d = decideStale(STALE_MODE, parsed, false)
emit({ gate: "stale", mode: STALE_MODE, act, drift: parsed.drift, delivered: d.delivered, reason: d.reason, latency_ms })
} catch (e) {
if (String(e).includes("CRYSTAL-GATE")) throw e
log({ kind: "stale_error", err: String(e).slice(0, 200) })
// mirrors the swallow-and-continue branch: host never breaks on gate errors
emit({ gate: "stale", mode: STALE_MODE, act, drift: 0, delivered: false, reason: "error_exception", latency_ms: Date.now() - t0 })
}
}

// ── Gate 2: graph isolation (advisory/block) ──
if (ISO_MODE !== "off") {
// ── Gate 2: graph isolation ──
if (ISO_MODE === "off") {
// mirrors the skip branch: gate off → no CLI call, nothing delivered
emit({ gate: "isolation", mode: ISO_MODE, act, count: 0, delivered: false, reason: "skipped_off", latency_ms: 0 })
} else {
const t0 = Date.now()
try {
const out = await cli(directory, "graph_query", ISO_ARGS)
const count = isolationCount(out)
log({ kind: "isolation", count, out: out.slice(0, 400) })
if (count > 0) {
if (ISO_MODE === "block") {
log({ kind: "isolation_block", count })
throw new Error(`GRAPH-GATE (block): this commit isolates ${count} graph node(s).\n${out.slice(0, 500)}`)
}
if (!isolationSuppressed(out)) stash.set(input.callID, out)
const parsed = parseIsolation(out)
const latency_ms = Date.now() - t0
if (parsed.count > 0 && ISO_MODE === "block") {
const err = new Error(`GRAPH-GATE (block): this commit isolates ${parsed.count} graph node(s).\n${out.slice(0, 500)}`)
// mirrors the throw below: refusal length, never the text
emit({ gate: "isolation", mode: ISO_MODE, act, count: parsed.count, delivered: true, reason: "fired_block", refusal_len: String(err).length, ...(parsed.restraint ? { restraint: parsed.restraint } : {}), latency_ms })
throw err
}
// mirrors the stash/suppress/fall-through branch below: the SAME parsed values feed the row
if (parsed.count > 0 && ISO_MODE === "advisory" && !parsed.suppressed) stash.set(input.callID, out)
const d = decideIsolation(ISO_MODE, parsed, false)
emit({ gate: "isolation", mode: ISO_MODE, act, count: parsed.count, delivered: d.delivered, reason: d.reason, ...(parsed.restraint ? { restraint: parsed.restraint } : {}), ...isolationRowExtra(out, parsed, ISO_MODE), latency_ms })
} catch (e) {
if (String(e).includes("GRAPH-GATE")) throw e
log({ kind: "isolation_error", err: String(e).slice(0, 200) })
// mirrors the swallow-and-continue branch: host never breaks on gate errors
emit({ gate: "isolation", mode: ISO_MODE, act, count: 0, delivered: false, reason: "error_exception", latency_ms: Date.now() - t0 })
}
}
},

"tool.execute.after": async (input: any, output: any) => {
if (ISO_MODE !== "advisory") return
if (!input || typeof input !== "object" || !output || typeof output !== "object") return
const cached = stash.get(input.callID)
if (!cached) return
stash.delete(input.callID)
log({ kind: "isolation_advisory", len: cached.length })
output.output =
(output.output ?? "") +
`\n\n✦ GRAPH-GATE (advisory): this commit isolates graph node(s).\n${cached.slice(0, 500)}`
// без лог-строки: fired_advisory уже записана в before (одна оценка — одна строка)
const note = buildAdvisoryNote(cached, ISO_MODE)
if (!note) return // тихий гейт молчит и с включённым override: доставка не выдумывается
output.output = (output.output ?? "") + note.text
},
}
}) satisfies Plugin
2 changes: 1 addition & 1 deletion experiments/4A_unit_of_return/README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# 4A — Unit of return + контуры против confound'ов агентной аппаратуры

**Статус:** 🟡 pre-registered (дизайн заморожен, прогона нет).
**Статус:** ✅ прогоны выполнены (дизайн был заморожен до первого прогона). **Артефакты:** `results/` — 795 файлов, 6 `RESULTS*.md`: `recovered_e7/` (регрессия, variant НЕ пинован), `pinned_variant/` (regression, `--variant` пинован), `f4b/` (held-out, 22 прогона), `f5/`, `f5judged/`, `f5relang/`. **Аудит публикуемых чисел и отзыв трёх утверждений:** `../publication_audit_2026-10-06/RESULTS.md` (`EXPERIMENTS_LOG.md` Exp 27). ⚠️ `results/recovered_e7/` — это регрессия F4 от 2026-09-26, **не** Exp 19 от 2026-09-22: каталог назван по списку, а не по эксперименту.
**Повод:** протокол Tom Jones `3fj0o` («four arms, everything else frozen», замок сверху — closed book) + вопрос владельца: **будет ли запуск чистых opencode-агентов/субагентов как-то влиять на эксперимент.**
**Короткий ответ:** ДА. В этом репо уже зафиксированы 6 каналов влияния (E7, E11, E17, E25, инцидент портов). Ниже — они и обязательные контуры. Дизайн без этих контуров повторяет ошибки, которые мы уже совершали.

Expand Down
Loading
Loading