Repository navigation
Commit 78330d6
fix(S2): owen-rewrite defends its own contract — the five HOLD blockers
The rewriter is the load-bearing step, so it may not assume the Python gate ran. Every
guarantee it makes it now proves for itself, over the exact bytes it parses.
1. SELF-CONTAINED input / hash / root validation. The CLI took raw plan + candidates on
faith (and CI called it with no validate_apply_inputs at all): a malformed shape could
surface as a GetProperty exception, `manual_review` needed no candidate, and a rooted
or `..` path in `postimage/<rel>` could have thrown the write clean out of the out-dir
— the "never writes the source tree" promise rested on the JSON's good manners. It now
RE-DERIVES the canonical plan projection from the bundle and requires the plan to BE
it: bundle_sha256 binding (a canonical-JSON writer matching Python's json.dumps
producer byte for byte), exact envelope key sets, fixed constraints, a total ordered
decision<->candidate bijection, per-candidate action permissions, canonical
root-relative source path, symlink-resolved root confinement, an out-dir that is
neither the root nor inside it nor already populated, and a postimage destination
confined to the out-dir. Every shape failure is exit 2, never a traceback.
validated_plan_sha256 now hashes the SAME planBytes that were parsed (it re-read the
file at report time).
2. STRICT UTF-8. `new StreamReader(..., Encoding.UTF8, ...)` silently replaces invalid
bytes with U+FFFD and still reports CodePage 65001, so the encoding check passed and
the post-image could re-encode bytes OUTSIDE the declared edits — invisibly to
EditsAreExact, which compares the already-corrupted decoded text. Now a throwing
decoder (UTF8Encoding(false, throwOnInvalidBytes: true)); DecoderFallbackException and
a UTF-16 BOM are controlled refusals.
3. TARGET API GRAMMAR, before the replacement exists. `target_api.subscribe` was an
arbitrary plan string interpolated straight into C#. It is now parsed with Roslyn and
must be a canonical identifier / member-access chain and nothing else — no invocation,
arguments, generics, `?.`, operators, object creation or trailing junk — and the
replacement is built from the VALIDATED syntax node via SyntaxFactory, not string
interpolation.
4. EXTRACTOR-COMPATIBLE normalization. Handler/receiver were compared as whitespace-
stripped raw text, and SyntacticTypeFqn dropped type parameters — so a candidate for
`N.ViewModel<T>` was compared against `N.ViewModel` and could never match. Both sides
are now re-derived with the PRODUCERS' own derivations (extractor `a.Right.ToString()`
for handler, the collector's last-'.' split for source, `FixNormWs`+`NormalizeHandler`
for the computed identities, and a type FQN carrying its type parameter list the way
SymbolDisplay prints it), so the comparison is exact over SHA-pinned bytes rather than
a lossy re-normalization. The weak wrapper now receives the NORMALIZED handler:
`new PropertyChangedEventHandler(OnChanged)` reaches it as `OnChanged`.
5. TRANSACTIONAL publication. postimage-then-report meant a failed report write left the
postimage behind, breaking "nothing written". The whole bundle is now staged in a
sibling dir and published with ONE atomic rename; any failure discards the staging dir
entirely, and a pre-existing out-dir is a hard refusal so runs cannot mix.
Regressions (tests/rewriter_regressions.sh, new CI step) drive the real chain end to end
and cover all five groups: 12 input/hash/path refusals — including path confinement
against an attacker who owns BOTH files (bundle re-hashed so only the rewriter's own
check can refuse) — 8 target-grammar refusals, out-dir confinement + a failed publication
leaving no out-dir and no staging dir, the normalization forms (new samples/
FixRewriteSample.cs: explicit delegate construction, `this.`-qualified handler, generic
containing type, and the nested type S0 itself refuses), and encoding (BOM preserved, all
CRLF endings preserved with no lone LF, invalid UTF-8 refused). Plus the happy path,
byte-identical re-run, source tree untouched, and the stale-SHA refusal.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JxKjqdGEFzq4UzZupw379G1 parent 7f3c142 commit 78330d6
4 files changed
Lines changed: 1023 additions & 252 deletions
File tree
- .github/workflows
- frontend/roslyn
- Owen.CSharp.Rewriter
- samples
- tests
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
1701 | 1701 | | |
1702 | 1702 | | |
1703 | 1703 | | |
1704 | | - | |
1705 | | - | |
1706 | | - | |
1707 | | - | |
1708 | | - | |
1709 | | - | |
1710 | | - | |
1711 | | - | |
1712 | | - | |
1713 | | - | |
1714 | | - | |
1715 | | - | |
1716 | | - | |
1717 | | - | |
1718 | | - | |
1719 | | - | |
1720 | | - | |
1721 | | - | |
1722 | | - | |
1723 | | - | |
1724 | | - | |
1725 | | - | |
1726 | | - | |
1727 | | - | |
1728 | | - | |
1729 | | - | |
1730 | | - | |
1731 | | - | |
1732 | | - | |
1733 | | - | |
| 1704 | + | |
| 1705 | + | |
| 1706 | + | |
| 1707 | + | |
| 1708 | + | |
| 1709 | + | |
| 1710 | + | |
| 1711 | + | |
| 1712 | + | |
| 1713 | + | |
| 1714 | + | |
| 1715 | + | |
1734 | 1716 | | |
1735 | | - | |
1736 | | - | |
1737 | | - | |
1738 | | - | |
| 1717 | + | |
| 1718 | + | |
| 1719 | + | |
| 1720 | + | |
| 1721 | + | |
1739 | 1722 | | |
1740 | 1723 | | |
1741 | | - | |
1742 | | - | |
| 1724 | + | |
| 1725 | + | |
| 1726 | + | |
1743 | 1727 | | |
1744 | 1728 | | |
1745 | 1729 | | |
| |||
0 commit comments