Review reset authority transition and same-process role refresh
Some checks failed
Use-case regression gate / usecase-regressions (push) Failing after 6m14s
Some checks failed
Use-case regression gate / usecase-regressions (push) Failing after 6m14s
Assistant: codex Assistant-Model: gpt-6-astra Assistant-Session: 01a18e2d-f513-7742-b8e7-12af0b744ff3
This commit is contained in:
parent
776dfc20a0
commit
d5adc4b998
7 changed files with 431 additions and 0 deletions
|
|
@ -220,3 +220,112 @@ including the full non-admin reach established by the native matrix. No grant,
|
|||
reset, app authorization or productive acceptance is contained in this receipt.
|
||||
Do not rerun the completed observer command. Evidence: reset-role-observation-
|
||||
2026-10-31.json and reset-role-return-verification-2026-10-31.json.
|
||||
|
||||
|
||||
## Reviewed role-transition proposal — 2026-10-31
|
||||
|
||||
**Prepared, not admitted or executed.** Owner preparation is NK-WP-0054-T14;
|
||||
founder admission/execution is T15; declaration alignment and productive reset plus
|
||||
fresh identity restore acceptance are T16. T10 and CSOC-WP-0015-T06 remain open.
|
||||
|
||||
Proposal: `sha256:753f50f139a0d090a8b64ba15174f2a7e8b0e639eb0dd01d17783476b5a3759f`. Exact controller and plan are
|
||||
`/home/worsch/net-kingdom/sso-mfa/k8s/lldap/reset-role-transition.py` and
|
||||
`reset-role-plan.json`. The digest binds the controller, existing transport and
|
||||
observer sources, current role observation, native rights matrix, pinned runtime
|
||||
identities/configuration and final validation. No new resources or recurring
|
||||
capacity are requested; no credential rotation, service rename or restart is
|
||||
included. Both pinned workloads were Ready when observed.
|
||||
|
||||
### Decision and scope
|
||||
|
||||
The proposed change replaces **only svc-authelia's strict-readonly membership**
|
||||
with **lldap_password_manager**. This restores the native password-write capability
|
||||
needed by the accepted tegwick recovery flow. The role can change passwords for
|
||||
**all non-LLDAP-admin directory identities**, including other service accounts and
|
||||
application administrators outside lldap_admin. It is not limited to Coulomb or to
|
||||
human accounts. LLDAP administrators are excluded. Seven tested unrelated writes
|
||||
remain refused; this is not an exhaustive rights audit. Both roles already permit
|
||||
own-profile/password operations in the pinned [LLDAP access-control source](https://github.com/lldap/lldap/blob/v0.6.2/crates/auth/src/access_control.rs).
|
||||
|
||||
The same dedicated SERVICE account and single Authelia consumer remain. The plan
|
||||
contains the proposed declaration: directory read USE plus narrowly described
|
||||
password ADMINISTER scope, without the read-only bind convention. SI-3 permits a
|
||||
service-owned write account with its narrowest working scope. The canonical
|
||||
read-only declaration has not been changed to imply approval. Following a verified
|
||||
grant, T16 must align it and existing bind verification/repair expectations. Do
|
||||
not rerun the historical read-only bind migration as reset verification.
|
||||
|
||||
**Rollback limit:** membership rollback restores fresh Authelia reset denial, but
|
||||
it does not revoke previously issued HTTP tokens or undo changed user passwords.
|
||||
A native test proves a token obtained during the grant can still change an eligible
|
||||
password after membership rollback; a freshly issued token cannot. The pinned
|
||||
[LLDAP token implementation](https://github.com/lldap/lldap/blob/v0.6.2/server/src/auth_service.rs)
|
||||
embeds groups in a token with a one-day expiry. Treat retained token authority as
|
||||
lasting up to 24 hours unless separately revoked. This packet includes no token
|
||||
revocation or incident-containment operation. Admission includes this limitation.
|
||||
|
||||
### Execution and recovery
|
||||
|
||||
Run in the founder's workstation terminal only after accepting the scope above:
|
||||
|
||||
```bash
|
||||
python3 /home/worsch/net-kingdom/sso-mfa/k8s/lldap/reset-role-transition.py \
|
||||
--execute --expect-digest sha256:753f50f139a0d090a8b64ba15174f2a7e8b0e639eb0dd01d17783476b5a3759f
|
||||
```
|
||||
|
||||
The hidden prompt requires the human admin password. Never paste it into chat.
|
||||
After reviewing the printed scope, enter exactly:
|
||||
|
||||
```text
|
||||
GRANT sha256:753f50f139a0d090a8b64ba15174f2a7e8b0e639eb0dd01d17783476b5a3759f
|
||||
```
|
||||
|
||||
The controller checks the exact live cluster/namespace/deployment identities,
|
||||
images and Authelia configuration before private authentication and again after
|
||||
confirmation. It rechecks the human admin role, tegwick's non-admin classification
|
||||
and svc-authelia's exact baseline before writing. The old role is removed before
|
||||
the new one is added; a brief directory lookup failure is possible. Every effect
|
||||
has a durable intent record. All user IDs and group memberships are compared after
|
||||
each step; unknown concurrent changes fail verification. Profile fields/passwords
|
||||
are not copied into the receipt or claimed as independently compared.
|
||||
|
||||
Success is `password_manager_role_verified`. Paste only the returned JSON; Codex
|
||||
will verify the durable mode0600 receipt in
|
||||
`~/.local/state/net-kingdom/reset-authority/753f50f139a0d090a8b64ba15174f2a7e8b0e639eb0dd01d17783476b5a3759f/journal.json`.
|
||||
An existing attempt refuses replay. Do not delete its state or repeat the grant.
|
||||
If the operation is interrupted or reports uncertain status, restore read-only
|
||||
membership with this separate attended recovery:
|
||||
|
||||
```bash
|
||||
python3 /home/worsch/net-kingdom/sso-mfa/k8s/lldap/reset-role-transition.py \
|
||||
--recover --expect-digest sha256:753f50f139a0d090a8b64ba15174f2a7e8b0e639eb0dd01d17783476b5a3759f
|
||||
```
|
||||
|
||||
Confirm `RECOVER sha256:753f50f139a0d090a8b64ba15174f2a7e8b0e639eb0dd01d17783476b5a3759f`. For an already completed successful
|
||||
grant, use `--rollback` instead of `--recover` and confirm
|
||||
`ROLLBACK sha256:753f50f139a0d090a8b64ba15174f2a7e8b0e639eb0dd01d17783476b5a3759f`. Both restore only the role and retain
|
||||
previous journal versions. They preserve unrelated subsequent memberships and
|
||||
user password changes. Recovery tolerates workload unready status but still
|
||||
requires pinned identities, images and configuration; runtime drift requires
|
||||
review, not a bypass. If the directory itself is unreachable, recovery cannot
|
||||
claim completion until the owner restores access. These are role restoration
|
||||
commands with the token limitation above, not full credential revocation.
|
||||
|
||||
### Evidence and remaining acceptance
|
||||
|
||||
25 unit checks and four native controller checks pass with no skips/failures.
|
||||
The native checks include real process termination immediately after removal and
|
||||
addition, successful recovery without replay, and the retained-token boundary.
|
||||
A separate confined native rehearsal uses **one running Authelia process** through
|
||||
read-only → password-manager → read-only: reset refuses, succeeds, then refuses;
|
||||
old/new passwords and consumed challenge denial are checked each time. No restart
|
||||
is needed in this tested configuration. Synthetic fixtures and generated passwords
|
||||
are removed; no provider SMTP or productive password reset was used.
|
||||
|
||||
Evidence: `reset-transition-validation-2026-10-31.json`,
|
||||
`reset-role-refresh-2026-10-31.json` and its source manifest. A successful role
|
||||
receipt still leaves T16: source alignment, productive tegwick reset/app/MFA and
|
||||
fresh identity backup/restore evidence. It does not complete the four goal workplans.
|
||||
|
||||
The final consumer `make check` gate passes546 tests (zero failures/skips;69
|
||||
existing warnings). Receipt: reset-transition-consumer-gate-2026-10-31.json.
|
||||
|
|
|
|||
76
docs/release/reset-role-refresh-2026-10-31.json
Normal file
76
docs/release/reset-role-refresh-2026-10-31.json
Normal file
|
|
@ -0,0 +1,76 @@
|
|||
{
|
||||
"schema": "coulomb.native-reset-role-refresh.v1",
|
||||
"observed_at": "2026-10-31T13:23:16.597535+00:00",
|
||||
"data": "synthetic",
|
||||
"status": "native_role_refresh_verified",
|
||||
"images": {
|
||||
"lldap": "sha256:9e605a66c02514bfcffd1b67cafb1e98d50992216bb2871d7ae44622047dd09d",
|
||||
"authelia": "sha256:bd97cff4fcbf715b5ff1f9ae286afbe6033afce385302520b0368122d43a6f54",
|
||||
"client": "sha256:512819463a1902b43a615beee14be7bf97e6a05e016385082b9380692ad1a8cd"
|
||||
},
|
||||
"production_access": false,
|
||||
"provider_smtp_contacted": false,
|
||||
"owner_runtime_or_permission_change_performed": false,
|
||||
"no_ports_host_mounts_or_external_routes": true,
|
||||
"backend_container_flags_verified_before_fixture_import": true,
|
||||
"source_manifest_sha256": "f3c6d7b5a286d15d3049bba7e4c543052388f6a95a5e3254414f3b51da1285d0",
|
||||
"findings": {
|
||||
"scenarios": [
|
||||
{
|
||||
"bind_role": "lldap_strict_readonly",
|
||||
"same_authelia_process": true,
|
||||
"status": "reset_refused",
|
||||
"only_loopback_interface": true,
|
||||
"external_routes_absent": true,
|
||||
"old_password_initially_accepted": true,
|
||||
"new_password_initially_refused": true,
|
||||
"native_first_factor": true,
|
||||
"interactive_service_login_refused": true,
|
||||
"reset_http_status": 200,
|
||||
"reset_response_status": "KO",
|
||||
"native_identity_finish": true,
|
||||
"reset_without_verified_challenge_refused": true,
|
||||
"consumed_identity_token_replay_refused": true,
|
||||
"old_password_accepted_after": true,
|
||||
"new_password_accepted_after": false
|
||||
},
|
||||
{
|
||||
"bind_role": "lldap_password_manager",
|
||||
"same_authelia_process": true,
|
||||
"status": "reset_completed",
|
||||
"only_loopback_interface": true,
|
||||
"external_routes_absent": true,
|
||||
"old_password_initially_accepted": true,
|
||||
"new_password_initially_refused": true,
|
||||
"native_first_factor": true,
|
||||
"interactive_service_login_refused": true,
|
||||
"reset_http_status": 200,
|
||||
"reset_response_status": null,
|
||||
"native_identity_finish": true,
|
||||
"reset_without_verified_challenge_refused": true,
|
||||
"consumed_identity_token_replay_refused": true,
|
||||
"old_password_accepted_after": false,
|
||||
"new_password_accepted_after": true
|
||||
},
|
||||
{
|
||||
"bind_role": "lldap_strict_readonly",
|
||||
"same_authelia_process": true,
|
||||
"status": "reset_refused",
|
||||
"only_loopback_interface": true,
|
||||
"external_routes_absent": true,
|
||||
"old_password_initially_accepted": true,
|
||||
"new_password_initially_refused": true,
|
||||
"native_first_factor": true,
|
||||
"interactive_service_login_refused": true,
|
||||
"reset_http_status": 200,
|
||||
"reset_response_status": "KO",
|
||||
"native_identity_finish": true,
|
||||
"reset_without_verified_challenge_refused": true,
|
||||
"consumed_identity_token_replay_refused": true,
|
||||
"old_password_accepted_after": true,
|
||||
"new_password_accepted_after": false
|
||||
}
|
||||
],
|
||||
"cleanup_verified": true
|
||||
}
|
||||
}
|
||||
3
docs/release/reset-role-refresh-2026-10-31.sources.txt
Normal file
3
docs/release/reset-role-refresh-2026-10-31.sources.txt
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
scripts/rehearse-reset-role-refresh.py: c84751c3ed2893ef25460401ab760f109ac8502b308aae120990d402f85ed42f
|
||||
scripts/rehearse-native-password-reset.py: 133c8230c524fbbaf7f66e4872e14b826ed2c5a4f4de3bc47fa86cfef7631d5d
|
||||
scripts/native_recovery/probe.py: eab3bae55626abd4920eb6aa2e329c83559007c54ac74c3aaa1cf41e84eb6556
|
||||
12
docs/release/reset-transition-consumer-gate-2026-10-31.json
Normal file
12
docs/release/reset-transition-consumer-gate-2026-10-31.json
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
{
|
||||
"schema": "coulomb.reset-transition-consumer-gate.v1",
|
||||
"command": "make check",
|
||||
"passed": 546,
|
||||
"failures": 0,
|
||||
"skipped": 0,
|
||||
"warnings": 69,
|
||||
"elapsed_seconds": 55.15,
|
||||
"junit_sha256": "717412e60cbe12d52c4a14f124fbe60a59789bc52467c0b938645c2b28c24232",
|
||||
"proposal_digest": "sha256:753f50f139a0d090a8b64ba15174f2a7e8b0e639eb0dd01d17783476b5a3759f",
|
||||
"production_grant_performed": false
|
||||
}
|
||||
74
docs/release/reset-transition-validation-2026-10-31.json
Normal file
74
docs/release/reset-transition-validation-2026-10-31.json
Normal file
|
|
@ -0,0 +1,74 @@
|
|||
{
|
||||
"schema": "netkingdom.reset-transition-validation.v1",
|
||||
"status": "tested_proposal_not_granted",
|
||||
"unit_checks": 25,
|
||||
"native_controller_checks": 4,
|
||||
"failures": 0,
|
||||
"errors": 0,
|
||||
"skipped": 0,
|
||||
"junit_sha256": "26911e26b410c242f3dafb1915301875a4bfd3f5d88770be3b7bec5ce4c5c3d5",
|
||||
"ruff": "passed",
|
||||
"native_findings": [
|
||||
"grant and rollback preserve all other user IDs and group memberships",
|
||||
"real process death after removal or addition recovers read-only without replaying grant",
|
||||
"membership rollback leaves prior HTTP token rights active; freshly issued token loses those rights"
|
||||
],
|
||||
"authelia_findings": "same running process refuses reset before grant, allows after grant, refuses after rollback; three fresh consumed challenges, old/new password checks; all confined containers removed",
|
||||
"runtime_observation": {
|
||||
"schema": "netkingdom.reset-runtime.v1",
|
||||
"kube-system_uid": "a553c742-0115-43d4-99a4-a5ca56fe0786",
|
||||
"sso_uid": "a4a148d8-92ab-41e6-b975-4e67b73edfc0",
|
||||
"authelia": {
|
||||
"uid": "dc40a925-2306-4a3b-89c0-e714187503c3",
|
||||
"images": [
|
||||
"docker.io/authelia/authelia@sha256:bd97cff4fcbf715b5ff1f9ae286afbe6033afce385302520b0368122d43a6f54"
|
||||
],
|
||||
"ready": true
|
||||
},
|
||||
"lldap": {
|
||||
"uid": "743a86bd-5cb0-4ca0-9c56-a3438f4746a8",
|
||||
"images": [
|
||||
"docker.io/lldap/lldap@sha256:9e605a66c02514bfcffd1b67cafb1e98d50992216bb2871d7ae44622047dd09d"
|
||||
],
|
||||
"ready": true
|
||||
},
|
||||
"authelia_configuration": {
|
||||
"uid": "7af1fa0d-1bd4-47e1-8873-0f620788ef3f",
|
||||
"sha256": "0a8bca50641a4c6f27830f476c2c9f413fc3795711becccb8344ee4d6c376e80",
|
||||
"bind_is_svc_authelia": true
|
||||
}
|
||||
},
|
||||
"production_membership_changed": false,
|
||||
"grant_approved": false,
|
||||
"productive_acceptance": false,
|
||||
"sources": [
|
||||
{
|
||||
"path": "sso-mfa/k8s/lldap/reset-role-transition.py",
|
||||
"sha256": "cc405861bb5cc0adf419a1cf98a90872d8395eecbfc7d4d38947cb4008bee217"
|
||||
},
|
||||
{
|
||||
"path": "sso-mfa/k8s/lldap/reset-role-plan.json",
|
||||
"sha256": "703e281ee6a17e1ef0a1dffbad35b8025ab3480d51d3fc2c36323f2bc6227106"
|
||||
},
|
||||
{
|
||||
"path": "tests/test_reset_role_transition.py",
|
||||
"sha256": "1437761ac60a7aeba16db70ccf07d48a9f5332617a00e7a325e678951985a08e"
|
||||
},
|
||||
{
|
||||
"path": "sso-mfa/k8s/lldap/test_reset_role_transition_native.py",
|
||||
"sha256": "13c53e8a757ed0e954ffd28df14283d51dd9d7d3278c1d7d7d5759b419a2a89a"
|
||||
},
|
||||
{
|
||||
"path": "sso-mfa/k8s/lldap/test_bind_split.py",
|
||||
"sha256": "ae69051a3df4596fe220306d88a28b2c12e42fbfe3587e02135e3684eb5f354a"
|
||||
},
|
||||
{
|
||||
"path": "sso-mfa/k8s/lldap/bind-split-rehearsal.py",
|
||||
"sha256": "9ab873d1e941807fb19a48fb18c3af246a98bf0788660d8d79a14fb0c0e0a067"
|
||||
},
|
||||
{
|
||||
"path": "docs/evidence/2026-10-31-reset-authority/role-refresh.json",
|
||||
"sha256": "325f446960134e9510591a2a27f5833bc80fa858d77e6a88082af41307993b65"
|
||||
}
|
||||
]
|
||||
}
|
||||
147
scripts/rehearse-reset-role-refresh.py
Normal file
147
scripts/rehearse-reset-role-refresh.py
Normal file
|
|
@ -0,0 +1,147 @@
|
|||
#!/usr/bin/env python3
|
||||
"""Verify role changes with one running Authelia process; synthetic peers only."""
|
||||
|
||||
import argparse
|
||||
import hashlib
|
||||
import json
|
||||
import re
|
||||
import runpy
|
||||
import secrets
|
||||
import urllib.parse
|
||||
import uuid
|
||||
from datetime import UTC, datetime
|
||||
from pathlib import Path
|
||||
|
||||
HERE = Path(__file__).resolve().parent
|
||||
BASE = runpy.run_path(str(HERE / 'rehearse-native-password-reset.py'))
|
||||
docker, container, archive, probe, configuration = (
|
||||
BASE[n] for n in ('docker', 'container', 'archive', 'probe', 'configuration'))
|
||||
|
||||
|
||||
def set_password(anchor, token, target, password):
|
||||
assert target in {'alice', 'bob', 'operator', 'appoperator', 'svc-other', 'svc-auth'}
|
||||
return docker('exec', '-i', anchor, '/bin/sh', '-c',
|
||||
'read -r token; read -r LLDAP_USER_PASSWORD; export LLDAP_USER_PASSWORD; '
|
||||
'exec /app/lldap_set_password --base-url http://127.0.0.1:17170 '
|
||||
'--token "$token" --username ' + target,
|
||||
data=(token + '\n' + password + '\n').encode(), check=False).returncode
|
||||
|
||||
|
||||
def rehearse():
|
||||
if not __debug__:
|
||||
raise RuntimeError('acceptance_assertions_must_be_enabled')
|
||||
context = json.loads(docker('context', 'inspect', 'default').stdout)[0]
|
||||
assert context['Endpoints']['docker']['Host'] == 'unix:///var/run/docker.sock'
|
||||
for image in (BASE['LLDAP'], BASE['AUTHELIA'], BASE['CLIENT']):
|
||||
assert json.loads(docker('image', 'inspect', image).stdout)[0]['Id'] == image
|
||||
name = 'csoc-reset-transition-' + uuid.uuid4().hex[:12]
|
||||
auth = name + '-auth'
|
||||
values = {key: secrets.token_urlsafe(24) for key in (
|
||||
'admin_password', 'bind_password', 'old_password', 'new_password')}
|
||||
result = {'scenarios': []}
|
||||
try:
|
||||
container(name, BASE['LLDAP'], 'none', {
|
||||
'LLDAP_JWT_SECRET_FILE': '/run/fixture/jwt',
|
||||
'LLDAP_LDAP_USER_PASS_FILE': '/run/fixture/admin-password',
|
||||
'LLDAP_LDAP_BASE_DN': 'dc=example,dc=test',
|
||||
'LLDAP_LDAP_HOST': '127.0.0.1', 'LLDAP_HTTP_HOST': '127.0.0.1'})
|
||||
docker('exec', '-i', name, 'tar', '-xf', '-', '-C', '/run/fixture', data=archive({
|
||||
'jwt': secrets.token_hex(32).encode(),
|
||||
'admin-password': values['admin_password'].encode(),
|
||||
'lldap.toml': ('database_url = "sqlite:///data/users.db?mode=rwc"\nkey_seed = '
|
||||
+ json.dumps(secrets.token_hex(32)) + '\n').encode()}))
|
||||
docker('exec', '-d', name, '/bin/sh', '-c',
|
||||
'exec /app/lldap run --config-file /run/fixture/lldap.toml '
|
||||
'> /run/fixture/server.log 2>&1')
|
||||
prepared = probe(name, {**values, 'mode': 'bootstrap', 'authority_matrix': True})
|
||||
assert set_password(name, prepared['admin_token'], 'svc-auth',
|
||||
values['bind_password']) == 0
|
||||
container(auth, BASE['AUTHELIA'], 'container:' + name, {})
|
||||
docker('exec', '-i', auth, 'tar', '-xf', '-', '-C', '/run/fixture', data=archive({
|
||||
'configuration.yml': json.dumps(configuration(values['bind_password'])).encode()}))
|
||||
# One Authelia process for all three phases, including rollback.
|
||||
probe(name, {**values, 'mode': 'bind_role', 'role': 'lldap_strict_readonly'})
|
||||
docker('exec', '-d', auth, '/bin/sh', '-c',
|
||||
'exec /app/authelia --config /run/fixture/configuration.yml '
|
||||
'> /run/fixture/server.log 2>&1')
|
||||
process_marker = None
|
||||
tokens = set()
|
||||
for index, role in enumerate(['lldap_strict_readonly', 'lldap_password_manager',
|
||||
'lldap_strict_readonly']):
|
||||
if index:
|
||||
probe(name, {**values, 'mode': 'bind_role', 'role': role})
|
||||
assert set_password(name, prepared['admin_token'], 'alice',
|
||||
values['old_password']) == 0
|
||||
docker('exec', auth, 'rm', '-f', '/data/notifications.txt')
|
||||
requested = probe(name, {**values, 'mode': 'reset_request', 'target': 'alice'})
|
||||
marker = docker('exec', auth, '/bin/sh', '-c',
|
||||
'for p in /proc/[0-9]*; do '
|
||||
'if [ "$(cat "$p/comm" 2>/dev/null)" = authelia ]; '
|
||||
'then cat "$p/stat"; fi; done').stdout
|
||||
# /proc stat changes CPU counters; compare PID and start ticks only.
|
||||
fields = marker.decode().split()
|
||||
current_marker = (fields[0], fields[21])
|
||||
if process_marker is None:
|
||||
process_marker = current_marker
|
||||
assert current_marker == process_marker, 'authelia_process_restarted'
|
||||
notification = docker('exec', auth, 'cat', '/data/notifications.txt').stdout.decode()
|
||||
links = re.findall(r'https://auth\.recovery\.test/reset-password/step2\?[^\s<>]+',
|
||||
notification)
|
||||
assert len(links) == 1, ('one_new_native_challenge_required_phase_'
|
||||
+ str(index) + '_count_' + str(len(links)))
|
||||
token = urllib.parse.parse_qs(urllib.parse.urlsplit(links[-1]).query)['token'][0]
|
||||
assert token not in tokens, 'fresh_challenge_required'
|
||||
tokens.add(token)
|
||||
settled = probe(name, {**values, 'mode': 'reset_finish', 'target': 'alice',
|
||||
'reset_token': token})
|
||||
expected = role == 'lldap_password_manager'
|
||||
assert settled['new_password_accepted_after'] is expected
|
||||
assert settled['old_password_accepted_after'] is not expected
|
||||
assert (settled['status'] == 'reset_completed') is expected
|
||||
result['scenarios'].append({'bind_role': role, 'same_authelia_process': True,
|
||||
**requested, **settled})
|
||||
finally:
|
||||
for current in [name + '-probe', auth, name]:
|
||||
docker('rm', '-f', '-v', current, check=False)
|
||||
result['cleanup_verified'] = all(not docker(
|
||||
'ps', '-aq', '--filter', 'name=^/' + n + '$').stdout.strip()
|
||||
for n in [name + '-probe', auth, name])
|
||||
if not result['cleanup_verified']:
|
||||
raise RuntimeError('fixture_cleanup_requires_review')
|
||||
assert not any(value in json.dumps(result) for value in values.values())
|
||||
return result
|
||||
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser(description=__doc__)
|
||||
parser.add_argument('--output', type=Path, required=True)
|
||||
args = parser.parse_args()
|
||||
manifest_path = args.output.with_suffix('.sources.txt')
|
||||
if args.output.exists() or manifest_path.exists():
|
||||
raise SystemExit('Fresh receipt and source manifest required')
|
||||
findings = rehearse()
|
||||
paths = [Path(__file__), HERE / 'rehearse-native-password-reset.py',
|
||||
HERE / 'native_recovery/probe.py']
|
||||
manifest = ''.join(str(p.relative_to(HERE.parent)) + ': '
|
||||
+ hashlib.sha256(p.read_bytes()).hexdigest() + '\n' for p in paths).encode()
|
||||
receipt = {'schema': 'coulomb.native-reset-role-refresh.v1',
|
||||
'observed_at': datetime.now(UTC).isoformat(), 'data': 'synthetic',
|
||||
'status': 'native_role_refresh_verified',
|
||||
'images': {n: BASE[n.upper()] for n in ('lldap', 'authelia', 'client')},
|
||||
'production_access': False, 'provider_smtp_contacted': False,
|
||||
'owner_runtime_or_permission_change_performed': False,
|
||||
'no_ports_host_mounts_or_external_routes': True,
|
||||
'backend_container_flags_verified_before_fixture_import': True,
|
||||
'source_manifest_sha256': hashlib.sha256(manifest).hexdigest(),
|
||||
'findings': findings}
|
||||
with manifest_path.open('xb') as stream:
|
||||
stream.write(manifest)
|
||||
with args.output.open('x') as stream:
|
||||
json.dump(receipt, stream, indent=2)
|
||||
stream.write('\n')
|
||||
print(json.dumps({'status': receipt['status'], 'roles': len(findings['scenarios']),
|
||||
'cleanup_verified': findings['cleanup_verified']}))
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
|
|
@ -300,3 +300,13 @@ reset-role-observation-2026-10-31.json and reset-role-return-verification-2026-1
|
|||
## Completion
|
||||
|
||||
Finish when every task above has its stated evidence and acceptance, or hand off any newly discovered residual to a live work record first. Cross-owner dependencies remain their existing canonical records.
|
||||
|
||||
|
||||
2026-10-31 reset transition preparation: owner T14 prepares the source-bound,
|
||||
crash-recoverable role controller; T15 explicitly holds founder admission and
|
||||
execution; T16 holds source alignment, productive reset/app/MFA and identity
|
||||
backup/restore acceptance. The same running Authelia process picks up grant and
|
||||
rollback in the confined native fixture. Membership rollback does not revoke
|
||||
HTTP tokens issued during a grant; the review includes their up-to-24-hour scope.
|
||||
No production role/password changed. T06 remains progress and consumes owner T10,
|
||||
whose acceptance is preserved rather than closed by fixture proof.
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue