Skip to content

Commit 03e2353

Browse files
committed
feat(docs): cleanup and simplify
1 parent e1a7111 commit 03e2353

8 files changed

Lines changed: 34 additions & 78 deletions

File tree

‎CHANGELOG.md‎

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,10 @@
22

33
## Unreleased
44

5+
### Documentation
6+
7+
- Documentation cleanup and simplification.
8+
59
## 0.5.0 - 2026-09-03
610

711
0.5.x runs on NetBox 4.7, 0.4.x stays on NetBox 4.6, and no version of NetBox runs both. See the [compatibility matrix](https://antoinekh.github.io/netbox-change-control/compatibility/).

‎docs/admin-guide.md‎

Lines changed: 1 addition & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -141,20 +141,7 @@ Both are custom actions, and both are optional.
141141
| `netbox_change_control.bypass_policy` | Write outside a branch while `protect_main` is enabled. | Automation accounts, and the people who run an incident. |
142142
| `netbox_change_control.override_window_changerequest` | Merge a change request outside its change window. | Whoever is allowed to break a change freeze. |
143143

144-
To grant them, go to **Administration > Permissions > Add**, and enter the action in the *additional actions* field rather than ticking view, add, change or delete:
145-
146-
| Exemption | Object type | Action to enter |
147-
|---|---|---|
148-
| Write directly to main | Change Control > Policy | `bypass` |
149-
| Merge outside the change window | Change Control > Change Request | `override_window` |
150-
151-
!!! info "Important"
152-
153-
The trailing part of a custom permission name has to be a real model name, because NetBox splits on the last underscore. That is why the bypass lives on `Policy` and the window override on `ChangeRequest`, rather than on names that would read better.
154-
155-
!!! note
156-
157-
Superusers hold every permission, so they are exempt from `protect_main` and from every change window without being granted anything.
144+
Neither is a tick box on the permission form: the action is typed into *additional actions*, and the object type is not the one the name suggests. See [granting the custom actions](permissions.md#granting-the-custom-actions), which covers **Abandon** and **Reopen** the same way.
158145

159146
### Setting up groups
160147

‎docs/change-requests.md‎

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -25,9 +25,7 @@ The two transitions a person makes by hand have an action each, and a permission
2525
| **Abandon** | The change request page, or `POST /change-requests/{id}/abandon/` | `netbox_change_control.abandon_changerequest` | Draft, Needs review, Approved, Rejected |
2626
| **Reopen** | The change request page, or `POST /change-requests/{id}/reopen/` | `netbox_change_control.reopen_changerequest` | Abandoned only |
2727

28-
Reopening returns the request to Draft and then recomputes, rather than restoring the status it held before. Its reviews may have gone stale and its policies may have changed while it was set aside, so the honest answer has to be worked out again.
29-
30-
Completed is never reopened. It records a merge that actually happened, and taking it back up would invite a second merge of a branch already in main.
28+
Reopening returns the request to Draft and recomputes from there, rather than restoring the status it held before. Completed is never reopened. See [what the states mean](#what-the-states-mean).
3129

3230
## The lifecycle
3331

‎docs/checks.md‎

Lines changed: 2 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -115,12 +115,7 @@ curl -X PATCH "$NETBOX/api/plugins/change-control/policies/$POLICY_ID/" \
115115
-d '{"checks": ["peer-signoff"]}'
116116
```
117117

118-
`checks` replaces the whole list, so send every name the policy should require, not just the new one. Read it back first if you are adding to an existing set:
119-
120-
```bash
121-
curl -s -H "Authorization: Token $TOKEN" \
122-
"$NETBOX/api/plugins/change-control/policies/$POLICY_ID/" | jq '.checks'
123-
```
118+
`checks` replaces the whole list, so send every name the policy should require, not just the new one. See [choosing which checks a policy requires](api.md#choosing-which-checks-a-policy-requires) for adding one without dropping the rest.
124119

125120
The check now runs only on change requests carrying that policy. Elsewhere it has no row, does not run, and does not block. Detach the policy and its check disappears with it.
126121

@@ -140,17 +135,7 @@ curl -X PATCH "$NETBOX/api/plugins/change-control/policies/$POLICY_ID/" \
140135
-d '{"checks": ["cab-approval"]}'
141136
```
142137

143-
Every change request carrying that policy now shows a required `cab-approval` check, pending, blocking the merge. Whatever performs the sign-off reports the result the same way any external check does:
144-
145-
```bash
146-
CHECK_ID=$(curl -s -H "Authorization: Token $TOKEN" \
147-
"$NETBOX/api/plugins/change-control/checks/?change_request_id=$CR_ID&name=cab-approval" \
148-
| jq -r '.results[0].id')
149-
150-
curl -X PATCH "$NETBOX/api/plugins/change-control/checks/$CHECK_ID/" \
151-
-H "Authorization: Token $TOKEN" -H "Content-Type: application/json" \
152-
-d '{"status": "success", "summary": "Approved at the 14:00 CAB", "details_url": "https://cab.example.com/2026-08-26"}'
153-
```
138+
Every change request carrying that policy now shows a required `cab-approval` check, pending, blocking the merge. Whatever performs the sign-off reports the result the same way any external check does: see [reporting a check result](api.md#reporting-a-check-result).
154139

155140
This is how to require a pipeline, a ticket, or a person outside NetBox for one class of change only, without wiring that requirement into every change request. See [checks reported by an external system](custom-checks.md#checks-reported-by-an-external-system) for the full contract, including why a reporter cannot decide whether its own check counts.
156141

‎docs/design.md‎

Lines changed: 5 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -97,19 +97,11 @@ The merge gate does not depend on this cache; it re-evaluates. The cache is for
9797

9898
### Repeated work is collapsed, not deferred
9999

100-
Refreshing a change request is correct on every event that could change its answer, and doing
101-
it per event is what keeps the cached status honest without any caller having to remember.
102-
The cost is that one user action is often many events: attaching policies is one signal each.
100+
Refreshing a change request is correct on every event that could change its answer, and doing it per event is what keeps the cached status honest without any caller having to remember. The cost is that one user action is often many events: attaching policies is one signal each.
103101

104-
`netbox_change_control/batching.py` collapses those bursts. A caller that knows it is about to
105-
cause one wraps it in `batched()`, and each affected change request is refreshed once when the
106-
block ends.
102+
`netbox_change_control/batching.py` collapses those bursts. A caller that knows it is about to cause one wraps it in `batched()`, and each affected change request is refreshed once when the block ends.
107103

108-
It deliberately does not use `transaction.on_commit`. Deferring past the commit would mean a
109-
request's checks are not yet run when the view that submitted it renders the next page, and it
110-
would make every test that asserts on a check result depend on `captureOnCommitCallbacks`.
111-
Outside a block the refresh is immediate, so nothing changes for the callers which are not
112-
part of a burst.
104+
It deliberately does not use `transaction.on_commit`. Deferring past the commit would mean a request's checks are not yet run when the view that submitted it renders the next page, and it would make every test that asserts on a check result depend on `captureOnCommitCallbacks`. Outside a block the refresh is immediate, so nothing changes for the callers which are not part of a burst.
113105

114106
### Staleness is derived from a snapshot, not a flag
115107

@@ -119,13 +111,9 @@ This is preferable to a `stale` boolean maintained by signals, because a flag ca
119111

120112
### `protect_main` listens to every write
121113

122-
`protect_main_on_save` and `protect_main_on_delete` are registered without a sender, so they
123-
run for every model write anywhere in NetBox. That is deliberate rather than an oversight: the
124-
set of protected models is not knowable at import time, because it depends on which models
125-
branching supports and on `protect_main_scope`, which is configuration.
114+
`protect_main_on_save` and `protect_main_on_delete` are registered without a sender, so they run for every model write anywhere in NetBox. That is deliberate rather than an oversight: the set of protected models is not knowable at import time, because it depends on which models branching supports and on `protect_main_scope`, which is configuration.
126115

127-
The cost is bounded by ordering the guard cheaply. The first thing each receiver does is read
128-
`protect_main`, and with it off, which is the default, that is the whole cost of the call.
116+
The cost is bounded by ordering the guard cheaply. The first thing each receiver does is read `protect_main`, and with it off, which is the default, that is the whole cost of the call.
129117

130118
### `protect_main_scope`
131119

‎docs/extending.md‎

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,7 @@ Another plugin can add content to this plugin's pages, and checks to its merge g
44

55
## What this plugin injects elsewhere
66

7-
The traffic runs both ways. This plugin uses the same hooks to put its own content on
8-
netbox-branching's branch page: the change request governing that branch, its status, what is
9-
still outstanding and a link to it, or an offer to open one when there is none. It uses the
10-
`alerts` and `right_page` hooks, and `branch_page_placement` decides which of them renders, so
11-
a deployment can compare the two or turn both off. See `netbox_change_control/template_content.py`.
7+
The traffic runs both ways. This plugin uses the same hooks to put its own content on netbox-branching's branch page: the change request governing that branch, its status, what is still outstanding and a link to it, or an offer to open one when there is none. It uses the `alerts` and `right_page` hooks, and `branch_page_placement` decides which of them renders, so a deployment can compare the two or turn both off. See `netbox_change_control/template_content.py`.
128

139
## Injecting content into a page
1410

‎docs/installation.md‎

Lines changed: 18 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -8,22 +8,6 @@
88

99
This is the NetBox 4.7 line. For NetBox 4.6, stay on the 0.4.x line; the two do not overlap, because netbox-branching 1.1.x and 1.2 do not either. [The compatibility matrix](compatibility.md) has every release.
1010

11-
## Upgrading from the 4.6 line
12-
13-
There is no version of NetBox which 0.4.x and 0.5.x share, so this is a move between lines rather than an ordinary upgrade. NetBox 4.7 replaced django-mptt with a PostgreSQL `ltree` implementation and dropped the `lft`, `rght`, `tree_id` and `level` columns. netbox-branching 1.1.x reads those columns and cannot run on 4.7; netbox-branching 1.2 rewrote that part and requires 4.7. The plugin sits on top of whichever is installed and inherits the split.
14-
15-
Upgrade NetBox and netbox-branching together, then the plugin. Follow NetBox's own upgrade guide for the rest; these are only the points this plugin adds to it.
16-
17-
1. **Check the platform.** NetBox 4.7 requires PostgreSQL 15 or later and Redis 6.0 or later. It installs the PostgreSQL `ltree` extension on the first migration, which needs the `CREATE` privilege on the database. A NetBox user which owns its database already holds it.
18-
2. **Drain the queues before restarting the workers.** NetBox 4.7 removed an argument from the function a queued webhook job calls, so a job still enqueued across the restart fails.
19-
3. **Plan a window.** The 4.7 migration backfills every hierarchical table, then rebuilds the config context cache for every device and virtual machine. Both scale with the size of the database, and neither is reversible in practice.
20-
4. **Merge or delete open branches first, if you can.** A branch schema is a copy of the main schema as it stood when the branch was created, and carrying one across a migration of this size is the least tested path there is.
21-
5. **Then run `manage.py migrate netbox_change_control`.** The one migration this release adds records a field change NetBox made to a base class. It writes no data and takes no time.
22-
23-
Nothing in the plugin's own data changes. Change requests, policies, rules, reviews, comments and checks all survive untouched.
24-
25-
Two things behave differently afterwards, both covered in the [changelog](changelog.md): a webhook payload no longer carries the top-level `username` and `request_id` keys, and [policy conditions](policy-conditions.md) can read the transition itself.
26-
2711
## Installation
2812

2913
Install the package into the NetBox virtual environment, then enable it **before** `netbox_branching`, which must stay last in the list:
@@ -71,7 +55,23 @@ Every setting is optional. The defaults are safe to install on an existing NetBo
7155
| `enforce_merge_gate` | `True` | Refuse to merge a branch without an approved change request. |
7256
| `notify_reviewers` | `True` | Raise NetBox notifications on status transitions. |
7357
| `branch_page_placement` | `['right_page']` | Where a branch page shows its change request. `right_page` puts it in a card in the right-hand column, `alerts` puts it across the top of the page, both shows both, and `[]` shows neither. |
74-
| `enable_builtin_checks` | `True` | Which built-in checks are available to policies. `True` for all, `False` for none, or a list of names. A check applies only where a policy names it. |
75-
| `required_external_checks` | `[]` | Checks reported through the REST API. Each blocks the merge until reported. |
58+
| `enable_builtin_checks` | `True` | Which built-in checks are available to policies. `True` for all, `False` for none, or a list of names from `has-changes`, `no-conflicts`, `not-stale` and `threads-resolved`. A check applies only where a policy names it. |
59+
| `required_external_checks` | `[]` | Checks reported through the REST API. Each entry is a name, or a `(name, label)` pair. Each blocks the merge until reported. |
7660
| `enable_auto_merge` | `True` | Allow requests that opt in to merge themselves once every gate passes. When `False`, the periodic job is not registered at all. |
7761
| `auto_merge_interval` | `10` | Minutes between automatic merge sweeps. Keep every change window longer than this. Takes effect when the worker restarts. |
62+
63+
## Upgrading from the 4.6 line
64+
65+
There is no version of NetBox which 0.4.x and 0.5.x share, so this is a move between lines rather than an ordinary upgrade. NetBox 4.7 replaced django-mptt with a PostgreSQL `ltree` implementation and dropped the `lft`, `rght`, `tree_id` and `level` columns. netbox-branching 1.1.x reads those columns and cannot run on 4.7; netbox-branching 1.2 rewrote that part and requires 4.7. The plugin sits on top of whichever is installed and inherits the split.
66+
67+
Upgrade NetBox and netbox-branching together, then the plugin. Follow NetBox's own upgrade guide for the rest; these are only the points this plugin adds to it.
68+
69+
1. **Check the platform.** NetBox 4.7 requires PostgreSQL 15 or later and Redis 6.0 or later. It installs the PostgreSQL `ltree` extension on the first migration, which needs the `CREATE` privilege on the database. A NetBox user which owns its database already holds it.
70+
2. **Drain the queues before restarting the workers.** NetBox 4.7 removed an argument from the function a queued webhook job calls, so a job still enqueued across the restart fails.
71+
3. **Plan a window.** The 4.7 migration backfills every hierarchical table, then rebuilds the config context cache for every device and virtual machine. Both scale with the size of the database, and neither is reversible in practice.
72+
4. **Merge or delete open branches first, if you can.** A branch schema is a copy of the main schema as it stood when the branch was created, and carrying one across a migration of this size is the least tested path there is.
73+
5. **Then run `manage.py migrate netbox_change_control`.** The one migration this release adds records a field change NetBox made to a base class. It writes no data and takes no time.
74+
75+
Nothing in the plugin's own data changes. Change requests, policies, rules, reviews, comments and checks all survive untouched.
76+
77+
Two things behave differently afterwards, both covered in the [changelog](changelog.md): a webhook payload no longer carries the top-level `username` and `request_id` keys, and [policy conditions](policy-conditions.md) can read the transition itself.

‎docs/merging.md‎

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,7 @@ So a change approved at midday with a window opening at 21:00 does merge at 21:0
6262

6363
Only ever one job per branch. A single write can reach the automatic merge by more than one route, and the request is still Approved at the second arrival because the merge has only been queued and not yet run. A queued, scheduled or running merge for the branch stops another being added.
6464

65-
The merge is **enqueued as a background job**, the same path the branching plugin's own merge button takes. It is never run inline: auto-merge is reached from a signal, so merging directly would run a whole branch merge inside the web request that submitted the final review.
65+
The merge is **enqueued as a background job**, the same path the branching plugin's own merge button takes. It is never run inline: auto-merge is reached from a signal, so merging directly would run a whole branch merge inside the web request that submitted the final review. It runs as the requester, so the changelog records the merge against the person who asked for it.
6666

6767
If the branch changes during the wait, the approvals go stale, the status returns to Needs review, and the evening merge does not happen. Approval is only valid for the branch state it was given against.
6868

@@ -94,6 +94,4 @@ The immediate trigger does not save you here. It only fires when the final appro
9494

9595
Fix it either way round: widen the window, or lower `auto_merge_interval`. Leave some margin rather than matching them exactly, because a busy worker can run a sweep late.
9696

97-
Setting `enable_auto_merge` to `False` skips registering the job entirely, so a site that does not use auto-merge gets no periodic job and no Job records from this plugin.
98-
99-
The merge runs as the requester. Set `enable_auto_merge` to `False` to disable the feature globally without editing any request.
97+
Setting `enable_auto_merge` to `False` disables the feature globally without editing any request, and skips registering the job entirely, so a site that does not use auto-merge gets no periodic job and no Job records from this plugin.

0 commit comments

Comments
 (0)