Skip to content

Fix pnpm audit violation GHSA-gmq8-994r-jv83: yauzl contains an off-by-one error#325

Merged
leynos merged 1 commit intoimplement-revision-safe-update-strategy-i43x3pfrom
fix-pnpm-audit-violations-3ttccw
Mar 13, 2026
Merged

Fix pnpm audit violation GHSA-gmq8-994r-jv83: yauzl contains an off-by-one error#325
leynos merged 1 commit intoimplement-revision-safe-update-strategy-i43x3pfrom
fix-pnpm-audit-violations-3ttccw

Conversation

@leynos
Copy link
Copy Markdown
Owner

@leynos leynos commented Mar 13, 2026

Summary by Sourcery

Implement revision-safe interests updates across backend domain, HTTP API, persistence, and tests, while updating tooling and docs, and add yauzl dependency to address the audit issue.

New Features:

  • Introduce a typed UpdateUserInterestsRequest with optional expectedRevision and return revision on successful interests updates.
  • Expose a revision field on UserInterests and on the /api/v1/users/me/interests HTTP response schema.

Bug Fixes:

  • Treat stale or missing expected revisions for interests updates as HTTP 409 conflicts with structured revision_mismatch details rather than silently retrying or overwriting.
  • Ensure revision overflow during interests updates is surfaced as an internal error and does not wrap.

Enhancements:

  • Refine DieselUserInterestsCommand to use explicit optimistic concurrency over user_preferences without hidden retry loops and to preserve non-interest preference fields.
  • Align error mapping for interests-related persistence conflicts with the existing revision_mismatch envelope used elsewhere in the backend.
  • Add save-call tracking to the StubUserPreferencesRepository to assert that no hidden retries occur in tests.
  • Refine interests HTTP parsing to carry through expectedRevision alongside validated interestThemeIds.
  • Update various backend tests and doubles to work with the new revision-aware interests contract and expectations.
  • Improve Makefile pg_worker preparation to reuse or install the pg-embed-setup-unpriv helper instead of building a local binary.
  • Tighten Vitest audit tests formatting and parameter handling for evaluateAudit scenarios.

Build:

  • Update Makefile test tooling to obtain pg_worker from pg-embed-setup-unpriv with caching under target/pg-worker-root and configurable installation paths.

Documentation:

  • Document the revision-safe interests update strategy, conflict semantics, and aggregate ownership in new and existing backend architecture and testing guides.
  • Extend the PWA data model docs to describe the UserInterests payload, expectedRevision usage, and conflict behaviour for PUT /api/v1/users/me/interests.
  • Clarify pg_worker setup, caching, and environment configuration in the testing guide.
  • Add an investigation report capturing recent toolchain and environment errors during backend validation.

Tests:

  • Replace legacy retry-focused interests persistence tests with new suites that verify conflict mapping, revision handling, and lack of hidden retries.
  • Add BDD coverage and shared helpers for revision-safe interests updates against embedded Postgres and HTTP flows.
  • Extend existing HTTP, adapter guardrail, and mapping tests to assert revisions, conflict details, and updated port contracts.
  • Adjust interests-related startup-mode, profile, and catalogue tests to assert the new revision field and request shape.

Chores:

  • Remove the now-unused backend pg_worker binary target and its test fixtures.
  • Clean up unnecessary clippy lint expectations in several BDD test modules.
  • Add yauzl as a direct dev dependency in the workspace package.json to satisfy the pnpm audit advisory.

@sourcery-ai
Copy link
Copy Markdown

sourcery-ai Bot commented Mar 13, 2026

Reviewer's Guide

Implements a revision-safe interests update flow across backend domain, HTTP, and persistence layers; removes hidden retry logic from the DieselUserInterestsCommand in favour of explicit optimistic concurrency using expectedRevision, extends the UserInterests model and HTTP schemas to carry revision, tightens error mapping to a standard revision_mismatch conflict envelope, updates tests and BDD flows accordingly, and adjusts tooling/docs including pg_worker provisioning and pnpm audit mitigation for yauzl.

Sequence diagram for the revision-safe interests update HTTP flow

sequenceDiagram
    actor Client
    participant UsersHandler as UsersHTTPHandler
    participant UserInterestsPort as UserInterestsCommand
    participant DieselInterests as DieselUserInterestsCommand
    participant PrefRepo as UserPreferencesRepository

    Client->>UsersHandler: PUT /api/v1/users/me/interests
    activate UsersHandler
    UsersHandler->>UsersHandler: parse InterestsRequest
    UsersHandler->>UsersHandler: parse_interest_theme_ids()
    UsersHandler->>UserInterestsPort: set_interests(UpdateUserInterestsRequest)
    activate UserInterestsPort

    UserInterestsPort->>DieselInterests: set_interests(request)
    activate DieselInterests
    DieselInterests->>PrefRepo: find_by_user_id(&request.user_id)
    activate PrefRepo
    PrefRepo-->>DieselInterests: existing_prefs: Option~UserPreferences~
    deactivate PrefRepo

    DieselInterests->>DieselInterests: build_preferences_for_interest_update(&request, existing_prefs)
    alt first_write_and_no_expected_revision
        DieselInterests-->>UserInterestsPort: Ok(UserInterests(revision=1))
    else existing_prefs_and_missing_expected_revision
        DieselInterests-->>UserInterestsPort: Err(Error::conflict revision_mismatch)
    else expected_revision_mismatch
        DieselInterests-->>UserInterestsPort: Err(Error::conflict revision_mismatch)
    else expected_revision_matches
        DieselInterests->>PrefRepo: save(&prefs, request.expected_revision)
        activate PrefRepo
        PrefRepo-->>DieselInterests: Ok(())
        deactivate PrefRepo
        DieselInterests-->>UserInterestsPort: Ok(UserInterests(revision=next_revision))
    end
    deactivate DieselInterests

    UserInterestsPort-->>UsersHandler: Result<UserInterests, Error>
    deactivate UserInterestsPort

    alt success
        UsersHandler-->>Client: 200 OK
        note right of Client: Body: { userId, interestThemeIds, revision }
    else conflict
        UsersHandler-->>Client: 409 Conflict
        note right of Client: Body: { code: conflict, details: { code: revision_mismatch, expectedRevision, actualRevision } }
    end
    deactivate UsersHandler
