Skip to content

fix(p2): cancellable/bounded async task lifecycle (FTS loop, forge3d accept, telemetry spawns)#3584

Closed
KooshaPari wants to merge 82 commits into
tailcallhq:mainfrom
KooshaPari:fix/p2-async-lifecycle-20260628
Closed

fix(p2): cancellable/bounded async task lifecycle (FTS loop, forge3d accept, telemetry spawns)#3584
KooshaPari wants to merge 82 commits into
tailcallhq:mainfrom
KooshaPari:fix/p2-async-lifecycle-20260628

Conversation

@KooshaPari

Copy link
Copy Markdown

Summary

  • forge_api FTS loop (crates/forge_api/src/forge_api.rs): replaced fire-and-forget tokio::spawn with a tracked JoinHandle and CancellationToken. The loop select!s on shutdown so it exits immediately rather than waiting for the next interval tick. BackgroundTasks owns the handle and is embedded in ForgeAPI; tasks are aborted on drop.
  • forge3d accept loop (crates/forge3d/src/server.rs): serve() now takes a CancellationToken and select!s on it in the accept loop. Per-connection spawns are tracked in a JoinSet (not fire-and-forget); on cancellation the set is aborted and drained.
  • Convention documented in both files via // Task-lifecycle convention (P2.4) comments.
  • forge_dbd untouched (owned by lane P2.3).

Test plan

  • serve_exits_on_cancellation — new test verifying serve() returns Ok(()) within 2 s of token cancellation
  • cargo test -p forge3d -p forge_api — 28/28 green
  • cargo build (full workspace) — exit 0
  • cargo clippy -p forge3d -p forge_api — clean

🤖 Generated with Claude Code

KooshaPari and others added 30 commits May 1, 2026 20:06
- Bootstrap deny.toml with license allowlist + advisory ignores
- Add license = MIT to workspace.package (was missing)
- Add license.workspace = true to all 27 crate manifests
- Ignore transitive unmaintained (bincode, yaml-rust, paste, rustls-pemfile)
- Ignore transitive vulns (hickory-proto, rustls-webpki) via aws-sdk/reqwest

Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Co-authored-by: Phenotype Agent <agent@phenotype.ai>
Pin all action refs to immutable SHAs across workflow files:
- checkout@v4 → @11bd71901bbe5b1630ceea73d27597364c9af683
- checkout@v6 → @de0fac2e4500dabe0009e67214ff5f5447ce83dd
- setup-node@v4/v5, setup-python@v4/v5, setup-go@v5
- upload-artifact@v4/v7, download-artifact@v4
- cache@v3/v4, github-script@v7
- configure-pages@v5/v6, deploy-pages@v4/v5
- upload-pages-artifact@v3/v5, dependency-review-action@v4

Fixes version-tag normalization (add v4/v5 tags where missing).
Fixes double-SHA corruption artifacts from prior patching rounds.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Reduce README from 1124 to 169 lines (-85%).
Keep: project name, description, quickstart, usage examples, why forge,
installation, community, documentation link.
Add fork disclaimer pointing to upstream tailcallhq/forgecode.
Preserve all upstream content via pointer comment.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Reduce README from 1124 to 169 lines (-85%).
Keep: project name, description, quickstart, usage examples, why forge,
installation, community, documentation link.
Add fork disclaimer pointing to upstream tailcallhq/forgecode.
Preserve all upstream content via pointer comment.

Co-authored-by: Phenotype Agent <agent@phenotype.ai>
Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Co-authored-by: Phenotype Agent <agent@phenotype.ai>
Verified resolved upstream; advisory no longer triggers.

Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
…nale

- Add 4 ignore entries for bincode 1.x (RUSTSEC-2025-0141), paste
  (2024-0436), rustls-pemfile (2025-0134), yaml-rust (2024-0320) — all
  transitive via upstream forgecode workspace deps; resolution depends on
  upstream tailcallhq/forgecode bumps.
- Pre-existing fork-specific RUSTSEC-2026-* ignores preserved.
- cargo deny check advisories: PASS.
…nale (#8)

- Add 4 ignore entries for bincode 1.x (RUSTSEC-2025-0141), paste
  (2024-0436), rustls-pemfile (2025-0134), yaml-rust (2024-0320) — all
  transitive via upstream forgecode workspace deps; resolution depends on
  upstream tailcallhq/forgecode bumps.
- Pre-existing fork-specific RUSTSEC-2026-* ignores preserved.
- cargo deny check advisories: PASS.

Co-authored-by: Phenotype Agent <agent@phenotype.ai>
Root cause: crossterm's cursor position CSI query times out (2s) when
multiple concurrent sessions are running or terminal is under load.

Fix:
- Add error::is_cursor_timeout_error() to detect cursor position errors
- Add terminal::get_cursor_position_with_retry() with backoff
- Suppress cursor errors during shutdown in Ui::shutdown()
- Add comprehensive tests for cursor error detection

Fixes session crashes where user sees:
  'cursor position could not be read within a normal duration'
  'Resource temporarily unavailable (os error 35)'

Tested: 337 tests pass (333 existing + 4 new cursor error tests)
Add summarization feature with:
- llm_summarizer: Async LLM-based summarization service
- adaptive_eviction: Importance-based eviction strategies
- metrics: Summarization metrics tracking
- prefilter: Pre-summarization filtering
- Updated compaction config and strategy

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
…p-go

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Restore the workspace members array by listing all crate directories
present on disk, removing invalid glob patterns.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
- Add [bans] section with recommended warnings
- Update GitHub workflow files (trufflehog, stale, labels, release)
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
KooshaPari and others added 24 commits June 26, 2026 03:57
- Cargo.lock, package-lock.json: keep ours
- Cargo.toml, crates/forge_tracker/Cargo.toml: UNION deps (higher versions)
  - posthog-rs: 0.12.0 → 0.14.0 (upstream)
  - gix: 0.84 → 0.85 (upstream)
- provider.json: keep ours (--ours)
- model.rs: UNION AppCommand enum (Subagents, Goal, Loop, Parent, Reparent, Cwd, Sort, Search + ConversationTree)
- ui.rs: keep ours (session-viewer feature)
- Implements stub handler for upstream's conversation-tree command
- Reuses list_conversations() for now; can be enhanced later with tree-specific logic
…contention (P3) (#33)

* feat(forge_dbd): scaffold single-writer daemon to collapse N:1 SQLite contention (P3)

Design + compiling scaffold for forge-dbd: one daemon owns the only R/W
connection to .forge.db; the ~13 forge processes become clients over a Unix
socket, with writes batched into shared transactions (the durable fix for
per-turn blob-rewrite amplification). Reads stay direct (WAL allows concurrent
readers); same .forge.db file, zero data migration; swaps behind the single
ConversationRepositoryImpl::run_with_connection seam in a follow-up.

This commit lands the protocol enums, client/server skeletons (todo!() bodies),
the workspace member, and the design doc. No existing crate behavior changes.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* feat(forge_dbd): async framed I/O + accept loop compiles (P3)

Fixed E0308 type mismatches in server.rs:
- Made timeout()+read_frame() properly await the future
- Added .await to all write_frame() calls (lines 108, 117, 132)
- Removed unused AsyncReadExt/AsyncWriteExt imports

The accept loop, batching writer queue, and oneshot acks now compile cleanly.
Protocol layer (async read/write frame functions) from prior commit.

- Accept loop: listener.accept(), spawn handle_client per connection
- Batch writer: accumulates requests with 15ms timeout, flushes at 100+ size
- Client handler: reads framed requests, enqueues via channel, awaits response

Build: cargo build -p forge_dbd ✓ / cargo test -p forge_dbd ✓

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Phenotype Agent <agent@phenotype.ai>
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…d work (#38)

Added 8 new tests covering:
1. Migration round-trip: verify all migrations apply cleanly on fresh in-memory DB
   - intent_state column exists with TEXT type
   - FTS5 external-content table (conversations_fts) created correctly
   - P4 indexes idx_conversations_intent_pending/verified exist

2. FTS5 external-content refresh and search:
   - Insert 3 test conversations
   - Call refresh_fts_index (rebuild) to populate FTS5 index
   - Verify FTS5 search returns correct results with BM25 ranking

3. IntentState transition guards (legal transitions):
   - Forward path: pending → extracting → extracted → verified → pruned
   - Idempotent transitions (state → same state)
   - Reversions on failure (extracting/extracted/verified → pending)
   - Forward skips (manual override: pending → extracted/verified)

4. IntentState transition guards (illegal transitions):
   - Cannot jump directly to pruned (must go through verified)
   - Pruned is final (no forward transitions from pruned)
   - No backwards skipping (extracting → verified forbidden)

5. Prune conversation gate:
   - Pruning denied when intent_state != 'verified'
   - Pruning succeeds when intent_state = 'verified'
   - Context set to NULL after successful prune

6. FTS5 external-content schema validation:
   - No synchronous FTS triggers remain (P2 removed them)
   - FTS5 uses porter tokenizer for stemming
   - FTS5 indexes title, context, cwd columns

7. Intent state indexing with mixed states:
   - Query idx_conversations_intent_pending (pending or extracting)
   - Query idx_conversations_intent_verified (verified only)
   - Correct counts for each state

8. Memory ID tracking for audit trail:
   - memory_id column stores MemoryPort UUID
   - extracted_at column tracks extraction completion time
   - Both queryable for audit trail and verification

All 8 tests pass. Full cargo test --workspace: 708 passed.
Validates merged work: P1a (checkpointer config), P2a (FTS refresh cadence),
P2b (external-content FTS5), P4 (intent_state schema + prune gate).

Co-authored-by: Phenotype Agent <agent@phenotype.ai>
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…#39)

Classifies forge conversations by first-user-message authorship to identify
AI-subagent sessions. Safe operation by default with dry-run reporting.

Core features:
- Classifies sessions as AI (subagent/bootstrap) or human (user/terminal)
- --apply mode with --tier 1/2 for selective deletion
- --yes flag refuses unless no processes hold database (via lsof)
- Backs up database before any deletion
- Cascading soft-delete for session_tasks, session_attachments
- Preserves terminal-paste sessions and human-authored entries
- Verified refusing against live forge PIDs

Usage:
  cargo run --quiet -- --db-path ~/forge/.forge.db --apply --tier 1 --yes

Co-authored-by: Phenotype Agent <agent@phenotype.ai>
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* feat(forge_repo): transparent zstd compression of context blobs (lossless, dual-read)

- Add zstd = 0.13 to workspace dependencies
- Create codec module: compress() and decompress() with zstd level 3 (fast ~4x on JSON)
- Migration 2026-06-26-000300: add context_zstd BLOB and is_compressed INTEGER columns
- ConversationRecord: dual-path write — compress on write to context_zstd, is_compressed=1
- TryFrom: dual-path read — decompress if is_compressed=1, else fallback to plain context
- Update schema.rs with new columns
- Update upsert_conversation() to write both context_zstd and is_compressed
- Fix query filters: check 'context IS NOT NULL OR is_compressed = 1' for get_all/get_last
- Unit tests: 6 codec tests (round-trip, compression ratio, unicode, invalid data)
- Backward compatible: old uncompressed rows continue to work
- No backfill: new rows compress automatically; existing rows remain uncompressed (backfill is future tool)
- Known limitation: FTS indexes need update for compressed rows (TODO for follow-up)

Test results: 345 passing, codec tests green, build clean, clippy OK

* fix(forge_repo): FTS indexes decompressed context so compressed rows are searchable

PROBLEM: FTS5 external-content mode reads the `context` column by name. For
compressed rows (context=NULL, is_compressed=1), external-content FTS5 silently
misses them because context is NULL. Searches find uncompressed rows but NOT
compressed rows.

ROOT CAUSE: The dual-path read in ConversationRecord decompresses on the fly, but
FTS5's external-content mode reads by column name before app-level decompression,
missing NULL context entirely.

SOLUTION: Revert to CONTENTFUL FTS5 (new migration 2026-06-26-000400) and
populate the index in application code:
- refresh_fts_index() now: (1) CLEARs FTS, (2) SELECTs all conversations,
  (3) for each row, decompresses context_zstd if is_compressed=1, else uses
  plain context, (4) INSERTs the decompressed text into FTS
- This ensures BOTH compressed and uncompressed rows are indexed and searchable

TRADEOFF: CONTENTFUL FTS5 stores a copy of indexed columns in _content table.
However, the base conversations.context is still compressed (zstd), so the primary
space savings remain. FTS's copy is a searchable index, not further compressed.

TEST: New test test_search_finds_compressed_conversations() proves:
- Compressed conversation (context_zstd, is_compressed=1, context=NULL) is inserted
- refresh_fts_index() populates FTS with decompressed context
- FTS search finds the compressed row by its content
- This test FAILS before the fix (search returns empty for compressed rows)
- This test PASSES after the fix (search finds all rows)

VERIFICATION:
✓ cargo build --workspace: clean
✓ cargo test -p forge_repo: 347 passed, 0 failed
✓ cargo clippy -p forge_repo: no new warnings
✓ New test test_search_finds_compressed_conversations: PASS
✓ All FTS-related tests updated and pass

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* fix(forge_repo): clippy linting (doc comment, module name, vec!)

- Remove empty line after doc comment in compression.rs
- Rename inner test module to integration_tests to avoid same-name warning
- Replace vec![] with array literal for test data

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Phenotype Agent <agent@phenotype.ai>
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Freed pages (P4 prune, zstd compression, deletes) return to the OS continuously via incremental_vacuum on the background checkpointer, instead of needing an exclusive-lock full VACUUM.

**Changes:**

1. **pool.rs (SqliteCustomizer)**: Added `PRAGMA auto_vacuum=INCREMENTAL` to on_acquire.
   - For NEW databases: takes effect immediately at creation.
   - For EXISTING .forge.db: pragma is a no-op; requires one forge-vacuum full-VACUUM to convert, after which incremental_vacuum self-maintains.

2. **checkpoint.rs**:
   - After each successful WAL truncate checkpoint, run `PRAGMA incremental_vacuum` if FORGE_INCREMENTAL_VACUUM is enabled (default: yes).
   - Non-fatal errors are logged; the loop continues.
   - Env gate: set FORGE_INCREMENTAL_VACUUM to "0"/"false"/"no"/"off" to disable.

**Testing:** All 340 forge_repo tests pass. cargo build green. cargo clippy clean.

Co-authored-by: Phenotype Agent <agent@phenotype.ai>
Co-authored-by: Claude Haiku 4.5 <noreply@anthropic.com>
…42)

* feat(tooling): forge-context-backfill — batch-compress existing rows

Batch zstd-compresses existing is_compressed=0 conversation rows, matching the
forge_repo codec exactly (level 3). Per-row lossless round-trip verification
before write; batched+resumable+idempotent (skips already-compressed rows).

Safety features:
- Preflight check: refuses if forge procs hold DB (lsof, lists PIDs, never kills)
- Disk space check: refuses if < (db_size + 1GB) free
- Automatic timestamped backup before any write (skippable with --skip-backup)
- Batched transactions (resumable on failure, configurable batch size)
- Per-row lossless verification: decompress and verify == original before write
- Full VACUUM option (--vacuum): reclaims space + converts to incremental auto_vacuum
- Idempotent: re-running skips already-compressed rows

CLI: --db-path, --apply (dry-run default), --yes (required with --apply),
--batch-size, --backup-dir, --skip-backup, --vacuum

Dry-run default shows what would be compressed (~12.5k rows expected),
estimated space savings without writing anything.

Standalone crate (own [workspace] table) to avoid bloating main workspace build.
Matches forge_repo codec compression format byte-for-byte.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* fix(forge-context-backfill): dry-run should work read-only without lsof gate

The dry-run (default, no --apply) is a read-only operation and should not require
forge processes to be idle. It only needs to query the database and report row counts
and estimated compression savings. The lsof gate (SAFETY GATE 1) now applies only to
--apply mode, not dry-run.

Changes:
- Wrapped SAFETY GATES 1-3 (process check, disk space, backup) in 'if args.apply' block
- Added Database::open_readonly() that sets PRAGMA query_only = ON
- Dry-run now opens DB in read-only mode, bypassing all safety gates
- --apply mode still enforces all three safety gates before write operations

Testing:
- Dry-run on 2510 uncompressed rows: prints row counts without any safety gate refusal
- --apply mode: enforces process check, disk space, and backup creation
- Compression ratio verified: 96.7% reduction (2.54 MiB → 84.92 KiB)

---------

Co-authored-by: Phenotype Agent <agent@phenotype.ai>
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…elector + version bump 2.10.0 (#43)

## Changes

### Conv-View Render Improvements (conversation_selector.rs:45→57)
- **Remove fork-added ID prefix**: Deleted `[{short_id}]` from display format (upstream-clean render)
- **Truncate titles to fixed width**: Titles now max 50 chars with `…` ellipsis; prevents line wrapping
- **Fixed-column date alignment**: Date/time_ago right-aligned in 10-char field for stable layout
  - Before: `[550e8400] Very long conversation title that pushes date off screen (2d ago)`
  - After: `Very long conversation title that pus… 2d ago       ` (aligned)

### FTS Search Wiring (conversation_selector.rs:86→106)
- Query parameter now honored: when query is provided, filters conversations by title substring match
- Enables search_conversations() backend to be reachable from UI
- Fallback to all valid conversations if search returns no results

### Sorting Integration (conversation_selector.rs:73→181)
- Added `sort: ConversationSort` parameter to `select_conversation()`
- Selector now renders items in UIState.sort order (updated/created/turns/title/cwd)
- Updated all 6 call sites in ui.rs to pass `self.state.sort`
- Sort is applied AFTER filtering so search results are sorted correctly

### Version Bump
- forge_main Cargo.toml: 0.1.1 → 2.10.0 (signals feature wave beyond fork baseline)
- Aligns with workspace parent version 2.9.9

## Quality
- cargo clippy: clean (0 warnings)
- cargo check: ✓ forge_main compiles
- Tests pass: test_select_conversation_empty_list updated with sort parameter

## Before/After

**Conv-View Selector Header**: `ID       Title                          Updated`
→ `Title                                          Updated   ` (fixed-width title column)

**Visibility**: Backend search_conversations() + ConversationSort now reflected in UI selection flow

Co-authored-by: Phenotype Agent <agent@phenotype.ai>
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…a wiring (#44)

Adds 3 Claude Code-parity commands that agents and users can invoke to manage the session:

- **/clear** — clears the terminal screen (via Console::clear_screen using ANSI escape)
- **/init** — writes an AGENTS.md bootstrap in the workspace root (via console prompt + fs write)
- **/rewind** — rolls back the conversation to the most recent compaction point (via
  rewind_conversation API, which truncates the context JSON back to the last user
  message + surrounding tool calls)

All 3 are registered as AppCommand variants with name(), parse-args patterns, and
on_command match arms. rewind_conversation is wired through the full 6-layer chain:
repo trait → repo impl → service → AgentService → API trait → API impl.

Build: cargo build --bin forge clean in 27.22s. Binary at ~/.local/bin/forge-dev (178MB).

Co-authored-by: Phenotype Agent <agent@phenotype.ai>
…napshot fix

crates
  forge3d          — daemon (UDS JSON-RPC + agent registry + pidfile)
  forge_drift      — DriftDetector (T0-T3) with forward-dated lease
  forge_similarity — SimilarityProvider trait + Tier/ApprovalMode config
  forge_mux        — MuxBridge + tmux + HarnessMuxBridge
  ghostty-kit      — Config parser + IPC client (prior session re-sync)

shell-plugin
  lib/drift.zsh     — New: forge_drift_observe, _override, _subscribe
  forge.plugin.zsh  — Wire drift hooks into precmd/preexec/fig_prezto

workspace
  Cargo.toml       — 5 crates added to members + internal deps
  Cargo.lock       — Resolved

fixes
  forge_repo       — Compile fix (broken dependency ref)
  forge_app snaps  — Accept 4 updated system-prompt snapshots
…eystroke subprocess spawn (#45)

Adds a simple time-based debounce: before calling render_preview(), check if >=150ms has elapsed since the last render. Holding arrow for 2s went from ~60 subprocess spawns to ~2-3. Matches the 250ms event::poll interval.

Co-authored-by: Phenotype Agent <agent@phenotype.ai>
…in (#46)

Two surgical fixes that were blocking ALL PR auto-merges:

1. **cargo-deny.yml** — was using taiki-e/upload-rust-binary-action (wrong action — that uploads release binaries) instead of taiki-e/install-action (which installs Rust tools). Replaced with install-action@v2 with tool: cargo-deny.

2. **trufflehog.yml** — stale SHA pin on actions/setup-go (the SHA was deleted/broken). Updated to explicit @f111f3307d8850f501ac008e886eec1fd1932a34 (# v5) with proper validation.

Both are 1-line changes that fix the root cause of the systemic 'required check CI failing' error that forced every PR to merge with --admin bypass.

Co-authored-by: Phenotype Agent <agent@phenotype.ai>
… fix

Remaining Claude Code parity gaps plus the debounce hotfix:

1. /review — opens a structured code review prompt
2. /test — triggers test creation workflow
3. /think — signals thinking before acting

All 3 are registered as AppCommand variants with name() arms, on_command match arms, and handler stubs.

4. Debounce fix (preview.rs) — prevents per-keystroke subprocess spawn by checking if >=150ms elapsed since last render.

Build: cargo build --bin forge clean. forge-dev installed.
…parent_id

Adds a visual breadcrumb in the conversation selector: subagent sessions
(conversations with a parent_id) now show with a '↳' prefix before their
title, making it immediately visible which sessions are subagents spawned
from a parent. Addresses the 'pollution of session history' complaint.

Also verified: OrchestratorDropGuard already wired at orch.rs:268-272,
preview debounce already at preview.rs:401-410, :search already wired to
FTS5 snippets in 6 layers.
…header

Shows a breadcrumb in both the conversation info panel (info.rs) and the
on_show_current_message chat header (ui.rs) when the current conversation
is a subagent (has a parent_id set). Displays: 'parent_id: <id>'. This
addresses the 'pollution of session history' pain by making it instantly
visible which sessions are subagents spawned from a parent, even when
you're inside the conversation itself.

Combined with the '↳' prefix in the conversation selector (previous
commit), this gives a complete visual hierarchy across both surfaces.
Adds a slash command that manually invokes the FTS5 optimize_fts_index
method to reclaim search index bloat. The optimize method was fully wired
through 6 layers but never called from any UI path — this is the final
'wired but dead' item from the deep audit.

:fts-optimize triggers an immediate FTS5 merge (not incremental). Uses
writeln_info and writeln_help for output. Also adds the AppCommand::FtsOptimize
variant + name() match arm + on_command dispatch + handler.
…okens)

Auth types that hold secret values derived `#[derive(Debug)]`, allowing
plaintext secrets to leak via `{:?}` into logs and the PostHog tracker.

Types fixed:
- `ApiKey` — custom Debug prints `ApiKey(<redacted>)`
- `AccessToken` — custom Debug prints `AccessToken(<redacted>)`
- `RefreshToken` — custom Debug prints `RefreshToken(<redacted>)`
- `AuthorizationCode` — custom Debug prints `AuthorizationCode(<redacted>)`
- `PkceVerifier` — custom Debug prints `PkceVerifier(<redacted>)`
- `State` — custom Debug prints `State(<redacted>)` (holds PKCE verifier in Anthropic flow)
- `OAuthTokenResponse` — hand-written Debug redacts access_token, refresh_token, id_token
- `McpOAuthTokens` — hand-written Debug redacts access_token, refresh_token
- `McpClientRegistration` — hand-written Debug redacts client_secret

Non-secret fields (provider id, expires_at, token_type, scope, client_id) remain
visible in Debug for usability. Display impls are unchanged (already redacted).

Tests added asserting `format!("{:?}", secret_value)` does not contain plaintext
and does contain `<redacted>`.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
)

5-cluster deep audit (W02/W03/W05/W07/W12, 87 findings) of weakest pillars
(means 0.83-1.40) against current main. Root cause: leftover TS-evals fork
scaffolding. Adds phased WBS+DAG (P0 de-fork docs -> P1 gates/stubs/security
-> P2 hardening -> P3 perf/concurrency -> P4 ops -> P5 cross-repo crates) with
agent-effort estimates, plus live-bug known-issues (P0 secret Debug-leak).

Co-authored-by: Phenotype Agent <agent@phenotype.ai>
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Replace leftover 'ForgeCode Evals' TypeScript scaffolding with accurate docs
for the real 33-crate Rust workspace (root cause of W12/W05/W01 audit misscoring).

- README: Rust product description, real crate architecture, cargo/just quick-start
- docs/SSOT.md: kill 'Rust: N/A' + fictional ProviderPort/CsvAdapter; real workspace
- Justfile: cargo-driven recipes (build/test/lint/fmt) replacing npm/eslint
- .gitignore: ignore .credentials.json (local 0o600 credential store)

cargo build exit 0; README/SSOT grep-clean of TS ghosts; just recipes parse.

Co-authored-by: Phenotype Agent <agent@phenotype.ai>
Co-authored-by: ForgeCode <noreply@forgecode.dev>
…#48)

- Delete crates/ghostty-kit entirely: no workspace crate depended on it
  (confirmed via grep on Cargo.toml files and .rs imports). Removed from
  workspace members and workspace.dependencies in root Cargo.toml.
- forge_dbd: add description field marking it WIP and add README.md
  explaining it is part of the SQLite-WAL/FTS epic, not yet wired into
  forge_app. No code deleted — crate preserved for the epic.

Co-authored-by: Phenotype Agent <agent@phenotype.ai>
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
… true no-op (#49)

- forge_repo: MockHttpClient.http_delete in #[cfg(test)] block now
  calls self.client.delete(url).send() instead of unimplemented!()
- forge_domain: NoopIntentExtractor.extract_intent returns
  Ok(ExtractedIntent { episodic: Null, identity: Null, project_knowledge: Null })
  and verify_extraction returns Ok(false) instead of propagating errors

Co-authored-by: Phenotype Agent <agent@phenotype.ai>
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
…accept, telemetry spawns)

- forge_api: FTS refresh loop now accepts a CancellationToken and select!s
  on shutdown so it exits immediately instead of waiting for the next tick.
  BackgroundTasks owns the JoinHandle and is stored in ForgeAPI; tasks are
  aborted on drop (no fire-and-forget orphans).
- forge3d: serve() loop select!s on a CancellationToken; per-connection
  spawns are tracked in a JoinSet (not fire-and-forget) and aborted cleanly
  on shutdown.
- Documented the task-lifecycle convention (P2.4) in both crates.
- Added serve_exits_on_cancellation test (forge3d); 28/28 tests green.
- cargo build (full workspace) clean; cargo clippy -p forge3d -p forge_api clean.

Co-Authored-By: ForgeCode <noreply@forgecode.dev>
@CLAassistant

Copy link
Copy Markdown

CLA assistant check
Thank you for your submission! We really appreciate it. Like many open source projects, we ask that you all sign our Contributor License Agreement before we can accept your contribution.
0 out of 3 committers have signed the CLA.

❌ Phenotype Agent
❌ KooshaPari
❌ Dmouse92


Phenotype Agent seems not to be a GitHub user. You need a GitHub account to be able to sign the CLA. If you have already a GitHub account, please add the email address used for this commit to your account.
You have signed the CLA already but the status is still pending? Let us recheck it.

@github-actions github-actions Bot added type: fix Iterations on existing features or infrastructure. type: provider Updates provider.json configuration. labels Jun 28, 2026
@KooshaPari

Copy link
Copy Markdown
Author

Wrong base (upstream); intra-fork for KooshaPari/forgecode. Reopening there.

@KooshaPari KooshaPari closed this Jun 28, 2026
@KooshaPari KooshaPari deleted the fix/p2-async-lifecycle-20260628 branch June 28, 2026 23:54
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

type: fix Iterations on existing features or infrastructure. type: provider Updates provider.json configuration.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants