# OpenWorkProof Protocol Specification

> Source: <https://dev.to/dengyier/openworkproof-protocol-specification-3k8e>
> Published: 2026-08-12 14:53:14+00:00

**Status:** Draft v0.3 — community co-design

**Date:** 2026-08-12

**Repository:** [https://github.com/dengyier/OpenWorkProof](https://github.com/dengyier/OpenWorkProof)

**License:** TBD (project convention: open, permissive)

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.

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).

**In scope:**

**Non-goals (for now):**

| 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) |

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

`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.

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.

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.

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]
```

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: [ ... ]
```

"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).

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
```

Rules:

`control_schema_version`

and `expected_failure_digest`

over time.`target_schema_version`

range.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
```

Semantics:

`eligible_seen`

— `population_size`

— 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.

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...
```

Rules:

`output_digest`

(what was produced), not merely record that a file exists.`control_target`

ties the control to the specific guardA 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
```

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 `retraction_auth`

is an `propagation_class`

lives in the protocol layer; `semantic_cause`

in the application layer.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...
┌─ 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             │
└──────────────────────────────────────────────────────────────┘
```

Outputs:

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.

(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.

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

`DualArmReceipt`

s, continuous controls, published inventory. What this specification defines.Teams adopt Tier 1 first; Tier 2 is the upgrade path, not the entry requirement.

`eligible_seen`

: is it total events or matched events? Do we need a third count, `total_seen`

?`control_schema_version`

migration: what is the deprecation protocol for a control whose `target_schema_version`

range expires?`semantic_cause`

enum: who curates versions, and how do downstream consumers handle unknown values?This specification was co-designed in public. Contributions by:

`ln.strip()`

`eligible_seen`

, FETCHED vs HOLD, reference scenarios (5.3, Appendix A)`DualArmReceipt`

(5.4)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.

`schema_version`

.`negative_control.control_schema_version`

tracks migration of control fixtures; a control is only valid within `control_spec.target_schema_version`

.
