Hold the constitution and see what diverged

The canon of a rule is one repository in your own organisation. This page composes the declaration that points at that address and reads what downstream has diverged from it. ⛔ Your repository is never sent to us: judgment runs in this browser, and no code here can send a result back.

Add a constitution — composed as one set

One constitution stands across several repositories. One repository authors the norms (the canon), and every repository that receives them carries a declaration (a link). Below, ① name the constitution, ② read the canon repository and pick its files and scope, ③ add the linked repositories, and the declarations (graduand.json) for every repository that answers to this constitution are composed at once.

⛔ This page neither writes nor commits. It hands you a string, and writing the file belongs to the command that runs on your own machine. The composer is one and the same, so both entrances produce identical bytes.

One repository can answer to several constitutions. Each axis carries its own canon, so a development constitution and a design constitution may live in different upstreams while one declaration points at both. ⛔ The name is yours to choose: only whitespace and : are refused, because a judged cell is named verifier:axis. ⭐ The one name you write here goes verbatim into every declaration composed below.

② The canon declaration — this repository is the canon

The declaration of the repository that authors the norms. It states no pins and no canon address: the canon does not pin its own values. ⭐ There is exactly one canon repository, because one axis points at one canon — and a constitution with two canons is a constitution with none. ⭐ Repository, file and scope are all taken as addresses, so a private repository can be declared here too — you can write the declaration of a repository we cannot read.

A path relative to the repository root. ⛔ Absolute paths and ~ are refused. One axis may carry several files — if you need that declaration, --file repeats in the terminal tab.

Where the adapter reads values — for CSS custom properties, a selector such as :root or @theme. There is exactly one scope, because one axis carries one scope.

⭕ This checks, it does not pick. Pressing it reads the repository and asks whether the file you wrote exists and whether that scope reads any value at all. ⛔ A private repository cannot be read from this browser — composing stands without pressing it. What remains then is not "it is true" but "we did not ask", and those are not the same place.

③ The link declarations — the canon is another repository

Each one points at the canon and carries the pinned values read from that repository. ⛔ Nothing is copied by hand: copying the same value into N repositories by hand is the production line of that drift, and this product measures it. ⭐ A repository that already has a declaration gets its whole declaration plus one axis. ⭐ The canon declaration need not be committed yet — the pins are read against the canon declaration composed just above. ⚠️ Pins can only be written by reading the canon, though: if the canon is private this page cannot produce link declarations, and that work belongs to graduand link in the terminal tab — on your machine both checkouts are in hand.

Commit each snippet as graduand.json at that repository root, and the diagnosis at the root reads the repository and reports. If the declaration points at a canon, it reads that too and judges "pins == canon".

Compose the same thing in the terminal

With the checkouts in hand, the command writes the declaration itself. The composer is the same single function this page uses, so both entrances produce identical bytes. ⛔ It does not commit: writing a file and putting it in the history are different acts, and the second one is yours.

The values below are an example — constitution design, canon repository acme/design, file tokens.css, scope :root. Swap in your own names and addresses.

  1. In the canon checkout — write "this is the canon"

    npx graduand canon --axis design --adapter css-custom-properties --file tokens.css --scope :root

    --file repeats (several files). The composer does not believe the declaration, it reads: if a declared file does not exist, or that scope reads no value at all, it refuses instead of writing. ⛔ It never overwrites an existing declaration — it prints the composed document on stdout and moving it is yours.

  2. In the linked checkout — write "the canon is over there and the values are these"

    npx graduand link --axis design --canon acme/design --canon-file tokens.css --canon-scope :root --root . --root ../design

    Several --root flags are the heart of this step. The first is the repository being written and the rest are sight — the pins are read from the canon checkout, never taken by hand. ⛔ If the canon is not in sight it refuses rather than writing an empty pin: a pin read from nowhere is an invented basis.

  3. Ask what stands as the denominator

    npx graduand rules

    It prints what the declaration put up as the denominator — admitted rules and the verifiers they chose, the canon address of each shared-value axis, exemptions and their expiry. ⛔ It does not judge: there is no red here, because asking for the denominator violates nothing.

  4. Judge

    npx graduand --root . --root ../design

    The answer is pass · violation · unjudged, and nothing else. A cell nobody looked at is not counted as a pass. To measure the cross axis ("pins == canon") here too, show the canon checkout with --root as well — sight of a single repository cannot, in principle, see drift between repositories.