Publish a digest of the rule set this package compiles in - #222
Merged
patchstackdave merged 1 commit intoSep 8, 2026
Merged
Conversation
|
Comprehensive script and tests deliver a robust rule-set digest feature. 🎯 Quality: 100% Elite · 📦 Size: Medium 📈 This month: Your 195th PR — above team average · Averaging Excellent |
patchstackdave
force-pushed
the
feat/publish-a-digest-of-the-shipped-rule-set
branch
from
September 7, 2026 14:16
ae18193 to
2866680
Compare
patchstackdave
force-pushed
the
feat/publish-a-digest-of-the-shipped-rule-set
branch
6 times, most recently
from
September 7, 2026 15:01
1858993 to
5356f4f
Compare
Contributor
Author
|
/review |
daniloradovic
approved these changes
Sep 7, 2026
patchstackdave
force-pushed
the
feat/publish-a-digest-of-the-shipped-rule-set
branch
2 times, most recently
from
September 7, 2026 17:34
0026624 to
b45fc88
Compare
The guard ships rules and the platform serves the same policies as documents it
can revise. Both copies exist on purpose — one protects an app before it is
enrolled, the other can be corrected without a release — and they are only
useful together if they agree. Nothing mechanical says whether they do.
`scripts/rule-set-digest.mjs` prints a digest per rule and one over the whole
set, so agreement is a comparison of two values rather than a reading of two
files. `--canonical` prints the material behind it, because a digest says "these
differ" and nothing else while whoever has to fix it needs the difference.
What is compared is derived from the rule contract rather than listed by hand:
every property in `RULE_PROPERTIES` except an explicit allowlist of five, each
with its reason stated where it is excluded. A property added to the contract is
therefore compared by default, and a test holds the projection to the contract so
an exclusion has to be argued for rather than drifting in.
Those five are excluded because the copies are meant to differ on them, not
because they do not affect behaviour: `enforcement` decides whether a rule acts,
and `title` becomes the block message for a rule declaring no `message`. Equal
digests therefore mean the copies agree on everything compared — a policy
projection — and not that they behave identically. The comparison is set-to-set
on those digests, which needs no shared identifier between the copies.
The material is TEXT, and its form is written out here rather than delegated to
`JSON.stringify`, because the other side of this comparison is another language
and the two encoders disagree on values the contract accepts:
- an empty object is indistinguishable from an empty array to an associative
decode, and `cookie_flags: {}` is a document somebody may write;
- one encoder keeps the sign on negative zero and the other drops it;
- `1e-7` is written `1e-7` by one and `1.0e-7` by the other.
So the form is the agreement: keys sorted at every depth, arrays in order, an
empty object as `{}`, only `"`, `\` and the characters below a space escaped and
those as `\u00xx` rather than the short forms, and numbers only where they are
SAFE integers. Integrality is not enough — `1e21` is an integer here and writes
itself in exponent form, while an implementation holding it as an integer writes
the digits and one converting it to a 64-bit integer gets a different number
altogether. Above 2^53 there is no shared spelling, so the form stops there.
A value the form cannot pin down is REFUSED rather than approximated. A digest is
worth comparing only if the other side would produce the same one, so a value
written differently there would report drift between documents that agree.
Positively or not at all.
Only the properties a rule states appear in its material. The contract treats an
absent property and an authored `null` as different documents, and refuses that
null for all but the exempt properties — so of two copies, the one authoring
`null` can have its rule refused while the other's runs on the engine default.
Standing an absent property in as a null would report those two as agreeing.
Nothing is refused here either, so an exempt `capture: null` is recorded as the
declaration it is.
Key order does not affect a digest and array order does: the same rule typed in a
different order is the same rule, while `rule_v2` is a sequence whose order
changes what a rule does and what it costs to evaluate.
The whole-set digest covers the projection and a version of the material as well
as the rule digests. A property no rule declares contributes nothing to any rule's
digest, so over those alone a comparator that stopped comparing such a property
would produce the identical value — and most of the compared properties are
declared by no rule this package ships, so that is the normal case rather than an
edge of it. Both lists are sorted, because a set has no order and two comparators
covering the same properties in a different order are comparing the same things.
No digest value of the shipped rule set is asserted in the tests. A snapshot of it
would have to be updated whenever a rule legitimately changes, and a test that
must be edited to keep passing stops describing anything. The properties are
asserted of it instead: the digest covers each compared property, moves when one
moves or is removed, holds across key order and rule order, and — stated directly,
because the removal case cannot see it — never lets a rule declaring nothing agree
with a rule declaring a value.
Because this is an interoperability format, its version is pinned by a fixed
synthetic vector as well. The vector carries nested key ordering, array order, a
slash and a backslash inside an operand, non-ASCII text, control characters, an
empty object, an integer, a boolean, an authored null and absence — and it is
pinned twice: the canonical text is written out in the test independently of the
code, and the bytes it hashes to are fixed. The aggregate vector uses a fixed
projection, so a property added to the contract does not send anyone editing a
pinned value: what is fixed is the algorithm, not this package's rule set.
The fixture values are checked against `enforceableRuleProblem`, the validator the
runtime itself uses, so a vector cannot describe a document no guard would accept.
Not on the request path and not in the published package. Every supported
invocation either emits the report or fails loudly: the absent-argument case is
answered before any path is resolved, so the module stays importable, and a named
script is compared by resolved real path, so a link or a path needing encoding
runs rather than exiting 0 having printed nothing. An entry that will not resolve
is an import, because a run of a path node cannot resolve never reaches any module.
patchstackdave
force-pushed
the
feat/publish-a-digest-of-the-shipped-rule-set
branch
from
September 8, 2026 06:32
b45fc88 to
c046ef7
Compare
patchstackdave
deleted the
feat/publish-a-digest-of-the-shipped-rule-set
branch
September 8, 2026 07:22
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Why
This package compiles a set of rules in, and the platform serves the same policies as documents it can
revise. Both copies exist deliberately: one protects an app before it is enrolled, the other can be
corrected without a release. They are only useful together if they agree, and nothing mechanical says
whether they do.
What this adds
scripts/rule-set-digest.mjs— a development script, not part of the published package and not on therequest path. It prints a digest per rule and one over the whole set, so checking agreement is a
comparison of two values instead of a reading of two files.
--canonicalprints the material behind thedigest, because a digest reports only that two things differ while whoever has to fix it needs to know
how.
It is a comparison projection, not a behaviour-parity proof
The compared fields are
RULE_PROPERTIESminus an explicit exclusion allowlist of five —id,rule_id,title,source_revision,enforcement— each carrying its reason where it sits. They areexcluded because the two copies are meant to differ on them, not because they have no effect:
enforcementdecides whether a matching rule acts at all, andtitlebecomes the block message for arule that declares no
message. So equal digests mean the copies agree on everything compared here, andnot that they behave identically.
Deriving the projection from the contract has two consequences: a property added to the contract is
compared by default, and a test holds the projection to the contract with a stated reason per exclusion,
so removing a field from the comparison has to be argued for rather than drifting in.
The form is stated, not delegated to an encoder
The other side of this comparison is another language, and the two encoders disagree on values the rule
contract accepts:
cookie_flags: {}{}[]after an associative decode-00-01e-71e-71.0e-7{"10":…,"2":…}So the canonical text is written out: keys sorted as text at every depth, arrays in order, an empty
object as
{}, only",\and the characters below a space escaped and those as\u00xxrather thanthe short forms, and numbers only where they are safe integers.
Integrality is not the bound.
1e21is an integer here and writes itself in exponent form, while animplementation holding it as an integer writes the digits, and one converting it to a 64-bit integer
gets a different number altogether. Above 2^53 there is no shared spelling, so the form stops there.
A value the form cannot pin down is refused, not approximated. A digest is worth comparing only if
the other side would produce the same one; a value written differently there reports drift between
documents that agree.
An absent property is not an authored null
Only the properties a rule states appear in its material. The contract treats an absent property and
an authored
nullas different documents and refuses that null for every property outsideNULL_EXEMPT_PROPERTIES— so of two copies, the one authoringnullcan have its rule refused outrightwhile the other's runs on the engine default. Nothing is refused here either, so an exempt
capture: nullis recorded as the declaration it is.The set digest covers the terms of the comparison
The whole-set value hashes the projection and a material version alongside the rule digests. An
undeclared property contributes nothing to any rule's digest, and most of the compared properties are
declared by no rule this package ships — so over the rule digests alone, a comparator that stopped
comparing one of those would emit the identical set value.
Testing
32 cases. No digest value of the shipped rule set is asserted: a snapshot would need updating whenever
a rule legitimately changes, and a test that must be edited to keep passing stops describing anything.
The properties are asserted of it instead — every shipped rule covered, digests distinct, the digest
moves when any compared field moves or is removed, distinguishes absent from authored
null, holdsacross key order and rule order, moves on clause reorder, stays aligned with
RULE_PROPERTIES, andnever lets a rule declaring nothing agree with a rule declaring a value.
Version 2 is pinned by a fixed synthetic vector, since relational properties survive a changed hash, a
changed truncation and a changed serialisation while breaking the other implementation. The vector
carries nested key ordering, numeric-looking keys, array order, a slash and a backslash inside an
operand, non-ASCII text, control characters, an empty object, an integer, a boolean, an authored null and
absence — and is pinned twice: the canonical text is written out in the test independently of the code, and the bytes are fixed.
The aggregate vector uses a fixed projection, so a property added to the contract does not send anyone
editing a pinned value.
The vector is checked against
enforceableRuleProblem, the validator the runtime itself uses, so theformat is not pinned against a document no guard would accept.
--canonicaloutput is checked to hashto the digest reported for that rule, which is what makes it the material rather than a description of
it.
Full suite: 2512 passing, 7 skipped.
typecheck,buildandgit diff --checkclean.