Loading

Updated class diagram for interests domain and ports

classDiagram
    class UserId
    class InterestThemeId

    class UpdateUserInterestsRequest {
        +UserId user_id
        +Vec~InterestThemeId~ interest_theme_ids
        +Option~u32~ expected_revision
    }

    class UserInterests {
        -UserId user_id
        -Vec~InterestThemeId~ interest_theme_ids
        -u32 revision
        +new(user_id: UserId, interest_theme_ids: Vec~InterestThemeId~, revision: u32) UserInterests
        +user_id() &UserId
        +interest_theme_ids() &Vec~InterestThemeId~
        +revision() u32
    }

    class UserInterestsCommand {
        <<trait>>
        +set_interests(request: UpdateUserInterestsRequest) Result~UserInterests, Error~
    }

    class FixtureUserInterestsCommand {
        +set_interests(request: UpdateUserInterestsRequest) Result~UserInterests, Error~
    }

    class DieselUserInterestsCommand {
        -preferences_repository: Arc~UserPreferencesRepository~
        +new(preferences_repository: Arc~UserPreferencesRepository~) DieselUserInterestsCommand
        +set_interests(request: UpdateUserInterestsRequest) Result~UserInterests, Error~
        -build_preferences_for_interest_update(request: &UpdateUserInterestsRequest, existing: Option~UserPreferences~) Result~UserPreferences, Error~
        -map_preferences_persistence_error(error: UserPreferencesRepositoryError) Error
        -revision_conflict(expected: Option~u32~, actual: u32) Error
    }

    class UserPreferences {
        +UserId user_id
        +Vec~uuid::Uuid~ interest_theme_ids
        +Vec~uuid::Uuid~ safety_toggle_ids
        +UnitSystem unit_system
        +u32 revision
        +builder(user_id: UserId) UserPreferencesBuilder
    }

    class UserPreferencesBuilder {
        +interest_theme_ids(ids: Vec~uuid::Uuid~) UserPreferencesBuilder
        +safety_toggle_ids(ids: Vec~uuid::Uuid~) UserPreferencesBuilder
        +unit_system(unit_system: UnitSystem) UserPreferencesBuilder
        +revision(revision: u32) UserPreferencesBuilder
        +build() UserPreferences
    }

    class UserPreferencesRepository {
        <<trait>>
        +find_by_user_id(user_id: &UserId) Result~Option~UserPreferences~, UserPreferencesRepositoryError~
        +save(prefs: &UserPreferences, expected_revision: Option~u32~) Result~(), UserPreferencesRepositoryError~
    }

    class UserPreferencesRepositoryError {
        <<enum>>
        +NotFound
        +MissingForUpdate expected: u32
        +RevisionMismatch expected: u32, actual: u32
        +ConcurrentWriteConflict
        +Query message: String
    }

    class Error {
        +conflict(message: &str) Error
        +internal(message: String) Error
        +with_details(details: serde_json::Value) Error
    }

    UserInterestsCommand <|.. FixtureUserInterestsCommand
    UserInterestsCommand <|.. DieselUserInterestsCommand

    DieselUserInterestsCommand --> UserPreferencesRepository
    DieselUserInterestsCommand ..> UpdateUserInterestsRequest
    DieselUserInterestsCommand ..> UserInterests
    DieselUserInterestsCommand ..> UserPreferences
    DieselUserInterestsCommand ..> UserPreferencesRepositoryError
    DieselUserInterestsCommand ..> Error

    FixtureUserInterestsCommand ..> UpdateUserInterestsRequest
    FixtureUserInterestsCommand ..> UserInterests

    UpdateUserInterestsRequest --> UserId
    UpdateUserInterestsRequest --> InterestThemeId

    UserInterests --> UserId
    UserInterests --> InterestThemeId

    UserPreferencesRepositoryError ..> Error
Loading

Updated class diagram for HTTP interests request and schema

classDiagram
    class InterestsRequest {
        +Vec~String~ interest_theme_ids
        +Option~u32~ expected_revision
    }

    class ParsedInterests {
        +Vec~InterestThemeId~ interest_theme_ids
        +Option~u32~ expected_revision
    }

    class UserInterestsSchema {
        +String user_id
        +Vec~String~ interest_theme_ids
        +u32 revision
    }

    class InterestsRequestError {
        <<enum>>
        +TooManyInterestThemeIds length: usize
        +InvalidInterestThemeId index: usize
    }

    class UsersHandler {
        +update_interests(state: AppState, session: UserSession, payload: InterestsRequest) ApiResult~UserInterestsSchema~
        -parse_interest_theme_ids(payload: InterestsRequest) Result~ParsedInterests, InterestsRequestError~
    }

    class AppState {
        +interests: Arc~UserInterestsCommand~
    }

    class UserSession {
        +require_user_id() Result~UserId, Error~
    }

    class UpdateUserInterestsRequest {
        +UserId user_id
        +Vec~InterestThemeId~ interest_theme_ids
        +Option~u32~ expected_revision
    }

    class UserInterests {
        +UserId user_id
        +Vec~InterestThemeId~ interest_theme_ids
        +u32 revision
    }

    UsersHandler ..> InterestsRequest
    UsersHandler ..> ParsedInterests
    UsersHandler ..> InterestsRequestError
    UsersHandler ..> AppState
    UsersHandler ..> UserSession
    UsersHandler ..> UpdateUserInterestsRequest
    UsersHandler ..> UserInterests
    UsersHandler ..> UserInterestsSchema

    AppState --> UserInterestsCommand
    ParsedInterests --> InterestThemeId
    UpdateUserInterestsRequest --> UserId
    UpdateUserInterestsRequest --> InterestThemeId
    UserInterests --> UserId
    UserInterests --> InterestThemeId
    UserInterestsSchema --> UserInterests
Loading

File-Level Changes

Change Details Files
Refactor UserInterests domain port and model to be revision-aware and to accept a typed UpdateUserInterestsRequest.
  • Introduce UpdateUserInterestsRequest with user_id, interest_theme_ids, and expected_revision, serialised in camelCase for HTTP use.
  • Change UserInterests::new and related code/tests to carry and expose a revision field.
  • Update FixtureUserInterestsCommand and recording doubles to work with the new request type and to synthesise a revision for tests.
