From bf78cf668e6b93701c83ea0a3537c185aaca40e8 Mon Sep 17 00:00:00 2001 From: LogicDuke Date: Thu, 20 Aug 2026 18:38:31 +0200 Subject: [PATCH 1/2] docs: bind change requests at provider boundary Co-Authored-By: Claude Opus 4.8 --- docs/architecture/C1-repair-job-authority.md | 46 ++++++++++++++------ src/domain/repair-job.ts | 14 ++++-- 2 files changed, 44 insertions(+), 16 deletions(-) diff --git a/docs/architecture/C1-repair-job-authority.md b/docs/architecture/C1-repair-job-authority.md index cca523e..51b16c8 100644 --- a/docs/architecture/C1-repair-job-authority.md +++ b/docs/architecture/C1-repair-job-authority.md @@ -282,16 +282,34 @@ branch **to** the protected parent. The boundary must establish that the effecti source identity is the authorized repair ref and the effective target identity is the protected parent ref, reject a symbolic or effective alias that changes that direction, and fail closed if either effective identity cannot be safely -established. - -*Concurrency is not closed by a pre-check.* A resolve-then-check-then-mutate -sequence is **not** an atomic security guarantee: the effective ref or referent -can change between the comparison and the update, so a name observed as an -ordinary repair ref can become symbolic to the protected parent before the -mutation lands. The invariant must be enforced **at the actual mutation/receiving -boundary**, by a mechanism whose semantics prevent an unchecked identity change -between comparison and update — not by an earlier client-side observation this -boundary later trusts. +established. Because this operation performs no ref update to guard, the boundary +that consumes these identities is the change-request/provider creation — or +update — request itself, and the established source and target identities must be +**bound through to that provider request**: the provider must create the request +from exactly the authorized effective source and target, and must not +independently re-resolve the ref names, derive the source or target from ambient +repository state, or otherwise act on an identity that has changed since it was +established. If that authorized source-to-target relationship cannot be maintained +through to the provider request — because an effective identity has changed, or +cannot be safely re-established at that boundary — the boundary must fail closed +and create no change request. + +*Concurrency is not closed by a pre-check.* A resolve-then-check-then-act +sequence is **not** an atomic security guarantee: an effective ref or referent can +change between the comparison and the moment the identity is consumed, so a name +observed as an ordinary repair ref can become symbolic to the protected parent — +or a target can cease to denote it — after the check and before the act. The +invariant must be enforced **at the actual trusted execution boundary that +consumes each identity, not only where a ref is mutated**, by a mechanism whose +semantics prevent an unchecked identity change between the comparison and that +consumption — not by an earlier client-side observation the boundary later trusts. +That consuming boundary differs by operation and the obligation is identical at +each: for `repair.commit` it is the commit mutation boundary, for `repair.push` +the push receiving/mutation boundary, and for `repair.change_request` — which +mutates no ref — the change-request/provider creation boundary at which the source +and target identities are actually consumed. An operation whose authorized +effective-identity relationship cannot be held through to its consuming boundary +must fail closed. This document states the required invariant, not an implementation: it names no git command, lock, or transaction mechanism, and it claims no more atomicity than @@ -537,9 +555,11 @@ touch, about which ref a commit or push would actually advance, or about the effective direction of a change request, so a permit must never be read as proof that repository-level ref identity, the effective mutation target, or the change-request direction is safe. The trusted execution boundary that acts on a -permit binds the effective target, resolves the effective identity, enforces it at -the mutation/receiving boundary, and fails closed; see *What canonical ref names -do and do not prove* above. +permit binds the effective identity, resolves it, and enforces it at the boundary +that actually consumes that identity — the mutation/receiving boundary for a +`repair.commit` or `repair.push`, and the change-request/provider creation boundary +for a `repair.change_request` — and fails closed; see *What canonical ref names do +and do not prove* above. ### Single use diff --git a/src/domain/repair-job.ts b/src/domain/repair-job.ts index c8ffd1a..a1a5e31 100644 --- a/src/domain/repair-job.ts +++ b/src/domain/repair-job.ts @@ -529,11 +529,19 @@ function endsWithDotLockSuffix(value: string, start: number, end: number): boole * effective destination ref for a push. It must **fail closed** — refusing the * operation — if that effective mutation target is or dereferences to the * protected parent, if it changes between comparison and update, or if it cannot - * be safely established; a resolve-then-mutate pre-check is not itself atomic, so - * the invariant is enforced at the mutation/receiving boundary. That refusal is + * be safely established; a resolve-then-act pre-check is not itself atomic, so the + * invariant is enforced at the boundary that actually consumes each identity — the + * mutation/receiving boundary for a commit or push, and the + * change-request/provider creation boundary for a change request. That refusal is * bound to the operand's role rather than being a blanket ban on the protected * parent's identity: a `repair.change_request` `targetRef` is *required* to reach - * the protected parent ref, and fails closed when it reaches anything else. + * the protected parent ref, and fails closed when it reaches anything else. Because + * that operation mutates no ref, the identity it consumes is bound at its provider + * create/update request: the effective source must remain the authorized repair ref + * and the effective target the protected parent ref through to that request, the + * provider must not independently re-resolve those refs or derive them from ambient + * repository state, and the boundary must fail closed if that relationship cannot be + * maintained there. * Nothing here acquires git invocation, filesystem access, a subprocess, or * network to decide it. See `docs/architecture/C1-repair-job-authority.md`, * "What canonical ref names do and do not prove". From 7810d6e337e3b664526bb8017f0f80e12ad2b312 Mon Sep 17 00:00:00 2001 From: LogicDuke Date: Thu, 20 Aug 2026 19:43:09 +0200 Subject: [PATCH 2/2] docs: allow identity-preserving provider resolution Co-Authored-By: Claude Opus 4.8 --- docs/architecture/C1-repair-job-authority.md | 20 +++++++++++++------- src/domain/repair-job.ts | 9 +++++---- 2 files changed, 18 insertions(+), 11 deletions(-) diff --git a/docs/architecture/C1-repair-job-authority.md b/docs/architecture/C1-repair-job-authority.md index 51b16c8..7d07781 100644 --- a/docs/architecture/C1-repair-job-authority.md +++ b/docs/architecture/C1-repair-job-authority.md @@ -286,13 +286,19 @@ established. Because this operation performs no ref update to guard, the boundar that consumes these identities is the change-request/provider creation — or update — request itself, and the established source and target identities must be **bound through to that provider request**: the provider must create the request -from exactly the authorized effective source and target, and must not -independently re-resolve the ref names, derive the source or target from ambient -repository state, or otherwise act on an identity that has changed since it was -established. If that authorized source-to-target relationship cannot be maintained -through to the provider request — because an effective identity has changed, or -cannot be safely re-established at that boundary — the boundary must fail closed -and create no change request. +from exactly the authorized effective source and target. Provider-side resolution +of the supplied ref names is not itself forbidden — a create/update API may have +to resolve the source and target names against its own authoritative repository +state — but it must yield exactly those authorized effective identities: it must +not let re-resolution, ambient repository state, or any substitution cause the +request to be created from, or to consume, a **materially different** effective +source or target than the one authorized. Resolution that preserves the exact +authorized source-to-target relationship conforms; resolution that would consume +a materially different effective identity does not. If that authorized +source-to-target relationship cannot be maintained through to the provider +request — because an effective identity has changed, cannot be safely +re-established, or cannot be shown equivalent to the authorized one at that +boundary — the boundary must fail closed and create no change request. *Concurrency is not closed by a pre-check.* A resolve-then-check-then-act sequence is **not** an atomic security guarantee: an effective ref or referent can diff --git a/src/domain/repair-job.ts b/src/domain/repair-job.ts index a1a5e31..8287b5b 100644 --- a/src/domain/repair-job.ts +++ b/src/domain/repair-job.ts @@ -538,10 +538,11 @@ function endsWithDotLockSuffix(value: string, start: number, end: number): boole * the protected parent ref, and fails closed when it reaches anything else. Because * that operation mutates no ref, the identity it consumes is bound at its provider * create/update request: the effective source must remain the authorized repair ref - * and the effective target the protected parent ref through to that request, the - * provider must not independently re-resolve those refs or derive them from ambient - * repository state, and the boundary must fail closed if that relationship cannot be - * maintained there. + * and the effective target the protected parent ref through to that request. The + * provider may resolve those ref names at its own boundary, but must not let + * re-resolution or ambient repository state substitute a materially different + * effective identity for either end, and the boundary must fail closed if the + * authorized relationship cannot be maintained or shown equivalent there. * Nothing here acquires git invocation, filesystem access, a subprocess, or * network to decide it. See `docs/architecture/C1-repair-job-authority.md`, * "What canonical ref names do and do not prove".