diff --git a/CHANGELOG.md b/CHANGELOG.md index 1c2d273..636c644 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,8 @@ here. ### Added +- Name the next Core release `0.3.0`, define conformance maturity independently, and require `v0.3.0-rc.1` before release (#231). + - Policy companion decision (#211): keep RFC 0023 Accepted at draft maturity and Policy 1.0.0 pinned, record its duplicate-identity and no-list-priority rules, and leave disagreement refusal and the second independently authored reader open. - RFC 0022 aligns reusable-subgraph bounds, routed handoff provenance, dialect-owned remapping and cross-installation packaging with the decisions on #129, #219, #232 and #170 (#233). diff --git a/VERSIONING.md b/VERSIONING.md index 14f66ec..3656908 100644 --- a/VERSIONING.md +++ b/VERSIONING.md @@ -5,11 +5,14 @@ Git tags. Specification `0.2.0-draft` is identified by the exact tag `v0.2.0-dra by `v0.1.0-draft`. Only a tag and GitHub release published by the maintainers establish an immutable release; similarly named files on the mutable `main` branch do not. +The next release is `0.3.0`, identified by `v0.3.0`. It is the first Core `specVersion` without the +retired `-draft` suffix; the identifiers and tags of `0.1.0-draft` and `0.2.0-draft` do not change. + ## Specification versions `specVersion` identifies the specification release a Judgment Pack targets. Readers MUST compare -the complete value; they must not infer compatibility from a shared major or minor number during -the `0.x` research period. +the complete value; they must not infer compatibility from a shared major or minor number before +`1.0.0`. A conformance claim is made against one exact `specVersion`. That applies to every class, and it binds hardest on evaluator conformance (Core §3.4): such a claim names the exact version and the @@ -25,8 +28,31 @@ claim. A preserved schema is served at the `$id` it was published under, and whe re-pointed to this project's own domain it differs from the tagged artifact in that one member and nothing else — the release notes of the version that preserved it publish the digest of both forms. -Any `0.x` release may change reader, writer, or semantic behavior incompatibly. Release notes must -identify those effects and include migration guidance when an earlier pack can be transformed. +Before `1.0.0`, a release may change reader, writer, or semantic behavior incompatibly. Its release +notes must record the change and include a `specVersion` migration note, including when no automatic +migration is available. + +## Maturity levels + +The `status` in each Core conformance manifest is a maturity level, not a version. Its closed ladder +is: + +- `draft`: the document is still being exercised and the acceptance evidence is not yet complete; +- `candidate`: independent conforming implementations, production adopters, and sustained outside + contribution are in and recorded for the document; and +- `release`: the evidence has been accepted and recorded for the `1.0.0` standards claim. + +Movement is one step at a time. The recorded evidence above moves a document from `draft` to +`candidate`; the maintainer's recorded `1.0.0` acceptance of that evidence moves it from `candidate` +to `release`. Production use alone does not move a document or establish a standard. + +## Core pre-releases + +A Core release candidate uses the SemVer `-rc.N` form. The `0.3.0` release is preceded by exactly +one candidate, tag `v0.3.0-rc.1`, published as a GitHub pre-release for about one week so adopters' +tooling can read the renamed manifest status. The unsuffixed `v0.3.0` tag is the release. The Policy +companion's `policy-v-draft.N` route remains independent and is not replaced by this +Core convention. The `main` branch describes work in progress. It is not an immutable specification release and must not be used as a reproducibility anchor. @@ -52,7 +78,7 @@ No compatibility dimension is implied merely because two releases share a versio ## Release artifacts -An immutable Core preview release contains, at minimum: +An immutable Core release contains, at minimum: - the tagged Core prose specification; - the tagged JSON Schema with a versioned `$id`, plus any superseded schema still published; @@ -67,7 +93,7 @@ each artifact is permitted to make. ## Deprecation and support -`0.x` draft releases have no compatibility or security service-level guarantee. Maintainers +Pre-`1.0.0` releases have no compatibility or security service-level guarantee. Maintainers may deprecate or remove behavior in a later `0.x` release, but must record the change in release notes. A stable release will require a separate support and deprecation policy before publication. diff --git a/conformance/evaluation/README.md b/conformance/evaluation/README.md index 34f204c..1866376 100644 --- a/conformance/evaluation/README.md +++ b/conformance/evaluation/README.md @@ -25,7 +25,7 @@ including the two invariants JSON Schema cannot express — and stop there. | -------------- | ---------------------------------------------------------------------------------------------- | | `suiteVersion` | The corpus version an evaluator-conformance claim must name (Core §3.4.1). | | `specVersion` | The exact Core version these expectations are computed under. | -| `status` | `research-preview`, matching the maturity of the specification itself. | +| `status` | `draft`, the corpus maturity level; `candidate` and `release` are later ladder steps. | | `label` | `seed` — this corpus is not exhaustive and does not aim to be. | `suiteVersion` equals the `specVersion` the corpus was published for, and the corpus is frozen when diff --git a/conformance/evaluation/manifest.json b/conformance/evaluation/manifest.json index 5754af3..fd34822 100644 --- a/conformance/evaluation/manifest.json +++ b/conformance/evaluation/manifest.json @@ -2,7 +2,7 @@ "$schema": "./manifest.schema.json", "suiteVersion": "0.2.0-draft", "specVersion": "0.2.0-draft", - "status": "research-preview", + "status": "draft", "label": "seed", "cases": [ { diff --git a/conformance/evaluation/manifest.schema.json b/conformance/evaluation/manifest.schema.json index a36e865..b6d9ac0 100644 --- a/conformance/evaluation/manifest.schema.json +++ b/conformance/evaluation/manifest.schema.json @@ -24,7 +24,7 @@ "const": "0.2.0-draft" }, "status": { - "const": "research-preview" + "enum": ["draft", "candidate", "release"] }, "label": { "const": "seed" diff --git a/conformance/manifest.json b/conformance/manifest.json index af43c56..1fc19df 100644 --- a/conformance/manifest.json +++ b/conformance/manifest.json @@ -2,7 +2,7 @@ "$schema": "./manifest.schema.json", "suiteVersion": "0.2.0-draft", "specVersion": "0.2.0-draft", - "status": "research-preview", + "status": "draft", "validationProfile": { "jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema", "formatAssertionRequired": true, diff --git a/conformance/manifest.schema.json b/conformance/manifest.schema.json index ae6f31d..75c3cf9 100644 --- a/conformance/manifest.schema.json +++ b/conformance/manifest.schema.json @@ -24,7 +24,7 @@ "const": "0.2.0-draft" }, "status": { - "const": "research-preview" + "enum": ["draft", "candidate", "release"] }, "validationProfile": { "type": "object", diff --git a/docs/releases/v0.3.0.md b/docs/releases/v0.3.0.md new file mode 100644 index 0000000..501a191 --- /dev/null +++ b/docs/releases/v0.3.0.md @@ -0,0 +1,43 @@ +# v0.3.0 + +`0.3.0`, tagged `v0.3.0`, is the first Core release whose `specVersion` has no `-draft` suffix. +The released `0.2.0-draft` identifier and its tag keep their names. Before the release, one +`v0.3.0-rc.1` GitHub pre-release gives adopters about a week to verify that their tooling reads the +manifest maturity `status` as `draft` rather than the retired `research-preview` value. + +## Included work + +- Core escalation routes from RFC 0025: an exclusive single target or bounded conditional routes, + with routed handoff provenance and complete retained reasons. +- The `base` and `extended` document ceiling profiles from RFC 0026, including reader negotiation + and explicit refusal of an unsupported profile. +- The non-normative rule-overlap check and its closed `JPS-RULES-OVERLAP` diagnostic. +- RFC 0020's bulk amendment: contributing-rule output records, disposition summaries, reproducible + seeded samples, drift comparison, and the closed workflow schema and hand-written fixture that + verify those records. +- RFC 0020's later-sibling rule, limited to instances admitted for acquisition so an admission + failure retains its own finding. +- Dialect data-quality conversions, reconciliation, typed research-result states, lineage, + versioned source profiles, resolved settings, and byte-retaining replay contracts. +- RFC 0022's alignment with the landed composition decisions for subgraph bounds, routed handoff + provenance, dialect-owned remapping, and cross-installation packaging. + +## Versioning and maturity + +Pre-`1.0.0` Core releases may be incompatible when their notes record the change and give a +`specVersion` migration note. Conformance manifest `status` is now the independent maturity ladder +`draft`, `candidate`, and `release`; `0.3.0` remains `draft`. The `release` maturity is reserved for +the `1.0.0` standards claim after the governance acceptance evidence is met and recorded. Production +use does not by itself establish that claim. + +The Policy companion remains on its independent `policy-v1.0.0-draft.N` route until that format's +first release and is not renamed by the Core scheme change. + +RFC 0024 and the Policy companion's record land in the release if merged before the tag. + +## Migration note + +The exact Core identifier accepted by packs and readers is not changed by this preparation. Before +the tag, the maintainer must decide which documents, schemas, vectors, and readers gain `0.3.0` +beside `0.2.0-draft`; the release cannot be cut until those exact-version surfaces and the Runtime's +vendored copy are updated together. diff --git a/rfcs/0000-rfc-process.md b/rfcs/0000-rfc-process.md index 25c5a94..919671c 100644 --- a/rfcs/0000-rfc-process.md +++ b/rfcs/0000-rfc-process.md @@ -63,7 +63,7 @@ the question was raised by that record and ruled on at its acceptance. ## Review -Research-preview RFCs remain open for public comment for a reasonable period based on complexity. +Draft RFCs remain open for public comment for a reasonable period based on complexity. Maintainers may request prototypes or conformance cases before disposition. A stable normative feature should not be accepted without evidence from two independent implementations. diff --git a/schema/conformance-manifest-0.1.0-draft.schema.json b/schema/conformance-manifest-0.1.0-draft.schema.json index 7efedb5..241622b 100644 --- a/schema/conformance-manifest-0.1.0-draft.schema.json +++ b/schema/conformance-manifest-0.1.0-draft.schema.json @@ -23,7 +23,7 @@ "const": "0.1.0-draft" }, "status": { - "const": "research-preview" + "enum": ["draft", "candidate", "release"] }, "validationProfile": { "type": "object", diff --git a/schema/judgment-pack-core-0.1.0-draft.schema.json b/schema/judgment-pack-core-0.1.0-draft.schema.json index 29bf458..fae5aba 100644 --- a/schema/judgment-pack-core-0.1.0-draft.schema.json +++ b/schema/judgment-pack-core-0.1.0-draft.schema.json @@ -2,7 +2,7 @@ "$schema": "https://json-schema.org/draft/2020-12/schema", "$id": "https://judgmentpack.org/schema/0.1.0-draft/judgment-pack-core.schema.json", "title": "Judgment Pack Core", - "description": "Research-preview structural schema. Conformance does not establish truth, authority, safety, or operational fitness.", + "description": "Structural schema for 0.1.0-draft. Conformance does not establish truth, authority, safety, or operational fitness.", "$comment": "JPS structural conformance requires uri, date, and date-time format assertions even when a general-purpose validator treats format as annotation-only.", "type": "object", "additionalProperties": false, diff --git a/schema/judgment-pack-core.schema.json b/schema/judgment-pack-core.schema.json index 14209e2..a2c8533 100644 --- a/schema/judgment-pack-core.schema.json +++ b/schema/judgment-pack-core.schema.json @@ -2,7 +2,7 @@ "$schema": "https://json-schema.org/draft/2020-12/schema", "$id": "https://judgmentpack.org/schema/0.2.0-draft/judgment-pack-core.schema.json", "title": "Judgment Pack Core", - "description": "Research-preview structural schema. Conformance does not establish truth, authority, safety, or operational fitness.", + "description": "Structural schema for 0.2.0-draft. Conformance does not establish truth, authority, safety, or operational fitness.", "$comment": "JPS structural conformance requires uri, date, and date-time format assertions even when a general-purpose validator treats format as annotation-only.", "type": "object", "additionalProperties": false, diff --git a/tests/conformance-status-vectors.json b/tests/conformance-status-vectors.json new file mode 100644 index 0000000..5c76f30 --- /dev/null +++ b/tests/conformance-status-vectors.json @@ -0,0 +1,22 @@ +[ + { + "status": "draft", + "valid": true + }, + { + "status": "candidate", + "valid": true + }, + { + "status": "release", + "valid": true + }, + { + "status": "research-preview", + "valid": false + }, + { + "status": "", + "valid": false + } +] diff --git a/tests/test_release.py b/tests/test_release.py index 006f845..7d033e8 100644 --- a/tests/test_release.py +++ b/tests/test_release.py @@ -21,8 +21,17 @@ class ReleaseBuilderTests(unittest.TestCase): + def test_0_3_0_release_naming_is_recorded(self) -> None: + versioning = (ROOT / "VERSIONING.md").read_text(encoding="utf-8") + notes = ROOT / "docs" / "releases" / "v0.3.0.md" + self.assertIn("`v0.3.0-rc.1`", versioning) + self.assertIn("`v0.3.0`", versioning) + self.assertTrue(notes.is_file()) + self.assertEqual("# v0.3.0", notes.read_text(encoding="utf-8").splitlines()[0]) + def test_release_version_accepts_exact_semver_tags(self) -> None: self.assertEqual(BUILD_RELEASE.release_version("v0.1.0-draft"), "0.1.0-draft") + self.assertEqual(BUILD_RELEASE.release_version("v0.3.0-rc.1"), "0.3.0-rc.1") self.assertEqual(BUILD_RELEASE.release_version("v1.2.3-rc.1"), "1.2.3-rc.1") self.assertEqual(BUILD_RELEASE.release_version("v1.2.3-0"), "1.2.3-0") self.assertEqual(BUILD_RELEASE.release_version("v1.2.3-alpha.1"), "1.2.3-alpha.1") @@ -55,20 +64,20 @@ def test_artifact_validation_pins_the_evaluation_corpus(self) -> None: # must not build even when every document artifact is correctly re-pinned. for stale in ("specVersion", "suiteVersion"): with self.subTest(member=stale), tempfile.TemporaryDirectory() as directory: - root = self._release_tree(Path(directory), "0.3.0-draft") + root = self._release_tree(Path(directory), "0.3.0") evaluation_path = root / "conformance" / "evaluation" / "manifest.json" evaluation = json.loads(evaluation_path.read_text(encoding="utf-8")) evaluation[stale] = "0.2.0-draft" evaluation_path.write_text(json.dumps(evaluation), encoding="utf-8") with self.assertRaises(ValueError) as raised: BUILD_RELEASE.validate_artifacts( - "0.3.0-draft", "v0.3.0-draft", root=root + "0.3.0", "v0.3.0", root=root ) self.assertIn("evaluation corpus", str(raised.exception)) # The same tree validates once the corpus is re-pinned. - evaluation[stale] = "0.3.0-draft" + evaluation[stale] = "0.3.0" evaluation_path.write_text(json.dumps(evaluation), encoding="utf-8") - BUILD_RELEASE.validate_artifacts("0.3.0-draft", "v0.3.0-draft", root=root) + BUILD_RELEASE.validate_artifacts("0.3.0", "v0.3.0", root=root) def test_validate_requires_head_to_be_the_release_commit(self) -> None: # Artifacts are validated from the worktree and archived from the commit, so a release built diff --git a/tests/test_repository.py b/tests/test_repository.py index 38752e5..b5d6882 100644 --- a/tests/test_repository.py +++ b/tests/test_repository.py @@ -47,6 +47,7 @@ f"https://judgmentpack.org/schema/{PREVIOUS_SPEC_VERSION}" "/conformance/manifest.schema.json" ) +STATUS_VECTORS_PATH = ROOT / "tests" / "conformance-status-vectors.json" # §8.3: the reason vocabulary a disposition may carry. REASONS = { "not-applicable", @@ -919,6 +920,7 @@ def test_manifest_matches_its_schema(self) -> None: self.assertEqual([], [error.message for error in errors]) self.assertEqual(self.manifest["suiteVersion"], SPEC_VERSION) self.assertEqual(self.manifest["specVersion"], SPEC_VERSION) + self.assertEqual("draft", self.manifest["status"]) self.assertTrue(self.manifest["validationProfile"]["formatAssertionRequired"]) self.assertTrue(self.manifest["validationProfile"]["rejectDuplicateObjectMembers"]) diagnostic_validator = validator.evolve( @@ -930,6 +932,33 @@ def test_manifest_matches_its_schema(self) -> None: set(), rule_overlap_diagnostic_keywords(diagnostic_validator, diagnostic) ) + def test_conformance_manifest_status_ladder_vectors(self) -> None: + vectors = strict_json_loads(STATUS_VECTORS_PATH.read_text(encoding="utf-8")) + schemas = ( + self.manifest_schema, + strict_json_loads( + EVALUATION_MANIFEST_SCHEMA_PATH.read_text(encoding="utf-8") + ), + strict_json_loads(PREVIOUS_MANIFEST_SCHEMA_PATH.read_text(encoding="utf-8")), + ) + for schema in schemas: + status_validator = Draft202012Validator(schema["properties"]["status"]) + for vector in vectors: + with self.subTest(schema=schema["$id"], status=vector["status"]): + self.assertEqual( + vector["valid"], status_validator.is_valid(vector["status"]) + ) + + def test_core_schema_descriptions_name_their_spec_versions(self) -> None: + for path in (SCHEMA_PATH, PREVIOUS_SCHEMA_PATH): + schema = strict_json_loads(path.read_text(encoding="utf-8")) + version = schema["properties"]["specVersion"]["const"] + self.assertEqual( + f"Structural schema for {version}. Conformance does not establish truth, " + "authority, safety, or operational fitness.", + schema["description"], + ) + def test_rule_overlap_advisory_vectors_cover_the_recommendation(self) -> None: self.assertEqual(["JPS-RULES-OVERLAP"], self.manifest["diagnosticVocabulary"]["advisory"]) @@ -1346,6 +1375,7 @@ def test_evaluation_manifest_matches_its_schema(self) -> None: self.assertEqual([], [error.message for error in errors]) self.assertEqual(manifest["suiteVersion"], SPEC_VERSION) self.assertEqual(manifest["specVersion"], SPEC_VERSION) + self.assertEqual("draft", manifest["status"]) self.assertEqual(manifest["label"], "seed") ids = collections.Counter(case["id"] for case in manifest["cases"]) for case_id, count in sorted(ids.items()):