Assistant: codex Assistant-Model: gpt-6-astra Assistant-Session: 01a1346b-72a4-7d40-b8bd-8b8ca346cfa2
162 lines
7.8 KiB
Python
162 lines
7.8 KiB
Python
#!/usr/bin/env python3
|
|
"""Prepare Playwright client records; no credential access or live mutation.
|
|
|
|
The registry adapter below is for an owner-controlled activation ceremony. Its
|
|
encoded result contains the original private registry and must stay in memory.
|
|
The CLI emits only newly constructed non-secret client/binding proposals.
|
|
"""
|
|
from __future__ import annotations
|
|
|
|
import argparse
|
|
import copy
|
|
from decimal import Decimal
|
|
import importlib.util
|
|
import json
|
|
from pathlib import Path
|
|
import re
|
|
import sys
|
|
|
|
spec = importlib.util.spec_from_file_location('playwright_registry', Path(__file__).with_name('keycape-client.py'))
|
|
registry = importlib.util.module_from_spec(spec)
|
|
spec.loader.exec_module(registry)
|
|
require = registry.require
|
|
Refused = registry.Refused
|
|
AUDIENCE = 'playwright-kubernetes'
|
|
SCOPE = 'playwright:connect'
|
|
CALLBACK = 'http://127.0.0.1:18764/callback'
|
|
ISSUER = 'https://kc.coulomb.social'
|
|
SLUG = re.compile(r'[a-z0-9][a-z0-9-]{0,39}\Z')
|
|
IDENTIFIER = re.compile(r'[a-z0-9][a-z0-9:._-]{0,159}\Z')
|
|
|
|
|
|
def public_client():
|
|
return {'clientId': 'playwright-cli', 'audience': AUDIENCE,
|
|
'displayName': 'Playwright platform consumer CLI',
|
|
'redirectUris': [CALLBACK],
|
|
'allowedScopes': ['openid', 'profile', 'groups', SCOPE],
|
|
'grantTypes': ['authorization_code'], 'clientType': 'public'}
|
|
|
|
|
|
def agent_client(identity):
|
|
"""Reference an existing registered identity; never manufacture its subject/tenant."""
|
|
require(isinstance(identity, dict) and set(identity) ==
|
|
{'agent_id', 'service_subject', 'tenant', 'identity_record'}, 'agent_identity_fields_required')
|
|
require(all(isinstance(v, str) for v in identity.values()), 'agent_identity_strings_required')
|
|
require(SLUG.fullmatch(identity['agent_id']) is not None, 'agent_id_invalid')
|
|
require(identity['service_subject'].startswith('service:') and
|
|
IDENTIFIER.fullmatch(identity['service_subject']) is not None and
|
|
len(identity['service_subject']) > len('service:'), 'registered_service_subject_required')
|
|
require(identity['tenant'].startswith('tenant:') and
|
|
IDENTIFIER.fullmatch(identity['tenant']) is not None and
|
|
len(identity['tenant']) > len('tenant:'), 'registered_tenant_required')
|
|
# This is a reference for owner review, not proof that registration exists.
|
|
reference = identity['identity_record']
|
|
require(1 <= len(reference) <= 512 and all(32 < ord(c) < 127 for c in reference),
|
|
'identity_record_reference_required')
|
|
cid = 'playwright-agent-' + identity['agent_id']
|
|
return {'clientId': cid, 'audience': AUDIENCE,
|
|
'displayName': 'Playwright agent ' + identity['agent_id'],
|
|
'allowedScopes': [SCOPE], 'grantTypes': ['client_credentials'],
|
|
'clientType': 'confidential', 'secretRef': 'env:' + registry.env_name(cid),
|
|
'serviceSubject': identity['service_subject'], 'tenant': identity['tenant'],
|
|
'roles': ['coding-agent'], 'tokenLifetime': '15m'}
|
|
|
|
|
|
def clients(identities):
|
|
require(isinstance(identities, list) and len(identities) <= 100, 'agent_inventory_invalid')
|
|
result = [public_client()] + [agent_client(identity) for identity in identities]
|
|
for field in ['clientId', 'serviceSubject', 'secretRef']:
|
|
values = [c[field] for c in result if field in c]
|
|
require(len(values) == len(set(values)), 'duplicate_agent_identity_or_binding')
|
|
return result
|
|
|
|
|
|
def human_lifetime(config):
|
|
"""Bound the existing issuer setting; do not alter all humans' token lifetime."""
|
|
raw = config.get('tokenLifetime')
|
|
require(isinstance(raw, str) and 0 < len(raw) <= 128, 'explicit_human_token_lifetime_required')
|
|
units = {'h': Decimal(3600), 'm': Decimal(60), 's': Decimal(1),
|
|
'ms': Decimal('.001'), 'us': Decimal('.000001'),
|
|
'µs': Decimal('.000001'), 'μs': Decimal('.000001'), 'ns': Decimal('.000000001')}
|
|
parts = re.findall(r'([0-9]+(?:\.[0-9]*)?|\.[0-9]+)(ns|us|µs|μs|ms|s|m|h)', raw)
|
|
require(parts and ''.join(value + unit for value, unit in parts) == raw,
|
|
'human_token_lifetime_invalid')
|
|
seconds = sum((Decimal(value) * units[unit] for value, unit in parts), Decimal(0))
|
|
require(1 <= seconds <= 3600, 'human_token_lifetime_outside_cluster_bound')
|
|
return seconds
|
|
|
|
|
|
def prepare_registry(secret, identities, operation='add'):
|
|
"""Byte-preserving proposal only. Caller owns CAS, check-config and readback.
|
|
|
|
Refuse drift before constructing any result. A missing client during removal
|
|
is an idempotent no-op. Removal does not revoke issued tokens/connections.
|
|
"""
|
|
require(operation in ('add', 'remove'), 'operation_invalid')
|
|
wanted = clients(identities)
|
|
_, config, _ = registry.pin.issuer_document(secret)
|
|
require(config.get('issuer') == ISSUER, 'keycape_issuer_mismatch')
|
|
human_lifetime(config)
|
|
require(isinstance(config.get('clients'), list), 'client_sequence_required')
|
|
for existing in config['clients']:
|
|
require(isinstance(existing, dict), 'client_shape_unexpected')
|
|
if (existing.get('audience') or existing.get('clientId')) == AUDIENCE:
|
|
require(existing in wanted, 'unreviewed_playwright_audience_registration')
|
|
# Preflight every record against the same original, including collisions.
|
|
for client in wanted:
|
|
_, current, _ = registry._inspect(secret, client)
|
|
if 'serviceSubject' in client:
|
|
for other in current['clients']:
|
|
if other['clientId'] != client['clientId'] and (other.get('audience') or other.get('clientId')) == AUDIENCE:
|
|
require(other.get('serviceSubject') != client['serviceSubject'],
|
|
'playwright_subject_already_registered')
|
|
matching = [c for c in current['clients'] if c['clientId'] == client['clientId']]
|
|
require(not matching or matching == [client], 'existing_registration_differs_requires_reconciliation')
|
|
candidate = copy.deepcopy(secret)
|
|
changed_ids = []
|
|
merge = registry.replacement if operation == 'add' else registry.removal
|
|
for client in wanted:
|
|
encoded, changed = merge(candidate, client)
|
|
candidate['data']['config.yaml'] = encoded
|
|
if changed:
|
|
changed_ids.append(client['clientId'])
|
|
return candidate['data']['config.yaml'], changed_ids
|
|
|
|
|
|
def unique_object(pairs):
|
|
result = {}
|
|
for key, value in pairs:
|
|
require(key not in result, 'duplicate_input_field')
|
|
result[key] = value
|
|
return result
|
|
|
|
|
|
def main(argv=None):
|
|
parser = argparse.ArgumentParser(description=__doc__.split('\n', 1)[0])
|
|
parser.add_argument('--agents', help='Non-secret JSON array of existing agent identity references')
|
|
args = parser.parse_args(argv)
|
|
try:
|
|
identities = []
|
|
if args.agents:
|
|
with Path(args.agents).open('rb') as stream:
|
|
raw = stream.read(65537)
|
|
require(len(raw) <= 65536, 'agent_inventory_too_large')
|
|
identities = json.loads(raw, object_pairs_hook=unique_object)
|
|
records = clients(identities)
|
|
print(json.dumps({'schema': 'netkingdom.playwright-registration.v1',
|
|
'status': 'source_proposal', 'clients': records,
|
|
'identity_references': {c['clientId']: identity['identity_record']
|
|
for c, identity in zip(records[1:], identities)},
|
|
'deployment_env': [registry.env_binding(c) for c in records if 'secretRef' in c],
|
|
'identity_evidence_verified': False,
|
|
'custody_admitted': False, 'live_activated': False}, indent=2))
|
|
return 0
|
|
except Refused as error:
|
|
print(json.dumps({'status': 'refused', 'reason': str(error)}))
|
|
except Exception:
|
|
print(json.dumps({'status': 'refused', 'reason': 'invalid_registration_input'}))
|
|
return 1
|
|
|
|
|
|
if __name__ == '__main__':
|
|
sys.exit(main())
|