Select explicitly pinned security case type revisions without fallback
All checks were successful
CI Smoke / host-smoke (push) Successful in 0s
CI Smoke / container-smoke (push) Successful in 1s

Assistant: codex
Assistant-Model: gpt-6.1-sol
Assistant-Session: 01a126e1-76f8-7643-8cf3-e93336539ade
This commit is contained in:
tegwick 2026-10-11 09:09:24 +02:00
parent f6856cdb53
commit bb9b9b556c
9 changed files with 415 additions and 9 deletions

View file

@ -0,0 +1,43 @@
{
"owner_source_commit": "f6856cdb53c051e35c984bad927395b7e8a8e5e0",
"orientation": {
"package_version": "0.2.6",
"digest": "8d1d6a9d4d07fec7b619618de7dfe2be2254c1790f85f4198c7fe19db9751c02",
"start_source_status": "withheld; source changed",
"source_review": "AGENTS/INTENT/SECURITY-COMPANION and relevant actual validator/acknowledgment/workplan reviewed; no guidance or authority inferred"
},
"tests_passed": 43,
"default11_type_status": "acknowledged",
"explicit12_type_status": "acknowledged",
"both_ordinary_admission_refused": true,
"actual_original_and_staged11_cli_render_bytes_identical": true,
"default11_render_digest": "sha256:e64b56e4590b57b858303fcda1a474693e3dd821bc83df26646fe78fc345498a",
"version12_preparation_render_digest": "sha256:c0afd5576cdae0cbc807eccb2b9fa87ac668f15e32ec7cf79d295bdb23c75b35",
"canonical11_files": {
"security-case-types/oidc-browser-relying-party/README.md": "c01ec1a3f058eab9e6c461e898166d496adb8542ea46f9e198ced4de83b997ef",
"security-case-types/oidc-browser-relying-party/decision/build_intake.py": "d214019a10801e14d7986474ff83370482116df6a0fdde6ecb255d75645b3b04",
"security-case-types/oidc-browser-relying-party/decision/intake.json": "a0ab9c57c71359b9ed31b9d6b04fd464db3e41bb6fe6f39bc6ac0b85556765e0",
"security-case-types/oidc-browser-relying-party/decision/packet.md": "452ea42168ddef64dc5498ba30ae63ff2ff47a3919f55e22f6379d668ea76c28",
"security-case-types/oidc-browser-relying-party/examples/example-public-portal.yaml": "41cc5bb4842209f5f86830d1e8b4b010c6ef06a050c4160faae06a1dfcd9b6e9",
"security-case-types/oidc-browser-relying-party/examples/forgejo.yaml": "96c8dcd4b4182893b3a005137b880c0a80d4aa07a2668b29964e1db33605e050",
"security-case-types/oidc-browser-relying-party/tests/test_oidc_browser_relying_party.py": "7670ad0e415ca9941255bbc111b91fb5c9c3ae41851096d53d85fe7ba1d8660f",
"security-case-types/oidc-browser-relying-party/tests/test_oidc_browser_relying_party_contract.py": "ed30cd00ae22c32fbe2184443a2a8a7775ea5c231d1dd8070f230d547051f4e0",
"security-case-types/oidc-browser-relying-party/type.yaml": "f705c64e4875e9b8e18a912fb51c135e77898926d6a451f5cb74ab3c0cbe4d1c"
},
"historical_accepted11_case_sha256": "bcc2c6d5bae0d28242b3cdb71e74d8ca297c2afe04766b55d53597f86893351d",
"acknowledged12_files": {
"security-case-type-revisions/1.2/oidc-browser-relying-party/README.md": "4c82d9517d423ada75e680a05b0ecc6dfa0bf9f877a92b68f3ab93c1a86e405e",
"security-case-type-revisions/1.2/oidc-browser-relying-party/decision/acknowledgment.json": "2dcc220febf125a17b3769f7014f18b63979d7121e0f7056a2b0d8b1ae6031b0",
"security-case-type-revisions/1.2/oidc-browser-relying-party/examples/example-public-portal.yaml": "8d6457248415daa6fc509da4e838aae0b66d7808e9da8e00f4b85a1e7faed10d",
"security-case-type-revisions/1.2/oidc-browser-relying-party/examples/forgejo.yaml": "1c868c1da7d9077fae97bc6bc029706ec3dc418cfeaae30dc0f92b5b4ba6f2ab",
"security-case-type-revisions/1.2/oidc-browser-relying-party/tests/test_source_contract.py": "4a958eb2d0b90c1ffd08300d3299e4c31c827dcd7fb3cb4c22df91cc29986124",
"security-case-type-revisions/1.2/oidc-browser-relying-party/type.yaml": "9e60ff569fb0d860a2bbde7bbb0991a54d4b0688f62b56df0fa5d161fd624578"
},
"provider_declaration_inputs": {
"/home/worsch/net-kingdom/capabilities/playbooks/net-kingdom.keycape-client-registration.yaml": "cd81e02c283d514492b7a99062c9ede59a7ae124736de1270a121bf0a96a984e"
},
"ordinary_admission_or_type_implemented_or_case_accepted_or_native_completed": false,
"native_or_credentials_accessed": false,
"formal_net_owner_review": false,
"NK_WP_0048_T06_remains_wait": true
}

