DEV Community

dengyier
dengyier

Posted on

OpenWorkProof Protocol Specification

This specification is the product of a public design conversation (2026-08-08 → 2026-08-12) across LinkedIn and Dev.to. Every primitive below was shaped by named community contributions; each section cites its origin. The protocol's governing principle, stated by the community and adopted here:

Verification must itself be tested. A check that never fails has never been checked.


1. Purpose

OpenWorkProof is a protocol for verifying AI agent work: producing signed, auditable evidence that a piece of agent execution happened as claimed, and that the verifier producing that evidence is capable of detecting failure.

Two distinct claims are always separated in this protocol:

Claim Mechanism Meaning
Authenticity Signature over a receipt This work was executed and attested as stated
Verifier capability Negative control arm in the receipt The verifier would have caught a lie

Signatures alone prove the first. They say nothing about the second — the ln.strip() lesson (Section 4.1).

2. Scope & Non-Goals

In scope:

  • Receipt formats for verified executions (positive arm + negative arm).
  • Guard definitions and guard inventory semantics.
  • Negative control contracts (provocation contract, digest pinning).
  • Population manifests (sampling honesty).
  • Temporal validity: retraction, policy state, scope change.
  • Trust model and bootstrap.

Non-goals (for now):

  • Model evaluation or benchmarking (evals measure models; we measure executions).
  • Tracing / observability (logs record what happened; receipts let third parties verify it).
  • Attestation of environment (TEE / enclave proofs prove where something ran, not that it was correct).
  • Policy judgment semantics (that is the JPS layer; OWP carries authorization bindings).

3. Terminology

Term Definition
Guard A verification check: command + assertion + expected failure behavior
Guard inventory The set of guards an operator runs, classified proven / unproven / broken
Negative control A deliberately broken input, run on every CI pass, asserting the guard goes red
Provocation contract The formal spec of what a negative control provokes (exit code, stderr pattern, schema scope)
Receipt Signed evidence of one verification event
Positive arm The "did it pass" side of a receipt: test suite, result, population
Negative arm The "would it catch a lie" side: control fixture, control result, control target
Population manifest The honest enumeration of what a check was supposed to examine
Eligible seen Pre-selection count: what reached the gate
Selection loss The auditable gap between eligible_seen and population_size
Rot Silent decay of a check's capability while output stays green (three kinds: guard, control, population)

4. Failure Model

The protocol exists because verification can be green and structurally meaningless. The failure model is explicit.

4.1 Structural death (ln.strip())

A production gateway ran a signed, audited check that reported verified: true on every run for months. The bug: a stray newline pushed an assert below a return, so the assertion never executed. Exit code 0. Verdict verified.

Measured impact (fintech engineer's post, 2026-08-09):

Status Count
Guards total 40
Proven (can detect failure) 7
Broken 0
Unproven 33

Five of eight caller-test shapes produced false passes. The agent had "passed 2,283 tests" and failed in production.

Lesson: the verifier is part of the system being verified. It must be tested with inputs designed to make it fail.

4.2 The three decay modes (rot)

Even with negative controls in place, a check can go silently dead three ways (community taxonomy, 2026-08-12):

Rot Failure Countermeasure Origin
Guard rot Guard stops catching real failures Continuous negative control on every CI pass Max Quimby
Control rot Control stops testing the right failure (recall/precision of the test itself) Digest pinning + control_schema_version Skillselion
Population rot Guard examines the wrong population, or none at all eligible_seen in the population manifest Tom Jones

All three can produce a green checkmark while being structurally meaningless. All three need different countermeasures.

4.3 Catch-rate measurability (23 of 41)

Production data (Ethan Walker, 2026-08-12): a gate caught 23 of 41 known degradations — a 56% catch rate — across eleven green weeks in which nobody asked what fraction it catches.

Lesson: every guard must be measurable against known-bad inputs. The negative control is the cheap, proactive version of the expensive forensic replay Ethan had to do retroactively.

5. Core Primitives

5.1 Guard & Guard Inventory

A guard is a check plus a control:

guard:
  id: gw_check_response_shape
  description: "Every gateway response matches the documented schema"
  command: "check_response.sh"
  assertion: "schema_validate $INPUT"
  status: unproven        # proven | unproven | broken — set by control runs
  controls: [gw_control_null_handling]
Enter fullscreen mode Exit fullscreen mode

The guard inventory is a published, versioned list — not a private detail. It is the unit of honesty:

guard_inventory:
  schema_version: "1.0"
  generated_at: 2026-08-12T00:00:00Z
  totals:
    guards: 40
    proven: 7
    unproven: 33
    broken: 0
  guards: [ ... ]
Enter fullscreen mode Exit fullscreen mode

"Proven" is defined operationally: a guard is proven only while its negative control fails as expected. The moment the control passes (green on broken input), the guard is reclassified unproven or broken. Proven is a time-decaying label, not a permanent badge (Max Quimby: guard rot).

5.2 Negative Control (provocation contract)

A negative control pins the exact broken input and the exact expected failure, and scopes itself to schema versions (Skillselion: control rot / digest pinning):

negative_control:
  fixture_digest: sha256:abc123...            # The exact broken input
  expected_failure_digest: sha256:def456...   # The exact failure signature
  control_schema_version: 2                   # For schema migration tracking
  control_spec:
    target_schema_version: ">=1.0, <3.0"      # Valid for these schema versions
    provocation_type: null_handling           # What class of failure it tests
    expected_exit_code: non-zero              # Minimum bar
    expected_stderr_pattern: "NullPointerException"  # Specific signal
Enter fullscreen mode Exit fullscreen mode

Rules:

  1. Pin by digest, never by path. The fixture and the expected failure are referenced by digest so "proven" is a verifiable claim, not a historical observation.
  2. Every control declares its provocation type. A control that stops testing the right failure class (recall) or that tolerates wrong failures (precision) is control rot — detected by comparing control_schema_version and expected_failure_digest over time.
  3. A control is valid only within its target_schema_version range. Two schema migrations later, the old control is not silently reused — it must be re-blessed.

5.3 Population Manifest (sampling honesty)

A guard's scope must be auditable. The manifest distinguishes what reached the gate from what passed selection (Tom Jones, third round):

population_manifest:
  selection_rule: "threads we have commented in"   # What we HOLD
  eligible_seen: 400                               # What reached the gate (pre-selection)
  population_size: 12                              # What passed selection (post-selection)
  population_digest: <merkle_root>                 # Tamper-evident enumeration
  sampling_rate: 1.0                               # 100% = no sampling
  effective_from: <timestamp>                      # When the rule was authoritative
Enter fullscreen mode Exit fullscreen mode

Semantics:

  • eligible_seenpre-selection count: did the collector even see the things it was supposed to check?
  • population_sizepost-selection count: of the things it saw, how many passed the filter?
  • The gap is selection loss — the auditable delta that turns a silent failure into a detectable one.

Canonical decision table (Tom Jones):

Scenario eligible_seen population_size Meaning
Healthy instrument 0 0 Nothing to do, nothing expected
Broken collector 400 0 400 things should have been checked, 0 were

Without eligible_seen, both scenarios produce the same receipt. With it, the broken case is a live, self-reporting rot signal on the day it happens — not at the next review.

Operational rule: check the set you HOLD, not the set you FETCHED. The selection_rule must be defined over the population you intend to cover, and eligible_seen proves the collector reached it.

Reference scenarios (included in Appendix A with attribution): the thread monitor and the sampler.

5.4 Receipt — DualArmReceipt v1

The signed unit of verification. One payload, two arms (Cophy Origin: the receipt–content gap; Mikhail: dual-arm verification):

dual_arm_receipt:
  schema_version: "1.0"
  claim:
    task_id: owp-task-20260812-001
    description: "Refund processed for order R-4491"
    result: done
    output_digest: sha256:9f8e...               # What was actually produced
  positive_arm:
    test_suite_digest: sha256:77aa...
    test_result: pass
    population_manifest:                       # 5.3 — what the pass covered
      selection_rule: "refunds with amount > 0"
      eligible_seen: 113
      population_size: 113
      population_digest: sha256:31cd...
      sampling_rate: 1.0
      effective_from: 2026-08-12T00:00:00Z
  negative_arm:                                # 5.2 — would the verifier catch a lie?
    control_fixture_digest: sha256:abc123...
    control_result: fail-as-expected
    control_schema_version: 2
    control_target: guard:gw_check_response_shape
  signature:
    algorithm: ed25519
    key_id: owp-key-issuer-01
    value: 0x...
Enter fullscreen mode Exit fullscreen mode

Rules:

  1. A receipt without a negative arm is a log, not evidence. The negative arm is what lets a third party check the verifier's capability — it answers "would this receipt have screamed on a lie?"
  2. File existence ≠ content correctness. A receipt must pin output_digest (what was produced), not merely record that a file exists.
  3. control_target ties the control to the specific guard it proves, so arm and guard cannot drift apart.

5.5 Temporal Validity — RetractionReceipt v0.2

A receipt is a bounded claim: "this was true under these conditions at this time" (Suraj Suradkar). Obsolescence is tracked, not hidden.

retraction_receipt:
  parent_receipt_id: owp-receipt-20260812-001
  retraction_auth: <PolicyDecision>            # Independent trust boundary; co-signed
  propagation_class: none | downstream_causal | same_predicate
  semantic_cause:                              # Open, versioned enum
    category: superseded_by | refuted_by | scope_changed
    superseding_receipt_id: owp-receipt-20260812-009
    valid_until: 2026-08-20T00:00:00Z
Enter fullscreen mode Exit fullscreen mode

Categories (Suraj, refining Mikhail's v0.2):

Category Meaning Consequence
superseded_by Decision was correct then, no longer current Old receipts: not invalid, bounded
refuted_by Old evidence was wrong Old receipts: retrospective scope loss
scope_changed Population/policy shifted Old receipts: incomplete relative to new scope

Design constraints (Mikhail):

  • REVOKED / SUPERSEDED / EXPIRED are overlapping flags, not a mutually exclusive enum — a receipt can be superseded and expired simultaneously.
  • retraction_auth is an independent trust boundary: the key that issued a guarantee should not be able to unilaterally cancel it (co-signed by a non-issuer).
  • propagation_class lives in the protocol layer; semantic_cause in the application layer.

5.6 Policy-State Registry & PolicyAnchor

Policies and their checkpoints are versioned in a monotonic registry (Brian Jin, rounds 2–3):

policy_checkpoint:
  policy_id: pol-refund-eligibility
  revision: 4
  artifact_digest: sha256:e0b1...
  effective_from: 2026-08-12T00:00:00Z
  previous_checkpoint_digest: sha256:c2a9...    # Monotonic chain
  signature:
    key_id: owp-key-policy-01
    value: 0x...
Enter fullscreen mode Exit fullscreen mode
  • Monotonic revision chain: every checkpoint references its predecessor, so policy authority has a tamper-evident history.
  • Dual timestamps on receipts: a receipt's validity has an execution time (when the agent ran) and a policy time (which policy revision was authoritative). If a policy is retracted, anchored receipts become retrospectively scoped — bounded, not invalid.
  • PolicyAnchor sits at Layer −1 of the stack (Section 8): it is the point where authority itself is pinned, not an output of the verification pipeline.

6. Verification Flow (end-to-end)

┌─ Every CI pass ─────────────────────────────────────────────┐
│ 1. Define  guard + negative_control (provocation contract)   │
│ 2. Run positive arm:   suite → test_result, population_manifest
│ 3. Run negative arm:   broken fixture → control_result        │
│    — control must FAIL as expected, else guard reclassified   │
│ 4. Package DualArmReceipt (claim + positive + negative)       │
│ 5. Sign with issuer key; publish to registry                  │
│ 6. On policy/scope change: emit RetractionReceipt             │
└──────────────────────────────────────────────────────────────┘
Enter fullscreen mode Exit fullscreen mode

Outputs:

  • A guard inventory updated every run (proven/unproven/broken counts).
  • A receipt chain with temporal validity.
  • A policy registry with monotonic revisions.

7. Trust Model & Bootstrap

  • Minimal bootstrap: a trusted authority key + a signed initial checkpoint. Nothing more is required to start; nothing less is honest.
  • Single-operator registry is a consistency proof, not a uniqueness proof: it proves all checkpoints descend from one chain; it does not prove no other chain exists. Multi-operator/multi-key registries are a future work item.
  • Transparency log: checkpoints are appended publicly so that retroactive editing is detectable even by parties who do not trust the operator.
  • Retraction authority is independent (5.5): the issuer cannot unilaterally revoke its own guarantees.

8. Layered Architecture

Causal order (community-corrected model; PolicyAnchor is Layer −1, not the top):

Layer Concern Primitive
Policy history / authority What rules were in force when Policy-State Registry, PolicyAnchor
Judgment What decision was authorized JPS layer (external)
Execution What the agent did claim + output_digest
Evidence That verification worked DualArmReceipt (positive + negative arm)

Evidence is the result of the pipeline; authority is its input. Confusing the two was the original ln.strip() error — evidence was signed while authority was dead.

9. Verification Maturity Model (Level 0–5)

(Glen Allen: "verification must itself be tested".)

Level State Signal
0 Trust the agent's own report No independent evidence
1 Logs exist "It ran" (unverifiable claim)
2 Signed receipts Authenticity proven
3 Negative controls Verifier capability proven for fixtures
4 Continuous controls + guard inventory published Capability continuously re-proven; rot visible
5 Controls are themselves tested (control rot guarded) The verifier's verifier is verified

Target for production adoption: Level 4, with Level 5 as the differentiator.

10. Adoption Path (two tiers)

(Sri Ramya: layered adoption — execution evidence first, signed receipts second.)

  • Tier 1 — Human-verifiable evidence: logs, video, replay. Zero protocol cost; establishes the habit of "show the work."
  • Tier 2 — Machine-verifiable receipts: signed DualArmReceipts, continuous controls, published inventory. What this specification defines.

Teams adopt Tier 1 first; Tier 2 is the upgrade path, not the entry requirement.

11. Open Questions

  1. eligible_seen: is it total events or matched events? Do we need a third count, total_seen?
  2. Multi-operator registry: how do independent signers converge on one chain?
  3. control_schema_version migration: what is the deprecation protocol for a control whose target_schema_version range expires?
  4. Interaction between TEE attestation and the negative arm: does environment attestation strengthen or complicate control claims?
  5. The semantic_cause enum: who curates versions, and how do downstream consumers handle unknown values?

12. Acknowledgements

This specification was co-designed in public. Contributions by:

  • The fintech engineer behind ln.strip() — the 40/7/33 guard inventory and the negative control that exposed structural death
  • Ethan Walker — 23 of 41, eleven green weeks, catch-rate measurability (4.3)
  • JinHyuk Sung — false-Done measurements motivating verifier capability as a first-class claim
  • Max Quimby — guard rot, continuous negative control (4.2)
  • Tom Jones — population manifest, eligible_seen, FETCHED vs HOLD, reference scenarios (5.3, Appendix A)
  • Skillselion — control rot, digest pinning, provocation contract (5.2)
  • Glen Allen — verification must itself be tested, maturity model (§9)
  • Suraj Suradkar — decision lifecycle, bounded claims, retraction categories (5.5)
  • Cophy Origin — receipt–content gap, DualArmReceipt (5.4)
  • Brian Jin — Policy-State Registry, bootstrap, PolicyAnchor (5.6, §7)
  • Mikhail — RetractionReceipt v0.2, dual-arm verification, independent retraction auth (5.5)
  • Sri Ramya — two-tier adoption path (§10)

Appendix A. Reference Scenarios (population manifest)

Attributed to Tom Jones (2026-08-12), canonical for the population manifest:

Scenario 1 — The thread monitor. A sampler configured to measure, on 100% of eligible events, how often two models agree on correctness. Four days produced zero rows, while the box served 113–209 requests/day. Every part of the receipt was valid: selection rule correct, tool ran, exit 0, signature valid. The population was empty because the eligible shape was too narrow. eligible_seen = 400, population_size = 0 exposes the broken collector the day it happens.

Scenario 2 — The sampler. An invite sampler claims to have sampled 12 threads. Without a manifest, the claim is unfalsifiable. With selection_rule + eligible_seen + population_size:

"You sampled 12 of 400" invites an argument. "You sampled 12" ends one.

Appendix B. Schema Versioning Policy

  • Every YAML primitive carries a schema_version.
  • negative_control.control_schema_version tracks migration of control fixtures; a control is only valid within control_spec.target_schema_version.
  • Receipts pin the schema versions of both arms, so old receipts remain interpretable after migration.
  • Breaking changes require a new major schema version; old versions remain valid for verification but are deprecated for issuance.

Top comments (4)

Collapse
 
kikashy profile image
Brian Jin

Congratulations on getting v0.3 written down, and thank you for the attribution in §5.6 and §12.

I read the checkpoint section against the current OWP schemas and the policy-currency work we have been discussing. I think the direction is promising, but there are four places I would tighten before treating the new model as a complete verification path.

1. The receipt still needs an explicit binding to the policy checkpoint.

§5.6 describes two relevant moments: when the action happened and which policy revision was authoritative.

But the receipt structure shown in §5.4 does not currently bind those together. The population_manifest.effective_from field belongs to the selection rule, not to the execution itself, and the printed receipt does not directly or transitively identify a policy_checkpoint.

I think the OWP side needs to define this part because only OWP can say what its receipt means.

Conceptually I would expect the receipt to bind at least:

receipt identity

execution evidence / execution time

policy identity
policy revision
policy checkpoint digest
Enter fullscreen mode Exit fullscreen mode

The exact shape is yours, but without that join the verifier can validate a checkpoint and validate a receipt without proving which checkpoint the receipt relied on.

There is also a smaller schema issue here: §5.5 refers to parent_receipt_id and superseding_receipt_id, but the §5.4 receipt as printed does not mint a receipt_id. The shipped v0.1 schema already has a stronger receipt identity/sequence shape, so I would preserve that property rather than lose it in v0.3.

2. effective_from needs one explicit meaning.

The policy-currency prototype we actually ran uses logical registry order for verification. effective_from is signed and retained, but the verifier never compares it.

That was intentional.

The moment effective_from determines whether an execution was valid, the verifier needs a trusted time source and a rule connecting execution time to registry time.

So I think v0.3 needs to make a choice explicit:

  • effective_from is descriptive metadata while checkpoint sequence/revision determines ordering, or
  • wall-clock time is normative, in which case the protocol also needs to state what clock is trusted and how execution time is bound to it.

Right now the draft appears to contain both concepts without yet defining the relationship between them.

3. I would narrow the strongest claim in §7.

"Single-operator registry is a consistency proof, not a uniqueness proof" is exactly the right boundary.

The next claim, that retroactive editing is detectable even for parties that do not trust the operator, needs another qualification.

If a verifier already retains an authenticated earlier head, changing that prefix is detectable.

But a fresh verifier given one internally consistent history cannot know that the operator showed someone else a contradictory history.

That is the split-view problem.

We tested the smallest witnessed version of it after Study 016. One conflicting sighting reaching the comparator can make a registered fork observable, but that result depends on the witness contract and does not create uniqueness by itself.

Study:

github.com/Judgment-Pack/judgment-...

Witness-contract draft:

github.com/Judgment-Pack/judgment-...

So I would scope §7 to the detection machinery actually specified in v0.3 and leave general split-view resistance as an open problem.

4. I would resolve the repository license metadata before external design work lands.

The repository LICENSE and server.json identify Apache-2.0, while pyproject.toml, mcp.json, and the currently published package metadata still identify MIT.

I know you already acknowledged the issue when I raised it. I am mentioning it again only because it affects contribution provenance: before I put implementation or format work directly into the OWP repository, I need the repository and distributed package to tell contributors the same license story.

A few smaller consistency items are worth cleaning up at the same time:

  • Appendix B says every YAML primitive carries schema_version, but several examples do not.
  • §5.6 refers to PolicyAnchor as "Layer -1 of the stack," while §8 does not currently define numbered layers.
  • Where §12 credits public work, linking the underlying RFC or study would make the attribution independently checkable.

The offer from the earlier thread still stands: open the GitHub Discussion you proposed and I'll bring the policy-currency RFC, witness-contract work, and study matrices there.

The piece I think only OWP can supply is still the most interesting one: the signed receipt-side binding between execution and the policy state it claims governed that execution.

Collapse
 
mansio profile image
Mikhail • Edited

I’ve run a comprehensive architectural audit on v0.3, mapping the spec's primitives to their prior art and analyzing the operational boundaries.

The spec is essentially a brilliant synthesis of proven technologies: RetractionReceipt maps to PKI CRL/OCSP; Negative control maps to Mutation Testing (PIT/Stryker); Transparency logs map to Certificate Transparency; Population manifest maps to Coverage/SBOM. This synthesis is a strength—these components are battle-tested. But it also means we inherit their known empirical failures, which an adversarial simulation highlights:

1. The OCSP Soft-Fail Trap (RetractionReceipt)
Browsers have soft-failed revocation checks for 15+ years because hard-fails break the web. My adversarial simulation showed that AI agents do the exact same thing: if checking a RetractionReceipt adds latency or token cost, the agent will quietly trust the old receipt to save compute. Without enforced hard-fail semantics on UNKNOWN/REVOKED lookups, the retraction layer is a paper tiger that agents will bypass under pressure.

2. The Certificate Transparency Illusion (Transparency Logs)
CT proves that a certificate was published, but not that it is legitimate. It logs lies perfectly. OWP's transparency log does the same: it makes hallucinations auditable, but it doesn't prevent them from entering the context window. The log is a ledger of claims, not a source of truth.

3. The Split-View Problem (Single-Operator Registry)
As @kikashy pointed out, a fresh verifier given one internally consistent history cannot know that the operator showed someone else a contradictory history. This is the exact definition of the echo-chamber trap. Without an independent census (like the AST git HEAD I use in MSCodeBase), the verifier is just trusting the operator's local view.

4. The SBOM Blind Spot (Population Manifest)
SBOMs and Coverage reports are self-reported by the CI/Builder. If the CI is blind or compromised, the manifest is garbage. eligible_seen is self-reported by the checker. A lying or broken collector can write any number. The digest protects the list from tampering after signing, but not from incorrect collection before signing.

The core question for v0.4: What in the protocol mandates a hard-fail on unreachable retraction state, preventing agents from silently trusting stale receipts? If we don't solve the soft-fail problem, OWP inherits the exact empirical failure mode that made OCSP ineffective in the real world.

Collapse
 
mansio profile image
Mikhail

Red team audit: six attacks, six successes — and what they tell us about the protocol's actual scope

Thank you for v0.3. I ran adversarial simulations against every primitive described here plus the patches that Brian and Mikhail are implicitly proposing. Six attacks, six successes — but not in the way that breaks the protocol. What they reveal is a precise boundary the spec should state explicitly.

Attack 1 — Control theater (extends Skillselion's control-rot)

fixture_digest pins which bytes were run. It does not pin that those bytes exercise the guard's declared provocation_type. A fixture that fails for a completely unrelated reason (say, malformed JSON that crashes the parser before the assertion fires) satisfies the negative control contract mechanically — guard goes red, digest matches, receipt is signed. The guard is classified "proven" while its actual assertion path was never touched.

The spec already has provocation_type in control_spec. The missing piece is a conformance rule: the fixture MUST be validated against its declared provocation_type before a guard can be promoted to "proven." Without it, digest pinning stops fixture-swapping between runs but not a bad fixture being pinned in the first place.

Attack 2 — Collector fabrication (Mikhail's SBOM blind spot, confirmed not closed)

I simulated a compromised collector that invents 400 plausible fake records, hashes them, and reports eligible_seen=400. The resulting population_digest is internally consistent and verifiable — it verifies a lie. The digest tamper-evidence only proves the enumerated list wasn't altered after hashing. It says nothing about whether the list corresponds to reality.

This is Mikhail's fourth point, and it is not closed by any patch in the current direction. The collector_witness I described as optional in earlier notes should be MANDATORY for any receipt used in a material decision — a gateway-level independent counter that the collector itself cannot write.

Attack 3 — Staple race window (new)

If retraction staples are adopted (the OCSP Must-Staple analogue Mikhail is pointing toward), the consumer bypass drops to 0%. But the underlying race doesn't disappear — it migrates. Simulating exponentially-distributed detection delays (mean 60s) against a 300s max-age: roughly 15% of compromise events land inside the valid window and aren't detected before the staple expires. The attacker no longer exploits agent laziness; they exploit max_age being long relative to detection speed.

Shortening max_age helps but trades off background refresh cost. This is a real, not cosmetic, residual risk, and v0.4 should name it explicitly rather than present stapling as a complete solution to soft-fail.

Attack 4 — Checkpoint stuffing (new — Fix3's scope boundary)

I tested whether a holder of a signing key could mint a new "legitimate" checkpoint with self-serving content and have it pass Fix3's validation. Result: valid=True, chain internally consistent. Fix3 proves binding — that this receipt used checkpoint X. It does NOT prove that checkpoint X's content was legitimately authorized by anyone other than the key holder.

This is explicitly out of scope per §2 Non-Goals ("Policy judgment semantics — that is the JPS layer"). But the safety claim needs to be stated with that precision. "Signed receipt bound to a policy checkpoint" is not the same as "policy checkpoint was legitimately authorized." If v0.4 doesn't say this loudly, someone will over-trust it.

Attack 5 — Split-view (Brian Jin's point, confirmed independently)

I reproduced this from first principles without needing to trust Study 017's results. Two verifiers, each receiving an internally consistent but mutually contradictory chain from the same operator: both pass local validation, divergence is invisible to either. Brian Jin is exactly right, and Study 016/017's bounded claim matches what the math shows: one conflicting sighting reaching a comparator can make a fork observable, but observability requires a witness; the spec's current transparency log does not provide one.

Having now read RFC 0012 in full: the seven candidate clauses there (attribution, delivery, enforcement, coverage, retention, recency, non-collusion) are precisely the right framework. Clause 5 (retention) has no measurement at all by the study's own admission. Clause 7 (non-collusion) lands where Study 017 honestly ends: "quorum and accountability can be mechanised; independence is a governance property." OWP should adopt that framing verbatim for §7 rather than restating it weaker.

Attack 6 — Issuer collusion (the capability/honesty gap)

The negative arm proves the verifier CAN catch a lie. It does not prove the positive arm of THIS specific receipt was run honestly. An issuer can run the negative control faithfully on a generic fixture (cheap, reusable, always passes) while writing an arbitrary test_result: pass on the positive arm. Both arms individually valid, signature valid.

The spec's own §7 caveat acknowledges this ("consistency proof, not a uniqueness proof"). Making it concrete: the current protocol is an honesty commitment device for parties who intend to be honest — it makes cheating auditable, not impossible. The defense against a colluding issuer requires either an independent party running the positive arm or statistical re-audit (spot-replay of a sample of "pass" claims against real inputs). Neither is currently proposed.

The pattern across all six

Every attack converts a "the protocol doesn't close this" into a precisely scoped statement of what the protocol DOES close. None of them break Fix 1/2/3 as engineering. What they break is implicit scope claims around those fixes.

The common structure: the protocol converts SILENT failures into DETECTABLE schema-level failures. That is its real, achievable, and genuinely valuable goal. What it does not do — and should say it doesn't do — is convert a single authority into an independently verified one. That requires a witness layer that RFC 0012 correctly identifies as needing seven spelled-out clauses, none of which OWP currently specifies.

Concrete proposal for §2 Non-Goals, one addition:

Witnessing and split-view resistance. OWP receipts prove internal consistency within an operator's signed chain. They do not prove that no other chain was shown to a different verifier. Witnessing — the mechanism that makes equivocation observable across views — is out of scope for this version and requires an external witness contract (see RFC 0012 for the clause structure).

And one addition to the maturity model: Level 4 should note that eligible_seen is self-reported; Level 5 should require an independent witness for the population count to be considered externally verified rather than self-attested.

The protocol as written is solid and the direction is correct. These are boundary conditions, not structural problems.


Some comments may only be visible to logged-in visitors. Sign in to view all comments.