From 63c5479f4564d9e7bba60b333151b31ff3d12d41 Mon Sep 17 00:00:00 2001 From: Antonios Voulvoulis Date: Sat, 19 Sep 2026 13:07:47 +0300 Subject: [PATCH] D-111: the native control catalog is authored first, framework authority second MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Frozen as an architecture invariant before W1-D is designed. LINUX FACT -> ISEDRAF NATIVE CONTROL -> RESULT/EVIDENCE -> OPTIONAL MAPPING An external framework is never the source of a control. ISEDRAF authors its own criteria in the frozen ISE-* namespace from Linux system behaviour; a mapping is a downstream overlay added only where licensing permits, and removing every mapping leaves the catalog unchanged. Three ownerships kept apart: control ownership is ISEDRAF's, evidence ownership is the customer's and the observed host's, and only framework mapping rights are provider-specific. The authorship claim is deliberately bounded — independently authored from Linux behaviour and sources whose reuse rights permit it, with no claim that no patent, contract, trademark or copyright could ever exist anywhere. An absolute claim is cost-free to write and expensive to defend. An earlier draft used ISE-IDENT-* for accounts. Host IDENTITY and user ACCOUNTS are different domains; accounts are ISE-ACCOUNT-*, and ISE-IDENT stays reserved so the earlier draft resolves to an explanation rather than silence. make check-native-catalog requires the registry and the catalog document to AGREE, checks the namespace, refuses a criterion derived from a framework, and refuses a production module named after a provider. There is no cis_collector.py, and the gate is what makes that a fact rather than an intention. Nothing is implemented. The catalog is empty and W1-D authors it. Gates 18. Injections 74, all firing. Implements: D-78, D-79, D-82, D-84, D-111, GOV-001, GOV-002 Assisted-by: Claude (invariant drafting, gate design, defect injection) --- Makefile | 8 +- docs/CURRENT_STATE.md | 5 +- docs/architecture/NATIVE_CONTROL_CATALOG.md | 147 ++++++++++++++++++++ docs/licensing/FRAMEWORK_MAPPING_POLICY.md | 7 +- scripts/ci/check_native_catalog.py | 129 +++++++++++++++++ scripts/ci/falsifiable.sh | 29 ++++ scripts/ci/gate_coverage.json | 9 ++ scripts/ci/native_controls.json | 32 +++++ scripts/ci/project_status.json | 5 + 9 files changed, 366 insertions(+), 5 deletions(-) create mode 100644 docs/architecture/NATIVE_CONTROL_CATALOG.md create mode 100644 scripts/ci/check_native_catalog.py create mode 100644 scripts/ci/native_controls.json diff --git a/Makefile b/Makefile index 25f5eeb..e10dca5 100644 --- a/Makefile +++ b/Makefile @@ -6,9 +6,9 @@ # CI invokes these same targets rather than re-implementing them in YAML, which is # what prevents a gate silently degrading into a warning. There is no warning tier. -.PHONY: check check-licensing check-public-claims check-deb-ordering check-reproducible check-sbom check-tests check-python-floor check-packaging check-licensing check-public-claims check-privacy check-docs-truth check-current-state check-headers check-docs check-scope check-shell check-refs check-paths check-index check-freeze check-vectors check-vectors-negative check-vectors-crossversion check-gate-coverage check-falsifiable help +.PHONY: check check-native-catalog check-licensing check-public-claims check-deb-ordering check-reproducible check-sbom check-tests check-python-floor check-packaging check-native-catalog check-licensing check-public-claims check-privacy check-docs-truth check-current-state check-headers check-docs check-scope check-shell check-refs check-paths check-index check-freeze check-vectors check-vectors-negative check-vectors-crossversion check-gate-coverage check-falsifiable help -check: check-scope check-headers check-python-floor check-packaging check-licensing check-public-claims check-privacy check-docs-truth check-current-state check-shell check-refs check-paths check-index check-freeze check-vectors check-vectors-negative check-vectors-crossversion check-tests check-docs +check: check-scope check-headers check-python-floor check-packaging check-native-catalog check-licensing check-public-claims check-privacy check-docs-truth check-current-state check-shell check-refs check-paths check-index check-freeze check-vectors check-vectors-negative check-vectors-crossversion check-tests check-docs @echo "make check: all gates passed" ## check-scope D-96: no product implementation before architecture freeze @@ -85,6 +85,10 @@ check-python-floor: check-packaging: @python3 scripts/ci/check_packaging.py +## check-native-catalog D-111: the control catalog is ISEDRAF's own, and stays that way +check-native-catalog: + @python3 scripts/ci/check_native_catalog.py + ## check-licensing D-84/D-90: MPL covers what we own; unknown licensing is not distributable check-licensing: @python3 scripts/ci/check_licensing.py diff --git a/docs/CURRENT_STATE.md b/docs/CURRENT_STATE.md index 8135288..7aa0c30 100644 --- a/docs/CURRENT_STATE.md +++ b/docs/CURRENT_STATE.md @@ -62,6 +62,7 @@ Designed, not built. No part of this runs. - `framework_mapping_packs` — design only - no pack loader, no manifest reader, no entitlement mechanism and no signing exists; no third-party mapping is licensed, reviewed or bundled (design: `docs/licensing/FRAMEWORK_PACK_ARCHITECTURE.md`) - `journald_recording` - `mounts` +- `native_control_catalog` — D-111 freezes the invariant and the ISE-* namespace: 14 families, ISE-IDENT reserved and not in use. NO native criterion is authored yet - they arrive in W1-D. make check-native-catalog enforces that the registry and the catalog document agree, that criteria hold to the namespace, that no criterion is derived from a framework, and that no production module is named after a provider (design: `docs/architecture/NATIVE_CONTROL_CATALOG.md`) - `pam` - `report_pdf` - `services` @@ -104,8 +105,8 @@ Not asserted. Each number is counted at generation time. | | | |---|---| -| Gates | 17 | -| Falsification injections | 70 | +| Gates | 18 | +| Falsification injections | 73 | | Golden vector cases | 15 | | Frozen artifacts | 7 | | Test files | 3 | diff --git a/docs/architecture/NATIVE_CONTROL_CATALOG.md b/docs/architecture/NATIVE_CONTROL_CATALOG.md new file mode 100644 index 0000000..5d41a6c --- /dev/null +++ b/docs/architecture/NATIVE_CONTROL_CATALOG.md @@ -0,0 +1,147 @@ + +# ISEDRAF Native Control Catalog + +Implements: D-78, D-79, D-82, D-84, D-111 + +**Architecture invariant, owner decision 2026-09-19 (`D-111`), frozen before W1-D is designed.** + +> The ISEDRAF native control catalog is authored first. Framework authority is mapped second, and +> only where licensing permits. **An external framework is never the source of a control.** + +## The layering + +```text +LINUX FACT + ↓ +ISEDRAF NATIVE CONTROL + ↓ +ISEDRAF RESULT / EVIDENCE + ↓ +OPTIONAL AUTHORITY MAPPING + ├── open / public mapping + ├── provider-licensed mapping + └── no mapping +``` + +The arrow runs one way. A framework requirement never defines how a collector is implemented or what +a criterion says, and removing every mapping leaves the catalog exactly as useful. + +## Three ownerships, kept apart + +| | | +|---|---| +| **Control ownership** | ISEDRAF | +| **Evidence ownership** | the customer, and the host observed | +| **Framework mapping rights** | framework- and provider-specific | + +Separating these is the whole point. The first two are ours and the customer's and carry no +third-party licensing question. Only the third does, and it is isolated so that a licensing problem +in one framework cannot reach the engine, the evidence, or any other framework. + +## What ISEDRAF claims about authorship — and what it does not + +> ISEDRAF independently authors technical host-assurance criteria based on Linux system behaviour, +> security engineering principles, and sources whose reuse rights permit that use. External framework +> content is not required to define those criteria. + +That is the claim, and it is deliberately narrower than *"nothing could ever forbid a control"*. This +project does not assert that no patent, contract, trademark, copyright, database right or other +restriction could exist anywhere in the world. It asserts what it can support: the criteria are +independently authored, and they do not depend on licensed framework content. + +The distinction matters in a legal document. An absolute claim is the kind of sentence that is +cost-free to write and expensive to defend. + +## Namespace + +Authority: `scripts/ci/native_controls.json`. `make check-native-catalog` requires this document and +that file to agree, because two authorities that can disagree are how a namespace drifts. + +| Family | Domain | +|---|---| +| `ISE-ASSET-*` | asset and host inventory | +| `ISE-SW-*` | software and package state | +| `ISE-ACCOUNT-*` | user, service and administrative accounts | +| `ISE-AUTH-*` | authentication | +| `ISE-PRIV-*` | sudo and privilege | +| `ISE-SSH-*` | SSH posture | +| `ISE-SVC-*` | service exposure and state | +| `ISE-KERNEL-*` | sysctl and kernel security | +| `ISE-LOG-*` | audit and logging | +| `ISE-TIME-*` | clock and time synchronization | +| `ISE-CRYPTO-*` | cryptographic posture | +| `ISE-STORAGE-*` | filesystem, storage and mount security | +| `ISE-NET-*` | network configuration | +| `ISE-UPDATE-*` | update and support posture | + +**`ISE-IDENT-*` is reserved and not in use.** An earlier draft used it for accounts. Host *identity* +(`machine-id`, `host_id`) and user *accounts* are different domains, and one prefix meaning both +would have been a permanent source of confusion. Accounts are `ISE-ACCOUNT-*`; the reservation stands +so the earlier draft resolves to this explanation rather than to silence. + +## What a criterion contains + +Required: `criterion_id` · `purpose` · `facts_required` · `dimensions` (declared / resolved / active, +where applicable) · `evaluation_semantics` · `applicability` · `limitations` · `evidence_pointers` · +`version`. Optional: `remediation_guidance`. + +Result states: `PASS` · `FAIL` · `PARTIAL` · `NOT_EVALUATED`. + +Every field is ISEDRAF-authored. No framework identifier, title, description or safeguard text +appears in a criterion — a mapping is a separate object in a separate layer. + +### Illustrative shape + +```text +ISE-SSH-001 + purpose Determine the effective SSH root-login posture. + facts effective PermitRootLogin value · configuration source · resolution status + states PASS / FAIL / PARTIAL / NOT_EVALUATED + evidence the exact normalized observed state +``` + +Nobody's permission is required to write a Linux security criterion about effective SSH +configuration. The licensing question begins one layer later, at *"this corresponds to provider X +control Y"* — which can involve another party's identifiers, taxonomy, titles, descriptions, +profiles, selection and arrangement, trademarks or proprietary mapping data. That is exactly why it +is isolated. + +## Build order + +**Stage 1 — the catalog.** Author the ISEDRAF control universe. No CIS, ISO, SCF or anything else is +required anywhere in it. *(W1-D and later. Nothing is authored today.)* + +**Stage 2 — open authorities.** For sources whose exact reuse rights are verified, add mappings. The +ISEDRAF criterion remains authoritative for the engine even here. + +**Stage 3 — restricted authorities.** An encrypted licensed mapping pack plus a provider entitlement. + +```text +ISEDRAF control always available +ISEDRAF evidence always available +provider mapping entitlement required +provider report view entitlement required +provider content provider controlled +``` + +## Why this is frozen before W1-D + +It means the technical engine can be finished without waiting for anyone. + +```text +provider says yes later → existing controls + licensed mapping pack +provider says no → existing controls +``` + +Either way there is no collector rewrite and nothing is lost — which is what +*measure once, map everywhere, fix only the delta* has to mean in practice if it is going to survive +contact with a licensing negotiation. + +## Current state + +**No native criterion is authored.** The namespace is frozen; the catalog is empty; no mapping of any +kind exists. `scripts/ci/native_controls.json` says so, and the gate does not treat an empty catalog +as a pass by omission. diff --git a/docs/licensing/FRAMEWORK_MAPPING_POLICY.md b/docs/licensing/FRAMEWORK_MAPPING_POLICY.md index 3fca16f..8f49a4a 100644 --- a/docs/licensing/FRAMEWORK_MAPPING_POLICY.md +++ b/docs/licensing/FRAMEWORK_MAPPING_POLICY.md @@ -30,7 +30,12 @@ ISEDRAF EVIDENCE / RESULT observed, with its limits stated OPTIONAL FRAMEWORK MAPPING a downstream overlay, if one is licensed ``` -The arrow never runs the other way. A framework requirement does not define how a collector is +The arrow never runs the other way. This is frozen as an architecture invariant in +[`NATIVE_CONTROL_CATALOG.md`](../architecture/NATIVE_CONTROL_CATALOG.md) (`D-111`), which fixes the +`ISE-*` namespace and separates three ownerships: **control ownership** is ISEDRAF's, **evidence +ownership** is the customer's and the observed host's, and only **framework mapping rights** are +framework-specific. The practical effect is that the technical engine is completed without waiting +for any provider — a later agreement adds a mapping pack, and a refusal costs nothing already built. A framework requirement does not define how a collector is implemented, and there is no `cis_collector.py`, `iso_collector.py` or `scf_collector.py` — there is a users collector, an SSH collector, a logging collector. One host collection can then serve many mappings. diff --git a/scripts/ci/check_native_catalog.py b/scripts/ci/check_native_catalog.py new file mode 100644 index 0000000..f88d8f9 --- /dev/null +++ b/scripts/ci/check_native_catalog.py @@ -0,0 +1,129 @@ +# ============================================================================= +# ISEDRAF — Linux Host Assurance, State Delta & Evidence Bridge (codename) +# ============================================================================= +# SPDX-License-Identifier: MPL-2.0 +# SPDX-FileCopyrightText: Copyright (c) 2026 Antonios Voulvoulis / ITCMS +# +# Purpose: The native control catalog is ISEDRAF's own, and stays that way. +# Implements: D-78, D-79, D-84, D-111, GOV-002 +# +# D-111 freezes the layering: LINUX FACT -> NATIVE CONTROL -> EVIDENCE -> OPTIONAL MAPPING. +# An invariant with no gate is a sentence, and this project has learned what those are +# worth. Four things are checked, and the first is the one that keeps the namespace honest. +# +# 1. The registry and the architecture document must AGREE on the families. Two +# authorities that can disagree are how a namespace drifts - the same defect class as +# the rpm spec staging two documents while build.sh staged four. +# 2. Every criterion ID matches the frozen namespace pattern and a declared family. +# 3. No criterion may cite a framework as its source. The control is ours or it is not a +# native control. +# 4. No production module may be named after a framework provider. There is no +# cis_collector.py, and the gate is what makes that a fact rather than an intention. +# +# meta:type="ci-gate" +# meta:owner="Antonios Voulvoulis / ITCMS" +# meta:stability="EXPERIMENTAL" +# meta:privilege="unprivileged" +# meta:mutates="none" +# meta:binaries="git,python3" +# ============================================================================= + +"""usage: check_native_catalog.py""" +import json +import pathlib +import re +import subprocess +import sys + +ROOT = pathlib.Path(subprocess.check_output( + ["git", "rev-parse", "--show-toplevel"], text=True).strip()) +REG = json.loads((ROOT / "scripts" / "ci" / "native_controls.json").read_text()) +DOC = ROOT / "docs" / "architecture" / "NATIVE_CONTROL_CATALOG.md" +FAIL = [] + + +def bad(msg): + FAIL.append(msg) + print(" FAIL %s" % msg) + + +print("--- native control catalog (D-111) ---") + +families = REG["families"] +# Z-18: count the input before judging it. A namespace with no families is not a pass. +if not families: + bad("the registry declares no control families") + sys.exit(1) + +# --- 1. the registry and the document agree ------------------------------------------ +if not DOC.exists(): + bad("the catalog document is missing: %s" % DOC.relative_to(ROOT)) +else: + text = DOC.read_text(encoding="utf-8") + in_doc = set(re.findall(r"`(ISE-[A-Z]+)-\*`", text)) + declared = set(families) + reserved = set(REG.get("reserved_families", {})) + for f in sorted(declared - in_doc): + bad("%s is in the registry and not in the catalog document" % f) + for f in sorted(in_doc - declared - reserved): + bad("%s is in the catalog document and not in the registry" % f) + for f in sorted(reserved): + if f not in text: + bad("%s is reserved but the document does not explain why" % f) + if not (declared - in_doc) and not (in_doc - declared - reserved): + print(" OK %d families: registry and catalog document agree" % len(declared)) + +# --- 2. criterion IDs hold to the frozen namespace ----------------------------------- +pattern = re.compile(REG["namespace_pattern"]) +required = set(REG["required_criterion_fields"]) +seen = set() +for c in REG["criteria"]: + cid = c.get("criterion_id", "") + if not pattern.match(cid or ""): + bad("%r does not match the frozen namespace %s" % (cid, REG["namespace_pattern"])) + continue + if cid in seen: + bad("%s is defined more than once" % cid) + seen.add(cid) + fam = cid.rsplit("-", 1)[0] + if fam not in families: + bad("%s belongs to family %s, which is not declared" % (cid, fam)) + missing = required - set(c) + if missing: + bad("%s is missing required field(s): %s" % (cid, ", ".join(sorted(missing)))) + +# --- 3. a native control is not derived from a framework ----------------------------- +RESTRICTED = re.compile( + r"\b(CIS|ISO[/ ]?IEC|ISO\s*27\d{3}|SCF|HITRUST|COBIT|PCI[- ]?DSS|NIS2|DORA|CCM)\b") +for c in REG["criteria"]: + blob = json.dumps(c) + m = RESTRICTED.search(blob) + if m: + bad("%s names %s in the criterion itself. A native control is authored from Linux " + "behaviour; a framework reference belongs in a mapping, which is a separate " + "layer." % (c.get("criterion_id", ""), m.group(0))) +if REG["criteria"]: + print(" OK %d criteria: namespace, fields and independence hold" % len(seen)) +else: + print(" OK catalog is empty — no native criterion is authored yet (W1-D), and the " + "registry says so rather than implying otherwise") + +# --- 4. no production module named after a framework provider ------------------------ +PROVIDER_NAME = re.compile(r"(^|[_/-])(cis|iso27\d*|iso|scf|hitrust|cobit|pci|nist|ccm)" + r"([_/-]|\.py$)", re.I) +tracked = subprocess.check_output(["git", "ls-files", "lib/"], cwd=str(ROOT), text=True).split() +if not tracked: + bad("no production files found under lib/ — nothing was checked") +else: + offenders = [p for p in tracked if PROVIDER_NAME.search(pathlib.Path(p).name)] + for p in offenders: + bad("%s is named after a framework provider. Collectors describe the host, not a " + "framework: there is no cis_collector.py." % p) + if not offenders: + print(" OK %d production files: none named after a framework provider" + % len(tracked)) + +if FAIL: + print("=== native control catalog gate FAILED ===") + print(" The control is ISEDRAF's, or it is not a native control.") + sys.exit(1) diff --git a/scripts/ci/falsifiable.sh b/scripts/ci/falsifiable.sh index 6501535..459b5d4 100755 --- a/scripts/ci/falsifiable.sh +++ b/scripts/ci/falsifiable.sh @@ -212,6 +212,35 @@ p.write_text(s.replace(old, "echo \"X-Build-Stamp: $(date +%s%N)\" >> \"$DEBROOT PYX' \ 'reproducible build gate FAILED|artifacts identical' +# D-111. The native control catalog is authored first and is ISEDRAF's own. The invariant +# is frozen; these prove the gate enforcing it can refuse. +inject "D-111 a production module is named after a framework provider" \ + 'python3 scripts/ci/check_native_catalog.py' \ + 'cp lib/isedraf/identity.py lib/isedraf/cis_collector.py && git add -f -A' \ + 'named after a framework provider|native control catalog gate FAILED' + +inject "D-111 the namespace and the catalog document disagree" \ + 'python3 scripts/ci/check_native_catalog.py' \ + 'python3 - <<'"'"'PYX'"'"' +import json, pathlib +p = pathlib.Path("scripts/ci/native_controls.json"); d = json.loads(p.read_text()) +d["families"]["ISE-GHOST"] = "a family the catalog document has never heard of" +p.write_text(json.dumps(d, indent=2)) +PYX' \ + 'registry and not in the catalog document|native control catalog gate FAILED' + +inject "D-111 a native criterion is derived from a framework" \ + 'python3 scripts/ci/check_native_catalog.py' \ + 'python3 - <<'"'"'PYX'"'"' +import json, pathlib +p = pathlib.Path("scripts/ci/native_controls.json"); d = json.loads(p.read_text()) +d["criteria"].append({f: "x" for f in d["required_criterion_fields"]}) +d["criteria"][0]["criterion_id"] = "ISE-SSH-001" +d["criteria"][0]["purpose"] = "Implements CIS Controls safeguard 4.1" +p.write_text(json.dumps(d, indent=2)) +PYX' \ + 'names CIS in the criterion itself|native control catalog gate FAILED' + # D-84/D-90. Third-party framework content is not relicensed by sitting in this # repository. The registry is deny-by-default, and "deny by default" is a claim that has # to be shown to deny something. diff --git a/scripts/ci/gate_coverage.json b/scripts/ci/gate_coverage.json index b3d61c7..d097faa 100644 --- a/scripts/ci/gate_coverage.json +++ b/scripts/ci/gate_coverage.json @@ -165,6 +165,15 @@ "REUSE.toml", "scripts/ci/framework_sources.json" ] + }, + "check-native-catalog": { + "script": "scripts/ci/check_native_catalog.py", + "implements": "D-78, D-79, D-84, D-111", + "falsification": "D-111 a production module is named after a framework provider", + "eligible": [ + "scripts/ci/native_controls.json", + "docs/architecture/NATIVE_CONTROL_CATALOG.md" + ] } }, "unpublished_paths": { diff --git a/scripts/ci/native_controls.json b/scripts/ci/native_controls.json new file mode 100644 index 0000000..72ec7cd --- /dev/null +++ b/scripts/ci/native_controls.json @@ -0,0 +1,32 @@ +{ + "$comment": "The ISEDRAF NATIVE CONTROL CATALOG registry. Authority for the ISE-* namespace. Consumed by scripts/ci/check_native_catalog.py, which requires this file and docs/architecture/NATIVE_CONTROL_CATALOG.md to agree - two authorities that can disagree are how a namespace drifts. Criteria are authored in W1-D and later; `criteria` is empty today and that is the accurate state.", + "catalog_version": 1, + "namespace_pattern": "^ISE-[A-Z]+-[0-9]{3}$", + "families": { + "ISE-ASSET": "asset and host inventory", + "ISE-SW": "software and package state", + "ISE-ACCOUNT": "user, service and administrative accounts", + "ISE-AUTH": "authentication", + "ISE-PRIV": "sudo and privilege", + "ISE-SSH": "SSH posture", + "ISE-SVC": "service exposure and state", + "ISE-KERNEL": "sysctl and kernel security", + "ISE-LOG": "audit and logging", + "ISE-TIME": "clock and time synchronization", + "ISE-CRYPTO": "cryptographic posture", + "ISE-STORAGE": "filesystem, storage and mount security", + "ISE-NET": "network configuration", + "ISE-UPDATE": "update and support posture" + }, + "reserved_families": { + "ISE-IDENT": "RESERVED, NOT IN USE. An earlier draft of the namespace used ISE-IDENT-* for accounts. Host IDENTITY (machine-id, host_id) and user ACCOUNTS are different domains, and one prefix meaning both would have been a permanent source of confusion. Accounts are ISE-ACCOUNT-*. ISE-IDENT stays reserved so it cannot be reused for something else and so the earlier draft resolves to an explanation rather than to silence." + }, + "required_criterion_fields": [ + "criterion_id", "purpose", "facts_required", "dimensions", "evaluation_semantics", + "applicability", "limitations", "evidence_pointers", "version" + ], + "optional_criterion_fields": ["remediation_guidance"], + "result_states": ["PASS", "FAIL", "PARTIAL", "NOT_EVALUATED"], + "criteria": [], + "$criteria_comment": "Empty. No native criterion is authored yet; they arrive in W1-D. The gate proves the namespace is consistent and that nothing framework-derived has entered it - it does not treat an empty catalog as a pass by omission." +} diff --git a/scripts/ci/project_status.json b/scripts/ci/project_status.json index 7dc68fe..c4db9b6 100644 --- a/scripts/ci/project_status.json +++ b/scripts/ci/project_status.json @@ -174,6 +174,11 @@ "framework_specific_reports": { "status": "NOT_TESTED", "note": "not available: framework-specific output does not exist, and core reports are complete without it" + }, + "native_control_catalog": { + "status": "PLANNED", + "design": "docs/architecture/NATIVE_CONTROL_CATALOG.md", + "note": "D-111 freezes the invariant and the ISE-* namespace: 14 families, ISE-IDENT reserved and not in use. NO native criterion is authored yet - they arrive in W1-D. make check-native-catalog enforces that the registry and the catalog document agree, that criteria hold to the namespace, that no criterion is derived from a framework, and that no production module is named after a provider" } }, "runtime": {