Skip to content

feat: safe-by-default SignLoadPolicy for verified loads#23

Open
Nelson Spence (Fieldnote-Echo) wants to merge 3 commits into
feat/sign-policyfrom
feat/sign-load-policy
Open

feat: safe-by-default SignLoadPolicy for verified loads#23
Nelson Spence (Fieldnote-Echo) wants to merge 3 commits into
feat/sign-policyfrom
feat/sign-load-policy

Conversation

@Fieldnote-Echo

Copy link
Copy Markdown
Member

Wave 1 / PR4 of the post-mortem hardening (post-mortem F5). Stacked on the SignPolicy PR (feat/sign-policy) — merge that first.

  • DenseLoadOptions.require_sign: boolsign: SignLoadPolicy { RequireIfSupported, Require, Any, Forbid }; hand-written Default = RequireIfSupported: a sign-capable bundle (bits=2, dim%64==0) missing its sidecar now FAILS by default instead of silently falling back to the slow exact scan. Any restores old behavior explicitly; Forbid gets a dedicated DenseError::SignSidecarForbidden.
  • expected_dim/expected_bits stay None-default caller assertions — adjudicated with evidence: the load path already unconditionally cross-checks manifest-recorded dim/bits against artifact bytes; the sign policy was the only real default-safety hole.
  • Convenience load() (io.rs) routes through the same strict default — no looser side door; verify_for_load stays integrity-only (policy only in open/load, per the round-2 API-split edit).
  • All construction sites migrated (boundary_api suite, hybrid doctest, downstream-smoke, cookbook, arxiv benchmark).

Adversarial 3-lens review: clean (low-only; SignLoadPolicy is deliberately a plain enum like SignPolicy — flagged deviation). Tests: 253 passed workspace all-features (+9 gate tests in tests/sign_load_policy.rs); doctests + clippy/fmt clean.

🤖 Generated with Claude Code

https://claude.ai/code/session_01BQ1JzAocstWiqtCF5J2V7K

DenseLoadOptions drops require_sign: bool for sign: SignLoadPolicy
{RequireIfSupported, Require, Any, Forbid}, defaulting to
RequireIfSupported — a bundle whose (dim, bits) can carry a sign
sidecar must have one to load. Forbid + declared sidecar surfaces as
the new DenseError::SignSidecarForbidden; Require + missing keeps
DenseError::MissingSignSidecar. io::load_bundle applies the same
default so the convenience load() entry points inherit the
strictness (typed errors for that surface arrive in PR2).
verify_for_load stays integrity-only: policy lives in open/load only.
Sign-capable bundle written with SignPolicy::Disabled fails the
default load with MissingSignSidecar; Any loads it; Forbid rejects a
signed bundle with SignSidecarForbidden and accepts an unsigned one;
Require round-trips both outcomes; a sign-incapable bundle (dim=8)
loads fine by default; the convenience load() matches open_verified
defaults on all three bundle shapes.
downstream-smoke, supportsearch, and the arxiv-precomputed benchmark
all pair a SignPolicy::Required write with a SignLoadPolicy::Require
load. supportsearch still cannot build standalone until the ordvec
0.6.0 publish (pre-existing: its lockfile pins crates.io ordvec 0.5.0
and it carries no git patch, unlike the other two consumers).
@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.

@qodo-code-review

Copy link
Copy Markdown

PR Summary by Qodo

Safe-by-default SignLoadPolicy for verified bundle loads

✨ Enhancement 🐞 Bug fix 🧪 Tests 🕐 40+ Minutes

Grey Divider

AI Description

• Replace boolean require_sign with SignLoadPolicy for verified dense loads.
• Default now rejects sign-capable bundles missing sidecar; convenience load() matches.
• Migrate all call sites and add load-gating tests for each policy mode.
Diagram

graph TD
  n1(["OrdinalIndex::load()"] ) --> n2(["io::load_bundle"] ) --> n5{ "SignLoadPolicy" }
  n3(["open_verified"] ) --> n4(["load_verified_bundle"] ) --> n5
  n5 --> n6[("sign sidecar")] --> n8(["OrdinalIndex"] )
  n5 --> n8
  n5 --> n7["DenseError / io::Error"]
  subgraph Legend
    direction LR
    _api(["API entrypoint"]) ~~~ _fn(["Loader function"]) ~~~ _dec{"Policy decision"} ~~~ _data[("Artifact")]
  end
Loading
High-Level Assessment

The following are alternative approaches to this PR:

1. Keep require_sign: bool and change its default to true
  • ➕ Minimal API churn for callers already passing require_sign explicitly
  • ➕ Less new surface area to document
  • ➖ Cannot represent Forbid or RequireIfSupported semantics without additional flags
  • ➖ Bool semantics are ambiguous ("require" vs "prefer"), making policy mistakes easier
