[pitboss] phase 01: Track L.0 — FrameworkAdapter trait + per-lang dispatch table

This commit is contained in:
pitboss 2026-05-17 14:29:14 -05:00
parent 4a2acf1bf9
commit 16834a6e7c
30 changed files with 456 additions and 0 deletions

View file

@ -1642,6 +1642,7 @@ mod tests {
spec_hash: "0000000000000000".to_owned(),
derivation: SpecDerivationStrategy::FromFlowSteps,
stubs_required: vec![],
framework: None,
}
}

View file

@ -1176,6 +1176,7 @@ mod tests {
spec_hash: "test0000abcd1234".into(),
derivation: SpecDerivationStrategy::FromFlowSteps,
stubs_required: vec![],
framework: None,
}
}

View file

@ -0,0 +1,280 @@
//! Framework adapter abstraction (Track L.0).
//!
//! Replaces the ad-hoc per-language route / `main` detection that was
//! scattered across [`crate::dynamic::lang`] sub-modules with a single
//! dispatching trait. Every later phase in Track L plugs a concrete
//! adapter (Flask, Spring, Express, axum, …) into this trait.
//!
//! # Determinism
//!
//! [`detect_binding`] iterates the per-language adapter slice returned
//! by [`registry::adapters_for`] in registration order and returns the
//! first non-`None` match. The registration order is fixed at
//! compile time and kept sorted by [`FrameworkAdapter::name`] so a
//! phase that adds a new adapter cannot silently re-order an existing
//! match.
pub mod registry;
use crate::evidence::EntryKind;
use crate::summary::FuncSummary;
use crate::symbol::Lang;
use serde::{Deserialize, Serialize};
/// HTTP method recognised by route bindings. Mirrors
/// [`crate::entry_points::HttpMethod`] but is re-declared here so the
/// framework module does not pull in the static-analysis entry-point
/// types in callers that only need the dynamic-side shape.
pub use crate::entry_points::HttpMethod;
/// HTTP route shape extracted from a framework binding (path +
/// method). Only populated when [`FrameworkBinding::kind`] is
/// [`EntryKind::HttpRoute`].
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct RouteShape {
/// HTTP verb (`GET`, `POST`, …).
pub method: HttpMethod,
/// Route path template as registered with the framework (e.g.
/// `"/users/{id}"`). Adapter-specific placeholder syntax is
/// preserved verbatim.
pub path: String,
}
/// Where on the external surface a function formal originates from.
///
/// Adapters classify each declared parameter into one of these
/// buckets so downstream harness emitters know which request field
/// carries the payload.
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum ParamSource {
/// URL path placeholder (e.g. `/users/{id}` → `id`).
PathSegment(String),
/// URL query string parameter.
QueryParam(String),
/// HTTP request header.
Header(String),
/// JSON request body (deserialised whole).
JsonBody,
/// HTML form field.
FormField(String),
/// HTTP cookie.
Cookie(String),
/// Implicit context object (e.g. `*gin.Context`, `HttpRequest`).
/// Not adversary-controlled directly; included so the binding
/// captures every formal position.
Implicit,
}
/// Binding between a function formal and its external request slot.
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct ParamBinding {
/// 0-based position in [`FuncSummary::param_names`].
pub index: usize,
/// Declared parameter name (mirrors
/// `summary.param_names[index]`).
pub name: String,
/// External slot this parameter is wired to.
pub source: ParamSource,
}
/// Shape of how the handler writes a response. Track L plans to use
/// this to pick the right oracle (HTML render → XSS, JSON → no-op,
/// redirect → open-redirect).
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct ResponseShape {
/// Response media kind.
pub kind: ResponseKind,
}
/// Coarse classification of a response writer's output.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum ResponseKind {
Json,
Html,
Text,
Redirect,
Stream,
}
/// Middleware attached to a route (auth filter, CSRF guard,
/// before-action, decorator chain, …). Adapters record the name so
/// later phases can classify it.
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct MiddlewareShape {
/// Adapter-local middleware identifier (e.g. `"login_required"`,
/// `"@PreAuthorize"`, `"csrf"`).
pub name: String,
}
/// Full framework binding for a function: every detail about how an
/// external surface reaches the function body.
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct FrameworkBinding {
/// Stable id of the adapter that produced this binding. Equal to
/// the originating [`FrameworkAdapter::name`]. Persisted into
/// trace details verbatim.
pub adapter: String,
/// Entry-surface taxonomy bucket this function falls into.
pub kind: EntryKind,
/// HTTP route shape when [`Self::kind`] is
/// [`EntryKind::HttpRoute`].
#[serde(default, skip_serializing_if = "Option::is_none")]
pub route: Option<RouteShape>,
/// Per-formal external-slot classification. May be empty if the
/// adapter does not yet model parameter shapes (e.g. a Phase-01
/// stub).
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub request_params: Vec<ParamBinding>,
/// Response writer shape, when the adapter can determine it.
#[serde(default, skip_serializing_if = "Option::is_none")]
pub response_writer: Option<ResponseShape>,
/// Middleware chain attached to the route, in declaration order.
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub middleware: Vec<MiddlewareShape>,
}
/// Per-framework adapter trait. Each implementation inspects a
/// function (via its [`FuncSummary`] and the file's AST root) and
/// decides whether the function is bound to an external entry
/// surface.
///
/// Implementations live next to the per-language harness emitters in
/// [`crate::dynamic::lang`] and register into [`registry::adapters_for`]
/// in subsequent Track-L phases. Phase 01 ships the trait and an
/// empty registry per language.
pub trait FrameworkAdapter: Sync {
/// Stable adapter id (e.g. `"flask"`, `"spring-mvc"`, `"axum"`).
/// Used for deterministic ordering inside the registry and for
/// the trace-event detail string emitted by the verifier.
fn name(&self) -> &'static str;
/// Language this adapter targets.
fn lang(&self) -> Lang;
/// Inspect a function and return its [`FrameworkBinding`] when
/// the function is driven by this adapter, otherwise `None`.
///
/// `ast` is the file's tree-sitter root node and `file_bytes` is
/// the raw source so adapters can re-walk for decorators,
/// routing macros, or registration sites that the
/// [`FuncSummary`] alone does not preserve.
fn detect(
&self,
summary: &FuncSummary,
ast: tree_sitter::Node<'_>,
file_bytes: &[u8],
) -> Option<FrameworkBinding>;
}
/// Walk every adapter registered for `lang` in registration order
/// and return the first non-`None` binding. Returns `None` when no
/// adapter matches or when no adapters are registered for `lang`.
pub fn detect_binding(
summary: &FuncSummary,
ast: tree_sitter::Node<'_>,
file_bytes: &[u8],
lang: Lang,
) -> Option<FrameworkBinding> {
for adapter in registry::adapters_for(lang) {
debug_assert_eq!(
adapter.lang(),
lang,
"adapter '{}' registered under wrong lang",
adapter.name()
);
if let Some(binding) = adapter.detect(summary, ast, file_bytes) {
return Some(binding);
}
}
None
}
#[cfg(test)]
mod tests {
use super::*;
use crate::summary::FuncSummary;
fn synth_summary(name: &str, lang: &str) -> FuncSummary {
FuncSummary {
name: name.into(),
file_path: "tests/synthetic.rs".into(),
lang: lang.into(),
..Default::default()
}
}
fn parse_python(src: &[u8]) -> tree_sitter::Tree {
let mut parser = tree_sitter::Parser::new();
let lang = tree_sitter::Language::from(tree_sitter_python::LANGUAGE);
parser.set_language(&lang).unwrap();
parser.parse(src, None).unwrap()
}
#[test]
fn registry_is_empty_for_every_lang_phase_01() {
// Regression guard: Phase 01 ships the trait + dispatch
// machinery but registers zero adapters. Subsequent Track-L
// phases register concrete adapters per language; this test
// documents the starting baseline so accidental re-ordering
// is caught by `tests/determinism_audit.rs`.
for lang in [
Lang::Rust,
Lang::C,
Lang::Cpp,
Lang::Java,
Lang::Go,
Lang::Php,
Lang::Python,
Lang::Ruby,
Lang::TypeScript,
Lang::JavaScript,
] {
assert!(
registry::adapters_for(lang).is_empty(),
"{:?} starts with zero registered adapters",
lang
);
}
}
#[test]
fn detect_binding_returns_none_with_empty_registry() {
// Empty registry means `detect_binding` short-circuits to
// `None` for every input regardless of summary content.
let summary = synth_summary("handler", "python");
let src: &[u8] = b"def handler():\n pass\n";
let tree = parse_python(src);
let binding = detect_binding(&summary, tree.root_node(), src, Lang::Python);
assert!(binding.is_none());
}
#[test]
fn framework_binding_round_trips_through_serde() {
// The binding is persisted into repro bundles; ensure every
// field round-trips.
let original = FrameworkBinding {
adapter: "flask".into(),
kind: EntryKind::HttpRoute,
route: Some(RouteShape {
method: HttpMethod::POST,
path: "/users/{id}".into(),
}),
request_params: vec![ParamBinding {
index: 0,
name: "id".into(),
source: ParamSource::PathSegment("id".into()),
}],
response_writer: Some(ResponseShape {
kind: ResponseKind::Json,
}),
middleware: vec![MiddlewareShape {
name: "login_required".into(),
}],
};
let json = serde_json::to_string(&original).unwrap();
let parsed: FrameworkBinding = serde_json::from_str(&json).unwrap();
assert_eq!(parsed, original);
}
}

