diff --git a/docs/confluence/api.md b/docs/confluence/api.md index 313c558..140cd4a 100644 --- a/docs/confluence/api.md +++ b/docs/confluence/api.md @@ -244,6 +244,49 @@ else's content is far worse than reporting a failure that actually succeeded. triggers on *any* error specifically so nothing depends on the answer; guessing a status and getting it wrong would leave the recovery silently never firing. +## What bumps a page's version + +A page's `version.number` tracks the **body and the title, and nothing else**. +Every other write markfluence makes to a page leaves it alone, which is what +makes it usable as a record of "has this page's content moved on?" (#149). + +**Verified 2026-09-13** against a scratch page in a personal space, created at +version 1, through the gateway: + +| write | route | page version | +|---|---|---| +| create a content property | `POST /wiki/api/v2/pages/{id}/properties` | **unchanged** | +| update a content property | `PUT /wiki/api/v2/pages/{id}/properties/{propId}` | **unchanged** | +| add a label | `POST /wiki/rest/api/content/{id}/label` | **unchanged** | +| remove a label | `DELETE /wiki/rest/api/content/{id}/label?name=` | **unchanged** | +| upload an attachment | `PUT /wiki/rest/api/content/{id}/child/attachment` | **unchanged** | +| change the body | `PUT /wiki/api/v2/pages/{id}` | **+1**, new `createdAt` | +| change only the title | `PUT /wiki/api/v2/pages/{id}` | **+1**, new `createdAt` | + +`version.createdAt` moves with the number and stays put when it does, so the +timestamp is no more sensitive than the counter — adding a label does not +re-stamp the page. + +A content property carries **its own independent version counter**: across the +create-then-update above it went 1 → 2 while the page stayed at 1. So a +property's version says nothing about the page's, in either direction. + +Two consequences, both load-bearing. + +**A publish advances the page by exactly one version.** `update` writes the body +and *then* applies width (two content properties), labels, and attachments, so a +naive reader might expect the live version to be several ahead of the one the +body PUT returned. It is not. Nothing after the body write touches it. + +**The version in the `PUT /pages/{id}` response is the version a later GET +reports**, checked on both publishes above, so a caller recording what it +published does not need a re-read to learn the number. + +**Not verified:** whether a *move* (the v1 `content/{id}/move` route, #10) +bumps the version, and whether restoring a previous version from the UI does. +Measured once, on one Cloud instance, through the gateway, with a personal +token. + ## Scopes **Derived 2026-08-20** from Atlassian's own OpenAPI documents, one lookup per diff --git a/docs/confluence/attachments.md b/docs/confluence/attachments.md index e00832f..2b8b10b 100644 --- a/docs/confluence/attachments.md +++ b/docs/confluence/attachments.md @@ -171,5 +171,10 @@ The comment shows up under `extensions` too. Top-level keys are `ari`, `base64EncodedAri`, `extensions`, `id`, `macroRenderedOutput`, `metadata`, `status`, `title`, `type`, `version` — `title` being the stored attachment name. +An attachment has a version of its own, and **uploading one does not bump the +*page's* version** (verified 2026-09-13) — so a publish's attachment pass +leaves the page at the version its body write produced. The full table of what +does and does not bump it: [api.md](api.md#what-bumps-a-pages-version). + See [api.md](api.md) for how these collections paginate and how downloads redirect. diff --git a/docs/confluence/labels.md b/docs/confluence/labels.md index 601d372..0fdc9c5 100644 --- a/docs/confluence/labels.md +++ b/docs/confluence/labels.md @@ -167,6 +167,18 @@ assert-exactly rule visibly, which is the better of the two available outcomes. Found in review, after the end-to-end run below had already passed — that run used `my:mine`, a name nothing collided with. +### A label write does not bump the page version + +**Verified 2026-09-13**: adding a label and removing one both leave the page's +`version.number` *and* its `version.createdAt` exactly as they were, while a +body change bumps both. Details and the full table of what does and does not +bump it: [api.md](api.md#what-bumps-a-pages-version). + +Two things follow. A label change is invisible to anything watching the page +version, so labels cannot be tracked that way. And `labels.Apply` running after +a publish does not advance the page past the version that publish produced, +which is what lets a caller record the version it published without re-reading. + ### v2 is read-only for labels | request | result | diff --git a/docs/confluence/page-width.md b/docs/confluence/page-width.md index e957826..9d608b6 100644 --- a/docs/confluence/page-width.md +++ b/docs/confluence/page-width.md @@ -19,6 +19,12 @@ content-appearance-draft -> ["max"] Both set, both agreeing. This is why `pagewidth.Apply` writes the pair rather than just the published one. +Writing either property leaves the page's `version.number` untouched, and each +property carries a version counter of its own — see +[api.md](api.md#what-bumps-a-pages-version). So a width change is invisible to +anything watching the page version, and `pagewidth.Apply` running after a +publish does not advance the page past the version that publish produced. + ## The vocabulary Authors write the UI's words in frontmatter; the property takes a different set diff --git a/docs/github-actions.md b/docs/github-actions.md index 66ce03d..16fee2c 100644 --- a/docs/github-actions.md +++ b/docs/github-actions.md @@ -27,6 +27,106 @@ make it a repository **variable** rather than a secret. [secrets]: https://docs.github.com/en/actions/security-guides/using-secrets-in-github-actions +## The source of truth is external to Confluence + +A CI workflow only makes sense when **the repository is the source of truth** +and the Confluence page is a published copy of it. If someone makes changes in +the Confluence UI, they will get stomped on when the CI workflow pushes a new +change. + +### Use `--force` + +```yaml + run: markfluence update --force docs/**/*.md +``` + +`update --force` prevents updates from failing in CI because someone +inadvertently edited the page in the Confluence UI. All edits are in the +Confluence history, so they can be recovered and applied to the repository +correctly. + +### Publish only the files that changed + +`paths:` on the trigger decides whether the *job* runs. It does not narrow the +glob, so `update --force docs/**/*.md` republishes every managed page on every +merge — one typo fix bumps the whole tree. + +That is worth avoiding for reasons beyond tidiness: + +- **Confluence notifies watchers on update.** Republishing 200 pages emails + everyone watching any of them, for a change to one. This is the cost that + gets a publishing bot switched off. +- **Page history stops being useful.** A run of identical new versions across + the tree makes "who changed this, and why" unanswerable in the UI. +- **It is N times the API calls**, on an instance whose rate limit is shared + with everyone else, and a correspondingly slow job. + +Let git pick the files: + +```yaml + - uses: actions/checkout@v4 + with: + fetch-depth: 0 # both ends of the push range have to be present + + - name: List changed docs + id: changed + run: | + base='${{ github.event.before }}' + # A new branch or a force-push reports an all-zero sha; fall back to + # the first commit so the run publishes the whole tree rather than + # failing on an unknown ref. + if [ -z "${base//0/}" ]; then + base="$(git rev-list --max-parents=0 HEAD | tail -1)" + fi + git diff --name-only --diff-filter=ACMRT "$base" '${{ github.sha }}' \ + -- 'docs/**/*.md' > changed.txt + echo "count=$(wc -l < changed.txt)" >> "$GITHUB_OUTPUT" + + - name: Publish + if: steps.changed.outputs.count != '0' + env: + # ... as above + run: xargs markfluence update --force < changed.txt +``` + +Two details that are easy to get wrong: + +- **`--diff-filter=ACMRT`** (added, copied, modified, renamed, type-changed) + excludes deletions. Without it a deleted file lands in the list and fails the + run, since `update` cannot publish a file that is not there. Deleting a page + is deliberately not something a publish does. +- **The empty-list guard.** `update` with no FILE arguments is an error, so a + run where the diff comes back empty has to skip the step rather than invoke + it. + +This is plain `git` rather than a marketplace changed-files action, which keeps +one less third-party dependency in the step that holds the Confluence token. + +### Say so on the page + +Since UI edits are going to be overwritten, the page should tell readers where +they can make edits. Put a callout at the top of the markdown — markfluence +converts a GitHub alert into a Confluence panel, so it renders as one: + +```markdown +> [!NOTE] +> This page is published from [docs/deploy-runbook.md](https://github.com/ORG/REPO/blob/main/docs/deploy-runbook.md). +> Edits made here are overwritten on the next push. Open a pull request instead. +``` + +`NOTE`, `TIP`, `IMPORTANT`, `WARNING` and `CAUTION` are all supported and keep +GitHub's colours. Linking the source file gives a reader somewhere to go, which +is what turns "do not edit" into something actionable. + +Consider restricting page permissions to the publishing account as well, if the +space allows it. A banner is a convention; permissions are a mechanism. + +### If Confluence is the source of truth, do not run this workflow + +If Confluence is the source of truth, you shouldn't be using a workflow to +update Confluence. markfluence has no way to discover changes that have been +made in the Confluence UI and has no mechanism for reconciling them. + ## Workflow ```yaml @@ -65,7 +165,8 @@ jobs: # A variable, not a secret: the cloud ID is public. Omit it if you're # using an unscoped personal token. CONFLUENCE_CLOUD_ID: ${{ vars.CONFLUENCE_CLOUD_ID }} - run: markfluence update docs/**/*.md + # --force because the repository is the source of truth here; see above. + run: markfluence update --force docs/**/*.md ``` That step takes no per-file inputs, and that is the point: each file's page id