One discipline · three doors contextkeeping.com machinereadyknowledge.com answerecon.com

MRK-2.0: Machine-Ready Knowledge, maturity layer

Status Released 2026-10-19

Standard versionMRK-2.0
Article format versions1.0 (or mrk_version absent) and 1.1. MRK-2.0 adds no format version (§13).
Date2026-10-19
EditorJason O'Donnell (Contextkeeping)
Canonical URLhttps://machinereadyknowledge.com/mrk-2.0
Authoritydocs/mrk-evolution/mrk-2.0/SPEC.md (this file)
Supersedes the normative parts ofgraduation-ladder.md, retrieval-conformance.md, reuse-transclusion.md (these files remain as rationale)

Abstract

MRK-1.0 defines a machine-ready article: YAML front matter that validates against a JSON Schema, plus four editorial clauses. MRK-1.1 adds optional fields to that format. MRK-2.0 is a maturity layer over the 1.1 article format. It does not change the article format. It defines:

Every requirement has a stable identifier and is mapped to the check that tests it in Appendix C. Requirements that can only be attested say so and name the evidence.

Contents

  1. Conventions and terminology
  2. Scope and relationship to MRK-1.0 and MRK-1.1
  3. Article format
  4. Producer conformance classes (L0 to L4)
  5. Namespace and identifiers
  6. Graph projection
  7. Integrity gate
  8. Reuse: declared transclusion and drift
  9. Clause R: retrieval conformance
  10. Consumer conformance classes and the citation object
  11. Conformance evidence
  12. Corpus reporting and conformance claims
  13. Versioning and compatibility
  14. Security and privacy considerations
  15. Changelog from MRK-1.1

1. Conventions and terminology

1.1 Requirement language

The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", "SHOULD", "SHOULD NOT", "RECOMMENDED", "NOT RECOMMENDED", "MAY", and "OPTIONAL" in this document are to be interpreted as described in BCP 14 [RFC 2119] [RFC 8174] when, and only when, they appear in all capitals, as shown here.

1.2 Requirement identifiers

Each requirement is a list item that begins with its identifier in bold, for example C-R5-1. Identifiers are stable. A later edition never reuses or renumbers an identifier; a withdrawn requirement keeps its identifier with the note "withdrawn". The prefixes are:

PrefixSectionPrefixSection
A-3 Article formatC-R1- … C-R7-9 Clause R
P-G-, P-L0- … P-L4-4 Producer classesC-L-10 Consumer classes
N-5 NamespaceE-11 Evidence
G-6 ProjectionK-12 Reporting and claims
I-7 Integrity gateV-13 Versioning
D-8 Reuse and driftS-14 Security
T-Appendix B Reference tools

Text outside requirement items is informative unless it defines a term.

1.3 Terminology

2. Scope and relationship to MRK-1.0 and MRK-1.1

MRK-2.0 is a version of the standard, not of the article format. The article format of MRK-2.0 is MRK-1.1, which is a strict superset of MRK-1.0 for every instance that does not reuse a 1.1 field name with a non-conforming value (§13, Appendix D item 23).

MRK-2.0 has three conformance targets:

  1. Articles and corpora (producer classes, §4), scored by a producer tool.
  2. Consumers (consumer classes, §10), tested by the conformance suite.
  3. Producer tools and validators, which must follow the processing rules in §3, §6 to §8, §11 and §13.

MRK-2.0 does not judge prose quality, coverage, or whether an answer is correct. Those remain human review.

3. Article format

The article format is defined by mrk-1.1.schema.json and, for instances that declare or default to format 1.0, mrk-1.0.schema.json (Appendix A). The four MRK-1.0 editorial clauses continue to hold.

4. Producer conformance classes (L0 to L4)

Each article sits on exactly one rung. Rungs measure machine-readiness depth. They are reached by adding front matter and running the gates, never by rewriting prose.

RungNameSummary
L0ProseNot contracted.
L1ContractedSchema-valid; editorial flags hold.
L2TypedL1 plus section roles, type shape, visibility.
L3LinkedL2 plus at least one edge, concepts, clean projection.
L4ReasonedL3 plus SHACL clean, drift clean, and graph-first retrieval measured or attested.

4.1 General rules

4.2 L0 Prose

4.3 L1 Contracted

L1 is checked against the 1.1 schema, which does not require plan and region. L1 is therefore not the same as MRK-1.0 conformance (see K-7).

4.4 L2 Typed

4.5 L3 Linked

4.6 L4 Reasoned

L4 has a mechanical half (P-L4-1, P-L4-2) and an operational half (P-L4-3), which is either measured by evidence or attested by the producer.

The rule, as a producer tool applies it:

for each article a with rung(a) == 3:
  if a.id in shacl_failures:                 L3, "L4: SHACL - <message>"
  elif a is .md and drift(a) != []:          L3, "L4: drift - <finding>"
  elif some evidence e measures a (P-L4-3):  L4, basis = measured
  elif --graph-first:                        L4, basis = attested
  else:                                      L3, most specific reason (P-L4-6)

5. Namespace and identifiers

6. Graph projection

The projection turns a corpus into one RDF graph. The context cannot express two transforms, so they are applied to each article's front matter first.

7. Integrity gate

shapes.ttl (Appendix A) is normative. JSON Schema checks one article; SHACL checks the corpus graph, where cross-article references can be resolved.

8. Reuse: declared transclusion and drift

MRK-1.0 §1 requires each section to restate its prerequisites. That creates copies. MRK-2.0 keeps the copy inline, declares where it came from, and gates drift between copy and source.

8.1 Grammar

8.2 Rules

Stop list (normative for D-6): a an the of to in on and or is are was were be been for with that this it its as at by from into if then so no not up out you your they them their.

9. Clause R: retrieval conformance

MRK-1.0 specifies the article. Clause R specifies the consumer that turns articles into an answer. R1 to R7 are the rule groups; the numbered items are the requirements.

R1 Applicability

R2 Freshness and lifecycle

R3 Safety

R4 Sections, not pages

R5 Graph before similarity

R6 Citation

R7 Role-aware assembly

10. Consumer conformance classes and the citation object

ClassNameRequirements
C1FilteredR1, R2, R3, R4, R6 (the mandatory items). Works over an L1 corpus with metadata filtering.
C2Graph-awareC1 plus R5 and R7, which become mandatory. Needs an L3 or higher corpus and the projection.

The citation object is defined by answer-object.schema.json (Appendix A): required section, article, verified; optional version, stale, superseded; further properties allowed.

11. Conformance evidence

Evidence binds a consumer's conformance run to a specific suite and a specific corpus, so that L4's operational half and C1/C2 claims can be measured rather than only attested.

11.1 The evidence document

Example (values shortened):

{
  "mrk_evidence": "1",
  "kind": "retrieval-conformance",
  "spec": "MRK-2.0",
  "tool": { "name": "mrk-tools", "version": "2.0.0" },
  "suite_sha256": "4f1c…",
  "consumer": { "entry": "acme_rag.mrk:Consumer", "name": "acme-rag", "version": "4.2.1" },
  "run_at": "2026-10-19T14:03:00Z",
  "today": "2026-10-19",
  "levels": { "C1": "pass", "C2": "pass" },
  "suite_results": [ { "name": "public-starter", "mode": "parity", "rules": "R1 R2 R3 R4 R6 R7", "ok": true } ],
  "self_test": "rejected-broken-consumer",
  "corpus": {
    "articles": { "ck-ops-1": "sha256:9a0e…", "ck-ops-2": "sha256:77b2…" },
    "derived_cases": [
      { "article": "ck-ops-2", "edge": "depends_on", "target": "ck-ops-1",
        "section": "ck-ops-2#steps", "ctx": { "reader_scope": "public", "locale": "en", "intent_role": "procedure" },
        "expect": ["ck-ops-2#steps", "ck-ops-1#resolution"], "ok": true }
    ]
  },
  "sha256": "c03d…"
}

11.2 Digests

11.3 Derived cases

11.4 Verification

Evidence proves integrity and freshness of a run, not who ran it (§14).

12. Corpus reporting and conformance claims

13. Versioning and compatibility

MRK has two version axes. The standard version (MRK-1.0, MRK-2.0) names this document and its conformance classes. The article-format version is the mrk_version value in front matter.

14. Security and privacy considerations

15. Changelog from MRK-1.1

Appendix A: Normative artifacts

SHA-256 values are of the files as released.

ArtifactServed URLAuthority in repoSHA-256 (file)
MRK-1.0 schema/mrk-1.0.schema.jsonsites/machinereadyknowledge/mrk-1.0.schema.json9eb5fc3489a7ebf979b45be20f4a731ac7a1eac9331ba92738420af05adcbbb9
MRK-1.1 schema/mrk-1.1.schema.jsondocs/mrk-evolution/mrk-1.1/mrk-1.1.schema.json35390dd2c2b33ccb77e816e09fa71c62d8ebf28ccd0df87fa407958f2b9c2270
JSON-LD context/mrk-1.1.context.jsonlddocs/mrk-evolution/projection/mrk-1.1.context.jsonld94d1cde84293a5da9edd70d25345d8f238e11fd718b6f1799c60ae653db7421c
SHACL shapes/shapes.ttldocs/mrk-evolution/mrk-2.0/shapes.ttl2ac155c5b4f1ac63829fbb634dadf693ba33314c11cc7acb5eff3c5a92cc5732
Citation object/answer-object.schema.jsondocs/mrk-evolution/mrk-2.0/conformance/answer-object.schema.jsona5320e35b726941172676cadb8139eecba1dccbdd620287d1d6e095e2b95bcc5
Evidence schema/mrk-evidence.schema.jsondocs/mrk-evolution/mrk-2.0/evidence.schema.json7388af111073214a9dc7583f43271e1d0dbc7335608d431566d36af98471c606
Vocabulary (Turtle)/ns (Accept: text/turtle) → /ns.ttldocs/mrk-evolution/ns/ns.ttl14fe57b62ebf626195f41b6902aa3aa4abe688f3c5be8565f79fab484b785d6a
Vocabulary (JSON-LD)/ns (Accept: application/ld+json) → /ns.jsonlddocs/mrk-evolution/ns/ns.jsonld8580acf5008414deb6fc4a9ec98aac24a555bdd431d375e73413d2add0027212

Validation fingerprint. The SHA-256 of the canonical JSON (E-2) of a schema after removing every title, description and $comment member that is in schema position. Members of properties, $defs, patternProperties and dependentSchemas are names, not keywords, and are kept (so the 1.0 and 1.1 property named title stays in the fingerprint).

SchemaValidation fingerprint
mrk-1.0.schema.jsonf7902cddbb48013409db44cf8fc970c3cfe0c33893d6b42cf8170b5dbda27f31
mrk-1.1.schema.json01a0e6322e50a23130b9477fb7a8178d6c684636a65853752cbd816e76e8b117
answer-object.schema.json78ddde558bfb7990f4e177d4d861d2c8ae98a76e692de4ada34bb8290267b02f

Appendix B: Reference tools

The reference tools ship as one kit, mrk-tools.zip, served at /downloads/mrk-tools.zip. The old URL /downloads/mrk-ladder-scorer.zip redirects to it. Commands, run from the unzipped directory:

python -m mrk ladder   [--graph-first] [--evidence FILE ...] [--spec DIR] [--json] PATHS...
python -m mrk conform  [--consumer MODULE:CLASS] [--corpus PATHS...] [--self-test] [--evidence-out FILE]
python -m mrk drift    PATHS.md...
python -m mrk project  build|demo PATHS...
python -m mrk verify-evidence FILE [--corpus PATHS...]
python ladder.py ...   # same as: python -m mrk ladder ...

The kit is a reference implementation. Conformance is defined by this document, not by the kit.

Appendix C: Conformance-test mapping

One row per requirement. Check names the test: a schema keyword (schema:), a SHACL shape (shacl:), a conformance-suite case (suite:), a drift-lint rule (drift:), a scorer rule (ladder:), an evidence-verifier step (verify:), a pre-upload gate check (gate:), or a repository test written for the requirement (test:). Where is the current implementation and the tests that exercise it; a test is named file::function, and the tests written for this appendix are in tests/test_appendix_c.py, named after the requirement they check (test_A_6_...). Status: exists (the check is in code and tests this requirement), partial (in code but incomplete, as noted and explained in Appendix E), missing (to be built), attested (cannot be machine-checked; the evidence is named in the requirement).

Current files: scorer = tools/mrk-tools/mrk/ladder.py; drift = tools/mrk-tools/mrk/drift.py; harness = tools/mrk-tools/mrk/conform.py; consumer = tools/mrk-tools/mrk/consumer.py; gate = tools/validate-machine-blocks.py. Suite case ids map to the current harness case names: public-starter (Starter / public / en), enterprise, internal, locale-de, r5-depends (graph completes the answer), self-test.

