Gate ESO adoption on verified predecessors and receive release custody handoff
Assistant: codex Assistant-Model: gpt-6.1-sol Assistant-Session: 01a126e1-76f8-7643-8cf3-e93336539ade
This commit is contained in:
parent
3001a8148f
commit
7c79293831
4 changed files with 148 additions and 1 deletions
|
|
@ -1,5 +1,5 @@
|
|||
# DRAFT for railiance01 (RPF-WP-0044). Not synced by any root: move to
|
||||
# ../applications/ only in this app's adoption task, with the founder's go-ahead.
|
||||
# ../applications/ only after predecessor automated proof under accepted D2.
|
||||
# No automated sync, no finalizer. CRDs survive Application pruning/deletion.
|
||||
apiVersion: argoproj.io/v1alpha1
|
||||
kind: Application
|
||||
|
|
|
|||
48
intakes/RPF-IN-0011-activity-core-release-custody.md
Normal file
48
intakes/RPF-IN-0011-activity-core-release-custody.md
Normal file
|
|
@ -0,0 +1,48 @@
|
|||
---
|
||||
id: RPF-IN-0011
|
||||
kind: intake
|
||||
type: intake
|
||||
title: "Prepare dedicated activity-core image-only release principal and custody"
|
||||
repo: railiance-platform
|
||||
lane: supervised
|
||||
status: open
|
||||
owner: railiance-platform
|
||||
requested_by: activity-core
|
||||
origin: handoff
|
||||
origin_ref: ACTIVITY-WP-0041-T03
|
||||
priority: high
|
||||
created: "2026-10-11"
|
||||
updated: "2026-10-11"
|
||||
tags: [security, cross-owner, release, credential-custody]
|
||||
---
|
||||
|
||||
Prepare and return an exact owner workplan/task binding for the already D2-authorized
|
||||
`ACTIVITY-WP-0041-image-only-v1` release scope. This intake is independent of the
|
||||
RPF48 gate that awaits ACT41 acceptance. It requests owner implementation and native
|
||||
acceptance, not another founder approval or a secret value. RPF-WP-0036-T03 is
|
||||
backup freshness work and does not supply this release identity/custody commitment.
|
||||
|
||||
Fixed intended Kubernetes identity is
|
||||
`system:serviceaccount:activity-core:activity-core-release-broker` on estate
|
||||
92.205.62.239, cluster UID a553c742-0115-43d4-99a4-a5ca56fe0786. Native read-only
|
||||
metadata found no dedicated account, only default/activity-core-eso, with no matching
|
||||
namespace/ArgoCD role bindings. ESO projection custody cannot be borrowed for release.
|
||||
Platform must choose and independently attest the fixed Git publication identity
|
||||
and exclusive broker custody, plus distinct build/review/health/retention issuer
|
||||
principals and Ed25519 key custody/rotation; no local fixture key is acceptance.
|
||||
|
||||
Activity-core source supplies bounded health GET, 60-second durable observer,
|
||||
private config/credential drift checks, actual Kubernetes username and eleven
|
||||
allow/deny checks, dedicated Temporal worker, exact one-field Git publication,
|
||||
targeted non-pruning Argo operations, durable rollback and sanitized audit sink.
|
||||
See activity-core docs/release-runtime-owner-handoff.md and this repo's
|
||||
docs/activity-core-release-admission.md for exact boundaries. Keep privileged
|
||||
transport behind this reviewed broker because Application RBAC and repository-write
|
||||
credentials alone cannot enforce the one-field scope.
|
||||
|
||||
Return source-bound owner acceptance and actual Git/Kubernetes allow/deny, custody,
|
||||
revocation, independent signed receipts, continuous observer/ledger, Temporal
|
||||
registration, normal release and failed-health rollback proof. No native activation
|
||||
is claimed. If preparation requires attended admin authentication, stage the exact
|
||||
reviewed custody command first, then place that login in the orange checklist.
|
||||
Only a genuinely expanded action/access/custody scope goes to the red daily batch.
|
||||
61
scripts/eso_adoption_readiness.py
Normal file
61
scripts/eso_adoption_readiness.py
Normal file
|
|
@ -0,0 +1,61 @@
|
|||
#!/usr/bin/env python3
|
||||
"""Local, non-executing ESO adoption readiness gate. No credential or live calls."""
|
||||
import argparse
|
||||
from datetime import datetime,timezone
|
||||
import hashlib
|
||||
import json
|
||||
from pathlib import Path
|
||||
import re
|
||||
import yaml
|
||||
|
||||
D2='89f1058f-33bc-47a8-8a49-0b44d426c67e'
|
||||
|
||||
def require(ok,reason):
|
||||
if not ok:raise ValueError(reason)
|
||||
|
||||
def fresh(receipt,now):
|
||||
stamp=datetime.fromisoformat(receipt['captured_at'].replace('Z','+00:00'))
|
||||
require(stamp.tzinfo is not None and 0<=(now-stamp).total_seconds()<=300,'predecessor_proof_stale')
|
||||
|
||||
def source_check(app):
|
||||
require(app.get('kind')=='Application' and app.get('metadata',{}).get('name')=='external-secrets' and app['metadata'].get('namespace')=='argocd','source_identity')
|
||||
spec=app['spec'];source=spec['source'];require(spec.get('project')=='railiance-platform-addons' and spec.get('destination')=={'server':'https://kubernetes.default.svc','namespace':'external-secrets'},'source_destination')
|
||||
require(source.get('repoURL')=='https://charts.external-secrets.io' and source.get('chart')=='external-secrets' and source.get('targetRevision')=='0.16.1' and source['helm'].get('releaseName')=='external-secrets','source_chart_pin')
|
||||
values=yaml.safe_load(source['helm']['values']);require(values.get('installCRDs') is True,'install_crds')
|
||||
options=values.get('crds',{}).get('annotations',{}).get('argocd.argoproj.io/sync-options','')
|
||||
require(set(options.split(','))=={'Prune=false','Delete=false'},'crd_prune_delete_protection')
|
||||
sync=spec.get('syncPolicy',{});require('automated' not in sync and not app['metadata'].get('finalizers'),'manual_no_finalizer')
|
||||
require('ServerSideApply=true' in sync.get('syncOptions',[]),'server_side_apply')
|
||||
require(not any(x in sync.get('syncOptions',[]) for x in ('Force=true','Replace=true')),'no_force_replace')
|
||||
return {'chart_pin':'0.16.1','crd_prune_delete_protected':True,'manual_sync_only':True,'server_side_apply':True}
|
||||
|
||||
def issue_check(receipt,now):
|
||||
require(receipt.get('schema_version')=='rapp-issue-core-adoption/v2' and receipt.get('passed') is True,'issue_core_adoption_missing')
|
||||
fresh(receipt,now);checks=receipt['checks'];require(checks.get('estate_node_ip') is True and checks.get('argo_synced_healthy') is True and checks.get('argo_tracking') is True and checks.get('external_secret_ready') is True and checks.get('private_service') is True,'issue_core_adoption_incomplete')
|
||||
require(bool(re.fullmatch(r'[0-9a-f]{40}',checks.get('source_revision',''))),'issue_core_immutable_revision')
|
||||
require(all(checks.get(k)==v for k,v in {'healthz_status':200,'unauthenticated_ingest_status':401,'authenticated_ingest_status':201,'authenticated_close_status':200}.items()),'issue_core_controlled_smoke_missing')
|
||||
|
||||
def target_check(receipt,now):
|
||||
require(receipt.get('schema_version')=='argocd-workload-adoption-proof/v1' and receipt.get('status')=='verified' and receipt.get('application')=='target-revenue','target_revenue_adoption_missing')
|
||||
fresh(receipt,now);require(receipt.get('manual_apply_prune_off') is True and receipt.get('child_automation_enabled') is False,'target_revenue_adoption_incomplete')
|
||||
|
||||
def assess(app,issue,target,now=None):
|
||||
now=now or datetime.now(timezone.utc);missing=[];checks={}
|
||||
for name,fn,value in [('source',source_check,app),('RPF-WP-0044-T04',issue_check,issue),('RPF-WP-0044-T05',target_check,target)]:
|
||||
try:
|
||||
result=fn(value) if name=='source' else fn(value,now)
|
||||
checks[name]=result if name=='source' else True
|
||||
except Exception:missing.append(name)
|
||||
return {'schema':'eso-adoption-readiness/v1','task':'RPF-WP-0044-T06','approval_ref':D2,'source_ready':bool(checks.get('source')),'ready_for_fresh_native_render_diff':not missing,'missing_or_invalid':missing,'checks':checks,'next_action':'fresh_native_render_diff' if not missing else 'wait_for_predecessor_automated_proof','founder_approval_required':False,'native_mutation':False,'live_or_credential_calls':False}
|
||||
|
||||
def main():
|
||||
parser=argparse.ArgumentParser(description=__doc__);parser.add_argument('--draft',required=True,type=Path);parser.add_argument('--issue-core-proof',type=Path);parser.add_argument('--target-revenue-proof',type=Path)
|
||||
args=parser.parse_args()
|
||||
def load(path):
|
||||
try:return json.loads(path.read_text()) if path else {}
|
||||
except Exception:return {}
|
||||
try:app=yaml.safe_load(args.draft.read_text());receipt=assess(app,load(args.issue_core_proof),load(args.target_revenue_proof))
|
||||
except Exception:receipt={'ready_for_fresh_native_render_diff':False,'failure':'source_unverified','native_mutation':False,'live_or_credential_calls':False}
|
||||
print(json.dumps(receipt,sort_keys=True));return 0 if receipt['ready_for_fresh_native_render_diff'] else 2
|
||||
|
||||
if __name__=='__main__':raise SystemExit(main())
|
||||
38
tests/test_eso_adoption_readiness.py
Normal file
38
tests/test_eso_adoption_readiness.py
Normal file
|
|
@ -0,0 +1,38 @@
|
|||
import copy
|
||||
from datetime import datetime,timezone,timedelta
|
||||
import importlib.util
|
||||
import json
|
||||
from pathlib import Path
|
||||
import unittest
|
||||
import yaml
|
||||
spec=importlib.util.spec_from_file_location('eso_gate',Path(__file__).resolve().parents[1]/'scripts/eso_adoption_readiness.py');gate=importlib.util.module_from_spec(spec);spec.loader.exec_module(gate)
|
||||
NOW=datetime(2026,10,11,21,0,tzinfo=timezone.utc)
|
||||
|
||||
class ReadinessTests(unittest.TestCase):
|
||||
def app(self):return yaml.safe_load((Path(__file__).resolve().parents[1]/'argocd/railiance01/drafts/external-secrets.application.yaml').read_text())
|
||||
def issue(self):return {'schema_version':'rapp-issue-core-adoption/v2','passed':True,'captured_at':NOW.isoformat(),'checks':{'estate_node_ip':True,'argo_synced_healthy':True,'argo_tracking':True,'external_secret_ready':True,'private_service':True,'source_revision':'a'*40,'healthz_status':200,'unauthenticated_ingest_status':401,'authenticated_ingest_status':201,'authenticated_close_status':200}}
|
||||
def target(self):return {'schema_version':'argocd-workload-adoption-proof/v1','status':'verified','application':'target-revenue','captured_at':NOW.isoformat(),'manual_apply_prune_off':True,'child_automation_enabled':False}
|
||||
def test_prepared_source_cannot_skip_issue_core_dependency(self):
|
||||
result=gate.assess(self.app(),{},self.target(),NOW);self.assertTrue(result['source_ready']);self.assertFalse(result['ready_for_fresh_native_render_diff']);self.assertEqual(result['missing_or_invalid'],['RPF-WP-0044-T04']);self.assertFalse(result['founder_approval_required'])
|
||||
def test_complete_fresh_predecessors_allow_diff_only(self):
|
||||
result=gate.assess(self.app(),self.issue(),self.target(),NOW);self.assertTrue(result['ready_for_fresh_native_render_diff']);self.assertFalse(result['native_mutation']);self.assertEqual(result['next_action'],'fresh_native_render_diff')
|
||||
def test_stale_future_wrong_estate_or_missing_smoke_cannot_authorize_adoption(self):
|
||||
for key,value in [('captured_at',(NOW-timedelta(minutes=6)).isoformat()),('captured_at',(NOW+timedelta(seconds=1)).isoformat())]:
|
||||
issue=self.issue();issue[key]=value;self.assertFalse(gate.assess(self.app(),issue,self.target(),NOW)['ready_for_fresh_native_render_diff'])
|
||||
for key,value in [('estate_node_ip',False),('argo_tracking',False),('authenticated_close_status',None),('source_revision','main')]:
|
||||
issue=self.issue();issue['checks'][key]=value;self.assertFalse(gate.assess(self.app(),issue,self.target(),NOW)['ready_for_fresh_native_render_diff'])
|
||||
def test_crd_guards_manual_policy_and_no_replace_required(self):
|
||||
for mutation in ('guard','automation','replace','chart'):
|
||||
app=self.app()
|
||||
if mutation=='guard':app['spec']['source']['helm']['values']='installCRDs: true\n'
|
||||
elif mutation=='automation':app['spec']['syncPolicy']['automated']={}
|
||||
elif mutation=='replace':app['spec']['syncPolicy']['syncOptions'].append('Replace=true')
|
||||
else:app['spec']['source']['targetRevision']='latest'
|
||||
result=gate.assess(app,self.issue(),self.target(),NOW);self.assertFalse(result['source_ready']);self.assertFalse(result['ready_for_fresh_native_render_diff'])
|
||||
def test_invalid_canary_fields_never_echo_values(self):
|
||||
app=self.app();app['spec']['source']['helm']['values']='SECRET_CANARY';issue=self.issue();issue['private_value']='SECRET_CANARY';result=gate.assess(app,issue,{},NOW);self.assertNotIn('SECRET_CANARY',json.dumps(result))
|
||||
def test_target_revenue_proof_must_be_exact_application_and_prune_off(self):
|
||||
for key,value in [('application','foreign-app'),('manual_apply_prune_off',False),('child_automation_enabled',True),('status','unverified')]:
|
||||
target=self.target();target[key]=value;self.assertFalse(gate.assess(self.app(),self.issue(),target,NOW)['ready_for_fresh_native_render_diff'])
|
||||
|
||||
if __name__=='__main__':unittest.main()
|
||||
Loading…
Add table
Add a link
Reference in a new issue