This repository was archived by the owner on Oct 6, 2026. It is now read-only.
Repository navigation
fix: the harness works outside the author's machine (spec 024, closed) - #3
Merged
Merged
Conversation
Первый аудит харнеса, сделанный НЕ на Windows. На Linux и macOS — единственной
серверной платформе, ради которой харнес заявлен ядром для автоматических
агентов, — не работало ни одно из трёх звеньев обещания «не пускать
непроверенное в main», и ни одно не сообщало об отказе.
Замер до/после (Linux, Node 22.22.2):
elt-oracle-runner --full 110/112 exit 1 -> 115/115 exit 0
node tools/elt.js oracle exit 1 (0s) -> полный прогон, настоящий код
elt commit под своим хуком непроходим -> проходит
прогон сьюта в дереве +5 фиктивных -> герметичен
ctx7-пруфов
T001 (D29) Платформа выбирается, а не предполагается. В поставочном конфиге
стоял shell:"powershell", sh() спавнил его без проверки, а ENOENT давал
status:null -> обычный красный оракул. Диспетчер шелла был скопирован пять раз;
теперь он один (tools/shell-run.js), дефолт из process.platform, отсутствующий
интерпретатор называет себя, поле shell входит в валидацию конфига.
T002 (D33) Зависший судья фейлится закрыто. timer.unref() в withJudgeTimeout
отменял весь смысл гарда: таймер не держал event loop, Node выходил ДО его
срабатывания, try/finally не выполнялся — ни run-log, ни строки очереди, — а
процесс возвращал НОЛЬ. Она же виснет в собственной регрессии гарда и уносила
13 тестов. Грейдер замера получил preflight: отсутствие pytest больше не
неотличимо от неверного решения.
T003 (D30) Пруф о дереве считается от содержимого, а не от формы вывода git.
treeHash хешировал колонку статуса, которую меняет индексация, а elt commit
делает git add -A ровно между записью пруфа и сверкой в хуке — совпадения не
было никогда. Плюс .trim() всего потока съедал ведущий пробел первой строки, и
slice(3) отъедал символ от пути.
T004 (D31) Кэш оракула перестал быть доказательством. computeEntry звался без
scanDirs при трёх корнях: правка bin/l0.js не сдвигала ключ bin/l0.test.js.
Сам кэш лежал в гитигнорённом .harness/, невидимом для treeHash. Теперь корни
общие, RUNNER_VERSION включает правила выборки, кэш в .git/elt/, пруф несёт
ran/cached, и ran:0 при непустой выборке отвергается гейтом.
T005 (D32) Замок гейта висит по-настоящему: .githooks/pre-commit был
закоммичен режимом 100644, git печатал hint и коммитил.
T006 (D34) Сьют перестал выписывать разрешения сам себе: живой ctx7-реестр был
75 строк 100% фабрикации, и подстрочный матч покрывал ими ai, mcp, agents,
playwright, cli, vercel.
T007 (D35) package.json появился: engines, npm test, version пятым источником
version-check (корень D28).
Охват 112 -> 115: три регрессии на дыры, у которых её не было. elt-tree-hash
краснеет на трёх утверждениях из пяти на коде до спеки.
Открыто и записано: D36-D41 (docs/DEFECTS.md), доказательства —
specs/024-harness-scale-readiness/audit.md.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01ADgXkZxYt9KBYyjnPGCQeF
Харнес заявлен ядром в том числе для агентов на автоматических серверах.
У такого агента ровно два канала — код выхода и stdout, — и оба молчали
о причине.
D37, три независимых потери причины:
1. judge-invoke.js:68 не пробрасывал r.reason: при runOk:false поле reasons
уезжало ПУСТЫМ, и наружу шло verdict:dead, reasons:["judge dead"].
2. elt.js получал judgeLog и писал его только в run-log. На экране оставался
exit 4 без причины; настоящая строка лежала в логе провайдера, который
никто не открывал. Живой случай на сервере под root:
"--dangerously-skip-permissions cannot be used with root/sudo privileges".
3. Код 4 стоял на 44 отказах из 55: "задача не найдена", "спека не подписана",
"пруф протух" и "судья мёртв" приезжали одним числом.
Переномеровать 44 места нельзя — существующие драйверы сверяются с === 4.
Поэтому различителем стал slug, а не код:
die(msg, code, reason) -> {"ok":false,"code":4,"reason":"spec-unapproved",
"message":"..."}
Конверт печатается под --json или ELT_JSON=1 (канал для хуков и
harness.json.oracle, куда argv не дописать). Текстовый вывод не изменился
ни на байт — конверт это добавка, а не замена, и регрессия это утверждает.
--json появился у commit, gate и judge run — ровно тех трёх команд, вокруг
которых крутится автоматика и у которых его не было.
Судья-мертвец теперь печатает машинную причину, путь к логу провайдера и его
хвост. Таблица кодов и причин — новый раздел "Running it headless" в
docs/USAGE.md, там же список того, что вообще работает без agent CLI.
Охват 115 -> 116: tools/elt-machine-readable.test.js.
Полный оракул на Linux: 116/116, exit 0.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01ADgXkZxYt9KBYyjnPGCQeF
This comment was marked as resolved.
This comment was marked as resolved.
… T010) Три места, каждое из которых выглядело работающей защитой (D38). 1. Очередь ревью фильтровалась по полю `resolved`, которого не пишет НИКТО: строки закрываются меткой `closedAt` (elt review close). `unresolvedReview` в `elt run` не убывал никогда; в graph-state.js разобранная строка навсегда оставалась неоднозначностью и могла навсегда заблокировать `elt cutover`. 2. Гард `--push` читал у сертификата `commitHash` — поле только РЕЛИЗНЫХ сертификатов (certification.js:224); батчевые несут `commit` (:162), поэтому условие было всегда ложным и привязка к коммиту не проверялась ни разу. Наивное переименование переключило бы гард в "всегда блокировать": в сертификат едет КОРОТКИЙ sha (rev-parse --short), а сверка брала полный. Сравнение стало префиксным по длине записанного — короткий против полного, полный против полного, чужой блокируется. 3. review-runtime требовал РОВНО пять линз (lenses.length !== LENS_NAMES.length), поэтому ШЕСТАЯ давала тот же dead, что и недостача. Форк, добавивший собственную линзу, получал мёртвого судью на КАЖДОМ слайсе — единственная объявленная точка расширения ломала харнес целиком. Набор закрыт снизу: все объявленные обязаны присутствовать, лишние работают наравне. Недостача по-прежнему смертельна и теперь называет имена, а не только количество. Охват 116 -> 117: tools/elt-dead-checks.test.js плюс новый кейс в review-runtime.test.js. Каждый тест утверждает и что защита срабатывает, и что она не срабатывает там, где не должна. Полный оракул на Linux: 117/117, exit 0. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01ADgXkZxYt9KBYyjnPGCQeF
3 tasks done
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to subscribe to this conversation on GitHub.
Already have an account?
Sign in.
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
The first audit of this harness run on Linux rather than Windows. On Linux and macOS — the only server platform, the one the harness is declared a core for — none of the three links in its own promise held, and none of the three reported a failure. Spec 024 is closed: ten defects, all reproduced, each fix shipping the regression that was missing.
node tools/elt-oracle-runner.js --full110/112, exit 1118/118, exit 0node tools/elt.js oracleexit 1 (0s), no reason printedelt commitwith the shipped hook enabledreasonslug per cause.harness/harness.jsonWhy it could not work outside Windows
shell: "powershell";sh()spawned it unchecked, and on ENOENTspawnSyncreturnsstatus: null, which became an ordinary red oracle. The shell dispatcher existed in five copies, so a fix in one never reached the others. Now one module (tools/shell-run.js), default fromprocess.platform, a missing interpreter names itself and the field to edit, andshellenters config validation.timer.unref()inwithJudgeTimeoutdefeated the guard's entire purpose: an unref'd timer does not hold the event loop, so with a judge that never returns Node drained the loop and exited before the timeout fired —try/finallynever ran and the process returned zero. The same line hangs the guard's own regression and cancelled 13 tests after it. The benchmark grader got a preflight: a missingpytestis no longer indistinguishable from a wrong solution..githooks/pre-commitwas committed at mode100644; git printshint: ... not set as executableand commits anyway.Why "green" did not mean "checked"
treeHash()hashedgit status --porcelainlines verbatim, status column included. That column changes on staging (" M f"→"M f","?? g"→"A g"), andelt commitrunsgit add -Aexactly between minting the proof and re-checking it in the hook — so it could never match. Second half of the same root:.trim()on the whole stream ate the leading space of the first line, andline.slice(3)then truncated the path.computeEntrywas called withoutscanDirswhileTEST_ROOTShas three entries, so editingbin/l0.jsnever moved the key ofbin/l0.test.js— sabotaging the L0 entry point printed65/65 passed in 2.4s. The cache also lived in a gitignored.harness/, invisible totreeHash. Now the roots are shared,RUNNER_VERSIONcovers the selection rules, the cache moved to.git/elt/, and the proof carriesran/cached—ran: 0on a non-empty selection is refused as an absence of checking.ai,mcp,agents,playwright,cliandvercel.resolvedfield nothing writes (rows close withclosedAt) — ingraph-statea settled row could blockelt cutoverforever. The--pushguard readcommitHashfrom certificates that carrycommit; a naive rename would have flipped it to always-block, since the certificate stores a short sha and the check used the full one.Why an outside developer, or a server agent, could not use it
package.jsonnow exists, withengines,npm test, and the version as a fifth source inversion-check(the root of D28).judge-invoke.jsdroppedr.reason, soreasonsarrived empty on failure;judgeLogreachedelt.jsand went only to the run-log. The real cause lived in a file nothing printed — on a server that is--dangerously-skip-permissions cannot be used with root/sudo privileges. And exit code 4 covered 44 of 55 refusals. Renumbering would break drivers that branch on=== 4, so the discriminator became a slug:die(msg, code, reason)plus{ok, code, reason, message}under--jsonorELT_JSON=1.--jsonnow coverscommit,gateandjudge run. Human output is unchanged byte for byte, and a regression asserts that.redProof: "OFF"kept the circuit on,specApproval: "no"kept the approval gate on,batch: "three"silently became 3. Types are errors now; unknown keys are warnings until the next minor (SCHEMA_VERSION), because retired fields sit in existing configs. The schema was written from the code, which paid off immediately — its first version rejected working configs onbranchPolicy, where an inventedcurrentdisagreed with the realfeature | none.Coverage 112 → 118. Full evidence in
specs/024-harness-scale-readiness/audit.md(E1–E22); D29–D38 closed and D39–D41 plus the D36 remainder opened indocs/DEFECTS.md.Test plan
Measured on Linux, Node 22.22.2, clean tree. CI ran the same suite green on
windows-latestandubuntu-latestfor every commit on this branch.Hermeticity verified separately:
git status --porcelain -uallhashed before and after a full run is byte-identical, and.harness/ctx7-proof.jsonlis no longer created.The branch's smallest regressions:
tools/elt-tree-hash.test.js— discriminating, verified by reverting the fix: 3 of its 5 assertions go red on the pre-024elt.js. The other two guard against over-fixing — a real content change after the proof must still be refused.tools/shell-run.test.js— a missing interpreter is named rather than reported as a red oracle; the shipped config carries no hardpowershell; no second dispatcher copy may reappear.tools/supply-exec-bit.test.js— every file under.githooks/is executable in git's index (checked there, not on the filesystem, so it holds on Windows too).tools/elt-oracle-cache.test.js— editing a source underbin/orbenchmarks/moves its test's cache key;SCAN_DIRSandTEST_ROOTSmay not diverge again.tools/elt-commit-proof.test.js— a proof withran: 0is refused on both the plain and the trusted gate path; an old-schema proof is refused by name.tools/context7-cli.test.js— an injected runner mints no proof; coverage matches package names exactly, and the free-text query no longer counts at all.tools/elt-machine-readable.test.js— two refusals sharing exit 4 are told apart by slug;ELT_JSON=1works without the flag; without--jsonstdout stays empty and stderr is unchanged; a dead judge prints the provider log.tools/elt-dead-checks.test.js— each of the three revived checks is asserted to fire and not to fire where it shouldn't.tools/harness-schema.test.js— the exact config that used to validate is now rejected; retired fields are named as retired; the repo's own config passes with zero warnings.tools/review-runtime.test.js— a sixth lens no longer kills the judge, while a missing declared lens still does.full suite exits 0
instruction drift check exits 0
the smallest regression for this branch exists (or the change is trivial)
🤖 Generated with Claude Code
https://claude.ai/code/session_01ADgXkZxYt9KBYyjnPGCQeF