Refine bootstrap actions and runbook templates

This commit is contained in:
2026-05-25 23:10:02 +02:00
parent 20fd300e88
commit cd043ca471
2 changed files with 152 additions and 271 deletions

View File

@@ -1216,20 +1216,9 @@ def command_payloads(data: dict[str, Any]) -> list[dict[str, str]]:
initial_config_applied = yes(data, "openbao_initial_config_applied")
trial_exposed = yes(data, "openbao_trial_material_exposed")
response_complete = yes(data, "openbao_compromise_response_complete")
keys_rotated = yes(data, "openbao_unseal_keys_rotated")
root_disposed = data.get("root_token_disposition") in {"revoked", "offline-sealed"}
openbao_direct_taint = openbao_trial_taint(data, "direct")
openbao_downstream_taint = openbao_trial_taint(data, "downstream")
status_state = "todo"
status_reason = "Run any time to inspect the current OpenBao deployment state."
if preflight_done:
status_state = "done"
status_reason = "Deployment and pre-init status were verified."
if (init_output or initialized) and not root_disposed:
status_state = "redo"
status_reason = "OpenBao changed during init/unseal; rerun status before root-token disposition."
preflight_state = "done" if preflight_done else "todo"
preflight_reason = "Safe preflight passed."
if not preflight_done:
@@ -1246,14 +1235,6 @@ def command_payloads(data: dict[str, Any]) -> list[dict[str, str]]:
init_state = "blocked"
init_reason = "OpenBao preflight must pass first."
unseal_state = "done" if initialized else "todo"
unseal_reason = "OpenBao is recorded as initialized and unsealed."
if not initialized:
unseal_reason = "Provide threshold shares by prompt, not as command arguments."
if not (init_output or initialized):
unseal_state = "blocked"
unseal_reason = "OpenBao init output must be produced first."
config_state = "done" if initial_config_applied else "todo"
config_reason = "Initial configuration is recorded. Root-token disposition remains a separate gate."
if not initial_config_applied:
@@ -1276,13 +1257,6 @@ def command_payloads(data: dict[str, Any]) -> list[dict[str, str]]:
verify_reason = "OpenBao must be initialized and unsealed first."
return [
{
"name": "OpenBao status",
"description": "Show pod, service, PVC, and seal/init status.",
"status": status_state,
"status_reason": status_reason,
"command": "make -C ../railiance-platform openbao-status",
},
{
"name": "OpenBao preflight",
"description": "Run safe status and verification checks. Does not initialize OpenBao.",
@@ -1303,16 +1277,6 @@ def command_payloads(data: dict[str, Any]) -> list[dict[str, str]]:
},
openbao_direct_taint if init_output or initialized else {},
),
add_taint(
{
"name": "OpenBao unseal prompt",
"description": "Enter unseal shares by interactive terminal prompt. Do not place shares on the command line.",
"status": unseal_state,
"status_reason": unseal_reason,
"command": "kubectl exec -it -n openbao openbao-0 -- bao operator unseal",
},
openbao_direct_taint if initialized else {},
),
add_taint(
{
"name": "OpenBao initial configuration",
@@ -1337,43 +1301,30 @@ def command_payloads(data: dict[str, Any]) -> list[dict[str, str]]:
def runbook_payloads(data: dict[str, Any]) -> list[dict[str, str]]:
init_output = yes(data, "openbao_init_output_produced")
initialized = yes(data, "openbao_initialized")
initial_config_applied = yes(data, "openbao_initial_config_applied")
restore_done = yes(data, "restore_drill_passed")
trial_exposed = yes(data, "openbao_trial_material_exposed")
response_complete = yes(data, "openbao_compromise_response_complete")
keys_rotated = yes(data, "openbao_unseal_keys_rotated")
lockdown_drilled = yes(data, "openbao_emergency_lockdown_drilled")
openbao_direct_taint = openbao_trial_taint(data, "direct")
openbao_downstream_taint = openbao_trial_taint(data, "downstream")
key_compromise_status = "done" if response_complete else "todo"
key_compromise_location = "Use for trial output exposure, screenshots, chat paste, shell history, or lost custody."
if not trial_exposed:
key_compromise_status = "blocked" if not init_output else "todo"
key_compromise_location = "Mark trial key material exposed before running the response checklist."
key_compromise_location = "Template: record the exposure, choose reset versus rotation, inspect affected paths, and record only the non-secret outcome."
if trial_exposed and not response_complete:
key_compromise_location = "Active template: trial material is marked exposed; choose reset versus rotation before production trust."
elif response_complete:
key_compromise_location = "Template retained for future incidents; the current non-secret compromise response is marked complete."
rotate_status = "done" if keys_rotated else "todo"
rotate_location = "Run only after OpenBao is unsealed and existing exposed shares are available for quorum."
rotate_location = "Template: unseal OpenBao, start rotate-keys, submit current shares by prompt, route new shares to custody holders, then record confirmation."
if not initialized:
rotate_status = "blocked"
rotate_location = "Unseal OpenBao first; rotate-keys needs a quorum of current unseal shares."
if not trial_exposed and not keys_rotated:
rotate_status = "blocked"
rotate_location = "Record the key-compromise condition or schedule a normal rotation first."
rotate_location = "Template: prepare now; execution needs an unsealed OpenBao instance and a quorum of current shares."
lockdown_status = "done" if lockdown_drilled else "todo"
lockdown_location = "Requires an unsealed OpenBao instance and a token with root or sudo on sys/seal."
lockdown_location = "Template: use a root/sudo-capable token, run emergency seal, confirm Sealed true, then record the drill or incident outcome."
if not initialized:
lockdown_status = "blocked"
lockdown_location = "OpenBao is not recorded as unsealed; sealing is only meaningful while it is serving requests."
lockdown_location = "Template: prepare now; execution only changes availability while OpenBao is unsealed and serving requests."
restore_status = "done" if restore_done else "todo"
restore_location = "Create, encrypt, and isolated-restore a Railiance OpenBao Raft snapshot before live secrets move in."
restore_location = "Template: prepare workspace, snapshot, encrypt to age recipient, restore in isolation, verify, destroy drill environment, then record evidence."
if not initial_config_applied:
restore_status = "blocked"
restore_location = "Apply OpenBao initial configuration before proving backup and restore."
restore_location = "Template: prepare now; execute after initial OpenBao configuration exists and before live secrets move in."
return [
add_taint(
@@ -1384,7 +1335,7 @@ def runbook_payloads(data: dict[str, Any]) -> list[dict[str, str]]:
"responsibility": "openbao-ceremony-operator",
"email": role_email(data, "role_openbao_operator_email"),
"location": key_compromise_location,
"state": key_compromise_status,
"state": "template",
},
openbao_direct_taint if trial_exposed and not response_complete else {},
),
@@ -1396,7 +1347,7 @@ def runbook_payloads(data: dict[str, Any]) -> list[dict[str, str]]:
"responsibility": "openbao-ceremony-operator",
"email": role_email(data, "role_openbao_operator_email"),
"location": rotate_location,
"state": rotate_status,
"state": "template",
},
openbao_downstream_taint if trial_exposed and not response_complete else {},
),
@@ -1408,7 +1359,7 @@ def runbook_payloads(data: dict[str, Any]) -> list[dict[str, str]]:
"responsibility": "openbao-ceremony-operator",
"email": role_email(data, "role_openbao_operator_email"),
"location": lockdown_location,
"state": lockdown_status,
"state": "template",
},
openbao_downstream_taint if initialized else {},
),
@@ -1420,7 +1371,7 @@ def runbook_payloads(data: dict[str, Any]) -> list[dict[str, str]]:
"responsibility": "openbao-ceremony-operator",
"email": role_email(data, "role_openbao_operator_email"),
"location": restore_location,
"state": restore_status,
"state": "template",
},
openbao_downstream_taint if initialized else {},
),
@@ -1428,61 +1379,40 @@ def runbook_payloads(data: dict[str, Any]) -> list[dict[str, str]]:
def runbook_command_payloads(data: dict[str, Any]) -> list[dict[str, str]]:
init_output = yes(data, "openbao_init_output_produced")
initialized = yes(data, "openbao_initialized")
initial_config_applied = yes(data, "openbao_initial_config_applied")
restore_done = yes(data, "restore_drill_passed")
trial_exposed = yes(data, "openbao_trial_material_exposed")
response_complete = yes(data, "openbao_compromise_response_complete")
keys_rotated = yes(data, "openbao_unseal_keys_rotated")
lockdown_drilled = yes(data, "openbao_emergency_lockdown_drilled")
openbao_direct_taint = openbao_trial_taint(data, "direct")
openbao_downstream_taint = openbao_trial_taint(data, "downstream")
exposure_status = "done" if trial_exposed else "todo"
exposure_reason = "Trial key-material exposure is recorded in non-secret metadata." if trial_exposed else "Record that the trial init output escaped custody before using affected material."
def token_prompt_command(bao_command: str) -> str:
return (
"printf 'OpenBao token: ' >&2\n"
"read -rs OPENBAO_TOKEN\n"
"printf '\\n' >&2\n"
"printf '%s\\n' \"$OPENBAO_TOKEN\" | kubectl exec -i -n openbao openbao-0 -- "
f"sh -c 'read -r BAO_TOKEN; export BAO_TOKEN; {bao_command}'\n"
"unset OPENBAO_TOKEN"
)
response_status = "done" if response_complete else "todo"
response_reason = "Compromise response was recorded." if response_complete else "Stop production use of exposed material, decide rotate-vs-reset, and record non-secret evidence."
if not trial_exposed:
response_status = "blocked"
response_reason = "Record the key-material exposure first."
def action(name: str, description: str, command: str, taint: dict[str, str] | None = None) -> dict[str, str]:
return add_taint(
{
"name": name,
"description": description,
"command": command,
},
taint or {},
)
unseal_status = "done" if initialized else "todo"
unseal_reason = "OpenBao is unsealed." if initialized else "Unseal by hidden prompt before rotating unseal keys."
if not init_output:
unseal_status = "blocked"
unseal_reason = "OpenBao init output must exist first."
rotate_status = "done" if keys_rotated else "todo"
rotate_reason = "New unseal keys are recorded as generated." if keys_rotated else "Start rotation, then submit current shares by prompt until quorum completes."
if not initialized:
rotate_status = "blocked"
rotate_reason = "OpenBao must be unsealed before rotate-keys can run."
if not trial_exposed and not keys_rotated:
rotate_status = "blocked"
rotate_reason = "Record exposure or schedule a normal rotation before generating new shares."
lockdown_status = "done" if lockdown_drilled else "todo"
lockdown_reason = "Emergency lock-down drill is recorded." if lockdown_drilled else "Seals OpenBao immediately. Use only during an emergency or scheduled drill."
if not initialized:
lockdown_status = "blocked"
lockdown_reason = "OpenBao must be unsealed before emergency seal changes availability."
seal_command = (
"printf 'OpenBao token: ' >&2\n"
"read -rs OPENBAO_TOKEN\n"
"printf '\\n' >&2\n"
"printf '%s\\n' \"$OPENBAO_TOKEN\" | kubectl exec -i -n openbao openbao-0 -- "
"sh -c 'read -r BAO_TOKEN; export BAO_TOKEN; bao operator seal'\n"
"unset OPENBAO_TOKEN"
)
restore_status = "done" if restore_done else "todo"
restore_reason = "Restore drill is recorded." if restore_done else "Create encrypted snapshot evidence and complete an isolated restore proof."
if not initial_config_applied:
restore_status = "blocked"
restore_reason = "OpenBao initial configuration must be applied before the restore drill."
restore_taint = openbao_downstream_taint if initialized else {}
seal_command = token_prompt_command("bao operator seal")
audit_list_command = token_prompt_command("bao audit list")
secrets_list_command = token_prompt_command("bao secrets list")
auth_list_command = token_prompt_command("bao auth list")
openbao_status_command = "kubectl exec -n openbao openbao-0 -- bao status"
direct_taint = openbao_direct_taint if initialized else {}
downstream_taint = openbao_downstream_taint if initialized else {}
compromise_taint = openbao_downstream_taint if trial_exposed and not response_complete else {}
public_key = extract_age_public_key(data.get("custodian_age_public_key"))
quoted_public_key = shlex.quote(public_key if public_key else "<age-recipient>")
snapshot_workspace_command = (
@@ -1505,8 +1435,6 @@ def runbook_command_payloads(data: dict[str, Any]) -> list[dict[str, str]]:
'kubectl exec -n openbao openbao-0 -- rm -f /tmp/openbao-raft.snap\n'
'sha256sum "$RESTORE_DRILL_DIR/openbao-raft.snap" | tee "$RESTORE_DRILL_DIR/openbao-raft.snap.sha256"'
)
encrypt_snapshot_status = restore_status if public_key else "blocked"
encrypt_snapshot_reason = restore_reason if public_key else "Record the custodian public age recipient before encrypting snapshot custody material."
encrypt_snapshot_command = (
'export RESTORE_DRILL_DIR="${RESTORE_DRILL_DIR:-/tmp/netkingdom-openbao-restore-drill}"\n'
f'age -r {quoted_public_key} -o "$RESTORE_DRILL_DIR/openbao-raft.snap.age" "$RESTORE_DRILL_DIR/openbao-raft.snap"\n'
@@ -1531,155 +1459,95 @@ def runbook_command_payloads(data: dict[str, Any]) -> list[dict[str, str]]:
)
return [
add_taint(
{
"name": "Record key exposure",
"description": "Non-secret metadata checkbox in this UI; do not paste exposed values.",
"status": exposure_status,
"status_reason": exposure_reason,
"command": "Use the checkbox: Trial key material exposed",
},
openbao_direct_taint if trial_exposed and not response_complete else {},
action(
"OpenBao status",
"Show seal, initialization, storage, and HA state for the OpenBao pod. This command does not require a token.",
openbao_status_command,
downstream_taint,
),
add_taint(
{
"name": "Unseal by prompt",
"description": "Provide threshold shares interactively. Never put shares on the command line.",
"status": unseal_status,
"status_reason": unseal_reason,
"command": "kubectl exec -it -n openbao openbao-0 -- bao operator unseal",
},
openbao_direct_taint if initialized else {},
action(
"Unseal by prompt",
"Provide threshold shares interactively. Never put shares on the command line.",
"kubectl exec -it -n openbao openbao-0 -- bao operator unseal",
direct_taint,
),
add_taint(
{
"name": "Start unseal-key rotation",
"description": "Generate a new 3-share, threshold-2 Shamir split after compromise or planned migration.",
"status": rotate_status,
"status_reason": rotate_reason,
"command": "kubectl exec -it -n openbao openbao-0 -- bao operator rotate-keys -init -key-shares=3 -key-threshold=2",
},
openbao_downstream_taint if trial_exposed and not response_complete else {},
action(
"bao audit list",
"List OpenBao audit devices using a token entered by local hidden prompt.",
audit_list_command,
downstream_taint,
),
add_taint(
{
"name": "Submit current shares for rotation",
"description": "Repeat by prompt until the required threshold completes. Use the nonce from rotation init.",
"status": rotate_status,
"status_reason": rotate_reason,
"command": "kubectl exec -it -n openbao openbao-0 -- bao operator rotate-keys -nonce=<nonce-from-rotation-init>",
},
openbao_downstream_taint if trial_exposed and not response_complete else {},
action(
"bao secrets list",
"List enabled OpenBao secrets engines using a token entered by local hidden prompt.",
secrets_list_command,
downstream_taint,
),
add_taint(
{
"name": "Cancel key rotation",
"description": "Abort a started rotation if the nonce, share handling, or ceremony context is wrong.",
"status": "todo" if initialized and not keys_rotated else "blocked",
"status_reason": "Available while a rotation is in progress." if initialized and not keys_rotated else "No active rotation expected.",
"command": "kubectl exec -it -n openbao openbao-0 -- bao operator rotate-keys -cancel",
},
openbao_downstream_taint if trial_exposed and not response_complete else {},
action(
"bao auth list",
"List enabled OpenBao auth methods using a token entered by local hidden prompt.",
auth_list_command,
downstream_taint,
),
add_taint(
{
"name": "Record compromise response complete",
"description": "Non-secret metadata checkbox after exposed material is rotated or the trial environment was reset.",
"status": response_status,
"status_reason": response_reason,
"command": "Use the checkbox: Compromise response complete",
},
openbao_downstream_taint if trial_exposed and not response_complete else {},
action(
"Start unseal-key rotation",
"Generate a new 3-share, threshold-2 Shamir split after compromise or planned migration.",
"kubectl exec -it -n openbao openbao-0 -- bao operator rotate-keys -init -key-shares=3 -key-threshold=2",
compromise_taint,
),
add_taint(
{
"name": "Emergency seal OpenBao",
"description": "Prompt locally for an OpenBao token and seal Railiance OpenBao without placing the token on the command line.",
"status": lockdown_status,
"status_reason": lockdown_reason,
"command": seal_command,
},
openbao_downstream_taint if initialized else {},
action(
"Submit current shares for rotation",
"Repeat by prompt until the required threshold completes. Use the nonce from rotation init.",
"kubectl exec -it -n openbao openbao-0 -- bao operator rotate-keys -nonce=<nonce-from-rotation-init>",
compromise_taint,
),
add_taint(
{
"name": "Confirm sealed status",
"description": "Check that Railiance OpenBao reports Sealed true after an emergency seal.",
"status": "todo" if initialized else "blocked",
"status_reason": "Run after emergency seal; expect Sealed true." if initialized else "OpenBao must be initialized before status confirms lock-down.",
"command": "kubectl exec -n openbao openbao-0 -- bao status",
},
openbao_downstream_taint if initialized else {},
action(
"Cancel key rotation",
"Abort a started rotation if the nonce, share handling, or ceremony context is wrong.",
"kubectl exec -it -n openbao openbao-0 -- bao operator rotate-keys -cancel",
compromise_taint,
),
add_taint(
{
"name": "Record emergency lock-down drill",
"description": "Non-secret metadata checkbox after an emergency seal drill or real lock-down is confirmed.",
"status": lockdown_status,
"status_reason": lockdown_reason,
"command": "Use the checkbox: Emergency lock-down drill recorded",
},
openbao_downstream_taint if initialized else {},
action(
"Emergency seal OpenBao",
"Prompt locally for an OpenBao token and seal Railiance OpenBao without placing the token on the command line.",
seal_command,
downstream_taint,
),
add_taint(
{
"name": "Prepare restore drill workspace",
"description": "Create a local restricted directory for temporary snapshot evidence.",
"status": restore_status,
"status_reason": restore_reason,
"command": snapshot_workspace_command,
},
restore_taint,
action(
"Confirm sealed status",
"Check that Railiance OpenBao reports Sealed true after an emergency seal.",
openbao_status_command,
downstream_taint,
),
add_taint(
{
"name": "Create encrypted-restore snapshot source",
"description": "Prompt locally for an OpenBao token, create a Raft snapshot in the pod, copy it out, remove the pod copy, and record a plaintext hash before encryption.",
"status": restore_status,
"status_reason": restore_reason,
"command": snapshot_command,
},
restore_taint,
action(
"Prepare restore drill workspace",
"Create a local restricted directory for temporary snapshot evidence.",
snapshot_workspace_command,
downstream_taint,
),
add_taint(
{
"name": "Encrypt restore snapshot",
"description": "Encrypt the Raft snapshot to the custodian age recipient and remove the local plaintext snapshot.",
"status": encrypt_snapshot_status,
"status_reason": encrypt_snapshot_reason,
"command": encrypt_snapshot_command,
},
restore_taint,
action(
"Create encrypted-restore snapshot source",
"Prompt locally for an OpenBao token, create a Raft snapshot in the pod, copy it out, remove the pod copy, and record a plaintext hash before encryption.",
snapshot_command,
downstream_taint,
),
add_taint(
{
"name": "Run isolated restore proof",
"description": "Checklist for proving the snapshot can restore into an isolated OpenBao instance before live secrets move in.",
"status": restore_status,
"status_reason": restore_reason,
"command": isolated_restore_command,
},
restore_taint,
action(
"Encrypt restore snapshot",
"Encrypt the Raft snapshot to the custodian age recipient and remove the local plaintext snapshot. Replace <age-recipient> if no recipient is recorded.",
encrypt_snapshot_command,
downstream_taint,
),
add_taint(
{
"name": "Run post-restore readiness check",
"description": "Re-run the Railiance post-unseal checks after restore evidence has been captured.",
"status": restore_status,
"status_reason": restore_reason,
"command": "make -C ../railiance-platform openbao-verify-post-unseal",
},
restore_taint,
action(
"Run isolated restore proof",
"Checklist for proving the snapshot can restore into an isolated OpenBao instance before live secrets move in.",
isolated_restore_command,
downstream_taint,
),
add_taint(
{
"name": "Record restore drill passed",
"description": "Non-secret metadata checkbox after encrypted snapshot evidence and isolated restore proof are complete.",
"status": restore_status,
"status_reason": restore_reason,
"command": "Use the checkbox: Restore drill passed",
},
restore_taint,
action(
"Run post-restore readiness check",
"Re-run the Railiance post-unseal checks after restore evidence has been captured.",
"make -C ../railiance-platform openbao-verify-post-unseal",
downstream_taint,
),
]
@@ -1689,7 +1557,6 @@ def section_gate_payloads(data: dict[str, Any]) -> list[dict[str, str]]:
role_ok = all(row["state"] != "nil" for row in role_rows[:5])
subsystem_rows = subsystem_payloads(data)
integration_rows = integration_payloads(data)
runbook_rows = runbook_payloads(data)
artifact_rows = artifact_payloads(data)
return [
{
@@ -1725,8 +1592,8 @@ def section_gate_payloads(data: dict[str, Any]) -> list[dict[str, str]]:
{
"key": "runbooks",
"name": "Usecases & Runbooks",
"status": "ok" if all(row["state"] in {"done", "blocked"} for row in runbook_rows) else "set",
"reason": "Runbook states are recorded." if all(row["state"] in {"done", "blocked"} for row in runbook_rows) else "Review active runbooks and record non-secret outcomes.",
"status": "ok",
"reason": "Reusable actions and runbook templates are available; execution state is tracked by Integration & Tests and explicit confirmations.",
},
{
"key": "terminology",
@@ -2268,6 +2135,7 @@ def ui_html() -> str:
.state.todo { background: var(--warn); }
.state.err { background: var(--bad); }
.state.blocked { background: var(--bad); }
.state.template { background: #f1eee5; }
.role-chip {
display: inline-flex;
max-width: 100%;
@@ -2553,7 +2421,7 @@ def ui_html() -> str:
<details class="panel workflow-section" data-section="integrations" open>
<summary><span class="summary-title">4. Integration & Tests</span><span class="state nil" data-section-state="integrations">nil</span></summary>
<div class="section-gate" data-section-gate="integrations">Loading integration gate.</div>
<p class="notice">This section connects the subsystems and shows every console command as a copyable block. Commands still run outside this browser so secret output never enters the control surface.</p>
<p class="notice">This section tracks stateful integration runbooks and gates. Status belongs to these tasks; reusable command-only actions live below in Usecases & Runbooks.</p>
<div id="integrations-records" class="record-list"></div>
<div class="inline-actions">
<a class="button-link" href="/oidc/start" target="_blank" rel="noreferrer" title="Start the bootstrap-console OIDC authorization flow through KeyCape.">Start OIDC login check</a>
@@ -2580,20 +2448,6 @@ def ui_html() -> str:
<div id="command-list" class="command-list"></div>
</details>
<details class="panel workflow-section" data-section="runbooks" open>
<summary><span class="summary-title">6. Usecases & Runbooks</span><span class="state nil" data-section-state="runbooks">nil</span></summary>
<div class="section-gate" data-section-gate="runbooks">Loading runbook gate.</div>
<p class="notice">Use these routines when the ceremony path changes, trial secrets are exposed, or custody material must be regenerated. The UI records only non-secret outcomes.</p>
<div id="runbooks-records" class="record-list"></div>
<div class="choice-list">
<label class="choice"><input id="openbao_trial_material_exposed" type="checkbox"><span><strong>Trial key material exposed</strong><span>Init output, unseal shares, or root-token material escaped the custody boundary during a trial.</span></span></label>
<label class="choice"><input id="openbao_compromise_response_complete" type="checkbox"><span><strong>Compromise response complete</strong><span>Exposed material was rotated or the trial environment was reset. No secret values are recorded here.</span></span></label>
<label class="choice"><input id="openbao_unseal_keys_rotated" type="checkbox"><span><strong>New unseal keys generated</strong><span>OpenBao generated replacement unseal shares under the current runbook.</span></span></label>
<label class="choice"><input id="openbao_emergency_lockdown_drilled" type="checkbox"><span><strong>Emergency lock-down drill recorded</strong><span>Railiance OpenBao was sealed and status-confirmed during a drill or real lock-down. No token or share is recorded here.</span></span></label>
</div>
<div id="runbook-command-list" class="command-list"></div>
</details>
<details class="panel workflow-section" data-section="artifacts" open>
<summary><span class="summary-title">5. Artefacts & Locations</span><span class="state nil" data-section-state="artifacts">nil</span></summary>
<div class="section-gate" data-section-gate="artifacts">Loading artefact gate.</div>
@@ -2622,6 +2476,20 @@ def ui_html() -> str:
</div>
</details>
<details class="panel workflow-section" data-section="runbooks" open>
<summary><span class="summary-title">6. Usecases & Runbooks</span><span class="state nil" data-section-state="runbooks">nil</span></summary>
<div class="section-gate" data-section-gate="runbooks">Loading runbook gate.</div>
<p class="notice">This section contains reusable actions and runbook templates. Action cards are copyable command references without task status; runbook task state belongs to Integration & Tests or to the explicit confirmation gates.</p>
<div id="runbooks-records" class="record-list"></div>
<div class="choice-list">
<label class="choice"><input id="openbao_trial_material_exposed" type="checkbox"><span><strong>Trial key material exposed</strong><span>Init output, unseal shares, or root-token material escaped the custody boundary during a trial.</span></span></label>
<label class="choice"><input id="openbao_compromise_response_complete" type="checkbox"><span><strong>Compromise response complete</strong><span>Exposed material was rotated or the trial environment was reset. No secret values are recorded here.</span></span></label>
<label class="choice"><input id="openbao_unseal_keys_rotated" type="checkbox"><span><strong>New unseal keys generated</strong><span>OpenBao generated replacement unseal shares under the current runbook.</span></span></label>
<label class="choice"><input id="openbao_emergency_lockdown_drilled" type="checkbox"><span><strong>Emergency lock-down drill recorded</strong><span>Railiance OpenBao was sealed and status-confirmed during a drill or real lock-down. No token or share is recorded here.</span></span></label>
</div>
<div id="runbook-command-list" class="command-list"></div>
</details>
<details class="panel workflow-section" data-section="terminology" open>
<summary><span class="summary-title">7. Terminology & Patterns</span><span class="state nil" data-section-state="terminology">nil</span></summary>
<div class="section-gate" data-section-gate="terminology">Loading terminology gate.</div>
@@ -2842,10 +2710,13 @@ def ui_html() -> str:
const description = document.createElement("div");
description.className = "record-description";
description.textContent = item.description;
const statusReason = document.createElement("div");
statusReason.className = "record-description";
statusReason.textContent = item.status_reason || "";
title.append(name, description, statusReason);
title.append(name, description);
if (item.status_reason) {
const statusReason = document.createElement("div");
statusReason.className = "record-description";
statusReason.textContent = item.status_reason;
title.append(statusReason);
}
const taintNote = makeTaintNote(item);
if (taintNote) title.append(taintNote);
@@ -2857,7 +2728,8 @@ def ui_html() -> str:
button.dataset.command = item.command;
const commandActions = document.createElement("div");
commandActions.className = "inline-actions";
commandActions.append(makeStateBadge(item.status), button);
if (item.status) commandActions.append(makeStateBadge(item.status));
commandActions.append(button);
head.append(title, commandActions);
const code = document.createElement("code");