feat(alpha): packaging — the proven story up front, a non-blocking Action, and an honest attach contract (A4) - #312
Conversation
The deadline was `DateTime.UtcNow + seconds` — wall-clock arithmetic. A
backwards system-clock adjustment extends it, so the documented guarantee
("a forgotten sample cannot outlive its job") held only while nobody touched
the clock. A bound a clock adjustment can extend is not a bound.
All four samples now measure it with a Stopwatch started at Announce(), and
ShouldRelease() drops its deadline parameter — the callers were each carrying
their own copy of the arithmetic, including the WPF drivers. The stdin-line
and stop-file paths are untouched.
Raised on PR #310 as a non-blocking tail and deliberately deferred out of it
rather than restarting a green run; landing it first here so the claim in the
README is true before the packaging arc quotes it.
Verified locally: stdin at EOF with a 3s deadline exits at 3s; the stop file
still releases in ~1s against a 60s deadline; the demo script still releases
through its FIFO with both variants green; all four samples keep their owen
verdicts (bad → 1, ok → 0).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015de4MezSeUnZBoWq1fFU5M
The README opened with a capability list. It now opens with the one claim the repository can actually back end to end: Owen finds a lifetime bug statically and confirms it at runtime by naming the reference path that keeps the object alive. Under the hero, the two surfaces are shown separately and deliberately NOT merged into one button: `owen check` is the static half and prints the finding at the line that causes it; the runtime witness is a SEPARATE step you run when you want proof, not something that happens on every build. Conflating them would promise a product that does not exist. Every number and every line of output under the hero comes from the flagship sample as CI runs it, and the acceptance contract is stated in the same breath — bad → RETAINED / exit 1, ok → ABSENT or OBSERVED_ONLY / exit 0 / zero durable roots — so a reader can check the claim instead of trusting it. No internal vocabulary: no root kinds, no traversal order, no arc numbers. A first-time reader has not yet asked to care about any of that. Both language variants updated together. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015de4MezSeUnZBoWq1fFU5M
The article the README hero points at. It walks from the user-visible defect to the boundary of what the proof covers: the guarded unsubscribe, what the static half can and cannot establish, why a static finding is not yet proof, why "reachable from a GC root" is the wrong question, and why durable roots must be walked to exhaustion before a stack frame gets to claim the answer. Two sections exist because the mistakes were real and are more instructive than the design. The first implementation let cost-bounding knobs change the verdict in four distinct ways — heap-order sampling, type-name-only grouping, a hop limit that erased the root kind, and a verdict taken from bare reachability — which is one principle stated four times: sampling and display limits affect presentation, never discovery, classification, aggregation, or the exit code. And the WPF round where a frozen dispatcher produced a real [gc-handle] snapshot of a sample that was leaking nothing: a witness is only as honest as the moment the picture is taken, and the narrow assertion that stayed green through it is its own lesson. Every paragraph is filtered: proven by the gate, observed in a named run, or an architectural rule — and the limits section says plainly what is NOT claimed, including that the witness reports retention rather than causation, repairs nothing, and does not ship inside the published CLI package today. Nothing aspirational is written in the present tense. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015de4MezSeUnZBoWq1fFU5M
The article opened by promising the thing its own limits section denies. "A leak report is a claim about the future: this object will never be collected" framed the witness as proving permanence; §9 correctly says a snapshot describes a moment, and reading it as "always" is the reader's inference. A document cannot lead with a claim it later withdraws. The opening now states the two questions plainly — can the release be proven to run, and is a live process holding the object through a durable path — and assigns each to the half that answers it, the second explicitly for a particular heap snapshot. Dropped with it: "most tools are guessing". That is a comparative claim about other analysers, and this repository has not done the comparative study that would back it. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015de4MezSeUnZBoWq1fFU5M
…n an operational failure Adding a checker to someone's repository should not turn their CI red on day one. The action's fail-on-finding now defaults to false: findings are published as PR annotations or SARIF exactly as before, and the step succeeds. Gating is an explicit opt-in. The dangerous half of that change is the half worth spelling out. "Annotations instead of failure" is a policy about DEFECTS FOUND IN YOUR CODE. It must never cover the analyser crashing, an unreadable input, a missing project, or a SARIF that could not be written — those are the tool failing to LOOK, and a friendly default that swallows them turns "could not run" into a green check. So the status contract is now four explicit tiers: exit 0 -> success, no annotations exit 1, findings -> annotations/SARIF published, success exit 1 + fail-on-finding: true -> annotations/SARIF published, failure exit >= 2 -> failure ALWAYS, diagnostic preserved To make that enforceable the non-SARIF branch stopped delegating its status to own-check. It now always runs with --fail-on-finding so it can see the true tier (0/1/>=2) and decides the step's outcome itself; run without the flag, own-check folds findings into 0 and tier 1 becomes indistinguishable from tier 0 — exactly the distinction the new default depends on. The SARIF branch already worked this way. Both modes analyse identically and publish identical evidence; only the final status differs. Verified locally by replaying the branch logic against real runs: default -> 160 annotations and SUCCESS; opt-in -> the same tree, FAILURE; clean tree -> SUCCESS with zero annotations; malformed config -> FAILURE at exit 2 even with fail-on-finding false. All four tiers are now pinned in ci.yml. Deliberately there rather than only in action-marketplace-readiness.yml, which covers similar ground for the consumer fixture but is path-filtered — a change to the core's exit codes, where these tiers originate, would never wake it. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015de4MezSeUnZBoWq1fFU5M
…usal in CI "Attach allowed -> analysis; denied by policy -> exit 2 with an explicit diagnostic" was the documented contract, and only the exit code was true. A denied attach printed the raw ClrMD exception, which says nothing about why: a developer on Ubuntu — where Yama's ptrace_scope defaults to 1 and forbids attaching to anything that is not a descendant — was told "could not attach" and left to guess. The witness now explains it: the policy value it read, that the target was alive so this is permission and not absence, that Owen DID NOT LOOK and this is therefore not a verdict about the heap, and the four ways forward (dump, launch the target as a descendant, PR_SET_PTRACER, or relax the policy deliberately). The advice is narrow on purpose — live attach, Linux, target exists, Yama actually restricting. A typo'd pid still gets "process is not running" without a lecture about kernel security. CI now proves the refusal instead of trusting it. One step sets ptrace_scope=1, holds a sample as a SIBLING of the witness (the shape Yama forbids), and requires exit 2, the policy named in the diagnostic, and NO runtime.json written — a refused read must not leave a verdict artifact behind. The next step sets the scope to 0 and requires the full end-to-end demo. Both halves of the contract, asserted on every run. This is the failure the first CI round of the witness arc actually hit. docs/runtime-witness-operations.md is the operator-facing page: the exit-code tiers, the ptrace_scope table, how to choose among the options (a dump is the right default for anything you did not launch), and the rule that policy is relaxed in a workflow — visibly, on a throwaway runner — never inside a script people also run on their laptops. Owen does not escalate, does not retry with sudo, and does not ask to be run as root. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015de4MezSeUnZBoWq1fFU5M
|
Warning Review limit reached
Next review available in: 49 minutes Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available. How can I continue?After more reviews become available, a review can be triggered using the To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews. How do review limits work?CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability. For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window. Please refer docs for additional details. Review details⚙️ Run configurationConfiguration used: Path: .coderabbit.yaml Review profile: CHILL Plan: Pro Plus Run ID: 📒 Files selected for processing (17)
✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 35872372d0
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
…mode; sync the documented default Three review findings, all real. The first is a regression this branch introduced and is the important one. 1. A FAILED EXTRACTOR READ AS A FINDING (P1). own-check.sh runs under `set -euo pipefail`, so a stage-1 failure — a broken build, a crashed extractor — killed the script with exit 1. Exit 1 is the "analysed, and there are findings" tier. While the Action defaulted to failing on findings, the ambiguity was invisible: both outcomes failed the step. Changing the default to annotate-and-pass turned it into the exact failure this branch exists to prevent — a green check over a run that analysed nothing. Reproduced before fixing: `--root` at a path with no extractor project exited 1. Stage-1 failures are now normalised into the >=2 tier in both own-check.sh and own-check.ps1 (the extractor's own contract codes, 2 = usage and 4 = no input, already live there and pass through untouched). CI pins it, because a doctrine that only lives in a commit message decays. 2. YAMA ADVICE ASSUMED MODE 1 (P2). The diagnostic explained every nonzero ptrace_scope as "not a descendant" and offered remedies that only work there. Mode 2 requires CAP_SYS_PTRACE, where PR_SET_PTRACER is useless; mode 3 disables attach until reboot, where no sysctl or capability helps at all. Confident, actionable, wrong advice is worse than a bare exception, so the message now branches per mode and says plainly when the only way forward on this boot is a dump. 3. STALE DOCUMENTED DEFAULT (P2). docs/suppression-and-fp-policy.md still told readers the Action defaults to failing on findings — a document someone could follow into an ungated pipeline believing it was gated. Updated, and while there, it now states the tier boundary too: the lever governs findings, never operational failures. Verified locally: broken stage 1 -> 2 (was 1); clean -> 0; findings with the flag -> 1; findings without it -> 0; malformed config -> 2. Witness rebuilt, selftest 16/16, full suite and ruff green, demo unchanged. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015de4MezSeUnZBoWq1fFU5M
Closes the evidence gap recorded in the #312 merge commit. The stage-1 normalisation — a broken extractor must land in the >=2 tier instead of borrowing exit 1 from "analysed, findings present" — was mirrored into the PowerShell wrapper and proven nowhere. No other job touches that file: the composite action drives the .sh, and the .ps1 builds its paths with backslashes, so it cannot run on Linux at all. A new windows-latest job exercises the tiers the wrapper actually has: broken stage 1 -> >=2, clean -> 0, findings -> 1 with -FailOnFinding and 0 without. The findings pair also asserts the two runs produce IDENTICAL output, so the flag can never quietly become a second, differently-behaving analysis. The "broken config -> 2" tier from the issue is deliberately absent: the PowerShell wrapper has no -Config parameter, so that tier does not exist on this surface. Asserting it would have meant inventing the surface to fit the test. One platform detail is load-bearing enough to be a comment rather than folk knowledge: the assertions read $LASTEXITCODE, never the wrapper process's exit code. Inside a PowerShell session — which is what `shell: pwsh` is — a script's `exit N` sets $LASTEXITCODE to N, but `pwsh -Command "& ./x.ps1"` collapses it to 1 at the process boundary. Measured the wrong way, every tier here reads as pass/fail and the contract looks broken when it is not; that is exactly what happened once while writing this. Verified locally with PowerShell 7.4.6: the stage-1 tier runs on Linux (the failure is the point) and reports $LASTEXITCODE = 2; every step body was parse-checked with the PowerShell parser. The remaining tiers need a working extractor and therefore first run on the Windows runner. Refs #313. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015de4MezSeUnZBoWq1fFU5M
…s on Windows (#313) Closes the evidence gap recorded in the #312 merge: own-check.ps1's stage-1 normalisation was mirrored from the shell wrapper and proven nowhere. A windows-latest job now pins the tiers the wrapper actually has — broken stage 1 >= 2, clean 0, findings 1 with -FailOnFinding and 0 without, with identical output in both modes. The "broken config" tier is absent on purpose: the PowerShell wrapper has no -Config parameter, and asserting it would have meant inventing the surface to fit the test. Three commits kept unsquashed because the sequence is the point: the thing under test held from the start, and the measuring rig broke twice. First a step that printed "OK ... (2)" and still failed, because GitHub's pwsh wrapper ends with `exit $LASTEXITCODE` and these steps deliberately run failing commands. Then a path that bound to -Root instead of -Paths, sending the extractor hunt inside the tree being scanned — PowerShell's `--` ends parameter parsing and $Root holds position 0. That second one is a user-facing defect in the wrapper's own documented examples, and it is deliberately NOT fixed here: the test that found it must not also repair what it tests. Tracked as #315.
Что и зачем
Последний срез подготовки Owen Alpha: упаковка. Инженерия арок A1–A3 уже в main; здесь — то, что видит человек, впервые встретивший проект, плюс два поведенческих контракта, которые эта упаковка обязана не оболгать.
README-герой. Вместо перечня возможностей — единственное утверждение, которое репозиторий подтверждает от начала до конца: Owen находит ошибку времени жизни статически и подтверждает её во время выполнения, называя ссылочный путь. Статический анализ и свидетель показаны как две отдельные поверхности, а не одна кнопка:
owen checkпечатает находку на строке, а свидетель — отдельный запуск «когда нужно доказательство», не часть каждой сборки. Весь вывод под героем взят из flagship-образца в том виде, в каком его гоняет CI, и рядом стоит контракт приёмки, чтобы читатель мог проверить, а не поверить. Внутренней терминологии в герое нет.Статья
docs/how-owen-proves-retention.md— девять частей от пользовательского дефекта к границам доказательства. Два раздела посвящены ошибкам, потому что они поучительнее устройства: четыре дефекта первой реализации свидетеля сводятся к одному принципу (выборка и лимиты отображения влияют на подачу доказательства — никогда на его обнаружение, классификацию, агрегацию и код возврата), а WPF-раунд с заблокированным диспетчером даёт урок про момент снимка и про то, что узкая ассерция пережила сломанный образец. Каждый абзац проходит фильтр «доказано гейтом / наблюдалось в конкретном прогоне / архитектурный принцип», и раздел границ говорит прямо, чего свидетель не утверждает.Дефолты Action: аннотации, а не падение.
fail-on-findingтеперьfalse— добавление Owen в чужой репозиторий не красит CI в первый же день. Опасная половина этой перемены выписана явно, потому что «дружелюбный дефолт» не имеет права поглотить операционный отказ:Чтобы это было исполнимо, не-SARIF ветка перестала делегировать статус
own-check: она всегда запускает его с--fail-on-finding, видит истинный ярус и решает сама — иначе находки сворачиваются в 0 и ярус 1 неотличим от яруса 0, то есть исчезает ровно та граница, на которой держится новый дефолт. Анализ, вывод и evidence в обоих режимах идентичны; отличается только итоговый статус.Контракт attach на Linux. Обещание «запрещён политикой → exit 2 с явной диагностикой» было выполнено наполовину: код честный, а печаталось голое исключение ClrMD, которое само не знает причины («either the process has exited or you don't have permission»). Свидетель теперь называет прочитанное значение
ptrace_scope, сообщает, что цель жива и потому это отказ в правах, что он не смотрел и это не вердикт о куче, и перечисляет четыре выхода. Совет узкий: живой attach, Linux, цель существует, Yama действительно ограничивает — опечатка в pid лекции про безопасность ядра не получает.Монотонный дедлайн удержания.
DateTime.UtcNowзаменён наStopwatchво всех четырёх образцах: обещание «забытый образец не переживёт свою джобу» ломалось переводом системных часов. Замечание ревьюера по PR #310, вынесенное туда намеренно, чтобы не перезапускать зелёный прогон.Что здесь доказывается, а не описывается
Оба яруса — ассерции в gate A на одном прогоне: один шаг ставит
ptrace_scope=1и держит образец братом свидетеля (форма, которую Yama запрещает), следующий ставит0и требует полное демо. Это ровно та ошибка, в которую упёрся первый прогон witness-арки, — теперь она проверка, а не воспоминание.Тип изменения
Как проверено
python tests/run_tests.py— весь набор зелёныйruff check .RetentionPath selftest(16 проверок) и смерженныйscripts/flagship-demo.shв обоих вариантах после изменения механизма удержанияown-check: дефолт → 160 аннотаций и SUCCESS; opt-in на том же дереве → FAILURE; чистое дерево → SUCCESS без аннотаций; сломанныйown.toml→ FAILURE на коде 2 приfail-on-finding: false3587237, включая оба новых шага с первой попытки. Отказ воспроизведён на раннере по-настоящему:kernel.yama.ptrace_scope = 1→Could not PTRACE_ATTACH to any thread of the process 2960→ диагностика с названной политикой →OK: denied attach -> exit 2, policy named, no artifact writtenЛокально не проверялось и почему: в контейнере разработки Yama отсутствует (
/proc/sys/kernel/yama/ptrace_scopeнет вовсе), поэтому отказ впервые бежал на раннере — это было отмечено заранее, вместе с решением снять шаг и сказать об этом, если денай не воспроизведётся, а не ослаблять ассерцию до зелёного.Связанные issue
Refs #250/#253/#254 (Owen Alpha — этим срезом инженерная часть подготовки закрыта; владельческий гейт #252: лицензия, версия, environments, секрет NuGet — остаётся единственным блокером публикации), #270 (runtime witnesses), #278 (сюжет flagship-репро).
Чеклист
docs/how-owen-proves-retention.md;docs/runtime-witness-operations.md)feat:,fix:,docs:…)Generated by Claude Code