Skip to content

feat(alpha): packaging — the proven story up front, a non-blocking Action, and an honest attach contract (A4) - #312

Merged
PhysShell merged 7 commits into
mainfrom
claude/complex-project-tasks-viyycs
Jul 28, 2026
Merged

feat(alpha): packaging — the proven story up front, a non-blocking Action, and an honest attach contract (A4)#312
PhysShell merged 7 commits into
mainfrom
claude/complex-project-tasks-viyycs

Conversation

@PhysShell

@PhysShell PhysShell commented Jul 28, 2026

Copy link
Copy Markdown
Owner

Что и зачем

Последний срез подготовки 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 в первый же день. Опасная половина этой перемены выписана явно, потому что «дружелюбный дефолт» не имеет права поглотить операционный отказ:

exit 0                          → success, аннотаций нет
exit 1, findings                → аннотации/SARIF опубликованы, success
exit 1 + fail-on-finding: true  → аннотации/SARIF опубликованы, failure
exit >= 2                       → failure ВСЕГДА, диагностика сохранена

Чтобы это было исполнимо, не-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, вынесенное туда намеренно, чтобы не перезапускать зелёный прогон.

Что здесь доказывается, а не описывается

attach разрешён   → анализ идёт
attach запрещён   → exit 2, политика названа, артефакт НЕ записан

Оба яруса — ассерции в gate A на одном прогоне: один шаг ставит ptrace_scope=1 и держит образец братом свидетеля (форма, которую Yama запрещает), следующий ставит 0 и требует полное демо. Это ровно та ошибка, в которую упёрся первый прогон witness-арки, — теперь она проверка, а не воспоминание.

Тип изменения

  • feat — новая возможность
  • fix — исправление бага
  • docs — документация
  • refactor / chore / test / ci — без изменения поведения

Как проверено

  • python tests/run_tests.py — весь набор зелёный
  • ruff check .
  • селфтесты затронутых скриптов — Python-скрипты не менялись; вместо них прогнан RetentionPath selftest (16 проверок) и смерженный scripts/flagship-demo.sh в обоих вариантах после изменения механизма удержания
  • Логика статусной ветки Action воспроизведена локально против настоящих запусков own-check: дефолт → 160 аннотаций и SUCCESS; opt-in на том же дереве → FAILURE; чистое дерево → SUCCESS без аннотаций; сломанный own.toml → FAILURE на коде 2 при fail-on-finding: false
  • Дедлайн проверен вживую: stdin в EOF с дедлайном 3 с выходит на 3 с; стоп-файл освобождает за ~1 с против 60-секундного; FIFO демо-скрипта — быстро
  • Ссылки в README (обе версии) и обоих документах проверены скриптом — битых нет
  • CI зелёный на 3587237, включая оба новых шага с первой попытки. Отказ воспроизведён на раннере по-настоящему: kernel.yama.ptrace_scope = 1Could 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-репро).

Чеклист

  • изменение покрыто тестом/селфтестом (или объяснено, почему нет)
  • README/docs обновлены при необходимости (README.md и README.ru.md — герой и снипет Action; docs/how-owen-proves-retention.md; docs/runtime-witness-operations.md)
  • коммиты в conventional-commit стиле (feat:, fix:, docs: …)

Generated by Claude Code

claude added 6 commits July 26, 2026 21:12
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
@coderabbitai

coderabbitai Bot commented Jul 28, 2026

Copy link
Copy Markdown

Review Change Stack

Warning

Review limit reached

@PhysShell, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 49 minutes

Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available.
You're only billed for reviews past your plan's rate limits ($0.25/file).

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

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 configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 517a2e1d-92bd-408e-a1e6-d0e615b17b2f

📥 Commits

Reviewing files that changed from the base of the PR and between 49498a5 and 604272e.

📒 Files selected for processing (17)
  • .github/workflows/ci.yml
  • README.md
  • README.ru.md
  • action.yml
  • audit/runtime/RetentionPath/Program.cs
  • docs/how-owen-proves-retention.md
  • docs/runtime-witness-operations.md
  • docs/suppression-and-fp-policy.md
  • examples/flagship/README.md
  • examples/flagship/console/bad/Hold.cs
  • examples/flagship/console/ok/Hold.cs
  • examples/flagship/wpf/bad/App.xaml.cs
  • examples/flagship/wpf/bad/Hold.cs
  • examples/flagship/wpf/ok/App.xaml.cs
  • examples/flagship/wpf/ok/Hold.cs
  • scripts/own-check.ps1
  • scripts/own-check.sh
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch claude/complex-project-tasks-viyycs

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 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".

Comment thread action.yml
Comment thread audit/runtime/RetentionPath/Program.cs Outdated
Comment thread action.yml
…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
@PhysShell
PhysShell merged commit df69b41 into main Jul 28, 2026
45 checks passed
PhysShell pushed a commit that referenced this pull request Jul 28, 2026
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
PhysShell added a commit that referenced this pull request Jul 28, 2026
…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.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants