Warren,

I think you're right about the request-semantics part.

An RS does not need an OAuth extension merely to receive an
application-specific precondition in a header or body and validate it. I
also agree that if all relevant predicates can be evaluated atomically with
the mutation, the problem is closed at the application layer.

I tightened the experiment around that distinction.

The case I am still trying to characterize is specifically where an
execution decision depends on mutable state controlled by an independently
changing authority.
For example:
target A is at v7;
compliance authority B is at v4/approved;
the RS receives both versions and validates them;
B changes to v5/suspended after that validation;
A remains at v7, so the target mutation can still commit.

Putting B:v4 in the request solves conveyance. Signing or propagating it
solves integrity. Neither alone appears to make B:v4 remain authoritative
until A's commit.

Conversely, when I model all predicates inside one atomic application
transaction, the race disappears. It also disappears with an explicit fence
over the external dependency.

So I no longer think the interesting question is whether OAuth needs to
carry application state. I think the narrower question is whether existing
mechanisms provide a generic commitment/ordering primitive when the
execution predicates belong to independently changing authorities, or
whether that necessarily remains application-specific coordination.

WebDAV's multi-resource If semantics are particularly relevant here, so I
am checking that case carefully as well.

If the answer is that applications must provide their own
transaction/lock/lease/fencing mechanism across those authorities, that may
simply be the correct architectural answer rather than a standards gap.

Best, Valentyn

On Mon, Sep 14, 2026, 00:01 Warren Parad <[email protected]> wrote:

> You can already put whatever you want in the body or headers of a request
> and an RS can already make whatever validations it wants to ensure that it
> does what the client asked for.
>
> If we already agree that oauth would not need to change here to
> accommodate the use case, I'm not sure what else you might be looking to
> do, as it fully depends on the RS expressing the semantics to allow the
> client to request what they actually want.
>
> On Mon, Sep 14, 2026, 08:38 Altru <[email protected]> wrote:
>
>> Warren,
>>
>> I think that distinction is important, and I agree that application state
>> such as available funds should not be captured in the authorization grant
>> itself.
>> The issue I am trying to isolate is narrower.
>> Using the withdrawal example:
>> OAuth can establish that the actor is authorized to request a withdrawal.
>> The application defines predicates such as sufficient funds.
>>
>> The execution path must ensure that any predicate required for the
>> operation is still true when the protected state transition commits.
>>
>> If the application evaluates that predicate atomically as part of the
>> commit — for example through a transaction, conditional mutation, or
>> equivalent mechanism — then that particular problem is already closed. I am
>> not suggesting OAuth should duplicate it.
>>
>> What I am testing is the weaker case where a decision depends on state
>> observed before execution, but that observation is not actually bound to
>> the subsequent mutation. In that case the authorization can remain valid
>> while one of the conditions under which execution was approved has become
>> stale.
>>
>> So I think the separation is:
>> authorization establishes authority;
>> application semantics define the required execution predicates;
>> the execution mechanism determines whether those predicates and the
>> resulting mutation are bound strongly enough to prevent an invalidated
>> decision from committing.
>>
>> The probe is intended to distinguish those cases, not to establish that
>> OAuth itself needs to understand balances, compliance state, or other
>> domain-specific semantics, and not yet to claim a standards gap.
>>
>> If existing HTTP/application mechanisms already provide that binding
>> wherever it is required, that is a perfectly valid outcome of the
>> investigation. The interesting question for me is whether that remains true
>> when authorization, state observation, policy evaluation, and execution are
>> separated across components.
>>
>> Best, Valentyn
>>
>> On Sun, Sep 13, 2026, 23:30 Warren Parad <[email protected]> wrote:
>>
>>> I don't understand why we are trying to put what is essentially API
>>> semantics into the authorization check.
>>>
>>> Surely access to do a thing like withdraw money from an account is
>>> predicated on their being available funds in the first place.
>>>
>>> This has no business being captured in the authorization.
>>>
>>> On Mon, Sep 14, 2026, 08:16 Iman Schrock <team=
>>> [email protected]> wrote:
>>>
>>>> Hi Valentyn,
>>>>
>>>> Thanks for sending the probe. I checked the SHA-256 and reran it. All
>>>> eight scenario outputs match your results.json.
>>>>
>>>> > [VR] fresh observation != commit-time guarantee
>>>>
>>>> [IS] T7b demonstrates that ordering in the model: the final read
>>>> passes, the approved compliance version changes, and the target
>>>> mutation still occurs. The unchanged-state control commits too.
>>>>
>>>> There is one test I'd strengthen before using this to compare
>>>> implementations. T4 calculates all_ok but never calls a guarded
>>>> execution path, so its zero effect doesn't yet test enforcement. Could
>>>> it call that path and assert refusal with zero effect, alongside a
>>>> no-race case that commits through the same path? That would catch a
>>>> guard being bypassed.
>>>>
>>>> For T7b, the code changes compliance.version but leaves
>>>> compliance.value at 1. I'd describe the current case as invalidation
>>>> of the approved version. If you mean an actual change from approved to
>>>> suspended, could that state change and its predicate be explicit too?
>>>>
>>>> > [VR] I can put it in a public GitHub commit next if that is the most
>>>> useful form for comparison.
>>>>
>>>> [IS] Yes, a pinned commit with the probe and results would give us a
>>>> shared baseline. Your README is right that this doesn't establish a
>>>> standards gap. I'd also say explicitly that these are in-memory cases
>>>> modeling the checks, not tests against OAuth, HTTP servers or
>>>> participating providers.
>>>>
>>>> Best,
>>>> Iman
>>>> _______________________________________________
>>>> OAuth mailing list -- [email protected]
>>>> To unsubscribe send an email to [email protected]
>>>>
>>>
cd9a66521a2fea6f018412d0f154c3d38809c266b9ed64f83e19f21feb805933  
ietf_commit_boundary_probe_v0_4.py
aa7723d69844b8f2494b6c8d3112fe779535dbb753fe5e71e1d1fa378a463332  
results_v0_4.json
from __future__ import annotations
from dataclasses import dataclass
from typing import Dict, Tuple
import json