IDCheckWhereStatus
A-1schema: whole mrk-1.1.schema.json; ladder:schemascorer rung()exists
A-2ladder:load (fence and mapping)core.parse_yaml(), core.load_front_matter(); test_hardening.py::test_front_matter_must_be_a_mapping, test_hardening.py::test_markdown_with_unterminated_fenceexists
A-3schema: section.self_contained (flag value only)schema; scorer clauses_hold()attested
A-4schema: applies_to required, minProperties: 1schemaexists
A-5schema: id and section id pattern (format only)schemaattested
A-6ladder:section-prefixscorer clauses_hold(); test_appendix_c.py::test_A_6_section_id_prefixexists
A-7schema: required owner/verified/lifecycle; verified format: dateschema; scorer make_validator() (a FormatChecker, so format is asserted), rung(); test_appendix_c.py::test_A_7_verified_must_be_a_full_dateexists
A-8ladder:date-normalise, ladder:format-assertcore.load_front_matter() (_iso_dates()), scorer make_validator(); test_appendix_c.py::test_A_8_unquoted_yaml_date_is_normalised_before_validation, test_A_8_a_yaml_timestamp_is_normalised_then_fails_the_date_formatexists
A-9ladder:safe-load; drift:safe-loadscorer, drift (yaml.safe_load)exists
P-G-1ladder:rungscorer rung()exists
P-G-2ladder:rung (first failing check stops)scorer rung()exists
P-G-3ladder:next-rung messagescorer rung() return valueexists
P-G-4ladder:rung reads verified/lifecycle for presence onlyscorer clauses_hold()exists
P-L0-1ladder:schema, ladder:clausesscorerexists
P-L1-1ladder:schemascorer rung()exists
P-L1-2ladder:clauses (self_contained)scorer clauses_hold()exists
P-L1-3ladder:clauses (applies_to); schema minProperties: 1scorer, schemaexists
P-L1-4ladder:clauses (owner/verified/lifecycle)scorer clauses_hold()exists
P-L1-5ladder:section-prefixscorer clauses_hold(); test_appendix_c.py::test_P_L1_5_section_id_prefix_is_an_l1_criterionexists
P-L2-1ladder:typed (role)scorer typed()exists
P-L2-2ladder:typed (TYPE_SHAPES); schema allOf (under 1.1)scorer typed()exists
P-L2-3ladder:typed (visibility)scorer typed()exists
P-L3-1ladder:linked (edge)scorer linked()exists
P-L3-2ladder:linked (concepts)scorer linked()exists
P-L3-3ladder:projectcore.build_graph_tolerant(); scorer score() reports the article at L2 with the reason; test_appendix_c.py::test_P_L3_3_an_unprojectable_article_is_attributed_not_an_abort (forced: no schema-valid article is known that fails to project)exists
P-L4-1ladder:shaclscorer shacl_failures()exists
P-L4-2ladder:driftscorer drift_failures(); test_review_m2.py::test_an_empty_or_all_stopword_transclusion_block_is_a_drift_findingexists
P-L4-3ladder:evidence (measured); ladder:attest (--graph-first)scorer score(), evidence.judge(); test_evidence.py::test_measured_l4_from_evidence, test_evidence.py::test_graph_first_alone_is_still_attested_l4exists
P-L4-4ladder:basisscorer score() sets l4_basis; test_evidence.py::test_evidence_plus_graph_first_mixes_basesexists
P-L4-5ladder:shacl (hard dependency on pyshacl)scorer imports pyshacl unconditionallyexists
P-L4-6ladder:reason-orderscorer score(), evidence.judge() and evidence.STAGES; test_appendix_c.py::test_P_L4_6_the_earlier_reason_wins_when_two_apply (every adjacent pair), test_P_L4_6_a_failed_case_is_the_last_evidence_reason, test_P_L4_6_shacl_before_drift_before_evidence_before_neitherexists
N-1test: context @vocab/mrk, shapes.ttl mrk prefix and ns.ttl/ns.jsonld are one IRItest_appendix_c.py::test_N_1_one_namespace_in_the_context_the_shapes_and_the_vocabularyexists
N-2test: served ns.ttl/ns.jsonld/ns.html and the /ns negotiation rulessites/machinereadyknowledge/.htaccess; test_appendix_c.py::test_N_2_ns_is_served_and_negotiated (files byte-match docs/; the .htaccess rules are evaluated for each Accept header; the live route cannot be tested offline, see Appendix E 8)partial: live route untestable offline
N-3test: no network modules in the kit; every command runs with sockets blockedtest_offline.py::test_no_network_modules_are_imported_by_the_kit, test_offline.py::test_each_command_works_offlineexists
G-1gate:3b (context byte-match); T-1 bundle checkgate §3bexists
G-2ladder:project golden graph (projection/graph.ttl isomorphism)core.normalize(); test_projection.py::test_build_is_isomorphic_to_committed_graph_ttl, test_projection.py::test_build_matches_goldenexists
G-3ladder:project golden graphcore.normalize(); test_projection.py::test_build_is_isomorphic_to_committed_graph_ttlexists
G-4ladder:project (single @graph)core.build_graph() (the scorer uses core.build_graph_tolerant())exists
G-5shacl: mrk:ArticleShape mrk:verified sh:datatype xsd:dateshapes.ttlexists
G-6ladder:project golden graph (rdflib isomorphism)test_projection.py::test_build_is_isomorphic_to_committed_graph_ttl (rdflib isomorphism)exists
I-1ladder:shacl (whole-corpus validation)scorer shacl_failures()exists
I-2shacl: mrk:ArticleShapeshapes.ttlexists
I-3shacl: mrk:SectionShapeshapes.ttlexists
I-4shacl: mrk:RequiresIntegrityShapeshapes.ttlexists
I-5shacl: mrk:DependsOnIntegrityShapeshapes.ttlexists
I-6shacl: mrk:SupersedesIntegrityShapeshapes.ttlexists
I-7shacl: none, by designshapes.ttl has no shape for these five edges; test_appendix_c.py::test_I_7_dangling_optional_edges_are_not_a_shacl_violation pins that (SHOULD only, see Appendix E 9)partial: SHOULD, unchecked by design
I-8ladder:shacl attributionscorer shacl_failures()exists
D-1drift:grammar (TRANSCLUDE_RE)drift analyze() (TRANSCLUDE_RE, OPEN_RE, MARKER_RE); test_appendix_c.py::test_D_1_nested_markers_are_reported, test_D_1_a_marker_that_never_closes_is_reported, test_D_1_well_formed_blocks_with_free_whitespace_are_cleanexists
D-2drift:anchors (HEADING_RE)drift parse()exists
D-3drift:danglingdrift analyze(); test_drift_lint.py::test_dangling_target_failsexists
D-4drift:undeclareddrift analyze(); test_drift_lint.py::test_target_missing_from_requires_failsexists
D-5drift:self-targetdrift analyze(); test_appendix_c.py::test_D_5_a_block_may_not_target_its_own_sectionexists
D-6drift:grounding (THRESHOLD = 0.5, words(), STOP)driftexists
D-7drift:exit (non-zero, names section/target)drift run() (exit 1; each finding names the section and the target); test_appendix_c.py::test_D_7_findings_name_the_section_and_the_target_and_exit_non_zeroexists
C-R1-1suite:public-starter, suite:enterpriseharnessexists
C-R1-2test: unanswered constrained dimensionconsumer applies(); test_conformance.py::test_r1_unanswered_constrained_dimension_is_no_matchexists
C-R1-3test: unevaluated dimensionconsumer applies(); test_appendix_c.py::test_C_R1_3_a_constrained_dimension_the_reader_does_not_answer_does_not_match, test_C_R1_3_all_matches_every_reader; test_review_m3.py::test_C_R1_3_version_is_compared_as_dotted_numeric_segments, test_C_R1_3_a_value_that_cannot_be_compared_fails_closed, test_C_R1_3_dimensions_are_compared_per_key_like_the_named_dimensionsexists
C-R2-1test: retired and expiredconsumer fresh(); test_conformance.py::test_r2_retired_article_never_served, test_appendix_c.py::test_C_R2_1_retired_and_expired_are_never_servedexists
C-R2-2test: stale flagconsumer fresh(); test_evidence.py::test_today_drives_the_freshness_flagexists
C-R2-3test: superseded flagconsumer fresh(); test_appendix_c.py::test_C_R2_3_superseded_is_served_downweighted_and_flagged_not_droppedexists
C-R2-4test: today passed in ctxconsumer fresh() reads ctx['today']; test_evidence.py::test_today_drives_the_freshness_flag, test_review_m2.py::test_verify_evidence_honours_today_in_the_rerunexists
C-R3-1suite:internal; suite:self-testharnessexists
C-R3-2test: entitlementconsumer visible(); test_appendix_c.py::test_C_R3_2_entitlement_is_required_to_surface_an_articleexists
C-R3-3suite:locale-de; test: regional fallback and accept_localesconsumer visible(); test_appendix_c.py::test_C_R3_3_regional_reader_falls_back_to_the_base_language_article, test_C_R3_3_a_different_language_needs_the_reader_to_accept_itexists
C-R3-4test: default localeconsumer DEFAULT_LOCALE, visible(); test_appendix_c.py::test_C_R3_4_the_default_locale_is_declared_and_appliedexists
C-R4-1suite: every case (R4 # check)harness conform.check()exists
C-R4-2test: section not self-containedconsumer answer(), answer_graph(); test_appendix_c.py::test_C_R4_2_only_self_contained_sections_are_quotedexists
C-R5-1suite:r5-dependsharnessexists
C-R5-2suite: parity cases (graph set == flat set)harness conform.check()exists
C-R5-3suite:r5-dependsharnessexists
C-R5-4suite:r5-depends; derived requires casesconsumer answer_graph() follows the requires closure; test_review_m2.py::test_conform_exits_1_when_a_derived_case_fails, test_evidence.py::test_evidence_validates_against_the_served_schemaexists
C-R5-5test: dependency that fails a gateconsumer answer_graph() (emit() applies servable()); test_appendix_c.py::test_C_R5_5_a_dependency_that_fails_a_gate_is_not_added, test_C_R5_5_a_required_section_in_a_gated_article_is_not_addedexists
C-R6-1suite: every case (answer-object.schema.json); schema required, pattern, formatharness _bad_citations()exists
C-R6-2harness: citation coherenceharness conform._incoherent(); test_appendix_c.py::test_C_R6_2_a_citation_belongs_to_the_article_that_owns_its_section, test_C_R6_2_the_harness_rejects_a_citation_whose_article_does_not_own_the_section, test_C_R6_2_the_harness_rejects_a_verified_date_that_is_not_the_articlesexists
C-R6-3harness: citation versionconsumer _cite() (the article digest); harness conform._incoherent(); test_appendix_c.py::test_C_R6_3_a_citation_version_is_the_article_digest_never_mrk_version, test_C_R6_3_the_harness_rejects_any_other_versionexists
C-R6-4test: graph citation flagsconsumer answer_graph() (_cite(..., fresh())); test_appendix_c.py::test_C_R6_4_graph_citations_carry_stale_and_supersededexists
C-R7-1suite: cases with intent_roleharnessexists
C-L-1suite: C1 verdict graded on answer() onlyharness conform.check() grades C1 on answer(); test_evidence.py::test_c1_only_consumer_reports_c2_not_claimed, test_evidence.py::test_answer_graph_raising_not_implemented_means_not_claimedexists
C-L-2suite: parity + r5_contract + derived casesharness conform.derive_cases(), conform.run_conform(); test_evidence.py::test_derived_case_ids_come_from_the_corpus_not_a_template, test_review_m2.py::test_conform_exits_1_when_a_derived_case_failsexists
C-L-3suite: --consumer MODULE:CLASS loaderharness conform.load_consumer(), conform.make() (--consumer); test_evidence.py::test_bad_consumer_entryexists
C-L-4suite:self-test; verify:self-testharness --self-testexists
E-1verify:schema (mrk-evidence.schema.json)evidence.problems(); test_review_m2.py::test_evidence_schema_requires_run_at_and_corpus_and_both_copies_are_identical, test_evidence.py::test_evidence_validates_against_the_served_schemaexists
E-2verify:canonical-jsoncore.canonical_json(); test_appendix_c.py::test_E_2_canonical_json_matches_an_independent_implementation (a hand-written encoder), test_E_2_and_E_3_article_digest_is_reproduced_from_the_written_algorithm, test_E_2_and_E_5_the_self_hash_is_reproduced_from_the_written_algorithmexists
E-3verify:article-digestcore.article_digest(); test_evidence.py::test_editing_an_article_invalidates_its_evidence, test_evidence.py::test_body_edit_does_not_invalidate_digest, test_appendix_c.py::test_E_2_and_E_3_article_digest_is_reproduced_from_the_written_algorithmexists
E-4verify:suite-digestmrk.suite_sha256(); test_review_m2.py::test_suite_digest_is_the_one_spec_e4_defines, test_review_m2.py::test_a_file_outside_fixtures_and_cases_is_not_part_of_the_digestexists
E-5verify:self-hashevidence.seal(), evidence.problems(); test_evidence.py::test_hand_edited_evidence_fails_its_self_hash, test_evidence.py::test_verify_evidence_rejects_tampering, test_appendix_c.py::test_E_2_and_E_5_the_self_hash_is_reproduced_from_the_written_algorithmexists
E-6conform:derived-casesharness conform.derive_cases(); test_evidence.py::test_derived_case_ids_come_from_the_corpus_not_a_template, test_review_m2.py::test_changing_an_edge_target_demotes_the_dependent_articleexists
E-7conform:todayharness conform.run_conform() (suite cases: the suite date; derived cases: the run date; --today overrides both); test_appendix_c.py::test_E_7_corpus_runs_use_the_run_date_and_suite_cases_the_suite_date, test_E_7_an_explicit_today_applies_to_every_case, test_E_7_without_a_corpus_the_suite_date_is_recorded; test_review_m3_minor.py::test_m4_each_derived_case_records_the_date_it_used; test_evidence.py::test_today_is_injectable_and_validatedexists
E-8verify-evidence procedureharness conform.verify(); test_evidence.py::test_verify_evidence_passes_and_detects_changes, test_evidence.py::test_verify_evidence_rejects_tamperingexists
K-1ladder:report (histogram, rates)scorer report_text()exists
K-2ladder:report (L4 basis line)scorer report_text(); test_review_m2.py::test_graph_first_alone_prints_the_basis_lineexists
K-3ladder:--jsonscorer report_json(); test_evidence.py::test_graph_first_alone_is_still_attested_l4, test_evidence.py::test_evidence_plus_graph_first_mixes_basesexists
K-4publisher claim quoting a retained report—attested
K-5ladder:report basis countsscorer report_text(); test_evidence.py::test_measured_l4_from_evidenceexists
K-6publisher claim citing evidence sha256; verify-evidence—attested
K-7ladder:report (L1 label)scorer BARS[1] = "contracted"; test_appendix_c.py::test_K_7_l1_is_not_labelled_mrk_1_0; tests/golden/ladder-samples.txtexists
V-1-1schema: mrk_version enum: ["1.0", "1.1"]schemaexists
V-2-1gate:compat (1.0 specimen valid under 1.1; every estate instance valid under its version's schema)gate §3 routing; test_backcompat.py::test_mrk_1_0_instances_still_validate_under_1_1, test_backcompat.py::test_declared_1_1_instances_validate_under_1_1exists
V-2-2gate:router (absent → 1.0)gate §3 routingexists
V-3-1gate:freeze fingerprint (no additionalProperties: false introduced)gate §3c 1.1 fingerprint (fails on any new validation keyword, additionalProperties: false included)exists
V-4-1gate:compat regression for each new minortest_appendix_c.py::test_V_4_1_every_minor_version_is_strictly_additive_over_the_one_before (iterates every mrk-1.N.schema.json served)exists
V-4-2schema: allOf if on mrk_version/profile; gate:freeze fingerprintschema; gate §3c 1.1 fingerprint; test_backcompat.py::test_1_1_new_constraints_are_gated_behind_the_opt_inexists
V-4-3schema: mrk_version enumschemaexists
V-5-1gate:router (unknown → FAIL)gate §3 (an unknown mrk_version fails); scorer rung(); test_hardening.py::test_unknown_mrk_version_messageexists
V-6-1gate:freeze fingerprintgate §3c 1.1 fingerprint checkexists
V-6-2gate:freeze-1.0 (file SHA-256)gate §3c MRK10_SHA256 checkexists
V-6-3gate:freeze-1.1 (fingerprint 01a0e632…)gate §3c MRK11_FINGERPRINT checkexists
S-1gate:3c offline guard (no urllib, http.client, requests, socket)spec.py reads bundled files or --spec DIR only; test_offline.py::test_no_network_modules_are_imported_by_the_kit, test_offline.py::test_each_command_works_offlineexists
T-1gate:3c (zip members byte-match)tools/build-kit.py; test_kit.py::test_zip_is_current; gate §3c member byte-matchexists
T-2gate:3b byte-match (schema 1.1, context, shapes, answer-object)gate §3b (schema 1.1, context, shapes, answer-object) and §3c (evidence schema) byte-match; test_review_m2.py::test_evidence_schema_requires_run_at_and_corpus_and_both_copies_are_identicalexists
T-3test: every requirement line has --hashtools/mrk-tools/requirements.txt and tests/requirements.txt (both pip-compile --generate-hashes); test_appendix_c.py::test_T_3_every_requirement_line_carries_a_hash; CI installs both with --require-hashesexists
T-4gate:shim (shim output equals module output on sample/)test_kit.py::test_shim_matches_module_entry_pointexists

Appendix D: Resolved inconsistencies

Where the design documents disagreed with each other or with the code, this specification follows the Gate B decisions (B1 maturity layer, B2 one kit, B3 corpus-bound evidence).

  1. Instance version "2.0". Pages describe "MRK-2.0 at L4"; the 1.1 schema's mrk_version enum is ["1.0", "1.1"], so a "2.0" instance would score L0 in the shipped scorer. Resolved: MRK-2.0 is a standard version only; no new mrk_version (V-1-1); values "2" and up reserved (V-4-3).
  2. Online scorer vs offline tools. The packaged scorer fetches spec files from the site by default. Resolved: no network at run time (S-1); files bundled or --spec (T-1).
  3. Two ladder implementations. The prototype mrk-2.0/ladder.py holds the whole corpus at L3 on any SHACL failure and treats pyshacl as optional; the packaged scorer attributes violations per article and requires pyshacl. Resolved: the packaged behaviour is normative (I-8, P-L4-5).
  4. Drift and L4. reuse-transclusion.md puts drift-lint in the L4 gate; neither scorer runs it. Resolved: drift is part of L4's mechanical half for .md articles (P-L4-2).
  5. L4 operational half. It was a bare flag. Resolved: measured by corpus-bound evidence or attested by flag, with the basis always reported (P-L4-3, P-L4-4, K-2).
  6. Per-corpus or per-article L4 evidence (ladder open question 3). Resolved: one run per corpus; acceptance per article by digest membership (P-L4-3, E-3).
  7. "No dangling relTarget". The ladder text says SHACL rejects dangling relTarget; the projection expands relations so relTarget never appears, and the shapes check only requires, dependsOn and replaces. Resolved: those three are mandatory (I-4 to I-6); other relation types are expected to resolve, with shapes to be added (I-7).
  8. Stable ids as an L1 criterion. The ladder table lists stable ids as part of L1, but no single snapshot can show stability. Resolved: attested from revision history (A-5); not a scored criterion.
  9. L1 labelled "MRK-1.0 bar". L1 is checked against the 1.1 schema, which drops plan/region. Resolved: L1 is not MRK-1.0 conformance (K-7); the label changes. The reference tool labels L1 "contracted".
  10. Recognised types. The 1.1 schema description says concept and reference gain section-shape rules; the schema has rules only for issue-resolution and task. Resolved: only those two have shapes (P-L2-2); the description is corrected editorially at release (V-6-1).
  11. Type shapes vs mrk_version. The schema enforces type shapes only under mrk_version: "1.1"; the scorer applies them always. Resolved: both stand. Schema validity is gated by version (V-4-2); the L2 criterion is not (P-L2-2).
  12. C1 scope. The harness prints "C1 (R1-R4, R6, R7)"; retrieval-conformance.md defines C1 as R1-R4 and R6. Resolved: R7 belongs to C2 (§10).
  13. C1-only consumers. Parity cases call answer_graph unconditionally, so a consumer without a graph path crashes. Resolved: C1 is graded on answer() alone (C-L-1).
  14. R6 "article version". No article version field exists; the reference consumer cites mrk_version. Resolved without a new field: version is the article digest or null, never mrk_version (C-R6-3).
  15. Graph-path citation flags. answer_graph omits stale/superseded. Resolved: C-R6-4.
  16. Hard-coded date. The reference consumer fixes TODAY = 2026-09-18. Resolved: the reference date is an input (C-R2-4) and recorded in evidence (E-7).
  17. Absent locale. The reference consumer silently treats an absent locale as en. Resolved: the consumer declares its default (C-R3-4).
  18. Unevaluated dimensions. The reference consumer ignores version* and dimensions, so such articles match every reader. Resolved: the reference consumer compares version* (dotted numeric segments; anything else by exact string) and dimensions (per key) with the reader context, and fails closed (C-R1-3) where the context does not answer or the values cannot be compared.
  19. Retired articles. MRK-1.0 §4 says consumers "may" refuse retired articles; R2 says they must not serve them. Resolved: R2 governs consumers claiming C1 or C2 (C-R2-1); MRK-1.0 had no consumer conformance.
  20. Superseded articles (§R open question 3). Resolved: refuse retired and expired; downweight and flag superseded (C-R2-1, C-R2-3).
  21. Freshness in the ladder (ladder open question 1). Resolved: separate from rung (P-G-4).
  22. Partial L2 (ladder open question 2). Resolved: all-or-nothing, with the next-rung gap reported (P-G-2, P-G-3).
  23. Scope of the 1.0 → 1.1 theorem. Both schemas are open, so a valid 1.0 instance can carry a property that 1.1 later defined (for example profile: custom) and fail 1.1. Resolved: tools accept instances under their own format version's schema (V-2-1); such an article scores L0 with the schema message as its gap; V-4-1 states additivity for instances that do not use new names.
  24. Unknown mrk_version routing. validate-machine-blocks.py validates any non-"1.1" value against 1.0. Resolved: unknown values fail (V-5-1).
  25. Fingerprint definition. The Gate B memo strips every title/description/$comment recursively, which would also delete the schema of the article property named title. Resolved: annotation keywords are stripped only in schema position (Appendix A).
  26. YAML dates. The scorer validates raw yaml.safe_load output, so an unquoted verified: 2026-07-30, as shown on the MRK-1.0 page, loads as a date and fails type: string; the scorer also does not assert format: date. The repository's own samples quote dates to avoid this. Resolved: A-8.
  27. Section id ownership. Nothing checks that a section id's prefix is its article's id, though citations and the projection depend on it. Resolved: A-6 and P-L1-5.
  28. requires in graph retrieval. requires counts as an L3 edge, but the reference consumer follows only depends_on. Resolved: C2 follows both (C-R5-4); derived cases cover both (E-6).
  29. Transclusion direction (reuse open question 3). Resolved: the marker's target is canonical; self-targets are forbidden (D-5). Cycles stay open (Appendix E).
  30. Marker syntax and metric (reuse open questions 1 and 2). Resolved: HTML comment markers (D-1) and the lexical grounding score at 0.5 (D-6); no embedding model.
  31. Namespace and /kb/ IRIs. The context's @base gives every adopter's articles IRIs under machinereadyknowledge.com/kb/, and /ns is not served today. Resolved: IRIs are identifiers only and not dereferenced (N-3); /ns must be served (N-2).
  32. Stale status lines. graduation-ladder.md, retrieval-conformance.md, reuse-transclusion.md, conformance/README.md and mrk-1.1/CHANGES.md say "DRAFT" or "Nothing here is served" for content that #60 serves. Resolved: this document is the authority; those files carry a "rationale, superseded by SPEC.md" status line (changed at release).

Appendix E: Open issues

  1. Transclusion cycles (A copies from B, B copies from A). The drift gate tolerates them because canonical text excludes transcluded blocks; whether to forbid them is open.
  2. Evidence is unsigned. A signing scheme would need key distribution, which conflicts with offline use.
  3. An answer-envelope schema (the whole answer, not each citation).
  4. Further r5_contract shapes: supersedes pruning and trust-tiered selection.
  5. MRK-2.1 may require a measured L4 basis. MRK-2.0 keeps attestation so that existing results do not change.
  6. The recognised type set (howto, faq, policy) and provenance.confidence as an enum or a number, carried over from MRK-1.1.
  7. Known limitation: the reference tool's CI runs on macOS arm64 with Python 3.12 and 3.14 only. The pytest job installs the hash-locked tests/requirements.txt (--require-hashes) and the unzip-and-install step installs the kit's lock the same way. Linux, Windows and Python 3.13 are not run in CI. What is checked for them is the dependency locks: tests/test_locks.py evaluates the metadata of every locked package for Windows, Linux and macOS under Python 3.12 and 3.14 and fails if a dependency active there is not locked. That check found one gap, now closed: pytest needs colorama on Windows only, so pip-compile run on macOS leaves it out; it is pinned by hand, with a sys_platform == "win32" marker and its hashes, in tests/requirements.txt. This shows the locks are complete, not that the tool runs on those platforms.
  8. Known limitation (N-2): the live route https://machinereadyknowledge.com/ns cannot be tested from the repository. The test reads the .htaccess negotiation rules and checks that the files they name are served byte-identical to docs/; after an upload, request /ns with each Accept header to confirm the route (DEPLOY.md describes the rules).
  9. I-7 is a SHOULD and the 2.0 shapes do not check the targets of prerequisite_of, references, part_of, see_also and applies_with; a dangling one does not block L4. Whether a later edition should add shapes, which would make a dangling optional edge an L4 blocker, is open.