Skip to content

test(types/agent): pin SessionMode strict-variant deserialization (audit-disputed)#5416

Merged
houko merged 6 commits into
mainfrom
fix/audit-session-mode
May 22, 2026
Merged

test(types/agent): pin SessionMode strict-variant deserialization (audit-disputed)#5416
houko merged 6 commits into
mainfrom
fix/audit-session-mode

Conversation

@houko

@houko houko commented May 21, 2026

Copy link
Copy Markdown
Contributor

Closes #5415.

Audit

docs/issues/session-mode-deserialize-fallback.md (Severity: Medium, audit status: DISPUTED in re-audit 2026-05-18).

The audit doc's original framing was that session_mode = "New" (capitalised typo) silently collapses to Persistent because #[serde(rename_all = "snake_case")] #[default] Persistent lacks #[serde(other)]. That framing contradicts serde semantics: the standard #[derive(Deserialize)] enum errors hard on unknown variants when there's no #[serde(other)] arm, and #[default] only fires for #[serde(default)] container/field init on a missing key — not as a fallback for unknown variant strings.

The bug as described does not currently exist.

Why a PR anyway

The boundary that makes the audit's framing wrong is also the boundary that a refactor could quietly remove. Adding #[serde(other)] to silence "unknown variant" errors looks like a reasonable forward-compat tweak. If that ever happens, operators who type "New" instead of "new" would land on CLAUDE.md's "concurrent writes to a single persistent session are undefined" warning.

So this is a documentation-and-regression-test patch that pins the boundary.

Change

crates/librefang-types/src/agent.rs:

  • Comment block on SessionMode records the strict-variant contract and points at the tests so a future refactor knows what it's about to break.
  • Four new tests in agent::tests:
    • session_mode_deserializes_lowercase_persistent_and_new — happy path.
    • session_mode_rejects_capitalised_variant_strings"New" / "PERSISTENT" must error with unknown variant.
    • session_mode_rejects_empty_string_and_typos"", "presistent", "none", "default" all hit the same path.
    • optional_session_mode_default_fires_on_missing_key_not_unknown_string#[serde(default)] Option<SessionMode> is the per-cron-job / per-trigger override shape. Pins that default only fires when the key is MISSING; an explicit "New" still errors.

cargo test -p librefang-types --lib session_mode → 4/4 pass. cargo clippy -p librefang-types --all-targets -- -D warnings clean.

Out of scope

The audit's secondary recommendation — extend the existing debug!(session_mode=resolved, …) in cron_tick.rs:198-206 to the manifest-load path so operators see the resolved value at boot — is a separate observability PR. The load sites span boot.rs, agent_state.rs, agent_control.rs and need consistent log targets / structured fields. Bundling it here would expand scope beyond "pin the boundary the dispute relies on."

@github-actions github-actions Bot added area/docs Documentation and guides size/M 50-249 lines changed labels May 21, 2026

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: e982a7851d

ℹ️ 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".

@@ -1,39 +0,0 @@
# `SessionMode` deserialization silently falls back on unknown values; typos cause semantic drift

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Keep issue index consistent with deleted audit file

Deleting this file leaves a dangling link in docs/issues/INDEX.md (the session-mode-deserialize-fallback entry at line 182 still points to session-mode-deserialize-fallback.md). That breaks documentation navigation and any tooling that expects every indexed issue slug to resolve to an existing markdown file; either keep the file as a disputed record or remove/update the index entry in the same change.

Useful? React with 👍 / 👎.

…oses #5415)

audit: session-mode-deserialize-fallback (Severity: Medium, audit
status: **DISPUTED** in the re-audit 2026-05-18).

The audit doc's framing — `session_mode = "New"` silently collapses
to `Persistent` — contradicts serde semantics. serde's standard
`#[derive(Deserialize)] enum` errors hard on unknown variants when
there's no `#[serde(other)]` arm, and `#[default]` only kicks in
for `#[serde(default)]` container/field initialisation on a missing
key — not as a fallback for unknown variant strings. The bug as
described does not currently exist.

But: the boundary that makes the audit's framing wrong is also the
boundary that a refactor could quietly remove (adding
`#[serde(other)]` to silence "unknown variant" errors looks like a
reasonable forward-compat tweak). If that ever happens, operators
who type `"New"` instead of `"new"` would land on CLAUDE.md's
"concurrent writes to a single persistent session are undefined"
warning.

So this PR is a documentation-and-regression-test patch:

  * Comment block on `SessionMode` records the strict-variant
    contract and points at the tests so a future refactor knows
    what it's about to break.
  * Four new tests in `agent::tests`:
      - `session_mode_deserializes_lowercase_persistent_and_new`
        — happy path.
      - `session_mode_rejects_capitalised_variant_strings` —
        `"New"` / `"PERSISTENT"` must error with "unknown variant".
      - `session_mode_rejects_empty_string_and_typos` — `""`,
        `"presistent"`, `"none"`, `"default"` all hit the same
        path.
      - `optional_session_mode_default_fires_on_missing_key_not_unknown_string`
        — `#[serde(default)] Option<SessionMode>` is the per-cron-job
        / per-trigger override shape. Pins that `default` only fires
        when the key is MISSING; an explicit `"New"` still errors.

cargo clippy -p librefang-types --all-targets clean. 4/4 tests
pass.

Out of scope (audit's secondary recommendation): extend the
existing `debug!(session_mode=resolved, …)` in
`cron_tick.rs:198-206` to the manifest-load path so operators see
the resolved value at boot. That's a separate observability PR —
the load sites span boot.rs, agent_state.rs, agent_control.rs and
need consistent log targets. Bundling it here would expand scope
beyond "pin the boundary the dispute relies on."
@houko
houko force-pushed the fix/audit-session-mode branch from e982a78 to 380cff5 Compare May 21, 2026 03:39
@chatgpt-codex-connector

Copy link
Copy Markdown

You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard.

@github-actions github-actions Bot added the has-conflicts PR has merge conflicts that need resolution label May 21, 2026
# Conflicts:
#	openapi.json
#	sdk/go/librefang.go
#	sdk/javascript/index.js
#	sdk/python/librefang/librefang_client.py
#	sdk/rust/src/lib.rs
@chatgpt-codex-connector

Copy link
Copy Markdown

You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard.

# Conflicts:
#	xtask/baselines/openapi.sha256

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 0339d20f73

ℹ️ 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".

@@ -1,39 +0,0 @@
# `SessionMode` deserialization silently falls back on unknown values; typos cause semantic drift

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Remove stale issue-index entry for deleted audit doc

Deleting this file leaves a dangling link in docs/issues/INDEX.md (session-mode-deserialize-fallback at line 182 still points to session-mode-deserialize-fallback.md). That breaks issue navigation and any tooling that expects every indexed slug to resolve to an existing markdown file; update the index entry in the same change when removing this document.

Useful? React with 👍 / 👎.

@github-actions github-actions Bot added ready-for-review PR is ready for maintainer review and removed has-conflicts PR has merge conflicts that need resolution labels May 21, 2026
@chatgpt-codex-connector

Copy link
Copy Markdown

You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard.

@houko
houko merged commit 477b5fa into main May 22, 2026
@houko
houko deleted the fix/audit-session-mode branch May 22, 2026 01:10
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area/docs Documentation and guides ready-for-review PR is ready for maintainer review size/M 50-249 lines changed

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Medium] SessionMode deserialization regression-guard (audit-disputed visibility fix)

1 participant