Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 13 additions & 0 deletions Justfile
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,19 @@ assail:
verify-abi:
cd src/abi && idris2 --check Types.idr && idris2 --check Protocol.idr && idris2 --check Encryption.idr && idris2 --check Layout.idr && idris2 --check Bridge.idr

# FFP conformance (requires standards checkout at $STANDARDS_DIR or ../standards)
ffp-conformance:
#!/usr/bin/env bash
set -euo pipefail
STANDARDS="${STANDARDS_DIR:-../standards}"
if [ ! -d "$STANDARDS/1-formats/sub-specs/form-fill-provenance/spec/conformance" ]; then
echo "standards checkout not found at $STANDARDS"
echo "hint: git clone https://github.com/hyperpolymath/standards ../standards"
exit 1
fi
cargo build --bin ffp-classify -p presswerk-document
FFP_DETECTOR="./target/debug/ffp-classify" bash "$STANDARDS/1-formats/sub-specs/form-fill-provenance/spec/conformance/run-conformance.sh"

# Full CI check (test + lint + fmt)
ci: test-libs lint fmt-check

Expand Down
25 changes: 25 additions & 0 deletions crates/presswerk-app/PrivacyInfo.xcprivacy
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>NSPrivacyAccessedAPITypes</key>
<array>
<dict>
<key>NSPrivacyAccessedAPIType</key>
<string>NSPrivacyAccessedAPICategoryFileTimestamp</string>
<key>NSPrivacyAccessedAPITypeReasons</key>
<array><string>C617.1</string></array>
</dict>
<dict>
<key>NSPrivacyAccessedAPIType</key>
<string>NSPrivacyAccessedAPICategoryUserDefaults</string>
<key>NSPrivacyAccessedAPITypeReasons</key>
<array><string>CA92.1</string></array>
</dict>
</array>
<key>NSPrivacyCollectedDataTypes</key>
<array/>
<key>NSPrivacyTracking</key>
<false/>
</dict>
</plist>
17 changes: 13 additions & 4 deletions crates/presswerk-app/src/pages/audit.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@

use dioxus::prelude::*;

use presswerk_core::provenance::{FfpRecord, FormProvenanceLegacy};
use presswerk_core::types::FormProvenance;
use presswerk_security::audit::AuditEntry;

Expand Down Expand Up @@ -103,10 +104,18 @@ fn summarise_details(action: &str, details: Option<&str>) -> String {
let Some(details) = details else {
return String::new();
};
if action == "form_provenance"
&& let Ok(provenance) = serde_json::from_str::<FormProvenance>(details)
{
return provenance.summary();
if action == "form_provenance" {
if let Ok(provenance) = serde_json::from_str::<FfpRecord>(details) {
return provenance.summary();
}
if let Ok(legacy) = serde_json::from_str::<FormProvenanceLegacy>(details) {
let rec: FfpRecord = legacy.into();
return rec.summary();
}
// Also try legacy alias via FormProvenance (which is now FfpRecord, but old JSON may still parse via alias fallback)
if let Ok(provenance) = serde_json::from_str::<FormProvenance>(details) {
return provenance.summary();
}
}
details.to_string()
}
47 changes: 43 additions & 4 deletions crates/presswerk-app/src/pages/jobs.rs
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,10 @@ pub fn Jobs() -> Element {
let can_release = matches!(job_status, JobStatus::Held);
let is_terminal = matches!(job_status, JobStatus::Completed | JobStatus::Failed | JobStatus::Cancelled);
let provenance_note = job.form_provenance.summary();
let machine_filled = job.form_provenance.is_machine_filled();
let provenance_label = job.form_provenance.label();
let show_provenance = job.form_provenance.form != presswerk_core::provenance::FfpForm::Unknown;
let is_machine_or_suspected = job.form_provenance.is_machine_or_suspected();
let hazard = job.form_provenance.has_blank_print_hazard();

rsx! {
div { style: "padding: 12px; margin: 8px 0; border: 1px solid #e0e0e0; border-radius: 8px;",
Expand All @@ -74,9 +77,14 @@ pub fn Jobs() -> Element {
}
}
p { style: "color: #666; font-size: 14px; margin: 4px 0;", "{ts}" }
if machine_filled {
p { style: "color: #8a5300; font-size: 12px; margin: 4px 0; background: #fff4e5; border: 1px solid #ffd8a8; border-radius: 4px; padding: 2px 6px;",
"Machine-filled: {provenance_note}"
if show_provenance {
p { style: "color: {provenance_color(&job.form_provenance)}; font-size: 12px; margin: 4px 0; background: {provenance_bg(&job.form_provenance)}; border: 1px solid {provenance_border(&job.form_provenance)}; border-radius: 4px; padding: 2px 6px;",
"{provenance_label}: {provenance_note}"
}
}
if hazard {
p { style: "color: #721c24; font-size: 12px; margin: 4px 0; background: #f8d7da; border: 1px solid #f5c6cb; border-radius: 4px; padding: 2px 6px;",
"⚠ Blank-print hazard: {job.form_provenance.filled_fields} field(s) have no appearance streams and may print empty (NeedAppearances is deprecated by ISO 32000-2)."
}
}
if let Some(ref uri) = job.printer_uri {
Expand Down Expand Up @@ -151,6 +159,37 @@ pub fn Jobs() -> Element {
}
}

fn provenance_bg(p: &presswerk_core::provenance::FfpRecord) -> &'static str {
match p.classification {
presswerk_core::provenance::FfpClassification::MachineFilled => "#fff4e5",
presswerk_core::provenance::FfpClassification::MachineFilledSuspected => "#fff9db",
presswerk_core::provenance::FfpClassification::BlankForm => "#e8f5e9",
presswerk_core::provenance::FfpClassification::FilledUnknown => "#f3e5f5",
presswerk_core::provenance::FfpClassification::NoForm => "#f5f5f5",
presswerk_core::provenance::FfpClassification::Unreadable => "#ffebee",
}
}
fn provenance_border(p: &presswerk_core::provenance::FfpRecord) -> &'static str {
match p.classification {
presswerk_core::provenance::FfpClassification::MachineFilled => "#ffd8a8",
presswerk_core::provenance::FfpClassification::MachineFilledSuspected => "#ffec99",
presswerk_core::provenance::FfpClassification::BlankForm => "#c8e6c9",
presswerk_core::provenance::FfpClassification::FilledUnknown => "#ce93d8",
presswerk_core::provenance::FfpClassification::NoForm => "#e0e0e0",
presswerk_core::provenance::FfpClassification::Unreadable => "#ef9a9a",
}
}
fn provenance_color(p: &presswerk_core::provenance::FfpRecord) -> &'static str {
match p.classification {
presswerk_core::provenance::FfpClassification::MachineFilled => "#8a5300",
presswerk_core::provenance::FfpClassification::MachineFilledSuspected => "#6d4c00",
presswerk_core::provenance::FfpClassification::BlankForm => "#1b5e20",
presswerk_core::provenance::FfpClassification::FilledUnknown => "#4a148c",
presswerk_core::provenance::FfpClassification::NoForm => "#616161",
presswerk_core::provenance::FfpClassification::Unreadable => "#b71c1c",
}
}

fn status_bg(s: JobStatus) -> &'static str {
match s {
JobStatus::Pending | JobStatus::Held => "#f0f0f0",
Expand Down
5 changes: 3 additions & 2 deletions crates/presswerk-app/src/services/app_services.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,9 @@ use std::sync::{Arc, Mutex};

use presswerk_core::AppConfig;
use presswerk_core::error::{PresswerkError, Result};
use presswerk_core::provenance::FfpClassification;
use presswerk_core::types::{
DiscoveredPrinter, DocumentType, FormOrigin, JobId, JobSource, JobStatus, PrintJob,
DiscoveredPrinter, DocumentType, JobId, JobSource, JobStatus, PrintJob,
PrintSettings, ServerStatus,
};
use presswerk_document::classify_for_print;
Expand Down Expand Up @@ -429,7 +430,7 @@ impl AppServices {
/// (issue #118, ruling D189).
pub fn machine_filled_jobs(&self) -> Result<Vec<PrintJob>> {
let queue = acquire_lock(&self.job_queue);
queue.get_jobs_with_form_origin(FormOrigin::Machine)
queue.get_jobs_with_form_origin(FfpClassification::MachineFilled)
}

/// Cancel a job.
Expand Down
5 changes: 5 additions & 0 deletions crates/presswerk-core/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,13 @@
pub mod config;
pub mod error;
pub mod human_errors;
pub mod provenance;
pub mod types;

pub use config::AppConfig;
pub use error::PresswerkError;
pub use provenance::{
FfpAppearances, FfpClassification, FfpDeclared, FfpForm, FfpRecord, FormOrigin,
FormProvenance, ProvenanceConfidence,
};
pub use types::*;
Loading
Loading