Record reviewed case source evidence (5/13)
This commit is contained in:
parent
42e18ef0c8
commit
5c456215ea
20 changed files with 2762 additions and 0 deletions
|
|
@ -0,0 +1,157 @@
|
|||
"""Kernel admission against the real 1.3 successor type (NK-WP-0048-T16 candidate).
|
||||
|
||||
Unlike test_admit_1_2_adapter_descriptors.py, nothing is patched into the type's
|
||||
execution_descriptors: the map tested here is the one committed in
|
||||
security-case-type-revisions/1.3. The type is `proposed`, so the kernel must refuse
|
||||
it as it stands. Warden's BrowserInputs also pins each type version's parameter
|
||||
fingerprint and knows only 1.1 and 1.2 (ops-warden case_inputs.PARAMETER_PINS), so an
|
||||
acknowledged 1.3 is still refused until ops-warden adds a 1.3 pin. That is an
|
||||
ops-warden owner change, and it is not made here. The tests show that 1.3's
|
||||
parameters carry 1.2's pinned fingerprint, and that with only that one pin added in
|
||||
memory the committed map admits exactly the composed descriptors and nothing else.
|
||||
Trust is fixture-only, nothing is dispatched, and none of this is owner admission
|
||||
or an acknowledgment.
|
||||
"""
|
||||
from dataclasses import replace
|
||||
import hashlib
|
||||
import importlib.util
|
||||
import json
|
||||
import os
|
||||
from pathlib import Path
|
||||
import tempfile
|
||||
import unittest
|
||||
from unittest import mock
|
||||
|
||||
import yaml
|
||||
|
||||
from warden.case_run import Adapter, Executor, Journal, digest
|
||||
from warden.case_receipt_state import Refused
|
||||
import warden.case_inputs as inputs
|
||||
|
||||
ROOT = Path(__file__).parent
|
||||
REPO = ROOT.parents[2]
|
||||
spec = importlib.util.spec_from_file_location('compose_13_admit', ROOT / 'compose-1.2-descriptors.py')
|
||||
c = importlib.util.module_from_spec(spec)
|
||||
spec.loader.exec_module(c)
|
||||
|
||||
TYPE_DIR = REPO / 'security-case-type-revisions/1.3/oidc-browser-relying-party'
|
||||
TYPE_BYTES = (TYPE_DIR / 'type.yaml').read_bytes()
|
||||
ARTIFACT = json.loads((ROOT / 'descriptor-composition-1.3.candidate.json').read_text())
|
||||
EVIDENCE = {'fixture-acceptance', 'fixture-materialization', 'fixture-descriptor', 'fixture-session'}
|
||||
|
||||
|
||||
def unused(*_):
|
||||
raise AssertionError('admission must not dispatch')
|
||||
|
||||
|
||||
def parameter_fingerprint(type_bytes):
|
||||
params = yaml.safe_load(type_bytes)['spec']['parameters']
|
||||
return hashlib.sha256(json.dumps(params, sort_keys=True, separators=(',', ':')).encode()).hexdigest()
|
||||
|
||||
|
||||
def pinned_13():
|
||||
"""Warden's parameter pins plus the one 1.3 entry ops-warden would have to add."""
|
||||
return mock.patch.dict(inputs.PARAMETER_PINS, {'1.3': parameter_fingerprint(TYPE_BYTES)})
|
||||
|
||||
|
||||
def with_status(status):
|
||||
"""The committed 1.3 type with only its status changed."""
|
||||
typ = yaml.safe_load(TYPE_BYTES)
|
||||
typ['spec']['status'] = status
|
||||
return yaml.safe_dump(typ).encode()
|
||||
|
||||
|
||||
class Admit13TypeTests(unittest.TestCase):
|
||||
def setUp(self):
|
||||
self.temp = tempfile.TemporaryDirectory()
|
||||
self.addCleanup(self.temp.cleanup)
|
||||
case = yaml.safe_load((TYPE_DIR / 'examples/forgejo.yaml').read_bytes())
|
||||
case['spec'].update(status='materialized', acceptance={
|
||||
'at': '2026-10-17', 'record': 'fixture-only acceptance, not an owner record',
|
||||
'by': [{'name': 'fixture', 'role': 'owner'}, {'name': 'fixture', 'role': 'founder'}]})
|
||||
self.case_source = yaml.safe_dump(case).encode()
|
||||
self.descriptors = dict(c.adapter_descriptors(ARTIFACT['check_config_digest'],
|
||||
ARTIFACT['params']['consumer_owner_repo'],
|
||||
version='1.3'))
|
||||
|
||||
def admit(self, desc, type_source, *, actor=None, lane=None):
|
||||
desc_bytes = c.encode(desc)
|
||||
adapter = Adapter(desc.adapter, desc.adapter_source, unused, unused)
|
||||
executor = Executor(Journal(Path(self.temp.name) / desc.step),
|
||||
{adapter.reference: adapter},
|
||||
lambda purpose, source, evidence: evidence in EVIDENCE,
|
||||
browser_inputs=inputs.BrowserInputs(REPO))
|
||||
with mock.patch.dict(os.environ):
|
||||
os.environ.pop('WARDEN_AGENT_ID', None)
|
||||
return executor.admit(self.case_source, type_source, desc_bytes,
|
||||
b'fixture-only-catalog', principal='founder', actor=actor or desc.actor,
|
||||
lane=lane or desc.lane,
|
||||
acceptance_evidence='fixture-acceptance',
|
||||
materialization_evidence='fixture-materialization',
|
||||
descriptor_evidence='fixture-descriptor', session_evidence='fixture-session')
|
||||
|
||||
def test_committed_map_is_exactly_the_composed_set(self):
|
||||
committed = yaml.safe_load(TYPE_BYTES)['spec']['execution_descriptors']
|
||||
composed = {s: digest(c.encode(d)) for s, d in self.descriptors.items()}
|
||||
self.assertEqual(len(composed), 7)
|
||||
self.assertEqual(committed, composed)
|
||||
self.assertEqual(committed, ARTIFACT['execution_descriptors'])
|
||||
|
||||
def test_proposed_type_is_refused_as_it_stands(self):
|
||||
self.assertEqual(yaml.safe_load(TYPE_BYTES)['spec']['status'], 'proposed')
|
||||
for step, desc in self.descriptors.items():
|
||||
with self.subTest(step=step), self.assertRaisesRegex(Refused, 'type is not acknowledged'):
|
||||
self.admit(desc, TYPE_BYTES)
|
||||
|
||||
def test_warden_has_no_1_3_parameter_pin_yet(self):
|
||||
self.assertNotIn('1.3', inputs.PARAMETER_PINS)
|
||||
# 1.3 keeps 1.2's parameters, so the missing pin is 1.2's fingerprint.
|
||||
self.assertEqual(parameter_fingerprint(TYPE_BYTES), inputs.PARAMETER_PINS['1.2'])
|
||||
acknowledged = with_status('acknowledged')
|
||||
for step, desc in self.descriptors.items():
|
||||
with self.subTest(step=step), self.assertRaisesRegex(
|
||||
Refused, 'failed exact-version validation'):
|
||||
self.admit(desc, acknowledged)
|
||||
|
||||
def test_committed_map_admits_every_descriptor_once_acknowledged_and_pinned(self):
|
||||
acknowledged = with_status('acknowledged')
|
||||
for step, desc in self.descriptors.items():
|
||||
with self.subTest(step=step), pinned_13():
|
||||
admitted, invocation, _ = self.admit(desc, acknowledged)
|
||||
self.assertEqual(admitted, desc)
|
||||
self.assertEqual((invocation.step, invocation.actor, invocation.target),
|
||||
(desc.step, desc.actor, 'forgejo'))
|
||||
|
||||
def test_descriptor_outside_the_map_is_refused(self):
|
||||
acknowledged = with_status('acknowledged')
|
||||
mint = self.descriptors['mint-secret']
|
||||
for changed in (replace(mint, lane='other-lane'), replace(mint, retry_safe=True)):
|
||||
with self.subTest(changed=changed), pinned_13(), self.assertRaisesRegex(
|
||||
Refused, 'no exact acknowledged machine descriptor'):
|
||||
self.admit(changed, acknowledged)
|
||||
|
||||
def test_mint_secret_admits_only_a_human_actor(self):
|
||||
# Moved from test_admit_1_2_adapter_descriptors.py (NK48-R1-F1) in r5.
|
||||
acknowledged = with_status('acknowledged')
|
||||
mint = self.descriptors['mint-secret']
|
||||
self.assertEqual((mint.effect, mint.actor), ('mint', 'human'))
|
||||
with pinned_13(), self.assertRaisesRegex(Refused, 'principal actor or lane mismatch'):
|
||||
self.admit(mint, acknowledged, actor='agent')
|
||||
|
||||
def test_wrong_lane_is_refused(self):
|
||||
# Moved from test_admit_1_2_adapter_descriptors.py in r5.
|
||||
acknowledged = with_status('acknowledged')
|
||||
for step, desc in self.descriptors.items():
|
||||
with self.subTest(step=step), pinned_13(), self.assertRaisesRegex(
|
||||
Refused, 'principal actor or lane mismatch'):
|
||||
self.admit(desc, acknowledged, lane='wrong-lane')
|
||||
|
||||
def test_earlier_versions_carry_no_execution_map(self):
|
||||
for version in ('1.2',):
|
||||
path = REPO / f'security-case-type-revisions/{version}/oidc-browser-relying-party/type.yaml'
|
||||
with self.subTest(version=version):
|
||||
self.assertNotIn('execution_descriptors', yaml.safe_load(path.read_bytes())['spec'])
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
unittest.main()
|
||||
|
|
@ -0,0 +1,69 @@
|
|||
# NK48-R5-F2 issuer store implementation candidate
|
||||
|
||||
The missing issuer store is now a concrete **proposed Platform source contract**,
|
||||
not a discovered existing grant. Neither patch has been applied to a canonical
|
||||
checkout. Independent Platform review remains necessary before the conditional
|
||||
NetKingdom patch can be integrated; F2 is not declared closed here.
|
||||
|
||||
The proposed reference is cluster-scoped `ClusterSecretStore
|
||||
openbao-keycape-{client_id}-issuer`, with no metadata namespace and use restricted
|
||||
to `sso`. For Forgejo, it binds role `external-secrets-keycape-forgejo` to the
|
||||
existing CCR0041 identity `external-secrets/external-secrets`, audience `openbao`.
|
||||
The exact store/provider/ExternalSecret/key/property/target source is in
|
||||
`platform-candidate/manifests/templates/keycape-issuer-eso.yaml`; its hash is
|
||||
recorded in `review.json` and the conditional type text. The new `-issuer` name
|
||||
is an implementation proposal: it does not reuse the Apps consumer SecretStore
|
||||
or claim that CCR0041 admitted this store name. It preserves type IDs ending in
|
||||
`-` without changing parameters. ClusterSecretStore names use DNS subdomain
|
||||
names; the existing adapter's 63-character target reference bound is retained.
|
||||
|
||||
`platform.candidate.patch` adds only that explicit template, a pure metadata
|
||||
render/validation helper, eight focused fixture tests and an owner contract doc.
|
||||
It contains no Kubernetes/OpenBao API client, apply/delete command, credentials,
|
||||
receipt issuer or catalog grant. Store provisioning is separate owner work; the
|
||||
delivery lane gets only the ExternalSecret document. The template is multi-doc
|
||||
review source and must not be applied through the delivery lane. Its strict spec
|
||||
comparison intentionally refuses drift; installed ESO schema/defaulting and
|
||||
trusted observation authenticity still belong to the real owner binding.
|
||||
|
||||
`netkingdom-entry-point.conditional.patch` changes **only** the issuer
|
||||
`deliver-secret.entry_point` at NetKingdom 3236d500. The prior type is byte-equal
|
||||
to r5's e75fe941 reviewed type. It names the proposed exact kind/name/scope and
|
||||
owner template digest. Parameters, descriptors, modes, lanes, other follow-through,
|
||||
materialization, verification, acceptance, and every rollback entry are unchanged.
|
||||
The root coordinator can combine this text with F3 after Platform source review,
|
||||
then regenerate the existing combined artifact. No source owner acknowledgment,
|
||||
case/type pin, catalog mutation or default revision switch has occurred.
|
||||
|
||||
Eight tests / 42 negative subcases passed using public synthetic metadata:
|
||||
independently fixed Forgejo mapping; wrong kind/name/namespace; role, server,
|
||||
mount, SA/audience or allowed-namespace drift; consumer-store substitution;
|
||||
remoteRef/property/target drift; malformed/unready observations; rejected caller
|
||||
store/path overrides; detached ExternalSecret-only render; type-compatible trailing
|
||||
dash. Both exact patches passed `git apply --check` in disposable scratch.
|
||||
See `focused-tests.*` and `structural-and-patch-checks.json`. These checks are not
|
||||
native delivery, signed evidence, API defaulting, issuer auth or isolation proof.
|
||||
|
||||
The source basis is pinned in `source-pins.json`: r5 F2 at coordinator review
|
||||
lines 429–439; Platform RPF-1.3-C1 explicitly requires a named store and the
|
||||
lane definition explicitly calls for a fixed client-derived Platform constant;
|
||||
CCR0041 grants the Forgejo role/SA/audience but declares no store name;
|
||||
Platform's historical issuer `eso.draft.yaml` supplies ClusterSecretStore,
|
||||
namespace restriction, serviceAccountRef.audiences and fixed target/remoteRef
|
||||
conventions; the scaffold also renders ClusterSecretStore and mount-relative
|
||||
remoteRefs but requires a caller-supplied owner config store_name. The scaffold's
|
||||
historical audience location differs: the candidate follows the issuer draft,
|
||||
and makes no claim about the actual installed schema.
|
||||
|
||||
Bounded source search: Platform, NetKingdom and Mason public YAML/Python/JSON/MD
|
||||
for `openbao-keycape-forgejo` and `external-secrets-keycape-forgejo`, plus Platform
|
||||
issuer/store/scaffold files. Existing hits name the separate consumer store,
|
||||
role/policy or old type text. No fixed Forgejo issuer store declaration was found.
|
||||
This is a public-source finding, not a live-cluster absence claim. No Kubernetes,
|
||||
SSH, Bao, credential route, Secret or private archive was accessed.
|
||||
|
||||
The next source step is independent Platform review of the exact new declaration,
|
||||
then coordinator integration of the accepted text with F3. RPF-1.3-T4's actual
|
||||
capabilities, authentic metadata receipts, per-attempt revocation, guarded
|
||||
created-only rollback and Warden catalog routing remain its existing owner work.
|
||||
No new task, UUID, protocol, policy gate or human approval request is created.
|
||||
|
|
@ -0,0 +1,12 @@
|
|||
{
|
||||
"net-kingdom": {
|
||||
"HEAD": "3236d5000e52e0643f33906a26a7825aa0b67465",
|
||||
"status_porcelain_v1": " M .custodian-brief.md\n M WORK-RECORDS.md\n?? docs/evidence/2026-10-15-cadence-adoption/hourly-observation-capture/\n?? docs/evidence/2026-10-15-cadence-adoption/hourly-observation-inventory.jsonl\n?? docs/evidence/2026-10-15-cadence-adoption/hourly-observation-monitor.json\n?? intakes/NET-IN-0005-single-use-setup-link-mail-handoff.md\n",
|
||||
"index_sha256": "47bcf3291b9613706337c2493c33a065ab089c0c5bbbe545de5f462b67b007a3"
|
||||
},
|
||||
"railiance-platform": {
|
||||
"HEAD": "0a9617a62d4696a8070798923b459e8405c3dbf2",
|
||||
"status_porcelain_v1": "M .custodian-brief.md\n M WORK-RECORDS.md\n",
|
||||
"index_sha256": "40f766948f7774c6215ab562e50b4154e9bbdcfbf3a14488625c95afc9cf6fe6"
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,10 @@
|
|||
{
|
||||
"schema": "custodian.source-test-result.v1",
|
||||
"scope": "synthetic public metadata only; no API, native, credential, receipt-authenticity or isolation proof",
|
||||
"command": "python3 -B platform-candidate/tests/test_keycape_issuer_eso.py",
|
||||
"tests": 8,
|
||||
"subtests": 42,
|
||||
"passed": true,
|
||||
"python": "3.12.3",
|
||||
"pyyaml": "6.0.1"
|
||||
}
|
||||
|
|
@ -0,0 +1,13 @@
|
|||
test_delivery_document_is_only_external_and_detached (fixture.FixedIssuerContract.test_delivery_document_is_only_external_and_detached) ... ok
|
||||
test_external_drift_refuses (fixture.FixedIssuerContract.test_external_drift_refuses) ... ok
|
||||
test_independent_forgejo_binding (fixture.FixedIssuerContract.test_independent_forgejo_binding) ... ok
|
||||
test_missing_or_unready_refuses (fixture.FixedIssuerContract.test_missing_or_unready_refuses) ... ok
|
||||
test_no_caller_store_or_path_override (fixture.FixedIssuerContract.test_no_caller_store_or_path_override) ... ok
|
||||
test_store_drift_refuses (fixture.FixedIssuerContract.test_store_drift_refuses) ... ok
|
||||
test_type_admitted_trailing_dash_is_preserved (fixture.FixedIssuerContract.test_type_admitted_trailing_dash_is_preserved) ... ok
|
||||
test_valid_ready_metadata_allows_extra_controller_metadata (fixture.FixedIssuerContract.test_valid_ready_metadata_allows_extra_controller_metadata) ... ok
|
||||
|
||||
----------------------------------------------------------------------
|
||||
Ran 8 tests in 0.247s
|
||||
|
||||
OK
|
||||
|
|
@ -0,0 +1,20 @@
|
|||
{
|
||||
"schema": "custodian.source-packet-manifest.v1",
|
||||
"sha256": {
|
||||
"OWNER-REVIEW.md": "sha256:7983fd841f09bc7584047426912c33d568c97b4c1126776f99aac42b34d2b742",
|
||||
"canonical-source-readonly-state.json": "sha256:129d6926bb996e510ab4b41d30e738096b53b71de1fb2dd470fe0ac94848cbec",
|
||||
"focused-tests.json": "sha256:8de5862a8d752dce0b3b96a9275e42952a99415a18d3f67c06bf9f5b6008d0e3",
|
||||
"focused-tests.txt": "sha256:aab46c51d87198ad4be6dd3078f61286e2faf1ef4d76fa14298698388f78ab24",
|
||||
"netkingdom-entry-point.conditional.patch": "sha256:6784a21fbd10bd62712be2e0f4929034601c94629aa74ccaa2e93fc815eecd61",
|
||||
"platform-candidate/docs/keycape-issuer-eso-contract.md": "sha256:7f59e59b17c58252982931ac61fe50dd7a07340add6c437fe48af36971eafe68",
|
||||
"platform-candidate/manifests/templates/keycape-issuer-eso.yaml": "sha256:e17498bd8a4c820041a4105e4efe9c60c6a50953db8f621ff54887080065c743",
|
||||
"platform-candidate/scripts/keycape_issuer_eso.py": "sha256:21c5797280c2a7825dbda8ac970d34a0cdef4c6458f68bce0b1c12407f122a87",
|
||||
"platform-candidate/tests/test_keycape_issuer_eso.py": "sha256:223d4c5b349062b9f93e6af79e8d5feda44377ba41a11726f6f2b762691e8308",
|
||||
"platform.candidate.patch": "sha256:a490df7dee41e15351da8d2906e1342edebb533984befe3d9ae74d952dbb091b",
|
||||
"review.json": "sha256:d99db9ab4f326308cde464129bbbb7573434e500fd03c130a37a590bac3643a4",
|
||||
"source-pins.json": "sha256:6e896b6196a0aca01ddaf433b4d14cc041fc0296c5dd6404c3a698b941b61bd8",
|
||||
"structural-and-patch-checks.json": "sha256:a3dc4cd405c412d4369986cc6736cd2d9eed02d4b86d95d62d5a9914106fdb14",
|
||||
"type.before.yaml": "sha256:e75fe94110ef9fb7fb18072fe2856941b33507c99cb406d36576046481485d0a",
|
||||
"type.candidate.yaml": "sha256:ef02ddae3167bdd1558193a5f6db0379fb8cc84207531706d5ef35caabc3684f"
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,16 @@
|
|||
--- a/security-case-type-revisions/1.3/oidc-browser-relying-party/type.yaml
|
||||
+++ b/security-case-type-revisions/1.3/oidc-browser-relying-party/type.yaml
|
||||
@@ -269,8 +269,11 @@
|
||||
when: client_type == confidential
|
||||
entry_point: reviewed adapter railiance-platform/keycape-deliver-secret, dispatched
|
||||
by Warden's case kernel; Platform's deliver capability applies ExternalSecret
|
||||
- sso/keycape-{client_id}-client (key client-secret) through the ESO store for
|
||||
- role external-secrets-keycape-{client_id}, from bao:platform/workloads/keycape/{client_id}
|
||||
+ sso/keycape-{client_id}-client (key client-secret) through fixed ClusterSecretStore
|
||||
+ openbao-keycape-{client_id}-issuer (cluster-scoped, restricted to namespace sso),
|
||||
+ defined by Platform-controlled manifests/templates/keycape-issuer-eso.yaml
|
||||
+ (sha256:e17498bd8a4c820041a4105e4efe9c60c6a50953db8f621ff54887080065c743),
|
||||
+ using role external-secrets-keycape-{client_id}, from bao:platform/workloads/keycape/{client_id}
|
||||
auth: Platform's own delivery path on lane platform-secret-delivery, inside the human
|
||||
aal2 session; ESO reads custody through role external-secrets-keycape-{client_id};
|
||||
Warden holds no Platform credential, and no value leaves custody
|
||||
|
|
@ -0,0 +1,68 @@
|
|||
# Proposed fixed issuer ESO contract — RPF-1.3-T4 / NK48-R5-F2
|
||||
|
||||
Status: proposed source; not admitted, deployed, or owner-acknowledged. This is a
|
||||
new Platform declaration for review, not an existing Forgejo grant discovery.
|
||||
|
||||
The only substitution in `manifests/templates/keycape-issuer-eso.yaml`
|
||||
(sha256:e17498bd8a4c820041a4105e4efe9c60c6a50953db8f621ff54887080065c743) is the admitted `client_id`. The proposed issuer
|
||||
store is **ClusterSecretStore `openbao-keycape-{client_id}-issuer`**, cluster
|
||||
scoped (no metadata namespace), restricted by conditions to ExternalSecrets in
|
||||
`sso`. For Forgejo it is `openbao-keycape-forgejo-issuer`. The suffix makes the
|
||||
issuer purpose explicit and keeps type-admitted IDs ending in `-` representable.
|
||||
The store's DNS subdomain name can exceed 63 characters; the existing adapter's
|
||||
63-character target Secret reference bound is preserved (client ID at most 48).
|
||||
No type parameter is added or changed.
|
||||
|
||||
The provider is `http://openbao.openbao.svc:8200`, KV v2 mount `platform`,
|
||||
Kubernetes auth mount `kubernetes`, role `external-secrets-keycape-{client_id}`,
|
||||
serviceAccountRef `external-secrets/external-secrets`, audience `openbao`.
|
||||
CCR-2026-0041 already records that Forgejo role/identity/audience, not this new
|
||||
store name or an unrestricted client family grant. Other clients still require
|
||||
their own existing owner-controlled role/case admission.
|
||||
|
||||
The ExternalSecret is `sso/keycape-{client_id}-client`, references precisely that
|
||||
ClusterSecretStore, refreshes every 5m and targets the same name with Owner
|
||||
creation and Retain remote deletion. Its only data entry maps `client-secret`
|
||||
from remoteRef `workloads/keycape/{client_id}`, property `CLIENT_SECRET`. The
|
||||
provider mount plus mount-relative remoteRef equals custody reference
|
||||
`platform/workloads/keycape/{client_id}`; do not double-prefix `platform/`.
|
||||
This is distinct from the Apps-owned consumer namespace SecretStore
|
||||
`openbao-keycape-{client_id}-consumer` and its dedicated consumer identity.
|
||||
|
||||
Store provisioning belongs to Platform's separate owner-controlled path. The
|
||||
proposed `platform-secret-delivery` lane retains ONLY create/get/watch/delete on
|
||||
the exact ExternalSecret in `sso`; this source does not delegate store creation
|
||||
or Secret read/list. Never apply the multi-document template through that lane.
|
||||
The pure `scripts/keycape_issuer_eso.py` module exposes only the ExternalSecret
|
||||
document through `external_secret()` and validates fixed public observations
|
||||
through `require_fixed_store()` and `require_ready_delivery()`. It performs no
|
||||
API call and issues no evidence/receipt. It is not a real capability or admission
|
||||
boundary; a caller-supplied observation alone establishes no authenticity.
|
||||
|
||||
The existing RPF-1.3-T4 delivery binding must validate the case/client binding,
|
||||
verify this exact preexisting store's public identity/spec/Ready state, apply or
|
||||
compare the exact ExternalSecret, and wait for matching spec plus Ready=True.
|
||||
The proposed helper accepts extra controller metadata but deliberately refuses
|
||||
spec drift. Actual API defaulting/normalization and installed ESO schema support
|
||||
must be checked in that implementation; these fixtures establish no native
|
||||
compatibility or credential isolation. ESO's shared controller remains trusted.
|
||||
No Secret bodies, values, arbitrary remoteRefs, or raw errors may enter receipts.
|
||||
|
||||
Existing owner requirements remain: closed `netkingdom.deliver-secret.v1`
|
||||
metadata receipt with authenticated before/after UID/resourceVersion provenance,
|
||||
unchanged objects reported NO_CHANGE, attempt-session revocation, and created-only
|
||||
rollback guarded by exact UID/resourceVersion (drift or uncertain ownership goes
|
||||
to owner reconciliation). The helper neither deletes a target Secret nor creates
|
||||
an OpenBao role/policy nor changes CCR-0041. These capabilities and catalog routing
|
||||
remain unimplemented/unaccepted under RPF-1.3-T4. Independent Platform source
|
||||
review of this declaration precedes adopting the conditional NetKingdom type
|
||||
text; no descriptor acceptance, acknowledgment, pin, grant, catalog or native
|
||||
permission is inferred.
|
||||
|
||||
Grounding: CCR-2026-0041 exact role/identity; the approved historical
|
||||
`docs/provisioning-windows/2026-10-04-1000/keycape/eso.draft.yaml` issuer
|
||||
ClusterSecretStore and target pattern; `scripts/catalog_lane_scaffold.py`
|
||||
ClusterSecretStore/namespace restriction and mount-relative remoteRef model;
|
||||
RPF-1.3-C1 and RPF-1.3-T4 in the frozen owner review. The scaffold's historical
|
||||
audience field placement differs from the issuer draft; this candidate uses
|
||||
the issuer draft's serviceAccountRef.audiences, not a claim of installed support.
|
||||
|
|
@ -0,0 +1,46 @@
|
|||
# PROPOSED SOURCE ONLY — RPF-1.3-T4 / NK48-R5-F2; not admitted or applied.
|
||||
# Platform controls this template. Only admitted client_id is substituted.
|
||||
# Provision the store through Platform's separate reviewed owner path; the
|
||||
# platform-secret-delivery lane applies ONLY the ExternalSecret document.
|
||||
apiVersion: external-secrets.io/v1
|
||||
kind: ClusterSecretStore
|
||||
metadata:
|
||||
name: openbao-keycape-{client_id}-issuer
|
||||
spec:
|
||||
provider:
|
||||
vault:
|
||||
server: http://openbao.openbao.svc:8200
|
||||
path: platform
|
||||
version: v2
|
||||
auth:
|
||||
kubernetes:
|
||||
mountPath: kubernetes
|
||||
role: external-secrets-keycape-{client_id}
|
||||
serviceAccountRef:
|
||||
name: external-secrets
|
||||
namespace: external-secrets
|
||||
audiences:
|
||||
- openbao
|
||||
conditions:
|
||||
- namespaces:
|
||||
- sso
|
||||
---
|
||||
apiVersion: external-secrets.io/v1
|
||||
kind: ExternalSecret
|
||||
metadata:
|
||||
name: keycape-{client_id}-client
|
||||
namespace: sso
|
||||
spec:
|
||||
refreshInterval: 5m
|
||||
secretStoreRef:
|
||||
kind: ClusterSecretStore
|
||||
name: openbao-keycape-{client_id}-issuer
|
||||
target:
|
||||
name: keycape-{client_id}-client
|
||||
creationPolicy: Owner
|
||||
deletionPolicy: Retain
|
||||
data:
|
||||
- secretKey: client-secret
|
||||
remoteRef:
|
||||
key: workloads/keycape/{client_id}
|
||||
property: CLIENT_SECRET
|
||||
|
|
@ -0,0 +1,70 @@
|
|||
"""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 _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 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])
|
||||
|
|
@ -0,0 +1,129 @@
|
|||
"""Public metadata fixtures only: no API, credentials, or native calls."""
|
||||
import copy
|
||||
import sys
|
||||
import unittest
|
||||
from pathlib import Path
|
||||
|
||||
sys.path.insert(0, str(Path(__file__).resolve().parents[1] / "scripts"))
|
||||
import keycape_issuer_eso as contract
|
||||
|
||||
|
||||
class FixedIssuerContract(unittest.TestCase):
|
||||
def observations(self):
|
||||
resources = contract.documents("forgejo")
|
||||
for resource in resources:
|
||||
resource["metadata"].update(uid="public-fixture-uid", resourceVersion="7")
|
||||
resource["status"] = {"conditions": [{"type": "Ready", "status": "True"}]}
|
||||
return resources
|
||||
|
||||
def test_independent_forgejo_binding(self):
|
||||
# Independent owner-source example: CCR0041 role/SA/audience and the
|
||||
# existing issuer ESO draft's provider/target/relative key convention.
|
||||
store, external = contract.documents("forgejo")
|
||||
self.assertEqual(store["kind"], "ClusterSecretStore")
|
||||
self.assertEqual(store["metadata"], {"name": "openbao-keycape-forgejo-issuer"})
|
||||
self.assertEqual(store["spec"], {
|
||||
"provider": {"vault": {
|
||||
"server": "http://openbao.openbao.svc:8200", "path": "platform", "version": "v2",
|
||||
"auth": {"kubernetes": {"mountPath": "kubernetes",
|
||||
"role": "external-secrets-keycape-forgejo",
|
||||
"serviceAccountRef": {"name": "external-secrets", "namespace": "external-secrets",
|
||||
"audiences": ["openbao"]}}}}},
|
||||
"conditions": [{"namespaces": ["sso"]}]})
|
||||
self.assertEqual(external["metadata"], {"name": "keycape-forgejo-client", "namespace": "sso"})
|
||||
self.assertEqual(external["spec"], {
|
||||
"refreshInterval": "5m",
|
||||
"secretStoreRef": {"kind": "ClusterSecretStore", "name": "openbao-keycape-forgejo-issuer"},
|
||||
"target": {"name": "keycape-forgejo-client", "creationPolicy": "Owner", "deletionPolicy": "Retain"},
|
||||
"data": [{"secretKey": "client-secret", "remoteRef": {
|
||||
"key": "workloads/keycape/forgejo", "property": "CLIENT_SECRET"}}]})
|
||||
|
||||
def test_valid_ready_metadata_allows_extra_controller_metadata(self):
|
||||
store, external = self.observations()
|
||||
contract.require_fixed_store(store, "forgejo")
|
||||
contract.require_ready_delivery(external, "forgejo")
|
||||
|
||||
def test_store_drift_refuses(self):
|
||||
changes = [
|
||||
("kind", "SecretStore"),
|
||||
("metadata.namespace", "sso"),
|
||||
("metadata.name", "caller-store"),
|
||||
("spec.provider.vault.server", "https://wrong.invalid"),
|
||||
("spec.provider.vault.path", "tenant"),
|
||||
("spec.provider.vault.auth.kubernetes.role", "external-secrets-keycape-sibling"),
|
||||
("spec.provider.vault.auth.kubernetes.serviceAccountRef.audiences", ["wrong"]),
|
||||
("spec.provider.vault.auth.kubernetes.serviceAccountRef.namespace", "sso"),
|
||||
("spec.conditions", [{"namespaces": ["sso", "other"]}]),
|
||||
]
|
||||
for path, value in changes:
|
||||
with self.subTest(path=path):
|
||||
store = self.observations()[0]
|
||||
mutate(store, path, value)
|
||||
with self.assertRaises(contract.Refused):
|
||||
contract.require_fixed_store(store, "forgejo")
|
||||
|
||||
def test_external_drift_refuses(self):
|
||||
changes = [
|
||||
("metadata.namespace", ""), ("metadata.namespace", None), ("metadata.namespace", "other"),
|
||||
("metadata.name", "keycape-sibling-client"),
|
||||
("spec.secretStoreRef.kind", "SecretStore"),
|
||||
("spec.secretStoreRef.name", "openbao-keycape-forgejo-consumer"),
|
||||
("spec.target.name", "other"), ("spec.target.creationPolicy", "Merge"),
|
||||
("spec.target.deletionPolicy", "Delete"),
|
||||
("spec.data", [{"secretKey": "client-secret", "remoteRef": {
|
||||
"key": "platform/workloads/keycape/forgejo", "property": "CLIENT_SECRET"}}]),
|
||||
("spec.data", [{"secretKey": "wrong", "remoteRef": {
|
||||
"key": "workloads/keycape/forgejo", "property": "CLIENT_SECRET"}}]),
|
||||
("spec.data", [{"secretKey": "client-secret", "remoteRef": {
|
||||
"key": "workloads/keycape/forgejo", "property": "wrong"}}]),
|
||||
]
|
||||
for path, value in changes:
|
||||
with self.subTest(path=path, value=value):
|
||||
external = self.observations()[1]
|
||||
mutate(external, path, value)
|
||||
with self.assertRaises(contract.Refused):
|
||||
contract.require_ready_delivery(external, "forgejo")
|
||||
|
||||
def test_missing_or_unready_refuses(self):
|
||||
for check, index in [(contract.require_fixed_store, 0), (contract.require_ready_delivery, 1)]:
|
||||
for status in [None, {}, [], "bad", {"conditions": None},
|
||||
{"conditions": [{"type": "Ready", "status": "False"}]}]:
|
||||
with self.subTest(check=check.__name__, status=status):
|
||||
observed = self.observations()[index]
|
||||
observed["status"] = status
|
||||
with self.assertRaises(contract.Refused):
|
||||
check(observed, "forgejo")
|
||||
for malformed in [None, [], {}, {"metadata": None}]:
|
||||
with self.assertRaises(contract.Refused):
|
||||
check(malformed, "forgejo")
|
||||
|
||||
def test_no_caller_store_or_path_override(self):
|
||||
for invalid in ["", "f", "../forgejo", "forgejo/sibling", "Forgejo", "forgejo\n", "-forgejo", "f" * 49, None]:
|
||||
with self.subTest(invalid=invalid), self.assertRaises(contract.Refused):
|
||||
contract.documents(invalid)
|
||||
with self.assertRaises(TypeError):
|
||||
contract.external_secret("forgejo", store_name="caller-store")
|
||||
|
||||
def test_type_admitted_trailing_dash_is_preserved(self):
|
||||
self.assertEqual(contract.external_secret("forgejo-")["metadata"]["name"],
|
||||
"keycape-forgejo--client")
|
||||
self.assertEqual(contract.documents("forgejo-")[0]["metadata"]["name"],
|
||||
"openbao-keycape-forgejo--issuer")
|
||||
|
||||
def test_delivery_document_is_only_external_and_detached(self):
|
||||
external = contract.external_secret("forgejo")
|
||||
self.assertEqual(external["kind"], "ExternalSecret")
|
||||
external["spec"]["secretStoreRef"]["name"] = "tampered"
|
||||
self.assertEqual(contract.external_secret("forgejo")["spec"]["secretStoreRef"]["name"],
|
||||
"openbao-keycape-forgejo-issuer")
|
||||
|
||||
|
||||
def mutate(resource, path, value):
|
||||
parts = path.split(".")
|
||||
for key in parts[:-1]:
|
||||
resource = resource[key]
|
||||
resource[parts[-1]] = copy.deepcopy(value)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main(verbosity=2)
|
||||
|
|
@ -0,0 +1,325 @@
|
|||
--- /dev/null
|
||||
+++ b/docs/keycape-issuer-eso-contract.md
|
||||
@@ -0,0 +1,68 @@
|
||||
+# Proposed fixed issuer ESO contract — RPF-1.3-T4 / NK48-R5-F2
|
||||
+
|
||||
+Status: proposed source; not admitted, deployed, or owner-acknowledged. This is a
|
||||
+new Platform declaration for review, not an existing Forgejo grant discovery.
|
||||
+
|
||||
+The only substitution in `manifests/templates/keycape-issuer-eso.yaml`
|
||||
+(sha256:e17498bd8a4c820041a4105e4efe9c60c6a50953db8f621ff54887080065c743) is the admitted `client_id`. The proposed issuer
|
||||
+store is **ClusterSecretStore `openbao-keycape-{client_id}-issuer`**, cluster
|
||||
+scoped (no metadata namespace), restricted by conditions to ExternalSecrets in
|
||||
+`sso`. For Forgejo it is `openbao-keycape-forgejo-issuer`. The suffix makes the
|
||||
+issuer purpose explicit and keeps type-admitted IDs ending in `-` representable.
|
||||
+The store's DNS subdomain name can exceed 63 characters; the existing adapter's
|
||||
+63-character target Secret reference bound is preserved (client ID at most 48).
|
||||
+No type parameter is added or changed.
|
||||
+
|
||||
+The provider is `http://openbao.openbao.svc:8200`, KV v2 mount `platform`,
|
||||
+Kubernetes auth mount `kubernetes`, role `external-secrets-keycape-{client_id}`,
|
||||
+serviceAccountRef `external-secrets/external-secrets`, audience `openbao`.
|
||||
+CCR-2026-0041 already records that Forgejo role/identity/audience, not this new
|
||||
+store name or an unrestricted client family grant. Other clients still require
|
||||
+their own existing owner-controlled role/case admission.
|
||||
+
|
||||
+The ExternalSecret is `sso/keycape-{client_id}-client`, references precisely that
|
||||
+ClusterSecretStore, refreshes every 5m and targets the same name with Owner
|
||||
+creation and Retain remote deletion. Its only data entry maps `client-secret`
|
||||
+from remoteRef `workloads/keycape/{client_id}`, property `CLIENT_SECRET`. The
|
||||
+provider mount plus mount-relative remoteRef equals custody reference
|
||||
+`platform/workloads/keycape/{client_id}`; do not double-prefix `platform/`.
|
||||
+This is distinct from the Apps-owned consumer namespace SecretStore
|
||||
+`openbao-keycape-{client_id}-consumer` and its dedicated consumer identity.
|
||||
+
|
||||
+Store provisioning belongs to Platform's separate owner-controlled path. The
|
||||
+proposed `platform-secret-delivery` lane retains ONLY create/get/watch/delete on
|
||||
+the exact ExternalSecret in `sso`; this source does not delegate store creation
|
||||
+or Secret read/list. Never apply the multi-document template through that lane.
|
||||
+The pure `scripts/keycape_issuer_eso.py` module exposes only the ExternalSecret
|
||||
+document through `external_secret()` and validates fixed public observations
|
||||
+through `require_fixed_store()` and `require_ready_delivery()`. It performs no
|
||||
+API call and issues no evidence/receipt. It is not a real capability or admission
|
||||
+boundary; a caller-supplied observation alone establishes no authenticity.
|
||||
+
|
||||
+The existing RPF-1.3-T4 delivery binding must validate the case/client binding,
|
||||
+verify this exact preexisting store's public identity/spec/Ready state, apply or
|
||||
+compare the exact ExternalSecret, and wait for matching spec plus Ready=True.
|
||||
+The proposed helper accepts extra controller metadata but deliberately refuses
|
||||
+spec drift. Actual API defaulting/normalization and installed ESO schema support
|
||||
+must be checked in that implementation; these fixtures establish no native
|
||||
+compatibility or credential isolation. ESO's shared controller remains trusted.
|
||||
+No Secret bodies, values, arbitrary remoteRefs, or raw errors may enter receipts.
|
||||
+
|
||||
+Existing owner requirements remain: closed `netkingdom.deliver-secret.v1`
|
||||
+metadata receipt with authenticated before/after UID/resourceVersion provenance,
|
||||
+unchanged objects reported NO_CHANGE, attempt-session revocation, and created-only
|
||||
+rollback guarded by exact UID/resourceVersion (drift or uncertain ownership goes
|
||||
+to owner reconciliation). The helper neither deletes a target Secret nor creates
|
||||
+an OpenBao role/policy nor changes CCR-0041. These capabilities and catalog routing
|
||||
+remain unimplemented/unaccepted under RPF-1.3-T4. Independent Platform source
|
||||
+review of this declaration precedes adopting the conditional NetKingdom type
|
||||
+text; no descriptor acceptance, acknowledgment, pin, grant, catalog or native
|
||||
+permission is inferred.
|
||||
+
|
||||
+Grounding: CCR-2026-0041 exact role/identity; the approved historical
|
||||
+`docs/provisioning-windows/2026-10-04-1000/keycape/eso.draft.yaml` issuer
|
||||
+ClusterSecretStore and target pattern; `scripts/catalog_lane_scaffold.py`
|
||||
+ClusterSecretStore/namespace restriction and mount-relative remoteRef model;
|
||||
+RPF-1.3-C1 and RPF-1.3-T4 in the frozen owner review. The scaffold's historical
|
||||
+audience field placement differs from the issuer draft; this candidate uses
|
||||
+the issuer draft's serviceAccountRef.audiences, not a claim of installed support.
|
||||
--- /dev/null
|
||||
+++ b/manifests/templates/keycape-issuer-eso.yaml
|
||||
@@ -0,0 +1,46 @@
|
||||
+# PROPOSED SOURCE ONLY — RPF-1.3-T4 / NK48-R5-F2; not admitted or applied.
|
||||
+# Platform controls this template. Only admitted client_id is substituted.
|
||||
+# Provision the store through Platform's separate reviewed owner path; the
|
||||
+# platform-secret-delivery lane applies ONLY the ExternalSecret document.
|
||||
+apiVersion: external-secrets.io/v1
|
||||
+kind: ClusterSecretStore
|
||||
+metadata:
|
||||
+ name: openbao-keycape-{client_id}-issuer
|
||||
+spec:
|
||||
+ provider:
|
||||
+ vault:
|
||||
+ server: http://openbao.openbao.svc:8200
|
||||
+ path: platform
|
||||
+ version: v2
|
||||
+ auth:
|
||||
+ kubernetes:
|
||||
+ mountPath: kubernetes
|
||||
+ role: external-secrets-keycape-{client_id}
|
||||
+ serviceAccountRef:
|
||||
+ name: external-secrets
|
||||
+ namespace: external-secrets
|
||||
+ audiences:
|
||||
+ - openbao
|
||||
+ conditions:
|
||||
+ - namespaces:
|
||||
+ - sso
|
||||
+---
|
||||
+apiVersion: external-secrets.io/v1
|
||||
+kind: ExternalSecret
|
||||
+metadata:
|
||||
+ name: keycape-{client_id}-client
|
||||
+ namespace: sso
|
||||
+spec:
|
||||
+ refreshInterval: 5m
|
||||
+ secretStoreRef:
|
||||
+ kind: ClusterSecretStore
|
||||
+ name: openbao-keycape-{client_id}-issuer
|
||||
+ target:
|
||||
+ name: keycape-{client_id}-client
|
||||
+ creationPolicy: Owner
|
||||
+ deletionPolicy: Retain
|
||||
+ data:
|
||||
+ - secretKey: client-secret
|
||||
+ remoteRef:
|
||||
+ key: workloads/keycape/{client_id}
|
||||
+ property: CLIENT_SECRET
|
||||
--- /dev/null
|
||||
+++ b/scripts/keycape_issuer_eso.py
|
||||
@@ -0,0 +1,70 @@
|
||||
+"""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 _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 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])
|
||||
--- /dev/null
|
||||
+++ b/tests/test_keycape_issuer_eso.py
|
||||
@@ -0,0 +1,129 @@
|
||||
+"""Public metadata fixtures only: no API, credentials, or native calls."""
|
||||
+import copy
|
||||
+import sys
|
||||
+import unittest
|
||||
+from pathlib import Path
|
||||
+
|
||||
+sys.path.insert(0, str(Path(__file__).resolve().parents[1] / "scripts"))
|
||||
+import keycape_issuer_eso as contract
|
||||
+
|
||||
+
|
||||
+class FixedIssuerContract(unittest.TestCase):
|
||||
+ def observations(self):
|
||||
+ resources = contract.documents("forgejo")
|
||||
+ for resource in resources:
|
||||
+ resource["metadata"].update(uid="public-fixture-uid", resourceVersion="7")
|
||||
+ resource["status"] = {"conditions": [{"type": "Ready", "status": "True"}]}
|
||||
+ return resources
|
||||
+
|
||||
+ def test_independent_forgejo_binding(self):
|
||||
+ # Independent owner-source example: CCR0041 role/SA/audience and the
|
||||
+ # existing issuer ESO draft's provider/target/relative key convention.
|
||||
+ store, external = contract.documents("forgejo")
|
||||
+ self.assertEqual(store["kind"], "ClusterSecretStore")
|
||||
+ self.assertEqual(store["metadata"], {"name": "openbao-keycape-forgejo-issuer"})
|
||||
+ self.assertEqual(store["spec"], {
|
||||
+ "provider": {"vault": {
|
||||
+ "server": "http://openbao.openbao.svc:8200", "path": "platform", "version": "v2",
|
||||
+ "auth": {"kubernetes": {"mountPath": "kubernetes",
|
||||
+ "role": "external-secrets-keycape-forgejo",
|
||||
+ "serviceAccountRef": {"name": "external-secrets", "namespace": "external-secrets",
|
||||
+ "audiences": ["openbao"]}}}}},
|
||||
+ "conditions": [{"namespaces": ["sso"]}]})
|
||||
+ self.assertEqual(external["metadata"], {"name": "keycape-forgejo-client", "namespace": "sso"})
|
||||
+ self.assertEqual(external["spec"], {
|
||||
+ "refreshInterval": "5m",
|
||||
+ "secretStoreRef": {"kind": "ClusterSecretStore", "name": "openbao-keycape-forgejo-issuer"},
|
||||
+ "target": {"name": "keycape-forgejo-client", "creationPolicy": "Owner", "deletionPolicy": "Retain"},
|
||||
+ "data": [{"secretKey": "client-secret", "remoteRef": {
|
||||
+ "key": "workloads/keycape/forgejo", "property": "CLIENT_SECRET"}}]})
|
||||
+
|
||||
+ def test_valid_ready_metadata_allows_extra_controller_metadata(self):
|
||||
+ store, external = self.observations()
|
||||
+ contract.require_fixed_store(store, "forgejo")
|
||||
+ contract.require_ready_delivery(external, "forgejo")
|
||||
+
|
||||
+ def test_store_drift_refuses(self):
|
||||
+ changes = [
|
||||
+ ("kind", "SecretStore"),
|
||||
+ ("metadata.namespace", "sso"),
|
||||
+ ("metadata.name", "caller-store"),
|
||||
+ ("spec.provider.vault.server", "https://wrong.invalid"),
|
||||
+ ("spec.provider.vault.path", "tenant"),
|
||||
+ ("spec.provider.vault.auth.kubernetes.role", "external-secrets-keycape-sibling"),
|
||||
+ ("spec.provider.vault.auth.kubernetes.serviceAccountRef.audiences", ["wrong"]),
|
||||
+ ("spec.provider.vault.auth.kubernetes.serviceAccountRef.namespace", "sso"),
|
||||
+ ("spec.conditions", [{"namespaces": ["sso", "other"]}]),
|
||||
+ ]
|
||||
+ for path, value in changes:
|
||||
+ with self.subTest(path=path):
|
||||
+ store = self.observations()[0]
|
||||
+ mutate(store, path, value)
|
||||
+ with self.assertRaises(contract.Refused):
|
||||
+ contract.require_fixed_store(store, "forgejo")
|
||||
+
|
||||
+ def test_external_drift_refuses(self):
|
||||
+ changes = [
|
||||
+ ("metadata.namespace", ""), ("metadata.namespace", None), ("metadata.namespace", "other"),
|
||||
+ ("metadata.name", "keycape-sibling-client"),
|
||||
+ ("spec.secretStoreRef.kind", "SecretStore"),
|
||||
+ ("spec.secretStoreRef.name", "openbao-keycape-forgejo-consumer"),
|
||||
+ ("spec.target.name", "other"), ("spec.target.creationPolicy", "Merge"),
|
||||
+ ("spec.target.deletionPolicy", "Delete"),
|
||||
+ ("spec.data", [{"secretKey": "client-secret", "remoteRef": {
|
||||
+ "key": "platform/workloads/keycape/forgejo", "property": "CLIENT_SECRET"}}]),
|
||||
+ ("spec.data", [{"secretKey": "wrong", "remoteRef": {
|
||||
+ "key": "workloads/keycape/forgejo", "property": "CLIENT_SECRET"}}]),
|
||||
+ ("spec.data", [{"secretKey": "client-secret", "remoteRef": {
|
||||
+ "key": "workloads/keycape/forgejo", "property": "wrong"}}]),
|
||||
+ ]
|
||||
+ for path, value in changes:
|
||||
+ with self.subTest(path=path, value=value):
|
||||
+ external = self.observations()[1]
|
||||
+ mutate(external, path, value)
|
||||
+ with self.assertRaises(contract.Refused):
|
||||
+ contract.require_ready_delivery(external, "forgejo")
|
||||
+
|
||||
+ def test_missing_or_unready_refuses(self):
|
||||
+ for check, index in [(contract.require_fixed_store, 0), (contract.require_ready_delivery, 1)]:
|
||||
+ for status in [None, {}, [], "bad", {"conditions": None},
|
||||
+ {"conditions": [{"type": "Ready", "status": "False"}]}]:
|
||||
+ with self.subTest(check=check.__name__, status=status):
|
||||
+ observed = self.observations()[index]
|
||||
+ observed["status"] = status
|
||||
+ with self.assertRaises(contract.Refused):
|
||||
+ check(observed, "forgejo")
|
||||
+ for malformed in [None, [], {}, {"metadata": None}]:
|
||||
+ with self.assertRaises(contract.Refused):
|
||||
+ check(malformed, "forgejo")
|
||||
+
|
||||
+ def test_no_caller_store_or_path_override(self):
|
||||
+ for invalid in ["", "f", "../forgejo", "forgejo/sibling", "Forgejo", "forgejo\n", "-forgejo", "f" * 49, None]:
|
||||
+ with self.subTest(invalid=invalid), self.assertRaises(contract.Refused):
|
||||
+ contract.documents(invalid)
|
||||
+ with self.assertRaises(TypeError):
|
||||
+ contract.external_secret("forgejo", store_name="caller-store")
|
||||
+
|
||||
+ def test_type_admitted_trailing_dash_is_preserved(self):
|
||||
+ self.assertEqual(contract.external_secret("forgejo-")["metadata"]["name"],
|
||||
+ "keycape-forgejo--client")
|
||||
+ self.assertEqual(contract.documents("forgejo-")[0]["metadata"]["name"],
|
||||
+ "openbao-keycape-forgejo--issuer")
|
||||
+
|
||||
+ def test_delivery_document_is_only_external_and_detached(self):
|
||||
+ external = contract.external_secret("forgejo")
|
||||
+ self.assertEqual(external["kind"], "ExternalSecret")
|
||||
+ external["spec"]["secretStoreRef"]["name"] = "tampered"
|
||||
+ self.assertEqual(contract.external_secret("forgejo")["spec"]["secretStoreRef"]["name"],
|
||||
+ "openbao-keycape-forgejo-issuer")
|
||||
+
|
||||
+
|
||||
+def mutate(resource, path, value):
|
||||
+ parts = path.split(".")
|
||||
+ for key in parts[:-1]:
|
||||
+ resource = resource[key]
|
||||
+ resource[parts[-1]] = copy.deepcopy(value)
|
||||
+
|
||||
+
|
||||
+if __name__ == "__main__":
|
||||
+ unittest.main(verbosity=2)
|
||||
|
|
@ -0,0 +1,66 @@
|
|||
{
|
||||
"schema": "custodian.nk48-r5-f2-implementation-candidate.v1",
|
||||
"status": "proposed-source-not-admitted",
|
||||
"date": "2026-10-17",
|
||||
"scope": "NK-WP-0048-T16; existing RPF-1.3-T4 implementation preparation only",
|
||||
"source_owner_assent_received": false,
|
||||
"finding": "NK48-R5-F2",
|
||||
"owner_requirement": "RPF-1.3-C1",
|
||||
"netkingdom_base": "3236d5000e52e0643f33906a26a7825aa0b67465",
|
||||
"reviewed_code": "001a38ca2aa85155ce4840c4da22aca6e2346171",
|
||||
"platform_base": "0a9617a62d4696a8070798923b459e8405c3dbf2",
|
||||
"discovery": "No existing Forgejo issuer store declaration found in bounded public source search. No claim about live absence.",
|
||||
"new_proposed_binding": {
|
||||
"kind": "ClusterSecretStore",
|
||||
"name": "openbao-keycape-{client_id}-issuer",
|
||||
"namespace": null,
|
||||
"allowed_external_secret_namespace": "sso",
|
||||
"role": "external-secrets-keycape-{client_id}",
|
||||
"service_account_ref": "external-secrets/external-secrets",
|
||||
"audiences": [
|
||||
"openbao"
|
||||
],
|
||||
"template_path": "manifests/templates/keycape-issuer-eso.yaml",
|
||||
"template_sha256": "e17498bd8a4c820041a4105e4efe9c60c6a50953db8f621ff54887080065c743",
|
||||
"external_secret": "sso/keycape-{client_id}-client",
|
||||
"secret_key": "client-secret",
|
||||
"provider_mount": "platform",
|
||||
"remote_ref_key": "workloads/keycape/{client_id}",
|
||||
"remote_ref_property": "CLIENT_SECRET",
|
||||
"target_creation_policy": "Owner",
|
||||
"remote_deletion_policy": "Retain"
|
||||
},
|
||||
"candidate_patches": {
|
||||
"platform.candidate.patch": "a490df7dee41e15351da8d2906e1342edebb533984befe3d9ae74d952dbb091b",
|
||||
"netkingdom-entry-point.conditional.patch": "6784a21fbd10bd62712be2e0f4929034601c94629aa74ccaa2e93fc815eecd61"
|
||||
},
|
||||
"checks": {
|
||||
"tests_passed": 8,
|
||||
"subtests_passed": 42,
|
||||
"patch_dryruns_passed": true,
|
||||
"only_type_structural_delta": "spec.follow_through[id=deliver-secret].entry_point",
|
||||
"parameters_bytes_unchanged": true
|
||||
},
|
||||
"remaining_existing_owner_work": [
|
||||
"Independent Platform review/adoption of explicit template and metadata helper under RPF-1.3-T4; no assent inferred.",
|
||||
"After that review, integrate the conditional NetKingdom text with F3 and regenerate combined composition artifact through existing owner path.",
|
||||
"RPF-1.3-T4 real authenticated deliver/remove/evidence/session capabilities and catalog routing acceptance remain separate existing requirements.",
|
||||
"Actual approved owner execution must establish installed ESO compatibility, authentic resource observation, fixed preexisting store and receipt/rollback semantics; source fixtures prove none of these."
|
||||
],
|
||||
"changes_not_made": [
|
||||
"canonical checkouts",
|
||||
"workplans/tasks/UUIDs",
|
||||
"descriptors",
|
||||
"catalog",
|
||||
"CCR0041/grants",
|
||||
"pins/acknowledgments",
|
||||
"default 1.1 or revision 1.2",
|
||||
"native resources",
|
||||
"credentials/Secret values"
|
||||
],
|
||||
"knowledge": {
|
||||
"version": "0.2.6",
|
||||
"digest": "8d1d6a9d4d07fec7b619618de7dfe2be2254c1790f85f4198c7fe19db9751c02",
|
||||
"status": "withheld-orientation-only; current source SECURITY-COMPANION and actual owner contracts reviewed; no authority inferred"
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,108 @@
|
|||
[
|
||||
{
|
||||
"repo": "net-kingdom",
|
||||
"commit": "3236d5000e52e0643f33906a26a7825aa0b67465",
|
||||
"path": "security-case-type-revisions/1.3/oidc-browser-relying-party/type.yaml",
|
||||
"sha256": "e75fe94110ef9fb7fb18072fe2856941b33507c99cb406d36576046481485d0a",
|
||||
"working_tree_equals_commit": true,
|
||||
"line_anchors": [
|
||||
14,
|
||||
17,
|
||||
269,
|
||||
270,
|
||||
273
|
||||
]
|
||||
},
|
||||
{
|
||||
"repo": "net-kingdom",
|
||||
"commit": "3236d5000e52e0643f33906a26a7825aa0b67465",
|
||||
"path": "sso-mfa/k8s/keycape/deliver-secret-adapter.py",
|
||||
"sha256": "cd1a515a1ae2def2093660ed2a810a9c66d1d4cf7126f573ee7e9c4c42afb98d",
|
||||
"working_tree_equals_commit": true,
|
||||
"line_anchors": [
|
||||
47,
|
||||
60,
|
||||
94,
|
||||
98
|
||||
]
|
||||
},
|
||||
{
|
||||
"repo": "railiance-platform",
|
||||
"commit": "0a9617a62d4696a8070798923b459e8405c3dbf2",
|
||||
"path": "credential-change-requests/CCR-2026-0041-forgejo-netkingdom-oidc-keycape-eso-reader.yaml",
|
||||
"sha256": "e69077ad29166770e4a57b4c4105615e272822d8fde11df591d0c127b158ac67",
|
||||
"working_tree_equals_commit": true,
|
||||
"line_anchors": [
|
||||
40,
|
||||
44,
|
||||
52,
|
||||
56,
|
||||
66,
|
||||
89
|
||||
]
|
||||
},
|
||||
{
|
||||
"repo": "railiance-platform",
|
||||
"commit": "0a9617a62d4696a8070798923b459e8405c3dbf2",
|
||||
"path": "docs/provisioning-windows/2026-10-04-1000/keycape/eso.draft.yaml",
|
||||
"sha256": "c8c334fae9d7e7e2f74ac30095d2266d626e857f7c5a85ad4e20b28543678c5d",
|
||||
"working_tree_equals_commit": true,
|
||||
"line_anchors": [
|
||||
3,
|
||||
5,
|
||||
16,
|
||||
18,
|
||||
19,
|
||||
22,
|
||||
26,
|
||||
33,
|
||||
43,
|
||||
55
|
||||
]
|
||||
},
|
||||
{
|
||||
"repo": "railiance-platform",
|
||||
"commit": "0a9617a62d4696a8070798923b459e8405c3dbf2",
|
||||
"path": "docs/credential-lane-designs/keycape-secrets-engine-openbao-issuer-input.yaml",
|
||||
"sha256": "ade2e10b0f65341051b955900c68931bdd56fd3a81bf452ae81229cc2390ad63",
|
||||
"working_tree_equals_commit": true,
|
||||
"line_anchors": [
|
||||
44,
|
||||
56
|
||||
]
|
||||
},
|
||||
{
|
||||
"repo": "railiance-platform",
|
||||
"commit": "0a9617a62d4696a8070798923b459e8405c3dbf2",
|
||||
"path": "scripts/catalog_lane_scaffold.py",
|
||||
"sha256": "96d60cc47aa78728f5ba96ce1fd63033adf23c05641de93ebbdda3902082d2d1",
|
||||
"working_tree_equals_commit": true,
|
||||
"line_anchors": [
|
||||
137,
|
||||
144,
|
||||
145,
|
||||
149,
|
||||
154
|
||||
]
|
||||
},
|
||||
{
|
||||
"repo": "railiance-platform",
|
||||
"commit": "0a9617a62d4696a8070798923b459e8405c3dbf2",
|
||||
"path": "argocd/platform-addons/openbao-secretstore/openbao-keycape-approval-clients.clustersecretstore.yaml",
|
||||
"sha256": "9ce6533e652db5428248c37319b6096a5f8f4af82138141473de4d4ca809e692",
|
||||
"working_tree_equals_commit": true,
|
||||
"line_anchors": [
|
||||
1
|
||||
]
|
||||
},
|
||||
{
|
||||
"repo": "the-custodian",
|
||||
"path": "docs/changes/netkingdom-target-2026-10-17/nk48-adapters-1.2-review/coordinator-review-r5.json",
|
||||
"sha256": "e3016352c58178894ccbfc600b960d3f9e2b5d534a05b8df2c5b79737cb0127a"
|
||||
},
|
||||
{
|
||||
"repo": "the-custodian",
|
||||
"path": "docs/changes/netkingdom-target-2026-10-17/nk48-1.3-owner-review/owner-review-railiance-platform.json",
|
||||
"sha256": "d3f523b60924b530376b7aac4459cecda5da183848dd6e35889c1013c73439d9"
|
||||
}
|
||||
]
|
||||
|
|
@ -0,0 +1,8 @@
|
|||
{
|
||||
"only_structural_delta": "spec.follow_through[id=deliver-secret].entry_point",
|
||||
"parameters_raw_bytes_identical": true,
|
||||
"descriptor_changes": false,
|
||||
"canonical_1_1_1_2_changes": false,
|
||||
"base": "3236d5000e52e0643f33906a26a7825aa0b67465",
|
||||
"both_patch_dryruns_exit": 0
|
||||
}
|
||||
|
|
@ -0,0 +1,495 @@
|
|||
apiVersion: netkingdom.io/security-case-type/v0.1
|
||||
kind: SecurityCaseType
|
||||
spec:
|
||||
id: oidc-browser-relying-party
|
||||
version: '1.3'
|
||||
status: proposed
|
||||
title: Browser OIDC relying party for an internal service
|
||||
purpose: 'Let an internal web service sign its human users in through NetKingdom
|
||||
(KeyCape): an exact-redirect OIDC client, optional MFA pin, the LLDAP groups the
|
||||
service maps to its own roles, and, for confidential clients, a client secret
|
||||
minted straight into OpenBao custody. Replaces the per-client rollout scripts
|
||||
in sso-mfa/k8s/keycape. First case: Forgejo (KEY-WP-0038).'
|
||||
parameters:
|
||||
- name: client_id
|
||||
type: string
|
||||
case_settable: true
|
||||
constraints:
|
||||
pattern: ^[a-z0-9][a-z0-9-]{1,62}$
|
||||
sensitivity: operational
|
||||
description: OAuth2 client_id, unique in KeyCape.
|
||||
- name: display_name
|
||||
type: string
|
||||
case_settable: true
|
||||
constraints:
|
||||
pattern: ^[^\n\r]{1,80}$
|
||||
sensitivity: public
|
||||
description: Name shown on the sign-in page.
|
||||
- name: redirect_uris
|
||||
type: string
|
||||
case_settable: true
|
||||
constraints:
|
||||
pattern: ^https://[^*?#\s,]+(,https://[^*?#\s,]+)*$
|
||||
sensitivity: security_sensitive
|
||||
description: 'Comma-separated exact https redirect URIs. No wildcards, no plain
|
||||
http, no query and no fragment: KeyCape refuses ''*'' and ''?'' at startup,
|
||||
and OAuth 2.0 forbids fragments (key-cape type review R1).'
|
||||
- name: allowed_scopes
|
||||
type: string
|
||||
case_settable: true
|
||||
constraints:
|
||||
enum:
|
||||
- openid,profile,email,groups
|
||||
- openid,email,groups
|
||||
- openid,profile,groups
|
||||
- openid,groups
|
||||
sensitivity: security_sensitive
|
||||
description: Scopes the client may request, openid first. Always includes groups,
|
||||
because access_groups is required and KeyCape's UserInfo returns groups only
|
||||
for that scope (key-cape type review R2).
|
||||
- name: client_type
|
||||
type: string
|
||||
case_settable: true
|
||||
constraints:
|
||||
enum:
|
||||
- confidential
|
||||
- public
|
||||
sensitivity: security_sensitive
|
||||
description: confidential for server-side services that can hold a secret; public
|
||||
for SPAs and native apps (PKCE only).
|
||||
- name: mfa_required
|
||||
type: string
|
||||
case_settable: true
|
||||
constraints:
|
||||
enum:
|
||||
- 'true'
|
||||
- 'false'
|
||||
sensitivity: security_sensitive
|
||||
description: '"true" pins MFA on this client; required when the service grants
|
||||
administrative or owner rights. "false" is not "no MFA": the client then carries
|
||||
no mfaRequired field and KeyCape applies the per-user privacyIDEA policy. A
|
||||
renderer must never emit an explicit mfaRequired false, which would skip that
|
||||
policy (key-cape type review R3).'
|
||||
- name: access_groups
|
||||
type: string
|
||||
case_settable: true
|
||||
constraints:
|
||||
pattern: ^[a-z][a-z0-9-]{1,62}(,[a-z][a-z0-9-]{1,62})*$
|
||||
sensitivity: operational
|
||||
description: 'Comma-separated LLDAP groups that the service maps to its roles.
|
||||
Created if absent. KeyCape has no per-client group gate: it issues tokens to
|
||||
any authenticated user and only carries the groups claim, so the consuming service
|
||||
enforces the gate itself (for Forgejo, its required-claim setting; key-cape
|
||||
type review R2).'
|
||||
- name: initial_members
|
||||
type: string
|
||||
required: false
|
||||
case_settable: true
|
||||
constraints:
|
||||
pattern: ^[a-z][a-z0-9-]*=[a-z][a-z0-9._-]*(\+[a-z][a-z0-9._-]*)*(;[a-z][a-z0-9-]*=[a-z][a-z0-9._-]*(\+[a-z][a-z0-9._-]*)*)*$
|
||||
sensitivity: operational
|
||||
description: Initial memberships as group=user+user;group=user. Usernames are
|
||||
directory usernames, never secrets.
|
||||
- name: consuming_service
|
||||
type: string
|
||||
case_settable: true
|
||||
constraints:
|
||||
pattern: ^[a-z0-9][a-z0-9-]{0,62}$
|
||||
sensitivity: operational
|
||||
description: The service that signs users in with this client.
|
||||
- name: consumer_owner_repo
|
||||
type: string
|
||||
case_settable: true
|
||||
constraints:
|
||||
pattern: ^[a-z0-9][a-z0-9-]*$
|
||||
sensitivity: operational
|
||||
description: The repository that owns the consuming service's configuration.
|
||||
- name: consumer_namespace
|
||||
type: string
|
||||
case_settable: true
|
||||
constraints:
|
||||
pattern: ^[a-z0-9]([a-z0-9-]{0,61}[a-z0-9])?$
|
||||
sensitivity: security_sensitive
|
||||
description: Consumer namespace for namespaced ESO TokenRequest reader; actual
|
||||
workload owner must confirm; never infer from service name.
|
||||
- name: issuer
|
||||
type: string
|
||||
case_settable: false
|
||||
default: https://kc.coulomb.social
|
||||
sensitivity: security_sensitive
|
||||
description: The NetKingdom issuer; fixed by the type.
|
||||
composition:
|
||||
authority: netkingdom
|
||||
initial_trust:
|
||||
- runtime_secret_trust
|
||||
- runtime_identity_trust
|
||||
capabilities:
|
||||
- c1.lightweight-sso
|
||||
providers:
|
||||
c1.lightweight-sso: net-kingdom.keycape-client-registration
|
||||
parameter_bindings:
|
||||
net-kingdom.keycape-client-registration.client_id: client_id
|
||||
net-kingdom.keycape-client-registration.display_name: display_name
|
||||
net-kingdom.keycape-client-registration.redirect_uris: redirect_uris
|
||||
net-kingdom.keycape-client-registration.allowed_scopes: allowed_scopes
|
||||
net-kingdom.keycape-client-registration.client_type: client_type
|
||||
net-kingdom.keycape-client-registration.mfa_required: mfa_required
|
||||
materialization:
|
||||
- id: client-secret-path
|
||||
kind: kv_path
|
||||
when: client_type == confidential
|
||||
template: platform/workloads/keycape/{client_id}
|
||||
body: field CLIENT_SECRET; minted by the credential broker, never written by hand
|
||||
- id: keycape-reader-policy
|
||||
kind: openbao_policy
|
||||
when: client_type == confidential
|
||||
template: workload-kv-read-keycape-{client_id}-eso
|
||||
body: "path \"platform/data/workloads/keycape/{client_id}\" {{\n capabilities\
|
||||
\ = [\"read\"]\n}}\n"
|
||||
description: KeyCape reads its client secret only through ESO. No metadata, list,
|
||||
parent or sibling access.
|
||||
- id: consumer-reader-policy
|
||||
kind: openbao_policy
|
||||
when: client_type == confidential
|
||||
template: workload-kv-read-keycape-{client_id}-{consuming_service}
|
||||
body: "path \"platform/data/workloads/keycape/{client_id}\" {{\n capabilities\
|
||||
\ = [\"read\"]\n}}\n"
|
||||
description: The consuming service reads the same secret. No metadata, list, parent
|
||||
or sibling access.
|
||||
- id: eso-role
|
||||
kind: openbao_role
|
||||
when: client_type == confidential
|
||||
template: external-secrets-keycape-{client_id}
|
||||
body: 'auth: kubernetes
|
||||
|
||||
bound_service_account_names: [external-secrets]
|
||||
|
||||
bound_service_account_namespaces: [external-secrets]
|
||||
|
||||
token_policies: [workload-kv-read-keycape-{client_id}-eso]
|
||||
|
||||
token_no_default_policy: true
|
||||
|
||||
token_ttl: 15m
|
||||
|
||||
token_max_ttl: 15m
|
||||
|
||||
'
|
||||
description: The Kubernetes-auth role deliver-secret uses; bound to the ESO service
|
||||
account only.
|
||||
- id: consumer-eso-role
|
||||
kind: openbao_role
|
||||
when: client_type == confidential
|
||||
template: external-secrets-keycape-{client_id}-{consuming_service}
|
||||
body: 'auth: kubernetes
|
||||
|
||||
bound_service_account_names: [{consuming_service}-keycape-{client_id}-eso]
|
||||
|
||||
bound_service_account_namespaces: [{consumer_namespace}]
|
||||
|
||||
audience: openbao
|
||||
|
||||
token_policies: [workload-kv-read-keycape-{client_id}-{consuming_service}]
|
||||
|
||||
token_no_default_policy: true
|
||||
|
||||
token_ttl: 15m
|
||||
|
||||
token_max_ttl: 15m
|
||||
|
||||
'
|
||||
description: Consumer ESO requests a token for this separate reference identity.
|
||||
Common ESO controller remains trusted and can request tokens; no cryptographic
|
||||
controller isolation is claimed.
|
||||
- id: catalog-entry
|
||||
kind: catalog_entry
|
||||
template: '{consuming_service}-keycape-client'
|
||||
body: 'id: {consuming_service}-keycape-client
|
||||
|
||||
owner_repo: key-cape
|
||||
|
||||
warden_executes: false
|
||||
|
||||
risk: high
|
||||
|
||||
status: active once verification passes
|
||||
|
||||
workload_ref: applicable, name {consuming_service}, declaration_ref of the consumer''s
|
||||
published declaration or the explicit unknown form, never inferred
|
||||
|
||||
path_template: platform/workloads/keycape/{client_id}
|
||||
|
||||
field: CLIENT_SECRET
|
||||
|
||||
delegation: native, intended_owner key-cape
|
||||
|
||||
rotation: mint-secret then verification; automatable false until a run has been
|
||||
proven
|
||||
|
||||
'
|
||||
description: One entry per case (ops-mason item 5, ops-warden R4'). It carries
|
||||
the consumer's workload reference, which ADR-0009 needs for zone selection.
|
||||
follow_through:
|
||||
- id: ensure-groups
|
||||
owner: net-kingdom
|
||||
entry_point: reviewed adapter net-kingdom/keycape-ensure-groups, dispatched by Warden's
|
||||
case kernel inside the attended session; it calls identity-provisioner
|
||||
ensure_service_groups in the provisioner pod through the session's contained
|
||||
cluster access (groups {access_groups}; members exactly {initial_members} as
|
||||
recorded in the accepted case, nothing else)
|
||||
auth: the attended human session on lane keycape-attended-case-change (actor human,
|
||||
parent-session revoked per attempt); the in-pod call uses the provisioner's own
|
||||
directory binding, and no provisioner or user-engine token is read or sent
|
||||
lane: keycape-attended-case-change
|
||||
warden_executes: false
|
||||
attended: true
|
||||
value_handling: none
|
||||
attended_justification: The provisioner's only HTTP credential is user-engine's
|
||||
runtime token, and its NetworkPolicy admits only user-engine pods. A proxy lane
|
||||
would act as user-engine (ops-warden review R8; canon section 3; ops-warden
|
||||
ADR-0002). Running it in the session register-client already needs adds no attended
|
||||
session. It can become unattended once the provisioner has an operator credential
|
||||
of its own and an admitted path. Warden's case kernel dispatches the reviewed adapter only inside the one attended human session (descriptor actor human, lane keycape-attended-case-change, parent-session revoked per attempt); the human session is the authority and the kernel is only the dispatcher.
|
||||
- id: mint-secret
|
||||
owner: railiance-platform
|
||||
when: client_type == confidential
|
||||
entry_point: 'railiance-platform credential broker: generate a client secret inside
|
||||
custody at bao:platform/workloads/keycape/{client_id}, through reviewed adapter
|
||||
railiance-platform/keycape-mint-secret'
|
||||
auth: a human caller (net-kingdom-admins, aal2) starts it; Warden runs it unattended
|
||||
through the broker lane under the caller's session, with the caller's broker grant
|
||||
for the case's KV path; agents and root are refused, and the value is generated
|
||||
in custody and never returned
|
||||
lane: keycape-client-secret-generate
|
||||
warden_executes: true
|
||||
attended: false
|
||||
value_handling: generated_in_custody
|
||||
- id: deliver-secret
|
||||
owner: railiance-platform
|
||||
when: client_type == confidential
|
||||
entry_point: reviewed adapter railiance-platform/keycape-deliver-secret, dispatched
|
||||
by Warden's case kernel; Platform's deliver capability applies ExternalSecret
|
||||
sso/keycape-{client_id}-client (key client-secret) through the ESO store for
|
||||
role external-secrets-keycape-{client_id}, from bao:platform/workloads/keycape/{client_id}
|
||||
auth: Platform's own delivery path on lane platform-secret-delivery, inside the human
|
||||
aal2 session; ESO reads custody through role external-secrets-keycape-{client_id};
|
||||
Warden holds no Platform credential, and no value leaves custody
|
||||
lane: platform-secret-delivery
|
||||
warden_executes: false
|
||||
attended: false
|
||||
value_handling: none
|
||||
ready_check: the delivery for sso/keycape-{client_id}-client reports Ready=True
|
||||
(status only, no value read)
|
||||
- id: bind-env
|
||||
owner: net-kingdom
|
||||
when: client_type == confidential
|
||||
entry_point: 'reviewed adapter net-kingdom/keycape-bind-env, dispatched by Warden''s
|
||||
case kernel inside the attended session: patch Deployment sso/keycape env
|
||||
KEYCAPE_{client_id:env}_CLIENT_SECRET from sso/keycape-{client_id}-client, by
|
||||
reference only'
|
||||
auth: the attended human session on lane keycape-attended-case-change, with operator
|
||||
kube credentials for namespace sso (actor human, parent-session revoked per attempt)
|
||||
lane: keycape-attended-case-change
|
||||
warden_executes: false
|
||||
attended: true
|
||||
value_handling: none
|
||||
attended_justification: A change to the live identity provider's Deployment. It
|
||||
runs in the same attended operator session as register-client, before it, because
|
||||
KeyCape resolves a confidential client's secret at start and fails closed if
|
||||
the binding is missing. Warden's case kernel dispatches the reviewed adapter only inside the one attended human session (descriptor actor human, lane keycape-attended-case-change, parent-session revoked per attempt); the human session is the authority and the kernel is only the dispatcher.
|
||||
- id: register-client
|
||||
owner: net-kingdom
|
||||
entry_point: reviewed adapter net-kingdom/keycape-register-client, dispatched by Warden's
|
||||
case kernel inside the attended session; it runs sso-mfa/k8s/keycape/keycape-client.py
|
||||
apply --client-file <client-from-case> --case-acceptance <record> --check-config
|
||||
<keycape binary> (net-kingdom.keycape-client-registration)
|
||||
auth: the attended human session on lane keycape-attended-case-change, with operator
|
||||
write access to the live KeyCape client registry in namespace sso (actor human,
|
||||
parent-session revoked per attempt)
|
||||
lane: keycape-attended-case-change
|
||||
warden_executes: false
|
||||
attended: true
|
||||
value_handling: none
|
||||
attended_justification: 'The live KeyCape client registry is a Kubernetes Secret,
|
||||
and agents do not read or write Secrets. Until KeyCape loads clients from a
|
||||
non-secret registry with secrets referenced from custody, one attended operator
|
||||
session applies the rendered registration. ops-warden groups all attended steps
|
||||
of a run into that one session. KeyCape loads clients at startup and keeps login,
|
||||
authorization and handoff state in memory, so the restart drops in-flight logins
|
||||
and sessions for every client; runs go in a quiet window (key-cape type review
|
||||
R4). The rendered registry must pass KeyCape''s own startup checks (keycape
|
||||
check-config, the pinned checker artifact sha256:be37738fe1c08259fb4f9ca0daf0f9301bfffc237e5bb38435525dfbf11ccff5
|
||||
built from key-cape 3c71226, which keycape-client.py runs before it patches)
|
||||
before the restart: a refused entry stops the single replica and every client
|
||||
loses sign-in (R1). Warden''s case kernel dispatches the reviewed adapter only inside the one attended human session (descriptor actor human, lane keycape-attended-case-change, parent-session revoked per attempt); the human session is the authority and the kernel is only the dispatcher.'
|
||||
- id: deliver-consumer-secret
|
||||
owner: '{consumer_owner_repo}'
|
||||
when: client_type == confidential
|
||||
entry_point: Reviewed adapter net-kingdom/keycape-deliver-consumer-secret, dispatched
|
||||
by Warden's case kernel inside the attended consumer-owner session; the owner's
|
||||
apply capability creates dedicated ServiceAccount {consumer_namespace}/{consuming_service}-keycape-{client_id}-eso
|
||||
(automount=false, no legacy tokens), namespace SecretStore openbao-keycape-{client_id}-consumer
|
||||
with serviceAccountRef and audiences [openbao], and ExternalSecret {consuming_service}-netkingdom-oidc-client
|
||||
with Owner target, Retain remote deletion and only CLIENT_SECRET from workloads/keycape/{client_id}.
|
||||
Admission validates actual installed ESO TokenRequest support and common controller
|
||||
RBAC before any owner-approved apply.
|
||||
auth: the attended consumer-owner session on lane consumer-owner-deployment (actor
|
||||
human, the consumer owner's operator deployment authority, on the founder's go
|
||||
for the change); ESO authenticates to OpenBao role external-secrets-keycape-{client_id}-{consuming_service}
|
||||
with a TokenRequest for the dedicated namespaced serviceAccountRef; controller
|
||||
remains trusted
|
||||
lane: consumer-owner-deployment
|
||||
warden_executes: false
|
||||
attended: true
|
||||
value_handling: none
|
||||
attended_justification: The consumer owner's only deployment authority in the consumer
|
||||
namespace is its operator credential, and production applies need the founder's
|
||||
go per change. No contained agent deployer identity exists, and the owner does
|
||||
not want a standing credential an agent could trigger. It runs in one attended
|
||||
consumer-owner session together with hand-over. Warden's case kernel dispatches
|
||||
the reviewed adapter only inside that session (actor human, parent-session
|
||||
revoked per attempt); the human session is the authority and the kernel is only
|
||||
the dispatcher.
|
||||
ready_check: Namespace SecretStore and ExternalSecret both report Ready=True;
|
||||
dedicated SA has no legacy token refs; status and fixed metadata only, never
|
||||
Secret values.
|
||||
- id: hand-over
|
||||
owner: '{consumer_owner_repo}'
|
||||
entry_point: The consumer owner first makes its own Helm change configuring {consuming_service}
|
||||
with issuer {issuer}, client {client_id} and custody bao:platform/workloads/keycape/{client_id},
|
||||
requiring one of {access_groups} in the groups claim before sign-in completes.
|
||||
Then reviewed adapter net-kingdom/keycape-hand-over, dispatched by Warden's case
|
||||
kernel inside the attended consumer-owner session, verifies the consumer with
|
||||
four value-free probes (member-login, non-member-refused, membership-add-grants,
|
||||
membership-remove-revokes) and records the hand-over receipt.
|
||||
auth: the attended consumer-owner session on lane apps-consumer-handover (actor human,
|
||||
parent-session revoked per attempt); the consumer configuration itself is the
|
||||
owner's own deployment path
|
||||
lane: apps-consumer-handover
|
||||
warden_executes: false
|
||||
attended: true
|
||||
value_handling: none
|
||||
attended_justification: The verification needs a real member login and a refused
|
||||
non-member login, which only an attended session can perform, and it follows
|
||||
the owner's own Helm change in the same consumer-owner session as
|
||||
deliver-consumer-secret. Warden's case kernel dispatches the reviewed adapter
|
||||
only inside that session (actor human, parent-session revoked per attempt); the
|
||||
human session is the authority and the kernel is only the dispatcher.
|
||||
verification:
|
||||
- id: authorize-accepts-redirects
|
||||
check: GET {issuer}/authorize with client {client_id} and each of {redirect_uris}
|
||||
returns the login page
|
||||
expect: present
|
||||
reads_secret_value: false
|
||||
- id: authorize-refuses-other-redirect
|
||||
check: GET {issuer}/authorize with client {client_id} and an unregistered redirect
|
||||
returns an OAuth error
|
||||
expect: deny
|
||||
reads_secret_value: false
|
||||
- id: custody-denied-to-default
|
||||
when: client_type == confidential
|
||||
check: a default-only OpenBao identity has no read capability on bao:platform/workloads/keycape/{client_id}
|
||||
expect: deny
|
||||
reads_secret_value: false
|
||||
- id: custody-allowed-to-consumer
|
||||
when: client_type == confidential
|
||||
check: policy workload-kv-read-keycape-{client_id}-{consuming_service} grants
|
||||
read on bao:platform/workloads/keycape/{client_id} (capabilities check only)
|
||||
expect: allow
|
||||
reads_secret_value: false
|
||||
- id: groups-present
|
||||
check: LLDAP groups {access_groups} exist, with exactly the memberships of the
|
||||
accepted case added
|
||||
expect: present
|
||||
reads_secret_value: false
|
||||
- id: consumer-enforces-groups
|
||||
check: '{consuming_service}''s OIDC source for {client_id} (owner {consumer_owner_repo})
|
||||
requires one of {access_groups} in the groups claim (configuration read, no
|
||||
secret value)'
|
||||
expect: present
|
||||
reads_secret_value: false
|
||||
- id: secret-delivered
|
||||
when: client_type == confidential
|
||||
check: the delivery for sso/keycape-{client_id}-client reports Ready=True (status
|
||||
only)
|
||||
expect: present
|
||||
reads_secret_value: false
|
||||
- id: mfa-pinned
|
||||
when: mfa_required == true
|
||||
check: keycape-client.py inspect for {client_id} shows the MFA pin on the registered
|
||||
client (non-secret client view)
|
||||
expect: present
|
||||
reads_secret_value: false
|
||||
- id: consumer-secret-delivered
|
||||
when: client_type == confidential
|
||||
check: '{consumer_namespace} SecretStore openbao-keycape-{client_id}-consumer
|
||||
and ExternalSecret {consuming_service}-netkingdom-oidc-client Ready=True, exact
|
||||
dedicated SA/audience role binding, fixed CLIENT_SECRET mapping; no Secret value
|
||||
read'
|
||||
expect: present
|
||||
reads_secret_value: false
|
||||
rollback:
|
||||
- reverses: hand-over
|
||||
action: the adapter removes only its hand-over receipt. Removing the OIDC source
|
||||
for {client_id} from {consuming_service} is the owner's Helm rollback (owner
|
||||
{consumer_owner_repo}), done inside deliver-consumer-secret's owner_remove
|
||||
before any consumer object is removed
|
||||
- reverses: deliver-consumer-secret
|
||||
when: client_type == confidential
|
||||
action: After reverting consumer secret reference, delete only authenticated created
|
||||
ExternalSecret/owned target by UID and ownerReference, then SecretStore and
|
||||
dedicated SA; preserve preexisting-matched resources and never delete a shared
|
||||
target. Retain audit and ownership receipts; removing SecretStore/SA alone is
|
||||
not immediate OpenBao token revocation.
|
||||
- reverses: register-client
|
||||
action: keycape-client.py apply --operation remove for {client_id}
|
||||
- reverses: bind-env
|
||||
when: client_type == confidential
|
||||
action: remove env KEYCAPE_{client_id:env}_CLIENT_SECRET from Deployment sso/keycape,
|
||||
after register-client is reversed
|
||||
- reverses: deliver-secret
|
||||
when: client_type == confidential
|
||||
action: delete ExternalSecret sso/keycape-{client_id}-client and its target
|
||||
- reverses: mint-secret
|
||||
when: client_type == confidential
|
||||
action: 'covered by client-secret-path: one metadata delete, after deliver-secret
|
||||
is reversed'
|
||||
- reverses: ensure-groups
|
||||
action: identity-provisioner revoke_service_groups inside the provisioner pod
|
||||
(attended, as ensure-groups) with the case's memberships; delete_if_empty only
|
||||
if no other case uses {access_groups}
|
||||
- reverses: client-secret-path
|
||||
when: client_type == confidential
|
||||
action: delete metadata and all versions at platform/workloads/keycape/{client_id},
|
||||
after the ExternalSecret is gone (this also reverses mint-secret)
|
||||
- reverses: eso-role
|
||||
when: client_type == confidential
|
||||
action: delete Kubernetes-auth role external-secrets-keycape-{client_id}
|
||||
- reverses: keycape-reader-policy
|
||||
when: client_type == confidential
|
||||
action: delete policy workload-kv-read-keycape-{client_id}-eso, after eso-role
|
||||
- reverses: consumer-eso-role
|
||||
when: client_type == confidential
|
||||
action: Delete only this case-created role external-secrets-keycape-{client_id}-{consuming_service},
|
||||
before deleting consumer-reader-policy; revoke case-issued OpenBao tokens or
|
||||
prove expiration before declaring deactivation.
|
||||
- reverses: consumer-reader-policy
|
||||
when: client_type == confidential
|
||||
action: delete policy workload-kv-read-keycape-{client_id}-{consuming_service}
|
||||
- reverses: catalog-entry
|
||||
action: remove catalog entry {consuming_service}-keycape-client
|
||||
acceptance:
|
||||
accepted_by:
|
||||
- owner
|
||||
- founder
|
||||
risk_class: medium
|
||||
# Reviewed machine descriptors (NK-WP-0048-T16 successor candidate). Digests are
|
||||
# generated by sso-mfa/k8s/keycape/compose-1.2-descriptors.py --version 1.3.
|
||||
execution_descriptors:
|
||||
bind-env: sha256:d5fcb20cf15bf7454890d2529180c582218e3df12584a99162e4b664b899caa0
|
||||
deliver-consumer-secret: sha256:36281df8139fb1ec64b226d94f80eb592de5addab7063ff54c691c641e275fc7
|
||||
deliver-secret: sha256:75aaa7346aebdea2a0eb0ba993995515b5248ffefc34caf8e713525e9b58da85
|
||||
ensure-groups: sha256:ebc9d5b9167d18e6015f4bbeb9699e8d396d64fbfaf195142781f84f4f47a70a
|
||||
hand-over: sha256:1bce07a3229b7d0c7c730f35ce5b40fe471009a82de8dd53788ed970a9be1224
|
||||
mint-secret: sha256:c92957227235db04953809d92b260d32c8b947472a8e8fd17a258ade3ae8da70
|
||||
register-client: sha256:4ecc769f75433b66c7161f13c0fe7e5599633cf466d46cb6d8031f0456f426e7
|
||||
|
|
@ -0,0 +1,498 @@
|
|||
apiVersion: netkingdom.io/security-case-type/v0.1
|
||||
kind: SecurityCaseType
|
||||
spec:
|
||||
id: oidc-browser-relying-party
|
||||
version: '1.3'
|
||||
status: proposed
|
||||
title: Browser OIDC relying party for an internal service
|
||||
purpose: 'Let an internal web service sign its human users in through NetKingdom
|
||||
(KeyCape): an exact-redirect OIDC client, optional MFA pin, the LLDAP groups the
|
||||
service maps to its own roles, and, for confidential clients, a client secret
|
||||
minted straight into OpenBao custody. Replaces the per-client rollout scripts
|
||||
in sso-mfa/k8s/keycape. First case: Forgejo (KEY-WP-0038).'
|
||||
parameters:
|
||||
- name: client_id
|
||||
type: string
|
||||
case_settable: true
|
||||
constraints:
|
||||
pattern: ^[a-z0-9][a-z0-9-]{1,62}$
|
||||
sensitivity: operational
|
||||
description: OAuth2 client_id, unique in KeyCape.
|
||||
- name: display_name
|
||||
type: string
|
||||
case_settable: true
|
||||
constraints:
|
||||
pattern: ^[^\n\r]{1,80}$
|
||||
sensitivity: public
|
||||
description: Name shown on the sign-in page.
|
||||
- name: redirect_uris
|
||||
type: string
|
||||
case_settable: true
|
||||
constraints:
|
||||
pattern: ^https://[^*?#\s,]+(,https://[^*?#\s,]+)*$
|
||||
sensitivity: security_sensitive
|
||||
description: 'Comma-separated exact https redirect URIs. No wildcards, no plain
|
||||
http, no query and no fragment: KeyCape refuses ''*'' and ''?'' at startup,
|
||||
and OAuth 2.0 forbids fragments (key-cape type review R1).'
|
||||
- name: allowed_scopes
|
||||
type: string
|
||||
case_settable: true
|
||||
constraints:
|
||||
enum:
|
||||
- openid,profile,email,groups
|
||||
- openid,email,groups
|
||||
- openid,profile,groups
|
||||
- openid,groups
|
||||
sensitivity: security_sensitive
|
||||
description: Scopes the client may request, openid first. Always includes groups,
|
||||
because access_groups is required and KeyCape's UserInfo returns groups only
|
||||
for that scope (key-cape type review R2).
|
||||
- name: client_type
|
||||
type: string
|
||||
case_settable: true
|
||||
constraints:
|
||||
enum:
|
||||
- confidential
|
||||
- public
|
||||
sensitivity: security_sensitive
|
||||
description: confidential for server-side services that can hold a secret; public
|
||||
for SPAs and native apps (PKCE only).
|
||||
- name: mfa_required
|
||||
type: string
|
||||
case_settable: true
|
||||
constraints:
|
||||
enum:
|
||||
- 'true'
|
||||
- 'false'
|
||||
sensitivity: security_sensitive
|
||||
description: '"true" pins MFA on this client; required when the service grants
|
||||
administrative or owner rights. "false" is not "no MFA": the client then carries
|
||||
no mfaRequired field and KeyCape applies the per-user privacyIDEA policy. A
|
||||
renderer must never emit an explicit mfaRequired false, which would skip that
|
||||
policy (key-cape type review R3).'
|
||||
- name: access_groups
|
||||
type: string
|
||||
case_settable: true
|
||||
constraints:
|
||||
pattern: ^[a-z][a-z0-9-]{1,62}(,[a-z][a-z0-9-]{1,62})*$
|
||||
sensitivity: operational
|
||||
description: 'Comma-separated LLDAP groups that the service maps to its roles.
|
||||
Created if absent. KeyCape has no per-client group gate: it issues tokens to
|
||||
any authenticated user and only carries the groups claim, so the consuming service
|
||||
enforces the gate itself (for Forgejo, its required-claim setting; key-cape
|
||||
type review R2).'
|
||||
- name: initial_members
|
||||
type: string
|
||||
required: false
|
||||
case_settable: true
|
||||
constraints:
|
||||
pattern: ^[a-z][a-z0-9-]*=[a-z][a-z0-9._-]*(\+[a-z][a-z0-9._-]*)*(;[a-z][a-z0-9-]*=[a-z][a-z0-9._-]*(\+[a-z][a-z0-9._-]*)*)*$
|
||||
sensitivity: operational
|
||||
description: Initial memberships as group=user+user;group=user. Usernames are
|
||||
directory usernames, never secrets.
|
||||
- name: consuming_service
|
||||
type: string
|
||||
case_settable: true
|
||||
constraints:
|
||||
pattern: ^[a-z0-9][a-z0-9-]{0,62}$
|
||||
sensitivity: operational
|
||||
description: The service that signs users in with this client.
|
||||
- name: consumer_owner_repo
|
||||
type: string
|
||||
case_settable: true
|
||||
constraints:
|
||||
pattern: ^[a-z0-9][a-z0-9-]*$
|
||||
sensitivity: operational
|
||||
description: The repository that owns the consuming service's configuration.
|
||||
- name: consumer_namespace
|
||||
type: string
|
||||
case_settable: true
|
||||
constraints:
|
||||
pattern: ^[a-z0-9]([a-z0-9-]{0,61}[a-z0-9])?$
|
||||
sensitivity: security_sensitive
|
||||
description: Consumer namespace for namespaced ESO TokenRequest reader; actual
|
||||
workload owner must confirm; never infer from service name.
|
||||
- name: issuer
|
||||
type: string
|
||||
case_settable: false
|
||||
default: https://kc.coulomb.social
|
||||
sensitivity: security_sensitive
|
||||
description: The NetKingdom issuer; fixed by the type.
|
||||
composition:
|
||||
authority: netkingdom
|
||||
initial_trust:
|
||||
- runtime_secret_trust
|
||||
- runtime_identity_trust
|
||||
capabilities:
|
||||
- c1.lightweight-sso
|
||||
providers:
|
||||
c1.lightweight-sso: net-kingdom.keycape-client-registration
|
||||
parameter_bindings:
|
||||
net-kingdom.keycape-client-registration.client_id: client_id
|
||||
net-kingdom.keycape-client-registration.display_name: display_name
|
||||
net-kingdom.keycape-client-registration.redirect_uris: redirect_uris
|
||||
net-kingdom.keycape-client-registration.allowed_scopes: allowed_scopes
|
||||
net-kingdom.keycape-client-registration.client_type: client_type
|
||||
net-kingdom.keycape-client-registration.mfa_required: mfa_required
|
||||
materialization:
|
||||
- id: client-secret-path
|
||||
kind: kv_path
|
||||
when: client_type == confidential
|
||||
template: platform/workloads/keycape/{client_id}
|
||||
body: field CLIENT_SECRET; minted by the credential broker, never written by hand
|
||||
- id: keycape-reader-policy
|
||||
kind: openbao_policy
|
||||
when: client_type == confidential
|
||||
template: workload-kv-read-keycape-{client_id}-eso
|
||||
body: "path \"platform/data/workloads/keycape/{client_id}\" {{\n capabilities\
|
||||
\ = [\"read\"]\n}}\n"
|
||||
description: KeyCape reads its client secret only through ESO. No metadata, list,
|
||||
parent or sibling access.
|
||||
- id: consumer-reader-policy
|
||||
kind: openbao_policy
|
||||
when: client_type == confidential
|
||||
template: workload-kv-read-keycape-{client_id}-{consuming_service}
|
||||
body: "path \"platform/data/workloads/keycape/{client_id}\" {{\n capabilities\
|
||||
\ = [\"read\"]\n}}\n"
|
||||
description: The consuming service reads the same secret. No metadata, list, parent
|
||||
or sibling access.
|
||||
- id: eso-role
|
||||
kind: openbao_role
|
||||
when: client_type == confidential
|
||||
template: external-secrets-keycape-{client_id}
|
||||
body: 'auth: kubernetes
|
||||
|
||||
bound_service_account_names: [external-secrets]
|
||||
|
||||
bound_service_account_namespaces: [external-secrets]
|
||||
|
||||
token_policies: [workload-kv-read-keycape-{client_id}-eso]
|
||||
|
||||
token_no_default_policy: true
|
||||
|
||||
token_ttl: 15m
|
||||
|
||||
token_max_ttl: 15m
|
||||
|
||||
'
|
||||
description: The Kubernetes-auth role deliver-secret uses; bound to the ESO service
|
||||
account only.
|
||||
- id: consumer-eso-role
|
||||
kind: openbao_role
|
||||
when: client_type == confidential
|
||||
template: external-secrets-keycape-{client_id}-{consuming_service}
|
||||
body: 'auth: kubernetes
|
||||
|
||||
bound_service_account_names: [{consuming_service}-keycape-{client_id}-eso]
|
||||
|
||||
bound_service_account_namespaces: [{consumer_namespace}]
|
||||
|
||||
audience: openbao
|
||||
|
||||
token_policies: [workload-kv-read-keycape-{client_id}-{consuming_service}]
|
||||
|
||||
token_no_default_policy: true
|
||||
|
||||
token_ttl: 15m
|
||||
|
||||
token_max_ttl: 15m
|
||||
|
||||
'
|
||||
description: Consumer ESO requests a token for this separate reference identity.
|
||||
Common ESO controller remains trusted and can request tokens; no cryptographic
|
||||
controller isolation is claimed.
|
||||
- id: catalog-entry
|
||||
kind: catalog_entry
|
||||
template: '{consuming_service}-keycape-client'
|
||||
body: 'id: {consuming_service}-keycape-client
|
||||
|
||||
owner_repo: key-cape
|
||||
|
||||
warden_executes: false
|
||||
|
||||
risk: high
|
||||
|
||||
status: active once verification passes
|
||||
|
||||
workload_ref: applicable, name {consuming_service}, declaration_ref of the consumer''s
|
||||
published declaration or the explicit unknown form, never inferred
|
||||
|
||||
path_template: platform/workloads/keycape/{client_id}
|
||||
|
||||
field: CLIENT_SECRET
|
||||
|
||||
delegation: native, intended_owner key-cape
|
||||
|
||||
rotation: mint-secret then verification; automatable false until a run has been
|
||||
proven
|
||||
|
||||
'
|
||||
description: One entry per case (ops-mason item 5, ops-warden R4'). It carries
|
||||
the consumer's workload reference, which ADR-0009 needs for zone selection.
|
||||
follow_through:
|
||||
- id: ensure-groups
|
||||
owner: net-kingdom
|
||||
entry_point: reviewed adapter net-kingdom/keycape-ensure-groups, dispatched by Warden's
|
||||
case kernel inside the attended session; it calls identity-provisioner
|
||||
ensure_service_groups in the provisioner pod through the session's contained
|
||||
cluster access (groups {access_groups}; members exactly {initial_members} as
|
||||
recorded in the accepted case, nothing else)
|
||||
auth: the attended human session on lane keycape-attended-case-change (actor human,
|
||||
parent-session revoked per attempt); the in-pod call uses the provisioner's own
|
||||
directory binding, and no provisioner or user-engine token is read or sent
|
||||
lane: keycape-attended-case-change
|
||||
warden_executes: false
|
||||
attended: true
|
||||
value_handling: none
|
||||
attended_justification: The provisioner's only HTTP credential is user-engine's
|
||||
runtime token, and its NetworkPolicy admits only user-engine pods. A proxy lane
|
||||
would act as user-engine (ops-warden review R8; canon section 3; ops-warden
|
||||
ADR-0002). Running it in the session register-client already needs adds no attended
|
||||
session. It can become unattended once the provisioner has an operator credential
|
||||
of its own and an admitted path. Warden's case kernel dispatches the reviewed adapter only inside the one attended human session (descriptor actor human, lane keycape-attended-case-change, parent-session revoked per attempt); the human session is the authority and the kernel is only the dispatcher.
|
||||
- id: mint-secret
|
||||
owner: railiance-platform
|
||||
when: client_type == confidential
|
||||
entry_point: 'railiance-platform credential broker: generate a client secret inside
|
||||
custody at bao:platform/workloads/keycape/{client_id}, through reviewed adapter
|
||||
railiance-platform/keycape-mint-secret'
|
||||
auth: a human caller (net-kingdom-admins, aal2) starts it; Warden runs it unattended
|
||||
through the broker lane under the caller's session, with the caller's broker grant
|
||||
for the case's KV path; agents and root are refused, and the value is generated
|
||||
in custody and never returned
|
||||
lane: keycape-client-secret-generate
|
||||
warden_executes: true
|
||||
attended: false
|
||||
value_handling: generated_in_custody
|
||||
- id: deliver-secret
|
||||
owner: railiance-platform
|
||||
when: client_type == confidential
|
||||
entry_point: reviewed adapter railiance-platform/keycape-deliver-secret, dispatched
|
||||
by Warden's case kernel; Platform's deliver capability applies ExternalSecret
|
||||
sso/keycape-{client_id}-client (key client-secret) through fixed ClusterSecretStore
|
||||
openbao-keycape-{client_id}-issuer (cluster-scoped, restricted to namespace sso),
|
||||
defined by Platform-controlled manifests/templates/keycape-issuer-eso.yaml
|
||||
(sha256:e17498bd8a4c820041a4105e4efe9c60c6a50953db8f621ff54887080065c743),
|
||||
using role external-secrets-keycape-{client_id}, from bao:platform/workloads/keycape/{client_id}
|
||||
auth: Platform's own delivery path on lane platform-secret-delivery, inside the human
|
||||
aal2 session; ESO reads custody through role external-secrets-keycape-{client_id};
|
||||
Warden holds no Platform credential, and no value leaves custody
|
||||
lane: platform-secret-delivery
|
||||
warden_executes: false
|
||||
attended: false
|
||||
value_handling: none
|
||||
ready_check: the delivery for sso/keycape-{client_id}-client reports Ready=True
|
||||
(status only, no value read)
|
||||
- id: bind-env
|
||||
owner: net-kingdom
|
||||
when: client_type == confidential
|
||||
entry_point: 'reviewed adapter net-kingdom/keycape-bind-env, dispatched by Warden''s
|
||||
case kernel inside the attended session: patch Deployment sso/keycape env
|
||||
KEYCAPE_{client_id:env}_CLIENT_SECRET from sso/keycape-{client_id}-client, by
|
||||
reference only'
|
||||
auth: the attended human session on lane keycape-attended-case-change, with operator
|
||||
kube credentials for namespace sso (actor human, parent-session revoked per attempt)
|
||||
lane: keycape-attended-case-change
|
||||
warden_executes: false
|
||||
attended: true
|
||||
value_handling: none
|
||||
attended_justification: A change to the live identity provider's Deployment. It
|
||||
runs in the same attended operator session as register-client, before it, because
|
||||
KeyCape resolves a confidential client's secret at start and fails closed if
|
||||
the binding is missing. Warden's case kernel dispatches the reviewed adapter only inside the one attended human session (descriptor actor human, lane keycape-attended-case-change, parent-session revoked per attempt); the human session is the authority and the kernel is only the dispatcher.
|
||||
- id: register-client
|
||||
owner: net-kingdom
|
||||
entry_point: reviewed adapter net-kingdom/keycape-register-client, dispatched by Warden's
|
||||
case kernel inside the attended session; it runs sso-mfa/k8s/keycape/keycape-client.py
|
||||
apply --client-file <client-from-case> --case-acceptance <record> --check-config
|
||||
<keycape binary> (net-kingdom.keycape-client-registration)
|
||||
auth: the attended human session on lane keycape-attended-case-change, with operator
|
||||
write access to the live KeyCape client registry in namespace sso (actor human,
|
||||
parent-session revoked per attempt)
|
||||
lane: keycape-attended-case-change
|
||||
warden_executes: false
|
||||
attended: true
|
||||
value_handling: none
|
||||
attended_justification: 'The live KeyCape client registry is a Kubernetes Secret,
|
||||
and agents do not read or write Secrets. Until KeyCape loads clients from a
|
||||
non-secret registry with secrets referenced from custody, one attended operator
|
||||
session applies the rendered registration. ops-warden groups all attended steps
|
||||
of a run into that one session. KeyCape loads clients at startup and keeps login,
|
||||
authorization and handoff state in memory, so the restart drops in-flight logins
|
||||
and sessions for every client; runs go in a quiet window (key-cape type review
|
||||
R4). The rendered registry must pass KeyCape''s own startup checks (keycape
|
||||
check-config, the pinned checker artifact sha256:be37738fe1c08259fb4f9ca0daf0f9301bfffc237e5bb38435525dfbf11ccff5
|
||||
built from key-cape 3c71226, which keycape-client.py runs before it patches)
|
||||
before the restart: a refused entry stops the single replica and every client
|
||||
loses sign-in (R1). Warden''s case kernel dispatches the reviewed adapter only inside the one attended human session (descriptor actor human, lane keycape-attended-case-change, parent-session revoked per attempt); the human session is the authority and the kernel is only the dispatcher.'
|
||||
- id: deliver-consumer-secret
|
||||
owner: '{consumer_owner_repo}'
|
||||
when: client_type == confidential
|
||||
entry_point: Reviewed adapter net-kingdom/keycape-deliver-consumer-secret, dispatched
|
||||
by Warden's case kernel inside the attended consumer-owner session; the owner's
|
||||
apply capability creates dedicated ServiceAccount {consumer_namespace}/{consuming_service}-keycape-{client_id}-eso
|
||||
(automount=false, no legacy tokens), namespace SecretStore openbao-keycape-{client_id}-consumer
|
||||
with serviceAccountRef and audiences [openbao], and ExternalSecret {consuming_service}-netkingdom-oidc-client
|
||||
with Owner target, Retain remote deletion and only CLIENT_SECRET from workloads/keycape/{client_id}.
|
||||
Admission validates actual installed ESO TokenRequest support and common controller
|
||||
RBAC before any owner-approved apply.
|
||||
auth: the attended consumer-owner session on lane consumer-owner-deployment (actor
|
||||
human, the consumer owner's operator deployment authority, on the founder's go
|
||||
for the change); ESO authenticates to OpenBao role external-secrets-keycape-{client_id}-{consuming_service}
|
||||
with a TokenRequest for the dedicated namespaced serviceAccountRef; controller
|
||||
remains trusted
|
||||
lane: consumer-owner-deployment
|
||||
warden_executes: false
|
||||
attended: true
|
||||
value_handling: none
|
||||
attended_justification: The consumer owner's only deployment authority in the consumer
|
||||
namespace is its operator credential, and production applies need the founder's
|
||||
go per change. No contained agent deployer identity exists, and the owner does
|
||||
not want a standing credential an agent could trigger. It runs in one attended
|
||||
consumer-owner session together with hand-over. Warden's case kernel dispatches
|
||||
the reviewed adapter only inside that session (actor human, parent-session
|
||||
revoked per attempt); the human session is the authority and the kernel is only
|
||||
the dispatcher.
|
||||
ready_check: Namespace SecretStore and ExternalSecret both report Ready=True;
|
||||
dedicated SA has no legacy token refs; status and fixed metadata only, never
|
||||
Secret values.
|
||||
- id: hand-over
|
||||
owner: '{consumer_owner_repo}'
|
||||
entry_point: The consumer owner first makes its own Helm change configuring {consuming_service}
|
||||
with issuer {issuer}, client {client_id} and custody bao:platform/workloads/keycape/{client_id},
|
||||
requiring one of {access_groups} in the groups claim before sign-in completes.
|
||||
Then reviewed adapter net-kingdom/keycape-hand-over, dispatched by Warden's case
|
||||
kernel inside the attended consumer-owner session, verifies the consumer with
|
||||
four value-free probes (member-login, non-member-refused, membership-add-grants,
|
||||
membership-remove-revokes) and records the hand-over receipt.
|
||||
auth: the attended consumer-owner session on lane apps-consumer-handover (actor human,
|
||||
parent-session revoked per attempt); the consumer configuration itself is the
|
||||
owner's own deployment path
|
||||
lane: apps-consumer-handover
|
||||
warden_executes: false
|
||||
attended: true
|
||||
value_handling: none
|
||||
attended_justification: The verification needs a real member login and a refused
|
||||
non-member login, which only an attended session can perform, and it follows
|
||||
the owner's own Helm change in the same consumer-owner session as
|
||||
deliver-consumer-secret. Warden's case kernel dispatches the reviewed adapter
|
||||
only inside that session (actor human, parent-session revoked per attempt); the
|
||||
human session is the authority and the kernel is only the dispatcher.
|
||||
verification:
|
||||
- id: authorize-accepts-redirects
|
||||
check: GET {issuer}/authorize with client {client_id} and each of {redirect_uris}
|
||||
returns the login page
|
||||
expect: present
|
||||
reads_secret_value: false
|
||||
- id: authorize-refuses-other-redirect
|
||||
check: GET {issuer}/authorize with client {client_id} and an unregistered redirect
|
||||
returns an OAuth error
|
||||
expect: deny
|
||||
reads_secret_value: false
|
||||
- id: custody-denied-to-default
|
||||
when: client_type == confidential
|
||||
check: a default-only OpenBao identity has no read capability on bao:platform/workloads/keycape/{client_id}
|
||||
expect: deny
|
||||
reads_secret_value: false
|
||||
- id: custody-allowed-to-consumer
|
||||
when: client_type == confidential
|
||||
check: policy workload-kv-read-keycape-{client_id}-{consuming_service} grants
|
||||
read on bao:platform/workloads/keycape/{client_id} (capabilities check only)
|
||||
expect: allow
|
||||
reads_secret_value: false
|
||||
- id: groups-present
|
||||
check: LLDAP groups {access_groups} exist, with exactly the memberships of the
|
||||
accepted case added
|
||||
expect: present
|
||||
reads_secret_value: false
|
||||
- id: consumer-enforces-groups
|
||||
check: '{consuming_service}''s OIDC source for {client_id} (owner {consumer_owner_repo})
|
||||
requires one of {access_groups} in the groups claim (configuration read, no
|
||||
secret value)'
|
||||
expect: present
|
||||
reads_secret_value: false
|
||||
- id: secret-delivered
|
||||
when: client_type == confidential
|
||||
check: the delivery for sso/keycape-{client_id}-client reports Ready=True (status
|
||||
only)
|
||||
expect: present
|
||||
reads_secret_value: false
|
||||
- id: mfa-pinned
|
||||
when: mfa_required == true
|
||||
check: keycape-client.py inspect for {client_id} shows the MFA pin on the registered
|
||||
client (non-secret client view)
|
||||
expect: present
|
||||
reads_secret_value: false
|
||||
- id: consumer-secret-delivered
|
||||
when: client_type == confidential
|
||||
check: '{consumer_namespace} SecretStore openbao-keycape-{client_id}-consumer
|
||||
and ExternalSecret {consuming_service}-netkingdom-oidc-client Ready=True, exact
|
||||
dedicated SA/audience role binding, fixed CLIENT_SECRET mapping; no Secret value
|
||||
read'
|
||||
expect: present
|
||||
reads_secret_value: false
|
||||
rollback:
|
||||
- reverses: hand-over
|
||||
action: the adapter removes only its hand-over receipt. Removing the OIDC source
|
||||
for {client_id} from {consuming_service} is the owner's Helm rollback (owner
|
||||
{consumer_owner_repo}), done inside deliver-consumer-secret's owner_remove
|
||||
before any consumer object is removed
|
||||
- reverses: deliver-consumer-secret
|
||||
when: client_type == confidential
|
||||
action: After reverting consumer secret reference, delete only authenticated created
|
||||
ExternalSecret/owned target by UID and ownerReference, then SecretStore and
|
||||
dedicated SA; preserve preexisting-matched resources and never delete a shared
|
||||
target. Retain audit and ownership receipts; removing SecretStore/SA alone is
|
||||
not immediate OpenBao token revocation.
|
||||
- reverses: register-client
|
||||
action: keycape-client.py apply --operation remove for {client_id}
|
||||
- reverses: bind-env
|
||||
when: client_type == confidential
|
||||
action: remove env KEYCAPE_{client_id:env}_CLIENT_SECRET from Deployment sso/keycape,
|
||||
after register-client is reversed
|
||||
- reverses: deliver-secret
|
||||
when: client_type == confidential
|
||||
action: delete ExternalSecret sso/keycape-{client_id}-client and its target
|
||||
- reverses: mint-secret
|
||||
when: client_type == confidential
|
||||
action: 'covered by client-secret-path: one metadata delete, after deliver-secret
|
||||
is reversed'
|
||||
- reverses: ensure-groups
|
||||
action: identity-provisioner revoke_service_groups inside the provisioner pod
|
||||
(attended, as ensure-groups) with the case's memberships; delete_if_empty only
|
||||
if no other case uses {access_groups}
|
||||
- reverses: client-secret-path
|
||||
when: client_type == confidential
|
||||
action: delete metadata and all versions at platform/workloads/keycape/{client_id},
|
||||
after the ExternalSecret is gone (this also reverses mint-secret)
|
||||
- reverses: eso-role
|
||||
when: client_type == confidential
|
||||
action: delete Kubernetes-auth role external-secrets-keycape-{client_id}
|
||||
- reverses: keycape-reader-policy
|
||||
when: client_type == confidential
|
||||
action: delete policy workload-kv-read-keycape-{client_id}-eso, after eso-role
|
||||
- reverses: consumer-eso-role
|
||||
when: client_type == confidential
|
||||
action: Delete only this case-created role external-secrets-keycape-{client_id}-{consuming_service},
|
||||
before deleting consumer-reader-policy; revoke case-issued OpenBao tokens or
|
||||
prove expiration before declaring deactivation.
|
||||
- reverses: consumer-reader-policy
|
||||
when: client_type == confidential
|
||||
action: delete policy workload-kv-read-keycape-{client_id}-{consuming_service}
|
||||
- reverses: catalog-entry
|
||||
action: remove catalog entry {consuming_service}-keycape-client
|
||||
acceptance:
|
||||
accepted_by:
|
||||
- owner
|
||||
- founder
|
||||
risk_class: medium
|
||||
# Reviewed machine descriptors (NK-WP-0048-T16 successor candidate). Digests are
|
||||
# generated by sso-mfa/k8s/keycape/compose-1.2-descriptors.py --version 1.3.
|
||||
execution_descriptors:
|
||||
bind-env: sha256:d5fcb20cf15bf7454890d2529180c582218e3df12584a99162e4b664b899caa0
|
||||
deliver-consumer-secret: sha256:36281df8139fb1ec64b226d94f80eb592de5addab7063ff54c691c641e275fc7
|
||||
deliver-secret: sha256:75aaa7346aebdea2a0eb0ba993995515b5248ffefc34caf8e713525e9b58da85
|
||||
ensure-groups: sha256:ebc9d5b9167d18e6015f4bbeb9699e8d396d64fbfaf195142781f84f4f47a70a
|
||||
hand-over: sha256:1bce07a3229b7d0c7c730f35ce5b40fe471009a82de8dd53788ed970a9be1224
|
||||
mint-secret: sha256:c92957227235db04953809d92b260d32c8b947472a8e8fd17a258ade3ae8da70
|
||||
register-client: sha256:4ecc769f75433b66c7161f13c0fe7e5599633cf466d46cb6d8031f0456f426e7
|
||||
|
|
@ -0,0 +1,43 @@
|
|||
# F2 proposed source publication packet
|
||||
|
||||
Prepared local integration only; not executed against either canonical checkout.
|
||||
`manifest.json` lists exact source hashes and absent preimages. Platform HEAD was
|
||||
`0a9617a62d4696a8070798923b459e8405c3dbf2`; staged `.custodian-brief.md` and
|
||||
unstaged `WORK-RECORDS.md` already existed. Preserve them.
|
||||
|
||||
Check, then apply the four reviewed files:
|
||||
|
||||
```bash
|
||||
python3 /home/worsch/the-custodian/docs/changes/netkingdom-target-2026-10-17/nk48-r5-f2-publication-r1/integrate_platform.py
|
||||
python3 /home/worsch/the-custodian/docs/changes/netkingdom-target-2026-10-17/nk48-r5-f2-publication-r1/integrate_platform.py --apply
|
||||
```
|
||||
|
||||
The script checks the exact HEAD, absent tracked/worktree preimages, hashes and
|
||||
symlink safety; it writes only the four manifest paths. It never stages, commits,
|
||||
syncs, applies native resources or edits NetKingdom. The source remains explicitly
|
||||
proposed/source-only, with no inferred owner assent. It verifies that unrelated
|
||||
Git status and index bytes are unchanged. A concurrent failure may leave a
|
||||
partial set of newly created files; inspect it rather than reset unrelated dirt.
|
||||
|
||||
Run the focused test in Platform, then stage only these paths. To keep the
|
||||
preexisting staged brief out of the source commit, use a path-scoped commit:
|
||||
|
||||
```bash
|
||||
git add -- docs/keycape-issuer-eso-contract.md manifests/templates/keycape-issuer-eso.yaml scripts/keycape_issuer_eso.py tests/test_keycape_issuer_eso.py
|
||||
git commit --only -m "Prepare proposed fixed KeyCape issuer ESO source contract" -- docs/keycape-issuer-eso-contract.md manifests/templates/keycape-issuer-eso.yaml scripts/keycape_issuer_eso.py tests/test_keycape_issuer_eso.py
|
||||
```
|
||||
|
||||
Root handles the already-authorized normal repository sync and its exact-commit
|
||||
receipt. No task is marked done. `progress-paragraph.md` explains the completed
|
||||
source-fit work and remaining real implementation. No legitimate Platform
|
||||
workplan task was found: `RPF-1.3-T4` lives in the original owner-review JSON only.
|
||||
Do not append the paragraph to an unrelated finished workplan or create a new one.
|
||||
|
||||
`netkingdom-entry-point.conditional.patch` is the unchanged exact proposed-1.3
|
||||
entry text from the reviewed packet. Keep it unapplied until F3 is combined and
|
||||
the existing combined artifact regenerated. It edits only the issuer delivery
|
||||
entry point, pins the exact template digest and grants no adoption or activation.
|
||||
|
||||
`integration-checks.json` records the isolated scratch application, preservation
|
||||
of synthetic staged/unstaged dirt, rejection of non-absent preimages, and eight
|
||||
passing focused tests. Tests used a minimal environment and local Git only.
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
{
|
||||
"first_attempt": "missing /home/worsch/railiance-platform/.venv/bin/python; exit127; tests did not run",
|
||||
"rerun": "env PYTHONDONTWRITEBYTECODE=1 HOME=/tmp PATH=/usr/bin:/bin python3 -B -m unittest discover -s tests -p test_keycape_issuer_eso.py -q",
|
||||
"result": "8 tests passed; fixture-only metadata, no native/API calls"
|
||||
}
|
||||
|
|
@ -0,0 +1,604 @@
|
|||
{
|
||||
"repo_slug": "railiance-platform",
|
||||
"repo_path": "/home/worsch/railiance-platform",
|
||||
"source_revision": "104ed90f42a9a86bf9fda343b49b65a85c9ee4b9",
|
||||
"source_revision_reason": null,
|
||||
"issues": [
|
||||
{
|
||||
"severity": "WARN",
|
||||
"check_id": "C-35",
|
||||
"message": "Repo Manager missing: README.md: README.md is required for flavor tooling",
|
||||
"file_path": "README.md",
|
||||
"db_id": "",
|
||||
"file_value": "",
|
||||
"db_value": "",
|
||||
"fixable": false
|
||||
},
|
||||
{
|
||||
"severity": "WARN",
|
||||
"check_id": "C-40",
|
||||
"message": "blocker satisfied, task still wait: 'RPF-WP-0015-T03' in workplan 'RPF-WP-0015' \u2014 decision 2fd5849c-240e-4d0a-bf07-5ebf8dfce3fd is resolved",
|
||||
"file_path": "workplans/RPF-WP-0015-audit-core-custody-and-recovery-coordination.md#RPF-WP-0015-T03",
|
||||
"db_id": "",
|
||||
"file_value": "2fd5849c-240e-4d0a-bf07-5ebf8dfce3fd",
|
||||
"db_value": "",
|
||||
"fixable": false
|
||||
},
|
||||
{
|
||||
"severity": "WARN",
|
||||
"check_id": "C-40",
|
||||
"message": "blocker satisfied, task still wait: 'RPF-WP-0035-T02' in workplan 'RPF-WP-0035' \u2014 decision 62aa95e4-3ccc-4867-8806-5e3db005b483 is resolved",
|
||||
"file_path": "workplans/RPF-WP-0035-credential-lane-implementation.md#RPF-WP-0035-T02",
|
||||
"db_id": "",
|
||||
"file_value": "62aa95e4-3ccc-4867-8806-5e3db005b483",
|
||||
"db_value": "",
|
||||
"fixable": false
|
||||
},
|
||||
{
|
||||
"severity": "WARN",
|
||||
"check_id": "C-40",
|
||||
"message": "blocker satisfied, task still wait: 'RPF-WP-0035-T03' in workplan 'RPF-WP-0035' \u2014 decision 62aa95e4-3ccc-4867-8806-5e3db005b483 is resolved",
|
||||
"file_path": "workplans/RPF-WP-0035-credential-lane-implementation.md#RPF-WP-0035-T03",
|
||||
"db_id": "",
|
||||
"file_value": "62aa95e4-3ccc-4867-8806-5e3db005b483",
|
||||
"db_value": "",
|
||||
"fixable": false
|
||||
},
|
||||
{
|
||||
"severity": "WARN",
|
||||
"check_id": "C-40",
|
||||
"message": "blocker satisfied, task still wait: 'RPF-WP-0035-T07' in workplan 'RPF-WP-0035' \u2014 decision 1591daa1-6e85-4dfd-aadd-5a9fe1ef47fa is resolved",
|
||||
"file_path": "workplans/RPF-WP-0035-credential-lane-implementation.md#RPF-WP-0035-T07",
|
||||
"db_id": "",
|
||||
"file_value": "1591daa1-6e85-4dfd-aadd-5a9fe1ef47fa",
|
||||
"db_value": "",
|
||||
"fixable": false
|
||||
},
|
||||
{
|
||||
"severity": "WARN",
|
||||
"check_id": "C-40",
|
||||
"message": "blocker satisfied, task still wait: 'RPF-WP-0035-T08' in workplan 'RPF-WP-0035' \u2014 decision ab85ae8a-12a0-4945-ae64-1d3d86d5b26f is resolved",
|
||||
"file_path": "workplans/RPF-WP-0035-credential-lane-implementation.md#RPF-WP-0035-T08",
|
||||
"db_id": "",
|
||||
"file_value": "ab85ae8a-12a0-4945-ae64-1d3d86d5b26f",
|
||||
"db_value": "",
|
||||
"fixable": false
|
||||
},
|
||||
{
|
||||
"severity": "WARN",
|
||||
"check_id": "C-40",
|
||||
"message": "blocker satisfied, task still wait: 'RPF-WP-0036-T03' in workplan 'RPF-WP-0036' \u2014 decision 89f1058f-33bc-47a8-8a49-0b44d426c67e is resolved",
|
||||
"file_path": "workplans/RPF-WP-0036-platform-service-assurance.md#RPF-WP-0036-T03",
|
||||
"db_id": "",
|
||||
"file_value": "89f1058f-33bc-47a8-8a49-0b44d426c67e",
|
||||
"db_value": "",
|
||||
"fixable": false
|
||||
},
|
||||
{
|
||||
"severity": "WARN",
|
||||
"check_id": "C-39",
|
||||
"message": "Wait task 'RPF-WP-0038-T04' in workplan 'RPF-WP-0038' has neither depends_on, needs_human: true, blocked_on: message-from:\u2026 nor wait_until \u2014 unqualified wait",
|
||||
"file_path": "workplans/RPF-WP-0038-forgejo-scaleway-primary-coverage.md#RPF-WP-0038-T04",
|
||||
"db_id": "",
|
||||
"file_value": "wait",
|
||||
"db_value": "",
|
||||
"fixable": false
|
||||
},
|
||||
{
|
||||
"severity": "WARN",
|
||||
"check_id": "C-40",
|
||||
"message": "blocker satisfied, task still wait: 'RPF-WP-0043-T03' in workplan 'RPF-WP-0043' \u2014 decision 89f1058f-33bc-47a8-8a49-0b44d426c67e is resolved",
|
||||
"file_path": "workplans/RPF-WP-0043-policy-nexus-argocd-onboarding.md#RPF-WP-0043-T03",
|
||||
"db_id": "",
|
||||
"file_value": "89f1058f-33bc-47a8-8a49-0b44d426c67e",
|
||||
"db_value": "",
|
||||
"fixable": false
|
||||
},
|
||||
{
|
||||
"severity": "WARN",
|
||||
"check_id": "C-40",
|
||||
"message": "blocker satisfied, task still wait: 'RPF-WP-0043-T04' in workplan 'RPF-WP-0043' \u2014 decision 89f1058f-33bc-47a8-8a49-0b44d426c67e is resolved",
|
||||
"file_path": "workplans/RPF-WP-0043-policy-nexus-argocd-onboarding.md#RPF-WP-0043-T04",
|
||||
"db_id": "",
|
||||
"file_value": "89f1058f-33bc-47a8-8a49-0b44d426c67e",
|
||||
"db_value": "",
|
||||
"fixable": false
|
||||
},
|
||||
{
|
||||
"severity": "WARN",
|
||||
"check_id": "C-40",
|
||||
"message": "blocker satisfied, task still wait: 'RPF-WP-0044-T04' in workplan 'RPF-WP-0044' \u2014 decision 89f1058f-33bc-47a8-8a49-0b44d426c67e is resolved",
|
||||
"file_path": "workplans/RPF-WP-0044-argocd-phase-b-adopt-existing-applications.md#RPF-WP-0044-T04",
|
||||
"db_id": "",
|
||||
"file_value": "89f1058f-33bc-47a8-8a49-0b44d426c67e",
|
||||
"db_value": "",
|
||||
"fixable": false
|
||||
},
|
||||
{
|
||||
"severity": "WARN",
|
||||
"check_id": "C-40",
|
||||
"message": "blocker satisfied, task still wait: 'RPF-WP-0044-T06' in workplan 'RPF-WP-0044' \u2014 decision 89f1058f-33bc-47a8-8a49-0b44d426c67e is resolved",
|
||||
"file_path": "workplans/RPF-WP-0044-argocd-phase-b-adopt-existing-applications.md#RPF-WP-0044-T06",
|
||||
"db_id": "",
|
||||
"file_value": "89f1058f-33bc-47a8-8a49-0b44d426c67e",
|
||||
"db_value": "",
|
||||
"fixable": false
|
||||
},
|
||||
{
|
||||
"severity": "WARN",
|
||||
"check_id": "C-40",
|
||||
"message": "blocker satisfied, task still wait: 'RPF-WP-0044-T07' in workplan 'RPF-WP-0044' \u2014 decision 89f1058f-33bc-47a8-8a49-0b44d426c67e is resolved",
|
||||
"file_path": "workplans/RPF-WP-0044-argocd-phase-b-adopt-existing-applications.md#RPF-WP-0044-T07",
|
||||
"db_id": "",
|
||||
"file_value": "89f1058f-33bc-47a8-8a49-0b44d426c67e",
|
||||
"db_value": "",
|
||||
"fixable": false
|
||||
},
|
||||
{
|
||||
"severity": "WARN",
|
||||
"check_id": "C-40",
|
||||
"message": "blocker satisfied, task still wait: 'RPF-WP-0048-T02' in workplan 'RPF-WP-0048' \u2014 decision 89f1058f-33bc-47a8-8a49-0b44d426c67e is resolved",
|
||||
"file_path": "workplans/RPF-WP-0048-activity-core-gitops-adoption.md#RPF-WP-0048-T02",
|
||||
"db_id": "",
|
||||
"file_value": "89f1058f-33bc-47a8-8a49-0b44d426c67e",
|
||||
"db_value": "",
|
||||
"fixable": false
|
||||
},
|
||||
{
|
||||
"severity": "WARN",
|
||||
"check_id": "C-23",
|
||||
"message": "Lifecycle drift in 'rpf-wp-0053': workplan status 'proposed' has an active task \u2014 report only (ADR-007: do not auto-promote proposed)",
|
||||
"file_path": "workplans/RPF-WP-0053-coulomb-paired-backup-admission.md",
|
||||
"db_id": "cb55e474-c7a5-5c6f-bd72-ce65f853e3e3",
|
||||
"file_value": "proposed",
|
||||
"db_value": "proposed",
|
||||
"fixable": false
|
||||
},
|
||||
{
|
||||
"severity": "WARN",
|
||||
"check_id": "C-39",
|
||||
"message": "Wait task 'RPF-WP-0058-T05' in workplan 'RPF-WP-0058' has neither depends_on, needs_human: true, blocked_on: message-from:\u2026 nor wait_until \u2014 unqualified wait",
|
||||
"file_path": "workplans/RPF-WP-0058-reuse-private-source-reader.md#RPF-WP-0058-T05",
|
||||
"db_id": "",
|
||||
"file_value": "wait",
|
||||
"db_value": "",
|
||||
"fixable": false
|
||||
},
|
||||
{
|
||||
"severity": "WARN",
|
||||
"check_id": "C-20",
|
||||
"message": "Workplan dependency target 'RAILIANCE-WP-0004' is not linked to State Hub",
|
||||
"file_path": "workplans/archived/260702-RPF-WP-0023-workload-kv-access-lanes.md",
|
||||
"db_id": "",
|
||||
"file_value": "RAILIANCE-WP-0004",
|
||||
"db_value": "",
|
||||
"fixable": false
|
||||
},
|
||||
{
|
||||
"severity": "WARN",
|
||||
"check_id": "C-20",
|
||||
"message": "Workplan dependency target 'RAILIANCE-WP-0005' is not linked to State Hub",
|
||||
"file_path": "workplans/archived/260702-RPF-WP-0024-credential-change-approval-workflow.md",
|
||||
"db_id": "",
|
||||
"file_value": "RAILIANCE-WP-0005",
|
||||
"db_value": "",
|
||||
"fixable": false
|
||||
},
|
||||
{
|
||||
"severity": "WARN",
|
||||
"check_id": "C-20",
|
||||
"message": "Workplan dependency target 'RAILIANCE-WP-0006' is not linked to State Hub",
|
||||
"file_path": "workplans/archived/260702-RPF-WP-0024-credential-change-approval-workflow.md",
|
||||
"db_id": "",
|
||||
"file_value": "RAILIANCE-WP-0006",
|
||||
"db_value": "",
|
||||
"fixable": false
|
||||
},
|
||||
{
|
||||
"severity": "WARN",
|
||||
"check_id": "C-20",
|
||||
"message": "Workplan dependency target 'RAILIANCE-WP-0005' is not linked to State Hub",
|
||||
"file_path": "workplans/archived/260905-RPF-WP-0002-openbao-approved-automation-delegation.md",
|
||||
"db_id": "",
|
||||
"file_value": "RAILIANCE-WP-0005",
|
||||
"db_value": "",
|
||||
"fixable": false
|
||||
},
|
||||
{
|
||||
"severity": "WARN",
|
||||
"check_id": "C-20",
|
||||
"message": "Workplan dependency target 'RAILIANCE-WP-0006' is not linked to State Hub",
|
||||
"file_path": "workplans/archived/260905-RPF-WP-0002-openbao-approved-automation-delegation.md",
|
||||
"db_id": "",
|
||||
"file_value": "RAILIANCE-WP-0006",
|
||||
"db_value": "",
|
||||
"fixable": false
|
||||
},
|
||||
{
|
||||
"severity": "WARN",
|
||||
"check_id": "C-20",
|
||||
"message": "Workplan dependency target 'RAILIANCE-WP-0007' is not linked to State Hub",
|
||||
"file_path": "workplans/archived/260905-RPF-WP-0002-openbao-approved-automation-delegation.md",
|
||||
"db_id": "",
|
||||
"file_value": "RAILIANCE-WP-0007",
|
||||
"db_value": "",
|
||||
"fixable": false
|
||||
},
|
||||
{
|
||||
"severity": "WARN",
|
||||
"check_id": "C-20",
|
||||
"message": "Workplan dependency target 'RAILIANCE-WP-0004' is not linked to State Hub",
|
||||
"file_path": "workplans/archived/260905-RPF-WP-0003-issue-core-runtime-ingestion-key-lane.md",
|
||||
"db_id": "",
|
||||
"file_value": "RAILIANCE-WP-0004",
|
||||
"db_value": "",
|
||||
"fixable": false
|
||||
},
|
||||
{
|
||||
"severity": "WARN",
|
||||
"check_id": "C-20",
|
||||
"message": "Workplan dependency target 'RAILIANCE-WP-0007' is not linked to State Hub",
|
||||
"file_path": "workplans/archived/260905-RPF-WP-0003-issue-core-runtime-ingestion-key-lane.md",
|
||||
"db_id": "",
|
||||
"file_value": "RAILIANCE-WP-0007",
|
||||
"db_value": "",
|
||||
"fixable": false
|
||||
},
|
||||
{
|
||||
"severity": "WARN",
|
||||
"check_id": "C-20",
|
||||
"message": "Workplan dependency target 'RAILIANCE-WP-0008' is not linked to State Hub",
|
||||
"file_path": "workplans/archived/260905-RPF-WP-0003-issue-core-runtime-ingestion-key-lane.md",
|
||||
"db_id": "",
|
||||
"file_value": "RAILIANCE-WP-0008",
|
||||
"db_value": "",
|
||||
"fixable": false
|
||||
},
|
||||
{
|
||||
"severity": "WARN",
|
||||
"check_id": "C-20",
|
||||
"message": "Workplan dependency target 'RAILIANCE-WP-0004' is not linked to State Hub",
|
||||
"file_path": "workplans/archived/260905-RPF-WP-0004-llm-connect-openrouter-provider-key-lane.md",
|
||||
"db_id": "",
|
||||
"file_value": "RAILIANCE-WP-0004",
|
||||
"db_value": "",
|
||||
"fixable": false
|
||||
},
|
||||
{
|
||||
"severity": "WARN",
|
||||
"check_id": "C-20",
|
||||
"message": "Workplan dependency target 'RAILIANCE-WP-0007' is not linked to State Hub",
|
||||
"file_path": "workplans/archived/260905-RPF-WP-0004-llm-connect-openrouter-provider-key-lane.md",
|
||||
"db_id": "",
|
||||
"file_value": "RAILIANCE-WP-0007",
|
||||
"db_value": "",
|
||||
"fixable": false
|
||||
},
|
||||
{
|
||||
"severity": "WARN",
|
||||
"check_id": "C-20",
|
||||
"message": "Workplan dependency target 'RAILIANCE-WP-0008' is not linked to State Hub",
|
||||
"file_path": "workplans/archived/260905-RPF-WP-0004-llm-connect-openrouter-provider-key-lane.md",
|
||||
"db_id": "",
|
||||
"file_value": "RAILIANCE-WP-0008",
|
||||
"db_value": "",
|
||||
"fixable": false
|
||||
},
|
||||
{
|
||||
"severity": "WARN",
|
||||
"check_id": "C-20",
|
||||
"message": "Workplan dependency target 'RAILIANCE-WP-0007' is not linked to State Hub",
|
||||
"file_path": "workplans/archived/260905-RPF-WP-0005-reuse-surface-runtime-secrets-openbao-lane.md",
|
||||
"db_id": "",
|
||||
"file_value": "RAILIANCE-WP-0007",
|
||||
"db_value": "",
|
||||
"fixable": false
|
||||
},
|
||||
{
|
||||
"severity": "WARN",
|
||||
"check_id": "C-20",
|
||||
"message": "Workplan dependency target 'RAILIANCE-WP-0008' is not linked to State Hub",
|
||||
"file_path": "workplans/archived/260905-RPF-WP-0005-reuse-surface-runtime-secrets-openbao-lane.md",
|
||||
"db_id": "",
|
||||
"file_value": "RAILIANCE-WP-0008",
|
||||
"db_value": "",
|
||||
"fixable": false
|
||||
},
|
||||
{
|
||||
"severity": "INFO",
|
||||
"check_id": "C-08",
|
||||
"message": "Retired DB workstream 'railiance-wp-0003@retired-20260826' (id=665b3b9b\u2026, status=finished) has no backing workplan file \u2014 projection tombstone, not live work",
|
||||
"file_path": "",
|
||||
"db_id": "665b3b9b-608a-4be4-84b6-dcb8261ff57b",
|
||||
"file_value": "",
|
||||
"db_value": "",
|
||||
"fixable": false
|
||||
},
|
||||
{
|
||||
"severity": "INFO",
|
||||
"check_id": "C-08",
|
||||
"message": "Retired DB workstream 'railiance-platform-railiance-wp-0004@retired-20260826' (id=e57e487b\u2026, status=finished) has no backing workplan file \u2014 projection tombstone, not live work",
|
||||
"file_path": "",
|
||||
"db_id": "e57e487b-8557-439d-8093-0457c73ede93",
|
||||
"file_value": "",
|
||||
"db_value": "",
|
||||
"fixable": false
|
||||
},
|
||||
{
|
||||
"severity": "INFO",
|
||||
"check_id": "C-08",
|
||||
"message": "Retired DB workstream 'railiance-platform-railiance-wp-0005@retired-20260826' (id=2731fece\u2026, status=finished) has no backing workplan file \u2014 projection tombstone, not live work",
|
||||
"file_path": "",
|
||||
"db_id": "2731fece-6c49-45b8-ab8a-4ea6c04ac603",
|
||||
"file_value": "",
|
||||
"db_value": "",
|
||||
"fixable": false
|
||||
},
|
||||
{
|
||||
"severity": "INFO",
|
||||
"check_id": "C-08",
|
||||
"message": "Retired DB workstream 'railiance-platform-railiance-wp-0006@retired-20260826' (id=96c8a93d\u2026, status=finished) has no backing workplan file \u2014 projection tombstone, not live work",
|
||||
"file_path": "",
|
||||
"db_id": "96c8a93d-7a5a-4fa9-8f7b-865119551da3",
|
||||
"file_value": "",
|
||||
"db_value": "",
|
||||
"fixable": false
|
||||
},
|
||||
{
|
||||
"severity": "INFO",
|
||||
"check_id": "C-08",
|
||||
"message": "Retired DB workstream 'railiance-platform-railiance-wp-0007@retired-20260826' (id=4d7ce243\u2026, status=finished) has no backing workplan file \u2014 projection tombstone, not live work",
|
||||
"file_path": "",
|
||||
"db_id": "4d7ce243-f40a-4249-a46a-a24f75d6fe4c",
|
||||
"file_value": "",
|
||||
"db_value": "",
|
||||
"fixable": false
|
||||
},
|
||||
{
|
||||
"severity": "INFO",
|
||||
"check_id": "C-08",
|
||||
"message": "Retired DB workstream 'railiance-platform-railiance-wp-0008@retired-20260826' (id=671898ef\u2026, status=finished) has no backing workplan file \u2014 projection tombstone, not live work",
|
||||
"file_path": "",
|
||||
"db_id": "671898ef-2378-4814-b8f6-066148cdad46",
|
||||
"file_value": "",
|
||||
"db_value": "",
|
||||
"fixable": false
|
||||
},
|
||||
{
|
||||
"severity": "INFO",
|
||||
"check_id": "C-08",
|
||||
"message": "Retired DB workstream 'railiance-platform-railiance-wp-0009@retired-20260826' (id=b059c81d\u2026, status=finished) has no backing workplan file \u2014 projection tombstone, not live work",
|
||||
"file_path": "",
|
||||
"db_id": "b059c81d-96f1-451f-896f-a05cd73744a1",
|
||||
"file_value": "",
|
||||
"db_value": "",
|
||||
"fixable": false
|
||||
},
|
||||
{
|
||||
"severity": "INFO",
|
||||
"check_id": "C-08",
|
||||
"message": "Retired DB workstream 'railiance-platform-railiance-wp-0010@retired-20260826' (id=f364d405\u2026, status=finished) has no backing workplan file \u2014 projection tombstone, not live work",
|
||||
"file_path": "",
|
||||
"db_id": "f364d405-a85d-4b89-b600-1964ab436cad",
|
||||
"file_value": "",
|
||||
"db_value": "",
|
||||
"fixable": false
|
||||
},
|
||||
{
|
||||
"severity": "INFO",
|
||||
"check_id": "C-08",
|
||||
"message": "Retired DB workstream 'railiance-wp-0029@retired-20260826' (id=038bc3c0\u2026, status=blocked) has no backing workplan file \u2014 projection tombstone, not live work",
|
||||
"file_path": "",
|
||||
"db_id": "038bc3c0-4492-5b91-95eb-ae515ca205df",
|
||||
"file_value": "",
|
||||
"db_value": "",
|
||||
"fixable": false
|
||||
},
|
||||
{
|
||||
"severity": "INFO",
|
||||
"check_id": "C-08",
|
||||
"message": "Retired DB workstream 'railiance-wp-0024@retired-20260826' (id=88c4ef7f\u2026, status=blocked) has no backing workplan file \u2014 projection tombstone, not live work",
|
||||
"file_path": "",
|
||||
"db_id": "88c4ef7f-0af8-580e-90dc-a2bae2675a4d",
|
||||
"file_value": "",
|
||||
"db_value": "",
|
||||
"fixable": false
|
||||
},
|
||||
{
|
||||
"severity": "INFO",
|
||||
"check_id": "C-08",
|
||||
"message": "Retired DB workstream 'railiance-platform-railiance-wp-0011@retired-20260826' (id=3f9fd8de\u2026, status=finished) has no backing workplan file \u2014 projection tombstone, not live work",
|
||||
"file_path": "",
|
||||
"db_id": "3f9fd8de-7235-4486-aaa7-634dba2bb6fa",
|
||||
"file_value": "",
|
||||
"db_value": "",
|
||||
"fixable": false
|
||||
},
|
||||
{
|
||||
"severity": "INFO",
|
||||
"check_id": "C-08",
|
||||
"message": "Retired DB workstream 'railiance-wp-0027@retired-20260826' (id=6f8a6cbc\u2026, status=blocked) has no backing workplan file \u2014 projection tombstone, not live work",
|
||||
"file_path": "",
|
||||
"db_id": "6f8a6cbc-c076-5f0a-ade2-281a7ec71360",
|
||||
"file_value": "",
|
||||
"db_value": "",
|
||||
"fixable": false
|
||||
},
|
||||
{
|
||||
"severity": "INFO",
|
||||
"check_id": "C-08",
|
||||
"message": "Retired DB workstream 'railiance-wp-0017@retired-20260826' (id=16e8f234\u2026, status=finished) has no backing workplan file \u2014 projection tombstone, not live work",
|
||||
"file_path": "",
|
||||
"db_id": "16e8f234-9305-5ff3-af82-4639215a0c54",
|
||||
"file_value": "",
|
||||
"db_value": "",
|
||||
"fixable": false
|
||||
},
|
||||
{
|
||||
"severity": "INFO",
|
||||
"check_id": "C-08",
|
||||
"message": "Retired DB workstream 'railiance-wp-0028@retired-20260826' (id=022dda1f\u2026, status=finished) has no backing workplan file \u2014 projection tombstone, not live work",
|
||||
"file_path": "",
|
||||
"db_id": "022dda1f-8772-57ad-862e-672924455f11",
|
||||
"file_value": "",
|
||||
"db_value": "",
|
||||
"fixable": false
|
||||
},
|
||||
{
|
||||
"severity": "INFO",
|
||||
"check_id": "C-08",
|
||||
"message": "Retired DB workstream 'railiance-wp-0015@retired-20260826' (id=1c73af72\u2026, status=finished) has no backing workplan file \u2014 projection tombstone, not live work",
|
||||
"file_path": "",
|
||||
"db_id": "1c73af72-fd4b-5ab6-8ff7-a3c302bf55a5",
|
||||
"file_value": "",
|
||||
"db_value": "",
|
||||
"fixable": false
|
||||
},
|
||||
{
|
||||
"severity": "INFO",
|
||||
"check_id": "C-08",
|
||||
"message": "Retired DB workstream 'railiance-wp-0022@retired-20260826' (id=a7977cbf\u2026, status=finished) has no backing workplan file \u2014 projection tombstone, not live work",
|
||||
"file_path": "",
|
||||
"db_id": "a7977cbf-ab43-56b7-b7a9-ceb52382b341",
|
||||
"file_value": "",
|
||||
"db_value": "",
|
||||
"fixable": false
|
||||
},
|
||||
{
|
||||
"severity": "INFO",
|
||||
"check_id": "C-08",
|
||||
"message": "Retired DB workstream 'railiance-wp-0025@retired-20260826' (id=2422d4e7\u2026, status=finished) has no backing workplan file \u2014 projection tombstone, not live work",
|
||||
"file_path": "",
|
||||
"db_id": "2422d4e7-2125-591e-8e95-80ecc6e09da0",
|
||||
"file_value": "",
|
||||
"db_value": "",
|
||||
"fixable": false
|
||||
},
|
||||
{
|
||||
"severity": "INFO",
|
||||
"check_id": "C-08",
|
||||
"message": "Retired DB workstream 'railiance-wp-0016@retired-20260826' (id=857ca302\u2026, status=finished) has no backing workplan file \u2014 projection tombstone, not live work",
|
||||
"file_path": "",
|
||||
"db_id": "857ca302-bb94-5d41-a649-1aa966368df4",
|
||||
"file_value": "",
|
||||
"db_value": "",
|
||||
"fixable": false
|
||||
},
|
||||
{
|
||||
"severity": "INFO",
|
||||
"check_id": "C-08",
|
||||
"message": "Retired DB workstream 'railiance-wp-0026@retired-20260826' (id=73878f8d\u2026, status=finished) has no backing workplan file \u2014 projection tombstone, not live work",
|
||||
"file_path": "",
|
||||
"db_id": "73878f8d-d8bb-5845-8a5c-91cdd83c1066",
|
||||
"file_value": "",
|
||||
"db_value": "",
|
||||
"fixable": false
|
||||
},
|
||||
{
|
||||
"severity": "INFO",
|
||||
"check_id": "C-08",
|
||||
"message": "Retired DB workstream 'railiance-wp-0023@retired-20260826' (id=ec52fbe6\u2026, status=finished) has no backing workplan file \u2014 projection tombstone, not live work",
|
||||
"file_path": "",
|
||||
"db_id": "ec52fbe6-571b-570e-884b-c7a32ff7bcc8",
|
||||
"file_value": "",
|
||||
"db_value": "",
|
||||
"fixable": false
|
||||
},
|
||||
{
|
||||
"severity": "WARN",
|
||||
"check_id": "C-25",
|
||||
"message": "Owner-return task 'RPF-WP-0035-T08' waiting on audit-core has 9 reply/replies after its last dated note (or unread when undated) \u2014 owner action needed: 570ca050-0316-4e38-9c16-be41057c2330, 2de1c6e7-9602-48e5-868d-0048fbbef150, e7c36b17-d8f7-4d86-af09-02111289d1f5, 5fe769a7-d35b-4e37-a671-673d13cf8948, b3c564a6-93f9-4c07-872b-7feb22cccb73, ec4995b5-e1b2-4c66-9f8d-5ac8d6ceb149, 7c4019f2-348d-475e-9c96-e1ff6d0d27f5, ff43fd2c-7633-4b34-9ae6-2483ba03a271, 0d0da0d2-36a1-4ffc-b7bf-7896fad691f6",
|
||||
"file_path": "workplans/RPF-WP-0035-credential-lane-implementation.md#RPF-WP-0035-T08",
|
||||
"db_id": "",
|
||||
"file_value": "message-from:audit-core",
|
||||
"db_value": "",
|
||||
"fixable": false,
|
||||
"repo_slug": "railiance-platform",
|
||||
"source_revision": "104ed90f42a9a86bf9fda343b49b65a85c9ee4b9",
|
||||
"source_revision_reason": null,
|
||||
"owner_return": {
|
||||
"task_record_id": "RPF-WP-0035-T08",
|
||||
"message_ids": [
|
||||
"570ca050-0316-4e38-9c16-be41057c2330",
|
||||
"2de1c6e7-9602-48e5-868d-0048fbbef150",
|
||||
"e7c36b17-d8f7-4d86-af09-02111289d1f5",
|
||||
"5fe769a7-d35b-4e37-a671-673d13cf8948",
|
||||
"b3c564a6-93f9-4c07-872b-7feb22cccb73",
|
||||
"ec4995b5-e1b2-4c66-9f8d-5ac8d6ceb149",
|
||||
"7c4019f2-348d-475e-9c96-e1ff6d0d27f5",
|
||||
"ff43fd2c-7633-4b34-9ae6-2483ba03a271",
|
||||
"0d0da0d2-36a1-4ffc-b7bf-7896fad691f6"
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"severity": "WARN",
|
||||
"check_id": "C-25",
|
||||
"message": "Owner-return task 'RPF-WP-0056-T03' waiting on railiance-cluster has 9 reply/replies after its last dated note (or unread when undated) \u2014 owner action needed: 4e9d4e58-71b3-497b-8954-cedc30740529, 13acb774-8fbe-4ca3-b44d-89a4742138bd, c9435491-8545-4d48-a57a-1773f2789092, fb8f25df-57ce-40e5-ae67-0c0b392746e8, 4bb0d7af-b644-44f3-acc8-69c7321e9824, 5301d4fb-0631-4ac5-8471-531e5362b2ba, 2087b9ac-c980-4881-89f1-6b1088132f5b, 78b89fb2-ced6-45f1-9718-0670b8257d45, 110e8dea-ce81-40ff-8161-325ddb89cd67",
|
||||
"file_path": "workplans/RPF-WP-0056-playwright-consumer-admission.md#RPF-WP-0056-T03",
|
||||
"db_id": "",
|
||||
"file_value": "message-from:railiance-cluster",
|
||||
"db_value": "",
|
||||
"fixable": false,
|
||||
"repo_slug": "railiance-platform",
|
||||
"source_revision": "104ed90f42a9a86bf9fda343b49b65a85c9ee4b9",
|
||||
"source_revision_reason": null,
|
||||
"owner_return": {
|
||||
"task_record_id": "RPF-WP-0056-T03",
|
||||
"message_ids": [
|
||||
"4e9d4e58-71b3-497b-8954-cedc30740529",
|
||||
"13acb774-8fbe-4ca3-b44d-89a4742138bd",
|
||||
"c9435491-8545-4d48-a57a-1773f2789092",
|
||||
"fb8f25df-57ce-40e5-ae67-0c0b392746e8",
|
||||
"4bb0d7af-b644-44f3-acc8-69c7321e9824",
|
||||
"5301d4fb-0631-4ac5-8471-531e5362b2ba",
|
||||
"2087b9ac-c980-4881-89f1-6b1088132f5b",
|
||||
"78b89fb2-ced6-45f1-9718-0670b8257d45",
|
||||
"110e8dea-ce81-40ff-8161-325ddb89cd67"
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"severity": "WARN",
|
||||
"check_id": "C-28",
|
||||
"message": "14 unread message(s) lack thread_id for supersession tracking: the-custodian:836a8502, the-custodian:72c6a148, audit-core:0d0da0d2, rapp-postgres:6d047a08, railiance-platform:c4a80a60",
|
||||
"file_path": "",
|
||||
"db_id": "",
|
||||
"file_value": "",
|
||||
"db_value": "",
|
||||
"fixable": false
|
||||
},
|
||||
{
|
||||
"severity": "WARN",
|
||||
"check_id": "C-26",
|
||||
"message": "Workplan id 'RAIL-PL-WP-0002' uses prefix 'RAIL-PL-WP' but repo canonical prefix is 'RPF-WP' \u2014 new plans must conform",
|
||||
"file_path": "workplans/archived/260702-RAIL-PL-WP-0002-openbao-platform-secrets-service.md",
|
||||
"db_id": "",
|
||||
"file_value": "RAIL-PL-WP-0002",
|
||||
"db_value": "RPF-WP",
|
||||
"fixable": false
|
||||
},
|
||||
{
|
||||
"severity": "WARN",
|
||||
"check_id": "C-26",
|
||||
"message": "Workplan id 'RAIL-PL-WP-0001' uses prefix 'RAIL-PL-WP' but repo canonical prefix is 'RPF-WP' \u2014 new plans must conform",
|
||||
"file_path": "workplans/archived/RAIL-PL-WP-0001-platform-baseline.md",
|
||||
"db_id": "",
|
||||
"file_value": "RAIL-PL-WP-0001",
|
||||
"db_value": "RPF-WP",
|
||||
"fixable": false
|
||||
}
|
||||
],
|
||||
"fixes_applied": [
|
||||
"bindings: synced 64/64",
|
||||
"C-17 cleared: pushed 1 backlogged commit(s)",
|
||||
"push: pushed"
|
||||
],
|
||||
"summary": {
|
||||
"fail": 0,
|
||||
"automation_error": 0,
|
||||
"warn": 35,
|
||||
"info": 20
|
||||
},
|
||||
"result": "warn"
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue