Implement offline catalog lane scaffold generation
All checks were successful
CI Smoke / host-smoke (push) Successful in 0s
CI Smoke / container-smoke (push) Successful in 1s

Assistant: codex
Assistant-Model: gpt-6.1-sol
Assistant-Session: 01a10391-6355-7b41-b0fa-3e14e874f091
This commit is contained in:
codex 2026-10-04 02:24:40 +02:00
parent 6b9b95bc6e
commit f5b7ff5118
9 changed files with 869 additions and 1 deletions

View file

@ -0,0 +1,63 @@
# Offline exact KV-v2 workload-read lane scaffold
Owner: railiance-platform, RPF-WP-0050-T04. Warden pointer seam:
`ops-warden/wiki/CatalogLaneScaffoldContract.md`, WARDEN-WP-0041.
Run `python3 scripts/credential-change.py scaffold-lane --input <platform-request.yaml> --catalog-manifest <warden-request.yaml> --output-dir <new-external-staging-dir>`.
The catalog manifest is optional for ordinary local preparation and required for
cross-owner fixture acceptance. Both inputs are metadata only; this command has
no execute, credential, network, approval or apply option. jsonschema and PyYAML
are required, as used by the local validation environment.
`schemas/catalog-lane-scaffold.schema.json` is the closed platform request v1.
Explicit owner/consumer/lane, prospective Kubernetes SA/namespace/role/audience,
exact KV mount/path/field names, namespace-restricted ESO destination, risk for the
entire readable path and required review roles must be supplied. Missing claims
and risk are not inferred. The first shape supports one prospective ESO reader,
audience openbao and a maximum fifteen-minute token TTL. Tenant/dynamic secret,
OIDC/ceremony, wildcard, metadata listing, self-lifecycle and executable fetching
need separate reviewed extensions. No credential values or protected URLs belong
in this input. Unknown fields, malformed/duplicate YAML keys, control characters,
secret markers, invalid calendar dates and mismatched mappings are refused with
safe errors. Warden's unknown workload identity remains unknown and does not
confirm the prospective platform auth binding.
The generator reuses `credential-change.py`'s `generated_policy_hcl` and
`auth_payload`, preserving one exact data-read path and explicit policies/claims.
It emits six draft review artifacts: a CCR under `credential-change-requests/`,
its policy under `openbao/policies/`, auth payload under `openbao/auth/`, ESO review
manifest under `manifests/`, a non-executed evidence checklist under
`docs/evidence/`, and README. The policy path in the CCR is the eventual owner
repository destination, relative to the staged bundle. Auth payloads are wrapped
in draft metadata; no command executes them. No Secret object or value is emitted.
CCR status is proposed, review comments empty, bound claims unconfirmed and
frontdoor pending-review/unresolvable. Store/ExternalSecret annotations also say
pending-review. The ExternalSecret target has explicit annotation metadata to
contain source last-applied annotation copying. Evidence is not-executed and
verified=false; it lists metadata/capability/negative/cleanup receipts the owner
must produce later using its reviewed output-suppressing tools. It is a scaffold,
not a successful verification receipt. Nothing is apply-ready authority.
Generation requires a new output directory outside the platform or other owner
repository, refuses existing paths including symlinks, and never overwrites any
file. Caller-selected staging beneath the system temporary root is supported.
Inputs remain immutable and artifacts are deterministic; explicit dates and
request/catalog SHA256 bind provenance without clock/network reads.
## Cross-owner fixture
`examples/catalog-lane-scaffold/platform-request.yaml` and `warden-request.yaml`
use synthetic lane example-workload-kv, consumer example-consumer, prospective
reader example-consumer-eso and CCR-2099-9001. No published workload identity,
existing client or credential is claimed. Warden manifest references the platform
request and eventual CCR destination, includes explicit reviewed date and whole
path risk. Platform checks shared schema/lane/title/consumer/owner/risk and exact
CCR destination; Warden validates its own complete pointer schema.
Run both owner scaffold commands into separate new temporary directories and
compare the Warden draft catalog with the proposed CCR. Same lane and pointers,
risk/owner/consumer parity, retained unknown identity, unconfirmed platform claims,
unresolvable frontdoor and absence of executable fetch/approval state are required.
The synthetic fixture does not admit an actual lane. Source parity remains distinct
from any founder custody choice, attended dated window or production promotion.

View file

@ -0,0 +1,33 @@
{
"status": "passed",
"scope": "actual offline owner CLIs + independently verified deterministic cross-owner fixture; no admission or runtime act",
"platform_stage": "/tmp/p4-cross-platform-tj7_84lv/platform",
"warden_stage": "/tmp/warden-cross-owner-catalog",
"fixture_sha256": "38647d436ccfa8893974c4b88c93d3bd94a9c0a21a2603b54bd84c0195954e32",
"artifact_sha256": {
"README.md": "2f48aa6ef925f62722ca32d38283d746c0c386da751dec3f9a76fba125d24245",
"credential-change-requests/CCR-2099-9001.yaml": "e9db54694b966f186433aaf1f3de6685d8f34be166af9d966950bcca448f151f",
"docs/evidence/example-workload-kv.checklist.json": "7d9a58b160c958d8d46f7029398eef8e70f87f0d9423198fb789bdf8fe3a9f9a",
"manifests/example-workload-kv.draft.yaml": "2d23bee7451735e2a0aa74435c65a0968bd05e35c35625a4087e2f286a2408b5",
"openbao/auth/example-consumer-eso.draft.json": "545a03c95c50b68b0eb127ae5e72d904faf9f68ecfabe26deac27730905b476d",
"openbao/policies/workload-kv-read-example-consumer.hcl": "c2821cd6fe5bb16564366fb12fd37c559048841a41c6f2778ad2dad5901e4df4"
},
"new_scaffold_tests": 14,
"prior_window_tests": 7,
"unknown_workload_preserved": true,
"prospective_auth_claims_unconfirmed": true,
"frontdoor_pending_unresolvable": true,
"evidence_not_executed": true,
"source_sha256": {
"scripts/catalog_lane_scaffold.py": "96d60cc47aa78728f5ba96ce1fd63033adf23c05641de93ebbdda3902082d2d1",
"scripts/credential-change.py": "d78c493e7c75f3ad0e013745bc13e9f79b9fffc560e5c33ece62e982d800b391",
"schemas/catalog-lane-scaffold.schema.json": "8c2100052a86ebef021398bed662ed8a0fe6970506b785b22cec2398e75adaff",
"examples/catalog-lane-scaffold/platform-request.yaml": "2070fa0f937ee03add01d1e78b9dbdebf72472b3d8fcc8fae9a51436ec60ab8e",
"examples/catalog-lane-scaffold/warden-request.yaml": "38647d436ccfa8893974c4b88c93d3bd94a9c0a21a2603b54bd84c0195954e32",
"tests/test_catalog_lane_scaffold.py": "a4abc80b0a29362296208f7d233b9a206a299d0c47f244929f9851fb543911c6",
"docs/catalog-lane-scaffold.md": "f6b46acf6515fc13a7ce64b74306c9225c9384f73e63bc7b9723232b08ad3c3c"
},
"task_id": "RPF-WP-0050-T04",
"task_uuid": "6475d924-92c6-504a-b9a1-4f035273f0e1",
"recorded_date": "2026-10-04"
}

View file

@ -0,0 +1,49 @@
schema_version: 1
lane_id: example-workload-kv
ccr_id: CCR-2099-9001
title: Synthetic exact workload KV read fixture
date: '2026-10-04'
consumer_repo: example-consumer
owner_repo: railiance-platform
requester:
agent: example-consumer
reason: Offline fixture only; no deployed consumer or credential exists
review_approvers:
- platform-owner
- consumer-owner
target:
domain: infotech
tenant: platform
workload: example-consumer
environment: build
purpose: Offline fixture of proposed exact-path metadata
openbao:
mount: platform
kv_path: platform/workloads/example-consumer/config
fields:
- CLIENT_SECRET
policy_name: workload-kv-read-example-consumer
auth:
mount: kubernetes
role: example-consumer-eso
audience: openbao
service_account_names:
- example-consumer-eso
service_account_namespaces:
- example-consumer
ttl: 15m
eso:
store_name: openbao-example-consumer
server: http://openbao.openbao.svc:8200
namespace: example-consumer
external_secret_name: example-consumer-config
secret_name: example-consumer-config
refresh_interval: 5m
data:
- secret_key: client-secret
property: CLIENT_SECRET
risk:
classification: standard
notes:
- Synthetic prospective lane risk applies to the entire readable path; no actual
credentials.

View file

@ -0,0 +1,15 @@
schema_version: 1
lane_id: example-workload-kv
title: Synthetic exact workload KV read fixture
need_keywords:
- synthetic example workload config
consumer_repo: example-consumer
owner_repo: railiance-platform
workload_ref:
applicability: applicable
unknown_reason: Synthetic fixture has no published deployment identity
risk: standard
wiki_ref: railiance-platform:docs/catalog-lane-scaffold.md
canon_ref: railiance-platform:credential-change-requests/CCR-2099-9001.yaml
reviewed: '2026-10-04'
platform_request_ref: railiance-platform:examples/catalog-lane-scaffold/platform-request.yaml

View file

@ -0,0 +1,344 @@
{
"type": "object",
"additionalProperties": false,
"properties": {
"schema_version": {
"const": 1
},
"lane_id": {
"type": "string",
"minLength": 1,
"maxLength": 512,
"pattern": "^[a-z0-9](?:[a-z0-9-]*[a-z0-9])?$"
},
"ccr_id": {
"type": "string",
"minLength": 1,
"maxLength": 512,
"pattern": "^CCR-[0-9]{4}-[0-9]{4}$"
},
"title": {
"type": "string",
"minLength": 1,
"maxLength": 512
},
"date": {
"type": "string",
"minLength": 1,
"maxLength": 512,
"pattern": "^[0-9]{4}-[0-9]{2}-[0-9]{2}$"
},
"consumer_repo": {
"type": "string",
"minLength": 1,
"maxLength": 512,
"pattern": "^[a-z0-9](?:[a-z0-9-]*[a-z0-9])?$"
},
"owner_repo": {
"const": "railiance-platform"
},
"requester": {
"type": "object",
"additionalProperties": false,
"properties": {
"agent": {
"type": "string",
"minLength": 1,
"maxLength": 512,
"pattern": "^[a-z0-9](?:[a-z0-9-]*[a-z0-9])?$"
},
"reason": {
"type": "string",
"minLength": 1,
"maxLength": 512
}
},
"required": [
"agent",
"reason"
]
},
"review_approvers": {
"type": "array",
"minItems": 1,
"maxItems": 32,
"uniqueItems": true,
"items": {
"type": "string",
"minLength": 1,
"maxLength": 512,
"pattern": "^[a-z0-9](?:[a-z0-9-]*[a-z0-9])?$"
}
},
"target": {
"type": "object",
"additionalProperties": false,
"properties": {
"domain": {
"type": "string",
"minLength": 1,
"maxLength": 512,
"pattern": "^[a-z0-9](?:[a-z0-9-]*[a-z0-9])?$"
},
"tenant": {
"type": "string",
"minLength": 1,
"maxLength": 512,
"pattern": "^[a-z0-9](?:[a-z0-9-]*[a-z0-9])?$"
},
"workload": {
"type": "string",
"minLength": 1,
"maxLength": 512,
"pattern": "^[a-z0-9](?:[a-z0-9-]*[a-z0-9])?$"
},
"environment": {
"enum": [
"build",
"development",
"test",
"staging",
"production"
]
},
"purpose": {
"type": "string",
"minLength": 1,
"maxLength": 512
}
},
"required": [
"domain",
"tenant",
"workload",
"environment",
"purpose"
]
},
"openbao": {
"type": "object",
"additionalProperties": false,
"properties": {
"mount": {
"type": "string",
"minLength": 1,
"maxLength": 512,
"pattern": "^[a-z0-9](?:[a-z0-9-]*[a-z0-9])?$"
},
"kv_path": {
"type": "string",
"minLength": 1,
"maxLength": 512,
"pattern": "^[a-z0-9][a-z0-9_-]*(?:/[a-z0-9][a-z0-9_-]*)+$"
},
"fields": {
"type": "array",
"minItems": 1,
"maxItems": 32,
"uniqueItems": true,
"items": {
"type": "string",
"minLength": 1,
"maxLength": 512,
"pattern": "^[A-Z][A-Z0-9_]*$"
}
},
"policy_name": {
"type": "string",
"minLength": 1,
"maxLength": 512,
"pattern": "^workload-kv-read-[a-z0-9-]+$"
},
"auth": {
"type": "object",
"additionalProperties": false,
"properties": {
"mount": {
"type": "string",
"minLength": 1,
"maxLength": 512,
"pattern": "^[a-z0-9](?:[a-z0-9-]*[a-z0-9])?$"
},
"role": {
"type": "string",
"minLength": 1,
"maxLength": 512,
"pattern": "^[a-z0-9](?:[a-z0-9-]*[a-z0-9])?$"
},
"audience": {
"const": "openbao"
},
"service_account_names": {
"type": "array",
"minItems": 1,
"maxItems": 32,
"uniqueItems": true,
"items": {
"type": "string",
"minLength": 1,
"maxLength": 512,
"pattern": "^[a-z0-9](?:[a-z0-9-]*[a-z0-9])?$"
}
},
"service_account_namespaces": {
"type": "array",
"minItems": 1,
"maxItems": 32,
"uniqueItems": true,
"items": {
"type": "string",
"minLength": 1,
"maxLength": 512,
"pattern": "^[a-z0-9](?:[a-z0-9-]*[a-z0-9])?$"
}
},
"ttl": {
"type": "string",
"minLength": 1,
"maxLength": 512,
"pattern": "^[1-9][0-9]*[smh]$"
}
},
"required": [
"mount",
"role",
"audience",
"service_account_names",
"service_account_namespaces",
"ttl"
]
}
},
"required": [
"mount",
"kv_path",
"fields",
"policy_name",
"auth"
]
},
"eso": {
"type": "object",
"additionalProperties": false,
"properties": {
"store_name": {
"type": "string",
"minLength": 1,
"maxLength": 512,
"pattern": "^[a-z0-9](?:[a-z0-9-]*[a-z0-9])?$"
},
"server": {
"type": "string",
"minLength": 1,
"maxLength": 512,
"pattern": "^https?://[a-z0-9](?:[a-z0-9.-]*[a-z0-9])?(?::[0-9]+)?$"
},
"namespace": {
"type": "string",
"minLength": 1,
"maxLength": 512,
"pattern": "^[a-z0-9](?:[a-z0-9-]*[a-z0-9])?$"
},
"external_secret_name": {
"type": "string",
"minLength": 1,
"maxLength": 512,
"pattern": "^[a-z0-9](?:[a-z0-9-]*[a-z0-9])?$"
},
"secret_name": {
"type": "string",
"minLength": 1,
"maxLength": 512,
"pattern": "^[a-z0-9](?:[a-z0-9-]*[a-z0-9])?$"
},
"refresh_interval": {
"type": "string",
"minLength": 1,
"maxLength": 512,
"pattern": "^[1-9][0-9]*[smh]$"
},
"data": {
"type": "array",
"minItems": 1,
"maxItems": 32,
"uniqueItems": true,
"items": {
"type": "object",
"additionalProperties": false,
"properties": {
"secret_key": {
"type": "string",
"minLength": 1,
"maxLength": 512,
"pattern": "^[a-zA-Z0-9._-]+$"
},
"property": {
"type": "string",
"minLength": 1,
"maxLength": 512,
"pattern": "^[A-Z][A-Z0-9_]*$"
}
},
"required": [
"secret_key",
"property"
]
}
}
},
"required": [
"store_name",
"server",
"namespace",
"external_secret_name",
"secret_name",
"refresh_interval",
"data"
]
},
"risk": {
"type": "object",
"additionalProperties": false,
"properties": {
"classification": {
"enum": [
"standard",
"high"
]
},
"notes": {
"type": "array",
"minItems": 1,
"maxItems": 32,
"uniqueItems": true,
"items": {
"type": "string",
"minLength": 1,
"maxLength": 512
}
}
},
"required": [
"classification",
"notes"
]
}
},
"required": [
"schema_version",
"lane_id",
"ccr_id",
"title",
"date",
"consumer_repo",
"owner_repo",
"requester",
"review_approvers",
"target",
"openbao",
"eso",
"risk"
],
"$schema": "https://json-schema.org/draft/2020-12/schema",
"title": "Draft exact KV-v2 workload-read lane input v1"
}

