decision: should repo protect set fully define final rule state, or keep create-with-defaults + update-partial? #427

Open
opened 2026-08-17 15:52:47 +00:00 by hexajon · 1 comment
Owner

Follow-up from #375. The set upsert shipped with the same field semantics as the existing create/update verbs. That leaves one open product-semantics question.

The divergence

set is not deterministic with respect to final rule state:

  • When NO rule exists, the CREATE path sends only the flags the user passed; Gitea fills the rest with server defaults (e.g. enable_push defaults to blocked).
  • When a rule ALREADY exists, the UPDATE path PATCHes only the passed flags and leaves every other existing value untouched.

So repo protect set main --required-approvals 1 produces different final protection depending purely on whether a rule pre-existed.

Options

  1. Keep current semantics (create-with-defaults, update-partial) and document the divergence in the set help. Matches the existing create/update field behavior.
  2. Make set fully deterministic: read-modify-write, or send the complete field set, so the final state does not depend on pre-existence. This is what a set/upsert verb usually implies.

My lean

(2). A verb named set reads as "make the rule look like this", so pre-existence should not change the outcome.

The tradeoff is real: (2) is a larger change, because it needs a full field model with explicit defaults, and it pins the intended contract for the verb. (1) is a help-text edit and stays consistent with create/update. I want to settle the contract deliberately before building either.

Related: #375.

Follow-up from #375. The `set` upsert shipped with the same field semantics as the existing create/update verbs. That leaves one open product-semantics question. ## The divergence `set` is not deterministic with respect to final rule state: - When NO rule exists, the CREATE path sends only the flags the user passed; Gitea fills the rest with server defaults (e.g. `enable_push` defaults to blocked). - When a rule ALREADY exists, the UPDATE path PATCHes only the passed flags and leaves every other existing value untouched. So `repo protect set main --required-approvals 1` produces different final protection depending purely on whether a rule pre-existed. ## Options 1. Keep current semantics (create-with-defaults, update-partial) and document the divergence in the `set` help. Matches the existing create/update field behavior. 2. Make `set` fully deterministic: read-modify-write, or send the complete field set, so the final state does not depend on pre-existence. This is what a `set`/upsert verb usually implies. ## My lean (2). A verb named `set` reads as "make the rule look like this", so pre-existence should not change the outcome. The tradeoff is real: (2) is a larger change, because it needs a full field model with explicit defaults, and it pins the intended contract for the verb. (1) is a help-text edit and stays consistent with create/update. I want to settle the contract deliberately before building either. Related: #375.
hexajon self-assigned this 2026-08-17 17:45:48 +00:00
Author
Owner

Decided: set becomes a deterministic desired-state verb, and stays explicit about the security-relevant fields.

repo protect set <branch> will send the full field set, so the final protection state never depends on whether a rule pre-existed. This is the desired-state model that a set/upsert verb should have.

It will NOT invent a security posture. Branch protection is a security control, so set does not silently fill push, approval, or force-push intent with a server or tool default. If that intent is unstated, set errors and names the missing field, rather than choosing the posture for you. That gives determinism without the tool quietly deciding how locked-down a branch is.

Build outline:

  • Add a full field model for a protection rule, with each field explicit.
  • Route set through read-modify-write (or a complete field send) so pre-existence stops changing the outcome.
  • Guard the security-relevant fields: unstated intent on push/approvals/force-push is an error, not a default.
  • Update the set help to document the desired-state contract.

Related: #375.

Decided: `set` becomes a deterministic desired-state verb, and stays explicit about the security-relevant fields. `repo protect set <branch>` will send the full field set, so the final protection state never depends on whether a rule pre-existed. This is the desired-state model that a `set`/upsert verb should have. It will NOT invent a security posture. Branch protection is a security control, so `set` does not silently fill push, approval, or force-push intent with a server or tool default. If that intent is unstated, `set` errors and names the missing field, rather than choosing the posture for you. That gives determinism without the tool quietly deciding how locked-down a branch is. Build outline: - Add a full field model for a protection rule, with each field explicit. - Route `set` through read-modify-write (or a complete field send) so pre-existence stops changing the outcome. - Guard the security-relevant fields: unstated intent on push/approvals/force-push is an error, not a default. - Update the `set` help to document the desired-state contract. Related: #375.
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
sixfold-space/madtea#427
No description provided.