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
6 changes: 6 additions & 0 deletions SCHEMA_INDEX.md
Original file line number Diff line number Diff line change
Expand Up @@ -54,3 +54,9 @@ To unblock the GA track described in [`docs/roadmap/ROAD-TO-V4-GA.md` §P0-2](./
- **No SDK is bumped.** No npm / PyPI / Zenodo release is triggered. No git tag is created.

**Validation:** see [`scripts/validate_v4_schemas.py`](./scripts/validate_v4_schemas.py) for the canonical local validation runner.

**Cross-impl strict vectors (P0-6 — PR candidate):**

- Positive: [`tests/vectors_v40_ga.json`](./tests/vectors_v40_ga.json) — 5 persona payloads (`profile_kind` learner/team/agent/creator/learner+gaming) covering the v1 frozen surface of `verification_gates` (structured + flat-map), `human_veto_policy`, `claim_sources`, `media_profile`, `migration`, plus one unified `encrypted=true` envelope (envelope-v3 contract retained per SPEC §33.10 #2).
- Negative: [`tests/negative_vectors_v40_ga.json`](./tests/negative_vectors_v40_ga.json) — 12 rejection cases pinning each frozen rule (gate-level enum, missing/unknown `payload_schema_version`, `media_profile` v1 hash/modality strictness, `human_veto_policy.min_level`, encrypted envelope completeness, `klickd_version` major range, `migration.migrated_at` RFC 3339 Z, `gateEntry` / `human_veto_policy` `additionalProperties: false`).
- Runners: [`verify_vectors.py`](./verify_vectors.py) (canonical `jsonschema` validation + structural assertions) and [`verify_vectors.mjs`](./verify_vectors.mjs) (structural rule checker mirroring the strict schema — no Ajv dep required at root). Both implementations validate the same fixtures and reject the same negatives without divergence. Preview vectors ([`tests/vectors_v40_preview.json`](./tests/vectors_v40_preview.json)) remain intact.
9 changes: 8 additions & 1 deletion docs/roadmap/ROAD-TO-V4-GA.md
Original file line number Diff line number Diff line change
Expand Up @@ -159,12 +159,19 @@ Chaque entrée précise : *Objet → Livrables → Critères de sortie (Definiti
#### P0-6 — Vectors stricts + cross-impl

- **Objet :** suite de vectors v4 *normative*, séparée de la suite preview, partagée Python ↔ JS, qui couvre toutes les sections v4 normées.
- **Livrables :** `tests/vectors_v40.json`, `tests/roundtrip_v40.json`, `tests/negative_vectors_v40.json`, CI `test-vectors` étendu.
- **Livrables :** `tests/vectors_v40_ga.json`, `tests/negative_vectors_v40_ga.json`, runners `verify_vectors.py` / `verify_vectors.mjs` étendus (suite `v4.0 GA strict` cross-impl).
- **DoD :**
- chaque MUST de SPEC v4 a au moins un vecteur positif et un vecteur négatif ;
- exécutés par les deux SDKs sans divergence ;
- les vectors preview ne disparaissent pas (preuve de continuité).
- **Dépendances :** P0-2, P0-3, P0-4.
- **Statut (2026-05-24) :** **PR candidate.** Livrés :
[`tests/vectors_v40_ga.json`](../../tests/vectors_v40_ga.json) (6 positifs : 5 personas + 1 enveloppe chiffrée unifiée) et
[`tests/negative_vectors_v40_ga.json`](../../tests/negative_vectors_v40_ga.json) (12 négatifs : `gate_level` hors énum, `payload_schema_version` manquant/inconnu, `media_profile` v1 sans `hash` / `modality` hors énum / `hash.algo ≠ blake3`,
`human_veto_policy.min_level` invalide, enveloppe `encrypted=true` sans `kdf`/`cipher`/`ciphertext`, `klickd_version` hors range, `migration.migrated_at` non RFC 3339 Z, `gateEntry` avec champ inconnu,
`human_veto_policy` avec champ inconnu). Les vectors preview ([`tests/vectors_v40_preview.json`](../../tests/vectors_v40_preview.json)) restent inchangés (continuité).
Cross-impl : Python (`verify_vectors.py` — validation `jsonschema` stricte + assertions) **et** JS (`verify_vectors.mjs` — checker structurel reflétant les règles strictes, pas de dépendance Ajv requise au root) passent à 0 divergence.
**Aucun bump de version, aucun release, aucun publish.**

### 2.2 P1 — bloquant adoption

Expand Down
179 changes: 179 additions & 0 deletions tests/negative_vectors_v40_ga.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,179 @@
{
"description": ".klickd v4.0 GA strict NEGATIVE cross-impl test vectors. Each vector MUST FAIL strict v4 GA validation for the documented reason. Strict failures are detected either via JSON-Schema validation (Python: jsonschema; JS: structural rule check) or via structural assertions that match a documented rule. These vectors illustrate the strict-vs-preview boundary and pin down each rule in RFC-001 v1 / RFC-002 v1 / RFC-004 v1 frozen surface.",
"spec_version": "4.0",
"schema": {
"payload": "schemas/klickd-payload-v4.schema.json",
"unified": "schema/klickd-v4.schema.json"
},
"notes": [
"Each vector carries a 'document' that MUST fail the strict v4 GA schema for the documented reason.",
"'failure_reason' is a short tag both implementations check structurally (in addition to schema validation, where available).",
"'against' picks which schema applies: 'payload' (default) or 'unified'."
],
"vectors": [
{
"id": "v4.0-ga-neg-invalid-gate-level",
"description": "verification_gates uses a level outside the v1 frozen 5-enum (silent|warn|confirm|block|require-owner). RFC-002 v1 §6.",
"against": "payload",
"failure_reason": "gate_level_not_in_enum",
"document": {
"payload_schema_version": "4.0",
"verification_gates": {
"version": 1,
"gates": [
{"action_class": "public_post", "level": "loud"}
]
}
}
},
{
"id": "v4.0-ga-neg-missing-payload-schema-version",
"description": "Strict v4 GA payload MUST declare payload_schema_version. Top-level required field missing.",
"against": "payload",
"failure_reason": "missing_required_payload_schema_version",
"document": {
"identity": {"display_name": "No Version"}
}
},
{
"id": "v4.0-ga-neg-unsupported-payload-schema-version",
"description": "payload_schema_version must be one of {'4.0','4.0.0-preview.1'} (strict enum).",
"against": "payload",
"failure_reason": "payload_schema_version_not_in_enum",
"document": {
"payload_schema_version": "9.9"
}
},
{
"id": "v4.0-ga-neg-media-entry-missing-hash",
"description": "media_profile v1 entry missing required 'hash' field (RFC-001 v1 §4 #3 — strict on v1 frozen fields id/modality/hash).",
"against": "payload",
"failure_reason": "media_entry_missing_hash",
"document": {
"payload_schema_version": "4.0",
"media_profile": {
"version": 1,
"entries": [
{"id": "x", "modality": "voice"}
]
}
}
},
{
"id": "v4.0-ga-neg-media-modality-not-in-v1-enum",
"description": "media_profile v1 entry uses an unfrozen modality ('video'). v1 closed enum is {voice,image,document,embedding} — custom modalities live under x_* namespaces (RFC-001 v1 §4 #3).",
"against": "payload",
"failure_reason": "media_modality_not_in_v1_enum",
"document": {
"payload_schema_version": "4.0",
"media_profile": {
"version": 1,
"entries": [
{
"id": "y",
"modality": "video",
"hash": {"algo": "blake3", "value": "b3:placeholder"}
}
]
}
}
},
{
"id": "v4.0-ga-neg-media-hash-algo-not-blake3",
"description": "media_profile v1 hash.algo MUST be 'blake3' (RFC-001 v1 §4 #2 — one hash, one algorithm).",
"against": "payload",
"failure_reason": "media_hash_algo_not_blake3",
"document": {
"payload_schema_version": "4.0",
"media_profile": {
"version": 1,
"entries": [
{
"id": "z",
"modality": "image",
"hash": {"algo": "sha256", "value": "deadbeef"}
}
]
}
}
},
{
"id": "v4.0-ga-neg-human-veto-min-level-invalid",
"description": "human_veto_policy.min_level MUST be a valid gate level. 'panic' is not in the v1 frozen enum.",
"against": "payload",
"failure_reason": "human_veto_min_level_not_in_enum",
"document": {
"payload_schema_version": "4.0",
"human_veto_policy": {
"applies_to": ["public_post"],
"second_party": null,
"min_level": "panic"
}
}
},
{
"id": "v4.0-ga-neg-encrypted-missing-kdf-cipher-ciphertext",
"description": "Unified schema: encrypted=true MUST carry kdf + cipher + ciphertext (envelope-v3 contract preserved in v4 per §33.10 #2).",
"against": "unified",
"failure_reason": "encrypted_missing_envelope_fields",
"document": {
"klickd_version": "4.0",
"created_at": "2026-05-24T00:00:00Z",
"encrypted": true
}
},
{
"id": "v4.0-ga-neg-unified-bad-klickd-version",
"description": "Unified schema: klickd_version must match ^(3|4)\\.\\d+(...)$. '5.0' is not yet a known wire version.",
"against": "unified",
"failure_reason": "klickd_version_unsupported_major",
"document": {
"klickd_version": "5.0",
"created_at": "2026-05-24T00:00:00Z",
"encrypted": false
}
},
{
"id": "v4.0-ga-neg-migration-bad-timestamp",
"description": "migration.migrated_at MUST be RFC 3339 Z-suffix (matches v3 envelope strict contract). 'yesterday' is not a valid timestamp.",
"against": "payload",
"failure_reason": "migration_migrated_at_not_rfc3339_z",
"document": {
"payload_schema_version": "4.0",
"migration": {
"source_version": "3.5.1",
"migrated_at": "yesterday"
}
}
},
{
"id": "v4.0-ga-neg-gate-entry-extra-field",
"description": "gateEntry uses additionalProperties:false. Adding an unknown field ('priority') MUST fail strict validation.",
"against": "payload",
"failure_reason": "gate_entry_additional_property",
"document": {
"payload_schema_version": "4.0",
"verification_gates": {
"version": 1,
"gates": [
{"action_class": "public_post", "level": "block", "priority": "high"}
]
}
}
},
{
"id": "v4.0-ga-neg-human-veto-additional-property",
"description": "human_veto_policy is strict (additionalProperties:false). 'unsafe_qr_target' as a free-form key is rejected.",
"against": "payload",
"failure_reason": "human_veto_additional_property",
"document": {
"payload_schema_version": "4.0",
"human_veto_policy": {
"applies_to": ["public_post"],
"min_level": "require-owner",
"unsafe_qr_target": "https://example.com/x"
}
}
}
]
}
Loading
Loading