Skip to content

[AI-1528] Trust-by-default borrowed-review advertisement - #376

Merged
realtonyyoung merged 2 commits into
mainfrom
tonyyoung/ai-1528-trust-by-default
Jul 28, 2026
Merged

realtonyyoung merged 2 commits into
mainfrom
tonyyoung/ai-1528-trust-by-default

Conversation

@realtonyyoung

Copy link
Copy Markdown
Collaborator

Problem

A live spec-review and a follow-up code-review, both with an explicitly selected Cursor reviewer and default workspace behavior, reviewed the stale committed base instead of the requester's working tree. Untracked and tracked-but-uncommitted changes were simply absent.

The root cause is not a snapshot defect — the snapshot path never ran. CursorBorrowedReviewCertification.TryCertify pinned the exact build 2026.07.20-8cc9c0b (version-directory name + launcher SHA-256 + whole-bundle digest). Cursor auto-updated on 2026-07-23, so TryCertify returned null, ComputeUnattendedVendorCapabilities advertised BorrowedReviewSupported = false, and the server correctly resolved workspace_mode = fallback from the capability it was told about.

Nothing reported any of this. The daemon's only Cursor diagnostic covers a missing binary, so an installed, working, uncertified one is silent; and on fallback the loud notice goes into the reviewer's prompt, not to the requester. Exact-pinning a whole-bundle digest against a binary that auto-updates guarantees recurrence — the certification shipped 07-20 and was dead by 07-23.

Change

Trust-by-default. A new version of a coding harness is assumed to work. Borrowed-review capability is now a property of the vendor's factory alone:

var borrowedSupported = factory.SupportsBorrowedReviewFlow;

The two matching pre-spawn throws go with it, or advertisement and launch disagree and a borrowed launch hard-fails instead of degrading:

  • the borrowed-snapshot binary path becomes the plain descriptor.ResolveBinaryPath(config) every other vendor uses;
  • the cursor_borrowed_artifact_not_certified guard is deleted.

The sibling borrowed_snapshot_containment_mismatch check stays — it validates the descriptor's declared containment against the launch context, which is a code-level invariant, not a version fact.

CursorBorrowedReviewCertification → CursorBorrowedReviewValidation, TryCertify → TryMatchValidatedBuild. It keeps its digests and implementation but loses its authority: a record of the last build the maintainer probe suite validated, gating nothing, where a non-match is the expected steady state rather than an error. Its only production caller is now that manual workflow. CursorLauncherPolicyVersion and the reviewer_certification_changed launch rejection are a different mechanism (daemon launcher policy vs. server certification revision) and are untouched.

One Information line per unattended vendor at daemon startup naming the probed CliVersion (or the literal unknown). No new wire field — UnattendedVendorCapability.CliVersion already exists and is already populated. The wording marks it as a startup observation that goes stale on a vendor update, because it genuinely is: the launch path resolves the binary at spawn time, so a reviewer launched after an auto-update runs a different build than the one logged. That limitation is stated rather than hidden.

No automated drift detection of any kind — deliberately. No marker comparison at startup (that would be exactly the version-drift detection this rejects, and would hand the demoted record a production caller immediately). No per-launch version probe or binary-path logging (ResolveBinaryPath is just cfg.CursorPath, normally the bare token cursor-agent; making it meaningful needs final-symlink inspection added specifically to reveal drift, and would still be racy). A defective vendor release is handled by a human report and a corrected record. That trade is weaker for containment than for reads, and the design says so openly rather than implying the probe suite still guards it.

⚠️ Platform expansion — a capability change, not a refactor

TryCertify also returned null on non-macOS and non-arm64, so removing the gate removes those preconditions. Cursor borrowed review is now advertised on every OS and architecture where cursor-agent is installed — not only macOS arm64, but Linux, Windows, and macOS x64 too.

This is accepted deliberately. No genuine platform dependency exists to encode: WorktreeManager.CreateBorrowedSnapshotAsync / SyncFromSourceAsync are platform-neutral git plus managed file operations whose branches already handle Windows explicitly; SafeUnixPath is a certification concern (vendor bundle permissions), not a snapshot one; and neither AcpHostedAgentRuntimeFactory nor the Cursor descriptor carries any other OS/arch gate. Reintroducing a platform gate would be a certification remnant in a different hat.

Coverage for that claim, recorded in DaemonRunnerCursorAvailabilityTests and in the CI matrix comment:

