Skip to content

merge main into this branch - #18

Merged
moeller-projects merged 15 commits into
dependabot/nuget/Microsoft.Agents.AI-1.22.0from
main
Sep 26, 2026
Merged

moeller-projects merged 15 commits into
dependabot/nuget/Microsoft.Agents.AI-1.22.0from
main

Conversation

@moeller-projects

Copy link
Copy Markdown
Owner

No description provided.

Lukas Möller and others added 15 commits September 25, 2026 21:13
…e routing bypass)

Implements implementation-plans/P1-high/P1-13-api-key-auth-case-bypass.md

- ApiKeyEndpointFilter on the /reviews route group replaces middleware path matching
- Deleted ApiKeyAuthenticationMiddleware (ordinal StartsWithSegments gate was
  bypassable: POST /REVIEWS routed case-insensitively but skipped auth)
- Endpoints mapped under MapGroup("/reviews") so enforcement cannot diverge
  from routing; segment-exact matching preserved (/reviewsx -> 404)
- Tests: ApiKeyAuthTests case-variant paths (/REVIEWS, /ReViews) -> 401,
  near-miss paths -> 404, filter unit tests for fail-closed/dev-opt-out branches

Refs: P1-13
Implements implementation-plans/P1-high/P1-11-status-aware-dedupe-regression-reopen.md

- ReviewContext gains ResolvedKeys (stage-1 threads with Fixed/Closed status);
  populated at the top of ExecuteReasoningStage — no extra ADO fetch
- ReviewCollector gains MarkRegressed/RegressedKeys; ReviewTools.RecordFinding and
  the homoglyph path accept a verbatim re-submission of a resolved key as a
  regression finding (IsRegression), exactly once per regression; Active threads
  keep the existing MarkRedetected silence
- ThreadTriage.Plan emits Reopen + "Regressed in <sha>" note for Fixed/Closed
  threads whose key regressed; the reopen outranks the pending-reply manual flag
- PublishFindingsStage suppresses the duplicate new thread for a reopened
  regression and re-stamps the reopened thread id for persistence
- CommentFormatter marks regressions with a "⚠️ regressed:" heading prefix
- Tests: ReviewTools/ThreadTriage/CommentFormatter/ExecuteReasoningStage units,
  stage-level triage+publish, end-to-end F1 scenario in ServiceTests

Refs: P1-11
…stale shells

Implements implementation-plans/P1-high/P1-12-shell-aware-backoff-and-reaper.md
- FailureBackoff.BlockedUntil ignores runs with CompletedAt=null (shells): a
  crash between BeginRun and PersistRun no longer reads as a failure streak
- ShellReaperService (hosted, one-shot): finalizes stale shells (Success=false,
  CompletedAt=now) preserving finding rows via the SaveRunAsync upsert, so
  discovery backoff applies a bounded window from reaper time
- Reap extracted as internal ReapOnceAsync: .NET 10 BackgroundService.StartAsync
  schedules ExecuteAsync via Task.Run(stoppingCts.Token); an immediate StopAsync
  cancels the token before the delegate runs and StopAsync suppresses the
  cancellation, silently dropping the reap
- DiscoveryService: in-flight claim check ordered before backoff attribution;
  in-flight PRs report "already in flight", never "head failing", and
  head-failing-backoff counts only real failures
- ReviewWorker PersistFailureAsync uses CancellationToken.None (never the
  cancelled stoppingToken) with a linked 5s timeout
- IFindingStore.GetStaleShellsAsync + SqliteFindingStore query
  (CompletedAt == null, StartedAt < cutoff), FakeFindingStore support
- Tests: DomainTests shell backoff, SqliteFindingStoreTests.GetStaleShells,
  ShellReaperServiceTests (finalize + no-op), DiscoveryServiceTests in-flight
  claim precedence, ReviewWorkerTests host-cancel mid-run
Refs: P1-12
…d files

Implements implementation-plans/P1-high/P1-14-quoted-diff-paths.md
- New DiffPathParser: decodes git C-style-quoted path tokens (octal \NNN,
  escaped \" \\ \t \n \r \a \b \f \v, UTF-8 reassembly); bare fast path with
  no allocations; malformed input reports false instead of throwing
- DiffIndex: diff --git / +++ / Binary files / rename to headers all route
  through the shared parser, fixing both the quoted-path blind spot (a file
  like payloäd.cs previously vanished from the diff, homoglyph scan, and
  scope guard) and the " b/" substring mis-split; a +++ header that fails to
  decode no longer registers the file, so the manifest match below fails
- HomoglyphDiffAnalyzer: same parser; header/hunk ordering mirrors
  DiffIndex (in-hunk content consumed before +++ headers) so an added line
  starting with "+++ b/" is scanned as content, never a file switch
- PrepareRepositoryStage: a manifest file with no diff entry now throws
  (fail closed) instead of warn-and-drop; budget-excluded files logged at
  Debug; binary/mode-only/rename/truncated whitelist unchanged
- Tests: DiffPathParser table (quoting rules, malformed headers),
  DiffIndex quoted-path end-to-end and fail-closed, analyzer ascii-vs-quoted
  parity + ordering, stage-level quoted/binary/manifest-orphan behavior
Refs: P1-14
Implements implementation-plans/P1-high/P1-15-reporeadtools-symlink-deny-bypass.md
- Resolve now re-applies the deny list to the symlink-resolved repo-relative
  path and refuses any read that traverses a symlink inside the checkout: a
  committed symlink to a contained-but-denied file (.env, appsettings.*.json,
  *.pem) can no longer launder the path past the deny list into the LLM context
- PathSafety.ResolveReal gains an out linksTraversed (component-level link
  count, relative to the resolved root so environment symlinks above the
  checkout do not false-positive)
- Grep re-denies per file about to be opened (stat'ing reserved for those
  files) and skips symlink-traversed entries; List hides them; both reuse one
  IsResolvedDenied helper. Denial messages stay non-oracular (identical
  "access denied" template, no symlink/escape hint)
- Deviation from plan text: helper named ResolveReal(path, out int) rather
  than TryResolveReal — resolution has no failure mode a Try-pattern can
  report honestly; Grep deny counts not logged at Debug — RepoReadTools has
  no logger dependency
- Tests: symlink to denied target (Read + Grep), symlink to allowed file
  (still refused, intentional), deep chain a -> b -> .env, symlinked dir as
  Grep path, List hiding, non-oracular message equality
Refs: P1-15
…ingle-row reads

Implements implementation-plans/P2-performance-ops/P2-26-finding-store-retention.md
- IFindingStore.PruneAsync: deletes runs older than the retention cutoff in one
  immediate SQLite transaction (findings first, then runs with RETURNING), always
  keeping the last MinRunsPerPr runs per PR and never the latest completed run
  (dedupe continuity) — the carry-forward design previously grew finding rows
  quadratically in run count per PR
- GetLastCompletedRunAsync: single-row id lookup via rowid DESC LIMIT 1 raw SQL
  (per-PR runs never overlap, so rowid order is completion order) plus one
  findings load — no longer materializes every completed run
- GetRecentRunsAsync: ORDER BY rowid DESC LIMIT in SQL; findings load only for
  the selected runs; failure-backoff streak semantics unchanged (per-PR
  sequential runs make rowid order identical to the old StartedAt order)
- Runs composite index extended with CompletedAt for the prunable set
- RetentionOptions (ReviewForge:Retention:Days=30 / MinRunsPerPr=5), validated
  fail-fast at startup; DiscoveryService prunes at the sweep tail, throttled to
  at most once per hour via TimeProvider
- Tests: prune keeps min-runs/latest-completed and cascades findings, prune
  exemption for an old completed run behind newer shells, 50-run latest
  correctness, 300x40 bounded-read scale, sweep prune call + hourly throttle
Refs: P2-26
Implements implementation-plans/P2-performance-ops/P2-27-homoglyph-scan-allocations.md
- Options.Default: shared immutable defaults (ImmutableHashSet) resolved once per
  scan instead of a record + two HashSets allocated per line
- Vectorized all-ASCII fast path in ScanLine skips tokenization for lines that can
  never flag; analyzer skips Preprocess for pure-ASCII added lines
- Span-based TokenizeRanges with inline ASCII tracking: substrings materialized only
  for tokens that flag; script distinctness via a PopCount bitmask (no per-token
  ToArray/Distinct)
- Analyzer iterates diff.AsSpan().EnumerateLines (no Split array + 30k line strings)
- NFKC retained per token: ScanLine is public and takes unnormalized input — fullwidth
  "var" only flags because NFKC maps it, so plan step 3.4's removal would regress
  detection (deviation, verified by fullwidth parity case)
- Tests: parity theory vs verbatim pre-P2-27 reference scanner (16 default-options
  corpus cases + custom-options cases), Options.Default equivalence, 10k-line
  GC allocation budget (<2 MB); also fix two pre-existing xUnit analyzer warnings
  (xUnit2000 arg order, xUnit2031 Where-before-Single) in test files from earlier waves
Refs: P2-27
…tion, non-backtracking regex

Implements implementation-plans/P2-performance-ops/P2-28-grep-aggregate-budget.md
- Grep aborts at an aggregate time (10 s) or line (200k) budget with a
  "…[truncated: budget-time|budget-lines]" marker; the tool description teaches the
  model to narrow and retry (string result shape kept — trailer markers per existing
  "…[match cap reached]" convention)
- CancellationToken parameter on Grep, checked per file and per line; MEAI binds it
  from the agent loop's token (verified by an AIFunctionFactory invoke test)
- Patterns compile with RegexOptions.NonBacktracking (linear-time, no catastrophic
  backtracking); constructs it rejects (lookarounds/backreferences) fall back to the
  interpreted engine with the 2 s per-line timeout, marked "…[pattern-fallback]"
- Enumeration yields FileInfo so the size cap reuses the enumeration stat instead of
  re-statting; P1-15 deny/containment checks unchanged
- Budgets configurable via the new validated RepoReadTools:GrepMaxMs/GrepMaxLines
  section (RepoReadToolsOptions), threaded through ReviewPipelineFactory -> AgentOptions
  -> RepoReadTools ctor (positive-only, ctor + DataAnnotations validated)
- Tests: line/time budget truncation, mid-scan cancellation, MEAI token binding,
  lookaround fallback + catastrophic-pattern completion, ctor budget validation
Refs: P2-28
…static

Implements implementation-plans/P2-performance-ops/P2-31-run-log-buffering.md
- RunLogWriter: AutoFlush=false with a 64 KB buffer; a provider-level 2 s timer flushes
  open writers and CloseRun/Dispose flush+close — agent-loop Debug lines no longer
  serialize + flush synchronously per entry (default MinLevel is now Information;
  Debug stays opt-in via RunLogs:MinLevel)
- Bounded closed-run id set (cap 10k): post-close scoped writes are dropped instead of
  re-creating a leaked writer; _Disposed guard drops post-dispose writes
- Disposal races are swallowed (ObjectDisposedException/IOException) at the provider
  and writer, including StreamWriter.Dispose's re-flush — no logging exception can
  escape into pipeline code; writer/stream seams make the failure paths testable
- RunLogFileProvider.Current static deleted: ReviewWorker now depends on the injected
  IRunLogLifecycle; DI registers the provider itself when enabled, a no-op otherwise
- Log dir 0700 / files 0600 on Unix (CodexCredential pattern); P3-x permissions half
  folded in here
- Tests: 1000-entry completeness after CloseRun, post-close drop (replaces the old
  reopen-append test per the new behavior), concurrent write-vs-dispose smoke,
  deterministic disposed-writer swallow via the factory seam, closed-set bounding,
  Unix mode assertions (skipped on Windows), default-level pin, IO-failure flush/
  dispose via a throwing stream; worker test sites use NoopRunLogLifecycle
Refs: P2-31
Implements implementation-plans/P2-performance-ops/P2-32-checkout-size-refresh.md
- RepoCheckoutPool no longer walks the full checkout tree on every AcquireAsync reuse:
  the reuse fast path drops RefreshCachedSize entirely (~200k syscalls saved per run on
  a 100k-file checkout); the clone path keeps its one-time materialization measurement
- Size cache entries carry a RefreshedAt stamp; the eviction sweep re-measures entries
  older than one hour (fake-clock test proves fresh stamps are trusted and stale ones
  re-walked before budget enforcement)
- Pool takes an optional TimeProvider so stamps and the sweep clock never mix; a failed
  re-measure now keeps the stale value instead of pretending the checkout is empty
- Telemetry gauge reads CheckoutDirectoryCount only — no behavioral change
- Tests: CountingFs proves acquire-materialize walks once and reuse walks zero extra;
  FakeTimeProvider test proves hour-stale refresh triggers budget eviction
Refs: P2-32
Implements implementation-plans/P2-performance-ops/P2-29-prompt-text-sanitization.md
- New PromptText.Clean = StripDelimiters(TextSanitizer.Sanitize(x)) — one trust
  boundary for every PR-author-controlled string in the prompt: title, description,
  work-item fields, thread replies, changed-file list, and the shrunk diff body;
  ReviewTools.ReadContext applies it to staged context payloads too
- Invisible/bidi/tag-block Unicode (e.g. U+E0049.. "IGNORE") no longer reaches the
  model from any PR-controlled field, closing the gap where detectors saw the
  sanitized view but the model saw raw text
- Strip-only, no NFKC: legitimate non-Latin source (Cyrillic comments, fullwidth
  identifiers) passes through byte-faithful for anchoring; detectors keep applying
  PipelineText.Preprocess themselves
- PromptBuilder.Sanitize renamed to StripDelimiters to end the two-"Sanitize"
  ambiguity; structural enrichment (tool-supplied) keeps delimiter-strip only
- DiffExclusions.IsExcluded reuses RepoPath.Normalize instead of duplicating it
- Tests: tag-block instruction absent from the built prompt (no TextSanitizer-
  forbidden char survives anywhere), bidi override stripped with visible text
  preserved, non-Latin diff content not normalized, full-prompt stable snapshot;
  P1-5 delimiter-forgery suite stays green
Refs: P2-29
Implements implementation-plans/P2-performance-ops/P2-30-homoglyph-whole-token-rule.md
- Confusables expanded from Unicode 16.0.0 confusables.txt (Cyrillic/Greek/
  Armenian Latin-lookalikes + visual Arabic-Indic digits); existing repo
  entries kept where confusables.txt's prototype differs
- New ScanLine branch: skeleton fully ASCII but different from the token ->
  'whole-token lookalike' (medium); keyword skeletons still claim High first
- Analyzer maps three rule ids incl. homoglyph/whole-token-lookalike
- security.json gains the whole-token rule entry
- Parity corpus cases the rule intentionally reclassifies (ΑΤΜ, var, аbc
  both-scripts, single-char а) asserted explicitly against the new rule
- Tests: HomoglyphDetectorTests whole-token cases, analyzer ruleId/severity,
  rulebook entry; allocation budget unchanged
Refs: P2-30
Implements implementation-plans/P2-performance-ops/P2-33-endpoint-submit-backoff-key.md
- FailureBackoff.BlockedUntil: a failure record with empty HeadSha is
  head-agnostic — it counts toward the streak for any requested head and
  never breaks one; headed mismatches still break (discovery unchanged)
- Endpoint submit path is not a backoff consumer (claims-only) — behavior
  pinned via the discovery sweep integration test
- Tests: FailureBackoffTests head-less streak/mixed-history cases,
  DiscoveryServiceTests sweep skips head whose fetch failures are headless
Refs: P2-33
@moeller-projects
moeller-projects merged commit 0d2a3c3 into dependabot/nuget/Microsoft.Agents.AI-1.22.0 Sep 26, 2026
4 of 13 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant