Skip to content

About

A provider-neutral evidence gate for provenance-aware aggregation, subject binding, freshness, and conservative abstention.

Topics

Resources

Code of conduct

Contributing

Security policy

Stars

0 stars

Watchers

0 watching

Forks

Minority Prophet Gate

An evidence-control plane for consequential agent actions. It lets clear policy decisions move quickly, demands verifiable evidence when uncertainty matters, counts independent evidence rather than repeated AI opinions, and prevents unsupported actions from reaching a runtime. It is vendor-neutral and can be embedded without replacing an existing model or agent framework.

Experimental preview: ready for simulation, adversarial evaluation, and shadow observation. It is not a production-security release. Production enforcement requires a real verifier, authorized adapters, durable state, protected runtime paths, and independent review.

Focused product pathway: ReleaseWarden

ReleaseWarden is the first narrow commercial adapter: evidence-backed production deployment approval for GitHub Actions. GitHub calls the App before a protected environment deploys; ReleaseWarden authenticates the webhook, collects check runs for the exact commit, applies deterministic requirements, groups evidence by explicitly configured independence domain, and returns a Gate decision.

The default shadow mode records what ReleaseWarden would decide while sending GitHub a non-blocking approval. enforce mode sends the actual approval or rejection. Neither mode treats repeated checks from one configured control domain as multiple independent roots. Start with the offline, zero-effect test:

python3 -m minority_prophet.release_warden simulate \
  --policy examples/releasewarden/policy.shadow.json \
  --payload examples/releasewarden/deployment-request.json \
  --checks examples/releasewarden/checks.pass.json

See RELEASEWARDEN.md for GitHub App registration, the one-repository test, handoff instructions, known limitations, and the exact point where a private-repository test requires GitHub Enterprise.

Attack it or shadow your workflow

What you are testing: whether the same MP Gate logic remains fail-closed when evidence is copied, contradictory, malformed, weak, or bound to the wrong action—and whether its shadow decisions reveal useful differences from your existing workflow. You are not testing signature security: the laboratory verifier is deliberately simulated and clearly labeled.

People and coding agents can attack the engine with their own JSON or JSONL—no API key or real runtime required:

python -m minority_prophet.byom_cli attack \
  --evidence examples/byom/messy-evidence.jsonl \
  --policy examples/byom/policy.json \
  --feedback byom-feedback.json

Attack and shadow commands are simulation-only and always perform zero runtime effects. Shadow mode consumes copies of workflow events and reports agreements and disagreements without joining the execution path. Read AGENT-QUICKSTART.md for the agent handoff contract, input formats, adversarial mission, shadow adapter, privacy-safe feedback, and the explicit path from evaluation to an installed enforcement integration.

Refuse manufactured consensus. A drop-in gate for multi-agent systems that counts independent, attested evidence roots instead of voices — so seven agents repeating one unverified guess, with the repetition on record, can never outvote two agents who actually checked.

Research status: REPLICA results pending canonical re-runs. The gate is a reference implementation; its library tests do not establish production safety.

Nine messages say a deploy is SAFE?             Voices:  SAFE 7 — UNSAFE 2
Seven of them trace to one security scan.       Roots:   SAFE 1 — UNSAFE 2
Two trace to independent failing test runs.     Verdict: BLOCK (flip cost: 1 forged root)

Why

Multi-agent consensus is currently counted by voice, and voices are free: copies, re-broadcasts, summaries, sybils, and conformity-injection attacks (e.g. MAD-Spear) all inflate agreement without adding evidence. This gate implements evidence-root aggregation, whose core properties are machine-checked under the stated model, not tuned:

  • T2 — Copy invariance: duplicating a claim whose parent is recorded can never change the verdict. The hypothesis is part of the theorem: a copy with no recorded parent is indistinguishable from a new root and is counted as one.
  • T1 — Immunity: given side-consistent attestations and an unchanged set of origins, the verdict is invariant under arbitrary corruption of who-copied-whom among the rest. Lineage accuracy between claims is irrelevant; only origins matter.
    • The origin-set hypothesis is load-bearing and was previously omitted here. Corruption that creates or destroys an origin — orphaning a claim so it becomes a root, or attaching a root under a parent — is outside T1, and the verdict may change. What T1 buys is indifference to who copied whom, not indifference to how many independent origins exist. Tolerance to origin-set error is a separate theorem, T5, with its own bound: a verdict survives if its margin exceeds the number of origin-set changes.
    • See FORMAL.md. The normative statement of every hypothesis is the theorem ledger in the research repository, not this list.
  • T4 — Margin flip condition: every decision ships with its attack price, and there are two prices, because there are two attacks. Message volume is worthless against both.
    • flip_budget — forgery: fabricating new independent attested roots on the losing side. Each moves the margin one unit, so flip_budget forgeries force abstention and one more reverses.
    • conversions_to_reverse — compromise: stealing the key of a root that already supports the winner and flipping it. That root leaves the winning side and joins the losing one, so each action moves the margin two units. This price is roughly half flip_budget, and it is the relevant one when the threat is key compromise. Quoting flip_budget alone overstates the attacker's cost by ~2× (research counterexample CE-03).
    • abstention_reachable_by_conversion — false at odd flip_budget. Conversions move the margin in steps of two and so preserve its parity: from an odd margin the attacker can never land on a tie. Abstention — the safe outcome — is simply not on the compromise path, and the cheapest attack goes straight to a confidently wrong verdict. Do not assume a thin margin degrades to "don't know"; at odd margins it does not.

Proof texts, an exhaustive machine verifier (all worlds ≤ 6 claims, 121,944 rewirings, 100k randomized instances, zero violations), benchmarks against Dawid–Skene / truth-discovery baselines, and the research paper draft live in the Minority Prophet research repository.

Mandate / Border continuity Gate

authorize_continuous_effect is the narrow authority lane. It consumes a Border-stamped border-intent-continuity/v0.1 receipt, rechecks its signature, audience, expiry, and revocation status, atomically consumes its nonce, and compares it with the exact candidate effect immediately before the runtime. Any mismatch or unavailable verifier/revocation/replay dependency returns a fail-closed block decision. The bundled in-memory nonce store is test-only; production must inject a durable atomic store.

This does not make Gate a translation layer. Mandate defines authority, Border proves that authority survived and narrowed across translations, and Gate only decides whether the exact final effect may occur now.

Quickstart

TrustAllVerifier below is an unsafe testing fixture for the bundled example. It must be replaced by a fail-closed production verifier before real use.

from minority_prophet import decide, TrustAllVerifier
import json

envelopes = [json.loads(l) for l in open("examples/pr482.jsonl")]
d = decide(envelopes, TrustAllVerifier(), proceed_side=1, min_flip_budget=1.0)

print(d.action)        # "block"
print(d.roots_for, d.roots_against)   # 1 2
print(d.flip_budget)              # 1.0 <- FORGED roots needed to change this
print(d.conversions_to_reverse)   # 1   <- COMPROMISED roots needed. Usually the
                                  #        smaller number, and the one to plan against.
print(d.diagnostics["abstention_reachable_by_conversion"])
                                  # False <- odd margin: a compromise attack cannot
                                  #          produce a tie, only a wrong answer.

Replace the one line in your orchestrator that says if enough_agents_agree: proceed with a call to decide(). Three actions come back: proceed, block, or escalate (abstention / thin margin / no verifiable evidence — a reason to ask a human, never a reason to proceed).

For belief formation, analysis, or any use that must not imply execution, call assess() instead. It returns an action-neutral evidence verdict and flip budget. decide() is the separate policy layer that interprets that assessment as proceed, block, or escalate.

Evidence-sensitive authorization example

examples/evidence_sensitive_authorization.py demonstrates the narrow case where this assessment adds information that ordinary authorization does not: seven agents approve a production deployment, but all seven copied one scan; two independent test runs reject it. The example produces this flow:

9 voices -> Minority Prophet assessment (1 SAFE root, 2 UNSAFE roots)
         -> separate authority policy (BLOCK)
         -> exact-action runtime boundary (0 effects)

Minority Prophet does not grant authority in this example. It reports the structure and strength of the evidence. A separate provider-owned policy interprets that assessment, and the runtime independently enforces the result. Run it from the repository root with python -m examples.evidence_sensitive_authorization.

The same runnable example includes a harder comparison. Twelve SAFE voices include six copies, two invalid forgeries, one expired observation, one claim bound to the wrong action, and two valid independent roots. Three fresh, bound, independently grounded UNSAFE roots oppose them. Head count, signature checking alone, and signature-plus-subject-plus-freshness still choose SAFE; independent-root assessment chooses UNSAFE and the runtime executes zero effects. This is an illustrative adversarial case, not a population-level accuracy benchmark.

Optional memory-evidence socket

Gate does not require a memory system. When an action explicitly depends on a remembered approval, observation, or prior event, callers may pass a neutral Memory Evidence Interoperability Profile to selective_decide together with the expected proposition, memory-object, request, and action digests.

The consumer is deliberately asymmetric:

  • malformed, incomplete, unverifiable, or unknown evidence escalates;
  • revoked, expired, replayed, or incorrectly bound evidence blocks;
  • accepted memory proceeds only to the ordinary evidence assessment;
  • memory never grants authority and never overrides a deterministic denial.

The neutral profile is defined in the Minority Prophet research repository. Identity, signature, controller, and revocation providers remain external to Gate. The caller must persist consumed nonces across processes if replay protection is required; passing an in-memory set is only a local demonstration.

Security model — read this before trusting anything

TrustAllVerifier is for testing only. The guarantees above are conditional on attestation: origins must be unforgeable, which means signatures checked by a real production verifier implementing the 3-state AttestationVerifier interface (root / derived / invalid). Wire yours via CallbackVerifier. Rules the adapter enforces regardless:

  1. Default-derived: any claim without a verified fresh-root attestation is presumed an echo. Copying is the presumption; independence requires proof. Unverifiable claims are quarantined and contribute zero roots.

  2. Zero evidence ⇒ escalate: no verifiable roots is never a green light.

  3. Side-consistency at signing: assertion and origin are fused in one signed unit, so a claim cannot be re-labeled to the other side later.

  4. Verifier independence: a verifier is not trusted merely because it is a third party. Its rules must be transparent, it must remain independent of the evidence producer, it must expose uncertainty, and it must be unable to mint, alter, or promote the evidence it verifies.

    The current Gate consumes a verifier's root / derived / invalid classification; it does not discover shared organizational control by itself. Deployments must not return root merely because producer and verifier use different names, keys, services, or labels. When an upstream verifier returns derived or invalid, the Gate does not count an independent root; when evidence is empty, tied, or below policy margin, it escalates rather than converting uncertainty into permission. The HVI-1 research study defines the future control-domain experiment; it is not a present implementation claim.

Subject binding and freshness (R2.5)

For a decision about a specific subject, call decide(..., decision_subject="job:123"). Matching bound roots determine flip_budget and the min_flip_budget policy threshold. A legacy root without a subject remains visible during migration at the configurable unbound_root_weight=0.5, but never increases the strength score; this makes migration-mode flip_budget conservative. Set unbound_root_weight=0.0 for the documented strict end state.

Units, when weights are in play. With any root weight other than 1.0, flip_budget is a quantity of root mass, not a count of roots — it can come back 1.5, and half a forged root is not something an attacker can buy. Every verdict therefore reports which it is:

field meaning
flip_budget_unit "independent roots" or "weighted root mass"
flip_budget_is_root_count True only when the number is a countable number of roots

conversions_to_reverse is always a count of actions, so it stays an integer in both modes and is the safer number to threshold on.

On T5, and what a threshold does not inherit from it. Earlier revisions of this file called min_flip_budget "the T5 safety floor". T5 states that a verdict with |margin| > k survives k root-set errors — but only between worlds that are side-consistent and carry the same assertions. That second hypothesis is necessary, not decorative: the research repository's CE-02 falsifies the version without it, and T5_needs_assert_fixed compiles a witness where a single side conversion flips a margin-2 verdict at zero root-set error.

So T5 covers misattribution — a root dropped, duplicated, or wrongly identified, with no assertion changed. It does not cover compromise, which changes assertions by definition. Measured on this aggregator, five supporting roots against two:

2 supporting roots removed (assertions preserved) -> verdict survives
2 supporting roots flipped (assertions changed)   -> verdict REVERSED

Same k. Setting min_flip_budget from T5 buys protection against the first column and none against the second. Price compromise with conversions_to_reverse, which is what it is for.

