Skip to content

prd-7 phase 1: auto-populate TransactionFacts from pipeline state #55

@promptexecutionerr

Description

@promptexecutionerr

Context

PipelineState<Validated>::to_transaction_facts() at crates/ledger-core/src/pipeline.rs currently returns TransactionFacts::new() — an empty default. Every field (vendor_jurisdiction, supply_type, tax_code, amount, is_business_activity, is_ordinary, is_necessary) is None.

Consequence: verify_legal() (introduced in PRD-7 Phase 0) dispatches to LegalSolver::verify_all() with a fact-free struct, which returns Z3Result::Unknown for every rule, including the five new rules in Jurisdiction::legal_ruleset(). The legal gate exists in the type system but produces no signal.

What Needs to Happen

Add a parsed document field contract. PipelineState<Validated> must carry enough structured data for to_transaction_facts() to emit non-None fields. Two options, pick the simpler one:

Option A — add a doc_fields: HashMap<String, String> field to PipelineState<S>.
to_transaction_facts() maps known keys ("vendor_jurisdiction", "supply_type", "tax_code", "amount") from the map. Callers (ingest path) populate the map from parsed document rows.

Option B — add a typed DocumentFields struct as a public field on PipelineState<S>.

pub struct DocumentFields {
    pub vendor_jurisdiction: Option<String>,
    pub supply_type: Option<String>,
    pub tax_code: Option<String>,
    pub amount: Option<rust_decimal::Decimal>,
    pub is_business_activity: Option<bool>,
    pub is_ordinary: Option<bool>,
    pub is_necessary: Option<bool>,
}

to_transaction_facts() copies fields directly. Preserves type safety; avoids stringly-typed map.

Option B is preferred — it mirrors TransactionFacts shape and makes the mapping trivial.

Acceptance Criteria

  • PipelineState<Validated> carries a DocumentFields (or equivalent) field populated at construction.
  • to_transaction_facts() returns a TransactionFacts with non-None fields when the document fields are set.
  • test_verify_legal_integration in pipeline.rs exercises a complete fact → Z3 verification path: set DocumentFields { vendor_jurisdiction: Some("US"), supply_type: Some("SaaS"), tax_code: Some("BASEXCLUDED"), .. } and assert verify_legal() returns Ok(Classified) against au_gst::rule_38_190().
  • Setting tax_code: Some("INPUT") for a US SaaS vendor asserts verify_legal() returns Err(NeedsReview).
  • No new dependencies. No unwrap in non-test code.

Files

  • crates/ledger-core/src/pipeline.rs — primary change site
  • crates/ledger-core/src/legal.rsTransactionFacts shape reference
  • crates/ledger-core/src/ingest.rs — must populate DocumentFields at row parse time

Dependency

Unblocks PRD-7 Phase 2 (symbolic Z3 upgrade) and PRD-7 Phase 3 (audit sheet) — both are meaningless until facts are real.

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or requestpipelineTransaction pipelineprd-7Legal Intelligence Layer

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions