Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
125 changes: 125 additions & 0 deletions docs/architecture-audit-2026-07-29/RpcPerformanceClosure.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
# Architecture Audit — Runtime Profiling and Contract Gates

**Scope:** PR #568 rebuilt on the current `develop`, limited to opt-in memory
profiling, RPC output-validation policy, generated Rust-to-TypeScript bindings,
and transport adapter serialization.
**Date:** 2026-07-30
**Auditor:** Codex cleanup session

## Acceptance criteria

- The PR contains one intended commit and no commits from the pre-force-push
`develop` history.
- Normal builds do not link DHAT, replace the allocator, start a profiling
worker, or validate RPC outputs in production.
- The opt-in DHAT lifecycle has one owner and finalizes at Tauri exit.
- RPC drift diagnostics have an explicit mode and a fixed memory bound.
- Mock and Tauri transport adapters serialize the same typed payload.
- Generated TypeScript bindings match the serialized Rust field names.
- No dependency removal, lockfile churn, missing quality-script entry, or
unrelated product/UI code remains.

## Layer 1 — Compilation correctness

- `pnpm typecheck`: passed.
- RPC output-validation Vitest: 6/6 passed.
- `cargo test -p transport --features ts-rs`: 9/9 passed.
- `cargo test -p session_persistence --features ts-rs`: 37/37 passed.
- `cargo check -p org2 --features dhat-heap`: passed.
- Changed-file rustfmt: passed. Whole-workspace rustfmt still reports unchanged
formatting drift outside this PR.
- The workspace still reports one unchanged `unused_mut` warning in
`orgtrack-core`; no changed module emitted a warning.

## Layer 2 — Dead code and structural deduplication

- The Tauri and mock adapters now delegate field naming to the same serde
definitions instead of maintaining parallel JSON maps.
- The transport emitter remains a pre-existing initialized service with no
production emit call sites outside its crate. This PR does not claim that the
transport events are a live product path; its tests enforce adapter/mock wire
parity for the existing boundary.
- The replay originally removed `ali-react-table` and `clsx`; that unrelated
dependency cleanup and its lockfile churn were removed from PR #568.
- A `check:component-boundaries` package entry whose script did not exist on
current `develop` was also removed.

## Layer 3 — Naming consistency

| Term | Meaning | Verdict |
| --- | --- | --- |
| `dhat-heap` | Explicit developer-only Rust heap-profiling feature | consistent |
| `RpcOutputValidationMode` | Process-wide `off` / `warn` / `throw` response policy | consistent |
| `output drift` | Rust response that fails the declared frontend output schema | consistent |
| `AgentEvent` | Tagged typed payload emitted on `agent://event` | consistent |

## Layer 4 — Semantic overloading

- Profiling activation is separate from normal runtime diagnostics.
- RPC validation mode is separate from command execution success.
- Transport event `type` identifies the agent-event variant; the Tauri channel
name identifies the event family. Neither is used as the other.

## Layer 5 — Default branch analysis

| Branch or fallback | Result |
| --- | --- |
| Normal Cargo feature set | DHAT dependency, allocator, worker, and exit hook are absent |
| Invalid or oversized start delay | Falls back to the documented 15-second delay |
| Browser global unavailable | RPC drift recording is a no-op |
| Production RPC policy | Output validation is `off` |
| Development/test RPC policy | Output validation is `warn`; explicit CI can select `throw` |
| Repeated profiler scheduling/finalization | State machine makes both idempotent |

## Layer 6 — Cross-domain concept leakage

- DHAT ownership stays in the desktop crate and is feature-gated.
- Session-persistence exports only its own `CacheStats` DTO.
- RPC policy stays at the typed invoke boundary.
- Transport payload types remain in the transport crate and do not import UI
or session-persistence concepts.

## Layer 7 — New developer confusion test

- Profiling scripts and docs name the required feature, delay, output path, and
shutdown requirement.
- Generated bindings make the Rust/TypeScript contract discoverable.
- Comments distinguish developer profiling from production behavior.

## Layer 8 — Wire protocol and serialization

| Boundary | Source of truth | Verification |
| --- | --- | --- |
| Agent lifecycle | `AgentEvent` serde tag/content and camel-case attributes | transport tests inspect serialized JSON |
| Text stream | `TextChunk` serde attributes | generated binding test and adapter payload test |
| Tool event | `ToolEvent` / `ToolEventType` serde attributes | generated binding test and adapter payload test |
| Cache stats | `CacheStats` serde attributes | generated binding test |
| RPC response | Procedure output Zod schema | 6-mode/failure-path Vitest cases |

Opaque tool `params` and `result` intentionally export as `unknown`; numeric
cache counters export as `number` under the repository's bounded-value
contract.

## Layer 9 — Init parity

| Entry path | Normal build | `dhat-heap` build |
| --- | --- | --- |
| Desktop startup | Existing runtime only | Installs the gated allocator and schedules one profiler start |
| Tauri exit | Existing shutdown | Finalizes the profiler once before process exit |
| Tests/other crates | No profiling initialization | No implicit initialization |

## Layer 10 — Resolver symmetry

