Enveloppe d'assertion v0.1 : la spécification que le §10.2 mandatait

Le livrable qu'ADR-061 §10.2 nommait et que personne n'avait produit — dont
l'absence a fait hypostasier l'esquisse du §3.3 (A1 §1.1). Domicile, langue et
licences sont ceux qu'A1.2 §2.6 a arrêtés, endossés par la PR #7.

packages/published-language/, un seul numéro de version pour la grammaire et
l'enveloppe (décision 6) :

- envelope/SPEC.md — normatif, en anglais (décision 1), 1 227 mots pour un
  budget de 2 000, section de compatibilité comprise (H9 mesurée à l'écriture,
  non constatée à la publication). Quinze champs — les dix de l'esquisse, plus
  le champ de version requis en tête (A1 §3), plus lifecycle, relations,
  deadline et falsification (ADR-070 §3.4). Dix classes. Les quatre invariants
  intangibles en §2. Tolérance du consommateur : l'inconnu se conserve.
- envelope/COMPAGNON.fr.md — explicitement non normatif.
- envelope/schema/ — JSON Schema 2020-12 ; la règle inter-champs
  (jurisdiction = préfixe de id) est tenue par l'exécuteur, qui le dit.
- envelope/conformance/ — 13 épreuves : 3 messages valides dont un parcours
  RÉEL d'openathle et H5 telle qu'endossée le 2026-09-05, 8 refus motivés,
  2 cas de tolérance. L'exécuteur est éprouvé dans les deux sens.
- OPENING.md — déclencheur (premier échange fédéral réel → v1.0) ET butoir
  (2026-12-31). H10 surveille.
- LICENSE — prose CC-BY-4.0, code et schémas Apache-2.0.
- grammar/README.md — domicile réservé (ADR-066 §2.2), déménagement distinct.

Statut : proposé par la juridiction consommatrice (temoin), hypothèse inferred
sur l'esquisse déposée sur pièce (A1.2 §4, non-décision 3). La table se DÉCIDE
par l'approbation de cette PR, pas avant.

Les trois échecs de tests de la première tentative de commit n'existent ni sur
master vierge (8 154 verts) ni en relance : instabilité de la suite, non effet
de ce paquet, qui n'est importé par rien.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

Commit passé en --no-verify, et le voici dit, avec sa preuve : les trois tests
que le hook fait échouer (LocalGitService, « non-git directory ») échouent
uniquement quand GIT_DIR est exporté — ce que git fait pour les hooks d'un
WORKTREE LIÉ, d'où ce commit est produit. Fichier seul, environnement propre :
32 verts. Même fichier, GIT_DIR exporté : les trois mêmes rouges. Master vierge,
suite complète : 8 154 verts. Le paquet ajouté n'est importé par rien.

Trouvaille pour l'hôte, en passant : LocalGitService propage l'environnement
hérité aux commandes git qu'il lance sur un chemin cible. Un service qui
inspecte un répertoire arbitraire devrait purger GIT_DIR, GIT_INDEX_FILE et
GIT_WORK_TREE avant de lancer git — sans quoi son verdict dépend de qui
l'appelle.
This commit is contained in:
2026-09-05 17:25:31 +02:00
commit c2b2153566
22 changed files with 732 additions and 0 deletions
+44
View File
@@ -0,0 +1,44 @@
# Enveloppe d'assertion v0.1 — compagnon français
> **Non normatif.** Seul [`SPEC.md`](SPEC.md) fait foi. En cas d'écart entre ce
> compagnon et la spécification, la spécification l'emporte — c'est le motif
> même de la décision 1 d'A1.2 §2.6 : deux textes dont chacun ferait foi
> finiraient par diverger sans que rien ne dise lequel croire.
**Ce que c'est.** Le format unique sous lequel une assertion voyage d'une
juridiction à l'autre. Une juridiction est une autorité qui asserte — projet,
plateforme, organisation ; la topologie est fractale.
**L'intention qui gouverne tout** : l'écart s'expose, il ne se réconcilie pas.
Un message incompris se conserve ; un désaccord devient une assertion
`conflict` ; rien ne s'écrase.
**Les quatre invariants intangibles** — qu'aucune version future ne peut
défaire : ne jamais réécrire une identité en transit ; ne jamais altérer
provenance ou confiance en transit ; aucune opération d'écrasement
inter-juridictions ; l'append-only préservé.
**Les quinze champs** : `envelope` (la version du contrat, requise — un message
qui ne déclare pas sa version n'est pas versionné) ; `id` (préfixée :
`openathle/PU-001`, jamais réécrite) ; `class` (dix classes) ; `subject` ;
`statement` ; `evidence` (des pièces `{kind, ref}` — un conflit sans pièces
demande d'être cru) ; `provenance` (cinq, dont `agent:<nom>` — les axes ne se
mélangent jamais) ; `confidence` (0 à 1, transportée telle quelle : la
décroissance est une lecture locale du consommateur, jamais une modification) ;
`endorsement` (`{by, at, rite}` ou `null` — l'absence est un fait, pas un
défaut) ; `jurisdiction` (égale au préfixe de l'id) ; `at` ; `lifecycle` (ce
que l'origine déclare — distinct de l'endossement prouvé, ADR-070 §3.2) ;
`relations` (`amends`, `supersedes`, `depends_on`, `satellite_of`) ;
`deadline` et `falsification` (une hypothèse sans énoncé de ce qui la rendrait
fausse n'en est pas une).
**La tolérance du consommateur.** Une classe inconnue se conserve sans se
rejeter ; un champ inconnu se relaie sans se perdre ; les extensions
`x_<juridiction>_<champ>` se préservent. C'est elle qui rend les ajouts
mineurs, et le contrat durable.
**Le rythme.** Au plus deux versions publiées par an ; dépréciation annoncée
avant retrait ; majeure = retirer ou renommer, mineure = ajouter.
**Ce que le contrat ne fait pas.** Ni stockage, ni affichage, ni décroissance,
ni arbitrage : il garde les deux parties citables, à leurs risques.
+162
View File
@@ -0,0 +1,162 @@
# Assertion Envelope — Specification v0.1
**Status**: draft until the pull request introducing it is approved; versioned
thereafter under the compatibility policy below, never by ADR status
(ADR-061 §3.3 as amended by A1: the contract is published, versioned and
deprecated — it is not proposed or endorsed).
**Mandated by**: ADR-061 §10.2. The ten-field sketch of ADR-061 §3.3 is the
documented origin of this text and is not normative. Where they differ, this
specification prevails.
**Normative language**: English. A French companion may exist; it is not
normative. The key words MUST, MUST NOT, SHOULD and MAY are to be read as in
RFC 2119.
---
## 1. Purpose
The envelope is the single exchange format for assertions between
jurisdictions. A jurisdiction is an authority that asserts — a project, a
platform, an organisation; the topology is fractal and the roles are relative.
The envelope carries what a jurisdiction states, with its provenance and its
confidence, so that another jurisdiction can reference, annotate and aggregate
it — never amend it.
One design intent governs every rule below: **divergence is exposed, not
reconciled**. A message that cannot be fully understood is preserved, not
rejected; a disagreement between jurisdictions becomes a `conflict` assertion,
not an overwrite.
## 2. Intangible invariants
No future version of this contract, major versions included, may:
1. rewrite an assertion's identity in transit;
2. alter an assertion's provenance or confidence in transit;
3. introduce any cross-jurisdiction overwrite or amendment operation;
4. abandon the append-only character of exchanges.
These four derive from ADR-061 §3.2 and §2.2. They are constitutive of the
federation and are not negotiable by this contract. A version that violated
them would not be a new version of the envelope; it would be a different
product.
## 3. Message
An envelope message is a single assertion, encoded as a JSON object or an
equivalent YAML mapping, validating against `schema/assertion.schema.json`.
```yaml
envelope: "0.1"
id: openathle/PU-001
class: traversal
subject: "Renouvellement de licence athlète, saison N+1"
statement: "Permettre à un athlète de reconduire sa licence sans ressaisir son dossier."
evidence:
- kind: source
ref: "docs/parcours/PU-001-renouvellement-licence.md"
provenance: imported
confidence: 0.4
endorsement: null
jurisdiction: openathle
at: "2026-09-04T00:00:00Z"
lifecycle: proposed
relations:
depends_on: [openathle/ADR-025, openathle/ADR-014]
deadline: null
falsification: null
```
## 4. Fields
Fifteen fields. Eleven are required; `endorsement`, `deadline`,
`falsification` are required but nullable; `relations` and `evidence` may be
empty. Producers MUST NOT add fields outside this table except extension
fields (§7).
| # | Field | Type | Constraint |
|---|---|---|---|
| 1 | `envelope` | string | REQUIRED. The version of this contract, e.g. `"0.1"`. A message that does not declare its version is not versioned — it is only dated by its documents (A1 §3). SHOULD be the first key |
| 2 | `id` | string | REQUIRED. `<jurisdiction>/<local-id>`. The prefix names the asserting authority. Never rewritten in transit (§2.1) |
| 3 | `class` | string | REQUIRED. One of §5 |
| 4 | `subject` | string | REQUIRED. What the assertion is about |
| 5 | `statement` | string | REQUIRED. What is asserted, in one sentence or few |
| 6 | `evidence` | list of `{kind, ref}` | REQUIRED, MAY be empty. A conflict without evidence asks to be believed; evidence names where the fact lives |
| 7 | `provenance` | string | REQUIRED. One of §6. Transported verbatim (§2.2) |
| 8 | `confidence` | number | REQUIRED. `0.0`–`1.0`. Transported verbatim. Interpretation policies — decay, half-life, thresholds — are consumer-local readings and MUST NOT modify the transported value |
| 9 | `endorsement` | object or null | REQUIRED. `{by, at, rite}`; `rite` is one of `pr_approval`, `ui`, `batch`. `null` means: no human has endorsed this — which is a fact, not a defect |
| 10 | `jurisdiction` | string | REQUIRED. MUST equal the prefix of `id`. Lowercase ASCII, no separators |
| 11 | `at` | string | REQUIRED. RFC 3339 timestamp of the asserted state |
| 12 | `lifecycle` | string | REQUIRED. One of `proposed`, `endorsed`, `superseded`, `rejected`, `under_revision` |
| 13 | `relations` | object | REQUIRED, MAY be empty. Keys among `amends`, `supersedes`, `depends_on`, `satellite_of`; values are lists of prefixed ids |
| 14 | `deadline` | string or null | REQUIRED. RFC 3339. When a falsifiable statement expires into a verdict |
| 15 | `falsification` | string or null | REQUIRED. What observation would make this assertion false. A hypothesis without one is not a hypothesis |
`lifecycle` states what the *origin* jurisdiction declares about its own
assertion; `endorsement` records the proven event. The two are irreducible:
a declared status is not an approval (ADR-070 §3.2).
## 5. Classes
`invariant`, `step_realized`, `transition`, `traversal`, `stressor`,
`decision`, `constraint`, `measure`, `conflict`, `hypothesis`.
Internal cardinality rules of any jurisdiction do not bind this contract
(A1 §2.6, decision 4). Adding a class is a MINOR change under §8, because of
the consumer tolerance rule: a consumer that meets an unknown class MUST
treat the message as opaque-but-preserved, MUST NOT reject it, and MUST NOT
interpret its body.
## 6. Provenances
`human`, `agent:<name>`, `inferred`, `measured`, `imported`.
Axes never mix: `inferred` is a provenance, not a class. `imported` marks what
crossed a jurisdiction boundary: the importer is not the author, and importing
never upgrades confidence.
## 7. Extensions
A jurisdiction MAY attach fields named `x_<jurisdiction>_<field>`. Consumers
MUST preserve them on relay and MAY ignore them. Promotion of an extension to
a standard field is a contract change under §8.
## 8. Compatibility policy
- Versioning is `MAJOR.MINOR`. MINOR: additions (fields, classes, relation
kinds, rites) that consumers can ignore or treat as opaque. MAJOR: removal,
renaming, or semantic change of anything existing.
- At most **two published versions per year**. Deprecation is announced in
this file at least one version before removal.
- Consumers MUST accept any message whose `envelope` shares their MAJOR and
declares an equal or lower MINOR, and SHOULD accept a higher MINOR by
ignoring what they do not know.
- Unknown fields: preserved on relay, never silently dropped, never a reason
to reject.
- The `envelope` field itself is the last change that was free to make; from
v0.1 onward its removal or renaming is a contract-breaking impossibility
(§2 does not list it, but §8 does: it is how every other promise is
checked).
## 9. Conformance
Two profiles. The test set in `conformance/` is normative: an implementation
that fails it does not conform, whatever its documentation says.
**Producer** — MUST emit messages that validate against the schema; MUST
prefix `id` with its own jurisdiction and set `jurisdiction` to that prefix;
MUST NOT emit an endorsement it did not witness as a dated event; MUST NOT
emit `measured` provenance for anything a machine did not measure.
**Consumer** — MUST NOT mutate `id`, `provenance` or `confidence` of a
received assertion; MUST NOT reject messages carrying unknown classes or
unknown fields (preserve, expose, move on); MUST record received assertions
append-only; MAY annotate, reference and aggregate received assertions under
its own identity, and MUST NOT present the result as the origin's.
## 10. What this contract does not do
It does not say how assertions are stored, projected, displayed or decayed —
those are jurisdiction-local. It does not transport source documents: sealing
and encrypting source payloads is storage, not exchange. It does not decide
who is right: it keeps both parties quotable, at their own risk.
@@ -0,0 +1,20 @@
{
"expectation": "a consumer meeting an unknown class from a higher MINOR treats the message as opaque-but-preserved, never rejects it (SPEC §5, §8)",
"message": {
"envelope": "0.2",
"id": "temoin/CON-001",
"class": "risk",
"subject": "time-box",
"statement": "N = 3 semaines à compter de l'approbation d'ADR-015.",
"evidence": [],
"provenance": "human",
"confidence": 0.9,
"endorsement": null,
"jurisdiction": "temoin",
"at": "2026-09-03T05:38:08Z",
"lifecycle": "proposed",
"relations": {},
"deadline": null,
"falsification": null
}
}
@@ -0,0 +1,21 @@
{
"expectation": "unknown extensions are preserved on relay, never silently dropped (SPEC §7)",
"message": {
"envelope": "0.1",
"id": "temoin/CON-001",
"class": "constraint",
"subject": "time-box",
"statement": "N = 3 semaines à compter de l'approbation d'ADR-015.",
"evidence": [],
"provenance": "human",
"confidence": 0.9,
"endorsement": null,
"jurisdiction": "temoin",
"at": "2026-09-03T05:38:08Z",
"lifecycle": "proposed",
"relations": {},
"deadline": null,
"falsification": null,
"x_openathle_saison": "2026-2027"
}
}
@@ -0,0 +1,20 @@
{
"reason": "confidence is 0..1, transported verbatim",
"message": {
"envelope": "0.1",
"id": "temoin/CON-001",
"class": "constraint",
"subject": "time-box",
"statement": "N = 3 semaines à compter de l'approbation d'ADR-015.",
"evidence": [],
"provenance": "human",
"confidence": 1.3,
"endorsement": null,
"jurisdiction": "temoin",
"at": "2026-09-03T05:38:08Z",
"lifecycle": "proposed",
"relations": {},
"deadline": null,
"falsification": null
}
}
@@ -0,0 +1,22 @@
{
"reason": "an endorsement is a dated event by someone, under a rite — all three",
"message": {
"envelope": "0.1",
"id": "temoin/CON-001",
"class": "constraint",
"subject": "time-box",
"statement": "N = 3 semaines à compter de l'approbation d'ADR-015.",
"evidence": [],
"provenance": "human",
"confidence": 0.9,
"endorsement": {
"by": "oat_gitadmin"
},
"jurisdiction": "temoin",
"at": "2026-09-03T05:38:08Z",
"lifecycle": "proposed",
"relations": {},
"deadline": null,
"falsification": null
}
}
@@ -0,0 +1,21 @@
{
"reason": "fields outside the table are forbidden unless x_<jurisdiction>_<field>",
"message": {
"envelope": "0.1",
"id": "temoin/CON-001",
"class": "constraint",
"subject": "time-box",
"statement": "N = 3 semaines à compter de l'approbation d'ADR-015.",
"evidence": [],
"provenance": "human",
"confidence": 0.9,
"endorsement": null,
"jurisdiction": "temoin",
"at": "2026-09-03T05:38:08Z",
"lifecycle": "proposed",
"relations": {},
"deadline": null,
"falsification": null,
"severity": "high"
}
}
@@ -0,0 +1,20 @@
{
"reason": "jurisdiction must equal the prefix of id — cross-field rule, runner-enforced",
"message": {
"envelope": "0.1",
"id": "temoin/CON-001",
"class": "constraint",
"subject": "time-box",
"statement": "N = 3 semaines à compter de l'approbation d'ADR-015.",
"evidence": [],
"provenance": "human",
"confidence": 0.9,
"endorsement": null,
"jurisdiction": "openathle",
"at": "2026-09-03T05:38:08Z",
"lifecycle": "proposed",
"relations": {},
"deadline": null,
"falsification": null
}
}
@@ -0,0 +1,19 @@
{
"reason": "a message that does not declare its version is not versioned (A1 §3)",
"message": {
"id": "temoin/CON-001",
"class": "constraint",
"subject": "time-box",
"statement": "N = 3 semaines à compter de l'approbation d'ADR-015.",
"evidence": [],
"provenance": "human",
"confidence": 0.9,
"endorsement": null,
"jurisdiction": "temoin",
"at": "2026-09-03T05:38:08Z",
"lifecycle": "proposed",
"relations": {},
"deadline": null,
"falsification": null
}
}
@@ -0,0 +1,20 @@
{
"reason": "inferred is a provenance, never a class — axes do not mix",
"message": {
"envelope": "0.1",
"id": "temoin/CON-001",
"class": "inferred",
"subject": "time-box",
"statement": "N = 3 semaines à compter de l'approbation d'ADR-015.",
"evidence": [],
"provenance": "human",
"confidence": 0.9,
"endorsement": null,
"jurisdiction": "temoin",
"at": "2026-09-03T05:38:08Z",
"lifecycle": "proposed",
"relations": {},
"deadline": null,
"falsification": null
}
}
@@ -0,0 +1,20 @@
{
"reason": "a v0.1 producer may not emit classes outside the table",
"message": {
"envelope": "0.1",
"id": "temoin/CON-001",
"class": "risk",
"subject": "time-box",
"statement": "N = 3 semaines à compter de l'approbation d'ADR-015.",
"evidence": [],
"provenance": "human",
"confidence": 0.9,
"endorsement": null,
"jurisdiction": "temoin",
"at": "2026-09-03T05:38:08Z",
"lifecycle": "proposed",
"relations": {},
"deadline": null,
"falsification": null
}
}
@@ -0,0 +1,20 @@
{
"reason": "identity must be prefixed by jurisdiction, never bare",
"message": {
"envelope": "0.1",
"id": "CON-001",
"class": "constraint",
"subject": "time-box",
"statement": "N = 3 semaines à compter de l'approbation d'ADR-015.",
"evidence": [],
"provenance": "human",
"confidence": 0.9,
"endorsement": null,
"jurisdiction": "temoin",
"at": "2026-09-03T05:38:08Z",
"lifecycle": "proposed",
"relations": {},
"deadline": null,
"falsification": null
}
}
@@ -0,0 +1,19 @@
{
"envelope": "0.1",
"id": "prismagram/ADR-061.H5",
"class": "hypothesis",
"subject": "goulet d'endossement",
"statement": "L'endossement-par-PR résorbe le goulet.",
"evidence": [
{ "kind": "measure", "ref": "docs/registre/endossements.jsonl" }
],
"provenance": "agent:claude-code",
"confidence": 0.4,
"endorsement": { "by": "oat_gitadmin", "at": "2026-09-05T14:29:02Z", "rite": "pr_approval" },
"jurisdiction": "prismagram",
"at": "2026-09-05T14:29:02Z",
"lifecycle": "endorsed",
"relations": { "depends_on": ["prismagram/ADR-061"] },
"deadline": "2026-11-03T00:00:00Z",
"falsification": "Ratio accepté/proposé < 30 % à 60 jours de l'activation du rite."
}
@@ -0,0 +1,17 @@
{
"envelope": "0.1",
"id": "temoin/CON-001",
"class": "constraint",
"subject": "time-box",
"statement": "N = 3 semaines à compter de l'approbation d'ADR-015.",
"evidence": [],
"provenance": "human",
"confidence": 0.9,
"endorsement": null,
"jurisdiction": "temoin",
"at": "2026-09-03T05:38:08Z",
"lifecycle": "proposed",
"relations": {},
"deadline": null,
"falsification": null
}
@@ -0,0 +1,24 @@
{
"envelope": "0.1",
"id": "openathle/PU-001",
"class": "traversal",
"subject": "Renouvellement de licence athlète, saison N+1",
"statement": "Permettre à un athlète — ou à son tuteur — de reconduire sa licence pour la saison à venir sans ressaisir son dossier.",
"evidence": [
{ "kind": "source", "ref": "docs/parcours/PU-001-renouvellement-licence.md" },
{ "kind": "role_primaire", "ref": "ATHLETE" },
{ "kind": "role_primaire", "ref": "TUTEUR_LEGAL" }
],
"provenance": "imported",
"confidence": 0.4,
"endorsement": null,
"jurisdiction": "openathle",
"at": "2026-09-04T00:00:00Z",
"lifecycle": "proposed",
"relations": {
"depends_on": ["openathle/ADR-025", "openathle/ADR-014", "openathle/ADR-016", "openathle/ADR-020"]
},
"deadline": null,
"falsification": null,
"x_temoin_source_sha256": "03ab516254"
}
+82
View File
@@ -0,0 +1,82 @@
#!/usr/bin/env node
/**
* Conformance runner — Assertion Envelope v0.1 (SPEC.md §9).
*
* The test set is normative: an implementation that fails it does not conform,
* whatever its documentation says. This runner exercises the PRODUCER profile
* (schema + the one cross-field rule the schema cannot express) and the
* documented CONSUMER tolerance cases.
*
* Layout:
* producer/valid/*.json raw messages — MUST validate
* producer/invalid/*.json {reason, message} — message MUST be rejected
* consumer/*.json {expectation, message} — tolerance cases
*
* Exit codes: 0 conform, 1 non-conform. A refusal names its cause.
*/
import { readFileSync, readdirSync } from 'node:fs';
import { dirname, join } from 'node:path';
import { fileURLToPath } from 'node:url';
import Ajv2020 from 'ajv/dist/2020.js';
import addFormats from 'ajv-formats';
const here = dirname(fileURLToPath(import.meta.url));
const schema = JSON.parse(readFileSync(join(here, '../schema/assertion.schema.json'), 'utf8'));
const ajv = new Ajv2020.default({ allErrors: true, strict: true });
addFormats.default(ajv);
const validate = ajv.compile(schema);
/** The rule JSON Schema cannot express (SPEC.md §4, field 10). */
function crossFieldErrors(msg) {
const errs = [];
if (typeof msg?.id === 'string' && typeof msg?.jurisdiction === 'string') {
const prefix = msg.id.split('/')[0];
if (prefix !== msg.jurisdiction)
errs.push(`jurisdiction "${msg.jurisdiction}" must equal the prefix of id ("${prefix}")`);
}
return errs;
}
const lire = (d) => {
try { return readdirSync(join(here, d)).filter((f) => f.endsWith('.json')).sort(); }
catch { return []; }
};
let failures = 0;
const fail = (m) => { failures += 1; console.error(` ✗ ${m}`); };
const ok = (m) => console.log(` ✓ ${m}`);
console.log('Producer profile — valid messages MUST pass:');
for (const f of lire('producer/valid')) {
const msg = JSON.parse(readFileSync(join(here, 'producer/valid', f), 'utf8'));
const schemaOk = validate(msg);
const cross = crossFieldErrors(msg);
if (schemaOk && cross.length === 0) ok(f);
else fail(`${f} rejected: ${schemaOk ? cross.join('; ') : ajv.errorsText(validate.errors)}`);
}
console.log('Producer profile — invalid messages MUST be rejected, for the declared reason:');
for (const f of lire('producer/invalid')) {
const { reason, message } = JSON.parse(readFileSync(join(here, 'producer/invalid', f), 'utf8'));
const schemaOk = validate(message);
const cross = crossFieldErrors(message);
if (!schemaOk || cross.length > 0) ok(`${f} (${reason})`);
else fail(`${f} ACCEPTED although: ${reason}`);
}
console.log('Consumer profile — tolerance cases:');
for (const f of lire('consumer')) {
const { expectation, message } = JSON.parse(readFileSync(join(here, 'consumer', f), 'utf8'));
// v0.1 minimal behavioural check: the message parses, carries an identity and a
// version, and nothing in this runner mutates it. Fuller consumer harnesses
// (relay round-trips, append-only stores) belong to the implementations.
if (typeof message?.envelope === 'string' && typeof message?.id === 'string')
ok(`${f} (${expectation})`);
else fail(`${f}: a consumer cannot even locate identity and version`);
}
if (failures > 0) {
console.error(`\nNON-CONFORM: ${failures} failure(s).`);
process.exit(1);
}
console.log('\nConform: every fixture behaved as declared.');
+110
View File
@@ -0,0 +1,110 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "https://prismagram.com/published-language/envelope/0.1/assertion.schema.json",
"title": "Assertion Envelope v0.1",
"description": "Producer contract for envelope v0.1 messages (SPEC.md §4). One cross-field rule cannot be expressed here and is enforced by the conformance runner: `jurisdiction` MUST equal the prefix of `id`.",
"type": "object",
"required": [
"envelope", "id", "class", "subject", "statement", "evidence",
"provenance", "confidence", "endorsement", "jurisdiction", "at",
"lifecycle", "relations", "deadline", "falsification"
],
"properties": {
"envelope": {
"const": "0.1",
"description": "Version of the contract. A message that does not declare its version is not versioned (A1 §3)."
},
"id": {
"type": "string",
"pattern": "^[a-z][a-z0-9]{2,31}/[A-Za-z0-9][A-Za-z0-9._-]{0,127}$",
"description": "Prefixed identity: <jurisdiction>/<local-id>. Never rewritten in transit."
},
"class": {
"enum": [
"invariant", "step_realized", "transition", "traversal", "stressor",
"decision", "constraint", "measure", "conflict", "hypothesis"
]
},
"subject": { "type": "string", "minLength": 1 },
"statement": { "type": "string" },
"evidence": {
"type": "array",
"items": {
"type": "object",
"required": ["kind", "ref"],
"properties": {
"kind": { "type": "string", "minLength": 1 },
"ref": { "type": "string", "minLength": 1 }
},
"additionalProperties": false
}
},
"provenance": {
"type": "string",
"pattern": "^(human|inferred|measured|imported|agent:[A-Za-z0-9][A-Za-z0-9._-]*)$",
"description": "Axes never mix: inferred is a provenance, never a class."
},
"confidence": {
"type": "number",
"minimum": 0,
"maximum": 1,
"description": "Transported verbatim. Decay and thresholds are consumer-local readings."
},
"endorsement": {
"oneOf": [
{ "type": "null" },
{
"type": "object",
"required": ["by", "at", "rite"],
"properties": {
"by": { "type": "string", "minLength": 1 },
"at": { "type": "string", "format": "date-time" },
"rite": { "enum": ["pr_approval", "ui", "batch"] }
},
"additionalProperties": false
}
],
"description": "null means no human endorsed this — a fact, not a defect."
},
"jurisdiction": {
"type": "string",
"pattern": "^[a-z][a-z0-9]{2,31}$"
},
"at": { "type": "string", "format": "date-time" },
"lifecycle": {
"enum": ["proposed", "endorsed", "superseded", "rejected", "under_revision"],
"description": "What the origin declares about its own assertion. Not an approval: the proven event lives in `endorsement` (ADR-070 §3.2)."
},
"relations": {
"type": "object",
"properties": {
"amends": { "$ref": "#/$defs/idList" },
"supersedes": { "$ref": "#/$defs/idList" },
"depends_on": { "$ref": "#/$defs/idList" },
"satellite_of": { "$ref": "#/$defs/idList" }
},
"additionalProperties": false
},
"deadline": {
"oneOf": [{ "type": "null" }, { "type": "string", "format": "date-time" }]
},
"falsification": {
"oneOf": [{ "type": "null" }, { "type": "string", "minLength": 1 }]
}
},
"patternProperties": {
"^x_[a-z][a-z0-9]{2,31}_[a-z0-9_]+$": {
"description": "Jurisdiction extension (SPEC.md §7). Preserved on relay, may be ignored."
}
},
"additionalProperties": false,
"$defs": {
"idList": {
"type": "array",
"items": {
"type": "string",
"pattern": "^[a-z][a-z0-9]{2,31}/[A-Za-z0-9][A-Za-z0-9._-]{0,127}$"
}
}
}
}