Skip to content
All releases

Release

v1.2.16

What shipped, in numbers

27
PRs merged
3,330,913
Lines added
915,480
Code lines
Code
915,480
Tests
1,006,498
Docs
1,408,935
Sum · code + tests + docs
3,330,913

of 27 merged

  • bug 18 67%
  • feature 3 11%
  • polish 0 0%
  • docs 0 0%
  • groundwork 0 0%
  • unclassified 6 22%

since v1.2.15 · tagged

What shipped

The release that lets a team reach their own workspace. v1.2.15 put a customer's tickets in FlowForge; this one lets their seats open them.

A team seat reaches its workspace — with nothing to configure

  • There was never anything for a customer to configure. The endpoint ships in the binary, the identity ships in the login, the membership ships in the JWT. The switcher refused only because one line of resolution still asked for a database password first. It no longer does (#5262).
  • The binding travels with membership — workspaces_for_user() now returns the store each workspace lives in, derived server-side on a call the switcher already makes. Zero added round-trips (#5268).
  • "You are not a member" no longer looks like "there is nothing here." Under row-level security both are the same empty response; an unseeded seat now gets told, instead of a blank board (#5269).
  • Board visibility is a per-company setting the customer chooses in console admin and row-level security enforces — not a decision FlowForge makes for them. A ten-person agency and a hundred-developer org want opposite defaults and both are right (#5288).
  • Seat readiness is visible. Invited · signed in · identity registered · last telemetry push — four states, so "nine of ten seats have not connected yet" is something you can read rather than something you discover (#5295).

Telemetry flows on its own

  • Scheduled device→cloud push — a daemon vein every 15 minutes, one Go process across macOS, Linux and WSL2. No plist, no unit file, nothing an installer can get wrong. The export previously ran only when a human remembered a hidden command (#4461).
  • The credential refreshes mid-run. A long drain used to die at the one-hour token boundary; it now resolves a live bearer per request, and an expired credential is reported as recoverable rather than as a refusal (#4461).
  • doctor gains a telemetry-flow row separating the pipe is healthy from rows actually moved — so a quiet developer no longer reads as a broken one, and a genuinely dark seat fails the check (#4461).
  • A third of the upload volume is gone. The liveness heartbeat is still emitted and still measures billable time locally; it simply stops crossing the wire, where nothing consumed it (#5274).

Billing that can be defended to a client

  • The invoice stops claiming what the meter does not do, and a rate the operator configured is actually found and used. An unresolved rate now refuses to render a money document rather than inventing one (#5282).
  • One named billing basis, printed on the invoice — hours are no longer summed across concurrent sessions without saying so (#5284).
  • Cost is corrected in both directions at once — deduplicated to the billable API message, and every model priced. An unknown model now renders as not measured, never as zero, because a zero on a money screen is a fabricated number (#5275).
  • A rollup can no longer report zero hours worked. Withheld is now a state, distinct from measured-and-none, with the invariant checked at the door (#5276).
  • The complete billing lifecycle is designed and ratified — an hour carries the invoice that consumed it, and an hour already invoiced cannot be invoiced again by construction (#5286).

The console, walked as a customer

  • All 26 screens audited as a real member seat, with every empty screen classified: no data, not granted, not selected, or not rendered. Grants verified with both instruments, because a table-level check cannot see a column-level grant (#5285).

Release discipline — so the last three incidents cannot repeat

  • The cut reconciles an already-open lane instead of leaving it stale. A lane opened ahead of its own release used to stay forked at its birth point through every later cut; that was the root cause of three consecutive integration incidents. The invariant is now explicit, and a lane that cannot be reconciled blocks the cut (#5287).
  • Lane drift is detected continuously, not once at the cut, and the destructive override is no longer offered as a peer remedy (#5260).
  • The merge verb picks its strategy from the PR class and refuses to squash an integration PR — a squash destroys the ancestry such a PR exists to restore (#5300).

Cockpit

  • Every Settings tab scrolls. On a short terminal the newest section was unreachable and nothing said so (#5264).
  • ELI5 and ASD-STE100 ship as authored output styles — the picker was always data-driven; the files had simply never been written (#5263).
  • Settings redesign, theme import, and the rules surface land together (#1980).

Release-cut integrity — found by the release crew itself

  • A migration ledger id claimed twice now fails loudly instead of recording nothing. ff-0055 was claimed by two migrations; the ledger insert is on-conflict-do-nothing, so the second claim applied and recorded nothing — invisible to the guard that exists to catch it (#5313).

  • A ceremony that would slow an already-timing-out view refuses to apply. ff-0065's dedup measured ~24× on v_cost_by_ticket, a view already returning a statement timeout in production; the ceremony now carries that measurement and fails closed rather than shipping the regression silently (#5315).

  • panels/settings.go returns under its Rule #24 budget — a union-only crossing that neither merge parent violated and that the release-cut path would otherwise have bypassed (#5315).

  • The Stats screen stops stalling on the fleet-token count. Correcting that number (#5275) made it a multi-second scan, and it ran while the cockpit painted — inside a three-second budget shared with every other statistic, so the reads after it were starved. The count is now computed in the background and served from memory: the same number, with the screen no longer waiting for it. Measured on a 1.1 GB store, the shared budget went from exhausted to less than half spent (#5317).

  • A lane name from a cloned repo can no longer rewrite a local ref. The injection guard closed option-shaped refs (-…) but not refspec-shaped ones (a:b) — -- stops option parsing, not refspec parsing — so git clone <untrusted> && flowforge doctor could silently repoint a branch. Closed at both doors (#5319).

  • The fleet-token scan is off the synchronous render path. It cost ~2.6 s on every paint and starved every stat after it; the comment defending it argued the wrong direction and was rewritten from the measurement (#5317).

Known, and stated plainly

  • Applying the billing lifecycle is gated until the hours basis and the rate fix are both live — recording is safe, issuing is not (#5286).
  • Nine of ten pilot seats had never connected at release time. The screens are correct; the substrate had no events. That is onboarding, not a defect — and #5295 is what makes it visible.

v1.2.16 — 27 PRs, 3.3M lines, made with FlowForge