View file

@ -0,0 +1,203 @@
"""Draft-only offline exact-path lane scaffolding; no execution surface."""
from __future__ import annotations
from copy import deepcopy
from datetime import date
import hashlib
import json
from pathlib import Path
import re
import sys
import tempfile
import jsonschema
import yaml
REPO_DIR = Path(__file__).resolve().parents[1]
MAX_INPUT_BYTES = 128 * 1024
SECRET_MARKERS = ('AGE-SECRET-KEY-1', '-----BEGIN', 'hvs.', 'hvb.', 'hvc.', 'ghp_', 'github_pat_', 'npm_', 'BAO_TOKEN=', 'VAULT_TOKEN=', 'OPENBAO_ROOT_TOKEN=')
class UniqueLoader(yaml.SafeLoader):
pass
def unique_mapping(loader, node):
result = {}
for key_node, value_node in node.value:
key = loader.construct_object(key_node)
if not isinstance(key, str) or key in result:
raise ValueError('Invalid or duplicate metadata key')
result[key] = loader.construct_object(value_node)
return result
UniqueLoader.add_constructor(yaml.resolver.BaseResolver.DEFAULT_MAPPING_TAG, unique_mapping)
def read_metadata(path):
with Path(path).open('rb') as stream:
raw = stream.read(MAX_INPUT_BYTES + 1)
if len(raw) > MAX_INPUT_BYTES:
raise ValueError('Metadata input exceeds size limit')
text = raw.decode('utf-8')
if any(marker in text for marker in SECRET_MARKERS) or re.search(r'(?<![A-Za-z])sk-[A-Za-z0-9]', text):
raise ValueError('Metadata input contains rejected secret material')
try:
data = yaml.load(text, Loader=UniqueLoader)
canonical = json.dumps(data, allow_nan=False)
if any(marker in canonical for marker in SECRET_MARKERS) or re.search(r"(?<![A-Za-z])sk-[A-Za-z0-9]", canonical):
raise ValueError("Rejected parsed secret material")
except (yaml.YAMLError, TypeError, RecursionError, ValueError) as exc:
raise ValueError('Invalid metadata document') from None
return data, hashlib.sha256(raw).hexdigest()
def validate_request(data):
schema = json.loads((REPO_DIR / 'schemas/catalog-lane-scaffold.schema.json').read_text())
if not jsonschema.Draft202012Validator(schema).is_valid(data):
raise ValueError('Request does not match closed platform scaffold schema v1')
def text_values(value):
if isinstance(value, dict):
return [item for nested in value.values() for item in text_values(nested)]
if isinstance(value, list):
return [item for nested in value for item in text_values(nested)]
return [value] if isinstance(value, str) else []
if any(any(ord(char) < 32 for char in value) for value in text_values(data)):
raise ValueError('Control characters are not supported in scaffold metadata')
date.fromisoformat(data['date'])
bao = data['openbao']
if not bao['kv_path'].startswith(bao['mount'] + '/'):
raise ValueError('KV path must use the explicitly declared mount')
auth = bao['auth']
units = {'s': 1, 'm': 60, 'h': 3600}
ttl = auth['ttl']
if int(ttl[:-1]) * units[ttl[-1]] > 900:
raise ValueError('Prospective Kubernetes token TTL exceeds fifteen minutes')
eso = data['eso']
if len(auth['service_account_names']) != 1 or len(auth['service_account_namespaces']) != 1:
raise ValueError('One explicit ESO auth identity is supported')
keys = [item['secret_key'] for item in eso['data']]
properties = [item['property'] for item in eso['data']]
if len(set(keys)) != len(keys) or len(set(properties)) != len(properties) or set(properties) != set(bao['fields']):
raise ValueError('ESO mappings must cover the explicit field set exactly once')
def verify_catalog_join(request, catalog):
# Warden validates its own full input; platform checks only the shared seam.
if not isinstance(catalog, dict):
raise ValueError('Catalog pointer manifest must be an object')
for field in ('schema_version', 'lane_id', 'title', 'consumer_repo', 'owner_repo'):
if catalog.get(field) != request[field]:
raise ValueError('Cross-owner fixture identity mismatch')
if catalog.get('risk') != request['risk']['classification']:
raise ValueError('Cross-owner risk mismatch')
if catalog.get('canon_ref') != 'railiance-platform:credential-change-requests/' + request['ccr_id'] + '.yaml':
raise ValueError('Catalog CCR destination pointer mismatch')
ref = catalog.get('platform_request_ref')
if not isinstance(ref, str) or not re.fullmatch(r'railiance-platform:[a-zA-Z0-9_./-]+', ref) or '..' in ref:
raise ValueError('Catalog needs a non-secret owner request pointer')
def render_artifacts(data, policy_renderer, auth_renderer, request_digest, catalog_digest=None):
validate_request(data)
data = deepcopy(data)
bao = data['openbao']
auth = bao['auth']
lane = data['lane_id']
policy_file = 'openbao/policies/' + bao['policy_name'] + '.hcl'
ccr = {'id': data['ccr_id'], 'kind': 'credential-change-request', 'schema_version': 1,
'request_type': 'workload-kv-read', 'title': data['title'], 'status': 'proposed',
'created': data['date'], 'updated': data['date'], 'requester': data['requester'],
'review': {'required': True, 'required_approvers': data['review_approvers'], 'comments': []},
'target': data['target'],
'openbao': {'mount': bao['mount'], 'kv_path': bao['kv_path'], 'fields': bao['fields'],
'policy_name': bao['policy_name'], 'policy_file': policy_file, 'metadata_read': False,
'auth': {'method': 'kubernetes', 'mount': auth['mount'], 'role': auth['role'],
'audience': auth['audience'], 'ttl': auth['ttl'],
'token_max_ttl': auth['ttl'], 'token_explicit_max_ttl': auth['ttl'],
'token_no_default_policy': True, 'policies': [bao['policy_name']],
'bound_claims': {'service_account_names': auth['service_account_names'],
'service_account_namespaces': auth['service_account_namespaces']},
'bound_claims_confirmed': False}},
'access_frontdoor': {'type': 'external-secrets', 'catalog_id': lane,
'readiness': 'pending-review', 'resolvable': False},
'risk': data['risk'],
'verification': {'positive': ['Owner-reviewed native exact identity and exact path proof required'],
'negative': ['Owner-reviewed wrong identity, sibling GET and parent LIST denials required'],
'activation_conditions': ['Exact CCR approval, confirmed identity, attended custody and native receipt acceptance required']},
'lifecycle': {'deactivate': 'Owner-reviewed delivery withdrawal and token revocation; exact rollback receipt required',
'rotate': 'Separate version-guarded owner approval and contained value delivery required',
'compromised': 'Owner incident process; no automatic read, reseed or cleanup'},
'scaffold': {'draft_only': True, 'consumer_repo': data['consumer_repo'],
'owner_repo': data['owner_repo'], 'request_sha256': request_digest,
'catalog_request_sha256': catalog_digest}}
annotation = {'railiance.io/credential-change': data['ccr_id'], 'railiance.io/admission': 'pending-review'}
eso = data['eso']
store = {'apiVersion': 'external-secrets.io/v1', 'kind': 'ClusterSecretStore',
'metadata': {'name': eso['store_name'], 'annotations': deepcopy(annotation)},
'spec': {'provider': {'vault': {'server': eso['server'], 'path': bao['mount'], 'version': 'v2',
'auth': {'kubernetes': {'mountPath': auth['mount'], 'role': auth['role'],
'audiences': [auth['audience']],
'serviceAccountRef': {'name': auth['service_account_names'][0],
'namespace': auth['service_account_namespaces'][0]}}}}},
'conditions': [{'namespaces': [eso['namespace']]}]}}
secret = {'apiVersion': 'external-secrets.io/v1', 'kind': 'ExternalSecret',
'metadata': {'name': eso['external_secret_name'], 'namespace': eso['namespace'], 'annotations': deepcopy(annotation)},
'spec': {'refreshInterval': eso['refresh_interval'],
'secretStoreRef': {'kind': 'ClusterSecretStore', 'name': eso['store_name']},
'target': {'name': eso['secret_name'], 'creationPolicy': 'Owner', 'deletionPolicy': 'Retain',
'template': {'metadata': {'annotations': deepcopy(annotation)}}},
'data': [{'secretKey': item['secret_key'],
'remoteRef': {'key': bao['kv_path'][len(bao['mount']) + 1:], 'property': item['property']}}
for item in eso['data']]}}
evidence = {'schema_version': 1, 'lane_id': lane, 'ccr_id': data['ccr_id'], 'status': 'not-executed',
'draft_only': True, 'verified': False, 'request_sha256': request_digest,
'required_owner_checks': ['Exact revision/digest and required owner reviews',
'Identity/auth claims confirmed separately; no inferred workload identity',
'Metadata/capability readback through reviewed output-suppressing owner tool',
'No Secret bodies, values or raw subprocess errors in evidence',
'Native scoped positive/negative, cleanup/revocation and rollback receipts after approval']}
artifacts = {'credential-change-requests/' + data['ccr_id'] + '.yaml': yaml.safe_dump(ccr, sort_keys=False),
policy_file: '# DRAFT ONLY: exact CCR review and confirmed identity required.\n' + policy_renderer(ccr),
'openbao/auth/' + auth['role'] + '.draft.json': json.dumps({'draft_only': True, 'status': 'proposed',
'bound_claims_confirmed': False,
'auth_payload': auth_renderer(ccr)}, indent=2) + '\n',
'manifests/' + lane + '.draft.yaml': '# DRAFT REVIEW ARTIFACT. Do not apply. No custody/admission supplied.\n' + yaml.safe_dump_all([store, secret], sort_keys=False),
'docs/evidence/' + lane + '.checklist.json': json.dumps(evidence, indent=2) + '\n',
'README.md': 'Draft-only review bundle. No admission, approval, confirmed identity, credential value or executed receipt.\n'
'Review the exact request digest and owner destinations before promoting any file.\n'
'ESO target metadata is explicit to contain source last-applied annotations.\n'}
return artifacts
def scaffold(input_path, output_dir, policy_renderer, auth_renderer, catalog_manifest=None):
data, digest = read_metadata(input_path)
catalog_digest = None
if catalog_manifest is not None:
catalog, catalog_digest = read_metadata(catalog_manifest)
validate_request(data)
verify_catalog_join(data, catalog)
artifacts = render_artifacts(data, policy_renderer, auth_renderer, digest, catalog_digest)
output = Path(output_dir).absolute()
if output.is_symlink() or output.exists() or output.resolve().is_relative_to(REPO_DIR.resolve()):
raise ValueError('Output must be a new staging directory outside the owner repository')
if any((p / '.git').exists() for p in output.resolve().parents if p != Path(tempfile.gettempdir()).resolve()):
raise ValueError('Output staging must not be inside a repository')
output.mkdir(mode=0o700, parents=False)
for relative, content in artifacts.items():
path = output / relative
path.parent.mkdir(parents=True, exist_ok=True)
with path.open('x', encoding='utf-8') as stream:
stream.write(content)
return artifacts
def command_scaffold(args, policy_renderer, auth_renderer):
try:
artifacts = scaffold(args.input, args.output_dir, policy_renderer, auth_renderer, args.catalog_manifest)
except (ValueError, OSError, UnicodeError, RecursionError):
print('Scaffold refused: invalid metadata, mismatched owner pointers, or unsafe staging output.', file=sys.stderr)
return 2
print('Prepared ' + str(len(artifacts)) + ' draft review artifacts; nothing admitted or executed.')
return 0

View file

@ -2240,6 +2240,13 @@ def build_parser() -> argparse.ArgumentParser:
)
applier_apply.set_defaults(func=command_applier_apply)
from catalog_lane_scaffold import command_scaffold
scaffold = sub.add_parser("scaffold-lane", help="Generate proposed offline CCR/policy/ESO/evidence drafts in a new staging directory")
scaffold.add_argument("--input", required=True)
scaffold.add_argument("--output-dir", required=True)
scaffold.add_argument("--catalog-manifest")
scaffold.set_defaults(func=lambda args: command_scaffold(args, generated_policy_hcl, auth_payload))
from provisioning_window import command_window
window = sub.add_parser("window-checklist", help="Render an offline attended-window preparation checklist; never execute")
window.add_argument("--window-file", required=True)

View file

@ -0,0 +1,150 @@
import argparse
from copy import deepcopy
import importlib.util
import io
import json
from pathlib import Path
import subprocess
import sys
import tempfile
import unittest
from unittest.mock import patch
import urllib.request
import yaml
REPO_DIR = Path(__file__).resolve().parents[1]
SCRIPTS = REPO_DIR / 'scripts'
sys.path.insert(0, str(SCRIPTS))
import catalog_lane_scaffold as scaffold
SPEC = importlib.util.spec_from_file_location('ccr_scaffold_helpers', SCRIPTS / 'credential-change.py')
ccr = importlib.util.module_from_spec(SPEC)
SPEC.loader.exec_module(ccr)
class ScaffoldTests(unittest.TestCase):
def setUp(self):
self.tmp = tempfile.TemporaryDirectory()
self.addCleanup(self.tmp.cleanup)
self.root = Path(self.tmp.name)
self.request = REPO_DIR / 'examples/catalog-lane-scaffold/platform-request.yaml'
self.catalog = REPO_DIR / 'examples/catalog-lane-scaffold/warden-request.yaml'
self.data, self.digest = scaffold.read_metadata(self.request)
def render(self, data=None):
return scaffold.render_artifacts(data or self.data, ccr.generated_policy_hcl, ccr.auth_payload, self.digest)
def write_request(self, data):
path = self.root / 'request.yaml'
path.write_text(yaml.safe_dump(data))
return path
def test_existing_ccr_validator_accepts_only_proposed_unconfirmed_draft(self):
output = self.root / 'bundle'
scaffold.scaffold(self.request, output, ccr.generated_policy_hcl, ccr.auth_payload, self.catalog)
path = output / 'credential-change-requests/CCR-2099-9001.yaml'
with patch.object(ccr, 'REPO_DIR', output):
data, errors, warnings = ccr.validate_ccr(path)
self.assertEqual(errors, [])
self.assertTrue(any('not confirmed' in w for w in warnings))
self.assertEqual(data['status'], 'proposed')
self.assertFalse(data['access_frontdoor']['resolvable'])
self.assertFalse(data['openbao']['auth']['bound_claims_confirmed'])
self.assertEqual(data['review']['comments'], [])
self.assertTrue(ccr.applier_readiness_blockers(data))
self.assertEqual(set(ccr.auth_payload(data)['bound_service_account_names']), {'example-consumer-eso'})
def test_policy_and_eso_exact_scope_annotation_containment_and_no_values(self):
artifacts = self.render()
policy = artifacts['openbao/policies/workload-kv-read-example-consumer.hcl']
self.assertIn('path "platform/data/workloads/example-consumer/config"', policy)
self.assertEqual(policy.count('path "'), 1)
self.assertNotIn('metadata/', policy)
store, secret = list(yaml.safe_load_all(artifacts['manifests/example-workload-kv.draft.yaml']))
self.assertEqual(store['spec']['conditions'], [{'namespaces': ['example-consumer']}])
self.assertEqual(store['spec']['provider']['vault']['auth']['kubernetes']['audiences'], ['openbao'])
target = secret['spec']['target']
self.assertEqual(target['template']['metadata']['annotations']['railiance.io/admission'], 'pending-review')
self.assertNotIn('kubectl.kubernetes.io/last-applied-configuration', json.dumps(target))
self.assertEqual(secret['spec']['data'], [{'secretKey': 'client-secret', 'remoteRef': {'key': 'workloads/example-consumer/config', 'property': 'CLIENT_SECRET'}}])
self.assertNotIn('dataFrom', secret['spec'])
self.assertNotIn('Secret', [store['kind'], secret['kind']])
evidence = json.loads(artifacts['docs/evidence/example-workload-kv.checklist.json'])
self.assertFalse(evidence['verified'])
self.assertEqual(evidence['status'], 'not-executed')
def test_closed_schema_and_authority_injection_refused(self):
for location, key, value in [('top','status','approved'),('auth','bound_claims_confirmed',True),('top','secret_values',{'CLIENT_SECRET':'not-a-value'})]:
data = deepcopy(self.data)
target = data if location == 'top' else data['openbao']['auth']
target[key] = value
with self.assertRaises(ValueError): self.render(data)
def test_missing_identity_and_risk_are_not_inferred(self):
for location, key in [('auth','service_account_names'),('top','risk')]:
data = deepcopy(self.data)
target = data if location == 'top' else data['openbao']['auth']
del target[key]
with self.assertRaises(ValueError): self.render(data)
def test_wildcards_traversal_and_hcl_injection_refused(self):
for path in ['platform/workloads/*','platform/../other','platform/workloads/config\n','platform/a/" { capabilities = ["sudo"] }']:
data = deepcopy(self.data);data['openbao']['kv_path'] = path
with self.assertRaises(ValueError): self.render(data)
def test_wrong_mount_and_overlong_ttl_refused(self):
data = deepcopy(self.data);data['openbao']['mount'] = 'other'
with self.assertRaises(ValueError): self.render(data)
data = deepcopy(self.data);data['openbao']['auth']['ttl'] = '1h'
with self.assertRaises(ValueError): self.render(data)
def test_field_mapping_omissions_duplicates_and_extra_fields_refused(self):
for mapping in [[], [{'secret_key':'one','property':'OTHER'}], [{'secret_key':'a','property':'CLIENT_SECRET'},{'secret_key':'b','property':'CLIENT_SECRET'}]]:
data = deepcopy(self.data);data['eso']['data'] = mapping
with self.assertRaises(ValueError): self.render(data)
def test_cross_owner_pointer_fixture_and_unknown_identity_preserved(self):
catalog, _ = scaffold.read_metadata(self.catalog)
scaffold.verify_catalog_join(self.data, catalog)
self.assertIn('unknown_reason', catalog['workload_ref'])
for key,value in [('lane_id','other'),('risk','high'),('canon_ref','railiance-platform:credential-change-requests/OTHER.yaml'),('platform_request_ref','other-owner:request.yaml')]:
bad=deepcopy(catalog);bad[key]=value
with self.assertRaises(ValueError): scaffold.verify_catalog_join(self.data,bad)
def test_canonical_outputs_deterministic_and_input_immutable(self):
before = self.request.read_bytes();data_before=deepcopy(self.data)
self.assertEqual(self.render(), self.render())
self.assertEqual(self.data,data_before)
self.assertEqual(self.request.read_bytes(),before)
def test_collision_and_repo_output_refused_without_overwrite(self):
output=self.root/'occupied';output.mkdir();sentinel=output/'sentinel';sentinel.write_text('keep')
with self.assertRaises(ValueError): scaffold.scaffold(self.request,output,ccr.generated_policy_hcl,ccr.auth_payload)
self.assertEqual(sentinel.read_text(),'keep')
with self.assertRaises(ValueError): scaffold.scaffold(self.request,REPO_DIR/'live-output',ccr.generated_policy_hcl,ccr.auth_payload)
def test_no_network_or_subprocess_in_generation(self):
with patch.object(urllib.request,'urlopen',side_effect=AssertionError('network forbidden')),patch.object(subprocess,'run',side_effect=AssertionError('execution forbidden')):
artifacts=scaffold.scaffold(self.request,self.root/'isolated',ccr.generated_policy_hcl,ccr.auth_payload,self.catalog)
self.assertEqual(len(artifacts),6)
def test_duplicate_yaml_and_secret_material_refused_without_echo(self):
for text in ['lane_id: a\nlane_id: b\n', 'title: hvs.CANARY_NEVER_ECHO\n', 'title: "\\x68vs.CANARY_NEVER_ECHO"\n']:
path=self.root/'bad.yaml';path.write_text(text)
args=argparse.Namespace(input=path,output_dir=self.root/'bad-output',catalog_manifest=None)
stream=io.StringIO()
with patch('sys.stderr',stream): self.assertEqual(scaffold.command_scaffold(args,ccr.generated_policy_hcl,ccr.auth_payload),2)
self.assertNotIn('CANARY',stream.getvalue())
self.assertFalse((self.root/'bad-output').exists())
def test_oversized_input_refused(self):
path=self.root/'big.yaml';path.write_text('a'*(scaffold.MAX_INPUT_BYTES+1))
with self.assertRaises(ValueError): scaffold.read_metadata(path)
def test_schema_rejects_boolean_version_and_nonexistent_calendar_day(self):
for version,day in [(True,'2026-10-04'),(1,'2026-02-31')]:
data=deepcopy(self.data);data['schema_version']=version;data['date']=day
with self.assertRaises(ValueError): self.render(data)
if __name__ == '__main__': unittest.main()

View file

@ -57,7 +57,7 @@ Generate a checklist from all named authoritative roots, verified Hub decision j
```task
id: RPF-WP-0050-T04
status: todo
status: done
priority: medium
state_hub_task_id: "6475d924-92c6-504a-b9a1-4f035273f0e1"
```
@ -65,3 +65,7 @@ state_hub_task_id: "6475d924-92c6-504a-b9a1-4f035273f0e1"
P4 accepted for platform-side source review and bounded scaffolding after Warden returns its contract. Catalog entry/scaffold never supplies credentials, reviews, admission or live authority. Keep owner acceptance explicit before implementation.
2026-10-03: Platform source review accepts the delivered Warden seam at `ops-warden/wiki/CatalogLaneScaffoldContract.md` (`ac112d8`). T04 is now a ready source implementation task for the platform-owned rendering and schema fixture. Warden T02 may progress independently; no mutual owner-return wait remains. This is source contract acceptance, not credential admission or production authorization.
2026-10-04 T04 implementation: closed platform metadata schema and `credential-change.py scaffold-lane` authored with deterministic proposed CCR, existing exact-path policy/auth rendering, namespace-restricted draft ESO with annotation containment, and non-executed owner evidence checklist. Shared synthetic platform/Warden pointer fixtures retain unknown workload identity and unconfirmed auth claims. Fourteen focused offline tests pass in isolated prepared source; cross-owner staging fixture acceptance remains before marking T04 done. No admission, credentials, human answer or production execution.
2026-10-04 T04 acceptance: actual applied-source scaffold tests 14/14 and existing window tests 7/7 pass, with CLI help and diff checks. Both actual owner CLIs produced matching cross-owner synthetic fixture drafts; platform independently verified Warden catalog/model loading, exact lane/owner/consumer/risk/CCR pointers and fixture digest, preserved unknown workload, unconfirmed prospective auth, proposed/unresolvable frontdoor, explicit ESO target annotations and non-executed evidence. Six platform artifacts match the independently generated second platform stage byte-for-byte. Receipt: `docs/evidence/2026-10-04-catalog-lane-scaffold-offline.json`. T04 is done for offline source/scaffold/fixture acceptance only. T02 remains the founder custody-choice wait; T03 remains the Custodian dated-window handoff. No admission, actual credentials, schedule or production execution was performed.