Skip to main content

Documentation Index

Fetch the complete documentation index at: https://docs.manthan.systems/llms.txt

Use this file to discover all available pages before exploring further.

What is signal provenance?

Signal provenance is optional metadata attached to governed signals that records where a signal value came from, how it was obtained, and how trustworthy the source is. Provenance answers the question an attestation cannot: “The decision was made deterministically from these signals — but where did the signals come from?” Provenance does not affect policy evaluation. Policy rules evaluate signal values only. Provenance is adjacent metadata — it rides alongside signals for audit and compliance purposes but never enters the deterministic evaluation boundary.

The GovernedSignal type

interface GovernedSignal<T = unknown> {
  value:       T;                   // deterministic value evaluated by policy
  provenance?: SignalProvenance;    // optional origin metadata
}

type GovernedSignalMap = Record<string, GovernedSignal>;
A GovernedSignal wraps a raw signal value with optional provenance. When no provenance is attached, the signal is treated identically to a plain value — there is no breaking change.

SignalProvenance fields

interface SignalProvenance {
  schemaVersion?:         string;            // provenance schema version
  source:                 string;            // e.g. "AccountAggregator", "DigiLocker"
  sourceType:             SourceType;        // classification of the source
  sourceVerified:         boolean;           // whether source verification succeeded
  verificationMethod?:    VerificationMethod; // how it was verified
  sourceTimestamp?:       string;            // ISO-8601 UTC timestamp from source
  adapterIdentity?:       string;            // e.g. "aa-income-adapter@1.2.0"
  transformationLineage?: string[];          // ordered list of transformations applied
  evidenceHash?:          string;            // SHA-256 of the raw source payload
  trustLevel?:            ProvenanceTrustLevel; // governance trust classification
  metadata?:              Record<string, unknown>; // source-specific additional context
}

SourceType values

type SourceType =
  | "financial_api"    // Account Aggregator, CIBIL, credit bureaus
  | "government_api"   // DigiLocker, Aadhaar, GST, MCA
  | "banking_api"      // Core banking systems
  | "document_ai"      // Claude, Sarvam, OCR pipelines
  | "voice_transcript" // Voice call analysis
  | "internal_system"  // Internal databases/services
  | "manual_entry"     // Human-entered values
  | "unknown";

TrustLevel values

type ProvenanceTrustLevel =
  | "unverified" // no verification attempted
  | "claimed"    // source claims validity, not cryptographically proven
  | "verified"   // provenance cryptographically verified
  | "trusted";   // verified by trusted authority
The TrustLevel type exported from @parmanasystems/core is a simplified three-level version ("low", "medium", "high") for application-layer use. ProvenanceTrustLevel is used internally by SignalProvenance.

withProvenance() usage

Wrap a signal value with provenance before passing it to executeFromSignals:
import {
  withProvenance,
  accountAggregatorProvenance,
  extractSignalValues,
  executeFromSignals,
} from "@parmanasystems/core";

// Step 1: build governed signals with provenance
const governedSignals = {
  monthly_income: withProvenance(
    82000,
    accountAggregatorProvenance({
      consentId:  "CONSENT-2024-ABC123",
      fiuId:      "FIU-NBFC-001",
      fetchedAt:  new Date().toISOString(),
      rawData:    aaApiResponse,
    })
  ),
  employed: withProvenance(
    true,
    {
      source:         "InternalCRM",
      sourceType:     "internal_system",
      sourceVerified: true,
      trustLevel:     "verified",
    }
  ),
};

// Step 2: extract raw values for policy evaluation (the determinism boundary)
const signals = extractSignalValues(governedSignals);
// { monthly_income: 82000, employed: true }

// Step 3: execute governed decision — policy sees only values
const attestation = await executeFromSignals(
  { policyId: "personal-loan", policyVersion: "1.0.0", signals },
  signer, verifier, store
);

extractSignalValues() — the determinism boundary

extractSignalValues strips provenance and returns plain Record<string, unknown>:
function extractSignalValues(
  input: Record<string, unknown>
): Record<string, unknown>
This is the critical boundary. Policy evaluation sees only the raw values — never the provenance metadata. This ensures that two executions with different provenance records but identical values produce identical decisions and identical execution_fingerprint values. Provenance is for audit lineage. It never influences the deterministic outcome.

validateProvenance() — trust checking

Before executing, you can validate provenance trust levels and source requirements:
import { validateProvenance } from "@parmanasystems/core";

const result = validateProvenance(governedSignals, {
  requireProvenance:   true,              // all signals must have provenance
  minimumTrustLevel:  "verified",         // reject below this trust level
  requiredSources:    ["AccountAggregator", "InternalCRM"],
});

if (!result.valid) {
  throw new Error(`Provenance invalid: ${result.errors.join(", ")}`);
}

console.log(result.trustSummary);
// {
//   totalSignals:              2,
//   signalsWithProvenance:     2,
//   verifiedSignals:           2,
//   unverifiedSignals:         0,
//   signalsMissingEvidenceHash: 0,
//   signalsMissingTimestamp:   0,
//   minimumTrustLevel:         "verified",
// }
validateProvenance performs deterministic local evaluation only. It does not make network calls, fetch remote certificates, or validate real-world truth.

Three adapter helpers

The three most common signal sources in the Indian financial stack have ready-made helpers:

accountAggregatorProvenance()

For signals sourced from the RBI Account Aggregator framework:
import { accountAggregatorProvenance } from "@parmanasystems/core";

const provenance = accountAggregatorProvenance({
  consentId:       "CONSENT-2024-ABC123",   // AA consent handle
  fiuId:           "FIU-NBFC-001",          // your FIU registration ID
  fetchedAt:       "2024-01-15T10:00:00Z",  // when data was fetched
  rawData:         aaApiResponse,           // hashed for evidenceHash
  transformations: ["rbi-aa-schema-normalization", "monthly-average-v2"],
});
// trustLevel: "verified", sourceVerified: true, verificationMethod: "signature"

documentProvenance()

For signals extracted by an AI or OCR pipeline from a document:
import { documentProvenance } from "@parmanasystems/core";

const provenance = documentProvenance({
  modelName:    "claude-3-5-sonnet",
  modelVersion: "20241022",
  extractedAt:  "2024-01-15T10:05:00Z",
  rawDocument:  pdfBytes,       // hashed for evidenceHash
  confidence:   0.97,
  transformations: ["pdf-text-extraction", "income-normalization"],
});
// trustLevel: "claimed", sourceVerified: false — AI extraction is not automatically trusted

voiceTranscriptProvenance()

For signals extracted from a voice call transcript:
import { voiceTranscriptProvenance } from "@parmanasystems/core";

const provenance = voiceTranscriptProvenance({
  callId:      "CALL-2024-XYZ789",
  sttModel:    "sarvam-1",
  language:    "hi-IN",
  extractedAt: "2024-01-15T10:10:00Z",
  transcript:  transcriptText,  // hashed for evidenceHash
  confidence:  0.89,
});
// trustLevel: "claimed", sourceVerified: false

Complete example: Account Aggregator income verification

A common pattern in Indian lending — using Account Aggregator data for income verification before a governed loan decision:
import {
  withProvenance,
  accountAggregatorProvenance,
  validateProvenance,
  extractSignalValues,
  executeFromSignals,
  LocalSigner,
  LocalVerifier,
  MemoryReplayStore,
} from "@parmanasystems/core";

async function governedLoanDecision(
  aaResponse: AccountAggregatorResponse,
  creditBureauScore: number,
) {
  // Build provenance-aware governed signals
  const governedSignals = {
    monthly_income: withProvenance(
      aaResponse.derivedMonthlyIncome,
      accountAggregatorProvenance({
        consentId:  aaResponse.consentId,
        fiuId:      "FIU-NBFC-001",
        fetchedAt:  aaResponse.fetchedAt,
        rawData:    aaResponse.rawFiData,
        transformations: ["rbi-aa-schema-normalization", "monthly-average-v2"],
      })
    ),
    cibil_score: withProvenance(
      creditBureauScore,
      {
        source:         "CIBIL",
        sourceType:     "financial_api",
        sourceVerified: true,
        verificationMethod: "api-key",
        trustLevel:     "verified",
        sourceTimestamp: new Date().toISOString(),
      }
    ),
    employed: withProvenance(
      aaResponse.hasRegularIncome,
      accountAggregatorProvenance({
        consentId:  aaResponse.consentId,
        fiuId:      "FIU-NBFC-001",
        fetchedAt:  aaResponse.fetchedAt,
      })
    ),
  };

  // Validate provenance before execution
  const validation = validateProvenance(governedSignals, {
    requireProvenance:  true,
    minimumTrustLevel: "verified",
  });

  if (!validation.valid) {
    throw new Error(`Provenance requirements not met: ${validation.errors.join(", ")}`);
  }

  // Extract values — this is the determinism boundary
  // Policy evaluation never sees provenance metadata
  const signals = extractSignalValues(governedSignals);

  // Execute governed decision
  const attestation = await executeFromSignals(
    {
      policyId:      "personal-loan",
      policyVersion: "1.0.0",
      signals,
    },
    signer, verifier, store
  );

  return {
    attestation,
    provenanceSummary: validation.trustSummary,
  };
}