- The profiler delay and output path each resolve from one documented
environment variable with one fallback.
- RPC output policy has one module-level source of truth used by every
`typedInvoke` call; set/get/reset operate on that same state.
- No multi-field account, model, workspace, or identity resolver is changed.

## Verdict

The rebuilt PR is scoped to runtime profiling and contract enforcement. The
historical branch contamination, unrelated dependency removal, broken package
script, and stale PR #512 audit claims were removed.

**Architecture verdict: pass.**
62 changes: 62 additions & 0 deletions docs/contributing/macos-memory-profiling.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
# macOS memory profiling with Instruments

Use macOS Instruments for interactive ORGII memory investigations. Unlike the
opt-in DHAT allocator, Instruments can attach to a normally running app without
capturing a Rust backtrace under a global lock for every allocation.

For a quick, zero-profiler-overhead comparison, click the gauge button in the
ORGII navigation sidebar. Its panel separates the native backend, WebView
helpers, file cache, terminal buffers, and other runtime estimates. Record the
values before the workload, after repeating it, and again after returning to
idle. Use Instruments when that comparison points to persistent native-backend
growth that needs allocation call stacks.

## Record native allocations

1. Start ORGII normally and wait for the main window to become usable.
2. From the repository root, run:

```bash
./scripts/dev/profile-macos-memory.sh
```

3. Exercise one controlled workload during the two-minute recording.
4. Let the recording finish. ORGII remains open, and the script prints the
generated `.trace` path.
5. Open the trace using the printed command, then inspect persistent bytes,
allocation growth, and responsible call trees.

The default `Allocations` recording is limited to two minutes and attaches only
to the native `org2` process. Override the limit when necessary:

```bash
./scripts/dev/profile-macos-memory.sh --duration 5m
```

If multiple ORGII instances are running, select the intended backend explicitly:

```bash
./scripts/dev/profile-macos-memory.sh --pid 12345
```

Run the `Leaks` template for a bounded native leak scan:

```bash
./scripts/dev/profile-macos-memory.sh --template Leaks --duration 2m
```

The first recording may cause macOS to request Developer Tools permission. If
attachment is denied, enable the terminal under **System Settings → Privacy &
Security → Developer Tools**, restart the terminal, and retry.

## What the trace covers

Attaching to `org2` covers native allocations in the Tauri/Rust backend and
native frameworks loaded into that process. It does not provide the JavaScript
object-retainer graph inside the WebKit WebContent helper. Use Web Inspector
heap snapshots for JavaScript/DOM leaks, and use the built-in App memory
snapshot or Activity Monitor when comparing total backend plus helper RSS.

For comparable results, record the same duration and workload before and after
a change. Let the app return to idle before the recording ends so persistent
growth is easier to distinguish from temporary peaks.
109 changes: 109 additions & 0 deletions docs/contributing/rust-heap-profiling.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
# Rust heap profiling with DHAT

ORGII includes an opt-in DHAT build for diagnosing allocations retained by the
Rust desktop backend. It is a developer tool, not a production feature and not
a replacement for the App memory/RSS monitor.

DHAT can impose extreme per-allocation overhead on the full ORGII GUI and may
make the Tauri window temporarily unresponsive. For normal interactive memory
investigations on macOS, prefer
[Instruments](./macos-memory-profiling.md). Reserve this DHAT path for short,
controlled Rust-backend scenarios where that slowdown is acceptable.

## Run a profile

From the repository root:

```bash
./scripts/dev/profile-rust-heap.sh
```

The script first builds the production frontend, then builds the `org2` desktop
binary with the optimized, symbolized `dhat` Cargo profile and enables the
`dhat-heap` feature. This keeps the measured Tauri/WebView path close to the
release application while preserving Rust allocation symbols.

After Tauri backend setup completes, DHAT waits 15 seconds before it starts.
Wait for the terminal's `[dhat] Rust heap profiling started` message, exercise
one controlled scenario, then quit ORGII normally. The final Tauri exit event
synchronously writes a timestamped JSON profile below
`${TMPDIR:-/tmp}/orgii-dhat-profiles/`, and the script prints the exact path. A
forced process kill cannot flush the profile.

On macOS, quit the application with `Cmd+Q`. The red window button only hides
the main window and does not end the profiling process.

Override the post-setup delay when a scenario needs more or less settling
time. Values from 0 through 3600 seconds are accepted:

```bash
ORGII_DHAT_START_DELAY_SECS=30 ./scripts/dev/profile-rust-heap.sh
```

To choose the output file explicitly:

```bash
ORGII_DHAT_OUTPUT=/absolute/path/session-switch.json \
./scripts/dev/profile-rust-heap.sh
```

For repeated profiling while `build/` is already current, skip the webpack
rebuild explicitly:

```bash
ORGII_DHAT_SKIP_FRONTEND_BUILD=true ./scripts/dev/profile-rust-heap.sh
```

The skip mode refuses to run unless `build/index.html` exists.

Open the JSON file in the
[DHAT viewer](https://nnethercote.github.io/dh_view/dh_view.html). Compare
`At t-gmax` for the peak and `At t-end` for allocations still live when the app
closed. Allocation call stacks are more useful than the headline byte count.

## Recommended scenario shape

1. Launch the app and wait for the terminal to confirm profiling has started.
2. Repeat one operation enough times to expose growth, such as opening and
leaving replay-heavy sessions.
3. Return to an idle screen and wait for configured eviction grace periods.
4. Quit the app normally with `Cmd+Q` so the final Tauri exit event writes the profile. The red window button only hides the app on macOS.
5. Repeat with the same workload after a proposed fix and compare call stacks,
peak bytes, and end-of-run live bytes.

DHAT starts once, after backend setup plus the configured settling delay.
Startup allocations before that point are intentionally excluded so allocation
backtrace collection cannot stall the initial WebView render. Before profiling
starts, the feature-gated allocator delegates directly to the system allocator
without entering DHAT's tracking lock. Use repeated before/after scenarios
instead of treating the final total as a standalone pass/fail number.

## Scope and limitations

DHAT replaces the Rust process-wide global allocator only when the
`dhat-heap` feature is enabled. Default development and release builds retain
their normal allocator and do not link the optional `dhat` dependency.

DHAT measures allocations made through the Rust allocator in the main ORGII
backend process. It does not attribute:

- JavaScript objects, DOM nodes, Jotai atoms, or WebView caches;
- xterm/WebGL/GPU memory;
- terminal, CLI-agent, MCP, or other child processes;
- memory-mapped files, kernel/file caches, or the complete process RSS.

Use WebKit/Chromium DevTools heap snapshots for the WebView object graph and
the built-in App memory snapshot for whole-process and owned-helper trends.

## Manual Cargo invocation

After `pnpm run build`, the equivalent backend command is:

```bash
ORGII_DHAT_OUTPUT=/absolute/path/dhat-heap.json \
cargo run --manifest-path src-tauri/Cargo.toml \
--profile dhat --features dhat-heap --bin org2
```

Do not add `dhat-heap` to default features or production build scripts. The
profiling allocator intentionally adds substantial runtime and memory overhead.
41 changes: 41 additions & 0 deletions docs/org2-performance-guard-2026-07-29/RpcPerformanceClosure.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
# Performance Guard — Runtime Profiling and Contract Gates

**Scope:** PR #568 rebuilt on current `develop`.
**Date:** 2026-07-30

## Lifecycle matrix

| Dimension | Audited behavior |
| --- | --- |
| App | Normal builds create no profiler resource. A `dhat-heap` build schedules one worker and finalizes one profiler guard at Tauri exit. |
| Document | Developer heap profiling intentionally continues across visible/hidden states so the requested measurement window is complete. No production timer is added. |
| Network | No network request, retry, subscription, or polling path is added. |
| Identity | Profiling and RPC drift diagnostics retain no account, endpoint, org, or session identity. |
| Scope | RPC drift records contain command, schema issues, and timestamp only. The buffer is process-local and capped at 200 records. |
| Session | No per-session registry or retained session payload is added. |
| Instance | Each desktop process owns its feature-gated profiler state and output path; no cross-process cache is introduced. |

## Findings and evidence

| Area | Verdict | Evidence | Change or reason kept | Verification |
| --- | --- | --- | --- | --- |
| Background work | keep | `schedule_from_env` can spawn one named thread only when `dhat-heap` is compiled; the delay is one-shot and bounded to one hour. | State transitions reject repeat scheduling; Tauri `Exit` finalizes or cancels the pending state. | `dhat_profiling` unit tests plus feature-enabled application check. |
| Memory | keep | One optional DHAT guard and `window.__orgiiRpcOutputDrift`. | Guard is dropped at exit; drift array evicts oldest records above 200. | RPC tests cover the cap and all validation modes. |
| Scope/isolation | keep | No identity-bearing cache; output path comes from the current process environment. | Profiling output and diagnostics remain process-local. | Static lifecycle trace; no account/endpoint path changed. |
| Rendering/hot path | keep | Production RPC output-validation default is `off`. | Development/CI validation is explicit; normal rendering gets no schema-parse overhead from this change. | RPC mode tests and TypeScript typecheck. |
| Wire serialization | keep | Transport adapters call serde directly instead of constructing parallel JSON objects. | One typed serialization path avoids duplicate field-remapping work and drift. | Transport tests inspect agent, text, and tool payloads. |

## Verification

- RPC output-validation Vitest: 6/6 passed.
- `cargo test -p transport --features ts-rs`: 9/9 passed.
- `cargo test -p session_persistence --features ts-rs`: 37/37 passed.
- `pnpm typecheck`, changed-file ESLint, script `bash -n`, changed-file
rustfmt, and `cargo check -p org2 --features dhat-heap`: passed.
- Whole-workspace rustfmt reports unchanged formatting drift outside this PR.
- A real Tauri profiling run and visible/hidden/post-exit measurement were not
repeated during this branch-history cleanup.

**Performance verdict: blocked — static ownership, bounds, tests, and compile
gates are covered, but a real feature-enabled Tauri profiling run was not
collected in this cleanup session.**
Loading
Loading