From 9839e2ab712e3e126f4739539741fcfdc0327fda Mon Sep 17 00:00:00 2001 From: Claude Date: Sat, 4 Jul 2026 20:12:00 +0000 Subject: [PATCH 01/25] docs(proposals): P-026 C# strictness retrofit profile (own audit strictness) A framing/packaging proposal, not a new engine: front the existing fleet's findings (lifetime/resource, typestate/obligations, config drift) under one strictness narrative + a single score aimed at the F#/Rust-refugee audience. Accepts the strictness-audit framing and keeps ownership/lifetime as the moat; explicitly rejects an Option/Result migration linter and a generic nullable nag as the SAST fight the charter refuses. Records the honest gap: a whole-solution score depends on the not-yet-general Roslyn extractor (P-001/P-014), so the first cut runs over the audit target or labels dimensions NO-TOOL rather than faking a clean pass. Adds the P-026 index row. Co-Authored-By: Claude Opus 4.8 Claude-Session: https://claude.ai/code/session_01YNzzRHgdDbue9LhWyLTui3 --- .../P-026-csharp-strictness-retrofit.md | 185 ++++++++++++++++++ docs/proposals/README.md | 1 + 2 files changed, 186 insertions(+) create mode 100644 docs/proposals/P-026-csharp-strictness-retrofit.md diff --git a/docs/proposals/P-026-csharp-strictness-retrofit.md b/docs/proposals/P-026-csharp-strictness-retrofit.md new file mode 100644 index 00000000..40c0cd32 --- /dev/null +++ b/docs/proposals/P-026-csharp-strictness-retrofit.md @@ -0,0 +1,185 @@ +# P-026 — C# strictness retrofit profile (`own audit strictness`) + +- **Status:** draft — a *framing/packaging* direction, not a new engine. + Sourced from an external strategic read (a developer's "The Missing + Programming Language" essay + a follow-up analysis of how Own.NET could speak + to that audience). Implementation not started. +- **Depends on:** the audit orchestrator ([`Plan.md`](../../Plan.md), + [`audit/README.md`](../../audit/README.md)) — SARIF normalization, cross-tool + agreement scoring, the health report; the existing lifetime/resource core + (`OWN001–015`), the typestate / obligation work + ([P-010](P-010-type-disciplines.md), [P-025](P-025-obligation-protocols.md)), + and the C# fact seam ([P-001](P-001-csharp-extractor.md), + [P-014](P-014-semantic-resolution.md)). Relates to + [P-015](P-015-configuration-surface.md) (check selection / severity) and + [P-023](P-023-architecture-guard.md) (drift). +- **Where it lives:** an **audit profile + a report renderer**, consumed through + CLI + SARIF only, zero coupling to `ownlang/`. It follows the audit code + (currently `OwnAudit`, per `audit/README.md`), like [P-024](P-024-security-audit-profile.md). + +## Decision (read this first) + +**`own audit strictness` is a witness, not an engine.** It is a profile and a +report that *front the findings the fleet already produces* under one narrative — +"where C# lets your types lie, your domain rules live in comments, and your +resources leak because the language cannot express ownership" — plus a single +headline number (a **strictness score**). It adds **no new detector heuristic**; +every number it prints traces to a real finding from a real tool, or is labelled +`NO-TOOL`. This is the same charter as the rest of the audit fleet +("оркестратор, не анализатор"; "берём готовое"; honest coverage). + +The motivating analysis proposed five angles. Two are **accepted as framing**, +two are **rejected as scope**, one is **already our identity**: + +| # | Angle from the analysis | Verdict | +|---|-------------------------|---------| +| 1 | Strictness audit — "make C# stop pretending to be grown-up" | **Accept as framing** — this profile *is* that packaging over existing findings | +| 2 | Domain-invariant miner (implicit state machines hidden in flags/dates) | **Already in flight** — [P-010](P-010-type-disciplines.md) typestate + [P-025](P-025-obligation-protocols.md); this profile *surfaces* their output, it does not re-implement | +| 3 | Result/Option migration assistant (`return null` → `Option`, `throw` → `Result`) | **Reject as core scope** — see Non-goals | +| 4 | Exhaustiveness guard over OneOf/LanguageExt/… | **Defer to P-010** — internal exhaustiveness is P-010's territory; not a new pack here | +| 5 | Ownership / lifetime as the unique card | **Already the identity** (ROADMAP: "lifetime/resource bugs C# cannot express") — this profile leads with it, does not dilute it | + +The load-bearing decision is the **rejection of #3/#4 as their own product.** +"Find `return null`, suggest `Option`; find `throw`, suggest `Result`" is, +in the analysis's own words, something "any caffeinated student with Roslyn can +write." It is a nullable-annotation / control-flow linter — exactly the broad +"we find smells" arena where CodeQL/Sonar/Semgrep win on sales budget, not +merit, and which the ROADMAP explicitly refuses. We take the *vocabulary* +("the domain model lies", "make invalid states visible → then unrepresentable") +without becoming that linter. Ownership/lifetime stays the moat. + +## Motivation — meeting an audience that already articulated the pain + +The essay ranks C# at 4.5/5: big ecosystem, good perf, good DevX, but +"expressive types: mixed" — nullable is opt-in and warning-only, `!` launders +nulls, unions are perennially "coming", no local immutability. The reader wants +**C# ecosystem + F#/Rust-like safety** and is not going to switch languages to +get it. That is precisely Own.NET's long-term identity restated by an outsider: + +> an external static-contract layer for C#/.NET that adds ownership, typestate, +> effects, capabilities, and domain-specific types **without rewriting the +> codebase.** — `docs/ROADMAP.md` + +So the value here is **not new capability** — it is a *doorway* sized for this +reader. Today the capabilities are addressed by their mechanism (`OWN008`, +`DI003`, `OBL002`, "captive dependency"), which lands with a lifetime nerd, not +with someone whose complaint is "C# lets my types lie". `own audit strictness` +re-presents the same evidence as *"here are the places C# let your model lie"*, +with one score to argue about. + +## Scope + +- A CLI surface `own audit strictness ` that runs the relevant slice of + the existing fleet and renders a **strictness report** (markdown + json), + reusing the audit orchestrator's normalize/score/report pipeline. +- A **strictness score** derived *only* from findings that already exist, + bucketed into named strictness dimensions (below), each dimension carrying an + honest coverage state (`covered` / `NO-TOOL` / `partial`). +- A markdown narrative that groups findings as *invariant lies*, not as tool + codes — e.g. an implicit state machine surfaced by P-010/P-025 is rendered as + "`Order.Status` + `PaidAt` + `CancelledAt` form an implicit state machine; + these constraints are not encoded in the type system", with the underlying + finding IDs as evidence. + +### The dimensions (all fronting existing or proposed detectors) + +| Strictness dimension | Backed by | +|----------------------|-----------| +| Ownership / resource lifetime not expressed | `OWN001–015`, WPF/`IDisposable`/DI/pool profiles ([P-004](P-004-wpf-lifetime-profile.md)–[P-007](P-007-arraypool-span.md)) | +| Implicit state machines / typestate in flags & dates | [P-010](P-010-type-disciplines.md), [P-025](P-025-obligation-protocols.md) | +| Non-exhaustive handling hiding future cases | [P-010](P-010-type-disciplines.md) | +| Source-of-truth drift (config vs DI, XAML vs VM, EF vs migrations) | [P-015](P-015-configuration-surface.md), [P-023](P-023-architecture-guard.md) | +| Null-safety not enforced (`!`, opt-in NRT) | external analyzers via SARIF adapter; `NO-TOOL` until wired | + +## Non-goals (the most important section) + +- **No new heuristic detector.** No regex over `return null` / `throw`. If a + dimension has no reliable tool, it is `NO-TOOL`, not faked. (Same rule that + killed the security scanner engine in [P-024](P-024-security-audit-profile.md).) +- **Not an Option/Result migration linter.** We do not ship "rewrite `Customer?` + to `Option`" as a product. Suggesting union/`Result` remodels *as + prose in the report* is fine (it is the narrative); shipping an autofix/analyzer + pack for it is the SAST fight we refuse. +- **Not a generic nullable-annotation nag.** Roslyn's own analyzers + Sonar + already own "you used `!`". We surface null-safety posture only as a *dimension + of the score*, via existing tools, not as our own rule. +- **The score is not a vanity metric.** Every point of the 0–100 must be + reconstructable from listed findings + coverage. A dimension that did not run + lowers *confidence/coverage*, it does not silently read as "clean" (the audit + charter's honest-coverage rule). +- **No new language, no autofix arm here.** This is a lens over the audit; the + fix-arm lives where the audit's `fix/` layer lives. + +## The honest gap (do not paper over it) + +The analysis's demo — `own audit strictness MySolution.sln → Strictness score: +61/100` over an *arbitrary* solution — assumes a general C# semantic frontend. +That is not shipped: real-C# ingestion is the Roslyn extractor +([P-001](P-001-csharp-extractor.md)) + semantic resolution +([P-014](P-014-semantic-resolution.md)), both "in progress", scoped to the +lifetime/leak profiles, not to whole-solution strictness. So the first cut is +either: + +- **(a)** run over the existing audit target (the legacy WPF app the fleet + already ingests) and score *that*, or +- **(b)** ship the score frame with most dimensions honestly `NO-TOOL` and fill + them as extractor coverage lands. + +Both are charter-honest. What we must **not** do is print a green `61/100` whose +missing dimensions were silently treated as passing — that is the exact +dishonesty `audit/README.md` forbids. + +## Sketch + +```text +own audit strictness [--profile wpf|generic] [--baseline ] + → run fleet slice → SARIF (existing adapters) + → normalize → categorized findings (audit/aggregate/normalize.py) + → map to strictness dimensions + coverage state (new: thin mapping table) + → score → 0..100 + per-dimension breakdown, confidence from coverage + → render → strictness.md + strictness.json (new renderer over report.py) +``` + +Report head (illustrative): + +```markdown +# C# Strictness Audit — @ + +Strictness score: 61/100 (coverage: 4/6 dimensions; 2 NO-TOOL) + +## Where the type system is not carrying the invariant +1. 9 implicit state machines (Status + *At flags) [P-010/P-025 evidence] +2. 8 ambiguous IDisposable ownership transfers [OWN / D-series] +3. 31 mutable setters on domain records [NO-TOOL: partial] +... +``` + +Score = weighted sum over dimensions; each dimension's contribution is a function +of its findings and severity; `NO-TOOL` dimensions do not contribute points but +*cap displayed confidence*. Deterministic over a fixed commit (diffable, like +every audit run), so `--baseline` yields new/old/suppressed exactly as the +orchestrator already does. + +## Open questions + +- **Score model.** Absolute (findings → deductions) is honest but noisy across + codebases; a percentile against a corpus is friendlier but needs a corpus and + can lie about small repos. Start absolute + per-dimension, defer any headline + cross-repo comparison. +- **First target.** (a) the audit's WPF target (real, narrow) vs (b) frame-first + with `NO-TOOL` dimensions. Leaning (a): it produces a *real* score to show, + not a mostly-empty frame. +- **Where the mapping table lives.** It is audit-side config (dimension ← rule + ids), so it should sit with the orchestrator's category map, not in `ownlang/`. +- **Naming.** `own audit strictness` vs `own strictness`; whether "strictness" + reads as scolding — the report's job is to read as *diagnosis of hidden + invariants*, not style-nagging. + +## What we deliberately keep from the analysis, in one line + +The *framing* ("the domain model lies; the lifetime is unexpressed; the error is +hidden in an exception; the invariant survives on the honor system") and the +*doorway* (one score, one narrative for the F#/Rust-refugee reader) — laid over +the evidence the fleet already produces. Not a new analyzer; a lens that makes +the existing verdicts legible to someone who wants an S-tier language and hasn't +noticed Own.NET is quietly building the missing layer for the one they already use. diff --git a/docs/proposals/README.md b/docs/proposals/README.md index b382cd7f..dbb90ff3 100644 --- a/docs/proposals/README.md +++ b/docs/proposals/README.md @@ -45,6 +45,7 @@ proposal is marked `done` with a pointer. | [P-023](P-023-architecture-guard.md) | Architecture guard (`Own.Arch`): rules.yaml intent model + dependency-graph gate + baseline ratchet | draft | | [P-024](P-024-security-audit-profile.md) | Security audit profile (external tools + SARIF adapters; rejects own scanner engine) | draft | | [P-025](P-025-obligation-protocols.md) | Obligation protocols (`Own.Protocols`): barrier-sensitive project invariants (OBL001–005) | first slice built (core + bridge + fixtures; extractor pending) | +| [P-026](P-026-csharp-strictness-retrofit.md) | C# strictness retrofit profile (`own audit strictness`): a witness/score over existing findings, not a new engine | draft (framing) | > For priorities, milestones, the framing, and the design philosophy across all > of these, see the strategy hub: [`docs/ROADMAP.md`](../ROADMAP.md). P-004 … P-016 From 3f82fe7b0dc1d309e13515e4abea701447826d1f Mon Sep 17 00:00:00 2001 From: Claude Date: Sun, 5 Jul 2026 06:52:07 +0000 Subject: [PATCH 02/25] docs(proposals): add P-026, resource state machines & stale-async-write detection Distills a React-useEffect critique into the two ideas that generalize past React: modeling a resource's lifecycle as one explicit state instead of boolean/nullable soup, and detecting stale async writes where a value read before an await gates a write committed after it. Cross-references what P-004/P-005/P-020/P-021 already cover so this only adds the two genuinely new diagnostics (ASYNC050-052) rather than duplicating cleanup/blocking-wait/async-void checks. Co-Authored-By: Claude Sonnet 5 Claude-Session: https://claude.ai/code/session_01L5JLaXfYrZYrt4JG1fYJFA --- docs/ROADMAP.md | 3 +- .../proposals/P-026-resource-state-machine.md | 262 ++++++++++++++++++ docs/proposals/README.md | 1 + 3 files changed, 265 insertions(+), 1 deletion(-) create mode 100644 docs/proposals/P-026-resource-state-machine.md diff --git a/docs/ROADMAP.md b/docs/ROADMAP.md index 0cabf97f..9c86d63a 100644 --- a/docs/ROADMAP.md +++ b/docs/ROADMAP.md @@ -128,7 +128,7 @@ ownership/lifetime/effects, (4) an MVP needs no PhD in Roslyn. |------|---------|----------| | **P0** | WPF/event/timer/subscription leaks; `IDisposable` ownership (leaks, fields, use-after-dispose); DI lifetime mismatch (captive dependency) | [P-004](proposals/P-004-wpf-lifetime-profile.md), [P-005](proposals/P-005-idisposable-ownership.md), [P-006](proposals/P-006-di-lifetimes.md) | | **P1** | ArrayPool/Span ownership-view bugs; hidden effects / architecture rules | [P-007](proposals/P-007-arraypool-span.md), [P-008](proposals/P-008-effects-and-resources.md) | -| **P2** | async resource lifecycle / WPF async audit; `ValueTask` affine usage; typestate/protocols | [P-021](proposals/P-021-async-audit-pack.md), [P-008](proposals/P-008-effects-and-resources.md), [P-010](proposals/P-010-type-disciplines.md) | +| **P2** | async resource lifecycle / WPF async audit; `ValueTask` affine usage; typestate/protocols; resource state-machine soup + stale async writes | [P-021](proposals/P-021-async-audit-pack.md), [P-008](proposals/P-008-effects-and-resources.md), [P-010](proposals/P-010-type-disciplines.md), [P-026](proposals/P-026-resource-state-machine.md) | | **P3** | LOH fragmentation; static-collection memory bloat; cross-thread `ObjectDisposedException` | — (runtime-bound; see detectability matrix) | > **Are we showable yet?** The concrete "delicious .NET alpha" gate — the A–G bar @@ -326,3 +326,4 @@ own scan. Label them as estimates wherever they appear. | [P-020](proposals/P-020-ownts-react-effects.md) | OwnTS React effects profile (`Own.React`) — effect-storm angle | horizon | draft | | [P-021](proposals/P-021-async-audit-pack.md) | Async audit pack (`Own.Async`) — safety-first WPF/application async lifecycle diagnostics | P2 | draft | | [P-025](proposals/P-025-obligation-protocols.md) | Obligation protocols (`Own.Protocols`) — barrier-sensitive project invariants (OBL001–005) | P1 | first slice built (core + bridge + fixtures; extractor pending) | +| [P-026](proposals/P-026-resource-state-machine.md) | Resource state machines & stale-async-write detection (extends `Own.Async`) | P2 | draft | diff --git a/docs/proposals/P-026-resource-state-machine.md b/docs/proposals/P-026-resource-state-machine.md new file mode 100644 index 00000000..95bd8b91 --- /dev/null +++ b/docs/proposals/P-026-resource-state-machine.md @@ -0,0 +1,262 @@ +# P-026 — Resource state machines & stale-async-write detection (extends `Own.Async`) + +- **Status:** draft — proposal for discussion, not a committed design. Recorded on + the record so the idea isn't lost, not "we are building this next." +- **Depends on:** [P-021](P-021-async-audit-pack.md) (`Own.Async` — the sibling + catalog this slots into), [P-020](P-020-ownts-react-effects.md) (`Own.React` — + the OwnTS mirror of the same lifecycle shape), [P-010](P-010-type-disciplines.md) + (typestate/protocols — the heavier-weight cousin this deliberately does not + become), [P-004](P-004-wpf-lifetime-profile.md)/[P-005](P-005-idisposable-ownership.md) + (subscription/`IDisposable` cleanup — already-covered ground this reuses rather + than reinvents), [P-025](P-025-obligation-protocols.md) (obligation protocols — + a plausible home for the in-flight-guard check). + +## Origin and honest framing + +This proposal started from a critique of a React `useEffect` blog post. The post's +headline advice — "your code should always work without the dependency array" — +is wrong on its own terms: the dependency list is part of `useEffect`'s +synchronization contract with React (it's exactly what `react-hooks/exhaustive-deps` +checks), not an optional optimization to delete. But the critique's *real* kernel +is right, and it has nothing to do with React specifically: + +> A resource's lifecycle (empty / loading / available / failed) should be modeled +> as one explicit state, not reconstructed at read time from a pile of booleans +> and a nullable field that also means "not loaded yet." + +That kernel generalizes cleanly to any .NET code that loads a resource +asynchronously and drives state off ad hoc flags — WPF ViewModels, Presenters, +Blazor components, service classes: + +```csharp +private bool _isLoading; +private bool _isLoaded; +private Customer _customer; // null: not loaded? loading? load failed? + +public async Task LoadAsync() +{ + if (!_isLoaded && !_isLoading) + { + _isLoading = true; + _customer = await _repository.GetCustomerAsync(_customerId); + _isLoaded = true; + _isLoading = false; + } +} +``` + +This is the same animal P-020 already names for the OwnTS/React side (effect +re-entry, missing cleanup) and P-021 already partly covers for .NET async +(blocking waits, `async void`, task escaping a `using`/`finally`). What neither +proposal covers yet is the *state-shape* problem itself, and its most common +correctness consequence: a **stale write** when the resource identity changes +while the `await` is in flight. + +**What this proposal is not**: a rewrite of the source critique's tone or its +"always fail without the second `useEffect` argument" claim. Both are wrong and +out of scope. The only thing worth taking from that text is the two diagnostic +ideas below — everything else in it (dependency-array rhetoric, a proposed +runtime `ResourceState` helper as gospel) is either already someone else's job +in this project or not this project's job at all. + +## What's already covered elsewhere (do not reinvent) + +Following the "honest split" discipline P-020 already established, most of the +patterns in the source material map onto existing Own.NET ground: + +| Pattern in the source critique | Already covered by | +|---|---| +| Missing `-=`/`Dispose()`/`Unsubscribe()` for an event/timer/subscription | `OWN001` (P-004/P-005), and its OwnTS mirror `EFF003`–`EFF005` (P-020) | +| `Task` escaping a `using`/`try`-`finally` scope | `ASYNC001`/`ASYNC002` (P-021) | +| Blocking `.Result`/`.Wait()` on the UI thread | `ASYNC010` (P-021) | +| `async void` outside an event handler | `ASYNC020` (P-021) | +| Fire-and-forget without observation | `ASYNC030` (P-021) | +| Proving valid state *transitions* with consume-self semantics (a `Connection`-style protocol) | typestate/`protocol` blocks (P-010) — deliberately heavier machinery than this proposal wants | + +This proposal only adds the **two genuinely new** dimensions: state-shape +normalization, and stale-write protection. If it turns out either is just a +restatement of something above, that's a reason to fold it in, not to ship a +duplicate checker — the project's standing rule is one core, no parallel +verdict engines. + +## Scope — two new diagnostics + +### 1. Boolean/nullable state soup + +A resource's lifecycle is a small state space (`Empty`, `Loading`, `Available`, +`Failed` — sometimes `Cancelled`/`Refreshing`). Representing it as N independent +boolean or nullable fields lets the compiler accept states that shouldn't exist +(`IsLoading == true && IsLoaded == true`), and pushes every reader ("has this +loaded yet?") into reconstructing the state from field combinations instead of +reading one value. + +Detect: a cluster of ≥3 boolean/nullable-reference fields that are all assigned +inside the same async-loading method (heuristically: a method that both sets one +of the fields and `await`s something), read together in `if`/`&&` conditions +elsewhere in the same type. + +```csharp +// flagged: 4 fields modeling one lifecycle +private bool _isLoading; +private bool _isLoaded; +private Customer _customer; // doubles as "has value" via null +private Exception _loadError; +``` + +```text +ASYNC050: fields 'IsLoading', 'IsLoaded', 'Customer' (null-as-status), 'LoadError' +appear to model a single resource lifecycle as independent flags. Combinations +like IsLoading == true && IsLoaded == true are representable but meaningless. +Consider one explicit status (e.g. an enum: Empty/Loading/Available/Failed). +``` + +Suggested replacement is explanation-only in v0 (see Non-goals) — a comment +pointing at the shape, not an autofix: + +```csharp +private enum LoadStatus { Empty, Loading, Available, Failed } +private LoadStatus _status; +private Customer _customer; +private Exception _loadError; +``` + +### 2. Stale async write / missing in-flight guard + +The sharper bug: a value read *before* an `await` gates what gets written +*after* it, but nothing proves the world hasn't moved on while the method was +suspended. + +```csharp +public async Task LoadCustomerAsync() +{ + var customer = await _repository.GetCustomerAsync(_customerId); // _customerId read before await + CurrentCustomer = customer; // written after await +} +``` + +If `_customerId` changes (user picks a different row) while the first call is +still in flight, the stale response can overwrite the fresher one — or a second +concurrent call starts because nothing checked "am I already loading?" first. +This is the same failure class as the Cloudflare-dashboard effect-storm P-020 +already cites, just on the read/write-race axis instead of the re-trigger axis. + +Detect two sub-shapes: + +- **No in-flight guard (`ASYNC051`)**: an async loading method with no + early-return/guard against re-entry while a status field is already + `Loading`, and no synchronization (`SemaphoreSlim`, `lock` around a flag, + `CompareExchange`) preventing two concurrent calls from both proceeding. +- **No staleness check (`ASYNC052`)**: a field read before an `await` is used + (directly, or via a captured local) to decide what a field write after the + `await` commits, with no `CancellationToken`, no version/request-id + comparison, and no re-read/compare of the gating field after the `await`. + +```text +ASYNC051: 'LoadCustomerAsync' starts an async operation with no guard against +re-entry. If called again before the first call completes, both calls will +proceed concurrently. Guard on a status field (e.g. 'return if Status != Empty') +or a synchronization primitive before starting the operation. + +ASYNC052: 'LoadCustomerAsync' reads '_customerId' before the await and writes +'CurrentCustomer' after it, with no CancellationToken, version check, or +re-read of '_customerId' in between. If '_customerId' changes while this call +is in flight, a stale response can overwrite fresher state. +``` + +Both codes are heuristic/evidence-tiered, in the same spirit as P-021's +detectability matrix — high confidence when the guard/version-check is provably +absent syntactically, lower confidence (or silent) when a helper method or +`SemaphoreSlim` field exists nearby and the extractor can't yet prove it's used +correctly. + +## Non-goals + +- **Not a general data-race detector.** No happens-before model, no full + shared-mutable-state analysis. Scope stays "resource load method reads before + an `await`, writes after it, with no visible guard" — a syntactic pattern, not + a soundness proof. +- **No shipped runtime type.** Own.NET does not ship a `ResourceState` + NuGet package as the mandated fix, matching P-008/P-010's existing rule that + brands/refinements/protocols lower to plain structs the *team* owns. The + diagnostic explains the pattern; teams write their own status type (or adopt + whichever shape fits their codebase). +- **Not typestate.** Proving that `Loading → Available` is the *only* valid + transition, with consume-self semantics, is P-010's `protocol` block. This + proposal only flags "these fields look like an unmodeled lifecycle" — a lint, + not a proof. +- **No duplicate of `OWN001`/`ASYNC001`/`ASYNC002`/`ASYNC010`/`ASYNC020`/`ASYNC030`.** + Cleanup, blocking waits, `async void`, task-escape, and fire-and-forget stay + exactly where P-004/P-005/P-020/P-021 already put them. +- **No claim of catching every stale-write race.** Silent by default whenever a + synchronization primitive or cancellation token is present, even if its use + can't yet be proven correct — false negatives over false positives, same + posture as the rest of the async pack. + +## Sketch + +Same seam as the rest of the project — the Roslyn extractor emits facts, the +Python core emits verdicts: + +```text +*.cs --[Roslyn extractor]--> facts.ownir.json --[Python core]--> ASYNC050..052 +``` + +This most likely lands as three more hazard kinds in P-021's existing +`async_methods` fact family (`ownlang/async_rules.py`), rather than a new fact +block: + +```json +{ + "async_methods": [ + { + "id": "CustomerViewModel.LoadCustomerAsync", + "await_count": 1, + "reads_before_await": ["_customerId"], + "writes_after_await": ["CurrentCustomer"], + "has_cancellation_token": false, + "has_version_check": false, + "guarded_fields": [], + "sibling_status_fields": ["_isLoading", "_isLoaded", "_loadError"], + "hazards": [ + { "kind": "no_inflight_guard", "line": 40 }, + { "kind": "stale_write_no_guard", "line": 41, "read": "_customerId", "written": "CurrentCustomer" } + ] + } + ] +} +``` + +`sibling_status_fields` feeds `ASYNC050` (state-soup) independently of the +per-method hazards, since the flag cluster is a type-level observation (fields +assigned together across the type), not a single-method one. + +## Open questions + +1. **Home for the codes.** Extend P-021's `ASYNC0xx` family (leaning: yes — same + audience, same fact shape, same team owns both), or a separate prefix so + "state soup" doesn't read as an async-specific bug even when the loading path + happens to be synchronous? Leaning toward `ASYNC05x` since the motivating + cases are overwhelmingly async load methods. +2. **Threshold for state soup.** Two co-varying fields (`HasValue`/`Value`) are + completely ordinary and shouldn't fire. Where's the real line — 3 fields? A + specific combination like `bool + bool + nullable`? This needs corpus + evidence (P-012) rather than a guessed constant, same as P-021's own open + question 2 about severity thresholds. +3. **Staleness-check feasibility.** Proving "the gating field was re-read or a + version was compared between the pre-await read and the post-await write" + needs more than single-method syntax — it's adjacent to P-016's deep fact + extraction (CFG across the `await` boundary). Does `ASYNC052` start as a + narrower syntactic pattern (a specific field/property re-read immediately + before the write) and grow into real flow evidence later, same staging P-016 + already uses elsewhere? +4. **Does the in-flight guard belong to P-025 instead?** "Must not start a new + load while `Status == Loading`" is exactly a barrier-sensitive temporal + obligation — P-025's `OBL` machinery already models "call A before call B, + guarded by a barrier." `ASYNC051` may be P-025's first non-toy consumer + rather than a bespoke check; worth prototyping against `OBL` before writing a + parallel guard-detector. +5. **Suggested-fix surface.** Stay explanation-only (a diagnostic message + pointing at the shape) in v0, or eventually offer a code-fix snippet for the + `enum`-based replacement? Leaning: explanation-only until the pattern proves + out on real code, matching P-021's own "no mass autofix" stance for + `ASYNC040`. diff --git a/docs/proposals/README.md b/docs/proposals/README.md index b382cd7f..2da12c86 100644 --- a/docs/proposals/README.md +++ b/docs/proposals/README.md @@ -45,6 +45,7 @@ proposal is marked `done` with a pointer. | [P-023](P-023-architecture-guard.md) | Architecture guard (`Own.Arch`): rules.yaml intent model + dependency-graph gate + baseline ratchet | draft | | [P-024](P-024-security-audit-profile.md) | Security audit profile (external tools + SARIF adapters; rejects own scanner engine) | draft | | [P-025](P-025-obligation-protocols.md) | Obligation protocols (`Own.Protocols`): barrier-sensitive project invariants (OBL001–005) | first slice built (core + bridge + fixtures; extractor pending) | +| [P-026](P-026-resource-state-machine.md) | Resource state machines & stale-async-write detection (extends `Own.Async`) | draft | > For priorities, milestones, the framing, and the design philosophy across all > of these, see the strategy hub: [`docs/ROADMAP.md`](../ROADMAP.md). P-004 … P-016 From 809c902bf517535b6d3fd8d82ffb59589af3458e Mon Sep 17 00:00:00 2001 From: Claude Date: Sun, 5 Jul 2026 06:59:22 +0000 Subject: [PATCH 03/25] =?UTF-8?q?docs(proposals):=20P-026=20=E2=80=94=20sp?= =?UTF-8?q?lit=20ASYNC050's=20type-level=20fact=20from=20per-method=20haza?= =?UTF-8?q?rds?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit CodeRabbit review on #179: the sketch mixed a type-scoped fact (sibling status fields) into the per-method async_methods entries. Give it its own types[] fact block so the extractor contract for ASYNC050 (type-level) stays separate from ASYNC051/052 (per-method). Co-Authored-By: Claude Sonnet 5 Claude-Session: https://claude.ai/code/session_01L5JLaXfYrZYrt4JG1fYJFA --- .../proposals/P-026-resource-state-machine.md | 21 ++++++++++++------- 1 file changed, 14 insertions(+), 7 deletions(-) diff --git a/docs/proposals/P-026-resource-state-machine.md b/docs/proposals/P-026-resource-state-machine.md index 95bd8b91..3f2f40eb 100644 --- a/docs/proposals/P-026-resource-state-machine.md +++ b/docs/proposals/P-026-resource-state-machine.md @@ -201,12 +201,20 @@ Python core emits verdicts: *.cs --[Roslyn extractor]--> facts.ownir.json --[Python core]--> ASYNC050..052 ``` -This most likely lands as three more hazard kinds in P-021's existing -`async_methods` fact family (`ownlang/async_rules.py`), rather than a new fact -block: +`ASYNC051`/`ASYNC052` are per-method and most likely land as two more hazard +kinds in P-021's existing `async_methods` fact family +(`ownlang/async_rules.py`). `ASYNC050` is type-scoped — a cluster of fields +assigned together across the whole class, not one method — so it needs its own +type-level fact rather than living inside a method entry: ```json { + "types": [ + { + "id": "CustomerViewModel", + "status_field_clusters": [["_isLoading", "_isLoaded", "_loadError"]] + } + ], "async_methods": [ { "id": "CustomerViewModel.LoadCustomerAsync", @@ -216,7 +224,6 @@ block: "has_cancellation_token": false, "has_version_check": false, "guarded_fields": [], - "sibling_status_fields": ["_isLoading", "_isLoaded", "_loadError"], "hazards": [ { "kind": "no_inflight_guard", "line": 40 }, { "kind": "stale_write_no_guard", "line": 41, "read": "_customerId", "written": "CurrentCustomer" } @@ -226,9 +233,9 @@ block: } ``` -`sibling_status_fields` feeds `ASYNC050` (state-soup) independently of the -per-method hazards, since the flag cluster is a type-level observation (fields -assigned together across the type), not a single-method one. +`types[].status_field_clusters` feeds `ASYNC050` independently of the +per-method `hazards`, keeping the type-level and method-level extractor +contracts separate. ## Open questions From 01ff4b82912de9b5d4635370b11861c0fc71d3ac Mon Sep 17 00:00:00 2001 From: Claude Date: Sun, 5 Jul 2026 07:00:08 +0000 Subject: [PATCH 04/25] docs(proposals): P-026 unneeded-dependency profile (Own.Lean) Evidence-only "you don't need this abstraction" checks, inspired by the you-dont-need/You-Dont-Need meta-list but scoped away from opinion: each finding names a call site with zero customization evidence (a trivial AutoMapper 1:1 profile, a MediatR handler with one impl and no pipeline behaviours) rather than judging a library's presence. Report-only, never a build gate; bounded against P-021 (async elision already owned by ASYNC040) and P-023 (Own.Arch gates forbidden structure, this flags redundant structure). Co-Authored-By: Claude Sonnet 5 Claude-Session: https://claude.ai/code/session_01NNLSE5z4V92RwdHHF1mW45 --- docs/ROADMAP.md | 1 + .../P-026-unneeded-dependency-profile.md | 140 ++++++++++++++++++ docs/proposals/README.md | 1 + 3 files changed, 142 insertions(+) create mode 100644 docs/proposals/P-026-unneeded-dependency-profile.md diff --git a/docs/ROADMAP.md b/docs/ROADMAP.md index 0cabf97f..5c69d896 100644 --- a/docs/ROADMAP.md +++ b/docs/ROADMAP.md @@ -326,3 +326,4 @@ own scan. Label them as estimates wherever they appear. | [P-020](proposals/P-020-ownts-react-effects.md) | OwnTS React effects profile (`Own.React`) — effect-storm angle | horizon | draft | | [P-021](proposals/P-021-async-audit-pack.md) | Async audit pack (`Own.Async`) — safety-first WPF/application async lifecycle diagnostics | P2 | draft | | [P-025](proposals/P-025-obligation-protocols.md) | Obligation protocols (`Own.Protocols`) — barrier-sensitive project invariants (OBL001–005) | P1 | first slice built (core + bridge + fixtures; extractor pending) | +| [P-026](proposals/P-026-unneeded-dependency-profile.md) | Unneeded-dependency profile (`Own.Lean`) — evidence-only "you don't need this abstraction" findings (YDN001–002) | P2 | draft | diff --git a/docs/proposals/P-026-unneeded-dependency-profile.md b/docs/proposals/P-026-unneeded-dependency-profile.md new file mode 100644 index 00000000..a2b2bf51 --- /dev/null +++ b/docs/proposals/P-026-unneeded-dependency-profile.md @@ -0,0 +1,140 @@ +# P-026 — Unneeded-dependency profile (`Own.Lean`) + +- **Status:** draft — not started. +- **Depends on:** [P-001](P-001-csharp-extractor.md) (the Roslyn extractor + seam), [P-006](P-006-di-lifetimes.md) (the DI `services[]` registration + graph — YDN002 reuses it as-is, no new extractor work), [P-015](P-015-configuration-surface.md) + (severity/opt-in surface for the phase-2 heuristics). Bounded explicitly + against [P-021](P-021-async-audit-pack.md) (`ASYNC040` already owns the + "trivial async passthrough" case — not duplicated here) and + [P-023](P-023-architecture-guard.md) (Own.Arch gates *forbidden* structure; + this profile flags *provably redundant* structure — different verdict shape, + never a build gate). + +## Motivation + +The `you-dont-need/You-Dont-Need` meta-list (a curated collection of +"You Might Not Need Lodash/Moment/Redux/…" write-ups) makes one real point +under all the individual takes: teams often reach for a popular dependency +because it is popular, not because the problem in front of them needs it. The +honest version of that point is not "dependencies are bad" — it is that every +dependency has to clear a bar: + +```text +dependency_value > dependency_cost +``` + +where cost is never just install size — it is maintenance, transitive CVEs, +build complexity, onboarding, and the debugging friction of an indirection +layer nobody on the team wrote. .NET has its own instances of the same +pattern: an `AutoMapper` profile that copies five identically-named properties +and nothing else, a `MediatR` handler with exactly one implementation and no +pipeline behaviours standing in for a direct method call. The libraries are +not the problem — using them where they buy nothing is. + +The trap is that "you don't need X" is trivially easy to turn into an +opinionated hot-take generator (see the source list's own "You Might Not Need +TypeScript" entry) that flags a library's mere presence. That is exactly the +kind of noisy, ungrounded quality gate this project's other proposals +deliberately reject (see P-023's "no SOLID detector" stance). So the scope +here is narrower and stricter than the inspiration: + +> **Own.Lean never passes judgment on a library. It flags one call site at a +> time, only when the code at that site proves the abstraction added nothing +> — and the moment any real customization is visible, it stays silent.** + +## Scope + +### MVP — deterministic, evidence-only + +| Code | Finding | Evidence required | Suggestion | +|------|---------|--------------------|------------| +| `YDN001` | `AutoMapper` `CreateMap()` (or `Profile`-declared map) that is a pure 1:1 copy | every public writable member of `TDest` has an exact-name, assignable-type public readable counterpart on `TSrc`; **no** `.ForMember`/`.Ignore`/`.ConvertUsing`/custom value resolver/`.ReverseMap`; member count within a configurable bound (default 10) | replace with an explicit object initializer or a mapping constructor | +| `YDN002` | `MediatR` `IRequestHandler` resolved via `ISender`/`IMediator` with exactly one registered implementation and zero registered `IPipelineBehavior<,>` (open or closed) anywhere in the DI graph | reuses the P-006 `services[]` registration-graph fact directly — no new extractor pass | inject the handler directly instead of dispatching through the mediator | + +Both rules are structurally the same shape already used for `DI001` +(P-006): read a graph the extractor already builds, compare cardinalities and +declared customization, emit a verdict only when the customization set is +empty. Neither rule inspects call-site *style* — only the declared shape of +the mapping/registration. + +### Phase 2 — heuristic, report-only, opt-in via P-015 + +| Code | Finding | Confidence | +|------|---------|------------| +| `YDN010` | A DI-registered service with exactly one registration across the whole solution, not exposed as a public extension point, and never re-registered in a test project | heuristic — a real single-impl service and a "this interface is pure ceremony" service look identical without knowing intent; ships as report-only or not at all | + +This tier stays report-only, never a build gate, and is the honest limit of +what this profile should attempt — see Non-goals for the parts of the "You +Don't Need" list that were deliberately left out rather than downgraded to +Phase 2. + +## Non-goals + +- **No library blocklist.** "Don't use Lodash/Axios/Moment" has no .NET + analogue that would be evidence rather than opinion, and even in spirit, + Own.Lean does not ship a list of disfavoured packages. Every finding names a + specific call site and the specific evidence at it. +- **No "replace the ORM with hand-written SQL" suggestion.** Whether a + hand-rolled query beats an ORM call depends on performance requirements this + tool cannot observe statically. Not evidence-based; not built. +- **No reflection → source-generator suggestion.** There is no oracle for + "this reflection could have been codegen'd" short of writing the generator — + guesswork, not a finding. +- **No JSON → MessagePack/binary-format suggestion.** A wire-format choice + depends on external constraints (interop, human-readability requirements) + invisible to static analysis. +- **No reimplementation of existing Roslyn/FxCop LINQ micro-optimizations** + (`.Where(p).Count()` → `.Count(p)` and siblings — already `CA1826`/`CA1827`/ + `CA1828`/`CA1829`). Own.NET's differentiator is checks nobody else runs, not + a third copy of a rule two analyzers already ship. +- **No build-blocking severity, ever, for this family.** Every `YDN###` is + info/warning and never wired into the P-023 architecture-guard ratchet. A + false positive here costs a reviewer one comment, not a red PR. +- **No bundle-size / transitive-CVE / dependency-count scoring.** That is a + supply-chain audit tool (NuGet advisory scanning, dependency-graph size), + a different project; if ever pursued it is its own proposal, not folded in + here. +- **No hostility to AutoMapper or MediatR as libraries.** Both are legitimate + the moment they are used for what they are for — custom resolvers, cross- + cutting pipeline behaviours, polymorphic dispatch over many handlers. + `YDN001`/`YDN002` are silent the instant any of that evidence appears. + +## Sketch + +```text +C# source --[Roslyn extractor]--> mapping-profile facts (YDN001) + \-> services[] graph (already built for P-006, reused as-is for YDN002) + | + [core: same Python seam] + | + YDN### verdicts --> SARIF + markdown +``` + +`YDN001` needs one new extractor fact family: for each `CreateMap` +call (or `Profile`-declared map), emit the two member lists plus whichever +customization calls (`.ForMember`, `.Ignore`, `.ConvertUsing`, `.ReverseMap`) +appear in the same fluent chain. `YDN002` needs **no new extraction at all** — +it is a second query over the P-006 `services[]` graph the extractor already +emits, checking handler-implementation cardinality and pipeline-behaviour +registration count instead of captive-lifetime edges. + +## Open questions + +1. Where does the "still trivial" member-count bound for `YDN001` live — + hardcoded default, or a P-015 per-project knob? Leaning: a default with a + P-015 override, consistent with how severity is already configured + elsewhere. +2. Does `YDN002` also need to inspect the handler body for inline cross- + cutting code (logging/validation) that a pipeline behaviour would normally + own, or is DI-graph evidence (impl count + behaviour count) sufficient on + its own? Needs a trial against a real MediatR-using corpus sample. +3. Naming/positioning: a standalone `Own.Lean` family, or a phase-4 "ceremony" + tier under `Own.Arch` (P-023)? Leaning: standalone — P-023 gates *forbidden* + structure (a graph-edge violation); this profile flags *provably redundant* + structure (an indirection with zero customization). The verdict shapes + differ (a gate vs. a suggestion), which argues for keeping them separate + families sharing only the extractor seam. +4. Prefix: following the `ASYNC`/`ARCH`/`OBL` precedent of a family-specific + code rather than overloading `OWN###` — `YDN###` as proposed above, unless + a shorter/clearer prefix surfaces during naming review. diff --git a/docs/proposals/README.md b/docs/proposals/README.md index b382cd7f..1cca39fc 100644 --- a/docs/proposals/README.md +++ b/docs/proposals/README.md @@ -45,6 +45,7 @@ proposal is marked `done` with a pointer. | [P-023](P-023-architecture-guard.md) | Architecture guard (`Own.Arch`): rules.yaml intent model + dependency-graph gate + baseline ratchet | draft | | [P-024](P-024-security-audit-profile.md) | Security audit profile (external tools + SARIF adapters; rejects own scanner engine) | draft | | [P-025](P-025-obligation-protocols.md) | Obligation protocols (`Own.Protocols`): barrier-sensitive project invariants (OBL001–005) | first slice built (core + bridge + fixtures; extractor pending) | +| [P-026](P-026-unneeded-dependency-profile.md) | Unneeded-dependency profile (`Own.Lean`): evidence-only "you don't need this abstraction" findings (YDN001–002) | draft | > For priorities, milestones, the framing, and the design philosophy across all > of these, see the strategy hub: [`docs/ROADMAP.md`](../ROADMAP.md). P-004 … P-016 From 9cd10b8767f1305485bafcb1ca4ee39c546e3cb2 Mon Sep 17 00:00:00 2001 From: Claude Date: Sun, 5 Jul 2026 07:05:57 +0000 Subject: [PATCH 05/25] docs(P-026): YDN002 needs new generic-preserving DI facts, not a free reuse Codex review on #180 caught it: the extractor's DiTypeName collapses IRequestHandler to the bare IRequestHandler identifier, so the existing P-006 services[] graph can't distinguish per-request-type implementation counts once a project has more than one MediatR handler. Correct the MVP scope and sketch to require a small additive extractor change that preserves closed generic arguments for MediatR marker interfaces, instead of claiming YDN002 rides the graph as-is. Co-Authored-By: Claude Sonnet 5 Claude-Session: https://claude.ai/code/session_01NNLSE5z4V92RwdHHF1mW45 --- .../P-026-unneeded-dependency-profile.md | 39 +++++++++++++------ 1 file changed, 27 insertions(+), 12 deletions(-) diff --git a/docs/proposals/P-026-unneeded-dependency-profile.md b/docs/proposals/P-026-unneeded-dependency-profile.md index a2b2bf51..820bf054 100644 --- a/docs/proposals/P-026-unneeded-dependency-profile.md +++ b/docs/proposals/P-026-unneeded-dependency-profile.md @@ -3,7 +3,8 @@ - **Status:** draft — not started. - **Depends on:** [P-001](P-001-csharp-extractor.md) (the Roslyn extractor seam), [P-006](P-006-di-lifetimes.md) (the DI `services[]` registration - graph — YDN002 reuses it as-is, no new extractor work), [P-015](P-015-configuration-surface.md) + graph — YDN002 extends its facts with the closed generic arguments the + existing graph collapses away, see Sketch), [P-015](P-015-configuration-surface.md) (severity/opt-in surface for the phase-2 heuristics). Bounded explicitly against [P-021](P-021-async-audit-pack.md) (`ASYNC040` already owns the "trivial async passthrough" case — not duplicated here) and @@ -50,13 +51,14 @@ here is narrower and stricter than the inspiration: | Code | Finding | Evidence required | Suggestion | |------|---------|--------------------|------------| | `YDN001` | `AutoMapper` `CreateMap()` (or `Profile`-declared map) that is a pure 1:1 copy | every public writable member of `TDest` has an exact-name, assignable-type public readable counterpart on `TSrc`; **no** `.ForMember`/`.Ignore`/`.ConvertUsing`/custom value resolver/`.ReverseMap`; member count within a configurable bound (default 10) | replace with an explicit object initializer or a mapping constructor | -| `YDN002` | `MediatR` `IRequestHandler` resolved via `ISender`/`IMediator` with exactly one registered implementation and zero registered `IPipelineBehavior<,>` (open or closed) anywhere in the DI graph | reuses the P-006 `services[]` registration-graph fact directly — no new extractor pass | inject the handler directly instead of dispatching through the mediator | +| `YDN002` | `MediatR` `IRequestHandler` resolved via `ISender`/`IMediator` with exactly one registered implementation and zero registered `IPipelineBehavior<,>` (open or closed) anywhere in the DI graph | needs the DI registration graph's generic arguments preserved per `IRequestHandler` registration (see Sketch — today's graph collapses these) | inject the handler directly instead of dispatching through the mediator | -Both rules are structurally the same shape already used for `DI001` -(P-006): read a graph the extractor already builds, compare cardinalities and -declared customization, emit a verdict only when the customization set is -empty. Neither rule inspects call-site *style* — only the declared shape of -the mapping/registration. +`YDN001` is structurally the same shape already used for `DI001` (P-006): read +a graph the extractor already builds, compare cardinalities and declared +customization, emit a verdict only when the customization set is empty. +`YDN002` needs the same shape but over a graph the extractor does not yet +build in the needed resolution — see Sketch. Neither rule inspects call-site +*style* — only the declared shape of the mapping/registration. ### Phase 2 — heuristic, report-only, opt-in via P-015 @@ -104,7 +106,7 @@ Phase 2. ```text C# source --[Roslyn extractor]--> mapping-profile facts (YDN001) - \-> services[] graph (already built for P-006, reused as-is for YDN002) + \-> services[] graph, extended with closed generic args (YDN002) | [core: same Python seam] | @@ -114,10 +116,23 @@ C# source --[Roslyn extractor]--> mapping-profile facts (YDN001) `YDN001` needs one new extractor fact family: for each `CreateMap` call (or `Profile`-declared map), emit the two member lists plus whichever customization calls (`.ForMember`, `.Ignore`, `.ConvertUsing`, `.ReverseMap`) -appear in the same fluent chain. `YDN002` needs **no new extraction at all** — -it is a second query over the P-006 `services[]` graph the extractor already -emits, checking handler-implementation cardinality and pipeline-behaviour -registration count instead of captive-lifetime edges. +appear in the same fluent chain. + +`YDN002` is **not** a free ride on the existing P-006 `services[]` graph, and +the MVP scope above was wrong to claim otherwise (caught in review): the +extractor's `DiTypeName` helper +(`frontend/roslyn/OwnSharp.Extractor/Program.cs`) deliberately reduces a +generic registration to its rightmost identifier — `IRequestHandler` +and `IRequestHandler` both become the bare `IRequestHandler` — because +P-006's captive-lifetime checks never needed to distinguish closed generic +arguments. `YDN002` does need that distinction: counting "implementations of +*this* `TReq[,TResp]`" from an identifier-only graph would silently count +every unrelated handler in the solution as the same bucket the moment a +project has more than one MediatR request. The fix is a small, additive +extractor change — preserve the closed type-argument pair (and the +`IPipelineBehavior<,>` type arguments, open or closed) alongside the existing +service/impl identifiers when the generic is one of the MediatR marker +interfaces — not a reinterpretation of the current collapsed facts. ## Open questions From 9cf6411161a971d90d76dfa407506a3a430321ed Mon Sep 17 00:00:00 2001 From: Claude Date: Sun, 5 Jul 2026 07:09:49 +0000 Subject: [PATCH 06/25] docs(proposals): P-026 agent memory & policy layer (.agents/) Defines the reviewed, structured destination for AGENTS.md content and learned-rule promotions: split AGENTS.md into an index over .agents/*.md satellite files, adopt .007/gate.toml as the machine-readable gate manifest, and fix a promotion contract (provenance + normal PR review, no auto-write). Companion to the reflect/learning-engine design proposed in the sibling private repo 007 (docs/reflect.md there), which is one possible source of promotions into this layer. Claude-Session: https://claude.ai/code/session_01RpcmoaVnkTzWmzEJjN3Hrq --- docs/proposals/P-026-agent-memory-layer.md | 204 +++++++++++++++++++++ docs/proposals/README.md | 1 + 2 files changed, 205 insertions(+) create mode 100644 docs/proposals/P-026-agent-memory-layer.md diff --git a/docs/proposals/P-026-agent-memory-layer.md b/docs/proposals/P-026-agent-memory-layer.md new file mode 100644 index 00000000..6a48449b --- /dev/null +++ b/docs/proposals/P-026-agent-memory-layer.md @@ -0,0 +1,204 @@ +# P-026 — Agent memory & policy layer (`.agents/`) + +- **Status:** draft — design only, no code/directory changes shipped by this + proposal. +- **Depends on:** nothing structurally; it formalizes conventions already used + elsewhere in this repo (see Sketch). Consumed by, but does not depend on, the + reflect/learning-engine design in the sibling private repo `PhysShell/007` + (`docs/reflect.md` there) — that engine is one possible *source* of + promotions into the layer this proposal defines; a human editing `AGENTS.md` + by hand is another, equally valid, source. + +## Motivation + +`AGENTS.md` today is one flat 18-line file covering core commands, the lint +gate, the pipeline shape, and a handful of hard rules (unknown-call handling, +`assert_never` dispatch sites, codegen modes, OwnIR versioning). That is +exactly the right size for what it covers today. The risk is what happens as +it grows: this repo already carries 25 proposals and 30+ design notes under +`docs/`, plus a second, much longer file +(`AGENTS.execution-surfaces.md`, 14KB) for a single ADR. Two failure modes are +already visible in miniature: + +1. **One file bloats past readability.** An agent-guidance file that grows by + accretion (one more bullet per lesson learned) turns into the "corporate + Confluence after three reorgs" problem — long enough that neither a human + nor an agent reliably reads all of it before acting. +2. **Guidance scatters undiscoverably.** `docs/notes/` already holds + agent-relevant lessons (e.g. `docs/notes/field-notes-patterns.md`, + `docs/notes/agent-capability-layer.md`) that a coding agent has no reason to + load unless it happens to grep for them. + +Separately, an external tool +([`claude-reflect`](https://github.com/BayramAnnakov/claude-reflect)) and the +sibling private harness `007` (which drives `claude`/`codex` over this repo +from the outside — see `007`'s own `README.md`) both converge on the same +idea: corrections and repeat-failure patterns from real agent runs should +become reviewed, persistent project memory rather than being re-learned every +session. `007` is explicitly the place that *mines* run records for candidate +learnings (`docs/reflect.md` in that repo) — it is private, and its own +`README.md` is explicit that harness-internal reasoning must never land in a +public tree. What this repo needs, independent of whether 007 ever ships that +engine, is: **a defined, reviewed, structured place those promotions — or a +human's own manual corrections — land in.** That is this proposal's entire +scope: the destination shape, not a detector. + +## Scope + +### The directory + +```text +AGENTS.md # short index — points into .agents/*.md, nothing else +.agents/ + commands.md # how to run check/emit/cfg/report, tests, lint — today's AGENTS.md body + invariants.md # hard rules: unknown-call handling, assert_never sites, OwnIR versioning + gates.md # prose description of what CI/agents must run and why + codegen.md # the two codegen modes; "do not add runtime released? flags" + frontend-roslyn.md # Roslyn extractor rules: bin/ refs, OWN050 advisory, facts-only boundary +.007/ + gate.toml # machine-readable mirror of gates.md — see below +``` + +`AGENTS.md` shrinks to an index in the same shape this repo already uses for +`docs/proposals/README.md` (a table of what exists and one line of status) and +`docs/ROADMAP.md` (a hub linking to satellite documents). This is not a new +idiom for the repo — it is applying the hub-and-satellite pattern the repo +already relies on to agent guidance specifically, instead of one growing file. + +Each `.agents/*.md` file gets a soft line budget (~150 lines, the same +heuristic `claude-reflect` uses to warn on oversized memory files). Once a +file would cross that budget, it splits — same discipline that already +produced 25 separate proposals instead of one `PROPOSALS.md`. + +### `.007/gate.toml` + +This repo does not yet have a `.007/` directory. `007`'s `o7 run` already +looks for `/.007/gate.toml` by convention and ships a worked example at +`007/examples/gate.own.net.toml` (three steps: `ruff check .`, `mypy ownlang`, +`python tests/run_tests.py` — a direct read of the current `AGENTS.md` lint +and regression rules). Adopting it here means: + +```toml +schema = 1 + +[[gate]] +name = "ruff" +cmd = "ruff check ." +required = true + +[[gate]] +name = "mypy-ownlang" +cmd = "mypy ownlang" +required = true + +[[gate]] +name = "regression" +cmd = "python tests/run_tests.py" +required = true +``` + +`gates.md` is then prose *about* this file (why each gate exists, what to run +when only touching a subset), not a competing source of truth — see the +open question on generation below. + +### The promotion contract + +Whatever proposes a change to `.agents/*.md` — a human noticing a repeat +correction, or an accepted candidate out of `007`'s (separate, private) +reflect queue — must arrive as an ordinary reviewed PR carrying: + +- **the rule itself**, scoped to one `.agents/*.md` file and section; +- **provenance**: what motivated it (a run, a PR comment, a postmortem) — + free text is enough, this is not a machine-checked field; +- **no bypass of normal review**. This repo does not gain a direct-write or + auto-merge path for agent memory. A promotion patch is a diff like any + other; it goes through the same PR process as this proposal itself. + +## Non-goals + +- **No detector/queue/regex-capture pipeline lives here.** Mining agent runs + for candidate learnings is explicitly `007`'s concern (private, separate + repo) or a human's own judgment — never a component added to this repo. +- **No live prompt-capture hook** (claude-reflect's `UserPromptSubmit` + mechanism). This repo has no chat surface to hook, and does not gain one for + this purpose. +- **No unreviewed auto-write.** Every change to `.agents/*.md` or + `.007/gate.toml` is a normal, human-reviewed commit — the same bar as any + other source change in this repo. +- **No cross-project memory.** `.agents/` describes this repo only; it is not + a place to accumulate generic "how agents should behave" advice that belongs + in a user's own global config. +- **No new rule DSL.** `.agents/*.md` is prose for humans and agents to read, + same register as the existing `AGENTS.md`; `.007/gate.toml` is the one + machine-readable artifact, and it already has an owner (`007`'s + `GateManifest` parser) — this proposal does not invent a second one. +- **Not a replacement for `.cursor/rules` or `.roo/rules-*`.** Those already + exist for tool-specific surfaces; this proposal does not touch them (see + open question below on whether they should later generate *from* + `.agents/`, not the reverse). + +## Sketch + +Today's `AGENTS.md` maps onto the split almost line-for-line, which is a good +sign the split is carving at a real joint rather than inventing one: + +| Current `AGENTS.md` line | Destination | +| --- | --- | +| `python -m ownlang check\|emit\|cfg\|report` usage | `.agents/commands.md` | +| `tests/run_tests.py` / `test_codegen_props.py` invocation | `.agents/commands.md` | +| `ruff check .` + `mypy` gate | `.agents/gates.md` (+ `.007/gate.toml`) | +| Ruff SIM omission rationale | `.agents/gates.md` | +| Pipeline shape (parser → CFG → analyses → diagnostics) | `.agents/invariants.md` | +| Unknown-call hard-error rule | `.agents/invariants.md` | +| `assert_never` dispatch-site rule | `.agents/invariants.md` | +| Codegen's two modes / no runtime flags | `.agents/codegen.md` | +| OwnIR schema versioning rule | `.agents/invariants.md` | +| `own-check.sh`/`.ps1`, `--flow-locals` default | `.agents/frontend-roslyn.md` | +| Roslyn `bin/` refs / OWN050 | `.agents/frontend-roslyn.md` | +| `audit/` decoupling note | `.agents/invariants.md` (one line, pointer to `audit/README.md`) | +| CodeGraph MCP preference | `.agents/commands.md` | + +`AGENTS.md` becomes: + +```markdown +# AGENTS.md + +Guidance for agents working in this repo. Start here, then follow a link: + +| File | Covers | +|---|---| +| `.agents/commands.md` | How to run check/emit/cfg/report, tests, lint | +| `.agents/gates.md` | What must pass before a change lands, and why | +| `.agents/invariants.md` | Hard rules that must not be violated | +| `.agents/codegen.md` | Codegen modes and constraints | +| `.agents/frontend-roslyn.md` | Roslyn extractor rules and boundaries | +``` + +## Open questions + +1. **Should `.agents/gates.md` be generated from `.007/gate.toml`, or hand + written?** P-023 (`Own.Arch`) already rejects the "two parrots" pattern for + C4 diagrams vs. `rules.yaml` — the same logic applies here: if `gates.md` + drifts from what `gate.toml` actually runs, agents get told one thing and + CI does another. Leaning: `gate.toml` is the source of truth; `gates.md` + carries only the *why*, with a generated table of the *what* (name + cmd) + checked in CI so drift fails loudly rather than rotting silently. +2. **Does the promotion contract need its own proposal**, or does it stay a + section of this one? Leaning: stays here until there's a second consumer + besides `007`'s reflect design — one contract, one place, until proven + otherwise. +3. **Should `.cursor/rules/` and `.roo/rules-*/` eventually generate from + `.agents/`** instead of maintaining separate tool-specific prose? Not in + scope for this proposal's MVP, but the same "single source of truth, many + renderers" instinct that shaped the `.007/gate.toml` question applies. Left + for a later proposal once `.agents/` exists and the duplication is real + (not hypothetical). +4. **Line-budget enforcement:** soft convention (reviewers watch for it) or a + CI check (`wc -l` gate on `.agents/*.md`, matching the "warn past ~150 + lines" heuristic)? Leaning: start as a soft PR-review convention; only add + a mechanical gate if bloat actually recurs — no gate for a problem that + hasn't happened yet. +5. **Timing relative to `007`'s reflect engine:** this proposal's directory + layout is useful on its own (splitting an already-growing `AGENTS.md`) + regardless of whether `007`'s mining engine ever ships. It should not block + on that design landing first. diff --git a/docs/proposals/README.md b/docs/proposals/README.md index b382cd7f..faa0f9ec 100644 --- a/docs/proposals/README.md +++ b/docs/proposals/README.md @@ -45,6 +45,7 @@ proposal is marked `done` with a pointer. | [P-023](P-023-architecture-guard.md) | Architecture guard (`Own.Arch`): rules.yaml intent model + dependency-graph gate + baseline ratchet | draft | | [P-024](P-024-security-audit-profile.md) | Security audit profile (external tools + SARIF adapters; rejects own scanner engine) | draft | | [P-025](P-025-obligation-protocols.md) | Obligation protocols (`Own.Protocols`): barrier-sensitive project invariants (OBL001–005) | first slice built (core + bridge + fixtures; extractor pending) | +| [P-026](P-026-agent-memory-layer.md) | Agent memory & policy layer (`.agents/`): reviewed destination for AGENTS.md, gates, and learned-rule promotions | draft | > For priorities, milestones, the framing, and the design philosophy across all > of these, see the strategy hub: [`docs/ROADMAP.md`](../ROADMAP.md). P-004 … P-016 From c66df2ba890b961f84feb36e54dd4a5144cef6ae Mon Sep 17 00:00:00 2001 From: Claude Date: Sun, 5 Jul 2026 07:12:19 +0000 Subject: [PATCH 07/25] Record CUE-over-TOML decision for owen.policy authoring MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Appends §8 to the Owen Gate design note: TOML's lack of merge semantics makes multi-profile policy (no-net / worktree-only / windows exec) copy-paste-prone, where a forgotten override silently reopens a denied capability. CUE's unification model turns a conflicting override into a compile error instead. Records Nickel as runner-up and Jsonnet/Dhall/HCL as considered-and-rejected, and reaffirms WIT/Wasmtime stays the untrusted-input plugin boundary, not a policy-authoring surface — cross-referenced from 007's zero-trust-framework.md. --- docs/notes/agent-capability-layer.md | 60 ++++++++++++++++++++++++++++ 1 file changed, 60 insertions(+) diff --git a/docs/notes/agent-capability-layer.md b/docs/notes/agent-capability-layer.md index 7924d7bd..313bdccd 100644 --- a/docs/notes/agent-capability-layer.md +++ b/docs/notes/agent-capability-layer.md @@ -161,3 +161,63 @@ generated output of one policy engine, not your architecture. 3. **Policy ⇄ Sandboy overlap** — how much of `[exec]`/`[network]` should be *compiled down* into a Sandboy policy (real enforcement) vs stay advisory context? Ideally `owen policy` emits a `sandboy` policy for mode (B). + +--- + +## 8. Addendum (2026-07-05): authoring language for `owen.policy` — CUE, not TOML + +§3 sketched `owen.policy.toml` as a single flat file. That's fine while there is +one profile. It stops being fine the moment there is more than one — `no-net`, +`worktree-only`, a `windows`-tagged exec profile, a `trusted-repo` vs +`untrusted-repo` split — because those need to **compose** ("inherit the base, +add these steps"), and TOML has no merge semantics of its own. Composing TOML by +hand means copy-pasting the base into every profile, and a copy that forgets +`network.default = "deny"` is precisely the failure mode this whole layer exists +to prevent — a config bug that reads as a permission grant. + +**Decision: author `owen.policy` in [CUE](https://cuelang.org).** The reason to +prefer it over "TOML + a templating layer" is CUE's *unification* model: a +parent and a child don't override each other, they unify, and unification is a +**compile error** if they disagree. A leaf profile that tries +`network: "allow"` against a base that says `network: "deny"` doesn't silently +win — it fails to build. That is a materially different guarantee than +inheritance-with-override (Terragrunt-style merge, Jsonnet `+`), where the leaf +always wins and a mistaken override ships silently. + +```text +policies/ + no-net.cue # network: "deny" — the floor, never overridden + worktree-only.cue # repo.read/write confined to the worktree + default-processes.cue # exec allowlist +gates/ + own-net.cue # unifies the policies above + step list + own-net.windows.cue # must *explicitly* switch to a different process + # profile to add e.g. `powershell` — can't inherit a + # denylist that silently forgot it +``` + +Compiled down to flat JSON for whatever actually enforces it at runtime (the +Sandboy policy, `owen policy check`'s consumer) — the authoring layer is for +humans; the enforcement point should stay a boring, strict parser with no CUE +evaluation at run time. + +Runner-up: **Nickel** (`import` + record merge via `&`, typed contracts) — a +reasonable second choice if the policy ever wants functions or generated +defaults; picked CUE first specifically because a security floor benefits more +from "conflicts are hard errors" than from programmability. + +**Rejected for this use** (fine tools, wrong fit for a security source of +truth): **Jsonnet** (`+`/`super` composition is generative — right for stamping +out many manifests, wrong posture for policy, and a silent-override bug is just +as easy as in TOML with fancier syntax); **Dhall** (safe and total, but more +ergonomic weight than this scale needs — CUE gets the same "disagreement is an +error" property more cheaply); **HCL/Terragrunt** (`include` + `merge_strategy` +gives structural inheritance, but drags in Terraform's whole tooling/mental +model for a project that has nothing to do with infrastructure deployment). + +**This does not change §0/§1.** WIT/Wasmtime stays the *execution* boundary for +tool components that parse untrusted input (already spiked as `audit/adapters`, +per `sandboy-isolation-adr.md` §6's update) — it is not a candidate for policy +*authoring*. The two axes stay separate: CUE composes the data, WIT/Sandboy +enforce it. See `007/docs/zero-trust-framework.md` for how 007 concretely +consumes a CUE-authored policy as `.007/gate.lock.json`. From c84f3667b678cf7c5de2c2f144e0ae4b7ad71ffb Mon Sep 17 00:00:00 2001 From: Claude Date: Sun, 5 Jul 2026 07:17:45 +0000 Subject: [PATCH 08/25] Point Sandboy's policy doc at the CUE authoring pipeline sandboy/README.md's policy.example.toml is the plain, boring artifact Sandboy actually reads; adds a pointer to where the composable CUE source and the cue export --out toml render step are documented (007's zero-trust-framework.md), so the two stay clearly separated instead of drifting apart in two repos. --- sandboy/README.md | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/sandboy/README.md b/sandboy/README.md index 591beac4..1950ec7d 100644 --- a/sandboy/README.md +++ b/sandboy/README.md @@ -51,6 +51,15 @@ tcp_bind = [] # omit seccomp_deny to use the curated default denylist ``` +This TOML is the file Sandboy actually reads, and it should stay exactly this +plain. Once there's more than one profile (`no-net`, `worktree-only`, a Windows +exec allowlist) to compose without copy-pasting, author the source in CUE and +render it down to this shape (`cue export step.cue --out toml > step.toml`) — +Sandboy's runtime never needs to know CUE exists. Full rationale and the +`#Policy`/`#Base`/`#NoNet` schema this maps onto: +[`007/docs/zero-trust-framework.md`](https://github.com/PhysShell/007/blob/main/docs/zero-trust-framework.md) +§12. + ## Build & run > **Authored, not compiled here.** Written in a network-restricted sandbox From dab92f12560f4ea75cf31f737d4c15018e32b1c6 Mon Sep 17 00:00:00 2001 From: PhysShell <45852143+PhysShell@users.noreply.github.com> Date: Sun, 5 Jul 2026 12:26:56 +0500 Subject: [PATCH 09/25] docs(sandboy): cross-link 007 loop-canvas for the gate/run integration contract The "Wiring into 007" section already describes the per-step wrap; point it at 007/docs/loop-canvas.md, where the same slot is framed as the canvas Actions/Limits/Observability fields. Docs only. Co-Authored-By: Claude Opus 4.8 Claude-Session: https://claude.ai/code/session_01VNgUjrvwqwtXqe8URbJaQN --- sandboy/README.md | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/sandboy/README.md b/sandboy/README.md index 591beac4..ba9a244a 100644 --- a/sandboy/README.md +++ b/sandboy/README.md @@ -98,6 +98,14 @@ Per-step policies let a `fmt` step run with no network and RO toolchain, while a which is exactly what `007/docs/security-layers.md` marks as the missing layer in the `run`/gate slot. +The same slot, framed as a loop-engineering design surface (the canvas +**Actions** boundary + **Limits** timeout + **Observability** evidence per gate +step), is in `007/docs/loop-canvas.md`. The wiring hook on the 007 side is a +per-step `sandbox_policy` field on `GateStep` — forward-compatible, not yet +added. An optional `--report ` from sandboy (enforcement status, exit +code, duration) is what turns confinement into the machine-readable evidence +that doc's Observability field asks for. + ## Kernel requirements - Landlock FS scoping: kernel ≥ 5.13. From 7f89d5410d0f701a9fa0b73423a94d49ffc35893 Mon Sep 17 00:00:00 2001 From: PhysShell <45852143+PhysShell@users.noreply.github.com> Date: Sun, 5 Jul 2026 12:32:33 +0500 Subject: [PATCH 10/25] docs(sandboy): label --report and sandbox_policy as not-yet-implemented Codex review (PR #183): the wiring note described `sandboy --report ` as if available, but `parse_args` accepts only `run`/`--policy`/`--` and rejects any other flag (exit 2). Mark both integration hooks (`--report` on sandboy, `sandbox_policy` on 007's GateStep) explicitly as Floor-1 work that does not exist yet, and note the current stderr enforcement status. Co-Authored-By: Claude Opus 4.8 Claude-Session: https://claude.ai/code/session_01VNgUjrvwqwtXqe8URbJaQN --- sandboy/README.md | 19 ++++++++++++++----- 1 file changed, 14 insertions(+), 5 deletions(-) diff --git a/sandboy/README.md b/sandboy/README.md index ba9a244a..2e1e6ba8 100644 --- a/sandboy/README.md +++ b/sandboy/README.md @@ -100,11 +100,20 @@ in the `run`/gate slot. The same slot, framed as a loop-engineering design surface (the canvas **Actions** boundary + **Limits** timeout + **Observability** evidence per gate -step), is in `007/docs/loop-canvas.md`. The wiring hook on the 007 side is a -per-step `sandbox_policy` field on `GateStep` — forward-compatible, not yet -added. An optional `--report ` from sandboy (enforcement status, exit -code, duration) is what turns confinement into the machine-readable evidence -that doc's Observability field asks for. +step), is in `007/docs/loop-canvas.md`. Two hooks make that real, and **neither +exists yet** — both are Floor-1 work, not current behaviour: + +- **007 side — `sandbox_policy` on `GateStep`.** A per-step policy path so the + gate runner knows to wrap the step. Forward-compatible with the current + manifest parser (unknown fields are tolerated), but **not yet added**. +- **sandboy side — `--report `.** A flag emitting enforcement status / + exit code / duration, the machine-readable evidence the Observability field + asks for. **Not implemented today:** `parse_args` (`src/main.rs`) accepts only + `run`, `--policy `, and `--`, so passing `--report` now is a usage error + (exit 2). Enforcement status *is* already surfaced, but only to **stderr** + (`FullyEnforced` silently / `PARTIALLY enforced` warning / `NOT enforced` + refusal); `--report` would make it structured so 007 can persist it into + `gate/.sandbox.json`. ## Kernel requirements From dbe8702f2389bd7e3dae6f2f331bbf668c27e477 Mon Sep 17 00:00:00 2001 From: PhysShell <45852143+PhysShell@users.noreply.github.com> Date: Sun, 5 Jul 2026 12:35:22 +0500 Subject: [PATCH 11/25] docs(sandboy): sandbox_policy must fail closed, matching 007 loop-canvas Keep the two sides of the contract consistent: the 007-side bullet framed `sandbox_policy` as plain "forward-compatible", but 007/docs/loop-canvas.md now requires it to fail closed (schema bump) since silent unknown-field tolerance fails open on a security control. Co-Authored-By: Claude Opus 4.8 Claude-Session: https://claude.ai/code/session_01VNgUjrvwqwtXqe8URbJaQN --- sandboy/README.md | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/sandboy/README.md b/sandboy/README.md index 2e1e6ba8..6401bbbd 100644 --- a/sandboy/README.md +++ b/sandboy/README.md @@ -104,8 +104,13 @@ step), is in `007/docs/loop-canvas.md`. Two hooks make that real, and **neither exists yet** — both are Floor-1 work, not current behaviour: - **007 side — `sandbox_policy` on `GateStep`.** A per-step policy path so the - gate runner knows to wrap the step. Forward-compatible with the current - manifest parser (unknown fields are tolerated), but **not yet added**. + gate runner knows to wrap the step. **Not yet added.** The manifest parser + tolerates unknown fields, but this is a **security control**, so it must + **fail closed** when it lands: a manifest `schema` bump (or explicit presence + check) so an older `o7` that can't enforce a `sandbox_policy` **refuses the + step** rather than silently running it bare under `bypassPermissions`. Relying + on unknown-field tolerance here would fail *open*. See + `007/docs/loop-canvas.md`. - **sandboy side — `--report `.** A flag emitting enforcement status / exit code / duration, the machine-readable evidence the Observability field asks for. **Not implemented today:** `parse_args` (`src/main.rs`) accepts only From 14411a4bf484a31de24f68396764b0718e46f967 Mon Sep 17 00:00:00 2001 From: PhysShell <45852143+PhysShell@users.noreply.github.com> Date: Sun, 5 Jul 2026 12:41:44 +0500 Subject: [PATCH 12/25] docs: add agentic coding discipline proposal --- docs/agentic-coding-discipline-proposal.md | 1103 ++++++++++++++++++++ 1 file changed, 1103 insertions(+) create mode 100644 docs/agentic-coding-discipline-proposal.md diff --git a/docs/agentic-coding-discipline-proposal.md b/docs/agentic-coding-discipline-proposal.md new file mode 100644 index 00000000..73b31918 --- /dev/null +++ b/docs/agentic-coding-discipline-proposal.md @@ -0,0 +1,1103 @@ +# Agentic Coding Discipline — Proposal + +> Статус: **proposal / design note**, не implementation plan. Зафиксировано как +> отправная точка для дисциплинированного agentic coding поверх Own.NET, +> OwnAudit и 007. Часть тезисов ниже уже реализована и задокументирована в +> репозиториях на момент коммита — см. раздел "Что уже есть". Остальное — +> открытые предложения, ждущие решения о приоритете. + +## Что из этого уже реализовано/задокументировано + +- **007** (`README.md`, `TODO.md`) — цикл `isolate → run → gate → harvest` уже + работает: `o7 run` делает git worktree, гоняет `claude` full-auto, прогоняет + `.007/gate.toml`, собирает `task.md / meta.json / agent.stdout / diff.patch / + gate/*.log` (`src/agent.rs`, `gate.rs`, `worktree.rs`, `record.rs`, + `verdict.rs`). +- **007/docs/security-layers.md** — источник тезисов про "deny-list ≠ sandbox", + "worktree isolation ≠ security boundary", `bash -lc` из target repo как + attacker-controlled code execution поверхность. +- **007/docs/performance.md** + **TODO.md** — тезис "007 subprocess/LLM-bound, + единственный рычаг — bounded `--jobs N` для per-file judge calls" и план + STS-run на 156 FP-suspects. +- **007/judge/** (`README.md`, `prompt.template.md`, `rubric.example.md`, + `fp-verdicts.schema.json`) — `o7 judge` как отдельный read-only режим с + machine-readable verdict уже реализован и верифицирован на oracle + (contract-conforming `fp-verdicts.json`). +- **OwnAudit/docs/fp-judge/verdict-contract.md + rubric.md** — источник истины, + под который подстроена схема `007/judge/fp-verdicts.schema.json`. +- **Own.NET/AGENTS.execution-surfaces.md** — живой пример agent-contract в духе + предложения ниже: ADR-решение (accept/reject), секция "Что НЕ делать" + (negative prompts), acceptance criteria, trigger table "пересмотреть если...". +- **Own.NET/AGENTS.md**, **OwnAudit/AGENTS.md** — repo-level своды правил для + агентов уже существуют. + +## Чего нет и что здесь предлагается как открытая задача + +- Папка `.ai/` в Own.NET (`project-context.md`, `architecture-rules.md`, + `forbidden-changes.md`, `task-template.md`, `review-template.md`, + `domain-glossary.md`, `testing-policy.md`). +- Машиночитаемый `task.o7.toml` / `TaskContract` + `DiffPolicy` gate в 007 — + сейчас есть только `examples/task.example.md` как обычный markdown-таск, без + scope/forbidden-paths/dependency enforcement. +- Команды `o7 plan`, `o7 judge-run`, `o7 replay`, `trust_level` + (trusted-local / semi-trusted / untrusted). +- Каталог analyzer rule id вида `OWNASYNC001` / `OWNLIFE00x` / `OWNWPF00x` / + `OWNDATA00x` для Own.NET. + +--- + +## Исходный текст предложения + +Я бы забрал из NoBootCamp не "vibe coding", а disciplined agentic coding. То есть не "AI, сделай мне модуль", а "AI, вот клетка, вот правила, вот тесты, вот ножницы, шаг влево — review тебя сожрёт". Иначе это не разработка, а генератор PR'ов, которые выглядят уверенно, пока не открываешь diff. + +### 1. Главное правило: AI не должен владеть задачей целиком + +Плохой стиль: + +``` +Сделай модуль анализа async-проблем в Own.NET. +``` + +Это приглашение к катастрофе. Агент начнёт придумывать архитектуру, термины, классы, API, тесты, формат отчёта и, возможно, религию. + +Правильный стиль: + +``` +Сделай только analyzer rule OWN0001: +- найти async void методы, кроме event handlers +- проект: .NET 8 analyzer +- целевые проекты: .NET Framework 4.7.2 legacy codebase +- diagnostic severity: Warning +- code fix пока не нужен +- не менять существующую архитектуру +- добавить unit tests на Roslyn analyzer test framework +``` + +Вот это уже задача. Маленькая, проверяемая, без "AI построит Рим, но на TypeScript". + +### 2. Plan-then-build: сначала план, потом код + +Для тебя это must-have. Особенно если агент работает с legacy WPF, графой расчётов, SQL, DevExpress, старым .NET Framework. Там любое "я просто чуть-чуть поправил" обычно означает "я переломал жизненный цикл окна и теперь форма держится в памяти до тепловой смерти Вселенной". + +Шаблон: + +``` +Сначала НЕ пиши код. + +Дай план изменения: +1. какие файлы надо менять; +2. какие файлы нельзя трогать; +3. какие public API останутся совместимыми; +4. какие риски есть; +5. какие тесты надо добавить; +6. как проверить, что поведение не изменилось. + +После плана остановись. +``` + +Зачем это нужно: ты ловишь бред до того, как он превратился в diff на 900 строк. Если в плане агент пишет "создадим новый ServiceLocator", сразу бьёшь по рукам. Потому что ServiceLocator в legacy-проекте — это не паттерн, это плесень с интерфейсом. + +### 3. Negative prompts: запрещай явно + +AI очень любит "помочь" через скрытые побочные эффекты. Поэтому ему надо не только сказать, что делать, но и что категорически нельзя. + +Для твоих проектов я бы держал постоянный блок: + +``` +Запрещено: +- менять public API без явного указания; +- добавлять NuGet-пакеты без отдельного разрешения; +- использовать static mutable state; +- добавлять service locator; +- глотать исключения через catch { }; +- использовать async void, кроме UI event handlers; +- использовать Task.Run как маскировку синхронного IO; +- менять SQL-семантику без тестов; +- менять threading model WPF; +- трогать .csproj, packages.config, build scripts без отдельного разрешения; +- делать "рефакторинг заодно"; +- переименовывать классы/методы, если задача не про это. +``` + +Это выглядит занудно, но это дешевле, чем потом спрашивать: "а почему checkout документа стал падать только на удалённом SQL Server после 17:30 по четвергам". + +### 4. Для Own.NET это можно оформить как "Agent Contract" + +Я бы сделал в репозитории папку: + +``` +.ai/ + project-context.md + architecture-rules.md + forbidden-changes.md + task-template.md + review-template.md + domain-glossary.md + testing-policy.md +``` + +Примерно так: + +**`.ai/project-context.md`** + +``` +Проект Own.NET — набор инструментов для анализа .NET-кода: +- Roslyn analyzers; +- архитектурные проверки; +- async/threading diagnostics; +- IDisposable/lifetime diagnostics; +- WPF-specific diagnostics; +- legacy .NET Framework compatibility checks. + +Цель: помогать безопасно улучшать legacy .NET/WPF код без массовых рискованных переписываний. +``` + +**`.ai/architecture-rules.md`** + +``` +Правила: +- analyzer rules должны быть маленькими и независимыми; +- каждая rule имеет ID, описание, severity, examples, tests; +- analyzer не должен требовать runtime execution; +- false positives должны быть явно описаны; +- dangerous autofix запрещён без отдельного review; +- diagnostics должны объяснять не только "что плохо", но и "почему". +``` + +**`.ai/forbidden-changes.md`** + +``` +Нельзя: +- делать глобальные refactorings; +- добавлять зависимости без обоснования; +- смешивать analyzer logic и reporting; +- добавлять rule без тестов; +- добавлять code fix без negative tests; +- ломать deterministic output. +``` + +Это превращает "чат с AI" в почти нормальный engineering workflow. Почти. Роботы всё ещё могут уверенно нести чушь, но хотя бы по форме. + +### 5. Self-review: заставлять агента проверять собственный diff + +После генерации кода не надо сразу верить. Это как верить junior'у, который говорит "у меня локально работает". Работает?! Телега с тремя колёсами тоже "работает", пока не надо повернуть. + +Шаблон review: + +``` +Проверь этот diff как злой maintainer. + +Ищи: +- изменение поведения; +- скрытые breaking changes; +- race conditions; +- memory leaks; +- swallowed exceptions; +- async/threading проблемы; +- слишком широкие изменения; +- отсутствие тестов; +- несоответствие задаче. + +Формат ответа: +1. Blocking issues +2. Non-blocking issues +3. Missing tests +4. Safer alternative +``` + +Для Own.NET можно сделать это отдельным режимом: AI Review Pass. + +Например: + +``` +Review rule OWN0003: EventSubscriptionLeakAnalyzer. +Проверь, не даёт ли analyzer false positive на weak event pattern, IDisposable cleanup, composite disposable, и WPF Binding events. +``` + +Вот это уже полезно. Не "похвали мой код", а "найди, где он врёт". + +### 6. Для legacy WPF особенно важны agent-readable invariants + +AI плохо понимает невидимые правила старого проекта. Поэтому их надо записывать явно. + +Например: + +``` +WPF invariants: +- ViewModel не должен напрямую знать о View; +- подписки на events должны освобождаться; +- Dispatcher usage должен быть явным; +- long-running операции не должны блокировать UI thread; +- ObservableCollection изменяется только на UI thread; +- IDisposable ownership должен быть очевиден; +- Presenter/Coordinator не должен превращаться в God Object. +``` + +Или для твоих справочников/TNVED/ставок: + +``` +Data invariants: +- не материализовать весь справочник без необходимости; +- большие деревья загружать лениво; +- MessagePack blobs не декодировать полностью ради одного поля; +- SQL query должен быть parameterized; +- paging обязателен для больших выборок; +- LRU/cache не должен менять доменную семантику. +``` + +Это прям корм для AI-агентов. Без этого они будут "улучшать" код так, что справочник на 20k узлов внезапно грузится весь "для удобства". Удобство, ага. Для пожара. + +### 7. Задачи надо резать на "атомы" + +Не так: + +``` +Сделай модуль AsyncAnalyzer. +``` + +А так: + +``` +Task 1: OWN0001 async void detector +Task 2: OWN0002 .Result/.Wait() detector in UI context +Task 3: OWN0003 fire-and-forget Task without observation +Task 4: OWN0004 ConfigureAwait misuse policy +Task 5: OWN0005 Task.Run around synchronous DB call +Task 6: report formatter +Task 7: suppression mechanism +Task 8: documentation examples +``` + +Каждая задача должна иметь: + +``` +- вход; +- ожидаемый diagnostic; +- false positive cases; +- false negative acceptable cases; +- тесты; +- запреты; +- done criteria. +``` + +Иначе агент сделает функцию, которая делает больше работ, чем single parent on three shifts. IT'S FUCKING BUGGY! + +### 8. Очень полезная штука: "definition of done" для AI + +В конце каждого промпта: + +``` +Definition of Done: +- код компилируется; +- добавлены тесты; +- покрыты positive и negative cases; +- нет изменений вне заявленных файлов; +- public API не изменён; +- нет новых зависимостей; +- diagnostic message понятный; +- есть пример bad/good code; +- описаны known limitations. +``` + +Это не гарантия качества. Это забор. AI всё ещё может перелезть, но хотя бы будет видно, где он испачкал штаны. + +### 9. Для твоего Own.NET я бы начал с таких модулей + +**Async module** + +Самый жирный кандидат. + +``` +OWNASYNC001: async void outside event handlers +OWNASYNC002: blocking wait on Task: .Wait(), .Result, GetAwaiter().GetResult() +OWNASYNC003: fire-and-forget Task without observation/logging +OWNASYNC004: Task.Run used to hide sync IO +OWNASYNC005: async method without CancellationToken in service boundary +OWNASYNC006: ConfigureAwait policy violation +``` + +Особенно интересно для WPF: ловить `.Result`/`.Wait()` в UI-пути. Это прям классика "почему окно умерло, хотя процесс живой". + +**IDisposable / lifetime module** + +``` +OWNLIFE001: IDisposable field not disposed +OWNLIFE002: event subscription not unsubscribed +OWNLIFE003: IDisposable created but not owned +OWNLIFE004: CancellationTokenSource not disposed +OWNLIFE005: Stream/SqlConnection/DbCommand lifetime leak +``` + +Для legacy desktop это золото. Там утечки обычно не "одна большая ошибка", а тысяча мелких "ну оно же работает". + +**SQL/data-access module** + +``` +OWNDATA001: string interpolation in SQL +OWNDATA002: concatenated SQL with user/domain input +OWNDATA003: SELECT * in repository/query object +OWNDATA004: missing transaction boundary +OWNDATA005: provider-specific SQL without abstraction marker +OWNDATA006: temp table incompatibility SQL Server/SQLite +``` + +С учётом твоей боли SQL Server/SQLite — прям вкусно. Можно делать analyzer + test corpus на query builder. + +**WPF module** + +``` +OWNWPF001: ObservableCollection modified outside UI thread +OWNWPF002: event subscription leak in View/ViewModel/Presenter +OWNWPF003: long-running operation in command handler +OWNWPF004: Dispatcher.Invoke instead of BeginInvoke/async path +OWNWPF005: direct View reference from ViewModel +OWNWPF006: Bitmap/Image resource not released +``` + +Вот тут надо аккуратно: WPF-анализ легко даёт false positive. Но даже advisory diagnostics уже полезны. + +### 10. Самый ценный формат промпта для тебя + +Вот шаблон, который можно реально использовать: + +``` +Ты работаешь как senior .NET/Roslyn developer. + +Контекст: +- проект: Own.NET +- цель: analyzer для legacy .NET/WPF проектов +- стиль: маленькие независимые rules +- тесты обязательны + +Задача: +Реализовать rule OWNASYNC001: async void outside event handlers. + +Требования: +- diagnostic для async void методов; +- не репортить event handlers вида void Handler(object sender, EventArgs e); +- не репортить override методов, если сигнатура навязана базовым API; +- diagnostic message должен объяснять риск; +- severity: Warning; +- code fix не делать. + +Ограничения: +- не добавлять NuGet dependencies; +- не менять public API существующих rules; +- не делать unrelated refactoring; +- не менять build scripts. + +Сначала дай план: +- какие файлы создашь/изменишь; +- какие тесты добавишь; +- какие false positives возможны; +- где нужна осторожность. + +После плана остановись. +``` + +А после утверждения плана: + +``` +Реализуй только согласованный план. +После кода дай: +1. список изменённых файлов; +2. краткое объяснение; +3. тестовые сценарии; +4. known limitations. +``` + +Вот это уже не "vibe". Это нормальный рабочий протокол. + +### 11. Для PR-review можно сделать отдельный "злой режим" + +Например: + +``` +Review this PR for Own.NET. + +Be strict. Assume the code is wrong until proven otherwise. + +Check: +- analyzer correctness; +- false positives; +- false negatives; +- Roslyn performance; +- allocations; +- cancellation support; +- concurrency safety; +- deterministic diagnostics; +- tests quality; +- public API compatibility. + +Do not praise. Find risks. +``` + +Это очень подходит под твой стиль "код должен заработать право существовать". И да, тут "не хвали" важно. AI по дефолту ведёт себя как HR на корпоративе: всё "promising", "solid foundation", "nice work". А тебе нужен maintainer, который скажет: "WHERE'S THE ERROR HANDLING?!" + +### 12. Что реально можно протащить в твой процесс + +Я бы внедрял так: + +1. `.ai/` папка с правилами проекта — чтобы каждый агент не начинал с нуля и не изобретал очередной "чистый" ад. +2. Task template — одинаковый формат задач для analyzer/rule/refactor/test. +3. Review template — отдельный промпт для проверки diff'а. +4. Negative constraints — запреты на side effects, новые зависимости, public API changes. +5. Rule skeleton generator — агент генерит каркас analyzer rule, тесты, examples, docs. +6. Golden test corpus — папка с bad/good examples, чтобы AI не спорил с реальностью. +7. Regression checklist — особенно для графы 47, SQL builder, WPF lifecycle, async/threading. + +### 13. Самая важная мысль + +AI полезен не как "разработчик вместо тебя", а как ускоритель конкретных операций: + +- накидать Roslyn analyzer skeleton; +- придумать negative test cases; +- проверить diff на риски; +- объяснить чужой код; +- сгенерить documentation examples; +- составить migration plan; +- найти edge cases; +- сделать boring code. + +Но архитектурные инварианты, границы модулей, совместимость, lifecycle, перформанс и "а не превратится ли это в болото через 3 месяца" — это всё ещё на тебе. Несправедливо, конечно. Но зато без иллюзии, что "vibe" заменяет engineering. Вайб не ловит memory leak. Тесты ловят. Анализаторы ловят. Review ловит. Иногда senior с кофеином ловит. + +Да. Более того, 007 — это как раз место, где такие подходы надо превращать из "хороших советов" в исполняемый протокол. + +Для Own.NET это выглядело как: + +> "AI, напиши analyzer rule, но вот инварианты, тесты, запреты, review." + +А для 007 это должно выглядеть так: + +> "007, запусти агента только внутри заданного контракта, собери diff, прогоняй gates, зафиксируй evidence, потом отдельный judge/reviewer решает, можно ли это вообще считать работой." + +То есть Own.NET — пациент, а 007 — хирургическая операционная. Если операционная не стерильная, пациенту кирдык, даже если врач в белом халате и с Claude Max. + +#### Что уже в 007 идеально подходит + +Судя по README, 007 уже задуман как harness: `o7 run` делает worktree, запускает claude/codex, прогоняет gate steps и складывает canonical record: `task.md`, `meta.json`, `agent.stdout`, `diff.patch`, `gate/*.log`, `verdict.json`. Это прям база для disciplined agentic coding, а не "AI, сделай красиво, я отвернусь". + +То есть NoBootCamp-идею надо не "применить к 007", а закодировать в 007 как режимы работы: + +``` +task contract → isolated run → gates → harvest → judge/review → verdict +``` + +И вот тут начинается мясо. + +--- + +### 1. Для 007 нужен не "prompt template", а Task Contract + +В Own.NET можно было держать `.ai/task-template.md`. + +В 007 лучше сделать машиночитаемый task contract, например: + +```toml +# task.o7.toml + +[target] +repo = "../Own.NET" +base = "main" + +[agent] +provider = "claude" +mode = "full-auto" + +[scope] +allowed_paths = [ + "src/OwnNet.Analyzers/**", + "tests/OwnNet.Analyzers.Tests/**" +] + +forbidden_paths = [ + "*.csproj", + "Directory.Build.props", + ".github/**" +] + +[change_policy] +allow_new_dependencies = false +allow_public_api_changes = false +allow_unrelated_refactoring = false +require_tests = true + +[task] +kind = "roslyn-analyzer" +summary = "Implement OWNASYNC001: async void outside event handlers" + +[done] +commands = [ + "dotnet test", + "dotnet build -warnaserror" +] +``` + +Почему это лучше обычного `task.md`? Потому что обычный markdown — это просьба. А `.toml`/schema — это контракт. Компьютер хотя бы может проверить, что агент не полез в `.csproj`, вместо того чтобы потом человек руками обнаруживал "маленький рефакторинг" на 1800 строк. Технический долг, но с бантиком. + +Что добавить в 007: + +``` +o7 validate-task --task task.o7.toml +o7 run --task task.o7.toml +o7 inspect-run runs/ +o7 judge-run runs/ +``` + +Минимальный MVP: + +``` +task.md # человеческое описание +task.o7.toml # машинный контракт +gate.toml # команды проверки +policy.toml # запреты / scope / allowlist +``` + +### 2. plan-then-build в 007 должен стать отдельной фазой + +Сейчас README описывает один цикл: isolate → run → gate → harvest. + +Но для coding-agent задач я бы разделил: + +``` +o7 plan +o7 run +o7 judge +``` + +То есть агент сначала не имеет права менять код. Он должен сгенерировать план: + +``` +runs/// + plan.md + plan.meta.json + plan.verdict.json +``` + +Потом отдельный gate проверяет план: + +``` +- план не трогает запрещённые файлы; +- план не добавляет зависимости; +- план перечисляет тесты; +- план содержит rollback/check strategy; +- план не предлагает глобальный рефакторинг. +``` + +И только потом: + +``` +o7 run --from-plan runs/.../plan.md +``` + +Это сильно лучше, чем давать агенту full-auto сразу. Потому что full-auto без предварительного плана — это как дать экскаватор человеку, который "примерно понял задачу". Земля, конечно, будет двигаться. Вопрос только, чья. + +### 3. Negative prompts в 007 должны стать policy/gate, а не текстом + +В Own.NET мы могли писать: + +``` +Не меняй public API. +Не добавляй зависимости. +Не трогай build scripts. +``` + +В 007 это надо превратить в проверяемые правила: + +```toml +[diff_policy] +max_changed_files = 8 +max_added_lines = 500 +forbid_paths = [ + "Cargo.toml", + "flake.nix", + ".github/**", + "**/*.sln", + "**/*.csproj" +] + +[dependency_policy] +allow_new_nuget = false +allow_new_npm = false +allow_new_cargo = false + +[api_policy] +require_public_api_report = true +``` + +И gate после diff должен проверять: + +``` +diff.patch против policy.toml +``` + +Если агент полез куда не просили: + +``` +FAIL: touched forbidden file Directory.Build.props +FAIL: added dependency Microsoft.Extensions.DependencyInjection +FAIL: changed public API without approval +``` + +Вот это уже нормальная инженерия. Не "агент, пожалуйста, будь хорошим мальчиком", а "вышел за пределы клетки — run failed". WHERE'S THE ERROR HANDLING?! Вот оно, наконец-то. + +### 4. 007 должен собирать не просто diff, а evidence pack + +README уже говорит, что 007 harvest'ит `meta.json`, `agent.stdout`, `diff.patch`, gate logs и verdict. + +Я бы расширил canonical record: + +``` +runs/// + task.md + task.o7.toml + plan.md + meta.json + + diff.patch + changed-files.json + forbidden-touches.json + + agent/ + stdout.log + stderr.log + tool-calls.json + + gate/ + build.log + test.log + lint.log + policy.log + verdict.json + + judge/ + review.md + verdict.json + risks.json + + replay/ + base_commit.txt + head_commit.txt + commands.sh +``` + +Зачем: 007 должен быть не просто "запустил агента", а черный ящик самолёта после падения. Агент внёс diff? Докажи: + +``` +что он запускался в правильном repo; +от какого base commit; +какие файлы поменял; +какие gates прошли; +какие упали; +что judge сказал; +где логи; +какой prompt был; +чем run воспроизводится. +``` + +Иначе это не automation harness, а "скрипт, который доверяет LLM". А это уже почти религиозная практика. + +### 5. judge в 007 — прямое продолжение self-review + +У тебя уже есть `o7 judge`, и TODO говорит, что он проверен на read-only FP-triage и выдавал contract-conforming `fp-verdicts.json`; дальше запланирован FP-control и реальный STS-run на 156 FP-suspects. + +Это очень важная часть. В терминах NoBootCamp: + +``` +self-correction / review prompt +``` + +В терминах 007: + +``` +judge command + rubric + schema + verdict contract +``` + +То есть не "Claude сам себя проверил, ну значит норм". Нет. Отдельный режим: + +``` +agent делает diff +judge смотрит diff + task + gate logs +judge возвращает machine-readable verdict +``` + +Например: + +```json +{ + "verdict": "fail", + "blocking": [ + { + "kind": "scope_violation", + "file": "Directory.Build.props", + "reason": "Task did not permit build infrastructure changes" + } + ], + "missing_tests": [ + "No negative test for event handler async void exception" + ], + "risk": "high" +} +``` + +Вот это годно. + +### 6. Для 007 особенно важна безопасность, потому что сейчас worktree не sandbox + +Вот тут надо быть неприятно честным, а не гладить 007 по README. Документ `security-layers.md` прямо говорит: `run` использует deny-list, а это не sandbox, потому что command obfuscation может проскочить. Там же указано, что worktree isolation — это cleanup/convention, но не security boundary: процесс всё равно может читать/писать вне worktree через абсолютные или `..` пути и ходить в сеть. + +Ещё хуже: `.007/gate.toml` запускает произвольный `bash -lc ` из target repo. Для недоверенного repo это attacker-controlled code execution. Документ прямо фиксирует, что `current_dir` не ограничивает ни writes, ни reads, ни egress. + +Что это значит для применения NoBootCamp-идей: + +``` +Для доверенных своих реп: +Можно начинать с policy/gates/worktree/evidence. + +Для чужих или полудоверенных реп: +Без container/WASI/egress hardening это рискованно. Агентный harness без +настоящей песочницы — это "изоляция" уровня таблички "не входить" на двери +без замка. +``` + +Практический вывод + +В 007 надо добавить `trust_level`: + +```toml +[target] +trust = "trusted-local" +# trusted-local | semi-trusted | untrusted +``` + +И правила: + +``` +trusted-local: + worktree + gates ok + +semi-trusted: + container required + network off by default + write mount only to worktree + +untrusted: + no agent full-auto + judge/read-only only + no bash gates unless sandboxed +``` + +Это прям должно быть в 007, иначе someday someone will run `o7 run` на "интересном" репозитории, и будет цирк с логами. + +### 7. Верификация в 007 уже ближе к правильной, но её надо встроить в агентный workflow + +`docs/verification.md` говорит, что проект уже использует/планирует несколько уровней: proptest для pure functions, cargo-fuzz для парсеров model stdout / findings.json / gate.toml, Kani для bounded no-panic proofs, плюс строгие lints и cargo deny. + +Это отлично подходит к 007, потому что 007 — glue/orchestration, а самые опасные поверхности там: + +``` +- model output parsing; +- gate.toml parsing; +- findings.json parsing; +- path handling; +- command execution; +- harvest/replay correctness. +``` + +Я бы добавил gate profile: + +```toml +[gate.profiles.fast] +steps = [ + "cargo test", + "cargo clippy --all-targets -- -D warnings" +] + +[gate.profiles.security] +steps = [ + "cargo test", + "cargo deny check", + "cargo +nightly fuzz run extract_json_array -- -max_total_time=60" +] + +[gate.profiles.release] +steps = [ + "nix flake check", + "cargo deny check" +] +``` + +И тогда task может сказать: + +```toml +[required_gates] +profile = "fast" +``` + +А security-sensitive change: + +```toml +[required_gates] +profile = "security" +``` + +Без этого агент может менять parser, а ты потом такой: "ну вроде тесты прошли". Какие тесты? Один happy path и молитва? IT'S FUCKING BUGGY! + +### 8. Перформанс-часть: NoBootCamp тут почти не нужен, но 007 уже знает правильный рычаг + +`docs/performance.md` правильно фиксирует, что 007 subprocess/LLM-bound, а не compute-bound: почти всё время уходит в ожидание claude, git, bash, а Rust glue занимает микросекунды. Единственный реальный рычаг — параллелить независимые per-file judge calls через bounded worker pool. + +И TODO это подтверждает: для STS-run уже указано, что per-file claude calls независимы, sequential = сумма latency, а bounded `--jobs N` даст near-linear speedup без изменения логики pairing. + +То есть для 007 я бы не тратил время на микротюнинг Rust. Никаких "давайте SmallVec", "давайте inline", "давайте cache locality". Это всё косметика на человеке, который опаздывает потому что ждёт поезд. + +Что делать: + +``` +o7 judge --jobs 4 +o7 judge --jobs 8 +``` + +Но обязательно: + +``` +- bounded concurrency; +- retry/backoff; +- per-file error isolation; +- deterministic output ordering; +- rate-limit aware logs. +``` + +### 9. Как бы я разложил NoBootCamp-подход именно по 007 + +**A. `o7 plan`** + +Новый режим: + +``` +o7 plan --repo ../Own.NET --base main --task ./task.md --out ./runs/... +``` + +Выход: + +``` +plan.md +plan.json +plan-verdict.json +``` + +Проверяет: + +``` +- scope; +- forbidden files; +- required tests; +- risk level; +- estimated gates. +``` + +**B. `o7 run`** + +Текущий MVP, но с policy: + +``` +o7 run --task task.o7.toml --gate ../Own.NET/.007/gate.toml +``` + +Обязательно собирает: + +``` +- diff.patch; +- changed files; +- touched forbidden paths; +- dependency changes; +- gate logs. +``` + +**C. `o7 judge-run`** + +Отдельная проверка результата: + +``` +o7 judge-run runs/Own.NET/ +``` + +Judge получает: + +``` +- original task; +- plan; +- diff; +- gate verdict; +- changed files; +- logs; +- policy violations. +``` + +Возвращает: + +``` +PASS | FAIL | NEEDS_HUMAN +``` + +**D. `o7 replay`** + +Суперважно: + +``` +o7 replay runs/Own.NET/ +``` + +Если нельзя воспроизвести, значит evidence pack неполный. А неполный evidence pack — это как тест без assert. Красиво, бесполезно, пахнет обманом. + +### 10. Самая сильная идея: 007 как "CI для AI-агентов" + +Вот как я бы сформулировал роль 007: + +``` +007 is not an AI coding assistant. +007 is a reproducible, gated, auditable execution harness for AI coding assistants. +``` + +По-русски: + +> 007 не должен быть "ещё одним агентом". 007 должен быть судьёй, клеткой, журналом и турникетом для агентов. + +Claude/Codex могут генерировать код. 007 должен отвечать за: + +``` +- изоляцию; +- scope; +- запреты; +- gates; +- diff capture; +- evidence; +- judge; +- воспроизводимость; +- policy failure; +- human handoff. +``` + +И вот это уже реально применимо ко всем твоим штукам: + +``` +Own.NET → агент пишет analyzer/rule/docs/tests +OwnAudit → агент triage'ит findings / FP / отчёты +legacy WPF → агент делает маленькие refactor tasks +STS → judge по findings и false positives +sandboy → будущая sandbox/plugin boundary +``` + +### 11. Что я бы сделал ближайшим шагом в 007 + +Не sandbox. Не Cedar. Не Verus. Не "consensus multi-agent tournament", как бы ни чесались руки построить роботизированный цирк. + +Ближайший полезный шаг: + +``` +Task Contract + Diff Policy Gate +``` + +Минимально: + +``` +src/task_contract.rs +src/diff_policy.rs +schemas/task.o7.schema.json +examples/task.ownnet.analyzer.o7.toml +examples/policy.safe-defaults.toml +``` + +Первый gate: + +``` +- changed files are inside allowed_paths; +- forbidden_paths untouched; +- max files/lines not exceeded; +- dependency manifests unchanged unless allowed; +- task/gate/meta copied into run record. +``` + +Это даст 007 реальную ценность как harness, а не просто runner. + +MVP формата + +```toml +version = 1 + +[target] +repo = "../Own.NET" +base = "main" + +[scope] +allowed_paths = [ + "src/**", + "tests/**" +] +forbidden_paths = [ + ".github/**", + "*.sln", + "*.csproj", + "Directory.Build.props" +] + +[limits] +max_changed_files = 10 +max_added_lines = 700 +max_deleted_lines = 300 + +[policy] +allow_dependencies = false +allow_public_api_changes = false +require_tests = true +``` + +И после run: + +``` +o7 policy-check runs//diff.patch --policy task.o7.toml +``` + +Вердикт: + +```json +{ + "verdict": "FAIL", + "violations": [ + { + "kind": "forbidden_path", + "path": "Directory.Build.props" + } + ] +} +``` + +Вот это прям вкусно. Маленько, полезно, проверяемо. + +--- + +## Итог + +Да, применимо. Но для 007 это не "как писать промпты". + +Для 007 правильная версия такая: + +``` +NoBootCamp principles → executable agent harness protocol +``` + +Грубо: + +``` +prompt discipline → task contract +negative prompts → diff policy +plan-then-build → o7 plan + o7 run +self-review → o7 judge-run +testing checklist → gate profiles +AI output history → evidence pack +security advice → trust levels + sandbox triggers +``` + +Own.NET использует эти идеи внутри задач. + +007 должен использовать эти идеи как инфраструктуру, которая не даёт задачам превратиться в агентный мусорный пожар. From 64f4e6772eb0e9cf93e6fc1a4202592669661fb5 Mon Sep 17 00:00:00 2001 From: Claude Date: Sun, 5 Jul 2026 07:58:07 +0000 Subject: [PATCH 13/25] docs(P-010): restructure Own.Types around the five-pillar taxonomy MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Reorganize the type-disciplines proposal under semantic primitives, algebraic domain modeling, state/lifetime discipline, tooling, and future — adding discriminated unions/exhaustive matching/Option/Result (previously undocumented anywhere in the repo), strongly typed IDs, ValueTask/single-use constraints, and an elaborated tooling section (source generators, code fixes, generated docs, OwnIR facts). Existing typestate/ownership items are cross-referenced to P-004/005/006/007 instead of duplicated, and a diagnostic-prefix open question (`TYP0xx` vs `[type: …]` tag) is recorded against the DI/EFF/OBL precedent. Co-Authored-By: Claude Sonnet 5 Claude-Session: https://claude.ai/code/session_019ChcHzqtYRaVGiHmL2dCQ8 --- docs/proposals/P-010-type-disciplines.md | 256 ++++++++++++++++++++--- 1 file changed, 231 insertions(+), 25 deletions(-) diff --git a/docs/proposals/P-010-type-disciplines.md b/docs/proposals/P-010-type-disciplines.md index 9e880aeb..63e8cbde 100644 --- a/docs/proposals/P-010-type-disciplines.md +++ b/docs/proposals/P-010-type-disciplines.md @@ -2,13 +2,18 @@ - **Status:** draft (horizon) - **Depends on:** `spec/OwnCore.md` (the ownership/affine core and its fact - vocabulary), `spec/Lifetimes.md`; relates to P-006 (capability/lifetime — where - branded `resource`/capability types are held), P-008 (effects — the `use !Db` - half of a signature), and P-005 (IDisposable typestate — the first concrete - protocol). See `docs/ROADMAP.md` for where this sits in the strategy. + vocabulary), `spec/Lifetimes.md`; relates to P-005 (`IDisposable` typestate — + the first concrete protocol), P-006 (DI lifetimes — a region contract, not a + protocol), P-007 (ArrayPool/Span borrow-view — the pooled-buffer instance), + P-008 (effects — the `use !Db` half of a signature), and P-017 (multi-stack + frontends — where `Own.Types` facts travel beyond C#). See `docs/ROADMAP.md` + for where this sits in the strategy. ## Motivation +> **Own.Types is not trying to make C# pretty. Own.Types makes domain lies +> mechanically harder to write.** + The guiding heuristic: types aren't only about the *shape* of data (`string`/`int`/`User`). They can encode validity, access rights, state, dimension, order of operations, effects, protocol, ownership, even proofs. If a @@ -24,25 +29,66 @@ not `DeclarationId`, even though both are `string`) and **what STATE it is in** make everything `string` / `int` / `Guid` / `Dictionary` — is not flexibility. It is homeless JSON pretending to be architecture. -`Own.Types` adds those two dimensions as an **external static-contract layer** -over existing C#: an analyzer / source generator / `.own` spec that checks the +`Own.Types` adds those dimensions as an **external static-contract layer** over +existing C#: an analyzer / source generator / `.own` spec that checks the discipline, without rewriting the code into a new language. This is the move that turns Own.NET from "a borrow checker for C#" into "an external static contract layer for C#/.NET that adds ownership, typestate, effects, capabilities, and domain types" — while the DSL stays a spec/model/contract language and pointedly refuses to become a second C# people write business logic in. +## Map of the five pillars + +```text +Own.Types +├─ Semantic primitives — what a value MEANS +│ ├─ newtype / branded types +│ ├─ constrained (refinement) types +│ ├─ units / quantities +│ └─ strongly typed IDs +│ +├─ Algebraic domain modeling — what a value CAN BE +│ ├─ discriminated unions +│ ├─ exhaustive matching +│ ├─ Option +│ └─ Result / error unions +│ +├─ State/lifetime discipline — what STATE a value is in +│ ├─ typestate +│ ├─ owned/borrowed/must-dispose +│ ├─ event subscription lifetime +│ ├─ pooled buffer lifecycle +│ └─ ValueTask/single-use constraints +│ +├─ Tooling — how the discipline is enforced +│ ├─ source generators +│ ├─ Roslyn analyzers +│ ├─ code fixes +│ ├─ generated docs +│ └─ OwnIR facts +│ +└─ Future — horizon, not committed + ├─ .own DSL + ├─ F# generator/backend + ├─ interop analyzers + └─ multi-language frontends +``` + +The first four pillars share one restraint: brands, refinements, units, unions, +and protocols all lower to plain structs/records plus smart constructors — the +*discipline* is enforced by analyzer, not by a second type checker that could +drift from the affine core (the project's standing meta-irony). + ## Scope -The four most-applied disciplines, in priority order. Each has a `.own` -declaration and a checked C# imitation; none requires a runtime. +### 1. Semantic primitives — what a value means 1. **Branded / opaque types.** Distinguish `ProductId`, `DeclarationId`, `Email` though all are `string` underneath, so `GetProduct(declarationId)` is a diagnostic, not a 2 a.m. incident. DSL: `brand ProductId : string;`. C#: `[OwnBrand("ProductId")]` on a `readonly record struct` plus a smart constructor; the analyzer enforces that the wrapped value only enters through - it. (Mechanically these are phantom types — see the catalog.) + it. (Mechanically these are phantom types — see the deferred catalog.) 2. **Refinement types** — "int, but valid": `refinement Port : int where value >= 1 && value <= 65535;`, @@ -58,8 +104,55 @@ declaration and a checked C# imitation; none requires a runtime. tax rates, and physical quantities — the domains where a silent unit mix-up is a financial bug, not a rounding one. -4. **Typestate / protocols.** Encode object state in the type so methods can only - be called in a valid order: +4. **Strongly typed IDs.** Not a new mechanism — the single most-applied + *instance* of branded types, called out because it is the pattern developers + reach for first: `brand OrderId : Guid; brand CustomerId : Guid;` so + `GetOrder(customerId)` is caught even though both brands share the same + underlying `Guid`. The analyzer's job here is narrower than general branding: + catch **argument-order transposition** at call sites where two branded IDs of + the same underlying type are adjacent parameters — the concrete bug this + pillar exists to kill. + +### 2. Algebraic domain modeling — what a value can be + +5. **Discriminated unions.** A closed set of shapes a value can take: + `union Shape { Circle(radius: float); Rect(w: float, h: float); }`. Lowers to + a sealed hierarchy (or a source-generated closed struct union) that cannot be + extended from outside the declaration — the analyzer, not `sealed` alone, + enforces closedness across partial classes and other-assembly subclassing + attempts. + +6. **Exhaustive matching.** C#'s `switch` over a non-`enum` type has no + exhaustiveness check at all, and even enum switches only get an ignorable + `CS8509` warning. Own.Types promotes this to a hard diagnostic tied to the + `union` declaration: every `switch`/pattern match over a branded union must + cover every case or an explicit `default`/discard, and a new case added to + the union must break every non-exhaustive match site at compile time, not at + 3 a.m. in production. + +7. **`Option`.** Replaces the gap nullable reference types leave open (nothing + stops a `string?` from silently meaning "not yet loaded" *and* "deliberately + absent" *and* "error", all at once). `Option` is a two-state union + (`Some`/`None`); the analyzer flags unmatched `.Value` access the same way it + flags a non-exhaustive union match — this pillar is a specialization of #6, + not a separate mechanism. + +8. **`Result` / error unions.** `Result` as the alternative to + exceptions-as-control-flow for expected failure. Two enforcement angles: (a) + the exhaustiveness rule from #6 — a `Result` must be matched on both `Ok` and + `Error`, not just unwrapped; (b) an "unobserved result" diagnostic, structurally + the same shape as an unawaited `Task` — a `Result` that is constructed and + never matched or propagated is silently swallowed failure. + +Pillar 2's four items are one mechanism wearing three hats: a closed-shape +declaration plus an exhaustiveness check. `Option` and `Result` are simply the +one- and two-error-case unions developers reach for constantly enough to name +directly, rather than making every call site spell out a bespoke `union`. + +### 3. State/lifetime discipline — what state a value is in + +9. **Typestate / protocols.** Encode object state in the type so methods can + only be called in a valid order: ```text protocol Report { @@ -78,12 +171,83 @@ declaration and a checked C# imitation; none requires a runtime. Typestate is also the generalization that subsumes **session types** (typed message-ordering protocols) as the special case where the object is a channel. -The combined picture — domain types, refinements, resources, protocol state, and -effects in one signature set: +10. **Owned / borrowed / must-dispose.** Already built as a standalone + diagnostic in [P-005](P-005-idisposable-ownership.md) — Own.NET already + treats `IDisposable` as typestate C# lacks. This pillar's job is *not* to + duplicate P-005's checker; it is to surface the same ownership verdict as a + **type-level marker** in the signature a developer reads (`Owned`, + `Borrowed`, `[OwnMustDispose]`) so the discipline is visible at the call + site, not only in an analyzer squiggle. + +11. **Event subscription lifetime.** Already covered as a resource-lifetime + profile in P-004 (WPF) and P-006 (DI lifetime, where a subscription is one + captive-dependency shape). Own.Types' angle: a typed subscription handle + that is itself a two-state protocol (`Active -> Disposed`, unsubscribe + consumes self), so double-unsubscribe and use-after-unsubscribe fall out of + the same typestate mechanism as #9, instead of a bespoke `SUB0xx` rule. + +12. **Pooled buffer lifecycle.** Already covered in + [P-007](P-007-arraypool-span.md) (ArrayPool/Span borrow-view). Own.Types' + angle: model a rented buffer as `Buffer` — a two-state + protocol exactly like #9 — so "view survives `Return`" is reported through + the general typestate/use-after-move path rather than a parallel + pool-specific engine. + +13. **`ValueTask`/single-use constraints.** A well-known .NET footgun: + `ValueTask` must be awaited (or converted) exactly once, and never both + stored and awaited. Structurally this is a **single-transition protocol** + (`Pending -> Awaited`; awaiting twice, or awaiting after `.AsTask()`, is + use-after-move) — the same affine "consume once" mechanism as #9, applied to + a BCL type Own.NET does not own and cannot annotate at the source, so the + marker has to live at the call site (`[OwnSingleUse]` on the + producing member, or an analyzer-only rule with no DSL declaration). + +Pillars 10–13 are **not** new checkers to build — they are existing or +near-existing lifetime facts (P-004/005/006/007) reframed as instances of the +two general Own.Types mechanisms (ownership marker, typestate protocol). The +payoff of doing this pillar at all is *unification*: one mental model +(protocol state, consume-on-transition) instead of four bespoke rule families. + +### 4. Tooling — how the discipline is enforced + +14. **Source generators.** Emit the boilerplate a brand/refinement/union + declaration implies — smart constructors, equality, `ToString`, exhaustive + `Match`/`Switch` helper methods — so the discipline costs one declaration, + not hand-written ceremony per type. + +15. **Roslyn analyzers.** The enforcement side for every pillar above: brand- + boundary violations (#1), predicate bypass (#2), unit mismatch (#3), + non-exhaustive match (#6), unobserved `Result` (#8), invalid protocol + transition / use-after-transition (#9, #11–#13). + +16. **Code fixes.** A matching quick-fix per analyzer: insert the missing + `switch` arm stub for a non-exhaustive match, wrap a raw literal in its + brand's smart constructor, insert the missing `Dispose`/transition call. + Diagnostics without a code fix push the discipline back onto the developer + manually re-deriving the fix; that is the gap this item exists to close. + +17. **Generated docs.** Render the `.own` declarations (brand / refinement / + union / protocol) into human-readable reference pages, the same way + `spec/Diagnostics.md` is the single source of truth for `OWN` codes today — + one declaration, read by the compiler *and* the wiki, so the contract and + its documentation cannot drift apart. + +18. **OwnIR facts.** The seam every pillar above lowers through: new OwnIR fact + kinds (`brand`, `refinement`, `union`, `protocol-state`) alongside the + existing resource/ownership facts in `spec/OwnIR.md`, so any frontend — + today's Roslyn C# extractor, tomorrow's OwnTS/OwnJava/OwnKotlin (P-017) — + emits and consumes the same domain-type vocabulary without re-deriving it + per language. + +The combined picture — domain types, refinements, unions, resources, protocol +state, and effects in one signature set: ```text brand ProductId : string; +brand OrderId : Guid; brand CustomerId : Guid; refinement NonEmptyString : string where !String.IsNullOrWhiteSpace(value); +unit usd; +union PaymentResult { Approved(txId: string); Declined(reason: string); } resource Db; resource ArrayPool; protocol Report { @@ -95,9 +259,33 @@ protocol Report { fn CalculateTotal(order: Order) -> Money pure; fn LoadOrder(id: ProductId) -> Order use Db; +fn Charge(customer: CustomerId, amount: usd) -> PaymentResult use Db; fn RenderReport(report: Report) -> File use !ArrayPool, !Log; ``` +## Future (horizon, not committed) + +Distinct from the deferred catalog below: these are things Own.Types plausibly +grows *into*, not type-theory tempo it is refusing. + +- **`.own` DSL.** Today's brand/refinement/union/protocol snippets are sketch + syntax inside this proposal, not a ratified grammar. Graduating this pillar + means these constructs get a real entry in `spec/Grammar.md`, with the same + test-pinned discipline as every other DSL construct. +- **F# generator/backend.** F#'s discriminated unions, units of measure, and + records are a structural match for pillars 1–2 — a codegen backend that lowers + `.own` declarations to *idiomatic F#* (real DUs, real `[]`) instead of + a C# analyzer shim, for teams that can host an F# core project inside a C# + solution and want the compiler itself enforcing the discipline. +- **Interop analyzers.** Once an F# backend exists, the boundary itself needs + checking: a value crossing from a real F# DU into the C#-side shim + representation must stay branded and exhaustive across the language edge, not + just within one language. +- **Multi-language frontends.** Ties directly to + [P-017](P-017-multi-stack-frontends.md) — Own.Types facts (#1–#13) become one + more fact family the OwnTS/OwnJVM frontends emit over the same OwnIR seam + (#18) the ownership facts already use. + ## Non-goals Refuse the boil-the-ocean version. The first move is explicitly **not** dependent @@ -105,9 +293,12 @@ types, GADTs, or higher-kinded types — that way lies a tower of type-level arithmetic (башня type-level арифметики) where you wanted to write a function and end up proving 2 + 2 = 4. The DSL must not become a new general-purpose language; it stays a spec/model/contract layer. No new runtime, no rewriting the codebase — -brands and refinements lower to plain structs and smart constructors, and the -discipline is enforced by analyzer, not by a parallel type checker that drifts -from the core (the project's standing meta-irony). `[OwnIgnore("reason")]` remains +brands, refinements, and unions lower to plain structs/records and smart +constructors, and the discipline is enforced by analyzer, not by a parallel type +checker that drifts from the core (the project's standing meta-irony). +Algebraic domain modeling (pillar 2) gets the same restraint: exhaustive matching +is enforcement of *existing* C# `switch`/pattern-match syntax, not a new +pattern-matching language grafted on top of it. `[OwnIgnore("reason")]` remains the escape hatch. ## Deferred catalog @@ -122,14 +313,16 @@ Surveyed and explicitly **not** first — recorded so the ideas aren't lost: `Add: Expr -> Expr -> Expr`). Only if a typed AST / DSL / query-builder need appears — relevant to the Snipper / Reactor / AST-transform ideas, not before. -- **Phantom types** — already in scope, as the underlying mechanism behind brands. +- **Phantom types** — already in scope, as the underlying mechanism behind + brands (pillar 1, including strongly typed IDs). - **Higher-kinded types** (abstract over `F<_>`: Functor / Monad). Do not touch: assembling a spaceship out of `IEnumerable`, `Task`, and pain. - **Row types** ("an object with at least these fields"), **existential types** ("there is some hidden `T`" — plugin/handler systems, heterogeneous - collections), **intersection `A & B`** / **union `A | B`** types, and - **gradual typing** (strict + dynamic mixed; the risk is `any` spreading until - the type system is a decorative quality sticker). + collections), **intersection `A & B`** / **union `A | B`** types (the + type-theory *union*, distinct from pillar 2's closed-shape `union` + declaration), and **gradual typing** (strict + dynamic mixed; the risk is + `any` spreading until the type system is a decorative quality sticker). - **Modal types** (`Html`, `Sql`, `sanitize: Html -> Html`) and **indexed types** (`Buffer`, `Password`, pipeline @@ -137,14 +330,16 @@ Surveyed and explicitly **not** first — recorded so the ideas aren't lost: with branded + typestate, so they may fall out for free once those two land. Priority, most-applied → academic tail: **branded/opaque · units of measure · -typestate · refinement · effect types (P-008) · session types · phantom**, then -**dependent / GADT / HKT** as the cognitively expensive end. +typestate · refinement · discriminated unions/exhaustive matching · effect types +(P-008) · session types · phantom**, then **dependent / GADT / HKT** as the +cognitively expensive end. ## Open questions 1. **Surface:** analyzer-only (annotate C# in place) vs `.own` spec + source - generator vs both. Brands and refinements want a generator (smart - constructors); typestate wants the analyzer + the affine core. + generator vs both. Brands, refinements, and unions want a generator (smart + constructors, exhaustive-match helpers); typestate wants the analyzer + the + affine core. 2. **Where do brands live** relative to P-006 capabilities — is a capability just a branded, non-`Copy` resource token, or its own kind? 3. **Refinement strength:** syntactic predicate enforced at the constructor @@ -152,6 +347,17 @@ typestate · refinement · effect types (P-008) · session types · phantom**, t verification backend, P-002). v0 should be the former. 4. **Typestate ↔ ownership seam:** confirm transitions express consume-self through the *existing* affine facts, so `commit` then `rollback` is reported as - use-after-move by the one core — no second mechanism. + use-after-move by the one core — no second mechanism. Pillar 3's items 10–13 + are the concrete test of this seam: each must reduce to it, not spawn a + parallel one. 5. Do **modal/indexed** types ever need their own surface, or are they always reducible to brand + typestate in practice? +6. **Diagnostic prefix.** `DI`, `EFF`, and `OBL` are established per-pillar + families (see `ownlang/diagnostics.py`). Does Own.Types reserve `TYP0xx` the + same way, or fold into core `OWN` codes with a `[type: …]` kind tag mirroring + the existing `[resource: …]` tag? Reserving `TYP` now avoids repeating the + `WPFxxx`-catalog-vs-emitted-code confusion recorded in + `docs/notes/consolidation-and-positioning.md`. +7. Do discriminated-union exhaustiveness (#6) and `Option`/`Result` unwrap-safety + (#7, #8) stay one analyzer rule family or split into separate rules sharing + only the source-generator scaffolding? From d78c13d90aaa3fd01d679badffd51288edffa8d3 Mon Sep 17 00:00:00 2001 From: PhysShell <45852143+PhysShell@users.noreply.github.com> Date: Sun, 5 Jul 2026 13:02:23 +0500 Subject: [PATCH 14/25] docs(proposals): add P-026 naughty-strings robustness pack MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Propose using the Big List of Naughty Strings (BLNS) as a pinned fixture corpus to crash-test the extractor/lexer/parser, the JSON/SARIF emitters, and the CLI against adversarial input text — motivated by the cp1251 console crash OwnAudit already hit once. --- .../P-026-naughty-strings-testing.md | 166 ++++++++++++++++++ docs/proposals/README.md | 1 + 2 files changed, 167 insertions(+) create mode 100644 docs/proposals/P-026-naughty-strings-testing.md diff --git a/docs/proposals/P-026-naughty-strings-testing.md b/docs/proposals/P-026-naughty-strings-testing.md new file mode 100644 index 00000000..054bdb95 --- /dev/null +++ b/docs/proposals/P-026-naughty-strings-testing.md @@ -0,0 +1,166 @@ +# P-026 — Naughty-strings robustness pack (BLNS-driven crash testing) + +- **Status:** draft +- **Depends on / relates to:** + - [P-001](P-001-csharp-extractor.md) — the C# → OwnIR extractor: the thing that + has to survive arbitrary third-party source text in the first place. + - [P-012](P-012-bug-corpus-mining.md) — same "curated corpus, gated in CI" + shape, but keyed by **string content**, not by bug pattern; orthogonal, not a + replacement. + - [P-015](P-015-configuration-surface.md) — the future `own.toml`/`.ownrc` + config surface; config discovery has to survive naughty paths/globs too. + - [P-024](P-024-security-audit-profile.md) — same "берём готовое, не + изобретаем свою эвристику" instinct (adopt an existing corpus instead of + hand-rolling a dozen Unicode edge cases), but explicitly **not** a security + profile — see Non-goals. + +## Motivation + +Own.NET's whole value proposition is running against real, uncontrolled legacy +C#/WPF/DevExpress code: arbitrary identifiers, string literals, resource +strings, file and project paths chosen by other people over twenty years. The +project's own honest-skip philosophy (`docs/ROADMAP.md`) already treats "the +checker doesn't know" as an acceptable, first-class outcome — but a **crash** +is not "doesn't know," it's the tool falling over on a customer's codebase, +which is strictly worse than a missed diagnostic. + +Today robustness is exercised only by *valid* fixtures — `corpus/wpf/`, +`corpus/real-world/`, `tests/fixtures/` — plus whatever `ParseError`/`LexError` +paths (`ownlang/lexer.py`, `ownlang/parser.py`) happen to be hit incidentally. +Nothing in the suite deliberately throws adversarial *text* at the extractor, +the JSON/SARIF emitters (`ownlang/cfg_json.py`, `ownlang/diag_sarif.py`, +`ownlang/diagnostics.py`, `ownlang/report.py`), or the CLI (`ownlang/__main__.py`) +— zero-width joiners, RTL/LTR override characters, unpaired surrogates, SQL/ +XSS-shaped strings sitting inert inside a C# string literal, absurdly long +lines, mixed line endings, strings that are themselves valid-looking JSON or +XML, "your kernel just crashed"-style command injection payloads. And this +class of bug is not hypothetical here: `OwnAudit/Run-Audit.ps1` already carries +a scar from exactly this — `PYTHONUTF8=1` is set specifically to dodge a +**cp1251 console crash on a Russian-locale Windows target**. That is one +instance of the bug class BLNS exists to catch *systematically*, found the hard +way instead of by test. + +[Big List of Naughty Strings](https://github.com/minimaxir/big-list-of-naughty-strings) +(BLNS) is a maintained, MIT-licensed corpus built for exactly this: ~500 strings +(Unicode edge cases, escaping/injection-shaped strings, whitespace and +line-ending oddities, format-breakers for JSON/XML/CSV/SQL/shell), shipped as a +plain `blns.json` array plus a `.NET` port (`NaughtyStrings` NuGet package) for +the C#-side pieces (`audit/`'s eventual C# on lift-out, per +[`OwnAudit/README.md`](https://github.com/PhysShell/OwnAudit)). BLNS itself is +explicit that it is not a substitute for real security testing (see +Non-goals) — its contract here is narrower and cheaper: **the tool must not +crash, hang, or corrupt output on any string in the corpus.** + +## Scope + +1. **Vendor the corpus.** A pinned, static copy of `blns.json` (upstream tag/ + commit recorded in a comment) as a fixture, e.g. + `tests/fixtures/blns.json` — no network fetch at test time, no submodule + (matches the project's existing "no external runtime deps beyond stdlib" + posture in the Python core). + +2. **Layer 1 — lexer/parser/extractor.** Parametrize over every BLNS entry, + embedding it as: (a) `.own` string-literal content, (b) a C# string literal + fed through the P-001 extractor, (c) a file/module name passed on the CLI. + Assert only: no unhandled exception escapes `ownlang/lexer.py` / + `ownlang/parser.py` / the extractor; the *only* acceptable failure shapes + are `LexError`/`ParseError` (or the extractor's own diagnostic-and-skip + path) — never a raw traceback, never a hang past a fixed timeout. + +3. **Layer 2 — serialization.** Pipe BLNS content through + `ownlang/diagnostics.py` → `ownlang/diag_sarif.py` / `ownlang/cfg_json.py` / + `ownlang/report.py` (as a synthesized finding message / file path / symbol + name) and assert the emitted JSON/SARIF/Markdown is well-formed + (round-trips through a JSON/SARIF parser) with no crash — this is the + layer `test_cfg_json.py` / `test_diag_sarif.py` already exercise for valid + input; this proposal is the adversarial-input twin. + +4. **Layer 3 — CLI & future config.** `ownlang/__main__.py` argument/path + handling, and (when [P-015](P-015-configuration-surface.md) lands) `own.toml` + discovery, given BLNS-flavored file names, directory names, and glob + patterns. + +5. **Land as one hermetic, parametrized module** — + `tests/test_naughty_strings.py` — wired into `tests/run_tests.py` and CI the + same way `tests/test_corpus.py` is: fast, offline, property-style + ("must not crash," not "must produce code X"). + +6. **Follow-on, not in v0:** an equivalent pass over `OwnAudit`'s SARIF + ingestion / `artifacts/health-report.*` rendering, since that's the other + place free text from arbitrary source flows into output — deferred because + it crosses the repo boundary and OwnAudit already treats SARIF as its + external contract. + +## Non-goals + +- **Not a security test / pentest substitute.** BLNS's own README says the + same. This proposal claims only "does not crash / does not corrupt state on + adversarial text" — nothing about exploitability, authorization, or network + surface. That territory is [P-024](P-024-security-audit-profile.md)'s, and + this proposal does not overlap it: no scanning, no CVE claims, no new + security-flavored diagnostic codes. +- **Not a new checker or diagnostic.** No new `OWN0NN` code, no severity + change, no touch to ownership/lifetime semantics. Purely a regression/ + robustness harness around existing entry points. +- **Not coverage-guided fuzzing.** That is `007`'s `fuzz/` (cargo-fuzz) + territory on the eventual Rust core (P-022) — an open-ended search for novel + crashes. This is a fixed, curated, deterministic corpus, cheap enough to run + on every commit, not a campaign. +- **Not "every naughty string gets a pretty diagnostic."** The honest-skip / + `ParseError` contract is sufficient; the property under test is "no crash, + no hang, no corrupted output," not "graceful handling with a nice message" + for all ~500 entries. +- **Does not change the `corpus/` layout** (`before.cs`/`after.cs`/`case.own`) + from P-012 — BLNS fixtures are a separate, orthogonal corpus keyed by string + content, not by bug pattern, and live under `tests/fixtures/`, not `corpus/`. + +## Sketch + +```text +tests/fixtures/blns.json # vendored, pinned copy (upstream commit noted) +tests/test_naughty_strings.py # parametrized over every entry, 3 layers above +``` + +```python +import json, os, pytest +from ownlang.lexer import LexError +from ownlang.parser import ParseError, parse + +with open(os.path.join(os.path.dirname(__file__), "fixtures", "blns.json"), + encoding="utf-8") as f: + BLNS = json.load(f) + +@pytest.mark.parametrize("naughty", BLNS) +def test_parser_does_not_crash(naughty): + src = f'resource R;\nfn f() {{ let s = "{naughty}"; }}\n' + try: + parse(src) + except (ParseError, LexError): + pass # an honest rejection is fine; anything else is a bug +``` + +Serialization side follows the same shape against `diag_sarif.py` / +`cfg_json.py`, asserting `json.loads(...)` / a SARIF-shape check succeeds. + +## Open questions + +1. **Generation vs. fixture files.** Synthesize `.own`/`.cs` source around each + BLNS entry on the fly (parametrized, no repo bloat — the sketch above) vs. + materializing ~500 tiny fixture files. Leaning generation; only fall back to + files if a specific entry needs a shape the generator can't express. +2. **Timeout bound.** Several BLNS entries are specifically shaped to blow up + naive parsers (repetition/expansion strings). What per-case wall-clock + cutoff counts as "hung" in CI? +3. **Encoding boundary.** Is UTF-8 the only contract for this pack, or does + OwnAudit's cp1251-console incident warrant its own explicit BLNS pass over + the PowerShell/console path, given it already burned once? +4. **Vendoring mechanics.** Pinned static copy of `blns.json` (simple, matches + current no-submodule posture) vs. a `scripts/` updater that re-fetches on + demand — leaning static copy with the upstream commit noted in a header + comment. +5. **Rust-core inheritance.** When/if P-022's Rust core lands with a + differential oracle (Python = golden), does this pack become a shared input + fed to both sides rather than a Python-only test? +6. **OwnAudit follow-on timing.** Scope item 6 defers the OwnAudit-side pass — + confirm that's the right call now vs. folding it in immediately given the + cp1251 precedent already lives there. diff --git a/docs/proposals/README.md b/docs/proposals/README.md index b382cd7f..3652004f 100644 --- a/docs/proposals/README.md +++ b/docs/proposals/README.md @@ -45,6 +45,7 @@ proposal is marked `done` with a pointer. | [P-023](P-023-architecture-guard.md) | Architecture guard (`Own.Arch`): rules.yaml intent model + dependency-graph gate + baseline ratchet | draft | | [P-024](P-024-security-audit-profile.md) | Security audit profile (external tools + SARIF adapters; rejects own scanner engine) | draft | | [P-025](P-025-obligation-protocols.md) | Obligation protocols (`Own.Protocols`): barrier-sensitive project invariants (OBL001–005) | first slice built (core + bridge + fixtures; extractor pending) | +| [P-026](P-026-naughty-strings-testing.md) | Naughty-strings robustness pack (BLNS-driven crash testing of extractor/serializers/CLI) | draft | > For priorities, milestones, the framing, and the design philosophy across all > of these, see the strategy hub: [`docs/ROADMAP.md`](../ROADMAP.md). P-004 … P-016 From a6d906c782e170fe13f0cb8a50672917f71e424d Mon Sep 17 00:00:00 2001 From: Claude Date: Sun, 5 Jul 2026 08:03:11 +0000 Subject: [PATCH 15/25] docs(proposals): P-026 project resource model files Externalizes the compiled-in resource-kind table (ownlang/ownir.py _prelude_resources) into a declarative, symbol-resolved project config so a codebase can teach the extractor its own acquire/release conventions (e.g. ConnectionFactory.Open()/Connection.Close()) without a new hardcoded classifier in Program.cs. Grounded against P-014's semantic-resolution discipline and P-015/P-024's precedents so it stays a fact producer, not a second detection DSL. --- docs/proposals/P-026-resource-model-files.md | 203 +++++++++++++++++++ docs/proposals/README.md | 1 + 2 files changed, 204 insertions(+) create mode 100644 docs/proposals/P-026-resource-model-files.md diff --git a/docs/proposals/P-026-resource-model-files.md b/docs/proposals/P-026-resource-model-files.md new file mode 100644 index 00000000..2e12a25e --- /dev/null +++ b/docs/proposals/P-026-resource-model-files.md @@ -0,0 +1,203 @@ +# P-026 — Project resource model files (declarative acquire/release/capture) + +- **Status:** draft +- **Depends on:** + - [P-001](P-001-csharp-extractor.md) — the Roslyn extractor → OwnIR seam this + proposal feeds; it adds a new *fact producer*, not a new fact shape. + - [P-014](P-014-semantic-resolution.md) — the project-local `SemanticModel` + resolution this proposal reuses verbatim: a model entry is bound to a real + symbol the same way a bare `+=` is bound to a real `IEventSymbol` today, with + the same honest-skip discipline (unresolved → **OWN050**, never a guess). + - `ownlang/ownir.py` `_prelude_resources()` (the built-in `Subscription` / + `Timer` / `Disposable` / `PooledBuffer` table, lines ~801-821) — the existing, + but **compiled-in**, form of exactly this idea. This proposal externalizes the + *extractor-side* half of that table (which real C# call sites feed a kind), + not the core lowering, which is untouched. + - [P-015](P-015-configuration-surface.md) — the sibling config surface + (check on/off, severity). Deliberately **not the same file**: see + *Relationship to P-015* below. + - [P-024](P-024-security-audit-profile.md) — the precedent this proposal must + not repeat. See *Relationship to P-024* below. +- **Strategy hub:** [`docs/ROADMAP.md`](../ROADMAP.md). + +## Motivation + +Every resource kind Own.NET knows about today — `Subscription` (`event +=`/`-=`), +`Timer` (`DispatcherTimer.Start`/`Stop`), `Disposable` (`new` / `Dispose`), +`PooledBuffer` (`ArrayPool.Rent`/`Return`) — is recognised by a **hardcoded C# +syntax classifier** inside `frontend/roslyn/OwnSharp.Extractor/Program.cs` (a +single ~4,400-line file; see the split deferred in +[`consolidation-and-positioning.md`](../notes/consolidation-and-positioning.md)). +The mapping from "this call is an acquire of that kind" to an OwnIR fact lives in +C# `if`/`switch` logic, not in data. + +That is fine for patterns common enough to justify a core classifier (an `event`, +`ArrayPool`). It breaks down for the pattern every real codebase has at least +one of: an **in-house** acquire/release pair that is semantically identical to +`Subscribe`/`Dispose` but syntactically invisible to the extractor — +`ConnectionFactory.Open()` / `Connection.Close()`, `Registry.RegisterCallback()` / +the returned token's `.Unregister()`, a legacy `EventBus.Subscribe(...)` that +predates `IObservable`. Today the only way to catch a leak of *that* resource +is to add a new hardcoded classifier to the extractor and cut a new Own.NET +release — the exact "teach the analyzer your project's `OpenDbConnection()` +convention" capability Coverity ships as **custom models**, which Own.NET has no +equivalent of. + +This is a real gap, not a nice-to-have: the fleet's own real-world corpus already +has cases whose bug class (a project-local `Close`/`Dispose` convention Own.NET +doesn't know) would be a model-file entry rather than a new core classifier, if +this surface existed. + +## Scope + +A discovered, versioned, project-local file — working name `own.models.yaml` +(location/format TBD, see *Open questions*) — that declares **additional** +acquire/release/capture sites for the existing OwnIR resource vocabulary, in +terms of real bound symbols, not source-text patterns: + +```yaml +resources: + DbConnection: + kind: "db connection" # the [resource: ...] tag on a finding + acquire: + - method: "MyApp.Data.ConnectionFactory.Open" + release: + - method: "MyApp.Data.Connection.Close" + - method: "System.IDisposable.Dispose" # already-known BCL release still allowed + + LegacyBusToken: + kind: "subscription token" + acquire: + - method: "MyApp.Messaging.EventBus.Subscribe" + release: + - method: "MyApp.Messaging.SubscriptionToken.Unregister" +``` + +Two shapes only, each mapping onto a fact the core already understands (no new +`ownlang` code path — this is purely a new fact *producer* at the extractor +edge, exactly like adding one more entry to `_prelude_resources()`, but sourced +from a project file instead of compiled in): + +1. **Paired method acquire/release** — `method:` names a call by its canonical + Roslyn symbol display string. The extractor resolves it through the same + project-local `CSharpCompilation`/`SemanticModel` P-014 already builds (plus + `--ref-dir` for third-party assemblies); a call site binding to that exact + symbol emits an `acquire`/`release` OwnIR record tagged with the declared + `kind`, indistinguishable from a hardcoded one downstream. +2. **Event-shaped add/remove pair** — for project APIs that mimic `+=`/`-=` + semantically (a custom `Subscribe`/`Unsubscribe` pair) without being a real + C# `event`, reusing the existing subscription-fact shape. + +## Non-goals + +- **Not a detection language.** Every entry is a **symbol reference resolved by + the compiler**, never a regex or glob over source text or identifier names. An + unresolved entry (typo, renamed method, unreferenced assembly) is skipped with + the same **OWN050**-style advisory P-014 already uses for an unresolved event — + never a silent guess, never a string-match fallback. This is the line that + keeps this proposal apart from the *Own.SecurityChecks* idea P-024 permanently + rejected — see below. +- **Not a new severity/config surface.** A project resource kind reuses the + existing generic families (**OWN001** leak, **OWN002** use-after-release, + **OWN003** double-release, **OWN014** region escape) with its own + `[resource: ]` tag, exactly as WPF/DI/Pool do today. No per-kind custom + diagnostic codes, no new severity tier — that axis is P-015's. +- **Not a replacement for the built-in kinds.** `Subscription`/`Timer`/ + `Disposable`/`PooledBuffer` and the DI registration graph stay first-class, + shipped-by-default core classifiers. Model files are strictly **additive**, + for the project-specific long tail the fleet cannot pre-populate. +- **Not a registry or marketplace.** One file, local to the repo, under version + control — the `.editorconfig` model, not an npm-style package ecosystem. +- **Not general aliasing or a new lifetime-ordering DSL.** No way to declare a + custom region ladder (that stays the built-in `OWN014` ordering); the only + expressiveness added is *which symbols count as acquire/release/subscribe* for + a kind, not new semantics for what "leak" or "escape" means. + +## Relationship to P-015 + +P-015 is the sibling axis, not the same file: it turns existing findings +on/off and reweights severity per **category** (`.ownrc`/`own.toml`, format TBD) +and explicitly disclaims being "a query/policy language — it is a settings +file." This proposal is squarely that disclaimed territory, deliberately kept in +its **own** file: `own.models.yaml` declares *what counts as a resource fact* (an +extractor-input concern), while P-015 decides *what to do with a fact once +emitted* (a core-output concern). They compose — a project-declared kind is just +another category name in P-015's vocabulary — but conflating the two files would +turn the settings file into exactly the policy language P-015 rejects. If P-015 +ships first, unifying discovery (one nearest-config walk-up) is worth revisiting; +until then they are independent, optional files. + +## Relationship to P-024 (why this is not the rejected DSL) + +P-024 permanently rejected *Own.SecurityChecks* — a custom YAML DSL of +request/response matchers over banners/config, because its checks were +"regex-over-config" and "regex-over-banner": free-text pattern matching with no +semantic grounding, which is an intrinsic false-positive/false-negative +generator (a version regex misses `1.1.0`, over-fires on backports, etc.) and a +second, competing decision-maker outside the one core. + +This proposal's model entries are the opposite shape: they name a symbol, which +the Roslyn `SemanticModel` either **resolves to one real, unambiguous method / +event** or does not — there is no partial match, no pattern language, no text +scanning. Precision is inherited from the same binder P-014 already trusts for +the built-in kinds; a project model entry is exactly as sound (or as silently +skipped) as a hardcoded classifier would be for the same symbol. The verdict +authority stays exactly where P-013's "one checker" discipline puts it — the +Python core, deciding leak/escape over OwnIR facts — this proposal only widens +which C# call sites *produce* those facts. Nothing about severity, wording, or +the leak/escape verdict itself is decided in the model file. + +## Sketch + +```text +own.models.yaml (project root) + │ discovered + parsed by the extractor at startup + ▼ +ModelLoader: for each declared `method:` / event pair, + resolve via the SAME project-local SemanticModel P-014 builds + (framework refs + --ref-dir) — unresolved entry -> OWN050-style warning, skip + │ resolved entries behave exactly like a built-in classifier hit + ▼ +existing acquire/release/capture OwnIR fact emission (unchanged) + │ + ▼ +ownlang core (unchanged): OWN001/002/003/014 over the widened fact set, + `[resource: ]` tag on the finding +``` + +No change to `ownlang/`, `spec/OwnIR.md`, or the JSON schema — a project kind is +indistinguishable, downstream of fact emission, from a built-in one. The only +new component is a small loader in the Roslyn extractor that turns YAML entries +into the same internal classifier shape `Program.cs`'s hardcoded ones already +use (a natural companion to the extractor split noted as deferred in +`consolidation-and-positioning.md` — this is one more reason that split earns +its keep once this lands). + +## Open questions + +1. **File location & format.** Repo-root `own.models.yaml` discovered + independently (v0), or folded into P-015's future `.ownrc`/`own.toml` once + that ships? Leaning: ship independently now (P-015 is still a draft stub), + revisit unification later. +2. **Overload granularity.** Does `method: "T.M"` mean the whole method group or + one exact overload? Real conventions (`Open()` vs `Open(string)`) may need a + parameter-list suffix; start with "whole group," add signature narrowing if a + real corpus case needs it. +3. **Cross-type acquire/release.** `ConnectionFactory.Open()` returns a + `Connection`, but `release` is a method *on the returned type*, not on the + acquiring type — same shape the built-in `Disposable` kind already handles + (`new` on one type, `Dispose` on the value). Confirm the schema states + acquire/release independently (as sketched above) rather than requiring a + `release_on:` cross-reference; the built-in table's precedent says + independent declarations are enough. +4. **Scope of the WPF component heuristic.** Do project-declared kinds + participate in the `ViewModel`/`View`-shaped component detection P-004 uses, + or are they type-agnostic (fire anywhere a resolved acquire/release pair is + unbalanced, regardless of enclosing class shape)? Leaning: type-agnostic — + the WPF heuristic is specific to the *subscription-outlives-source* escape + judgment (OWN014), not to the ordinary leak judgment (OWN001) a custom + resource kind mostly needs. +5. **Silent staleness.** A renamed method quietly drops its model entry to + "always unresolved." Should the extractor emit one summary warning per run + listing every model entry it could not bind (so CI catches drift), the same + way an unresolved `--ref-dir` DLL is reported today? diff --git a/docs/proposals/README.md b/docs/proposals/README.md index b382cd7f..41710d98 100644 --- a/docs/proposals/README.md +++ b/docs/proposals/README.md @@ -45,6 +45,7 @@ proposal is marked `done` with a pointer. | [P-023](P-023-architecture-guard.md) | Architecture guard (`Own.Arch`): rules.yaml intent model + dependency-graph gate + baseline ratchet | draft | | [P-024](P-024-security-audit-profile.md) | Security audit profile (external tools + SARIF adapters; rejects own scanner engine) | draft | | [P-025](P-025-obligation-protocols.md) | Obligation protocols (`Own.Protocols`): barrier-sensitive project invariants (OBL001–005) | first slice built (core + bridge + fixtures; extractor pending) | +| [P-026](P-026-resource-model-files.md) | Project resource model files (declarative acquire/release/capture, symbol-resolved) | draft | > For priorities, milestones, the framing, and the design philosophy across all > of these, see the strategy hub: [`docs/ROADMAP.md`](../ROADMAP.md). P-004 … P-016 From 96238edd421074162d51e38b89434aa2755a182b Mon Sep 17 00:00:00 2001 From: PhysShell <45852143+PhysShell@users.noreply.github.com> Date: Sun, 5 Jul 2026 13:09:05 +0500 Subject: [PATCH 16/25] docs(proposals): widen P-026 index title per review CodeRabbit: the row only named extractor/serializers/CLI but the proposal also covers lexer/parser and config discovery. --- docs/proposals/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/proposals/README.md b/docs/proposals/README.md index 3652004f..9424963f 100644 --- a/docs/proposals/README.md +++ b/docs/proposals/README.md @@ -45,7 +45,7 @@ proposal is marked `done` with a pointer. | [P-023](P-023-architecture-guard.md) | Architecture guard (`Own.Arch`): rules.yaml intent model + dependency-graph gate + baseline ratchet | draft | | [P-024](P-024-security-audit-profile.md) | Security audit profile (external tools + SARIF adapters; rejects own scanner engine) | draft | | [P-025](P-025-obligation-protocols.md) | Obligation protocols (`Own.Protocols`): barrier-sensitive project invariants (OBL001–005) | first slice built (core + bridge + fixtures; extractor pending) | -| [P-026](P-026-naughty-strings-testing.md) | Naughty-strings robustness pack (BLNS-driven crash testing of extractor/serializers/CLI) | draft | +| [P-026](P-026-naughty-strings-testing.md) | Naughty-strings robustness pack (BLNS-driven crash testing of lexer/parser/extractor/serializers/CLI/config) | draft | > For priorities, milestones, the framing, and the design philosophy across all > of these, see the strategy hub: [`docs/ROADMAP.md`](../ROADMAP.md). P-004 … P-016 From b3324894ddb7b2f48582d4f1b8670f01da50f01f Mon Sep 17 00:00:00 2001 From: PhysShell <45852143+PhysShell@users.noreply.github.com> Date: Sun, 5 Jul 2026 13:09:47 +0500 Subject: [PATCH 17/25] docs(proposals): spell out Layer 3's failure contract MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit CodeRabbit: Layer 1 named its acceptable failure shapes but Layer 3 (CLI/path/future config) didn't. Name the I/O exceptions that count as an honest rejection, mirroring cmd_explain's existing OSError catch, and flag that ownlang/__main__.py's _read() catches nothing today — so a bad path is a live candidate for the very first red case, not a hypothetical. --- docs/proposals/P-026-naughty-strings-testing.md | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/docs/proposals/P-026-naughty-strings-testing.md b/docs/proposals/P-026-naughty-strings-testing.md index 054bdb95..0634e56f 100644 --- a/docs/proposals/P-026-naughty-strings-testing.md +++ b/docs/proposals/P-026-naughty-strings-testing.md @@ -78,7 +78,17 @@ crash, hang, or corrupt output on any string in the corpus.** 4. **Layer 3 — CLI & future config.** `ownlang/__main__.py` argument/path handling, and (when [P-015](P-015-configuration-surface.md) lands) `own.toml` discovery, given BLNS-flavored file names, directory names, and glob - patterns. + patterns. Same contract as Layer 1, spelled out for I/O: the *only* + acceptable rejections are `FileNotFoundError` / `IsADirectoryError` / + `PermissionError` / `UnicodeDecodeError` / `OSError` surfaced as a clean CLI + error — the shape `cmd_explain`'s `--json` path already uses + (`except (OSError, json.JSONDecodeError)`) — and, once P-015 lands, a + documented config-parse error; never a raw traceback, never a hang past a + fixed timeout. This pack is expected to *find*, not assume, that contract: + today `_read()` — the path opener behind `check`/`emit`/`cfg`/`report` — + catches nothing, so a BLNS-flavored path (a null byte, an unpaired + surrogate, a name that turns out to be a directory) is a live candidate for + turning this layer red on day one, not a hypothetical. 5. **Land as one hermetic, parametrized module** — `tests/test_naughty_strings.py` — wired into `tests/run_tests.py` and CI the From 9ae499af68c43a86169a27fc4b923ba5a65fecb0 Mon Sep 17 00:00:00 2001 From: Claude Date: Sun, 5 Jul 2026 08:12:20 +0000 Subject: [PATCH 18/25] =?UTF-8?q?docs(proposals):=20P-026=20=E2=80=94=20bi?= =?UTF-8?q?nd=20to=20existing=20OwnIR=20resource=20kinds,=20not=20new=20on?= =?UTF-8?q?es?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Codex review on #186 caught it: the resource discriminator and its [resource: ...] tag are a closed, fixed-mapping vocabulary (ownlang/ownir.py::_KNOWN_RESOURCE_KINDS/_RESOURCES, pinned by spec/ownir.schema.json) that load() rejects unknown values for. v0 now explicitly binds project model entries to an existing discriminator instead of claiming a free-text kind/tag, with the precision loss and the scoped-out follow-up (an additive display_kind field) called out as Open Question 6. --- docs/proposals/P-026-resource-model-files.md | 83 ++++++++++++++------ 1 file changed, 61 insertions(+), 22 deletions(-) diff --git a/docs/proposals/P-026-resource-model-files.md b/docs/proposals/P-026-resource-model-files.md index 2e12a25e..120573dd 100644 --- a/docs/proposals/P-026-resource-model-files.md +++ b/docs/proposals/P-026-resource-model-files.md @@ -13,6 +13,14 @@ but **compiled-in**, form of exactly this idea. This proposal externalizes the *extractor-side* half of that table (which real C# call sites feed a kind), not the core lowering, which is untouched. + - `ownlang/ownir.py` `_KNOWN_RESOURCE_KINDS` / `_RESOURCES` and + `spec/ownir.schema.json`'s `resourceKind` — the **closed** discriminator + vocabulary (`subscription` / `subscribe` / `timer` / `disposable` / + `local-disposable` / `pool`, plus `capture` / `unresolved-subscription`) with + a **fixed** `resource → [resource: tag]` mapping (spec/OwnIR.md §4); + `load()` rejects an unknown discriminator (fail-loud). v0 of this proposal + binds to that vocabulary rather than extending it — see *Scope* and the + *Non-goal* on minting new kinds/tags. - [P-015](P-015-configuration-surface.md) — the sibling config surface (check on/off, severity). Deliberately **not the same file**: see *Relationship to P-015* below. @@ -52,41 +60,53 @@ this surface existed. A discovered, versioned, project-local file — working name `own.models.yaml` (location/format TBD, see *Open questions*) — that declares **additional** -acquire/release/capture sites for the existing OwnIR resource vocabulary, in -terms of real bound symbols, not source-text patterns: +acquire/release/capture *call sites* for the **existing, closed** OwnIR +`resource` discriminators, in terms of real bound symbols, not source-text +patterns. v0 does **not** mint new discriminators or new `[resource: …]` tags +(both are closed vocabulary — see *Non-goals*); a model entry picks which +already-known analysis path (and its already-fixed tag) a project-specific call +pair feeds: ```yaml resources: - DbConnection: - kind: "db connection" # the [resource: ...] tag on a finding + - name: DbConnection # documentation label only — never emitted + resource: disposable # existing discriminator -> fixed tag "disposable field" acquire: - method: "MyApp.Data.ConnectionFactory.Open" release: - method: "MyApp.Data.Connection.Close" - method: "System.IDisposable.Dispose" # already-known BCL release still allowed - LegacyBusToken: - kind: "subscription token" + - name: LegacyBusToken + resource: subscribe # existing discriminator -> fixed tag "subscription token" acquire: - method: "MyApp.Messaging.EventBus.Subscribe" release: - method: "MyApp.Messaging.SubscriptionToken.Unregister" ``` -Two shapes only, each mapping onto a fact the core already understands (no new -`ownlang` code path — this is purely a new fact *producer* at the extractor -edge, exactly like adding one more entry to `_prelude_resources()`, but sourced -from a project file instead of compiled in): +A finding on `DbConnection` above reads `[resource: disposable field]`, not +`[resource: db connection]` — the same tag an ordinary hardcoded `disposable` +hit would carry. That precision loss (the message names the *bucket*, not the +project's own vocabulary) is the price of zero core/schema change in v0; see +Open Question 6 for the natural, separately-scoped follow-up. + +Two call shapes only, each binding onto a fact the core already understands (no +new `ownlang` code path — this is purely a new fact *producer* at the extractor +edge, choosing an existing `resource:` value, exactly like routing one more +call site into an existing entry of `_prelude_resources()`'s table instead of +compiling it in): 1. **Paired method acquire/release** — `method:` names a call by its canonical Roslyn symbol display string. The extractor resolves it through the same project-local `CSharpCompilation`/`SemanticModel` P-014 already builds (plus `--ref-dir` for third-party assemblies); a call site binding to that exact - symbol emits an `acquire`/`release` OwnIR record tagged with the declared - `kind`, indistinguishable from a hardcoded one downstream. + symbol emits an `acquire`/`release` OwnIR record under the declared + `resource:` discriminator, indistinguishable downstream from a hardcoded hit + on that same discriminator. 2. **Event-shaped add/remove pair** — for project APIs that mimic `+=`/`-=` semantically (a custom `Subscribe`/`Unsubscribe` pair) without being a real - C# `event`, reusing the existing subscription-fact shape. + C# `event`, reusing the existing `subscribe`/`subscription` fact shape. ## Non-goals @@ -112,6 +132,15 @@ from a project file instead of compiled in): custom region ladder (that stays the built-in `OWN014` ordering); the only expressiveness added is *which symbols count as acquire/release/subscribe* for a kind, not new semantics for what "leak" or "escape" means. +- **Not a way to mint new `resource` discriminators or new `[resource: …]` + display tags in v0.** Both are closed vocabulary today — + `ownlang/ownir.py::_KNOWN_RESOURCE_KINDS`/`_RESOURCES`, pinned by + `spec/ownir.schema.json`'s `resourceKind` — and `load()` fails loud on an + unknown value; adding a genuinely new one is a core vocabulary change that + must bump `OWNIR_VERSION` (spec/OwnIR.md §2), which this proposal does not + attempt. A model entry **binds** a project's call sites to one of the + existing discriminators (and inherits its existing fixed tag); it does not + extend the enum. See Open Question 6 for the scoped-out follow-up that would. ## Relationship to P-015 @@ -156,22 +185,23 @@ own.models.yaml (project root) ModelLoader: for each declared `method:` / event pair, resolve via the SAME project-local SemanticModel P-014 builds (framework refs + --ref-dir) — unresolved entry -> OWN050-style warning, skip - │ resolved entries behave exactly like a built-in classifier hit + │ resolved entries route into the declared EXISTING `resource:` bucket ▼ existing acquire/release/capture OwnIR fact emission (unchanged) │ ▼ ownlang core (unchanged): OWN001/002/003/014 over the widened fact set, - `[resource: ]` tag on the finding + the bucket's already-fixed `[resource: …]` tag on the finding ``` -No change to `ownlang/`, `spec/OwnIR.md`, or the JSON schema — a project kind is -indistinguishable, downstream of fact emission, from a built-in one. The only -new component is a small loader in the Roslyn extractor that turns YAML entries -into the same internal classifier shape `Program.cs`'s hardcoded ones already -use (a natural companion to the extractor split noted as deferred in -`consolidation-and-positioning.md` — this is one more reason that split earns -its keep once this lands). +No change to `ownlang/`, `spec/OwnIR.md`, or the JSON schema in v0 — a +project-bound call site is indistinguishable, downstream of fact emission, from +a hardcoded hit on the *same* discriminator (it is not a new discriminator; see +the Non-goal above). The only new component is a small loader in the Roslyn +extractor that turns YAML entries into the same internal classifier shape +`Program.cs`'s hardcoded ones already use (a natural companion to the extractor +split noted as deferred in `consolidation-and-positioning.md` — this is one +more reason that split earns its keep once this lands). ## Open questions @@ -201,3 +231,12 @@ its keep once this lands). "always unresolved." Should the extractor emit one summary warning per run listing every model entry it could not bind (so CI catches drift), the same way an unresolved `--ref-dir` DLL is reported today? +6. **A free-text display tag, later.** v0 accepts the precision loss of + reusing an existing discriminator's fixed tag (`DbConnection` reads + `[resource: disposable field]`). A follow-up **could** add a small, + optional, additive `resourceRecord` field (e.g. `display_kind: string`) that + overrides only the rendered tag, without touching the closed `resource` + discriminator or its analysis-path routing — that is a genuinely additive + OwnIR schema change (§2), unlike minting a new discriminator, and would be + its own small PR against `spec/ownir.schema.json` + `ownlang/ownir.py`, not + a prerequisite for this proposal's v0. From 2470ba7a65f4823116ccbffee50b63f02aa72dca Mon Sep 17 00:00:00 2001 From: PhysShell <45852143+PhysShell@users.noreply.github.com> Date: Mon, 6 Jul 2026 17:08:57 +0500 Subject: [PATCH 19/25] Create P-026-own-arch-facts.md --- docs/proposals/P-026-own-arch-facts.md | 356 +++++++++++++++++++++++++ 1 file changed, 356 insertions(+) create mode 100644 docs/proposals/P-026-own-arch-facts.md diff --git a/docs/proposals/P-026-own-arch-facts.md b/docs/proposals/P-026-own-arch-facts.md new file mode 100644 index 00000000..a2d6c8ab --- /dev/null +++ b/docs/proposals/P-026-own-arch-facts.md @@ -0,0 +1,356 @@ +Proposal: Own.Arch Facts & Intent Model for Own.NET + +Status + +Draft. + +Target repository + +"PhysShell/Own.NET" + +Suggested file: + +"docs/proposals/P-027-own-arch-facts-and-intent-model.md" + +Summary + +Introduce "Own.Arch" as the Own.NET-side architecture analysis core: a deterministic extractor and evaluator for architecture facts, intent models, and rule packs. + +This proposal is intentionally scoped to Own.NET. Own.NET should not become the full audit dashboard, PR gate, or agent runner. Its job is to produce reliable architecture facts and deterministic findings that other projects can consume. + +Own.NET owns: + +- project graph extraction; +- package/reference extraction; +- later Roslyn/type-level dependency extraction; +- architecture intent model schema; +- deterministic rule evaluation; +- stable finding fingerprints; +- generated architecture artifacts such as graph JSON and optional diagrams. + +OwnAudit consumes these outputs for reporting, baseline, drift, SARIF, and dashboards. + +007 consumes these outputs indirectly through typed refactoring tasks and gates. + +Existing groundwork + +This builds directly on the existing "P-023 — Architecture guard (Own.Arch)" proposal. P-023 already defines the key architecture: hand-written intent model, extracted actual dependency graph, and drift as "actual - allowed"; it also explicitly says PRs should fail only on new architectural dependency violations, while existing debt is baselined and ratcheted. + +P-023 already scopes the MVP to project-level dependency checks over ".sln", ".csproj", "ProjectReference", "PackageReference", and "packages.config", with rules such as forbidden project edges, forbidden packages per layer, unmapped projects, and unused allowed edges. + +P-023 also already sketches Phase 2 as type-level facts using IL/Roslyn to catch type dependencies, forbidden APIs, and namespace-level cycles. + +Related open PRs: + +- Own.NET PR #184: "docs: add agentic coding discipline proposal" + https://github.com/PhysShell/Own.NET/pull/184 + This PR introduces disciplined agentic coding ideas for Own.NET/OwnAudit/007, including task contracts, diff policy gates, agent-readable invariants, and analyzer rule catalogs. + +- Own.NET PR #186: "docs(proposals): P-026 project resource model files" + https://github.com/PhysShell/Own.NET/pull/186 + This PR proposes declarative per-project resource model files, "own.models.yaml", for acquire/release/capture conventions. It is not an architecture model, but it is useful prior art for project-local declarative models resolved through the semantic layer. + +Problem + +Own.NET already has strong ambitions around ownership, resources, WPF lifetime diagnostics, Roslyn extraction, and analyzer rules. The missing architecture piece is not “draw a diagram”. The missing piece is a stable fact model that can answer: + +- which projects belong to which architectural layer; +- which references cross forbidden boundaries; +- which packages are forbidden in a layer; +- which namespaces/types depend on presentation, persistence, SQL, DevExpress, or other sensitive APIs; +- which intended dependencies are unused and probably stale; +- which violations are new versus old debt. + +Without a deterministic architecture fact layer, any high-level architecture review becomes subjective prose. That is useless as a gate. A gate needs facts, fingerprints, and evidence. + +Non-goals + +Own.NET should not own: + +- PR dashboards; +- long-term trend history; +- SARIF publishing to GitHub; +- AI-generated refactoring patches; +- 007 run records; +- runtime heap correlation; +- hand-maintained C4 diagrams as source of truth. + +Own.NET may generate diagrams from the intent model and graph, but diagrams must be artifacts, not the canonical architecture model. + +Proposed design + +Introduce a small "Own.Arch" subsystem: + +.sln/.csproj/packages.config + ↓ +project/package extractor + ↓ +arch-facts.json + +architecture.intent.json + ↓ +rule evaluator + ↓ +arch-findings.json + ↓ +optional generated artifacts: + - arch-report.md + - architecture.mmd + - structurizr.dsl + +Phase 2 adds: + +compiled solution / Roslyn workspace / IL + ↓ +type dependency extractor + ↓ +type-level arch-facts.json + +Architecture intent model + +Use JSON for the MVP, because OwnAudit already uses stdlib-only Python and existing "arch/rules.json" is JSON. YAML can be added later as an authoring format if needed. + +Example: + +{ + "schema": "own.arch.intent/v1", + "architecture": { + "name": "STS Broker", + "style": ["layered", "modular-monolith", "hexagonal-boundaries"] + }, + "layers": [ + { + "name": "Presentation", + "matches": ["*.UI.*", "*.Wpf.*", "*.ViewModels.*"], + "mayDependOn": ["Application", "DomainAbstractions"] + }, + { + "name": "Application", + "matches": ["*.Application.*", "*.Services.*"], + "mayDependOn": ["Domain", "DomainAbstractions"] + }, + { + "name": "Domain", + "matches": ["*.Domain.*", "*.Core.*"], + "mayDependOn": ["DomainAbstractions"], + "forbiddenApis": ["System.Windows.*", "System.Data.*", "DevExpress.*"] + }, + { + "name": "Infrastructure", + "matches": ["*.Infrastructure.*", "*.DataAccess.*"], + "mayDependOn": ["Domain", "DomainAbstractions"] + } + ], + "forbiddenPackages": { + "Domain": [ + "System.Data.SqlClient", + "Microsoft.Data.SqlClient", + "DevExpress*", + "PresentationFramework" + ], + "Application": [ + "DevExpress*", + "PresentationFramework" + ] + } +} + +Finding codes + +MVP deterministic rules: + +ARCH001: ProjectReference crosses a forbidden layer boundary. +ARCH002: Forbidden package is referenced from a layer. +ARCH003: Project matches zero or multiple layers. +ARCH004: Forbidden direct framework/API reference at project level. +ARCH030: Allowed dependency is declared but unused. + +Phase 2 rules: + +ARCH010: Type-level dependency crosses a forbidden layer boundary. +ARCH011: Forbidden API is used in a layer. +ARCH012: Namespace-level dependency cycle. +ARCH013: Type-level dependency on forbidden framework namespace. + +Phase 3 heuristic/report-only rules: + +ARCH020: Type has excessive fan-out. +ARCH021: Type mixes APIs from unrelated layers. +ARCH022: Interface has too many members. +ARCH023: Component behaves as unstable hub. + +Important: deterministic findings may gate. Heuristic findings are report-only until proven reliable on the project corpus. + +Fingerprint policy + +Every deterministic architecture finding must have a stable fingerprint: + +sha256(rule_id | normalized_from_symbol | normalized_to_symbol | normalized_target_kind) + +Do not include file path or line number in the primary fingerprint. File paths and line numbers are evidence, not identity. + +This prevents baseline churn during refactors while still catching newly introduced architectural edges. + +CLI sketch + +own-arch extract-projects \ + --solution Broker.sln \ + --out arch-facts.project.json + +own-arch evaluate \ + --facts arch-facts.project.json \ + --intent architecture.intent.json \ + --out arch-findings.json \ + --report arch-report.md + +own-arch render \ + --intent architecture.intent.json \ + --facts arch-facts.project.json \ + --format mermaid \ + --out architecture.mmd + +Phase 2: + +own-arch extract-types \ + --solution Broker.sln \ + --configuration Release \ + --out arch-facts.types.json + +Output contracts + +"arch-facts.project.json": + +{ + "schema": "own.arch.facts.project/v1", + "projects": [ + { + "name": "Broker.Presentation", + "path": "src/Broker.Presentation/Broker.Presentation.csproj", + "targetFrameworks": ["net472"], + "projectReferences": ["Broker.Application"], + "packageReferences": ["DevExpress.Xpf"], + "layer": "Presentation" + } + ], + "edges": [ + { + "from": "Broker.Presentation", + "to": "Broker.Application", + "kind": "ProjectReference" + } + ] +} + +"arch-findings.json": + +{ + "schema": "own.findings/v1", + "tool": "own-arch", + "findings": [ + { + "rule": "ARCH001", + "severity": "error", + "category": "architecture", + "resource": "Broker.Presentation", + "message": "Presentation depends on Infrastructure directly", + "fingerprint": "sha256:...", + "evidence": [ + { + "kind": "ProjectReference", + "from": "Broker.Presentation", + "to": "Broker.Infrastructure", + "path": "src/Broker.Presentation/Broker.Presentation.csproj" + } + ] + } + ] +} + +Integration with OwnAudit + +Own.NET produces: + +- "arch-facts.project.json"; +- "arch-facts.types.json"; +- "arch-findings.json"; +- optional generated diagrams/reports. + +OwnAudit consumes these artifacts for: + +- SARIF export; +- baseline/diff; +- PR drift report; +- dashboards; +- health scoring. + +Own.NET should not duplicate OwnAudit’s baseline/diff/reporting layer. + +Integration with 007 + +007 should consume Own.Arch through task specs, not through direct architecture logic. + +Example 007 task target: + +task_id: ownarch.fix.arch001.ui-sql +target_repo: Own.NET +input: + findings: artifacts/arch-findings.json + selector: + rule: ARCH001 + rank: 1 +constraints: + max_files_changed: 5 + require_tests: true + require_reaudit: true + forbid_baseline_update_without_reason: true +gates: + required: + - own-arch-evaluate + - no-new-arch-findings + +Acceptance criteria + +MVP is accepted when: + +1. "own-arch extract-projects" can read a solution/project set and emit "arch-facts.project.json". +2. "own-arch evaluate" can detect forbidden project edges, forbidden packages, and unmapped projects. +3. Findings use stable fingerprints. +4. Existing violations can be consumed by OwnAudit baseline/diff without schema translation hacks. +5. Generated markdown report includes evidence for every finding. +6. A simple Mermaid or Structurizr artifact can be generated from the intent model, but is not source of truth. +7. Tests cover: + - valid layered graph; + - forbidden Presentation → Infrastructure edge; + - Domain package pollution; + - unmapped project; + - multi-mapped project; + - unused allowed dependency. + +Risks + +Risk: Own.Arch becomes a second NDepend clone + +Mitigation: keep scope narrow. Own.Arch detects architecture dependency facts, not every possible code smell. + +Risk: false confidence from inferred architecture style + +Mitigation: style inference must be report-only. Gates rely on deterministic facts. + +Risk: duplicate rule languages + +Mitigation: one intent schema. Generated ArchUnitNET, NetArchTest, C4, Mermaid, or Structurizr outputs are optional render targets, not alternative sources of truth. + +First implementation slice + +Implement only: + +architecture.intent.json +project graph extraction +ARCH001 +ARCH002 +ARCH003 +arch-findings.json +arch-report.md +tests + +Everything else waits. The first slice should be boring, deterministic, and hard to misinterpret. \ No newline at end of file From 66fc8ad002ce3378841d0f54237dd28cf16d7a86 Mon Sep 17 00:00:00 2001 From: PhysShell <45852143+PhysShell@users.noreply.github.com> Date: Mon, 6 Jul 2026 18:01:34 +0500 Subject: [PATCH 20/25] Create P-027-probabilistic-data-structures.md --- .../P-027-probabilistic-data-structures.md | 260 ++++++++++++++++++ 1 file changed, 260 insertions(+) create mode 100644 docs/proposals/P-027-probabilistic-data-structures.md diff --git a/docs/proposals/P-027-probabilistic-data-structures.md b/docs/proposals/P-027-probabilistic-data-structures.md new file mode 100644 index 00000000..fca40048 --- /dev/null +++ b/docs/proposals/P-027-probabilistic-data-structures.md @@ -0,0 +1,260 @@ +Proposal: In-Process Sketches and Bitmap Indexes for Legacy .NET Diagnostics + +Target repository + +"PhysShell/Own.NET" + +Suggested file: + +"docs/proposals/P-028-in-process-sketches-and-bitmaps.md" + +Summary + +Own.NET should add a small, dependency-light module for compact runtime diagnostics and fast set operations using classic probabilistic and compressed data structures: + +- bitsets / roaring-style bitmap indexes; +- Top-K / heavy-hitter counters; +- Count-Min Sketch for approximate frequencies; +- t-digest or DDSketch-style latency summaries; +- optional Bloom/Cuckoo filters for import and lookup pre-checks; +- optional SimHash for grouping similar errors. + +The goal is not to turn a legacy desktop .NET application into a fake distributed analytics platform. That would be architecture cosplay, and nobody needs that circus. The goal is narrower: improve local diagnostics, filtering, dirty tracking, and performance visibility without requiring Redis, Valkey, Kafka, or some other infrastructure animal. + +Problem + +Own.NET has several known pain points: + +- large legacy WPF/.NET Framework surface; +- heavy dictionaries and reference data; +- expensive recalculation paths; +- memory-sensitive UI workflows; +- difficult-to-debug performance spikes; +- repeated validation and import scenarios; +- need for better local evidence before changing architecture. + +Current code can observe some issues, but it likely lacks compact, queryable runtime summaries: + +- which operations are actually slow at p95/p99; +- which validations fail most often; +- which dictionary/reference entries are hot; +- which rows/documents are affected by a recalculation; +- which errors are effectively the same root cause; +- which imports contain duplicates or obviously invalid references. + +Without compact summaries, developers either over-log, under-measure, or guess. Guessing is not engineering. It is astrology with stack traces. + +Proposed solution + +Add an internal module tentatively named: + +"Own.Diagnostics.Sketches" + +The module should expose simple interfaces, not leak implementation details into business logic. + +Example conceptual interfaces: + +public interface ILatencySketch +{ + void Record(long elapsedMilliseconds); + LatencySnapshot Snapshot(); +} + +public interface IHeavyHitters +{ + void Add(T item, long weight = 1); + IReadOnlyList> Top(int count); +} + +public interface IApproxFrequency +{ + void Add(T item, long count = 1); + long Estimate(T item); +} + +public interface IBitmapIndex +{ + void Add(int id); + void Remove(int id); + bool Contains(int id); + IBitmapIndex And(IBitmapIndex other); + IBitmapIndex Or(IBitmapIndex other); + IBitmapIndex Except(IBitmapIndex other); +} + +The first implementation may be deliberately boring: + +- "BitArray" / custom packed bitset for dense ids; +- "HashSet" fallback for sparse ids; +- simple Space-Saving Top-K; +- simple Count-Min Sketch; +- latency sketch adapter with an initially simple histogram implementation. + +The point is to introduce the model safely before chasing cleverness. Cleverness without containment is how a “small optimization” becomes a haunted subsystem. + +Candidate use cases + +1. Dirty tracking and affected-row calculation + +Use bitmap indexes to represent sets such as: + +- rows with validation errors; +- rows affected by changed customs rate; +- rows requiring recalculation; +- rows visible after current filter; +- rows already processed; +- rows excluded by user action. + +Instead of scanning large collections repeatedly, compute set operations: + +RowsToRecalculate = + AffectedByRateChange + AND CurrentDeclarationRows + AND NOT AlreadyRecalculated + +This is especially suitable when ids are stable integer indexes within a document/import/session. + +2. Validation and import diagnostics + +Use Top-K and Count-Min Sketch to track: + +- most frequent validation errors; +- most frequent invalid TNVED codes; +- most frequent import normalization problems; +- most frequently missing reference data; +- most common user correction patterns. + +This helps answer: + +Which 20 validation problems actually hurt users most? + +Not “which validation problems look important in a meeting”, because apparently humans needed a database to learn humility. + +3. Performance telemetry + +Use latency sketches to record p50/p90/p95/p99 for operations such as: + +- opening large WPF forms; +- loading reference dictionaries; +- graph 47 recalculation; +- report generation; +- import parsing; +- SQL query wrappers; +- UI filtering. + +The output should be local and cheap: + +Operation: LoadTnvedTree +Count: 143 +p50: 120 ms +p95: 2.4 s +p99: 8.1 s +Max: 9.6 s + +Average latency alone should be treated as suspicious. Averages hide pain like a rug hides broken glass. + +4. Error grouping + +Use SimHash-like fingerprints to group similar: + +- exception messages; +- stack traces; +- validation failure clusters; +- SQL error patterns. + +This can later connect to the existing idea of error ids, hidden stack traces, and build-aware deobfuscation. + +Scope + +MVP + +The MVP should include: + +1. "ILatencySketch" +2. "IHeavyHitters" +3. "IBitmapIndex" +4. one local diagnostic sink: + - JSON file; + - text report; + - or debug window export. +5. instrumentation examples for 2–3 real operations. + +Suggested first targets: + +- dictionary/reference loading; +- graph 47 recalculation; +- validation/import flow. + +Phase 2 + +Add: + +- Count-Min Sketch; +- Bloom filter for import pre-checks; +- SimHash grouping; +- optional compact binary export; +- analyzer/test coverage for misuse. + +Phase 3 + +Integrate with OwnAudit or 007 by exporting normalized evidence: + +{ + "schema": "own.sketches.v1", + "source": "Own.NET", + "operation": "LoadTnvedTree", + "latency": { + "p50_ms": 120, + "p95_ms": 2400, + "p99_ms": 8100 + }, + "top_errors": [], + "affected_sets": [] +} + +Non-goals + +This proposal explicitly does not include: + +- adding Redis/Valkey as a runtime dependency; +- replacing SQL Server; +- changing business rules; +- introducing approximate answers into critical legal/business decisions; +- using Bloom/HLL/Count-Min for authorization, licensing, billing, or correctness checks; +- rewriting existing WPF flows around sketches. + +Approximate structures may support diagnostics and optimization. They must not become the source of truth for business decisions. Works fine?! A cart with three wheels “works fine” too. + +Safety rules + +1. Every approximate structure must expose its error model in docs. +2. Approximate values must be named as estimates. +3. Exact fallback must exist where correctness matters. +4. Sketches must be resettable and exportable. +5. No global mutable singleton dumping random metrics from everywhere. +6. No business logic may depend on false-positive behavior. + +Acceptance criteria + +The proposal is successful when: + +- a developer can instrument an operation in fewer than 10 lines; +- bitmap indexes can represent affected row sets and combine them efficiently; +- p95/p99 latency is visible for selected operations; +- Top-K diagnostics identify frequent validation/import issues; +- exported evidence can be consumed later by OwnAudit; +- no new infrastructure is required; +- no correctness-sensitive path relies only on probabilistic results. + +Expected benefit + +Own.NET gets a practical local observability and set-processing layer: + +- fewer full scans; +- better dirty tracking; +- better recalculation targeting; +- better import diagnostics; +- clearer performance evidence; +- less guessing before refactoring. + +This is not highload cosplay. It is a small internal toolbox for making the old codebase confess where it hurts. \ No newline at end of file From 3ac1a23488663ad33d91fcefd5f03826314a2e15 Mon Sep 17 00:00:00 2001 From: PhysShell Date: Wed, 8 Jul 2026 11:48:32 +0000 Subject: [PATCH 21/25] docs(proposals): resolve P-026/P-027 numbering collisions from parallel PRs Seven proposals landed in parallel branches all claiming P-026 (and one P-027). Renumber by PR creation order, keeping P-026 for the earliest: - P-026 csharp-strictness-retrofit (unchanged, PR #178) - P-027 resource-state-machine (PR #179) - P-028 unneeded-dependency-profile (PR #180) - P-029 agent-memory-layer (PR #181) - P-030 naughty-strings-testing (PR #185) - P-031 resource-model-files (PR #186) - P-032 own-arch-facts (PR #188) - P-033 probabilistic-data-structures (PR #189) Update headings, proposals/README.md index rows (adding missing P-032/P-033 rows), and ROADMAP.md links. Normalize the two chat-pasted proposals (P-032, P-033) to the standard proposal header and replace live-PR URLs with in-repo links. --- docs/ROADMAP.md | 6 ++-- ...ine.md => P-027-resource-state-machine.md} | 2 +- ...d => P-028-unneeded-dependency-profile.md} | 2 +- ...y-layer.md => P-029-agent-memory-layer.md} | 2 +- ...ng.md => P-030-naughty-strings-testing.md} | 2 +- ...files.md => P-031-resource-model-files.md} | 2 +- ...-arch-facts.md => P-032-own-arch-facts.md} | 31 +++++++------------ ...=> P-033-probabilistic-data-structures.md} | 13 +++----- docs/proposals/README.md | 12 ++++--- 9 files changed, 30 insertions(+), 42 deletions(-) rename docs/proposals/{P-026-resource-state-machine.md => P-027-resource-state-machine.md} (99%) rename docs/proposals/{P-026-unneeded-dependency-profile.md => P-028-unneeded-dependency-profile.md} (99%) rename docs/proposals/{P-026-agent-memory-layer.md => P-029-agent-memory-layer.md} (99%) rename docs/proposals/{P-026-naughty-strings-testing.md => P-030-naughty-strings-testing.md} (99%) rename docs/proposals/{P-026-resource-model-files.md => P-031-resource-model-files.md} (99%) rename docs/proposals/{P-026-own-arch-facts.md => P-032-own-arch-facts.md} (91%) rename docs/proposals/{P-027-probabilistic-data-structures.md => P-033-probabilistic-data-structures.md} (96%) diff --git a/docs/ROADMAP.md b/docs/ROADMAP.md index 80f8a33f..bfdae34c 100644 --- a/docs/ROADMAP.md +++ b/docs/ROADMAP.md @@ -128,7 +128,7 @@ ownership/lifetime/effects, (4) an MVP needs no PhD in Roslyn. |------|---------|----------| | **P0** | WPF/event/timer/subscription leaks; `IDisposable` ownership (leaks, fields, use-after-dispose); DI lifetime mismatch (captive dependency) | [P-004](proposals/P-004-wpf-lifetime-profile.md), [P-005](proposals/P-005-idisposable-ownership.md), [P-006](proposals/P-006-di-lifetimes.md) | | **P1** | ArrayPool/Span ownership-view bugs; hidden effects / architecture rules | [P-007](proposals/P-007-arraypool-span.md), [P-008](proposals/P-008-effects-and-resources.md) | -| **P2** | async resource lifecycle / WPF async audit; `ValueTask` affine usage; typestate/protocols; resource state-machine soup + stale async writes | [P-021](proposals/P-021-async-audit-pack.md), [P-008](proposals/P-008-effects-and-resources.md), [P-010](proposals/P-010-type-disciplines.md), [P-026](proposals/P-026-resource-state-machine.md) | +| **P2** | async resource lifecycle / WPF async audit; `ValueTask` affine usage; typestate/protocols; resource state-machine soup + stale async writes | [P-021](proposals/P-021-async-audit-pack.md), [P-008](proposals/P-008-effects-and-resources.md), [P-010](proposals/P-010-type-disciplines.md), [P-027](proposals/P-027-resource-state-machine.md) | | **P3** | LOH fragmentation; static-collection memory bloat; cross-thread `ObjectDisposedException` | — (runtime-bound; see detectability matrix) | > **Are we showable yet?** The concrete "delicious .NET alpha" gate — the A–G bar @@ -326,5 +326,5 @@ own scan. Label them as estimates wherever they appear. | [P-020](proposals/P-020-ownts-react-effects.md) | OwnTS React effects profile (`Own.React`) — effect-storm angle | horizon | draft | | [P-021](proposals/P-021-async-audit-pack.md) | Async audit pack (`Own.Async`) — safety-first WPF/application async lifecycle diagnostics | P2 | draft | | [P-025](proposals/P-025-obligation-protocols.md) | Obligation protocols (`Own.Protocols`) — barrier-sensitive project invariants (OBL001–005) | P1 | first slice built (core + bridge + fixtures; extractor pending) | -| [P-026](proposals/P-026-resource-state-machine.md) | Resource state machines & stale-async-write detection (extends `Own.Async`) | P2 | draft | -| [P-026](proposals/P-026-unneeded-dependency-profile.md) | Unneeded-dependency profile (`Own.Lean`) — evidence-only "you don't need this abstraction" findings (YDN001–002) | P2 | draft | +| [P-027](proposals/P-027-resource-state-machine.md) | Resource state machines & stale-async-write detection (extends `Own.Async`) | P2 | draft | +| [P-028](proposals/P-028-unneeded-dependency-profile.md) | Unneeded-dependency profile (`Own.Lean`) — evidence-only "you don't need this abstraction" findings (YDN001–002) | P2 | draft | diff --git a/docs/proposals/P-026-resource-state-machine.md b/docs/proposals/P-027-resource-state-machine.md similarity index 99% rename from docs/proposals/P-026-resource-state-machine.md rename to docs/proposals/P-027-resource-state-machine.md index 3f2f40eb..414bb231 100644 --- a/docs/proposals/P-026-resource-state-machine.md +++ b/docs/proposals/P-027-resource-state-machine.md @@ -1,4 +1,4 @@ -# P-026 — Resource state machines & stale-async-write detection (extends `Own.Async`) +# P-027 — Resource state machines & stale-async-write detection (extends `Own.Async`) - **Status:** draft — proposal for discussion, not a committed design. Recorded on the record so the idea isn't lost, not "we are building this next." diff --git a/docs/proposals/P-026-unneeded-dependency-profile.md b/docs/proposals/P-028-unneeded-dependency-profile.md similarity index 99% rename from docs/proposals/P-026-unneeded-dependency-profile.md rename to docs/proposals/P-028-unneeded-dependency-profile.md index 820bf054..4ebf3167 100644 --- a/docs/proposals/P-026-unneeded-dependency-profile.md +++ b/docs/proposals/P-028-unneeded-dependency-profile.md @@ -1,4 +1,4 @@ -# P-026 — Unneeded-dependency profile (`Own.Lean`) +# P-028 — Unneeded-dependency profile (`Own.Lean`) - **Status:** draft — not started. - **Depends on:** [P-001](P-001-csharp-extractor.md) (the Roslyn extractor diff --git a/docs/proposals/P-026-agent-memory-layer.md b/docs/proposals/P-029-agent-memory-layer.md similarity index 99% rename from docs/proposals/P-026-agent-memory-layer.md rename to docs/proposals/P-029-agent-memory-layer.md index 6a48449b..bbfe913d 100644 --- a/docs/proposals/P-026-agent-memory-layer.md +++ b/docs/proposals/P-029-agent-memory-layer.md @@ -1,4 +1,4 @@ -# P-026 — Agent memory & policy layer (`.agents/`) +# P-029 — Agent memory & policy layer (`.agents/`) - **Status:** draft — design only, no code/directory changes shipped by this proposal. diff --git a/docs/proposals/P-026-naughty-strings-testing.md b/docs/proposals/P-030-naughty-strings-testing.md similarity index 99% rename from docs/proposals/P-026-naughty-strings-testing.md rename to docs/proposals/P-030-naughty-strings-testing.md index 0634e56f..c72d8c5b 100644 --- a/docs/proposals/P-026-naughty-strings-testing.md +++ b/docs/proposals/P-030-naughty-strings-testing.md @@ -1,4 +1,4 @@ -# P-026 — Naughty-strings robustness pack (BLNS-driven crash testing) +# P-030 — Naughty-strings robustness pack (BLNS-driven crash testing) - **Status:** draft - **Depends on / relates to:** diff --git a/docs/proposals/P-026-resource-model-files.md b/docs/proposals/P-031-resource-model-files.md similarity index 99% rename from docs/proposals/P-026-resource-model-files.md rename to docs/proposals/P-031-resource-model-files.md index 120573dd..eddea1c5 100644 --- a/docs/proposals/P-026-resource-model-files.md +++ b/docs/proposals/P-031-resource-model-files.md @@ -1,4 +1,4 @@ -# P-026 — Project resource model files (declarative acquire/release/capture) +# P-031 — Project resource model files (declarative acquire/release/capture) - **Status:** draft - **Depends on:** diff --git a/docs/proposals/P-026-own-arch-facts.md b/docs/proposals/P-032-own-arch-facts.md similarity index 91% rename from docs/proposals/P-026-own-arch-facts.md rename to docs/proposals/P-032-own-arch-facts.md index a2d6c8ab..70fc96a1 100644 --- a/docs/proposals/P-026-own-arch-facts.md +++ b/docs/proposals/P-032-own-arch-facts.md @@ -1,18 +1,11 @@ -Proposal: Own.Arch Facts & Intent Model for Own.NET +# P-032 — Own.Arch facts & intent model -Status +- **Status:** draft. Imported from a design discussion and normalized into the + proposal series (the pasted original self-titled itself P-027; renumbered here). +- **Extends:** [P-023 — Architecture guard](P-023-architecture-guard.md) — this + proposal deepens P-023's intent-model/drift design into a fact-extraction core. -Draft. - -Target repository - -"PhysShell/Own.NET" - -Suggested file: - -"docs/proposals/P-027-own-arch-facts-and-intent-model.md" - -Summary +## Summary Introduce "Own.Arch" as the Own.NET-side architecture analysis core: a deterministic extractor and evaluator for architecture facts, intent models, and rule packs. @@ -40,15 +33,13 @@ P-023 already scopes the MVP to project-level dependency checks over ".sln", ".c P-023 also already sketches Phase 2 as type-level facts using IL/Roslyn to catch type dependencies, forbidden APIs, and namespace-level cycles. -Related open PRs: +Related proposals: -- Own.NET PR #184: "docs: add agentic coding discipline proposal" - https://github.com/PhysShell/Own.NET/pull/184 - This PR introduces disciplined agentic coding ideas for Own.NET/OwnAudit/007, including task contracts, diff policy gates, agent-readable invariants, and analyzer rule catalogs. +- [Agentic coding discipline proposal](../agentic-coding-discipline-proposal.md) + introduces disciplined agentic coding ideas for Own.NET/OwnAudit/007, including task contracts, diff policy gates, agent-readable invariants, and analyzer rule catalogs. -- Own.NET PR #186: "docs(proposals): P-026 project resource model files" - https://github.com/PhysShell/Own.NET/pull/186 - This PR proposes declarative per-project resource model files, "own.models.yaml", for acquire/release/capture conventions. It is not an architecture model, but it is useful prior art for project-local declarative models resolved through the semantic layer. +- [P-031 — Project resource model files](P-031-resource-model-files.md) + proposes declarative per-project resource model files, "own.models.yaml", for acquire/release/capture conventions. It is not an architecture model, but it is useful prior art for project-local declarative models resolved through the semantic layer. Problem diff --git a/docs/proposals/P-027-probabilistic-data-structures.md b/docs/proposals/P-033-probabilistic-data-structures.md similarity index 96% rename from docs/proposals/P-027-probabilistic-data-structures.md rename to docs/proposals/P-033-probabilistic-data-structures.md index fca40048..e0668354 100644 --- a/docs/proposals/P-027-probabilistic-data-structures.md +++ b/docs/proposals/P-033-probabilistic-data-structures.md @@ -1,14 +1,9 @@ -Proposal: In-Process Sketches and Bitmap Indexes for Legacy .NET Diagnostics +# P-033 — In-process sketches and bitmap indexes for legacy .NET diagnostics -Target repository +- **Status:** draft. Imported from a design discussion and normalized into the + proposal series (the pasted original suggested the then-taken number P-028). -"PhysShell/Own.NET" - -Suggested file: - -"docs/proposals/P-028-in-process-sketches-and-bitmaps.md" - -Summary +## Summary Own.NET should add a small, dependency-light module for compact runtime diagnostics and fast set operations using classic probabilistic and compressed data structures: diff --git a/docs/proposals/README.md b/docs/proposals/README.md index aeb06d6c..4f6cc9bc 100644 --- a/docs/proposals/README.md +++ b/docs/proposals/README.md @@ -46,11 +46,13 @@ proposal is marked `done` with a pointer. | [P-024](P-024-security-audit-profile.md) | Security audit profile (external tools + SARIF adapters; rejects own scanner engine) | draft | | [P-025](P-025-obligation-protocols.md) | Obligation protocols (`Own.Protocols`): barrier-sensitive project invariants (OBL001–005) | first slice built (core + bridge + fixtures; extractor pending) | | [P-026](P-026-csharp-strictness-retrofit.md) | C# strictness retrofit profile (`own audit strictness`): a witness/score over existing findings, not a new engine | draft (framing) | -| [P-026](P-026-resource-state-machine.md) | Resource state machines & stale-async-write detection (extends `Own.Async`) | draft | -| [P-026](P-026-unneeded-dependency-profile.md) | Unneeded-dependency profile (`Own.Lean`): evidence-only "you don't need this abstraction" findings (YDN001–002) | draft | -| [P-026](P-026-agent-memory-layer.md) | Agent memory & policy layer (`.agents/`): reviewed destination for AGENTS.md, gates, and learned-rule promotions | draft | -| [P-026](P-026-naughty-strings-testing.md) | Naughty-strings robustness pack (BLNS-driven crash testing of lexer/parser/extractor/serializers/CLI/config) | draft | -| [P-026](P-026-resource-model-files.md) | Project resource model files (declarative acquire/release/capture, symbol-resolved) | draft | +| [P-027](P-027-resource-state-machine.md) | Resource state machines & stale-async-write detection (extends `Own.Async`) | draft | +| [P-028](P-028-unneeded-dependency-profile.md) | Unneeded-dependency profile (`Own.Lean`): evidence-only "you don't need this abstraction" findings (YDN001–002) | draft | +| [P-029](P-029-agent-memory-layer.md) | Agent memory & policy layer (`.agents/`): reviewed destination for AGENTS.md, gates, and learned-rule promotions | draft | +| [P-030](P-030-naughty-strings-testing.md) | Naughty-strings robustness pack (BLNS-driven crash testing of lexer/parser/extractor/serializers/CLI/config) | draft | +| [P-031](P-031-resource-model-files.md) | Project resource model files (declarative acquire/release/capture, symbol-resolved) | draft | +| [P-032](P-032-own-arch-facts.md) | Own.Arch facts & intent model: deterministic architecture-fact extractor/evaluator core (deepens P-023) | draft | +| [P-033](P-033-probabilistic-data-structures.md) | In-process sketches & bitmap indexes for legacy .NET diagnostics (Top-K, CMS, t-digest, Bloom) | draft | > For priorities, milestones, the framing, and the design philosophy across all > of these, see the strategy hub: [`docs/ROADMAP.md`](../ROADMAP.md). P-004 … P-016 From ce9eab92f6563549cf3ed7e6e78bb6dc3e14c4bb Mon Sep 17 00:00:00 2001 From: PhysShell Date: Wed, 8 Jul 2026 14:50:35 +0000 Subject: [PATCH 22/25] =?UTF-8?q?docs:=20reconcile=20P-026=20strictness=20?= =?UTF-8?q?dimensions=20with=20its=20example=20report;=20align=20=C2=A78?= =?UTF-8?q?=20tree=20comments?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/notes/agent-capability-layer.md | 8 ++++---- docs/proposals/P-026-csharp-strictness-retrofit.md | 1 + 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/docs/notes/agent-capability-layer.md b/docs/notes/agent-capability-layer.md index 313bdccd..eba45771 100644 --- a/docs/notes/agent-capability-layer.md +++ b/docs/notes/agent-capability-layer.md @@ -186,14 +186,14 @@ always wins and a mistaken override ships silently. ```text policies/ - no-net.cue # network: "deny" — the floor, never overridden + no-net.cue # network: "deny" — the floor, never overridden worktree-only.cue # repo.read/write confined to the worktree default-processes.cue # exec allowlist gates/ own-net.cue # unifies the policies above + step list - own-net.windows.cue # must *explicitly* switch to a different process - # profile to add e.g. `powershell` — can't inherit a - # denylist that silently forgot it + own-net.windows.cue # must *explicitly* switch to a different process + # profile to add e.g. `powershell` — can't inherit a + # denylist that silently forgot it ``` Compiled down to flat JSON for whatever actually enforces it at runtime (the diff --git a/docs/proposals/P-026-csharp-strictness-retrofit.md b/docs/proposals/P-026-csharp-strictness-retrofit.md index 40c0cd32..77acc3f2 100644 --- a/docs/proposals/P-026-csharp-strictness-retrofit.md +++ b/docs/proposals/P-026-csharp-strictness-retrofit.md @@ -90,6 +90,7 @@ with one score to argue about. | Non-exhaustive handling hiding future cases | [P-010](P-010-type-disciplines.md) | | Source-of-truth drift (config vs DI, XAML vs VM, EF vs migrations) | [P-015](P-015-configuration-surface.md), [P-023](P-023-architecture-guard.md) | | Null-safety not enforced (`!`, opt-in NRT) | external analyzers via SARIF adapter; `NO-TOOL` until wired | +| Local immutability not expressed (mutable setters on domain state) | `NO-TOOL` until wired (candidate: setter census via the P-001/P-014 extractor) | ## Non-goals (the most important section) From a5c0c97ab1a5384cc110afee761289ce829d80c1 Mon Sep 17 00:00:00 2001 From: PhysShell Date: Wed, 8 Jul 2026 16:26:46 +0000 Subject: [PATCH 23/25] docs: apply validation corrections across consolidated proposals MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Multi-pass review of the consolidated docs (per-file validation against the code plus cross-repo consistency checks). Highlights: - P-010: spec/Diagnostics.md is derived; ownlang/diagnostics.py TITLES is the source of truth for OWN codes — inverted claim fixed - P-026: add the missing local-immutability dimension so the illustrative report's 4/6 coverage matches the dimensions table - P-027: drop 'existing' for P-021's planned async_methods module; repoint a wrong P-021 open-question citation; attribute the plain-structs lowering rule to P-010 only - P-029: fix broken 007 companion reference (docs/reflect.md -> docs/agent-memory-layer.md), align 'reflect' vocabulary with the landed design, refresh stale proposal counts - P-030: replace the pytest-based sketch with a stdlib runner matching the zero-dependency suite; make the synthesized OwnLang wrapper actually parse per ownlang/parser.py grammar - P-032/P-033: fence all code blocks, promote plain-text section titles to headings, backtick quoted identifiers; P-033 reframed to target the audited legacy app, not the analyzer (source: own.diagnostics.sketches) - agentic-coding-discipline: soften 'o7 run works' to match 007 TODO.md (scaffolded, not yet exercised); add part-2 divider heading - agent-capability-layer §8: Sandboy consumes rendered TOML (not JSON); sandboy status corrected Built -> Spiked (authored, not compiled) - sandboy/README: replace live 007 blob URL with plain cross-repo path; gate steps described as bare bash (bypassPermissions applies to the agent phase) - ROADMAP: index rows for all new proposals P-026..P-033 --- docs/ROADMAP.md | 6 ++ docs/agentic-coding-discipline-proposal.md | 10 ++- docs/notes/agent-capability-layer.md | 10 +-- docs/proposals/P-010-type-disciplines.md | 3 +- .../proposals/P-027-resource-state-machine.md | 10 +-- docs/proposals/P-029-agent-memory-layer.md | 24 +++---- .../P-030-naughty-strings-testing.md | 33 ++++++--- docs/proposals/P-032-own-arch-facts.md | 68 ++++++++++++------ .../P-033-probabilistic-data-structures.md | 72 +++++++++++-------- sandboy/README.md | 8 +-- 10 files changed, 157 insertions(+), 87 deletions(-) diff --git a/docs/ROADMAP.md b/docs/ROADMAP.md index bfdae34c..770063b4 100644 --- a/docs/ROADMAP.md +++ b/docs/ROADMAP.md @@ -326,5 +326,11 @@ own scan. Label them as estimates wherever they appear. | [P-020](proposals/P-020-ownts-react-effects.md) | OwnTS React effects profile (`Own.React`) — effect-storm angle | horizon | draft | | [P-021](proposals/P-021-async-audit-pack.md) | Async audit pack (`Own.Async`) — safety-first WPF/application async lifecycle diagnostics | P2 | draft | | [P-025](proposals/P-025-obligation-protocols.md) | Obligation protocols (`Own.Protocols`) — barrier-sensitive project invariants (OBL001–005) | P1 | first slice built (core + bridge + fixtures; extractor pending) | +| [P-026](proposals/P-026-csharp-strictness-retrofit.md) | C# strictness retrofit profile (`own audit strictness`) — witness/score over existing findings | P2 | draft (framing) | | [P-027](proposals/P-027-resource-state-machine.md) | Resource state machines & stale-async-write detection (extends `Own.Async`) | P2 | draft | | [P-028](proposals/P-028-unneeded-dependency-profile.md) | Unneeded-dependency profile (`Own.Lean`) — evidence-only "you don't need this abstraction" findings (YDN001–002) | P2 | draft | +| [P-029](proposals/P-029-agent-memory-layer.md) | Agent memory & policy layer (`.agents/`) — reviewed destination for AGENTS.md and learned-rule promotions | enabling | draft | +| [P-030](proposals/P-030-naughty-strings-testing.md) | Naughty-strings robustness pack (BLNS crash-testing of lexer/parser/serializers/CLI) | enabling | draft | +| [P-031](proposals/P-031-resource-model-files.md) | Project resource model files (`own.models.yaml`) — declarative acquire/release/capture | P2 | draft | +| [P-032](proposals/P-032-own-arch-facts.md) | Own.Arch facts & intent model — deterministic architecture-fact core (deepens P-023) | P2 | draft | +| [P-033](proposals/P-033-probabilistic-data-structures.md) | In-process sketches & bitmap indexes for audited legacy apps | horizon | draft | diff --git a/docs/agentic-coding-discipline-proposal.md b/docs/agentic-coding-discipline-proposal.md index 73b31918..aeb729ce 100644 --- a/docs/agentic-coding-discipline-proposal.md +++ b/docs/agentic-coding-discipline-proposal.md @@ -9,7 +9,9 @@ ## Что из этого уже реализовано/задокументировано - **007** (`README.md`, `TODO.md`) — цикл `isolate → run → gate → harvest` уже - работает: `o7 run` делает git worktree, гоняет `claude` full-auto, прогоняет + реализован (`o7 run` — scaffolded, ещё не прогнан на реальной coding-задаче; + `o7 judge` — verified working): `o7 run` делает git worktree, гоняет `claude` + full-auto, прогоняет `.007/gate.toml`, собирает `task.md / meta.json / agent.stdout / diff.patch / gate/*.log` (`src/agent.rs`, `gate.rs`, `worktree.rs`, `record.rs`, `verdict.rs`). @@ -487,6 +489,8 @@ task contract → isolated run → gates → harvest → judge/review → verdic --- +## Часть 2 — применение к 007 + ### 1. Для 007 нужен не "prompt template", а Task Contract В Own.NET можно было держать `.ai/task-template.md`. @@ -760,7 +764,7 @@ judge возвращает machine-readable verdict без замка. ``` -Практический вывод +#### Практический вывод В 007 надо добавить `trust_level`: @@ -1020,7 +1024,7 @@ examples/policy.safe-defaults.toml Это даст 007 реальную ценность как harness, а не просто runner. -MVP формата +#### MVP формата ```toml version = 1 diff --git a/docs/notes/agent-capability-layer.md b/docs/notes/agent-capability-layer.md index eba45771..0a5fde98 100644 --- a/docs/notes/agent-capability-layer.md +++ b/docs/notes/agent-capability-layer.md @@ -112,7 +112,7 @@ syscall/reachable-binary level. Two different enforcement models — don't confl | Phase | What | Verdict | |---|---|---| | **1. Policy engine** | `owen-policy`: parse `owen.policy.toml`, `policy check/explain`, `gen-ignore` | **Do.** Daily use, zero risk, not built. 80% of daily value. | -| **2. Runner enforcement** | wrap agent in worktree + Sandboy | **Built** (`sandboy/`). Wire to a real gate step. | +| **2. Runner enforcement** | wrap agent in worktree + Sandboy | **Spiked** (`sandboy/` — authored, not yet compiled; acceptance gate: `cargo build` + `tests/demo.sh`, see `sandboy/README.md`). Wire to a real gate step. | | **3. WIT tool components** | move tools to capability-scoped components | **Selective.** WIT only where input/author is untrusted: `secret-scanner`, `patch-analyzer`, `verifier-adapter` (parse untrusted output) — yes. `memory-search` over **your own** data — plain code, WIT buys nothing. | | **4. MCP/WIT bridge** | `owen-mcp` tools backed by policy + components | Thin, later. | @@ -139,7 +139,7 @@ syscall/reachable-binary level. Two different enforcement models — don't confl canonical policy owen.policy.toml (Phase 1 — build) context hygiene generated ignore + filtered packs (hygiene, NOT security) tool isolation WIT + Wasmtime (own-adapter-host+) (Phase 3 — selective) -native isolation worktree + Sandboy (built: sandboy/) +native isolation worktree + Sandboy (spiked: sandboy/) agent integration mode (B): agent-with-shell in the (Sandboy is the cage, Sandboy cage, Owen tools on top WIT tools are contracts) memory / verifier only through policy-mediated ifaces @@ -196,8 +196,10 @@ gates/ # denylist that silently forgot it ``` -Compiled down to flat JSON for whatever actually enforces it at runtime (the -Sandboy policy, `owen policy check`'s consumer) — the authoring layer is for +Compiled down to flat artifacts for whatever actually enforces it at runtime — +rendered TOML for the per-step Sandboy policy (`cue export --out toml`, see +`sandboy/README.md`), flat JSON for the gate manifest / `owen policy check` +consumer — the authoring layer is for humans; the enforcement point should stay a boring, strict parser with no CUE evaluation at run time. diff --git a/docs/proposals/P-010-type-disciplines.md b/docs/proposals/P-010-type-disciplines.md index 63e8cbde..490efedd 100644 --- a/docs/proposals/P-010-type-disciplines.md +++ b/docs/proposals/P-010-type-disciplines.md @@ -228,7 +228,8 @@ payoff of doing this pillar at all is *unification*: one mental model 17. **Generated docs.** Render the `.own` declarations (brand / refinement / union / protocol) into human-readable reference pages, the same way - `spec/Diagnostics.md` is the single source of truth for `OWN` codes today — + `ownlang/diagnostics.py` (`TITLES`) is the single source of truth for `OWN` + codes today, with `spec/Diagnostics.md` as its human-readable grouping — one declaration, read by the compiler *and* the wiki, so the contract and its documentation cannot drift apart. diff --git a/docs/proposals/P-027-resource-state-machine.md b/docs/proposals/P-027-resource-state-machine.md index 414bb231..000bec3e 100644 --- a/docs/proposals/P-027-resource-state-machine.md +++ b/docs/proposals/P-027-resource-state-machine.md @@ -176,7 +176,7 @@ correctly. an `await`, writes after it, with no visible guard" — a syntactic pattern, not a soundness proof. - **No shipped runtime type.** Own.NET does not ship a `ResourceState` - NuGet package as the mandated fix, matching P-008/P-010's existing rule that + NuGet package as the mandated fix, matching P-010's existing rule that brands/refinements/protocols lower to plain structs the *team* owns. The diagnostic explains the pattern; teams write their own status type (or adopt whichever shape fits their codebase). @@ -202,8 +202,8 @@ Python core emits verdicts: ``` `ASYNC051`/`ASYNC052` are per-method and most likely land as two more hazard -kinds in P-021's existing `async_methods` fact family -(`ownlang/async_rules.py`). `ASYNC050` is type-scoped — a cluster of fields +kinds in the `async_methods` fact family P-021 plans (its proposed +`ownlang/async_rules.py` module). `ASYNC050` is type-scoped — a cluster of fields assigned together across the whole class, not one method — so it needs its own type-level fact rather than living inside a method entry: @@ -247,8 +247,8 @@ contracts separate. 2. **Threshold for state soup.** Two co-varying fields (`HasValue`/`Value`) are completely ordinary and shouldn't fire. Where's the real line — 3 fields? A specific combination like `bool + bool + nullable`? This needs corpus - evidence (P-012) rather than a guessed constant, same as P-021's own open - question 2 about severity thresholds. + evidence (P-012) rather than a guessed constant, in the same evidence-first + spirit as P-021. 3. **Staleness-check feasibility.** Proving "the gating field was re-read or a version was compared between the pre-await read and the post-await write" needs more than single-method syntax — it's adjacent to P-016's deep fact diff --git a/docs/proposals/P-029-agent-memory-layer.md b/docs/proposals/P-029-agent-memory-layer.md index bbfe913d..ad8a7197 100644 --- a/docs/proposals/P-029-agent-memory-layer.md +++ b/docs/proposals/P-029-agent-memory-layer.md @@ -4,8 +4,8 @@ proposal. - **Depends on:** nothing structurally; it formalizes conventions already used elsewhere in this repo (see Sketch). Consumed by, but does not depend on, the - reflect/learning-engine design in the sibling private repo `PhysShell/007` - (`docs/reflect.md` there) — that engine is one possible *source* of + agent-memory-layer design in the sibling private repo `PhysShell/007` + (`docs/agent-memory-layer.md` there) — that memory layer is one possible *source* of promotions into the layer this proposal defines; a human editing `AGENTS.md` by hand is another, equally valid, source. @@ -15,7 +15,7 @@ gate, the pipeline shape, and a handful of hard rules (unknown-call handling, `assert_never` dispatch sites, codegen modes, OwnIR versioning). That is exactly the right size for what it covers today. The risk is what happens as -it grows: this repo already carries 25 proposals and 30+ design notes under +it grows: this repo already carries 30+ proposals and 30+ design notes under `docs/`, plus a second, much longer file (`AGENTS.execution-surfaces.md`, 14KB) for a single ADR. Two failure modes are already visible in miniature: @@ -36,10 +36,10 @@ from the outside — see `007`'s own `README.md`) both converge on the same idea: corrections and repeat-failure patterns from real agent runs should become reviewed, persistent project memory rather than being re-learned every session. `007` is explicitly the place that *mines* run records for candidate -learnings (`docs/reflect.md` in that repo) — it is private, and its own -`README.md` is explicit that harness-internal reasoning must never land in a -public tree. What this repo needs, independent of whether 007 ever ships that -engine, is: **a defined, reviewed, structured place those promotions — or a +learnings (`docs/agent-memory-layer.md` in that repo) — it is private, and its own +`README.md` is explicit that the harness must stay private (its auth/agent- +routing internals must not land in a public tree). What this repo needs, +independent of whether 007 ever ships that engine, is: **a defined, reviewed, structured place those promotions — or a human's own manual corrections — land in.** That is this proposal's entire scope: the destination shape, not a detector. @@ -68,7 +68,7 @@ already relies on to agent guidance specifically, instead of one growing file. Each `.agents/*.md` file gets a soft line budget (~150 lines, the same heuristic `claude-reflect` uses to warn on oversized memory files). Once a file would cross that budget, it splits — same discipline that already -produced 25 separate proposals instead of one `PROPOSALS.md`. +produced 30+ separate proposals instead of one `PROPOSALS.md`. ### `.007/gate.toml` @@ -104,8 +104,8 @@ open question on generation below. ### The promotion contract Whatever proposes a change to `.agents/*.md` — a human noticing a repeat -correction, or an accepted candidate out of `007`'s (separate, private) -reflect queue — must arrive as an ordinary reviewed PR carrying: +correction, or a human-confirmed memory entry promoted out of `007`'s (separate, +private) memory layer — must arrive as an ordinary reviewed PR carrying: - **the rule itself**, scoped to one `.agents/*.md` file and section; - **provenance**: what motivated it (a run, a PR comment, a postmortem) — @@ -185,7 +185,7 @@ Guidance for agents working in this repo. Start here, then follow a link: checked in CI so drift fails loudly rather than rotting silently. 2. **Does the promotion contract need its own proposal**, or does it stay a section of this one? Leaning: stays here until there's a second consumer - besides `007`'s reflect design — one contract, one place, until proven + besides `007`'s memory-layer design — one contract, one place, until proven otherwise. 3. **Should `.cursor/rules/` and `.roo/rules-*/` eventually generate from `.agents/`** instead of maintaining separate tool-specific prose? Not in @@ -198,7 +198,7 @@ Guidance for agents working in this repo. Start here, then follow a link: lines" heuristic)? Leaning: start as a soft PR-review convention; only add a mechanical gate if bloat actually recurs — no gate for a problem that hasn't happened yet. -5. **Timing relative to `007`'s reflect engine:** this proposal's directory +5. **Timing relative to `007`'s memory layer:** this proposal's directory layout is useful on its own (splitting an already-growing `AGENTS.md`) regardless of whether `007`'s mining engine ever ships. It should not block on that design landing first. diff --git a/docs/proposals/P-030-naughty-strings-testing.md b/docs/proposals/P-030-naughty-strings-testing.md index c72d8c5b..d370cdd5 100644 --- a/docs/proposals/P-030-naughty-strings-testing.md +++ b/docs/proposals/P-030-naughty-strings-testing.md @@ -46,7 +46,7 @@ way instead of by test. line-ending oddities, format-breakers for JSON/XML/CSV/SQL/shell), shipped as a plain `blns.json` array plus a `.NET` port (`NaughtyStrings` NuGet package) for the C#-side pieces (`audit/`'s eventual C# on lift-out, per -[`OwnAudit/README.md`](https://github.com/PhysShell/OwnAudit)). BLNS itself is +[`OwnAudit/README.md`](https://github.com/PhysShell/OwnAudit/blob/main/README.md)). BLNS itself is explicit that it is not a substitute for real security testing (see Non-goals) — its contract here is narrower and cheaper: **the tool must not crash, hang, or corrupt output on any string in the corpus.** @@ -132,7 +132,7 @@ tests/test_naughty_strings.py # parametrized over every entry, 3 layers abov ``` ```python -import json, os, pytest +import json, os from ownlang.lexer import LexError from ownlang.parser import ParseError, parse @@ -140,13 +140,28 @@ with open(os.path.join(os.path.dirname(__file__), "fixtures", "blns.json"), encoding="utf-8") as f: BLNS = json.load(f) -@pytest.mark.parametrize("naughty", BLNS) -def test_parser_does_not_crash(naughty): - src = f'resource R;\nfn f() {{ let s = "{naughty}"; }}\n' - try: - parse(src) - except (ParseError, LexError): - pass # an honest rejection is fine; anything else is a bug + +def run() -> int: + """One case per BLNS entry, in the suite's zero-dependency style: no + pytest — tests/run_tests.py auto-discovers every test_*.py that exposes + a run() -> int, same as tests/test_corpus.py.""" + fails: list[str] = [] + for naughty in BLNS: + # Embed the entry in a grammatically legal STRING position: per + # ownlang/parser.py's grammar, string literals appear only in + # resource emit_*/kind members (a `let` rhs never takes a string), + # so a benign entry parses cleanly and only the naughty content is + # under test — the wrapper follows the run_tests.py PRELUDE shape. + src = f'module M\nresource R {{ acquire a release r emit_type "{naughty}" }}\n' + try: + parse(src) + except (ParseError, LexError): + pass # an honest rejection is fine; anything else is a bug + except Exception as e: + fails.append(f"{naughty!r}: {type(e).__name__}: {e}") + for f in fails: + print(f"NAUGHTY FAIL: {f}") + return 1 if fails else 0 ``` Serialization side follows the same shape against `diag_sarif.py` / diff --git a/docs/proposals/P-032-own-arch-facts.md b/docs/proposals/P-032-own-arch-facts.md index 70fc96a1..92922771 100644 --- a/docs/proposals/P-032-own-arch-facts.md +++ b/docs/proposals/P-032-own-arch-facts.md @@ -7,7 +7,7 @@ ## Summary -Introduce "Own.Arch" as the Own.NET-side architecture analysis core: a deterministic extractor and evaluator for architecture facts, intent models, and rule packs. +Introduce `Own.Arch` as the Own.NET-side architecture analysis core: a deterministic extractor and evaluator for architecture facts, intent models, and rule packs. This proposal is intentionally scoped to Own.NET. Own.NET should not become the full audit dashboard, PR gate, or agent runner. Its job is to produce reliable architecture facts and deterministic findings that other projects can consume. @@ -25,11 +25,11 @@ OwnAudit consumes these outputs for reporting, baseline, drift, SARIF, and dashb 007 consumes these outputs indirectly through typed refactoring tasks and gates. -Existing groundwork +## Existing groundwork This builds directly on the existing "P-023 — Architecture guard (Own.Arch)" proposal. P-023 already defines the key architecture: hand-written intent model, extracted actual dependency graph, and drift as "actual - allowed"; it also explicitly says PRs should fail only on new architectural dependency violations, while existing debt is baselined and ratcheted. -P-023 already scopes the MVP to project-level dependency checks over ".sln", ".csproj", "ProjectReference", "PackageReference", and "packages.config", with rules such as forbidden project edges, forbidden packages per layer, unmapped projects, and unused allowed edges. +P-023 already scopes the MVP to project-level dependency checks over `.sln`, `.csproj`, `ProjectReference`, `PackageReference`, and `packages.config`, with rules such as forbidden project edges, forbidden packages per layer, unmapped projects, and unused allowed edges. P-023 also already sketches Phase 2 as type-level facts using IL/Roslyn to catch type dependencies, forbidden APIs, and namespace-level cycles. @@ -39,9 +39,9 @@ Related proposals: introduces disciplined agentic coding ideas for Own.NET/OwnAudit/007, including task contracts, diff policy gates, agent-readable invariants, and analyzer rule catalogs. - [P-031 — Project resource model files](P-031-resource-model-files.md) - proposes declarative per-project resource model files, "own.models.yaml", for acquire/release/capture conventions. It is not an architecture model, but it is useful prior art for project-local declarative models resolved through the semantic layer. + proposes declarative per-project resource model files (`own.models.yaml`) for acquire/release/capture conventions. It is not an architecture model, but it is useful prior art for project-local declarative models resolved through the semantic layer. -Problem +## Problem Own.NET already has strong ambitions around ownership, resources, WPF lifetime diagnostics, Roslyn extraction, and analyzer rules. The missing architecture piece is not “draw a diagram”. The missing piece is a stable fact model that can answer: @@ -54,7 +54,7 @@ Own.NET already has strong ambitions around ownership, resources, WPF lifetime d Without a deterministic architecture fact layer, any high-level architecture review becomes subjective prose. That is useless as a gate. A gate needs facts, fingerprints, and evidence. -Non-goals +## Non-goals Own.NET should not own: @@ -68,10 +68,11 @@ Own.NET should not own: Own.NET may generate diagrams from the intent model and graph, but diagrams must be artifacts, not the canonical architecture model. -Proposed design +## Proposed design -Introduce a small "Own.Arch" subsystem: +Introduce a small `Own.Arch` subsystem: +```text .sln/.csproj/packages.config ↓ project/package extractor @@ -88,21 +89,25 @@ optional generated artifacts: - arch-report.md - architecture.mmd - structurizr.dsl +``` Phase 2 adds: +```text compiled solution / Roslyn workspace / IL ↓ type dependency extractor ↓ type-level arch-facts.json +``` -Architecture intent model +## Architecture intent model Use JSON for the MVP, because OwnAudit already uses stdlib-only Python and existing "arch/rules.json" is JSON. YAML can be added later as an authoring format if needed. Example: +```json { "schema": "own.arch.intent/v1", "architecture": { @@ -145,45 +150,55 @@ Example: ] } } +``` -Finding codes +## Finding codes MVP deterministic rules: +```text ARCH001: ProjectReference crosses a forbidden layer boundary. ARCH002: Forbidden package is referenced from a layer. ARCH003: Project matches zero or multiple layers. ARCH004: Forbidden direct framework/API reference at project level. ARCH030: Allowed dependency is declared but unused. +``` Phase 2 rules: +```text ARCH010: Type-level dependency crosses a forbidden layer boundary. ARCH011: Forbidden API is used in a layer. ARCH012: Namespace-level dependency cycle. ARCH013: Type-level dependency on forbidden framework namespace. +``` Phase 3 heuristic/report-only rules: +```text ARCH020: Type has excessive fan-out. ARCH021: Type mixes APIs from unrelated layers. ARCH022: Interface has too many members. ARCH023: Component behaves as unstable hub. +``` Important: deterministic findings may gate. Heuristic findings are report-only until proven reliable on the project corpus. -Fingerprint policy +## Fingerprint policy Every deterministic architecture finding must have a stable fingerprint: +```text sha256(rule_id | normalized_from_symbol | normalized_to_symbol | normalized_target_kind) +``` Do not include file path or line number in the primary fingerprint. File paths and line numbers are evidence, not identity. This prevents baseline churn during refactors while still catching newly introduced architectural edges. -CLI sketch +## CLI sketch +```bash own-arch extract-projects \ --solution Broker.sln \ --out arch-facts.project.json @@ -199,18 +214,22 @@ own-arch render \ --facts arch-facts.project.json \ --format mermaid \ --out architecture.mmd +``` Phase 2: +```bash own-arch extract-types \ --solution Broker.sln \ --configuration Release \ --out arch-facts.types.json +``` -Output contracts +## Output contracts "arch-facts.project.json": +```json { "schema": "own.arch.facts.project/v1", "projects": [ @@ -231,9 +250,11 @@ Output contracts } ] } +``` "arch-findings.json": +```json { "schema": "own.findings/v1", "tool": "own-arch", @@ -256,8 +277,9 @@ Output contracts } ] } +``` -Integration with OwnAudit +## Integration with OwnAudit Own.NET produces: @@ -276,12 +298,13 @@ OwnAudit consumes these artifacts for: Own.NET should not duplicate OwnAudit’s baseline/diff/reporting layer. -Integration with 007 +## Integration with 007 007 should consume Own.Arch through task specs, not through direct architecture logic. Example 007 task target: +```yaml task_id: ownarch.fix.arch001.ui-sql target_repo: Own.NET input: @@ -298,8 +321,9 @@ gates: required: - own-arch-evaluate - no-new-arch-findings +``` -Acceptance criteria +## Acceptance criteria MVP is accepted when: @@ -317,24 +341,25 @@ MVP is accepted when: - multi-mapped project; - unused allowed dependency. -Risks +## Risks -Risk: Own.Arch becomes a second NDepend clone +### Risk: Own.Arch becomes a second NDepend clone Mitigation: keep scope narrow. Own.Arch detects architecture dependency facts, not every possible code smell. -Risk: false confidence from inferred architecture style +### Risk: false confidence from inferred architecture style Mitigation: style inference must be report-only. Gates rely on deterministic facts. -Risk: duplicate rule languages +### Risk: duplicate rule languages Mitigation: one intent schema. Generated ArchUnitNET, NetArchTest, C4, Mermaid, or Structurizr outputs are optional render targets, not alternative sources of truth. -First implementation slice +## First implementation slice Implement only: +```text architecture.intent.json project graph extraction ARCH001 @@ -343,5 +368,6 @@ ARCH003 arch-findings.json arch-report.md tests +``` Everything else waits. The first slice should be boring, deterministic, and hard to misinterpret. \ No newline at end of file diff --git a/docs/proposals/P-033-probabilistic-data-structures.md b/docs/proposals/P-033-probabilistic-data-structures.md index e0668354..76596f96 100644 --- a/docs/proposals/P-033-probabilistic-data-structures.md +++ b/docs/proposals/P-033-probabilistic-data-structures.md @@ -2,10 +2,16 @@ - **Status:** draft. Imported from a design discussion and normalized into the proposal series (the pasted original suggested the then-taken number P-028). + Note on scope: the subject is **not** the OwnLang analyzer itself but the + legacy .NET Framework / WPF desktop application the audit targets (see + [`audit/README.md`](../../audit/README.md)). The proposed module would ship + as instrumentation guidance for audited legacy apps — e.g. alongside the + runtime harnesses in [`audit/runtime/`](../../audit/runtime/README.md) — not + as part of the analyzer. ## Summary -Own.NET should add a small, dependency-light module for compact runtime diagnostics and fast set operations using classic probabilistic and compressed data structures: +The legacy .NET application under audit (the WPF/.NET Framework desktop app targeted by `audit/README.md`) should gain a small, dependency-light module for compact runtime diagnostics and fast set operations using classic probabilistic and compressed data structures: - bitsets / roaring-style bitmap indexes; - Top-K / heavy-hitter counters; @@ -16,9 +22,9 @@ Own.NET should add a small, dependency-light module for compact runtime diagnost The goal is not to turn a legacy desktop .NET application into a fake distributed analytics platform. That would be architecture cosplay, and nobody needs that circus. The goal is narrower: improve local diagnostics, filtering, dirty tracking, and performance visibility without requiring Redis, Valkey, Kafka, or some other infrastructure animal. -Problem +## Problem -Own.NET has several known pain points: +The legacy .NET application under audit has several known pain points: - large legacy WPF/.NET Framework surface; - heavy dictionaries and reference data; @@ -39,16 +45,16 @@ Current code can observe some issues, but it likely lacks compact, queryable run Without compact summaries, developers either over-log, under-measure, or guess. Guessing is not engineering. It is astrology with stack traces. -Proposed solution +## Proposed solution -Add an internal module tentatively named: - -"Own.Diagnostics.Sketches" +Add an internal module to the audited application, tentatively named +`Own.Diagnostics.Sketches`. The module should expose simple interfaces, not leak implementation details into business logic. Example conceptual interfaces: +```csharp public interface ILatencySketch { void Record(long elapsedMilliseconds); @@ -76,20 +82,21 @@ public interface IBitmapIndex IBitmapIndex Or(IBitmapIndex other); IBitmapIndex Except(IBitmapIndex other); } +``` The first implementation may be deliberately boring: -- "BitArray" / custom packed bitset for dense ids; -- "HashSet" fallback for sparse ids; +- `BitArray` / custom packed bitset for dense ids; +- `HashSet` fallback for sparse ids; - simple Space-Saving Top-K; - simple Count-Min Sketch; - latency sketch adapter with an initially simple histogram implementation. The point is to introduce the model safely before chasing cleverness. Cleverness without containment is how a “small optimization” becomes a haunted subsystem. -Candidate use cases +## Candidate use cases -1. Dirty tracking and affected-row calculation +### 1. Dirty tracking and affected-row calculation Use bitmap indexes to represent sets such as: @@ -102,14 +109,16 @@ Use bitmap indexes to represent sets such as: Instead of scanning large collections repeatedly, compute set operations: +```text RowsToRecalculate = AffectedByRateChange AND CurrentDeclarationRows AND NOT AlreadyRecalculated +``` This is especially suitable when ids are stable integer indexes within a document/import/session. -2. Validation and import diagnostics +### 2. Validation and import diagnostics Use Top-K and Count-Min Sketch to track: @@ -125,7 +134,7 @@ Which 20 validation problems actually hurt users most? Not “which validation problems look important in a meeting”, because apparently humans needed a database to learn humility. -3. Performance telemetry +### 3. Performance telemetry Use latency sketches to record p50/p90/p95/p99 for operations such as: @@ -139,16 +148,18 @@ Use latency sketches to record p50/p90/p95/p99 for operations such as: The output should be local and cheap: +```text Operation: LoadTnvedTree Count: 143 p50: 120 ms p95: 2.4 s p99: 8.1 s Max: 9.6 s +``` Average latency alone should be treated as suspicious. Averages hide pain like a rug hides broken glass. -4. Error grouping +### 4. Error grouping Use SimHash-like fingerprints to group similar: @@ -159,15 +170,15 @@ Use SimHash-like fingerprints to group similar: This can later connect to the existing idea of error ids, hidden stack traces, and build-aware deobfuscation. -Scope +## Scope -MVP +### MVP The MVP should include: -1. "ILatencySketch" -2. "IHeavyHitters" -3. "IBitmapIndex" +1. `ILatencySketch` +2. `IHeavyHitters` +3. `IBitmapIndex` 4. one local diagnostic sink: - JSON file; - text report; @@ -180,7 +191,7 @@ Suggested first targets: - graph 47 recalculation; - validation/import flow. -Phase 2 +### Phase 2 Add: @@ -190,13 +201,17 @@ Add: - optional compact binary export; - analyzer/test coverage for misuse. -Phase 3 +### Phase 3 -Integrate with OwnAudit or 007 by exporting normalized evidence: +Integrate with OwnAudit or 007 by exporting normalized evidence +(OwnAudit's `docs/sketch-based-evidence.md` already anticipates ingesting +these runtime diagnostic exports; the 007-side run evidence is specified in +007's `docs/sketch-aware-evidence.md`): +```json { "schema": "own.sketches.v1", - "source": "Own.NET", + "source": "own.diagnostics.sketches", "operation": "LoadTnvedTree", "latency": { "p50_ms": 120, @@ -206,8 +221,9 @@ Integrate with OwnAudit or 007 by exporting normalized evidence: "top_errors": [], "affected_sets": [] } +``` -Non-goals +## Non-goals This proposal explicitly does not include: @@ -220,7 +236,7 @@ This proposal explicitly does not include: Approximate structures may support diagnostics and optimization. They must not become the source of truth for business decisions. Works fine?! A cart with three wheels “works fine” too. -Safety rules +## Safety rules 1. Every approximate structure must expose its error model in docs. 2. Approximate values must be named as estimates. @@ -229,7 +245,7 @@ Safety rules 5. No global mutable singleton dumping random metrics from everywhere. 6. No business logic may depend on false-positive behavior. -Acceptance criteria +## Acceptance criteria The proposal is successful when: @@ -241,9 +257,9 @@ The proposal is successful when: - no new infrastructure is required; - no correctness-sensitive path relies only on probabilistic results. -Expected benefit +## Expected benefit -Own.NET gets a practical local observability and set-processing layer: +The audited legacy application gets a practical local observability and set-processing layer: - fewer full scans; - better dirty tracking; diff --git a/sandboy/README.md b/sandboy/README.md index 684ba7f4..2b7fc988 100644 --- a/sandboy/README.md +++ b/sandboy/README.md @@ -57,8 +57,7 @@ exec allowlist) to compose without copy-pasting, author the source in CUE and render it down to this shape (`cue export step.cue --out toml > step.toml`) — Sandboy's runtime never needs to know CUE exists. Full rationale and the `#Policy`/`#Base`/`#NoNet` schema this maps onto: -[`007/docs/zero-trust-framework.md`](https://github.com/PhysShell/007/blob/main/docs/zero-trust-framework.md) -§12. +`007/docs/zero-trust-framework.md` §12. ## Build & run @@ -98,7 +97,7 @@ into the wrapped command and bypass the FS/port allowlists entirely. So before The gate runner wraps each step instead of running it bare: ``` -# before: bash -lc "" (under bypassPermissions) +# before: bash -lc "" (bare, no confinement) # after: sandboy run --policy -- bash -lc "" ``` @@ -117,7 +116,8 @@ exists yet** — both are Floor-1 work, not current behaviour: tolerates unknown fields, but this is a **security control**, so it must **fail closed** when it lands: a manifest `schema` bump (or explicit presence check) so an older `o7` that can't enforce a `sandbox_policy` **refuses the - step** rather than silently running it bare under `bypassPermissions`. Relying + step** rather than silently running it bare and unconfined (the + `bypassPermissions` mode applies to the agent phase, not gate steps). Relying on unknown-field tolerance here would fail *open*. See `007/docs/loop-canvas.md`. - **sandboy side — `--report `.** A flag emitting enforcement status / From b114ec527b018bfc133ec81e9807518fbc352461 Mon Sep 17 00:00:00 2001 From: PhysShell Date: Wed, 8 Jul 2026 16:32:51 +0000 Subject: [PATCH 24/25] docs(P-030): escape BLNS payloads for Own string syntax in the sketch Codex review on the consolidated PR: entries containing quotes or backslashes injected raw into the f-string would terminate the Own string literal early (counted as an 'honest rejection') or be decoded into different text, silently skipping exactly the corpus rows the pack is meant to exercise. Escape \\ and " before embedding, and note the complementary raw-lexer path that still exercises the unescaped entries. --- docs/proposals/P-030-naughty-strings-testing.md | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/docs/proposals/P-030-naughty-strings-testing.md b/docs/proposals/P-030-naughty-strings-testing.md index d370cdd5..e27d9d97 100644 --- a/docs/proposals/P-030-naughty-strings-testing.md +++ b/docs/proposals/P-030-naughty-strings-testing.md @@ -152,7 +152,13 @@ def run() -> int: # resource emit_*/kind members (a `let` rhs never takes a string), # so a benign entry parses cleanly and only the naughty content is # under test — the wrapper follows the run_tests.py PRELUDE shape. - src = f'module M\nresource R {{ acquire a release r emit_type "{naughty}" }}\n' + # Escape for Own string syntax first (the lexer decodes \\ and \") + # so entries containing quotes/backslashes reach the parser as + # themselves instead of terminating the literal early or being + # decoded into different text — otherwise those exact corpus rows + # would be silently skipped as "honest rejections". + payload = naughty.replace("\\", "\\\\").replace('"', '\\"') + src = f'module M\nresource R {{ acquire a release r emit_type "{payload}" }}\n' try: parse(src) except (ParseError, LexError): @@ -164,6 +170,12 @@ def run() -> int: return 1 if fails else 0 ``` +Two complementary paths per entry: the *escaped* embedding above proves the +parser/analyzer path survives the payload as data; Layer 1 additionally feeds +the **raw, unescaped** entry straight to `ownlang.lexer` (no wrapper), where +`LexError` is a legal outcome — that path exercises exactly the quote/escape/ +control-character entries the escaped wrapper neutralizes. + Serialization side follows the same shape against `diag_sarif.py` / `cfg_json.py`, asserting `json.loads(...)` / a SARIF-shape check succeeds. From fad5248efd3dc2b1e69185b7a0185c6193a1fc66 Mon Sep 17 00:00:00 2001 From: PhysShell Date: Wed, 8 Jul 2026 16:40:04 +0000 Subject: [PATCH 25/25] docs: address CodeRabbit review on consolidated PR MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - P-010: narrow the lowering claim — brands/refinements/units lower to plain structs + smart constructors; unions to a sealed hierarchy / closed struct union; protocols to consume-on-transition typestate rules - P-026: define the one-bucket-per-finding precedence rule so the strictness score cannot double-count a defect across dimensions - P-032: reconcile the intent-model contract with P-023 — one canonical machine contract (architecture.intent.json), P-023's rules.yaml as the authoring layer that renders to it - P-033: state that IBitmapIndex And/Or/Except are non-mutating and return new indexes - agent-capability-layer: acceptance gate build mode aligned with sandboy/README.md (cargo build --release) - agentic-coding-discipline: language tags on all 61 bare fences (MD040) --- docs/agentic-coding-discipline-proposal.md | 122 +++++++++--------- docs/notes/agent-capability-layer.md | 2 +- docs/proposals/P-010-type-disciplines.md | 10 +- .../P-026-csharp-strictness-retrofit.md | 8 ++ docs/proposals/P-032-own-arch-facts.md | 10 +- .../P-033-probabilistic-data-structures.md | 2 + 6 files changed, 87 insertions(+), 67 deletions(-) diff --git a/docs/agentic-coding-discipline-proposal.md b/docs/agentic-coding-discipline-proposal.md index aeb729ce..22e54e45 100644 --- a/docs/agentic-coding-discipline-proposal.md +++ b/docs/agentic-coding-discipline-proposal.md @@ -56,7 +56,7 @@ Плохой стиль: -``` +```text Сделай модуль анализа async-проблем в Own.NET. ``` @@ -64,7 +64,7 @@ Правильный стиль: -``` +```text Сделай только analyzer rule OWN0001: - найти async void методы, кроме event handlers - проект: .NET 8 analyzer @@ -83,7 +83,7 @@ Шаблон: -``` +```text Сначала НЕ пиши код. Дай план изменения: @@ -105,7 +105,7 @@ AI очень любит "помочь" через скрытые побочны Для твоих проектов я бы держал постоянный блок: -``` +```text Запрещено: - менять public API без явного указания; - добавлять NuGet-пакеты без отдельного разрешения; @@ -127,7 +127,7 @@ AI очень любит "помочь" через скрытые побочны Я бы сделал в репозитории папку: -``` +```text .ai/ project-context.md architecture-rules.md @@ -142,7 +142,7 @@ AI очень любит "помочь" через скрытые побочны **`.ai/project-context.md`** -``` +```text Проект Own.NET — набор инструментов для анализа .NET-кода: - Roslyn analyzers; - архитектурные проверки; @@ -156,7 +156,7 @@ AI очень любит "помочь" через скрытые побочны **`.ai/architecture-rules.md`** -``` +```text Правила: - analyzer rules должны быть маленькими и независимыми; - каждая rule имеет ID, описание, severity, examples, tests; @@ -168,7 +168,7 @@ AI очень любит "помочь" через скрытые побочны **`.ai/forbidden-changes.md`** -``` +```text Нельзя: - делать глобальные refactorings; - добавлять зависимости без обоснования; @@ -186,7 +186,7 @@ AI очень любит "помочь" через скрытые побочны Шаблон review: -``` +```text Проверь этот diff как злой maintainer. Ищи: @@ -211,7 +211,7 @@ AI очень любит "помочь" через скрытые побочны Например: -``` +```text Review rule OWN0003: EventSubscriptionLeakAnalyzer. Проверь, не даёт ли analyzer false positive на weak event pattern, IDisposable cleanup, composite disposable, и WPF Binding events. ``` @@ -224,7 +224,7 @@ AI плохо понимает невидимые правила старого Например: -``` +```text WPF invariants: - ViewModel не должен напрямую знать о View; - подписки на events должны освобождаться; @@ -237,7 +237,7 @@ WPF invariants: Или для твоих справочников/TNVED/ставок: -``` +```text Data invariants: - не материализовать весь справочник без необходимости; - большие деревья загружать лениво; @@ -253,13 +253,13 @@ Data invariants: Не так: -``` +```text Сделай модуль AsyncAnalyzer. ``` А так: -``` +```text Task 1: OWN0001 async void detector Task 2: OWN0002 .Result/.Wait() detector in UI context Task 3: OWN0003 fire-and-forget Task without observation @@ -272,7 +272,7 @@ Task 8: documentation examples Каждая задача должна иметь: -``` +```text - вход; - ожидаемый diagnostic; - false positive cases; @@ -288,7 +288,7 @@ Task 8: documentation examples В конце каждого промпта: -``` +```text Definition of Done: - код компилируется; - добавлены тесты; @@ -309,7 +309,7 @@ Definition of Done: Самый жирный кандидат. -``` +```text OWNASYNC001: async void outside event handlers OWNASYNC002: blocking wait on Task: .Wait(), .Result, GetAwaiter().GetResult() OWNASYNC003: fire-and-forget Task without observation/logging @@ -322,7 +322,7 @@ OWNASYNC006: ConfigureAwait policy violation **IDisposable / lifetime module** -``` +```text OWNLIFE001: IDisposable field not disposed OWNLIFE002: event subscription not unsubscribed OWNLIFE003: IDisposable created but not owned @@ -334,7 +334,7 @@ OWNLIFE005: Stream/SqlConnection/DbCommand lifetime leak **SQL/data-access module** -``` +```text OWNDATA001: string interpolation in SQL OWNDATA002: concatenated SQL with user/domain input OWNDATA003: SELECT * in repository/query object @@ -347,7 +347,7 @@ OWNDATA006: temp table incompatibility SQL Server/SQLite **WPF module** -``` +```text OWNWPF001: ObservableCollection modified outside UI thread OWNWPF002: event subscription leak in View/ViewModel/Presenter OWNWPF003: long-running operation in command handler @@ -362,7 +362,7 @@ OWNWPF006: Bitmap/Image resource not released Вот шаблон, который можно реально использовать: -``` +```text Ты работаешь как senior .NET/Roslyn developer. Контекст: @@ -399,7 +399,7 @@ OWNWPF006: Bitmap/Image resource not released А после утверждения плана: -``` +```text Реализуй только согласованный план. После кода дай: 1. список изменённых файлов; @@ -414,7 +414,7 @@ OWNWPF006: Bitmap/Image resource not released Например: -``` +```text Review this PR for Own.NET. Be strict. Assume the code is wrong until proven otherwise. @@ -481,7 +481,7 @@ AI полезен не как "разработчик вместо тебя", а То есть NoBootCamp-идею надо не "применить к 007", а закодировать в 007 как режимы работы: -``` +```text task contract → isolated run → gates → harvest → judge/review → verdict ``` @@ -541,7 +541,7 @@ commands = [ Что добавить в 007: -``` +```bash o7 validate-task --task task.o7.toml o7 run --task task.o7.toml o7 inspect-run runs/ @@ -550,7 +550,7 @@ o7 judge-run runs/ Минимальный MVP: -``` +```text task.md # человеческое описание task.o7.toml # машинный контракт gate.toml # команды проверки @@ -563,7 +563,7 @@ policy.toml # запреты / scope / allowlist Но для coding-agent задач я бы разделил: -``` +```bash o7 plan o7 run o7 judge @@ -571,7 +571,7 @@ o7 judge То есть агент сначала не имеет права менять код. Он должен сгенерировать план: -``` +```text runs/// plan.md plan.meta.json @@ -580,7 +580,7 @@ runs/// Потом отдельный gate проверяет план: -``` +```text - план не трогает запрещённые файлы; - план не добавляет зависимости; - план перечисляет тесты; @@ -590,7 +590,7 @@ runs/// И только потом: -``` +```bash o7 run --from-plan runs/.../plan.md ``` @@ -600,7 +600,7 @@ o7 run --from-plan runs/.../plan.md В Own.NET мы могли писать: -``` +```text Не меняй public API. Не добавляй зависимости. Не трогай build scripts. @@ -631,13 +631,13 @@ require_public_api_report = true И gate после diff должен проверять: -``` +```text diff.patch против policy.toml ``` Если агент полез куда не просили: -``` +```yaml FAIL: touched forbidden file Directory.Build.props FAIL: added dependency Microsoft.Extensions.DependencyInjection FAIL: changed public API without approval @@ -651,7 +651,7 @@ README уже говорит, что 007 harvest'ит `meta.json`, `agent.stdout Я бы расширил canonical record: -``` +```text runs/// task.md task.o7.toml @@ -687,7 +687,7 @@ runs/// Зачем: 007 должен быть не просто "запустил агента", а черный ящик самолёта после падения. Агент внёс diff? Докажи: -``` +```text что он запускался в правильном repo; от какого base commit; какие файлы поменял; @@ -707,19 +707,19 @@ runs/// Это очень важная часть. В терминах NoBootCamp: -``` +```text self-correction / review prompt ``` В терминах 007: -``` +```text judge command + rubric + schema + verdict contract ``` То есть не "Claude сам себя проверил, ну значит норм". Нет. Отдельный режим: -``` +```text agent делает diff judge смотрит diff + task + gate logs judge возвращает machine-readable verdict @@ -754,7 +754,7 @@ judge возвращает machine-readable verdict Что это значит для применения NoBootCamp-идей: -``` +```text Для доверенных своих реп: Можно начинать с policy/gates/worktree/evidence. @@ -776,7 +776,7 @@ trust = "trusted-local" И правила: -``` +```yaml trusted-local: worktree + gates ok @@ -799,7 +799,7 @@ untrusted: Это отлично подходит к 007, потому что 007 — glue/orchestration, а самые опасные поверхности там: -``` +```text - model output parsing; - gate.toml parsing; - findings.json parsing; @@ -857,14 +857,14 @@ profile = "security" Что делать: -``` +```bash o7 judge --jobs 4 o7 judge --jobs 8 ``` Но обязательно: -``` +```text - bounded concurrency; - retry/backoff; - per-file error isolation; @@ -878,13 +878,13 @@ o7 judge --jobs 8 Новый режим: -``` +```bash o7 plan --repo ../Own.NET --base main --task ./task.md --out ./runs/... ``` Выход: -``` +```text plan.md plan.json plan-verdict.json @@ -892,7 +892,7 @@ plan-verdict.json Проверяет: -``` +```text - scope; - forbidden files; - required tests; @@ -904,13 +904,13 @@ plan-verdict.json Текущий MVP, но с policy: -``` +```bash o7 run --task task.o7.toml --gate ../Own.NET/.007/gate.toml ``` Обязательно собирает: -``` +```text - diff.patch; - changed files; - touched forbidden paths; @@ -922,13 +922,13 @@ o7 run --task task.o7.toml --gate ../Own.NET/.007/gate.toml Отдельная проверка результата: -``` +```bash o7 judge-run runs/Own.NET/ ``` Judge получает: -``` +```text - original task; - plan; - diff; @@ -940,7 +940,7 @@ Judge получает: Возвращает: -``` +```text PASS | FAIL | NEEDS_HUMAN ``` @@ -948,7 +948,7 @@ PASS | FAIL | NEEDS_HUMAN Суперважно: -``` +```bash o7 replay runs/Own.NET/ ``` @@ -958,7 +958,7 @@ o7 replay runs/Own.NET/ Вот как я бы сформулировал роль 007: -``` +```text 007 is not an AI coding assistant. 007 is a reproducible, gated, auditable execution harness for AI coding assistants. ``` @@ -969,7 +969,7 @@ o7 replay runs/Own.NET/ Claude/Codex могут генерировать код. 007 должен отвечать за: -``` +```text - изоляцию; - scope; - запреты; @@ -984,7 +984,7 @@ Claude/Codex могут генерировать код. 007 должен отв И вот это уже реально применимо ко всем твоим штукам: -``` +```text Own.NET → агент пишет analyzer/rule/docs/tests OwnAudit → агент triage'ит findings / FP / отчёты legacy WPF → агент делает маленькие refactor tasks @@ -998,13 +998,13 @@ sandboy → будущая sandbox/plugin boundary Ближайший полезный шаг: -``` +```text Task Contract + Diff Policy Gate ``` Минимально: -``` +```text src/task_contract.rs src/diff_policy.rs schemas/task.o7.schema.json @@ -1014,7 +1014,7 @@ examples/policy.safe-defaults.toml Первый gate: -``` +```text - changed files are inside allowed_paths; - forbidden_paths untouched; - max files/lines not exceeded; @@ -1058,7 +1058,7 @@ require_tests = true И после run: -``` +```bash o7 policy-check runs//diff.patch --policy task.o7.toml ``` @@ -1086,13 +1086,13 @@ o7 policy-check runs//diff.patch --policy task.o7.toml Для 007 правильная версия такая: -``` +```text NoBootCamp principles → executable agent harness protocol ``` Грубо: -``` +```text prompt discipline → task contract negative prompts → diff policy plan-then-build → o7 plan + o7 run diff --git a/docs/notes/agent-capability-layer.md b/docs/notes/agent-capability-layer.md index 0a5fde98..9d864fc2 100644 --- a/docs/notes/agent-capability-layer.md +++ b/docs/notes/agent-capability-layer.md @@ -112,7 +112,7 @@ syscall/reachable-binary level. Two different enforcement models — don't confl | Phase | What | Verdict | |---|---|---| | **1. Policy engine** | `owen-policy`: parse `owen.policy.toml`, `policy check/explain`, `gen-ignore` | **Do.** Daily use, zero risk, not built. 80% of daily value. | -| **2. Runner enforcement** | wrap agent in worktree + Sandboy | **Spiked** (`sandboy/` — authored, not yet compiled; acceptance gate: `cargo build` + `tests/demo.sh`, see `sandboy/README.md`). Wire to a real gate step. | +| **2. Runner enforcement** | wrap agent in worktree + Sandboy | **Spiked** (`sandboy/` — authored, not yet compiled; acceptance gate: `cargo build --release` + `tests/demo.sh`, see `sandboy/README.md`). Wire to a real gate step. | | **3. WIT tool components** | move tools to capability-scoped components | **Selective.** WIT only where input/author is untrusted: `secret-scanner`, `patch-analyzer`, `verifier-adapter` (parse untrusted output) — yes. `memory-search` over **your own** data — plain code, WIT buys nothing. | | **4. MCP/WIT bridge** | `owen-mcp` tools backed by policy + components | Thin, later. | diff --git a/docs/proposals/P-010-type-disciplines.md b/docs/proposals/P-010-type-disciplines.md index 490efedd..10222da3 100644 --- a/docs/proposals/P-010-type-disciplines.md +++ b/docs/proposals/P-010-type-disciplines.md @@ -74,10 +74,12 @@ Own.Types └─ multi-language frontends ``` -The first four pillars share one restraint: brands, refinements, units, unions, -and protocols all lower to plain structs/records plus smart constructors — the -*discipline* is enforced by analyzer, not by a second type checker that could -drift from the affine core (the project's standing meta-irony). +The first four pillars share one restraint: brands, refinements, and units +lower to plain structs/records plus smart constructors; unions lower to a +sealed hierarchy / closed struct union; protocols are enforced as +consume-on-transition typestate rules — in every case the *discipline* is +enforced by analyzer, not by a second type checker that could drift from the +affine core (the project's standing meta-irony). ## Scope diff --git a/docs/proposals/P-026-csharp-strictness-retrofit.md b/docs/proposals/P-026-csharp-strictness-retrofit.md index 77acc3f2..ff55c1ee 100644 --- a/docs/proposals/P-026-csharp-strictness-retrofit.md +++ b/docs/proposals/P-026-csharp-strictness-retrofit.md @@ -92,6 +92,14 @@ with one score to argue about. | Null-safety not enforced (`!`, opt-in NRT) | external analyzers via SARIF adapter; `NO-TOOL` until wired | | Local immutability not expressed (mutable setters on domain state) | `NO-TOOL` until wired (candidate: setter census via the P-001/P-014 extractor) | +**One bucket per finding.** Every finding counts against exactly one *primary* +dimension, chosen by the table order above (first matching row wins — e.g. an +ambiguous `IDisposable` transfer on a field that is also an implicit-state flag +scores under ownership/lifetime, not typestate). Reclassifying a finding moves +it between buckets without changing its total contribution, so the headline +score never double-counts one defect across dimensions and stays stable under +re-bucketing. + ## Non-goals (the most important section) - **No new heuristic detector.** No regex over `return null` / `throw`. If a diff --git a/docs/proposals/P-032-own-arch-facts.md b/docs/proposals/P-032-own-arch-facts.md index 92922771..3561be02 100644 --- a/docs/proposals/P-032-own-arch-facts.md +++ b/docs/proposals/P-032-own-arch-facts.md @@ -103,7 +103,15 @@ type-level arch-facts.json ## Architecture intent model -Use JSON for the MVP, because OwnAudit already uses stdlib-only Python and existing "arch/rules.json" is JSON. YAML can be added later as an authoring format if needed. +Use JSON for the MVP, because OwnAudit already uses stdlib-only Python and existing `arch/rules.json` is JSON. + +Contract note (reconciling with P-023): P-023 names hand-written +`architecture.rules.yaml` as the intent-model source of truth. This proposal +keeps a single canonical *machine* contract — `architecture.intent.json`, the +form the evaluator reads — and treats P-023's YAML as the human authoring +layer that renders to it (`yaml → json`, in CI or locally). Until that +authoring layer exists, the JSON file is hand-written and reviewed directly; +at no point are there two independently edited sources of truth. Example: diff --git a/docs/proposals/P-033-probabilistic-data-structures.md b/docs/proposals/P-033-probabilistic-data-structures.md index 76596f96..df24f31c 100644 --- a/docs/proposals/P-033-probabilistic-data-structures.md +++ b/docs/proposals/P-033-probabilistic-data-structures.md @@ -78,6 +78,8 @@ public interface IBitmapIndex void Add(int id); void Remove(int id); bool Contains(int id); + // Non-mutating: each set operation returns a new index; the receiver + // and `other` are never modified (no aliasing surprises for callers). IBitmapIndex And(IBitmapIndex other); IBitmapIndex Or(IBitmapIndex other); IBitmapIndex Except(IBitmapIndex other);