Platform Coverage
linux-x64 Asserted, executed on CI ubuntu-latest
windows-x64 Asserted, executed on CI windows-latest
macos-arm64 Asserted, executed on maintainer machines (this PR's suite runs)
macos-x64 Intentionally untested — no runner in this repo's matrix, and adding a macOS leg is not viable: the unit suite has a known cluster of macOS-local temp-path/parallel-load flakes that would make it permanently red

A Linux-only assertion would have been insufficient, so the residual macos-x64 risk is instead bounded by a host-independent source guard: neither the advertisement path (DaemonRunner.cs) nor the launch path (AcpHostedAgentRuntimeFactory.cs) may reference the validated-build record. A re-introduced gate fails that guard from any host, including for the leg no runner executes — which a behavioral assertion can never do. The guard ships with its own self-test.

Tests

  • Capability: a Cursor build not matching the record is still advertised BorrowedReviewSupported = true with independent-snapshot (the direct regression test — it replaces one that pinned the opposite rule and passed on main); a factory with SupportsBorrowedReviewFlow = false is still not advertised (the gate that remains, asserted on Cursor so a blanket "always true" would be caught); the advertisement mirrors the factory flag across a vendor matrix.
  • Launch: a borrowed Cursor launch on a non-matching build spawns rather than throwing; the borrowed-snapshot binary is the plainly configured path; borrowed_snapshot_containment_mismatch still throws before spawn on a genuine mismatch.
  • Logging: one line per vendor naming the probed version; a null/blank probe renders unknown and the vendor is still advertised borrowed-capable; the wording marks it a startup observation. Two negative tests, and they are load-bearing: the startup line performs no marker-agreement evaluation, and a launch logs no vendor version and no binary path — a test demanding either would drag the rejected behavior straight back in.
  • Snapshot integration (the real regression net). On the evidence above this path had never executed in production, so its correctness was unevidenced. The new orchestrator test uses two distinct checkouts — RepoPath = daemon-registered decoy, BorrowCwd = divergent requester — because AgentOrchestrator snapshots BorrowCwd/CanonicalGitRoot independently of RepoPath, so a single-directory test satisfies every content assertion while passing just as happily against the stale-base behavior. The load-bearing assertion is WorktreeInfo.SourceRepo == requester git root; verified by mutation (repointing BorrowCwd at the decoy fails at exactly that assertion). It further asserts branch-only commit / tracked-modified content / untracked file present, .gitignored file and decoy content absent, snapshot root outside the source repo, reviewer-mutation isolation, per-round refresh picking up requester changes, and reviewer-only files and .git metadata disappearing from the refreshed snapshot (the weaker "didn't reach the requester" form passes against a refresh that accumulates droppings; the droppings are asserted to exist first, so the disappearance is not vacuous).
    SnapshotTree is deliberately not reused for the byte-comparison — despite its comments it records entry names only and would miss in-place corruption. The baseline is per-path SHA-256 plus unix mode, re-checked at all four points (after creation, after reviewer mutation, after refresh, after stop), with .git excluded (read-only git plumbing legitimately refreshes index stat metadata) and git integrity covered separately by HEAD sha + porcelain status.

Results

Suite Baseline (origin/main) This branch
Tests.Unit (macOS arm64) 4006 total, 42 failed 4028 total, 42 failed
Tests.Integration — 144 total, 0 failed

The 42 failures are byte-identical to the origin/main baseline (diff of the sorted failure lists is empty) — the documented pre-existing macOS /tmp-symlink / parallel-load flakes in the config-toml, MCP-registration and uninstall tests. +22 new tests, all green.

Marker validation finding

cursor-agent --version on the installed macOS build prints exactly the version-directory form (2026.07.23-e383d2b), so the probed CliVersion and the marker's Version constant agree today. They remain independent facts and nothing in production compares them; this is recorded in the marker's doc comment.

Outstanding (requires a live authenticated Cursor — not available in this environment)

  1. Marker refresh / maintainer probe suite run against the installed 2026.07.23-e383d2b. The embedded digests are deliberately left at 2026.07.20-8cc9c0b: recomputing the hashes without running the probes would record a validation that never happened. Under trust-by-default a stale marker gates nothing, so this does not block the change — but it is a real outstanding item, not a skipped one.
  2. Live re-run of the AI-1528 scenario, capturing all five gate observations (workspace_mode=borrowed; snapshot under {WorktreeRoot}/borrowed-snapshots/ rather than an owned agent-{guid}; three sentinels in round 1; refreshed contents in round 2; write isolation proven positively — a successful mutation under the snapshot root plus a content-hash baseline showing the requester byte-identical). The executable sequence is one flow, two rounds on the same run — not two aliases, since SubmitReviewRoundRequest carries no kind and a follow-up resolves run.DefinitionId. Items 1/2/4/5 exist for the sibling Copilot issue's rollout gate; confirming item 3 alone satisfies this issue but leaves that one blocked.
  3. docs/HOW_IT_WORKS.md's Copilot/Cursor items live in kcap-server and are corrected there, alongside the submodule bump after this merges.

Knock-on

The sibling Copilot regression's design is built on the opposite rule and cannot ship unamended: its CopilotBorrowedReviewCertification + record table, the pre-spawn TryCertify revalidation and its identity cache, the "binary swapped after advertisement" test matrix, the certification-lag UX, and the additive-installed-version-field claim (CliVersion already exists) all collapse. Snapshot routing, the fail-closed start, the context-only contract, and the copilot + native-tool-clamp classification arm survive — that arm keys on a vendor-descriptor fact, not a certification fact.

Compatibility

No new events, no projection bump, no new error codes, no flow-response changes, and no new field on the daemon's unattended-capability advertisement. New daemon + current server is fixed with no server change (the server already accepts independent-snapshot and resolves borrow from the advertised capability); new daemon + old server is fixed identically since only the advertised value differs. The change is the removal of a gate plus one startup log line per unattended vendor.

Design: docs/superpowers/specs/2026-07-27-ai1528-trust-by-default-borrowed-review-design.md (kcap-server).

Borrowed-review capability was gated on the installed cursor-agent matching
an exact-build certification record. Cursor auto-updates, so the record went
stale within days: the daemon then advertised BorrowedReviewSupported=false,
the server resolved workspace_mode=fallback, and reviewers read a stale
committed base instead of the requester's working tree — silently, with no
warning in the daemon log and no notice to the requester.

Capability is now a property of the vendor's factory alone. The two matching
pre-spawn throws go with it (advertisement and launch must agree); the sibling
borrowed_snapshot_containment_mismatch check stays, since it validates a
declared-containment invariant rather than a version fact.

The certification record is demoted to CursorBorrowedReviewValidation /
TryMatchValidatedBuild — a record of the last build the maintainer probe suite
validated, gating nothing, with a non-match the expected steady state.

One Information line per unattended vendor at daemon startup names the probed
CliVersion (or the literal `unknown`), worded as a startup observation that
goes stale on a vendor update. It deliberately does not evaluate agreement
with the validation record, and nothing equivalent is logged per launch.

Removing the gate also removes its macOS/arm64 preconditions, so borrowed
review is now advertised on every platform where the vendor CLI is installed.
That expansion is deliberate — the snapshot path is platform-neutral.

Design: docs/superpowers/specs/2026-07-27-ai1528-trust-by-default-borrowed-review-design.md
Adds the two-checkout orchestrator integration test the borrowed-snapshot path
never had. It launches with RepoPath = a daemon-registered decoy checkout and
BorrowCwd = a divergent requester checkout, so the load-bearing assertion is
that WorktreeInfo.SourceRepo is the REQUESTER's git root — a single-directory
test satisfies every content assertion while passing just as happily against
the stale-base behavior it exists to prevent.

Also asserts: branch-only commit, tracked-modified content and non-ignored
untracked file present; .gitignore'd file and decoy content absent; snapshot
root outside the source repo; reviewer mutation isolated; per-round refresh
picks up requester changes AND drops reviewer-only files and git metadata.

The "byte-unchanged" claim uses a real content-hash baseline (per-path SHA-256
plus unix mode) re-checked after snapshot creation, reviewer mutation, refresh
and stop, plus HEAD sha + porcelain status for git state. SnapshotTree is not
reused: it records entry names only, so it would miss in-place corruption.

Docs: state the trust-by-default posture in CLAUDE.md and correct the README's
"certified borrowed-context strategy" wording. CI matrix records why macOS is
absent and how that platform is covered instead.
@linear-code

linear-code Bot commented Jul 28, 2026

Copy link
Copy Markdown

AI-1528

@qodo-code-review

Copy link
Copy Markdown

PR Summary by Qodo

Trust-by-default Cursor borrowed-review capability and snapshot launch

🐞 Bug fix 🧪 Tests 📝 Documentation ⚙️ Configuration changes 🕐 40+ Minutes

Grey Divider

AI Description

• Advertise borrowed-review support based on vendor factory, not Cursor build certification.
• Always spawn the configured Cursor binary for borrowed snapshots; keep containment invariants.
• Log probed unattended vendor CLI versions at daemon startup and add regression tests.
Diagram

graph TD
  A["DaemonRunner"] --> B["Compute vendor caps"] --> C["UnattendedVendorCapability"] --> D["Server workspace mode"]
  B --> E["Startup CLI version log"]
  F["AcpHostedAgentRuntimeFactory"] --> G["Borrowed snapshot worktree"] --> H["Spawn vendor CLI"]
  I["CursorBorrowedReviewValidation"] --> J["Maintainer probe workflow"]
Loading
High-Level Assessment

The following are alternative approaches to this PR:

1. Semver-range validation gate
  • ➕ Avoids brittle exact-digest pinning while still blocking known-bad major versions
  • ➕ Could retain an automated safety valve if a vendor release is truly incompatible
  • ➖ Still vulnerable to silent capability withdrawal on auto-update if ranges aren’t updated quickly
  • ➖ Requires defining/maintaining an authoritative version scheme that vendors may not provide consistently
2. Keep exact-build gate but surface requester-visible errors
  • ➕ Preserves strict safety policy for borrowed review on only known-good builds
  • ➕ Makes failures explicit instead of silent fallback
  • ➖ Still causes frequent breakage under auto-update; users will be forced into constant restarts/updates
  • ➖ Adds UX and protocol work to ensure the requester (not only the reviewer) sees the degradation
3. Per-launch drift telemetry (log resolved binary + version each launch)
  • ➕ Improves observability and makes post-update drift obvious without changing capability semantics
  • ➕ Can support targeted incident response
  • ➖ Adds noise and potential privacy/sensitivity concerns in logs
  • ➖ Still doesn’t prevent silent fallback unless coupled with a gate; adds complexity the design explicitly rejects

Recommendation: The PR’s trust-by-default approach is the best fit given vendor auto-update behavior and the high cost of silent fallback to stale committed bases. Advertising borrowed-review solely from the factory and aligning the launch path to spawn the configured binary removes a brittle operational dependency, while keeping the containment mismatch guard preserves the real safety invariant (declared containment must match launch context). The added startup identity logging and regression tests provide the right level of observability and guardrails without reintroducing automated drift gating.

Files changed (10) +708 / -29

Bug fix (2) +52 / -17
DaemonRunner.csMake borrowed-review capability factory-driven; log vendor CLI identities at startup +38/-6

Make borrowed-review capability factory-driven; log vendor CLI identities at startup

• Removes Cursor exact-build gating from unattended vendor capability computation so BorrowedReviewSupported mirrors 'factory.SupportsBorrowedReviewFlow'. Adds startup logging that records each unattended vendor’s probed CLI version (or 'unknown') without comparing against any validation record.

src/Capacitor.Cli.Daemon/DaemonRunner.cs

AcpHostedAgentRuntimeFactory.csSpawn configured vendor binary for borrowed snapshots; keep containment invariant check +14/-11

Spawn configured vendor binary for borrowed snapshots; keep containment invariant check

• Stops resolving Cursor borrowed-snapshot launches through a validated-build artifact and always spawns the configured binary path. Removes the pre-spawn 'cursor_borrowed_artifact_not_certified' throw while retaining the containment mismatch guard for snapshot-materialized review flows.

src/Capacitor.Cli.Daemon/Services/AcpHostedAgentRuntimeFactory.cs

Refactor (1) +30 / -5
CursorBorrowedReviewValidation.csDemote Cursor build certification to non-gating validation record +30/-5

Demote Cursor build certification to non-gating validation record

• Renames/repurposes the previous certification mechanism into a maintainer-only validation record ('TryMatchValidatedBuild') that returns null on mismatch and gates nothing in production. Expands documentation to clarify version form, expected drift, and non-authoritative status.

src/Capacitor.Cli.Daemon/Services/CursorBorrowedReviewValidation.cs

Tests (4) +607 / -5
AgentOrchestratorBorrowLaunchTests.csAdd two-checkout regression test for borrowed snapshot source and isolation +209/-0

Add two-checkout regression test for borrowed snapshot source and isolation

• Introduces a comprehensive test ensuring borrowed snapshots are derived from the requester checkout (not the registered repo path) and include dirty/untracked-but-not-ignored content. Verifies reviewer mutations stay inside the snapshot, refresh cleans reviewer droppings, and stopping deletes the snapshot without altering requester git state.

test/Capacitor.Cli.Tests.Unit/AgentOrchestratorBorrowLaunchTests.cs

DaemonRunnerCursorAvailabilityTests.csReplace build-certification gate tests with trust-by-default advertisement + logging tests +257/-5

Replace build-certification gate tests with trust-by-default advertisement + logging tests

• Updates test doubles to carry a borrowed containment token and adds regression tests proving Cursor borrowed support is advertised even when the binary is missing/unprobeable. Adds a source-level guard preventing reintroduction of build-identity gating in advertisement/launch code, plus assertions for the new startup CLI version log wording and behavior.

test/Capacitor.Cli.Tests.Unit/Daemon/DaemonRunnerCursorAvailabilityTests.cs

AcpHostedAgentRuntimeFactoryTests.csEnsure borrowed snapshot Cursor launches spawn on non-matching builds +109/-0

Ensure borrowed snapshot Cursor launches spawn on non-matching builds

• Adds tests asserting borrowed-snapshot Cursor launches use the configured binary path and spawn without 'cursor_borrowed_artifact_not_certified'. Confirms containment mismatch still fails pre-spawn and verifies no per-launch logging of vendor version or binary path is introduced.

test/Capacitor.Cli.Tests.Unit/Services/AcpHostedAgentRuntimeFactoryTests.cs

CursorBorrowedReviewValidationTests.csRename certification tests and assert non-matching validation returns null +32/-0

Rename certification tests and assert non-matching validation returns null

• Ports the bundle-digest stability test to the new validation type and adds a test that 'TryMatchValidatedBuild' returns null (without throwing) for non-matching paths.

test/Capacitor.Cli.Tests.Unit/Services/CursorBorrowedReviewValidationTests.cs

Documentation (2) +11 / -2
CLAUDE.mdDocument trust-by-default borrowed-review capability semantics +10/-1

Document trust-by-default borrowed-review capability semantics

• Updates design notes to state borrowed-review capability is advertised whenever the vendor factory declares a containment strategy, independent of installed build identity. Clarifies that the daemon logs probed CLI versions at startup without drift detection.

CLAUDE.md

README.mdUpdate start_flow docs for trust-by-default borrowed-review behavior +1/-1

Update start_flow docs for trust-by-default borrowed-review behavior

• Rewrites the 'start_flow' description to reflect that borrowed-context support depends on vendor-declared containment strategy, not a per-version validation record. Clarifies Cursor snapshot behavior and Claude’s lack of a borrowed containment strategy.

README.md

Other (1) +8 / -0
ci.ymlDocument CI OS matrix exclusions and platform-coverage rationale +8/-0

Document CI OS matrix exclusions and platform-coverage rationale

• Adds inline commentary explaining why macOS is excluded from the CI matrix and how platform-sensitive borrowed-review behavior is guarded via unit tests plus maintainer runs.

.github/workflows/ci.yml

@qodo-code-review

Copy link
Copy Markdown

Code Review by Qodo

🐞 Bugs (0) 📘 Rule violations (1) 📜 Skill insights (0)

Grey Divider


Remediation recommended

1. Overly verbose test narrative comments 📘 Rule violation ⚙ Maintainability
Description
The PR introduces extensive multi-paragraph narrative comments across a unit test, core daemon
logic, and the CI workflow configuration, which adds noise and makes the code and config harder to
read and maintain. These historical/motivational explanations are better kept brief inline (with a
pointer to external docs when needed) rather than embedded as long narratives in source files.
Code

test/Capacitor.Cli.Tests.Unit/AgentOrchestratorBorrowLaunchTests.cs[R99-114]

+    // ── Borrowed-snapshot regression net (two distinct checkouts) ─────────────────────────
+    // docs/superpowers/specs/2026-07-27-ai1528-trust-by-default-borrowed-review-design.md
+    //
+    // Until the capability gate above was removed this path had, on the evidence in that design,
+    // never executed in production — the daemon advertised no borrowed support, the server resolved
+    // workspace_mode=fallback, and reviewers ran in an owned worktree at the last commit. Its
+    // correctness was therefore unevidenced, which is what this test supplies.
+    //
+    // TWO checkouts are mandatory. The orchestrator snapshots the borrow cwd's own canonical git
+    // root, deliberately INDEPENDENT of the launch command's registered RepoPath, so a test pointing
+    // both at one directory satisfies every content assertion below while proving nothing about
+    // which checkout was selected — it would pass just as happily against the stale-base behavior
+    // this test exists to prevent. The launch below registers the DAEMON checkout as RepoPath and
+    // borrows the REQUESTER checkout, and the load-bearing assertion is that the snapshot's
+    // SourceRepo is the requester's git root.
+
Evidence
PR Compliance ID 6 requires comments to be brief and essential, but the cited changes add multi-line
narrative blocks: the unit test includes a lengthy motivation and historical-behavior explanation,
DaemonRunner.cs adds a historical narrative about past behavior and outcomes, and the CI workflow
matrix contains a multi-line rationale comment. These citations demonstrate the comments are
significantly more verbose than necessary for inline code/config, reducing readability and
maintainability and suggesting they should be shortened to concise summaries and/or moved to
external documentation with a short reference.

CLAUDE.md: Keep Code Comments Concise; Prefer Self-Explanatory Code Over Verbose Comments
test/Capacitor.Cli.Tests.Unit/AgentOrchestratorBorrowLaunchTests.cs[99-114]
src/Capacitor.Cli.Daemon/DaemonRunner.cs[612-616]
.github/workflows/ci.yml[24-31]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
Long, narrative comment blocks were added in multiple places (a unit test, core daemon logic, and the CI workflow), which increases noise and reduces readability/maintainability; these should be reduced to brief, essential comments, with detailed historical rationale moved to external documentation (and referenced via a short pointer) when necessary.

## Issue Context
Compliance (PR Compliance ID 6) requires keeping comments concise and essential, preferring self-explanatory code/configuration. Where additional rationale or historical failure-mode context is important, keep a short inline summary and include a link/reference to a design spec or other external documentation rather than embedding multi-paragraph narratives directly in the codebase.

## Fix Focus Areas
- test/Capacitor.Cli.Tests.Unit/AgentOrchestratorBorrowLaunchTests.cs[99-114]
- src/Capacitor.Cli.Daemon/DaemonRunner.cs[612-616]
- .github/workflows/ci.yml[24-31]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


Grey Divider

Qodo Logo

Comment on lines +99 to +114
// ── Borrowed-snapshot regression net (two distinct checkouts) ─────────────────────────
// docs/superpowers/specs/2026-07-27-ai1528-trust-by-default-borrowed-review-design.md
//
// Until the capability gate above was removed this path had, on the evidence in that design,
// never executed in production — the daemon advertised no borrowed support, the server resolved
// workspace_mode=fallback, and reviewers ran in an owned worktree at the last commit. Its
// correctness was therefore unevidenced, which is what this test supplies.
//
// TWO checkouts are mandatory. The orchestrator snapshots the borrow cwd's own canonical git
// root, deliberately INDEPENDENT of the launch command's registered RepoPath, so a test pointing
// both at one directory satisfies every content assertion below while proving nothing about
// which checkout was selected — it would pass just as happily against the stale-base behavior
// this test exists to prevent. The launch below registers the DAEMON checkout as RepoPath and
// borrows the REQUESTER checkout, and the load-bearing assertion is that the snapshot's
// SourceRepo is the requester's git root.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Remediation recommended

1. Overly verbose test narrative comments 📘 Rule violation ⚙ Maintainability

The PR introduces extensive multi-paragraph narrative comments across a unit test, core daemon
logic, and the CI workflow configuration, which adds noise and makes the code and config harder to
read and maintain. These historical/motivational explanations are better kept brief inline (with a
pointer to external docs when needed) rather than embedded as long narratives in source files.
Agent Prompt
## Issue description
Long, narrative comment blocks were added in multiple places (a unit test, core daemon logic, and the CI workflow), which increases noise and reduces readability/maintainability; these should be reduced to brief, essential comments, with detailed historical rationale moved to external documentation (and referenced via a short pointer) when necessary.

## Issue Context
Compliance (PR Compliance ID 6) requires keeping comments concise and essential, preferring self-explanatory code/configuration. Where additional rationale or historical failure-mode context is important, keep a short inline summary and include a link/reference to a design spec or other external documentation rather than embedding multi-paragraph narratives directly in the codebase.

## Fix Focus Areas
- test/Capacitor.Cli.Tests.Unit/AgentOrchestratorBorrowLaunchTests.cs[99-114]
- src/Capacitor.Cli.Daemon/DaemonRunner.cs[612-616]
- .github/workflows/ci.yml[24-31]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools

@realtonyyoung
realtonyyoung merged commit c093685 into main Jul 28, 2026
6 checks passed
@realtonyyoung
realtonyyoung deleted the tonyyoung/ai-1528-trust-by-default branch July 28, 2026 11:09
realtonyyoung added a commit that referenced this pull request Jul 28, 2026
Codex review round 2 of #378. Both findings predate this PR -- they are in the
unreviewed #376/#377 surface -- but I asked whether any send was left
uncontained, so they belong here.

P1: the lane's rejection sends. Both arms called _sendRejected BEFORE the cache
entry was marked Processed and Done resolved. A synchronous throw escaped
RunLaneAsync, permanently killing the single serial consumer: the command stayed
nonterminal, its submitter waited forever, and every queued command was stranded
-- the same held-capacity-slot class the ack fix addressed. Now the lane settles
FIRST, then notifies, with both sends contained; relative order (rejection then
ack) is unchanged. The whole body is wrapped in try/finally so Done is released
even if something unforeseen throws.

P2: the in-progress duplicate's Accepted ack was still a bare send under _lock.
A synchronous throw escaped into the hub, a faulted task went unobserved, and
any synchronous transport work ran inside the processor's critical section --
the section this PR exists to keep narrow. HandleDuplicateLocked now only
CAPTURES which answer is owed; SubmitAsync sends after releasing the lock.

Consolidated into one SendContained(Func<Task>, seq, what) helper that invokes
the delegate inside the try, so argument construction is covered at every site.
Every _sendRejected/_sendAck in the file now routes through it.

Two more tests: a throwing rejection send must not fault the lane or lose the
settlement, and a throwing Accepted-ack send must not escape an in-progress
duplicate. Both bounded, for the reason recorded on the previous test.

22/22 unit tests, check-linear-ids clean.
realtonyyoung added a commit that referenced this pull request Jul 28, 2026
* Address Qodo findings from #376/#377

Keep the liveness read off the sequenced-command lock, correct the README's
settlement-retry description, and trim design-doc-grade comments.

- SequencedCommandProcessor no longer calls the readLiveness delegate while
  holding _lock. The proactive terminal ack introduced by #377 put that read on
  every settled command, not just a duplicate replay, so lock-hold time grew and
  could delay concurrent SubmitAsync/AckPrefix callers. Both ack paths now
  record the outcome in the cache under the lock and build the ack after
  releasing it, preserving the ordering invariant that an ack can never
  advertise an outcome the cache has not recorded.

- AgentKillQuarantine gains an allocation-free IsQuarantined(agentId);
  AgentOrchestrator.ReadLiveness uses it instead of Snapshot().Any(...), which
  allocated a projected list on every call.

- README: settlement-layer coded 409s are no longer described as retried "a few
  times" and normally invisible. Documents the real 3-minute elapsed deadline,
  the backoff shape, which codes retry, and the explicit isError tool result the
  flows MCP server returns when the deadline is exhausted.

- Trimmed restated-mechanics comments in FlowRetryTiming, SequencedCommandProcessor
  and AgentOrchestratorBorrowLaunchTests. Comments recording a non-obvious why
  (the pinned backoff formula, the two-checkout requirement, the non-vacuity
  guards) are kept.

Tests: new regression coverage that the liveness delegate is never invoked under
the processor lock (mutation-verified), that the cache records the outcome
before the ack is built, and for IsQuarantined.

* fix: contain ack CONSTRUCTION, not just the send

Codex review of #378. SendProactiveAck(BuildProcessedAck(..)) evaluates its
argument before entering the try, so a throwing readLiveness -- a delegate that
walks the orchestrator's live lifecycle collections -- escaped the containment
entirely.

From the lane that faults RunLaneAsync and leaves the item's Done unresolved:
the submitter waits forever and every later command is stranded, which the
server reads as a permanently held daemon capacity slot. From SubmitAsync it
escapes into the hub handler, and that is the RECOVERY path -- a duplicate
replay is what the server sends when it never received the terminal ack.

SendProactiveAck becomes SendSettledAck(item, outcome) and builds inside the
try. The replay path now goes through it too, replacing a bare
`_ = _sendAck(...)` that contained neither a synchronous throw nor a faulted
task.

Three tests. The lane one is bounded on purpose: reverting the fix makes it
HANG rather than fail (verified -- an unbounded await pinned the suite until
the harness timeout), and a timed-out CI job is a much worse signal than a
named assertion.

Also corrected the comment on Proactive_ack_is_built_only_after_the_outcome_
is_recorded_in_the_cache: the review pointed out it is not a regression net for
the lock placement, since Monitor is reentrant and LastProcessedSeq would read
the same updated value from inside the lock. It pins the ordering, which is
worth pinning; lock placement is covered by the test above it.

20/20 unit tests, check-linear-ids clean.

* fix: no wire send may gate settlement or kill the lane

Codex review round 2 of #378. Both findings predate this PR -- they are in the
unreviewed #376/#377 surface -- but I asked whether any send was left
uncontained, so they belong here.

P1: the lane's rejection sends. Both arms called _sendRejected BEFORE the cache
entry was marked Processed and Done resolved. A synchronous throw escaped
RunLaneAsync, permanently killing the single serial consumer: the command stayed
nonterminal, its submitter waited forever, and every queued command was stranded
-- the same held-capacity-slot class the ack fix addressed. Now the lane settles
FIRST, then notifies, with both sends contained; relative order (rejection then
ack) is unchanged. The whole body is wrapped in try/finally so Done is released
even if something unforeseen throws.

P2: the in-progress duplicate's Accepted ack was still a bare send under _lock.
A synchronous throw escaped into the hub, a faulted task went unobserved, and
any synchronous transport work ran inside the processor's critical section --
the section this PR exists to keep narrow. HandleDuplicateLocked now only
CAPTURES which answer is owed; SubmitAsync sends after releasing the lock.

Consolidated into one SendContained(Func<Task>, seq, what) helper that invokes
the delegate inside the try, so argument construction is covered at every site.
Every _sendRejected/_sendAck in the file now routes through it.

Two more tests: a throwing rejection send must not fault the lane or lose the
settlement, and a throwing Accepted-ack send must not escape an in-progress
duplicate. Both bounded, for the reason recorded on the previous test.

22/22 unit tests, check-linear-ids clean.

* fix: keep the lane alive through diagnostics, and get every send out of the lock

Codex review round 3 of #378.

The try/finally comment I wrote last round was wrong, and the review said so.
finally releases the submitter but the exception still propagates out of the
await foreach and faults the lane -- and it reports SUCCESS to a submitter whose
command may never have been marked terminal. The realistic trigger is
diagnostics: an ILogger provider throwing from the execute-fault LogWarning, or
from SendContained's own catch. There is now a real per-item catch that
synthesizes the terminal state when the normal path did not reach it and lets
the loop continue, with every diagnostic inside it individually guarded --
because the most likely way to arrive there is diagnostics throwing.

Second: SendContained catches exceptions but still invokes the delegate
immediately, so it does nothing about lock scope. RejectLocked, the
duplicate-collision branch and SynthesizeErrorLocked were all still running
transport delegates inside _lock -- the same objection that moved the Accepted
ack out. They now only record WHICH rejection is owed; SubmitAsync sends it
after releasing the lock. Containment and lock-scope were two problems and I had
only fixed one.

Two tests, both aimed at the gaps the review identified in the previous ones:
a throwing ILogger (NullLogger cannot reach that path) asserting the command is
still terminal and the lane survives, and a direct assertion that no rejection
send runs with the lock held, covering the two locked reject paths the
lane-based test could not.

24/24 unit tests, check-linear-ids clean.

* fix: commit-then-notify ordering, and diagnostics that cannot become the failure

Codex review round 4 of #378.

P1: `settled` was set AFTER the rejection notification, so a throw escaping
SendRejectedContained reached the outer catch with the real terminal outcome
already committed -- and the catch overwrote it with InternalError. A
LaunchRejected(daemon_capacity) could be cached and announced as one result and
replayed later as a contradictory one. It is now set the instant the
cache+watermark commit succeeds, before any notification: the cache is
authoritative from that point and announcing it must never be able to rewrite
it.

P2: SendContained's own diagnostics were unguarded. On the hub-side paths
(stale epoch, gap, duplicate collision, accepted/processed replay) there is no
outer per-item catch, so a transport throw followed by a logging throw escaped
straight into the hub; and the async branch's unguarded LogDebug faulted its own
DISCARDED continuation, recreating the unobserved task failure the wrapper
exists to prevent. Both now route through LogQuietly.

One new test, mutation-verified: un-guarding the synchronous diagnostic makes it
fail.

Deliberately NO test for the `settled` ordering. I wrote one, then
mutation-tested it and it passed against the un-fixed code -- with the
diagnostics guarded, no post-commit path can escape into the outer catch, so the
overwrite is currently unreachable. The ordering stays as defence-in-depth (free,
and it removes a latent contradiction) but a test that passes either way would
claim coverage that is not there, which is the exact criticism this review made
of an earlier ordering test. The reasoning is recorded in a comment where the
test would have gone.

25/25 unit tests, check-linear-ids clean.

* fix: address Qodo findings

1. Removed the design-doc paths from FlowRetryTiming's XML comments. They
   embedded an internal issue slug AND pointed at a file in a private repo, so
   a public reader could not follow them anyway. The API explanation stays.

3. The deadline message asserted a cause the client had not observed.
   LastCode is null when the deadline cancels an in-flight request before any
   coded response is parsed -- the very first attempt can time out -- and the
   message still said "the daemon is still settling a prior launch" while
   printing the client's own default code in the Error(...) position, which
   reads as a server verdict. The code token stays stable (agents match on it)
   but the narrative now says where it came from. README corrected to match.
   Two tests pin both arms of the discriminator.

2. Trimmed the duplicated concurrency narration: the caller-side comments now
   point at SendSettledAck instead of restating its rationale, and
   BuildProcessedAck's summary is shorter. The invariants themselves stay --
   "never under _lock" and "the cache is committed before the ack is built"
   are exactly what five rounds of review found, and losing them would invite
   the same defects back.

38/38 settlement-retry tests, 25/25 processor tests, check-linear-ids clean.
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