Select explicitly pinned security case type revisions without fallback
Assistant: codex Assistant-Model: gpt-6.1-sol Assistant-Session: 01a126e1-76f8-7643-8cf3-e93336539ade
This commit is contained in:
parent
f6856cdb53
commit
bb9b9b556c
9 changed files with 415 additions and 9 deletions
|
|
@ -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
|
||||
}
|
||||
|
|
@ -11,7 +11,7 @@ immutable. Independent acknowledged-package validator and22 tests pass; closed
|
|||
schema/version load and identical c0afd preparation render are proved.
|
||||
|
||||
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
|
||||
version root loads1.2 through existing --types/--types-root input, only for source
|
||||
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
|
||||
records. This document adds no broad plan, promoted residual or implementation
|
||||
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.
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
}
|
||||
|
|
@ -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"
|
||||
}
|
||||
|
|
@ -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.
|
||||
|
||||
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.
|
||||
|
|
|
|||
|
|
@ -361,18 +361,69 @@ def _validate_acceptance(acc: Any, issues: list[Issue]) -> None:
|
|||
|
||||
# ── Cases ────────────────────────────────────────────────────────────────────
|
||||
|
||||
def load_type(types_root: Path, ref: str) -> tuple[list[Issue], dict[str, Any] | None, Path | None]:
|
||||
m = TYPE_REF.fullmatch(ref or "")
|
||||
def parse_version_roots(entries: list[str]) -> tuple[list[Issue], dict[str, Path]]:
|
||||
"""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:
|
||||
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"
|
||||
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():
|
||||
return [err("case.spec.type", f"unknown type {m.group(1)!r}")], None, None
|
||||
data = yaml.safe_load(type_file.read_text()) or {}
|
||||
if str(data.get("spec", {}).get("version")) != m.group(2):
|
||||
return [err("case.spec.type", f"unknown type {m.group(1)!r} in selected root")], None, None
|
||||
try:
|
||||
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 "
|
||||
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
|
||||
|
||||
|
||||
|
|
@ -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"):
|
||||
issues.append(err("case.spec.owner", "needs repo and person"))
|
||||
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":
|
||||
issues.append(err("case.spec.type", f"type {tspec.get('id')!r} is {tspec.get('status')!r}; "
|
||||
"only implemented types accept cases"))
|
||||
|
|
@ -564,6 +617,8 @@ def main(argv: list[str] | None = None) -> int:
|
|||
vc = sub.add_parser("validate-case")
|
||||
vc.add_argument("case")
|
||||
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",
|
||||
help="validate an example case against a type that is not implemented yet")
|
||||
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]),
|
||||
help="directory holding the owning repositories (default: the parent of this checkout)")
|
||||
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")
|
||||
args = ap.parse_args(argv)
|
||||
|
||||
|
|
@ -584,7 +641,11 @@ def main(argv: list[str] | None = None) -> int:
|
|||
return code
|
||||
|
||||
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:
|
||||
return _report(issues)
|
||||
if args.cmd == "validate-case":
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
@ -162,3 +162,17 @@ canonicalcase acceptance. Requesting coordinator records actual foundergo; no
|
|||
worker native/credential/grant action. Canonical1.2case remains proposed without
|
||||
acceptance; implementation followthrough remains existing NK0048-T06/project
|
||||
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.
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
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.
|
||||
|
||||
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.
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue