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))
results_v0_4.json
Description: application/json
_______________________________________________ OAuth mailing list -- [email protected] To unsubscribe send an email to [email protected]
