diff --git a/docs/changes/netkingdom-target-2026-10-17/nk48-consumer-compatibility-r1/apply_refinement.py b/docs/changes/netkingdom-target-2026-10-17/nk48-consumer-compatibility-r1/apply_refinement.py new file mode 100644 index 00000000..0eb93c9b --- /dev/null +++ b/docs/changes/netkingdom-target-2026-10-17/nk48-consumer-compatibility-r1/apply_refinement.py @@ -0,0 +1,64 @@ +"""Record proved compatibility findings and the real remaining owner work.""" +from pathlib import Path +import hashlib +import json +import re +import subprocess +import sys + +HERE = Path(__file__).resolve().parent +ROOT = Path('/home/worsch/net-kingdom') +PLAN = 'workplans/NK-WP-0048-security-case-types.md' +BASE = 'befb0bc68cd25b625804fce559476eefd702521d' + + +def sha(raw): + return hashlib.sha256(raw).hexdigest() + + +def git(*args): + return subprocess.check_output(['git', '-C', str(ROOT), *args], + stderr=subprocess.PIPE, timeout=40).decode().strip() + + +assert git('rev-parse', 'HEAD') == BASE +assert git('ls-remote', 'origin', 'refs/heads/main').split()[0] == BASE +before = (ROOT / PLAN).read_bytes() +assert sha(before) == '80279bbdf6d36e2321ecfeb5a3f996a8345e6da2ceb9b26167e52b3668ba7e85' +blocks = re.findall(rb'```task\n.*?```', before, re.S) +assert len(blocks) == 35 +assert b'id: NK-WP-0048-T36' not in before and b'id: NK-WP-0048-T37' not in before +dirty = set(git('diff', '--name-only').splitlines() + git('diff', '--cached', '--name-only').splitlines()) +assert dirty == {'.custodian-brief.md', 'WORK-RECORDS.md'} +originals = {p: {'worktree': sha((ROOT / p).read_bytes()), 'index': git('ls-files', '--stage', '--', p)} for p in dirty} +sys.path.insert(0, '/home/worsch/repo-manager/src') +from repo_manager.identifiers import ensure_missing_work_record_identifiers +preview = ensure_missing_work_record_identifiers(ROOT, execute=False) +assert preview['ok'] and not preview['assignments'] and not preview['files_changed'] +(ROOT / PLAN).write_bytes(before + (HERE / 'task-additions.md').read_bytes()) +preview = ensure_missing_work_record_identifiers(ROOT, execute=False) +(HERE / 'identifier-preview.json').write_text(json.dumps(preview, indent=2) + '\n') +assert preview['ok'] and preview['files_changed'] == [PLAN] +assert {r['record_id'] for r in preview['assignments']} == {'NK-WP-0048-T36', 'NK-WP-0048-T37'} +assigned = ensure_missing_work_record_identifiers(ROOT, execute=True) +(HERE / 'identifier-assignment.json').write_text(json.dumps(assigned, indent=2) + '\n') +after = (ROOT / PLAN).read_bytes() +assert re.findall(rb'```task\n.*?```', after, re.S)[:35] == blocks +assert len(re.findall(rb'```task\n.*?```', after, re.S)) == 37 +for p, old in originals.items(): + assert sha((ROOT / p).read_bytes()) == old['worktree'] + assert git('ls-files', '--stage', '--', p) == old['index'] +files = [{'path': PLAN, 'sha256': sha(after)}] +(HERE / 'publication-inputs.json').write_text(json.dumps({ + 'owner': 'net-kingdom', 'base_revision': BASE, + 'message': 'Record actual Forgejo chart compatibility and preserve exact any-of gate work', + 'files': files}, indent=2) + '\n') +(HERE / 'source-refinement.json').write_text(json.dumps({ + 'base': BASE, 'files': files, 'original_task_blocks_preserved': 35, + 'source_task_count': 37, 'new_tasks': {'NK-WP-0048-T36': 'done', 'NK-WP-0048-T37': 'progress'}, + 'unrelated_state': originals, 'new_ids': assigned['assignments'], + 'knowledge': {'version': '0.2.6', 'digest': 'sha256:8d1d6a9d4d07fec7b619618de7dfe2be2254c1790f85f4198c7fe19db9751c02', + 'status': 'withheld/orientation-only; actual INTENT and SECURITY-COMPANION reviewed'}, + 'native_or_credential_actions': False}, indent=2) + '\n') +print(json.dumps({'status': 'source_refined', 'original_task_blocks_preserved': 35, + 'source_task_count': 37, 'normal_allocator_assignments': len(assigned['assignments'])})) diff --git a/docs/changes/netkingdom-target-2026-10-17/nk48-consumer-compatibility-r1/consistency.json b/docs/changes/netkingdom-target-2026-10-17/nk48-consumer-compatibility-r1/consistency.json new file mode 100644 index 00000000..2f967903 --- /dev/null +++ b/docs/changes/netkingdom-target-2026-10-17/nk48-consumer-compatibility-r1/consistency.json @@ -0,0 +1,354 @@ +{ + "repo_slug": "net-kingdom", + "repo_path": "/home/worsch/net-kingdom", + "source_revision": "0241070b914d62b6ee483d61d587ee9a8e7152f0", + "source_revision_reason": null, + "issues": [ + { + "severity": "WARN", + "check_id": "C-24", + "message": "Repo classification advisory: capability_tag 'security' is not a recommended family tag (allowed, check for synonym)", + "file_path": "", + "db_id": "", + "file_value": "", + "db_value": "", + "fixable": false + }, + { + "severity": "WARN", + "check_id": "C-35", + "message": "Repo Manager warning: workplans/: repository uses more than one workplan prefix: ['NET-WP', 'NK-WP']", + "file_path": "workplans/", + "db_id": "", + "file_value": "", + "db_value": "", + "fixable": false + }, + { + "severity": "WARN", + "check_id": "C-33", + "message": "WORK-RECORDS.md is missing or stale \u2014 regenerate via fix-consistency (CUST-WP-0061-T04 generated work-record index)", + "file_path": "WORK-RECORDS.md", + "db_id": "", + "file_value": "", + "db_value": "", + "fixable": true + }, + { + "severity": "WARN", + "check_id": "C-37", + "message": "Residual workplan 'nk-wp-0011' has no origin/origin_ref provenance (not invented)", + "file_path": "workplans/NK-WP-0011-enterprise-federation-saml.md", + "db_id": "1075448f-d533-5f9e-94b7-c3adfe151a07", + "file_value": "", + "db_value": "", + "fixable": false + }, + { + "severity": "WARN", + "check_id": "C-20", + "message": "Workplan dependency target 'USER-WP-0020' is not linked to State Hub", + "file_path": "workplans/NK-WP-0022-railiance01-identity-cutover-and-coulombcore-retirement.md", + "db_id": "", + "file_value": "USER-WP-0020", + "db_value": "", + "fixable": false + }, + { + "severity": "WARN", + "check_id": "C-20", + "message": "Workplan dependency target 'KEY-WP-0004' is not linked to State Hub", + "file_path": "workplans/NK-WP-0022-railiance01-identity-cutover-and-coulombcore-retirement.md", + "db_id": "", + "file_value": "KEY-WP-0004", + "db_value": "", + "fixable": false + }, + { + "severity": "WARN", + "check_id": "C-20", + "message": "Workplan dependency target 'USER-WP-0020' is not linked to State Hub", + "file_path": "workplans/NK-WP-0023-user-engine-portal-platform-integration.md", + "db_id": "", + "file_value": "USER-WP-0020", + "db_value": "", + "fixable": false + }, + { + "severity": "WARN", + "check_id": "C-20", + "message": "Workplan dependency target 'KEY-WP-0004' is not linked to State Hub", + "file_path": "workplans/NK-WP-0023-user-engine-portal-platform-integration.md", + "db_id": "", + "file_value": "KEY-WP-0004", + "db_value": "", + "fixable": false + }, + { + "severity": "WARN", + "check_id": "C-20", + "message": "Workplan dependency target 'USER-WP-0021' is not linked to State Hub", + "file_path": "workplans/NK-WP-0024-user-engine-portal-integration-expansion.md", + "db_id": "", + "file_value": "USER-WP-0021", + "db_value": "", + "fixable": false + }, + { + "severity": "WARN", + "check_id": "C-20", + "message": "Workplan dependency target 'FLEX-WP-0009' is not linked to State Hub", + "file_path": "workplans/NK-WP-0024-user-engine-portal-integration-expansion.md", + "db_id": "", + "file_value": "FLEX-WP-0009", + "db_value": "", + "fixable": false + }, + { + "severity": "WARN", + "check_id": "C-20", + "message": "Workplan dependency target 'AUDIT-WP-0003' is not linked to State Hub", + "file_path": "workplans/NK-WP-0024-user-engine-portal-integration-expansion.md", + "db_id": "", + "file_value": "AUDIT-WP-0003", + "db_value": "", + "fixable": false + }, + { + "severity": "WARN", + "check_id": "C-20", + "message": "Workplan dependency target 'EMAIL-WP-0004' is not linked to State Hub", + "file_path": "workplans/NK-WP-0024-user-engine-portal-integration-expansion.md", + "db_id": "", + "file_value": "EMAIL-WP-0004", + "db_value": "", + "fixable": false + }, + { + "severity": "WARN", + "check_id": "C-20", + "message": "Workplan dependency target 'USER-WP-0022' is not linked to State Hub", + "file_path": "workplans/NK-WP-0025-public-self-registration-and-application-jit.md", + "db_id": "", + "file_value": "USER-WP-0022", + "db_value": "", + "fixable": false + }, + { + "severity": "WARN", + "check_id": "C-20", + "message": "Workplan dependency target 'KEY-WP-0008' is not linked to State Hub", + "file_path": "workplans/NK-WP-0025-public-self-registration-and-application-jit.md", + "db_id": "", + "file_value": "KEY-WP-0008", + "db_value": "", + "fixable": false + }, + { + "severity": "WARN", + "check_id": "C-20", + "message": "Workplan dependency target 'CSOC-WP-0003' is not linked to State Hub", + "file_path": "workplans/NK-WP-0025-public-self-registration-and-application-jit.md", + "db_id": "", + "file_value": "CSOC-WP-0003", + "db_value": "", + "fixable": false + }, + { + "severity": "WARN", + "check_id": "C-37", + "message": "Residual workplan 'nk-wp-0027' has no origin/origin_ref provenance (not invented)", + "file_path": "workplans/NK-WP-0027-reef-placement-reconciliation.md", + "db_id": "965ad365-6b81-50a1-a2a3-2d0c1fcce0b4", + "file_value": "", + "db_value": "", + "fixable": false + }, + { + "severity": "WARN", + "check_id": "C-37", + "message": "Residual workplan 'nk-wp-0031' has no origin/origin_ref provenance (not invented)", + "file_path": "workplans/NK-WP-0031-deterministic-posture-feedback.md", + "db_id": "9d7b04f9-3803-5613-b7a5-8bd606c77f5a", + "file_value": "", + "db_value": "", + "fixable": false + }, + { + "severity": "WARN", + "check_id": "C-37", + "message": "Residual workplan 'nk-wp-0040' has no origin/origin_ref provenance (not invented)", + "file_path": "workplans/NK-WP-0040-execution-attribution-receipt.md", + "db_id": "e2533f3a-aa43-59b3-bff3-8e64b6149487", + "file_value": "", + "db_value": "", + "fixable": false + }, + { + "severity": "WARN", + "check_id": "C-40", + "message": "blocker satisfied, task still wait: 'NK-WP-0048-T15' in workplan 'NK-WP-0048' \u2014 every depends_on target is terminal (NK-WP-0048-T31)", + "file_path": "workplans/NK-WP-0048-security-case-types.md#NK-WP-0048-T15", + "db_id": "", + "file_value": "NK-WP-0048-T31", + "db_value": "", + "fixable": false + }, + { + "severity": "WARN", + "check_id": "C-11", + "message": "Derived task 'NK-WP-0048-T36' is absent from this projection", + "file_path": "workplans/NK-WP-0048-security-case-types.md#NK-WP-0048-T36", + "db_id": "7ba71abd-3c85-53ce-9837-7b0653ec709c", + "file_value": "", + "db_value": "", + "fixable": true + }, + { + "severity": "WARN", + "check_id": "C-11", + "message": "Derived task 'NK-WP-0048-T37' is absent from this projection", + "file_path": "workplans/NK-WP-0048-security-case-types.md#NK-WP-0048-T37", + "db_id": "e493fb4b-52d2-54d4-a732-b42242855b3e", + "file_value": "", + "db_value": "", + "fixable": true + }, + { + "severity": "WARN", + "check_id": "C-20", + "message": "Workplan dependency target 'RAIL-PL-WP-0002' is not linked to State Hub", + "file_path": "workplans/archived/260702-NET-WP-0017-it-security-readiness-for-user-onboarding.md", + "db_id": "", + "file_value": "RAIL-PL-WP-0002", + "db_value": "", + "fixable": false + }, + { + "severity": "INFO", + "check_id": "C-08", + "message": "Retired DB workstream 'nk-wp-0035@retired-20260908' (id=064c5e8b\u2026, status=archived) has no backing workplan file \u2014 projection tombstone, not live work", + "file_path": "", + "db_id": "064c5e8b-7852-4d7f-a5fa-e26e406ab8e0", + "file_value": "", + "db_value": "", + "fixable": false + }, + { + "severity": "INFO", + "check_id": "C-08", + "message": "Retired DB workstream 'adhoc-2026-08-14@retired-20260827' (id=a5097121\u2026, status=finished) has no backing workplan file \u2014 projection tombstone, not live work", + "file_path": "", + "db_id": "a5097121-1461-59f8-81fa-e6ecef527337", + "file_value": "", + "db_value": "", + "fixable": false + }, + { + "severity": "INFO", + "check_id": "C-08", + "message": "Retired DB workstream 'adhoc-2026-08-23@retired-20260827' (id=e25ba5fa\u2026, status=finished) has no backing workplan file \u2014 projection tombstone, not live work", + "file_path": "", + "db_id": "e25ba5fa-eaae-5039-8d93-a69c6d837fc4", + "file_value": "", + "db_value": "", + "fixable": false + }, + { + "severity": "INFO", + "check_id": "C-08", + "message": "Retired DB workstream 'net-kingdom-adhoc-2026-07-02@retired-20260827' (id=67c1c7ac\u2026, status=finished) has no backing workplan file \u2014 projection tombstone, not live work", + "file_path": "", + "db_id": "67c1c7ac-d8b1-41dc-a81a-4e43f1afd068", + "file_value": "", + "db_value": "", + "fixable": false + }, + { + "severity": "INFO", + "check_id": "C-08", + "message": "Retired DB workstream 'net-kingdom-adhoc-2026-06-14@retired-20260827' (id=c6f3d6bf\u2026, status=finished) has no backing workplan file \u2014 projection tombstone, not live work", + "file_path": "", + "db_id": "c6f3d6bf-4916-490d-96ce-092d2776d7e7", + "file_value": "", + "db_value": "", + "fixable": false + }, + { + "severity": "WARN", + "check_id": "C-26", + "message": "Workplan id 'NET-WP-0020' uses prefix 'NET-WP' but repo canonical prefix is 'NK-WP' \u2014 new plans must conform", + "file_path": "workplans/NET-WP-0020-openbao-unseal-custody-and-ssh-automation.md", + "db_id": "", + "file_value": "NET-WP-0020", + "db_value": "NK-WP", + "fixable": false + }, + { + "severity": "WARN", + "check_id": "C-26", + "message": "Workplan id 'NET-WP-0015' uses prefix 'NET-WP' but repo canonical prefix is 'NK-WP' \u2014 new plans must conform", + "file_path": "workplans/archived/260603-NET-WP-0015-platform-root-custody-and-openbao-identity-bootstrap.md", + "db_id": "", + "file_value": "NET-WP-0015", + "db_value": "NK-WP", + "fixable": false + }, + { + "severity": "WARN", + "check_id": "C-26", + "message": "Workplan id 'NET-WP-0016' uses prefix 'NET-WP' but repo canonical prefix is 'NK-WP' \u2014 new plans must conform", + "file_path": "workplans/archived/260603-NET-WP-0016-guided-security-bootstrap-experience.md", + "db_id": "", + "file_value": "NET-WP-0016", + "db_value": "NK-WP", + "fixable": false + }, + { + "severity": "WARN", + "check_id": "C-26", + "message": "Workplan id 'NET-WP-0018' uses prefix 'NET-WP' but repo canonical prefix is 'NK-WP' \u2014 new plans must conform", + "file_path": "workplans/archived/260604-NET-WP-0018-bootstrap-automation-and-rebuild-readiness.md", + "db_id": "", + "file_value": "NET-WP-0018", + "db_value": "NK-WP", + "fixable": false + }, + { + "severity": "WARN", + "check_id": "C-26", + "message": "Workplan id 'NET-WP-0017' uses prefix 'NET-WP' but repo canonical prefix is 'NK-WP' \u2014 new plans must conform", + "file_path": "workplans/archived/260702-NET-WP-0017-it-security-readiness-for-user-onboarding.md", + "db_id": "", + "file_value": "NET-WP-0017", + "db_value": "NK-WP", + "fixable": false + }, + { + "severity": "WARN", + "check_id": "C-26", + "message": "Workplan id 'NET-WP-0019' uses prefix 'NET-WP' but repo canonical prefix is 'NK-WP' \u2014 new plans must conform", + "file_path": "workplans/archived/260702-NET-WP-0019-t06-adjacent-user-lifecycle-dry-run-polish.md", + "db_id": "", + "file_value": "NET-WP-0019", + "db_value": "NK-WP", + "fixable": false + } + ], + "fixes_applied": [ + "bindings: synced 64/64", + "C-17 cleared: pushed 1 backlogged commit(s)", + "C-33 fixed: regenerated WORK-RECORDS.md", + "C-11 fixed: task 'NK-WP-0048-T36' \u2192 7ba71abd\u2026", + "C-11 fixed: task 'NK-WP-0048-T37' \u2192 e493fb4b\u2026", + "brief: .custodian-brief.md updated", + "push: pushed" + ], + "summary": { + "fail": 0, + "automation_error": 0, + "warn": 28, + "info": 5 + }, + "result": "warn" +} diff --git a/docs/changes/netkingdom-target-2026-10-17/nk48-consumer-compatibility-r1/consistency.stderr.txt b/docs/changes/netkingdom-target-2026-10-17/nk48-consumer-compatibility-r1/consistency.stderr.txt new file mode 100644 index 00000000..3345563f --- /dev/null +++ b/docs/changes/netkingdom-target-2026-10-17/nk48-consumer-compatibility-r1/consistency.stderr.txt @@ -0,0 +1 @@ +WARN: git commit failed for writeback: Generated-only commits rejected: staged diff --git a/docs/changes/netkingdom-target-2026-10-17/nk48-consumer-compatibility-r1/identifier-assignment.json b/docs/changes/netkingdom-target-2026-10-17/nk48-consumer-compatibility-r1/identifier-assignment.json new file mode 100644 index 00000000..4decf880 --- /dev/null +++ b/docs/changes/netkingdom-target-2026-10-17/nk48-consumer-compatibility-r1/identifier-assignment.json @@ -0,0 +1,26 @@ +{ + "schema": "repo-manager.identifier-assignment.v1", + "ok": true, + "executed": true, + "repo": "net-kingdom", + "namespace": "helixforge", + "assignments": [ + { + "kind": "task", + "record_id": "NK-WP-0048-T36", + "uuid": "7ba71abd-3c85-53ce-9837-7b0653ec709c", + "path": "workplans/NK-WP-0048-security-case-types.md" + }, + { + "kind": "task", + "record_id": "NK-WP-0048-T37", + "uuid": "e493fb4b-52d2-54d4-a732-b42242855b3e", + "path": "workplans/NK-WP-0048-security-case-types.md" + } + ], + "historical_invalid_identifiers": [], + "historical_identity_collisions": [], + "files_changed": [ + "workplans/NK-WP-0048-security-case-types.md" + ] +} diff --git a/docs/changes/netkingdom-target-2026-10-17/nk48-consumer-compatibility-r1/identifier-preview.json b/docs/changes/netkingdom-target-2026-10-17/nk48-consumer-compatibility-r1/identifier-preview.json new file mode 100644 index 00000000..772fe43b --- /dev/null +++ b/docs/changes/netkingdom-target-2026-10-17/nk48-consumer-compatibility-r1/identifier-preview.json @@ -0,0 +1,26 @@ +{ + "schema": "repo-manager.identifier-assignment.v1", + "ok": true, + "executed": false, + "repo": "net-kingdom", + "namespace": "helixforge", + "assignments": [ + { + "kind": "task", + "record_id": "NK-WP-0048-T36", + "uuid": "7ba71abd-3c85-53ce-9837-7b0653ec709c", + "path": "workplans/NK-WP-0048-security-case-types.md" + }, + { + "kind": "task", + "record_id": "NK-WP-0048-T37", + "uuid": "e493fb4b-52d2-54d4-a732-b42242855b3e", + "path": "workplans/NK-WP-0048-security-case-types.md" + } + ], + "historical_invalid_identifiers": [], + "historical_identity_collisions": [], + "files_changed": [ + "workplans/NK-WP-0048-security-case-types.md" + ] +} diff --git a/docs/changes/netkingdom-target-2026-10-17/platform-delivery-review-r1/externalsecret-v1-schema.json b/docs/changes/netkingdom-target-2026-10-17/platform-delivery-review-r1/externalsecret-v1-schema.json new file mode 100644 index 00000000..1dc4ab64 --- /dev/null +++ b/docs/changes/netkingdom-target-2026-10-17/platform-delivery-review-r1/externalsecret-v1-schema.json @@ -0,0 +1,667 @@ +{ + "description": "ExternalSecret is the Schema for the external-secrets API.", + "properties": { + "apiVersion": { + "description": "APIVersion defines the versioned schema of this representation of an object.\nServers should convert recognized schemas to the latest internal value, and\nmay reject unrecognized values.\nMore info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources", + "type": "string" + }, + "kind": { + "description": "Kind is a string value representing the REST resource this object represents.\nServers may infer this from the endpoint the client submits requests to.\nCannot be updated.\nIn CamelCase.\nMore info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds", + "type": "string" + }, + "metadata": { + "type": "object" + }, + "spec": { + "description": "ExternalSecretSpec defines the desired state of ExternalSecret.", + "properties": { + "data": { + "description": "Data defines the connection between the Kubernetes Secret keys and the Provider data", + "items": { + "description": "ExternalSecretData defines the connection between the Kubernetes Secret key (spec.data.) and the Provider data.", + "properties": { + "remoteRef": { + "description": "RemoteRef points to the remote secret and defines\nwhich secret (version/property/..) to fetch.", + "properties": { + "conversionStrategy": { + "default": "Default", + "description": "Used to define a conversion Strategy", + "enum": [ + "Default", + "Unicode" + ], + "type": "string" + }, + "decodingStrategy": { + "default": "None", + "description": "Used to define a decoding Strategy", + "enum": [ + "Auto", + "Base64", + "Base64URL", + "None" + ], + "type": "string" + }, + "key": { + "description": "Key is the key used in the Provider, mandatory", + "type": "string" + }, + "metadataPolicy": { + "default": "None", + "description": "Policy for fetching tags/labels from provider secrets, possible options are Fetch, None. Defaults to None", + "enum": [ + "None", + "Fetch" + ], + "type": "string" + }, + "property": { + "description": "Used to select a specific property of the Provider value (if a map), if supported", + "type": "string" + }, + "version": { + "description": "Used to select a specific version of the Provider value, if supported", + "type": "string" + } + }, + "required": [ + "key" + ], + "type": "object" + }, + "secretKey": { + "description": "The key in the Kubernetes Secret to store the value.", + "maxLength": 253, + "minLength": 1, + "pattern": "^[-._a-zA-Z0-9]+$", + "type": "string" + }, + "sourceRef": { + "description": "SourceRef allows you to override the source\nfrom which the value will be pulled.", + "maxProperties": 1, + "minProperties": 1, + "properties": { + "generatorRef": { + "description": "GeneratorRef points to a generator custom resource.\n\nDeprecated: The generatorRef is not implemented in .data[].\nthis will be removed with v1.", + "properties": { + "apiVersion": { + "default": "generators.external-secrets.io/v1alpha1", + "description": "Specify the apiVersion of the generator resource", + "type": "string" + }, + "kind": { + "description": "Specify the Kind of the generator resource", + "enum": [ + "ACRAccessToken", + "ClusterGenerator", + "ECRAuthorizationToken", + "Fake", + "GCRAccessToken", + "GithubAccessToken", + "QuayAccessToken", + "Password", + "STSSessionToken", + "UUID", + "VaultDynamicSecret", + "Webhook", + "Grafana" + ], + "type": "string" + }, + "name": { + "description": "Specify the name of the generator resource", + "maxLength": 253, + "minLength": 1, + "pattern": "^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$", + "type": "string" + } + }, + "required": [ + "kind", + "name" + ], + "type": "object" + }, + "storeRef": { + "description": "SecretStoreRef defines which SecretStore to fetch the ExternalSecret data.", + "properties": { + "kind": { + "description": "Kind of the SecretStore resource (SecretStore or ClusterSecretStore)\nDefaults to `SecretStore`", + "enum": [ + "SecretStore", + "ClusterSecretStore" + ], + "type": "string" + }, + "name": { + "description": "Name of the SecretStore resource", + "maxLength": 253, + "minLength": 1, + "pattern": "^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$", + "type": "string" + } + }, + "type": "object" + } + }, + "type": "object" + } + }, + "required": [ + "remoteRef", + "secretKey" + ], + "type": "object" + }, + "type": "array" + }, + "dataFrom": { + "description": "DataFrom is used to fetch all properties from a specific Provider data\nIf multiple entries are specified, the Secret keys are merged in the specified order", + "items": { + "properties": { + "extract": { + "description": "Used to extract multiple key/value pairs from one secret\nNote: Extract does not support sourceRef.Generator or sourceRef.GeneratorRef.", + "properties": { + "conversionStrategy": { + "default": "Default", + "description": "Used to define a conversion Strategy", + "enum": [ + "Default", + "Unicode" + ], + "type": "string" + }, + "decodingStrategy": { + "default": "None", + "description": "Used to define a decoding Strategy", + "enum": [ + "Auto", + "Base64", + "Base64URL", + "None" + ], + "type": "string" + }, + "key": { + "description": "Key is the key used in the Provider, mandatory", + "type": "string" + }, + "metadataPolicy": { + "default": "None", + "description": "Policy for fetching tags/labels from provider secrets, possible options are Fetch, None. Defaults to None", + "enum": [ + "None", + "Fetch" + ], + "type": "string" + }, + "property": { + "description": "Used to select a specific property of the Provider value (if a map), if supported", + "type": "string" + }, + "version": { + "description": "Used to select a specific version of the Provider value, if supported", + "type": "string" + } + }, + "required": [ + "key" + ], + "type": "object" + }, + "find": { + "description": "Used to find secrets based on tags or regular expressions\nNote: Find does not support sourceRef.Generator or sourceRef.GeneratorRef.", + "properties": { + "conversionStrategy": { + "default": "Default", + "description": "Used to define a conversion Strategy", + "enum": [ + "Default", + "Unicode" + ], + "type": "string" + }, + "decodingStrategy": { + "default": "None", + "description": "Used to define a decoding Strategy", + "enum": [ + "Auto", + "Base64", + "Base64URL", + "None" + ], + "type": "string" + }, + "name": { + "description": "Finds secrets based on the name.", + "properties": { + "regexp": { + "description": "Finds secrets base", + "type": "string" + } + }, + "type": "object" + }, + "path": { + "description": "A root path to start the find operations.", + "type": "string" + }, + "tags": { + "additionalProperties": { + "type": "string" + }, + "description": "Find secrets based on tags.", + "type": "object" + } + }, + "type": "object" + }, + "rewrite": { + "description": "Used to rewrite secret Keys after getting them from the secret Provider\nMultiple Rewrite operations can be provided. They are applied in a layered order (first to last)", + "items": { + "properties": { + "regexp": { + "description": "Used to rewrite with regular expressions.\nThe resulting key will be the output of a regexp.ReplaceAll operation.", + "properties": { + "source": { + "description": "Used to define the regular expression of a re.Compiler.", + "type": "string" + }, + "target": { + "description": "Used to define the target pattern of a ReplaceAll operation.", + "type": "string" + } + }, + "required": [ + "source", + "target" + ], + "type": "object" + }, + "transform": { + "description": "Used to apply string transformation on the secrets.\nThe resulting key will be the output of the template applied by the operation.", + "properties": { + "template": { + "description": "Used to define the template to apply on the secret name.\n`.value ` will specify the secret name in the template.", + "type": "string" + } + }, + "required": [ + "template" + ], + "type": "object" + } + }, + "type": "object" + }, + "type": "array" + }, + "sourceRef": { + "description": "SourceRef points to a store or generator\nwhich contains secret values ready to use.\nUse this in combination with Extract or Find pull values out of\na specific SecretStore.\nWhen sourceRef points to a generator Extract or Find is not supported.\nThe generator returns a static map of values", + "maxProperties": 1, + "minProperties": 1, + "properties": { + "generatorRef": { + "description": "GeneratorRef points to a generator custom resource.", + "properties": { + "apiVersion": { + "default": "generators.external-secrets.io/v1alpha1", + "description": "Specify the apiVersion of the generator resource", + "type": "string" + }, + "kind": { + "description": "Specify the Kind of the generator resource", + "enum": [ + "ACRAccessToken", + "ClusterGenerator", + "ECRAuthorizationToken", + "Fake", + "GCRAccessToken", + "GithubAccessToken", + "QuayAccessToken", + "Password", + "STSSessionToken", + "UUID", + "VaultDynamicSecret", + "Webhook", + "Grafana" + ], + "type": "string" + }, + "name": { + "description": "Specify the name of the generator resource", + "maxLength": 253, + "minLength": 1, + "pattern": "^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$", + "type": "string" + } + }, + "required": [ + "kind", + "name" + ], + "type": "object" + }, + "storeRef": { + "description": "SecretStoreRef defines which SecretStore to fetch the ExternalSecret data.", + "properties": { + "kind": { + "description": "Kind of the SecretStore resource (SecretStore or ClusterSecretStore)\nDefaults to `SecretStore`", + "enum": [ + "SecretStore", + "ClusterSecretStore" + ], + "type": "string" + }, + "name": { + "description": "Name of the SecretStore resource", + "maxLength": 253, + "minLength": 1, + "pattern": "^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$", + "type": "string" + } + }, + "type": "object" + } + }, + "type": "object" + } + }, + "type": "object" + }, + "type": "array" + }, + "refreshInterval": { + "default": "1h", + "description": "RefreshInterval is the amount of time before the values are read again from the SecretStore provider,\nspecified as Golang Duration strings.\nValid time units are \"ns\", \"us\" (or \"\u00b5s\"), \"ms\", \"s\", \"m\", \"h\"\nExample values: \"1h\", \"2h30m\", \"10s\"\nMay be set to zero to fetch and create it once. Defaults to 1h.", + "type": "string" + }, + "refreshPolicy": { + "description": "RefreshPolicy determines how the ExternalSecret should be refreshed:\n- CreatedOnce: Creates the Secret only if it does not exist and does not update it thereafter\n- Periodic: Synchronizes the Secret from the external source at regular intervals specified by refreshInterval.\n No periodic updates occur if refreshInterval is 0.\n- OnChange: Only synchronizes the Secret when the ExternalSecret's metadata or specification changes", + "enum": [ + "CreatedOnce", + "Periodic", + "OnChange" + ], + "type": "string" + }, + "secretStoreRef": { + "description": "SecretStoreRef defines which SecretStore to fetch the ExternalSecret data.", + "properties": { + "kind": { + "description": "Kind of the SecretStore resource (SecretStore or ClusterSecretStore)\nDefaults to `SecretStore`", + "enum": [ + "SecretStore", + "ClusterSecretStore" + ], + "type": "string" + }, + "name": { + "description": "Name of the SecretStore resource", + "maxLength": 253, + "minLength": 1, + "pattern": "^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$", + "type": "string" + } + }, + "type": "object" + }, + "target": { + "default": { + "creationPolicy": "Owner", + "deletionPolicy": "Retain" + }, + "description": "ExternalSecretTarget defines the Kubernetes Secret to be created\nThere can be only one target per ExternalSecret.", + "properties": { + "creationPolicy": { + "default": "Owner", + "description": "CreationPolicy defines rules on how to create the resulting Secret.\nDefaults to \"Owner\"", + "enum": [ + "Owner", + "Orphan", + "Merge", + "None" + ], + "type": "string" + }, + "deletionPolicy": { + "default": "Retain", + "description": "DeletionPolicy defines rules on how to delete the resulting Secret.\nDefaults to \"Retain\"", + "enum": [ + "Delete", + "Merge", + "Retain" + ], + "type": "string" + }, + "immutable": { + "description": "Immutable defines if the final secret will be immutable", + "type": "boolean" + }, + "name": { + "description": "The name of the Secret resource to be managed.\nDefaults to the .metadata.name of the ExternalSecret resource", + "maxLength": 253, + "minLength": 1, + "pattern": "^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$", + "type": "string" + }, + "template": { + "description": "Template defines a blueprint for the created Secret resource.", + "properties": { + "data": { + "additionalProperties": { + "type": "string" + }, + "type": "object" + }, + "engineVersion": { + "default": "v2", + "description": "EngineVersion specifies the template engine version\nthat should be used to compile/execute the\ntemplate specified in .data and .templateFrom[].", + "enum": [ + "v2" + ], + "type": "string" + }, + "mergePolicy": { + "default": "Replace", + "enum": [ + "Replace", + "Merge" + ], + "type": "string" + }, + "metadata": { + "description": "ExternalSecretTemplateMetadata defines metadata fields for the Secret blueprint.", + "properties": { + "annotations": { + "additionalProperties": { + "type": "string" + }, + "type": "object" + }, + "labels": { + "additionalProperties": { + "type": "string" + }, + "type": "object" + } + }, + "type": "object" + }, + "templateFrom": { + "items": { + "properties": { + "configMap": { + "properties": { + "items": { + "description": "A list of keys in the ConfigMap/Secret to use as templates for Secret data", + "items": { + "properties": { + "key": { + "description": "A key in the ConfigMap/Secret", + "maxLength": 253, + "minLength": 1, + "pattern": "^[-._a-zA-Z0-9]+$", + "type": "string" + }, + "templateAs": { + "default": "Values", + "enum": [ + "Values", + "KeysAndValues" + ], + "type": "string" + } + }, + "required": [ + "key" + ], + "type": "object" + }, + "type": "array" + }, + "name": { + "description": "The name of the ConfigMap/Secret resource", + "maxLength": 253, + "minLength": 1, + "pattern": "^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$", + "type": "string" + } + }, + "required": [ + "items", + "name" + ], + "type": "object" + }, + "literal": { + "type": "string" + }, + "secret": { + "properties": { + "items": { + "description": "A list of keys in the ConfigMap/Secret to use as templates for Secret data", + "items": { + "properties": { + "key": { + "description": "A key in the ConfigMap/Secret", + "maxLength": 253, + "minLength": 1, + "pattern": "^[-._a-zA-Z0-9]+$", + "type": "string" + }, + "templateAs": { + "default": "Values", + "enum": [ + "Values", + "KeysAndValues" + ], + "type": "string" + } + }, + "required": [ + "key" + ], + "type": "object" + }, + "type": "array" + }, + "name": { + "description": "The name of the ConfigMap/Secret resource", + "maxLength": 253, + "minLength": 1, + "pattern": "^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$", + "type": "string" + } + }, + "required": [ + "items", + "name" + ], + "type": "object" + }, + "target": { + "default": "Data", + "enum": [ + "Data", + "Annotations", + "Labels" + ], + "type": "string" + } + }, + "type": "object" + }, + "type": "array" + }, + "type": { + "type": "string" + } + }, + "type": "object" + } + }, + "type": "object" + } + }, + "type": "object" + }, + "status": { + "properties": { + "binding": { + "description": "Binding represents a servicebinding.io Provisioned Service reference to the secret", + "properties": { + "name": { + "default": "", + "description": "Name of the referent.\nThis field is effectively required, but due to backwards compatibility is\nallowed to be empty. Instances of this type with an empty value here are\nalmost certainly wrong.\nMore info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names", + "type": "string" + } + }, + "type": "object", + "x-kubernetes-map-type": "atomic" + }, + "conditions": { + "items": { + "properties": { + "lastTransitionTime": { + "format": "date-time", + "type": "string" + }, + "message": { + "type": "string" + }, + "reason": { + "type": "string" + }, + "status": { + "type": "string" + }, + "type": { + "type": "string" + } + }, + "required": [ + "status", + "type" + ], + "type": "object" + }, + "type": "array" + }, + "refreshTime": { + "description": "refreshTime is the time and date the external secret was fetched and\nthe target secret updated", + "format": "date-time", + "nullable": true, + "type": "string" + }, + "syncedResourceVersion": { + "description": "SyncedResourceVersion keeps track of the last synced version", + "type": "string" + } + }, + "type": "object" + } + }, + "type": "object" +} diff --git a/docs/changes/netkingdom-target-2026-10-17/platform-delivery-review-r1/independent-defect-reproductions.log b/docs/changes/netkingdom-target-2026-10-17/platform-delivery-review-r1/independent-defect-reproductions.log new file mode 100644 index 00000000..4f4e964b --- /dev/null +++ b/docs/changes/netkingdom-target-2026-10-17/platform-delivery-review-r1/independent-defect-reproductions.log @@ -0,0 +1,17 @@ +test_conditional_delete_preserves_uid_and_rv (__main__.IndependentReview.test_conditional_delete_preserves_uid_and_rv) ... ok +test_existing_no_change_is_not_one_shot_admission (__main__.IndependentReview.test_existing_no_change_is_not_one_shot_admission) ... ok +test_never_dispatch_secret_or_store_mutations (__main__.IndependentReview.test_never_dispatch_secret_or_store_mutations) ... ok +test_production_refuses_without_any_dispatch (__main__.IndependentReview.test_production_refuses_without_any_dispatch) ... ok +test_reproduce_api_defaulted_create_response_rejected (__main__.IndependentReview.test_reproduce_api_defaulted_create_response_rejected) ... ok +test_reproduce_get_version_uses_discovery_not_explicit_v1_endpoint (__main__.IndependentReview.test_reproduce_get_version_uses_discovery_not_explicit_v1_endpoint) ... ok +test_reproduce_malformed_metadata_escapes_fixed_error_envelope (__main__.IndependentReview.test_reproduce_malformed_metadata_escapes_fixed_error_envelope) ... ok +test_reproduce_missing_freshness_fields_accepted (__main__.IndependentReview.test_reproduce_missing_freshness_fields_accepted) ... ok +test_reproduce_no_store_read_after_initial_validation (__main__.IndependentReview.test_reproduce_no_store_read_after_initial_validation) ... ok +test_reproduce_post_capture_limit_and_unbounded_stderr (__main__.IndependentReview.test_reproduce_post_capture_limit_and_unbounded_stderr) ... ok +test_reproduce_stale_ready_accepted_for_current_spec (__main__.IndependentReview.test_reproduce_stale_ready_accepted_for_current_spec) ... ok +test_restart_and_lost_create_never_gain_delete_provenance (__main__.IndependentReview.test_restart_and_lost_create_never_gain_delete_provenance) ... ok + +---------------------------------------------------------------------- +Ran 12 tests in 0.088s + +OK diff --git a/docs/changes/netkingdom-target-2026-10-17/platform-delivery-review-r1/original-command-tests.log b/docs/changes/netkingdom-target-2026-10-17/platform-delivery-review-r1/original-command-tests.log new file mode 100644 index 00000000..0a807f17 --- /dev/null +++ b/docs/changes/netkingdom-target-2026-10-17/platform-delivery-review-r1/original-command-tests.log @@ -0,0 +1,22 @@ +test_absent_create_ready_and_uid_rv_precondition_delete (test_keycape_issuer_delivery.DeliveryTests.test_absent_create_ready_and_uid_rv_precondition_delete) ... ok +test_conflicting_existing_untouched (test_keycape_issuer_delivery.DeliveryTests.test_conflicting_existing_untouched) ... ok +test_create_lost_response_or_race_uncertain_no_retry (test_keycape_issuer_delivery.DeliveryTests.test_create_lost_response_or_race_uncertain_no_retry) ... ok +test_delete_response_loss_and_conflict_uncertain (test_keycape_issuer_delivery.DeliveryTests.test_delete_response_loss_and_conflict_uncertain) ... ok +test_delete_unconfirmed_or_replaced_never_repeated (test_keycape_issuer_delivery.DeliveryTests.test_delete_unconfirmed_or_replaced_never_repeated) ... ok +test_drift_and_replacement_never_deleted (test_keycape_issuer_delivery.DeliveryTests.test_drift_and_replacement_never_deleted) ... ok +test_forged_revision_and_fresh_process_have_no_delete_provenance (test_keycape_issuer_delivery.DeliveryTests.test_forged_revision_and_fresh_process_have_no_delete_provenance) ... ok +test_get_error_not_absence_and_output_contained (test_keycape_issuer_delivery.DeliveryTests.test_get_error_not_absence_and_output_contained) ... ok +test_malformed_create_or_poll_never_returns_success (test_keycape_issuer_delivery.DeliveryTests.test_malformed_create_or_poll_never_returns_success) ... ok +test_matching_existing_no_change_never_delete (test_keycape_issuer_delivery.DeliveryTests.test_matching_existing_no_change_never_delete) ... ok +test_missing_drifted_unready_store_never_create (test_keycape_issuer_delivery.DeliveryTests.test_missing_drifted_unready_store_never_create) ... ok +test_no_caller_paths_or_injection (test_keycape_issuer_delivery.DeliveryTests.test_no_caller_paths_or_injection) ... ok +test_poll_timeout_contains_provider_details (test_keycape_issuer_delivery.DeliveryTests.test_poll_timeout_contains_provider_details) ... ok +test_production_factory_fails_before_dispatch (test_keycape_issuer_delivery.DeliveryTests.test_production_factory_fails_before_dispatch) ... ok +test_readiness_updates_resource_version (test_keycape_issuer_delivery.DeliveryTests.test_readiness_updates_resource_version) ... ok +test_ready_poll_is_bounded_and_created_provenance_retained (test_keycape_issuer_delivery.DeliveryTests.test_ready_poll_is_bounded_and_created_provenance_retained) ... ok +test_replacement_during_ready_not_adopted (test_keycape_issuer_delivery.DeliveryTests.test_replacement_during_ready_not_adopted) ... ok + +---------------------------------------------------------------------- +Ran 17 tests in 0.187s + +OK diff --git a/docs/changes/netkingdom-target-2026-10-17/platform-delivery-review-r1/original-contract-tests.log b/docs/changes/netkingdom-target-2026-10-17/platform-delivery-review-r1/original-contract-tests.log new file mode 100644 index 00000000..450a3885 --- /dev/null +++ b/docs/changes/netkingdom-target-2026-10-17/platform-delivery-review-r1/original-contract-tests.log @@ -0,0 +1,13 @@ +test_delivery_document_is_only_external_and_detached (test_keycape_issuer_eso.FixedIssuerContract.test_delivery_document_is_only_external_and_detached) ... ok +test_external_drift_refuses (test_keycape_issuer_eso.FixedIssuerContract.test_external_drift_refuses) ... ok +test_independent_forgejo_binding (test_keycape_issuer_eso.FixedIssuerContract.test_independent_forgejo_binding) ... ok +test_missing_or_unready_refuses (test_keycape_issuer_eso.FixedIssuerContract.test_missing_or_unready_refuses) ... ok +test_no_caller_store_or_path_override (test_keycape_issuer_eso.FixedIssuerContract.test_no_caller_store_or_path_override) ... ok +test_store_drift_refuses (test_keycape_issuer_eso.FixedIssuerContract.test_store_drift_refuses) ... ok +test_type_admitted_trailing_dash_is_preserved (test_keycape_issuer_eso.FixedIssuerContract.test_type_admitted_trailing_dash_is_preserved) ... ok +test_valid_ready_metadata_allows_extra_controller_metadata (test_keycape_issuer_eso.FixedIssuerContract.test_valid_ready_metadata_allows_extra_controller_metadata) ... ok + +---------------------------------------------------------------------- +Ran 8 tests in 0.242s + +OK diff --git a/docs/changes/netkingdom-target-2026-10-17/platform-delivery-review-r1/partial-defaults-fix.patch b/docs/changes/netkingdom-target-2026-10-17/platform-delivery-review-r1/partial-defaults-fix.patch new file mode 100644 index 00000000..08ffaaeb --- /dev/null +++ b/docs/changes/netkingdom-target-2026-10-17/platform-delivery-review-r1/partial-defaults-fix.patch @@ -0,0 +1,59 @@ +--- a/scripts/keycape_issuer_eso.py ++++ b/scripts/keycape_issuer_eso.py +@@ -37,6 +37,23 @@ + return deepcopy(documents(client_id)[1]) + + ++def canonical_external_spec(spec): ++ """Normalize only the three pinned ESO v0.16.1 remoteRef defaults. ++ ++ Omitted and explicit defaults are equivalent; all other fields remain exact. ++ This says nothing about freshness, admission authority or Store continuity. ++ """ ++ value = deepcopy(spec) ++ if isinstance(value, dict) and isinstance(value.get('data'), list): ++ for item in value['data']: ++ if isinstance(item, dict) and isinstance(item.get('remoteRef'), dict): ++ for name, default in (('conversionStrategy', 'Default'), ++ ('decodingStrategy', 'None'), ++ ('metadataPolicy', 'None')): ++ item['remoteRef'].setdefault(name, default) ++ return value ++ ++ + def _matching_public_resource(observed: dict, expected: dict) -> None: + if not isinstance(observed, dict): + raise Refused('public resource observation required') +@@ -50,7 +67,10 @@ + or observed.get('kind') != expected['kind'] + or metadata.get('name') != expected['metadata']['name'] + or not namespace_matches +- or observed.get('spec') != expected['spec']): ++ or (canonical_external_spec(observed.get('spec')) ++ != canonical_external_spec(expected['spec']) ++ if expected['kind'] == 'ExternalSecret' ++ else observed.get('spec') != expected['spec'])): + raise Refused('fixed issuer resource differs') + status = observed.get('status') + conditions = status.get('conditions', []) if isinstance(status, dict) else [] +--- a/scripts/keycape_issuer_delivery.py ++++ b/scripts/keycape_issuer_delivery.py +@@ -11,7 +11,7 @@ + import re + import time + +-from keycape_issuer_eso import documents, require_fixed_store, Refused ++from keycape_issuer_eso import documents, require_fixed_store, Refused, canonical_external_spec + from state_hub_preflight_lane import command, LaneError + + POLL_COUNT = 10 +@@ -90,7 +90,8 @@ + or metadata.get('name') != self.external['metadata']['name'] + or metadata.get('namespace') != 'sso' + or metadata.get('deletionTimestamp') +- or obj.get('spec') != self.external['spec']): ++ or canonical_external_spec(obj.get('spec')) ++ != canonical_external_spec(self.external['spec'])): + raise Refused('delivery_conflict') + return _revision(obj) + diff --git a/docs/changes/netkingdom-target-2026-10-17/platform-delivery-review-r1/partial-fix-source/scripts/keycape_issuer_delivery.py b/docs/changes/netkingdom-target-2026-10-17/platform-delivery-review-r1/partial-fix-source/scripts/keycape_issuer_delivery.py new file mode 100644 index 00000000..dfe021b9 --- /dev/null +++ b/docs/changes/netkingdom-target-2026-10-17/platform-delivery-review-r1/partial-fix-source/scripts/keycape_issuer_delivery.py @@ -0,0 +1,183 @@ +"""RPF-1.3-T4 concrete source-preparation mechanics; NO production admission. + +Only public ESO objects are fetched. The production factory refuses before +dispatch: no authenticated delivery session/evidence service currently exists. +Private mechanics are exercised by fake-command tests, never a grant boundary. +""" +from __future__ import annotations + +from copy import deepcopy +import json +import re +import time + +from keycape_issuer_eso import documents, require_fixed_store, Refused, canonical_external_spec +from state_hub_preflight_lane import command, LaneError + +POLL_COUNT = 10 +POLL_DELAY = 1 +API = '/apis/external-secrets.io/v1' + + +class Uncertain(Refused): + """No success receipt: retain exact metadata for owner reconciliation.""" + def __init__(self, reason, revision=None): + super().__init__(reason) + self.revision = deepcopy(revision) + + +def production_bindings(*args, **kwargs): + # Neither Warden invocation fields, an operator's kubeconfig, an unsigned + # local receipt nor mint-lane lookup-self authenticates THIS lane/caller. + raise Refused('delivery_admission_session_journal_and_evidence_unavailable') + + +def _revision(obj): + meta = obj.get('metadata', {}) + result = {'uid': meta.get('uid'), 'resource_version': meta.get('resourceVersion')} + if any(not isinstance(x, str) or not re.fullmatch(r'[A-Za-z0-9._:-]{1,192}', x) + for x in result.values()): + raise Refused('public_revision_invalid') + return result + + +class _DeliveryMechanics: + """Concrete fixed kubectl algorithm, not an authenticated owner capability. + + No caller resource, remoteRef, namespace, store or path argument is accepted. + The deliberately unauthenticated transport cannot load ambient credentials. + A reviewed session transport must replace it only after real lane admission, + one-shot durable journaling, evidence and revocation are implemented. + """ + def __init__(self, client_id): + self.client_id = client_id + self.store, self.external = documents(client_id) + self.created = None + self.pending = False + + def _call(self, args, payload=None): + try: + result = command(['kubectl', '--kubeconfig=/dev/null', + '--request-timeout=5s', *args], + payload=payload, env={}) + if len(result.stdout) > 262144: + raise Refused('public_response_too_large') + if not result.stdout.strip(): + return None + value = json.loads(result.stdout) + if not isinstance(value, dict): + raise Refused('public_response_shape') + return value + except (LaneError, OSError, ValueError, TimeoutError): + raise Refused('public_command_failed') from None + except Exception: + # Includes subprocess timeouts; never expose stdout/stderr or argv. + raise Refused('public_command_failed') from None + + def _get(self, store=False): + obj = self.store if store else self.external + resource = 'clustersecretstores.external-secrets.io' if store else 'externalsecrets.external-secrets.io' + args = [] if store else ['-n', 'sso'] + return self._call([*args, 'get', resource, obj['metadata']['name'], + '--ignore-not-found=true', '-o', 'json']) + + def _match(self, obj): + if not isinstance(obj, dict): + raise Refused('delivery_missing') + metadata = obj.get('metadata', {}) + if (obj.get('apiVersion') != self.external['apiVersion'] + or obj.get('kind') != 'ExternalSecret' + or metadata.get('name') != self.external['metadata']['name'] + or metadata.get('namespace') != 'sso' + or metadata.get('deletionTimestamp') + or canonical_external_spec(obj.get('spec')) + != canonical_external_spec(self.external['spec'])): + raise Refused('delivery_conflict') + return _revision(obj) + + @staticmethod + def _ready(obj): + status = obj.get('status', {}) + conditions = status.get('conditions', []) if isinstance(status, dict) else [] + return isinstance(conditions, list) and any( + isinstance(c, dict) and c.get('type') == 'Ready' and c.get('status') == 'True' + for c in conditions) + + def _receipt(self, operation, before, after): + return {'schema': 'netkingdom.deliver-secret.v1', 'operation': operation, + 'namespace': 'sso', 'name': self.external['metadata']['name'], + 'custody_reference': 'platform/workloads/keycape/' + self.client_id, + 'key_names': ['client-secret'], 'values_emitted': False, + 'changed': before is None if operation == 'deliver' else True, + 'before': before, 'after': after} + + def deliver(self): + if self.pending or self.created is not None: + raise Refused('attempt_already_used_or_unreconciled') + # A successful empty response proves absence; errors never do. + store = self._get(store=True) + require_fixed_store(store, self.client_id) + if store.get('metadata', {}).get('deletionTimestamp'): + raise Refused('store_terminating') + old = self._get() + before = self._match(old) if old is not None else None + uid = before['uid'] if before else None + if old is None: + # Set before dispatch: a lost create response MUST NOT be retried, + # adopted, or claimed as a pre-existing no-change result. + self.pending = True + try: + created = self._call(['-n', 'sso', 'create', '-f', '-', '-o', 'json'], self.external) + self.created = self._match(created) + uid = self.created['uid'] + except Refused: + raise Uncertain('create_outcome_unverified') from None + try: + for index in range(POLL_COUNT): + current = self._get() + after = self._match(current) + if after['uid'] != uid: + raise Refused('delivery_replaced') + if self._ready(current): + if self.created is not None: + self.created = after + self.pending = False + return self._receipt('deliver', before, after) + if index + 1 < POLL_COUNT: + time.sleep(POLL_DELAY) + raise Refused('delivery_ready_timeout') + except Refused: + if self.created is not None: + raise Uncertain('created_delivery_requires_reconciliation', self.created) from None + raise + + def remove(self, revision): + # An arbitrary adapter argument alone is never deletion provenance. + if self.pending or self.created is None or revision != self.created: + raise Refused('no_verified_case_creation') + try: + if self._match(self._get()) != self.created: + raise Refused('created_revision_changed') + except Refused: + raise Uncertain('created_revision_changed', self.created) from None + uri = API + '/namespaces/sso/externalsecrets/' + self.external['metadata']['name'] + options = {'apiVersion': 'v1', 'kind': 'DeleteOptions', + 'preconditions': {'uid': self.created['uid'], + 'resourceVersion': self.created['resource_version']}} + prior = deepcopy(self.created) + self.pending = True + try: + self._call(['delete', '--raw', uri, '-f', '-'], options) + for index in range(POLL_COUNT): + current = self._get() + if current is None: + self.created = None + self.pending = False + return self._receipt('remove', prior, None) + if _revision(current)['uid'] != prior['uid']: + raise Refused('replacement_preserved') + if index + 1 < POLL_COUNT: + time.sleep(POLL_DELAY) + raise Refused('delete_unconfirmed') + except Refused: + raise Uncertain('cleanup_requires_reconciliation', prior) from None diff --git a/docs/changes/netkingdom-target-2026-10-17/platform-delivery-review-r1/partial-fix-source/scripts/keycape_issuer_eso.py b/docs/changes/netkingdom-target-2026-10-17/platform-delivery-review-r1/partial-fix-source/scripts/keycape_issuer_eso.py new file mode 100644 index 00000000..08cd23e5 --- /dev/null +++ b/docs/changes/netkingdom-target-2026-10-17/platform-delivery-review-r1/partial-fix-source/scripts/keycape_issuer_eso.py @@ -0,0 +1,90 @@ +"""Proposed fixed issuer ESO source contract for RPF-1.3-T4 / NK48-R5-F2. + +Pure public metadata only. No API/credential/Secret access or apply path. This +new declaration requires independent Platform review before type integration. +The delivery lane may use only external_secret(); store provisioning is a +separate owner action and is never delegated by this module. +""" +from __future__ import annotations + +from copy import deepcopy +from pathlib import Path +import re + +import yaml + +ROOT = Path(__file__).resolve().parents[1] +TEMPLATE = ROOT / 'manifests/templates/keycape-issuer-eso.yaml' + + +class Refused(ValueError): + """Fixed issuer metadata differs; never return an owner delivery receipt.""" + + +def documents(client_id: str) -> tuple[dict, dict]: + # Match the frozen type client_id pattern and existing adapter's derived bound. + # No caller can supply a store name/kind/namespace or an OpenBao path. + if not isinstance(client_id, str) or not re.fullmatch(r'[a-z0-9][a-z0-9-]{1,62}', client_id): + raise Refused('bounded admitted client_id required') + if len('keycape-' + client_id + '-client') > 63: + raise Refused('derived issuer reference exceeds existing adapter bound') + store, external = yaml.safe_load_all(TEMPLATE.read_text().format(client_id=client_id)) + return store, external + + +def external_secret(client_id: str) -> dict: + """The ONLY document the proposed delivery capability may apply.""" + return deepcopy(documents(client_id)[1]) + + +def canonical_external_spec(spec): + """Normalize only the three pinned ESO v0.16.1 remoteRef defaults. + + Omitted and explicit defaults are equivalent; all other fields remain exact. + This says nothing about freshness, admission authority or Store continuity. + """ + value = deepcopy(spec) + if isinstance(value, dict) and isinstance(value.get('data'), list): + for item in value['data']: + if isinstance(item, dict) and isinstance(item.get('remoteRef'), dict): + for name, default in (('conversionStrategy', 'Default'), + ('decodingStrategy', 'None'), + ('metadataPolicy', 'None')): + item['remoteRef'].setdefault(name, default) + return value + + +def _matching_public_resource(observed: dict, expected: dict) -> None: + if not isinstance(observed, dict): + raise Refused('public resource observation required') + metadata = observed.get('metadata') + if not isinstance(metadata, dict): + raise Refused('resource metadata required') + expected_namespace = expected['metadata'].get('namespace') + namespace_matches = (metadata.get('namespace') == expected_namespace + if expected_namespace else metadata.get('namespace') in (None, '')) + if (observed.get('apiVersion') != expected['apiVersion'] + or observed.get('kind') != expected['kind'] + or metadata.get('name') != expected['metadata']['name'] + or not namespace_matches + or (canonical_external_spec(observed.get('spec')) + != canonical_external_spec(expected['spec']) + if expected['kind'] == 'ExternalSecret' + else observed.get('spec') != expected['spec'])): + raise Refused('fixed issuer resource differs') + status = observed.get('status') + conditions = status.get('conditions', []) if isinstance(status, dict) else [] + if not isinstance(conditions, list) or not any( + isinstance(c, dict) and c.get('type') == 'Ready' and c.get('status') == 'True' + for c in conditions): + raise Refused('fixed issuer resource not ready') + + +def require_fixed_store(observed: dict, client_id: str) -> None: + """Refuse a missing/drifted/not-ready store; never create it through delivery.""" + _matching_public_resource(observed, documents(client_id)[0]) + + +def require_ready_delivery(observed: dict, client_id: str) -> None: + """Spec equality and Ready precede a created/NO_CHANGE owner receipt.""" + _matching_public_resource(observed, documents(client_id)[1]) diff --git a/docs/changes/netkingdom-target-2026-10-17/platform-delivery-review-r1/partial-fix-tests.log b/docs/changes/netkingdom-target-2026-10-17/platform-delivery-review-r1/partial-fix-tests.log new file mode 100644 index 00000000..17c56f01 --- /dev/null +++ b/docs/changes/netkingdom-target-2026-10-17/platform-delivery-review-r1/partial-fix-tests.log @@ -0,0 +1,34 @@ +test_absent_create_ready_and_uid_rv_precondition_delete (test_keycape_issuer_delivery.DeliveryTests.test_absent_create_ready_and_uid_rv_precondition_delete) ... ok +test_conflicting_existing_untouched (test_keycape_issuer_delivery.DeliveryTests.test_conflicting_existing_untouched) ... ok +test_create_lost_response_or_race_uncertain_no_retry (test_keycape_issuer_delivery.DeliveryTests.test_create_lost_response_or_race_uncertain_no_retry) ... ok +test_delete_response_loss_and_conflict_uncertain (test_keycape_issuer_delivery.DeliveryTests.test_delete_response_loss_and_conflict_uncertain) ... ok +test_delete_unconfirmed_or_replaced_never_repeated (test_keycape_issuer_delivery.DeliveryTests.test_delete_unconfirmed_or_replaced_never_repeated) ... ok +test_drift_and_replacement_never_deleted (test_keycape_issuer_delivery.DeliveryTests.test_drift_and_replacement_never_deleted) ... ok +test_forged_revision_and_fresh_process_have_no_delete_provenance (test_keycape_issuer_delivery.DeliveryTests.test_forged_revision_and_fresh_process_have_no_delete_provenance) ... ok +test_get_error_not_absence_and_output_contained (test_keycape_issuer_delivery.DeliveryTests.test_get_error_not_absence_and_output_contained) ... ok +test_malformed_create_or_poll_never_returns_success (test_keycape_issuer_delivery.DeliveryTests.test_malformed_create_or_poll_never_returns_success) ... ok +test_matching_existing_no_change_never_delete (test_keycape_issuer_delivery.DeliveryTests.test_matching_existing_no_change_never_delete) ... ok +test_missing_drifted_unready_store_never_create (test_keycape_issuer_delivery.DeliveryTests.test_missing_drifted_unready_store_never_create) ... ok +test_no_caller_paths_or_injection (test_keycape_issuer_delivery.DeliveryTests.test_no_caller_paths_or_injection) ... ok +test_poll_timeout_contains_provider_details (test_keycape_issuer_delivery.DeliveryTests.test_poll_timeout_contains_provider_details) ... ok +test_production_factory_fails_before_dispatch (test_keycape_issuer_delivery.DeliveryTests.test_production_factory_fails_before_dispatch) ... ok +test_readiness_updates_resource_version (test_keycape_issuer_delivery.DeliveryTests.test_readiness_updates_resource_version) ... ok +test_ready_poll_is_bounded_and_created_provenance_retained (test_keycape_issuer_delivery.DeliveryTests.test_ready_poll_is_bounded_and_created_provenance_retained) ... ok +test_replacement_during_ready_not_adopted (test_keycape_issuer_delivery.DeliveryTests.test_replacement_during_ready_not_adopted) ... ok +test_delivery_document_is_only_external_and_detached (test_keycape_issuer_eso.FixedIssuerContract.test_delivery_document_is_only_external_and_detached) ... ok +test_external_drift_refuses (test_keycape_issuer_eso.FixedIssuerContract.test_external_drift_refuses) ... ok +test_independent_forgejo_binding (test_keycape_issuer_eso.FixedIssuerContract.test_independent_forgejo_binding) ... ok +test_missing_or_unready_refuses (test_keycape_issuer_eso.FixedIssuerContract.test_missing_or_unready_refuses) ... ok +test_no_caller_store_or_path_override (test_keycape_issuer_eso.FixedIssuerContract.test_no_caller_store_or_path_override) ... ok +test_store_drift_refuses (test_keycape_issuer_eso.FixedIssuerContract.test_store_drift_refuses) ... ok +test_type_admitted_trailing_dash_is_preserved (test_keycape_issuer_eso.FixedIssuerContract.test_type_admitted_trailing_dash_is_preserved) ... ok +test_valid_ready_metadata_allows_extra_controller_metadata (test_keycape_issuer_eso.FixedIssuerContract.test_valid_ready_metadata_allows_extra_controller_metadata) ... ok +test_defaulted_response_matches_both_checks (test_partial_defaults_fix.DefaultNormalization.test_defaulted_response_matches_both_checks) ... ok +test_nondefault_values_refused (test_partial_defaults_fix.DefaultNormalization.test_nondefault_values_refused) ... ok +test_normalization_does_not_mutate_document (test_partial_defaults_fix.DefaultNormalization.test_normalization_does_not_mutate_document) ... ok +test_unready_and_wrong_identity_still_refused (test_partial_defaults_fix.DefaultNormalization.test_unready_and_wrong_identity_still_refused) ... ok + +---------------------------------------------------------------------- +Ran 29 tests in 0.460s + +OK diff --git a/docs/changes/netkingdom-target-2026-10-17/platform-delivery-review-r1/partial-patch-check.log b/docs/changes/netkingdom-target-2026-10-17/platform-delivery-review-r1/partial-patch-check.log new file mode 100644 index 00000000..e69de29b diff --git a/docs/changes/netkingdom-target-2026-10-17/platform-delivery-review-r1/runs.json b/docs/changes/netkingdom-target-2026-10-17/platform-delivery-review-r1/runs.json new file mode 100644 index 00000000..80b7049d --- /dev/null +++ b/docs/changes/netkingdom-target-2026-10-17/platform-delivery-review-r1/runs.json @@ -0,0 +1,69 @@ +{ + "environment": { + "HOME": "/tmp/platform-delivery-independent-review/empty-home", + "PATH": "/usr/bin:/bin", + "PYTHONDONTWRITEBYTECODE": "1", + "PYTHONPATH": "/home/worsch/railiance-platform/scripts" + }, + "network": "Test suites intercept command dispatch; independent tests and command suite forbid socket construction. Helm render used only cached archive. No live commands or credentials.", + "runs": [ + { + "name": "original-command-tests", + "argv": [ + "/usr/bin/python3", + "-m", + "unittest", + "discover", + "-s", + "/home/worsch/the-custodian/docs/changes/netkingdom-target-2026-10-17/platform-delivery-backend-r1/prepared-source/tests", + "-v" + ], + "exit_code": 0, + "log": "original-command-tests.log" + }, + { + "name": "original-contract-tests", + "argv": [ + "/usr/bin/python3", + "-m", + "unittest", + "discover", + "-s", + "/home/worsch/railiance-platform/tests", + "-p", + "test_keycape_issuer_eso.py", + "-v" + ], + "exit_code": 0, + "log": "original-contract-tests.log" + }, + { + "name": "independent-defect-reproductions", + "argv": [ + "/usr/bin/python3", + "/home/worsch/the-custodian/docs/changes/netkingdom-target-2026-10-17/platform-delivery-review-r1/test_independent_review.py" + ], + "exit_code": 0, + "log": "independent-defect-reproductions.log" + }, + { + "name": "partial-fix-tests", + "argv": [ + "/usr/bin/python3", + "-m", + "unittest", + "discover", + "-s", + "/tmp/platform-delivery-independent-review/partial-fixed/tests", + "-v" + ], + "exit_code": 0, + "log": "partial-fix-tests.log" + }, + { + "name": "partial-patch-check-on-r1-tree", + "exit_code": 0, + "log": "partial-patch-check.log" + } + ] +} diff --git a/docs/changes/netkingdom-target-2026-10-17/platform-delivery-review-r1/source-pins.json b/docs/changes/netkingdom-target-2026-10-17/platform-delivery-review-r1/source-pins.json new file mode 100644 index 00000000..08a9bf58 --- /dev/null +++ b/docs/changes/netkingdom-target-2026-10-17/platform-delivery-review-r1/source-pins.json @@ -0,0 +1,26 @@ +{ + "platform_head": "git:104ed90f42a9a86bf9fda343b49b65a85c9ee4b9", + "input_sha256": { + "/home/worsch/the-custodian/docs/changes/netkingdom-target-2026-10-17/platform-delivery-backend-r1/railiance-platform.patch": "sha256:9b9b5a81bfc65693112e136fff44ce98c35a91151dc645659d80e6a5b2da86ca", + "/home/worsch/the-custodian/docs/changes/netkingdom-target-2026-10-17/platform-delivery-backend-r1/prepared-source/scripts/keycape_issuer_delivery.py": "sha256:bd0fbbbdd0b08037e0d39e76f458e143663afa7e1e4ea9697639863c74e27ef7", + "/home/worsch/railiance-platform/scripts/keycape_issuer_eso.py": "sha256:21c5797280c2a7825dbda8ac970d34a0cdef4c6458f68bce0b1c12407f122a87", + "/home/worsch/railiance-platform/scripts/state_hub_preflight_lane.py": "sha256:8bf63beda028de7c2375084afb7c22fb00a731c54ebf0b3ba369a699d920e0f5", + "/home/worsch/.cache/helm/repository/external-secrets-0.16.1.tgz": "sha256:848189f301b16e16765d62cae93cf3a05ab32b674ce47bbab58cc1db0e0102d6" + }, + "artifact_sha256": { + "README.md": "sha256:11e7d8d18d291f609ad98c4d2c033343005061d155a2f5c1ad3482d309e59653", + "clustersecretstore-v1-schema.json": "sha256:f74b1c186ed40fd7e4af47941fb5ec48e70e5fa66631ef1307e943b0773baf00", + "externalsecret-v1-schema.json": "sha256:22f69a2d2c64d1b4887766194b74e9543ec7b2e24f4696f9d7e0838b428ae2a7", + "independent-defect-reproductions.log": "sha256:6608fcaee88f89acf73b3a42fa0da4af42cfaca2c85426616c8103062fe3dbd6", + "original-command-tests.log": "sha256:2d75ce8f96f7d410fa2cf82b695bbce0235449fefa81430de71c0291a1fe3af2", + "original-contract-tests.log": "sha256:a60cbc658cabf862267757db7d303fa678e7a7ca4fe58aa2f2ba933e654451a7", + "partial-defaults-fix.patch": "sha256:1f2da9690e1c61871f7db8f885c90152cd7a68e392f5bcc62fcc8e0f408f8d4e", + "partial-fix-source/scripts/keycape_issuer_delivery.py": "sha256:f740564866f2c71efc034eb1c4f844308a301499c2f67e7629a9f588a76cc84a", + "partial-fix-source/scripts/keycape_issuer_eso.py": "sha256:e0164a39c4a51ebbea66c38c2d42880675e242edb3920b2a2d362f365dee7c67", + "partial-fix-tests.log": "sha256:7cee0c9848167e8417202a0832de5e3ea72db2b36d83ac33a8df932711988f1b", + "partial-patch-check.log": "sha256:e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "runs.json": "sha256:727665c0b005d01b3cd02ede816939d7a643d0fbe481ab05d90d1fce670a479a", + "test_independent_review.py": "sha256:6caa6686b85e22461a2fdc1f9893fb11ed074540ebf62a18226f744a169f386e", + "test_partial_defaults_fix.py": "sha256:7c6489103a223a42fe8a31c8d54a38dd066f2ec38db5e18454309b6185c253c4" + } +} diff --git a/docs/changes/netkingdom-target-2026-10-17/platform-delivery-review-r1/test_independent_review.py b/docs/changes/netkingdom-target-2026-10-17/platform-delivery-review-r1/test_independent_review.py new file mode 100644 index 00000000..5a3a7525 --- /dev/null +++ b/docs/changes/netkingdom-target-2026-10-17/platform-delivery-review-r1/test_independent_review.py @@ -0,0 +1,161 @@ +"""Reproduce defects and verify containment. Fake kubectl only, no network.""" +from copy import deepcopy +import json +import os +from pathlib import Path +import subprocess +import sys +import unittest +from unittest.mock import patch + +HERE = Path(__file__).resolve().parent +sys.path[:0] = [str(HERE.parent / 'platform-delivery-backend-r1/prepared-source/scripts'), + '/home/worsch/railiance-platform/scripts'] +import keycape_issuer_delivery as lane + + +def defaulted(value, schema): + """Apply only defaults actually present in the cached 0.16.1 v1 CRD.""" + value = deepcopy(value) + if isinstance(value, dict): + for key, child in schema.get('properties', {}).items(): + if key not in value and 'default' in child: + value[key] = deepcopy(child['default']) + if key in value: + value[key] = defaulted(value[key], child) + elif isinstance(value, list): + value = [defaulted(item, schema.get('items', {})) for item in value] + return value + + +class IndependentReview(unittest.TestCase): + def setUp(self): + self.env = patch.dict(os.environ, {}, clear=True) + self.env.start(); self.addCleanup(self.env.stop) + network = patch('socket.socket', side_effect=AssertionError('network forbidden')) + network.start(); self.addCleanup(network.stop) + self.m = lane._DeliveryMechanics('forgejo') + self.store = self.resource(self.m.store) + self.obj = self.resource(self.m.external) + self.calls = [] + self.responses = [] + run = patch('subprocess.run', side_effect=self.run_command) + run.start(); self.addCleanup(run.stop) + sleep = patch.object(lane.time, 'sleep') + sleep.start(); self.addCleanup(sleep.stop) + + def resource(self, doc): + value = deepcopy(doc) + value['metadata'].update(uid='fixture-uid', resourceVersion='10', generation=2) + value['status'] = {'conditions': [{'type': 'Ready', 'status': 'True'}], + 'syncedResourceVersion': '1-old-spec', + 'refreshTime': '2020-01-01T00:00:00Z'} + return value + + def run_command(self, argv, **kwargs): + self.calls.append((deepcopy(argv), deepcopy(kwargs))) + self.assertEqual(kwargs['env'], {}) + response = self.responses.pop(0) + if isinstance(response, Exception): + raise response + return subprocess.CompletedProcess(argv, 0, + b'' if response is None else json.dumps(response).encode(), b'') + + def test_reproduce_stale_ready_accepted_for_current_spec(self): + self.responses = [self.store, self.obj, self.obj] + receipt = self.m.deliver() + self.assertFalse(receipt['changed']) + self.assertEqual(self.obj['metadata']['generation'], 2) + self.assertEqual(self.obj['status']['syncedResourceVersion'], '1-old-spec') + + def test_reproduce_missing_freshness_fields_accepted(self): + for doc in (self.store, self.obj): + doc['status'] = {'conditions': [{'type': 'Ready', 'status': 'True'}]} + self.responses = [self.store, self.obj, self.obj] + self.assertFalse(self.m.deliver()['changed']) + + def test_reproduce_no_store_read_after_initial_validation(self): + # A server can replace or change the Store immediately after response 1. + # Neither create nor ES status binds the Store UID. No further Store GET. + self.responses = [self.store, None, self.obj, self.obj] + self.assertTrue(self.m.deliver()['changed']) + self.assertEqual(sum('clustersecretstores.external-secrets.io' in args + for args, _ in self.calls), 1) + + def test_reproduce_api_defaulted_create_response_rejected(self): + schema = json.loads((HERE / 'externalsecret-v1-schema.json').read_text()) + admitted = defaulted(self.obj, schema) + self.assertEqual(admitted['spec']['data'][0]['remoteRef']['decodingStrategy'], 'None') + self.responses = [self.store, None, admitted] + with self.assertRaises(lane.Uncertain): + self.m.deliver() + self.assertTrue(self.m.pending) + self.assertIsNone(self.m.created) # response UID was real but discarded by spec check + + def test_reproduce_get_version_uses_discovery_not_explicit_v1_endpoint(self): + self.responses = [self.store, self.obj, self.obj] + self.m.deliver() + for argv, _ in self.calls: + self.assertIn('get', argv) + self.assertNotIn('--raw', argv) + self.assertNotIn('/apis/external-secrets.io/v1', argv) + + def test_reproduce_post_capture_limit_and_unbounded_stderr(self): + self.responses = [self.store, self.obj, self.obj] + self.m.deliver() + for _, kwargs in self.calls: + self.assertTrue(kwargs['capture_output']) + self.assertEqual(kwargs['timeout'], 60) + # subprocess.run returns a fully collected stdout/stderr before _call + # can inspect stdout size; no stderr size check exists at all. + + def test_conditional_delete_preserves_uid_and_rv(self): + self.responses = [self.store, None, self.obj, self.obj] + receipt = self.m.deliver() + self.responses = [self.obj, {'kind': 'Status'}, None] + self.m.remove(receipt['after']) + argv, kwargs = next(call for call in self.calls if 'delete' in call[0]) + self.assertEqual(argv[5], '/apis/external-secrets.io/v1/namespaces/sso/externalsecrets/keycape-forgejo-client') + self.assertEqual(json.loads(kwargs['input'])['preconditions'], + {'uid': 'fixture-uid', 'resourceVersion': '10'}) + + def test_production_refuses_without_any_dispatch(self): + with self.assertRaises(lane.Refused): + lane.production_bindings(owner=object(), authenticated=True) + self.assertEqual(self.calls, []) + + def test_restart_and_lost_create_never_gain_delete_provenance(self): + self.responses = [self.store, None, TimeoutError()] + with self.assertRaises(lane.Uncertain): + self.m.deliver() + with self.assertRaises(lane.Refused): + lane._DeliveryMechanics('forgejo').remove({'uid': 'fixture-uid', 'resource_version': '10'}) + self.assertFalse(any('delete' in argv for argv, _ in self.calls)) + + def test_reproduce_malformed_metadata_escapes_fixed_error_envelope(self): + malformed = deepcopy(self.obj) + malformed['metadata'] = None + self.responses = [self.store, None, malformed] + with self.assertRaises(AttributeError): + self.m.deliver() + self.assertTrue(self.m.pending) + + def test_existing_no_change_is_not_one_shot_admission(self): + self.responses = [self.store, self.obj, self.obj] * 2 + self.assertFalse(self.m.deliver()['changed']) + self.assertFalse(self.m.deliver()['changed']) + self.assertEqual(len(self.calls), 6) + + def test_never_dispatch_secret_or_store_mutations(self): + self.responses = [self.store, None, self.obj, self.obj] + self.m.deliver() + for argv, kwargs in self.calls: + self.assertFalse(set(argv) & {'secret', 'secrets'}) + if 'create' in argv: + body = json.loads(kwargs['input']) + self.assertEqual(body['kind'], 'ExternalSecret') + self.assertEqual(body['metadata']['namespace'], 'sso') + + +if __name__ == '__main__': + unittest.main(verbosity=2) diff --git a/docs/changes/netkingdom-target-2026-10-17/platform-delivery-review-r1/test_partial_defaults_fix.py b/docs/changes/netkingdom-target-2026-10-17/platform-delivery-review-r1/test_partial_defaults_fix.py new file mode 100644 index 00000000..64e19547 --- /dev/null +++ b/docs/changes/netkingdom-target-2026-10-17/platform-delivery-review-r1/test_partial_defaults_fix.py @@ -0,0 +1,58 @@ +"""Run with partial-fix-source staged in an isolated Platform-shaped directory.""" +from copy import deepcopy +import json +from pathlib import Path +import sys +import unittest + +sys.path.insert(0, str(Path(__file__).resolve().parents[1] / 'scripts')) +import keycape_issuer_eso as eso +import keycape_issuer_delivery as lane + + +class DefaultNormalization(unittest.TestCase): + def setUp(self): + self.mechanics = lane._DeliveryMechanics('forgejo') + self.obj = deepcopy(self.mechanics.external) + self.obj['metadata'].update(uid='fixture', resourceVersion='10') + self.obj['status'] = {'conditions': [{'type': 'Ready', 'status': 'True'}]} + self.defaults = {'conversionStrategy': 'Default', 'decodingStrategy': 'None', + 'metadataPolicy': 'None'} + + def test_defaulted_response_matches_both_checks(self): + self.obj['spec']['data'][0]['remoteRef'].update(self.defaults) + eso.require_ready_delivery(self.obj, 'forgejo') + self.assertEqual(self.mechanics._match(self.obj)['uid'], 'fixture') + + def test_nondefault_values_refused(self): + for key, value in {'conversionStrategy': 'Unicode', 'decodingStrategy': 'Base64', + 'metadataPolicy': 'Fetch', 'key': 'elsewhere', 'property': 'PASSWORD', + 'version': '2', 'extra': 'value'}.items(): + with self.subTest(key=key): + obj = deepcopy(self.obj) + obj['spec']['data'][0]['remoteRef'][key] = value + with self.assertRaises(eso.Refused): + eso.require_ready_delivery(obj, 'forgejo') + with self.assertRaises(eso.Refused): + self.mechanics._match(obj) + + def test_normalization_does_not_mutate_document(self): + original = deepcopy(self.obj['spec']) + eso.canonical_external_spec(self.obj['spec']) + self.assertEqual(self.obj['spec'], original) + + def test_unready_and_wrong_identity_still_refused(self): + self.obj['spec']['data'][0]['remoteRef'].update(self.defaults) + for key, value in [('namespace', 'default'), ('name', 'other')]: + obj = deepcopy(self.obj); obj['metadata'][key] = value + with self.assertRaises(eso.Refused): + eso.require_ready_delivery(obj, 'forgejo') + with self.assertRaises(eso.Refused): + self.mechanics._match(obj) + self.obj['status']['conditions'] = [] + with self.assertRaises(eso.Refused): + eso.require_ready_delivery(self.obj, 'forgejo') + + +if __name__ == '__main__': + unittest.main()