View file

@ -11,7 +11,7 @@ immutable. Independent acknowledged-package validator and22 tests pass; closed
schema/version load and identical c0afd preparation render are proved. schema/version load and identical c0afd preparation render are proved.
Current canonical1.1 package and accepted1.1 case remain unchanged, without Current canonical1.1 package and accepted1.1 case remain unchanged, without
inherited1.2 authority. Current load_type reads one type.yaml per id and requires inherited1.2 authority. At source acknowledgment, load_type read one type.yaml per id and required
exact version, so selecting1.2 as default would break that1.1 case. The explicit exact version, so selecting1.2 as default would break that1.1 case. The explicit
version root loads1.2 through existing --types/--types-root input, only for source version root loads1.2 through existing --types/--types-root input, only for source
validation/preparation. This record changes no resolver/default, native broker validation/preparation. This record changes no resolver/default, native broker
@ -37,3 +37,11 @@ No new login/remint or speculative delete authority follows from this record.
Existing native acceptance and human gates remain in their current owner/project Existing native acceptance and human gates remain in their current owner/project
records. This document adds no broad plan, promoted residual or implementation records. This document adds no broad plan, promoted residual or implementation
permission and marks no existing implementation task done. permission and marks no existing implementation task done.
## Source-only explicit selection integration — 2026-10-11
A bounded optional version_roots input and CLI --version-root VERSION=PATH now prepare exact1.2 selection alongside the unchanged --types default. Matching exact version chooses one declared root; missing/mismatched/duplicate/symlinked source refuses without fallback. Source type ID/version must equal the case reference, including direct validation/render callers. Existing two-argument callers remain default-only; no automatic Mason/Warden/native adoption or highest/latest version resolver is introduced. See tools/security-case-types/README.md and docs/evidence/2026-10-11-explicit-type-version-selection/source-integration-validation.json.
Actual canonical1.1 is acknowledged (not implemented); its historical accepted case is retained exactly, including ordinary-admission refusal and the existing nonexecuting preparation behavior. Both1.1 and acknowledged1.2 remain refused in ordinary admission. The original and staged1.1 preparation CLI output bytes match exactly (digest e64b56e4...);1.2 mapped preparation equals its direct-root render (digest c0afd557...). No type status/canonical case/acknowledgment is rewritten. Forty-three validator tests cover exact selection, malformed/traversal/ambiguity/no-fallback, pin separation, deterministic plan-only rendering and unchanged ordinary gates. These are maker source checks, not formal NetKingdom owner agreement or native implementation proof.
NK-WP-0048-T06 remains wait in its existing ORGREF cross-owner driver. Source selection alone does not provide first-reference owner receipts, implemented type, case acceptance/materialized/live status, owner adapters/global authenticated verifier, broker source reconciliation or attended/native proof. Those existing remaining gates and approvals are unchanged.

View file

@ -0,0 +1,41 @@
{
"reviewer": "net-kingdom-owner (agent net-kingdom, codex)",
"decision": "approved",
"work_item": "NK-WP-ADHOC-2026-10-11-T06",
"scope": "explicit-version-source-selection-review-only",
"reviewed_manifest_sha256": "88238f29ebeceee36f6f6e77bb19cb43837b5bc03bce21c16acc50399b302084",
"reviewed_validator_sha256": "5b9e46a5d0a8f387c6e0d87216c5695c03753c564378b1e43c07b4628cd07dc7",
"comment": "Established NetKingdom owner only, existing completedT06 anchor verified before this source followthrough review. INTENT Taxonomy, inspectable identity contracts and Meta-Orchestration support exact explicit source version selection, immutable predecessor history and owner-controlled later integration. This optional selector prepares source without discovering or promoting authority; canonicalcase/type acceptance and native owner gates remain distinct. Currentdefault1.1 is acknowledged/unimplemented, not implemented.",
"independent_validation": {
"all_six_staged_and_original_sha_guards_matched": true,
"isolated_validator_and_version_selection_tests_passed": 43,
"acknowledged12_package_compatibility_tests_passed": 22,
"temporary_snapshot_setup_initial_collection_failure": "Missing playbook-capability-contract dependency in my temporary copy; copied unchanged actual dependency and reran successfully without candidate source edits.",
"actual_canonical11_and_acknowledged12_file_pins_match_candidate_evidence": true,
"both_ordinary_admission_refused": true,
"default11_preparation_render_digest": "sha256:e64b56e4590b57b858303fcda1a474693e3dd821bc83df26646fe78fc345498a",
"explicit12_preparation_render_digest": "sha256:c0afd5576cdae0cbc807eccb2b9fa87ac668f15e32ec7cf79d295bdb23c75b35",
"execution_permitted": false
},
"selection_contract": [
"Existing two-argument callers retain their supplied default root; unmapped versions use that root.",
"A matching explicit version selects one supplied Path root only; missing/wrong source refuses without falling back, directory scanning or highest/latest selection.",
"Duplicate or invalid CLI mappings refuse even when identical; closed exact reference rejects traversal/unpinned selectors.",
"Selected root/type directory/typefile symlink and duplicate YAML source keys refuse; exact source id/version match required.",
"Direct case validation now refuses mixed case/type version inputs, including in source preparation mode.",
"Explicit roots are caller-supplied source inputs, not authenticated owner provenance or new acknowledgment; exact content hashes/receipts still bind acceptance."
],
"retained_limits": {
"canonical11_package_or_case_changed": false,
"acknowledged12_package_or_receipt_changed": false,
"type_implemented": false,
"case_accepted_by_this_change": false,
"owner_adapter_installed": false,
"global_native_evidence_verifier_supplied": false,
"bound_claims_confirmed": false,
"native_action_or_credential_access": false,
"grant_or_first_case_complete": false,
"NK_WP_0048_T06_status": "wait"
},
"actual_completed_workrecord_sha256": "a09f12d10bbb6fe89daca687b619f79583836afa9b68664ef1442787975d9065"
}

View file

@ -0,0 +1,96 @@
{
"candidate_files": [
{
"repo": "net-kingdom",
"path": "docs/evidence/2026-10-11-explicit-type-version-selection/source-integration-validation.json",
"staged_path": "/tmp/nk-explicit-version-selection-stage/net-kingdom/docs/evidence/2026-10-11-explicit-type-version-selection/source-integration-validation.json",
"original_sha256": null,
"staged_sha256": "de288546b8d1c797fc4cc63978d586a6f61d5cc4782ded67dafc907dd09723fa"
},
{
"repo": "net-kingdom",
"path": "docs/forgejo-type12-source-acknowledgment-followthrough.md",
"staged_path": "/tmp/nk-explicit-version-selection-stage/net-kingdom/docs/forgejo-type12-source-acknowledgment-followthrough.md",
"original_sha256": "1755b5c45f30f622ba7bbe67f64bf310de61ea3a2cd192472dc2c0bcedc7562a",
"staged_sha256": "fda468e5cbe346217bc1bb481e89563fd76778ad67f64a41e8385057f528c221"
},
{
"repo": "net-kingdom",
"path": "tools/security-case-types/README.md",
"staged_path": "/tmp/nk-explicit-version-selection-stage/net-kingdom/tools/security-case-types/README.md",
"original_sha256": "c000d0b6ca18ce15b777372ca00b813d15700c652ac1040d1ee883712aeee4ac",
"staged_sha256": "03e1c503bc60990214d5b1a7cec8878cf47fc892d82c82d682f3490e33dc71f5"
},
{
"repo": "net-kingdom",
"path": "tools/security-case-types/security_case_validator.py",
"staged_path": "/tmp/nk-explicit-version-selection-stage/net-kingdom/tools/security-case-types/security_case_validator.py",
"original_sha256": "ef380a85ca9949e0ebc59d1cdce422bd45e362eb976330b2b72b8fd73784ae0a",
"staged_sha256": "5b9e46a5d0a8f387c6e0d87216c5695c03753c564378b1e43c07b4628cd07dc7"
},
{
"repo": "net-kingdom",
"path": "tools/security-case-types/tests/test_explicit_version_selection.py",
"staged_path": "/tmp/nk-explicit-version-selection-stage/net-kingdom/tools/security-case-types/tests/test_explicit_version_selection.py",
"original_sha256": null,
"staged_sha256": "0592c751ed75f27231d3ca2a2eb97af7e2f45b79b0572fbdf8cadbe3738aa76d"
},
{
"repo": "net-kingdom",
"path": "workplans/NK-WP-0048-security-case-types.md",
"staged_path": "/tmp/nk-explicit-version-selection-stage/net-kingdom/workplans/NK-WP-0048-security-case-types.md",
"original_sha256": "a7f8e24b4da32a421c4877fbfa9e0914233b7d6f8ed7e71165998ce3cc501ff0",
"staged_sha256": "c5f0e3bce81ae662d04158689ee250a54af36e17c5bac32858ba889795597001"
}
],
"source_evidence": {
"owner_source_commit": "f6856cdb53c051e35c984bad927395b7e8a8e5e0",
"orientation": {
"package_version": "0.2.6",
"digest": "8d1d6a9d4d07fec7b619618de7dfe2be2254c1790f85f4198c7fe19db9751c02",
"start_source_status": "withheld; source changed",
"source_review": "AGENTS/INTENT/SECURITY-COMPANION and relevant actual validator/acknowledgment/workplan reviewed; no guidance or authority inferred"
},
"tests_passed": 43,
"default11_type_status": "acknowledged",
"explicit12_type_status": "acknowledged",
"both_ordinary_admission_refused": true,
"actual_original_and_staged11_cli_render_bytes_identical": true,
"default11_render_digest": "sha256:e64b56e4590b57b858303fcda1a474693e3dd821bc83df26646fe78fc345498a",
"version12_preparation_render_digest": "sha256:c0afd5576cdae0cbc807eccb2b9fa87ac668f15e32ec7cf79d295bdb23c75b35",
"canonical11_files": {
"security-case-types/oidc-browser-relying-party/README.md": "c01ec1a3f058eab9e6c461e898166d496adb8542ea46f9e198ced4de83b997ef",
"security-case-types/oidc-browser-relying-party/decision/build_intake.py": "d214019a10801e14d7986474ff83370482116df6a0fdde6ecb255d75645b3b04",
"security-case-types/oidc-browser-relying-party/decision/intake.json": "a0ab9c57c71359b9ed31b9d6b04fd464db3e41bb6fe6f39bc6ac0b85556765e0",
"security-case-types/oidc-browser-relying-party/decision/packet.md": "452ea42168ddef64dc5498ba30ae63ff2ff47a3919f55e22f6379d668ea76c28",
"security-case-types/oidc-browser-relying-party/examples/example-public-portal.yaml": "41cc5bb4842209f5f86830d1e8b4b010c6ef06a050c4160faae06a1dfcd9b6e9",
"security-case-types/oidc-browser-relying-party/examples/forgejo.yaml": "96c8dcd4b4182893b3a005137b880c0a80d4aa07a2668b29964e1db33605e050",
"security-case-types/oidc-browser-relying-party/tests/test_oidc_browser_relying_party.py": "7670ad0e415ca9941255bbc111b91fb5c9c3ae41851096d53d85fe7ba1d8660f",
"security-case-types/oidc-browser-relying-party/tests/test_oidc_browser_relying_party_contract.py": "ed30cd00ae22c32fbe2184443a2a8a7775ea5c231d1dd8070f230d547051f4e0",
"security-case-types/oidc-browser-relying-party/type.yaml": "f705c64e4875e9b8e18a912fb51c135e77898926d6a451f5cb74ab3c0cbe4d1c"
},
"historical_accepted11_case_sha256": "bcc2c6d5bae0d28242b3cdb71e74d8ca297c2afe04766b55d53597f86893351d",
"acknowledged12_files": {
"security-case-type-revisions/1.2/oidc-browser-relying-party/README.md": "4c82d9517d423ada75e680a05b0ecc6dfa0bf9f877a92b68f3ab93c1a86e405e",
"security-case-type-revisions/1.2/oidc-browser-relying-party/decision/acknowledgment.json": "2dcc220febf125a17b3769f7014f18b63979d7121e0f7056a2b0d8b1ae6031b0",
"security-case-type-revisions/1.2/oidc-browser-relying-party/examples/example-public-portal.yaml": "8d6457248415daa6fc509da4e838aae0b66d7808e9da8e00f4b85a1e7faed10d",
"security-case-type-revisions/1.2/oidc-browser-relying-party/examples/forgejo.yaml": "1c868c1da7d9077fae97bc6bc029706ec3dc418cfeaae30dc0f92b5b4ba6f2ab",
"security-case-type-revisions/1.2/oidc-browser-relying-party/tests/test_source_contract.py": "4a958eb2d0b90c1ffd08300d3299e4c31c827dcd7fb3cb4c22df91cc29986124",
"security-case-type-revisions/1.2/oidc-browser-relying-party/type.yaml": "9e60ff569fb0d860a2bbde7bbb0991a54d4b0688f62b56df0fa5d161fd624578"
},
"provider_declaration_inputs": {
"/home/worsch/net-kingdom/capabilities/playbooks/net-kingdom.keycape-client-registration.yaml": "cd81e02c283d514492b7a99062c9ede59a7ae124736de1270a121bf0a96a984e"
},
"ordinary_admission_or_type_implemented_or_case_accepted_or_native_completed": false,
"native_or_credentials_accessed": false,
"formal_net_owner_review": false,
"NK_WP_0048_T06_remains_wait": true
},
"commands": [
"PYTHONDONTWRITEBYTECODE=1 python3 -m pytest -p no:cacheprovider /tmp/nk-explicit-version-owner-checkout/net-kingdom/tools/security-case-types/tests -q",
"PYTHONDONTWRITEBYTECODE=1 NET_KINGDOM_ROOT=/tmp/nk-explicit-version-owner-checkout/net-kingdom python3 -m pytest -p no:cacheprovider /tmp/nk-explicit-version-owner-checkout/net-kingdom/security-case-type-revisions/1.2/oidc-browser-relying-party/tests -q"
],
"owner_intent_sha256": "fd2ba8dff9dfd5583ec6d8b0348f273bfe00c59923b5d5d47cd22b1fb4658c54",
"owner_instructions_sha256": "065d47b7bdbfe829672a2a6161a7f8cf243237a1210fb61d82f153fa37f22131",
"existing_owner_workrecord_original_sha256": "a09f12d10bbb6fe89daca687b619f79583836afa9b68664ef1442787975d9065"
}

View file

@ -33,3 +33,20 @@ the type id, and rewrite every section. The template's tests travel with it:
rename `tests/test_type.py` to `tests/test_<type_id>_contract.py` so module names stay unique. rename `tests/test_type.py` to `tests/test_<type_id>_contract.py` so module names stay unique.
Tests: `python3 -m pytest tools/security-case-types/tests security-case-types/*/tests`. Tests: `python3 -m pytest tools/security-case-types/tests security-case-types/*/tests`.
## Explicit source versions
The default --types root is unchanged. Select a separately acknowledged source version explicitly, without moving its package into the default root:
```bash
python3 tools/security-case-types/security_case_validator.py validate-case \
security-case-type-revisions/1.2/oidc-browser-relying-party/examples/forgejo.yaml \
--types security-case-types \
--version-root 1.2=security-case-type-revisions/1.2
```
This ordinary command refuses:1.2 is acknowledged, not implemented. For source-only preparation, the existing --allow-unimplemented option may be added; its render is deterministic and execution.permitted remains false. Current canonical1.1 is also acknowledged and its historical accepted case/normal refusal/preparation render remain exactly retained. No source acknowledgment or historical case acceptance is an implementation/native proof waiver.
The Python API is load_type(default_root, exact_reference, version_roots={"1.2": explicit_root}). Each key must be an exact major.minor and each root a Path. CLI --version-root VERSION=PATH may repeat for DIFFERENT versions. A matching explicit version selects exactly one root; a missing/wrong/ambiguous source refuses without trying the default or another revision. Versions without a mapping continue using --types. No directory discovery, highest/latest selection, status promotion or automatic owner-adapter adoption occurs. Duplicate mappings (even identical), duplicate YAML fields, mismatched source ID/version, malformed/traversal references and selected symlinks refuse. Direct case validation also requires the case reference to match the supplied type ID/version.
An explicit root is source input, not an owner acknowledgment or grant. Callers retain exact source hashes/type/case/render in their own reviewed receipts. Existing two-argument load_type callers, including ops-mason, retain their default behavior until their owner deliberately adopts the optional input. Adapter/global provenance/native first-reference gates remain separate in NK-WP-0048-T06 and the existing ORGREF driver.

View file

@ -361,18 +361,69 @@ def _validate_acceptance(acc: Any, issues: list[Issue]) -> None:
# ── Cases ──────────────────────────────────────────────────────────────────── # ── Cases ────────────────────────────────────────────────────────────────────
def load_type(types_root: Path, ref: str) -> tuple[list[Issue], dict[str, Any] | None, Path | None]: def parse_version_roots(entries: list[str]) -> tuple[list[Issue], dict[str, Path]]:
m = TYPE_REF.fullmatch(ref or "") """Closed explicit selectors; duplicate versions are ambiguous, even same path."""
roots: dict[str, Path] = {}
for entry in entries:
if not isinstance(entry, str):
return [err("types.version_roots", "must be VERSION=PATH entries")], {}
version, separator, path = entry.partition("=")
if not separator or not VERSION.fullmatch(version) or not path or version in roots:
return [err("types.version_roots", "needs unique <major>.<minor>=<path> entries")], {}
roots[version] = Path(path)
return [], roots
class _UniqueTypeLoader(yaml.SafeLoader):
"""A duplicate source field must not choose an implicit winning revision."""
def _unique_type_mapping(loader, node, deep=False):
result = {}
for key_node, value_node in node.value:
key = loader.construct_object(key_node, deep=deep)
if key in result:
raise ValueError("duplicate type source key")
result[key] = loader.construct_object(value_node, deep=deep)
return result
_UniqueTypeLoader.add_constructor(yaml.resolver.BaseResolver.DEFAULT_MAPPING_TAG, _unique_type_mapping)
def load_type(types_root: Path, ref: str, *, version_roots: dict[str, Path] | None = None
) -> tuple[list[Issue], dict[str, Any] | None, Path | None]:
"""Load exactly the named version from one selected root; never search/fallback.
Existing two-argument callers retain their explicit default root. New callers
may name per-version roots. A selection is source input, not implementation,
case acceptance, source acknowledgment or native authority.
"""
m = TYPE_REF.fullmatch(ref if isinstance(ref, str) else "")
if not m: if not m:
return [err("case.spec.type", "must be <type-id>@<major>.<minor>")], None, None return [err("case.spec.type", "must be <type-id>@<major>.<minor>")], None, None
type_dir = types_root / m.group(1) if version_roots is not None and (not isinstance(version_roots, dict) or
any(not isinstance(version, str) or not VERSION.fullmatch(version) or
not isinstance(root, Path) for version, root in version_roots.items())):
return [err("types.version_roots", "must map exact versions to Path roots")], None, None
selected = (version_roots or {}).get(m.group(2), Path(types_root))
type_dir = selected / m.group(1)
type_file = type_dir / "type.yaml" type_file = type_dir / "type.yaml"
if selected.is_symlink() or type_dir.is_symlink() or type_file.is_symlink():
return [err("case.spec.type", "symlinked selected type source refused")], None, None
if not type_file.is_file(): if not type_file.is_file():
return [err("case.spec.type", f"unknown type {m.group(1)!r}")], None, None return [err("case.spec.type", f"unknown type {m.group(1)!r} in selected root")], None, None
data = yaml.safe_load(type_file.read_text()) or {} try:
if str(data.get("spec", {}).get("version")) != m.group(2): data = yaml.load(type_file.read_text(), Loader=_UniqueTypeLoader)
except (OSError, ValueError, TypeError, yaml.YAMLError):
return [err("case.spec.type", "invalid or ambiguous type source")], None, None
if not isinstance(data, dict) or not isinstance(data.get("spec"), dict):
return [err("case.spec.type", "type source needs a spec mapping")], None, None
if data["spec"].get("id") != m.group(1):
return [err("case.spec.type", "selected type source id does not match reference")], None, None
if str(data["spec"].get("version")) != m.group(2):
return [err("case.spec.type", f"type {m.group(1)!r} is at version " return [err("case.spec.type", f"type {m.group(1)!r} is at version "
f"{data.get('spec', {}).get('version')}, not {m.group(2)}")], None, None f"{data['spec'].get('version')}, not {m.group(2)}")], None, None
return [], data, type_dir return [], data, type_dir
@ -391,6 +442,8 @@ def validate_case(case: dict[str, Any], type_data: dict[str, Any], *, require_im
if not owner.get("repo") or not owner.get("person"): if not owner.get("repo") or not owner.get("person"):
issues.append(err("case.spec.owner", "needs repo and person")) issues.append(err("case.spec.owner", "needs repo and person"))
tspec = type_data.get("spec", {}) tspec = type_data.get("spec", {})
if spec.get("type") != f"{tspec.get('id')}@{tspec.get('version')}":
issues.append(err("case.spec.type", "case reference does not match selected type id/version"))
if require_implemented and tspec.get("status") != "implemented": if require_implemented and tspec.get("status") != "implemented":
issues.append(err("case.spec.type", f"type {tspec.get('id')!r} is {tspec.get('status')!r}; " issues.append(err("case.spec.type", f"type {tspec.get('id')!r} is {tspec.get('status')!r}; "
"only implemented types accept cases")) "only implemented types accept cases"))
@ -564,6 +617,8 @@ def main(argv: list[str] | None = None) -> int:
vc = sub.add_parser("validate-case") vc = sub.add_parser("validate-case")
vc.add_argument("case") vc.add_argument("case")
vc.add_argument("--types", required=True) vc.add_argument("--types", required=True)
vc.add_argument("--version-root", action="append", default=[], metavar="VERSION=PATH",
help="select an exact version root; no search or fallback")
vc.add_argument("--allow-unimplemented", action="store_true", vc.add_argument("--allow-unimplemented", action="store_true",
help="validate an example case against a type that is not implemented yet") help="validate an example case against a type that is not implemented yet")
rd = sub.add_parser("render") rd = sub.add_parser("render")
@ -572,6 +627,8 @@ def main(argv: list[str] | None = None) -> int:
rd.add_argument("--repos-root", default=str(TOOL_DIR.parents[2]), rd.add_argument("--repos-root", default=str(TOOL_DIR.parents[2]),
help="directory holding the owning repositories (default: the parent of this checkout)") help="directory holding the owning repositories (default: the parent of this checkout)")
rd.add_argument("--types", required=True) rd.add_argument("--types", required=True)
rd.add_argument("--version-root", action="append", default=[], metavar="VERSION=PATH",
help="select an exact version root; no search or fallback")
rd.add_argument("--allow-unimplemented", action="store_true") rd.add_argument("--allow-unimplemented", action="store_true")
args = ap.parse_args(argv) args = ap.parse_args(argv)
@ -584,7 +641,11 @@ def main(argv: list[str] | None = None) -> int:
return code return code
case = yaml.safe_load(Path(args.case).read_text()) or {} case = yaml.safe_load(Path(args.case).read_text()) or {}
issues, type_data, _ = load_type(Path(args.types), (case.get("spec") or {}).get("type", "")) selection_issues, version_roots = parse_version_roots(args.version_root)
if has_errors(selection_issues):
return _report(selection_issues)
issues, type_data, _ = load_type(Path(args.types), (case.get("spec") or {}).get("type", ""),
version_roots=version_roots)
if type_data is None: if type_data is None:
return _report(issues) return _report(issues)
if args.cmd == "validate-case": if args.cmd == "validate-case":

View file

@ -0,0 +1,114 @@
"""Exact source selection is not type implementation or native admission."""
import copy
import importlib.util
import json
from pathlib import Path
import subprocess
import sys
import pytest
import yaml
TOOL=Path(__file__).resolve().parents[1]
REPO=TOOL.parents[1]
spec=importlib.util.spec_from_file_location('version_selection_validator',TOOL/'security_case_validator.py')
v=importlib.util.module_from_spec(spec);spec.loader.exec_module(v)
DEFAULT=REPO/'security-case-types'
REVISION=REPO/'security-case-type-revisions/1.2'
REF='oidc-browser-relying-party'
CASE11=REPO/'security-cases/forgejo-netkingdom-oidc.yaml'
CASE12=REVISION/REF/'examples/forgejo.yaml'
def load(ref,roots=None):return v.load_type(DEFAULT,ref,version_roots=roots)
def test_version12_selected_exactly_default11_unchanged():
before,data11,path11=load(REF+'@1.1')
after,again,againpath=load(REF+'@1.1',{'1.2':REVISION})
assert not before and not after and data11==again and path11==againpath==DEFAULT/REF
issues,data12,path12=load(REF+'@1.2',{'1.2':REVISION})
assert not issues and data12['spec']['status']=='acknowledged'
assert path12==REVISION/REF and data11['spec']['status']=='acknowledged'
@pytest.mark.parametrize('root',[Path('/nonexistent-type-revision'),DEFAULT])
def test_missing_or_wrong_explicit_version_never_falls_back(root):
issues,data,_=load(REF+'@1.2',{'1.2':root})
assert v.has_errors(issues) and data is None
def test_no_mapping_does_not_discover_acknowledged_package():
issues,data,_=load(REF+'@1.2')
assert data is None and v.has_errors(issues)
@pytest.mark.parametrize('ref',['../oidc-browser-relying-party@1.2',REF+'@../1.2',REF+'@latest',REF+'@1.2/other',None])
def test_traversal_malformed_unpinned_reference_refused(ref):
issues,data,_=load(ref,{'1.2':REVISION})
assert data is None and v.has_errors(issues)
@pytest.mark.parametrize('entries',[['1.2=x','1.2=y'],['1.2=x','1.2=x'],['../1.2=x'],['1.2='],['latest=x'],['1.2']])
def test_ambiguous_or_invalid_root_mapping_refused(entries):
issues,roots=v.parse_version_roots(entries)
assert v.has_errors(issues) and roots=={}
def test_selected_source_id_or_duplicate_revision_fields_refused(tmp_path):
folder=tmp_path/REF;folder.mkdir();source=yaml.safe_load((REVISION/REF/'type.yaml').read_text())
source['spec']['id']='different-id';(folder/'type.yaml').write_text(yaml.safe_dump(source))
issues,data,_=load(REF+'@1.2',{'1.2':tmp_path})
assert data is None and v.has_errors(issues)
(folder/'type.yaml').write_text('spec:\n id: oidc-browser-relying-party\n version: "1.1"\n version: "1.2"\n')
issues,data,_=load(REF+'@1.2',{'1.2':tmp_path})
assert data is None and v.has_errors(issues)
def test_selected_symlink_cannot_escape_root(tmp_path):
(tmp_path/REF).symlink_to(REVISION/REF,target_is_directory=True)
issues,data,_=load(REF+'@1.2',{'1.2':tmp_path})
assert data is None and v.has_errors(issues)
def test_direct_case_validation_cannot_mix_source_pins():
_,type12,_=load(REF+'@1.2',{'1.2':REVISION})
case11=yaml.safe_load(CASE11.read_text())
assert any('does not match selected type' in x.message for x in v.validate_case(case11,type12,require_implemented=False))
_,type11,_=load(REF+'@1.1')
case12=yaml.safe_load(CASE12.read_text())
assert any('does not match selected type' in x.message for x in v.validate_case(case12,type11,require_implemented=False))
def test_acknowledged12_load_does_not_admit_or_render_ordinary_case():
_,type12,_=load(REF+'@1.2',{'1.2':REVISION});case=yaml.safe_load(CASE12.read_text())
assert any('only implemented types accept cases' in x.message for x in v.validate_case(case,type12))
issues,rendered=v.render_case(case,type12,[])
assert rendered is None and v.has_errors(issues)
def cli(*args):return subprocess.run([sys.executable,str(TOOL/'security_case_validator.py'),*map(str,args)],capture_output=True,text=True)
def test_cli_default11_and_mapped12_admission_boundaries():
original=cli('validate-case',CASE11,'--types',DEFAULT)
mapped=cli('validate-case',CASE11,'--types',DEFAULT,'--version-root','1.2='+str(REVISION))
assert original.returncode==mapped.returncode==1 and original.stderr==mapped.stderr and 'only implemented types' in original.stderr
refused=cli('validate-case',CASE12,'--types',DEFAULT,'--version-root','1.2='+str(REVISION))
assert refused.returncode==1 and 'only implemented types' in refused.stderr
preparation=cli('validate-case',CASE12,'--types',DEFAULT,'--version-root','1.2='+str(REVISION),'--allow-unimplemented')
assert preparation.returncode==0
def test_cli_render_source_selection_isolated_deterministic_plan_only():
owners=Path('/home/worsch')
before=cli('render',CASE11,'--types',DEFAULT,'--repos-root',owners,'--allow-unimplemented')
selected11=cli('render',CASE11,'--types',DEFAULT,'--repos-root',owners,'--version-root','1.2='+str(REVISION),'--allow-unimplemented')
assert before.returncode==selected11.returncode==0 and before.stdout==selected11.stdout
after=cli('render',CASE12,'--types',DEFAULT,'--repos-root',owners,'--version-root','1.2='+str(REVISION),'--allow-unimplemented')
direct=cli('render',CASE12,'--types',REVISION,'--repos-root',owners,'--allow-unimplemented')
assert after.returncode==direct.returncode==0 and after.stdout==direct.stdout
a,b=json.loads(before.stdout),json.loads(after.stdout)
assert a['type'].endswith('@1.1') and b['type'].endswith('@1.2') and a['digest']!=b['digest']
assert a['execution']['permitted'] is b['execution']['permitted'] is False
assert b['composition']['execution']['permitted'] is False

View file

@ -162,3 +162,17 @@ canonicalcase acceptance. Requesting coordinator records actual foundergo; no
worker native/credential/grant action. Canonical1.2case remains proposed without worker native/credential/grant action. Canonical1.2case remains proposed without
acceptance; implementation followthrough remains existing NK0048-T06/project acceptance; implementation followthrough remains existing NK0048-T06/project
owner tasks. No new trial, taskT07, broad plan or whole-workplan finish. owner tasks. No new trial, taskT07, broad plan or whole-workplan finish.
Explicit version-selection source followthrough review, same completed owner T06
anchor: reviewed exact six-file source manifest
88238f29ebeceee36f6f6e77bb19cb43837b5bc03bce21c16acc50399b302084.
Independent43 validator/selection tests and22 acknowledged1.2 package compatibility
tests pass in isolated source snapshot. No mappings preserve default1.1; explicit
1.2 loads only its selected exact root with no fallback/discovery, and mismatched
case/type references, duplicate mappings/YAML keys and selected symlinks refuse.
Both types remain acknowledged/unimplemented; historical accepted1.1 case and
its normal refusal remain unchanged. Preparation digests stay e64b56e4.../c0afd557...
and executionfalse. Canonical/acknowledgment hashes verified unchanged; source
loader selection confers no case admission, native adapter/global verifier,
materialization, grant or implemented status. Existing NK0048-T06 remains wait.
No new source-ack trial or broad implementation plan is created.

View file

@ -547,3 +547,15 @@ receipt; no1.1 approval is inherited. Source-recording review/test proof and exa
next gates are in docs/forgejo-type12-source-acknowledgment-followthrough.md. next gates are in docs/forgejo-type12-source-acknowledgment-followthrough.md.
T06 remains wait and T07 remains its existing dependent task; type is NOT T06 remains wait and T07 remains its existing dependent task; type is NOT
implemented and no first-case/native grant or broader workplan finish is asserted. implemented and no first-case/native grant or broader workplan finish is asserted.
2026-10-11 — bounded explicit version selection source preparation for T06:
The validator now offers optional exact version_roots/--version-root VERSION=PATH
selection with duplicate/mismatch/traversal/symlink refusal and no fallback. The
default1.1 source and historicalacceptedcase hashes/ordinaryrefusal/preparation
render stayunchanged; actual1.1 is acknowledged, not implemented. Acknowledged1.2
ordinaryadmission also refuses; optionalpreparation remains nonexecuting.43local
validatorchecks pass and original1.1 CLIrenderbytes match exactly. Makerproof:
docs/evidence/2026-10-11-explicit-type-version-selection/source-integration-validation.json.
FormalNetownerreview/reference-source integration and all existing native adapter,
provenance/broker/materialization/attendedproof gates remain; T06 stayswait, no
new residual or firstcase/typeimplementation/native grant is inferred.