parlov-elicit 0.5.0

Elicitation engine: strategy selection and probe plan generation for parlov.
Documentation
# parlov-elicit

Elicitation engine for parlov. Given a target endpoint and operator context, generates a plan of `ProbeSpec`s designed to trigger specific server-side differentials that reveal resource existence.

## Overview

The engine codifies the [elicitation playbook](../../docs/existenceOracle/elicitationPlaybook.md) as 39 composable strategies across four detection vectors. Each strategy targets a different layer of the HTTP pipeline — content negotiation, conditional headers, cache validation, auth, payload validation, rate limiting — and produces probe definitions with `Technique` metadata that the binary feeds into its execution loops.

A second `generate_dag_chained_plan(ctx, exchanges, registry)` path walks a typed Producer/Consumer DAG to emit Phase-2 chained specs whose construction depends on Phase-1 observations (e.g. follow a baseline `Location` redirect, replay an `ETag` validator, substitute extracted resource IDs). Seven chain families ship in `default_chain_registry`: redirect-location (B1), ETag validators (B2), Content-Range size (B3), resource-ID (B4), Problem-Details (B5), Content-Type negotiation (C7), and auth-challenge informed (C8). Phase-2 specs carry `ChainProvenance` so each derived finding can be traced back to the harvested signal that produced it.

## Usage

```rust
use parlov_elicit::{generate_plan, RiskLevel, ScanContext};
use http::HeaderMap;

let ctx = ScanContext {
    target: "https://api.example.com/users/{id}".to_string(),
    baseline_id: "1001".to_string(),
    probe_id: "9999".to_string(),
    headers: HeaderMap::new(),
    max_risk: RiskLevel::Safe,
    known_duplicate: None,
    state_field: None,
    alt_credential: None,
};

let plan = generate_plan(&ctx);
// plan contains ProbeSpec::Pair, ::Burst, and ::HeaderDiff items
// ready for dispatch by the binary's scan pipeline
```

## Strategies

### Status Code Diff (`Vector::StatusCodeDiff`)

| # | ID | Risk | Methods | Prereq |
|---|---|---|---|---|
| 1 | `accept-elicit` | Safe | GET, HEAD | — |
| 2 | `if-none-match-elicit` | Safe | GET, HEAD | — |
| 3 | `if-match-read-elicit` | Safe | GET, HEAD | — |
| 4 | `trailing-slash-elicit` | Safe | GET, HEAD | — |
| 5 | `case-normalize-elicit` | Safe | GET, HEAD | — |
| 6 | `auth-strip-elicit` | Safe | GET, HEAD | `Authorization` header |
| 7 | `low-privilege-elicit` | Safe | GET, HEAD | `Authorization` header |
| 8 | `scope-manipulation-elicit` | Safe | GET, HEAD | `alt_credential` |
| 9 | `rate-limit-headers-elicit` | Safe | GET, HEAD | — |
| 10 | `content-type-elicit` | MethodDestructive | PUT, PATCH | — |
| 11 | `if-match-elicit` | MethodDestructive | PUT, PATCH, DELETE | — |
| 12 | `empty-body-elicit` | MethodDestructive | POST, PUT, PATCH | — |
| 13 | `state-transition-elicit` | MethodDestructive | PATCH, PUT | `state_field` |
| 14 | `uniqueness-elicit` | OperationDestructive | POST, PUT | `known_duplicate` |
| 15 | `dependency-delete-elicit` | OperationDestructive | DELETE | — |
| 16 | `rate-limit-burst-elicit` | OperationDestructive | GET, HEAD | — |

### Cache Probing (`Vector::CacheProbing`)

| # | ID | Risk | Methods | Prereq |
|---|---|---|---|---|
| 17 | `cp-if-none-match` | Safe | GET, HEAD | — |
| 18 | `cp-if-modified-since` | Safe | GET, HEAD | — |
| 19 | `cp-if-match` | Safe | GET, HEAD | — |
| 20 | `cp-if-unmodified-since` | Safe | GET, HEAD | — |
| 21 | `cp-range-satisfiable` | Safe | GET | — |
| 22 | `cp-range-unsatisfiable` | Safe | GET | — |
| 23 | `cp-if-range` | Safe | GET | — |
| 24 | `cp-accept` | Safe | GET, HEAD | — |

### Error Message Granularity (`Vector::ErrorMessageGranularity`)

| # | ID | Risk | Methods | Prereq |
|---|---|---|---|---|
| 25 | `emg-bola` | Safe | GET | — |
| 26 | `emg-query-validation` | Safe | GET | — |
| 27 | `emg-app-vs-server-404` | Safe | GET | — |
| 28 | `emg-schema-validation-patch` | MethodDestructive | PATCH | — |
| 29 | `emg-schema-validation-put` | MethodDestructive | PUT | — |
| 30 | `emg-state-conflict` | MethodDestructive | POST | — |
| 31 | `emg-fk-violation` | MethodDestructive | DELETE | — |

### Redirect Diff (`Vector::RedirectDiff`)

| # | ID | Risk | Methods | Prereq |
|---|---|---|---|---|
| 32 | `rd-slash-append` | Safe | GET, HEAD | — |
| 33 | `rd-slash-strip` | Safe | GET, HEAD | — |
| 34 | `rd-case-variation` | Safe | GET, HEAD | — |
| 35 | `rd-double-slash` | Safe | GET, HEAD | — |
| 36 | `rd-percent-encoding` | Safe | GET, HEAD | — |
| 37 | `rd-protocol-upgrade` | Safe | GET, HEAD | — |
| 38 | `rd-post-to-303` | MethodDestructive | POST | — |
| 39 | `rd-put-to-303` | MethodDestructive | PUT | — |

## Design

- **Pure computation** — no I/O, no async. The binary owns the async boundary.
- **Strategy as trait** — adding a strategy is one file + one registry line.
- **Four detection vectors** — `StatusCodeDiff` (status code differentials), `CacheProbing` (cache-conditional header responses), `ErrorMessageGranularity` (response body differentials), and `RedirectDiff` (3xx vs non-3xx redirect differentials). Strategies are organized by vector under `existence/status_code_diff/`, `existence/cache_probing/`, `existence/error_message_granularity/`, and `existence/redirect_diff/`.
- **`Technique` metadata** — every `ProbeSpec` carries technique context (id, name, vector, normative strength) that flows through execution into analysis. Signal extraction is unconditional.
- **`ProbeSpec` variants** drive dispatch in the binary: `Pair` → adaptive loop, `Burst` → volume loop, `HeaderDiff` → single-request header comparison.
- **`RiskLevel` uses `Ord`** — `generate_plan` filters with `risk() <= ctx.max_risk`.