View file

@ -0,0 +1,53 @@
//! Per-language [`super::FrameworkAdapter`] dispatch table.
//!
//! Phase 01 (Track L.0) ships an empty table for every language; the
//! [`super::FrameworkAdapter`] trait, [`super::FrameworkBinding`] data
//! shape, and the [`super::detect_binding`] dispatcher are wired
//! through so subsequent Track-L phases only need to register a
//! concrete adapter here.
//!
//! # Ordering contract
//!
//! Within each `static` slice, adapters must be listed in alphabetical
//! order of [`super::FrameworkAdapter::name`]. The lexical ordering
//! gives a deterministic first-match result that survives merges /
//! rebases without subtle re-ordering bugs. A `framework` unit test
//! ([`super::tests::registry_is_empty_for_every_lang_phase_01`])
//! captures the Phase-01 starting baseline so a phase that registers
//! its first adapter is forced to update both the slice *and* the
//! regression guard in the same change.
use super::FrameworkAdapter;
use crate::symbol::Lang;
/// Adapters registered for `lang`, returned in deterministic
/// first-match order. Returns an empty slice for languages that have
/// no adapters registered yet.
pub fn adapters_for(lang: Lang) -> &'static [&'static dyn FrameworkAdapter] {
match lang {
Lang::Rust => RUST,
Lang::C => C,
Lang::Cpp => CPP,
Lang::Java => JAVA,
Lang::Go => GO,
Lang::Php => PHP,
Lang::Python => PYTHON,
Lang::Ruby => RUBY,
Lang::TypeScript => TYPESCRIPT,
Lang::JavaScript => JAVASCRIPT,
}
}
// All slices intentionally empty in Phase 01. Later Track-L phases
// register concrete adapters (Flask, Spring, axum, Express, …) into
// the appropriate language slice.
static RUST: &[&dyn FrameworkAdapter] = &[];
static C: &[&dyn FrameworkAdapter] = &[];
static CPP: &[&dyn FrameworkAdapter] = &[];
static JAVA: &[&dyn FrameworkAdapter] = &[];
static GO: &[&dyn FrameworkAdapter] = &[];
static PHP: &[&dyn FrameworkAdapter] = &[];
static PYTHON: &[&dyn FrameworkAdapter] = &[];
static RUBY: &[&dyn FrameworkAdapter] = &[];
static TYPESCRIPT: &[&dyn FrameworkAdapter] = &[];
static JAVASCRIPT: &[&dyn FrameworkAdapter] = &[];

View file

@ -200,6 +200,7 @@ mod tests {
spec_hash: "0000000000000000".into(),
derivation: crate::dynamic::spec::SpecDerivationStrategy::FromFlowSteps,
stubs_required: vec![],
framework: None,
};
let err = build(&spec).unwrap_err();
assert!(matches!(err, HarnessError::Unsupported(_)));
@ -222,6 +223,7 @@ mod tests {
spec_hash: "test0000abcd1234".into(),
derivation: crate::dynamic::spec::SpecDerivationStrategy::FromFlowSteps,
stubs_required: vec![],
framework: None,
};
let harness = build(&spec).unwrap();
assert!(harness.workdir.join("harness.py").exists());

View file

@ -666,6 +666,7 @@ mod tests {
spec_hash: "ctest0000000001".into(),
derivation: crate::dynamic::spec::SpecDerivationStrategy::FromFlowSteps,
stubs_required: vec![],
framework: None,
}
}

View file

@ -583,6 +583,7 @@ mod tests {
spec_hash: "cpptest00000001".into(),
derivation: crate::dynamic::spec::SpecDerivationStrategy::FromFlowSteps,
stubs_required: vec![],
framework: None,
}
}

View file

@ -764,6 +764,7 @@ mod tests {
spec_hash: "go0000000000001".into(),
derivation: crate::dynamic::spec::SpecDerivationStrategy::FromFlowSteps,
stubs_required: vec![],
framework: None,
}
}

View file

@ -998,6 +998,7 @@ mod tests {
spec_hash: "java00000000001".into(),
derivation: crate::dynamic::spec::SpecDerivationStrategy::FromFlowSteps,
stubs_required: vec![],
framework: None,
}
}

View file

@ -81,6 +81,7 @@ mod tests {
spec_hash: "js000000000001".into(),
derivation: crate::dynamic::spec::SpecDerivationStrategy::FromFlowSteps,
stubs_required: vec![],
framework: None,
}
}

View file

@ -990,6 +990,7 @@ mod tests {
spec_hash: "jsshared00000001".into(),
derivation: crate::dynamic::spec::SpecDerivationStrategy::FromFlowSteps,
stubs_required: vec![],
framework: None,
}
}

View file

@ -606,6 +606,7 @@ mod tests {
spec_hash: "php0000000000001".into(),
derivation: crate::dynamic::spec::SpecDerivationStrategy::FromFlowSteps,
stubs_required: vec![],
framework: None,
}
}

View file

@ -1213,6 +1213,7 @@ mod tests {
spec_hash: "00000000deadbeef".into(),
derivation: crate::dynamic::spec::SpecDerivationStrategy::FromFlowSteps,
stubs_required: vec![],
framework: None,
}
}

View file

@ -685,6 +685,7 @@ mod tests {
spec_hash: "rb000000000001".into(),
derivation: crate::dynamic::spec::SpecDerivationStrategy::FromFlowSteps,
stubs_required: vec![],
framework: None,
}
}

View file

@ -797,6 +797,7 @@ mod tests {
spec_hash: "rusttest00000001".into(),
derivation: crate::dynamic::spec::SpecDerivationStrategy::FromFlowSteps,
stubs_required: vec![],
framework: None,
}
}

View file

@ -79,6 +79,7 @@ mod tests {
spec_hash: "ts000000000001ab".into(),
derivation: SpecDerivationStrategy::FromFlowSteps,
stubs_required: vec![],
framework: None,
}
}

View file

@ -69,6 +69,7 @@ pub mod build_sandbox;
pub mod corpus;
pub mod differential;
pub mod environment;
pub mod framework;
pub mod harness;
pub mod lang;
pub mod mount_filter;

View file

@ -680,6 +680,7 @@ mod tests {
spec_hash: "cafecafecafe0001".into(),
derivation: crate::dynamic::spec::SpecDerivationStrategy::FromFlowSteps,
stubs_required: vec![],
framework: None,
}
}

View file

@ -20,6 +20,7 @@
use crate::callgraph::{CallGraph, CallGraphAnalysis};
use crate::commands::scan::Diag;
use crate::dynamic::corpus::CORPUS_VERSION;
use crate::dynamic::framework::FrameworkBinding;
use crate::dynamic::stubs::StubKind;
use crate::evidence::{Confidence, FlowStepKind, UnsupportedReason};
use crate::labels::Cap;
@ -124,6 +125,25 @@ pub struct HarnessSpec {
/// the cache deserialise as an empty list.
#[serde(default)]
pub stubs_required: Vec<StubKind>,
/// Track L.0 — framework binding recovered for the entry function
/// (route shape, request slots, response writer, middleware chain).
///
/// Populated by [`crate::dynamic::framework::detect_binding`] when
/// a registered [`crate::dynamic::framework::FrameworkAdapter`]
/// matches the resolved entry; `None` when no adapter matches or
/// when the spec-derivation path lacks the AST context required
/// to dispatch. Phase 01 ships with an empty adapter registry so
/// this field is `None` for every spec; subsequent Track-L phases
/// register adapters and back-fill the binding.
///
/// Excluded from [`compute_spec_hash`]: the binding is descriptive
/// metadata derived from the entry function and does not change
/// the harness boundary topology that the spec hash protects.
/// `#[serde(default, skip_serializing_if = "Option::is_none")]` so
/// pre-Phase-01 serialised specs deserialise unchanged and an
/// absent binding does not bloat repro-bundle JSON.
#[serde(default, skip_serializing_if = "Option::is_none")]
pub framework: Option<FrameworkBinding>,
}
fn default_derivation_strategy() -> SpecDerivationStrategy {
@ -1054,11 +1074,51 @@ fn finalize_spec(
spec_hash: String::new(),
derivation,
stubs_required,
// Phase 01 (Track L.0): the framework adapter registry is
// empty, so leave the binding unpopulated. Subsequent phases
// back-fill via `attach_framework_binding` once the spec's
// entry has been resolved and an AST is available.
framework: None,
};
attach_framework_binding(&mut spec);
spec.spec_hash = compute_spec_hash(&spec);
spec
}
/// Dispatch the resolved entry function through
/// [`crate::dynamic::framework::detect_binding`] and stash the result
/// on [`HarnessSpec::framework`].
///
/// Invoked unconditionally at the tail of [`finalize_spec`] so every
/// strategy ([`SpecDerivationStrategy::FromFlowSteps`] …
/// [`SpecDerivationStrategy::FromCallgraphEntry`]) benefits without
/// per-strategy plumbing.
///
/// # Phase 01 contract
///
/// The framework adapter registry is empty in Phase 01, so this
/// function fast-paths to a no-op when
/// [`crate::dynamic::framework::registry::adapters_for`] returns an
/// empty slice. That avoids parsing the entry file from disk in the
/// common (empty) case and keeps the spec-derivation path side-effect
/// free. Subsequent Track-L phases that register concrete adapters
/// also extend this function to parse `spec.entry_file` and call
/// [`crate::dynamic::framework::detect_binding`] with the resulting
/// tree-sitter root.
fn attach_framework_binding(spec: &mut HarnessSpec) {
if crate::dynamic::framework::registry::adapters_for(spec.lang).is_empty() {
return;
}
// Phase-01 stub. When Track L.1+ registers its first adapter,
// this branch will (a) read `spec.entry_file` via
// `std::fs::read`, (b) parse with the language's tree-sitter
// grammar, (c) construct a `FuncSummary` from `spec` + the
// matching summary index, and (d) call
// `crate::dynamic::framework::detect_binding`. Left empty here
// because Phase 01 ships zero adapters and the verifier's
// acceptance test demands byte-identical verdicts.
}
/// Walk `flow_steps` and return the entry point: the enclosing function of
/// the first `Source` step that has a function annotation. This is the
/// outermost callable that receives the tainted input.
@ -1331,6 +1391,7 @@ mod tests {
spec_hash: String::new(),
derivation: SpecDerivationStrategy::FromFlowSteps,
stubs_required: vec![],
framework: None,
};
spec.spec_hash = compute_spec_hash(&spec);
spec

View file

@ -640,6 +640,7 @@ mod tests {
spec_hash: "abcd1234abcd1234".into(),
derivation: crate::dynamic::spec::SpecDerivationStrategy::FromFlowSteps,
stubs_required: vec![],
framework: None,
}
}

View file

@ -40,6 +40,15 @@ use std::sync::Mutex;
pub enum TraceStage {
SpecStarted,
SpecDone,
/// Track L.0 — a [`crate::dynamic::framework::FrameworkAdapter`]
/// claimed the spec's entry function. `detail` carries the
/// adapter name verbatim (e.g. `"flask"`, `"spring-mvc"`).
FrameworkAdapterDetected,
/// Track L.0 — no registered adapter matched the spec's entry
/// function. Emitted alongside [`Self::SpecDone`] for every spec
/// so a trace consumer can audit framework-detection coverage by
/// counting `framework_adapter_*` events.
FrameworkAdapterNone,
BuildStarted,
BuildDone,
SandboxStarted,
@ -56,6 +65,8 @@ impl TraceStage {
match self {
Self::SpecStarted => "spec_started",
Self::SpecDone => "spec_done",
Self::FrameworkAdapterDetected => "framework_adapter_detected",
Self::FrameworkAdapterNone => "framework_adapter_none",
Self::BuildStarted => "build_started",
Self::BuildDone => "build_done",
Self::SandboxStarted => "sandbox_started",

View file

@ -572,6 +572,25 @@ pub fn verify_finding(diag: &Diag, opts: &VerifyOptions) -> VerifyResult {
spec.spec_hash, spec.lang, spec.entry_kind
)),
);
// Track L.0: surface framework-adapter dispatch outcome to the
// trace so operators (and the Phase 30 determinism audit) can see
// whether an adapter claimed the entry function. Phase 01 always
// emits the `None` variant because the adapter registry is empty;
// subsequent Track-L phases register adapters and switch the
// event to `Detected` with the adapter name in `detail`.
match &spec.framework {
Some(binding) => trace.record(
crate::dynamic::trace::TraceStage::FrameworkAdapterDetected,
Some(format!(
"adapter={} kind={:?}",
binding.adapter, binding.kind
)),
),
None => trace.record(
crate::dynamic::trace::TraceStage::FrameworkAdapterNone,
Some(format!("lang={:?} entry={}", spec.lang, spec.entry_name)),
),
}
// Pre-flight gate: surface a structured `Inconclusive(EntryKindUnsupported)`
// up-front when the spec's [`EntryKind`] is not in the lang emitter's

View file

@ -491,6 +491,7 @@ pub fn run_shape_fixture_lang(
spec_hash: spec_hash.clone(),
derivation: SpecDerivationStrategy::FromFlowSteps,
stubs_required: vec![],
framework: None,
};
// Phase 14: Java shape fixtures bundle annotation / type stubs as
@ -785,6 +786,7 @@ pub fn run_harness_snapshot_lang(
spec_hash: "snapshotsnapshot".into(),
derivation: SpecDerivationStrategy::FromFlowSteps,
stubs_required: vec![],
framework: None,
};
let harness = lang_emit::emit(&spec).expect("emitter must produce a harness");

View file

@ -58,6 +58,7 @@ fn flask_spec(entry_rel: &str) -> HarnessSpec {
spec_hash: "phase09testabcd1".into(),
derivation: SpecDerivationStrategy::FromCallgraphEntry,
stubs_required: vec![],
framework: None,
}
}