Subject and observed_at live in the signed Entry Stamp predicate, not in an issuer manifest: manifests answer may this identity testify?; stamps answer is this observation current and about this case? Derived claims must retain their root subject or are quarantined. Default freshness is configurable: probes decay with a five-minute half-life; start events expire after 24 hours; origins without a policy do not decay; a missing timestamp under a policy is expired.

What this gate does NOT do: verify signatures itself (bring a verifier), decide which tool boundaries count as roots (that's your runtime's stamping policy), or protect root signing keys (that's ops). The attack surface it leaves is exactly: compromise of root signing keys — stated per-decision by flip_budget.

State Reconciliation

reconcile() answers a different operational question: many status views, one job state. It counts receipt roots rather than board voices and returns None as unverified on a tie or a configured thin margin.

from minority_prophet import reconcile
import json

status_claims = [json.loads(line) for line in open("examples/state_reconciliation.jsonl")]
state = reconcile(status_claims, freshness={"half_life_s": 600})
print(state.state, state.flip_budget)  # exited, ~2.0

Rule: receipts are roots, everything else is derived, and derived claims never outrank a root. Freshness applies to roots only; a root without an observation time under a freshness policy is excluded conservatively.

Status

Selective decision ladder

selective_decide keeps explicit deterministic policy primary. Clear allows and denies do not invoke the provenance challenger. Only an action marked evidence-sensitive (or returned as review) enters independent-root assessment. Contradictory evidence blocks; balanced, missing, or thin evidence escalates to a separately authorized human by default. Escalation is not permission, and evidence cannot override a deterministic deny.

Bounded evidence collection

An operator may opt unresolved decisions into a fourth selective outcome: request_evidence. This means not yet. The protected action remains frozen while an external orchestrator asks the agent to collect specified kinds of evidence and return them for another complete Gate evaluation.

from minority_prophet import (
    CollectorRoute, DeterministicDecision,
    EvidenceRequestPolicy, EvidenceRequirement,
    TrustAllVerifier, selective_decide,
)

collection = EvidenceRequestPolicy(
    requirements=(EvidenceRequirement(
        "fresh-tests",
        "Collect fresh test-run receipts bound to this action",
        CollectorRoute(
            "requesting-agent-tests", "requesting_agent", "test.execution",
            "candidate_evidence",
            ("repository.read", "test.run"),
        ),
        ("test.receipt",),
    ),),
    max_rounds=2,
)
primary = DeterministicDecision(
    "review", "current evidence required", policy_id="deploy-policy-v3",
)
first = selective_decide(
    primary, [], TrustAllVerifier(),
    decision_subject="deploy:123",
    action_digest="sha256:exact-action-payload",
    evidence_request_policy=collection,
)
assert first.action == "request_evidence"
assert first.evidence_request.grants_authority is False

# The orchestrator performs only separately authorized collection, retains the
# original request in trusted state, and returns new attested envelopes.
final = selective_decide(
    primary, returned_envelopes, production_verifier,
    decision_subject="deploy:123",
    action_digest="sha256:exact-action-payload",
    evidence_request_policy=collection,
    prior_evidence_request=first.evidence_request,
)

Each challenge is bound to the exact action digest, decision subject, policy identifier, policy contents, and collection round. It includes a hash-chain ID, neutral evidence requirements, collection scopes, an item cap, and a round cap. The requirements describe observations to obtain; they must not tell the agent which conclusion to prove. When the round budget is exhausted, the route becomes human escalation. A contradictory result blocks normally.

When using the bundled runtime contract, pass RuntimeAction.binding_digest as action_digest; it covers the action ID, type, target, and payload digest.

The allowed collection list is not an authority grant. Each search, read, probe, or test still needs authorization from the surrounding system. The protected runtime rejects request_evidence as non-final and performs zero effects. Keep issued challenges in a trusted durable ledger: the bundled hash identifies and binds request contents but is not a signature and cannot authenticate state supplied solely by an untrusted agent.

Drop-in evidence-control plane

EvidenceControlPlane closes the orchestration gap for an embedded install. It sits in front of one consequential runtime boundary and runs this bounded loop:

frozen action
  -> deterministic policy + Minority Prophet Gate
  -> proceed / block / escalate -----------------------> runtime adapter
  -> request_evidence -> authorized collectors -> verifier -> Gate again

Only a final Gate outcome reaches RuntimeController. A collection request, collector permit, returned artifact, or verification artifact cannot execute the protected action. Every challenge retains the exact RuntimeAction.binding_digest, and the controller preserves the existing exactly-once runtime boundary.

from minority_prophet import (
    CandidateEvidenceBridge, EvidenceControlPlane, EvidenceControlPolicy,
    RuntimeController, VerifiedEvidenceBatch,
)

plane = EvidenceControlPlane(
    evidence_router,
    CandidateEvidenceBridge(production_attestation_verifier),
    RuntimeController(),
)
outcome = plane.run(
    frozen_runtime_action,
    EvidenceControlPolicy(
        deterministic_policy, evidence_request_policy,
        decision_subject="deploy:123",
        requester_control_domain="agent:orchestrator",
    ),
    VerifiedEvidenceBatch(initial_envelopes, production_attestation_verifier),
    customer_runtime_adapter,
)

CandidateEvidenceBridge is the light path: candidate assertions return to Gate and are classified by the injected verifier. TrustAllVerifier is not a deployment verifier. A hardened installation may put Minority Prophet Border or another admission service on the evidence-input side. Admitted evidence still returns to Gate for the decision; Border never replaces Gate or runtime enforcement.

The shim does not require a particular model, agent framework, cloud, evidence service, or runtime. Integrators implement the small collector, verifier, and runtime protocols.

Signed-receipt reference path

The experimental preview now includes the smallest production-shaped path for assembling several agents' work without trusting their declarations:

tool/API interception -> durable case -> signed receipts -> verifier
                      -> MP Gate -> in-process or HTTP runtime

AuthenticatedSqliteCaseStore groups asynchronous submissions under one frozen action and policy, rejects claim-ID rebinding, survives process restart, and detects modified or missing stored envelopes. It is an assembly boundary, not an authority source: callers decide when a case is ready and only Gate can authorize the protected runtime.

SignedReceiptVerifier is a fail-closed local reference verifier. It authenticates the complete envelope with an operator-managed issuer key and binds it to a decision subject, source root, issuer, and control domain. The first accepted use of a root_id can count as a root. Later uses count only as declared derivations from an already accepted member of that family; an undeclared reuse is quarantined rather than manufactured into another vote.

Run the harmless end-to-end example:

PYTHONPATH=. python3 examples/reference_signed_interception.py

It assembles three agent claims, collapses one repeated source into an echo, passes two independent signed roots to Gate, and invokes an allowlisted demo tool exactly once. No network service or model key is required.

The reference verifier uses shared-secret HMAC keys so an adopter can exercise the complete contract locally. It is not a universal identity system. For cross-organization production use, replace it with CallbackVerifier backed by asymmetric signatures, workload identity, Border, Nxtlinq, or the customer's attestation service. The verifier instance is scoped to one case. Its independence guarantee is only as strong as the trusted issuer's canonical assignment of root_id; signing two IDs for one underlying source would manufacture two roots and is therefore a verifier-boundary failure.

Gate autonomy profiles

Gate evidence decisions and autonomy are separate axes. Gate still returns proceed, block, request_evidence, or escalate. Only a final proceed may be resolved through an authenticated owner mandate into one of five increasing autonomy levels:

Level Gate release Runtime effect
observe report the decision none
recommend recommend the frozen action none
prepare validate and prepare the exact action none
act execute inside the mandate exactly one permitted attempt
emergency_act notify the owner, then execute bounded containment exactly one permitted attempt

AutonomyMandate binds the decision subject, maximum level, action types, targets, expiry, minimum independent roots, minimum attack price, optional exact action digests, reversibility requirement, and emergency permission. Requesting a level above the mandate is capped. An expired, weak, unbound, or out-of-scope mandate falls back to observe and performs no protected effect. block, escalate, and request_evidence can never be upgraded into action.

from minority_prophet import AutonomyLevel, AutonomyMandate

owner_mandate = AutonomyMandate(
    mandate_id="containment-v1",
    decision_subject="incident:742",
    max_level=AutonomyLevel.ACT,
    allowed_action_types=("isolate",),
    allowed_targets=("staging-server-7",),
    expires_at="2026-09-01T00:00:00Z",
    min_flip_budget=2.0,
    min_roots_for=2,
    require_reversible=True,
)

outcome = plane.run(
    frozen_runtime_action, policy, initial_evidence, runtime,
    autonomy_mandate=owner_mandate,
    requested_autonomy=AutonomyLevel.ACT,
)

The mandate object is not self-authenticating. Border, Nxtlinq, IAM, or another trusted mandate store must authenticate and revoke production mandates before they reach this layer. emergency_act additionally requires a notifier; if notification fails, execution fails closed.

Vendor-neutral evidence router

Each EvidenceRequirement carries an explicit CollectorRoute. Policy—not the requesting model—selects one of four neutral collector kinds:

Kind Use
requesting_agent Return candidate_evidence for ordinary Gate verification
epistemic_service Return a verification_artifact; an MP adapter may serve this route
human Produce a human_handoff, never a fabricated evidence root
program Return candidate evidence or a verification artifact, as policy specifies

EvidenceRouter selects adapters by route ID, collector kind, and capability. It rejects same-control-domain collectors when the route requires independence, and it requires a separately bound CollectionAuthorization before calling any adapter. An allowed collection action never authorizes the protected action. An epistemic-service result cannot carry an assertion: it informs the verifier how to classify the original evidence and is never counted as another vote.

The router automatically writes hash-chained audit events for challenge receipt, route planning, authorization, dispatch intent, and collection return/failure; record_gate_decision appends the subsequent Gate handback. Returned evidence is stored separately and only its digest enters the audit log. The optional JSONL backend flushes and fsyncs each event; unresolved dispatches fail closed on retry.

See EVIDENCE-ROUTING.md for the adapter contract, policy mapping, audit events, and production boundary.

Evidence-sensitive policy may independently require both attack prices: min_flip_budget for newly forged opposing root mass and min_conversions_to_reverse for compromised winning roots. The latter is optional for backward compatibility. When configured, a missing or sub-threshold conversion price escalates; it can never inherit the deterministic allow.

This routing contract is an execution-safety policy, not a claim that the provenance method is globally more accurate than ordinary authorization rules.

Two reference runtime integrations exercise the neutral boundary: an allowlisted in-process tool adapter and an idempotent HTTP adapter. Both bind the exact payload digest and idempotency key. The HTTP target and any in-process handler must persist idempotency outcomes to provide crash-safe exactly-once effects; the reference controller's in-memory ledger alone does not provide that production guarantee.

v0.1.1 — reference implementation. Aggregator core is conformance-tested against spec-derived vectors and backed by exhaustively machine-checked theorems; the ACP adapter and gate policy are new and seeking adversarial review. Not yet battle-tested against hostile inputs in production. Issues, attack attempts, and counterexamples are actively invited — a counterexample to T1/T2 within the stated preconditions is a research result, and we will credit and publish it.

Repo layout

minority_prophet/aggregator.py   # machine-checked core: Claim, EvidenceGraph, aggregate()
minority_prophet/adapter_acp.py  # envelopes -> verified Claims (security model here)
minority_prophet/gate.py         # decide(): proceed / block / escalate + flip_budget
minority_prophet/evidence_request.py # bounded request-evidence challenge contract
minority_prophet/evidence_audit.py # append-only hash-chained event log
minority_prophet/evidence_router.py # neutral collector routing and dispatch
minority_prophet/case_store.py # durable asynchronous multi-agent case assembly
minority_prophet/receipt_verifier.py # fail-closed signed-receipt reference verifier
minority_prophet/autonomy.py # mandate-bound Gate autonomy profiles
minority_prophet/reconcile.py    # reconcile(): many status sources, one state
minority_prophet/runtime_adapter.py # neutral prepare/execute-once/prevent boundary
minority_prophet/runtime_integrations.py # in-process and idempotent HTTP adapters
examples/pr482.jsonl           # the 9-message scenario from the paper
tests/                         # unit + end-to-end attack tests + conformance vectors

Cite

See CITATION.cff. Research: the minority-prophet repository (paper draft, proofs, benchmarks E1–E8b).

License

Licensed under Apache License 2.0. See LICENSE and NOTICE.

About

A provider-neutral evidence gate for provenance-aware aggregation, subject binding, freshness, and conservative abstention.

Topics

Resources

Code of conduct

Contributing

Security policy

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages