Repository navigation
fix: correct provenance moved-value and get_array_ids syntax + fmt auto-fix #126
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
fa14610
aa74f86
4337fcd
201d2d8
a0dac80
be481cb
2414d37
a3f1efd
3bdc97a
c65f2f1
31e8f9d
b6627f2
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,38 @@ | ||
| # SPDX-License-Identifier: MPL-2.0 | ||
| # Auto-fix cargo fmt (temporary — remove after D189 lands) | ||
| # Uses only actions already in actions.lock to avoid lock churn. | ||
|
|
||
| name: fmt | ||
|
|
||
| on: | ||
| pull_request: | ||
| branches: [main] | ||
| workflow_dispatch: | ||
|
|
||
| permissions: | ||
| contents: write | ||
|
|
||
| jobs: | ||
| fmt: | ||
| runs-on: ubuntu-latest | ||
| steps: | ||
| - uses: actions/checkout@v7.0.1 | ||
| with: | ||
| ref: ${{ github.head_ref }} | ||
| token: ${{ secrets.GITHUB_TOKEN }} | ||
| - uses: dtolnay/rust-toolchain@v1 | ||
| with: | ||
| toolchain: stable | ||
| components: rustfmt | ||
| - uses: Swatinem/rust-cache@v2.9.2 | ||
| - run: cargo fmt --all | ||
| - run: | | ||
| git config user.name "github-actions" | ||
| git config user.email "github-actions@github.com" | ||
| if ! git diff --quiet; then | ||
| git add -A | ||
| git commit -m "fmt: cargo fmt --all" | ||
| git push | ||
| else | ||
| echo "no fmt changes" | ||
| fi |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -17,5 +17,32 @@ permissions: | |
| contents: read | ||
|
|
||
| jobs: | ||
| # Auto-format before the reusable Rust CI so PRs don't fail on fmt alone. | ||
| fmt: | ||
| runs-on: ubuntu-latest | ||
| permissions: | ||
| contents: write | ||
| steps: | ||
| - uses: actions/checkout@v7.0.1 | ||
| with: | ||
| ref: ${{ github.head_ref }} | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🩺 Stability & Availability | 🟠 Major | ⚡ Quick win Select a branch before pushing on When a 🤖 Prompt for AI Agents |
||
| token: ${{ secrets.GITHUB_TOKEN }} | ||
| - uses: dtolnay/rust-toolchain@v1 | ||
| with: | ||
| toolchain: stable | ||
| components: rustfmt | ||
| - uses: Swatinem/rust-cache@v2.9.2 | ||
| - run: cargo fmt --all | ||
| - run: | | ||
| git config user.name "github-actions" | ||
| git config user.email "github-actions@github.com" | ||
| if ! git diff --quiet; then | ||
| git add -A | ||
| git commit -m "fmt: cargo fmt --all" | ||
| git push | ||
|
Comment on lines
+39
to
+42
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🩺 Stability & Availability | 🟠 Major | ⚡ Quick win Do not push formatting commits from fork pull requests. Fork pull requests normally receive a read-only
📍 Affects 2 files
🤖 Prompt for AI Agents |
||
| else | ||
| echo "no fmt changes" | ||
| fi | ||
| rust-ci: | ||
| needs: fmt | ||
| uses: hyperpolymath/standards/.github/workflows/rust-ci-reusable.yml@84355587cb2a1f86e6882de83514a32db2646e7a # re-pinned 2026-07-01: toolchain-required-input fix (standards#439) | ||
| Original file line number | Diff line number | Diff line change | ||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -146,12 +146,21 @@ pub struct FfpDeclared { | |||||||||||||||||
| pub tool_version: Option<String>, | ||||||||||||||||||
| #[serde(rename = "filledAt", skip_serializing_if = "Option::is_none")] | ||||||||||||||||||
| pub filled_at: Option<String>, | ||||||||||||||||||
| #[serde(rename = "appearancesGenerated", skip_serializing_if = "Option::is_none")] | ||||||||||||||||||
| #[serde( | ||||||||||||||||||
| rename = "appearancesGenerated", | ||||||||||||||||||
| skip_serializing_if = "Option::is_none" | ||||||||||||||||||
| )] | ||||||||||||||||||
| pub appearances_generated: Option<bool>, | ||||||||||||||||||
| } | ||||||||||||||||||
|
|
||||||||||||||||||
| impl FfpDeclared { | ||||||||||||||||||
| pub fn machine(tool: Option<String>, tool_version: Option<String>, filled_at: Option<String>, appearances_generated: Option<bool>) -> Self { | ||||||||||||||||||
| /// Create a machine-filled declaration with optional tool and appearance metadata. | ||||||||||||||||||
| pub fn machine( | ||||||||||||||||||
| tool: Option<String>, | ||||||||||||||||||
| tool_version: Option<String>, | ||||||||||||||||||
| filled_at: Option<String>, | ||||||||||||||||||
| appearances_generated: Option<bool>, | ||||||||||||||||||
| ) -> Self { | ||||||||||||||||||
| Self { | ||||||||||||||||||
| filled_by: "machine".to_string(), | ||||||||||||||||||
| tool, | ||||||||||||||||||
|
|
@@ -248,7 +257,9 @@ impl FfpRecord { | |||||||||||||||||
| pub fn should_hold(&self, policy: crate::types::FormProvenancePolicy) -> bool { | ||||||||||||||||||
| use crate::types::FormProvenancePolicy; | ||||||||||||||||||
| match policy { | ||||||||||||||||||
| FormProvenancePolicy::HoldForReview => self.is_machine_or_suspected() || self.has_blank_print_hazard(), | ||||||||||||||||||
| FormProvenancePolicy::HoldForReview => { | ||||||||||||||||||
| self.is_machine_or_suspected() || self.has_blank_print_hazard() | ||||||||||||||||||
| } | ||||||||||||||||||
| _ => false, | ||||||||||||||||||
| } | ||||||||||||||||||
| } | ||||||||||||||||||
|
|
@@ -269,12 +280,21 @@ impl FfpRecord { | |||||||||||||||||
| "machine-filled form".to_string() | ||||||||||||||||||
| } | ||||||||||||||||||
| } | ||||||||||||||||||
| FfpClassification::MachineFilledSuspected => "machine-filled form (suspected — incomplete appearances)".to_string(), | ||||||||||||||||||
| FfpClassification::FilledUnknown => "form values present — provenance unknown".to_string(), | ||||||||||||||||||
| FfpClassification::MachineFilledSuspected => { | ||||||||||||||||||
| "machine-filled form (suspected — incomplete appearances)".to_string() | ||||||||||||||||||
| } | ||||||||||||||||||
| FfpClassification::FilledUnknown => { | ||||||||||||||||||
| "form values present — provenance unknown".to_string() | ||||||||||||||||||
| } | ||||||||||||||||||
| FfpClassification::Unreadable => "form provenance unreadable".to_string(), | ||||||||||||||||||
| }; | ||||||||||||||||||
| if self.is_form() { | ||||||||||||||||||
| format!("{head}; {}/{} fields filled; appearances {}", self.filled_fields, self.total_fields, self.appearances.as_str()) | ||||||||||||||||||
| format!( | ||||||||||||||||||
| "{head}; {}/{} fields filled; appearances {}", | ||||||||||||||||||
| self.filled_fields, | ||||||||||||||||||
| self.total_fields, | ||||||||||||||||||
| self.appearances.as_str() | ||||||||||||||||||
| ) | ||||||||||||||||||
| } else { | ||||||||||||||||||
| head | ||||||||||||||||||
| } | ||||||||||||||||||
|
|
@@ -324,7 +344,9 @@ impl FfpRecord { | |||||||||||||||||
| FfpClassification::NoForm => "NotAForm", | ||||||||||||||||||
| FfpClassification::BlankForm => "Empty", | ||||||||||||||||||
| FfpClassification::FilledUnknown => "Human", | ||||||||||||||||||
| FfpClassification::MachineFilled | FfpClassification::MachineFilledSuspected => "Machine", | ||||||||||||||||||
| FfpClassification::MachineFilled | FfpClassification::MachineFilledSuspected => { | ||||||||||||||||||
| "Machine" | ||||||||||||||||||
| } | ||||||||||||||||||
| FfpClassification::Unreadable => "Unknown", | ||||||||||||||||||
| } | ||||||||||||||||||
| } | ||||||||||||||||||
|
|
@@ -388,7 +410,9 @@ impl std::fmt::Display for FormOrigin { | |||||||||||||||||
| } | ||||||||||||||||||
| } | ||||||||||||||||||
|
|
||||||||||||||||||
| #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize, Default)] | ||||||||||||||||||
| #[derive( | ||||||||||||||||||
| Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize, Default, | ||||||||||||||||||
| )] | ||||||||||||||||||
| pub enum ProvenanceConfidence { | ||||||||||||||||||
| #[default] | ||||||||||||||||||
| None, | ||||||||||||||||||
|
|
@@ -459,6 +483,8 @@ pub type FormProvenance = FfpRecord; | |||||||||||||||||
|
|
||||||||||||||||||
| /// Convert legacy into new record (best-effort, for migration). | ||||||||||||||||||
| impl From<FormProvenanceLegacy> for FfpRecord { | ||||||||||||||||||
| /// Migrate legacy provenance to FFP, mapping unmarked machine origins to suspected. | ||||||||||||||||||
| /// Preserve field counts and translate legacy signals into evidence codes. | ||||||||||||||||||
| fn from(legacy: FormProvenanceLegacy) -> Self { | ||||||||||||||||||
| // Map legacy origin → new classification | ||||||||||||||||||
| let classification = match legacy.origin { | ||||||||||||||||||
|
|
@@ -492,19 +518,25 @@ impl From<FormProvenanceLegacy> for FfpRecord { | |||||||||||||||||
| } else { | ||||||||||||||||||
| FfpAppearances::Generated | ||||||||||||||||||
| }; | ||||||||||||||||||
| let declared = legacy.marker.map(|m| FfpDeclared { | ||||||||||||||||||
| filled_by: m, | ||||||||||||||||||
| tool: legacy.producer, | ||||||||||||||||||
| tool_version: None, | ||||||||||||||||||
| filled_at: None, | ||||||||||||||||||
| appearances_generated: None, | ||||||||||||||||||
| }).or_else(|| legacy.producer.map(|p| FfpDeclared { | ||||||||||||||||||
| filled_by: "machine".to_string(), | ||||||||||||||||||
| tool: Some(p), | ||||||||||||||||||
| tool_version: None, | ||||||||||||||||||
| filled_at: None, | ||||||||||||||||||
| appearances_generated: None, | ||||||||||||||||||
| })); | ||||||||||||||||||
| let declared = if let Some(m) = &legacy.marker { | ||||||||||||||||||
| Some(FfpDeclared { | ||||||||||||||||||
| filled_by: m.clone(), | ||||||||||||||||||
| tool: legacy.producer.clone(), | ||||||||||||||||||
| tool_version: None, | ||||||||||||||||||
| filled_at: None, | ||||||||||||||||||
| appearances_generated: None, | ||||||||||||||||||
| }) | ||||||||||||||||||
| } else if let Some(p) = &legacy.producer { | ||||||||||||||||||
| Some(FfpDeclared { | ||||||||||||||||||
| filled_by: "machine".to_string(), | ||||||||||||||||||
| tool: Some(p.clone()), | ||||||||||||||||||
| tool_version: None, | ||||||||||||||||||
| filled_at: None, | ||||||||||||||||||
| appearances_generated: None, | ||||||||||||||||||
| }) | ||||||||||||||||||
|
Comment on lines
+529
to
+536
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🗄️ Data Integrity & Integration | 🟡 Minor | ⚡ Quick win Do not create a If a legacy record has no 🐛 Proposed fix- } else if let Some(p) = &legacy.producer {
- Some(FfpDeclared {
- filled_by: "machine".to_string(),
- tool: Some(p.clone()),
- tool_version: None,
- filled_at: None,
- appearances_generated: None,
- })
} else {
None
};📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||||||
| } else { | ||||||||||||||||||
| None | ||||||||||||||||||
| }; | ||||||||||||||||||
| let mut evidence = Vec::new(); | ||||||||||||||||||
| if legacy.need_appearances { | ||||||||||||||||||
| evidence.push("FFP-E-NEED-APPEARANCES".to_string()); | ||||||||||||||||||
|
|
@@ -515,7 +547,9 @@ impl From<FormProvenanceLegacy> for FfpRecord { | |||||||||||||||||
| match legacy.origin { | ||||||||||||||||||
| FormOrigin::NotAForm => evidence.push("FFP-E-NO-ACROFORM".to_string()), | ||||||||||||||||||
| FormOrigin::Empty => evidence.push("FFP-E-NO-VALUES".to_string()), | ||||||||||||||||||
| FormOrigin::Unknown if !legacy.inspected => evidence.push("FFP-E-UNREADABLE".to_string()), | ||||||||||||||||||
| FormOrigin::Unknown if !legacy.inspected => { | ||||||||||||||||||
| evidence.push("FFP-E-UNREADABLE".to_string()) | ||||||||||||||||||
| } | ||||||||||||||||||
| _ => {} | ||||||||||||||||||
| } | ||||||||||||||||||
| if legacy.marker.is_some() { | ||||||||||||||||||
|
|
@@ -562,6 +596,7 @@ mod tests { | |||||||||||||||||
| } | ||||||||||||||||||
| } | ||||||||||||||||||
|
|
||||||||||||||||||
| /// Verify the conformance line includes field counts and sorted evidence codes. | ||||||||||||||||||
| #[test] | ||||||||||||||||||
| fn canonical_line_format() { | ||||||||||||||||||
| let r = FfpRecord { | ||||||||||||||||||
|
|
@@ -572,7 +607,10 @@ mod tests { | |||||||||||||||||
| total_fields: 2, | ||||||||||||||||||
| appearances: FfpAppearances::Incomplete, | ||||||||||||||||||
| declared: None, | ||||||||||||||||||
| evidence: vec!["FFP-E-NEED-APPEARANCES".to_string(), "FFP-E-AP-INCOMPLETE".to_string()], | ||||||||||||||||||
| evidence: vec![ | ||||||||||||||||||
| "FFP-E-NEED-APPEARANCES".to_string(), | ||||||||||||||||||
| "FFP-E-AP-INCOMPLETE".to_string(), | ||||||||||||||||||
| ], | ||||||||||||||||||
| }; | ||||||||||||||||||
| assert_eq!( | ||||||||||||||||||
| r.canonical_line(), | ||||||||||||||||||
|
|
@@ -581,6 +619,7 @@ mod tests { | |||||||||||||||||
| // evidence is sorted | ||||||||||||||||||
| } | ||||||||||||||||||
|
|
||||||||||||||||||
| /// Verify audit JSON preserves the complete record and its declared metadata. | ||||||||||||||||||
| #[test] | ||||||||||||||||||
| fn json_roundtrip() { | ||||||||||||||||||
| let r = FfpRecord { | ||||||||||||||||||
|
|
@@ -590,20 +629,31 @@ mod tests { | |||||||||||||||||
| filled_fields: 2, | ||||||||||||||||||
| total_fields: 2, | ||||||||||||||||||
| appearances: FfpAppearances::Incomplete, | ||||||||||||||||||
| declared: Some(FfpDeclared { filled_by: "machine".to_string(), tool: Some("blocky-writer".to_string()), tool_version: Some("0.2.0".to_string()), filled_at: None, appearances_generated: Some(false) }), | ||||||||||||||||||
| declared: Some(FfpDeclared { | ||||||||||||||||||
| filled_by: "machine".to_string(), | ||||||||||||||||||
| tool: Some("blocky-writer".to_string()), | ||||||||||||||||||
| tool_version: Some("0.2.0".to_string()), | ||||||||||||||||||
| filled_at: None, | ||||||||||||||||||
| appearances_generated: Some(false), | ||||||||||||||||||
| }), | ||||||||||||||||||
| evidence: vec!["FFP-E-DECL-MACHINE".to_string()], | ||||||||||||||||||
| }; | ||||||||||||||||||
| let json = r.audit_json(); | ||||||||||||||||||
| let back: FfpRecord = serde_json::from_str(&json).unwrap(); | ||||||||||||||||||
| assert_eq!(back, r); | ||||||||||||||||||
| } | ||||||||||||||||||
|
|
||||||||||||||||||
| /// Verify unreadable records emit the canonical unknown-form representation. | ||||||||||||||||||
| #[test] | ||||||||||||||||||
| fn unreadable_line() { | ||||||||||||||||||
| let r = FfpRecord::unreadable("parse failed"); | ||||||||||||||||||
| assert_eq!(r.canonical_line(), "classification=unreadable form=unknown filled=0/0 appearances=unknown evidence=FFP-E-UNREADABLE"); | ||||||||||||||||||
| assert_eq!( | ||||||||||||||||||
| r.canonical_line(), | ||||||||||||||||||
| "classification=unreadable form=unknown filled=0/0 appearances=unknown evidence=FFP-E-UNREADABLE" | ||||||||||||||||||
| ); | ||||||||||||||||||
| } | ||||||||||||||||||
|
|
||||||||||||||||||
| /// Verify review holds cover machine origins and incomplete filled appearances. | ||||||||||||||||||
| #[test] | ||||||||||||||||||
| fn should_hold() { | ||||||||||||||||||
| use crate::types::FormProvenancePolicy; | ||||||||||||||||||
|
|
@@ -614,7 +664,13 @@ mod tests { | |||||||||||||||||
| filled_fields: 2, | ||||||||||||||||||
| total_fields: 2, | ||||||||||||||||||
| appearances: FfpAppearances::Incomplete, | ||||||||||||||||||
| declared: Some(FfpDeclared { filled_by: "machine".to_string(), tool: None, tool_version: None, filled_at: None, appearances_generated: None }), | ||||||||||||||||||
| declared: Some(FfpDeclared { | ||||||||||||||||||
| filled_by: "machine".to_string(), | ||||||||||||||||||
| tool: None, | ||||||||||||||||||
| tool_version: None, | ||||||||||||||||||
| filled_at: None, | ||||||||||||||||||
| appearances_generated: None, | ||||||||||||||||||
| }), | ||||||||||||||||||
| evidence: vec!["FFP-E-DECL-MACHINE".to_string()], | ||||||||||||||||||
| }; | ||||||||||||||||||
| assert!(r.should_hold(FormProvenancePolicy::HoldForReview)); | ||||||||||||||||||
|
|
||||||||||||||||||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🎯 Functional Correctness | 🟠 Major | ⚡ Quick win
Check the submitted formatting before changing the working tree.
When a pull request contains unformatted Rust files,
cargo fmt --allchanges them beforecargo fmt --all -- --checkruns. The check then passes although the submitted commit remains unformatted. Run the check first, or remove the mutating command from this verification job.🤖 Prompt for AI Agents