View file

@ -744,6 +744,7 @@ public class App {
spec_hash: "phase14staging00".into(),
derivation: SpecDerivationStrategy::FromFlowSteps,
stubs_required: vec![],
framework: None,
};
let captured = capture_project_dependencies(project_root.path(), &spec);

View file

@ -364,6 +364,7 @@ mod e2e_phase_08 {
spec_hash: spec_hash.clone(),
derivation: SpecDerivationStrategy::FromFlowSteps,
stubs_required: vec![],
framework: None,
};
(spec, tmp)

View file

@ -35,6 +35,7 @@ mod repro_determinism_tests {
spec_hash: spec_hash.to_owned(),
derivation: nyx_scanner::dynamic::spec::SpecDerivationStrategy::FromFlowSteps,
stubs_required: vec![],
framework: None,
}
}
@ -172,6 +173,7 @@ mod repro_determinism_tests {
spec_hash: spec_hash.to_owned(),
derivation: nyx_scanner::dynamic::spec::SpecDerivationStrategy::FromFlowSteps,
stubs_required: vec![],
framework: None,
}
}
@ -304,6 +306,7 @@ fn main() {
spec_hash: spec_hash.to_owned(),
derivation: nyx_scanner::dynamic::spec::SpecDerivationStrategy::FromFlowSteps,
stubs_required: vec![],
framework: None,
}
}
@ -359,6 +362,7 @@ fn main() {
spec_hash: spec_hash.to_owned(),
derivation: nyx_scanner::dynamic::spec::SpecDerivationStrategy::FromFlowSteps,
stubs_required: vec![],
framework: None,
}
}
@ -414,6 +418,7 @@ fn main() {
spec_hash: spec_hash.to_owned(),
derivation: nyx_scanner::dynamic::spec::SpecDerivationStrategy::FromFlowSteps,
stubs_required: vec![],
framework: None,
}
}
@ -469,6 +474,7 @@ fn main() {
spec_hash: spec_hash.to_owned(),
derivation: nyx_scanner::dynamic::spec::SpecDerivationStrategy::FromFlowSteps,
stubs_required: vec![],
framework: None,
}
}

View file

@ -97,6 +97,7 @@ fn flask_eval_spec() -> HarnessSpec {
spec_hash: FLASK_EVAL_SPEC_HASH.into(),
derivation: SpecDerivationStrategy::FromFlowSteps,
stubs_required: vec![],
framework: None,
}
}

View file

@ -54,6 +54,7 @@ mod repro_hermetic_tests {
spec_hash: "hermetic00000001".into(),
derivation: nyx_scanner::dynamic::spec::SpecDerivationStrategy::FromFlowSteps,
stubs_required: vec![],
framework: None,
}
}

View file

@ -41,6 +41,7 @@ fn make_spec(hash: &str) -> HarnessSpec {
spec_hash: hash.into(),
derivation: SpecDerivationStrategy::FromFlowSteps,
stubs_required: vec![],
framework: None,
}
}