backend/src/domain/ports/user_interests_command.rs
backend/src/domain/user_interests.rs
backend/tests/adapter_guardrails/doubles_users.rs
backend/tests/user_session_bdd.rs
backend/tests/adapter_guardrails/harness_defaults.rs
Rework DieselUserInterestsCommand to remove internal retry loops and implement explicit optimistic-concurrency semantics over user_preferences.revision.
  • Remove MAX_CONCURRENT_WRITE_ATTEMPTS loop and RetrySaveTracker-based test harnesses in favour of single-attempt save semantics.
  • Add revision_conflict helper and build_preferences_for_interest_update to enforce the matrix of (existing prefs, expected_revision) combinations, including overflow handling and pre-save conflict detection.
  • Change set_interests to accept UpdateUserInterestsRequest, call find_by_user_id once, build a new preferences aggregate preserving safety_toggle_ids and unit_system, call save with the request’s expected_revision, and return UserInterests including the resulting revision.
  • Update persistence tests to assert that insert races, stale writes, missing revisions, and revision overflows now surface as proper conflicts or internal errors without retries, and that save is called exactly once where expected.
backend/src/outbound/persistence/diesel_user_interests_command.rs
backend/src/outbound/persistence/diesel_user_interests_command/tests/support.rs
backend/src/outbound/persistence/diesel_user_interests_command/tests/retry.rs
backend/src/outbound/persistence/diesel_user_interests_command/tests/mapping.rs
backend/src/outbound/persistence/diesel_user_interests_command/tests.rs
Extend the HTTP interests endpoint contract to include expectedRevision, return revision in the response, and map conflicts consistently.
  • Augment InterestsRequest with expected_revision and adjust parse_interest_theme_ids to return a ParsedInterests struct that carries both parsed IDs and the optional revision.
  • Update update_interests handler to build an UpdateUserInterestsRequest and handle a 409 Conflict in the OpenAPI annotations.
  • Extend UserInterestsSchema with a revision field so the interests response mirrors the domain model.
  • Adapt HTTP tests and fixtures to pass expected_revision where needed and assert revision in responses and JSON schema serialisation, including a dedicated test that expectedRevision uses camelCase in JSON.
backend/src/inbound/http/users.rs
backend/src/inbound/http/users/tests.rs
backend/src/inbound/http/schemas.rs
backend/tests/diesel_profile_interests_adapters.rs
backend/tests/user_state_profile_interests_startup_modes_bdd/flow_support.rs
backend/tests/user_state_profile_interests_startup_modes_bdd.rs
Add end-to-end BDD coverage for revision-safe interests behaviour with embedded Postgres.
  • Introduce a new BDD test module with flow_support helpers that build the HTTP app, perform login, call the interests and preferences endpoints, and capture JSON snapshots.
  • Define scenarios and step functions that cover first write creating revision 1, matching revision advancing to 2, stale and missing expectedRevision yielding 409 conflicts with expected/actual details, and interests updates preserving non-interest preferences fields while bumping revision.
  • Add a supporting feature file describing the scenarios in Gherkin, plus DB seeding helpers for users and preferences.
  • Wire these tests into the backend test suite and reuse existing cluster skipping/PG-embedded setup patterns.
backend/tests/user_interests_revision_conflicts_bdd.rs
backend/tests/user_interests_revision_conflicts_bdd/flow_support.rs
backend/tests/user_interests_revision_conflicts_bdd/db_support.rs
backend/tests/features/user_interests_revision_conflicts.feature
backend/tests/support/cluster_skip.rs
Standardise error mapping for preferences-related conflicts and extend mapping tests to cover new failure cases.
  • Map repository RevisionMismatch and MissingForUpdate errors to a shared revision_conflict helper that always produces Error::conflict("revision mismatch") with a standard details payload containing code, expectedRevision, and actualRevision.
  • Extend mapping tests to assert that various StubFailure cases (connection, query, revision mismatch, missing for update, concurrent write conflict) map to the expected ErrorCode values and revision details.
  • Ensure pre-save conflicts (missing expectedRevision when preferences exist, stale expectedRevision) never call save and instead produce conflict errors with correct details.
backend/src/outbound/persistence/diesel_user_interests_command.rs
backend/src/outbound/persistence/diesel_user_interests_command/tests/mapping.rs
backend/src/outbound/persistence/diesel_user_interests_command/tests/support.rs
Update documentation to describe the new revision-safe interests model, endpoint semantics, and pg_worker provisioning.
  • Document that PUT /api/v1/users/me/interests is a backward-compatibility endpoint layered over the user_preferences aggregate, sharing the same revision and optimistic concurrency policy as full preferences writes.
  • Clarify in the backend architecture doc that interests writes preserve safety_toggle_ids and unit_system, and that stale or missing revisions yield 409 Conflict with revision_mismatch details.
  • Add a new execplan documenting the design and implementation plan for revision-safe interests, and an investigation report capturing toolchain/environment issues during validation.
  • Revise the testing guide to explain the new pg_worker provisioning flow using pg-embed-setup-unpriv and PG_WORKER_PATH/PG_EMBEDDED_WORKER wiring.
  • Extend the PWA data model doc to include the UserInterests response shape and the expectedRevision contract for the interests endpoint.
docs/wildside-backend-architecture.md
docs/wildside-pwa-data-model.md
docs/wildside-testing-guide.md
docs/execplans/backend-3-5-4-revision-safe-interests-update-strategy.md
docs/investigations/2026-03-13-toolchain-errors.md
Adjust test doubles and startup harnesses to accommodate the new interests contract and revision field.
  • Change RecordingUserInterestsCommand to record full UpdateUserInterestsRequest objects instead of tupled user_id and interest IDs, updating assertions in guardrail tests accordingly.
  • Update BDD harness defaults and profile/interests startup-mode flows to construct UserInterests with a concrete revision, and to assert that HTTP responses include the revision field.
  • Remove now-unnecessary clippy::type_complexity expectations from several test modules after simplifying doubles, reducing lint noise.
backend/tests/adapter_guardrails/doubles_users.rs
backend/tests/adapter_guardrails/harness_defaults.rs
backend/tests/user_session_bdd.rs
backend/tests/user_state_profile_interests_startup_modes_bdd.rs
backend/tests/admin_enrichment_reporting_bdd.rs
backend/tests/catalogue_endpoints_bdd.rs
backend/tests/offline_walk_endpoints_bdd.rs
backend/tests/pwa_annotations_bdd.rs
backend/tests/pwa_preferences_bdd.rs
Update build tooling to use external pg_worker and fix pnpm audit violations including the yauzl advisory.
  • Change prepare-pg-worker Makefile target to prefer an existing pg_worker on PATH and otherwise install pg-embed-setup-unpriv’s pg_worker into a cached root under target/pg-worker-root, copying it to PG_WORKER_PATH and forwarding that to PG_EMBEDDED_WORKER.
  • Remove the backend pg_worker binary target now that the helper is externalised.
  • Update the testing guide to explain the new PG_WORKER_PATH default and override behaviour, and troubleshooting for permission issues.
  • Add yauzl 3.2.1 to the root package.json overrides and update pnpm-lock.yaml accordingly to address GHSA-gmq8-994r-jv83.
  • Perform minor test-style cleanup in the pnpm audit script tests (reformat it.each calls without changing behaviour).
Makefile
backend/Cargo.toml
docs/wildside-testing-guide.md
package.json
pnpm-lock.yaml
frontend-pwa/scripts/run-audit.test.mjs

Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Mar 13, 2026

Caution

Review failed

Failed to post review comments

Summary by CodeRabbit

Release Notes

  • New Features

    • User interests updates now include revision tracking, preventing conflicts when multiple clients update simultaneously.
    • API returns HTTP 409 Conflict with revision details when stale update attempts are detected.
  • Tests

    • Expanded test coverage for revision conflict scenarios and data preservation during interest updates.
  • Documentation

    • Updated architecture documentation describing revision-safe user interests updates and conflict handling behaviour.
  • Chores

    • Optimised build configuration for PostgreSQL worker setup with improved PATH-based fallback logic.

Walkthrough

This pull request implements optimistic concurrency control for user interests updates by introducing revision tracking. The user_preferences aggregate now shares a revision counter across both full preference writes and partial interests updates. Clients must provide the expected revision when updating after the initial write; stale or missing revisions return HTTP 409 Conflict responses with detailed mismatch information.

Changes

Cohort / File(s) Summary
Build tooling
Makefile
Adds conditional pg_worker setup: prefers PATH lookup; otherwise installs via cargo into PG_WORKER_INSTALL_ROOT. Introduces PG_WORKER_INSTALL_ROOT and PG_EMBED_SETUP_UNPRIV_VERSION variables.
Binary removal
backend/Cargo.toml, backend/src/bin/pg_worker.rs
Removes the pg_worker Rust binary and its Cargo.toml entry in favour of the pg-embed-setup-unpriv-shipped version.
Domain model — revision tracking
backend/src/domain/user_interests.rs, backend/src/domain/ports/user_interests_command.rs, backend/src/domain/ports/mod.rs
Adds revision field to UserInterests; introduces UpdateUserInterestsRequest containing user_id, interest_theme_ids, and optional expected_revision; updates trait signature to accept the request type.
HTTP layer — request/response contracts
backend/src/inbound/http/schemas.rs, backend/src/inbound/http/users.rs
Adds revision field to UserInterestsSchema; extends InterestsRequest with optional expected_revision; updates handler to parse and pass revision through to domain layer; adds 409 Conflict response code.
Persistence layer — revision-aware conflict handling
backend/src/outbound/persistence/diesel_user_interests_command.rs, backend/src/outbound/persistence/diesel_user_interests_command/tests/...
Major refactor: removes retry machinery; introduces revision_conflict helper and build_preferences_for_interest_update with four-case revision matching logic; implements optimistic concurrency via explicit revision validation; eliminates MAX_CONCURRENT_WRITE_ATTEMPTS and PreferencesUpdate scaffolding; updates signature to accept UpdateUserInterestsRequest.
Test doubles and support
backend/src/outbound/persistence/diesel_user_interests_command/tests/support.rs, backend/tests/adapter_guardrails/doubles_users.rs, backend/tests/adapter_guardrails/harness_defaults.rs
Updates RecordingUserInterestsCommand to track UpdateUserInterestsRequest payloads instead of tuples; simplifies StubUserPreferencesRepository with save_call_count tracking; removes legacy retry-tracker infrastructure; adds request() helper constructor.
BDD test infrastructure — revision conflict scenarios
backend/tests/user_interests_revision_conflicts_bdd.rs, backend/tests/user_interests_revision_conflicts_bdd/db_support.rs, backend/tests/user_interests_revision_conflicts_bdd/flow_support.rs
Introduces comprehensive end-to-end BDD tests with database bootstrap, user/preference seeding, HTTP snapshot capture, and assertions for first write (revision 1), matching revision (increment), stale revision (409 Conflict), missing revision (409 Conflict), and field preservation scenarios.
BDD test feature specification
backend/tests/features/user_interests_revision_conflicts.feature
New feature file defining five scenarios: first write creates revision 1; matching revision advances revision; stale revision returns conflict; missing revision returns conflict; non-interest fields preserved during updates.
HTTP test updates
backend/tests/diesel_profile_interests_adapters.rs, backend/tests/user_session_bdd.rs, backend/tests/user_state_profile_interests_startup_modes_bdd.rs, backend/tests/user_state_profile_interests_startup_modes_bdd/flow_support.rs
Updates test payloads to include expected_revision field; adds revision assertions in response validation; adjusts assert_interests_response signature to check expected_revision; updates doubles to use UpdateUserInterestsRequest.
Lint suppression removals
backend/tests/admin_enrichment_reporting_bdd.rs, backend/tests/catalogue_endpoints_bdd.rs, backend/tests/offline_walk_endpoints_bdd.rs, backend/tests/pwa_annotations_bdd.rs, backend/tests/pwa_preferences_bdd.rs
Removes clippy::type_complexity lint suppressions from test modules, leaving dead_code suppressions intact.
Documentation — architecture and strategy
docs/execplans/backend-3-5-4-revision-safe-interests-update-strategy.md, docs/wildside-backend-architecture.md, docs/wildside-pwa-data-model.md, docs/wildside-testing-guide.md
Adds detailed execution plan for revision-safe interests; documents architecture decisions (shared revision, preservation of non-interest fields, 409 Conflict handling); updates API documentation and testing guide for pg_worker setup changes.
Investigation and dependency updates
docs/investigations/2026-03-13-toolchain-errors.md, package.json, frontend-pwa/scripts/run-audit.test.mjs
Adds toolchain failure investigation report; updates pnpm overrides for yauzl; reformats test callbacks with consistent spacing.

Sequence Diagram

sequenceDiagram
    participant Client
    participant HTTP as HTTP Handler
    participant Domain as Domain<br/>(UserInterests)
    participant Persistence as Persistence<br/>(Diesel)
    participant DB as Database

    Client->>HTTP: PUT /api/v1/users/me/interests<br/>{interestThemeIds, expectedRevision?}
    HTTP->>Domain: set_interests(UpdateUserInterestsRequest)
    Domain->>Persistence: set_interests(request)
    
    Persistence->>DB: SELECT user_preferences WHERE user_id
    DB-->>Persistence: existing (Option<UserPreferences>)
    
    alt expectedRevision matches existing.revision
        Persistence->>Persistence: Compute next_revision
        Persistence->>DB: UPDATE user_preferences<br/>SET interest_theme_ids=?, revision=?
        DB-->>Persistence: Success
        Persistence-->>Domain: Ok(UserInterests{user_id, themes, new_revision})
    else expectedRevision mismatch
        Persistence-->>Domain: Err(Conflict{expected, actual})
    else expectedRevision missing when preferences exist
        Persistence-->>Domain: Err(Conflict{missing, actual})
    else First write (no existing preferences)
        Persistence->>DB: INSERT user_preferences<br/>SET revision=1
        DB-->>Persistence: Success
        Persistence-->>Domain: Ok(UserInterests{..., revision: 1})
    end
    
    Domain-->>HTTP: Result<UserInterests, Error>
    
    alt Success
        HTTP->>Client: 200 {userId, interestThemeIds, revision}
    else Conflict
        HTTP->>Client: 409 {code: "revision_mismatch",<br/>expectedRevision, actualRevision}
    end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

  • Implement DB-backed user types with tests (3.5.3) #322: Introduces the DB-backed DieselUserInterestsCommand with retry-based concurrency handling; this PR evolves that adapter to replace retries with explicit revision-based optimistic concurrency and removes the retry machinery entirely, creating a direct functional dependency.

Poem

Revisions rise like tides so grand,
Conflicts caught with a steady hand—
First write blooms with revision one, ✨
Stale writes clash, but no harm done,
Data dances, safe and sound. 🔄

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 76.92% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately describes the primary change: addressing a pnpm audit violation by fixing yauzl's off-by-one error.
Description check ✅ Passed The description comprehensively relates to the changeset, detailing the main objective alongside extensive refactoring of interests-update infrastructure and tooling.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
  • 📝 Generate docstrings (stacked PR)
  • 📝 Generate docstrings (commit on current branch)
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch fix-pnpm-audit-violations-3ttccw
📝 Coding Plan
  • Generate coding plan for human review comments

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown

@codescene-delta-analysis codescene-delta-analysis Bot left a comment

Choose a reason for hiding this comment

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

Code Health Improved (1 files improve in Code Health)

Gates Failed
Enforce advisory code health rules (2 files with Code Duplication)

Gates Passed
5 Quality Gates Passed

See analysis details in CodeScene

Reason for failure
Enforce advisory code health rules Violations Code Health Impact
user_interests_revision_conflicts_bdd.rs 1 advisory rule 9.39 Suppress
flow_support.rs 1 advisory rule 9.39 Suppress
View Improvements
File Code Health Impact Categories Improved
support.rs 9.39 → 10.00 Code Duplication

Quality Gate Profile: Pay Down Tech Debt
Install CodeScene MCP: safeguard and uplift AI-generated code. Catch issues early with our IDE extension and CLI tool.

Comment on lines +60 to +77
fn existing_preferences_revision_1_with_preserved_safety_and_unit_settings(world: &mut World) {
if is_skipped(world) {
return;
}

let db = world.db.as_ref().expect("db context");
seed_preferences(
db.database_url.as_str(),
SeedPreferences {
user_id: Uuid::parse_str(FIXTURE_AUTH_ID).expect("valid fixture UUID"),
interest_ids: &[FIRST_THEME_ID],
safety_ids: &[SAFETY_TOGGLE_ID],
unit_system: "imperial",
revision: 1,
},
)
.expect("seed user preferences");
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

❌ New issue: Code Duplication
The module contains 6 functions with similar structure: existing_preferences_revision_1_with_preserved_safety_and_unit_settings,existing_preferences_revision_2,the_first_interests_response_includes_revision_1,the_response_is_a_conflict_with_expected_revision_1_and_actual_revision_2 and 2 more functions

Suppress

Comment on lines +121 to +139
async fn update_interests_snapshot<S>(
app: &S,
cookie: Cookie<'static>,
payload: InterestsRequest,
) -> Snapshot
where
S: actix_web::dev::Service<
actix_http::Request,
Response = actix_web::dev::ServiceResponse<actix_web::body::BoxBody>,
Error = actix_web::Error,
>,
{
let req = actix_test::TestRequest::put()
.uri("/api/v1/users/me/interests")
.cookie(cookie)
.set_json(payload)
.to_request();
capture_snapshot(actix_test::call_service(app, req).await, false).await
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

❌ New issue: Code Duplication
The module contains 5 functions with similar structure: preferences_snapshot,run_first_write,run_missing_revision_conflict,run_stale_revision_conflict and 1 more functions

Suppress

Copy link
Copy Markdown

@sourcery-ai sourcery-ai Bot left a comment

Choose a reason for hiding this comment

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

Hey - I've found 4 issues

Prompt for AI Agents
Please address the comments from this code review:

## Individual Comments

### Comment 1
<location path="backend/src/domain/ports/user_interests_command.rs" line_range="43-46" />
<code_context>
+        request: UpdateUserInterestsRequest,
     ) -> Result<UserInterests, Error> {
-        Ok(UserInterests::new(user_id.clone(), interest_theme_ids))
+        Ok(UserInterests::new(
+            request.user_id,
+            request.interest_theme_ids,
+            request.expected_revision.map_or(1, |revision| revision + 1),
+        ))
     }
</code_context>
<issue_to_address>
**issue:** Fixture implementation can overflow the revision counter when `expected_revision == u32::MAX`.

Here the increment can overflow when `expected_revision` is `u32::MAX`, panicking in debug and wrapping in release. The production code uses `checked_add(1)` and turns overflow into a domain error. To keep behaviour consistent and avoid surprising test/fixture behaviour, mirror that approach (e.g. `revision.checked_add(1).unwrap_or(revision)` or return an error-equivalent).
</issue_to_address>

### Comment 2
<location path="backend/src/outbound/persistence/diesel_user_interests_command/tests/mapping.rs" line_range="120" />
<code_context>
 #[rstest]
 #[case(StubFailure::Connection, ErrorCode::ServiceUnavailable)]
 #[case(StubFailure::Query, ErrorCode::InternalError)]
</code_context>
<issue_to_address>
**suggestion (testing):** Assert that `save` is only called once for each failure variant to prove retries were removed

Since the retry loop was removed and `save_call_count` was added to `StubUserPreferencesRepository`, please extend this test to assert `repository.save_call_count() == 1` for each failure case (e.g. `RevisionMismatch`, `MissingForUpdate`, `ConcurrentWriteConflict`). This will codify the expectation that these failures do not trigger retries and help catch regressions if retries are reintroduced.

Suggested implementation:

```rust
    // existing error mapping assertion
    assert_eq!(expected_code, error.code());

    // ensure that save is only called once and no retries occur
    assert_eq!(
        1,
        repository.save_call_count(),
        "save should be called exactly once for failure {:?}",
        failure
    );

```

Because I only see the attribute section of the test, you will need to ensure:

1. The test function already has (or can access) a `repository` variable of type `StubUserPreferencesRepository` (or a reference to it). If it’s created locally, keep the name `repository` to match the snippet above; otherwise, adjust the assertion to use the actual variable name.
2. The error value is bound to a variable named `error` and the expected code variable is `expected_code`. If your names differ, update the `assert_eq!(expected_code, error.code());` search pattern and the assertion accordingly.
3. The failure case argument is named `failure` to use in the assertion message. If it is named differently (e.g. `stub_failure`), update the format string argument accordingly.
4. This assertion is placed in the test that is using the `#[rstest]` block you showed (the one with cases for `Connection`, `Query`, `RevisionMismatch`, `MissingForUpdate`, and `ConcurrentWriteConflict`) so that it runs for each failure variant.
</issue_to_address>

### Comment 3
<location path="docs/wildside-testing-guide.md" line_range="34" />
<code_context>
+- Otherwise, the Makefile installs `pg-embed-setup-unpriv`'s `pg_worker` into
+  a cache under `target/pg-worker-root/` and then copies it to
+  `PG_WORKER_PATH`.
+- By default, `PG_WORKER_PATH` is `target/pg_worker`. Override it when you need
+  a different writable destination, for example:

</code_context>
<issue_to_address>
**suggestion (review_instructions):** The phrase "when you need" uses a second-person pronoun, which the style guidelines disallow.

Consider rephrasing to avoid "you", for example:

- "Override it when a different writable destination is required" or
- "Override it when a different writable destination is needed".

<details>
<summary>Review instructions:</summary>

**Path patterns:** `**/*.md`

**Instructions:**
Avoid 2nd person or 1st person pronouns ("I", "you", "we").

</details>
</issue_to_address>

### Comment 4
<location path="docs/investigations/2026-03-13-toolchain-errors.md" line_range="348" />
<code_context>
+- A later `cargo clippy` run reaches normal linting and reports unrelated,
+  comprehensible source issues instead.
+
+What I cannot prove from current evidence:
+
+- Whether the root cause was a nightly Rust/Clippy regression, a transient
</code_context>
<issue_to_address>
**suggestion (review_instructions):** This sentence and nearby ones use first-person pronouns ("I"), which the style rules disallow.

To follow the pronoun guideline, consider rephrasing here and in nearby sentences such as "I therefore assign the following confidence levels", "I re-ran the exact probe command", and "I also re-ran `cargo clippy`".

For example:
- "What cannot be proved from current evidence:" instead of "What I cannot prove...";
- "The following confidence levels are assigned:" instead of "I therefore assign...";
- "The exact probe command was re-run directly" instead of "I re-ran the exact probe command directly".

<details>
<summary>Review instructions:</summary>

**Path patterns:** `**/*.md`

**Instructions:**
Avoid 2nd person or 1st person pronouns ("I", "you", "we").

</details>
</issue_to_address>

Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

Comment on lines +43 to +46
Ok(UserInterests::new(
request.user_id,
request.interest_theme_ids,
request.expected_revision.map_or(1, |revision| revision + 1),
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

issue: Fixture implementation can overflow the revision counter when expected_revision == u32::MAX.

Here the increment can overflow when expected_revision is u32::MAX, panicking in debug and wrapping in release. The production code uses checked_add(1) and turns overflow into a domain error. To keep behaviour consistent and avoid surprising test/fixture behaviour, mirror that approach (e.g. revision.checked_add(1).unwrap_or(revision) or return an error-equivalent).

assert_eq!(err.code(), expected_code);
}

#[rstest]
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

suggestion (testing): Assert that save is only called once for each failure variant to prove retries were removed

Since the retry loop was removed and save_call_count was added to StubUserPreferencesRepository, please extend this test to assert repository.save_call_count() == 1 for each failure case (e.g. RevisionMismatch, MissingForUpdate, ConcurrentWriteConflict). This will codify the expectation that these failures do not trigger retries and help catch regressions if retries are reintroduced.

Suggested implementation:

    // existing error mapping assertion
    assert_eq!(expected_code, error.code());

    // ensure that save is only called once and no retries occur
    assert_eq!(
        1,
        repository.save_call_count(),
        "save should be called exactly once for failure {:?}",
        failure
    );

Because I only see the attribute section of the test, you will need to ensure:

  1. The test function already has (or can access) a repository variable of type StubUserPreferencesRepository (or a reference to it). If it’s created locally, keep the name repository to match the snippet above; otherwise, adjust the assertion to use the actual variable name.
  2. The error value is bound to a variable named error and the expected code variable is expected_code. If your names differ, update the assert_eq!(expected_code, error.code()); search pattern and the assertion accordingly.
  3. The failure case argument is named failure to use in the assertion message. If it is named differently (e.g. stub_failure), update the format string argument accordingly.
  4. This assertion is placed in the test that is using the #[rstest] block you showed (the one with cases for Connection, Query, RevisionMismatch, MissingForUpdate, and ConcurrentWriteConflict) so that it runs for each failure variant.

- Otherwise, the Makefile installs `pg-embed-setup-unpriv`'s `pg_worker` into
a cache under `target/pg-worker-root/` and then copies it to
`PG_WORKER_PATH`.
- By default, `PG_WORKER_PATH` is `target/pg_worker`. Override it when you need
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

suggestion (review_instructions): The phrase "when you need" uses a second-person pronoun, which the style guidelines disallow.

Consider rephrasing to avoid "you", for example:

  • "Override it when a different writable destination is required" or
  • "Override it when a different writable destination is needed".
Review instructions:

Path patterns: **/*.md

Instructions:
Avoid 2nd person or 1st person pronouns ("I", "you", "we").

- A later `cargo clippy` run reaches normal linting and reports unrelated,
comprehensible source issues instead.

What I cannot prove from current evidence:
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

suggestion (review_instructions): This sentence and nearby ones use first-person pronouns ("I"), which the style rules disallow.

To follow the pronoun guideline, consider rephrasing here and in nearby sentences such as "I therefore assign the following confidence levels", "I re-ran the exact probe command", and "I also re-ran cargo clippy".

For example:

  • "What cannot be proved from current evidence:" instead of "What I cannot prove...";
  • "The following confidence levels are assigned:" instead of "I therefore assign...";
  • "The exact probe command was re-run directly" instead of "I re-ran the exact probe command directly".
Review instructions:

Path patterns: **/*.md

Instructions:
Avoid 2nd person or 1st person pronouns ("I", "you", "we").

@leynos leynos changed the base branch from main to implement-revision-safe-update-strategy-i43x3p March 13, 2026 20:36
@leynos leynos merged commit 22c3e5a into implement-revision-safe-update-strategy-i43x3p Mar 13, 2026
1 of 4 checks passed
@leynos leynos deleted the fix-pnpm-audit-violations-3ttccw branch March 13, 2026 20:36
@chatgpt-codex-connector
Copy link
Copy Markdown

💡 Codex Review

install -m 0755 "$$(command -v pg_worker)" "$(PG_WORKER_PATH)"; \

P1 Badge Create PG_WORKER_PATH parent before copying pg_worker

prepare-pg-worker now copies a discovered pg_worker binary straight to $(PG_WORKER_PATH), but this fails whenever that destination directory is missing (for example, a clean checkout or a custom PG_WORKER_PATH), which blocks make test-rust before tests start. I verified CLI semantics with install --help: it copies to DEST/existing DIRECTORY, and only -D “create[s] all leading components of DEST”, so this branch should create the parent directory (or use install -D) before copying.


request.expected_revision.map_or(1, |revision| revision + 1),

P2 Badge Use checked_add when deriving fixture interests revision

The fixture interests command derives the next revision with revision + 1 without overflow handling, so a request with expectedRevision = 4294967295 will panic in debug builds and wrap to 0 in release builds; in fixture startup mode this can crash the endpoint or emit an invalid revision sequence. This should use checked_add and return a domain error on overflow, matching the overflow handling already used in the Diesel-backed path.

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

leynos added a commit that referenced this pull request Mar 18, 2026
…#323)

* docs(execplans): add ExecPlan for revision-safe interests update strategy

Add a detailed 616-line execution plan for roadmap item 3.5.4 defining and implementing a revision-safe update strategy for user interests.

This new document outlines the purpose, constraints, tolerances, risks, progress stages, surprises, decision log, outcomes, plan of work, interfaces, error mapping, and team ownership related to replacing the legacy last-write-wins interests update with an optimistic concurrency model aligned with the existing user preferences aggregate.

No implementation is included yet; this draft locks the architectural and concurrency decisions to guide subsequent development and testing.

Co-authored-by: devboxerhub[bot] <devboxerhub[bot]@users.noreply.github.com>

* feat(interests): add optimistic concurrency with revision to interests update

- Introduce UpdateUserInterestsRequest to carry expectedRevision for concurrency control.
- UserInterests now includes a revision field to track aggregate version.
- Convert DieselUserInterestsCommand to enforce revision checks and reject stale writes.
- Expose revision-safe update and conflict semantics in HTTP API for PUT /users/me/interests.
- Remove implicit retries on concurrency conflicts; surface 409 Conflict with revision details.
- Add extensive unit and integration tests covering revision conflicts and concurrency handling.
- Update documentation to reflect that interests share aggregate and revision with user preferences, with optimistic locking.

This change ensures safe concurrent updates on user interests by leveraging the aggregate revision from user preferences, providing explicit conflict feedback to clients when revisions are stale or missing after the first write.

Co-authored-by: devboxerhub[bot] <devboxerhub[bot]@users.noreply.github.com>

* docs(investigations): add detailed investigation report on toolchain errors and failures

Add a comprehensive 500-line investigation document describing three transient
failures observed in the backend validation process. This report includes
failure reproductions, environment snapshots, failure diagnoses, relevant
code paths, and recommended next steps. The failures are analyzed in detail,
identifying causes such as a broken /dev/null device and transient toolchain
instabilities, with evidence ruling out persistent code defects.

This addition aids in debugging and future failure mitigation related to the
Rust toolchain and container environment.

Co-authored-by: devboxerhub[bot] <devboxerhub[bot]@users.noreply.github.com>

* refactor(pg_worker): replace in-repo pg_worker binary with external one

This change removes the local `backend/src/bin/pg_worker.rs` binary and the corresponding entry in `Cargo.toml`. Instead, the Makefile is updated to use the `pg_worker` binary provided by the `pg-embed-setup-unpriv` crate.

- Makefile updated to check for an existing `pg_worker` on PATH and install it if missing.
- Introduced `PG_WORKER_INSTALL_ROOT` and `PG_EMBED_SETUP_UNPRIV_VERSION` variables in Makefile.
- Documentation updated to describe the new usage pattern for the embedded Postgres worker helper binary.

This simplifies maintenance by relying on an external, versioned binary rather than one built and maintained in this repository.

Co-authored-by: devboxerhub[bot] <devboxerhub[bot]@users.noreply.github.com>

* refactor(tests/user_interests_revision_conflicts_bdd): refactor test flow and DB support for user interests revision

- Extracted common test logic into helper functions (e.g., with_world, seed_prefs) to reduce code duplication and improve readability.
- Modified seed_preferences DB support function to accept user_id as explicit parameter enhancing clarity.
- Simplified test step implementations by wrapping them with helper functions managing skip checks.
- Refined flow_support functions to unify update operations and introduce reusable session handling (with_app_session), improving async control and reducing boilerplate.
- Cleaned up conditional checks scattered in tests and standardized update call patterns.

These changes improve maintainability and clarity of BDD test code for user preferences revision conflict scenarios.

Co-authored-by: devboxerhub[bot] <devboxerhub[bot]@users.noreply.github.com>

* Fix GHSA-gmq8-994r-jv83: yauzl contains an off-by-one error (#325)

* refactor(tests/user_interests_revision_conflicts_bdd): improve test helper functions and use consistent calls

- Introduced reusable step helpers for interest and conflict assertions to reduce duplication
- Updated seed_prefs to use a single parameter struct for clarity
- Simplified then-step functions to use new helpers for clearer intent
- Added generic call_endpoint helper in flow_support.rs to centralize request handling
- Refactored test requests to use the new call_endpoint improving code reuse and readability

Co-authored-by: devboxerhub[bot] <devboxerhub[bot]@users.noreply.github.com>

* refactor(ci): simplify pg_worker build step by using make prepare-pg-worker

Replaced the manual cargo build and install commands with a single make prepare-pg-worker command in the GitHub Actions CI workflow. This improves maintainability and reuses existing build scripts.

Co-authored-by: devboxerhub[bot] <devboxerhub[bot]@users.noreply.github.com>

* fix(make): ensure target directory exists before installing pg_worker

When pg_worker is found on PATH, the prepare-pg-worker target copies it
to target/pg_worker without first creating the parent directory. On a
clean checkout or after 'make clean', this fails with 'No such file or
directory', blocking 'make test' in environments with a global pg_worker.

This commit adds 'mkdir -p $(dirname $(PG_WORKER_PATH))' before the
install command in both branches of the conditional to ensure the target
directory always exists.

Also removes invalid 'noImportCycles' Biome rule that was causing
check-fmt to fail, and applies auto-formatting to test files.

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

* refactor: address code review findings for revision-safe interests update

This commit addresses inline and outside-diff code review findings:

Code changes:
- Add Rustdoc examples to UserInterests::new and revision() methods
  demonstrating construction and revision assertion
- Fix test in users/tests.rs to assert on HTTP DTO (InterestsRequest)
  instead of domain DTO for proper boundary testing
- Add revision field assertion to user_interests_schema test
- Add skip_serializing_if to InterestsRequest.expected_revision to omit
  None values from JSON serialization
- Deduplicate mkdir command in prepare-pg-worker Makefile target by
  moving it before the conditional

Documentation changes:
- Update execplan, backend architecture, and PWA data model docs to
  correctly describe conflict response envelope shape: top-level
  code: "conflict" with nested details containing code:
  "revision_mismatch", expectedRevision, and actualRevision
- Fix first-person narration in toolchain errors investigation doc,
  replacing "I re-ran" with passive constructions
- Convert all headings to sentence case in toolchain errors doc per
  style guidelines
- Update PWA data model doc timestamp to current date

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

* fix(docs): wrap long line in toolchain errors investigation

Fix MD013/line-length violation at line 312 by wrapping the sentence to
stay within the 80-character limit.

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

* fix(docs): use sequential numbering for execplan execution steps

Replace repeated "1." markers with sequential numbering (1., 2., 3., 4.,
5., 6.) in the concrete execution steps section. Indent all list item
content (code blocks and text) to maintain proper list continuity and
satisfy MD029/ol-prefix markdownlint rule with "ordered" style.

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

* refactor(tests): eliminate duplication in schema has_expected_name tests

Introduce assert_schema_contains helper to replace repeated pattern across
all five has_expected_name test functions. The helper takes the expected
schema name and a slice of field names to check, eliminating ~40 lines of
duplicated test code.

Also remove unused UpdateUserInterestsRequest import from users/tests.rs
that was left over from the previous refactoring.

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

* fix: address remaining code review findings

Fix three remaining issues from code review:

1. Replace first-person phrasing "What I cannot prove" with neutral
   voice "What the available evidence does not establish" in toolchain
   errors investigation doc

2. Rename test function from
   update_user_interests_request_serializes_expected_revision_in_camel_case
   to interests_request_serializes_expected_revision_in_camel_case to
   match the actual DTO being tested (InterestsRequest)

3. Update execplan conflict-envelope description to accurately reflect
   the two-level structure: top-level code: "conflict" with nested
   details containing code: "revision_mismatch", expectedRevision, and
   actualRevision

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

* fix: address final code review findings

Fix three remaining issues from code review:

1. Harden interests_request serialization test by adding assertion that
   snake_case key "expected_revision" is not present in JSON, ensuring
   only camelCase "expectedRevision" is serialized

2. Fix article/noun agreement in execplan: change "an existing
   preferences row" to "an existing preference row" (singular)

3. Update toolchain investigation doc to use neutral voice: change "What
   the available evidence does not establish:" to "What cannot be proved
   from current evidence:" to avoid first-person phrasing

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

---------

Co-authored-by: devboxerhub[bot] <devboxerhub[bot]@users.noreply.github.com>
Co-authored-by: Claude Sonnet 4.5 <noreply@anthropic.com>
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