@flashyos/conformance
v0.2.3
Published
Run the FlashyOS conformance suite against any domain, or audit your own charter and handshake offline. Scaffolds a new property to L2 with one command. Exit 0 only when the level you asked for is actually met.
Maintainers
Readme
@flashyos/conformance
██
██
██████
██
██
██Is this organization actually accountable, or does it just say so?
npx @flashyos/conformance yourdomain.comFetches your /.well-known/flashyos.json, checks it against the flashyos/1
contract, reads your AAO charter, runs every static conformance question, and
then asks the register the three questions a file cannot answer.
Three levels
| | | |
|---|---|---|
| L1 Discoverable | A valid flashyos/1 handshake. One file. | offline · self-claimed |
| L2 Chartered | Plus an AAO charter that passes every static question. | offline · self-claimed |
| L3 Accountable | Plus who authorized this agent, can it be revoked, what did it do — read from the register, not from you. | carries the mark |
L1 and L2 need no account, no network call to us, and no permission. Run them in your own CI on every commit and publish the result wherever you like.
L3 is different on purpose. An organization that certifies its own agents are revocable has said nothing, so those three verdicts come from the register. It is the only level that carries the mark — see MARK.md.
In CI
- run: npx @flashyos/conformance ${{ vars.DOMAIN }} --level 2Exit codes are three, not two:
| | |
|---|---|
| 0 | the level you asked for is met |
| 1 | a check at that level failed |
| 2 | the run could not reach a verdict |
2 exists because a suite that exits green on "I could not tell" is how
green ticks stop meaning anything.
As a library
import { runConformance, renderReport } from '@flashyos/conformance';
const report = await runConformance({ domain: 'acme.example' });
// { spec: 'flashyos-conformance/1', level: 2, mark: false, checks: [...] }
if (!report.mark) {
console.log(renderReport(report, 3).lines.join('\n'));
}runConformance takes an injected fetchImpl and now, so the whole suite is
testable without a network and reproducible against a frozen clock.
Where the charter lives
The suite looks for your charter in two places, in order:
- a
charterkey on your handshake, holding anhttpsURL; /.well-known/flashyos-charter.json.
charter is not part of the flashyos/1 contract. The contract allows unknown
top-level keys and ignores them, which is exactly what lets the suite define a
convention on top of the contract rather than a new version underneath it.
An org with no charter is not malformed — it just cannot reach L2, and the
report says so in those words.
The licence, and the mark
This package, the spec, the schema and every verifier are Apache-2.0. Fork them, ship a competing implementation, run the suite against your own register. That is what a standard is for.
The name, the ⚡ mark and the words FlashyOS Conformant are not covered by that licence. MARK.md says who may claim what, and is candid about which half of the programme is built and which half still needs a lawyer.
Turnkey: a new property, from nothing to L2
npx @flashyos/conformance init \
--slug your-org --name "Your Org" \
--description "One line: what this organization is." \
--accountable-to [email protected] \
--profile https://yourdomain.com \
--repository your-repoFour files: the charter, both well-known surfaces, and the test that keeps
them true. --serve static if you ship a public/ directory instead of Next
route handlers; --test jest|node-test to match your suite; --dry-run to
see it first.
It refuses to overwrite anything, and it refuses before writing rather than halfway through — a half-written scaffold is worse than none.
The charter it writes declares one role. That is not a stub to fill in. A generator that produced ten plausible roles would produce exactly the roster inflation this standard exists to stop, and the organization would ship it unread. Add the responsibilities you actually hold, and leave whole families empty when nothing holds them. An empty family is honest.
Auditing your own documents, offline
init writes a test that calls this. You can call it directly:
import { assertProperty } from '@flashyos/conformance';
assertProperty({ charter, handshake }); // throws with EVERY problem at onceauditProperty returns { ok, level, issues } if you would rather branch than
throw. Both apply the same rules the CLI applies to a live domain, to the two
documents as files — no network, no production read, and it works in the CI of
a property that has not shipped yet.
Every constant it checks against comes from @flashyos/aao: the ten families,
the 24-character role-name cap, the top-level keys. Copying those into your own
test is the mistake this function exists to spare you — nine properties in the
FlashyOS estate each made it once, and a copied list has nothing to notice when
the spec moves.
What it cannot tell you
That your domain serves the files. Every property in the FlashyOS estate had a valid charter in its repository and served it nowhere the checker looks, so every one of them was L1 — including flashyos.com, which publishes this package. Run the domain check after a deploy:
npx @flashyos/conformance yourdomain.com --level 2A repository with no package.json
Some properties have no dependencies on purpose — a static site generator with
nothing to install, for instance. assertProperty is not available there, and
that is fine: npx fetches this package without adding it to anything, so the
domain check is the whole CI step.
- run: npx @flashyos/conformance yourdomain.com --level 2That check is strictly stronger than the offline one — it proves the domain actually serves the files, which is the half a test cannot reach. What it costs is a network call and a deployed site, so it belongs after a deploy rather than on every commit. A repository in this position should keep a small hand-written test over its own two files and accept that it will not track the spec automatically; the estate has exactly one, and it is documented there as the exception rather than the pattern.
Two vocabularies
A role name is a governance label. A capability is a discovery tag. They are
often the same word and do not have to be: nobody searching a network types
standard, and capability-matching is not a standing responsibility anyone
is accountable for. So the rule is that every advertised capability is
answered for by some role — either by name, or through a role's
x-capability (a string, or an array of them).
x-capability is also the answer when a capability slug is longer than the
24-character role-name cap. Keep the slug, name the role something shorter,
and carry the long form. A truncated role name is a different role.
