Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 4 additions & 5 deletions decisions/conformance-declaration.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ it, in the shortest form a claim can take. A report carries the values, the
measurements, the method each one was produced with and the hardware it ran on,
so a reader who doubts it can run it again and find out.

The report lives in the client's repository rather than here. It describes one
The report lives in the client's repository. It describes one
build of one client at one moment, so a copy kept in this tree is stale the next
time that client releases, and a stale copy of somebody else's evidence is worse
than no copy. This repository holds the system. Each client holds its own
Expand Down Expand Up @@ -79,8 +79,7 @@ A missed budget number is already answered, in
may be changed in the open for everybody but not waived for one release. Nothing
here reopens that.

Two things it does not answer, which belong to the report rather than to the
budget.
Two things it does not answer. Both belong to the report.

A value that does not match blocks nothing on its own. A corner radius two pixels
off harms no one, and a client that cannot publish until every value matches is a
Expand Down Expand Up @@ -112,8 +111,8 @@ files can.
Every check this repository owns reads this repository. A client's report lives
in a tree this repository does not hold and does not gate, so no leg of this
gate can refuse a badge, a missing entry or a summary verdict that collapses the
three states. What is available is narrower and worth naming rather than
implying: this repository can hold its own published pages to the wording in the
three states. What is available is narrower, and it is named here so nobody has
to infer it: this repository can hold its own published pages to the wording in the
section above, and it can publish the values and the method in forms a client's
own gate can read, which is #39 and #41.

Expand Down
2 changes: 1 addition & 1 deletion decisions/design-system-binding.md
Original file line number Diff line number Diff line change
Expand Up @@ -126,7 +126,7 @@ is not comparable between two clients and this rule cannot be applied fairly.
A number may be changed, and it may not be waived. Changing it is a change to this
system, argued in the open, applying to every client from then on. Waiving it for
one release is how a budget becomes a preference, and it is the specific failure
this file exists to prevent. If 80 ms turns out to be the wrong number, the repair
the rule above prevents. If 80 ms turns out to be the wrong number, the repair
is to move it and say why, for everybody.

A client that has not been measured has not met the budget. Not measured and met
Expand Down
2 changes: 1 addition & 1 deletion decisions/first-release.md
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,7 @@ noise at one.
## What blocks it

These are the open issues the sequence above cannot happen without. The list is
a judgement rather than a derivation, and the current state of each one is:
a judgement, and the current state of each one is:

for n in 1 5 7 11 17 18 19 24 25 26 27 28 31 32 33 34 35 51 53 54 55; do
gh issue view "$n" --repo Flowfin/hub --json number,state,title \
Expand Down
44 changes: 21 additions & 23 deletions decisions/gate-parity.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
# Which legs of the plugin board's gate this board carries

The gate on `iderex/jellyfin-plugin-sso` is the target this board is measured
against. It is public, so it is read rather than described:
against. It is public, so the list below is read out of the API:

gh api repos/iderex/jellyfin-plugin-sso/rules/branches/main \
--jq '.[] | select(.type=="required_status_checks")
Expand All @@ -20,8 +20,8 @@ against. It is public, so it is read rather than described:
prettier
dependency-review

Run 2026-08-09. Thirteen names, and they are read at the time of writing rather
than copied from where this question was raised, because a required set moves.
Run 2026-08-09. Thirteen names, read at the time of writing, because a required
set moves and a copy taken where the question was raised would already be stale.

Parity does not mean copying. That board is a compiled plugin with a test
project, a fuzz harness and a packaging step, and several of its legs have
Expand Down Expand Up @@ -56,17 +56,16 @@ Both run 2026-08-09. Ten required names against the target's thirteen.
Some of this board's own legs are missing from that list, and the set grows
every time one lands, because nothing adds a name to a ruleset on its own. A leg
in that state runs on every pull request and blocks nothing. How many there are
today is derived rather than written here, since the number moves whenever
either side does:
today is derived by the command below, since the number moves whenever either
side does:

comm -23 <(grep -o 'Gate: [a-z0-9-]*' .github/workflows/gate.yml | sort -u) \
<(gh api repos/Flowfin/hub/rules/branches/main \
--jq '.[] | select(.type=="required_status_checks")
| .parameters.required_status_checks[].context' | sort)

The left side is the workflow's job names rather than the leg list, and the two
cannot disagree: `internal/gate`'s suite refuses a leg with no job and a job
with no leg.
The left side is the workflow's job names, and it cannot disagree with the leg
list: `internal/gate`'s suite refuses a leg with no job and a job with no leg.

Widening the required set is #48 and is not done here; recording that the gap
exists is.
Expand All @@ -90,8 +89,8 @@ tool over the built plugin to produce the archive an operator installs. This
board produces no archive: it publishes a file that points at archives other
repositories built.

**Package (JPRM) / Generate SBOM.** DROPPED, and it is the one drop worth
arguing rather than stating. An SBOM lists what a build depends on, and this
**Package (JPRM) / Generate SBOM.** DROPPED, and it is the one drop that owes an
argument. An SBOM lists what a build depends on, and this
tree's dependency set is empty and is held empty by the absence of a `go.sum`
that `CONTRIBUTING.md` spends a section on. An SBOM of nothing is a file nobody
reads. The condition that reverses this is the first `require` line in `go.mod`,
Expand All @@ -106,8 +105,8 @@ What carries over unchanged is that a finding blocks rather than annotates, and
that half is not free. The analyse step uploads its results and exits zero
whether it found anything or not, so a job that stopped there would be green with
findings in it. `go run . scan` reads the report the analysis wrote and exits
non-zero on any result. It is a Go verb over a directory rather than an
expression in the workflow, which is what `decisions/means.md` asks for, and
non-zero on any result. It is a Go verb over a directory, which is what
`decisions/means.md` asks for, so no logic sits in the workflow file, and
`internal/scan` carries a fixture for each of its refusals.

Three of those four refusals are the analysis having NOT happened rather than the
Expand All @@ -128,7 +127,7 @@ same certificate, same refusal of a non-merge commit without the line.
**Deterministic PR-hygiene checks.** ADAPTED, landed as `Gate: pr-hygiene` in
`internal/hygiene`. What carries over is the tiering, so high-confidence rules
block and soft conventions annotate, and the explicit skip for an author from
outside the repository that announces itself rather than going quiet. What is
outside the repository, which announces itself on every run it applies to. What is
dropped from it is every rule about a solution file, a changelog format or a
test project, none of which exist here.

Expand Down Expand Up @@ -157,8 +156,8 @@ names. The target runs a pattern linter over its source; this board's equivalent
is `Gate: no-hardcoded-names`, which refuses an account or organisation name
written into source or into a workflow step, and `Gate:
site-fetches-nothing-outside`, which refuses a served page that would load from
another host. Both are Go tests over the tree rather than a pattern language,
which is what `decisions/means.md` asks for: a rule that can be run against a
another host. Both are Go tests over the tree, which is what
`decisions/means.md` asks for: a rule that can be run against a
planted input rather than one that can only be shown to bite by breaking the tree.

**Reject Trojan Source Unicode.** ADOPTED, unchanged, and already required.
Expand All @@ -180,8 +179,7 @@ exists to notice changing.

Neither is a required name on the target's list above, and both are legs it runs.
They are recorded here because a parity list that quietly drops what was too much
trouble is the failure this document exists against, and #46 is where that was
raised.
trouble is the failure #46 raised.

**Coverage. ADOPTED, with a floor, as `Gate: coverage`.** It costs no dependency:
`decisions/means.md` fixes the toolchain as Go, coverage instrumentation arrives
Expand Down Expand Up @@ -222,7 +220,7 @@ one in `CONTRIBUTING.md`: a guard ships with a planted input it refuses.

**Mutation. DROPPED.** The Go toolchain carries no mutation runner, so adopting
it means adding the first third-party tool to a tree whose empty dependency set
is a stated property rather than an accident: there is no `go.sum`, the toolchain
is a stated property: there is no `go.sum`, the toolchain
refuses an unrequired import at build time, and `dependency-review` exists to
notice that changing. `decisions/means.md` asks for that cost to be paid
knowingly, and the thing bought does not pay it here.
Expand All @@ -232,10 +230,10 @@ the generator's value is in its refusals, and a test that calls a refusing
function and asserts it returned is green whether the refusal fired or not. This
board answers that question per guard instead of per run. Every pull request
adding a guard shows it refusing a planted input, and the fixtures live in the
tree afterwards, so the answer is in the suite rather than in a nightly report
nobody opens.
tree afterwards, so the answer lives in the suite, next to the guard it is about,
where a nightly report nobody opens would have held it.

The bound on that is real and is stated rather than hidden: a planted fixture
The bound on that is real and is stated here: a planted fixture
proves the guard bites for the case somebody thought of, and a mutation run looks
for the case nobody did. Nothing here replaces that.

Expand Down Expand Up @@ -271,5 +269,5 @@ gate's own report says on every run that none of them ran.
Which of these becomes a required name on `main` is #48. This document says what
each leg is; a ruleset is what makes one block, and nothing in this tree can read
a ruleset. The list above was derived by running the commands at the top on
2026-08-09, and it is derived again rather than trusted the next time somebody
needs it.
2026-08-09. Run them again the next time somebody needs the list; what they
print then is the answer.
11 changes: 5 additions & 6 deletions decisions/manifest-schema.md
Original file line number Diff line number Diff line change
Expand Up @@ -151,13 +151,12 @@ the failure #23 is about and is not left to a checkout setting.
**Keys are emitted in the order above, not sorted.** Sorted keys would put
`category` before `changelog` and scatter a version entry's identity through it.
The order above is fixed by the order of the fields in the types the generator
marshals, so it is a property of the source rather than a formatting pass, and
changing it is a change to a struct that a reader sees.
marshals, so it is a property of the source, and changing it is a change to a
struct that a reader sees.

**Strings carry their own characters.** An ampersand is `&` and not `\u0026`, an
angle bracket is `<` and not `\u003c`, and a non-ASCII character is emitted as
UTF-8 rather than as an escape. The published catalogue does the same, which is
worth stating as bytes rather than as an intention:
UTF-8. The published catalogue does the same, and the bytes say so:

python -c "
raw=open('jf-manifest.json','rb').read()
Expand Down Expand Up @@ -213,13 +212,13 @@ above as bytes. It carries two plugins so the plugin ordering is visible, one wi
on one plugin with five entries on one and two on the other so the per-target cap
is visible as a shape, and a changelog containing an ampersand, angle brackets, a
quotation mark, a backslash and a non-ASCII letter so the escaping is fixed by an
example rather than by a sentence.
example a reader can run.

It is a fixture and its contents are fixture contents. The names, guids, URLs and
digests in it are invented, the host is the documentation domain reserved for
exactly this, and nothing in it is a claim about any real release. A fixture built
out of the real catalogue would prove the state of the world on the day it was
built rather than the format.
built, and the format is what needs proving.

The test that compares generator output against it byte for byte is #29. This file
and the fixture land together because a format decision written without an example
Expand Down
2 changes: 1 addition & 1 deletion decisions/means.md
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ of the workflows this repository already carries are that shape:

Run 2026-08-08 at 6a98de6. Both are worth keeping, because they are cheap and
because rewriting somebody's working scanner is not free. What they are not is a
pattern to extend once the checks start encoding decisions rather than scanning
pattern to extend once the checks start encoding decisions instead of scanning
for characters.

## What it adds that the tree does not already carry
Expand Down
11 changes: 5 additions & 6 deletions decisions/plugin-identity.md
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ catalogue uses and those six are in both of them.

Optional: `imageUrl`. Absent in eleven of the thirty-four entries measured there,
so a plugin with no artwork is ordinary rather than defective, and the key is
omitted rather than emitted empty.
left out where there is no value for it.

`guid` is also checked for shape, because it is the one field whose damage is
invisible. It must be eight, four, four, four and twelve hexadecimal characters
Expand All @@ -59,8 +59,8 @@ the entry as a plugin it has never seen.
`owner` is read from the descriptor like the rest. Entry 4 of #1 decided that the
catalogue carries the account that publishes the releases, and the reason given
there was that the field should show the same name as the release it came from.
Reading it out of the release is that decision implemented rather than a second
place the name is decided. Writing the answer into this generator instead would
Reading it out of the release is that decision implemented, and it opens no
second place where the name is decided. Writing the answer into this generator would
be refused by `no-hardcoded-names`, which is `decisions/names-are-data.md`.

## Why the sidecar rather than the build descriptor
Expand Down Expand Up @@ -105,7 +105,7 @@ for, and a reader has to know which of them to ignore.

The cost is stated: a release that ships a `build.yaml` and no sidecar carries
identity this generator will not read, and is refused rather than parsed by the
other route. That is one file format rather than two, and two readers of one fact
other route. That holds the tree to one file format, and two readers of one fact
disagree the first time one of them is corrected.

## What this costs, and the cost is live today
Expand Down Expand Up @@ -160,8 +160,7 @@ ENFORCEMENT` for that half.
## What this does not settle

Where `version`, `targetAbi`, `changelog` and `timestamp` are read from. They sit
in the same file and they are the version entry rather than the identity, and
nothing here reads them.
in the same file, they belong to the version entry, and nothing here reads them.

What the run does with a refused plugin. That is
`decisions/failure-posture.md`, which already places a defect in the newest
Expand Down
6 changes: 3 additions & 3 deletions decisions/release-procedure.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ of a run reads one run's whole output or the previous one's and never a mixture.
Landed. The placed file is a tracked file, so it reaches the address the way
every other change does: a branch, a pull request, the gate, a merge. There is
no route from a generator run to the served directory that skips the merge, and
that is deliberate rather than missing.
that gap is deliberate, and it is recorded here so it reads that way.

Served. The site is served from the `docs` directory of the default branch by a
host this repository does not run and cannot make deploy. A merge is not a
Expand Down Expand Up @@ -103,8 +103,8 @@ them holds. The conditions are `internal/readiness`'s and every one of them is
tripped in that package's own suite against a planted reading, so a condition
that has never refused anything is not among them.

Two answers rather than one when a condition does not clear, and they are not
the same sentence. A condition that HOLDS is a fact about this tree, and the
A condition that does not clear has two answers, and they are not the same
sentence. A condition that HOLDS is a fact about this tree, and the
repair is to change the tree. A condition NOT EVALUATED is a fact about this
run: the address nobody has recorded, the read that failed, the release lists
that could not be fetched. Both refuse. An unread condition is never a clear
Expand Down
4 changes: 2 additions & 2 deletions decisions/source-set-declaration.md
Original file line number Diff line number Diff line change
Expand Up @@ -103,8 +103,8 @@ first release is proving.

## What refuses a violation

Nothing yet. This file states where the set comes from; #24 is the loader that
reads it and refuses a declaration whose repository or release it cannot resolve,
Nothing yet. The declaration below is where the set comes from; #24 is the loader
that reads it and refuses a declaration whose repository or release it cannot resolve,
and #30 is the check that refuses either name being written into the source
instead. Until those land, the rule holds because nothing has broken it, which is
a different sentence from the rule being enforced.
Loading
Loading