feat(adr-118/p6.12): examples/bfld_minimal.rs operator quickstart (315/315 GREEN)
Iter 47. Ships the operator-facing quickstart as doc-as-code. Three
goals:
1. New operators reading the crate get a 50-line working example
instead of having to assemble pipeline + config + hasher + inputs
+ embedding + JSON publish themselves.
2. CI proves the example COMPILES and RUNS end-to-end via a
separate test that re-executes the same flow inline.
3. The example output is the canonical BfldEvent JSON, demonstrating
every documented field (presence/motion/count/conf/zone/class/
identity_risk_score/rf_signature_hash) for a typical Anonymous
class publish.
Added:
- v2/crates/wifi-densepose-bfld/examples/bfld_minimal.rs (~70 LOC):
* Per-site secret salt
* BfldPipeline::new(BfldConfig::new(...).with_signature_hasher(...))
* SensingInputs with low-risk factors so the gate emits
* IdentityEmbedding from a deterministic ramp
* pipeline.process(...).ok_or(...) for the gate-drop case
* event.to_json() printed to stdout
* Run command in the doc comment:
cargo run -p wifi-densepose-bfld --example bfld_minimal
- v2/crates/wifi-densepose-bfld/tests/example_minimal.rs (4 tests):
minimal_example_documents_the_operator_quickstart_flow
(asserts file contains BfldPipeline, SignatureHasher,
SensingInputs, IdentityEmbedding, BfldConfig, .process(,
to_json — catches doc drift if the example removes a key
symbol)
minimal_example_carries_run_instructions_in_doc_comments
(the cargo run --example line must be present)
minimal_example_flow_produces_valid_json_with_documented_fields
*** Re-runs the example flow inline and asserts every
documented JSON field appears in the output ***
example_returns_box_dyn_error_for_main_signature
(canonical Rust-example main signature)
- v2/crates/wifi-densepose-bfld/Cargo.toml:
[[example]] name = "bfld_minimal", required-features = ["serde-json"]
so `cargo test --no-default-features` doesn't try to build the
example (which needs to_json gated on serde-json).
Example run output (sanity check before commit):
{"type":"bfld_update","node_id":"seed-example","timestamp_ns":...,
"presence":true,"motion":0.42,"person_count":1,"confidence":0.91,
"privacy_class":"anonymous","identity_risk_score":0.0016000001,
"rf_signature_hash":"blake3:cc3615c7aaab9d0867a0c15327444b8f...bf"}
ADR-124 status (iter step 0 sibling check):
- docs/adr/ADR-124-rvagent-mcp-ruvector-npm-integration.md unchanged
at 431 lines. SENSE-BRIDGE scope remains orthogonal.
ACs progressed:
- ADR-118 §2.1 documentation surface — first operator-facing example
shipped as part of the crate. Discoverable via
`cargo run --example bfld_minimal` and verified via cargo test.
Test config:
- cargo test --no-default-features → 101 passed (example_minimal cfg-out)
- cargo test → 315 passed (311 + 4 example_minimal)
Out of scope (next iter target):
- PR-readiness pivot still pending: CHANGELOG, witness bundle,
AC closeout table. External-resource-gated work still skipped.
Co-Authored-By: claude-flow <ruv@ruv.net>
This commit is contained in:
parent
354829ec81
commit
ea7b5711a1
|
|
@ -39,6 +39,13 @@ rumqttc = { version = "0.24", default-features = false, features = ["use-rustls"
|
|||
[dev-dependencies]
|
||||
proptest.workspace = true
|
||||
|
||||
# The minimal example uses BfldEvent::to_json(), which is gated on serde-json.
|
||||
# Without this declaration, `cargo test --no-default-features` tries to build
|
||||
# the example and fails on the missing to_json() method.
|
||||
[[example]]
|
||||
name = "bfld_minimal"
|
||||
required-features = ["serde-json"]
|
||||
|
||||
[lints.rust]
|
||||
unsafe_code = "forbid"
|
||||
missing_docs = "warn"
|
||||
|
|
|
|||
|
|
@ -0,0 +1,70 @@
|
|||
//! Minimal end-to-end BFLD pipeline example. Demonstrates the operator-facing
|
||||
//! flow: construct a `BfldPipeline` with a `SignatureHasher`, feed one
|
||||
//! `SensingInputs` + `IdentityEmbedding`, and print the resulting privacy-
|
||||
//! gated `BfldEvent` as JSON.
|
||||
//!
|
||||
//! Run with:
|
||||
//! ```sh
|
||||
//! cargo run -p wifi-densepose-bfld --example bfld_minimal
|
||||
//! ```
|
||||
//!
|
||||
//! Expected output: one JSON line on stdout matching the BfldEvent schema
|
||||
//! (presence, motion, person_count, identity_risk_score, rf_signature_hash,
|
||||
//! privacy_class = "anonymous").
|
||||
|
||||
use wifi_densepose_bfld::{
|
||||
BfldConfig, BfldPipeline, IdentityEmbedding, SensingInputs, SignatureHasher, EMBEDDING_DIM,
|
||||
SITE_SALT_LEN,
|
||||
};
|
||||
|
||||
fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
// 1. Per-site secret (in production: loaded from TPM / KMS / secret file).
|
||||
let site_salt: [u8; SITE_SALT_LEN] = [
|
||||
0xA1, 0xB2, 0xC3, 0xD4, 0xE5, 0xF6, 0x07, 0x18, 0x29, 0x3A, 0x4B, 0x5C, 0x6D, 0x7E, 0x8F,
|
||||
0x90, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0xAA, 0xBB, 0xCC, 0xDD, 0xEE,
|
||||
0xFF, 0x00,
|
||||
];
|
||||
|
||||
// 2. Build the pipeline. Default class = Anonymous, no zone, hasher
|
||||
// installed so rf_signature_hash gets derived from the embedding.
|
||||
let mut pipeline = BfldPipeline::new(
|
||||
BfldConfig::new("seed-example")
|
||||
.with_signature_hasher(SignatureHasher::new(site_salt)),
|
||||
);
|
||||
|
||||
// 3. One per-frame sensing observation. In production these come from
|
||||
// the BFI extractor + RuvSense feature engine.
|
||||
let inputs = SensingInputs {
|
||||
timestamp_ns: 1_700_000_000_000_000_000,
|
||||
presence: true,
|
||||
motion: 0.42,
|
||||
person_count: 1,
|
||||
sensing_confidence: 0.91,
|
||||
// Low risk — gate stays in Accept; event is published.
|
||||
sep: 0.2,
|
||||
stab: 0.2,
|
||||
consist: 0.2,
|
||||
risk_conf: 0.2,
|
||||
rf_signature_hash: None, // hasher will derive
|
||||
};
|
||||
|
||||
// 4. Embedding from the AETHER encoder (ADR-024). For the example we
|
||||
// fill with a deterministic ramp; production uses real model output.
|
||||
let mut emb_values = [0.0f32; EMBEDDING_DIM];
|
||||
for (i, v) in emb_values.iter_mut().enumerate() {
|
||||
*v = (i as f32) * 0.0073;
|
||||
}
|
||||
let embedding = IdentityEmbedding::from_raw(emb_values);
|
||||
|
||||
// 5. Drive the pipeline. Returns Some(BfldEvent) when the gate permits;
|
||||
// None on Reject / Recalibrate.
|
||||
let event = pipeline
|
||||
.process(inputs, Some(embedding))
|
||||
.ok_or("gate dropped the event — should not happen at this risk level")?;
|
||||
|
||||
// 6. Publish JSON. Real deployments would feed this to MQTT via the
|
||||
// iter-22 publish_event(&publisher, &event) helper.
|
||||
let json = event.to_json()?;
|
||||
println!("{json}");
|
||||
Ok(())
|
||||
}
|
||||
|
|
@ -0,0 +1,98 @@
|
|||
//! Validates the `examples/bfld_minimal.rs` operator-quickstart contract.
|
||||
//! The example file embeds via include_str! for documentation-drift checks,
|
||||
//! then a separate test re-executes the same end-to-end flow inline so we
|
||||
//! get a CI-runnable proof that the operator workflow produces valid JSON.
|
||||
|
||||
#![cfg(feature = "std")]
|
||||
|
||||
use wifi_densepose_bfld::{
|
||||
BfldConfig, BfldPipeline, IdentityEmbedding, SensingInputs, SignatureHasher, EMBEDDING_DIM,
|
||||
SITE_SALT_LEN,
|
||||
};
|
||||
|
||||
const MINIMAL_EXAMPLE: &str = include_str!("../examples/bfld_minimal.rs");
|
||||
|
||||
#[test]
|
||||
fn minimal_example_documents_the_operator_quickstart_flow() {
|
||||
// The example must call out the canonical operator-facing types so
|
||||
// anyone reading it sees the right entry points.
|
||||
assert!(MINIMAL_EXAMPLE.contains("BfldPipeline"));
|
||||
assert!(MINIMAL_EXAMPLE.contains("SignatureHasher"));
|
||||
assert!(MINIMAL_EXAMPLE.contains("SensingInputs"));
|
||||
assert!(MINIMAL_EXAMPLE.contains("IdentityEmbedding"));
|
||||
assert!(MINIMAL_EXAMPLE.contains("BfldConfig"));
|
||||
assert!(
|
||||
MINIMAL_EXAMPLE.contains(".process("),
|
||||
"example must invoke pipeline.process(...) — method-chain style OK",
|
||||
);
|
||||
assert!(MINIMAL_EXAMPLE.contains("to_json"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn minimal_example_carries_run_instructions_in_doc_comments() {
|
||||
assert!(
|
||||
MINIMAL_EXAMPLE.contains("cargo run -p wifi-densepose-bfld --example bfld_minimal"),
|
||||
"example must document its own run command",
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn minimal_example_flow_produces_valid_json_with_documented_fields() {
|
||||
// Re-execute the same logic the example does so CI proves the flow
|
||||
// works end-to-end without needing `cargo run --example`.
|
||||
let site_salt: [u8; SITE_SALT_LEN] = [0xAB; SITE_SALT_LEN];
|
||||
let mut pipeline = BfldPipeline::new(
|
||||
BfldConfig::new("seed-example")
|
||||
.with_signature_hasher(SignatureHasher::new(site_salt)),
|
||||
);
|
||||
let inputs = SensingInputs {
|
||||
timestamp_ns: 1_700_000_000_000_000_000,
|
||||
presence: true,
|
||||
motion: 0.42,
|
||||
person_count: 1,
|
||||
sensing_confidence: 0.91,
|
||||
sep: 0.2,
|
||||
stab: 0.2,
|
||||
consist: 0.2,
|
||||
risk_conf: 0.2,
|
||||
rf_signature_hash: None,
|
||||
};
|
||||
let mut emb_values = [0.0f32; EMBEDDING_DIM];
|
||||
for (i, v) in emb_values.iter_mut().enumerate() {
|
||||
*v = (i as f32) * 0.0073;
|
||||
}
|
||||
let embedding = IdentityEmbedding::from_raw(emb_values);
|
||||
|
||||
let event = pipeline
|
||||
.process(inputs, Some(embedding))
|
||||
.expect("low-risk emit must succeed");
|
||||
let json = event.to_json().expect("JSON serialization must succeed");
|
||||
|
||||
// The published JSON should carry every documented anonymous-class field.
|
||||
for needle in [
|
||||
"\"type\":\"bfld_update\"",
|
||||
"\"node_id\":\"seed-example\"",
|
||||
"\"presence\":true",
|
||||
"\"motion\":",
|
||||
"\"person_count\":1",
|
||||
"\"confidence\":",
|
||||
"\"privacy_class\":\"anonymous\"",
|
||||
"\"identity_risk_score\":",
|
||||
"\"rf_signature_hash\":\"blake3:",
|
||||
] {
|
||||
assert!(
|
||||
json.contains(needle),
|
||||
"example JSON missing expected snippet `{needle}`\nfull JSON: {json}",
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn example_returns_box_dyn_error_for_main_signature() {
|
||||
// `main() -> Result<(), Box<dyn std::error::Error>>` is the standard
|
||||
// Rust-example pattern. Confirm the file uses it so future copy-paste
|
||||
// doesn't drop error propagation.
|
||||
assert!(
|
||||
MINIMAL_EXAMPLE.contains("fn main() -> Result<(), Box<dyn std::error::Error>>"),
|
||||
);
|
||||
}
|
||||
Loading…
Reference in New Issue