Why provenance is optional

Provenance is optional by design. Existing code that passes plain signals without provenance continues to work unchanged. There is no breaking change, no forced migration, and no runtime cost when provenance is absent. Adding provenance is an incremental improvement — you can start with unverified signals and progressively add provenance metadata as your integrations mature.

Why provenance never enters policy evaluation

Policy evaluation is deterministic. The execution_fingerprint is the SHA-256 of { policyId, policyVersion, signals } — not of the provenance. Two executions with identical signal values but different provenance records produce identical decisions, identical fingerprints, and identical attestations. This is intentional. Provenance is about lineage and audit, not about governance outcomes. If provenance were allowed to influence decisions, the determinism guarantee would be broken — the same business inputs could produce different decisions depending on metadata that changes with each API call. If you want provenance characteristics to influence governance decisions (e.g. “only accept signals from verified sources”), express them explicitly as signals:
"signalsSchema": {
  "income_source_verified": { "type": "boolean" },
  "monthly_income":         { "type": "integer" }
}
Then your adapter sets income_source_verified: true only when the AA data is cryptographically verified, and your policy rules use it accordingly.

The execution gap it closes

An ExecutionAttestation proves a governance decision was made correctly. It does not prove that the signals were accurate. Signal provenance closes part of this gap by providing a traceable record of where each signal came from:
Without provenanceWith provenance
”We decided based on signals""We decided based on signals that came from source X, fetched at time T, with evidence hash H”
Auditor must trust the callerAuditor can trace signals to the source system
No evidence of data freshnesssourceTimestamp proves data age
No lineage of transformationstransformationLineage records each step
Provenance does not eliminate the need for trust — it makes the trust explicit and auditable.

Use Cases

Account Aggregator income verification for personal loans

An NBFC fetches bank statement data via the RBI Account Aggregator framework. The raw FI data is hashed before transformation, the derived monthly_income is wrapped with accountAggregatorProvenance, and validateProvenance confirms that all signals meet minimumTrustLevel: "verified" before executeFromSignals runs. The resulting attestation — combined with the provenance metadata — gives the auditor a complete chain: AA consent → raw data hash → derived income signal → governed decision.

AI-extracted signals from bank statements

A loan processor uses Claude to extract monthly_income and emi_obligations from uploaded bank statement PDFs. Each extracted signal is wrapped with documentProvenance({ modelName: "claude-3-5-sonnet", modelVersion: "20241022", rawDocument: pdfBytes, confidence: 0.95 }). The trust level is "claimed" — the AI extraction is not automatically trusted. The NBFC then validates these signals against Account Aggregator data before execution, or expresses the verification status explicitly in the signal: income_source_verified: true.

Voice KYC for rural lending

A microfinance institution conducts video KYC for rural borrowers via voice call. The call is transcribed by Sarvam-1 and signals are extracted. Each signal is wrapped with voiceTranscriptProvenance({ callId, sttModel: "sarvam-1", language: "hi-IN", extractedAt, confidence: 0.87 }). The trust level is "claimed". The compliance team can trace every signal back to the specific call recording via the evidenceHash — without embedding the full transcript in the attestation.

Progressive trust adoption

An NBFC starts with plain signals and no provenance. Over time it adds accountAggregatorProvenance to income signals, then documentProvenance to document-extracted signals. Because provenance is optional and extractSignalValues handles both plain values and GovernedSignal wrappers, there is no breaking change and no flag day — each integration is added incrementally.

See also