Sitting requester: accept approval:create+read; one-shot refuses re-run; CCR-0026/0027 amendment proposed
key-cape 82b471f (founder decision 2026-11-01) adds approval:read to informed-decision-sitting-requester. provision-sitting-requester.py contract() now requires exactly [approval:create, approval:read] and still refuses consume, approve and openid. The one-shot helper refuses to re-run once its 2026-09-15 receipt is applied, before it could overwrite that evidence, and names keycape-client.py remove-then-add. Tests updated. CCR-2026-0026/0027: text-only amendment_proposed; custody, policy, role and delivery unchanged, so no new CCR and status stays applied. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Assistant: claude-code Assistant-Model: opus Assistant-Process: 5502@bnt-lap001 Assistant-Session: 68b3b02f-5d0f-4e71-8421-796f62e50196
This commit is contained in:
parent
6858bef858
commit
356d84a095
4 changed files with 95 additions and 9 deletions
|
|
@ -28,6 +28,15 @@ review:
|
|||
decision: approved
|
||||
comment: User instructed register sitting-requester. Apply remains the attended
|
||||
helper with CAS=0 custody, exact create-only client, and no sitting POST.
|
||||
- at: '2026-11-01'
|
||||
reviewer: railiance-platform
|
||||
decision: amendment_proposed
|
||||
comment: 'key-cape 82b471f (founder decision 2026-11-01) adds approval:read to client
|
||||
informed-decision-sitting-requester for informed-decision present_existing intake.
|
||||
Text-only amendment, see amendments: custody path, field, policy, role, bound identity,
|
||||
ESO delivery and env binding are unchanged, so no new CCR and no OpenBao change.
|
||||
Needs platform-owner and key-cape-owner approval, recorded by the founder''s scope
|
||||
decision, before the live keycape-client.py remove-then-add.'
|
||||
target:
|
||||
domain: financials
|
||||
tenant: platform
|
||||
|
|
@ -108,3 +117,17 @@ state_hub:
|
|||
task_id: RPF-WP-0042-T01
|
||||
related_request: CCR-2026-0027
|
||||
related_workplan: INFD-WP-0002
|
||||
amendments:
|
||||
- at: '2026-11-01'
|
||||
by: railiance-platform
|
||||
status: proposed
|
||||
change: 'Scope of the client this credential authenticates widens from approval:create
|
||||
to approval:create + approval:read (key-cape 82b471f, docs/sitting-requester-read-scope.md;
|
||||
founder decision 2026-11-01). Still never approval:consume, approval:approve or
|
||||
openid. Where this record says create-only, approval:create only or no read scope,
|
||||
read: create and read, never consume, approve or openid. Residual: an exchange without
|
||||
a scope parameter yields a create+read token (key-cape token.go). The 2026-09-15
|
||||
exchange proof (scripts/prove-sitting-requester-exchange.py) stays historical
|
||||
evidence of the create-only shape; after the live apply the new shape is verified
|
||||
as in the key-cape doc (read 200 with scope exactly approval:read; consume and approve
|
||||
400). No OpenBao object, custody value or delivery changes; status stays applied.'
|
||||
|
|
|
|||
|
|
@ -27,6 +27,15 @@ review:
|
|||
decision: approved
|
||||
comment: User instructed register sitting-requester. Apply remains the attended
|
||||
helper with CAS=0 custody, exact create-only client, and no sitting POST.
|
||||
- at: '2026-11-01'
|
||||
reviewer: railiance-platform
|
||||
decision: amendment_proposed
|
||||
comment: 'key-cape 82b471f (founder decision 2026-11-01) adds approval:read to client
|
||||
informed-decision-sitting-requester for informed-decision present_existing intake.
|
||||
Text-only amendment, see amendments: custody path, field, policy, role, bound identity,
|
||||
ESO delivery and env binding are unchanged, so no new CCR and no OpenBao change.
|
||||
Needs platform-owner and key-cape-owner approval, recorded by the founder''s scope
|
||||
decision, before the live keycape-client.py remove-then-add.'
|
||||
target:
|
||||
domain: financials
|
||||
tenant: platform
|
||||
|
|
@ -117,3 +126,17 @@ state_hub:
|
|||
task_id: RPF-WP-0042-T01
|
||||
related_request: CCR-2026-0026
|
||||
related_workplan: INFD-WP-0002
|
||||
amendments:
|
||||
- at: '2026-11-01'
|
||||
by: railiance-platform
|
||||
status: proposed
|
||||
change: 'Scope of the client this credential authenticates widens from approval:create
|
||||
to approval:create + approval:read (key-cape 82b471f, docs/sitting-requester-read-scope.md;
|
||||
founder decision 2026-11-01). Still never approval:consume, approval:approve or
|
||||
openid. Where this record says create-only, approval:create only or no read scope,
|
||||
read: create and read, never consume, approve or openid. Residual: an exchange without
|
||||
a scope parameter yields a create+read token (key-cape token.go). The 2026-09-15
|
||||
exchange proof (scripts/prove-sitting-requester-exchange.py) stays historical
|
||||
evidence of the create-only shape; after the live apply the new shape is verified
|
||||
as in the key-cape doc (read 200 with scope exactly approval:read; consume and approve
|
||||
400). No OpenBao object, custody value or delivery changes; status stays applied.'
|
||||
|
|
|
|||
|
|
@ -28,14 +28,31 @@ rollout = load('requester_rollout', '/home/worsch/net-kingdom/sso-mfa/k8s/keycap
|
|||
rollout.IDS = ('informed-decision-sitting-requester',)
|
||||
|
||||
|
||||
# key-cape 82b471f (founder decision 2026-11-01): create + read for present_existing intake.
|
||||
# Still never consume, approve or openid. Custody is unchanged (CCR-2026-0026/0027 amendment).
|
||||
SCOPES = ['approval:create', 'approval:read']
|
||||
RERUN_REFUSAL = ('sitting requester already provisioned (receipt status applied, 2026-09-15); this '
|
||||
'one-shot helper does not re-run. A registration change, such as the 2026-11-01 '
|
||||
'approval:read scope, is a founder-run remove-then-add through net-kingdom '
|
||||
'sso-mfa/k8s/keycape/keycape-client.py: see key-cape docs/sitting-requester-read-scope.md.')
|
||||
|
||||
|
||||
def already_provisioned():
|
||||
try:
|
||||
return json.loads(RECEIPT.read_text()).get('status') == 'applied'
|
||||
except (OSError, ValueError):
|
||||
return False
|
||||
|
||||
|
||||
def contract():
|
||||
require(CLIENT['clientId'] == 'informed-decision-sitting-requester', 'requester_contract_drift')
|
||||
require(CLIENT['allowedScopes'] == ['approval:create'], 'requester_contract_drift')
|
||||
require(CLIENT['allowedScopes'] == SCOPES, 'requester_contract_drift')
|
||||
require(CLIENT['serviceSubject'] == 'informed-decision', 'requester_contract_drift')
|
||||
require(CLIENT['audience'] == 'approval-engine', 'requester_contract_drift')
|
||||
require(CLIENT['tenant'] == 'tenant:platform', 'requester_contract_drift')
|
||||
require('approval:consume' not in CLIENT['allowedScopes'], 'consume_scope_forbidden')
|
||||
require('approval:approve' not in CLIENT['allowedScopes'], 'approve_scope_forbidden')
|
||||
require('openid' not in CLIENT['allowedScopes'], 'openid_scope_forbidden')
|
||||
|
||||
|
||||
def run(receipt):
|
||||
|
|
@ -92,6 +109,10 @@ def run(receipt):
|
|||
|
||||
|
||||
if __name__ == '__main__':
|
||||
# Refuse before the receipt handler below, which would overwrite the 2026-09-15 evidence.
|
||||
if already_provisioned():
|
||||
sys.stderr.write('refused: ' + RERUN_REFUSAL + '\n')
|
||||
raise SystemExit(2)
|
||||
receipt = {'observed_at': datetime.now(timezone.utc).isoformat(), 'status': 'failed', 'phase': 'preflight', 'ccrs': list(CCRS), 'credential_values_emitted': False, 'approval_mutations': False, 'sitting_post': False}
|
||||
try:
|
||||
run(receipt)
|
||||
|
|
|
|||
|
|
@ -9,28 +9,47 @@ spec.loader.exec_module(m)
|
|||
|
||||
|
||||
class SittingRequesterProvisioningTests(unittest.TestCase):
|
||||
def test_contract_is_create_only_informed_decision(self):
|
||||
def test_contract_is_create_and_read_informed_decision(self):
|
||||
# key-cape 82b471f, founder decision 2026-11-01: approval:read for present_existing.
|
||||
m.contract()
|
||||
self.assertEqual(m.CLIENT['clientId'], 'informed-decision-sitting-requester')
|
||||
self.assertEqual(m.CLIENT['allowedScopes'], ['approval:create'])
|
||||
self.assertEqual(m.CLIENT['allowedScopes'], ['approval:create', 'approval:read'])
|
||||
self.assertEqual(m.CLIENT['serviceSubject'], 'informed-decision')
|
||||
self.assertEqual(m.CCRS, ('CCR-2026-0026', 'CCR-2026-0027'))
|
||||
self.assertNotIn('CCR-2026-0024', m.CCRS)
|
||||
self.assertNotIn('CCR-2026-0025', m.CCRS)
|
||||
|
||||
def test_contract_refuses_widened_scopes(self):
|
||||
def test_contract_refuses_widened_or_narrowed_scopes(self):
|
||||
original = list(m.CLIENT['allowedScopes'])
|
||||
m.CLIENT['allowedScopes'] = ['approval:create', 'approval:consume']
|
||||
with self.assertRaises(Exception):
|
||||
m.contract()
|
||||
m.CLIENT['allowedScopes'] = original
|
||||
try:
|
||||
for scopes in (['approval:create', 'approval:read', 'approval:consume'],
|
||||
['approval:create', 'approval:read', 'approval:approve'],
|
||||
['approval:create', 'approval:read', 'openid'],
|
||||
['approval:create', 'approval:consume'],
|
||||
['approval:create'],
|
||||
['approval:read', 'approval:create']):
|
||||
m.CLIENT['allowedScopes'] = scopes
|
||||
with self.assertRaises(Exception, msg=scopes):
|
||||
m.contract()
|
||||
finally:
|
||||
m.CLIENT['allowedScopes'] = original
|
||||
|
||||
def test_one_shot_refuses_rerun_and_names_remove_then_add(self):
|
||||
self.assertTrue(m.already_provisioned())
|
||||
self.assertIn('keycape-client.py', m.RERUN_REFUSAL)
|
||||
self.assertIn('remove-then-add', m.RERUN_REFUSAL)
|
||||
source = Path(m.__file__).read_text()
|
||||
main = source.split("if __name__ == '__main__':", 1)[1]
|
||||
self.assertLess(main.index('already_provisioned()'), main.index('RECEIPT.write_text'))
|
||||
|
||||
def test_source_keycape_registration_matches_helper(self):
|
||||
import yaml
|
||||
clients = yaml.safe_load(Path('/home/worsch/key-cape/config/service-clients.example.yaml').read_text())['clients']
|
||||
named = next(c for c in clients if c['clientId'] == 'informed-decision-sitting-requester')
|
||||
self.assertEqual(named['secretRef'], 'env:' + m.ENV_NAME)
|
||||
self.assertEqual(named['allowedScopes'], ['approval:create'])
|
||||
self.assertEqual(named['allowedScopes'], ['approval:create', 'approval:read'])
|
||||
for forbidden in ('approval:consume', 'approval:approve', 'openid'):
|
||||
self.assertNotIn(forbidden, named['allowedScopes'])
|
||||
self.assertEqual(named['serviceSubject'], 'informed-decision')
|
||||
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue