Files
werkator/docs/prs/2026-09-09-PR#43-private-repositories-and-quota-severity.md
2026-09-09 18:49:25 +02:00

9.2 KiB

WARNING: This document describes only the change applied in this PR. It may already be outdated once the next PR is merged. Historic PR-documentation is not maintained along with new PRs — treat it as a snapshot, not as current documentation.

  • PR#42 — corrected the sizing numbers; this PR corrects how loudly they are said, and adds the order button this PR now gates.

The Problem

A full quota was as red as a full disk. The SSD group quota on the production host reached 92 %, so the advice chip turned red and the machine verdict read overloaded. Neither is true in the way the colour claims: a quota is raised by asking a service desk, nothing stops while it is being asked for, and the hardware behind it had well over a terabyte free. Red belongs to what cannot be asked for.

The build pages showed every repository's work to everyone. The pages are readable without any token — that is deliberate, a build page is meant to be linkable — and they name every branch, every commit, every artifact and every log line of every served repository. For a repository that is private on the forge, that is more than a passer-by should see, and the instance is on the internet.

The order button offered to spend money. PR#42 put a button on the System page that composes a hardware order; it appeared for anyone who opened the page, and it named the webspace and the machine while doing so.

Non-Goals

  • No login, no sessions, no users. The control token is what Werkator has, and this PR only makes it readable earlier in the request.
  • No configuration key for visibility. The forge knows, and a second place to declare it could only drift from the truth.
  • Private repositories are not removed from the overview. That a repository exists, that it builds, and that a build failed stays visible; only what names the work goes.
  • Nothing changes for a public repository, or for an instance with no forge configured.

The Scenarios

Feature: how loudly an advice is shown

Scenario#43.01: A quota to raise is yellow, a filling volume is red

So that a colour says how urgent a thing is, not merely what kind of thing it is.

  • Given a disk whose peak is 92 % of a group quota
    • and whose physical volume has room to spare
  • Then the verdict stays upgrade and the chip is drawn in the watch colour
    • and the machine verdict counts it as pressure, not as overload
  • Given the same fill against the volume itself, or against a quota whose volume is the tighter budget
  • Then the chip is drawn in the critical colour and the machine verdict counts it as overload
    • because raising a quota whose backing is what runs out buys nothing
Verified by

Feature: private repositories

Background

  • The pages and the JSON API are readable without a token; only mutating endpoints have ever required one.
  • A repository's visibility is the forge's own fact, reachable through the Gitea repository API.

Scenario#43.02: What a visitor sees of a private repository

So that a build page can stay public without publishing the work behind it.

  • Given a repository the forge calls private
    • and a browser without the control token
  • When any page or API of that repository is requested
  • Then its name reads hidden private repo, greyed out, and carries no link
    • and branch, build name and commit read n/a
    • and the artifact key is empty, so no artifact, no log and no permanent branch link is offered
    • and no link into the forge is rendered
    • and the redaction happened on the server: the JSON the browser received never held the values
  • And then the counts, the statuses and the timestamps are untouched, and the shapes are unchanged, so the page renders the repository like any other
  • Given a public repository
  • Then nothing changes at all
Verified by

Scenario#43.03: The overview keeps the shape, loses the identity

  • Given a private repository among the served ones
  • Then its entry shows hidden private repo and is not clickable
    • and its failed builds are listed as n/a, with their timestamps
    • and its branch count, running and pending counts and watcher problem stay
Verified by

Scenario#43.04: The forge is asked, and the fallbacks are asymmetric

So that nothing has to be configured, and a failure does not publish a repository.

  • Given a repository whose forge answers private
  • Then it is hidden; answered public, it is shown
  • Given a repository with no forge configured
  • Then it is public, exactly as before this change — there is nothing to ask and nothing to hide behind
  • Given a repository whose forge is configured but does not answer
  • Then the last answer stands, and without one the repository is hidden
  • And the answer is cached, so the pages' polling never reaches the forge
Verified by

So that a server-rendered page can decide what to show, without weakening what protects the actions.

  • Given a browser that has been given the control token
  • Then it is kept in local storage as before and in a cookie
    • and the cookie makes every page and every API answer unredacted
    • and restarting or deleting a build still requires the token in the request header
  • Given a foreign page that makes the browser send the cookie
  • Then it gains nothing: it can neither read the response across origins nor set the header the actions require
Verified by

Feature: the order button

Scenario#43.06: Only the operator may order

  • Given a browser without the control token
  • Then GET /api/system reports no hosting, so neither the order button nor the webspace and machine names reach it
  • Given the control token
  • Then the button appears as in PR#42
Verified by

Scenario#43.07: An action nobody may take is shown, but inert

So that the page says what exists without inviting a click that can only fail.

  • Given a browser without a valid control token
  • Then the restart, delete and cancel buttons are rendered greyed out and disabled
    • and their tooltip says why: Needs the control token
    • and this holds for the server-rendered rows and for the rows the script re-renders alike
  • And the header carries a key button that asks for the token and reloads
    • because with every action disabled there would otherwise be nothing left to click that asks for it
  • Given a valid token
  • Then the buttons behave as before, and the key button offers to forget it again
Verified by

The Solution

ResourceAdvice gains an askable flag and, from it, a severity in the page's own ok/warn/crit vocabulary. The chips take their colour from the severity rather than from the verdict, so the text keeps saying upgrade while the colour says how loudly; SystemHealth applies the same rule to the machine verdict. A disk is askable when its budget is a quota and the volume behind it is not the tighter budget.

GiteaClient.readVisibility asks the forge, and RepositoryPrivacy caches the answer per repository with the fallbacks of Scenario#43.04. Redaction is one object holding what a redacted build, branch and overview look like, applied in the controllers before anything is serialized — never in the page. That is what keeps the frontend unaware: same fields, same types, one row per build, and a first paint that already agrees with the first poll.

The same cookie tells a page whether it may act: canAct reaches the templates and, as a data- attribute, the script, so the disabled state is decided in one place and by the only party that can tell a valid token from a stale one.

ViewerIdentity reads the token from the cookie, from the current request where a controller has many handlers. The cookie is written by werkator.js next to the local-storage copy it has always kept, including once on load for a browser that was given the token before this change.

Open Questions

  • The visibility cache has no invalidation other than its fifteen minutes; making a repository public shows up within that window, not immediately.
  • A repository is hidden as a whole. A forge that distinguishes internal from private is treated as private.

Additional Changes

  • Version 1.5.0 and its release-notes entry, since this is what gets deployed.