2. Move sign-sidecar policy into VerifyOptions (manifest verification layer)
  • ➕ Single place for all verification-related knobs
  • ➕ Potentially reusable across dense/sparse loaders if generalized
  • ➖ Conflates integrity verification with load-time acceptance policy
  • ➖ Harder to keep the I/O convenience load() aligned without threading options everywhere
3. Strict-by-default only for open_verified; keep load() permissive for compatibility
  • ➕ Reduces unexpected breakage for existing users relying on load()
  • ➕ Allows a staged migration to strict defaults
  • ➖ Creates a "loose side door" that undermines the hardening goal
  • ➖ Makes behavior inconsistent across entrypoints, increasing support burden

Recommendation: The enum-based SignLoadPolicy with a safe RequireIfSupported default is the strongest and clearest approach: it encodes all needed states (including Forbid) and removes ambiguity inherent in a bool. Keeping load() aligned with open_verified is also the right hardening tradeoff; the temporary io::Error surface is acceptable given the documented TODO to type it as DenseError later.

Files changed (10) +355 / -40

Enhancement (4) +81 / -23
main.rsMigrate benchmark loader to SignLoadPolicy::Require +2/-2

Migrate benchmark loader to SignLoadPolicy::Require

• Imports SignLoadPolicy and updates DenseLoadOptions to use sign: Require instead of require_sign: true. Keeps expected dim/bits assertions unchanged.

benchmarks/arxiv-precomputed/src/main.rs

index.rsUpdate cookbook consumer to SignLoadPolicy and clarify comments +5/-3

Update cookbook consumer to SignLoadPolicy and clarify comments

• Adds SignLoadPolicy import, updates DenseLoadOptions to sign: Require, and refreshes comment wording to reflect policy-based loading semantics.

cookbook/supportsearch/src/index.rs

main.rsUpdate downstream smoke example to SignLoadPolicy::Require +5/-3

Update downstream smoke example to SignLoadPolicy::Require

• Adds SignLoadPolicy import and migrates DenseLoadOptions from require_sign to sign: Require. Updates explanatory comments to match the new API.

examples/downstream-smoke/src/main.rs

ordinal.rsIntroduce SignLoadPolicy and refactor verified sign-sidecar loading +69/-15

Introduce SignLoadPolicy and refactor verified sign-sidecar loading

• Adds the SignLoadPolicy enum and replaces DenseLoadOptions.require_sign with DenseLoadOptions.sign plus a manual Default implementing RequireIfSupported. Refactors load_verified_bundle to enforce Require/Any/Forbid/RequireIfSupported and adds helper required_sign_path to standardize Missing vs Auxiliary error mapping.

ordinaldb/src/ordinal.rs

Bug fix (2) +24 / -4
error.rsAdd SignSidecarForbidden and tighten sign-sidecar error docs +13/-4

Add SignSidecarForbidden and tighten sign-sidecar error docs

• Introduces DenseError::SignSidecarForbidden for Forbid-mode violations and wires it into Display/Error implementations. Updates Auxiliary and MissingSignSidecar docs to reflect load-policy-driven behavior.

ordinaldb/src/error.rs

io.rsEnforce strict default sign policy in convenience load_bundle() +11/-0

Enforce strict default sign policy in convenience load_bundle()

• Adds a sign-capability gate: if the bundle can carry a sign sidecar and none is present, return InvalidData with guidance to use open_verified + Any. Notes a TODO to return typed DenseError in a follow-up PR.

ordinaldb/src/io.rs

Tests (2) +237 / -8
boundary_api.rsUpdate boundary API tests to use SignLoadPolicy::Require +8/-8

Update boundary API tests to use SignLoadPolicy::Require

• Migrates all verified-load test sites from require_sign: true to sign: SignLoadPolicy::Require, preserving expected dim/bits checks.

ordinaldb/tests/boundary_api.rs

sign_load_policy.rsAdd full matrix tests for SignLoadPolicy and convenience load defaults +229/-0

Add full matrix tests for SignLoadPolicy and convenience load defaults

• Adds new tests covering default RequireIfSupported behavior, Any tolerance, Require strictness, and Forbid rejection/acceptance. Verifies convenience OrdinalIndex::load() matches open_verified defaults across signed/unsigned/sign-incapable bundles.

ordinaldb/tests/sign_load_policy.rs

Documentation (2) +13 / -5
id_map.rsDocument default strict sign-sidecar behavior for IdMapIndex::load +5/-2

Document default strict sign-sidecar behavior for IdMapIndex::load

• Updates IdMapIndex::load docs to state it inherits the default RequireIfSupported behavior and points callers to open_verified + Any for legacy unsigned sign-capable bundles.

ordinaldb/src/id_map.rs

lib.rsExpose SignLoadPolicy and document default verified-load behavior +8/-3

Expose SignLoadPolicy and document default verified-load behavior

• Updates crate-level docs to describe default load options and the strict RequireIfSupported behavior across entrypoints. Re-exports SignLoadPolicy from the public prelude.

ordinaldb/src/lib.rs

@qodo-code-review

Copy link
Copy Markdown

Code Review by Qodo

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

Grey Divider


Remediation recommended

1. Misleading sign-sidecar error 🐞 Bug ◔ Observability
Description
DenseError::MissingSignSidecar is emitted by both verified-load and SignTwoStage search paths, but
its Display text claims it is a verified-load failure, which misleads callers when the error
originates from search APIs.
Code

ordinaldb/src/error.rs[R353-358]

                "invalid query value at query {query_index}, coord {coord_index}: {value}"
            ),
            Self::MissingSignSidecar => f.write_str("verified dense load requires a sign sidecar"),
+            Self::SignSidecarForbidden => f.write_str(
+                "verified dense load forbids a sign sidecar, but the bundle declares one",
+            ),
Relevance

⭐⭐⭐ High

Team previously accepted fixes for misleading/incorrect error reporting and new error variants (PR
#18).

PR-#18

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
The Display implementation hardcodes a verified-load-specific message, but the same error is
returned by the search path when SignTwoStage is requested without a sign sidecar, so the message
will be shown in the wrong context.

ordinaldb/src/error.rs[278-288]
ordinaldb/src/error.rs[332-369]
ordinaldb/src/ordinal.rs[815-821]
ordinaldb/tests/ordinal_index.rs[166-179]

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

## Issue description
`DenseError::MissingSignSidecar` is used in multiple contexts (verified load policy and SignTwoStage search), but its `Display` message is load-specific ("verified dense load requires a sign sidecar"). This makes error output misleading in search contexts and in any downstream code that logs `{err}`.

## Issue Context
- The enum variant is explicitly documented as used by search (`DenseSearchMode::SignTwoStage`) and now also by verified load.
- Tests print the error via `{err}` in a search context, so the message is user-visible.

## Fix Focus Areas
- ordinaldb/src/error.rs[332-370]

## Suggested fix
- Change the `Display` string for `DenseError::MissingSignSidecar` to be context-agnostic, e.g. "missing required sign sidecar" or "operation requires a sign sidecar".
- (Optional) If you want richer diagnostics, consider splitting into two variants (search vs load) or carrying context, but the minimal fix is to generalize the message.

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


2. Stringly typed load error 🐞 Bug ⚙ Maintainability
Description
The convenience load() path enforces the new default sign-sidecar policy but reports a missing
sign sidecar as a generic io::Error with a formatted string, forcing downstream callers to
string-match to distinguish it from other InvalidData failures.
Code

ordinaldb/src/io.rs[R614-626]

+    // This backs the convenience `load()` entry points, which take no
+    // `DenseLoadOptions`; they inherit the same default sign policy as
+    // `open_verified` (`SignLoadPolicy::RequireIfSupported`).
+    // TODO(PR2): type this error surface as `DenseError` instead of an
+    // `InvalidData` `io::Error`.
    let sign = match auxiliary_path(&plan, SIGN_AUX_NAME)? {
        Some(path) => Some(SignBitmap::load(path)?),
+        None if crate::ordinal::sign_compatible(metadata.dim, metadata_bits) => {
+            return Err(invalid_data(
+                "sign-capable bundle (bits=2, dim divisible by 64) has no sign sidecar; \
+                 open it with open_verified and SignLoadPolicy::Any to load without one",
+            ));
+        }
Relevance

⭐⭐ Medium

No precedent for changing load() io::Error to DenseError; current APIs/tests still rely on
InvalidData strings.

PR-#18
PR-#34

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
The convenience loader explicitly returns an InvalidData string error for missing sign sidecars,
and the new tests demonstrate that callers can only assert on ErrorKind and string
contents—confirming the lack of structured error information.

ordinaldb/src/io.rs[577-636]
ordinaldb/tests/sign_load_policy.rs[185-211]

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

## Issue description
`OrdinalIndex::load` / `IdMapIndex::load` fail a sign-capable bundle missing its sign sidecar, but the error is currently returned as `io::ErrorKind::InvalidData` with a string message. This prevents reliable programmatic handling (without brittle substring checks) and creates inconsistency with `open_verified`, which returns a typed `DenseError::MissingSignSidecar`.

## Issue Context
- This new failure mode is part of the PR’s safe-by-default behavior, so it is more likely to be encountered by callers using the convenience APIs.
- The repository already has a TODO noting the error surface should be typed.

## Fix Focus Areas
- ordinaldb/src/io.rs[577-636]
- ordinaldb/tests/sign_load_policy.rs[185-211]

## Suggested fix
- Instead of `invalid_data("...")`, construct an `io::Error` that carries a typed inner error, e.g.:
 - `return Err(std::io::Error::new(std::io::ErrorKind::InvalidData, crate::DenseError::MissingSignSidecar));`
 This keeps the public return type as `io::Result` while allowing callers to downcast via `err.get_ref().and_then(|e| e.downcast_ref::<DenseError>())`.
- Update the test to assert on the downcasted `DenseError::MissingSignSidecar` rather than substring-matching.
- If you prefer a dedicated typed error for convenience loads, introduce a small `LoadError` enum and wrap that instead.

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


Grey Divider

Qodo Logo

Comment thread ordinaldb/src/error.rs
Comment on lines 353 to +358
"invalid query value at query {query_index}, coord {coord_index}: {value}"
),
Self::MissingSignSidecar => f.write_str("verified dense load requires a sign sidecar"),
Self::SignSidecarForbidden => f.write_str(
"verified dense load forbids a sign sidecar, but the bundle declares one",
),

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. Misleading sign-sidecar error 🐞 Bug ◔ Observability

DenseError::MissingSignSidecar is emitted by both verified-load and SignTwoStage search paths, but
its Display text claims it is a verified-load failure, which misleads callers when the error
originates from search APIs.
Agent Prompt
## Issue description
`DenseError::MissingSignSidecar` is used in multiple contexts (verified load policy and SignTwoStage search), but its `Display` message is load-specific ("verified dense load requires a sign sidecar"). This makes error output misleading in search contexts and in any downstream code that logs `{err}`.

## Issue Context
- The enum variant is explicitly documented as used by search (`DenseSearchMode::SignTwoStage`) and now also by verified load.
- Tests print the error via `{err}` in a search context, so the message is user-visible.

## Fix Focus Areas
- ordinaldb/src/error.rs[332-370]

## Suggested fix
- Change the `Display` string for `DenseError::MissingSignSidecar` to be context-agnostic, e.g. "missing required sign sidecar" or "operation requires a sign sidecar".
- (Optional) If you want richer diagnostics, consider splitting into two variants (search vs load) or carrying context, but the minimal fix is to generalize the message.

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

Comment thread ordinaldb/src/io.rs
Comment on lines +614 to +626
// This backs the convenience `load()` entry points, which take no
// `DenseLoadOptions`; they inherit the same default sign policy as
// `open_verified` (`SignLoadPolicy::RequireIfSupported`).
// TODO(PR2): type this error surface as `DenseError` instead of an
// `InvalidData` `io::Error`.
let sign = match auxiliary_path(&plan, SIGN_AUX_NAME)? {
Some(path) => Some(SignBitmap::load(path)?),
None if crate::ordinal::sign_compatible(metadata.dim, metadata_bits) => {
return Err(invalid_data(
"sign-capable bundle (bits=2, dim divisible by 64) has no sign sidecar; \
open it with open_verified and SignLoadPolicy::Any to load without one",
));
}

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

2. Stringly typed load error 🐞 Bug ⚙ Maintainability

The convenience load() path enforces the new default sign-sidecar policy but reports a missing
sign sidecar as a generic io::Error with a formatted string, forcing downstream callers to
string-match to distinguish it from other InvalidData failures.
Agent Prompt
## Issue description
`OrdinalIndex::load` / `IdMapIndex::load` fail a sign-capable bundle missing its sign sidecar, but the error is currently returned as `io::ErrorKind::InvalidData` with a string message. This prevents reliable programmatic handling (without brittle substring checks) and creates inconsistency with `open_verified`, which returns a typed `DenseError::MissingSignSidecar`.

## Issue Context
- This new failure mode is part of the PR’s safe-by-default behavior, so it is more likely to be encountered by callers using the convenience APIs.
- The repository already has a TODO noting the error surface should be typed.

## Fix Focus Areas
- ordinaldb/src/io.rs[577-636]
- ordinaldb/tests/sign_load_policy.rs[185-211]

## Suggested fix
- Instead of `invalid_data("...")`, construct an `io::Error` that carries a typed inner error, e.g.:
  - `return Err(std::io::Error::new(std::io::ErrorKind::InvalidData, crate::DenseError::MissingSignSidecar));`
  This keeps the public return type as `io::Result` while allowing callers to downcast via `err.get_ref().and_then(|e| e.downcast_ref::<DenseError>())`.
- Update the test to assert on the downcasted `DenseError::MissingSignSidecar` rather than substring-matching.
- If you prefer a dedicated typed error for convenience loads, introduce a small `LoadError` enum and wrap that instead.

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

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