@dataclass
class Resource:
    version: int
    value: int = 0

@dataclass(frozen=True)
class Approval:
    target_version: int
    external_versions: Dict[str, int]
    action: str = "increment"
    amount: int = 1

class World:
    def __init__(self) -> None:
        self.resources = {
            "target": Resource(7, 0),
            "budget": Resource(12, 100),
            "compliance": Resource(4, 1),
        }

    def snapshot(self) -> Dict[str, int]:
        return {name: r.version for name, r in self.resources.items()}

    def revoke_compliance(self) -> None:
        self.resources["compliance"].version += 1
        self.resources["compliance"].value = 0

    def mutate_version(self, name: str) -> None:
        self.resources[name].version += 1

    def target_if_match_commit(self, approval: Approval) -> Tuple[bool, str]:
        if self.resources["target"].version != approval.target_version:
            return False, "target_precondition_failed"
        self.resources["target"].value += approval.amount
        self.resources["target"].version += 1
        return True, "committed"

    def validate_external(self, approval: Approval) -> bool:
        return (
            all(
                self.resources[name].version == expected
                for name, expected in approval.external_versions.items()
            )
            and self.resources["compliance"].value == 1
        )

    def atomic_application_commit(self, approval: Approval) -> Tuple[bool, str]:
        if self.resources["target"].version != approval.target_version:
            return False, "target_precondition_failed"
        if not self.validate_external(approval):
            return False, "external_precondition_failed"
        self.resources["target"].value += approval.amount
        self.resources["target"].version += 1
        return True, "committed"

    def fenced_cross_authority_commit(
        self, approval: Approval, *, concurrent_revocation: bool
    ) -> Tuple[bool, str]:
        if self.resources["target"].version != approval.target_version:
            return False, "target_precondition_failed"
        if not self.validate_external(approval):
            return False, "external_precondition_failed"
        if concurrent_revocation:
            return False, "revocation_blocked_by_dependency_fence"
        self.resources["target"].value += approval.amount
        self.resources["target"].version += 1
        return True, "committed"

def authorize(world: World) -> Approval:
    snap = world.snapshot()
    return Approval(
        target_version=snap["target"],
        external_versions={
            "budget": snap["budget"],
            "compliance": snap["compliance"],
        },
    )

def run_matrix() -> Dict[str, object]:
    out: Dict[str, object] = {}

    w = World()
    a = authorize(w)
    w.mutate_version("target")
    out["W1_target_if_match_race"] = {
        "commit": w.target_if_match_commit(a),
        "effect": w.resources["target"].value,
    }

    w = World()
    a = authorize(w)
    validated = w.validate_external(a)
    out["W2_rs_custom_semantics_no_race"] = {
        "validated": validated,
        "commit": w.target_if_match_commit(a) if validated else (False, "validation_failed"),
        "effect": w.resources["target"].value,
    }

    w = World()
    a = authorize(w)
    validated = w.validate_external(a)
    w.revoke_compliance()
    out["W3_rs_external_dependency_race"] = {
        "validated": validated,
        "dependency_version_matches_at_commit":
            w.resources["compliance"].version == a.external_versions["compliance"],
        "compliance_approved_at_commit":
            w.resources["compliance"].value == 1,
        "commit": w.target_if_match_commit(a),
        "effect": w.resources["target"].value,
    }

    w = World()
    a = authorize(w)
    transaction_context = {
        "target_version": a.target_version,
        "external_versions": dict(a.external_versions),
        "compliance_predicate": "approved",
    }
    context_before = json.dumps(transaction_context, sort_keys=True)
    validated = w.validate_external(a)
    w.revoke_compliance()
    context_after = json.dumps(transaction_context, sort_keys=True)
    out["W4_propagated_context_external_change"] = {
        "validated": validated,
        "context_unchanged": context_before == context_after,
        "compliance_approved_at_commit":
            w.resources["compliance"].value == 1,
        "commit": w.target_if_match_commit(a),
        "effect": w.resources["target"].value,
    }

    w = World()
    a = authorize(w)
    w.revoke_compliance()
    out["W5_shared_atomic_domain"] = {
        "commit": w.atomic_application_commit(a),
        "effect": w.resources["target"].value,
    }

    w = World()
    a = authorize(w)
    out["W6a_cross_authority_fence_conflict"] = {
        "commit": w.fenced_cross_authority_commit(a, concurrent_revocation=True),
        "effect": w.resources["target"].value,
    }

    w = World()
    a = authorize(w)
    out["W6b_cross_authority_fence_no_conflict"] = {
        "commit": w.fenced_cross_authority_commit(a, concurrent_revocation=False),
        "effect": w.resources["target"].value,
    }

    return out

def self_test(r: Dict[str, object]) -> None:
    assert r["W1_target_if_match_race"]["commit"][0] is False
    assert r["W1_target_if_match_race"]["effect"] == 0

    assert r["W2_rs_custom_semantics_no_race"]["validated"] is True
    assert r["W2_rs_custom_semantics_no_race"]["commit"][0] is True
    assert r["W2_rs_custom_semantics_no_race"]["effect"] == 1

    assert r["W3_rs_external_dependency_race"]["validated"] is True
    assert r["W3_rs_external_dependency_race"]["dependency_version_matches_at_commit"] is False
    assert r["W3_rs_external_dependency_race"]["compliance_approved_at_commit"] is False
    assert r["W3_rs_external_dependency_race"]["commit"][0] is True
    assert r["W3_rs_external_dependency_race"]["effect"] == 1

    assert r["W4_propagated_context_external_change"]["validated"] is True
    assert r["W4_propagated_context_external_change"]["context_unchanged"] is True
    assert r["W4_propagated_context_external_change"]["compliance_approved_at_commit"] is False
    assert r["W4_propagated_context_external_change"]["commit"][0] is True
    assert r["W4_propagated_context_external_change"]["effect"] == 1

    assert r["W5_shared_atomic_domain"]["commit"][0] is False
    assert r["W5_shared_atomic_domain"]["effect"] == 0

    assert r["W6a_cross_authority_fence_conflict"]["commit"][0] is False
    assert r["W6a_cross_authority_fence_conflict"]["effect"] == 0

    assert r["W6b_cross_authority_fence_no_conflict"]["commit"][0] is True
    assert r["W6b_cross_authority_fence_no_conflict"]["effect"] == 1

if __name__ == "__main__":
    results = run_matrix()
    self_test(results)
    print(json.dumps(results, indent=2, sort_keys=True))

Attachment: results_v0_4.json
Description: application/json

_______________________________________________
OAuth mailing list -- [email protected]
To unsubscribe send an email to [email protected]

Reply via email to