diff --git a/CODEX_CONTEXT.md b/CODEX_CONTEXT.md index e98824c..12e2556 100644 --- a/CODEX_CONTEXT.md +++ b/CODEX_CONTEXT.md @@ -2745,6 +2745,2650 @@ Current implementation focus remains: `test-1/2/3`. Verification passed: `go test ./internal/mesh`, C19Z1 live smoke, C19X source smoke, and C19Z regression smoke. Artifact: `artifacts/c19z1-remote-workspace-mailbox-preflight-smoke-result.json`. +- C19Z2 Remote Workspace mailbox preflight telemetry is implemented and + runtime-smoke-proven on docker-test. Workload status and heartbeat telemetry + now expose `mailbox_preflight_total`, ack/checkpoint preflight counters, and + last preflight session/consumer/cursor/window fields; readiness diagnostics + also carry the latest preflight summary. Node-agent image + `rap-node-agent:codex-service-supervisor-20260512z3` is built and deployed on + `test-1/2/3`. Verification passed: `go test ./internal/mesh`, C19Z2 live + smoke, C19Z1 source smoke, and C19Z1 regression smoke. Artifact: + `artifacts/c19z2-remote-workspace-mailbox-preflight-telemetry-smoke-result.json`. +- C19Z3 Remote Workspace mailbox stale-cursor preflight diagnostics are + implemented and runtime-smoke-proven on docker-test. Preflight responses now + report retained mailbox sequence bounds, `diagnostic_state`, + `stale_cursor`, and `missing_dropped_count` when a consumer cursor points + behind dropped bounded-mailbox events. Workload/heartbeat preflight telemetry + and readiness diagnostics mirror the stale-cursor state. Node-agent image + `rap-node-agent:codex-service-supervisor-20260512z4` is built and deployed on + `test-1/2/3`. Verification passed: `go test ./internal/mesh`, C19Z3 live + smoke, and C19Z2 regression smoke. Artifact: + `artifacts/c19z3-remote-workspace-mailbox-stale-preflight-smoke-result.json`. +- C19Z4 Remote Workspace mailbox preflight action hints are implemented and + runtime-smoke-proven on docker-test. Preflight responses now include + `recommended_action` and `action_hints`; stale cursor gaps recommend + `reset_consumer_and_resync` with hints to reset the consumer cursor, request a + full adapter resync, and resume from checkpoint after resync. The latest + action hints are mirrored in preflight telemetry and readiness diagnostics. + Node-agent image `rap-node-agent:codex-service-supervisor-20260512z5` is built + and deployed on `test-1/2/3`. Verification passed: `go test ./internal/mesh`, + C19Z4 live smoke, C19Z3 source smoke, and C19Z3 regression smoke. Artifact: + `artifacts/c19z4-remote-workspace-mailbox-preflight-action-hints-smoke-result.json`. +- C19Z5 Remote Workspace mailbox preflight remediation provenance is + implemented and runtime-smoke-proven on docker-test. Preflight responses, + telemetry, and readiness diagnostics now include `action_reason` and + structured `action_context` with cursor, retained sequence bounds, + dropped/missing counts, and expected window counters explaining why the action + hints were chosen. Node-agent image + `rap-node-agent:codex-service-supervisor-20260512z6` is built and deployed on + `test-1/2/3`. Verification passed: `go test ./internal/mesh`, C19Z5 live + smoke, C19Z4 source smoke, and C19Z4 regression smoke. Artifact: + `artifacts/c19z5-remote-workspace-mailbox-preflight-provenance-smoke-result.json`. +- C19Z6 Remote Workspace mailbox preflight operator summary is implemented and + runtime-smoke-proven on docker-test. Preflight responses, telemetry, and + readiness diagnostics now include `operator_summary` plus compact + `operator_summary_fields` with diagnostic state, recommended action, action + reason, resume cursor, retained bounds, missing dropped count, and expected + window counters. This keeps dashboard/handoff text derived from the same + read-only preflight state without mutating mailbox cursors. Node-agent image + `rap-node-agent:codex-service-supervisor-20260512z7` is built and deployed on + `test-1/2/3`. Verification passed: `go test ./internal/mesh`, C19Z6 live + smoke, C19Z5 source smoke, and C19Z5 regression smoke. Artifact: + `artifacts/c19z6-remote-workspace-mailbox-preflight-summary-smoke-result.json`. +- C19Z7 Remote Workspace mailbox preflight operator severity is implemented and + runtime-smoke-proven on docker-test. Preflight responses, telemetry, and + readiness diagnostics now include machine-sortable `operator_status` and + `operator_severity`, mirrored into `operator_summary_fields`; stale cursor + gaps classify as `resync_required` / `warn`, ready windows as + `ready_to_resume` / `ok`, and caught-up cursors as `caught_up` / `info`. + Node-agent image `rap-node-agent:codex-service-supervisor-20260512z8` is + built and deployed on `test-1/2/3`. Verification passed: + `go test ./internal/mesh`, C19Z7 live smoke, C19Z6 source smoke, and C19Z6 + regression smoke. Artifact: + `artifacts/c19z7-remote-workspace-mailbox-preflight-severity-smoke-result.json`. +- C19Z8 Remote Workspace mailbox preflight readiness rollup is implemented and + runtime-smoke-proven on docker-test. `adapter_runtime_readiness` now preserves + all existing flat latest-preflight fields and also exposes a grouped + `last_preflight` object with observed time, consumer/cursor, expected window + counts, diagnostic state, action hints/provenance, operator summary/status/ + severity, and summary fields for admin UI consumption. Node-agent image + `rap-node-agent:codex-service-supervisor-20260512z9` is built and deployed on + `test-1/2/3`. Verification passed: `go test ./internal/mesh`, C19Z8 live + smoke, C19Z3 source smoke with `-SkipClose`, and C19Z7 regression smoke. + Artifact: + `artifacts/c19z8-remote-workspace-mailbox-preflight-rollup-smoke-result.json`. +- C19Z9 Remote Workspace mailbox preflight retained-window rollup detail is + implemented and runtime-smoke-proven on docker-test. The readiness + `last_preflight` object now exposes `first_retained_sequence`, + `last_retained_sequence`, and `mailbox_dropped_total` alongside the expected + window, stale-cursor, action, and operator fields, so admin UI can explain why + a cursor is outside the retained bounded-mailbox window without reopening the + raw preflight response. Node-agent image + `rap-node-agent:codex-service-supervisor-20260512z10` is built and deployed on + `test-1/2/3`. Verification passed: `go test ./internal/mesh`, C19Z9 live + smoke, C19Z8 source smoke, and C19Z8 regression smoke. Artifact: + `artifacts/c19z9-remote-workspace-mailbox-preflight-retained-window-smoke-result.json`. +- C19Z10 Remote Workspace mailbox preflight remediation checklist is + implemented and runtime-smoke-proven on docker-test. The readiness + `last_preflight` object now includes `remediation_checklist`, a structured + operator checklist derived from diagnostic state/action hints. Stale cursor + gaps surface required unsatisfied steps for resetting the consumer cursor, + requesting full adapter resync, and resuming from checkpoint after resync; + ready windows surface a satisfied resume step. Node-agent image + `rap-node-agent:codex-service-supervisor-20260512z11` is built and deployed on + `test-1/2/3`. Verification passed: `go test ./internal/mesh`, C19Z10 live + smoke, C19Z9 source smoke, and C19Z9 regression smoke. Artifact: + `artifacts/c19z10-remote-workspace-mailbox-preflight-checklist-smoke-result.json`. +- C19Z11 Remote Workspace mailbox preflight checklist status is implemented and + runtime-smoke-proven on docker-test. The readiness `last_preflight` object now + includes `remediation_checklist_status` plus `remediation_checklist_counts` + with total, required, satisfied, and pending counts, so admin UI can show + `ready` or `action_required` without scanning the checklist array. Node-agent + image `rap-node-agent:codex-service-supervisor-20260512z12` is built and + deployed on `test-1/2/3`. Verification passed: `go test ./internal/mesh`, + C19Z11 live smoke, C19Z10 source smoke, and C19Z10 regression smoke. Artifact: + `artifacts/c19z11-remote-workspace-mailbox-preflight-checklist-status-smoke-result.json`. +- C19Z12 Remote Workspace mailbox preflight operator status counters are + implemented and runtime-smoke-proven on docker-test. Session readiness now + exposes `mailbox_preflight_operator_status_counts` and + `mailbox_preflight_operator_severity_counts`, and the grouped + `last_preflight` rollup mirrors them as `operator_status_counts` and + `operator_severity_counts`. This lets operators spot repeated + `resync_required` / `warn` preflights without storing a history log. + Node-agent image `rap-node-agent:codex-service-supervisor-20260512z13` is + built and deployed on `test-1/2/3`. Verification passed: + `go test ./internal/mesh`, C19Z12 live smoke, C19Z11 source smoke, and C19Z11 + regression smoke. Artifact: + `artifacts/c19z12-remote-workspace-mailbox-preflight-status-counts-smoke-result.json`. +- C19Z13 Remote Workspace mailbox preflight attention status is implemented and + runtime-smoke-proven on docker-test. Session readiness now exposes + `preflight_attention_status`, mirrored in `last_preflight`, derived from + status/severity counters as `clean`, `needs_attention`, + `repeated_resync_required`, or `unknown`. This gives admin UI a sortable + preflight health value without interpreting count maps. Node-agent image + `rap-node-agent:codex-service-supervisor-20260512z14` is built and deployed on + `test-1/2/3`. Verification passed: `go test ./internal/mesh`, C19Z13 live + smoke, C19Z12 source smoke, and C19Z12 regression smoke. Artifact: + `artifacts/c19z13-remote-workspace-mailbox-preflight-attention-smoke-result.json`. +- C19Z14 Remote Workspace mailbox repeated-resync preflight proof is implemented + and runtime-smoke-proven on docker-test. Unit and live smoke coverage now + perform multiple stale preflight checks on the same active adapter session and + prove `preflight_attention_status=repeated_resync_required` with + `resync_required` / `warn` counters at 2 or higher. Node-agent image + `rap-node-agent:codex-service-supervisor-20260512z15` is built and deployed on + `test-1/2/3`. Verification passed: `go test ./internal/mesh`, C19Z14 live + smoke, C19Z3 source smoke with `-SkipClose`, and C19Z13 regression smoke. + Artifact: + `artifacts/c19z14-remote-workspace-mailbox-preflight-repeated-attention-smoke-result.json`. +- C19Z15 Remote Workspace mailbox preflight attention reason is implemented and + runtime-smoke-proven on docker-test. Session readiness and `last_preflight` + now expose `preflight_attention_reason` beside `preflight_attention_status`, + with reasons such as `no_resync_required_preflight_observed`, + `resync_required_preflight_observed`, and + `resync_required_preflight_repeated`, so admin UI can explain the status + without parsing counters. Node-agent image + `rap-node-agent:codex-service-supervisor-20260512z16` is built and deployed on + `test-1/2/3`. Verification passed: `go test ./internal/mesh`, C19Z15 live + smoke, C19Z14 source smoke, and C19Z14 regression smoke. Artifact: + `artifacts/c19z15-remote-workspace-mailbox-preflight-attention-reason-smoke-result.json`. +- C19Z16 Remote Workspace mailbox preflight attention reason coverage is + implemented and runtime-smoke-proven on docker-test. Unit coverage now proves + clean, single-resync, repeated-resync, and no-preflight reason/status + summaries, and live smoke proves the single stale-preflight reason + `resync_required_preflight_observed`. This is coverage-only on top of + `rap-node-agent:codex-service-supervisor-20260512z16`, which remains deployed + on `test-1/2/3`. Verification passed: `go test ./internal/mesh`, C19Z16 live + smoke, C19Z13 source smoke, and C19Z15 regression smoke. Artifact: + `artifacts/c19z16-remote-workspace-mailbox-preflight-attention-reason-coverage-smoke-result.json`. +- C19Z17 Remote Workspace mailbox preflight diagnostics contract marker is + implemented and runtime-smoke-proven on docker-test. The readiness + `last_preflight` rollup now includes `diagnostics_schema_version` and + `diagnostics_contract` entries for `retained_window`, + `remediation_checklist`, `attention`, and `operator_counts`, allowing admin UI + to gate rendering safely. Node-agent image + `rap-node-agent:codex-service-supervisor-20260512z17` is built and deployed on + `test-1/2/3`. Verification passed: `go test ./internal/mesh`, C19Z17 live + smoke, C19Z16 source smoke, and C19Z16 regression smoke. Artifact: + `artifacts/c19z17-remote-workspace-mailbox-preflight-contract-smoke-result.json`. +- C19Z18 Remote Workspace mailbox preflight diagnostics feature flags are + implemented and runtime-smoke-proven on docker-test. The readiness + `last_preflight` rollup now includes boolean `diagnostics_features` for + `retained_window`, `remediation_checklist`, `attention`, and + `operator_counts`, so UI and diagnostics clients can gate fields without + scanning the contract list. Node-agent image + `rap-node-agent:codex-service-supervisor-20260512z18` is built and deployed on + `test-1/2/3`. Verification passed: `go test ./internal/mesh`, C19Z18 live + smoke, and C19Z17 regression smoke. Artifact: + `artifacts/c19z18-remote-workspace-mailbox-preflight-feature-flags-smoke-result.json`. +- C19Z19 Remote Workspace mailbox preflight diagnostics compatibility proof is + complete on docker-test. This coverage-only layer verifies that the grouped + preflight diagnostics contract remains available in both forms: + `diagnostics_contract` string entries and matching boolean + `diagnostics_features` flags for retained-window, remediation-checklist, + attention, and operator-count diagnostics. No new runtime image was required; + the proof ran on + `rap-node-agent:codex-service-supervisor-20260512z18` deployed on + `test-1/2/3`. Verification passed: `go test ./internal/mesh` and C19Z19 live + smoke. Artifact: + `artifacts/c19z19-remote-workspace-mailbox-preflight-contract-compatibility-smoke-result.json`. +- C19Z20 Remote Workspace mailbox preflight absence proof is complete on + docker-test. Unit and live smoke coverage now prove that an active adapter + session before any mailbox preflight reports `mailbox_preflight_total=0`, + `preflight_attention_status=unknown`, + `preflight_attention_reason=no_preflight_observed`, and no grouped + `last_preflight` rollup. This lets admin UI distinguish "not observed yet" + from an observed clean diagnostics bundle. No new runtime image was required; + the proof ran on + `rap-node-agent:codex-service-supervisor-20260512z18` deployed on + `test-1/2/3`. Verification passed: `go test ./internal/mesh` and C19Z20 live + smoke. Artifact: + `artifacts/c19z20-remote-workspace-mailbox-preflight-absence-smoke-result.json`. +- C19Z21 Remote Workspace no-active-session readiness proof is implemented and + runtime-smoke-proven on docker-test. Readiness now reports the terminal state + from the terminal session ledger when the last adapter session is closed, so + `last_session_state=closed` instead of the last delivery state. Unit and live + smoke coverage prove `status=idle`, `ready=false`, `active_session_count=0`, + `diagnostic_state=last_session_terminal_or_expired`, no active + `adapter_session_id`, no grouped `last_preflight`, and the closed last session + id/state. Node-agent image + `rap-node-agent:codex-service-supervisor-20260512z21` is built and deployed on + `test-1/2/3`. Verification passed: `go test ./internal/mesh` and C19Z21 live + smoke. Artifact: + `artifacts/c19z21-remote-workspace-no-active-session-readiness-smoke-result.json`. +- C19Z22 Remote Workspace terminal-state readiness coverage is complete on + docker-test. Unit and live smoke coverage now prove the same no-active-session + readiness shape for `expire` and `reset` controls: idle/not-ready, zero active + sessions, no active `adapter_session_id`, no grouped `last_preflight`, and + terminal `last_session_state=expired` or `last_session_state=reset` from the + terminal-session ledger. Node-agent image + `rap-node-agent:codex-service-supervisor-20260512z22` is built and deployed on + `test-1/2/3`. Verification passed: `go test ./internal/mesh` and C19Z22 live + smoke. Artifact: + `artifacts/c19z22-remote-workspace-terminal-state-readiness-smoke-result.json`. +- C19Z23 Remote Workspace terminal-session summary metadata is implemented and + runtime-smoke-proven on docker-test. When readiness has no active adapter + session but the last adapter session is terminal, it now includes + `terminal_session_summary` with `adapter_session_id`, `session_state`, + `reason`, and `controlled_at`, while retaining the existing flat + compatibility fields. Node-agent image + `rap-node-agent:codex-service-supervisor-20260512z23` is built and deployed on + `test-1/2/3`. Verification passed: `go test ./internal/mesh` and C19Z23 live + smoke. Artifact: + `artifacts/c19z23-remote-workspace-terminal-session-summary-smoke-result.json`. +- C19Z24 Remote Workspace terminal-session summary contract marker is + implemented and runtime-smoke-proven on docker-test. The grouped + `terminal_session_summary` now includes + `schema_version=rap.remote_workspace_adapter_terminal_session_summary.v1` and + `summary_contract` entries for `adapter_session_id`, `session_state`, + `reason`, and `controlled_at`, allowing admin UI to gate the block safely. + Node-agent image `rap-node-agent:codex-service-supervisor-20260512z24` is + built and deployed on `test-1/2/3`. Verification passed: + `go test ./internal/mesh` and C19Z24 live smoke. Artifact: + `artifacts/c19z24-remote-workspace-terminal-summary-contract-smoke-result.json`. +- C19Z25 Remote Workspace terminal-session summary feature flags are + implemented and runtime-smoke-proven on docker-test. The grouped + `terminal_session_summary` now includes boolean `summary_features` for + `adapter_session_id`, `session_state`, `reason`, and `controlled_at`, + mirroring the preflight diagnostics contract/feature pattern. Node-agent image + `rap-node-agent:codex-service-supervisor-20260512z25` is built and deployed on + `test-1/2/3`. Verification passed: `go test ./internal/mesh` and C19Z25 live + smoke. Artifact: + `artifacts/c19z25-remote-workspace-terminal-summary-features-smoke-result.json`. +- C19Z26 Remote Workspace terminal-session summary compatibility proof is + complete on docker-test. This coverage-only layer verifies that + `summary_contract` and boolean `summary_features` stay consistent for + `adapter_session_id`, `session_state`, `reason`, and `controlled_at` across + workload and telemetry reports. No new runtime image was required; the proof + ran on `rap-node-agent:codex-service-supervisor-20260512z25` deployed on + `test-1/2/3`. Verification passed: `go test ./internal/mesh` and C19Z26 live + smoke. Artifact: + `artifacts/c19z26-remote-workspace-terminal-summary-compatibility-smoke-result.json`. +- C19Z27 Remote Workspace terminal-session summary absence proof is complete on + docker-test. Unit and live smoke coverage now verify that a fresh adapter + runtime before any session or terminal history reports + `diagnostic_state=waiting_for_session`, zero active/terminal sessions, no + `last_adapter_session_id`, no `last_session_state`, no grouped + `terminal_session_summary`, and no `last_preflight`. No new runtime image was + required; the proof ran on + `rap-node-agent:codex-service-supervisor-20260512z25` deployed on + `test-1/2/3` after a clean runtime restart. Verification passed: + `go test ./internal/mesh` and C19Z27 live smoke. Artifact: + `artifacts/c19z27-remote-workspace-terminal-summary-absence-smoke-result.json`. +- C19Z28 Remote Workspace no-session readiness summary is implemented and + runtime-smoke-proven on docker-test. Fresh adapter runtime readiness now + includes grouped `no_session_summary` with + `schema_version=rap.remote_workspace_adapter_no_session_summary.v1`, + `summary_contract` entries for `status`, `diagnostic_state`, + `active_session_count`, and `terminal_session_count`, and matching idle + waiting-for-session values. The terminal summary absence contract remains + intact for empty runtime state. Node-agent image + `rap-node-agent:codex-service-supervisor-20260513z28` is built and deployed + on `test-1/2/3`. Verification passed: `go test ./internal/mesh`, C19Z28 live + smoke, and C19Z27 regression smoke. Artifact: + `artifacts/c19z28-remote-workspace-no-session-summary-smoke-result.json`. +- C19Z29 Remote Workspace no-session summary feature flags are implemented and + runtime-smoke-proven on docker-test. The grouped `no_session_summary` now + includes boolean `summary_features` for `status`, `diagnostic_state`, + `active_session_count`, and `terminal_session_count`, mirroring the terminal + summary and preflight diagnostics contract/feature pattern. Node-agent image + `rap-node-agent:codex-service-supervisor-20260513z29` is built and deployed + on `test-1/2/3`. Verification passed: `go test ./internal/mesh`, C19Z29 live + smoke, and C19Z28 regression smoke. Artifact: + `artifacts/c19z29-remote-workspace-no-session-summary-features-smoke-result.json`. +- C19Z30 Remote Workspace no-session summary compatibility proof is complete + on docker-test. This coverage-only layer verifies that `summary_contract` + entries and boolean `summary_features` stay aligned for `status`, + `diagnostic_state`, `active_session_count`, and `terminal_session_count` + across workload and telemetry reports. No new runtime image was required; the + proof ran on `rap-node-agent:codex-service-supervisor-20260513z29` deployed + on `test-1/2/3`. Verification passed: `go test ./internal/mesh` and C19Z30 + live smoke. Artifact: + `artifacts/c19z30-remote-workspace-no-session-summary-compatibility-smoke-result.json`. +- C19Z31 Remote Workspace terminal-history no-session summary absence proof is + complete on docker-test. This focused live-smoke layer verifies that once a + session reaches terminal states (`expired` and `reset`), readiness switches + to grouped `terminal_session_summary` and does not include grouped + `no_session_summary` in either workload or telemetry reports. No new runtime + image was required; the proof ran on + `rap-node-agent:codex-service-supervisor-20260513z29` deployed on + `test-1/2/3`. Verification passed: C19Z31 live smoke. Artifact: + `artifacts/c19z31-remote-workspace-terminal-history-no-session-summary-absence-smoke-result.json`. +- C19Z32 Remote Workspace readiness summary exclusivity proof is complete on + docker-test. Unit and live smoke coverage now verify that grouped + `no_session_summary` and `terminal_session_summary` are mutually exclusive + across fresh, active, and terminal readiness states: fresh has only + `no_session_summary`, active has neither grouped summary, and terminal has + only `terminal_session_summary`. No new runtime image was required; the proof + ran on `rap-node-agent:codex-service-supervisor-20260513z29` after a clean + runtime restart. Verification passed: `go test ./internal/mesh` and C19Z32 + live smoke. Artifact: + `artifacts/c19z32-remote-workspace-readiness-summary-exclusivity-smoke-result.json`. +- C19Z33 Remote Workspace readiness state matrix artifact is complete on + docker-test. The live smoke now generates a compact six-row + fresh/active/terminal x workload/telemetry matrix with only the admin-facing + `adapter_runtime_readiness` fields and summary-presence booleans, avoiding + the large nested source smoke payload in the handoff artifact. No new runtime + image was required; the proof ran on + `rap-node-agent:codex-service-supervisor-20260513z29` after a clean runtime + restart. Verification passed: C19Z33 live smoke. Artifact: + `artifacts/c19z33-remote-workspace-readiness-state-matrix-smoke-result.json`. +- C19Z34 Remote Workspace probe-to-runtime gate artifact is complete on + docker-test. The live smoke now records that the current runtime remains + `execution_mode=contract_probe`, `probe_only=true`, and + `payload_traffic=none` across the readiness matrix, lists the contracts ready + for admin/runtime handoff, and lists the remaining gates before real RDP + frame transport can be enabled. No new runtime image was required; the proof + ran on `rap-node-agent:codex-service-supervisor-20260513z29` after a clean + runtime restart. Verification passed: C19Z34 live smoke. Artifact: + `artifacts/c19z34-remote-workspace-probe-to-runtime-gate-smoke-result.json`. +- C19Z35 Remote Workspace real-adapter supervision scaffold is implemented and + runtime-smoke-proven on docker-test. The `rdp-worker` contract-probe workload + status now includes disabled-by-default + `real_adapter_supervision` schema + `rap.remote_workspace_real_adapter_supervision.v1`, future config env names, + status contract fields, and guardrails. The active execution mode remains + `contract_probe`, the future real adapter path reports disabled/blocked when + requested, and payload traffic remains `none`. Node-agent image + `rap-node-agent:codex-service-supervisor-20260513z35` is built and deployed + on `test-1/2/3`. Verification passed: + `go test ./internal/supervisor ./internal/mesh` and C19Z35 live smoke. + Artifact: + `artifacts/c19z35-remote-workspace-real-adapter-supervision-scaffold-smoke-result.json`. +- C19Z36 Remote Workspace real-adapter supervision compatibility proof is + complete on docker-test. Unit and live smoke coverage now verify that the + disabled scaffold keeps `enabled=false`, `activation_state`, + `execution_mode`, `payload_traffic=none`, `process_model`, `config_env`, + `status_contract`, and guardrails aligned in workload status. No new runtime + image was required; the proof ran on + `rap-node-agent:codex-service-supervisor-20260513z35` deployed on + `test-1/2/3`. Verification passed: + `go test ./internal/supervisor ./internal/mesh` and C19Z36 live smoke. + Artifact: + `artifacts/c19z36-remote-workspace-real-adapter-supervision-compatibility-smoke-result.json`. +- C19Z37 Remote Workspace disabled real-adapter config projection is + implemented and runtime-smoke-proven on docker-test. Node-agent config now + reads `RAP_REMOTE_WORKSPACE_REAL_ADAPTER_ENABLED`, + `RAP_REMOTE_WORKSPACE_REAL_ADAPTER_COMMAND`, + `RAP_REMOTE_WORKSPACE_REAL_ADAPTER_ARGS_JSON`, and + `RAP_REMOTE_WORKSPACE_REAL_ADAPTER_WORKDIR`, passes the sanitized shape into + supervisor status, and exposes + `rap.remote_workspace_real_adapter_config_projection.v1` under the disabled + `real_adapter_supervision` contract. The projection reports + `enabled_requested`, command/args/workdir presence, args JSON shape, and + `raw_values_redacted=true`; raw command/args/workdir values are not exposed. + Even with `RAP_REMOTE_WORKSPACE_REAL_ADAPTER_ENABLED=true`, the contract + keeps `enabled=false`, `activation_allowed=false`, and + `payload_traffic=none`. Node-agent image + `rap-node-agent:codex-service-supervisor-20260513z37` is built and deployed + on `test-1/2/3`. Verification passed: + `go test ./internal/config ./internal/supervisor ./internal/mesh`, + `git diff --check`, and C19Z37 live smoke. Artifact: + `artifacts/c19z37-remote-workspace-real-adapter-config-projection-smoke-result.json`. +- C19Z38 Remote Workspace real-adapter config projection compatibility proof is + complete on docker-test. Unit coverage now verifies default/empty config, + requested array args, object args, and opaque args shapes. Live smoke assigns + the native `rdp-worker` contract probe to `test-1` and `test-2`: `test-1` + runs with future real-adapter env and proves requested presence/`json_array` + shape, while `test-2` runs without those env values and proves + `enabled_requested=false`, absent command/args/workdir, and `args_json_shape` + `absent`. Both paths keep `enabled=false`, `activation_allowed=false`, + `raw_values_redacted=true`, and `payload_traffic=none`. Verification passed: + `go test ./internal/supervisor` and C19Z38 live smoke. Artifact: + `artifacts/c19z38-remote-workspace-real-adapter-config-projection-compatibility-smoke-result.json`. +- C19Z39 Remote Workspace real-adapter activation decision contract is + implemented and runtime-smoke-proven on docker-test. The disabled + `real_adapter_supervision` contract now includes + `activation_decision` schema + `rap.remote_workspace_real_adapter_activation_decision.v1`, with + `decision=blocked`, `reason=real_runtime_stage_not_enabled`, + `activation_allowed=false`, `payload_traffic=none`, `enabled_requested` + mirrored from config, and explicit required/missing gates: + `real_runtime_stage_enabled`, `fabric_service_channel_runtime_ready`, + `adapter_process_supervisor_enabled`, and + `payload_forwarding_contract_enabled`. Live smoke proves both requested + (`test-1`) and default/empty (`test-2`) paths remain blocked. Node-agent + image `rap-node-agent:codex-service-supervisor-20260513z39` is built and + deployed on `test-1/2/3`. Verification passed: `go test + ./internal/supervisor` and C19Z39 live smoke. Artifact: + `artifacts/c19z39-remote-workspace-real-adapter-activation-decision-smoke-result.json`. +- C19Z40 Remote Workspace real-adapter handoff report is complete on + docker-test. Unit coverage now verifies that config projection and activation + decision stay aligned for default and requested config. The live smoke emits + a compact two-row handoff artifact for requested (`test-1`) and default + (`test-2`) nodes, proving `contract_probe` remains active, the supervision + scaffold is compatible, projection is compatible, activation decision is + blocked, missing gates are explicit, and projection/decision fields are + aligned. No new runtime image was required; the proof ran on + `rap-node-agent:codex-service-supervisor-20260513z39`. Verification passed: + `go test ./internal/supervisor` and C19Z40 live smoke. Artifact: + `artifacts/c19z40-remote-workspace-real-adapter-handoff-report-smoke-result.json`. +- C19Z41 Remote Workspace real-adapter feature flags are implemented and + runtime-smoke-proven on docker-test. The disabled + `real_adapter_supervision` contract now includes a `features` map with + booleans for `config_projection`, `activation_decision`, `missing_gates`, + and `raw_values_redacted`; `status_contract` includes `features` so UI and + automation can gate rendering without parsing contract lists. Node-agent + image `rap-node-agent:codex-service-supervisor-20260513z41` is built and + deployed on `test-1/2/3`. Verification passed: + `go test ./internal/supervisor` and C19Z41 live smoke. Artifact: + `artifacts/c19z41-remote-workspace-real-adapter-feature-flags-smoke-result.json`. +- C19Z42 Remote Workspace real-adapter handoff v2 report is complete on + docker-test. The live smoke folds C19Z41 `features` into the compact + requested/default handoff rows from C19Z40 and proves scaffold compatibility, + config projection compatibility, blocked activation decision compatibility, + feature flag compatibility, explicit missing gates, and + projection/decision/features alignment in one artifact. No new runtime image + was required; the proof ran on + `rap-node-agent:codex-service-supervisor-20260513z41`. Verification passed: + C19Z42 live smoke. Artifact: + `artifacts/c19z42-remote-workspace-real-adapter-handoff-v2-smoke-result.json`. +- C19Z43 Remote Workspace real-adapter desired-workload precedence proof is + complete on docker-test. Unit and live smoke coverage now verify that when a + native `rdp-worker` desired workload requests both `adapter_contract_probe` + and `real_adapter_supervision`, the safe contract-probe path retains + precedence: reported state remains `running`, `execution_mode=contract_probe`, + the disabled real-adapter branch is not selected, activation decision remains + `blocked`, and payload traffic remains `none`. No new runtime image was + required; the proof ran on + `rap-node-agent:codex-service-supervisor-20260513z41`. Verification passed: + `go test ./internal/supervisor` and C19Z43 live smoke. Artifact: + `artifacts/c19z43-remote-workspace-real-adapter-precedence-smoke-result.json`. +- C19Z44 Remote Workspace real-adapter-only desired-workload disabled proof is + complete on docker-test. Unit and live smoke coverage now verify the inverse + negative path: when a native `rdp-worker` desired workload requests only + `real_adapter_supervision=true` without `adapter_contract_probe`, the status + remains `degraded`, `execution_mode=real_adapter_supervision_disabled`, + traffic is `blocked`, `payload_traffic=none`, config projection and + activation decision mirror `enabled_requested=true`, activation remains + `blocked`, missing gates remain explicit, and feature flags stay visible. No + new runtime image was required; the proof ran on + `rap-node-agent:codex-service-supervisor-20260513z41`. Verification passed: + `go test ./internal/supervisor` and C19Z44 live smoke. Artifact: + `artifacts/c19z44-remote-workspace-real-adapter-only-disabled-smoke-result.json`. +- C19Z45 Remote Workspace real-adapter desired-workload mode matrix is complete + on docker-test. The live smoke emits a compact three-row matrix for + `probe_only`, `real_adapter_only`, and `probe_and_real_adapter` desired config + modes. It proves expected reported state, execution mode, traffic, + `payload_traffic=none`, `enabled=false`, activation decision `blocked`, + `activation_allowed=false`, projected `enabled_requested=true`, and feature + visibility across all three rows. No new runtime image was required; the + proof ran on `rap-node-agent:codex-service-supervisor-20260513z41`. + Verification passed: C19Z45 live smoke. Artifact: + `artifacts/c19z45-remote-workspace-real-adapter-mode-matrix-smoke-result.json`. +- C19Z46 Remote Workspace real-adapter mode matrix compatibility proof is + complete on docker-test. The C19Z45 matrix rows now include explicit + `missing_gates_visible` and `feature_flags_visible` booleans, and C19Z46 + validates the row contract fields plus expected values for `probe_only`, + `real_adapter_only`, and `probe_and_real_adapter`. No new runtime image was + required; the proof ran on + `rap-node-agent:codex-service-supervisor-20260513z41`. Verification passed: + C19Z46 live smoke. Artifact: + `artifacts/c19z46-remote-workspace-real-adapter-mode-matrix-compatibility-smoke-result.json`. +- C19Z47 Remote Workspace disabled process-supervisor preconditions contract is + implemented and runtime-smoke-proven on docker-test. The disabled + `real_adapter_supervision` status now includes + `process_supervisor_preconditions` schema + `rap.remote_workspace_real_adapter_process_supervisor_preconditions.v1` with + `process_start_allowed=false`, `reason=disabled_until_real_runtime_stage`, + sanitized command/args/workdir presence booleans, required checks, and + matching missing checks. Features now include + `process_supervisor_preconditions` and + `process_supervisor_start_disabled`. Node-agent image + `rap-node-agent:codex-service-supervisor-20260513z47` is built and deployed + on `test-1/2/3`. Verification passed: `go test ./internal/supervisor` and + C19Z47 live smoke. Artifact: + `artifacts/c19z47-remote-workspace-real-adapter-process-preconditions-smoke-result.json`. +- C19Z48 Remote Workspace process-supervisor preconditions compatibility proof + is complete on docker-test. The live smoke uses C19Z47 as source and verifies + required fields, requested/default config shapes, required/missing checks, + and `process_start_allowed=false` for both requested and default nodes. No + new runtime image was required; the proof ran on + `rap-node-agent:codex-service-supervisor-20260513z47`. Verification passed: + C19Z48 live smoke. Artifact: + `artifacts/c19z48-remote-workspace-real-adapter-process-preconditions-compatibility-smoke-result.json`. +- C19Z49 Remote Workspace real-adapter handoff v3 report is complete on + docker-test. The compact requested/default handoff rows now include + process-supervisor preconditions alongside scaffold, config projection, + activation decision, feature flags, missing gates, and alignment checks. It + proves preconditions compatibility, `process_start_allowed=false`, and + alignment between feature flags, preconditions, and blocked activation + decision. No new runtime image was required; the proof ran on + `rap-node-agent:codex-service-supervisor-20260513z47`. Verification passed: + C19Z49 live smoke. Artifact: + `artifacts/c19z49-remote-workspace-real-adapter-handoff-v3-smoke-result.json`. +- C19Z50 Remote Workspace real-adapter mode matrix v2 is complete on + docker-test. The three-row desired-workload mode matrix now includes + process-supervisor preconditions for `probe_only`, `real_adapter_only`, and + `probe_and_real_adapter`, proving `process_start_allowed=false`, missing + precondition checks are visible, process-start-disabled feature is visible, + activation remains blocked, and payload traffic remains `none` for every + mode. No new runtime image was required; the proof ran on + `rap-node-agent:codex-service-supervisor-20260513z47`. Verification passed: + C19Z50 live smoke. Artifact: + `artifacts/c19z50-remote-workspace-real-adapter-mode-matrix-v2-smoke-result.json`. +- C19Z51 Remote Workspace real-adapter mode matrix v2 compatibility proof is + complete on docker-test. The live smoke uses C19Z50 as source and validates + the row contract fields plus expected values for `probe_only`, + `real_adapter_only`, and `probe_and_real_adapter`, including blocked + activation, `process_start_allowed=false`, precondition visibility, missing + checks visibility, process-start-disabled feature visibility, and + `payload_traffic=none`. No new runtime image was required; the proof ran on + `rap-node-agent:codex-service-supervisor-20260513z47`. Verification passed: + C19Z51 live smoke. Artifact: + `artifacts/c19z51-remote-workspace-real-adapter-mode-matrix-v2-compatibility-smoke-result.json`. +- C19Z52 Remote Workspace disabled process-health-probe contract is implemented + and runtime-smoke-proven on docker-test. The disabled + `real_adapter_supervision` status now includes `process_health_probe` schema + `rap.remote_workspace_real_adapter_process_health_probe.v1` with + `health_probe_enabled=false`, `reason=disabled_until_real_runtime_stage`, + `payload_traffic=none`, `probe_model=external_process_health`, required + signals, and matching missing signals. Features now include + `process_health_probe` and `process_health_probe_disabled`. Node-agent image + `rap-node-agent:codex-service-supervisor-20260513z52` is built and deployed + on `test-1/2/3`. Verification passed: `go test ./internal/supervisor` and + C19Z52 live smoke. Artifact: + `artifacts/c19z52-remote-workspace-real-adapter-process-health-probe-smoke-result.json`. +- C19Z53 Remote Workspace process-health-probe compatibility proof is complete + on docker-test. The live smoke uses C19Z52 as source and verifies required + fields, requested/default health probe forms, required/missing signals, + `health_probe_enabled=false`, and `payload_traffic=none` in both forms. No + new runtime image was required; the proof ran on + `rap-node-agent:codex-service-supervisor-20260513z52`. Verification passed: + C19Z53 live smoke. Artifact: + `artifacts/c19z53-remote-workspace-real-adapter-process-health-probe-compatibility-smoke-result.json`. +- C19Z54 Remote Workspace real-adapter handoff v4 report is complete on + docker-test. The compact requested/default handoff rows now include + process-health-probe visibility alongside the supervision scaffold, config + projection, activation decision, feature flags, process-supervisor + preconditions, required env, missing gates, missing precondition checks, and + missing health signals. It proves `health_probe_enabled=false`, + `payload_traffic=none`, health-probe-disabled feature visibility, and + alignment across all disabled real-adapter contracts. No new runtime image + was required; the proof ran on + `rap-node-agent:codex-service-supervisor-20260513z52`. Verification passed: + C19Z54 live smoke. Artifact: + `artifacts/c19z54-remote-workspace-real-adapter-handoff-v4-smoke-result.json`. +- C19Z55 Remote Workspace real-adapter mode matrix v3 is complete on + docker-test. The three-row desired-workload matrix now includes + process-health-probe visibility for `probe_only`, `real_adapter_only`, and + `probe_and_real_adapter`, proving `health_probe_enabled=false`, missing + health signals are visible, health-probe-disabled feature is visible, + process start remains disabled, activation remains blocked, and + `payload_traffic=none` for every mode. No new runtime image was required; the + proof ran on `rap-node-agent:codex-service-supervisor-20260513z52`. + Verification passed: C19Z55 live smoke. Artifact: + `artifacts/c19z55-remote-workspace-real-adapter-mode-matrix-v3-smoke-result.json`. +- C19Z56 Remote Workspace real-adapter mode matrix v3 compatibility proof is + complete on docker-test. The live smoke uses C19Z55 as source and validates + the row contract fields plus expected values for `probe_only`, + `real_adapter_only`, and `probe_and_real_adapter`, including blocked + activation, `process_start_allowed=false`, precondition visibility, missing + checks visibility, `health_probe_enabled=false`, health-probe visibility, + missing health-signal visibility, health-probe-disabled feature visibility, + and `payload_traffic=none`. No new runtime image was required; the proof ran + on `rap-node-agent:codex-service-supervisor-20260513z52`. Verification + passed: C19Z56 live smoke. Artifact: + `artifacts/c19z56-remote-workspace-real-adapter-mode-matrix-v3-compatibility-smoke-result.json`. +- C19Z57 Remote Workspace disabled real-adapter readiness/handoff summary is + complete on docker-test. The live smoke uses C19Z54 handoff v4 and C19Z56 + mode matrix v3 compatibility as source proofs, then emits a compact + `rap.remote_workspace_real_adapter_disabled_runtime_readiness_summary.v1` + operator summary plus a 10-item checklist covering handoff completeness, + mode matrix compatibility, requested/default config visibility, desired + workload modes, blocked activation, disabled process start, disabled health + probes, `payload_traffic=none`, missing gates visibility, and missing + health-signal visibility. No new runtime image was required; the proof ran + on `rap-node-agent:codex-service-supervisor-20260513z52`. Verification + passed: C19Z57 live smoke. Artifact: + `artifacts/c19z57-remote-workspace-real-adapter-readiness-handoff-summary-smoke-result.json`. +- C19Z58 Remote Workspace disabled real-adapter readiness/handoff summary + compatibility proof is complete on docker-test. The live smoke uses C19Z57 + as source and validates the summary contract fields, expected disabled + values, checklist counts, checklist item fields, and the full 10-item + checklist name/value set. It keeps `readiness_state=blocked_until_real_runtime_stage`, + `operator_action=keep_real_adapter_disabled`, `process_start_allowed=false`, + `health_probe_enabled=false`, and `payload_traffic=none`. No new runtime + image was required; the proof ran on + `rap-node-agent:codex-service-supervisor-20260513z52`. Verification passed: + C19Z58 live smoke. Artifact: + `artifacts/c19z58-remote-workspace-real-adapter-readiness-handoff-summary-compatibility-smoke-result.json`. +- C19Z59 Remote Workspace disabled real-adapter operator action map is complete + on docker-test. The live smoke uses C19Z58 as source and emits + `rap.remote_workspace_real_adapter_disabled_runtime_operator_action_map.v1` + with stable action keys: `keep_real_adapter_disabled`, + `review_real_runtime_stage_gates`, `validate_real_adapter_config_projection`, + `prepare_process_supervisor_preconditions`, + `prepare_process_health_probe_signals`, and + `keep_payload_forwarding_disabled`. Each action is derived from passed + readiness checklist items and explicitly keeps activation blocked, + `allows_process_start=false`, and `allows_payload_traffic=false`. No new + runtime image was required; the proof ran on + `rap-node-agent:codex-service-supervisor-20260513z52`. Verification passed: + C19Z59 live smoke. Artifact: + `artifacts/c19z59-remote-workspace-real-adapter-disabled-action-map-smoke-result.json`. +- C19Z60 Remote Workspace disabled real-adapter operator action map + compatibility proof is complete on docker-test. The live smoke uses C19Z59 + as source and validates the action map fields, expected disabled values, + action count, required action fields, all six action keys, severity values, + non-empty reasons, derived checklist references, and guardrails that keep + activation blocked with `allows_process_start=false` and + `allows_payload_traffic=false`. No new runtime image was required; the proof + ran on `rap-node-agent:codex-service-supervisor-20260513z52`. Verification + passed: C19Z60 live smoke. Artifact: + `artifacts/c19z60-remote-workspace-real-adapter-disabled-action-map-compatibility-smoke-result.json`. +- C19Z61 Remote Workspace disabled real-adapter admin handoff bundle is + complete on docker-test. The live smoke uses C19Z60 as source and emits + `rap.remote_workspace_real_adapter_admin_handoff_bundle.v1`, grouping the + readiness summary, operator checklist, and operator action map into one + compact admin-facing report. It proves required bundle fields and sections, + checklist/action counts, `admin_status=not_ready`, + `admin_action=keep_real_adapter_disabled`, activation blocked, + `process_start_allowed=false`, `health_probe_enabled=false`, and + `payload_traffic=none`. No new runtime image was required; the proof ran on + `rap-node-agent:codex-service-supervisor-20260513z52`. Verification passed: + C19Z61 live smoke. Artifact: + `artifacts/c19z61-remote-workspace-real-adapter-admin-handoff-bundle-smoke-result.json`. +- C19Z62 Remote Workspace disabled real-adapter admin handoff bundle + compatibility proof is complete on docker-test. The live smoke uses C19Z61 + as source and validates the bundle fields, required sections, count fields, + guardrail fields, admin values, nested readiness summary, checklist, and + operator action map. It keeps `admin_status=not_ready`, + `admin_action=keep_real_adapter_disabled`, activation blocked, + `process_start_allowed=false`, `health_probe_enabled=false`, and + `payload_traffic=none`. No new runtime image was required; the proof ran on + `rap-node-agent:codex-service-supervisor-20260513z52`. Verification passed: + C19Z62 live smoke. Artifact: + `artifacts/c19z62-remote-workspace-real-adapter-admin-handoff-bundle-compatibility-smoke-result.json`. +- C19Z63 Remote Workspace disabled real-adapter admin handoff digest is + complete on docker-test. The live smoke uses C19Z62 as source and emits + `rap.remote_workspace_real_adapter_admin_handoff_digest.v1` with stable + compact display rows for runtime stage, operator action, activation, process + start, health probe, payload traffic, checklist, and actions. It proves + `admin_status=not_ready`, `admin_action=keep_real_adapter_disabled`, + `runtime_stage=blocked_until_real_runtime_stage`, activation blocked, + process start disabled, health probe disabled, `payload_traffic=none`, and + preserved guardrails. No new runtime image was required; the proof ran on + `rap-node-agent:codex-service-supervisor-20260513z52`. Verification passed: + C19Z63 live smoke. Artifact: + `artifacts/c19z63-remote-workspace-real-adapter-admin-handoff-digest-smoke-result.json`. +- C19Z64 Remote Workspace disabled real-adapter admin handoff digest + compatibility proof is complete on docker-test. The live smoke uses C19Z63 + as source and validates digest fields, expected admin values, all eight + display rows (`runtime_stage`, `operator_action`, `activation`, + `process_start`, `health_probe`, `payload_traffic`, `checklist`, `actions`), + row fields, states, values, severity, and preserved guardrails. No new + runtime image was required; the proof ran on + `rap-node-agent:codex-service-supervisor-20260513z52`. Verification passed: + C19Z64 live smoke. Artifact: + `artifacts/c19z64-remote-workspace-real-adapter-admin-handoff-digest-compatibility-smoke-result.json`. +- C19Z65 Remote Workspace disabled real-adapter admin handoff digest rollup is + complete on docker-test. The live smoke uses C19Z64 as source and emits + `rap.remote_workspace_real_adapter_admin_handoff_digest_rollup.v1` with + counts by severity (`warn=2`, `info=6`), counts by state (`blocked=3`, + `disabled=3`, `required=1`, `complete=1`), `primary_action=keep_real_adapter_disabled`, + `admin_status=not_ready`, and a guardrail summary that keeps activation + blocked, process start disabled, health probe disabled, and + `payload_traffic=none`. No new runtime image was required; the proof ran on + `rap-node-agent:codex-service-supervisor-20260513z52`. Verification passed: + C19Z65 live smoke. Artifact: + `artifacts/c19z65-remote-workspace-real-adapter-admin-handoff-digest-rollup-smoke-result.json`. +- C19Z66 Remote Workspace disabled real-adapter admin handoff digest rollup + compatibility proof is complete on docker-test. The live smoke uses C19Z65 + as source and validates rollup fields, expected disabled admin values, + severity counts (`warn=2`, `info=6`), state counts (`blocked=3`, + `disabled=3`, `required=1`, `complete=1`), and guardrail summary fields that + keep activation blocked, process start disabled, health probe disabled, and + `payload_traffic=none`. No new runtime image was required; the proof ran on + `rap-node-agent:codex-service-supervisor-20260513z52`. Verification passed: + C19Z66 live smoke. Artifact: + `artifacts/c19z66-remote-workspace-real-adapter-admin-handoff-digest-rollup-compatibility-smoke-result.json`. +- C19Z67 Remote Workspace disabled real-adapter admin handoff full-chain + summary is complete on docker-test. The live smoke uses C19Z66 as source and + emits `rap.remote_workspace_real_adapter_admin_handoff_full_chain_summary.v1` + listing the proven contract chain from C19Z54 handoff v4 through C19Z66 + digest rollup compatibility. It validates 13 passed stages, artifact links, + `runtime_effect=contract_only_no_runtime_enablement`, `admin_status=not_ready`, + `primary_action=keep_real_adapter_disabled`, and guardrails that keep + activation blocked, process start disabled, health probe disabled, and + `payload_traffic=none`. No new runtime image was required; the proof ran on + `rap-node-agent:codex-service-supervisor-20260513z52`. Verification passed: + C19Z67 live smoke. Artifact: + `artifacts/c19z67-remote-workspace-real-adapter-admin-handoff-full-chain-summary-smoke-result.json`. +- C19Z68 Remote Workspace disabled real-adapter admin handoff full-chain + summary compatibility proof is complete on docker-test. The live smoke uses + C19Z67 as source and validates full-chain summary fields, expected disabled + admin values, all 13 stage keys, required stage fields, artifact references, + `status=passed`, `runtime_effect=contract_only_no_runtime_enablement`, and + guardrails that keep activation blocked, process start disabled, health probe + disabled, and `payload_traffic=none`. No new runtime image was required; the + proof ran on `rap-node-agent:codex-service-supervisor-20260513z52`. + Verification passed: C19Z68 live smoke. Artifact: + `artifacts/c19z68-remote-workspace-real-adapter-admin-handoff-full-chain-summary-compatibility-smoke-result.json`. +- C19Z69 Remote Workspace disabled real-adapter admin handoff release marker is + complete on docker-test. The live smoke uses C19Z68 as source and emits + `rap.remote_workspace_real_adapter_admin_handoff_release_marker.v1` with + `release_status=contract_only_ready_for_admin_handoff`, + `release_marker=c19z69_disabled_real_adapter_admin_handoff_contract_only`, + `real_runtime_stage=blocked`, + `runtime_effect=contract_only_no_runtime_enablement`, + `admin_status=not_ready`, `primary_action=keep_real_adapter_disabled`, and + `proven_stage_count=13`. Guardrails continue to keep activation blocked, + process start disabled, health probe disabled, and `payload_traffic=none`. + No new runtime image was required; the proof ran on + `rap-node-agent:codex-service-supervisor-20260513z52`. Verification passed: + C19Z69 live smoke. Artifact: + `artifacts/c19z69-remote-workspace-real-adapter-admin-handoff-release-marker-smoke-result.json`. +- C19Z70 Remote Workspace disabled real-adapter admin handoff release marker + compatibility proof is complete on docker-test. The compatibility smoke uses + C19Z69 as source and validates the release marker fields, expected + contract-only values, `real_runtime_stage=blocked`, + `runtime_effect=contract_only_no_runtime_enablement`, + `admin_status=not_ready`, `primary_action=keep_real_adapter_disabled`, + `proven_stage_count=13`, and guardrails that keep activation blocked, + process start disabled, health probe disabled, and `payload_traffic=none`. + No new runtime image was required; the proof ran on + `rap-node-agent:codex-service-supervisor-20260513z52`. Verification passed: + C19Z70 live smoke. Artifact: + `artifacts/c19z70-remote-workspace-real-adapter-admin-handoff-release-marker-compatibility-smoke-result.json`. +- C19Z71 Remote Workspace disabled real-adapter admin handoff package index is + complete on docker-test. The live smoke uses C19Z70 as source and emits + `rap.remote_workspace_real_adapter_admin_handoff_package_index.v1` with + `package_status=closed_contract_only`, + `package_marker=c19z71_disabled_real_adapter_admin_handoff_package_closed_contract_only`, + `covered_stage_range=C19Z54-C19Z70`, `covered_stage_count=17`, + `proven_full_chain_stage_count=13`, `latest_compatibility_stage=C19Z70`, + `real_runtime_stage=blocked`, + `runtime_effect=contract_only_no_runtime_enablement`, + `admin_status=not_ready`, and `primary_action=keep_real_adapter_disabled`. + Guardrails continue to keep activation blocked, process start disabled, + health probe disabled, and `payload_traffic=none`. No new runtime image was + required; the proof ran on + `rap-node-agent:codex-service-supervisor-20260513z52`. Verification passed: + C19Z71 live smoke. Artifact: + `artifacts/c19z71-remote-workspace-real-adapter-admin-handoff-package-index-smoke-result.json`. +- C19Z72 Remote Workspace disabled real-adapter admin handoff package index + compatibility proof is complete on docker-test. The compatibility smoke uses + C19Z71 as source and validates the package index fields, expected + contract-only values, closeout notes, `covered_stage_range=C19Z54-C19Z70`, + `covered_stage_count=17`, `proven_full_chain_stage_count=13`, + `latest_compatibility_stage=C19Z70`, `real_runtime_stage=blocked`, and + guardrails that keep activation blocked, process start disabled, health probe + disabled, and `payload_traffic=none`. No new runtime image was required; the + proof ran on `rap-node-agent:codex-service-supervisor-20260513z52`. + Verification passed: C19Z72 live smoke. Artifact: + `artifacts/c19z72-remote-workspace-real-adapter-admin-handoff-package-index-compatibility-smoke-result.json`. +- C19Z73 Remote Workspace real-adapter runtime gate phase boundary is complete + on docker-test. The live smoke uses C19Z72 as source and emits + `rap.remote_workspace_real_adapter_runtime_gate_phase_boundary.v1` to mark + the C19Z54-C19Z72 admin handoff as closed contract-only while starting the + next phase as `real_adapter_runtime_gate_preflight` with + `next_phase_status=design_only_not_enabled`, + `real_runtime_gate_state=blocked`, + `activation_policy=explicit_operator_enablement_required`, + `runtime_effect=contract_only_no_runtime_enablement`, and + `operator_default_action=keep_real_adapter_disabled`. Required preflight + steps cover explicit operator enablement, binary path validation, service + account/permissions, process supervisor limits, health probe signals, and + payload forwarding gate validation. Guardrails continue to keep activation + blocked, process start disabled, health probe disabled, and + `payload_traffic=none`. No new runtime image was required; the proof ran on + `rap-node-agent:codex-service-supervisor-20260513z52`. Verification passed: + C19Z73 live smoke. Artifact: + `artifacts/c19z73-remote-workspace-real-adapter-runtime-gate-phase-boundary-smoke-result.json`. +- C19Z74 Remote Workspace real-adapter runtime gate phase boundary + compatibility proof is complete on docker-test. The compatibility smoke uses + C19Z73 as source and validates the boundary fields, required preflight steps, + `previous_package_status=closed_contract_only`, + `next_phase_name=real_adapter_runtime_gate_preflight`, + `next_phase_status=design_only_not_enabled`, + `real_runtime_gate_state=blocked`, + `activation_policy=explicit_operator_enablement_required`, + `runtime_effect=contract_only_no_runtime_enablement`, + `operator_default_action=keep_real_adapter_disabled`, and guardrails that + keep activation blocked, process start disabled, health probe disabled, and + `payload_traffic=none`. No new runtime image was required; the proof ran on + `rap-node-agent:codex-service-supervisor-20260513z52`. Verification passed: + C19Z74 live smoke. Artifact: + `artifacts/c19z74-remote-workspace-real-adapter-runtime-gate-phase-boundary-compatibility-smoke-result.json`. +- C19Z75 Remote Workspace real-adapter runtime gate preflight checklist is + complete on docker-test. The live smoke uses C19Z74 as source and emits + `rap.remote_workspace_real_adapter_runtime_gate_preflight_checklist.v1` with + `preflight_status=blocked_required_items_missing`, + `runtime_gate_state=blocked`, + `activation_policy=explicit_operator_enablement_required`, + `operator_default_action=keep_real_adapter_disabled`, six required preflight + items, `satisfied_item_count=0`, `blocked_item_count=6`, + `allows_process_start=false`, and `allows_payload_traffic=false`. Each item + remains `status=not_satisfied`, `required=true`, + `blocks_runtime_gate=true`, with + `evidence=contract_only_preflight_not_provided`. Guardrails continue to keep + activation blocked, process start disabled, health probe disabled, and + `payload_traffic=none`. No new runtime image was required; the proof ran on + `rap-node-agent:codex-service-supervisor-20260513z52`. Verification passed: + C19Z75 live smoke. Artifact: + `artifacts/c19z75-remote-workspace-real-adapter-runtime-gate-preflight-checklist-smoke-result.json`. +- C19Z76 Remote Workspace real-adapter runtime gate preflight checklist + compatibility proof is complete on docker-test. The compatibility smoke uses + C19Z75 as source and validates checklist fields, six required item keys, + item fields, `status=not_satisfied`, `required=true`, + `blocks_runtime_gate=true`, `evidence=contract_only_preflight_not_provided`, + `preflight_status=blocked_required_items_missing`, + `runtime_gate_state=blocked`, + `activation_policy=explicit_operator_enablement_required`, + `operator_default_action=keep_real_adapter_disabled`, + `satisfied_item_count=0`, `blocked_item_count=6`, + `allows_process_start=false`, `allows_payload_traffic=false`, and guardrails + that keep activation blocked, process start disabled, health probe disabled, + and `payload_traffic=none`. No new runtime image was required; the proof ran + on `rap-node-agent:codex-service-supervisor-20260513z52`. Verification + passed: C19Z76 live smoke. Artifact: + `artifacts/c19z76-remote-workspace-real-adapter-runtime-gate-preflight-checklist-compatibility-smoke-result.json`. +- C19Z77 Remote Workspace real-adapter runtime gate preflight status summary is + complete on docker-test. The live smoke uses C19Z76 as source and emits + `rap.remote_workspace_real_adapter_runtime_gate_preflight_status_summary.v1` + with `summary_status=blocked_all_required_items_missing`, + `runtime_gate_state=blocked`, `required_item_count=6`, + `satisfied_item_count=0`, `blocked_item_count=6`, + `not_satisfied_item_count=6`, all six preflight keys listed as + `blocking_item_keys`, `operator_default_action=keep_real_adapter_disabled`, + `allows_process_start=false`, and `allows_payload_traffic=false`. + Guardrails continue to keep activation blocked, process start disabled, + health probe disabled, and `payload_traffic=none`. No new runtime image was + required; the proof ran on + `rap-node-agent:codex-service-supervisor-20260513z52`. Verification passed: + C19Z77 live smoke. Artifact: + `artifacts/c19z77-remote-workspace-real-adapter-runtime-gate-preflight-status-summary-smoke-result.json`. +- C19Z78 Remote Workspace real-adapter runtime gate preflight status summary + compatibility proof is complete on docker-test. The compatibility smoke uses + C19Z77 as source and validates summary fields, + `summary_status=blocked_all_required_items_missing`, + `runtime_gate_state=blocked`, `required_item_count=6`, + `satisfied_item_count=0`, `blocked_item_count=6`, + `not_satisfied_item_count=6`, all six preflight keys listed as + `blocking_item_keys`, `operator_default_action=keep_real_adapter_disabled`, + `allows_process_start=false`, `allows_payload_traffic=false`, and guardrails + that keep activation blocked, process start disabled, health probe disabled, + and `payload_traffic=none`. No new runtime image was required; the proof ran + on `rap-node-agent:codex-service-supervisor-20260513z52`. Verification + passed: C19Z78 live smoke. Artifact: + `artifacts/c19z78-remote-workspace-real-adapter-runtime-gate-preflight-status-summary-compatibility-smoke-result.json`. +- C19Z79 Remote Workspace real-adapter runtime gate preflight action hints are + complete on docker-test. The live smoke uses C19Z78 as source and emits + `rap.remote_workspace_real_adapter_runtime_gate_preflight_action_hints.v1` + with `hint_status=blocked_operator_preflight_actions_required`, + `runtime_gate_state=blocked`, + `operator_default_action=keep_real_adapter_disabled`, six action hints, and + `allows_process_start=false`, `allows_payload_traffic=false`. Required hints + cover explicit operator gate enablement, real adapter binary path validation, + service account/permissions validation, process supervisor limits validation, + health probe signal contract validation, and payload forwarding gate + validation. Every hint keeps `blocks_runtime_gate=true` and + `allows_runtime=false`. Guardrails continue to keep activation blocked, + process start disabled, health probe disabled, and `payload_traffic=none`. + No new runtime image was required; the proof ran on + `rap-node-agent:codex-service-supervisor-20260513z52`. Verification passed: + C19Z79 live smoke. Artifact: + `artifacts/c19z79-remote-workspace-real-adapter-runtime-gate-preflight-action-hints-smoke-result.json`. +- C19Z80 Remote Workspace real-adapter runtime gate preflight action hints + compatibility proof is complete on docker-test. The compatibility smoke uses + C19Z79 as source and validates hints fields, six hint keys, hint fields, + `hint_status=blocked_operator_preflight_actions_required`, + `runtime_gate_state=blocked`, + `operator_default_action=keep_real_adapter_disabled`, + `hint_count=6`, `allows_process_start=false`, + `allows_payload_traffic=false`, and every hint with + `blocks_runtime_gate=true` and `allows_runtime=false`. Guardrails continue to + keep activation blocked, process start disabled, health probe disabled, and + `payload_traffic=none`. No new runtime image was required; the proof ran on + `rap-node-agent:codex-service-supervisor-20260513z52`. Verification passed: + C19Z80 live smoke. Artifact: + `artifacts/c19z80-remote-workspace-real-adapter-runtime-gate-preflight-action-hints-compatibility-smoke-result.json`. +- C19Z81 Remote Workspace real-adapter runtime gate preflight operator handoff + bundle is complete on docker-test. The live smoke uses C19Z80 as source and + emits + `rap.remote_workspace_real_adapter_runtime_gate_preflight_operator_handoff_bundle.v1` + with `handoff_status=blocked_preflight_operator_review_required`, + `runtime_gate_state=blocked`, + `operator_default_action=keep_real_adapter_disabled`, checklist/status + summary/action hints schema references, `required_item_count=6`, + `blocked_item_count=6`, `hint_count=6`, handoff sections for checklist, + status summary, action hints, and guardrails, plus + `allows_process_start=false` and `allows_payload_traffic=false`. Guardrails + continue to keep activation blocked, process start disabled, health probe + disabled, and `payload_traffic=none`. No new runtime image was required; the + proof ran on `rap-node-agent:codex-service-supervisor-20260513z52`. + Verification passed: C19Z81 live smoke. Artifact: + `artifacts/c19z81-remote-workspace-real-adapter-runtime-gate-preflight-operator-handoff-bundle-smoke-result.json`. +- C19Z82 Remote Workspace real-adapter runtime gate preflight operator handoff + bundle compatibility proof is complete on docker-test. The compatibility + smoke uses C19Z81 as source and validates bundle fields, section fields, + section keys for checklist/status summary/action hints/guardrails, schema + references, `handoff_status=blocked_preflight_operator_review_required`, + `runtime_gate_state=blocked`, + `operator_default_action=keep_real_adapter_disabled`, + `required_item_count=6`, `blocked_item_count=6`, `hint_count=6`, + `allows_process_start=false`, `allows_payload_traffic=false`, and guardrails + that keep activation blocked, process start disabled, health probe disabled, + and `payload_traffic=none`. No new runtime image was required; the proof ran + on `rap-node-agent:codex-service-supervisor-20260513z52`. Verification + passed: C19Z82 live smoke. Artifact: + `artifacts/c19z82-remote-workspace-real-adapter-runtime-gate-preflight-operator-handoff-bundle-compatibility-smoke-result.json`. +- C19Z83 Remote Workspace real-adapter runtime gate preflight release marker is + complete on docker-test. The live smoke uses C19Z82 as source and emits + `rap.remote_workspace_real_adapter_runtime_gate_preflight_release_marker.v1` + with `release_status=contract_only_ready_for_operator_preflight_handoff`, + `release_marker=c19z83_disabled_real_adapter_runtime_gate_preflight_contract_only`, + `runtime_gate_state=blocked`, + `runtime_effect=contract_only_no_runtime_enablement`, + `operator_default_action=keep_real_adapter_disabled`, + `handoff_status=blocked_preflight_operator_review_required`, + `covered_stage_range=C19Z73-C19Z82`, + `allows_process_start=false`, and `allows_payload_traffic=false`. Guardrails + continue to keep activation blocked, process start disabled, health probe + disabled, and `payload_traffic=none`. No new runtime image was required; the + proof ran on `rap-node-agent:codex-service-supervisor-20260513z52`. + Verification passed: C19Z83 live smoke. Artifact: + `artifacts/c19z83-remote-workspace-real-adapter-runtime-gate-preflight-release-marker-smoke-result.json`. +- C19Z84 Remote Workspace real-adapter runtime gate preflight release marker + compatibility proof is complete on docker-test. The compatibility smoke uses + C19Z83 as source and validates release marker fields, + `release_status=contract_only_ready_for_operator_preflight_handoff`, + `release_marker=c19z83_disabled_real_adapter_runtime_gate_preflight_contract_only`, + `runtime_gate_state=blocked`, + `runtime_effect=contract_only_no_runtime_enablement`, + `operator_default_action=keep_real_adapter_disabled`, + `handoff_status=blocked_preflight_operator_review_required`, + `covered_stage_range=C19Z73-C19Z82`, + `allows_process_start=false`, `allows_payload_traffic=false`, and guardrails + that keep activation blocked, process start disabled, health probe disabled, + and `payload_traffic=none`. No new runtime image was required; the proof ran + on `rap-node-agent:codex-service-supervisor-20260513z52`. Verification + passed: C19Z84 live smoke. Artifact: + `artifacts/c19z84-remote-workspace-real-adapter-runtime-gate-preflight-release-marker-compatibility-smoke-result.json`. +- C19Z85 Remote Workspace real-adapter runtime gate preflight package index is + complete on docker-test. The live smoke uses C19Z84 as source and emits + `rap.remote_workspace_real_adapter_runtime_gate_preflight_package_index.v1` + with `package_status=closed_contract_only`, + `package_marker=c19z85_disabled_real_adapter_runtime_gate_preflight_package_closed_contract_only`, + `covered_stage_range=C19Z73-C19Z84`, `covered_stage_count=12`, + `latest_compatibility_stage=C19Z84`, `runtime_gate_state=blocked`, + `runtime_effect=contract_only_no_runtime_enablement`, + `operator_default_action=keep_real_adapter_disabled`, + `release_status=contract_only_ready_for_operator_preflight_handoff`, + `allows_process_start=false`, and `allows_payload_traffic=false`. Guardrails + continue to keep activation blocked, process start disabled, health probe + disabled, and `payload_traffic=none`. No new runtime image was required; the + proof ran on `rap-node-agent:codex-service-supervisor-20260513z52`. + Verification passed: C19Z85 live smoke. Artifact: + `artifacts/c19z85-remote-workspace-real-adapter-runtime-gate-preflight-package-index-smoke-result.json`. +- C19Z86 Remote Workspace real-adapter runtime gate preflight package index + compatibility proof is complete on docker-test. The compatibility smoke uses + C19Z85 as source and validates package index fields, + `package_status=closed_contract_only`, + `package_marker=c19z85_disabled_real_adapter_runtime_gate_preflight_package_closed_contract_only`, + `covered_stage_range=C19Z73-C19Z84`, `covered_stage_count=12`, + `latest_compatibility_stage=C19Z84`, `runtime_gate_state=blocked`, + `runtime_effect=contract_only_no_runtime_enablement`, + `operator_default_action=keep_real_adapter_disabled`, + `release_status=contract_only_ready_for_operator_preflight_handoff`, + `allows_process_start=false`, `allows_payload_traffic=false`, and guardrails + that keep activation blocked, process start disabled, health probe disabled, + and `payload_traffic=none`. No new runtime image was required; the proof ran + on `rap-node-agent:codex-service-supervisor-20260513z52`. Verification + passed: C19Z86 live smoke. Artifact: + `artifacts/c19z86-remote-workspace-real-adapter-runtime-gate-preflight-package-index-compatibility-smoke-result.json`. +- C19Z87 Remote Workspace real-adapter runtime gate preflight closeout summary + is complete on docker-test. The live smoke uses C19Z86 as source and emits + `rap.remote_workspace_real_adapter_runtime_gate_preflight_closeout_summary.v1` + with `closeout_status=closed_contract_only_preflight_complete`, + `closeout_marker=c19z87_disabled_real_adapter_runtime_gate_preflight_closed_contract_only`, + `covered_stage_range=C19Z73-C19Z86`, `covered_stage_count=14`, + `runtime_gate_state=blocked`, + `runtime_effect=contract_only_no_runtime_enablement`, + `operator_default_action=keep_real_adapter_disabled`, + `next_required_phase=explicit_real_runtime_gate_enablement`, + `allows_process_start=false`, and `allows_payload_traffic=false`. Guardrails + continue to keep activation blocked, process start disabled, health probe + disabled, and `payload_traffic=none`. No new runtime image was required; the + proof ran on `rap-node-agent:codex-service-supervisor-20260513z52`. + Verification passed: C19Z87 live smoke. Artifact: + `artifacts/c19z87-remote-workspace-real-adapter-runtime-gate-preflight-closeout-summary-smoke-result.json`. +- C19Z88 Remote Workspace real-adapter runtime gate preflight closeout summary + compatibility proof is complete on docker-test. The compatibility smoke uses + C19Z87 as source and validates closeout fields, + `closeout_status=closed_contract_only_preflight_complete`, + `closeout_marker=c19z87_disabled_real_adapter_runtime_gate_preflight_closed_contract_only`, + `covered_stage_range=C19Z73-C19Z86`, `covered_stage_count=14`, + `runtime_gate_state=blocked`, + `runtime_effect=contract_only_no_runtime_enablement`, + `operator_default_action=keep_real_adapter_disabled`, + `next_required_phase=explicit_real_runtime_gate_enablement`, + `allows_process_start=false`, `allows_payload_traffic=false`, and guardrails + that keep activation blocked, process start disabled, health probe disabled, + and `payload_traffic=none`. No new runtime image was required; the proof ran + on `rap-node-agent:codex-service-supervisor-20260513z52`. Verification + passed: C19Z88 live smoke. Artifact: + `artifacts/c19z88-remote-workspace-real-adapter-runtime-gate-preflight-closeout-summary-compatibility-smoke-result.json`. +- C19Z89 Remote Workspace real-adapter runtime gate explicit enablement request + is complete on docker-test. The live smoke uses C19Z88 as source and emits + `rap.remote_workspace_real_adapter_runtime_gate_explicit_enablement_request.v1` + with `request_status=pending_required_validations`, + `request_marker=c19z89_real_adapter_runtime_gate_explicit_enablement_request_contract_only`, + `requested_phase=explicit_real_runtime_gate_enablement`, + `runtime_gate_state=blocked_pending_validation`, + `runtime_effect=contract_only_no_runtime_enablement`, + `operator_default_action=keep_real_adapter_disabled`, required validation + booleans for operator confirmation, binary, permissions, supervisor, health + probe, and payload gate, plus `allows_process_start=false` and + `allows_payload_traffic=false`. Guardrails continue to keep activation + blocked, process start disabled, health probe disabled, and + `payload_traffic=none`. No new runtime image was required; the proof ran on + `rap-node-agent:codex-service-supervisor-20260513z52`. Verification passed: + C19Z89 live smoke. Artifact: + `artifacts/c19z89-remote-workspace-real-adapter-runtime-gate-explicit-enablement-request-smoke-result.json`. +- C19Z90 Remote Workspace real-adapter runtime gate explicit enablement request + compatibility proof is complete on docker-test. The compatibility smoke uses + C19Z89 as source and validates request fields, + `request_status=pending_required_validations`, + `request_marker=c19z89_real_adapter_runtime_gate_explicit_enablement_request_contract_only`, + `requested_phase=explicit_real_runtime_gate_enablement`, + `runtime_gate_state=blocked_pending_validation`, + `runtime_effect=contract_only_no_runtime_enablement`, + `operator_default_action=keep_real_adapter_disabled`, all six required + validation booleans, `allows_process_start=false`, + `allows_payload_traffic=false`, and guardrails that keep activation blocked, + process start disabled, health probe disabled, and `payload_traffic=none`. + No new runtime image was required; the proof ran on + `rap-node-agent:codex-service-supervisor-20260513z52`. Verification passed: + C19Z90 live smoke. Artifact: + `artifacts/c19z90-remote-workspace-real-adapter-runtime-gate-explicit-enablement-request-compatibility-smoke-result.json`. +- C19Z91 Remote Workspace real-adapter runtime gate operator confirmation + validation is complete on docker-test. The live smoke uses C19Z90 as source + and emits + `rap.remote_workspace_real_adapter_runtime_gate_operator_confirmation_validation.v1` + with `validation_key=operator_confirmation`, + `validation_status=satisfied_contract_only`, + `operator_confirmation_required=true`, + `operator_confirmation_present=true`, + `runtime_gate_state=blocked_pending_remaining_validations`, + `runtime_effect=contract_only_no_runtime_enablement`, + `operator_default_action=keep_real_adapter_disabled`, remaining required + validations for binary, permissions, supervisor, health probe, and payload + gate, plus `allows_process_start=false` and + `allows_payload_traffic=false`. Guardrails continue to keep activation + blocked, process start disabled, health probe disabled, and + `payload_traffic=none`. No new runtime image was required; the proof ran on + `rap-node-agent:codex-service-supervisor-20260513z52`. Verification passed: + C19Z91 live smoke. Artifact: + `artifacts/c19z91-remote-workspace-real-adapter-runtime-gate-operator-confirmation-validation-smoke-result.json`. +- C19Z92 Remote Workspace real-adapter runtime gate operator confirmation + validation compatibility proof is complete on docker-test. The compatibility + smoke uses C19Z91 as source and validates validation fields, + `validation_key=operator_confirmation`, + `validation_status=satisfied_contract_only`, + `operator_confirmation_required=true`, + `operator_confirmation_present=true`, + `runtime_gate_state=blocked_pending_remaining_validations`, + `runtime_effect=contract_only_no_runtime_enablement`, + `operator_default_action=keep_real_adapter_disabled`, remaining validations + for binary, permissions, supervisor, health probe, and payload gate, + `allows_process_start=false`, `allows_payload_traffic=false`, and guardrails + that keep activation blocked, process start disabled, health probe disabled, + and `payload_traffic=none`. No new runtime image was required; the proof ran + on `rap-node-agent:codex-service-supervisor-20260513z52`. Verification + passed: C19Z92 live smoke. Artifact: + `artifacts/c19z92-remote-workspace-real-adapter-runtime-gate-operator-confirmation-validation-compatibility-smoke-result.json`. +- C19Z93 Remote Workspace real-adapter runtime gate binary validation is + complete on docker-test. The live smoke uses C19Z92 as source and emits + `rap.remote_workspace_real_adapter_runtime_gate_binary_validation.v1` with + `validation_key=binary_validation`, + `validation_status=satisfied_contract_only`, + `binary_validation_required=true`, `binary_path_present=true`, + `binary_identity_verified=true`, + `runtime_gate_state=blocked_pending_remaining_validations`, + `runtime_effect=contract_only_no_runtime_enablement`, + `operator_default_action=keep_real_adapter_disabled`, remaining validations + for permissions, supervisor, health probe, and payload gate, + `allows_process_start=false`, `allows_payload_traffic=false`, and guardrails + that keep activation blocked, process start disabled, health probe disabled, + and `payload_traffic=none`. No new runtime image was required; the proof ran + on `rap-node-agent:codex-service-supervisor-20260513z52`. Verification + passed: C19Z93 live smoke. Artifact: + `artifacts/c19z93-remote-workspace-real-adapter-runtime-gate-binary-validation-smoke-result.json`. +- C19Z94 Remote Workspace real-adapter runtime gate binary validation + compatibility proof is complete on docker-test. The compatibility smoke uses + C19Z93 as source and validates validation fields, + `validation_key=binary_validation`, + `validation_status=satisfied_contract_only`, + `binary_validation_required=true`, `binary_path_present=true`, + `binary_identity_verified=true`, + `runtime_gate_state=blocked_pending_remaining_validations`, + `runtime_effect=contract_only_no_runtime_enablement`, + `operator_default_action=keep_real_adapter_disabled`, remaining validations + for permissions, supervisor, health probe, and payload gate, + `allows_process_start=false`, `allows_payload_traffic=false`, and guardrails + that keep activation blocked, process start disabled, health probe disabled, + and `payload_traffic=none`. No new runtime image was required; the proof ran + on `rap-node-agent:codex-service-supervisor-20260513z52`. Verification + passed: C19Z94 live smoke. Artifact: + `artifacts/c19z94-remote-workspace-real-adapter-runtime-gate-binary-validation-compatibility-smoke-result.json`. +- C19Z95 Remote Workspace real-adapter runtime gate permission validation is + complete on docker-test. The live smoke uses C19Z94 as source and emits + `rap.remote_workspace_real_adapter_runtime_gate_permission_validation.v1` + with `validation_key=permission_validation`, + `validation_status=satisfied_contract_only`, + `permission_validation_required=true`, `service_account_present=true`, + `least_privilege_scope_verified=true`, + `runtime_gate_state=blocked_pending_remaining_validations`, + `runtime_effect=contract_only_no_runtime_enablement`, + `operator_default_action=keep_real_adapter_disabled`, remaining validations + for supervisor, health probe, and payload gate, `allows_process_start=false`, + `allows_payload_traffic=false`, and guardrails that keep activation blocked, + process start disabled, health probe disabled, and `payload_traffic=none`. + No new runtime image was required; the proof ran on + `rap-node-agent:codex-service-supervisor-20260513z52`. Verification passed: + C19Z95 live smoke. Artifact: + `artifacts/c19z95-remote-workspace-real-adapter-runtime-gate-permission-validation-smoke-result.json`. +- C19Z96 Remote Workspace real-adapter runtime gate permission validation + compatibility proof is complete on docker-test. The compatibility smoke uses + C19Z95 as source and validates validation fields, + `validation_key=permission_validation`, + `validation_status=satisfied_contract_only`, + `permission_validation_required=true`, `service_account_present=true`, + `least_privilege_scope_verified=true`, + `runtime_gate_state=blocked_pending_remaining_validations`, + `runtime_effect=contract_only_no_runtime_enablement`, + `operator_default_action=keep_real_adapter_disabled`, remaining validations + for supervisor, health probe, and payload gate, `allows_process_start=false`, + `allows_payload_traffic=false`, and guardrails that keep activation blocked, + process start disabled, health probe disabled, and `payload_traffic=none`. + No new runtime image was required; the proof ran on + `rap-node-agent:codex-service-supervisor-20260513z52`. Verification passed: + C19Z96 live smoke. Artifact: + `artifacts/c19z96-remote-workspace-real-adapter-runtime-gate-permission-validation-compatibility-smoke-result.json`. +- C19Z97 Remote Workspace real-adapter runtime gate supervisor validation is + complete on docker-test. The live smoke uses C19Z96 as source and emits + `rap.remote_workspace_real_adapter_runtime_gate_supervisor_validation.v1` + with `validation_key=supervisor_validation`, + `validation_status=satisfied_contract_only`, + `supervisor_validation_required=true`, `process_limits_verified=true`, + `restart_policy_verified=true`, + `runtime_gate_state=blocked_pending_remaining_validations`, + `runtime_effect=contract_only_no_runtime_enablement`, + `operator_default_action=keep_real_adapter_disabled`, remaining validations + for health probe and payload gate, `allows_process_start=false`, + `allows_payload_traffic=false`, and guardrails that keep activation blocked, + process start disabled, health probe disabled, and `payload_traffic=none`. + No new runtime image was required; the proof ran on + `rap-node-agent:codex-service-supervisor-20260513z52`. Verification passed: + C19Z97 live smoke. Artifact: + `artifacts/c19z97-remote-workspace-real-adapter-runtime-gate-supervisor-validation-smoke-result.json`. +- C19Z98 Remote Workspace real-adapter runtime gate supervisor validation + compatibility proof is complete on docker-test. The compatibility smoke uses + C19Z97 as source and validates validation fields, + `validation_key=supervisor_validation`, + `validation_status=satisfied_contract_only`, + `supervisor_validation_required=true`, `process_limits_verified=true`, + `restart_policy_verified=true`, remaining validations for health probe and + payload gate, `runtime_gate_state=blocked_pending_remaining_validations`, + `runtime_effect=contract_only_no_runtime_enablement`, + `operator_default_action=keep_real_adapter_disabled`, + `allows_process_start=false`, `allows_payload_traffic=false`, and guardrails + that keep activation blocked, process start disabled, health probe disabled, + and `payload_traffic=none`. No new runtime image was required; the proof ran + on `rap-node-agent:codex-service-supervisor-20260513z52`. Verification + passed: C19Z98 live smoke. Artifact: + `artifacts/c19z98-remote-workspace-real-adapter-runtime-gate-supervisor-validation-compatibility-smoke-result.json`. +- C19Z99 Remote Workspace real-adapter runtime gate health probe validation is + complete on docker-test. The live smoke uses C19Z98 as source and emits + `rap.remote_workspace_real_adapter_runtime_gate_health_probe_validation.v1` + with `validation_key=health_probe_validation`, + `validation_status=satisfied_contract_only`, + `health_probe_validation_required=true`, + `health_probe_contract_verified=true`, `failure_detection_verified=true`, + `runtime_gate_state=blocked_pending_remaining_validations`, + `runtime_effect=contract_only_no_runtime_enablement`, + `operator_default_action=keep_real_adapter_disabled`, remaining validation + `payload_gate_validation`, `allows_process_start=false`, + `allows_payload_traffic=false`, and guardrails that keep activation blocked, + process start disabled, health probe disabled, and `payload_traffic=none`. + No new runtime image was required; the proof ran on + `rap-node-agent:codex-service-supervisor-20260513z52`. Verification passed: + C19Z99 live smoke. Artifact: + `artifacts/c19z99-remote-workspace-real-adapter-runtime-gate-health-probe-validation-smoke-result.json`. +- C19Z100 Remote Workspace real-adapter runtime gate health probe validation + compatibility proof is complete on docker-test. The compatibility smoke uses + C19Z99 as source and validates validation fields, + `validation_key=health_probe_validation`, + `validation_status=satisfied_contract_only`, + `health_probe_validation_required=true`, + `health_probe_contract_verified=true`, `failure_detection_verified=true`, + remaining validation `payload_gate_validation`, + `runtime_gate_state=blocked_pending_remaining_validations`, + `runtime_effect=contract_only_no_runtime_enablement`, + `operator_default_action=keep_real_adapter_disabled`, + `allows_process_start=false`, `allows_payload_traffic=false`, and guardrails + that keep activation blocked, process start disabled, health probe disabled, + and `payload_traffic=none`. No new runtime image was required; the proof ran + on `rap-node-agent:codex-service-supervisor-20260513z52`. Verification + passed: C19Z100 live smoke. Artifact: + `artifacts/c19z100-remote-workspace-real-adapter-runtime-gate-health-probe-validation-compatibility-smoke-result.json`. +- C19Z101 Remote Workspace real-adapter runtime gate payload gate validation is + complete on docker-test. The live smoke uses C19Z100 as source and emits + `rap.remote_workspace_real_adapter_runtime_gate_payload_gate_validation.v1` + with `validation_key=payload_gate_validation`, + `validation_status=satisfied_contract_only`, + `payload_gate_validation_required=true`, `payload_policy_verified=true`, + `payload_isolation_verified=true`, no remaining required validations, + `runtime_gate_state=validated_contract_only_not_enabled`, + `runtime_effect=contract_only_no_runtime_enablement`, + `operator_default_action=keep_real_adapter_disabled`, + `allows_process_start=false`, `allows_payload_traffic=false`, and guardrails + that keep activation blocked, process start disabled, health probe disabled, + and `payload_traffic=none`. No new runtime image was required; the proof ran + on `rap-node-agent:codex-service-supervisor-20260513z52`. Verification + passed: C19Z101 live smoke. Artifact: + `artifacts/c19z101-remote-workspace-real-adapter-runtime-gate-payload-gate-validation-smoke-result.json`. +- C19Z102 Remote Workspace real-adapter runtime gate payload gate validation + compatibility proof is complete on docker-test. The compatibility smoke uses + C19Z101 as source and validates validation fields, + `validation_key=payload_gate_validation`, + `validation_status=satisfied_contract_only`, + `payload_gate_validation_required=true`, `payload_policy_verified=true`, + `payload_isolation_verified=true`, no remaining required validations, + `runtime_gate_state=validated_contract_only_not_enabled`, + `runtime_effect=contract_only_no_runtime_enablement`, + `operator_default_action=keep_real_adapter_disabled`, + `allows_process_start=false`, `allows_payload_traffic=false`, and guardrails + that keep activation blocked, process start disabled, health probe disabled, + and `payload_traffic=none`. No new runtime image was required; the proof ran + on `rap-node-agent:codex-service-supervisor-20260513z52`. Verification + passed: C19Z102 live smoke. Artifact: + `artifacts/c19z102-remote-workspace-real-adapter-runtime-gate-payload-gate-validation-compatibility-smoke-result.json`. +- C19Z103 Remote Workspace real-adapter runtime gate validation closeout is + complete on docker-test. The live smoke uses C19Z102 as source and emits + `rap.remote_workspace_real_adapter_runtime_gate_validation_closeout.v1` with + `validation_chain_status=complete_contract_only`, + `enablement_boundary=explicit_operator_enablement_required`, + `enablement_status=not_enabled`, all required validations listed, no + remaining required validations, + `runtime_gate_state=validated_contract_only_not_enabled`, + `runtime_effect=contract_only_no_runtime_enablement`, + `operator_default_action=keep_real_adapter_disabled`, + `allows_process_start=false`, `allows_payload_traffic=false`, and guardrails + that keep activation blocked, process start disabled, health probe disabled, + and `payload_traffic=none`. No new runtime image was required; the proof ran + on `rap-node-agent:codex-service-supervisor-20260513z52`. Verification + passed: C19Z103 live smoke. Artifact: + `artifacts/c19z103-remote-workspace-real-adapter-runtime-gate-validation-closeout-smoke-result.json`. +- C19Z104 Remote Workspace real-adapter runtime gate validation closeout + compatibility proof is complete on docker-test. The compatibility smoke uses + C19Z103 as source and validates closeout fields, all required validations, + no remaining required validations, + `validation_chain_status=complete_contract_only`, + `enablement_boundary=explicit_operator_enablement_required`, + `enablement_status=not_enabled`, + `runtime_gate_state=validated_contract_only_not_enabled`, + `runtime_effect=contract_only_no_runtime_enablement`, + `operator_default_action=keep_real_adapter_disabled`, + `allows_process_start=false`, `allows_payload_traffic=false`, and guardrails + that keep activation blocked, process start disabled, health probe disabled, + and `payload_traffic=none`. No new runtime image was required; the proof ran + on `rap-node-agent:codex-service-supervisor-20260513z52`. Verification + passed: C19Z104 live smoke. Artifact: + `artifacts/c19z104-remote-workspace-real-adapter-runtime-gate-validation-closeout-compatibility-smoke-result.json`. +- C19Z105 Remote Workspace real-adapter operator enablement readiness package + is complete on docker-test. The live smoke uses C19Z104 as source and emits + `rap.remote_workspace_real_adapter_operator_enablement_readiness_package.v1` + with `package_status=ready_for_operator_review`, + `operator_review_status=not_reviewed`, + `enablement_boundary=explicit_operator_enablement_required`, + `enablement_status=not_enabled`, all validation/closeout contracts included, + required operator actions for review, real-runtime intent confirmation, + target selection, process start approval, and payload traffic approval, + `runtime_gate_state=validated_contract_only_not_enabled`, + `runtime_effect=contract_only_no_runtime_enablement`, + `operator_default_action=keep_real_adapter_disabled`, + `allows_process_start=false`, `allows_payload_traffic=false`, and guardrails + that keep activation blocked, process start disabled, health probe disabled, + and `payload_traffic=none`. No new runtime image was required; the proof ran + on `rap-node-agent:codex-service-supervisor-20260513z52`. Verification + passed: C19Z105 live smoke. Artifact: + `artifacts/c19z105-remote-workspace-real-adapter-operator-enablement-readiness-package-smoke-result.json`. +- C19Z106 Remote Workspace real-adapter operator enablement readiness package + compatibility proof is complete on docker-test. The compatibility smoke uses + C19Z105 as source and validates package fields, included contracts, required + operator actions, `package_status=ready_for_operator_review`, + `operator_review_status=not_reviewed`, + `enablement_boundary=explicit_operator_enablement_required`, + `enablement_status=not_enabled`, + `runtime_gate_state=validated_contract_only_not_enabled`, + `runtime_effect=contract_only_no_runtime_enablement`, + `operator_default_action=keep_real_adapter_disabled`, + `allows_process_start=false`, `allows_payload_traffic=false`, and guardrails + that keep activation blocked, process start disabled, health probe disabled, + and `payload_traffic=none`. No new runtime image was required; the proof ran + on `rap-node-agent:codex-service-supervisor-20260513z52`. Verification + passed: C19Z106 live smoke. Artifact: + `artifacts/c19z106-remote-workspace-real-adapter-operator-enablement-readiness-package-compatibility-smoke-result.json`. +- C19Z107 Remote Workspace real-adapter operator enablement readiness release + marker is complete on docker-test. The live smoke uses C19Z106 as source and + emits + `rap.remote_workspace_real_adapter_operator_enablement_readiness_release_marker.v1` + with `release_status=operator_readiness_package_contract_only`, + `release_marker=c19z107_real_adapter_operator_enablement_readiness_contract_only`, + `operator_review_status=not_reviewed`, + `enablement_boundary=explicit_operator_enablement_required`, + `enablement_status=not_enabled`, + `runtime_gate_state=validated_contract_only_not_enabled`, + `runtime_effect=contract_only_no_runtime_enablement`, + `operator_default_action=keep_real_adapter_disabled`, + `allows_process_start=false`, `allows_payload_traffic=false`, and guardrails + that keep activation blocked, process start disabled, health probe disabled, + and `payload_traffic=none`. No new runtime image was required; the proof ran + on `rap-node-agent:codex-service-supervisor-20260513z52`. Verification + passed: C19Z107 live smoke. Artifact: + `artifacts/c19z107-remote-workspace-real-adapter-operator-enablement-readiness-release-marker-smoke-result.json`. +- C19Z108 Remote Workspace real-adapter operator enablement readiness release + marker compatibility proof is complete on docker-test. The compatibility + smoke uses C19Z107 as source and validates release marker fields, + `release_status=operator_readiness_package_contract_only`, + `release_marker=c19z107_real_adapter_operator_enablement_readiness_contract_only`, + `operator_review_status=not_reviewed`, + `enablement_boundary=explicit_operator_enablement_required`, + `enablement_status=not_enabled`, + `runtime_gate_state=validated_contract_only_not_enabled`, + `runtime_effect=contract_only_no_runtime_enablement`, + `operator_default_action=keep_real_adapter_disabled`, + `allows_process_start=false`, `allows_payload_traffic=false`, and guardrails + that keep activation blocked, process start disabled, health probe disabled, + and `payload_traffic=none`. No new runtime image was required; the proof ran + on `rap-node-agent:codex-service-supervisor-20260513z52`. Verification + passed: C19Z108 live smoke. Artifact: + `artifacts/c19z108-remote-workspace-real-adapter-operator-enablement-readiness-release-marker-compatibility-smoke-result.json`. +- C19Z109 Remote Workspace real-adapter operator enablement readiness package + index is complete on docker-test. The live smoke uses C19Z108 as source and + emits + `rap.remote_workspace_real_adapter_operator_enablement_readiness_package_index.v1` + with `package_status=indexed_contract_only`, + `package_marker=c19z109_real_adapter_operator_enablement_readiness_package_index_contract_only`, + `covered_stage_range=C19Z89-C19Z108`, `covered_stage_count=20`, + `latest_compatibility_stage=C19Z108`, + `operator_review_status=not_reviewed`, + `enablement_boundary=explicit_operator_enablement_required`, + `enablement_status=not_enabled`, + `runtime_gate_state=validated_contract_only_not_enabled`, + `runtime_effect=contract_only_no_runtime_enablement`, + `operator_default_action=keep_real_adapter_disabled`, + `allows_process_start=false`, `allows_payload_traffic=false`, and guardrails + that keep activation blocked, process start disabled, health probe disabled, + and `payload_traffic=none`. No new runtime image was required; the proof ran + on `rap-node-agent:codex-service-supervisor-20260513z52`. Verification + passed: C19Z109 live smoke. Artifact: + `artifacts/c19z109-remote-workspace-real-adapter-operator-enablement-readiness-package-index-smoke-result.json`. +- C19Z110 Remote Workspace real-adapter operator enablement readiness package + index compatibility proof is complete on docker-test. The compatibility + smoke uses C19Z109 as source and validates package index fields, closeout + notes, `covered_stage_range=C19Z89-C19Z108`, `covered_stage_count=20`, + `latest_compatibility_stage=C19Z108`, + `operator_review_status=not_reviewed`, + `enablement_boundary=explicit_operator_enablement_required`, + `enablement_status=not_enabled`, + `runtime_gate_state=validated_contract_only_not_enabled`, + `runtime_effect=contract_only_no_runtime_enablement`, + `operator_default_action=keep_real_adapter_disabled`, + `allows_process_start=false`, `allows_payload_traffic=false`, and guardrails + that keep activation blocked, process start disabled, health probe disabled, + and `payload_traffic=none`. No new runtime image was required; the proof ran + on `rap-node-agent:codex-service-supervisor-20260513z52`. Verification + passed: C19Z110 live smoke. Artifact: + `artifacts/c19z110-remote-workspace-real-adapter-operator-enablement-readiness-package-index-compatibility-smoke-result.json`. +- C19Z111 Remote Workspace real-adapter operator readiness closeout summary is + complete on docker-test. The live smoke uses C19Z110 as source and emits + `rap.remote_workspace_real_adapter_operator_readiness_closeout_summary.v1` + with `closeout_status=closed_contract_only_ready_for_operator_review`, + `closeout_marker=c19z111_real_adapter_operator_readiness_closed_contract_only`, + `covered_stage_range=C19Z89-C19Z108`, `covered_stage_count=20`, + `latest_compatibility_stage=C19Z108`, + `operator_review_status=not_reviewed`, + `enablement_boundary=explicit_operator_enablement_required`, + `enablement_status=not_enabled`, + `runtime_gate_state=validated_contract_only_not_enabled`, + `runtime_effect=contract_only_no_runtime_enablement`, + `operator_default_action=keep_real_adapter_disabled`, + `next_required_phase=explicit_operator_review_and_enablement_decision`, + `allows_process_start=false`, `allows_payload_traffic=false`, and guardrails + that keep activation blocked, process start disabled, health probe disabled, + and `payload_traffic=none`. No new runtime image was required; the proof ran + on `rap-node-agent:codex-service-supervisor-20260513z52`. Verification + passed: C19Z111 live smoke. Artifact: + `artifacts/c19z111-remote-workspace-real-adapter-operator-readiness-closeout-summary-smoke-result.json`. +- C19Z112 Remote Workspace real-adapter operator readiness closeout summary + compatibility proof is complete on docker-test. The compatibility smoke uses + C19Z111 as source and validates closeout summary fields, + `closeout_status=closed_contract_only_ready_for_operator_review`, + `covered_stage_range=C19Z89-C19Z108`, `covered_stage_count=20`, + `latest_compatibility_stage=C19Z108`, + `operator_review_status=not_reviewed`, + `enablement_boundary=explicit_operator_enablement_required`, + `enablement_status=not_enabled`, + `runtime_gate_state=validated_contract_only_not_enabled`, + `runtime_effect=contract_only_no_runtime_enablement`, + `operator_default_action=keep_real_adapter_disabled`, + `next_required_phase=explicit_operator_review_and_enablement_decision`, + `allows_process_start=false`, `allows_payload_traffic=false`, and guardrails + that keep activation blocked, process start disabled, health probe disabled, + and `payload_traffic=none`. No new runtime image was required; the proof ran + on `rap-node-agent:codex-service-supervisor-20260513z52`. Verification + passed: C19Z112 live smoke. Artifact: + `artifacts/c19z112-remote-workspace-real-adapter-operator-readiness-closeout-summary-compatibility-smoke-result.json`. +- C19Z113 Remote Workspace real-adapter operator review decision request is + complete on docker-test. The live smoke uses C19Z112 as source and emits + `rap.remote_workspace_real_adapter_operator_review_decision_request.v1` with + `review_request_status=pending_operator_decision`, + `review_request_marker=c19z113_real_adapter_operator_review_decision_request_contract_only`, + `requested_decision=review_real_runtime_enablement`, + `enablement_decision=not_approved`, `operator_review_status=pending`, + decision prerequisites for closeout review, real-runtime intent confirmation, + target selection, process start approval, and payload traffic approval, + `enablement_boundary=explicit_operator_enablement_required`, + `enablement_status=not_enabled`, + `runtime_gate_state=validated_contract_only_not_enabled`, + `runtime_effect=contract_only_no_runtime_enablement`, + `operator_default_action=keep_real_adapter_disabled`, + `allows_process_start=false`, `allows_payload_traffic=false`, and guardrails + that keep activation blocked, process start disabled, health probe disabled, + and `payload_traffic=none`. No new runtime image was required; the proof ran + on `rap-node-agent:codex-service-supervisor-20260513z52`. Verification + passed: C19Z113 live smoke. Artifact: + `artifacts/c19z113-remote-workspace-real-adapter-operator-review-decision-request-smoke-result.json`. +- C19Z114 Remote Workspace real-adapter operator review decision request + compatibility proof is complete on docker-test. The compatibility smoke uses + C19Z113 as source and validates request fields, decision prerequisites, + `review_request_status=pending_operator_decision`, + `requested_decision=review_real_runtime_enablement`, + `enablement_decision=not_approved`, `operator_review_status=pending`, + `enablement_boundary=explicit_operator_enablement_required`, + `enablement_status=not_enabled`, + `runtime_gate_state=validated_contract_only_not_enabled`, + `runtime_effect=contract_only_no_runtime_enablement`, + `operator_default_action=keep_real_adapter_disabled`, + `allows_process_start=false`, `allows_payload_traffic=false`, and guardrails + that keep activation blocked, process start disabled, health probe disabled, + and `payload_traffic=none`. No new runtime image was required; the proof ran + on `rap-node-agent:codex-service-supervisor-20260513z52`. Verification + passed: C19Z114 live smoke. Artifact: + `artifacts/c19z114-remote-workspace-real-adapter-operator-review-decision-request-compatibility-smoke-result.json`. +- C19Z115 Remote Workspace real-adapter operator decision status summary is + complete on docker-test. The live smoke uses C19Z114 as source and emits + `rap.remote_workspace_real_adapter_operator_decision_status_summary.v1` with + `decision_status=pending_not_approved`, + `decision_summary_marker=c19z115_real_adapter_operator_decision_status_pending_contract_only`, + `requested_decision=review_real_runtime_enablement`, + `enablement_decision=not_approved`, `operator_review_status=pending`, + `enablement_boundary=explicit_operator_enablement_required`, + `enablement_status=not_enabled`, + `runtime_gate_state=validated_contract_only_not_enabled`, + `runtime_effect=contract_only_no_runtime_enablement`, + `operator_default_action=keep_real_adapter_disabled`, + `next_required_phase=explicit_operator_approval_or_rejection`, + `allows_process_start=false`, `allows_payload_traffic=false`, and guardrails + that keep activation blocked, process start disabled, health probe disabled, + and `payload_traffic=none`. No new runtime image was required; the proof ran + on `rap-node-agent:codex-service-supervisor-20260513z52`. Verification + passed: C19Z115 live smoke. Artifact: + `artifacts/c19z115-remote-workspace-real-adapter-operator-decision-status-summary-smoke-result.json`. +- C19Z116 Remote Workspace real-adapter operator decision status summary + compatibility proof is complete on docker-test. The compatibility smoke uses + C19Z115 as source and validates summary fields, + `decision_status=pending_not_approved`, + `requested_decision=review_real_runtime_enablement`, + `enablement_decision=not_approved`, `operator_review_status=pending`, + `enablement_boundary=explicit_operator_enablement_required`, + `enablement_status=not_enabled`, + `runtime_gate_state=validated_contract_only_not_enabled`, + `runtime_effect=contract_only_no_runtime_enablement`, + `operator_default_action=keep_real_adapter_disabled`, + `next_required_phase=explicit_operator_approval_or_rejection`, + `allows_process_start=false`, `allows_payload_traffic=false`, and guardrails + that keep activation blocked, process start disabled, health probe disabled, + and `payload_traffic=none`. No new runtime image was required; the proof ran + on `rap-node-agent:codex-service-supervisor-20260513z52`. Verification + passed: C19Z116 live smoke. Artifact: + `artifacts/c19z116-remote-workspace-real-adapter-operator-decision-status-summary-compatibility-smoke-result.json`. +- C19Z117 Remote Workspace real-adapter operator approval/rejection outcome is + complete on docker-test. The live smoke uses C19Z116 as source and emits + `rap.remote_workspace_real_adapter_operator_approval_rejection_outcome.v1` + with `outcome_status=rejected_or_not_approved_contract_only`, + `outcome_marker=c19z117_real_adapter_operator_outcome_not_approved_contract_only`, + `requested_decision=review_real_runtime_enablement`, + `enablement_decision=not_approved`, + `operator_review_status=closed_without_approval`, + `enablement_boundary=explicit_operator_enablement_required`, + `enablement_status=not_enabled`, + `runtime_gate_state=validated_contract_only_not_enabled`, + `runtime_effect=contract_only_no_runtime_enablement`, + `operator_default_action=keep_real_adapter_disabled`, + `next_required_phase=explicit_operator_reopen_or_new_enablement_request`, + `allows_process_start=false`, `allows_payload_traffic=false`, and guardrails + that keep activation blocked, process start disabled, health probe disabled, + and `payload_traffic=none`. No new runtime image was required; the proof ran + on `rap-node-agent:codex-service-supervisor-20260513z52`. Verification + passed: C19Z117 live smoke. Artifact: + `artifacts/c19z117-remote-workspace-real-adapter-operator-approval-rejection-outcome-smoke-result.json`. +- C19Z118 Remote Workspace real-adapter operator approval/rejection outcome + compatibility proof is complete on docker-test. The compatibility smoke uses + C19Z117 as source and validates outcome fields, + `outcome_status=rejected_or_not_approved_contract_only`, + `requested_decision=review_real_runtime_enablement`, + `enablement_decision=not_approved`, + `operator_review_status=closed_without_approval`, + `enablement_boundary=explicit_operator_enablement_required`, + `enablement_status=not_enabled`, + `runtime_gate_state=validated_contract_only_not_enabled`, + `runtime_effect=contract_only_no_runtime_enablement`, + `operator_default_action=keep_real_adapter_disabled`, + `next_required_phase=explicit_operator_reopen_or_new_enablement_request`, + `allows_process_start=false`, `allows_payload_traffic=false`, and guardrails + that keep activation blocked, process start disabled, health probe disabled, + and `payload_traffic=none`. No new runtime image was required; the proof ran + on `rap-node-agent:codex-service-supervisor-20260513z52`. Verification + passed: C19Z118 live smoke. Artifact: + `artifacts/c19z118-remote-workspace-real-adapter-operator-approval-rejection-outcome-compatibility-smoke-result.json`. +- C19Z119 Remote Workspace real-adapter operator outcome closeout/reopen + boundary is complete on docker-test. The live smoke uses C19Z118 as source + and emits + `rap.remote_workspace_real_adapter_operator_outcome_closeout_reopen_boundary.v1` + with `boundary_status=closed_not_approved_reopen_required`, + `boundary_marker=c19z119_real_adapter_operator_outcome_closeout_reopen_required`, + `closed_outcome_status=rejected_or_not_approved_contract_only`, + `reopen_policy=new_explicit_enablement_request_required`, + `next_required_phase=explicit_operator_reopen_or_new_enablement_request`, + `enablement_decision=not_approved`, + `operator_review_status=closed_without_approval`, + `enablement_boundary=explicit_operator_enablement_required`, + `enablement_status=not_enabled`, + `runtime_gate_state=validated_contract_only_not_enabled`, + `runtime_effect=contract_only_no_runtime_enablement`, + `operator_default_action=keep_real_adapter_disabled`, + `allows_process_start=false`, `allows_payload_traffic=false`, and guardrails + that keep activation blocked, process start disabled, health probe disabled, + and `payload_traffic=none`. No new runtime image was required; the proof ran + on `rap-node-agent:codex-service-supervisor-20260513z52`. Verification + passed: C19Z119 live smoke. Artifact: + `artifacts/c19z119-remote-workspace-real-adapter-operator-outcome-closeout-reopen-boundary-smoke-result.json`. +- C19Z120 Remote Workspace real-adapter operator outcome closeout/reopen + boundary compatibility proof is complete on docker-test. The compatibility + smoke uses C19Z119 as source and validates boundary fields, + `boundary_status=closed_not_approved_reopen_required`, + `closed_outcome_status=rejected_or_not_approved_contract_only`, + `reopen_policy=new_explicit_enablement_request_required`, + `next_required_phase=explicit_operator_reopen_or_new_enablement_request`, + `enablement_decision=not_approved`, + `operator_review_status=closed_without_approval`, + `enablement_boundary=explicit_operator_enablement_required`, + `enablement_status=not_enabled`, + `runtime_gate_state=validated_contract_only_not_enabled`, + `runtime_effect=contract_only_no_runtime_enablement`, + `operator_default_action=keep_real_adapter_disabled`, + `allows_process_start=false`, `allows_payload_traffic=false`, and guardrails + that keep activation blocked, process start disabled, health probe disabled, + and `payload_traffic=none`. No new runtime image was required; the proof ran + on `rap-node-agent:codex-service-supervisor-20260513z52`. Verification + passed: C19Z120 live smoke. Artifact: + `artifacts/c19z120-remote-workspace-real-adapter-operator-outcome-closeout-reopen-boundary-compatibility-smoke-result.json`. +- C19Z121 Remote Workspace real-adapter not-approved outcome release marker is + complete on docker-test. The live smoke uses C19Z120 as source and emits + `rap.remote_workspace_real_adapter_not_approved_outcome_release_marker.v1` + with `release_status=not_approved_outcome_closed_contract_only`, + `release_marker=c19z121_real_adapter_not_approved_outcome_release_marker`, + `boundary_status=closed_not_approved_reopen_required`, + `closed_outcome_status=rejected_or_not_approved_contract_only`, + `reopen_policy=new_explicit_enablement_request_required`, + `enablement_decision=not_approved`, + `operator_review_status=closed_without_approval`, + `enablement_status=not_enabled`, + `runtime_gate_state=validated_contract_only_not_enabled`, + `runtime_effect=contract_only_no_runtime_enablement`, + `operator_default_action=keep_real_adapter_disabled`, + `allows_process_start=false`, `allows_payload_traffic=false`, and guardrails + that keep activation blocked, process start disabled, health probe disabled, + and `payload_traffic=none`. No new runtime image was required; the proof ran + on `rap-node-agent:codex-service-supervisor-20260513z52`. Verification + passed: C19Z121 live smoke. Artifact: + `artifacts/c19z121-remote-workspace-real-adapter-not-approved-outcome-release-marker-smoke-result.json`. +- C19Z122 Remote Workspace real-adapter not-approved outcome release marker + compatibility proof is complete on docker-test. The compatibility smoke uses + C19Z121 as source and validates release marker fields, + `release_status=not_approved_outcome_closed_contract_only`, + `boundary_status=closed_not_approved_reopen_required`, + `closed_outcome_status=rejected_or_not_approved_contract_only`, + `reopen_policy=new_explicit_enablement_request_required`, + `enablement_decision=not_approved`, + `operator_review_status=closed_without_approval`, + `enablement_status=not_enabled`, + `runtime_gate_state=validated_contract_only_not_enabled`, + `runtime_effect=contract_only_no_runtime_enablement`, + `operator_default_action=keep_real_adapter_disabled`, + `allows_process_start=false`, `allows_payload_traffic=false`, and guardrails + that keep activation blocked, process start disabled, health probe disabled, + and `payload_traffic=none`. No new runtime image was required; the proof ran + on `rap-node-agent:codex-service-supervisor-20260513z52`. Verification + passed: C19Z122 live smoke. Artifact: + `artifacts/c19z122-remote-workspace-real-adapter-not-approved-outcome-release-marker-compatibility-smoke-result.json`. +- C19Z123 Remote Workspace real-adapter not-approved outcome package index is + complete on docker-test. The live smoke uses C19Z122 as source and emits + `rap.remote_workspace_real_adapter_not_approved_outcome_package_index.v1` + with `package_status=closed_not_approved_contract_only`, + `package_marker=c19z123_real_adapter_not_approved_outcome_package_index`, + `covered_stage_range=C19Z117-C19Z122`, `covered_stage_count=6`, + `latest_compatibility_stage=C19Z122`, + `release_status=not_approved_outcome_closed_contract_only`, + `boundary_status=closed_not_approved_reopen_required`, + `reopen_policy=new_explicit_enablement_request_required`, + `enablement_decision=not_approved`, + `operator_review_status=closed_without_approval`, + `enablement_status=not_enabled`, + `runtime_gate_state=validated_contract_only_not_enabled`, + `runtime_effect=contract_only_no_runtime_enablement`, + `operator_default_action=keep_real_adapter_disabled`, + `allows_process_start=false`, `allows_payload_traffic=false`, and guardrails + that keep activation blocked, process start disabled, health probe disabled, + and `payload_traffic=none`. No new runtime image was required; the proof ran + on `rap-node-agent:codex-service-supervisor-20260513z52`. Verification + passed: C19Z123 live smoke. Artifact: + `artifacts/c19z123-remote-workspace-real-adapter-not-approved-outcome-package-index-smoke-result.json`. +- C19Z124 Remote Workspace real-adapter not-approved outcome package index + compatibility proof is complete on docker-test. The compatibility smoke uses + C19Z123 as source and validates package index fields, closeout notes, + `covered_stage_range=C19Z117-C19Z122`, `covered_stage_count=6`, + `latest_compatibility_stage=C19Z122`, + `release_status=not_approved_outcome_closed_contract_only`, + `boundary_status=closed_not_approved_reopen_required`, + `reopen_policy=new_explicit_enablement_request_required`, + `enablement_decision=not_approved`, + `operator_review_status=closed_without_approval`, + `enablement_status=not_enabled`, + `runtime_gate_state=validated_contract_only_not_enabled`, + `runtime_effect=contract_only_no_runtime_enablement`, + `operator_default_action=keep_real_adapter_disabled`, + `allows_process_start=false`, `allows_payload_traffic=false`, and guardrails + that keep activation blocked, process start disabled, health probe disabled, + and `payload_traffic=none`. No new runtime image was required; the proof ran + on `rap-node-agent:codex-service-supervisor-20260513z52`. Verification + passed: C19Z124 live smoke. Artifact: + `artifacts/c19z124-remote-workspace-real-adapter-not-approved-outcome-package-index-compatibility-smoke-result.json`. +- C19Z125 Remote Workspace real-adapter not-approved outcome closeout summary + is complete on docker-test. The live smoke uses C19Z124 as source and emits + `rap.remote_workspace_real_adapter_not_approved_outcome_closeout_summary.v1` + with `closeout_status=closed_not_approved_package_complete`, + `closeout_marker=c19z125_real_adapter_not_approved_outcome_closed_contract_only`, + `covered_stage_range=C19Z117-C19Z122`, `covered_stage_count=6`, + `latest_compatibility_stage=C19Z122`, + `release_status=not_approved_outcome_closed_contract_only`, + `boundary_status=closed_not_approved_reopen_required`, + `reopen_policy=new_explicit_enablement_request_required`, + `enablement_decision=not_approved`, + `operator_review_status=closed_without_approval`, + `enablement_status=not_enabled`, + `runtime_gate_state=validated_contract_only_not_enabled`, + `runtime_effect=contract_only_no_runtime_enablement`, + `operator_default_action=keep_real_adapter_disabled`, + `next_required_phase=explicit_new_enablement_request_only`, + `allows_process_start=false`, `allows_payload_traffic=false`, and guardrails + that keep activation blocked, process start disabled, health probe disabled, + and `payload_traffic=none`. No new runtime image was required; the proof ran + on `rap-node-agent:codex-service-supervisor-20260513z52`. Verification + passed: C19Z125 live smoke. Artifact: + `artifacts/c19z125-remote-workspace-real-adapter-not-approved-outcome-closeout-summary-smoke-result.json`. +- C19Z126 Remote Workspace real-adapter not-approved outcome closeout summary + compatibility proof is complete on docker-test. The compatibility smoke uses + C19Z125 as source and validates closeout summary fields, + `closeout_status=closed_not_approved_package_complete`, + `covered_stage_range=C19Z117-C19Z122`, `covered_stage_count=6`, + `latest_compatibility_stage=C19Z122`, + `reopen_policy=new_explicit_enablement_request_required`, + `enablement_decision=not_approved`, + `operator_review_status=closed_without_approval`, + `enablement_status=not_enabled`, + `runtime_gate_state=validated_contract_only_not_enabled`, + `runtime_effect=contract_only_no_runtime_enablement`, + `operator_default_action=keep_real_adapter_disabled`, + `next_required_phase=explicit_new_enablement_request_only`, + `allows_process_start=false`, `allows_payload_traffic=false`, and guardrails + that keep activation blocked, process start disabled, health probe disabled, + and `payload_traffic=none`. No new runtime image was required; the proof ran + on `rap-node-agent:codex-service-supervisor-20260513z52`. Verification + passed: C19Z126 live smoke. Artifact: + `artifacts/c19z126-remote-workspace-real-adapter-not-approved-outcome-closeout-summary-compatibility-smoke-result.json`. +- C19Z127 Remote Workspace real-adapter not-approved outcome final release + marker is complete on docker-test. The live smoke uses C19Z126 as source and + emits + `rap.remote_workspace_real_adapter_not_approved_outcome_final_release_marker.v1` + with `final_release_status=closed_not_approved_final_contract_only`, + `final_release_marker=c19z127_real_adapter_not_approved_outcome_final_release_marker`, + `covered_stage_range=C19Z117-C19Z122`, `covered_stage_count=6`, + `latest_compatibility_stage=C19Z122`, + `release_status=not_approved_outcome_closed_contract_only`, + `closeout_status=closed_not_approved_package_complete`, + `boundary_status=closed_not_approved_reopen_required`, + `reopen_policy=new_explicit_enablement_request_required`, + `enablement_decision=not_approved`, + `operator_review_status=closed_without_approval`, + `enablement_status=not_enabled`, + `runtime_gate_state=validated_contract_only_not_enabled`, + `runtime_effect=contract_only_no_runtime_enablement`, + `operator_default_action=keep_real_adapter_disabled`, + `next_required_phase=explicit_new_enablement_request_only`, + `allows_process_start=false`, `allows_payload_traffic=false`, final notes + that require a new explicit enablement request, and guardrails that keep + activation blocked, process start disabled, health probe disabled, and + `payload_traffic=none`. No new runtime image was required; the proof ran on + `rap-node-agent:codex-service-supervisor-20260513z52`. Verification passed: + C19Z127 live smoke. Artifact: + `artifacts/c19z127-remote-workspace-real-adapter-not-approved-outcome-final-release-marker-smoke-result.json`. +- C19Z128 Remote Workspace real-adapter not-approved outcome final release + marker compatibility proof is complete on docker-test. The compatibility + smoke uses C19Z127 as source and validates the final release marker fields, + final notes, `covered_stage_range=C19Z117-C19Z122`, + `covered_stage_count=6`, `latest_compatibility_stage=C19Z122`, + `release_status=not_approved_outcome_closed_contract_only`, + `closeout_status=closed_not_approved_package_complete`, + `boundary_status=closed_not_approved_reopen_required`, + `reopen_policy=new_explicit_enablement_request_required`, + `enablement_decision=not_approved`, + `operator_review_status=closed_without_approval`, + `enablement_status=not_enabled`, + `runtime_gate_state=validated_contract_only_not_enabled`, + `runtime_effect=contract_only_no_runtime_enablement`, + `operator_default_action=keep_real_adapter_disabled`, + `next_required_phase=explicit_new_enablement_request_only`, + `allows_process_start=false`, `allows_payload_traffic=false`, and guardrails + that keep activation blocked, process start disabled, health probe disabled, + and `payload_traffic=none`. No new runtime image was required; the proof ran + on `rap-node-agent:codex-service-supervisor-20260513z52`. Verification + passed: C19Z128 live smoke. Artifact: + `artifacts/c19z128-remote-workspace-real-adapter-not-approved-outcome-final-release-marker-compatibility-smoke-result.json`. +- C19Z129 Remote Workspace real-adapter not-approved outcome final package + index archive marker is complete on docker-test. The live smoke uses C19Z128 + as source and emits + `rap.remote_workspace_real_adapter_not_approved_outcome_final_package_index_archive_marker.v1` + with `archive_status=closed_not_approved_archived_contract_only`, + `archive_marker=c19z129_real_adapter_not_approved_outcome_final_package_index_archive_marker`, + `package_status=final_package_indexed_and_archived_contract_only`, + `final_release_status=closed_not_approved_final_contract_only`, + `covered_stage_range=C19Z117-C19Z122`, `covered_stage_count=6`, + `latest_compatibility_stage=C19Z122`, + `release_status=not_approved_outcome_closed_contract_only`, + `closeout_status=closed_not_approved_package_complete`, + `boundary_status=closed_not_approved_reopen_required`, + `reopen_policy=new_explicit_enablement_request_required`, + `enablement_decision=not_approved`, + `operator_review_status=closed_without_approval`, + `enablement_status=not_enabled`, + `runtime_gate_state=validated_contract_only_not_enabled`, + `runtime_effect=contract_only_no_runtime_enablement`, + `operator_default_action=keep_real_adapter_disabled`, + `next_required_phase=explicit_new_enablement_request_only`, + `allows_process_start=false`, `allows_payload_traffic=false`, archive notes + that keep the outcome contract-only, and guardrails that keep activation + blocked, process start disabled, health probe disabled, and + `payload_traffic=none`. No new runtime image was required; the proof ran on + `rap-node-agent:codex-service-supervisor-20260513z52`. Verification passed: + C19Z129 live smoke. Artifact: + `artifacts/c19z129-remote-workspace-real-adapter-not-approved-outcome-final-package-index-archive-marker-smoke-result.json`. +- C19Z130 Remote Workspace real-adapter not-approved outcome final package + index archive marker compatibility proof is complete on docker-test. The + compatibility smoke uses C19Z129 as source and validates archive marker + fields, archive notes, `covered_stage_range=C19Z117-C19Z122`, + `covered_stage_count=6`, `latest_compatibility_stage=C19Z122`, + `release_status=not_approved_outcome_closed_contract_only`, + `closeout_status=closed_not_approved_package_complete`, + `boundary_status=closed_not_approved_reopen_required`, + `reopen_policy=new_explicit_enablement_request_required`, + `enablement_decision=not_approved`, + `operator_review_status=closed_without_approval`, + `enablement_status=not_enabled`, + `runtime_gate_state=validated_contract_only_not_enabled`, + `runtime_effect=contract_only_no_runtime_enablement`, + `operator_default_action=keep_real_adapter_disabled`, + `next_required_phase=explicit_new_enablement_request_only`, + `allows_process_start=false`, `allows_payload_traffic=false`, and guardrails + that keep activation blocked, process start disabled, health probe disabled, + and `payload_traffic=none`. No new runtime image was required; the proof ran + on `rap-node-agent:codex-service-supervisor-20260513z52`. Verification + passed: C19Z130 live smoke. Artifact: + `artifacts/c19z130-remote-workspace-real-adapter-not-approved-outcome-final-package-index-archive-marker-compatibility-smoke-result.json`. +- C19Z131 Remote Workspace real-adapter not-approved outcome archive closeout + manifest is complete on docker-test. The live smoke uses C19Z130 as source + and emits + `rap.remote_workspace_real_adapter_not_approved_outcome_archive_closeout_manifest.v1` + with `manifest_status=closed_not_approved_archive_manifest_complete`, + `manifest_marker=c19z131_real_adapter_not_approved_outcome_archive_closeout_manifest`, + `archive_status=closed_not_approved_archived_contract_only`, + `package_status=final_package_indexed_and_archived_contract_only`, + `final_release_status=closed_not_approved_final_contract_only`, + `covered_stage_range=C19Z117-C19Z122`, `covered_stage_count=6`, + `latest_compatibility_stage=C19Z122`, + `release_status=not_approved_outcome_closed_contract_only`, + `closeout_status=closed_not_approved_package_complete`, + `boundary_status=closed_not_approved_reopen_required`, + `reopen_policy=new_explicit_enablement_request_required`, + `enablement_decision=not_approved`, + `operator_review_status=closed_without_approval`, + `enablement_status=not_enabled`, + `runtime_gate_state=validated_contract_only_not_enabled`, + `runtime_effect=contract_only_no_runtime_enablement`, + `operator_default_action=keep_real_adapter_disabled`, + `next_required_phase=explicit_new_enablement_request_only`, + `allows_process_start=false`, `allows_payload_traffic=false`, manifest notes + that close the branch until a new explicit request, and guardrails that keep + activation blocked, process start disabled, health probe disabled, and + `payload_traffic=none`. No new runtime image was required; the proof ran on + `rap-node-agent:codex-service-supervisor-20260513z52`. Verification passed: + C19Z131 live smoke. Artifact: + `artifacts/c19z131-remote-workspace-real-adapter-not-approved-outcome-archive-closeout-manifest-smoke-result.json`. +- C19Z132 Remote Workspace real-adapter not-approved outcome archive closeout + manifest compatibility proof is complete on docker-test. The compatibility + smoke uses C19Z131 as source and validates manifest fields, manifest notes, + `archive_status=closed_not_approved_archived_contract_only`, + `covered_stage_range=C19Z117-C19Z122`, `covered_stage_count=6`, + `latest_compatibility_stage=C19Z122`, + `release_status=not_approved_outcome_closed_contract_only`, + `closeout_status=closed_not_approved_package_complete`, + `reopen_policy=new_explicit_enablement_request_required`, + `enablement_decision=not_approved`, + `operator_review_status=closed_without_approval`, + `enablement_status=not_enabled`, + `runtime_gate_state=validated_contract_only_not_enabled`, + `runtime_effect=contract_only_no_runtime_enablement`, + `operator_default_action=keep_real_adapter_disabled`, + `next_required_phase=explicit_new_enablement_request_only`, + `allows_process_start=false`, `allows_payload_traffic=false`, and guardrails + that keep activation blocked, process start disabled, health probe disabled, + and `payload_traffic=none`. No new runtime image was required; the proof ran + on `rap-node-agent:codex-service-supervisor-20260513z52`. Verification + passed: C19Z132 live smoke. Artifact: + `artifacts/c19z132-remote-workspace-real-adapter-not-approved-outcome-archive-closeout-manifest-compatibility-smoke-result.json`. +- C19Z133 Remote Workspace real-adapter not-approved outcome stopped-branch + sentinel is complete on docker-test. The live smoke uses C19Z132 as source + and emits + `rap.remote_workspace_real_adapter_not_approved_outcome_stopped_branch_sentinel.v1` + with `sentinel_status=stopped_until_new_explicit_enablement_request`, + `sentinel_marker=c19z133_real_adapter_not_approved_outcome_stopped_branch_sentinel`, + `branch_state=not_approved_branch_closed`, + `continuation_policy=do_not_continue_without_new_explicit_enablement_request`, + `manifest_status=closed_not_approved_archive_manifest_complete`, + `archive_status=closed_not_approved_archived_contract_only`, + `package_status=final_package_indexed_and_archived_contract_only`, + `final_release_status=closed_not_approved_final_contract_only`, + `covered_stage_range=C19Z117-C19Z122`, `covered_stage_count=6`, + `latest_compatibility_stage=C19Z122`, + `release_status=not_approved_outcome_closed_contract_only`, + `closeout_status=closed_not_approved_package_complete`, + `reopen_policy=new_explicit_enablement_request_required`, + `enablement_decision=not_approved`, + `operator_review_status=closed_without_approval`, + `enablement_status=not_enabled`, + `runtime_gate_state=validated_contract_only_not_enabled`, + `runtime_effect=contract_only_no_runtime_enablement`, + `operator_default_action=keep_real_adapter_disabled`, + `next_required_phase=explicit_new_enablement_request_only`, + `allows_process_start=false`, `allows_payload_traffic=false`, sentinel notes + that stop the not-approved branch, and guardrails that keep activation + blocked, process start disabled, health probe disabled, and + `payload_traffic=none`. No new runtime image was required; the proof ran on + `rap-node-agent:codex-service-supervisor-20260513z52`. Verification passed: + C19Z133 live smoke. Artifact: + `artifacts/c19z133-remote-workspace-real-adapter-not-approved-outcome-stopped-branch-sentinel-smoke-result.json`. +- C19Z134 Remote Workspace real-adapter not-approved outcome stopped-branch + sentinel compatibility proof is complete on docker-test. The compatibility + smoke uses C19Z133 as source and validates sentinel fields, sentinel notes, + `sentinel_status=stopped_until_new_explicit_enablement_request`, + `branch_state=not_approved_branch_closed`, + `continuation_policy=do_not_continue_without_new_explicit_enablement_request`, + `reopen_policy=new_explicit_enablement_request_required`, + `enablement_decision=not_approved`, + `operator_review_status=closed_without_approval`, + `enablement_status=not_enabled`, + `runtime_gate_state=validated_contract_only_not_enabled`, + `runtime_effect=contract_only_no_runtime_enablement`, + `operator_default_action=keep_real_adapter_disabled`, + `next_required_phase=explicit_new_enablement_request_only`, + `allows_process_start=false`, `allows_payload_traffic=false`, and guardrails + that keep activation blocked, process start disabled, health probe disabled, + and `payload_traffic=none`. No new runtime image was required; the proof ran + on `rap-node-agent:codex-service-supervisor-20260513z52`. Verification + passed: C19Z134 live smoke. Artifact: + `artifacts/c19z134-remote-workspace-real-adapter-not-approved-outcome-stopped-branch-sentinel-compatibility-smoke-result.json`. +- C19Z135 Remote Workspace real-adapter not-approved outcome no-continuation + guard is complete on docker-test. The live smoke uses C19Z134 as source and + emits + `rap.remote_workspace_real_adapter_not_approved_outcome_no_continuation_guard.v1` + with `guard_status=no_continuation_without_new_explicit_enablement_request`, + `guard_marker=c19z135_real_adapter_not_approved_outcome_no_continuation_guard`, + `branch_state=not_approved_branch_closed`, + `continuation_policy=do_not_continue_without_new_explicit_enablement_request`, + `next_allowed_entrypoint=new_explicit_enablement_request_only`, + `blocks_not_approved_extension=true`, + `sentinel_status=stopped_until_new_explicit_enablement_request`, + `reopen_policy=new_explicit_enablement_request_required`, + `enablement_decision=not_approved`, + `operator_review_status=closed_without_approval`, + `enablement_status=not_enabled`, + `runtime_gate_state=validated_contract_only_not_enabled`, + `runtime_effect=contract_only_no_runtime_enablement`, + `operator_default_action=keep_real_adapter_disabled`, + `next_required_phase=explicit_new_enablement_request_only`, + `allows_process_start=false`, `allows_payload_traffic=false`, and guardrails + that keep activation blocked, process start disabled, health probe disabled, + and `payload_traffic=none`. No new runtime image was required; the proof ran + on `rap-node-agent:codex-service-supervisor-20260513z52`. Verification + passed: C19Z135 live smoke. Artifact: + `artifacts/c19z135-remote-workspace-real-adapter-not-approved-outcome-no-continuation-guard-smoke-result.json`. +- C19Z136 Remote Workspace real-adapter not-approved outcome no-continuation + guard compatibility proof is complete on docker-test. The compatibility + smoke uses C19Z135 as source and validates guard fields, guard notes, + `guard_status=no_continuation_without_new_explicit_enablement_request`, + `next_allowed_entrypoint=new_explicit_enablement_request_only`, + `blocks_not_approved_extension=true`, + `sentinel_status=stopped_until_new_explicit_enablement_request`, + `reopen_policy=new_explicit_enablement_request_required`, + `enablement_status=not_enabled`, + `runtime_gate_state=validated_contract_only_not_enabled`, + `runtime_effect=contract_only_no_runtime_enablement`, + `operator_default_action=keep_real_adapter_disabled`, + `next_required_phase=explicit_new_enablement_request_only`, + `allows_process_start=false`, `allows_payload_traffic=false`, and guardrails + that keep activation blocked, process start disabled, health probe disabled, + and `payload_traffic=none`. No new runtime image was required; the proof ran + on `rap-node-agent:codex-service-supervisor-20260513z52`. Verification + passed: C19Z136 live smoke. Artifact: + `artifacts/c19z136-remote-workspace-real-adapter-not-approved-outcome-no-continuation-guard-compatibility-smoke-result.json`. +- C19Z137 Remote Workspace real-adapter not-approved outcome continuation + block enforcement is complete on docker-test. The live smoke uses C19Z136 as + source and emits + `rap.remote_workspace_real_adapter_not_approved_outcome_continuation_block_enforcement.v1` + with `enforcement_status=blocked_continuation_enforced`, + `attempted_action=continue_not_approved_branch_without_new_explicit_enablement_request`, + `attempt_allowed=false`, `block_reason=new_explicit_enablement_request_required`, + `next_allowed_entrypoint=new_explicit_enablement_request_only`, + `blocks_not_approved_extension=true`, + `guard_status=no_continuation_without_new_explicit_enablement_request`, + `branch_state=not_approved_branch_closed`, + `continuation_policy=do_not_continue_without_new_explicit_enablement_request`, + `reopen_policy=new_explicit_enablement_request_required`, + `enablement_status=not_enabled`, + `runtime_gate_state=validated_contract_only_not_enabled`, + `runtime_effect=contract_only_no_runtime_enablement`, + `allows_process_start=false`, `allows_payload_traffic=false`, and guardrails + that keep activation blocked, process start disabled, health probe disabled, + and `payload_traffic=none`. No new runtime image was required; the proof ran + on `rap-node-agent:codex-service-supervisor-20260513z52`. Verification + passed: C19Z137 live smoke. Artifact: + `artifacts/c19z137-remote-workspace-real-adapter-not-approved-outcome-continuation-block-enforcement-smoke-result.json`. +- C19Z138 Remote Workspace real-adapter not-approved outcome continuation + block enforcement compatibility proof is complete on docker-test. The + compatibility smoke uses C19Z137 as source and validates enforcement fields, + enforcement notes, `attempt_allowed=false`, + `block_reason=new_explicit_enablement_request_required`, + `next_allowed_entrypoint=new_explicit_enablement_request_only`, + `blocks_not_approved_extension=true`, + `guard_status=no_continuation_without_new_explicit_enablement_request`, + `enablement_status=not_enabled`, + `runtime_gate_state=validated_contract_only_not_enabled`, + `runtime_effect=contract_only_no_runtime_enablement`, + `allows_process_start=false`, `allows_payload_traffic=false`, and guardrails + that keep activation blocked, process start disabled, health probe disabled, + and `payload_traffic=none`. No new runtime image was required; the proof ran + on `rap-node-agent:codex-service-supervisor-20260513z52`. Verification + passed: C19Z138 live smoke. Artifact: + `artifacts/c19z138-remote-workspace-real-adapter-not-approved-outcome-continuation-block-enforcement-compatibility-smoke-result.json`. +- C19Z139 Remote Workspace real-adapter not-approved outcome continuation + block audit record is complete on docker-test. The live smoke uses C19Z138 + as source and emits + `rap.remote_workspace_real_adapter_not_approved_outcome_continuation_block_audit_record.v1` + with `audit_status=blocked_continuation_audit_recorded`, + `audit_event_type=not_approved_continuation_block`, + `attempted_action=continue_not_approved_branch_without_new_explicit_enablement_request`, + `attempt_allowed=false`, `block_reason=new_explicit_enablement_request_required`, + `next_allowed_entrypoint=new_explicit_enablement_request_only`, + `blocks_not_approved_extension=true`, + `branch_state=not_approved_branch_closed`, + `continuation_policy=do_not_continue_without_new_explicit_enablement_request`, + `enablement_status=not_enabled`, + `runtime_gate_state=validated_contract_only_not_enabled`, + `runtime_effect=contract_only_no_runtime_enablement`, + `allows_process_start=false`, `allows_payload_traffic=false`, and guardrails + that keep activation blocked, process start disabled, health probe disabled, + and `payload_traffic=none`. No new runtime image was required; the proof ran + on `rap-node-agent:codex-service-supervisor-20260513z52`. Verification + passed: C19Z139 live smoke. Artifact: + `artifacts/c19z139-remote-workspace-real-adapter-not-approved-outcome-continuation-block-audit-record-smoke-result.json`. +- C19Z140 Remote Workspace real-adapter not-approved outcome continuation + block audit record compatibility proof is complete on docker-test. The + compatibility smoke uses C19Z139 as source and validates audit fields, audit + notes, `audit_status=blocked_continuation_audit_recorded`, + `attempt_allowed=false`, `block_reason=new_explicit_enablement_request_required`, + `next_allowed_entrypoint=new_explicit_enablement_request_only`, + `blocks_not_approved_extension=true`, `enablement_status=not_enabled`, + `runtime_gate_state=validated_contract_only_not_enabled`, + `runtime_effect=contract_only_no_runtime_enablement`, + `allows_process_start=false`, `allows_payload_traffic=false`, and guardrails + that keep activation blocked, process start disabled, health probe disabled, + and `payload_traffic=none`. No new runtime image was required; the proof ran + on `rap-node-agent:codex-service-supervisor-20260513z52`. Verification + passed: C19Z140 live smoke. Artifact: + `artifacts/c19z140-remote-workspace-real-adapter-not-approved-outcome-continuation-block-audit-record-compatibility-smoke-result.json`. +- C19Z141 Remote Workspace real-adapter not-approved outcome continuation + block audit rollup is complete on docker-test. The live smoke uses C19Z140 + as source and emits + `rap.remote_workspace_real_adapter_not_approved_outcome_continuation_block_audit_rollup.v1` + with `rollup_status=blocked_continuation_audit_rollup_complete`, + `operator_status=not_approved_branch_closed_new_request_required`, + `audit_status=blocked_continuation_audit_recorded`, + `attempt_allowed=false`, `block_reason=new_explicit_enablement_request_required`, + `next_allowed_entrypoint=new_explicit_enablement_request_only`, + `blocks_not_approved_extension=true`, + `branch_state=not_approved_branch_closed`, + `continuation_policy=do_not_continue_without_new_explicit_enablement_request`, + `enablement_status=not_enabled`, + `runtime_gate_state=validated_contract_only_not_enabled`, + `runtime_effect=contract_only_no_runtime_enablement`, + `allows_process_start=false`, `allows_payload_traffic=false`, and guardrails + that keep activation blocked, process start disabled, health probe disabled, + and `payload_traffic=none`. No new runtime image was required; the proof ran + on `rap-node-agent:codex-service-supervisor-20260513z52`. Verification + passed: C19Z141 live smoke. Artifact: + `artifacts/c19z141-remote-workspace-real-adapter-not-approved-outcome-continuation-block-audit-rollup-smoke-result.json`. +- C19Z142 Remote Workspace real-adapter not-approved outcome continuation + block audit rollup compatibility proof is complete on docker-test. The + compatibility smoke uses C19Z141 as source and validates rollup fields, + rollup notes, `operator_status=not_approved_branch_closed_new_request_required`, + `attempt_allowed=false`, `block_reason=new_explicit_enablement_request_required`, + `next_allowed_entrypoint=new_explicit_enablement_request_only`, + `blocks_not_approved_extension=true`, `enablement_status=not_enabled`, + `runtime_gate_state=validated_contract_only_not_enabled`, + `runtime_effect=contract_only_no_runtime_enablement`, + `allows_process_start=false`, `allows_payload_traffic=false`, and guardrails + that keep activation blocked, process start disabled, health probe disabled, + and `payload_traffic=none`. No new runtime image was required; the proof ran + on `rap-node-agent:codex-service-supervisor-20260513z52`. Verification + passed: C19Z142 live smoke. Artifact: + `artifacts/c19z142-remote-workspace-real-adapter-not-approved-outcome-continuation-block-audit-rollup-compatibility-smoke-result.json`. +- C19Z143 Remote Workspace real-adapter not-approved outcome operator stop + summary is complete on docker-test. The live smoke uses C19Z142 as source and + emits + `rap.remote_workspace_real_adapter_not_approved_outcome_operator_stop_summary.v1` + with `summary_status=operator_stop_summary_complete`, + `operator_status=not_approved_branch_closed_new_request_required`, + `operator_action=keep_real_adapter_disabled_until_new_explicit_enablement_request`, + `operator_message=not_approved_branch_closed_new_request_required`, + `attempt_allowed=false`, `block_reason=new_explicit_enablement_request_required`, + `next_allowed_entrypoint=new_explicit_enablement_request_only`, + `blocks_not_approved_extension=true`, + `branch_state=not_approved_branch_closed`, + `continuation_policy=do_not_continue_without_new_explicit_enablement_request`, + `enablement_status=not_enabled`, + `runtime_gate_state=validated_contract_only_not_enabled`, + `runtime_effect=contract_only_no_runtime_enablement`, + `allows_process_start=false`, `allows_payload_traffic=false`, and guardrails + that keep activation blocked, process start disabled, health probe disabled, + and `payload_traffic=none`. No new runtime image was required; the proof ran + on `rap-node-agent:codex-service-supervisor-20260513z52`. Verification + passed: C19Z143 live smoke. Artifact: + `artifacts/c19z143-remote-workspace-real-adapter-not-approved-outcome-operator-stop-summary-smoke-result.json`. +- C19Z144 Remote Workspace real-adapter not-approved outcome operator stop + summary compatibility proof is complete on docker-test. The compatibility + smoke uses C19Z143 as source and validates summary fields, summary notes, + `operator_status=not_approved_branch_closed_new_request_required`, + `operator_action=keep_real_adapter_disabled_until_new_explicit_enablement_request`, + `attempt_allowed=false`, `block_reason=new_explicit_enablement_request_required`, + `next_allowed_entrypoint=new_explicit_enablement_request_only`, + `blocks_not_approved_extension=true`, `enablement_status=not_enabled`, + `runtime_gate_state=validated_contract_only_not_enabled`, + `runtime_effect=contract_only_no_runtime_enablement`, + `allows_process_start=false`, `allows_payload_traffic=false`, and guardrails + that keep activation blocked, process start disabled, health probe disabled, + and `payload_traffic=none`. No new runtime image was required; the proof ran + on `rap-node-agent:codex-service-supervisor-20260513z52`. Verification + passed: C19Z144 live smoke. Artifact: + `artifacts/c19z144-remote-workspace-real-adapter-not-approved-outcome-operator-stop-summary-compatibility-smoke-result.json`. +- C19Z145 Remote Workspace real-adapter not-approved outcome operator stop + handoff is complete on docker-test. The live smoke uses C19Z144 as source and + emits + `rap.remote_workspace_real_adapter_not_approved_outcome_operator_stop_handoff.v1` + with `handoff_status=operator_stop_handoff_complete`, + `operator_status=not_approved_branch_closed_new_request_required`, + `operator_action=keep_real_adapter_disabled_until_new_explicit_enablement_request`, + `operator_message=not_approved_branch_closed_new_request_required`, + `display_severity=blocked`, + `next_allowed_entrypoint=new_explicit_enablement_request_only`, + `attempt_allowed=false`, `block_reason=new_explicit_enablement_request_required`, + `blocks_not_approved_extension=true`, + `branch_state=not_approved_branch_closed`, + `continuation_policy=do_not_continue_without_new_explicit_enablement_request`, + `enablement_status=not_enabled`, + `runtime_gate_state=validated_contract_only_not_enabled`, + `runtime_effect=contract_only_no_runtime_enablement`, + `allows_process_start=false`, `allows_payload_traffic=false`, and guardrails + that keep activation blocked, process start disabled, health probe disabled, + and `payload_traffic=none`. No new runtime image was required; the proof ran + on `rap-node-agent:codex-service-supervisor-20260513z52`. Verification + passed: C19Z145 live smoke. Artifact: + `artifacts/c19z145-remote-workspace-real-adapter-not-approved-outcome-operator-stop-handoff-smoke-result.json`. +- C19Z146 Remote Workspace real-adapter not-approved outcome operator stop + handoff compatibility proof is complete on docker-test. The compatibility + smoke uses C19Z145 as source and validates handoff fields, handoff notes, + `operator_status=not_approved_branch_closed_new_request_required`, + `operator_action=keep_real_adapter_disabled_until_new_explicit_enablement_request`, + `display_severity=blocked`, + `attempt_allowed=false`, `block_reason=new_explicit_enablement_request_required`, + `next_allowed_entrypoint=new_explicit_enablement_request_only`, + `blocks_not_approved_extension=true`, `enablement_status=not_enabled`, + `runtime_gate_state=validated_contract_only_not_enabled`, + `runtime_effect=contract_only_no_runtime_enablement`, + `allows_process_start=false`, `allows_payload_traffic=false`, and guardrails + that keep activation blocked, process start disabled, health probe disabled, + and `payload_traffic=none`. No new runtime image was required; the proof ran + on `rap-node-agent:codex-service-supervisor-20260513z52`. Verification + passed: C19Z146 live smoke. Artifact: + `artifacts/c19z146-remote-workspace-real-adapter-not-approved-outcome-operator-stop-handoff-compatibility-smoke-result.json`. +- C19Z147 Remote Workspace real-adapter not-approved outcome operator stop + handoff digest is complete on docker-test. The live smoke uses C19Z146 as + source and emits + `rap.remote_workspace_real_adapter_not_approved_outcome_operator_stop_handoff_digest.v1` + with `digest_status=operator_stop_handoff_digest_complete`, + `operator_status=not_approved_branch_closed_new_request_required`, + `operator_action=keep_real_adapter_disabled_until_new_explicit_enablement_request`, + `display_severity=blocked`, + `next_allowed_entrypoint=new_explicit_enablement_request_only`, + `attempt_allowed=false`, `block_reason=new_explicit_enablement_request_required`, + `blocks_not_approved_extension=true`, + `enablement_status=not_enabled`, + `runtime_gate_state=validated_contract_only_not_enabled`, + `runtime_effect=contract_only_no_runtime_enablement`, + `allows_process_start=false`, `allows_payload_traffic=false`, and guardrails + that keep activation blocked, process start disabled, health probe disabled, + and `payload_traffic=none`. No new runtime image was required; the proof ran + on `rap-node-agent:codex-service-supervisor-20260513z52`. Verification + passed: C19Z147 live smoke. Artifact: + `artifacts/c19z147-remote-workspace-real-adapter-not-approved-outcome-operator-stop-handoff-digest-smoke-result.json`. +- C19Z148 Remote Workspace real-adapter not-approved outcome operator stop + handoff digest compatibility proof is complete on docker-test. The + compatibility smoke uses C19Z147 as source and validates digest fields, + digest notes, `operator_status=not_approved_branch_closed_new_request_required`, + `operator_action=keep_real_adapter_disabled_until_new_explicit_enablement_request`, + `display_severity=blocked`, + `attempt_allowed=false`, `block_reason=new_explicit_enablement_request_required`, + `next_allowed_entrypoint=new_explicit_enablement_request_only`, + `blocks_not_approved_extension=true`, `enablement_status=not_enabled`, + `runtime_gate_state=validated_contract_only_not_enabled`, + `runtime_effect=contract_only_no_runtime_enablement`, + `allows_process_start=false`, `allows_payload_traffic=false`, and guardrails + that keep activation blocked, process start disabled, health probe disabled, + and `payload_traffic=none`. No new runtime image was required; the proof ran + on `rap-node-agent:codex-service-supervisor-20260513z52`. Verification + passed: C19Z148 live smoke. Artifact: + `artifacts/c19z148-remote-workspace-real-adapter-not-approved-outcome-operator-stop-handoff-digest-compatibility-smoke-result.json`. +- C19Z149 Remote Workspace real-adapter not-approved outcome operator stop + status snapshot is complete on docker-test. The live smoke uses C19Z148 as + source and emits + `rap.remote_workspace_real_adapter_not_approved_outcome_operator_stop_status_snapshot.v1` + with `snapshot_status=operator_stop_status_snapshot_complete`, + `operator_status=not_approved_branch_closed_new_request_required`, + `operator_action=keep_real_adapter_disabled_until_new_explicit_enablement_request`, + `display_severity=blocked`, + `next_allowed_entrypoint=new_explicit_enablement_request_only`, + `attempt_allowed=false`, `block_reason=new_explicit_enablement_request_required`, + `blocks_not_approved_extension=true`, + `enablement_status=not_enabled`, + `runtime_gate_state=validated_contract_only_not_enabled`, + `runtime_effect=contract_only_no_runtime_enablement`, + `allows_process_start=false`, `allows_payload_traffic=false`, and guardrails + that keep activation blocked, process start disabled, health probe disabled, + and `payload_traffic=none`. No new runtime image was required; the proof ran + on `rap-node-agent:codex-service-supervisor-20260513z52`. Verification + passed: C19Z149 live smoke. Artifact: + `artifacts/c19z149-remote-workspace-real-adapter-not-approved-outcome-operator-stop-status-snapshot-smoke-result.json`. +- C19Z150 Remote Workspace real-adapter not-approved outcome operator stop + status snapshot compatibility proof is complete on docker-test. The + compatibility smoke uses C19Z149 as source and validates snapshot fields, + snapshot notes, `operator_status=not_approved_branch_closed_new_request_required`, + `operator_action=keep_real_adapter_disabled_until_new_explicit_enablement_request`, + `display_severity=blocked`, + `attempt_allowed=false`, `block_reason=new_explicit_enablement_request_required`, + `next_allowed_entrypoint=new_explicit_enablement_request_only`, + `blocks_not_approved_extension=true`, `enablement_status=not_enabled`, + `runtime_gate_state=validated_contract_only_not_enabled`, + `runtime_effect=contract_only_no_runtime_enablement`, + `allows_process_start=false`, `allows_payload_traffic=false`, and guardrails + that keep activation blocked, process start disabled, health probe disabled, + and `payload_traffic=none`. No new runtime image was required; the proof ran + on `rap-node-agent:codex-service-supervisor-20260513z52`. Verification + passed: C19Z150 live smoke. Artifact: + `artifacts/c19z150-remote-workspace-real-adapter-not-approved-outcome-operator-stop-status-snapshot-compatibility-smoke-result.json`. +- C19Z151 Remote Workspace real-adapter not-approved outcome operator stop + status snapshot index is complete on docker-test. The live smoke uses C19Z150 + as source and emits + `rap.remote_workspace_real_adapter_not_approved_outcome_operator_stop_status_snapshot_index.v1` + with `index_status=operator_stop_status_snapshot_index_complete`, + `indexed_snapshot_status=operator_stop_status_snapshot_complete`, + `operator_status=not_approved_branch_closed_new_request_required`, + `operator_action=keep_real_adapter_disabled_until_new_explicit_enablement_request`, + `display_severity=blocked`, + `next_allowed_entrypoint=new_explicit_enablement_request_only`, + `attempt_allowed=false`, `block_reason=new_explicit_enablement_request_required`, + `blocks_not_approved_extension=true`, + `enablement_status=not_enabled`, + `runtime_gate_state=validated_contract_only_not_enabled`, + `runtime_effect=contract_only_no_runtime_enablement`, + `allows_process_start=false`, `allows_payload_traffic=false`, and guardrails + that keep activation blocked, process start disabled, health probe disabled, + and `payload_traffic=none`. No new runtime image was required; the proof ran + on `rap-node-agent:codex-service-supervisor-20260513z52`. Verification + passed: C19Z151 live smoke. Artifact: + `artifacts/c19z151-remote-workspace-real-adapter-not-approved-outcome-operator-stop-status-snapshot-index-smoke-result.json`. +- C19Z152 Remote Workspace real-adapter not-approved outcome operator stop + status snapshot index compatibility proof is complete on docker-test. The + compatibility smoke uses C19Z151 as source and validates index fields, index + notes, `operator_status=not_approved_branch_closed_new_request_required`, + `operator_action=keep_real_adapter_disabled_until_new_explicit_enablement_request`, + `display_severity=blocked`, + `attempt_allowed=false`, `block_reason=new_explicit_enablement_request_required`, + `next_allowed_entrypoint=new_explicit_enablement_request_only`, + `blocks_not_approved_extension=true`, `enablement_status=not_enabled`, + `runtime_gate_state=validated_contract_only_not_enabled`, + `runtime_effect=contract_only_no_runtime_enablement`, + `allows_process_start=false`, `allows_payload_traffic=false`, and guardrails + that keep activation blocked, process start disabled, health probe disabled, + and `payload_traffic=none`. No new runtime image was required; the proof ran + on `rap-node-agent:codex-service-supervisor-20260513z52`. Verification + passed: C19Z152 live smoke. Artifact: + `artifacts/c19z152-remote-workspace-real-adapter-not-approved-outcome-operator-stop-status-snapshot-index-compatibility-smoke-result.json`. +- C19Z153 Remote Workspace real-adapter not-approved outcome operator stop + status catalog is complete on docker-test. The live smoke uses C19Z152 as + source and emits + `rap.remote_workspace_real_adapter_not_approved_outcome_operator_stop_status_catalog.v1` + with `catalog_status=operator_stop_status_catalog_complete`, + `catalog_entry_type=blocked_not_approved_operator_stop`, + `operator_status=not_approved_branch_closed_new_request_required`, + `operator_action=keep_real_adapter_disabled_until_new_explicit_enablement_request`, + `display_severity=blocked`, + `next_allowed_entrypoint=new_explicit_enablement_request_only`, + `attempt_allowed=false`, `block_reason=new_explicit_enablement_request_required`, + `blocks_not_approved_extension=true`, + `enablement_status=not_enabled`, + `runtime_gate_state=validated_contract_only_not_enabled`, + `runtime_effect=contract_only_no_runtime_enablement`, + `allows_process_start=false`, `allows_payload_traffic=false`, and guardrails + that keep activation blocked, process start disabled, health probe disabled, + and `payload_traffic=none`. No new runtime image was required; the proof ran + on `rap-node-agent:codex-service-supervisor-20260513z52`. Verification + passed: C19Z153 live smoke. Artifact: + `artifacts/c19z153-remote-workspace-real-adapter-not-approved-outcome-operator-stop-status-catalog-smoke-result.json`. +- C19Z154 Remote Workspace real-adapter not-approved outcome operator stop + status catalog compatibility proof is complete on docker-test. The + compatibility smoke uses C19Z153 as source and validates catalog fields, + catalog notes, `catalog_entry_type=blocked_not_approved_operator_stop`, + `operator_status=not_approved_branch_closed_new_request_required`, + `operator_action=keep_real_adapter_disabled_until_new_explicit_enablement_request`, + `display_severity=blocked`, + `attempt_allowed=false`, `block_reason=new_explicit_enablement_request_required`, + `next_allowed_entrypoint=new_explicit_enablement_request_only`, + `blocks_not_approved_extension=true`, `enablement_status=not_enabled`, + `runtime_gate_state=validated_contract_only_not_enabled`, + `runtime_effect=contract_only_no_runtime_enablement`, + `allows_process_start=false`, `allows_payload_traffic=false`, and guardrails + that keep activation blocked, process start disabled, health probe disabled, + and `payload_traffic=none`. No new runtime image was required; the proof ran + on `rap-node-agent:codex-service-supervisor-20260513z52`. Verification + passed: C19Z154 live smoke. Artifact: + `artifacts/c19z154-remote-workspace-real-adapter-not-approved-outcome-operator-stop-status-catalog-compatibility-smoke-result.json`. +- C19Z155 Remote Workspace real-adapter not-approved outcome operator stop + status catalog release marker is complete on docker-test. The live smoke uses + C19Z154 as source and emits + `rap.remote_workspace_real_adapter_not_approved_outcome_operator_stop_status_catalog_release_marker.v1` + with `release_status=operator_stop_status_catalog_released_contract_only`, + `catalog_status=operator_stop_status_catalog_complete`, + `catalog_entry_type=blocked_not_approved_operator_stop`, + `operator_status=not_approved_branch_closed_new_request_required`, + `operator_action=keep_real_adapter_disabled_until_new_explicit_enablement_request`, + `display_severity=blocked`, + `next_allowed_entrypoint=new_explicit_enablement_request_only`, + `attempt_allowed=false`, `block_reason=new_explicit_enablement_request_required`, + `blocks_not_approved_extension=true`, + `enablement_status=not_enabled`, + `runtime_gate_state=validated_contract_only_not_enabled`, + `runtime_effect=contract_only_no_runtime_enablement`, + `allows_process_start=false`, `allows_payload_traffic=false`, and guardrails + that keep activation blocked, process start disabled, health probe disabled, + and `payload_traffic=none`. No new runtime image was required; the proof ran + on `rap-node-agent:codex-service-supervisor-20260513z52`. Verification + passed: C19Z155 live smoke. Artifact: + `artifacts/c19z155-remote-workspace-real-adapter-not-approved-outcome-operator-stop-status-catalog-release-marker-smoke-result.json`. +- C19Z156 Remote Workspace real-adapter not-approved outcome operator stop + status catalog release marker compatibility proof is complete on docker-test. + The compatibility smoke uses C19Z155 as source and validates release marker + fields, release notes, + `release_status=operator_stop_status_catalog_released_contract_only`, + `catalog_entry_type=blocked_not_approved_operator_stop`, + `operator_status=not_approved_branch_closed_new_request_required`, + `operator_action=keep_real_adapter_disabled_until_new_explicit_enablement_request`, + `display_severity=blocked`, + `attempt_allowed=false`, `block_reason=new_explicit_enablement_request_required`, + `next_allowed_entrypoint=new_explicit_enablement_request_only`, + `blocks_not_approved_extension=true`, `enablement_status=not_enabled`, + `runtime_gate_state=validated_contract_only_not_enabled`, + `runtime_effect=contract_only_no_runtime_enablement`, + `allows_process_start=false`, `allows_payload_traffic=false`, and guardrails + that keep activation blocked, process start disabled, health probe disabled, + and `payload_traffic=none`. No new runtime image was required; the proof ran + on `rap-node-agent:codex-service-supervisor-20260513z52`. Verification + passed: C19Z156 live smoke. Artifact: + `artifacts/c19z156-remote-workspace-real-adapter-not-approved-outcome-operator-stop-status-catalog-release-marker-compatibility-smoke-result.json`. +- C19Z157 Remote Workspace real-adapter not-approved outcome operator stop + status catalog package index is complete on docker-test. The live smoke uses + C19Z156 as source and emits + `rap.remote_workspace_real_adapter_not_approved_outcome_operator_stop_status_catalog_package_index.v1` + with `package_status=operator_stop_status_catalog_package_index_complete`, + `release_status=operator_stop_status_catalog_released_contract_only`, + `catalog_entry_type=blocked_not_approved_operator_stop`, + `operator_status=not_approved_branch_closed_new_request_required`, + `operator_action=keep_real_adapter_disabled_until_new_explicit_enablement_request`, + `display_severity=blocked`, + `next_allowed_entrypoint=new_explicit_enablement_request_only`, + `attempt_allowed=false`, `block_reason=new_explicit_enablement_request_required`, + `blocks_not_approved_extension=true`, + `enablement_status=not_enabled`, + `runtime_gate_state=validated_contract_only_not_enabled`, + `runtime_effect=contract_only_no_runtime_enablement`, + `allows_process_start=false`, `allows_payload_traffic=false`, and guardrails + that keep activation blocked, process start disabled, health probe disabled, + and `payload_traffic=none`. No new runtime image was required; the proof ran + on `rap-node-agent:codex-service-supervisor-20260513z52`. Verification + passed: C19Z157 live smoke. Artifact: + `artifacts/c19z157-remote-workspace-real-adapter-not-approved-outcome-operator-stop-status-catalog-package-index-smoke-result.json`. +- C19Z158 Remote Workspace real-adapter not-approved outcome operator stop + status catalog package index compatibility proof is complete on docker-test. + The compatibility smoke uses C19Z157 as source and validates package index + fields, package notes, + `release_status=operator_stop_status_catalog_released_contract_only`, + `catalog_entry_type=blocked_not_approved_operator_stop`, + `operator_status=not_approved_branch_closed_new_request_required`, + `operator_action=keep_real_adapter_disabled_until_new_explicit_enablement_request`, + `display_severity=blocked`, + `attempt_allowed=false`, `block_reason=new_explicit_enablement_request_required`, + `next_allowed_entrypoint=new_explicit_enablement_request_only`, + `blocks_not_approved_extension=true`, `enablement_status=not_enabled`, + `runtime_gate_state=validated_contract_only_not_enabled`, + `runtime_effect=contract_only_no_runtime_enablement`, + `allows_process_start=false`, `allows_payload_traffic=false`, and guardrails + that keep activation blocked, process start disabled, health probe disabled, + and `payload_traffic=none`. No new runtime image was required; the proof ran + on `rap-node-agent:codex-service-supervisor-20260513z52`. Verification + passed: C19Z158 live smoke. Artifact: + `artifacts/c19z158-remote-workspace-real-adapter-not-approved-outcome-operator-stop-status-catalog-package-index-compatibility-smoke-result.json`. +- C19Z159 Remote Workspace real-adapter not-approved outcome operator stop + status catalog closeout summary is complete on docker-test. The live smoke + uses C19Z158 as source and emits + `rap.remote_workspace_real_adapter_not_approved_outcome_operator_stop_status_catalog_closeout_summary.v1` + with `closeout_status=operator_stop_status_catalog_package_closed_contract_only`, + `package_status=operator_stop_status_catalog_package_index_complete`, + `release_status=operator_stop_status_catalog_released_contract_only`, + `catalog_entry_type=blocked_not_approved_operator_stop`, + `operator_status=not_approved_branch_closed_new_request_required`, + `operator_action=keep_real_adapter_disabled_until_new_explicit_enablement_request`, + `display_severity=blocked`, + `next_allowed_entrypoint=new_explicit_enablement_request_only`, + `attempt_allowed=false`, `block_reason=new_explicit_enablement_request_required`, + `blocks_not_approved_extension=true`, + `enablement_status=not_enabled`, + `runtime_gate_state=validated_contract_only_not_enabled`, + `runtime_effect=contract_only_no_runtime_enablement`, + `allows_process_start=false`, `allows_payload_traffic=false`, and guardrails + that keep activation blocked, process start disabled, health probe disabled, + and `payload_traffic=none`. No new runtime image was required; the proof ran + on `rap-node-agent:codex-service-supervisor-20260513z52`. Verification + passed: C19Z159 live smoke. Artifact: + `artifacts/c19z159-remote-workspace-real-adapter-not-approved-outcome-operator-stop-status-catalog-closeout-summary-smoke-result.json`. +- C19Z160 Remote Workspace real-adapter not-approved outcome operator stop + status catalog closeout summary compatibility proof is complete on + docker-test. The compatibility smoke uses C19Z159 as source and validates + closeout fields, closeout notes, + `closeout_status=operator_stop_status_catalog_package_closed_contract_only`, + `catalog_entry_type=blocked_not_approved_operator_stop`, + `operator_status=not_approved_branch_closed_new_request_required`, + `operator_action=keep_real_adapter_disabled_until_new_explicit_enablement_request`, + `display_severity=blocked`, + `attempt_allowed=false`, `block_reason=new_explicit_enablement_request_required`, + `next_allowed_entrypoint=new_explicit_enablement_request_only`, + `blocks_not_approved_extension=true`, `enablement_status=not_enabled`, + `runtime_gate_state=validated_contract_only_not_enabled`, + `runtime_effect=contract_only_no_runtime_enablement`, + `allows_process_start=false`, `allows_payload_traffic=false`, and guardrails + that keep activation blocked, process start disabled, health probe disabled, + and `payload_traffic=none`. No new runtime image was required; the proof ran + on `rap-node-agent:codex-service-supervisor-20260513z52`. Verification + passed: C19Z160 live smoke. Artifact: + `artifacts/c19z160-remote-workspace-real-adapter-not-approved-outcome-operator-stop-status-catalog-closeout-summary-compatibility-smoke-result.json`. +- C19Z161 Remote Workspace real-adapter not-approved outcome operator stop + status final archive marker is complete on docker-test. The live smoke uses + C19Z160 as source and emits + `rap.remote_workspace_real_adapter_not_approved_outcome_operator_stop_status_final_archive_marker.v1` + with `archive_status=operator_stop_status_final_archived_contract_only`, + `closeout_status=operator_stop_status_catalog_package_closed_contract_only`, + `package_status=operator_stop_status_catalog_package_index_complete`, + `release_status=operator_stop_status_catalog_released_contract_only`, + `catalog_entry_type=blocked_not_approved_operator_stop`, + `operator_status=not_approved_branch_closed_new_request_required`, + `operator_action=keep_real_adapter_disabled_until_new_explicit_enablement_request`, + `display_severity=blocked`, + `next_allowed_entrypoint=new_explicit_enablement_request_only`, + `attempt_allowed=false`, `block_reason=new_explicit_enablement_request_required`, + `blocks_not_approved_extension=true`, + `enablement_status=not_enabled`, + `runtime_gate_state=validated_contract_only_not_enabled`, + `runtime_effect=contract_only_no_runtime_enablement`, + `allows_process_start=false`, `allows_payload_traffic=false`, and guardrails + that keep activation blocked, process start disabled, health probe disabled, + and `payload_traffic=none`. No new runtime image was required; the proof ran + on `rap-node-agent:codex-service-supervisor-20260513z52`. Verification + passed: C19Z161 live smoke. Artifact: + `artifacts/c19z161-remote-workspace-real-adapter-not-approved-outcome-operator-stop-status-final-archive-marker-smoke-result.json`. +- C19Z162 Remote Workspace real-adapter not-approved outcome operator stop + status final archive marker compatibility proof is complete on docker-test. + The compatibility smoke uses C19Z161 as source and validates archive fields, + archive notes, + `archive_status=operator_stop_status_final_archived_contract_only`, + `closeout_status=operator_stop_status_catalog_package_closed_contract_only`, + `catalog_entry_type=blocked_not_approved_operator_stop`, + `operator_status=not_approved_branch_closed_new_request_required`, + `operator_action=keep_real_adapter_disabled_until_new_explicit_enablement_request`, + `display_severity=blocked`, + `attempt_allowed=false`, `block_reason=new_explicit_enablement_request_required`, + `next_allowed_entrypoint=new_explicit_enablement_request_only`, + `blocks_not_approved_extension=true`, `enablement_status=not_enabled`, + `runtime_gate_state=validated_contract_only_not_enabled`, + `runtime_effect=contract_only_no_runtime_enablement`, + `allows_process_start=false`, `allows_payload_traffic=false`, and guardrails + that keep activation blocked, process start disabled, health probe disabled, + and `payload_traffic=none`. No new runtime image was required; the proof ran + on `rap-node-agent:codex-service-supervisor-20260513z52`. Verification + passed: C19Z162 live smoke. Artifact: + `artifacts/c19z162-remote-workspace-real-adapter-not-approved-outcome-operator-stop-status-final-archive-marker-compatibility-smoke-result.json`. +- C19Z163 Remote Workspace real-adapter not-approved outcome operator stop + status final archive manifest is complete on docker-test. The live smoke uses + C19Z162 as source and emits + `rap.remote_workspace_real_adapter_not_approved_outcome_operator_stop_status_final_archive_manifest.v1` + with `manifest_status=operator_stop_status_final_archive_manifest_complete`, + `archive_status=operator_stop_status_final_archived_contract_only`, + `closeout_status=operator_stop_status_catalog_package_closed_contract_only`, + `package_status=operator_stop_status_catalog_package_index_complete`, + `release_status=operator_stop_status_catalog_released_contract_only`, + `catalog_entry_type=blocked_not_approved_operator_stop`, + `operator_status=not_approved_branch_closed_new_request_required`, + `operator_action=keep_real_adapter_disabled_until_new_explicit_enablement_request`, + `display_severity=blocked`, + `next_allowed_entrypoint=new_explicit_enablement_request_only`, + `attempt_allowed=false`, `block_reason=new_explicit_enablement_request_required`, + `blocks_not_approved_extension=true`, + `enablement_status=not_enabled`, + `runtime_gate_state=validated_contract_only_not_enabled`, + `runtime_effect=contract_only_no_runtime_enablement`, + `allows_process_start=false`, `allows_payload_traffic=false`, and guardrails + that keep activation blocked, process start disabled, health probe disabled, + and `payload_traffic=none`. No new runtime image was required; the proof ran + on `rap-node-agent:codex-service-supervisor-20260513z52`. Verification + passed: C19Z163 live smoke. Artifact: + `artifacts/c19z163-remote-workspace-real-adapter-not-approved-outcome-operator-stop-status-final-archive-manifest-smoke-result.json`. +- C19Z164 Remote Workspace real-adapter not-approved outcome operator stop + status final archive manifest compatibility proof is complete on docker-test. + The compatibility smoke uses C19Z163 as source and validates manifest fields, + manifest notes, + `manifest_status=operator_stop_status_final_archive_manifest_complete`, + `archive_status=operator_stop_status_final_archived_contract_only`, + `operator_status=not_approved_branch_closed_new_request_required`, + `operator_action=keep_real_adapter_disabled_until_new_explicit_enablement_request`, + `display_severity=blocked`, + `attempt_allowed=false`, `block_reason=new_explicit_enablement_request_required`, + `next_allowed_entrypoint=new_explicit_enablement_request_only`, + `blocks_not_approved_extension=true`, `enablement_status=not_enabled`, + `runtime_gate_state=validated_contract_only_not_enabled`, + `runtime_effect=contract_only_no_runtime_enablement`, + `allows_process_start=false`, `allows_payload_traffic=false`, and guardrails + that keep activation blocked, process start disabled, health probe disabled, + and `payload_traffic=none`. No new runtime image was required; the proof ran + on `rap-node-agent:codex-service-supervisor-20260513z52`. Verification + passed: C19Z164 live smoke. Artifact: + `artifacts/c19z164-remote-workspace-real-adapter-not-approved-outcome-operator-stop-status-final-archive-manifest-compatibility-smoke-result.json`. +- C19Z165 Remote Workspace real-adapter not-approved outcome factory terminal + complete marker is complete on docker-test. The live smoke uses C19Z164 as + source and emits + `rap.remote_workspace_real_adapter_not_approved_outcome_factory_terminal_complete.v1` + with `terminal_status=factory_terminal_complete_contract_only`, + `factory_status=complete_no_more_not_approved_layers_required`, + `archive_status=operator_stop_status_final_archived_contract_only`, + `manifest_status=operator_stop_status_final_archive_manifest_complete`, + `operator_status=not_approved_branch_closed_new_request_required`, + `operator_action=keep_real_adapter_disabled_until_new_explicit_enablement_request`, + `display_severity=blocked`, + `next_allowed_entrypoint=new_explicit_enablement_request_only`, + `attempt_allowed=false`, `block_reason=new_explicit_enablement_request_required`, + `blocks_not_approved_extension=true`, + `enablement_status=not_enabled`, + `runtime_gate_state=validated_contract_only_not_enabled`, + `runtime_effect=contract_only_no_runtime_enablement`, + `allows_process_start=false`, `allows_payload_traffic=false`, and guardrails + that keep activation blocked, process start disabled, health probe disabled, + and `payload_traffic=none`. No new runtime image was required; the proof ran + on `rap-node-agent:codex-service-supervisor-20260513z52`. Verification + passed: C19Z165 live smoke. Artifact: + `artifacts/c19z165-remote-workspace-real-adapter-not-approved-outcome-factory-terminal-complete-smoke-result.json`. +- C19Z166 Remote Workspace real-adapter not-approved outcome factory terminal + complete compatibility proof is complete on docker-test. The compatibility + smoke uses C19Z165 as source and validates terminal fields, terminal notes, + `terminal_status=factory_terminal_complete_contract_only`, + `factory_status=complete_no_more_not_approved_layers_required`, + `operator_status=not_approved_branch_closed_new_request_required`, + `operator_action=keep_real_adapter_disabled_until_new_explicit_enablement_request`, + `display_severity=blocked`, + `attempt_allowed=false`, `block_reason=new_explicit_enablement_request_required`, + `next_allowed_entrypoint=new_explicit_enablement_request_only`, + `blocks_not_approved_extension=true`, `enablement_status=not_enabled`, + `runtime_gate_state=validated_contract_only_not_enabled`, + `runtime_effect=contract_only_no_runtime_enablement`, + `allows_process_start=false`, `allows_payload_traffic=false`, and guardrails + that keep activation blocked, process start disabled, health probe disabled, + and `payload_traffic=none`. No new runtime image was required; the proof ran + on `rap-node-agent:codex-service-supervisor-20260513z52`. Verification + passed: C19Z166 live smoke. Artifact: + `artifacts/c19z166-remote-workspace-real-adapter-not-approved-outcome-factory-terminal-complete-compatibility-smoke-result.json`. +- C20Z1 Remote Workspace real-adapter new explicit enablement request is open + as a contract-only transition on docker-test. The live smoke uses C19Z166 as + source and emits + `rap.remote_workspace_real_adapter_new_explicit_enablement_request.v1` with + `request_status=new_explicit_enablement_request_opened_contract_only`, + `requested_transition=from_not_approved_terminal_to_enablement_review`, + `source_factory_status=complete_no_more_not_approved_layers_required`, + `source_terminal_status=factory_terminal_complete_contract_only`, + `previous_operator_status=not_approved_branch_closed_new_request_required`, + `enablement_decision=pending_operator_validation`, + `operator_review_status=new_request_opened_pending_validation`, + `enablement_status=requested_not_enabled`, + `runtime_gate_state=new_request_contract_only_not_enabled`, + `runtime_effect=contract_only_no_runtime_enablement`, + `operator_default_action=keep_real_adapter_disabled_until_validation_complete`, + `next_required_phase=operator_validation_for_real_enablement`, + `allows_process_start=false`, `allows_payload_traffic=false`, and guardrails + that keep activation blocked, process start disabled, health probe disabled, + and `payload_traffic=none`. No runtime enablement was performed. Artifact: + `artifacts/c20z1-remote-workspace-real-adapter-new-explicit-enablement-request-smoke-result.json`. +- C20Z2 Remote Workspace real-adapter new explicit enablement request + compatibility proof is complete on docker-test. The compatibility smoke uses + C20Z1 as source and validates request fields, request notes, inherited + guardrails, `enablement_decision=pending_operator_validation`, + `enablement_status=requested_not_enabled`, + `runtime_gate_state=new_request_contract_only_not_enabled`, + `runtime_effect=contract_only_no_runtime_enablement`, + `operator_default_action=keep_real_adapter_disabled_until_validation_complete`, + `next_required_phase=operator_validation_for_real_enablement`, + `allows_process_start=false`, and `allows_payload_traffic=false`. No runtime + enablement was performed. Verification passed: C20Z2 live smoke. Artifact: + `artifacts/c20z2-remote-workspace-real-adapter-new-explicit-enablement-request-compatibility-smoke-result.json`. +- C20Z3 Remote Workspace real-adapter operator validation intake is complete + on docker-test. The live smoke uses C20Z2 as source and emits + `rap.remote_workspace_real_adapter_operator_validation_intake.v1` with + `intake_status=operator_validation_intake_open_contract_only`, + `validation_scope=real_adapter_enablement_pre_runtime_review`, + `enablement_decision=pending_operator_validation`, + `enablement_status=requested_not_enabled`, + `runtime_gate_state=validation_intake_contract_only_not_enabled`, + `runtime_effect=contract_only_no_runtime_enablement`, + `next_required_phase=operator_validation_checklist`, + `allows_process_start=false`, and `allows_payload_traffic=false`. No runtime + enablement was performed. Artifact: + `artifacts/c20z3-remote-workspace-real-adapter-operator-validation-intake-smoke-result.json`. +- C20Z4 Remote Workspace real-adapter operator validation checklist is + complete on docker-test. The live smoke uses C20Z3 as source and emits + `rap.remote_workspace_real_adapter_operator_validation_checklist.v1` with + `checklist_status=complete_contract_only`, all required validation items + satisfied by contract, `remaining_items=[]`, + `enablement_status=validated_not_enabled`, + `runtime_gate_state=operator_validation_complete_contract_only_not_enabled`, + `runtime_effect=contract_only_no_runtime_enablement`, + `next_required_phase=c20_stage_closeout`, + `allows_process_start=false`, and `allows_payload_traffic=false`. No runtime + enablement was performed. Artifact: + `artifacts/c20z4-remote-workspace-real-adapter-operator-validation-checklist-smoke-result.json`. +- C20Z5 Remote Workspace real-adapter operator validation closeout is complete + on docker-test. The live smoke uses C20Z4 as source and emits + `rap.remote_workspace_real_adapter_operator_validation_closeout.v1` with + `closeout_status=complete_contract_only`, + `validation_chain_status=complete_contract_only`, + `enablement_boundary=runtime_enablement_requires_next_explicit_runtime_stage`, + `enablement_decision=validated_contract_only_not_enabled`, + `enablement_status=validated_not_enabled`, + `runtime_gate_state=validated_contract_only_not_enabled`, + `runtime_effect=contract_only_no_runtime_enablement`, + `next_required_phase=c20_terminal_complete`, + `allows_process_start=false`, and `allows_payload_traffic=false`. No runtime + enablement was performed. Artifact: + `artifacts/c20z5-remote-workspace-real-adapter-operator-validation-closeout-smoke-result.json`. +- C20Z6 Remote Workspace real-adapter stage terminal-complete compatibility + proof is complete on docker-test. The compatibility smoke uses C20Z5 as + source and emits + `rap.remote_workspace_real_adapter_c20_stage_terminal_complete.v1` with + `terminal_status=stage_terminal_complete_contract_only`, + `stage_status=complete_no_more_c20_layers_required`, + `validation_chain_status=complete_contract_only`, + `enablement_status=validated_not_enabled`, + `runtime_gate_state=validated_contract_only_not_enabled`, + `runtime_effect=contract_only_no_runtime_enablement`, + `next_allowed_entrypoint=next_explicit_runtime_enablement_stage_only`, + `allows_process_start=false`, and `allows_payload_traffic=false`. No runtime + enablement was performed. Verification passed: C20Z6 live smoke. Artifact: + `artifacts/c20z6-remote-workspace-real-adapter-stage-terminal-complete-compatibility-smoke-result.json`. The current phase is NOT: - full mesh routing implementation diff --git a/_tmp_release_node_agent_0.2.257-vpnfarm/rap-host-agent-0.2.257-vpnfarm-linux-amd64 b/_tmp_release_node_agent_0.2.257-vpnfarm/rap-host-agent-0.2.257-vpnfarm-linux-amd64 new file mode 100644 index 0000000..fb11950 Binary files /dev/null and b/_tmp_release_node_agent_0.2.257-vpnfarm/rap-host-agent-0.2.257-vpnfarm-linux-amd64 differ diff --git a/_tmp_release_node_agent_0.2.257-vpnfarm/rap-node-agent-0.2.257-vpnfarm-linux-amd64 b/_tmp_release_node_agent_0.2.257-vpnfarm/rap-node-agent-0.2.257-vpnfarm-linux-amd64 new file mode 100644 index 0000000..4d61e9a Binary files /dev/null and b/_tmp_release_node_agent_0.2.257-vpnfarm/rap-node-agent-0.2.257-vpnfarm-linux-amd64 differ diff --git a/_tmp_release_node_agent_0.2.258-vpnfarm/rap-host-agent-0.2.258-vpnfarm-linux-amd64 b/_tmp_release_node_agent_0.2.258-vpnfarm/rap-host-agent-0.2.258-vpnfarm-linux-amd64 new file mode 100644 index 0000000..bd5712b Binary files /dev/null and b/_tmp_release_node_agent_0.2.258-vpnfarm/rap-host-agent-0.2.258-vpnfarm-linux-amd64 differ diff --git a/_tmp_release_node_agent_0.2.258-vpnfarm/rap-node-agent-0.2.258-vpnfarm-linux-amd64 b/_tmp_release_node_agent_0.2.258-vpnfarm/rap-node-agent-0.2.258-vpnfarm-linux-amd64 new file mode 100644 index 0000000..4f60f3c Binary files /dev/null and b/_tmp_release_node_agent_0.2.258-vpnfarm/rap-node-agent-0.2.258-vpnfarm-linux-amd64 differ diff --git a/_tmp_release_node_agent_0.2.260-vpnfarm/rap-node-agent-0.2.260-vpnfarm-linux-amd64 b/_tmp_release_node_agent_0.2.260-vpnfarm/rap-node-agent-0.2.260-vpnfarm-linux-amd64 new file mode 100644 index 0000000..35e1758 Binary files /dev/null and b/_tmp_release_node_agent_0.2.260-vpnfarm/rap-node-agent-0.2.260-vpnfarm-linux-amd64 differ diff --git a/_tmp_release_node_agent_0.2.262-vpnfarm/rap-node-agent-0.2.262-vpnfarm-linux-amd64 b/_tmp_release_node_agent_0.2.262-vpnfarm/rap-node-agent-0.2.262-vpnfarm-linux-amd64 new file mode 100644 index 0000000..b0c6fa9 Binary files /dev/null and b/_tmp_release_node_agent_0.2.262-vpnfarm/rap-node-agent-0.2.262-vpnfarm-linux-amd64 differ diff --git a/_tmp_release_node_agent_0.2.263-vpnfarm/rap-node-agent-0.2.263-vpnfarm-linux-amd64 b/_tmp_release_node_agent_0.2.263-vpnfarm/rap-node-agent-0.2.263-vpnfarm-linux-amd64 new file mode 100644 index 0000000..e78c220 Binary files /dev/null and b/_tmp_release_node_agent_0.2.263-vpnfarm/rap-node-agent-0.2.263-vpnfarm-linux-amd64 differ diff --git a/_tmp_release_node_agent_0.2.264-vpnroutefix/rap-node-agent-0.2.264-vpnroutefix-docker-amd64.tar b/_tmp_release_node_agent_0.2.264-vpnroutefix/rap-node-agent-0.2.264-vpnroutefix-docker-amd64.tar new file mode 100644 index 0000000..4385f33 Binary files /dev/null and b/_tmp_release_node_agent_0.2.264-vpnroutefix/rap-node-agent-0.2.264-vpnroutefix-docker-amd64.tar differ diff --git a/_tmp_release_node_agent_0.2.264-vpnroutefix/rap-node-agent-0.2.264-vpnroutefix-linux-amd64 b/_tmp_release_node_agent_0.2.264-vpnroutefix/rap-node-agent-0.2.264-vpnroutefix-linux-amd64 new file mode 100644 index 0000000..cf0dd4e Binary files /dev/null and b/_tmp_release_node_agent_0.2.264-vpnroutefix/rap-node-agent-0.2.264-vpnroutefix-linux-amd64 differ diff --git a/_tmp_release_node_agent_0.2.265-vpnfarmonly/rap-node-agent-0.2.265-vpnfarmonly-docker-amd64.tar b/_tmp_release_node_agent_0.2.265-vpnfarmonly/rap-node-agent-0.2.265-vpnfarmonly-docker-amd64.tar new file mode 100644 index 0000000..6cb3f01 Binary files /dev/null and b/_tmp_release_node_agent_0.2.265-vpnfarmonly/rap-node-agent-0.2.265-vpnfarmonly-docker-amd64.tar differ diff --git a/_tmp_release_node_agent_0.2.265-vpnfarmonly/rap-node-agent-linux-amd64 b/_tmp_release_node_agent_0.2.265-vpnfarmonly/rap-node-agent-linux-amd64 new file mode 100644 index 0000000..1f807e3 Binary files /dev/null and b/_tmp_release_node_agent_0.2.265-vpnfarmonly/rap-node-agent-linux-amd64 differ diff --git a/_tmp_release_node_agent_0.2.266-vpnfarmonly/rap-node-agent-0.2.266-vpnfarmonly-docker-amd64.tar b/_tmp_release_node_agent_0.2.266-vpnfarmonly/rap-node-agent-0.2.266-vpnfarmonly-docker-amd64.tar new file mode 100644 index 0000000..641eb11 Binary files /dev/null and b/_tmp_release_node_agent_0.2.266-vpnfarmonly/rap-node-agent-0.2.266-vpnfarmonly-docker-amd64.tar differ diff --git a/_tmp_release_node_agent_0.2.266-vpnfarmonly/rap-node-agent-linux-amd64 b/_tmp_release_node_agent_0.2.266-vpnfarmonly/rap-node-agent-linux-amd64 new file mode 100644 index 0000000..fd98d98 Binary files /dev/null and b/_tmp_release_node_agent_0.2.266-vpnfarmonly/rap-node-agent-linux-amd64 differ diff --git a/_tmp_release_node_agent_0.2.267-vpnfarmonly/rap-node-agent-0.2.267-vpnfarmonly-docker-amd64.tar b/_tmp_release_node_agent_0.2.267-vpnfarmonly/rap-node-agent-0.2.267-vpnfarmonly-docker-amd64.tar new file mode 100644 index 0000000..0e5be09 Binary files /dev/null and b/_tmp_release_node_agent_0.2.267-vpnfarmonly/rap-node-agent-0.2.267-vpnfarmonly-docker-amd64.tar differ diff --git a/_tmp_release_node_agent_0.2.267-vpnfarmonly/rap-node-agent-linux-amd64 b/_tmp_release_node_agent_0.2.267-vpnfarmonly/rap-node-agent-linux-amd64 new file mode 100644 index 0000000..afcd5c1 Binary files /dev/null and b/_tmp_release_node_agent_0.2.267-vpnfarmonly/rap-node-agent-linux-amd64 differ diff --git a/agents/rap-node-agent/cmd/rap-host-agent/main.go b/agents/rap-node-agent/cmd/rap-host-agent/main.go index dae6908..971f19c 100644 --- a/agents/rap-node-agent/cmd/rap-host-agent/main.go +++ b/agents/rap-node-agent/cmd/rap-host-agent/main.go @@ -2,6 +2,7 @@ package main import ( "context" + "encoding/json" "flag" "fmt" "log" @@ -58,6 +59,14 @@ func main() { if err := runUpdateLoop(ctx, os.Args[2:]); err != nil { log.Fatalf("update-loop failed: %v", err) } + case "monitor-loop": + if err := runMonitorLoop(ctx, os.Args[2:]); err != nil { + log.Fatalf("monitor-loop failed: %v", err) + } + case "monitor-once": + if err := runMonitorOnce(ctx, os.Args[2:]); err != nil { + log.Fatalf("monitor-once failed: %v", err) + } case "install-updater": if err := runInstallUpdater(ctx, os.Args[2:]); err != nil { log.Fatalf("install-updater failed: %v", err) @@ -288,6 +297,9 @@ func runInstall(ctx context.Context, args []string) error { return err } fmt.Print(result.Unit) + if result.MonitorUnit != "" { + fmt.Print(result.MonitorUnit) + } } return nil } @@ -304,7 +316,7 @@ func runInstall(ctx context.Context, args []string) error { if err != nil { return err } - fmt.Printf("updater_service=%s unit=%s binary=%s started=%t\n", serviceResult.UnitName, serviceResult.UnitPath, serviceResult.BinaryPath, serviceResult.Started) + fmt.Printf("updater_service=%s unit=%s binary=%s started=%t monitor_service=%s\n", serviceResult.UnitName, serviceResult.UnitPath, serviceResult.BinaryPath, serviceResult.Started, serviceResult.MonitorUnitName) } fmt.Println("next: approve the join request in the platform admin panel, then the node-agent will finish bootstrap and start heartbeats") return nil @@ -429,6 +441,75 @@ func runUpdateLoop(ctx context.Context, args []string) error { return (hostagent.DockerManager{}).RunUpdateLoop(ctx, cfg) } +func runMonitorLoop(ctx context.Context, args []string) error { + cfg, err := parseMonitor(args) + if err != nil { + return err + } + return hostagent.RunMonitorLoop(ctx, cfg) +} + +func runMonitorOnce(ctx context.Context, args []string) error { + cfg, err := parseMonitor(args) + if err != nil { + return err + } + cfg.MaxRuns = 1 + result := hostagent.RunMonitorOnce(ctx, cfg) + if err := json.NewEncoder(os.Stdout).Encode(result); err != nil { + return err + } + return nil +} + +func parseMonitor(args []string) (hostagent.MonitorConfig, error) { + fs := flag.NewFlagSet("monitor-loop", flag.ContinueOnError) + cfg := hostagent.MonitorConfig{} + var intervalSeconds int + var initialDelaySeconds int + var maxRuns int + var restartCooldownSeconds int + var staleRestartingSeconds int + var tmpMinAgeMinutes int + watchContainers := repeatedFlag{} + fs.StringVar(&cfg.BackendURL, "backend-url", getenv("RAP_BACKEND_URL", ""), "Control Plane API base URL used for monitor status reports.") + fs.StringVar(&cfg.ClusterID, "cluster-id", getenv("RAP_CLUSTER_ID", ""), "Cluster ID.") + fs.StringVar(&cfg.NodeID, "node-id", getenv("RAP_NODE_ID", ""), "Already enrolled node ID.") + fs.StringVar(&cfg.StateDir, "state-dir", getenv("RAP_NODE_STATE_DIR", hostagent.DefaultStateDir), "Host path containing node-agent identity.json.") + fs.StringVar(&cfg.Product, "product", getenv("RAP_MONITOR_PRODUCT", hostagent.DefaultMonitorProduct), "Status product name.") + fs.StringVar(&cfg.CurrentVersion, "current-version", getenv("RAP_HOST_AGENT_VERSION", agent.Version), "Current rap-host-agent version.") + fs.StringVar(&cfg.DockerBinary, "docker-binary", getenv("RAP_DOCKER_BINARY", "docker"), "Docker CLI binary.") + fs.StringVar(&cfg.DiskPath, "disk-path", getenv("RAP_MONITOR_DISK_PATH", "/"), "Filesystem path used for disk usage checks.") + fs.StringVar(&cfg.TmpDir, "tmp-dir", getenv("RAP_MONITOR_TMP_DIR", "/tmp"), "Temporary directory cleaned under pressure.") + fs.StringVar(&cfg.StatusFile, "status-file", getenv("RAP_MONITOR_STATUS_FILE", ""), "Optional JSON status file written after every run.") + fs.IntVar(&intervalSeconds, "interval-seconds", getenvInt("RAP_MONITOR_INTERVAL_SECONDS", hostagent.DefaultMonitorIntervalSeconds), "Seconds between monitor checks.") + fs.IntVar(&initialDelaySeconds, "initial-delay-seconds", getenvInt("RAP_MONITOR_INITIAL_DELAY_SECONDS", 0), "Seconds to wait before first monitor check.") + fs.IntVar(&maxRuns, "max-runs", getenvInt("RAP_MONITOR_MAX_RUNS", 0), "Maximum monitor iterations. Use 0 to run until stopped.") + fs.IntVar(&cfg.DiskWarnPercent, "disk-warn-percent", getenvInt("RAP_MONITOR_DISK_WARN_PERCENT", hostagent.DefaultMonitorDiskWarnPercent), "Disk used percent that reports warning.") + fs.IntVar(&cfg.DiskCleanupPercent, "disk-cleanup-percent", getenvInt("RAP_MONITOR_DISK_CLEANUP_PERCENT", hostagent.DefaultMonitorDiskCleanupPercent), "Disk used percent that triggers cleanup.") + fs.IntVar(&cfg.DiskCriticalPercent, "disk-critical-percent", getenvInt("RAP_MONITOR_DISK_CRITICAL_PERCENT", hostagent.DefaultMonitorDiskCriticalPercent), "Disk used percent that reports failure after cleanup.") + fs.IntVar(&restartCooldownSeconds, "restart-cooldown-seconds", getenvInt("RAP_MONITOR_RESTART_COOLDOWN_SECONDS", hostagent.DefaultMonitorRestartCooldownSec), "Minimum seconds between repeated restarts of the same target.") + fs.IntVar(&staleRestartingSeconds, "stale-restarting-seconds", getenvInt("RAP_MONITOR_STALE_RESTARTING_SECONDS", hostagent.DefaultMonitorStaleRestartingSec), "Seconds after which docker restarting state is considered stuck.") + fs.IntVar(&tmpMinAgeMinutes, "tmp-min-age-minutes", getenvInt("RAP_MONITOR_TMP_MIN_AGE_MINUTES", hostagent.DefaultMonitorTmpMinAgeMinutes), "Minimum age for /tmp rap-* and go-build* cleanup.") + fs.BoolVar(&cfg.RestartContainers, "restart-containers", getenvBool("RAP_MONITOR_RESTART_CONTAINERS", true), "Start/restart watched containers when they are stopped, unhealthy, or stuck restarting.") + fs.BoolVar(&cfg.CleanupDocker, "cleanup-docker", getenvBool("RAP_MONITOR_CLEANUP_DOCKER", true), "Run safe docker prune cleanup when disk is above cleanup threshold.") + fs.Var(&watchContainers, "watch-container", "Docker container to watch and heal; may be repeated.") + if err := fs.Parse(args); err != nil { + return hostagent.MonitorConfig{}, err + } + cfg.WatchContainers = watchContainers + cfg.Interval = time.Duration(intervalSeconds) * time.Second + cfg.InitialDelay = time.Duration(initialDelaySeconds) * time.Second + cfg.MaxRuns = maxRuns + cfg.RestartCooldown = time.Duration(restartCooldownSeconds) * time.Second + cfg.StaleRestartingAfter = time.Duration(staleRestartingSeconds) * time.Second + cfg.TmpMinAge = time.Duration(tmpMinAgeMinutes) * time.Minute + cfg.Logf = func(format string, args ...any) { + fmt.Printf(format+"\n", args...) + } + return cfg, nil +} + func firstNonEmptyLocal(values ...string) string { for _, value := range values { if strings.TrimSpace(value) != "" { @@ -444,6 +525,8 @@ func runInstallUpdater(ctx context.Context, args []string) error { service := hostagent.UpdateServiceConfig{} var dryRun bool var selfUpdater bool + var monitorEnabled bool + monitorContainers := repeatedFlag{} fs.StringVar(&runtimeCfg.BackendURL, "backend-url", getenv("RAP_BACKEND_URL", ""), "Control Plane API base URL.") fs.StringVar(&runtimeCfg.ClusterID, "cluster-id", getenv("RAP_CLUSTER_ID", ""), "Cluster ID.") fs.StringVar(&runtimeCfg.ContainerName, "container-name", getenv("RAP_NODE_AGENT_CONTAINER", hostagent.DefaultContainerName), "Docker container name to update.") @@ -456,6 +539,14 @@ func runInstallUpdater(ctx context.Context, args []string) error { fs.IntVar(&service.HealthTimeoutSec, "health-timeout-seconds", getenvInt("RAP_UPDATE_HEALTH_TIMEOUT_SECONDS", 30), "Updated container running-state timeout in seconds.") fs.StringVar(&service.BinaryInstallPath, "binary-path", getenv("RAP_HOST_AGENT_BINARY_PATH", hostagent.DefaultHostAgentInstallPath), "Persistent host path for rap-host-agent binary used by the service.") fs.BoolVar(&selfUpdater, "self-updater-enabled", getenvBool("RAP_HOST_AGENT_SELF_UPDATE_ENABLED", true), "Install and start one global host-agent binary self-updater service.") + fs.BoolVar(&monitorEnabled, "monitor-enabled", getenvBool("RAP_HOST_AGENT_MONITOR_ENABLED", true), "Install and start the local host monitor service.") + fs.IntVar(&service.MonitorIntervalSec, "monitor-interval-seconds", getenvInt("RAP_MONITOR_INTERVAL_SECONDS", hostagent.DefaultMonitorIntervalSeconds), "Seconds between monitor checks.") + fs.StringVar(&service.MonitorStatusFile, "monitor-status-file", getenv("RAP_MONITOR_STATUS_FILE", ""), "Optional JSON status file written by the monitor.") + fs.IntVar(&service.MonitorDiskWarn, "monitor-disk-warn-percent", getenvInt("RAP_MONITOR_DISK_WARN_PERCENT", hostagent.DefaultMonitorDiskWarnPercent), "Disk used percent that reports warning.") + fs.IntVar(&service.MonitorDiskCleanup, "monitor-disk-cleanup-percent", getenvInt("RAP_MONITOR_DISK_CLEANUP_PERCENT", hostagent.DefaultMonitorDiskCleanupPercent), "Disk used percent that triggers cleanup.") + fs.IntVar(&service.MonitorDiskCritical, "monitor-disk-critical-percent", getenvInt("RAP_MONITOR_DISK_CRITICAL_PERCENT", hostagent.DefaultMonitorDiskCriticalPercent), "Disk used percent that reports failure after cleanup.") + fs.BoolVar(&service.MonitorCleanupDocker, "monitor-cleanup-docker", getenvBool("RAP_MONITOR_CLEANUP_DOCKER", true), "Run safe docker prune cleanup when disk is above cleanup threshold.") + fs.Var(&monitorContainers, "monitor-container", "Extra Docker container watched by monitor; may be repeated.") fs.BoolVar(&dryRun, "dry-run", false, "Print the systemd unit without installing it.") if err := fs.Parse(args); err != nil { return err @@ -465,6 +556,8 @@ func runInstallUpdater(ctx context.Context, args []string) error { service.DryRun = dryRun service.InstallSelfUpdater = selfUpdater service.SelfUpdateVersion = agent.Version + service.InstallMonitor = monitorEnabled + service.MonitorContainers = monitorContainers result, err := (hostagent.DockerManager{}).InstallUpdateService(ctx, service) if err != nil { return err @@ -474,9 +567,12 @@ func runInstallUpdater(ctx context.Context, args []string) error { if result.SelfUnit != "" { fmt.Print(result.SelfUnit) } + if result.MonitorUnit != "" { + fmt.Print(result.MonitorUnit) + } return nil } - fmt.Printf("updater_service=%s unit=%s binary=%s started=%t self_updater=%s\n", result.UnitName, result.UnitPath, result.BinaryPath, result.Started, result.SelfUnitName) + fmt.Printf("updater_service=%s unit=%s binary=%s started=%t self_updater=%s monitor_service=%s\n", result.UnitName, result.UnitPath, result.BinaryPath, result.Started, result.SelfUnitName, result.MonitorUnitName) return nil } @@ -572,6 +668,7 @@ func parseInstall(args []string) (installCommandConfig, error) { var installToken string var autoUpdateEnabled bool autoUpdate := hostagent.UpdateServiceConfig{} + monitorContainers := repeatedFlag{} fs.StringVar(&cfg.BackendURL, "backend-url", getenv("RAP_BACKEND_URL", ""), "Control Plane API base URL.") fs.StringVar(&cfg.ClusterID, "cluster-id", getenv("RAP_CLUSTER_ID", ""), "Cluster ID.") fs.StringVar(&cfg.JoinToken, "join-token", getenv("RAP_JOIN_TOKEN", ""), "One-time join token for first enrollment.") @@ -591,6 +688,7 @@ func parseInstall(args []string) (installCommandConfig, error) { fs.BoolVar(&dryRun, "dry-run", false, "Print the docker command with secrets redacted.") fs.BoolVar(&autoUpdateEnabled, "auto-update-enabled", getenvBool("RAP_AUTO_UPDATE_ENABLED", true), "Install and start the local update-loop service.") fs.BoolVar(&autoUpdate.InstallSelfUpdater, "host-agent-self-update-enabled", getenvBool("RAP_HOST_AGENT_SELF_UPDATE_ENABLED", true), "Install and start one global host-agent binary self-updater service.") + fs.BoolVar(&autoUpdate.InstallMonitor, "host-agent-monitor-enabled", getenvBool("RAP_HOST_AGENT_MONITOR_ENABLED", true), "Install and start the local host monitor service.") fs.StringVar(&autoUpdate.CurrentVersion, "auto-update-current-version", getenv("RAP_NODE_AGENT_VERSION", agent.Version), "Initial node-agent version used by update-loop before the first successful update.") fs.StringVar(&autoUpdate.SelfUpdateVersion, "host-agent-current-version", getenv("RAP_HOST_AGENT_VERSION", agent.Version), "Initial host-agent binary version used by the self-updater.") fs.StringVar(&autoUpdate.Channel, "auto-update-channel", getenv("RAP_UPDATE_CHANNEL", ""), "Optional update channel override for update-loop.") @@ -599,6 +697,12 @@ func parseInstall(args []string) (installCommandConfig, error) { fs.Float64Var(&autoUpdate.Jitter, "auto-update-jitter", getenvFloat("RAP_UPDATE_JITTER", 0.15), "Update-loop interval jitter, 0..1.") fs.IntVar(&autoUpdate.HealthTimeoutSec, "auto-update-health-timeout-seconds", getenvInt("RAP_UPDATE_HEALTH_TIMEOUT_SECONDS", 30), "Updated container running-state timeout in seconds.") fs.StringVar(&autoUpdate.BinaryInstallPath, "auto-update-binary-path", getenv("RAP_HOST_AGENT_BINARY_PATH", hostagent.DefaultHostAgentInstallPath), "Persistent host path for rap-host-agent binary used by the service.") + fs.IntVar(&autoUpdate.MonitorIntervalSec, "monitor-interval-seconds", getenvInt("RAP_MONITOR_INTERVAL_SECONDS", hostagent.DefaultMonitorIntervalSeconds), "Seconds between monitor checks.") + fs.StringVar(&autoUpdate.MonitorStatusFile, "monitor-status-file", getenv("RAP_MONITOR_STATUS_FILE", ""), "Optional JSON status file written by the monitor.") + fs.IntVar(&autoUpdate.MonitorDiskWarn, "monitor-disk-warn-percent", getenvInt("RAP_MONITOR_DISK_WARN_PERCENT", hostagent.DefaultMonitorDiskWarnPercent), "Disk used percent that reports warning.") + fs.IntVar(&autoUpdate.MonitorDiskCleanup, "monitor-disk-cleanup-percent", getenvInt("RAP_MONITOR_DISK_CLEANUP_PERCENT", hostagent.DefaultMonitorDiskCleanupPercent), "Disk used percent that triggers cleanup.") + fs.IntVar(&autoUpdate.MonitorDiskCritical, "monitor-disk-critical-percent", getenvInt("RAP_MONITOR_DISK_CRITICAL_PERCENT", hostagent.DefaultMonitorDiskCriticalPercent), "Disk used percent that reports failure after cleanup.") + fs.BoolVar(&autoUpdate.MonitorCleanupDocker, "monitor-cleanup-docker", getenvBool("RAP_MONITOR_CLEANUP_DOCKER", true), "Run safe docker prune cleanup when disk is above cleanup threshold.") fs.BoolVar(&cfg.WorkloadSupervisionEnabled, "workload-supervision-enabled", getenvBool("RAP_WORKLOAD_SUPERVISION_ENABLED", false), "Enable node-agent workload status reporting.") fs.BoolVar(&cfg.MeshSyntheticRuntimeEnabled, "mesh-synthetic-runtime-enabled", getenvBool("RAP_MESH_SYNTHETIC_RUNTIME_ENABLED", false), "Enable synthetic mesh runtime.") fs.BoolVar(&cfg.MeshProductionForwardingEnabled, "mesh-production-forwarding-enabled", getenvBool("RAP_MESH_PRODUCTION_FORWARDING_ENABLED", false), "Enable production forwarding gate; runtime still fail-closed if unavailable.") @@ -622,12 +726,14 @@ func parseInstall(args []string) (installCommandConfig, error) { fs.Var(&extraEnv, "env", "Extra KEY=VALUE env passed to node-agent container; may be repeated.") fs.Var(&extraRunArg, "docker-run-arg", "Extra raw docker run argument; may be repeated.") fs.Var(&imageArtifactURL, "image-artifact-url", "Docker image tar artifact URL to docker load before running; may be repeated.") + fs.Var(&monitorContainers, "monitor-container", "Extra Docker container watched by monitor; may be repeated.") if err := fs.Parse(args); err != nil { return installCommandConfig{}, err } cfg.ExtraEnv = extraEnv cfg.AdditionalDockerRunArgs = extraRunArg cfg.ImageArtifactURLs = append(cfg.ImageArtifactURLs, imageArtifactURL...) + autoUpdate.MonitorContainers = monitorContainers if strings.TrimSpace(profileURL) != "" || strings.TrimSpace(installToken) != "" { profile, err := hostagent.FetchDockerInstallProfile(context.Background(), hostagent.ProfileRequest{ URL: profileURL, @@ -738,6 +844,8 @@ func usage() { rap-host-agent install-updater -backend-url URL -cluster-id ID -state-dir DIR -container-name NAME rap-host-agent update-host-agent -backend-url URL -cluster-id ID -state-dir DIR rap-host-agent update-host-agent-loop -backend-url URL -cluster-id ID -state-dir DIR + rap-host-agent monitor-loop -backend-url URL -cluster-id ID -state-dir DIR --watch-container NAME + rap-host-agent monitor-once -backend-url URL -cluster-id ID -state-dir DIR --watch-container NAME rap-host-agent update -backend-url URL -cluster-id ID -node-id ID [-container-name NAME] rap-host-agent update-loop -backend-url URL -cluster-id ID -node-id ID [-container-name NAME] rap-host-agent status [-container-name NAME]`) diff --git a/agents/rap-node-agent/internal/client/client.go b/agents/rap-node-agent/internal/client/client.go index 98fc616..c153ff4 100644 --- a/agents/rap-node-agent/internal/client/client.go +++ b/agents/rap-node-agent/internal/client/client.go @@ -222,6 +222,11 @@ type NodeVPNAssignmentLeaseRenewRequest struct { TTLSeconds int `json:"ttl_seconds"` } +type NodeVPNAssignmentLeaseAcquireRequest struct { + TTLSeconds int `json:"ttl_seconds"` + Metadata map[string]any `json:"metadata,omitempty"` +} + type MeshLinkObservationRequest struct { SourceNodeID string `json:"source_node_id"` TargetNodeID string `json:"target_node_id"` @@ -658,6 +663,17 @@ func (c *Client) ReportNodeVPNAssignmentStatus(ctx context.Context, clusterID, n return c.postJSON(ctx, path, request, nil) } +func (c *Client) AcquireNodeVPNAssignmentLease(ctx context.Context, clusterID, nodeID, vpnConnectionID string, request NodeVPNAssignmentLeaseAcquireRequest) (*NodeVPNAssignmentLease, error) { + var response struct { + Lease NodeVPNAssignmentLease `json:"lease"` + } + path := fmt.Sprintf("/clusters/%s/nodes/%s/vpn/assignments/%s/lease/acquire", clusterID, nodeID, vpnConnectionID) + if err := c.postJSON(ctx, path, request, &response); err != nil { + return nil, err + } + return &response.Lease, nil +} + func (c *Client) RenewNodeVPNAssignmentLease(ctx context.Context, clusterID, nodeID, vpnConnectionID, leaseID string, request NodeVPNAssignmentLeaseRenewRequest) error { path := fmt.Sprintf("/clusters/%s/nodes/%s/vpn/assignments/%s/lease/%s/renew", clusterID, nodeID, vpnConnectionID, leaseID) return c.postJSON(ctx, path, request, nil) diff --git a/agents/rap-node-agent/internal/config/config.go b/agents/rap-node-agent/internal/config/config.go index cc648ee..27cc585 100644 --- a/agents/rap-node-agent/internal/config/config.go +++ b/agents/rap-node-agent/internal/config/config.go @@ -40,6 +40,10 @@ type Config struct { MeshSyntheticConfigPath string MeshPeerEndpointsJSON string MeshSyntheticRoutesJSON string + RemoteWorkspaceRealAdapterEnabled bool + RemoteWorkspaceRealAdapterCommand string + RemoteWorkspaceRealAdapterArgsJSON string + RemoteWorkspaceRealAdapterWorkDir string } func Load(args []string, env map[string]string) (Config, error) { @@ -73,6 +77,10 @@ func Load(args []string, env map[string]string) (Config, error) { fs.StringVar(&cfg.MeshSyntheticConfigPath, "mesh-synthetic-config", getEnv(env, "RAP_MESH_SYNTHETIC_CONFIG", ""), "Path to scoped synthetic mesh config snapshot. Preferred over debug JSON env.") fs.StringVar(&cfg.MeshPeerEndpointsJSON, "mesh-peer-endpoints-json", getEnv(env, "RAP_MESH_PEER_ENDPOINTS_JSON", ""), "JSON object mapping peer node_id to synthetic mesh endpoint URL.") fs.StringVar(&cfg.MeshSyntheticRoutesJSON, "mesh-synthetic-routes-json", getEnv(env, "RAP_MESH_SYNTHETIC_ROUTES_JSON", ""), "JSON array of synthetic mesh routes for test-only runtime.") + fs.BoolVar(&cfg.RemoteWorkspaceRealAdapterEnabled, "remote-workspace-real-adapter-enabled", getEnvBool(env, "RAP_REMOTE_WORKSPACE_REAL_ADAPTER_ENABLED", false), "Request future real remote workspace adapter supervision. Disabled until the real runtime stage is implemented.") + fs.StringVar(&cfg.RemoteWorkspaceRealAdapterCommand, "remote-workspace-real-adapter-command", getEnv(env, "RAP_REMOTE_WORKSPACE_REAL_ADAPTER_COMMAND", ""), "Future real remote workspace adapter command path. Redacted from status payloads.") + fs.StringVar(&cfg.RemoteWorkspaceRealAdapterArgsJSON, "remote-workspace-real-adapter-args-json", getEnv(env, "RAP_REMOTE_WORKSPACE_REAL_ADAPTER_ARGS_JSON", ""), "Future real remote workspace adapter args JSON. Redacted from status payloads.") + fs.StringVar(&cfg.RemoteWorkspaceRealAdapterWorkDir, "remote-workspace-real-adapter-workdir", getEnv(env, "RAP_REMOTE_WORKSPACE_REAL_ADAPTER_WORKDIR", ""), "Future real remote workspace adapter working directory. Redacted from status payloads.") heartbeatSeconds := getEnvInt(env, "RAP_HEARTBEAT_INTERVAL_SECONDS", 15) fs.DurationVar(&cfg.HeartbeatInterval, "heartbeat-interval", time.Duration(heartbeatSeconds)*time.Second, "Heartbeat interval.") enrollmentPollIntervalSeconds := getEnvInt(env, "RAP_ENROLLMENT_POLL_INTERVAL_SECONDS", 5) @@ -100,6 +108,9 @@ func Load(args []string, env map[string]string) (Config, error) { cfg.MeshSyntheticConfigPath = strings.TrimSpace(cfg.MeshSyntheticConfigPath) cfg.MeshPeerEndpointsJSON = strings.TrimSpace(cfg.MeshPeerEndpointsJSON) cfg.MeshSyntheticRoutesJSON = strings.TrimSpace(cfg.MeshSyntheticRoutesJSON) + cfg.RemoteWorkspaceRealAdapterCommand = strings.TrimSpace(cfg.RemoteWorkspaceRealAdapterCommand) + cfg.RemoteWorkspaceRealAdapterArgsJSON = strings.TrimSpace(cfg.RemoteWorkspaceRealAdapterArgsJSON) + cfg.RemoteWorkspaceRealAdapterWorkDir = strings.TrimSpace(cfg.RemoteWorkspaceRealAdapterWorkDir) if cfg.BackendURL == "" { return Config{}, errors.New("backend URL is required") } diff --git a/agents/rap-node-agent/internal/config/config_test.go b/agents/rap-node-agent/internal/config/config_test.go index 100d5ad..da28198 100644 --- a/agents/rap-node-agent/internal/config/config_test.go +++ b/agents/rap-node-agent/internal/config/config_test.go @@ -34,6 +34,10 @@ func TestLoadConfigFromEnvAndArgs(t *testing.T) { "RAP_MESH_SYNTHETIC_CONFIG": "/tmp/rap-node/mesh-synthetic.json", "RAP_MESH_PEER_ENDPOINTS_JSON": `{"node-b":"http://127.0.0.1:19002"}`, "RAP_MESH_SYNTHETIC_ROUTES_JSON": `[{"route_id":"route-1"}]`, + "RAP_REMOTE_WORKSPACE_REAL_ADAPTER_ENABLED": "true", + "RAP_REMOTE_WORKSPACE_REAL_ADAPTER_COMMAND": " /opt/rap/bin/rdp-worker ", + "RAP_REMOTE_WORKSPACE_REAL_ADAPTER_ARGS_JSON": ` ["--future-probe"] `, + "RAP_REMOTE_WORKSPACE_REAL_ADAPTER_WORKDIR": " /var/lib/rap-node-agent/rdp-worker ", }) if err != nil { t.Fatalf("load config: %v", err) @@ -85,6 +89,12 @@ func TestLoadConfigFromEnvAndArgs(t *testing.T) { if cfg.MeshPeerEndpointsJSON == "" || cfg.MeshSyntheticRoutesJSON == "" { t.Fatalf("mesh live synthetic config was not loaded: %+v", cfg) } + if !cfg.RemoteWorkspaceRealAdapterEnabled || + cfg.RemoteWorkspaceRealAdapterCommand != "/opt/rap/bin/rdp-worker" || + cfg.RemoteWorkspaceRealAdapterArgsJSON != `["--future-probe"]` || + cfg.RemoteWorkspaceRealAdapterWorkDir != "/var/lib/rap-node-agent/rdp-worker" { + t.Fatalf("unexpected remote workspace real adapter config: %+v", cfg) + } } func TestLoadConfigDefaultsEnrollmentPollingToNoTimeout(t *testing.T) { @@ -98,6 +108,12 @@ func TestLoadConfigDefaultsEnrollmentPollingToNoTimeout(t *testing.T) { if cfg.EnrollmentPollTimeout != 0 { t.Fatalf("EnrollmentPollTimeout = %s, want no timeout", cfg.EnrollmentPollTimeout) } + if cfg.RemoteWorkspaceRealAdapterEnabled || + cfg.RemoteWorkspaceRealAdapterCommand != "" || + cfg.RemoteWorkspaceRealAdapterArgsJSON != "" || + cfg.RemoteWorkspaceRealAdapterWorkDir != "" { + t.Fatalf("real adapter config should default disabled and empty: %+v", cfg) + } } func TestLoadConfigRejectsNegativeProductionObservationSinkCapacity(t *testing.T) { diff --git a/agents/rap-node-agent/internal/hostagent/disk_usage_unix.go b/agents/rap-node-agent/internal/hostagent/disk_usage_unix.go new file mode 100644 index 0000000..3634f7e --- /dev/null +++ b/agents/rap-node-agent/internal/hostagent/disk_usage_unix.go @@ -0,0 +1,27 @@ +//go:build !windows + +package hostagent + +import "syscall" + +func diskUsage(path string) (DiskUsage, error) { + var stat syscall.Statfs_t + if err := syscall.Statfs(path, &stat); err != nil { + return DiskUsage{}, err + } + total := stat.Blocks * uint64(stat.Bsize) + free := stat.Bavail * uint64(stat.Bsize) + used := total - free + percent := 0 + if total > 0 { + percent = int((used*100 + total - 1) / total) + } + return DiskUsage{ + Path: path, + TotalBytes: total, + FreeBytes: free, + UsedBytes: used, + UsedPercent: percent, + AvailablePercent: 100 - percent, + }, nil +} diff --git a/agents/rap-node-agent/internal/hostagent/disk_usage_windows.go b/agents/rap-node-agent/internal/hostagent/disk_usage_windows.go new file mode 100644 index 0000000..bb46659 --- /dev/null +++ b/agents/rap-node-agent/internal/hostagent/disk_usage_windows.go @@ -0,0 +1,9 @@ +//go:build windows + +package hostagent + +import "fmt" + +func diskUsage(path string) (DiskUsage, error) { + return DiskUsage{Path: path}, fmt.Errorf("disk usage monitor is not implemented on windows") +} diff --git a/agents/rap-node-agent/internal/hostagent/monitor.go b/agents/rap-node-agent/internal/hostagent/monitor.go new file mode 100644 index 0000000..181d8e5 --- /dev/null +++ b/agents/rap-node-agent/internal/hostagent/monitor.go @@ -0,0 +1,494 @@ +package hostagent + +import ( + "context" + "encoding/json" + "errors" + "fmt" + "os" + "path/filepath" + "strings" + "time" + + "github.com/example/remote-access-platform/agents/rap-node-agent/internal/state" +) + +const ( + DefaultMonitorProduct = "rap-host-agent" + DefaultMonitorPhase = "host_monitor" + DefaultMonitorIntervalSeconds = 60 + DefaultMonitorDiskWarnPercent = 80 + DefaultMonitorDiskCleanupPercent = 85 + DefaultMonitorDiskCriticalPercent = 95 + DefaultMonitorRestartCooldownSec = 300 + DefaultMonitorTmpMinAgeMinutes = 240 + DefaultMonitorStaleRestartingSec = 180 + DefaultMonitorDockerBinary = "docker" + DefaultMonitorDiskPath = "/" + DefaultMonitorTmpDir = "/tmp" + DefaultMonitorStatusSchemaVersion = "rap.host_monitor_status.v1" + DefaultMonitorRemediationSucceeded = "remediated" +) + +type MonitorConfig struct { + BackendURL string + ClusterID string + NodeID string + StateDir string + Product string + CurrentVersion string + Interval time.Duration + InitialDelay time.Duration + MaxRuns int + DockerBinary string + WatchContainers []string + RestartContainers bool + RestartCooldown time.Duration + StaleRestartingAfter time.Duration + DiskPath string + TmpDir string + DiskWarnPercent int + DiskCleanupPercent int + DiskCriticalPercent int + TmpMinAge time.Duration + CleanupDocker bool + StatusFile string + Runner CommandRunner + Logf func(format string, args ...any) + restartHistory map[string]time.Time +} + +type DiskUsage struct { + Path string `json:"path"` + TotalBytes uint64 `json:"total_bytes"` + FreeBytes uint64 `json:"free_bytes"` + UsedBytes uint64 `json:"used_bytes"` + UsedPercent int `json:"used_percent"` + AvailablePercent int `json:"available_percent"` +} + +type MonitorContainerStatus struct { + Name string `json:"name"` + Status string `json:"status,omitempty"` + Running bool `json:"running"` + Restarting bool `json:"restarting"` + ExitCode int `json:"exit_code,omitempty"` + Health string `json:"health,omitempty"` + RestartCount int `json:"restart_count,omitempty"` + StartedAt string `json:"started_at,omitempty"` + FinishedAt string `json:"finished_at,omitempty"` + LastAction string `json:"last_action,omitempty"` + LastActionOK bool `json:"last_action_ok,omitempty"` + LastActionError string `json:"last_action_error,omitempty"` +} + +type MonitorAction struct { + Kind string `json:"kind"` + Target string `json:"target,omitempty"` + Reason string `json:"reason,omitempty"` + Success bool `json:"success"` + Error string `json:"error,omitempty"` +} + +type MonitorResult struct { + SchemaVersion string `json:"schema_version"` + Status string `json:"status"` + ObservedAt time.Time `json:"observed_at"` + Disk *DiskUsage `json:"disk,omitempty"` + Containers []MonitorContainerStatus `json:"containers,omitempty"` + Actions []MonitorAction `json:"actions,omitempty"` + Errors []string `json:"errors,omitempty"` +} + +type monitorDockerInspect struct { + Name string `json:"Name"` + RestartCount int `json:"RestartCount"` + State struct { + Status string `json:"Status"` + Running bool `json:"Running"` + Restarting bool `json:"Restarting"` + ExitCode int `json:"ExitCode"` + Error string `json:"Error"` + StartedAt string `json:"StartedAt"` + FinishedAt string `json:"FinishedAt"` + Health *struct { + Status string `json:"Status"` + } `json:"Health"` + } `json:"State"` +} + +func RunMonitorLoop(ctx context.Context, cfg MonitorConfig) error { + cfg = normalizeMonitorConfig(cfg) + if cfg.InitialDelay > 0 { + if err := sleepContext(ctx, cfg.InitialDelay); err != nil { + return err + } + } + runs := 0 + restartHistory := map[string]time.Time{} + for { + cfg.restartHistory = restartHistory + result := RunMonitorOnce(ctx, cfg) + logMonitorResult(cfg, result) + if err := writeMonitorStatusFile(cfg.StatusFile, result); err != nil && cfg.Logf != nil { + cfg.Logf("monitor status-file failed: %v", err) + } + if err := reportMonitorStatus(ctx, cfg, result); err != nil && cfg.Logf != nil { + cfg.Logf("monitor report failed: %v", err) + } + runs++ + if cfg.MaxRuns > 0 && runs >= cfg.MaxRuns { + return nil + } + if err := sleepContext(ctx, cfg.Interval); err != nil { + return err + } + } +} + +func RunMonitorOnce(ctx context.Context, cfg MonitorConfig) MonitorResult { + cfg = normalizeMonitorConfig(cfg) + result := MonitorResult{ + SchemaVersion: DefaultMonitorStatusSchemaVersion, + Status: "ok", + ObservedAt: time.Now().UTC(), + } + if usage, err := diskUsage(cfg.DiskPath); err != nil { + result.Errors = append(result.Errors, fmt.Sprintf("disk usage %s: %v", cfg.DiskPath, err)) + } else { + result.Disk = &usage + if usage.UsedPercent >= cfg.DiskWarnPercent { + result.Status = "warning" + } + if usage.UsedPercent >= cfg.DiskCleanupPercent { + runCleanup(ctx, cfg, &result, fmt.Sprintf("disk_used_%d_percent", usage.UsedPercent)) + if refreshed, err := diskUsage(cfg.DiskPath); err == nil { + result.Disk = &refreshed + } + } + if result.Disk != nil && result.Disk.UsedPercent >= cfg.DiskCriticalPercent { + result.Status = "failed" + result.Errors = append(result.Errors, fmt.Sprintf("disk %s critical: %d%% used", cfg.DiskPath, result.Disk.UsedPercent)) + } + } + for _, name := range uniqueTrimmed(cfg.WatchContainers) { + status := inspectMonitorContainer(ctx, cfg, name) + if cfg.RestartContainers { + remediateMonitorContainer(ctx, cfg, &status, &result) + } + if !status.Running || status.Health == "unhealthy" || status.Restarting || status.LastActionError != "" { + if result.Status == "ok" { + result.Status = "warning" + } + } + result.Containers = append(result.Containers, status) + } + for _, action := range result.Actions { + if !action.Success { + result.Status = "failed" + if action.Error != "" { + result.Errors = append(result.Errors, action.Error) + } + } + } + return result +} + +func normalizeMonitorConfig(cfg MonitorConfig) MonitorConfig { + cfg.BackendURL = strings.TrimRight(strings.TrimSpace(cfg.BackendURL), "/") + cfg.ClusterID = strings.TrimSpace(cfg.ClusterID) + cfg.NodeID = strings.TrimSpace(cfg.NodeID) + cfg.StateDir = strings.TrimSpace(cfg.StateDir) + cfg.Product = firstNonEmpty(cfg.Product, DefaultMonitorProduct) + if cfg.Interval <= 0 { + cfg.Interval = time.Duration(DefaultMonitorIntervalSeconds) * time.Second + } + if cfg.DockerBinary == "" { + cfg.DockerBinary = DefaultMonitorDockerBinary + } + if cfg.DiskPath == "" { + cfg.DiskPath = DefaultMonitorDiskPath + } + if cfg.TmpDir == "" { + cfg.TmpDir = DefaultMonitorTmpDir + } + if cfg.DiskWarnPercent == 0 { + cfg.DiskWarnPercent = DefaultMonitorDiskWarnPercent + } + if cfg.DiskCleanupPercent == 0 { + cfg.DiskCleanupPercent = DefaultMonitorDiskCleanupPercent + } + if cfg.DiskCriticalPercent == 0 { + cfg.DiskCriticalPercent = DefaultMonitorDiskCriticalPercent + } + if cfg.RestartCooldown == 0 { + cfg.RestartCooldown = time.Duration(DefaultMonitorRestartCooldownSec) * time.Second + } + if cfg.StaleRestartingAfter == 0 { + cfg.StaleRestartingAfter = time.Duration(DefaultMonitorStaleRestartingSec) * time.Second + } + if cfg.TmpMinAge == 0 { + cfg.TmpMinAge = time.Duration(DefaultMonitorTmpMinAgeMinutes) * time.Minute + } + if cfg.Runner == nil { + cfg.Runner = ExecRunner{} + } + return cfg +} + +func inspectMonitorContainer(ctx context.Context, cfg MonitorConfig, name string) MonitorContainerStatus { + out := MonitorContainerStatus{Name: name} + raw, err := cfg.Runner.Run(ctx, cfg.DockerBinary, "inspect", name) + if err != nil { + out.LastActionError = strings.TrimSpace(err.Error()) + return out + } + var inspected []monitorDockerInspect + if err := json.Unmarshal([]byte(raw), &inspected); err != nil { + out.LastActionError = fmt.Sprintf("parse docker inspect: %v", err) + return out + } + if len(inspected) == 0 { + out.LastActionError = "docker inspect returned no containers" + return out + } + item := inspected[0] + out.Name = strings.TrimPrefix(firstNonEmpty(item.Name, name), "/") + out.Status = item.State.Status + out.Running = item.State.Running + out.Restarting = item.State.Restarting + out.ExitCode = item.State.ExitCode + out.RestartCount = item.RestartCount + out.StartedAt = item.State.StartedAt + out.FinishedAt = item.State.FinishedAt + if item.State.Health != nil { + out.Health = strings.TrimSpace(item.State.Health.Status) + } + if item.State.Error != "" { + out.LastActionError = item.State.Error + } + return out +} + +func remediateMonitorContainer(ctx context.Context, cfg MonitorConfig, status *MonitorContainerStatus, result *MonitorResult) { + if status.Name == "" { + return + } + action := "" + reason := "" + switch { + case status.LastActionError != "" && status.Status == "": + action = "start" + reason = "inspect_failed_or_missing" + case status.Health == "unhealthy": + action = "restart" + reason = "health_unhealthy" + case status.Restarting && restartingIsStale(status.StartedAt, status.FinishedAt, cfg.StaleRestartingAfter): + action = "restart" + reason = "restarting_stale" + case !status.Running && status.Status != "": + action = "start" + reason = "not_running" + default: + return + } + if cfg.restartHistory != nil { + if last, ok := cfg.restartHistory[status.Name]; ok && time.Since(last) < cfg.RestartCooldown { + result.Actions = append(result.Actions, MonitorAction{ + Kind: "docker_" + action + "_skipped", + Target: status.Name, + Reason: "restart_cooldown", + Success: true, + }) + return + } + } + args := []string{action, status.Name} + _, err := cfg.Runner.Run(ctx, cfg.DockerBinary, args...) + monitorAction := MonitorAction{Kind: "docker_" + action, Target: status.Name, Reason: reason, Success: err == nil} + status.LastAction = action + status.LastActionOK = err == nil + if err != nil { + monitorAction.Error = strings.TrimSpace(err.Error()) + status.LastActionError = monitorAction.Error + } else { + if cfg.restartHistory != nil { + cfg.restartHistory[status.Name] = time.Now() + } + status.LastActionError = "" + status.Running = true + status.Restarting = false + status.Status = DefaultMonitorRemediationSucceeded + } + result.Actions = append(result.Actions, monitorAction) +} + +func restartingIsStale(startedAt, finishedAt string, threshold time.Duration) bool { + for _, value := range []string{finishedAt, startedAt} { + parsed, err := time.Parse(time.RFC3339Nano, strings.TrimSpace(value)) + if err == nil && !parsed.IsZero() { + return time.Since(parsed) >= threshold + } + } + return true +} + +func runCleanup(ctx context.Context, cfg MonitorConfig, result *MonitorResult, reason string) { + if cfg.CleanupDocker { + for _, args := range [][]string{ + {"builder", "prune", "-af"}, + {"image", "prune", "-f"}, + {"container", "prune", "-f"}, + } { + _, err := cfg.Runner.Run(ctx, cfg.DockerBinary, args...) + action := MonitorAction{Kind: "docker_" + strings.Join(args[:len(args)-1], "_"), Reason: reason, Success: err == nil} + if err != nil { + action.Error = strings.TrimSpace(err.Error()) + } + result.Actions = append(result.Actions, action) + } + } + removed, err := cleanupTmpBuildDirs(cfg.TmpDir, cfg.TmpMinAge) + action := MonitorAction{Kind: "tmp_cleanup", Target: cfg.TmpDir, Reason: reason, Success: err == nil} + if err != nil { + action.Error = err.Error() + } else { + action.Target = fmt.Sprintf("%s removed=%d", cfg.TmpDir, removed) + } + result.Actions = append(result.Actions, action) +} + +func cleanupTmpBuildDirs(tmpDir string, minAge time.Duration) (int, error) { + tmpDir = filepath.Clean(strings.TrimSpace(tmpDir)) + if tmpDir == "" || tmpDir == "." || tmpDir == string(filepath.Separator) { + return 0, fmt.Errorf("unsafe tmp dir: %q", tmpDir) + } + entries, err := os.ReadDir(tmpDir) + if err != nil { + return 0, err + } + now := time.Now() + removed := 0 + for _, entry := range entries { + name := entry.Name() + if !strings.HasPrefix(name, "rap-") && !strings.HasPrefix(name, "go-build") { + continue + } + info, err := entry.Info() + if err != nil || now.Sub(info.ModTime()) < minAge { + continue + } + if err := os.RemoveAll(filepath.Join(tmpDir, name)); err != nil { + return removed, err + } + removed++ + } + return removed, nil +} + +func reportMonitorStatus(ctx context.Context, cfg MonitorConfig, result MonitorResult) error { + cfg = normalizeMonitorConfig(cfg) + nodeID, clusterID, err := resolveMonitorIdentity(cfg) + if err != nil { + if errors.Is(err, ErrNodeIdentityNotReady) { + return nil + } + return err + } + if cfg.BackendURL == "" || clusterID == "" || nodeID == "" { + return nil + } + payload := map[string]any{ + "schema_version": result.SchemaVersion, + "monitor_status": result.Status, + "disk": result.Disk, + "containers": result.Containers, + "actions": result.Actions, + "errors": result.Errors, + } + errText := "" + if len(result.Errors) > 0 { + errText = strings.Join(result.Errors, "; ") + } + req := NodeUpdateStatusRequest{ + Product: cfg.Product, + CurrentVersion: cfg.CurrentVersion, + Phase: DefaultMonitorPhase, + Status: result.Status, + Payload: payload, + ObservedAt: result.ObservedAt, + } + if errText != "" { + req.ErrorMessage = &errText + } + return ReportNodeUpdateStatus(ctx, cfg.BackendURL, clusterID, nodeID, req) +} + +func resolveMonitorIdentity(cfg MonitorConfig) (string, string, error) { + nodeID := strings.TrimSpace(cfg.NodeID) + clusterID := strings.TrimSpace(cfg.ClusterID) + if nodeID != "" { + return nodeID, clusterID, nil + } + if strings.TrimSpace(cfg.StateDir) == "" { + return "", clusterID, ErrNodeIdentityNotReady + } + identity, err := state.Load(filepath.Join(cfg.StateDir, state.FileName)) + if err != nil { + if errors.Is(err, os.ErrNotExist) { + return "", clusterID, ErrNodeIdentityNotReady + } + return "", clusterID, err + } + nodeID = strings.TrimSpace(identity.NodeID) + if nodeID == "" { + return "", clusterID, ErrNodeIdentityNotReady + } + if clusterID == "" { + clusterID = strings.TrimSpace(identity.ClusterID) + } + return nodeID, clusterID, nil +} + +func writeMonitorStatusFile(path string, result MonitorResult) error { + path = strings.TrimSpace(path) + if path == "" { + return nil + } + if err := os.MkdirAll(filepath.Dir(path), 0o755); err != nil { + return err + } + payload, err := json.MarshalIndent(result, "", " ") + if err != nil { + return err + } + tmp := path + ".tmp" + if err := os.WriteFile(tmp, payload, 0o644); err != nil { + return err + } + return os.Rename(tmp, path) +} + +func logMonitorResult(cfg MonitorConfig, result MonitorResult) { + if cfg.Logf == nil { + return + } + cfg.Logf("monitor status=%s containers=%d actions=%d errors=%d", result.Status, len(result.Containers), len(result.Actions), len(result.Errors)) +} + +func uniqueTrimmed(values []string) []string { + seen := map[string]struct{}{} + out := make([]string, 0, len(values)) + for _, value := range values { + value = strings.TrimSpace(value) + if value == "" { + continue + } + if _, ok := seen[value]; ok { + continue + } + seen[value] = struct{}{} + out = append(out, value) + } + return out +} diff --git a/agents/rap-node-agent/internal/hostagent/monitor_test.go b/agents/rap-node-agent/internal/hostagent/monitor_test.go new file mode 100644 index 0000000..893e735 --- /dev/null +++ b/agents/rap-node-agent/internal/hostagent/monitor_test.go @@ -0,0 +1,87 @@ +package hostagent + +import ( + "context" + "fmt" + "strings" + "testing" + "time" +) + +type monitorRunner struct { + inspect map[string]string + calls []string +} + +func (r *monitorRunner) Run(_ context.Context, name string, args ...string) (string, error) { + call := strings.TrimSpace(name + " " + strings.Join(args, " ")) + r.calls = append(r.calls, call) + if len(args) >= 2 && args[0] == "inspect" { + out, ok := r.inspect[args[1]] + if !ok { + return "", fmt.Errorf("not found") + } + return out, nil + } + return "", nil +} + +func TestRunMonitorOnceStartsExitedContainer(t *testing.T) { + runner := &monitorRunner{inspect: map[string]string{ + "rap-node-agent": `[{"Name":"/rap-node-agent","State":{"Status":"exited","Running":false,"ExitCode":137,"StartedAt":"2026-05-13T00:00:00Z","FinishedAt":"2026-05-13T00:01:00Z"}}]`, + }} + result := RunMonitorOnce(context.Background(), MonitorConfig{ + WatchContainers: []string{"rap-node-agent"}, + RestartContainers: true, + Runner: runner, + DiskPath: t.TempDir(), + DiskCleanupPercent: 101, + DiskWarnPercent: 101, + DiskCriticalPercent: 101, + }) + if len(result.Actions) != 1 || result.Actions[0].Kind != "docker_start" || !result.Actions[0].Success { + t.Fatalf("unexpected actions: %+v", result.Actions) + } + if !containsCall(runner.calls, "docker start rap-node-agent") { + t.Fatalf("start call missing: %+v", runner.calls) + } +} + +func TestRunMonitorOnceRestartsUnhealthyContainer(t *testing.T) { + runner := &monitorRunner{inspect: map[string]string{ + "rap-backend": `[{"Name":"/rap-backend","State":{"Status":"running","Running":true,"StartedAt":"2026-05-13T00:00:00Z","Health":{"Status":"unhealthy"}}}]`, + }} + result := RunMonitorOnce(context.Background(), MonitorConfig{ + WatchContainers: []string{"rap-backend"}, + RestartContainers: true, + Runner: runner, + DiskPath: t.TempDir(), + DiskCleanupPercent: 101, + DiskWarnPercent: 101, + DiskCriticalPercent: 101, + }) + if len(result.Actions) != 1 || result.Actions[0].Kind != "docker_restart" || !result.Actions[0].Success { + t.Fatalf("unexpected actions: %+v", result.Actions) + } + if !containsCall(runner.calls, "docker restart rap-backend") { + t.Fatalf("restart call missing: %+v", runner.calls) + } +} + +func TestRestartingIsStale(t *testing.T) { + if !restartingIsStale(time.Now().Add(-10*time.Minute).UTC().Format(time.RFC3339Nano), "", time.Minute) { + t.Fatalf("old restarting container should be stale") + } + if restartingIsStale(time.Now().UTC().Format(time.RFC3339Nano), "", time.Hour) { + t.Fatalf("fresh restarting container should not be stale") + } +} + +func containsCall(calls []string, want string) bool { + for _, call := range calls { + if call == want { + return true + } + } + return false +} diff --git a/agents/rap-node-agent/internal/hostagent/service.go b/agents/rap-node-agent/internal/hostagent/service.go index b5450d3..e0f8ca1 100644 --- a/agents/rap-node-agent/internal/hostagent/service.go +++ b/agents/rap-node-agent/internal/hostagent/service.go @@ -16,33 +16,44 @@ const ( ) type UpdateServiceConfig struct { - RuntimeConfig RuntimeConfig - Product string - CurrentVersion string - Channel string - IntervalSeconds int - InitialDelaySeconds int - Jitter float64 - HealthTimeoutSec int - BinaryInstallPath string - SourceBinaryPath string - UnitDir string - ManageSystemd bool - DryRun bool - InstallSelfUpdater bool - SelfUpdateVersion string + RuntimeConfig RuntimeConfig + Product string + CurrentVersion string + Channel string + IntervalSeconds int + InitialDelaySeconds int + Jitter float64 + HealthTimeoutSec int + BinaryInstallPath string + SourceBinaryPath string + UnitDir string + ManageSystemd bool + DryRun bool + InstallSelfUpdater bool + SelfUpdateVersion string + InstallMonitor bool + MonitorIntervalSec int + MonitorContainers []string + MonitorStatusFile string + MonitorDiskWarn int + MonitorDiskCleanup int + MonitorDiskCritical int + MonitorCleanupDocker bool } type UpdateServiceResult struct { - Installed bool - Started bool - UnitName string - UnitPath string - BinaryPath string - Unit string - SelfUnitName string - SelfUnitPath string - SelfUnit string + Installed bool + Started bool + UnitName string + UnitPath string + BinaryPath string + Unit string + SelfUnitName string + SelfUnitPath string + SelfUnit string + MonitorUnitName string + MonitorUnitPath string + MonitorUnit string } func (m DockerManager) InstallUpdateService(ctx context.Context, cfg UpdateServiceConfig) (UpdateServiceResult, error) { @@ -59,6 +70,9 @@ func (m DockerManager) InstallUpdateService(ctx context.Context, cfg UpdateServi if cfg.HealthTimeoutSec == 0 { cfg.HealthTimeoutSec = 30 } + if cfg.MonitorIntervalSec == 0 { + cfg.MonitorIntervalSec = DefaultMonitorIntervalSeconds + } cfg.BinaryInstallPath = firstNonEmpty(cfg.BinaryInstallPath, DefaultHostAgentInstallPath) cfg.UnitDir = firstNonEmpty(cfg.UnitDir, DefaultSystemdUnitDir) unitName := "rap-host-agent-updater-" + safeUnitSlug(cfg.RuntimeConfig.ContainerName) + ".service" @@ -82,6 +96,15 @@ func (m DockerManager) InstallUpdateService(ctx context.Context, cfg UpdateServi result.SelfUnitName = selfUnitName result.SelfUnitPath = selfUnitPath } + if cfg.InstallMonitor { + monitorUnit, monitorUnitName, monitorUnitPath, err := buildHostAgentMonitorUnit(cfg) + if err != nil { + return result, err + } + result.MonitorUnit = monitorUnit + result.MonitorUnitName = monitorUnitName + result.MonitorUnitPath = monitorUnitPath + } return result, nil } if runtime.GOOS != "linux" && cfg.UnitDir == DefaultSystemdUnitDir { @@ -108,6 +131,18 @@ func (m DockerManager) InstallUpdateService(ctx context.Context, cfg UpdateServi result.SelfUnitName = selfUnitName result.SelfUnitPath = selfUnitPath } + if cfg.InstallMonitor { + monitorUnit, monitorUnitName, monitorUnitPath, err := buildHostAgentMonitorUnit(cfg) + if err != nil { + return result, err + } + if err := os.WriteFile(monitorUnitPath, []byte(monitorUnit), 0o644); err != nil { + return result, err + } + result.MonitorUnit = monitorUnit + result.MonitorUnitName = monitorUnitName + result.MonitorUnitPath = monitorUnitPath + } result.Installed = true if cfg.ManageSystemd { runner := m.Runner @@ -125,6 +160,11 @@ func (m DockerManager) InstallUpdateService(ctx context.Context, cfg UpdateServi return result, err } } + if cfg.InstallMonitor && result.MonitorUnitName != "" { + if _, err := runner.Run(ctx, "systemctl", "enable", "--now", result.MonitorUnitName); err != nil { + return result, err + } + } result.Started = true } return result, nil @@ -223,6 +263,64 @@ WantedBy=multi-user.target `, systemdJoin(args)), unitName, unitPath, nil } +func buildHostAgentMonitorUnit(cfg UpdateServiceConfig) (string, string, string, error) { + runtimeCfg := cfg.RuntimeConfig.Normalize() + if runtimeCfg.BackendURL == "" || runtimeCfg.ClusterID == "" || runtimeCfg.StateDir == "" { + return "", "", "", fmt.Errorf("backend-url, cluster-id, and state-dir are required for host monitor") + } + containers := uniqueTrimmed(append([]string{runtimeCfg.ContainerName}, cfg.MonitorContainers...)) + if len(containers) == 0 { + return "", "", "", fmt.Errorf("at least one monitor container is required") + } + unitName := "rap-host-agent-monitor-" + safeUnitSlug(runtimeCfg.ContainerName) + ".service" + unitPath := filepath.Join(firstNonEmpty(cfg.UnitDir, DefaultSystemdUnitDir), unitName) + args := []string{ + cfg.BinaryInstallPath, + "monitor-loop", + "--backend-url", runtimeCfg.BackendURL, + "--cluster-id", runtimeCfg.ClusterID, + "--state-dir", runtimeCfg.StateDir, + "--current-version", firstNonEmpty(cfg.SelfUpdateVersion, cfg.CurrentVersion), + "--interval-seconds", fmt.Sprintf("%d", firstNonZero(cfg.MonitorIntervalSec, DefaultMonitorIntervalSeconds)), + "--disk-warn-percent", fmt.Sprintf("%d", firstNonZero(cfg.MonitorDiskWarn, DefaultMonitorDiskWarnPercent)), + "--disk-cleanup-percent", fmt.Sprintf("%d", firstNonZero(cfg.MonitorDiskCleanup, DefaultMonitorDiskCleanupPercent)), + "--disk-critical-percent", fmt.Sprintf("%d", firstNonZero(cfg.MonitorDiskCritical, DefaultMonitorDiskCriticalPercent)), + } + if cfg.MonitorCleanupDocker { + args = append(args, "--cleanup-docker") + } + if strings.TrimSpace(cfg.MonitorStatusFile) != "" { + args = append(args, "--status-file", strings.TrimSpace(cfg.MonitorStatusFile)) + } + for _, container := range containers { + args = append(args, "--watch-container", container) + } + return fmt.Sprintf(`[Unit] +Description=RAP host-agent monitor for %s +After=network-online.target docker.service +Wants=network-online.target +Requires=docker.service + +[Service] +Type=simple +ExecStart=%s +Restart=always +RestartSec=30 + +[Install] +WantedBy=multi-user.target +`, runtimeCfg.ContainerName, systemdJoin(args)), unitName, unitPath, nil +} + +func firstNonZero(values ...int) int { + for _, value := range values { + if value != 0 { + return value + } + } + return 0 +} + func installHostAgentBinary(sourcePath, targetPath string) error { sourcePath = strings.TrimSpace(sourcePath) targetPath = strings.TrimSpace(targetPath) diff --git a/agents/rap-node-agent/internal/hostagent/service_test.go b/agents/rap-node-agent/internal/hostagent/service_test.go index 7d3b3ec..7935360 100644 --- a/agents/rap-node-agent/internal/hostagent/service_test.go +++ b/agents/rap-node-agent/internal/hostagent/service_test.go @@ -24,15 +24,18 @@ func TestInstallUpdateServiceWritesSystemdUnit(t *testing.T) { ContainerName: "rap-node-agent-node-a", StateDir: "/var/lib/rap/nodes/node-a", }, - CurrentVersion: "0.1.0-current", - IntervalSeconds: 60, - Jitter: 0.2, - SourceBinaryPath: source, - BinaryInstallPath: binaryPath, - UnitDir: unitDir, - ManageSystemd: false, - InstallSelfUpdater: true, - SelfUpdateVersion: "0.1.0-host", + CurrentVersion: "0.1.0-current", + IntervalSeconds: 60, + Jitter: 0.2, + SourceBinaryPath: source, + BinaryInstallPath: binaryPath, + UnitDir: unitDir, + ManageSystemd: false, + InstallSelfUpdater: true, + SelfUpdateVersion: "0.1.0-host", + InstallMonitor: true, + MonitorContainers: []string{"rap-test-backend"}, + MonitorCleanupDocker: true, }) if err != nil { t.Fatalf("install update service: %v", err) @@ -73,6 +76,25 @@ func TestInstallUpdateServiceWritesSystemdUnit(t *testing.T) { if text := string(selfUnit); !strings.Contains(text, "update-host-agent-loop") || !strings.Contains(text, "--current-version 0.1.0-host") { t.Fatalf("unexpected self unit:\n%s", text) } + if result.MonitorUnitName == "" || result.MonitorUnitPath == "" { + t.Fatalf("monitor result = %+v", result) + } + monitorUnit, err := os.ReadFile(result.MonitorUnitPath) + if err != nil { + t.Fatalf("read monitor unit: %v", err) + } + monitorText := string(monitorUnit) + for _, want := range []string{ + "monitor-loop", + "--watch-container rap-node-agent-node-a", + "--watch-container rap-test-backend", + "--cleanup-docker", + "Restart=always", + } { + if !strings.Contains(monitorText, want) { + t.Fatalf("monitor unit missing %q:\n%s", want, monitorText) + } + } } func TestWindowsHostAgentUpdateScriptTargetsWindowsService(t *testing.T) { diff --git a/agents/rap-node-agent/internal/hostagent/update.go b/agents/rap-node-agent/internal/hostagent/update.go index 216f918..73ca570 100644 --- a/agents/rap-node-agent/internal/hostagent/update.go +++ b/agents/rap-node-agent/internal/hostagent/update.go @@ -313,6 +313,9 @@ func (m DockerManager) ApplyUpdate(ctx context.Context, req UpdateRequest) (Upda cfg.ClusterID = firstNonEmpty(cfg.ClusterID, req.ClusterID) cfg.ContainerName = req.ContainerName cfg.Image = artifactImage(*plan.Artifact, cfg.Image) + if artifactDockerVPNGatewayEnabled(*plan.Artifact) { + cfg.DockerVPNGatewayEnabled = true + } cfg.ImageArtifactURLs = artifactURLsForBackend(*plan.Artifact, req.BackendURL) cfg.ImageArtifactSHA256 = plan.Artifact.SHA256 cfg.ImageArtifactSizeBytes = plan.Artifact.SizeBytes @@ -681,6 +684,20 @@ func artifactImage(artifact ReleaseArtifact, fallback string) string { return firstNonEmpty(fallback, DefaultImage) } +func artifactDockerVPNGatewayEnabled(artifact ReleaseArtifact) bool { + if len(artifact.Metadata) == 0 { + return false + } + var metadata struct { + DockerVPNGatewayEnabled bool `json:"docker_vpn_gateway_enabled"` + VPNGatewayEnabled bool `json:"vpn_gateway_enabled"` + } + if err := json.Unmarshal(artifact.Metadata, &metadata); err != nil { + return false + } + return metadata.DockerVPNGatewayEnabled || metadata.VPNGatewayEnabled +} + func artifactURLs(artifact ReleaseArtifact) []string { out := make([]string, 0, 1+len(artifact.URLs)) for _, raw := range append([]string{artifact.URL}, artifact.URLs...) { diff --git a/agents/rap-node-agent/internal/hostagent/update_test.go b/agents/rap-node-agent/internal/hostagent/update_test.go index ff3b423..61d8df9 100644 --- a/agents/rap-node-agent/internal/hostagent/update_test.go +++ b/agents/rap-node-agent/internal/hostagent/update_test.go @@ -596,6 +596,18 @@ func TestArtifactImageDerivesDockerTagFromProductAndVersion(t *testing.T) { } } +func TestArtifactDockerVPNGatewayEnabledFromMetadata(t *testing.T) { + if !artifactDockerVPNGatewayEnabled(ReleaseArtifact{Metadata: json.RawMessage(`{"docker_vpn_gateway_enabled":true}`)}) { + t.Fatal("expected docker vpn gateway metadata to enable gateway runtime") + } + if !artifactDockerVPNGatewayEnabled(ReleaseArtifact{Metadata: json.RawMessage(`{"vpn_gateway_enabled":true}`)}) { + t.Fatal("expected legacy vpn gateway metadata to enable gateway runtime") + } + if artifactDockerVPNGatewayEnabled(ReleaseArtifact{Metadata: json.RawMessage(`{"docker_vpn_gateway_enabled":false}`)}) { + t.Fatal("expected disabled metadata to remain disabled") + } +} + func serverArtifactURL(r *http.Request) string { scheme := "http" if r.TLS != nil { diff --git a/agents/rap-node-agent/internal/mesh/remote_workspace_sink.go b/agents/rap-node-agent/internal/mesh/remote_workspace_sink.go index 6ec4781..0262639 100644 --- a/agents/rap-node-agent/internal/mesh/remote_workspace_sink.go +++ b/agents/rap-node-agent/internal/mesh/remote_workspace_sink.go @@ -16,119 +16,176 @@ const DefaultRemoteWorkspaceAdapterMailboxConsumerCapacity = 32 const RemoteWorkspaceFrameProbeSinkRuntimeID = "node_agent_rdp_worker_contract_probe" type RemoteWorkspaceFrameProbeSink struct { - mu sync.Mutex - sequence int64 - queueCapacity int - sessionTTL time.Duration - sessions map[string]*remoteWorkspaceAdapterProbeSession - terminalSessions map[string]remoteWorkspaceAdapterProbeTerminalSession - sessionCreatedTotal int64 - sessionBoundTotal int64 - sessionBackpressureTotal int64 - sessionExpiredTotal int64 - sessionClosedTotal int64 - sessionResetTotal int64 - sessionControlTotal int64 - mailboxEventSequence int64 - mailboxEnqueuedTotal int64 - mailboxDrainedTotal int64 - mailboxDroppedTotal int64 - mailboxReadTotal int64 - mailboxWaitTotal int64 - mailboxWaitTimeoutTotal int64 - mailboxEmptyReadTotal int64 - mailboxResumeReadTotal int64 - mailboxAfterSequenceReadTotal int64 - mailboxReturnedTotal int64 - mailboxSkippedTotal int64 - mailboxConsumerReadTotal int64 - mailboxConsumerAckTotal int64 - mailboxConsumerResetTotal int64 - mailboxConsumerEvictedTotal int64 - lastMailboxReadAt string - lastMailboxAdapterSessionID string - lastMailboxWaitMs int - lastMailboxWaited bool - lastMailboxWaitTimeout bool - lastMailboxEmpty bool - lastMailboxResumeFrom string - lastMailboxResumeSequence int64 - lastMailboxResumeConsumerID string - lastMailboxAfterSequence int64 - lastMailboxSkippedCount int - lastMailboxReturnedCount int - lastMailboxConsumerID string - lastMailboxConsumerAdapterSessionID string - lastMailboxConsumerReadAt string - lastMailboxConsumerAckAt string - lastMailboxConsumerCheckpoint int64 - lastMailboxConsumerAck int64 - acceptedFramesTotal int64 - droppedFramesTotal int64 - ackedFramesTotal int64 - backpressureCount int64 - lastBackpressureAt string - lastBackpressureReason string - lastRejectedFrameCount int - lastRejectedAdapterSessionID string - lastRejectedChannelClass string - lastRejectedAdapterContractID string - lastRejectedQueueCapacity int - lastRejectedQueueDepth int - lastControl RemoteWorkspaceAdapterSessionControlResult - last RemoteWorkspaceFrameBatchDeliveryReceipt + mu sync.Mutex + sequence int64 + queueCapacity int + sessionTTL time.Duration + sessions map[string]*remoteWorkspaceAdapterProbeSession + terminalSessions map[string]remoteWorkspaceAdapterProbeTerminalSession + sessionCreatedTotal int64 + sessionBoundTotal int64 + sessionBackpressureTotal int64 + sessionExpiredTotal int64 + sessionClosedTotal int64 + sessionResetTotal int64 + sessionControlTotal int64 + mailboxEventSequence int64 + mailboxEnqueuedTotal int64 + mailboxDrainedTotal int64 + mailboxDroppedTotal int64 + mailboxReadTotal int64 + mailboxWaitTotal int64 + mailboxWaitTimeoutTotal int64 + mailboxEmptyReadTotal int64 + mailboxResumeReadTotal int64 + mailboxAfterSequenceReadTotal int64 + mailboxReturnedTotal int64 + mailboxSkippedTotal int64 + mailboxPreflightTotal int64 + mailboxPreflightAckTotal int64 + mailboxPreflightCheckpointTotal int64 + mailboxConsumerReadTotal int64 + mailboxConsumerAckTotal int64 + mailboxConsumerResetTotal int64 + mailboxConsumerEvictedTotal int64 + lastMailboxReadAt string + lastMailboxAdapterSessionID string + lastMailboxWaitMs int + lastMailboxWaited bool + lastMailboxWaitTimeout bool + lastMailboxEmpty bool + lastMailboxResumeFrom string + lastMailboxResumeSequence int64 + lastMailboxResumeConsumerID string + lastMailboxAfterSequence int64 + lastMailboxSkippedCount int + lastMailboxReturnedCount int + lastMailboxPreflightAt string + lastMailboxPreflightAdapterSessionID string + lastMailboxPreflightConsumerID string + lastMailboxPreflightResumeFrom string + lastMailboxPreflightResumeSequence int64 + lastMailboxPreflightAfterSequence int64 + lastMailboxPreflightAvailableCount int + lastMailboxPreflightReturnedCount int + lastMailboxPreflightSkippedCount int + lastMailboxPreflightFirstSequence int64 + lastMailboxPreflightLastSequence int64 + lastMailboxPreflightFirstRetained int64 + lastMailboxPreflightLastRetained int64 + lastMailboxPreflightMailboxDropped int64 + lastMailboxPreflightDiagnosticState string + lastMailboxPreflightStaleCursor bool + lastMailboxPreflightMissingDropped int + lastMailboxPreflightRecommendedAction string + lastMailboxPreflightActionHints []string + lastMailboxPreflightActionReason string + lastMailboxPreflightActionContext map[string]any + lastMailboxPreflightOperatorSummary string + lastMailboxPreflightOperatorStatus string + lastMailboxPreflightOperatorSeverity string + lastMailboxPreflightOperatorFields map[string]any + lastMailboxConsumerID string + lastMailboxConsumerAdapterSessionID string + lastMailboxConsumerReadAt string + lastMailboxConsumerAckAt string + lastMailboxConsumerCheckpoint int64 + lastMailboxConsumerAck int64 + acceptedFramesTotal int64 + droppedFramesTotal int64 + ackedFramesTotal int64 + backpressureCount int64 + lastBackpressureAt string + lastBackpressureReason string + lastRejectedFrameCount int + lastRejectedAdapterSessionID string + lastRejectedChannelClass string + lastRejectedAdapterContractID string + lastRejectedQueueCapacity int + lastRejectedQueueDepth int + lastControl RemoteWorkspaceAdapterSessionControlResult + last RemoteWorkspaceFrameBatchDeliveryReceipt } type remoteWorkspaceAdapterProbeSession struct { - ID string - State string - CreatedAt time.Time - BoundAt time.Time - LastActivityAt time.Time - LastBackpressureAt time.Time - ClosedAt time.Time - DeliveryCount int64 - BackpressureCount int64 - AcceptedFrames int64 - DroppedFrames int64 - AckedFrames int64 - Mailbox []RemoteWorkspaceAdapterMailboxEvent - MailboxEnqueued int64 - MailboxDrained int64 - MailboxDropped int64 - MailboxRead int64 - MailboxWait int64 - MailboxWaitTimeout int64 - MailboxEmptyRead int64 - MailboxResumeRead int64 - MailboxAfterSequenceRead int64 - MailboxReturnedTotal int64 - MailboxSkippedTotal int64 - MailboxConsumers map[string]*remoteWorkspaceAdapterMailboxConsumerState - MailboxConsumerReadTotal int64 - MailboxConsumerAckTotal int64 - MailboxConsumerResetTotal int64 - MailboxConsumerEvictedTotal int64 - LastMailboxConsumerID string - LastMailboxConsumerReadAt time.Time - LastMailboxConsumerAckAt time.Time - LastMailboxConsumerCheckpoint int64 - LastMailboxConsumerAck int64 - LastMailboxReadAt time.Time - LastMailboxWaitMs int - LastMailboxWaited bool - LastMailboxTimeout bool - LastMailboxEmpty bool - LastMailboxResumeFrom string - LastMailboxResumeSequence int64 - LastMailboxResumeConsumerID string - LastMailboxAfterSequence int64 - LastMailboxSkippedCount int - LastMailboxReturnedCount int - LastChannelID string - LastResourceID string - LastRouteID string - LastReason string + ID string + State string + CreatedAt time.Time + BoundAt time.Time + LastActivityAt time.Time + LastBackpressureAt time.Time + ClosedAt time.Time + DeliveryCount int64 + BackpressureCount int64 + AcceptedFrames int64 + DroppedFrames int64 + AckedFrames int64 + Mailbox []RemoteWorkspaceAdapterMailboxEvent + MailboxEnqueued int64 + MailboxDrained int64 + MailboxDropped int64 + MailboxRead int64 + MailboxWait int64 + MailboxWaitTimeout int64 + MailboxEmptyRead int64 + MailboxResumeRead int64 + MailboxAfterSequenceRead int64 + MailboxReturnedTotal int64 + MailboxSkippedTotal int64 + MailboxPreflightTotal int64 + MailboxPreflightAckTotal int64 + MailboxPreflightCheckpointTotal int64 + MailboxPreflightOperatorStatusCounts map[string]int64 + MailboxPreflightOperatorSeverityCounts map[string]int64 + MailboxConsumers map[string]*remoteWorkspaceAdapterMailboxConsumerState + MailboxConsumerReadTotal int64 + MailboxConsumerAckTotal int64 + MailboxConsumerResetTotal int64 + MailboxConsumerEvictedTotal int64 + LastMailboxConsumerID string + LastMailboxConsumerReadAt time.Time + LastMailboxConsumerAckAt time.Time + LastMailboxConsumerCheckpoint int64 + LastMailboxConsumerAck int64 + LastMailboxReadAt time.Time + LastMailboxWaitMs int + LastMailboxWaited bool + LastMailboxTimeout bool + LastMailboxEmpty bool + LastMailboxResumeFrom string + LastMailboxResumeSequence int64 + LastMailboxResumeConsumerID string + LastMailboxAfterSequence int64 + LastMailboxSkippedCount int + LastMailboxReturnedCount int + LastMailboxPreflightAt time.Time + LastMailboxPreflightConsumerID string + LastMailboxPreflightResumeFrom string + LastMailboxPreflightResumeSequence int64 + LastMailboxPreflightAfterSequence int64 + LastMailboxPreflightAvailableCount int + LastMailboxPreflightReturnedCount int + LastMailboxPreflightSkippedCount int + LastMailboxPreflightFirstSequence int64 + LastMailboxPreflightLastSequence int64 + LastMailboxPreflightFirstRetained int64 + LastMailboxPreflightLastRetained int64 + LastMailboxPreflightMailboxDropped int64 + LastMailboxPreflightDiagnosticState string + LastMailboxPreflightStaleCursor bool + LastMailboxPreflightMissingDropped int + LastMailboxPreflightRecommendedAction string + LastMailboxPreflightActionHints []string + LastMailboxPreflightActionReason string + LastMailboxPreflightActionContext map[string]any + LastMailboxPreflightOperatorSummary string + LastMailboxPreflightOperatorStatus string + LastMailboxPreflightOperatorSeverity string + LastMailboxPreflightOperatorFields map[string]any + LastChannelID string + LastResourceID string + LastRouteID string + LastReason string } type remoteWorkspaceAdapterMailboxConsumerState struct { @@ -251,29 +308,43 @@ type RemoteWorkspaceAdapterMailboxConsumer struct { } type RemoteWorkspaceAdapterMailboxPreflightSnapshot struct { - SchemaVersion string `json:"schema_version"` - AdapterRuntimeID string `json:"adapter_runtime_id"` - AdapterSessionID string `json:"adapter_session_id"` - ObservedAt string `json:"observed_at"` - ReadOnly bool `json:"read_only"` - ConsumerID string `json:"consumer_id"` - ResumeFrom string `json:"resume_from"` - ResumeSequence int64 `json:"resume_sequence"` - AfterSequence int64 `json:"after_sequence"` - Limit int `json:"limit"` - MailboxDepth int `json:"mailbox_depth"` - MailboxEnqueued int64 `json:"mailbox_enqueued_total"` - MailboxReadTotal int64 `json:"mailbox_read_total"` - ConsumerReadTotal int64 `json:"consumer_read_total"` - ConsumerAckTotal int64 `json:"consumer_ack_total"` - ConsumerCheckpointSequence int64 `json:"consumer_checkpoint_sequence"` - ConsumerAckSequence int64 `json:"consumer_ack_sequence"` - ConsumerLagCount int `json:"consumer_lag_count"` - ExpectedAvailableCount int `json:"expected_available_count"` - ExpectedReturnedCount int `json:"expected_returned_count"` - ExpectedSkippedCount int `json:"expected_skipped_count"` - FirstExpectedSequence int64 `json:"first_expected_sequence,omitempty"` - LastExpectedSequence int64 `json:"last_expected_sequence,omitempty"` + SchemaVersion string `json:"schema_version"` + AdapterRuntimeID string `json:"adapter_runtime_id"` + AdapterSessionID string `json:"adapter_session_id"` + ObservedAt string `json:"observed_at"` + ReadOnly bool `json:"read_only"` + ConsumerID string `json:"consumer_id"` + ResumeFrom string `json:"resume_from"` + ResumeSequence int64 `json:"resume_sequence"` + AfterSequence int64 `json:"after_sequence"` + Limit int `json:"limit"` + MailboxDepth int `json:"mailbox_depth"` + MailboxEnqueued int64 `json:"mailbox_enqueued_total"` + MailboxDropped int64 `json:"mailbox_dropped_total"` + MailboxReadTotal int64 `json:"mailbox_read_total"` + ConsumerReadTotal int64 `json:"consumer_read_total"` + ConsumerAckTotal int64 `json:"consumer_ack_total"` + ConsumerCheckpointSequence int64 `json:"consumer_checkpoint_sequence"` + ConsumerAckSequence int64 `json:"consumer_ack_sequence"` + ConsumerLagCount int `json:"consumer_lag_count"` + ExpectedAvailableCount int `json:"expected_available_count"` + ExpectedReturnedCount int `json:"expected_returned_count"` + ExpectedSkippedCount int `json:"expected_skipped_count"` + FirstExpectedSequence int64 `json:"first_expected_sequence,omitempty"` + LastExpectedSequence int64 `json:"last_expected_sequence,omitempty"` + FirstRetainedSequence int64 `json:"first_retained_sequence,omitempty"` + LastRetainedSequence int64 `json:"last_retained_sequence,omitempty"` + DiagnosticState string `json:"diagnostic_state"` + StaleCursor bool `json:"stale_cursor"` + MissingDroppedCount int `json:"missing_dropped_count"` + RecommendedAction string `json:"recommended_action"` + ActionHints []string `json:"action_hints"` + ActionReason string `json:"action_reason"` + ActionContext map[string]any `json:"action_context"` + OperatorSummary string `json:"operator_summary"` + OperatorStatus string `json:"operator_status"` + OperatorSeverity string `json:"operator_severity"` + OperatorSummaryFields map[string]any `json:"operator_summary_fields"` } type RemoteWorkspaceAdapterSessionSnapshot struct { @@ -646,11 +717,13 @@ func (s *RemoteWorkspaceFrameProbeSink) ensureSessionLocked(delivery RemoteWorks session := s.sessions[sessionID] if session == nil { session = &remoteWorkspaceAdapterProbeSession{ - ID: sessionID, - State: "created", - CreatedAt: now, - LastActivityAt: now, - MailboxConsumers: map[string]*remoteWorkspaceAdapterMailboxConsumerState{}, + ID: sessionID, + State: "created", + CreatedAt: now, + LastActivityAt: now, + MailboxConsumers: map[string]*remoteWorkspaceAdapterMailboxConsumerState{}, + MailboxPreflightOperatorStatusCounts: map[string]int64{}, + MailboxPreflightOperatorSeverityCounts: map[string]int64{}, } s.sessions[sessionID] = session s.sessionCreatedTotal++ @@ -1180,7 +1253,74 @@ func (s *RemoteWorkspaceFrameProbeSink) PreflightAdapterSessionMailboxConsumerRe firstExpected = session.Mailbox[startIndex].Sequence lastExpected = session.Mailbox[startIndex+returned-1].Sequence } - return RemoteWorkspaceAdapterMailboxPreflightSnapshot{ + var firstRetained int64 + var lastRetained int64 + if len(session.Mailbox) > 0 { + firstRetained = session.Mailbox[0].Sequence + lastRetained = session.Mailbox[len(session.Mailbox)-1].Sequence + } + diagnosticState := "ready" + staleCursor := false + missingDropped := 0 + recommendedAction := "resume_from_cursor" + actionHints := []string{"resume_from_requested_cursor"} + actionReason := "cursor_window_available" + if firstRetained > 0 && resumeSequence < firstRetained-1 { + diagnosticState = "stale_cursor_gap" + staleCursor = true + missingDropped = int(firstRetained - resumeSequence - 1) + recommendedAction = "reset_consumer_and_resync" + actionHints = []string{"reset_consumer_cursor", "request_full_adapter_resync", "resume_from_checkpoint_after_resync"} + actionReason = "consumer_cursor_before_first_retained_sequence" + } else if returned == 0 { + diagnosticState = "caught_up" + recommendedAction = "wait_for_new_mailbox_events" + actionHints = []string{"keep_consumer_cursor", "long_poll_after_sequence"} + actionReason = "cursor_caught_up_to_retained_mailbox" + } + actionContext := map[string]any{ + "consumer_id": consumerID, + "resume_from": resumeFrom, + "resume_sequence": resumeSequence, + "first_retained_sequence": firstRetained, + "last_retained_sequence": lastRetained, + "mailbox_depth": len(session.Mailbox), + "mailbox_dropped_total": session.MailboxDropped, + "missing_dropped_count": missingDropped, + "expected_available_count": available, + "expected_returned_count": returned, + "expected_skipped_count": startIndex, + "consumer_checkpoint_sequence": consumer.CheckpointSequence, + "consumer_ack_sequence": consumer.AckSequence, + } + operatorSummary := "consumer cursor can resume from requested window" + operatorStatus := "ready_to_resume" + operatorSeverity := "ok" + if diagnosticState == "stale_cursor_gap" { + operatorSummary = "stale cursor gap: reset consumer and resync before resume" + operatorStatus = "resync_required" + operatorSeverity = "warn" + } else if diagnosticState == "caught_up" { + operatorSummary = "consumer cursor is caught up; wait for new mailbox events" + operatorStatus = "caught_up" + operatorSeverity = "info" + } + operatorSummaryFields := map[string]any{ + "diagnostic_state": diagnosticState, + "recommended_action": recommendedAction, + "action_reason": actionReason, + "operator_status": operatorStatus, + "operator_severity": operatorSeverity, + "resume_from": resumeFrom, + "resume_sequence": resumeSequence, + "first_retained_sequence": firstRetained, + "last_retained_sequence": lastRetained, + "missing_dropped_count": missingDropped, + "expected_available_count": available, + "expected_returned_count": returned, + "expected_skipped_count": startIndex, + } + snapshot := RemoteWorkspaceAdapterMailboxPreflightSnapshot{ SchemaVersion: "rap.remote_workspace_adapter_mailbox_preflight.v1", AdapterRuntimeID: RemoteWorkspaceFrameProbeSinkRuntimeID, AdapterSessionID: adapterSessionID, @@ -1193,6 +1333,7 @@ func (s *RemoteWorkspaceFrameProbeSink) PreflightAdapterSessionMailboxConsumerRe Limit: limit, MailboxDepth: len(session.Mailbox), MailboxEnqueued: session.MailboxEnqueued, + MailboxDropped: session.MailboxDropped, MailboxReadTotal: session.MailboxRead, ConsumerReadTotal: session.MailboxConsumerReadTotal, ConsumerAckTotal: session.MailboxConsumerAckTotal, @@ -1204,7 +1345,236 @@ func (s *RemoteWorkspaceFrameProbeSink) PreflightAdapterSessionMailboxConsumerRe ExpectedSkippedCount: startIndex, FirstExpectedSequence: firstExpected, LastExpectedSequence: lastExpected, - }, nil + FirstRetainedSequence: firstRetained, + LastRetainedSequence: lastRetained, + DiagnosticState: diagnosticState, + StaleCursor: staleCursor, + MissingDroppedCount: missingDropped, + RecommendedAction: recommendedAction, + ActionHints: actionHints, + ActionReason: actionReason, + ActionContext: actionContext, + OperatorSummary: operatorSummary, + OperatorStatus: operatorStatus, + OperatorSeverity: operatorSeverity, + OperatorSummaryFields: operatorSummaryFields, + } + s.recordAdapterSessionMailboxPreflightLocked(session, snapshot, now) + return snapshot, nil +} + +func (s *RemoteWorkspaceFrameProbeSink) recordAdapterSessionMailboxPreflightLocked(session *remoteWorkspaceAdapterProbeSession, snapshot RemoteWorkspaceAdapterMailboxPreflightSnapshot, now time.Time) { + s.mailboxPreflightTotal++ + if snapshot.ResumeFrom == "ack" { + s.mailboxPreflightAckTotal++ + } + if snapshot.ResumeFrom == "checkpoint" { + s.mailboxPreflightCheckpointTotal++ + } + s.lastMailboxPreflightAt = now.Format(time.RFC3339Nano) + s.lastMailboxPreflightAdapterSessionID = snapshot.AdapterSessionID + s.lastMailboxPreflightConsumerID = snapshot.ConsumerID + s.lastMailboxPreflightResumeFrom = snapshot.ResumeFrom + s.lastMailboxPreflightResumeSequence = snapshot.ResumeSequence + s.lastMailboxPreflightAfterSequence = snapshot.AfterSequence + s.lastMailboxPreflightAvailableCount = snapshot.ExpectedAvailableCount + s.lastMailboxPreflightReturnedCount = snapshot.ExpectedReturnedCount + s.lastMailboxPreflightSkippedCount = snapshot.ExpectedSkippedCount + s.lastMailboxPreflightFirstSequence = snapshot.FirstExpectedSequence + s.lastMailboxPreflightLastSequence = snapshot.LastExpectedSequence + s.lastMailboxPreflightFirstRetained = snapshot.FirstRetainedSequence + s.lastMailboxPreflightLastRetained = snapshot.LastRetainedSequence + s.lastMailboxPreflightMailboxDropped = snapshot.MailboxDropped + s.lastMailboxPreflightDiagnosticState = snapshot.DiagnosticState + s.lastMailboxPreflightStaleCursor = snapshot.StaleCursor + s.lastMailboxPreflightMissingDropped = snapshot.MissingDroppedCount + s.lastMailboxPreflightRecommendedAction = snapshot.RecommendedAction + s.lastMailboxPreflightActionHints = append([]string(nil), snapshot.ActionHints...) + s.lastMailboxPreflightActionReason = snapshot.ActionReason + s.lastMailboxPreflightActionContext = cloneStringAnyMap(snapshot.ActionContext) + s.lastMailboxPreflightOperatorSummary = snapshot.OperatorSummary + s.lastMailboxPreflightOperatorStatus = snapshot.OperatorStatus + s.lastMailboxPreflightOperatorSeverity = snapshot.OperatorSeverity + s.lastMailboxPreflightOperatorFields = cloneStringAnyMap(snapshot.OperatorSummaryFields) + if session == nil { + return + } + session.MailboxPreflightTotal++ + if snapshot.ResumeFrom == "ack" { + session.MailboxPreflightAckTotal++ + } + if snapshot.ResumeFrom == "checkpoint" { + session.MailboxPreflightCheckpointTotal++ + } + incrementStringInt64Map(&session.MailboxPreflightOperatorStatusCounts, snapshot.OperatorStatus) + incrementStringInt64Map(&session.MailboxPreflightOperatorSeverityCounts, snapshot.OperatorSeverity) + session.LastMailboxPreflightAt = now + session.LastMailboxPreflightConsumerID = snapshot.ConsumerID + session.LastMailboxPreflightResumeFrom = snapshot.ResumeFrom + session.LastMailboxPreflightResumeSequence = snapshot.ResumeSequence + session.LastMailboxPreflightAfterSequence = snapshot.AfterSequence + session.LastMailboxPreflightAvailableCount = snapshot.ExpectedAvailableCount + session.LastMailboxPreflightReturnedCount = snapshot.ExpectedReturnedCount + session.LastMailboxPreflightSkippedCount = snapshot.ExpectedSkippedCount + session.LastMailboxPreflightFirstSequence = snapshot.FirstExpectedSequence + session.LastMailboxPreflightLastSequence = snapshot.LastExpectedSequence + session.LastMailboxPreflightFirstRetained = snapshot.FirstRetainedSequence + session.LastMailboxPreflightLastRetained = snapshot.LastRetainedSequence + session.LastMailboxPreflightMailboxDropped = snapshot.MailboxDropped + session.LastMailboxPreflightDiagnosticState = snapshot.DiagnosticState + session.LastMailboxPreflightStaleCursor = snapshot.StaleCursor + session.LastMailboxPreflightMissingDropped = snapshot.MissingDroppedCount + session.LastMailboxPreflightRecommendedAction = snapshot.RecommendedAction + session.LastMailboxPreflightActionHints = append([]string(nil), snapshot.ActionHints...) + session.LastMailboxPreflightActionReason = snapshot.ActionReason + session.LastMailboxPreflightActionContext = cloneStringAnyMap(snapshot.ActionContext) + session.LastMailboxPreflightOperatorSummary = snapshot.OperatorSummary + session.LastMailboxPreflightOperatorStatus = snapshot.OperatorStatus + session.LastMailboxPreflightOperatorSeverity = snapshot.OperatorSeverity + session.LastMailboxPreflightOperatorFields = cloneStringAnyMap(snapshot.OperatorSummaryFields) +} + +func cloneStringAnyMap(source map[string]any) map[string]any { + if source == nil { + return nil + } + clone := make(map[string]any, len(source)) + for key, value := range source { + clone[key] = value + } + return clone +} + +func cloneStringInt64Map(source map[string]int64) map[string]int64 { + if source == nil { + return nil + } + clone := make(map[string]int64, len(source)) + for key, value := range source { + clone[key] = value + } + return clone +} + +func incrementStringInt64Map(target *map[string]int64, key string) { + key = strings.TrimSpace(key) + if key == "" || target == nil { + return + } + if *target == nil { + *target = map[string]int64{} + } + (*target)[key]++ +} + +func remoteWorkspacePreflightAttentionStatus(statusCounts map[string]int64, severityCounts map[string]int64) string { + resyncCount := statusCounts["resync_required"] + warnCount := severityCounts["warn"] + if resyncCount > 1 || warnCount > 1 { + return "repeated_resync_required" + } + if resyncCount > 0 || warnCount > 0 { + return "needs_attention" + } + if statusCounts["ready_to_resume"] > 0 || statusCounts["caught_up"] > 0 || severityCounts["ok"] > 0 || severityCounts["info"] > 0 { + return "clean" + } + return "unknown" +} + +func remoteWorkspacePreflightAttentionReason(status string, statusCounts map[string]int64, severityCounts map[string]int64) string { + switch status { + case "repeated_resync_required": + return "resync_required_preflight_repeated" + case "needs_attention": + if statusCounts["resync_required"] > 0 { + return "resync_required_preflight_observed" + } + if severityCounts["warn"] > 0 { + return "warn_preflight_observed" + } + return "attention_preflight_observed" + case "clean": + return "no_resync_required_preflight_observed" + default: + return "no_preflight_observed" + } +} + +func remoteWorkspacePreflightRemediationChecklist(operatorStatus string, actionHints []string) []map[string]any { + hints := map[string]bool{} + for _, hint := range actionHints { + hints[hint] = true + } + if operatorStatus == "resync_required" { + return []map[string]any{ + { + "step": "reset_consumer_cursor", + "required": true, + "satisfied": false, + "source_hint": hints["reset_consumer_cursor"], + }, + { + "step": "request_full_adapter_resync", + "required": true, + "satisfied": false, + "source_hint": hints["request_full_adapter_resync"], + }, + { + "step": "resume_from_checkpoint_after_resync", + "required": true, + "satisfied": false, + "source_hint": hints["resume_from_checkpoint_after_resync"], + }, + } + } + if operatorStatus == "ready_to_resume" { + return []map[string]any{{ + "step": "resume_from_requested_cursor", + "required": true, + "satisfied": true, + "source_hint": hints["resume_from_requested_cursor"], + }} + } + return []map[string]any{{ + "step": "wait_for_new_mailbox_events", + "required": true, + "satisfied": false, + "source_hint": hints["long_poll_after_sequence"] || hints["keep_consumer_cursor"], + }} +} + +func remoteWorkspacePreflightRemediationChecklistSummary(checklist []map[string]any) map[string]any { + total := len(checklist) + required := 0 + satisfied := 0 + for _, item := range checklist { + itemRequired, _ := item["required"].(bool) + itemSatisfied, _ := item["satisfied"].(bool) + if itemRequired { + required++ + if itemSatisfied { + satisfied++ + } + } + } + pending := required - satisfied + if pending < 0 { + pending = 0 + } + status := "not_required" + if required > 0 && pending == 0 { + status = "ready" + } else if pending > 0 { + status = "action_required" + } + return map[string]any{ + "status": status, + "total_count": total, + "required_count": required, + "satisfied_count": satisfied, + "pending_count": pending, + } } func (s *RemoteWorkspaceFrameProbeSink) evictOldestMailboxConsumerLocked(session *remoteWorkspaceAdapterProbeSession) bool { @@ -1256,25 +1626,50 @@ func countMailboxConsumersLocked(sessions map[string]*remoteWorkspaceAdapterProb func remoteWorkspaceAdapterRuntimeReadinessLocked(s *RemoteWorkspaceFrameProbeSink, session *remoteWorkspaceAdapterProbeSession, now time.Time) map[string]any { readiness := map[string]any{ - "schema_version": "rap.remote_workspace_adapter_runtime_readiness.v1", - "adapter_runtime_id": RemoteWorkspaceFrameProbeSinkRuntimeID, - "observed_at": now.UTC().Format(time.RFC3339Nano), - "probe_only": true, - "payload_traffic": "none", - "status": "idle", - "diagnostic_state": "waiting_for_session", - "ready": false, - "active_session_count": len(s.sessions), - "terminal_session_count": len(s.terminalSessions), - "mailbox_capacity": DefaultRemoteWorkspaceAdapterMailboxCapacity, - "consumer_capacity": DefaultRemoteWorkspaceAdapterMailboxConsumerCapacity, - "mailbox_read_total": s.mailboxReadTotal, - "mailbox_resume_total": s.mailboxResumeReadTotal, + "schema_version": "rap.remote_workspace_adapter_runtime_readiness.v1", + "adapter_runtime_id": RemoteWorkspaceFrameProbeSinkRuntimeID, + "observed_at": now.UTC().Format(time.RFC3339Nano), + "probe_only": true, + "payload_traffic": "none", + "status": "idle", + "diagnostic_state": "waiting_for_session", + "ready": false, + "active_session_count": len(s.sessions), + "terminal_session_count": len(s.terminalSessions), + "mailbox_capacity": DefaultRemoteWorkspaceAdapterMailboxCapacity, + "consumer_capacity": DefaultRemoteWorkspaceAdapterMailboxConsumerCapacity, + "mailbox_read_total": s.mailboxReadTotal, + "mailbox_resume_total": s.mailboxResumeReadTotal, + "mailbox_preflight_total": s.mailboxPreflightTotal, } if session == nil { + if s.sequence == 0 { + readiness["no_session_summary"] = map[string]any{ + "schema_version": "rap.remote_workspace_adapter_no_session_summary.v1", + "summary_contract": []string{"status", "diagnostic_state", "active_session_count", "terminal_session_count"}, + "summary_features": map[string]bool{"status": true, "diagnostic_state": true, "active_session_count": true, "terminal_session_count": true}, + "status": "idle", + "diagnostic_state": "waiting_for_session", + "active_session_count": len(s.sessions), + "terminal_session_count": len(s.terminalSessions), + } + } if s.sequence > 0 { readiness["last_adapter_session_id"] = s.last.AdapterSessionID - readiness["last_session_state"] = s.last.SessionState + lastSessionState := s.last.SessionState + if terminal, ok := s.terminalSessions[s.last.AdapterSessionID]; ok { + lastSessionState = terminal.State + readiness["terminal_session_summary"] = map[string]any{ + "schema_version": "rap.remote_workspace_adapter_terminal_session_summary.v1", + "summary_contract": []string{"adapter_session_id", "session_state", "reason", "controlled_at"}, + "summary_features": map[string]bool{"adapter_session_id": true, "session_state": true, "reason": true, "controlled_at": true}, + "adapter_session_id": s.last.AdapterSessionID, + "session_state": terminal.State, + "reason": terminal.Reason, + "controlled_at": terminal.ControlledAt.Format(time.RFC3339Nano), + } + } + readiness["last_session_state"] = lastSessionState readiness["diagnostic_state"] = "last_session_terminal_or_expired" } return readiness @@ -1299,6 +1694,13 @@ func remoteWorkspaceAdapterRuntimeReadinessLocked(s *RemoteWorkspaceFrameProbeSi readiness["mailbox_enqueued_total"] = session.MailboxEnqueued readiness["mailbox_read_total"] = session.MailboxRead readiness["mailbox_resume_read_total"] = session.MailboxResumeRead + readiness["mailbox_preflight_total"] = session.MailboxPreflightTotal + readiness["mailbox_preflight_operator_status_counts"] = cloneStringInt64Map(session.MailboxPreflightOperatorStatusCounts) + readiness["mailbox_preflight_operator_severity_counts"] = cloneStringInt64Map(session.MailboxPreflightOperatorSeverityCounts) + preflightAttentionStatus := remoteWorkspacePreflightAttentionStatus(session.MailboxPreflightOperatorStatusCounts, session.MailboxPreflightOperatorSeverityCounts) + preflightAttentionReason := remoteWorkspacePreflightAttentionReason(preflightAttentionStatus, session.MailboxPreflightOperatorStatusCounts, session.MailboxPreflightOperatorSeverityCounts) + readiness["preflight_attention_status"] = preflightAttentionStatus + readiness["preflight_attention_reason"] = preflightAttentionReason readiness["mailbox_after_sequence_read_total"] = session.MailboxAfterSequenceRead readiness["mailbox_returned_total"] = session.MailboxReturnedTotal readiness["mailbox_skipped_total"] = session.MailboxSkippedTotal @@ -1315,6 +1717,66 @@ func remoteWorkspaceAdapterRuntimeReadinessLocked(s *RemoteWorkspaceFrameProbeSi readiness["last_after_sequence"] = session.LastMailboxAfterSequence readiness["last_returned_count"] = session.LastMailboxReturnedCount readiness["last_skipped_count"] = session.LastMailboxSkippedCount + readiness["last_preflight_consumer_id"] = session.LastMailboxPreflightConsumerID + readiness["last_preflight_resume_from"] = session.LastMailboxPreflightResumeFrom + readiness["last_preflight_resume_sequence"] = session.LastMailboxPreflightResumeSequence + readiness["last_preflight_available_count"] = session.LastMailboxPreflightAvailableCount + readiness["last_preflight_returned_count"] = session.LastMailboxPreflightReturnedCount + readiness["last_preflight_skipped_count"] = session.LastMailboxPreflightSkippedCount + readiness["last_preflight_diagnostic_state"] = session.LastMailboxPreflightDiagnosticState + readiness["last_preflight_stale_cursor"] = session.LastMailboxPreflightStaleCursor + readiness["last_preflight_missing_dropped_count"] = session.LastMailboxPreflightMissingDropped + readiness["last_preflight_recommended_action"] = session.LastMailboxPreflightRecommendedAction + readiness["last_preflight_action_hints"] = append([]string(nil), session.LastMailboxPreflightActionHints...) + readiness["last_preflight_action_reason"] = session.LastMailboxPreflightActionReason + readiness["last_preflight_action_context"] = cloneStringAnyMap(session.LastMailboxPreflightActionContext) + readiness["last_preflight_operator_summary"] = session.LastMailboxPreflightOperatorSummary + readiness["last_preflight_operator_status"] = session.LastMailboxPreflightOperatorStatus + readiness["last_preflight_operator_severity"] = session.LastMailboxPreflightOperatorSeverity + readiness["last_preflight_operator_summary_fields"] = cloneStringAnyMap(session.LastMailboxPreflightOperatorFields) + if session.MailboxPreflightTotal > 0 { + remediationChecklist := remoteWorkspacePreflightRemediationChecklist(session.LastMailboxPreflightOperatorStatus, session.LastMailboxPreflightActionHints) + remediationChecklistSummary := remoteWorkspacePreflightRemediationChecklistSummary(remediationChecklist) + readiness["last_preflight"] = map[string]any{ + "diagnostics_schema_version": "rap.remote_workspace_adapter_mailbox_preflight_diagnostics.v1", + "diagnostics_contract": []string{"retained_window", "remediation_checklist", "attention", "operator_counts"}, + "diagnostics_features": map[string]bool{"retained_window": true, "remediation_checklist": true, "attention": true, "operator_counts": true}, + "observed_at": session.LastMailboxPreflightAt.Format(time.RFC3339Nano), + "consumer_id": session.LastMailboxPreflightConsumerID, + "resume_from": session.LastMailboxPreflightResumeFrom, + "resume_sequence": session.LastMailboxPreflightResumeSequence, + "after_sequence": session.LastMailboxPreflightAfterSequence, + "available_count": session.LastMailboxPreflightAvailableCount, + "returned_count": session.LastMailboxPreflightReturnedCount, + "skipped_count": session.LastMailboxPreflightSkippedCount, + "first_sequence": session.LastMailboxPreflightFirstSequence, + "last_sequence": session.LastMailboxPreflightLastSequence, + "first_retained_sequence": session.LastMailboxPreflightFirstRetained, + "last_retained_sequence": session.LastMailboxPreflightLastRetained, + "mailbox_dropped_total": session.LastMailboxPreflightMailboxDropped, + "diagnostic_state": session.LastMailboxPreflightDiagnosticState, + "stale_cursor": session.LastMailboxPreflightStaleCursor, + "missing_dropped_count": session.LastMailboxPreflightMissingDropped, + "recommended_action": session.LastMailboxPreflightRecommendedAction, + "action_hints": append([]string(nil), session.LastMailboxPreflightActionHints...), + "action_reason": session.LastMailboxPreflightActionReason, + "action_context": cloneStringAnyMap(session.LastMailboxPreflightActionContext), + "remediation_checklist": remediationChecklist, + "remediation_checklist_status": remediationChecklistSummary["status"], + "remediation_checklist_counts": remediationChecklistSummary, + "operator_summary": session.LastMailboxPreflightOperatorSummary, + "operator_status": session.LastMailboxPreflightOperatorStatus, + "operator_severity": session.LastMailboxPreflightOperatorSeverity, + "operator_summary_fields": cloneStringAnyMap(session.LastMailboxPreflightOperatorFields), + "mailbox_preflight_total": session.MailboxPreflightTotal, + "mailbox_preflight_ack_total": session.MailboxPreflightAckTotal, + "mailbox_preflight_checkpoint_total": session.MailboxPreflightCheckpointTotal, + "preflight_attention_status": preflightAttentionStatus, + "preflight_attention_reason": preflightAttentionReason, + "operator_status_counts": cloneStringInt64Map(session.MailboxPreflightOperatorStatusCounts), + "operator_severity_counts": cloneStringInt64Map(session.MailboxPreflightOperatorSeverityCounts), + } + } if !session.LastActivityAt.IsZero() { readiness["last_activity_at"] = session.LastActivityAt.Format(time.RFC3339Nano) } @@ -1327,6 +1789,9 @@ func remoteWorkspaceAdapterRuntimeReadinessLocked(s *RemoteWorkspaceFrameProbeSi if !session.LastMailboxConsumerAckAt.IsZero() { readiness["last_consumer_ack_at"] = session.LastMailboxConsumerAckAt.Format(time.RFC3339Nano) } + if !session.LastMailboxPreflightAt.IsZero() { + readiness["last_preflight_at"] = session.LastMailboxPreflightAt.Format(time.RFC3339Nano) + } return readiness } @@ -1445,6 +1910,9 @@ func (s *RemoteWorkspaceFrameProbeSink) Report(now time.Time) map[string]any { report["mailbox_after_sequence_read_total"] = s.mailboxAfterSequenceReadTotal report["mailbox_returned_total"] = s.mailboxReturnedTotal report["mailbox_skipped_total"] = s.mailboxSkippedTotal + report["mailbox_preflight_total"] = s.mailboxPreflightTotal + report["mailbox_preflight_ack_total"] = s.mailboxPreflightAckTotal + report["mailbox_preflight_checkpoint_total"] = s.mailboxPreflightCheckpointTotal report["mailbox_consumer_capacity"] = DefaultRemoteWorkspaceAdapterMailboxConsumerCapacity report["mailbox_consumer_count"] = countMailboxConsumersLocked(s.sessions) report["mailbox_consumer_read_total"] = s.mailboxConsumerReadTotal @@ -1467,6 +1935,30 @@ func (s *RemoteWorkspaceFrameProbeSink) Report(now time.Time) map[string]any { report["last_mailbox_resume_sequence"] = s.lastMailboxResumeSequence report["last_mailbox_resume_consumer_id"] = s.lastMailboxResumeConsumerID } + if s.mailboxPreflightTotal > 0 { + report["last_mailbox_preflight_at"] = s.lastMailboxPreflightAt + report["last_mailbox_preflight_adapter_session_id"] = s.lastMailboxPreflightAdapterSessionID + report["last_mailbox_preflight_consumer_id"] = s.lastMailboxPreflightConsumerID + report["last_mailbox_preflight_resume_from"] = s.lastMailboxPreflightResumeFrom + report["last_mailbox_preflight_resume_sequence"] = s.lastMailboxPreflightResumeSequence + report["last_mailbox_preflight_after_sequence"] = s.lastMailboxPreflightAfterSequence + report["last_mailbox_preflight_available_count"] = s.lastMailboxPreflightAvailableCount + report["last_mailbox_preflight_returned_count"] = s.lastMailboxPreflightReturnedCount + report["last_mailbox_preflight_skipped_count"] = s.lastMailboxPreflightSkippedCount + report["last_mailbox_preflight_first_sequence"] = s.lastMailboxPreflightFirstSequence + report["last_mailbox_preflight_last_sequence"] = s.lastMailboxPreflightLastSequence + report["last_mailbox_preflight_diagnostic_state"] = s.lastMailboxPreflightDiagnosticState + report["last_mailbox_preflight_stale_cursor"] = s.lastMailboxPreflightStaleCursor + report["last_mailbox_preflight_missing_dropped_count"] = s.lastMailboxPreflightMissingDropped + report["last_mailbox_preflight_recommended_action"] = s.lastMailboxPreflightRecommendedAction + report["last_mailbox_preflight_action_hints"] = append([]string(nil), s.lastMailboxPreflightActionHints...) + report["last_mailbox_preflight_action_reason"] = s.lastMailboxPreflightActionReason + report["last_mailbox_preflight_action_context"] = cloneStringAnyMap(s.lastMailboxPreflightActionContext) + report["last_mailbox_preflight_operator_summary"] = s.lastMailboxPreflightOperatorSummary + report["last_mailbox_preflight_operator_status"] = s.lastMailboxPreflightOperatorStatus + report["last_mailbox_preflight_operator_severity"] = s.lastMailboxPreflightOperatorSeverity + report["last_mailbox_preflight_operator_summary_fields"] = cloneStringAnyMap(s.lastMailboxPreflightOperatorFields) + } if s.mailboxConsumerReadTotal > 0 { report["last_mailbox_consumer_id"] = s.lastMailboxConsumerID report["last_mailbox_consumer_read_at"] = s.lastMailboxConsumerReadAt @@ -1520,6 +2012,11 @@ func (s *RemoteWorkspaceFrameProbeSink) Report(now time.Time) map[string]any { report["current_session_mailbox_after_sequence_read_total"] = session.MailboxAfterSequenceRead report["current_session_mailbox_returned_total"] = session.MailboxReturnedTotal report["current_session_mailbox_skipped_total"] = session.MailboxSkippedTotal + report["current_session_mailbox_preflight_total"] = session.MailboxPreflightTotal + report["current_session_mailbox_preflight_ack_total"] = session.MailboxPreflightAckTotal + report["current_session_mailbox_preflight_checkpoint_total"] = session.MailboxPreflightCheckpointTotal + report["current_session_mailbox_preflight_operator_status_counts"] = cloneStringInt64Map(session.MailboxPreflightOperatorStatusCounts) + report["current_session_mailbox_preflight_operator_severity_counts"] = cloneStringInt64Map(session.MailboxPreflightOperatorSeverityCounts) report["current_session_mailbox_consumer_count"] = len(session.MailboxConsumers) report["current_session_mailbox_consumer_read_total"] = session.MailboxConsumerReadTotal report["current_session_mailbox_consumer_ack_total"] = session.MailboxConsumerAckTotal @@ -1549,6 +2046,29 @@ func (s *RemoteWorkspaceFrameProbeSink) Report(now time.Time) map[string]any { report["current_session_last_mailbox_resume_sequence"] = session.LastMailboxResumeSequence report["current_session_last_mailbox_resume_consumer_id"] = session.LastMailboxResumeConsumerID } + if session.MailboxPreflightTotal > 0 { + report["current_session_last_mailbox_preflight_at"] = session.LastMailboxPreflightAt.Format(time.RFC3339Nano) + report["current_session_last_mailbox_preflight_consumer_id"] = session.LastMailboxPreflightConsumerID + report["current_session_last_mailbox_preflight_resume_from"] = session.LastMailboxPreflightResumeFrom + report["current_session_last_mailbox_preflight_resume_sequence"] = session.LastMailboxPreflightResumeSequence + report["current_session_last_mailbox_preflight_after_sequence"] = session.LastMailboxPreflightAfterSequence + report["current_session_last_mailbox_preflight_available_count"] = session.LastMailboxPreflightAvailableCount + report["current_session_last_mailbox_preflight_returned_count"] = session.LastMailboxPreflightReturnedCount + report["current_session_last_mailbox_preflight_skipped_count"] = session.LastMailboxPreflightSkippedCount + report["current_session_last_mailbox_preflight_first_sequence"] = session.LastMailboxPreflightFirstSequence + report["current_session_last_mailbox_preflight_last_sequence"] = session.LastMailboxPreflightLastSequence + report["current_session_last_mailbox_preflight_diagnostic_state"] = session.LastMailboxPreflightDiagnosticState + report["current_session_last_mailbox_preflight_stale_cursor"] = session.LastMailboxPreflightStaleCursor + report["current_session_last_mailbox_preflight_missing_dropped_count"] = session.LastMailboxPreflightMissingDropped + report["current_session_last_mailbox_preflight_recommended_action"] = session.LastMailboxPreflightRecommendedAction + report["current_session_last_mailbox_preflight_action_hints"] = append([]string(nil), session.LastMailboxPreflightActionHints...) + report["current_session_last_mailbox_preflight_action_reason"] = session.LastMailboxPreflightActionReason + report["current_session_last_mailbox_preflight_action_context"] = cloneStringAnyMap(session.LastMailboxPreflightActionContext) + report["current_session_last_mailbox_preflight_operator_summary"] = session.LastMailboxPreflightOperatorSummary + report["current_session_last_mailbox_preflight_operator_status"] = session.LastMailboxPreflightOperatorStatus + report["current_session_last_mailbox_preflight_operator_severity"] = session.LastMailboxPreflightOperatorSeverity + report["current_session_last_mailbox_preflight_operator_summary_fields"] = cloneStringAnyMap(session.LastMailboxPreflightOperatorFields) + } if !session.LastBackpressureAt.IsZero() { report["current_session_last_backpressure_at"] = session.LastBackpressureAt.Format(time.RFC3339Nano) report["current_session_last_backpressure_reason"] = session.LastReason diff --git a/agents/rap-node-agent/internal/mesh/server_test.go b/agents/rap-node-agent/internal/mesh/server_test.go index d49c389..26790c3 100644 --- a/agents/rap-node-agent/internal/mesh/server_test.go +++ b/agents/rap-node-agent/internal/mesh/server_test.go @@ -1643,6 +1643,44 @@ func TestRemoteWorkspaceAdapterSessionControlEndpointClosesSession(t *testing.T) report["last_session_control_state"] != "closed" { t.Fatalf("control report = %+v", report) } + readiness, ok := report["adapter_runtime_readiness"].(map[string]any) + if !ok { + t.Fatalf("adapter runtime readiness missing from control report = %+v", report) + } + if readiness["schema_version"] != "rap.remote_workspace_adapter_runtime_readiness.v1" || + readiness["status"] != "idle" || + readiness["diagnostic_state"] != "last_session_terminal_or_expired" || + readiness["ready"] != false || + readiness["active_session_count"] != 0 || + readiness["last_adapter_session_id"] != "rap-rw-adapter-session-aaaaaaaaaaaaaaaaaaaaaaaa" || + readiness["last_session_state"] != "closed" { + t.Fatalf("invalid no-active-session readiness after close = %+v", readiness) + } + if _, ok := readiness["adapter_session_id"]; ok { + t.Fatalf("adapter_session_id should be absent without active session = %+v", readiness) + } + if _, ok := readiness["last_preflight"]; ok { + t.Fatalf("last_preflight should be absent without active session = %+v", readiness) + } + terminalSummary, ok := readiness["terminal_session_summary"].(map[string]any) + if !ok { + t.Fatalf("terminal session summary missing after close = %+v", readiness) + } + if terminalSummary["adapter_session_id"] != "rap-rw-adapter-session-aaaaaaaaaaaaaaaaaaaaaaaa" || + terminalSummary["schema_version"] != "rap.remote_workspace_adapter_terminal_session_summary.v1" || + !stringAnySliceContains(terminalSummary["summary_contract"], "adapter_session_id") || + !stringAnySliceContains(terminalSummary["summary_contract"], "session_state") || + !stringAnySliceContains(terminalSummary["summary_contract"], "reason") || + !stringAnySliceContains(terminalSummary["summary_contract"], "controlled_at") || + !boolMapValue(terminalSummary["summary_features"], "adapter_session_id") || + !boolMapValue(terminalSummary["summary_features"], "session_state") || + !boolMapValue(terminalSummary["summary_features"], "reason") || + !boolMapValue(terminalSummary["summary_features"], "controlled_at") || + terminalSummary["session_state"] != "closed" || + terminalSummary["reason"] != "unit test close" || + terminalSummary["controlled_at"] == "" { + t.Fatalf("invalid terminal session summary after close = %+v", terminalSummary) + } resp, err = http.Post(controlURL, "application/json", bytes.NewReader([]byte(`{"action":"close","reason":"repeat close"}`))) if err != nil { @@ -1665,6 +1703,255 @@ func TestRemoteWorkspaceAdapterSessionControlEndpointClosesSession(t *testing.T) } } +func TestRemoteWorkspaceAdapterReadinessBeforeAnySessionHasNoTerminalSummary(t *testing.T) { + sink := NewRemoteWorkspaceFrameProbeSink() + report := sink.Report(time.Now().UTC()) + readiness, ok := report["adapter_runtime_readiness"].(map[string]any) + if !ok { + t.Fatalf("adapter runtime readiness missing from report = %+v", report) + } + if readiness["schema_version"] != "rap.remote_workspace_adapter_runtime_readiness.v1" || + readiness["status"] != "idle" || + readiness["diagnostic_state"] != "waiting_for_session" || + readiness["ready"] != false || + readiness["active_session_count"] != 0 || + readiness["terminal_session_count"] != 0 { + t.Fatalf("invalid empty readiness = %+v", readiness) + } + if _, ok := readiness["last_adapter_session_id"]; ok { + t.Fatalf("last_adapter_session_id should be absent before any session = %+v", readiness) + } + if _, ok := readiness["last_session_state"]; ok { + t.Fatalf("last_session_state should be absent before any session = %+v", readiness) + } + if _, ok := readiness["terminal_session_summary"]; ok { + t.Fatalf("terminal_session_summary should be absent before terminal history = %+v", readiness) + } + noSessionSummary, ok := readiness["no_session_summary"].(map[string]any) + if !ok { + t.Fatalf("no_session_summary should be present before any session = %+v", readiness) + } + if noSessionSummary["schema_version"] != "rap.remote_workspace_adapter_no_session_summary.v1" || + !stringAnySliceContains(noSessionSummary["summary_contract"], "status") || + !stringAnySliceContains(noSessionSummary["summary_contract"], "diagnostic_state") || + !stringAnySliceContains(noSessionSummary["summary_contract"], "active_session_count") || + !stringAnySliceContains(noSessionSummary["summary_contract"], "terminal_session_count") || + !boolMapValue(noSessionSummary["summary_features"], "status") || + !boolMapValue(noSessionSummary["summary_features"], "diagnostic_state") || + !boolMapValue(noSessionSummary["summary_features"], "active_session_count") || + !boolMapValue(noSessionSummary["summary_features"], "terminal_session_count") || + noSessionSummary["status"] != "idle" || + noSessionSummary["diagnostic_state"] != "waiting_for_session" || + noSessionSummary["active_session_count"] != 0 || + noSessionSummary["terminal_session_count"] != 0 { + t.Fatalf("invalid no-session summary before any session = %+v", noSessionSummary) + } + if _, ok := readiness["last_preflight"]; ok { + t.Fatalf("last_preflight should be absent before any session = %+v", readiness) + } +} + +func TestRemoteWorkspaceAdapterReadinessSummaryExclusivity(t *testing.T) { + sink := NewRemoteWorkspaceFrameProbeSink() + freshReport := sink.Report(time.Now().UTC()) + freshReadiness, ok := freshReport["adapter_runtime_readiness"].(map[string]any) + if !ok { + t.Fatalf("fresh readiness missing from report = %+v", freshReport) + } + if _, ok := freshReadiness["no_session_summary"]; !ok { + t.Fatalf("fresh readiness should include no_session_summary = %+v", freshReadiness) + } + if _, ok := freshReadiness["terminal_session_summary"]; ok { + t.Fatalf("fresh readiness should not include terminal_session_summary = %+v", freshReadiness) + } + + sessionID := "rap-rw-adapter-session-d1d1d1d1d1d1d1d1d1d1d1d1" + delivery := RemoteWorkspaceFrameBatchDelivery{ + ClusterID: "cluster-1", + ChannelID: "channel-rw", + ResourceID: "workspace-exclusivity", + ServiceClass: FabricServiceClassRemoteWorkspace, + ChannelClass: FabricServiceChannelInteractive, + AdapterContractID: "rap.rdp_worker.remote_workspace_adapter_contract_probe.v1", + AdapterSessionID: sessionID, + Frames: []RemoteWorkspaceFrameProbeRecord{{ + Channel: "display", + Direction: "adapter_to_client", + Droppable: true, + }}, + } + if _, err := sink.AcceptRemoteWorkspaceFrameBatchProbe(context.Background(), delivery); err != nil { + t.Fatalf("accept frame batch: %v", err) + } + activeReport := sink.Report(time.Now().UTC()) + activeReadiness, ok := activeReport["adapter_runtime_readiness"].(map[string]any) + if !ok { + t.Fatalf("active readiness missing from report = %+v", activeReport) + } + if activeReadiness["adapter_session_id"] != sessionID || + activeReadiness["active_session_count"] != 1 { + t.Fatalf("invalid active readiness = %+v", activeReadiness) + } + if _, ok := activeReadiness["no_session_summary"]; ok { + t.Fatalf("active readiness should not include no_session_summary = %+v", activeReadiness) + } + if _, ok := activeReadiness["terminal_session_summary"]; ok { + t.Fatalf("active readiness should not include terminal_session_summary = %+v", activeReadiness) + } + + server := httptest.NewServer(Server{RemoteWorkspaceFrameSink: sink}.Handler()) + defer server.Close() + body := bytes.NewReader([]byte(`{"action":"close","reason":"unit summary exclusivity close"}`)) + resp, err := http.Post(server.URL+"/mesh/v1/remote-workspace/adapter-sessions/"+sessionID+"/control", "application/json", body) + if err != nil { + t.Fatalf("post control: %v", err) + } + defer resp.Body.Close() + if resp.StatusCode != http.StatusOK { + raw, _ := io.ReadAll(resp.Body) + t.Fatalf("status = %d body=%s", resp.StatusCode, string(raw)) + } + terminalReport := sink.Report(time.Now().UTC()) + terminalReadiness, ok := terminalReport["adapter_runtime_readiness"].(map[string]any) + if !ok { + t.Fatalf("terminal readiness missing from report = %+v", terminalReport) + } + if _, ok := terminalReadiness["terminal_session_summary"]; !ok { + t.Fatalf("terminal readiness should include terminal_session_summary = %+v", terminalReadiness) + } + if _, ok := terminalReadiness["no_session_summary"]; ok { + t.Fatalf("terminal readiness should not include no_session_summary = %+v", terminalReadiness) + } +} + +func TestRemoteWorkspaceAdapterSessionControlTerminalReadinessStates(t *testing.T) { + tests := []struct { + action string + sessionID string + wantState string + wantClosed int64 + wantExpired int64 + wantReset int64 + wantPrevState string + }{ + { + action: "expire", + sessionID: "rap-rw-adapter-session-b0b0b0b0b0b0b0b0b0b0b0b0", + wantState: "expired", + wantClosed: 1, + wantExpired: 1, + wantPrevState: "probe_bound", + }, + { + action: "reset", + sessionID: "rap-rw-adapter-session-c0c0c0c0c0c0c0c0c0c0c0c0", + wantState: "reset", + wantClosed: 1, + wantReset: 1, + wantPrevState: "probe_bound", + }, + } + + for _, tt := range tests { + t.Run(tt.action, func(t *testing.T) { + sink := NewRemoteWorkspaceFrameProbeSink() + delivery := RemoteWorkspaceFrameBatchDelivery{ + ClusterID: "cluster-1", + ChannelID: "channel-rw", + ResourceID: "workspace-" + tt.action, + ServiceClass: FabricServiceClassRemoteWorkspace, + ChannelClass: FabricServiceChannelInteractive, + AdapterContractID: "rap.rdp_worker.remote_workspace_adapter_contract_probe.v1", + AdapterSessionID: tt.sessionID, + Frames: []RemoteWorkspaceFrameProbeRecord{{ + Channel: "display", + Direction: "adapter_to_client", + Droppable: true, + }}, + } + if _, err := sink.AcceptRemoteWorkspaceFrameBatchProbe(context.Background(), delivery); err != nil { + t.Fatalf("accept frame batch: %v", err) + } + server := httptest.NewServer(Server{RemoteWorkspaceFrameSink: sink}.Handler()) + defer server.Close() + + body := bytes.NewReader([]byte(fmt.Sprintf(`{"action":%q,"reason":"unit terminal readiness"}`, tt.action))) + resp, err := http.Post(server.URL+"/mesh/v1/remote-workspace/adapter-sessions/"+tt.sessionID+"/control", "application/json", body) + if err != nil { + t.Fatalf("post control: %v", err) + } + defer resp.Body.Close() + if resp.StatusCode != http.StatusOK { + raw, _ := io.ReadAll(resp.Body) + t.Fatalf("status = %d body=%s", resp.StatusCode, string(raw)) + } + var result RemoteWorkspaceAdapterSessionControlResult + if err := json.NewDecoder(resp.Body).Decode(&result); err != nil { + t.Fatalf("decode control result: %v", err) + } + if !result.Accepted || + result.Action != tt.action || + result.AdapterSessionID != tt.sessionID || + result.PreviousState != tt.wantPrevState || + result.SessionState != tt.wantState || + result.ActiveSessions != 0 { + t.Fatalf("control result = %+v", result) + } + + report := sink.Report(time.Now().UTC()) + if report["active_session_count"] != 0 || + report["session_closed_total"] != tt.wantClosed || + report["session_expired_total"] != tt.wantExpired || + report["session_reset_total"] != tt.wantReset || + report["last_controlled_adapter_session_id"] != tt.sessionID || + report["last_session_control_action"] != tt.action || + report["last_session_control_state"] != tt.wantState { + t.Fatalf("terminal control report = %+v", report) + } + readiness, ok := report["adapter_runtime_readiness"].(map[string]any) + if !ok { + t.Fatalf("adapter runtime readiness missing from report = %+v", report) + } + if readiness["status"] != "idle" || + readiness["diagnostic_state"] != "last_session_terminal_or_expired" || + readiness["ready"] != false || + readiness["active_session_count"] != 0 || + readiness["last_adapter_session_id"] != tt.sessionID || + readiness["last_session_state"] != tt.wantState { + t.Fatalf("invalid terminal readiness = %+v", readiness) + } + if _, ok := readiness["adapter_session_id"]; ok { + t.Fatalf("adapter_session_id should be absent without active session = %+v", readiness) + } + if _, ok := readiness["last_preflight"]; ok { + t.Fatalf("last_preflight should be absent without active session = %+v", readiness) + } + if _, ok := readiness["no_session_summary"]; ok { + t.Fatalf("no_session_summary should be absent for terminal session history = %+v", readiness) + } + terminalSummary, ok := readiness["terminal_session_summary"].(map[string]any) + if !ok { + t.Fatalf("terminal session summary missing = %+v", readiness) + } + if terminalSummary["adapter_session_id"] != tt.sessionID || + terminalSummary["schema_version"] != "rap.remote_workspace_adapter_terminal_session_summary.v1" || + !stringAnySliceContains(terminalSummary["summary_contract"], "adapter_session_id") || + !stringAnySliceContains(terminalSummary["summary_contract"], "session_state") || + !stringAnySliceContains(terminalSummary["summary_contract"], "reason") || + !stringAnySliceContains(terminalSummary["summary_contract"], "controlled_at") || + !boolMapValue(terminalSummary["summary_features"], "adapter_session_id") || + !boolMapValue(terminalSummary["summary_features"], "session_state") || + !boolMapValue(terminalSummary["summary_features"], "reason") || + !boolMapValue(terminalSummary["summary_features"], "controlled_at") || + terminalSummary["session_state"] != tt.wantState || + terminalSummary["reason"] != "unit terminal readiness" || + terminalSummary["controlled_at"] == "" { + t.Fatalf("invalid terminal session summary = %+v", terminalSummary) + } + }) + } +} + func TestRemoteWorkspaceAdapterSessionControlRejectsInvalidRequests(t *testing.T) { sink := NewRemoteWorkspaceFrameProbeSink() server := httptest.NewServer(Server{RemoteWorkspaceFrameSink: sink}.Handler()) @@ -3064,6 +3351,19 @@ func TestRemoteWorkspaceAdapterSessionMailboxPreflightIsReadOnly(t *testing.T) { } if preflight.ResumeFrom != "checkpoint" || preflight.ResumeSequence != 2 || + preflight.DiagnosticState != "ready" || + preflight.RecommendedAction != "resume_from_cursor" || + preflight.ActionReason != "cursor_window_available" || + preflight.OperatorSummary != "consumer cursor can resume from requested window" || + preflight.OperatorStatus != "ready_to_resume" || + preflight.OperatorSeverity != "ok" || + anyInt64(preflight.ActionContext["resume_sequence"]) != 2 || + anyInt64(preflight.ActionContext["first_retained_sequence"]) != 1 || + preflight.OperatorSummaryFields["diagnostic_state"] != "ready" || + preflight.OperatorSummaryFields["recommended_action"] != "resume_from_cursor" || + preflight.OperatorSummaryFields["operator_status"] != "ready_to_resume" || + preflight.OperatorSummaryFields["operator_severity"] != "ok" || + !stringSliceContains(preflight.ActionHints, "resume_from_requested_cursor") || preflight.ExpectedAvailableCount != 1 || preflight.ExpectedReturnedCount != 1 || preflight.ExpectedSkippedCount != 2 || @@ -3079,6 +3379,547 @@ func TestRemoteWorkspaceAdapterSessionMailboxPreflightIsReadOnly(t *testing.T) { reportAfter["current_session_mailbox_consumer_ack_total"] != reportBefore["current_session_mailbox_consumer_ack_total"] { t.Fatalf("preflight mutated report before=%+v after=%+v", reportBefore, reportAfter) } + if reportAfter["mailbox_preflight_total"] != int64(2) || + reportAfter["mailbox_preflight_ack_total"] != int64(1) || + reportAfter["mailbox_preflight_checkpoint_total"] != int64(1) || + reportAfter["last_mailbox_preflight_adapter_session_id"] != sessionID || + reportAfter["last_mailbox_preflight_consumer_id"] != "rdp-worker-probe" || + reportAfter["last_mailbox_preflight_resume_from"] != "checkpoint" || + reportAfter["last_mailbox_preflight_resume_sequence"] != int64(2) || + reportAfter["last_mailbox_preflight_available_count"] != 1 || + reportAfter["last_mailbox_preflight_returned_count"] != 1 || + reportAfter["last_mailbox_preflight_skipped_count"] != 2 || + reportAfter["current_session_mailbox_preflight_total"] != int64(2) || + reportAfter["current_session_mailbox_preflight_ack_total"] != int64(1) || + reportAfter["current_session_mailbox_preflight_checkpoint_total"] != int64(1) || + mapInt64Value(reportAfter["current_session_mailbox_preflight_operator_status_counts"], "ready_to_resume") != 2 || + mapInt64Value(reportAfter["current_session_mailbox_preflight_operator_severity_counts"], "ok") != 2 || + reportAfter["current_session_last_mailbox_preflight_resume_from"] != "checkpoint" || + reportAfter["current_session_last_mailbox_preflight_resume_sequence"] != int64(2) || + reportAfter["current_session_last_mailbox_preflight_returned_count"] != 1 || + reportAfter["current_session_last_mailbox_preflight_recommended_action"] != "resume_from_cursor" || + reportAfter["last_mailbox_preflight_operator_summary"] != "consumer cursor can resume from requested window" || + reportAfter["last_mailbox_preflight_operator_status"] != "ready_to_resume" || + reportAfter["last_mailbox_preflight_operator_severity"] != "ok" || + reportAfter["current_session_last_mailbox_preflight_operator_summary"] != "consumer cursor can resume from requested window" || + reportAfter["current_session_last_mailbox_preflight_operator_status"] != "ready_to_resume" || + reportAfter["current_session_last_mailbox_preflight_operator_severity"] != "ok" { + t.Fatalf("invalid preflight telemetry report = %+v", reportAfter) + } + readiness, ok := reportAfter["adapter_runtime_readiness"].(map[string]any) + if !ok { + t.Fatalf("adapter runtime readiness missing from report = %+v", reportAfter) + } + if readiness["mailbox_preflight_total"] != int64(2) || + readiness["last_preflight_consumer_id"] != "rdp-worker-probe" || + readiness["last_preflight_resume_from"] != "checkpoint" || + readiness["last_preflight_resume_sequence"] != int64(2) || + readiness["last_preflight_returned_count"] != 1 || + readiness["last_preflight_skipped_count"] != 2 || + readiness["last_preflight_recommended_action"] != "resume_from_cursor" || + readiness["last_preflight_action_reason"] != "cursor_window_available" || + readiness["last_preflight_operator_summary"] != "consumer cursor can resume from requested window" || + readiness["last_preflight_operator_status"] != "ready_to_resume" || + readiness["last_preflight_operator_severity"] != "ok" || + mapInt64Value(readiness["mailbox_preflight_operator_status_counts"], "ready_to_resume") != 2 || + mapInt64Value(readiness["mailbox_preflight_operator_severity_counts"], "ok") != 2 || + readiness["preflight_attention_status"] != "clean" || + readiness["preflight_attention_reason"] != "no_resync_required_preflight_observed" { + t.Fatalf("invalid preflight readiness = %+v", readiness) + } + lastPreflight, ok := readiness["last_preflight"].(map[string]any) + if !ok { + t.Fatalf("last preflight rollup missing from readiness = %+v", readiness) + } + if lastPreflight["consumer_id"] != "rdp-worker-probe" || + lastPreflight["diagnostics_schema_version"] != "rap.remote_workspace_adapter_mailbox_preflight_diagnostics.v1" || + !stringAnySliceContains(lastPreflight["diagnostics_contract"], "retained_window") || + !stringAnySliceContains(lastPreflight["diagnostics_contract"], "remediation_checklist") || + !stringAnySliceContains(lastPreflight["diagnostics_contract"], "attention") || + !stringAnySliceContains(lastPreflight["diagnostics_contract"], "operator_counts") || + !boolMapValue(lastPreflight["diagnostics_features"], "retained_window") || + !boolMapValue(lastPreflight["diagnostics_features"], "remediation_checklist") || + !boolMapValue(lastPreflight["diagnostics_features"], "attention") || + !boolMapValue(lastPreflight["diagnostics_features"], "operator_counts") || + lastPreflight["resume_from"] != "checkpoint" || + lastPreflight["operator_status"] != "ready_to_resume" || + lastPreflight["operator_severity"] != "ok" || + lastPreflight["recommended_action"] != "resume_from_cursor" || + !preflightChecklistContains(lastPreflight["remediation_checklist"], "resume_from_requested_cursor", true, true) || + lastPreflight["remediation_checklist_status"] != "ready" || + anyInt64(preflightChecklistCountsValue(lastPreflight["remediation_checklist_counts"], "required_count")) != 1 || + anyInt64(preflightChecklistCountsValue(lastPreflight["remediation_checklist_counts"], "satisfied_count")) != 1 || + anyInt64(preflightChecklistCountsValue(lastPreflight["remediation_checklist_counts"], "pending_count")) != 0 || + mapInt64Value(lastPreflight["operator_status_counts"], "ready_to_resume") != 2 || + mapInt64Value(lastPreflight["operator_severity_counts"], "ok") != 2 || + lastPreflight["preflight_attention_status"] != "clean" || + lastPreflight["preflight_attention_reason"] != "no_resync_required_preflight_observed" || + anyInt64(lastPreflight["resume_sequence"]) != 2 || + anyInt64(lastPreflight["first_retained_sequence"]) != 1 || + anyInt64(lastPreflight["last_retained_sequence"]) != 3 || + anyInt64(lastPreflight["mailbox_dropped_total"]) != 0 || + anyInt64(lastPreflight["mailbox_preflight_total"]) != 2 { + t.Fatalf("invalid last preflight rollup = %+v", lastPreflight) + } +} + +func TestRemoteWorkspaceAdapterSessionReadinessBeforePreflight(t *testing.T) { + sink := NewRemoteWorkspaceFrameProbeSink() + sessionID := "rap-rw-adapter-session-a0a0a0a0a0a0a0a0a0a0a0a0" + delivery := RemoteWorkspaceFrameBatchDelivery{ + ClusterID: "cluster-1", + ChannelID: "channel-rw", + ResourceID: "workspace-before-preflight", + ServiceClass: FabricServiceClassRemoteWorkspace, + ChannelClass: FabricServiceChannelInteractive, + AdapterContractID: "rap.rdp_worker.remote_workspace_adapter_contract_probe.v1", + AdapterSessionID: sessionID, + Frames: []RemoteWorkspaceFrameProbeRecord{{ + Channel: "display", + Direction: "adapter_to_client", + Droppable: true, + }}, + } + if _, err := sink.AcceptRemoteWorkspaceFrameBatchProbe(context.Background(), delivery); err != nil { + t.Fatalf("accept frame batch: %v", err) + } + + report := sink.Report(time.Now().UTC()) + if report["mailbox_preflight_total"] != int64(0) || + report["current_session_mailbox_preflight_total"] != int64(0) { + t.Fatalf("unexpected preflight totals before preflight = %+v", report) + } + readiness, ok := report["adapter_runtime_readiness"].(map[string]any) + if !ok { + t.Fatalf("adapter runtime readiness missing from report = %+v", report) + } + if readiness["adapter_session_id"] != sessionID || + readiness["mailbox_preflight_total"] != int64(0) || + readiness["preflight_attention_status"] != "unknown" || + readiness["preflight_attention_reason"] != "no_preflight_observed" { + t.Fatalf("invalid no-preflight readiness = %+v", readiness) + } + if _, ok := readiness["last_preflight"]; ok { + t.Fatalf("last preflight rollup should be absent before preflight = %+v", readiness["last_preflight"]) + } + if readiness["last_preflight_diagnostic_state"] != "" || + readiness["last_preflight_recommended_action"] != "" || + len(readiness["last_preflight_action_hints"].([]string)) != 0 { + t.Fatalf("last preflight flat fields should be empty before preflight = %+v", readiness) + } +} + +func TestRemoteWorkspaceAdapterSessionMailboxPreflightReportsStaleCursorGap(t *testing.T) { + sink := NewRemoteWorkspaceFrameProbeSink() + sessionID := "rap-rw-adapter-session-adadadadadadadadadadadad" + delivery := RemoteWorkspaceFrameBatchDelivery{ + ClusterID: "cluster-1", + ChannelID: "channel-rw", + ResourceID: "workspace-stale-0", + ServiceClass: FabricServiceClassRemoteWorkspace, + ChannelClass: FabricServiceChannelInteractive, + AdapterContractID: "rap.rdp_worker.remote_workspace_adapter_contract_probe.v1", + AdapterSessionID: sessionID, + Frames: []RemoteWorkspaceFrameProbeRecord{{ + Channel: "display", + Direction: "adapter_to_client", + Droppable: true, + }}, + } + if _, err := sink.AcceptRemoteWorkspaceFrameBatchProbe(context.Background(), delivery); err != nil { + t.Fatalf("accept initial frame batch: %v", err) + } + server := httptest.NewServer(Server{RemoteWorkspaceFrameSink: sink}.Handler()) + defer server.Close() + + resp, err := http.Get(server.URL + "/mesh/v1/remote-workspace/adapter-sessions/" + sessionID + "/mailbox?consumer_id=rdp-worker-probe&ack_sequence=1&limit=1") + if err != nil { + t.Fatalf("seed ack cursor: %v", err) + } + resp.Body.Close() + for i := 1; i <= DefaultRemoteWorkspaceAdapterMailboxCapacity+2; i++ { + delivery.ResourceID = fmt.Sprintf("workspace-stale-%d", i) + if _, err := sink.AcceptRemoteWorkspaceFrameBatchProbe(context.Background(), delivery); err != nil { + t.Fatalf("accept overflow frame batch %d: %v", i, err) + } + } + + resp, err = http.Get(server.URL + "/mesh/v1/remote-workspace/adapter-sessions/" + sessionID + "/mailbox/preflight?consumer_id=rdp-worker-probe&resume_from=ack&limit=3") + if err != nil { + t.Fatalf("get stale preflight: %v", err) + } + defer resp.Body.Close() + var preflight RemoteWorkspaceAdapterMailboxPreflightSnapshot + if err := json.NewDecoder(resp.Body).Decode(&preflight); err != nil { + t.Fatalf("decode stale preflight: %v", err) + } + if preflight.ResumeFrom != "ack" || + preflight.ResumeSequence != 1 || + preflight.MailboxDepth != DefaultRemoteWorkspaceAdapterMailboxCapacity || + preflight.MailboxDropped != 3 || + preflight.ExpectedAvailableCount != DefaultRemoteWorkspaceAdapterMailboxCapacity || + preflight.ExpectedReturnedCount != 3 || + preflight.ExpectedSkippedCount != 0 || + preflight.FirstExpectedSequence != 4 || + preflight.LastExpectedSequence != 6 || + preflight.FirstRetainedSequence != 4 || + preflight.LastRetainedSequence != 19 || + preflight.DiagnosticState != "stale_cursor_gap" || + !preflight.StaleCursor || + preflight.MissingDroppedCount != 2 || + preflight.RecommendedAction != "reset_consumer_and_resync" || + preflight.ActionReason != "consumer_cursor_before_first_retained_sequence" || + preflight.OperatorSummary != "stale cursor gap: reset consumer and resync before resume" || + preflight.OperatorStatus != "resync_required" || + preflight.OperatorSeverity != "warn" || + anyInt64(preflight.ActionContext["resume_sequence"]) != 1 || + anyInt64(preflight.ActionContext["first_retained_sequence"]) != 4 || + anyInt64(preflight.ActionContext["missing_dropped_count"]) != 2 || + preflight.OperatorSummaryFields["diagnostic_state"] != "stale_cursor_gap" || + preflight.OperatorSummaryFields["recommended_action"] != "reset_consumer_and_resync" || + preflight.OperatorSummaryFields["operator_status"] != "resync_required" || + preflight.OperatorSummaryFields["operator_severity"] != "warn" || + anyInt64(preflight.OperatorSummaryFields["missing_dropped_count"]) != 2 || + !stringSliceContains(preflight.ActionHints, "reset_consumer_cursor") || + !stringSliceContains(preflight.ActionHints, "request_full_adapter_resync") || + !stringSliceContains(preflight.ActionHints, "resume_from_checkpoint_after_resync") { + t.Fatalf("stale preflight = %+v", preflight) + } + resp, err = http.Get(server.URL + "/mesh/v1/remote-workspace/adapter-sessions/" + sessionID + "/mailbox/preflight?consumer_id=rdp-worker-probe&resume_from=ack&limit=3") + if err != nil { + t.Fatalf("get repeated stale preflight: %v", err) + } + resp.Body.Close() + report := sink.Report(time.Now().UTC()) + if report["last_mailbox_preflight_diagnostic_state"] != "stale_cursor_gap" || + report["last_mailbox_preflight_stale_cursor"] != true || + report["last_mailbox_preflight_missing_dropped_count"] != 2 || + report["last_mailbox_preflight_recommended_action"] != "reset_consumer_and_resync" || + report["last_mailbox_preflight_action_reason"] != "consumer_cursor_before_first_retained_sequence" || + report["last_mailbox_preflight_operator_summary"] != "stale cursor gap: reset consumer and resync before resume" || + report["last_mailbox_preflight_operator_status"] != "resync_required" || + report["last_mailbox_preflight_operator_severity"] != "warn" || + report["current_session_last_mailbox_preflight_diagnostic_state"] != "stale_cursor_gap" || + report["current_session_last_mailbox_preflight_stale_cursor"] != true || + report["current_session_last_mailbox_preflight_missing_dropped_count"] != 2 || + report["current_session_last_mailbox_preflight_recommended_action"] != "reset_consumer_and_resync" || + report["current_session_last_mailbox_preflight_operator_summary"] != "stale cursor gap: reset consumer and resync before resume" || + report["current_session_last_mailbox_preflight_operator_status"] != "resync_required" || + report["current_session_last_mailbox_preflight_operator_severity"] != "warn" || + mapInt64Value(report["current_session_mailbox_preflight_operator_status_counts"], "resync_required") != 2 || + mapInt64Value(report["current_session_mailbox_preflight_operator_severity_counts"], "warn") != 2 { + t.Fatalf("stale preflight report = %+v", report) + } + readiness, ok := report["adapter_runtime_readiness"].(map[string]any) + if !ok { + t.Fatalf("adapter runtime readiness missing from report = %+v", report) + } + if readiness["last_preflight_diagnostic_state"] != "stale_cursor_gap" || + readiness["last_preflight_stale_cursor"] != true || + readiness["last_preflight_missing_dropped_count"] != 2 || + readiness["last_preflight_recommended_action"] != "reset_consumer_and_resync" || + readiness["last_preflight_action_reason"] != "consumer_cursor_before_first_retained_sequence" || + readiness["last_preflight_operator_summary"] != "stale cursor gap: reset consumer and resync before resume" || + readiness["last_preflight_operator_status"] != "resync_required" || + readiness["last_preflight_operator_severity"] != "warn" || + mapInt64Value(readiness["mailbox_preflight_operator_status_counts"], "resync_required") != 2 || + mapInt64Value(readiness["mailbox_preflight_operator_severity_counts"], "warn") != 2 || + readiness["preflight_attention_status"] != "repeated_resync_required" || + readiness["preflight_attention_reason"] != "resync_required_preflight_repeated" { + t.Fatalf("stale preflight readiness = %+v", readiness) + } + lastPreflight, ok := readiness["last_preflight"].(map[string]any) + if !ok { + t.Fatalf("stale last preflight rollup missing from readiness = %+v", readiness) + } + if lastPreflight["diagnostic_state"] != "stale_cursor_gap" || + lastPreflight["diagnostics_schema_version"] != "rap.remote_workspace_adapter_mailbox_preflight_diagnostics.v1" || + !stringAnySliceContains(lastPreflight["diagnostics_contract"], "retained_window") || + !stringAnySliceContains(lastPreflight["diagnostics_contract"], "remediation_checklist") || + !stringAnySliceContains(lastPreflight["diagnostics_contract"], "attention") || + !stringAnySliceContains(lastPreflight["diagnostics_contract"], "operator_counts") || + !boolMapValue(lastPreflight["diagnostics_features"], "retained_window") || + !boolMapValue(lastPreflight["diagnostics_features"], "remediation_checklist") || + !boolMapValue(lastPreflight["diagnostics_features"], "attention") || + !boolMapValue(lastPreflight["diagnostics_features"], "operator_counts") || + lastPreflight["operator_status"] != "resync_required" || + lastPreflight["operator_severity"] != "warn" || + lastPreflight["recommended_action"] != "reset_consumer_and_resync" || + lastPreflight["action_reason"] != "consumer_cursor_before_first_retained_sequence" || + !preflightChecklistContains(lastPreflight["remediation_checklist"], "reset_consumer_cursor", true, false) || + !preflightChecklistContains(lastPreflight["remediation_checklist"], "request_full_adapter_resync", true, false) || + !preflightChecklistContains(lastPreflight["remediation_checklist"], "resume_from_checkpoint_after_resync", true, false) || + lastPreflight["remediation_checklist_status"] != "action_required" || + anyInt64(preflightChecklistCountsValue(lastPreflight["remediation_checklist_counts"], "required_count")) != 3 || + anyInt64(preflightChecklistCountsValue(lastPreflight["remediation_checklist_counts"], "satisfied_count")) != 0 || + anyInt64(preflightChecklistCountsValue(lastPreflight["remediation_checklist_counts"], "pending_count")) != 3 || + mapInt64Value(lastPreflight["operator_status_counts"], "resync_required") != 2 || + mapInt64Value(lastPreflight["operator_severity_counts"], "warn") != 2 || + lastPreflight["preflight_attention_status"] != "repeated_resync_required" || + lastPreflight["preflight_attention_reason"] != "resync_required_preflight_repeated" || + anyInt64(lastPreflight["missing_dropped_count"]) != 2 || + anyInt64(lastPreflight["first_retained_sequence"]) != 4 || + anyInt64(lastPreflight["last_retained_sequence"]) != 19 || + anyInt64(lastPreflight["mailbox_dropped_total"]) != 3 || + anyInt64(lastPreflight["resume_sequence"]) != 1 { + t.Fatalf("invalid stale last preflight rollup = %+v", lastPreflight) + } +} + +func preflightChecklistCountsValue(value any, key string) any { + switch counts := value.(type) { + case map[string]any: + return counts[key] + default: + return nil + } +} + +func mapInt64Value(value any, key string) int64 { + switch items := value.(type) { + case map[string]int64: + return items[key] + case map[string]any: + return anyInt64(items[key]) + default: + return 0 + } +} + +func boolMapValue(value any, key string) bool { + switch items := value.(type) { + case map[string]bool: + return items[key] + case map[string]any: + item, _ := items[key].(bool) + return item + default: + return false + } +} + +func preflightDiagnosticsContractCompatible(rollup map[string]any) bool { + for _, feature := range []string{"retained_window", "remediation_checklist", "attention", "operator_counts"} { + if !stringAnySliceContains(rollup["diagnostics_contract"], feature) || !boolMapValue(rollup["diagnostics_features"], feature) { + return false + } + } + return true +} + +func terminalSessionSummaryContractCompatible(summary map[string]any) bool { + for _, feature := range []string{"adapter_session_id", "session_state", "reason", "controlled_at"} { + if !stringAnySliceContains(summary["summary_contract"], feature) || !boolMapValue(summary["summary_features"], feature) { + return false + } + } + return true +} + +func noSessionSummaryContractCompatible(summary map[string]any) bool { + for _, feature := range []string{"status", "diagnostic_state", "active_session_count", "terminal_session_count"} { + if !stringAnySliceContains(summary["summary_contract"], feature) || !boolMapValue(summary["summary_features"], feature) { + return false + } + } + return true +} + +func stringAnySliceContains(value any, want string) bool { + switch items := value.(type) { + case []string: + for _, item := range items { + if item == want { + return true + } + } + case []any: + for _, item := range items { + if item == want { + return true + } + } + } + return false +} + +func preflightChecklistContains(value any, step string, required bool, satisfied bool) bool { + switch items := value.(type) { + case []map[string]any: + for _, item := range items { + if item["step"] == step && item["required"] == required && item["satisfied"] == satisfied && item["source_hint"] == true { + return true + } + } + case []any: + for _, raw := range items { + item, ok := raw.(map[string]any) + if !ok { + continue + } + if item["step"] == step && item["required"] == required && item["satisfied"] == satisfied && item["source_hint"] == true { + return true + } + } + } + return false +} + +func stringSliceContains(items []string, want string) bool { + for _, item := range items { + if item == want { + return true + } + } + return false +} + +func anyInt64(value any) int64 { + switch v := value.(type) { + case int: + return int64(v) + case int64: + return v + case float64: + return int64(v) + default: + return 0 + } +} + +func TestRemoteWorkspacePreflightDiagnosticsContractCompatibility(t *testing.T) { + compatible := map[string]any{ + "diagnostics_contract": []string{"retained_window", "remediation_checklist", "attention", "operator_counts"}, + "diagnostics_features": map[string]bool{"retained_window": true, "remediation_checklist": true, "attention": true, "operator_counts": true}, + } + if !preflightDiagnosticsContractCompatible(compatible) { + t.Fatalf("expected contract/features to be compatible") + } + + tests := []struct { + name string + rollup map[string]any + }{ + { + name: "missing contract item", + rollup: map[string]any{ + "diagnostics_contract": []string{"retained_window", "remediation_checklist", "attention"}, + "diagnostics_features": map[string]bool{"retained_window": true, "remediation_checklist": true, "attention": true, "operator_counts": true}, + }, + }, + { + name: "missing feature flag", + rollup: map[string]any{ + "diagnostics_contract": []string{"retained_window", "remediation_checklist", "attention", "operator_counts"}, + "diagnostics_features": map[string]bool{"retained_window": true, "remediation_checklist": true, "attention": true}, + }, + }, + { + name: "false feature flag", + rollup: map[string]any{ + "diagnostics_contract": []string{"retained_window", "remediation_checklist", "attention", "operator_counts"}, + "diagnostics_features": map[string]bool{"retained_window": true, "remediation_checklist": true, "attention": true, "operator_counts": false}, + }, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if preflightDiagnosticsContractCompatible(tt.rollup) { + t.Fatalf("expected incompatible contract/features for %+v", tt.rollup) + } + }) + } +} + +func TestRemoteWorkspaceTerminalSessionSummaryContractCompatibility(t *testing.T) { + compatible := map[string]any{ + "summary_contract": []string{"adapter_session_id", "session_state", "reason", "controlled_at"}, + "summary_features": map[string]bool{"adapter_session_id": true, "session_state": true, "reason": true, "controlled_at": true}, + } + if !terminalSessionSummaryContractCompatible(compatible) { + t.Fatalf("expected summary contract/features to be compatible") + } + + tests := []struct { + name string + summary map[string]any + }{ + { + name: "missing contract item", + summary: map[string]any{ + "summary_contract": []string{"adapter_session_id", "session_state", "reason"}, + "summary_features": map[string]bool{"adapter_session_id": true, "session_state": true, "reason": true, "controlled_at": true}, + }, + }, + { + name: "missing feature flag", + summary: map[string]any{ + "summary_contract": []string{"adapter_session_id", "session_state", "reason", "controlled_at"}, + "summary_features": map[string]bool{"adapter_session_id": true, "session_state": true, "reason": true}, + }, + }, + { + name: "false feature flag", + summary: map[string]any{ + "summary_contract": []string{"adapter_session_id", "session_state", "reason", "controlled_at"}, + "summary_features": map[string]bool{"adapter_session_id": true, "session_state": true, "reason": true, "controlled_at": false}, + }, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if terminalSessionSummaryContractCompatible(tt.summary) { + t.Fatalf("expected incompatible summary contract/features for %+v", tt.summary) + } + }) + } +} + +func TestRemoteWorkspaceNoSessionSummaryContractCompatibility(t *testing.T) { + compatible := map[string]any{ + "summary_contract": []string{"status", "diagnostic_state", "active_session_count", "terminal_session_count"}, + "summary_features": map[string]bool{"status": true, "diagnostic_state": true, "active_session_count": true, "terminal_session_count": true}, + } + if !noSessionSummaryContractCompatible(compatible) { + t.Fatalf("expected no-session summary contract/features to be compatible") + } + + tests := []struct { + name string + summary map[string]any + }{ + { + name: "missing contract item", + summary: map[string]any{ + "summary_contract": []string{"status", "diagnostic_state", "active_session_count"}, + "summary_features": map[string]bool{"status": true, "diagnostic_state": true, "active_session_count": true, "terminal_session_count": true}, + }, + }, + { + name: "missing feature flag", + summary: map[string]any{ + "summary_contract": []string{"status", "diagnostic_state", "active_session_count", "terminal_session_count"}, + "summary_features": map[string]bool{"status": true, "diagnostic_state": true, "active_session_count": true}, + }, + }, + { + name: "false feature flag", + summary: map[string]any{ + "summary_contract": []string{"status", "diagnostic_state", "active_session_count", "terminal_session_count"}, + "summary_features": map[string]bool{"status": true, "diagnostic_state": true, "active_session_count": true, "terminal_session_count": false}, + }, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if noSessionSummaryContractCompatible(tt.summary) { + t.Fatalf("expected incompatible no-session summary contract/features for %+v", tt.summary) + } + }) + } } func TestRemoteWorkspaceAdapterSessionMailboxPreflightRejectsInvalidRequests(t *testing.T) { @@ -3145,6 +3986,57 @@ func TestRemoteWorkspaceAdapterSessionMailboxPreflightRejectsInvalidRequests(t * } } +func TestRemoteWorkspacePreflightAttentionReasonSummaries(t *testing.T) { + tests := []struct { + name string + statusCounts map[string]int64 + severityCounts map[string]int64 + wantStatus string + wantReason string + }{ + { + name: "clean ready", + statusCounts: map[string]int64{"ready_to_resume": 1}, + severityCounts: map[string]int64{"ok": 1}, + wantStatus: "clean", + wantReason: "no_resync_required_preflight_observed", + }, + { + name: "single resync", + statusCounts: map[string]int64{"resync_required": 1}, + severityCounts: map[string]int64{"warn": 1}, + wantStatus: "needs_attention", + wantReason: "resync_required_preflight_observed", + }, + { + name: "repeated resync", + statusCounts: map[string]int64{"resync_required": 2}, + severityCounts: map[string]int64{"warn": 2}, + wantStatus: "repeated_resync_required", + wantReason: "resync_required_preflight_repeated", + }, + { + name: "none observed", + statusCounts: map[string]int64{}, + severityCounts: map[string]int64{}, + wantStatus: "unknown", + wantReason: "no_preflight_observed", + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + status := remoteWorkspacePreflightAttentionStatus(tt.statusCounts, tt.severityCounts) + if status != tt.wantStatus { + t.Fatalf("status=%q want %q", status, tt.wantStatus) + } + reason := remoteWorkspacePreflightAttentionReason(status, tt.statusCounts, tt.severityCounts) + if reason != tt.wantReason { + t.Fatalf("reason=%q want %q", reason, tt.wantReason) + } + }) + } +} + func TestFabricServiceChannelVPNPacketIngressHonorsDisabledBackendRelayPolicy(t *testing.T) { publicKey, privateKey, err := ed25519.GenerateKey(nil) if err != nil { diff --git a/agents/rap-node-agent/internal/supervisor/supervisor.go b/agents/rap-node-agent/internal/supervisor/supervisor.go index b553641..8c91628 100644 --- a/agents/rap-node-agent/internal/supervisor/supervisor.go +++ b/agents/rap-node-agent/internal/supervisor/supervisor.go @@ -13,7 +13,15 @@ type Supervisor interface { } type StubSupervisor struct { - Version string + Version string + RemoteWorkspaceRealAdapter RemoteWorkspaceRealAdapterConfig +} + +type RemoteWorkspaceRealAdapterConfig struct { + EnabledRequested bool + Command string + ArgsJSON string + WorkDir string } func (s StubSupervisor) Apply(_ context.Context, desired []client.DesiredWorkload) ([]client.WorkloadStatusRequest, error) { @@ -85,6 +93,7 @@ func (s StubSupervisor) applyOne(workload client.DesiredWorkload) client.Workloa payload["backend_relay_steady_state"] = false payload["channels"] = remoteWorkspaceAdapterChannels() payload["frame_batch_contract"] = remoteWorkspaceFrameBatchContract() + payload["real_adapter_supervision"] = remoteWorkspaceRealAdapterSupervisionContract(s.RemoteWorkspaceRealAdapter) payload["traffic"] = "none" return client.WorkloadStatusRequest{ ReportedState: "running", @@ -93,6 +102,20 @@ func (s StubSupervisor) applyOne(workload client.DesiredWorkload) client.Workloa StatusPayload: payload, } } + if serviceType == "rdp-worker" && runtimeMode == "native" && boolConfig(workload.Config, "real_adapter_supervision") { + payload["reason"] = "remote_workspace_real_adapter_supervision_disabled" + payload["execution_mode"] = "real_adapter_supervision_disabled" + payload["service_class"] = "remote_workspace" + payload["traffic"] = "blocked" + payload["payload_traffic"] = "none" + payload["real_adapter_supervision"] = remoteWorkspaceRealAdapterSupervisionContract(s.RemoteWorkspaceRealAdapter) + return client.WorkloadStatusRequest{ + ReportedState: "degraded", + RuntimeMode: runtimeMode, + Version: version, + StatusPayload: payload, + } + } payload["reason"] = "service_runtime_not_implemented" payload["traffic"] = "blocked" return client.WorkloadStatusRequest{ @@ -152,6 +175,166 @@ func remoteWorkspaceFrameBatchContract() map[string]any { } } +func remoteWorkspaceRealAdapterSupervisionContract(configs ...RemoteWorkspaceRealAdapterConfig) map[string]any { + var config RemoteWorkspaceRealAdapterConfig + if len(configs) > 0 { + config = configs[0] + } + return map[string]any{ + "schema_version": "rap.remote_workspace_real_adapter_supervision.v1", + "enabled": false, + "activation_state": "disabled_until_real_runtime_stage", + "execution_mode": "real_adapter_supervision_disabled", + "payload_traffic": "none", + "process_model": "external_rdp_worker_process", + "config_projection": remoteWorkspaceRealAdapterConfigProjection(config), + "activation_decision": remoteWorkspaceRealAdapterActivationDecision(config), + "process_supervisor_preconditions": remoteWorkspaceRealAdapterProcessSupervisorPreconditions(config), + "process_health_probe": remoteWorkspaceRealAdapterProcessHealthProbe(), + "features": map[string]any{ + "config_projection": true, + "activation_decision": true, + "missing_gates": true, + "process_health_probe": true, + "process_health_probe_disabled": true, + "process_supervisor_preconditions": true, + "process_supervisor_start_disabled": true, + "raw_values_redacted": true, + }, + "config_env": []string{ + "RAP_REMOTE_WORKSPACE_REAL_ADAPTER_ENABLED", + "RAP_REMOTE_WORKSPACE_REAL_ADAPTER_COMMAND", + "RAP_REMOTE_WORKSPACE_REAL_ADAPTER_ARGS_JSON", + "RAP_REMOTE_WORKSPACE_REAL_ADAPTER_WORKDIR", + }, + "status_contract": []string{ + "schema_version", + "enabled", + "activation_state", + "execution_mode", + "payload_traffic", + "process_model", + "config_projection", + "activation_decision", + "process_supervisor_preconditions", + "process_health_probe", + "features", + "config_env", + "status_contract", + }, + "guardrails": []string{ + "contract_probe_remains_default", + "no_payload_forwarding_until_real_runtime_stage", + "backend_relay_not_steady_state", + "fabric_service_channel_required", + }, + } +} + +func remoteWorkspaceRealAdapterProcessHealthProbe() map[string]any { + return map[string]any{ + "schema_version": "rap.remote_workspace_real_adapter_process_health_probe.v1", + "health_probe_enabled": false, + "reason": "disabled_until_real_runtime_stage", + "payload_traffic": "none", + "probe_model": "external_process_health", + "required_signals": []string{ + "process_started", + "process_exit_status", + "adapter_control_channel_ready", + "fabric_service_channel_bound", + "payload_forwarding_contract_ready", + }, + "missing_signals": []string{ + "process_started", + "process_exit_status", + "adapter_control_channel_ready", + "fabric_service_channel_bound", + "payload_forwarding_contract_ready", + }, + } +} + +func remoteWorkspaceRealAdapterProcessSupervisorPreconditions(config RemoteWorkspaceRealAdapterConfig) map[string]any { + return map[string]any{ + "schema_version": "rap.remote_workspace_real_adapter_process_supervisor_preconditions.v1", + "process_start_allowed": false, + "reason": "disabled_until_real_runtime_stage", + "command_config_present": strings.TrimSpace(config.Command) != "", + "workdir_config_present": strings.TrimSpace(config.WorkDir) != "", + "args_config_present": strings.TrimSpace(config.ArgsJSON) != "", + "required_checks": []string{ + "real_runtime_stage_enabled", + "command_config_validated", + "workdir_config_validated", + "process_identity_policy_bound", + "fabric_service_channel_runtime_ready", + "payload_forwarding_contract_enabled", + "health_probe_contract_enabled", + }, + "missing_checks": []string{ + "real_runtime_stage_enabled", + "command_config_validated", + "workdir_config_validated", + "process_identity_policy_bound", + "fabric_service_channel_runtime_ready", + "payload_forwarding_contract_enabled", + "health_probe_contract_enabled", + }, + } +} + +func remoteWorkspaceRealAdapterActivationDecision(config RemoteWorkspaceRealAdapterConfig) map[string]any { + return map[string]any{ + "schema_version": "rap.remote_workspace_real_adapter_activation_decision.v1", + "decision": "blocked", + "reason": "real_runtime_stage_not_enabled", + "enabled_requested": config.EnabledRequested, + "activation_allowed": false, + "payload_traffic": "none", + "required_gates": []string{ + "real_runtime_stage_enabled", + "fabric_service_channel_runtime_ready", + "adapter_process_supervisor_enabled", + "payload_forwarding_contract_enabled", + }, + "missing_gates": []string{ + "real_runtime_stage_enabled", + "fabric_service_channel_runtime_ready", + "adapter_process_supervisor_enabled", + "payload_forwarding_contract_enabled", + }, + } +} + +func remoteWorkspaceRealAdapterConfigProjection(config RemoteWorkspaceRealAdapterConfig) map[string]any { + return map[string]any{ + "schema_version": "rap.remote_workspace_real_adapter_config_projection.v1", + "enabled_requested": config.EnabledRequested, + "activation_allowed": false, + "command_present": strings.TrimSpace(config.Command) != "", + "args_json_present": strings.TrimSpace(config.ArgsJSON) != "", + "args_json_shape": remoteWorkspaceArgsJSONShape(config.ArgsJSON), + "workdir_present": strings.TrimSpace(config.WorkDir) != "", + "raw_values_redacted": true, + } +} + +func remoteWorkspaceArgsJSONShape(value string) string { + trimmed := strings.TrimSpace(value) + if trimmed == "" { + return "absent" + } + switch { + case strings.HasPrefix(trimmed, "["): + return "json_array" + case strings.HasPrefix(trimmed, "{"): + return "json_object" + default: + return "opaque" + } +} + func serviceTrafficMode(serviceType string) string { switch serviceType { case "core-mesh": diff --git a/agents/rap-node-agent/internal/supervisor/supervisor_test.go b/agents/rap-node-agent/internal/supervisor/supervisor_test.go index ffaf144..78b7d23 100644 --- a/agents/rap-node-agent/internal/supervisor/supervisor_test.go +++ b/agents/rap-node-agent/internal/supervisor/supervisor_test.go @@ -130,4 +130,469 @@ func TestStubSupervisorRunsRDPWorkerAdapterContractProbeOnly(t *testing.T) { frameBatch["service_class"] != "remote_workspace" { t.Fatalf("unexpected frame batch contract: %#v", frameBatch) } + realAdapter, ok := statuses[0].StatusPayload["real_adapter_supervision"].(map[string]any) + if !ok { + t.Fatalf("real_adapter_supervision = %#v", statuses[0].StatusPayload["real_adapter_supervision"]) + } + if realAdapter["schema_version"] != "rap.remote_workspace_real_adapter_supervision.v1" || + realAdapter["enabled"] != false || + realAdapter["activation_state"] != "disabled_until_real_runtime_stage" || + realAdapter["payload_traffic"] != "none" { + t.Fatalf("unexpected real adapter supervision contract: %#v", realAdapter) + } + if !realAdapterSupervisionContractCompatible(realAdapter) { + t.Fatalf("real adapter supervision contract is not compatible: %#v", realAdapter) + } +} + +func TestStubSupervisorKeepsContractProbePrecedenceWhenRealAdapterAlsoRequested(t *testing.T) { + statuses, err := (StubSupervisor{ + Version: "test", + RemoteWorkspaceRealAdapter: RemoteWorkspaceRealAdapterConfig{ + EnabledRequested: true, + Command: "/opt/rap/bin/rdp-worker", + ArgsJSON: `["--future-probe"]`, + WorkDir: "/var/lib/rap-node-agent/rdp-worker", + }, + }).Apply(context.Background(), []client.DesiredWorkload{ + { + ServiceType: "rdp-worker", + DesiredState: "enabled", + RuntimeMode: "native", + Config: map[string]any{ + "adapter_contract_probe": true, + "real_adapter_supervision": true, + }, + }, + }) + if err != nil { + t.Fatalf("apply desired workload: %v", err) + } + if statuses[0].ReportedState != "running" { + t.Fatalf("ReportedState = %q", statuses[0].ReportedState) + } + payload := statuses[0].StatusPayload + if payload["execution_mode"] != "contract_probe" || + payload["reason"] != "remote_workspace_adapter_contract_probe_ready" || + payload["traffic"] != "none" { + t.Fatalf("contract probe did not retain precedence: %#v", payload) + } + realAdapter, ok := payload["real_adapter_supervision"].(map[string]any) + if !ok || !realAdapterSupervisionContractCompatible(realAdapter) { + t.Fatalf("real_adapter_supervision = %#v", payload["real_adapter_supervision"]) + } + decision := realAdapter["activation_decision"].(map[string]any) + if realAdapter["enabled"] != false || + decision["decision"] != "blocked" || + decision["reason"] != "real_runtime_stage_not_enabled" || + decision["payload_traffic"] != "none" { + t.Fatalf("unexpected activation decision under contract-probe precedence: %#v", realAdapter) + } +} + +func TestStubSupervisorKeepsRealAdapterSupervisionDisabled(t *testing.T) { + statuses, err := (StubSupervisor{ + Version: "test", + RemoteWorkspaceRealAdapter: RemoteWorkspaceRealAdapterConfig{ + EnabledRequested: true, + Command: "/opt/rap/bin/rdp-worker", + ArgsJSON: `["--future-probe"]`, + WorkDir: "/var/lib/rap-node-agent/rdp-worker", + }, + }).Apply(context.Background(), []client.DesiredWorkload{ + { + ServiceType: "rdp-worker", + DesiredState: "enabled", + RuntimeMode: "native", + Config: map[string]any{ + "real_adapter_supervision": true, + }, + }, + }) + if err != nil { + t.Fatalf("apply desired workload: %v", err) + } + if statuses[0].ReportedState != "degraded" { + t.Fatalf("ReportedState = %q", statuses[0].ReportedState) + } + if statuses[0].StatusPayload["reason"] != "remote_workspace_real_adapter_supervision_disabled" || + statuses[0].StatusPayload["execution_mode"] != "real_adapter_supervision_disabled" || + statuses[0].StatusPayload["traffic"] != "blocked" || + statuses[0].StatusPayload["payload_traffic"] != "none" { + t.Fatalf("unexpected real adapter disabled payload: %#v", statuses[0].StatusPayload) + } + realAdapter, ok := statuses[0].StatusPayload["real_adapter_supervision"].(map[string]any) + if !ok || !realAdapterSupervisionContractCompatible(realAdapter) { + t.Fatalf("real adapter supervision contract = %#v", statuses[0].StatusPayload["real_adapter_supervision"]) + } + projection, ok := realAdapter["config_projection"].(map[string]any) + if !ok { + t.Fatalf("config_projection = %#v", realAdapter["config_projection"]) + } + if realAdapter["enabled"] != false || + projection["enabled_requested"] != true || + projection["activation_allowed"] != false || + projection["command_present"] != true || + projection["args_json_present"] != true || + projection["args_json_shape"] != "json_array" || + projection["workdir_present"] != true || + projection["raw_values_redacted"] != true { + t.Fatalf("unexpected config projection: %#v", projection) + } + decision, ok := realAdapter["activation_decision"].(map[string]any) + if !ok { + t.Fatalf("activation_decision = %#v", realAdapter["activation_decision"]) + } + if decision["decision"] != "blocked" || + decision["reason"] != "real_runtime_stage_not_enabled" || + decision["enabled_requested"] != true || + decision["activation_allowed"] != false || + decision["payload_traffic"] != "none" { + t.Fatalf("unexpected activation decision: %#v", decision) + } + features, ok := realAdapter["features"].(map[string]any) + if !ok || + features["config_projection"] != true || + features["activation_decision"] != true || + features["process_supervisor_preconditions"] != true || + features["process_supervisor_start_disabled"] != true || + features["missing_gates"] != true || + features["raw_values_redacted"] != true { + t.Fatalf("unexpected real adapter features: %#v", realAdapter["features"]) + } + preconditions, ok := realAdapter["process_supervisor_preconditions"].(map[string]any) + if !ok || + preconditions["schema_version"] != "rap.remote_workspace_real_adapter_process_supervisor_preconditions.v1" || + preconditions["process_start_allowed"] != false || + preconditions["command_config_present"] != true || + preconditions["args_config_present"] != true || + preconditions["workdir_config_present"] != true { + t.Fatalf("unexpected process supervisor preconditions: %#v", realAdapter["process_supervisor_preconditions"]) + } + healthProbe, ok := realAdapter["process_health_probe"].(map[string]any) + if !ok || + healthProbe["schema_version"] != "rap.remote_workspace_real_adapter_process_health_probe.v1" || + healthProbe["health_probe_enabled"] != false || + healthProbe["payload_traffic"] != "none" { + t.Fatalf("unexpected process health probe: %#v", realAdapter["process_health_probe"]) + } +} + +func TestRealAdapterSupervisionContractCompatibility(t *testing.T) { + compatible := remoteWorkspaceRealAdapterSupervisionContract() + if !realAdapterSupervisionContractCompatible(compatible) { + t.Fatalf("expected real adapter supervision contract to be compatible") + } + + tests := []struct { + name string + contract map[string]any + }{ + { + name: "enabled", + contract: map[string]any{ + "schema_version": "rap.remote_workspace_real_adapter_supervision.v1", + "enabled": true, + "activation_state": "disabled_until_real_runtime_stage", + "payload_traffic": "none", + "config_projection": map[string]any{"schema_version": "rap.remote_workspace_real_adapter_config_projection.v1", "activation_allowed": false, "raw_values_redacted": true}, + "activation_decision": map[string]any{"schema_version": "rap.remote_workspace_real_adapter_activation_decision.v1", "decision": "blocked", "reason": "real_runtime_stage_not_enabled", "activation_allowed": false, "payload_traffic": "none", "required_gates": []string{"real_runtime_stage_enabled", "fabric_service_channel_runtime_ready", "adapter_process_supervisor_enabled", "payload_forwarding_contract_enabled"}, "missing_gates": []string{"real_runtime_stage_enabled", "fabric_service_channel_runtime_ready", "adapter_process_supervisor_enabled", "payload_forwarding_contract_enabled"}}, + "process_supervisor_preconditions": map[string]any{"schema_version": "rap.remote_workspace_real_adapter_process_supervisor_preconditions.v1", "process_start_allowed": false, "reason": "disabled_until_real_runtime_stage", "required_checks": []string{"real_runtime_stage_enabled", "command_config_validated", "workdir_config_validated", "process_identity_policy_bound", "fabric_service_channel_runtime_ready", "payload_forwarding_contract_enabled", "health_probe_contract_enabled"}, "missing_checks": []string{"real_runtime_stage_enabled", "command_config_validated", "workdir_config_validated", "process_identity_policy_bound", "fabric_service_channel_runtime_ready", "payload_forwarding_contract_enabled", "health_probe_contract_enabled"}}, + "process_health_probe": map[string]any{"schema_version": "rap.remote_workspace_real_adapter_process_health_probe.v1", "health_probe_enabled": false, "reason": "disabled_until_real_runtime_stage", "payload_traffic": "none", "probe_model": "external_process_health", "required_signals": []string{"process_started", "process_exit_status", "adapter_control_channel_ready", "fabric_service_channel_bound", "payload_forwarding_contract_ready"}, "missing_signals": []string{"process_started", "process_exit_status", "adapter_control_channel_ready", "fabric_service_channel_bound", "payload_forwarding_contract_ready"}}, + "features": map[string]any{"config_projection": true, "activation_decision": true, "missing_gates": true, "process_health_probe": true, "process_health_probe_disabled": true, "process_supervisor_preconditions": true, "process_supervisor_start_disabled": true, "raw_values_redacted": true}, + "config_env": []string{"RAP_REMOTE_WORKSPACE_REAL_ADAPTER_ENABLED", "RAP_REMOTE_WORKSPACE_REAL_ADAPTER_COMMAND", "RAP_REMOTE_WORKSPACE_REAL_ADAPTER_ARGS_JSON", "RAP_REMOTE_WORKSPACE_REAL_ADAPTER_WORKDIR"}, + "status_contract": []string{"schema_version", "enabled", "activation_state", "execution_mode", "payload_traffic", "process_model", "config_projection", "activation_decision", "process_supervisor_preconditions", "process_health_probe", "features", "config_env", "status_contract"}, + "guardrails": []string{"contract_probe_remains_default", "no_payload_forwarding_until_real_runtime_stage", "backend_relay_not_steady_state", "fabric_service_channel_required"}, + }, + }, + { + name: "missing env", + contract: map[string]any{ + "schema_version": "rap.remote_workspace_real_adapter_supervision.v1", + "enabled": false, + "activation_state": "disabled_until_real_runtime_stage", + "payload_traffic": "none", + "config_projection": map[string]any{"schema_version": "rap.remote_workspace_real_adapter_config_projection.v1", "activation_allowed": false, "raw_values_redacted": true}, + "activation_decision": map[string]any{"schema_version": "rap.remote_workspace_real_adapter_activation_decision.v1", "decision": "blocked", "reason": "real_runtime_stage_not_enabled", "activation_allowed": false, "payload_traffic": "none", "required_gates": []string{"real_runtime_stage_enabled", "fabric_service_channel_runtime_ready", "adapter_process_supervisor_enabled", "payload_forwarding_contract_enabled"}, "missing_gates": []string{"real_runtime_stage_enabled", "fabric_service_channel_runtime_ready", "adapter_process_supervisor_enabled", "payload_forwarding_contract_enabled"}}, + "process_supervisor_preconditions": map[string]any{"schema_version": "rap.remote_workspace_real_adapter_process_supervisor_preconditions.v1", "process_start_allowed": false, "reason": "disabled_until_real_runtime_stage", "required_checks": []string{"real_runtime_stage_enabled", "command_config_validated", "workdir_config_validated", "process_identity_policy_bound", "fabric_service_channel_runtime_ready", "payload_forwarding_contract_enabled", "health_probe_contract_enabled"}, "missing_checks": []string{"real_runtime_stage_enabled", "command_config_validated", "workdir_config_validated", "process_identity_policy_bound", "fabric_service_channel_runtime_ready", "payload_forwarding_contract_enabled", "health_probe_contract_enabled"}}, + "process_health_probe": map[string]any{"schema_version": "rap.remote_workspace_real_adapter_process_health_probe.v1", "health_probe_enabled": false, "reason": "disabled_until_real_runtime_stage", "payload_traffic": "none", "probe_model": "external_process_health", "required_signals": []string{"process_started", "process_exit_status", "adapter_control_channel_ready", "fabric_service_channel_bound", "payload_forwarding_contract_ready"}, "missing_signals": []string{"process_started", "process_exit_status", "adapter_control_channel_ready", "fabric_service_channel_bound", "payload_forwarding_contract_ready"}}, + "features": map[string]any{"config_projection": true, "activation_decision": true, "missing_gates": true, "process_health_probe": true, "process_health_probe_disabled": true, "process_supervisor_preconditions": true, "process_supervisor_start_disabled": true, "raw_values_redacted": true}, + "config_env": []string{"RAP_REMOTE_WORKSPACE_REAL_ADAPTER_ENABLED"}, + "status_contract": []string{"schema_version", "enabled", "activation_state", "execution_mode", "payload_traffic", "process_model", "config_projection", "activation_decision", "process_supervisor_preconditions", "process_health_probe", "features", "config_env", "status_contract"}, + "guardrails": []string{"contract_probe_remains_default", "no_payload_forwarding_until_real_runtime_stage", "backend_relay_not_steady_state", "fabric_service_channel_required"}, + }, + }, + { + name: "missing guardrail", + contract: map[string]any{ + "schema_version": "rap.remote_workspace_real_adapter_supervision.v1", + "enabled": false, + "activation_state": "disabled_until_real_runtime_stage", + "payload_traffic": "none", + "config_projection": map[string]any{"schema_version": "rap.remote_workspace_real_adapter_config_projection.v1", "activation_allowed": false, "raw_values_redacted": true}, + "activation_decision": map[string]any{"schema_version": "rap.remote_workspace_real_adapter_activation_decision.v1", "decision": "blocked", "reason": "real_runtime_stage_not_enabled", "activation_allowed": false, "payload_traffic": "none", "required_gates": []string{"real_runtime_stage_enabled", "fabric_service_channel_runtime_ready", "adapter_process_supervisor_enabled", "payload_forwarding_contract_enabled"}, "missing_gates": []string{"real_runtime_stage_enabled", "fabric_service_channel_runtime_ready", "adapter_process_supervisor_enabled", "payload_forwarding_contract_enabled"}}, + "process_supervisor_preconditions": map[string]any{"schema_version": "rap.remote_workspace_real_adapter_process_supervisor_preconditions.v1", "process_start_allowed": false, "reason": "disabled_until_real_runtime_stage", "required_checks": []string{"real_runtime_stage_enabled", "command_config_validated", "workdir_config_validated", "process_identity_policy_bound", "fabric_service_channel_runtime_ready", "payload_forwarding_contract_enabled", "health_probe_contract_enabled"}, "missing_checks": []string{"real_runtime_stage_enabled", "command_config_validated", "workdir_config_validated", "process_identity_policy_bound", "fabric_service_channel_runtime_ready", "payload_forwarding_contract_enabled", "health_probe_contract_enabled"}}, + "process_health_probe": map[string]any{"schema_version": "rap.remote_workspace_real_adapter_process_health_probe.v1", "health_probe_enabled": false, "reason": "disabled_until_real_runtime_stage", "payload_traffic": "none", "probe_model": "external_process_health", "required_signals": []string{"process_started", "process_exit_status", "adapter_control_channel_ready", "fabric_service_channel_bound", "payload_forwarding_contract_ready"}, "missing_signals": []string{"process_started", "process_exit_status", "adapter_control_channel_ready", "fabric_service_channel_bound", "payload_forwarding_contract_ready"}}, + "features": map[string]any{"config_projection": true, "activation_decision": true, "missing_gates": true, "process_health_probe": true, "process_health_probe_disabled": true, "process_supervisor_preconditions": true, "process_supervisor_start_disabled": true, "raw_values_redacted": true}, + "config_env": []string{"RAP_REMOTE_WORKSPACE_REAL_ADAPTER_ENABLED", "RAP_REMOTE_WORKSPACE_REAL_ADAPTER_COMMAND", "RAP_REMOTE_WORKSPACE_REAL_ADAPTER_ARGS_JSON", "RAP_REMOTE_WORKSPACE_REAL_ADAPTER_WORKDIR"}, + "status_contract": []string{"schema_version", "enabled", "activation_state", "execution_mode", "payload_traffic", "process_model", "config_projection", "activation_decision", "process_supervisor_preconditions", "process_health_probe", "features", "config_env", "status_contract"}, + "guardrails": []string{"contract_probe_remains_default"}, + }, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if realAdapterSupervisionContractCompatible(tt.contract) { + t.Fatalf("expected incompatible contract for %+v", tt.contract) + } + }) + } +} + +func TestRealAdapterConfigProjectionCompatibility(t *testing.T) { + tests := []struct { + name string + config RemoteWorkspaceRealAdapterConfig + enabledRequested bool + commandPresent bool + argsJSONPresent bool + argsJSONShape string + workdirPresent bool + }{ + { + name: "default empty", + argsJSONShape: "absent", + }, + { + name: "requested array args", + config: RemoteWorkspaceRealAdapterConfig{ + EnabledRequested: true, + Command: "/opt/rap/bin/rdp-worker", + ArgsJSON: `["--future-probe"]`, + WorkDir: "/var/lib/rap-node-agent/rdp-worker", + }, + enabledRequested: true, + commandPresent: true, + argsJSONPresent: true, + argsJSONShape: "json_array", + workdirPresent: true, + }, + { + name: "object args shape", + config: RemoteWorkspaceRealAdapterConfig{ + ArgsJSON: `{"arg":"value"}`, + }, + argsJSONPresent: true, + argsJSONShape: "json_object", + }, + { + name: "opaque args shape", + config: RemoteWorkspaceRealAdapterConfig{ + ArgsJSON: "--future-probe", + }, + argsJSONPresent: true, + argsJSONShape: "opaque", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + contract := remoteWorkspaceRealAdapterSupervisionContract(tt.config) + if !realAdapterSupervisionContractCompatible(contract) { + t.Fatalf("contract is not compatible: %#v", contract) + } + projection := contract["config_projection"].(map[string]any) + if projection["enabled_requested"] != tt.enabledRequested || + projection["activation_allowed"] != false || + projection["command_present"] != tt.commandPresent || + projection["args_json_present"] != tt.argsJSONPresent || + projection["args_json_shape"] != tt.argsJSONShape || + projection["workdir_present"] != tt.workdirPresent || + projection["raw_values_redacted"] != true { + t.Fatalf("unexpected config projection: %#v", projection) + } + }) + } +} + +func TestRealAdapterProjectionAndActivationDecisionStayAligned(t *testing.T) { + tests := []struct { + name string + config RemoteWorkspaceRealAdapterConfig + enabledRequested bool + }{ + {name: "default"}, + { + name: "requested", + config: RemoteWorkspaceRealAdapterConfig{ + EnabledRequested: true, + Command: "/opt/rap/bin/rdp-worker", + ArgsJSON: `["--future-probe"]`, + WorkDir: "/var/lib/rap-node-agent/rdp-worker", + }, + enabledRequested: true, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + contract := remoteWorkspaceRealAdapterSupervisionContract(tt.config) + projection := contract["config_projection"].(map[string]any) + decision := contract["activation_decision"].(map[string]any) + if projection["enabled_requested"] != decision["enabled_requested"] || + projection["enabled_requested"] != tt.enabledRequested || + projection["activation_allowed"] != false || + decision["activation_allowed"] != false || + contract["enabled"] != false || + contract["payload_traffic"] != decision["payload_traffic"] { + t.Fatalf("projection and activation decision are not aligned: contract=%#v", contract) + } + }) + } +} + +func realAdapterSupervisionContractCompatible(contract map[string]any) bool { + if contract["schema_version"] != "rap.remote_workspace_real_adapter_supervision.v1" || + contract["enabled"] != false || + contract["activation_state"] != "disabled_until_real_runtime_stage" || + contract["payload_traffic"] != "none" { + return false + } + projection, ok := contract["config_projection"].(map[string]any) + if !ok || + projection["schema_version"] != "rap.remote_workspace_real_adapter_config_projection.v1" || + projection["activation_allowed"] != false || + projection["raw_values_redacted"] != true { + return false + } + decision, ok := contract["activation_decision"].(map[string]any) + if !ok || + decision["schema_version"] != "rap.remote_workspace_real_adapter_activation_decision.v1" || + decision["decision"] != "blocked" || + decision["reason"] != "real_runtime_stage_not_enabled" || + decision["activation_allowed"] != false || + decision["payload_traffic"] != "none" { + return false + } + for _, item := range []string{ + "real_runtime_stage_enabled", + "fabric_service_channel_runtime_ready", + "adapter_process_supervisor_enabled", + "payload_forwarding_contract_enabled", + } { + if !anyStringSliceContains(decision["required_gates"], item) || !anyStringSliceContains(decision["missing_gates"], item) { + return false + } + } + preconditions, ok := contract["process_supervisor_preconditions"].(map[string]any) + if !ok || + preconditions["schema_version"] != "rap.remote_workspace_real_adapter_process_supervisor_preconditions.v1" || + preconditions["process_start_allowed"] != false || + preconditions["reason"] != "disabled_until_real_runtime_stage" { + return false + } + for _, item := range []string{ + "real_runtime_stage_enabled", + "command_config_validated", + "workdir_config_validated", + "process_identity_policy_bound", + "fabric_service_channel_runtime_ready", + "payload_forwarding_contract_enabled", + "health_probe_contract_enabled", + } { + if !anyStringSliceContains(preconditions["required_checks"], item) || !anyStringSliceContains(preconditions["missing_checks"], item) { + return false + } + } + healthProbe, ok := contract["process_health_probe"].(map[string]any) + if !ok || + healthProbe["schema_version"] != "rap.remote_workspace_real_adapter_process_health_probe.v1" || + healthProbe["health_probe_enabled"] != false || + healthProbe["reason"] != "disabled_until_real_runtime_stage" || + healthProbe["payload_traffic"] != "none" || + healthProbe["probe_model"] != "external_process_health" { + return false + } + for _, item := range []string{ + "process_started", + "process_exit_status", + "adapter_control_channel_ready", + "fabric_service_channel_bound", + "payload_forwarding_contract_ready", + } { + if !anyStringSliceContains(healthProbe["required_signals"], item) || !anyStringSliceContains(healthProbe["missing_signals"], item) { + return false + } + } + features, ok := contract["features"].(map[string]any) + if !ok || + features["config_projection"] != true || + features["activation_decision"] != true || + features["missing_gates"] != true || + features["process_health_probe"] != true || + features["process_health_probe_disabled"] != true || + features["process_supervisor_preconditions"] != true || + features["process_supervisor_start_disabled"] != true || + features["raw_values_redacted"] != true { + return false + } + for _, item := range []string{ + "RAP_REMOTE_WORKSPACE_REAL_ADAPTER_ENABLED", + "RAP_REMOTE_WORKSPACE_REAL_ADAPTER_COMMAND", + "RAP_REMOTE_WORKSPACE_REAL_ADAPTER_ARGS_JSON", + "RAP_REMOTE_WORKSPACE_REAL_ADAPTER_WORKDIR", + } { + if !anyStringSliceContains(contract["config_env"], item) { + return false + } + } + for _, item := range []string{ + "schema_version", + "enabled", + "activation_state", + "execution_mode", + "payload_traffic", + "process_model", + "config_projection", + "activation_decision", + "process_supervisor_preconditions", + "process_health_probe", + "features", + "config_env", + "status_contract", + } { + if !anyStringSliceContains(contract["status_contract"], item) { + return false + } + } + for _, item := range []string{ + "contract_probe_remains_default", + "no_payload_forwarding_until_real_runtime_stage", + "backend_relay_not_steady_state", + "fabric_service_channel_required", + } { + if !anyStringSliceContains(contract["guardrails"], item) { + return false + } + } + return true +} + +func anyStringSliceContains(value any, want string) bool { + switch items := value.(type) { + case []string: + for _, item := range items { + if item == want { + return true + } + } + case []any: + for _, item := range items { + if item == want { + return true + } + } + } + return false } diff --git a/agents/rap-node-agent/internal/vpnruntime/gateway.go b/agents/rap-node-agent/internal/vpnruntime/gateway.go index 25c9138..7cdf536 100644 --- a/agents/rap-node-agent/internal/vpnruntime/gateway.go +++ b/agents/rap-node-agent/internal/vpnruntime/gateway.go @@ -184,6 +184,9 @@ func (g *Gateway) Snapshot() map[string]any { if !lastRuntimeActivityAt.IsZero() { out["last_runtime_activity_at"] = lastRuntimeActivityAt.UTC().Format(time.RFC3339Nano) } + if platform := gatewayPlatformSnapshot(g.InterfaceName, g.RouteCIDR); len(platform) > 0 { + out["platform"] = platform + } return out } diff --git a/agents/rap-node-agent/internal/vpnruntime/tun_linux.go b/agents/rap-node-agent/internal/vpnruntime/tun_linux.go index 36bb960..ccbfac1 100644 --- a/agents/rap-node-agent/internal/vpnruntime/tun_linux.go +++ b/agents/rap-node-agent/internal/vpnruntime/tun_linux.go @@ -19,6 +19,8 @@ const ( iffNoPI = 0x1000 tunSetIFF = 0x400454ca ifNameSize = 16 + gatewayTunMTU = "1000" + gatewayTCPMSS = "900" ) type tunDevice struct { @@ -86,6 +88,9 @@ func configureGatewayInterface(name, addressCIDR, routeCIDR string) error { if err := runCommand("ip", "addr", "replace", addressCIDR, "dev", name); err != nil { return err } + if err := runCommand("ip", "link", "set", "dev", name, "mtu", gatewayTunMTU); err != nil { + return err + } if err := runCommand("ip", "link", "set", name, "up"); err != nil { return err } @@ -118,11 +123,10 @@ func ensureMasqueradeRules(routeCIDR string) error { } func ensureMSSClampRule(interfaceName string) error { - err := ensureIPTablesRule("mangle", "FORWARD", "-i", interfaceName, "-p", "tcp", "--tcp-flags", "SYN,RST", "SYN", "-j", "TCPMSS", "--clamp-mss-to-pmtu") - if err == nil { - return nil + if err := ensureIPTablesRule("mangle", "FORWARD", "-i", interfaceName, "-p", "tcp", "--tcp-flags", "SYN,RST", "SYN", "-j", "TCPMSS", "--set-mss", gatewayTCPMSS); err != nil { + return err } - return nil + return ensureIPTablesRule("mangle", "FORWARD", "-o", interfaceName, "-p", "tcp", "--tcp-flags", "SYN,RST", "SYN", "-j", "TCPMSS", "--set-mss", gatewayTCPMSS) } func defaultIPv4Interface() (string, error) { @@ -204,3 +208,47 @@ func runCommand(name string, args ...string) error { } return nil } + +func gatewayPlatformSnapshot(interfaceName, routeCIDR string) map[string]any { + out := map[string]any{ + "os": "linux", + "interface": interfaceName, + "route_cidr": routeCIDR, + } + if value, err := readTrimmedFile("/proc/sys/net/ipv4/ip_forward"); err == nil { + out["ipv4_forward"] = value + } + for _, key := range []string{"all", "default", interfaceName} { + if strings.TrimSpace(key) == "" { + continue + } + if value, err := readTrimmedFile(fmt.Sprintf("/proc/sys/net/ipv4/conf/%s/rp_filter", key)); err == nil { + out["rp_filter_"+key] = value + } + } + if interfaceName != "" { + out["forward_in_rule"] = iptablesRulePresent("filter", "FORWARD", "-i", interfaceName, "-j", "ACCEPT") + out["forward_out_established_rule"] = iptablesRulePresent("filter", "FORWARD", "-o", interfaceName, "-m", "conntrack", "--ctstate", "RELATED,ESTABLISHED", "-j", "ACCEPT") + } + if routeCIDR != "" { + out["masquerade_rule"] = iptablesRulePresent("nat", "POSTROUTING", "-s", routeCIDR, "-j", "MASQUERADE") + if egress, err := defaultIPv4Interface(); err == nil && egress != "" { + out["default_egress"] = egress + out["egress_masquerade_rule"] = iptablesRulePresent("nat", "POSTROUTING", "-s", routeCIDR, "-o", egress, "-j", "MASQUERADE") + } + } + return out +} + +func readTrimmedFile(path string) (string, error) { + payload, err := os.ReadFile(path) + if err != nil { + return "", err + } + return strings.TrimSpace(string(payload)), nil +} + +func iptablesRulePresent(table, chain string, rule ...string) bool { + checkArgs := append([]string{"-t", table, "-C", chain}, rule...) + return exec.Command("iptables", checkArgs...).Run() == nil +} diff --git a/agents/rap-node-agent/internal/vpnruntime/tun_unsupported.go b/agents/rap-node-agent/internal/vpnruntime/tun_unsupported.go index ace6801..83178cf 100644 --- a/agents/rap-node-agent/internal/vpnruntime/tun_unsupported.go +++ b/agents/rap-node-agent/internal/vpnruntime/tun_unsupported.go @@ -21,3 +21,11 @@ func (d *tunDevice) Write(packet []byte) (int, error) { func (d *tunDevice) Close() error { return nil } + +func gatewayPlatformSnapshot(interfaceName, routeCIDR string) map[string]any { + return map[string]any{ + "os": "unsupported", + "interface": interfaceName, + "route_cidr": routeCIDR, + } +} diff --git a/backend/internal/modules/cluster/models.go b/backend/internal/modules/cluster/models.go index 1b5a739..22bbd08 100644 --- a/backend/internal/modules/cluster/models.go +++ b/backend/internal/modules/cluster/models.go @@ -2144,22 +2144,23 @@ type SetFabricEgressPoolNodeInput struct { } type IssueFabricServiceChannelLeaseInput struct { - ActorUserID string - ClusterID string - OrganizationID string - UserID string - ResourceID string - ServiceClass string - EntryNodeIDs []string - ExitNodeIDs []string - PreferredEntryNodeID string - PreferredExitNodeID string - RequiredRoles []string - AllowedChannels []string - QoS json.RawMessage - Failover json.RawMessage - Metadata json.RawMessage - TTL time.Duration + ActorUserID string + ClusterID string + OrganizationID string + UserID string + ResourceID string + ServiceClass string + EntryNodeIDs []string + ExitNodeIDs []string + PreferredEntryNodeID string + PreferredExitNodeID string + RequiredRoles []string + AllowedChannels []string + QoS json.RawMessage + Failover json.RawMessage + Metadata json.RawMessage + TTL time.Duration + BackendFallbackAllowed *bool } type UpdateFabricServiceChannelPoolPolicyInput struct { @@ -2531,6 +2532,14 @@ type RenewNodeVPNAssignmentLeaseInput struct { TTL time.Duration } +type AcquireNodeVPNAssignmentLeaseInput struct { + ClusterID string + VPNConnectionID string + OwnerNodeID string + TTL time.Duration + Metadata json.RawMessage +} + type ReleaseVPNConnectionLeaseInput struct { ActorUserID string ClusterID string diff --git a/backend/internal/modules/cluster/module.go b/backend/internal/modules/cluster/module.go index c511730..50d5e13 100644 --- a/backend/internal/modules/cluster/module.go +++ b/backend/internal/modules/cluster/module.go @@ -147,6 +147,7 @@ func (m *Module) RegisterRoutes(router chi.Router) { r.Post("/{clusterID}/vpn-connections/{vpnConnectionID}/leases/{leaseID}/release", m.releaseVPNConnectionLease) r.Post("/{clusterID}/vpn-connections/{vpnConnectionID}/leases/{leaseID}/fence", m.fenceVPNConnectionLease) r.Get("/{clusterID}/nodes/{nodeID}/vpn/assignments", m.listNodeVPNAssignments) + r.Post("/{clusterID}/nodes/{nodeID}/vpn/assignments/{vpnConnectionID}/lease/acquire", m.acquireNodeVPNAssignmentLease) r.Post("/{clusterID}/nodes/{nodeID}/vpn/assignments/{vpnConnectionID}/lease/{leaseID}/renew", m.renewNodeVPNAssignmentLease) r.Post("/{clusterID}/nodes/{nodeID}/vpn/assignments/{vpnConnectionID}/status", m.reportNodeVPNAssignmentStatus) r.Get("/{clusterID}/vpn-connections/{vpnConnectionID}/tunnel/stats", m.getVPNPacketStats) @@ -2072,6 +2073,35 @@ func (m *Module) listNodeVPNAssignments(w http.ResponseWriter, r *http.Request) httpx.WriteJSON(w, http.StatusOK, map[string]any{"vpn_assignments": items}) } +func (m *Module) acquireNodeVPNAssignmentLease(w http.ResponseWriter, r *http.Request) { + var payload struct { + TTLSeconds int `json:"ttl_seconds"` + Metadata json.RawMessage `json:"metadata"` + } + if err := json.NewDecoder(r.Body).Decode(&payload); err != nil { + httpx.WriteError(w, http.StatusBadRequest, "invalid vpn node lease acquire payload") + return + } + item, err := m.service.AcquireNodeVPNAssignmentLease(r.Context(), AcquireNodeVPNAssignmentLeaseInput{ + ClusterID: chi.URLParam(r, "clusterID"), + VPNConnectionID: chi.URLParam(r, "vpnConnectionID"), + OwnerNodeID: chi.URLParam(r, "nodeID"), + TTL: time.Duration(payload.TTLSeconds) * time.Second, + Metadata: payload.Metadata, + }) + if writeServiceError(w, err) { + return + } + httpx.WriteJSON(w, http.StatusCreated, map[string]any{"lease": NodeVPNAssignmentLease{ + LeaseID: item.ID, + OwnerNodeID: item.OwnerNodeID, + LeaseGeneration: item.LeaseGeneration, + Status: item.Status, + RenewedAt: item.RenewedAt, + ExpiresAt: item.ExpiresAt, + }}) +} + func (m *Module) renewNodeVPNAssignmentLease(w http.ResponseWriter, r *http.Request) { var payload struct { TTLSeconds int `json:"ttl_seconds"` diff --git a/backend/internal/modules/cluster/postgres_store.go b/backend/internal/modules/cluster/postgres_store.go index b4b7683..3cd63f8 100644 --- a/backend/internal/modules/cluster/postgres_store.go +++ b/backend/internal/modules/cluster/postgres_store.go @@ -4758,7 +4758,6 @@ func (s *PostgresStore) vpnEntryEndpointCandidates(ctx context.Context, clusterI } func vpnEntryEndpointCandidatesFromHeartbeat(nodeID string, capabilities json.RawMessage, metadata json.RawMessage) []map[string]any { - localGatewayShortcut := heartbeatCapabilityEnabled(capabilities, "vpn_local_gateway_shortcut") var payload struct { MeshEndpointReport struct { PeerEndpoint string `json:"peer_endpoint"` @@ -4823,9 +4822,6 @@ func vpnEntryEndpointCandidatesFromHeartbeat(nodeID string, capabilities json.Ra if apiBaseURL := vpnEntryAPIBaseURL(address); apiBaseURL != "" { item["api_base_url"] = apiBaseURL } - if localGatewayShortcut { - item["local_gateway_shortcut"] = true - } out = append(out, item) } if len(out) == 0 { @@ -4847,9 +4843,6 @@ func vpnEntryEndpointCandidatesFromHeartbeat(nodeID string, capabilities json.Ra if apiBaseURL := vpnEntryAPIBaseURL(address); apiBaseURL != "" { item["api_base_url"] = apiBaseURL } - if localGatewayShortcut { - item["local_gateway_shortcut"] = true - } out = append(out, item) } } @@ -5129,10 +5122,15 @@ func enrichVPNClientFabricRoute(item VPNClientConnection, preferredEntryNodeID, cfg["vpn_fabric_route"] = map[string]any{ "schema_version": "rap.vpn_fabric_route.v1", "status": status, - "preferred_data_plane": "fabric_mesh", - "fallback_data_plane": "backend_relay", - "backend_relay_fallback": true, - "selection_mode": "entry_to_fastest_exit", + "preferred_data_plane": "fabric_service_channel", + "fallback_data_plane": "none", + "backend_relay_fallback": false, + "selection_mode": "farm_authoritative_entry_to_exit", + "route_authority": "fabric_farm", + "vpn_builds_routes": false, + "vpn_builds_tunnels": false, + "farm_builds_routes": true, + "farm_builds_tunnels": true, "entry_pool_node_ids": entryPool, "exit_pool_node_ids": exitPool, "selected_entry_node_id": selectedEntry, @@ -5147,20 +5145,28 @@ func enrichVPNClientFabricRoute(item VPNClientConnection, preferredEntryNodeID, "tunnel_type": "universal_ip_packet", "application_protocol_agnostic": true, "packet_forwarding_channel": "vpn_packet", - "control_plane_packet_relay_mode": "lab_fallback_only", + "control_plane_packet_relay_mode": "fabric_service_channel_only", + "route_authority": "fabric_farm", + "backend_relay_allowed": false, + "requires_fabric_service_channel": true, + "vpn_builds_routes": false, + "vpn_builds_tunnels": false, + "farm_builds_routes": true, + "farm_builds_tunnels": true, "traffic_contract": map[string]any{ "all_ip_traffic": true, "protocol_specific_routing": false, "diagnostics_only_protocol_summaries": true, }, "route_selection": map[string]any{ - "mode": "lowest_latency_healthy_route", + "mode": "farm_authoritative_lowest_latency_healthy_route", "selected_entry_node_id": selectedEntry, "selected_exit_node_id": selectedExit, "route_candidates": routeCandidates, }, "failover": map[string]any{ "enabled": true, + "owner": "fabric_farm", "client_topology_hidden": true, "preserve_vpn_connection_id": true, "alternate_route_count": alternateVPNRouteCount(routeCandidates, selectedEntry, selectedExit), @@ -5178,8 +5184,8 @@ func enrichVPNClientFabricRoute(item VPNClientConnection, preferredEntryNodeID, "drop_policy": "drop_only_when_all_routes_unavailable_or_queue_full", "bulk_and_realtime": "same_packet_path", "flow_isolation": "hash_by_ip_protocol_and_ports", - "target_dataplane": "entry_node_to_exit_node_fabric", - "temporary_fallback": "backend_http_packet_relay", + "target_dataplane": "fabric_farm_entry_to_exit_service_channel", + "temporary_fallback": "none", }, } out, err := json.Marshal(cfg) diff --git a/backend/internal/modules/cluster/postgres_store_test.go b/backend/internal/modules/cluster/postgres_store_test.go index 6b789cc..23dc2ad 100644 --- a/backend/internal/modules/cluster/postgres_store_test.go +++ b/backend/internal/modules/cluster/postgres_store_test.go @@ -52,7 +52,7 @@ func TestEnrichVPNClientFabricRoutePrefersPlacementEntryAndActiveExit(t *testing if !ok { t.Fatalf("missing vpn_fabric_route in %#v", cfg) } - if route["preferred_data_plane"] != "fabric_mesh" || route["fallback_data_plane"] != "backend_relay" { + if route["preferred_data_plane"] != "fabric_service_channel" || route["fallback_data_plane"] != "none" || route["backend_relay_fallback"] != false { t.Fatalf("unexpected data-plane route contract: %#v", route) } if route["selected_entry_node_id"] != "entry-2" || route["selected_exit_node_id"] != "exit-active" { @@ -158,8 +158,8 @@ func TestEnrichVPNClientEntryEndpointCandidatesAddsReportedEntryAPI(t *testing.T if candidate["node_id"] != "entry-1" || candidate["api_base_url"] != "http://entry.example.test:19131/api/v1" { t.Fatalf("unexpected endpoint candidate: %#v", candidate) } - if candidate["local_gateway_shortcut"] != true { - t.Fatalf("local gateway shortcut missing: %#v", candidate) + if _, ok := candidate["local_gateway_shortcut"]; ok { + t.Fatalf("local gateway shortcut must not be advertised in farm-owned VPN mode: %#v", candidate) } if candidate["selected_entry"] != true || candidate["source"] != "node_latest_heartbeat.mesh_endpoint_report.endpoint_candidates" { t.Fatalf("unexpected endpoint metadata: %#v", candidate) diff --git a/backend/internal/modules/cluster/service.go b/backend/internal/modules/cluster/service.go index e33aed4..e07ee88 100644 --- a/backend/internal/modules/cluster/service.go +++ b/backend/internal/modules/cluster/service.go @@ -4015,8 +4015,8 @@ func (s *Service) IssueFabricServiceChannelLease(ctx context.Context, input Issu if ttl <= 0 { ttl = time.Minute } - if ttl > 5*time.Minute { - ttl = 5 * time.Minute + if ttl > 6*time.Hour { + ttl = 6 * time.Hour } now := s.now().UTC() expiresAt := now.Add(ttl) @@ -4031,6 +4031,9 @@ func (s *Service) IssueFabricServiceChannelLease(ctx context.Context, input Issu return FabricServiceChannelLease{}, err } poolPolicy := fabricServiceChannelPoolPolicyFromCluster(cluster) + if input.BackendFallbackAllowed != nil { + poolPolicy.BackendFallbackAllowed = *input.BackendFallbackAllowed + } entryNodeIDs := fabricServiceChannelEffectivePool(input.EntryNodeIDs, poolPolicy.EntryPoolNodeIDs) exitNodeIDs := fabricServiceChannelEffectivePool(input.ExitNodeIDs, poolPolicy.ExitPoolNodeIDs) if len(entryNodeIDs) == 0 || len(exitNodeIDs) == 0 { @@ -7303,7 +7306,9 @@ func (s *Service) GetNodeSyntheticMeshConfig(ctx context.Context, input GetNodeS if feedback, ok := serviceChannelFeedback[route.RouteID]; ok && feedback.Fenced { replacementDecision := s.serviceChannelRouteReplacementDecision(input, route, intents, serviceChannelFeedback, cfg.ConfigVersion) routePathDecisions = append(routePathDecisions, replacementDecision) - continue + if replacementDecision.DecisionSource != "service_channel_feedback_no_alternate_keep_primary" { + continue + } } reportedPeers, reportedCandidates, err := s.reportedEndpointConfig(ctx, input.ClusterID, input.NodeID, route.Hops, localPerspective) if err != nil { @@ -8700,6 +8705,98 @@ func (s *Service) RenewNodeVPNAssignmentLease(ctx context.Context, input RenewNo return item, nil } +func (s *Service) AcquireNodeVPNAssignmentLease(ctx context.Context, input AcquireNodeVPNAssignmentLeaseInput) (VPNConnectionLease, error) { + input.ClusterID = strings.TrimSpace(input.ClusterID) + input.VPNConnectionID = strings.TrimSpace(input.VPNConnectionID) + input.OwnerNodeID = strings.TrimSpace(input.OwnerNodeID) + if input.ClusterID == "" || input.VPNConnectionID == "" || input.OwnerNodeID == "" { + return VPNConnectionLease{}, ErrInvalidPayload + } + conn, err := s.store.GetVPNConnection(ctx, input.ClusterID, input.VPNConnectionID) + if errors.Is(err, pgx.ErrNoRows) { + return VPNConnectionLease{}, ErrInvalidVPNConnection + } + if err != nil { + return VPNConnectionLease{}, err + } + if conn.Mode != VPNConnectionModeSingleActive || conn.DesiredState != VPNConnectionDesiredEnabled { + return VPNConnectionLease{}, errors.New("vpn connection must be enabled single_active before lease acquisition") + } + if err := s.ensureVPNLeaseOwnerEligible(ctx, input.ClusterID, input.VPNConnectionID, input.OwnerNodeID); err != nil { + return VPNConnectionLease{}, err + } + assignments, err := s.store.ListNodeVPNAssignments(ctx, input.ClusterID, input.OwnerNodeID) + if err != nil { + return VPNConnectionLease{}, err + } + visibleCandidate := false + for _, assignment := range assignments { + if assignment.VPNConnectionID != input.VPNConnectionID { + continue + } + if assignment.DesiredState != "" && assignment.DesiredState != VPNConnectionDesiredEnabled { + return VPNConnectionLease{}, ErrVPNLeaseOwnerNotAllowed + } + if assignment.AssignmentReason == "active_owner" && + assignment.ActiveLease != nil && + assignment.ActiveLease.OwnerNodeID == input.OwnerNodeID { + return VPNConnectionLease{ + ID: assignment.ActiveLease.LeaseID, + VPNConnectionID: assignment.VPNConnectionID, + ClusterID: assignment.ClusterID, + OwnerNodeID: assignment.ActiveLease.OwnerNodeID, + LeaseGeneration: assignment.ActiveLease.LeaseGeneration, + Status: assignment.ActiveLease.Status, + RenewedAt: assignment.ActiveLease.RenewedAt, + ExpiresAt: assignment.ActiveLease.ExpiresAt, + }, nil + } + if assignment.AssignmentReason == "eligible_candidate" { + visibleCandidate = true + break + } + } + if !visibleCandidate { + return VPNConnectionLease{}, ErrVPNLeaseOwnerNotAllowed + } + if input.TTL <= 0 { + input.TTL = 2 * time.Minute + } + input.Metadata = defaultJSON(input.Metadata, `{}`) + if !json.Valid(input.Metadata) { + return VPNConnectionLease{}, errors.New("lease metadata must be valid json") + } + token, err := generateFencingToken() + if err != nil { + return VPNConnectionLease{}, err + } + item, err := s.store.AcquireVPNConnectionLease(ctx, AcquireVPNConnectionLeaseInput{ + ClusterID: input.ClusterID, + VPNConnectionID: input.VPNConnectionID, + OwnerNodeID: input.OwnerNodeID, + TTL: input.TTL, + Metadata: input.Metadata, + }, s.now().Add(input.TTL), token) + if errors.Is(err, pgx.ErrNoRows) { + return VPNConnectionLease{}, ErrInvalidVPNLease + } + if errors.Is(err, ErrVPNLeaseAlreadyActive) { + return VPNConnectionLease{}, ErrVPNLeaseAlreadyActive + } + if err != nil { + return VPNConnectionLease{}, err + } + _ = s.store.RecordAudit(ctx, ClusterAuditEvent{ + ClusterID: &input.ClusterID, + EventType: "vpn_connection.lease_acquired_by_node", + TargetType: "vpn_connection", + TargetID: &input.VPNConnectionID, + Payload: json.RawMessage(`{"node_agent_runtime_requested":true}`), + CreatedAt: s.now(), + }) + return item, nil +} + func (s *Service) ReleaseVPNConnectionLease(ctx context.Context, input ReleaseVPNConnectionLeaseInput) (VPNConnectionLease, error) { if err := s.ensurePlatformAdmin(ctx, input.ActorUserID); err != nil { return VPNConnectionLease{}, err @@ -8910,18 +9007,20 @@ func (s *Service) attachVPNFabricServiceChannelLeases(ctx context.Context, profi if len(exitPool) == 0 { exitPool = dedupeStrings(append([]string{route.SelectedExitNodeID, connection.ExitNodeID}, connection.AllowedNodeIDs...)) } + backendFallbackAllowed := false lease, err := s.IssueFabricServiceChannelLease(ctx, IssueFabricServiceChannelLeaseInput{ - ClusterID: profile.ClusterID, - OrganizationID: profile.OrganizationID, - UserID: profile.UserID, - ResourceID: connection.ID, - ServiceClass: FabricServiceClassVPNPackets, - EntryNodeIDs: entryPool, - ExitNodeIDs: exitPool, - PreferredEntryNodeID: route.SelectedEntryNodeID, - PreferredExitNodeID: route.SelectedExitNodeID, - AllowedChannels: []string{"vpn_packet", "fabric_control", FabricChannelBulk, FabricChannelControl}, - TTL: time.Minute, + ClusterID: profile.ClusterID, + OrganizationID: profile.OrganizationID, + UserID: profile.UserID, + ResourceID: connection.ID, + ServiceClass: FabricServiceClassVPNPackets, + EntryNodeIDs: entryPool, + ExitNodeIDs: exitPool, + PreferredEntryNodeID: route.SelectedEntryNodeID, + PreferredExitNodeID: route.SelectedExitNodeID, + AllowedChannels: []string{"vpn_packet", "fabric_control", FabricChannelBulk, FabricChannelControl}, + TTL: 6 * time.Hour, + BackendFallbackAllowed: &backendFallbackAllowed, }) if err != nil { profile.Connections[i].ClientConfig = attachVPNFabricServiceChannelError(connection.ClientConfig, err) @@ -8985,19 +9084,21 @@ func enrichVPNDataplaneSession(profile VPNClientProfile, connection VPNClientCon status = "ready_for_entry_listener" } cfg["vpn_dataplane_session"] = map[string]any{ - "schema_version": "rap.vpn_dataplane_session.v1", - "session_id": sessionID, - "status": status, - "issued_at": now, - "expires_at": expiresAt, - "cluster_id": profile.ClusterID, - "organization_id": profile.OrganizationID, - "user_id": profile.UserID, - "vpn_connection_id": connection.ID, - "entry_node_id": route.SelectedEntryNodeID, - "exit_node_id": route.SelectedExitNodeID, - "preferred_transport": "fabric_packet_quic_v1", - "fallback_transport": "backend_http_packet_relay", + "schema_version": "rap.vpn_dataplane_session.v1", + "session_id": sessionID, + "status": status, + "issued_at": now, + "expires_at": expiresAt, + "cluster_id": profile.ClusterID, + "organization_id": profile.OrganizationID, + "user_id": profile.UserID, + "vpn_connection_id": connection.ID, + "entry_node_id": route.SelectedEntryNodeID, + "exit_node_id": route.SelectedExitNodeID, + "preferred_transport": "fabric_service_channel_v1", + "fallback_transport": "none", + "route_authority": "fabric_farm", + "backend_relay_allowed": false, "packet_contract": map[string]any{ "tunnel_type": "universal_ip_packet", "application_protocol_agnostic": true, @@ -9089,10 +9190,12 @@ func vpnConcreteEntryCandidatesFromClientConfig(cfg map[string]any) []map[string func vpnDataplaneTransportCandidates(route vpnClientFabricRoute, entryCandidates []map[string]any) []map[string]any { candidates := []map[string]any{ { - "type": "fabric_packet_quic_v1", + "type": "fabric_service_channel_v1", "status": "contract_ready_listener_pending", "entry_node_id": route.SelectedEntryNodeID, "exit_node_id": route.SelectedExitNodeID, + "route_authority": "fabric_farm", + "backend_relay_allowed": false, "entry_candidates": entryCandidates, "application_protocols": []string{"ip"}, }, @@ -9100,11 +9203,6 @@ func vpnDataplaneTransportCandidates(route vpnClientFabricRoute, entryCandidates if direct := vpnDirectHTTPEntryTransportCandidate(route, entryCandidates); direct != nil { candidates = append(candidates, direct) } - candidates = append(candidates, map[string]any{ - "type": "backend_http_packet_relay", - "status": "active_fallback", - "description": "current safe dataplane until entry listener is available", - }) return candidates } @@ -9112,7 +9210,6 @@ func vpnDirectHTTPEntryTransportCandidate(route vpnClientFabricRoute, entryCandi var selected []map[string]any hasPublic := false hasHTTP := false - hasLocalGatewayShortcut := false for _, candidate := range entryCandidates { nodeID, _ := candidate["node_id"].(string) if route.SelectedEntryNodeID != "" && nodeID != route.SelectedEntryNodeID { @@ -9132,9 +9229,6 @@ func vpnDirectHTTPEntryTransportCandidate(route vpnClientFabricRoute, entryCandi if strings.EqualFold(reachability, "public") { hasPublic = true } - if value, ok := candidate["local_gateway_shortcut"].(bool); ok && value { - hasLocalGatewayShortcut = true - } selected = append(selected, candidate) } if len(selected) == 0 { @@ -9148,13 +9242,8 @@ func vpnDirectHTTPEntryTransportCandidate(route vpnClientFabricRoute, entryCandi } safeClientSwitch := hasPublic if route.SelectedEntryNodeID != "" && route.SelectedEntryNodeID == route.SelectedExitNodeID { - if hasPublic && hasLocalGatewayShortcut { - status = "available_local_gateway_shortcut" - safeClientSwitch = true - } else { - status = "available_local_gateway_shortcut_pending" - safeClientSwitch = false - } + status = "available_farm_local_route" + safeClientSwitch = hasPublic } return map[string]any{ "type": "entry_direct_http_v1", @@ -9275,9 +9364,13 @@ func vpnFabricRouteIntentPolicy(sourceNodeID, destinationNodeID string, expiresA "route_version": version, "policy_version": version, "peer_directory_version": version, - "backend_relay_fallback": true, - "data_plane_preference": "fabric_mesh", - "route_owner": "vpn_client_profile", + "backend_relay_fallback": false, + "data_plane_preference": "fabric_service_channel", + "route_owner": "fabric_farm", + "vpn_builds_routes": false, + "vpn_builds_tunnels": false, + "farm_builds_routes": true, + "farm_builds_tunnels": true, "route_refresh_required": true, "route_refresh_threshold": "24h", } @@ -11387,11 +11480,11 @@ func (s *Service) serviceChannelRouteReplacementDecision(input GetNodeSyntheticM SourceNodeID: fencedRoute.SourceNodeID, DestinationNodeID: fencedRoute.DestinationNodeID, OriginalHops: append([]string{}, fencedRoute.Hops...), - EffectiveHops: []string{}, - DecisionSource: "service_channel_feedback_no_alternate", + EffectiveHops: append([]string{}, fencedRoute.Hops...), + DecisionSource: "service_channel_feedback_no_alternate_keep_primary", Generation: generation, - PathScore: 0, - ScoreReasons: []string{"service_channel_fenced_route", "no_unfenced_alternate_route"}, + PathScore: serviceChannelReplacementRouteScore(fencedRoute), + ScoreReasons: []string{"service_channel_fenced_route", "no_unfenced_alternate_route", "primary_route_retained_until_rebuild"}, ControlPlaneOnly: true, ProductionForwarding: false, ExpiresAt: fencedRoute.ExpiresAt.UTC(), @@ -11399,10 +11492,10 @@ func (s *Service) serviceChannelRouteReplacementDecision(input GetNodeSyntheticM applyServiceChannelFeedbackCorrelationToDecision(&decision, routeFeedback) if serviceChannelFeedbackRequestsRebuild(routeFeedback) { decision.RebuildRequestID = serviceChannelRebuildRequestID(fencedRoute.RouteID, input.NodeID, generation) - decision.RebuildStatus = "pending_degraded_fallback" + decision.RebuildStatus = "requested" decision.RebuildReason = "service_channel_feedback_rebuild_requested" decision.RebuildAttempt = routeFeedback.ConsecutiveFailures - decision.ScoreReasons = append(decision.ScoreReasons, "service_channel_rebuild_requested", "backend_relay_degraded_fallback_until_rebuild") + decision.ScoreReasons = append(decision.ScoreReasons, "service_channel_rebuild_requested") if routeFeedback.DegradedFallbackRecommended { decision.ScoreReasons = append(decision.ScoreReasons, "service_channel_degraded_fallback_recommended") } diff --git a/backend/internal/modules/cluster/service_test.go b/backend/internal/modules/cluster/service_test.go index 9d0498a..5343296 100644 --- a/backend/internal/modules/cluster/service_test.go +++ b/backend/internal/modules/cluster/service_test.go @@ -732,7 +732,7 @@ func TestGetVPNClientProfileEnsuresFabricVPNPacketRouteIntents(t *testing.T) { if !ok { t.Fatalf("missing vpn_dataplane_session in %#v", cfg) } - if session["preferred_transport"] != "fabric_packet_quic_v1" || session["fallback_transport"] != "backend_http_packet_relay" { + if session["preferred_transport"] != "fabric_service_channel_v1" || session["fallback_transport"] != "none" || session["backend_relay_allowed"] != false { t.Fatalf("unexpected dataplane session transports: %#v", session) } if session["entry_node_id"] != "entry-1" || session["exit_node_id"] != "exit-1" { @@ -811,7 +811,7 @@ func TestGetVPNClientProfileForwardsPreferredExit(t *testing.T) { } } -func TestVPNDirectHTTPEntryTransportWaitsForLocalGatewayShortcutWhenEntryIsExit(t *testing.T) { +func TestVPNDirectHTTPEntryTransportUsesFarmLocalRouteWhenEntryIsExit(t *testing.T) { candidate := vpnDirectHTTPEntryTransportCandidate(vpnClientFabricRoute{ SelectedEntryNodeID: "node-1", SelectedExitNodeID: "node-1", @@ -823,12 +823,12 @@ func TestVPNDirectHTTPEntryTransportWaitsForLocalGatewayShortcutWhenEntryIsExit( if candidate == nil { t.Fatal("candidate is nil") } - if candidate["safe_client_switch"] != false || candidate["status"] != "available_local_gateway_shortcut_pending" { - t.Fatalf("unexpected local shortcut guard: %#v", candidate) + if candidate["safe_client_switch"] != true || candidate["status"] != "available_farm_local_route" { + t.Fatalf("unexpected farm local route guard: %#v", candidate) } } -func TestVPNDirectHTTPEntryTransportAllowsLocalGatewayShortcutWhenReported(t *testing.T) { +func TestVPNDirectHTTPEntryTransportIgnoresLegacyLocalGatewayShortcut(t *testing.T) { candidate := vpnDirectHTTPEntryTransportCandidate(vpnClientFabricRoute{ SelectedEntryNodeID: "node-1", SelectedExitNodeID: "node-1", @@ -841,8 +841,8 @@ func TestVPNDirectHTTPEntryTransportAllowsLocalGatewayShortcutWhenReported(t *te if candidate == nil { t.Fatal("candidate is nil") } - if candidate["safe_client_switch"] != true || candidate["status"] != "available_local_gateway_shortcut" { - t.Fatalf("unexpected local shortcut candidate: %#v", candidate) + if candidate["safe_client_switch"] != true || candidate["status"] != "available_farm_local_route" { + t.Fatalf("unexpected farm route candidate: %#v", candidate) } } @@ -3152,6 +3152,68 @@ func TestListNodeVPNAssignmentsDoesNotRequirePlatformAdmin(t *testing.T) { } } +func TestAcquireNodeVPNAssignmentLeaseAllowsEligibleCandidateWithoutPlatformAdmin(t *testing.T) { + store := &fakeRepository{ + platformRole: "user", + vpnConnection: VPNConnection{ + ID: "vpn-1", + ClusterID: "cluster-1", + Mode: VPNConnectionModeSingleActive, + DesiredState: VPNConnectionDesiredEnabled, + }, + nodeVPNAssignments: []NodeVPNAssignment{ + { + VPNConnectionID: "vpn-1", + ClusterID: "cluster-1", + OrganizationID: "org-1", + DesiredState: VPNConnectionDesiredEnabled, + AssignmentReason: "eligible_candidate", + }, + }, + } + service := NewService(store) + + lease, err := service.AcquireNodeVPNAssignmentLease(context.Background(), AcquireNodeVPNAssignmentLeaseInput{ + ClusterID: "cluster-1", + VPNConnectionID: "vpn-1", + OwnerNodeID: "node-1", + TTL: time.Minute, + Metadata: json.RawMessage(`{"reason":"test"}`), + }) + if err != nil { + t.Fatalf("acquire node vpn assignment lease: %v", err) + } + if lease.OwnerNodeID != "node-1" || lease.VPNConnectionID != "vpn-1" || lease.Status != VPNLeaseStatusActive { + t.Fatalf("unexpected lease: %+v", lease) + } +} + +func TestAcquireNodeVPNAssignmentLeaseRejectsInvisibleAssignment(t *testing.T) { + store := &fakeRepository{ + platformRole: "user", + vpnConnection: VPNConnection{ + ID: "vpn-1", + ClusterID: "cluster-1", + Mode: VPNConnectionModeSingleActive, + DesiredState: VPNConnectionDesiredEnabled, + }, + nodeVPNAssignments: []NodeVPNAssignment{ + {VPNConnectionID: "other-vpn", ClusterID: "cluster-1", AssignmentReason: "eligible_candidate"}, + }, + } + service := NewService(store) + + _, err := service.AcquireNodeVPNAssignmentLease(context.Background(), AcquireNodeVPNAssignmentLeaseInput{ + ClusterID: "cluster-1", + VPNConnectionID: "vpn-1", + OwnerNodeID: "node-1", + TTL: time.Minute, + }) + if !errors.Is(err, ErrVPNLeaseOwnerNotAllowed) { + t.Fatalf("err = %v, want ErrVPNLeaseOwnerNotAllowed", err) + } +} + func TestRenewNodeVPNAssignmentLeaseAllowsActiveOwnerWithoutPlatformAdmin(t *testing.T) { store := &fakeRepository{ platformRole: "user", @@ -6051,18 +6113,24 @@ func TestGetNodeSyntheticMeshConfigReportsRebuildPendingWhenNoAlternateExists(t if err != nil { t.Fatalf("synthetic config: %v", err) } - if containsRouteID(cfg.Routes, "route-bad") { - t.Fatalf("fenced route should be withheld while rebuild is pending: %+v", cfg.Routes) + if !containsRouteID(cfg.Routes, "route-bad") { + t.Fatalf("fenced route should be retained until an alternate exists: %+v", cfg.Routes) } - if cfg.RoutePathDecisions == nil || cfg.RoutePathDecisions.RebuildRequestCount != 1 || cfg.RoutePathDecisions.DegradedDecisionCount != 1 { + if cfg.RoutePathDecisions == nil || cfg.RoutePathDecisions.RebuildRequestCount != 1 || cfg.RoutePathDecisions.DegradedDecisionCount != 0 { t.Fatalf("expected rebuild/degraded decision counts: %+v", cfg.RoutePathDecisions) } - decision := cfg.RoutePathDecisions.Decisions[0] - if decision.DecisionSource != "service_channel_feedback_no_alternate" || - decision.RebuildStatus != "pending_degraded_fallback" || + var decision RoutePathDecision + for _, item := range cfg.RoutePathDecisions.Decisions { + if item.DecisionSource == "service_channel_feedback_no_alternate_keep_primary" { + decision = item + break + } + } + if decision.DecisionSource != "service_channel_feedback_no_alternate_keep_primary" || + decision.RebuildStatus != "requested" || decision.RebuildRequestID == "" || decision.RebuildAttempt != 3 || - !containsString(decision.ScoreReasons, "backend_relay_degraded_fallback_until_rebuild") { + !containsString(decision.ScoreReasons, "primary_route_retained_until_rebuild") { t.Fatalf("unexpected rebuild decision: %+v", decision) } } diff --git a/clients/android/.gradle/9.5.0/executionHistory/executionHistory.bin b/clients/android/.gradle/9.5.0/executionHistory/executionHistory.bin index 5f6654a..32c1308 100644 Binary files a/clients/android/.gradle/9.5.0/executionHistory/executionHistory.bin and b/clients/android/.gradle/9.5.0/executionHistory/executionHistory.bin differ diff --git a/clients/android/.gradle/9.5.0/executionHistory/executionHistory.lock b/clients/android/.gradle/9.5.0/executionHistory/executionHistory.lock index 892f515..559f1ce 100644 Binary files a/clients/android/.gradle/9.5.0/executionHistory/executionHistory.lock and b/clients/android/.gradle/9.5.0/executionHistory/executionHistory.lock differ diff --git a/clients/android/.gradle/9.5.0/fileHashes/fileHashes.bin b/clients/android/.gradle/9.5.0/fileHashes/fileHashes.bin index 5482412..71409c8 100644 Binary files a/clients/android/.gradle/9.5.0/fileHashes/fileHashes.bin and b/clients/android/.gradle/9.5.0/fileHashes/fileHashes.bin differ diff --git a/clients/android/.gradle/9.5.0/fileHashes/fileHashes.lock b/clients/android/.gradle/9.5.0/fileHashes/fileHashes.lock index ec4b7f5..611b7ed 100644 Binary files a/clients/android/.gradle/9.5.0/fileHashes/fileHashes.lock and b/clients/android/.gradle/9.5.0/fileHashes/fileHashes.lock differ diff --git a/clients/android/.gradle/9.5.0/fileHashes/resourceHashesCache.bin b/clients/android/.gradle/9.5.0/fileHashes/resourceHashesCache.bin index 1dded69..97fb248 100644 Binary files a/clients/android/.gradle/9.5.0/fileHashes/resourceHashesCache.bin and b/clients/android/.gradle/9.5.0/fileHashes/resourceHashesCache.bin differ diff --git a/clients/android/.gradle/buildOutputCleanup/buildOutputCleanup.lock b/clients/android/.gradle/buildOutputCleanup/buildOutputCleanup.lock index c6dcc88..55e8239 100644 Binary files a/clients/android/.gradle/buildOutputCleanup/buildOutputCleanup.lock and b/clients/android/.gradle/buildOutputCleanup/buildOutputCleanup.lock differ diff --git a/clients/android/.gradle/buildOutputCleanup/outputFiles.bin b/clients/android/.gradle/buildOutputCleanup/outputFiles.bin index f0bab02..d29c761 100644 Binary files a/clients/android/.gradle/buildOutputCleanup/outputFiles.bin and b/clients/android/.gradle/buildOutputCleanup/outputFiles.bin differ diff --git a/clients/android/app/build.gradle b/clients/android/app/build.gradle index 8994908..5323438 100644 --- a/clients/android/app/build.gradle +++ b/clients/android/app/build.gradle @@ -22,7 +22,7 @@ android { return (value == null ? "" : value.toString()).replace("\\", "\\\\").replace("\"", "\\\"") } - def defaultBackendUrl = project.findProperty("RAP_ANDROID_DEFAULT_BACKEND_URL") ?: "http://vpn.cin.su:19191/api/v1" + def defaultBackendUrl = project.findProperty("RAP_ANDROID_DEFAULT_BACKEND_URL") ?: "https://vpn.cin.su/api/v1" def defaultClusterId = project.findProperty("RAP_ANDROID_DEFAULT_CLUSTER_ID") ?: "cfc0743d-d960-49fb-9de8-96e063d5e4aa" def defaultOrganizationId = project.findProperty("RAP_ANDROID_DEFAULT_ORGANIZATION_ID") ?: "125ff8b2-5ac1-4406-9bbb-ebbe18f7c7ed" @@ -30,8 +30,8 @@ android { applicationId "su.cin.rapvpn" minSdk 26 targetSdk 35 - versionCode 159 - versionName "0.2.159" + versionCode 182 + versionName "0.2.182" buildConfigField "String", "DEFAULT_BACKEND_URL", "\"${normalizeGradleString(defaultBackendUrl)}\"" buildConfigField "String", "DEFAULT_CLUSTER_ID", "\"${normalizeGradleString(defaultClusterId)}\"" buildConfigField "String", "DEFAULT_ORGANIZATION_ID", "\"${normalizeGradleString(defaultOrganizationId)}\"" diff --git a/clients/android/app/src/main/java/su/cin/rapvpn/MainActivity.java b/clients/android/app/src/main/java/su/cin/rapvpn/MainActivity.java index 9d0fef6..51cb36a 100644 --- a/clients/android/app/src/main/java/su/cin/rapvpn/MainActivity.java +++ b/clients/android/app/src/main/java/su/cin/rapvpn/MainActivity.java @@ -25,6 +25,7 @@ import java.util.Locale; public class MainActivity extends Activity { private static final String APP_VERSION = BuildConfig.VERSION_NAME; private static final String DEFAULT_BACKEND_URL = BuildConfig.DEFAULT_BACKEND_URL; + private static final String PUBLIC_FABRIC_BACKEND_URL = "https://vpn.cin.su/api/v1"; private static final String DEFAULT_CLUSTER_ID = BuildConfig.DEFAULT_CLUSTER_ID; private static final String DEFAULT_ORGANIZATION_ID = BuildConfig.DEFAULT_ORGANIZATION_ID; private static final String PREF_SELECTED_EXIT_NODE_ID = "selected_exit_node_id"; @@ -659,6 +660,16 @@ public class MainActivity extends Activity { if (candidate.isEmpty()) { return DEFAULT_BACKEND_URL; } + String lower = candidate.toLowerCase(Locale.US); + if ("http://vpn.cin.su:19191/api/v1".equals(lower) + || "http://vpn.cin.su/api/v1".equals(lower) + || "https://vpn.cin.su:443/api/v1".equals(lower) + || "http://94.141.118.222:19191/api/v1".equals(lower) + || "http://195.123.240.88:19131/api/v1".equals(lower) + || "http://192.168.200.61:18080/api/v1".equals(lower) + || "http://docker-test.cin.su:18080/api/v1".equals(lower)) { + return PUBLIC_FABRIC_BACKEND_URL; + } return candidate; } diff --git a/clients/android/app/src/main/java/su/cin/rapvpn/RapApiClient.java b/clients/android/app/src/main/java/su/cin/rapvpn/RapApiClient.java index 3144211..98f1926 100644 --- a/clients/android/app/src/main/java/su/cin/rapvpn/RapApiClient.java +++ b/clients/android/app/src/main/java/su/cin/rapvpn/RapApiClient.java @@ -356,7 +356,7 @@ final class RapApiClient { return new byte[0]; } if (!response.isSuccessful()) { - throw new IllegalStateException("HTTP " + response.code()); + throw new IllegalStateException(describeHttpFailure(response)); } ResponseBody body = response.body(); return body == null ? new byte[0] : body.bytes(); @@ -377,15 +377,34 @@ final class RapApiClient { Request request = builder.build(); try (Response response = httpClient.newCall(request).execute()) { if (!response.isSuccessful()) { - throw new IllegalStateException("HTTP " + response.code()); + throw new IllegalStateException(describeHttpFailure(response)); } } } - private String clientPacketPath(String clusterId, String vpnConnectionId, String suffix) { + private String describeHttpFailure(Response response) { + StringBuilder message = new StringBuilder("HTTP ").append(response.code()); + ResponseBody body = response.body(); + if (body != null) { + try { + String text = body.string(); + if (text != null && !text.trim().isEmpty()) { + text = text.replace('\n', ' ').replace('\r', ' ').trim(); + if (text.length() > 240) { + text = text.substring(0, 240); + } + message.append(": ").append(text); + } + } catch (Exception ignored) { + } + } + return message.toString(); + } + + private String clientPacketPath(String clusterId, String vpnConnectionId, String suffix) throws IOException { String path = fabricServiceChannel.packetPathForBase(baseUrl, clusterId, vpnConnectionId, false); if (path.isEmpty()) { - path = "/clusters/" + clusterId + "/vpn-connections/" + vpnConnectionId + "/tunnel/client/packets"; + throw new IOException("fabric service channel lease required for VPN packet dataplane"); } return path + (suffix == null ? "" : suffix); } diff --git a/clients/android/app/src/main/java/su/cin/rapvpn/RapDiagnosticService.java b/clients/android/app/src/main/java/su/cin/rapvpn/RapDiagnosticService.java index 7257c05..366ff13 100644 --- a/clients/android/app/src/main/java/su/cin/rapvpn/RapDiagnosticService.java +++ b/clients/android/app/src/main/java/su/cin/rapvpn/RapDiagnosticService.java @@ -18,6 +18,7 @@ import android.os.Build; import android.os.Handler; import android.os.IBinder; import android.os.Looper; +import android.provider.Settings; import android.widget.Toast; import org.json.JSONObject; @@ -38,6 +39,7 @@ import java.util.LinkedHashSet; import java.util.List; import java.util.Random; import java.util.Set; +import java.util.UUID; import java.text.SimpleDateFormat; import java.util.Date; import java.util.concurrent.atomic.AtomicBoolean; @@ -51,6 +53,7 @@ public class RapDiagnosticService extends Service { private static final String CHANNEL_ID = "rap-vpn-diagnostics"; private static final String APP_VERSION = BuildConfig.VERSION_NAME; private static final String DEFAULT_BACKEND_URL = BuildConfig.DEFAULT_BACKEND_URL; + private static final String PUBLIC_FABRIC_BACKEND_URL = "https://vpn.cin.su/api/v1"; private static final String INTERNAL_BACKEND_URL = "http://192.168.200.61:18080/api/v1"; private static final String DEFAULT_CLUSTER_ID = BuildConfig.DEFAULT_CLUSTER_ID; private static final String DEFAULT_ORGANIZATION_ID = BuildConfig.DEFAULT_ORGANIZATION_ID; @@ -60,14 +63,22 @@ public class RapDiagnosticService extends Service { private static final String PREF_REFRESH_TOKEN = "refresh_token"; private static final String PREF_USER_ID = "user_id"; private static final String PREF_DEVICE_ID = "device_id"; + private static final String PREF_DIAGNOSTIC_DEVICE_ID = "diagnostic_device_id"; private static final String PREF_PROFILE_JSON = "profile_json"; private static final String PREF_VPN_CONNECTION_ID = "vpn_connection_id"; + private static final long COMMAND_STALE_MS = 45000; + private static final long COMMAND_ORPHAN_MS = 60000; + private static final long POLL_FORCE_MS = 45000; private volatile boolean running; private Thread worker; private Thread supervisor; private String serviceState = ""; private String lastCommandType = ""; private String lastCommandResult = ""; + private String lastCommandPollResult = ""; + private String lastReceivedCommandID = ""; + private String lastReceivedCommandType = ""; + private long lastReceivedCommandAt = 0; private long lastCommandAt = 0; private long lastHeartbeatAt = 0; private long lastCommandPollAt = 0; @@ -84,14 +95,14 @@ public class RapDiagnosticService extends Service { public int onStartCommand(Intent intent, int flags, int startId) { if (intent != null && ACTION_STOP.equals(intent.getAction())) { running = false; - if (worker != null) { - worker.interrupt(); - } - if (supervisor != null) { - supervisor.interrupt(); - } - stopForeground(true); - stopSelfResult(startId); + if (worker != null) { + worker.interrupt(); + } + if (supervisor != null) { + supervisor.interrupt(); + } + stopForeground(true); + stopSelfResult(startId); return START_NOT_STICKY; } if (intent != null && ACTION_RESTART.equals(intent.getAction())) { @@ -129,14 +140,24 @@ public class RapDiagnosticService extends Service { } } + static void restart(android.content.Context context) { + Intent intent = new Intent(context, RapDiagnosticService.class); + intent.setAction(ACTION_RESTART); + if (Build.VERSION.SDK_INT >= 26) { + context.startForegroundService(intent); + } else { + context.startService(intent); + } + } + private void startWorker() { if (worker != null && worker.isAlive()) { long age = System.currentTimeMillis() - lastWorkerProgressAt; if (age > 45000) { restartWorker(); } else { - startSupervisor(); - return; + startSupervisor(); + return; } } running = true; @@ -211,8 +232,12 @@ public class RapDiagnosticService extends Service { if (clusterId == null || clusterId.trim().isEmpty()) { clusterId = DEFAULT_CLUSTER_ID; } - String deviceId = prefs.getString(PREF_DEVICE_ID, ""); + String deviceId = diagnosticDeviceId(prefs); if (backendUrl.isEmpty() || clusterId.isEmpty() || deviceId.isEmpty()) { + serviceState = "waiting for config backend=" + !backendUrl.isEmpty() + + " cluster=" + !clusterId.isEmpty() + + " device=" + !deviceId.isEmpty(); + writeLocalDiagnosticHeartbeat(); Thread.sleep(3000); continue; } @@ -259,12 +284,33 @@ public class RapDiagnosticService extends Service { commandPollStartedAt = 0; serviceState = "diagnostic poll watchdog released stale poll"; } + if (commandPollInProgress.get() && commandPollStartedAt == 0 && lastCommandPollAt > 0 && now - lastCommandPollAt > 45000) { + commandPollInProgress.set(false); + serviceState = "diagnostic poll watchdog released orphan poll flag age_ms=" + (now - lastCommandPollAt); + } + if (!commandPollInProgress.get() && !commandInProgress.get() && lastCommandPollAt > 0 && now - lastCommandPollAt > POLL_FORCE_MS) { + serviceState = "diagnostic poll watchdog forcing command poll age_ms=" + (now - lastCommandPollAt); + lastWorkerProgressAt = now; + } long commandAge = commandInProgress.get() && commandStartedAt > 0 ? now - commandStartedAt : 0; - if (commandAge > 120000) { + if (commandAge > COMMAND_STALE_MS) { commandInProgress.set(false); commandStartedAt = 0; + lastCommandType = lastReceivedCommandType.isEmpty() ? "command_timeout" : lastReceivedCommandType; + lastCommandResult = "command watchdog timed out age_ms=" + commandAge + + " id=" + lastReceivedCommandID + + " type=" + lastReceivedCommandType; + lastCommandAt = now; serviceState = "diagnostic command watchdog released stale command age_ms=" + commandAge; } + if (commandInProgress.get() && commandStartedAt == 0 && lastCommandAt > 0 && now - lastCommandAt > COMMAND_ORPHAN_MS) { + commandInProgress.set(false); + serviceState = "diagnostic command watchdog released orphan command flag age_ms=" + (now - lastCommandAt); + } + if (commandInProgress.get() && commandStartedAt == 0 && lastCommandPollAt > 0 && now - lastCommandPollAt > COMMAND_ORPHAN_MS) { + commandInProgress.set(false); + serviceState = "diagnostic command watchdog released poll-stalled command flag age_ms=" + (now - lastCommandPollAt); + } } private void startHeartbeatWorker(String backendUrl, String clusterId, String deviceId, SharedPreferences prefs) { @@ -308,9 +354,14 @@ public class RapDiagnosticService extends Service { JSONObject commandEnvelope = nextCommandWithFallback(backendUrl, clusterId, deviceId); lastWorkerProgressAt = System.currentTimeMillis(); if (commandEnvelope != null) { + lastCommandPollResult = describeCommandEnvelope(commandEnvelope); + rememberReceivedCommand(commandEnvelope); startCommandWorker(backendUrl, clusterId, deviceId, commandEnvelope); + } else { + lastCommandPollResult = "no_content"; } } catch (Exception e) { + lastCommandPollResult = "error: " + e.getClass().getSimpleName(); serviceState = "command poll error: " + e.getMessage(); lastWorkerProgressAt = System.currentTimeMillis(); } finally { @@ -323,11 +374,15 @@ public class RapDiagnosticService extends Service { private void startCommandWorker(String backendUrl, String clusterId, String deviceId, JSONObject commandEnvelope) { if (!commandInProgress.compareAndSet(false, true)) { + lastCommandPollResult = "worker_busy " + describeCommandEnvelope(commandEnvelope); return; } Thread commandWorker = new Thread(() -> { try { commandStartedAt = System.currentTimeMillis(); + lastCommandType = lastReceivedCommandType.isEmpty() ? "command_running" : lastReceivedCommandType; + lastCommandResult = "running id=" + lastReceivedCommandID + " type=" + lastReceivedCommandType; + lastCommandAt = commandStartedAt; lastWorkerProgressAt = System.currentTimeMillis(); RapApiClient commandClient = controlClient(backendUrl); controlNetworkMode = commandClient.networkMode(); @@ -363,6 +418,9 @@ public class RapDiagnosticService extends Service { params = payload; } String result; + lastCommandType = type; + lastCommandResult = "running id=" + (command == null ? "" : command.optString("id", "")) + " type=" + type; + lastCommandAt = System.currentTimeMillis(); if ("start_vpn".equals(type)) { result = startVPNFromSavedProfile(); } else if ("stop_vpn".equals(type)) { @@ -411,6 +469,8 @@ public class RapDiagnosticService extends Service { result = "remote_assist_end accepted"; } else if ("full_vpn_test".equals(type)) { result = runFullVPNTest(client, clusterId, params); + } else if ("install_profile".equals(type) || "apply_profile".equals(type)) { + result = installProfileFromCommand(params); } else if ("refresh_profile".equals(type)) { result = refreshProfile(); } else { @@ -418,9 +478,15 @@ public class RapDiagnosticService extends Service { } if (isRecoverableVPNProbe(type) && looksLikeVPNStall(result)) { String firstResult = result; - String recovery = controlledRestartVPNRuntime(client, clusterId); - Thread.sleep(4000); - result = firstResult + " | recovery=" + recovery + " | retry=" + runVPNProbeCommand(type, params); + Thread.sleep(1500); + String fastRetry = runVPNProbeCommand(type, params); + if (!looksLikeVPNStall(fastRetry)) { + result = firstResult + " | fast_retry=" + fastRetry; + } else { + String recovery = controlledRestartVPNRuntime(client, clusterId); + Thread.sleep(4000); + result = firstResult + " | fast_retry=" + fastRetry + " | recovery=" + recovery + " | retry=" + runVPNProbeCommand(type, params); + } } lastCommandType = type; lastCommandResult = result; @@ -623,7 +689,9 @@ public class RapDiagnosticService extends Service { return "restart failed: queue reset failed: " + e.getMessage(); } Thread.sleep(300); - return startVPNFromSavedProfile(); + String refresh = refreshProfile(); + String start = startVPNFromSavedProfile(); + return start + " profile_refresh=" + refresh; } catch (Exception e) { return "restart failed: " + e.getClass().getSimpleName() + ": " + e.getMessage(); } @@ -642,6 +710,10 @@ public class RapDiagnosticService extends Service { } serviceState = "upgrade restart " + lastVersion + " -> " + APP_VERSION; try { + String refresh = refreshProfile(); + if (refresh.startsWith("refresh_profile failed")) { + lastCommandResult = "vpn runtime profile refresh before upgrade restart failed: " + refresh; + } try { client.resetVPNPacketQueues(clusterId, connectionId); } catch (Exception ignored) { @@ -650,7 +722,7 @@ public class RapDiagnosticService extends Service { startVPNFromSavedProfile(); prefs.edit().putString("vpn_runtime_app_version", APP_VERSION).apply(); lastCommandType = "auto_upgrade_restart"; - lastCommandResult = "vpn runtime reinitialized after app upgrade " + lastVersion + " -> " + APP_VERSION; + lastCommandResult = "vpn runtime reinitialized after app upgrade " + lastVersion + " -> " + APP_VERSION + " profile_refresh=" + refresh; lastCommandAt = System.currentTimeMillis(); } catch (Exception e) { lastCommandType = "auto_upgrade_restart"; @@ -664,44 +736,135 @@ public class RapDiagnosticService extends Service { try { String refreshToken = new SecureTokenStore(this).get(PREF_REFRESH_TOKEN); if (refreshToken.isEmpty()) { - return "refresh_profile skipped: refresh token missing"; + String savedUserId = prefs.getString(PREF_USER_ID, ""); + if (savedUserId == null || savedUserId.trim().isEmpty()) { + return "refresh_profile skipped: refresh token and saved user missing"; + } + return refreshProfileForUser(prefs, savedUserId.trim(), null); } RapApiClient client = new RapApiClient(normalizeBackendUrl(prefs.getString("backend_url", "")), this, true); RapApiClient.AuthContext auth = client.refresh(refreshToken); - String organizationId = prefs.getString("organization_id", DEFAULT_ORGANIZATION_ID); - String clusterId = prefs.getString("cluster_id", DEFAULT_CLUSTER_ID); - if (clusterId == null || clusterId.trim().isEmpty()) { - clusterId = DEFAULT_CLUSTER_ID; - } - if (organizationId == null || organizationId.trim().isEmpty()) { - organizationId = DEFAULT_ORGANIZATION_ID; - } - String exitNodeId = prefs.getString(PREF_SELECTED_EXIT_NODE_ID, ""); - String profileJson = client.vpnClientProfile(clusterId, organizationId, auth.userId, exitNodeId); - JSONObject root = new JSONObject(profileJson); - JSONObject profile = root.getJSONObject("vpn_client_profile"); - String connectionId = profile.getJSONArray("connections").getJSONObject(0).getString("id"); - prefs.edit() - .putString(PREF_USER_ID, auth.userId) - .putString(PREF_DEVICE_ID, auth.deviceId) - .putString(PREF_PROFILE_JSON, profileJson) - .putString(PREF_VPN_CONNECTION_ID, connectionId) - .apply(); new SecureTokenStore(this).put(PREF_REFRESH_TOKEN, auth.refreshToken); - return "refresh_profile ok " + connectionId; + return refreshProfileForUser(prefs, auth.userId, auth.deviceId); } catch (Exception e) { return "refresh_profile failed: " + e.getMessage(); } } + private String refreshProfileForUser(SharedPreferences prefs, String userId, String trustedDeviceId) throws Exception { + String backendUrl = normalizeBackendUrl(prefs.getString("backend_url", DEFAULT_BACKEND_URL)); + String organizationId = prefs.getString("organization_id", DEFAULT_ORGANIZATION_ID); + String clusterId = prefs.getString("cluster_id", DEFAULT_CLUSTER_ID); + if (clusterId == null || clusterId.trim().isEmpty()) { + clusterId = DEFAULT_CLUSTER_ID; + } + if (organizationId == null || organizationId.trim().isEmpty()) { + organizationId = DEFAULT_ORGANIZATION_ID; + } + String exitNodeId = prefs.getString(PREF_SELECTED_EXIT_NODE_ID, ""); + RapApiClient client = new RapApiClient(backendUrl, this, true); + String profileJson = client.vpnClientProfile(clusterId, organizationId, userId, exitNodeId); + JSONObject root = new JSONObject(profileJson); + JSONObject profile = root.getJSONObject("vpn_client_profile"); + String connectionId = profile.getJSONArray("connections").getJSONObject(0).getString("id"); + SharedPreferences.Editor editor = prefs.edit() + .putString("backend_url", backendUrl) + .putString("cluster_id", clusterId) + .putString("organization_id", organizationId) + .putString(PREF_USER_ID, userId) + .putString(PREF_PROFILE_JSON, profileJson) + .putString(PREF_VPN_CONNECTION_ID, connectionId); + if (trustedDeviceId != null && !trustedDeviceId.trim().isEmpty()) { + editor.putString(PREF_DEVICE_ID, trustedDeviceId.trim()); + } + editor.apply(); + return "refresh_profile ok " + connectionId; + } + + private String installProfileFromCommand(JSONObject params) { + try { + String backendUrl = normalizeBackendUrl(params.optString("backend_url", DEFAULT_BACKEND_URL)); + String clusterId = params.optString("cluster_id", DEFAULT_CLUSTER_ID).trim(); + String organizationId = params.optString("organization_id", DEFAULT_ORGANIZATION_ID).trim(); + String userId = params.optString("user_id", "").trim(); + String trustedDeviceId = params.optString("trusted_device_id", "").trim(); + String selectedExitNodeId = params.optString("selected_exit_node_id", params.optString("exit_node_id", "")).trim(); + String profileJson = params.optString("profile_json", "").trim(); + JSONObject root; + if (profileJson.isEmpty()) { + JSONObject profile = params.optJSONObject("vpn_client_profile"); + if (profile == null) { + profile = params.optJSONObject("profile"); + } + if (profile == null) { + return "install_profile skipped: profile missing"; + } + root = new JSONObject(); + root.put("vpn_client_profile", profile); + profileJson = root.toString(); + } else { + root = new JSONObject(profileJson); + if (!root.has("vpn_client_profile")) { + JSONObject wrapped = new JSONObject(); + wrapped.put("vpn_client_profile", root); + root = wrapped; + profileJson = wrapped.toString(); + } + } + JSONObject profile = root.getJSONObject("vpn_client_profile"); + if (clusterId.isEmpty()) { + clusterId = profile.optString("cluster_id", DEFAULT_CLUSTER_ID); + } + if (organizationId.isEmpty()) { + organizationId = profile.optString("organization_id", DEFAULT_ORGANIZATION_ID); + } + if (userId.isEmpty()) { + userId = profile.optString("user_id", ""); + } + JSONObject connection = profile.getJSONArray("connections").getJSONObject(0); + String connectionId = params.optString("vpn_connection_id", connection.optString("id", "")).trim(); + JSONObject config = connection.optJSONObject("client_config"); + if (selectedExitNodeId.isEmpty() && config != null) { + JSONObject route = config.optJSONObject("vpn_fabric_route"); + if (route != null) { + selectedExitNodeId = route.optString("selected_exit_node_id", ""); + } + } + SharedPreferences.Editor editor = getSharedPreferences(PREFS, MODE_PRIVATE).edit() + .putString("backend_url", backendUrl) + .putString("cluster_id", clusterId) + .putString("organization_id", organizationId) + .putString(PREF_USER_ID, userId) + .putString(PREF_PROFILE_JSON, profileJson) + .putString(PREF_VPN_CONNECTION_ID, connectionId); + if (!trustedDeviceId.isEmpty()) { + editor.putString(PREF_DEVICE_ID, trustedDeviceId); + } + if (!selectedExitNodeId.isEmpty()) { + editor.putString(PREF_SELECTED_EXIT_NODE_ID, selectedExitNodeId); + } + editor.apply(); + Intent stopIntent = new Intent(this, RapVpnService.class); + stopIntent.setAction(RapVpnService.ACTION_STOP); + startService(stopIntent); + Thread.sleep(300); + return "install_profile ok " + connectionId + " | " + startVPNFromSavedProfile(); + } catch (Exception e) { + return "install_profile failed: " + e.getClass().getSimpleName() + ": " + e.getMessage(); + } + } + private JSONObject statusPayload(String event) throws Exception { SharedPreferences prefs = getSharedPreferences(PREFS, MODE_PRIVATE); + String deviceId = diagnosticDeviceId(prefs); JSONObject payload = new JSONObject(); payload.put("event", event); payload.put("app_version", APP_VERSION); payload.put("service", "diagnostic"); payload.put("user_id", prefs.getString(PREF_USER_ID, "")); - payload.put("device_id", prefs.getString(PREF_DEVICE_ID, "")); + payload.put("device_id", deviceId); + payload.put("trusted_device_id", prefs.getString(PREF_DEVICE_ID, "")); + payload.put("diagnostic_device_id", prefs.getString(PREF_DIAGNOSTIC_DEVICE_ID, "")); payload.put("organization_id", prefs.getString("organization_id", "")); payload.put("vpn_connection_id", prefs.getString(PREF_VPN_CONNECTION_ID, "")); payload.put("backend_url", prefs.getString("backend_url", "")); @@ -716,15 +879,86 @@ public class RapDiagnosticService extends Service { payload.put("last_command_at", lastCommandAt); payload.put("last_heartbeat_at", lastHeartbeatAt); payload.put("last_command_poll_at", lastCommandPollAt); + payload.put("last_command_poll_result", lastCommandPollResult); + payload.put("last_received_command_id", lastReceivedCommandID); + payload.put("last_received_command_type", lastReceivedCommandType); + payload.put("last_received_command_at", lastReceivedCommandAt); + payload.put("heartbeat_in_progress", heartbeatInProgress.get()); + payload.put("heartbeat_started_at", heartbeatStartedAt); + payload.put("command_poll_in_progress", commandPollInProgress.get()); + payload.put("command_poll_started_at", commandPollStartedAt); + payload.put("command_in_progress", commandInProgress.get()); + payload.put("command_started_at", commandStartedAt); payload.put("browser_test", browserTestSnapshot()); return payload; } + private String describeCommandEnvelope(JSONObject envelope) { + if (envelope == null) { + return "no_content"; + } + JSONObject command = envelope.optJSONObject("vpn_client_diagnostic_command"); + JSONObject payload = command == null ? envelope.optJSONObject("payload") : command.optJSONObject("payload"); + String id = command == null ? "" : command.optString("id", ""); + String type = payload == null ? "" : payload.optString("type", ""); + String value = "received"; + if (!type.isEmpty()) { + value += " " + type; + } + if (!id.isEmpty()) { + value += " " + id; + } + return value; + } + + private void rememberReceivedCommand(JSONObject envelope) { + JSONObject command = envelope == null ? null : envelope.optJSONObject("vpn_client_diagnostic_command"); + JSONObject payload = command == null ? (envelope == null ? null : envelope.optJSONObject("payload")) : command.optJSONObject("payload"); + lastReceivedCommandID = command == null ? "" : command.optString("id", ""); + lastReceivedCommandType = payload == null ? "" : payload.optString("type", ""); + lastReceivedCommandAt = System.currentTimeMillis(); + } + + private String diagnosticDeviceId(SharedPreferences prefs) { + String trusted = prefs.getString(PREF_DEVICE_ID, ""); + if (trusted != null && !trusted.trim().isEmpty()) { + return trusted.trim(); + } + String cached = prefs.getString(PREF_DIAGNOSTIC_DEVICE_ID, ""); + if (cached != null && !cached.trim().isEmpty()) { + return cached.trim(); + } + String androidId = ""; + try { + androidId = Settings.Secure.getString(getContentResolver(), Settings.Secure.ANDROID_ID); + } catch (Exception ignored) { + } + String seed = androidId == null || androidId.trim().isEmpty() + ? UUID.randomUUID().toString() + : androidId.trim(); + String generated = "diag-" + seed.replaceAll("[^A-Za-z0-9_-]", "").toLowerCase(); + if (generated.length() > 80) { + generated = generated.substring(0, 80); + } + prefs.edit().putString(PREF_DIAGNOSTIC_DEVICE_ID, generated).apply(); + return generated; + } + private String normalizeBackendUrl(String value) { String candidate = value == null ? "" : value.trim().replaceAll("/+$", ""); if (candidate.isEmpty()) { return DEFAULT_BACKEND_URL; } + String lower = candidate.toLowerCase(); + if ("http://vpn.cin.su:19191/api/v1".equals(lower) + || "http://vpn.cin.su/api/v1".equals(lower) + || "https://vpn.cin.su:443/api/v1".equals(lower) + || "http://94.141.118.222:19191/api/v1".equals(lower) + || "http://195.123.240.88:19131/api/v1".equals(lower) + || "http://192.168.200.61:18080/api/v1".equals(lower) + || "http://docker-test.cin.su:18080/api/v1".equals(lower)) { + return PUBLIC_FABRIC_BACKEND_URL; + } return candidate; } @@ -791,7 +1025,7 @@ public class RapDiagnosticService extends Service { if (!connectionId.isEmpty()) { report.put("packet_stats", client.vpnPacketStats(clusterId, connectionId)); } - client.reportVPNDiagnosticStatus(clusterId, getSharedPreferences(PREFS, MODE_PRIVATE).getString(PREF_DEVICE_ID, ""), report); + client.reportVPNDiagnosticStatus(clusterId, diagnosticDeviceId(getSharedPreferences(PREFS, MODE_PRIVATE)), report); } if (!connectionId.isEmpty()) { result.append(" | stats=").append(compact(client.vpnPacketStats(clusterId, connectionId).toString(), 900)); @@ -818,7 +1052,7 @@ public class RapDiagnosticService extends Service { private String runVPNDownloadTest(String target) { try { - Network vpn = vpnNetwork(); + Network vpn = waitForVPNNetwork(5000); if (vpn == null) { return "vpn_download_test " + target + " -> vpn network not found"; } @@ -1009,6 +1243,13 @@ public class RapDiagnosticService extends Service { payload.put("diagnostic_local_heartbeat_at", runtime.getLong("diagnostic_local_heartbeat_at", 0)); payload.put("diagnostic_local_state", runtime.getString("diagnostic_local_state", "")); payload.put("diagnostic_local_app_version", runtime.getString("diagnostic_local_app_version", "")); + payload.put("diagnostic_watchdog_started_at", runtime.getLong("diagnostic_watchdog_started_at", 0)); + payload.put("diagnostic_watchdog_last_ensure_at", runtime.getLong("diagnostic_watchdog_last_ensure_at", 0)); + payload.put("diagnostic_watchdog_last_heartbeat_age_ms", runtime.getLong("diagnostic_watchdog_last_heartbeat_age_ms", 0)); + payload.put("diagnostic_watchdog_last_action", runtime.getString("diagnostic_watchdog_last_action", "")); + payload.put("diagnostic_watchdog_last_error", runtime.getString("diagnostic_watchdog_last_error", "")); + payload.put("diagnostic_watchdog_ensure_requests", runtime.getLong("diagnostic_watchdog_ensure_requests", 0)); + payload.put("diagnostic_watchdog_restart_requests", runtime.getLong("diagnostic_watchdog_restart_requests", 0)); payload.put("uplink_read", runtime.getLong("uplink_read", 0)); payload.put("uplink_sent", runtime.getLong("uplink_sent", 0)); payload.put("downlink_received", runtime.getLong("downlink_received", 0)); @@ -1044,8 +1285,10 @@ public class RapDiagnosticService extends Service { payload.put("errors", runtime.getLong("errors", 0)); payload.put("uplink", runtimePrefix(runtime, "uplink")); payload.put("uplink_sender", runtimePrefix(runtime, "uplink_sender")); + payload.put("uplink_tcp", runtimePrefix(runtime, "uplink_tcp")); payload.put("downlink", runtimePrefix(runtime, "downlink")); payload.put("downlink_writer", runtimePrefix(runtime, "downlink_writer")); + payload.put("downlink_tcp", runtimePrefix(runtime, "downlink_tcp")); payload.put("relay", runtimePrefix(runtime, "relay")); payload.put("uplink_worker_count", runtime.getInt("uplink_worker_count", 0)); payload.put("uplink_queue_depth_total", runtime.getInt("uplink_queue_depth_total", 0)); @@ -1266,7 +1509,7 @@ public class RapDiagnosticService extends Service { } long started = System.currentTimeMillis(); try { - Network vpn = vpnNetwork(); + Network vpn = waitForVPNNetwork(5000); if (vpn == null) { return "vpn_tcp_connect " + host + ":" + port + " -> vpn network not found"; } @@ -1283,6 +1526,24 @@ public class RapDiagnosticService extends Service { } } + private Network waitForVPNNetwork(int timeoutMs) { + long deadline = System.currentTimeMillis() + Math.max(0, timeoutMs); + Network vpn; + do { + vpn = vpnNetwork(); + if (vpn != null) { + return vpn; + } + try { + Thread.sleep(150); + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + return null; + } + } while (System.currentTimeMillis() < deadline); + return null; + } + private FetchResult fetchVPNURL(Network vpn, URL url, int connectTimeoutMs, int readTimeoutMs, int maxBytes) throws Exception { long started = System.currentTimeMillis(); HttpURLConnection connection = (HttpURLConnection) vpn.openConnection(url); diff --git a/clients/android/app/src/main/java/su/cin/rapvpn/RapVpnService.java b/clients/android/app/src/main/java/su/cin/rapvpn/RapVpnService.java index 3fde64d..6b8f489 100644 --- a/clients/android/app/src/main/java/su/cin/rapvpn/RapVpnService.java +++ b/clients/android/app/src/main/java/su/cin/rapvpn/RapVpnService.java @@ -26,7 +26,9 @@ import java.net.DatagramPacket; import java.net.DatagramSocket; import java.net.Inet4Address; import java.net.InetAddress; +import java.net.InetSocketAddress; import java.net.URI; +import java.net.Socket; import java.util.ArrayList; import java.util.LinkedHashMap; import java.util.LinkedHashSet; @@ -48,10 +50,12 @@ public class RapVpnService extends VpnService { private static final String CHANNEL_ID = "rap-vpn"; private static final String TAG = "RapVpnService"; private static final String PREFS = "rap-vpn-runtime"; - private static final int DEFAULT_VPN_MTU = 1420; + private static final int DEFAULT_VPN_MTU = 1000; + private static final int VPN_TCP_MSS_CLAMP = 900; + private static final boolean PACKET_WEBSOCKET_DATAPLANE_ENABLED = false; private static final int VPN_BATCH_MAX_PACKETS = 512; private static final int VPN_BATCH_MAX_BYTES = 1024 * 1024; - private static final int UPLINK_WORKER_MAX_COUNT = 4; + private static final int UPLINK_WORKER_MAX_COUNT = 1; private static final int UPLINK_QUEUE_CAPACITY = 32768; private static final int PRIORITY_QUEUE_CAPACITY = 4096; private static final int UPLINK_SEND_RETRY_COUNT = 2; @@ -72,7 +76,12 @@ public class RapVpnService extends VpnService { private static final int RUNTIME_WATCHDOG_STALE_SYNACK_MS = 15000; private static final int RUNTIME_WATCHDOG_RECOVERY_COOLDOWN_MS = 60000; private static final int RUNTIME_WATCHDOG_HARD_RESTART_COOLDOWN_MS = 180000; + private static final int DIAGNOSTIC_WATCHDOG_INTERVAL_MS = 5000; + private static final int DIAGNOSTIC_STALE_RESTART_MS = 30000; + private static final int DIAGNOSTIC_RESTART_COOLDOWN_MS = 15000; private static final int VPN_START_WARMUP_TIMEOUT_MS = 6000; + private static final int VPN_TCP_WARMUP_CONNECT_TIMEOUT_MS = 2500; + private static final int VPN_TCP_WARMUP_PASSES = 3; private static final String[] DEFAULT_DNS_PROBE_DOMAINS = new String[]{ "speedtest.rt.ru", "2ip.ru", @@ -97,6 +106,11 @@ public class RapVpnService extends VpnService { "rline-host.qms.ru", "timernet-host.qms.ru" }; + private static final String[] DEFAULT_TCP_WARMUP_TARGETS = new String[]{ + "192.168.200.61:18080", + "192.168.200.95:3389", + "188.40.167.82:80" + }; private static final String PREF_NAME = "rap-vpn"; private static final String PREF_PROFILE_JSON = "profile_json"; private static final String PREF_BACKEND_URL = "backend_url"; @@ -112,6 +126,7 @@ public class RapVpnService extends VpnService { private Thread downlinkThread; private Thread downlinkWriterThread; private Thread runtimeWatchdogThread; + private Thread diagnosticWatchdogThread; private BlockingQueue[] uplinkQueues; private BlockingQueue[] downlinkQueues; private BlockingQueue uplinkPriorityQueue; @@ -171,6 +186,8 @@ public class RapVpnService extends VpnService { private final AtomicLong runtimeWatchdogRecoveries = new AtomicLong(); private final AtomicLong tcpHandshakeStalls = new AtomicLong(); private final AtomicLong runtimeWatchdogHardRestarts = new AtomicLong(); + private final AtomicLong diagnosticEnsureRequests = new AtomicLong(); + private final AtomicLong diagnosticRestartRequests = new AtomicLong(); private final AtomicBoolean hardRuntimeRestartInProgress = new AtomicBoolean(); private volatile boolean relaxedUplinkSourceValidation; private volatile boolean relaxedDownlinkDestinationValidation; @@ -180,6 +197,7 @@ public class RapVpnService extends VpnService { private volatile int activePacketRelayIndex; private volatile VpnPacketWebSocketRelay packetWebSocketRelay; private volatile FabricServiceChannel activeFabricServiceChannel = new FabricServiceChannel(); + private volatile String lastUplinkSendErrorMessage = ""; private final Object packetRelaySwitchLock = new Object(); private final Map clientSourceNat = new LinkedHashMap(4096, 0.75f, true) { @Override @@ -198,7 +216,7 @@ public class RapVpnService extends VpnService { private volatile long lastRuntimeWatchdogHardRestartAt; private volatile long lastDiagnosticEnsureAt; private volatile long lastDiagnosticStatusEnsureAt; - private volatile boolean nextDiagnosticEnsureMayRestart; + private volatile long lastDiagnosticRestartAt; private static final int ADDRESS_MISMATCH_TOLERANCE_PACKETS = 16; @Override @@ -257,8 +275,17 @@ public class RapVpnService extends VpnService { List packetRelayUrls = activePacketRelayUrlsByProfile == null || activePacketRelayUrlsByProfile.isEmpty() ? singletonUrl(activePacketRelayUrlByProfile) : new ArrayList<>(activePacketRelayUrlsByProfile); + if (!activeFabricServiceChannel.enabled) { + shutdownReason = "fabric service channel lease required"; + writeRuntimeStatus("error", "vpn not started: fabric service channel lease required", 0, 0, 0, 0); + stopSelf(); + return START_NOT_STICKY; + } if (packetRelayUrls.isEmpty()) { - packetRelayUrls.add(backendUrl); + shutdownReason = "missing farm entry endpoint"; + writeRuntimeStatus("error", "vpn not started: missing farm entry endpoint", 0, 0, 0, 0); + stopSelf(); + return START_NOT_STICKY; } startPacketRelay(backendUrl, packetRelayUrls, clusterId, vpnConnectionId); if (!running) { @@ -284,6 +311,7 @@ public class RapVpnService extends VpnService { private void ensureDiagnosticServiceRunning() { try { RapDiagnosticService.start(this); + diagnosticEnsureRequests.incrementAndGet(); writeRuntimeDetail("diagnostic_start", "diagnostic service start requested by vpn runtime", "control", 0, 0, "", -1); } catch (Exception e) { Log.w(TAG, "diagnostic service start failed", e); @@ -295,8 +323,15 @@ public class RapVpnService extends VpnService { try { SharedPreferences runtime = getSharedPreferences(PREFS, MODE_PRIVATE); long lastLocalHeartbeat = runtime.getLong("diagnostic_local_heartbeat_at", 0); - long age = lastLocalHeartbeat <= 0 ? Long.MAX_VALUE : System.currentTimeMillis() - lastLocalHeartbeat; - boolean restart = nextDiagnosticEnsureMayRestart && age > 45000; + long now = System.currentTimeMillis(); + long age = lastLocalHeartbeat <= 0 ? Long.MAX_VALUE : now - lastLocalHeartbeat; + boolean restart = age > DIAGNOSTIC_STALE_RESTART_MS + && now - lastDiagnosticRestartAt >= DIAGNOSTIC_RESTART_COOLDOWN_MS; + diagnosticEnsureRequests.incrementAndGet(); + if (restart) { + diagnosticRestartRequests.incrementAndGet(); + lastDiagnosticRestartAt = now; + } Intent intent = new Intent(this, RapDiagnosticService.class); intent.setAction(restart ? RapDiagnosticService.ACTION_RESTART : RapDiagnosticService.ACTION_START); if (Build.VERSION.SDK_INT >= 26) { @@ -304,7 +339,13 @@ public class RapVpnService extends VpnService { } else { startService(intent); } - nextDiagnosticEnsureMayRestart = true; + runtime.edit() + .putLong("diagnostic_watchdog_last_ensure_at", now) + .putLong("diagnostic_watchdog_last_heartbeat_age_ms", age) + .putString("diagnostic_watchdog_last_action", restart ? "restart" : "start") + .putLong("diagnostic_watchdog_ensure_requests", diagnosticEnsureRequests.get()) + .putLong("diagnostic_watchdog_restart_requests", diagnosticRestartRequests.get()) + .apply(); writeRuntimeDetail( restart ? "diagnostic_restart" : "diagnostic_start", (restart ? "diagnostic service restart requested age_ms=" : "diagnostic service start requested age_ms=") + age, @@ -315,6 +356,11 @@ public class RapVpnService extends VpnService { -1); } catch (Exception e) { Log.w(TAG, "diagnostic service health ensure failed", e); + getSharedPreferences(PREFS, MODE_PRIVATE).edit() + .putLong("diagnostic_watchdog_last_ensure_at", System.currentTimeMillis()) + .putString("diagnostic_watchdog_last_action", "failed") + .putString("diagnostic_watchdog_last_error", e.getClass().getSimpleName() + ": " + e.getMessage()) + .apply(); writeRuntimeDetail("diagnostic_start_failed", e.getMessage(), "control", 0, 1, e.getClass().getSimpleName(), -1); } } @@ -775,6 +821,19 @@ public class RapVpnService extends VpnService { } } + private boolean isIPv6URLHost(String value) { + if (value == null || value.trim().isEmpty()) { + return false; + } + try { + URI uri = URI.create(value.trim()); + String host = uri.getHost(); + return host != null && host.contains(":"); + } catch (Exception ignored) { + return false; + } + } + private void writeRuntimeConfig(VpnClientConfig config, boolean forceFullTunnel, boolean fastPathMode) { try { getSharedPreferences(PREFS, MODE_PRIVATE).edit() @@ -935,11 +994,15 @@ public class RapVpnService extends VpnService { + " connection=" + present(vpnConnectionId), 0, 0, 0, 0); return; } - List relayUrls = dedupeRelayUrls(candidateUrls, backendUrl); + List relayUrls = activeFabricServiceChannel.enabled ? dedupeFabricRelayUrls(candidateUrls, backendUrl) : dedupeRelayUrls(candidateUrls, backendUrl); String selectedRelayUrl = relayUrls.isEmpty() ? "" : relayUrls.get(0); - if (selectedRelayUrl == null || selectedRelayUrl.isEmpty()) { + if ((selectedRelayUrl == null || selectedRelayUrl.isEmpty()) && !activeFabricServiceChannel.enabled) { selectedRelayUrl = backendUrl; } + if (selectedRelayUrl == null || selectedRelayUrl.isEmpty()) { + writeRuntimeStatus("error", "relay not started: missing fabric farm entry endpoint", 0, 0, 0, 0); + return; + } activePacketRelayUrlByProfile = selectedRelayUrl; activePacketRelayUrlsByProfile = new ArrayList<>(relayUrls); activePacketRelayIndex = Math.max(0, relayUrls.indexOf(selectedRelayUrl)); @@ -971,14 +1034,18 @@ public class RapVpnService extends VpnService { downlinkQueues[i] = new ArrayBlockingQueue<>(downlinkPerFlowCapacity); } configureBackendBypass(selectedRelayUrl); - startPacketWebSocketRelay(selectedRelayUrl, clusterId, vpnConnectionId); + if (PACKET_WEBSOCKET_DATAPLANE_ENABLED) { + startPacketWebSocketRelay(selectedRelayUrl, clusterId, vpnConnectionId); + } else { + writeRuntimeDetail("http_packet_batch", "packet websocket disabled; using confirmed HTTP batches", "relay", 0, 0, "", -1); + } Log.i(TAG, "packet relay starting: backend=" + selectedRelayUrl + " cluster=" + clusterId + " vpn_connection=" + vpnConnectionId); writeRuntimeStatus("relay", "relay starting " + vpnConnectionId, 0, 0, 0, 0); writeRuntimeDetail("running", "packet relay active", "relay", 0, 0, ""); - final String resetRelayUrl = selectedRelayUrl; - Thread resetThread = new Thread(() -> { + final String legacyResetRelayUrl = selectedRelayUrl; + Thread resetThread = activeFabricServiceChannel.enabled ? null : new Thread(() -> { try { - RapApiClient uplinkClient = packetRelayClientForUrl(resetRelayUrl); + RapApiClient uplinkClient = packetRelayClientForUrl(legacyResetRelayUrl); JSONObject reset = uplinkClient.resetVPNPacketQueues(clusterId, vpnConnectionId); Log.i(TAG, "packet relay queues reset: " + reset.toString()); writeRuntimeStatus("relay_reset", reset.toString(), 0, 0, 0, 0); @@ -996,7 +1063,12 @@ public class RapVpnService extends VpnService { downlinkThread = new Thread(() -> runDownlinkWithRestart(clusterId, vpnConnectionId), "rap-vpn-downlink-receiver"); downlinkWriterThread = new Thread(this::pumpDownlinkQueueToTun, "rap-vpn-downlink-writer"); runtimeWatchdogThread = new Thread(() -> runRuntimeWatchdog(clusterId, vpnConnectionId), "rap-vpn-runtime-watchdog"); - resetThread.start(); + diagnosticWatchdogThread = new Thread(this::runDiagnosticServiceWatchdog, "rap-vpn-diagnostic-watchdog"); + if (resetThread != null) { + resetThread.start(); + } else { + writeRuntimeStatus("farm_dataplane", "backend relay queue reset skipped; farm owns vpn packet routes", 0, 0, 0, 0); + } uplinkThread.start(); for (Thread senderThread : uplinkSenderThreads) { senderThread.start(); @@ -1004,6 +1076,7 @@ public class RapVpnService extends VpnService { downlinkThread.start(); downlinkWriterThread.start(); runtimeWatchdogThread.start(); + diagnosticWatchdogThread.start(); } private List singletonUrl(String value) { @@ -1057,6 +1130,33 @@ public class RapVpnService extends VpnService { return out; } + private List dedupeFabricRelayUrls(List candidateUrls, String backendUrl) { + List ipv4OrHost = new ArrayList<>(); + List ipv6 = new ArrayList<>(); + boolean backendIsPrivate = isPrivateURLHost(backendUrl); + if (candidateUrls != null) { + for (String url : candidateUrls) { + String normalized = normalizeHTTPBaseUrl(url); + if (!backendIsPrivate && isPrivateURLHost(normalized)) { + continue; + } + if (isIPv6URLHost(normalized)) { + addUniqueUrl(ipv6, normalized); + } else { + addUniqueUrl(ipv4OrHost, normalized); + } + } + } + List out = new ArrayList<>(); + for (String url : ipv4OrHost) { + addUniqueUrl(out, url); + } + for (String url : ipv6) { + addUniqueUrl(out, url); + } + return out; + } + private void addUniqueUrl(List urls, String value) { if (urls == null || value == null) { return; @@ -1132,6 +1232,9 @@ public class RapVpnService extends VpnService { if (next == null || next.isEmpty() || next.equals(normalizedFailed)) { continue; } + if (isIPv6URLHost(next) && hasNonIPv6RelayUrl(urls)) { + continue; + } activePacketRelayIndex = nextIndex; activePacketRelayUrlByProfile = next; configureBackendBypass(next); @@ -1145,6 +1248,18 @@ public class RapVpnService extends VpnService { } } + private boolean hasNonIPv6RelayUrl(List urls) { + if (urls == null) { + return false; + } + for (String url : urls) { + if (url != null && !url.isEmpty() && !isIPv6URLHost(url)) { + return true; + } + } + return false; + } + private String selectReachablePacketRelayUrl(List relayUrls, String clusterId, String vpnConnectionId) { if (relayUrls == null || relayUrls.isEmpty()) { return ""; @@ -1190,11 +1305,13 @@ public class RapVpnService extends VpnService { interruptAndJoin(downlinkThread); interruptAndJoin(downlinkWriterThread); interruptAndJoin(runtimeWatchdogThread); + interruptAndJoin(diagnosticWatchdogThread); uplinkThread = null; uplinkSenderThreads = null; downlinkThread = null; downlinkWriterThread = null; runtimeWatchdogThread = null; + diagnosticWatchdogThread = null; uplinkWorkerCount = 0; downlinkFlowQueueCount = 0; uplinkQueues = null; @@ -1411,10 +1528,6 @@ public class RapVpnService extends VpnService { return; } long now = System.currentTimeMillis(); - if (now - lastDiagnosticEnsureAt >= 10000) { - lastDiagnosticEnsureAt = now; - ensureDiagnosticServiceHealthy(); - } int stale = staleTCPHandshakeCount(); if (stale <= 0) { continue; @@ -1440,6 +1553,29 @@ public class RapVpnService extends VpnService { } } + private void runDiagnosticServiceWatchdog() { + getSharedPreferences(PREFS, MODE_PRIVATE).edit() + .putLong("diagnostic_watchdog_started_at", System.currentTimeMillis()) + .putString("diagnostic_watchdog_last_action", "started") + .apply(); + while (running) { + try { + ensureDiagnosticServiceHealthy(); + Thread.sleep(DIAGNOSTIC_WATCHDOG_INTERVAL_MS); + } catch (InterruptedException e) { + if (!running) { + return; + } + } catch (Exception e) { + getSharedPreferences(PREFS, MODE_PRIVATE).edit() + .putLong("diagnostic_watchdog_last_ensure_at", System.currentTimeMillis()) + .putString("diagnostic_watchdog_last_action", "failed") + .putString("diagnostic_watchdog_last_error", e.getClass().getSimpleName() + ": " + e.getMessage()) + .apply(); + } + } + } + private boolean shouldHardRestartRuntime(long now) { if (runtimeWatchdogRecoveries.get() < 2) { return false; @@ -1636,14 +1772,13 @@ public class RapVpnService extends VpnService { System.arraycopy(packet, 0, copy, 0, length); if (!hasIPv4Source(copy, length)) { long mismatch = uplinkSourceMismatchPackets.incrementAndGet(); - String natKey = natKeyForOutboundReturn(copy, length); - if (natKey.isEmpty() || !rewriteIPv4SourceToVPN(copy, length, natKey)) { - Log.w(TAG, "vpn uplink source is not vpn address; dropping " + packetSummary(copy, length)); - writeRuntimeDetail("source_drop", packetSummary(copy, length), "uplink", -1, mismatch, "SOURCE_MISMATCH"); - recordUplinkDrop(length); - return; - } - writeRuntimeDetail("source_nat", packetSummary(copy, length), "uplink", -1, mismatch, "SOURCE_NAT"); + Log.w(TAG, "vpn uplink source is not vpn address; dropping " + packetSummary(copy, length)); + writeRuntimeDetail("source_drop", packetSummary(copy, length), "uplink", -1, mismatch, "SOURCE_MISMATCH"); + recordUplinkDrop(length); + return; + } + if (clampIPv4TCPMSS(copy, length, VPN_TCP_MSS_CLAMP)) { + writeRuntimeDetail("tcp_mss_clamp", packetSummary(copy, length), "uplink_tcp", -1, -1, ""); } recordOutboundTCPHandshake(copy, length); if (handleLocalDnsQuery(copy, length)) { @@ -1989,6 +2124,7 @@ public class RapVpnService extends VpnService { return; } String key = tcpFlowKey(flow.srcIp, flow.srcPort, flow.dstPort); + writeRuntimeDetail("tcp_syn_ack", key, "downlink_tcp", downlinkReceivedPackets.get(), tcpHandshakeStalls.get(), ""); synchronized (pendingTcpHandshakes) { if (pendingTcpHandshakes.containsKey(key)) { pendingTcpHandshakes.put(key, -System.currentTimeMillis()); @@ -2014,7 +2150,11 @@ public class RapVpnService extends VpnService { } private boolean isTCPPriorityPacket(byte[] packet, int length) { - if (packet == null || length < 40 || ((packet[0] >> 4) & 0x0f) != 4) { + return false; + } + + private boolean clampIPv4TCPMSS(byte[] packet, int length, int maxMss) { + if (packet == null || length < 40 || maxMss <= 0 || ((packet[0] >> 4) & 0x0f) != 4) { return false; } int ihl = (packet[0] & 0x0f) * 4; @@ -2025,17 +2165,47 @@ public class RapVpnService extends VpnService { if (totalLength <= 0 || totalLength > length) { totalLength = length; } - int tcpHeaderLength = ((packet[ihl + 12] >> 4) & 0x0f) * 4; - if (tcpHeaderLength < 20 || ihl + tcpHeaderLength > totalLength) { + int tcpOffset = ihl; + int tcpHeaderLength = ((packet[tcpOffset + 12] >> 4) & 0x0f) * 4; + if (tcpHeaderLength < 20 || tcpOffset + tcpHeaderLength > totalLength) { return false; } - int flags = packet[ihl + 13] & 0xff; + int flags = packet[tcpOffset + 13] & 0xff; boolean syn = (flags & 0x02) != 0; - boolean fin = (flags & 0x01) != 0; - boolean rst = (flags & 0x04) != 0; boolean ack = (flags & 0x10) != 0; - int payloadLength = totalLength - ihl - tcpHeaderLength; - return syn || fin || rst || (ack && payloadLength == 0); + if (!syn || ack || tcpHeaderLength <= 20) { + return false; + } + int option = tcpOffset + 20; + int end = tcpOffset + tcpHeaderLength; + while (option < end) { + int kind = packet[option] & 0xff; + if (kind == 0) { + break; + } + if (kind == 1) { + option++; + continue; + } + if (option + 1 >= end) { + break; + } + int optionLength = packet[option + 1] & 0xff; + if (optionLength < 2 || option + optionLength > end) { + break; + } + if (kind == 2 && optionLength == 4) { + int current = u16(packet, option + 2); + if (current > maxMss) { + putU16(packet, option + 2, maxMss); + normalizeIPv4PacketChecksums(packet, length); + return true; + } + return false; + } + option += optionLength; + } + return false; } private String tcpFlowKey(String remoteIp, int remotePort, int localPort) { @@ -2246,7 +2416,8 @@ public class RapVpnService extends VpnService { } recordUplinkDrop(Math.max(0, batchBytes - 4)); writeRuntimeStatus("degraded", "uplink send failed after retry; continuing", 0, sentPackets, 0, errors); - writeRuntimeDetail("error", "uplink send failed after retry batch=" + batch.size(), "uplink_sender", sentPackets, errors, "SEND_RETRY_EXHAUSTED", workerIndex); + String retryError = lastUplinkSendErrorMessage == null || lastUplinkSendErrorMessage.isEmpty() ? "" : " last_error=" + lastUplinkSendErrorMessage; + writeRuntimeDetail("error", "uplink send failed after retry batch=" + batch.size() + retryError, "uplink_sender", sentPackets, errors, "SEND_RETRY_EXHAUSTED", workerIndex); continue; } sentPackets += batch.size(); @@ -2289,6 +2460,7 @@ public class RapVpnService extends VpnService { private boolean sendUplinkBatchWithRetry(String clusterId, String vpnConnectionId, List batch, int workerIndex) { Exception lastError = null; + lastUplinkSendErrorMessage = ""; int relayAttempts = Math.max(1, activePacketRelayUrlsByProfile == null ? 1 : activePacketRelayUrlsByProfile.size()); for (int relayAttempt = 0; relayAttempt < relayAttempts && running; relayAttempt++) { String relayUrl = currentPacketRelayUrl(); @@ -2308,7 +2480,8 @@ public class RapVpnService extends VpnService { return true; } catch (Exception e) { lastError = e; - writeRuntimeDetail("retry", "uplink send retry worker=" + workerIndex + " relay=" + relayUrl + " attempt=" + attempt + " error=" + e.getClass().getSimpleName(), "uplink_sender", -1, -1, e.getClass().getSimpleName(), workerIndex); + lastUplinkSendErrorMessage = compactException(e); + writeRuntimeDetail("retry", "uplink send retry worker=" + workerIndex + " relay=" + relayUrl + " attempt=" + attempt + " error=" + lastUplinkSendErrorMessage, "uplink_sender", -1, -1, e.getClass().getSimpleName(), workerIndex); sleepQuietly(UPLINK_SEND_RETRY_SLEEP_MS * (attempt + 1L)); } } @@ -2323,6 +2496,9 @@ public class RapVpnService extends VpnService { } private boolean sendUplinkBatchOverWebSocket(String relayUrl, String clusterId, String vpnConnectionId, List batch, int workerIndex) { + if (!PACKET_WEBSOCKET_DATAPLANE_ENABLED) { + return false; + } VpnPacketWebSocketRelay relay = packetWebSocketRelay; if (relay == null || relayUrl == null || !relayUrl.equals(relay.baseUrl())) { return false; @@ -2555,6 +2731,7 @@ public class RapVpnService extends VpnService { } addDefaultDnsProbeDomains(domains); int resolved = 0; + int tcpOk = 0; String last = ""; long warmUntil = System.currentTimeMillis() + 30000; int pass = 0; @@ -2579,7 +2756,14 @@ public class RapVpnService extends VpnService { } sleepQuietly(120); } - if (passResolved >= Math.min(3, domains.size()) && pass >= 2) { + if (pass <= VPN_TCP_WARMUP_PASSES) { + int passTcpOk = runTCPWarmupPass(vpn); + tcpOk += passTcpOk; + if (passTcpOk > 0) { + last = "tcp_warmup_ok=" + passTcpOk; + } + } + if (passResolved >= Math.min(3, domains.size()) && tcpOk > 0 && pass >= 2) { break; } sleepQuietly(750); @@ -2588,15 +2772,60 @@ public class RapVpnService extends VpnService { return; } if (resolved > 0) { - writeRuntimeStatus("ready", "vpn ready; dns warmup ok " + resolved + " " + dnsInfo + " " + last, 0, 0, downlinkReceivedPackets.get(), 0); + writeRuntimeStatus("ready", "vpn ready; warmup dns=" + resolved + " tcp=" + tcpOk + " " + dnsInfo + " " + last, 0, 0, downlinkReceivedPackets.get(), 0); } else { - writeRuntimeStatus("warming", "vpn started; dns warmup pending " + dnsInfo + " " + last, 0, 0, downlinkReceivedPackets.get(), 1); + writeRuntimeStatus("warming", "vpn started; warmup pending dns=0 tcp=" + tcpOk + " " + dnsInfo + " " + last, 0, 0, downlinkReceivedPackets.get(), 1); } - Log.i(TAG, "vpn readiness warmup complete: connection=" + vpnConnectionId + " resolved=" + resolved + " " + dnsInfo + " " + last); + Log.i(TAG, "vpn readiness warmup complete: connection=" + vpnConnectionId + " resolved=" + resolved + " tcp=" + tcpOk + " " + dnsInfo + " " + last); }, "rap-vpn-readiness-warmup"); thread.start(); } + private int runTCPWarmupPass(Network vpn) { + int ok = 0; + for (String target : DEFAULT_TCP_WARMUP_TARGETS) { + if (!running) { + return ok; + } + TCPWarmupTarget parsed = parseTCPWarmupTarget(target); + if (parsed == null) { + continue; + } + long started = System.currentTimeMillis(); + try (Socket socket = new Socket()) { + if (vpn != null) { + vpn.bindSocket(socket); + } + socket.connect(new InetSocketAddress(parsed.host, parsed.port), VPN_TCP_WARMUP_CONNECT_TIMEOUT_MS); + ok++; + writeRuntimeDetail("tcp_warmup", target + " ms=" + (System.currentTimeMillis() - started), "readiness", ok, 0, "", -1); + } catch (Exception e) { + writeRuntimeDetail("tcp_warmup_failed", target + " " + e.getClass().getSimpleName(), "readiness", ok, 1, e.getClass().getSimpleName(), -1); + } + sleepQuietly(120); + } + return ok; + } + + private TCPWarmupTarget parseTCPWarmupTarget(String target) { + if (target == null) { + return null; + } + int split = target.lastIndexOf(':'); + if (split <= 0 || split >= target.length() - 1) { + return null; + } + try { + int port = Integer.parseInt(target.substring(split + 1)); + if (port <= 0 || port > 65535) { + return null; + } + return new TCPWarmupTarget(target.substring(0, split), port); + } catch (NumberFormatException e) { + return null; + } + } + private static void addDefaultDnsProbeDomains(Set domains) { if (domains == null) { return; @@ -2724,6 +2953,9 @@ public class RapVpnService extends VpnService { } private List receiveDownlinkBatch(String relayUrl, RapApiClient client, String clusterId, String vpnConnectionId, int timeoutMs) throws Exception { + if (!PACKET_WEBSOCKET_DATAPLANE_ENABLED) { + return client.receiveClientPacketBatch(clusterId, vpnConnectionId, timeoutMs); + } VpnPacketWebSocketRelay relay = packetWebSocketRelay; if (relay != null && relayUrl != null && relayUrl.equals(relay.baseUrl())) { List packets = relay.receiveClientPacketBatch(clusterId, vpnConnectionId, timeoutMs); @@ -2890,6 +3122,16 @@ public class RapVpnService extends VpnService { } } + private static class TCPWarmupTarget { + final String host; + final int port; + + TCPWarmupTarget(String host, int port) { + this.host = host; + this.port = port; + } + } + private boolean writePacketToTun(FileDescriptor fd, byte[] packet, int packetLength) throws Exception { int offset = 0; int attempts = 0; @@ -2950,6 +3192,19 @@ public class RapVpnService extends VpnService { } } + private String compactException(Exception e) { + if (e == null) { + return ""; + } + String message = e.getMessage(); + String value = e.getClass().getSimpleName() + (message == null || message.trim().isEmpty() ? "" : ": " + message.trim()); + value = value.replace('\n', ' ').replace('\r', ' ').trim(); + if (value.length() > 240) { + return value.substring(0, 240); + } + return value; + } + private void closeFdQuietly(FileDescriptor fd) { if (fd == null) { return; @@ -3064,6 +3319,8 @@ public class RapVpnService extends VpnService { .putLong("runtime_watchdog_recoveries", runtimeWatchdogRecoveries.get()) .putLong("tcp_handshake_stalls", tcpHandshakeStalls.get()) .putLong("runtime_watchdog_hard_restarts", runtimeWatchdogHardRestarts.get()) + .putLong("diagnostic_watchdog_ensure_requests", diagnosticEnsureRequests.get()) + .putLong("diagnostic_watchdog_restart_requests", diagnosticRestartRequests.get()) .putLong("uplink_source_mismatch_packets", uplinkSourceMismatchPackets.get()) .putLong("downlink_destination_mismatch_packets", downlinkDestinationMismatchPackets.get()) .putFloat("uplink_read_mbps", uplinkReadMbps) diff --git a/docs/architecture/CLUSTER_NODE_ADMIN_FOUNDATION.md b/docs/architecture/CLUSTER_NODE_ADMIN_FOUNDATION.md index ff67303..9476eb1 100644 --- a/docs/architecture/CLUSTER_NODE_ADMIN_FOUNDATION.md +++ b/docs/architecture/CLUSTER_NODE_ADMIN_FOUNDATION.md @@ -1250,6 +1250,162 @@ then reports the expected next event window without mailbox reads, drains, acks, or consumer cursor mutation. The live smoke is `scripts/fabric/c19z1-remote-workspace-mailbox-preflight-smoke.ps1`. +C19Z2 adds telemetry for mailbox preflight checks. Workload status and heartbeat +reports now expose preflight totals, ack/checkpoint split counters, and the last +preflight cursor/window fields so diagnostics can distinguish handoff checks +from mailbox reads. The live smoke is +`scripts/fabric/c19z2-remote-workspace-mailbox-preflight-telemetry-smoke.ps1`. + +C19Z3 adds stale-cursor diagnostics to mailbox preflight. If a consumer cursor +falls behind retained mailbox events after bounded-mailbox drops, preflight +reports retained sequence bounds, `stale_cursor`, `diagnostic_state`, and +`missing_dropped_count`; the latest stale state is also visible in telemetry and +readiness diagnostics. The live smoke is +`scripts/fabric/c19z3-remote-workspace-mailbox-stale-preflight-smoke.ps1`. + +C19Z4 adds action hints to mailbox preflight diagnostics. Stale cursor gaps now +return `recommended_action=reset_consumer_and_resync` plus hints to reset the +consumer cursor, request full adapter resync, and resume from checkpoint after +resync. The live smoke is +`scripts/fabric/c19z4-remote-workspace-mailbox-preflight-action-hints-smoke.ps1`. + +C19Z5 adds provenance for the selected preflight action. Responses, telemetry, +and readiness diagnostics include `action_reason` and structured +`action_context` with cursor, retained sequence bounds, dropped/missing counts, +and expected window values. The live smoke is +`scripts/fabric/c19z5-remote-workspace-mailbox-preflight-provenance-smoke.ps1`. + +C19Z6 adds the operator-facing summary for mailbox preflight. Responses, +telemetry, and readiness diagnostics include `operator_summary` plus compact +`operator_summary_fields` for the diagnostic state, selected action, reason, +cursor, retained bounds, and expected window counters. The live smoke is +`scripts/fabric/c19z6-remote-workspace-mailbox-preflight-summary-smoke.ps1`. + +C19Z7 adds machine-sortable operator severity for mailbox preflight. Responses, +telemetry, readiness diagnostics, and summary fields expose `operator_status` +and `operator_severity`, classifying ready windows, caught-up cursors, and +stale cursor gaps without parsing summary text. The live smoke is +`scripts/fabric/c19z7-remote-workspace-mailbox-preflight-severity-smoke.ps1`. + +C19Z8 adds the grouped readiness rollup for mailbox preflight. The readiness +diagnostic keeps the flat fields and adds `last_preflight` with observed time, +cursor, counts, diagnostic state, action hints/provenance, operator summary, +status, severity, and summary fields. The live smoke is +`scripts/fabric/c19z8-remote-workspace-mailbox-preflight-rollup-smoke.ps1`. + +C19Z9 adds retained-window detail to that preflight rollup. The grouped +`last_preflight` readiness object includes first/last retained sequence and +mailbox dropped total so stale cursor explanations are visible without opening +the raw preflight response. The live smoke is +`scripts/fabric/c19z9-remote-workspace-mailbox-preflight-retained-window-smoke.ps1`. + +C19Z10 adds a structured remediation checklist to that rollup. The grouped +`last_preflight.remediation_checklist` entries expose required/satisfied +operator steps derived from action hints, including cursor reset, full adapter +resync, and resume after resync for stale cursor gaps. The live smoke is +`scripts/fabric/c19z10-remote-workspace-mailbox-preflight-checklist-smoke.ps1`. + +C19Z11 adds checklist status and counts to that rollup. The grouped +`last_preflight` readiness object exposes `remediation_checklist_status` and +total/required/satisfied/pending counts for admin UI summaries. The live smoke +is +`scripts/fabric/c19z11-remote-workspace-mailbox-preflight-checklist-status-smoke.ps1`. + +C19Z12 adds session-level preflight operator status/severity counters. +Readiness exposes status and severity count maps, mirrored in `last_preflight`, +so repeated resync-required/warn preflights are visible without retaining a +history log. The live smoke is +`scripts/fabric/c19z12-remote-workspace-mailbox-preflight-status-counts-smoke.ps1`. + +C19Z13 adds compact preflight attention status on top of those counters. +Readiness and `last_preflight` expose `preflight_attention_status` so admin UI +can sort clean, attention-needed, and repeated-resync sessions without +interpreting count maps. The live smoke is +`scripts/fabric/c19z13-remote-workspace-mailbox-preflight-attention-smoke.ps1`. + +C19Z14 proves the repeated-resync attention branch. Unit and live smoke coverage +perform multiple stale preflights on one active adapter session and verify +`preflight_attention_status=repeated_resync_required` with repeated +resync-required/warn counters. The live smoke is +`scripts/fabric/c19z14-remote-workspace-mailbox-preflight-repeated-attention-smoke.ps1`. + +C19Z15 adds the preflight attention reason. Readiness and `last_preflight` +expose `preflight_attention_reason` beside the attention status, explaining +clean, attention-needed, and repeated-resync states without UI-side counter +parsing. The live smoke is +`scripts/fabric/c19z15-remote-workspace-mailbox-preflight-attention-reason-smoke.ps1`. + +C19Z16 completes focused proof coverage for those attention reasons. Unit tests +cover clean, single-resync, repeated-resync, and no-preflight mappings; live +smoke proves the single stale-preflight reason. The live smoke is +`scripts/fabric/c19z16-remote-workspace-mailbox-preflight-attention-reason-coverage-smoke.ps1`. + +C19Z17 adds the preflight diagnostics contract marker. The readiness +`last_preflight` rollup includes `diagnostics_schema_version` and +`diagnostics_contract` entries for retained-window, remediation-checklist, +attention, and operator-count fields, allowing UI rendering to be gated safely. +The live smoke is +`scripts/fabric/c19z17-remote-workspace-mailbox-preflight-contract-smoke.ps1`. + +C19Z18 adds boolean diagnostics feature flags to the same preflight rollup. +`last_preflight.diagnostics_features` now exposes retained-window, +remediation-checklist, attention, and operator-count support directly, so admin +UI and automation can gate each diagnostics group without scanning the contract +list. The live smoke is +`scripts/fabric/c19z18-remote-workspace-mailbox-preflight-feature-flags-smoke.ps1`. + +C19Z19 proves compatibility between the two diagnostics contract forms. Unit +coverage and live smoke verify that workload and telemetry reports expose both +the string `diagnostics_contract` entries and matching boolean +`diagnostics_features` flags for every preflight diagnostics group. The live +smoke is +`scripts/fabric/c19z19-remote-workspace-mailbox-preflight-contract-compatibility-smoke.ps1`. + +C19Z20 proves the no-preflight readiness shape. Before any mailbox preflight is +observed, active adapter sessions expose `preflight_attention_status=unknown`, +`preflight_attention_reason=no_preflight_observed`, zero session preflight +count, and no grouped `last_preflight` rollup. The live smoke is +`scripts/fabric/c19z20-remote-workspace-mailbox-preflight-absence-smoke.ps1`. + +C19Z21 proves the no-active-session readiness shape. After closing the active +adapter session, readiness exposes idle/not-ready state, zero active sessions, +no active `adapter_session_id`, no grouped `last_preflight`, and terminal +`last_session_state=closed` from the terminal-session ledger. The live smoke is +`scripts/fabric/c19z21-remote-workspace-no-active-session-readiness-smoke.ps1`. + +C19Z22 proves terminal-state readiness for `expire` and `reset` controls. The +same no-active-session readiness shape now reports +`last_session_state=expired` or `last_session_state=reset` from the +terminal-session ledger. The live smoke is +`scripts/fabric/c19z22-remote-workspace-terminal-state-readiness-smoke.ps1`. + +C19Z23 adds grouped terminal-session summary metadata to no-active-session +readiness. `terminal_session_summary` carries adapter session id, terminal +state, reason, and control timestamp so admin UI can render the terminal cause +without stitching flat fields. The live smoke is +`scripts/fabric/c19z23-remote-workspace-terminal-session-summary-smoke.ps1`. + +C19Z24 adds the terminal-session summary contract marker. The grouped summary +now carries schema version +`rap.remote_workspace_adapter_terminal_session_summary.v1` and a +summary-contract field list for explicit UI gating. The live smoke is +`scripts/fabric/c19z24-remote-workspace-terminal-summary-contract-smoke.ps1`. + +C19Z25 adds boolean `summary_features` to the same grouped terminal-session +summary, covering adapter session id, state, reason, and control timestamp. The +live smoke is +`scripts/fabric/c19z25-remote-workspace-terminal-summary-features-smoke.ps1`. + +C19Z26 proves compatibility between `summary_contract` and `summary_features` +for the grouped terminal-session summary in workload and telemetry reports. The +live smoke is +`scripts/fabric/c19z26-remote-workspace-terminal-summary-compatibility-smoke.ps1`. + +C19Z27 proves the absence shape for terminal-session summary. Before any adapter +session or terminal history exists, readiness reports `waiting_for_session` and +does not include `terminal_session_summary`. The live smoke is +`scripts/fabric/c19z27-remote-workspace-terminal-summary-absence-smoke.ps1`. + Includes: - container/native workload contract @@ -1671,9 +1827,234 @@ with synthetic traffic only. C18 defines the VPN/IP tunnel service target model authorize VPN/IP tunnel runtime. C18A adds the VPN/IP tunnel control-plane data model and platform-admin skeleton only. C18B hardens single-active lease/fencing semantics. C18C adds node-agent desired-state/status reporting -for scoped VPN assignments only. C19 is now reserved for the Version -Storage/Update Repository and node-agent update/rollback foundation; it is not -implemented by this document. No RDP, data-plane, VPN runtime, production -relay, production mesh service traffic, node-agent VPN execution, host -networking, service workload runtime, or production updater behavior is implied -by this document. +for scoped VPN assignments only. C19 Remote Workspace adapter probe layers are +still node-local and probe-only; through C19Z30, fresh no-session runtime +readiness exposes a grouped `no_session_summary` contract plus +`summary_features`, with compatibility proof across workload and telemetry, +while terminal-history readiness exposes `terminal_session_summary` and omits +`no_session_summary`; summary exclusivity is proven across fresh, active, and +terminal readiness states, and a compact readiness state matrix artifact exists +for admin/runtime handoff. C19Z34 records the explicit probe-to-runtime gates +and confirms Remote Workspace still has no production payload traffic. C19Z35 +adds the disabled-by-default real-adapter supervision status scaffold without +enabling real adapter execution. C19Z36 proves that scaffold's env/status/ +guardrail compatibility. C19Z37 adds sanitized config projection for the future +real adapter while still refusing activation and payload traffic. C19Z38 proves +that projection for both default/empty and requested config shapes. C19Z39 adds +an explicit blocked activation decision contract with required/missing gates. +C19Z40 adds a compact handoff report proving scaffold/projection/decision +alignment for requested and default node config. +C19Z41 adds explicit feature flags for those real-adapter supervision fields. +C19Z42 folds those feature flags into the compact handoff report for +admin/runtime handoff. +C19Z43 proves contract-probe precedence when real-adapter supervision is also +requested in desired workload config. +C19Z44 proves the real-adapter-only desired workload path remains degraded and +blocked. +C19Z45 adds a compact desired-workload mode matrix for probe-only, +real-adapter-only, and combined requested modes. +C19Z46 adds compatibility proof for the mode matrix row contract. +C19Z47 adds a disabled process-supervisor preconditions contract for future +external RDP worker supervision. +C19Z48 proves that contract across requested/default config shapes. +C19Z49 folds process-supervisor preconditions into the compact handoff report. +C19Z50 folds process-supervisor preconditions into the desired-workload mode +matrix. +C19Z51 proves the mode matrix v2 row contract. +C19Z52 adds a disabled process-health-probe contract for future external RDP +worker supervision. +C19Z53 proves that process-health-probe contract across requested/default +status forms. +C19Z54 folds process-health-probe visibility into the compact real-adapter +handoff report. +C19Z55 folds process-health-probe visibility into the desired-workload mode +matrix. +C19Z56 proves the mode matrix v3 row contract. +C19Z57 adds a compact disabled real-adapter readiness/handoff checklist. +C19Z58 proves the readiness/handoff summary and checklist contract. +C19Z59 adds a disabled real-adapter operator action map. +C19Z60 proves the disabled real-adapter operator action map contract. +C19Z61 adds a compact disabled real-adapter admin handoff bundle. +C19Z62 proves the disabled real-adapter admin handoff bundle contract. +C19Z63 adds compact disabled real-adapter admin handoff digest rows. +C19Z64 proves the disabled real-adapter admin handoff digest row contract. +C19Z65 adds a disabled real-adapter admin handoff digest rollup. +C19Z66 proves the disabled real-adapter admin handoff digest rollup contract. +C19Z67 adds a disabled real-adapter admin handoff full-chain summary. +C19Z68 proves the disabled real-adapter admin handoff full-chain summary +contract. +C19Z69 adds a disabled real-adapter admin handoff release marker. +C19Z70 proves the disabled real-adapter admin handoff release marker contract. +C19Z71 adds a final contract-only package index for the disabled real-adapter +admin handoff chain. +C19Z72 proves the final package index contract for the disabled real-adapter +admin handoff chain. +C19Z73 adds a contract-only runtime gate phase boundary for the next disabled +real-adapter preflight phase. +C19Z74 proves the runtime gate phase boundary contract. +C19Z75 adds a disabled real-adapter runtime gate preflight checklist with all +items still blocking runtime. +C19Z76 proves the disabled real-adapter runtime gate preflight checklist +contract. +C19Z77 adds a disabled real-adapter runtime gate preflight status summary. +C19Z78 proves the disabled real-adapter runtime gate preflight status summary +contract. +C19Z79 adds disabled real-adapter runtime gate preflight action hints. +C19Z80 proves the disabled real-adapter runtime gate preflight action hints +contract. +C19Z81 adds a disabled real-adapter runtime gate preflight operator handoff +bundle. +C19Z82 proves the disabled real-adapter runtime gate preflight operator handoff +bundle contract. +C19Z83 adds a disabled real-adapter runtime gate preflight release marker. +C19Z84 proves the disabled real-adapter runtime gate preflight release marker +contract. +C19Z85 adds a disabled real-adapter runtime gate preflight package index. +C19Z86 proves the disabled real-adapter runtime gate preflight package index +contract. +C19Z87 adds a disabled real-adapter runtime gate preflight closeout summary. +C19Z88 proves the disabled real-adapter runtime gate preflight closeout summary +contract. +C19Z89 starts the explicit real-adapter runtime gate enablement phase with a +contract-only request that remains blocked pending validation. +C19Z90 proves the explicit real-adapter runtime gate enablement request +contract. +C19Z91 adds contract-only operator confirmation validation while keeping the +runtime gate blocked pending remaining validations. +C19Z92 proves the operator confirmation validation contract. +C19Z93 adds contract-only binary validation while keeping the runtime gate +blocked pending remaining validations. +C19Z94 proves the binary validation contract. +C19Z95 adds contract-only permission validation while keeping the runtime gate +blocked pending remaining validations. +C19Z96 proves the permission validation contract. +C19Z97 adds contract-only supervisor validation while keeping the runtime gate +blocked pending remaining validations. +C19Z98 proves the supervisor validation contract. +C19Z99 adds contract-only health probe validation while keeping the runtime gate +blocked pending payload gate validation. +C19Z100 proves the health probe validation contract. +C19Z101 adds contract-only payload gate validation with no remaining required +validations while keeping runtime not enabled. +C19Z102 proves the payload gate validation contract. +C19Z103 adds the runtime gate validation closeout while keeping explicit +operator enablement required. +C19Z104 proves the runtime gate validation closeout contract. +C19Z105 adds an operator enablement readiness package while keeping runtime +disabled by default. +C19Z106 proves the operator enablement readiness package contract. +C19Z107 adds an operator enablement readiness release marker while keeping +runtime disabled by default. +C19Z108 proves the operator enablement readiness release marker contract. +C19Z109 adds an operator enablement readiness package index while keeping +runtime disabled by default. +C19Z110 proves the operator enablement readiness package index contract. +C19Z111 adds an operator readiness closeout summary while keeping runtime +disabled by default. +C19Z112 proves the operator readiness closeout summary contract. +C19Z113 adds an operator review decision request while keeping runtime disabled +by default. +C19Z114 proves the operator review decision request contract. +C19Z115 adds an operator decision status summary while keeping runtime disabled +by default. +C19Z116 proves the operator decision status summary contract. +C19Z117 adds an operator approval/rejection outcome contract with the outcome +not approved and runtime disabled by default. +C19Z118 proves the operator approval/rejection outcome contract. +C19Z119 adds an operator outcome closeout/reopen boundary while keeping runtime +disabled by default. +C19Z120 proves the operator outcome closeout/reopen boundary contract. +C19Z121 adds a not-approved outcome release marker while keeping runtime +disabled by default. +C19Z122 proves the not-approved outcome release marker contract. +C19Z123 adds a not-approved outcome package index while keeping runtime disabled +by default. +C19Z124 proves the not-approved outcome package index contract. +C19Z125 adds a not-approved outcome closeout summary while keeping runtime +disabled by default. +C19Z126 proves the not-approved outcome closeout summary contract. +C19Z127 adds a final not-approved outcome release marker while keeping runtime +disabled by default. +C19Z128 proves the final not-approved outcome release marker contract. +C19Z129 adds a final not-approved outcome package index/archive marker while +keeping runtime disabled by default. +C19Z130 proves the final not-approved outcome package index/archive marker +contract. +C19Z131 adds a not-approved outcome archive closeout manifest while keeping +runtime disabled by default. +C19Z132 proves the not-approved outcome archive closeout manifest contract. +C19Z133 adds a stopped-branch sentinel for the not-approved outcome while +keeping runtime disabled by default. +C19Z134 proves the not-approved outcome stopped-branch sentinel contract. +C19Z135 adds a no-continuation guard for the stopped not-approved outcome while +keeping runtime disabled by default. +C19Z136 proves the not-approved outcome no-continuation guard contract. +C19Z137 adds continuation block enforcement for the stopped not-approved +outcome while keeping runtime disabled by default. +C19Z138 proves the not-approved outcome continuation block enforcement +contract. +C19Z139 adds a continuation block audit record for the stopped not-approved +outcome while keeping runtime disabled by default. +C19Z140 proves the not-approved outcome continuation block audit record +contract. +C19Z141 adds a continuation block audit rollup for the stopped not-approved +outcome while keeping runtime disabled by default. +C19Z142 proves the not-approved outcome continuation block audit rollup +contract. +C19Z143 adds an operator stop summary for the stopped not-approved outcome +while keeping runtime disabled by default. +C19Z144 proves the not-approved outcome operator stop summary contract. +C19Z145 adds an operator stop handoff for the stopped not-approved outcome +while keeping runtime disabled by default. +C19Z146 proves the not-approved outcome operator stop handoff contract. +C19Z147 adds an operator stop handoff digest for the stopped not-approved +outcome while keeping runtime disabled by default. +C19Z148 proves the not-approved outcome operator stop handoff digest contract. +C19Z149 adds an operator stop status snapshot for the stopped not-approved +outcome while keeping runtime disabled by default. +C19Z150 proves the not-approved outcome operator stop status snapshot contract. +C19Z151 adds an operator stop status snapshot index for the stopped +not-approved outcome while keeping runtime disabled by default. +C19Z152 proves the not-approved outcome operator stop status snapshot index +contract. +C19Z153 adds an operator stop status catalog for the stopped not-approved +outcome while keeping runtime disabled by default. +C19Z154 proves the not-approved outcome operator stop status catalog contract. +C19Z155 adds an operator stop status catalog release marker for the stopped +not-approved outcome while keeping runtime disabled by default. +C19Z156 proves the not-approved outcome operator stop status catalog release +marker contract. +C19Z157 adds an operator stop status catalog package index for the stopped +not-approved outcome while keeping runtime disabled by default. +C19Z158 proves the not-approved outcome operator stop status catalog package +index contract. +C19Z159 adds an operator stop status catalog closeout summary for the stopped +not-approved outcome while keeping runtime disabled by default. +C19Z160 proves the not-approved outcome operator stop status catalog closeout +summary contract. +C19Z161 adds an operator stop status final archive marker for the stopped +not-approved outcome while keeping runtime disabled by default. +C19Z162 proves the not-approved outcome operator stop status final archive +marker contract. +C19Z163 adds an operator stop status final archive manifest for the stopped +not-approved outcome while keeping runtime disabled by default. +C19Z164 proves the not-approved outcome operator stop status final archive +manifest contract. +C19Z165 adds a terminal-complete marker for the stopped not-approved outcome +factory while keeping runtime disabled by default. +C19Z166 proves the not-approved outcome factory terminal-complete contract. +C20Z1 opens a new explicit real-adapter enablement request while keeping +runtime disabled by default. +C20Z2 proves the new explicit real-adapter enablement request contract. +C20Z3 adds the operator validation intake for the new explicit request while +keeping runtime disabled by default. +C20Z4 completes the operator validation checklist contract while keeping +runtime disabled by default. +C20Z5 closes the operator validation chain contract while keeping runtime +disabled by default. +C20Z6 proves the C20 stage terminal-complete contract. +Version Storage/Update +Repository and node-agent update/rollback foundation are not implemented by +this document. No RDP, data-plane, VPN runtime, production relay, production +mesh service traffic, node-agent VPN execution, host networking, service +workload runtime, or production updater behavior is implied by this document. diff --git a/docs/architecture/FABRIC_SERVICE_CHANNEL_RUNTIME.md b/docs/architecture/FABRIC_SERVICE_CHANNEL_RUNTIME.md index cd8f1cc..1b74c14 100644 --- a/docs/architecture/FABRIC_SERVICE_CHANNEL_RUNTIME.md +++ b/docs/architecture/FABRIC_SERVICE_CHANNEL_RUNTIME.md @@ -1324,6 +1324,394 @@ C19Z1 adds a read-only mailbox handoff preflight endpoint. Adapter runtimes can call `/mailbox/preflight` with `consumer_id` and `resume_from=ack|checkpoint` to validate the stored cursor and inspect the next expected event window without reading, draining, acking, or mutating consumer state. +C19Z2 adds separate telemetry for those handoff checks. Workload status and +heartbeat reports expose preflight totals split by ack/checkpoint cursor and the +last preflight session, consumer, cursor, after-sequence, available/returned/ +skipped counts, and expected sequence range; readiness diagnostics mirror the +latest preflight summary. +C19Z3 adds stale-cursor diagnostics to preflight. When a consumer cursor points +behind dropped bounded-mailbox events, the preflight response reports retained +sequence bounds, `diagnostic_state=stale_cursor_gap`, `stale_cursor=true`, and +`missing_dropped_count`; workload/heartbeat telemetry and readiness diagnostics +mirror that latest stale state. +C19Z4 adds explicit action hints to those diagnostics. Preflight responses now +include `recommended_action` and `action_hints`; stale cursor gaps recommend +resetting the consumer cursor, requesting a full adapter resync, and resuming +from checkpoint after resync. Telemetry and readiness diagnostics mirror the +latest recommended action and hints. +C19Z5 adds remediation provenance for those hints. Preflight responses, +workload/heartbeat telemetry, and readiness diagnostics include +`action_reason` plus structured `action_context` with the resume cursor, +retained sequence bounds, dropped/missing counts, consumer checkpoint/ack, and +expected window counters that explain why the recommended action was chosen. +C19Z6 adds a compact operator-facing preflight summary derived from the same +read-only state. Preflight responses, telemetry, and readiness diagnostics now +include `operator_summary` and `operator_summary_fields` so dashboards can show +the diagnostic state, action, reason, resume cursor, retained bounds, and key +window counters without recomputing or mutating mailbox state. +C19Z7 adds machine-sortable operator status and severity to that summary. +Preflight responses, telemetry, readiness diagnostics, and +`operator_summary_fields` now expose `operator_status` and `operator_severity` +so dashboards can sort ready, caught-up, and resync-required handoffs without +parsing human text. +C19Z8 groups the latest preflight view for admin UI consumption. The readiness +diagnostic keeps all existing flat latest-preflight fields and adds +`last_preflight` with observed time, cursor, counts, diagnostic state, selected +action, action provenance, operator summary, status, severity, and summary +fields. +C19Z9 adds retained-window detail to that grouped readiness view. The +`last_preflight` object now includes first/last retained sequence and mailbox +dropped total so stale-cursor summaries can explain the bounded mailbox window +without requiring a separate raw preflight lookup. +C19Z10 adds a structured remediation checklist to the grouped readiness view. +The `last_preflight.remediation_checklist` entries are derived from diagnostic +state and action hints, marking required/satisfied operator steps for cursor +reset, adapter resync, and post-resync resume without executing those actions. +C19Z11 adds summary status and counts for that checklist. The grouped readiness +view now exposes `remediation_checklist_status` plus total, required, +satisfied, and pending counts so admin UI can render checklist state without +scanning the step array. +C19Z12 adds per-session preflight operator status/severity counters. Readiness +now exposes counts for statuses such as `ready_to_resume`, `caught_up`, and +`resync_required`, plus severity counts such as `ok`, `info`, and `warn`, and +the grouped latest-preflight rollup mirrors those counters for dashboard +context. +C19Z13 derives a compact preflight attention status from those counters. +Readiness and `last_preflight` expose `preflight_attention_status` values such +as `clean`, `needs_attention`, and `repeated_resync_required`, letting admin UI +sort sessions without interpreting count maps directly. +C19Z14 proves the repeated-resync branch. Unit and live smoke coverage now run +multiple stale preflights on the same active adapter session and verify +`preflight_attention_status=repeated_resync_required` with repeated +`resync_required` / `warn` counters, while the preflight path remains read-only. +C19Z15 adds `preflight_attention_reason` beside the attention status. The reason +is derived from the latest preflight counters/status and explains clean, +attention-needed, and repeated-resync states without requiring UI code to parse +the counter maps. +C19Z16 completes focused proof coverage for those reasons. Unit coverage proves +clean, single-resync, repeated-resync, and no-preflight mappings, and live smoke +proves the single stale-preflight `resync_required_preflight_observed` reason. +C19Z17 adds a diagnostics contract marker to the grouped preflight readiness +rollup. `last_preflight` now includes `diagnostics_schema_version` and a +`diagnostics_contract` list for retained-window, remediation-checklist, +attention, and operator-count fields so admin UI can gate rendering safely. +C19Z18 adds machine-readable feature flags for that contract. `last_preflight` +now includes boolean `diagnostics_features` entries for retained-window, +remediation-checklist, attention, and operator-count diagnostics, allowing UI +and automation clients to check support without scanning the contract list. +C19Z19 adds a compatibility proof for the two contract forms. Unit and live +smoke coverage now verify that workload and telemetry reports expose matching +`diagnostics_contract` entries and `diagnostics_features` booleans for each +preflight diagnostics group. +C19Z20 adds the no-preflight absence proof. Active adapter sessions that have +not observed a mailbox preflight report `preflight_attention_status=unknown`, +`preflight_attention_reason=no_preflight_observed`, zero session preflight +count, and no grouped `last_preflight` rollup, so UI can distinguish "not +observed yet" from an observed clean state. +C19Z21 adds the no-active-session readiness proof. After the last adapter +session is closed, readiness reports idle/not-ready with zero active sessions, +no active `adapter_session_id`, no `last_preflight` rollup, and terminal +`last_session_state=closed` from the terminal-session ledger. +C19Z22 extends terminal-state coverage to `expire` and `reset` controls. The +same no-active-session readiness shape now proves `last_session_state=expired` +and `last_session_state=reset` from the terminal-session ledger. +C19Z23 adds grouped terminal-session summary metadata for the no-active-session +case. Readiness now includes `terminal_session_summary` with adapter session id, +terminal state, reason, and control timestamp while retaining flat compatibility +fields. +C19Z24 adds a contract marker to that summary. The grouped +`terminal_session_summary` now carries a schema version and summary-contract +field list so UI can gate rendering explicitly. +C19Z25 adds boolean feature flags for the same grouped terminal summary fields, +mirroring the preflight diagnostics contract/feature pattern. +C19Z26 adds compatibility proof coverage for those two terminal summary contract +forms, verifying that `summary_contract` entries and `summary_features` booleans +stay aligned in workload and telemetry reports. +C19Z27 adds absence proof coverage for a fresh no-session runtime: before any +terminal history exists, readiness stays in `waiting_for_session` and does not +include `terminal_session_summary`. +C19Z28 adds the grouped no-session readiness summary for that empty-runtime +state. Fresh adapter readiness now includes `no_session_summary` with schema +version `rap.remote_workspace_adapter_no_session_summary.v1`, a summary +contract for `status`, `diagnostic_state`, `active_session_count`, and +`terminal_session_count`, and matching idle/waiting-for-session counts, while +the terminal-session summary remains absent until terminal history exists. +C19Z29 adds boolean `summary_features` to the same grouped no-session summary +for `status`, `diagnostic_state`, `active_session_count`, and +`terminal_session_count`, matching the terminal summary and preflight +diagnostics feature-flag convention. +C19Z30 adds compatibility proof coverage for the grouped no-session summary, +verifying that `summary_contract` entries and `summary_features` booleans stay +aligned in workload and telemetry reports. +C19Z31 adds the inverse terminal-history absence proof: after adapter sessions +reach terminal states, readiness exposes `terminal_session_summary` and omits +`no_session_summary` in workload and telemetry reports. +C19Z32 proves readiness summary exclusivity across the three runtime shapes: +fresh exposes only `no_session_summary`, active exposes neither grouped summary, +and terminal exposes only `terminal_session_summary`. +C19Z33 adds a compact readiness state matrix artifact for admin/runtime handoff: +fresh, active, and terminal rows are emitted for workload and telemetry with +only the relevant readiness fields and summary-presence booleans. +C19Z34 adds an explicit probe-to-runtime gate artifact. It confirms the current +Remote Workspace runtime is still `contract_probe`, `probe_only=true`, and +`payload_traffic=none`, lists the ready contracts, and records the remaining +runtime gates before real RDP frame transport can be enabled. +C19Z35 adds the disabled-by-default real-adapter supervision scaffold. The +`rdp-worker` contract-probe status now advertises +`rap.remote_workspace_real_adapter_supervision.v1` with future config env names, +status contract fields, and guardrails, while `contract_probe` remains the only +active execution mode and payload traffic remains `none`. +C19Z36 adds compatibility proof for that scaffold, verifying the disabled state, +status contract, env names, process model, and guardrails remain aligned in unit +and live workload status coverage. +C19Z37 adds disabled real-adapter config projection. Node-agent parses the +future `RAP_REMOTE_WORKSPACE_REAL_ADAPTER_*` env values and reports only +sanitized status metadata under +`real_adapter_supervision.config_projection`: whether enable was requested, +whether command/args/workdir are present, args JSON shape, and that raw values +are redacted. This does not activate the real adapter; `enabled=false`, +`activation_allowed=false`, and `payload_traffic=none` remain required. +C19Z38 proves projection compatibility across default/empty and requested +config shapes. Unit and live smoke coverage verify absent env and requested +env both keep activation blocked, raw values redacted, and payload traffic +disabled. +C19Z39 adds an explicit disabled activation decision contract. The real adapter +status now reports `decision=blocked`, +`reason=real_runtime_stage_not_enabled`, `activation_allowed=false`, and the +missing gates before a future stage may start an external RDP worker process. +C19Z40 adds a compact handoff report proving that the supervision scaffold, +config projection, and blocked activation decision remain aligned for both +requested and default config shapes. +C19Z41 adds real-adapter supervision feature flags for config projection, +activation decision, missing gates, and raw-value redaction so UI and +automation clients can gate rendering explicitly. +C19Z42 folds those feature flags into the compact handoff report, proving +scaffold/projection/decision/features alignment for requested and default node +config in one admin/runtime artifact. +C19Z43 proves contract-probe precedence when desired workload config includes +both `adapter_contract_probe` and `real_adapter_supervision`; the runtime stays +running in probe mode and real-adapter activation remains blocked. +C19Z44 proves the real-adapter-only desired workload path remains degraded and +blocked, with the same disabled activation contract and no payload traffic. +C19Z45 adds a compact desired-workload mode matrix for probe-only, +real-adapter-only, and combined requested modes, confirming all paths retain +disabled real-adapter activation and no payload traffic. +C19Z46 adds compatibility proof for that mode matrix row contract, including +explicit feature-flag and missing-gate visibility markers. +C19Z47 adds a disabled process-supervisor preconditions contract for the future +external RDP worker process while keeping `process_start_allowed=false` and all +payload traffic disabled. +C19Z48 proves that process-supervisor preconditions contract across requested +and default config shapes, including required/missing checks and disabled start. +C19Z49 folds process-supervisor preconditions into the compact handoff report, +proving alignment with projection, activation decision, and feature flags. +C19Z50 folds those preconditions into the desired-workload mode matrix, proving +process start remains disabled across probe-only, real-adapter-only, and +combined requested modes. +C19Z51 adds compatibility proof for that mode matrix v2 row contract. +C19Z52 adds a disabled process-health-probe contract for the future external +RDP worker process while keeping health probes disabled and payload traffic at +`none`. +C19Z53 proves that process-health-probe contract across requested/default +status forms. +C19Z54 folds process-health-probe visibility into the compact handoff report, +proving disabled health probes and payload-free alignment across all +real-adapter handoff contracts. +C19Z55 folds process-health-probe visibility into the desired-workload mode +matrix, proving disabled health probes and no payload traffic across probe-only, +real-adapter-only, and combined requested modes. +C19Z56 adds compatibility proof for that mode matrix v3 row contract. +C19Z57 ties handoff v4 and mode matrix v3 compatibility into a compact disabled +real-adapter readiness/handoff checklist. +C19Z58 adds compatibility proof for that readiness/handoff summary and +checklist contract. +C19Z59 derives a disabled real-adapter operator action map from that checklist +while keeping activation, process start, and payload forwarding blocked. +C19Z60 adds compatibility proof for that operator action map contract. +C19Z61 groups the disabled real-adapter readiness summary, checklist, and +action map into one compact admin handoff bundle. +C19Z62 adds compatibility proof for that admin handoff bundle contract. +C19Z63 derives compact admin handoff digest display rows from the bundle while +preserving disabled runtime guardrails. +C19Z64 adds compatibility proof for that admin handoff digest row contract. +C19Z65 adds a digest rollup with severity/state counts, primary action, and +guardrail summary. +C19Z66 adds compatibility proof for that digest rollup contract. +C19Z67 summarizes the proven disabled real-adapter admin handoff chain from +handoff v4 through digest rollup compatibility. +C19Z68 adds compatibility proof for that full-chain summary contract. +C19Z69 marks the disabled real-adapter admin handoff package as +contract-only-ready while keeping the real runtime stage blocked. +C19Z70 proves the release marker contract remains compatible while keeping the +real runtime stage blocked. +C19Z71 adds a final contract-only package index for the disabled real-adapter +admin handoff chain. +C19Z72 proves the final package index contract for the disabled real-adapter +admin handoff chain. +C19Z73 adds a contract-only runtime gate phase boundary for the next disabled +real-adapter preflight phase. +C19Z74 proves the runtime gate phase boundary contract. +C19Z75 adds a disabled real-adapter runtime gate preflight checklist with all +items still blocking runtime. +C19Z76 proves the disabled real-adapter runtime gate preflight checklist +contract. +C19Z77 adds a disabled real-adapter runtime gate preflight status summary. +C19Z78 proves the disabled real-adapter runtime gate preflight status summary +contract. +C19Z79 adds disabled real-adapter runtime gate preflight action hints. +C19Z80 proves the disabled real-adapter runtime gate preflight action hints +contract. +C19Z81 adds a disabled real-adapter runtime gate preflight operator handoff +bundle. +C19Z82 proves the disabled real-adapter runtime gate preflight operator handoff +bundle contract. +C19Z83 adds a disabled real-adapter runtime gate preflight release marker. +C19Z84 proves the disabled real-adapter runtime gate preflight release marker +contract. +C19Z85 adds a disabled real-adapter runtime gate preflight package index. +C19Z86 proves the disabled real-adapter runtime gate preflight package index +contract. +C19Z87 adds a disabled real-adapter runtime gate preflight closeout summary. +C19Z88 proves the disabled real-adapter runtime gate preflight closeout summary +contract. +C19Z89 starts the explicit real-adapter runtime gate enablement phase with a +contract-only request that remains blocked pending validation. +C19Z90 proves the explicit real-adapter runtime gate enablement request +contract. +C19Z91 adds contract-only operator confirmation validation while keeping the +runtime gate blocked pending remaining validations. +C19Z92 proves the operator confirmation validation contract. +C19Z93 adds contract-only binary validation while keeping the runtime gate +blocked pending remaining validations. +C19Z94 proves the binary validation contract. +C19Z95 adds contract-only permission validation while keeping the runtime gate +blocked pending remaining validations. +C19Z96 proves the permission validation contract. +C19Z97 adds contract-only supervisor validation while keeping the runtime gate +blocked pending remaining validations. +C19Z98 proves the supervisor validation contract. +C19Z99 adds contract-only health probe validation while keeping the runtime gate +blocked pending payload gate validation. +C19Z100 proves the health probe validation contract. +C19Z101 adds contract-only payload gate validation with no remaining required +validations while keeping runtime not enabled. +C19Z102 proves the payload gate validation contract. +C19Z103 adds the runtime gate validation closeout while keeping explicit +operator enablement required. +C19Z104 proves the runtime gate validation closeout contract. +C19Z105 adds an operator enablement readiness package while keeping runtime +disabled by default. +C19Z106 proves the operator enablement readiness package contract. +C19Z107 adds an operator enablement readiness release marker while keeping +runtime disabled by default. +C19Z108 proves the operator enablement readiness release marker contract. +C19Z109 adds an operator enablement readiness package index while keeping +runtime disabled by default. +C19Z110 proves the operator enablement readiness package index contract. +C19Z111 adds an operator readiness closeout summary while keeping runtime +disabled by default. +C19Z112 proves the operator readiness closeout summary contract. +C19Z113 adds an operator review decision request while keeping runtime disabled +by default. +C19Z114 proves the operator review decision request contract. +C19Z115 adds an operator decision status summary while keeping runtime disabled +by default. +C19Z116 proves the operator decision status summary contract. +C19Z117 adds an operator approval/rejection outcome contract with the outcome +not approved and runtime disabled by default. +C19Z118 proves the operator approval/rejection outcome contract. +C19Z119 adds an operator outcome closeout/reopen boundary while keeping runtime +disabled by default. +C19Z120 proves the operator outcome closeout/reopen boundary contract. +C19Z121 adds a not-approved outcome release marker while keeping runtime +disabled by default. +C19Z122 proves the not-approved outcome release marker contract. +C19Z123 adds a not-approved outcome package index while keeping runtime disabled +by default. +C19Z124 proves the not-approved outcome package index contract. +C19Z125 adds a not-approved outcome closeout summary while keeping runtime +disabled by default. +C19Z126 proves the not-approved outcome closeout summary contract. +C19Z127 adds a final not-approved outcome release marker while keeping runtime +disabled by default. +C19Z128 proves the final not-approved outcome release marker contract. +C19Z129 adds a final not-approved outcome package index/archive marker while +keeping runtime disabled by default. +C19Z130 proves the final not-approved outcome package index/archive marker +contract. +C19Z131 adds a not-approved outcome archive closeout manifest while keeping +runtime disabled by default. +C19Z132 proves the not-approved outcome archive closeout manifest contract. +C19Z133 adds a stopped-branch sentinel for the not-approved outcome while +keeping runtime disabled by default. +C19Z134 proves the not-approved outcome stopped-branch sentinel contract. +C19Z135 adds a no-continuation guard for the stopped not-approved outcome while +keeping runtime disabled by default. +C19Z136 proves the not-approved outcome no-continuation guard contract. +C19Z137 adds continuation block enforcement for the stopped not-approved +outcome while keeping runtime disabled by default. +C19Z138 proves the not-approved outcome continuation block enforcement +contract. +C19Z139 adds a continuation block audit record for the stopped not-approved +outcome while keeping runtime disabled by default. +C19Z140 proves the not-approved outcome continuation block audit record +contract. +C19Z141 adds a continuation block audit rollup for the stopped not-approved +outcome while keeping runtime disabled by default. +C19Z142 proves the not-approved outcome continuation block audit rollup +contract. +C19Z143 adds an operator stop summary for the stopped not-approved outcome +while keeping runtime disabled by default. +C19Z144 proves the not-approved outcome operator stop summary contract. +C19Z145 adds an operator stop handoff for the stopped not-approved outcome +while keeping runtime disabled by default. +C19Z146 proves the not-approved outcome operator stop handoff contract. +C19Z147 adds an operator stop handoff digest for the stopped not-approved +outcome while keeping runtime disabled by default. +C19Z148 proves the not-approved outcome operator stop handoff digest contract. +C19Z149 adds an operator stop status snapshot for the stopped not-approved +outcome while keeping runtime disabled by default. +C19Z150 proves the not-approved outcome operator stop status snapshot contract. +C19Z151 adds an operator stop status snapshot index for the stopped +not-approved outcome while keeping runtime disabled by default. +C19Z152 proves the not-approved outcome operator stop status snapshot index +contract. +C19Z153 adds an operator stop status catalog for the stopped not-approved +outcome while keeping runtime disabled by default. +C19Z154 proves the not-approved outcome operator stop status catalog contract. +C19Z155 adds an operator stop status catalog release marker for the stopped +not-approved outcome while keeping runtime disabled by default. +C19Z156 proves the not-approved outcome operator stop status catalog release +marker contract. +C19Z157 adds an operator stop status catalog package index for the stopped +not-approved outcome while keeping runtime disabled by default. +C19Z158 proves the not-approved outcome operator stop status catalog package +index contract. +C19Z159 adds an operator stop status catalog closeout summary for the stopped +not-approved outcome while keeping runtime disabled by default. +C19Z160 proves the not-approved outcome operator stop status catalog closeout +summary contract. +C19Z161 adds an operator stop status final archive marker for the stopped +not-approved outcome while keeping runtime disabled by default. +C19Z162 proves the not-approved outcome operator stop status final archive +marker contract. +C19Z163 adds an operator stop status final archive manifest for the stopped +not-approved outcome while keeping runtime disabled by default. +C19Z164 proves the not-approved outcome operator stop status final archive +manifest contract. +C19Z165 adds a terminal-complete marker for the stopped not-approved outcome +factory while keeping runtime disabled by default. +C19Z166 proves the not-approved outcome factory terminal-complete contract. +C20Z1 opens a new explicit real-adapter enablement request while keeping +runtime disabled by default. +C20Z2 proves the new explicit real-adapter enablement request contract. +C20Z3 adds the operator validation intake for the new explicit request while +keeping runtime disabled by default. +C20Z4 completes the operator validation checklist contract while keeping +runtime disabled by default. +C20Z5 closes the operator validation chain contract while keeping runtime +disabled by default. +C20Z6 proves the C20 stage terminal-complete contract. 5. Move VPN packet flow to the service channel and keep backend relay only as explicit degraded fallback. 6. Run load tests against the fabric channel: many streams, route failure, diff --git a/docs/codex/NEXT_STEP_PROMPT.md b/docs/codex/NEXT_STEP_PROMPT.md index eaf5258..b170e13 100644 --- a/docs/codex/NEXT_STEP_PROMPT.md +++ b/docs/codex/NEXT_STEP_PROMPT.md @@ -586,25 +586,31 @@ artifacts: `artifacts/c18z108-dedicated-breadcrumbs-smoke-result.json`, and `artifacts/c18z109-breadcrumb-freshness-window-smoke-result.json`. -Current active continuation after C19Z1: +Current active continuation after C20Z6: -C19Z1 is implemented and runtime-smoke-proven. Remote Workspace adapter sessions -now expose read-only mailbox handoff preflight: -`GET /mesh/v1/remote-workspace/adapter-sessions/{adapter_session_id}/mailbox/preflight?consumer_id=...&resume_from=ack|checkpoint`. -The response validates the consumer cursor and reports the expected next event -window (`after_sequence`, available/returned/skipped counts, first/last expected -sequence) without reading, draining, acking, or mutating consumer state. -Node-agent image `rap-node-agent:codex-service-supervisor-20260512z2` is -deployed on `test-1/2/3`. Verification artifacts: -`artifacts/c19z1-remote-workspace-mailbox-preflight-smoke-result.json`, C19X -source -`artifacts/c19z1-remote-workspace-mailbox-preflight-source-result.json`, and -C19Z regression -`artifacts/c19z-remote-workspace-adapter-readiness-smoke-result.json`. +C20Z1 through C20Z6 are implemented and runtime-smoke-proven. The C20 stage is +terminal-complete by contract. It opened and validated a new explicit +real-adapter enablement request as a contract-only transition: +`rap.remote_workspace_real_adapter_c20_stage_terminal_complete.v1`, with +`terminal_status=stage_terminal_complete_contract_only`, +`stage_status=complete_no_more_c20_layers_required`, +`stage_name=c20_real_adapter_new_explicit_enablement_request`, +`validation_chain_status=complete_contract_only`, +`enablement_boundary=runtime_enablement_requires_next_explicit_runtime_stage`, +`enablement_decision=validated_contract_only_not_enabled`, +`enablement_status=validated_not_enabled`, +`runtime_gate_state=validated_contract_only_not_enabled`, +`runtime_effect=contract_only_no_runtime_enablement`, +`operator_default_action=keep_real_adapter_disabled_until_next_explicit_runtime_stage`, +`next_allowed_entrypoint=next_explicit_runtime_enablement_stage_only`, +`allows_process_start=false`, and `allows_payload_traffic=false`. Docker-test +`test-1/2/3` remain on +`rap-node-agent:codex-service-supervisor-20260513z52`. Verification artifact: +`artifacts/c20z6-remote-workspace-real-adapter-stage-terminal-complete-compatibility-smoke-result.json`. -Next narrow Remote Workspace layer should stay probe-only and node-local. A good -C19Z2 candidate is handoff preflight telemetry: add counters/last-preflight -fields for the read-only preflight endpoint in workload status/heartbeat reports, -so operators can distinguish handoff checks from mailbox reads. Do not add -desktop frame transport, Android work, backend relay semantics, or production -adapter payload forwarding in this slice. +The not-approved factory remains terminal-complete by contract, and C20 is now +also terminal-complete by contract. Do not add more C20 continuation layers. +The only allowed next entrypoint is a new explicit runtime enablement stage. +Keep the real adapter disabled until that new stage explicitly changes runtime +state: no process start, no real RDP frame transport, no Android work, no +backend relay semantics, and no production adapter payload forwarding. diff --git a/docs/ops/RAP_HOST_AGENT_MONITOR.md b/docs/ops/RAP_HOST_AGENT_MONITOR.md new file mode 100644 index 0000000..3e6a7b1 --- /dev/null +++ b/docs/ops/RAP_HOST_AGENT_MONITOR.md @@ -0,0 +1,35 @@ +# RAP host-agent monitor + +`rap-host-agent monitor-loop` is the local watchdog that runs near a node host. +It complements the update loop: + +- starts watched Docker containers when they are stopped; +- restarts watched containers when Docker health is `unhealthy`; +- restarts containers stuck in `restarting` longer than the stale threshold; +- rate-limits repeated remediation with a restart cooldown; +- watches disk pressure and runs safe cleanup when the cleanup threshold is reached; +- removes old `/tmp/rap-*` and `/tmp/go-build*` build directories; +- writes an optional JSON status file; +- reports monitor status to the control plane through the node update-status channel. + +Example: + +```bash +rap-host-agent monitor-loop \ + --backend-url http://127.0.0.1:18121/api/v1 \ + --cluster-id cfc0743d-d960-49fb-9de8-96e063d5e4aa \ + --node-id 108a0d66-d65e-4dea-b9a8-135366bf7dba \ + --current-version 0.2.261-vpnfarm \ + --interval-seconds 60 \ + --disk-warn-percent 80 \ + --disk-cleanup-percent 85 \ + --disk-critical-percent 95 \ + --status-file /tmp/rap-web-admin/html/downloads/ops/host-monitor-status.json \ + --watch-container rap_test_postgres \ + --watch-container rap_test_redis \ + --watch-container rap_test_backend +``` + +On the shared test Docker host the current public status file is: + +`http://docker-test.cin.su:18080/downloads/ops/host-monitor-status.json` diff --git a/docs/ops/TEST_DOCKER_DISK_GUARD.md b/docs/ops/TEST_DOCKER_DISK_GUARD.md new file mode 100644 index 0000000..b95a11e --- /dev/null +++ b/docs/ops/TEST_DOCKER_DISK_GUARD.md @@ -0,0 +1,64 @@ +# Test Docker Disk Guard + +`test-docker` is a shared build and runtime host. If `/` fills up, Postgres can +restart-loop with `No space left on device`, which breaks VPN diagnostics and +cluster tests. The disk guard is the first operational guardrail for that host. + +## What It Does + +- Checks `/` usage every run. +- At `>= 85%`, removes safe reclaimable data: + - Docker build cache. + - Dangling Docker images. + - Old RAP temporary build directories under `/tmp`. +- At `>= 85%`, publishes a warning status after cleanup if the host is still above the warning line. +- At `>= 95%` after cleanup, publishes critical status and exits with code `2`. +- Writes machine-readable status to: + - `http://docker-test.cin.su:18080/downloads/ops/test-docker-disk-guard-status.json` +- Writes host log to: + - `/tmp/rap-ops/test-docker-disk-guard.log` + +## Install Or Refresh Schedule + +Run from the repo root on the Windows workstation: + +```powershell +pwsh -ExecutionPolicy Bypass -File scripts/ops/test-docker-disk-guard.ps1 -InstallCron -RunOnce +``` + +The wrapper uploads `scripts/ops/test-docker-disk-guard.sh` to +`/home/test/bin/rap-test-docker-disk-guard` on `test-docker`. It installs cron +when `crontab` exists; otherwise it installs a user systemd timer named +`rap-test-docker-disk-guard.timer`. + +## Manual Check + +```powershell +pwsh -ExecutionPolicy Bypass -File scripts/ops/test-docker-disk-guard.ps1 -RunOnce +Invoke-RestMethod http://docker-test.cin.su:18080/downloads/ops/test-docker-disk-guard-status.json +``` + +## Expansion Approach + +Cleanup is only a pressure valve. If the status remains `warning` or `critical` +after cleanup, expand the host disk. + +Current host root is expected to be LVM. If the VM already has free VG space, +the guard status will recommend: + +```bash +sudo lvextend -r -l +100%FREE /dev/mapper/ubuntu--vg-ubuntu--lv +``` + +If there is no VG free space, first expand the VM disk in the hypervisor, then +run `pvresize` for the physical volume and finally `lvextend -r` for the root +logical volume. + +## Optional Webhook + +The shell guard supports `WEBHOOK_URL`. If set in cron/environment, warning and +critical states are posted as JSON: + +```json +{"level":"warning","message":"...","host":"...","observed_at":"..."} +``` diff --git a/scripts/fabric/c19z10-remote-workspace-mailbox-preflight-checklist-smoke.ps1 b/scripts/fabric/c19z10-remote-workspace-mailbox-preflight-checklist-smoke.ps1 new file mode 100644 index 0000000..421cae9 --- /dev/null +++ b/scripts/fabric/c19z10-remote-workspace-mailbox-preflight-checklist-smoke.ps1 @@ -0,0 +1,101 @@ +param( + [string]$ApiBaseUrl = "http://192.168.200.61:18121/api/v1", + [string]$ClusterID = "cfc0743d-d960-49fb-9de8-96e063d5e4aa", + [string]$ActorUserID = "f67d943f-5397-4b3a-a229-695fe67ad700", + [string]$EntryNodeName = "test-1", + [string]$ExitNodeName = "test-2", + [string]$EntryBaseUrl = "http://192.168.200.61:19131", + [string]$ResultPath = "artifacts\c19z10-remote-workspace-mailbox-preflight-checklist-smoke-result.json" +) + +Set-StrictMode -Version Latest +$ErrorActionPreference = "Stop" + +$repoRoot = (Resolve-Path (Join-Path $PSScriptRoot "..\..")).ProviderPath +$sourceResultPath = "artifacts\c19z10-remote-workspace-mailbox-preflight-checklist-source-result.json" + +function Get-PropertyValue { + param([object]$Item, [string]$Name, [object]$Default = $null) + if ($null -eq $Item) { return $Default } + $property = $Item.PSObject.Properties[$Name] + if ($null -eq $property) { return $Default } + return $property.Value +} + +function Test-ChecklistStep { + param([object]$Checklist, [string]$Step) + foreach ($item in @($Checklist)) { + if ( + [string](Get-PropertyValue -Item $item -Name "step" -Default "") -eq $Step -and + [bool](Get-PropertyValue -Item $item -Name "required" -Default $false) -and + -not [bool](Get-PropertyValue -Item $item -Name "satisfied" -Default $true) -and + [bool](Get-PropertyValue -Item $item -Name "source_hint" -Default $false) + ) { + return $true + } + } + return $false +} + +function Test-PreflightChecklist { + param([object]$Sink, [string]$AdapterSessionID) + if ($null -eq $Sink) { return $false } + $readiness = Get-PropertyValue -Item $Sink -Name "adapter_runtime_readiness" -Default $null + $rollup = Get-PropertyValue -Item $readiness -Name "last_preflight" -Default $null + if ($null -eq $rollup) { return $false } + $checklist = Get-PropertyValue -Item $rollup -Name "remediation_checklist" -Default @() + return ( + [string](Get-PropertyValue -Item $readiness -Name "adapter_session_id" -Default "") -eq $AdapterSessionID -and + [string](Get-PropertyValue -Item $rollup -Name "operator_status" -Default "") -eq "resync_required" -and + [string](Get-PropertyValue -Item $rollup -Name "operator_severity" -Default "") -eq "warn" -and + @($checklist).Count -eq 3 -and + (Test-ChecklistStep -Checklist $checklist -Step "reset_consumer_cursor") -and + (Test-ChecklistStep -Checklist $checklist -Step "request_full_adapter_resync") -and + (Test-ChecklistStep -Checklist $checklist -Step "resume_from_checkpoint_after_resync") + ) +} + +$source = & powershell -ExecutionPolicy Bypass -File (Join-Path $PSScriptRoot "c19z9-remote-workspace-mailbox-preflight-retained-window-smoke.ps1") ` + -ApiBaseUrl $ApiBaseUrl ` + -ClusterID $ClusterID ` + -ActorUserID $ActorUserID ` + -EntryNodeName $EntryNodeName ` + -ExitNodeName $ExitNodeName ` + -EntryBaseUrl $EntryBaseUrl ` + -ResultPath $sourceResultPath + +$sourceFile = Join-Path $repoRoot $sourceResultPath +$sourceResult = Get-Content -Raw -Path $sourceFile | ConvertFrom-Json +$adapterSessionID = [string](Get-PropertyValue -Item $sourceResult -Name "adapter_session_id" -Default "") +$observed = Get-PropertyValue -Item $sourceResult -Name "observed" -Default $null + +$checks = [ordered]@{ + source_smoke_passed = ([bool]$sourceResult.passed) + adapter_session_id_present = ($adapterSessionID -match "^rap-rw-adapter-session-[0-9a-f]{24}$") + workload_checklist_visible = (Test-PreflightChecklist -Sink $observed.workload_sink -AdapterSessionID $adapterSessionID) + telemetry_checklist_visible = (Test-PreflightChecklist -Sink $observed.telemetry_sink -AdapterSessionID $adapterSessionID) +} +$failed = @($checks.GetEnumerator() | Where-Object { -not $_.Value } | ForEach-Object { $_.Key }) + +$result = [ordered]@{ + schema_version = "c19z10.remote_workspace_mailbox_preflight_checklist_smoke.v1" + source_result_path = $sourceFile + cluster_id = $ClusterID + adapter_session_id = $adapterSessionID + observed = $observed + checks = $checks + failed_checks = $failed + passed = ($failed.Count -eq 0) +} + +$fullResultPath = Join-Path $repoRoot $ResultPath +$resultDir = Split-Path -Parent $fullResultPath +if ($resultDir) { New-Item -ItemType Directory -Force -Path $resultDir | Out-Null } +$result | ConvertTo-Json -Depth 100 | Set-Content -Encoding UTF8 -Path $fullResultPath + +if (-not $result.passed) { + throw "C19Z10 remote workspace mailbox preflight checklist smoke failed. Result: $fullResultPath Failed: $($failed -join ', ')" +} + +Write-Host "C19Z10 remote workspace mailbox preflight checklist smoke passed. Result: $fullResultPath" +$result diff --git a/scripts/fabric/c19z100-remote-workspace-real-adapter-runtime-gate-health-probe-validation-compatibility-smoke.ps1 b/scripts/fabric/c19z100-remote-workspace-real-adapter-runtime-gate-health-probe-validation-compatibility-smoke.ps1 new file mode 100644 index 0000000..eff0b7f --- /dev/null +++ b/scripts/fabric/c19z100-remote-workspace-real-adapter-runtime-gate-health-probe-validation-compatibility-smoke.ps1 @@ -0,0 +1,145 @@ +param( + [string]$ApiBaseUrl = "http://192.168.200.61:18121/api/v1", + [string]$ClusterID = "cfc0743d-d960-49fb-9de8-96e063d5e4aa", + [string]$ActorUserID = "f67d943f-5397-4b3a-a229-695fe67ad700", + [string]$RequestedNodeName = "test-1", + [string]$DefaultNodeName = "test-2", + [string]$MatrixNodeName = "test-1", + [string]$ResultPath = "artifacts\c19z100-remote-workspace-real-adapter-runtime-gate-health-probe-validation-compatibility-smoke-result.json" +) + +Set-StrictMode -Version Latest +$ErrorActionPreference = "Stop" + +$repoRoot = (Resolve-Path (Join-Path $PSScriptRoot "..\..")).ProviderPath +$sourceResultPath = "artifacts\c19z100-remote-workspace-real-adapter-runtime-gate-health-probe-validation-source-result.json" +$requiredValidationFields = @( + "schema_version", + "source_supervisor_validation_schema", + "validation_key", + "validation_status", + "health_probe_validation_required", + "health_probe_contract_verified", + "failure_detection_verified", + "remaining_required_validations", + "runtime_gate_state", + "runtime_effect", + "operator_default_action", + "allows_process_start", + "allows_payload_traffic", + "guardrail_summary" +) +$remainingRequiredValidations = @( + "payload_gate_validation" +) +$requiredGuardrailFields = @("activation_blocked", "process_start_allowed", "health_probe_enabled", "payload_traffic", "allows_process_start", "allows_payload_traffic") + +function Get-PropertyValue { + param([object]$Item, [string]$Name, [object]$Default = $null) + if ($null -eq $Item) { return $Default } + if ($Item -is [System.Collections.IDictionary]) { + if ($Item.Contains($Name)) { return $Item[$Name] } + return $Default + } + $property = $Item.PSObject.Properties[$Name] + if ($null -eq $property) { return $Default } + return $property.Value +} + +function Test-ObjectHasFields { + param([object]$Item, [string[]]$Fields) + if ($null -eq $Item) { return $false } + foreach ($field in $Fields) { + if ($Item -is [System.Collections.IDictionary]) { + if (-not $Item.Contains($field)) { return $false } + continue + } + if ($null -eq $Item.PSObject.Properties[$field]) { return $false } + } + return $true +} + +function Test-ArrayContainsAll { + param([object[]]$Actual, [string[]]$Required) + $values = @($Actual | ForEach-Object { [string]$_ }) + foreach ($item in $Required) { + if ($values -notcontains $item) { return $false } + } + return $true +} + +& powershell -ExecutionPolicy Bypass -File (Join-Path $PSScriptRoot "c19z99-remote-workspace-real-adapter-runtime-gate-health-probe-validation-smoke.ps1") ` + -ApiBaseUrl $ApiBaseUrl ` + -ClusterID $ClusterID ` + -ActorUserID $ActorUserID ` + -RequestedNodeName $RequestedNodeName ` + -DefaultNodeName $DefaultNodeName ` + -MatrixNodeName $MatrixNodeName ` + -ResultPath $sourceResultPath | Out-Null + +$sourceFile = Join-Path $repoRoot $sourceResultPath +$sourceResult = Get-Content -Raw -Path $sourceFile | ConvertFrom-Json +$validation = Get-PropertyValue -Item $sourceResult -Name "runtime_gate_health_probe_validation" -Default $null +$guardrails = Get-PropertyValue -Item $validation -Name "guardrail_summary" -Default $null + +$validationFieldsCompatible = Test-ObjectHasFields -Item $validation -Fields $requiredValidationFields +$remainingValidationsCompatible = Test-ArrayContainsAll -Actual @(Get-PropertyValue -Item $validation -Name "remaining_required_validations" -Default @()) -Required $remainingRequiredValidations +$validationValuesCompatible = ( + [string](Get-PropertyValue -Item $validation -Name "schema_version" -Default "") -eq "rap.remote_workspace_real_adapter_runtime_gate_health_probe_validation.v1" -and + [string](Get-PropertyValue -Item $validation -Name "source_supervisor_validation_schema" -Default "") -eq "rap.remote_workspace_real_adapter_runtime_gate_supervisor_validation.v1" -and + [string](Get-PropertyValue -Item $validation -Name "validation_key" -Default "") -eq "health_probe_validation" -and + [string](Get-PropertyValue -Item $validation -Name "validation_status" -Default "") -eq "satisfied_contract_only" -and + [bool](Get-PropertyValue -Item $validation -Name "health_probe_validation_required" -Default $false) -and + [bool](Get-PropertyValue -Item $validation -Name "health_probe_contract_verified" -Default $false) -and + [bool](Get-PropertyValue -Item $validation -Name "failure_detection_verified" -Default $false) -and + [string](Get-PropertyValue -Item $validation -Name "runtime_gate_state" -Default "") -eq "blocked_pending_remaining_validations" -and + [string](Get-PropertyValue -Item $validation -Name "runtime_effect" -Default "") -eq "contract_only_no_runtime_enablement" -and + [string](Get-PropertyValue -Item $validation -Name "operator_default_action" -Default "") -eq "keep_real_adapter_disabled" -and + -not [bool](Get-PropertyValue -Item $validation -Name "allows_process_start" -Default $true) -and + -not [bool](Get-PropertyValue -Item $validation -Name "allows_payload_traffic" -Default $true) +) +$guardrailsCompatible = ( + (Test-ObjectHasFields -Item $guardrails -Fields $requiredGuardrailFields) -and + [bool](Get-PropertyValue -Item $guardrails -Name "activation_blocked" -Default $false) -and + -not [bool](Get-PropertyValue -Item $guardrails -Name "process_start_allowed" -Default $true) -and + -not [bool](Get-PropertyValue -Item $guardrails -Name "health_probe_enabled" -Default $true) -and + [string](Get-PropertyValue -Item $guardrails -Name "payload_traffic" -Default "") -eq "none" -and + -not [bool](Get-PropertyValue -Item $guardrails -Name "allows_process_start" -Default $true) -and + -not [bool](Get-PropertyValue -Item $guardrails -Name "allows_payload_traffic" -Default $true) +) + +$checks = [ordered]@{ + source_smoke_passed = ([bool]$sourceResult.passed) + source_schema_expected = ([string]$sourceResult.schema_version -eq "c19z99.remote_workspace_real_adapter_runtime_gate_health_probe_validation_smoke.v1") + health_probe_validation_present = ($null -ne $validation) + validation_fields_compatible = $validationFieldsCompatible + validation_values_compatible = $validationValuesCompatible + remaining_validations_compatible = $remainingValidationsCompatible + guardrails_compatible = $guardrailsCompatible +} +$failed = @($checks.GetEnumerator() | Where-Object { -not $_.Value } | ForEach-Object { $_.Key }) + +$result = [ordered]@{ + schema_version = "c19z100.remote_workspace_real_adapter_runtime_gate_health_probe_validation_compatibility_smoke.v1" + source_result_path = $sourceFile + cluster_id = $ClusterID + required_validation_fields = $requiredValidationFields + remaining_required_validations = $remainingRequiredValidations + required_guardrail_fields = $requiredGuardrailFields + runtime_gate_health_probe_validation = $validation + checks = $checks + failed_checks = $failed + passed = ($failed.Count -eq 0) +} + +$fullResultPath = Join-Path $repoRoot $ResultPath +$resultDir = Split-Path -Parent $fullResultPath +if ($resultDir) { New-Item -ItemType Directory -Force -Path $resultDir | Out-Null } +$result | ConvertTo-Json -Depth 100 | Set-Content -Encoding UTF8 -Path $fullResultPath + +if (-not $result.passed) { + throw "C19Z100 remote workspace real-adapter runtime gate health probe validation compatibility smoke failed. Result: $fullResultPath Failed: $($failed -join ', ')" +} + +Write-Host "C19Z100 remote workspace real-adapter runtime gate health probe validation compatibility smoke passed. Result: $fullResultPath" +$result diff --git a/scripts/fabric/c19z101-remote-workspace-real-adapter-runtime-gate-payload-gate-validation-smoke.ps1 b/scripts/fabric/c19z101-remote-workspace-real-adapter-runtime-gate-payload-gate-validation-smoke.ps1 new file mode 100644 index 0000000..aa4d50c --- /dev/null +++ b/scripts/fabric/c19z101-remote-workspace-real-adapter-runtime-gate-payload-gate-validation-smoke.ps1 @@ -0,0 +1,161 @@ +param( + [string]$ApiBaseUrl = "http://192.168.200.61:18121/api/v1", + [string]$ClusterID = "cfc0743d-d960-49fb-9de8-96e063d5e4aa", + [string]$ActorUserID = "f67d943f-5397-4b3a-a229-695fe67ad700", + [string]$RequestedNodeName = "test-1", + [string]$DefaultNodeName = "test-2", + [string]$MatrixNodeName = "test-1", + [string]$ResultPath = "artifacts\c19z101-remote-workspace-real-adapter-runtime-gate-payload-gate-validation-smoke-result.json" +) + +Set-StrictMode -Version Latest +$ErrorActionPreference = "Stop" + +$repoRoot = (Resolve-Path (Join-Path $PSScriptRoot "..\..")).ProviderPath +$sourceResultPath = "artifacts\c19z101-remote-workspace-real-adapter-runtime-gate-payload-gate-validation-source-result.json" +$requiredValidationFields = @( + "schema_version", + "source_health_probe_validation_schema", + "validation_key", + "validation_status", + "payload_gate_validation_required", + "payload_policy_verified", + "payload_isolation_verified", + "remaining_required_validations", + "runtime_gate_state", + "runtime_effect", + "operator_default_action", + "allows_process_start", + "allows_payload_traffic", + "guardrail_summary" +) +$remainingRequiredValidations = @() +$requiredGuardrailFields = @("activation_blocked", "process_start_allowed", "health_probe_enabled", "payload_traffic", "allows_process_start", "allows_payload_traffic") + +function Get-PropertyValue { + param([object]$Item, [string]$Name, [object]$Default = $null) + if ($null -eq $Item) { return $Default } + if ($Item -is [System.Collections.IDictionary]) { + if ($Item.Contains($Name)) { return $Item[$Name] } + return $Default + } + $property = $Item.PSObject.Properties[$Name] + if ($null -eq $property) { return $Default } + return $property.Value +} + +function Test-ObjectHasFields { + param([object]$Item, [string[]]$Fields) + if ($null -eq $Item) { return $false } + foreach ($field in $Fields) { + if ($Item -is [System.Collections.IDictionary]) { + if (-not $Item.Contains($field)) { return $false } + continue + } + if ($null -eq $Item.PSObject.Properties[$field]) { return $false } + } + return $true +} + +function Test-ArrayEquals { + param([object[]]$Actual, [string[]]$Expected) + $actualValues = @($Actual | ForEach-Object { [string]$_ }) + if ($actualValues.Count -ne $Expected.Count) { return $false } + foreach ($item in $Expected) { + if ($actualValues -notcontains $item) { return $false } + } + return $true +} + +& powershell -ExecutionPolicy Bypass -File (Join-Path $PSScriptRoot "c19z100-remote-workspace-real-adapter-runtime-gate-health-probe-validation-compatibility-smoke.ps1") ` + -ApiBaseUrl $ApiBaseUrl ` + -ClusterID $ClusterID ` + -ActorUserID $ActorUserID ` + -RequestedNodeName $RequestedNodeName ` + -DefaultNodeName $DefaultNodeName ` + -MatrixNodeName $MatrixNodeName ` + -ResultPath $sourceResultPath | Out-Null + +$sourceFile = Join-Path $repoRoot $sourceResultPath +$sourceResult = Get-Content -Raw -Path $sourceFile | ConvertFrom-Json +$healthProbeValidation = Get-PropertyValue -Item $sourceResult -Name "runtime_gate_health_probe_validation" -Default $null +$guardrails = Get-PropertyValue -Item $healthProbeValidation -Name "guardrail_summary" -Default $null + +$validation = [ordered]@{ + schema_version = "rap.remote_workspace_real_adapter_runtime_gate_payload_gate_validation.v1" + source_health_probe_validation_schema = Get-PropertyValue -Item $healthProbeValidation -Name "schema_version" -Default $null + validation_key = "payload_gate_validation" + validation_status = "satisfied_contract_only" + payload_gate_validation_required = $true + payload_policy_verified = $true + payload_isolation_verified = $true + remaining_required_validations = $remainingRequiredValidations + runtime_gate_state = "validated_contract_only_not_enabled" + runtime_effect = "contract_only_no_runtime_enablement" + operator_default_action = Get-PropertyValue -Item $healthProbeValidation -Name "operator_default_action" -Default $null + allows_process_start = $false + allows_payload_traffic = $false + guardrail_summary = $guardrails +} + +$validationFieldsCompatible = Test-ObjectHasFields -Item $validation -Fields $requiredValidationFields +$remainingValidationsCompatible = Test-ArrayEquals -Actual @(Get-PropertyValue -Item $validation -Name "remaining_required_validations" -Default @()) -Expected $remainingRequiredValidations +$validationValuesCompatible = ( + [string](Get-PropertyValue -Item $validation -Name "schema_version" -Default "") -eq "rap.remote_workspace_real_adapter_runtime_gate_payload_gate_validation.v1" -and + [string](Get-PropertyValue -Item $validation -Name "source_health_probe_validation_schema" -Default "") -eq "rap.remote_workspace_real_adapter_runtime_gate_health_probe_validation.v1" -and + [string](Get-PropertyValue -Item $validation -Name "validation_key" -Default "") -eq "payload_gate_validation" -and + [string](Get-PropertyValue -Item $validation -Name "validation_status" -Default "") -eq "satisfied_contract_only" -and + [bool](Get-PropertyValue -Item $validation -Name "payload_gate_validation_required" -Default $false) -and + [bool](Get-PropertyValue -Item $validation -Name "payload_policy_verified" -Default $false) -and + [bool](Get-PropertyValue -Item $validation -Name "payload_isolation_verified" -Default $false) -and + [string](Get-PropertyValue -Item $validation -Name "runtime_gate_state" -Default "") -eq "validated_contract_only_not_enabled" -and + [string](Get-PropertyValue -Item $validation -Name "runtime_effect" -Default "") -eq "contract_only_no_runtime_enablement" -and + [string](Get-PropertyValue -Item $validation -Name "operator_default_action" -Default "") -eq "keep_real_adapter_disabled" -and + -not [bool](Get-PropertyValue -Item $validation -Name "allows_process_start" -Default $true) -and + -not [bool](Get-PropertyValue -Item $validation -Name "allows_payload_traffic" -Default $true) +) +$guardrailsCompatible = ( + (Test-ObjectHasFields -Item $guardrails -Fields $requiredGuardrailFields) -and + [bool](Get-PropertyValue -Item $guardrails -Name "activation_blocked" -Default $false) -and + -not [bool](Get-PropertyValue -Item $guardrails -Name "process_start_allowed" -Default $true) -and + -not [bool](Get-PropertyValue -Item $guardrails -Name "health_probe_enabled" -Default $true) -and + [string](Get-PropertyValue -Item $guardrails -Name "payload_traffic" -Default "") -eq "none" -and + -not [bool](Get-PropertyValue -Item $guardrails -Name "allows_process_start" -Default $true) -and + -not [bool](Get-PropertyValue -Item $guardrails -Name "allows_payload_traffic" -Default $true) +) + +$checks = [ordered]@{ + source_smoke_passed = ([bool]$sourceResult.passed) + source_schema_expected = ([string]$sourceResult.schema_version -eq "c19z100.remote_workspace_real_adapter_runtime_gate_health_probe_validation_compatibility_smoke.v1") + health_probe_validation_present = ($null -ne $healthProbeValidation) + validation_fields_compatible = $validationFieldsCompatible + validation_values_compatible = $validationValuesCompatible + remaining_validations_compatible = $remainingValidationsCompatible + guardrails_compatible = $guardrailsCompatible +} +$failed = @($checks.GetEnumerator() | Where-Object { -not $_.Value } | ForEach-Object { $_.Key }) + +$result = [ordered]@{ + schema_version = "c19z101.remote_workspace_real_adapter_runtime_gate_payload_gate_validation_smoke.v1" + source_result_path = $sourceFile + cluster_id = $ClusterID + required_validation_fields = $requiredValidationFields + remaining_required_validations = $remainingRequiredValidations + required_guardrail_fields = $requiredGuardrailFields + runtime_gate_payload_gate_validation = $validation + checks = $checks + failed_checks = $failed + passed = ($failed.Count -eq 0) +} + +$fullResultPath = Join-Path $repoRoot $ResultPath +$resultDir = Split-Path -Parent $fullResultPath +if ($resultDir) { New-Item -ItemType Directory -Force -Path $resultDir | Out-Null } +$result | ConvertTo-Json -Depth 100 | Set-Content -Encoding UTF8 -Path $fullResultPath + +if (-not $result.passed) { + throw "C19Z101 remote workspace real-adapter runtime gate payload gate validation smoke failed. Result: $fullResultPath Failed: $($failed -join ', ')" +} + +Write-Host "C19Z101 remote workspace real-adapter runtime gate payload gate validation smoke passed. Result: $fullResultPath" +$result diff --git a/scripts/fabric/c19z102-remote-workspace-real-adapter-runtime-gate-payload-gate-validation-compatibility-smoke.ps1 b/scripts/fabric/c19z102-remote-workspace-real-adapter-runtime-gate-payload-gate-validation-compatibility-smoke.ps1 new file mode 100644 index 0000000..e35b2bb --- /dev/null +++ b/scripts/fabric/c19z102-remote-workspace-real-adapter-runtime-gate-payload-gate-validation-compatibility-smoke.ps1 @@ -0,0 +1,144 @@ +param( + [string]$ApiBaseUrl = "http://192.168.200.61:18121/api/v1", + [string]$ClusterID = "cfc0743d-d960-49fb-9de8-96e063d5e4aa", + [string]$ActorUserID = "f67d943f-5397-4b3a-a229-695fe67ad700", + [string]$RequestedNodeName = "test-1", + [string]$DefaultNodeName = "test-2", + [string]$MatrixNodeName = "test-1", + [string]$ResultPath = "artifacts\c19z102-remote-workspace-real-adapter-runtime-gate-payload-gate-validation-compatibility-smoke-result.json" +) + +Set-StrictMode -Version Latest +$ErrorActionPreference = "Stop" + +$repoRoot = (Resolve-Path (Join-Path $PSScriptRoot "..\..")).ProviderPath +$sourceResultPath = "artifacts\c19z102-remote-workspace-real-adapter-runtime-gate-payload-gate-validation-source-result.json" +$requiredValidationFields = @( + "schema_version", + "source_health_probe_validation_schema", + "validation_key", + "validation_status", + "payload_gate_validation_required", + "payload_policy_verified", + "payload_isolation_verified", + "remaining_required_validations", + "runtime_gate_state", + "runtime_effect", + "operator_default_action", + "allows_process_start", + "allows_payload_traffic", + "guardrail_summary" +) +$remainingRequiredValidations = @() +$requiredGuardrailFields = @("activation_blocked", "process_start_allowed", "health_probe_enabled", "payload_traffic", "allows_process_start", "allows_payload_traffic") + +function Get-PropertyValue { + param([object]$Item, [string]$Name, [object]$Default = $null) + if ($null -eq $Item) { return $Default } + if ($Item -is [System.Collections.IDictionary]) { + if ($Item.Contains($Name)) { return $Item[$Name] } + return $Default + } + $property = $Item.PSObject.Properties[$Name] + if ($null -eq $property) { return $Default } + return $property.Value +} + +function Test-ObjectHasFields { + param([object]$Item, [string[]]$Fields) + if ($null -eq $Item) { return $false } + foreach ($field in $Fields) { + if ($Item -is [System.Collections.IDictionary]) { + if (-not $Item.Contains($field)) { return $false } + continue + } + if ($null -eq $Item.PSObject.Properties[$field]) { return $false } + } + return $true +} + +function Test-ArrayEquals { + param([object[]]$Actual, [string[]]$Expected) + $actualValues = @($Actual | ForEach-Object { [string]$_ }) + if ($actualValues.Count -ne $Expected.Count) { return $false } + foreach ($item in $Expected) { + if ($actualValues -notcontains $item) { return $false } + } + return $true +} + +& powershell -ExecutionPolicy Bypass -File (Join-Path $PSScriptRoot "c19z101-remote-workspace-real-adapter-runtime-gate-payload-gate-validation-smoke.ps1") ` + -ApiBaseUrl $ApiBaseUrl ` + -ClusterID $ClusterID ` + -ActorUserID $ActorUserID ` + -RequestedNodeName $RequestedNodeName ` + -DefaultNodeName $DefaultNodeName ` + -MatrixNodeName $MatrixNodeName ` + -ResultPath $sourceResultPath | Out-Null + +$sourceFile = Join-Path $repoRoot $sourceResultPath +$sourceResult = Get-Content -Raw -Path $sourceFile | ConvertFrom-Json +$validation = Get-PropertyValue -Item $sourceResult -Name "runtime_gate_payload_gate_validation" -Default $null +$guardrails = Get-PropertyValue -Item $validation -Name "guardrail_summary" -Default $null + +$validationFieldsCompatible = Test-ObjectHasFields -Item $validation -Fields $requiredValidationFields +$remainingValidationsCompatible = Test-ArrayEquals -Actual @(Get-PropertyValue -Item $validation -Name "remaining_required_validations" -Default @()) -Expected $remainingRequiredValidations +$validationValuesCompatible = ( + [string](Get-PropertyValue -Item $validation -Name "schema_version" -Default "") -eq "rap.remote_workspace_real_adapter_runtime_gate_payload_gate_validation.v1" -and + [string](Get-PropertyValue -Item $validation -Name "source_health_probe_validation_schema" -Default "") -eq "rap.remote_workspace_real_adapter_runtime_gate_health_probe_validation.v1" -and + [string](Get-PropertyValue -Item $validation -Name "validation_key" -Default "") -eq "payload_gate_validation" -and + [string](Get-PropertyValue -Item $validation -Name "validation_status" -Default "") -eq "satisfied_contract_only" -and + [bool](Get-PropertyValue -Item $validation -Name "payload_gate_validation_required" -Default $false) -and + [bool](Get-PropertyValue -Item $validation -Name "payload_policy_verified" -Default $false) -and + [bool](Get-PropertyValue -Item $validation -Name "payload_isolation_verified" -Default $false) -and + [string](Get-PropertyValue -Item $validation -Name "runtime_gate_state" -Default "") -eq "validated_contract_only_not_enabled" -and + [string](Get-PropertyValue -Item $validation -Name "runtime_effect" -Default "") -eq "contract_only_no_runtime_enablement" -and + [string](Get-PropertyValue -Item $validation -Name "operator_default_action" -Default "") -eq "keep_real_adapter_disabled" -and + -not [bool](Get-PropertyValue -Item $validation -Name "allows_process_start" -Default $true) -and + -not [bool](Get-PropertyValue -Item $validation -Name "allows_payload_traffic" -Default $true) +) +$guardrailsCompatible = ( + (Test-ObjectHasFields -Item $guardrails -Fields $requiredGuardrailFields) -and + [bool](Get-PropertyValue -Item $guardrails -Name "activation_blocked" -Default $false) -and + -not [bool](Get-PropertyValue -Item $guardrails -Name "process_start_allowed" -Default $true) -and + -not [bool](Get-PropertyValue -Item $guardrails -Name "health_probe_enabled" -Default $true) -and + [string](Get-PropertyValue -Item $guardrails -Name "payload_traffic" -Default "") -eq "none" -and + -not [bool](Get-PropertyValue -Item $guardrails -Name "allows_process_start" -Default $true) -and + -not [bool](Get-PropertyValue -Item $guardrails -Name "allows_payload_traffic" -Default $true) +) + +$checks = [ordered]@{ + source_smoke_passed = ([bool]$sourceResult.passed) + source_schema_expected = ([string]$sourceResult.schema_version -eq "c19z101.remote_workspace_real_adapter_runtime_gate_payload_gate_validation_smoke.v1") + payload_gate_validation_present = ($null -ne $validation) + validation_fields_compatible = $validationFieldsCompatible + validation_values_compatible = $validationValuesCompatible + remaining_validations_compatible = $remainingValidationsCompatible + guardrails_compatible = $guardrailsCompatible +} +$failed = @($checks.GetEnumerator() | Where-Object { -not $_.Value } | ForEach-Object { $_.Key }) + +$result = [ordered]@{ + schema_version = "c19z102.remote_workspace_real_adapter_runtime_gate_payload_gate_validation_compatibility_smoke.v1" + source_result_path = $sourceFile + cluster_id = $ClusterID + required_validation_fields = $requiredValidationFields + remaining_required_validations = $remainingRequiredValidations + required_guardrail_fields = $requiredGuardrailFields + runtime_gate_payload_gate_validation = $validation + checks = $checks + failed_checks = $failed + passed = ($failed.Count -eq 0) +} + +$fullResultPath = Join-Path $repoRoot $ResultPath +$resultDir = Split-Path -Parent $fullResultPath +if ($resultDir) { New-Item -ItemType Directory -Force -Path $resultDir | Out-Null } +$result | ConvertTo-Json -Depth 100 | Set-Content -Encoding UTF8 -Path $fullResultPath + +if (-not $result.passed) { + throw "C19Z102 remote workspace real-adapter runtime gate payload gate validation compatibility smoke failed. Result: $fullResultPath Failed: $($failed -join ', ')" +} + +Write-Host "C19Z102 remote workspace real-adapter runtime gate payload gate validation compatibility smoke passed. Result: $fullResultPath" +$result diff --git a/scripts/fabric/c19z103-remote-workspace-real-adapter-runtime-gate-validation-closeout-smoke.ps1 b/scripts/fabric/c19z103-remote-workspace-real-adapter-runtime-gate-validation-closeout-smoke.ps1 new file mode 100644 index 0000000..f0e2942 --- /dev/null +++ b/scripts/fabric/c19z103-remote-workspace-real-adapter-runtime-gate-validation-closeout-smoke.ps1 @@ -0,0 +1,180 @@ +param( + [string]$ApiBaseUrl = "http://192.168.200.61:18121/api/v1", + [string]$ClusterID = "cfc0743d-d960-49fb-9de8-96e063d5e4aa", + [string]$ActorUserID = "f67d943f-5397-4b3a-a229-695fe67ad700", + [string]$RequestedNodeName = "test-1", + [string]$DefaultNodeName = "test-2", + [string]$MatrixNodeName = "test-1", + [string]$ResultPath = "artifacts\c19z103-remote-workspace-real-adapter-runtime-gate-validation-closeout-smoke-result.json" +) + +Set-StrictMode -Version Latest +$ErrorActionPreference = "Stop" + +$repoRoot = (Resolve-Path (Join-Path $PSScriptRoot "..\..")).ProviderPath +$sourceResultPath = "artifacts\c19z103-remote-workspace-real-adapter-runtime-gate-validation-closeout-source-result.json" +$requiredCloseoutFields = @( + "schema_version", + "source_payload_gate_validation_schema", + "phase_name", + "validation_chain_status", + "enablement_boundary", + "enablement_status", + "required_validations", + "remaining_required_validations", + "runtime_gate_state", + "runtime_effect", + "operator_default_action", + "allows_process_start", + "allows_payload_traffic", + "guardrail_summary" +) +$requiredValidations = @( + "operator_confirmation", + "binary_validation", + "permission_validation", + "supervisor_validation", + "health_probe_validation", + "payload_gate_validation" +) +$remainingRequiredValidations = @() +$requiredGuardrailFields = @("activation_blocked", "process_start_allowed", "health_probe_enabled", "payload_traffic", "allows_process_start", "allows_payload_traffic") + +function Get-PropertyValue { + param([object]$Item, [string]$Name, [object]$Default = $null) + if ($null -eq $Item) { return $Default } + if ($Item -is [System.Collections.IDictionary]) { + if ($Item.Contains($Name)) { return $Item[$Name] } + return $Default + } + $property = $Item.PSObject.Properties[$Name] + if ($null -eq $property) { return $Default } + return $property.Value +} + +function Test-ObjectHasFields { + param([object]$Item, [string[]]$Fields) + if ($null -eq $Item) { return $false } + foreach ($field in $Fields) { + if ($Item -is [System.Collections.IDictionary]) { + if (-not $Item.Contains($field)) { return $false } + continue + } + if ($null -eq $Item.PSObject.Properties[$field]) { return $false } + } + return $true +} + +function Test-ArrayContainsAll { + param([object[]]$Actual, [string[]]$Required) + $values = @($Actual | ForEach-Object { [string]$_ }) + foreach ($item in $Required) { + if ($values -notcontains $item) { return $false } + } + return $true +} + +function Test-ArrayEquals { + param([object[]]$Actual, [string[]]$Expected) + $actualValues = @($Actual | ForEach-Object { [string]$_ }) + if ($actualValues.Count -ne $Expected.Count) { return $false } + foreach ($item in $Expected) { + if ($actualValues -notcontains $item) { return $false } + } + return $true +} + +& powershell -ExecutionPolicy Bypass -File (Join-Path $PSScriptRoot "c19z102-remote-workspace-real-adapter-runtime-gate-payload-gate-validation-compatibility-smoke.ps1") ` + -ApiBaseUrl $ApiBaseUrl ` + -ClusterID $ClusterID ` + -ActorUserID $ActorUserID ` + -RequestedNodeName $RequestedNodeName ` + -DefaultNodeName $DefaultNodeName ` + -MatrixNodeName $MatrixNodeName ` + -ResultPath $sourceResultPath | Out-Null + +$sourceFile = Join-Path $repoRoot $sourceResultPath +$sourceResult = Get-Content -Raw -Path $sourceFile | ConvertFrom-Json +$payloadGateValidation = Get-PropertyValue -Item $sourceResult -Name "runtime_gate_payload_gate_validation" -Default $null +$guardrails = Get-PropertyValue -Item $payloadGateValidation -Name "guardrail_summary" -Default $null + +$closeout = [ordered]@{ + schema_version = "rap.remote_workspace_real_adapter_runtime_gate_validation_closeout.v1" + source_payload_gate_validation_schema = Get-PropertyValue -Item $payloadGateValidation -Name "schema_version" -Default $null + phase_name = "explicit_real_runtime_gate_enablement_validation" + validation_chain_status = "complete_contract_only" + enablement_boundary = "explicit_operator_enablement_required" + enablement_status = "not_enabled" + required_validations = $requiredValidations + remaining_required_validations = $remainingRequiredValidations + runtime_gate_state = "validated_contract_only_not_enabled" + runtime_effect = "contract_only_no_runtime_enablement" + operator_default_action = Get-PropertyValue -Item $payloadGateValidation -Name "operator_default_action" -Default $null + allows_process_start = $false + allows_payload_traffic = $false + guardrail_summary = $guardrails +} + +$closeoutFieldsCompatible = Test-ObjectHasFields -Item $closeout -Fields $requiredCloseoutFields +$requiredValidationsCompatible = Test-ArrayContainsAll -Actual @(Get-PropertyValue -Item $closeout -Name "required_validations" -Default @()) -Required $requiredValidations +$remainingValidationsCompatible = Test-ArrayEquals -Actual @(Get-PropertyValue -Item $closeout -Name "remaining_required_validations" -Default @()) -Expected $remainingRequiredValidations +$closeoutValuesCompatible = ( + [string](Get-PropertyValue -Item $closeout -Name "schema_version" -Default "") -eq "rap.remote_workspace_real_adapter_runtime_gate_validation_closeout.v1" -and + [string](Get-PropertyValue -Item $closeout -Name "source_payload_gate_validation_schema" -Default "") -eq "rap.remote_workspace_real_adapter_runtime_gate_payload_gate_validation.v1" -and + [string](Get-PropertyValue -Item $closeout -Name "phase_name" -Default "") -eq "explicit_real_runtime_gate_enablement_validation" -and + [string](Get-PropertyValue -Item $closeout -Name "validation_chain_status" -Default "") -eq "complete_contract_only" -and + [string](Get-PropertyValue -Item $closeout -Name "enablement_boundary" -Default "") -eq "explicit_operator_enablement_required" -and + [string](Get-PropertyValue -Item $closeout -Name "enablement_status" -Default "") -eq "not_enabled" -and + [string](Get-PropertyValue -Item $closeout -Name "runtime_gate_state" -Default "") -eq "validated_contract_only_not_enabled" -and + [string](Get-PropertyValue -Item $closeout -Name "runtime_effect" -Default "") -eq "contract_only_no_runtime_enablement" -and + [string](Get-PropertyValue -Item $closeout -Name "operator_default_action" -Default "") -eq "keep_real_adapter_disabled" -and + -not [bool](Get-PropertyValue -Item $closeout -Name "allows_process_start" -Default $true) -and + -not [bool](Get-PropertyValue -Item $closeout -Name "allows_payload_traffic" -Default $true) +) +$guardrailsCompatible = ( + (Test-ObjectHasFields -Item $guardrails -Fields $requiredGuardrailFields) -and + [bool](Get-PropertyValue -Item $guardrails -Name "activation_blocked" -Default $false) -and + -not [bool](Get-PropertyValue -Item $guardrails -Name "process_start_allowed" -Default $true) -and + -not [bool](Get-PropertyValue -Item $guardrails -Name "health_probe_enabled" -Default $true) -and + [string](Get-PropertyValue -Item $guardrails -Name "payload_traffic" -Default "") -eq "none" -and + -not [bool](Get-PropertyValue -Item $guardrails -Name "allows_process_start" -Default $true) -and + -not [bool](Get-PropertyValue -Item $guardrails -Name "allows_payload_traffic" -Default $true) +) + +$checks = [ordered]@{ + source_smoke_passed = ([bool]$sourceResult.passed) + source_schema_expected = ([string]$sourceResult.schema_version -eq "c19z102.remote_workspace_real_adapter_runtime_gate_payload_gate_validation_compatibility_smoke.v1") + payload_gate_validation_present = ($null -ne $payloadGateValidation) + closeout_fields_compatible = $closeoutFieldsCompatible + closeout_values_compatible = $closeoutValuesCompatible + required_validations_compatible = $requiredValidationsCompatible + remaining_validations_compatible = $remainingValidationsCompatible + guardrails_compatible = $guardrailsCompatible +} +$failed = @($checks.GetEnumerator() | Where-Object { -not $_.Value } | ForEach-Object { $_.Key }) + +$result = [ordered]@{ + schema_version = "c19z103.remote_workspace_real_adapter_runtime_gate_validation_closeout_smoke.v1" + source_result_path = $sourceFile + cluster_id = $ClusterID + required_closeout_fields = $requiredCloseoutFields + required_validations = $requiredValidations + remaining_required_validations = $remainingRequiredValidations + required_guardrail_fields = $requiredGuardrailFields + runtime_gate_validation_closeout = $closeout + checks = $checks + failed_checks = $failed + passed = ($failed.Count -eq 0) +} + +$fullResultPath = Join-Path $repoRoot $ResultPath +$resultDir = Split-Path -Parent $fullResultPath +if ($resultDir) { New-Item -ItemType Directory -Force -Path $resultDir | Out-Null } +$result | ConvertTo-Json -Depth 100 | Set-Content -Encoding UTF8 -Path $fullResultPath + +if (-not $result.passed) { + throw "C19Z103 remote workspace real-adapter runtime gate validation closeout smoke failed. Result: $fullResultPath Failed: $($failed -join ', ')" +} + +Write-Host "C19Z103 remote workspace real-adapter runtime gate validation closeout smoke passed. Result: $fullResultPath" +$result diff --git a/scripts/fabric/c19z104-remote-workspace-real-adapter-runtime-gate-validation-closeout-compatibility-smoke.ps1 b/scripts/fabric/c19z104-remote-workspace-real-adapter-runtime-gate-validation-closeout-compatibility-smoke.ps1 new file mode 100644 index 0000000..27d68d9 --- /dev/null +++ b/scripts/fabric/c19z104-remote-workspace-real-adapter-runtime-gate-validation-closeout-compatibility-smoke.ps1 @@ -0,0 +1,163 @@ +param( + [string]$ApiBaseUrl = "http://192.168.200.61:18121/api/v1", + [string]$ClusterID = "cfc0743d-d960-49fb-9de8-96e063d5e4aa", + [string]$ActorUserID = "f67d943f-5397-4b3a-a229-695fe67ad700", + [string]$RequestedNodeName = "test-1", + [string]$DefaultNodeName = "test-2", + [string]$MatrixNodeName = "test-1", + [string]$ResultPath = "artifacts\c19z104-remote-workspace-real-adapter-runtime-gate-validation-closeout-compatibility-smoke-result.json" +) + +Set-StrictMode -Version Latest +$ErrorActionPreference = "Stop" + +$repoRoot = (Resolve-Path (Join-Path $PSScriptRoot "..\..")).ProviderPath +$sourceResultPath = "artifacts\c19z104-remote-workspace-real-adapter-runtime-gate-validation-closeout-source-result.json" +$requiredCloseoutFields = @( + "schema_version", + "source_payload_gate_validation_schema", + "phase_name", + "validation_chain_status", + "enablement_boundary", + "enablement_status", + "required_validations", + "remaining_required_validations", + "runtime_gate_state", + "runtime_effect", + "operator_default_action", + "allows_process_start", + "allows_payload_traffic", + "guardrail_summary" +) +$requiredValidations = @( + "operator_confirmation", + "binary_validation", + "permission_validation", + "supervisor_validation", + "health_probe_validation", + "payload_gate_validation" +) +$remainingRequiredValidations = @() +$requiredGuardrailFields = @("activation_blocked", "process_start_allowed", "health_probe_enabled", "payload_traffic", "allows_process_start", "allows_payload_traffic") + +function Get-PropertyValue { + param([object]$Item, [string]$Name, [object]$Default = $null) + if ($null -eq $Item) { return $Default } + if ($Item -is [System.Collections.IDictionary]) { + if ($Item.Contains($Name)) { return $Item[$Name] } + return $Default + } + $property = $Item.PSObject.Properties[$Name] + if ($null -eq $property) { return $Default } + return $property.Value +} + +function Test-ObjectHasFields { + param([object]$Item, [string[]]$Fields) + if ($null -eq $Item) { return $false } + foreach ($field in $Fields) { + if ($Item -is [System.Collections.IDictionary]) { + if (-not $Item.Contains($field)) { return $false } + continue + } + if ($null -eq $Item.PSObject.Properties[$field]) { return $false } + } + return $true +} + +function Test-ArrayContainsAll { + param([object[]]$Actual, [string[]]$Required) + $values = @($Actual | ForEach-Object { [string]$_ }) + foreach ($item in $Required) { + if ($values -notcontains $item) { return $false } + } + return $true +} + +function Test-ArrayEquals { + param([object[]]$Actual, [string[]]$Expected) + $actualValues = @($Actual | ForEach-Object { [string]$_ }) + if ($actualValues.Count -ne $Expected.Count) { return $false } + foreach ($item in $Expected) { + if ($actualValues -notcontains $item) { return $false } + } + return $true +} + +& powershell -ExecutionPolicy Bypass -File (Join-Path $PSScriptRoot "c19z103-remote-workspace-real-adapter-runtime-gate-validation-closeout-smoke.ps1") ` + -ApiBaseUrl $ApiBaseUrl ` + -ClusterID $ClusterID ` + -ActorUserID $ActorUserID ` + -RequestedNodeName $RequestedNodeName ` + -DefaultNodeName $DefaultNodeName ` + -MatrixNodeName $MatrixNodeName ` + -ResultPath $sourceResultPath | Out-Null + +$sourceFile = Join-Path $repoRoot $sourceResultPath +$sourceResult = Get-Content -Raw -Path $sourceFile | ConvertFrom-Json +$closeout = Get-PropertyValue -Item $sourceResult -Name "runtime_gate_validation_closeout" -Default $null +$guardrails = Get-PropertyValue -Item $closeout -Name "guardrail_summary" -Default $null + +$closeoutFieldsCompatible = Test-ObjectHasFields -Item $closeout -Fields $requiredCloseoutFields +$requiredValidationsCompatible = Test-ArrayContainsAll -Actual @(Get-PropertyValue -Item $closeout -Name "required_validations" -Default @()) -Required $requiredValidations +$remainingValidationsCompatible = Test-ArrayEquals -Actual @(Get-PropertyValue -Item $closeout -Name "remaining_required_validations" -Default @()) -Expected $remainingRequiredValidations +$closeoutValuesCompatible = ( + [string](Get-PropertyValue -Item $closeout -Name "schema_version" -Default "") -eq "rap.remote_workspace_real_adapter_runtime_gate_validation_closeout.v1" -and + [string](Get-PropertyValue -Item $closeout -Name "source_payload_gate_validation_schema" -Default "") -eq "rap.remote_workspace_real_adapter_runtime_gate_payload_gate_validation.v1" -and + [string](Get-PropertyValue -Item $closeout -Name "phase_name" -Default "") -eq "explicit_real_runtime_gate_enablement_validation" -and + [string](Get-PropertyValue -Item $closeout -Name "validation_chain_status" -Default "") -eq "complete_contract_only" -and + [string](Get-PropertyValue -Item $closeout -Name "enablement_boundary" -Default "") -eq "explicit_operator_enablement_required" -and + [string](Get-PropertyValue -Item $closeout -Name "enablement_status" -Default "") -eq "not_enabled" -and + [string](Get-PropertyValue -Item $closeout -Name "runtime_gate_state" -Default "") -eq "validated_contract_only_not_enabled" -and + [string](Get-PropertyValue -Item $closeout -Name "runtime_effect" -Default "") -eq "contract_only_no_runtime_enablement" -and + [string](Get-PropertyValue -Item $closeout -Name "operator_default_action" -Default "") -eq "keep_real_adapter_disabled" -and + -not [bool](Get-PropertyValue -Item $closeout -Name "allows_process_start" -Default $true) -and + -not [bool](Get-PropertyValue -Item $closeout -Name "allows_payload_traffic" -Default $true) +) +$guardrailsCompatible = ( + (Test-ObjectHasFields -Item $guardrails -Fields $requiredGuardrailFields) -and + [bool](Get-PropertyValue -Item $guardrails -Name "activation_blocked" -Default $false) -and + -not [bool](Get-PropertyValue -Item $guardrails -Name "process_start_allowed" -Default $true) -and + -not [bool](Get-PropertyValue -Item $guardrails -Name "health_probe_enabled" -Default $true) -and + [string](Get-PropertyValue -Item $guardrails -Name "payload_traffic" -Default "") -eq "none" -and + -not [bool](Get-PropertyValue -Item $guardrails -Name "allows_process_start" -Default $true) -and + -not [bool](Get-PropertyValue -Item $guardrails -Name "allows_payload_traffic" -Default $true) +) + +$checks = [ordered]@{ + source_smoke_passed = ([bool]$sourceResult.passed) + source_schema_expected = ([string]$sourceResult.schema_version -eq "c19z103.remote_workspace_real_adapter_runtime_gate_validation_closeout_smoke.v1") + closeout_present = ($null -ne $closeout) + closeout_fields_compatible = $closeoutFieldsCompatible + closeout_values_compatible = $closeoutValuesCompatible + required_validations_compatible = $requiredValidationsCompatible + remaining_validations_compatible = $remainingValidationsCompatible + guardrails_compatible = $guardrailsCompatible +} +$failed = @($checks.GetEnumerator() | Where-Object { -not $_.Value } | ForEach-Object { $_.Key }) + +$result = [ordered]@{ + schema_version = "c19z104.remote_workspace_real_adapter_runtime_gate_validation_closeout_compatibility_smoke.v1" + source_result_path = $sourceFile + cluster_id = $ClusterID + required_closeout_fields = $requiredCloseoutFields + required_validations = $requiredValidations + remaining_required_validations = $remainingRequiredValidations + required_guardrail_fields = $requiredGuardrailFields + runtime_gate_validation_closeout = $closeout + checks = $checks + failed_checks = $failed + passed = ($failed.Count -eq 0) +} + +$fullResultPath = Join-Path $repoRoot $ResultPath +$resultDir = Split-Path -Parent $fullResultPath +if ($resultDir) { New-Item -ItemType Directory -Force -Path $resultDir | Out-Null } +$result | ConvertTo-Json -Depth 100 | Set-Content -Encoding UTF8 -Path $fullResultPath + +if (-not $result.passed) { + throw "C19Z104 remote workspace real-adapter runtime gate validation closeout compatibility smoke failed. Result: $fullResultPath Failed: $($failed -join ', ')" +} + +Write-Host "C19Z104 remote workspace real-adapter runtime gate validation closeout compatibility smoke passed. Result: $fullResultPath" +$result diff --git a/scripts/fabric/c19z105-remote-workspace-real-adapter-operator-enablement-readiness-package-smoke.ps1 b/scripts/fabric/c19z105-remote-workspace-real-adapter-operator-enablement-readiness-package-smoke.ps1 new file mode 100644 index 0000000..72ff261 --- /dev/null +++ b/scripts/fabric/c19z105-remote-workspace-real-adapter-operator-enablement-readiness-package-smoke.ps1 @@ -0,0 +1,177 @@ +param( + [string]$ApiBaseUrl = "http://192.168.200.61:18121/api/v1", + [string]$ClusterID = "cfc0743d-d960-49fb-9de8-96e063d5e4aa", + [string]$ActorUserID = "f67d943f-5397-4b3a-a229-695fe67ad700", + [string]$RequestedNodeName = "test-1", + [string]$DefaultNodeName = "test-2", + [string]$MatrixNodeName = "test-1", + [string]$ResultPath = "artifacts\c19z105-remote-workspace-real-adapter-operator-enablement-readiness-package-smoke-result.json" +) + +Set-StrictMode -Version Latest +$ErrorActionPreference = "Stop" + +$repoRoot = (Resolve-Path (Join-Path $PSScriptRoot "..\..")).ProviderPath +$sourceResultPath = "artifacts\c19z105-remote-workspace-real-adapter-operator-enablement-readiness-package-source-result.json" +$requiredPackageFields = @( + "schema_version", + "source_closeout_schema", + "package_status", + "operator_review_status", + "enablement_boundary", + "enablement_status", + "included_contracts", + "required_operator_actions", + "runtime_gate_state", + "runtime_effect", + "operator_default_action", + "allows_process_start", + "allows_payload_traffic", + "guardrail_summary" +) +$includedContracts = @( + "operator_confirmation_validation", + "binary_validation", + "permission_validation", + "supervisor_validation", + "health_probe_validation", + "payload_gate_validation", + "validation_closeout" +) +$requiredOperatorActions = @( + "review_validation_closeout", + "confirm_real_runtime_enablement_intent", + "select_runtime_targets", + "approve_process_start", + "approve_payload_traffic" +) +$requiredGuardrailFields = @("activation_blocked", "process_start_allowed", "health_probe_enabled", "payload_traffic", "allows_process_start", "allows_payload_traffic") + +function Get-PropertyValue { + param([object]$Item, [string]$Name, [object]$Default = $null) + if ($null -eq $Item) { return $Default } + if ($Item -is [System.Collections.IDictionary]) { + if ($Item.Contains($Name)) { return $Item[$Name] } + return $Default + } + $property = $Item.PSObject.Properties[$Name] + if ($null -eq $property) { return $Default } + return $property.Value +} + +function Test-ObjectHasFields { + param([object]$Item, [string[]]$Fields) + if ($null -eq $Item) { return $false } + foreach ($field in $Fields) { + if ($Item -is [System.Collections.IDictionary]) { + if (-not $Item.Contains($field)) { return $false } + continue + } + if ($null -eq $Item.PSObject.Properties[$field]) { return $false } + } + return $true +} + +function Test-ArrayContainsAll { + param([object[]]$Actual, [string[]]$Required) + $values = @($Actual | ForEach-Object { [string]$_ }) + foreach ($item in $Required) { + if ($values -notcontains $item) { return $false } + } + return $true +} + +& powershell -ExecutionPolicy Bypass -File (Join-Path $PSScriptRoot "c19z104-remote-workspace-real-adapter-runtime-gate-validation-closeout-compatibility-smoke.ps1") ` + -ApiBaseUrl $ApiBaseUrl ` + -ClusterID $ClusterID ` + -ActorUserID $ActorUserID ` + -RequestedNodeName $RequestedNodeName ` + -DefaultNodeName $DefaultNodeName ` + -MatrixNodeName $MatrixNodeName ` + -ResultPath $sourceResultPath | Out-Null + +$sourceFile = Join-Path $repoRoot $sourceResultPath +$sourceResult = Get-Content -Raw -Path $sourceFile | ConvertFrom-Json +$closeout = Get-PropertyValue -Item $sourceResult -Name "runtime_gate_validation_closeout" -Default $null +$guardrails = Get-PropertyValue -Item $closeout -Name "guardrail_summary" -Default $null + +$package = [ordered]@{ + schema_version = "rap.remote_workspace_real_adapter_operator_enablement_readiness_package.v1" + source_closeout_schema = Get-PropertyValue -Item $closeout -Name "schema_version" -Default $null + package_status = "ready_for_operator_review" + operator_review_status = "not_reviewed" + enablement_boundary = "explicit_operator_enablement_required" + enablement_status = "not_enabled" + included_contracts = $includedContracts + required_operator_actions = $requiredOperatorActions + runtime_gate_state = Get-PropertyValue -Item $closeout -Name "runtime_gate_state" -Default $null + runtime_effect = Get-PropertyValue -Item $closeout -Name "runtime_effect" -Default $null + operator_default_action = Get-PropertyValue -Item $closeout -Name "operator_default_action" -Default $null + allows_process_start = $false + allows_payload_traffic = $false + guardrail_summary = $guardrails +} + +$packageFieldsCompatible = Test-ObjectHasFields -Item $package -Fields $requiredPackageFields +$includedContractsCompatible = Test-ArrayContainsAll -Actual @(Get-PropertyValue -Item $package -Name "included_contracts" -Default @()) -Required $includedContracts +$operatorActionsCompatible = Test-ArrayContainsAll -Actual @(Get-PropertyValue -Item $package -Name "required_operator_actions" -Default @()) -Required $requiredOperatorActions +$packageValuesCompatible = ( + [string](Get-PropertyValue -Item $package -Name "schema_version" -Default "") -eq "rap.remote_workspace_real_adapter_operator_enablement_readiness_package.v1" -and + [string](Get-PropertyValue -Item $package -Name "source_closeout_schema" -Default "") -eq "rap.remote_workspace_real_adapter_runtime_gate_validation_closeout.v1" -and + [string](Get-PropertyValue -Item $package -Name "package_status" -Default "") -eq "ready_for_operator_review" -and + [string](Get-PropertyValue -Item $package -Name "operator_review_status" -Default "") -eq "not_reviewed" -and + [string](Get-PropertyValue -Item $package -Name "enablement_boundary" -Default "") -eq "explicit_operator_enablement_required" -and + [string](Get-PropertyValue -Item $package -Name "enablement_status" -Default "") -eq "not_enabled" -and + [string](Get-PropertyValue -Item $package -Name "runtime_gate_state" -Default "") -eq "validated_contract_only_not_enabled" -and + [string](Get-PropertyValue -Item $package -Name "runtime_effect" -Default "") -eq "contract_only_no_runtime_enablement" -and + [string](Get-PropertyValue -Item $package -Name "operator_default_action" -Default "") -eq "keep_real_adapter_disabled" -and + -not [bool](Get-PropertyValue -Item $package -Name "allows_process_start" -Default $true) -and + -not [bool](Get-PropertyValue -Item $package -Name "allows_payload_traffic" -Default $true) +) +$guardrailsCompatible = ( + (Test-ObjectHasFields -Item $guardrails -Fields $requiredGuardrailFields) -and + [bool](Get-PropertyValue -Item $guardrails -Name "activation_blocked" -Default $false) -and + -not [bool](Get-PropertyValue -Item $guardrails -Name "process_start_allowed" -Default $true) -and + -not [bool](Get-PropertyValue -Item $guardrails -Name "health_probe_enabled" -Default $true) -and + [string](Get-PropertyValue -Item $guardrails -Name "payload_traffic" -Default "") -eq "none" -and + -not [bool](Get-PropertyValue -Item $guardrails -Name "allows_process_start" -Default $true) -and + -not [bool](Get-PropertyValue -Item $guardrails -Name "allows_payload_traffic" -Default $true) +) + +$checks = [ordered]@{ + source_smoke_passed = ([bool]$sourceResult.passed) + source_schema_expected = ([string]$sourceResult.schema_version -eq "c19z104.remote_workspace_real_adapter_runtime_gate_validation_closeout_compatibility_smoke.v1") + closeout_present = ($null -ne $closeout) + package_fields_compatible = $packageFieldsCompatible + package_values_compatible = $packageValuesCompatible + included_contracts_compatible = $includedContractsCompatible + operator_actions_compatible = $operatorActionsCompatible + guardrails_compatible = $guardrailsCompatible +} +$failed = @($checks.GetEnumerator() | Where-Object { -not $_.Value } | ForEach-Object { $_.Key }) + +$result = [ordered]@{ + schema_version = "c19z105.remote_workspace_real_adapter_operator_enablement_readiness_package_smoke.v1" + source_result_path = $sourceFile + cluster_id = $ClusterID + required_package_fields = $requiredPackageFields + included_contracts = $includedContracts + required_operator_actions = $requiredOperatorActions + required_guardrail_fields = $requiredGuardrailFields + operator_enablement_readiness_package = $package + checks = $checks + failed_checks = $failed + passed = ($failed.Count -eq 0) +} + +$fullResultPath = Join-Path $repoRoot $ResultPath +$resultDir = Split-Path -Parent $fullResultPath +if ($resultDir) { New-Item -ItemType Directory -Force -Path $resultDir | Out-Null } +$result | ConvertTo-Json -Depth 100 | Set-Content -Encoding UTF8 -Path $fullResultPath + +if (-not $result.passed) { + throw "C19Z105 remote workspace real-adapter operator enablement readiness package smoke failed. Result: $fullResultPath Failed: $($failed -join ', ')" +} + +Write-Host "C19Z105 remote workspace real-adapter operator enablement readiness package smoke passed. Result: $fullResultPath" +$result diff --git a/scripts/fabric/c19z106-remote-workspace-real-adapter-operator-enablement-readiness-package-compatibility-smoke.ps1 b/scripts/fabric/c19z106-remote-workspace-real-adapter-operator-enablement-readiness-package-compatibility-smoke.ps1 new file mode 100644 index 0000000..c89499b --- /dev/null +++ b/scripts/fabric/c19z106-remote-workspace-real-adapter-operator-enablement-readiness-package-compatibility-smoke.ps1 @@ -0,0 +1,160 @@ +param( + [string]$ApiBaseUrl = "http://192.168.200.61:18121/api/v1", + [string]$ClusterID = "cfc0743d-d960-49fb-9de8-96e063d5e4aa", + [string]$ActorUserID = "f67d943f-5397-4b3a-a229-695fe67ad700", + [string]$RequestedNodeName = "test-1", + [string]$DefaultNodeName = "test-2", + [string]$MatrixNodeName = "test-1", + [string]$ResultPath = "artifacts\c19z106-remote-workspace-real-adapter-operator-enablement-readiness-package-compatibility-smoke-result.json" +) + +Set-StrictMode -Version Latest +$ErrorActionPreference = "Stop" + +$repoRoot = (Resolve-Path (Join-Path $PSScriptRoot "..\..")).ProviderPath +$sourceResultPath = "artifacts\c19z106-remote-workspace-real-adapter-operator-enablement-readiness-package-source-result.json" +$requiredPackageFields = @( + "schema_version", + "source_closeout_schema", + "package_status", + "operator_review_status", + "enablement_boundary", + "enablement_status", + "included_contracts", + "required_operator_actions", + "runtime_gate_state", + "runtime_effect", + "operator_default_action", + "allows_process_start", + "allows_payload_traffic", + "guardrail_summary" +) +$includedContracts = @( + "operator_confirmation_validation", + "binary_validation", + "permission_validation", + "supervisor_validation", + "health_probe_validation", + "payload_gate_validation", + "validation_closeout" +) +$requiredOperatorActions = @( + "review_validation_closeout", + "confirm_real_runtime_enablement_intent", + "select_runtime_targets", + "approve_process_start", + "approve_payload_traffic" +) +$requiredGuardrailFields = @("activation_blocked", "process_start_allowed", "health_probe_enabled", "payload_traffic", "allows_process_start", "allows_payload_traffic") + +function Get-PropertyValue { + param([object]$Item, [string]$Name, [object]$Default = $null) + if ($null -eq $Item) { return $Default } + if ($Item -is [System.Collections.IDictionary]) { + if ($Item.Contains($Name)) { return $Item[$Name] } + return $Default + } + $property = $Item.PSObject.Properties[$Name] + if ($null -eq $property) { return $Default } + return $property.Value +} + +function Test-ObjectHasFields { + param([object]$Item, [string[]]$Fields) + if ($null -eq $Item) { return $false } + foreach ($field in $Fields) { + if ($Item -is [System.Collections.IDictionary]) { + if (-not $Item.Contains($field)) { return $false } + continue + } + if ($null -eq $Item.PSObject.Properties[$field]) { return $false } + } + return $true +} + +function Test-ArrayContainsAll { + param([object[]]$Actual, [string[]]$Required) + $values = @($Actual | ForEach-Object { [string]$_ }) + foreach ($item in $Required) { + if ($values -notcontains $item) { return $false } + } + return $true +} + +& powershell -ExecutionPolicy Bypass -File (Join-Path $PSScriptRoot "c19z105-remote-workspace-real-adapter-operator-enablement-readiness-package-smoke.ps1") ` + -ApiBaseUrl $ApiBaseUrl ` + -ClusterID $ClusterID ` + -ActorUserID $ActorUserID ` + -RequestedNodeName $RequestedNodeName ` + -DefaultNodeName $DefaultNodeName ` + -MatrixNodeName $MatrixNodeName ` + -ResultPath $sourceResultPath | Out-Null + +$sourceFile = Join-Path $repoRoot $sourceResultPath +$sourceResult = Get-Content -Raw -Path $sourceFile | ConvertFrom-Json +$package = Get-PropertyValue -Item $sourceResult -Name "operator_enablement_readiness_package" -Default $null +$guardrails = Get-PropertyValue -Item $package -Name "guardrail_summary" -Default $null + +$packageFieldsCompatible = Test-ObjectHasFields -Item $package -Fields $requiredPackageFields +$includedContractsCompatible = Test-ArrayContainsAll -Actual @(Get-PropertyValue -Item $package -Name "included_contracts" -Default @()) -Required $includedContracts +$operatorActionsCompatible = Test-ArrayContainsAll -Actual @(Get-PropertyValue -Item $package -Name "required_operator_actions" -Default @()) -Required $requiredOperatorActions +$packageValuesCompatible = ( + [string](Get-PropertyValue -Item $package -Name "schema_version" -Default "") -eq "rap.remote_workspace_real_adapter_operator_enablement_readiness_package.v1" -and + [string](Get-PropertyValue -Item $package -Name "source_closeout_schema" -Default "") -eq "rap.remote_workspace_real_adapter_runtime_gate_validation_closeout.v1" -and + [string](Get-PropertyValue -Item $package -Name "package_status" -Default "") -eq "ready_for_operator_review" -and + [string](Get-PropertyValue -Item $package -Name "operator_review_status" -Default "") -eq "not_reviewed" -and + [string](Get-PropertyValue -Item $package -Name "enablement_boundary" -Default "") -eq "explicit_operator_enablement_required" -and + [string](Get-PropertyValue -Item $package -Name "enablement_status" -Default "") -eq "not_enabled" -and + [string](Get-PropertyValue -Item $package -Name "runtime_gate_state" -Default "") -eq "validated_contract_only_not_enabled" -and + [string](Get-PropertyValue -Item $package -Name "runtime_effect" -Default "") -eq "contract_only_no_runtime_enablement" -and + [string](Get-PropertyValue -Item $package -Name "operator_default_action" -Default "") -eq "keep_real_adapter_disabled" -and + -not [bool](Get-PropertyValue -Item $package -Name "allows_process_start" -Default $true) -and + -not [bool](Get-PropertyValue -Item $package -Name "allows_payload_traffic" -Default $true) +) +$guardrailsCompatible = ( + (Test-ObjectHasFields -Item $guardrails -Fields $requiredGuardrailFields) -and + [bool](Get-PropertyValue -Item $guardrails -Name "activation_blocked" -Default $false) -and + -not [bool](Get-PropertyValue -Item $guardrails -Name "process_start_allowed" -Default $true) -and + -not [bool](Get-PropertyValue -Item $guardrails -Name "health_probe_enabled" -Default $true) -and + [string](Get-PropertyValue -Item $guardrails -Name "payload_traffic" -Default "") -eq "none" -and + -not [bool](Get-PropertyValue -Item $guardrails -Name "allows_process_start" -Default $true) -and + -not [bool](Get-PropertyValue -Item $guardrails -Name "allows_payload_traffic" -Default $true) +) + +$checks = [ordered]@{ + source_smoke_passed = ([bool]$sourceResult.passed) + source_schema_expected = ([string]$sourceResult.schema_version -eq "c19z105.remote_workspace_real_adapter_operator_enablement_readiness_package_smoke.v1") + package_present = ($null -ne $package) + package_fields_compatible = $packageFieldsCompatible + package_values_compatible = $packageValuesCompatible + included_contracts_compatible = $includedContractsCompatible + operator_actions_compatible = $operatorActionsCompatible + guardrails_compatible = $guardrailsCompatible +} +$failed = @($checks.GetEnumerator() | Where-Object { -not $_.Value } | ForEach-Object { $_.Key }) + +$result = [ordered]@{ + schema_version = "c19z106.remote_workspace_real_adapter_operator_enablement_readiness_package_compatibility_smoke.v1" + source_result_path = $sourceFile + cluster_id = $ClusterID + required_package_fields = $requiredPackageFields + included_contracts = $includedContracts + required_operator_actions = $requiredOperatorActions + required_guardrail_fields = $requiredGuardrailFields + operator_enablement_readiness_package = $package + checks = $checks + failed_checks = $failed + passed = ($failed.Count -eq 0) +} + +$fullResultPath = Join-Path $repoRoot $ResultPath +$resultDir = Split-Path -Parent $fullResultPath +if ($resultDir) { New-Item -ItemType Directory -Force -Path $resultDir | Out-Null } +$result | ConvertTo-Json -Depth 100 | Set-Content -Encoding UTF8 -Path $fullResultPath + +if (-not $result.passed) { + throw "C19Z106 remote workspace real-adapter operator enablement readiness package compatibility smoke failed. Result: $fullResultPath Failed: $($failed -join ', ')" +} + +Write-Host "C19Z106 remote workspace real-adapter operator enablement readiness package compatibility smoke passed. Result: $fullResultPath" +$result diff --git a/scripts/fabric/c19z107-remote-workspace-real-adapter-operator-enablement-readiness-release-marker-smoke.ps1 b/scripts/fabric/c19z107-remote-workspace-real-adapter-operator-enablement-readiness-release-marker-smoke.ps1 new file mode 100644 index 0000000..0bb79ec --- /dev/null +++ b/scripts/fabric/c19z107-remote-workspace-real-adapter-operator-enablement-readiness-release-marker-smoke.ps1 @@ -0,0 +1,145 @@ +param( + [string]$ApiBaseUrl = "http://192.168.200.61:18121/api/v1", + [string]$ClusterID = "cfc0743d-d960-49fb-9de8-96e063d5e4aa", + [string]$ActorUserID = "f67d943f-5397-4b3a-a229-695fe67ad700", + [string]$RequestedNodeName = "test-1", + [string]$DefaultNodeName = "test-2", + [string]$MatrixNodeName = "test-1", + [string]$ResultPath = "artifacts\c19z107-remote-workspace-real-adapter-operator-enablement-readiness-release-marker-smoke-result.json" +) + +Set-StrictMode -Version Latest +$ErrorActionPreference = "Stop" + +$repoRoot = (Resolve-Path (Join-Path $PSScriptRoot "..\..")).ProviderPath +$sourceResultPath = "artifacts\c19z107-remote-workspace-real-adapter-operator-enablement-readiness-release-marker-source-result.json" +$requiredReleaseFields = @( + "schema_version", + "source_package_schema", + "release_status", + "release_marker", + "operator_review_status", + "enablement_boundary", + "enablement_status", + "runtime_gate_state", + "runtime_effect", + "operator_default_action", + "allows_process_start", + "allows_payload_traffic", + "guardrail_summary" +) +$requiredGuardrailFields = @("activation_blocked", "process_start_allowed", "health_probe_enabled", "payload_traffic", "allows_process_start", "allows_payload_traffic") + +function Get-PropertyValue { + param([object]$Item, [string]$Name, [object]$Default = $null) + if ($null -eq $Item) { return $Default } + if ($Item -is [System.Collections.IDictionary]) { + if ($Item.Contains($Name)) { return $Item[$Name] } + return $Default + } + $property = $Item.PSObject.Properties[$Name] + if ($null -eq $property) { return $Default } + return $property.Value +} + +function Test-ObjectHasFields { + param([object]$Item, [string[]]$Fields) + if ($null -eq $Item) { return $false } + foreach ($field in $Fields) { + if ($Item -is [System.Collections.IDictionary]) { + if (-not $Item.Contains($field)) { return $false } + continue + } + if ($null -eq $Item.PSObject.Properties[$field]) { return $false } + } + return $true +} + +& powershell -ExecutionPolicy Bypass -File (Join-Path $PSScriptRoot "c19z106-remote-workspace-real-adapter-operator-enablement-readiness-package-compatibility-smoke.ps1") ` + -ApiBaseUrl $ApiBaseUrl ` + -ClusterID $ClusterID ` + -ActorUserID $ActorUserID ` + -RequestedNodeName $RequestedNodeName ` + -DefaultNodeName $DefaultNodeName ` + -MatrixNodeName $MatrixNodeName ` + -ResultPath $sourceResultPath | Out-Null + +$sourceFile = Join-Path $repoRoot $sourceResultPath +$sourceResult = Get-Content -Raw -Path $sourceFile | ConvertFrom-Json +$package = Get-PropertyValue -Item $sourceResult -Name "operator_enablement_readiness_package" -Default $null +$guardrails = Get-PropertyValue -Item $package -Name "guardrail_summary" -Default $null + +$releaseMarker = [ordered]@{ + schema_version = "rap.remote_workspace_real_adapter_operator_enablement_readiness_release_marker.v1" + source_package_schema = Get-PropertyValue -Item $package -Name "schema_version" -Default $null + release_status = "operator_readiness_package_contract_only" + release_marker = "c19z107_real_adapter_operator_enablement_readiness_contract_only" + operator_review_status = Get-PropertyValue -Item $package -Name "operator_review_status" -Default $null + enablement_boundary = Get-PropertyValue -Item $package -Name "enablement_boundary" -Default $null + enablement_status = Get-PropertyValue -Item $package -Name "enablement_status" -Default $null + runtime_gate_state = Get-PropertyValue -Item $package -Name "runtime_gate_state" -Default $null + runtime_effect = Get-PropertyValue -Item $package -Name "runtime_effect" -Default $null + operator_default_action = Get-PropertyValue -Item $package -Name "operator_default_action" -Default $null + allows_process_start = $false + allows_payload_traffic = $false + guardrail_summary = $guardrails +} + +$releaseFieldsCompatible = Test-ObjectHasFields -Item $releaseMarker -Fields $requiredReleaseFields +$releaseValuesCompatible = ( + [string](Get-PropertyValue -Item $releaseMarker -Name "schema_version" -Default "") -eq "rap.remote_workspace_real_adapter_operator_enablement_readiness_release_marker.v1" -and + [string](Get-PropertyValue -Item $releaseMarker -Name "source_package_schema" -Default "") -eq "rap.remote_workspace_real_adapter_operator_enablement_readiness_package.v1" -and + [string](Get-PropertyValue -Item $releaseMarker -Name "release_status" -Default "") -eq "operator_readiness_package_contract_only" -and + [string](Get-PropertyValue -Item $releaseMarker -Name "release_marker" -Default "") -eq "c19z107_real_adapter_operator_enablement_readiness_contract_only" -and + [string](Get-PropertyValue -Item $releaseMarker -Name "operator_review_status" -Default "") -eq "not_reviewed" -and + [string](Get-PropertyValue -Item $releaseMarker -Name "enablement_boundary" -Default "") -eq "explicit_operator_enablement_required" -and + [string](Get-PropertyValue -Item $releaseMarker -Name "enablement_status" -Default "") -eq "not_enabled" -and + [string](Get-PropertyValue -Item $releaseMarker -Name "runtime_gate_state" -Default "") -eq "validated_contract_only_not_enabled" -and + [string](Get-PropertyValue -Item $releaseMarker -Name "runtime_effect" -Default "") -eq "contract_only_no_runtime_enablement" -and + [string](Get-PropertyValue -Item $releaseMarker -Name "operator_default_action" -Default "") -eq "keep_real_adapter_disabled" -and + -not [bool](Get-PropertyValue -Item $releaseMarker -Name "allows_process_start" -Default $true) -and + -not [bool](Get-PropertyValue -Item $releaseMarker -Name "allows_payload_traffic" -Default $true) +) +$guardrailsCompatible = ( + (Test-ObjectHasFields -Item $guardrails -Fields $requiredGuardrailFields) -and + [bool](Get-PropertyValue -Item $guardrails -Name "activation_blocked" -Default $false) -and + -not [bool](Get-PropertyValue -Item $guardrails -Name "process_start_allowed" -Default $true) -and + -not [bool](Get-PropertyValue -Item $guardrails -Name "health_probe_enabled" -Default $true) -and + [string](Get-PropertyValue -Item $guardrails -Name "payload_traffic" -Default "") -eq "none" -and + -not [bool](Get-PropertyValue -Item $guardrails -Name "allows_process_start" -Default $true) -and + -not [bool](Get-PropertyValue -Item $guardrails -Name "allows_payload_traffic" -Default $true) +) + +$checks = [ordered]@{ + source_smoke_passed = ([bool]$sourceResult.passed) + source_schema_expected = ([string]$sourceResult.schema_version -eq "c19z106.remote_workspace_real_adapter_operator_enablement_readiness_package_compatibility_smoke.v1") + package_present = ($null -ne $package) + release_fields_compatible = $releaseFieldsCompatible + release_values_compatible = $releaseValuesCompatible + guardrails_compatible = $guardrailsCompatible +} +$failed = @($checks.GetEnumerator() | Where-Object { -not $_.Value } | ForEach-Object { $_.Key }) + +$result = [ordered]@{ + schema_version = "c19z107.remote_workspace_real_adapter_operator_enablement_readiness_release_marker_smoke.v1" + source_result_path = $sourceFile + cluster_id = $ClusterID + required_release_fields = $requiredReleaseFields + required_guardrail_fields = $requiredGuardrailFields + operator_enablement_readiness_release_marker = $releaseMarker + checks = $checks + failed_checks = $failed + passed = ($failed.Count -eq 0) +} + +$fullResultPath = Join-Path $repoRoot $ResultPath +$resultDir = Split-Path -Parent $fullResultPath +if ($resultDir) { New-Item -ItemType Directory -Force -Path $resultDir | Out-Null } +$result | ConvertTo-Json -Depth 100 | Set-Content -Encoding UTF8 -Path $fullResultPath + +if (-not $result.passed) { + throw "C19Z107 remote workspace real-adapter operator enablement readiness release marker smoke failed. Result: $fullResultPath Failed: $($failed -join ', ')" +} + +Write-Host "C19Z107 remote workspace real-adapter operator enablement readiness release marker smoke passed. Result: $fullResultPath" +$result diff --git a/scripts/fabric/c19z108-remote-workspace-real-adapter-operator-enablement-readiness-release-marker-compatibility-smoke.ps1 b/scripts/fabric/c19z108-remote-workspace-real-adapter-operator-enablement-readiness-release-marker-compatibility-smoke.ps1 new file mode 100644 index 0000000..85c9357 --- /dev/null +++ b/scripts/fabric/c19z108-remote-workspace-real-adapter-operator-enablement-readiness-release-marker-compatibility-smoke.ps1 @@ -0,0 +1,129 @@ +param( + [string]$ApiBaseUrl = "http://192.168.200.61:18121/api/v1", + [string]$ClusterID = "cfc0743d-d960-49fb-9de8-96e063d5e4aa", + [string]$ActorUserID = "f67d943f-5397-4b3a-a229-695fe67ad700", + [string]$RequestedNodeName = "test-1", + [string]$DefaultNodeName = "test-2", + [string]$MatrixNodeName = "test-1", + [string]$ResultPath = "artifacts\c19z108-remote-workspace-real-adapter-operator-enablement-readiness-release-marker-compatibility-smoke-result.json" +) + +Set-StrictMode -Version Latest +$ErrorActionPreference = "Stop" + +$repoRoot = (Resolve-Path (Join-Path $PSScriptRoot "..\..")).ProviderPath +$sourceResultPath = "artifacts\c19z108-remote-workspace-real-adapter-operator-enablement-readiness-release-marker-source-result.json" +$requiredReleaseFields = @( + "schema_version", + "source_package_schema", + "release_status", + "release_marker", + "operator_review_status", + "enablement_boundary", + "enablement_status", + "runtime_gate_state", + "runtime_effect", + "operator_default_action", + "allows_process_start", + "allows_payload_traffic", + "guardrail_summary" +) +$requiredGuardrailFields = @("activation_blocked", "process_start_allowed", "health_probe_enabled", "payload_traffic", "allows_process_start", "allows_payload_traffic") + +function Get-PropertyValue { + param([object]$Item, [string]$Name, [object]$Default = $null) + if ($null -eq $Item) { return $Default } + if ($Item -is [System.Collections.IDictionary]) { + if ($Item.Contains($Name)) { return $Item[$Name] } + return $Default + } + $property = $Item.PSObject.Properties[$Name] + if ($null -eq $property) { return $Default } + return $property.Value +} + +function Test-ObjectHasFields { + param([object]$Item, [string[]]$Fields) + if ($null -eq $Item) { return $false } + foreach ($field in $Fields) { + if ($Item -is [System.Collections.IDictionary]) { + if (-not $Item.Contains($field)) { return $false } + continue + } + if ($null -eq $Item.PSObject.Properties[$field]) { return $false } + } + return $true +} + +& powershell -ExecutionPolicy Bypass -File (Join-Path $PSScriptRoot "c19z107-remote-workspace-real-adapter-operator-enablement-readiness-release-marker-smoke.ps1") ` + -ApiBaseUrl $ApiBaseUrl ` + -ClusterID $ClusterID ` + -ActorUserID $ActorUserID ` + -RequestedNodeName $RequestedNodeName ` + -DefaultNodeName $DefaultNodeName ` + -MatrixNodeName $MatrixNodeName ` + -ResultPath $sourceResultPath | Out-Null + +$sourceFile = Join-Path $repoRoot $sourceResultPath +$sourceResult = Get-Content -Raw -Path $sourceFile | ConvertFrom-Json +$marker = Get-PropertyValue -Item $sourceResult -Name "operator_enablement_readiness_release_marker" -Default $null +$guardrails = Get-PropertyValue -Item $marker -Name "guardrail_summary" -Default $null + +$releaseFieldsCompatible = Test-ObjectHasFields -Item $marker -Fields $requiredReleaseFields +$releaseValuesCompatible = ( + [string](Get-PropertyValue -Item $marker -Name "schema_version" -Default "") -eq "rap.remote_workspace_real_adapter_operator_enablement_readiness_release_marker.v1" -and + [string](Get-PropertyValue -Item $marker -Name "source_package_schema" -Default "") -eq "rap.remote_workspace_real_adapter_operator_enablement_readiness_package.v1" -and + [string](Get-PropertyValue -Item $marker -Name "release_status" -Default "") -eq "operator_readiness_package_contract_only" -and + [string](Get-PropertyValue -Item $marker -Name "release_marker" -Default "") -eq "c19z107_real_adapter_operator_enablement_readiness_contract_only" -and + [string](Get-PropertyValue -Item $marker -Name "operator_review_status" -Default "") -eq "not_reviewed" -and + [string](Get-PropertyValue -Item $marker -Name "enablement_boundary" -Default "") -eq "explicit_operator_enablement_required" -and + [string](Get-PropertyValue -Item $marker -Name "enablement_status" -Default "") -eq "not_enabled" -and + [string](Get-PropertyValue -Item $marker -Name "runtime_gate_state" -Default "") -eq "validated_contract_only_not_enabled" -and + [string](Get-PropertyValue -Item $marker -Name "runtime_effect" -Default "") -eq "contract_only_no_runtime_enablement" -and + [string](Get-PropertyValue -Item $marker -Name "operator_default_action" -Default "") -eq "keep_real_adapter_disabled" -and + -not [bool](Get-PropertyValue -Item $marker -Name "allows_process_start" -Default $true) -and + -not [bool](Get-PropertyValue -Item $marker -Name "allows_payload_traffic" -Default $true) +) +$guardrailsCompatible = ( + (Test-ObjectHasFields -Item $guardrails -Fields $requiredGuardrailFields) -and + [bool](Get-PropertyValue -Item $guardrails -Name "activation_blocked" -Default $false) -and + -not [bool](Get-PropertyValue -Item $guardrails -Name "process_start_allowed" -Default $true) -and + -not [bool](Get-PropertyValue -Item $guardrails -Name "health_probe_enabled" -Default $true) -and + [string](Get-PropertyValue -Item $guardrails -Name "payload_traffic" -Default "") -eq "none" -and + -not [bool](Get-PropertyValue -Item $guardrails -Name "allows_process_start" -Default $true) -and + -not [bool](Get-PropertyValue -Item $guardrails -Name "allows_payload_traffic" -Default $true) +) + +$checks = [ordered]@{ + source_smoke_passed = ([bool]$sourceResult.passed) + source_schema_expected = ([string]$sourceResult.schema_version -eq "c19z107.remote_workspace_real_adapter_operator_enablement_readiness_release_marker_smoke.v1") + release_marker_present = ($null -ne $marker) + release_fields_compatible = $releaseFieldsCompatible + release_values_compatible = $releaseValuesCompatible + guardrails_compatible = $guardrailsCompatible +} +$failed = @($checks.GetEnumerator() | Where-Object { -not $_.Value } | ForEach-Object { $_.Key }) + +$result = [ordered]@{ + schema_version = "c19z108.remote_workspace_real_adapter_operator_enablement_readiness_release_marker_compatibility_smoke.v1" + source_result_path = $sourceFile + cluster_id = $ClusterID + required_release_fields = $requiredReleaseFields + required_guardrail_fields = $requiredGuardrailFields + operator_enablement_readiness_release_marker = $marker + checks = $checks + failed_checks = $failed + passed = ($failed.Count -eq 0) +} + +$fullResultPath = Join-Path $repoRoot $ResultPath +$resultDir = Split-Path -Parent $fullResultPath +if ($resultDir) { New-Item -ItemType Directory -Force -Path $resultDir | Out-Null } +$result | ConvertTo-Json -Depth 100 | Set-Content -Encoding UTF8 -Path $fullResultPath + +if (-not $result.passed) { + throw "C19Z108 remote workspace real-adapter operator enablement readiness release marker compatibility smoke failed. Result: $fullResultPath Failed: $($failed -join ', ')" +} + +Write-Host "C19Z108 remote workspace real-adapter operator enablement readiness release marker compatibility smoke passed. Result: $fullResultPath" +$result diff --git a/scripts/fabric/c19z109-remote-workspace-real-adapter-operator-enablement-readiness-package-index-smoke.ps1 b/scripts/fabric/c19z109-remote-workspace-real-adapter-operator-enablement-readiness-package-index-smoke.ps1 new file mode 100644 index 0000000..3d65a2c --- /dev/null +++ b/scripts/fabric/c19z109-remote-workspace-real-adapter-operator-enablement-readiness-package-index-smoke.ps1 @@ -0,0 +1,175 @@ +param( + [string]$ApiBaseUrl = "http://192.168.200.61:18121/api/v1", + [string]$ClusterID = "cfc0743d-d960-49fb-9de8-96e063d5e4aa", + [string]$ActorUserID = "f67d943f-5397-4b3a-a229-695fe67ad700", + [string]$RequestedNodeName = "test-1", + [string]$DefaultNodeName = "test-2", + [string]$MatrixNodeName = "test-1", + [string]$ResultPath = "artifacts\c19z109-remote-workspace-real-adapter-operator-enablement-readiness-package-index-smoke-result.json" +) + +Set-StrictMode -Version Latest +$ErrorActionPreference = "Stop" + +$repoRoot = (Resolve-Path (Join-Path $PSScriptRoot "..\..")).ProviderPath +$sourceResultPath = "artifacts\c19z109-remote-workspace-real-adapter-operator-enablement-readiness-package-index-source-result.json" +$requiredPackageFields = @( + "schema_version", + "source_release_marker_schema", + "package_status", + "package_marker", + "covered_stage_range", + "covered_stage_count", + "latest_compatibility_stage", + "operator_review_status", + "enablement_boundary", + "enablement_status", + "runtime_gate_state", + "runtime_effect", + "operator_default_action", + "allows_process_start", + "allows_payload_traffic", + "guardrail_summary", + "closeout_notes" +) +$requiredGuardrailFields = @("activation_blocked", "process_start_allowed", "health_probe_enabled", "payload_traffic", "allows_process_start", "allows_payload_traffic") +$requiredCloseoutNotes = @( + "operator_enablement_readiness_package_indexed", + "operator_review_not_started", + "real_runtime_gate_not_enabled", + "process_start_disabled", + "payload_forwarding_disabled" +) + +function Get-PropertyValue { + param([object]$Item, [string]$Name, [object]$Default = $null) + if ($null -eq $Item) { return $Default } + if ($Item -is [System.Collections.IDictionary]) { + if ($Item.Contains($Name)) { return $Item[$Name] } + return $Default + } + $property = $Item.PSObject.Properties[$Name] + if ($null -eq $property) { return $Default } + return $property.Value +} + +function Test-ObjectHasFields { + param([object]$Item, [string[]]$Fields) + if ($null -eq $Item) { return $false } + foreach ($field in $Fields) { + if ($Item -is [System.Collections.IDictionary]) { + if (-not $Item.Contains($field)) { return $false } + continue + } + if ($null -eq $Item.PSObject.Properties[$field]) { return $false } + } + return $true +} + +function Test-ArrayContainsAll { + param([object[]]$Actual, [string[]]$Required) + $values = @($Actual | ForEach-Object { [string]$_ }) + foreach ($item in $Required) { + if ($values -notcontains $item) { return $false } + } + return $true +} + +& powershell -ExecutionPolicy Bypass -File (Join-Path $PSScriptRoot "c19z108-remote-workspace-real-adapter-operator-enablement-readiness-release-marker-compatibility-smoke.ps1") ` + -ApiBaseUrl $ApiBaseUrl ` + -ClusterID $ClusterID ` + -ActorUserID $ActorUserID ` + -RequestedNodeName $RequestedNodeName ` + -DefaultNodeName $DefaultNodeName ` + -MatrixNodeName $MatrixNodeName ` + -ResultPath $sourceResultPath | Out-Null + +$sourceFile = Join-Path $repoRoot $sourceResultPath +$sourceResult = Get-Content -Raw -Path $sourceFile | ConvertFrom-Json +$marker = Get-PropertyValue -Item $sourceResult -Name "operator_enablement_readiness_release_marker" -Default $null +$guardrails = Get-PropertyValue -Item $marker -Name "guardrail_summary" -Default $null + +$packageIndex = [ordered]@{ + schema_version = "rap.remote_workspace_real_adapter_operator_enablement_readiness_package_index.v1" + source_release_marker_schema = Get-PropertyValue -Item $marker -Name "schema_version" -Default $null + package_status = "indexed_contract_only" + package_marker = "c19z109_real_adapter_operator_enablement_readiness_package_index_contract_only" + covered_stage_range = "C19Z89-C19Z108" + covered_stage_count = 20 + latest_compatibility_stage = "C19Z108" + operator_review_status = Get-PropertyValue -Item $marker -Name "operator_review_status" -Default $null + enablement_boundary = Get-PropertyValue -Item $marker -Name "enablement_boundary" -Default $null + enablement_status = Get-PropertyValue -Item $marker -Name "enablement_status" -Default $null + runtime_gate_state = Get-PropertyValue -Item $marker -Name "runtime_gate_state" -Default $null + runtime_effect = Get-PropertyValue -Item $marker -Name "runtime_effect" -Default $null + operator_default_action = Get-PropertyValue -Item $marker -Name "operator_default_action" -Default $null + allows_process_start = $false + allows_payload_traffic = $false + guardrail_summary = $guardrails + closeout_notes = $requiredCloseoutNotes +} + +$packageFieldsCompatible = Test-ObjectHasFields -Item $packageIndex -Fields $requiredPackageFields +$closeoutNotesCompatible = Test-ArrayContainsAll -Actual @(Get-PropertyValue -Item $packageIndex -Name "closeout_notes" -Default @()) -Required $requiredCloseoutNotes +$packageValuesCompatible = ( + [string](Get-PropertyValue -Item $packageIndex -Name "schema_version" -Default "") -eq "rap.remote_workspace_real_adapter_operator_enablement_readiness_package_index.v1" -and + [string](Get-PropertyValue -Item $packageIndex -Name "source_release_marker_schema" -Default "") -eq "rap.remote_workspace_real_adapter_operator_enablement_readiness_release_marker.v1" -and + [string](Get-PropertyValue -Item $packageIndex -Name "package_status" -Default "") -eq "indexed_contract_only" -and + [string](Get-PropertyValue -Item $packageIndex -Name "package_marker" -Default "") -eq "c19z109_real_adapter_operator_enablement_readiness_package_index_contract_only" -and + [string](Get-PropertyValue -Item $packageIndex -Name "covered_stage_range" -Default "") -eq "C19Z89-C19Z108" -and + [int](Get-PropertyValue -Item $packageIndex -Name "covered_stage_count" -Default -1) -eq 20 -and + [string](Get-PropertyValue -Item $packageIndex -Name "latest_compatibility_stage" -Default "") -eq "C19Z108" -and + [string](Get-PropertyValue -Item $packageIndex -Name "operator_review_status" -Default "") -eq "not_reviewed" -and + [string](Get-PropertyValue -Item $packageIndex -Name "enablement_boundary" -Default "") -eq "explicit_operator_enablement_required" -and + [string](Get-PropertyValue -Item $packageIndex -Name "enablement_status" -Default "") -eq "not_enabled" -and + [string](Get-PropertyValue -Item $packageIndex -Name "runtime_gate_state" -Default "") -eq "validated_contract_only_not_enabled" -and + [string](Get-PropertyValue -Item $packageIndex -Name "runtime_effect" -Default "") -eq "contract_only_no_runtime_enablement" -and + [string](Get-PropertyValue -Item $packageIndex -Name "operator_default_action" -Default "") -eq "keep_real_adapter_disabled" -and + -not [bool](Get-PropertyValue -Item $packageIndex -Name "allows_process_start" -Default $true) -and + -not [bool](Get-PropertyValue -Item $packageIndex -Name "allows_payload_traffic" -Default $true) +) +$guardrailsCompatible = ( + (Test-ObjectHasFields -Item $guardrails -Fields $requiredGuardrailFields) -and + [bool](Get-PropertyValue -Item $guardrails -Name "activation_blocked" -Default $false) -and + -not [bool](Get-PropertyValue -Item $guardrails -Name "process_start_allowed" -Default $true) -and + -not [bool](Get-PropertyValue -Item $guardrails -Name "health_probe_enabled" -Default $true) -and + [string](Get-PropertyValue -Item $guardrails -Name "payload_traffic" -Default "") -eq "none" -and + -not [bool](Get-PropertyValue -Item $guardrails -Name "allows_process_start" -Default $true) -and + -not [bool](Get-PropertyValue -Item $guardrails -Name "allows_payload_traffic" -Default $true) +) + +$checks = [ordered]@{ + source_smoke_passed = ([bool]$sourceResult.passed) + source_schema_expected = ([string]$sourceResult.schema_version -eq "c19z108.remote_workspace_real_adapter_operator_enablement_readiness_release_marker_compatibility_smoke.v1") + release_marker_present = ($null -ne $marker) + package_fields_compatible = $packageFieldsCompatible + package_values_compatible = $packageValuesCompatible + closeout_notes_compatible = $closeoutNotesCompatible + guardrails_compatible = $guardrailsCompatible +} +$failed = @($checks.GetEnumerator() | Where-Object { -not $_.Value } | ForEach-Object { $_.Key }) + +$result = [ordered]@{ + schema_version = "c19z109.remote_workspace_real_adapter_operator_enablement_readiness_package_index_smoke.v1" + source_result_path = $sourceFile + cluster_id = $ClusterID + required_package_fields = $requiredPackageFields + required_guardrail_fields = $requiredGuardrailFields + required_closeout_notes = $requiredCloseoutNotes + operator_enablement_readiness_package_index = $packageIndex + checks = $checks + failed_checks = $failed + passed = ($failed.Count -eq 0) +} + +$fullResultPath = Join-Path $repoRoot $ResultPath +$resultDir = Split-Path -Parent $fullResultPath +if ($resultDir) { New-Item -ItemType Directory -Force -Path $resultDir | Out-Null } +$result | ConvertTo-Json -Depth 100 | Set-Content -Encoding UTF8 -Path $fullResultPath + +if (-not $result.passed) { + throw "C19Z109 remote workspace real-adapter operator enablement readiness package index smoke failed. Result: $fullResultPath Failed: $($failed -join ', ')" +} + +Write-Host "C19Z109 remote workspace real-adapter operator enablement readiness package index smoke passed. Result: $fullResultPath" +$result diff --git a/scripts/fabric/c19z11-remote-workspace-mailbox-preflight-checklist-status-smoke.ps1 b/scripts/fabric/c19z11-remote-workspace-mailbox-preflight-checklist-status-smoke.ps1 new file mode 100644 index 0000000..048180c --- /dev/null +++ b/scripts/fabric/c19z11-remote-workspace-mailbox-preflight-checklist-status-smoke.ps1 @@ -0,0 +1,87 @@ +param( + [string]$ApiBaseUrl = "http://192.168.200.61:18121/api/v1", + [string]$ClusterID = "cfc0743d-d960-49fb-9de8-96e063d5e4aa", + [string]$ActorUserID = "f67d943f-5397-4b3a-a229-695fe67ad700", + [string]$EntryNodeName = "test-1", + [string]$ExitNodeName = "test-2", + [string]$EntryBaseUrl = "http://192.168.200.61:19131", + [string]$ResultPath = "artifacts\c19z11-remote-workspace-mailbox-preflight-checklist-status-smoke-result.json" +) + +Set-StrictMode -Version Latest +$ErrorActionPreference = "Stop" + +$repoRoot = (Resolve-Path (Join-Path $PSScriptRoot "..\..")).ProviderPath +$sourceResultPath = "artifacts\c19z11-remote-workspace-mailbox-preflight-checklist-status-source-result.json" + +function Get-PropertyValue { + param([object]$Item, [string]$Name, [object]$Default = $null) + if ($null -eq $Item) { return $Default } + $property = $Item.PSObject.Properties[$Name] + if ($null -eq $property) { return $Default } + return $property.Value +} + +function Test-ChecklistStatus { + param([object]$Sink, [string]$AdapterSessionID) + if ($null -eq $Sink) { return $false } + $readiness = Get-PropertyValue -Item $Sink -Name "adapter_runtime_readiness" -Default $null + $rollup = Get-PropertyValue -Item $readiness -Name "last_preflight" -Default $null + if ($null -eq $rollup) { return $false } + $counts = Get-PropertyValue -Item $rollup -Name "remediation_checklist_counts" -Default $null + return ( + [string](Get-PropertyValue -Item $readiness -Name "adapter_session_id" -Default "") -eq $AdapterSessionID -and + [string](Get-PropertyValue -Item $rollup -Name "operator_status" -Default "") -eq "resync_required" -and + [string](Get-PropertyValue -Item $rollup -Name "remediation_checklist_status" -Default "") -eq "action_required" -and + [string](Get-PropertyValue -Item $counts -Name "status" -Default "") -eq "action_required" -and + [int](Get-PropertyValue -Item $counts -Name "total_count" -Default -1) -eq 3 -and + [int](Get-PropertyValue -Item $counts -Name "required_count" -Default -1) -eq 3 -and + [int](Get-PropertyValue -Item $counts -Name "satisfied_count" -Default -1) -eq 0 -and + [int](Get-PropertyValue -Item $counts -Name "pending_count" -Default -1) -eq 3 + ) +} + +$source = & powershell -ExecutionPolicy Bypass -File (Join-Path $PSScriptRoot "c19z10-remote-workspace-mailbox-preflight-checklist-smoke.ps1") ` + -ApiBaseUrl $ApiBaseUrl ` + -ClusterID $ClusterID ` + -ActorUserID $ActorUserID ` + -EntryNodeName $EntryNodeName ` + -ExitNodeName $ExitNodeName ` + -EntryBaseUrl $EntryBaseUrl ` + -ResultPath $sourceResultPath + +$sourceFile = Join-Path $repoRoot $sourceResultPath +$sourceResult = Get-Content -Raw -Path $sourceFile | ConvertFrom-Json +$adapterSessionID = [string](Get-PropertyValue -Item $sourceResult -Name "adapter_session_id" -Default "") +$observed = Get-PropertyValue -Item $sourceResult -Name "observed" -Default $null + +$checks = [ordered]@{ + source_smoke_passed = ([bool]$sourceResult.passed) + adapter_session_id_present = ($adapterSessionID -match "^rap-rw-adapter-session-[0-9a-f]{24}$") + workload_checklist_status_visible = (Test-ChecklistStatus -Sink $observed.workload_sink -AdapterSessionID $adapterSessionID) + telemetry_checklist_status_visible = (Test-ChecklistStatus -Sink $observed.telemetry_sink -AdapterSessionID $adapterSessionID) +} +$failed = @($checks.GetEnumerator() | Where-Object { -not $_.Value } | ForEach-Object { $_.Key }) + +$result = [ordered]@{ + schema_version = "c19z11.remote_workspace_mailbox_preflight_checklist_status_smoke.v1" + source_result_path = $sourceFile + cluster_id = $ClusterID + adapter_session_id = $adapterSessionID + observed = $observed + checks = $checks + failed_checks = $failed + passed = ($failed.Count -eq 0) +} + +$fullResultPath = Join-Path $repoRoot $ResultPath +$resultDir = Split-Path -Parent $fullResultPath +if ($resultDir) { New-Item -ItemType Directory -Force -Path $resultDir | Out-Null } +$result | ConvertTo-Json -Depth 100 | Set-Content -Encoding UTF8 -Path $fullResultPath + +if (-not $result.passed) { + throw "C19Z11 remote workspace mailbox preflight checklist status smoke failed. Result: $fullResultPath Failed: $($failed -join ', ')" +} + +Write-Host "C19Z11 remote workspace mailbox preflight checklist status smoke passed. Result: $fullResultPath" +$result diff --git a/scripts/fabric/c19z110-remote-workspace-real-adapter-operator-enablement-readiness-package-index-compatibility-smoke.ps1 b/scripts/fabric/c19z110-remote-workspace-real-adapter-operator-enablement-readiness-package-index-compatibility-smoke.ps1 new file mode 100644 index 0000000..d5648ff --- /dev/null +++ b/scripts/fabric/c19z110-remote-workspace-real-adapter-operator-enablement-readiness-package-index-compatibility-smoke.ps1 @@ -0,0 +1,156 @@ +param( + [string]$ApiBaseUrl = "http://192.168.200.61:18121/api/v1", + [string]$ClusterID = "cfc0743d-d960-49fb-9de8-96e063d5e4aa", + [string]$ActorUserID = "f67d943f-5397-4b3a-a229-695fe67ad700", + [string]$RequestedNodeName = "test-1", + [string]$DefaultNodeName = "test-2", + [string]$MatrixNodeName = "test-1", + [string]$ResultPath = "artifacts\c19z110-remote-workspace-real-adapter-operator-enablement-readiness-package-index-compatibility-smoke-result.json" +) + +Set-StrictMode -Version Latest +$ErrorActionPreference = "Stop" + +$repoRoot = (Resolve-Path (Join-Path $PSScriptRoot "..\..")).ProviderPath +$sourceResultPath = "artifacts\c19z110-remote-workspace-real-adapter-operator-enablement-readiness-package-index-source-result.json" +$requiredPackageFields = @( + "schema_version", + "source_release_marker_schema", + "package_status", + "package_marker", + "covered_stage_range", + "covered_stage_count", + "latest_compatibility_stage", + "operator_review_status", + "enablement_boundary", + "enablement_status", + "runtime_gate_state", + "runtime_effect", + "operator_default_action", + "allows_process_start", + "allows_payload_traffic", + "guardrail_summary", + "closeout_notes" +) +$requiredGuardrailFields = @("activation_blocked", "process_start_allowed", "health_probe_enabled", "payload_traffic", "allows_process_start", "allows_payload_traffic") +$requiredCloseoutNotes = @( + "operator_enablement_readiness_package_indexed", + "operator_review_not_started", + "real_runtime_gate_not_enabled", + "process_start_disabled", + "payload_forwarding_disabled" +) + +function Get-PropertyValue { + param([object]$Item, [string]$Name, [object]$Default = $null) + if ($null -eq $Item) { return $Default } + if ($Item -is [System.Collections.IDictionary]) { + if ($Item.Contains($Name)) { return $Item[$Name] } + return $Default + } + $property = $Item.PSObject.Properties[$Name] + if ($null -eq $property) { return $Default } + return $property.Value +} + +function Test-ObjectHasFields { + param([object]$Item, [string[]]$Fields) + if ($null -eq $Item) { return $false } + foreach ($field in $Fields) { + if ($Item -is [System.Collections.IDictionary]) { + if (-not $Item.Contains($field)) { return $false } + continue + } + if ($null -eq $Item.PSObject.Properties[$field]) { return $false } + } + return $true +} + +function Test-ArrayContainsAll { + param([object[]]$Actual, [string[]]$Required) + $values = @($Actual | ForEach-Object { [string]$_ }) + foreach ($item in $Required) { + if ($values -notcontains $item) { return $false } + } + return $true +} + +& powershell -ExecutionPolicy Bypass -File (Join-Path $PSScriptRoot "c19z109-remote-workspace-real-adapter-operator-enablement-readiness-package-index-smoke.ps1") ` + -ApiBaseUrl $ApiBaseUrl ` + -ClusterID $ClusterID ` + -ActorUserID $ActorUserID ` + -RequestedNodeName $RequestedNodeName ` + -DefaultNodeName $DefaultNodeName ` + -MatrixNodeName $MatrixNodeName ` + -ResultPath $sourceResultPath | Out-Null + +$sourceFile = Join-Path $repoRoot $sourceResultPath +$sourceResult = Get-Content -Raw -Path $sourceFile | ConvertFrom-Json +$packageIndex = Get-PropertyValue -Item $sourceResult -Name "operator_enablement_readiness_package_index" -Default $null +$guardrails = Get-PropertyValue -Item $packageIndex -Name "guardrail_summary" -Default $null +$closeoutNotes = @(Get-PropertyValue -Item $packageIndex -Name "closeout_notes" -Default @()) + +$packageFieldsCompatible = Test-ObjectHasFields -Item $packageIndex -Fields $requiredPackageFields +$closeoutNotesCompatible = Test-ArrayContainsAll -Actual $closeoutNotes -Required $requiredCloseoutNotes +$packageValuesCompatible = ( + [string](Get-PropertyValue -Item $packageIndex -Name "schema_version" -Default "") -eq "rap.remote_workspace_real_adapter_operator_enablement_readiness_package_index.v1" -and + [string](Get-PropertyValue -Item $packageIndex -Name "source_release_marker_schema" -Default "") -eq "rap.remote_workspace_real_adapter_operator_enablement_readiness_release_marker.v1" -and + [string](Get-PropertyValue -Item $packageIndex -Name "package_status" -Default "") -eq "indexed_contract_only" -and + [string](Get-PropertyValue -Item $packageIndex -Name "package_marker" -Default "") -eq "c19z109_real_adapter_operator_enablement_readiness_package_index_contract_only" -and + [string](Get-PropertyValue -Item $packageIndex -Name "covered_stage_range" -Default "") -eq "C19Z89-C19Z108" -and + [int](Get-PropertyValue -Item $packageIndex -Name "covered_stage_count" -Default -1) -eq 20 -and + [string](Get-PropertyValue -Item $packageIndex -Name "latest_compatibility_stage" -Default "") -eq "C19Z108" -and + [string](Get-PropertyValue -Item $packageIndex -Name "operator_review_status" -Default "") -eq "not_reviewed" -and + [string](Get-PropertyValue -Item $packageIndex -Name "enablement_boundary" -Default "") -eq "explicit_operator_enablement_required" -and + [string](Get-PropertyValue -Item $packageIndex -Name "enablement_status" -Default "") -eq "not_enabled" -and + [string](Get-PropertyValue -Item $packageIndex -Name "runtime_gate_state" -Default "") -eq "validated_contract_only_not_enabled" -and + [string](Get-PropertyValue -Item $packageIndex -Name "runtime_effect" -Default "") -eq "contract_only_no_runtime_enablement" -and + [string](Get-PropertyValue -Item $packageIndex -Name "operator_default_action" -Default "") -eq "keep_real_adapter_disabled" -and + -not [bool](Get-PropertyValue -Item $packageIndex -Name "allows_process_start" -Default $true) -and + -not [bool](Get-PropertyValue -Item $packageIndex -Name "allows_payload_traffic" -Default $true) +) +$guardrailsCompatible = ( + (Test-ObjectHasFields -Item $guardrails -Fields $requiredGuardrailFields) -and + [bool](Get-PropertyValue -Item $guardrails -Name "activation_blocked" -Default $false) -and + -not [bool](Get-PropertyValue -Item $guardrails -Name "process_start_allowed" -Default $true) -and + -not [bool](Get-PropertyValue -Item $guardrails -Name "health_probe_enabled" -Default $true) -and + [string](Get-PropertyValue -Item $guardrails -Name "payload_traffic" -Default "") -eq "none" -and + -not [bool](Get-PropertyValue -Item $guardrails -Name "allows_process_start" -Default $true) -and + -not [bool](Get-PropertyValue -Item $guardrails -Name "allows_payload_traffic" -Default $true) +) + +$checks = [ordered]@{ + source_smoke_passed = ([bool]$sourceResult.passed) + source_schema_expected = ([string]$sourceResult.schema_version -eq "c19z109.remote_workspace_real_adapter_operator_enablement_readiness_package_index_smoke.v1") + package_index_present = ($null -ne $packageIndex) + package_fields_compatible = $packageFieldsCompatible + package_values_compatible = $packageValuesCompatible + closeout_notes_compatible = $closeoutNotesCompatible + guardrails_compatible = $guardrailsCompatible +} +$failed = @($checks.GetEnumerator() | Where-Object { -not $_.Value } | ForEach-Object { $_.Key }) + +$result = [ordered]@{ + schema_version = "c19z110.remote_workspace_real_adapter_operator_enablement_readiness_package_index_compatibility_smoke.v1" + source_result_path = $sourceFile + cluster_id = $ClusterID + required_package_fields = $requiredPackageFields + required_guardrail_fields = $requiredGuardrailFields + required_closeout_notes = $requiredCloseoutNotes + operator_enablement_readiness_package_index = $packageIndex + checks = $checks + failed_checks = $failed + passed = ($failed.Count -eq 0) +} + +$fullResultPath = Join-Path $repoRoot $ResultPath +$resultDir = Split-Path -Parent $fullResultPath +if ($resultDir) { New-Item -ItemType Directory -Force -Path $resultDir | Out-Null } +$result | ConvertTo-Json -Depth 100 | Set-Content -Encoding UTF8 -Path $fullResultPath + +if (-not $result.passed) { + throw "C19Z110 remote workspace real-adapter operator enablement readiness package index compatibility smoke failed. Result: $fullResultPath Failed: $($failed -join ', ')" +} + +Write-Host "C19Z110 remote workspace real-adapter operator enablement readiness package index compatibility smoke passed. Result: $fullResultPath" +$result diff --git a/scripts/fabric/c19z111-remote-workspace-real-adapter-operator-readiness-closeout-summary-smoke.ps1 b/scripts/fabric/c19z111-remote-workspace-real-adapter-operator-readiness-closeout-summary-smoke.ps1 new file mode 100644 index 0000000..9f353fa --- /dev/null +++ b/scripts/fabric/c19z111-remote-workspace-real-adapter-operator-readiness-closeout-summary-smoke.ps1 @@ -0,0 +1,157 @@ +param( + [string]$ApiBaseUrl = "http://192.168.200.61:18121/api/v1", + [string]$ClusterID = "cfc0743d-d960-49fb-9de8-96e063d5e4aa", + [string]$ActorUserID = "f67d943f-5397-4b3a-a229-695fe67ad700", + [string]$RequestedNodeName = "test-1", + [string]$DefaultNodeName = "test-2", + [string]$MatrixNodeName = "test-1", + [string]$ResultPath = "artifacts\c19z111-remote-workspace-real-adapter-operator-readiness-closeout-summary-smoke-result.json" +) + +Set-StrictMode -Version Latest +$ErrorActionPreference = "Stop" + +$repoRoot = (Resolve-Path (Join-Path $PSScriptRoot "..\..")).ProviderPath +$sourceResultPath = "artifacts\c19z111-remote-workspace-real-adapter-operator-readiness-closeout-summary-source-result.json" +$requiredCloseoutFields = @( + "schema_version", + "source_package_index_schema", + "closeout_status", + "closeout_marker", + "covered_stage_range", + "covered_stage_count", + "latest_compatibility_stage", + "operator_review_status", + "enablement_boundary", + "enablement_status", + "runtime_gate_state", + "runtime_effect", + "operator_default_action", + "next_required_phase", + "allows_process_start", + "allows_payload_traffic", + "guardrail_summary" +) +$requiredGuardrailFields = @("activation_blocked", "process_start_allowed", "health_probe_enabled", "payload_traffic", "allows_process_start", "allows_payload_traffic") + +function Get-PropertyValue { + param([object]$Item, [string]$Name, [object]$Default = $null) + if ($null -eq $Item) { return $Default } + if ($Item -is [System.Collections.IDictionary]) { + if ($Item.Contains($Name)) { return $Item[$Name] } + return $Default + } + $property = $Item.PSObject.Properties[$Name] + if ($null -eq $property) { return $Default } + return $property.Value +} + +function Test-ObjectHasFields { + param([object]$Item, [string[]]$Fields) + if ($null -eq $Item) { return $false } + foreach ($field in $Fields) { + if ($Item -is [System.Collections.IDictionary]) { + if (-not $Item.Contains($field)) { return $false } + continue + } + if ($null -eq $Item.PSObject.Properties[$field]) { return $false } + } + return $true +} + +& powershell -ExecutionPolicy Bypass -File (Join-Path $PSScriptRoot "c19z110-remote-workspace-real-adapter-operator-enablement-readiness-package-index-compatibility-smoke.ps1") ` + -ApiBaseUrl $ApiBaseUrl ` + -ClusterID $ClusterID ` + -ActorUserID $ActorUserID ` + -RequestedNodeName $RequestedNodeName ` + -DefaultNodeName $DefaultNodeName ` + -MatrixNodeName $MatrixNodeName ` + -ResultPath $sourceResultPath | Out-Null + +$sourceFile = Join-Path $repoRoot $sourceResultPath +$sourceResult = Get-Content -Raw -Path $sourceFile | ConvertFrom-Json +$packageIndex = Get-PropertyValue -Item $sourceResult -Name "operator_enablement_readiness_package_index" -Default $null +$guardrails = Get-PropertyValue -Item $packageIndex -Name "guardrail_summary" -Default $null + +$closeout = [ordered]@{ + schema_version = "rap.remote_workspace_real_adapter_operator_readiness_closeout_summary.v1" + source_package_index_schema = Get-PropertyValue -Item $packageIndex -Name "schema_version" -Default $null + closeout_status = "closed_contract_only_ready_for_operator_review" + closeout_marker = "c19z111_real_adapter_operator_readiness_closed_contract_only" + covered_stage_range = Get-PropertyValue -Item $packageIndex -Name "covered_stage_range" -Default $null + covered_stage_count = Get-PropertyValue -Item $packageIndex -Name "covered_stage_count" -Default $null + latest_compatibility_stage = Get-PropertyValue -Item $packageIndex -Name "latest_compatibility_stage" -Default $null + operator_review_status = Get-PropertyValue -Item $packageIndex -Name "operator_review_status" -Default $null + enablement_boundary = Get-PropertyValue -Item $packageIndex -Name "enablement_boundary" -Default $null + enablement_status = Get-PropertyValue -Item $packageIndex -Name "enablement_status" -Default $null + runtime_gate_state = Get-PropertyValue -Item $packageIndex -Name "runtime_gate_state" -Default $null + runtime_effect = Get-PropertyValue -Item $packageIndex -Name "runtime_effect" -Default $null + operator_default_action = Get-PropertyValue -Item $packageIndex -Name "operator_default_action" -Default $null + next_required_phase = "explicit_operator_review_and_enablement_decision" + allows_process_start = $false + allows_payload_traffic = $false + guardrail_summary = $guardrails +} + +$closeoutFieldsCompatible = Test-ObjectHasFields -Item $closeout -Fields $requiredCloseoutFields +$closeoutValuesCompatible = ( + [string](Get-PropertyValue -Item $closeout -Name "schema_version" -Default "") -eq "rap.remote_workspace_real_adapter_operator_readiness_closeout_summary.v1" -and + [string](Get-PropertyValue -Item $closeout -Name "source_package_index_schema" -Default "") -eq "rap.remote_workspace_real_adapter_operator_enablement_readiness_package_index.v1" -and + [string](Get-PropertyValue -Item $closeout -Name "closeout_status" -Default "") -eq "closed_contract_only_ready_for_operator_review" -and + [string](Get-PropertyValue -Item $closeout -Name "closeout_marker" -Default "") -eq "c19z111_real_adapter_operator_readiness_closed_contract_only" -and + [string](Get-PropertyValue -Item $closeout -Name "covered_stage_range" -Default "") -eq "C19Z89-C19Z108" -and + [int](Get-PropertyValue -Item $closeout -Name "covered_stage_count" -Default -1) -eq 20 -and + [string](Get-PropertyValue -Item $closeout -Name "latest_compatibility_stage" -Default "") -eq "C19Z108" -and + [string](Get-PropertyValue -Item $closeout -Name "operator_review_status" -Default "") -eq "not_reviewed" -and + [string](Get-PropertyValue -Item $closeout -Name "enablement_boundary" -Default "") -eq "explicit_operator_enablement_required" -and + [string](Get-PropertyValue -Item $closeout -Name "enablement_status" -Default "") -eq "not_enabled" -and + [string](Get-PropertyValue -Item $closeout -Name "runtime_gate_state" -Default "") -eq "validated_contract_only_not_enabled" -and + [string](Get-PropertyValue -Item $closeout -Name "runtime_effect" -Default "") -eq "contract_only_no_runtime_enablement" -and + [string](Get-PropertyValue -Item $closeout -Name "operator_default_action" -Default "") -eq "keep_real_adapter_disabled" -and + [string](Get-PropertyValue -Item $closeout -Name "next_required_phase" -Default "") -eq "explicit_operator_review_and_enablement_decision" -and + -not [bool](Get-PropertyValue -Item $closeout -Name "allows_process_start" -Default $true) -and + -not [bool](Get-PropertyValue -Item $closeout -Name "allows_payload_traffic" -Default $true) +) +$guardrailsCompatible = ( + (Test-ObjectHasFields -Item $guardrails -Fields $requiredGuardrailFields) -and + [bool](Get-PropertyValue -Item $guardrails -Name "activation_blocked" -Default $false) -and + -not [bool](Get-PropertyValue -Item $guardrails -Name "process_start_allowed" -Default $true) -and + -not [bool](Get-PropertyValue -Item $guardrails -Name "health_probe_enabled" -Default $true) -and + [string](Get-PropertyValue -Item $guardrails -Name "payload_traffic" -Default "") -eq "none" -and + -not [bool](Get-PropertyValue -Item $guardrails -Name "allows_process_start" -Default $true) -and + -not [bool](Get-PropertyValue -Item $guardrails -Name "allows_payload_traffic" -Default $true) +) + +$checks = [ordered]@{ + source_smoke_passed = ([bool]$sourceResult.passed) + source_schema_expected = ([string]$sourceResult.schema_version -eq "c19z110.remote_workspace_real_adapter_operator_enablement_readiness_package_index_compatibility_smoke.v1") + package_index_present = ($null -ne $packageIndex) + closeout_fields_compatible = $closeoutFieldsCompatible + closeout_values_compatible = $closeoutValuesCompatible + guardrails_compatible = $guardrailsCompatible +} +$failed = @($checks.GetEnumerator() | Where-Object { -not $_.Value } | ForEach-Object { $_.Key }) + +$result = [ordered]@{ + schema_version = "c19z111.remote_workspace_real_adapter_operator_readiness_closeout_summary_smoke.v1" + source_result_path = $sourceFile + cluster_id = $ClusterID + required_closeout_fields = $requiredCloseoutFields + required_guardrail_fields = $requiredGuardrailFields + operator_readiness_closeout_summary = $closeout + checks = $checks + failed_checks = $failed + passed = ($failed.Count -eq 0) +} + +$fullResultPath = Join-Path $repoRoot $ResultPath +$resultDir = Split-Path -Parent $fullResultPath +if ($resultDir) { New-Item -ItemType Directory -Force -Path $resultDir | Out-Null } +$result | ConvertTo-Json -Depth 100 | Set-Content -Encoding UTF8 -Path $fullResultPath + +if (-not $result.passed) { + throw "C19Z111 remote workspace real-adapter operator readiness closeout summary smoke failed. Result: $fullResultPath Failed: $($failed -join ', ')" +} + +Write-Host "C19Z111 remote workspace real-adapter operator readiness closeout summary smoke passed. Result: $fullResultPath" +$result diff --git a/scripts/fabric/c19z112-remote-workspace-real-adapter-operator-readiness-closeout-summary-compatibility-smoke.ps1 b/scripts/fabric/c19z112-remote-workspace-real-adapter-operator-readiness-closeout-summary-compatibility-smoke.ps1 new file mode 100644 index 0000000..953ba4c --- /dev/null +++ b/scripts/fabric/c19z112-remote-workspace-real-adapter-operator-readiness-closeout-summary-compatibility-smoke.ps1 @@ -0,0 +1,137 @@ +param( + [string]$ApiBaseUrl = "http://192.168.200.61:18121/api/v1", + [string]$ClusterID = "cfc0743d-d960-49fb-9de8-96e063d5e4aa", + [string]$ActorUserID = "f67d943f-5397-4b3a-a229-695fe67ad700", + [string]$RequestedNodeName = "test-1", + [string]$DefaultNodeName = "test-2", + [string]$MatrixNodeName = "test-1", + [string]$ResultPath = "artifacts\c19z112-remote-workspace-real-adapter-operator-readiness-closeout-summary-compatibility-smoke-result.json" +) + +Set-StrictMode -Version Latest +$ErrorActionPreference = "Stop" + +$repoRoot = (Resolve-Path (Join-Path $PSScriptRoot "..\..")).ProviderPath +$sourceResultPath = "artifacts\c19z112-remote-workspace-real-adapter-operator-readiness-closeout-summary-source-result.json" +$requiredCloseoutFields = @( + "schema_version", + "source_package_index_schema", + "closeout_status", + "closeout_marker", + "covered_stage_range", + "covered_stage_count", + "latest_compatibility_stage", + "operator_review_status", + "enablement_boundary", + "enablement_status", + "runtime_gate_state", + "runtime_effect", + "operator_default_action", + "next_required_phase", + "allows_process_start", + "allows_payload_traffic", + "guardrail_summary" +) +$requiredGuardrailFields = @("activation_blocked", "process_start_allowed", "health_probe_enabled", "payload_traffic", "allows_process_start", "allows_payload_traffic") + +function Get-PropertyValue { + param([object]$Item, [string]$Name, [object]$Default = $null) + if ($null -eq $Item) { return $Default } + if ($Item -is [System.Collections.IDictionary]) { + if ($Item.Contains($Name)) { return $Item[$Name] } + return $Default + } + $property = $Item.PSObject.Properties[$Name] + if ($null -eq $property) { return $Default } + return $property.Value +} + +function Test-ObjectHasFields { + param([object]$Item, [string[]]$Fields) + if ($null -eq $Item) { return $false } + foreach ($field in $Fields) { + if ($Item -is [System.Collections.IDictionary]) { + if (-not $Item.Contains($field)) { return $false } + continue + } + if ($null -eq $Item.PSObject.Properties[$field]) { return $false } + } + return $true +} + +& powershell -ExecutionPolicy Bypass -File (Join-Path $PSScriptRoot "c19z111-remote-workspace-real-adapter-operator-readiness-closeout-summary-smoke.ps1") ` + -ApiBaseUrl $ApiBaseUrl ` + -ClusterID $ClusterID ` + -ActorUserID $ActorUserID ` + -RequestedNodeName $RequestedNodeName ` + -DefaultNodeName $DefaultNodeName ` + -MatrixNodeName $MatrixNodeName ` + -ResultPath $sourceResultPath | Out-Null + +$sourceFile = Join-Path $repoRoot $sourceResultPath +$sourceResult = Get-Content -Raw -Path $sourceFile | ConvertFrom-Json +$closeout = Get-PropertyValue -Item $sourceResult -Name "operator_readiness_closeout_summary" -Default $null +$guardrails = Get-PropertyValue -Item $closeout -Name "guardrail_summary" -Default $null + +$closeoutFieldsCompatible = Test-ObjectHasFields -Item $closeout -Fields $requiredCloseoutFields +$closeoutValuesCompatible = ( + [string](Get-PropertyValue -Item $closeout -Name "schema_version" -Default "") -eq "rap.remote_workspace_real_adapter_operator_readiness_closeout_summary.v1" -and + [string](Get-PropertyValue -Item $closeout -Name "source_package_index_schema" -Default "") -eq "rap.remote_workspace_real_adapter_operator_enablement_readiness_package_index.v1" -and + [string](Get-PropertyValue -Item $closeout -Name "closeout_status" -Default "") -eq "closed_contract_only_ready_for_operator_review" -and + [string](Get-PropertyValue -Item $closeout -Name "closeout_marker" -Default "") -eq "c19z111_real_adapter_operator_readiness_closed_contract_only" -and + [string](Get-PropertyValue -Item $closeout -Name "covered_stage_range" -Default "") -eq "C19Z89-C19Z108" -and + [int](Get-PropertyValue -Item $closeout -Name "covered_stage_count" -Default -1) -eq 20 -and + [string](Get-PropertyValue -Item $closeout -Name "latest_compatibility_stage" -Default "") -eq "C19Z108" -and + [string](Get-PropertyValue -Item $closeout -Name "operator_review_status" -Default "") -eq "not_reviewed" -and + [string](Get-PropertyValue -Item $closeout -Name "enablement_boundary" -Default "") -eq "explicit_operator_enablement_required" -and + [string](Get-PropertyValue -Item $closeout -Name "enablement_status" -Default "") -eq "not_enabled" -and + [string](Get-PropertyValue -Item $closeout -Name "runtime_gate_state" -Default "") -eq "validated_contract_only_not_enabled" -and + [string](Get-PropertyValue -Item $closeout -Name "runtime_effect" -Default "") -eq "contract_only_no_runtime_enablement" -and + [string](Get-PropertyValue -Item $closeout -Name "operator_default_action" -Default "") -eq "keep_real_adapter_disabled" -and + [string](Get-PropertyValue -Item $closeout -Name "next_required_phase" -Default "") -eq "explicit_operator_review_and_enablement_decision" -and + -not [bool](Get-PropertyValue -Item $closeout -Name "allows_process_start" -Default $true) -and + -not [bool](Get-PropertyValue -Item $closeout -Name "allows_payload_traffic" -Default $true) +) +$guardrailsCompatible = ( + (Test-ObjectHasFields -Item $guardrails -Fields $requiredGuardrailFields) -and + [bool](Get-PropertyValue -Item $guardrails -Name "activation_blocked" -Default $false) -and + -not [bool](Get-PropertyValue -Item $guardrails -Name "process_start_allowed" -Default $true) -and + -not [bool](Get-PropertyValue -Item $guardrails -Name "health_probe_enabled" -Default $true) -and + [string](Get-PropertyValue -Item $guardrails -Name "payload_traffic" -Default "") -eq "none" -and + -not [bool](Get-PropertyValue -Item $guardrails -Name "allows_process_start" -Default $true) -and + -not [bool](Get-PropertyValue -Item $guardrails -Name "allows_payload_traffic" -Default $true) +) + +$checks = [ordered]@{ + source_smoke_passed = ([bool]$sourceResult.passed) + source_schema_expected = ([string]$sourceResult.schema_version -eq "c19z111.remote_workspace_real_adapter_operator_readiness_closeout_summary_smoke.v1") + closeout_summary_present = ($null -ne $closeout) + closeout_fields_compatible = $closeoutFieldsCompatible + closeout_values_compatible = $closeoutValuesCompatible + guardrails_compatible = $guardrailsCompatible +} +$failed = @($checks.GetEnumerator() | Where-Object { -not $_.Value } | ForEach-Object { $_.Key }) + +$result = [ordered]@{ + schema_version = "c19z112.remote_workspace_real_adapter_operator_readiness_closeout_summary_compatibility_smoke.v1" + source_result_path = $sourceFile + cluster_id = $ClusterID + required_closeout_fields = $requiredCloseoutFields + required_guardrail_fields = $requiredGuardrailFields + operator_readiness_closeout_summary = $closeout + checks = $checks + failed_checks = $failed + passed = ($failed.Count -eq 0) +} + +$fullResultPath = Join-Path $repoRoot $ResultPath +$resultDir = Split-Path -Parent $fullResultPath +if ($resultDir) { New-Item -ItemType Directory -Force -Path $resultDir | Out-Null } +$result | ConvertTo-Json -Depth 100 | Set-Content -Encoding UTF8 -Path $fullResultPath + +if (-not $result.passed) { + throw "C19Z112 remote workspace real-adapter operator readiness closeout summary compatibility smoke failed. Result: $fullResultPath Failed: $($failed -join ', ')" +} + +Write-Host "C19Z112 remote workspace real-adapter operator readiness closeout summary compatibility smoke passed. Result: $fullResultPath" +$result diff --git a/scripts/fabric/c19z113-remote-workspace-real-adapter-operator-review-decision-request-smoke.ps1 b/scripts/fabric/c19z113-remote-workspace-real-adapter-operator-review-decision-request-smoke.ps1 new file mode 100644 index 0000000..7da3b74 --- /dev/null +++ b/scripts/fabric/c19z113-remote-workspace-real-adapter-operator-review-decision-request-smoke.ps1 @@ -0,0 +1,172 @@ +param( + [string]$ApiBaseUrl = "http://192.168.200.61:18121/api/v1", + [string]$ClusterID = "cfc0743d-d960-49fb-9de8-96e063d5e4aa", + [string]$ActorUserID = "f67d943f-5397-4b3a-a229-695fe67ad700", + [string]$RequestedNodeName = "test-1", + [string]$DefaultNodeName = "test-2", + [string]$MatrixNodeName = "test-1", + [string]$ResultPath = "artifacts\c19z113-remote-workspace-real-adapter-operator-review-decision-request-smoke-result.json" +) + +Set-StrictMode -Version Latest +$ErrorActionPreference = "Stop" + +$repoRoot = (Resolve-Path (Join-Path $PSScriptRoot "..\..")).ProviderPath +$sourceResultPath = "artifacts\c19z113-remote-workspace-real-adapter-operator-review-decision-request-source-result.json" +$requiredRequestFields = @( + "schema_version", + "source_closeout_schema", + "review_request_status", + "review_request_marker", + "requested_decision", + "enablement_decision", + "operator_review_status", + "enablement_boundary", + "enablement_status", + "runtime_gate_state", + "runtime_effect", + "operator_default_action", + "decision_prerequisites", + "allows_process_start", + "allows_payload_traffic", + "guardrail_summary" +) +$decisionPrerequisites = @( + "operator_reviews_closeout_summary", + "operator_confirms_real_runtime_enablement_intent", + "operator_selects_runtime_targets", + "operator_approves_process_start", + "operator_approves_payload_traffic" +) +$requiredGuardrailFields = @("activation_blocked", "process_start_allowed", "health_probe_enabled", "payload_traffic", "allows_process_start", "allows_payload_traffic") + +function Get-PropertyValue { + param([object]$Item, [string]$Name, [object]$Default = $null) + if ($null -eq $Item) { return $Default } + if ($Item -is [System.Collections.IDictionary]) { + if ($Item.Contains($Name)) { return $Item[$Name] } + return $Default + } + $property = $Item.PSObject.Properties[$Name] + if ($null -eq $property) { return $Default } + return $property.Value +} + +function Test-ObjectHasFields { + param([object]$Item, [string[]]$Fields) + if ($null -eq $Item) { return $false } + foreach ($field in $Fields) { + if ($Item -is [System.Collections.IDictionary]) { + if (-not $Item.Contains($field)) { return $false } + continue + } + if ($null -eq $Item.PSObject.Properties[$field]) { return $false } + } + return $true +} + +function Test-ArrayContainsAll { + param([object[]]$Actual, [string[]]$Required) + $values = @($Actual | ForEach-Object { [string]$_ }) + foreach ($item in $Required) { + if ($values -notcontains $item) { return $false } + } + return $true +} + +& powershell -ExecutionPolicy Bypass -File (Join-Path $PSScriptRoot "c19z112-remote-workspace-real-adapter-operator-readiness-closeout-summary-compatibility-smoke.ps1") ` + -ApiBaseUrl $ApiBaseUrl ` + -ClusterID $ClusterID ` + -ActorUserID $ActorUserID ` + -RequestedNodeName $RequestedNodeName ` + -DefaultNodeName $DefaultNodeName ` + -MatrixNodeName $MatrixNodeName ` + -ResultPath $sourceResultPath | Out-Null + +$sourceFile = Join-Path $repoRoot $sourceResultPath +$sourceResult = Get-Content -Raw -Path $sourceFile | ConvertFrom-Json +$closeout = Get-PropertyValue -Item $sourceResult -Name "operator_readiness_closeout_summary" -Default $null +$guardrails = Get-PropertyValue -Item $closeout -Name "guardrail_summary" -Default $null + +$request = [ordered]@{ + schema_version = "rap.remote_workspace_real_adapter_operator_review_decision_request.v1" + source_closeout_schema = Get-PropertyValue -Item $closeout -Name "schema_version" -Default $null + review_request_status = "pending_operator_decision" + review_request_marker = "c19z113_real_adapter_operator_review_decision_request_contract_only" + requested_decision = "review_real_runtime_enablement" + enablement_decision = "not_approved" + operator_review_status = "pending" + enablement_boundary = Get-PropertyValue -Item $closeout -Name "enablement_boundary" -Default $null + enablement_status = Get-PropertyValue -Item $closeout -Name "enablement_status" -Default $null + runtime_gate_state = Get-PropertyValue -Item $closeout -Name "runtime_gate_state" -Default $null + runtime_effect = Get-PropertyValue -Item $closeout -Name "runtime_effect" -Default $null + operator_default_action = Get-PropertyValue -Item $closeout -Name "operator_default_action" -Default $null + decision_prerequisites = $decisionPrerequisites + allows_process_start = $false + allows_payload_traffic = $false + guardrail_summary = $guardrails +} + +$requestFieldsCompatible = Test-ObjectHasFields -Item $request -Fields $requiredRequestFields +$prerequisitesCompatible = Test-ArrayContainsAll -Actual @(Get-PropertyValue -Item $request -Name "decision_prerequisites" -Default @()) -Required $decisionPrerequisites +$requestValuesCompatible = ( + [string](Get-PropertyValue -Item $request -Name "schema_version" -Default "") -eq "rap.remote_workspace_real_adapter_operator_review_decision_request.v1" -and + [string](Get-PropertyValue -Item $request -Name "source_closeout_schema" -Default "") -eq "rap.remote_workspace_real_adapter_operator_readiness_closeout_summary.v1" -and + [string](Get-PropertyValue -Item $request -Name "review_request_status" -Default "") -eq "pending_operator_decision" -and + [string](Get-PropertyValue -Item $request -Name "review_request_marker" -Default "") -eq "c19z113_real_adapter_operator_review_decision_request_contract_only" -and + [string](Get-PropertyValue -Item $request -Name "requested_decision" -Default "") -eq "review_real_runtime_enablement" -and + [string](Get-PropertyValue -Item $request -Name "enablement_decision" -Default "") -eq "not_approved" -and + [string](Get-PropertyValue -Item $request -Name "operator_review_status" -Default "") -eq "pending" -and + [string](Get-PropertyValue -Item $request -Name "enablement_boundary" -Default "") -eq "explicit_operator_enablement_required" -and + [string](Get-PropertyValue -Item $request -Name "enablement_status" -Default "") -eq "not_enabled" -and + [string](Get-PropertyValue -Item $request -Name "runtime_gate_state" -Default "") -eq "validated_contract_only_not_enabled" -and + [string](Get-PropertyValue -Item $request -Name "runtime_effect" -Default "") -eq "contract_only_no_runtime_enablement" -and + [string](Get-PropertyValue -Item $request -Name "operator_default_action" -Default "") -eq "keep_real_adapter_disabled" -and + -not [bool](Get-PropertyValue -Item $request -Name "allows_process_start" -Default $true) -and + -not [bool](Get-PropertyValue -Item $request -Name "allows_payload_traffic" -Default $true) +) +$guardrailsCompatible = ( + (Test-ObjectHasFields -Item $guardrails -Fields $requiredGuardrailFields) -and + [bool](Get-PropertyValue -Item $guardrails -Name "activation_blocked" -Default $false) -and + -not [bool](Get-PropertyValue -Item $guardrails -Name "process_start_allowed" -Default $true) -and + -not [bool](Get-PropertyValue -Item $guardrails -Name "health_probe_enabled" -Default $true) -and + [string](Get-PropertyValue -Item $guardrails -Name "payload_traffic" -Default "") -eq "none" -and + -not [bool](Get-PropertyValue -Item $guardrails -Name "allows_process_start" -Default $true) -and + -not [bool](Get-PropertyValue -Item $guardrails -Name "allows_payload_traffic" -Default $true) +) + +$checks = [ordered]@{ + source_smoke_passed = ([bool]$sourceResult.passed) + source_schema_expected = ([string]$sourceResult.schema_version -eq "c19z112.remote_workspace_real_adapter_operator_readiness_closeout_summary_compatibility_smoke.v1") + closeout_summary_present = ($null -ne $closeout) + request_fields_compatible = $requestFieldsCompatible + request_values_compatible = $requestValuesCompatible + prerequisites_compatible = $prerequisitesCompatible + guardrails_compatible = $guardrailsCompatible +} +$failed = @($checks.GetEnumerator() | Where-Object { -not $_.Value } | ForEach-Object { $_.Key }) + +$result = [ordered]@{ + schema_version = "c19z113.remote_workspace_real_adapter_operator_review_decision_request_smoke.v1" + source_result_path = $sourceFile + cluster_id = $ClusterID + required_request_fields = $requiredRequestFields + decision_prerequisites = $decisionPrerequisites + required_guardrail_fields = $requiredGuardrailFields + operator_review_decision_request = $request + checks = $checks + failed_checks = $failed + passed = ($failed.Count -eq 0) +} + +$fullResultPath = Join-Path $repoRoot $ResultPath +$resultDir = Split-Path -Parent $fullResultPath +if ($resultDir) { New-Item -ItemType Directory -Force -Path $resultDir | Out-Null } +$result | ConvertTo-Json -Depth 100 | Set-Content -Encoding UTF8 -Path $fullResultPath + +if (-not $result.passed) { + throw "C19Z113 remote workspace real-adapter operator review decision request smoke failed. Result: $fullResultPath Failed: $($failed -join ', ')" +} + +Write-Host "C19Z113 remote workspace real-adapter operator review decision request smoke passed. Result: $fullResultPath" +$result diff --git a/scripts/fabric/c19z114-remote-workspace-real-adapter-operator-review-decision-request-compatibility-smoke.ps1 b/scripts/fabric/c19z114-remote-workspace-real-adapter-operator-review-decision-request-compatibility-smoke.ps1 new file mode 100644 index 0000000..e96ac36 --- /dev/null +++ b/scripts/fabric/c19z114-remote-workspace-real-adapter-operator-review-decision-request-compatibility-smoke.ps1 @@ -0,0 +1,153 @@ +param( + [string]$ApiBaseUrl = "http://192.168.200.61:18121/api/v1", + [string]$ClusterID = "cfc0743d-d960-49fb-9de8-96e063d5e4aa", + [string]$ActorUserID = "f67d943f-5397-4b3a-a229-695fe67ad700", + [string]$RequestedNodeName = "test-1", + [string]$DefaultNodeName = "test-2", + [string]$MatrixNodeName = "test-1", + [string]$ResultPath = "artifacts\c19z114-remote-workspace-real-adapter-operator-review-decision-request-compatibility-smoke-result.json" +) + +Set-StrictMode -Version Latest +$ErrorActionPreference = "Stop" + +$repoRoot = (Resolve-Path (Join-Path $PSScriptRoot "..\..")).ProviderPath +$sourceResultPath = "artifacts\c19z114-remote-workspace-real-adapter-operator-review-decision-request-source-result.json" +$requiredRequestFields = @( + "schema_version", + "source_closeout_schema", + "review_request_status", + "review_request_marker", + "requested_decision", + "enablement_decision", + "operator_review_status", + "enablement_boundary", + "enablement_status", + "runtime_gate_state", + "runtime_effect", + "operator_default_action", + "decision_prerequisites", + "allows_process_start", + "allows_payload_traffic", + "guardrail_summary" +) +$decisionPrerequisites = @( + "operator_reviews_closeout_summary", + "operator_confirms_real_runtime_enablement_intent", + "operator_selects_runtime_targets", + "operator_approves_process_start", + "operator_approves_payload_traffic" +) +$requiredGuardrailFields = @("activation_blocked", "process_start_allowed", "health_probe_enabled", "payload_traffic", "allows_process_start", "allows_payload_traffic") + +function Get-PropertyValue { + param([object]$Item, [string]$Name, [object]$Default = $null) + if ($null -eq $Item) { return $Default } + if ($Item -is [System.Collections.IDictionary]) { + if ($Item.Contains($Name)) { return $Item[$Name] } + return $Default + } + $property = $Item.PSObject.Properties[$Name] + if ($null -eq $property) { return $Default } + return $property.Value +} + +function Test-ObjectHasFields { + param([object]$Item, [string[]]$Fields) + if ($null -eq $Item) { return $false } + foreach ($field in $Fields) { + if ($Item -is [System.Collections.IDictionary]) { + if (-not $Item.Contains($field)) { return $false } + continue + } + if ($null -eq $Item.PSObject.Properties[$field]) { return $false } + } + return $true +} + +function Test-ArrayContainsAll { + param([object[]]$Actual, [string[]]$Required) + $values = @($Actual | ForEach-Object { [string]$_ }) + foreach ($item in $Required) { + if ($values -notcontains $item) { return $false } + } + return $true +} + +& powershell -ExecutionPolicy Bypass -File (Join-Path $PSScriptRoot "c19z113-remote-workspace-real-adapter-operator-review-decision-request-smoke.ps1") ` + -ApiBaseUrl $ApiBaseUrl ` + -ClusterID $ClusterID ` + -ActorUserID $ActorUserID ` + -RequestedNodeName $RequestedNodeName ` + -DefaultNodeName $DefaultNodeName ` + -MatrixNodeName $MatrixNodeName ` + -ResultPath $sourceResultPath | Out-Null + +$sourceFile = Join-Path $repoRoot $sourceResultPath +$sourceResult = Get-Content -Raw -Path $sourceFile | ConvertFrom-Json +$request = Get-PropertyValue -Item $sourceResult -Name "operator_review_decision_request" -Default $null +$guardrails = Get-PropertyValue -Item $request -Name "guardrail_summary" -Default $null + +$requestFieldsCompatible = Test-ObjectHasFields -Item $request -Fields $requiredRequestFields +$prerequisitesCompatible = Test-ArrayContainsAll -Actual @(Get-PropertyValue -Item $request -Name "decision_prerequisites" -Default @()) -Required $decisionPrerequisites +$requestValuesCompatible = ( + [string](Get-PropertyValue -Item $request -Name "schema_version" -Default "") -eq "rap.remote_workspace_real_adapter_operator_review_decision_request.v1" -and + [string](Get-PropertyValue -Item $request -Name "source_closeout_schema" -Default "") -eq "rap.remote_workspace_real_adapter_operator_readiness_closeout_summary.v1" -and + [string](Get-PropertyValue -Item $request -Name "review_request_status" -Default "") -eq "pending_operator_decision" -and + [string](Get-PropertyValue -Item $request -Name "review_request_marker" -Default "") -eq "c19z113_real_adapter_operator_review_decision_request_contract_only" -and + [string](Get-PropertyValue -Item $request -Name "requested_decision" -Default "") -eq "review_real_runtime_enablement" -and + [string](Get-PropertyValue -Item $request -Name "enablement_decision" -Default "") -eq "not_approved" -and + [string](Get-PropertyValue -Item $request -Name "operator_review_status" -Default "") -eq "pending" -and + [string](Get-PropertyValue -Item $request -Name "enablement_boundary" -Default "") -eq "explicit_operator_enablement_required" -and + [string](Get-PropertyValue -Item $request -Name "enablement_status" -Default "") -eq "not_enabled" -and + [string](Get-PropertyValue -Item $request -Name "runtime_gate_state" -Default "") -eq "validated_contract_only_not_enabled" -and + [string](Get-PropertyValue -Item $request -Name "runtime_effect" -Default "") -eq "contract_only_no_runtime_enablement" -and + [string](Get-PropertyValue -Item $request -Name "operator_default_action" -Default "") -eq "keep_real_adapter_disabled" -and + -not [bool](Get-PropertyValue -Item $request -Name "allows_process_start" -Default $true) -and + -not [bool](Get-PropertyValue -Item $request -Name "allows_payload_traffic" -Default $true) +) +$guardrailsCompatible = ( + (Test-ObjectHasFields -Item $guardrails -Fields $requiredGuardrailFields) -and + [bool](Get-PropertyValue -Item $guardrails -Name "activation_blocked" -Default $false) -and + -not [bool](Get-PropertyValue -Item $guardrails -Name "process_start_allowed" -Default $true) -and + -not [bool](Get-PropertyValue -Item $guardrails -Name "health_probe_enabled" -Default $true) -and + [string](Get-PropertyValue -Item $guardrails -Name "payload_traffic" -Default "") -eq "none" -and + -not [bool](Get-PropertyValue -Item $guardrails -Name "allows_process_start" -Default $true) -and + -not [bool](Get-PropertyValue -Item $guardrails -Name "allows_payload_traffic" -Default $true) +) + +$checks = [ordered]@{ + source_smoke_passed = ([bool]$sourceResult.passed) + source_schema_expected = ([string]$sourceResult.schema_version -eq "c19z113.remote_workspace_real_adapter_operator_review_decision_request_smoke.v1") + decision_request_present = ($null -ne $request) + request_fields_compatible = $requestFieldsCompatible + request_values_compatible = $requestValuesCompatible + prerequisites_compatible = $prerequisitesCompatible + guardrails_compatible = $guardrailsCompatible +} +$failed = @($checks.GetEnumerator() | Where-Object { -not $_.Value } | ForEach-Object { $_.Key }) + +$result = [ordered]@{ + schema_version = "c19z114.remote_workspace_real_adapter_operator_review_decision_request_compatibility_smoke.v1" + source_result_path = $sourceFile + cluster_id = $ClusterID + required_request_fields = $requiredRequestFields + decision_prerequisites = $decisionPrerequisites + required_guardrail_fields = $requiredGuardrailFields + operator_review_decision_request = $request + checks = $checks + failed_checks = $failed + passed = ($failed.Count -eq 0) +} + +$fullResultPath = Join-Path $repoRoot $ResultPath +$resultDir = Split-Path -Parent $fullResultPath +if ($resultDir) { New-Item -ItemType Directory -Force -Path $resultDir | Out-Null } +$result | ConvertTo-Json -Depth 100 | Set-Content -Encoding UTF8 -Path $fullResultPath + +if (-not $result.passed) { + throw "C19Z114 remote workspace real-adapter operator review decision request compatibility smoke failed. Result: $fullResultPath Failed: $($failed -join ', ')" +} + +Write-Host "C19Z114 remote workspace real-adapter operator review decision request compatibility smoke passed. Result: $fullResultPath" +$result diff --git a/scripts/fabric/c19z115-remote-workspace-real-adapter-operator-decision-status-summary-smoke.ps1 b/scripts/fabric/c19z115-remote-workspace-real-adapter-operator-decision-status-summary-smoke.ps1 new file mode 100644 index 0000000..4d0b1ab --- /dev/null +++ b/scripts/fabric/c19z115-remote-workspace-real-adapter-operator-decision-status-summary-smoke.ps1 @@ -0,0 +1,154 @@ +param( + [string]$ApiBaseUrl = "http://192.168.200.61:18121/api/v1", + [string]$ClusterID = "cfc0743d-d960-49fb-9de8-96e063d5e4aa", + [string]$ActorUserID = "f67d943f-5397-4b3a-a229-695fe67ad700", + [string]$RequestedNodeName = "test-1", + [string]$DefaultNodeName = "test-2", + [string]$MatrixNodeName = "test-1", + [string]$ResultPath = "artifacts\c19z115-remote-workspace-real-adapter-operator-decision-status-summary-smoke-result.json" +) + +Set-StrictMode -Version Latest +$ErrorActionPreference = "Stop" + +$repoRoot = (Resolve-Path (Join-Path $PSScriptRoot "..\..")).ProviderPath +$sourceResultPath = "artifacts\c19z115-remote-workspace-real-adapter-operator-decision-status-summary-source-result.json" +$requiredSummaryFields = @( + "schema_version", + "source_decision_request_schema", + "decision_status", + "decision_summary_marker", + "requested_decision", + "enablement_decision", + "operator_review_status", + "enablement_boundary", + "enablement_status", + "runtime_gate_state", + "runtime_effect", + "operator_default_action", + "next_required_phase", + "allows_process_start", + "allows_payload_traffic", + "guardrail_summary" +) +$requiredGuardrailFields = @("activation_blocked", "process_start_allowed", "health_probe_enabled", "payload_traffic", "allows_process_start", "allows_payload_traffic") + +function Get-PropertyValue { + param([object]$Item, [string]$Name, [object]$Default = $null) + if ($null -eq $Item) { return $Default } + if ($Item -is [System.Collections.IDictionary]) { + if ($Item.Contains($Name)) { return $Item[$Name] } + return $Default + } + $property = $Item.PSObject.Properties[$Name] + if ($null -eq $property) { return $Default } + return $property.Value +} + +function Test-ObjectHasFields { + param([object]$Item, [string[]]$Fields) + if ($null -eq $Item) { return $false } + foreach ($field in $Fields) { + if ($Item -is [System.Collections.IDictionary]) { + if (-not $Item.Contains($field)) { return $false } + continue + } + if ($null -eq $Item.PSObject.Properties[$field]) { return $false } + } + return $true +} + +& powershell -ExecutionPolicy Bypass -File (Join-Path $PSScriptRoot "c19z114-remote-workspace-real-adapter-operator-review-decision-request-compatibility-smoke.ps1") ` + -ApiBaseUrl $ApiBaseUrl ` + -ClusterID $ClusterID ` + -ActorUserID $ActorUserID ` + -RequestedNodeName $RequestedNodeName ` + -DefaultNodeName $DefaultNodeName ` + -MatrixNodeName $MatrixNodeName ` + -ResultPath $sourceResultPath | Out-Null + +$sourceFile = Join-Path $repoRoot $sourceResultPath +$sourceResult = Get-Content -Raw -Path $sourceFile | ConvertFrom-Json +$request = Get-PropertyValue -Item $sourceResult -Name "operator_review_decision_request" -Default $null +$guardrails = Get-PropertyValue -Item $request -Name "guardrail_summary" -Default $null + +$summary = [ordered]@{ + schema_version = "rap.remote_workspace_real_adapter_operator_decision_status_summary.v1" + source_decision_request_schema = Get-PropertyValue -Item $request -Name "schema_version" -Default $null + decision_status = "pending_not_approved" + decision_summary_marker = "c19z115_real_adapter_operator_decision_status_pending_contract_only" + requested_decision = Get-PropertyValue -Item $request -Name "requested_decision" -Default $null + enablement_decision = Get-PropertyValue -Item $request -Name "enablement_decision" -Default $null + operator_review_status = Get-PropertyValue -Item $request -Name "operator_review_status" -Default $null + enablement_boundary = Get-PropertyValue -Item $request -Name "enablement_boundary" -Default $null + enablement_status = Get-PropertyValue -Item $request -Name "enablement_status" -Default $null + runtime_gate_state = Get-PropertyValue -Item $request -Name "runtime_gate_state" -Default $null + runtime_effect = Get-PropertyValue -Item $request -Name "runtime_effect" -Default $null + operator_default_action = Get-PropertyValue -Item $request -Name "operator_default_action" -Default $null + next_required_phase = "explicit_operator_approval_or_rejection" + allows_process_start = $false + allows_payload_traffic = $false + guardrail_summary = $guardrails +} + +$summaryFieldsCompatible = Test-ObjectHasFields -Item $summary -Fields $requiredSummaryFields +$summaryValuesCompatible = ( + [string](Get-PropertyValue -Item $summary -Name "schema_version" -Default "") -eq "rap.remote_workspace_real_adapter_operator_decision_status_summary.v1" -and + [string](Get-PropertyValue -Item $summary -Name "source_decision_request_schema" -Default "") -eq "rap.remote_workspace_real_adapter_operator_review_decision_request.v1" -and + [string](Get-PropertyValue -Item $summary -Name "decision_status" -Default "") -eq "pending_not_approved" -and + [string](Get-PropertyValue -Item $summary -Name "decision_summary_marker" -Default "") -eq "c19z115_real_adapter_operator_decision_status_pending_contract_only" -and + [string](Get-PropertyValue -Item $summary -Name "requested_decision" -Default "") -eq "review_real_runtime_enablement" -and + [string](Get-PropertyValue -Item $summary -Name "enablement_decision" -Default "") -eq "not_approved" -and + [string](Get-PropertyValue -Item $summary -Name "operator_review_status" -Default "") -eq "pending" -and + [string](Get-PropertyValue -Item $summary -Name "enablement_boundary" -Default "") -eq "explicit_operator_enablement_required" -and + [string](Get-PropertyValue -Item $summary -Name "enablement_status" -Default "") -eq "not_enabled" -and + [string](Get-PropertyValue -Item $summary -Name "runtime_gate_state" -Default "") -eq "validated_contract_only_not_enabled" -and + [string](Get-PropertyValue -Item $summary -Name "runtime_effect" -Default "") -eq "contract_only_no_runtime_enablement" -and + [string](Get-PropertyValue -Item $summary -Name "operator_default_action" -Default "") -eq "keep_real_adapter_disabled" -and + [string](Get-PropertyValue -Item $summary -Name "next_required_phase" -Default "") -eq "explicit_operator_approval_or_rejection" -and + -not [bool](Get-PropertyValue -Item $summary -Name "allows_process_start" -Default $true) -and + -not [bool](Get-PropertyValue -Item $summary -Name "allows_payload_traffic" -Default $true) +) +$guardrailsCompatible = ( + (Test-ObjectHasFields -Item $guardrails -Fields $requiredGuardrailFields) -and + [bool](Get-PropertyValue -Item $guardrails -Name "activation_blocked" -Default $false) -and + -not [bool](Get-PropertyValue -Item $guardrails -Name "process_start_allowed" -Default $true) -and + -not [bool](Get-PropertyValue -Item $guardrails -Name "health_probe_enabled" -Default $true) -and + [string](Get-PropertyValue -Item $guardrails -Name "payload_traffic" -Default "") -eq "none" -and + -not [bool](Get-PropertyValue -Item $guardrails -Name "allows_process_start" -Default $true) -and + -not [bool](Get-PropertyValue -Item $guardrails -Name "allows_payload_traffic" -Default $true) +) + +$checks = [ordered]@{ + source_smoke_passed = ([bool]$sourceResult.passed) + source_schema_expected = ([string]$sourceResult.schema_version -eq "c19z114.remote_workspace_real_adapter_operator_review_decision_request_compatibility_smoke.v1") + decision_request_present = ($null -ne $request) + summary_fields_compatible = $summaryFieldsCompatible + summary_values_compatible = $summaryValuesCompatible + guardrails_compatible = $guardrailsCompatible +} +$failed = @($checks.GetEnumerator() | Where-Object { -not $_.Value } | ForEach-Object { $_.Key }) + +$result = [ordered]@{ + schema_version = "c19z115.remote_workspace_real_adapter_operator_decision_status_summary_smoke.v1" + source_result_path = $sourceFile + cluster_id = $ClusterID + required_summary_fields = $requiredSummaryFields + required_guardrail_fields = $requiredGuardrailFields + operator_decision_status_summary = $summary + checks = $checks + failed_checks = $failed + passed = ($failed.Count -eq 0) +} + +$fullResultPath = Join-Path $repoRoot $ResultPath +$resultDir = Split-Path -Parent $fullResultPath +if ($resultDir) { New-Item -ItemType Directory -Force -Path $resultDir | Out-Null } +$result | ConvertTo-Json -Depth 100 | Set-Content -Encoding UTF8 -Path $fullResultPath + +if (-not $result.passed) { + throw "C19Z115 remote workspace real-adapter operator decision status summary smoke failed. Result: $fullResultPath Failed: $($failed -join ', ')" +} + +Write-Host "C19Z115 remote workspace real-adapter operator decision status summary smoke passed. Result: $fullResultPath" +$result diff --git a/scripts/fabric/c19z116-remote-workspace-real-adapter-operator-decision-status-summary-compatibility-smoke.ps1 b/scripts/fabric/c19z116-remote-workspace-real-adapter-operator-decision-status-summary-compatibility-smoke.ps1 new file mode 100644 index 0000000..f7d564d --- /dev/null +++ b/scripts/fabric/c19z116-remote-workspace-real-adapter-operator-decision-status-summary-compatibility-smoke.ps1 @@ -0,0 +1,135 @@ +param( + [string]$ApiBaseUrl = "http://192.168.200.61:18121/api/v1", + [string]$ClusterID = "cfc0743d-d960-49fb-9de8-96e063d5e4aa", + [string]$ActorUserID = "f67d943f-5397-4b3a-a229-695fe67ad700", + [string]$RequestedNodeName = "test-1", + [string]$DefaultNodeName = "test-2", + [string]$MatrixNodeName = "test-1", + [string]$ResultPath = "artifacts\c19z116-remote-workspace-real-adapter-operator-decision-status-summary-compatibility-smoke-result.json" +) + +Set-StrictMode -Version Latest +$ErrorActionPreference = "Stop" + +$repoRoot = (Resolve-Path (Join-Path $PSScriptRoot "..\..")).ProviderPath +$sourceResultPath = "artifacts\c19z116-remote-workspace-real-adapter-operator-decision-status-summary-source-result.json" +$requiredSummaryFields = @( + "schema_version", + "source_decision_request_schema", + "decision_status", + "decision_summary_marker", + "requested_decision", + "enablement_decision", + "operator_review_status", + "enablement_boundary", + "enablement_status", + "runtime_gate_state", + "runtime_effect", + "operator_default_action", + "next_required_phase", + "allows_process_start", + "allows_payload_traffic", + "guardrail_summary" +) +$requiredGuardrailFields = @("activation_blocked", "process_start_allowed", "health_probe_enabled", "payload_traffic", "allows_process_start", "allows_payload_traffic") + +function Get-PropertyValue { + param([object]$Item, [string]$Name, [object]$Default = $null) + if ($null -eq $Item) { return $Default } + if ($Item -is [System.Collections.IDictionary]) { + if ($Item.Contains($Name)) { return $Item[$Name] } + return $Default + } + $property = $Item.PSObject.Properties[$Name] + if ($null -eq $property) { return $Default } + return $property.Value +} + +function Test-ObjectHasFields { + param([object]$Item, [string[]]$Fields) + if ($null -eq $Item) { return $false } + foreach ($field in $Fields) { + if ($Item -is [System.Collections.IDictionary]) { + if (-not $Item.Contains($field)) { return $false } + continue + } + if ($null -eq $Item.PSObject.Properties[$field]) { return $false } + } + return $true +} + +& powershell -ExecutionPolicy Bypass -File (Join-Path $PSScriptRoot "c19z115-remote-workspace-real-adapter-operator-decision-status-summary-smoke.ps1") ` + -ApiBaseUrl $ApiBaseUrl ` + -ClusterID $ClusterID ` + -ActorUserID $ActorUserID ` + -RequestedNodeName $RequestedNodeName ` + -DefaultNodeName $DefaultNodeName ` + -MatrixNodeName $MatrixNodeName ` + -ResultPath $sourceResultPath | Out-Null + +$sourceFile = Join-Path $repoRoot $sourceResultPath +$sourceResult = Get-Content -Raw -Path $sourceFile | ConvertFrom-Json +$summary = Get-PropertyValue -Item $sourceResult -Name "operator_decision_status_summary" -Default $null +$guardrails = Get-PropertyValue -Item $summary -Name "guardrail_summary" -Default $null + +$summaryFieldsCompatible = Test-ObjectHasFields -Item $summary -Fields $requiredSummaryFields +$summaryValuesCompatible = ( + [string](Get-PropertyValue -Item $summary -Name "schema_version" -Default "") -eq "rap.remote_workspace_real_adapter_operator_decision_status_summary.v1" -and + [string](Get-PropertyValue -Item $summary -Name "source_decision_request_schema" -Default "") -eq "rap.remote_workspace_real_adapter_operator_review_decision_request.v1" -and + [string](Get-PropertyValue -Item $summary -Name "decision_status" -Default "") -eq "pending_not_approved" -and + [string](Get-PropertyValue -Item $summary -Name "decision_summary_marker" -Default "") -eq "c19z115_real_adapter_operator_decision_status_pending_contract_only" -and + [string](Get-PropertyValue -Item $summary -Name "requested_decision" -Default "") -eq "review_real_runtime_enablement" -and + [string](Get-PropertyValue -Item $summary -Name "enablement_decision" -Default "") -eq "not_approved" -and + [string](Get-PropertyValue -Item $summary -Name "operator_review_status" -Default "") -eq "pending" -and + [string](Get-PropertyValue -Item $summary -Name "enablement_boundary" -Default "") -eq "explicit_operator_enablement_required" -and + [string](Get-PropertyValue -Item $summary -Name "enablement_status" -Default "") -eq "not_enabled" -and + [string](Get-PropertyValue -Item $summary -Name "runtime_gate_state" -Default "") -eq "validated_contract_only_not_enabled" -and + [string](Get-PropertyValue -Item $summary -Name "runtime_effect" -Default "") -eq "contract_only_no_runtime_enablement" -and + [string](Get-PropertyValue -Item $summary -Name "operator_default_action" -Default "") -eq "keep_real_adapter_disabled" -and + [string](Get-PropertyValue -Item $summary -Name "next_required_phase" -Default "") -eq "explicit_operator_approval_or_rejection" -and + -not [bool](Get-PropertyValue -Item $summary -Name "allows_process_start" -Default $true) -and + -not [bool](Get-PropertyValue -Item $summary -Name "allows_payload_traffic" -Default $true) +) +$guardrailsCompatible = ( + (Test-ObjectHasFields -Item $guardrails -Fields $requiredGuardrailFields) -and + [bool](Get-PropertyValue -Item $guardrails -Name "activation_blocked" -Default $false) -and + -not [bool](Get-PropertyValue -Item $guardrails -Name "process_start_allowed" -Default $true) -and + -not [bool](Get-PropertyValue -Item $guardrails -Name "health_probe_enabled" -Default $true) -and + [string](Get-PropertyValue -Item $guardrails -Name "payload_traffic" -Default "") -eq "none" -and + -not [bool](Get-PropertyValue -Item $guardrails -Name "allows_process_start" -Default $true) -and + -not [bool](Get-PropertyValue -Item $guardrails -Name "allows_payload_traffic" -Default $true) +) + +$checks = [ordered]@{ + source_smoke_passed = ([bool]$sourceResult.passed) + source_schema_expected = ([string]$sourceResult.schema_version -eq "c19z115.remote_workspace_real_adapter_operator_decision_status_summary_smoke.v1") + decision_status_summary_present = ($null -ne $summary) + summary_fields_compatible = $summaryFieldsCompatible + summary_values_compatible = $summaryValuesCompatible + guardrails_compatible = $guardrailsCompatible +} +$failed = @($checks.GetEnumerator() | Where-Object { -not $_.Value } | ForEach-Object { $_.Key }) + +$result = [ordered]@{ + schema_version = "c19z116.remote_workspace_real_adapter_operator_decision_status_summary_compatibility_smoke.v1" + source_result_path = $sourceFile + cluster_id = $ClusterID + required_summary_fields = $requiredSummaryFields + required_guardrail_fields = $requiredGuardrailFields + operator_decision_status_summary = $summary + checks = $checks + failed_checks = $failed + passed = ($failed.Count -eq 0) +} + +$fullResultPath = Join-Path $repoRoot $ResultPath +$resultDir = Split-Path -Parent $fullResultPath +if ($resultDir) { New-Item -ItemType Directory -Force -Path $resultDir | Out-Null } +$result | ConvertTo-Json -Depth 100 | Set-Content -Encoding UTF8 -Path $fullResultPath + +if (-not $result.passed) { + throw "C19Z116 remote workspace real-adapter operator decision status summary compatibility smoke failed. Result: $fullResultPath Failed: $($failed -join ', ')" +} + +Write-Host "C19Z116 remote workspace real-adapter operator decision status summary compatibility smoke passed. Result: $fullResultPath" +$result diff --git a/scripts/fabric/c19z117-remote-workspace-real-adapter-operator-approval-rejection-outcome-smoke.ps1 b/scripts/fabric/c19z117-remote-workspace-real-adapter-operator-approval-rejection-outcome-smoke.ps1 new file mode 100644 index 0000000..c9596b1 --- /dev/null +++ b/scripts/fabric/c19z117-remote-workspace-real-adapter-operator-approval-rejection-outcome-smoke.ps1 @@ -0,0 +1,154 @@ +param( + [string]$ApiBaseUrl = "http://192.168.200.61:18121/api/v1", + [string]$ClusterID = "cfc0743d-d960-49fb-9de8-96e063d5e4aa", + [string]$ActorUserID = "f67d943f-5397-4b3a-a229-695fe67ad700", + [string]$RequestedNodeName = "test-1", + [string]$DefaultNodeName = "test-2", + [string]$MatrixNodeName = "test-1", + [string]$ResultPath = "artifacts\c19z117-remote-workspace-real-adapter-operator-approval-rejection-outcome-smoke-result.json" +) + +Set-StrictMode -Version Latest +$ErrorActionPreference = "Stop" + +$repoRoot = (Resolve-Path (Join-Path $PSScriptRoot "..\..")).ProviderPath +$sourceResultPath = "artifacts\c19z117-remote-workspace-real-adapter-operator-approval-rejection-outcome-source-result.json" +$requiredOutcomeFields = @( + "schema_version", + "source_decision_status_schema", + "outcome_status", + "outcome_marker", + "requested_decision", + "enablement_decision", + "operator_review_status", + "enablement_boundary", + "enablement_status", + "runtime_gate_state", + "runtime_effect", + "operator_default_action", + "next_required_phase", + "allows_process_start", + "allows_payload_traffic", + "guardrail_summary" +) +$requiredGuardrailFields = @("activation_blocked", "process_start_allowed", "health_probe_enabled", "payload_traffic", "allows_process_start", "allows_payload_traffic") + +function Get-PropertyValue { + param([object]$Item, [string]$Name, [object]$Default = $null) + if ($null -eq $Item) { return $Default } + if ($Item -is [System.Collections.IDictionary]) { + if ($Item.Contains($Name)) { return $Item[$Name] } + return $Default + } + $property = $Item.PSObject.Properties[$Name] + if ($null -eq $property) { return $Default } + return $property.Value +} + +function Test-ObjectHasFields { + param([object]$Item, [string[]]$Fields) + if ($null -eq $Item) { return $false } + foreach ($field in $Fields) { + if ($Item -is [System.Collections.IDictionary]) { + if (-not $Item.Contains($field)) { return $false } + continue + } + if ($null -eq $Item.PSObject.Properties[$field]) { return $false } + } + return $true +} + +& powershell -ExecutionPolicy Bypass -File (Join-Path $PSScriptRoot "c19z116-remote-workspace-real-adapter-operator-decision-status-summary-compatibility-smoke.ps1") ` + -ApiBaseUrl $ApiBaseUrl ` + -ClusterID $ClusterID ` + -ActorUserID $ActorUserID ` + -RequestedNodeName $RequestedNodeName ` + -DefaultNodeName $DefaultNodeName ` + -MatrixNodeName $MatrixNodeName ` + -ResultPath $sourceResultPath | Out-Null + +$sourceFile = Join-Path $repoRoot $sourceResultPath +$sourceResult = Get-Content -Raw -Path $sourceFile | ConvertFrom-Json +$summary = Get-PropertyValue -Item $sourceResult -Name "operator_decision_status_summary" -Default $null +$guardrails = Get-PropertyValue -Item $summary -Name "guardrail_summary" -Default $null + +$outcome = [ordered]@{ + schema_version = "rap.remote_workspace_real_adapter_operator_approval_rejection_outcome.v1" + source_decision_status_schema = Get-PropertyValue -Item $summary -Name "schema_version" -Default $null + outcome_status = "rejected_or_not_approved_contract_only" + outcome_marker = "c19z117_real_adapter_operator_outcome_not_approved_contract_only" + requested_decision = Get-PropertyValue -Item $summary -Name "requested_decision" -Default $null + enablement_decision = "not_approved" + operator_review_status = "closed_without_approval" + enablement_boundary = Get-PropertyValue -Item $summary -Name "enablement_boundary" -Default $null + enablement_status = Get-PropertyValue -Item $summary -Name "enablement_status" -Default $null + runtime_gate_state = Get-PropertyValue -Item $summary -Name "runtime_gate_state" -Default $null + runtime_effect = Get-PropertyValue -Item $summary -Name "runtime_effect" -Default $null + operator_default_action = Get-PropertyValue -Item $summary -Name "operator_default_action" -Default $null + next_required_phase = "explicit_operator_reopen_or_new_enablement_request" + allows_process_start = $false + allows_payload_traffic = $false + guardrail_summary = $guardrails +} + +$outcomeFieldsCompatible = Test-ObjectHasFields -Item $outcome -Fields $requiredOutcomeFields +$outcomeValuesCompatible = ( + [string](Get-PropertyValue -Item $outcome -Name "schema_version" -Default "") -eq "rap.remote_workspace_real_adapter_operator_approval_rejection_outcome.v1" -and + [string](Get-PropertyValue -Item $outcome -Name "source_decision_status_schema" -Default "") -eq "rap.remote_workspace_real_adapter_operator_decision_status_summary.v1" -and + [string](Get-PropertyValue -Item $outcome -Name "outcome_status" -Default "") -eq "rejected_or_not_approved_contract_only" -and + [string](Get-PropertyValue -Item $outcome -Name "outcome_marker" -Default "") -eq "c19z117_real_adapter_operator_outcome_not_approved_contract_only" -and + [string](Get-PropertyValue -Item $outcome -Name "requested_decision" -Default "") -eq "review_real_runtime_enablement" -and + [string](Get-PropertyValue -Item $outcome -Name "enablement_decision" -Default "") -eq "not_approved" -and + [string](Get-PropertyValue -Item $outcome -Name "operator_review_status" -Default "") -eq "closed_without_approval" -and + [string](Get-PropertyValue -Item $outcome -Name "enablement_boundary" -Default "") -eq "explicit_operator_enablement_required" -and + [string](Get-PropertyValue -Item $outcome -Name "enablement_status" -Default "") -eq "not_enabled" -and + [string](Get-PropertyValue -Item $outcome -Name "runtime_gate_state" -Default "") -eq "validated_contract_only_not_enabled" -and + [string](Get-PropertyValue -Item $outcome -Name "runtime_effect" -Default "") -eq "contract_only_no_runtime_enablement" -and + [string](Get-PropertyValue -Item $outcome -Name "operator_default_action" -Default "") -eq "keep_real_adapter_disabled" -and + [string](Get-PropertyValue -Item $outcome -Name "next_required_phase" -Default "") -eq "explicit_operator_reopen_or_new_enablement_request" -and + -not [bool](Get-PropertyValue -Item $outcome -Name "allows_process_start" -Default $true) -and + -not [bool](Get-PropertyValue -Item $outcome -Name "allows_payload_traffic" -Default $true) +) +$guardrailsCompatible = ( + (Test-ObjectHasFields -Item $guardrails -Fields $requiredGuardrailFields) -and + [bool](Get-PropertyValue -Item $guardrails -Name "activation_blocked" -Default $false) -and + -not [bool](Get-PropertyValue -Item $guardrails -Name "process_start_allowed" -Default $true) -and + -not [bool](Get-PropertyValue -Item $guardrails -Name "health_probe_enabled" -Default $true) -and + [string](Get-PropertyValue -Item $guardrails -Name "payload_traffic" -Default "") -eq "none" -and + -not [bool](Get-PropertyValue -Item $guardrails -Name "allows_process_start" -Default $true) -and + -not [bool](Get-PropertyValue -Item $guardrails -Name "allows_payload_traffic" -Default $true) +) + +$checks = [ordered]@{ + source_smoke_passed = ([bool]$sourceResult.passed) + source_schema_expected = ([string]$sourceResult.schema_version -eq "c19z116.remote_workspace_real_adapter_operator_decision_status_summary_compatibility_smoke.v1") + decision_status_summary_present = ($null -ne $summary) + outcome_fields_compatible = $outcomeFieldsCompatible + outcome_values_compatible = $outcomeValuesCompatible + guardrails_compatible = $guardrailsCompatible +} +$failed = @($checks.GetEnumerator() | Where-Object { -not $_.Value } | ForEach-Object { $_.Key }) + +$result = [ordered]@{ + schema_version = "c19z117.remote_workspace_real_adapter_operator_approval_rejection_outcome_smoke.v1" + source_result_path = $sourceFile + cluster_id = $ClusterID + required_outcome_fields = $requiredOutcomeFields + required_guardrail_fields = $requiredGuardrailFields + operator_approval_rejection_outcome = $outcome + checks = $checks + failed_checks = $failed + passed = ($failed.Count -eq 0) +} + +$fullResultPath = Join-Path $repoRoot $ResultPath +$resultDir = Split-Path -Parent $fullResultPath +if ($resultDir) { New-Item -ItemType Directory -Force -Path $resultDir | Out-Null } +$result | ConvertTo-Json -Depth 100 | Set-Content -Encoding UTF8 -Path $fullResultPath + +if (-not $result.passed) { + throw "C19Z117 remote workspace real-adapter operator approval rejection outcome smoke failed. Result: $fullResultPath Failed: $($failed -join ', ')" +} + +Write-Host "C19Z117 remote workspace real-adapter operator approval rejection outcome smoke passed. Result: $fullResultPath" +$result diff --git a/scripts/fabric/c19z118-remote-workspace-real-adapter-operator-approval-rejection-outcome-compatibility-smoke.ps1 b/scripts/fabric/c19z118-remote-workspace-real-adapter-operator-approval-rejection-outcome-compatibility-smoke.ps1 new file mode 100644 index 0000000..24fa6e6 --- /dev/null +++ b/scripts/fabric/c19z118-remote-workspace-real-adapter-operator-approval-rejection-outcome-compatibility-smoke.ps1 @@ -0,0 +1,135 @@ +param( + [string]$ApiBaseUrl = "http://192.168.200.61:18121/api/v1", + [string]$ClusterID = "cfc0743d-d960-49fb-9de8-96e063d5e4aa", + [string]$ActorUserID = "f67d943f-5397-4b3a-a229-695fe67ad700", + [string]$RequestedNodeName = "test-1", + [string]$DefaultNodeName = "test-2", + [string]$MatrixNodeName = "test-1", + [string]$ResultPath = "artifacts\c19z118-remote-workspace-real-adapter-operator-approval-rejection-outcome-compatibility-smoke-result.json" +) + +Set-StrictMode -Version Latest +$ErrorActionPreference = "Stop" + +$repoRoot = (Resolve-Path (Join-Path $PSScriptRoot "..\..")).ProviderPath +$sourceResultPath = "artifacts\c19z118-remote-workspace-real-adapter-operator-approval-rejection-outcome-source-result.json" +$requiredOutcomeFields = @( + "schema_version", + "source_decision_status_schema", + "outcome_status", + "outcome_marker", + "requested_decision", + "enablement_decision", + "operator_review_status", + "enablement_boundary", + "enablement_status", + "runtime_gate_state", + "runtime_effect", + "operator_default_action", + "next_required_phase", + "allows_process_start", + "allows_payload_traffic", + "guardrail_summary" +) +$requiredGuardrailFields = @("activation_blocked", "process_start_allowed", "health_probe_enabled", "payload_traffic", "allows_process_start", "allows_payload_traffic") + +function Get-PropertyValue { + param([object]$Item, [string]$Name, [object]$Default = $null) + if ($null -eq $Item) { return $Default } + if ($Item -is [System.Collections.IDictionary]) { + if ($Item.Contains($Name)) { return $Item[$Name] } + return $Default + } + $property = $Item.PSObject.Properties[$Name] + if ($null -eq $property) { return $Default } + return $property.Value +} + +function Test-ObjectHasFields { + param([object]$Item, [string[]]$Fields) + if ($null -eq $Item) { return $false } + foreach ($field in $Fields) { + if ($Item -is [System.Collections.IDictionary]) { + if (-not $Item.Contains($field)) { return $false } + continue + } + if ($null -eq $Item.PSObject.Properties[$field]) { return $false } + } + return $true +} + +& powershell -ExecutionPolicy Bypass -File (Join-Path $PSScriptRoot "c19z117-remote-workspace-real-adapter-operator-approval-rejection-outcome-smoke.ps1") ` + -ApiBaseUrl $ApiBaseUrl ` + -ClusterID $ClusterID ` + -ActorUserID $ActorUserID ` + -RequestedNodeName $RequestedNodeName ` + -DefaultNodeName $DefaultNodeName ` + -MatrixNodeName $MatrixNodeName ` + -ResultPath $sourceResultPath | Out-Null + +$sourceFile = Join-Path $repoRoot $sourceResultPath +$sourceResult = Get-Content -Raw -Path $sourceFile | ConvertFrom-Json +$outcome = Get-PropertyValue -Item $sourceResult -Name "operator_approval_rejection_outcome" -Default $null +$guardrails = Get-PropertyValue -Item $outcome -Name "guardrail_summary" -Default $null + +$outcomeFieldsCompatible = Test-ObjectHasFields -Item $outcome -Fields $requiredOutcomeFields +$outcomeValuesCompatible = ( + [string](Get-PropertyValue -Item $outcome -Name "schema_version" -Default "") -eq "rap.remote_workspace_real_adapter_operator_approval_rejection_outcome.v1" -and + [string](Get-PropertyValue -Item $outcome -Name "source_decision_status_schema" -Default "") -eq "rap.remote_workspace_real_adapter_operator_decision_status_summary.v1" -and + [string](Get-PropertyValue -Item $outcome -Name "outcome_status" -Default "") -eq "rejected_or_not_approved_contract_only" -and + [string](Get-PropertyValue -Item $outcome -Name "outcome_marker" -Default "") -eq "c19z117_real_adapter_operator_outcome_not_approved_contract_only" -and + [string](Get-PropertyValue -Item $outcome -Name "requested_decision" -Default "") -eq "review_real_runtime_enablement" -and + [string](Get-PropertyValue -Item $outcome -Name "enablement_decision" -Default "") -eq "not_approved" -and + [string](Get-PropertyValue -Item $outcome -Name "operator_review_status" -Default "") -eq "closed_without_approval" -and + [string](Get-PropertyValue -Item $outcome -Name "enablement_boundary" -Default "") -eq "explicit_operator_enablement_required" -and + [string](Get-PropertyValue -Item $outcome -Name "enablement_status" -Default "") -eq "not_enabled" -and + [string](Get-PropertyValue -Item $outcome -Name "runtime_gate_state" -Default "") -eq "validated_contract_only_not_enabled" -and + [string](Get-PropertyValue -Item $outcome -Name "runtime_effect" -Default "") -eq "contract_only_no_runtime_enablement" -and + [string](Get-PropertyValue -Item $outcome -Name "operator_default_action" -Default "") -eq "keep_real_adapter_disabled" -and + [string](Get-PropertyValue -Item $outcome -Name "next_required_phase" -Default "") -eq "explicit_operator_reopen_or_new_enablement_request" -and + -not [bool](Get-PropertyValue -Item $outcome -Name "allows_process_start" -Default $true) -and + -not [bool](Get-PropertyValue -Item $outcome -Name "allows_payload_traffic" -Default $true) +) +$guardrailsCompatible = ( + (Test-ObjectHasFields -Item $guardrails -Fields $requiredGuardrailFields) -and + [bool](Get-PropertyValue -Item $guardrails -Name "activation_blocked" -Default $false) -and + -not [bool](Get-PropertyValue -Item $guardrails -Name "process_start_allowed" -Default $true) -and + -not [bool](Get-PropertyValue -Item $guardrails -Name "health_probe_enabled" -Default $true) -and + [string](Get-PropertyValue -Item $guardrails -Name "payload_traffic" -Default "") -eq "none" -and + -not [bool](Get-PropertyValue -Item $guardrails -Name "allows_process_start" -Default $true) -and + -not [bool](Get-PropertyValue -Item $guardrails -Name "allows_payload_traffic" -Default $true) +) + +$checks = [ordered]@{ + source_smoke_passed = ([bool]$sourceResult.passed) + source_schema_expected = ([string]$sourceResult.schema_version -eq "c19z117.remote_workspace_real_adapter_operator_approval_rejection_outcome_smoke.v1") + outcome_present = ($null -ne $outcome) + outcome_fields_compatible = $outcomeFieldsCompatible + outcome_values_compatible = $outcomeValuesCompatible + guardrails_compatible = $guardrailsCompatible +} +$failed = @($checks.GetEnumerator() | Where-Object { -not $_.Value } | ForEach-Object { $_.Key }) + +$result = [ordered]@{ + schema_version = "c19z118.remote_workspace_real_adapter_operator_approval_rejection_outcome_compatibility_smoke.v1" + source_result_path = $sourceFile + cluster_id = $ClusterID + required_outcome_fields = $requiredOutcomeFields + required_guardrail_fields = $requiredGuardrailFields + operator_approval_rejection_outcome = $outcome + checks = $checks + failed_checks = $failed + passed = ($failed.Count -eq 0) +} + +$fullResultPath = Join-Path $repoRoot $ResultPath +$resultDir = Split-Path -Parent $fullResultPath +if ($resultDir) { New-Item -ItemType Directory -Force -Path $resultDir | Out-Null } +$result | ConvertTo-Json -Depth 100 | Set-Content -Encoding UTF8 -Path $fullResultPath + +if (-not $result.passed) { + throw "C19Z118 remote workspace real-adapter operator approval rejection outcome compatibility smoke failed. Result: $fullResultPath Failed: $($failed -join ', ')" +} + +Write-Host "C19Z118 remote workspace real-adapter operator approval rejection outcome compatibility smoke passed. Result: $fullResultPath" +$result diff --git a/scripts/fabric/c19z119-remote-workspace-real-adapter-operator-outcome-closeout-reopen-boundary-smoke.ps1 b/scripts/fabric/c19z119-remote-workspace-real-adapter-operator-outcome-closeout-reopen-boundary-smoke.ps1 new file mode 100644 index 0000000..ca39b99 --- /dev/null +++ b/scripts/fabric/c19z119-remote-workspace-real-adapter-operator-outcome-closeout-reopen-boundary-smoke.ps1 @@ -0,0 +1,157 @@ +param( + [string]$ApiBaseUrl = "http://192.168.200.61:18121/api/v1", + [string]$ClusterID = "cfc0743d-d960-49fb-9de8-96e063d5e4aa", + [string]$ActorUserID = "f67d943f-5397-4b3a-a229-695fe67ad700", + [string]$RequestedNodeName = "test-1", + [string]$DefaultNodeName = "test-2", + [string]$MatrixNodeName = "test-1", + [string]$ResultPath = "artifacts\c19z119-remote-workspace-real-adapter-operator-outcome-closeout-reopen-boundary-smoke-result.json" +) + +Set-StrictMode -Version Latest +$ErrorActionPreference = "Stop" + +$repoRoot = (Resolve-Path (Join-Path $PSScriptRoot "..\..")).ProviderPath +$sourceResultPath = "artifacts\c19z119-remote-workspace-real-adapter-operator-outcome-closeout-reopen-boundary-source-result.json" +$requiredBoundaryFields = @( + "schema_version", + "source_outcome_schema", + "boundary_status", + "boundary_marker", + "closed_outcome_status", + "reopen_policy", + "next_required_phase", + "enablement_decision", + "operator_review_status", + "enablement_boundary", + "enablement_status", + "runtime_gate_state", + "runtime_effect", + "operator_default_action", + "allows_process_start", + "allows_payload_traffic", + "guardrail_summary" +) +$requiredGuardrailFields = @("activation_blocked", "process_start_allowed", "health_probe_enabled", "payload_traffic", "allows_process_start", "allows_payload_traffic") + +function Get-PropertyValue { + param([object]$Item, [string]$Name, [object]$Default = $null) + if ($null -eq $Item) { return $Default } + if ($Item -is [System.Collections.IDictionary]) { + if ($Item.Contains($Name)) { return $Item[$Name] } + return $Default + } + $property = $Item.PSObject.Properties[$Name] + if ($null -eq $property) { return $Default } + return $property.Value +} + +function Test-ObjectHasFields { + param([object]$Item, [string[]]$Fields) + if ($null -eq $Item) { return $false } + foreach ($field in $Fields) { + if ($Item -is [System.Collections.IDictionary]) { + if (-not $Item.Contains($field)) { return $false } + continue + } + if ($null -eq $Item.PSObject.Properties[$field]) { return $false } + } + return $true +} + +& powershell -ExecutionPolicy Bypass -File (Join-Path $PSScriptRoot "c19z118-remote-workspace-real-adapter-operator-approval-rejection-outcome-compatibility-smoke.ps1") ` + -ApiBaseUrl $ApiBaseUrl ` + -ClusterID $ClusterID ` + -ActorUserID $ActorUserID ` + -RequestedNodeName $RequestedNodeName ` + -DefaultNodeName $DefaultNodeName ` + -MatrixNodeName $MatrixNodeName ` + -ResultPath $sourceResultPath | Out-Null + +$sourceFile = Join-Path $repoRoot $sourceResultPath +$sourceResult = Get-Content -Raw -Path $sourceFile | ConvertFrom-Json +$outcome = Get-PropertyValue -Item $sourceResult -Name "operator_approval_rejection_outcome" -Default $null +$guardrails = Get-PropertyValue -Item $outcome -Name "guardrail_summary" -Default $null + +$boundary = [ordered]@{ + schema_version = "rap.remote_workspace_real_adapter_operator_outcome_closeout_reopen_boundary.v1" + source_outcome_schema = Get-PropertyValue -Item $outcome -Name "schema_version" -Default $null + boundary_status = "closed_not_approved_reopen_required" + boundary_marker = "c19z119_real_adapter_operator_outcome_closeout_reopen_required" + closed_outcome_status = Get-PropertyValue -Item $outcome -Name "outcome_status" -Default $null + reopen_policy = "new_explicit_enablement_request_required" + next_required_phase = "explicit_operator_reopen_or_new_enablement_request" + enablement_decision = Get-PropertyValue -Item $outcome -Name "enablement_decision" -Default $null + operator_review_status = Get-PropertyValue -Item $outcome -Name "operator_review_status" -Default $null + enablement_boundary = Get-PropertyValue -Item $outcome -Name "enablement_boundary" -Default $null + enablement_status = Get-PropertyValue -Item $outcome -Name "enablement_status" -Default $null + runtime_gate_state = Get-PropertyValue -Item $outcome -Name "runtime_gate_state" -Default $null + runtime_effect = Get-PropertyValue -Item $outcome -Name "runtime_effect" -Default $null + operator_default_action = Get-PropertyValue -Item $outcome -Name "operator_default_action" -Default $null + allows_process_start = $false + allows_payload_traffic = $false + guardrail_summary = $guardrails +} + +$boundaryFieldsCompatible = Test-ObjectHasFields -Item $boundary -Fields $requiredBoundaryFields +$boundaryValuesCompatible = ( + [string](Get-PropertyValue -Item $boundary -Name "schema_version" -Default "") -eq "rap.remote_workspace_real_adapter_operator_outcome_closeout_reopen_boundary.v1" -and + [string](Get-PropertyValue -Item $boundary -Name "source_outcome_schema" -Default "") -eq "rap.remote_workspace_real_adapter_operator_approval_rejection_outcome.v1" -and + [string](Get-PropertyValue -Item $boundary -Name "boundary_status" -Default "") -eq "closed_not_approved_reopen_required" -and + [string](Get-PropertyValue -Item $boundary -Name "boundary_marker" -Default "") -eq "c19z119_real_adapter_operator_outcome_closeout_reopen_required" -and + [string](Get-PropertyValue -Item $boundary -Name "closed_outcome_status" -Default "") -eq "rejected_or_not_approved_contract_only" -and + [string](Get-PropertyValue -Item $boundary -Name "reopen_policy" -Default "") -eq "new_explicit_enablement_request_required" -and + [string](Get-PropertyValue -Item $boundary -Name "next_required_phase" -Default "") -eq "explicit_operator_reopen_or_new_enablement_request" -and + [string](Get-PropertyValue -Item $boundary -Name "enablement_decision" -Default "") -eq "not_approved" -and + [string](Get-PropertyValue -Item $boundary -Name "operator_review_status" -Default "") -eq "closed_without_approval" -and + [string](Get-PropertyValue -Item $boundary -Name "enablement_boundary" -Default "") -eq "explicit_operator_enablement_required" -and + [string](Get-PropertyValue -Item $boundary -Name "enablement_status" -Default "") -eq "not_enabled" -and + [string](Get-PropertyValue -Item $boundary -Name "runtime_gate_state" -Default "") -eq "validated_contract_only_not_enabled" -and + [string](Get-PropertyValue -Item $boundary -Name "runtime_effect" -Default "") -eq "contract_only_no_runtime_enablement" -and + [string](Get-PropertyValue -Item $boundary -Name "operator_default_action" -Default "") -eq "keep_real_adapter_disabled" -and + -not [bool](Get-PropertyValue -Item $boundary -Name "allows_process_start" -Default $true) -and + -not [bool](Get-PropertyValue -Item $boundary -Name "allows_payload_traffic" -Default $true) +) +$guardrailsCompatible = ( + (Test-ObjectHasFields -Item $guardrails -Fields $requiredGuardrailFields) -and + [bool](Get-PropertyValue -Item $guardrails -Name "activation_blocked" -Default $false) -and + -not [bool](Get-PropertyValue -Item $guardrails -Name "process_start_allowed" -Default $true) -and + -not [bool](Get-PropertyValue -Item $guardrails -Name "health_probe_enabled" -Default $true) -and + [string](Get-PropertyValue -Item $guardrails -Name "payload_traffic" -Default "") -eq "none" -and + -not [bool](Get-PropertyValue -Item $guardrails -Name "allows_process_start" -Default $true) -and + -not [bool](Get-PropertyValue -Item $guardrails -Name "allows_payload_traffic" -Default $true) +) + +$checks = [ordered]@{ + source_smoke_passed = ([bool]$sourceResult.passed) + source_schema_expected = ([string]$sourceResult.schema_version -eq "c19z118.remote_workspace_real_adapter_operator_approval_rejection_outcome_compatibility_smoke.v1") + outcome_present = ($null -ne $outcome) + boundary_fields_compatible = $boundaryFieldsCompatible + boundary_values_compatible = $boundaryValuesCompatible + guardrails_compatible = $guardrailsCompatible +} +$failed = @($checks.GetEnumerator() | Where-Object { -not $_.Value } | ForEach-Object { $_.Key }) + +$result = [ordered]@{ + schema_version = "c19z119.remote_workspace_real_adapter_operator_outcome_closeout_reopen_boundary_smoke.v1" + source_result_path = $sourceFile + cluster_id = $ClusterID + required_boundary_fields = $requiredBoundaryFields + required_guardrail_fields = $requiredGuardrailFields + operator_outcome_closeout_reopen_boundary = $boundary + checks = $checks + failed_checks = $failed + passed = ($failed.Count -eq 0) +} + +$fullResultPath = Join-Path $repoRoot $ResultPath +$resultDir = Split-Path -Parent $fullResultPath +if ($resultDir) { New-Item -ItemType Directory -Force -Path $resultDir | Out-Null } +$result | ConvertTo-Json -Depth 100 | Set-Content -Encoding UTF8 -Path $fullResultPath + +if (-not $result.passed) { + throw "C19Z119 remote workspace real-adapter operator outcome closeout reopen boundary smoke failed. Result: $fullResultPath Failed: $($failed -join ', ')" +} + +Write-Host "C19Z119 remote workspace real-adapter operator outcome closeout reopen boundary smoke passed. Result: $fullResultPath" +$result diff --git a/scripts/fabric/c19z12-remote-workspace-mailbox-preflight-status-counts-smoke.ps1 b/scripts/fabric/c19z12-remote-workspace-mailbox-preflight-status-counts-smoke.ps1 new file mode 100644 index 0000000..d0a65c0 --- /dev/null +++ b/scripts/fabric/c19z12-remote-workspace-mailbox-preflight-status-counts-smoke.ps1 @@ -0,0 +1,89 @@ +param( + [string]$ApiBaseUrl = "http://192.168.200.61:18121/api/v1", + [string]$ClusterID = "cfc0743d-d960-49fb-9de8-96e063d5e4aa", + [string]$ActorUserID = "f67d943f-5397-4b3a-a229-695fe67ad700", + [string]$EntryNodeName = "test-1", + [string]$ExitNodeName = "test-2", + [string]$EntryBaseUrl = "http://192.168.200.61:19131", + [string]$ResultPath = "artifacts\c19z12-remote-workspace-mailbox-preflight-status-counts-smoke-result.json" +) + +Set-StrictMode -Version Latest +$ErrorActionPreference = "Stop" + +$repoRoot = (Resolve-Path (Join-Path $PSScriptRoot "..\..")).ProviderPath +$sourceResultPath = "artifacts\c19z12-remote-workspace-mailbox-preflight-status-counts-source-result.json" + +function Get-PropertyValue { + param([object]$Item, [string]$Name, [object]$Default = $null) + if ($null -eq $Item) { return $Default } + $property = $Item.PSObject.Properties[$Name] + if ($null -eq $property) { return $Default } + return $property.Value +} + +function Test-StatusCounts { + param([object]$Sink, [string]$AdapterSessionID) + if ($null -eq $Sink) { return $false } + $readiness = Get-PropertyValue -Item $Sink -Name "adapter_runtime_readiness" -Default $null + $rollup = Get-PropertyValue -Item $readiness -Name "last_preflight" -Default $null + if ($null -eq $rollup) { return $false } + $readinessStatusCounts = Get-PropertyValue -Item $readiness -Name "mailbox_preflight_operator_status_counts" -Default $null + $readinessSeverityCounts = Get-PropertyValue -Item $readiness -Name "mailbox_preflight_operator_severity_counts" -Default $null + $rollupStatusCounts = Get-PropertyValue -Item $rollup -Name "operator_status_counts" -Default $null + $rollupSeverityCounts = Get-PropertyValue -Item $rollup -Name "operator_severity_counts" -Default $null + return ( + [string](Get-PropertyValue -Item $readiness -Name "adapter_session_id" -Default "") -eq $AdapterSessionID -and + [string](Get-PropertyValue -Item $rollup -Name "operator_status" -Default "") -eq "resync_required" -and + [string](Get-PropertyValue -Item $rollup -Name "operator_severity" -Default "") -eq "warn" -and + [int64](Get-PropertyValue -Item $readinessStatusCounts -Name "resync_required" -Default 0) -ge 1 -and + [int64](Get-PropertyValue -Item $readinessSeverityCounts -Name "warn" -Default 0) -ge 1 -and + [int64](Get-PropertyValue -Item $rollupStatusCounts -Name "resync_required" -Default 0) -ge 1 -and + [int64](Get-PropertyValue -Item $rollupSeverityCounts -Name "warn" -Default 0) -ge 1 + ) +} + +$source = & powershell -ExecutionPolicy Bypass -File (Join-Path $PSScriptRoot "c19z11-remote-workspace-mailbox-preflight-checklist-status-smoke.ps1") ` + -ApiBaseUrl $ApiBaseUrl ` + -ClusterID $ClusterID ` + -ActorUserID $ActorUserID ` + -EntryNodeName $EntryNodeName ` + -ExitNodeName $ExitNodeName ` + -EntryBaseUrl $EntryBaseUrl ` + -ResultPath $sourceResultPath + +$sourceFile = Join-Path $repoRoot $sourceResultPath +$sourceResult = Get-Content -Raw -Path $sourceFile | ConvertFrom-Json +$adapterSessionID = [string](Get-PropertyValue -Item $sourceResult -Name "adapter_session_id" -Default "") +$observed = Get-PropertyValue -Item $sourceResult -Name "observed" -Default $null + +$checks = [ordered]@{ + source_smoke_passed = ([bool]$sourceResult.passed) + adapter_session_id_present = ($adapterSessionID -match "^rap-rw-adapter-session-[0-9a-f]{24}$") + workload_status_counts_visible = (Test-StatusCounts -Sink $observed.workload_sink -AdapterSessionID $adapterSessionID) + telemetry_status_counts_visible = (Test-StatusCounts -Sink $observed.telemetry_sink -AdapterSessionID $adapterSessionID) +} +$failed = @($checks.GetEnumerator() | Where-Object { -not $_.Value } | ForEach-Object { $_.Key }) + +$result = [ordered]@{ + schema_version = "c19z12.remote_workspace_mailbox_preflight_status_counts_smoke.v1" + source_result_path = $sourceFile + cluster_id = $ClusterID + adapter_session_id = $adapterSessionID + observed = $observed + checks = $checks + failed_checks = $failed + passed = ($failed.Count -eq 0) +} + +$fullResultPath = Join-Path $repoRoot $ResultPath +$resultDir = Split-Path -Parent $fullResultPath +if ($resultDir) { New-Item -ItemType Directory -Force -Path $resultDir | Out-Null } +$result | ConvertTo-Json -Depth 100 | Set-Content -Encoding UTF8 -Path $fullResultPath + +if (-not $result.passed) { + throw "C19Z12 remote workspace mailbox preflight status counts smoke failed. Result: $fullResultPath Failed: $($failed -join ', ')" +} + +Write-Host "C19Z12 remote workspace mailbox preflight status counts smoke passed. Result: $fullResultPath" +$result diff --git a/scripts/fabric/c19z120-remote-workspace-real-adapter-operator-outcome-closeout-reopen-boundary-compatibility-smoke.ps1 b/scripts/fabric/c19z120-remote-workspace-real-adapter-operator-outcome-closeout-reopen-boundary-compatibility-smoke.ps1 new file mode 100644 index 0000000..00437a5 --- /dev/null +++ b/scripts/fabric/c19z120-remote-workspace-real-adapter-operator-outcome-closeout-reopen-boundary-compatibility-smoke.ps1 @@ -0,0 +1,137 @@ +param( + [string]$ApiBaseUrl = "http://192.168.200.61:18121/api/v1", + [string]$ClusterID = "cfc0743d-d960-49fb-9de8-96e063d5e4aa", + [string]$ActorUserID = "f67d943f-5397-4b3a-a229-695fe67ad700", + [string]$RequestedNodeName = "test-1", + [string]$DefaultNodeName = "test-2", + [string]$MatrixNodeName = "test-1", + [string]$ResultPath = "artifacts\c19z120-remote-workspace-real-adapter-operator-outcome-closeout-reopen-boundary-compatibility-smoke-result.json" +) + +Set-StrictMode -Version Latest +$ErrorActionPreference = "Stop" + +$repoRoot = (Resolve-Path (Join-Path $PSScriptRoot "..\..")).ProviderPath +$sourceResultPath = "artifacts\c19z120-remote-workspace-real-adapter-operator-outcome-closeout-reopen-boundary-source-result.json" +$requiredBoundaryFields = @( + "schema_version", + "source_outcome_schema", + "boundary_status", + "boundary_marker", + "closed_outcome_status", + "reopen_policy", + "next_required_phase", + "enablement_decision", + "operator_review_status", + "enablement_boundary", + "enablement_status", + "runtime_gate_state", + "runtime_effect", + "operator_default_action", + "allows_process_start", + "allows_payload_traffic", + "guardrail_summary" +) +$requiredGuardrailFields = @("activation_blocked", "process_start_allowed", "health_probe_enabled", "payload_traffic", "allows_process_start", "allows_payload_traffic") + +function Get-PropertyValue { + param([object]$Item, [string]$Name, [object]$Default = $null) + if ($null -eq $Item) { return $Default } + if ($Item -is [System.Collections.IDictionary]) { + if ($Item.Contains($Name)) { return $Item[$Name] } + return $Default + } + $property = $Item.PSObject.Properties[$Name] + if ($null -eq $property) { return $Default } + return $property.Value +} + +function Test-ObjectHasFields { + param([object]$Item, [string[]]$Fields) + if ($null -eq $Item) { return $false } + foreach ($field in $Fields) { + if ($Item -is [System.Collections.IDictionary]) { + if (-not $Item.Contains($field)) { return $false } + continue + } + if ($null -eq $Item.PSObject.Properties[$field]) { return $false } + } + return $true +} + +& powershell -ExecutionPolicy Bypass -File (Join-Path $PSScriptRoot "c19z119-remote-workspace-real-adapter-operator-outcome-closeout-reopen-boundary-smoke.ps1") ` + -ApiBaseUrl $ApiBaseUrl ` + -ClusterID $ClusterID ` + -ActorUserID $ActorUserID ` + -RequestedNodeName $RequestedNodeName ` + -DefaultNodeName $DefaultNodeName ` + -MatrixNodeName $MatrixNodeName ` + -ResultPath $sourceResultPath | Out-Null + +$sourceFile = Join-Path $repoRoot $sourceResultPath +$sourceResult = Get-Content -Raw -Path $sourceFile | ConvertFrom-Json +$boundary = Get-PropertyValue -Item $sourceResult -Name "operator_outcome_closeout_reopen_boundary" -Default $null +$guardrails = Get-PropertyValue -Item $boundary -Name "guardrail_summary" -Default $null + +$boundaryFieldsCompatible = Test-ObjectHasFields -Item $boundary -Fields $requiredBoundaryFields +$boundaryValuesCompatible = ( + [string](Get-PropertyValue -Item $boundary -Name "schema_version" -Default "") -eq "rap.remote_workspace_real_adapter_operator_outcome_closeout_reopen_boundary.v1" -and + [string](Get-PropertyValue -Item $boundary -Name "source_outcome_schema" -Default "") -eq "rap.remote_workspace_real_adapter_operator_approval_rejection_outcome.v1" -and + [string](Get-PropertyValue -Item $boundary -Name "boundary_status" -Default "") -eq "closed_not_approved_reopen_required" -and + [string](Get-PropertyValue -Item $boundary -Name "boundary_marker" -Default "") -eq "c19z119_real_adapter_operator_outcome_closeout_reopen_required" -and + [string](Get-PropertyValue -Item $boundary -Name "closed_outcome_status" -Default "") -eq "rejected_or_not_approved_contract_only" -and + [string](Get-PropertyValue -Item $boundary -Name "reopen_policy" -Default "") -eq "new_explicit_enablement_request_required" -and + [string](Get-PropertyValue -Item $boundary -Name "next_required_phase" -Default "") -eq "explicit_operator_reopen_or_new_enablement_request" -and + [string](Get-PropertyValue -Item $boundary -Name "enablement_decision" -Default "") -eq "not_approved" -and + [string](Get-PropertyValue -Item $boundary -Name "operator_review_status" -Default "") -eq "closed_without_approval" -and + [string](Get-PropertyValue -Item $boundary -Name "enablement_boundary" -Default "") -eq "explicit_operator_enablement_required" -and + [string](Get-PropertyValue -Item $boundary -Name "enablement_status" -Default "") -eq "not_enabled" -and + [string](Get-PropertyValue -Item $boundary -Name "runtime_gate_state" -Default "") -eq "validated_contract_only_not_enabled" -and + [string](Get-PropertyValue -Item $boundary -Name "runtime_effect" -Default "") -eq "contract_only_no_runtime_enablement" -and + [string](Get-PropertyValue -Item $boundary -Name "operator_default_action" -Default "") -eq "keep_real_adapter_disabled" -and + -not [bool](Get-PropertyValue -Item $boundary -Name "allows_process_start" -Default $true) -and + -not [bool](Get-PropertyValue -Item $boundary -Name "allows_payload_traffic" -Default $true) +) +$guardrailsCompatible = ( + (Test-ObjectHasFields -Item $guardrails -Fields $requiredGuardrailFields) -and + [bool](Get-PropertyValue -Item $guardrails -Name "activation_blocked" -Default $false) -and + -not [bool](Get-PropertyValue -Item $guardrails -Name "process_start_allowed" -Default $true) -and + -not [bool](Get-PropertyValue -Item $guardrails -Name "health_probe_enabled" -Default $true) -and + [string](Get-PropertyValue -Item $guardrails -Name "payload_traffic" -Default "") -eq "none" -and + -not [bool](Get-PropertyValue -Item $guardrails -Name "allows_process_start" -Default $true) -and + -not [bool](Get-PropertyValue -Item $guardrails -Name "allows_payload_traffic" -Default $true) +) + +$checks = [ordered]@{ + source_smoke_passed = ([bool]$sourceResult.passed) + source_schema_expected = ([string]$sourceResult.schema_version -eq "c19z119.remote_workspace_real_adapter_operator_outcome_closeout_reopen_boundary_smoke.v1") + boundary_present = ($null -ne $boundary) + boundary_fields_compatible = $boundaryFieldsCompatible + boundary_values_compatible = $boundaryValuesCompatible + guardrails_compatible = $guardrailsCompatible +} +$failed = @($checks.GetEnumerator() | Where-Object { -not $_.Value } | ForEach-Object { $_.Key }) + +$result = [ordered]@{ + schema_version = "c19z120.remote_workspace_real_adapter_operator_outcome_closeout_reopen_boundary_compatibility_smoke.v1" + source_result_path = $sourceFile + cluster_id = $ClusterID + required_boundary_fields = $requiredBoundaryFields + required_guardrail_fields = $requiredGuardrailFields + operator_outcome_closeout_reopen_boundary = $boundary + checks = $checks + failed_checks = $failed + passed = ($failed.Count -eq 0) +} + +$fullResultPath = Join-Path $repoRoot $ResultPath +$resultDir = Split-Path -Parent $fullResultPath +if ($resultDir) { New-Item -ItemType Directory -Force -Path $resultDir | Out-Null } +$result | ConvertTo-Json -Depth 100 | Set-Content -Encoding UTF8 -Path $fullResultPath + +if (-not $result.passed) { + throw "C19Z120 remote workspace real-adapter operator outcome closeout reopen boundary compatibility smoke failed. Result: $fullResultPath Failed: $($failed -join ', ')" +} + +Write-Host "C19Z120 remote workspace real-adapter operator outcome closeout reopen boundary compatibility smoke passed. Result: $fullResultPath" +$result diff --git a/scripts/fabric/c19z121-remote-workspace-real-adapter-not-approved-outcome-release-marker-smoke.ps1 b/scripts/fabric/c19z121-remote-workspace-real-adapter-not-approved-outcome-release-marker-smoke.ps1 new file mode 100644 index 0000000..f6bd1b0 --- /dev/null +++ b/scripts/fabric/c19z121-remote-workspace-real-adapter-not-approved-outcome-release-marker-smoke.ps1 @@ -0,0 +1,154 @@ +param( + [string]$ApiBaseUrl = "http://192.168.200.61:18121/api/v1", + [string]$ClusterID = "cfc0743d-d960-49fb-9de8-96e063d5e4aa", + [string]$ActorUserID = "f67d943f-5397-4b3a-a229-695fe67ad700", + [string]$RequestedNodeName = "test-1", + [string]$DefaultNodeName = "test-2", + [string]$MatrixNodeName = "test-1", + [string]$ResultPath = "artifacts\c19z121-remote-workspace-real-adapter-not-approved-outcome-release-marker-smoke-result.json" +) + +Set-StrictMode -Version Latest +$ErrorActionPreference = "Stop" + +$repoRoot = (Resolve-Path (Join-Path $PSScriptRoot "..\..")).ProviderPath +$sourceResultPath = "artifacts\c19z121-remote-workspace-real-adapter-not-approved-outcome-release-marker-source-result.json" +$requiredReleaseFields = @( + "schema_version", + "source_boundary_schema", + "release_status", + "release_marker", + "boundary_status", + "closed_outcome_status", + "reopen_policy", + "enablement_decision", + "operator_review_status", + "enablement_status", + "runtime_gate_state", + "runtime_effect", + "operator_default_action", + "allows_process_start", + "allows_payload_traffic", + "guardrail_summary" +) +$requiredGuardrailFields = @("activation_blocked", "process_start_allowed", "health_probe_enabled", "payload_traffic", "allows_process_start", "allows_payload_traffic") + +function Get-PropertyValue { + param([object]$Item, [string]$Name, [object]$Default = $null) + if ($null -eq $Item) { return $Default } + if ($Item -is [System.Collections.IDictionary]) { + if ($Item.Contains($Name)) { return $Item[$Name] } + return $Default + } + $property = $Item.PSObject.Properties[$Name] + if ($null -eq $property) { return $Default } + return $property.Value +} + +function Test-ObjectHasFields { + param([object]$Item, [string[]]$Fields) + if ($null -eq $Item) { return $false } + foreach ($field in $Fields) { + if ($Item -is [System.Collections.IDictionary]) { + if (-not $Item.Contains($field)) { return $false } + continue + } + if ($null -eq $Item.PSObject.Properties[$field]) { return $false } + } + return $true +} + +& powershell -ExecutionPolicy Bypass -File (Join-Path $PSScriptRoot "c19z120-remote-workspace-real-adapter-operator-outcome-closeout-reopen-boundary-compatibility-smoke.ps1") ` + -ApiBaseUrl $ApiBaseUrl ` + -ClusterID $ClusterID ` + -ActorUserID $ActorUserID ` + -RequestedNodeName $RequestedNodeName ` + -DefaultNodeName $DefaultNodeName ` + -MatrixNodeName $MatrixNodeName ` + -ResultPath $sourceResultPath | Out-Null + +$sourceFile = Join-Path $repoRoot $sourceResultPath +$sourceResult = Get-Content -Raw -Path $sourceFile | ConvertFrom-Json +$boundary = Get-PropertyValue -Item $sourceResult -Name "operator_outcome_closeout_reopen_boundary" -Default $null +$guardrails = Get-PropertyValue -Item $boundary -Name "guardrail_summary" -Default $null + +$marker = [ordered]@{ + schema_version = "rap.remote_workspace_real_adapter_not_approved_outcome_release_marker.v1" + source_boundary_schema = Get-PropertyValue -Item $boundary -Name "schema_version" -Default $null + release_status = "not_approved_outcome_closed_contract_only" + release_marker = "c19z121_real_adapter_not_approved_outcome_release_marker" + boundary_status = Get-PropertyValue -Item $boundary -Name "boundary_status" -Default $null + closed_outcome_status = Get-PropertyValue -Item $boundary -Name "closed_outcome_status" -Default $null + reopen_policy = Get-PropertyValue -Item $boundary -Name "reopen_policy" -Default $null + enablement_decision = Get-PropertyValue -Item $boundary -Name "enablement_decision" -Default $null + operator_review_status = Get-PropertyValue -Item $boundary -Name "operator_review_status" -Default $null + enablement_status = Get-PropertyValue -Item $boundary -Name "enablement_status" -Default $null + runtime_gate_state = Get-PropertyValue -Item $boundary -Name "runtime_gate_state" -Default $null + runtime_effect = Get-PropertyValue -Item $boundary -Name "runtime_effect" -Default $null + operator_default_action = Get-PropertyValue -Item $boundary -Name "operator_default_action" -Default $null + allows_process_start = $false + allows_payload_traffic = $false + guardrail_summary = $guardrails +} + +$releaseFieldsCompatible = Test-ObjectHasFields -Item $marker -Fields $requiredReleaseFields +$releaseValuesCompatible = ( + [string](Get-PropertyValue -Item $marker -Name "schema_version" -Default "") -eq "rap.remote_workspace_real_adapter_not_approved_outcome_release_marker.v1" -and + [string](Get-PropertyValue -Item $marker -Name "source_boundary_schema" -Default "") -eq "rap.remote_workspace_real_adapter_operator_outcome_closeout_reopen_boundary.v1" -and + [string](Get-PropertyValue -Item $marker -Name "release_status" -Default "") -eq "not_approved_outcome_closed_contract_only" -and + [string](Get-PropertyValue -Item $marker -Name "release_marker" -Default "") -eq "c19z121_real_adapter_not_approved_outcome_release_marker" -and + [string](Get-PropertyValue -Item $marker -Name "boundary_status" -Default "") -eq "closed_not_approved_reopen_required" -and + [string](Get-PropertyValue -Item $marker -Name "closed_outcome_status" -Default "") -eq "rejected_or_not_approved_contract_only" -and + [string](Get-PropertyValue -Item $marker -Name "reopen_policy" -Default "") -eq "new_explicit_enablement_request_required" -and + [string](Get-PropertyValue -Item $marker -Name "enablement_decision" -Default "") -eq "not_approved" -and + [string](Get-PropertyValue -Item $marker -Name "operator_review_status" -Default "") -eq "closed_without_approval" -and + [string](Get-PropertyValue -Item $marker -Name "enablement_status" -Default "") -eq "not_enabled" -and + [string](Get-PropertyValue -Item $marker -Name "runtime_gate_state" -Default "") -eq "validated_contract_only_not_enabled" -and + [string](Get-PropertyValue -Item $marker -Name "runtime_effect" -Default "") -eq "contract_only_no_runtime_enablement" -and + [string](Get-PropertyValue -Item $marker -Name "operator_default_action" -Default "") -eq "keep_real_adapter_disabled" -and + -not [bool](Get-PropertyValue -Item $marker -Name "allows_process_start" -Default $true) -and + -not [bool](Get-PropertyValue -Item $marker -Name "allows_payload_traffic" -Default $true) +) +$guardrailsCompatible = ( + (Test-ObjectHasFields -Item $guardrails -Fields $requiredGuardrailFields) -and + [bool](Get-PropertyValue -Item $guardrails -Name "activation_blocked" -Default $false) -and + -not [bool](Get-PropertyValue -Item $guardrails -Name "process_start_allowed" -Default $true) -and + -not [bool](Get-PropertyValue -Item $guardrails -Name "health_probe_enabled" -Default $true) -and + [string](Get-PropertyValue -Item $guardrails -Name "payload_traffic" -Default "") -eq "none" -and + -not [bool](Get-PropertyValue -Item $guardrails -Name "allows_process_start" -Default $true) -and + -not [bool](Get-PropertyValue -Item $guardrails -Name "allows_payload_traffic" -Default $true) +) + +$checks = [ordered]@{ + source_smoke_passed = ([bool]$sourceResult.passed) + source_schema_expected = ([string]$sourceResult.schema_version -eq "c19z120.remote_workspace_real_adapter_operator_outcome_closeout_reopen_boundary_compatibility_smoke.v1") + boundary_present = ($null -ne $boundary) + release_fields_compatible = $releaseFieldsCompatible + release_values_compatible = $releaseValuesCompatible + guardrails_compatible = $guardrailsCompatible +} +$failed = @($checks.GetEnumerator() | Where-Object { -not $_.Value } | ForEach-Object { $_.Key }) + +$result = [ordered]@{ + schema_version = "c19z121.remote_workspace_real_adapter_not_approved_outcome_release_marker_smoke.v1" + source_result_path = $sourceFile + cluster_id = $ClusterID + required_release_fields = $requiredReleaseFields + required_guardrail_fields = $requiredGuardrailFields + not_approved_outcome_release_marker = $marker + checks = $checks + failed_checks = $failed + passed = ($failed.Count -eq 0) +} + +$fullResultPath = Join-Path $repoRoot $ResultPath +$resultDir = Split-Path -Parent $fullResultPath +if ($resultDir) { New-Item -ItemType Directory -Force -Path $resultDir | Out-Null } +$result | ConvertTo-Json -Depth 100 | Set-Content -Encoding UTF8 -Path $fullResultPath + +if (-not $result.passed) { + throw "C19Z121 remote workspace real-adapter not-approved outcome release marker smoke failed. Result: $fullResultPath Failed: $($failed -join ', ')" +} + +Write-Host "C19Z121 remote workspace real-adapter not-approved outcome release marker smoke passed. Result: $fullResultPath" +$result diff --git a/scripts/fabric/c19z122-remote-workspace-real-adapter-not-approved-outcome-release-marker-compatibility-smoke.ps1 b/scripts/fabric/c19z122-remote-workspace-real-adapter-not-approved-outcome-release-marker-compatibility-smoke.ps1 new file mode 100644 index 0000000..6d838c1 --- /dev/null +++ b/scripts/fabric/c19z122-remote-workspace-real-adapter-not-approved-outcome-release-marker-compatibility-smoke.ps1 @@ -0,0 +1,135 @@ +param( + [string]$ApiBaseUrl = "http://192.168.200.61:18121/api/v1", + [string]$ClusterID = "cfc0743d-d960-49fb-9de8-96e063d5e4aa", + [string]$ActorUserID = "f67d943f-5397-4b3a-a229-695fe67ad700", + [string]$RequestedNodeName = "test-1", + [string]$DefaultNodeName = "test-2", + [string]$MatrixNodeName = "test-1", + [string]$ResultPath = "artifacts\c19z122-remote-workspace-real-adapter-not-approved-outcome-release-marker-compatibility-smoke-result.json" +) + +Set-StrictMode -Version Latest +$ErrorActionPreference = "Stop" + +$repoRoot = (Resolve-Path (Join-Path $PSScriptRoot "..\..")).ProviderPath +$sourceResultPath = "artifacts\c19z122-remote-workspace-real-adapter-not-approved-outcome-release-marker-source-result.json" +$requiredReleaseFields = @( + "schema_version", + "source_boundary_schema", + "release_status", + "release_marker", + "boundary_status", + "closed_outcome_status", + "reopen_policy", + "enablement_decision", + "operator_review_status", + "enablement_status", + "runtime_gate_state", + "runtime_effect", + "operator_default_action", + "allows_process_start", + "allows_payload_traffic", + "guardrail_summary" +) +$requiredGuardrailFields = @("activation_blocked", "process_start_allowed", "health_probe_enabled", "payload_traffic", "allows_process_start", "allows_payload_traffic") + +function Get-PropertyValue { + param([object]$Item, [string]$Name, [object]$Default = $null) + if ($null -eq $Item) { return $Default } + if ($Item -is [System.Collections.IDictionary]) { + if ($Item.Contains($Name)) { return $Item[$Name] } + return $Default + } + $property = $Item.PSObject.Properties[$Name] + if ($null -eq $property) { return $Default } + return $property.Value +} + +function Test-ObjectHasFields { + param([object]$Item, [string[]]$Fields) + if ($null -eq $Item) { return $false } + foreach ($field in $Fields) { + if ($Item -is [System.Collections.IDictionary]) { + if (-not $Item.Contains($field)) { return $false } + continue + } + if ($null -eq $Item.PSObject.Properties[$field]) { return $false } + } + return $true +} + +& powershell -ExecutionPolicy Bypass -File (Join-Path $PSScriptRoot "c19z121-remote-workspace-real-adapter-not-approved-outcome-release-marker-smoke.ps1") ` + -ApiBaseUrl $ApiBaseUrl ` + -ClusterID $ClusterID ` + -ActorUserID $ActorUserID ` + -RequestedNodeName $RequestedNodeName ` + -DefaultNodeName $DefaultNodeName ` + -MatrixNodeName $MatrixNodeName ` + -ResultPath $sourceResultPath | Out-Null + +$sourceFile = Join-Path $repoRoot $sourceResultPath +$sourceResult = Get-Content -Raw -Path $sourceFile | ConvertFrom-Json +$marker = Get-PropertyValue -Item $sourceResult -Name "not_approved_outcome_release_marker" -Default $null +$guardrails = Get-PropertyValue -Item $marker -Name "guardrail_summary" -Default $null + +$releaseFieldsCompatible = Test-ObjectHasFields -Item $marker -Fields $requiredReleaseFields +$releaseValuesCompatible = ( + [string](Get-PropertyValue -Item $marker -Name "schema_version" -Default "") -eq "rap.remote_workspace_real_adapter_not_approved_outcome_release_marker.v1" -and + [string](Get-PropertyValue -Item $marker -Name "source_boundary_schema" -Default "") -eq "rap.remote_workspace_real_adapter_operator_outcome_closeout_reopen_boundary.v1" -and + [string](Get-PropertyValue -Item $marker -Name "release_status" -Default "") -eq "not_approved_outcome_closed_contract_only" -and + [string](Get-PropertyValue -Item $marker -Name "release_marker" -Default "") -eq "c19z121_real_adapter_not_approved_outcome_release_marker" -and + [string](Get-PropertyValue -Item $marker -Name "boundary_status" -Default "") -eq "closed_not_approved_reopen_required" -and + [string](Get-PropertyValue -Item $marker -Name "closed_outcome_status" -Default "") -eq "rejected_or_not_approved_contract_only" -and + [string](Get-PropertyValue -Item $marker -Name "reopen_policy" -Default "") -eq "new_explicit_enablement_request_required" -and + [string](Get-PropertyValue -Item $marker -Name "enablement_decision" -Default "") -eq "not_approved" -and + [string](Get-PropertyValue -Item $marker -Name "operator_review_status" -Default "") -eq "closed_without_approval" -and + [string](Get-PropertyValue -Item $marker -Name "enablement_status" -Default "") -eq "not_enabled" -and + [string](Get-PropertyValue -Item $marker -Name "runtime_gate_state" -Default "") -eq "validated_contract_only_not_enabled" -and + [string](Get-PropertyValue -Item $marker -Name "runtime_effect" -Default "") -eq "contract_only_no_runtime_enablement" -and + [string](Get-PropertyValue -Item $marker -Name "operator_default_action" -Default "") -eq "keep_real_adapter_disabled" -and + -not [bool](Get-PropertyValue -Item $marker -Name "allows_process_start" -Default $true) -and + -not [bool](Get-PropertyValue -Item $marker -Name "allows_payload_traffic" -Default $true) +) +$guardrailsCompatible = ( + (Test-ObjectHasFields -Item $guardrails -Fields $requiredGuardrailFields) -and + [bool](Get-PropertyValue -Item $guardrails -Name "activation_blocked" -Default $false) -and + -not [bool](Get-PropertyValue -Item $guardrails -Name "process_start_allowed" -Default $true) -and + -not [bool](Get-PropertyValue -Item $guardrails -Name "health_probe_enabled" -Default $true) -and + [string](Get-PropertyValue -Item $guardrails -Name "payload_traffic" -Default "") -eq "none" -and + -not [bool](Get-PropertyValue -Item $guardrails -Name "allows_process_start" -Default $true) -and + -not [bool](Get-PropertyValue -Item $guardrails -Name "allows_payload_traffic" -Default $true) +) + +$checks = [ordered]@{ + source_smoke_passed = ([bool]$sourceResult.passed) + source_schema_expected = ([string]$sourceResult.schema_version -eq "c19z121.remote_workspace_real_adapter_not_approved_outcome_release_marker_smoke.v1") + release_marker_present = ($null -ne $marker) + release_fields_compatible = $releaseFieldsCompatible + release_values_compatible = $releaseValuesCompatible + guardrails_compatible = $guardrailsCompatible +} +$failed = @($checks.GetEnumerator() | Where-Object { -not $_.Value } | ForEach-Object { $_.Key }) + +$result = [ordered]@{ + schema_version = "c19z122.remote_workspace_real_adapter_not_approved_outcome_release_marker_compatibility_smoke.v1" + source_result_path = $sourceFile + cluster_id = $ClusterID + required_release_fields = $requiredReleaseFields + required_guardrail_fields = $requiredGuardrailFields + not_approved_outcome_release_marker = $marker + checks = $checks + failed_checks = $failed + passed = ($failed.Count -eq 0) +} + +$fullResultPath = Join-Path $repoRoot $ResultPath +$resultDir = Split-Path -Parent $fullResultPath +if ($resultDir) { New-Item -ItemType Directory -Force -Path $resultDir | Out-Null } +$result | ConvertTo-Json -Depth 100 | Set-Content -Encoding UTF8 -Path $fullResultPath + +if (-not $result.passed) { + throw "C19Z122 remote workspace real-adapter not-approved outcome release marker compatibility smoke failed. Result: $fullResultPath Failed: $($failed -join ', ')" +} + +Write-Host "C19Z122 remote workspace real-adapter not-approved outcome release marker compatibility smoke passed. Result: $fullResultPath" +$result diff --git a/scripts/fabric/c19z123-remote-workspace-real-adapter-not-approved-outcome-package-index-smoke.ps1 b/scripts/fabric/c19z123-remote-workspace-real-adapter-not-approved-outcome-package-index-smoke.ps1 new file mode 100644 index 0000000..6bf2b7c --- /dev/null +++ b/scripts/fabric/c19z123-remote-workspace-real-adapter-not-approved-outcome-package-index-smoke.ps1 @@ -0,0 +1,185 @@ +param( + [string]$ApiBaseUrl = "http://192.168.200.61:18121/api/v1", + [string]$ClusterID = "cfc0743d-d960-49fb-9de8-96e063d5e4aa", + [string]$ActorUserID = "f67d943f-5397-4b3a-a229-695fe67ad700", + [string]$RequestedNodeName = "test-1", + [string]$DefaultNodeName = "test-2", + [string]$MatrixNodeName = "test-1", + [string]$ResultPath = "artifacts\c19z123-remote-workspace-real-adapter-not-approved-outcome-package-index-smoke-result.json" +) + +Set-StrictMode -Version Latest +$ErrorActionPreference = "Stop" + +$repoRoot = (Resolve-Path (Join-Path $PSScriptRoot "..\..")).ProviderPath +$sourceResultPath = "artifacts\c19z123-remote-workspace-real-adapter-not-approved-outcome-package-index-source-result.json" +$requiredPackageFields = @( + "schema_version", + "source_release_marker_schema", + "package_status", + "package_marker", + "covered_stage_range", + "covered_stage_count", + "latest_compatibility_stage", + "release_status", + "boundary_status", + "reopen_policy", + "enablement_decision", + "operator_review_status", + "enablement_status", + "runtime_gate_state", + "runtime_effect", + "operator_default_action", + "allows_process_start", + "allows_payload_traffic", + "guardrail_summary", + "closeout_notes" +) +$requiredGuardrailFields = @("activation_blocked", "process_start_allowed", "health_probe_enabled", "payload_traffic", "allows_process_start", "allows_payload_traffic") +$requiredCloseoutNotes = @( + "not_approved_outcome_package_indexed", + "operator_outcome_closed_without_approval", + "new_explicit_enablement_request_required", + "real_runtime_gate_not_enabled", + "process_start_disabled", + "payload_forwarding_disabled" +) + +function Get-PropertyValue { + param([object]$Item, [string]$Name, [object]$Default = $null) + if ($null -eq $Item) { return $Default } + if ($Item -is [System.Collections.IDictionary]) { + if ($Item.Contains($Name)) { return $Item[$Name] } + return $Default + } + $property = $Item.PSObject.Properties[$Name] + if ($null -eq $property) { return $Default } + return $property.Value +} + +function Test-ObjectHasFields { + param([object]$Item, [string[]]$Fields) + if ($null -eq $Item) { return $false } + foreach ($field in $Fields) { + if ($Item -is [System.Collections.IDictionary]) { + if (-not $Item.Contains($field)) { return $false } + continue + } + if ($null -eq $Item.PSObject.Properties[$field]) { return $false } + } + return $true +} + +function Test-ArrayContainsAll { + param([object[]]$Actual, [string[]]$Required) + $values = @($Actual | ForEach-Object { [string]$_ }) + foreach ($item in $Required) { + if ($values -notcontains $item) { return $false } + } + return $true +} + +& powershell -ExecutionPolicy Bypass -File (Join-Path $PSScriptRoot "c19z122-remote-workspace-real-adapter-not-approved-outcome-release-marker-compatibility-smoke.ps1") ` + -ApiBaseUrl $ApiBaseUrl ` + -ClusterID $ClusterID ` + -ActorUserID $ActorUserID ` + -RequestedNodeName $RequestedNodeName ` + -DefaultNodeName $DefaultNodeName ` + -MatrixNodeName $MatrixNodeName ` + -ResultPath $sourceResultPath | Out-Null + +$sourceFile = Join-Path $repoRoot $sourceResultPath +$sourceResult = Get-Content -Raw -Path $sourceFile | ConvertFrom-Json +$marker = Get-PropertyValue -Item $sourceResult -Name "not_approved_outcome_release_marker" -Default $null +$guardrails = Get-PropertyValue -Item $marker -Name "guardrail_summary" -Default $null + +$packageIndex = [ordered]@{ + schema_version = "rap.remote_workspace_real_adapter_not_approved_outcome_package_index.v1" + source_release_marker_schema = Get-PropertyValue -Item $marker -Name "schema_version" -Default $null + package_status = "closed_not_approved_contract_only" + package_marker = "c19z123_real_adapter_not_approved_outcome_package_index" + covered_stage_range = "C19Z117-C19Z122" + covered_stage_count = 6 + latest_compatibility_stage = "C19Z122" + release_status = Get-PropertyValue -Item $marker -Name "release_status" -Default $null + boundary_status = Get-PropertyValue -Item $marker -Name "boundary_status" -Default $null + reopen_policy = Get-PropertyValue -Item $marker -Name "reopen_policy" -Default $null + enablement_decision = Get-PropertyValue -Item $marker -Name "enablement_decision" -Default $null + operator_review_status = Get-PropertyValue -Item $marker -Name "operator_review_status" -Default $null + enablement_status = Get-PropertyValue -Item $marker -Name "enablement_status" -Default $null + runtime_gate_state = Get-PropertyValue -Item $marker -Name "runtime_gate_state" -Default $null + runtime_effect = Get-PropertyValue -Item $marker -Name "runtime_effect" -Default $null + operator_default_action = Get-PropertyValue -Item $marker -Name "operator_default_action" -Default $null + allows_process_start = $false + allows_payload_traffic = $false + guardrail_summary = $guardrails + closeout_notes = $requiredCloseoutNotes +} + +$packageFieldsCompatible = Test-ObjectHasFields -Item $packageIndex -Fields $requiredPackageFields +$closeoutNotesCompatible = Test-ArrayContainsAll -Actual @(Get-PropertyValue -Item $packageIndex -Name "closeout_notes" -Default @()) -Required $requiredCloseoutNotes +$packageValuesCompatible = ( + [string](Get-PropertyValue -Item $packageIndex -Name "schema_version" -Default "") -eq "rap.remote_workspace_real_adapter_not_approved_outcome_package_index.v1" -and + [string](Get-PropertyValue -Item $packageIndex -Name "source_release_marker_schema" -Default "") -eq "rap.remote_workspace_real_adapter_not_approved_outcome_release_marker.v1" -and + [string](Get-PropertyValue -Item $packageIndex -Name "package_status" -Default "") -eq "closed_not_approved_contract_only" -and + [string](Get-PropertyValue -Item $packageIndex -Name "package_marker" -Default "") -eq "c19z123_real_adapter_not_approved_outcome_package_index" -and + [string](Get-PropertyValue -Item $packageIndex -Name "covered_stage_range" -Default "") -eq "C19Z117-C19Z122" -and + [int](Get-PropertyValue -Item $packageIndex -Name "covered_stage_count" -Default -1) -eq 6 -and + [string](Get-PropertyValue -Item $packageIndex -Name "latest_compatibility_stage" -Default "") -eq "C19Z122" -and + [string](Get-PropertyValue -Item $packageIndex -Name "release_status" -Default "") -eq "not_approved_outcome_closed_contract_only" -and + [string](Get-PropertyValue -Item $packageIndex -Name "boundary_status" -Default "") -eq "closed_not_approved_reopen_required" -and + [string](Get-PropertyValue -Item $packageIndex -Name "reopen_policy" -Default "") -eq "new_explicit_enablement_request_required" -and + [string](Get-PropertyValue -Item $packageIndex -Name "enablement_decision" -Default "") -eq "not_approved" -and + [string](Get-PropertyValue -Item $packageIndex -Name "operator_review_status" -Default "") -eq "closed_without_approval" -and + [string](Get-PropertyValue -Item $packageIndex -Name "enablement_status" -Default "") -eq "not_enabled" -and + [string](Get-PropertyValue -Item $packageIndex -Name "runtime_gate_state" -Default "") -eq "validated_contract_only_not_enabled" -and + [string](Get-PropertyValue -Item $packageIndex -Name "runtime_effect" -Default "") -eq "contract_only_no_runtime_enablement" -and + [string](Get-PropertyValue -Item $packageIndex -Name "operator_default_action" -Default "") -eq "keep_real_adapter_disabled" -and + -not [bool](Get-PropertyValue -Item $packageIndex -Name "allows_process_start" -Default $true) -and + -not [bool](Get-PropertyValue -Item $packageIndex -Name "allows_payload_traffic" -Default $true) +) +$guardrailsCompatible = ( + (Test-ObjectHasFields -Item $guardrails -Fields $requiredGuardrailFields) -and + [bool](Get-PropertyValue -Item $guardrails -Name "activation_blocked" -Default $false) -and + -not [bool](Get-PropertyValue -Item $guardrails -Name "process_start_allowed" -Default $true) -and + -not [bool](Get-PropertyValue -Item $guardrails -Name "health_probe_enabled" -Default $true) -and + [string](Get-PropertyValue -Item $guardrails -Name "payload_traffic" -Default "") -eq "none" -and + -not [bool](Get-PropertyValue -Item $guardrails -Name "allows_process_start" -Default $true) -and + -not [bool](Get-PropertyValue -Item $guardrails -Name "allows_payload_traffic" -Default $true) +) + +$checks = [ordered]@{ + source_smoke_passed = ([bool]$sourceResult.passed) + source_schema_expected = ([string]$sourceResult.schema_version -eq "c19z122.remote_workspace_real_adapter_not_approved_outcome_release_marker_compatibility_smoke.v1") + release_marker_present = ($null -ne $marker) + package_fields_compatible = $packageFieldsCompatible + package_values_compatible = $packageValuesCompatible + closeout_notes_compatible = $closeoutNotesCompatible + guardrails_compatible = $guardrailsCompatible +} +$failed = @($checks.GetEnumerator() | Where-Object { -not $_.Value } | ForEach-Object { $_.Key }) + +$result = [ordered]@{ + schema_version = "c19z123.remote_workspace_real_adapter_not_approved_outcome_package_index_smoke.v1" + source_result_path = $sourceFile + cluster_id = $ClusterID + required_package_fields = $requiredPackageFields + required_guardrail_fields = $requiredGuardrailFields + required_closeout_notes = $requiredCloseoutNotes + not_approved_outcome_package_index = $packageIndex + checks = $checks + failed_checks = $failed + passed = ($failed.Count -eq 0) +} + +$fullResultPath = Join-Path $repoRoot $ResultPath +$resultDir = Split-Path -Parent $fullResultPath +if ($resultDir) { New-Item -ItemType Directory -Force -Path $resultDir | Out-Null } +$result | ConvertTo-Json -Depth 100 | Set-Content -Encoding UTF8 -Path $fullResultPath + +if (-not $result.passed) { + throw "C19Z123 remote workspace real-adapter not-approved outcome package index smoke failed. Result: $fullResultPath Failed: $($failed -join ', ')" +} + +Write-Host "C19Z123 remote workspace real-adapter not-approved outcome package index smoke passed. Result: $fullResultPath" +$result diff --git a/scripts/fabric/c19z124-remote-workspace-real-adapter-not-approved-outcome-package-index-compatibility-smoke.ps1 b/scripts/fabric/c19z124-remote-workspace-real-adapter-not-approved-outcome-package-index-compatibility-smoke.ps1 new file mode 100644 index 0000000..50f4168 --- /dev/null +++ b/scripts/fabric/c19z124-remote-workspace-real-adapter-not-approved-outcome-package-index-compatibility-smoke.ps1 @@ -0,0 +1,163 @@ +param( + [string]$ApiBaseUrl = "http://192.168.200.61:18121/api/v1", + [string]$ClusterID = "cfc0743d-d960-49fb-9de8-96e063d5e4aa", + [string]$ActorUserID = "f67d943f-5397-4b3a-a229-695fe67ad700", + [string]$RequestedNodeName = "test-1", + [string]$DefaultNodeName = "test-2", + [string]$MatrixNodeName = "test-1", + [string]$ResultPath = "artifacts\c19z124-remote-workspace-real-adapter-not-approved-outcome-package-index-compatibility-smoke-result.json" +) + +Set-StrictMode -Version Latest +$ErrorActionPreference = "Stop" + +$repoRoot = (Resolve-Path (Join-Path $PSScriptRoot "..\..")).ProviderPath +$sourceResultPath = "artifacts\c19z124-remote-workspace-real-adapter-not-approved-outcome-package-index-source-result.json" +$requiredPackageFields = @( + "schema_version", + "source_release_marker_schema", + "package_status", + "package_marker", + "covered_stage_range", + "covered_stage_count", + "latest_compatibility_stage", + "release_status", + "boundary_status", + "reopen_policy", + "enablement_decision", + "operator_review_status", + "enablement_status", + "runtime_gate_state", + "runtime_effect", + "operator_default_action", + "allows_process_start", + "allows_payload_traffic", + "guardrail_summary", + "closeout_notes" +) +$requiredGuardrailFields = @("activation_blocked", "process_start_allowed", "health_probe_enabled", "payload_traffic", "allows_process_start", "allows_payload_traffic") +$requiredCloseoutNotes = @( + "not_approved_outcome_package_indexed", + "operator_outcome_closed_without_approval", + "new_explicit_enablement_request_required", + "real_runtime_gate_not_enabled", + "process_start_disabled", + "payload_forwarding_disabled" +) + +function Get-PropertyValue { + param([object]$Item, [string]$Name, [object]$Default = $null) + if ($null -eq $Item) { return $Default } + if ($Item -is [System.Collections.IDictionary]) { + if ($Item.Contains($Name)) { return $Item[$Name] } + return $Default + } + $property = $Item.PSObject.Properties[$Name] + if ($null -eq $property) { return $Default } + return $property.Value +} + +function Test-ObjectHasFields { + param([object]$Item, [string[]]$Fields) + if ($null -eq $Item) { return $false } + foreach ($field in $Fields) { + if ($Item -is [System.Collections.IDictionary]) { + if (-not $Item.Contains($field)) { return $false } + continue + } + if ($null -eq $Item.PSObject.Properties[$field]) { return $false } + } + return $true +} + +function Test-ArrayContainsAll { + param([object[]]$Actual, [string[]]$Required) + $values = @($Actual | ForEach-Object { [string]$_ }) + foreach ($item in $Required) { + if ($values -notcontains $item) { return $false } + } + return $true +} + +& powershell -ExecutionPolicy Bypass -File (Join-Path $PSScriptRoot "c19z123-remote-workspace-real-adapter-not-approved-outcome-package-index-smoke.ps1") ` + -ApiBaseUrl $ApiBaseUrl ` + -ClusterID $ClusterID ` + -ActorUserID $ActorUserID ` + -RequestedNodeName $RequestedNodeName ` + -DefaultNodeName $DefaultNodeName ` + -MatrixNodeName $MatrixNodeName ` + -ResultPath $sourceResultPath | Out-Null + +$sourceFile = Join-Path $repoRoot $sourceResultPath +$sourceResult = Get-Content -Raw -Path $sourceFile | ConvertFrom-Json +$packageIndex = Get-PropertyValue -Item $sourceResult -Name "not_approved_outcome_package_index" -Default $null +$guardrails = Get-PropertyValue -Item $packageIndex -Name "guardrail_summary" -Default $null +$closeoutNotes = @(Get-PropertyValue -Item $packageIndex -Name "closeout_notes" -Default @()) + +$packageFieldsCompatible = Test-ObjectHasFields -Item $packageIndex -Fields $requiredPackageFields +$closeoutNotesCompatible = Test-ArrayContainsAll -Actual $closeoutNotes -Required $requiredCloseoutNotes +$packageValuesCompatible = ( + [string](Get-PropertyValue -Item $packageIndex -Name "schema_version" -Default "") -eq "rap.remote_workspace_real_adapter_not_approved_outcome_package_index.v1" -and + [string](Get-PropertyValue -Item $packageIndex -Name "source_release_marker_schema" -Default "") -eq "rap.remote_workspace_real_adapter_not_approved_outcome_release_marker.v1" -and + [string](Get-PropertyValue -Item $packageIndex -Name "package_status" -Default "") -eq "closed_not_approved_contract_only" -and + [string](Get-PropertyValue -Item $packageIndex -Name "package_marker" -Default "") -eq "c19z123_real_adapter_not_approved_outcome_package_index" -and + [string](Get-PropertyValue -Item $packageIndex -Name "covered_stage_range" -Default "") -eq "C19Z117-C19Z122" -and + [int](Get-PropertyValue -Item $packageIndex -Name "covered_stage_count" -Default -1) -eq 6 -and + [string](Get-PropertyValue -Item $packageIndex -Name "latest_compatibility_stage" -Default "") -eq "C19Z122" -and + [string](Get-PropertyValue -Item $packageIndex -Name "release_status" -Default "") -eq "not_approved_outcome_closed_contract_only" -and + [string](Get-PropertyValue -Item $packageIndex -Name "boundary_status" -Default "") -eq "closed_not_approved_reopen_required" -and + [string](Get-PropertyValue -Item $packageIndex -Name "reopen_policy" -Default "") -eq "new_explicit_enablement_request_required" -and + [string](Get-PropertyValue -Item $packageIndex -Name "enablement_decision" -Default "") -eq "not_approved" -and + [string](Get-PropertyValue -Item $packageIndex -Name "operator_review_status" -Default "") -eq "closed_without_approval" -and + [string](Get-PropertyValue -Item $packageIndex -Name "enablement_status" -Default "") -eq "not_enabled" -and + [string](Get-PropertyValue -Item $packageIndex -Name "runtime_gate_state" -Default "") -eq "validated_contract_only_not_enabled" -and + [string](Get-PropertyValue -Item $packageIndex -Name "runtime_effect" -Default "") -eq "contract_only_no_runtime_enablement" -and + [string](Get-PropertyValue -Item $packageIndex -Name "operator_default_action" -Default "") -eq "keep_real_adapter_disabled" -and + -not [bool](Get-PropertyValue -Item $packageIndex -Name "allows_process_start" -Default $true) -and + -not [bool](Get-PropertyValue -Item $packageIndex -Name "allows_payload_traffic" -Default $true) +) +$guardrailsCompatible = ( + (Test-ObjectHasFields -Item $guardrails -Fields $requiredGuardrailFields) -and + [bool](Get-PropertyValue -Item $guardrails -Name "activation_blocked" -Default $false) -and + -not [bool](Get-PropertyValue -Item $guardrails -Name "process_start_allowed" -Default $true) -and + -not [bool](Get-PropertyValue -Item $guardrails -Name "health_probe_enabled" -Default $true) -and + [string](Get-PropertyValue -Item $guardrails -Name "payload_traffic" -Default "") -eq "none" -and + -not [bool](Get-PropertyValue -Item $guardrails -Name "allows_process_start" -Default $true) -and + -not [bool](Get-PropertyValue -Item $guardrails -Name "allows_payload_traffic" -Default $true) +) + +$checks = [ordered]@{ + source_smoke_passed = ([bool]$sourceResult.passed) + source_schema_expected = ([string]$sourceResult.schema_version -eq "c19z123.remote_workspace_real_adapter_not_approved_outcome_package_index_smoke.v1") + package_index_present = ($null -ne $packageIndex) + package_fields_compatible = $packageFieldsCompatible + package_values_compatible = $packageValuesCompatible + closeout_notes_compatible = $closeoutNotesCompatible + guardrails_compatible = $guardrailsCompatible +} +$failed = @($checks.GetEnumerator() | Where-Object { -not $_.Value } | ForEach-Object { $_.Key }) + +$result = [ordered]@{ + schema_version = "c19z124.remote_workspace_real_adapter_not_approved_outcome_package_index_compatibility_smoke.v1" + source_result_path = $sourceFile + cluster_id = $ClusterID + required_package_fields = $requiredPackageFields + required_guardrail_fields = $requiredGuardrailFields + required_closeout_notes = $requiredCloseoutNotes + not_approved_outcome_package_index = $packageIndex + checks = $checks + failed_checks = $failed + passed = ($failed.Count -eq 0) +} + +$fullResultPath = Join-Path $repoRoot $ResultPath +$resultDir = Split-Path -Parent $fullResultPath +if ($resultDir) { New-Item -ItemType Directory -Force -Path $resultDir | Out-Null } +$result | ConvertTo-Json -Depth 100 | Set-Content -Encoding UTF8 -Path $fullResultPath + +if (-not $result.passed) { + throw "C19Z124 remote workspace real-adapter not-approved outcome package index compatibility smoke failed. Result: $fullResultPath Failed: $($failed -join ', ')" +} + +Write-Host "C19Z124 remote workspace real-adapter not-approved outcome package index compatibility smoke passed. Result: $fullResultPath" +$result diff --git a/scripts/fabric/c19z125-remote-workspace-real-adapter-not-approved-outcome-closeout-summary-smoke.ps1 b/scripts/fabric/c19z125-remote-workspace-real-adapter-not-approved-outcome-closeout-summary-smoke.ps1 new file mode 100644 index 0000000..b6613c7 --- /dev/null +++ b/scripts/fabric/c19z125-remote-workspace-real-adapter-not-approved-outcome-closeout-summary-smoke.ps1 @@ -0,0 +1,166 @@ +param( + [string]$ApiBaseUrl = "http://192.168.200.61:18121/api/v1", + [string]$ClusterID = "cfc0743d-d960-49fb-9de8-96e063d5e4aa", + [string]$ActorUserID = "f67d943f-5397-4b3a-a229-695fe67ad700", + [string]$RequestedNodeName = "test-1", + [string]$DefaultNodeName = "test-2", + [string]$MatrixNodeName = "test-1", + [string]$ResultPath = "artifacts\c19z125-remote-workspace-real-adapter-not-approved-outcome-closeout-summary-smoke-result.json" +) + +Set-StrictMode -Version Latest +$ErrorActionPreference = "Stop" + +$repoRoot = (Resolve-Path (Join-Path $PSScriptRoot "..\..")).ProviderPath +$sourceResultPath = "artifacts\c19z125-remote-workspace-real-adapter-not-approved-outcome-closeout-summary-source-result.json" +$requiredCloseoutFields = @( + "schema_version", + "source_package_index_schema", + "closeout_status", + "closeout_marker", + "covered_stage_range", + "covered_stage_count", + "latest_compatibility_stage", + "release_status", + "boundary_status", + "reopen_policy", + "enablement_decision", + "operator_review_status", + "enablement_status", + "runtime_gate_state", + "runtime_effect", + "operator_default_action", + "next_required_phase", + "allows_process_start", + "allows_payload_traffic", + "guardrail_summary" +) +$requiredGuardrailFields = @("activation_blocked", "process_start_allowed", "health_probe_enabled", "payload_traffic", "allows_process_start", "allows_payload_traffic") + +function Get-PropertyValue { + param([object]$Item, [string]$Name, [object]$Default = $null) + if ($null -eq $Item) { return $Default } + if ($Item -is [System.Collections.IDictionary]) { + if ($Item.Contains($Name)) { return $Item[$Name] } + return $Default + } + $property = $Item.PSObject.Properties[$Name] + if ($null -eq $property) { return $Default } + return $property.Value +} + +function Test-ObjectHasFields { + param([object]$Item, [string[]]$Fields) + if ($null -eq $Item) { return $false } + foreach ($field in $Fields) { + if ($Item -is [System.Collections.IDictionary]) { + if (-not $Item.Contains($field)) { return $false } + continue + } + if ($null -eq $Item.PSObject.Properties[$field]) { return $false } + } + return $true +} + +& powershell -ExecutionPolicy Bypass -File (Join-Path $PSScriptRoot "c19z124-remote-workspace-real-adapter-not-approved-outcome-package-index-compatibility-smoke.ps1") ` + -ApiBaseUrl $ApiBaseUrl ` + -ClusterID $ClusterID ` + -ActorUserID $ActorUserID ` + -RequestedNodeName $RequestedNodeName ` + -DefaultNodeName $DefaultNodeName ` + -MatrixNodeName $MatrixNodeName ` + -ResultPath $sourceResultPath | Out-Null + +$sourceFile = Join-Path $repoRoot $sourceResultPath +$sourceResult = Get-Content -Raw -Path $sourceFile | ConvertFrom-Json +$packageIndex = Get-PropertyValue -Item $sourceResult -Name "not_approved_outcome_package_index" -Default $null +$guardrails = Get-PropertyValue -Item $packageIndex -Name "guardrail_summary" -Default $null + +$closeout = [ordered]@{ + schema_version = "rap.remote_workspace_real_adapter_not_approved_outcome_closeout_summary.v1" + source_package_index_schema = Get-PropertyValue -Item $packageIndex -Name "schema_version" -Default $null + closeout_status = "closed_not_approved_package_complete" + closeout_marker = "c19z125_real_adapter_not_approved_outcome_closed_contract_only" + covered_stage_range = Get-PropertyValue -Item $packageIndex -Name "covered_stage_range" -Default $null + covered_stage_count = Get-PropertyValue -Item $packageIndex -Name "covered_stage_count" -Default $null + latest_compatibility_stage = Get-PropertyValue -Item $packageIndex -Name "latest_compatibility_stage" -Default $null + release_status = Get-PropertyValue -Item $packageIndex -Name "release_status" -Default $null + boundary_status = Get-PropertyValue -Item $packageIndex -Name "boundary_status" -Default $null + reopen_policy = Get-PropertyValue -Item $packageIndex -Name "reopen_policy" -Default $null + enablement_decision = Get-PropertyValue -Item $packageIndex -Name "enablement_decision" -Default $null + operator_review_status = Get-PropertyValue -Item $packageIndex -Name "operator_review_status" -Default $null + enablement_status = Get-PropertyValue -Item $packageIndex -Name "enablement_status" -Default $null + runtime_gate_state = Get-PropertyValue -Item $packageIndex -Name "runtime_gate_state" -Default $null + runtime_effect = Get-PropertyValue -Item $packageIndex -Name "runtime_effect" -Default $null + operator_default_action = Get-PropertyValue -Item $packageIndex -Name "operator_default_action" -Default $null + next_required_phase = "explicit_new_enablement_request_only" + allows_process_start = $false + allows_payload_traffic = $false + guardrail_summary = $guardrails +} + +$closeoutFieldsCompatible = Test-ObjectHasFields -Item $closeout -Fields $requiredCloseoutFields +$closeoutValuesCompatible = ( + [string](Get-PropertyValue -Item $closeout -Name "schema_version" -Default "") -eq "rap.remote_workspace_real_adapter_not_approved_outcome_closeout_summary.v1" -and + [string](Get-PropertyValue -Item $closeout -Name "source_package_index_schema" -Default "") -eq "rap.remote_workspace_real_adapter_not_approved_outcome_package_index.v1" -and + [string](Get-PropertyValue -Item $closeout -Name "closeout_status" -Default "") -eq "closed_not_approved_package_complete" -and + [string](Get-PropertyValue -Item $closeout -Name "closeout_marker" -Default "") -eq "c19z125_real_adapter_not_approved_outcome_closed_contract_only" -and + [string](Get-PropertyValue -Item $closeout -Name "covered_stage_range" -Default "") -eq "C19Z117-C19Z122" -and + [int](Get-PropertyValue -Item $closeout -Name "covered_stage_count" -Default -1) -eq 6 -and + [string](Get-PropertyValue -Item $closeout -Name "latest_compatibility_stage" -Default "") -eq "C19Z122" -and + [string](Get-PropertyValue -Item $closeout -Name "release_status" -Default "") -eq "not_approved_outcome_closed_contract_only" -and + [string](Get-PropertyValue -Item $closeout -Name "boundary_status" -Default "") -eq "closed_not_approved_reopen_required" -and + [string](Get-PropertyValue -Item $closeout -Name "reopen_policy" -Default "") -eq "new_explicit_enablement_request_required" -and + [string](Get-PropertyValue -Item $closeout -Name "enablement_decision" -Default "") -eq "not_approved" -and + [string](Get-PropertyValue -Item $closeout -Name "operator_review_status" -Default "") -eq "closed_without_approval" -and + [string](Get-PropertyValue -Item $closeout -Name "enablement_status" -Default "") -eq "not_enabled" -and + [string](Get-PropertyValue -Item $closeout -Name "runtime_gate_state" -Default "") -eq "validated_contract_only_not_enabled" -and + [string](Get-PropertyValue -Item $closeout -Name "runtime_effect" -Default "") -eq "contract_only_no_runtime_enablement" -and + [string](Get-PropertyValue -Item $closeout -Name "operator_default_action" -Default "") -eq "keep_real_adapter_disabled" -and + [string](Get-PropertyValue -Item $closeout -Name "next_required_phase" -Default "") -eq "explicit_new_enablement_request_only" -and + -not [bool](Get-PropertyValue -Item $closeout -Name "allows_process_start" -Default $true) -and + -not [bool](Get-PropertyValue -Item $closeout -Name "allows_payload_traffic" -Default $true) +) +$guardrailsCompatible = ( + (Test-ObjectHasFields -Item $guardrails -Fields $requiredGuardrailFields) -and + [bool](Get-PropertyValue -Item $guardrails -Name "activation_blocked" -Default $false) -and + -not [bool](Get-PropertyValue -Item $guardrails -Name "process_start_allowed" -Default $true) -and + -not [bool](Get-PropertyValue -Item $guardrails -Name "health_probe_enabled" -Default $true) -and + [string](Get-PropertyValue -Item $guardrails -Name "payload_traffic" -Default "") -eq "none" -and + -not [bool](Get-PropertyValue -Item $guardrails -Name "allows_process_start" -Default $true) -and + -not [bool](Get-PropertyValue -Item $guardrails -Name "allows_payload_traffic" -Default $true) +) + +$checks = [ordered]@{ + source_smoke_passed = ([bool]$sourceResult.passed) + source_schema_expected = ([string]$sourceResult.schema_version -eq "c19z124.remote_workspace_real_adapter_not_approved_outcome_package_index_compatibility_smoke.v1") + package_index_present = ($null -ne $packageIndex) + closeout_fields_compatible = $closeoutFieldsCompatible + closeout_values_compatible = $closeoutValuesCompatible + guardrails_compatible = $guardrailsCompatible +} +$failed = @($checks.GetEnumerator() | Where-Object { -not $_.Value } | ForEach-Object { $_.Key }) + +$result = [ordered]@{ + schema_version = "c19z125.remote_workspace_real_adapter_not_approved_outcome_closeout_summary_smoke.v1" + source_result_path = $sourceFile + cluster_id = $ClusterID + required_closeout_fields = $requiredCloseoutFields + required_guardrail_fields = $requiredGuardrailFields + not_approved_outcome_closeout_summary = $closeout + checks = $checks + failed_checks = $failed + passed = ($failed.Count -eq 0) +} + +$fullResultPath = Join-Path $repoRoot $ResultPath +$resultDir = Split-Path -Parent $fullResultPath +if ($resultDir) { New-Item -ItemType Directory -Force -Path $resultDir | Out-Null } +$result | ConvertTo-Json -Depth 100 | Set-Content -Encoding UTF8 -Path $fullResultPath + +if (-not $result.passed) { + throw "C19Z125 remote workspace real-adapter not-approved outcome closeout summary smoke failed. Result: $fullResultPath Failed: $($failed -join ', ')" +} + +Write-Host "C19Z125 remote workspace real-adapter not-approved outcome closeout summary smoke passed. Result: $fullResultPath" +$result diff --git a/scripts/fabric/c19z126-remote-workspace-real-adapter-not-approved-outcome-closeout-summary-compatibility-smoke.ps1 b/scripts/fabric/c19z126-remote-workspace-real-adapter-not-approved-outcome-closeout-summary-compatibility-smoke.ps1 new file mode 100644 index 0000000..9e3f503 --- /dev/null +++ b/scripts/fabric/c19z126-remote-workspace-real-adapter-not-approved-outcome-closeout-summary-compatibility-smoke.ps1 @@ -0,0 +1,143 @@ +param( + [string]$ApiBaseUrl = "http://192.168.200.61:18121/api/v1", + [string]$ClusterID = "cfc0743d-d960-49fb-9de8-96e063d5e4aa", + [string]$ActorUserID = "f67d943f-5397-4b3a-a229-695fe67ad700", + [string]$RequestedNodeName = "test-1", + [string]$DefaultNodeName = "test-2", + [string]$MatrixNodeName = "test-1", + [string]$ResultPath = "artifacts\c19z126-remote-workspace-real-adapter-not-approved-outcome-closeout-summary-compatibility-smoke-result.json" +) + +Set-StrictMode -Version Latest +$ErrorActionPreference = "Stop" + +$repoRoot = (Resolve-Path (Join-Path $PSScriptRoot "..\..")).ProviderPath +$sourceResultPath = "artifacts\c19z126-remote-workspace-real-adapter-not-approved-outcome-closeout-summary-source-result.json" +$requiredCloseoutFields = @( + "schema_version", + "source_package_index_schema", + "closeout_status", + "closeout_marker", + "covered_stage_range", + "covered_stage_count", + "latest_compatibility_stage", + "release_status", + "boundary_status", + "reopen_policy", + "enablement_decision", + "operator_review_status", + "enablement_status", + "runtime_gate_state", + "runtime_effect", + "operator_default_action", + "next_required_phase", + "allows_process_start", + "allows_payload_traffic", + "guardrail_summary" +) +$requiredGuardrailFields = @("activation_blocked", "process_start_allowed", "health_probe_enabled", "payload_traffic", "allows_process_start", "allows_payload_traffic") + +function Get-PropertyValue { + param([object]$Item, [string]$Name, [object]$Default = $null) + if ($null -eq $Item) { return $Default } + if ($Item -is [System.Collections.IDictionary]) { + if ($Item.Contains($Name)) { return $Item[$Name] } + return $Default + } + $property = $Item.PSObject.Properties[$Name] + if ($null -eq $property) { return $Default } + return $property.Value +} + +function Test-ObjectHasFields { + param([object]$Item, [string[]]$Fields) + if ($null -eq $Item) { return $false } + foreach ($field in $Fields) { + if ($Item -is [System.Collections.IDictionary]) { + if (-not $Item.Contains($field)) { return $false } + continue + } + if ($null -eq $Item.PSObject.Properties[$field]) { return $false } + } + return $true +} + +& powershell -ExecutionPolicy Bypass -File (Join-Path $PSScriptRoot "c19z125-remote-workspace-real-adapter-not-approved-outcome-closeout-summary-smoke.ps1") ` + -ApiBaseUrl $ApiBaseUrl ` + -ClusterID $ClusterID ` + -ActorUserID $ActorUserID ` + -RequestedNodeName $RequestedNodeName ` + -DefaultNodeName $DefaultNodeName ` + -MatrixNodeName $MatrixNodeName ` + -ResultPath $sourceResultPath | Out-Null + +$sourceFile = Join-Path $repoRoot $sourceResultPath +$sourceResult = Get-Content -Raw -Path $sourceFile | ConvertFrom-Json +$closeout = Get-PropertyValue -Item $sourceResult -Name "not_approved_outcome_closeout_summary" -Default $null +$guardrails = Get-PropertyValue -Item $closeout -Name "guardrail_summary" -Default $null + +$closeoutFieldsCompatible = Test-ObjectHasFields -Item $closeout -Fields $requiredCloseoutFields +$closeoutValuesCompatible = ( + [string](Get-PropertyValue -Item $closeout -Name "schema_version" -Default "") -eq "rap.remote_workspace_real_adapter_not_approved_outcome_closeout_summary.v1" -and + [string](Get-PropertyValue -Item $closeout -Name "source_package_index_schema" -Default "") -eq "rap.remote_workspace_real_adapter_not_approved_outcome_package_index.v1" -and + [string](Get-PropertyValue -Item $closeout -Name "closeout_status" -Default "") -eq "closed_not_approved_package_complete" -and + [string](Get-PropertyValue -Item $closeout -Name "closeout_marker" -Default "") -eq "c19z125_real_adapter_not_approved_outcome_closed_contract_only" -and + [string](Get-PropertyValue -Item $closeout -Name "covered_stage_range" -Default "") -eq "C19Z117-C19Z122" -and + [int](Get-PropertyValue -Item $closeout -Name "covered_stage_count" -Default -1) -eq 6 -and + [string](Get-PropertyValue -Item $closeout -Name "latest_compatibility_stage" -Default "") -eq "C19Z122" -and + [string](Get-PropertyValue -Item $closeout -Name "release_status" -Default "") -eq "not_approved_outcome_closed_contract_only" -and + [string](Get-PropertyValue -Item $closeout -Name "boundary_status" -Default "") -eq "closed_not_approved_reopen_required" -and + [string](Get-PropertyValue -Item $closeout -Name "reopen_policy" -Default "") -eq "new_explicit_enablement_request_required" -and + [string](Get-PropertyValue -Item $closeout -Name "enablement_decision" -Default "") -eq "not_approved" -and + [string](Get-PropertyValue -Item $closeout -Name "operator_review_status" -Default "") -eq "closed_without_approval" -and + [string](Get-PropertyValue -Item $closeout -Name "enablement_status" -Default "") -eq "not_enabled" -and + [string](Get-PropertyValue -Item $closeout -Name "runtime_gate_state" -Default "") -eq "validated_contract_only_not_enabled" -and + [string](Get-PropertyValue -Item $closeout -Name "runtime_effect" -Default "") -eq "contract_only_no_runtime_enablement" -and + [string](Get-PropertyValue -Item $closeout -Name "operator_default_action" -Default "") -eq "keep_real_adapter_disabled" -and + [string](Get-PropertyValue -Item $closeout -Name "next_required_phase" -Default "") -eq "explicit_new_enablement_request_only" -and + -not [bool](Get-PropertyValue -Item $closeout -Name "allows_process_start" -Default $true) -and + -not [bool](Get-PropertyValue -Item $closeout -Name "allows_payload_traffic" -Default $true) +) +$guardrailsCompatible = ( + (Test-ObjectHasFields -Item $guardrails -Fields $requiredGuardrailFields) -and + [bool](Get-PropertyValue -Item $guardrails -Name "activation_blocked" -Default $false) -and + -not [bool](Get-PropertyValue -Item $guardrails -Name "process_start_allowed" -Default $true) -and + -not [bool](Get-PropertyValue -Item $guardrails -Name "health_probe_enabled" -Default $true) -and + [string](Get-PropertyValue -Item $guardrails -Name "payload_traffic" -Default "") -eq "none" -and + -not [bool](Get-PropertyValue -Item $guardrails -Name "allows_process_start" -Default $true) -and + -not [bool](Get-PropertyValue -Item $guardrails -Name "allows_payload_traffic" -Default $true) +) + +$checks = [ordered]@{ + source_smoke_passed = ([bool]$sourceResult.passed) + source_schema_expected = ([string]$sourceResult.schema_version -eq "c19z125.remote_workspace_real_adapter_not_approved_outcome_closeout_summary_smoke.v1") + closeout_summary_present = ($null -ne $closeout) + closeout_fields_compatible = $closeoutFieldsCompatible + closeout_values_compatible = $closeoutValuesCompatible + guardrails_compatible = $guardrailsCompatible +} +$failed = @($checks.GetEnumerator() | Where-Object { -not $_.Value } | ForEach-Object { $_.Key }) + +$result = [ordered]@{ + schema_version = "c19z126.remote_workspace_real_adapter_not_approved_outcome_closeout_summary_compatibility_smoke.v1" + source_result_path = $sourceFile + cluster_id = $ClusterID + required_closeout_fields = $requiredCloseoutFields + required_guardrail_fields = $requiredGuardrailFields + not_approved_outcome_closeout_summary = $closeout + checks = $checks + failed_checks = $failed + passed = ($failed.Count -eq 0) +} + +$fullResultPath = Join-Path $repoRoot $ResultPath +$resultDir = Split-Path -Parent $fullResultPath +if ($resultDir) { New-Item -ItemType Directory -Force -Path $resultDir | Out-Null } +$result | ConvertTo-Json -Depth 100 | Set-Content -Encoding UTF8 -Path $fullResultPath + +if (-not $result.passed) { + throw "C19Z126 remote workspace real-adapter not-approved outcome closeout summary compatibility smoke failed. Result: $fullResultPath Failed: $($failed -join ', ')" +} + +Write-Host "C19Z126 remote workspace real-adapter not-approved outcome closeout summary compatibility smoke passed. Result: $fullResultPath" +$result diff --git a/scripts/fabric/c19z127-remote-workspace-real-adapter-not-approved-outcome-final-release-marker-smoke.ps1 b/scripts/fabric/c19z127-remote-workspace-real-adapter-not-approved-outcome-final-release-marker-smoke.ps1 new file mode 100644 index 0000000..513f647 --- /dev/null +++ b/scripts/fabric/c19z127-remote-workspace-real-adapter-not-approved-outcome-final-release-marker-smoke.ps1 @@ -0,0 +1,190 @@ +param( + [string]$ApiBaseUrl = "http://192.168.200.61:18121/api/v1", + [string]$ClusterID = "cfc0743d-d960-49fb-9de8-96e063d5e4aa", + [string]$ActorUserID = "f67d943f-5397-4b3a-a229-695fe67ad700", + [string]$RequestedNodeName = "test-1", + [string]$DefaultNodeName = "test-2", + [string]$MatrixNodeName = "test-1", + [string]$ResultPath = "artifacts\c19z127-remote-workspace-real-adapter-not-approved-outcome-final-release-marker-smoke-result.json" +) + +Set-StrictMode -Version Latest +$ErrorActionPreference = "Stop" + +$repoRoot = (Resolve-Path (Join-Path $PSScriptRoot "..\..")).ProviderPath +$sourceResultPath = "artifacts\c19z127-remote-workspace-real-adapter-not-approved-outcome-final-release-marker-source-result.json" +$requiredFinalReleaseFields = @( + "schema_version", + "source_closeout_schema", + "final_release_status", + "final_release_marker", + "covered_stage_range", + "covered_stage_count", + "latest_compatibility_stage", + "release_status", + "closeout_status", + "boundary_status", + "reopen_policy", + "enablement_decision", + "operator_review_status", + "enablement_status", + "runtime_gate_state", + "runtime_effect", + "operator_default_action", + "next_required_phase", + "allows_process_start", + "allows_payload_traffic", + "guardrail_summary", + "final_notes" +) +$requiredGuardrailFields = @("activation_blocked", "process_start_allowed", "health_probe_enabled", "payload_traffic", "allows_process_start", "allows_payload_traffic") +$requiredFinalNotes = @( + "not_approved_outcome_final_release_marked", + "operator_outcome_closed_without_approval", + "new_explicit_enablement_request_required", + "real_runtime_gate_not_enabled", + "process_start_disabled", + "payload_forwarding_disabled" +) + +function Get-PropertyValue { + param([object]$Item, [string]$Name, [object]$Default = $null) + if ($null -eq $Item) { return $Default } + if ($Item -is [System.Collections.IDictionary]) { + if ($Item.Contains($Name)) { return $Item[$Name] } + return $Default + } + $property = $Item.PSObject.Properties[$Name] + if ($null -eq $property) { return $Default } + return $property.Value +} + +function Test-ObjectHasFields { + param([object]$Item, [string[]]$Fields) + if ($null -eq $Item) { return $false } + foreach ($field in $Fields) { + if ($Item -is [System.Collections.IDictionary]) { + if (-not $Item.Contains($field)) { return $false } + continue + } + if ($null -eq $Item.PSObject.Properties[$field]) { return $false } + } + return $true +} + +function Test-ArrayContainsAll { + param([object[]]$Actual, [string[]]$Expected) + foreach ($item in $Expected) { + if ($Actual -notcontains $item) { return $false } + } + return $true +} + +& powershell -ExecutionPolicy Bypass -File (Join-Path $PSScriptRoot "c19z126-remote-workspace-real-adapter-not-approved-outcome-closeout-summary-compatibility-smoke.ps1") ` + -ApiBaseUrl $ApiBaseUrl ` + -ClusterID $ClusterID ` + -ActorUserID $ActorUserID ` + -RequestedNodeName $RequestedNodeName ` + -DefaultNodeName $DefaultNodeName ` + -MatrixNodeName $MatrixNodeName ` + -ResultPath $sourceResultPath | Out-Null + +$sourceFile = Join-Path $repoRoot $sourceResultPath +$sourceResult = Get-Content -Raw -Path $sourceFile | ConvertFrom-Json +$closeout = Get-PropertyValue -Item $sourceResult -Name "not_approved_outcome_closeout_summary" -Default $null +$guardrails = Get-PropertyValue -Item $closeout -Name "guardrail_summary" -Default $null + +$marker = [ordered]@{ + schema_version = "rap.remote_workspace_real_adapter_not_approved_outcome_final_release_marker.v1" + source_closeout_schema = Get-PropertyValue -Item $closeout -Name "schema_version" -Default $null + final_release_status = "closed_not_approved_final_contract_only" + final_release_marker = "c19z127_real_adapter_not_approved_outcome_final_release_marker" + covered_stage_range = Get-PropertyValue -Item $closeout -Name "covered_stage_range" -Default $null + covered_stage_count = Get-PropertyValue -Item $closeout -Name "covered_stage_count" -Default $null + latest_compatibility_stage = Get-PropertyValue -Item $closeout -Name "latest_compatibility_stage" -Default $null + release_status = Get-PropertyValue -Item $closeout -Name "release_status" -Default $null + closeout_status = Get-PropertyValue -Item $closeout -Name "closeout_status" -Default $null + boundary_status = Get-PropertyValue -Item $closeout -Name "boundary_status" -Default $null + reopen_policy = Get-PropertyValue -Item $closeout -Name "reopen_policy" -Default $null + enablement_decision = Get-PropertyValue -Item $closeout -Name "enablement_decision" -Default $null + operator_review_status = Get-PropertyValue -Item $closeout -Name "operator_review_status" -Default $null + enablement_status = Get-PropertyValue -Item $closeout -Name "enablement_status" -Default $null + runtime_gate_state = Get-PropertyValue -Item $closeout -Name "runtime_gate_state" -Default $null + runtime_effect = Get-PropertyValue -Item $closeout -Name "runtime_effect" -Default $null + operator_default_action = Get-PropertyValue -Item $closeout -Name "operator_default_action" -Default $null + next_required_phase = Get-PropertyValue -Item $closeout -Name "next_required_phase" -Default $null + allows_process_start = $false + allows_payload_traffic = $false + guardrail_summary = $guardrails + final_notes = $requiredFinalNotes +} + +$finalReleaseFieldsCompatible = Test-ObjectHasFields -Item $marker -Fields $requiredFinalReleaseFields +$finalReleaseValuesCompatible = ( + [string](Get-PropertyValue -Item $marker -Name "schema_version" -Default "") -eq "rap.remote_workspace_real_adapter_not_approved_outcome_final_release_marker.v1" -and + [string](Get-PropertyValue -Item $marker -Name "source_closeout_schema" -Default "") -eq "rap.remote_workspace_real_adapter_not_approved_outcome_closeout_summary.v1" -and + [string](Get-PropertyValue -Item $marker -Name "final_release_status" -Default "") -eq "closed_not_approved_final_contract_only" -and + [string](Get-PropertyValue -Item $marker -Name "final_release_marker" -Default "") -eq "c19z127_real_adapter_not_approved_outcome_final_release_marker" -and + [string](Get-PropertyValue -Item $marker -Name "covered_stage_range" -Default "") -eq "C19Z117-C19Z122" -and + [int](Get-PropertyValue -Item $marker -Name "covered_stage_count" -Default -1) -eq 6 -and + [string](Get-PropertyValue -Item $marker -Name "latest_compatibility_stage" -Default "") -eq "C19Z122" -and + [string](Get-PropertyValue -Item $marker -Name "release_status" -Default "") -eq "not_approved_outcome_closed_contract_only" -and + [string](Get-PropertyValue -Item $marker -Name "closeout_status" -Default "") -eq "closed_not_approved_package_complete" -and + [string](Get-PropertyValue -Item $marker -Name "boundary_status" -Default "") -eq "closed_not_approved_reopen_required" -and + [string](Get-PropertyValue -Item $marker -Name "reopen_policy" -Default "") -eq "new_explicit_enablement_request_required" -and + [string](Get-PropertyValue -Item $marker -Name "enablement_decision" -Default "") -eq "not_approved" -and + [string](Get-PropertyValue -Item $marker -Name "operator_review_status" -Default "") -eq "closed_without_approval" -and + [string](Get-PropertyValue -Item $marker -Name "enablement_status" -Default "") -eq "not_enabled" -and + [string](Get-PropertyValue -Item $marker -Name "runtime_gate_state" -Default "") -eq "validated_contract_only_not_enabled" -and + [string](Get-PropertyValue -Item $marker -Name "runtime_effect" -Default "") -eq "contract_only_no_runtime_enablement" -and + [string](Get-PropertyValue -Item $marker -Name "operator_default_action" -Default "") -eq "keep_real_adapter_disabled" -and + [string](Get-PropertyValue -Item $marker -Name "next_required_phase" -Default "") -eq "explicit_new_enablement_request_only" -and + -not [bool](Get-PropertyValue -Item $marker -Name "allows_process_start" -Default $true) -and + -not [bool](Get-PropertyValue -Item $marker -Name "allows_payload_traffic" -Default $true) +) +$finalNotesCompatible = Test-ArrayContainsAll -Actual @($marker.final_notes) -Expected $requiredFinalNotes +$guardrailsCompatible = ( + (Test-ObjectHasFields -Item $guardrails -Fields $requiredGuardrailFields) -and + [bool](Get-PropertyValue -Item $guardrails -Name "activation_blocked" -Default $false) -and + -not [bool](Get-PropertyValue -Item $guardrails -Name "process_start_allowed" -Default $true) -and + -not [bool](Get-PropertyValue -Item $guardrails -Name "health_probe_enabled" -Default $true) -and + [string](Get-PropertyValue -Item $guardrails -Name "payload_traffic" -Default "") -eq "none" -and + -not [bool](Get-PropertyValue -Item $guardrails -Name "allows_process_start" -Default $true) -and + -not [bool](Get-PropertyValue -Item $guardrails -Name "allows_payload_traffic" -Default $true) +) + +$checks = [ordered]@{ + source_smoke_passed = ([bool]$sourceResult.passed) + source_schema_expected = ([string]$sourceResult.schema_version -eq "c19z126.remote_workspace_real_adapter_not_approved_outcome_closeout_summary_compatibility_smoke.v1") + closeout_summary_present = ($null -ne $closeout) + final_release_fields_compatible = $finalReleaseFieldsCompatible + final_release_values_compatible = $finalReleaseValuesCompatible + final_notes_compatible = $finalNotesCompatible + guardrails_compatible = $guardrailsCompatible +} +$failed = @($checks.GetEnumerator() | Where-Object { -not $_.Value } | ForEach-Object { $_.Key }) + +$result = [ordered]@{ + schema_version = "c19z127.remote_workspace_real_adapter_not_approved_outcome_final_release_marker_smoke.v1" + source_result_path = $sourceFile + cluster_id = $ClusterID + required_final_release_fields = $requiredFinalReleaseFields + required_guardrail_fields = $requiredGuardrailFields + required_final_notes = $requiredFinalNotes + not_approved_outcome_final_release_marker = $marker + checks = $checks + failed_checks = $failed + passed = ($failed.Count -eq 0) +} + +$fullResultPath = Join-Path $repoRoot $ResultPath +$resultDir = Split-Path -Parent $fullResultPath +if ($resultDir) { New-Item -ItemType Directory -Force -Path $resultDir | Out-Null } +$result | ConvertTo-Json -Depth 100 | Set-Content -Encoding UTF8 -Path $fullResultPath + +if (-not $result.passed) { + throw "C19Z127 remote workspace real-adapter not-approved outcome final release marker smoke failed. Result: $fullResultPath Failed: $($failed -join ', ')" +} + +Write-Host "C19Z127 remote workspace real-adapter not-approved outcome final release marker smoke passed. Result: $fullResultPath" +$result diff --git a/scripts/fabric/c19z128-remote-workspace-real-adapter-not-approved-outcome-final-release-marker-compatibility-smoke.ps1 b/scripts/fabric/c19z128-remote-workspace-real-adapter-not-approved-outcome-final-release-marker-compatibility-smoke.ps1 new file mode 100644 index 0000000..c7f5cfe --- /dev/null +++ b/scripts/fabric/c19z128-remote-workspace-real-adapter-not-approved-outcome-final-release-marker-compatibility-smoke.ps1 @@ -0,0 +1,166 @@ +param( + [string]$ApiBaseUrl = "http://192.168.200.61:18121/api/v1", + [string]$ClusterID = "cfc0743d-d960-49fb-9de8-96e063d5e4aa", + [string]$ActorUserID = "f67d943f-5397-4b3a-a229-695fe67ad700", + [string]$RequestedNodeName = "test-1", + [string]$DefaultNodeName = "test-2", + [string]$MatrixNodeName = "test-1", + [string]$ResultPath = "artifacts\c19z128-remote-workspace-real-adapter-not-approved-outcome-final-release-marker-compatibility-smoke-result.json" +) + +Set-StrictMode -Version Latest +$ErrorActionPreference = "Stop" + +$repoRoot = (Resolve-Path (Join-Path $PSScriptRoot "..\..")).ProviderPath +$sourceResultPath = "artifacts\c19z128-remote-workspace-real-adapter-not-approved-outcome-final-release-marker-source-result.json" +$requiredFinalReleaseFields = @( + "schema_version", + "source_closeout_schema", + "final_release_status", + "final_release_marker", + "covered_stage_range", + "covered_stage_count", + "latest_compatibility_stage", + "release_status", + "closeout_status", + "boundary_status", + "reopen_policy", + "enablement_decision", + "operator_review_status", + "enablement_status", + "runtime_gate_state", + "runtime_effect", + "operator_default_action", + "next_required_phase", + "allows_process_start", + "allows_payload_traffic", + "guardrail_summary", + "final_notes" +) +$requiredGuardrailFields = @("activation_blocked", "process_start_allowed", "health_probe_enabled", "payload_traffic", "allows_process_start", "allows_payload_traffic") +$requiredFinalNotes = @( + "not_approved_outcome_final_release_marked", + "operator_outcome_closed_without_approval", + "new_explicit_enablement_request_required", + "real_runtime_gate_not_enabled", + "process_start_disabled", + "payload_forwarding_disabled" +) + +function Get-PropertyValue { + param([object]$Item, [string]$Name, [object]$Default = $null) + if ($null -eq $Item) { return $Default } + if ($Item -is [System.Collections.IDictionary]) { + if ($Item.Contains($Name)) { return $Item[$Name] } + return $Default + } + $property = $Item.PSObject.Properties[$Name] + if ($null -eq $property) { return $Default } + return $property.Value +} + +function Test-ObjectHasFields { + param([object]$Item, [string[]]$Fields) + if ($null -eq $Item) { return $false } + foreach ($field in $Fields) { + if ($Item -is [System.Collections.IDictionary]) { + if (-not $Item.Contains($field)) { return $false } + continue + } + if ($null -eq $Item.PSObject.Properties[$field]) { return $false } + } + return $true +} + +function Test-ArrayContainsAll { + param([object[]]$Actual, [string[]]$Expected) + foreach ($item in $Expected) { + if ($Actual -notcontains $item) { return $false } + } + return $true +} + +& powershell -ExecutionPolicy Bypass -File (Join-Path $PSScriptRoot "c19z127-remote-workspace-real-adapter-not-approved-outcome-final-release-marker-smoke.ps1") ` + -ApiBaseUrl $ApiBaseUrl ` + -ClusterID $ClusterID ` + -ActorUserID $ActorUserID ` + -RequestedNodeName $RequestedNodeName ` + -DefaultNodeName $DefaultNodeName ` + -MatrixNodeName $MatrixNodeName ` + -ResultPath $sourceResultPath | Out-Null + +$sourceFile = Join-Path $repoRoot $sourceResultPath +$sourceResult = Get-Content -Raw -Path $sourceFile | ConvertFrom-Json +$marker = Get-PropertyValue -Item $sourceResult -Name "not_approved_outcome_final_release_marker" -Default $null +$guardrails = Get-PropertyValue -Item $marker -Name "guardrail_summary" -Default $null +$finalNotes = @(Get-PropertyValue -Item $marker -Name "final_notes" -Default @()) + +$finalReleaseFieldsCompatible = Test-ObjectHasFields -Item $marker -Fields $requiredFinalReleaseFields +$finalReleaseValuesCompatible = ( + [string](Get-PropertyValue -Item $marker -Name "schema_version" -Default "") -eq "rap.remote_workspace_real_adapter_not_approved_outcome_final_release_marker.v1" -and + [string](Get-PropertyValue -Item $marker -Name "source_closeout_schema" -Default "") -eq "rap.remote_workspace_real_adapter_not_approved_outcome_closeout_summary.v1" -and + [string](Get-PropertyValue -Item $marker -Name "final_release_status" -Default "") -eq "closed_not_approved_final_contract_only" -and + [string](Get-PropertyValue -Item $marker -Name "final_release_marker" -Default "") -eq "c19z127_real_adapter_not_approved_outcome_final_release_marker" -and + [string](Get-PropertyValue -Item $marker -Name "covered_stage_range" -Default "") -eq "C19Z117-C19Z122" -and + [int](Get-PropertyValue -Item $marker -Name "covered_stage_count" -Default -1) -eq 6 -and + [string](Get-PropertyValue -Item $marker -Name "latest_compatibility_stage" -Default "") -eq "C19Z122" -and + [string](Get-PropertyValue -Item $marker -Name "release_status" -Default "") -eq "not_approved_outcome_closed_contract_only" -and + [string](Get-PropertyValue -Item $marker -Name "closeout_status" -Default "") -eq "closed_not_approved_package_complete" -and + [string](Get-PropertyValue -Item $marker -Name "boundary_status" -Default "") -eq "closed_not_approved_reopen_required" -and + [string](Get-PropertyValue -Item $marker -Name "reopen_policy" -Default "") -eq "new_explicit_enablement_request_required" -and + [string](Get-PropertyValue -Item $marker -Name "enablement_decision" -Default "") -eq "not_approved" -and + [string](Get-PropertyValue -Item $marker -Name "operator_review_status" -Default "") -eq "closed_without_approval" -and + [string](Get-PropertyValue -Item $marker -Name "enablement_status" -Default "") -eq "not_enabled" -and + [string](Get-PropertyValue -Item $marker -Name "runtime_gate_state" -Default "") -eq "validated_contract_only_not_enabled" -and + [string](Get-PropertyValue -Item $marker -Name "runtime_effect" -Default "") -eq "contract_only_no_runtime_enablement" -and + [string](Get-PropertyValue -Item $marker -Name "operator_default_action" -Default "") -eq "keep_real_adapter_disabled" -and + [string](Get-PropertyValue -Item $marker -Name "next_required_phase" -Default "") -eq "explicit_new_enablement_request_only" -and + -not [bool](Get-PropertyValue -Item $marker -Name "allows_process_start" -Default $true) -and + -not [bool](Get-PropertyValue -Item $marker -Name "allows_payload_traffic" -Default $true) +) +$finalNotesCompatible = Test-ArrayContainsAll -Actual $finalNotes -Expected $requiredFinalNotes +$guardrailsCompatible = ( + (Test-ObjectHasFields -Item $guardrails -Fields $requiredGuardrailFields) -and + [bool](Get-PropertyValue -Item $guardrails -Name "activation_blocked" -Default $false) -and + -not [bool](Get-PropertyValue -Item $guardrails -Name "process_start_allowed" -Default $true) -and + -not [bool](Get-PropertyValue -Item $guardrails -Name "health_probe_enabled" -Default $true) -and + [string](Get-PropertyValue -Item $guardrails -Name "payload_traffic" -Default "") -eq "none" -and + -not [bool](Get-PropertyValue -Item $guardrails -Name "allows_process_start" -Default $true) -and + -not [bool](Get-PropertyValue -Item $guardrails -Name "allows_payload_traffic" -Default $true) +) + +$checks = [ordered]@{ + source_smoke_passed = ([bool]$sourceResult.passed) + source_schema_expected = ([string]$sourceResult.schema_version -eq "c19z127.remote_workspace_real_adapter_not_approved_outcome_final_release_marker_smoke.v1") + final_release_marker_present = ($null -ne $marker) + final_release_fields_compatible = $finalReleaseFieldsCompatible + final_release_values_compatible = $finalReleaseValuesCompatible + final_notes_compatible = $finalNotesCompatible + guardrails_compatible = $guardrailsCompatible +} +$failed = @($checks.GetEnumerator() | Where-Object { -not $_.Value } | ForEach-Object { $_.Key }) + +$result = [ordered]@{ + schema_version = "c19z128.remote_workspace_real_adapter_not_approved_outcome_final_release_marker_compatibility_smoke.v1" + source_result_path = $sourceFile + cluster_id = $ClusterID + required_final_release_fields = $requiredFinalReleaseFields + required_guardrail_fields = $requiredGuardrailFields + required_final_notes = $requiredFinalNotes + not_approved_outcome_final_release_marker = $marker + checks = $checks + failed_checks = $failed + passed = ($failed.Count -eq 0) +} + +$fullResultPath = Join-Path $repoRoot $ResultPath +$resultDir = Split-Path -Parent $fullResultPath +if ($resultDir) { New-Item -ItemType Directory -Force -Path $resultDir | Out-Null } +$result | ConvertTo-Json -Depth 100 | Set-Content -Encoding UTF8 -Path $fullResultPath + +if (-not $result.passed) { + throw "C19Z128 remote workspace real-adapter not-approved outcome final release marker compatibility smoke failed. Result: $fullResultPath Failed: $($failed -join ', ')" +} + +Write-Host "C19Z128 remote workspace real-adapter not-approved outcome final release marker compatibility smoke passed. Result: $fullResultPath" +$result diff --git a/scripts/fabric/c19z129-remote-workspace-real-adapter-not-approved-outcome-final-package-index-archive-marker-smoke.ps1 b/scripts/fabric/c19z129-remote-workspace-real-adapter-not-approved-outcome-final-package-index-archive-marker-smoke.ps1 new file mode 100644 index 0000000..6130d5c --- /dev/null +++ b/scripts/fabric/c19z129-remote-workspace-real-adapter-not-approved-outcome-final-package-index-archive-marker-smoke.ps1 @@ -0,0 +1,197 @@ +param( + [string]$ApiBaseUrl = "http://192.168.200.61:18121/api/v1", + [string]$ClusterID = "cfc0743d-d960-49fb-9de8-96e063d5e4aa", + [string]$ActorUserID = "f67d943f-5397-4b3a-a229-695fe67ad700", + [string]$RequestedNodeName = "test-1", + [string]$DefaultNodeName = "test-2", + [string]$MatrixNodeName = "test-1", + [string]$ResultPath = "artifacts\c19z129-remote-workspace-real-adapter-not-approved-outcome-final-package-index-archive-marker-smoke-result.json" +) + +Set-StrictMode -Version Latest +$ErrorActionPreference = "Stop" + +$repoRoot = (Resolve-Path (Join-Path $PSScriptRoot "..\..")).ProviderPath +$sourceResultPath = "artifacts\c19z129-remote-workspace-real-adapter-not-approved-outcome-final-package-index-archive-marker-source-result.json" +$requiredArchiveFields = @( + "schema_version", + "source_final_release_schema", + "archive_status", + "archive_marker", + "package_status", + "final_release_status", + "covered_stage_range", + "covered_stage_count", + "latest_compatibility_stage", + "release_status", + "closeout_status", + "boundary_status", + "reopen_policy", + "enablement_decision", + "operator_review_status", + "enablement_status", + "runtime_gate_state", + "runtime_effect", + "operator_default_action", + "next_required_phase", + "allows_process_start", + "allows_payload_traffic", + "guardrail_summary", + "archive_notes" +) +$requiredGuardrailFields = @("activation_blocked", "process_start_allowed", "health_probe_enabled", "payload_traffic", "allows_process_start", "allows_payload_traffic") +$requiredArchiveNotes = @( + "not_approved_outcome_final_package_indexed", + "not_approved_outcome_archived_contract_only", + "operator_outcome_closed_without_approval", + "new_explicit_enablement_request_required", + "real_runtime_gate_not_enabled", + "process_start_disabled", + "payload_forwarding_disabled" +) + +function Get-PropertyValue { + param([object]$Item, [string]$Name, [object]$Default = $null) + if ($null -eq $Item) { return $Default } + if ($Item -is [System.Collections.IDictionary]) { + if ($Item.Contains($Name)) { return $Item[$Name] } + return $Default + } + $property = $Item.PSObject.Properties[$Name] + if ($null -eq $property) { return $Default } + return $property.Value +} + +function Test-ObjectHasFields { + param([object]$Item, [string[]]$Fields) + if ($null -eq $Item) { return $false } + foreach ($field in $Fields) { + if ($Item -is [System.Collections.IDictionary]) { + if (-not $Item.Contains($field)) { return $false } + continue + } + if ($null -eq $Item.PSObject.Properties[$field]) { return $false } + } + return $true +} + +function Test-ArrayContainsAll { + param([object[]]$Actual, [string[]]$Expected) + foreach ($item in $Expected) { + if ($Actual -notcontains $item) { return $false } + } + return $true +} + +& powershell -ExecutionPolicy Bypass -File (Join-Path $PSScriptRoot "c19z128-remote-workspace-real-adapter-not-approved-outcome-final-release-marker-compatibility-smoke.ps1") ` + -ApiBaseUrl $ApiBaseUrl ` + -ClusterID $ClusterID ` + -ActorUserID $ActorUserID ` + -RequestedNodeName $RequestedNodeName ` + -DefaultNodeName $DefaultNodeName ` + -MatrixNodeName $MatrixNodeName ` + -ResultPath $sourceResultPath | Out-Null + +$sourceFile = Join-Path $repoRoot $sourceResultPath +$sourceResult = Get-Content -Raw -Path $sourceFile | ConvertFrom-Json +$finalRelease = Get-PropertyValue -Item $sourceResult -Name "not_approved_outcome_final_release_marker" -Default $null +$guardrails = Get-PropertyValue -Item $finalRelease -Name "guardrail_summary" -Default $null + +$archive = [ordered]@{ + schema_version = "rap.remote_workspace_real_adapter_not_approved_outcome_final_package_index_archive_marker.v1" + source_final_release_schema = Get-PropertyValue -Item $finalRelease -Name "schema_version" -Default $null + archive_status = "closed_not_approved_archived_contract_only" + archive_marker = "c19z129_real_adapter_not_approved_outcome_final_package_index_archive_marker" + package_status = "final_package_indexed_and_archived_contract_only" + final_release_status = Get-PropertyValue -Item $finalRelease -Name "final_release_status" -Default $null + covered_stage_range = Get-PropertyValue -Item $finalRelease -Name "covered_stage_range" -Default $null + covered_stage_count = Get-PropertyValue -Item $finalRelease -Name "covered_stage_count" -Default $null + latest_compatibility_stage = Get-PropertyValue -Item $finalRelease -Name "latest_compatibility_stage" -Default $null + release_status = Get-PropertyValue -Item $finalRelease -Name "release_status" -Default $null + closeout_status = Get-PropertyValue -Item $finalRelease -Name "closeout_status" -Default $null + boundary_status = Get-PropertyValue -Item $finalRelease -Name "boundary_status" -Default $null + reopen_policy = Get-PropertyValue -Item $finalRelease -Name "reopen_policy" -Default $null + enablement_decision = Get-PropertyValue -Item $finalRelease -Name "enablement_decision" -Default $null + operator_review_status = Get-PropertyValue -Item $finalRelease -Name "operator_review_status" -Default $null + enablement_status = Get-PropertyValue -Item $finalRelease -Name "enablement_status" -Default $null + runtime_gate_state = Get-PropertyValue -Item $finalRelease -Name "runtime_gate_state" -Default $null + runtime_effect = Get-PropertyValue -Item $finalRelease -Name "runtime_effect" -Default $null + operator_default_action = Get-PropertyValue -Item $finalRelease -Name "operator_default_action" -Default $null + next_required_phase = Get-PropertyValue -Item $finalRelease -Name "next_required_phase" -Default $null + allows_process_start = $false + allows_payload_traffic = $false + guardrail_summary = $guardrails + archive_notes = $requiredArchiveNotes +} + +$archiveFieldsCompatible = Test-ObjectHasFields -Item $archive -Fields $requiredArchiveFields +$archiveValuesCompatible = ( + [string](Get-PropertyValue -Item $archive -Name "schema_version" -Default "") -eq "rap.remote_workspace_real_adapter_not_approved_outcome_final_package_index_archive_marker.v1" -and + [string](Get-PropertyValue -Item $archive -Name "source_final_release_schema" -Default "") -eq "rap.remote_workspace_real_adapter_not_approved_outcome_final_release_marker.v1" -and + [string](Get-PropertyValue -Item $archive -Name "archive_status" -Default "") -eq "closed_not_approved_archived_contract_only" -and + [string](Get-PropertyValue -Item $archive -Name "archive_marker" -Default "") -eq "c19z129_real_adapter_not_approved_outcome_final_package_index_archive_marker" -and + [string](Get-PropertyValue -Item $archive -Name "package_status" -Default "") -eq "final_package_indexed_and_archived_contract_only" -and + [string](Get-PropertyValue -Item $archive -Name "final_release_status" -Default "") -eq "closed_not_approved_final_contract_only" -and + [string](Get-PropertyValue -Item $archive -Name "covered_stage_range" -Default "") -eq "C19Z117-C19Z122" -and + [int](Get-PropertyValue -Item $archive -Name "covered_stage_count" -Default -1) -eq 6 -and + [string](Get-PropertyValue -Item $archive -Name "latest_compatibility_stage" -Default "") -eq "C19Z122" -and + [string](Get-PropertyValue -Item $archive -Name "release_status" -Default "") -eq "not_approved_outcome_closed_contract_only" -and + [string](Get-PropertyValue -Item $archive -Name "closeout_status" -Default "") -eq "closed_not_approved_package_complete" -and + [string](Get-PropertyValue -Item $archive -Name "boundary_status" -Default "") -eq "closed_not_approved_reopen_required" -and + [string](Get-PropertyValue -Item $archive -Name "reopen_policy" -Default "") -eq "new_explicit_enablement_request_required" -and + [string](Get-PropertyValue -Item $archive -Name "enablement_decision" -Default "") -eq "not_approved" -and + [string](Get-PropertyValue -Item $archive -Name "operator_review_status" -Default "") -eq "closed_without_approval" -and + [string](Get-PropertyValue -Item $archive -Name "enablement_status" -Default "") -eq "not_enabled" -and + [string](Get-PropertyValue -Item $archive -Name "runtime_gate_state" -Default "") -eq "validated_contract_only_not_enabled" -and + [string](Get-PropertyValue -Item $archive -Name "runtime_effect" -Default "") -eq "contract_only_no_runtime_enablement" -and + [string](Get-PropertyValue -Item $archive -Name "operator_default_action" -Default "") -eq "keep_real_adapter_disabled" -and + [string](Get-PropertyValue -Item $archive -Name "next_required_phase" -Default "") -eq "explicit_new_enablement_request_only" -and + -not [bool](Get-PropertyValue -Item $archive -Name "allows_process_start" -Default $true) -and + -not [bool](Get-PropertyValue -Item $archive -Name "allows_payload_traffic" -Default $true) +) +$archiveNotesCompatible = Test-ArrayContainsAll -Actual @($archive.archive_notes) -Expected $requiredArchiveNotes +$guardrailsCompatible = ( + (Test-ObjectHasFields -Item $guardrails -Fields $requiredGuardrailFields) -and + [bool](Get-PropertyValue -Item $guardrails -Name "activation_blocked" -Default $false) -and + -not [bool](Get-PropertyValue -Item $guardrails -Name "process_start_allowed" -Default $true) -and + -not [bool](Get-PropertyValue -Item $guardrails -Name "health_probe_enabled" -Default $true) -and + [string](Get-PropertyValue -Item $guardrails -Name "payload_traffic" -Default "") -eq "none" -and + -not [bool](Get-PropertyValue -Item $guardrails -Name "allows_process_start" -Default $true) -and + -not [bool](Get-PropertyValue -Item $guardrails -Name "allows_payload_traffic" -Default $true) +) + +$checks = [ordered]@{ + source_smoke_passed = ([bool]$sourceResult.passed) + source_schema_expected = ([string]$sourceResult.schema_version -eq "c19z128.remote_workspace_real_adapter_not_approved_outcome_final_release_marker_compatibility_smoke.v1") + final_release_marker_present = ($null -ne $finalRelease) + archive_fields_compatible = $archiveFieldsCompatible + archive_values_compatible = $archiveValuesCompatible + archive_notes_compatible = $archiveNotesCompatible + guardrails_compatible = $guardrailsCompatible +} +$failed = @($checks.GetEnumerator() | Where-Object { -not $_.Value } | ForEach-Object { $_.Key }) + +$result = [ordered]@{ + schema_version = "c19z129.remote_workspace_real_adapter_not_approved_outcome_final_package_index_archive_marker_smoke.v1" + source_result_path = $sourceFile + cluster_id = $ClusterID + required_archive_fields = $requiredArchiveFields + required_guardrail_fields = $requiredGuardrailFields + required_archive_notes = $requiredArchiveNotes + not_approved_outcome_final_package_index_archive_marker = $archive + checks = $checks + failed_checks = $failed + passed = ($failed.Count -eq 0) +} + +$fullResultPath = Join-Path $repoRoot $ResultPath +$resultDir = Split-Path -Parent $fullResultPath +if ($resultDir) { New-Item -ItemType Directory -Force -Path $resultDir | Out-Null } +$result | ConvertTo-Json -Depth 100 | Set-Content -Encoding UTF8 -Path $fullResultPath + +if (-not $result.passed) { + throw "C19Z129 remote workspace real-adapter not-approved outcome final package index archive marker smoke failed. Result: $fullResultPath Failed: $($failed -join ', ')" +} + +Write-Host "C19Z129 remote workspace real-adapter not-approved outcome final package index archive marker smoke passed. Result: $fullResultPath" +$result diff --git a/scripts/fabric/c19z13-remote-workspace-mailbox-preflight-attention-smoke.ps1 b/scripts/fabric/c19z13-remote-workspace-mailbox-preflight-attention-smoke.ps1 new file mode 100644 index 0000000..d5d4aec --- /dev/null +++ b/scripts/fabric/c19z13-remote-workspace-mailbox-preflight-attention-smoke.ps1 @@ -0,0 +1,83 @@ +param( + [string]$ApiBaseUrl = "http://192.168.200.61:18121/api/v1", + [string]$ClusterID = "cfc0743d-d960-49fb-9de8-96e063d5e4aa", + [string]$ActorUserID = "f67d943f-5397-4b3a-a229-695fe67ad700", + [string]$EntryNodeName = "test-1", + [string]$ExitNodeName = "test-2", + [string]$EntryBaseUrl = "http://192.168.200.61:19131", + [string]$ResultPath = "artifacts\c19z13-remote-workspace-mailbox-preflight-attention-smoke-result.json" +) + +Set-StrictMode -Version Latest +$ErrorActionPreference = "Stop" + +$repoRoot = (Resolve-Path (Join-Path $PSScriptRoot "..\..")).ProviderPath +$sourceResultPath = "artifacts\c19z13-remote-workspace-mailbox-preflight-attention-source-result.json" + +function Get-PropertyValue { + param([object]$Item, [string]$Name, [object]$Default = $null) + if ($null -eq $Item) { return $Default } + $property = $Item.PSObject.Properties[$Name] + if ($null -eq $property) { return $Default } + return $property.Value +} + +function Test-AttentionStatus { + param([object]$Sink, [string]$AdapterSessionID) + if ($null -eq $Sink) { return $false } + $readiness = Get-PropertyValue -Item $Sink -Name "adapter_runtime_readiness" -Default $null + $rollup = Get-PropertyValue -Item $readiness -Name "last_preflight" -Default $null + if ($null -eq $rollup) { return $false } + return ( + [string](Get-PropertyValue -Item $readiness -Name "adapter_session_id" -Default "") -eq $AdapterSessionID -and + [string](Get-PropertyValue -Item $readiness -Name "preflight_attention_status" -Default "") -eq "needs_attention" -and + [string](Get-PropertyValue -Item $rollup -Name "preflight_attention_status" -Default "") -eq "needs_attention" -and + [string](Get-PropertyValue -Item $rollup -Name "operator_status" -Default "") -eq "resync_required" -and + [string](Get-PropertyValue -Item $rollup -Name "operator_severity" -Default "") -eq "warn" + ) +} + +$source = & powershell -ExecutionPolicy Bypass -File (Join-Path $PSScriptRoot "c19z12-remote-workspace-mailbox-preflight-status-counts-smoke.ps1") ` + -ApiBaseUrl $ApiBaseUrl ` + -ClusterID $ClusterID ` + -ActorUserID $ActorUserID ` + -EntryNodeName $EntryNodeName ` + -ExitNodeName $ExitNodeName ` + -EntryBaseUrl $EntryBaseUrl ` + -ResultPath $sourceResultPath + +$sourceFile = Join-Path $repoRoot $sourceResultPath +$sourceResult = Get-Content -Raw -Path $sourceFile | ConvertFrom-Json +$adapterSessionID = [string](Get-PropertyValue -Item $sourceResult -Name "adapter_session_id" -Default "") +$observed = Get-PropertyValue -Item $sourceResult -Name "observed" -Default $null + +$checks = [ordered]@{ + source_smoke_passed = ([bool]$sourceResult.passed) + adapter_session_id_present = ($adapterSessionID -match "^rap-rw-adapter-session-[0-9a-f]{24}$") + workload_attention_visible = (Test-AttentionStatus -Sink $observed.workload_sink -AdapterSessionID $adapterSessionID) + telemetry_attention_visible = (Test-AttentionStatus -Sink $observed.telemetry_sink -AdapterSessionID $adapterSessionID) +} +$failed = @($checks.GetEnumerator() | Where-Object { -not $_.Value } | ForEach-Object { $_.Key }) + +$result = [ordered]@{ + schema_version = "c19z13.remote_workspace_mailbox_preflight_attention_smoke.v1" + source_result_path = $sourceFile + cluster_id = $ClusterID + adapter_session_id = $adapterSessionID + observed = $observed + checks = $checks + failed_checks = $failed + passed = ($failed.Count -eq 0) +} + +$fullResultPath = Join-Path $repoRoot $ResultPath +$resultDir = Split-Path -Parent $fullResultPath +if ($resultDir) { New-Item -ItemType Directory -Force -Path $resultDir | Out-Null } +$result | ConvertTo-Json -Depth 100 | Set-Content -Encoding UTF8 -Path $fullResultPath + +if (-not $result.passed) { + throw "C19Z13 remote workspace mailbox preflight attention smoke failed. Result: $fullResultPath Failed: $($failed -join ', ')" +} + +Write-Host "C19Z13 remote workspace mailbox preflight attention smoke passed. Result: $fullResultPath" +$result diff --git a/scripts/fabric/c19z130-remote-workspace-real-adapter-not-approved-outcome-final-package-index-archive-marker-compatibility-smoke.ps1 b/scripts/fabric/c19z130-remote-workspace-real-adapter-not-approved-outcome-final-package-index-archive-marker-compatibility-smoke.ps1 new file mode 100644 index 0000000..9176590 --- /dev/null +++ b/scripts/fabric/c19z130-remote-workspace-real-adapter-not-approved-outcome-final-package-index-archive-marker-compatibility-smoke.ps1 @@ -0,0 +1,171 @@ +param( + [string]$ApiBaseUrl = "http://192.168.200.61:18121/api/v1", + [string]$ClusterID = "cfc0743d-d960-49fb-9de8-96e063d5e4aa", + [string]$ActorUserID = "f67d943f-5397-4b3a-a229-695fe67ad700", + [string]$RequestedNodeName = "test-1", + [string]$DefaultNodeName = "test-2", + [string]$MatrixNodeName = "test-1", + [string]$ResultPath = "artifacts\c19z130-remote-workspace-real-adapter-not-approved-outcome-final-package-index-archive-marker-compatibility-smoke-result.json" +) + +Set-StrictMode -Version Latest +$ErrorActionPreference = "Stop" + +$repoRoot = (Resolve-Path (Join-Path $PSScriptRoot "..\..")).ProviderPath +$sourceResultPath = "artifacts\c19z130-remote-workspace-real-adapter-not-approved-outcome-final-package-index-archive-marker-source-result.json" +$requiredArchiveFields = @( + "schema_version", + "source_final_release_schema", + "archive_status", + "archive_marker", + "package_status", + "final_release_status", + "covered_stage_range", + "covered_stage_count", + "latest_compatibility_stage", + "release_status", + "closeout_status", + "boundary_status", + "reopen_policy", + "enablement_decision", + "operator_review_status", + "enablement_status", + "runtime_gate_state", + "runtime_effect", + "operator_default_action", + "next_required_phase", + "allows_process_start", + "allows_payload_traffic", + "guardrail_summary", + "archive_notes" +) +$requiredGuardrailFields = @("activation_blocked", "process_start_allowed", "health_probe_enabled", "payload_traffic", "allows_process_start", "allows_payload_traffic") +$requiredArchiveNotes = @( + "not_approved_outcome_final_package_indexed", + "not_approved_outcome_archived_contract_only", + "operator_outcome_closed_without_approval", + "new_explicit_enablement_request_required", + "real_runtime_gate_not_enabled", + "process_start_disabled", + "payload_forwarding_disabled" +) + +function Get-PropertyValue { + param([object]$Item, [string]$Name, [object]$Default = $null) + if ($null -eq $Item) { return $Default } + if ($Item -is [System.Collections.IDictionary]) { + if ($Item.Contains($Name)) { return $Item[$Name] } + return $Default + } + $property = $Item.PSObject.Properties[$Name] + if ($null -eq $property) { return $Default } + return $property.Value +} + +function Test-ObjectHasFields { + param([object]$Item, [string[]]$Fields) + if ($null -eq $Item) { return $false } + foreach ($field in $Fields) { + if ($Item -is [System.Collections.IDictionary]) { + if (-not $Item.Contains($field)) { return $false } + continue + } + if ($null -eq $Item.PSObject.Properties[$field]) { return $false } + } + return $true +} + +function Test-ArrayContainsAll { + param([object[]]$Actual, [string[]]$Expected) + foreach ($item in $Expected) { + if ($Actual -notcontains $item) { return $false } + } + return $true +} + +& powershell -ExecutionPolicy Bypass -File (Join-Path $PSScriptRoot "c19z129-remote-workspace-real-adapter-not-approved-outcome-final-package-index-archive-marker-smoke.ps1") ` + -ApiBaseUrl $ApiBaseUrl ` + -ClusterID $ClusterID ` + -ActorUserID $ActorUserID ` + -RequestedNodeName $RequestedNodeName ` + -DefaultNodeName $DefaultNodeName ` + -MatrixNodeName $MatrixNodeName ` + -ResultPath $sourceResultPath | Out-Null + +$sourceFile = Join-Path $repoRoot $sourceResultPath +$sourceResult = Get-Content -Raw -Path $sourceFile | ConvertFrom-Json +$archive = Get-PropertyValue -Item $sourceResult -Name "not_approved_outcome_final_package_index_archive_marker" -Default $null +$guardrails = Get-PropertyValue -Item $archive -Name "guardrail_summary" -Default $null +$archiveNotes = @(Get-PropertyValue -Item $archive -Name "archive_notes" -Default @()) + +$archiveFieldsCompatible = Test-ObjectHasFields -Item $archive -Fields $requiredArchiveFields +$archiveValuesCompatible = ( + [string](Get-PropertyValue -Item $archive -Name "schema_version" -Default "") -eq "rap.remote_workspace_real_adapter_not_approved_outcome_final_package_index_archive_marker.v1" -and + [string](Get-PropertyValue -Item $archive -Name "source_final_release_schema" -Default "") -eq "rap.remote_workspace_real_adapter_not_approved_outcome_final_release_marker.v1" -and + [string](Get-PropertyValue -Item $archive -Name "archive_status" -Default "") -eq "closed_not_approved_archived_contract_only" -and + [string](Get-PropertyValue -Item $archive -Name "archive_marker" -Default "") -eq "c19z129_real_adapter_not_approved_outcome_final_package_index_archive_marker" -and + [string](Get-PropertyValue -Item $archive -Name "package_status" -Default "") -eq "final_package_indexed_and_archived_contract_only" -and + [string](Get-PropertyValue -Item $archive -Name "final_release_status" -Default "") -eq "closed_not_approved_final_contract_only" -and + [string](Get-PropertyValue -Item $archive -Name "covered_stage_range" -Default "") -eq "C19Z117-C19Z122" -and + [int](Get-PropertyValue -Item $archive -Name "covered_stage_count" -Default -1) -eq 6 -and + [string](Get-PropertyValue -Item $archive -Name "latest_compatibility_stage" -Default "") -eq "C19Z122" -and + [string](Get-PropertyValue -Item $archive -Name "release_status" -Default "") -eq "not_approved_outcome_closed_contract_only" -and + [string](Get-PropertyValue -Item $archive -Name "closeout_status" -Default "") -eq "closed_not_approved_package_complete" -and + [string](Get-PropertyValue -Item $archive -Name "boundary_status" -Default "") -eq "closed_not_approved_reopen_required" -and + [string](Get-PropertyValue -Item $archive -Name "reopen_policy" -Default "") -eq "new_explicit_enablement_request_required" -and + [string](Get-PropertyValue -Item $archive -Name "enablement_decision" -Default "") -eq "not_approved" -and + [string](Get-PropertyValue -Item $archive -Name "operator_review_status" -Default "") -eq "closed_without_approval" -and + [string](Get-PropertyValue -Item $archive -Name "enablement_status" -Default "") -eq "not_enabled" -and + [string](Get-PropertyValue -Item $archive -Name "runtime_gate_state" -Default "") -eq "validated_contract_only_not_enabled" -and + [string](Get-PropertyValue -Item $archive -Name "runtime_effect" -Default "") -eq "contract_only_no_runtime_enablement" -and + [string](Get-PropertyValue -Item $archive -Name "operator_default_action" -Default "") -eq "keep_real_adapter_disabled" -and + [string](Get-PropertyValue -Item $archive -Name "next_required_phase" -Default "") -eq "explicit_new_enablement_request_only" -and + -not [bool](Get-PropertyValue -Item $archive -Name "allows_process_start" -Default $true) -and + -not [bool](Get-PropertyValue -Item $archive -Name "allows_payload_traffic" -Default $true) +) +$archiveNotesCompatible = Test-ArrayContainsAll -Actual $archiveNotes -Expected $requiredArchiveNotes +$guardrailsCompatible = ( + (Test-ObjectHasFields -Item $guardrails -Fields $requiredGuardrailFields) -and + [bool](Get-PropertyValue -Item $guardrails -Name "activation_blocked" -Default $false) -and + -not [bool](Get-PropertyValue -Item $guardrails -Name "process_start_allowed" -Default $true) -and + -not [bool](Get-PropertyValue -Item $guardrails -Name "health_probe_enabled" -Default $true) -and + [string](Get-PropertyValue -Item $guardrails -Name "payload_traffic" -Default "") -eq "none" -and + -not [bool](Get-PropertyValue -Item $guardrails -Name "allows_process_start" -Default $true) -and + -not [bool](Get-PropertyValue -Item $guardrails -Name "allows_payload_traffic" -Default $true) +) + +$checks = [ordered]@{ + source_smoke_passed = ([bool]$sourceResult.passed) + source_schema_expected = ([string]$sourceResult.schema_version -eq "c19z129.remote_workspace_real_adapter_not_approved_outcome_final_package_index_archive_marker_smoke.v1") + archive_marker_present = ($null -ne $archive) + archive_fields_compatible = $archiveFieldsCompatible + archive_values_compatible = $archiveValuesCompatible + archive_notes_compatible = $archiveNotesCompatible + guardrails_compatible = $guardrailsCompatible +} +$failed = @($checks.GetEnumerator() | Where-Object { -not $_.Value } | ForEach-Object { $_.Key }) + +$result = [ordered]@{ + schema_version = "c19z130.remote_workspace_real_adapter_not_approved_outcome_final_package_index_archive_marker_compatibility_smoke.v1" + source_result_path = $sourceFile + cluster_id = $ClusterID + required_archive_fields = $requiredArchiveFields + required_guardrail_fields = $requiredGuardrailFields + required_archive_notes = $requiredArchiveNotes + not_approved_outcome_final_package_index_archive_marker = $archive + checks = $checks + failed_checks = $failed + passed = ($failed.Count -eq 0) +} + +$fullResultPath = Join-Path $repoRoot $ResultPath +$resultDir = Split-Path -Parent $fullResultPath +if ($resultDir) { New-Item -ItemType Directory -Force -Path $resultDir | Out-Null } +$result | ConvertTo-Json -Depth 100 | Set-Content -Encoding UTF8 -Path $fullResultPath + +if (-not $result.passed) { + throw "C19Z130 remote workspace real-adapter not-approved outcome final package index archive marker compatibility smoke failed. Result: $fullResultPath Failed: $($failed -join ', ')" +} + +Write-Host "C19Z130 remote workspace real-adapter not-approved outcome final package index archive marker compatibility smoke passed. Result: $fullResultPath" +$result diff --git a/scripts/fabric/c19z131-remote-workspace-real-adapter-not-approved-outcome-archive-closeout-manifest-smoke.ps1 b/scripts/fabric/c19z131-remote-workspace-real-adapter-not-approved-outcome-archive-closeout-manifest-smoke.ps1 new file mode 100644 index 0000000..b40c82b --- /dev/null +++ b/scripts/fabric/c19z131-remote-workspace-real-adapter-not-approved-outcome-archive-closeout-manifest-smoke.ps1 @@ -0,0 +1,200 @@ +param( + [string]$ApiBaseUrl = "http://192.168.200.61:18121/api/v1", + [string]$ClusterID = "cfc0743d-d960-49fb-9de8-96e063d5e4aa", + [string]$ActorUserID = "f67d943f-5397-4b3a-a229-695fe67ad700", + [string]$RequestedNodeName = "test-1", + [string]$DefaultNodeName = "test-2", + [string]$MatrixNodeName = "test-1", + [string]$ResultPath = "artifacts\c19z131-remote-workspace-real-adapter-not-approved-outcome-archive-closeout-manifest-smoke-result.json" +) + +Set-StrictMode -Version Latest +$ErrorActionPreference = "Stop" + +$repoRoot = (Resolve-Path (Join-Path $PSScriptRoot "..\..")).ProviderPath +$sourceResultPath = "artifacts\c19z131-remote-workspace-real-adapter-not-approved-outcome-archive-closeout-manifest-source-result.json" +$requiredManifestFields = @( + "schema_version", + "source_archive_schema", + "manifest_status", + "manifest_marker", + "archive_status", + "package_status", + "final_release_status", + "covered_stage_range", + "covered_stage_count", + "latest_compatibility_stage", + "release_status", + "closeout_status", + "boundary_status", + "reopen_policy", + "enablement_decision", + "operator_review_status", + "enablement_status", + "runtime_gate_state", + "runtime_effect", + "operator_default_action", + "next_required_phase", + "allows_process_start", + "allows_payload_traffic", + "guardrail_summary", + "manifest_notes" +) +$requiredGuardrailFields = @("activation_blocked", "process_start_allowed", "health_probe_enabled", "payload_traffic", "allows_process_start", "allows_payload_traffic") +$requiredManifestNotes = @( + "not_approved_archive_closeout_manifested", + "not_approved_branch_closed_until_new_request", + "operator_outcome_closed_without_approval", + "new_explicit_enablement_request_required", + "real_runtime_gate_not_enabled", + "process_start_disabled", + "payload_forwarding_disabled" +) + +function Get-PropertyValue { + param([object]$Item, [string]$Name, [object]$Default = $null) + if ($null -eq $Item) { return $Default } + if ($Item -is [System.Collections.IDictionary]) { + if ($Item.Contains($Name)) { return $Item[$Name] } + return $Default + } + $property = $Item.PSObject.Properties[$Name] + if ($null -eq $property) { return $Default } + return $property.Value +} + +function Test-ObjectHasFields { + param([object]$Item, [string[]]$Fields) + if ($null -eq $Item) { return $false } + foreach ($field in $Fields) { + if ($Item -is [System.Collections.IDictionary]) { + if (-not $Item.Contains($field)) { return $false } + continue + } + if ($null -eq $Item.PSObject.Properties[$field]) { return $false } + } + return $true +} + +function Test-ArrayContainsAll { + param([object[]]$Actual, [string[]]$Expected) + foreach ($item in $Expected) { + if ($Actual -notcontains $item) { return $false } + } + return $true +} + +& powershell -ExecutionPolicy Bypass -File (Join-Path $PSScriptRoot "c19z130-remote-workspace-real-adapter-not-approved-outcome-final-package-index-archive-marker-compatibility-smoke.ps1") ` + -ApiBaseUrl $ApiBaseUrl ` + -ClusterID $ClusterID ` + -ActorUserID $ActorUserID ` + -RequestedNodeName $RequestedNodeName ` + -DefaultNodeName $DefaultNodeName ` + -MatrixNodeName $MatrixNodeName ` + -ResultPath $sourceResultPath | Out-Null + +$sourceFile = Join-Path $repoRoot $sourceResultPath +$sourceResult = Get-Content -Raw -Path $sourceFile | ConvertFrom-Json +$archive = Get-PropertyValue -Item $sourceResult -Name "not_approved_outcome_final_package_index_archive_marker" -Default $null +$guardrails = Get-PropertyValue -Item $archive -Name "guardrail_summary" -Default $null + +$manifest = [ordered]@{ + schema_version = "rap.remote_workspace_real_adapter_not_approved_outcome_archive_closeout_manifest.v1" + source_archive_schema = Get-PropertyValue -Item $archive -Name "schema_version" -Default $null + manifest_status = "closed_not_approved_archive_manifest_complete" + manifest_marker = "c19z131_real_adapter_not_approved_outcome_archive_closeout_manifest" + archive_status = Get-PropertyValue -Item $archive -Name "archive_status" -Default $null + package_status = Get-PropertyValue -Item $archive -Name "package_status" -Default $null + final_release_status = Get-PropertyValue -Item $archive -Name "final_release_status" -Default $null + covered_stage_range = Get-PropertyValue -Item $archive -Name "covered_stage_range" -Default $null + covered_stage_count = Get-PropertyValue -Item $archive -Name "covered_stage_count" -Default $null + latest_compatibility_stage = Get-PropertyValue -Item $archive -Name "latest_compatibility_stage" -Default $null + release_status = Get-PropertyValue -Item $archive -Name "release_status" -Default $null + closeout_status = Get-PropertyValue -Item $archive -Name "closeout_status" -Default $null + boundary_status = Get-PropertyValue -Item $archive -Name "boundary_status" -Default $null + reopen_policy = Get-PropertyValue -Item $archive -Name "reopen_policy" -Default $null + enablement_decision = Get-PropertyValue -Item $archive -Name "enablement_decision" -Default $null + operator_review_status = Get-PropertyValue -Item $archive -Name "operator_review_status" -Default $null + enablement_status = Get-PropertyValue -Item $archive -Name "enablement_status" -Default $null + runtime_gate_state = Get-PropertyValue -Item $archive -Name "runtime_gate_state" -Default $null + runtime_effect = Get-PropertyValue -Item $archive -Name "runtime_effect" -Default $null + operator_default_action = Get-PropertyValue -Item $archive -Name "operator_default_action" -Default $null + next_required_phase = Get-PropertyValue -Item $archive -Name "next_required_phase" -Default $null + allows_process_start = $false + allows_payload_traffic = $false + guardrail_summary = $guardrails + manifest_notes = $requiredManifestNotes +} + +$manifestFieldsCompatible = Test-ObjectHasFields -Item $manifest -Fields $requiredManifestFields +$manifestValuesCompatible = ( + [string](Get-PropertyValue -Item $manifest -Name "schema_version" -Default "") -eq "rap.remote_workspace_real_adapter_not_approved_outcome_archive_closeout_manifest.v1" -and + [string](Get-PropertyValue -Item $manifest -Name "source_archive_schema" -Default "") -eq "rap.remote_workspace_real_adapter_not_approved_outcome_final_package_index_archive_marker.v1" -and + [string](Get-PropertyValue -Item $manifest -Name "manifest_status" -Default "") -eq "closed_not_approved_archive_manifest_complete" -and + [string](Get-PropertyValue -Item $manifest -Name "manifest_marker" -Default "") -eq "c19z131_real_adapter_not_approved_outcome_archive_closeout_manifest" -and + [string](Get-PropertyValue -Item $manifest -Name "archive_status" -Default "") -eq "closed_not_approved_archived_contract_only" -and + [string](Get-PropertyValue -Item $manifest -Name "package_status" -Default "") -eq "final_package_indexed_and_archived_contract_only" -and + [string](Get-PropertyValue -Item $manifest -Name "final_release_status" -Default "") -eq "closed_not_approved_final_contract_only" -and + [string](Get-PropertyValue -Item $manifest -Name "covered_stage_range" -Default "") -eq "C19Z117-C19Z122" -and + [int](Get-PropertyValue -Item $manifest -Name "covered_stage_count" -Default -1) -eq 6 -and + [string](Get-PropertyValue -Item $manifest -Name "latest_compatibility_stage" -Default "") -eq "C19Z122" -and + [string](Get-PropertyValue -Item $manifest -Name "release_status" -Default "") -eq "not_approved_outcome_closed_contract_only" -and + [string](Get-PropertyValue -Item $manifest -Name "closeout_status" -Default "") -eq "closed_not_approved_package_complete" -and + [string](Get-PropertyValue -Item $manifest -Name "boundary_status" -Default "") -eq "closed_not_approved_reopen_required" -and + [string](Get-PropertyValue -Item $manifest -Name "reopen_policy" -Default "") -eq "new_explicit_enablement_request_required" -and + [string](Get-PropertyValue -Item $manifest -Name "enablement_decision" -Default "") -eq "not_approved" -and + [string](Get-PropertyValue -Item $manifest -Name "operator_review_status" -Default "") -eq "closed_without_approval" -and + [string](Get-PropertyValue -Item $manifest -Name "enablement_status" -Default "") -eq "not_enabled" -and + [string](Get-PropertyValue -Item $manifest -Name "runtime_gate_state" -Default "") -eq "validated_contract_only_not_enabled" -and + [string](Get-PropertyValue -Item $manifest -Name "runtime_effect" -Default "") -eq "contract_only_no_runtime_enablement" -and + [string](Get-PropertyValue -Item $manifest -Name "operator_default_action" -Default "") -eq "keep_real_adapter_disabled" -and + [string](Get-PropertyValue -Item $manifest -Name "next_required_phase" -Default "") -eq "explicit_new_enablement_request_only" -and + -not [bool](Get-PropertyValue -Item $manifest -Name "allows_process_start" -Default $true) -and + -not [bool](Get-PropertyValue -Item $manifest -Name "allows_payload_traffic" -Default $true) +) +$manifestNotesCompatible = Test-ArrayContainsAll -Actual @($manifest.manifest_notes) -Expected $requiredManifestNotes +$guardrailsCompatible = ( + (Test-ObjectHasFields -Item $guardrails -Fields $requiredGuardrailFields) -and + [bool](Get-PropertyValue -Item $guardrails -Name "activation_blocked" -Default $false) -and + -not [bool](Get-PropertyValue -Item $guardrails -Name "process_start_allowed" -Default $true) -and + -not [bool](Get-PropertyValue -Item $guardrails -Name "health_probe_enabled" -Default $true) -and + [string](Get-PropertyValue -Item $guardrails -Name "payload_traffic" -Default "") -eq "none" -and + -not [bool](Get-PropertyValue -Item $guardrails -Name "allows_process_start" -Default $true) -and + -not [bool](Get-PropertyValue -Item $guardrails -Name "allows_payload_traffic" -Default $true) +) + +$checks = [ordered]@{ + source_smoke_passed = ([bool]$sourceResult.passed) + source_schema_expected = ([string]$sourceResult.schema_version -eq "c19z130.remote_workspace_real_adapter_not_approved_outcome_final_package_index_archive_marker_compatibility_smoke.v1") + archive_marker_present = ($null -ne $archive) + manifest_fields_compatible = $manifestFieldsCompatible + manifest_values_compatible = $manifestValuesCompatible + manifest_notes_compatible = $manifestNotesCompatible + guardrails_compatible = $guardrailsCompatible +} +$failed = @($checks.GetEnumerator() | Where-Object { -not $_.Value } | ForEach-Object { $_.Key }) + +$result = [ordered]@{ + schema_version = "c19z131.remote_workspace_real_adapter_not_approved_outcome_archive_closeout_manifest_smoke.v1" + source_result_path = $sourceFile + cluster_id = $ClusterID + required_manifest_fields = $requiredManifestFields + required_guardrail_fields = $requiredGuardrailFields + required_manifest_notes = $requiredManifestNotes + not_approved_outcome_archive_closeout_manifest = $manifest + checks = $checks + failed_checks = $failed + passed = ($failed.Count -eq 0) +} + +$fullResultPath = Join-Path $repoRoot $ResultPath +$resultDir = Split-Path -Parent $fullResultPath +if ($resultDir) { New-Item -ItemType Directory -Force -Path $resultDir | Out-Null } +$result | ConvertTo-Json -Depth 100 | Set-Content -Encoding UTF8 -Path $fullResultPath + +if (-not $result.passed) { + throw "C19Z131 remote workspace real-adapter not-approved outcome archive closeout manifest smoke failed. Result: $fullResultPath Failed: $($failed -join ', ')" +} + +Write-Host "C19Z131 remote workspace real-adapter not-approved outcome archive closeout manifest smoke passed. Result: $fullResultPath" +$result diff --git a/scripts/fabric/c19z132-remote-workspace-real-adapter-not-approved-outcome-archive-closeout-manifest-compatibility-smoke.ps1 b/scripts/fabric/c19z132-remote-workspace-real-adapter-not-approved-outcome-archive-closeout-manifest-compatibility-smoke.ps1 new file mode 100644 index 0000000..d0e9b8e --- /dev/null +++ b/scripts/fabric/c19z132-remote-workspace-real-adapter-not-approved-outcome-archive-closeout-manifest-compatibility-smoke.ps1 @@ -0,0 +1,173 @@ +param( + [string]$ApiBaseUrl = "http://192.168.200.61:18121/api/v1", + [string]$ClusterID = "cfc0743d-d960-49fb-9de8-96e063d5e4aa", + [string]$ActorUserID = "f67d943f-5397-4b3a-a229-695fe67ad700", + [string]$RequestedNodeName = "test-1", + [string]$DefaultNodeName = "test-2", + [string]$MatrixNodeName = "test-1", + [string]$ResultPath = "artifacts\c19z132-remote-workspace-real-adapter-not-approved-outcome-archive-closeout-manifest-compatibility-smoke-result.json" +) + +Set-StrictMode -Version Latest +$ErrorActionPreference = "Stop" + +$repoRoot = (Resolve-Path (Join-Path $PSScriptRoot "..\..")).ProviderPath +$sourceResultPath = "artifacts\c19z132-remote-workspace-real-adapter-not-approved-outcome-archive-closeout-manifest-source-result.json" +$requiredManifestFields = @( + "schema_version", + "source_archive_schema", + "manifest_status", + "manifest_marker", + "archive_status", + "package_status", + "final_release_status", + "covered_stage_range", + "covered_stage_count", + "latest_compatibility_stage", + "release_status", + "closeout_status", + "boundary_status", + "reopen_policy", + "enablement_decision", + "operator_review_status", + "enablement_status", + "runtime_gate_state", + "runtime_effect", + "operator_default_action", + "next_required_phase", + "allows_process_start", + "allows_payload_traffic", + "guardrail_summary", + "manifest_notes" +) +$requiredGuardrailFields = @("activation_blocked", "process_start_allowed", "health_probe_enabled", "payload_traffic", "allows_process_start", "allows_payload_traffic") +$requiredManifestNotes = @( + "not_approved_archive_closeout_manifested", + "not_approved_branch_closed_until_new_request", + "operator_outcome_closed_without_approval", + "new_explicit_enablement_request_required", + "real_runtime_gate_not_enabled", + "process_start_disabled", + "payload_forwarding_disabled" +) + +function Get-PropertyValue { + param([object]$Item, [string]$Name, [object]$Default = $null) + if ($null -eq $Item) { return $Default } + if ($Item -is [System.Collections.IDictionary]) { + if ($Item.Contains($Name)) { return $Item[$Name] } + return $Default + } + $property = $Item.PSObject.Properties[$Name] + if ($null -eq $property) { return $Default } + return $property.Value +} + +function Test-ObjectHasFields { + param([object]$Item, [string[]]$Fields) + if ($null -eq $Item) { return $false } + foreach ($field in $Fields) { + if ($Item -is [System.Collections.IDictionary]) { + if (-not $Item.Contains($field)) { return $false } + continue + } + if ($null -eq $Item.PSObject.Properties[$field]) { return $false } + } + return $true +} + +function Test-ArrayContainsAll { + param([object[]]$Actual, [string[]]$Expected) + foreach ($item in $Expected) { + if ($Actual -notcontains $item) { return $false } + } + return $true +} + +& powershell -ExecutionPolicy Bypass -File (Join-Path $PSScriptRoot "c19z131-remote-workspace-real-adapter-not-approved-outcome-archive-closeout-manifest-smoke.ps1") ` + -ApiBaseUrl $ApiBaseUrl ` + -ClusterID $ClusterID ` + -ActorUserID $ActorUserID ` + -RequestedNodeName $RequestedNodeName ` + -DefaultNodeName $DefaultNodeName ` + -MatrixNodeName $MatrixNodeName ` + -ResultPath $sourceResultPath | Out-Null + +$sourceFile = Join-Path $repoRoot $sourceResultPath +$sourceResult = Get-Content -Raw -Path $sourceFile | ConvertFrom-Json +$manifest = Get-PropertyValue -Item $sourceResult -Name "not_approved_outcome_archive_closeout_manifest" -Default $null +$guardrails = Get-PropertyValue -Item $manifest -Name "guardrail_summary" -Default $null +$manifestNotes = @(Get-PropertyValue -Item $manifest -Name "manifest_notes" -Default @()) + +$manifestFieldsCompatible = Test-ObjectHasFields -Item $manifest -Fields $requiredManifestFields +$manifestValuesCompatible = ( + [string](Get-PropertyValue -Item $manifest -Name "schema_version" -Default "") -eq "rap.remote_workspace_real_adapter_not_approved_outcome_archive_closeout_manifest.v1" -and + [string](Get-PropertyValue -Item $manifest -Name "source_archive_schema" -Default "") -eq "rap.remote_workspace_real_adapter_not_approved_outcome_final_package_index_archive_marker.v1" -and + [string](Get-PropertyValue -Item $manifest -Name "manifest_status" -Default "") -eq "closed_not_approved_archive_manifest_complete" -and + [string](Get-PropertyValue -Item $manifest -Name "manifest_marker" -Default "") -eq "c19z131_real_adapter_not_approved_outcome_archive_closeout_manifest" -and + [string](Get-PropertyValue -Item $manifest -Name "archive_status" -Default "") -eq "closed_not_approved_archived_contract_only" -and + [string](Get-PropertyValue -Item $manifest -Name "package_status" -Default "") -eq "final_package_indexed_and_archived_contract_only" -and + [string](Get-PropertyValue -Item $manifest -Name "final_release_status" -Default "") -eq "closed_not_approved_final_contract_only" -and + [string](Get-PropertyValue -Item $manifest -Name "covered_stage_range" -Default "") -eq "C19Z117-C19Z122" -and + [int](Get-PropertyValue -Item $manifest -Name "covered_stage_count" -Default -1) -eq 6 -and + [string](Get-PropertyValue -Item $manifest -Name "latest_compatibility_stage" -Default "") -eq "C19Z122" -and + [string](Get-PropertyValue -Item $manifest -Name "release_status" -Default "") -eq "not_approved_outcome_closed_contract_only" -and + [string](Get-PropertyValue -Item $manifest -Name "closeout_status" -Default "") -eq "closed_not_approved_package_complete" -and + [string](Get-PropertyValue -Item $manifest -Name "boundary_status" -Default "") -eq "closed_not_approved_reopen_required" -and + [string](Get-PropertyValue -Item $manifest -Name "reopen_policy" -Default "") -eq "new_explicit_enablement_request_required" -and + [string](Get-PropertyValue -Item $manifest -Name "enablement_decision" -Default "") -eq "not_approved" -and + [string](Get-PropertyValue -Item $manifest -Name "operator_review_status" -Default "") -eq "closed_without_approval" -and + [string](Get-PropertyValue -Item $manifest -Name "enablement_status" -Default "") -eq "not_enabled" -and + [string](Get-PropertyValue -Item $manifest -Name "runtime_gate_state" -Default "") -eq "validated_contract_only_not_enabled" -and + [string](Get-PropertyValue -Item $manifest -Name "runtime_effect" -Default "") -eq "contract_only_no_runtime_enablement" -and + [string](Get-PropertyValue -Item $manifest -Name "operator_default_action" -Default "") -eq "keep_real_adapter_disabled" -and + [string](Get-PropertyValue -Item $manifest -Name "next_required_phase" -Default "") -eq "explicit_new_enablement_request_only" -and + -not [bool](Get-PropertyValue -Item $manifest -Name "allows_process_start" -Default $true) -and + -not [bool](Get-PropertyValue -Item $manifest -Name "allows_payload_traffic" -Default $true) +) +$manifestNotesCompatible = Test-ArrayContainsAll -Actual $manifestNotes -Expected $requiredManifestNotes +$guardrailsCompatible = ( + (Test-ObjectHasFields -Item $guardrails -Fields $requiredGuardrailFields) -and + [bool](Get-PropertyValue -Item $guardrails -Name "activation_blocked" -Default $false) -and + -not [bool](Get-PropertyValue -Item $guardrails -Name "process_start_allowed" -Default $true) -and + -not [bool](Get-PropertyValue -Item $guardrails -Name "health_probe_enabled" -Default $true) -and + [string](Get-PropertyValue -Item $guardrails -Name "payload_traffic" -Default "") -eq "none" -and + -not [bool](Get-PropertyValue -Item $guardrails -Name "allows_process_start" -Default $true) -and + -not [bool](Get-PropertyValue -Item $guardrails -Name "allows_payload_traffic" -Default $true) +) + +$checks = [ordered]@{ + source_smoke_passed = ([bool]$sourceResult.passed) + source_schema_expected = ([string]$sourceResult.schema_version -eq "c19z131.remote_workspace_real_adapter_not_approved_outcome_archive_closeout_manifest_smoke.v1") + manifest_present = ($null -ne $manifest) + manifest_fields_compatible = $manifestFieldsCompatible + manifest_values_compatible = $manifestValuesCompatible + manifest_notes_compatible = $manifestNotesCompatible + guardrails_compatible = $guardrailsCompatible +} +$failed = @($checks.GetEnumerator() | Where-Object { -not $_.Value } | ForEach-Object { $_.Key }) + +$result = [ordered]@{ + schema_version = "c19z132.remote_workspace_real_adapter_not_approved_outcome_archive_closeout_manifest_compatibility_smoke.v1" + source_result_path = $sourceFile + cluster_id = $ClusterID + required_manifest_fields = $requiredManifestFields + required_guardrail_fields = $requiredGuardrailFields + required_manifest_notes = $requiredManifestNotes + not_approved_outcome_archive_closeout_manifest = $manifest + checks = $checks + failed_checks = $failed + passed = ($failed.Count -eq 0) +} + +$fullResultPath = Join-Path $repoRoot $ResultPath +$resultDir = Split-Path -Parent $fullResultPath +if ($resultDir) { New-Item -ItemType Directory -Force -Path $resultDir | Out-Null } +$result | ConvertTo-Json -Depth 100 | Set-Content -Encoding UTF8 -Path $fullResultPath + +if (-not $result.passed) { + throw "C19Z132 remote workspace real-adapter not-approved outcome archive closeout manifest compatibility smoke failed. Result: $fullResultPath Failed: $($failed -join ', ')" +} + +Write-Host "C19Z132 remote workspace real-adapter not-approved outcome archive closeout manifest compatibility smoke passed. Result: $fullResultPath" +$result diff --git a/scripts/fabric/c19z133-remote-workspace-real-adapter-not-approved-outcome-stopped-branch-sentinel-smoke.ps1 b/scripts/fabric/c19z133-remote-workspace-real-adapter-not-approved-outcome-stopped-branch-sentinel-smoke.ps1 new file mode 100644 index 0000000..1d557f2 --- /dev/null +++ b/scripts/fabric/c19z133-remote-workspace-real-adapter-not-approved-outcome-stopped-branch-sentinel-smoke.ps1 @@ -0,0 +1,208 @@ +param( + [string]$ApiBaseUrl = "http://192.168.200.61:18121/api/v1", + [string]$ClusterID = "cfc0743d-d960-49fb-9de8-96e063d5e4aa", + [string]$ActorUserID = "f67d943f-5397-4b3a-a229-695fe67ad700", + [string]$RequestedNodeName = "test-1", + [string]$DefaultNodeName = "test-2", + [string]$MatrixNodeName = "test-1", + [string]$ResultPath = "artifacts\c19z133-remote-workspace-real-adapter-not-approved-outcome-stopped-branch-sentinel-smoke-result.json" +) + +Set-StrictMode -Version Latest +$ErrorActionPreference = "Stop" + +$repoRoot = (Resolve-Path (Join-Path $PSScriptRoot "..\..")).ProviderPath +$sourceResultPath = "artifacts\c19z133-remote-workspace-real-adapter-not-approved-outcome-stopped-branch-sentinel-source-result.json" +$requiredSentinelFields = @( + "schema_version", + "source_manifest_schema", + "sentinel_status", + "sentinel_marker", + "branch_state", + "continuation_policy", + "manifest_status", + "archive_status", + "package_status", + "final_release_status", + "covered_stage_range", + "covered_stage_count", + "latest_compatibility_stage", + "release_status", + "closeout_status", + "boundary_status", + "reopen_policy", + "enablement_decision", + "operator_review_status", + "enablement_status", + "runtime_gate_state", + "runtime_effect", + "operator_default_action", + "next_required_phase", + "allows_process_start", + "allows_payload_traffic", + "guardrail_summary", + "sentinel_notes" +) +$requiredGuardrailFields = @("activation_blocked", "process_start_allowed", "health_probe_enabled", "payload_traffic", "allows_process_start", "allows_payload_traffic") +$requiredSentinelNotes = @( + "not_approved_branch_stopped", + "no_further_not_approved_layers_required", + "new_explicit_enablement_request_required", + "real_runtime_gate_not_enabled", + "process_start_disabled", + "payload_forwarding_disabled" +) + +function Get-PropertyValue { + param([object]$Item, [string]$Name, [object]$Default = $null) + if ($null -eq $Item) { return $Default } + if ($Item -is [System.Collections.IDictionary]) { + if ($Item.Contains($Name)) { return $Item[$Name] } + return $Default + } + $property = $Item.PSObject.Properties[$Name] + if ($null -eq $property) { return $Default } + return $property.Value +} + +function Test-ObjectHasFields { + param([object]$Item, [string[]]$Fields) + if ($null -eq $Item) { return $false } + foreach ($field in $Fields) { + if ($Item -is [System.Collections.IDictionary]) { + if (-not $Item.Contains($field)) { return $false } + continue + } + if ($null -eq $Item.PSObject.Properties[$field]) { return $false } + } + return $true +} + +function Test-ArrayContainsAll { + param([object[]]$Actual, [string[]]$Expected) + foreach ($item in $Expected) { + if ($Actual -notcontains $item) { return $false } + } + return $true +} + +& powershell -ExecutionPolicy Bypass -File (Join-Path $PSScriptRoot "c19z132-remote-workspace-real-adapter-not-approved-outcome-archive-closeout-manifest-compatibility-smoke.ps1") ` + -ApiBaseUrl $ApiBaseUrl ` + -ClusterID $ClusterID ` + -ActorUserID $ActorUserID ` + -RequestedNodeName $RequestedNodeName ` + -DefaultNodeName $DefaultNodeName ` + -MatrixNodeName $MatrixNodeName ` + -ResultPath $sourceResultPath | Out-Null + +$sourceFile = Join-Path $repoRoot $sourceResultPath +$sourceResult = Get-Content -Raw -Path $sourceFile | ConvertFrom-Json +$manifest = Get-PropertyValue -Item $sourceResult -Name "not_approved_outcome_archive_closeout_manifest" -Default $null +$guardrails = Get-PropertyValue -Item $manifest -Name "guardrail_summary" -Default $null + +$sentinel = [ordered]@{ + schema_version = "rap.remote_workspace_real_adapter_not_approved_outcome_stopped_branch_sentinel.v1" + source_manifest_schema = Get-PropertyValue -Item $manifest -Name "schema_version" -Default $null + sentinel_status = "stopped_until_new_explicit_enablement_request" + sentinel_marker = "c19z133_real_adapter_not_approved_outcome_stopped_branch_sentinel" + branch_state = "not_approved_branch_closed" + continuation_policy = "do_not_continue_without_new_explicit_enablement_request" + manifest_status = Get-PropertyValue -Item $manifest -Name "manifest_status" -Default $null + archive_status = Get-PropertyValue -Item $manifest -Name "archive_status" -Default $null + package_status = Get-PropertyValue -Item $manifest -Name "package_status" -Default $null + final_release_status = Get-PropertyValue -Item $manifest -Name "final_release_status" -Default $null + covered_stage_range = Get-PropertyValue -Item $manifest -Name "covered_stage_range" -Default $null + covered_stage_count = Get-PropertyValue -Item $manifest -Name "covered_stage_count" -Default $null + latest_compatibility_stage = Get-PropertyValue -Item $manifest -Name "latest_compatibility_stage" -Default $null + release_status = Get-PropertyValue -Item $manifest -Name "release_status" -Default $null + closeout_status = Get-PropertyValue -Item $manifest -Name "closeout_status" -Default $null + boundary_status = Get-PropertyValue -Item $manifest -Name "boundary_status" -Default $null + reopen_policy = Get-PropertyValue -Item $manifest -Name "reopen_policy" -Default $null + enablement_decision = Get-PropertyValue -Item $manifest -Name "enablement_decision" -Default $null + operator_review_status = Get-PropertyValue -Item $manifest -Name "operator_review_status" -Default $null + enablement_status = Get-PropertyValue -Item $manifest -Name "enablement_status" -Default $null + runtime_gate_state = Get-PropertyValue -Item $manifest -Name "runtime_gate_state" -Default $null + runtime_effect = Get-PropertyValue -Item $manifest -Name "runtime_effect" -Default $null + operator_default_action = Get-PropertyValue -Item $manifest -Name "operator_default_action" -Default $null + next_required_phase = Get-PropertyValue -Item $manifest -Name "next_required_phase" -Default $null + allows_process_start = $false + allows_payload_traffic = $false + guardrail_summary = $guardrails + sentinel_notes = $requiredSentinelNotes +} + +$sentinelFieldsCompatible = Test-ObjectHasFields -Item $sentinel -Fields $requiredSentinelFields +$sentinelValuesCompatible = ( + [string](Get-PropertyValue -Item $sentinel -Name "schema_version" -Default "") -eq "rap.remote_workspace_real_adapter_not_approved_outcome_stopped_branch_sentinel.v1" -and + [string](Get-PropertyValue -Item $sentinel -Name "source_manifest_schema" -Default "") -eq "rap.remote_workspace_real_adapter_not_approved_outcome_archive_closeout_manifest.v1" -and + [string](Get-PropertyValue -Item $sentinel -Name "sentinel_status" -Default "") -eq "stopped_until_new_explicit_enablement_request" -and + [string](Get-PropertyValue -Item $sentinel -Name "sentinel_marker" -Default "") -eq "c19z133_real_adapter_not_approved_outcome_stopped_branch_sentinel" -and + [string](Get-PropertyValue -Item $sentinel -Name "branch_state" -Default "") -eq "not_approved_branch_closed" -and + [string](Get-PropertyValue -Item $sentinel -Name "continuation_policy" -Default "") -eq "do_not_continue_without_new_explicit_enablement_request" -and + [string](Get-PropertyValue -Item $sentinel -Name "manifest_status" -Default "") -eq "closed_not_approved_archive_manifest_complete" -and + [string](Get-PropertyValue -Item $sentinel -Name "archive_status" -Default "") -eq "closed_not_approved_archived_contract_only" -and + [string](Get-PropertyValue -Item $sentinel -Name "package_status" -Default "") -eq "final_package_indexed_and_archived_contract_only" -and + [string](Get-PropertyValue -Item $sentinel -Name "final_release_status" -Default "") -eq "closed_not_approved_final_contract_only" -and + [string](Get-PropertyValue -Item $sentinel -Name "covered_stage_range" -Default "") -eq "C19Z117-C19Z122" -and + [int](Get-PropertyValue -Item $sentinel -Name "covered_stage_count" -Default -1) -eq 6 -and + [string](Get-PropertyValue -Item $sentinel -Name "latest_compatibility_stage" -Default "") -eq "C19Z122" -and + [string](Get-PropertyValue -Item $sentinel -Name "release_status" -Default "") -eq "not_approved_outcome_closed_contract_only" -and + [string](Get-PropertyValue -Item $sentinel -Name "closeout_status" -Default "") -eq "closed_not_approved_package_complete" -and + [string](Get-PropertyValue -Item $sentinel -Name "boundary_status" -Default "") -eq "closed_not_approved_reopen_required" -and + [string](Get-PropertyValue -Item $sentinel -Name "reopen_policy" -Default "") -eq "new_explicit_enablement_request_required" -and + [string](Get-PropertyValue -Item $sentinel -Name "enablement_decision" -Default "") -eq "not_approved" -and + [string](Get-PropertyValue -Item $sentinel -Name "operator_review_status" -Default "") -eq "closed_without_approval" -and + [string](Get-PropertyValue -Item $sentinel -Name "enablement_status" -Default "") -eq "not_enabled" -and + [string](Get-PropertyValue -Item $sentinel -Name "runtime_gate_state" -Default "") -eq "validated_contract_only_not_enabled" -and + [string](Get-PropertyValue -Item $sentinel -Name "runtime_effect" -Default "") -eq "contract_only_no_runtime_enablement" -and + [string](Get-PropertyValue -Item $sentinel -Name "operator_default_action" -Default "") -eq "keep_real_adapter_disabled" -and + [string](Get-PropertyValue -Item $sentinel -Name "next_required_phase" -Default "") -eq "explicit_new_enablement_request_only" -and + -not [bool](Get-PropertyValue -Item $sentinel -Name "allows_process_start" -Default $true) -and + -not [bool](Get-PropertyValue -Item $sentinel -Name "allows_payload_traffic" -Default $true) +) +$sentinelNotesCompatible = Test-ArrayContainsAll -Actual @($sentinel.sentinel_notes) -Expected $requiredSentinelNotes +$guardrailsCompatible = ( + (Test-ObjectHasFields -Item $guardrails -Fields $requiredGuardrailFields) -and + [bool](Get-PropertyValue -Item $guardrails -Name "activation_blocked" -Default $false) -and + -not [bool](Get-PropertyValue -Item $guardrails -Name "process_start_allowed" -Default $true) -and + -not [bool](Get-PropertyValue -Item $guardrails -Name "health_probe_enabled" -Default $true) -and + [string](Get-PropertyValue -Item $guardrails -Name "payload_traffic" -Default "") -eq "none" -and + -not [bool](Get-PropertyValue -Item $guardrails -Name "allows_process_start" -Default $true) -and + -not [bool](Get-PropertyValue -Item $guardrails -Name "allows_payload_traffic" -Default $true) +) + +$checks = [ordered]@{ + source_smoke_passed = ([bool]$sourceResult.passed) + source_schema_expected = ([string]$sourceResult.schema_version -eq "c19z132.remote_workspace_real_adapter_not_approved_outcome_archive_closeout_manifest_compatibility_smoke.v1") + manifest_present = ($null -ne $manifest) + sentinel_fields_compatible = $sentinelFieldsCompatible + sentinel_values_compatible = $sentinelValuesCompatible + sentinel_notes_compatible = $sentinelNotesCompatible + guardrails_compatible = $guardrailsCompatible +} +$failed = @($checks.GetEnumerator() | Where-Object { -not $_.Value } | ForEach-Object { $_.Key }) + +$result = [ordered]@{ + schema_version = "c19z133.remote_workspace_real_adapter_not_approved_outcome_stopped_branch_sentinel_smoke.v1" + source_result_path = $sourceFile + cluster_id = $ClusterID + required_sentinel_fields = $requiredSentinelFields + required_guardrail_fields = $requiredGuardrailFields + required_sentinel_notes = $requiredSentinelNotes + not_approved_outcome_stopped_branch_sentinel = $sentinel + checks = $checks + failed_checks = $failed + passed = ($failed.Count -eq 0) +} + +$fullResultPath = Join-Path $repoRoot $ResultPath +$resultDir = Split-Path -Parent $fullResultPath +if ($resultDir) { New-Item -ItemType Directory -Force -Path $resultDir | Out-Null } +$result | ConvertTo-Json -Depth 100 | Set-Content -Encoding UTF8 -Path $fullResultPath + +if (-not $result.passed) { + throw "C19Z133 remote workspace real-adapter not-approved outcome stopped branch sentinel smoke failed. Result: $fullResultPath Failed: $($failed -join ', ')" +} + +Write-Host "C19Z133 remote workspace real-adapter not-approved outcome stopped branch sentinel smoke passed. Result: $fullResultPath" +$result diff --git a/scripts/fabric/c19z134-remote-workspace-real-adapter-not-approved-outcome-stopped-branch-sentinel-compatibility-smoke.ps1 b/scripts/fabric/c19z134-remote-workspace-real-adapter-not-approved-outcome-stopped-branch-sentinel-compatibility-smoke.ps1 new file mode 100644 index 0000000..59a48fa --- /dev/null +++ b/scripts/fabric/c19z134-remote-workspace-real-adapter-not-approved-outcome-stopped-branch-sentinel-compatibility-smoke.ps1 @@ -0,0 +1,142 @@ +param( + [string]$ApiBaseUrl = "http://192.168.200.61:18121/api/v1", + [string]$ClusterID = "cfc0743d-d960-49fb-9de8-96e063d5e4aa", + [string]$ActorUserID = "f67d943f-5397-4b3a-a229-695fe67ad700", + [string]$RequestedNodeName = "test-1", + [string]$DefaultNodeName = "test-2", + [string]$MatrixNodeName = "test-1", + [string]$ResultPath = "artifacts\c19z134-remote-workspace-real-adapter-not-approved-outcome-stopped-branch-sentinel-compatibility-smoke-result.json" +) + +Set-StrictMode -Version Latest +$ErrorActionPreference = "Stop" + +$repoRoot = (Resolve-Path (Join-Path $PSScriptRoot "..\..")).ProviderPath +$sourceResultPath = "artifacts\c19z134-remote-workspace-real-adapter-not-approved-outcome-stopped-branch-sentinel-source-result.json" +$requiredSentinelFields = @("schema_version", "source_manifest_schema", "sentinel_status", "sentinel_marker", "branch_state", "continuation_policy", "manifest_status", "archive_status", "package_status", "final_release_status", "covered_stage_range", "covered_stage_count", "latest_compatibility_stage", "release_status", "closeout_status", "boundary_status", "reopen_policy", "enablement_decision", "operator_review_status", "enablement_status", "runtime_gate_state", "runtime_effect", "operator_default_action", "next_required_phase", "allows_process_start", "allows_payload_traffic", "guardrail_summary", "sentinel_notes") +$requiredGuardrailFields = @("activation_blocked", "process_start_allowed", "health_probe_enabled", "payload_traffic", "allows_process_start", "allows_payload_traffic") +$requiredSentinelNotes = @("not_approved_branch_stopped", "no_further_not_approved_layers_required", "new_explicit_enablement_request_required", "real_runtime_gate_not_enabled", "process_start_disabled", "payload_forwarding_disabled") + +function Get-PropertyValue { + param([object]$Item, [string]$Name, [object]$Default = $null) + if ($null -eq $Item) { return $Default } + if ($Item -is [System.Collections.IDictionary]) { + if ($Item.Contains($Name)) { return $Item[$Name] } + return $Default + } + $property = $Item.PSObject.Properties[$Name] + if ($null -eq $property) { return $Default } + return $property.Value +} + +function Test-ObjectHasFields { + param([object]$Item, [string[]]$Fields) + if ($null -eq $Item) { return $false } + foreach ($field in $Fields) { + if ($Item -is [System.Collections.IDictionary]) { + if (-not $Item.Contains($field)) { return $false } + continue + } + if ($null -eq $Item.PSObject.Properties[$field]) { return $false } + } + return $true +} + +function Test-ArrayContainsAll { + param([object[]]$Actual, [string[]]$Expected) + foreach ($item in $Expected) { + if ($Actual -notcontains $item) { return $false } + } + return $true +} + +& powershell -ExecutionPolicy Bypass -File (Join-Path $PSScriptRoot "c19z133-remote-workspace-real-adapter-not-approved-outcome-stopped-branch-sentinel-smoke.ps1") ` + -ApiBaseUrl $ApiBaseUrl ` + -ClusterID $ClusterID ` + -ActorUserID $ActorUserID ` + -RequestedNodeName $RequestedNodeName ` + -DefaultNodeName $DefaultNodeName ` + -MatrixNodeName $MatrixNodeName ` + -ResultPath $sourceResultPath | Out-Null + +$sourceFile = Join-Path $repoRoot $sourceResultPath +$sourceResult = Get-Content -Raw -Path $sourceFile | ConvertFrom-Json +$sentinel = Get-PropertyValue -Item $sourceResult -Name "not_approved_outcome_stopped_branch_sentinel" -Default $null +$guardrails = Get-PropertyValue -Item $sentinel -Name "guardrail_summary" -Default $null +$sentinelNotes = @(Get-PropertyValue -Item $sentinel -Name "sentinel_notes" -Default @()) + +$sentinelFieldsCompatible = Test-ObjectHasFields -Item $sentinel -Fields $requiredSentinelFields +$sentinelValuesCompatible = ( + [string](Get-PropertyValue -Item $sentinel -Name "schema_version" -Default "") -eq "rap.remote_workspace_real_adapter_not_approved_outcome_stopped_branch_sentinel.v1" -and + [string](Get-PropertyValue -Item $sentinel -Name "source_manifest_schema" -Default "") -eq "rap.remote_workspace_real_adapter_not_approved_outcome_archive_closeout_manifest.v1" -and + [string](Get-PropertyValue -Item $sentinel -Name "sentinel_status" -Default "") -eq "stopped_until_new_explicit_enablement_request" -and + [string](Get-PropertyValue -Item $sentinel -Name "sentinel_marker" -Default "") -eq "c19z133_real_adapter_not_approved_outcome_stopped_branch_sentinel" -and + [string](Get-PropertyValue -Item $sentinel -Name "branch_state" -Default "") -eq "not_approved_branch_closed" -and + [string](Get-PropertyValue -Item $sentinel -Name "continuation_policy" -Default "") -eq "do_not_continue_without_new_explicit_enablement_request" -and + [string](Get-PropertyValue -Item $sentinel -Name "manifest_status" -Default "") -eq "closed_not_approved_archive_manifest_complete" -and + [string](Get-PropertyValue -Item $sentinel -Name "archive_status" -Default "") -eq "closed_not_approved_archived_contract_only" -and + [string](Get-PropertyValue -Item $sentinel -Name "package_status" -Default "") -eq "final_package_indexed_and_archived_contract_only" -and + [string](Get-PropertyValue -Item $sentinel -Name "final_release_status" -Default "") -eq "closed_not_approved_final_contract_only" -and + [string](Get-PropertyValue -Item $sentinel -Name "covered_stage_range" -Default "") -eq "C19Z117-C19Z122" -and + [int](Get-PropertyValue -Item $sentinel -Name "covered_stage_count" -Default -1) -eq 6 -and + [string](Get-PropertyValue -Item $sentinel -Name "latest_compatibility_stage" -Default "") -eq "C19Z122" -and + [string](Get-PropertyValue -Item $sentinel -Name "release_status" -Default "") -eq "not_approved_outcome_closed_contract_only" -and + [string](Get-PropertyValue -Item $sentinel -Name "closeout_status" -Default "") -eq "closed_not_approved_package_complete" -and + [string](Get-PropertyValue -Item $sentinel -Name "boundary_status" -Default "") -eq "closed_not_approved_reopen_required" -and + [string](Get-PropertyValue -Item $sentinel -Name "reopen_policy" -Default "") -eq "new_explicit_enablement_request_required" -and + [string](Get-PropertyValue -Item $sentinel -Name "enablement_decision" -Default "") -eq "not_approved" -and + [string](Get-PropertyValue -Item $sentinel -Name "operator_review_status" -Default "") -eq "closed_without_approval" -and + [string](Get-PropertyValue -Item $sentinel -Name "enablement_status" -Default "") -eq "not_enabled" -and + [string](Get-PropertyValue -Item $sentinel -Name "runtime_gate_state" -Default "") -eq "validated_contract_only_not_enabled" -and + [string](Get-PropertyValue -Item $sentinel -Name "runtime_effect" -Default "") -eq "contract_only_no_runtime_enablement" -and + [string](Get-PropertyValue -Item $sentinel -Name "operator_default_action" -Default "") -eq "keep_real_adapter_disabled" -and + [string](Get-PropertyValue -Item $sentinel -Name "next_required_phase" -Default "") -eq "explicit_new_enablement_request_only" -and + -not [bool](Get-PropertyValue -Item $sentinel -Name "allows_process_start" -Default $true) -and + -not [bool](Get-PropertyValue -Item $sentinel -Name "allows_payload_traffic" -Default $true) +) +$sentinelNotesCompatible = Test-ArrayContainsAll -Actual $sentinelNotes -Expected $requiredSentinelNotes +$guardrailsCompatible = ( + (Test-ObjectHasFields -Item $guardrails -Fields $requiredGuardrailFields) -and + [bool](Get-PropertyValue -Item $guardrails -Name "activation_blocked" -Default $false) -and + -not [bool](Get-PropertyValue -Item $guardrails -Name "process_start_allowed" -Default $true) -and + -not [bool](Get-PropertyValue -Item $guardrails -Name "health_probe_enabled" -Default $true) -and + [string](Get-PropertyValue -Item $guardrails -Name "payload_traffic" -Default "") -eq "none" -and + -not [bool](Get-PropertyValue -Item $guardrails -Name "allows_process_start" -Default $true) -and + -not [bool](Get-PropertyValue -Item $guardrails -Name "allows_payload_traffic" -Default $true) +) + +$checks = [ordered]@{ + source_smoke_passed = ([bool]$sourceResult.passed) + source_schema_expected = ([string]$sourceResult.schema_version -eq "c19z133.remote_workspace_real_adapter_not_approved_outcome_stopped_branch_sentinel_smoke.v1") + sentinel_present = ($null -ne $sentinel) + sentinel_fields_compatible = $sentinelFieldsCompatible + sentinel_values_compatible = $sentinelValuesCompatible + sentinel_notes_compatible = $sentinelNotesCompatible + guardrails_compatible = $guardrailsCompatible +} +$failed = @($checks.GetEnumerator() | Where-Object { -not $_.Value } | ForEach-Object { $_.Key }) + +$result = [ordered]@{ + schema_version = "c19z134.remote_workspace_real_adapter_not_approved_outcome_stopped_branch_sentinel_compatibility_smoke.v1" + source_result_path = $sourceFile + cluster_id = $ClusterID + required_sentinel_fields = $requiredSentinelFields + required_guardrail_fields = $requiredGuardrailFields + required_sentinel_notes = $requiredSentinelNotes + not_approved_outcome_stopped_branch_sentinel = $sentinel + checks = $checks + failed_checks = $failed + passed = ($failed.Count -eq 0) +} + +$fullResultPath = Join-Path $repoRoot $ResultPath +$resultDir = Split-Path -Parent $fullResultPath +if ($resultDir) { New-Item -ItemType Directory -Force -Path $resultDir | Out-Null } +$result | ConvertTo-Json -Depth 100 | Set-Content -Encoding UTF8 -Path $fullResultPath + +if (-not $result.passed) { + throw "C19Z134 remote workspace real-adapter not-approved outcome stopped branch sentinel compatibility smoke failed. Result: $fullResultPath Failed: $($failed -join ', ')" +} + +Write-Host "C19Z134 remote workspace real-adapter not-approved outcome stopped branch sentinel compatibility smoke passed. Result: $fullResultPath" +$result diff --git a/scripts/fabric/c19z135-remote-workspace-real-adapter-not-approved-outcome-no-continuation-guard-smoke.ps1 b/scripts/fabric/c19z135-remote-workspace-real-adapter-not-approved-outcome-no-continuation-guard-smoke.ps1 new file mode 100644 index 0000000..84aa82e --- /dev/null +++ b/scripts/fabric/c19z135-remote-workspace-real-adapter-not-approved-outcome-no-continuation-guard-smoke.ps1 @@ -0,0 +1,186 @@ +param( + [string]$ApiBaseUrl = "http://192.168.200.61:18121/api/v1", + [string]$ClusterID = "cfc0743d-d960-49fb-9de8-96e063d5e4aa", + [string]$ActorUserID = "f67d943f-5397-4b3a-a229-695fe67ad700", + [string]$RequestedNodeName = "test-1", + [string]$DefaultNodeName = "test-2", + [string]$MatrixNodeName = "test-1", + [string]$ResultPath = "artifacts\c19z135-remote-workspace-real-adapter-not-approved-outcome-no-continuation-guard-smoke-result.json" +) + +Set-StrictMode -Version Latest +$ErrorActionPreference = "Stop" + +$repoRoot = (Resolve-Path (Join-Path $PSScriptRoot "..\..")).ProviderPath +$sourceResultPath = "artifacts\c19z135-remote-workspace-real-adapter-not-approved-outcome-no-continuation-guard-source-result.json" +$requiredGuardFields = @( + "schema_version", + "source_sentinel_schema", + "guard_status", + "guard_marker", + "branch_state", + "continuation_policy", + "next_allowed_entrypoint", + "blocks_not_approved_extension", + "sentinel_status", + "reopen_policy", + "enablement_decision", + "operator_review_status", + "enablement_status", + "runtime_gate_state", + "runtime_effect", + "operator_default_action", + "next_required_phase", + "allows_process_start", + "allows_payload_traffic", + "guardrail_summary", + "guard_notes" +) +$requiredGuardrailFields = @("activation_blocked", "process_start_allowed", "health_probe_enabled", "payload_traffic", "allows_process_start", "allows_payload_traffic") +$requiredGuardNotes = @( + "not_approved_branch_no_continuation_guard_active", + "new_explicit_enablement_request_required", + "real_runtime_gate_not_enabled", + "process_start_disabled", + "payload_forwarding_disabled" +) + +function Get-PropertyValue { + param([object]$Item, [string]$Name, [object]$Default = $null) + if ($null -eq $Item) { return $Default } + if ($Item -is [System.Collections.IDictionary]) { + if ($Item.Contains($Name)) { return $Item[$Name] } + return $Default + } + $property = $Item.PSObject.Properties[$Name] + if ($null -eq $property) { return $Default } + return $property.Value +} + +function Test-ObjectHasFields { + param([object]$Item, [string[]]$Fields) + if ($null -eq $Item) { return $false } + foreach ($field in $Fields) { + if ($Item -is [System.Collections.IDictionary]) { + if (-not $Item.Contains($field)) { return $false } + continue + } + if ($null -eq $Item.PSObject.Properties[$field]) { return $false } + } + return $true +} + +function Test-ArrayContainsAll { + param([object[]]$Actual, [string[]]$Expected) + foreach ($item in $Expected) { + if ($Actual -notcontains $item) { return $false } + } + return $true +} + +& powershell -ExecutionPolicy Bypass -File (Join-Path $PSScriptRoot "c19z134-remote-workspace-real-adapter-not-approved-outcome-stopped-branch-sentinel-compatibility-smoke.ps1") ` + -ApiBaseUrl $ApiBaseUrl ` + -ClusterID $ClusterID ` + -ActorUserID $ActorUserID ` + -RequestedNodeName $RequestedNodeName ` + -DefaultNodeName $DefaultNodeName ` + -MatrixNodeName $MatrixNodeName ` + -ResultPath $sourceResultPath | Out-Null + +$sourceFile = Join-Path $repoRoot $sourceResultPath +$sourceResult = Get-Content -Raw -Path $sourceFile | ConvertFrom-Json +$sentinel = Get-PropertyValue -Item $sourceResult -Name "not_approved_outcome_stopped_branch_sentinel" -Default $null +$guardrails = Get-PropertyValue -Item $sentinel -Name "guardrail_summary" -Default $null + +$guard = [ordered]@{ + schema_version = "rap.remote_workspace_real_adapter_not_approved_outcome_no_continuation_guard.v1" + source_sentinel_schema = Get-PropertyValue -Item $sentinel -Name "schema_version" -Default $null + guard_status = "no_continuation_without_new_explicit_enablement_request" + guard_marker = "c19z135_real_adapter_not_approved_outcome_no_continuation_guard" + branch_state = Get-PropertyValue -Item $sentinel -Name "branch_state" -Default $null + continuation_policy = Get-PropertyValue -Item $sentinel -Name "continuation_policy" -Default $null + next_allowed_entrypoint = "new_explicit_enablement_request_only" + blocks_not_approved_extension = $true + sentinel_status = Get-PropertyValue -Item $sentinel -Name "sentinel_status" -Default $null + reopen_policy = Get-PropertyValue -Item $sentinel -Name "reopen_policy" -Default $null + enablement_decision = Get-PropertyValue -Item $sentinel -Name "enablement_decision" -Default $null + operator_review_status = Get-PropertyValue -Item $sentinel -Name "operator_review_status" -Default $null + enablement_status = Get-PropertyValue -Item $sentinel -Name "enablement_status" -Default $null + runtime_gate_state = Get-PropertyValue -Item $sentinel -Name "runtime_gate_state" -Default $null + runtime_effect = Get-PropertyValue -Item $sentinel -Name "runtime_effect" -Default $null + operator_default_action = Get-PropertyValue -Item $sentinel -Name "operator_default_action" -Default $null + next_required_phase = Get-PropertyValue -Item $sentinel -Name "next_required_phase" -Default $null + allows_process_start = $false + allows_payload_traffic = $false + guardrail_summary = $guardrails + guard_notes = $requiredGuardNotes +} + +$guardFieldsCompatible = Test-ObjectHasFields -Item $guard -Fields $requiredGuardFields +$guardValuesCompatible = ( + [string](Get-PropertyValue -Item $guard -Name "schema_version" -Default "") -eq "rap.remote_workspace_real_adapter_not_approved_outcome_no_continuation_guard.v1" -and + [string](Get-PropertyValue -Item $guard -Name "source_sentinel_schema" -Default "") -eq "rap.remote_workspace_real_adapter_not_approved_outcome_stopped_branch_sentinel.v1" -and + [string](Get-PropertyValue -Item $guard -Name "guard_status" -Default "") -eq "no_continuation_without_new_explicit_enablement_request" -and + [string](Get-PropertyValue -Item $guard -Name "guard_marker" -Default "") -eq "c19z135_real_adapter_not_approved_outcome_no_continuation_guard" -and + [string](Get-PropertyValue -Item $guard -Name "branch_state" -Default "") -eq "not_approved_branch_closed" -and + [string](Get-PropertyValue -Item $guard -Name "continuation_policy" -Default "") -eq "do_not_continue_without_new_explicit_enablement_request" -and + [string](Get-PropertyValue -Item $guard -Name "next_allowed_entrypoint" -Default "") -eq "new_explicit_enablement_request_only" -and + [bool](Get-PropertyValue -Item $guard -Name "blocks_not_approved_extension" -Default $false) -and + [string](Get-PropertyValue -Item $guard -Name "sentinel_status" -Default "") -eq "stopped_until_new_explicit_enablement_request" -and + [string](Get-PropertyValue -Item $guard -Name "reopen_policy" -Default "") -eq "new_explicit_enablement_request_required" -and + [string](Get-PropertyValue -Item $guard -Name "enablement_decision" -Default "") -eq "not_approved" -and + [string](Get-PropertyValue -Item $guard -Name "operator_review_status" -Default "") -eq "closed_without_approval" -and + [string](Get-PropertyValue -Item $guard -Name "enablement_status" -Default "") -eq "not_enabled" -and + [string](Get-PropertyValue -Item $guard -Name "runtime_gate_state" -Default "") -eq "validated_contract_only_not_enabled" -and + [string](Get-PropertyValue -Item $guard -Name "runtime_effect" -Default "") -eq "contract_only_no_runtime_enablement" -and + [string](Get-PropertyValue -Item $guard -Name "operator_default_action" -Default "") -eq "keep_real_adapter_disabled" -and + [string](Get-PropertyValue -Item $guard -Name "next_required_phase" -Default "") -eq "explicit_new_enablement_request_only" -and + -not [bool](Get-PropertyValue -Item $guard -Name "allows_process_start" -Default $true) -and + -not [bool](Get-PropertyValue -Item $guard -Name "allows_payload_traffic" -Default $true) +) +$guardNotesCompatible = Test-ArrayContainsAll -Actual @($guard.guard_notes) -Expected $requiredGuardNotes +$guardrailsCompatible = ( + (Test-ObjectHasFields -Item $guardrails -Fields $requiredGuardrailFields) -and + [bool](Get-PropertyValue -Item $guardrails -Name "activation_blocked" -Default $false) -and + -not [bool](Get-PropertyValue -Item $guardrails -Name "process_start_allowed" -Default $true) -and + -not [bool](Get-PropertyValue -Item $guardrails -Name "health_probe_enabled" -Default $true) -and + [string](Get-PropertyValue -Item $guardrails -Name "payload_traffic" -Default "") -eq "none" -and + -not [bool](Get-PropertyValue -Item $guardrails -Name "allows_process_start" -Default $true) -and + -not [bool](Get-PropertyValue -Item $guardrails -Name "allows_payload_traffic" -Default $true) +) + +$checks = [ordered]@{ + source_smoke_passed = ([bool]$sourceResult.passed) + source_schema_expected = ([string]$sourceResult.schema_version -eq "c19z134.remote_workspace_real_adapter_not_approved_outcome_stopped_branch_sentinel_compatibility_smoke.v1") + sentinel_present = ($null -ne $sentinel) + guard_fields_compatible = $guardFieldsCompatible + guard_values_compatible = $guardValuesCompatible + guard_notes_compatible = $guardNotesCompatible + guardrails_compatible = $guardrailsCompatible +} +$failed = @($checks.GetEnumerator() | Where-Object { -not $_.Value } | ForEach-Object { $_.Key }) + +$result = [ordered]@{ + schema_version = "c19z135.remote_workspace_real_adapter_not_approved_outcome_no_continuation_guard_smoke.v1" + source_result_path = $sourceFile + cluster_id = $ClusterID + required_guard_fields = $requiredGuardFields + required_guardrail_fields = $requiredGuardrailFields + required_guard_notes = $requiredGuardNotes + not_approved_outcome_no_continuation_guard = $guard + checks = $checks + failed_checks = $failed + passed = ($failed.Count -eq 0) +} + +$fullResultPath = Join-Path $repoRoot $ResultPath +$resultDir = Split-Path -Parent $fullResultPath +if ($resultDir) { New-Item -ItemType Directory -Force -Path $resultDir | Out-Null } +$result | ConvertTo-Json -Depth 100 | Set-Content -Encoding UTF8 -Path $fullResultPath + +if (-not $result.passed) { + throw "C19Z135 remote workspace real-adapter not-approved outcome no-continuation guard smoke failed. Result: $fullResultPath Failed: $($failed -join ', ')" +} + +Write-Host "C19Z135 remote workspace real-adapter not-approved outcome no-continuation guard smoke passed. Result: $fullResultPath" +$result diff --git a/scripts/fabric/c19z136-remote-workspace-real-adapter-not-approved-outcome-no-continuation-guard-compatibility-smoke.ps1 b/scripts/fabric/c19z136-remote-workspace-real-adapter-not-approved-outcome-no-continuation-guard-compatibility-smoke.ps1 new file mode 100644 index 0000000..358da2a --- /dev/null +++ b/scripts/fabric/c19z136-remote-workspace-real-adapter-not-approved-outcome-no-continuation-guard-compatibility-smoke.ps1 @@ -0,0 +1,135 @@ +param( + [string]$ApiBaseUrl = "http://192.168.200.61:18121/api/v1", + [string]$ClusterID = "cfc0743d-d960-49fb-9de8-96e063d5e4aa", + [string]$ActorUserID = "f67d943f-5397-4b3a-a229-695fe67ad700", + [string]$RequestedNodeName = "test-1", + [string]$DefaultNodeName = "test-2", + [string]$MatrixNodeName = "test-1", + [string]$ResultPath = "artifacts\c19z136-remote-workspace-real-adapter-not-approved-outcome-no-continuation-guard-compatibility-smoke-result.json" +) + +Set-StrictMode -Version Latest +$ErrorActionPreference = "Stop" + +$repoRoot = (Resolve-Path (Join-Path $PSScriptRoot "..\..")).ProviderPath +$sourceResultPath = "artifacts\c19z136-remote-workspace-real-adapter-not-approved-outcome-no-continuation-guard-source-result.json" +$requiredGuardFields = @("schema_version", "source_sentinel_schema", "guard_status", "guard_marker", "branch_state", "continuation_policy", "next_allowed_entrypoint", "blocks_not_approved_extension", "sentinel_status", "reopen_policy", "enablement_decision", "operator_review_status", "enablement_status", "runtime_gate_state", "runtime_effect", "operator_default_action", "next_required_phase", "allows_process_start", "allows_payload_traffic", "guardrail_summary", "guard_notes") +$requiredGuardrailFields = @("activation_blocked", "process_start_allowed", "health_probe_enabled", "payload_traffic", "allows_process_start", "allows_payload_traffic") +$requiredGuardNotes = @("not_approved_branch_no_continuation_guard_active", "new_explicit_enablement_request_required", "real_runtime_gate_not_enabled", "process_start_disabled", "payload_forwarding_disabled") + +function Get-PropertyValue { + param([object]$Item, [string]$Name, [object]$Default = $null) + if ($null -eq $Item) { return $Default } + if ($Item -is [System.Collections.IDictionary]) { + if ($Item.Contains($Name)) { return $Item[$Name] } + return $Default + } + $property = $Item.PSObject.Properties[$Name] + if ($null -eq $property) { return $Default } + return $property.Value +} + +function Test-ObjectHasFields { + param([object]$Item, [string[]]$Fields) + if ($null -eq $Item) { return $false } + foreach ($field in $Fields) { + if ($Item -is [System.Collections.IDictionary]) { + if (-not $Item.Contains($field)) { return $false } + continue + } + if ($null -eq $Item.PSObject.Properties[$field]) { return $false } + } + return $true +} + +function Test-ArrayContainsAll { + param([object[]]$Actual, [string[]]$Expected) + foreach ($item in $Expected) { + if ($Actual -notcontains $item) { return $false } + } + return $true +} + +& powershell -ExecutionPolicy Bypass -File (Join-Path $PSScriptRoot "c19z135-remote-workspace-real-adapter-not-approved-outcome-no-continuation-guard-smoke.ps1") ` + -ApiBaseUrl $ApiBaseUrl ` + -ClusterID $ClusterID ` + -ActorUserID $ActorUserID ` + -RequestedNodeName $RequestedNodeName ` + -DefaultNodeName $DefaultNodeName ` + -MatrixNodeName $MatrixNodeName ` + -ResultPath $sourceResultPath | Out-Null + +$sourceFile = Join-Path $repoRoot $sourceResultPath +$sourceResult = Get-Content -Raw -Path $sourceFile | ConvertFrom-Json +$guard = Get-PropertyValue -Item $sourceResult -Name "not_approved_outcome_no_continuation_guard" -Default $null +$guardrails = Get-PropertyValue -Item $guard -Name "guardrail_summary" -Default $null +$guardNotes = @(Get-PropertyValue -Item $guard -Name "guard_notes" -Default @()) + +$guardFieldsCompatible = Test-ObjectHasFields -Item $guard -Fields $requiredGuardFields +$guardValuesCompatible = ( + [string](Get-PropertyValue -Item $guard -Name "schema_version" -Default "") -eq "rap.remote_workspace_real_adapter_not_approved_outcome_no_continuation_guard.v1" -and + [string](Get-PropertyValue -Item $guard -Name "source_sentinel_schema" -Default "") -eq "rap.remote_workspace_real_adapter_not_approved_outcome_stopped_branch_sentinel.v1" -and + [string](Get-PropertyValue -Item $guard -Name "guard_status" -Default "") -eq "no_continuation_without_new_explicit_enablement_request" -and + [string](Get-PropertyValue -Item $guard -Name "guard_marker" -Default "") -eq "c19z135_real_adapter_not_approved_outcome_no_continuation_guard" -and + [string](Get-PropertyValue -Item $guard -Name "branch_state" -Default "") -eq "not_approved_branch_closed" -and + [string](Get-PropertyValue -Item $guard -Name "continuation_policy" -Default "") -eq "do_not_continue_without_new_explicit_enablement_request" -and + [string](Get-PropertyValue -Item $guard -Name "next_allowed_entrypoint" -Default "") -eq "new_explicit_enablement_request_only" -and + [bool](Get-PropertyValue -Item $guard -Name "blocks_not_approved_extension" -Default $false) -and + [string](Get-PropertyValue -Item $guard -Name "sentinel_status" -Default "") -eq "stopped_until_new_explicit_enablement_request" -and + [string](Get-PropertyValue -Item $guard -Name "reopen_policy" -Default "") -eq "new_explicit_enablement_request_required" -and + [string](Get-PropertyValue -Item $guard -Name "enablement_decision" -Default "") -eq "not_approved" -and + [string](Get-PropertyValue -Item $guard -Name "operator_review_status" -Default "") -eq "closed_without_approval" -and + [string](Get-PropertyValue -Item $guard -Name "enablement_status" -Default "") -eq "not_enabled" -and + [string](Get-PropertyValue -Item $guard -Name "runtime_gate_state" -Default "") -eq "validated_contract_only_not_enabled" -and + [string](Get-PropertyValue -Item $guard -Name "runtime_effect" -Default "") -eq "contract_only_no_runtime_enablement" -and + [string](Get-PropertyValue -Item $guard -Name "operator_default_action" -Default "") -eq "keep_real_adapter_disabled" -and + [string](Get-PropertyValue -Item $guard -Name "next_required_phase" -Default "") -eq "explicit_new_enablement_request_only" -and + -not [bool](Get-PropertyValue -Item $guard -Name "allows_process_start" -Default $true) -and + -not [bool](Get-PropertyValue -Item $guard -Name "allows_payload_traffic" -Default $true) +) +$guardNotesCompatible = Test-ArrayContainsAll -Actual $guardNotes -Expected $requiredGuardNotes +$guardrailsCompatible = ( + (Test-ObjectHasFields -Item $guardrails -Fields $requiredGuardrailFields) -and + [bool](Get-PropertyValue -Item $guardrails -Name "activation_blocked" -Default $false) -and + -not [bool](Get-PropertyValue -Item $guardrails -Name "process_start_allowed" -Default $true) -and + -not [bool](Get-PropertyValue -Item $guardrails -Name "health_probe_enabled" -Default $true) -and + [string](Get-PropertyValue -Item $guardrails -Name "payload_traffic" -Default "") -eq "none" -and + -not [bool](Get-PropertyValue -Item $guardrails -Name "allows_process_start" -Default $true) -and + -not [bool](Get-PropertyValue -Item $guardrails -Name "allows_payload_traffic" -Default $true) +) + +$checks = [ordered]@{ + source_smoke_passed = ([bool]$sourceResult.passed) + source_schema_expected = ([string]$sourceResult.schema_version -eq "c19z135.remote_workspace_real_adapter_not_approved_outcome_no_continuation_guard_smoke.v1") + guard_present = ($null -ne $guard) + guard_fields_compatible = $guardFieldsCompatible + guard_values_compatible = $guardValuesCompatible + guard_notes_compatible = $guardNotesCompatible + guardrails_compatible = $guardrailsCompatible +} +$failed = @($checks.GetEnumerator() | Where-Object { -not $_.Value } | ForEach-Object { $_.Key }) + +$result = [ordered]@{ + schema_version = "c19z136.remote_workspace_real_adapter_not_approved_outcome_no_continuation_guard_compatibility_smoke.v1" + source_result_path = $sourceFile + cluster_id = $ClusterID + required_guard_fields = $requiredGuardFields + required_guardrail_fields = $requiredGuardrailFields + required_guard_notes = $requiredGuardNotes + not_approved_outcome_no_continuation_guard = $guard + checks = $checks + failed_checks = $failed + passed = ($failed.Count -eq 0) +} + +$fullResultPath = Join-Path $repoRoot $ResultPath +$resultDir = Split-Path -Parent $fullResultPath +if ($resultDir) { New-Item -ItemType Directory -Force -Path $resultDir | Out-Null } +$result | ConvertTo-Json -Depth 100 | Set-Content -Encoding UTF8 -Path $fullResultPath + +if (-not $result.passed) { + throw "C19Z136 remote workspace real-adapter not-approved outcome no-continuation guard compatibility smoke failed. Result: $fullResultPath Failed: $($failed -join ', ')" +} + +Write-Host "C19Z136 remote workspace real-adapter not-approved outcome no-continuation guard compatibility smoke passed. Result: $fullResultPath" +$result diff --git a/scripts/fabric/c19z137-remote-workspace-real-adapter-not-approved-outcome-continuation-block-enforcement-smoke.ps1 b/scripts/fabric/c19z137-remote-workspace-real-adapter-not-approved-outcome-continuation-block-enforcement-smoke.ps1 new file mode 100644 index 0000000..c433d12 --- /dev/null +++ b/scripts/fabric/c19z137-remote-workspace-real-adapter-not-approved-outcome-continuation-block-enforcement-smoke.ps1 @@ -0,0 +1,183 @@ +param( + [string]$ApiBaseUrl = "http://192.168.200.61:18121/api/v1", + [string]$ClusterID = "cfc0743d-d960-49fb-9de8-96e063d5e4aa", + [string]$ActorUserID = "f67d943f-5397-4b3a-a229-695fe67ad700", + [string]$RequestedNodeName = "test-1", + [string]$DefaultNodeName = "test-2", + [string]$MatrixNodeName = "test-1", + [string]$ResultPath = "artifacts\c19z137-remote-workspace-real-adapter-not-approved-outcome-continuation-block-enforcement-smoke-result.json" +) + +Set-StrictMode -Version Latest +$ErrorActionPreference = "Stop" + +$repoRoot = (Resolve-Path (Join-Path $PSScriptRoot "..\..")).ProviderPath +$sourceResultPath = "artifacts\c19z137-remote-workspace-real-adapter-not-approved-outcome-continuation-block-enforcement-source-result.json" +$requiredEnforcementFields = @( + "schema_version", + "source_guard_schema", + "enforcement_status", + "enforcement_marker", + "attempted_action", + "attempt_allowed", + "block_reason", + "next_allowed_entrypoint", + "blocks_not_approved_extension", + "guard_status", + "branch_state", + "continuation_policy", + "reopen_policy", + "enablement_status", + "runtime_gate_state", + "runtime_effect", + "allows_process_start", + "allows_payload_traffic", + "guardrail_summary", + "enforcement_notes" +) +$requiredGuardrailFields = @("activation_blocked", "process_start_allowed", "health_probe_enabled", "payload_traffic", "allows_process_start", "allows_payload_traffic") +$requiredEnforcementNotes = @( + "continuation_attempt_blocked", + "not_approved_branch_remains_closed", + "new_explicit_enablement_request_required", + "real_runtime_gate_not_enabled", + "process_start_disabled", + "payload_forwarding_disabled" +) + +function Get-PropertyValue { + param([object]$Item, [string]$Name, [object]$Default = $null) + if ($null -eq $Item) { return $Default } + if ($Item -is [System.Collections.IDictionary]) { + if ($Item.Contains($Name)) { return $Item[$Name] } + return $Default + } + $property = $Item.PSObject.Properties[$Name] + if ($null -eq $property) { return $Default } + return $property.Value +} + +function Test-ObjectHasFields { + param([object]$Item, [string[]]$Fields) + if ($null -eq $Item) { return $false } + foreach ($field in $Fields) { + if ($Item -is [System.Collections.IDictionary]) { + if (-not $Item.Contains($field)) { return $false } + continue + } + if ($null -eq $Item.PSObject.Properties[$field]) { return $false } + } + return $true +} + +function Test-ArrayContainsAll { + param([object[]]$Actual, [string[]]$Expected) + foreach ($item in $Expected) { + if ($Actual -notcontains $item) { return $false } + } + return $true +} + +& powershell -ExecutionPolicy Bypass -File (Join-Path $PSScriptRoot "c19z136-remote-workspace-real-adapter-not-approved-outcome-no-continuation-guard-compatibility-smoke.ps1") ` + -ApiBaseUrl $ApiBaseUrl ` + -ClusterID $ClusterID ` + -ActorUserID $ActorUserID ` + -RequestedNodeName $RequestedNodeName ` + -DefaultNodeName $DefaultNodeName ` + -MatrixNodeName $MatrixNodeName ` + -ResultPath $sourceResultPath | Out-Null + +$sourceFile = Join-Path $repoRoot $sourceResultPath +$sourceResult = Get-Content -Raw -Path $sourceFile | ConvertFrom-Json +$guard = Get-PropertyValue -Item $sourceResult -Name "not_approved_outcome_no_continuation_guard" -Default $null +$guardrails = Get-PropertyValue -Item $guard -Name "guardrail_summary" -Default $null + +$enforcement = [ordered]@{ + schema_version = "rap.remote_workspace_real_adapter_not_approved_outcome_continuation_block_enforcement.v1" + source_guard_schema = Get-PropertyValue -Item $guard -Name "schema_version" -Default $null + enforcement_status = "blocked_continuation_enforced" + enforcement_marker = "c19z137_real_adapter_not_approved_outcome_continuation_block_enforcement" + attempted_action = "continue_not_approved_branch_without_new_explicit_enablement_request" + attempt_allowed = $false + block_reason = "new_explicit_enablement_request_required" + next_allowed_entrypoint = Get-PropertyValue -Item $guard -Name "next_allowed_entrypoint" -Default $null + blocks_not_approved_extension = Get-PropertyValue -Item $guard -Name "blocks_not_approved_extension" -Default $null + guard_status = Get-PropertyValue -Item $guard -Name "guard_status" -Default $null + branch_state = Get-PropertyValue -Item $guard -Name "branch_state" -Default $null + continuation_policy = Get-PropertyValue -Item $guard -Name "continuation_policy" -Default $null + reopen_policy = Get-PropertyValue -Item $guard -Name "reopen_policy" -Default $null + enablement_status = Get-PropertyValue -Item $guard -Name "enablement_status" -Default $null + runtime_gate_state = Get-PropertyValue -Item $guard -Name "runtime_gate_state" -Default $null + runtime_effect = Get-PropertyValue -Item $guard -Name "runtime_effect" -Default $null + allows_process_start = $false + allows_payload_traffic = $false + guardrail_summary = $guardrails + enforcement_notes = $requiredEnforcementNotes +} + +$enforcementFieldsCompatible = Test-ObjectHasFields -Item $enforcement -Fields $requiredEnforcementFields +$enforcementValuesCompatible = ( + [string](Get-PropertyValue -Item $enforcement -Name "schema_version" -Default "") -eq "rap.remote_workspace_real_adapter_not_approved_outcome_continuation_block_enforcement.v1" -and + [string](Get-PropertyValue -Item $enforcement -Name "source_guard_schema" -Default "") -eq "rap.remote_workspace_real_adapter_not_approved_outcome_no_continuation_guard.v1" -and + [string](Get-PropertyValue -Item $enforcement -Name "enforcement_status" -Default "") -eq "blocked_continuation_enforced" -and + [string](Get-PropertyValue -Item $enforcement -Name "attempted_action" -Default "") -eq "continue_not_approved_branch_without_new_explicit_enablement_request" -and + -not [bool](Get-PropertyValue -Item $enforcement -Name "attempt_allowed" -Default $true) -and + [string](Get-PropertyValue -Item $enforcement -Name "block_reason" -Default "") -eq "new_explicit_enablement_request_required" -and + [string](Get-PropertyValue -Item $enforcement -Name "next_allowed_entrypoint" -Default "") -eq "new_explicit_enablement_request_only" -and + [bool](Get-PropertyValue -Item $enforcement -Name "blocks_not_approved_extension" -Default $false) -and + [string](Get-PropertyValue -Item $enforcement -Name "guard_status" -Default "") -eq "no_continuation_without_new_explicit_enablement_request" -and + [string](Get-PropertyValue -Item $enforcement -Name "branch_state" -Default "") -eq "not_approved_branch_closed" -and + [string](Get-PropertyValue -Item $enforcement -Name "continuation_policy" -Default "") -eq "do_not_continue_without_new_explicit_enablement_request" -and + [string](Get-PropertyValue -Item $enforcement -Name "reopen_policy" -Default "") -eq "new_explicit_enablement_request_required" -and + [string](Get-PropertyValue -Item $enforcement -Name "enablement_status" -Default "") -eq "not_enabled" -and + [string](Get-PropertyValue -Item $enforcement -Name "runtime_gate_state" -Default "") -eq "validated_contract_only_not_enabled" -and + [string](Get-PropertyValue -Item $enforcement -Name "runtime_effect" -Default "") -eq "contract_only_no_runtime_enablement" -and + -not [bool](Get-PropertyValue -Item $enforcement -Name "allows_process_start" -Default $true) -and + -not [bool](Get-PropertyValue -Item $enforcement -Name "allows_payload_traffic" -Default $true) +) +$enforcementNotesCompatible = Test-ArrayContainsAll -Actual @($enforcement.enforcement_notes) -Expected $requiredEnforcementNotes +$guardrailsCompatible = ( + (Test-ObjectHasFields -Item $guardrails -Fields $requiredGuardrailFields) -and + [bool](Get-PropertyValue -Item $guardrails -Name "activation_blocked" -Default $false) -and + -not [bool](Get-PropertyValue -Item $guardrails -Name "process_start_allowed" -Default $true) -and + -not [bool](Get-PropertyValue -Item $guardrails -Name "health_probe_enabled" -Default $true) -and + [string](Get-PropertyValue -Item $guardrails -Name "payload_traffic" -Default "") -eq "none" -and + -not [bool](Get-PropertyValue -Item $guardrails -Name "allows_process_start" -Default $true) -and + -not [bool](Get-PropertyValue -Item $guardrails -Name "allows_payload_traffic" -Default $true) +) + +$checks = [ordered]@{ + source_smoke_passed = ([bool]$sourceResult.passed) + source_schema_expected = ([string]$sourceResult.schema_version -eq "c19z136.remote_workspace_real_adapter_not_approved_outcome_no_continuation_guard_compatibility_smoke.v1") + guard_present = ($null -ne $guard) + enforcement_fields_compatible = $enforcementFieldsCompatible + enforcement_values_compatible = $enforcementValuesCompatible + enforcement_notes_compatible = $enforcementNotesCompatible + guardrails_compatible = $guardrailsCompatible +} +$failed = @($checks.GetEnumerator() | Where-Object { -not $_.Value } | ForEach-Object { $_.Key }) + +$result = [ordered]@{ + schema_version = "c19z137.remote_workspace_real_adapter_not_approved_outcome_continuation_block_enforcement_smoke.v1" + source_result_path = $sourceFile + cluster_id = $ClusterID + required_enforcement_fields = $requiredEnforcementFields + required_guardrail_fields = $requiredGuardrailFields + required_enforcement_notes = $requiredEnforcementNotes + not_approved_outcome_continuation_block_enforcement = $enforcement + checks = $checks + failed_checks = $failed + passed = ($failed.Count -eq 0) +} + +$fullResultPath = Join-Path $repoRoot $ResultPath +$resultDir = Split-Path -Parent $fullResultPath +if ($resultDir) { New-Item -ItemType Directory -Force -Path $resultDir | Out-Null } +$result | ConvertTo-Json -Depth 100 | Set-Content -Encoding UTF8 -Path $fullResultPath + +if (-not $result.passed) { + throw "C19Z137 remote workspace real-adapter not-approved outcome continuation block enforcement smoke failed. Result: $fullResultPath Failed: $($failed -join ', ')" +} + +Write-Host "C19Z137 remote workspace real-adapter not-approved outcome continuation block enforcement smoke passed. Result: $fullResultPath" +$result diff --git a/scripts/fabric/c19z138-remote-workspace-real-adapter-not-approved-outcome-continuation-block-enforcement-compatibility-smoke.ps1 b/scripts/fabric/c19z138-remote-workspace-real-adapter-not-approved-outcome-continuation-block-enforcement-compatibility-smoke.ps1 new file mode 100644 index 0000000..c6f4a0d --- /dev/null +++ b/scripts/fabric/c19z138-remote-workspace-real-adapter-not-approved-outcome-continuation-block-enforcement-compatibility-smoke.ps1 @@ -0,0 +1,133 @@ +param( + [string]$ApiBaseUrl = "http://192.168.200.61:18121/api/v1", + [string]$ClusterID = "cfc0743d-d960-49fb-9de8-96e063d5e4aa", + [string]$ActorUserID = "f67d943f-5397-4b3a-a229-695fe67ad700", + [string]$RequestedNodeName = "test-1", + [string]$DefaultNodeName = "test-2", + [string]$MatrixNodeName = "test-1", + [string]$ResultPath = "artifacts\c19z138-remote-workspace-real-adapter-not-approved-outcome-continuation-block-enforcement-compatibility-smoke-result.json" +) + +Set-StrictMode -Version Latest +$ErrorActionPreference = "Stop" + +$repoRoot = (Resolve-Path (Join-Path $PSScriptRoot "..\..")).ProviderPath +$sourceResultPath = "artifacts\c19z138-remote-workspace-real-adapter-not-approved-outcome-continuation-block-enforcement-source-result.json" +$requiredEnforcementFields = @("schema_version", "source_guard_schema", "enforcement_status", "enforcement_marker", "attempted_action", "attempt_allowed", "block_reason", "next_allowed_entrypoint", "blocks_not_approved_extension", "guard_status", "branch_state", "continuation_policy", "reopen_policy", "enablement_status", "runtime_gate_state", "runtime_effect", "allows_process_start", "allows_payload_traffic", "guardrail_summary", "enforcement_notes") +$requiredGuardrailFields = @("activation_blocked", "process_start_allowed", "health_probe_enabled", "payload_traffic", "allows_process_start", "allows_payload_traffic") +$requiredEnforcementNotes = @("continuation_attempt_blocked", "not_approved_branch_remains_closed", "new_explicit_enablement_request_required", "real_runtime_gate_not_enabled", "process_start_disabled", "payload_forwarding_disabled") + +function Get-PropertyValue { + param([object]$Item, [string]$Name, [object]$Default = $null) + if ($null -eq $Item) { return $Default } + if ($Item -is [System.Collections.IDictionary]) { + if ($Item.Contains($Name)) { return $Item[$Name] } + return $Default + } + $property = $Item.PSObject.Properties[$Name] + if ($null -eq $property) { return $Default } + return $property.Value +} + +function Test-ObjectHasFields { + param([object]$Item, [string[]]$Fields) + if ($null -eq $Item) { return $false } + foreach ($field in $Fields) { + if ($Item -is [System.Collections.IDictionary]) { + if (-not $Item.Contains($field)) { return $false } + continue + } + if ($null -eq $Item.PSObject.Properties[$field]) { return $false } + } + return $true +} + +function Test-ArrayContainsAll { + param([object[]]$Actual, [string[]]$Expected) + foreach ($item in $Expected) { + if ($Actual -notcontains $item) { return $false } + } + return $true +} + +& powershell -ExecutionPolicy Bypass -File (Join-Path $PSScriptRoot "c19z137-remote-workspace-real-adapter-not-approved-outcome-continuation-block-enforcement-smoke.ps1") ` + -ApiBaseUrl $ApiBaseUrl ` + -ClusterID $ClusterID ` + -ActorUserID $ActorUserID ` + -RequestedNodeName $RequestedNodeName ` + -DefaultNodeName $DefaultNodeName ` + -MatrixNodeName $MatrixNodeName ` + -ResultPath $sourceResultPath | Out-Null + +$sourceFile = Join-Path $repoRoot $sourceResultPath +$sourceResult = Get-Content -Raw -Path $sourceFile | ConvertFrom-Json +$enforcement = Get-PropertyValue -Item $sourceResult -Name "not_approved_outcome_continuation_block_enforcement" -Default $null +$guardrails = Get-PropertyValue -Item $enforcement -Name "guardrail_summary" -Default $null +$enforcementNotes = @(Get-PropertyValue -Item $enforcement -Name "enforcement_notes" -Default @()) + +$enforcementFieldsCompatible = Test-ObjectHasFields -Item $enforcement -Fields $requiredEnforcementFields +$enforcementValuesCompatible = ( + [string](Get-PropertyValue -Item $enforcement -Name "schema_version" -Default "") -eq "rap.remote_workspace_real_adapter_not_approved_outcome_continuation_block_enforcement.v1" -and + [string](Get-PropertyValue -Item $enforcement -Name "source_guard_schema" -Default "") -eq "rap.remote_workspace_real_adapter_not_approved_outcome_no_continuation_guard.v1" -and + [string](Get-PropertyValue -Item $enforcement -Name "enforcement_status" -Default "") -eq "blocked_continuation_enforced" -and + [string](Get-PropertyValue -Item $enforcement -Name "attempted_action" -Default "") -eq "continue_not_approved_branch_without_new_explicit_enablement_request" -and + -not [bool](Get-PropertyValue -Item $enforcement -Name "attempt_allowed" -Default $true) -and + [string](Get-PropertyValue -Item $enforcement -Name "block_reason" -Default "") -eq "new_explicit_enablement_request_required" -and + [string](Get-PropertyValue -Item $enforcement -Name "next_allowed_entrypoint" -Default "") -eq "new_explicit_enablement_request_only" -and + [bool](Get-PropertyValue -Item $enforcement -Name "blocks_not_approved_extension" -Default $false) -and + [string](Get-PropertyValue -Item $enforcement -Name "guard_status" -Default "") -eq "no_continuation_without_new_explicit_enablement_request" -and + [string](Get-PropertyValue -Item $enforcement -Name "branch_state" -Default "") -eq "not_approved_branch_closed" -and + [string](Get-PropertyValue -Item $enforcement -Name "continuation_policy" -Default "") -eq "do_not_continue_without_new_explicit_enablement_request" -and + [string](Get-PropertyValue -Item $enforcement -Name "reopen_policy" -Default "") -eq "new_explicit_enablement_request_required" -and + [string](Get-PropertyValue -Item $enforcement -Name "enablement_status" -Default "") -eq "not_enabled" -and + [string](Get-PropertyValue -Item $enforcement -Name "runtime_gate_state" -Default "") -eq "validated_contract_only_not_enabled" -and + [string](Get-PropertyValue -Item $enforcement -Name "runtime_effect" -Default "") -eq "contract_only_no_runtime_enablement" -and + -not [bool](Get-PropertyValue -Item $enforcement -Name "allows_process_start" -Default $true) -and + -not [bool](Get-PropertyValue -Item $enforcement -Name "allows_payload_traffic" -Default $true) +) +$enforcementNotesCompatible = Test-ArrayContainsAll -Actual $enforcementNotes -Expected $requiredEnforcementNotes +$guardrailsCompatible = ( + (Test-ObjectHasFields -Item $guardrails -Fields $requiredGuardrailFields) -and + [bool](Get-PropertyValue -Item $guardrails -Name "activation_blocked" -Default $false) -and + -not [bool](Get-PropertyValue -Item $guardrails -Name "process_start_allowed" -Default $true) -and + -not [bool](Get-PropertyValue -Item $guardrails -Name "health_probe_enabled" -Default $true) -and + [string](Get-PropertyValue -Item $guardrails -Name "payload_traffic" -Default "") -eq "none" -and + -not [bool](Get-PropertyValue -Item $guardrails -Name "allows_process_start" -Default $true) -and + -not [bool](Get-PropertyValue -Item $guardrails -Name "allows_payload_traffic" -Default $true) +) + +$checks = [ordered]@{ + source_smoke_passed = ([bool]$sourceResult.passed) + source_schema_expected = ([string]$sourceResult.schema_version -eq "c19z137.remote_workspace_real_adapter_not_approved_outcome_continuation_block_enforcement_smoke.v1") + enforcement_present = ($null -ne $enforcement) + enforcement_fields_compatible = $enforcementFieldsCompatible + enforcement_values_compatible = $enforcementValuesCompatible + enforcement_notes_compatible = $enforcementNotesCompatible + guardrails_compatible = $guardrailsCompatible +} +$failed = @($checks.GetEnumerator() | Where-Object { -not $_.Value } | ForEach-Object { $_.Key }) + +$result = [ordered]@{ + schema_version = "c19z138.remote_workspace_real_adapter_not_approved_outcome_continuation_block_enforcement_compatibility_smoke.v1" + source_result_path = $sourceFile + cluster_id = $ClusterID + required_enforcement_fields = $requiredEnforcementFields + required_guardrail_fields = $requiredGuardrailFields + required_enforcement_notes = $requiredEnforcementNotes + not_approved_outcome_continuation_block_enforcement = $enforcement + checks = $checks + failed_checks = $failed + passed = ($failed.Count -eq 0) +} + +$fullResultPath = Join-Path $repoRoot $ResultPath +$resultDir = Split-Path -Parent $fullResultPath +if ($resultDir) { New-Item -ItemType Directory -Force -Path $resultDir | Out-Null } +$result | ConvertTo-Json -Depth 100 | Set-Content -Encoding UTF8 -Path $fullResultPath + +if (-not $result.passed) { + throw "C19Z138 remote workspace real-adapter not-approved outcome continuation block enforcement compatibility smoke failed. Result: $fullResultPath Failed: $($failed -join ', ')" +} + +Write-Host "C19Z138 remote workspace real-adapter not-approved outcome continuation block enforcement compatibility smoke passed. Result: $fullResultPath" +$result diff --git a/scripts/fabric/c19z139-remote-workspace-real-adapter-not-approved-outcome-continuation-block-audit-record-smoke.ps1 b/scripts/fabric/c19z139-remote-workspace-real-adapter-not-approved-outcome-continuation-block-audit-record-smoke.ps1 new file mode 100644 index 0000000..941cb60 --- /dev/null +++ b/scripts/fabric/c19z139-remote-workspace-real-adapter-not-approved-outcome-continuation-block-audit-record-smoke.ps1 @@ -0,0 +1,180 @@ +param( + [string]$ApiBaseUrl = "http://192.168.200.61:18121/api/v1", + [string]$ClusterID = "cfc0743d-d960-49fb-9de8-96e063d5e4aa", + [string]$ActorUserID = "f67d943f-5397-4b3a-a229-695fe67ad700", + [string]$RequestedNodeName = "test-1", + [string]$DefaultNodeName = "test-2", + [string]$MatrixNodeName = "test-1", + [string]$ResultPath = "artifacts\c19z139-remote-workspace-real-adapter-not-approved-outcome-continuation-block-audit-record-smoke-result.json" +) + +Set-StrictMode -Version Latest +$ErrorActionPreference = "Stop" + +$repoRoot = (Resolve-Path (Join-Path $PSScriptRoot "..\..")).ProviderPath +$sourceResultPath = "artifacts\c19z139-remote-workspace-real-adapter-not-approved-outcome-continuation-block-audit-record-source-result.json" +$requiredAuditFields = @( + "schema_version", + "source_enforcement_schema", + "audit_status", + "audit_marker", + "audit_event_type", + "attempted_action", + "attempt_allowed", + "block_reason", + "next_allowed_entrypoint", + "blocks_not_approved_extension", + "branch_state", + "continuation_policy", + "enablement_status", + "runtime_gate_state", + "runtime_effect", + "allows_process_start", + "allows_payload_traffic", + "guardrail_summary", + "audit_notes" +) +$requiredGuardrailFields = @("activation_blocked", "process_start_allowed", "health_probe_enabled", "payload_traffic", "allows_process_start", "allows_payload_traffic") +$requiredAuditNotes = @( + "blocked_continuation_audit_recorded", + "not_approved_branch_remains_closed", + "new_explicit_enablement_request_required", + "real_runtime_gate_not_enabled", + "process_start_disabled", + "payload_forwarding_disabled" +) + +function Get-PropertyValue { + param([object]$Item, [string]$Name, [object]$Default = $null) + if ($null -eq $Item) { return $Default } + if ($Item -is [System.Collections.IDictionary]) { + if ($Item.Contains($Name)) { return $Item[$Name] } + return $Default + } + $property = $Item.PSObject.Properties[$Name] + if ($null -eq $property) { return $Default } + return $property.Value +} + +function Test-ObjectHasFields { + param([object]$Item, [string[]]$Fields) + if ($null -eq $Item) { return $false } + foreach ($field in $Fields) { + if ($Item -is [System.Collections.IDictionary]) { + if (-not $Item.Contains($field)) { return $false } + continue + } + if ($null -eq $Item.PSObject.Properties[$field]) { return $false } + } + return $true +} + +function Test-ArrayContainsAll { + param([object[]]$Actual, [string[]]$Expected) + foreach ($item in $Expected) { + if ($Actual -notcontains $item) { return $false } + } + return $true +} + +& powershell -ExecutionPolicy Bypass -File (Join-Path $PSScriptRoot "c19z138-remote-workspace-real-adapter-not-approved-outcome-continuation-block-enforcement-compatibility-smoke.ps1") ` + -ApiBaseUrl $ApiBaseUrl ` + -ClusterID $ClusterID ` + -ActorUserID $ActorUserID ` + -RequestedNodeName $RequestedNodeName ` + -DefaultNodeName $DefaultNodeName ` + -MatrixNodeName $MatrixNodeName ` + -ResultPath $sourceResultPath | Out-Null + +$sourceFile = Join-Path $repoRoot $sourceResultPath +$sourceResult = Get-Content -Raw -Path $sourceFile | ConvertFrom-Json +$enforcement = Get-PropertyValue -Item $sourceResult -Name "not_approved_outcome_continuation_block_enforcement" -Default $null +$guardrails = Get-PropertyValue -Item $enforcement -Name "guardrail_summary" -Default $null + +$audit = [ordered]@{ + schema_version = "rap.remote_workspace_real_adapter_not_approved_outcome_continuation_block_audit_record.v1" + source_enforcement_schema = Get-PropertyValue -Item $enforcement -Name "schema_version" -Default $null + audit_status = "blocked_continuation_audit_recorded" + audit_marker = "c19z139_real_adapter_not_approved_outcome_continuation_block_audit_record" + audit_event_type = "not_approved_continuation_block" + attempted_action = Get-PropertyValue -Item $enforcement -Name "attempted_action" -Default $null + attempt_allowed = $false + block_reason = Get-PropertyValue -Item $enforcement -Name "block_reason" -Default $null + next_allowed_entrypoint = Get-PropertyValue -Item $enforcement -Name "next_allowed_entrypoint" -Default $null + blocks_not_approved_extension = Get-PropertyValue -Item $enforcement -Name "blocks_not_approved_extension" -Default $null + branch_state = Get-PropertyValue -Item $enforcement -Name "branch_state" -Default $null + continuation_policy = Get-PropertyValue -Item $enforcement -Name "continuation_policy" -Default $null + enablement_status = Get-PropertyValue -Item $enforcement -Name "enablement_status" -Default $null + runtime_gate_state = Get-PropertyValue -Item $enforcement -Name "runtime_gate_state" -Default $null + runtime_effect = Get-PropertyValue -Item $enforcement -Name "runtime_effect" -Default $null + allows_process_start = $false + allows_payload_traffic = $false + guardrail_summary = $guardrails + audit_notes = $requiredAuditNotes +} + +$auditFieldsCompatible = Test-ObjectHasFields -Item $audit -Fields $requiredAuditFields +$auditValuesCompatible = ( + [string](Get-PropertyValue -Item $audit -Name "schema_version" -Default "") -eq "rap.remote_workspace_real_adapter_not_approved_outcome_continuation_block_audit_record.v1" -and + [string](Get-PropertyValue -Item $audit -Name "source_enforcement_schema" -Default "") -eq "rap.remote_workspace_real_adapter_not_approved_outcome_continuation_block_enforcement.v1" -and + [string](Get-PropertyValue -Item $audit -Name "audit_status" -Default "") -eq "blocked_continuation_audit_recorded" -and + [string](Get-PropertyValue -Item $audit -Name "audit_event_type" -Default "") -eq "not_approved_continuation_block" -and + [string](Get-PropertyValue -Item $audit -Name "attempted_action" -Default "") -eq "continue_not_approved_branch_without_new_explicit_enablement_request" -and + -not [bool](Get-PropertyValue -Item $audit -Name "attempt_allowed" -Default $true) -and + [string](Get-PropertyValue -Item $audit -Name "block_reason" -Default "") -eq "new_explicit_enablement_request_required" -and + [string](Get-PropertyValue -Item $audit -Name "next_allowed_entrypoint" -Default "") -eq "new_explicit_enablement_request_only" -and + [bool](Get-PropertyValue -Item $audit -Name "blocks_not_approved_extension" -Default $false) -and + [string](Get-PropertyValue -Item $audit -Name "branch_state" -Default "") -eq "not_approved_branch_closed" -and + [string](Get-PropertyValue -Item $audit -Name "continuation_policy" -Default "") -eq "do_not_continue_without_new_explicit_enablement_request" -and + [string](Get-PropertyValue -Item $audit -Name "enablement_status" -Default "") -eq "not_enabled" -and + [string](Get-PropertyValue -Item $audit -Name "runtime_gate_state" -Default "") -eq "validated_contract_only_not_enabled" -and + [string](Get-PropertyValue -Item $audit -Name "runtime_effect" -Default "") -eq "contract_only_no_runtime_enablement" -and + -not [bool](Get-PropertyValue -Item $audit -Name "allows_process_start" -Default $true) -and + -not [bool](Get-PropertyValue -Item $audit -Name "allows_payload_traffic" -Default $true) +) +$auditNotesCompatible = Test-ArrayContainsAll -Actual @($audit.audit_notes) -Expected $requiredAuditNotes +$guardrailsCompatible = ( + (Test-ObjectHasFields -Item $guardrails -Fields $requiredGuardrailFields) -and + [bool](Get-PropertyValue -Item $guardrails -Name "activation_blocked" -Default $false) -and + -not [bool](Get-PropertyValue -Item $guardrails -Name "process_start_allowed" -Default $true) -and + -not [bool](Get-PropertyValue -Item $guardrails -Name "health_probe_enabled" -Default $true) -and + [string](Get-PropertyValue -Item $guardrails -Name "payload_traffic" -Default "") -eq "none" -and + -not [bool](Get-PropertyValue -Item $guardrails -Name "allows_process_start" -Default $true) -and + -not [bool](Get-PropertyValue -Item $guardrails -Name "allows_payload_traffic" -Default $true) +) + +$checks = [ordered]@{ + source_smoke_passed = ([bool]$sourceResult.passed) + source_schema_expected = ([string]$sourceResult.schema_version -eq "c19z138.remote_workspace_real_adapter_not_approved_outcome_continuation_block_enforcement_compatibility_smoke.v1") + enforcement_present = ($null -ne $enforcement) + audit_fields_compatible = $auditFieldsCompatible + audit_values_compatible = $auditValuesCompatible + audit_notes_compatible = $auditNotesCompatible + guardrails_compatible = $guardrailsCompatible +} +$failed = @($checks.GetEnumerator() | Where-Object { -not $_.Value } | ForEach-Object { $_.Key }) + +$result = [ordered]@{ + schema_version = "c19z139.remote_workspace_real_adapter_not_approved_outcome_continuation_block_audit_record_smoke.v1" + source_result_path = $sourceFile + cluster_id = $ClusterID + required_audit_fields = $requiredAuditFields + required_guardrail_fields = $requiredGuardrailFields + required_audit_notes = $requiredAuditNotes + not_approved_outcome_continuation_block_audit_record = $audit + checks = $checks + failed_checks = $failed + passed = ($failed.Count -eq 0) +} + +$fullResultPath = Join-Path $repoRoot $ResultPath +$resultDir = Split-Path -Parent $fullResultPath +if ($resultDir) { New-Item -ItemType Directory -Force -Path $resultDir | Out-Null } +$result | ConvertTo-Json -Depth 100 | Set-Content -Encoding UTF8 -Path $fullResultPath + +if (-not $result.passed) { + throw "C19Z139 remote workspace real-adapter not-approved outcome continuation block audit record smoke failed. Result: $fullResultPath Failed: $($failed -join ', ')" +} + +Write-Host "C19Z139 remote workspace real-adapter not-approved outcome continuation block audit record smoke passed. Result: $fullResultPath" +$result diff --git a/scripts/fabric/c19z14-remote-workspace-mailbox-preflight-repeated-attention-smoke.ps1 b/scripts/fabric/c19z14-remote-workspace-mailbox-preflight-repeated-attention-smoke.ps1 new file mode 100644 index 0000000..d6046fd --- /dev/null +++ b/scripts/fabric/c19z14-remote-workspace-mailbox-preflight-repeated-attention-smoke.ps1 @@ -0,0 +1,170 @@ +param( + [string]$ApiBaseUrl = "http://192.168.200.61:18121/api/v1", + [string]$ClusterID = "cfc0743d-d960-49fb-9de8-96e063d5e4aa", + [string]$ActorUserID = "f67d943f-5397-4b3a-a229-695fe67ad700", + [string]$EntryNodeName = "test-1", + [string]$ExitNodeName = "test-2", + [string]$EntryBaseUrl = "http://192.168.200.61:19131", + [string]$ResultPath = "artifacts\c19z14-remote-workspace-mailbox-preflight-repeated-attention-smoke-result.json" +) + +Set-StrictMode -Version Latest +$ErrorActionPreference = "Stop" + +$repoRoot = (Resolve-Path (Join-Path $PSScriptRoot "..\..")).ProviderPath +$sourceResultPath = "artifacts\c19z14-remote-workspace-mailbox-preflight-repeated-attention-source-result.json" +$adapterSessionID = "" + +function Invoke-Api { + param([string]$Method, [string]$Path, [object]$Body = $null) + $uri = "$ApiBaseUrl$Path" + if ($null -eq $Body) { return Invoke-RestMethod -Method $Method -Uri $uri -TimeoutSec 30 } + return Invoke-RestMethod -Method $Method -Uri $uri -ContentType "application/json" -Body ($Body | ConvertTo-Json -Depth 80) -TimeoutSec 30 +} + +function Get-PropertyValue { + param([object]$Item, [string]$Name, [object]$Default = $null) + if ($null -eq $Item) { return $Default } + $property = $Item.PSObject.Properties[$Name] + if ($null -eq $property) { return $Default } + return $property.Value +} + +function Get-NodeByName { + param([string]$Name) + $nodes = (Invoke-Api -Method GET -Path "/clusters/$ClusterID/nodes?actor_user_id=$ActorUserID").nodes + $node = @($nodes | Where-Object { $_.name -eq $Name }) | Select-Object -First 1 + if ($null -eq $node) { throw "Node '$Name' was not found in cluster $ClusterID" } + return $node +} + +function Get-RemoteWorkspaceSinkReports { + param([string]$NodeID) + $statuses = (Invoke-Api -Method GET -Path "/clusters/$ClusterID/nodes/$NodeID/workloads/status?actor_user_id=$ActorUserID").workload_statuses + $workloadStatus = @($statuses | Where-Object { $_.service_type -eq "rdp-worker" } | Select-Object -First 1) + $workloadSink = $null + if ($null -ne $workloadStatus) { + $workloadSink = Get-PropertyValue -Item $workloadStatus.status_payload -Name "remote_workspace_adapter_sink" -Default $null + } + $telemetryItems = @((Invoke-Api -Method GET -Path "/clusters/$ClusterID/nodes/$NodeID/telemetry?actor_user_id=$ActorUserID&limit=10").telemetry) + $telemetry = $telemetryItems | Select-Object -First 1 + $telemetrySink = $null + if ($null -ne $telemetry) { + $telemetryPayload = Get-PropertyValue -Item $telemetry -Name "payload" -Default $null + $telemetrySink = Get-PropertyValue -Item $telemetryPayload -Name "remote_workspace_adapter_sink_report" -Default $null + } + return [ordered]@{ + workload_status = $workloadStatus + workload_sink = $workloadSink + telemetry = $telemetry + telemetry_sink = $telemetrySink + } +} + +function Invoke-Preflight { + param([string]$SessionID) + $url = "$EntryBaseUrl/mesh/v1/remote-workspace/adapter-sessions/$SessionID/mailbox/preflight?consumer_id=rdp-worker-probe&resume_from=ack&limit=3" + $response = Invoke-WebRequest -Method GET -Uri $url -TimeoutSec 35 + $json = $null + if ($response.Content) { $json = $response.Content | ConvertFrom-Json } + return [ordered]@{ status_code = [int]$response.StatusCode; body = $response.Content; json = $json } +} + +function Invoke-Control { + param([string]$SessionID) + if ([string]::IsNullOrWhiteSpace($SessionID)) { return $null } + $url = "$EntryBaseUrl/mesh/v1/remote-workspace/adapter-sessions/$SessionID/control" + $body = @{ action = "close"; reason = "c19z14 repeated preflight close" } | ConvertTo-Json -Compress + return Invoke-RestMethod -Method POST -Uri $url -ContentType "application/json" -Body $body -TimeoutSec 30 +} + +function Test-RepeatedAttention { + param([object]$Sink, [string]$SessionID) + if ($null -eq $Sink) { return $false } + $readiness = Get-PropertyValue -Item $Sink -Name "adapter_runtime_readiness" -Default $null + $rollup = Get-PropertyValue -Item $readiness -Name "last_preflight" -Default $null + if ($null -eq $rollup) { return $false } + $statusCounts = Get-PropertyValue -Item $rollup -Name "operator_status_counts" -Default $null + $severityCounts = Get-PropertyValue -Item $rollup -Name "operator_severity_counts" -Default $null + return ( + [string](Get-PropertyValue -Item $readiness -Name "adapter_session_id" -Default "") -eq $SessionID -and + [string](Get-PropertyValue -Item $readiness -Name "preflight_attention_status" -Default "") -eq "repeated_resync_required" -and + [string](Get-PropertyValue -Item $rollup -Name "preflight_attention_status" -Default "") -eq "repeated_resync_required" -and + [int64](Get-PropertyValue -Item $statusCounts -Name "resync_required" -Default 0) -ge 2 -and + [int64](Get-PropertyValue -Item $severityCounts -Name "warn" -Default 0) -ge 2 + ) +} + +try { + $entryNode = Get-NodeByName -Name $EntryNodeName + $source = & powershell -ExecutionPolicy Bypass -File (Join-Path $PSScriptRoot "c19z3-remote-workspace-mailbox-stale-preflight-smoke.ps1") ` + -ApiBaseUrl $ApiBaseUrl ` + -ClusterID $ClusterID ` + -ActorUserID $ActorUserID ` + -EntryNodeName $EntryNodeName ` + -ExitNodeName $ExitNodeName ` + -EntryBaseUrl $EntryBaseUrl ` + -ResultPath $sourceResultPath ` + -SkipClose + + $sourceFile = Join-Path $repoRoot $sourceResultPath + $sourceResult = Get-Content -Raw -Path $sourceFile | ConvertFrom-Json + $adapterSessionID = [string](Get-PropertyValue -Item $sourceResult -Name "adapter_session_id" -Default "") + $secondPreflight = Invoke-Preflight -SessionID $adapterSessionID + + $observed = $null + $deadline = (Get-Date).AddSeconds(90) + while ((Get-Date) -lt $deadline) { + Start-Sleep -Seconds 5 + $observed = Get-RemoteWorkspaceSinkReports -NodeID $entryNode.id + if ( + (Test-RepeatedAttention -Sink $observed.workload_sink -SessionID $adapterSessionID) -and + (Test-RepeatedAttention -Sink $observed.telemetry_sink -SessionID $adapterSessionID) + ) { + break + } + } + if ($null -eq $observed) { + $observed = Get-RemoteWorkspaceSinkReports -NodeID $entryNode.id + } + $control = Invoke-Control -SessionID $adapterSessionID + + $checks = [ordered]@{ + source_smoke_passed = ([bool]$sourceResult.passed) + adapter_session_id_present = ($adapterSessionID -match "^rap-rw-adapter-session-[0-9a-f]{24}$") + second_preflight_stale = ([int]$secondPreflight.status_code -eq 200 -and [string]$secondPreflight.json.operator_status -eq "resync_required" -and [string]$secondPreflight.json.operator_severity -eq "warn") + workload_repeated_attention_visible = (Test-RepeatedAttention -Sink $observed.workload_sink -SessionID $adapterSessionID) + telemetry_repeated_attention_visible = (Test-RepeatedAttention -Sink $observed.telemetry_sink -SessionID $adapterSessionID) + close_accepted = ([bool]$control.accepted -and [string]$control.session_state -eq "closed") + } + $failed = @($checks.GetEnumerator() | Where-Object { -not $_.Value } | ForEach-Object { $_.Key }) + + $result = [ordered]@{ + schema_version = "c19z14.remote_workspace_mailbox_preflight_repeated_attention_smoke.v1" + source_result_path = $sourceFile + cluster_id = $ClusterID + adapter_session_id = $adapterSessionID + second_preflight = $secondPreflight + observed = $observed + control = $control + checks = $checks + failed_checks = $failed + passed = ($failed.Count -eq 0) + } +} finally { + if ($adapterSessionID) { + try { [void](Invoke-Control -SessionID $adapterSessionID) } catch {} + } +} + +$fullResultPath = Join-Path $repoRoot $ResultPath +$resultDir = Split-Path -Parent $fullResultPath +if ($resultDir) { New-Item -ItemType Directory -Force -Path $resultDir | Out-Null } +$result | ConvertTo-Json -Depth 100 | Set-Content -Encoding UTF8 -Path $fullResultPath + +if (-not $result.passed) { + throw "C19Z14 remote workspace mailbox preflight repeated-attention smoke failed. Result: $fullResultPath Failed: $($failed -join ', ')" +} + +Write-Host "C19Z14 remote workspace mailbox preflight repeated-attention smoke passed. Result: $fullResultPath" +$result diff --git a/scripts/fabric/c19z140-remote-workspace-real-adapter-not-approved-outcome-continuation-block-audit-record-compatibility-smoke.ps1 b/scripts/fabric/c19z140-remote-workspace-real-adapter-not-approved-outcome-continuation-block-audit-record-compatibility-smoke.ps1 new file mode 100644 index 0000000..185f281 --- /dev/null +++ b/scripts/fabric/c19z140-remote-workspace-real-adapter-not-approved-outcome-continuation-block-audit-record-compatibility-smoke.ps1 @@ -0,0 +1,132 @@ +param( + [string]$ApiBaseUrl = "http://192.168.200.61:18121/api/v1", + [string]$ClusterID = "cfc0743d-d960-49fb-9de8-96e063d5e4aa", + [string]$ActorUserID = "f67d943f-5397-4b3a-a229-695fe67ad700", + [string]$RequestedNodeName = "test-1", + [string]$DefaultNodeName = "test-2", + [string]$MatrixNodeName = "test-1", + [string]$ResultPath = "artifacts\c19z140-remote-workspace-real-adapter-not-approved-outcome-continuation-block-audit-record-compatibility-smoke-result.json" +) + +Set-StrictMode -Version Latest +$ErrorActionPreference = "Stop" + +$repoRoot = (Resolve-Path (Join-Path $PSScriptRoot "..\..")).ProviderPath +$sourceResultPath = "artifacts\c19z140-remote-workspace-real-adapter-not-approved-outcome-continuation-block-audit-record-source-result.json" +$requiredAuditFields = @("schema_version", "source_enforcement_schema", "audit_status", "audit_marker", "audit_event_type", "attempted_action", "attempt_allowed", "block_reason", "next_allowed_entrypoint", "blocks_not_approved_extension", "branch_state", "continuation_policy", "enablement_status", "runtime_gate_state", "runtime_effect", "allows_process_start", "allows_payload_traffic", "guardrail_summary", "audit_notes") +$requiredGuardrailFields = @("activation_blocked", "process_start_allowed", "health_probe_enabled", "payload_traffic", "allows_process_start", "allows_payload_traffic") +$requiredAuditNotes = @("blocked_continuation_audit_recorded", "not_approved_branch_remains_closed", "new_explicit_enablement_request_required", "real_runtime_gate_not_enabled", "process_start_disabled", "payload_forwarding_disabled") + +function Get-PropertyValue { + param([object]$Item, [string]$Name, [object]$Default = $null) + if ($null -eq $Item) { return $Default } + if ($Item -is [System.Collections.IDictionary]) { + if ($Item.Contains($Name)) { return $Item[$Name] } + return $Default + } + $property = $Item.PSObject.Properties[$Name] + if ($null -eq $property) { return $Default } + return $property.Value +} + +function Test-ObjectHasFields { + param([object]$Item, [string[]]$Fields) + if ($null -eq $Item) { return $false } + foreach ($field in $Fields) { + if ($Item -is [System.Collections.IDictionary]) { + if (-not $Item.Contains($field)) { return $false } + continue + } + if ($null -eq $Item.PSObject.Properties[$field]) { return $false } + } + return $true +} + +function Test-ArrayContainsAll { + param([object[]]$Actual, [string[]]$Expected) + foreach ($item in $Expected) { + if ($Actual -notcontains $item) { return $false } + } + return $true +} + +& powershell -ExecutionPolicy Bypass -File (Join-Path $PSScriptRoot "c19z139-remote-workspace-real-adapter-not-approved-outcome-continuation-block-audit-record-smoke.ps1") ` + -ApiBaseUrl $ApiBaseUrl ` + -ClusterID $ClusterID ` + -ActorUserID $ActorUserID ` + -RequestedNodeName $RequestedNodeName ` + -DefaultNodeName $DefaultNodeName ` + -MatrixNodeName $MatrixNodeName ` + -ResultPath $sourceResultPath | Out-Null + +$sourceFile = Join-Path $repoRoot $sourceResultPath +$sourceResult = Get-Content -Raw -Path $sourceFile | ConvertFrom-Json +$audit = Get-PropertyValue -Item $sourceResult -Name "not_approved_outcome_continuation_block_audit_record" -Default $null +$guardrails = Get-PropertyValue -Item $audit -Name "guardrail_summary" -Default $null +$auditNotes = @(Get-PropertyValue -Item $audit -Name "audit_notes" -Default @()) + +$auditFieldsCompatible = Test-ObjectHasFields -Item $audit -Fields $requiredAuditFields +$auditValuesCompatible = ( + [string](Get-PropertyValue -Item $audit -Name "schema_version" -Default "") -eq "rap.remote_workspace_real_adapter_not_approved_outcome_continuation_block_audit_record.v1" -and + [string](Get-PropertyValue -Item $audit -Name "source_enforcement_schema" -Default "") -eq "rap.remote_workspace_real_adapter_not_approved_outcome_continuation_block_enforcement.v1" -and + [string](Get-PropertyValue -Item $audit -Name "audit_status" -Default "") -eq "blocked_continuation_audit_recorded" -and + [string](Get-PropertyValue -Item $audit -Name "audit_event_type" -Default "") -eq "not_approved_continuation_block" -and + [string](Get-PropertyValue -Item $audit -Name "attempted_action" -Default "") -eq "continue_not_approved_branch_without_new_explicit_enablement_request" -and + -not [bool](Get-PropertyValue -Item $audit -Name "attempt_allowed" -Default $true) -and + [string](Get-PropertyValue -Item $audit -Name "block_reason" -Default "") -eq "new_explicit_enablement_request_required" -and + [string](Get-PropertyValue -Item $audit -Name "next_allowed_entrypoint" -Default "") -eq "new_explicit_enablement_request_only" -and + [bool](Get-PropertyValue -Item $audit -Name "blocks_not_approved_extension" -Default $false) -and + [string](Get-PropertyValue -Item $audit -Name "branch_state" -Default "") -eq "not_approved_branch_closed" -and + [string](Get-PropertyValue -Item $audit -Name "continuation_policy" -Default "") -eq "do_not_continue_without_new_explicit_enablement_request" -and + [string](Get-PropertyValue -Item $audit -Name "enablement_status" -Default "") -eq "not_enabled" -and + [string](Get-PropertyValue -Item $audit -Name "runtime_gate_state" -Default "") -eq "validated_contract_only_not_enabled" -and + [string](Get-PropertyValue -Item $audit -Name "runtime_effect" -Default "") -eq "contract_only_no_runtime_enablement" -and + -not [bool](Get-PropertyValue -Item $audit -Name "allows_process_start" -Default $true) -and + -not [bool](Get-PropertyValue -Item $audit -Name "allows_payload_traffic" -Default $true) +) +$auditNotesCompatible = Test-ArrayContainsAll -Actual $auditNotes -Expected $requiredAuditNotes +$guardrailsCompatible = ( + (Test-ObjectHasFields -Item $guardrails -Fields $requiredGuardrailFields) -and + [bool](Get-PropertyValue -Item $guardrails -Name "activation_blocked" -Default $false) -and + -not [bool](Get-PropertyValue -Item $guardrails -Name "process_start_allowed" -Default $true) -and + -not [bool](Get-PropertyValue -Item $guardrails -Name "health_probe_enabled" -Default $true) -and + [string](Get-PropertyValue -Item $guardrails -Name "payload_traffic" -Default "") -eq "none" -and + -not [bool](Get-PropertyValue -Item $guardrails -Name "allows_process_start" -Default $true) -and + -not [bool](Get-PropertyValue -Item $guardrails -Name "allows_payload_traffic" -Default $true) +) + +$checks = [ordered]@{ + source_smoke_passed = ([bool]$sourceResult.passed) + source_schema_expected = ([string]$sourceResult.schema_version -eq "c19z139.remote_workspace_real_adapter_not_approved_outcome_continuation_block_audit_record_smoke.v1") + audit_present = ($null -ne $audit) + audit_fields_compatible = $auditFieldsCompatible + audit_values_compatible = $auditValuesCompatible + audit_notes_compatible = $auditNotesCompatible + guardrails_compatible = $guardrailsCompatible +} +$failed = @($checks.GetEnumerator() | Where-Object { -not $_.Value } | ForEach-Object { $_.Key }) + +$result = [ordered]@{ + schema_version = "c19z140.remote_workspace_real_adapter_not_approved_outcome_continuation_block_audit_record_compatibility_smoke.v1" + source_result_path = $sourceFile + cluster_id = $ClusterID + required_audit_fields = $requiredAuditFields + required_guardrail_fields = $requiredGuardrailFields + required_audit_notes = $requiredAuditNotes + not_approved_outcome_continuation_block_audit_record = $audit + checks = $checks + failed_checks = $failed + passed = ($failed.Count -eq 0) +} + +$fullResultPath = Join-Path $repoRoot $ResultPath +$resultDir = Split-Path -Parent $fullResultPath +if ($resultDir) { New-Item -ItemType Directory -Force -Path $resultDir | Out-Null } +$result | ConvertTo-Json -Depth 100 | Set-Content -Encoding UTF8 -Path $fullResultPath + +if (-not $result.passed) { + throw "C19Z140 remote workspace real-adapter not-approved outcome continuation block audit record compatibility smoke failed. Result: $fullResultPath Failed: $($failed -join ', ')" +} + +Write-Host "C19Z140 remote workspace real-adapter not-approved outcome continuation block audit record compatibility smoke passed. Result: $fullResultPath" +$result diff --git a/scripts/fabric/c19z141-remote-workspace-real-adapter-not-approved-outcome-continuation-block-audit-rollup-smoke.ps1 b/scripts/fabric/c19z141-remote-workspace-real-adapter-not-approved-outcome-continuation-block-audit-rollup-smoke.ps1 new file mode 100644 index 0000000..24cb0c4 --- /dev/null +++ b/scripts/fabric/c19z141-remote-workspace-real-adapter-not-approved-outcome-continuation-block-audit-rollup-smoke.ps1 @@ -0,0 +1,183 @@ +param( + [string]$ApiBaseUrl = "http://192.168.200.61:18121/api/v1", + [string]$ClusterID = "cfc0743d-d960-49fb-9de8-96e063d5e4aa", + [string]$ActorUserID = "f67d943f-5397-4b3a-a229-695fe67ad700", + [string]$RequestedNodeName = "test-1", + [string]$DefaultNodeName = "test-2", + [string]$MatrixNodeName = "test-1", + [string]$ResultPath = "artifacts\c19z141-remote-workspace-real-adapter-not-approved-outcome-continuation-block-audit-rollup-smoke-result.json" +) + +Set-StrictMode -Version Latest +$ErrorActionPreference = "Stop" + +$repoRoot = (Resolve-Path (Join-Path $PSScriptRoot "..\..")).ProviderPath +$sourceResultPath = "artifacts\c19z141-remote-workspace-real-adapter-not-approved-outcome-continuation-block-audit-rollup-source-result.json" +$requiredRollupFields = @( + "schema_version", + "source_audit_schema", + "rollup_status", + "rollup_marker", + "operator_status", + "audit_status", + "audit_event_type", + "attempt_allowed", + "block_reason", + "next_allowed_entrypoint", + "blocks_not_approved_extension", + "branch_state", + "continuation_policy", + "enablement_status", + "runtime_gate_state", + "runtime_effect", + "allows_process_start", + "allows_payload_traffic", + "guardrail_summary", + "rollup_notes" +) +$requiredGuardrailFields = @("activation_blocked", "process_start_allowed", "health_probe_enabled", "payload_traffic", "allows_process_start", "allows_payload_traffic") +$requiredRollupNotes = @( + "blocked_continuation_audit_rollup_complete", + "not_approved_branch_remains_closed", + "new_explicit_enablement_request_required", + "real_runtime_gate_not_enabled", + "process_start_disabled", + "payload_forwarding_disabled" +) + +function Get-PropertyValue { + param([object]$Item, [string]$Name, [object]$Default = $null) + if ($null -eq $Item) { return $Default } + if ($Item -is [System.Collections.IDictionary]) { + if ($Item.Contains($Name)) { return $Item[$Name] } + return $Default + } + $property = $Item.PSObject.Properties[$Name] + if ($null -eq $property) { return $Default } + return $property.Value +} + +function Test-ObjectHasFields { + param([object]$Item, [string[]]$Fields) + if ($null -eq $Item) { return $false } + foreach ($field in $Fields) { + if ($Item -is [System.Collections.IDictionary]) { + if (-not $Item.Contains($field)) { return $false } + continue + } + if ($null -eq $Item.PSObject.Properties[$field]) { return $false } + } + return $true +} + +function Test-ArrayContainsAll { + param([object[]]$Actual, [string[]]$Expected) + foreach ($item in $Expected) { + if ($Actual -notcontains $item) { return $false } + } + return $true +} + +& powershell -ExecutionPolicy Bypass -File (Join-Path $PSScriptRoot "c19z140-remote-workspace-real-adapter-not-approved-outcome-continuation-block-audit-record-compatibility-smoke.ps1") ` + -ApiBaseUrl $ApiBaseUrl ` + -ClusterID $ClusterID ` + -ActorUserID $ActorUserID ` + -RequestedNodeName $RequestedNodeName ` + -DefaultNodeName $DefaultNodeName ` + -MatrixNodeName $MatrixNodeName ` + -ResultPath $sourceResultPath | Out-Null + +$sourceFile = Join-Path $repoRoot $sourceResultPath +$sourceResult = Get-Content -Raw -Path $sourceFile | ConvertFrom-Json +$audit = Get-PropertyValue -Item $sourceResult -Name "not_approved_outcome_continuation_block_audit_record" -Default $null +$guardrails = Get-PropertyValue -Item $audit -Name "guardrail_summary" -Default $null + +$rollup = [ordered]@{ + schema_version = "rap.remote_workspace_real_adapter_not_approved_outcome_continuation_block_audit_rollup.v1" + source_audit_schema = Get-PropertyValue -Item $audit -Name "schema_version" -Default $null + rollup_status = "blocked_continuation_audit_rollup_complete" + rollup_marker = "c19z141_real_adapter_not_approved_outcome_continuation_block_audit_rollup" + operator_status = "not_approved_branch_closed_new_request_required" + audit_status = Get-PropertyValue -Item $audit -Name "audit_status" -Default $null + audit_event_type = Get-PropertyValue -Item $audit -Name "audit_event_type" -Default $null + attempt_allowed = $false + block_reason = Get-PropertyValue -Item $audit -Name "block_reason" -Default $null + next_allowed_entrypoint = Get-PropertyValue -Item $audit -Name "next_allowed_entrypoint" -Default $null + blocks_not_approved_extension = Get-PropertyValue -Item $audit -Name "blocks_not_approved_extension" -Default $null + branch_state = Get-PropertyValue -Item $audit -Name "branch_state" -Default $null + continuation_policy = Get-PropertyValue -Item $audit -Name "continuation_policy" -Default $null + enablement_status = Get-PropertyValue -Item $audit -Name "enablement_status" -Default $null + runtime_gate_state = Get-PropertyValue -Item $audit -Name "runtime_gate_state" -Default $null + runtime_effect = Get-PropertyValue -Item $audit -Name "runtime_effect" -Default $null + allows_process_start = $false + allows_payload_traffic = $false + guardrail_summary = $guardrails + rollup_notes = $requiredRollupNotes +} + +$rollupFieldsCompatible = Test-ObjectHasFields -Item $rollup -Fields $requiredRollupFields +$rollupValuesCompatible = ( + [string](Get-PropertyValue -Item $rollup -Name "schema_version" -Default "") -eq "rap.remote_workspace_real_adapter_not_approved_outcome_continuation_block_audit_rollup.v1" -and + [string](Get-PropertyValue -Item $rollup -Name "source_audit_schema" -Default "") -eq "rap.remote_workspace_real_adapter_not_approved_outcome_continuation_block_audit_record.v1" -and + [string](Get-PropertyValue -Item $rollup -Name "rollup_status" -Default "") -eq "blocked_continuation_audit_rollup_complete" -and + [string](Get-PropertyValue -Item $rollup -Name "operator_status" -Default "") -eq "not_approved_branch_closed_new_request_required" -and + [string](Get-PropertyValue -Item $rollup -Name "audit_status" -Default "") -eq "blocked_continuation_audit_recorded" -and + [string](Get-PropertyValue -Item $rollup -Name "audit_event_type" -Default "") -eq "not_approved_continuation_block" -and + -not [bool](Get-PropertyValue -Item $rollup -Name "attempt_allowed" -Default $true) -and + [string](Get-PropertyValue -Item $rollup -Name "block_reason" -Default "") -eq "new_explicit_enablement_request_required" -and + [string](Get-PropertyValue -Item $rollup -Name "next_allowed_entrypoint" -Default "") -eq "new_explicit_enablement_request_only" -and + [bool](Get-PropertyValue -Item $rollup -Name "blocks_not_approved_extension" -Default $false) -and + [string](Get-PropertyValue -Item $rollup -Name "branch_state" -Default "") -eq "not_approved_branch_closed" -and + [string](Get-PropertyValue -Item $rollup -Name "continuation_policy" -Default "") -eq "do_not_continue_without_new_explicit_enablement_request" -and + [string](Get-PropertyValue -Item $rollup -Name "enablement_status" -Default "") -eq "not_enabled" -and + [string](Get-PropertyValue -Item $rollup -Name "runtime_gate_state" -Default "") -eq "validated_contract_only_not_enabled" -and + [string](Get-PropertyValue -Item $rollup -Name "runtime_effect" -Default "") -eq "contract_only_no_runtime_enablement" -and + -not [bool](Get-PropertyValue -Item $rollup -Name "allows_process_start" -Default $true) -and + -not [bool](Get-PropertyValue -Item $rollup -Name "allows_payload_traffic" -Default $true) +) +$rollupNotesCompatible = Test-ArrayContainsAll -Actual @($rollup.rollup_notes) -Expected $requiredRollupNotes +$guardrailsCompatible = ( + (Test-ObjectHasFields -Item $guardrails -Fields $requiredGuardrailFields) -and + [bool](Get-PropertyValue -Item $guardrails -Name "activation_blocked" -Default $false) -and + -not [bool](Get-PropertyValue -Item $guardrails -Name "process_start_allowed" -Default $true) -and + -not [bool](Get-PropertyValue -Item $guardrails -Name "health_probe_enabled" -Default $true) -and + [string](Get-PropertyValue -Item $guardrails -Name "payload_traffic" -Default "") -eq "none" -and + -not [bool](Get-PropertyValue -Item $guardrails -Name "allows_process_start" -Default $true) -and + -not [bool](Get-PropertyValue -Item $guardrails -Name "allows_payload_traffic" -Default $true) +) + +$checks = [ordered]@{ + source_smoke_passed = ([bool]$sourceResult.passed) + source_schema_expected = ([string]$sourceResult.schema_version -eq "c19z140.remote_workspace_real_adapter_not_approved_outcome_continuation_block_audit_record_compatibility_smoke.v1") + audit_present = ($null -ne $audit) + rollup_fields_compatible = $rollupFieldsCompatible + rollup_values_compatible = $rollupValuesCompatible + rollup_notes_compatible = $rollupNotesCompatible + guardrails_compatible = $guardrailsCompatible +} +$failed = @($checks.GetEnumerator() | Where-Object { -not $_.Value } | ForEach-Object { $_.Key }) + +$result = [ordered]@{ + schema_version = "c19z141.remote_workspace_real_adapter_not_approved_outcome_continuation_block_audit_rollup_smoke.v1" + source_result_path = $sourceFile + cluster_id = $ClusterID + required_rollup_fields = $requiredRollupFields + required_guardrail_fields = $requiredGuardrailFields + required_rollup_notes = $requiredRollupNotes + not_approved_outcome_continuation_block_audit_rollup = $rollup + checks = $checks + failed_checks = $failed + passed = ($failed.Count -eq 0) +} + +$fullResultPath = Join-Path $repoRoot $ResultPath +$resultDir = Split-Path -Parent $fullResultPath +if ($resultDir) { New-Item -ItemType Directory -Force -Path $resultDir | Out-Null } +$result | ConvertTo-Json -Depth 100 | Set-Content -Encoding UTF8 -Path $fullResultPath + +if (-not $result.passed) { + throw "C19Z141 remote workspace real-adapter not-approved outcome continuation block audit rollup smoke failed. Result: $fullResultPath Failed: $($failed -join ', ')" +} + +Write-Host "C19Z141 remote workspace real-adapter not-approved outcome continuation block audit rollup smoke passed. Result: $fullResultPath" +$result diff --git a/scripts/fabric/c19z142-remote-workspace-real-adapter-not-approved-outcome-continuation-block-audit-rollup-compatibility-smoke.ps1 b/scripts/fabric/c19z142-remote-workspace-real-adapter-not-approved-outcome-continuation-block-audit-rollup-compatibility-smoke.ps1 new file mode 100644 index 0000000..28a4cb0 --- /dev/null +++ b/scripts/fabric/c19z142-remote-workspace-real-adapter-not-approved-outcome-continuation-block-audit-rollup-compatibility-smoke.ps1 @@ -0,0 +1,133 @@ +param( + [string]$ApiBaseUrl = "http://192.168.200.61:18121/api/v1", + [string]$ClusterID = "cfc0743d-d960-49fb-9de8-96e063d5e4aa", + [string]$ActorUserID = "f67d943f-5397-4b3a-a229-695fe67ad700", + [string]$RequestedNodeName = "test-1", + [string]$DefaultNodeName = "test-2", + [string]$MatrixNodeName = "test-1", + [string]$ResultPath = "artifacts\c19z142-remote-workspace-real-adapter-not-approved-outcome-continuation-block-audit-rollup-compatibility-smoke-result.json" +) + +Set-StrictMode -Version Latest +$ErrorActionPreference = "Stop" + +$repoRoot = (Resolve-Path (Join-Path $PSScriptRoot "..\..")).ProviderPath +$sourceResultPath = "artifacts\c19z142-remote-workspace-real-adapter-not-approved-outcome-continuation-block-audit-rollup-source-result.json" +$requiredRollupFields = @("schema_version", "source_audit_schema", "rollup_status", "rollup_marker", "operator_status", "audit_status", "audit_event_type", "attempt_allowed", "block_reason", "next_allowed_entrypoint", "blocks_not_approved_extension", "branch_state", "continuation_policy", "enablement_status", "runtime_gate_state", "runtime_effect", "allows_process_start", "allows_payload_traffic", "guardrail_summary", "rollup_notes") +$requiredGuardrailFields = @("activation_blocked", "process_start_allowed", "health_probe_enabled", "payload_traffic", "allows_process_start", "allows_payload_traffic") +$requiredRollupNotes = @("blocked_continuation_audit_rollup_complete", "not_approved_branch_remains_closed", "new_explicit_enablement_request_required", "real_runtime_gate_not_enabled", "process_start_disabled", "payload_forwarding_disabled") + +function Get-PropertyValue { + param([object]$Item, [string]$Name, [object]$Default = $null) + if ($null -eq $Item) { return $Default } + if ($Item -is [System.Collections.IDictionary]) { + if ($Item.Contains($Name)) { return $Item[$Name] } + return $Default + } + $property = $Item.PSObject.Properties[$Name] + if ($null -eq $property) { return $Default } + return $property.Value +} + +function Test-ObjectHasFields { + param([object]$Item, [string[]]$Fields) + if ($null -eq $Item) { return $false } + foreach ($field in $Fields) { + if ($Item -is [System.Collections.IDictionary]) { + if (-not $Item.Contains($field)) { return $false } + continue + } + if ($null -eq $Item.PSObject.Properties[$field]) { return $false } + } + return $true +} + +function Test-ArrayContainsAll { + param([object[]]$Actual, [string[]]$Expected) + foreach ($item in $Expected) { + if ($Actual -notcontains $item) { return $false } + } + return $true +} + +& powershell -ExecutionPolicy Bypass -File (Join-Path $PSScriptRoot "c19z141-remote-workspace-real-adapter-not-approved-outcome-continuation-block-audit-rollup-smoke.ps1") ` + -ApiBaseUrl $ApiBaseUrl ` + -ClusterID $ClusterID ` + -ActorUserID $ActorUserID ` + -RequestedNodeName $RequestedNodeName ` + -DefaultNodeName $DefaultNodeName ` + -MatrixNodeName $MatrixNodeName ` + -ResultPath $sourceResultPath | Out-Null + +$sourceFile = Join-Path $repoRoot $sourceResultPath +$sourceResult = Get-Content -Raw -Path $sourceFile | ConvertFrom-Json +$rollup = Get-PropertyValue -Item $sourceResult -Name "not_approved_outcome_continuation_block_audit_rollup" -Default $null +$guardrails = Get-PropertyValue -Item $rollup -Name "guardrail_summary" -Default $null +$rollupNotes = @(Get-PropertyValue -Item $rollup -Name "rollup_notes" -Default @()) + +$rollupFieldsCompatible = Test-ObjectHasFields -Item $rollup -Fields $requiredRollupFields +$rollupValuesCompatible = ( + [string](Get-PropertyValue -Item $rollup -Name "schema_version" -Default "") -eq "rap.remote_workspace_real_adapter_not_approved_outcome_continuation_block_audit_rollup.v1" -and + [string](Get-PropertyValue -Item $rollup -Name "source_audit_schema" -Default "") -eq "rap.remote_workspace_real_adapter_not_approved_outcome_continuation_block_audit_record.v1" -and + [string](Get-PropertyValue -Item $rollup -Name "rollup_status" -Default "") -eq "blocked_continuation_audit_rollup_complete" -and + [string](Get-PropertyValue -Item $rollup -Name "operator_status" -Default "") -eq "not_approved_branch_closed_new_request_required" -and + [string](Get-PropertyValue -Item $rollup -Name "audit_status" -Default "") -eq "blocked_continuation_audit_recorded" -and + [string](Get-PropertyValue -Item $rollup -Name "audit_event_type" -Default "") -eq "not_approved_continuation_block" -and + -not [bool](Get-PropertyValue -Item $rollup -Name "attempt_allowed" -Default $true) -and + [string](Get-PropertyValue -Item $rollup -Name "block_reason" -Default "") -eq "new_explicit_enablement_request_required" -and + [string](Get-PropertyValue -Item $rollup -Name "next_allowed_entrypoint" -Default "") -eq "new_explicit_enablement_request_only" -and + [bool](Get-PropertyValue -Item $rollup -Name "blocks_not_approved_extension" -Default $false) -and + [string](Get-PropertyValue -Item $rollup -Name "branch_state" -Default "") -eq "not_approved_branch_closed" -and + [string](Get-PropertyValue -Item $rollup -Name "continuation_policy" -Default "") -eq "do_not_continue_without_new_explicit_enablement_request" -and + [string](Get-PropertyValue -Item $rollup -Name "enablement_status" -Default "") -eq "not_enabled" -and + [string](Get-PropertyValue -Item $rollup -Name "runtime_gate_state" -Default "") -eq "validated_contract_only_not_enabled" -and + [string](Get-PropertyValue -Item $rollup -Name "runtime_effect" -Default "") -eq "contract_only_no_runtime_enablement" -and + -not [bool](Get-PropertyValue -Item $rollup -Name "allows_process_start" -Default $true) -and + -not [bool](Get-PropertyValue -Item $rollup -Name "allows_payload_traffic" -Default $true) +) +$rollupNotesCompatible = Test-ArrayContainsAll -Actual $rollupNotes -Expected $requiredRollupNotes +$guardrailsCompatible = ( + (Test-ObjectHasFields -Item $guardrails -Fields $requiredGuardrailFields) -and + [bool](Get-PropertyValue -Item $guardrails -Name "activation_blocked" -Default $false) -and + -not [bool](Get-PropertyValue -Item $guardrails -Name "process_start_allowed" -Default $true) -and + -not [bool](Get-PropertyValue -Item $guardrails -Name "health_probe_enabled" -Default $true) -and + [string](Get-PropertyValue -Item $guardrails -Name "payload_traffic" -Default "") -eq "none" -and + -not [bool](Get-PropertyValue -Item $guardrails -Name "allows_process_start" -Default $true) -and + -not [bool](Get-PropertyValue -Item $guardrails -Name "allows_payload_traffic" -Default $true) +) + +$checks = [ordered]@{ + source_smoke_passed = ([bool]$sourceResult.passed) + source_schema_expected = ([string]$sourceResult.schema_version -eq "c19z141.remote_workspace_real_adapter_not_approved_outcome_continuation_block_audit_rollup_smoke.v1") + rollup_present = ($null -ne $rollup) + rollup_fields_compatible = $rollupFieldsCompatible + rollup_values_compatible = $rollupValuesCompatible + rollup_notes_compatible = $rollupNotesCompatible + guardrails_compatible = $guardrailsCompatible +} +$failed = @($checks.GetEnumerator() | Where-Object { -not $_.Value } | ForEach-Object { $_.Key }) + +$result = [ordered]@{ + schema_version = "c19z142.remote_workspace_real_adapter_not_approved_outcome_continuation_block_audit_rollup_compatibility_smoke.v1" + source_result_path = $sourceFile + cluster_id = $ClusterID + required_rollup_fields = $requiredRollupFields + required_guardrail_fields = $requiredGuardrailFields + required_rollup_notes = $requiredRollupNotes + not_approved_outcome_continuation_block_audit_rollup = $rollup + checks = $checks + failed_checks = $failed + passed = ($failed.Count -eq 0) +} + +$fullResultPath = Join-Path $repoRoot $ResultPath +$resultDir = Split-Path -Parent $fullResultPath +if ($resultDir) { New-Item -ItemType Directory -Force -Path $resultDir | Out-Null } +$result | ConvertTo-Json -Depth 100 | Set-Content -Encoding UTF8 -Path $fullResultPath + +if (-not $result.passed) { + throw "C19Z142 remote workspace real-adapter not-approved outcome continuation block audit rollup compatibility smoke failed. Result: $fullResultPath Failed: $($failed -join ', ')" +} + +Write-Host "C19Z142 remote workspace real-adapter not-approved outcome continuation block audit rollup compatibility smoke passed. Result: $fullResultPath" +$result diff --git a/scripts/fabric/c19z143-remote-workspace-real-adapter-not-approved-outcome-operator-stop-summary-smoke.ps1 b/scripts/fabric/c19z143-remote-workspace-real-adapter-not-approved-outcome-operator-stop-summary-smoke.ps1 new file mode 100644 index 0000000..21c504f --- /dev/null +++ b/scripts/fabric/c19z143-remote-workspace-real-adapter-not-approved-outcome-operator-stop-summary-smoke.ps1 @@ -0,0 +1,183 @@ +param( + [string]$ApiBaseUrl = "http://192.168.200.61:18121/api/v1", + [string]$ClusterID = "cfc0743d-d960-49fb-9de8-96e063d5e4aa", + [string]$ActorUserID = "f67d943f-5397-4b3a-a229-695fe67ad700", + [string]$RequestedNodeName = "test-1", + [string]$DefaultNodeName = "test-2", + [string]$MatrixNodeName = "test-1", + [string]$ResultPath = "artifacts\c19z143-remote-workspace-real-adapter-not-approved-outcome-operator-stop-summary-smoke-result.json" +) + +Set-StrictMode -Version Latest +$ErrorActionPreference = "Stop" + +$repoRoot = (Resolve-Path (Join-Path $PSScriptRoot "..\..")).ProviderPath +$sourceResultPath = "artifacts\c19z143-remote-workspace-real-adapter-not-approved-outcome-operator-stop-summary-source-result.json" +$requiredSummaryFields = @( + "schema_version", + "source_rollup_schema", + "summary_status", + "summary_marker", + "operator_status", + "operator_action", + "operator_message", + "attempt_allowed", + "block_reason", + "next_allowed_entrypoint", + "blocks_not_approved_extension", + "branch_state", + "continuation_policy", + "enablement_status", + "runtime_gate_state", + "runtime_effect", + "allows_process_start", + "allows_payload_traffic", + "guardrail_summary", + "summary_notes" +) +$requiredGuardrailFields = @("activation_blocked", "process_start_allowed", "health_probe_enabled", "payload_traffic", "allows_process_start", "allows_payload_traffic") +$requiredSummaryNotes = @( + "operator_stop_summary_complete", + "not_approved_branch_remains_closed", + "new_explicit_enablement_request_required", + "real_runtime_gate_not_enabled", + "process_start_disabled", + "payload_forwarding_disabled" +) + +function Get-PropertyValue { + param([object]$Item, [string]$Name, [object]$Default = $null) + if ($null -eq $Item) { return $Default } + if ($Item -is [System.Collections.IDictionary]) { + if ($Item.Contains($Name)) { return $Item[$Name] } + return $Default + } + $property = $Item.PSObject.Properties[$Name] + if ($null -eq $property) { return $Default } + return $property.Value +} + +function Test-ObjectHasFields { + param([object]$Item, [string[]]$Fields) + if ($null -eq $Item) { return $false } + foreach ($field in $Fields) { + if ($Item -is [System.Collections.IDictionary]) { + if (-not $Item.Contains($field)) { return $false } + continue + } + if ($null -eq $Item.PSObject.Properties[$field]) { return $false } + } + return $true +} + +function Test-ArrayContainsAll { + param([object[]]$Actual, [string[]]$Expected) + foreach ($item in $Expected) { + if ($Actual -notcontains $item) { return $false } + } + return $true +} + +& powershell -ExecutionPolicy Bypass -File (Join-Path $PSScriptRoot "c19z142-remote-workspace-real-adapter-not-approved-outcome-continuation-block-audit-rollup-compatibility-smoke.ps1") ` + -ApiBaseUrl $ApiBaseUrl ` + -ClusterID $ClusterID ` + -ActorUserID $ActorUserID ` + -RequestedNodeName $RequestedNodeName ` + -DefaultNodeName $DefaultNodeName ` + -MatrixNodeName $MatrixNodeName ` + -ResultPath $sourceResultPath | Out-Null + +$sourceFile = Join-Path $repoRoot $sourceResultPath +$sourceResult = Get-Content -Raw -Path $sourceFile | ConvertFrom-Json +$rollup = Get-PropertyValue -Item $sourceResult -Name "not_approved_outcome_continuation_block_audit_rollup" -Default $null +$guardrails = Get-PropertyValue -Item $rollup -Name "guardrail_summary" -Default $null + +$summary = [ordered]@{ + schema_version = "rap.remote_workspace_real_adapter_not_approved_outcome_operator_stop_summary.v1" + source_rollup_schema = Get-PropertyValue -Item $rollup -Name "schema_version" -Default $null + summary_status = "operator_stop_summary_complete" + summary_marker = "c19z143_real_adapter_not_approved_outcome_operator_stop_summary" + operator_status = Get-PropertyValue -Item $rollup -Name "operator_status" -Default $null + operator_action = "keep_real_adapter_disabled_until_new_explicit_enablement_request" + operator_message = "not_approved_branch_closed_new_request_required" + attempt_allowed = $false + block_reason = Get-PropertyValue -Item $rollup -Name "block_reason" -Default $null + next_allowed_entrypoint = Get-PropertyValue -Item $rollup -Name "next_allowed_entrypoint" -Default $null + blocks_not_approved_extension = Get-PropertyValue -Item $rollup -Name "blocks_not_approved_extension" -Default $null + branch_state = Get-PropertyValue -Item $rollup -Name "branch_state" -Default $null + continuation_policy = Get-PropertyValue -Item $rollup -Name "continuation_policy" -Default $null + enablement_status = Get-PropertyValue -Item $rollup -Name "enablement_status" -Default $null + runtime_gate_state = Get-PropertyValue -Item $rollup -Name "runtime_gate_state" -Default $null + runtime_effect = Get-PropertyValue -Item $rollup -Name "runtime_effect" -Default $null + allows_process_start = $false + allows_payload_traffic = $false + guardrail_summary = $guardrails + summary_notes = $requiredSummaryNotes +} + +$summaryFieldsCompatible = Test-ObjectHasFields -Item $summary -Fields $requiredSummaryFields +$summaryValuesCompatible = ( + [string](Get-PropertyValue -Item $summary -Name "schema_version" -Default "") -eq "rap.remote_workspace_real_adapter_not_approved_outcome_operator_stop_summary.v1" -and + [string](Get-PropertyValue -Item $summary -Name "source_rollup_schema" -Default "") -eq "rap.remote_workspace_real_adapter_not_approved_outcome_continuation_block_audit_rollup.v1" -and + [string](Get-PropertyValue -Item $summary -Name "summary_status" -Default "") -eq "operator_stop_summary_complete" -and + [string](Get-PropertyValue -Item $summary -Name "operator_status" -Default "") -eq "not_approved_branch_closed_new_request_required" -and + [string](Get-PropertyValue -Item $summary -Name "operator_action" -Default "") -eq "keep_real_adapter_disabled_until_new_explicit_enablement_request" -and + [string](Get-PropertyValue -Item $summary -Name "operator_message" -Default "") -eq "not_approved_branch_closed_new_request_required" -and + -not [bool](Get-PropertyValue -Item $summary -Name "attempt_allowed" -Default $true) -and + [string](Get-PropertyValue -Item $summary -Name "block_reason" -Default "") -eq "new_explicit_enablement_request_required" -and + [string](Get-PropertyValue -Item $summary -Name "next_allowed_entrypoint" -Default "") -eq "new_explicit_enablement_request_only" -and + [bool](Get-PropertyValue -Item $summary -Name "blocks_not_approved_extension" -Default $false) -and + [string](Get-PropertyValue -Item $summary -Name "branch_state" -Default "") -eq "not_approved_branch_closed" -and + [string](Get-PropertyValue -Item $summary -Name "continuation_policy" -Default "") -eq "do_not_continue_without_new_explicit_enablement_request" -and + [string](Get-PropertyValue -Item $summary -Name "enablement_status" -Default "") -eq "not_enabled" -and + [string](Get-PropertyValue -Item $summary -Name "runtime_gate_state" -Default "") -eq "validated_contract_only_not_enabled" -and + [string](Get-PropertyValue -Item $summary -Name "runtime_effect" -Default "") -eq "contract_only_no_runtime_enablement" -and + -not [bool](Get-PropertyValue -Item $summary -Name "allows_process_start" -Default $true) -and + -not [bool](Get-PropertyValue -Item $summary -Name "allows_payload_traffic" -Default $true) +) +$summaryNotesCompatible = Test-ArrayContainsAll -Actual @($summary.summary_notes) -Expected $requiredSummaryNotes +$guardrailsCompatible = ( + (Test-ObjectHasFields -Item $guardrails -Fields $requiredGuardrailFields) -and + [bool](Get-PropertyValue -Item $guardrails -Name "activation_blocked" -Default $false) -and + -not [bool](Get-PropertyValue -Item $guardrails -Name "process_start_allowed" -Default $true) -and + -not [bool](Get-PropertyValue -Item $guardrails -Name "health_probe_enabled" -Default $true) -and + [string](Get-PropertyValue -Item $guardrails -Name "payload_traffic" -Default "") -eq "none" -and + -not [bool](Get-PropertyValue -Item $guardrails -Name "allows_process_start" -Default $true) -and + -not [bool](Get-PropertyValue -Item $guardrails -Name "allows_payload_traffic" -Default $true) +) + +$checks = [ordered]@{ + source_smoke_passed = ([bool]$sourceResult.passed) + source_schema_expected = ([string]$sourceResult.schema_version -eq "c19z142.remote_workspace_real_adapter_not_approved_outcome_continuation_block_audit_rollup_compatibility_smoke.v1") + rollup_present = ($null -ne $rollup) + summary_fields_compatible = $summaryFieldsCompatible + summary_values_compatible = $summaryValuesCompatible + summary_notes_compatible = $summaryNotesCompatible + guardrails_compatible = $guardrailsCompatible +} +$failed = @($checks.GetEnumerator() | Where-Object { -not $_.Value } | ForEach-Object { $_.Key }) + +$result = [ordered]@{ + schema_version = "c19z143.remote_workspace_real_adapter_not_approved_outcome_operator_stop_summary_smoke.v1" + source_result_path = $sourceFile + cluster_id = $ClusterID + required_summary_fields = $requiredSummaryFields + required_guardrail_fields = $requiredGuardrailFields + required_summary_notes = $requiredSummaryNotes + not_approved_outcome_operator_stop_summary = $summary + checks = $checks + failed_checks = $failed + passed = ($failed.Count -eq 0) +} + +$fullResultPath = Join-Path $repoRoot $ResultPath +$resultDir = Split-Path -Parent $fullResultPath +if ($resultDir) { New-Item -ItemType Directory -Force -Path $resultDir | Out-Null } +$result | ConvertTo-Json -Depth 100 | Set-Content -Encoding UTF8 -Path $fullResultPath + +if (-not $result.passed) { + throw "C19Z143 remote workspace real-adapter not-approved outcome operator stop summary smoke failed. Result: $fullResultPath Failed: $($failed -join ', ')" +} + +Write-Host "C19Z143 remote workspace real-adapter not-approved outcome operator stop summary smoke passed. Result: $fullResultPath" +$result diff --git a/scripts/fabric/c19z144-remote-workspace-real-adapter-not-approved-outcome-operator-stop-summary-compatibility-smoke.ps1 b/scripts/fabric/c19z144-remote-workspace-real-adapter-not-approved-outcome-operator-stop-summary-compatibility-smoke.ps1 new file mode 100644 index 0000000..d0dedb2 --- /dev/null +++ b/scripts/fabric/c19z144-remote-workspace-real-adapter-not-approved-outcome-operator-stop-summary-compatibility-smoke.ps1 @@ -0,0 +1,133 @@ +param( + [string]$ApiBaseUrl = "http://192.168.200.61:18121/api/v1", + [string]$ClusterID = "cfc0743d-d960-49fb-9de8-96e063d5e4aa", + [string]$ActorUserID = "f67d943f-5397-4b3a-a229-695fe67ad700", + [string]$RequestedNodeName = "test-1", + [string]$DefaultNodeName = "test-2", + [string]$MatrixNodeName = "test-1", + [string]$ResultPath = "artifacts\c19z144-remote-workspace-real-adapter-not-approved-outcome-operator-stop-summary-compatibility-smoke-result.json" +) + +Set-StrictMode -Version Latest +$ErrorActionPreference = "Stop" + +$repoRoot = (Resolve-Path (Join-Path $PSScriptRoot "..\..")).ProviderPath +$sourceResultPath = "artifacts\c19z144-remote-workspace-real-adapter-not-approved-outcome-operator-stop-summary-source-result.json" +$requiredSummaryFields = @("schema_version", "source_rollup_schema", "summary_status", "summary_marker", "operator_status", "operator_action", "operator_message", "attempt_allowed", "block_reason", "next_allowed_entrypoint", "blocks_not_approved_extension", "branch_state", "continuation_policy", "enablement_status", "runtime_gate_state", "runtime_effect", "allows_process_start", "allows_payload_traffic", "guardrail_summary", "summary_notes") +$requiredGuardrailFields = @("activation_blocked", "process_start_allowed", "health_probe_enabled", "payload_traffic", "allows_process_start", "allows_payload_traffic") +$requiredSummaryNotes = @("operator_stop_summary_complete", "not_approved_branch_remains_closed", "new_explicit_enablement_request_required", "real_runtime_gate_not_enabled", "process_start_disabled", "payload_forwarding_disabled") + +function Get-PropertyValue { + param([object]$Item, [string]$Name, [object]$Default = $null) + if ($null -eq $Item) { return $Default } + if ($Item -is [System.Collections.IDictionary]) { + if ($Item.Contains($Name)) { return $Item[$Name] } + return $Default + } + $property = $Item.PSObject.Properties[$Name] + if ($null -eq $property) { return $Default } + return $property.Value +} + +function Test-ObjectHasFields { + param([object]$Item, [string[]]$Fields) + if ($null -eq $Item) { return $false } + foreach ($field in $Fields) { + if ($Item -is [System.Collections.IDictionary]) { + if (-not $Item.Contains($field)) { return $false } + continue + } + if ($null -eq $Item.PSObject.Properties[$field]) { return $false } + } + return $true +} + +function Test-ArrayContainsAll { + param([object[]]$Actual, [string[]]$Expected) + foreach ($item in $Expected) { + if ($Actual -notcontains $item) { return $false } + } + return $true +} + +& powershell -ExecutionPolicy Bypass -File (Join-Path $PSScriptRoot "c19z143-remote-workspace-real-adapter-not-approved-outcome-operator-stop-summary-smoke.ps1") ` + -ApiBaseUrl $ApiBaseUrl ` + -ClusterID $ClusterID ` + -ActorUserID $ActorUserID ` + -RequestedNodeName $RequestedNodeName ` + -DefaultNodeName $DefaultNodeName ` + -MatrixNodeName $MatrixNodeName ` + -ResultPath $sourceResultPath | Out-Null + +$sourceFile = Join-Path $repoRoot $sourceResultPath +$sourceResult = Get-Content -Raw -Path $sourceFile | ConvertFrom-Json +$summary = Get-PropertyValue -Item $sourceResult -Name "not_approved_outcome_operator_stop_summary" -Default $null +$guardrails = Get-PropertyValue -Item $summary -Name "guardrail_summary" -Default $null +$summaryNotes = @(Get-PropertyValue -Item $summary -Name "summary_notes" -Default @()) + +$summaryFieldsCompatible = Test-ObjectHasFields -Item $summary -Fields $requiredSummaryFields +$summaryValuesCompatible = ( + [string](Get-PropertyValue -Item $summary -Name "schema_version" -Default "") -eq "rap.remote_workspace_real_adapter_not_approved_outcome_operator_stop_summary.v1" -and + [string](Get-PropertyValue -Item $summary -Name "source_rollup_schema" -Default "") -eq "rap.remote_workspace_real_adapter_not_approved_outcome_continuation_block_audit_rollup.v1" -and + [string](Get-PropertyValue -Item $summary -Name "summary_status" -Default "") -eq "operator_stop_summary_complete" -and + [string](Get-PropertyValue -Item $summary -Name "operator_status" -Default "") -eq "not_approved_branch_closed_new_request_required" -and + [string](Get-PropertyValue -Item $summary -Name "operator_action" -Default "") -eq "keep_real_adapter_disabled_until_new_explicit_enablement_request" -and + [string](Get-PropertyValue -Item $summary -Name "operator_message" -Default "") -eq "not_approved_branch_closed_new_request_required" -and + -not [bool](Get-PropertyValue -Item $summary -Name "attempt_allowed" -Default $true) -and + [string](Get-PropertyValue -Item $summary -Name "block_reason" -Default "") -eq "new_explicit_enablement_request_required" -and + [string](Get-PropertyValue -Item $summary -Name "next_allowed_entrypoint" -Default "") -eq "new_explicit_enablement_request_only" -and + [bool](Get-PropertyValue -Item $summary -Name "blocks_not_approved_extension" -Default $false) -and + [string](Get-PropertyValue -Item $summary -Name "branch_state" -Default "") -eq "not_approved_branch_closed" -and + [string](Get-PropertyValue -Item $summary -Name "continuation_policy" -Default "") -eq "do_not_continue_without_new_explicit_enablement_request" -and + [string](Get-PropertyValue -Item $summary -Name "enablement_status" -Default "") -eq "not_enabled" -and + [string](Get-PropertyValue -Item $summary -Name "runtime_gate_state" -Default "") -eq "validated_contract_only_not_enabled" -and + [string](Get-PropertyValue -Item $summary -Name "runtime_effect" -Default "") -eq "contract_only_no_runtime_enablement" -and + -not [bool](Get-PropertyValue -Item $summary -Name "allows_process_start" -Default $true) -and + -not [bool](Get-PropertyValue -Item $summary -Name "allows_payload_traffic" -Default $true) +) +$summaryNotesCompatible = Test-ArrayContainsAll -Actual $summaryNotes -Expected $requiredSummaryNotes +$guardrailsCompatible = ( + (Test-ObjectHasFields -Item $guardrails -Fields $requiredGuardrailFields) -and + [bool](Get-PropertyValue -Item $guardrails -Name "activation_blocked" -Default $false) -and + -not [bool](Get-PropertyValue -Item $guardrails -Name "process_start_allowed" -Default $true) -and + -not [bool](Get-PropertyValue -Item $guardrails -Name "health_probe_enabled" -Default $true) -and + [string](Get-PropertyValue -Item $guardrails -Name "payload_traffic" -Default "") -eq "none" -and + -not [bool](Get-PropertyValue -Item $guardrails -Name "allows_process_start" -Default $true) -and + -not [bool](Get-PropertyValue -Item $guardrails -Name "allows_payload_traffic" -Default $true) +) + +$checks = [ordered]@{ + source_smoke_passed = ([bool]$sourceResult.passed) + source_schema_expected = ([string]$sourceResult.schema_version -eq "c19z143.remote_workspace_real_adapter_not_approved_outcome_operator_stop_summary_smoke.v1") + summary_present = ($null -ne $summary) + summary_fields_compatible = $summaryFieldsCompatible + summary_values_compatible = $summaryValuesCompatible + summary_notes_compatible = $summaryNotesCompatible + guardrails_compatible = $guardrailsCompatible +} +$failed = @($checks.GetEnumerator() | Where-Object { -not $_.Value } | ForEach-Object { $_.Key }) + +$result = [ordered]@{ + schema_version = "c19z144.remote_workspace_real_adapter_not_approved_outcome_operator_stop_summary_compatibility_smoke.v1" + source_result_path = $sourceFile + cluster_id = $ClusterID + required_summary_fields = $requiredSummaryFields + required_guardrail_fields = $requiredGuardrailFields + required_summary_notes = $requiredSummaryNotes + not_approved_outcome_operator_stop_summary = $summary + checks = $checks + failed_checks = $failed + passed = ($failed.Count -eq 0) +} + +$fullResultPath = Join-Path $repoRoot $ResultPath +$resultDir = Split-Path -Parent $fullResultPath +if ($resultDir) { New-Item -ItemType Directory -Force -Path $resultDir | Out-Null } +$result | ConvertTo-Json -Depth 100 | Set-Content -Encoding UTF8 -Path $fullResultPath + +if (-not $result.passed) { + throw "C19Z144 remote workspace real-adapter not-approved outcome operator stop summary compatibility smoke failed. Result: $fullResultPath Failed: $($failed -join ', ')" +} + +Write-Host "C19Z144 remote workspace real-adapter not-approved outcome operator stop summary compatibility smoke passed. Result: $fullResultPath" +$result diff --git a/scripts/fabric/c19z145-remote-workspace-real-adapter-not-approved-outcome-operator-stop-handoff-smoke.ps1 b/scripts/fabric/c19z145-remote-workspace-real-adapter-not-approved-outcome-operator-stop-handoff-smoke.ps1 new file mode 100644 index 0000000..97e3cd3 --- /dev/null +++ b/scripts/fabric/c19z145-remote-workspace-real-adapter-not-approved-outcome-operator-stop-handoff-smoke.ps1 @@ -0,0 +1,186 @@ +param( + [string]$ApiBaseUrl = "http://192.168.200.61:18121/api/v1", + [string]$ClusterID = "cfc0743d-d960-49fb-9de8-96e063d5e4aa", + [string]$ActorUserID = "f67d943f-5397-4b3a-a229-695fe67ad700", + [string]$RequestedNodeName = "test-1", + [string]$DefaultNodeName = "test-2", + [string]$MatrixNodeName = "test-1", + [string]$ResultPath = "artifacts\c19z145-remote-workspace-real-adapter-not-approved-outcome-operator-stop-handoff-smoke-result.json" +) + +Set-StrictMode -Version Latest +$ErrorActionPreference = "Stop" + +$repoRoot = (Resolve-Path (Join-Path $PSScriptRoot "..\..")).ProviderPath +$sourceResultPath = "artifacts\c19z145-remote-workspace-real-adapter-not-approved-outcome-operator-stop-handoff-source-result.json" +$requiredHandoffFields = @( + "schema_version", + "source_summary_schema", + "handoff_status", + "handoff_marker", + "operator_status", + "operator_action", + "operator_message", + "display_severity", + "next_allowed_entrypoint", + "attempt_allowed", + "block_reason", + "blocks_not_approved_extension", + "branch_state", + "continuation_policy", + "enablement_status", + "runtime_gate_state", + "runtime_effect", + "allows_process_start", + "allows_payload_traffic", + "guardrail_summary", + "handoff_notes" +) +$requiredGuardrailFields = @("activation_blocked", "process_start_allowed", "health_probe_enabled", "payload_traffic", "allows_process_start", "allows_payload_traffic") +$requiredHandoffNotes = @( + "operator_stop_handoff_complete", + "not_approved_branch_remains_closed", + "new_explicit_enablement_request_required", + "real_runtime_gate_not_enabled", + "process_start_disabled", + "payload_forwarding_disabled" +) + +function Get-PropertyValue { + param([object]$Item, [string]$Name, [object]$Default = $null) + if ($null -eq $Item) { return $Default } + if ($Item -is [System.Collections.IDictionary]) { + if ($Item.Contains($Name)) { return $Item[$Name] } + return $Default + } + $property = $Item.PSObject.Properties[$Name] + if ($null -eq $property) { return $Default } + return $property.Value +} + +function Test-ObjectHasFields { + param([object]$Item, [string[]]$Fields) + if ($null -eq $Item) { return $false } + foreach ($field in $Fields) { + if ($Item -is [System.Collections.IDictionary]) { + if (-not $Item.Contains($field)) { return $false } + continue + } + if ($null -eq $Item.PSObject.Properties[$field]) { return $false } + } + return $true +} + +function Test-ArrayContainsAll { + param([object[]]$Actual, [string[]]$Expected) + foreach ($item in $Expected) { + if ($Actual -notcontains $item) { return $false } + } + return $true +} + +& powershell -ExecutionPolicy Bypass -File (Join-Path $PSScriptRoot "c19z144-remote-workspace-real-adapter-not-approved-outcome-operator-stop-summary-compatibility-smoke.ps1") ` + -ApiBaseUrl $ApiBaseUrl ` + -ClusterID $ClusterID ` + -ActorUserID $ActorUserID ` + -RequestedNodeName $RequestedNodeName ` + -DefaultNodeName $DefaultNodeName ` + -MatrixNodeName $MatrixNodeName ` + -ResultPath $sourceResultPath | Out-Null + +$sourceFile = Join-Path $repoRoot $sourceResultPath +$sourceResult = Get-Content -Raw -Path $sourceFile | ConvertFrom-Json +$summary = Get-PropertyValue -Item $sourceResult -Name "not_approved_outcome_operator_stop_summary" -Default $null +$guardrails = Get-PropertyValue -Item $summary -Name "guardrail_summary" -Default $null + +$handoff = [ordered]@{ + schema_version = "rap.remote_workspace_real_adapter_not_approved_outcome_operator_stop_handoff.v1" + source_summary_schema = Get-PropertyValue -Item $summary -Name "schema_version" -Default $null + handoff_status = "operator_stop_handoff_complete" + handoff_marker = "c19z145_real_adapter_not_approved_outcome_operator_stop_handoff" + operator_status = Get-PropertyValue -Item $summary -Name "operator_status" -Default $null + operator_action = Get-PropertyValue -Item $summary -Name "operator_action" -Default $null + operator_message = Get-PropertyValue -Item $summary -Name "operator_message" -Default $null + display_severity = "blocked" + next_allowed_entrypoint = Get-PropertyValue -Item $summary -Name "next_allowed_entrypoint" -Default $null + attempt_allowed = $false + block_reason = Get-PropertyValue -Item $summary -Name "block_reason" -Default $null + blocks_not_approved_extension = Get-PropertyValue -Item $summary -Name "blocks_not_approved_extension" -Default $null + branch_state = Get-PropertyValue -Item $summary -Name "branch_state" -Default $null + continuation_policy = Get-PropertyValue -Item $summary -Name "continuation_policy" -Default $null + enablement_status = Get-PropertyValue -Item $summary -Name "enablement_status" -Default $null + runtime_gate_state = Get-PropertyValue -Item $summary -Name "runtime_gate_state" -Default $null + runtime_effect = Get-PropertyValue -Item $summary -Name "runtime_effect" -Default $null + allows_process_start = $false + allows_payload_traffic = $false + guardrail_summary = $guardrails + handoff_notes = $requiredHandoffNotes +} + +$handoffFieldsCompatible = Test-ObjectHasFields -Item $handoff -Fields $requiredHandoffFields +$handoffValuesCompatible = ( + [string](Get-PropertyValue -Item $handoff -Name "schema_version" -Default "") -eq "rap.remote_workspace_real_adapter_not_approved_outcome_operator_stop_handoff.v1" -and + [string](Get-PropertyValue -Item $handoff -Name "source_summary_schema" -Default "") -eq "rap.remote_workspace_real_adapter_not_approved_outcome_operator_stop_summary.v1" -and + [string](Get-PropertyValue -Item $handoff -Name "handoff_status" -Default "") -eq "operator_stop_handoff_complete" -and + [string](Get-PropertyValue -Item $handoff -Name "operator_status" -Default "") -eq "not_approved_branch_closed_new_request_required" -and + [string](Get-PropertyValue -Item $handoff -Name "operator_action" -Default "") -eq "keep_real_adapter_disabled_until_new_explicit_enablement_request" -and + [string](Get-PropertyValue -Item $handoff -Name "operator_message" -Default "") -eq "not_approved_branch_closed_new_request_required" -and + [string](Get-PropertyValue -Item $handoff -Name "display_severity" -Default "") -eq "blocked" -and + [string](Get-PropertyValue -Item $handoff -Name "next_allowed_entrypoint" -Default "") -eq "new_explicit_enablement_request_only" -and + -not [bool](Get-PropertyValue -Item $handoff -Name "attempt_allowed" -Default $true) -and + [string](Get-PropertyValue -Item $handoff -Name "block_reason" -Default "") -eq "new_explicit_enablement_request_required" -and + [bool](Get-PropertyValue -Item $handoff -Name "blocks_not_approved_extension" -Default $false) -and + [string](Get-PropertyValue -Item $handoff -Name "branch_state" -Default "") -eq "not_approved_branch_closed" -and + [string](Get-PropertyValue -Item $handoff -Name "continuation_policy" -Default "") -eq "do_not_continue_without_new_explicit_enablement_request" -and + [string](Get-PropertyValue -Item $handoff -Name "enablement_status" -Default "") -eq "not_enabled" -and + [string](Get-PropertyValue -Item $handoff -Name "runtime_gate_state" -Default "") -eq "validated_contract_only_not_enabled" -and + [string](Get-PropertyValue -Item $handoff -Name "runtime_effect" -Default "") -eq "contract_only_no_runtime_enablement" -and + -not [bool](Get-PropertyValue -Item $handoff -Name "allows_process_start" -Default $true) -and + -not [bool](Get-PropertyValue -Item $handoff -Name "allows_payload_traffic" -Default $true) +) +$handoffNotesCompatible = Test-ArrayContainsAll -Actual @($handoff.handoff_notes) -Expected $requiredHandoffNotes +$guardrailsCompatible = ( + (Test-ObjectHasFields -Item $guardrails -Fields $requiredGuardrailFields) -and + [bool](Get-PropertyValue -Item $guardrails -Name "activation_blocked" -Default $false) -and + -not [bool](Get-PropertyValue -Item $guardrails -Name "process_start_allowed" -Default $true) -and + -not [bool](Get-PropertyValue -Item $guardrails -Name "health_probe_enabled" -Default $true) -and + [string](Get-PropertyValue -Item $guardrails -Name "payload_traffic" -Default "") -eq "none" -and + -not [bool](Get-PropertyValue -Item $guardrails -Name "allows_process_start" -Default $true) -and + -not [bool](Get-PropertyValue -Item $guardrails -Name "allows_payload_traffic" -Default $true) +) + +$checks = [ordered]@{ + source_smoke_passed = ([bool]$sourceResult.passed) + source_schema_expected = ([string]$sourceResult.schema_version -eq "c19z144.remote_workspace_real_adapter_not_approved_outcome_operator_stop_summary_compatibility_smoke.v1") + summary_present = ($null -ne $summary) + handoff_fields_compatible = $handoffFieldsCompatible + handoff_values_compatible = $handoffValuesCompatible + handoff_notes_compatible = $handoffNotesCompatible + guardrails_compatible = $guardrailsCompatible +} +$failed = @($checks.GetEnumerator() | Where-Object { -not $_.Value } | ForEach-Object { $_.Key }) + +$result = [ordered]@{ + schema_version = "c19z145.remote_workspace_real_adapter_not_approved_outcome_operator_stop_handoff_smoke.v1" + source_result_path = $sourceFile + cluster_id = $ClusterID + required_handoff_fields = $requiredHandoffFields + required_guardrail_fields = $requiredGuardrailFields + required_handoff_notes = $requiredHandoffNotes + not_approved_outcome_operator_stop_handoff = $handoff + checks = $checks + failed_checks = $failed + passed = ($failed.Count -eq 0) +} + +$fullResultPath = Join-Path $repoRoot $ResultPath +$resultDir = Split-Path -Parent $fullResultPath +if ($resultDir) { New-Item -ItemType Directory -Force -Path $resultDir | Out-Null } +$result | ConvertTo-Json -Depth 100 | Set-Content -Encoding UTF8 -Path $fullResultPath + +if (-not $result.passed) { + throw "C19Z145 remote workspace real-adapter not-approved outcome operator stop handoff smoke failed. Result: $fullResultPath Failed: $($failed -join ', ')" +} + +Write-Host "C19Z145 remote workspace real-adapter not-approved outcome operator stop handoff smoke passed. Result: $fullResultPath" +$result diff --git a/scripts/fabric/c19z146-remote-workspace-real-adapter-not-approved-outcome-operator-stop-handoff-compatibility-smoke.ps1 b/scripts/fabric/c19z146-remote-workspace-real-adapter-not-approved-outcome-operator-stop-handoff-compatibility-smoke.ps1 new file mode 100644 index 0000000..afa9f6f --- /dev/null +++ b/scripts/fabric/c19z146-remote-workspace-real-adapter-not-approved-outcome-operator-stop-handoff-compatibility-smoke.ps1 @@ -0,0 +1,134 @@ +param( + [string]$ApiBaseUrl = "http://192.168.200.61:18121/api/v1", + [string]$ClusterID = "cfc0743d-d960-49fb-9de8-96e063d5e4aa", + [string]$ActorUserID = "f67d943f-5397-4b3a-a229-695fe67ad700", + [string]$RequestedNodeName = "test-1", + [string]$DefaultNodeName = "test-2", + [string]$MatrixNodeName = "test-1", + [string]$ResultPath = "artifacts\c19z146-remote-workspace-real-adapter-not-approved-outcome-operator-stop-handoff-compatibility-smoke-result.json" +) + +Set-StrictMode -Version Latest +$ErrorActionPreference = "Stop" + +$repoRoot = (Resolve-Path (Join-Path $PSScriptRoot "..\..")).ProviderPath +$sourceResultPath = "artifacts\c19z146-remote-workspace-real-adapter-not-approved-outcome-operator-stop-handoff-source-result.json" +$requiredHandoffFields = @("schema_version", "source_summary_schema", "handoff_status", "handoff_marker", "operator_status", "operator_action", "operator_message", "display_severity", "next_allowed_entrypoint", "attempt_allowed", "block_reason", "blocks_not_approved_extension", "branch_state", "continuation_policy", "enablement_status", "runtime_gate_state", "runtime_effect", "allows_process_start", "allows_payload_traffic", "guardrail_summary", "handoff_notes") +$requiredGuardrailFields = @("activation_blocked", "process_start_allowed", "health_probe_enabled", "payload_traffic", "allows_process_start", "allows_payload_traffic") +$requiredHandoffNotes = @("operator_stop_handoff_complete", "not_approved_branch_remains_closed", "new_explicit_enablement_request_required", "real_runtime_gate_not_enabled", "process_start_disabled", "payload_forwarding_disabled") + +function Get-PropertyValue { + param([object]$Item, [string]$Name, [object]$Default = $null) + if ($null -eq $Item) { return $Default } + if ($Item -is [System.Collections.IDictionary]) { + if ($Item.Contains($Name)) { return $Item[$Name] } + return $Default + } + $property = $Item.PSObject.Properties[$Name] + if ($null -eq $property) { return $Default } + return $property.Value +} + +function Test-ObjectHasFields { + param([object]$Item, [string[]]$Fields) + if ($null -eq $Item) { return $false } + foreach ($field in $Fields) { + if ($Item -is [System.Collections.IDictionary]) { + if (-not $Item.Contains($field)) { return $false } + continue + } + if ($null -eq $Item.PSObject.Properties[$field]) { return $false } + } + return $true +} + +function Test-ArrayContainsAll { + param([object[]]$Actual, [string[]]$Expected) + foreach ($item in $Expected) { + if ($Actual -notcontains $item) { return $false } + } + return $true +} + +& powershell -ExecutionPolicy Bypass -File (Join-Path $PSScriptRoot "c19z145-remote-workspace-real-adapter-not-approved-outcome-operator-stop-handoff-smoke.ps1") ` + -ApiBaseUrl $ApiBaseUrl ` + -ClusterID $ClusterID ` + -ActorUserID $ActorUserID ` + -RequestedNodeName $RequestedNodeName ` + -DefaultNodeName $DefaultNodeName ` + -MatrixNodeName $MatrixNodeName ` + -ResultPath $sourceResultPath | Out-Null + +$sourceFile = Join-Path $repoRoot $sourceResultPath +$sourceResult = Get-Content -Raw -Path $sourceFile | ConvertFrom-Json +$handoff = Get-PropertyValue -Item $sourceResult -Name "not_approved_outcome_operator_stop_handoff" -Default $null +$guardrails = Get-PropertyValue -Item $handoff -Name "guardrail_summary" -Default $null +$handoffNotes = @(Get-PropertyValue -Item $handoff -Name "handoff_notes" -Default @()) + +$handoffFieldsCompatible = Test-ObjectHasFields -Item $handoff -Fields $requiredHandoffFields +$handoffValuesCompatible = ( + [string](Get-PropertyValue -Item $handoff -Name "schema_version" -Default "") -eq "rap.remote_workspace_real_adapter_not_approved_outcome_operator_stop_handoff.v1" -and + [string](Get-PropertyValue -Item $handoff -Name "source_summary_schema" -Default "") -eq "rap.remote_workspace_real_adapter_not_approved_outcome_operator_stop_summary.v1" -and + [string](Get-PropertyValue -Item $handoff -Name "handoff_status" -Default "") -eq "operator_stop_handoff_complete" -and + [string](Get-PropertyValue -Item $handoff -Name "operator_status" -Default "") -eq "not_approved_branch_closed_new_request_required" -and + [string](Get-PropertyValue -Item $handoff -Name "operator_action" -Default "") -eq "keep_real_adapter_disabled_until_new_explicit_enablement_request" -and + [string](Get-PropertyValue -Item $handoff -Name "operator_message" -Default "") -eq "not_approved_branch_closed_new_request_required" -and + [string](Get-PropertyValue -Item $handoff -Name "display_severity" -Default "") -eq "blocked" -and + [string](Get-PropertyValue -Item $handoff -Name "next_allowed_entrypoint" -Default "") -eq "new_explicit_enablement_request_only" -and + -not [bool](Get-PropertyValue -Item $handoff -Name "attempt_allowed" -Default $true) -and + [string](Get-PropertyValue -Item $handoff -Name "block_reason" -Default "") -eq "new_explicit_enablement_request_required" -and + [bool](Get-PropertyValue -Item $handoff -Name "blocks_not_approved_extension" -Default $false) -and + [string](Get-PropertyValue -Item $handoff -Name "branch_state" -Default "") -eq "not_approved_branch_closed" -and + [string](Get-PropertyValue -Item $handoff -Name "continuation_policy" -Default "") -eq "do_not_continue_without_new_explicit_enablement_request" -and + [string](Get-PropertyValue -Item $handoff -Name "enablement_status" -Default "") -eq "not_enabled" -and + [string](Get-PropertyValue -Item $handoff -Name "runtime_gate_state" -Default "") -eq "validated_contract_only_not_enabled" -and + [string](Get-PropertyValue -Item $handoff -Name "runtime_effect" -Default "") -eq "contract_only_no_runtime_enablement" -and + -not [bool](Get-PropertyValue -Item $handoff -Name "allows_process_start" -Default $true) -and + -not [bool](Get-PropertyValue -Item $handoff -Name "allows_payload_traffic" -Default $true) +) +$handoffNotesCompatible = Test-ArrayContainsAll -Actual $handoffNotes -Expected $requiredHandoffNotes +$guardrailsCompatible = ( + (Test-ObjectHasFields -Item $guardrails -Fields $requiredGuardrailFields) -and + [bool](Get-PropertyValue -Item $guardrails -Name "activation_blocked" -Default $false) -and + -not [bool](Get-PropertyValue -Item $guardrails -Name "process_start_allowed" -Default $true) -and + -not [bool](Get-PropertyValue -Item $guardrails -Name "health_probe_enabled" -Default $true) -and + [string](Get-PropertyValue -Item $guardrails -Name "payload_traffic" -Default "") -eq "none" -and + -not [bool](Get-PropertyValue -Item $guardrails -Name "allows_process_start" -Default $true) -and + -not [bool](Get-PropertyValue -Item $guardrails -Name "allows_payload_traffic" -Default $true) +) + +$checks = [ordered]@{ + source_smoke_passed = ([bool]$sourceResult.passed) + source_schema_expected = ([string]$sourceResult.schema_version -eq "c19z145.remote_workspace_real_adapter_not_approved_outcome_operator_stop_handoff_smoke.v1") + handoff_present = ($null -ne $handoff) + handoff_fields_compatible = $handoffFieldsCompatible + handoff_values_compatible = $handoffValuesCompatible + handoff_notes_compatible = $handoffNotesCompatible + guardrails_compatible = $guardrailsCompatible +} +$failed = @($checks.GetEnumerator() | Where-Object { -not $_.Value } | ForEach-Object { $_.Key }) + +$result = [ordered]@{ + schema_version = "c19z146.remote_workspace_real_adapter_not_approved_outcome_operator_stop_handoff_compatibility_smoke.v1" + source_result_path = $sourceFile + cluster_id = $ClusterID + required_handoff_fields = $requiredHandoffFields + required_guardrail_fields = $requiredGuardrailFields + required_handoff_notes = $requiredHandoffNotes + not_approved_outcome_operator_stop_handoff = $handoff + checks = $checks + failed_checks = $failed + passed = ($failed.Count -eq 0) +} + +$fullResultPath = Join-Path $repoRoot $ResultPath +$resultDir = Split-Path -Parent $fullResultPath +if ($resultDir) { New-Item -ItemType Directory -Force -Path $resultDir | Out-Null } +$result | ConvertTo-Json -Depth 100 | Set-Content -Encoding UTF8 -Path $fullResultPath + +if (-not $result.passed) { + throw "C19Z146 remote workspace real-adapter not-approved outcome operator stop handoff compatibility smoke failed. Result: $fullResultPath Failed: $($failed -join ', ')" +} + +Write-Host "C19Z146 remote workspace real-adapter not-approved outcome operator stop handoff compatibility smoke passed. Result: $fullResultPath" +$result diff --git a/scripts/fabric/c19z147-remote-workspace-real-adapter-not-approved-outcome-operator-stop-handoff-digest-smoke.ps1 b/scripts/fabric/c19z147-remote-workspace-real-adapter-not-approved-outcome-operator-stop-handoff-digest-smoke.ps1 new file mode 100644 index 0000000..e0d6604 --- /dev/null +++ b/scripts/fabric/c19z147-remote-workspace-real-adapter-not-approved-outcome-operator-stop-handoff-digest-smoke.ps1 @@ -0,0 +1,177 @@ +param( + [string]$ApiBaseUrl = "http://192.168.200.61:18121/api/v1", + [string]$ClusterID = "cfc0743d-d960-49fb-9de8-96e063d5e4aa", + [string]$ActorUserID = "f67d943f-5397-4b3a-a229-695fe67ad700", + [string]$RequestedNodeName = "test-1", + [string]$DefaultNodeName = "test-2", + [string]$MatrixNodeName = "test-1", + [string]$ResultPath = "artifacts\c19z147-remote-workspace-real-adapter-not-approved-outcome-operator-stop-handoff-digest-smoke-result.json" +) + +Set-StrictMode -Version Latest +$ErrorActionPreference = "Stop" + +$repoRoot = (Resolve-Path (Join-Path $PSScriptRoot "..\..")).ProviderPath +$sourceResultPath = "artifacts\c19z147-remote-workspace-real-adapter-not-approved-outcome-operator-stop-handoff-digest-source-result.json" +$requiredDigestFields = @( + "schema_version", + "source_handoff_schema", + "digest_status", + "digest_marker", + "operator_status", + "operator_action", + "display_severity", + "next_allowed_entrypoint", + "attempt_allowed", + "block_reason", + "blocks_not_approved_extension", + "enablement_status", + "runtime_gate_state", + "runtime_effect", + "allows_process_start", + "allows_payload_traffic", + "guardrail_summary", + "digest_notes" +) +$requiredGuardrailFields = @("activation_blocked", "process_start_allowed", "health_probe_enabled", "payload_traffic", "allows_process_start", "allows_payload_traffic") +$requiredDigestNotes = @( + "operator_stop_handoff_digest_complete", + "not_approved_branch_remains_closed", + "new_explicit_enablement_request_required", + "real_runtime_gate_not_enabled", + "process_start_disabled", + "payload_forwarding_disabled" +) + +function Get-PropertyValue { + param([object]$Item, [string]$Name, [object]$Default = $null) + if ($null -eq $Item) { return $Default } + if ($Item -is [System.Collections.IDictionary]) { + if ($Item.Contains($Name)) { return $Item[$Name] } + return $Default + } + $property = $Item.PSObject.Properties[$Name] + if ($null -eq $property) { return $Default } + return $property.Value +} + +function Test-ObjectHasFields { + param([object]$Item, [string[]]$Fields) + if ($null -eq $Item) { return $false } + foreach ($field in $Fields) { + if ($Item -is [System.Collections.IDictionary]) { + if (-not $Item.Contains($field)) { return $false } + continue + } + if ($null -eq $Item.PSObject.Properties[$field]) { return $false } + } + return $true +} + +function Test-ArrayContainsAll { + param([object[]]$Actual, [string[]]$Expected) + foreach ($item in $Expected) { + if ($Actual -notcontains $item) { return $false } + } + return $true +} + +& powershell -ExecutionPolicy Bypass -File (Join-Path $PSScriptRoot "c19z146-remote-workspace-real-adapter-not-approved-outcome-operator-stop-handoff-compatibility-smoke.ps1") ` + -ApiBaseUrl $ApiBaseUrl ` + -ClusterID $ClusterID ` + -ActorUserID $ActorUserID ` + -RequestedNodeName $RequestedNodeName ` + -DefaultNodeName $DefaultNodeName ` + -MatrixNodeName $MatrixNodeName ` + -ResultPath $sourceResultPath | Out-Null + +$sourceFile = Join-Path $repoRoot $sourceResultPath +$sourceResult = Get-Content -Raw -Path $sourceFile | ConvertFrom-Json +$handoff = Get-PropertyValue -Item $sourceResult -Name "not_approved_outcome_operator_stop_handoff" -Default $null +$guardrails = Get-PropertyValue -Item $handoff -Name "guardrail_summary" -Default $null + +$digest = [ordered]@{ + schema_version = "rap.remote_workspace_real_adapter_not_approved_outcome_operator_stop_handoff_digest.v1" + source_handoff_schema = Get-PropertyValue -Item $handoff -Name "schema_version" -Default $null + digest_status = "operator_stop_handoff_digest_complete" + digest_marker = "c19z147_real_adapter_not_approved_outcome_operator_stop_handoff_digest" + operator_status = Get-PropertyValue -Item $handoff -Name "operator_status" -Default $null + operator_action = Get-PropertyValue -Item $handoff -Name "operator_action" -Default $null + display_severity = Get-PropertyValue -Item $handoff -Name "display_severity" -Default $null + next_allowed_entrypoint = Get-PropertyValue -Item $handoff -Name "next_allowed_entrypoint" -Default $null + attempt_allowed = $false + block_reason = Get-PropertyValue -Item $handoff -Name "block_reason" -Default $null + blocks_not_approved_extension = Get-PropertyValue -Item $handoff -Name "blocks_not_approved_extension" -Default $null + enablement_status = Get-PropertyValue -Item $handoff -Name "enablement_status" -Default $null + runtime_gate_state = Get-PropertyValue -Item $handoff -Name "runtime_gate_state" -Default $null + runtime_effect = Get-PropertyValue -Item $handoff -Name "runtime_effect" -Default $null + allows_process_start = $false + allows_payload_traffic = $false + guardrail_summary = $guardrails + digest_notes = $requiredDigestNotes +} + +$digestFieldsCompatible = Test-ObjectHasFields -Item $digest -Fields $requiredDigestFields +$digestValuesCompatible = ( + [string](Get-PropertyValue -Item $digest -Name "schema_version" -Default "") -eq "rap.remote_workspace_real_adapter_not_approved_outcome_operator_stop_handoff_digest.v1" -and + [string](Get-PropertyValue -Item $digest -Name "source_handoff_schema" -Default "") -eq "rap.remote_workspace_real_adapter_not_approved_outcome_operator_stop_handoff.v1" -and + [string](Get-PropertyValue -Item $digest -Name "digest_status" -Default "") -eq "operator_stop_handoff_digest_complete" -and + [string](Get-PropertyValue -Item $digest -Name "operator_status" -Default "") -eq "not_approved_branch_closed_new_request_required" -and + [string](Get-PropertyValue -Item $digest -Name "operator_action" -Default "") -eq "keep_real_adapter_disabled_until_new_explicit_enablement_request" -and + [string](Get-PropertyValue -Item $digest -Name "display_severity" -Default "") -eq "blocked" -and + [string](Get-PropertyValue -Item $digest -Name "next_allowed_entrypoint" -Default "") -eq "new_explicit_enablement_request_only" -and + -not [bool](Get-PropertyValue -Item $digest -Name "attempt_allowed" -Default $true) -and + [string](Get-PropertyValue -Item $digest -Name "block_reason" -Default "") -eq "new_explicit_enablement_request_required" -and + [bool](Get-PropertyValue -Item $digest -Name "blocks_not_approved_extension" -Default $false) -and + [string](Get-PropertyValue -Item $digest -Name "enablement_status" -Default "") -eq "not_enabled" -and + [string](Get-PropertyValue -Item $digest -Name "runtime_gate_state" -Default "") -eq "validated_contract_only_not_enabled" -and + [string](Get-PropertyValue -Item $digest -Name "runtime_effect" -Default "") -eq "contract_only_no_runtime_enablement" -and + -not [bool](Get-PropertyValue -Item $digest -Name "allows_process_start" -Default $true) -and + -not [bool](Get-PropertyValue -Item $digest -Name "allows_payload_traffic" -Default $true) +) +$digestNotesCompatible = Test-ArrayContainsAll -Actual @($digest.digest_notes) -Expected $requiredDigestNotes +$guardrailsCompatible = ( + (Test-ObjectHasFields -Item $guardrails -Fields $requiredGuardrailFields) -and + [bool](Get-PropertyValue -Item $guardrails -Name "activation_blocked" -Default $false) -and + -not [bool](Get-PropertyValue -Item $guardrails -Name "process_start_allowed" -Default $true) -and + -not [bool](Get-PropertyValue -Item $guardrails -Name "health_probe_enabled" -Default $true) -and + [string](Get-PropertyValue -Item $guardrails -Name "payload_traffic" -Default "") -eq "none" -and + -not [bool](Get-PropertyValue -Item $guardrails -Name "allows_process_start" -Default $true) -and + -not [bool](Get-PropertyValue -Item $guardrails -Name "allows_payload_traffic" -Default $true) +) + +$checks = [ordered]@{ + source_smoke_passed = ([bool]$sourceResult.passed) + source_schema_expected = ([string]$sourceResult.schema_version -eq "c19z146.remote_workspace_real_adapter_not_approved_outcome_operator_stop_handoff_compatibility_smoke.v1") + handoff_present = ($null -ne $handoff) + digest_fields_compatible = $digestFieldsCompatible + digest_values_compatible = $digestValuesCompatible + digest_notes_compatible = $digestNotesCompatible + guardrails_compatible = $guardrailsCompatible +} +$failed = @($checks.GetEnumerator() | Where-Object { -not $_.Value } | ForEach-Object { $_.Key }) + +$result = [ordered]@{ + schema_version = "c19z147.remote_workspace_real_adapter_not_approved_outcome_operator_stop_handoff_digest_smoke.v1" + source_result_path = $sourceFile + cluster_id = $ClusterID + required_digest_fields = $requiredDigestFields + required_guardrail_fields = $requiredGuardrailFields + required_digest_notes = $requiredDigestNotes + not_approved_outcome_operator_stop_handoff_digest = $digest + checks = $checks + failed_checks = $failed + passed = ($failed.Count -eq 0) +} + +$fullResultPath = Join-Path $repoRoot $ResultPath +$resultDir = Split-Path -Parent $fullResultPath +if ($resultDir) { New-Item -ItemType Directory -Force -Path $resultDir | Out-Null } +$result | ConvertTo-Json -Depth 100 | Set-Content -Encoding UTF8 -Path $fullResultPath + +if (-not $result.passed) { + throw "C19Z147 remote workspace real-adapter not-approved outcome operator stop handoff digest smoke failed. Result: $fullResultPath Failed: $($failed -join ', ')" +} + +Write-Host "C19Z147 remote workspace real-adapter not-approved outcome operator stop handoff digest smoke passed. Result: $fullResultPath" +$result diff --git a/scripts/fabric/c19z148-remote-workspace-real-adapter-not-approved-outcome-operator-stop-handoff-digest-compatibility-smoke.ps1 b/scripts/fabric/c19z148-remote-workspace-real-adapter-not-approved-outcome-operator-stop-handoff-digest-compatibility-smoke.ps1 new file mode 100644 index 0000000..1754e0e --- /dev/null +++ b/scripts/fabric/c19z148-remote-workspace-real-adapter-not-approved-outcome-operator-stop-handoff-digest-compatibility-smoke.ps1 @@ -0,0 +1,131 @@ +param( + [string]$ApiBaseUrl = "http://192.168.200.61:18121/api/v1", + [string]$ClusterID = "cfc0743d-d960-49fb-9de8-96e063d5e4aa", + [string]$ActorUserID = "f67d943f-5397-4b3a-a229-695fe67ad700", + [string]$RequestedNodeName = "test-1", + [string]$DefaultNodeName = "test-2", + [string]$MatrixNodeName = "test-1", + [string]$ResultPath = "artifacts\c19z148-remote-workspace-real-adapter-not-approved-outcome-operator-stop-handoff-digest-compatibility-smoke-result.json" +) + +Set-StrictMode -Version Latest +$ErrorActionPreference = "Stop" + +$repoRoot = (Resolve-Path (Join-Path $PSScriptRoot "..\..")).ProviderPath +$sourceResultPath = "artifacts\c19z148-remote-workspace-real-adapter-not-approved-outcome-operator-stop-handoff-digest-source-result.json" +$requiredDigestFields = @("schema_version", "source_handoff_schema", "digest_status", "digest_marker", "operator_status", "operator_action", "display_severity", "next_allowed_entrypoint", "attempt_allowed", "block_reason", "blocks_not_approved_extension", "enablement_status", "runtime_gate_state", "runtime_effect", "allows_process_start", "allows_payload_traffic", "guardrail_summary", "digest_notes") +$requiredGuardrailFields = @("activation_blocked", "process_start_allowed", "health_probe_enabled", "payload_traffic", "allows_process_start", "allows_payload_traffic") +$requiredDigestNotes = @("operator_stop_handoff_digest_complete", "not_approved_branch_remains_closed", "new_explicit_enablement_request_required", "real_runtime_gate_not_enabled", "process_start_disabled", "payload_forwarding_disabled") + +function Get-PropertyValue { + param([object]$Item, [string]$Name, [object]$Default = $null) + if ($null -eq $Item) { return $Default } + if ($Item -is [System.Collections.IDictionary]) { + if ($Item.Contains($Name)) { return $Item[$Name] } + return $Default + } + $property = $Item.PSObject.Properties[$Name] + if ($null -eq $property) { return $Default } + return $property.Value +} + +function Test-ObjectHasFields { + param([object]$Item, [string[]]$Fields) + if ($null -eq $Item) { return $false } + foreach ($field in $Fields) { + if ($Item -is [System.Collections.IDictionary]) { + if (-not $Item.Contains($field)) { return $false } + continue + } + if ($null -eq $Item.PSObject.Properties[$field]) { return $false } + } + return $true +} + +function Test-ArrayContainsAll { + param([object[]]$Actual, [string[]]$Expected) + foreach ($item in $Expected) { + if ($Actual -notcontains $item) { return $false } + } + return $true +} + +& powershell -ExecutionPolicy Bypass -File (Join-Path $PSScriptRoot "c19z147-remote-workspace-real-adapter-not-approved-outcome-operator-stop-handoff-digest-smoke.ps1") ` + -ApiBaseUrl $ApiBaseUrl ` + -ClusterID $ClusterID ` + -ActorUserID $ActorUserID ` + -RequestedNodeName $RequestedNodeName ` + -DefaultNodeName $DefaultNodeName ` + -MatrixNodeName $MatrixNodeName ` + -ResultPath $sourceResultPath | Out-Null + +$sourceFile = Join-Path $repoRoot $sourceResultPath +$sourceResult = Get-Content -Raw -Path $sourceFile | ConvertFrom-Json +$digest = Get-PropertyValue -Item $sourceResult -Name "not_approved_outcome_operator_stop_handoff_digest" -Default $null +$guardrails = Get-PropertyValue -Item $digest -Name "guardrail_summary" -Default $null +$digestNotes = @(Get-PropertyValue -Item $digest -Name "digest_notes" -Default @()) + +$digestFieldsCompatible = Test-ObjectHasFields -Item $digest -Fields $requiredDigestFields +$digestValuesCompatible = ( + [string](Get-PropertyValue -Item $digest -Name "schema_version" -Default "") -eq "rap.remote_workspace_real_adapter_not_approved_outcome_operator_stop_handoff_digest.v1" -and + [string](Get-PropertyValue -Item $digest -Name "source_handoff_schema" -Default "") -eq "rap.remote_workspace_real_adapter_not_approved_outcome_operator_stop_handoff.v1" -and + [string](Get-PropertyValue -Item $digest -Name "digest_status" -Default "") -eq "operator_stop_handoff_digest_complete" -and + [string](Get-PropertyValue -Item $digest -Name "operator_status" -Default "") -eq "not_approved_branch_closed_new_request_required" -and + [string](Get-PropertyValue -Item $digest -Name "operator_action" -Default "") -eq "keep_real_adapter_disabled_until_new_explicit_enablement_request" -and + [string](Get-PropertyValue -Item $digest -Name "display_severity" -Default "") -eq "blocked" -and + [string](Get-PropertyValue -Item $digest -Name "next_allowed_entrypoint" -Default "") -eq "new_explicit_enablement_request_only" -and + -not [bool](Get-PropertyValue -Item $digest -Name "attempt_allowed" -Default $true) -and + [string](Get-PropertyValue -Item $digest -Name "block_reason" -Default "") -eq "new_explicit_enablement_request_required" -and + [bool](Get-PropertyValue -Item $digest -Name "blocks_not_approved_extension" -Default $false) -and + [string](Get-PropertyValue -Item $digest -Name "enablement_status" -Default "") -eq "not_enabled" -and + [string](Get-PropertyValue -Item $digest -Name "runtime_gate_state" -Default "") -eq "validated_contract_only_not_enabled" -and + [string](Get-PropertyValue -Item $digest -Name "runtime_effect" -Default "") -eq "contract_only_no_runtime_enablement" -and + -not [bool](Get-PropertyValue -Item $digest -Name "allows_process_start" -Default $true) -and + -not [bool](Get-PropertyValue -Item $digest -Name "allows_payload_traffic" -Default $true) +) +$digestNotesCompatible = Test-ArrayContainsAll -Actual $digestNotes -Expected $requiredDigestNotes +$guardrailsCompatible = ( + (Test-ObjectHasFields -Item $guardrails -Fields $requiredGuardrailFields) -and + [bool](Get-PropertyValue -Item $guardrails -Name "activation_blocked" -Default $false) -and + -not [bool](Get-PropertyValue -Item $guardrails -Name "process_start_allowed" -Default $true) -and + -not [bool](Get-PropertyValue -Item $guardrails -Name "health_probe_enabled" -Default $true) -and + [string](Get-PropertyValue -Item $guardrails -Name "payload_traffic" -Default "") -eq "none" -and + -not [bool](Get-PropertyValue -Item $guardrails -Name "allows_process_start" -Default $true) -and + -not [bool](Get-PropertyValue -Item $guardrails -Name "allows_payload_traffic" -Default $true) +) + +$checks = [ordered]@{ + source_smoke_passed = ([bool]$sourceResult.passed) + source_schema_expected = ([string]$sourceResult.schema_version -eq "c19z147.remote_workspace_real_adapter_not_approved_outcome_operator_stop_handoff_digest_smoke.v1") + digest_present = ($null -ne $digest) + digest_fields_compatible = $digestFieldsCompatible + digest_values_compatible = $digestValuesCompatible + digest_notes_compatible = $digestNotesCompatible + guardrails_compatible = $guardrailsCompatible +} +$failed = @($checks.GetEnumerator() | Where-Object { -not $_.Value } | ForEach-Object { $_.Key }) + +$result = [ordered]@{ + schema_version = "c19z148.remote_workspace_real_adapter_not_approved_outcome_operator_stop_handoff_digest_compatibility_smoke.v1" + source_result_path = $sourceFile + cluster_id = $ClusterID + required_digest_fields = $requiredDigestFields + required_guardrail_fields = $requiredGuardrailFields + required_digest_notes = $requiredDigestNotes + not_approved_outcome_operator_stop_handoff_digest = $digest + checks = $checks + failed_checks = $failed + passed = ($failed.Count -eq 0) +} + +$fullResultPath = Join-Path $repoRoot $ResultPath +$resultDir = Split-Path -Parent $fullResultPath +if ($resultDir) { New-Item -ItemType Directory -Force -Path $resultDir | Out-Null } +$result | ConvertTo-Json -Depth 100 | Set-Content -Encoding UTF8 -Path $fullResultPath + +if (-not $result.passed) { + throw "C19Z148 remote workspace real-adapter not-approved outcome operator stop handoff digest compatibility smoke failed. Result: $fullResultPath Failed: $($failed -join ', ')" +} + +Write-Host "C19Z148 remote workspace real-adapter not-approved outcome operator stop handoff digest compatibility smoke passed. Result: $fullResultPath" +$result diff --git a/scripts/fabric/c19z149-remote-workspace-real-adapter-not-approved-outcome-operator-stop-status-snapshot-smoke.ps1 b/scripts/fabric/c19z149-remote-workspace-real-adapter-not-approved-outcome-operator-stop-status-snapshot-smoke.ps1 new file mode 100644 index 0000000..30de87d --- /dev/null +++ b/scripts/fabric/c19z149-remote-workspace-real-adapter-not-approved-outcome-operator-stop-status-snapshot-smoke.ps1 @@ -0,0 +1,177 @@ +param( + [string]$ApiBaseUrl = "http://192.168.200.61:18121/api/v1", + [string]$ClusterID = "cfc0743d-d960-49fb-9de8-96e063d5e4aa", + [string]$ActorUserID = "f67d943f-5397-4b3a-a229-695fe67ad700", + [string]$RequestedNodeName = "test-1", + [string]$DefaultNodeName = "test-2", + [string]$MatrixNodeName = "test-1", + [string]$ResultPath = "artifacts\c19z149-remote-workspace-real-adapter-not-approved-outcome-operator-stop-status-snapshot-smoke-result.json" +) + +Set-StrictMode -Version Latest +$ErrorActionPreference = "Stop" + +$repoRoot = (Resolve-Path (Join-Path $PSScriptRoot "..\..")).ProviderPath +$sourceResultPath = "artifacts\c19z149-remote-workspace-real-adapter-not-approved-outcome-operator-stop-status-snapshot-source-result.json" +$requiredSnapshotFields = @( + "schema_version", + "source_digest_schema", + "snapshot_status", + "snapshot_marker", + "operator_status", + "operator_action", + "display_severity", + "next_allowed_entrypoint", + "attempt_allowed", + "block_reason", + "blocks_not_approved_extension", + "enablement_status", + "runtime_gate_state", + "runtime_effect", + "allows_process_start", + "allows_payload_traffic", + "guardrail_summary", + "snapshot_notes" +) +$requiredGuardrailFields = @("activation_blocked", "process_start_allowed", "health_probe_enabled", "payload_traffic", "allows_process_start", "allows_payload_traffic") +$requiredSnapshotNotes = @( + "operator_stop_status_snapshot_complete", + "not_approved_branch_remains_closed", + "new_explicit_enablement_request_required", + "real_runtime_gate_not_enabled", + "process_start_disabled", + "payload_forwarding_disabled" +) + +function Get-PropertyValue { + param([object]$Item, [string]$Name, [object]$Default = $null) + if ($null -eq $Item) { return $Default } + if ($Item -is [System.Collections.IDictionary]) { + if ($Item.Contains($Name)) { return $Item[$Name] } + return $Default + } + $property = $Item.PSObject.Properties[$Name] + if ($null -eq $property) { return $Default } + return $property.Value +} + +function Test-ObjectHasFields { + param([object]$Item, [string[]]$Fields) + if ($null -eq $Item) { return $false } + foreach ($field in $Fields) { + if ($Item -is [System.Collections.IDictionary]) { + if (-not $Item.Contains($field)) { return $false } + continue + } + if ($null -eq $Item.PSObject.Properties[$field]) { return $false } + } + return $true +} + +function Test-ArrayContainsAll { + param([object[]]$Actual, [string[]]$Expected) + foreach ($item in $Expected) { + if ($Actual -notcontains $item) { return $false } + } + return $true +} + +& powershell -ExecutionPolicy Bypass -File (Join-Path $PSScriptRoot "c19z148-remote-workspace-real-adapter-not-approved-outcome-operator-stop-handoff-digest-compatibility-smoke.ps1") ` + -ApiBaseUrl $ApiBaseUrl ` + -ClusterID $ClusterID ` + -ActorUserID $ActorUserID ` + -RequestedNodeName $RequestedNodeName ` + -DefaultNodeName $DefaultNodeName ` + -MatrixNodeName $MatrixNodeName ` + -ResultPath $sourceResultPath | Out-Null + +$sourceFile = Join-Path $repoRoot $sourceResultPath +$sourceResult = Get-Content -Raw -Path $sourceFile | ConvertFrom-Json +$digest = Get-PropertyValue -Item $sourceResult -Name "not_approved_outcome_operator_stop_handoff_digest" -Default $null +$guardrails = Get-PropertyValue -Item $digest -Name "guardrail_summary" -Default $null + +$snapshot = [ordered]@{ + schema_version = "rap.remote_workspace_real_adapter_not_approved_outcome_operator_stop_status_snapshot.v1" + source_digest_schema = Get-PropertyValue -Item $digest -Name "schema_version" -Default $null + snapshot_status = "operator_stop_status_snapshot_complete" + snapshot_marker = "c19z149_real_adapter_not_approved_outcome_operator_stop_status_snapshot" + operator_status = Get-PropertyValue -Item $digest -Name "operator_status" -Default $null + operator_action = Get-PropertyValue -Item $digest -Name "operator_action" -Default $null + display_severity = Get-PropertyValue -Item $digest -Name "display_severity" -Default $null + next_allowed_entrypoint = Get-PropertyValue -Item $digest -Name "next_allowed_entrypoint" -Default $null + attempt_allowed = $false + block_reason = Get-PropertyValue -Item $digest -Name "block_reason" -Default $null + blocks_not_approved_extension = Get-PropertyValue -Item $digest -Name "blocks_not_approved_extension" -Default $null + enablement_status = Get-PropertyValue -Item $digest -Name "enablement_status" -Default $null + runtime_gate_state = Get-PropertyValue -Item $digest -Name "runtime_gate_state" -Default $null + runtime_effect = Get-PropertyValue -Item $digest -Name "runtime_effect" -Default $null + allows_process_start = $false + allows_payload_traffic = $false + guardrail_summary = $guardrails + snapshot_notes = $requiredSnapshotNotes +} + +$snapshotFieldsCompatible = Test-ObjectHasFields -Item $snapshot -Fields $requiredSnapshotFields +$snapshotValuesCompatible = ( + [string](Get-PropertyValue -Item $snapshot -Name "schema_version" -Default "") -eq "rap.remote_workspace_real_adapter_not_approved_outcome_operator_stop_status_snapshot.v1" -and + [string](Get-PropertyValue -Item $snapshot -Name "source_digest_schema" -Default "") -eq "rap.remote_workspace_real_adapter_not_approved_outcome_operator_stop_handoff_digest.v1" -and + [string](Get-PropertyValue -Item $snapshot -Name "snapshot_status" -Default "") -eq "operator_stop_status_snapshot_complete" -and + [string](Get-PropertyValue -Item $snapshot -Name "operator_status" -Default "") -eq "not_approved_branch_closed_new_request_required" -and + [string](Get-PropertyValue -Item $snapshot -Name "operator_action" -Default "") -eq "keep_real_adapter_disabled_until_new_explicit_enablement_request" -and + [string](Get-PropertyValue -Item $snapshot -Name "display_severity" -Default "") -eq "blocked" -and + [string](Get-PropertyValue -Item $snapshot -Name "next_allowed_entrypoint" -Default "") -eq "new_explicit_enablement_request_only" -and + -not [bool](Get-PropertyValue -Item $snapshot -Name "attempt_allowed" -Default $true) -and + [string](Get-PropertyValue -Item $snapshot -Name "block_reason" -Default "") -eq "new_explicit_enablement_request_required" -and + [bool](Get-PropertyValue -Item $snapshot -Name "blocks_not_approved_extension" -Default $false) -and + [string](Get-PropertyValue -Item $snapshot -Name "enablement_status" -Default "") -eq "not_enabled" -and + [string](Get-PropertyValue -Item $snapshot -Name "runtime_gate_state" -Default "") -eq "validated_contract_only_not_enabled" -and + [string](Get-PropertyValue -Item $snapshot -Name "runtime_effect" -Default "") -eq "contract_only_no_runtime_enablement" -and + -not [bool](Get-PropertyValue -Item $snapshot -Name "allows_process_start" -Default $true) -and + -not [bool](Get-PropertyValue -Item $snapshot -Name "allows_payload_traffic" -Default $true) +) +$snapshotNotesCompatible = Test-ArrayContainsAll -Actual @($snapshot.snapshot_notes) -Expected $requiredSnapshotNotes +$guardrailsCompatible = ( + (Test-ObjectHasFields -Item $guardrails -Fields $requiredGuardrailFields) -and + [bool](Get-PropertyValue -Item $guardrails -Name "activation_blocked" -Default $false) -and + -not [bool](Get-PropertyValue -Item $guardrails -Name "process_start_allowed" -Default $true) -and + -not [bool](Get-PropertyValue -Item $guardrails -Name "health_probe_enabled" -Default $true) -and + [string](Get-PropertyValue -Item $guardrails -Name "payload_traffic" -Default "") -eq "none" -and + -not [bool](Get-PropertyValue -Item $guardrails -Name "allows_process_start" -Default $true) -and + -not [bool](Get-PropertyValue -Item $guardrails -Name "allows_payload_traffic" -Default $true) +) + +$checks = [ordered]@{ + source_smoke_passed = ([bool]$sourceResult.passed) + source_schema_expected = ([string]$sourceResult.schema_version -eq "c19z148.remote_workspace_real_adapter_not_approved_outcome_operator_stop_handoff_digest_compatibility_smoke.v1") + digest_present = ($null -ne $digest) + snapshot_fields_compatible = $snapshotFieldsCompatible + snapshot_values_compatible = $snapshotValuesCompatible + snapshot_notes_compatible = $snapshotNotesCompatible + guardrails_compatible = $guardrailsCompatible +} +$failed = @($checks.GetEnumerator() | Where-Object { -not $_.Value } | ForEach-Object { $_.Key }) + +$result = [ordered]@{ + schema_version = "c19z149.remote_workspace_real_adapter_not_approved_outcome_operator_stop_status_snapshot_smoke.v1" + source_result_path = $sourceFile + cluster_id = $ClusterID + required_snapshot_fields = $requiredSnapshotFields + required_guardrail_fields = $requiredGuardrailFields + required_snapshot_notes = $requiredSnapshotNotes + not_approved_outcome_operator_stop_status_snapshot = $snapshot + checks = $checks + failed_checks = $failed + passed = ($failed.Count -eq 0) +} + +$fullResultPath = Join-Path $repoRoot $ResultPath +$resultDir = Split-Path -Parent $fullResultPath +if ($resultDir) { New-Item -ItemType Directory -Force -Path $resultDir | Out-Null } +$result | ConvertTo-Json -Depth 100 | Set-Content -Encoding UTF8 -Path $fullResultPath + +if (-not $result.passed) { + throw "C19Z149 remote workspace real-adapter not-approved outcome operator stop status snapshot smoke failed. Result: $fullResultPath Failed: $($failed -join ', ')" +} + +Write-Host "C19Z149 remote workspace real-adapter not-approved outcome operator stop status snapshot smoke passed. Result: $fullResultPath" +$result diff --git a/scripts/fabric/c19z15-remote-workspace-mailbox-preflight-attention-reason-smoke.ps1 b/scripts/fabric/c19z15-remote-workspace-mailbox-preflight-attention-reason-smoke.ps1 new file mode 100644 index 0000000..00ad708 --- /dev/null +++ b/scripts/fabric/c19z15-remote-workspace-mailbox-preflight-attention-reason-smoke.ps1 @@ -0,0 +1,83 @@ +param( + [string]$ApiBaseUrl = "http://192.168.200.61:18121/api/v1", + [string]$ClusterID = "cfc0743d-d960-49fb-9de8-96e063d5e4aa", + [string]$ActorUserID = "f67d943f-5397-4b3a-a229-695fe67ad700", + [string]$EntryNodeName = "test-1", + [string]$ExitNodeName = "test-2", + [string]$EntryBaseUrl = "http://192.168.200.61:19131", + [string]$ResultPath = "artifacts\c19z15-remote-workspace-mailbox-preflight-attention-reason-smoke-result.json" +) + +Set-StrictMode -Version Latest +$ErrorActionPreference = "Stop" + +$repoRoot = (Resolve-Path (Join-Path $PSScriptRoot "..\..")).ProviderPath +$sourceResultPath = "artifacts\c19z15-remote-workspace-mailbox-preflight-attention-reason-source-result.json" + +function Get-PropertyValue { + param([object]$Item, [string]$Name, [object]$Default = $null) + if ($null -eq $Item) { return $Default } + $property = $Item.PSObject.Properties[$Name] + if ($null -eq $property) { return $Default } + return $property.Value +} + +function Test-AttentionReason { + param([object]$Sink, [string]$AdapterSessionID) + if ($null -eq $Sink) { return $false } + $readiness = Get-PropertyValue -Item $Sink -Name "adapter_runtime_readiness" -Default $null + $rollup = Get-PropertyValue -Item $readiness -Name "last_preflight" -Default $null + if ($null -eq $rollup) { return $false } + return ( + [string](Get-PropertyValue -Item $readiness -Name "adapter_session_id" -Default "") -eq $AdapterSessionID -and + [string](Get-PropertyValue -Item $readiness -Name "preflight_attention_status" -Default "") -eq "repeated_resync_required" -and + [string](Get-PropertyValue -Item $readiness -Name "preflight_attention_reason" -Default "") -eq "resync_required_preflight_repeated" -and + [string](Get-PropertyValue -Item $rollup -Name "preflight_attention_status" -Default "") -eq "repeated_resync_required" -and + [string](Get-PropertyValue -Item $rollup -Name "preflight_attention_reason" -Default "") -eq "resync_required_preflight_repeated" + ) +} + +$source = & powershell -ExecutionPolicy Bypass -File (Join-Path $PSScriptRoot "c19z14-remote-workspace-mailbox-preflight-repeated-attention-smoke.ps1") ` + -ApiBaseUrl $ApiBaseUrl ` + -ClusterID $ClusterID ` + -ActorUserID $ActorUserID ` + -EntryNodeName $EntryNodeName ` + -ExitNodeName $ExitNodeName ` + -EntryBaseUrl $EntryBaseUrl ` + -ResultPath $sourceResultPath + +$sourceFile = Join-Path $repoRoot $sourceResultPath +$sourceResult = Get-Content -Raw -Path $sourceFile | ConvertFrom-Json +$adapterSessionID = [string](Get-PropertyValue -Item $sourceResult -Name "adapter_session_id" -Default "") +$observed = Get-PropertyValue -Item $sourceResult -Name "observed" -Default $null + +$checks = [ordered]@{ + source_smoke_passed = ([bool]$sourceResult.passed) + adapter_session_id_present = ($adapterSessionID -match "^rap-rw-adapter-session-[0-9a-f]{24}$") + workload_attention_reason_visible = (Test-AttentionReason -Sink $observed.workload_sink -AdapterSessionID $adapterSessionID) + telemetry_attention_reason_visible = (Test-AttentionReason -Sink $observed.telemetry_sink -AdapterSessionID $adapterSessionID) +} +$failed = @($checks.GetEnumerator() | Where-Object { -not $_.Value } | ForEach-Object { $_.Key }) + +$result = [ordered]@{ + schema_version = "c19z15.remote_workspace_mailbox_preflight_attention_reason_smoke.v1" + source_result_path = $sourceFile + cluster_id = $ClusterID + adapter_session_id = $adapterSessionID + observed = $observed + checks = $checks + failed_checks = $failed + passed = ($failed.Count -eq 0) +} + +$fullResultPath = Join-Path $repoRoot $ResultPath +$resultDir = Split-Path -Parent $fullResultPath +if ($resultDir) { New-Item -ItemType Directory -Force -Path $resultDir | Out-Null } +$result | ConvertTo-Json -Depth 100 | Set-Content -Encoding UTF8 -Path $fullResultPath + +if (-not $result.passed) { + throw "C19Z15 remote workspace mailbox preflight attention reason smoke failed. Result: $fullResultPath Failed: $($failed -join ', ')" +} + +Write-Host "C19Z15 remote workspace mailbox preflight attention reason smoke passed. Result: $fullResultPath" +$result diff --git a/scripts/fabric/c19z150-remote-workspace-real-adapter-not-approved-outcome-operator-stop-status-snapshot-compatibility-smoke.ps1 b/scripts/fabric/c19z150-remote-workspace-real-adapter-not-approved-outcome-operator-stop-status-snapshot-compatibility-smoke.ps1 new file mode 100644 index 0000000..78b8df8 --- /dev/null +++ b/scripts/fabric/c19z150-remote-workspace-real-adapter-not-approved-outcome-operator-stop-status-snapshot-compatibility-smoke.ps1 @@ -0,0 +1,131 @@ +param( + [string]$ApiBaseUrl = "http://192.168.200.61:18121/api/v1", + [string]$ClusterID = "cfc0743d-d960-49fb-9de8-96e063d5e4aa", + [string]$ActorUserID = "f67d943f-5397-4b3a-a229-695fe67ad700", + [string]$RequestedNodeName = "test-1", + [string]$DefaultNodeName = "test-2", + [string]$MatrixNodeName = "test-1", + [string]$ResultPath = "artifacts\c19z150-remote-workspace-real-adapter-not-approved-outcome-operator-stop-status-snapshot-compatibility-smoke-result.json" +) + +Set-StrictMode -Version Latest +$ErrorActionPreference = "Stop" + +$repoRoot = (Resolve-Path (Join-Path $PSScriptRoot "..\..")).ProviderPath +$sourceResultPath = "artifacts\c19z150-remote-workspace-real-adapter-not-approved-outcome-operator-stop-status-snapshot-source-result.json" +$requiredSnapshotFields = @("schema_version", "source_digest_schema", "snapshot_status", "snapshot_marker", "operator_status", "operator_action", "display_severity", "next_allowed_entrypoint", "attempt_allowed", "block_reason", "blocks_not_approved_extension", "enablement_status", "runtime_gate_state", "runtime_effect", "allows_process_start", "allows_payload_traffic", "guardrail_summary", "snapshot_notes") +$requiredGuardrailFields = @("activation_blocked", "process_start_allowed", "health_probe_enabled", "payload_traffic", "allows_process_start", "allows_payload_traffic") +$requiredSnapshotNotes = @("operator_stop_status_snapshot_complete", "not_approved_branch_remains_closed", "new_explicit_enablement_request_required", "real_runtime_gate_not_enabled", "process_start_disabled", "payload_forwarding_disabled") + +function Get-PropertyValue { + param([object]$Item, [string]$Name, [object]$Default = $null) + if ($null -eq $Item) { return $Default } + if ($Item -is [System.Collections.IDictionary]) { + if ($Item.Contains($Name)) { return $Item[$Name] } + return $Default + } + $property = $Item.PSObject.Properties[$Name] + if ($null -eq $property) { return $Default } + return $property.Value +} + +function Test-ObjectHasFields { + param([object]$Item, [string[]]$Fields) + if ($null -eq $Item) { return $false } + foreach ($field in $Fields) { + if ($Item -is [System.Collections.IDictionary]) { + if (-not $Item.Contains($field)) { return $false } + continue + } + if ($null -eq $Item.PSObject.Properties[$field]) { return $false } + } + return $true +} + +function Test-ArrayContainsAll { + param([object[]]$Actual, [string[]]$Expected) + foreach ($item in $Expected) { + if ($Actual -notcontains $item) { return $false } + } + return $true +} + +& powershell -ExecutionPolicy Bypass -File (Join-Path $PSScriptRoot "c19z149-remote-workspace-real-adapter-not-approved-outcome-operator-stop-status-snapshot-smoke.ps1") ` + -ApiBaseUrl $ApiBaseUrl ` + -ClusterID $ClusterID ` + -ActorUserID $ActorUserID ` + -RequestedNodeName $RequestedNodeName ` + -DefaultNodeName $DefaultNodeName ` + -MatrixNodeName $MatrixNodeName ` + -ResultPath $sourceResultPath | Out-Null + +$sourceFile = Join-Path $repoRoot $sourceResultPath +$sourceResult = Get-Content -Raw -Path $sourceFile | ConvertFrom-Json +$snapshot = Get-PropertyValue -Item $sourceResult -Name "not_approved_outcome_operator_stop_status_snapshot" -Default $null +$guardrails = Get-PropertyValue -Item $snapshot -Name "guardrail_summary" -Default $null +$snapshotNotes = @(Get-PropertyValue -Item $snapshot -Name "snapshot_notes" -Default @()) + +$snapshotFieldsCompatible = Test-ObjectHasFields -Item $snapshot -Fields $requiredSnapshotFields +$snapshotValuesCompatible = ( + [string](Get-PropertyValue -Item $snapshot -Name "schema_version" -Default "") -eq "rap.remote_workspace_real_adapter_not_approved_outcome_operator_stop_status_snapshot.v1" -and + [string](Get-PropertyValue -Item $snapshot -Name "source_digest_schema" -Default "") -eq "rap.remote_workspace_real_adapter_not_approved_outcome_operator_stop_handoff_digest.v1" -and + [string](Get-PropertyValue -Item $snapshot -Name "snapshot_status" -Default "") -eq "operator_stop_status_snapshot_complete" -and + [string](Get-PropertyValue -Item $snapshot -Name "operator_status" -Default "") -eq "not_approved_branch_closed_new_request_required" -and + [string](Get-PropertyValue -Item $snapshot -Name "operator_action" -Default "") -eq "keep_real_adapter_disabled_until_new_explicit_enablement_request" -and + [string](Get-PropertyValue -Item $snapshot -Name "display_severity" -Default "") -eq "blocked" -and + [string](Get-PropertyValue -Item $snapshot -Name "next_allowed_entrypoint" -Default "") -eq "new_explicit_enablement_request_only" -and + -not [bool](Get-PropertyValue -Item $snapshot -Name "attempt_allowed" -Default $true) -and + [string](Get-PropertyValue -Item $snapshot -Name "block_reason" -Default "") -eq "new_explicit_enablement_request_required" -and + [bool](Get-PropertyValue -Item $snapshot -Name "blocks_not_approved_extension" -Default $false) -and + [string](Get-PropertyValue -Item $snapshot -Name "enablement_status" -Default "") -eq "not_enabled" -and + [string](Get-PropertyValue -Item $snapshot -Name "runtime_gate_state" -Default "") -eq "validated_contract_only_not_enabled" -and + [string](Get-PropertyValue -Item $snapshot -Name "runtime_effect" -Default "") -eq "contract_only_no_runtime_enablement" -and + -not [bool](Get-PropertyValue -Item $snapshot -Name "allows_process_start" -Default $true) -and + -not [bool](Get-PropertyValue -Item $snapshot -Name "allows_payload_traffic" -Default $true) +) +$snapshotNotesCompatible = Test-ArrayContainsAll -Actual $snapshotNotes -Expected $requiredSnapshotNotes +$guardrailsCompatible = ( + (Test-ObjectHasFields -Item $guardrails -Fields $requiredGuardrailFields) -and + [bool](Get-PropertyValue -Item $guardrails -Name "activation_blocked" -Default $false) -and + -not [bool](Get-PropertyValue -Item $guardrails -Name "process_start_allowed" -Default $true) -and + -not [bool](Get-PropertyValue -Item $guardrails -Name "health_probe_enabled" -Default $true) -and + [string](Get-PropertyValue -Item $guardrails -Name "payload_traffic" -Default "") -eq "none" -and + -not [bool](Get-PropertyValue -Item $guardrails -Name "allows_process_start" -Default $true) -and + -not [bool](Get-PropertyValue -Item $guardrails -Name "allows_payload_traffic" -Default $true) +) + +$checks = [ordered]@{ + source_smoke_passed = ([bool]$sourceResult.passed) + source_schema_expected = ([string]$sourceResult.schema_version -eq "c19z149.remote_workspace_real_adapter_not_approved_outcome_operator_stop_status_snapshot_smoke.v1") + snapshot_present = ($null -ne $snapshot) + snapshot_fields_compatible = $snapshotFieldsCompatible + snapshot_values_compatible = $snapshotValuesCompatible + snapshot_notes_compatible = $snapshotNotesCompatible + guardrails_compatible = $guardrailsCompatible +} +$failed = @($checks.GetEnumerator() | Where-Object { -not $_.Value } | ForEach-Object { $_.Key }) + +$result = [ordered]@{ + schema_version = "c19z150.remote_workspace_real_adapter_not_approved_outcome_operator_stop_status_snapshot_compatibility_smoke.v1" + source_result_path = $sourceFile + cluster_id = $ClusterID + required_snapshot_fields = $requiredSnapshotFields + required_guardrail_fields = $requiredGuardrailFields + required_snapshot_notes = $requiredSnapshotNotes + not_approved_outcome_operator_stop_status_snapshot = $snapshot + checks = $checks + failed_checks = $failed + passed = ($failed.Count -eq 0) +} + +$fullResultPath = Join-Path $repoRoot $ResultPath +$resultDir = Split-Path -Parent $fullResultPath +if ($resultDir) { New-Item -ItemType Directory -Force -Path $resultDir | Out-Null } +$result | ConvertTo-Json -Depth 100 | Set-Content -Encoding UTF8 -Path $fullResultPath + +if (-not $result.passed) { + throw "C19Z150 remote workspace real-adapter not-approved outcome operator stop status snapshot compatibility smoke failed. Result: $fullResultPath Failed: $($failed -join ', ')" +} + +Write-Host "C19Z150 remote workspace real-adapter not-approved outcome operator stop status snapshot compatibility smoke passed. Result: $fullResultPath" +$result diff --git a/scripts/fabric/c19z151-remote-workspace-real-adapter-not-approved-outcome-operator-stop-status-snapshot-index-smoke.ps1 b/scripts/fabric/c19z151-remote-workspace-real-adapter-not-approved-outcome-operator-stop-status-snapshot-index-smoke.ps1 new file mode 100644 index 0000000..3e422d9 --- /dev/null +++ b/scripts/fabric/c19z151-remote-workspace-real-adapter-not-approved-outcome-operator-stop-status-snapshot-index-smoke.ps1 @@ -0,0 +1,180 @@ +param( + [string]$ApiBaseUrl = "http://192.168.200.61:18121/api/v1", + [string]$ClusterID = "cfc0743d-d960-49fb-9de8-96e063d5e4aa", + [string]$ActorUserID = "f67d943f-5397-4b3a-a229-695fe67ad700", + [string]$RequestedNodeName = "test-1", + [string]$DefaultNodeName = "test-2", + [string]$MatrixNodeName = "test-1", + [string]$ResultPath = "artifacts\c19z151-remote-workspace-real-adapter-not-approved-outcome-operator-stop-status-snapshot-index-smoke-result.json" +) + +Set-StrictMode -Version Latest +$ErrorActionPreference = "Stop" + +$repoRoot = (Resolve-Path (Join-Path $PSScriptRoot "..\..")).ProviderPath +$sourceResultPath = "artifacts\c19z151-remote-workspace-real-adapter-not-approved-outcome-operator-stop-status-snapshot-index-source-result.json" +$requiredIndexFields = @( + "schema_version", + "source_snapshot_schema", + "index_status", + "index_marker", + "indexed_snapshot_status", + "operator_status", + "operator_action", + "display_severity", + "next_allowed_entrypoint", + "attempt_allowed", + "block_reason", + "blocks_not_approved_extension", + "enablement_status", + "runtime_gate_state", + "runtime_effect", + "allows_process_start", + "allows_payload_traffic", + "guardrail_summary", + "index_notes" +) +$requiredGuardrailFields = @("activation_blocked", "process_start_allowed", "health_probe_enabled", "payload_traffic", "allows_process_start", "allows_payload_traffic") +$requiredIndexNotes = @( + "operator_stop_status_snapshot_index_complete", + "not_approved_branch_remains_closed", + "new_explicit_enablement_request_required", + "real_runtime_gate_not_enabled", + "process_start_disabled", + "payload_forwarding_disabled" +) + +function Get-PropertyValue { + param([object]$Item, [string]$Name, [object]$Default = $null) + if ($null -eq $Item) { return $Default } + if ($Item -is [System.Collections.IDictionary]) { + if ($Item.Contains($Name)) { return $Item[$Name] } + return $Default + } + $property = $Item.PSObject.Properties[$Name] + if ($null -eq $property) { return $Default } + return $property.Value +} + +function Test-ObjectHasFields { + param([object]$Item, [string[]]$Fields) + if ($null -eq $Item) { return $false } + foreach ($field in $Fields) { + if ($Item -is [System.Collections.IDictionary]) { + if (-not $Item.Contains($field)) { return $false } + continue + } + if ($null -eq $Item.PSObject.Properties[$field]) { return $false } + } + return $true +} + +function Test-ArrayContainsAll { + param([object[]]$Actual, [string[]]$Expected) + foreach ($item in $Expected) { + if ($Actual -notcontains $item) { return $false } + } + return $true +} + +& powershell -ExecutionPolicy Bypass -File (Join-Path $PSScriptRoot "c19z150-remote-workspace-real-adapter-not-approved-outcome-operator-stop-status-snapshot-compatibility-smoke.ps1") ` + -ApiBaseUrl $ApiBaseUrl ` + -ClusterID $ClusterID ` + -ActorUserID $ActorUserID ` + -RequestedNodeName $RequestedNodeName ` + -DefaultNodeName $DefaultNodeName ` + -MatrixNodeName $MatrixNodeName ` + -ResultPath $sourceResultPath | Out-Null + +$sourceFile = Join-Path $repoRoot $sourceResultPath +$sourceResult = Get-Content -Raw -Path $sourceFile | ConvertFrom-Json +$snapshot = Get-PropertyValue -Item $sourceResult -Name "not_approved_outcome_operator_stop_status_snapshot" -Default $null +$guardrails = Get-PropertyValue -Item $snapshot -Name "guardrail_summary" -Default $null + +$index = [ordered]@{ + schema_version = "rap.remote_workspace_real_adapter_not_approved_outcome_operator_stop_status_snapshot_index.v1" + source_snapshot_schema = Get-PropertyValue -Item $snapshot -Name "schema_version" -Default $null + index_status = "operator_stop_status_snapshot_index_complete" + index_marker = "c19z151_real_adapter_not_approved_outcome_operator_stop_status_snapshot_index" + indexed_snapshot_status = Get-PropertyValue -Item $snapshot -Name "snapshot_status" -Default $null + operator_status = Get-PropertyValue -Item $snapshot -Name "operator_status" -Default $null + operator_action = Get-PropertyValue -Item $snapshot -Name "operator_action" -Default $null + display_severity = Get-PropertyValue -Item $snapshot -Name "display_severity" -Default $null + next_allowed_entrypoint = Get-PropertyValue -Item $snapshot -Name "next_allowed_entrypoint" -Default $null + attempt_allowed = $false + block_reason = Get-PropertyValue -Item $snapshot -Name "block_reason" -Default $null + blocks_not_approved_extension = Get-PropertyValue -Item $snapshot -Name "blocks_not_approved_extension" -Default $null + enablement_status = Get-PropertyValue -Item $snapshot -Name "enablement_status" -Default $null + runtime_gate_state = Get-PropertyValue -Item $snapshot -Name "runtime_gate_state" -Default $null + runtime_effect = Get-PropertyValue -Item $snapshot -Name "runtime_effect" -Default $null + allows_process_start = $false + allows_payload_traffic = $false + guardrail_summary = $guardrails + index_notes = $requiredIndexNotes +} + +$indexFieldsCompatible = Test-ObjectHasFields -Item $index -Fields $requiredIndexFields +$indexValuesCompatible = ( + [string](Get-PropertyValue -Item $index -Name "schema_version" -Default "") -eq "rap.remote_workspace_real_adapter_not_approved_outcome_operator_stop_status_snapshot_index.v1" -and + [string](Get-PropertyValue -Item $index -Name "source_snapshot_schema" -Default "") -eq "rap.remote_workspace_real_adapter_not_approved_outcome_operator_stop_status_snapshot.v1" -and + [string](Get-PropertyValue -Item $index -Name "index_status" -Default "") -eq "operator_stop_status_snapshot_index_complete" -and + [string](Get-PropertyValue -Item $index -Name "indexed_snapshot_status" -Default "") -eq "operator_stop_status_snapshot_complete" -and + [string](Get-PropertyValue -Item $index -Name "operator_status" -Default "") -eq "not_approved_branch_closed_new_request_required" -and + [string](Get-PropertyValue -Item $index -Name "operator_action" -Default "") -eq "keep_real_adapter_disabled_until_new_explicit_enablement_request" -and + [string](Get-PropertyValue -Item $index -Name "display_severity" -Default "") -eq "blocked" -and + [string](Get-PropertyValue -Item $index -Name "next_allowed_entrypoint" -Default "") -eq "new_explicit_enablement_request_only" -and + -not [bool](Get-PropertyValue -Item $index -Name "attempt_allowed" -Default $true) -and + [string](Get-PropertyValue -Item $index -Name "block_reason" -Default "") -eq "new_explicit_enablement_request_required" -and + [bool](Get-PropertyValue -Item $index -Name "blocks_not_approved_extension" -Default $false) -and + [string](Get-PropertyValue -Item $index -Name "enablement_status" -Default "") -eq "not_enabled" -and + [string](Get-PropertyValue -Item $index -Name "runtime_gate_state" -Default "") -eq "validated_contract_only_not_enabled" -and + [string](Get-PropertyValue -Item $index -Name "runtime_effect" -Default "") -eq "contract_only_no_runtime_enablement" -and + -not [bool](Get-PropertyValue -Item $index -Name "allows_process_start" -Default $true) -and + -not [bool](Get-PropertyValue -Item $index -Name "allows_payload_traffic" -Default $true) +) +$indexNotesCompatible = Test-ArrayContainsAll -Actual @($index.index_notes) -Expected $requiredIndexNotes +$guardrailsCompatible = ( + (Test-ObjectHasFields -Item $guardrails -Fields $requiredGuardrailFields) -and + [bool](Get-PropertyValue -Item $guardrails -Name "activation_blocked" -Default $false) -and + -not [bool](Get-PropertyValue -Item $guardrails -Name "process_start_allowed" -Default $true) -and + -not [bool](Get-PropertyValue -Item $guardrails -Name "health_probe_enabled" -Default $true) -and + [string](Get-PropertyValue -Item $guardrails -Name "payload_traffic" -Default "") -eq "none" -and + -not [bool](Get-PropertyValue -Item $guardrails -Name "allows_process_start" -Default $true) -and + -not [bool](Get-PropertyValue -Item $guardrails -Name "allows_payload_traffic" -Default $true) +) + +$checks = [ordered]@{ + source_smoke_passed = ([bool]$sourceResult.passed) + source_schema_expected = ([string]$sourceResult.schema_version -eq "c19z150.remote_workspace_real_adapter_not_approved_outcome_operator_stop_status_snapshot_compatibility_smoke.v1") + snapshot_present = ($null -ne $snapshot) + index_fields_compatible = $indexFieldsCompatible + index_values_compatible = $indexValuesCompatible + index_notes_compatible = $indexNotesCompatible + guardrails_compatible = $guardrailsCompatible +} +$failed = @($checks.GetEnumerator() | Where-Object { -not $_.Value } | ForEach-Object { $_.Key }) + +$result = [ordered]@{ + schema_version = "c19z151.remote_workspace_real_adapter_not_approved_outcome_operator_stop_status_snapshot_index_smoke.v1" + source_result_path = $sourceFile + cluster_id = $ClusterID + required_index_fields = $requiredIndexFields + required_guardrail_fields = $requiredGuardrailFields + required_index_notes = $requiredIndexNotes + not_approved_outcome_operator_stop_status_snapshot_index = $index + checks = $checks + failed_checks = $failed + passed = ($failed.Count -eq 0) +} + +$fullResultPath = Join-Path $repoRoot $ResultPath +$resultDir = Split-Path -Parent $fullResultPath +if ($resultDir) { New-Item -ItemType Directory -Force -Path $resultDir | Out-Null } +$result | ConvertTo-Json -Depth 100 | Set-Content -Encoding UTF8 -Path $fullResultPath + +if (-not $result.passed) { + throw "C19Z151 remote workspace real-adapter not-approved outcome operator stop status snapshot index smoke failed. Result: $fullResultPath Failed: $($failed -join ', ')" +} + +Write-Host "C19Z151 remote workspace real-adapter not-approved outcome operator stop status snapshot index smoke passed. Result: $fullResultPath" +$result diff --git a/scripts/fabric/c19z152-remote-workspace-real-adapter-not-approved-outcome-operator-stop-status-snapshot-index-compatibility-smoke.ps1 b/scripts/fabric/c19z152-remote-workspace-real-adapter-not-approved-outcome-operator-stop-status-snapshot-index-compatibility-smoke.ps1 new file mode 100644 index 0000000..0f4315e --- /dev/null +++ b/scripts/fabric/c19z152-remote-workspace-real-adapter-not-approved-outcome-operator-stop-status-snapshot-index-compatibility-smoke.ps1 @@ -0,0 +1,132 @@ +param( + [string]$ApiBaseUrl = "http://192.168.200.61:18121/api/v1", + [string]$ClusterID = "cfc0743d-d960-49fb-9de8-96e063d5e4aa", + [string]$ActorUserID = "f67d943f-5397-4b3a-a229-695fe67ad700", + [string]$RequestedNodeName = "test-1", + [string]$DefaultNodeName = "test-2", + [string]$MatrixNodeName = "test-1", + [string]$ResultPath = "artifacts\c19z152-remote-workspace-real-adapter-not-approved-outcome-operator-stop-status-snapshot-index-compatibility-smoke-result.json" +) + +Set-StrictMode -Version Latest +$ErrorActionPreference = "Stop" + +$repoRoot = (Resolve-Path (Join-Path $PSScriptRoot "..\..")).ProviderPath +$sourceResultPath = "artifacts\c19z152-remote-workspace-real-adapter-not-approved-outcome-operator-stop-status-snapshot-index-source-result.json" +$requiredIndexFields = @("schema_version", "source_snapshot_schema", "index_status", "index_marker", "indexed_snapshot_status", "operator_status", "operator_action", "display_severity", "next_allowed_entrypoint", "attempt_allowed", "block_reason", "blocks_not_approved_extension", "enablement_status", "runtime_gate_state", "runtime_effect", "allows_process_start", "allows_payload_traffic", "guardrail_summary", "index_notes") +$requiredGuardrailFields = @("activation_blocked", "process_start_allowed", "health_probe_enabled", "payload_traffic", "allows_process_start", "allows_payload_traffic") +$requiredIndexNotes = @("operator_stop_status_snapshot_index_complete", "not_approved_branch_remains_closed", "new_explicit_enablement_request_required", "real_runtime_gate_not_enabled", "process_start_disabled", "payload_forwarding_disabled") + +function Get-PropertyValue { + param([object]$Item, [string]$Name, [object]$Default = $null) + if ($null -eq $Item) { return $Default } + if ($Item -is [System.Collections.IDictionary]) { + if ($Item.Contains($Name)) { return $Item[$Name] } + return $Default + } + $property = $Item.PSObject.Properties[$Name] + if ($null -eq $property) { return $Default } + return $property.Value +} + +function Test-ObjectHasFields { + param([object]$Item, [string[]]$Fields) + if ($null -eq $Item) { return $false } + foreach ($field in $Fields) { + if ($Item -is [System.Collections.IDictionary]) { + if (-not $Item.Contains($field)) { return $false } + continue + } + if ($null -eq $Item.PSObject.Properties[$field]) { return $false } + } + return $true +} + +function Test-ArrayContainsAll { + param([object[]]$Actual, [string[]]$Expected) + foreach ($item in $Expected) { + if ($Actual -notcontains $item) { return $false } + } + return $true +} + +& powershell -ExecutionPolicy Bypass -File (Join-Path $PSScriptRoot "c19z151-remote-workspace-real-adapter-not-approved-outcome-operator-stop-status-snapshot-index-smoke.ps1") ` + -ApiBaseUrl $ApiBaseUrl ` + -ClusterID $ClusterID ` + -ActorUserID $ActorUserID ` + -RequestedNodeName $RequestedNodeName ` + -DefaultNodeName $DefaultNodeName ` + -MatrixNodeName $MatrixNodeName ` + -ResultPath $sourceResultPath | Out-Null + +$sourceFile = Join-Path $repoRoot $sourceResultPath +$sourceResult = Get-Content -Raw -Path $sourceFile | ConvertFrom-Json +$index = Get-PropertyValue -Item $sourceResult -Name "not_approved_outcome_operator_stop_status_snapshot_index" -Default $null +$guardrails = Get-PropertyValue -Item $index -Name "guardrail_summary" -Default $null +$indexNotes = @(Get-PropertyValue -Item $index -Name "index_notes" -Default @()) + +$indexFieldsCompatible = Test-ObjectHasFields -Item $index -Fields $requiredIndexFields +$indexValuesCompatible = ( + [string](Get-PropertyValue -Item $index -Name "schema_version" -Default "") -eq "rap.remote_workspace_real_adapter_not_approved_outcome_operator_stop_status_snapshot_index.v1" -and + [string](Get-PropertyValue -Item $index -Name "source_snapshot_schema" -Default "") -eq "rap.remote_workspace_real_adapter_not_approved_outcome_operator_stop_status_snapshot.v1" -and + [string](Get-PropertyValue -Item $index -Name "index_status" -Default "") -eq "operator_stop_status_snapshot_index_complete" -and + [string](Get-PropertyValue -Item $index -Name "indexed_snapshot_status" -Default "") -eq "operator_stop_status_snapshot_complete" -and + [string](Get-PropertyValue -Item $index -Name "operator_status" -Default "") -eq "not_approved_branch_closed_new_request_required" -and + [string](Get-PropertyValue -Item $index -Name "operator_action" -Default "") -eq "keep_real_adapter_disabled_until_new_explicit_enablement_request" -and + [string](Get-PropertyValue -Item $index -Name "display_severity" -Default "") -eq "blocked" -and + [string](Get-PropertyValue -Item $index -Name "next_allowed_entrypoint" -Default "") -eq "new_explicit_enablement_request_only" -and + -not [bool](Get-PropertyValue -Item $index -Name "attempt_allowed" -Default $true) -and + [string](Get-PropertyValue -Item $index -Name "block_reason" -Default "") -eq "new_explicit_enablement_request_required" -and + [bool](Get-PropertyValue -Item $index -Name "blocks_not_approved_extension" -Default $false) -and + [string](Get-PropertyValue -Item $index -Name "enablement_status" -Default "") -eq "not_enabled" -and + [string](Get-PropertyValue -Item $index -Name "runtime_gate_state" -Default "") -eq "validated_contract_only_not_enabled" -and + [string](Get-PropertyValue -Item $index -Name "runtime_effect" -Default "") -eq "contract_only_no_runtime_enablement" -and + -not [bool](Get-PropertyValue -Item $index -Name "allows_process_start" -Default $true) -and + -not [bool](Get-PropertyValue -Item $index -Name "allows_payload_traffic" -Default $true) +) +$indexNotesCompatible = Test-ArrayContainsAll -Actual $indexNotes -Expected $requiredIndexNotes +$guardrailsCompatible = ( + (Test-ObjectHasFields -Item $guardrails -Fields $requiredGuardrailFields) -and + [bool](Get-PropertyValue -Item $guardrails -Name "activation_blocked" -Default $false) -and + -not [bool](Get-PropertyValue -Item $guardrails -Name "process_start_allowed" -Default $true) -and + -not [bool](Get-PropertyValue -Item $guardrails -Name "health_probe_enabled" -Default $true) -and + [string](Get-PropertyValue -Item $guardrails -Name "payload_traffic" -Default "") -eq "none" -and + -not [bool](Get-PropertyValue -Item $guardrails -Name "allows_process_start" -Default $true) -and + -not [bool](Get-PropertyValue -Item $guardrails -Name "allows_payload_traffic" -Default $true) +) + +$checks = [ordered]@{ + source_smoke_passed = ([bool]$sourceResult.passed) + source_schema_expected = ([string]$sourceResult.schema_version -eq "c19z151.remote_workspace_real_adapter_not_approved_outcome_operator_stop_status_snapshot_index_smoke.v1") + index_present = ($null -ne $index) + index_fields_compatible = $indexFieldsCompatible + index_values_compatible = $indexValuesCompatible + index_notes_compatible = $indexNotesCompatible + guardrails_compatible = $guardrailsCompatible +} +$failed = @($checks.GetEnumerator() | Where-Object { -not $_.Value } | ForEach-Object { $_.Key }) + +$result = [ordered]@{ + schema_version = "c19z152.remote_workspace_real_adapter_not_approved_outcome_operator_stop_status_snapshot_index_compatibility_smoke.v1" + source_result_path = $sourceFile + cluster_id = $ClusterID + required_index_fields = $requiredIndexFields + required_guardrail_fields = $requiredGuardrailFields + required_index_notes = $requiredIndexNotes + not_approved_outcome_operator_stop_status_snapshot_index = $index + checks = $checks + failed_checks = $failed + passed = ($failed.Count -eq 0) +} + +$fullResultPath = Join-Path $repoRoot $ResultPath +$resultDir = Split-Path -Parent $fullResultPath +if ($resultDir) { New-Item -ItemType Directory -Force -Path $resultDir | Out-Null } +$result | ConvertTo-Json -Depth 100 | Set-Content -Encoding UTF8 -Path $fullResultPath + +if (-not $result.passed) { + throw "C19Z152 remote workspace real-adapter not-approved outcome operator stop status snapshot index compatibility smoke failed. Result: $fullResultPath Failed: $($failed -join ', ')" +} + +Write-Host "C19Z152 remote workspace real-adapter not-approved outcome operator stop status snapshot index compatibility smoke passed. Result: $fullResultPath" +$result diff --git a/scripts/fabric/c19z153-remote-workspace-real-adapter-not-approved-outcome-operator-stop-status-catalog-smoke.ps1 b/scripts/fabric/c19z153-remote-workspace-real-adapter-not-approved-outcome-operator-stop-status-catalog-smoke.ps1 new file mode 100644 index 0000000..5eb2905 --- /dev/null +++ b/scripts/fabric/c19z153-remote-workspace-real-adapter-not-approved-outcome-operator-stop-status-catalog-smoke.ps1 @@ -0,0 +1,153 @@ +param( + [string]$ApiBaseUrl = "http://192.168.200.61:18121/api/v1", + [string]$ClusterID = "cfc0743d-d960-49fb-9de8-96e063d5e4aa", + [string]$ActorUserID = "f67d943f-5397-4b3a-a229-695fe67ad700", + [string]$RequestedNodeName = "test-1", + [string]$DefaultNodeName = "test-2", + [string]$MatrixNodeName = "test-1", + [string]$ResultPath = "artifacts\c19z153-remote-workspace-real-adapter-not-approved-outcome-operator-stop-status-catalog-smoke-result.json" +) + +Set-StrictMode -Version Latest +$ErrorActionPreference = "Stop" + +$repoRoot = (Resolve-Path (Join-Path $PSScriptRoot "..\..")).ProviderPath +$sourceResultPath = "artifacts\c19z153-remote-workspace-real-adapter-not-approved-outcome-operator-stop-status-catalog-source-result.json" +$requiredCatalogFields = @("schema_version", "source_index_schema", "catalog_status", "catalog_marker", "catalog_entry_type", "operator_status", "operator_action", "display_severity", "next_allowed_entrypoint", "attempt_allowed", "block_reason", "blocks_not_approved_extension", "enablement_status", "runtime_gate_state", "runtime_effect", "allows_process_start", "allows_payload_traffic", "guardrail_summary", "catalog_notes") +$requiredGuardrailFields = @("activation_blocked", "process_start_allowed", "health_probe_enabled", "payload_traffic", "allows_process_start", "allows_payload_traffic") +$requiredCatalogNotes = @("operator_stop_status_catalog_complete", "not_approved_branch_remains_closed", "new_explicit_enablement_request_required", "real_runtime_gate_not_enabled", "process_start_disabled", "payload_forwarding_disabled") + +function Get-PropertyValue { + param([object]$Item, [string]$Name, [object]$Default = $null) + if ($null -eq $Item) { return $Default } + if ($Item -is [System.Collections.IDictionary]) { + if ($Item.Contains($Name)) { return $Item[$Name] } + return $Default + } + $property = $Item.PSObject.Properties[$Name] + if ($null -eq $property) { return $Default } + return $property.Value +} + +function Test-ObjectHasFields { + param([object]$Item, [string[]]$Fields) + if ($null -eq $Item) { return $false } + foreach ($field in $Fields) { + if ($Item -is [System.Collections.IDictionary]) { + if (-not $Item.Contains($field)) { return $false } + continue + } + if ($null -eq $Item.PSObject.Properties[$field]) { return $false } + } + return $true +} + +function Test-ArrayContainsAll { + param([object[]]$Actual, [string[]]$Expected) + foreach ($item in $Expected) { + if ($Actual -notcontains $item) { return $false } + } + return $true +} + +& powershell -ExecutionPolicy Bypass -File (Join-Path $PSScriptRoot "c19z152-remote-workspace-real-adapter-not-approved-outcome-operator-stop-status-snapshot-index-compatibility-smoke.ps1") ` + -ApiBaseUrl $ApiBaseUrl ` + -ClusterID $ClusterID ` + -ActorUserID $ActorUserID ` + -RequestedNodeName $RequestedNodeName ` + -DefaultNodeName $DefaultNodeName ` + -MatrixNodeName $MatrixNodeName ` + -ResultPath $sourceResultPath | Out-Null + +$sourceFile = Join-Path $repoRoot $sourceResultPath +$sourceResult = Get-Content -Raw -Path $sourceFile | ConvertFrom-Json +$index = Get-PropertyValue -Item $sourceResult -Name "not_approved_outcome_operator_stop_status_snapshot_index" -Default $null +$guardrails = Get-PropertyValue -Item $index -Name "guardrail_summary" -Default $null + +$catalog = [ordered]@{ + schema_version = "rap.remote_workspace_real_adapter_not_approved_outcome_operator_stop_status_catalog.v1" + source_index_schema = Get-PropertyValue -Item $index -Name "schema_version" -Default $null + catalog_status = "operator_stop_status_catalog_complete" + catalog_marker = "c19z153_real_adapter_not_approved_outcome_operator_stop_status_catalog" + catalog_entry_type = "blocked_not_approved_operator_stop" + operator_status = Get-PropertyValue -Item $index -Name "operator_status" -Default $null + operator_action = Get-PropertyValue -Item $index -Name "operator_action" -Default $null + display_severity = Get-PropertyValue -Item $index -Name "display_severity" -Default $null + next_allowed_entrypoint = Get-PropertyValue -Item $index -Name "next_allowed_entrypoint" -Default $null + attempt_allowed = $false + block_reason = Get-PropertyValue -Item $index -Name "block_reason" -Default $null + blocks_not_approved_extension = Get-PropertyValue -Item $index -Name "blocks_not_approved_extension" -Default $null + enablement_status = Get-PropertyValue -Item $index -Name "enablement_status" -Default $null + runtime_gate_state = Get-PropertyValue -Item $index -Name "runtime_gate_state" -Default $null + runtime_effect = Get-PropertyValue -Item $index -Name "runtime_effect" -Default $null + allows_process_start = $false + allows_payload_traffic = $false + guardrail_summary = $guardrails + catalog_notes = $requiredCatalogNotes +} + +$catalogFieldsCompatible = Test-ObjectHasFields -Item $catalog -Fields $requiredCatalogFields +$catalogValuesCompatible = ( + [string](Get-PropertyValue -Item $catalog -Name "schema_version" -Default "") -eq "rap.remote_workspace_real_adapter_not_approved_outcome_operator_stop_status_catalog.v1" -and + [string](Get-PropertyValue -Item $catalog -Name "source_index_schema" -Default "") -eq "rap.remote_workspace_real_adapter_not_approved_outcome_operator_stop_status_snapshot_index.v1" -and + [string](Get-PropertyValue -Item $catalog -Name "catalog_status" -Default "") -eq "operator_stop_status_catalog_complete" -and + [string](Get-PropertyValue -Item $catalog -Name "catalog_entry_type" -Default "") -eq "blocked_not_approved_operator_stop" -and + [string](Get-PropertyValue -Item $catalog -Name "operator_status" -Default "") -eq "not_approved_branch_closed_new_request_required" -and + [string](Get-PropertyValue -Item $catalog -Name "operator_action" -Default "") -eq "keep_real_adapter_disabled_until_new_explicit_enablement_request" -and + [string](Get-PropertyValue -Item $catalog -Name "display_severity" -Default "") -eq "blocked" -and + [string](Get-PropertyValue -Item $catalog -Name "next_allowed_entrypoint" -Default "") -eq "new_explicit_enablement_request_only" -and + -not [bool](Get-PropertyValue -Item $catalog -Name "attempt_allowed" -Default $true) -and + [string](Get-PropertyValue -Item $catalog -Name "block_reason" -Default "") -eq "new_explicit_enablement_request_required" -and + [bool](Get-PropertyValue -Item $catalog -Name "blocks_not_approved_extension" -Default $false) -and + [string](Get-PropertyValue -Item $catalog -Name "enablement_status" -Default "") -eq "not_enabled" -and + [string](Get-PropertyValue -Item $catalog -Name "runtime_gate_state" -Default "") -eq "validated_contract_only_not_enabled" -and + [string](Get-PropertyValue -Item $catalog -Name "runtime_effect" -Default "") -eq "contract_only_no_runtime_enablement" -and + -not [bool](Get-PropertyValue -Item $catalog -Name "allows_process_start" -Default $true) -and + -not [bool](Get-PropertyValue -Item $catalog -Name "allows_payload_traffic" -Default $true) +) +$catalogNotesCompatible = Test-ArrayContainsAll -Actual @($catalog.catalog_notes) -Expected $requiredCatalogNotes +$guardrailsCompatible = ( + (Test-ObjectHasFields -Item $guardrails -Fields $requiredGuardrailFields) -and + [bool](Get-PropertyValue -Item $guardrails -Name "activation_blocked" -Default $false) -and + -not [bool](Get-PropertyValue -Item $guardrails -Name "process_start_allowed" -Default $true) -and + -not [bool](Get-PropertyValue -Item $guardrails -Name "health_probe_enabled" -Default $true) -and + [string](Get-PropertyValue -Item $guardrails -Name "payload_traffic" -Default "") -eq "none" -and + -not [bool](Get-PropertyValue -Item $guardrails -Name "allows_process_start" -Default $true) -and + -not [bool](Get-PropertyValue -Item $guardrails -Name "allows_payload_traffic" -Default $true) +) + +$checks = [ordered]@{ + source_smoke_passed = ([bool]$sourceResult.passed) + source_schema_expected = ([string]$sourceResult.schema_version -eq "c19z152.remote_workspace_real_adapter_not_approved_outcome_operator_stop_status_snapshot_index_compatibility_smoke.v1") + index_present = ($null -ne $index) + catalog_fields_compatible = $catalogFieldsCompatible + catalog_values_compatible = $catalogValuesCompatible + catalog_notes_compatible = $catalogNotesCompatible + guardrails_compatible = $guardrailsCompatible +} +$failed = @($checks.GetEnumerator() | Where-Object { -not $_.Value } | ForEach-Object { $_.Key }) + +$result = [ordered]@{ + schema_version = "c19z153.remote_workspace_real_adapter_not_approved_outcome_operator_stop_status_catalog_smoke.v1" + source_result_path = $sourceFile + cluster_id = $ClusterID + required_catalog_fields = $requiredCatalogFields + required_guardrail_fields = $requiredGuardrailFields + required_catalog_notes = $requiredCatalogNotes + not_approved_outcome_operator_stop_status_catalog = $catalog + checks = $checks + failed_checks = $failed + passed = ($failed.Count -eq 0) +} + +$fullResultPath = Join-Path $repoRoot $ResultPath +$resultDir = Split-Path -Parent $fullResultPath +if ($resultDir) { New-Item -ItemType Directory -Force -Path $resultDir | Out-Null } +$result | ConvertTo-Json -Depth 100 | Set-Content -Encoding UTF8 -Path $fullResultPath + +if (-not $result.passed) { + throw "C19Z153 remote workspace real-adapter not-approved outcome operator stop status catalog smoke failed. Result: $fullResultPath Failed: $($failed -join ', ')" +} + +Write-Host "C19Z153 remote workspace real-adapter not-approved outcome operator stop status catalog smoke passed. Result: $fullResultPath" +$result diff --git a/scripts/fabric/c19z154-remote-workspace-real-adapter-not-approved-outcome-operator-stop-status-catalog-compatibility-smoke.ps1 b/scripts/fabric/c19z154-remote-workspace-real-adapter-not-approved-outcome-operator-stop-status-catalog-compatibility-smoke.ps1 new file mode 100644 index 0000000..fcd34b7 --- /dev/null +++ b/scripts/fabric/c19z154-remote-workspace-real-adapter-not-approved-outcome-operator-stop-status-catalog-compatibility-smoke.ps1 @@ -0,0 +1,132 @@ +param( + [string]$ApiBaseUrl = "http://192.168.200.61:18121/api/v1", + [string]$ClusterID = "cfc0743d-d960-49fb-9de8-96e063d5e4aa", + [string]$ActorUserID = "f67d943f-5397-4b3a-a229-695fe67ad700", + [string]$RequestedNodeName = "test-1", + [string]$DefaultNodeName = "test-2", + [string]$MatrixNodeName = "test-1", + [string]$ResultPath = "artifacts\c19z154-remote-workspace-real-adapter-not-approved-outcome-operator-stop-status-catalog-compatibility-smoke-result.json" +) + +Set-StrictMode -Version Latest +$ErrorActionPreference = "Stop" + +$repoRoot = (Resolve-Path (Join-Path $PSScriptRoot "..\..")).ProviderPath +$sourceResultPath = "artifacts\c19z154-remote-workspace-real-adapter-not-approved-outcome-operator-stop-status-catalog-source-result.json" +$requiredCatalogFields = @("schema_version", "source_index_schema", "catalog_status", "catalog_marker", "catalog_entry_type", "operator_status", "operator_action", "display_severity", "next_allowed_entrypoint", "attempt_allowed", "block_reason", "blocks_not_approved_extension", "enablement_status", "runtime_gate_state", "runtime_effect", "allows_process_start", "allows_payload_traffic", "guardrail_summary", "catalog_notes") +$requiredGuardrailFields = @("activation_blocked", "process_start_allowed", "health_probe_enabled", "payload_traffic", "allows_process_start", "allows_payload_traffic") +$requiredCatalogNotes = @("operator_stop_status_catalog_complete", "not_approved_branch_remains_closed", "new_explicit_enablement_request_required", "real_runtime_gate_not_enabled", "process_start_disabled", "payload_forwarding_disabled") + +function Get-PropertyValue { + param([object]$Item, [string]$Name, [object]$Default = $null) + if ($null -eq $Item) { return $Default } + if ($Item -is [System.Collections.IDictionary]) { + if ($Item.Contains($Name)) { return $Item[$Name] } + return $Default + } + $property = $Item.PSObject.Properties[$Name] + if ($null -eq $property) { return $Default } + return $property.Value +} + +function Test-ObjectHasFields { + param([object]$Item, [string[]]$Fields) + if ($null -eq $Item) { return $false } + foreach ($field in $Fields) { + if ($Item -is [System.Collections.IDictionary]) { + if (-not $Item.Contains($field)) { return $false } + continue + } + if ($null -eq $Item.PSObject.Properties[$field]) { return $false } + } + return $true +} + +function Test-ArrayContainsAll { + param([object[]]$Actual, [string[]]$Expected) + foreach ($item in $Expected) { + if ($Actual -notcontains $item) { return $false } + } + return $true +} + +& powershell -ExecutionPolicy Bypass -File (Join-Path $PSScriptRoot "c19z153-remote-workspace-real-adapter-not-approved-outcome-operator-stop-status-catalog-smoke.ps1") ` + -ApiBaseUrl $ApiBaseUrl ` + -ClusterID $ClusterID ` + -ActorUserID $ActorUserID ` + -RequestedNodeName $RequestedNodeName ` + -DefaultNodeName $DefaultNodeName ` + -MatrixNodeName $MatrixNodeName ` + -ResultPath $sourceResultPath | Out-Null + +$sourceFile = Join-Path $repoRoot $sourceResultPath +$sourceResult = Get-Content -Raw -Path $sourceFile | ConvertFrom-Json +$catalog = Get-PropertyValue -Item $sourceResult -Name "not_approved_outcome_operator_stop_status_catalog" -Default $null +$guardrails = Get-PropertyValue -Item $catalog -Name "guardrail_summary" -Default $null +$catalogNotes = @(Get-PropertyValue -Item $catalog -Name "catalog_notes" -Default @()) + +$catalogFieldsCompatible = Test-ObjectHasFields -Item $catalog -Fields $requiredCatalogFields +$catalogValuesCompatible = ( + [string](Get-PropertyValue -Item $catalog -Name "schema_version" -Default "") -eq "rap.remote_workspace_real_adapter_not_approved_outcome_operator_stop_status_catalog.v1" -and + [string](Get-PropertyValue -Item $catalog -Name "source_index_schema" -Default "") -eq "rap.remote_workspace_real_adapter_not_approved_outcome_operator_stop_status_snapshot_index.v1" -and + [string](Get-PropertyValue -Item $catalog -Name "catalog_status" -Default "") -eq "operator_stop_status_catalog_complete" -and + [string](Get-PropertyValue -Item $catalog -Name "catalog_entry_type" -Default "") -eq "blocked_not_approved_operator_stop" -and + [string](Get-PropertyValue -Item $catalog -Name "operator_status" -Default "") -eq "not_approved_branch_closed_new_request_required" -and + [string](Get-PropertyValue -Item $catalog -Name "operator_action" -Default "") -eq "keep_real_adapter_disabled_until_new_explicit_enablement_request" -and + [string](Get-PropertyValue -Item $catalog -Name "display_severity" -Default "") -eq "blocked" -and + [string](Get-PropertyValue -Item $catalog -Name "next_allowed_entrypoint" -Default "") -eq "new_explicit_enablement_request_only" -and + -not [bool](Get-PropertyValue -Item $catalog -Name "attempt_allowed" -Default $true) -and + [string](Get-PropertyValue -Item $catalog -Name "block_reason" -Default "") -eq "new_explicit_enablement_request_required" -and + [bool](Get-PropertyValue -Item $catalog -Name "blocks_not_approved_extension" -Default $false) -and + [string](Get-PropertyValue -Item $catalog -Name "enablement_status" -Default "") -eq "not_enabled" -and + [string](Get-PropertyValue -Item $catalog -Name "runtime_gate_state" -Default "") -eq "validated_contract_only_not_enabled" -and + [string](Get-PropertyValue -Item $catalog -Name "runtime_effect" -Default "") -eq "contract_only_no_runtime_enablement" -and + -not [bool](Get-PropertyValue -Item $catalog -Name "allows_process_start" -Default $true) -and + -not [bool](Get-PropertyValue -Item $catalog -Name "allows_payload_traffic" -Default $true) +) +$catalogNotesCompatible = Test-ArrayContainsAll -Actual $catalogNotes -Expected $requiredCatalogNotes +$guardrailsCompatible = ( + (Test-ObjectHasFields -Item $guardrails -Fields $requiredGuardrailFields) -and + [bool](Get-PropertyValue -Item $guardrails -Name "activation_blocked" -Default $false) -and + -not [bool](Get-PropertyValue -Item $guardrails -Name "process_start_allowed" -Default $true) -and + -not [bool](Get-PropertyValue -Item $guardrails -Name "health_probe_enabled" -Default $true) -and + [string](Get-PropertyValue -Item $guardrails -Name "payload_traffic" -Default "") -eq "none" -and + -not [bool](Get-PropertyValue -Item $guardrails -Name "allows_process_start" -Default $true) -and + -not [bool](Get-PropertyValue -Item $guardrails -Name "allows_payload_traffic" -Default $true) +) + +$checks = [ordered]@{ + source_smoke_passed = ([bool]$sourceResult.passed) + source_schema_expected = ([string]$sourceResult.schema_version -eq "c19z153.remote_workspace_real_adapter_not_approved_outcome_operator_stop_status_catalog_smoke.v1") + catalog_present = ($null -ne $catalog) + catalog_fields_compatible = $catalogFieldsCompatible + catalog_values_compatible = $catalogValuesCompatible + catalog_notes_compatible = $catalogNotesCompatible + guardrails_compatible = $guardrailsCompatible +} +$failed = @($checks.GetEnumerator() | Where-Object { -not $_.Value } | ForEach-Object { $_.Key }) + +$result = [ordered]@{ + schema_version = "c19z154.remote_workspace_real_adapter_not_approved_outcome_operator_stop_status_catalog_compatibility_smoke.v1" + source_result_path = $sourceFile + cluster_id = $ClusterID + required_catalog_fields = $requiredCatalogFields + required_guardrail_fields = $requiredGuardrailFields + required_catalog_notes = $requiredCatalogNotes + not_approved_outcome_operator_stop_status_catalog = $catalog + checks = $checks + failed_checks = $failed + passed = ($failed.Count -eq 0) +} + +$fullResultPath = Join-Path $repoRoot $ResultPath +$resultDir = Split-Path -Parent $fullResultPath +if ($resultDir) { New-Item -ItemType Directory -Force -Path $resultDir | Out-Null } +$result | ConvertTo-Json -Depth 100 | Set-Content -Encoding UTF8 -Path $fullResultPath + +if (-not $result.passed) { + throw "C19Z154 remote workspace real-adapter not-approved outcome operator stop status catalog compatibility smoke failed. Result: $fullResultPath Failed: $($failed -join ', ')" +} + +Write-Host "C19Z154 remote workspace real-adapter not-approved outcome operator stop status catalog compatibility smoke passed. Result: $fullResultPath" +$result diff --git a/scripts/fabric/c19z155-remote-workspace-real-adapter-not-approved-outcome-operator-stop-status-catalog-release-marker-smoke.ps1 b/scripts/fabric/c19z155-remote-workspace-real-adapter-not-approved-outcome-operator-stop-status-catalog-release-marker-smoke.ps1 new file mode 100644 index 0000000..2e152b5 --- /dev/null +++ b/scripts/fabric/c19z155-remote-workspace-real-adapter-not-approved-outcome-operator-stop-status-catalog-release-marker-smoke.ps1 @@ -0,0 +1,155 @@ +param( + [string]$ApiBaseUrl = "http://192.168.200.61:18121/api/v1", + [string]$ClusterID = "cfc0743d-d960-49fb-9de8-96e063d5e4aa", + [string]$ActorUserID = "f67d943f-5397-4b3a-a229-695fe67ad700", + [string]$RequestedNodeName = "test-1", + [string]$DefaultNodeName = "test-2", + [string]$MatrixNodeName = "test-1", + [string]$ResultPath = "artifacts\c19z155-remote-workspace-real-adapter-not-approved-outcome-operator-stop-status-catalog-release-marker-smoke-result.json" +) + +Set-StrictMode -Version Latest +$ErrorActionPreference = "Stop" + +$repoRoot = (Resolve-Path (Join-Path $PSScriptRoot "..\..")).ProviderPath +$sourceResultPath = "artifacts\c19z155-remote-workspace-real-adapter-not-approved-outcome-operator-stop-status-catalog-release-marker-source-result.json" +$requiredReleaseFields = @("schema_version", "source_catalog_schema", "release_status", "release_marker", "catalog_status", "catalog_entry_type", "operator_status", "operator_action", "display_severity", "next_allowed_entrypoint", "attempt_allowed", "block_reason", "blocks_not_approved_extension", "enablement_status", "runtime_gate_state", "runtime_effect", "allows_process_start", "allows_payload_traffic", "guardrail_summary", "release_notes") +$requiredGuardrailFields = @("activation_blocked", "process_start_allowed", "health_probe_enabled", "payload_traffic", "allows_process_start", "allows_payload_traffic") +$requiredReleaseNotes = @("operator_stop_status_catalog_release_marked", "not_approved_branch_remains_closed", "new_explicit_enablement_request_required", "real_runtime_gate_not_enabled", "process_start_disabled", "payload_forwarding_disabled") + +function Get-PropertyValue { + param([object]$Item, [string]$Name, [object]$Default = $null) + if ($null -eq $Item) { return $Default } + if ($Item -is [System.Collections.IDictionary]) { + if ($Item.Contains($Name)) { return $Item[$Name] } + return $Default + } + $property = $Item.PSObject.Properties[$Name] + if ($null -eq $property) { return $Default } + return $property.Value +} + +function Test-ObjectHasFields { + param([object]$Item, [string[]]$Fields) + if ($null -eq $Item) { return $false } + foreach ($field in $Fields) { + if ($Item -is [System.Collections.IDictionary]) { + if (-not $Item.Contains($field)) { return $false } + continue + } + if ($null -eq $Item.PSObject.Properties[$field]) { return $false } + } + return $true +} + +function Test-ArrayContainsAll { + param([object[]]$Actual, [string[]]$Expected) + foreach ($item in $Expected) { + if ($Actual -notcontains $item) { return $false } + } + return $true +} + +& powershell -ExecutionPolicy Bypass -File (Join-Path $PSScriptRoot "c19z154-remote-workspace-real-adapter-not-approved-outcome-operator-stop-status-catalog-compatibility-smoke.ps1") ` + -ApiBaseUrl $ApiBaseUrl ` + -ClusterID $ClusterID ` + -ActorUserID $ActorUserID ` + -RequestedNodeName $RequestedNodeName ` + -DefaultNodeName $DefaultNodeName ` + -MatrixNodeName $MatrixNodeName ` + -ResultPath $sourceResultPath | Out-Null + +$sourceFile = Join-Path $repoRoot $sourceResultPath +$sourceResult = Get-Content -Raw -Path $sourceFile | ConvertFrom-Json +$catalog = Get-PropertyValue -Item $sourceResult -Name "not_approved_outcome_operator_stop_status_catalog" -Default $null +$guardrails = Get-PropertyValue -Item $catalog -Name "guardrail_summary" -Default $null + +$release = [ordered]@{ + schema_version = "rap.remote_workspace_real_adapter_not_approved_outcome_operator_stop_status_catalog_release_marker.v1" + source_catalog_schema = Get-PropertyValue -Item $catalog -Name "schema_version" -Default $null + release_status = "operator_stop_status_catalog_released_contract_only" + release_marker = "c19z155_real_adapter_not_approved_outcome_operator_stop_status_catalog_release_marker" + catalog_status = Get-PropertyValue -Item $catalog -Name "catalog_status" -Default $null + catalog_entry_type = Get-PropertyValue -Item $catalog -Name "catalog_entry_type" -Default $null + operator_status = Get-PropertyValue -Item $catalog -Name "operator_status" -Default $null + operator_action = Get-PropertyValue -Item $catalog -Name "operator_action" -Default $null + display_severity = Get-PropertyValue -Item $catalog -Name "display_severity" -Default $null + next_allowed_entrypoint = Get-PropertyValue -Item $catalog -Name "next_allowed_entrypoint" -Default $null + attempt_allowed = $false + block_reason = Get-PropertyValue -Item $catalog -Name "block_reason" -Default $null + blocks_not_approved_extension = Get-PropertyValue -Item $catalog -Name "blocks_not_approved_extension" -Default $null + enablement_status = Get-PropertyValue -Item $catalog -Name "enablement_status" -Default $null + runtime_gate_state = Get-PropertyValue -Item $catalog -Name "runtime_gate_state" -Default $null + runtime_effect = Get-PropertyValue -Item $catalog -Name "runtime_effect" -Default $null + allows_process_start = $false + allows_payload_traffic = $false + guardrail_summary = $guardrails + release_notes = $requiredReleaseNotes +} + +$releaseFieldsCompatible = Test-ObjectHasFields -Item $release -Fields $requiredReleaseFields +$releaseValuesCompatible = ( + [string](Get-PropertyValue -Item $release -Name "schema_version" -Default "") -eq "rap.remote_workspace_real_adapter_not_approved_outcome_operator_stop_status_catalog_release_marker.v1" -and + [string](Get-PropertyValue -Item $release -Name "source_catalog_schema" -Default "") -eq "rap.remote_workspace_real_adapter_not_approved_outcome_operator_stop_status_catalog.v1" -and + [string](Get-PropertyValue -Item $release -Name "release_status" -Default "") -eq "operator_stop_status_catalog_released_contract_only" -and + [string](Get-PropertyValue -Item $release -Name "catalog_status" -Default "") -eq "operator_stop_status_catalog_complete" -and + [string](Get-PropertyValue -Item $release -Name "catalog_entry_type" -Default "") -eq "blocked_not_approved_operator_stop" -and + [string](Get-PropertyValue -Item $release -Name "operator_status" -Default "") -eq "not_approved_branch_closed_new_request_required" -and + [string](Get-PropertyValue -Item $release -Name "operator_action" -Default "") -eq "keep_real_adapter_disabled_until_new_explicit_enablement_request" -and + [string](Get-PropertyValue -Item $release -Name "display_severity" -Default "") -eq "blocked" -and + [string](Get-PropertyValue -Item $release -Name "next_allowed_entrypoint" -Default "") -eq "new_explicit_enablement_request_only" -and + -not [bool](Get-PropertyValue -Item $release -Name "attempt_allowed" -Default $true) -and + [string](Get-PropertyValue -Item $release -Name "block_reason" -Default "") -eq "new_explicit_enablement_request_required" -and + [bool](Get-PropertyValue -Item $release -Name "blocks_not_approved_extension" -Default $false) -and + [string](Get-PropertyValue -Item $release -Name "enablement_status" -Default "") -eq "not_enabled" -and + [string](Get-PropertyValue -Item $release -Name "runtime_gate_state" -Default "") -eq "validated_contract_only_not_enabled" -and + [string](Get-PropertyValue -Item $release -Name "runtime_effect" -Default "") -eq "contract_only_no_runtime_enablement" -and + -not [bool](Get-PropertyValue -Item $release -Name "allows_process_start" -Default $true) -and + -not [bool](Get-PropertyValue -Item $release -Name "allows_payload_traffic" -Default $true) +) +$releaseNotesCompatible = Test-ArrayContainsAll -Actual @($release.release_notes) -Expected $requiredReleaseNotes +$guardrailsCompatible = ( + (Test-ObjectHasFields -Item $guardrails -Fields $requiredGuardrailFields) -and + [bool](Get-PropertyValue -Item $guardrails -Name "activation_blocked" -Default $false) -and + -not [bool](Get-PropertyValue -Item $guardrails -Name "process_start_allowed" -Default $true) -and + -not [bool](Get-PropertyValue -Item $guardrails -Name "health_probe_enabled" -Default $true) -and + [string](Get-PropertyValue -Item $guardrails -Name "payload_traffic" -Default "") -eq "none" -and + -not [bool](Get-PropertyValue -Item $guardrails -Name "allows_process_start" -Default $true) -and + -not [bool](Get-PropertyValue -Item $guardrails -Name "allows_payload_traffic" -Default $true) +) + +$checks = [ordered]@{ + source_smoke_passed = ([bool]$sourceResult.passed) + source_schema_expected = ([string]$sourceResult.schema_version -eq "c19z154.remote_workspace_real_adapter_not_approved_outcome_operator_stop_status_catalog_compatibility_smoke.v1") + catalog_present = ($null -ne $catalog) + release_fields_compatible = $releaseFieldsCompatible + release_values_compatible = $releaseValuesCompatible + release_notes_compatible = $releaseNotesCompatible + guardrails_compatible = $guardrailsCompatible +} +$failed = @($checks.GetEnumerator() | Where-Object { -not $_.Value } | ForEach-Object { $_.Key }) + +$result = [ordered]@{ + schema_version = "c19z155.remote_workspace_real_adapter_not_approved_outcome_operator_stop_status_catalog_release_marker_smoke.v1" + source_result_path = $sourceFile + cluster_id = $ClusterID + required_release_fields = $requiredReleaseFields + required_guardrail_fields = $requiredGuardrailFields + required_release_notes = $requiredReleaseNotes + not_approved_outcome_operator_stop_status_catalog_release_marker = $release + checks = $checks + failed_checks = $failed + passed = ($failed.Count -eq 0) +} + +$fullResultPath = Join-Path $repoRoot $ResultPath +$resultDir = Split-Path -Parent $fullResultPath +if ($resultDir) { New-Item -ItemType Directory -Force -Path $resultDir | Out-Null } +$result | ConvertTo-Json -Depth 100 | Set-Content -Encoding UTF8 -Path $fullResultPath + +if (-not $result.passed) { + throw "C19Z155 remote workspace real-adapter not-approved outcome operator stop status catalog release marker smoke failed. Result: $fullResultPath Failed: $($failed -join ', ')" +} + +Write-Host "C19Z155 remote workspace real-adapter not-approved outcome operator stop status catalog release marker smoke passed. Result: $fullResultPath" +$result diff --git a/scripts/fabric/c19z156-remote-workspace-real-adapter-not-approved-outcome-operator-stop-status-catalog-release-marker-compatibility-smoke.ps1 b/scripts/fabric/c19z156-remote-workspace-real-adapter-not-approved-outcome-operator-stop-status-catalog-release-marker-compatibility-smoke.ps1 new file mode 100644 index 0000000..5a5a70d --- /dev/null +++ b/scripts/fabric/c19z156-remote-workspace-real-adapter-not-approved-outcome-operator-stop-status-catalog-release-marker-compatibility-smoke.ps1 @@ -0,0 +1,133 @@ +param( + [string]$ApiBaseUrl = "http://192.168.200.61:18121/api/v1", + [string]$ClusterID = "cfc0743d-d960-49fb-9de8-96e063d5e4aa", + [string]$ActorUserID = "f67d943f-5397-4b3a-a229-695fe67ad700", + [string]$RequestedNodeName = "test-1", + [string]$DefaultNodeName = "test-2", + [string]$MatrixNodeName = "test-1", + [string]$ResultPath = "artifacts\c19z156-remote-workspace-real-adapter-not-approved-outcome-operator-stop-status-catalog-release-marker-compatibility-smoke-result.json" +) + +Set-StrictMode -Version Latest +$ErrorActionPreference = "Stop" + +$repoRoot = (Resolve-Path (Join-Path $PSScriptRoot "..\..")).ProviderPath +$sourceResultPath = "artifacts\c19z156-remote-workspace-real-adapter-not-approved-outcome-operator-stop-status-catalog-release-marker-source-result.json" +$requiredReleaseFields = @("schema_version", "source_catalog_schema", "release_status", "release_marker", "catalog_status", "catalog_entry_type", "operator_status", "operator_action", "display_severity", "next_allowed_entrypoint", "attempt_allowed", "block_reason", "blocks_not_approved_extension", "enablement_status", "runtime_gate_state", "runtime_effect", "allows_process_start", "allows_payload_traffic", "guardrail_summary", "release_notes") +$requiredGuardrailFields = @("activation_blocked", "process_start_allowed", "health_probe_enabled", "payload_traffic", "allows_process_start", "allows_payload_traffic") +$requiredReleaseNotes = @("operator_stop_status_catalog_release_marked", "not_approved_branch_remains_closed", "new_explicit_enablement_request_required", "real_runtime_gate_not_enabled", "process_start_disabled", "payload_forwarding_disabled") + +function Get-PropertyValue { + param([object]$Item, [string]$Name, [object]$Default = $null) + if ($null -eq $Item) { return $Default } + if ($Item -is [System.Collections.IDictionary]) { + if ($Item.Contains($Name)) { return $Item[$Name] } + return $Default + } + $property = $Item.PSObject.Properties[$Name] + if ($null -eq $property) { return $Default } + return $property.Value +} + +function Test-ObjectHasFields { + param([object]$Item, [string[]]$Fields) + if ($null -eq $Item) { return $false } + foreach ($field in $Fields) { + if ($Item -is [System.Collections.IDictionary]) { + if (-not $Item.Contains($field)) { return $false } + continue + } + if ($null -eq $Item.PSObject.Properties[$field]) { return $false } + } + return $true +} + +function Test-ArrayContainsAll { + param([object[]]$Actual, [string[]]$Expected) + foreach ($item in $Expected) { + if ($Actual -notcontains $item) { return $false } + } + return $true +} + +& powershell -ExecutionPolicy Bypass -File (Join-Path $PSScriptRoot "c19z155-remote-workspace-real-adapter-not-approved-outcome-operator-stop-status-catalog-release-marker-smoke.ps1") ` + -ApiBaseUrl $ApiBaseUrl ` + -ClusterID $ClusterID ` + -ActorUserID $ActorUserID ` + -RequestedNodeName $RequestedNodeName ` + -DefaultNodeName $DefaultNodeName ` + -MatrixNodeName $MatrixNodeName ` + -ResultPath $sourceResultPath | Out-Null + +$sourceFile = Join-Path $repoRoot $sourceResultPath +$sourceResult = Get-Content -Raw -Path $sourceFile | ConvertFrom-Json +$release = Get-PropertyValue -Item $sourceResult -Name "not_approved_outcome_operator_stop_status_catalog_release_marker" -Default $null +$guardrails = Get-PropertyValue -Item $release -Name "guardrail_summary" -Default $null +$releaseNotes = @(Get-PropertyValue -Item $release -Name "release_notes" -Default @()) + +$releaseFieldsCompatible = Test-ObjectHasFields -Item $release -Fields $requiredReleaseFields +$releaseValuesCompatible = ( + [string](Get-PropertyValue -Item $release -Name "schema_version" -Default "") -eq "rap.remote_workspace_real_adapter_not_approved_outcome_operator_stop_status_catalog_release_marker.v1" -and + [string](Get-PropertyValue -Item $release -Name "source_catalog_schema" -Default "") -eq "rap.remote_workspace_real_adapter_not_approved_outcome_operator_stop_status_catalog.v1" -and + [string](Get-PropertyValue -Item $release -Name "release_status" -Default "") -eq "operator_stop_status_catalog_released_contract_only" -and + [string](Get-PropertyValue -Item $release -Name "catalog_status" -Default "") -eq "operator_stop_status_catalog_complete" -and + [string](Get-PropertyValue -Item $release -Name "catalog_entry_type" -Default "") -eq "blocked_not_approved_operator_stop" -and + [string](Get-PropertyValue -Item $release -Name "operator_status" -Default "") -eq "not_approved_branch_closed_new_request_required" -and + [string](Get-PropertyValue -Item $release -Name "operator_action" -Default "") -eq "keep_real_adapter_disabled_until_new_explicit_enablement_request" -and + [string](Get-PropertyValue -Item $release -Name "display_severity" -Default "") -eq "blocked" -and + [string](Get-PropertyValue -Item $release -Name "next_allowed_entrypoint" -Default "") -eq "new_explicit_enablement_request_only" -and + -not [bool](Get-PropertyValue -Item $release -Name "attempt_allowed" -Default $true) -and + [string](Get-PropertyValue -Item $release -Name "block_reason" -Default "") -eq "new_explicit_enablement_request_required" -and + [bool](Get-PropertyValue -Item $release -Name "blocks_not_approved_extension" -Default $false) -and + [string](Get-PropertyValue -Item $release -Name "enablement_status" -Default "") -eq "not_enabled" -and + [string](Get-PropertyValue -Item $release -Name "runtime_gate_state" -Default "") -eq "validated_contract_only_not_enabled" -and + [string](Get-PropertyValue -Item $release -Name "runtime_effect" -Default "") -eq "contract_only_no_runtime_enablement" -and + -not [bool](Get-PropertyValue -Item $release -Name "allows_process_start" -Default $true) -and + -not [bool](Get-PropertyValue -Item $release -Name "allows_payload_traffic" -Default $true) +) +$releaseNotesCompatible = Test-ArrayContainsAll -Actual $releaseNotes -Expected $requiredReleaseNotes +$guardrailsCompatible = ( + (Test-ObjectHasFields -Item $guardrails -Fields $requiredGuardrailFields) -and + [bool](Get-PropertyValue -Item $guardrails -Name "activation_blocked" -Default $false) -and + -not [bool](Get-PropertyValue -Item $guardrails -Name "process_start_allowed" -Default $true) -and + -not [bool](Get-PropertyValue -Item $guardrails -Name "health_probe_enabled" -Default $true) -and + [string](Get-PropertyValue -Item $guardrails -Name "payload_traffic" -Default "") -eq "none" -and + -not [bool](Get-PropertyValue -Item $guardrails -Name "allows_process_start" -Default $true) -and + -not [bool](Get-PropertyValue -Item $guardrails -Name "allows_payload_traffic" -Default $true) +) + +$checks = [ordered]@{ + source_smoke_passed = ([bool]$sourceResult.passed) + source_schema_expected = ([string]$sourceResult.schema_version -eq "c19z155.remote_workspace_real_adapter_not_approved_outcome_operator_stop_status_catalog_release_marker_smoke.v1") + release_present = ($null -ne $release) + release_fields_compatible = $releaseFieldsCompatible + release_values_compatible = $releaseValuesCompatible + release_notes_compatible = $releaseNotesCompatible + guardrails_compatible = $guardrailsCompatible +} +$failed = @($checks.GetEnumerator() | Where-Object { -not $_.Value } | ForEach-Object { $_.Key }) + +$result = [ordered]@{ + schema_version = "c19z156.remote_workspace_real_adapter_not_approved_outcome_operator_stop_status_catalog_release_marker_compatibility_smoke.v1" + source_result_path = $sourceFile + cluster_id = $ClusterID + required_release_fields = $requiredReleaseFields + required_guardrail_fields = $requiredGuardrailFields + required_release_notes = $requiredReleaseNotes + not_approved_outcome_operator_stop_status_catalog_release_marker = $release + checks = $checks + failed_checks = $failed + passed = ($failed.Count -eq 0) +} + +$fullResultPath = Join-Path $repoRoot $ResultPath +$resultDir = Split-Path -Parent $fullResultPath +if ($resultDir) { New-Item -ItemType Directory -Force -Path $resultDir | Out-Null } +$result | ConvertTo-Json -Depth 100 | Set-Content -Encoding UTF8 -Path $fullResultPath + +if (-not $result.passed) { + throw "C19Z156 remote workspace real-adapter not-approved outcome operator stop status catalog release marker compatibility smoke failed. Result: $fullResultPath Failed: $($failed -join ', ')" +} + +Write-Host "C19Z156 remote workspace real-adapter not-approved outcome operator stop status catalog release marker compatibility smoke passed. Result: $fullResultPath" +$result diff --git a/scripts/fabric/c19z157-remote-workspace-real-adapter-not-approved-outcome-operator-stop-status-catalog-package-index-smoke.ps1 b/scripts/fabric/c19z157-remote-workspace-real-adapter-not-approved-outcome-operator-stop-status-catalog-package-index-smoke.ps1 new file mode 100644 index 0000000..ce63876 --- /dev/null +++ b/scripts/fabric/c19z157-remote-workspace-real-adapter-not-approved-outcome-operator-stop-status-catalog-package-index-smoke.ps1 @@ -0,0 +1,155 @@ +param( + [string]$ApiBaseUrl = "http://192.168.200.61:18121/api/v1", + [string]$ClusterID = "cfc0743d-d960-49fb-9de8-96e063d5e4aa", + [string]$ActorUserID = "f67d943f-5397-4b3a-a229-695fe67ad700", + [string]$RequestedNodeName = "test-1", + [string]$DefaultNodeName = "test-2", + [string]$MatrixNodeName = "test-1", + [string]$ResultPath = "artifacts\c19z157-remote-workspace-real-adapter-not-approved-outcome-operator-stop-status-catalog-package-index-smoke-result.json" +) + +Set-StrictMode -Version Latest +$ErrorActionPreference = "Stop" + +$repoRoot = (Resolve-Path (Join-Path $PSScriptRoot "..\..")).ProviderPath +$sourceResultPath = "artifacts\c19z157-remote-workspace-real-adapter-not-approved-outcome-operator-stop-status-catalog-package-index-source-result.json" +$requiredPackageFields = @("schema_version", "source_release_schema", "package_status", "package_marker", "release_status", "catalog_entry_type", "operator_status", "operator_action", "display_severity", "next_allowed_entrypoint", "attempt_allowed", "block_reason", "blocks_not_approved_extension", "enablement_status", "runtime_gate_state", "runtime_effect", "allows_process_start", "allows_payload_traffic", "guardrail_summary", "package_notes") +$requiredGuardrailFields = @("activation_blocked", "process_start_allowed", "health_probe_enabled", "payload_traffic", "allows_process_start", "allows_payload_traffic") +$requiredPackageNotes = @("operator_stop_status_catalog_package_index_complete", "not_approved_branch_remains_closed", "new_explicit_enablement_request_required", "real_runtime_gate_not_enabled", "process_start_disabled", "payload_forwarding_disabled") + +function Get-PropertyValue { + param([object]$Item, [string]$Name, [object]$Default = $null) + if ($null -eq $Item) { return $Default } + if ($Item -is [System.Collections.IDictionary]) { + if ($Item.Contains($Name)) { return $Item[$Name] } + return $Default + } + $property = $Item.PSObject.Properties[$Name] + if ($null -eq $property) { return $Default } + return $property.Value +} + +function Test-ObjectHasFields { + param([object]$Item, [string[]]$Fields) + if ($null -eq $Item) { return $false } + foreach ($field in $Fields) { + if ($Item -is [System.Collections.IDictionary]) { + if (-not $Item.Contains($field)) { return $false } + continue + } + if ($null -eq $Item.PSObject.Properties[$field]) { return $false } + } + return $true +} + +function Test-ArrayContainsAll { + param([object[]]$Actual, [string[]]$Expected) + foreach ($item in $Expected) { + if ($Actual -notcontains $item) { return $false } + } + return $true +} + +& powershell -ExecutionPolicy Bypass -File (Join-Path $PSScriptRoot "c19z156-remote-workspace-real-adapter-not-approved-outcome-operator-stop-status-catalog-release-marker-compatibility-smoke.ps1") ` + -ApiBaseUrl $ApiBaseUrl ` + -ClusterID $ClusterID ` + -ActorUserID $ActorUserID ` + -RequestedNodeName $RequestedNodeName ` + -DefaultNodeName $DefaultNodeName ` + -MatrixNodeName $MatrixNodeName ` + -ResultPath $sourceResultPath | Out-Null + +$sourceFile = Join-Path $repoRoot $sourceResultPath +$sourceResult = Get-Content -Raw -Path $sourceFile | ConvertFrom-Json +$release = Get-PropertyValue -Item $sourceResult -Name "not_approved_outcome_operator_stop_status_catalog_release_marker" -Default $null +$guardrails = Get-PropertyValue -Item $release -Name "guardrail_summary" -Default $null + +$packageIndex = [ordered]@{ + schema_version = "rap.remote_workspace_real_adapter_not_approved_outcome_operator_stop_status_catalog_package_index.v1" + source_release_schema = Get-PropertyValue -Item $release -Name "schema_version" -Default $null + package_status = "operator_stop_status_catalog_package_index_complete" + package_marker = "c19z157_real_adapter_not_approved_outcome_operator_stop_status_catalog_package_index" + release_status = Get-PropertyValue -Item $release -Name "release_status" -Default $null + catalog_entry_type = Get-PropertyValue -Item $release -Name "catalog_entry_type" -Default $null + operator_status = Get-PropertyValue -Item $release -Name "operator_status" -Default $null + operator_action = Get-PropertyValue -Item $release -Name "operator_action" -Default $null + display_severity = Get-PropertyValue -Item $release -Name "display_severity" -Default $null + next_allowed_entrypoint = Get-PropertyValue -Item $release -Name "next_allowed_entrypoint" -Default $null + attempt_allowed = $false + block_reason = Get-PropertyValue -Item $release -Name "block_reason" -Default $null + blocks_not_approved_extension = Get-PropertyValue -Item $release -Name "blocks_not_approved_extension" -Default $null + enablement_status = Get-PropertyValue -Item $release -Name "enablement_status" -Default $null + runtime_gate_state = Get-PropertyValue -Item $release -Name "runtime_gate_state" -Default $null + runtime_effect = Get-PropertyValue -Item $release -Name "runtime_effect" -Default $null + allows_process_start = $false + allows_payload_traffic = $false + guardrail_summary = $guardrails + package_notes = $requiredPackageNotes +} + +$packageFieldsCompatible = Test-ObjectHasFields -Item $packageIndex -Fields $requiredPackageFields +$packageValuesCompatible = ( + [string](Get-PropertyValue -Item $packageIndex -Name "schema_version" -Default "") -eq "rap.remote_workspace_real_adapter_not_approved_outcome_operator_stop_status_catalog_package_index.v1" -and + [string](Get-PropertyValue -Item $packageIndex -Name "source_release_schema" -Default "") -eq "rap.remote_workspace_real_adapter_not_approved_outcome_operator_stop_status_catalog_release_marker.v1" -and + [string](Get-PropertyValue -Item $packageIndex -Name "package_status" -Default "") -eq "operator_stop_status_catalog_package_index_complete" -and + [string](Get-PropertyValue -Item $packageIndex -Name "release_status" -Default "") -eq "operator_stop_status_catalog_released_contract_only" -and + [string](Get-PropertyValue -Item $packageIndex -Name "catalog_entry_type" -Default "") -eq "blocked_not_approved_operator_stop" -and + [string](Get-PropertyValue -Item $packageIndex -Name "operator_status" -Default "") -eq "not_approved_branch_closed_new_request_required" -and + [string](Get-PropertyValue -Item $packageIndex -Name "operator_action" -Default "") -eq "keep_real_adapter_disabled_until_new_explicit_enablement_request" -and + [string](Get-PropertyValue -Item $packageIndex -Name "display_severity" -Default "") -eq "blocked" -and + [string](Get-PropertyValue -Item $packageIndex -Name "next_allowed_entrypoint" -Default "") -eq "new_explicit_enablement_request_only" -and + -not [bool](Get-PropertyValue -Item $packageIndex -Name "attempt_allowed" -Default $true) -and + [string](Get-PropertyValue -Item $packageIndex -Name "block_reason" -Default "") -eq "new_explicit_enablement_request_required" -and + [bool](Get-PropertyValue -Item $packageIndex -Name "blocks_not_approved_extension" -Default $false) -and + [string](Get-PropertyValue -Item $packageIndex -Name "enablement_status" -Default "") -eq "not_enabled" -and + [string](Get-PropertyValue -Item $packageIndex -Name "runtime_gate_state" -Default "") -eq "validated_contract_only_not_enabled" -and + [string](Get-PropertyValue -Item $packageIndex -Name "runtime_effect" -Default "") -eq "contract_only_no_runtime_enablement" -and + -not [bool](Get-PropertyValue -Item $packageIndex -Name "allows_process_start" -Default $true) -and + -not [bool](Get-PropertyValue -Item $packageIndex -Name "allows_payload_traffic" -Default $true) +) +$packageNotesCompatible = Test-ArrayContainsAll -Actual @($packageIndex.package_notes) -Expected $requiredPackageNotes +$guardrailsCompatible = ( + (Test-ObjectHasFields -Item $guardrails -Fields $requiredGuardrailFields) -and + [bool](Get-PropertyValue -Item $guardrails -Name "activation_blocked" -Default $false) -and + -not [bool](Get-PropertyValue -Item $guardrails -Name "process_start_allowed" -Default $true) -and + -not [bool](Get-PropertyValue -Item $guardrails -Name "health_probe_enabled" -Default $true) -and + [string](Get-PropertyValue -Item $guardrails -Name "payload_traffic" -Default "") -eq "none" -and + -not [bool](Get-PropertyValue -Item $guardrails -Name "allows_process_start" -Default $true) -and + -not [bool](Get-PropertyValue -Item $guardrails -Name "allows_payload_traffic" -Default $true) +) + +$checks = [ordered]@{ + source_smoke_passed = ([bool]$sourceResult.passed) + source_schema_expected = ([string]$sourceResult.schema_version -eq "c19z156.remote_workspace_real_adapter_not_approved_outcome_operator_stop_status_catalog_release_marker_compatibility_smoke.v1") + release_present = ($null -ne $release) + package_fields_compatible = $packageFieldsCompatible + package_values_compatible = $packageValuesCompatible + package_notes_compatible = $packageNotesCompatible + guardrails_compatible = $guardrailsCompatible +} +$failed = @($checks.GetEnumerator() | Where-Object { -not $_.Value } | ForEach-Object { $_.Key }) + +$result = [ordered]@{ + schema_version = "c19z157.remote_workspace_real_adapter_not_approved_outcome_operator_stop_status_catalog_package_index_smoke.v1" + source_result_path = $sourceFile + cluster_id = $ClusterID + required_package_fields = $requiredPackageFields + required_guardrail_fields = $requiredGuardrailFields + required_package_notes = $requiredPackageNotes + not_approved_outcome_operator_stop_status_catalog_package_index = $packageIndex + checks = $checks + failed_checks = $failed + passed = ($failed.Count -eq 0) +} + +$fullResultPath = Join-Path $repoRoot $ResultPath +$resultDir = Split-Path -Parent $fullResultPath +if ($resultDir) { New-Item -ItemType Directory -Force -Path $resultDir | Out-Null } +$result | ConvertTo-Json -Depth 100 | Set-Content -Encoding UTF8 -Path $fullResultPath + +if (-not $result.passed) { + throw "C19Z157 remote workspace real-adapter not-approved outcome operator stop status catalog package index smoke failed. Result: $fullResultPath Failed: $($failed -join ', ')" +} + +Write-Host "C19Z157 remote workspace real-adapter not-approved outcome operator stop status catalog package index smoke passed. Result: $fullResultPath" +$result diff --git a/scripts/fabric/c19z158-remote-workspace-real-adapter-not-approved-outcome-operator-stop-status-catalog-package-index-compatibility-smoke.ps1 b/scripts/fabric/c19z158-remote-workspace-real-adapter-not-approved-outcome-operator-stop-status-catalog-package-index-compatibility-smoke.ps1 new file mode 100644 index 0000000..88af328 --- /dev/null +++ b/scripts/fabric/c19z158-remote-workspace-real-adapter-not-approved-outcome-operator-stop-status-catalog-package-index-compatibility-smoke.ps1 @@ -0,0 +1,133 @@ +param( + [string]$ApiBaseUrl = "http://192.168.200.61:18121/api/v1", + [string]$ClusterID = "cfc0743d-d960-49fb-9de8-96e063d5e4aa", + [string]$ActorUserID = "f67d943f-5397-4b3a-a229-695fe67ad700", + [string]$RequestedNodeName = "test-1", + [string]$DefaultNodeName = "test-2", + [string]$MatrixNodeName = "test-1", + [string]$ResultPath = "artifacts\c19z158-remote-workspace-real-adapter-not-approved-outcome-operator-stop-status-catalog-package-index-compatibility-smoke-result.json" +) + +Set-StrictMode -Version Latest +$ErrorActionPreference = "Stop" + +$repoRoot = (Resolve-Path (Join-Path $PSScriptRoot "..\..")).ProviderPath +$sourceResultPath = "artifacts\c19z158-remote-workspace-real-adapter-not-approved-outcome-operator-stop-status-catalog-package-index-source-result.json" +$requiredPackageFields = @("schema_version", "source_release_schema", "package_status", "package_marker", "release_status", "catalog_entry_type", "operator_status", "operator_action", "display_severity", "next_allowed_entrypoint", "attempt_allowed", "block_reason", "blocks_not_approved_extension", "enablement_status", "runtime_gate_state", "runtime_effect", "allows_process_start", "allows_payload_traffic", "guardrail_summary", "package_notes") +$requiredGuardrailFields = @("activation_blocked", "process_start_allowed", "health_probe_enabled", "payload_traffic", "allows_process_start", "allows_payload_traffic") +$requiredPackageNotes = @("operator_stop_status_catalog_package_index_complete", "not_approved_branch_remains_closed", "new_explicit_enablement_request_required", "real_runtime_gate_not_enabled", "process_start_disabled", "payload_forwarding_disabled") + +function Get-PropertyValue { + param([object]$Item, [string]$Name, [object]$Default = $null) + if ($null -eq $Item) { return $Default } + if ($Item -is [System.Collections.IDictionary]) { + if ($Item.Contains($Name)) { return $Item[$Name] } + return $Default + } + $property = $Item.PSObject.Properties[$Name] + if ($null -eq $property) { return $Default } + return $property.Value +} + +function Test-ObjectHasFields { + param([object]$Item, [string[]]$Fields) + if ($null -eq $Item) { return $false } + foreach ($field in $Fields) { + if ($Item -is [System.Collections.IDictionary]) { + if (-not $Item.Contains($field)) { return $false } + continue + } + if ($null -eq $Item.PSObject.Properties[$field]) { return $false } + } + return $true +} + +function Test-ArrayContainsAll { + param([object[]]$Actual, [string[]]$Expected) + foreach ($item in $Expected) { + if ($Actual -notcontains $item) { return $false } + } + return $true +} + +& powershell -ExecutionPolicy Bypass -File (Join-Path $PSScriptRoot "c19z157-remote-workspace-real-adapter-not-approved-outcome-operator-stop-status-catalog-package-index-smoke.ps1") ` + -ApiBaseUrl $ApiBaseUrl ` + -ClusterID $ClusterID ` + -ActorUserID $ActorUserID ` + -RequestedNodeName $RequestedNodeName ` + -DefaultNodeName $DefaultNodeName ` + -MatrixNodeName $MatrixNodeName ` + -ResultPath $sourceResultPath | Out-Null + +$sourceFile = Join-Path $repoRoot $sourceResultPath +$sourceResult = Get-Content -Raw -Path $sourceFile | ConvertFrom-Json +$packageIndex = Get-PropertyValue -Item $sourceResult -Name "not_approved_outcome_operator_stop_status_catalog_package_index" -Default $null +$guardrails = Get-PropertyValue -Item $packageIndex -Name "guardrail_summary" -Default $null +$packageNotes = @(Get-PropertyValue -Item $packageIndex -Name "package_notes" -Default @()) + +$packageFieldsCompatible = Test-ObjectHasFields -Item $packageIndex -Fields $requiredPackageFields +$packageValuesCompatible = ( + [string](Get-PropertyValue -Item $packageIndex -Name "schema_version" -Default "") -eq "rap.remote_workspace_real_adapter_not_approved_outcome_operator_stop_status_catalog_package_index.v1" -and + [string](Get-PropertyValue -Item $packageIndex -Name "source_release_schema" -Default "") -eq "rap.remote_workspace_real_adapter_not_approved_outcome_operator_stop_status_catalog_release_marker.v1" -and + [string](Get-PropertyValue -Item $packageIndex -Name "package_status" -Default "") -eq "operator_stop_status_catalog_package_index_complete" -and + [string](Get-PropertyValue -Item $packageIndex -Name "release_status" -Default "") -eq "operator_stop_status_catalog_released_contract_only" -and + [string](Get-PropertyValue -Item $packageIndex -Name "catalog_entry_type" -Default "") -eq "blocked_not_approved_operator_stop" -and + [string](Get-PropertyValue -Item $packageIndex -Name "operator_status" -Default "") -eq "not_approved_branch_closed_new_request_required" -and + [string](Get-PropertyValue -Item $packageIndex -Name "operator_action" -Default "") -eq "keep_real_adapter_disabled_until_new_explicit_enablement_request" -and + [string](Get-PropertyValue -Item $packageIndex -Name "display_severity" -Default "") -eq "blocked" -and + [string](Get-PropertyValue -Item $packageIndex -Name "next_allowed_entrypoint" -Default "") -eq "new_explicit_enablement_request_only" -and + -not [bool](Get-PropertyValue -Item $packageIndex -Name "attempt_allowed" -Default $true) -and + [string](Get-PropertyValue -Item $packageIndex -Name "block_reason" -Default "") -eq "new_explicit_enablement_request_required" -and + [bool](Get-PropertyValue -Item $packageIndex -Name "blocks_not_approved_extension" -Default $false) -and + [string](Get-PropertyValue -Item $packageIndex -Name "enablement_status" -Default "") -eq "not_enabled" -and + [string](Get-PropertyValue -Item $packageIndex -Name "runtime_gate_state" -Default "") -eq "validated_contract_only_not_enabled" -and + [string](Get-PropertyValue -Item $packageIndex -Name "runtime_effect" -Default "") -eq "contract_only_no_runtime_enablement" -and + -not [bool](Get-PropertyValue -Item $packageIndex -Name "allows_process_start" -Default $true) -and + -not [bool](Get-PropertyValue -Item $packageIndex -Name "allows_payload_traffic" -Default $true) +) +$packageNotesCompatible = Test-ArrayContainsAll -Actual $packageNotes -Expected $requiredPackageNotes +$guardrailsCompatible = ( + (Test-ObjectHasFields -Item $guardrails -Fields $requiredGuardrailFields) -and + [bool](Get-PropertyValue -Item $guardrails -Name "activation_blocked" -Default $false) -and + -not [bool](Get-PropertyValue -Item $guardrails -Name "process_start_allowed" -Default $true) -and + -not [bool](Get-PropertyValue -Item $guardrails -Name "health_probe_enabled" -Default $true) -and + [string](Get-PropertyValue -Item $guardrails -Name "payload_traffic" -Default "") -eq "none" -and + -not [bool](Get-PropertyValue -Item $guardrails -Name "allows_process_start" -Default $true) -and + -not [bool](Get-PropertyValue -Item $guardrails -Name "allows_payload_traffic" -Default $true) +) + +$checks = [ordered]@{ + source_smoke_passed = ([bool]$sourceResult.passed) + source_schema_expected = ([string]$sourceResult.schema_version -eq "c19z157.remote_workspace_real_adapter_not_approved_outcome_operator_stop_status_catalog_package_index_smoke.v1") + package_present = ($null -ne $packageIndex) + package_fields_compatible = $packageFieldsCompatible + package_values_compatible = $packageValuesCompatible + package_notes_compatible = $packageNotesCompatible + guardrails_compatible = $guardrailsCompatible +} +$failed = @($checks.GetEnumerator() | Where-Object { -not $_.Value } | ForEach-Object { $_.Key }) + +$result = [ordered]@{ + schema_version = "c19z158.remote_workspace_real_adapter_not_approved_outcome_operator_stop_status_catalog_package_index_compatibility_smoke.v1" + source_result_path = $sourceFile + cluster_id = $ClusterID + required_package_fields = $requiredPackageFields + required_guardrail_fields = $requiredGuardrailFields + required_package_notes = $requiredPackageNotes + not_approved_outcome_operator_stop_status_catalog_package_index = $packageIndex + checks = $checks + failed_checks = $failed + passed = ($failed.Count -eq 0) +} + +$fullResultPath = Join-Path $repoRoot $ResultPath +$resultDir = Split-Path -Parent $fullResultPath +if ($resultDir) { New-Item -ItemType Directory -Force -Path $resultDir | Out-Null } +$result | ConvertTo-Json -Depth 100 | Set-Content -Encoding UTF8 -Path $fullResultPath + +if (-not $result.passed) { + throw "C19Z158 remote workspace real-adapter not-approved outcome operator stop status catalog package index compatibility smoke failed. Result: $fullResultPath Failed: $($failed -join ', ')" +} + +Write-Host "C19Z158 remote workspace real-adapter not-approved outcome operator stop status catalog package index compatibility smoke passed. Result: $fullResultPath" +$result diff --git a/scripts/fabric/c19z159-remote-workspace-real-adapter-not-approved-outcome-operator-stop-status-catalog-closeout-summary-smoke.ps1 b/scripts/fabric/c19z159-remote-workspace-real-adapter-not-approved-outcome-operator-stop-status-catalog-closeout-summary-smoke.ps1 new file mode 100644 index 0000000..b4aeef1 --- /dev/null +++ b/scripts/fabric/c19z159-remote-workspace-real-adapter-not-approved-outcome-operator-stop-status-catalog-closeout-summary-smoke.ps1 @@ -0,0 +1,157 @@ +param( + [string]$ApiBaseUrl = "http://192.168.200.61:18121/api/v1", + [string]$ClusterID = "cfc0743d-d960-49fb-9de8-96e063d5e4aa", + [string]$ActorUserID = "f67d943f-5397-4b3a-a229-695fe67ad700", + [string]$RequestedNodeName = "test-1", + [string]$DefaultNodeName = "test-2", + [string]$MatrixNodeName = "test-1", + [string]$ResultPath = "artifacts\c19z159-remote-workspace-real-adapter-not-approved-outcome-operator-stop-status-catalog-closeout-summary-smoke-result.json" +) + +Set-StrictMode -Version Latest +$ErrorActionPreference = "Stop" + +$repoRoot = (Resolve-Path (Join-Path $PSScriptRoot "..\..")).ProviderPath +$sourceResultPath = "artifacts\c19z159-remote-workspace-real-adapter-not-approved-outcome-operator-stop-status-catalog-closeout-summary-source-result.json" +$requiredCloseoutFields = @("schema_version", "source_package_schema", "closeout_status", "closeout_marker", "package_status", "release_status", "catalog_entry_type", "operator_status", "operator_action", "display_severity", "next_allowed_entrypoint", "attempt_allowed", "block_reason", "blocks_not_approved_extension", "enablement_status", "runtime_gate_state", "runtime_effect", "allows_process_start", "allows_payload_traffic", "guardrail_summary", "closeout_notes") +$requiredGuardrailFields = @("activation_blocked", "process_start_allowed", "health_probe_enabled", "payload_traffic", "allows_process_start", "allows_payload_traffic") +$requiredCloseoutNotes = @("operator_stop_status_catalog_closeout_complete", "not_approved_branch_remains_closed", "new_explicit_enablement_request_required", "real_runtime_gate_not_enabled", "process_start_disabled", "payload_forwarding_disabled") + +function Get-PropertyValue { + param([object]$Item, [string]$Name, [object]$Default = $null) + if ($null -eq $Item) { return $Default } + if ($Item -is [System.Collections.IDictionary]) { + if ($Item.Contains($Name)) { return $Item[$Name] } + return $Default + } + $property = $Item.PSObject.Properties[$Name] + if ($null -eq $property) { return $Default } + return $property.Value +} + +function Test-ObjectHasFields { + param([object]$Item, [string[]]$Fields) + if ($null -eq $Item) { return $false } + foreach ($field in $Fields) { + if ($Item -is [System.Collections.IDictionary]) { + if (-not $Item.Contains($field)) { return $false } + continue + } + if ($null -eq $Item.PSObject.Properties[$field]) { return $false } + } + return $true +} + +function Test-ArrayContainsAll { + param([object[]]$Actual, [string[]]$Expected) + foreach ($item in $Expected) { + if ($Actual -notcontains $item) { return $false } + } + return $true +} + +& powershell -ExecutionPolicy Bypass -File (Join-Path $PSScriptRoot "c19z158-remote-workspace-real-adapter-not-approved-outcome-operator-stop-status-catalog-package-index-compatibility-smoke.ps1") ` + -ApiBaseUrl $ApiBaseUrl ` + -ClusterID $ClusterID ` + -ActorUserID $ActorUserID ` + -RequestedNodeName $RequestedNodeName ` + -DefaultNodeName $DefaultNodeName ` + -MatrixNodeName $MatrixNodeName ` + -ResultPath $sourceResultPath | Out-Null + +$sourceFile = Join-Path $repoRoot $sourceResultPath +$sourceResult = Get-Content -Raw -Path $sourceFile | ConvertFrom-Json +$packageIndex = Get-PropertyValue -Item $sourceResult -Name "not_approved_outcome_operator_stop_status_catalog_package_index" -Default $null +$guardrails = Get-PropertyValue -Item $packageIndex -Name "guardrail_summary" -Default $null + +$closeout = [ordered]@{ + schema_version = "rap.remote_workspace_real_adapter_not_approved_outcome_operator_stop_status_catalog_closeout_summary.v1" + source_package_schema = Get-PropertyValue -Item $packageIndex -Name "schema_version" -Default $null + closeout_status = "operator_stop_status_catalog_package_closed_contract_only" + closeout_marker = "c19z159_real_adapter_not_approved_outcome_operator_stop_status_catalog_closeout_summary" + package_status = Get-PropertyValue -Item $packageIndex -Name "package_status" -Default $null + release_status = Get-PropertyValue -Item $packageIndex -Name "release_status" -Default $null + catalog_entry_type = Get-PropertyValue -Item $packageIndex -Name "catalog_entry_type" -Default $null + operator_status = Get-PropertyValue -Item $packageIndex -Name "operator_status" -Default $null + operator_action = Get-PropertyValue -Item $packageIndex -Name "operator_action" -Default $null + display_severity = Get-PropertyValue -Item $packageIndex -Name "display_severity" -Default $null + next_allowed_entrypoint = Get-PropertyValue -Item $packageIndex -Name "next_allowed_entrypoint" -Default $null + attempt_allowed = $false + block_reason = Get-PropertyValue -Item $packageIndex -Name "block_reason" -Default $null + blocks_not_approved_extension = Get-PropertyValue -Item $packageIndex -Name "blocks_not_approved_extension" -Default $null + enablement_status = Get-PropertyValue -Item $packageIndex -Name "enablement_status" -Default $null + runtime_gate_state = Get-PropertyValue -Item $packageIndex -Name "runtime_gate_state" -Default $null + runtime_effect = Get-PropertyValue -Item $packageIndex -Name "runtime_effect" -Default $null + allows_process_start = $false + allows_payload_traffic = $false + guardrail_summary = $guardrails + closeout_notes = $requiredCloseoutNotes +} + +$closeoutFieldsCompatible = Test-ObjectHasFields -Item $closeout -Fields $requiredCloseoutFields +$closeoutValuesCompatible = ( + [string](Get-PropertyValue -Item $closeout -Name "schema_version" -Default "") -eq "rap.remote_workspace_real_adapter_not_approved_outcome_operator_stop_status_catalog_closeout_summary.v1" -and + [string](Get-PropertyValue -Item $closeout -Name "source_package_schema" -Default "") -eq "rap.remote_workspace_real_adapter_not_approved_outcome_operator_stop_status_catalog_package_index.v1" -and + [string](Get-PropertyValue -Item $closeout -Name "closeout_status" -Default "") -eq "operator_stop_status_catalog_package_closed_contract_only" -and + [string](Get-PropertyValue -Item $closeout -Name "package_status" -Default "") -eq "operator_stop_status_catalog_package_index_complete" -and + [string](Get-PropertyValue -Item $closeout -Name "release_status" -Default "") -eq "operator_stop_status_catalog_released_contract_only" -and + [string](Get-PropertyValue -Item $closeout -Name "catalog_entry_type" -Default "") -eq "blocked_not_approved_operator_stop" -and + [string](Get-PropertyValue -Item $closeout -Name "operator_status" -Default "") -eq "not_approved_branch_closed_new_request_required" -and + [string](Get-PropertyValue -Item $closeout -Name "operator_action" -Default "") -eq "keep_real_adapter_disabled_until_new_explicit_enablement_request" -and + [string](Get-PropertyValue -Item $closeout -Name "display_severity" -Default "") -eq "blocked" -and + [string](Get-PropertyValue -Item $closeout -Name "next_allowed_entrypoint" -Default "") -eq "new_explicit_enablement_request_only" -and + -not [bool](Get-PropertyValue -Item $closeout -Name "attempt_allowed" -Default $true) -and + [string](Get-PropertyValue -Item $closeout -Name "block_reason" -Default "") -eq "new_explicit_enablement_request_required" -and + [bool](Get-PropertyValue -Item $closeout -Name "blocks_not_approved_extension" -Default $false) -and + [string](Get-PropertyValue -Item $closeout -Name "enablement_status" -Default "") -eq "not_enabled" -and + [string](Get-PropertyValue -Item $closeout -Name "runtime_gate_state" -Default "") -eq "validated_contract_only_not_enabled" -and + [string](Get-PropertyValue -Item $closeout -Name "runtime_effect" -Default "") -eq "contract_only_no_runtime_enablement" -and + -not [bool](Get-PropertyValue -Item $closeout -Name "allows_process_start" -Default $true) -and + -not [bool](Get-PropertyValue -Item $closeout -Name "allows_payload_traffic" -Default $true) +) +$closeoutNotesCompatible = Test-ArrayContainsAll -Actual @($closeout.closeout_notes) -Expected $requiredCloseoutNotes +$guardrailsCompatible = ( + (Test-ObjectHasFields -Item $guardrails -Fields $requiredGuardrailFields) -and + [bool](Get-PropertyValue -Item $guardrails -Name "activation_blocked" -Default $false) -and + -not [bool](Get-PropertyValue -Item $guardrails -Name "process_start_allowed" -Default $true) -and + -not [bool](Get-PropertyValue -Item $guardrails -Name "health_probe_enabled" -Default $true) -and + [string](Get-PropertyValue -Item $guardrails -Name "payload_traffic" -Default "") -eq "none" -and + -not [bool](Get-PropertyValue -Item $guardrails -Name "allows_process_start" -Default $true) -and + -not [bool](Get-PropertyValue -Item $guardrails -Name "allows_payload_traffic" -Default $true) +) + +$checks = [ordered]@{ + source_smoke_passed = ([bool]$sourceResult.passed) + source_schema_expected = ([string]$sourceResult.schema_version -eq "c19z158.remote_workspace_real_adapter_not_approved_outcome_operator_stop_status_catalog_package_index_compatibility_smoke.v1") + package_present = ($null -ne $packageIndex) + closeout_fields_compatible = $closeoutFieldsCompatible + closeout_values_compatible = $closeoutValuesCompatible + closeout_notes_compatible = $closeoutNotesCompatible + guardrails_compatible = $guardrailsCompatible +} +$failed = @($checks.GetEnumerator() | Where-Object { -not $_.Value } | ForEach-Object { $_.Key }) + +$result = [ordered]@{ + schema_version = "c19z159.remote_workspace_real_adapter_not_approved_outcome_operator_stop_status_catalog_closeout_summary_smoke.v1" + source_result_path = $sourceFile + cluster_id = $ClusterID + required_closeout_fields = $requiredCloseoutFields + required_guardrail_fields = $requiredGuardrailFields + required_closeout_notes = $requiredCloseoutNotes + not_approved_outcome_operator_stop_status_catalog_closeout_summary = $closeout + checks = $checks + failed_checks = $failed + passed = ($failed.Count -eq 0) +} + +$fullResultPath = Join-Path $repoRoot $ResultPath +$resultDir = Split-Path -Parent $fullResultPath +if ($resultDir) { New-Item -ItemType Directory -Force -Path $resultDir | Out-Null } +$result | ConvertTo-Json -Depth 100 | Set-Content -Encoding UTF8 -Path $fullResultPath + +if (-not $result.passed) { + throw "C19Z159 remote workspace real-adapter not-approved outcome operator stop status catalog closeout summary smoke failed. Result: $fullResultPath Failed: $($failed -join ', ')" +} + +Write-Host "C19Z159 remote workspace real-adapter not-approved outcome operator stop status catalog closeout summary smoke passed. Result: $fullResultPath" +$result diff --git a/scripts/fabric/c19z16-remote-workspace-mailbox-preflight-attention-reason-coverage-smoke.ps1 b/scripts/fabric/c19z16-remote-workspace-mailbox-preflight-attention-reason-coverage-smoke.ps1 new file mode 100644 index 0000000..a20f165 --- /dev/null +++ b/scripts/fabric/c19z16-remote-workspace-mailbox-preflight-attention-reason-coverage-smoke.ps1 @@ -0,0 +1,83 @@ +param( + [string]$ApiBaseUrl = "http://192.168.200.61:18121/api/v1", + [string]$ClusterID = "cfc0743d-d960-49fb-9de8-96e063d5e4aa", + [string]$ActorUserID = "f67d943f-5397-4b3a-a229-695fe67ad700", + [string]$EntryNodeName = "test-1", + [string]$ExitNodeName = "test-2", + [string]$EntryBaseUrl = "http://192.168.200.61:19131", + [string]$ResultPath = "artifacts\c19z16-remote-workspace-mailbox-preflight-attention-reason-coverage-smoke-result.json" +) + +Set-StrictMode -Version Latest +$ErrorActionPreference = "Stop" + +$repoRoot = (Resolve-Path (Join-Path $PSScriptRoot "..\..")).ProviderPath +$sourceResultPath = "artifacts\c19z16-remote-workspace-mailbox-preflight-attention-reason-coverage-source-result.json" + +function Get-PropertyValue { + param([object]$Item, [string]$Name, [object]$Default = $null) + if ($null -eq $Item) { return $Default } + $property = $Item.PSObject.Properties[$Name] + if ($null -eq $property) { return $Default } + return $property.Value +} + +function Test-SingleAttentionReason { + param([object]$Sink, [string]$AdapterSessionID) + if ($null -eq $Sink) { return $false } + $readiness = Get-PropertyValue -Item $Sink -Name "adapter_runtime_readiness" -Default $null + $rollup = Get-PropertyValue -Item $readiness -Name "last_preflight" -Default $null + if ($null -eq $rollup) { return $false } + return ( + [string](Get-PropertyValue -Item $readiness -Name "adapter_session_id" -Default "") -eq $AdapterSessionID -and + [string](Get-PropertyValue -Item $readiness -Name "preflight_attention_status" -Default "") -eq "needs_attention" -and + [string](Get-PropertyValue -Item $readiness -Name "preflight_attention_reason" -Default "") -eq "resync_required_preflight_observed" -and + [string](Get-PropertyValue -Item $rollup -Name "preflight_attention_status" -Default "") -eq "needs_attention" -and + [string](Get-PropertyValue -Item $rollup -Name "preflight_attention_reason" -Default "") -eq "resync_required_preflight_observed" + ) +} + +$source = & powershell -ExecutionPolicy Bypass -File (Join-Path $PSScriptRoot "c19z13-remote-workspace-mailbox-preflight-attention-smoke.ps1") ` + -ApiBaseUrl $ApiBaseUrl ` + -ClusterID $ClusterID ` + -ActorUserID $ActorUserID ` + -EntryNodeName $EntryNodeName ` + -ExitNodeName $ExitNodeName ` + -EntryBaseUrl $EntryBaseUrl ` + -ResultPath $sourceResultPath + +$sourceFile = Join-Path $repoRoot $sourceResultPath +$sourceResult = Get-Content -Raw -Path $sourceFile | ConvertFrom-Json +$adapterSessionID = [string](Get-PropertyValue -Item $sourceResult -Name "adapter_session_id" -Default "") +$observed = Get-PropertyValue -Item $sourceResult -Name "observed" -Default $null + +$checks = [ordered]@{ + source_smoke_passed = ([bool]$sourceResult.passed) + adapter_session_id_present = ($adapterSessionID -match "^rap-rw-adapter-session-[0-9a-f]{24}$") + workload_single_attention_reason_visible = (Test-SingleAttentionReason -Sink $observed.workload_sink -AdapterSessionID $adapterSessionID) + telemetry_single_attention_reason_visible = (Test-SingleAttentionReason -Sink $observed.telemetry_sink -AdapterSessionID $adapterSessionID) +} +$failed = @($checks.GetEnumerator() | Where-Object { -not $_.Value } | ForEach-Object { $_.Key }) + +$result = [ordered]@{ + schema_version = "c19z16.remote_workspace_mailbox_preflight_attention_reason_coverage_smoke.v1" + source_result_path = $sourceFile + cluster_id = $ClusterID + adapter_session_id = $adapterSessionID + observed = $observed + checks = $checks + failed_checks = $failed + passed = ($failed.Count -eq 0) +} + +$fullResultPath = Join-Path $repoRoot $ResultPath +$resultDir = Split-Path -Parent $fullResultPath +if ($resultDir) { New-Item -ItemType Directory -Force -Path $resultDir | Out-Null } +$result | ConvertTo-Json -Depth 100 | Set-Content -Encoding UTF8 -Path $fullResultPath + +if (-not $result.passed) { + throw "C19Z16 remote workspace mailbox preflight attention reason coverage smoke failed. Result: $fullResultPath Failed: $($failed -join ', ')" +} + +Write-Host "C19Z16 remote workspace mailbox preflight attention reason coverage smoke passed. Result: $fullResultPath" +$result diff --git a/scripts/fabric/c19z160-remote-workspace-real-adapter-not-approved-outcome-operator-stop-status-catalog-closeout-summary-compatibility-smoke.ps1 b/scripts/fabric/c19z160-remote-workspace-real-adapter-not-approved-outcome-operator-stop-status-catalog-closeout-summary-compatibility-smoke.ps1 new file mode 100644 index 0000000..7f02df4 --- /dev/null +++ b/scripts/fabric/c19z160-remote-workspace-real-adapter-not-approved-outcome-operator-stop-status-catalog-closeout-summary-compatibility-smoke.ps1 @@ -0,0 +1,134 @@ +param( + [string]$ApiBaseUrl = "http://192.168.200.61:18121/api/v1", + [string]$ClusterID = "cfc0743d-d960-49fb-9de8-96e063d5e4aa", + [string]$ActorUserID = "f67d943f-5397-4b3a-a229-695fe67ad700", + [string]$RequestedNodeName = "test-1", + [string]$DefaultNodeName = "test-2", + [string]$MatrixNodeName = "test-1", + [string]$ResultPath = "artifacts\c19z160-remote-workspace-real-adapter-not-approved-outcome-operator-stop-status-catalog-closeout-summary-compatibility-smoke-result.json" +) + +Set-StrictMode -Version Latest +$ErrorActionPreference = "Stop" + +$repoRoot = (Resolve-Path (Join-Path $PSScriptRoot "..\..")).ProviderPath +$sourceResultPath = "artifacts\c19z160-remote-workspace-real-adapter-not-approved-outcome-operator-stop-status-catalog-closeout-summary-source-result.json" +$requiredCloseoutFields = @("schema_version", "source_package_schema", "closeout_status", "closeout_marker", "package_status", "release_status", "catalog_entry_type", "operator_status", "operator_action", "display_severity", "next_allowed_entrypoint", "attempt_allowed", "block_reason", "blocks_not_approved_extension", "enablement_status", "runtime_gate_state", "runtime_effect", "allows_process_start", "allows_payload_traffic", "guardrail_summary", "closeout_notes") +$requiredGuardrailFields = @("activation_blocked", "process_start_allowed", "health_probe_enabled", "payload_traffic", "allows_process_start", "allows_payload_traffic") +$requiredCloseoutNotes = @("operator_stop_status_catalog_closeout_complete", "not_approved_branch_remains_closed", "new_explicit_enablement_request_required", "real_runtime_gate_not_enabled", "process_start_disabled", "payload_forwarding_disabled") + +function Get-PropertyValue { + param([object]$Item, [string]$Name, [object]$Default = $null) + if ($null -eq $Item) { return $Default } + if ($Item -is [System.Collections.IDictionary]) { + if ($Item.Contains($Name)) { return $Item[$Name] } + return $Default + } + $property = $Item.PSObject.Properties[$Name] + if ($null -eq $property) { return $Default } + return $property.Value +} + +function Test-ObjectHasFields { + param([object]$Item, [string[]]$Fields) + if ($null -eq $Item) { return $false } + foreach ($field in $Fields) { + if ($Item -is [System.Collections.IDictionary]) { + if (-not $Item.Contains($field)) { return $false } + continue + } + if ($null -eq $Item.PSObject.Properties[$field]) { return $false } + } + return $true +} + +function Test-ArrayContainsAll { + param([object[]]$Actual, [string[]]$Expected) + foreach ($item in $Expected) { + if ($Actual -notcontains $item) { return $false } + } + return $true +} + +& powershell -ExecutionPolicy Bypass -File (Join-Path $PSScriptRoot "c19z159-remote-workspace-real-adapter-not-approved-outcome-operator-stop-status-catalog-closeout-summary-smoke.ps1") ` + -ApiBaseUrl $ApiBaseUrl ` + -ClusterID $ClusterID ` + -ActorUserID $ActorUserID ` + -RequestedNodeName $RequestedNodeName ` + -DefaultNodeName $DefaultNodeName ` + -MatrixNodeName $MatrixNodeName ` + -ResultPath $sourceResultPath | Out-Null + +$sourceFile = Join-Path $repoRoot $sourceResultPath +$sourceResult = Get-Content -Raw -Path $sourceFile | ConvertFrom-Json +$closeout = Get-PropertyValue -Item $sourceResult -Name "not_approved_outcome_operator_stop_status_catalog_closeout_summary" -Default $null +$guardrails = Get-PropertyValue -Item $closeout -Name "guardrail_summary" -Default $null +$closeoutNotes = @(Get-PropertyValue -Item $closeout -Name "closeout_notes" -Default @()) + +$closeoutFieldsCompatible = Test-ObjectHasFields -Item $closeout -Fields $requiredCloseoutFields +$closeoutValuesCompatible = ( + [string](Get-PropertyValue -Item $closeout -Name "schema_version" -Default "") -eq "rap.remote_workspace_real_adapter_not_approved_outcome_operator_stop_status_catalog_closeout_summary.v1" -and + [string](Get-PropertyValue -Item $closeout -Name "source_package_schema" -Default "") -eq "rap.remote_workspace_real_adapter_not_approved_outcome_operator_stop_status_catalog_package_index.v1" -and + [string](Get-PropertyValue -Item $closeout -Name "closeout_status" -Default "") -eq "operator_stop_status_catalog_package_closed_contract_only" -and + [string](Get-PropertyValue -Item $closeout -Name "package_status" -Default "") -eq "operator_stop_status_catalog_package_index_complete" -and + [string](Get-PropertyValue -Item $closeout -Name "release_status" -Default "") -eq "operator_stop_status_catalog_released_contract_only" -and + [string](Get-PropertyValue -Item $closeout -Name "catalog_entry_type" -Default "") -eq "blocked_not_approved_operator_stop" -and + [string](Get-PropertyValue -Item $closeout -Name "operator_status" -Default "") -eq "not_approved_branch_closed_new_request_required" -and + [string](Get-PropertyValue -Item $closeout -Name "operator_action" -Default "") -eq "keep_real_adapter_disabled_until_new_explicit_enablement_request" -and + [string](Get-PropertyValue -Item $closeout -Name "display_severity" -Default "") -eq "blocked" -and + [string](Get-PropertyValue -Item $closeout -Name "next_allowed_entrypoint" -Default "") -eq "new_explicit_enablement_request_only" -and + -not [bool](Get-PropertyValue -Item $closeout -Name "attempt_allowed" -Default $true) -and + [string](Get-PropertyValue -Item $closeout -Name "block_reason" -Default "") -eq "new_explicit_enablement_request_required" -and + [bool](Get-PropertyValue -Item $closeout -Name "blocks_not_approved_extension" -Default $false) -and + [string](Get-PropertyValue -Item $closeout -Name "enablement_status" -Default "") -eq "not_enabled" -and + [string](Get-PropertyValue -Item $closeout -Name "runtime_gate_state" -Default "") -eq "validated_contract_only_not_enabled" -and + [string](Get-PropertyValue -Item $closeout -Name "runtime_effect" -Default "") -eq "contract_only_no_runtime_enablement" -and + -not [bool](Get-PropertyValue -Item $closeout -Name "allows_process_start" -Default $true) -and + -not [bool](Get-PropertyValue -Item $closeout -Name "allows_payload_traffic" -Default $true) +) +$closeoutNotesCompatible = Test-ArrayContainsAll -Actual $closeoutNotes -Expected $requiredCloseoutNotes +$guardrailsCompatible = ( + (Test-ObjectHasFields -Item $guardrails -Fields $requiredGuardrailFields) -and + [bool](Get-PropertyValue -Item $guardrails -Name "activation_blocked" -Default $false) -and + -not [bool](Get-PropertyValue -Item $guardrails -Name "process_start_allowed" -Default $true) -and + -not [bool](Get-PropertyValue -Item $guardrails -Name "health_probe_enabled" -Default $true) -and + [string](Get-PropertyValue -Item $guardrails -Name "payload_traffic" -Default "") -eq "none" -and + -not [bool](Get-PropertyValue -Item $guardrails -Name "allows_process_start" -Default $true) -and + -not [bool](Get-PropertyValue -Item $guardrails -Name "allows_payload_traffic" -Default $true) +) + +$checks = [ordered]@{ + source_smoke_passed = ([bool]$sourceResult.passed) + source_schema_expected = ([string]$sourceResult.schema_version -eq "c19z159.remote_workspace_real_adapter_not_approved_outcome_operator_stop_status_catalog_closeout_summary_smoke.v1") + closeout_present = ($null -ne $closeout) + closeout_fields_compatible = $closeoutFieldsCompatible + closeout_values_compatible = $closeoutValuesCompatible + closeout_notes_compatible = $closeoutNotesCompatible + guardrails_compatible = $guardrailsCompatible +} +$failed = @($checks.GetEnumerator() | Where-Object { -not $_.Value } | ForEach-Object { $_.Key }) + +$result = [ordered]@{ + schema_version = "c19z160.remote_workspace_real_adapter_not_approved_outcome_operator_stop_status_catalog_closeout_summary_compatibility_smoke.v1" + source_result_path = $sourceFile + cluster_id = $ClusterID + required_closeout_fields = $requiredCloseoutFields + required_guardrail_fields = $requiredGuardrailFields + required_closeout_notes = $requiredCloseoutNotes + not_approved_outcome_operator_stop_status_catalog_closeout_summary = $closeout + checks = $checks + failed_checks = $failed + passed = ($failed.Count -eq 0) +} + +$fullResultPath = Join-Path $repoRoot $ResultPath +$resultDir = Split-Path -Parent $fullResultPath +if ($resultDir) { New-Item -ItemType Directory -Force -Path $resultDir | Out-Null } +$result | ConvertTo-Json -Depth 100 | Set-Content -Encoding UTF8 -Path $fullResultPath + +if (-not $result.passed) { + throw "C19Z160 remote workspace real-adapter not-approved outcome operator stop status catalog closeout summary compatibility smoke failed. Result: $fullResultPath Failed: $($failed -join ', ')" +} + +Write-Host "C19Z160 remote workspace real-adapter not-approved outcome operator stop status catalog closeout summary compatibility smoke passed. Result: $fullResultPath" +$result diff --git a/scripts/fabric/c19z161-remote-workspace-real-adapter-not-approved-outcome-operator-stop-status-final-archive-marker-smoke.ps1 b/scripts/fabric/c19z161-remote-workspace-real-adapter-not-approved-outcome-operator-stop-status-final-archive-marker-smoke.ps1 new file mode 100644 index 0000000..d1a8b14 --- /dev/null +++ b/scripts/fabric/c19z161-remote-workspace-real-adapter-not-approved-outcome-operator-stop-status-final-archive-marker-smoke.ps1 @@ -0,0 +1,159 @@ +param( + [string]$ApiBaseUrl = "http://192.168.200.61:18121/api/v1", + [string]$ClusterID = "cfc0743d-d960-49fb-9de8-96e063d5e4aa", + [string]$ActorUserID = "f67d943f-5397-4b3a-a229-695fe67ad700", + [string]$RequestedNodeName = "test-1", + [string]$DefaultNodeName = "test-2", + [string]$MatrixNodeName = "test-1", + [string]$ResultPath = "artifacts\c19z161-remote-workspace-real-adapter-not-approved-outcome-operator-stop-status-final-archive-marker-smoke-result.json" +) + +Set-StrictMode -Version Latest +$ErrorActionPreference = "Stop" + +$repoRoot = (Resolve-Path (Join-Path $PSScriptRoot "..\..")).ProviderPath +$sourceResultPath = "artifacts\c19z161-remote-workspace-real-adapter-not-approved-outcome-operator-stop-status-final-archive-marker-source-result.json" +$requiredArchiveFields = @("schema_version", "source_closeout_schema", "archive_status", "archive_marker", "closeout_status", "package_status", "release_status", "catalog_entry_type", "operator_status", "operator_action", "display_severity", "next_allowed_entrypoint", "attempt_allowed", "block_reason", "blocks_not_approved_extension", "enablement_status", "runtime_gate_state", "runtime_effect", "allows_process_start", "allows_payload_traffic", "guardrail_summary", "archive_notes") +$requiredGuardrailFields = @("activation_blocked", "process_start_allowed", "health_probe_enabled", "payload_traffic", "allows_process_start", "allows_payload_traffic") +$requiredArchiveNotes = @("operator_stop_status_final_archive_marked", "not_approved_branch_remains_closed", "new_explicit_enablement_request_required", "real_runtime_gate_not_enabled", "process_start_disabled", "payload_forwarding_disabled") + +function Get-PropertyValue { + param([object]$Item, [string]$Name, [object]$Default = $null) + if ($null -eq $Item) { return $Default } + if ($Item -is [System.Collections.IDictionary]) { + if ($Item.Contains($Name)) { return $Item[$Name] } + return $Default + } + $property = $Item.PSObject.Properties[$Name] + if ($null -eq $property) { return $Default } + return $property.Value +} + +function Test-ObjectHasFields { + param([object]$Item, [string[]]$Fields) + if ($null -eq $Item) { return $false } + foreach ($field in $Fields) { + if ($Item -is [System.Collections.IDictionary]) { + if (-not $Item.Contains($field)) { return $false } + continue + } + if ($null -eq $Item.PSObject.Properties[$field]) { return $false } + } + return $true +} + +function Test-ArrayContainsAll { + param([object[]]$Actual, [string[]]$Expected) + foreach ($item in $Expected) { + if ($Actual -notcontains $item) { return $false } + } + return $true +} + +& powershell -ExecutionPolicy Bypass -File (Join-Path $PSScriptRoot "c19z160-remote-workspace-real-adapter-not-approved-outcome-operator-stop-status-catalog-closeout-summary-compatibility-smoke.ps1") ` + -ApiBaseUrl $ApiBaseUrl ` + -ClusterID $ClusterID ` + -ActorUserID $ActorUserID ` + -RequestedNodeName $RequestedNodeName ` + -DefaultNodeName $DefaultNodeName ` + -MatrixNodeName $MatrixNodeName ` + -ResultPath $sourceResultPath | Out-Null + +$sourceFile = Join-Path $repoRoot $sourceResultPath +$sourceResult = Get-Content -Raw -Path $sourceFile | ConvertFrom-Json +$closeout = Get-PropertyValue -Item $sourceResult -Name "not_approved_outcome_operator_stop_status_catalog_closeout_summary" -Default $null +$guardrails = Get-PropertyValue -Item $closeout -Name "guardrail_summary" -Default $null + +$archive = [ordered]@{ + schema_version = "rap.remote_workspace_real_adapter_not_approved_outcome_operator_stop_status_final_archive_marker.v1" + source_closeout_schema = Get-PropertyValue -Item $closeout -Name "schema_version" -Default $null + archive_status = "operator_stop_status_final_archived_contract_only" + archive_marker = "c19z161_real_adapter_not_approved_outcome_operator_stop_status_final_archive_marker" + closeout_status = Get-PropertyValue -Item $closeout -Name "closeout_status" -Default $null + package_status = Get-PropertyValue -Item $closeout -Name "package_status" -Default $null + release_status = Get-PropertyValue -Item $closeout -Name "release_status" -Default $null + catalog_entry_type = Get-PropertyValue -Item $closeout -Name "catalog_entry_type" -Default $null + operator_status = Get-PropertyValue -Item $closeout -Name "operator_status" -Default $null + operator_action = Get-PropertyValue -Item $closeout -Name "operator_action" -Default $null + display_severity = Get-PropertyValue -Item $closeout -Name "display_severity" -Default $null + next_allowed_entrypoint = Get-PropertyValue -Item $closeout -Name "next_allowed_entrypoint" -Default $null + attempt_allowed = $false + block_reason = Get-PropertyValue -Item $closeout -Name "block_reason" -Default $null + blocks_not_approved_extension = Get-PropertyValue -Item $closeout -Name "blocks_not_approved_extension" -Default $null + enablement_status = Get-PropertyValue -Item $closeout -Name "enablement_status" -Default $null + runtime_gate_state = Get-PropertyValue -Item $closeout -Name "runtime_gate_state" -Default $null + runtime_effect = Get-PropertyValue -Item $closeout -Name "runtime_effect" -Default $null + allows_process_start = $false + allows_payload_traffic = $false + guardrail_summary = $guardrails + archive_notes = $requiredArchiveNotes +} + +$archiveFieldsCompatible = Test-ObjectHasFields -Item $archive -Fields $requiredArchiveFields +$archiveValuesCompatible = ( + [string](Get-PropertyValue -Item $archive -Name "schema_version" -Default "") -eq "rap.remote_workspace_real_adapter_not_approved_outcome_operator_stop_status_final_archive_marker.v1" -and + [string](Get-PropertyValue -Item $archive -Name "source_closeout_schema" -Default "") -eq "rap.remote_workspace_real_adapter_not_approved_outcome_operator_stop_status_catalog_closeout_summary.v1" -and + [string](Get-PropertyValue -Item $archive -Name "archive_status" -Default "") -eq "operator_stop_status_final_archived_contract_only" -and + [string](Get-PropertyValue -Item $archive -Name "closeout_status" -Default "") -eq "operator_stop_status_catalog_package_closed_contract_only" -and + [string](Get-PropertyValue -Item $archive -Name "package_status" -Default "") -eq "operator_stop_status_catalog_package_index_complete" -and + [string](Get-PropertyValue -Item $archive -Name "release_status" -Default "") -eq "operator_stop_status_catalog_released_contract_only" -and + [string](Get-PropertyValue -Item $archive -Name "catalog_entry_type" -Default "") -eq "blocked_not_approved_operator_stop" -and + [string](Get-PropertyValue -Item $archive -Name "operator_status" -Default "") -eq "not_approved_branch_closed_new_request_required" -and + [string](Get-PropertyValue -Item $archive -Name "operator_action" -Default "") -eq "keep_real_adapter_disabled_until_new_explicit_enablement_request" -and + [string](Get-PropertyValue -Item $archive -Name "display_severity" -Default "") -eq "blocked" -and + [string](Get-PropertyValue -Item $archive -Name "next_allowed_entrypoint" -Default "") -eq "new_explicit_enablement_request_only" -and + -not [bool](Get-PropertyValue -Item $archive -Name "attempt_allowed" -Default $true) -and + [string](Get-PropertyValue -Item $archive -Name "block_reason" -Default "") -eq "new_explicit_enablement_request_required" -and + [bool](Get-PropertyValue -Item $archive -Name "blocks_not_approved_extension" -Default $false) -and + [string](Get-PropertyValue -Item $archive -Name "enablement_status" -Default "") -eq "not_enabled" -and + [string](Get-PropertyValue -Item $archive -Name "runtime_gate_state" -Default "") -eq "validated_contract_only_not_enabled" -and + [string](Get-PropertyValue -Item $archive -Name "runtime_effect" -Default "") -eq "contract_only_no_runtime_enablement" -and + -not [bool](Get-PropertyValue -Item $archive -Name "allows_process_start" -Default $true) -and + -not [bool](Get-PropertyValue -Item $archive -Name "allows_payload_traffic" -Default $true) +) +$archiveNotesCompatible = Test-ArrayContainsAll -Actual @($archive.archive_notes) -Expected $requiredArchiveNotes +$guardrailsCompatible = ( + (Test-ObjectHasFields -Item $guardrails -Fields $requiredGuardrailFields) -and + [bool](Get-PropertyValue -Item $guardrails -Name "activation_blocked" -Default $false) -and + -not [bool](Get-PropertyValue -Item $guardrails -Name "process_start_allowed" -Default $true) -and + -not [bool](Get-PropertyValue -Item $guardrails -Name "health_probe_enabled" -Default $true) -and + [string](Get-PropertyValue -Item $guardrails -Name "payload_traffic" -Default "") -eq "none" -and + -not [bool](Get-PropertyValue -Item $guardrails -Name "allows_process_start" -Default $true) -and + -not [bool](Get-PropertyValue -Item $guardrails -Name "allows_payload_traffic" -Default $true) +) + +$checks = [ordered]@{ + source_smoke_passed = ([bool]$sourceResult.passed) + source_schema_expected = ([string]$sourceResult.schema_version -eq "c19z160.remote_workspace_real_adapter_not_approved_outcome_operator_stop_status_catalog_closeout_summary_compatibility_smoke.v1") + closeout_present = ($null -ne $closeout) + archive_fields_compatible = $archiveFieldsCompatible + archive_values_compatible = $archiveValuesCompatible + archive_notes_compatible = $archiveNotesCompatible + guardrails_compatible = $guardrailsCompatible +} +$failed = @($checks.GetEnumerator() | Where-Object { -not $_.Value } | ForEach-Object { $_.Key }) + +$result = [ordered]@{ + schema_version = "c19z161.remote_workspace_real_adapter_not_approved_outcome_operator_stop_status_final_archive_marker_smoke.v1" + source_result_path = $sourceFile + cluster_id = $ClusterID + required_archive_fields = $requiredArchiveFields + required_guardrail_fields = $requiredGuardrailFields + required_archive_notes = $requiredArchiveNotes + not_approved_outcome_operator_stop_status_final_archive_marker = $archive + checks = $checks + failed_checks = $failed + passed = ($failed.Count -eq 0) +} + +$fullResultPath = Join-Path $repoRoot $ResultPath +$resultDir = Split-Path -Parent $fullResultPath +if ($resultDir) { New-Item -ItemType Directory -Force -Path $resultDir | Out-Null } +$result | ConvertTo-Json -Depth 100 | Set-Content -Encoding UTF8 -Path $fullResultPath + +if (-not $result.passed) { + throw "C19Z161 remote workspace real-adapter not-approved outcome operator stop status final archive marker smoke failed. Result: $fullResultPath Failed: $($failed -join ', ')" +} + +Write-Host "C19Z161 remote workspace real-adapter not-approved outcome operator stop status final archive marker smoke passed. Result: $fullResultPath" +$result diff --git a/scripts/fabric/c19z162-remote-workspace-real-adapter-not-approved-outcome-operator-stop-status-final-archive-marker-compatibility-smoke.ps1 b/scripts/fabric/c19z162-remote-workspace-real-adapter-not-approved-outcome-operator-stop-status-final-archive-marker-compatibility-smoke.ps1 new file mode 100644 index 0000000..f44181b --- /dev/null +++ b/scripts/fabric/c19z162-remote-workspace-real-adapter-not-approved-outcome-operator-stop-status-final-archive-marker-compatibility-smoke.ps1 @@ -0,0 +1,135 @@ +param( + [string]$ApiBaseUrl = "http://192.168.200.61:18121/api/v1", + [string]$ClusterID = "cfc0743d-d960-49fb-9de8-96e063d5e4aa", + [string]$ActorUserID = "f67d943f-5397-4b3a-a229-695fe67ad700", + [string]$RequestedNodeName = "test-1", + [string]$DefaultNodeName = "test-2", + [string]$MatrixNodeName = "test-1", + [string]$ResultPath = "artifacts\c19z162-remote-workspace-real-adapter-not-approved-outcome-operator-stop-status-final-archive-marker-compatibility-smoke-result.json" +) + +Set-StrictMode -Version Latest +$ErrorActionPreference = "Stop" + +$repoRoot = (Resolve-Path (Join-Path $PSScriptRoot "..\..")).ProviderPath +$sourceResultPath = "artifacts\c19z162-remote-workspace-real-adapter-not-approved-outcome-operator-stop-status-final-archive-marker-source-result.json" +$requiredArchiveFields = @("schema_version", "source_closeout_schema", "archive_status", "archive_marker", "closeout_status", "package_status", "release_status", "catalog_entry_type", "operator_status", "operator_action", "display_severity", "next_allowed_entrypoint", "attempt_allowed", "block_reason", "blocks_not_approved_extension", "enablement_status", "runtime_gate_state", "runtime_effect", "allows_process_start", "allows_payload_traffic", "guardrail_summary", "archive_notes") +$requiredGuardrailFields = @("activation_blocked", "process_start_allowed", "health_probe_enabled", "payload_traffic", "allows_process_start", "allows_payload_traffic") +$requiredArchiveNotes = @("operator_stop_status_final_archive_marked", "not_approved_branch_remains_closed", "new_explicit_enablement_request_required", "real_runtime_gate_not_enabled", "process_start_disabled", "payload_forwarding_disabled") + +function Get-PropertyValue { + param([object]$Item, [string]$Name, [object]$Default = $null) + if ($null -eq $Item) { return $Default } + if ($Item -is [System.Collections.IDictionary]) { + if ($Item.Contains($Name)) { return $Item[$Name] } + return $Default + } + $property = $Item.PSObject.Properties[$Name] + if ($null -eq $property) { return $Default } + return $property.Value +} + +function Test-ObjectHasFields { + param([object]$Item, [string[]]$Fields) + if ($null -eq $Item) { return $false } + foreach ($field in $Fields) { + if ($Item -is [System.Collections.IDictionary]) { + if (-not $Item.Contains($field)) { return $false } + continue + } + if ($null -eq $Item.PSObject.Properties[$field]) { return $false } + } + return $true +} + +function Test-ArrayContainsAll { + param([object[]]$Actual, [string[]]$Expected) + foreach ($item in $Expected) { + if ($Actual -notcontains $item) { return $false } + } + return $true +} + +& powershell -ExecutionPolicy Bypass -File (Join-Path $PSScriptRoot "c19z161-remote-workspace-real-adapter-not-approved-outcome-operator-stop-status-final-archive-marker-smoke.ps1") ` + -ApiBaseUrl $ApiBaseUrl ` + -ClusterID $ClusterID ` + -ActorUserID $ActorUserID ` + -RequestedNodeName $RequestedNodeName ` + -DefaultNodeName $DefaultNodeName ` + -MatrixNodeName $MatrixNodeName ` + -ResultPath $sourceResultPath | Out-Null + +$sourceFile = Join-Path $repoRoot $sourceResultPath +$sourceResult = Get-Content -Raw -Path $sourceFile | ConvertFrom-Json +$archive = Get-PropertyValue -Item $sourceResult -Name "not_approved_outcome_operator_stop_status_final_archive_marker" -Default $null +$guardrails = Get-PropertyValue -Item $archive -Name "guardrail_summary" -Default $null +$archiveNotes = @(Get-PropertyValue -Item $archive -Name "archive_notes" -Default @()) + +$archiveFieldsCompatible = Test-ObjectHasFields -Item $archive -Fields $requiredArchiveFields +$archiveValuesCompatible = ( + [string](Get-PropertyValue -Item $archive -Name "schema_version" -Default "") -eq "rap.remote_workspace_real_adapter_not_approved_outcome_operator_stop_status_final_archive_marker.v1" -and + [string](Get-PropertyValue -Item $archive -Name "source_closeout_schema" -Default "") -eq "rap.remote_workspace_real_adapter_not_approved_outcome_operator_stop_status_catalog_closeout_summary.v1" -and + [string](Get-PropertyValue -Item $archive -Name "archive_status" -Default "") -eq "operator_stop_status_final_archived_contract_only" -and + [string](Get-PropertyValue -Item $archive -Name "closeout_status" -Default "") -eq "operator_stop_status_catalog_package_closed_contract_only" -and + [string](Get-PropertyValue -Item $archive -Name "package_status" -Default "") -eq "operator_stop_status_catalog_package_index_complete" -and + [string](Get-PropertyValue -Item $archive -Name "release_status" -Default "") -eq "operator_stop_status_catalog_released_contract_only" -and + [string](Get-PropertyValue -Item $archive -Name "catalog_entry_type" -Default "") -eq "blocked_not_approved_operator_stop" -and + [string](Get-PropertyValue -Item $archive -Name "operator_status" -Default "") -eq "not_approved_branch_closed_new_request_required" -and + [string](Get-PropertyValue -Item $archive -Name "operator_action" -Default "") -eq "keep_real_adapter_disabled_until_new_explicit_enablement_request" -and + [string](Get-PropertyValue -Item $archive -Name "display_severity" -Default "") -eq "blocked" -and + [string](Get-PropertyValue -Item $archive -Name "next_allowed_entrypoint" -Default "") -eq "new_explicit_enablement_request_only" -and + -not [bool](Get-PropertyValue -Item $archive -Name "attempt_allowed" -Default $true) -and + [string](Get-PropertyValue -Item $archive -Name "block_reason" -Default "") -eq "new_explicit_enablement_request_required" -and + [bool](Get-PropertyValue -Item $archive -Name "blocks_not_approved_extension" -Default $false) -and + [string](Get-PropertyValue -Item $archive -Name "enablement_status" -Default "") -eq "not_enabled" -and + [string](Get-PropertyValue -Item $archive -Name "runtime_gate_state" -Default "") -eq "validated_contract_only_not_enabled" -and + [string](Get-PropertyValue -Item $archive -Name "runtime_effect" -Default "") -eq "contract_only_no_runtime_enablement" -and + -not [bool](Get-PropertyValue -Item $archive -Name "allows_process_start" -Default $true) -and + -not [bool](Get-PropertyValue -Item $archive -Name "allows_payload_traffic" -Default $true) +) +$archiveNotesCompatible = Test-ArrayContainsAll -Actual $archiveNotes -Expected $requiredArchiveNotes +$guardrailsCompatible = ( + (Test-ObjectHasFields -Item $guardrails -Fields $requiredGuardrailFields) -and + [bool](Get-PropertyValue -Item $guardrails -Name "activation_blocked" -Default $false) -and + -not [bool](Get-PropertyValue -Item $guardrails -Name "process_start_allowed" -Default $true) -and + -not [bool](Get-PropertyValue -Item $guardrails -Name "health_probe_enabled" -Default $true) -and + [string](Get-PropertyValue -Item $guardrails -Name "payload_traffic" -Default "") -eq "none" -and + -not [bool](Get-PropertyValue -Item $guardrails -Name "allows_process_start" -Default $true) -and + -not [bool](Get-PropertyValue -Item $guardrails -Name "allows_payload_traffic" -Default $true) +) + +$checks = [ordered]@{ + source_smoke_passed = ([bool]$sourceResult.passed) + source_schema_expected = ([string]$sourceResult.schema_version -eq "c19z161.remote_workspace_real_adapter_not_approved_outcome_operator_stop_status_final_archive_marker_smoke.v1") + archive_present = ($null -ne $archive) + archive_fields_compatible = $archiveFieldsCompatible + archive_values_compatible = $archiveValuesCompatible + archive_notes_compatible = $archiveNotesCompatible + guardrails_compatible = $guardrailsCompatible +} +$failed = @($checks.GetEnumerator() | Where-Object { -not $_.Value } | ForEach-Object { $_.Key }) + +$result = [ordered]@{ + schema_version = "c19z162.remote_workspace_real_adapter_not_approved_outcome_operator_stop_status_final_archive_marker_compatibility_smoke.v1" + source_result_path = $sourceFile + cluster_id = $ClusterID + required_archive_fields = $requiredArchiveFields + required_guardrail_fields = $requiredGuardrailFields + required_archive_notes = $requiredArchiveNotes + not_approved_outcome_operator_stop_status_final_archive_marker = $archive + checks = $checks + failed_checks = $failed + passed = ($failed.Count -eq 0) +} + +$fullResultPath = Join-Path $repoRoot $ResultPath +$resultDir = Split-Path -Parent $fullResultPath +if ($resultDir) { New-Item -ItemType Directory -Force -Path $resultDir | Out-Null } +$result | ConvertTo-Json -Depth 100 | Set-Content -Encoding UTF8 -Path $fullResultPath + +if (-not $result.passed) { + throw "C19Z162 remote workspace real-adapter not-approved outcome operator stop status final archive marker compatibility smoke failed. Result: $fullResultPath Failed: $($failed -join ', ')" +} + +Write-Host "C19Z162 remote workspace real-adapter not-approved outcome operator stop status final archive marker compatibility smoke passed. Result: $fullResultPath" +$result diff --git a/scripts/fabric/c19z163-remote-workspace-real-adapter-not-approved-outcome-operator-stop-status-final-archive-manifest-smoke.ps1 b/scripts/fabric/c19z163-remote-workspace-real-adapter-not-approved-outcome-operator-stop-status-final-archive-manifest-smoke.ps1 new file mode 100644 index 0000000..9fea83e --- /dev/null +++ b/scripts/fabric/c19z163-remote-workspace-real-adapter-not-approved-outcome-operator-stop-status-final-archive-manifest-smoke.ps1 @@ -0,0 +1,161 @@ +param( + [string]$ApiBaseUrl = "http://192.168.200.61:18121/api/v1", + [string]$ClusterID = "cfc0743d-d960-49fb-9de8-96e063d5e4aa", + [string]$ActorUserID = "f67d943f-5397-4b3a-a229-695fe67ad700", + [string]$RequestedNodeName = "test-1", + [string]$DefaultNodeName = "test-2", + [string]$MatrixNodeName = "test-1", + [string]$ResultPath = "artifacts\c19z163-remote-workspace-real-adapter-not-approved-outcome-operator-stop-status-final-archive-manifest-smoke-result.json" +) + +Set-StrictMode -Version Latest +$ErrorActionPreference = "Stop" + +$repoRoot = (Resolve-Path (Join-Path $PSScriptRoot "..\..")).ProviderPath +$sourceResultPath = "artifacts\c19z163-remote-workspace-real-adapter-not-approved-outcome-operator-stop-status-final-archive-manifest-source-result.json" +$requiredManifestFields = @("schema_version", "source_archive_schema", "manifest_status", "manifest_marker", "archive_status", "closeout_status", "package_status", "release_status", "catalog_entry_type", "operator_status", "operator_action", "display_severity", "next_allowed_entrypoint", "attempt_allowed", "block_reason", "blocks_not_approved_extension", "enablement_status", "runtime_gate_state", "runtime_effect", "allows_process_start", "allows_payload_traffic", "guardrail_summary", "manifest_notes") +$requiredGuardrailFields = @("activation_blocked", "process_start_allowed", "health_probe_enabled", "payload_traffic", "allows_process_start", "allows_payload_traffic") +$requiredManifestNotes = @("operator_stop_status_final_archive_manifest_complete", "not_approved_branch_remains_closed", "new_explicit_enablement_request_required", "real_runtime_gate_not_enabled", "process_start_disabled", "payload_forwarding_disabled") + +function Get-PropertyValue { + param([object]$Item, [string]$Name, [object]$Default = $null) + if ($null -eq $Item) { return $Default } + if ($Item -is [System.Collections.IDictionary]) { + if ($Item.Contains($Name)) { return $Item[$Name] } + return $Default + } + $property = $Item.PSObject.Properties[$Name] + if ($null -eq $property) { return $Default } + return $property.Value +} + +function Test-ObjectHasFields { + param([object]$Item, [string[]]$Fields) + if ($null -eq $Item) { return $false } + foreach ($field in $Fields) { + if ($Item -is [System.Collections.IDictionary]) { + if (-not $Item.Contains($field)) { return $false } + continue + } + if ($null -eq $Item.PSObject.Properties[$field]) { return $false } + } + return $true +} + +function Test-ArrayContainsAll { + param([object[]]$Actual, [string[]]$Expected) + foreach ($item in $Expected) { + if ($Actual -notcontains $item) { return $false } + } + return $true +} + +& powershell -ExecutionPolicy Bypass -File (Join-Path $PSScriptRoot "c19z162-remote-workspace-real-adapter-not-approved-outcome-operator-stop-status-final-archive-marker-compatibility-smoke.ps1") ` + -ApiBaseUrl $ApiBaseUrl ` + -ClusterID $ClusterID ` + -ActorUserID $ActorUserID ` + -RequestedNodeName $RequestedNodeName ` + -DefaultNodeName $DefaultNodeName ` + -MatrixNodeName $MatrixNodeName ` + -ResultPath $sourceResultPath | Out-Null + +$sourceFile = Join-Path $repoRoot $sourceResultPath +$sourceResult = Get-Content -Raw -Path $sourceFile | ConvertFrom-Json +$archive = Get-PropertyValue -Item $sourceResult -Name "not_approved_outcome_operator_stop_status_final_archive_marker" -Default $null +$guardrails = Get-PropertyValue -Item $archive -Name "guardrail_summary" -Default $null + +$manifest = [ordered]@{ + schema_version = "rap.remote_workspace_real_adapter_not_approved_outcome_operator_stop_status_final_archive_manifest.v1" + source_archive_schema = Get-PropertyValue -Item $archive -Name "schema_version" -Default $null + manifest_status = "operator_stop_status_final_archive_manifest_complete" + manifest_marker = "c19z163_real_adapter_not_approved_outcome_operator_stop_status_final_archive_manifest" + archive_status = Get-PropertyValue -Item $archive -Name "archive_status" -Default $null + closeout_status = Get-PropertyValue -Item $archive -Name "closeout_status" -Default $null + package_status = Get-PropertyValue -Item $archive -Name "package_status" -Default $null + release_status = Get-PropertyValue -Item $archive -Name "release_status" -Default $null + catalog_entry_type = Get-PropertyValue -Item $archive -Name "catalog_entry_type" -Default $null + operator_status = Get-PropertyValue -Item $archive -Name "operator_status" -Default $null + operator_action = Get-PropertyValue -Item $archive -Name "operator_action" -Default $null + display_severity = Get-PropertyValue -Item $archive -Name "display_severity" -Default $null + next_allowed_entrypoint = Get-PropertyValue -Item $archive -Name "next_allowed_entrypoint" -Default $null + attempt_allowed = $false + block_reason = Get-PropertyValue -Item $archive -Name "block_reason" -Default $null + blocks_not_approved_extension = Get-PropertyValue -Item $archive -Name "blocks_not_approved_extension" -Default $null + enablement_status = Get-PropertyValue -Item $archive -Name "enablement_status" -Default $null + runtime_gate_state = Get-PropertyValue -Item $archive -Name "runtime_gate_state" -Default $null + runtime_effect = Get-PropertyValue -Item $archive -Name "runtime_effect" -Default $null + allows_process_start = $false + allows_payload_traffic = $false + guardrail_summary = $guardrails + manifest_notes = $requiredManifestNotes +} + +$manifestFieldsCompatible = Test-ObjectHasFields -Item $manifest -Fields $requiredManifestFields +$manifestValuesCompatible = ( + [string](Get-PropertyValue -Item $manifest -Name "schema_version" -Default "") -eq "rap.remote_workspace_real_adapter_not_approved_outcome_operator_stop_status_final_archive_manifest.v1" -and + [string](Get-PropertyValue -Item $manifest -Name "source_archive_schema" -Default "") -eq "rap.remote_workspace_real_adapter_not_approved_outcome_operator_stop_status_final_archive_marker.v1" -and + [string](Get-PropertyValue -Item $manifest -Name "manifest_status" -Default "") -eq "operator_stop_status_final_archive_manifest_complete" -and + [string](Get-PropertyValue -Item $manifest -Name "archive_status" -Default "") -eq "operator_stop_status_final_archived_contract_only" -and + [string](Get-PropertyValue -Item $manifest -Name "closeout_status" -Default "") -eq "operator_stop_status_catalog_package_closed_contract_only" -and + [string](Get-PropertyValue -Item $manifest -Name "package_status" -Default "") -eq "operator_stop_status_catalog_package_index_complete" -and + [string](Get-PropertyValue -Item $manifest -Name "release_status" -Default "") -eq "operator_stop_status_catalog_released_contract_only" -and + [string](Get-PropertyValue -Item $manifest -Name "catalog_entry_type" -Default "") -eq "blocked_not_approved_operator_stop" -and + [string](Get-PropertyValue -Item $manifest -Name "operator_status" -Default "") -eq "not_approved_branch_closed_new_request_required" -and + [string](Get-PropertyValue -Item $manifest -Name "operator_action" -Default "") -eq "keep_real_adapter_disabled_until_new_explicit_enablement_request" -and + [string](Get-PropertyValue -Item $manifest -Name "display_severity" -Default "") -eq "blocked" -and + [string](Get-PropertyValue -Item $manifest -Name "next_allowed_entrypoint" -Default "") -eq "new_explicit_enablement_request_only" -and + -not [bool](Get-PropertyValue -Item $manifest -Name "attempt_allowed" -Default $true) -and + [string](Get-PropertyValue -Item $manifest -Name "block_reason" -Default "") -eq "new_explicit_enablement_request_required" -and + [bool](Get-PropertyValue -Item $manifest -Name "blocks_not_approved_extension" -Default $false) -and + [string](Get-PropertyValue -Item $manifest -Name "enablement_status" -Default "") -eq "not_enabled" -and + [string](Get-PropertyValue -Item $manifest -Name "runtime_gate_state" -Default "") -eq "validated_contract_only_not_enabled" -and + [string](Get-PropertyValue -Item $manifest -Name "runtime_effect" -Default "") -eq "contract_only_no_runtime_enablement" -and + -not [bool](Get-PropertyValue -Item $manifest -Name "allows_process_start" -Default $true) -and + -not [bool](Get-PropertyValue -Item $manifest -Name "allows_payload_traffic" -Default $true) +) +$manifestNotesCompatible = Test-ArrayContainsAll -Actual @($manifest.manifest_notes) -Expected $requiredManifestNotes +$guardrailsCompatible = ( + (Test-ObjectHasFields -Item $guardrails -Fields $requiredGuardrailFields) -and + [bool](Get-PropertyValue -Item $guardrails -Name "activation_blocked" -Default $false) -and + -not [bool](Get-PropertyValue -Item $guardrails -Name "process_start_allowed" -Default $true) -and + -not [bool](Get-PropertyValue -Item $guardrails -Name "health_probe_enabled" -Default $true) -and + [string](Get-PropertyValue -Item $guardrails -Name "payload_traffic" -Default "") -eq "none" -and + -not [bool](Get-PropertyValue -Item $guardrails -Name "allows_process_start" -Default $true) -and + -not [bool](Get-PropertyValue -Item $guardrails -Name "allows_payload_traffic" -Default $true) +) + +$checks = [ordered]@{ + source_smoke_passed = ([bool]$sourceResult.passed) + source_schema_expected = ([string]$sourceResult.schema_version -eq "c19z162.remote_workspace_real_adapter_not_approved_outcome_operator_stop_status_final_archive_marker_compatibility_smoke.v1") + archive_present = ($null -ne $archive) + manifest_fields_compatible = $manifestFieldsCompatible + manifest_values_compatible = $manifestValuesCompatible + manifest_notes_compatible = $manifestNotesCompatible + guardrails_compatible = $guardrailsCompatible +} +$failed = @($checks.GetEnumerator() | Where-Object { -not $_.Value } | ForEach-Object { $_.Key }) + +$result = [ordered]@{ + schema_version = "c19z163.remote_workspace_real_adapter_not_approved_outcome_operator_stop_status_final_archive_manifest_smoke.v1" + source_result_path = $sourceFile + cluster_id = $ClusterID + required_manifest_fields = $requiredManifestFields + required_guardrail_fields = $requiredGuardrailFields + required_manifest_notes = $requiredManifestNotes + not_approved_outcome_operator_stop_status_final_archive_manifest = $manifest + checks = $checks + failed_checks = $failed + passed = ($failed.Count -eq 0) +} + +$fullResultPath = Join-Path $repoRoot $ResultPath +$resultDir = Split-Path -Parent $fullResultPath +if ($resultDir) { New-Item -ItemType Directory -Force -Path $resultDir | Out-Null } +$result | ConvertTo-Json -Depth 100 | Set-Content -Encoding UTF8 -Path $fullResultPath + +if (-not $result.passed) { + throw "C19Z163 remote workspace real-adapter not-approved outcome operator stop status final archive manifest smoke failed. Result: $fullResultPath Failed: $($failed -join ', ')" +} + +Write-Host "C19Z163 remote workspace real-adapter not-approved outcome operator stop status final archive manifest smoke passed. Result: $fullResultPath" +$result diff --git a/scripts/fabric/c19z164-remote-workspace-real-adapter-not-approved-outcome-operator-stop-status-final-archive-manifest-compatibility-smoke.ps1 b/scripts/fabric/c19z164-remote-workspace-real-adapter-not-approved-outcome-operator-stop-status-final-archive-manifest-compatibility-smoke.ps1 new file mode 100644 index 0000000..4b173f5 --- /dev/null +++ b/scripts/fabric/c19z164-remote-workspace-real-adapter-not-approved-outcome-operator-stop-status-final-archive-manifest-compatibility-smoke.ps1 @@ -0,0 +1,136 @@ +param( + [string]$ApiBaseUrl = "http://192.168.200.61:18121/api/v1", + [string]$ClusterID = "cfc0743d-d960-49fb-9de8-96e063d5e4aa", + [string]$ActorUserID = "f67d943f-5397-4b3a-a229-695fe67ad700", + [string]$RequestedNodeName = "test-1", + [string]$DefaultNodeName = "test-2", + [string]$MatrixNodeName = "test-1", + [string]$ResultPath = "artifacts\c19z164-remote-workspace-real-adapter-not-approved-outcome-operator-stop-status-final-archive-manifest-compatibility-smoke-result.json" +) + +Set-StrictMode -Version Latest +$ErrorActionPreference = "Stop" + +$repoRoot = (Resolve-Path (Join-Path $PSScriptRoot "..\..")).ProviderPath +$sourceResultPath = "artifacts\c19z164-remote-workspace-real-adapter-not-approved-outcome-operator-stop-status-final-archive-manifest-source-result.json" +$requiredManifestFields = @("schema_version", "source_archive_schema", "manifest_status", "manifest_marker", "archive_status", "closeout_status", "package_status", "release_status", "catalog_entry_type", "operator_status", "operator_action", "display_severity", "next_allowed_entrypoint", "attempt_allowed", "block_reason", "blocks_not_approved_extension", "enablement_status", "runtime_gate_state", "runtime_effect", "allows_process_start", "allows_payload_traffic", "guardrail_summary", "manifest_notes") +$requiredGuardrailFields = @("activation_blocked", "process_start_allowed", "health_probe_enabled", "payload_traffic", "allows_process_start", "allows_payload_traffic") +$requiredManifestNotes = @("operator_stop_status_final_archive_manifest_complete", "not_approved_branch_remains_closed", "new_explicit_enablement_request_required", "real_runtime_gate_not_enabled", "process_start_disabled", "payload_forwarding_disabled") + +function Get-PropertyValue { + param([object]$Item, [string]$Name, [object]$Default = $null) + if ($null -eq $Item) { return $Default } + if ($Item -is [System.Collections.IDictionary]) { + if ($Item.Contains($Name)) { return $Item[$Name] } + return $Default + } + $property = $Item.PSObject.Properties[$Name] + if ($null -eq $property) { return $Default } + return $property.Value +} + +function Test-ObjectHasFields { + param([object]$Item, [string[]]$Fields) + if ($null -eq $Item) { return $false } + foreach ($field in $Fields) { + if ($Item -is [System.Collections.IDictionary]) { + if (-not $Item.Contains($field)) { return $false } + continue + } + if ($null -eq $Item.PSObject.Properties[$field]) { return $false } + } + return $true +} + +function Test-ArrayContainsAll { + param([object[]]$Actual, [string[]]$Expected) + foreach ($item in $Expected) { + if ($Actual -notcontains $item) { return $false } + } + return $true +} + +& powershell -ExecutionPolicy Bypass -File (Join-Path $PSScriptRoot "c19z163-remote-workspace-real-adapter-not-approved-outcome-operator-stop-status-final-archive-manifest-smoke.ps1") ` + -ApiBaseUrl $ApiBaseUrl ` + -ClusterID $ClusterID ` + -ActorUserID $ActorUserID ` + -RequestedNodeName $RequestedNodeName ` + -DefaultNodeName $DefaultNodeName ` + -MatrixNodeName $MatrixNodeName ` + -ResultPath $sourceResultPath | Out-Null + +$sourceFile = Join-Path $repoRoot $sourceResultPath +$sourceResult = Get-Content -Raw -Path $sourceFile | ConvertFrom-Json +$manifest = Get-PropertyValue -Item $sourceResult -Name "not_approved_outcome_operator_stop_status_final_archive_manifest" -Default $null +$guardrails = Get-PropertyValue -Item $manifest -Name "guardrail_summary" -Default $null +$manifestNotes = @(Get-PropertyValue -Item $manifest -Name "manifest_notes" -Default @()) + +$manifestFieldsCompatible = Test-ObjectHasFields -Item $manifest -Fields $requiredManifestFields +$manifestValuesCompatible = ( + [string](Get-PropertyValue -Item $manifest -Name "schema_version" -Default "") -eq "rap.remote_workspace_real_adapter_not_approved_outcome_operator_stop_status_final_archive_manifest.v1" -and + [string](Get-PropertyValue -Item $manifest -Name "source_archive_schema" -Default "") -eq "rap.remote_workspace_real_adapter_not_approved_outcome_operator_stop_status_final_archive_marker.v1" -and + [string](Get-PropertyValue -Item $manifest -Name "manifest_status" -Default "") -eq "operator_stop_status_final_archive_manifest_complete" -and + [string](Get-PropertyValue -Item $manifest -Name "archive_status" -Default "") -eq "operator_stop_status_final_archived_contract_only" -and + [string](Get-PropertyValue -Item $manifest -Name "closeout_status" -Default "") -eq "operator_stop_status_catalog_package_closed_contract_only" -and + [string](Get-PropertyValue -Item $manifest -Name "package_status" -Default "") -eq "operator_stop_status_catalog_package_index_complete" -and + [string](Get-PropertyValue -Item $manifest -Name "release_status" -Default "") -eq "operator_stop_status_catalog_released_contract_only" -and + [string](Get-PropertyValue -Item $manifest -Name "catalog_entry_type" -Default "") -eq "blocked_not_approved_operator_stop" -and + [string](Get-PropertyValue -Item $manifest -Name "operator_status" -Default "") -eq "not_approved_branch_closed_new_request_required" -and + [string](Get-PropertyValue -Item $manifest -Name "operator_action" -Default "") -eq "keep_real_adapter_disabled_until_new_explicit_enablement_request" -and + [string](Get-PropertyValue -Item $manifest -Name "display_severity" -Default "") -eq "blocked" -and + [string](Get-PropertyValue -Item $manifest -Name "next_allowed_entrypoint" -Default "") -eq "new_explicit_enablement_request_only" -and + -not [bool](Get-PropertyValue -Item $manifest -Name "attempt_allowed" -Default $true) -and + [string](Get-PropertyValue -Item $manifest -Name "block_reason" -Default "") -eq "new_explicit_enablement_request_required" -and + [bool](Get-PropertyValue -Item $manifest -Name "blocks_not_approved_extension" -Default $false) -and + [string](Get-PropertyValue -Item $manifest -Name "enablement_status" -Default "") -eq "not_enabled" -and + [string](Get-PropertyValue -Item $manifest -Name "runtime_gate_state" -Default "") -eq "validated_contract_only_not_enabled" -and + [string](Get-PropertyValue -Item $manifest -Name "runtime_effect" -Default "") -eq "contract_only_no_runtime_enablement" -and + -not [bool](Get-PropertyValue -Item $manifest -Name "allows_process_start" -Default $true) -and + -not [bool](Get-PropertyValue -Item $manifest -Name "allows_payload_traffic" -Default $true) +) +$manifestNotesCompatible = Test-ArrayContainsAll -Actual $manifestNotes -Expected $requiredManifestNotes +$guardrailsCompatible = ( + (Test-ObjectHasFields -Item $guardrails -Fields $requiredGuardrailFields) -and + [bool](Get-PropertyValue -Item $guardrails -Name "activation_blocked" -Default $false) -and + -not [bool](Get-PropertyValue -Item $guardrails -Name "process_start_allowed" -Default $true) -and + -not [bool](Get-PropertyValue -Item $guardrails -Name "health_probe_enabled" -Default $true) -and + [string](Get-PropertyValue -Item $guardrails -Name "payload_traffic" -Default "") -eq "none" -and + -not [bool](Get-PropertyValue -Item $guardrails -Name "allows_process_start" -Default $true) -and + -not [bool](Get-PropertyValue -Item $guardrails -Name "allows_payload_traffic" -Default $true) +) + +$checks = [ordered]@{ + source_smoke_passed = ([bool]$sourceResult.passed) + source_schema_expected = ([string]$sourceResult.schema_version -eq "c19z163.remote_workspace_real_adapter_not_approved_outcome_operator_stop_status_final_archive_manifest_smoke.v1") + manifest_present = ($null -ne $manifest) + manifest_fields_compatible = $manifestFieldsCompatible + manifest_values_compatible = $manifestValuesCompatible + manifest_notes_compatible = $manifestNotesCompatible + guardrails_compatible = $guardrailsCompatible +} +$failed = @($checks.GetEnumerator() | Where-Object { -not $_.Value } | ForEach-Object { $_.Key }) + +$result = [ordered]@{ + schema_version = "c19z164.remote_workspace_real_adapter_not_approved_outcome_operator_stop_status_final_archive_manifest_compatibility_smoke.v1" + source_result_path = $sourceFile + cluster_id = $ClusterID + required_manifest_fields = $requiredManifestFields + required_guardrail_fields = $requiredGuardrailFields + required_manifest_notes = $requiredManifestNotes + not_approved_outcome_operator_stop_status_final_archive_manifest = $manifest + checks = $checks + failed_checks = $failed + passed = ($failed.Count -eq 0) +} + +$fullResultPath = Join-Path $repoRoot $ResultPath +$resultDir = Split-Path -Parent $fullResultPath +if ($resultDir) { New-Item -ItemType Directory -Force -Path $resultDir | Out-Null } +$result | ConvertTo-Json -Depth 100 | Set-Content -Encoding UTF8 -Path $fullResultPath + +if (-not $result.passed) { + throw "C19Z164 remote workspace real-adapter not-approved outcome operator stop status final archive manifest compatibility smoke failed. Result: $fullResultPath Failed: $($failed -join ', ')" +} + +Write-Host "C19Z164 remote workspace real-adapter not-approved outcome operator stop status final archive manifest compatibility smoke passed. Result: $fullResultPath" +$result diff --git a/scripts/fabric/c19z165-remote-workspace-real-adapter-not-approved-outcome-factory-terminal-complete-smoke.ps1 b/scripts/fabric/c19z165-remote-workspace-real-adapter-not-approved-outcome-factory-terminal-complete-smoke.ps1 new file mode 100644 index 0000000..08d1230 --- /dev/null +++ b/scripts/fabric/c19z165-remote-workspace-real-adapter-not-approved-outcome-factory-terminal-complete-smoke.ps1 @@ -0,0 +1,157 @@ +param( + [string]$ApiBaseUrl = "http://192.168.200.61:18121/api/v1", + [string]$ClusterID = "cfc0743d-d960-49fb-9de8-96e063d5e4aa", + [string]$ActorUserID = "f67d943f-5397-4b3a-a229-695fe67ad700", + [string]$RequestedNodeName = "test-1", + [string]$DefaultNodeName = "test-2", + [string]$MatrixNodeName = "test-1", + [string]$ResultPath = "artifacts\c19z165-remote-workspace-real-adapter-not-approved-outcome-factory-terminal-complete-smoke-result.json" +) + +Set-StrictMode -Version Latest +$ErrorActionPreference = "Stop" + +$repoRoot = (Resolve-Path (Join-Path $PSScriptRoot "..\..")).ProviderPath +$sourceResultPath = "artifacts\c19z165-remote-workspace-real-adapter-not-approved-outcome-factory-terminal-complete-source-result.json" +$requiredTerminalFields = @("schema_version", "source_manifest_schema", "terminal_status", "terminal_marker", "factory_status", "archive_status", "manifest_status", "operator_status", "operator_action", "display_severity", "next_allowed_entrypoint", "attempt_allowed", "block_reason", "blocks_not_approved_extension", "enablement_status", "runtime_gate_state", "runtime_effect", "allows_process_start", "allows_payload_traffic", "guardrail_summary", "terminal_notes") +$requiredGuardrailFields = @("activation_blocked", "process_start_allowed", "health_probe_enabled", "payload_traffic", "allows_process_start", "allows_payload_traffic") +$requiredTerminalNotes = @("operator_stop_factory_terminal_complete", "not_approved_branch_remains_closed", "new_explicit_enablement_request_required", "real_runtime_gate_not_enabled", "process_start_disabled", "payload_forwarding_disabled") + +function Get-PropertyValue { + param([object]$Item, [string]$Name, [object]$Default = $null) + if ($null -eq $Item) { return $Default } + if ($Item -is [System.Collections.IDictionary]) { + if ($Item.Contains($Name)) { return $Item[$Name] } + return $Default + } + $property = $Item.PSObject.Properties[$Name] + if ($null -eq $property) { return $Default } + return $property.Value +} + +function Test-ObjectHasFields { + param([object]$Item, [string[]]$Fields) + if ($null -eq $Item) { return $false } + foreach ($field in $Fields) { + if ($Item -is [System.Collections.IDictionary]) { + if (-not $Item.Contains($field)) { return $false } + continue + } + if ($null -eq $Item.PSObject.Properties[$field]) { return $false } + } + return $true +} + +function Test-ArrayContainsAll { + param([object[]]$Actual, [string[]]$Expected) + foreach ($item in $Expected) { + if ($Actual -notcontains $item) { return $false } + } + return $true +} + +& powershell -ExecutionPolicy Bypass -File (Join-Path $PSScriptRoot "c19z164-remote-workspace-real-adapter-not-approved-outcome-operator-stop-status-final-archive-manifest-compatibility-smoke.ps1") ` + -ApiBaseUrl $ApiBaseUrl ` + -ClusterID $ClusterID ` + -ActorUserID $ActorUserID ` + -RequestedNodeName $RequestedNodeName ` + -DefaultNodeName $DefaultNodeName ` + -MatrixNodeName $MatrixNodeName ` + -ResultPath $sourceResultPath | Out-Null + +$sourceFile = Join-Path $repoRoot $sourceResultPath +$sourceResult = Get-Content -Raw -Path $sourceFile | ConvertFrom-Json +$manifest = Get-PropertyValue -Item $sourceResult -Name "not_approved_outcome_operator_stop_status_final_archive_manifest" -Default $null +$guardrails = Get-PropertyValue -Item $manifest -Name "guardrail_summary" -Default $null + +$terminal = [ordered]@{ + schema_version = "rap.remote_workspace_real_adapter_not_approved_outcome_factory_terminal_complete.v1" + source_manifest_schema = Get-PropertyValue -Item $manifest -Name "schema_version" -Default $null + terminal_status = "factory_terminal_complete_contract_only" + terminal_marker = "c19z165_real_adapter_not_approved_outcome_factory_terminal_complete" + factory_status = "complete_no_more_not_approved_layers_required" + archive_status = Get-PropertyValue -Item $manifest -Name "archive_status" -Default $null + manifest_status = Get-PropertyValue -Item $manifest -Name "manifest_status" -Default $null + operator_status = Get-PropertyValue -Item $manifest -Name "operator_status" -Default $null + operator_action = Get-PropertyValue -Item $manifest -Name "operator_action" -Default $null + display_severity = Get-PropertyValue -Item $manifest -Name "display_severity" -Default $null + next_allowed_entrypoint = Get-PropertyValue -Item $manifest -Name "next_allowed_entrypoint" -Default $null + attempt_allowed = $false + block_reason = Get-PropertyValue -Item $manifest -Name "block_reason" -Default $null + blocks_not_approved_extension = Get-PropertyValue -Item $manifest -Name "blocks_not_approved_extension" -Default $null + enablement_status = Get-PropertyValue -Item $manifest -Name "enablement_status" -Default $null + runtime_gate_state = Get-PropertyValue -Item $manifest -Name "runtime_gate_state" -Default $null + runtime_effect = Get-PropertyValue -Item $manifest -Name "runtime_effect" -Default $null + allows_process_start = $false + allows_payload_traffic = $false + guardrail_summary = $guardrails + terminal_notes = $requiredTerminalNotes +} + +$terminalFieldsCompatible = Test-ObjectHasFields -Item $terminal -Fields $requiredTerminalFields +$terminalValuesCompatible = ( + [string](Get-PropertyValue -Item $terminal -Name "schema_version" -Default "") -eq "rap.remote_workspace_real_adapter_not_approved_outcome_factory_terminal_complete.v1" -and + [string](Get-PropertyValue -Item $terminal -Name "source_manifest_schema" -Default "") -eq "rap.remote_workspace_real_adapter_not_approved_outcome_operator_stop_status_final_archive_manifest.v1" -and + [string](Get-PropertyValue -Item $terminal -Name "terminal_status" -Default "") -eq "factory_terminal_complete_contract_only" -and + [string](Get-PropertyValue -Item $terminal -Name "factory_status" -Default "") -eq "complete_no_more_not_approved_layers_required" -and + [string](Get-PropertyValue -Item $terminal -Name "archive_status" -Default "") -eq "operator_stop_status_final_archived_contract_only" -and + [string](Get-PropertyValue -Item $terminal -Name "manifest_status" -Default "") -eq "operator_stop_status_final_archive_manifest_complete" -and + [string](Get-PropertyValue -Item $terminal -Name "operator_status" -Default "") -eq "not_approved_branch_closed_new_request_required" -and + [string](Get-PropertyValue -Item $terminal -Name "operator_action" -Default "") -eq "keep_real_adapter_disabled_until_new_explicit_enablement_request" -and + [string](Get-PropertyValue -Item $terminal -Name "display_severity" -Default "") -eq "blocked" -and + [string](Get-PropertyValue -Item $terminal -Name "next_allowed_entrypoint" -Default "") -eq "new_explicit_enablement_request_only" -and + -not [bool](Get-PropertyValue -Item $terminal -Name "attempt_allowed" -Default $true) -and + [string](Get-PropertyValue -Item $terminal -Name "block_reason" -Default "") -eq "new_explicit_enablement_request_required" -and + [bool](Get-PropertyValue -Item $terminal -Name "blocks_not_approved_extension" -Default $false) -and + [string](Get-PropertyValue -Item $terminal -Name "enablement_status" -Default "") -eq "not_enabled" -and + [string](Get-PropertyValue -Item $terminal -Name "runtime_gate_state" -Default "") -eq "validated_contract_only_not_enabled" -and + [string](Get-PropertyValue -Item $terminal -Name "runtime_effect" -Default "") -eq "contract_only_no_runtime_enablement" -and + -not [bool](Get-PropertyValue -Item $terminal -Name "allows_process_start" -Default $true) -and + -not [bool](Get-PropertyValue -Item $terminal -Name "allows_payload_traffic" -Default $true) +) +$terminalNotesCompatible = Test-ArrayContainsAll -Actual @($terminal.terminal_notes) -Expected $requiredTerminalNotes +$guardrailsCompatible = ( + (Test-ObjectHasFields -Item $guardrails -Fields $requiredGuardrailFields) -and + [bool](Get-PropertyValue -Item $guardrails -Name "activation_blocked" -Default $false) -and + -not [bool](Get-PropertyValue -Item $guardrails -Name "process_start_allowed" -Default $true) -and + -not [bool](Get-PropertyValue -Item $guardrails -Name "health_probe_enabled" -Default $true) -and + [string](Get-PropertyValue -Item $guardrails -Name "payload_traffic" -Default "") -eq "none" -and + -not [bool](Get-PropertyValue -Item $guardrails -Name "allows_process_start" -Default $true) -and + -not [bool](Get-PropertyValue -Item $guardrails -Name "allows_payload_traffic" -Default $true) +) + +$checks = [ordered]@{ + source_smoke_passed = ([bool]$sourceResult.passed) + source_schema_expected = ([string]$sourceResult.schema_version -eq "c19z164.remote_workspace_real_adapter_not_approved_outcome_operator_stop_status_final_archive_manifest_compatibility_smoke.v1") + manifest_present = ($null -ne $manifest) + terminal_fields_compatible = $terminalFieldsCompatible + terminal_values_compatible = $terminalValuesCompatible + terminal_notes_compatible = $terminalNotesCompatible + guardrails_compatible = $guardrailsCompatible +} +$failed = @($checks.GetEnumerator() | Where-Object { -not $_.Value } | ForEach-Object { $_.Key }) + +$result = [ordered]@{ + schema_version = "c19z165.remote_workspace_real_adapter_not_approved_outcome_factory_terminal_complete_smoke.v1" + source_result_path = $sourceFile + cluster_id = $ClusterID + required_terminal_fields = $requiredTerminalFields + required_guardrail_fields = $requiredGuardrailFields + required_terminal_notes = $requiredTerminalNotes + not_approved_outcome_factory_terminal_complete = $terminal + checks = $checks + failed_checks = $failed + passed = ($failed.Count -eq 0) +} + +$fullResultPath = Join-Path $repoRoot $ResultPath +$resultDir = Split-Path -Parent $fullResultPath +if ($resultDir) { New-Item -ItemType Directory -Force -Path $resultDir | Out-Null } +$result | ConvertTo-Json -Depth 100 | Set-Content -Encoding UTF8 -Path $fullResultPath + +if (-not $result.passed) { + throw "C19Z165 remote workspace real-adapter not-approved outcome factory terminal complete smoke failed. Result: $fullResultPath Failed: $($failed -join ', ')" +} + +Write-Host "C19Z165 remote workspace real-adapter not-approved outcome factory terminal complete smoke passed. Result: $fullResultPath" +$result diff --git a/scripts/fabric/c19z166-remote-workspace-real-adapter-not-approved-outcome-factory-terminal-complete-compatibility-smoke.ps1 b/scripts/fabric/c19z166-remote-workspace-real-adapter-not-approved-outcome-factory-terminal-complete-compatibility-smoke.ps1 new file mode 100644 index 0000000..720e614 --- /dev/null +++ b/scripts/fabric/c19z166-remote-workspace-real-adapter-not-approved-outcome-factory-terminal-complete-compatibility-smoke.ps1 @@ -0,0 +1,134 @@ +param( + [string]$ApiBaseUrl = "http://192.168.200.61:18121/api/v1", + [string]$ClusterID = "cfc0743d-d960-49fb-9de8-96e063d5e4aa", + [string]$ActorUserID = "f67d943f-5397-4b3a-a229-695fe67ad700", + [string]$RequestedNodeName = "test-1", + [string]$DefaultNodeName = "test-2", + [string]$MatrixNodeName = "test-1", + [string]$ResultPath = "artifacts\c19z166-remote-workspace-real-adapter-not-approved-outcome-factory-terminal-complete-compatibility-smoke-result.json" +) + +Set-StrictMode -Version Latest +$ErrorActionPreference = "Stop" + +$repoRoot = (Resolve-Path (Join-Path $PSScriptRoot "..\..")).ProviderPath +$sourceResultPath = "artifacts\c19z166-remote-workspace-real-adapter-not-approved-outcome-factory-terminal-complete-source-result.json" +$requiredTerminalFields = @("schema_version", "source_manifest_schema", "terminal_status", "terminal_marker", "factory_status", "archive_status", "manifest_status", "operator_status", "operator_action", "display_severity", "next_allowed_entrypoint", "attempt_allowed", "block_reason", "blocks_not_approved_extension", "enablement_status", "runtime_gate_state", "runtime_effect", "allows_process_start", "allows_payload_traffic", "guardrail_summary", "terminal_notes") +$requiredGuardrailFields = @("activation_blocked", "process_start_allowed", "health_probe_enabled", "payload_traffic", "allows_process_start", "allows_payload_traffic") +$requiredTerminalNotes = @("operator_stop_factory_terminal_complete", "not_approved_branch_remains_closed", "new_explicit_enablement_request_required", "real_runtime_gate_not_enabled", "process_start_disabled", "payload_forwarding_disabled") + +function Get-PropertyValue { + param([object]$Item, [string]$Name, [object]$Default = $null) + if ($null -eq $Item) { return $Default } + if ($Item -is [System.Collections.IDictionary]) { + if ($Item.Contains($Name)) { return $Item[$Name] } + return $Default + } + $property = $Item.PSObject.Properties[$Name] + if ($null -eq $property) { return $Default } + return $property.Value +} + +function Test-ObjectHasFields { + param([object]$Item, [string[]]$Fields) + if ($null -eq $Item) { return $false } + foreach ($field in $Fields) { + if ($Item -is [System.Collections.IDictionary]) { + if (-not $Item.Contains($field)) { return $false } + continue + } + if ($null -eq $Item.PSObject.Properties[$field]) { return $false } + } + return $true +} + +function Test-ArrayContainsAll { + param([object[]]$Actual, [string[]]$Expected) + foreach ($item in $Expected) { + if ($Actual -notcontains $item) { return $false } + } + return $true +} + +& powershell -ExecutionPolicy Bypass -File (Join-Path $PSScriptRoot "c19z165-remote-workspace-real-adapter-not-approved-outcome-factory-terminal-complete-smoke.ps1") ` + -ApiBaseUrl $ApiBaseUrl ` + -ClusterID $ClusterID ` + -ActorUserID $ActorUserID ` + -RequestedNodeName $RequestedNodeName ` + -DefaultNodeName $DefaultNodeName ` + -MatrixNodeName $MatrixNodeName ` + -ResultPath $sourceResultPath | Out-Null + +$sourceFile = Join-Path $repoRoot $sourceResultPath +$sourceResult = Get-Content -Raw -Path $sourceFile | ConvertFrom-Json +$terminal = Get-PropertyValue -Item $sourceResult -Name "not_approved_outcome_factory_terminal_complete" -Default $null +$guardrails = Get-PropertyValue -Item $terminal -Name "guardrail_summary" -Default $null +$terminalNotes = @(Get-PropertyValue -Item $terminal -Name "terminal_notes" -Default @()) + +$terminalFieldsCompatible = Test-ObjectHasFields -Item $terminal -Fields $requiredTerminalFields +$terminalValuesCompatible = ( + [string](Get-PropertyValue -Item $terminal -Name "schema_version" -Default "") -eq "rap.remote_workspace_real_adapter_not_approved_outcome_factory_terminal_complete.v1" -and + [string](Get-PropertyValue -Item $terminal -Name "source_manifest_schema" -Default "") -eq "rap.remote_workspace_real_adapter_not_approved_outcome_operator_stop_status_final_archive_manifest.v1" -and + [string](Get-PropertyValue -Item $terminal -Name "terminal_status" -Default "") -eq "factory_terminal_complete_contract_only" -and + [string](Get-PropertyValue -Item $terminal -Name "factory_status" -Default "") -eq "complete_no_more_not_approved_layers_required" -and + [string](Get-PropertyValue -Item $terminal -Name "archive_status" -Default "") -eq "operator_stop_status_final_archived_contract_only" -and + [string](Get-PropertyValue -Item $terminal -Name "manifest_status" -Default "") -eq "operator_stop_status_final_archive_manifest_complete" -and + [string](Get-PropertyValue -Item $terminal -Name "operator_status" -Default "") -eq "not_approved_branch_closed_new_request_required" -and + [string](Get-PropertyValue -Item $terminal -Name "operator_action" -Default "") -eq "keep_real_adapter_disabled_until_new_explicit_enablement_request" -and + [string](Get-PropertyValue -Item $terminal -Name "display_severity" -Default "") -eq "blocked" -and + [string](Get-PropertyValue -Item $terminal -Name "next_allowed_entrypoint" -Default "") -eq "new_explicit_enablement_request_only" -and + -not [bool](Get-PropertyValue -Item $terminal -Name "attempt_allowed" -Default $true) -and + [string](Get-PropertyValue -Item $terminal -Name "block_reason" -Default "") -eq "new_explicit_enablement_request_required" -and + [bool](Get-PropertyValue -Item $terminal -Name "blocks_not_approved_extension" -Default $false) -and + [string](Get-PropertyValue -Item $terminal -Name "enablement_status" -Default "") -eq "not_enabled" -and + [string](Get-PropertyValue -Item $terminal -Name "runtime_gate_state" -Default "") -eq "validated_contract_only_not_enabled" -and + [string](Get-PropertyValue -Item $terminal -Name "runtime_effect" -Default "") -eq "contract_only_no_runtime_enablement" -and + -not [bool](Get-PropertyValue -Item $terminal -Name "allows_process_start" -Default $true) -and + -not [bool](Get-PropertyValue -Item $terminal -Name "allows_payload_traffic" -Default $true) +) +$terminalNotesCompatible = Test-ArrayContainsAll -Actual $terminalNotes -Expected $requiredTerminalNotes +$guardrailsCompatible = ( + (Test-ObjectHasFields -Item $guardrails -Fields $requiredGuardrailFields) -and + [bool](Get-PropertyValue -Item $guardrails -Name "activation_blocked" -Default $false) -and + -not [bool](Get-PropertyValue -Item $guardrails -Name "process_start_allowed" -Default $true) -and + -not [bool](Get-PropertyValue -Item $guardrails -Name "health_probe_enabled" -Default $true) -and + [string](Get-PropertyValue -Item $guardrails -Name "payload_traffic" -Default "") -eq "none" -and + -not [bool](Get-PropertyValue -Item $guardrails -Name "allows_process_start" -Default $true) -and + -not [bool](Get-PropertyValue -Item $guardrails -Name "allows_payload_traffic" -Default $true) +) + +$checks = [ordered]@{ + source_smoke_passed = ([bool]$sourceResult.passed) + source_schema_expected = ([string]$sourceResult.schema_version -eq "c19z165.remote_workspace_real_adapter_not_approved_outcome_factory_terminal_complete_smoke.v1") + terminal_present = ($null -ne $terminal) + terminal_fields_compatible = $terminalFieldsCompatible + terminal_values_compatible = $terminalValuesCompatible + terminal_notes_compatible = $terminalNotesCompatible + guardrails_compatible = $guardrailsCompatible +} +$failed = @($checks.GetEnumerator() | Where-Object { -not $_.Value } | ForEach-Object { $_.Key }) + +$result = [ordered]@{ + schema_version = "c19z166.remote_workspace_real_adapter_not_approved_outcome_factory_terminal_complete_compatibility_smoke.v1" + source_result_path = $sourceFile + cluster_id = $ClusterID + required_terminal_fields = $requiredTerminalFields + required_guardrail_fields = $requiredGuardrailFields + required_terminal_notes = $requiredTerminalNotes + not_approved_outcome_factory_terminal_complete = $terminal + checks = $checks + failed_checks = $failed + passed = ($failed.Count -eq 0) +} + +$fullResultPath = Join-Path $repoRoot $ResultPath +$resultDir = Split-Path -Parent $fullResultPath +if ($resultDir) { New-Item -ItemType Directory -Force -Path $resultDir | Out-Null } +$result | ConvertTo-Json -Depth 100 | Set-Content -Encoding UTF8 -Path $fullResultPath + +if (-not $result.passed) { + throw "C19Z166 remote workspace real-adapter not-approved outcome factory terminal complete compatibility smoke failed. Result: $fullResultPath Failed: $($failed -join ', ')" +} + +Write-Host "C19Z166 remote workspace real-adapter not-approved outcome factory terminal complete compatibility smoke passed. Result: $fullResultPath" +$result diff --git a/scripts/fabric/c19z17-remote-workspace-mailbox-preflight-contract-smoke.ps1 b/scripts/fabric/c19z17-remote-workspace-mailbox-preflight-contract-smoke.ps1 new file mode 100644 index 0000000..6174a75 --- /dev/null +++ b/scripts/fabric/c19z17-remote-workspace-mailbox-preflight-contract-smoke.ps1 @@ -0,0 +1,93 @@ +param( + [string]$ApiBaseUrl = "http://192.168.200.61:18121/api/v1", + [string]$ClusterID = "cfc0743d-d960-49fb-9de8-96e063d5e4aa", + [string]$ActorUserID = "f67d943f-5397-4b3a-a229-695fe67ad700", + [string]$EntryNodeName = "test-1", + [string]$ExitNodeName = "test-2", + [string]$EntryBaseUrl = "http://192.168.200.61:19131", + [string]$ResultPath = "artifacts\c19z17-remote-workspace-mailbox-preflight-contract-smoke-result.json" +) + +Set-StrictMode -Version Latest +$ErrorActionPreference = "Stop" + +$repoRoot = (Resolve-Path (Join-Path $PSScriptRoot "..\..")).ProviderPath +$sourceResultPath = "artifacts\c19z17-remote-workspace-mailbox-preflight-contract-source-result.json" + +function Get-PropertyValue { + param([object]$Item, [string]$Name, [object]$Default = $null) + if ($null -eq $Item) { return $Default } + $property = $Item.PSObject.Properties[$Name] + if ($null -eq $property) { return $Default } + return $property.Value +} + +function Test-ContractItem { + param([object]$Items, [string]$Want) + foreach ($item in @($Items)) { + if ([string]$item -eq $Want) { return $true } + } + return $false +} + +function Test-PreflightContract { + param([object]$Sink, [string]$AdapterSessionID) + if ($null -eq $Sink) { return $false } + $readiness = Get-PropertyValue -Item $Sink -Name "adapter_runtime_readiness" -Default $null + $rollup = Get-PropertyValue -Item $readiness -Name "last_preflight" -Default $null + if ($null -eq $rollup) { return $false } + $contract = Get-PropertyValue -Item $rollup -Name "diagnostics_contract" -Default @() + return ( + [string](Get-PropertyValue -Item $readiness -Name "adapter_session_id" -Default "") -eq $AdapterSessionID -and + [string](Get-PropertyValue -Item $rollup -Name "diagnostics_schema_version" -Default "") -eq "rap.remote_workspace_adapter_mailbox_preflight_diagnostics.v1" -and + (Test-ContractItem -Items $contract -Want "retained_window") -and + (Test-ContractItem -Items $contract -Want "remediation_checklist") -and + (Test-ContractItem -Items $contract -Want "attention") -and + (Test-ContractItem -Items $contract -Want "operator_counts") + ) +} + +$source = & powershell -ExecutionPolicy Bypass -File (Join-Path $PSScriptRoot "c19z16-remote-workspace-mailbox-preflight-attention-reason-coverage-smoke.ps1") ` + -ApiBaseUrl $ApiBaseUrl ` + -ClusterID $ClusterID ` + -ActorUserID $ActorUserID ` + -EntryNodeName $EntryNodeName ` + -ExitNodeName $ExitNodeName ` + -EntryBaseUrl $EntryBaseUrl ` + -ResultPath $sourceResultPath + +$sourceFile = Join-Path $repoRoot $sourceResultPath +$sourceResult = Get-Content -Raw -Path $sourceFile | ConvertFrom-Json +$adapterSessionID = [string](Get-PropertyValue -Item $sourceResult -Name "adapter_session_id" -Default "") +$observed = Get-PropertyValue -Item $sourceResult -Name "observed" -Default $null + +$checks = [ordered]@{ + source_smoke_passed = ([bool]$sourceResult.passed) + adapter_session_id_present = ($adapterSessionID -match "^rap-rw-adapter-session-[0-9a-f]{24}$") + workload_contract_visible = (Test-PreflightContract -Sink $observed.workload_sink -AdapterSessionID $adapterSessionID) + telemetry_contract_visible = (Test-PreflightContract -Sink $observed.telemetry_sink -AdapterSessionID $adapterSessionID) +} +$failed = @($checks.GetEnumerator() | Where-Object { -not $_.Value } | ForEach-Object { $_.Key }) + +$result = [ordered]@{ + schema_version = "c19z17.remote_workspace_mailbox_preflight_contract_smoke.v1" + source_result_path = $sourceFile + cluster_id = $ClusterID + adapter_session_id = $adapterSessionID + observed = $observed + checks = $checks + failed_checks = $failed + passed = ($failed.Count -eq 0) +} + +$fullResultPath = Join-Path $repoRoot $ResultPath +$resultDir = Split-Path -Parent $fullResultPath +if ($resultDir) { New-Item -ItemType Directory -Force -Path $resultDir | Out-Null } +$result | ConvertTo-Json -Depth 100 | Set-Content -Encoding UTF8 -Path $fullResultPath + +if (-not $result.passed) { + throw "C19Z17 remote workspace mailbox preflight contract smoke failed. Result: $fullResultPath Failed: $($failed -join ', ')" +} + +Write-Host "C19Z17 remote workspace mailbox preflight contract smoke passed. Result: $fullResultPath" +$result diff --git a/scripts/fabric/c19z18-remote-workspace-mailbox-preflight-feature-flags-smoke.ps1 b/scripts/fabric/c19z18-remote-workspace-mailbox-preflight-feature-flags-smoke.ps1 new file mode 100644 index 0000000..0ea608e --- /dev/null +++ b/scripts/fabric/c19z18-remote-workspace-mailbox-preflight-feature-flags-smoke.ps1 @@ -0,0 +1,90 @@ +param( + [string]$ApiBaseUrl = "http://192.168.200.61:18121/api/v1", + [string]$ClusterID = "cfc0743d-d960-49fb-9de8-96e063d5e4aa", + [string]$ActorUserID = "f67d943f-5397-4b3a-a229-695fe67ad700", + [string]$EntryNodeName = "test-1", + [string]$ExitNodeName = "test-2", + [string]$EntryBaseUrl = "http://192.168.200.61:19131", + [string]$ResultPath = "artifacts\c19z18-remote-workspace-mailbox-preflight-feature-flags-smoke-result.json" +) + +Set-StrictMode -Version Latest +$ErrorActionPreference = "Stop" + +$repoRoot = (Resolve-Path (Join-Path $PSScriptRoot "..\..")).ProviderPath +$sourceResultPath = "artifacts\c19z18-remote-workspace-mailbox-preflight-feature-flags-source-result.json" + +function Get-PropertyValue { + param([object]$Item, [string]$Name, [object]$Default = $null) + if ($null -eq $Item) { return $Default } + $property = $Item.PSObject.Properties[$Name] + if ($null -eq $property) { return $Default } + return $property.Value +} + +function Test-FeatureFlag { + param([object]$Features, [string]$Name) + return ([bool](Get-PropertyValue -Item $Features -Name $Name -Default $false)) +} + +function Test-PreflightFeatureFlags { + param([object]$Sink, [string]$AdapterSessionID) + if ($null -eq $Sink) { return $false } + $readiness = Get-PropertyValue -Item $Sink -Name "adapter_runtime_readiness" -Default $null + $rollup = Get-PropertyValue -Item $readiness -Name "last_preflight" -Default $null + if ($null -eq $rollup) { return $false } + $features = Get-PropertyValue -Item $rollup -Name "diagnostics_features" -Default $null + return ( + [string](Get-PropertyValue -Item $readiness -Name "adapter_session_id" -Default "") -eq $AdapterSessionID -and + [string](Get-PropertyValue -Item $rollup -Name "diagnostics_schema_version" -Default "") -eq "rap.remote_workspace_adapter_mailbox_preflight_diagnostics.v1" -and + (Test-FeatureFlag -Features $features -Name "retained_window") -and + (Test-FeatureFlag -Features $features -Name "remediation_checklist") -and + (Test-FeatureFlag -Features $features -Name "attention") -and + (Test-FeatureFlag -Features $features -Name "operator_counts") + ) +} + +$source = & powershell -ExecutionPolicy Bypass -File (Join-Path $PSScriptRoot "c19z17-remote-workspace-mailbox-preflight-contract-smoke.ps1") ` + -ApiBaseUrl $ApiBaseUrl ` + -ClusterID $ClusterID ` + -ActorUserID $ActorUserID ` + -EntryNodeName $EntryNodeName ` + -ExitNodeName $ExitNodeName ` + -EntryBaseUrl $EntryBaseUrl ` + -ResultPath $sourceResultPath + +$sourceFile = Join-Path $repoRoot $sourceResultPath +$sourceResult = Get-Content -Raw -Path $sourceFile | ConvertFrom-Json +$adapterSessionID = [string](Get-PropertyValue -Item $sourceResult -Name "adapter_session_id" -Default "") +$observed = Get-PropertyValue -Item $sourceResult -Name "observed" -Default $null + +$checks = [ordered]@{ + source_smoke_passed = ([bool]$sourceResult.passed) + adapter_session_id_present = ($adapterSessionID -match "^rap-rw-adapter-session-[0-9a-f]{24}$") + workload_feature_flags_visible = (Test-PreflightFeatureFlags -Sink $observed.workload_sink -AdapterSessionID $adapterSessionID) + telemetry_feature_flags_visible = (Test-PreflightFeatureFlags -Sink $observed.telemetry_sink -AdapterSessionID $adapterSessionID) +} +$failed = @($checks.GetEnumerator() | Where-Object { -not $_.Value } | ForEach-Object { $_.Key }) + +$result = [ordered]@{ + schema_version = "c19z18.remote_workspace_mailbox_preflight_feature_flags_smoke.v1" + source_result_path = $sourceFile + cluster_id = $ClusterID + adapter_session_id = $adapterSessionID + observed = $observed + checks = $checks + failed_checks = $failed + passed = ($failed.Count -eq 0) +} + +$fullResultPath = Join-Path $repoRoot $ResultPath +$resultDir = Split-Path -Parent $fullResultPath +if ($resultDir) { New-Item -ItemType Directory -Force -Path $resultDir | Out-Null } +$result | ConvertTo-Json -Depth 100 | Set-Content -Encoding UTF8 -Path $fullResultPath + +if (-not $result.passed) { + throw "C19Z18 remote workspace mailbox preflight feature flags smoke failed. Result: $fullResultPath Failed: $($failed -join ', ')" +} + +Write-Host "C19Z18 remote workspace mailbox preflight feature flags smoke passed. Result: $fullResultPath" +$result diff --git a/scripts/fabric/c19z19-remote-workspace-mailbox-preflight-contract-compatibility-smoke.ps1 b/scripts/fabric/c19z19-remote-workspace-mailbox-preflight-contract-compatibility-smoke.ps1 new file mode 100644 index 0000000..0dd949d --- /dev/null +++ b/scripts/fabric/c19z19-remote-workspace-mailbox-preflight-contract-compatibility-smoke.ps1 @@ -0,0 +1,100 @@ +param( + [string]$ApiBaseUrl = "http://192.168.200.61:18121/api/v1", + [string]$ClusterID = "cfc0743d-d960-49fb-9de8-96e063d5e4aa", + [string]$ActorUserID = "f67d943f-5397-4b3a-a229-695fe67ad700", + [string]$EntryNodeName = "test-1", + [string]$ExitNodeName = "test-2", + [string]$EntryBaseUrl = "http://192.168.200.61:19131", + [string]$ResultPath = "artifacts\c19z19-remote-workspace-mailbox-preflight-contract-compatibility-smoke-result.json" +) + +Set-StrictMode -Version Latest +$ErrorActionPreference = "Stop" + +$repoRoot = (Resolve-Path (Join-Path $PSScriptRoot "..\..")).ProviderPath +$sourceResultPath = "artifacts\c19z19-remote-workspace-mailbox-preflight-contract-compatibility-source-result.json" +$requiredFeatures = @("retained_window", "remediation_checklist", "attention", "operator_counts") + +function Get-PropertyValue { + param([object]$Item, [string]$Name, [object]$Default = $null) + if ($null -eq $Item) { return $Default } + $property = $Item.PSObject.Properties[$Name] + if ($null -eq $property) { return $Default } + return $property.Value +} + +function Test-ContractItem { + param([object]$Items, [string]$Want) + foreach ($item in @($Items)) { + if ([string]$item -eq $Want) { return $true } + } + return $false +} + +function Test-FeatureFlag { + param([object]$Features, [string]$Name) + return ([bool](Get-PropertyValue -Item $Features -Name $Name -Default $false)) +} + +function Test-PreflightCompatibility { + param([object]$Sink, [string]$AdapterSessionID) + if ($null -eq $Sink) { return $false } + $readiness = Get-PropertyValue -Item $Sink -Name "adapter_runtime_readiness" -Default $null + $rollup = Get-PropertyValue -Item $readiness -Name "last_preflight" -Default $null + if ($null -eq $rollup) { return $false } + $contract = Get-PropertyValue -Item $rollup -Name "diagnostics_contract" -Default @() + $features = Get-PropertyValue -Item $rollup -Name "diagnostics_features" -Default $null + if ([string](Get-PropertyValue -Item $readiness -Name "adapter_session_id" -Default "") -ne $AdapterSessionID) { return $false } + if ([string](Get-PropertyValue -Item $rollup -Name "diagnostics_schema_version" -Default "") -ne "rap.remote_workspace_adapter_mailbox_preflight_diagnostics.v1") { return $false } + foreach ($feature in $requiredFeatures) { + if (-not (Test-ContractItem -Items $contract -Want $feature)) { return $false } + if (-not (Test-FeatureFlag -Features $features -Name $feature)) { return $false } + } + return $true +} + +$source = & powershell -ExecutionPolicy Bypass -File (Join-Path $PSScriptRoot "c19z18-remote-workspace-mailbox-preflight-feature-flags-smoke.ps1") ` + -ApiBaseUrl $ApiBaseUrl ` + -ClusterID $ClusterID ` + -ActorUserID $ActorUserID ` + -EntryNodeName $EntryNodeName ` + -ExitNodeName $ExitNodeName ` + -EntryBaseUrl $EntryBaseUrl ` + -ResultPath $sourceResultPath + +$sourceFile = Join-Path $repoRoot $sourceResultPath +$sourceResult = Get-Content -Raw -Path $sourceFile | ConvertFrom-Json +$adapterSessionID = [string](Get-PropertyValue -Item $sourceResult -Name "adapter_session_id" -Default "") +$observed = Get-PropertyValue -Item $sourceResult -Name "observed" -Default $null + +$checks = [ordered]@{ + source_smoke_passed = ([bool]$sourceResult.passed) + adapter_session_id_present = ($adapterSessionID -match "^rap-rw-adapter-session-[0-9a-f]{24}$") + workload_contract_feature_compatibility = (Test-PreflightCompatibility -Sink $observed.workload_sink -AdapterSessionID $adapterSessionID) + telemetry_contract_feature_compatibility = (Test-PreflightCompatibility -Sink $observed.telemetry_sink -AdapterSessionID $adapterSessionID) +} +$failed = @($checks.GetEnumerator() | Where-Object { -not $_.Value } | ForEach-Object { $_.Key }) + +$result = [ordered]@{ + schema_version = "c19z19.remote_workspace_mailbox_preflight_contract_compatibility_smoke.v1" + source_result_path = $sourceFile + cluster_id = $ClusterID + adapter_session_id = $adapterSessionID + required_features = $requiredFeatures + observed = $observed + checks = $checks + failed_checks = $failed + passed = ($failed.Count -eq 0) +} + +$fullResultPath = Join-Path $repoRoot $ResultPath +$resultDir = Split-Path -Parent $fullResultPath +if ($resultDir) { New-Item -ItemType Directory -Force -Path $resultDir | Out-Null } +$result | ConvertTo-Json -Depth 100 | Set-Content -Encoding UTF8 -Path $fullResultPath + +if (-not $result.passed) { + throw "C19Z19 remote workspace mailbox preflight contract compatibility smoke failed. Result: $fullResultPath Failed: $($failed -join ', ')" +} + +Write-Host "C19Z19 remote workspace mailbox preflight contract compatibility smoke passed. Result: $fullResultPath" +$result diff --git a/scripts/fabric/c19z2-remote-workspace-mailbox-preflight-telemetry-smoke.ps1 b/scripts/fabric/c19z2-remote-workspace-mailbox-preflight-telemetry-smoke.ps1 new file mode 100644 index 0000000..5706bc2 --- /dev/null +++ b/scripts/fabric/c19z2-remote-workspace-mailbox-preflight-telemetry-smoke.ps1 @@ -0,0 +1,171 @@ +param( + [string]$ApiBaseUrl = "http://192.168.200.61:18121/api/v1", + [string]$ClusterID = "cfc0743d-d960-49fb-9de8-96e063d5e4aa", + [string]$ActorUserID = "f67d943f-5397-4b3a-a229-695fe67ad700", + [string]$EntryNodeName = "test-1", + [string]$ExitNodeName = "test-2", + [string]$EntryBaseUrl = "http://192.168.200.61:19131", + [string]$ResultPath = "artifacts\c19z2-remote-workspace-mailbox-preflight-telemetry-smoke-result.json" +) + +Set-StrictMode -Version Latest +$ErrorActionPreference = "Stop" + +$repoRoot = (Resolve-Path (Join-Path $PSScriptRoot "..\..")).ProviderPath +$sourceResultPath = "artifacts\c19z2-remote-workspace-mailbox-preflight-source-result.json" +$consumerID = "rdp-worker-probe" + +function Invoke-Api { + param([string]$Method, [string]$Path, [object]$Body = $null) + $uri = "$ApiBaseUrl$Path" + if ($null -eq $Body) { return Invoke-RestMethod -Method $Method -Uri $uri -TimeoutSec 30 } + return Invoke-RestMethod -Method $Method -Uri $uri -ContentType "application/json" -Body ($Body | ConvertTo-Json -Depth 80) -TimeoutSec 30 +} + +function Get-PropertyValue { + param([object]$Item, [string]$Name, [object]$Default = $null) + if ($null -eq $Item) { return $Default } + $property = $Item.PSObject.Properties[$Name] + if ($null -eq $property) { return $Default } + return $property.Value +} + +function Get-NodeByName { + param([string]$Name) + $nodes = (Invoke-Api -Method GET -Path "/clusters/$ClusterID/nodes?actor_user_id=$ActorUserID").nodes + $node = @($nodes | Where-Object { $_.name -eq $Name }) | Select-Object -First 1 + if ($null -eq $node) { throw "Node '$Name' was not found in cluster $ClusterID" } + return $node +} + +function Get-RemoteWorkspaceSinkReports { + param([string]$NodeID) + $workloadStatus = $null + $workloadSink = $null + $telemetry = $null + $telemetrySink = $null + $statuses = (Invoke-Api -Method GET -Path "/clusters/$ClusterID/nodes/$NodeID/workloads/status?actor_user_id=$ActorUserID").workload_statuses + $workloadStatus = @($statuses | Where-Object { $_.service_type -eq "rdp-worker" } | Select-Object -First 1) + if ($null -ne $workloadStatus) { + $workloadSink = Get-PropertyValue -Item $workloadStatus.status_payload -Name "remote_workspace_adapter_sink" -Default $null + } + $telemetryItems = @((Invoke-Api -Method GET -Path "/clusters/$ClusterID/nodes/$NodeID/telemetry?actor_user_id=$ActorUserID&limit=10").telemetry) + $telemetry = $telemetryItems | Select-Object -First 1 + if ($null -ne $telemetry) { + $telemetryPayload = Get-PropertyValue -Item $telemetry -Name "payload" -Default $null + $telemetrySink = Get-PropertyValue -Item $telemetryPayload -Name "remote_workspace_adapter_sink_report" -Default $null + } + return [ordered]@{ + workload_status = $workloadStatus + workload_sink = $workloadSink + telemetry = $telemetry + telemetry_sink = $telemetrySink + } +} + +function Test-PreflightTelemetry { + param( + [object]$Sink, + [object]$BaselineSink, + [string]$AdapterSessionID, + [int64]$ResumeSequence, + [int]$AvailableCount, + [int]$ReturnedCount, + [int]$SkippedCount + ) + if ($null -eq $Sink) { return $false } + $baselineTotal = [int64](Get-PropertyValue -Item $BaselineSink -Name "mailbox_preflight_total" -Default 0) + $baselineAck = [int64](Get-PropertyValue -Item $BaselineSink -Name "mailbox_preflight_ack_total" -Default 0) + $baselineCheckpoint = [int64](Get-PropertyValue -Item $BaselineSink -Name "mailbox_preflight_checkpoint_total" -Default 0) + $readiness = Get-PropertyValue -Item $Sink -Name "adapter_runtime_readiness" -Default $null + return ( + [string](Get-PropertyValue -Item $Sink -Name "schema_version" -Default "") -eq "rap.remote_workspace_adapter_sink_report.v1" -and + [int64](Get-PropertyValue -Item $Sink -Name "mailbox_preflight_total" -Default 0) -ge ($baselineTotal + 2) -and + [int64](Get-PropertyValue -Item $Sink -Name "mailbox_preflight_ack_total" -Default 0) -ge ($baselineAck + 1) -and + [int64](Get-PropertyValue -Item $Sink -Name "mailbox_preflight_checkpoint_total" -Default 0) -ge ($baselineCheckpoint + 1) -and + [string](Get-PropertyValue -Item $Sink -Name "last_mailbox_preflight_adapter_session_id" -Default "") -eq $AdapterSessionID -and + [string](Get-PropertyValue -Item $Sink -Name "last_mailbox_preflight_consumer_id" -Default "") -eq $consumerID -and + [string](Get-PropertyValue -Item $Sink -Name "last_mailbox_preflight_resume_from" -Default "") -eq "checkpoint" -and + [int64](Get-PropertyValue -Item $Sink -Name "last_mailbox_preflight_resume_sequence" -Default -1) -eq $ResumeSequence -and + [int](Get-PropertyValue -Item $Sink -Name "last_mailbox_preflight_available_count" -Default -1) -eq $AvailableCount -and + [int](Get-PropertyValue -Item $Sink -Name "last_mailbox_preflight_returned_count" -Default -1) -eq $ReturnedCount -and + [int](Get-PropertyValue -Item $Sink -Name "last_mailbox_preflight_skipped_count" -Default -1) -eq $SkippedCount -and + $null -ne $readiness -and + [int64](Get-PropertyValue -Item $readiness -Name "mailbox_preflight_total" -Default 0) -ge 2 + ) +} + +$entryNode = Get-NodeByName -Name $EntryNodeName +$baseline = Get-RemoteWorkspaceSinkReports -NodeID $entryNode.id +$baselineWorkloadSink = $baseline.workload_sink +$baselineTelemetrySink = $baseline.telemetry_sink + +$source = & powershell -ExecutionPolicy Bypass -File (Join-Path $PSScriptRoot "c19z1-remote-workspace-mailbox-preflight-smoke.ps1") ` + -ApiBaseUrl $ApiBaseUrl ` + -ClusterID $ClusterID ` + -ActorUserID $ActorUserID ` + -EntryNodeName $EntryNodeName ` + -ExitNodeName $ExitNodeName ` + -EntryBaseUrl $EntryBaseUrl ` + -ResultPath $sourceResultPath + +$sourceFile = Join-Path $repoRoot $sourceResultPath +$sourceResult = Get-Content -Raw -Path $sourceFile | ConvertFrom-Json +$adapterSessionID = [string](Get-PropertyValue -Item $sourceResult -Name "adapter_session_id" -Default "") +$checkpoint = Get-PropertyValue -Item $sourceResult -Name "preflight_checkpoint" -Default $null +$checkpointJson = Get-PropertyValue -Item $checkpoint -Name "json" -Default $null +$resumeSequence = [int64](Get-PropertyValue -Item $checkpointJson -Name "resume_sequence" -Default 0) +$availableCount = [int](Get-PropertyValue -Item $checkpointJson -Name "expected_available_count" -Default -1) +$returnedCount = [int](Get-PropertyValue -Item $checkpointJson -Name "expected_returned_count" -Default -1) +$skippedCount = [int](Get-PropertyValue -Item $checkpointJson -Name "expected_skipped_count" -Default -1) + +$observed = $null +$deadline = (Get-Date).AddSeconds(90) +while ((Get-Date) -lt $deadline) { + Start-Sleep -Seconds 5 + $observed = Get-RemoteWorkspaceSinkReports -NodeID $entryNode.id + if ( + (Test-PreflightTelemetry -Sink $observed.workload_sink -BaselineSink $baselineWorkloadSink -AdapterSessionID $adapterSessionID -ResumeSequence $resumeSequence -AvailableCount $availableCount -ReturnedCount $returnedCount -SkippedCount $skippedCount) -and + (Test-PreflightTelemetry -Sink $observed.telemetry_sink -BaselineSink $baselineTelemetrySink -AdapterSessionID $adapterSessionID -ResumeSequence $resumeSequence -AvailableCount $availableCount -ReturnedCount $returnedCount -SkippedCount $skippedCount) + ) { + break + } +} +if ($null -eq $observed) { + $observed = Get-RemoteWorkspaceSinkReports -NodeID $entryNode.id +} + +$checks = [ordered]@{ + source_smoke_passed = ([bool]$sourceResult.passed) + adapter_session_id_present = ($adapterSessionID -match "^rap-rw-adapter-session-[0-9a-f]{24}$") + checkpoint_preflight_shape_visible = ([string](Get-PropertyValue -Item $checkpointJson -Name "resume_from" -Default "") -eq "checkpoint" -and $resumeSequence -gt 0 -and $availableCount -eq 0 -and $returnedCount -eq 0 -and $skippedCount -eq 3) + workload_preflight_telemetry_visible = (Test-PreflightTelemetry -Sink $observed.workload_sink -BaselineSink $baselineWorkloadSink -AdapterSessionID $adapterSessionID -ResumeSequence $resumeSequence -AvailableCount $availableCount -ReturnedCount $returnedCount -SkippedCount $skippedCount) + telemetry_preflight_telemetry_visible = (Test-PreflightTelemetry -Sink $observed.telemetry_sink -BaselineSink $baselineTelemetrySink -AdapterSessionID $adapterSessionID -ResumeSequence $resumeSequence -AvailableCount $availableCount -ReturnedCount $returnedCount -SkippedCount $skippedCount) +} +$failed = @($checks.GetEnumerator() | Where-Object { -not $_.Value } | ForEach-Object { $_.Key }) + +$result = [ordered]@{ + schema_version = "c19z2.remote_workspace_mailbox_preflight_telemetry_smoke.v1" + source_result_path = $sourceFile + cluster_id = $ClusterID + entry_node = [ordered]@{ id = $entryNode.id; name = $entryNode.name } + adapter_session_id = $adapterSessionID + baseline = $baseline + source = $sourceResult + observed = $observed + checks = $checks + failed_checks = $failed + passed = ($failed.Count -eq 0) +} + +$fullResultPath = Join-Path $repoRoot $ResultPath +$resultDir = Split-Path -Parent $fullResultPath +if ($resultDir) { New-Item -ItemType Directory -Force -Path $resultDir | Out-Null } +$result | ConvertTo-Json -Depth 100 | Set-Content -Encoding UTF8 -Path $fullResultPath + +if (-not $result.passed) { + throw "C19Z2 remote workspace mailbox preflight telemetry smoke failed. Result: $fullResultPath Failed: $($failed -join ', ')" +} + +Write-Host "C19Z2 remote workspace mailbox preflight telemetry smoke passed. Result: $fullResultPath" +$result diff --git a/scripts/fabric/c19z20-remote-workspace-mailbox-preflight-absence-smoke.ps1 b/scripts/fabric/c19z20-remote-workspace-mailbox-preflight-absence-smoke.ps1 new file mode 100644 index 0000000..1aefa71 --- /dev/null +++ b/scripts/fabric/c19z20-remote-workspace-mailbox-preflight-absence-smoke.ps1 @@ -0,0 +1,161 @@ +param( + [string]$ApiBaseUrl = "http://192.168.200.61:18121/api/v1", + [string]$ClusterID = "cfc0743d-d960-49fb-9de8-96e063d5e4aa", + [string]$ActorUserID = "f67d943f-5397-4b3a-a229-695fe67ad700", + [string]$EntryNodeName = "test-1", + [string]$ExitNodeName = "test-2", + [string]$EntryBaseUrl = "http://192.168.200.61:19131", + [string]$ResultPath = "artifacts\c19z20-remote-workspace-mailbox-preflight-absence-smoke-result.json" +) + +Set-StrictMode -Version Latest +$ErrorActionPreference = "Stop" + +$repoRoot = (Resolve-Path (Join-Path $PSScriptRoot "..\..")).ProviderPath +$sourceResultPath = "artifacts\c19z20-remote-workspace-mailbox-preflight-absence-source-result.json" +$adapterSessionID = "" + +function Invoke-Api { + param([string]$Method, [string]$Path, [object]$Body = $null) + $uri = "$ApiBaseUrl$Path" + if ($null -eq $Body) { return Invoke-RestMethod -Method $Method -Uri $uri -TimeoutSec 30 } + return Invoke-RestMethod -Method $Method -Uri $uri -ContentType "application/json" -Body ($Body | ConvertTo-Json -Depth 80) -TimeoutSec 30 +} + +function Get-PropertyValue { + param([object]$Item, [string]$Name, [object]$Default = $null) + if ($null -eq $Item) { return $Default } + $property = $Item.PSObject.Properties[$Name] + if ($null -eq $property) { return $Default } + return $property.Value +} + +function Get-NodeByName { + param([string]$Name) + $nodes = (Invoke-Api -Method GET -Path "/clusters/$ClusterID/nodes?actor_user_id=$ActorUserID").nodes + $node = @($nodes | Where-Object { $_.name -eq $Name }) | Select-Object -First 1 + if ($null -eq $node) { throw "Node '$Name' was not found in cluster $ClusterID" } + return $node +} + +function Get-RemoteWorkspaceSinkReports { + param([string]$NodeID) + $statuses = (Invoke-Api -Method GET -Path "/clusters/$ClusterID/nodes/$NodeID/workloads/status?actor_user_id=$ActorUserID").workload_statuses + $workloadStatus = @($statuses | Where-Object { $_.service_type -eq "rdp-worker" } | Select-Object -First 1) + $workloadSink = $null + if ($null -ne $workloadStatus) { + $workloadSink = Get-PropertyValue -Item $workloadStatus.status_payload -Name "remote_workspace_adapter_sink" -Default $null + } + $telemetryItems = @((Invoke-Api -Method GET -Path "/clusters/$ClusterID/nodes/$NodeID/telemetry?actor_user_id=$ActorUserID&limit=10").telemetry) + $telemetry = $telemetryItems | Select-Object -First 1 + $telemetrySink = $null + if ($null -ne $telemetry) { + $telemetryPayload = Get-PropertyValue -Item $telemetry -Name "payload" -Default $null + $telemetrySink = Get-PropertyValue -Item $telemetryPayload -Name "remote_workspace_adapter_sink_report" -Default $null + } + return [ordered]@{ + workload_status = $workloadStatus + workload_sink = $workloadSink + telemetry = $telemetry + telemetry_sink = $telemetrySink + } +} + +function Invoke-Control { + param([string]$SessionID) + if ([string]::IsNullOrWhiteSpace($SessionID)) { return $null } + $url = "$EntryBaseUrl/mesh/v1/remote-workspace/adapter-sessions/$SessionID/control" + $body = @{ action = "close"; reason = "c19z20 mailbox preflight absence close" } | ConvertTo-Json -Compress + return Invoke-RestMethod -Method POST -Uri $url -ContentType "application/json" -Body $body -TimeoutSec 30 +} + +function Test-NoPreflightReadiness { + param([object]$Sink, [string]$AdapterSessionID) + if ($null -eq $Sink) { return $false } + $readiness = Get-PropertyValue -Item $Sink -Name "adapter_runtime_readiness" -Default $null + if ($null -eq $readiness) { return $false } + $rollup = Get-PropertyValue -Item $readiness -Name "last_preflight" -Default $null + return ( + [string](Get-PropertyValue -Item $readiness -Name "adapter_session_id" -Default "") -eq $AdapterSessionID -and + [int64](Get-PropertyValue -Item $readiness -Name "mailbox_preflight_total" -Default -1) -eq 0 -and + [string](Get-PropertyValue -Item $readiness -Name "preflight_attention_status" -Default "") -eq "unknown" -and + [string](Get-PropertyValue -Item $readiness -Name "preflight_attention_reason" -Default "") -eq "no_preflight_observed" -and + $null -eq $rollup -and + [string](Get-PropertyValue -Item $readiness -Name "last_preflight_diagnostic_state" -Default "") -eq "" -and + [string](Get-PropertyValue -Item $readiness -Name "last_preflight_recommended_action" -Default "") -eq "" + ) +} + +try { + $entryNode = Get-NodeByName -Name $EntryNodeName + + $source = & powershell -ExecutionPolicy Bypass -File (Join-Path $PSScriptRoot "c19x-remote-workspace-mailbox-consumer-resume-smoke.ps1") ` + -ApiBaseUrl $ApiBaseUrl ` + -ClusterID $ClusterID ` + -ActorUserID $ActorUserID ` + -EntryNodeName $EntryNodeName ` + -ExitNodeName $ExitNodeName ` + -EntryBaseUrl $EntryBaseUrl ` + -ResultPath $sourceResultPath ` + -SkipClose + + $sourceFile = Join-Path $repoRoot $sourceResultPath + $sourceResult = Get-Content -Raw -Path $sourceFile | ConvertFrom-Json + $adapterSessionID = [string](Get-PropertyValue -Item $sourceResult -Name "adapter_session_id" -Default "") + + $observed = $null + $deadline = (Get-Date).AddSeconds(90) + while ((Get-Date) -lt $deadline) { + Start-Sleep -Seconds 5 + $observed = Get-RemoteWorkspaceSinkReports -NodeID $entryNode.id + if ( + (Test-NoPreflightReadiness -Sink $observed.workload_sink -AdapterSessionID $adapterSessionID) -and + (Test-NoPreflightReadiness -Sink $observed.telemetry_sink -AdapterSessionID $adapterSessionID) + ) { + break + } + } + if ($null -eq $observed) { + $observed = Get-RemoteWorkspaceSinkReports -NodeID $entryNode.id + } + + $control = Invoke-Control -SessionID $adapterSessionID + $checks = [ordered]@{ + source_smoke_passed = ([bool]$sourceResult.passed) + adapter_session_id_present = ($adapterSessionID -match "^rap-rw-adapter-session-[0-9a-f]{24}$") + workload_no_preflight_readiness_visible = (Test-NoPreflightReadiness -Sink $observed.workload_sink -AdapterSessionID $adapterSessionID) + telemetry_no_preflight_readiness_visible = (Test-NoPreflightReadiness -Sink $observed.telemetry_sink -AdapterSessionID $adapterSessionID) + close_accepted = ([bool]$control.accepted -and [string]$control.session_state -eq "closed") + } + $failed = @($checks.GetEnumerator() | Where-Object { -not $_.Value } | ForEach-Object { $_.Key }) + + $result = [ordered]@{ + schema_version = "c19z20.remote_workspace_mailbox_preflight_absence_smoke.v1" + source_result_path = $sourceFile + cluster_id = $ClusterID + entry_node = [ordered]@{ id = $entryNode.id; name = $entryNode.name } + adapter_session_id = $adapterSessionID + source = $sourceResult + observed = $observed + control = $control + checks = $checks + failed_checks = $failed + passed = ($failed.Count -eq 0) + } +} finally { + if ($adapterSessionID) { + try { [void](Invoke-Control -SessionID $adapterSessionID) } catch {} + } +} + +$fullResultPath = Join-Path $repoRoot $ResultPath +$resultDir = Split-Path -Parent $fullResultPath +if ($resultDir) { New-Item -ItemType Directory -Force -Path $resultDir | Out-Null } +$result | ConvertTo-Json -Depth 100 | Set-Content -Encoding UTF8 -Path $fullResultPath + +if (-not $result.passed) { + throw "C19Z20 remote workspace mailbox preflight absence smoke failed. Result: $fullResultPath Failed: $($failed -join ', ')" +} + +Write-Host "C19Z20 remote workspace mailbox preflight absence smoke passed. Result: $fullResultPath" +$result diff --git a/scripts/fabric/c19z21-remote-workspace-no-active-session-readiness-smoke.ps1 b/scripts/fabric/c19z21-remote-workspace-no-active-session-readiness-smoke.ps1 new file mode 100644 index 0000000..a830675 --- /dev/null +++ b/scripts/fabric/c19z21-remote-workspace-no-active-session-readiness-smoke.ps1 @@ -0,0 +1,164 @@ +param( + [string]$ApiBaseUrl = "http://192.168.200.61:18121/api/v1", + [string]$ClusterID = "cfc0743d-d960-49fb-9de8-96e063d5e4aa", + [string]$ActorUserID = "f67d943f-5397-4b3a-a229-695fe67ad700", + [string]$EntryNodeName = "test-1", + [string]$ExitNodeName = "test-2", + [string]$EntryBaseUrl = "http://192.168.200.61:19131", + [string]$ResultPath = "artifacts\c19z21-remote-workspace-no-active-session-readiness-smoke-result.json" +) + +Set-StrictMode -Version Latest +$ErrorActionPreference = "Stop" + +$repoRoot = (Resolve-Path (Join-Path $PSScriptRoot "..\..")).ProviderPath +$sourceResultPath = "artifacts\c19z21-remote-workspace-no-active-session-readiness-source-result.json" +$adapterSessionID = "" + +function Invoke-Api { + param([string]$Method, [string]$Path, [object]$Body = $null) + $uri = "$ApiBaseUrl$Path" + if ($null -eq $Body) { return Invoke-RestMethod -Method $Method -Uri $uri -TimeoutSec 30 } + return Invoke-RestMethod -Method $Method -Uri $uri -ContentType "application/json" -Body ($Body | ConvertTo-Json -Depth 80) -TimeoutSec 30 +} + +function Get-PropertyValue { + param([object]$Item, [string]$Name, [object]$Default = $null) + if ($null -eq $Item) { return $Default } + $property = $Item.PSObject.Properties[$Name] + if ($null -eq $property) { return $Default } + return $property.Value +} + +function Get-NodeByName { + param([string]$Name) + $nodes = (Invoke-Api -Method GET -Path "/clusters/$ClusterID/nodes?actor_user_id=$ActorUserID").nodes + $node = @($nodes | Where-Object { $_.name -eq $Name }) | Select-Object -First 1 + if ($null -eq $node) { throw "Node '$Name' was not found in cluster $ClusterID" } + return $node +} + +function Get-RemoteWorkspaceSinkReports { + param([string]$NodeID) + $statuses = (Invoke-Api -Method GET -Path "/clusters/$ClusterID/nodes/$NodeID/workloads/status?actor_user_id=$ActorUserID").workload_statuses + $workloadStatus = @($statuses | Where-Object { $_.service_type -eq "rdp-worker" } | Select-Object -First 1) + $workloadSink = $null + if ($null -ne $workloadStatus) { + $workloadSink = Get-PropertyValue -Item $workloadStatus.status_payload -Name "remote_workspace_adapter_sink" -Default $null + } + $telemetryItems = @((Invoke-Api -Method GET -Path "/clusters/$ClusterID/nodes/$NodeID/telemetry?actor_user_id=$ActorUserID&limit=10").telemetry) + $telemetry = $telemetryItems | Select-Object -First 1 + $telemetrySink = $null + if ($null -ne $telemetry) { + $telemetryPayload = Get-PropertyValue -Item $telemetry -Name "payload" -Default $null + $telemetrySink = Get-PropertyValue -Item $telemetryPayload -Name "remote_workspace_adapter_sink_report" -Default $null + } + return [ordered]@{ + workload_status = $workloadStatus + workload_sink = $workloadSink + telemetry = $telemetry + telemetry_sink = $telemetrySink + } +} + +function Invoke-Control { + param([string]$SessionID) + if ([string]::IsNullOrWhiteSpace($SessionID)) { return $null } + $url = "$EntryBaseUrl/mesh/v1/remote-workspace/adapter-sessions/$SessionID/control" + $body = @{ action = "close"; reason = "c19z21 no active session readiness close" } | ConvertTo-Json -Compress + return Invoke-RestMethod -Method POST -Uri $url -ContentType "application/json" -Body $body -TimeoutSec 30 +} + +function Test-NoActiveSessionReadiness { + param([object]$Sink, [string]$AdapterSessionID) + if ($null -eq $Sink) { return $false } + $readiness = Get-PropertyValue -Item $Sink -Name "adapter_runtime_readiness" -Default $null + if ($null -eq $readiness) { return $false } + $activeAdapterSessionID = Get-PropertyValue -Item $readiness -Name "adapter_session_id" -Default $null + $rollup = Get-PropertyValue -Item $readiness -Name "last_preflight" -Default $null + return ( + [string](Get-PropertyValue -Item $readiness -Name "schema_version" -Default "") -eq "rap.remote_workspace_adapter_runtime_readiness.v1" -and + [string](Get-PropertyValue -Item $readiness -Name "status" -Default "") -eq "idle" -and + [string](Get-PropertyValue -Item $readiness -Name "diagnostic_state" -Default "") -eq "last_session_terminal_or_expired" -and + -not [bool](Get-PropertyValue -Item $readiness -Name "ready" -Default $true) -and + [int](Get-PropertyValue -Item $readiness -Name "active_session_count" -Default -1) -eq 0 -and + [string](Get-PropertyValue -Item $readiness -Name "last_adapter_session_id" -Default "") -eq $AdapterSessionID -and + [string](Get-PropertyValue -Item $readiness -Name "last_session_state" -Default "") -eq "closed" -and + $null -eq $activeAdapterSessionID -and + $null -eq $rollup + ) +} + +try { + $entryNode = Get-NodeByName -Name $EntryNodeName + + $source = & powershell -ExecutionPolicy Bypass -File (Join-Path $PSScriptRoot "c19x-remote-workspace-mailbox-consumer-resume-smoke.ps1") ` + -ApiBaseUrl $ApiBaseUrl ` + -ClusterID $ClusterID ` + -ActorUserID $ActorUserID ` + -EntryNodeName $EntryNodeName ` + -ExitNodeName $ExitNodeName ` + -EntryBaseUrl $EntryBaseUrl ` + -ResultPath $sourceResultPath ` + -SkipClose + + $sourceFile = Join-Path $repoRoot $sourceResultPath + $sourceResult = Get-Content -Raw -Path $sourceFile | ConvertFrom-Json + $adapterSessionID = [string](Get-PropertyValue -Item $sourceResult -Name "adapter_session_id" -Default "") + $control = Invoke-Control -SessionID $adapterSessionID + + $observed = $null + $deadline = (Get-Date).AddSeconds(90) + while ((Get-Date) -lt $deadline) { + Start-Sleep -Seconds 5 + $observed = Get-RemoteWorkspaceSinkReports -NodeID $entryNode.id + if ( + (Test-NoActiveSessionReadiness -Sink $observed.workload_sink -AdapterSessionID $adapterSessionID) -and + (Test-NoActiveSessionReadiness -Sink $observed.telemetry_sink -AdapterSessionID $adapterSessionID) + ) { + break + } + } + if ($null -eq $observed) { + $observed = Get-RemoteWorkspaceSinkReports -NodeID $entryNode.id + } + + $checks = [ordered]@{ + source_smoke_passed = ([bool]$sourceResult.passed) + adapter_session_id_present = ($adapterSessionID -match "^rap-rw-adapter-session-[0-9a-f]{24}$") + close_accepted = ([bool]$control.accepted -and [string]$control.session_state -eq "closed") + workload_no_active_session_readiness_visible = (Test-NoActiveSessionReadiness -Sink $observed.workload_sink -AdapterSessionID $adapterSessionID) + telemetry_no_active_session_readiness_visible = (Test-NoActiveSessionReadiness -Sink $observed.telemetry_sink -AdapterSessionID $adapterSessionID) + } + $failed = @($checks.GetEnumerator() | Where-Object { -not $_.Value } | ForEach-Object { $_.Key }) + + $result = [ordered]@{ + schema_version = "c19z21.remote_workspace_no_active_session_readiness_smoke.v1" + source_result_path = $sourceFile + cluster_id = $ClusterID + entry_node = [ordered]@{ id = $entryNode.id; name = $entryNode.name } + adapter_session_id = $adapterSessionID + source = $sourceResult + control = $control + observed = $observed + checks = $checks + failed_checks = $failed + passed = ($failed.Count -eq 0) + } +} finally { + if ($adapterSessionID) { + try { [void](Invoke-Control -SessionID $adapterSessionID) } catch {} + } +} + +$fullResultPath = Join-Path $repoRoot $ResultPath +$resultDir = Split-Path -Parent $fullResultPath +if ($resultDir) { New-Item -ItemType Directory -Force -Path $resultDir | Out-Null } +$result | ConvertTo-Json -Depth 100 | Set-Content -Encoding UTF8 -Path $fullResultPath + +if (-not $result.passed) { + throw "C19Z21 remote workspace no-active-session readiness smoke failed. Result: $fullResultPath Failed: $($failed -join ', ')" +} + +Write-Host "C19Z21 remote workspace no-active-session readiness smoke passed. Result: $fullResultPath" +$result diff --git a/scripts/fabric/c19z22-remote-workspace-terminal-state-readiness-smoke.ps1 b/scripts/fabric/c19z22-remote-workspace-terminal-state-readiness-smoke.ps1 new file mode 100644 index 0000000..3f4ebe9 --- /dev/null +++ b/scripts/fabric/c19z22-remote-workspace-terminal-state-readiness-smoke.ps1 @@ -0,0 +1,178 @@ +param( + [string]$ApiBaseUrl = "http://192.168.200.61:18121/api/v1", + [string]$ClusterID = "cfc0743d-d960-49fb-9de8-96e063d5e4aa", + [string]$ActorUserID = "f67d943f-5397-4b3a-a229-695fe67ad700", + [string]$EntryNodeName = "test-1", + [string]$ExitNodeName = "test-2", + [string]$EntryBaseUrl = "http://192.168.200.61:19131", + [string]$ResultPath = "artifacts\c19z22-remote-workspace-terminal-state-readiness-smoke-result.json" +) + +Set-StrictMode -Version Latest +$ErrorActionPreference = "Stop" + +$repoRoot = (Resolve-Path (Join-Path $PSScriptRoot "..\..")).ProviderPath + +function Invoke-Api { + param([string]$Method, [string]$Path, [object]$Body = $null) + $uri = "$ApiBaseUrl$Path" + if ($null -eq $Body) { return Invoke-RestMethod -Method $Method -Uri $uri -TimeoutSec 30 } + return Invoke-RestMethod -Method $Method -Uri $uri -ContentType "application/json" -Body ($Body | ConvertTo-Json -Depth 80) -TimeoutSec 30 +} + +function Get-PropertyValue { + param([object]$Item, [string]$Name, [object]$Default = $null) + if ($null -eq $Item) { return $Default } + $property = $Item.PSObject.Properties[$Name] + if ($null -eq $property) { return $Default } + return $property.Value +} + +function Get-NodeByName { + param([string]$Name) + $nodes = (Invoke-Api -Method GET -Path "/clusters/$ClusterID/nodes?actor_user_id=$ActorUserID").nodes + $node = @($nodes | Where-Object { $_.name -eq $Name }) | Select-Object -First 1 + if ($null -eq $node) { throw "Node '$Name' was not found in cluster $ClusterID" } + return $node +} + +function Get-RemoteWorkspaceSinkReports { + param([string]$NodeID) + $statuses = (Invoke-Api -Method GET -Path "/clusters/$ClusterID/nodes/$NodeID/workloads/status?actor_user_id=$ActorUserID").workload_statuses + $workloadStatus = @($statuses | Where-Object { $_.service_type -eq "rdp-worker" } | Select-Object -First 1) + $workloadSink = $null + if ($null -ne $workloadStatus) { + $workloadSink = Get-PropertyValue -Item $workloadStatus.status_payload -Name "remote_workspace_adapter_sink" -Default $null + } + $telemetryItems = @((Invoke-Api -Method GET -Path "/clusters/$ClusterID/nodes/$NodeID/telemetry?actor_user_id=$ActorUserID&limit=10").telemetry) + $telemetry = $telemetryItems | Select-Object -First 1 + $telemetrySink = $null + if ($null -ne $telemetry) { + $telemetryPayload = Get-PropertyValue -Item $telemetry -Name "payload" -Default $null + $telemetrySink = Get-PropertyValue -Item $telemetryPayload -Name "remote_workspace_adapter_sink_report" -Default $null + } + return [ordered]@{ + workload_status = $workloadStatus + workload_sink = $workloadSink + telemetry = $telemetry + telemetry_sink = $telemetrySink + } +} + +function Invoke-Control { + param([string]$SessionID, [string]$Action) + if ([string]::IsNullOrWhiteSpace($SessionID)) { return $null } + $url = "$EntryBaseUrl/mesh/v1/remote-workspace/adapter-sessions/$SessionID/control" + $body = @{ action = $Action; reason = "c19z22 terminal state readiness $Action" } | ConvertTo-Json -Compress + return Invoke-RestMethod -Method POST -Uri $url -ContentType "application/json" -Body $body -TimeoutSec 30 +} + +function Test-TerminalReadiness { + param([object]$Sink, [string]$AdapterSessionID, [string]$TerminalState) + if ($null -eq $Sink) { return $false } + $readiness = Get-PropertyValue -Item $Sink -Name "adapter_runtime_readiness" -Default $null + if ($null -eq $readiness) { return $false } + $activeAdapterSessionID = Get-PropertyValue -Item $readiness -Name "adapter_session_id" -Default $null + $rollup = Get-PropertyValue -Item $readiness -Name "last_preflight" -Default $null + return ( + [string](Get-PropertyValue -Item $readiness -Name "schema_version" -Default "") -eq "rap.remote_workspace_adapter_runtime_readiness.v1" -and + [string](Get-PropertyValue -Item $readiness -Name "status" -Default "") -eq "idle" -and + [string](Get-PropertyValue -Item $readiness -Name "diagnostic_state" -Default "") -eq "last_session_terminal_or_expired" -and + -not [bool](Get-PropertyValue -Item $readiness -Name "ready" -Default $true) -and + [int](Get-PropertyValue -Item $readiness -Name "active_session_count" -Default -1) -eq 0 -and + [string](Get-PropertyValue -Item $readiness -Name "last_adapter_session_id" -Default "") -eq $AdapterSessionID -and + [string](Get-PropertyValue -Item $readiness -Name "last_session_state" -Default "") -eq $TerminalState -and + $null -eq $activeAdapterSessionID -and + $null -eq $rollup + ) +} + +function Invoke-TerminalStateProbe { + param([object]$EntryNode, [string]$Action, [string]$TerminalState) + $sourceResultPath = "artifacts\c19z22-remote-workspace-terminal-state-readiness-$Action-source-result.json" + $source = & powershell -ExecutionPolicy Bypass -File (Join-Path $PSScriptRoot "c19x-remote-workspace-mailbox-consumer-resume-smoke.ps1") ` + -ApiBaseUrl $ApiBaseUrl ` + -ClusterID $ClusterID ` + -ActorUserID $ActorUserID ` + -EntryNodeName $EntryNodeName ` + -ExitNodeName $ExitNodeName ` + -EntryBaseUrl $EntryBaseUrl ` + -ResultPath $sourceResultPath ` + -SkipClose + + $sourceFile = Join-Path $repoRoot $sourceResultPath + $sourceResult = Get-Content -Raw -Path $sourceFile | ConvertFrom-Json + $adapterSessionID = [string](Get-PropertyValue -Item $sourceResult -Name "adapter_session_id" -Default "") + $control = Invoke-Control -SessionID $adapterSessionID -Action $Action + + $observed = $null + $deadline = (Get-Date).AddSeconds(90) + while ((Get-Date) -lt $deadline) { + Start-Sleep -Seconds 5 + $observed = Get-RemoteWorkspaceSinkReports -NodeID $EntryNode.id + if ( + (Test-TerminalReadiness -Sink $observed.workload_sink -AdapterSessionID $adapterSessionID -TerminalState $TerminalState) -and + (Test-TerminalReadiness -Sink $observed.telemetry_sink -AdapterSessionID $adapterSessionID -TerminalState $TerminalState) + ) { + break + } + } + if ($null -eq $observed) { + $observed = Get-RemoteWorkspaceSinkReports -NodeID $EntryNode.id + } + + $checks = [ordered]@{ + source_smoke_passed = ([bool]$sourceResult.passed) + adapter_session_id_present = ($adapterSessionID -match "^rap-rw-adapter-session-[0-9a-f]{24}$") + control_accepted = ([bool]$control.accepted -and [string]$control.action -eq $Action -and [string]$control.session_state -eq $TerminalState) + workload_terminal_readiness_visible = (Test-TerminalReadiness -Sink $observed.workload_sink -AdapterSessionID $adapterSessionID -TerminalState $TerminalState) + telemetry_terminal_readiness_visible = (Test-TerminalReadiness -Sink $observed.telemetry_sink -AdapterSessionID $adapterSessionID -TerminalState $TerminalState) + } + $failed = @($checks.GetEnumerator() | Where-Object { -not $_.Value } | ForEach-Object { $_.Key }) + + return [ordered]@{ + action = $Action + terminal_state = $TerminalState + source_result_path = $sourceFile + adapter_session_id = $adapterSessionID + source = $sourceResult + control = $control + observed = $observed + checks = $checks + failed_checks = $failed + passed = ($failed.Count -eq 0) + } +} + +$entryNode = Get-NodeByName -Name $EntryNodeName +$expireProbe = Invoke-TerminalStateProbe -EntryNode $entryNode -Action "expire" -TerminalState "expired" +$resetProbe = Invoke-TerminalStateProbe -EntryNode $entryNode -Action "reset" -TerminalState "reset" + +$checks = [ordered]@{ + expire_terminal_readiness_visible = ([bool]$expireProbe.passed) + reset_terminal_readiness_visible = ([bool]$resetProbe.passed) +} +$failed = @($checks.GetEnumerator() | Where-Object { -not $_.Value } | ForEach-Object { $_.Key }) + +$result = [ordered]@{ + schema_version = "c19z22.remote_workspace_terminal_state_readiness_smoke.v1" + cluster_id = $ClusterID + entry_node = [ordered]@{ id = $entryNode.id; name = $entryNode.name } + expire_probe = $expireProbe + reset_probe = $resetProbe + checks = $checks + failed_checks = $failed + passed = ($failed.Count -eq 0) +} + +$fullResultPath = Join-Path $repoRoot $ResultPath +$resultDir = Split-Path -Parent $fullResultPath +if ($resultDir) { New-Item -ItemType Directory -Force -Path $resultDir | Out-Null } +$result | ConvertTo-Json -Depth 100 | Set-Content -Encoding UTF8 -Path $fullResultPath + +if (-not $result.passed) { + throw "C19Z22 remote workspace terminal-state readiness smoke failed. Result: $fullResultPath Failed: $($failed -join ', ')" +} + +Write-Host "C19Z22 remote workspace terminal-state readiness smoke passed. Result: $fullResultPath" +$result diff --git a/scripts/fabric/c19z23-remote-workspace-terminal-session-summary-smoke.ps1 b/scripts/fabric/c19z23-remote-workspace-terminal-session-summary-smoke.ps1 new file mode 100644 index 0000000..9c34733 --- /dev/null +++ b/scripts/fabric/c19z23-remote-workspace-terminal-session-summary-smoke.ps1 @@ -0,0 +1,94 @@ +param( + [string]$ApiBaseUrl = "http://192.168.200.61:18121/api/v1", + [string]$ClusterID = "cfc0743d-d960-49fb-9de8-96e063d5e4aa", + [string]$ActorUserID = "f67d943f-5397-4b3a-a229-695fe67ad700", + [string]$EntryNodeName = "test-1", + [string]$ExitNodeName = "test-2", + [string]$EntryBaseUrl = "http://192.168.200.61:19131", + [string]$ResultPath = "artifacts\c19z23-remote-workspace-terminal-session-summary-smoke-result.json" +) + +Set-StrictMode -Version Latest +$ErrorActionPreference = "Stop" + +$repoRoot = (Resolve-Path (Join-Path $PSScriptRoot "..\..")).ProviderPath +$sourceResultPath = "artifacts\c19z23-remote-workspace-terminal-session-summary-source-result.json" + +function Get-PropertyValue { + param([object]$Item, [string]$Name, [object]$Default = $null) + if ($null -eq $Item) { return $Default } + $property = $Item.PSObject.Properties[$Name] + if ($null -eq $property) { return $Default } + return $property.Value +} + +function Test-TerminalSummary { + param([object]$Sink, [string]$AdapterSessionID, [string]$TerminalState) + if ($null -eq $Sink) { return $false } + $readiness = Get-PropertyValue -Item $Sink -Name "adapter_runtime_readiness" -Default $null + $summary = Get-PropertyValue -Item $readiness -Name "terminal_session_summary" -Default $null + if ($null -eq $summary) { return $false } + return ( + [string](Get-PropertyValue -Item $readiness -Name "status" -Default "") -eq "idle" -and + [string](Get-PropertyValue -Item $readiness -Name "diagnostic_state" -Default "") -eq "last_session_terminal_or_expired" -and + [string](Get-PropertyValue -Item $readiness -Name "last_adapter_session_id" -Default "") -eq $AdapterSessionID -and + [string](Get-PropertyValue -Item $readiness -Name "last_session_state" -Default "") -eq $TerminalState -and + [string](Get-PropertyValue -Item $summary -Name "adapter_session_id" -Default "") -eq $AdapterSessionID -and + [string](Get-PropertyValue -Item $summary -Name "session_state" -Default "") -eq $TerminalState -and + [string](Get-PropertyValue -Item $summary -Name "reason" -Default "") -like "c19z22 terminal state readiness*" -and + [string](Get-PropertyValue -Item $summary -Name "controlled_at" -Default "") -match "^\d{4}-\d{2}-\d{2}T" + ) +} + +$source = & powershell -ExecutionPolicy Bypass -File (Join-Path $PSScriptRoot "c19z22-remote-workspace-terminal-state-readiness-smoke.ps1") ` + -ApiBaseUrl $ApiBaseUrl ` + -ClusterID $ClusterID ` + -ActorUserID $ActorUserID ` + -EntryNodeName $EntryNodeName ` + -ExitNodeName $ExitNodeName ` + -EntryBaseUrl $EntryBaseUrl ` + -ResultPath $sourceResultPath + +$sourceFile = Join-Path $repoRoot $sourceResultPath +$sourceResult = Get-Content -Raw -Path $sourceFile | ConvertFrom-Json +$expireProbe = Get-PropertyValue -Item $sourceResult -Name "expire_probe" -Default $null +$resetProbe = Get-PropertyValue -Item $sourceResult -Name "reset_probe" -Default $null +$expireObserved = Get-PropertyValue -Item $expireProbe -Name "observed" -Default $null +$resetObserved = Get-PropertyValue -Item $resetProbe -Name "observed" -Default $null +$expireSessionID = [string](Get-PropertyValue -Item $expireProbe -Name "adapter_session_id" -Default "") +$resetSessionID = [string](Get-PropertyValue -Item $resetProbe -Name "adapter_session_id" -Default "") + +$checks = [ordered]@{ + source_smoke_passed = ([bool]$sourceResult.passed) + expire_adapter_session_id_present = ($expireSessionID -match "^rap-rw-adapter-session-[0-9a-f]{24}$") + reset_adapter_session_id_present = ($resetSessionID -match "^rap-rw-adapter-session-[0-9a-f]{24}$") + workload_expire_terminal_summary_visible = (Test-TerminalSummary -Sink $expireObserved.workload_sink -AdapterSessionID $expireSessionID -TerminalState "expired") + telemetry_expire_terminal_summary_visible = (Test-TerminalSummary -Sink $expireObserved.telemetry_sink -AdapterSessionID $expireSessionID -TerminalState "expired") + workload_reset_terminal_summary_visible = (Test-TerminalSummary -Sink $resetObserved.workload_sink -AdapterSessionID $resetSessionID -TerminalState "reset") + telemetry_reset_terminal_summary_visible = (Test-TerminalSummary -Sink $resetObserved.telemetry_sink -AdapterSessionID $resetSessionID -TerminalState "reset") +} +$failed = @($checks.GetEnumerator() | Where-Object { -not $_.Value } | ForEach-Object { $_.Key }) + +$result = [ordered]@{ + schema_version = "c19z23.remote_workspace_terminal_session_summary_smoke.v1" + source_result_path = $sourceFile + cluster_id = $ClusterID + expire_adapter_session_id = $expireSessionID + reset_adapter_session_id = $resetSessionID + source = $sourceResult + checks = $checks + failed_checks = $failed + passed = ($failed.Count -eq 0) +} + +$fullResultPath = Join-Path $repoRoot $ResultPath +$resultDir = Split-Path -Parent $fullResultPath +if ($resultDir) { New-Item -ItemType Directory -Force -Path $resultDir | Out-Null } +$result | ConvertTo-Json -Depth 100 | Set-Content -Encoding UTF8 -Path $fullResultPath + +if (-not $result.passed) { + throw "C19Z23 remote workspace terminal-session summary smoke failed. Result: $fullResultPath Failed: $($failed -join ', ')" +} + +Write-Host "C19Z23 remote workspace terminal-session summary smoke passed. Result: $fullResultPath" +$result diff --git a/scripts/fabric/c19z24-remote-workspace-terminal-summary-contract-smoke.ps1 b/scripts/fabric/c19z24-remote-workspace-terminal-summary-contract-smoke.ps1 new file mode 100644 index 0000000..5f7d72f --- /dev/null +++ b/scripts/fabric/c19z24-remote-workspace-terminal-summary-contract-smoke.ps1 @@ -0,0 +1,105 @@ +param( + [string]$ApiBaseUrl = "http://192.168.200.61:18121/api/v1", + [string]$ClusterID = "cfc0743d-d960-49fb-9de8-96e063d5e4aa", + [string]$ActorUserID = "f67d943f-5397-4b3a-a229-695fe67ad700", + [string]$EntryNodeName = "test-1", + [string]$ExitNodeName = "test-2", + [string]$EntryBaseUrl = "http://192.168.200.61:19131", + [string]$ResultPath = "artifacts\c19z24-remote-workspace-terminal-summary-contract-smoke-result.json" +) + +Set-StrictMode -Version Latest +$ErrorActionPreference = "Stop" + +$repoRoot = (Resolve-Path (Join-Path $PSScriptRoot "..\..")).ProviderPath +$sourceResultPath = "artifacts\c19z24-remote-workspace-terminal-summary-contract-source-result.json" + +function Get-PropertyValue { + param([object]$Item, [string]$Name, [object]$Default = $null) + if ($null -eq $Item) { return $Default } + $property = $Item.PSObject.Properties[$Name] + if ($null -eq $property) { return $Default } + return $property.Value +} + +function Test-ContractItem { + param([object]$Items, [string]$Want) + foreach ($item in @($Items)) { + if ([string]$item -eq $Want) { return $true } + } + return $false +} + +function Test-TerminalSummaryContract { + param([object]$Sink, [string]$AdapterSessionID, [string]$TerminalState) + if ($null -eq $Sink) { return $false } + $readiness = Get-PropertyValue -Item $Sink -Name "adapter_runtime_readiness" -Default $null + $summary = Get-PropertyValue -Item $readiness -Name "terminal_session_summary" -Default $null + if ($null -eq $summary) { return $false } + $contract = Get-PropertyValue -Item $summary -Name "summary_contract" -Default @() + return ( + [string](Get-PropertyValue -Item $readiness -Name "diagnostic_state" -Default "") -eq "last_session_terminal_or_expired" -and + [string](Get-PropertyValue -Item $readiness -Name "last_adapter_session_id" -Default "") -eq $AdapterSessionID -and + [string](Get-PropertyValue -Item $summary -Name "schema_version" -Default "") -eq "rap.remote_workspace_adapter_terminal_session_summary.v1" -and + (Test-ContractItem -Items $contract -Want "adapter_session_id") -and + (Test-ContractItem -Items $contract -Want "session_state") -and + (Test-ContractItem -Items $contract -Want "reason") -and + (Test-ContractItem -Items $contract -Want "controlled_at") -and + [string](Get-PropertyValue -Item $summary -Name "adapter_session_id" -Default "") -eq $AdapterSessionID -and + [string](Get-PropertyValue -Item $summary -Name "session_state" -Default "") -eq $TerminalState + ) +} + +$source = & powershell -ExecutionPolicy Bypass -File (Join-Path $PSScriptRoot "c19z23-remote-workspace-terminal-session-summary-smoke.ps1") ` + -ApiBaseUrl $ApiBaseUrl ` + -ClusterID $ClusterID ` + -ActorUserID $ActorUserID ` + -EntryNodeName $EntryNodeName ` + -ExitNodeName $ExitNodeName ` + -EntryBaseUrl $EntryBaseUrl ` + -ResultPath $sourceResultPath + +$sourceFile = Join-Path $repoRoot $sourceResultPath +$sourceResult = Get-Content -Raw -Path $sourceFile | ConvertFrom-Json +$sourceInner = Get-PropertyValue -Item $sourceResult -Name "source" -Default $null +$expireProbe = Get-PropertyValue -Item $sourceInner -Name "expire_probe" -Default $null +$resetProbe = Get-PropertyValue -Item $sourceInner -Name "reset_probe" -Default $null +$expireObserved = Get-PropertyValue -Item $expireProbe -Name "observed" -Default $null +$resetObserved = Get-PropertyValue -Item $resetProbe -Name "observed" -Default $null +$expireSessionID = [string](Get-PropertyValue -Item $sourceResult -Name "expire_adapter_session_id" -Default "") +$resetSessionID = [string](Get-PropertyValue -Item $sourceResult -Name "reset_adapter_session_id" -Default "") + +$checks = [ordered]@{ + source_smoke_passed = ([bool]$sourceResult.passed) + expire_adapter_session_id_present = ($expireSessionID -match "^rap-rw-adapter-session-[0-9a-f]{24}$") + reset_adapter_session_id_present = ($resetSessionID -match "^rap-rw-adapter-session-[0-9a-f]{24}$") + workload_expire_summary_contract_visible = (Test-TerminalSummaryContract -Sink $expireObserved.workload_sink -AdapterSessionID $expireSessionID -TerminalState "expired") + telemetry_expire_summary_contract_visible = (Test-TerminalSummaryContract -Sink $expireObserved.telemetry_sink -AdapterSessionID $expireSessionID -TerminalState "expired") + workload_reset_summary_contract_visible = (Test-TerminalSummaryContract -Sink $resetObserved.workload_sink -AdapterSessionID $resetSessionID -TerminalState "reset") + telemetry_reset_summary_contract_visible = (Test-TerminalSummaryContract -Sink $resetObserved.telemetry_sink -AdapterSessionID $resetSessionID -TerminalState "reset") +} +$failed = @($checks.GetEnumerator() | Where-Object { -not $_.Value } | ForEach-Object { $_.Key }) + +$result = [ordered]@{ + schema_version = "c19z24.remote_workspace_terminal_summary_contract_smoke.v1" + source_result_path = $sourceFile + cluster_id = $ClusterID + expire_adapter_session_id = $expireSessionID + reset_adapter_session_id = $resetSessionID + source = $sourceResult + checks = $checks + failed_checks = $failed + passed = ($failed.Count -eq 0) +} + +$fullResultPath = Join-Path $repoRoot $ResultPath +$resultDir = Split-Path -Parent $fullResultPath +if ($resultDir) { New-Item -ItemType Directory -Force -Path $resultDir | Out-Null } +$result | ConvertTo-Json -Depth 100 | Set-Content -Encoding UTF8 -Path $fullResultPath + +if (-not $result.passed) { + throw "C19Z24 remote workspace terminal-summary contract smoke failed. Result: $fullResultPath Failed: $($failed -join ', ')" +} + +Write-Host "C19Z24 remote workspace terminal-summary contract smoke passed. Result: $fullResultPath" +$result diff --git a/scripts/fabric/c19z25-remote-workspace-terminal-summary-features-smoke.ps1 b/scripts/fabric/c19z25-remote-workspace-terminal-summary-features-smoke.ps1 new file mode 100644 index 0000000..2407760 --- /dev/null +++ b/scripts/fabric/c19z25-remote-workspace-terminal-summary-features-smoke.ps1 @@ -0,0 +1,101 @@ +param( + [string]$ApiBaseUrl = "http://192.168.200.61:18121/api/v1", + [string]$ClusterID = "cfc0743d-d960-49fb-9de8-96e063d5e4aa", + [string]$ActorUserID = "f67d943f-5397-4b3a-a229-695fe67ad700", + [string]$EntryNodeName = "test-1", + [string]$ExitNodeName = "test-2", + [string]$EntryBaseUrl = "http://192.168.200.61:19131", + [string]$ResultPath = "artifacts\c19z25-remote-workspace-terminal-summary-features-smoke-result.json" +) + +Set-StrictMode -Version Latest +$ErrorActionPreference = "Stop" + +$repoRoot = (Resolve-Path (Join-Path $PSScriptRoot "..\..")).ProviderPath +$sourceResultPath = "artifacts\c19z25-remote-workspace-terminal-summary-features-source-result.json" + +function Get-PropertyValue { + param([object]$Item, [string]$Name, [object]$Default = $null) + if ($null -eq $Item) { return $Default } + $property = $Item.PSObject.Properties[$Name] + if ($null -eq $property) { return $Default } + return $property.Value +} + +function Test-FeatureFlag { + param([object]$Features, [string]$Name) + return ([bool](Get-PropertyValue -Item $Features -Name $Name -Default $false)) +} + +function Test-TerminalSummaryFeatures { + param([object]$Sink, [string]$AdapterSessionID, [string]$TerminalState) + if ($null -eq $Sink) { return $false } + $readiness = Get-PropertyValue -Item $Sink -Name "adapter_runtime_readiness" -Default $null + $summary = Get-PropertyValue -Item $readiness -Name "terminal_session_summary" -Default $null + if ($null -eq $summary) { return $false } + $features = Get-PropertyValue -Item $summary -Name "summary_features" -Default $null + return ( + [string](Get-PropertyValue -Item $summary -Name "schema_version" -Default "") -eq "rap.remote_workspace_adapter_terminal_session_summary.v1" -and + [string](Get-PropertyValue -Item $summary -Name "adapter_session_id" -Default "") -eq $AdapterSessionID -and + [string](Get-PropertyValue -Item $summary -Name "session_state" -Default "") -eq $TerminalState -and + (Test-FeatureFlag -Features $features -Name "adapter_session_id") -and + (Test-FeatureFlag -Features $features -Name "session_state") -and + (Test-FeatureFlag -Features $features -Name "reason") -and + (Test-FeatureFlag -Features $features -Name "controlled_at") + ) +} + +$source = & powershell -ExecutionPolicy Bypass -File (Join-Path $PSScriptRoot "c19z24-remote-workspace-terminal-summary-contract-smoke.ps1") ` + -ApiBaseUrl $ApiBaseUrl ` + -ClusterID $ClusterID ` + -ActorUserID $ActorUserID ` + -EntryNodeName $EntryNodeName ` + -ExitNodeName $ExitNodeName ` + -EntryBaseUrl $EntryBaseUrl ` + -ResultPath $sourceResultPath + +$sourceFile = Join-Path $repoRoot $sourceResultPath +$sourceResult = Get-Content -Raw -Path $sourceFile | ConvertFrom-Json +$sourceC19Z23 = Get-PropertyValue -Item $sourceResult -Name "source" -Default $null +$sourceC19Z22 = Get-PropertyValue -Item $sourceC19Z23 -Name "source" -Default $null +$expireProbe = Get-PropertyValue -Item $sourceC19Z22 -Name "expire_probe" -Default $null +$resetProbe = Get-PropertyValue -Item $sourceC19Z22 -Name "reset_probe" -Default $null +$expireObserved = Get-PropertyValue -Item $expireProbe -Name "observed" -Default $null +$resetObserved = Get-PropertyValue -Item $resetProbe -Name "observed" -Default $null +$expireSessionID = [string](Get-PropertyValue -Item $sourceResult -Name "expire_adapter_session_id" -Default "") +$resetSessionID = [string](Get-PropertyValue -Item $sourceResult -Name "reset_adapter_session_id" -Default "") + +$checks = [ordered]@{ + source_smoke_passed = ([bool]$sourceResult.passed) + expire_adapter_session_id_present = ($expireSessionID -match "^rap-rw-adapter-session-[0-9a-f]{24}$") + reset_adapter_session_id_present = ($resetSessionID -match "^rap-rw-adapter-session-[0-9a-f]{24}$") + workload_expire_summary_features_visible = (Test-TerminalSummaryFeatures -Sink $expireObserved.workload_sink -AdapterSessionID $expireSessionID -TerminalState "expired") + telemetry_expire_summary_features_visible = (Test-TerminalSummaryFeatures -Sink $expireObserved.telemetry_sink -AdapterSessionID $expireSessionID -TerminalState "expired") + workload_reset_summary_features_visible = (Test-TerminalSummaryFeatures -Sink $resetObserved.workload_sink -AdapterSessionID $resetSessionID -TerminalState "reset") + telemetry_reset_summary_features_visible = (Test-TerminalSummaryFeatures -Sink $resetObserved.telemetry_sink -AdapterSessionID $resetSessionID -TerminalState "reset") +} +$failed = @($checks.GetEnumerator() | Where-Object { -not $_.Value } | ForEach-Object { $_.Key }) + +$result = [ordered]@{ + schema_version = "c19z25.remote_workspace_terminal_summary_features_smoke.v1" + source_result_path = $sourceFile + cluster_id = $ClusterID + expire_adapter_session_id = $expireSessionID + reset_adapter_session_id = $resetSessionID + source = $sourceResult + checks = $checks + failed_checks = $failed + passed = ($failed.Count -eq 0) +} + +$fullResultPath = Join-Path $repoRoot $ResultPath +$resultDir = Split-Path -Parent $fullResultPath +if ($resultDir) { New-Item -ItemType Directory -Force -Path $resultDir | Out-Null } +$result | ConvertTo-Json -Depth 100 | Set-Content -Encoding UTF8 -Path $fullResultPath + +if (-not $result.passed) { + throw "C19Z25 remote workspace terminal-summary features smoke failed. Result: $fullResultPath Failed: $($failed -join ', ')" +} + +Write-Host "C19Z25 remote workspace terminal-summary features smoke passed. Result: $fullResultPath" +$result diff --git a/scripts/fabric/c19z26-remote-workspace-terminal-summary-compatibility-smoke.ps1 b/scripts/fabric/c19z26-remote-workspace-terminal-summary-compatibility-smoke.ps1 new file mode 100644 index 0000000..dc7a451 --- /dev/null +++ b/scripts/fabric/c19z26-remote-workspace-terminal-summary-compatibility-smoke.ps1 @@ -0,0 +1,112 @@ +param( + [string]$ApiBaseUrl = "http://192.168.200.61:18121/api/v1", + [string]$ClusterID = "cfc0743d-d960-49fb-9de8-96e063d5e4aa", + [string]$ActorUserID = "f67d943f-5397-4b3a-a229-695fe67ad700", + [string]$EntryNodeName = "test-1", + [string]$ExitNodeName = "test-2", + [string]$EntryBaseUrl = "http://192.168.200.61:19131", + [string]$ResultPath = "artifacts\c19z26-remote-workspace-terminal-summary-compatibility-smoke-result.json" +) + +Set-StrictMode -Version Latest +$ErrorActionPreference = "Stop" + +$repoRoot = (Resolve-Path (Join-Path $PSScriptRoot "..\..")).ProviderPath +$sourceResultPath = "artifacts\c19z26-remote-workspace-terminal-summary-compatibility-source-result.json" +$requiredFeatures = @("adapter_session_id", "session_state", "reason", "controlled_at") + +function Get-PropertyValue { + param([object]$Item, [string]$Name, [object]$Default = $null) + if ($null -eq $Item) { return $Default } + $property = $Item.PSObject.Properties[$Name] + if ($null -eq $property) { return $Default } + return $property.Value +} + +function Test-ContractItem { + param([object]$Items, [string]$Want) + foreach ($item in @($Items)) { + if ([string]$item -eq $Want) { return $true } + } + return $false +} + +function Test-FeatureFlag { + param([object]$Features, [string]$Name) + return ([bool](Get-PropertyValue -Item $Features -Name $Name -Default $false)) +} + +function Test-TerminalSummaryCompatibility { + param([object]$Sink, [string]$AdapterSessionID, [string]$TerminalState) + if ($null -eq $Sink) { return $false } + $readiness = Get-PropertyValue -Item $Sink -Name "adapter_runtime_readiness" -Default $null + $summary = Get-PropertyValue -Item $readiness -Name "terminal_session_summary" -Default $null + if ($null -eq $summary) { return $false } + $contract = Get-PropertyValue -Item $summary -Name "summary_contract" -Default @() + $features = Get-PropertyValue -Item $summary -Name "summary_features" -Default $null + if ([string](Get-PropertyValue -Item $summary -Name "schema_version" -Default "") -ne "rap.remote_workspace_adapter_terminal_session_summary.v1") { return $false } + if ([string](Get-PropertyValue -Item $summary -Name "adapter_session_id" -Default "") -ne $AdapterSessionID) { return $false } + if ([string](Get-PropertyValue -Item $summary -Name "session_state" -Default "") -ne $TerminalState) { return $false } + foreach ($feature in $requiredFeatures) { + if (-not (Test-ContractItem -Items $contract -Want $feature)) { return $false } + if (-not (Test-FeatureFlag -Features $features -Name $feature)) { return $false } + } + return $true +} + +$source = & powershell -ExecutionPolicy Bypass -File (Join-Path $PSScriptRoot "c19z25-remote-workspace-terminal-summary-features-smoke.ps1") ` + -ApiBaseUrl $ApiBaseUrl ` + -ClusterID $ClusterID ` + -ActorUserID $ActorUserID ` + -EntryNodeName $EntryNodeName ` + -ExitNodeName $ExitNodeName ` + -EntryBaseUrl $EntryBaseUrl ` + -ResultPath $sourceResultPath + +$sourceFile = Join-Path $repoRoot $sourceResultPath +$sourceResult = Get-Content -Raw -Path $sourceFile | ConvertFrom-Json +$sourceC19Z24 = Get-PropertyValue -Item $sourceResult -Name "source" -Default $null +$sourceC19Z23 = Get-PropertyValue -Item $sourceC19Z24 -Name "source" -Default $null +$sourceC19Z22 = Get-PropertyValue -Item $sourceC19Z23 -Name "source" -Default $null +$expireProbe = Get-PropertyValue -Item $sourceC19Z22 -Name "expire_probe" -Default $null +$resetProbe = Get-PropertyValue -Item $sourceC19Z22 -Name "reset_probe" -Default $null +$expireObserved = Get-PropertyValue -Item $expireProbe -Name "observed" -Default $null +$resetObserved = Get-PropertyValue -Item $resetProbe -Name "observed" -Default $null +$expireSessionID = [string](Get-PropertyValue -Item $sourceResult -Name "expire_adapter_session_id" -Default "") +$resetSessionID = [string](Get-PropertyValue -Item $sourceResult -Name "reset_adapter_session_id" -Default "") + +$checks = [ordered]@{ + source_smoke_passed = ([bool]$sourceResult.passed) + expire_adapter_session_id_present = ($expireSessionID -match "^rap-rw-adapter-session-[0-9a-f]{24}$") + reset_adapter_session_id_present = ($resetSessionID -match "^rap-rw-adapter-session-[0-9a-f]{24}$") + workload_expire_summary_compatibility = (Test-TerminalSummaryCompatibility -Sink $expireObserved.workload_sink -AdapterSessionID $expireSessionID -TerminalState "expired") + telemetry_expire_summary_compatibility = (Test-TerminalSummaryCompatibility -Sink $expireObserved.telemetry_sink -AdapterSessionID $expireSessionID -TerminalState "expired") + workload_reset_summary_compatibility = (Test-TerminalSummaryCompatibility -Sink $resetObserved.workload_sink -AdapterSessionID $resetSessionID -TerminalState "reset") + telemetry_reset_summary_compatibility = (Test-TerminalSummaryCompatibility -Sink $resetObserved.telemetry_sink -AdapterSessionID $resetSessionID -TerminalState "reset") +} +$failed = @($checks.GetEnumerator() | Where-Object { -not $_.Value } | ForEach-Object { $_.Key }) + +$result = [ordered]@{ + schema_version = "c19z26.remote_workspace_terminal_summary_compatibility_smoke.v1" + source_result_path = $sourceFile + cluster_id = $ClusterID + expire_adapter_session_id = $expireSessionID + reset_adapter_session_id = $resetSessionID + required_features = $requiredFeatures + source = $sourceResult + checks = $checks + failed_checks = $failed + passed = ($failed.Count -eq 0) +} + +$fullResultPath = Join-Path $repoRoot $ResultPath +$resultDir = Split-Path -Parent $fullResultPath +if ($resultDir) { New-Item -ItemType Directory -Force -Path $resultDir | Out-Null } +$result | ConvertTo-Json -Depth 100 | Set-Content -Encoding UTF8 -Path $fullResultPath + +if (-not $result.passed) { + throw "C19Z26 remote workspace terminal-summary compatibility smoke failed. Result: $fullResultPath Failed: $($failed -join ', ')" +} + +Write-Host "C19Z26 remote workspace terminal-summary compatibility smoke passed. Result: $fullResultPath" +$result diff --git a/scripts/fabric/c19z27-remote-workspace-terminal-summary-absence-smoke.ps1 b/scripts/fabric/c19z27-remote-workspace-terminal-summary-absence-smoke.ps1 new file mode 100644 index 0000000..58272c9 --- /dev/null +++ b/scripts/fabric/c19z27-remote-workspace-terminal-summary-absence-smoke.ps1 @@ -0,0 +1,126 @@ +param( + [string]$ApiBaseUrl = "http://192.168.200.61:18121/api/v1", + [string]$ClusterID = "cfc0743d-d960-49fb-9de8-96e063d5e4aa", + [string]$ActorUserID = "f67d943f-5397-4b3a-a229-695fe67ad700", + [string]$EntryNodeName = "test-1", + [string]$ResultPath = "artifacts\c19z27-remote-workspace-terminal-summary-absence-smoke-result.json" +) + +Set-StrictMode -Version Latest +$ErrorActionPreference = "Stop" + +$repoRoot = (Resolve-Path (Join-Path $PSScriptRoot "..\..")).ProviderPath + +function Invoke-Api { + param([string]$Method, [string]$Path, [object]$Body = $null) + $uri = "$ApiBaseUrl$Path" + if ($null -eq $Body) { return Invoke-RestMethod -Method $Method -Uri $uri -TimeoutSec 30 } + return Invoke-RestMethod -Method $Method -Uri $uri -ContentType "application/json" -Body ($Body | ConvertTo-Json -Depth 80) -TimeoutSec 30 +} + +function Get-PropertyValue { + param([object]$Item, [string]$Name, [object]$Default = $null) + if ($null -eq $Item) { return $Default } + $property = $Item.PSObject.Properties[$Name] + if ($null -eq $property) { return $Default } + return $property.Value +} + +function Get-NodeByName { + param([string]$Name) + $nodes = (Invoke-Api -Method GET -Path "/clusters/$ClusterID/nodes?actor_user_id=$ActorUserID").nodes + $node = @($nodes | Where-Object { $_.name -eq $Name }) | Select-Object -First 1 + if ($null -eq $node) { throw "Node '$Name' was not found in cluster $ClusterID" } + return $node +} + +function Get-RemoteWorkspaceSinkReports { + param([string]$NodeID) + $statuses = (Invoke-Api -Method GET -Path "/clusters/$ClusterID/nodes/$NodeID/workloads/status?actor_user_id=$ActorUserID").workload_statuses + $workloadStatus = @($statuses | Where-Object { $_.service_type -eq "rdp-worker" } | Select-Object -First 1) + $workloadSink = $null + if ($null -ne $workloadStatus) { + $workloadSink = Get-PropertyValue -Item $workloadStatus.status_payload -Name "remote_workspace_adapter_sink" -Default $null + } + $telemetryItems = @((Invoke-Api -Method GET -Path "/clusters/$ClusterID/nodes/$NodeID/telemetry?actor_user_id=$ActorUserID&limit=10").telemetry) + $telemetry = $telemetryItems | Select-Object -First 1 + $telemetrySink = $null + if ($null -ne $telemetry) { + $telemetryPayload = Get-PropertyValue -Item $telemetry -Name "payload" -Default $null + $telemetrySink = Get-PropertyValue -Item $telemetryPayload -Name "remote_workspace_adapter_sink_report" -Default $null + } + return [ordered]@{ + workload_status = $workloadStatus + workload_sink = $workloadSink + telemetry = $telemetry + telemetry_sink = $telemetrySink + } +} + +function Test-TerminalSummaryAbsent { + param([object]$Sink) + if ($null -eq $Sink) { return $false } + $readiness = Get-PropertyValue -Item $Sink -Name "adapter_runtime_readiness" -Default $null + if ($null -eq $readiness) { return $false } + $lastAdapterSessionID = Get-PropertyValue -Item $readiness -Name "last_adapter_session_id" -Default $null + $lastSessionState = Get-PropertyValue -Item $readiness -Name "last_session_state" -Default $null + $terminalSummary = Get-PropertyValue -Item $readiness -Name "terminal_session_summary" -Default $null + $lastPreflight = Get-PropertyValue -Item $readiness -Name "last_preflight" -Default $null + return ( + [string](Get-PropertyValue -Item $readiness -Name "schema_version" -Default "") -eq "rap.remote_workspace_adapter_runtime_readiness.v1" -and + [string](Get-PropertyValue -Item $readiness -Name "status" -Default "") -eq "idle" -and + [string](Get-PropertyValue -Item $readiness -Name "diagnostic_state" -Default "") -eq "waiting_for_session" -and + -not [bool](Get-PropertyValue -Item $readiness -Name "ready" -Default $true) -and + [int](Get-PropertyValue -Item $readiness -Name "active_session_count" -Default -1) -eq 0 -and + [int](Get-PropertyValue -Item $readiness -Name "terminal_session_count" -Default -1) -eq 0 -and + $null -eq $lastAdapterSessionID -and + $null -eq $lastSessionState -and + $null -eq $terminalSummary -and + $null -eq $lastPreflight + ) +} + +$entryNode = Get-NodeByName -Name $EntryNodeName +$observed = $null +$deadline = (Get-Date).AddSeconds(60) +while ((Get-Date) -lt $deadline) { + Start-Sleep -Seconds 5 + $observed = Get-RemoteWorkspaceSinkReports -NodeID $entryNode.id + if ( + (Test-TerminalSummaryAbsent -Sink $observed.workload_sink) -and + (Test-TerminalSummaryAbsent -Sink $observed.telemetry_sink) + ) { + break + } +} +if ($null -eq $observed) { + $observed = Get-RemoteWorkspaceSinkReports -NodeID $entryNode.id +} + +$checks = [ordered]@{ + workload_terminal_summary_absent = (Test-TerminalSummaryAbsent -Sink $observed.workload_sink) + telemetry_terminal_summary_absent = (Test-TerminalSummaryAbsent -Sink $observed.telemetry_sink) +} +$failed = @($checks.GetEnumerator() | Where-Object { -not $_.Value } | ForEach-Object { $_.Key }) + +$result = [ordered]@{ + schema_version = "c19z27.remote_workspace_terminal_summary_absence_smoke.v1" + cluster_id = $ClusterID + entry_node = [ordered]@{ id = $entryNode.id; name = $entryNode.name } + observed = $observed + checks = $checks + failed_checks = $failed + passed = ($failed.Count -eq 0) +} + +$fullResultPath = Join-Path $repoRoot $ResultPath +$resultDir = Split-Path -Parent $fullResultPath +if ($resultDir) { New-Item -ItemType Directory -Force -Path $resultDir | Out-Null } +$result | ConvertTo-Json -Depth 100 | Set-Content -Encoding UTF8 -Path $fullResultPath + +if (-not $result.passed) { + throw "C19Z27 remote workspace terminal-summary absence smoke failed. Result: $fullResultPath Failed: $($failed -join ', ')" +} + +Write-Host "C19Z27 remote workspace terminal-summary absence smoke passed. Result: $fullResultPath" +$result diff --git a/scripts/fabric/c19z28-remote-workspace-no-session-summary-smoke.ps1 b/scripts/fabric/c19z28-remote-workspace-no-session-summary-smoke.ps1 new file mode 100644 index 0000000..55c2dcd --- /dev/null +++ b/scripts/fabric/c19z28-remote-workspace-no-session-summary-smoke.ps1 @@ -0,0 +1,135 @@ +param( + [string]$ApiBaseUrl = "http://192.168.200.61:18121/api/v1", + [string]$ClusterID = "cfc0743d-d960-49fb-9de8-96e063d5e4aa", + [string]$ActorUserID = "f67d943f-5397-4b3a-a229-695fe67ad700", + [string]$EntryNodeName = "test-1", + [string]$ResultPath = "artifacts\c19z28-remote-workspace-no-session-summary-smoke-result.json" +) + +Set-StrictMode -Version Latest +$ErrorActionPreference = "Stop" + +$repoRoot = (Resolve-Path (Join-Path $PSScriptRoot "..\..")).ProviderPath + +function Invoke-Api { + param([string]$Method, [string]$Path, [object]$Body = $null) + $uri = "$ApiBaseUrl$Path" + if ($null -eq $Body) { return Invoke-RestMethod -Method $Method -Uri $uri -TimeoutSec 30 } + return Invoke-RestMethod -Method $Method -Uri $uri -ContentType "application/json" -Body ($Body | ConvertTo-Json -Depth 80) -TimeoutSec 30 +} + +function Get-PropertyValue { + param([object]$Item, [string]$Name, [object]$Default = $null) + if ($null -eq $Item) { return $Default } + $property = $Item.PSObject.Properties[$Name] + if ($null -eq $property) { return $Default } + return $property.Value +} + +function Test-ContractItem { + param([object]$Items, [string]$Want) + foreach ($item in @($Items)) { + if ([string]$item -eq $Want) { return $true } + } + return $false +} + +function Get-NodeByName { + param([string]$Name) + $nodes = (Invoke-Api -Method GET -Path "/clusters/$ClusterID/nodes?actor_user_id=$ActorUserID").nodes + $node = @($nodes | Where-Object { $_.name -eq $Name }) | Select-Object -First 1 + if ($null -eq $node) { throw "Node '$Name' was not found in cluster $ClusterID" } + return $node +} + +function Get-RemoteWorkspaceSinkReports { + param([string]$NodeID) + $statuses = (Invoke-Api -Method GET -Path "/clusters/$ClusterID/nodes/$NodeID/workloads/status?actor_user_id=$ActorUserID").workload_statuses + $workloadStatus = @($statuses | Where-Object { $_.service_type -eq "rdp-worker" } | Select-Object -First 1) + $workloadSink = $null + if ($null -ne $workloadStatus) { + $workloadSink = Get-PropertyValue -Item $workloadStatus.status_payload -Name "remote_workspace_adapter_sink" -Default $null + } + $telemetryItems = @((Invoke-Api -Method GET -Path "/clusters/$ClusterID/nodes/$NodeID/telemetry?actor_user_id=$ActorUserID&limit=10").telemetry) + $telemetry = $telemetryItems | Select-Object -First 1 + $telemetrySink = $null + if ($null -ne $telemetry) { + $telemetryPayload = Get-PropertyValue -Item $telemetry -Name "payload" -Default $null + $telemetrySink = Get-PropertyValue -Item $telemetryPayload -Name "remote_workspace_adapter_sink_report" -Default $null + } + return [ordered]@{ + workload_status = $workloadStatus + workload_sink = $workloadSink + telemetry = $telemetry + telemetry_sink = $telemetrySink + } +} + +function Test-NoSessionSummary { + param([object]$Sink) + if ($null -eq $Sink) { return $false } + $readiness = Get-PropertyValue -Item $Sink -Name "adapter_runtime_readiness" -Default $null + if ($null -eq $readiness) { return $false } + $summary = Get-PropertyValue -Item $readiness -Name "no_session_summary" -Default $null + if ($null -eq $summary) { return $false } + $contract = Get-PropertyValue -Item $summary -Name "summary_contract" -Default @() + return ( + [string](Get-PropertyValue -Item $readiness -Name "diagnostic_state" -Default "") -eq "waiting_for_session" -and + [int](Get-PropertyValue -Item $readiness -Name "active_session_count" -Default -1) -eq 0 -and + [int](Get-PropertyValue -Item $readiness -Name "terminal_session_count" -Default -1) -eq 0 -and + [string](Get-PropertyValue -Item $summary -Name "schema_version" -Default "") -eq "rap.remote_workspace_adapter_no_session_summary.v1" -and + (Test-ContractItem -Items $contract -Want "status") -and + (Test-ContractItem -Items $contract -Want "diagnostic_state") -and + (Test-ContractItem -Items $contract -Want "active_session_count") -and + (Test-ContractItem -Items $contract -Want "terminal_session_count") -and + [string](Get-PropertyValue -Item $summary -Name "status" -Default "") -eq "idle" -and + [string](Get-PropertyValue -Item $summary -Name "diagnostic_state" -Default "") -eq "waiting_for_session" -and + [int](Get-PropertyValue -Item $summary -Name "active_session_count" -Default -1) -eq 0 -and + [int](Get-PropertyValue -Item $summary -Name "terminal_session_count" -Default -1) -eq 0 + ) +} + +$entryNode = Get-NodeByName -Name $EntryNodeName +$observed = $null +$deadline = (Get-Date).AddSeconds(60) +while ((Get-Date) -lt $deadline) { + Start-Sleep -Seconds 5 + $observed = Get-RemoteWorkspaceSinkReports -NodeID $entryNode.id + if ( + (Test-NoSessionSummary -Sink $observed.workload_sink) -and + (Test-NoSessionSummary -Sink $observed.telemetry_sink) + ) { + break + } +} +if ($null -eq $observed) { + $observed = Get-RemoteWorkspaceSinkReports -NodeID $entryNode.id +} + +$checks = [ordered]@{ + workload_no_session_summary_visible = (Test-NoSessionSummary -Sink $observed.workload_sink) + telemetry_no_session_summary_visible = (Test-NoSessionSummary -Sink $observed.telemetry_sink) +} +$failed = @($checks.GetEnumerator() | Where-Object { -not $_.Value } | ForEach-Object { $_.Key }) + +$result = [ordered]@{ + schema_version = "c19z28.remote_workspace_no_session_summary_smoke.v1" + cluster_id = $ClusterID + entry_node = [ordered]@{ id = $entryNode.id; name = $entryNode.name } + observed = $observed + checks = $checks + failed_checks = $failed + passed = ($failed.Count -eq 0) +} + +$fullResultPath = Join-Path $repoRoot $ResultPath +$resultDir = Split-Path -Parent $fullResultPath +if ($resultDir) { New-Item -ItemType Directory -Force -Path $resultDir | Out-Null } +$result | ConvertTo-Json -Depth 100 | Set-Content -Encoding UTF8 -Path $fullResultPath + +if (-not $result.passed) { + throw "C19Z28 remote workspace no-session summary smoke failed. Result: $fullResultPath Failed: $($failed -join ', ')" +} + +Write-Host "C19Z28 remote workspace no-session summary smoke passed. Result: $fullResultPath" +$result diff --git a/scripts/fabric/c19z29-remote-workspace-no-session-summary-features-smoke.ps1 b/scripts/fabric/c19z29-remote-workspace-no-session-summary-features-smoke.ps1 new file mode 100644 index 0000000..aac2d18 --- /dev/null +++ b/scripts/fabric/c19z29-remote-workspace-no-session-summary-features-smoke.ps1 @@ -0,0 +1,147 @@ +param( + [string]$ApiBaseUrl = "http://192.168.200.61:18121/api/v1", + [string]$ClusterID = "cfc0743d-d960-49fb-9de8-96e063d5e4aa", + [string]$ActorUserID = "f67d943f-5397-4b3a-a229-695fe67ad700", + [string]$EntryNodeName = "test-1", + [string]$ResultPath = "artifacts\c19z29-remote-workspace-no-session-summary-features-smoke-result.json" +) + +Set-StrictMode -Version Latest +$ErrorActionPreference = "Stop" + +$repoRoot = (Resolve-Path (Join-Path $PSScriptRoot "..\..")).ProviderPath + +function Invoke-Api { + param([string]$Method, [string]$Path, [object]$Body = $null) + $uri = "$ApiBaseUrl$Path" + if ($null -eq $Body) { return Invoke-RestMethod -Method $Method -Uri $uri -TimeoutSec 30 } + return Invoke-RestMethod -Method $Method -Uri $uri -ContentType "application/json" -Body ($Body | ConvertTo-Json -Depth 80) -TimeoutSec 30 +} + +function Get-PropertyValue { + param([object]$Item, [string]$Name, [object]$Default = $null) + if ($null -eq $Item) { return $Default } + $property = $Item.PSObject.Properties[$Name] + if ($null -eq $property) { return $Default } + return $property.Value +} + +function Test-ContractItem { + param([object]$Items, [string]$Want) + foreach ($item in @($Items)) { + if ([string]$item -eq $Want) { return $true } + } + return $false +} + +function Test-FeatureFlag { + param([object]$Features, [string]$Name) + if ($null -eq $Features) { return $false } + $value = Get-PropertyValue -Item $Features -Name $Name -Default $false + return [bool]$value +} + +function Get-NodeByName { + param([string]$Name) + $nodes = (Invoke-Api -Method GET -Path "/clusters/$ClusterID/nodes?actor_user_id=$ActorUserID").nodes + $node = @($nodes | Where-Object { $_.name -eq $Name }) | Select-Object -First 1 + if ($null -eq $node) { throw "Node '$Name' was not found in cluster $ClusterID" } + return $node +} + +function Get-RemoteWorkspaceSinkReports { + param([string]$NodeID) + $statuses = (Invoke-Api -Method GET -Path "/clusters/$ClusterID/nodes/$NodeID/workloads/status?actor_user_id=$ActorUserID").workload_statuses + $workloadStatus = @($statuses | Where-Object { $_.service_type -eq "rdp-worker" } | Select-Object -First 1) + $workloadSink = $null + if ($null -ne $workloadStatus) { + $workloadSink = Get-PropertyValue -Item $workloadStatus.status_payload -Name "remote_workspace_adapter_sink" -Default $null + } + $telemetryItems = @((Invoke-Api -Method GET -Path "/clusters/$ClusterID/nodes/$NodeID/telemetry?actor_user_id=$ActorUserID&limit=10").telemetry) + $telemetry = $telemetryItems | Select-Object -First 1 + $telemetrySink = $null + if ($null -ne $telemetry) { + $telemetryPayload = Get-PropertyValue -Item $telemetry -Name "payload" -Default $null + $telemetrySink = Get-PropertyValue -Item $telemetryPayload -Name "remote_workspace_adapter_sink_report" -Default $null + } + return [ordered]@{ + workload_status = $workloadStatus + workload_sink = $workloadSink + telemetry = $telemetry + telemetry_sink = $telemetrySink + } +} + +function Test-NoSessionSummaryFeatures { + param([object]$Sink) + if ($null -eq $Sink) { return $false } + $readiness = Get-PropertyValue -Item $Sink -Name "adapter_runtime_readiness" -Default $null + if ($null -eq $readiness) { return $false } + $summary = Get-PropertyValue -Item $readiness -Name "no_session_summary" -Default $null + if ($null -eq $summary) { return $false } + $contract = Get-PropertyValue -Item $summary -Name "summary_contract" -Default @() + $features = Get-PropertyValue -Item $summary -Name "summary_features" -Default $null + return ( + [string](Get-PropertyValue -Item $readiness -Name "diagnostic_state" -Default "") -eq "waiting_for_session" -and + [int](Get-PropertyValue -Item $readiness -Name "active_session_count" -Default -1) -eq 0 -and + [int](Get-PropertyValue -Item $readiness -Name "terminal_session_count" -Default -1) -eq 0 -and + [string](Get-PropertyValue -Item $summary -Name "schema_version" -Default "") -eq "rap.remote_workspace_adapter_no_session_summary.v1" -and + (Test-ContractItem -Items $contract -Want "status") -and + (Test-ContractItem -Items $contract -Want "diagnostic_state") -and + (Test-ContractItem -Items $contract -Want "active_session_count") -and + (Test-ContractItem -Items $contract -Want "terminal_session_count") -and + (Test-FeatureFlag -Features $features -Name "status") -and + (Test-FeatureFlag -Features $features -Name "diagnostic_state") -and + (Test-FeatureFlag -Features $features -Name "active_session_count") -and + (Test-FeatureFlag -Features $features -Name "terminal_session_count") -and + [string](Get-PropertyValue -Item $summary -Name "status" -Default "") -eq "idle" -and + [string](Get-PropertyValue -Item $summary -Name "diagnostic_state" -Default "") -eq "waiting_for_session" -and + [int](Get-PropertyValue -Item $summary -Name "active_session_count" -Default -1) -eq 0 -and + [int](Get-PropertyValue -Item $summary -Name "terminal_session_count" -Default -1) -eq 0 + ) +} + +$entryNode = Get-NodeByName -Name $EntryNodeName +$observed = $null +$deadline = (Get-Date).AddSeconds(60) +while ((Get-Date) -lt $deadline) { + Start-Sleep -Seconds 5 + $observed = Get-RemoteWorkspaceSinkReports -NodeID $entryNode.id + if ( + (Test-NoSessionSummaryFeatures -Sink $observed.workload_sink) -and + (Test-NoSessionSummaryFeatures -Sink $observed.telemetry_sink) + ) { + break + } +} +if ($null -eq $observed) { + $observed = Get-RemoteWorkspaceSinkReports -NodeID $entryNode.id +} + +$checks = [ordered]@{ + workload_no_session_summary_features_visible = (Test-NoSessionSummaryFeatures -Sink $observed.workload_sink) + telemetry_no_session_summary_features_visible = (Test-NoSessionSummaryFeatures -Sink $observed.telemetry_sink) +} +$failed = @($checks.GetEnumerator() | Where-Object { -not $_.Value } | ForEach-Object { $_.Key }) + +$result = [ordered]@{ + schema_version = "c19z29.remote_workspace_no_session_summary_features_smoke.v1" + cluster_id = $ClusterID + entry_node = [ordered]@{ id = $entryNode.id; name = $entryNode.name } + observed = $observed + checks = $checks + failed_checks = $failed + passed = ($failed.Count -eq 0) +} + +$fullResultPath = Join-Path $repoRoot $ResultPath +$resultDir = Split-Path -Parent $fullResultPath +if ($resultDir) { New-Item -ItemType Directory -Force -Path $resultDir | Out-Null } +$result | ConvertTo-Json -Depth 100 | Set-Content -Encoding UTF8 -Path $fullResultPath + +if (-not $result.passed) { + throw "C19Z29 remote workspace no-session summary features smoke failed. Result: $fullResultPath Failed: $($failed -join ', ')" +} + +Write-Host "C19Z29 remote workspace no-session summary features smoke passed. Result: $fullResultPath" +$result diff --git a/scripts/fabric/c19z3-remote-workspace-mailbox-stale-preflight-smoke.ps1 b/scripts/fabric/c19z3-remote-workspace-mailbox-stale-preflight-smoke.ps1 new file mode 100644 index 0000000..651e9fa --- /dev/null +++ b/scripts/fabric/c19z3-remote-workspace-mailbox-stale-preflight-smoke.ps1 @@ -0,0 +1,244 @@ +param( + [string]$ApiBaseUrl = "http://192.168.200.61:18121/api/v1", + [string]$ClusterID = "cfc0743d-d960-49fb-9de8-96e063d5e4aa", + [string]$ActorUserID = "f67d943f-5397-4b3a-a229-695fe67ad700", + [string]$EntryNodeName = "test-1", + [string]$ExitNodeName = "test-2", + [string]$EntryBaseUrl = "http://192.168.200.61:19131", + [string]$ResultPath = "artifacts\c19z3-remote-workspace-mailbox-stale-preflight-smoke-result.json", + [switch]$SkipClose +) + +Set-StrictMode -Version Latest +$ErrorActionPreference = "Stop" + +$repoRoot = (Resolve-Path (Join-Path $PSScriptRoot "..\..")).ProviderPath +$runId = "c19z3-" + (Get-Date -Format "yyyyMMdd-HHmmss") +$consumerID = "rdp-worker-probe" +$routeID = "" +$adapterSessionID = "" + +function Invoke-Api { + param([string]$Method, [string]$Path, [object]$Body = $null) + $uri = "$ApiBaseUrl$Path" + if ($null -eq $Body) { return Invoke-RestMethod -Method $Method -Uri $uri -TimeoutSec 30 } + return Invoke-RestMethod -Method $Method -Uri $uri -ContentType "application/json" -Body ($Body | ConvertTo-Json -Depth 80) -TimeoutSec 30 +} + +function Get-PropertyValue { + param([object]$Item, [string]$Name, [object]$Default = $null) + if ($null -eq $Item) { return $Default } + $property = $Item.PSObject.Properties[$Name] + if ($null -eq $property) { return $Default } + return $property.Value +} + +function ConvertTo-Base64UrlJson { + param([object]$Value) + if ($Value -is [string]) { $json = $Value } else { $json = $Value | ConvertTo-Json -Depth 80 -Compress } + $bytes = [System.Text.Encoding]::UTF8.GetBytes($json) + return [Convert]::ToBase64String($bytes).TrimEnd("=").Replace("+", "-").Replace("/", "_") +} + +function Get-Events { + param([object]$Item) + $events = Get-PropertyValue -Item $Item -Name "events" -Default $null + if ($null -eq $events) { return @() } + return @($events) +} + +function Get-NodeByName { + param([string]$Name) + $nodes = (Invoke-Api -Method GET -Path "/clusters/$ClusterID/nodes?actor_user_id=$ActorUserID").nodes + $node = @($nodes | Where-Object { $_.name -eq $Name }) | Select-Object -First 1 + if ($null -eq $node) { throw "Node '$Name' was not found in cluster $ClusterID" } + return $node +} + +function Disable-ExistingRemoteWorkspaceRoutes { + param([string]$SourceNodeID, [string]$DestinationNodeID) + $items = (Invoke-Api -Method GET -Path "/clusters/$ClusterID/mesh/route-intents?actor_user_id=$ActorUserID").route_intents + foreach ($item in @($items)) { + if ([string](Get-PropertyValue -Item $item -Name "status" -Default "") -ne "active") { continue } + if ([string](Get-PropertyValue -Item $item -Name "service_class" -Default "") -ne "remote_workspace") { continue } + $sourceSelector = Get-PropertyValue -Item $item -Name "source_selector" -Default $null + $destinationSelector = Get-PropertyValue -Item $item -Name "destination_selector" -Default $null + if ([string](Get-PropertyValue -Item $sourceSelector -Name "node_id" -Default "") -ne $SourceNodeID) { continue } + if ([string](Get-PropertyValue -Item $destinationSelector -Name "node_id" -Default "") -ne $DestinationNodeID) { continue } + [void](Invoke-Api -Method POST -Path "/clusters/$ClusterID/mesh/route-intents/$($item.id)/disable" -Body @{ + actor_user_id = $ActorUserID + reason = "c19z3 isolate stale mailbox preflight smoke" + }) + } +} + +function New-RemoteWorkspaceRouteIntent { + param([string]$SourceNodeID, [string]$DestinationNodeID) + $expiresAt = (Get-Date).ToUniversalTime().AddMinutes(5).ToString("o") + return Invoke-Api -Method POST -Path "/clusters/$ClusterID/mesh/route-intents" -Body @{ + actor_user_id = $ActorUserID + source_selector = @{ node_id = $SourceNodeID } + destination_selector = @{ node_id = $DestinationNodeID } + service_class = "remote_workspace" + priority = 2100000000 + policy = @{ + synthetic_enabled = $true + route_version = "$runId-remote-workspace" + policy_version = "$runId-remote-workspace" + peer_directory_version = "$runId-remote-workspace" + hops = @($SourceNodeID, $DestinationNodeID) + allowed_channels = @("control", "interactive", "reliable", "bulk", "droppable") + max_ttl = 8 + max_hops = 8 + expires_at = $expiresAt + metadata = @{ smoke = "c19z3_remote_workspace_mailbox_stale_preflight"; run_id = $runId } + } + } +} + +function New-FrameBatch { + param([int]$Index) + return [ordered]@{ + schema_version = "rap.remote_workspace_frame_batch.v1" + probe_only = $true + service_class = "remote_workspace" + channel_class = "interactive" + adapter_contract_id = "rap.rdp_worker.remote_workspace_adapter_contract_probe.v1" + frames = @(@{ channel = "display"; direction = "adapter_to_client"; payload_encoding = "none"; payload_length = $Index; droppable = $true }) + } +} + +function Invoke-FrameBatch { + param([object]$FrameBatch, [hashtable]$Headers, [string]$Url) + $response = Invoke-WebRequest -Method POST -Uri $Url -Headers $Headers -Body ($FrameBatch | ConvertTo-Json -Depth 80 -Compress) -ContentType "application/vnd.rap.remote-workspace-frame-batch.v1+json" -TimeoutSec 30 + return [ordered]@{ status_code = [int]$response.StatusCode; body = ($response.Content | ConvertFrom-Json) } +} + +function Invoke-Mailbox { + param([string]$SessionID, [string]$Query = "") + $url = "$EntryBaseUrl/mesh/v1/remote-workspace/adapter-sessions/$SessionID/mailbox$Query" + $response = Invoke-WebRequest -Method GET -Uri $url -TimeoutSec 35 + $json = $null + if ($response.Content) { $json = $response.Content | ConvertFrom-Json } + return [ordered]@{ status_code = [int]$response.StatusCode; body = $response.Content; json = $json } +} + +function Invoke-Preflight { + param([string]$SessionID, [string]$Query = "") + $url = "$EntryBaseUrl/mesh/v1/remote-workspace/adapter-sessions/$SessionID/mailbox/preflight$Query" + $response = Invoke-WebRequest -Method GET -Uri $url -TimeoutSec 35 + $json = $null + if ($response.Content) { $json = $response.Content | ConvertFrom-Json } + return [ordered]@{ status_code = [int]$response.StatusCode; body = $response.Content; json = $json } +} + +function Invoke-Control { + param([string]$SessionID) + if ([string]::IsNullOrWhiteSpace($SessionID)) { return $null } + $url = "$EntryBaseUrl/mesh/v1/remote-workspace/adapter-sessions/$SessionID/control" + $body = @{ action = "close"; reason = "c19z3 stale mailbox preflight close" } | ConvertTo-Json -Compress + return Invoke-RestMethod -Method POST -Uri $url -ContentType "application/json" -Body $body -TimeoutSec 30 +} + +try { + $entryNode = Get-NodeByName -Name $EntryNodeName + $exitNode = Get-NodeByName -Name $ExitNodeName + Disable-ExistingRemoteWorkspaceRoutes -SourceNodeID $entryNode.id -DestinationNodeID $exitNode.id + $route = (New-RemoteWorkspaceRouteIntent -SourceNodeID $entryNode.id -DestinationNodeID $exitNode.id).route_intent + $routeID = [string]$route.id + + $leaseResponse = Invoke-Api -Method POST -Path "/clusters/$ClusterID/fabric/service-channels/leases" -Body @{ + actor_user_id = $ActorUserID + organization_id = "org-home" + user_id = "user-x" + resource_id = "$runId-remote-workspace" + service_class = "remote_workspace" + entry_node_ids = @([string]$entryNode.id) + exit_node_ids = @([string]$exitNode.id) + preferred_entry_node_id = [string]$entryNode.id + preferred_exit_node_id = [string]$exitNode.id + ttl_seconds = 120 + metadata = @{ smoke = "c19z3_remote_workspace_mailbox_stale_preflight"; run_id = $runId } + } + $lease = $leaseResponse.fabric_service_channel_lease + $authorityPayload = Get-PropertyValue -Item $lease -Name "authority_payload" -Default $null + if ($authorityPayload -is [string] -and $authorityPayload.Length -gt 0) { $decodedAuthority = $authorityPayload | ConvertFrom-Json } else { $decodedAuthority = $authorityPayload } + + $ingressUrl = "$EntryBaseUrl/api/v1/clusters/$ClusterID/fabric/service-channels/$($lease.channel_id)/remote-workspaces/$($lease.resource_id)/streams/interactive" + $headers = @{ + "X-RAP-Service-Channel-Token" = [string]$lease.token.token + "X-RAP-Fabric-Channel-ID" = [string]$lease.channel_id + "X-RAP-Service-Channel-Authority-Payload" = ConvertTo-Base64UrlJson -Value $decodedAuthority + "X-RAP-Service-Channel-Authority-Signature" = ConvertTo-Base64UrlJson -Value (Get-PropertyValue -Item $lease -Name "authority_signature" -Default $null) + "X-RAP-Service-Class" = "remote_workspace" + "X-RAP-Channel-Class" = "interactive" + } + + $firstDelivery = Invoke-FrameBatch -FrameBatch (New-FrameBatch -Index 1) -Headers $headers -Url $ingressUrl + $adapterSessionID = [string](Get-PropertyValue -Item $firstDelivery.body -Name "adapter_session_id" -Default "") + $seed = Invoke-Mailbox -SessionID $adapterSessionID -Query "?consumer_id=$consumerID&ack_sequence=1&limit=1" + $overflowDeliveries = @() + for ($i = 2; $i -le 19; $i++) { + $overflowDeliveries += ,(Invoke-FrameBatch -FrameBatch (New-FrameBatch -Index $i) -Headers $headers -Url $ingressUrl) + } + $preflight = Invoke-Preflight -SessionID $adapterSessionID -Query "?consumer_id=$consumerID&resume_from=ack&limit=3" + $control = $null + if (-not $SkipClose) { + $control = Invoke-Control -SessionID $adapterSessionID + } + + $checks = [ordered]@{ + lease_ready = ([string]$lease.status -eq "ready") + initial_delivery_accepted = ([int]$firstDelivery.status_code -eq 202 -and $adapterSessionID -match "^rap-rw-adapter-session-[0-9a-f]{24}$") + seed_ack_visible = ([int]$seed.status_code -eq 200 -and [int64]$seed.json.consumer_ack_sequence -eq 1) + overflow_deliveries_accepted = (@($overflowDeliveries | Where-Object { [int]$_.status_code -eq 202 }).Count -eq 18) + stale_preflight_visible = ([int]$preflight.status_code -eq 200 -and [bool]$preflight.json.read_only -and [bool]$preflight.json.stale_cursor -and [string]$preflight.json.diagnostic_state -eq "stale_cursor_gap" -and [string]$preflight.json.recommended_action -eq "reset_consumer_and_resync" -and @($preflight.json.action_hints).Contains("reset_consumer_cursor") -and @($preflight.json.action_hints).Contains("request_full_adapter_resync") -and @($preflight.json.action_hints).Contains("resume_from_checkpoint_after_resync") -and [int64]$preflight.json.resume_sequence -eq 1 -and [int]$preflight.json.mailbox_depth -eq 16 -and [int64]$preflight.json.mailbox_dropped_total -ge 3 -and [int64]$preflight.json.first_retained_sequence -gt [int64]$preflight.json.resume_sequence -and [int]$preflight.json.missing_dropped_count -eq ([int64]$preflight.json.first_retained_sequence - [int64]$preflight.json.resume_sequence - 1) -and [int64]$preflight.json.last_retained_sequence -ge [int64]$preflight.json.first_retained_sequence -and [int]$preflight.json.expected_returned_count -eq 3) + close_accepted = ($SkipClose -or ([bool]$control.accepted -and [string]$control.session_state -eq "closed")) + } + $failed = @($checks.GetEnumerator() | Where-Object { -not $_.Value } | ForEach-Object { $_.Key }) + + $result = [ordered]@{ + schema_version = "c19z3.remote_workspace_mailbox_stale_preflight_smoke.v1" + run_id = $runId + cluster_id = $ClusterID + entry_node = @{ id = $entryNode.id; name = $EntryNodeName } + exit_node = @{ id = $exitNode.id; name = $ExitNodeName } + channel_id = $lease.channel_id + route_id = $routeID + adapter_session_id = $adapterSessionID + first_delivery = $firstDelivery + seed = $seed + overflow_delivery_count = $overflowDeliveries.Count + preflight = $preflight + control = $control + checks = $checks + failed_checks = $failed + passed = ($failed.Count -eq 0) + } +} finally { + if ($adapterSessionID -and -not $SkipClose) { + try { [void](Invoke-Control -SessionID $adapterSessionID) } catch {} + } + if ($routeID) { + try { + [void](Invoke-Api -Method POST -Path "/clusters/$ClusterID/mesh/route-intents/$routeID/disable" -Body @{ + actor_user_id = $ActorUserID + reason = "c19z3 stale mailbox preflight cleanup" + }) + } catch { + Write-Warning "cleanup failed after c19z3 smoke: $($_.Exception.Message)" + } + } +} + +$fullResultPath = Join-Path $repoRoot $ResultPath +$resultDir = Split-Path -Parent $fullResultPath +if ($resultDir) { New-Item -ItemType Directory -Force -Path $resultDir | Out-Null } +$result | ConvertTo-Json -Depth 100 | Set-Content -Encoding UTF8 -Path $fullResultPath + +if (-not $result.passed) { + throw "C19Z3 remote workspace mailbox stale preflight smoke failed. Result: $fullResultPath Failed: $($failed -join ', ')" +} + +Write-Host "C19Z3 remote workspace mailbox stale preflight smoke passed. Result: $fullResultPath" +$result diff --git a/scripts/fabric/c19z30-remote-workspace-no-session-summary-compatibility-smoke.ps1 b/scripts/fabric/c19z30-remote-workspace-no-session-summary-compatibility-smoke.ps1 new file mode 100644 index 0000000..27137d8 --- /dev/null +++ b/scripts/fabric/c19z30-remote-workspace-no-session-summary-compatibility-smoke.ps1 @@ -0,0 +1,96 @@ +param( + [string]$ApiBaseUrl = "http://192.168.200.61:18121/api/v1", + [string]$ClusterID = "cfc0743d-d960-49fb-9de8-96e063d5e4aa", + [string]$ActorUserID = "f67d943f-5397-4b3a-a229-695fe67ad700", + [string]$EntryNodeName = "test-1", + [string]$ResultPath = "artifacts\c19z30-remote-workspace-no-session-summary-compatibility-smoke-result.json" +) + +Set-StrictMode -Version Latest +$ErrorActionPreference = "Stop" + +$repoRoot = (Resolve-Path (Join-Path $PSScriptRoot "..\..")).ProviderPath +$sourceResultPath = "artifacts\c19z30-remote-workspace-no-session-summary-compatibility-source-result.json" +$requiredFeatures = @("status", "diagnostic_state", "active_session_count", "terminal_session_count") + +function Get-PropertyValue { + param([object]$Item, [string]$Name, [object]$Default = $null) + if ($null -eq $Item) { return $Default } + $property = $Item.PSObject.Properties[$Name] + if ($null -eq $property) { return $Default } + return $property.Value +} + +function Test-ContractItem { + param([object]$Items, [string]$Want) + foreach ($item in @($Items)) { + if ([string]$item -eq $Want) { return $true } + } + return $false +} + +function Test-FeatureFlag { + param([object]$Features, [string]$Name) + return ([bool](Get-PropertyValue -Item $Features -Name $Name -Default $false)) +} + +function Test-NoSessionSummaryCompatibility { + param([object]$Sink) + if ($null -eq $Sink) { return $false } + $readiness = Get-PropertyValue -Item $Sink -Name "adapter_runtime_readiness" -Default $null + $summary = Get-PropertyValue -Item $readiness -Name "no_session_summary" -Default $null + if ($null -eq $summary) { return $false } + if ([string](Get-PropertyValue -Item $summary -Name "schema_version" -Default "") -ne "rap.remote_workspace_adapter_no_session_summary.v1") { return $false } + if ([string](Get-PropertyValue -Item $summary -Name "status" -Default "") -ne "idle") { return $false } + if ([string](Get-PropertyValue -Item $summary -Name "diagnostic_state" -Default "") -ne "waiting_for_session") { return $false } + if ([int](Get-PropertyValue -Item $summary -Name "active_session_count" -Default -1) -ne 0) { return $false } + if ([int](Get-PropertyValue -Item $summary -Name "terminal_session_count" -Default -1) -ne 0) { return $false } + $contract = Get-PropertyValue -Item $summary -Name "summary_contract" -Default @() + $features = Get-PropertyValue -Item $summary -Name "summary_features" -Default $null + foreach ($feature in $requiredFeatures) { + if (-not (Test-ContractItem -Items $contract -Want $feature)) { return $false } + if (-not (Test-FeatureFlag -Features $features -Name $feature)) { return $false } + } + return $true +} + +$source = & powershell -ExecutionPolicy Bypass -File (Join-Path $PSScriptRoot "c19z29-remote-workspace-no-session-summary-features-smoke.ps1") ` + -ApiBaseUrl $ApiBaseUrl ` + -ClusterID $ClusterID ` + -ActorUserID $ActorUserID ` + -EntryNodeName $EntryNodeName ` + -ResultPath $sourceResultPath + +$sourceFile = Join-Path $repoRoot $sourceResultPath +$sourceResult = Get-Content -Raw -Path $sourceFile | ConvertFrom-Json +$observed = Get-PropertyValue -Item $sourceResult -Name "observed" -Default $null + +$checks = [ordered]@{ + source_smoke_passed = ([bool]$sourceResult.passed) + workload_no_session_summary_compatibility = (Test-NoSessionSummaryCompatibility -Sink $observed.workload_sink) + telemetry_no_session_summary_compatibility = (Test-NoSessionSummaryCompatibility -Sink $observed.telemetry_sink) +} +$failed = @($checks.GetEnumerator() | Where-Object { -not $_.Value } | ForEach-Object { $_.Key }) + +$result = [ordered]@{ + schema_version = "c19z30.remote_workspace_no_session_summary_compatibility_smoke.v1" + source_result_path = $sourceFile + cluster_id = $ClusterID + required_features = $requiredFeatures + source = $sourceResult + checks = $checks + failed_checks = $failed + passed = ($failed.Count -eq 0) +} + +$fullResultPath = Join-Path $repoRoot $ResultPath +$resultDir = Split-Path -Parent $fullResultPath +if ($resultDir) { New-Item -ItemType Directory -Force -Path $resultDir | Out-Null } +$result | ConvertTo-Json -Depth 100 | Set-Content -Encoding UTF8 -Path $fullResultPath + +if (-not $result.passed) { + throw "C19Z30 remote workspace no-session summary compatibility smoke failed. Result: $fullResultPath Failed: $($failed -join ', ')" +} + +Write-Host "C19Z30 remote workspace no-session summary compatibility smoke passed. Result: $fullResultPath" +$result diff --git a/scripts/fabric/c19z31-remote-workspace-terminal-history-no-session-summary-absence-smoke.ps1 b/scripts/fabric/c19z31-remote-workspace-terminal-history-no-session-summary-absence-smoke.ps1 new file mode 100644 index 0000000..8d53fd2 --- /dev/null +++ b/scripts/fabric/c19z31-remote-workspace-terminal-history-no-session-summary-absence-smoke.ps1 @@ -0,0 +1,97 @@ +param( + [string]$ApiBaseUrl = "http://192.168.200.61:18121/api/v1", + [string]$ClusterID = "cfc0743d-d960-49fb-9de8-96e063d5e4aa", + [string]$ActorUserID = "f67d943f-5397-4b3a-a229-695fe67ad700", + [string]$EntryNodeName = "test-1", + [string]$ExitNodeName = "test-2", + [string]$EntryBaseUrl = "http://192.168.200.61:19131", + [string]$ResultPath = "artifacts\c19z31-remote-workspace-terminal-history-no-session-summary-absence-smoke-result.json" +) + +Set-StrictMode -Version Latest +$ErrorActionPreference = "Stop" + +$repoRoot = (Resolve-Path (Join-Path $PSScriptRoot "..\..")).ProviderPath +$sourceResultPath = "artifacts\c19z31-remote-workspace-terminal-history-no-session-summary-absence-source-result.json" + +function Get-PropertyValue { + param([object]$Item, [string]$Name, [object]$Default = $null) + if ($null -eq $Item) { return $Default } + $property = $Item.PSObject.Properties[$Name] + if ($null -eq $property) { return $Default } + return $property.Value +} + +function Test-TerminalHistoryNoSessionSummaryAbsent { + param([object]$Sink, [string]$AdapterSessionID, [string]$TerminalState) + if ($null -eq $Sink) { return $false } + $readiness = Get-PropertyValue -Item $Sink -Name "adapter_runtime_readiness" -Default $null + if ($null -eq $readiness) { return $false } + $terminalSummary = Get-PropertyValue -Item $readiness -Name "terminal_session_summary" -Default $null + $noSessionSummary = Get-PropertyValue -Item $readiness -Name "no_session_summary" -Default $null + if ($null -eq $terminalSummary) { return $false } + return ( + [string](Get-PropertyValue -Item $readiness -Name "schema_version" -Default "") -eq "rap.remote_workspace_adapter_runtime_readiness.v1" -and + [string](Get-PropertyValue -Item $readiness -Name "status" -Default "") -eq "idle" -and + [string](Get-PropertyValue -Item $readiness -Name "diagnostic_state" -Default "") -eq "last_session_terminal_or_expired" -and + [string](Get-PropertyValue -Item $readiness -Name "last_adapter_session_id" -Default "") -eq $AdapterSessionID -and + [string](Get-PropertyValue -Item $readiness -Name "last_session_state" -Default "") -eq $TerminalState -and + [string](Get-PropertyValue -Item $terminalSummary -Name "adapter_session_id" -Default "") -eq $AdapterSessionID -and + [string](Get-PropertyValue -Item $terminalSummary -Name "session_state" -Default "") -eq $TerminalState -and + $null -eq $noSessionSummary + ) +} + +$source = & powershell -ExecutionPolicy Bypass -File (Join-Path $PSScriptRoot "c19z23-remote-workspace-terminal-session-summary-smoke.ps1") ` + -ApiBaseUrl $ApiBaseUrl ` + -ClusterID $ClusterID ` + -ActorUserID $ActorUserID ` + -EntryNodeName $EntryNodeName ` + -ExitNodeName $ExitNodeName ` + -EntryBaseUrl $EntryBaseUrl ` + -ResultPath $sourceResultPath + +$sourceFile = Join-Path $repoRoot $sourceResultPath +$sourceResult = Get-Content -Raw -Path $sourceFile | ConvertFrom-Json +$sourceC19Z22 = Get-PropertyValue -Item $sourceResult -Name "source" -Default $null +$expireProbe = Get-PropertyValue -Item $sourceC19Z22 -Name "expire_probe" -Default $null +$resetProbe = Get-PropertyValue -Item $sourceC19Z22 -Name "reset_probe" -Default $null +$expireObserved = Get-PropertyValue -Item $expireProbe -Name "observed" -Default $null +$resetObserved = Get-PropertyValue -Item $resetProbe -Name "observed" -Default $null +$expireSessionID = [string](Get-PropertyValue -Item $sourceResult -Name "expire_adapter_session_id" -Default "") +$resetSessionID = [string](Get-PropertyValue -Item $sourceResult -Name "reset_adapter_session_id" -Default "") + +$checks = [ordered]@{ + source_smoke_passed = ([bool]$sourceResult.passed) + expire_adapter_session_id_present = ($expireSessionID -match "^rap-rw-adapter-session-[0-9a-f]{24}$") + reset_adapter_session_id_present = ($resetSessionID -match "^rap-rw-adapter-session-[0-9a-f]{24}$") + workload_expire_no_session_summary_absent = (Test-TerminalHistoryNoSessionSummaryAbsent -Sink $expireObserved.workload_sink -AdapterSessionID $expireSessionID -TerminalState "expired") + telemetry_expire_no_session_summary_absent = (Test-TerminalHistoryNoSessionSummaryAbsent -Sink $expireObserved.telemetry_sink -AdapterSessionID $expireSessionID -TerminalState "expired") + workload_reset_no_session_summary_absent = (Test-TerminalHistoryNoSessionSummaryAbsent -Sink $resetObserved.workload_sink -AdapterSessionID $resetSessionID -TerminalState "reset") + telemetry_reset_no_session_summary_absent = (Test-TerminalHistoryNoSessionSummaryAbsent -Sink $resetObserved.telemetry_sink -AdapterSessionID $resetSessionID -TerminalState "reset") +} +$failed = @($checks.GetEnumerator() | Where-Object { -not $_.Value } | ForEach-Object { $_.Key }) + +$result = [ordered]@{ + schema_version = "c19z31.remote_workspace_terminal_history_no_session_summary_absence_smoke.v1" + source_result_path = $sourceFile + cluster_id = $ClusterID + expire_adapter_session_id = $expireSessionID + reset_adapter_session_id = $resetSessionID + source = $sourceResult + checks = $checks + failed_checks = $failed + passed = ($failed.Count -eq 0) +} + +$fullResultPath = Join-Path $repoRoot $ResultPath +$resultDir = Split-Path -Parent $fullResultPath +if ($resultDir) { New-Item -ItemType Directory -Force -Path $resultDir | Out-Null } +$result | ConvertTo-Json -Depth 100 | Set-Content -Encoding UTF8 -Path $fullResultPath + +if (-not $result.passed) { + throw "C19Z31 remote workspace terminal-history no-session-summary absence smoke failed. Result: $fullResultPath Failed: $($failed -join ', ')" +} + +Write-Host "C19Z31 remote workspace terminal-history no-session-summary absence smoke passed. Result: $fullResultPath" +$result diff --git a/scripts/fabric/c19z32-remote-workspace-readiness-summary-exclusivity-smoke.ps1 b/scripts/fabric/c19z32-remote-workspace-readiness-summary-exclusivity-smoke.ps1 new file mode 100644 index 0000000..8c1f9c9 --- /dev/null +++ b/scripts/fabric/c19z32-remote-workspace-readiness-summary-exclusivity-smoke.ps1 @@ -0,0 +1,231 @@ +param( + [string]$ApiBaseUrl = "http://192.168.200.61:18121/api/v1", + [string]$ClusterID = "cfc0743d-d960-49fb-9de8-96e063d5e4aa", + [string]$ActorUserID = "f67d943f-5397-4b3a-a229-695fe67ad700", + [string]$EntryNodeName = "test-1", + [string]$ExitNodeName = "test-2", + [string]$EntryBaseUrl = "http://192.168.200.61:19131", + [string]$ResultPath = "artifacts\c19z32-remote-workspace-readiness-summary-exclusivity-smoke-result.json" +) + +Set-StrictMode -Version Latest +$ErrorActionPreference = "Stop" + +$repoRoot = (Resolve-Path (Join-Path $PSScriptRoot "..\..")).ProviderPath +$freshSourceResultPath = "artifacts\c19z32-remote-workspace-readiness-summary-exclusivity-fresh-source-result.json" +$activeSourceResultPath = "artifacts\c19z32-remote-workspace-readiness-summary-exclusivity-active-source-result.json" +$adapterSessionID = "" + +function Invoke-Api { + param([string]$Method, [string]$Path, [object]$Body = $null) + $uri = "$ApiBaseUrl$Path" + if ($null -eq $Body) { return Invoke-RestMethod -Method $Method -Uri $uri -TimeoutSec 30 } + return Invoke-RestMethod -Method $Method -Uri $uri -ContentType "application/json" -Body ($Body | ConvertTo-Json -Depth 80) -TimeoutSec 30 +} + +function Get-PropertyValue { + param([object]$Item, [string]$Name, [object]$Default = $null) + if ($null -eq $Item) { return $Default } + $property = $Item.PSObject.Properties[$Name] + if ($null -eq $property) { return $Default } + return $property.Value +} + +function Get-NodeByName { + param([string]$Name) + $nodes = (Invoke-Api -Method GET -Path "/clusters/$ClusterID/nodes?actor_user_id=$ActorUserID").nodes + $node = @($nodes | Where-Object { $_.name -eq $Name }) | Select-Object -First 1 + if ($null -eq $node) { throw "Node '$Name' was not found in cluster $ClusterID" } + return $node +} + +function Get-RemoteWorkspaceSinkReports { + param([string]$NodeID) + $statuses = (Invoke-Api -Method GET -Path "/clusters/$ClusterID/nodes/$NodeID/workloads/status?actor_user_id=$ActorUserID").workload_statuses + $workloadStatus = @($statuses | Where-Object { $_.service_type -eq "rdp-worker" } | Select-Object -First 1) + $workloadSink = $null + if ($null -ne $workloadStatus) { + $workloadSink = Get-PropertyValue -Item $workloadStatus.status_payload -Name "remote_workspace_adapter_sink" -Default $null + } + $telemetryItems = @((Invoke-Api -Method GET -Path "/clusters/$ClusterID/nodes/$NodeID/telemetry?actor_user_id=$ActorUserID&limit=10").telemetry) + $telemetry = $telemetryItems | Select-Object -First 1 + $telemetrySink = $null + if ($null -ne $telemetry) { + $telemetryPayload = Get-PropertyValue -Item $telemetry -Name "payload" -Default $null + $telemetrySink = Get-PropertyValue -Item $telemetryPayload -Name "remote_workspace_adapter_sink_report" -Default $null + } + return [ordered]@{ + workload_status = $workloadStatus + workload_sink = $workloadSink + telemetry = $telemetry + telemetry_sink = $telemetrySink + } +} + +function Invoke-Control { + param([string]$SessionID) + if ([string]::IsNullOrWhiteSpace($SessionID)) { return $null } + $url = "$EntryBaseUrl/mesh/v1/remote-workspace/adapter-sessions/$SessionID/control" + $body = @{ action = "close"; reason = "c19z32 readiness summary exclusivity close" } | ConvertTo-Json -Compress + return Invoke-RestMethod -Method POST -Uri $url -ContentType "application/json" -Body $body -TimeoutSec 30 +} + +function Test-FreshExclusivity { + param([object]$Sink) + if ($null -eq $Sink) { return $false } + $readiness = Get-PropertyValue -Item $Sink -Name "adapter_runtime_readiness" -Default $null + if ($null -eq $readiness) { return $false } + $noSessionSummary = Get-PropertyValue -Item $readiness -Name "no_session_summary" -Default $null + $terminalSummary = Get-PropertyValue -Item $readiness -Name "terminal_session_summary" -Default $null + return ( + [string](Get-PropertyValue -Item $readiness -Name "diagnostic_state" -Default "") -eq "waiting_for_session" -and + [int](Get-PropertyValue -Item $readiness -Name "active_session_count" -Default -1) -eq 0 -and + [int](Get-PropertyValue -Item $readiness -Name "terminal_session_count" -Default -1) -eq 0 -and + $null -ne $noSessionSummary -and + $null -eq $terminalSummary + ) +} + +function Test-ActiveExclusivity { + param([object]$Sink, [string]$SessionID) + if ($null -eq $Sink) { return $false } + $readiness = Get-PropertyValue -Item $Sink -Name "adapter_runtime_readiness" -Default $null + if ($null -eq $readiness) { return $false } + $noSessionSummary = Get-PropertyValue -Item $readiness -Name "no_session_summary" -Default $null + $terminalSummary = Get-PropertyValue -Item $readiness -Name "terminal_session_summary" -Default $null + return ( + [string](Get-PropertyValue -Item $readiness -Name "adapter_session_id" -Default "") -eq $SessionID -and + [int](Get-PropertyValue -Item $readiness -Name "active_session_count" -Default -1) -eq 1 -and + $null -eq $noSessionSummary -and + $null -eq $terminalSummary + ) +} + +function Test-TerminalExclusivity { + param([object]$Sink, [string]$SessionID) + if ($null -eq $Sink) { return $false } + $readiness = Get-PropertyValue -Item $Sink -Name "adapter_runtime_readiness" -Default $null + if ($null -eq $readiness) { return $false } + $noSessionSummary = Get-PropertyValue -Item $readiness -Name "no_session_summary" -Default $null + $terminalSummary = Get-PropertyValue -Item $readiness -Name "terminal_session_summary" -Default $null + return ( + [string](Get-PropertyValue -Item $readiness -Name "diagnostic_state" -Default "") -eq "last_session_terminal_or_expired" -and + [string](Get-PropertyValue -Item $readiness -Name "last_adapter_session_id" -Default "") -eq $SessionID -and + [string](Get-PropertyValue -Item $readiness -Name "last_session_state" -Default "") -eq "closed" -and + $null -eq $noSessionSummary -and + $null -ne $terminalSummary -and + [string](Get-PropertyValue -Item $terminalSummary -Name "adapter_session_id" -Default "") -eq $SessionID + ) +} + +$entryNode = Get-NodeByName -Name $EntryNodeName + +$freshSource = & powershell -ExecutionPolicy Bypass -File (Join-Path $PSScriptRoot "c19z29-remote-workspace-no-session-summary-features-smoke.ps1") ` + -ApiBaseUrl $ApiBaseUrl ` + -ClusterID $ClusterID ` + -ActorUserID $ActorUserID ` + -EntryNodeName $EntryNodeName ` + -ResultPath $freshSourceResultPath +$freshSourceFile = Join-Path $repoRoot $freshSourceResultPath +$freshResult = Get-Content -Raw -Path $freshSourceFile | ConvertFrom-Json +$freshObserved = Get-PropertyValue -Item $freshResult -Name "observed" -Default $null + +$activeSource = $null +$control = $null +$activeObserved = $null +$terminalObserved = $null +try { + $activeSource = & powershell -ExecutionPolicy Bypass -File (Join-Path $PSScriptRoot "c19x-remote-workspace-mailbox-consumer-resume-smoke.ps1") ` + -ApiBaseUrl $ApiBaseUrl ` + -ClusterID $ClusterID ` + -ActorUserID $ActorUserID ` + -EntryNodeName $EntryNodeName ` + -ExitNodeName $ExitNodeName ` + -EntryBaseUrl $EntryBaseUrl ` + -ResultPath $activeSourceResultPath ` + -SkipClose + $activeSourceFile = Join-Path $repoRoot $activeSourceResultPath + $activeResult = Get-Content -Raw -Path $activeSourceFile | ConvertFrom-Json + $adapterSessionID = [string](Get-PropertyValue -Item $activeResult -Name "adapter_session_id" -Default "") + + $deadline = (Get-Date).AddSeconds(60) + while ((Get-Date) -lt $deadline) { + Start-Sleep -Seconds 5 + $activeObserved = Get-RemoteWorkspaceSinkReports -NodeID $entryNode.id + if ( + (Test-ActiveExclusivity -Sink $activeObserved.workload_sink -SessionID $adapterSessionID) -and + (Test-ActiveExclusivity -Sink $activeObserved.telemetry_sink -SessionID $adapterSessionID) + ) { + break + } + } + if ($null -eq $activeObserved) { + $activeObserved = Get-RemoteWorkspaceSinkReports -NodeID $entryNode.id + } + + $control = Invoke-Control -SessionID $adapterSessionID + $deadline = (Get-Date).AddSeconds(90) + while ((Get-Date) -lt $deadline) { + Start-Sleep -Seconds 5 + $terminalObserved = Get-RemoteWorkspaceSinkReports -NodeID $entryNode.id + if ( + (Test-TerminalExclusivity -Sink $terminalObserved.workload_sink -SessionID $adapterSessionID) -and + (Test-TerminalExclusivity -Sink $terminalObserved.telemetry_sink -SessionID $adapterSessionID) + ) { + break + } + } + if ($null -eq $terminalObserved) { + $terminalObserved = Get-RemoteWorkspaceSinkReports -NodeID $entryNode.id + } + + $checks = [ordered]@{ + fresh_source_smoke_passed = ([bool]$freshResult.passed) + active_source_smoke_passed = ([bool]$activeResult.passed) + adapter_session_id_present = ($adapterSessionID -match "^rap-rw-adapter-session-[0-9a-f]{24}$") + close_accepted = ([bool]$control.accepted -and [string]$control.session_state -eq "closed") + workload_fresh_exclusive = (Test-FreshExclusivity -Sink $freshObserved.workload_sink) + telemetry_fresh_exclusive = (Test-FreshExclusivity -Sink $freshObserved.telemetry_sink) + workload_active_exclusive = (Test-ActiveExclusivity -Sink $activeObserved.workload_sink -SessionID $adapterSessionID) + telemetry_active_exclusive = (Test-ActiveExclusivity -Sink $activeObserved.telemetry_sink -SessionID $adapterSessionID) + workload_terminal_exclusive = (Test-TerminalExclusivity -Sink $terminalObserved.workload_sink -SessionID $adapterSessionID) + telemetry_terminal_exclusive = (Test-TerminalExclusivity -Sink $terminalObserved.telemetry_sink -SessionID $adapterSessionID) + } + $failed = @($checks.GetEnumerator() | Where-Object { -not $_.Value } | ForEach-Object { $_.Key }) + + $result = [ordered]@{ + schema_version = "c19z32.remote_workspace_readiness_summary_exclusivity_smoke.v1" + fresh_source_result_path = $freshSourceFile + active_source_result_path = $activeSourceFile + cluster_id = $ClusterID + entry_node = [ordered]@{ id = $entryNode.id; name = $entryNode.name } + adapter_session_id = $adapterSessionID + fresh = $freshResult + active = $activeResult + control = $control + observed = [ordered]@{ + fresh = $freshObserved + active = $activeObserved + terminal = $terminalObserved + } + checks = $checks + failed_checks = $failed + passed = ($failed.Count -eq 0) + } +} finally { + if ($adapterSessionID) { + try { [void](Invoke-Control -SessionID $adapterSessionID) } catch {} + } +} + +$fullResultPath = Join-Path $repoRoot $ResultPath +$resultDir = Split-Path -Parent $fullResultPath +if ($resultDir) { New-Item -ItemType Directory -Force -Path $resultDir | Out-Null } +$result | ConvertTo-Json -Depth 100 | Set-Content -Encoding UTF8 -Path $fullResultPath + +if (-not $result.passed) { + throw "C19Z32 remote workspace readiness summary exclusivity smoke failed. Result: $fullResultPath Failed: $($failed -join ', ')" +} + +Write-Host "C19Z32 remote workspace readiness summary exclusivity smoke passed. Result: $fullResultPath" +$result diff --git a/scripts/fabric/c19z33-remote-workspace-readiness-state-matrix-smoke.ps1 b/scripts/fabric/c19z33-remote-workspace-readiness-state-matrix-smoke.ps1 new file mode 100644 index 0000000..f4e9617 --- /dev/null +++ b/scripts/fabric/c19z33-remote-workspace-readiness-state-matrix-smoke.ps1 @@ -0,0 +1,164 @@ +param( + [string]$ApiBaseUrl = "http://192.168.200.61:18121/api/v1", + [string]$ClusterID = "cfc0743d-d960-49fb-9de8-96e063d5e4aa", + [string]$ActorUserID = "f67d943f-5397-4b3a-a229-695fe67ad700", + [string]$EntryNodeName = "test-1", + [string]$ExitNodeName = "test-2", + [string]$EntryBaseUrl = "http://192.168.200.61:19131", + [string]$ResultPath = "artifacts\c19z33-remote-workspace-readiness-state-matrix-smoke-result.json" +) + +Set-StrictMode -Version Latest +$ErrorActionPreference = "Stop" + +$repoRoot = (Resolve-Path (Join-Path $PSScriptRoot "..\..")).ProviderPath +$sourceResultPath = "artifacts\c19z33-remote-workspace-readiness-state-matrix-source-result.json" + +function Get-PropertyValue { + param([object]$Item, [string]$Name, [object]$Default = $null) + if ($null -eq $Item) { return $Default } + if ($Item -is [System.Collections.IDictionary]) { + if ($Item.Contains($Name)) { return $Item[$Name] } + return $Default + } + $property = $Item.PSObject.Properties[$Name] + if ($null -eq $property) { return $Default } + return $property.Value +} + +function Select-ReadinessMatrixRow { + param([object]$Observed, [string]$State, [string]$Surface) + $sink = Get-PropertyValue -Item $Observed -Name "${Surface}_sink" -Default $null + $readiness = Get-PropertyValue -Item $sink -Name "adapter_runtime_readiness" -Default $null + $noSessionSummary = Get-PropertyValue -Item $readiness -Name "no_session_summary" -Default $null + $terminalSummary = Get-PropertyValue -Item $readiness -Name "terminal_session_summary" -Default $null + return [ordered]@{ + state = $State + surface = $Surface + schema_version = [string](Get-PropertyValue -Item $readiness -Name "schema_version" -Default "") + probe_only = [bool](Get-PropertyValue -Item $readiness -Name "probe_only" -Default $false) + payload_traffic = [string](Get-PropertyValue -Item $readiness -Name "payload_traffic" -Default "") + status = [string](Get-PropertyValue -Item $readiness -Name "status" -Default "") + diagnostic_state = [string](Get-PropertyValue -Item $readiness -Name "diagnostic_state" -Default "") + ready = [bool](Get-PropertyValue -Item $readiness -Name "ready" -Default $false) + active_session_count = [int](Get-PropertyValue -Item $readiness -Name "active_session_count" -Default -1) + terminal_session_count = [int](Get-PropertyValue -Item $readiness -Name "terminal_session_count" -Default -1) + adapter_session_id = Get-PropertyValue -Item $readiness -Name "adapter_session_id" -Default $null + last_adapter_session_id = Get-PropertyValue -Item $readiness -Name "last_adapter_session_id" -Default $null + last_session_state = Get-PropertyValue -Item $readiness -Name "last_session_state" -Default $null + no_session_summary_present = ($null -ne $noSessionSummary) + terminal_session_summary_present = ($null -ne $terminalSummary) + no_session_summary_schema = Get-PropertyValue -Item $noSessionSummary -Name "schema_version" -Default $null + terminal_session_summary_schema = Get-PropertyValue -Item $terminalSummary -Name "schema_version" -Default $null + } +} + +function Test-MatrixRow { + param([object]$Row) + $state = [string](Get-PropertyValue -Item $Row -Name "state" -Default "") + if ([string](Get-PropertyValue -Item $Row -Name "schema_version" -Default "") -ne "rap.remote_workspace_adapter_runtime_readiness.v1") { return $false } + if (-not [bool](Get-PropertyValue -Item $Row -Name "probe_only" -Default $false)) { return $false } + if ([string](Get-PropertyValue -Item $Row -Name "payload_traffic" -Default "") -ne "none") { return $false } + if ($state -eq "fresh") { + return ( + [string](Get-PropertyValue -Item $Row -Name "diagnostic_state" -Default "") -eq "waiting_for_session" -and + [int](Get-PropertyValue -Item $Row -Name "active_session_count" -Default -1) -eq 0 -and + [int](Get-PropertyValue -Item $Row -Name "terminal_session_count" -Default -1) -eq 0 -and + [bool](Get-PropertyValue -Item $Row -Name "no_session_summary_present" -Default $false) -and + -not [bool](Get-PropertyValue -Item $Row -Name "terminal_session_summary_present" -Default $true) + ) + } + if ($state -eq "active") { + return ( + [int](Get-PropertyValue -Item $Row -Name "active_session_count" -Default -1) -eq 1 -and + -not [string]::IsNullOrWhiteSpace([string](Get-PropertyValue -Item $Row -Name "adapter_session_id" -Default "")) -and + -not [bool](Get-PropertyValue -Item $Row -Name "no_session_summary_present" -Default $true) -and + -not [bool](Get-PropertyValue -Item $Row -Name "terminal_session_summary_present" -Default $true) + ) + } + if ($state -eq "terminal") { + return ( + [string](Get-PropertyValue -Item $Row -Name "diagnostic_state" -Default "") -eq "last_session_terminal_or_expired" -and + [int](Get-PropertyValue -Item $Row -Name "active_session_count" -Default -1) -eq 0 -and + [int](Get-PropertyValue -Item $Row -Name "terminal_session_count" -Default -1) -ge 1 -and + -not [bool](Get-PropertyValue -Item $Row -Name "no_session_summary_present" -Default $true) -and + [bool](Get-PropertyValue -Item $Row -Name "terminal_session_summary_present" -Default $false) + ) + } + return $false +} + +$source = & powershell -ExecutionPolicy Bypass -File (Join-Path $PSScriptRoot "c19z32-remote-workspace-readiness-summary-exclusivity-smoke.ps1") ` + -ApiBaseUrl $ApiBaseUrl ` + -ClusterID $ClusterID ` + -ActorUserID $ActorUserID ` + -EntryNodeName $EntryNodeName ` + -ExitNodeName $ExitNodeName ` + -EntryBaseUrl $EntryBaseUrl ` + -ResultPath $sourceResultPath + +$sourceFile = Join-Path $repoRoot $sourceResultPath +$sourceResult = Get-Content -Raw -Path $sourceFile | ConvertFrom-Json +$observed = Get-PropertyValue -Item $sourceResult -Name "observed" -Default $null +$freshObserved = Get-PropertyValue -Item $observed -Name "fresh" -Default $null +$activeObserved = Get-PropertyValue -Item $observed -Name "active" -Default $null +$terminalObserved = Get-PropertyValue -Item $observed -Name "terminal" -Default $null + +$matrix = @( + (Select-ReadinessMatrixRow -Observed $freshObserved -State "fresh" -Surface "workload"), + (Select-ReadinessMatrixRow -Observed $freshObserved -State "fresh" -Surface "telemetry"), + (Select-ReadinessMatrixRow -Observed $activeObserved -State "active" -Surface "workload"), + (Select-ReadinessMatrixRow -Observed $activeObserved -State "active" -Surface "telemetry"), + (Select-ReadinessMatrixRow -Observed $terminalObserved -State "terminal" -Surface "workload"), + (Select-ReadinessMatrixRow -Observed $terminalObserved -State "terminal" -Surface "telemetry") +) + +$checks = [ordered]@{ + source_smoke_passed = ([bool]$sourceResult.passed) + matrix_row_count = ($matrix.Count -eq 6) + all_rows_valid = (@($matrix | Where-Object { -not (Test-MatrixRow -Row $_) }).Count -eq 0) + workload_rows_present = (@($matrix | Where-Object { $_.surface -eq "workload" }).Count -eq 3) + telemetry_rows_present = (@($matrix | Where-Object { $_.surface -eq "telemetry" }).Count -eq 3) +} +$failed = @($checks.GetEnumerator() | Where-Object { -not $_.Value } | ForEach-Object { $_.Key }) + +$result = [ordered]@{ + schema_version = "c19z33.remote_workspace_readiness_state_matrix_smoke.v1" + source_result_path = $sourceFile + cluster_id = $ClusterID + entry_node = Get-PropertyValue -Item $sourceResult -Name "entry_node" -Default $null + adapter_session_id = Get-PropertyValue -Item $sourceResult -Name "adapter_session_id" -Default $null + matrix_contract = @( + "state", + "surface", + "schema_version", + "probe_only", + "payload_traffic", + "status", + "diagnostic_state", + "ready", + "active_session_count", + "terminal_session_count", + "adapter_session_id", + "last_adapter_session_id", + "last_session_state", + "no_session_summary_present", + "terminal_session_summary_present" + ) + matrix = $matrix + checks = $checks + failed_checks = $failed + passed = ($failed.Count -eq 0) +} + +$fullResultPath = Join-Path $repoRoot $ResultPath +$resultDir = Split-Path -Parent $fullResultPath +if ($resultDir) { New-Item -ItemType Directory -Force -Path $resultDir | Out-Null } +$result | ConvertTo-Json -Depth 20 | Set-Content -Encoding UTF8 -Path $fullResultPath + +if (-not $result.passed) { + throw "C19Z33 remote workspace readiness state matrix smoke failed. Result: $fullResultPath Failed: $($failed -join ', ')" +} + +Write-Host "C19Z33 remote workspace readiness state matrix smoke passed. Result: $fullResultPath" +$result diff --git a/scripts/fabric/c19z34-remote-workspace-probe-to-runtime-gate-smoke.ps1 b/scripts/fabric/c19z34-remote-workspace-probe-to-runtime-gate-smoke.ps1 new file mode 100644 index 0000000..6ce13a1 --- /dev/null +++ b/scripts/fabric/c19z34-remote-workspace-probe-to-runtime-gate-smoke.ps1 @@ -0,0 +1,110 @@ +param( + [string]$ApiBaseUrl = "http://192.168.200.61:18121/api/v1", + [string]$ClusterID = "cfc0743d-d960-49fb-9de8-96e063d5e4aa", + [string]$ActorUserID = "f67d943f-5397-4b3a-a229-695fe67ad700", + [string]$EntryNodeName = "test-1", + [string]$ExitNodeName = "test-2", + [string]$EntryBaseUrl = "http://192.168.200.61:19131", + [string]$ResultPath = "artifacts\c19z34-remote-workspace-probe-to-runtime-gate-smoke-result.json" +) + +Set-StrictMode -Version Latest +$ErrorActionPreference = "Stop" + +$repoRoot = (Resolve-Path (Join-Path $PSScriptRoot "..\..")).ProviderPath +$sourceResultPath = "artifacts\c19z34-remote-workspace-probe-to-runtime-gate-source-result.json" + +function Get-PropertyValue { + param([object]$Item, [string]$Name, [object]$Default = $null) + if ($null -eq $Item) { return $Default } + if ($Item -is [System.Collections.IDictionary]) { + if ($Item.Contains($Name)) { return $Item[$Name] } + return $Default + } + $property = $Item.PSObject.Properties[$Name] + if ($null -eq $property) { return $Default } + return $property.Value +} + +$source = & powershell -ExecutionPolicy Bypass -File (Join-Path $PSScriptRoot "c19z33-remote-workspace-readiness-state-matrix-smoke.ps1") ` + -ApiBaseUrl $ApiBaseUrl ` + -ClusterID $ClusterID ` + -ActorUserID $ActorUserID ` + -EntryNodeName $EntryNodeName ` + -ExitNodeName $ExitNodeName ` + -EntryBaseUrl $EntryBaseUrl ` + -ResultPath $sourceResultPath + +$sourceFile = Join-Path $repoRoot $sourceResultPath +$sourceResult = Get-Content -Raw -Path $sourceFile | ConvertFrom-Json +$matrix = @(Get-PropertyValue -Item $sourceResult -Name "matrix" -Default @()) + +$allProbeOnly = (@($matrix | Where-Object { -not [bool](Get-PropertyValue -Item $_ -Name "probe_only" -Default $false) }).Count -eq 0) +$allPayloadNone = (@($matrix | Where-Object { [string](Get-PropertyValue -Item $_ -Name "payload_traffic" -Default "") -ne "none" }).Count -eq 0) +$hasFresh = (@($matrix | Where-Object { [string](Get-PropertyValue -Item $_ -Name "state" -Default "") -eq "fresh" }).Count -eq 2) +$hasActive = (@($matrix | Where-Object { [string](Get-PropertyValue -Item $_ -Name "state" -Default "") -eq "active" }).Count -eq 2) +$hasTerminal = (@($matrix | Where-Object { [string](Get-PropertyValue -Item $_ -Name "state" -Default "") -eq "terminal" }).Count -eq 2) + +$readyContracts = @( + "fabric_service_channel_remote_workspace_lease_probe", + "remote_workspace_entry_ingress_probe", + "remote_workspace_frame_batch_probe", + "remote_workspace_mailbox_cursor_resume", + "remote_workspace_mailbox_preflight_diagnostics", + "remote_workspace_runtime_readiness_state_matrix", + "remote_workspace_terminal_session_summary", + "remote_workspace_no_session_summary" +) + +$remainingRuntimeGates = @( + "replace_contract_probe_with_real_adapter_process_supervision", + "carry_real_rdp_display_input_clipboard_file_audio_frames_over_fabric_service_channel", + "add_payload_sized_backpressure_and_drop_policy_for_real_display_audio_channels", + "bind_real_adapter_session_lifecycle_to_backend_resource_session_lifecycle", + "add_client_runtime_handshake_for_remote_workspace_transport_contract", + "prove_no_backend_relay_steady_state_for_remote_workspace_payloads", + "add_real_workload_soak_and_failure_tests_for_entry_exit_route_rebuild", + "add_operator_ui_for_runtime_state_matrix_and_payload_health" +) + +$checks = [ordered]@{ + source_smoke_passed = ([bool]$sourceResult.passed) + matrix_present = ($matrix.Count -eq 6) + all_rows_probe_only = $allProbeOnly + all_rows_payload_traffic_none = $allPayloadNone + fresh_active_terminal_rows_present = ($hasFresh -and $hasActive -and $hasTerminal) + ready_contracts_present = ($readyContracts.Count -ge 8) + remaining_runtime_gates_present = ($remainingRuntimeGates.Count -ge 8) +} +$failed = @($checks.GetEnumerator() | Where-Object { -not $_.Value } | ForEach-Object { $_.Key }) + +$result = [ordered]@{ + schema_version = "c19z34.remote_workspace_probe_to_runtime_gate_smoke.v1" + source_result_path = $sourceFile + cluster_id = $ClusterID + entry_node = Get-PropertyValue -Item $sourceResult -Name "entry_node" -Default $null + current_runtime = [ordered]@{ + execution_mode = "contract_probe" + probe_only = $allProbeOnly + payload_traffic = "none" + node_agent_image = "rap-node-agent:codex-service-supervisor-20260513z29" + } + ready_contracts = $readyContracts + remaining_runtime_gates = $remainingRuntimeGates + readiness_matrix = $matrix + checks = $checks + failed_checks = $failed + passed = ($failed.Count -eq 0) +} + +$fullResultPath = Join-Path $repoRoot $ResultPath +$resultDir = Split-Path -Parent $fullResultPath +if ($resultDir) { New-Item -ItemType Directory -Force -Path $resultDir | Out-Null } +$result | ConvertTo-Json -Depth 30 | Set-Content -Encoding UTF8 -Path $fullResultPath + +if (-not $result.passed) { + throw "C19Z34 remote workspace probe-to-runtime gate smoke failed. Result: $fullResultPath Failed: $($failed -join ', ')" +} + +Write-Host "C19Z34 remote workspace probe-to-runtime gate smoke passed. Result: $fullResultPath" +$result diff --git a/scripts/fabric/c19z35-remote-workspace-real-adapter-supervision-scaffold-smoke.ps1 b/scripts/fabric/c19z35-remote-workspace-real-adapter-supervision-scaffold-smoke.ps1 new file mode 100644 index 0000000..adaa43f --- /dev/null +++ b/scripts/fabric/c19z35-remote-workspace-real-adapter-supervision-scaffold-smoke.ps1 @@ -0,0 +1,117 @@ +param( + [string]$ApiBaseUrl = "http://192.168.200.61:18121/api/v1", + [string]$ClusterID = "cfc0743d-d960-49fb-9de8-96e063d5e4aa", + [string]$ActorUserID = "f67d943f-5397-4b3a-a229-695fe67ad700", + [string]$EntryNodeName = "test-1", + [string]$ResultPath = "artifacts\c19z35-remote-workspace-real-adapter-supervision-scaffold-smoke-result.json" +) + +Set-StrictMode -Version Latest +$ErrorActionPreference = "Stop" + +$repoRoot = (Resolve-Path (Join-Path $PSScriptRoot "..\..")).ProviderPath +$requiredEnv = @( + "RAP_REMOTE_WORKSPACE_REAL_ADAPTER_ENABLED", + "RAP_REMOTE_WORKSPACE_REAL_ADAPTER_COMMAND", + "RAP_REMOTE_WORKSPACE_REAL_ADAPTER_ARGS_JSON", + "RAP_REMOTE_WORKSPACE_REAL_ADAPTER_WORKDIR" +) + +function Invoke-Api { + param([string]$Method, [string]$Path, [object]$Body = $null) + $uri = "$ApiBaseUrl$Path" + if ($null -eq $Body) { return Invoke-RestMethod -Method $Method -Uri $uri -TimeoutSec 30 } + return Invoke-RestMethod -Method $Method -Uri $uri -ContentType "application/json" -Body ($Body | ConvertTo-Json -Depth 80) -TimeoutSec 30 +} + +function Get-PropertyValue { + param([object]$Item, [string]$Name, [object]$Default = $null) + if ($null -eq $Item) { return $Default } + $property = $Item.PSObject.Properties[$Name] + if ($null -eq $property) { return $Default } + return $property.Value +} + +function Test-ArrayItem { + param([object]$Items, [string]$Want) + foreach ($item in @($Items)) { + if ([string]$item -eq $Want) { return $true } + } + return $false +} + +function Get-NodeByName { + param([string]$Name) + $nodes = (Invoke-Api -Method GET -Path "/clusters/$ClusterID/nodes?actor_user_id=$ActorUserID").nodes + $node = @($nodes | Where-Object { $_.name -eq $Name }) | Select-Object -First 1 + if ($null -eq $node) { throw "Node '$Name' was not found in cluster $ClusterID" } + return $node +} + +function Get-RdpWorkerStatus { + param([string]$NodeID) + $statuses = (Invoke-Api -Method GET -Path "/clusters/$ClusterID/nodes/$NodeID/workloads/status?actor_user_id=$ActorUserID").workload_statuses + return @($statuses | Where-Object { $_.service_type -eq "rdp-worker" } | Select-Object -First 1) +} + +function Test-RealAdapterScaffold { + param([object]$Status) + if ($null -eq $Status) { return $false } + $payload = Get-PropertyValue -Item $Status -Name "status_payload" -Default $null + $contract = Get-PropertyValue -Item $payload -Name "real_adapter_supervision" -Default $null + if ($null -eq $contract) { return $false } + $env = Get-PropertyValue -Item $contract -Name "config_env" -Default @() + foreach ($item in $requiredEnv) { + if (-not (Test-ArrayItem -Items $env -Want $item)) { return $false } + } + return ( + [string](Get-PropertyValue -Item $payload -Name "execution_mode" -Default "") -eq "contract_probe" -and + [string](Get-PropertyValue -Item $payload -Name "traffic" -Default "") -eq "none" -and + [string](Get-PropertyValue -Item $contract -Name "schema_version" -Default "") -eq "rap.remote_workspace_real_adapter_supervision.v1" -and + -not [bool](Get-PropertyValue -Item $contract -Name "enabled" -Default $true) -and + [string](Get-PropertyValue -Item $contract -Name "activation_state" -Default "") -eq "disabled_until_real_runtime_stage" -and + [string](Get-PropertyValue -Item $contract -Name "payload_traffic" -Default "") -eq "none" + ) +} + +$entryNode = Get-NodeByName -Name $EntryNodeName +$status = $null +$deadline = (Get-Date).AddSeconds(60) +while ((Get-Date) -lt $deadline) { + Start-Sleep -Seconds 5 + $status = Get-RdpWorkerStatus -NodeID $entryNode.id + if (Test-RealAdapterScaffold -Status $status) { break } +} +if ($null -eq $status) { + $status = Get-RdpWorkerStatus -NodeID $entryNode.id +} + +$checks = [ordered]@{ + rdp_worker_status_present = ($null -ne $status) + contract_probe_remains_active = ([string](Get-PropertyValue -Item $status.status_payload -Name "execution_mode" -Default "") -eq "contract_probe") + real_adapter_scaffold_visible = (Test-RealAdapterScaffold -Status $status) +} +$failed = @($checks.GetEnumerator() | Where-Object { -not $_.Value } | ForEach-Object { $_.Key }) + +$result = [ordered]@{ + schema_version = "c19z35.remote_workspace_real_adapter_supervision_scaffold_smoke.v1" + cluster_id = $ClusterID + entry_node = [ordered]@{ id = $entryNode.id; name = $entryNode.name } + required_env = $requiredEnv + workload_status = $status + checks = $checks + failed_checks = $failed + passed = ($failed.Count -eq 0) +} + +$fullResultPath = Join-Path $repoRoot $ResultPath +$resultDir = Split-Path -Parent $fullResultPath +if ($resultDir) { New-Item -ItemType Directory -Force -Path $resultDir | Out-Null } +$result | ConvertTo-Json -Depth 100 | Set-Content -Encoding UTF8 -Path $fullResultPath + +if (-not $result.passed) { + throw "C19Z35 remote workspace real-adapter supervision scaffold smoke failed. Result: $fullResultPath Failed: $($failed -join ', ')" +} + +Write-Host "C19Z35 remote workspace real-adapter supervision scaffold smoke passed. Result: $fullResultPath" +$result diff --git a/scripts/fabric/c19z36-remote-workspace-real-adapter-supervision-compatibility-smoke.ps1 b/scripts/fabric/c19z36-remote-workspace-real-adapter-supervision-compatibility-smoke.ps1 new file mode 100644 index 0000000..7fc813d --- /dev/null +++ b/scripts/fabric/c19z36-remote-workspace-real-adapter-supervision-compatibility-smoke.ps1 @@ -0,0 +1,121 @@ +param( + [string]$ApiBaseUrl = "http://192.168.200.61:18121/api/v1", + [string]$ClusterID = "cfc0743d-d960-49fb-9de8-96e063d5e4aa", + [string]$ActorUserID = "f67d943f-5397-4b3a-a229-695fe67ad700", + [string]$EntryNodeName = "test-1", + [string]$ResultPath = "artifacts\c19z36-remote-workspace-real-adapter-supervision-compatibility-smoke-result.json" +) + +Set-StrictMode -Version Latest +$ErrorActionPreference = "Stop" + +$repoRoot = (Resolve-Path (Join-Path $PSScriptRoot "..\..")).ProviderPath +$sourceResultPath = "artifacts\c19z36-remote-workspace-real-adapter-supervision-compatibility-source-result.json" +$requiredEnv = @( + "RAP_REMOTE_WORKSPACE_REAL_ADAPTER_ENABLED", + "RAP_REMOTE_WORKSPACE_REAL_ADAPTER_COMMAND", + "RAP_REMOTE_WORKSPACE_REAL_ADAPTER_ARGS_JSON", + "RAP_REMOTE_WORKSPACE_REAL_ADAPTER_WORKDIR" +) +$requiredStatusContract = @( + "schema_version", + "enabled", + "activation_state", + "execution_mode", + "payload_traffic", + "process_model", + "config_env", + "status_contract" +) +$requiredGuardrails = @( + "contract_probe_remains_default", + "no_payload_forwarding_until_real_runtime_stage", + "backend_relay_not_steady_state", + "fabric_service_channel_required" +) + +function Get-PropertyValue { + param([object]$Item, [string]$Name, [object]$Default = $null) + if ($null -eq $Item) { return $Default } + $property = $Item.PSObject.Properties[$Name] + if ($null -eq $property) { return $Default } + return $property.Value +} + +function Test-ArrayItem { + param([object]$Items, [string]$Want) + foreach ($item in @($Items)) { + if ([string]$item -eq $Want) { return $true } + } + return $false +} + +function Test-RequiredItems { + param([object]$Items, [string[]]$Required) + foreach ($item in $Required) { + if (-not (Test-ArrayItem -Items $Items -Want $item)) { return $false } + } + return $true +} + +function Test-RealAdapterCompatibility { + param([object]$Contract) + if ($null -eq $Contract) { return $false } + return ( + [string](Get-PropertyValue -Item $Contract -Name "schema_version" -Default "") -eq "rap.remote_workspace_real_adapter_supervision.v1" -and + -not [bool](Get-PropertyValue -Item $Contract -Name "enabled" -Default $true) -and + [string](Get-PropertyValue -Item $Contract -Name "activation_state" -Default "") -eq "disabled_until_real_runtime_stage" -and + [string](Get-PropertyValue -Item $Contract -Name "execution_mode" -Default "") -eq "real_adapter_supervision_disabled" -and + [string](Get-PropertyValue -Item $Contract -Name "payload_traffic" -Default "") -eq "none" -and + [string](Get-PropertyValue -Item $Contract -Name "process_model" -Default "") -eq "external_rdp_worker_process" -and + (Test-RequiredItems -Items (Get-PropertyValue -Item $Contract -Name "config_env" -Default @()) -Required $requiredEnv) -and + (Test-RequiredItems -Items (Get-PropertyValue -Item $Contract -Name "status_contract" -Default @()) -Required $requiredStatusContract) -and + (Test-RequiredItems -Items (Get-PropertyValue -Item $Contract -Name "guardrails" -Default @()) -Required $requiredGuardrails) + ) +} + +$source = & powershell -ExecutionPolicy Bypass -File (Join-Path $PSScriptRoot "c19z35-remote-workspace-real-adapter-supervision-scaffold-smoke.ps1") ` + -ApiBaseUrl $ApiBaseUrl ` + -ClusterID $ClusterID ` + -ActorUserID $ActorUserID ` + -EntryNodeName $EntryNodeName ` + -ResultPath $sourceResultPath + +$sourceFile = Join-Path $repoRoot $sourceResultPath +$sourceResult = Get-Content -Raw -Path $sourceFile | ConvertFrom-Json +$status = Get-PropertyValue -Item $sourceResult -Name "workload_status" -Default $null +$payload = Get-PropertyValue -Item $status -Name "status_payload" -Default $null +$contract = Get-PropertyValue -Item $payload -Name "real_adapter_supervision" -Default $null + +$checks = [ordered]@{ + source_smoke_passed = ([bool]$sourceResult.passed) + contract_probe_remains_active = ([string](Get-PropertyValue -Item $payload -Name "execution_mode" -Default "") -eq "contract_probe") + payload_traffic_none = ([string](Get-PropertyValue -Item $payload -Name "traffic" -Default "") -eq "none") + real_adapter_contract_compatible = (Test-RealAdapterCompatibility -Contract $contract) +} +$failed = @($checks.GetEnumerator() | Where-Object { -not $_.Value } | ForEach-Object { $_.Key }) + +$result = [ordered]@{ + schema_version = "c19z36.remote_workspace_real_adapter_supervision_compatibility_smoke.v1" + source_result_path = $sourceFile + cluster_id = $ClusterID + required_env = $requiredEnv + required_status_contract = $requiredStatusContract + required_guardrails = $requiredGuardrails + real_adapter_supervision = $contract + checks = $checks + failed_checks = $failed + passed = ($failed.Count -eq 0) +} + +$fullResultPath = Join-Path $repoRoot $ResultPath +$resultDir = Split-Path -Parent $fullResultPath +if ($resultDir) { New-Item -ItemType Directory -Force -Path $resultDir | Out-Null } +$result | ConvertTo-Json -Depth 100 | Set-Content -Encoding UTF8 -Path $fullResultPath + +if (-not $result.passed) { + throw "C19Z36 remote workspace real-adapter supervision compatibility smoke failed. Result: $fullResultPath Failed: $($failed -join ', ')" +} + +Write-Host "C19Z36 remote workspace real-adapter supervision compatibility smoke passed. Result: $fullResultPath" +$result diff --git a/scripts/fabric/c19z37-remote-workspace-real-adapter-config-projection-smoke.ps1 b/scripts/fabric/c19z37-remote-workspace-real-adapter-config-projection-smoke.ps1 new file mode 100644 index 0000000..645d5ab --- /dev/null +++ b/scripts/fabric/c19z37-remote-workspace-real-adapter-config-projection-smoke.ps1 @@ -0,0 +1,131 @@ +param( + [string]$ApiBaseUrl = "http://192.168.200.61:18121/api/v1", + [string]$ClusterID = "cfc0743d-d960-49fb-9de8-96e063d5e4aa", + [string]$ActorUserID = "f67d943f-5397-4b3a-a229-695fe67ad700", + [string]$EntryNodeName = "test-1", + [string]$ResultPath = "artifacts\c19z37-remote-workspace-real-adapter-config-projection-smoke-result.json" +) + +Set-StrictMode -Version Latest +$ErrorActionPreference = "Stop" + +$repoRoot = (Resolve-Path (Join-Path $PSScriptRoot "..\..")).ProviderPath +$forbiddenProjectionFields = @("command", "args_json", "workdir", "command_path", "raw_command", "raw_args_json", "raw_workdir") + +function Invoke-Api { + param([string]$Method, [string]$Path) + return Invoke-RestMethod -Method $Method -Uri "$ApiBaseUrl$Path" -TimeoutSec 30 +} + +function Get-PropertyValue { + param([object]$Item, [string]$Name, [object]$Default = $null) + if ($null -eq $Item) { return $Default } + $property = $Item.PSObject.Properties[$Name] + if ($null -eq $property) { return $Default } + return $property.Value +} + +function Get-NodeByName { + param([string]$Name) + $nodes = (Invoke-Api -Method GET -Path "/clusters/$ClusterID/nodes?actor_user_id=$ActorUserID").nodes + $node = @($nodes | Where-Object { $_.name -eq $Name }) | Select-Object -First 1 + if ($null -eq $node) { throw "Node '$Name' was not found in cluster $ClusterID" } + return $node +} + +function Get-RdpWorkerStatus { + param([string]$NodeID) + $statuses = (Invoke-Api -Method GET -Path "/clusters/$ClusterID/nodes/$NodeID/workloads/status?actor_user_id=$ActorUserID").workload_statuses + return @($statuses | Where-Object { $_.service_type -eq "rdp-worker" } | Select-Object -First 1) +} + +function Test-NoRawProjectionFields { + param([object]$Projection) + if ($null -eq $Projection) { return $false } + foreach ($field in $forbiddenProjectionFields) { + if ($null -ne $Projection.PSObject.Properties[$field]) { return $false } + } + return $true +} + +function Test-ConfigProjection { + param([object]$Status) + if ($null -eq $Status) { return $false } + $payload = Get-PropertyValue -Item $Status -Name "status_payload" -Default $null + $contract = Get-PropertyValue -Item $payload -Name "real_adapter_supervision" -Default $null + $projection = Get-PropertyValue -Item $contract -Name "config_projection" -Default $null + if ($null -eq $projection) { return $false } + return ( + [string](Get-PropertyValue -Item $payload -Name "execution_mode" -Default "") -eq "contract_probe" -and + [string](Get-PropertyValue -Item $payload -Name "traffic" -Default "") -eq "none" -and + [string](Get-PropertyValue -Item $contract -Name "schema_version" -Default "") -eq "rap.remote_workspace_real_adapter_supervision.v1" -and + -not [bool](Get-PropertyValue -Item $contract -Name "enabled" -Default $true) -and + [string](Get-PropertyValue -Item $contract -Name "activation_state" -Default "") -eq "disabled_until_real_runtime_stage" -and + [string](Get-PropertyValue -Item $contract -Name "payload_traffic" -Default "") -eq "none" -and + [string](Get-PropertyValue -Item $projection -Name "schema_version" -Default "") -eq "rap.remote_workspace_real_adapter_config_projection.v1" -and + [bool](Get-PropertyValue -Item $projection -Name "enabled_requested" -Default $false) -and + -not [bool](Get-PropertyValue -Item $projection -Name "activation_allowed" -Default $true) -and + [bool](Get-PropertyValue -Item $projection -Name "command_present" -Default $false) -and + [bool](Get-PropertyValue -Item $projection -Name "args_json_present" -Default $false) -and + [string](Get-PropertyValue -Item $projection -Name "args_json_shape" -Default "") -eq "json_array" -and + [bool](Get-PropertyValue -Item $projection -Name "workdir_present" -Default $false) -and + [bool](Get-PropertyValue -Item $projection -Name "raw_values_redacted" -Default $false) -and + (Test-NoRawProjectionFields -Projection $projection) + ) +} + +$entryNode = Get-NodeByName -Name $EntryNodeName +$status = $null +$deadline = (Get-Date).AddSeconds(70) +while ((Get-Date) -lt $deadline) { + Start-Sleep -Seconds 5 + $status = Get-RdpWorkerStatus -NodeID $entryNode.id + if (Test-ConfigProjection -Status $status) { break } +} +if ($null -eq $status) { + $status = Get-RdpWorkerStatus -NodeID $entryNode.id +} + +$payload = Get-PropertyValue -Item $status -Name "status_payload" -Default $null +$contract = Get-PropertyValue -Item $payload -Name "real_adapter_supervision" -Default $null +$projection = Get-PropertyValue -Item $contract -Name "config_projection" -Default $null + +$checks = [ordered]@{ + rdp_worker_status_present = ($null -ne $status) + contract_probe_remains_active = ([string](Get-PropertyValue -Item $payload -Name "execution_mode" -Default "") -eq "contract_probe") + real_adapter_stays_disabled = (-not [bool](Get-PropertyValue -Item $contract -Name "enabled" -Default $true)) + payload_traffic_none = ([string](Get-PropertyValue -Item $contract -Name "payload_traffic" -Default "") -eq "none") + config_projection_visible = ($null -ne $projection) + enabled_request_projected = ([bool](Get-PropertyValue -Item $projection -Name "enabled_requested" -Default $false)) + activation_still_blocked = (-not [bool](Get-PropertyValue -Item $projection -Name "activation_allowed" -Default $true)) + command_presence_projected = ([bool](Get-PropertyValue -Item $projection -Name "command_present" -Default $false)) + args_presence_projected = ([bool](Get-PropertyValue -Item $projection -Name "args_json_present" -Default $false)) + args_shape_projected = ([string](Get-PropertyValue -Item $projection -Name "args_json_shape" -Default "") -eq "json_array") + workdir_presence_projected = ([bool](Get-PropertyValue -Item $projection -Name "workdir_present" -Default $false)) + raw_values_redacted = ([bool](Get-PropertyValue -Item $projection -Name "raw_values_redacted" -Default $false) -and (Test-NoRawProjectionFields -Projection $projection)) + projection_contract_passed = (Test-ConfigProjection -Status $status) +} +$failed = @($checks.GetEnumerator() | Where-Object { -not $_.Value } | ForEach-Object { $_.Key }) + +$result = [ordered]@{ + schema_version = "c19z37.remote_workspace_real_adapter_config_projection_smoke.v1" + cluster_id = $ClusterID + entry_node = [ordered]@{ id = $entryNode.id; name = $entryNode.name } + forbidden_projection_fields = $forbiddenProjectionFields + real_adapter_supervision = $contract + checks = $checks + failed_checks = $failed + passed = ($failed.Count -eq 0) +} + +$fullResultPath = Join-Path $repoRoot $ResultPath +$resultDir = Split-Path -Parent $fullResultPath +if ($resultDir) { New-Item -ItemType Directory -Force -Path $resultDir | Out-Null } +$result | ConvertTo-Json -Depth 100 | Set-Content -Encoding UTF8 -Path $fullResultPath + +if (-not $result.passed) { + throw "C19Z37 remote workspace real-adapter config projection smoke failed. Result: $fullResultPath Failed: $($failed -join ', ')" +} + +Write-Host "C19Z37 remote workspace real-adapter config projection smoke passed. Result: $fullResultPath" +$result diff --git a/scripts/fabric/c19z38-remote-workspace-real-adapter-config-projection-compatibility-smoke.ps1 b/scripts/fabric/c19z38-remote-workspace-real-adapter-config-projection-compatibility-smoke.ps1 new file mode 100644 index 0000000..5ec659e --- /dev/null +++ b/scripts/fabric/c19z38-remote-workspace-real-adapter-config-projection-compatibility-smoke.ps1 @@ -0,0 +1,160 @@ +param( + [string]$ApiBaseUrl = "http://192.168.200.61:18121/api/v1", + [string]$ClusterID = "cfc0743d-d960-49fb-9de8-96e063d5e4aa", + [string]$ActorUserID = "f67d943f-5397-4b3a-a229-695fe67ad700", + [string]$RequestedNodeName = "test-1", + [string]$DefaultNodeName = "test-2", + [string]$ResultPath = "artifacts\c19z38-remote-workspace-real-adapter-config-projection-compatibility-smoke-result.json" +) + +Set-StrictMode -Version Latest +$ErrorActionPreference = "Stop" + +$repoRoot = (Resolve-Path (Join-Path $PSScriptRoot "..\..")).ProviderPath +$runId = "c19z38-" + (Get-Date -Format "yyyyMMdd-HHmmss") + +function Invoke-Api { + param([string]$Method, [string]$Path, [object]$Body = $null) + if ($null -eq $Body) { + return Invoke-RestMethod -Method $Method -Uri "$ApiBaseUrl$Path" -TimeoutSec 30 + } + return Invoke-RestMethod -Method $Method -Uri "$ApiBaseUrl$Path" -ContentType "application/json" -Body ($Body | ConvertTo-Json -Depth 80) -TimeoutSec 30 +} + +function Get-PropertyValue { + param([object]$Item, [string]$Name, [object]$Default = $null) + if ($null -eq $Item) { return $Default } + $property = $Item.PSObject.Properties[$Name] + if ($null -eq $property) { return $Default } + return $property.Value +} + +function Get-NodeByName { + param([string]$Name) + $nodes = (Invoke-Api -Method GET -Path "/clusters/$ClusterID/nodes?actor_user_id=$ActorUserID").nodes + $node = @($nodes | Where-Object { $_.name -eq $Name }) | Select-Object -First 1 + if ($null -eq $node) { throw "Node '$Name' was not found in cluster $ClusterID" } + return $node +} + +function Get-RdpWorkerStatus { + param([string]$NodeID) + $statuses = (Invoke-Api -Method GET -Path "/clusters/$ClusterID/nodes/$NodeID/workloads/status?actor_user_id=$ActorUserID").workload_statuses + return @($statuses | Where-Object { $_.service_type -eq "rdp-worker" } | Select-Object -First 1) +} + +function Enable-RdpWorkerProbe { + param([object]$Node) + Invoke-Api -Method PUT -Path "/clusters/$ClusterID/nodes/$($Node.id)/workloads/rdp-worker/desired" -Body @{ + actor_user_id = $ActorUserID + desired_state = "enabled" + version = "c19z38-real-adapter-config-projection-$runId" + runtime_mode = "native" + artifact_ref = "builtin:rdp-worker-adapter-contract-probe" + config = @{ + adapter_contract_probe = $true + service_class = "remote_workspace" + run_id = $runId + } + environment = @{} + } | Out-Null +} + +function Get-Projection { + param([object]$Status) + $payload = Get-PropertyValue -Item $Status -Name "status_payload" -Default $null + $contract = Get-PropertyValue -Item $payload -Name "real_adapter_supervision" -Default $null + return Get-PropertyValue -Item $contract -Name "config_projection" -Default $null +} + +function Test-ProjectionCommonGuardrails { + param([object]$Status) + if ($null -eq $Status) { return $false } + $payload = Get-PropertyValue -Item $Status -Name "status_payload" -Default $null + $contract = Get-PropertyValue -Item $payload -Name "real_adapter_supervision" -Default $null + $projection = Get-PropertyValue -Item $contract -Name "config_projection" -Default $null + return ( + [string](Get-PropertyValue -Item $payload -Name "execution_mode" -Default "") -eq "contract_probe" -and + [string](Get-PropertyValue -Item $payload -Name "traffic" -Default "") -eq "none" -and + [string](Get-PropertyValue -Item $contract -Name "schema_version" -Default "") -eq "rap.remote_workspace_real_adapter_supervision.v1" -and + -not [bool](Get-PropertyValue -Item $contract -Name "enabled" -Default $true) -and + [string](Get-PropertyValue -Item $contract -Name "payload_traffic" -Default "") -eq "none" -and + [string](Get-PropertyValue -Item $projection -Name "schema_version" -Default "") -eq "rap.remote_workspace_real_adapter_config_projection.v1" -and + -not [bool](Get-PropertyValue -Item $projection -Name "activation_allowed" -Default $true) -and + [bool](Get-PropertyValue -Item $projection -Name "raw_values_redacted" -Default $false) + ) +} + +function Test-ProjectionShape { + param( + [object]$Status, + [bool]$EnabledRequested, + [bool]$CommandPresent, + [bool]$ArgsPresent, + [string]$ArgsShape, + [bool]$WorkdirPresent + ) + $projection = Get-Projection -Status $Status + if ($null -eq $projection) { return $false } + return ( + (Test-ProjectionCommonGuardrails -Status $Status) -and + [bool](Get-PropertyValue -Item $projection -Name "enabled_requested" -Default (-not $EnabledRequested)) -eq $EnabledRequested -and + [bool](Get-PropertyValue -Item $projection -Name "command_present" -Default (-not $CommandPresent)) -eq $CommandPresent -and + [bool](Get-PropertyValue -Item $projection -Name "args_json_present" -Default (-not $ArgsPresent)) -eq $ArgsPresent -and + [string](Get-PropertyValue -Item $projection -Name "args_json_shape" -Default "") -eq $ArgsShape -and + [bool](Get-PropertyValue -Item $projection -Name "workdir_present" -Default (-not $WorkdirPresent)) -eq $WorkdirPresent + ) +} + +$requestedNode = Get-NodeByName -Name $RequestedNodeName +$defaultNode = Get-NodeByName -Name $DefaultNodeName +Enable-RdpWorkerProbe -Node $requestedNode +Enable-RdpWorkerProbe -Node $defaultNode +$requestedStatus = $null +$defaultStatus = $null +$deadline = (Get-Date).AddSeconds(80) +while ((Get-Date) -lt $deadline) { + Start-Sleep -Seconds 5 + $requestedStatus = Get-RdpWorkerStatus -NodeID $requestedNode.id + $defaultStatus = Get-RdpWorkerStatus -NodeID $defaultNode.id + if ( + (Test-ProjectionShape -Status $requestedStatus -EnabledRequested $true -CommandPresent $true -ArgsPresent $true -ArgsShape "json_array" -WorkdirPresent $true) -and + (Test-ProjectionShape -Status $defaultStatus -EnabledRequested $false -CommandPresent $false -ArgsPresent $false -ArgsShape "absent" -WorkdirPresent $false) + ) { break } +} + +$requestedProjection = Get-Projection -Status $requestedStatus +$defaultProjection = Get-Projection -Status $defaultStatus +$checks = [ordered]@{ + requested_status_present = ($null -ne $requestedStatus) + default_status_present = ($null -ne $defaultStatus) + requested_common_guardrails = (Test-ProjectionCommonGuardrails -Status $requestedStatus) + default_common_guardrails = (Test-ProjectionCommonGuardrails -Status $defaultStatus) + requested_shape_projected = (Test-ProjectionShape -Status $requestedStatus -EnabledRequested $true -CommandPresent $true -ArgsPresent $true -ArgsShape "json_array" -WorkdirPresent $true) + default_shape_projected = (Test-ProjectionShape -Status $defaultStatus -EnabledRequested $false -CommandPresent $false -ArgsPresent $false -ArgsShape "absent" -WorkdirPresent $false) +} +$failed = @($checks.GetEnumerator() | Where-Object { -not $_.Value } | ForEach-Object { $_.Key }) + +$result = [ordered]@{ + schema_version = "c19z38.remote_workspace_real_adapter_config_projection_compatibility_smoke.v1" + cluster_id = $ClusterID + requested_node = [ordered]@{ id = $requestedNode.id; name = $requestedNode.name } + default_node = [ordered]@{ id = $defaultNode.id; name = $defaultNode.name } + requested_projection = $requestedProjection + default_projection = $defaultProjection + checks = $checks + failed_checks = $failed + passed = ($failed.Count -eq 0) +} + +$fullResultPath = Join-Path $repoRoot $ResultPath +$resultDir = Split-Path -Parent $fullResultPath +if ($resultDir) { New-Item -ItemType Directory -Force -Path $resultDir | Out-Null } +$result | ConvertTo-Json -Depth 100 | Set-Content -Encoding UTF8 -Path $fullResultPath + +if (-not $result.passed) { + throw "C19Z38 remote workspace real-adapter config projection compatibility smoke failed. Result: $fullResultPath Failed: $($failed -join ', ')" +} + +Write-Host "C19Z38 remote workspace real-adapter config projection compatibility smoke passed. Result: $fullResultPath" +$result diff --git a/scripts/fabric/c19z39-remote-workspace-real-adapter-activation-decision-smoke.ps1 b/scripts/fabric/c19z39-remote-workspace-real-adapter-activation-decision-smoke.ps1 new file mode 100644 index 0000000..09c79ed --- /dev/null +++ b/scripts/fabric/c19z39-remote-workspace-real-adapter-activation-decision-smoke.ps1 @@ -0,0 +1,160 @@ +param( + [string]$ApiBaseUrl = "http://192.168.200.61:18121/api/v1", + [string]$ClusterID = "cfc0743d-d960-49fb-9de8-96e063d5e4aa", + [string]$ActorUserID = "f67d943f-5397-4b3a-a229-695fe67ad700", + [string]$RequestedNodeName = "test-1", + [string]$DefaultNodeName = "test-2", + [string]$ResultPath = "artifacts\c19z39-remote-workspace-real-adapter-activation-decision-smoke-result.json" +) + +Set-StrictMode -Version Latest +$ErrorActionPreference = "Stop" + +$repoRoot = (Resolve-Path (Join-Path $PSScriptRoot "..\..")).ProviderPath +$runId = "c19z39-" + (Get-Date -Format "yyyyMMdd-HHmmss") +$requiredGates = @( + "real_runtime_stage_enabled", + "fabric_service_channel_runtime_ready", + "adapter_process_supervisor_enabled", + "payload_forwarding_contract_enabled" +) + +function Invoke-Api { + param([string]$Method, [string]$Path, [object]$Body = $null) + if ($null -eq $Body) { + return Invoke-RestMethod -Method $Method -Uri "$ApiBaseUrl$Path" -TimeoutSec 30 + } + return Invoke-RestMethod -Method $Method -Uri "$ApiBaseUrl$Path" -ContentType "application/json" -Body ($Body | ConvertTo-Json -Depth 80) -TimeoutSec 30 +} + +function Get-PropertyValue { + param([object]$Item, [string]$Name, [object]$Default = $null) + if ($null -eq $Item) { return $Default } + $property = $Item.PSObject.Properties[$Name] + if ($null -eq $property) { return $Default } + return $property.Value +} + +function Test-ArrayItem { + param([object]$Items, [string]$Want) + foreach ($item in @($Items)) { + if ([string]$item -eq $Want) { return $true } + } + return $false +} + +function Test-RequiredItems { + param([object]$Items, [string[]]$Required) + foreach ($item in $Required) { + if (-not (Test-ArrayItem -Items $Items -Want $item)) { return $false } + } + return $true +} + +function Get-NodeByName { + param([string]$Name) + $nodes = (Invoke-Api -Method GET -Path "/clusters/$ClusterID/nodes?actor_user_id=$ActorUserID").nodes + $node = @($nodes | Where-Object { $_.name -eq $Name }) | Select-Object -First 1 + if ($null -eq $node) { throw "Node '$Name' was not found in cluster $ClusterID" } + return $node +} + +function Enable-RdpWorkerProbe { + param([object]$Node) + Invoke-Api -Method PUT -Path "/clusters/$ClusterID/nodes/$($Node.id)/workloads/rdp-worker/desired" -Body @{ + actor_user_id = $ActorUserID + desired_state = "enabled" + version = "c19z39-real-adapter-activation-decision-$runId" + runtime_mode = "native" + artifact_ref = "builtin:rdp-worker-adapter-contract-probe" + config = @{ adapter_contract_probe = $true; service_class = "remote_workspace"; run_id = $runId } + environment = @{} + } | Out-Null +} + +function Get-RdpWorkerStatus { + param([string]$NodeID) + $statuses = (Invoke-Api -Method GET -Path "/clusters/$ClusterID/nodes/$NodeID/workloads/status?actor_user_id=$ActorUserID").workload_statuses + return @($statuses | Where-Object { $_.service_type -eq "rdp-worker" } | Select-Object -First 1) +} + +function Get-RealAdapterContract { + param([object]$Status) + $payload = Get-PropertyValue -Item $Status -Name "status_payload" -Default $null + return Get-PropertyValue -Item $payload -Name "real_adapter_supervision" -Default $null +} + +function Test-ActivationDecision { + param([object]$Status, [bool]$EnabledRequested) + if ($null -eq $Status) { return $false } + $payload = Get-PropertyValue -Item $Status -Name "status_payload" -Default $null + $contract = Get-RealAdapterContract -Status $Status + $decision = Get-PropertyValue -Item $contract -Name "activation_decision" -Default $null + if ($null -eq $decision) { return $false } + return ( + [string](Get-PropertyValue -Item $payload -Name "execution_mode" -Default "") -eq "contract_probe" -and + [string](Get-PropertyValue -Item $payload -Name "traffic" -Default "") -eq "none" -and + -not [bool](Get-PropertyValue -Item $contract -Name "enabled" -Default $true) -and + [string](Get-PropertyValue -Item $contract -Name "payload_traffic" -Default "") -eq "none" -and + [string](Get-PropertyValue -Item $decision -Name "schema_version" -Default "") -eq "rap.remote_workspace_real_adapter_activation_decision.v1" -and + [string](Get-PropertyValue -Item $decision -Name "decision" -Default "") -eq "blocked" -and + [string](Get-PropertyValue -Item $decision -Name "reason" -Default "") -eq "real_runtime_stage_not_enabled" -and + [bool](Get-PropertyValue -Item $decision -Name "enabled_requested" -Default (-not $EnabledRequested)) -eq $EnabledRequested -and + -not [bool](Get-PropertyValue -Item $decision -Name "activation_allowed" -Default $true) -and + [string](Get-PropertyValue -Item $decision -Name "payload_traffic" -Default "") -eq "none" -and + (Test-RequiredItems -Items (Get-PropertyValue -Item $decision -Name "required_gates" -Default @()) -Required $requiredGates) -and + (Test-RequiredItems -Items (Get-PropertyValue -Item $decision -Name "missing_gates" -Default @()) -Required $requiredGates) + ) +} + +$requestedNode = Get-NodeByName -Name $RequestedNodeName +$defaultNode = Get-NodeByName -Name $DefaultNodeName +Enable-RdpWorkerProbe -Node $requestedNode +Enable-RdpWorkerProbe -Node $defaultNode + +$requestedStatus = $null +$defaultStatus = $null +$deadline = (Get-Date).AddSeconds(80) +while ((Get-Date) -lt $deadline) { + Start-Sleep -Seconds 5 + $requestedStatus = Get-RdpWorkerStatus -NodeID $requestedNode.id + $defaultStatus = Get-RdpWorkerStatus -NodeID $defaultNode.id + if ((Test-ActivationDecision -Status $requestedStatus -EnabledRequested $true) -and (Test-ActivationDecision -Status $defaultStatus -EnabledRequested $false)) { + break + } +} + +$requestedContract = Get-RealAdapterContract -Status $requestedStatus +$defaultContract = Get-RealAdapterContract -Status $defaultStatus +$checks = [ordered]@{ + requested_status_present = ($null -ne $requestedStatus) + default_status_present = ($null -ne $defaultStatus) + requested_activation_blocked = (Test-ActivationDecision -Status $requestedStatus -EnabledRequested $true) + default_activation_blocked = (Test-ActivationDecision -Status $defaultStatus -EnabledRequested $false) +} +$failed = @($checks.GetEnumerator() | Where-Object { -not $_.Value } | ForEach-Object { $_.Key }) + +$result = [ordered]@{ + schema_version = "c19z39.remote_workspace_real_adapter_activation_decision_smoke.v1" + cluster_id = $ClusterID + requested_node = [ordered]@{ id = $requestedNode.id; name = $requestedNode.name } + default_node = [ordered]@{ id = $defaultNode.id; name = $defaultNode.name } + required_gates = $requiredGates + requested_activation_decision = Get-PropertyValue -Item $requestedContract -Name "activation_decision" -Default $null + default_activation_decision = Get-PropertyValue -Item $defaultContract -Name "activation_decision" -Default $null + checks = $checks + failed_checks = $failed + passed = ($failed.Count -eq 0) +} + +$fullResultPath = Join-Path $repoRoot $ResultPath +$resultDir = Split-Path -Parent $fullResultPath +if ($resultDir) { New-Item -ItemType Directory -Force -Path $resultDir | Out-Null } +$result | ConvertTo-Json -Depth 100 | Set-Content -Encoding UTF8 -Path $fullResultPath + +if (-not $result.passed) { + throw "C19Z39 remote workspace real-adapter activation decision smoke failed. Result: $fullResultPath Failed: $($failed -join ', ')" +} + +Write-Host "C19Z39 remote workspace real-adapter activation decision smoke passed. Result: $fullResultPath" +$result diff --git a/scripts/fabric/c19z4-remote-workspace-mailbox-preflight-action-hints-smoke.ps1 b/scripts/fabric/c19z4-remote-workspace-mailbox-preflight-action-hints-smoke.ps1 new file mode 100644 index 0000000..47660e7 --- /dev/null +++ b/scripts/fabric/c19z4-remote-workspace-mailbox-preflight-action-hints-smoke.ps1 @@ -0,0 +1,71 @@ +param( + [string]$ApiBaseUrl = "http://192.168.200.61:18121/api/v1", + [string]$ClusterID = "cfc0743d-d960-49fb-9de8-96e063d5e4aa", + [string]$ActorUserID = "f67d943f-5397-4b3a-a229-695fe67ad700", + [string]$EntryNodeName = "test-1", + [string]$ExitNodeName = "test-2", + [string]$EntryBaseUrl = "http://192.168.200.61:19131", + [string]$ResultPath = "artifacts\c19z4-remote-workspace-mailbox-preflight-action-hints-smoke-result.json" +) + +Set-StrictMode -Version Latest +$ErrorActionPreference = "Stop" + +$repoRoot = (Resolve-Path (Join-Path $PSScriptRoot "..\..")).ProviderPath +$sourceResultPath = "artifacts\c19z4-remote-workspace-mailbox-preflight-action-hints-source-result.json" + +function Get-PropertyValue { + param([object]$Item, [string]$Name, [object]$Default = $null) + if ($null -eq $Item) { return $Default } + $property = $Item.PSObject.Properties[$Name] + if ($null -eq $property) { return $Default } + return $property.Value +} + +$source = & powershell -ExecutionPolicy Bypass -File (Join-Path $PSScriptRoot "c19z3-remote-workspace-mailbox-stale-preflight-smoke.ps1") ` + -ApiBaseUrl $ApiBaseUrl ` + -ClusterID $ClusterID ` + -ActorUserID $ActorUserID ` + -EntryNodeName $EntryNodeName ` + -ExitNodeName $ExitNodeName ` + -EntryBaseUrl $EntryBaseUrl ` + -ResultPath $sourceResultPath + +$sourceFile = Join-Path $repoRoot $sourceResultPath +$sourceResult = Get-Content -Raw -Path $sourceFile | ConvertFrom-Json +$preflight = Get-PropertyValue -Item $sourceResult -Name "preflight" -Default $null +$preflightJson = Get-PropertyValue -Item $preflight -Name "json" -Default $null +$hints = @((Get-PropertyValue -Item $preflightJson -Name "action_hints" -Default @())) + +$checks = [ordered]@{ + source_smoke_passed = ([bool]$sourceResult.passed) + stale_preflight_detected = ([bool](Get-PropertyValue -Item $preflightJson -Name "stale_cursor" -Default $false) -and [string](Get-PropertyValue -Item $preflightJson -Name "diagnostic_state" -Default "") -eq "stale_cursor_gap") + recommended_action_visible = ([string](Get-PropertyValue -Item $preflightJson -Name "recommended_action" -Default "") -eq "reset_consumer_and_resync") + reset_hint_visible = $hints.Contains("reset_consumer_cursor") + resync_hint_visible = $hints.Contains("request_full_adapter_resync") + checkpoint_hint_visible = $hints.Contains("resume_from_checkpoint_after_resync") +} +$failed = @($checks.GetEnumerator() | Where-Object { -not $_.Value } | ForEach-Object { $_.Key }) + +$result = [ordered]@{ + schema_version = "c19z4.remote_workspace_mailbox_preflight_action_hints_smoke.v1" + source_result_path = $sourceFile + cluster_id = $ClusterID + adapter_session_id = [string](Get-PropertyValue -Item $sourceResult -Name "adapter_session_id" -Default "") + preflight = $preflight + checks = $checks + failed_checks = $failed + passed = ($failed.Count -eq 0) +} + +$fullResultPath = Join-Path $repoRoot $ResultPath +$resultDir = Split-Path -Parent $fullResultPath +if ($resultDir) { New-Item -ItemType Directory -Force -Path $resultDir | Out-Null } +$result | ConvertTo-Json -Depth 100 | Set-Content -Encoding UTF8 -Path $fullResultPath + +if (-not $result.passed) { + throw "C19Z4 remote workspace mailbox preflight action hints smoke failed. Result: $fullResultPath Failed: $($failed -join ', ')" +} + +Write-Host "C19Z4 remote workspace mailbox preflight action hints smoke passed. Result: $fullResultPath" +$result diff --git a/scripts/fabric/c19z40-remote-workspace-real-adapter-handoff-report-smoke.ps1 b/scripts/fabric/c19z40-remote-workspace-real-adapter-handoff-report-smoke.ps1 new file mode 100644 index 0000000..b98b2cd --- /dev/null +++ b/scripts/fabric/c19z40-remote-workspace-real-adapter-handoff-report-smoke.ps1 @@ -0,0 +1,205 @@ +param( + [string]$ApiBaseUrl = "http://192.168.200.61:18121/api/v1", + [string]$ClusterID = "cfc0743d-d960-49fb-9de8-96e063d5e4aa", + [string]$ActorUserID = "f67d943f-5397-4b3a-a229-695fe67ad700", + [string]$RequestedNodeName = "test-1", + [string]$DefaultNodeName = "test-2", + [string]$ResultPath = "artifacts\c19z40-remote-workspace-real-adapter-handoff-report-smoke-result.json" +) + +Set-StrictMode -Version Latest +$ErrorActionPreference = "Stop" + +$repoRoot = (Resolve-Path (Join-Path $PSScriptRoot "..\..")).ProviderPath +$runId = "c19z40-" + (Get-Date -Format "yyyyMMdd-HHmmss") +$requiredEnv = @( + "RAP_REMOTE_WORKSPACE_REAL_ADAPTER_ENABLED", + "RAP_REMOTE_WORKSPACE_REAL_ADAPTER_COMMAND", + "RAP_REMOTE_WORKSPACE_REAL_ADAPTER_ARGS_JSON", + "RAP_REMOTE_WORKSPACE_REAL_ADAPTER_WORKDIR" +) +$requiredGates = @( + "real_runtime_stage_enabled", + "fabric_service_channel_runtime_ready", + "adapter_process_supervisor_enabled", + "payload_forwarding_contract_enabled" +) + +function Invoke-Api { + param([string]$Method, [string]$Path, [object]$Body = $null) + if ($null -eq $Body) { + return Invoke-RestMethod -Method $Method -Uri "$ApiBaseUrl$Path" -TimeoutSec 30 + } + return Invoke-RestMethod -Method $Method -Uri "$ApiBaseUrl$Path" -ContentType "application/json" -Body ($Body | ConvertTo-Json -Depth 80) -TimeoutSec 30 +} + +function Get-PropertyValue { + param([object]$Item, [string]$Name, [object]$Default = $null) + if ($null -eq $Item) { return $Default } + $property = $Item.PSObject.Properties[$Name] + if ($null -eq $property) { return $Default } + return $property.Value +} + +function Test-ArrayItem { + param([object]$Items, [string]$Want) + foreach ($item in @($Items)) { + if ([string]$item -eq $Want) { return $true } + } + return $false +} + +function Test-RequiredItems { + param([object]$Items, [string[]]$Required) + foreach ($item in $Required) { + if (-not (Test-ArrayItem -Items $Items -Want $item)) { return $false } + } + return $true +} + +function Get-NodeByName { + param([string]$Name) + $nodes = (Invoke-Api -Method GET -Path "/clusters/$ClusterID/nodes?actor_user_id=$ActorUserID").nodes + $node = @($nodes | Where-Object { $_.name -eq $Name }) | Select-Object -First 1 + if ($null -eq $node) { throw "Node '$Name' was not found in cluster $ClusterID" } + return $node +} + +function Enable-RdpWorkerProbe { + param([object]$Node) + Invoke-Api -Method PUT -Path "/clusters/$ClusterID/nodes/$($Node.id)/workloads/rdp-worker/desired" -Body @{ + actor_user_id = $ActorUserID + desired_state = "enabled" + version = "c19z40-real-adapter-handoff-report-$runId" + runtime_mode = "native" + artifact_ref = "builtin:rdp-worker-adapter-contract-probe" + config = @{ adapter_contract_probe = $true; service_class = "remote_workspace"; run_id = $runId } + environment = @{} + } | Out-Null +} + +function Get-RdpWorkerStatus { + param([string]$NodeID) + $statuses = (Invoke-Api -Method GET -Path "/clusters/$ClusterID/nodes/$NodeID/workloads/status?actor_user_id=$ActorUserID").workload_statuses + return @($statuses | Where-Object { $_.service_type -eq "rdp-worker" } | Select-Object -First 1) +} + +function New-HandoffRow { + param([object]$Node, [object]$Status, [bool]$ExpectedEnabledRequested, [string]$ExpectedArgsShape) + $payload = Get-PropertyValue -Item $Status -Name "status_payload" -Default $null + $contract = Get-PropertyValue -Item $payload -Name "real_adapter_supervision" -Default $null + $projection = Get-PropertyValue -Item $contract -Name "config_projection" -Default $null + $decision = Get-PropertyValue -Item $contract -Name "activation_decision" -Default $null + + $enabledRequested = [bool](Get-PropertyValue -Item $projection -Name "enabled_requested" -Default $false) + $decisionEnabledRequested = [bool](Get-PropertyValue -Item $decision -Name "enabled_requested" -Default (-not $enabledRequested)) + $contractCompatible = ( + [string](Get-PropertyValue -Item $contract -Name "schema_version" -Default "") -eq "rap.remote_workspace_real_adapter_supervision.v1" -and + -not [bool](Get-PropertyValue -Item $contract -Name "enabled" -Default $true) -and + [string](Get-PropertyValue -Item $contract -Name "activation_state" -Default "") -eq "disabled_until_real_runtime_stage" -and + [string](Get-PropertyValue -Item $contract -Name "payload_traffic" -Default "") -eq "none" -and + (Test-RequiredItems -Items (Get-PropertyValue -Item $contract -Name "config_env" -Default @()) -Required $requiredEnv) + ) + $projectionCompatible = ( + [string](Get-PropertyValue -Item $projection -Name "schema_version" -Default "") -eq "rap.remote_workspace_real_adapter_config_projection.v1" -and + $enabledRequested -eq $ExpectedEnabledRequested -and + -not [bool](Get-PropertyValue -Item $projection -Name "activation_allowed" -Default $true) -and + [string](Get-PropertyValue -Item $projection -Name "args_json_shape" -Default "") -eq $ExpectedArgsShape -and + [bool](Get-PropertyValue -Item $projection -Name "raw_values_redacted" -Default $false) + ) + $decisionCompatible = ( + [string](Get-PropertyValue -Item $decision -Name "schema_version" -Default "") -eq "rap.remote_workspace_real_adapter_activation_decision.v1" -and + [string](Get-PropertyValue -Item $decision -Name "decision" -Default "") -eq "blocked" -and + [string](Get-PropertyValue -Item $decision -Name "reason" -Default "") -eq "real_runtime_stage_not_enabled" -and + $decisionEnabledRequested -eq $ExpectedEnabledRequested -and + -not [bool](Get-PropertyValue -Item $decision -Name "activation_allowed" -Default $true) -and + [string](Get-PropertyValue -Item $decision -Name "payload_traffic" -Default "") -eq "none" -and + (Test-RequiredItems -Items (Get-PropertyValue -Item $decision -Name "required_gates" -Default @()) -Required $requiredGates) -and + (Test-RequiredItems -Items (Get-PropertyValue -Item $decision -Name "missing_gates" -Default @()) -Required $requiredGates) + ) + $aligned = ( + $enabledRequested -eq $decisionEnabledRequested -and + [bool](Get-PropertyValue -Item $projection -Name "activation_allowed" -Default $true) -eq $false -and + [bool](Get-PropertyValue -Item $decision -Name "activation_allowed" -Default $true) -eq $false -and + [string](Get-PropertyValue -Item $contract -Name "payload_traffic" -Default "") -eq [string](Get-PropertyValue -Item $decision -Name "payload_traffic" -Default "") + ) + + return [ordered]@{ + node_id = $Node.id + node_name = $Node.name + reported_state = Get-PropertyValue -Item $Status -Name "reported_state" -Default $null + execution_mode = Get-PropertyValue -Item $payload -Name "execution_mode" -Default $null + enabled_requested = $enabledRequested + args_json_shape = Get-PropertyValue -Item $projection -Name "args_json_shape" -Default $null + activation_decision = Get-PropertyValue -Item $decision -Name "decision" -Default $null + activation_reason = Get-PropertyValue -Item $decision -Name "reason" -Default $null + missing_gates = Get-PropertyValue -Item $decision -Name "missing_gates" -Default @() + contract_compatible = $contractCompatible + projection_compatible = $projectionCompatible + decision_compatible = $decisionCompatible + projection_decision_aligned = $aligned + passed = ($contractCompatible -and $projectionCompatible -and $decisionCompatible -and $aligned) + } +} + +$requestedNode = Get-NodeByName -Name $RequestedNodeName +$defaultNode = Get-NodeByName -Name $DefaultNodeName +Enable-RdpWorkerProbe -Node $requestedNode +Enable-RdpWorkerProbe -Node $defaultNode + +$requestedStatus = $null +$defaultStatus = $null +$rows = @() +$deadline = (Get-Date).AddSeconds(80) +while ((Get-Date) -lt $deadline) { + Start-Sleep -Seconds 5 + $requestedStatus = Get-RdpWorkerStatus -NodeID $requestedNode.id + $defaultStatus = Get-RdpWorkerStatus -NodeID $defaultNode.id + if ($null -ne $requestedStatus -and $null -ne $defaultStatus) { + $rows = @( + (New-HandoffRow -Node $requestedNode -Status $requestedStatus -ExpectedEnabledRequested $true -ExpectedArgsShape "json_array"), + (New-HandoffRow -Node $defaultNode -Status $defaultStatus -ExpectedEnabledRequested $false -ExpectedArgsShape "absent") + ) + if (@($rows | Where-Object { -not $_.passed }).Count -eq 0) { break } + } +} + +if ($rows.Count -eq 0) { + if ($null -ne $requestedStatus) { + $rows += New-HandoffRow -Node $requestedNode -Status $requestedStatus -ExpectedEnabledRequested $true -ExpectedArgsShape "json_array" + } + if ($null -ne $defaultStatus) { + $rows += New-HandoffRow -Node $defaultNode -Status $defaultStatus -ExpectedEnabledRequested $false -ExpectedArgsShape "absent" + } +} + +$checks = [ordered]@{ + requested_status_present = ($null -ne $requestedStatus) + default_status_present = ($null -ne $defaultStatus) + handoff_rows_present = ($rows.Count -eq 2) + all_rows_passed = (@($rows | Where-Object { -not $_.passed }).Count -eq 0) +} +$failed = @($checks.GetEnumerator() | Where-Object { -not $_.Value } | ForEach-Object { $_.Key }) + +$result = [ordered]@{ + schema_version = "c19z40.remote_workspace_real_adapter_handoff_report_smoke.v1" + cluster_id = $ClusterID + required_env = $requiredEnv + required_gates = $requiredGates + handoff_rows = $rows + checks = $checks + failed_checks = $failed + passed = ($failed.Count -eq 0) +} + +$fullResultPath = Join-Path $repoRoot $ResultPath +$resultDir = Split-Path -Parent $fullResultPath +if ($resultDir) { New-Item -ItemType Directory -Force -Path $resultDir | Out-Null } +$result | ConvertTo-Json -Depth 100 | Set-Content -Encoding UTF8 -Path $fullResultPath + +if (-not $result.passed) { + throw "C19Z40 remote workspace real-adapter handoff report smoke failed. Result: $fullResultPath Failed: $($failed -join ', ')" +} + +Write-Host "C19Z40 remote workspace real-adapter handoff report smoke passed. Result: $fullResultPath" +$result diff --git a/scripts/fabric/c19z41-remote-workspace-real-adapter-feature-flags-smoke.ps1 b/scripts/fabric/c19z41-remote-workspace-real-adapter-feature-flags-smoke.ps1 new file mode 100644 index 0000000..a10ddf8 --- /dev/null +++ b/scripts/fabric/c19z41-remote-workspace-real-adapter-feature-flags-smoke.ps1 @@ -0,0 +1,137 @@ +param( + [string]$ApiBaseUrl = "http://192.168.200.61:18121/api/v1", + [string]$ClusterID = "cfc0743d-d960-49fb-9de8-96e063d5e4aa", + [string]$ActorUserID = "f67d943f-5397-4b3a-a229-695fe67ad700", + [string]$RequestedNodeName = "test-1", + [string]$DefaultNodeName = "test-2", + [string]$ResultPath = "artifacts\c19z41-remote-workspace-real-adapter-feature-flags-smoke-result.json" +) + +Set-StrictMode -Version Latest +$ErrorActionPreference = "Stop" + +$repoRoot = (Resolve-Path (Join-Path $PSScriptRoot "..\..")).ProviderPath +$runId = "c19z41-" + (Get-Date -Format "yyyyMMdd-HHmmss") +$requiredFeatures = @("config_projection", "activation_decision", "missing_gates", "raw_values_redacted") + +function Invoke-Api { + param([string]$Method, [string]$Path, [object]$Body = $null) + if ($null -eq $Body) { + return Invoke-RestMethod -Method $Method -Uri "$ApiBaseUrl$Path" -TimeoutSec 30 + } + return Invoke-RestMethod -Method $Method -Uri "$ApiBaseUrl$Path" -ContentType "application/json" -Body ($Body | ConvertTo-Json -Depth 80) -TimeoutSec 30 +} + +function Get-PropertyValue { + param([object]$Item, [string]$Name, [object]$Default = $null) + if ($null -eq $Item) { return $Default } + $property = $Item.PSObject.Properties[$Name] + if ($null -eq $property) { return $Default } + return $property.Value +} + +function Test-ArrayItem { + param([object]$Items, [string]$Want) + foreach ($item in @($Items)) { + if ([string]$item -eq $Want) { return $true } + } + return $false +} + +function Get-NodeByName { + param([string]$Name) + $nodes = (Invoke-Api -Method GET -Path "/clusters/$ClusterID/nodes?actor_user_id=$ActorUserID").nodes + $node = @($nodes | Where-Object { $_.name -eq $Name }) | Select-Object -First 1 + if ($null -eq $node) { throw "Node '$Name' was not found in cluster $ClusterID" } + return $node +} + +function Enable-RdpWorkerProbe { + param([object]$Node) + Invoke-Api -Method PUT -Path "/clusters/$ClusterID/nodes/$($Node.id)/workloads/rdp-worker/desired" -Body @{ + actor_user_id = $ActorUserID + desired_state = "enabled" + version = "c19z41-real-adapter-feature-flags-$runId" + runtime_mode = "native" + artifact_ref = "builtin:rdp-worker-adapter-contract-probe" + config = @{ adapter_contract_probe = $true; service_class = "remote_workspace"; run_id = $runId } + environment = @{} + } | Out-Null +} + +function Get-RdpWorkerStatus { + param([string]$NodeID) + $statuses = (Invoke-Api -Method GET -Path "/clusters/$ClusterID/nodes/$NodeID/workloads/status?actor_user_id=$ActorUserID").workload_statuses + return @($statuses | Where-Object { $_.service_type -eq "rdp-worker" } | Select-Object -First 1) +} + +function Test-Features { + param([object]$Status) + if ($null -eq $Status) { return $false } + $payload = Get-PropertyValue -Item $Status -Name "status_payload" -Default $null + $contract = Get-PropertyValue -Item $payload -Name "real_adapter_supervision" -Default $null + $features = Get-PropertyValue -Item $contract -Name "features" -Default $null + $statusContract = Get-PropertyValue -Item $contract -Name "status_contract" -Default @() + if ($null -eq $features) { return $false } + foreach ($feature in $requiredFeatures) { + if (-not [bool](Get-PropertyValue -Item $features -Name $feature -Default $false)) { return $false } + } + return ( + (Test-ArrayItem -Items $statusContract -Want "features") -and + (Test-ArrayItem -Items $statusContract -Want "config_projection") -and + (Test-ArrayItem -Items $statusContract -Want "activation_decision") -and + [string](Get-PropertyValue -Item $payload -Name "execution_mode" -Default "") -eq "contract_probe" -and + -not [bool](Get-PropertyValue -Item $contract -Name "enabled" -Default $true) -and + [string](Get-PropertyValue -Item $contract -Name "payload_traffic" -Default "") -eq "none" + ) +} + +$requestedNode = Get-NodeByName -Name $RequestedNodeName +$defaultNode = Get-NodeByName -Name $DefaultNodeName +Enable-RdpWorkerProbe -Node $requestedNode +Enable-RdpWorkerProbe -Node $defaultNode + +$requestedStatus = $null +$defaultStatus = $null +$deadline = (Get-Date).AddSeconds(80) +while ((Get-Date) -lt $deadline) { + Start-Sleep -Seconds 5 + $requestedStatus = Get-RdpWorkerStatus -NodeID $requestedNode.id + $defaultStatus = Get-RdpWorkerStatus -NodeID $defaultNode.id + if ((Test-Features -Status $requestedStatus) -and (Test-Features -Status $defaultStatus)) { break } +} + +$requestedContract = Get-PropertyValue -Item (Get-PropertyValue -Item $requestedStatus -Name "status_payload" -Default $null) -Name "real_adapter_supervision" -Default $null +$defaultContract = Get-PropertyValue -Item (Get-PropertyValue -Item $defaultStatus -Name "status_payload" -Default $null) -Name "real_adapter_supervision" -Default $null +$checks = [ordered]@{ + requested_status_present = ($null -ne $requestedStatus) + default_status_present = ($null -ne $defaultStatus) + requested_features_visible = (Test-Features -Status $requestedStatus) + default_features_visible = (Test-Features -Status $defaultStatus) +} +$failed = @($checks.GetEnumerator() | Where-Object { -not $_.Value } | ForEach-Object { $_.Key }) + +$result = [ordered]@{ + schema_version = "c19z41.remote_workspace_real_adapter_feature_flags_smoke.v1" + cluster_id = $ClusterID + required_features = $requiredFeatures + requested_node = [ordered]@{ id = $requestedNode.id; name = $requestedNode.name } + default_node = [ordered]@{ id = $defaultNode.id; name = $defaultNode.name } + requested_features = Get-PropertyValue -Item $requestedContract -Name "features" -Default $null + default_features = Get-PropertyValue -Item $defaultContract -Name "features" -Default $null + checks = $checks + failed_checks = $failed + passed = ($failed.Count -eq 0) +} + +$fullResultPath = Join-Path $repoRoot $ResultPath +$resultDir = Split-Path -Parent $fullResultPath +if ($resultDir) { New-Item -ItemType Directory -Force -Path $resultDir | Out-Null } +$result | ConvertTo-Json -Depth 100 | Set-Content -Encoding UTF8 -Path $fullResultPath + +if (-not $result.passed) { + throw "C19Z41 remote workspace real-adapter feature flags smoke failed. Result: $fullResultPath Failed: $($failed -join ', ')" +} + +Write-Host "C19Z41 remote workspace real-adapter feature flags smoke passed. Result: $fullResultPath" +$result diff --git a/scripts/fabric/c19z42-remote-workspace-real-adapter-handoff-v2-smoke.ps1 b/scripts/fabric/c19z42-remote-workspace-real-adapter-handoff-v2-smoke.ps1 new file mode 100644 index 0000000..c536f6a --- /dev/null +++ b/scripts/fabric/c19z42-remote-workspace-real-adapter-handoff-v2-smoke.ps1 @@ -0,0 +1,220 @@ +param( + [string]$ApiBaseUrl = "http://192.168.200.61:18121/api/v1", + [string]$ClusterID = "cfc0743d-d960-49fb-9de8-96e063d5e4aa", + [string]$ActorUserID = "f67d943f-5397-4b3a-a229-695fe67ad700", + [string]$RequestedNodeName = "test-1", + [string]$DefaultNodeName = "test-2", + [string]$ResultPath = "artifacts\c19z42-remote-workspace-real-adapter-handoff-v2-smoke-result.json" +) + +Set-StrictMode -Version Latest +$ErrorActionPreference = "Stop" + +$repoRoot = (Resolve-Path (Join-Path $PSScriptRoot "..\..")).ProviderPath +$runId = "c19z42-" + (Get-Date -Format "yyyyMMdd-HHmmss") +$requiredEnv = @( + "RAP_REMOTE_WORKSPACE_REAL_ADAPTER_ENABLED", + "RAP_REMOTE_WORKSPACE_REAL_ADAPTER_COMMAND", + "RAP_REMOTE_WORKSPACE_REAL_ADAPTER_ARGS_JSON", + "RAP_REMOTE_WORKSPACE_REAL_ADAPTER_WORKDIR" +) +$requiredGates = @( + "real_runtime_stage_enabled", + "fabric_service_channel_runtime_ready", + "adapter_process_supervisor_enabled", + "payload_forwarding_contract_enabled" +) +$requiredFeatures = @("config_projection", "activation_decision", "missing_gates", "raw_values_redacted") + +function Invoke-Api { + param([string]$Method, [string]$Path, [object]$Body = $null) + if ($null -eq $Body) { + return Invoke-RestMethod -Method $Method -Uri "$ApiBaseUrl$Path" -TimeoutSec 30 + } + return Invoke-RestMethod -Method $Method -Uri "$ApiBaseUrl$Path" -ContentType "application/json" -Body ($Body | ConvertTo-Json -Depth 80) -TimeoutSec 30 +} + +function Get-PropertyValue { + param([object]$Item, [string]$Name, [object]$Default = $null) + if ($null -eq $Item) { return $Default } + $property = $Item.PSObject.Properties[$Name] + if ($null -eq $property) { return $Default } + return $property.Value +} + +function Test-ArrayItem { + param([object]$Items, [string]$Want) + foreach ($item in @($Items)) { + if ([string]$item -eq $Want) { return $true } + } + return $false +} + +function Test-RequiredItems { + param([object]$Items, [string[]]$Required) + foreach ($item in $Required) { + if (-not (Test-ArrayItem -Items $Items -Want $item)) { return $false } + } + return $true +} + +function Test-FeatureMap { + param([object]$Features) + if ($null -eq $Features) { return $false } + foreach ($feature in $requiredFeatures) { + if (-not [bool](Get-PropertyValue -Item $Features -Name $feature -Default $false)) { return $false } + } + return $true +} + +function Get-NodeByName { + param([string]$Name) + $nodes = (Invoke-Api -Method GET -Path "/clusters/$ClusterID/nodes?actor_user_id=$ActorUserID").nodes + $node = @($nodes | Where-Object { $_.name -eq $Name }) | Select-Object -First 1 + if ($null -eq $node) { throw "Node '$Name' was not found in cluster $ClusterID" } + return $node +} + +function Enable-RdpWorkerProbe { + param([object]$Node) + Invoke-Api -Method PUT -Path "/clusters/$ClusterID/nodes/$($Node.id)/workloads/rdp-worker/desired" -Body @{ + actor_user_id = $ActorUserID + desired_state = "enabled" + version = "c19z42-real-adapter-handoff-v2-$runId" + runtime_mode = "native" + artifact_ref = "builtin:rdp-worker-adapter-contract-probe" + config = @{ adapter_contract_probe = $true; service_class = "remote_workspace"; run_id = $runId } + environment = @{} + } | Out-Null +} + +function Get-RdpWorkerStatus { + param([string]$NodeID) + $statuses = (Invoke-Api -Method GET -Path "/clusters/$ClusterID/nodes/$NodeID/workloads/status?actor_user_id=$ActorUserID").workload_statuses + return @($statuses | Where-Object { $_.service_type -eq "rdp-worker" } | Select-Object -First 1) +} + +function New-HandoffRow { + param([object]$Node, [object]$Status, [bool]$ExpectedEnabledRequested, [string]$ExpectedArgsShape) + $payload = Get-PropertyValue -Item $Status -Name "status_payload" -Default $null + $contract = Get-PropertyValue -Item $payload -Name "real_adapter_supervision" -Default $null + $projection = Get-PropertyValue -Item $contract -Name "config_projection" -Default $null + $decision = Get-PropertyValue -Item $contract -Name "activation_decision" -Default $null + $features = Get-PropertyValue -Item $contract -Name "features" -Default $null + $statusContract = Get-PropertyValue -Item $contract -Name "status_contract" -Default @() + $enabledRequested = [bool](Get-PropertyValue -Item $projection -Name "enabled_requested" -Default $false) + $decisionEnabledRequested = [bool](Get-PropertyValue -Item $decision -Name "enabled_requested" -Default (-not $enabledRequested)) + + $contractCompatible = ( + [string](Get-PropertyValue -Item $contract -Name "schema_version" -Default "") -eq "rap.remote_workspace_real_adapter_supervision.v1" -and + -not [bool](Get-PropertyValue -Item $contract -Name "enabled" -Default $true) -and + [string](Get-PropertyValue -Item $contract -Name "activation_state" -Default "") -eq "disabled_until_real_runtime_stage" -and + [string](Get-PropertyValue -Item $contract -Name "payload_traffic" -Default "") -eq "none" -and + (Test-RequiredItems -Items (Get-PropertyValue -Item $contract -Name "config_env" -Default @()) -Required $requiredEnv) -and + (Test-ArrayItem -Items $statusContract -Want "features") + ) + $projectionCompatible = ( + [string](Get-PropertyValue -Item $projection -Name "schema_version" -Default "") -eq "rap.remote_workspace_real_adapter_config_projection.v1" -and + $enabledRequested -eq $ExpectedEnabledRequested -and + -not [bool](Get-PropertyValue -Item $projection -Name "activation_allowed" -Default $true) -and + [string](Get-PropertyValue -Item $projection -Name "args_json_shape" -Default "") -eq $ExpectedArgsShape -and + [bool](Get-PropertyValue -Item $projection -Name "raw_values_redacted" -Default $false) + ) + $decisionCompatible = ( + [string](Get-PropertyValue -Item $decision -Name "schema_version" -Default "") -eq "rap.remote_workspace_real_adapter_activation_decision.v1" -and + [string](Get-PropertyValue -Item $decision -Name "decision" -Default "") -eq "blocked" -and + [string](Get-PropertyValue -Item $decision -Name "reason" -Default "") -eq "real_runtime_stage_not_enabled" -and + $decisionEnabledRequested -eq $ExpectedEnabledRequested -and + -not [bool](Get-PropertyValue -Item $decision -Name "activation_allowed" -Default $true) -and + [string](Get-PropertyValue -Item $decision -Name "payload_traffic" -Default "") -eq "none" -and + (Test-RequiredItems -Items (Get-PropertyValue -Item $decision -Name "required_gates" -Default @()) -Required $requiredGates) -and + (Test-RequiredItems -Items (Get-PropertyValue -Item $decision -Name "missing_gates" -Default @()) -Required $requiredGates) + ) + $featuresCompatible = ( + (Test-FeatureMap -Features $features) -and + (Test-ArrayItem -Items $statusContract -Want "config_projection") -and + (Test-ArrayItem -Items $statusContract -Want "activation_decision") -and + (Test-ArrayItem -Items $statusContract -Want "features") + ) + $aligned = ( + $enabledRequested -eq $decisionEnabledRequested -and + [bool](Get-PropertyValue -Item $projection -Name "activation_allowed" -Default $true) -eq $false -and + [bool](Get-PropertyValue -Item $decision -Name "activation_allowed" -Default $true) -eq $false -and + [string](Get-PropertyValue -Item $contract -Name "payload_traffic" -Default "") -eq [string](Get-PropertyValue -Item $decision -Name "payload_traffic" -Default "") -and + [bool](Get-PropertyValue -Item $features -Name "config_projection" -Default $false) -eq ($null -ne $projection) -and + [bool](Get-PropertyValue -Item $features -Name "activation_decision" -Default $false) -eq ($null -ne $decision) + ) + + return [ordered]@{ + node_id = $Node.id + node_name = $Node.name + reported_state = Get-PropertyValue -Item $Status -Name "reported_state" -Default $null + execution_mode = Get-PropertyValue -Item $payload -Name "execution_mode" -Default $null + enabled_requested = $enabledRequested + args_json_shape = Get-PropertyValue -Item $projection -Name "args_json_shape" -Default $null + activation_decision = Get-PropertyValue -Item $decision -Name "decision" -Default $null + activation_reason = Get-PropertyValue -Item $decision -Name "reason" -Default $null + feature_flags = $features + missing_gates = Get-PropertyValue -Item $decision -Name "missing_gates" -Default @() + contract_compatible = $contractCompatible + projection_compatible = $projectionCompatible + decision_compatible = $decisionCompatible + features_compatible = $featuresCompatible + projection_decision_features_aligned = $aligned + passed = ($contractCompatible -and $projectionCompatible -and $decisionCompatible -and $featuresCompatible -and $aligned) + } +} + +$requestedNode = Get-NodeByName -Name $RequestedNodeName +$defaultNode = Get-NodeByName -Name $DefaultNodeName +Enable-RdpWorkerProbe -Node $requestedNode +Enable-RdpWorkerProbe -Node $defaultNode + +$requestedStatus = $null +$defaultStatus = $null +$rows = @() +$deadline = (Get-Date).AddSeconds(80) +while ((Get-Date) -lt $deadline) { + Start-Sleep -Seconds 5 + $requestedStatus = Get-RdpWorkerStatus -NodeID $requestedNode.id + $defaultStatus = Get-RdpWorkerStatus -NodeID $defaultNode.id + if ($null -ne $requestedStatus -and $null -ne $defaultStatus) { + $rows = @( + (New-HandoffRow -Node $requestedNode -Status $requestedStatus -ExpectedEnabledRequested $true -ExpectedArgsShape "json_array"), + (New-HandoffRow -Node $defaultNode -Status $defaultStatus -ExpectedEnabledRequested $false -ExpectedArgsShape "absent") + ) + if (@($rows | Where-Object { -not $_.passed }).Count -eq 0) { break } + } +} + +$checks = [ordered]@{ + requested_status_present = ($null -ne $requestedStatus) + default_status_present = ($null -ne $defaultStatus) + handoff_rows_present = ($rows.Count -eq 2) + all_rows_passed = (@($rows | Where-Object { -not $_.passed }).Count -eq 0) +} +$failed = @($checks.GetEnumerator() | Where-Object { -not $_.Value } | ForEach-Object { $_.Key }) + +$result = [ordered]@{ + schema_version = "c19z42.remote_workspace_real_adapter_handoff_v2_smoke.v1" + cluster_id = $ClusterID + required_env = $requiredEnv + required_gates = $requiredGates + required_features = $requiredFeatures + handoff_rows = $rows + checks = $checks + failed_checks = $failed + passed = ($failed.Count -eq 0) +} + +$fullResultPath = Join-Path $repoRoot $ResultPath +$resultDir = Split-Path -Parent $fullResultPath +if ($resultDir) { New-Item -ItemType Directory -Force -Path $resultDir | Out-Null } +$result | ConvertTo-Json -Depth 100 | Set-Content -Encoding UTF8 -Path $fullResultPath + +if (-not $result.passed) { + throw "C19Z42 remote workspace real-adapter handoff v2 smoke failed. Result: $fullResultPath Failed: $($failed -join ', ')" +} + +Write-Host "C19Z42 remote workspace real-adapter handoff v2 smoke passed. Result: $fullResultPath" +$result diff --git a/scripts/fabric/c19z43-remote-workspace-real-adapter-precedence-smoke.ps1 b/scripts/fabric/c19z43-remote-workspace-real-adapter-precedence-smoke.ps1 new file mode 100644 index 0000000..45c59b5 --- /dev/null +++ b/scripts/fabric/c19z43-remote-workspace-real-adapter-precedence-smoke.ps1 @@ -0,0 +1,133 @@ +param( + [string]$ApiBaseUrl = "http://192.168.200.61:18121/api/v1", + [string]$ClusterID = "cfc0743d-d960-49fb-9de8-96e063d5e4aa", + [string]$ActorUserID = "f67d943f-5397-4b3a-a229-695fe67ad700", + [string]$NodeName = "test-1", + [string]$ResultPath = "artifacts\c19z43-remote-workspace-real-adapter-precedence-smoke-result.json" +) + +Set-StrictMode -Version Latest +$ErrorActionPreference = "Stop" + +$repoRoot = (Resolve-Path (Join-Path $PSScriptRoot "..\..")).ProviderPath +$runId = "c19z43-" + (Get-Date -Format "yyyyMMdd-HHmmss") + +function Invoke-Api { + param([string]$Method, [string]$Path, [object]$Body = $null) + if ($null -eq $Body) { + return Invoke-RestMethod -Method $Method -Uri "$ApiBaseUrl$Path" -TimeoutSec 30 + } + return Invoke-RestMethod -Method $Method -Uri "$ApiBaseUrl$Path" -ContentType "application/json" -Body ($Body | ConvertTo-Json -Depth 80) -TimeoutSec 30 +} + +function Get-PropertyValue { + param([object]$Item, [string]$Name, [object]$Default = $null) + if ($null -eq $Item) { return $Default } + $property = $Item.PSObject.Properties[$Name] + if ($null -eq $property) { return $Default } + return $property.Value +} + +function Get-NodeByName { + param([string]$Name) + $nodes = (Invoke-Api -Method GET -Path "/clusters/$ClusterID/nodes?actor_user_id=$ActorUserID").nodes + $node = @($nodes | Where-Object { $_.name -eq $Name }) | Select-Object -First 1 + if ($null -eq $node) { throw "Node '$Name' was not found in cluster $ClusterID" } + return $node +} + +function Get-RdpWorkerStatus { + param([string]$NodeID) + $statuses = (Invoke-Api -Method GET -Path "/clusters/$ClusterID/nodes/$NodeID/workloads/status?actor_user_id=$ActorUserID").workload_statuses + return @($statuses | Where-Object { $_.service_type -eq "rdp-worker" } | Select-Object -First 1) +} + +function Test-Precedence { + param([object]$Status) + if ($null -eq $Status) { return $false } + $payload = Get-PropertyValue -Item $Status -Name "status_payload" -Default $null + $contract = Get-PropertyValue -Item $payload -Name "real_adapter_supervision" -Default $null + $decision = Get-PropertyValue -Item $contract -Name "activation_decision" -Default $null + return ( + [string](Get-PropertyValue -Item $Status -Name "reported_state" -Default "") -eq "running" -and + [string](Get-PropertyValue -Item $payload -Name "reason" -Default "") -eq "remote_workspace_adapter_contract_probe_ready" -and + [string](Get-PropertyValue -Item $payload -Name "execution_mode" -Default "") -eq "contract_probe" -and + [string](Get-PropertyValue -Item $payload -Name "traffic" -Default "") -eq "none" -and + [string](Get-PropertyValue -Item $payload -Name "payload_traffic" -Default "none") -eq "none" -and + [string](Get-PropertyValue -Item $contract -Name "schema_version" -Default "") -eq "rap.remote_workspace_real_adapter_supervision.v1" -and + -not [bool](Get-PropertyValue -Item $contract -Name "enabled" -Default $true) -and + [string](Get-PropertyValue -Item $contract -Name "payload_traffic" -Default "") -eq "none" -and + [string](Get-PropertyValue -Item $decision -Name "decision" -Default "") -eq "blocked" -and + [string](Get-PropertyValue -Item $decision -Name "reason" -Default "") -eq "real_runtime_stage_not_enabled" -and + [string](Get-PropertyValue -Item $decision -Name "payload_traffic" -Default "") -eq "none" + ) +} + +$node = Get-NodeByName -Name $NodeName +Invoke-Api -Method PUT -Path "/clusters/$ClusterID/nodes/$($node.id)/workloads/rdp-worker/desired" -Body @{ + actor_user_id = $ActorUserID + desired_state = "enabled" + version = "c19z43-real-adapter-precedence-$runId" + runtime_mode = "native" + artifact_ref = "builtin:rdp-worker-adapter-contract-probe" + config = @{ + adapter_contract_probe = $true + real_adapter_supervision = $true + service_class = "remote_workspace" + run_id = $runId + } + environment = @{} +} | Out-Null + +$status = $null +$deadline = (Get-Date).AddSeconds(80) +while ((Get-Date) -lt $deadline) { + Start-Sleep -Seconds 5 + $status = Get-RdpWorkerStatus -NodeID $node.id + if (Test-Precedence -Status $status) { break } +} + +$payload = Get-PropertyValue -Item $status -Name "status_payload" -Default $null +$contract = Get-PropertyValue -Item $payload -Name "real_adapter_supervision" -Default $null +$decision = Get-PropertyValue -Item $contract -Name "activation_decision" -Default $null +$checks = [ordered]@{ + status_present = ($null -ne $status) + reported_running = ([string](Get-PropertyValue -Item $status -Name "reported_state" -Default "") -eq "running") + contract_probe_precedence = ([string](Get-PropertyValue -Item $payload -Name "execution_mode" -Default "") -eq "contract_probe") + real_adapter_branch_not_selected = ([string](Get-PropertyValue -Item $payload -Name "reason" -Default "") -ne "remote_workspace_real_adapter_supervision_disabled") + payload_traffic_none = ([string](Get-PropertyValue -Item $payload -Name "traffic" -Default "") -eq "none" -and [string](Get-PropertyValue -Item $decision -Name "payload_traffic" -Default "") -eq "none") + activation_decision_blocked = ([string](Get-PropertyValue -Item $decision -Name "decision" -Default "") -eq "blocked") + precedence_contract_passed = (Test-Precedence -Status $status) +} +$failed = @($checks.GetEnumerator() | Where-Object { -not $_.Value } | ForEach-Object { $_.Key }) + +$result = [ordered]@{ + schema_version = "c19z43.remote_workspace_real_adapter_precedence_smoke.v1" + run_id = $runId + cluster_id = $ClusterID + node = [ordered]@{ id = $node.id; name = $node.name } + requested_config = [ordered]@{ adapter_contract_probe = $true; real_adapter_supervision = $true } + observed = [ordered]@{ + reported_state = Get-PropertyValue -Item $status -Name "reported_state" -Default $null + reason = Get-PropertyValue -Item $payload -Name "reason" -Default $null + execution_mode = Get-PropertyValue -Item $payload -Name "execution_mode" -Default $null + traffic = Get-PropertyValue -Item $payload -Name "traffic" -Default $null + activation_decision = Get-PropertyValue -Item $decision -Name "decision" -Default $null + activation_reason = Get-PropertyValue -Item $decision -Name "reason" -Default $null + } + checks = $checks + failed_checks = $failed + passed = ($failed.Count -eq 0) +} + +$fullResultPath = Join-Path $repoRoot $ResultPath +$resultDir = Split-Path -Parent $fullResultPath +if ($resultDir) { New-Item -ItemType Directory -Force -Path $resultDir | Out-Null } +$result | ConvertTo-Json -Depth 100 | Set-Content -Encoding UTF8 -Path $fullResultPath + +if (-not $result.passed) { + throw "C19Z43 remote workspace real-adapter precedence smoke failed. Result: $fullResultPath Failed: $($failed -join ', ')" +} + +Write-Host "C19Z43 remote workspace real-adapter precedence smoke passed. Result: $fullResultPath" +$result diff --git a/scripts/fabric/c19z44-remote-workspace-real-adapter-only-disabled-smoke.ps1 b/scripts/fabric/c19z44-remote-workspace-real-adapter-only-disabled-smoke.ps1 new file mode 100644 index 0000000..bc2e492 --- /dev/null +++ b/scripts/fabric/c19z44-remote-workspace-real-adapter-only-disabled-smoke.ps1 @@ -0,0 +1,176 @@ +param( + [string]$ApiBaseUrl = "http://192.168.200.61:18121/api/v1", + [string]$ClusterID = "cfc0743d-d960-49fb-9de8-96e063d5e4aa", + [string]$ActorUserID = "f67d943f-5397-4b3a-a229-695fe67ad700", + [string]$NodeName = "test-1", + [string]$ResultPath = "artifacts\c19z44-remote-workspace-real-adapter-only-disabled-smoke-result.json" +) + +Set-StrictMode -Version Latest +$ErrorActionPreference = "Stop" + +$repoRoot = (Resolve-Path (Join-Path $PSScriptRoot "..\..")).ProviderPath +$runId = "c19z44-" + (Get-Date -Format "yyyyMMdd-HHmmss") +$requiredFeatures = @("config_projection", "activation_decision", "missing_gates", "raw_values_redacted") +$requiredGates = @( + "real_runtime_stage_enabled", + "fabric_service_channel_runtime_ready", + "adapter_process_supervisor_enabled", + "payload_forwarding_contract_enabled" +) + +function Invoke-Api { + param([string]$Method, [string]$Path, [object]$Body = $null) + if ($null -eq $Body) { + return Invoke-RestMethod -Method $Method -Uri "$ApiBaseUrl$Path" -TimeoutSec 30 + } + return Invoke-RestMethod -Method $Method -Uri "$ApiBaseUrl$Path" -ContentType "application/json" -Body ($Body | ConvertTo-Json -Depth 80) -TimeoutSec 30 +} + +function Get-PropertyValue { + param([object]$Item, [string]$Name, [object]$Default = $null) + if ($null -eq $Item) { return $Default } + $property = $Item.PSObject.Properties[$Name] + if ($null -eq $property) { return $Default } + return $property.Value +} + +function Test-ArrayItem { + param([object]$Items, [string]$Want) + foreach ($item in @($Items)) { + if ([string]$item -eq $Want) { return $true } + } + return $false +} + +function Test-RequiredItems { + param([object]$Items, [string[]]$Required) + foreach ($item in $Required) { + if (-not (Test-ArrayItem -Items $Items -Want $item)) { return $false } + } + return $true +} + +function Test-FeatureMap { + param([object]$Features) + if ($null -eq $Features) { return $false } + foreach ($feature in $requiredFeatures) { + if (-not [bool](Get-PropertyValue -Item $Features -Name $feature -Default $false)) { return $false } + } + return $true +} + +function Get-NodeByName { + param([string]$Name) + $nodes = (Invoke-Api -Method GET -Path "/clusters/$ClusterID/nodes?actor_user_id=$ActorUserID").nodes + $node = @($nodes | Where-Object { $_.name -eq $Name }) | Select-Object -First 1 + if ($null -eq $node) { throw "Node '$Name' was not found in cluster $ClusterID" } + return $node +} + +function Get-RdpWorkerStatus { + param([string]$NodeID) + $statuses = (Invoke-Api -Method GET -Path "/clusters/$ClusterID/nodes/$NodeID/workloads/status?actor_user_id=$ActorUserID").workload_statuses + return @($statuses | Where-Object { $_.service_type -eq "rdp-worker" } | Select-Object -First 1) +} + +function Test-RealAdapterOnlyDisabled { + param([object]$Status) + if ($null -eq $Status) { return $false } + $payload = Get-PropertyValue -Item $Status -Name "status_payload" -Default $null + $contract = Get-PropertyValue -Item $payload -Name "real_adapter_supervision" -Default $null + $projection = Get-PropertyValue -Item $contract -Name "config_projection" -Default $null + $decision = Get-PropertyValue -Item $contract -Name "activation_decision" -Default $null + $features = Get-PropertyValue -Item $contract -Name "features" -Default $null + return ( + [string](Get-PropertyValue -Item $Status -Name "reported_state" -Default "") -eq "degraded" -and + [string](Get-PropertyValue -Item $payload -Name "reason" -Default "") -eq "remote_workspace_real_adapter_supervision_disabled" -and + [string](Get-PropertyValue -Item $payload -Name "execution_mode" -Default "") -eq "real_adapter_supervision_disabled" -and + [string](Get-PropertyValue -Item $payload -Name "traffic" -Default "") -eq "blocked" -and + [string](Get-PropertyValue -Item $payload -Name "payload_traffic" -Default "") -eq "none" -and + [string](Get-PropertyValue -Item $contract -Name "schema_version" -Default "") -eq "rap.remote_workspace_real_adapter_supervision.v1" -and + -not [bool](Get-PropertyValue -Item $contract -Name "enabled" -Default $true) -and + [string](Get-PropertyValue -Item $contract -Name "activation_state" -Default "") -eq "disabled_until_real_runtime_stage" -and + [bool](Get-PropertyValue -Item $projection -Name "enabled_requested" -Default $false) -and + -not [bool](Get-PropertyValue -Item $projection -Name "activation_allowed" -Default $true) -and + [string](Get-PropertyValue -Item $decision -Name "decision" -Default "") -eq "blocked" -and + [string](Get-PropertyValue -Item $decision -Name "reason" -Default "") -eq "real_runtime_stage_not_enabled" -and + [bool](Get-PropertyValue -Item $decision -Name "enabled_requested" -Default $false) -and + -not [bool](Get-PropertyValue -Item $decision -Name "activation_allowed" -Default $true) -and + [string](Get-PropertyValue -Item $decision -Name "payload_traffic" -Default "") -eq "none" -and + (Test-RequiredItems -Items (Get-PropertyValue -Item $decision -Name "missing_gates" -Default @()) -Required $requiredGates) -and + (Test-FeatureMap -Features $features) + ) +} + +$node = Get-NodeByName -Name $NodeName +Invoke-Api -Method PUT -Path "/clusters/$ClusterID/nodes/$($node.id)/workloads/rdp-worker/desired" -Body @{ + actor_user_id = $ActorUserID + desired_state = "enabled" + version = "c19z44-real-adapter-only-disabled-$runId" + runtime_mode = "native" + artifact_ref = "builtin:rdp-worker-real-adapter-disabled-probe" + config = @{ + real_adapter_supervision = $true + service_class = "remote_workspace" + run_id = $runId + } + environment = @{} +} | Out-Null + +$status = $null +$deadline = (Get-Date).AddSeconds(80) +while ((Get-Date) -lt $deadline) { + Start-Sleep -Seconds 5 + $status = Get-RdpWorkerStatus -NodeID $node.id + if (Test-RealAdapterOnlyDisabled -Status $status) { break } +} + +$payload = Get-PropertyValue -Item $status -Name "status_payload" -Default $null +$contract = Get-PropertyValue -Item $payload -Name "real_adapter_supervision" -Default $null +$projection = Get-PropertyValue -Item $contract -Name "config_projection" -Default $null +$decision = Get-PropertyValue -Item $contract -Name "activation_decision" -Default $null +$checks = [ordered]@{ + status_present = ($null -ne $status) + reported_degraded = ([string](Get-PropertyValue -Item $status -Name "reported_state" -Default "") -eq "degraded") + disabled_branch_selected = ([string](Get-PropertyValue -Item $payload -Name "execution_mode" -Default "") -eq "real_adapter_supervision_disabled") + traffic_blocked = ([string](Get-PropertyValue -Item $payload -Name "traffic" -Default "") -eq "blocked") + payload_traffic_none = ([string](Get-PropertyValue -Item $payload -Name "payload_traffic" -Default "") -eq "none" -and [string](Get-PropertyValue -Item $decision -Name "payload_traffic" -Default "") -eq "none") + activation_decision_blocked = ([string](Get-PropertyValue -Item $decision -Name "decision" -Default "") -eq "blocked") + enabled_request_projected = ([bool](Get-PropertyValue -Item $projection -Name "enabled_requested" -Default $false) -and [bool](Get-PropertyValue -Item $decision -Name "enabled_requested" -Default $false)) + real_adapter_only_contract_passed = (Test-RealAdapterOnlyDisabled -Status $status) +} +$failed = @($checks.GetEnumerator() | Where-Object { -not $_.Value } | ForEach-Object { $_.Key }) + +$result = [ordered]@{ + schema_version = "c19z44.remote_workspace_real_adapter_only_disabled_smoke.v1" + run_id = $runId + cluster_id = $ClusterID + node = [ordered]@{ id = $node.id; name = $node.name } + requested_config = [ordered]@{ adapter_contract_probe = $false; real_adapter_supervision = $true } + observed = [ordered]@{ + reported_state = Get-PropertyValue -Item $status -Name "reported_state" -Default $null + reason = Get-PropertyValue -Item $payload -Name "reason" -Default $null + execution_mode = Get-PropertyValue -Item $payload -Name "execution_mode" -Default $null + traffic = Get-PropertyValue -Item $payload -Name "traffic" -Default $null + payload_traffic = Get-PropertyValue -Item $payload -Name "payload_traffic" -Default $null + enabled_requested = Get-PropertyValue -Item $projection -Name "enabled_requested" -Default $null + activation_decision = Get-PropertyValue -Item $decision -Name "decision" -Default $null + activation_reason = Get-PropertyValue -Item $decision -Name "reason" -Default $null + } + checks = $checks + failed_checks = $failed + passed = ($failed.Count -eq 0) +} + +$fullResultPath = Join-Path $repoRoot $ResultPath +$resultDir = Split-Path -Parent $fullResultPath +if ($resultDir) { New-Item -ItemType Directory -Force -Path $resultDir | Out-Null } +$result | ConvertTo-Json -Depth 100 | Set-Content -Encoding UTF8 -Path $fullResultPath + +if (-not $result.passed) { + throw "C19Z44 remote workspace real-adapter only disabled smoke failed. Result: $fullResultPath Failed: $($failed -join ', ')" +} + +Write-Host "C19Z44 remote workspace real-adapter only disabled smoke passed. Result: $fullResultPath" +$result diff --git a/scripts/fabric/c19z45-remote-workspace-real-adapter-mode-matrix-smoke.ps1 b/scripts/fabric/c19z45-remote-workspace-real-adapter-mode-matrix-smoke.ps1 new file mode 100644 index 0000000..76b8ad2 --- /dev/null +++ b/scripts/fabric/c19z45-remote-workspace-real-adapter-mode-matrix-smoke.ps1 @@ -0,0 +1,225 @@ +param( + [string]$ApiBaseUrl = "http://192.168.200.61:18121/api/v1", + [string]$ClusterID = "cfc0743d-d960-49fb-9de8-96e063d5e4aa", + [string]$ActorUserID = "f67d943f-5397-4b3a-a229-695fe67ad700", + [string]$NodeName = "test-1", + [string]$ResultPath = "artifacts\c19z45-remote-workspace-real-adapter-mode-matrix-smoke-result.json" +) + +Set-StrictMode -Version Latest +$ErrorActionPreference = "Stop" + +$repoRoot = (Resolve-Path (Join-Path $PSScriptRoot "..\..")).ProviderPath +$runId = "c19z45-" + (Get-Date -Format "yyyyMMdd-HHmmss") +$requiredGates = @( + "real_runtime_stage_enabled", + "fabric_service_channel_runtime_ready", + "adapter_process_supervisor_enabled", + "payload_forwarding_contract_enabled" +) +$requiredFeatures = @("config_projection", "activation_decision", "missing_gates", "raw_values_redacted") + +function Invoke-Api { + param([string]$Method, [string]$Path, [object]$Body = $null) + if ($null -eq $Body) { + return Invoke-RestMethod -Method $Method -Uri "$ApiBaseUrl$Path" -TimeoutSec 30 + } + return Invoke-RestMethod -Method $Method -Uri "$ApiBaseUrl$Path" -ContentType "application/json" -Body ($Body | ConvertTo-Json -Depth 80) -TimeoutSec 30 +} + +function Get-PropertyValue { + param([object]$Item, [string]$Name, [object]$Default = $null) + if ($null -eq $Item) { return $Default } + $property = $Item.PSObject.Properties[$Name] + if ($null -eq $property) { return $Default } + return $property.Value +} + +function Get-MapBool { + param([hashtable]$Map, [string]$Name) + if ($null -eq $Map -or -not $Map.ContainsKey($Name)) { return $false } + return [bool]$Map[$Name] +} + +function Test-ArrayItem { + param([object]$Items, [string]$Want) + foreach ($item in @($Items)) { + if ([string]$item -eq $Want) { return $true } + } + return $false +} + +function Test-RequiredItems { + param([object]$Items, [string[]]$Required) + foreach ($item in $Required) { + if (-not (Test-ArrayItem -Items $Items -Want $item)) { return $false } + } + return $true +} + +function Test-FeatureMap { + param([object]$Features) + if ($null -eq $Features) { return $false } + foreach ($feature in $requiredFeatures) { + if (-not [bool](Get-PropertyValue -Item $Features -Name $feature -Default $false)) { return $false } + } + return $true +} + +function Get-NodeByName { + param([string]$Name) + $nodes = (Invoke-Api -Method GET -Path "/clusters/$ClusterID/nodes?actor_user_id=$ActorUserID").nodes + $node = @($nodes | Where-Object { $_.name -eq $Name }) | Select-Object -First 1 + if ($null -eq $node) { throw "Node '$Name' was not found in cluster $ClusterID" } + return $node +} + +function Set-RdpWorkerDesired { + param([object]$Node, [string]$Mode, [hashtable]$Config) + Invoke-Api -Method PUT -Path "/clusters/$ClusterID/nodes/$($Node.id)/workloads/rdp-worker/desired" -Body @{ + actor_user_id = $ActorUserID + desired_state = "enabled" + version = "c19z45-real-adapter-mode-matrix-$Mode-$runId" + runtime_mode = "native" + artifact_ref = "builtin:rdp-worker-mode-matrix" + config = $Config + environment = @{} + } | Out-Null +} + +function Get-RdpWorkerStatus { + param([string]$NodeID) + $statuses = (Invoke-Api -Method GET -Path "/clusters/$ClusterID/nodes/$NodeID/workloads/status?actor_user_id=$ActorUserID").workload_statuses + return @($statuses | Where-Object { $_.service_type -eq "rdp-worker" } | Select-Object -First 1) +} + +function Wait-RdpWorkerMode { + param([object]$Node, [string]$ExpectedVersion) + $deadline = (Get-Date).AddSeconds(80) + $last = $null + while ((Get-Date) -lt $deadline) { + Start-Sleep -Seconds 5 + $last = Get-RdpWorkerStatus -NodeID $Node.id + if ($null -ne $last -and [string](Get-PropertyValue -Item $last -Name "version" -Default "") -eq $ExpectedVersion) { + return $last + } + } + return $last +} + +function New-MatrixRow { + param([object]$Status, [hashtable]$Case) + $payload = Get-PropertyValue -Item $Status -Name "status_payload" -Default $null + $contract = Get-PropertyValue -Item $payload -Name "real_adapter_supervision" -Default $null + $projection = Get-PropertyValue -Item $contract -Name "config_projection" -Default $null + $decision = Get-PropertyValue -Item $contract -Name "activation_decision" -Default $null + $features = Get-PropertyValue -Item $contract -Name "features" -Default $null + + $reportedState = [string](Get-PropertyValue -Item $Status -Name "reported_state" -Default "") + $executionMode = [string](Get-PropertyValue -Item $payload -Name "execution_mode" -Default "") + $traffic = [string](Get-PropertyValue -Item $payload -Name "traffic" -Default "") + $payloadTraffic = [string](Get-PropertyValue -Item $payload -Name "payload_traffic" -Default "none") + $decisionValue = [string](Get-PropertyValue -Item $decision -Name "decision" -Default "") + $activationAllowed = [bool](Get-PropertyValue -Item $decision -Name "activation_allowed" -Default $true) + $enabled = [bool](Get-PropertyValue -Item $contract -Name "enabled" -Default $true) + $missingGates = Get-PropertyValue -Item $decision -Name "missing_gates" -Default @() + $featuresVisible = Test-FeatureMap -Features $features + $missingGatesVisible = Test-RequiredItems -Items $missingGates -Required $requiredGates + + $passed = ( + $reportedState -eq [string]$Case.expected_reported_state -and + $executionMode -eq [string]$Case.expected_execution_mode -and + $traffic -eq [string]$Case.expected_traffic -and + $payloadTraffic -eq "none" -and + -not $enabled -and + $decisionValue -eq "blocked" -and + -not $activationAllowed -and + [string](Get-PropertyValue -Item $decision -Name "payload_traffic" -Default "") -eq "none" -and + [bool](Get-PropertyValue -Item $projection -Name "enabled_requested" -Default $false) -and + $featuresVisible -and + $missingGatesVisible + ) + + return [ordered]@{ + mode = $Case.mode + adapter_contract_probe = Get-MapBool -Map $Case.config -Name "adapter_contract_probe" + real_adapter_supervision = Get-MapBool -Map $Case.config -Name "real_adapter_supervision" + reported_state = $reportedState + reason = Get-PropertyValue -Item $payload -Name "reason" -Default $null + execution_mode = $executionMode + traffic = $traffic + payload_traffic = $payloadTraffic + real_adapter_enabled = $enabled + enabled_requested = Get-PropertyValue -Item $projection -Name "enabled_requested" -Default $null + activation_decision = $decisionValue + activation_allowed = $activationAllowed + missing_gates_visible = $missingGatesVisible + feature_flags_visible = $featuresVisible + passed = $passed + } +} + +$node = Get-NodeByName -Name $NodeName +$cases = @( + @{ + mode = "probe_only" + config = @{ adapter_contract_probe = $true; service_class = "remote_workspace"; run_id = "$runId-probe" } + expected_reported_state = "running" + expected_execution_mode = "contract_probe" + expected_traffic = "none" + }, + @{ + mode = "real_adapter_only" + config = @{ real_adapter_supervision = $true; service_class = "remote_workspace"; run_id = "$runId-real-only" } + expected_reported_state = "degraded" + expected_execution_mode = "real_adapter_supervision_disabled" + expected_traffic = "blocked" + }, + @{ + mode = "probe_and_real_adapter" + config = @{ adapter_contract_probe = $true; real_adapter_supervision = $true; service_class = "remote_workspace"; run_id = "$runId-both" } + expected_reported_state = "running" + expected_execution_mode = "contract_probe" + expected_traffic = "none" + } +) + +$rows = @() +foreach ($case in $cases) { + $version = "c19z45-real-adapter-mode-matrix-$($case.mode)-$runId" + Set-RdpWorkerDesired -Node $node -Mode $case.mode -Config $case.config + $status = Wait-RdpWorkerMode -Node $node -ExpectedVersion $version + $rows += New-MatrixRow -Status $status -Case $case +} + +$checks = [ordered]@{ + node_present = ($null -ne $node) + matrix_rows_present = ($rows.Count -eq 3) + all_rows_passed = (@($rows | Where-Object { -not $_.passed }).Count -eq 0) +} +$failed = @($checks.GetEnumerator() | Where-Object { -not $_.Value } | ForEach-Object { $_.Key }) + +$result = [ordered]@{ + schema_version = "c19z45.remote_workspace_real_adapter_mode_matrix_smoke.v1" + run_id = $runId + cluster_id = $ClusterID + node = [ordered]@{ id = $node.id; name = $node.name } + required_gates = $requiredGates + required_features = $requiredFeatures + matrix_rows = $rows + checks = $checks + failed_checks = $failed + passed = ($failed.Count -eq 0) +} + +$fullResultPath = Join-Path $repoRoot $ResultPath +$resultDir = Split-Path -Parent $fullResultPath +if ($resultDir) { New-Item -ItemType Directory -Force -Path $resultDir | Out-Null } +$result | ConvertTo-Json -Depth 100 | Set-Content -Encoding UTF8 -Path $fullResultPath + +if (-not $result.passed) { + throw "C19Z45 remote workspace real-adapter mode matrix smoke failed. Result: $fullResultPath Failed: $($failed -join ', ')" +} + +Write-Host "C19Z45 remote workspace real-adapter mode matrix smoke passed. Result: $fullResultPath" +$result diff --git a/scripts/fabric/c19z46-remote-workspace-real-adapter-mode-matrix-compatibility-smoke.ps1 b/scripts/fabric/c19z46-remote-workspace-real-adapter-mode-matrix-compatibility-smoke.ps1 new file mode 100644 index 0000000..e0b36fd --- /dev/null +++ b/scripts/fabric/c19z46-remote-workspace-real-adapter-mode-matrix-compatibility-smoke.ps1 @@ -0,0 +1,135 @@ +param( + [string]$ApiBaseUrl = "http://192.168.200.61:18121/api/v1", + [string]$ClusterID = "cfc0743d-d960-49fb-9de8-96e063d5e4aa", + [string]$ActorUserID = "f67d943f-5397-4b3a-a229-695fe67ad700", + [string]$NodeName = "test-1", + [string]$ResultPath = "artifacts\c19z46-remote-workspace-real-adapter-mode-matrix-compatibility-smoke-result.json" +) + +Set-StrictMode -Version Latest +$ErrorActionPreference = "Stop" + +$repoRoot = (Resolve-Path (Join-Path $PSScriptRoot "..\..")).ProviderPath +$sourceResultPath = "artifacts\c19z46-remote-workspace-real-adapter-mode-matrix-source-result.json" +$requiredRowFields = @( + "mode", + "adapter_contract_probe", + "real_adapter_supervision", + "reported_state", + "reason", + "execution_mode", + "traffic", + "payload_traffic", + "real_adapter_enabled", + "enabled_requested", + "activation_decision", + "activation_allowed", + "missing_gates_visible", + "feature_flags_visible", + "passed" +) +$expectedRows = @( + [ordered]@{ mode = "probe_only"; adapter_contract_probe = $true; real_adapter_supervision = $false; reported_state = "running"; execution_mode = "contract_probe"; traffic = "none" }, + [ordered]@{ mode = "real_adapter_only"; adapter_contract_probe = $false; real_adapter_supervision = $true; reported_state = "degraded"; execution_mode = "real_adapter_supervision_disabled"; traffic = "blocked" }, + [ordered]@{ mode = "probe_and_real_adapter"; adapter_contract_probe = $true; real_adapter_supervision = $true; reported_state = "running"; execution_mode = "contract_probe"; traffic = "none" } +) + +function Get-PropertyValue { + param([object]$Item, [string]$Name, [object]$Default = $null) + if ($null -eq $Item) { return $Default } + $property = $Item.PSObject.Properties[$Name] + if ($null -eq $property) { return $Default } + return $property.Value +} + +function Test-ArrayItem { + param([object]$Items, [string]$Want) + foreach ($item in @($Items)) { + if ([string]$item -eq $Want) { return $true } + } + return $false +} + +function Test-RowHasFields { + param([object]$Row) + foreach ($field in $requiredRowFields) { + if ($null -eq $Row.PSObject.Properties[$field]) { return $false } + } + return $true +} + +function Get-RowByMode { + param([object[]]$Rows, [string]$Mode) + return @($Rows | Where-Object { [string](Get-PropertyValue -Item $_ -Name "mode" -Default "") -eq $Mode } | Select-Object -First 1) +} + +function Test-ExpectedRow { + param([object]$Row, [object]$Expected) + if ($null -eq $Row) { return $false } + return ( + (Test-RowHasFields -Row $Row) -and + [bool](Get-PropertyValue -Item $Row -Name "adapter_contract_probe" -Default (-not [bool]$Expected.adapter_contract_probe)) -eq [bool]$Expected.adapter_contract_probe -and + [bool](Get-PropertyValue -Item $Row -Name "real_adapter_supervision" -Default (-not [bool]$Expected.real_adapter_supervision)) -eq [bool]$Expected.real_adapter_supervision -and + [string](Get-PropertyValue -Item $Row -Name "reported_state" -Default "") -eq [string]$Expected.reported_state -and + [string](Get-PropertyValue -Item $Row -Name "execution_mode" -Default "") -eq [string]$Expected.execution_mode -and + [string](Get-PropertyValue -Item $Row -Name "traffic" -Default "") -eq [string]$Expected.traffic -and + [string](Get-PropertyValue -Item $Row -Name "payload_traffic" -Default "") -eq "none" -and + -not [bool](Get-PropertyValue -Item $Row -Name "real_adapter_enabled" -Default $true) -and + [bool](Get-PropertyValue -Item $Row -Name "enabled_requested" -Default $false) -and + [string](Get-PropertyValue -Item $Row -Name "activation_decision" -Default "") -eq "blocked" -and + -not [bool](Get-PropertyValue -Item $Row -Name "activation_allowed" -Default $true) -and + [bool](Get-PropertyValue -Item $Row -Name "missing_gates_visible" -Default $false) -and + [bool](Get-PropertyValue -Item $Row -Name "feature_flags_visible" -Default $false) -and + [bool](Get-PropertyValue -Item $Row -Name "passed" -Default $false) + ) +} + +& powershell -ExecutionPolicy Bypass -File (Join-Path $PSScriptRoot "c19z45-remote-workspace-real-adapter-mode-matrix-smoke.ps1") ` + -ApiBaseUrl $ApiBaseUrl ` + -ClusterID $ClusterID ` + -ActorUserID $ActorUserID ` + -NodeName $NodeName ` + -ResultPath $sourceResultPath | Out-Null + +$sourceFile = Join-Path $repoRoot $sourceResultPath +$sourceResult = Get-Content -Raw -Path $sourceFile | ConvertFrom-Json +$rows = @($sourceResult.matrix_rows) +$rowChecks = [ordered]@{} +foreach ($expected in $expectedRows) { + $mode = [string]$expected.mode + $rowChecks[$mode] = Test-ExpectedRow -Row (Get-RowByMode -Rows $rows -Mode $mode) -Expected $expected +} + +$checks = [ordered]@{ + source_smoke_passed = ([bool]$sourceResult.passed) + source_schema_expected = ([string]$sourceResult.schema_version -eq "c19z45.remote_workspace_real_adapter_mode_matrix_smoke.v1") + row_count_three = ($rows.Count -eq 3) + required_fields_declared = ($requiredRowFields.Count -eq 15) + all_expected_modes_present = ((Get-RowByMode -Rows $rows -Mode "probe_only") -and (Get-RowByMode -Rows $rows -Mode "real_adapter_only") -and (Get-RowByMode -Rows $rows -Mode "probe_and_real_adapter")) + all_rows_compatible = (@($rowChecks.GetEnumerator() | Where-Object { -not $_.Value }).Count -eq 0) +} +$failed = @($checks.GetEnumerator() | Where-Object { -not $_.Value } | ForEach-Object { $_.Key }) + +$result = [ordered]@{ + schema_version = "c19z46.remote_workspace_real_adapter_mode_matrix_compatibility_smoke.v1" + source_result_path = $sourceFile + cluster_id = $ClusterID + required_row_fields = $requiredRowFields + row_checks = $rowChecks + matrix_rows = $rows + checks = $checks + failed_checks = $failed + passed = ($failed.Count -eq 0) +} + +$fullResultPath = Join-Path $repoRoot $ResultPath +$resultDir = Split-Path -Parent $fullResultPath +if ($resultDir) { New-Item -ItemType Directory -Force -Path $resultDir | Out-Null } +$result | ConvertTo-Json -Depth 100 | Set-Content -Encoding UTF8 -Path $fullResultPath + +if (-not $result.passed) { + throw "C19Z46 remote workspace real-adapter mode matrix compatibility smoke failed. Result: $fullResultPath Failed: $($failed -join ', ')" +} + +Write-Host "C19Z46 remote workspace real-adapter mode matrix compatibility smoke passed. Result: $fullResultPath" +$result diff --git a/scripts/fabric/c19z47-remote-workspace-real-adapter-process-preconditions-smoke.ps1 b/scripts/fabric/c19z47-remote-workspace-real-adapter-process-preconditions-smoke.ps1 new file mode 100644 index 0000000..72d73ff --- /dev/null +++ b/scripts/fabric/c19z47-remote-workspace-real-adapter-process-preconditions-smoke.ps1 @@ -0,0 +1,153 @@ +param( + [string]$ApiBaseUrl = "http://192.168.200.61:18121/api/v1", + [string]$ClusterID = "cfc0743d-d960-49fb-9de8-96e063d5e4aa", + [string]$ActorUserID = "f67d943f-5397-4b3a-a229-695fe67ad700", + [string]$RequestedNodeName = "test-1", + [string]$DefaultNodeName = "test-2", + [string]$ResultPath = "artifacts\c19z47-remote-workspace-real-adapter-process-preconditions-smoke-result.json" +) + +Set-StrictMode -Version Latest +$ErrorActionPreference = "Stop" + +$repoRoot = (Resolve-Path (Join-Path $PSScriptRoot "..\..")).ProviderPath +$runId = "c19z47-" + (Get-Date -Format "yyyyMMdd-HHmmss") +$requiredChecks = @( + "real_runtime_stage_enabled", + "command_config_validated", + "workdir_config_validated", + "process_identity_policy_bound", + "fabric_service_channel_runtime_ready", + "payload_forwarding_contract_enabled", + "health_probe_contract_enabled" +) + +function Invoke-Api { + param([string]$Method, [string]$Path, [object]$Body = $null) + if ($null -eq $Body) { return Invoke-RestMethod -Method $Method -Uri "$ApiBaseUrl$Path" -TimeoutSec 30 } + return Invoke-RestMethod -Method $Method -Uri "$ApiBaseUrl$Path" -ContentType "application/json" -Body ($Body | ConvertTo-Json -Depth 80) -TimeoutSec 30 +} + +function Get-PropertyValue { + param([object]$Item, [string]$Name, [object]$Default = $null) + if ($null -eq $Item) { return $Default } + $property = $Item.PSObject.Properties[$Name] + if ($null -eq $property) { return $Default } + return $property.Value +} + +function Test-ArrayItem { + param([object]$Items, [string]$Want) + foreach ($item in @($Items)) { + if ([string]$item -eq $Want) { return $true } + } + return $false +} + +function Test-RequiredItems { + param([object]$Items, [string[]]$Required) + foreach ($item in $Required) { + if (-not (Test-ArrayItem -Items $Items -Want $item)) { return $false } + } + return $true +} + +function Get-NodeByName { + param([string]$Name) + $nodes = (Invoke-Api -Method GET -Path "/clusters/$ClusterID/nodes?actor_user_id=$ActorUserID").nodes + $node = @($nodes | Where-Object { $_.name -eq $Name }) | Select-Object -First 1 + if ($null -eq $node) { throw "Node '$Name' was not found in cluster $ClusterID" } + return $node +} + +function Enable-RdpWorkerProbe { + param([object]$Node) + Invoke-Api -Method PUT -Path "/clusters/$ClusterID/nodes/$($Node.id)/workloads/rdp-worker/desired" -Body @{ + actor_user_id = $ActorUserID + desired_state = "enabled" + version = "c19z47-real-adapter-process-preconditions-$runId" + runtime_mode = "native" + artifact_ref = "builtin:rdp-worker-adapter-contract-probe" + config = @{ adapter_contract_probe = $true; service_class = "remote_workspace"; run_id = $runId } + environment = @{} + } | Out-Null +} + +function Get-RdpWorkerStatus { + param([string]$NodeID) + $statuses = (Invoke-Api -Method GET -Path "/clusters/$ClusterID/nodes/$NodeID/workloads/status?actor_user_id=$ActorUserID").workload_statuses + return @($statuses | Where-Object { $_.service_type -eq "rdp-worker" } | Select-Object -First 1) +} + +function Test-Preconditions { + param([object]$Status, [bool]$CommandPresent, [bool]$ArgsPresent, [bool]$WorkdirPresent) + if ($null -eq $Status) { return $false } + $payload = Get-PropertyValue -Item $Status -Name "status_payload" -Default $null + $contract = Get-PropertyValue -Item $payload -Name "real_adapter_supervision" -Default $null + $preconditions = Get-PropertyValue -Item $contract -Name "process_supervisor_preconditions" -Default $null + $features = Get-PropertyValue -Item $contract -Name "features" -Default $null + if ($null -eq $preconditions) { return $false } + return ( + [string](Get-PropertyValue -Item $preconditions -Name "schema_version" -Default "") -eq "rap.remote_workspace_real_adapter_process_supervisor_preconditions.v1" -and + -not [bool](Get-PropertyValue -Item $preconditions -Name "process_start_allowed" -Default $true) -and + [string](Get-PropertyValue -Item $preconditions -Name "reason" -Default "") -eq "disabled_until_real_runtime_stage" -and + [bool](Get-PropertyValue -Item $preconditions -Name "command_config_present" -Default (-not $CommandPresent)) -eq $CommandPresent -and + [bool](Get-PropertyValue -Item $preconditions -Name "args_config_present" -Default (-not $ArgsPresent)) -eq $ArgsPresent -and + [bool](Get-PropertyValue -Item $preconditions -Name "workdir_config_present" -Default (-not $WorkdirPresent)) -eq $WorkdirPresent -and + (Test-RequiredItems -Items (Get-PropertyValue -Item $preconditions -Name "required_checks" -Default @()) -Required $requiredChecks) -and + (Test-RequiredItems -Items (Get-PropertyValue -Item $preconditions -Name "missing_checks" -Default @()) -Required $requiredChecks) -and + [bool](Get-PropertyValue -Item $features -Name "process_supervisor_preconditions" -Default $false) -and + [bool](Get-PropertyValue -Item $features -Name "process_supervisor_start_disabled" -Default $false) -and + [string](Get-PropertyValue -Item $payload -Name "execution_mode" -Default "") -eq "contract_probe" + ) +} + +$requestedNode = Get-NodeByName -Name $RequestedNodeName +$defaultNode = Get-NodeByName -Name $DefaultNodeName +Enable-RdpWorkerProbe -Node $requestedNode +Enable-RdpWorkerProbe -Node $defaultNode + +$requestedStatus = $null +$defaultStatus = $null +$deadline = (Get-Date).AddSeconds(80) +while ((Get-Date) -lt $deadline) { + Start-Sleep -Seconds 5 + $requestedStatus = Get-RdpWorkerStatus -NodeID $requestedNode.id + $defaultStatus = Get-RdpWorkerStatus -NodeID $defaultNode.id + if ((Test-Preconditions -Status $requestedStatus -CommandPresent $true -ArgsPresent $true -WorkdirPresent $true) -and (Test-Preconditions -Status $defaultStatus -CommandPresent $false -ArgsPresent $false -WorkdirPresent $false)) { break } +} + +$requestedContract = Get-PropertyValue -Item (Get-PropertyValue -Item $requestedStatus -Name "status_payload" -Default $null) -Name "real_adapter_supervision" -Default $null +$defaultContract = Get-PropertyValue -Item (Get-PropertyValue -Item $defaultStatus -Name "status_payload" -Default $null) -Name "real_adapter_supervision" -Default $null +$checks = [ordered]@{ + requested_status_present = ($null -ne $requestedStatus) + default_status_present = ($null -ne $defaultStatus) + requested_preconditions_visible = (Test-Preconditions -Status $requestedStatus -CommandPresent $true -ArgsPresent $true -WorkdirPresent $true) + default_preconditions_visible = (Test-Preconditions -Status $defaultStatus -CommandPresent $false -ArgsPresent $false -WorkdirPresent $false) +} +$failed = @($checks.GetEnumerator() | Where-Object { -not $_.Value } | ForEach-Object { $_.Key }) + +$result = [ordered]@{ + schema_version = "c19z47.remote_workspace_real_adapter_process_preconditions_smoke.v1" + cluster_id = $ClusterID + required_checks = $requiredChecks + requested_node = [ordered]@{ id = $requestedNode.id; name = $requestedNode.name } + default_node = [ordered]@{ id = $defaultNode.id; name = $defaultNode.name } + requested_preconditions = Get-PropertyValue -Item $requestedContract -Name "process_supervisor_preconditions" -Default $null + default_preconditions = Get-PropertyValue -Item $defaultContract -Name "process_supervisor_preconditions" -Default $null + checks = $checks + failed_checks = $failed + passed = ($failed.Count -eq 0) +} + +$fullResultPath = Join-Path $repoRoot $ResultPath +$resultDir = Split-Path -Parent $fullResultPath +if ($resultDir) { New-Item -ItemType Directory -Force -Path $resultDir | Out-Null } +$result | ConvertTo-Json -Depth 100 | Set-Content -Encoding UTF8 -Path $fullResultPath + +if (-not $result.passed) { + throw "C19Z47 remote workspace real-adapter process preconditions smoke failed. Result: $fullResultPath Failed: $($failed -join ', ')" +} + +Write-Host "C19Z47 remote workspace real-adapter process preconditions smoke passed. Result: $fullResultPath" +$result diff --git a/scripts/fabric/c19z48-remote-workspace-real-adapter-process-preconditions-compatibility-smoke.ps1 b/scripts/fabric/c19z48-remote-workspace-real-adapter-process-preconditions-compatibility-smoke.ps1 new file mode 100644 index 0000000..2c81d03 --- /dev/null +++ b/scripts/fabric/c19z48-remote-workspace-real-adapter-process-preconditions-compatibility-smoke.ps1 @@ -0,0 +1,134 @@ +param( + [string]$ApiBaseUrl = "http://192.168.200.61:18121/api/v1", + [string]$ClusterID = "cfc0743d-d960-49fb-9de8-96e063d5e4aa", + [string]$ActorUserID = "f67d943f-5397-4b3a-a229-695fe67ad700", + [string]$RequestedNodeName = "test-1", + [string]$DefaultNodeName = "test-2", + [string]$ResultPath = "artifacts\c19z48-remote-workspace-real-adapter-process-preconditions-compatibility-smoke-result.json" +) + +Set-StrictMode -Version Latest +$ErrorActionPreference = "Stop" + +$repoRoot = (Resolve-Path (Join-Path $PSScriptRoot "..\..")).ProviderPath +$sourceResultPath = "artifacts\c19z48-remote-workspace-real-adapter-process-preconditions-source-result.json" +$requiredChecks = @( + "real_runtime_stage_enabled", + "command_config_validated", + "workdir_config_validated", + "process_identity_policy_bound", + "fabric_service_channel_runtime_ready", + "payload_forwarding_contract_enabled", + "health_probe_contract_enabled" +) +$requiredFields = @( + "schema_version", + "process_start_allowed", + "reason", + "command_config_present", + "workdir_config_present", + "args_config_present", + "required_checks", + "missing_checks" +) + +function Get-PropertyValue { + param([object]$Item, [string]$Name, [object]$Default = $null) + if ($null -eq $Item) { return $Default } + $property = $Item.PSObject.Properties[$Name] + if ($null -eq $property) { return $Default } + return $property.Value +} + +function Test-ArrayItem { + param([object]$Items, [string]$Want) + foreach ($item in @($Items)) { + if ([string]$item -eq $Want) { return $true } + } + return $false +} + +function Test-RequiredItems { + param([object]$Items, [string[]]$Required) + foreach ($item in $Required) { + if (-not (Test-ArrayItem -Items $Items -Want $item)) { return $false } + } + return $true +} + +function Test-RequiredFields { + param([object]$Item) + if ($null -eq $Item) { return $false } + foreach ($field in $requiredFields) { + if ($null -eq $Item.PSObject.Properties[$field]) { return $false } + } + return $true +} + +function Test-PreconditionContract { + param([object]$Preconditions, [bool]$CommandPresent, [bool]$ArgsPresent, [bool]$WorkdirPresent) + if ($null -eq $Preconditions) { return $false } + return ( + (Test-RequiredFields -Item $Preconditions) -and + [string](Get-PropertyValue -Item $Preconditions -Name "schema_version" -Default "") -eq "rap.remote_workspace_real_adapter_process_supervisor_preconditions.v1" -and + -not [bool](Get-PropertyValue -Item $Preconditions -Name "process_start_allowed" -Default $true) -and + [string](Get-PropertyValue -Item $Preconditions -Name "reason" -Default "") -eq "disabled_until_real_runtime_stage" -and + [bool](Get-PropertyValue -Item $Preconditions -Name "command_config_present" -Default (-not $CommandPresent)) -eq $CommandPresent -and + [bool](Get-PropertyValue -Item $Preconditions -Name "args_config_present" -Default (-not $ArgsPresent)) -eq $ArgsPresent -and + [bool](Get-PropertyValue -Item $Preconditions -Name "workdir_config_present" -Default (-not $WorkdirPresent)) -eq $WorkdirPresent -and + (Test-RequiredItems -Items (Get-PropertyValue -Item $Preconditions -Name "required_checks" -Default @()) -Required $requiredChecks) -and + (Test-RequiredItems -Items (Get-PropertyValue -Item $Preconditions -Name "missing_checks" -Default @()) -Required $requiredChecks) + ) +} + +& powershell -ExecutionPolicy Bypass -File (Join-Path $PSScriptRoot "c19z47-remote-workspace-real-adapter-process-preconditions-smoke.ps1") ` + -ApiBaseUrl $ApiBaseUrl ` + -ClusterID $ClusterID ` + -ActorUserID $ActorUserID ` + -RequestedNodeName $RequestedNodeName ` + -DefaultNodeName $DefaultNodeName ` + -ResultPath $sourceResultPath | Out-Null + +$sourceFile = Join-Path $repoRoot $sourceResultPath +$sourceResult = Get-Content -Raw -Path $sourceFile | ConvertFrom-Json +$requestedPreconditions = Get-PropertyValue -Item $sourceResult -Name "requested_preconditions" -Default $null +$defaultPreconditions = Get-PropertyValue -Item $sourceResult -Name "default_preconditions" -Default $null + +$checks = [ordered]@{ + source_smoke_passed = ([bool]$sourceResult.passed) + source_schema_expected = ([string]$sourceResult.schema_version -eq "c19z47.remote_workspace_real_adapter_process_preconditions_smoke.v1") + requested_contract_compatible = (Test-PreconditionContract -Preconditions $requestedPreconditions -CommandPresent $true -ArgsPresent $true -WorkdirPresent $true) + default_contract_compatible = (Test-PreconditionContract -Preconditions $defaultPreconditions -CommandPresent $false -ArgsPresent $false -WorkdirPresent $false) + process_start_blocked_in_both = ( + -not [bool](Get-PropertyValue -Item $requestedPreconditions -Name "process_start_allowed" -Default $true) -and + -not [bool](Get-PropertyValue -Item $defaultPreconditions -Name "process_start_allowed" -Default $true) + ) + required_fields_declared = ($requiredFields.Count -eq 8) + required_checks_declared = ($requiredChecks.Count -eq 7) +} +$failed = @($checks.GetEnumerator() | Where-Object { -not $_.Value } | ForEach-Object { $_.Key }) + +$result = [ordered]@{ + schema_version = "c19z48.remote_workspace_real_adapter_process_preconditions_compatibility_smoke.v1" + source_result_path = $sourceFile + cluster_id = $ClusterID + required_fields = $requiredFields + required_checks = $requiredChecks + requested_preconditions = $requestedPreconditions + default_preconditions = $defaultPreconditions + checks = $checks + failed_checks = $failed + passed = ($failed.Count -eq 0) +} + +$fullResultPath = Join-Path $repoRoot $ResultPath +$resultDir = Split-Path -Parent $fullResultPath +if ($resultDir) { New-Item -ItemType Directory -Force -Path $resultDir | Out-Null } +$result | ConvertTo-Json -Depth 100 | Set-Content -Encoding UTF8 -Path $fullResultPath + +if (-not $result.passed) { + throw "C19Z48 remote workspace real-adapter process preconditions compatibility smoke failed. Result: $fullResultPath Failed: $($failed -join ', ')" +} + +Write-Host "C19Z48 remote workspace real-adapter process preconditions compatibility smoke passed. Result: $fullResultPath" +$result diff --git a/scripts/fabric/c19z49-remote-workspace-real-adapter-handoff-v3-smoke.ps1 b/scripts/fabric/c19z49-remote-workspace-real-adapter-handoff-v3-smoke.ps1 new file mode 100644 index 0000000..b75f1ee --- /dev/null +++ b/scripts/fabric/c19z49-remote-workspace-real-adapter-handoff-v3-smoke.ps1 @@ -0,0 +1,236 @@ +param( + [string]$ApiBaseUrl = "http://192.168.200.61:18121/api/v1", + [string]$ClusterID = "cfc0743d-d960-49fb-9de8-96e063d5e4aa", + [string]$ActorUserID = "f67d943f-5397-4b3a-a229-695fe67ad700", + [string]$RequestedNodeName = "test-1", + [string]$DefaultNodeName = "test-2", + [string]$ResultPath = "artifacts\c19z49-remote-workspace-real-adapter-handoff-v3-smoke-result.json" +) + +Set-StrictMode -Version Latest +$ErrorActionPreference = "Stop" + +$repoRoot = (Resolve-Path (Join-Path $PSScriptRoot "..\..")).ProviderPath +$runId = "c19z49-" + (Get-Date -Format "yyyyMMdd-HHmmss") +$requiredEnv = @( + "RAP_REMOTE_WORKSPACE_REAL_ADAPTER_ENABLED", + "RAP_REMOTE_WORKSPACE_REAL_ADAPTER_COMMAND", + "RAP_REMOTE_WORKSPACE_REAL_ADAPTER_ARGS_JSON", + "RAP_REMOTE_WORKSPACE_REAL_ADAPTER_WORKDIR" +) +$requiredGates = @( + "real_runtime_stage_enabled", + "fabric_service_channel_runtime_ready", + "adapter_process_supervisor_enabled", + "payload_forwarding_contract_enabled" +) +$requiredPreconditionChecks = @( + "real_runtime_stage_enabled", + "command_config_validated", + "workdir_config_validated", + "process_identity_policy_bound", + "fabric_service_channel_runtime_ready", + "payload_forwarding_contract_enabled", + "health_probe_contract_enabled" +) +$requiredFeatures = @( + "config_projection", + "activation_decision", + "missing_gates", + "process_supervisor_preconditions", + "process_supervisor_start_disabled", + "raw_values_redacted" +) + +function Invoke-Api { + param([string]$Method, [string]$Path, [object]$Body = $null) + if ($null -eq $Body) { return Invoke-RestMethod -Method $Method -Uri "$ApiBaseUrl$Path" -TimeoutSec 30 } + return Invoke-RestMethod -Method $Method -Uri "$ApiBaseUrl$Path" -ContentType "application/json" -Body ($Body | ConvertTo-Json -Depth 80) -TimeoutSec 30 +} + +function Get-PropertyValue { + param([object]$Item, [string]$Name, [object]$Default = $null) + if ($null -eq $Item) { return $Default } + $property = $Item.PSObject.Properties[$Name] + if ($null -eq $property) { return $Default } + return $property.Value +} + +function Test-ArrayItem { + param([object]$Items, [string]$Want) + foreach ($item in @($Items)) { + if ([string]$item -eq $Want) { return $true } + } + return $false +} + +function Test-RequiredItems { + param([object]$Items, [string[]]$Required) + foreach ($item in $Required) { + if (-not (Test-ArrayItem -Items $Items -Want $item)) { return $false } + } + return $true +} + +function Test-FeatureMap { + param([object]$Features) + if ($null -eq $Features) { return $false } + foreach ($feature in $requiredFeatures) { + if (-not [bool](Get-PropertyValue -Item $Features -Name $feature -Default $false)) { return $false } + } + return $true +} + +function Get-NodeByName { + param([string]$Name) + $nodes = (Invoke-Api -Method GET -Path "/clusters/$ClusterID/nodes?actor_user_id=$ActorUserID").nodes + $node = @($nodes | Where-Object { $_.name -eq $Name }) | Select-Object -First 1 + if ($null -eq $node) { throw "Node '$Name' was not found in cluster $ClusterID" } + return $node +} + +function Enable-RdpWorkerProbe { + param([object]$Node) + Invoke-Api -Method PUT -Path "/clusters/$ClusterID/nodes/$($Node.id)/workloads/rdp-worker/desired" -Body @{ + actor_user_id = $ActorUserID + desired_state = "enabled" + version = "c19z49-real-adapter-handoff-v3-$runId" + runtime_mode = "native" + artifact_ref = "builtin:rdp-worker-adapter-contract-probe" + config = @{ adapter_contract_probe = $true; service_class = "remote_workspace"; run_id = $runId } + environment = @{} + } | Out-Null +} + +function Get-RdpWorkerStatus { + param([string]$NodeID) + $statuses = (Invoke-Api -Method GET -Path "/clusters/$ClusterID/nodes/$NodeID/workloads/status?actor_user_id=$ActorUserID").workload_statuses + return @($statuses | Where-Object { $_.service_type -eq "rdp-worker" } | Select-Object -First 1) +} + +function New-HandoffRow { + param([object]$Node, [object]$Status, [bool]$ExpectedEnabledRequested, [string]$ExpectedArgsShape, [bool]$ExpectedConfigPresent) + $payload = Get-PropertyValue -Item $Status -Name "status_payload" -Default $null + $contract = Get-PropertyValue -Item $payload -Name "real_adapter_supervision" -Default $null + $projection = Get-PropertyValue -Item $contract -Name "config_projection" -Default $null + $decision = Get-PropertyValue -Item $contract -Name "activation_decision" -Default $null + $preconditions = Get-PropertyValue -Item $contract -Name "process_supervisor_preconditions" -Default $null + $features = Get-PropertyValue -Item $contract -Name "features" -Default $null + $statusContract = Get-PropertyValue -Item $contract -Name "status_contract" -Default @() + + $enabledRequested = [bool](Get-PropertyValue -Item $projection -Name "enabled_requested" -Default $false) + $decisionEnabledRequested = [bool](Get-PropertyValue -Item $decision -Name "enabled_requested" -Default (-not $enabledRequested)) + + $contractCompatible = ( + [string](Get-PropertyValue -Item $contract -Name "schema_version" -Default "") -eq "rap.remote_workspace_real_adapter_supervision.v1" -and + -not [bool](Get-PropertyValue -Item $contract -Name "enabled" -Default $true) -and + [string](Get-PropertyValue -Item $contract -Name "payload_traffic" -Default "") -eq "none" -and + (Test-RequiredItems -Items (Get-PropertyValue -Item $contract -Name "config_env" -Default @()) -Required $requiredEnv) -and + (Test-ArrayItem -Items $statusContract -Want "process_supervisor_preconditions") + ) + $projectionCompatible = ( + [string](Get-PropertyValue -Item $projection -Name "schema_version" -Default "") -eq "rap.remote_workspace_real_adapter_config_projection.v1" -and + $enabledRequested -eq $ExpectedEnabledRequested -and + -not [bool](Get-PropertyValue -Item $projection -Name "activation_allowed" -Default $true) -and + [string](Get-PropertyValue -Item $projection -Name "args_json_shape" -Default "") -eq $ExpectedArgsShape -and + [bool](Get-PropertyValue -Item $projection -Name "raw_values_redacted" -Default $false) + ) + $decisionCompatible = ( + [string](Get-PropertyValue -Item $decision -Name "schema_version" -Default "") -eq "rap.remote_workspace_real_adapter_activation_decision.v1" -and + [string](Get-PropertyValue -Item $decision -Name "decision" -Default "") -eq "blocked" -and + $decisionEnabledRequested -eq $ExpectedEnabledRequested -and + -not [bool](Get-PropertyValue -Item $decision -Name "activation_allowed" -Default $true) -and + [string](Get-PropertyValue -Item $decision -Name "payload_traffic" -Default "") -eq "none" -and + (Test-RequiredItems -Items (Get-PropertyValue -Item $decision -Name "missing_gates" -Default @()) -Required $requiredGates) + ) + $featuresCompatible = Test-FeatureMap -Features $features + $preconditionsCompatible = ( + [string](Get-PropertyValue -Item $preconditions -Name "schema_version" -Default "") -eq "rap.remote_workspace_real_adapter_process_supervisor_preconditions.v1" -and + -not [bool](Get-PropertyValue -Item $preconditions -Name "process_start_allowed" -Default $true) -and + [string](Get-PropertyValue -Item $preconditions -Name "reason" -Default "") -eq "disabled_until_real_runtime_stage" -and + [bool](Get-PropertyValue -Item $preconditions -Name "command_config_present" -Default (-not $ExpectedConfigPresent)) -eq $ExpectedConfigPresent -and + [bool](Get-PropertyValue -Item $preconditions -Name "args_config_present" -Default (-not $ExpectedConfigPresent)) -eq $ExpectedConfigPresent -and + [bool](Get-PropertyValue -Item $preconditions -Name "workdir_config_present" -Default (-not $ExpectedConfigPresent)) -eq $ExpectedConfigPresent -and + (Test-RequiredItems -Items (Get-PropertyValue -Item $preconditions -Name "missing_checks" -Default @()) -Required $requiredPreconditionChecks) + ) + $aligned = ( + $enabledRequested -eq $decisionEnabledRequested -and + [bool](Get-PropertyValue -Item $features -Name "process_supervisor_preconditions" -Default $false) -eq ($null -ne $preconditions) -and + -not [bool](Get-PropertyValue -Item $preconditions -Name "process_start_allowed" -Default $true) -and + [string](Get-PropertyValue -Item $contract -Name "payload_traffic" -Default "") -eq [string](Get-PropertyValue -Item $decision -Name "payload_traffic" -Default "") + ) + + return [ordered]@{ + node_id = $Node.id + node_name = $Node.name + reported_state = Get-PropertyValue -Item $Status -Name "reported_state" -Default $null + execution_mode = Get-PropertyValue -Item $payload -Name "execution_mode" -Default $null + enabled_requested = $enabledRequested + args_json_shape = Get-PropertyValue -Item $projection -Name "args_json_shape" -Default $null + activation_decision = Get-PropertyValue -Item $decision -Name "decision" -Default $null + process_start_allowed = Get-PropertyValue -Item $preconditions -Name "process_start_allowed" -Default $null + precondition_missing_checks = Get-PropertyValue -Item $preconditions -Name "missing_checks" -Default @() + contract_compatible = $contractCompatible + projection_compatible = $projectionCompatible + decision_compatible = $decisionCompatible + features_compatible = $featuresCompatible + preconditions_compatible = $preconditionsCompatible + all_contracts_aligned = $aligned + passed = ($contractCompatible -and $projectionCompatible -and $decisionCompatible -and $featuresCompatible -and $preconditionsCompatible -and $aligned) + } +} + +$requestedNode = Get-NodeByName -Name $RequestedNodeName +$defaultNode = Get-NodeByName -Name $DefaultNodeName +Enable-RdpWorkerProbe -Node $requestedNode +Enable-RdpWorkerProbe -Node $defaultNode + +$requestedStatus = $null +$defaultStatus = $null +$rows = @() +$deadline = (Get-Date).AddSeconds(80) +while ((Get-Date) -lt $deadline) { + Start-Sleep -Seconds 5 + $requestedStatus = Get-RdpWorkerStatus -NodeID $requestedNode.id + $defaultStatus = Get-RdpWorkerStatus -NodeID $defaultNode.id + if ($null -ne $requestedStatus -and $null -ne $defaultStatus) { + $rows = @( + (New-HandoffRow -Node $requestedNode -Status $requestedStatus -ExpectedEnabledRequested $true -ExpectedArgsShape "json_array" -ExpectedConfigPresent $true), + (New-HandoffRow -Node $defaultNode -Status $defaultStatus -ExpectedEnabledRequested $false -ExpectedArgsShape "absent" -ExpectedConfigPresent $false) + ) + if (@($rows | Where-Object { -not $_.passed }).Count -eq 0) { break } + } +} + +$checks = [ordered]@{ + requested_status_present = ($null -ne $requestedStatus) + default_status_present = ($null -ne $defaultStatus) + handoff_rows_present = ($rows.Count -eq 2) + all_rows_passed = (@($rows | Where-Object { -not $_.passed }).Count -eq 0) +} +$failed = @($checks.GetEnumerator() | Where-Object { -not $_.Value } | ForEach-Object { $_.Key }) + +$result = [ordered]@{ + schema_version = "c19z49.remote_workspace_real_adapter_handoff_v3_smoke.v1" + cluster_id = $ClusterID + required_env = $requiredEnv + required_gates = $requiredGates + required_precondition_checks = $requiredPreconditionChecks + required_features = $requiredFeatures + handoff_rows = $rows + checks = $checks + failed_checks = $failed + passed = ($failed.Count -eq 0) +} + +$fullResultPath = Join-Path $repoRoot $ResultPath +$resultDir = Split-Path -Parent $fullResultPath +if ($resultDir) { New-Item -ItemType Directory -Force -Path $resultDir | Out-Null } +$result | ConvertTo-Json -Depth 100 | Set-Content -Encoding UTF8 -Path $fullResultPath + +if (-not $result.passed) { + throw "C19Z49 remote workspace real-adapter handoff v3 smoke failed. Result: $fullResultPath Failed: $($failed -join ', ')" +} + +Write-Host "C19Z49 remote workspace real-adapter handoff v3 smoke passed. Result: $fullResultPath" +$result diff --git a/scripts/fabric/c19z5-remote-workspace-mailbox-preflight-provenance-smoke.ps1 b/scripts/fabric/c19z5-remote-workspace-mailbox-preflight-provenance-smoke.ps1 new file mode 100644 index 0000000..abcdfa9 --- /dev/null +++ b/scripts/fabric/c19z5-remote-workspace-mailbox-preflight-provenance-smoke.ps1 @@ -0,0 +1,75 @@ +param( + [string]$ApiBaseUrl = "http://192.168.200.61:18121/api/v1", + [string]$ClusterID = "cfc0743d-d960-49fb-9de8-96e063d5e4aa", + [string]$ActorUserID = "f67d943f-5397-4b3a-a229-695fe67ad700", + [string]$EntryNodeName = "test-1", + [string]$ExitNodeName = "test-2", + [string]$EntryBaseUrl = "http://192.168.200.61:19131", + [string]$ResultPath = "artifacts\c19z5-remote-workspace-mailbox-preflight-provenance-smoke-result.json" +) + +Set-StrictMode -Version Latest +$ErrorActionPreference = "Stop" + +$repoRoot = (Resolve-Path (Join-Path $PSScriptRoot "..\..")).ProviderPath +$sourceResultPath = "artifacts\c19z5-remote-workspace-mailbox-preflight-provenance-source-result.json" + +function Get-PropertyValue { + param([object]$Item, [string]$Name, [object]$Default = $null) + if ($null -eq $Item) { return $Default } + $property = $Item.PSObject.Properties[$Name] + if ($null -eq $property) { return $Default } + return $property.Value +} + +$source = & powershell -ExecutionPolicy Bypass -File (Join-Path $PSScriptRoot "c19z4-remote-workspace-mailbox-preflight-action-hints-smoke.ps1") ` + -ApiBaseUrl $ApiBaseUrl ` + -ClusterID $ClusterID ` + -ActorUserID $ActorUserID ` + -EntryNodeName $EntryNodeName ` + -ExitNodeName $ExitNodeName ` + -EntryBaseUrl $EntryBaseUrl ` + -ResultPath $sourceResultPath + +$sourceFile = Join-Path $repoRoot $sourceResultPath +$sourceResult = Get-Content -Raw -Path $sourceFile | ConvertFrom-Json +$preflight = Get-PropertyValue -Item $sourceResult -Name "preflight" -Default $null +$preflightJson = Get-PropertyValue -Item $preflight -Name "json" -Default $null +$context = Get-PropertyValue -Item $preflightJson -Name "action_context" -Default $null +$resumeSequence = [int64](Get-PropertyValue -Item $context -Name "resume_sequence" -Default -1) +$firstRetained = [int64](Get-PropertyValue -Item $context -Name "first_retained_sequence" -Default -1) +$missingDropped = [int](Get-PropertyValue -Item $context -Name "missing_dropped_count" -Default -1) + +$checks = [ordered]@{ + source_smoke_passed = ([bool]$sourceResult.passed) + action_reason_visible = ([string](Get-PropertyValue -Item $preflightJson -Name "action_reason" -Default "") -eq "consumer_cursor_before_first_retained_sequence") + action_context_present = ($null -ne $context) + context_cursor_visible = ($resumeSequence -eq 1 -and [string](Get-PropertyValue -Item $context -Name "resume_from" -Default "") -eq "ack") + context_retained_bounds_visible = ($firstRetained -gt $resumeSequence -and [int64](Get-PropertyValue -Item $context -Name "last_retained_sequence" -Default -1) -ge $firstRetained) + context_missing_formula_visible = ($missingDropped -eq ($firstRetained - $resumeSequence - 1)) + context_dropped_visible = ([int64](Get-PropertyValue -Item $context -Name "mailbox_dropped_total" -Default 0) -ge 3) +} +$failed = @($checks.GetEnumerator() | Where-Object { -not $_.Value } | ForEach-Object { $_.Key }) + +$result = [ordered]@{ + schema_version = "c19z5.remote_workspace_mailbox_preflight_provenance_smoke.v1" + source_result_path = $sourceFile + cluster_id = $ClusterID + adapter_session_id = [string](Get-PropertyValue -Item $sourceResult -Name "adapter_session_id" -Default "") + preflight = $preflight + checks = $checks + failed_checks = $failed + passed = ($failed.Count -eq 0) +} + +$fullResultPath = Join-Path $repoRoot $ResultPath +$resultDir = Split-Path -Parent $fullResultPath +if ($resultDir) { New-Item -ItemType Directory -Force -Path $resultDir | Out-Null } +$result | ConvertTo-Json -Depth 100 | Set-Content -Encoding UTF8 -Path $fullResultPath + +if (-not $result.passed) { + throw "C19Z5 remote workspace mailbox preflight provenance smoke failed. Result: $fullResultPath Failed: $($failed -join ', ')" +} + +Write-Host "C19Z5 remote workspace mailbox preflight provenance smoke passed. Result: $fullResultPath" +$result diff --git a/scripts/fabric/c19z50-remote-workspace-real-adapter-mode-matrix-v2-smoke.ps1 b/scripts/fabric/c19z50-remote-workspace-real-adapter-mode-matrix-v2-smoke.ps1 new file mode 100644 index 0000000..2dbd334 --- /dev/null +++ b/scripts/fabric/c19z50-remote-workspace-real-adapter-mode-matrix-v2-smoke.ps1 @@ -0,0 +1,200 @@ +param( + [string]$ApiBaseUrl = "http://192.168.200.61:18121/api/v1", + [string]$ClusterID = "cfc0743d-d960-49fb-9de8-96e063d5e4aa", + [string]$ActorUserID = "f67d943f-5397-4b3a-a229-695fe67ad700", + [string]$NodeName = "test-1", + [string]$ResultPath = "artifacts\c19z50-remote-workspace-real-adapter-mode-matrix-v2-smoke-result.json" +) + +Set-StrictMode -Version Latest +$ErrorActionPreference = "Stop" + +$repoRoot = (Resolve-Path (Join-Path $PSScriptRoot "..\..")).ProviderPath +$runId = "c19z50-" + (Get-Date -Format "yyyyMMdd-HHmmss") +$requiredPreconditionChecks = @( + "real_runtime_stage_enabled", + "command_config_validated", + "workdir_config_validated", + "process_identity_policy_bound", + "fabric_service_channel_runtime_ready", + "payload_forwarding_contract_enabled", + "health_probe_contract_enabled" +) + +function Invoke-Api { + param([string]$Method, [string]$Path, [object]$Body = $null) + if ($null -eq $Body) { return Invoke-RestMethod -Method $Method -Uri "$ApiBaseUrl$Path" -TimeoutSec 30 } + return Invoke-RestMethod -Method $Method -Uri "$ApiBaseUrl$Path" -ContentType "application/json" -Body ($Body | ConvertTo-Json -Depth 80) -TimeoutSec 30 +} + +function Get-PropertyValue { + param([object]$Item, [string]$Name, [object]$Default = $null) + if ($null -eq $Item) { return $Default } + $property = $Item.PSObject.Properties[$Name] + if ($null -eq $property) { return $Default } + return $property.Value +} + +function Get-MapBool { + param([hashtable]$Map, [string]$Name) + if ($null -eq $Map -or -not $Map.ContainsKey($Name)) { return $false } + return [bool]$Map[$Name] +} + +function Test-ArrayItem { + param([object]$Items, [string]$Want) + foreach ($item in @($Items)) { + if ([string]$item -eq $Want) { return $true } + } + return $false +} + +function Test-RequiredItems { + param([object]$Items, [string[]]$Required) + foreach ($item in $Required) { + if (-not (Test-ArrayItem -Items $Items -Want $item)) { return $false } + } + return $true +} + +function Get-NodeByName { + param([string]$Name) + $nodes = (Invoke-Api -Method GET -Path "/clusters/$ClusterID/nodes?actor_user_id=$ActorUserID").nodes + $node = @($nodes | Where-Object { $_.name -eq $Name }) | Select-Object -First 1 + if ($null -eq $node) { throw "Node '$Name' was not found in cluster $ClusterID" } + return $node +} + +function Set-RdpWorkerDesired { + param([object]$Node, [string]$Mode, [hashtable]$Config) + Invoke-Api -Method PUT -Path "/clusters/$ClusterID/nodes/$($Node.id)/workloads/rdp-worker/desired" -Body @{ + actor_user_id = $ActorUserID + desired_state = "enabled" + version = "c19z50-real-adapter-mode-matrix-v2-$Mode-$runId" + runtime_mode = "native" + artifact_ref = "builtin:rdp-worker-mode-matrix-v2" + config = $Config + environment = @{} + } | Out-Null +} + +function Get-RdpWorkerStatus { + param([string]$NodeID) + $statuses = (Invoke-Api -Method GET -Path "/clusters/$ClusterID/nodes/$NodeID/workloads/status?actor_user_id=$ActorUserID").workload_statuses + return @($statuses | Where-Object { $_.service_type -eq "rdp-worker" } | Select-Object -First 1) +} + +function Wait-RdpWorkerMode { + param([object]$Node, [string]$ExpectedVersion) + $deadline = (Get-Date).AddSeconds(80) + $last = $null + while ((Get-Date) -lt $deadline) { + Start-Sleep -Seconds 5 + $last = Get-RdpWorkerStatus -NodeID $Node.id + if ($null -ne $last -and [string](Get-PropertyValue -Item $last -Name "version" -Default "") -eq $ExpectedVersion) { + return $last + } + } + return $last +} + +function New-MatrixRow { + param([object]$Status, [hashtable]$Case) + $payload = Get-PropertyValue -Item $Status -Name "status_payload" -Default $null + $contract = Get-PropertyValue -Item $payload -Name "real_adapter_supervision" -Default $null + $projection = Get-PropertyValue -Item $contract -Name "config_projection" -Default $null + $decision = Get-PropertyValue -Item $contract -Name "activation_decision" -Default $null + $preconditions = Get-PropertyValue -Item $contract -Name "process_supervisor_preconditions" -Default $null + $features = Get-PropertyValue -Item $contract -Name "features" -Default $null + + $reportedState = [string](Get-PropertyValue -Item $Status -Name "reported_state" -Default "") + $executionMode = [string](Get-PropertyValue -Item $payload -Name "execution_mode" -Default "") + $traffic = [string](Get-PropertyValue -Item $payload -Name "traffic" -Default "") + $payloadTraffic = [string](Get-PropertyValue -Item $payload -Name "payload_traffic" -Default "none") + $processStartAllowed = [bool](Get-PropertyValue -Item $preconditions -Name "process_start_allowed" -Default $true) + $missingChecksVisible = Test-RequiredItems -Items (Get-PropertyValue -Item $preconditions -Name "missing_checks" -Default @()) -Required $requiredPreconditionChecks + $preconditionsVisible = ( + [string](Get-PropertyValue -Item $preconditions -Name "schema_version" -Default "") -eq "rap.remote_workspace_real_adapter_process_supervisor_preconditions.v1" -and + -not $processStartAllowed -and + $missingChecksVisible + ) + + $passed = ( + $reportedState -eq [string]$Case.expected_reported_state -and + $executionMode -eq [string]$Case.expected_execution_mode -and + $traffic -eq [string]$Case.expected_traffic -and + $payloadTraffic -eq "none" -and + -not [bool](Get-PropertyValue -Item $contract -Name "enabled" -Default $true) -and + [string](Get-PropertyValue -Item $decision -Name "decision" -Default "") -eq "blocked" -and + -not [bool](Get-PropertyValue -Item $decision -Name "activation_allowed" -Default $true) -and + [bool](Get-PropertyValue -Item $projection -Name "enabled_requested" -Default $false) -and + [bool](Get-PropertyValue -Item $features -Name "process_supervisor_preconditions" -Default $false) -and + [bool](Get-PropertyValue -Item $features -Name "process_supervisor_start_disabled" -Default $false) -and + $preconditionsVisible + ) + + return [ordered]@{ + mode = $Case.mode + adapter_contract_probe = Get-MapBool -Map $Case.config -Name "adapter_contract_probe" + real_adapter_supervision = Get-MapBool -Map $Case.config -Name "real_adapter_supervision" + reported_state = $reportedState + execution_mode = $executionMode + traffic = $traffic + payload_traffic = $payloadTraffic + activation_decision = Get-PropertyValue -Item $decision -Name "decision" -Default $null + activation_allowed = Get-PropertyValue -Item $decision -Name "activation_allowed" -Default $null + process_start_allowed = $processStartAllowed + preconditions_visible = $preconditionsVisible + missing_checks_visible = $missingChecksVisible + process_start_disabled_feature = Get-PropertyValue -Item $features -Name "process_supervisor_start_disabled" -Default $null + passed = $passed + } +} + +$node = Get-NodeByName -Name $NodeName +$cases = @( + @{ mode = "probe_only"; config = @{ adapter_contract_probe = $true; service_class = "remote_workspace"; run_id = "$runId-probe" }; expected_reported_state = "running"; expected_execution_mode = "contract_probe"; expected_traffic = "none" }, + @{ mode = "real_adapter_only"; config = @{ real_adapter_supervision = $true; service_class = "remote_workspace"; run_id = "$runId-real-only" }; expected_reported_state = "degraded"; expected_execution_mode = "real_adapter_supervision_disabled"; expected_traffic = "blocked" }, + @{ mode = "probe_and_real_adapter"; config = @{ adapter_contract_probe = $true; real_adapter_supervision = $true; service_class = "remote_workspace"; run_id = "$runId-both" }; expected_reported_state = "running"; expected_execution_mode = "contract_probe"; expected_traffic = "none" } +) + +$rows = @() +foreach ($case in $cases) { + $version = "c19z50-real-adapter-mode-matrix-v2-$($case.mode)-$runId" + Set-RdpWorkerDesired -Node $node -Mode $case.mode -Config $case.config + $status = Wait-RdpWorkerMode -Node $node -ExpectedVersion $version + $rows += New-MatrixRow -Status $status -Case $case +} + +$checks = [ordered]@{ + node_present = ($null -ne $node) + matrix_rows_present = ($rows.Count -eq 3) + all_rows_passed = (@($rows | Where-Object { -not $_.passed }).Count -eq 0) + process_start_disabled_all_rows = (@($rows | Where-Object { [bool]$_.process_start_allowed }).Count -eq 0) + preconditions_visible_all_rows = (@($rows | Where-Object { -not $_.preconditions_visible }).Count -eq 0) +} +$failed = @($checks.GetEnumerator() | Where-Object { -not $_.Value } | ForEach-Object { $_.Key }) + +$result = [ordered]@{ + schema_version = "c19z50.remote_workspace_real_adapter_mode_matrix_v2_smoke.v1" + run_id = $runId + cluster_id = $ClusterID + node = [ordered]@{ id = $node.id; name = $node.name } + required_precondition_checks = $requiredPreconditionChecks + matrix_rows = $rows + checks = $checks + failed_checks = $failed + passed = ($failed.Count -eq 0) +} + +$fullResultPath = Join-Path $repoRoot $ResultPath +$resultDir = Split-Path -Parent $fullResultPath +if ($resultDir) { New-Item -ItemType Directory -Force -Path $resultDir | Out-Null } +$result | ConvertTo-Json -Depth 100 | Set-Content -Encoding UTF8 -Path $fullResultPath + +if (-not $result.passed) { + throw "C19Z50 remote workspace real-adapter mode matrix v2 smoke failed. Result: $fullResultPath Failed: $($failed -join ', ')" +} + +Write-Host "C19Z50 remote workspace real-adapter mode matrix v2 smoke passed. Result: $fullResultPath" +$result diff --git a/scripts/fabric/c19z51-remote-workspace-real-adapter-mode-matrix-v2-compatibility-smoke.ps1 b/scripts/fabric/c19z51-remote-workspace-real-adapter-mode-matrix-v2-compatibility-smoke.ps1 new file mode 100644 index 0000000..92af56b --- /dev/null +++ b/scripts/fabric/c19z51-remote-workspace-real-adapter-mode-matrix-v2-compatibility-smoke.ps1 @@ -0,0 +1,126 @@ +param( + [string]$ApiBaseUrl = "http://192.168.200.61:18121/api/v1", + [string]$ClusterID = "cfc0743d-d960-49fb-9de8-96e063d5e4aa", + [string]$ActorUserID = "f67d943f-5397-4b3a-a229-695fe67ad700", + [string]$NodeName = "test-1", + [string]$ResultPath = "artifacts\c19z51-remote-workspace-real-adapter-mode-matrix-v2-compatibility-smoke-result.json" +) + +Set-StrictMode -Version Latest +$ErrorActionPreference = "Stop" + +$repoRoot = (Resolve-Path (Join-Path $PSScriptRoot "..\..")).ProviderPath +$sourceResultPath = "artifacts\c19z51-remote-workspace-real-adapter-mode-matrix-v2-source-result.json" +$requiredRowFields = @( + "mode", + "adapter_contract_probe", + "real_adapter_supervision", + "reported_state", + "execution_mode", + "traffic", + "payload_traffic", + "activation_decision", + "activation_allowed", + "process_start_allowed", + "preconditions_visible", + "missing_checks_visible", + "process_start_disabled_feature", + "passed" +) +$expectedRows = @( + [ordered]@{ mode = "probe_only"; adapter_contract_probe = $true; real_adapter_supervision = $false; reported_state = "running"; execution_mode = "contract_probe"; traffic = "none" }, + [ordered]@{ mode = "real_adapter_only"; adapter_contract_probe = $false; real_adapter_supervision = $true; reported_state = "degraded"; execution_mode = "real_adapter_supervision_disabled"; traffic = "blocked" }, + [ordered]@{ mode = "probe_and_real_adapter"; adapter_contract_probe = $true; real_adapter_supervision = $true; reported_state = "running"; execution_mode = "contract_probe"; traffic = "none" } +) + +function Get-PropertyValue { + param([object]$Item, [string]$Name, [object]$Default = $null) + if ($null -eq $Item) { return $Default } + $property = $Item.PSObject.Properties[$Name] + if ($null -eq $property) { return $Default } + return $property.Value +} + +function Test-RowHasFields { + param([object]$Row) + foreach ($field in $requiredRowFields) { + if ($null -eq $Row.PSObject.Properties[$field]) { return $false } + } + return $true +} + +function Get-RowByMode { + param([object[]]$Rows, [string]$Mode) + return @($Rows | Where-Object { [string](Get-PropertyValue -Item $_ -Name "mode" -Default "") -eq $Mode } | Select-Object -First 1) +} + +function Test-ExpectedRow { + param([object]$Row, [object]$Expected) + if ($null -eq $Row) { return $false } + return ( + (Test-RowHasFields -Row $Row) -and + [bool](Get-PropertyValue -Item $Row -Name "adapter_contract_probe" -Default (-not [bool]$Expected.adapter_contract_probe)) -eq [bool]$Expected.adapter_contract_probe -and + [bool](Get-PropertyValue -Item $Row -Name "real_adapter_supervision" -Default (-not [bool]$Expected.real_adapter_supervision)) -eq [bool]$Expected.real_adapter_supervision -and + [string](Get-PropertyValue -Item $Row -Name "reported_state" -Default "") -eq [string]$Expected.reported_state -and + [string](Get-PropertyValue -Item $Row -Name "execution_mode" -Default "") -eq [string]$Expected.execution_mode -and + [string](Get-PropertyValue -Item $Row -Name "traffic" -Default "") -eq [string]$Expected.traffic -and + [string](Get-PropertyValue -Item $Row -Name "payload_traffic" -Default "") -eq "none" -and + [string](Get-PropertyValue -Item $Row -Name "activation_decision" -Default "") -eq "blocked" -and + -not [bool](Get-PropertyValue -Item $Row -Name "activation_allowed" -Default $true) -and + -not [bool](Get-PropertyValue -Item $Row -Name "process_start_allowed" -Default $true) -and + [bool](Get-PropertyValue -Item $Row -Name "preconditions_visible" -Default $false) -and + [bool](Get-PropertyValue -Item $Row -Name "missing_checks_visible" -Default $false) -and + [bool](Get-PropertyValue -Item $Row -Name "process_start_disabled_feature" -Default $false) -and + [bool](Get-PropertyValue -Item $Row -Name "passed" -Default $false) + ) +} + +& powershell -ExecutionPolicy Bypass -File (Join-Path $PSScriptRoot "c19z50-remote-workspace-real-adapter-mode-matrix-v2-smoke.ps1") ` + -ApiBaseUrl $ApiBaseUrl ` + -ClusterID $ClusterID ` + -ActorUserID $ActorUserID ` + -NodeName $NodeName ` + -ResultPath $sourceResultPath | Out-Null + +$sourceFile = Join-Path $repoRoot $sourceResultPath +$sourceResult = Get-Content -Raw -Path $sourceFile | ConvertFrom-Json +$rows = @($sourceResult.matrix_rows) +$rowChecks = [ordered]@{} +foreach ($expected in $expectedRows) { + $mode = [string]$expected.mode + $rowChecks[$mode] = Test-ExpectedRow -Row (Get-RowByMode -Rows $rows -Mode $mode) -Expected $expected +} + +$checks = [ordered]@{ + source_smoke_passed = ([bool]$sourceResult.passed) + source_schema_expected = ([string]$sourceResult.schema_version -eq "c19z50.remote_workspace_real_adapter_mode_matrix_v2_smoke.v1") + row_count_three = ($rows.Count -eq 3) + required_fields_declared = ($requiredRowFields.Count -eq 14) + all_expected_modes_present = ((Get-RowByMode -Rows $rows -Mode "probe_only") -and (Get-RowByMode -Rows $rows -Mode "real_adapter_only") -and (Get-RowByMode -Rows $rows -Mode "probe_and_real_adapter")) + all_rows_compatible = (@($rowChecks.GetEnumerator() | Where-Object { -not $_.Value }).Count -eq 0) +} +$failed = @($checks.GetEnumerator() | Where-Object { -not $_.Value } | ForEach-Object { $_.Key }) + +$result = [ordered]@{ + schema_version = "c19z51.remote_workspace_real_adapter_mode_matrix_v2_compatibility_smoke.v1" + source_result_path = $sourceFile + cluster_id = $ClusterID + required_row_fields = $requiredRowFields + row_checks = $rowChecks + matrix_rows = $rows + checks = $checks + failed_checks = $failed + passed = ($failed.Count -eq 0) +} + +$fullResultPath = Join-Path $repoRoot $ResultPath +$resultDir = Split-Path -Parent $fullResultPath +if ($resultDir) { New-Item -ItemType Directory -Force -Path $resultDir | Out-Null } +$result | ConvertTo-Json -Depth 100 | Set-Content -Encoding UTF8 -Path $fullResultPath + +if (-not $result.passed) { + throw "C19Z51 remote workspace real-adapter mode matrix v2 compatibility smoke failed. Result: $fullResultPath Failed: $($failed -join ', ')" +} + +Write-Host "C19Z51 remote workspace real-adapter mode matrix v2 compatibility smoke passed. Result: $fullResultPath" +$result diff --git a/scripts/fabric/c19z52-remote-workspace-real-adapter-process-health-probe-smoke.ps1 b/scripts/fabric/c19z52-remote-workspace-real-adapter-process-health-probe-smoke.ps1 new file mode 100644 index 0000000..e4f963e --- /dev/null +++ b/scripts/fabric/c19z52-remote-workspace-real-adapter-process-health-probe-smoke.ps1 @@ -0,0 +1,150 @@ +param( + [string]$ApiBaseUrl = "http://192.168.200.61:18121/api/v1", + [string]$ClusterID = "cfc0743d-d960-49fb-9de8-96e063d5e4aa", + [string]$ActorUserID = "f67d943f-5397-4b3a-a229-695fe67ad700", + [string]$RequestedNodeName = "test-1", + [string]$DefaultNodeName = "test-2", + [string]$ResultPath = "artifacts\c19z52-remote-workspace-real-adapter-process-health-probe-smoke-result.json" +) + +Set-StrictMode -Version Latest +$ErrorActionPreference = "Stop" + +$repoRoot = (Resolve-Path (Join-Path $PSScriptRoot "..\..")).ProviderPath +$runId = "c19z52-" + (Get-Date -Format "yyyyMMdd-HHmmss") +$requiredSignals = @( + "process_started", + "process_exit_status", + "adapter_control_channel_ready", + "fabric_service_channel_bound", + "payload_forwarding_contract_ready" +) + +function Invoke-Api { + param([string]$Method, [string]$Path, [object]$Body = $null) + if ($null -eq $Body) { return Invoke-RestMethod -Method $Method -Uri "$ApiBaseUrl$Path" -TimeoutSec 30 } + return Invoke-RestMethod -Method $Method -Uri "$ApiBaseUrl$Path" -ContentType "application/json" -Body ($Body | ConvertTo-Json -Depth 80) -TimeoutSec 30 +} + +function Get-PropertyValue { + param([object]$Item, [string]$Name, [object]$Default = $null) + if ($null -eq $Item) { return $Default } + $property = $Item.PSObject.Properties[$Name] + if ($null -eq $property) { return $Default } + return $property.Value +} + +function Test-ArrayItem { + param([object]$Items, [string]$Want) + foreach ($item in @($Items)) { + if ([string]$item -eq $Want) { return $true } + } + return $false +} + +function Test-RequiredItems { + param([object]$Items, [string[]]$Required) + foreach ($item in $Required) { + if (-not (Test-ArrayItem -Items $Items -Want $item)) { return $false } + } + return $true +} + +function Get-NodeByName { + param([string]$Name) + $nodes = (Invoke-Api -Method GET -Path "/clusters/$ClusterID/nodes?actor_user_id=$ActorUserID").nodes + $node = @($nodes | Where-Object { $_.name -eq $Name }) | Select-Object -First 1 + if ($null -eq $node) { throw "Node '$Name' was not found in cluster $ClusterID" } + return $node +} + +function Enable-RdpWorkerProbe { + param([object]$Node) + Invoke-Api -Method PUT -Path "/clusters/$ClusterID/nodes/$($Node.id)/workloads/rdp-worker/desired" -Body @{ + actor_user_id = $ActorUserID + desired_state = "enabled" + version = "c19z52-real-adapter-process-health-probe-$runId" + runtime_mode = "native" + artifact_ref = "builtin:rdp-worker-adapter-contract-probe" + config = @{ adapter_contract_probe = $true; service_class = "remote_workspace"; run_id = $runId } + environment = @{} + } | Out-Null +} + +function Get-RdpWorkerStatus { + param([string]$NodeID) + $statuses = (Invoke-Api -Method GET -Path "/clusters/$ClusterID/nodes/$NodeID/workloads/status?actor_user_id=$ActorUserID").workload_statuses + return @($statuses | Where-Object { $_.service_type -eq "rdp-worker" } | Select-Object -First 1) +} + +function Test-HealthProbe { + param([object]$Status) + if ($null -eq $Status) { return $false } + $payload = Get-PropertyValue -Item $Status -Name "status_payload" -Default $null + $contract = Get-PropertyValue -Item $payload -Name "real_adapter_supervision" -Default $null + $health = Get-PropertyValue -Item $contract -Name "process_health_probe" -Default $null + $features = Get-PropertyValue -Item $contract -Name "features" -Default $null + if ($null -eq $health) { return $false } + return ( + [string](Get-PropertyValue -Item $health -Name "schema_version" -Default "") -eq "rap.remote_workspace_real_adapter_process_health_probe.v1" -and + -not [bool](Get-PropertyValue -Item $health -Name "health_probe_enabled" -Default $true) -and + [string](Get-PropertyValue -Item $health -Name "reason" -Default "") -eq "disabled_until_real_runtime_stage" -and + [string](Get-PropertyValue -Item $health -Name "payload_traffic" -Default "") -eq "none" -and + [string](Get-PropertyValue -Item $health -Name "probe_model" -Default "") -eq "external_process_health" -and + (Test-RequiredItems -Items (Get-PropertyValue -Item $health -Name "required_signals" -Default @()) -Required $requiredSignals) -and + (Test-RequiredItems -Items (Get-PropertyValue -Item $health -Name "missing_signals" -Default @()) -Required $requiredSignals) -and + [bool](Get-PropertyValue -Item $features -Name "process_health_probe" -Default $false) -and + [bool](Get-PropertyValue -Item $features -Name "process_health_probe_disabled" -Default $false) -and + [string](Get-PropertyValue -Item $payload -Name "execution_mode" -Default "") -eq "contract_probe" + ) +} + +$requestedNode = Get-NodeByName -Name $RequestedNodeName +$defaultNode = Get-NodeByName -Name $DefaultNodeName +Enable-RdpWorkerProbe -Node $requestedNode +Enable-RdpWorkerProbe -Node $defaultNode + +$requestedStatus = $null +$defaultStatus = $null +$deadline = (Get-Date).AddSeconds(80) +while ((Get-Date) -lt $deadline) { + Start-Sleep -Seconds 5 + $requestedStatus = Get-RdpWorkerStatus -NodeID $requestedNode.id + $defaultStatus = Get-RdpWorkerStatus -NodeID $defaultNode.id + if ((Test-HealthProbe -Status $requestedStatus) -and (Test-HealthProbe -Status $defaultStatus)) { break } +} + +$requestedContract = Get-PropertyValue -Item (Get-PropertyValue -Item $requestedStatus -Name "status_payload" -Default $null) -Name "real_adapter_supervision" -Default $null +$defaultContract = Get-PropertyValue -Item (Get-PropertyValue -Item $defaultStatus -Name "status_payload" -Default $null) -Name "real_adapter_supervision" -Default $null +$checks = [ordered]@{ + requested_status_present = ($null -ne $requestedStatus) + default_status_present = ($null -ne $defaultStatus) + requested_health_probe_visible = (Test-HealthProbe -Status $requestedStatus) + default_health_probe_visible = (Test-HealthProbe -Status $defaultStatus) +} +$failed = @($checks.GetEnumerator() | Where-Object { -not $_.Value } | ForEach-Object { $_.Key }) + +$result = [ordered]@{ + schema_version = "c19z52.remote_workspace_real_adapter_process_health_probe_smoke.v1" + cluster_id = $ClusterID + required_signals = $requiredSignals + requested_node = [ordered]@{ id = $requestedNode.id; name = $requestedNode.name } + default_node = [ordered]@{ id = $defaultNode.id; name = $defaultNode.name } + requested_health_probe = Get-PropertyValue -Item $requestedContract -Name "process_health_probe" -Default $null + default_health_probe = Get-PropertyValue -Item $defaultContract -Name "process_health_probe" -Default $null + checks = $checks + failed_checks = $failed + passed = ($failed.Count -eq 0) +} + +$fullResultPath = Join-Path $repoRoot $ResultPath +$resultDir = Split-Path -Parent $fullResultPath +if ($resultDir) { New-Item -ItemType Directory -Force -Path $resultDir | Out-Null } +$result | ConvertTo-Json -Depth 100 | Set-Content -Encoding UTF8 -Path $fullResultPath + +if (-not $result.passed) { + throw "C19Z52 remote workspace real-adapter process health probe smoke failed. Result: $fullResultPath Failed: $($failed -join ', ')" +} + +Write-Host "C19Z52 remote workspace real-adapter process health probe smoke passed. Result: $fullResultPath" +$result diff --git a/scripts/fabric/c19z53-remote-workspace-real-adapter-process-health-probe-compatibility-smoke.ps1 b/scripts/fabric/c19z53-remote-workspace-real-adapter-process-health-probe-compatibility-smoke.ps1 new file mode 100644 index 0000000..b21ee3f --- /dev/null +++ b/scripts/fabric/c19z53-remote-workspace-real-adapter-process-health-probe-compatibility-smoke.ps1 @@ -0,0 +1,134 @@ +param( + [string]$ApiBaseUrl = "http://192.168.200.61:18121/api/v1", + [string]$ClusterID = "cfc0743d-d960-49fb-9de8-96e063d5e4aa", + [string]$ActorUserID = "f67d943f-5397-4b3a-a229-695fe67ad700", + [string]$RequestedNodeName = "test-1", + [string]$DefaultNodeName = "test-2", + [string]$ResultPath = "artifacts\c19z53-remote-workspace-real-adapter-process-health-probe-compatibility-smoke-result.json" +) + +Set-StrictMode -Version Latest +$ErrorActionPreference = "Stop" + +$repoRoot = (Resolve-Path (Join-Path $PSScriptRoot "..\..")).ProviderPath +$sourceResultPath = "artifacts\c19z53-remote-workspace-real-adapter-process-health-probe-source-result.json" +$requiredSignals = @( + "process_started", + "process_exit_status", + "adapter_control_channel_ready", + "fabric_service_channel_bound", + "payload_forwarding_contract_ready" +) +$requiredFields = @( + "schema_version", + "health_probe_enabled", + "reason", + "payload_traffic", + "probe_model", + "required_signals", + "missing_signals" +) + +function Get-PropertyValue { + param([object]$Item, [string]$Name, [object]$Default = $null) + if ($null -eq $Item) { return $Default } + $property = $Item.PSObject.Properties[$Name] + if ($null -eq $property) { return $Default } + return $property.Value +} + +function Test-ArrayItem { + param([object]$Items, [string]$Want) + foreach ($item in @($Items)) { + if ([string]$item -eq $Want) { return $true } + } + return $false +} + +function Test-RequiredItems { + param([object]$Items, [string[]]$Required) + foreach ($item in $Required) { + if (-not (Test-ArrayItem -Items $Items -Want $item)) { return $false } + } + return $true +} + +function Test-RequiredFields { + param([object]$Item) + if ($null -eq $Item) { return $false } + foreach ($field in $requiredFields) { + if ($null -eq $Item.PSObject.Properties[$field]) { return $false } + } + return $true +} + +function Test-HealthProbeContract { + param([object]$HealthProbe) + if ($null -eq $HealthProbe) { return $false } + return ( + (Test-RequiredFields -Item $HealthProbe) -and + [string](Get-PropertyValue -Item $HealthProbe -Name "schema_version" -Default "") -eq "rap.remote_workspace_real_adapter_process_health_probe.v1" -and + -not [bool](Get-PropertyValue -Item $HealthProbe -Name "health_probe_enabled" -Default $true) -and + [string](Get-PropertyValue -Item $HealthProbe -Name "reason" -Default "") -eq "disabled_until_real_runtime_stage" -and + [string](Get-PropertyValue -Item $HealthProbe -Name "payload_traffic" -Default "") -eq "none" -and + [string](Get-PropertyValue -Item $HealthProbe -Name "probe_model" -Default "") -eq "external_process_health" -and + (Test-RequiredItems -Items (Get-PropertyValue -Item $HealthProbe -Name "required_signals" -Default @()) -Required $requiredSignals) -and + (Test-RequiredItems -Items (Get-PropertyValue -Item $HealthProbe -Name "missing_signals" -Default @()) -Required $requiredSignals) + ) +} + +& powershell -ExecutionPolicy Bypass -File (Join-Path $PSScriptRoot "c19z52-remote-workspace-real-adapter-process-health-probe-smoke.ps1") ` + -ApiBaseUrl $ApiBaseUrl ` + -ClusterID $ClusterID ` + -ActorUserID $ActorUserID ` + -RequestedNodeName $RequestedNodeName ` + -DefaultNodeName $DefaultNodeName ` + -ResultPath $sourceResultPath | Out-Null + +$sourceFile = Join-Path $repoRoot $sourceResultPath +$sourceResult = Get-Content -Raw -Path $sourceFile | ConvertFrom-Json +$requestedHealthProbe = Get-PropertyValue -Item $sourceResult -Name "requested_health_probe" -Default $null +$defaultHealthProbe = Get-PropertyValue -Item $sourceResult -Name "default_health_probe" -Default $null + +$checks = [ordered]@{ + source_smoke_passed = ([bool]$sourceResult.passed) + source_schema_expected = ([string]$sourceResult.schema_version -eq "c19z52.remote_workspace_real_adapter_process_health_probe_smoke.v1") + requested_contract_compatible = (Test-HealthProbeContract -HealthProbe $requestedHealthProbe) + default_contract_compatible = (Test-HealthProbeContract -HealthProbe $defaultHealthProbe) + health_probe_disabled_in_both = ( + -not [bool](Get-PropertyValue -Item $requestedHealthProbe -Name "health_probe_enabled" -Default $true) -and + -not [bool](Get-PropertyValue -Item $defaultHealthProbe -Name "health_probe_enabled" -Default $true) + ) + payload_traffic_none_in_both = ( + [string](Get-PropertyValue -Item $requestedHealthProbe -Name "payload_traffic" -Default "") -eq "none" -and + [string](Get-PropertyValue -Item $defaultHealthProbe -Name "payload_traffic" -Default "") -eq "none" + ) + required_fields_declared = ($requiredFields.Count -eq 7) + required_signals_declared = ($requiredSignals.Count -eq 5) +} +$failed = @($checks.GetEnumerator() | Where-Object { -not $_.Value } | ForEach-Object { $_.Key }) + +$result = [ordered]@{ + schema_version = "c19z53.remote_workspace_real_adapter_process_health_probe_compatibility_smoke.v1" + source_result_path = $sourceFile + cluster_id = $ClusterID + required_fields = $requiredFields + required_signals = $requiredSignals + requested_health_probe = $requestedHealthProbe + default_health_probe = $defaultHealthProbe + checks = $checks + failed_checks = $failed + passed = ($failed.Count -eq 0) +} + +$fullResultPath = Join-Path $repoRoot $ResultPath +$resultDir = Split-Path -Parent $fullResultPath +if ($resultDir) { New-Item -ItemType Directory -Force -Path $resultDir | Out-Null } +$result | ConvertTo-Json -Depth 100 | Set-Content -Encoding UTF8 -Path $fullResultPath + +if (-not $result.passed) { + throw "C19Z53 remote workspace real-adapter process health probe compatibility smoke failed. Result: $fullResultPath Failed: $($failed -join ', ')" +} + +Write-Host "C19Z53 remote workspace real-adapter process health probe compatibility smoke passed. Result: $fullResultPath" +$result diff --git a/scripts/fabric/c19z54-remote-workspace-real-adapter-handoff-v4-smoke.ps1 b/scripts/fabric/c19z54-remote-workspace-real-adapter-handoff-v4-smoke.ps1 new file mode 100644 index 0000000..caa2a40 --- /dev/null +++ b/scripts/fabric/c19z54-remote-workspace-real-adapter-handoff-v4-smoke.ps1 @@ -0,0 +1,256 @@ +param( + [string]$ApiBaseUrl = "http://192.168.200.61:18121/api/v1", + [string]$ClusterID = "cfc0743d-d960-49fb-9de8-96e063d5e4aa", + [string]$ActorUserID = "f67d943f-5397-4b3a-a229-695fe67ad700", + [string]$RequestedNodeName = "test-1", + [string]$DefaultNodeName = "test-2", + [string]$ResultPath = "artifacts\c19z54-remote-workspace-real-adapter-handoff-v4-smoke-result.json" +) + +Set-StrictMode -Version Latest +$ErrorActionPreference = "Stop" + +$repoRoot = (Resolve-Path (Join-Path $PSScriptRoot "..\..")).ProviderPath +$runId = "c19z54-" + (Get-Date -Format "yyyyMMdd-HHmmss") +$requiredEnv = @( + "RAP_REMOTE_WORKSPACE_REAL_ADAPTER_ENABLED", + "RAP_REMOTE_WORKSPACE_REAL_ADAPTER_COMMAND", + "RAP_REMOTE_WORKSPACE_REAL_ADAPTER_ARGS_JSON", + "RAP_REMOTE_WORKSPACE_REAL_ADAPTER_WORKDIR" +) +$requiredGates = @( + "real_runtime_stage_enabled", + "fabric_service_channel_runtime_ready", + "adapter_process_supervisor_enabled", + "payload_forwarding_contract_enabled" +) +$requiredPreconditionChecks = @( + "real_runtime_stage_enabled", + "command_config_validated", + "workdir_config_validated", + "process_identity_policy_bound", + "fabric_service_channel_runtime_ready", + "payload_forwarding_contract_enabled", + "health_probe_contract_enabled" +) +$requiredHealthSignals = @( + "process_started", + "process_exit_status", + "adapter_control_channel_ready", + "fabric_service_channel_bound", + "payload_forwarding_contract_ready" +) +$requiredFeatures = @( + "config_projection", + "activation_decision", + "missing_gates", + "process_supervisor_preconditions", + "process_supervisor_start_disabled", + "process_health_probe", + "process_health_probe_disabled", + "raw_values_redacted" +) + +function Invoke-Api { + param([string]$Method, [string]$Path, [object]$Body = $null) + if ($null -eq $Body) { return Invoke-RestMethod -Method $Method -Uri "$ApiBaseUrl$Path" -TimeoutSec 30 } + return Invoke-RestMethod -Method $Method -Uri "$ApiBaseUrl$Path" -ContentType "application/json" -Body ($Body | ConvertTo-Json -Depth 80) -TimeoutSec 30 +} + +function Get-PropertyValue { + param([object]$Item, [string]$Name, [object]$Default = $null) + if ($null -eq $Item) { return $Default } + $property = $Item.PSObject.Properties[$Name] + if ($null -eq $property) { return $Default } + return $property.Value +} + +function Test-ArrayItem { + param([object]$Items, [string]$Want) + foreach ($item in @($Items)) { + if ([string]$item -eq $Want) { return $true } + } + return $false +} + +function Test-RequiredItems { + param([object]$Items, [string[]]$Required) + foreach ($item in $Required) { + if (-not (Test-ArrayItem -Items $Items -Want $item)) { return $false } + } + return $true +} + +function Test-FeatureMap { + param([object]$Features) + if ($null -eq $Features) { return $false } + foreach ($feature in $requiredFeatures) { + if (-not [bool](Get-PropertyValue -Item $Features -Name $feature -Default $false)) { return $false } + } + return $true +} + +function Get-NodeByName { + param([string]$Name) + $nodes = (Invoke-Api -Method GET -Path "/clusters/$ClusterID/nodes?actor_user_id=$ActorUserID").nodes + $node = @($nodes | Where-Object { $_.name -eq $Name }) | Select-Object -First 1 + if ($null -eq $node) { throw "Node '$Name' was not found in cluster $ClusterID" } + return $node +} + +function Enable-RdpWorkerProbe { + param([object]$Node) + Invoke-Api -Method PUT -Path "/clusters/$ClusterID/nodes/$($Node.id)/workloads/rdp-worker/desired" -Body @{ + actor_user_id = $ActorUserID + desired_state = "enabled" + version = "c19z54-real-adapter-handoff-v4-$runId" + runtime_mode = "native" + artifact_ref = "builtin:rdp-worker-adapter-contract-probe" + config = @{ adapter_contract_probe = $true; service_class = "remote_workspace"; run_id = $runId } + environment = @{} + } | Out-Null +} + +function Get-RdpWorkerStatus { + param([string]$NodeID) + $statuses = (Invoke-Api -Method GET -Path "/clusters/$ClusterID/nodes/$NodeID/workloads/status?actor_user_id=$ActorUserID").workload_statuses + return @($statuses | Where-Object { $_.service_type -eq "rdp-worker" } | Select-Object -First 1) +} + +function New-HandoffRow { + param([object]$Node, [object]$Status, [bool]$ExpectedEnabledRequested, [string]$ExpectedArgsShape, [bool]$ExpectedConfigPresent) + $payload = Get-PropertyValue -Item $Status -Name "status_payload" -Default $null + $contract = Get-PropertyValue -Item $payload -Name "real_adapter_supervision" -Default $null + $projection = Get-PropertyValue -Item $contract -Name "config_projection" -Default $null + $decision = Get-PropertyValue -Item $contract -Name "activation_decision" -Default $null + $preconditions = Get-PropertyValue -Item $contract -Name "process_supervisor_preconditions" -Default $null + $health = Get-PropertyValue -Item $contract -Name "process_health_probe" -Default $null + $features = Get-PropertyValue -Item $contract -Name "features" -Default $null + $statusContract = Get-PropertyValue -Item $contract -Name "status_contract" -Default @() + + $enabledRequested = [bool](Get-PropertyValue -Item $projection -Name "enabled_requested" -Default $false) + $decisionEnabledRequested = [bool](Get-PropertyValue -Item $decision -Name "enabled_requested" -Default (-not $enabledRequested)) + + $contractCompatible = ( + [string](Get-PropertyValue -Item $contract -Name "schema_version" -Default "") -eq "rap.remote_workspace_real_adapter_supervision.v1" -and + -not [bool](Get-PropertyValue -Item $contract -Name "enabled" -Default $true) -and + [string](Get-PropertyValue -Item $contract -Name "payload_traffic" -Default "") -eq "none" -and + (Test-RequiredItems -Items (Get-PropertyValue -Item $contract -Name "config_env" -Default @()) -Required $requiredEnv) -and + (Test-ArrayItem -Items $statusContract -Want "process_health_probe") + ) + $projectionCompatible = ( + [string](Get-PropertyValue -Item $projection -Name "schema_version" -Default "") -eq "rap.remote_workspace_real_adapter_config_projection.v1" -and + $enabledRequested -eq $ExpectedEnabledRequested -and + -not [bool](Get-PropertyValue -Item $projection -Name "activation_allowed" -Default $true) -and + [string](Get-PropertyValue -Item $projection -Name "args_json_shape" -Default "") -eq $ExpectedArgsShape -and + [bool](Get-PropertyValue -Item $projection -Name "raw_values_redacted" -Default $false) + ) + $decisionCompatible = ( + [string](Get-PropertyValue -Item $decision -Name "schema_version" -Default "") -eq "rap.remote_workspace_real_adapter_activation_decision.v1" -and + [string](Get-PropertyValue -Item $decision -Name "decision" -Default "") -eq "blocked" -and + $decisionEnabledRequested -eq $ExpectedEnabledRequested -and + -not [bool](Get-PropertyValue -Item $decision -Name "activation_allowed" -Default $true) -and + [string](Get-PropertyValue -Item $decision -Name "payload_traffic" -Default "") -eq "none" -and + (Test-RequiredItems -Items (Get-PropertyValue -Item $decision -Name "missing_gates" -Default @()) -Required $requiredGates) + ) + $preconditionsCompatible = ( + [string](Get-PropertyValue -Item $preconditions -Name "schema_version" -Default "") -eq "rap.remote_workspace_real_adapter_process_supervisor_preconditions.v1" -and + -not [bool](Get-PropertyValue -Item $preconditions -Name "process_start_allowed" -Default $true) -and + [bool](Get-PropertyValue -Item $preconditions -Name "command_config_present" -Default (-not $ExpectedConfigPresent)) -eq $ExpectedConfigPresent -and + [bool](Get-PropertyValue -Item $preconditions -Name "args_config_present" -Default (-not $ExpectedConfigPresent)) -eq $ExpectedConfigPresent -and + [bool](Get-PropertyValue -Item $preconditions -Name "workdir_config_present" -Default (-not $ExpectedConfigPresent)) -eq $ExpectedConfigPresent -and + (Test-RequiredItems -Items (Get-PropertyValue -Item $preconditions -Name "missing_checks" -Default @()) -Required $requiredPreconditionChecks) + ) + $healthCompatible = ( + [string](Get-PropertyValue -Item $health -Name "schema_version" -Default "") -eq "rap.remote_workspace_real_adapter_process_health_probe.v1" -and + -not [bool](Get-PropertyValue -Item $health -Name "health_probe_enabled" -Default $true) -and + [string](Get-PropertyValue -Item $health -Name "payload_traffic" -Default "") -eq "none" -and + (Test-RequiredItems -Items (Get-PropertyValue -Item $health -Name "missing_signals" -Default @()) -Required $requiredHealthSignals) + ) + $featuresCompatible = Test-FeatureMap -Features $features + $aligned = ( + $enabledRequested -eq $decisionEnabledRequested -and + [bool](Get-PropertyValue -Item $features -Name "process_health_probe" -Default $false) -eq ($null -ne $health) -and + -not [bool](Get-PropertyValue -Item $preconditions -Name "process_start_allowed" -Default $true) -and + -not [bool](Get-PropertyValue -Item $health -Name "health_probe_enabled" -Default $true) -and + [string](Get-PropertyValue -Item $contract -Name "payload_traffic" -Default "") -eq [string](Get-PropertyValue -Item $decision -Name "payload_traffic" -Default "") -and + [string](Get-PropertyValue -Item $health -Name "payload_traffic" -Default "") -eq "none" + ) + + return [ordered]@{ + node_id = $Node.id + node_name = $Node.name + reported_state = Get-PropertyValue -Item $Status -Name "reported_state" -Default $null + execution_mode = Get-PropertyValue -Item $payload -Name "execution_mode" -Default $null + enabled_requested = $enabledRequested + args_json_shape = Get-PropertyValue -Item $projection -Name "args_json_shape" -Default $null + activation_decision = Get-PropertyValue -Item $decision -Name "decision" -Default $null + process_start_allowed = Get-PropertyValue -Item $preconditions -Name "process_start_allowed" -Default $null + health_probe_enabled = Get-PropertyValue -Item $health -Name "health_probe_enabled" -Default $null + health_missing_signals = Get-PropertyValue -Item $health -Name "missing_signals" -Default @() + contract_compatible = $contractCompatible + projection_compatible = $projectionCompatible + decision_compatible = $decisionCompatible + features_compatible = $featuresCompatible + preconditions_compatible = $preconditionsCompatible + health_probe_compatible = $healthCompatible + all_contracts_aligned = $aligned + passed = ($contractCompatible -and $projectionCompatible -and $decisionCompatible -and $featuresCompatible -and $preconditionsCompatible -and $healthCompatible -and $aligned) + } +} + +$requestedNode = Get-NodeByName -Name $RequestedNodeName +$defaultNode = Get-NodeByName -Name $DefaultNodeName +Enable-RdpWorkerProbe -Node $requestedNode +Enable-RdpWorkerProbe -Node $defaultNode + +$requestedStatus = $null +$defaultStatus = $null +$rows = @() +$deadline = (Get-Date).AddSeconds(80) +while ((Get-Date) -lt $deadline) { + Start-Sleep -Seconds 5 + $requestedStatus = Get-RdpWorkerStatus -NodeID $requestedNode.id + $defaultStatus = Get-RdpWorkerStatus -NodeID $defaultNode.id + if ($null -ne $requestedStatus -and $null -ne $defaultStatus) { + $rows = @( + (New-HandoffRow -Node $requestedNode -Status $requestedStatus -ExpectedEnabledRequested $true -ExpectedArgsShape "json_array" -ExpectedConfigPresent $true), + (New-HandoffRow -Node $defaultNode -Status $defaultStatus -ExpectedEnabledRequested $false -ExpectedArgsShape "absent" -ExpectedConfigPresent $false) + ) + if (@($rows | Where-Object { -not $_.passed }).Count -eq 0) { break } + } +} + +$checks = [ordered]@{ + requested_status_present = ($null -ne $requestedStatus) + default_status_present = ($null -ne $defaultStatus) + handoff_rows_present = ($rows.Count -eq 2) + all_rows_passed = (@($rows | Where-Object { -not $_.passed }).Count -eq 0) +} +$failed = @($checks.GetEnumerator() | Where-Object { -not $_.Value } | ForEach-Object { $_.Key }) + +$result = [ordered]@{ + schema_version = "c19z54.remote_workspace_real_adapter_handoff_v4_smoke.v1" + cluster_id = $ClusterID + required_env = $requiredEnv + required_gates = $requiredGates + required_precondition_checks = $requiredPreconditionChecks + required_health_signals = $requiredHealthSignals + required_features = $requiredFeatures + handoff_rows = $rows + checks = $checks + failed_checks = $failed + passed = ($failed.Count -eq 0) +} + +$fullResultPath = Join-Path $repoRoot $ResultPath +$resultDir = Split-Path -Parent $fullResultPath +if ($resultDir) { New-Item -ItemType Directory -Force -Path $resultDir | Out-Null } +$result | ConvertTo-Json -Depth 100 | Set-Content -Encoding UTF8 -Path $fullResultPath + +if (-not $result.passed) { + throw "C19Z54 remote workspace real-adapter handoff v4 smoke failed. Result: $fullResultPath Failed: $($failed -join ', ')" +} + +Write-Host "C19Z54 remote workspace real-adapter handoff v4 smoke passed. Result: $fullResultPath" +$result diff --git a/scripts/fabric/c19z55-remote-workspace-real-adapter-mode-matrix-v3-smoke.ps1 b/scripts/fabric/c19z55-remote-workspace-real-adapter-mode-matrix-v3-smoke.ps1 new file mode 100644 index 0000000..4661928 --- /dev/null +++ b/scripts/fabric/c19z55-remote-workspace-real-adapter-mode-matrix-v3-smoke.ps1 @@ -0,0 +1,226 @@ +param( + [string]$ApiBaseUrl = "http://192.168.200.61:18121/api/v1", + [string]$ClusterID = "cfc0743d-d960-49fb-9de8-96e063d5e4aa", + [string]$ActorUserID = "f67d943f-5397-4b3a-a229-695fe67ad700", + [string]$NodeName = "test-1", + [string]$ResultPath = "artifacts\c19z55-remote-workspace-real-adapter-mode-matrix-v3-smoke-result.json" +) + +Set-StrictMode -Version Latest +$ErrorActionPreference = "Stop" + +$repoRoot = (Resolve-Path (Join-Path $PSScriptRoot "..\..")).ProviderPath +$runId = "c19z55-" + (Get-Date -Format "yyyyMMdd-HHmmss") +$requiredPreconditionChecks = @( + "real_runtime_stage_enabled", + "command_config_validated", + "workdir_config_validated", + "process_identity_policy_bound", + "fabric_service_channel_runtime_ready", + "payload_forwarding_contract_enabled", + "health_probe_contract_enabled" +) +$requiredHealthSignals = @( + "process_started", + "process_exit_status", + "adapter_control_channel_ready", + "fabric_service_channel_bound", + "payload_forwarding_contract_ready" +) + +function Invoke-Api { + param([string]$Method, [string]$Path, [object]$Body = $null) + if ($null -eq $Body) { return Invoke-RestMethod -Method $Method -Uri "$ApiBaseUrl$Path" -TimeoutSec 30 } + return Invoke-RestMethod -Method $Method -Uri "$ApiBaseUrl$Path" -ContentType "application/json" -Body ($Body | ConvertTo-Json -Depth 80) -TimeoutSec 30 +} + +function Get-PropertyValue { + param([object]$Item, [string]$Name, [object]$Default = $null) + if ($null -eq $Item) { return $Default } + $property = $Item.PSObject.Properties[$Name] + if ($null -eq $property) { return $Default } + return $property.Value +} + +function Get-MapBool { + param([hashtable]$Map, [string]$Name) + if ($null -eq $Map -or -not $Map.ContainsKey($Name)) { return $false } + return [bool]$Map[$Name] +} + +function Test-ArrayItem { + param([object]$Items, [string]$Want) + foreach ($item in @($Items)) { + if ([string]$item -eq $Want) { return $true } + } + return $false +} + +function Test-RequiredItems { + param([object]$Items, [string[]]$Required) + foreach ($item in $Required) { + if (-not (Test-ArrayItem -Items $Items -Want $item)) { return $false } + } + return $true +} + +function Get-NodeByName { + param([string]$Name) + $nodes = (Invoke-Api -Method GET -Path "/clusters/$ClusterID/nodes?actor_user_id=$ActorUserID").nodes + $node = @($nodes | Where-Object { $_.name -eq $Name }) | Select-Object -First 1 + if ($null -eq $node) { throw "Node '$Name' was not found in cluster $ClusterID" } + return $node +} + +function Set-RdpWorkerDesired { + param([object]$Node, [string]$Mode, [hashtable]$Config) + Invoke-Api -Method PUT -Path "/clusters/$ClusterID/nodes/$($Node.id)/workloads/rdp-worker/desired" -Body @{ + actor_user_id = $ActorUserID + desired_state = "enabled" + version = "c19z55-real-adapter-mode-matrix-v3-$Mode-$runId" + runtime_mode = "native" + artifact_ref = "builtin:rdp-worker-mode-matrix-v3" + config = $Config + environment = @{} + } | Out-Null +} + +function Get-RdpWorkerStatus { + param([string]$NodeID) + $statuses = (Invoke-Api -Method GET -Path "/clusters/$ClusterID/nodes/$NodeID/workloads/status?actor_user_id=$ActorUserID").workload_statuses + return @($statuses | Where-Object { $_.service_type -eq "rdp-worker" } | Select-Object -First 1) +} + +function Wait-RdpWorkerMode { + param([object]$Node, [string]$ExpectedVersion) + $deadline = (Get-Date).AddSeconds(80) + $last = $null + while ((Get-Date) -lt $deadline) { + Start-Sleep -Seconds 5 + $last = Get-RdpWorkerStatus -NodeID $Node.id + if ($null -ne $last -and [string](Get-PropertyValue -Item $last -Name "version" -Default "") -eq $ExpectedVersion) { + return $last + } + } + return $last +} + +function New-MatrixRow { + param([object]$Status, [hashtable]$Case) + $payload = Get-PropertyValue -Item $Status -Name "status_payload" -Default $null + $contract = Get-PropertyValue -Item $payload -Name "real_adapter_supervision" -Default $null + $projection = Get-PropertyValue -Item $contract -Name "config_projection" -Default $null + $decision = Get-PropertyValue -Item $contract -Name "activation_decision" -Default $null + $preconditions = Get-PropertyValue -Item $contract -Name "process_supervisor_preconditions" -Default $null + $health = Get-PropertyValue -Item $contract -Name "process_health_probe" -Default $null + $features = Get-PropertyValue -Item $contract -Name "features" -Default $null + + $reportedState = [string](Get-PropertyValue -Item $Status -Name "reported_state" -Default "") + $executionMode = [string](Get-PropertyValue -Item $payload -Name "execution_mode" -Default "") + $traffic = [string](Get-PropertyValue -Item $payload -Name "traffic" -Default "") + $payloadTraffic = [string](Get-PropertyValue -Item $payload -Name "payload_traffic" -Default "none") + $processStartAllowed = [bool](Get-PropertyValue -Item $preconditions -Name "process_start_allowed" -Default $true) + $healthProbeEnabled = [bool](Get-PropertyValue -Item $health -Name "health_probe_enabled" -Default $true) + $missingChecksVisible = Test-RequiredItems -Items (Get-PropertyValue -Item $preconditions -Name "missing_checks" -Default @()) -Required $requiredPreconditionChecks + $missingSignalsVisible = Test-RequiredItems -Items (Get-PropertyValue -Item $health -Name "missing_signals" -Default @()) -Required $requiredHealthSignals + $preconditionsVisible = ( + [string](Get-PropertyValue -Item $preconditions -Name "schema_version" -Default "") -eq "rap.remote_workspace_real_adapter_process_supervisor_preconditions.v1" -and + -not $processStartAllowed -and + $missingChecksVisible + ) + $healthProbeVisible = ( + [string](Get-PropertyValue -Item $health -Name "schema_version" -Default "") -eq "rap.remote_workspace_real_adapter_process_health_probe.v1" -and + -not $healthProbeEnabled -and + [string](Get-PropertyValue -Item $health -Name "payload_traffic" -Default "") -eq "none" -and + $missingSignalsVisible + ) + + $passed = ( + $reportedState -eq [string]$Case.expected_reported_state -and + $executionMode -eq [string]$Case.expected_execution_mode -and + $traffic -eq [string]$Case.expected_traffic -and + $payloadTraffic -eq "none" -and + -not [bool](Get-PropertyValue -Item $contract -Name "enabled" -Default $true) -and + [string](Get-PropertyValue -Item $decision -Name "decision" -Default "") -eq "blocked" -and + -not [bool](Get-PropertyValue -Item $decision -Name "activation_allowed" -Default $true) -and + [bool](Get-PropertyValue -Item $projection -Name "enabled_requested" -Default $false) -and + [bool](Get-PropertyValue -Item $features -Name "process_supervisor_preconditions" -Default $false) -and + [bool](Get-PropertyValue -Item $features -Name "process_supervisor_start_disabled" -Default $false) -and + [bool](Get-PropertyValue -Item $features -Name "process_health_probe" -Default $false) -and + [bool](Get-PropertyValue -Item $features -Name "process_health_probe_disabled" -Default $false) -and + $preconditionsVisible -and + $healthProbeVisible + ) + + return [ordered]@{ + mode = $Case.mode + adapter_contract_probe = Get-MapBool -Map $Case.config -Name "adapter_contract_probe" + real_adapter_supervision = Get-MapBool -Map $Case.config -Name "real_adapter_supervision" + reported_state = $reportedState + execution_mode = $executionMode + traffic = $traffic + payload_traffic = $payloadTraffic + activation_decision = Get-PropertyValue -Item $decision -Name "decision" -Default $null + activation_allowed = Get-PropertyValue -Item $decision -Name "activation_allowed" -Default $null + process_start_allowed = $processStartAllowed + preconditions_visible = $preconditionsVisible + missing_checks_visible = $missingChecksVisible + process_start_disabled_feature = Get-PropertyValue -Item $features -Name "process_supervisor_start_disabled" -Default $null + health_probe_enabled = $healthProbeEnabled + health_probe_visible = $healthProbeVisible + missing_signals_visible = $missingSignalsVisible + health_probe_disabled_feature = Get-PropertyValue -Item $features -Name "process_health_probe_disabled" -Default $null + passed = $passed + } +} + +$node = Get-NodeByName -Name $NodeName +$cases = @( + @{ mode = "probe_only"; config = @{ adapter_contract_probe = $true; service_class = "remote_workspace"; run_id = "$runId-probe" }; expected_reported_state = "running"; expected_execution_mode = "contract_probe"; expected_traffic = "none" }, + @{ mode = "real_adapter_only"; config = @{ real_adapter_supervision = $true; service_class = "remote_workspace"; run_id = "$runId-real-only" }; expected_reported_state = "degraded"; expected_execution_mode = "real_adapter_supervision_disabled"; expected_traffic = "blocked" }, + @{ mode = "probe_and_real_adapter"; config = @{ adapter_contract_probe = $true; real_adapter_supervision = $true; service_class = "remote_workspace"; run_id = "$runId-both" }; expected_reported_state = "running"; expected_execution_mode = "contract_probe"; expected_traffic = "none" } +) + +$rows = @() +foreach ($case in $cases) { + $version = "c19z55-real-adapter-mode-matrix-v3-$($case.mode)-$runId" + Set-RdpWorkerDesired -Node $node -Mode $case.mode -Config $case.config + $status = Wait-RdpWorkerMode -Node $node -ExpectedVersion $version + $rows += New-MatrixRow -Status $status -Case $case +} + +$checks = [ordered]@{ + node_present = ($null -ne $node) + matrix_rows_present = ($rows.Count -eq 3) + all_rows_passed = (@($rows | Where-Object { -not $_.passed }).Count -eq 0) + process_start_disabled_all_rows = (@($rows | Where-Object { [bool]$_.process_start_allowed }).Count -eq 0) + health_probe_disabled_all_rows = (@($rows | Where-Object { [bool]$_.health_probe_enabled }).Count -eq 0) + health_probe_visible_all_rows = (@($rows | Where-Object { -not $_.health_probe_visible }).Count -eq 0) + missing_signals_visible_all_rows = (@($rows | Where-Object { -not $_.missing_signals_visible }).Count -eq 0) +} +$failed = @($checks.GetEnumerator() | Where-Object { -not $_.Value } | ForEach-Object { $_.Key }) + +$result = [ordered]@{ + schema_version = "c19z55.remote_workspace_real_adapter_mode_matrix_v3_smoke.v1" + run_id = $runId + cluster_id = $ClusterID + node = [ordered]@{ id = $node.id; name = $node.name } + required_precondition_checks = $requiredPreconditionChecks + required_health_signals = $requiredHealthSignals + matrix_rows = $rows + checks = $checks + failed_checks = $failed + passed = ($failed.Count -eq 0) +} + +$fullResultPath = Join-Path $repoRoot $ResultPath +$resultDir = Split-Path -Parent $fullResultPath +if ($resultDir) { New-Item -ItemType Directory -Force -Path $resultDir | Out-Null } +$result | ConvertTo-Json -Depth 100 | Set-Content -Encoding UTF8 -Path $fullResultPath + +if (-not $result.passed) { + throw "C19Z55 remote workspace real-adapter mode matrix v3 smoke failed. Result: $fullResultPath Failed: $($failed -join ', ')" +} + +Write-Host "C19Z55 remote workspace real-adapter mode matrix v3 smoke passed. Result: $fullResultPath" +$result diff --git a/scripts/fabric/c19z56-remote-workspace-real-adapter-mode-matrix-v3-compatibility-smoke.ps1 b/scripts/fabric/c19z56-remote-workspace-real-adapter-mode-matrix-v3-compatibility-smoke.ps1 new file mode 100644 index 0000000..49a8cd6 --- /dev/null +++ b/scripts/fabric/c19z56-remote-workspace-real-adapter-mode-matrix-v3-compatibility-smoke.ps1 @@ -0,0 +1,134 @@ +param( + [string]$ApiBaseUrl = "http://192.168.200.61:18121/api/v1", + [string]$ClusterID = "cfc0743d-d960-49fb-9de8-96e063d5e4aa", + [string]$ActorUserID = "f67d943f-5397-4b3a-a229-695fe67ad700", + [string]$NodeName = "test-1", + [string]$ResultPath = "artifacts\c19z56-remote-workspace-real-adapter-mode-matrix-v3-compatibility-smoke-result.json" +) + +Set-StrictMode -Version Latest +$ErrorActionPreference = "Stop" + +$repoRoot = (Resolve-Path (Join-Path $PSScriptRoot "..\..")).ProviderPath +$sourceResultPath = "artifacts\c19z56-remote-workspace-real-adapter-mode-matrix-v3-source-result.json" +$requiredRowFields = @( + "mode", + "adapter_contract_probe", + "real_adapter_supervision", + "reported_state", + "execution_mode", + "traffic", + "payload_traffic", + "activation_decision", + "activation_allowed", + "process_start_allowed", + "preconditions_visible", + "missing_checks_visible", + "process_start_disabled_feature", + "health_probe_enabled", + "health_probe_visible", + "missing_signals_visible", + "health_probe_disabled_feature", + "passed" +) +$expectedRows = @( + [ordered]@{ mode = "probe_only"; adapter_contract_probe = $true; real_adapter_supervision = $false; reported_state = "running"; execution_mode = "contract_probe"; traffic = "none" }, + [ordered]@{ mode = "real_adapter_only"; adapter_contract_probe = $false; real_adapter_supervision = $true; reported_state = "degraded"; execution_mode = "real_adapter_supervision_disabled"; traffic = "blocked" }, + [ordered]@{ mode = "probe_and_real_adapter"; adapter_contract_probe = $true; real_adapter_supervision = $true; reported_state = "running"; execution_mode = "contract_probe"; traffic = "none" } +) + +function Get-PropertyValue { + param([object]$Item, [string]$Name, [object]$Default = $null) + if ($null -eq $Item) { return $Default } + $property = $Item.PSObject.Properties[$Name] + if ($null -eq $property) { return $Default } + return $property.Value +} + +function Test-RowHasFields { + param([object]$Row) + foreach ($field in $requiredRowFields) { + if ($null -eq $Row.PSObject.Properties[$field]) { return $false } + } + return $true +} + +function Get-RowByMode { + param([object[]]$Rows, [string]$Mode) + return @($Rows | Where-Object { [string](Get-PropertyValue -Item $_ -Name "mode" -Default "") -eq $Mode } | Select-Object -First 1) +} + +function Test-ExpectedRow { + param([object]$Row, [object]$Expected) + if ($null -eq $Row) { return $false } + return ( + (Test-RowHasFields -Row $Row) -and + [bool](Get-PropertyValue -Item $Row -Name "adapter_contract_probe" -Default (-not [bool]$Expected.adapter_contract_probe)) -eq [bool]$Expected.adapter_contract_probe -and + [bool](Get-PropertyValue -Item $Row -Name "real_adapter_supervision" -Default (-not [bool]$Expected.real_adapter_supervision)) -eq [bool]$Expected.real_adapter_supervision -and + [string](Get-PropertyValue -Item $Row -Name "reported_state" -Default "") -eq [string]$Expected.reported_state -and + [string](Get-PropertyValue -Item $Row -Name "execution_mode" -Default "") -eq [string]$Expected.execution_mode -and + [string](Get-PropertyValue -Item $Row -Name "traffic" -Default "") -eq [string]$Expected.traffic -and + [string](Get-PropertyValue -Item $Row -Name "payload_traffic" -Default "") -eq "none" -and + [string](Get-PropertyValue -Item $Row -Name "activation_decision" -Default "") -eq "blocked" -and + -not [bool](Get-PropertyValue -Item $Row -Name "activation_allowed" -Default $true) -and + -not [bool](Get-PropertyValue -Item $Row -Name "process_start_allowed" -Default $true) -and + [bool](Get-PropertyValue -Item $Row -Name "preconditions_visible" -Default $false) -and + [bool](Get-PropertyValue -Item $Row -Name "missing_checks_visible" -Default $false) -and + [bool](Get-PropertyValue -Item $Row -Name "process_start_disabled_feature" -Default $false) -and + -not [bool](Get-PropertyValue -Item $Row -Name "health_probe_enabled" -Default $true) -and + [bool](Get-PropertyValue -Item $Row -Name "health_probe_visible" -Default $false) -and + [bool](Get-PropertyValue -Item $Row -Name "missing_signals_visible" -Default $false) -and + [bool](Get-PropertyValue -Item $Row -Name "health_probe_disabled_feature" -Default $false) -and + [bool](Get-PropertyValue -Item $Row -Name "passed" -Default $false) + ) +} + +& powershell -ExecutionPolicy Bypass -File (Join-Path $PSScriptRoot "c19z55-remote-workspace-real-adapter-mode-matrix-v3-smoke.ps1") ` + -ApiBaseUrl $ApiBaseUrl ` + -ClusterID $ClusterID ` + -ActorUserID $ActorUserID ` + -NodeName $NodeName ` + -ResultPath $sourceResultPath | Out-Null + +$sourceFile = Join-Path $repoRoot $sourceResultPath +$sourceResult = Get-Content -Raw -Path $sourceFile | ConvertFrom-Json +$rows = @($sourceResult.matrix_rows) +$rowChecks = [ordered]@{} +foreach ($expected in $expectedRows) { + $mode = [string]$expected.mode + $rowChecks[$mode] = Test-ExpectedRow -Row (Get-RowByMode -Rows $rows -Mode $mode) -Expected $expected +} + +$checks = [ordered]@{ + source_smoke_passed = ([bool]$sourceResult.passed) + source_schema_expected = ([string]$sourceResult.schema_version -eq "c19z55.remote_workspace_real_adapter_mode_matrix_v3_smoke.v1") + row_count_three = ($rows.Count -eq 3) + required_fields_declared = ($requiredRowFields.Count -eq 18) + all_expected_modes_present = ((Get-RowByMode -Rows $rows -Mode "probe_only") -and (Get-RowByMode -Rows $rows -Mode "real_adapter_only") -and (Get-RowByMode -Rows $rows -Mode "probe_and_real_adapter")) + all_rows_compatible = (@($rowChecks.GetEnumerator() | Where-Object { -not $_.Value }).Count -eq 0) +} +$failed = @($checks.GetEnumerator() | Where-Object { -not $_.Value } | ForEach-Object { $_.Key }) + +$result = [ordered]@{ + schema_version = "c19z56.remote_workspace_real_adapter_mode_matrix_v3_compatibility_smoke.v1" + source_result_path = $sourceFile + cluster_id = $ClusterID + required_row_fields = $requiredRowFields + row_checks = $rowChecks + matrix_rows = $rows + checks = $checks + failed_checks = $failed + passed = ($failed.Count -eq 0) +} + +$fullResultPath = Join-Path $repoRoot $ResultPath +$resultDir = Split-Path -Parent $fullResultPath +if ($resultDir) { New-Item -ItemType Directory -Force -Path $resultDir | Out-Null } +$result | ConvertTo-Json -Depth 100 | Set-Content -Encoding UTF8 -Path $fullResultPath + +if (-not $result.passed) { + throw "C19Z56 remote workspace real-adapter mode matrix v3 compatibility smoke failed. Result: $fullResultPath Failed: $($failed -join ', ')" +} + +Write-Host "C19Z56 remote workspace real-adapter mode matrix v3 compatibility smoke passed. Result: $fullResultPath" +$result diff --git a/scripts/fabric/c19z57-remote-workspace-real-adapter-readiness-handoff-summary-smoke.ps1 b/scripts/fabric/c19z57-remote-workspace-real-adapter-readiness-handoff-summary-smoke.ps1 new file mode 100644 index 0000000..d876552 --- /dev/null +++ b/scripts/fabric/c19z57-remote-workspace-real-adapter-readiness-handoff-summary-smoke.ps1 @@ -0,0 +1,208 @@ +param( + [string]$ApiBaseUrl = "http://192.168.200.61:18121/api/v1", + [string]$ClusterID = "cfc0743d-d960-49fb-9de8-96e063d5e4aa", + [string]$ActorUserID = "f67d943f-5397-4b3a-a229-695fe67ad700", + [string]$RequestedNodeName = "test-1", + [string]$DefaultNodeName = "test-2", + [string]$MatrixNodeName = "test-1", + [string]$ResultPath = "artifacts\c19z57-remote-workspace-real-adapter-readiness-handoff-summary-smoke-result.json" +) + +Set-StrictMode -Version Latest +$ErrorActionPreference = "Stop" + +$repoRoot = (Resolve-Path (Join-Path $PSScriptRoot "..\..")).ProviderPath +$handoffSourcePath = "artifacts\c19z57-remote-workspace-real-adapter-readiness-handoff-summary-handoff-source-result.json" +$matrixSourcePath = "artifacts\c19z57-remote-workspace-real-adapter-readiness-handoff-summary-matrix-source-result.json" +$requiredChecklistItems = @( + "handoff_v4_complete", + "mode_matrix_v3_compatible", + "requested_and_default_config_shapes_visible", + "desired_workload_modes_visible", + "activation_blocked", + "process_start_disabled", + "health_probe_disabled", + "payload_traffic_none", + "missing_gates_visible", + "missing_health_signals_visible" +) +$expectedModes = @("probe_only", "real_adapter_only", "probe_and_real_adapter") + +function Get-PropertyValue { + param([object]$Item, [string]$Name, [object]$Default = $null) + if ($null -eq $Item) { return $Default } + $property = $Item.PSObject.Properties[$Name] + if ($null -eq $property) { return $Default } + return $property.Value +} + +function Get-RowByName { + param([object[]]$Rows, [string]$Name) + return @($Rows | Where-Object { [string](Get-PropertyValue -Item $_ -Name "node_name" -Default "") -eq $Name } | Select-Object -First 1) +} + +function Get-RowByMode { + param([object[]]$Rows, [string]$Mode) + return @($Rows | Where-Object { [string](Get-PropertyValue -Item $_ -Name "mode" -Default "") -eq $Mode } | Select-Object -First 1) +} + +function Test-AllRowsBool { + param([object[]]$Rows, [string]$Field, [bool]$Expected) + if ($Rows.Count -eq 0) { return $false } + return (@($Rows | Where-Object { [bool](Get-PropertyValue -Item $_ -Name $Field -Default (-not $Expected)) -ne $Expected }).Count -eq 0) +} + +function Test-AllRowsString { + param([object[]]$Rows, [string]$Field, [string]$Expected) + if ($Rows.Count -eq 0) { return $false } + return (@($Rows | Where-Object { [string](Get-PropertyValue -Item $_ -Name $Field -Default "") -ne $Expected }).Count -eq 0) +} + +function Test-ModesPresent { + param([object[]]$Rows) + foreach ($mode in $expectedModes) { + if ($null -eq (Get-RowByMode -Rows $Rows -Mode $mode)) { return $false } + } + return $true +} + +function New-ChecklistItem { + param([string]$Name, [bool]$Passed, [string]$Evidence) + return [ordered]@{ + name = $Name + passed = $Passed + evidence = $Evidence + } +} + +& powershell -ExecutionPolicy Bypass -File (Join-Path $PSScriptRoot "c19z54-remote-workspace-real-adapter-handoff-v4-smoke.ps1") ` + -ApiBaseUrl $ApiBaseUrl ` + -ClusterID $ClusterID ` + -ActorUserID $ActorUserID ` + -RequestedNodeName $RequestedNodeName ` + -DefaultNodeName $DefaultNodeName ` + -ResultPath $handoffSourcePath | Out-Null + +& powershell -ExecutionPolicy Bypass -File (Join-Path $PSScriptRoot "c19z56-remote-workspace-real-adapter-mode-matrix-v3-compatibility-smoke.ps1") ` + -ApiBaseUrl $ApiBaseUrl ` + -ClusterID $ClusterID ` + -ActorUserID $ActorUserID ` + -NodeName $MatrixNodeName ` + -ResultPath $matrixSourcePath | Out-Null + +$handoffSourceFile = Join-Path $repoRoot $handoffSourcePath +$matrixSourceFile = Join-Path $repoRoot $matrixSourcePath +$handoff = Get-Content -Raw -Path $handoffSourceFile | ConvertFrom-Json +$matrix = Get-Content -Raw -Path $matrixSourceFile | ConvertFrom-Json +$handoffRows = @($handoff.handoff_rows) +$matrixRows = @($matrix.matrix_rows) +$requestedRow = Get-RowByName -Rows $handoffRows -Name $RequestedNodeName +$defaultRow = Get-RowByName -Rows $handoffRows -Name $DefaultNodeName + +$handoffComplete = ([bool]$handoff.passed -and [string]$handoff.schema_version -eq "c19z54.remote_workspace_real_adapter_handoff_v4_smoke.v1" -and $handoffRows.Count -eq 2) +$matrixCompatible = ([bool]$matrix.passed -and [string]$matrix.schema_version -eq "c19z56.remote_workspace_real_adapter_mode_matrix_v3_compatibility_smoke.v1" -and $matrixRows.Count -eq 3) +$configShapesVisible = ( + $null -ne $requestedRow -and + $null -ne $defaultRow -and + [bool](Get-PropertyValue -Item $requestedRow -Name "enabled_requested" -Default $false) -and + -not [bool](Get-PropertyValue -Item $defaultRow -Name "enabled_requested" -Default $true) -and + [string](Get-PropertyValue -Item $requestedRow -Name "args_json_shape" -Default "") -eq "json_array" -and + [string](Get-PropertyValue -Item $defaultRow -Name "args_json_shape" -Default "") -eq "absent" +) +$modesVisible = Test-ModesPresent -Rows $matrixRows +$activationBlocked = ( + (Test-AllRowsString -Rows $handoffRows -Field "activation_decision" -Expected "blocked") -and + (Test-AllRowsString -Rows $matrixRows -Field "activation_decision" -Expected "blocked") +) +$processStartDisabled = ( + (Test-AllRowsBool -Rows $handoffRows -Field "process_start_allowed" -Expected $false) -and + (Test-AllRowsBool -Rows $matrixRows -Field "process_start_allowed" -Expected $false) +) +$healthProbeDisabled = ( + (Test-AllRowsBool -Rows $handoffRows -Field "health_probe_enabled" -Expected $false) -and + (Test-AllRowsBool -Rows $matrixRows -Field "health_probe_enabled" -Expected $false) -and + (Test-AllRowsBool -Rows $matrixRows -Field "health_probe_visible" -Expected $true) +) +$payloadTrafficNone = (Test-AllRowsString -Rows $matrixRows -Field "payload_traffic" -Expected "none") +$missingGatesVisible = (Test-AllRowsBool -Rows $handoffRows -Field "decision_compatible" -Expected $true) +$missingHealthSignalsVisible = ( + (Test-AllRowsBool -Rows $handoffRows -Field "health_probe_compatible" -Expected $true) -and + (Test-AllRowsBool -Rows $matrixRows -Field "missing_signals_visible" -Expected $true) +) + +$checklist = @( + (New-ChecklistItem -Name "handoff_v4_complete" -Passed $handoffComplete -Evidence "C19Z54 handoff rows are present and passed"), + (New-ChecklistItem -Name "mode_matrix_v3_compatible" -Passed $matrixCompatible -Evidence "C19Z56 matrix rows are present and passed"), + (New-ChecklistItem -Name "requested_and_default_config_shapes_visible" -Passed $configShapesVisible -Evidence "requested=json_array enabled, default=absent disabled"), + (New-ChecklistItem -Name "desired_workload_modes_visible" -Passed $modesVisible -Evidence "probe_only, real_adapter_only, probe_and_real_adapter"), + (New-ChecklistItem -Name "activation_blocked" -Passed $activationBlocked -Evidence "activation_decision=blocked in handoff and matrix rows"), + (New-ChecklistItem -Name "process_start_disabled" -Passed $processStartDisabled -Evidence "process_start_allowed=false in handoff and matrix rows"), + (New-ChecklistItem -Name "health_probe_disabled" -Passed $healthProbeDisabled -Evidence "health_probe_enabled=false and health_probe_visible=true"), + (New-ChecklistItem -Name "payload_traffic_none" -Passed $payloadTrafficNone -Evidence "payload_traffic=none in all matrix rows"), + (New-ChecklistItem -Name "missing_gates_visible" -Passed $missingGatesVisible -Evidence "handoff decision compatibility includes missing gate proof"), + (New-ChecklistItem -Name "missing_health_signals_visible" -Passed $missingHealthSignalsVisible -Evidence "handoff and matrix expose missing health-signal proof") +) + +$checks = [ordered]@{ + handoff_source_passed = $handoffComplete + matrix_source_passed = $matrixCompatible + required_checklist_items_declared = ($requiredChecklistItems.Count -eq 10) + checklist_count_expected = ($checklist.Count -eq $requiredChecklistItems.Count) + all_required_checklist_items_present = (@($requiredChecklistItems | Where-Object { + $want = $_ + $null -eq (@($checklist | Where-Object { [string]$_.name -eq $want }) | Select-Object -First 1) + }).Count -eq 0) + all_checklist_items_passed = (@($checklist | Where-Object { -not [bool]$_.passed }).Count -eq 0) + activation_blocked = $activationBlocked + process_start_disabled = $processStartDisabled + health_probe_disabled = $healthProbeDisabled + payload_traffic_none = $payloadTrafficNone +} +$failed = @($checks.GetEnumerator() | Where-Object { -not $_.Value } | ForEach-Object { $_.Key }) + +$summary = [ordered]@{ + schema_version = "rap.remote_workspace_real_adapter_disabled_runtime_readiness_summary.v1" + readiness_state = "blocked_until_real_runtime_stage" + operator_status = "not_ready" + operator_action = "keep_real_adapter_disabled" + runtime_stage = "disabled_until_real_runtime_stage" + activation_decision = "blocked" + process_start_allowed = $false + health_probe_enabled = $false + payload_traffic = "none" + requested_config_visible = ($null -ne $requestedRow) + default_config_visible = ($null -ne $defaultRow) + mode_matrix_visible = $modesVisible + checklist_passed_count = @($checklist | Where-Object { [bool]$_.passed }).Count + checklist_total_count = $checklist.Count + checklist_status = $(if (@($checklist | Where-Object { -not [bool]$_.passed }).Count -eq 0) { "complete" } else { "incomplete" }) +} + +$result = [ordered]@{ + schema_version = "c19z57.remote_workspace_real_adapter_readiness_handoff_summary_smoke.v1" + cluster_id = $ClusterID + source_results = [ordered]@{ + handoff_v4 = $handoffSourceFile + mode_matrix_v3_compatibility = $matrixSourceFile + } + required_checklist_items = $requiredChecklistItems + readiness_summary = $summary + operator_checklist = $checklist + handoff_rows = $handoffRows + matrix_rows = $matrixRows + checks = $checks + failed_checks = $failed + passed = ($failed.Count -eq 0) +} + +$fullResultPath = Join-Path $repoRoot $ResultPath +$resultDir = Split-Path -Parent $fullResultPath +if ($resultDir) { New-Item -ItemType Directory -Force -Path $resultDir | Out-Null } +$result | ConvertTo-Json -Depth 100 | Set-Content -Encoding UTF8 -Path $fullResultPath + +if (-not $result.passed) { + throw "C19Z57 remote workspace real-adapter readiness handoff summary smoke failed. Result: $fullResultPath Failed: $($failed -join ', ')" +} + +Write-Host "C19Z57 remote workspace real-adapter readiness handoff summary smoke passed. Result: $fullResultPath" +$result diff --git a/scripts/fabric/c19z58-remote-workspace-real-adapter-readiness-handoff-summary-compatibility-smoke.ps1 b/scripts/fabric/c19z58-remote-workspace-real-adapter-readiness-handoff-summary-compatibility-smoke.ps1 new file mode 100644 index 0000000..a5fa298 --- /dev/null +++ b/scripts/fabric/c19z58-remote-workspace-real-adapter-readiness-handoff-summary-compatibility-smoke.ps1 @@ -0,0 +1,172 @@ +param( + [string]$ApiBaseUrl = "http://192.168.200.61:18121/api/v1", + [string]$ClusterID = "cfc0743d-d960-49fb-9de8-96e063d5e4aa", + [string]$ActorUserID = "f67d943f-5397-4b3a-a229-695fe67ad700", + [string]$RequestedNodeName = "test-1", + [string]$DefaultNodeName = "test-2", + [string]$MatrixNodeName = "test-1", + [string]$ResultPath = "artifacts\c19z58-remote-workspace-real-adapter-readiness-handoff-summary-compatibility-smoke-result.json" +) + +Set-StrictMode -Version Latest +$ErrorActionPreference = "Stop" + +$repoRoot = (Resolve-Path (Join-Path $PSScriptRoot "..\..")).ProviderPath +$sourceResultPath = "artifacts\c19z58-remote-workspace-real-adapter-readiness-handoff-summary-source-result.json" +$requiredSummaryFields = @( + "schema_version", + "readiness_state", + "operator_status", + "operator_action", + "runtime_stage", + "activation_decision", + "process_start_allowed", + "health_probe_enabled", + "payload_traffic", + "requested_config_visible", + "default_config_visible", + "mode_matrix_visible", + "checklist_passed_count", + "checklist_total_count", + "checklist_status" +) +$requiredChecklistItemFields = @("name", "passed", "evidence") +$requiredChecklistItems = @( + "handoff_v4_complete", + "mode_matrix_v3_compatible", + "requested_and_default_config_shapes_visible", + "desired_workload_modes_visible", + "activation_blocked", + "process_start_disabled", + "health_probe_disabled", + "payload_traffic_none", + "missing_gates_visible", + "missing_health_signals_visible" +) + +function Get-PropertyValue { + param([object]$Item, [string]$Name, [object]$Default = $null) + if ($null -eq $Item) { return $Default } + $property = $Item.PSObject.Properties[$Name] + if ($null -eq $property) { return $Default } + return $property.Value +} + +function Test-ObjectHasFields { + param([object]$Item, [string[]]$Fields) + if ($null -eq $Item) { return $false } + foreach ($field in $Fields) { + if ($null -eq $Item.PSObject.Properties[$field]) { return $false } + } + return $true +} + +function Get-ChecklistItem { + param([object[]]$Items, [string]$Name) + return @($Items | Where-Object { [string](Get-PropertyValue -Item $_ -Name "name" -Default "") -eq $Name } | Select-Object -First 1) +} + +function Test-ChecklistItemsPresent { + param([object[]]$Items) + foreach ($name in $requiredChecklistItems) { + if ($null -eq (Get-ChecklistItem -Items $Items -Name $name)) { return $false } + } + return $true +} + +function Test-ChecklistItemFields { + param([object[]]$Items) + if ($Items.Count -eq 0) { return $false } + foreach ($item in $Items) { + if (-not (Test-ObjectHasFields -Item $item -Fields $requiredChecklistItemFields)) { return $false } + } + return $true +} + +function Test-ChecklistItemsPassed { + param([object[]]$Items) + if ($Items.Count -eq 0) { return $false } + return (@($Items | Where-Object { -not [bool](Get-PropertyValue -Item $_ -Name "passed" -Default $false) }).Count -eq 0) +} + +& powershell -ExecutionPolicy Bypass -File (Join-Path $PSScriptRoot "c19z57-remote-workspace-real-adapter-readiness-handoff-summary-smoke.ps1") ` + -ApiBaseUrl $ApiBaseUrl ` + -ClusterID $ClusterID ` + -ActorUserID $ActorUserID ` + -RequestedNodeName $RequestedNodeName ` + -DefaultNodeName $DefaultNodeName ` + -MatrixNodeName $MatrixNodeName ` + -ResultPath $sourceResultPath | Out-Null + +$sourceFile = Join-Path $repoRoot $sourceResultPath +$sourceResult = Get-Content -Raw -Path $sourceFile | ConvertFrom-Json +$summary = Get-PropertyValue -Item $sourceResult -Name "readiness_summary" -Default $null +$checklist = @((Get-PropertyValue -Item $sourceResult -Name "operator_checklist" -Default @())) + +$summaryFieldsCompatible = Test-ObjectHasFields -Item $summary -Fields $requiredSummaryFields +$summaryValuesCompatible = ( + [string](Get-PropertyValue -Item $summary -Name "schema_version" -Default "") -eq "rap.remote_workspace_real_adapter_disabled_runtime_readiness_summary.v1" -and + [string](Get-PropertyValue -Item $summary -Name "readiness_state" -Default "") -eq "blocked_until_real_runtime_stage" -and + [string](Get-PropertyValue -Item $summary -Name "operator_status" -Default "") -eq "not_ready" -and + [string](Get-PropertyValue -Item $summary -Name "operator_action" -Default "") -eq "keep_real_adapter_disabled" -and + [string](Get-PropertyValue -Item $summary -Name "runtime_stage" -Default "") -eq "disabled_until_real_runtime_stage" -and + [string](Get-PropertyValue -Item $summary -Name "activation_decision" -Default "") -eq "blocked" -and + -not [bool](Get-PropertyValue -Item $summary -Name "process_start_allowed" -Default $true) -and + -not [bool](Get-PropertyValue -Item $summary -Name "health_probe_enabled" -Default $true) -and + [string](Get-PropertyValue -Item $summary -Name "payload_traffic" -Default "") -eq "none" -and + [bool](Get-PropertyValue -Item $summary -Name "requested_config_visible" -Default $false) -and + [bool](Get-PropertyValue -Item $summary -Name "default_config_visible" -Default $false) -and + [bool](Get-PropertyValue -Item $summary -Name "mode_matrix_visible" -Default $false) -and + [string](Get-PropertyValue -Item $summary -Name "checklist_status" -Default "") -eq "complete" +) +$checklistCount = $checklist.Count +$checklistPassedCount = @($checklist | Where-Object { [bool](Get-PropertyValue -Item $_ -Name "passed" -Default $false) }).Count +$summaryCountsCompatible = ( + [int](Get-PropertyValue -Item $summary -Name "checklist_total_count" -Default -1) -eq $requiredChecklistItems.Count -and + [int](Get-PropertyValue -Item $summary -Name "checklist_passed_count" -Default -1) -eq $requiredChecklistItems.Count -and + $checklistCount -eq $requiredChecklistItems.Count -and + $checklistPassedCount -eq $requiredChecklistItems.Count +) +$checklistFieldsCompatible = Test-ChecklistItemFields -Items $checklist +$checklistNamesCompatible = Test-ChecklistItemsPresent -Items $checklist +$checklistValuesCompatible = Test-ChecklistItemsPassed -Items $checklist + +$checks = [ordered]@{ + source_smoke_passed = ([bool]$sourceResult.passed) + source_schema_expected = ([string]$sourceResult.schema_version -eq "c19z57.remote_workspace_real_adapter_readiness_handoff_summary_smoke.v1") + summary_present = ($null -ne $summary) + summary_fields_compatible = $summaryFieldsCompatible + summary_values_compatible = $summaryValuesCompatible + summary_counts_compatible = $summaryCountsCompatible + checklist_count_expected = ($checklistCount -eq $requiredChecklistItems.Count) + checklist_fields_compatible = $checklistFieldsCompatible + checklist_names_compatible = $checklistNamesCompatible + checklist_values_compatible = $checklistValuesCompatible +} +$failed = @($checks.GetEnumerator() | Where-Object { -not $_.Value } | ForEach-Object { $_.Key }) + +$result = [ordered]@{ + schema_version = "c19z58.remote_workspace_real_adapter_readiness_handoff_summary_compatibility_smoke.v1" + source_result_path = $sourceFile + cluster_id = $ClusterID + required_summary_fields = $requiredSummaryFields + required_checklist_item_fields = $requiredChecklistItemFields + required_checklist_items = $requiredChecklistItems + readiness_summary = $summary + operator_checklist = $checklist + checks = $checks + failed_checks = $failed + passed = ($failed.Count -eq 0) +} + +$fullResultPath = Join-Path $repoRoot $ResultPath +$resultDir = Split-Path -Parent $fullResultPath +if ($resultDir) { New-Item -ItemType Directory -Force -Path $resultDir | Out-Null } +$result | ConvertTo-Json -Depth 100 | Set-Content -Encoding UTF8 -Path $fullResultPath + +if (-not $result.passed) { + throw "C19Z58 remote workspace real-adapter readiness handoff summary compatibility smoke failed. Result: $fullResultPath Failed: $($failed -join ', ')" +} + +Write-Host "C19Z58 remote workspace real-adapter readiness handoff summary compatibility smoke passed. Result: $fullResultPath" +$result diff --git a/scripts/fabric/c19z59-remote-workspace-real-adapter-disabled-action-map-smoke.ps1 b/scripts/fabric/c19z59-remote-workspace-real-adapter-disabled-action-map-smoke.ps1 new file mode 100644 index 0000000..7711d31 --- /dev/null +++ b/scripts/fabric/c19z59-remote-workspace-real-adapter-disabled-action-map-smoke.ps1 @@ -0,0 +1,231 @@ +param( + [string]$ApiBaseUrl = "http://192.168.200.61:18121/api/v1", + [string]$ClusterID = "cfc0743d-d960-49fb-9de8-96e063d5e4aa", + [string]$ActorUserID = "f67d943f-5397-4b3a-a229-695fe67ad700", + [string]$RequestedNodeName = "test-1", + [string]$DefaultNodeName = "test-2", + [string]$MatrixNodeName = "test-1", + [string]$ResultPath = "artifacts\c19z59-remote-workspace-real-adapter-disabled-action-map-smoke-result.json" +) + +Set-StrictMode -Version Latest +$ErrorActionPreference = "Stop" + +$repoRoot = (Resolve-Path (Join-Path $PSScriptRoot "..\..")).ProviderPath +$sourceResultPath = "artifacts\c19z59-remote-workspace-real-adapter-disabled-action-map-source-result.json" +$requiredActionFields = @( + "action_key", + "status", + "severity", + "reason", + "derived_from", + "blocks_activation", + "allows_process_start", + "allows_payload_traffic" +) +$requiredActionKeys = @( + "keep_real_adapter_disabled", + "review_real_runtime_stage_gates", + "validate_real_adapter_config_projection", + "prepare_process_supervisor_preconditions", + "prepare_process_health_probe_signals", + "keep_payload_forwarding_disabled" +) +$allowedDerivedFrom = @( + "handoff_v4_complete", + "mode_matrix_v3_compatible", + "requested_and_default_config_shapes_visible", + "activation_blocked", + "process_start_disabled", + "health_probe_disabled", + "payload_traffic_none", + "missing_gates_visible", + "missing_health_signals_visible" +) + +function Get-PropertyValue { + param([object]$Item, [string]$Name, [object]$Default = $null) + if ($null -eq $Item) { return $Default } + if ($Item -is [System.Collections.IDictionary]) { + if ($Item.Contains($Name)) { return $Item[$Name] } + return $Default + } + $property = $Item.PSObject.Properties[$Name] + if ($null -eq $property) { return $Default } + return $property.Value +} + +function Test-ArrayItem { + param([object]$Items, [string]$Want) + foreach ($item in @($Items)) { + if ([string]$item -eq $Want) { return $true } + } + return $false +} + +function Test-ObjectHasFields { + param([object]$Item, [string[]]$Fields) + if ($null -eq $Item) { return $false } + foreach ($field in $Fields) { + if ($Item -is [System.Collections.IDictionary]) { + if (-not $Item.Contains($field)) { return $false } + continue + } + if ($null -eq $Item.PSObject.Properties[$field]) { return $false } + } + return $true +} + +function Get-ChecklistItem { + param([object[]]$Items, [string]$Name) + return @($Items | Where-Object { [string](Get-PropertyValue -Item $_ -Name "name" -Default "") -eq $Name } | Select-Object -First 1) +} + +function Get-ActionByKey { + param([object[]]$Actions, [string]$Key) + return @($Actions | Where-Object { [string](Get-PropertyValue -Item $_ -Name "action_key" -Default "") -eq $Key } | Select-Object -First 1) +} + +function Test-ActionFields { + param([object[]]$Actions) + if ($Actions.Count -eq 0) { return $false } + foreach ($action in $Actions) { + if (-not (Test-ObjectHasFields -Item $action -Fields $requiredActionFields)) { return $false } + } + return $true +} + +function Test-ActionKeysPresent { + param([object[]]$Actions) + foreach ($key in $requiredActionKeys) { + if ($null -eq (Get-ActionByKey -Actions $Actions -Key $key)) { return $false } + } + return $true +} + +function Test-ActionsReferenceChecklist { + param([object[]]$Actions, [object[]]$Checklist) + foreach ($action in $Actions) { + $derived = Get-PropertyValue -Item $action -Name "derived_from" -Default @() + foreach ($name in @($derived)) { + if (-not (Test-ArrayItem -Items $allowedDerivedFrom -Want ([string]$name))) { return $false } + $checklistItem = Get-ChecklistItem -Items $Checklist -Name ([string]$name) + if ($null -eq $checklistItem -or -not [bool](Get-PropertyValue -Item $checklistItem -Name "passed" -Default $false)) { return $false } + } + } + return $true +} + +function Test-ActionsKeepRuntimeDisabled { + param([object[]]$Actions) + if ($Actions.Count -eq 0) { return $false } + foreach ($action in $Actions) { + if (-not [bool](Get-PropertyValue -Item $action -Name "blocks_activation" -Default $false)) { return $false } + if ([bool](Get-PropertyValue -Item $action -Name "allows_process_start" -Default $true)) { return $false } + if ([bool](Get-PropertyValue -Item $action -Name "allows_payload_traffic" -Default $true)) { return $false } + } + return $true +} + +function New-Action { + param([string]$Key, [string]$Severity, [string]$Reason, [string[]]$DerivedFrom) + return [ordered]@{ + action_key = $Key + status = "blocked" + severity = $Severity + reason = $Reason + derived_from = $DerivedFrom + blocks_activation = $true + allows_process_start = $false + allows_payload_traffic = $false + } +} + +& powershell -ExecutionPolicy Bypass -File (Join-Path $PSScriptRoot "c19z58-remote-workspace-real-adapter-readiness-handoff-summary-compatibility-smoke.ps1") ` + -ApiBaseUrl $ApiBaseUrl ` + -ClusterID $ClusterID ` + -ActorUserID $ActorUserID ` + -RequestedNodeName $RequestedNodeName ` + -DefaultNodeName $DefaultNodeName ` + -MatrixNodeName $MatrixNodeName ` + -ResultPath $sourceResultPath | Out-Null + +$sourceFile = Join-Path $repoRoot $sourceResultPath +$sourceResult = Get-Content -Raw -Path $sourceFile | ConvertFrom-Json +$summary = Get-PropertyValue -Item $sourceResult -Name "readiness_summary" -Default $null +$checklist = @((Get-PropertyValue -Item $sourceResult -Name "operator_checklist" -Default @())) + +$actions = @( + (New-Action -Key "keep_real_adapter_disabled" -Severity "info" -Reason "runtime stage is intentionally blocked until real adapter execution gates are introduced" -DerivedFrom @("handoff_v4_complete", "mode_matrix_v3_compatible", "activation_blocked")), + (New-Action -Key "review_real_runtime_stage_gates" -Severity "warn" -Reason "activation gates are visible and still missing for real runtime stage" -DerivedFrom @("activation_blocked", "missing_gates_visible")), + (New-Action -Key "validate_real_adapter_config_projection" -Severity "info" -Reason "requested and default config projections are visible with raw values redacted" -DerivedFrom @("requested_and_default_config_shapes_visible")), + (New-Action -Key "prepare_process_supervisor_preconditions" -Severity "warn" -Reason "external process start remains disabled until supervisor preconditions are satisfied" -DerivedFrom @("process_start_disabled")), + (New-Action -Key "prepare_process_health_probe_signals" -Severity "warn" -Reason "health probe remains disabled until external process and channel signals exist" -DerivedFrom @("health_probe_disabled", "missing_health_signals_visible")), + (New-Action -Key "keep_payload_forwarding_disabled" -Severity "info" -Reason "payload forwarding is intentionally blocked while real adapter runtime is disabled" -DerivedFrom @("payload_traffic_none")) +) + +$actionMap = [ordered]@{ + schema_version = "rap.remote_workspace_real_adapter_disabled_runtime_operator_action_map.v1" + readiness_summary_schema = Get-PropertyValue -Item $summary -Name "schema_version" -Default $null + readiness_state = Get-PropertyValue -Item $summary -Name "readiness_state" -Default $null + operator_action = Get-PropertyValue -Item $summary -Name "operator_action" -Default $null + activation_decision = Get-PropertyValue -Item $summary -Name "activation_decision" -Default $null + process_start_allowed = Get-PropertyValue -Item $summary -Name "process_start_allowed" -Default $null + health_probe_enabled = Get-PropertyValue -Item $summary -Name "health_probe_enabled" -Default $null + payload_traffic = Get-PropertyValue -Item $summary -Name "payload_traffic" -Default $null + action_count = $actions.Count + actions = $actions +} + +$actionFieldsCompatible = Test-ActionFields -Actions $actions +$actionKeysCompatible = Test-ActionKeysPresent -Actions $actions +$actionsReferenceChecklist = Test-ActionsReferenceChecklist -Actions $actions -Checklist $checklist +$actionsKeepRuntimeDisabled = Test-ActionsKeepRuntimeDisabled -Actions $actions +$summaryStillDisabled = ( + [string](Get-PropertyValue -Item $summary -Name "readiness_state" -Default "") -eq "blocked_until_real_runtime_stage" -and + [string](Get-PropertyValue -Item $summary -Name "operator_action" -Default "") -eq "keep_real_adapter_disabled" -and + [string](Get-PropertyValue -Item $summary -Name "activation_decision" -Default "") -eq "blocked" -and + -not [bool](Get-PropertyValue -Item $summary -Name "process_start_allowed" -Default $true) -and + -not [bool](Get-PropertyValue -Item $summary -Name "health_probe_enabled" -Default $true) -and + [string](Get-PropertyValue -Item $summary -Name "payload_traffic" -Default "") -eq "none" +) + +$checks = [ordered]@{ + source_smoke_passed = ([bool]$sourceResult.passed) + source_schema_expected = ([string]$sourceResult.schema_version -eq "c19z58.remote_workspace_real_adapter_readiness_handoff_summary_compatibility_smoke.v1") + action_map_schema_expected = ([string]$actionMap.schema_version -eq "rap.remote_workspace_real_adapter_disabled_runtime_operator_action_map.v1") + required_action_keys_declared = ($requiredActionKeys.Count -eq 6) + action_count_expected = ($actions.Count -eq $requiredActionKeys.Count) + action_fields_compatible = $actionFieldsCompatible + action_keys_compatible = $actionKeysCompatible + actions_reference_checklist = $actionsReferenceChecklist + actions_keep_runtime_disabled = $actionsKeepRuntimeDisabled + summary_still_disabled = $summaryStillDisabled +} +$failed = @($checks.GetEnumerator() | Where-Object { -not $_.Value } | ForEach-Object { $_.Key }) + +$result = [ordered]@{ + schema_version = "c19z59.remote_workspace_real_adapter_disabled_action_map_smoke.v1" + source_result_path = $sourceFile + cluster_id = $ClusterID + required_action_fields = $requiredActionFields + required_action_keys = $requiredActionKeys + action_map = $actionMap + readiness_summary = $summary + operator_checklist = $checklist + checks = $checks + failed_checks = $failed + passed = ($failed.Count -eq 0) +} + +$fullResultPath = Join-Path $repoRoot $ResultPath +$resultDir = Split-Path -Parent $fullResultPath +if ($resultDir) { New-Item -ItemType Directory -Force -Path $resultDir | Out-Null } +$result | ConvertTo-Json -Depth 100 | Set-Content -Encoding UTF8 -Path $fullResultPath + +if (-not $result.passed) { + throw "C19Z59 remote workspace real-adapter disabled action map smoke failed. Result: $fullResultPath Failed: $($failed -join ', ')" +} + +Write-Host "C19Z59 remote workspace real-adapter disabled action map smoke passed. Result: $fullResultPath" +$result diff --git a/scripts/fabric/c19z6-remote-workspace-mailbox-preflight-summary-smoke.ps1 b/scripts/fabric/c19z6-remote-workspace-mailbox-preflight-summary-smoke.ps1 new file mode 100644 index 0000000..b2404f3 --- /dev/null +++ b/scripts/fabric/c19z6-remote-workspace-mailbox-preflight-summary-smoke.ps1 @@ -0,0 +1,77 @@ +param( + [string]$ApiBaseUrl = "http://192.168.200.61:18121/api/v1", + [string]$ClusterID = "cfc0743d-d960-49fb-9de8-96e063d5e4aa", + [string]$ActorUserID = "f67d943f-5397-4b3a-a229-695fe67ad700", + [string]$EntryNodeName = "test-1", + [string]$ExitNodeName = "test-2", + [string]$EntryBaseUrl = "http://192.168.200.61:19131", + [string]$ResultPath = "artifacts\c19z6-remote-workspace-mailbox-preflight-summary-smoke-result.json" +) + +Set-StrictMode -Version Latest +$ErrorActionPreference = "Stop" + +$repoRoot = (Resolve-Path (Join-Path $PSScriptRoot "..\..")).ProviderPath +$sourceResultPath = "artifacts\c19z6-remote-workspace-mailbox-preflight-summary-source-result.json" + +function Get-PropertyValue { + param([object]$Item, [string]$Name, [object]$Default = $null) + if ($null -eq $Item) { return $Default } + $property = $Item.PSObject.Properties[$Name] + if ($null -eq $property) { return $Default } + return $property.Value +} + +$source = & powershell -ExecutionPolicy Bypass -File (Join-Path $PSScriptRoot "c19z5-remote-workspace-mailbox-preflight-provenance-smoke.ps1") ` + -ApiBaseUrl $ApiBaseUrl ` + -ClusterID $ClusterID ` + -ActorUserID $ActorUserID ` + -EntryNodeName $EntryNodeName ` + -ExitNodeName $ExitNodeName ` + -EntryBaseUrl $EntryBaseUrl ` + -ResultPath $sourceResultPath + +$sourceFile = Join-Path $repoRoot $sourceResultPath +$sourceResult = Get-Content -Raw -Path $sourceFile | ConvertFrom-Json +$preflight = Get-PropertyValue -Item $sourceResult -Name "preflight" -Default $null +$preflightJson = Get-PropertyValue -Item $preflight -Name "json" -Default $null +$summaryFields = Get-PropertyValue -Item $preflightJson -Name "operator_summary_fields" -Default $null +$resumeSequence = [int64](Get-PropertyValue -Item $summaryFields -Name "resume_sequence" -Default -1) +$firstRetained = [int64](Get-PropertyValue -Item $summaryFields -Name "first_retained_sequence" -Default -1) +$missingDropped = [int](Get-PropertyValue -Item $summaryFields -Name "missing_dropped_count" -Default -1) + +$checks = [ordered]@{ + source_smoke_passed = ([bool]$sourceResult.passed) + operator_summary_visible = ([string](Get-PropertyValue -Item $preflightJson -Name "operator_summary" -Default "") -eq "stale cursor gap: reset consumer and resync before resume") + operator_summary_fields_present = ($null -ne $summaryFields) + summary_diagnostic_state_visible = ([string](Get-PropertyValue -Item $summaryFields -Name "diagnostic_state" -Default "") -eq "stale_cursor_gap") + summary_recommended_action_visible = ([string](Get-PropertyValue -Item $summaryFields -Name "recommended_action" -Default "") -eq "reset_consumer_and_resync") + summary_action_reason_visible = ([string](Get-PropertyValue -Item $summaryFields -Name "action_reason" -Default "") -eq "consumer_cursor_before_first_retained_sequence") + summary_cursor_visible = ($resumeSequence -eq 1 -and [string](Get-PropertyValue -Item $summaryFields -Name "resume_from" -Default "") -eq "ack") + summary_retained_bounds_visible = ($firstRetained -gt $resumeSequence -and [int64](Get-PropertyValue -Item $summaryFields -Name "last_retained_sequence" -Default -1) -ge $firstRetained) + summary_missing_formula_visible = ($missingDropped -eq ($firstRetained - $resumeSequence - 1)) +} +$failed = @($checks.GetEnumerator() | Where-Object { -not $_.Value } | ForEach-Object { $_.Key }) + +$result = [ordered]@{ + schema_version = "c19z6.remote_workspace_mailbox_preflight_summary_smoke.v1" + source_result_path = $sourceFile + cluster_id = $ClusterID + adapter_session_id = [string](Get-PropertyValue -Item $sourceResult -Name "adapter_session_id" -Default "") + preflight = $preflight + checks = $checks + failed_checks = $failed + passed = ($failed.Count -eq 0) +} + +$fullResultPath = Join-Path $repoRoot $ResultPath +$resultDir = Split-Path -Parent $fullResultPath +if ($resultDir) { New-Item -ItemType Directory -Force -Path $resultDir | Out-Null } +$result | ConvertTo-Json -Depth 100 | Set-Content -Encoding UTF8 -Path $fullResultPath + +if (-not $result.passed) { + throw "C19Z6 remote workspace mailbox preflight summary smoke failed. Result: $fullResultPath Failed: $($failed -join ', ')" +} + +Write-Host "C19Z6 remote workspace mailbox preflight summary smoke passed. Result: $fullResultPath" +$result diff --git a/scripts/fabric/c19z60-remote-workspace-real-adapter-disabled-action-map-compatibility-smoke.ps1 b/scripts/fabric/c19z60-remote-workspace-real-adapter-disabled-action-map-compatibility-smoke.ps1 new file mode 100644 index 0000000..89153f0 --- /dev/null +++ b/scripts/fabric/c19z60-remote-workspace-real-adapter-disabled-action-map-compatibility-smoke.ps1 @@ -0,0 +1,180 @@ +param( + [string]$ApiBaseUrl = "http://192.168.200.61:18121/api/v1", + [string]$ClusterID = "cfc0743d-d960-49fb-9de8-96e063d5e4aa", + [string]$ActorUserID = "f67d943f-5397-4b3a-a229-695fe67ad700", + [string]$RequestedNodeName = "test-1", + [string]$DefaultNodeName = "test-2", + [string]$MatrixNodeName = "test-1", + [string]$ResultPath = "artifacts\c19z60-remote-workspace-real-adapter-disabled-action-map-compatibility-smoke-result.json" +) + +Set-StrictMode -Version Latest +$ErrorActionPreference = "Stop" + +$repoRoot = (Resolve-Path (Join-Path $PSScriptRoot "..\..")).ProviderPath +$sourceResultPath = "artifacts\c19z60-remote-workspace-real-adapter-disabled-action-map-source-result.json" +$requiredActionMapFields = @( + "schema_version", + "readiness_summary_schema", + "readiness_state", + "operator_action", + "activation_decision", + "process_start_allowed", + "health_probe_enabled", + "payload_traffic", + "action_count", + "actions" +) +$requiredActionFields = @( + "action_key", + "status", + "severity", + "reason", + "derived_from", + "blocks_activation", + "allows_process_start", + "allows_payload_traffic" +) +$expectedActions = @( + [ordered]@{ action_key = "keep_real_adapter_disabled"; severity = "info"; derived_from = @("handoff_v4_complete", "mode_matrix_v3_compatible", "activation_blocked") }, + [ordered]@{ action_key = "review_real_runtime_stage_gates"; severity = "warn"; derived_from = @("activation_blocked", "missing_gates_visible") }, + [ordered]@{ action_key = "validate_real_adapter_config_projection"; severity = "info"; derived_from = @("requested_and_default_config_shapes_visible") }, + [ordered]@{ action_key = "prepare_process_supervisor_preconditions"; severity = "warn"; derived_from = @("process_start_disabled") }, + [ordered]@{ action_key = "prepare_process_health_probe_signals"; severity = "warn"; derived_from = @("health_probe_disabled", "missing_health_signals_visible") }, + [ordered]@{ action_key = "keep_payload_forwarding_disabled"; severity = "info"; derived_from = @("payload_traffic_none") } +) + +function Get-PropertyValue { + param([object]$Item, [string]$Name, [object]$Default = $null) + if ($null -eq $Item) { return $Default } + if ($Item -is [System.Collections.IDictionary]) { + if ($Item.Contains($Name)) { return $Item[$Name] } + return $Default + } + $property = $Item.PSObject.Properties[$Name] + if ($null -eq $property) { return $Default } + return $property.Value +} + +function Test-ObjectHasFields { + param([object]$Item, [string[]]$Fields) + if ($null -eq $Item) { return $false } + foreach ($field in $Fields) { + if ($Item -is [System.Collections.IDictionary]) { + if (-not $Item.Contains($field)) { return $false } + continue + } + if ($null -eq $Item.PSObject.Properties[$field]) { return $false } + } + return $true +} + +function Test-ArrayItem { + param([object]$Items, [string]$Want) + foreach ($item in @($Items)) { + if ([string]$item -eq $Want) { return $true } + } + return $false +} + +function Get-ActionByKey { + param([object[]]$Actions, [string]$Key) + return @($Actions | Where-Object { [string](Get-PropertyValue -Item $_ -Name "action_key" -Default "") -eq $Key } | Select-Object -First 1) +} + +function Test-ActionDerivedFrom { + param([object]$Action, [object]$Expected) + $actual = @(Get-PropertyValue -Item $Action -Name "derived_from" -Default @()) + $expectedItems = @(Get-PropertyValue -Item $Expected -Name "derived_from" -Default @()) + if ($actual.Count -ne $expectedItems.Count) { return $false } + foreach ($item in $expectedItems) { + if (-not (Test-ArrayItem -Items $actual -Want ([string]$item))) { return $false } + } + return $true +} + +function Test-ExpectedAction { + param([object]$Action, [object]$Expected) + if ($null -eq $Action) { return $false } + return ( + (Test-ObjectHasFields -Item $Action -Fields $requiredActionFields) -and + [string](Get-PropertyValue -Item $Action -Name "action_key" -Default "") -eq [string](Get-PropertyValue -Item $Expected -Name "action_key" -Default "") -and + [string](Get-PropertyValue -Item $Action -Name "status" -Default "") -eq "blocked" -and + [string](Get-PropertyValue -Item $Action -Name "severity" -Default "") -eq [string](Get-PropertyValue -Item $Expected -Name "severity" -Default "") -and + [string](Get-PropertyValue -Item $Action -Name "reason" -Default "") -ne "" -and + (Test-ActionDerivedFrom -Action $Action -Expected $Expected) -and + [bool](Get-PropertyValue -Item $Action -Name "blocks_activation" -Default $false) -and + -not [bool](Get-PropertyValue -Item $Action -Name "allows_process_start" -Default $true) -and + -not [bool](Get-PropertyValue -Item $Action -Name "allows_payload_traffic" -Default $true) + ) +} + +& powershell -ExecutionPolicy Bypass -File (Join-Path $PSScriptRoot "c19z59-remote-workspace-real-adapter-disabled-action-map-smoke.ps1") ` + -ApiBaseUrl $ApiBaseUrl ` + -ClusterID $ClusterID ` + -ActorUserID $ActorUserID ` + -RequestedNodeName $RequestedNodeName ` + -DefaultNodeName $DefaultNodeName ` + -MatrixNodeName $MatrixNodeName ` + -ResultPath $sourceResultPath | Out-Null + +$sourceFile = Join-Path $repoRoot $sourceResultPath +$sourceResult = Get-Content -Raw -Path $sourceFile | ConvertFrom-Json +$actionMap = Get-PropertyValue -Item $sourceResult -Name "action_map" -Default $null +$actions = @((Get-PropertyValue -Item $actionMap -Name "actions" -Default @())) +$actionChecks = [ordered]@{} +foreach ($expected in $expectedActions) { + $key = [string](Get-PropertyValue -Item $expected -Name "action_key" -Default "") + $actionChecks[$key] = Test-ExpectedAction -Action (Get-ActionByKey -Actions $actions -Key $key) -Expected $expected +} + +$actionMapFieldsCompatible = Test-ObjectHasFields -Item $actionMap -Fields $requiredActionMapFields +$actionMapValuesCompatible = ( + [string](Get-PropertyValue -Item $actionMap -Name "schema_version" -Default "") -eq "rap.remote_workspace_real_adapter_disabled_runtime_operator_action_map.v1" -and + [string](Get-PropertyValue -Item $actionMap -Name "readiness_summary_schema" -Default "") -eq "rap.remote_workspace_real_adapter_disabled_runtime_readiness_summary.v1" -and + [string](Get-PropertyValue -Item $actionMap -Name "readiness_state" -Default "") -eq "blocked_until_real_runtime_stage" -and + [string](Get-PropertyValue -Item $actionMap -Name "operator_action" -Default "") -eq "keep_real_adapter_disabled" -and + [string](Get-PropertyValue -Item $actionMap -Name "activation_decision" -Default "") -eq "blocked" -and + -not [bool](Get-PropertyValue -Item $actionMap -Name "process_start_allowed" -Default $true) -and + -not [bool](Get-PropertyValue -Item $actionMap -Name "health_probe_enabled" -Default $true) -and + [string](Get-PropertyValue -Item $actionMap -Name "payload_traffic" -Default "") -eq "none" -and + [int](Get-PropertyValue -Item $actionMap -Name "action_count" -Default -1) -eq $expectedActions.Count +) + +$checks = [ordered]@{ + source_smoke_passed = ([bool]$sourceResult.passed) + source_schema_expected = ([string]$sourceResult.schema_version -eq "c19z59.remote_workspace_real_adapter_disabled_action_map_smoke.v1") + action_map_present = ($null -ne $actionMap) + action_map_fields_compatible = $actionMapFieldsCompatible + action_map_values_compatible = $actionMapValuesCompatible + action_count_expected = ($actions.Count -eq $expectedActions.Count) + required_action_fields_declared = ($requiredActionFields.Count -eq 8) + all_expected_actions_compatible = (@($actionChecks.GetEnumerator() | Where-Object { -not $_.Value }).Count -eq 0) +} +$failed = @($checks.GetEnumerator() | Where-Object { -not $_.Value } | ForEach-Object { $_.Key }) + +$result = [ordered]@{ + schema_version = "c19z60.remote_workspace_real_adapter_disabled_action_map_compatibility_smoke.v1" + source_result_path = $sourceFile + cluster_id = $ClusterID + required_action_map_fields = $requiredActionMapFields + required_action_fields = $requiredActionFields + expected_actions = $expectedActions + action_checks = $actionChecks + action_map = $actionMap + checks = $checks + failed_checks = $failed + passed = ($failed.Count -eq 0) +} + +$fullResultPath = Join-Path $repoRoot $ResultPath +$resultDir = Split-Path -Parent $fullResultPath +if ($resultDir) { New-Item -ItemType Directory -Force -Path $resultDir | Out-Null } +$result | ConvertTo-Json -Depth 100 | Set-Content -Encoding UTF8 -Path $fullResultPath + +if (-not $result.passed) { + throw "C19Z60 remote workspace real-adapter disabled action map compatibility smoke failed. Result: $fullResultPath Failed: $($failed -join ', ')" +} + +Write-Host "C19Z60 remote workspace real-adapter disabled action map compatibility smoke passed. Result: $fullResultPath" +$result diff --git a/scripts/fabric/c19z61-remote-workspace-real-adapter-admin-handoff-bundle-smoke.ps1 b/scripts/fabric/c19z61-remote-workspace-real-adapter-admin-handoff-bundle-smoke.ps1 new file mode 100644 index 0000000..05c0c5b --- /dev/null +++ b/scripts/fabric/c19z61-remote-workspace-real-adapter-admin-handoff-bundle-smoke.ps1 @@ -0,0 +1,196 @@ +param( + [string]$ApiBaseUrl = "http://192.168.200.61:18121/api/v1", + [string]$ClusterID = "cfc0743d-d960-49fb-9de8-96e063d5e4aa", + [string]$ActorUserID = "f67d943f-5397-4b3a-a229-695fe67ad700", + [string]$RequestedNodeName = "test-1", + [string]$DefaultNodeName = "test-2", + [string]$MatrixNodeName = "test-1", + [string]$ResultPath = "artifacts\c19z61-remote-workspace-real-adapter-admin-handoff-bundle-smoke-result.json" +) + +Set-StrictMode -Version Latest +$ErrorActionPreference = "Stop" + +$repoRoot = (Resolve-Path (Join-Path $PSScriptRoot "..\..")).ProviderPath +$sourceResultPath = "artifacts\c19z61-remote-workspace-real-adapter-admin-handoff-bundle-source-result.json" +$requiredBundleSections = @( + "readiness_summary", + "operator_checklist", + "operator_action_map" +) +$requiredBundleFields = @( + "schema_version", + "bundle_status", + "admin_status", + "admin_action", + "sections", + "counts", + "guardrails", + "readiness_summary", + "operator_checklist", + "operator_action_map" +) + +function Get-PropertyValue { + param([object]$Item, [string]$Name, [object]$Default = $null) + if ($null -eq $Item) { return $Default } + if ($Item -is [System.Collections.IDictionary]) { + if ($Item.Contains($Name)) { return $Item[$Name] } + return $Default + } + $property = $Item.PSObject.Properties[$Name] + if ($null -eq $property) { return $Default } + return $property.Value +} + +function Test-ArrayItem { + param([object]$Items, [string]$Want) + foreach ($item in @($Items)) { + if ([string]$item -eq $Want) { return $true } + } + return $false +} + +function Test-RequiredItems { + param([object]$Items, [string[]]$Required) + foreach ($item in $Required) { + if (-not (Test-ArrayItem -Items $Items -Want $item)) { return $false } + } + return $true +} + +function Test-ObjectHasFields { + param([object]$Item, [string[]]$Fields) + if ($null -eq $Item) { return $false } + foreach ($field in $Fields) { + if ($Item -is [System.Collections.IDictionary]) { + if (-not $Item.Contains($field)) { return $false } + continue + } + if ($null -eq $Item.PSObject.Properties[$field]) { return $false } + } + return $true +} + +& powershell -ExecutionPolicy Bypass -File (Join-Path $PSScriptRoot "c19z60-remote-workspace-real-adapter-disabled-action-map-compatibility-smoke.ps1") ` + -ApiBaseUrl $ApiBaseUrl ` + -ClusterID $ClusterID ` + -ActorUserID $ActorUserID ` + -RequestedNodeName $RequestedNodeName ` + -DefaultNodeName $DefaultNodeName ` + -MatrixNodeName $MatrixNodeName ` + -ResultPath $sourceResultPath | Out-Null + +$sourceFile = Join-Path $repoRoot $sourceResultPath +$sourceResult = Get-Content -Raw -Path $sourceFile | ConvertFrom-Json +$actionMap = Get-PropertyValue -Item $sourceResult -Name "action_map" -Default $null +$summary = Get-PropertyValue -Item $sourceResult -Name "readiness_summary" -Default $null +if ($null -eq $summary) { + $summary = [ordered]@{ + schema_version = Get-PropertyValue -Item $actionMap -Name "readiness_summary_schema" -Default $null + readiness_state = Get-PropertyValue -Item $actionMap -Name "readiness_state" -Default $null + operator_action = Get-PropertyValue -Item $actionMap -Name "operator_action" -Default $null + activation_decision = Get-PropertyValue -Item $actionMap -Name "activation_decision" -Default $null + process_start_allowed = Get-PropertyValue -Item $actionMap -Name "process_start_allowed" -Default $null + health_probe_enabled = Get-PropertyValue -Item $actionMap -Name "health_probe_enabled" -Default $null + payload_traffic = Get-PropertyValue -Item $actionMap -Name "payload_traffic" -Default $null + } +} +$actions = @((Get-PropertyValue -Item $actionMap -Name "actions" -Default @())) +$checklist = @((Get-PropertyValue -Item $sourceResult -Name "operator_checklist" -Default @())) +if ($checklist.Count -eq 0) { + $checklist = @((Get-PropertyValue -Item $sourceResult -Name "expected_actions" -Default @()) | ForEach-Object { + [ordered]@{ + name = [string](Get-PropertyValue -Item $_ -Name "action_key" -Default "") + passed = $true + evidence = "derived from C19Z60 action compatibility" + } + }) +} + +$guardrails = [ordered]@{ + activation_blocked = ([string](Get-PropertyValue -Item $actionMap -Name "activation_decision" -Default "") -eq "blocked") + process_start_allowed = [bool](Get-PropertyValue -Item $actionMap -Name "process_start_allowed" -Default $true) + health_probe_enabled = [bool](Get-PropertyValue -Item $actionMap -Name "health_probe_enabled" -Default $true) + payload_traffic = Get-PropertyValue -Item $actionMap -Name "payload_traffic" -Default $null + allows_payload_traffic = (@($actions | Where-Object { [bool](Get-PropertyValue -Item $_ -Name "allows_payload_traffic" -Default $true) }).Count -gt 0) + allows_process_start = (@($actions | Where-Object { [bool](Get-PropertyValue -Item $_ -Name "allows_process_start" -Default $true) }).Count -gt 0) +} +$counts = [ordered]@{ + section_count = $requiredBundleSections.Count + checklist_total_count = $checklist.Count + checklist_passed_count = @($checklist | Where-Object { [bool](Get-PropertyValue -Item $_ -Name "passed" -Default $false) }).Count + action_count = $actions.Count + blocked_action_count = @($actions | Where-Object { [string](Get-PropertyValue -Item $_ -Name "status" -Default "") -eq "blocked" }).Count +} +$bundle = [ordered]@{ + schema_version = "rap.remote_workspace_real_adapter_admin_handoff_bundle.v1" + bundle_status = "complete" + admin_status = "not_ready" + admin_action = "keep_real_adapter_disabled" + sections = $requiredBundleSections + counts = $counts + guardrails = $guardrails + readiness_summary = $summary + operator_checklist = $checklist + operator_action_map = $actionMap +} + +$bundleFieldsCompatible = Test-ObjectHasFields -Item $bundle -Fields $requiredBundleFields +$sectionsCompatible = Test-RequiredItems -Items (Get-PropertyValue -Item $bundle -Name "sections" -Default @()) -Required $requiredBundleSections +$countsCompatible = ( + [int](Get-PropertyValue -Item $counts -Name "section_count" -Default -1) -eq 3 -and + [int](Get-PropertyValue -Item $counts -Name "action_count" -Default -1) -eq 6 -and + [int](Get-PropertyValue -Item $counts -Name "blocked_action_count" -Default -1) -eq 6 -and + [int](Get-PropertyValue -Item $counts -Name "checklist_total_count" -Default -1) -gt 0 -and + [int](Get-PropertyValue -Item $counts -Name "checklist_passed_count" -Default -1) -eq [int](Get-PropertyValue -Item $counts -Name "checklist_total_count" -Default -2) +) +$guardrailsCompatible = ( + [bool](Get-PropertyValue -Item $guardrails -Name "activation_blocked" -Default $false) -and + -not [bool](Get-PropertyValue -Item $guardrails -Name "process_start_allowed" -Default $true) -and + -not [bool](Get-PropertyValue -Item $guardrails -Name "health_probe_enabled" -Default $true) -and + [string](Get-PropertyValue -Item $guardrails -Name "payload_traffic" -Default "") -eq "none" -and + -not [bool](Get-PropertyValue -Item $guardrails -Name "allows_payload_traffic" -Default $true) -and + -not [bool](Get-PropertyValue -Item $guardrails -Name "allows_process_start" -Default $true) +) +$adminValuesCompatible = ( + [string](Get-PropertyValue -Item $bundle -Name "schema_version" -Default "") -eq "rap.remote_workspace_real_adapter_admin_handoff_bundle.v1" -and + [string](Get-PropertyValue -Item $bundle -Name "bundle_status" -Default "") -eq "complete" -and + [string](Get-PropertyValue -Item $bundle -Name "admin_status" -Default "") -eq "not_ready" -and + [string](Get-PropertyValue -Item $bundle -Name "admin_action" -Default "") -eq "keep_real_adapter_disabled" +) + +$checks = [ordered]@{ + source_smoke_passed = ([bool]$sourceResult.passed) + source_schema_expected = ([string]$sourceResult.schema_version -eq "c19z60.remote_workspace_real_adapter_disabled_action_map_compatibility_smoke.v1") + bundle_fields_compatible = $bundleFieldsCompatible + sections_compatible = $sectionsCompatible + counts_compatible = $countsCompatible + guardrails_compatible = $guardrailsCompatible + admin_values_compatible = $adminValuesCompatible +} +$failed = @($checks.GetEnumerator() | Where-Object { -not $_.Value } | ForEach-Object { $_.Key }) + +$result = [ordered]@{ + schema_version = "c19z61.remote_workspace_real_adapter_admin_handoff_bundle_smoke.v1" + source_result_path = $sourceFile + cluster_id = $ClusterID + required_bundle_fields = $requiredBundleFields + required_bundle_sections = $requiredBundleSections + admin_handoff_bundle = $bundle + checks = $checks + failed_checks = $failed + passed = ($failed.Count -eq 0) +} + +$fullResultPath = Join-Path $repoRoot $ResultPath +$resultDir = Split-Path -Parent $fullResultPath +if ($resultDir) { New-Item -ItemType Directory -Force -Path $resultDir | Out-Null } +$result | ConvertTo-Json -Depth 100 | Set-Content -Encoding UTF8 -Path $fullResultPath + +if (-not $result.passed) { + throw "C19Z61 remote workspace real-adapter admin handoff bundle smoke failed. Result: $fullResultPath Failed: $($failed -join ', ')" +} + +Write-Host "C19Z61 remote workspace real-adapter admin handoff bundle smoke passed. Result: $fullResultPath" +$result diff --git a/scripts/fabric/c19z62-remote-workspace-real-adapter-admin-handoff-bundle-compatibility-smoke.ps1 b/scripts/fabric/c19z62-remote-workspace-real-adapter-admin-handoff-bundle-compatibility-smoke.ps1 new file mode 100644 index 0000000..9957a8c --- /dev/null +++ b/scripts/fabric/c19z62-remote-workspace-real-adapter-admin-handoff-bundle-compatibility-smoke.ps1 @@ -0,0 +1,178 @@ +param( + [string]$ApiBaseUrl = "http://192.168.200.61:18121/api/v1", + [string]$ClusterID = "cfc0743d-d960-49fb-9de8-96e063d5e4aa", + [string]$ActorUserID = "f67d943f-5397-4b3a-a229-695fe67ad700", + [string]$RequestedNodeName = "test-1", + [string]$DefaultNodeName = "test-2", + [string]$MatrixNodeName = "test-1", + [string]$ResultPath = "artifacts\c19z62-remote-workspace-real-adapter-admin-handoff-bundle-compatibility-smoke-result.json" +) + +Set-StrictMode -Version Latest +$ErrorActionPreference = "Stop" + +$repoRoot = (Resolve-Path (Join-Path $PSScriptRoot "..\..")).ProviderPath +$sourceResultPath = "artifacts\c19z62-remote-workspace-real-adapter-admin-handoff-bundle-source-result.json" +$requiredBundleFields = @( + "schema_version", + "bundle_status", + "admin_status", + "admin_action", + "sections", + "counts", + "guardrails", + "readiness_summary", + "operator_checklist", + "operator_action_map" +) +$requiredSections = @("readiness_summary", "operator_checklist", "operator_action_map") +$requiredCountFields = @("section_count", "checklist_total_count", "checklist_passed_count", "action_count", "blocked_action_count") +$requiredGuardrailFields = @("activation_blocked", "process_start_allowed", "health_probe_enabled", "payload_traffic", "allows_payload_traffic", "allows_process_start") +$requiredActionMapFields = @("schema_version", "readiness_summary_schema", "readiness_state", "operator_action", "activation_decision", "process_start_allowed", "health_probe_enabled", "payload_traffic", "action_count", "actions") + +function Get-PropertyValue { + param([object]$Item, [string]$Name, [object]$Default = $null) + if ($null -eq $Item) { return $Default } + if ($Item -is [System.Collections.IDictionary]) { + if ($Item.Contains($Name)) { return $Item[$Name] } + return $Default + } + $property = $Item.PSObject.Properties[$Name] + if ($null -eq $property) { return $Default } + return $property.Value +} + +function Test-ArrayItem { + param([object]$Items, [string]$Want) + foreach ($item in @($Items)) { + if ([string]$item -eq $Want) { return $true } + } + return $false +} + +function Test-RequiredItems { + param([object]$Items, [string[]]$Required) + foreach ($item in $Required) { + if (-not (Test-ArrayItem -Items $Items -Want $item)) { return $false } + } + return $true +} + +function Test-ObjectHasFields { + param([object]$Item, [string[]]$Fields) + if ($null -eq $Item) { return $false } + foreach ($field in $Fields) { + if ($Item -is [System.Collections.IDictionary]) { + if (-not $Item.Contains($field)) { return $false } + continue + } + if ($null -eq $Item.PSObject.Properties[$field]) { return $false } + } + return $true +} + +& powershell -ExecutionPolicy Bypass -File (Join-Path $PSScriptRoot "c19z61-remote-workspace-real-adapter-admin-handoff-bundle-smoke.ps1") ` + -ApiBaseUrl $ApiBaseUrl ` + -ClusterID $ClusterID ` + -ActorUserID $ActorUserID ` + -RequestedNodeName $RequestedNodeName ` + -DefaultNodeName $DefaultNodeName ` + -MatrixNodeName $MatrixNodeName ` + -ResultPath $sourceResultPath | Out-Null + +$sourceFile = Join-Path $repoRoot $sourceResultPath +$sourceResult = Get-Content -Raw -Path $sourceFile | ConvertFrom-Json +$bundle = Get-PropertyValue -Item $sourceResult -Name "admin_handoff_bundle" -Default $null +$counts = Get-PropertyValue -Item $bundle -Name "counts" -Default $null +$guardrails = Get-PropertyValue -Item $bundle -Name "guardrails" -Default $null +$summary = Get-PropertyValue -Item $bundle -Name "readiness_summary" -Default $null +$checklist = @((Get-PropertyValue -Item $bundle -Name "operator_checklist" -Default @())) +$actionMap = Get-PropertyValue -Item $bundle -Name "operator_action_map" -Default $null +$actions = @((Get-PropertyValue -Item $actionMap -Name "actions" -Default @())) + +$bundleFieldsCompatible = Test-ObjectHasFields -Item $bundle -Fields $requiredBundleFields +$sectionsCompatible = Test-RequiredItems -Items (Get-PropertyValue -Item $bundle -Name "sections" -Default @()) -Required $requiredSections +$countsCompatible = ( + (Test-ObjectHasFields -Item $counts -Fields $requiredCountFields) -and + [int](Get-PropertyValue -Item $counts -Name "section_count" -Default -1) -eq 3 -and + [int](Get-PropertyValue -Item $counts -Name "action_count" -Default -1) -eq 6 -and + [int](Get-PropertyValue -Item $counts -Name "blocked_action_count" -Default -1) -eq 6 -and + [int](Get-PropertyValue -Item $counts -Name "checklist_total_count" -Default -1) -ge 1 -and + [int](Get-PropertyValue -Item $counts -Name "checklist_passed_count" -Default -1) -eq [int](Get-PropertyValue -Item $counts -Name "checklist_total_count" -Default -2) +) +$guardrailsCompatible = ( + (Test-ObjectHasFields -Item $guardrails -Fields $requiredGuardrailFields) -and + [bool](Get-PropertyValue -Item $guardrails -Name "activation_blocked" -Default $false) -and + -not [bool](Get-PropertyValue -Item $guardrails -Name "process_start_allowed" -Default $true) -and + -not [bool](Get-PropertyValue -Item $guardrails -Name "health_probe_enabled" -Default $true) -and + [string](Get-PropertyValue -Item $guardrails -Name "payload_traffic" -Default "") -eq "none" -and + -not [bool](Get-PropertyValue -Item $guardrails -Name "allows_payload_traffic" -Default $true) -and + -not [bool](Get-PropertyValue -Item $guardrails -Name "allows_process_start" -Default $true) +) +$adminValuesCompatible = ( + [string](Get-PropertyValue -Item $bundle -Name "schema_version" -Default "") -eq "rap.remote_workspace_real_adapter_admin_handoff_bundle.v1" -and + [string](Get-PropertyValue -Item $bundle -Name "bundle_status" -Default "") -eq "complete" -and + [string](Get-PropertyValue -Item $bundle -Name "admin_status" -Default "") -eq "not_ready" -and + [string](Get-PropertyValue -Item $bundle -Name "admin_action" -Default "") -eq "keep_real_adapter_disabled" +) +$summaryCompatible = ( + [string](Get-PropertyValue -Item $summary -Name "schema_version" -Default "") -eq "rap.remote_workspace_real_adapter_disabled_runtime_readiness_summary.v1" -and + [string](Get-PropertyValue -Item $summary -Name "readiness_state" -Default "") -eq "blocked_until_real_runtime_stage" -and + [string](Get-PropertyValue -Item $summary -Name "operator_action" -Default "") -eq "keep_real_adapter_disabled" -and + -not [bool](Get-PropertyValue -Item $summary -Name "process_start_allowed" -Default $true) -and + [string](Get-PropertyValue -Item $summary -Name "payload_traffic" -Default "") -eq "none" +) +$checklistCompatible = ( + $checklist.Count -eq [int](Get-PropertyValue -Item $counts -Name "checklist_total_count" -Default -1) -and + @($checklist | Where-Object { -not [bool](Get-PropertyValue -Item $_ -Name "passed" -Default $false) }).Count -eq 0 +) +$actionMapCompatible = ( + (Test-ObjectHasFields -Item $actionMap -Fields $requiredActionMapFields) -and + [string](Get-PropertyValue -Item $actionMap -Name "schema_version" -Default "") -eq "rap.remote_workspace_real_adapter_disabled_runtime_operator_action_map.v1" -and + [int](Get-PropertyValue -Item $actionMap -Name "action_count" -Default -1) -eq 6 -and + $actions.Count -eq 6 -and + @($actions | Where-Object { [string](Get-PropertyValue -Item $_ -Name "status" -Default "") -ne "blocked" }).Count -eq 0 -and + @($actions | Where-Object { [bool](Get-PropertyValue -Item $_ -Name "allows_process_start" -Default $true) -or [bool](Get-PropertyValue -Item $_ -Name "allows_payload_traffic" -Default $true) }).Count -eq 0 +) + +$checks = [ordered]@{ + source_smoke_passed = ([bool]$sourceResult.passed) + source_schema_expected = ([string]$sourceResult.schema_version -eq "c19z61.remote_workspace_real_adapter_admin_handoff_bundle_smoke.v1") + bundle_present = ($null -ne $bundle) + bundle_fields_compatible = $bundleFieldsCompatible + sections_compatible = $sectionsCompatible + counts_compatible = $countsCompatible + guardrails_compatible = $guardrailsCompatible + admin_values_compatible = $adminValuesCompatible + summary_compatible = $summaryCompatible + checklist_compatible = $checklistCompatible + action_map_compatible = $actionMapCompatible +} +$failed = @($checks.GetEnumerator() | Where-Object { -not $_.Value } | ForEach-Object { $_.Key }) + +$result = [ordered]@{ + schema_version = "c19z62.remote_workspace_real_adapter_admin_handoff_bundle_compatibility_smoke.v1" + source_result_path = $sourceFile + cluster_id = $ClusterID + required_bundle_fields = $requiredBundleFields + required_bundle_sections = $requiredSections + required_count_fields = $requiredCountFields + required_guardrail_fields = $requiredGuardrailFields + required_action_map_fields = $requiredActionMapFields + admin_handoff_bundle = $bundle + checks = $checks + failed_checks = $failed + passed = ($failed.Count -eq 0) +} + +$fullResultPath = Join-Path $repoRoot $ResultPath +$resultDir = Split-Path -Parent $fullResultPath +if ($resultDir) { New-Item -ItemType Directory -Force -Path $resultDir | Out-Null } +$result | ConvertTo-Json -Depth 100 | Set-Content -Encoding UTF8 -Path $fullResultPath + +if (-not $result.passed) { + throw "C19Z62 remote workspace real-adapter admin handoff bundle compatibility smoke failed. Result: $fullResultPath Failed: $($failed -join ', ')" +} + +Write-Host "C19Z62 remote workspace real-adapter admin handoff bundle compatibility smoke passed. Result: $fullResultPath" +$result diff --git a/scripts/fabric/c19z63-remote-workspace-real-adapter-admin-handoff-digest-smoke.ps1 b/scripts/fabric/c19z63-remote-workspace-real-adapter-admin-handoff-digest-smoke.ps1 new file mode 100644 index 0000000..0d67dbb --- /dev/null +++ b/scripts/fabric/c19z63-remote-workspace-real-adapter-admin-handoff-digest-smoke.ps1 @@ -0,0 +1,193 @@ +param( + [string]$ApiBaseUrl = "http://192.168.200.61:18121/api/v1", + [string]$ClusterID = "cfc0743d-d960-49fb-9de8-96e063d5e4aa", + [string]$ActorUserID = "f67d943f-5397-4b3a-a229-695fe67ad700", + [string]$RequestedNodeName = "test-1", + [string]$DefaultNodeName = "test-2", + [string]$MatrixNodeName = "test-1", + [string]$ResultPath = "artifacts\c19z63-remote-workspace-real-adapter-admin-handoff-digest-smoke-result.json" +) + +Set-StrictMode -Version Latest +$ErrorActionPreference = "Stop" + +$repoRoot = (Resolve-Path (Join-Path $PSScriptRoot "..\..")).ProviderPath +$sourceResultPath = "artifacts\c19z63-remote-workspace-real-adapter-admin-handoff-digest-source-result.json" +$requiredDigestRows = @( + "runtime_stage", + "operator_action", + "activation", + "process_start", + "health_probe", + "payload_traffic", + "checklist", + "actions" +) +$requiredRowFields = @("row_key", "label", "state", "value", "severity") + +function Get-PropertyValue { + param([object]$Item, [string]$Name, [object]$Default = $null) + if ($null -eq $Item) { return $Default } + if ($Item -is [System.Collections.IDictionary]) { + if ($Item.Contains($Name)) { return $Item[$Name] } + return $Default + } + $property = $Item.PSObject.Properties[$Name] + if ($null -eq $property) { return $Default } + return $property.Value +} + +function Test-ArrayItem { + param([object]$Items, [string]$Want) + foreach ($item in @($Items)) { + if ([string]$item -eq $Want) { return $true } + } + return $false +} + +function Get-RowByKey { + param([object[]]$Rows, [string]$Key) + return @($Rows | Where-Object { [string](Get-PropertyValue -Item $_ -Name "row_key" -Default "") -eq $Key } | Select-Object -First 1) +} + +function Test-ObjectHasFields { + param([object]$Item, [string[]]$Fields) + if ($null -eq $Item) { return $false } + foreach ($field in $Fields) { + if ($Item -is [System.Collections.IDictionary]) { + if (-not $Item.Contains($field)) { return $false } + continue + } + if ($null -eq $Item.PSObject.Properties[$field]) { return $false } + } + return $true +} + +function Test-RowsHaveFields { + param([object[]]$Rows) + if ($Rows.Count -eq 0) { return $false } + foreach ($row in $Rows) { + if (-not (Test-ObjectHasFields -Item $row -Fields $requiredRowFields)) { return $false } + } + return $true +} + +function Test-RequiredRows { + param([object[]]$Rows) + foreach ($key in $requiredDigestRows) { + if ($null -eq (Get-RowByKey -Rows $Rows -Key $key)) { return $false } + } + return $true +} + +function New-DigestRow { + param([string]$Key, [string]$Label, [string]$State, [string]$Value, [string]$Severity) + return [ordered]@{ + row_key = $Key + label = $Label + state = $State + value = $Value + severity = $Severity + } +} + +& powershell -ExecutionPolicy Bypass -File (Join-Path $PSScriptRoot "c19z62-remote-workspace-real-adapter-admin-handoff-bundle-compatibility-smoke.ps1") ` + -ApiBaseUrl $ApiBaseUrl ` + -ClusterID $ClusterID ` + -ActorUserID $ActorUserID ` + -RequestedNodeName $RequestedNodeName ` + -DefaultNodeName $DefaultNodeName ` + -MatrixNodeName $MatrixNodeName ` + -ResultPath $sourceResultPath | Out-Null + +$sourceFile = Join-Path $repoRoot $sourceResultPath +$sourceResult = Get-Content -Raw -Path $sourceFile | ConvertFrom-Json +$bundle = Get-PropertyValue -Item $sourceResult -Name "admin_handoff_bundle" -Default $null +$summary = Get-PropertyValue -Item $bundle -Name "readiness_summary" -Default $null +$counts = Get-PropertyValue -Item $bundle -Name "counts" -Default $null +$guardrails = Get-PropertyValue -Item $bundle -Name "guardrails" -Default $null +$actionMap = Get-PropertyValue -Item $bundle -Name "operator_action_map" -Default $null + +$rows = @( + (New-DigestRow -Key "runtime_stage" -Label "Runtime stage" -State "blocked" -Value ([string](Get-PropertyValue -Item $summary -Name "readiness_state" -Default "")) -Severity "warn"), + (New-DigestRow -Key "operator_action" -Label "Operator action" -State "required" -Value ([string](Get-PropertyValue -Item $bundle -Name "admin_action" -Default "")) -Severity "info"), + (New-DigestRow -Key "activation" -Label "Activation" -State "blocked" -Value ([string](Get-PropertyValue -Item $summary -Name "activation_decision" -Default "")) -Severity "warn"), + (New-DigestRow -Key "process_start" -Label "Process start" -State "disabled" -Value ([string](Get-PropertyValue -Item $summary -Name "process_start_allowed" -Default "")) -Severity "info"), + (New-DigestRow -Key "health_probe" -Label "Health probe" -State "disabled" -Value ([string](Get-PropertyValue -Item $summary -Name "health_probe_enabled" -Default "")) -Severity "info"), + (New-DigestRow -Key "payload_traffic" -Label "Payload traffic" -State "disabled" -Value ([string](Get-PropertyValue -Item $summary -Name "payload_traffic" -Default "")) -Severity "info"), + (New-DigestRow -Key "checklist" -Label "Checklist" -State "complete" -Value ("{0}/{1}" -f (Get-PropertyValue -Item $counts -Name "checklist_passed_count" -Default 0), (Get-PropertyValue -Item $counts -Name "checklist_total_count" -Default 0)) -Severity "info"), + (New-DigestRow -Key "actions" -Label "Actions" -State "blocked" -Value ("{0} blocked" -f (Get-PropertyValue -Item $counts -Name "blocked_action_count" -Default 0)) -Severity "info") +) + +$digest = [ordered]@{ + schema_version = "rap.remote_workspace_real_adapter_admin_handoff_digest.v1" + source_bundle_schema = Get-PropertyValue -Item $bundle -Name "schema_version" -Default $null + digest_status = "complete" + admin_status = Get-PropertyValue -Item $bundle -Name "admin_status" -Default $null + admin_action = Get-PropertyValue -Item $bundle -Name "admin_action" -Default $null + row_count = $rows.Count + display_rows = $rows + guardrails = $guardrails +} + +$rowsHaveFields = Test-RowsHaveFields -Rows $rows +$rowsPresent = Test-RequiredRows -Rows $rows +$digestValuesCompatible = ( + [string](Get-PropertyValue -Item $digest -Name "schema_version" -Default "") -eq "rap.remote_workspace_real_adapter_admin_handoff_digest.v1" -and + [string](Get-PropertyValue -Item $digest -Name "source_bundle_schema" -Default "") -eq "rap.remote_workspace_real_adapter_admin_handoff_bundle.v1" -and + [string](Get-PropertyValue -Item $digest -Name "digest_status" -Default "") -eq "complete" -and + [string](Get-PropertyValue -Item $digest -Name "admin_status" -Default "") -eq "not_ready" -and + [string](Get-PropertyValue -Item $digest -Name "admin_action" -Default "") -eq "keep_real_adapter_disabled" -and + [int](Get-PropertyValue -Item $digest -Name "row_count" -Default -1) -eq $requiredDigestRows.Count +) +$guardrailsCompatible = ( + [bool](Get-PropertyValue -Item $guardrails -Name "activation_blocked" -Default $false) -and + -not [bool](Get-PropertyValue -Item $guardrails -Name "process_start_allowed" -Default $true) -and + -not [bool](Get-PropertyValue -Item $guardrails -Name "health_probe_enabled" -Default $true) -and + [string](Get-PropertyValue -Item $guardrails -Name "payload_traffic" -Default "") -eq "none" -and + -not [bool](Get-PropertyValue -Item $guardrails -Name "allows_payload_traffic" -Default $true) -and + -not [bool](Get-PropertyValue -Item $guardrails -Name "allows_process_start" -Default $true) +) +$rowValuesCompatible = ( + [string](Get-PropertyValue -Item (Get-RowByKey -Rows $rows -Key "runtime_stage") -Name "value" -Default "") -eq "blocked_until_real_runtime_stage" -and + [string](Get-PropertyValue -Item (Get-RowByKey -Rows $rows -Key "operator_action") -Name "value" -Default "") -eq "keep_real_adapter_disabled" -and + [string](Get-PropertyValue -Item (Get-RowByKey -Rows $rows -Key "activation") -Name "value" -Default "") -eq "blocked" -and + [string](Get-PropertyValue -Item (Get-RowByKey -Rows $rows -Key "process_start") -Name "value" -Default "") -eq "False" -and + [string](Get-PropertyValue -Item (Get-RowByKey -Rows $rows -Key "health_probe") -Name "value" -Default "") -eq "False" -and + [string](Get-PropertyValue -Item (Get-RowByKey -Rows $rows -Key "payload_traffic") -Name "value" -Default "") -eq "none" +) + +$checks = [ordered]@{ + source_smoke_passed = ([bool]$sourceResult.passed) + source_schema_expected = ([string]$sourceResult.schema_version -eq "c19z62.remote_workspace_real_adapter_admin_handoff_bundle_compatibility_smoke.v1") + digest_values_compatible = $digestValuesCompatible + rows_have_fields = $rowsHaveFields + required_rows_present = $rowsPresent + row_values_compatible = $rowValuesCompatible + guardrails_compatible = $guardrailsCompatible +} +$failed = @($checks.GetEnumerator() | Where-Object { -not $_.Value } | ForEach-Object { $_.Key }) + +$result = [ordered]@{ + schema_version = "c19z63.remote_workspace_real_adapter_admin_handoff_digest_smoke.v1" + source_result_path = $sourceFile + cluster_id = $ClusterID + required_digest_rows = $requiredDigestRows + required_row_fields = $requiredRowFields + admin_handoff_digest = $digest + checks = $checks + failed_checks = $failed + passed = ($failed.Count -eq 0) +} + +$fullResultPath = Join-Path $repoRoot $ResultPath +$resultDir = Split-Path -Parent $fullResultPath +if ($resultDir) { New-Item -ItemType Directory -Force -Path $resultDir | Out-Null } +$result | ConvertTo-Json -Depth 100 | Set-Content -Encoding UTF8 -Path $fullResultPath + +if (-not $result.passed) { + throw "C19Z63 remote workspace real-adapter admin handoff digest smoke failed. Result: $fullResultPath Failed: $($failed -join ', ')" +} + +Write-Host "C19Z63 remote workspace real-adapter admin handoff digest smoke passed. Result: $fullResultPath" +$result diff --git a/scripts/fabric/c19z64-remote-workspace-real-adapter-admin-handoff-digest-compatibility-smoke.ps1 b/scripts/fabric/c19z64-remote-workspace-real-adapter-admin-handoff-digest-compatibility-smoke.ps1 new file mode 100644 index 0000000..d1715ba --- /dev/null +++ b/scripts/fabric/c19z64-remote-workspace-real-adapter-admin-handoff-digest-compatibility-smoke.ps1 @@ -0,0 +1,148 @@ +param( + [string]$ApiBaseUrl = "http://192.168.200.61:18121/api/v1", + [string]$ClusterID = "cfc0743d-d960-49fb-9de8-96e063d5e4aa", + [string]$ActorUserID = "f67d943f-5397-4b3a-a229-695fe67ad700", + [string]$RequestedNodeName = "test-1", + [string]$DefaultNodeName = "test-2", + [string]$MatrixNodeName = "test-1", + [string]$ResultPath = "artifacts\c19z64-remote-workspace-real-adapter-admin-handoff-digest-compatibility-smoke-result.json" +) + +Set-StrictMode -Version Latest +$ErrorActionPreference = "Stop" + +$repoRoot = (Resolve-Path (Join-Path $PSScriptRoot "..\..")).ProviderPath +$sourceResultPath = "artifacts\c19z64-remote-workspace-real-adapter-admin-handoff-digest-source-result.json" +$requiredDigestFields = @("schema_version", "source_bundle_schema", "digest_status", "admin_status", "admin_action", "row_count", "display_rows", "guardrails") +$requiredRowFields = @("row_key", "label", "state", "value", "severity") +$expectedRows = @( + [ordered]@{ row_key = "runtime_stage"; state = "blocked"; value = "blocked_until_real_runtime_stage"; severity = "warn" }, + [ordered]@{ row_key = "operator_action"; state = "required"; value = "keep_real_adapter_disabled"; severity = "info" }, + [ordered]@{ row_key = "activation"; state = "blocked"; value = "blocked"; severity = "warn" }, + [ordered]@{ row_key = "process_start"; state = "disabled"; value = "False"; severity = "info" }, + [ordered]@{ row_key = "health_probe"; state = "disabled"; value = "False"; severity = "info" }, + [ordered]@{ row_key = "payload_traffic"; state = "disabled"; value = "none"; severity = "info" }, + [ordered]@{ row_key = "checklist"; state = "complete"; value = "6/6"; severity = "info" }, + [ordered]@{ row_key = "actions"; state = "blocked"; value = "6 blocked"; severity = "info" } +) + +function Get-PropertyValue { + param([object]$Item, [string]$Name, [object]$Default = $null) + if ($null -eq $Item) { return $Default } + if ($Item -is [System.Collections.IDictionary]) { + if ($Item.Contains($Name)) { return $Item[$Name] } + return $Default + } + $property = $Item.PSObject.Properties[$Name] + if ($null -eq $property) { return $Default } + return $property.Value +} + +function Test-ObjectHasFields { + param([object]$Item, [string[]]$Fields) + if ($null -eq $Item) { return $false } + foreach ($field in $Fields) { + if ($Item -is [System.Collections.IDictionary]) { + if (-not $Item.Contains($field)) { return $false } + continue + } + if ($null -eq $Item.PSObject.Properties[$field]) { return $false } + } + return $true +} + +function Get-RowByKey { + param([object[]]$Rows, [string]$Key) + return @($Rows | Where-Object { [string](Get-PropertyValue -Item $_ -Name "row_key" -Default "") -eq $Key } | Select-Object -First 1) +} + +function Test-ExpectedRow { + param([object]$Row, [object]$Expected) + if ($null -eq $Row) { return $false } + return ( + (Test-ObjectHasFields -Item $Row -Fields $requiredRowFields) -and + [string](Get-PropertyValue -Item $Row -Name "row_key" -Default "") -eq [string](Get-PropertyValue -Item $Expected -Name "row_key" -Default "") -and + [string](Get-PropertyValue -Item $Row -Name "label" -Default "") -ne "" -and + [string](Get-PropertyValue -Item $Row -Name "state" -Default "") -eq [string](Get-PropertyValue -Item $Expected -Name "state" -Default "") -and + [string](Get-PropertyValue -Item $Row -Name "value" -Default "") -eq [string](Get-PropertyValue -Item $Expected -Name "value" -Default "") -and + [string](Get-PropertyValue -Item $Row -Name "severity" -Default "") -eq [string](Get-PropertyValue -Item $Expected -Name "severity" -Default "") + ) +} + +& powershell -ExecutionPolicy Bypass -File (Join-Path $PSScriptRoot "c19z63-remote-workspace-real-adapter-admin-handoff-digest-smoke.ps1") ` + -ApiBaseUrl $ApiBaseUrl ` + -ClusterID $ClusterID ` + -ActorUserID $ActorUserID ` + -RequestedNodeName $RequestedNodeName ` + -DefaultNodeName $DefaultNodeName ` + -MatrixNodeName $MatrixNodeName ` + -ResultPath $sourceResultPath | Out-Null + +$sourceFile = Join-Path $repoRoot $sourceResultPath +$sourceResult = Get-Content -Raw -Path $sourceFile | ConvertFrom-Json +$digest = Get-PropertyValue -Item $sourceResult -Name "admin_handoff_digest" -Default $null +$rows = @((Get-PropertyValue -Item $digest -Name "display_rows" -Default @())) +$guardrails = Get-PropertyValue -Item $digest -Name "guardrails" -Default $null + +$rowChecks = [ordered]@{} +foreach ($expected in $expectedRows) { + $key = [string](Get-PropertyValue -Item $expected -Name "row_key" -Default "") + $rowChecks[$key] = Test-ExpectedRow -Row (Get-RowByKey -Rows $rows -Key $key) -Expected $expected +} + +$digestFieldsCompatible = Test-ObjectHasFields -Item $digest -Fields $requiredDigestFields +$digestValuesCompatible = ( + [string](Get-PropertyValue -Item $digest -Name "schema_version" -Default "") -eq "rap.remote_workspace_real_adapter_admin_handoff_digest.v1" -and + [string](Get-PropertyValue -Item $digest -Name "source_bundle_schema" -Default "") -eq "rap.remote_workspace_real_adapter_admin_handoff_bundle.v1" -and + [string](Get-PropertyValue -Item $digest -Name "digest_status" -Default "") -eq "complete" -and + [string](Get-PropertyValue -Item $digest -Name "admin_status" -Default "") -eq "not_ready" -and + [string](Get-PropertyValue -Item $digest -Name "admin_action" -Default "") -eq "keep_real_adapter_disabled" -and + [int](Get-PropertyValue -Item $digest -Name "row_count" -Default -1) -eq $expectedRows.Count +) +$guardrailsCompatible = ( + [bool](Get-PropertyValue -Item $guardrails -Name "activation_blocked" -Default $false) -and + -not [bool](Get-PropertyValue -Item $guardrails -Name "process_start_allowed" -Default $true) -and + -not [bool](Get-PropertyValue -Item $guardrails -Name "health_probe_enabled" -Default $true) -and + [string](Get-PropertyValue -Item $guardrails -Name "payload_traffic" -Default "") -eq "none" -and + -not [bool](Get-PropertyValue -Item $guardrails -Name "allows_payload_traffic" -Default $true) -and + -not [bool](Get-PropertyValue -Item $guardrails -Name "allows_process_start" -Default $true) +) + +$checks = [ordered]@{ + source_smoke_passed = ([bool]$sourceResult.passed) + source_schema_expected = ([string]$sourceResult.schema_version -eq "c19z63.remote_workspace_real_adapter_admin_handoff_digest_smoke.v1") + digest_present = ($null -ne $digest) + digest_fields_compatible = $digestFieldsCompatible + digest_values_compatible = $digestValuesCompatible + row_count_expected = ($rows.Count -eq $expectedRows.Count) + required_row_fields_declared = ($requiredRowFields.Count -eq 5) + all_rows_compatible = (@($rowChecks.GetEnumerator() | Where-Object { -not $_.Value }).Count -eq 0) + guardrails_compatible = $guardrailsCompatible +} +$failed = @($checks.GetEnumerator() | Where-Object { -not $_.Value } | ForEach-Object { $_.Key }) + +$result = [ordered]@{ + schema_version = "c19z64.remote_workspace_real_adapter_admin_handoff_digest_compatibility_smoke.v1" + source_result_path = $sourceFile + cluster_id = $ClusterID + required_digest_fields = $requiredDigestFields + required_row_fields = $requiredRowFields + expected_rows = $expectedRows + row_checks = $rowChecks + admin_handoff_digest = $digest + checks = $checks + failed_checks = $failed + passed = ($failed.Count -eq 0) +} + +$fullResultPath = Join-Path $repoRoot $ResultPath +$resultDir = Split-Path -Parent $fullResultPath +if ($resultDir) { New-Item -ItemType Directory -Force -Path $resultDir | Out-Null } +$result | ConvertTo-Json -Depth 100 | Set-Content -Encoding UTF8 -Path $fullResultPath + +if (-not $result.passed) { + throw "C19Z64 remote workspace real-adapter admin handoff digest compatibility smoke failed. Result: $fullResultPath Failed: $($failed -join ', ')" +} + +Write-Host "C19Z64 remote workspace real-adapter admin handoff digest compatibility smoke passed. Result: $fullResultPath" +$result diff --git a/scripts/fabric/c19z65-remote-workspace-real-adapter-admin-handoff-digest-rollup-smoke.ps1 b/scripts/fabric/c19z65-remote-workspace-real-adapter-admin-handoff-digest-rollup-smoke.ps1 new file mode 100644 index 0000000..0a926e9 --- /dev/null +++ b/scripts/fabric/c19z65-remote-workspace-real-adapter-admin-handoff-digest-rollup-smoke.ps1 @@ -0,0 +1,140 @@ +param( + [string]$ApiBaseUrl = "http://192.168.200.61:18121/api/v1", + [string]$ClusterID = "cfc0743d-d960-49fb-9de8-96e063d5e4aa", + [string]$ActorUserID = "f67d943f-5397-4b3a-a229-695fe67ad700", + [string]$RequestedNodeName = "test-1", + [string]$DefaultNodeName = "test-2", + [string]$MatrixNodeName = "test-1", + [string]$ResultPath = "artifacts\c19z65-remote-workspace-real-adapter-admin-handoff-digest-rollup-smoke-result.json" +) + +Set-StrictMode -Version Latest +$ErrorActionPreference = "Stop" + +$repoRoot = (Resolve-Path (Join-Path $PSScriptRoot "..\..")).ProviderPath +$sourceResultPath = "artifacts\c19z65-remote-workspace-real-adapter-admin-handoff-digest-rollup-source-result.json" + +function Get-PropertyValue { + param([object]$Item, [string]$Name, [object]$Default = $null) + if ($null -eq $Item) { return $Default } + if ($Item -is [System.Collections.IDictionary]) { + if ($Item.Contains($Name)) { return $Item[$Name] } + return $Default + } + $property = $Item.PSObject.Properties[$Name] + if ($null -eq $property) { return $Default } + return $property.Value +} + +function New-CountMap { + param([object[]]$Rows, [string]$Field) + $map = [ordered]@{} + foreach ($row in $Rows) { + $key = [string](Get-PropertyValue -Item $row -Name $Field -Default "") + if ($key -eq "") { $key = "unknown" } + if ($map.Contains($key)) { $map[$key] = [int]$map[$key] + 1 } else { $map[$key] = 1 } + } + return $map +} + +function Get-MapInt { + param([object]$Map, [string]$Name) + if ($null -eq $Map) { return 0 } + $value = Get-PropertyValue -Item $Map -Name $Name -Default 0 + return [int]$value +} + +& powershell -ExecutionPolicy Bypass -File (Join-Path $PSScriptRoot "c19z64-remote-workspace-real-adapter-admin-handoff-digest-compatibility-smoke.ps1") ` + -ApiBaseUrl $ApiBaseUrl ` + -ClusterID $ClusterID ` + -ActorUserID $ActorUserID ` + -RequestedNodeName $RequestedNodeName ` + -DefaultNodeName $DefaultNodeName ` + -MatrixNodeName $MatrixNodeName ` + -ResultPath $sourceResultPath | Out-Null + +$sourceFile = Join-Path $repoRoot $sourceResultPath +$sourceResult = Get-Content -Raw -Path $sourceFile | ConvertFrom-Json +$digest = Get-PropertyValue -Item $sourceResult -Name "admin_handoff_digest" -Default $null +$rows = @((Get-PropertyValue -Item $digest -Name "display_rows" -Default @())) +$guardrails = Get-PropertyValue -Item $digest -Name "guardrails" -Default $null +$severityCounts = New-CountMap -Rows $rows -Field "severity" +$stateCounts = New-CountMap -Rows $rows -Field "state" + +$rollup = [ordered]@{ + schema_version = "rap.remote_workspace_real_adapter_admin_handoff_digest_rollup.v1" + source_digest_schema = Get-PropertyValue -Item $digest -Name "schema_version" -Default $null + rollup_status = "complete" + admin_status = Get-PropertyValue -Item $digest -Name "admin_status" -Default $null + primary_action = Get-PropertyValue -Item $digest -Name "admin_action" -Default $null + row_count = $rows.Count + counts_by_severity = $severityCounts + counts_by_state = $stateCounts + guardrail_summary = [ordered]@{ + activation_blocked = Get-PropertyValue -Item $guardrails -Name "activation_blocked" -Default $null + process_start_allowed = Get-PropertyValue -Item $guardrails -Name "process_start_allowed" -Default $null + health_probe_enabled = Get-PropertyValue -Item $guardrails -Name "health_probe_enabled" -Default $null + payload_traffic = Get-PropertyValue -Item $guardrails -Name "payload_traffic" -Default $null + allows_process_start = Get-PropertyValue -Item $guardrails -Name "allows_process_start" -Default $null + allows_payload_traffic = Get-PropertyValue -Item $guardrails -Name "allows_payload_traffic" -Default $null + } +} + +$severityCompatible = ( + (Get-MapInt -Map $severityCounts -Name "warn") -eq 2 -and + (Get-MapInt -Map $severityCounts -Name "info") -eq 6 +) +$stateCompatible = ( + (Get-MapInt -Map $stateCounts -Name "blocked") -eq 3 -and + (Get-MapInt -Map $stateCounts -Name "disabled") -eq 3 -and + (Get-MapInt -Map $stateCounts -Name "required") -eq 1 -and + (Get-MapInt -Map $stateCounts -Name "complete") -eq 1 +) +$guardrailsCompatible = ( + [bool](Get-PropertyValue -Item $rollup.guardrail_summary -Name "activation_blocked" -Default $false) -and + -not [bool](Get-PropertyValue -Item $rollup.guardrail_summary -Name "process_start_allowed" -Default $true) -and + -not [bool](Get-PropertyValue -Item $rollup.guardrail_summary -Name "health_probe_enabled" -Default $true) -and + [string](Get-PropertyValue -Item $rollup.guardrail_summary -Name "payload_traffic" -Default "") -eq "none" -and + -not [bool](Get-PropertyValue -Item $rollup.guardrail_summary -Name "allows_process_start" -Default $true) -and + -not [bool](Get-PropertyValue -Item $rollup.guardrail_summary -Name "allows_payload_traffic" -Default $true) +) +$rollupValuesCompatible = ( + [string](Get-PropertyValue -Item $rollup -Name "schema_version" -Default "") -eq "rap.remote_workspace_real_adapter_admin_handoff_digest_rollup.v1" -and + [string](Get-PropertyValue -Item $rollup -Name "source_digest_schema" -Default "") -eq "rap.remote_workspace_real_adapter_admin_handoff_digest.v1" -and + [string](Get-PropertyValue -Item $rollup -Name "rollup_status" -Default "") -eq "complete" -and + [string](Get-PropertyValue -Item $rollup -Name "admin_status" -Default "") -eq "not_ready" -and + [string](Get-PropertyValue -Item $rollup -Name "primary_action" -Default "") -eq "keep_real_adapter_disabled" -and + [int](Get-PropertyValue -Item $rollup -Name "row_count" -Default -1) -eq 8 +) + +$checks = [ordered]@{ + source_smoke_passed = ([bool]$sourceResult.passed) + source_schema_expected = ([string]$sourceResult.schema_version -eq "c19z64.remote_workspace_real_adapter_admin_handoff_digest_compatibility_smoke.v1") + rollup_values_compatible = $rollupValuesCompatible + severity_counts_compatible = $severityCompatible + state_counts_compatible = $stateCompatible + guardrails_compatible = $guardrailsCompatible +} +$failed = @($checks.GetEnumerator() | Where-Object { -not $_.Value } | ForEach-Object { $_.Key }) + +$result = [ordered]@{ + schema_version = "c19z65.remote_workspace_real_adapter_admin_handoff_digest_rollup_smoke.v1" + source_result_path = $sourceFile + cluster_id = $ClusterID + admin_handoff_digest_rollup = $rollup + checks = $checks + failed_checks = $failed + passed = ($failed.Count -eq 0) +} + +$fullResultPath = Join-Path $repoRoot $ResultPath +$resultDir = Split-Path -Parent $fullResultPath +if ($resultDir) { New-Item -ItemType Directory -Force -Path $resultDir | Out-Null } +$result | ConvertTo-Json -Depth 100 | Set-Content -Encoding UTF8 -Path $fullResultPath + +if (-not $result.passed) { + throw "C19Z65 remote workspace real-adapter admin handoff digest rollup smoke failed. Result: $fullResultPath Failed: $($failed -join ', ')" +} + +Write-Host "C19Z65 remote workspace real-adapter admin handoff digest rollup smoke passed. Result: $fullResultPath" +$result diff --git a/scripts/fabric/c19z66-remote-workspace-real-adapter-admin-handoff-digest-rollup-compatibility-smoke.ps1 b/scripts/fabric/c19z66-remote-workspace-real-adapter-admin-handoff-digest-rollup-compatibility-smoke.ps1 new file mode 100644 index 0000000..45c1d13 --- /dev/null +++ b/scripts/fabric/c19z66-remote-workspace-real-adapter-admin-handoff-digest-rollup-compatibility-smoke.ps1 @@ -0,0 +1,129 @@ +param( + [string]$ApiBaseUrl = "http://192.168.200.61:18121/api/v1", + [string]$ClusterID = "cfc0743d-d960-49fb-9de8-96e063d5e4aa", + [string]$ActorUserID = "f67d943f-5397-4b3a-a229-695fe67ad700", + [string]$RequestedNodeName = "test-1", + [string]$DefaultNodeName = "test-2", + [string]$MatrixNodeName = "test-1", + [string]$ResultPath = "artifacts\c19z66-remote-workspace-real-adapter-admin-handoff-digest-rollup-compatibility-smoke-result.json" +) + +Set-StrictMode -Version Latest +$ErrorActionPreference = "Stop" + +$repoRoot = (Resolve-Path (Join-Path $PSScriptRoot "..\..")).ProviderPath +$sourceResultPath = "artifacts\c19z66-remote-workspace-real-adapter-admin-handoff-digest-rollup-source-result.json" +$requiredRollupFields = @("schema_version", "source_digest_schema", "rollup_status", "admin_status", "primary_action", "row_count", "counts_by_severity", "counts_by_state", "guardrail_summary") +$requiredGuardrailFields = @("activation_blocked", "process_start_allowed", "health_probe_enabled", "payload_traffic", "allows_process_start", "allows_payload_traffic") + +function Get-PropertyValue { + param([object]$Item, [string]$Name, [object]$Default = $null) + if ($null -eq $Item) { return $Default } + if ($Item -is [System.Collections.IDictionary]) { + if ($Item.Contains($Name)) { return $Item[$Name] } + return $Default + } + $property = $Item.PSObject.Properties[$Name] + if ($null -eq $property) { return $Default } + return $property.Value +} + +function Test-ObjectHasFields { + param([object]$Item, [string[]]$Fields) + if ($null -eq $Item) { return $false } + foreach ($field in $Fields) { + if ($Item -is [System.Collections.IDictionary]) { + if (-not $Item.Contains($field)) { return $false } + continue + } + if ($null -eq $Item.PSObject.Properties[$field]) { return $false } + } + return $true +} + +function Get-MapInt { + param([object]$Map, [string]$Name) + if ($null -eq $Map) { return 0 } + return [int](Get-PropertyValue -Item $Map -Name $Name -Default 0) +} + +& powershell -ExecutionPolicy Bypass -File (Join-Path $PSScriptRoot "c19z65-remote-workspace-real-adapter-admin-handoff-digest-rollup-smoke.ps1") ` + -ApiBaseUrl $ApiBaseUrl ` + -ClusterID $ClusterID ` + -ActorUserID $ActorUserID ` + -RequestedNodeName $RequestedNodeName ` + -DefaultNodeName $DefaultNodeName ` + -MatrixNodeName $MatrixNodeName ` + -ResultPath $sourceResultPath | Out-Null + +$sourceFile = Join-Path $repoRoot $sourceResultPath +$sourceResult = Get-Content -Raw -Path $sourceFile | ConvertFrom-Json +$rollup = Get-PropertyValue -Item $sourceResult -Name "admin_handoff_digest_rollup" -Default $null +$severity = Get-PropertyValue -Item $rollup -Name "counts_by_severity" -Default $null +$state = Get-PropertyValue -Item $rollup -Name "counts_by_state" -Default $null +$guardrails = Get-PropertyValue -Item $rollup -Name "guardrail_summary" -Default $null + +$rollupFieldsCompatible = Test-ObjectHasFields -Item $rollup -Fields $requiredRollupFields +$rollupValuesCompatible = ( + [string](Get-PropertyValue -Item $rollup -Name "schema_version" -Default "") -eq "rap.remote_workspace_real_adapter_admin_handoff_digest_rollup.v1" -and + [string](Get-PropertyValue -Item $rollup -Name "source_digest_schema" -Default "") -eq "rap.remote_workspace_real_adapter_admin_handoff_digest.v1" -and + [string](Get-PropertyValue -Item $rollup -Name "rollup_status" -Default "") -eq "complete" -and + [string](Get-PropertyValue -Item $rollup -Name "admin_status" -Default "") -eq "not_ready" -and + [string](Get-PropertyValue -Item $rollup -Name "primary_action" -Default "") -eq "keep_real_adapter_disabled" -and + [int](Get-PropertyValue -Item $rollup -Name "row_count" -Default -1) -eq 8 +) +$severityCompatible = ( + (Get-MapInt -Map $severity -Name "warn") -eq 2 -and + (Get-MapInt -Map $severity -Name "info") -eq 6 +) +$stateCompatible = ( + (Get-MapInt -Map $state -Name "blocked") -eq 3 -and + (Get-MapInt -Map $state -Name "disabled") -eq 3 -and + (Get-MapInt -Map $state -Name "required") -eq 1 -and + (Get-MapInt -Map $state -Name "complete") -eq 1 +) +$guardrailsCompatible = ( + (Test-ObjectHasFields -Item $guardrails -Fields $requiredGuardrailFields) -and + [bool](Get-PropertyValue -Item $guardrails -Name "activation_blocked" -Default $false) -and + -not [bool](Get-PropertyValue -Item $guardrails -Name "process_start_allowed" -Default $true) -and + -not [bool](Get-PropertyValue -Item $guardrails -Name "health_probe_enabled" -Default $true) -and + [string](Get-PropertyValue -Item $guardrails -Name "payload_traffic" -Default "") -eq "none" -and + -not [bool](Get-PropertyValue -Item $guardrails -Name "allows_process_start" -Default $true) -and + -not [bool](Get-PropertyValue -Item $guardrails -Name "allows_payload_traffic" -Default $true) +) + +$checks = [ordered]@{ + source_smoke_passed = ([bool]$sourceResult.passed) + source_schema_expected = ([string]$sourceResult.schema_version -eq "c19z65.remote_workspace_real_adapter_admin_handoff_digest_rollup_smoke.v1") + rollup_present = ($null -ne $rollup) + rollup_fields_compatible = $rollupFieldsCompatible + rollup_values_compatible = $rollupValuesCompatible + severity_counts_compatible = $severityCompatible + state_counts_compatible = $stateCompatible + guardrails_compatible = $guardrailsCompatible +} +$failed = @($checks.GetEnumerator() | Where-Object { -not $_.Value } | ForEach-Object { $_.Key }) + +$result = [ordered]@{ + schema_version = "c19z66.remote_workspace_real_adapter_admin_handoff_digest_rollup_compatibility_smoke.v1" + source_result_path = $sourceFile + cluster_id = $ClusterID + required_rollup_fields = $requiredRollupFields + required_guardrail_fields = $requiredGuardrailFields + admin_handoff_digest_rollup = $rollup + checks = $checks + failed_checks = $failed + passed = ($failed.Count -eq 0) +} + +$fullResultPath = Join-Path $repoRoot $ResultPath +$resultDir = Split-Path -Parent $fullResultPath +if ($resultDir) { New-Item -ItemType Directory -Force -Path $resultDir | Out-Null } +$result | ConvertTo-Json -Depth 100 | Set-Content -Encoding UTF8 -Path $fullResultPath + +if (-not $result.passed) { + throw "C19Z66 remote workspace real-adapter admin handoff digest rollup compatibility smoke failed. Result: $fullResultPath Failed: $($failed -join ', ')" +} + +Write-Host "C19Z66 remote workspace real-adapter admin handoff digest rollup compatibility smoke passed. Result: $fullResultPath" +$result diff --git a/scripts/fabric/c19z67-remote-workspace-real-adapter-admin-handoff-full-chain-summary-smoke.ps1 b/scripts/fabric/c19z67-remote-workspace-real-adapter-admin-handoff-full-chain-summary-smoke.ps1 new file mode 100644 index 0000000..9f5e0ce --- /dev/null +++ b/scripts/fabric/c19z67-remote-workspace-real-adapter-admin-handoff-full-chain-summary-smoke.ps1 @@ -0,0 +1,178 @@ +param( + [string]$ApiBaseUrl = "http://192.168.200.61:18121/api/v1", + [string]$ClusterID = "cfc0743d-d960-49fb-9de8-96e063d5e4aa", + [string]$ActorUserID = "f67d943f-5397-4b3a-a229-695fe67ad700", + [string]$RequestedNodeName = "test-1", + [string]$DefaultNodeName = "test-2", + [string]$MatrixNodeName = "test-1", + [string]$ResultPath = "artifacts\c19z67-remote-workspace-real-adapter-admin-handoff-full-chain-summary-smoke-result.json" +) + +Set-StrictMode -Version Latest +$ErrorActionPreference = "Stop" + +$repoRoot = (Resolve-Path (Join-Path $PSScriptRoot "..\..")).ProviderPath +$sourceResultPath = "artifacts\c19z67-remote-workspace-real-adapter-admin-handoff-full-chain-summary-source-result.json" +$expectedStageKeys = @( + "c19z54_handoff_v4", + "c19z55_mode_matrix_v3", + "c19z56_mode_matrix_v3_compatibility", + "c19z57_readiness_handoff_summary", + "c19z58_readiness_handoff_summary_compatibility", + "c19z59_operator_action_map", + "c19z60_operator_action_map_compatibility", + "c19z61_admin_handoff_bundle", + "c19z62_admin_handoff_bundle_compatibility", + "c19z63_admin_handoff_digest", + "c19z64_admin_handoff_digest_compatibility", + "c19z65_admin_handoff_digest_rollup", + "c19z66_admin_handoff_digest_rollup_compatibility" +) +$requiredStageFields = @("stage_key", "status", "artifact", "runtime_effect") + +function Get-PropertyValue { + param([object]$Item, [string]$Name, [object]$Default = $null) + if ($null -eq $Item) { return $Default } + if ($Item -is [System.Collections.IDictionary]) { + if ($Item.Contains($Name)) { return $Item[$Name] } + return $Default + } + $property = $Item.PSObject.Properties[$Name] + if ($null -eq $property) { return $Default } + return $property.Value +} + +function Test-ObjectHasFields { + param([object]$Item, [string[]]$Fields) + if ($null -eq $Item) { return $false } + foreach ($field in $Fields) { + if ($Item -is [System.Collections.IDictionary]) { + if (-not $Item.Contains($field)) { return $false } + continue + } + if ($null -eq $Item.PSObject.Properties[$field]) { return $false } + } + return $true +} + +function Get-StageByKey { + param([object[]]$Stages, [string]$Key) + return @($Stages | Where-Object { [string](Get-PropertyValue -Item $_ -Name "stage_key" -Default "") -eq $Key } | Select-Object -First 1) +} + +function Test-StagesCompatible { + param([object[]]$Stages) + if ($Stages.Count -ne $expectedStageKeys.Count) { return $false } + foreach ($key in $expectedStageKeys) { + $stage = Get-StageByKey -Stages $Stages -Key $key + if ($null -eq $stage) { return $false } + if (-not (Test-ObjectHasFields -Item $stage -Fields $requiredStageFields)) { return $false } + if ([string](Get-PropertyValue -Item $stage -Name "status" -Default "") -ne "passed") { return $false } + if ([string](Get-PropertyValue -Item $stage -Name "runtime_effect" -Default "") -ne "contract_only_no_runtime_enablement") { return $false } + } + return $true +} + +function New-Stage { + param([string]$Key, [string]$Artifact) + return [ordered]@{ + stage_key = $Key + status = "passed" + artifact = $Artifact + runtime_effect = "contract_only_no_runtime_enablement" + } +} + +& powershell -ExecutionPolicy Bypass -File (Join-Path $PSScriptRoot "c19z66-remote-workspace-real-adapter-admin-handoff-digest-rollup-compatibility-smoke.ps1") ` + -ApiBaseUrl $ApiBaseUrl ` + -ClusterID $ClusterID ` + -ActorUserID $ActorUserID ` + -RequestedNodeName $RequestedNodeName ` + -DefaultNodeName $DefaultNodeName ` + -MatrixNodeName $MatrixNodeName ` + -ResultPath $sourceResultPath | Out-Null + +$sourceFile = Join-Path $repoRoot $sourceResultPath +$sourceResult = Get-Content -Raw -Path $sourceFile | ConvertFrom-Json +$rollup = Get-PropertyValue -Item $sourceResult -Name "admin_handoff_digest_rollup" -Default $null +$guardrails = Get-PropertyValue -Item $rollup -Name "guardrail_summary" -Default $null + +$stages = @( + (New-Stage -Key "c19z54_handoff_v4" -Artifact "artifacts/c19z54-remote-workspace-real-adapter-handoff-v4-smoke-result.json"), + (New-Stage -Key "c19z55_mode_matrix_v3" -Artifact "artifacts/c19z55-remote-workspace-real-adapter-mode-matrix-v3-smoke-result.json"), + (New-Stage -Key "c19z56_mode_matrix_v3_compatibility" -Artifact "artifacts/c19z56-remote-workspace-real-adapter-mode-matrix-v3-compatibility-smoke-result.json"), + (New-Stage -Key "c19z57_readiness_handoff_summary" -Artifact "artifacts/c19z57-remote-workspace-real-adapter-readiness-handoff-summary-smoke-result.json"), + (New-Stage -Key "c19z58_readiness_handoff_summary_compatibility" -Artifact "artifacts/c19z58-remote-workspace-real-adapter-readiness-handoff-summary-compatibility-smoke-result.json"), + (New-Stage -Key "c19z59_operator_action_map" -Artifact "artifacts/c19z59-remote-workspace-real-adapter-disabled-action-map-smoke-result.json"), + (New-Stage -Key "c19z60_operator_action_map_compatibility" -Artifact "artifacts/c19z60-remote-workspace-real-adapter-disabled-action-map-compatibility-smoke-result.json"), + (New-Stage -Key "c19z61_admin_handoff_bundle" -Artifact "artifacts/c19z61-remote-workspace-real-adapter-admin-handoff-bundle-smoke-result.json"), + (New-Stage -Key "c19z62_admin_handoff_bundle_compatibility" -Artifact "artifacts/c19z62-remote-workspace-real-adapter-admin-handoff-bundle-compatibility-smoke-result.json"), + (New-Stage -Key "c19z63_admin_handoff_digest" -Artifact "artifacts/c19z63-remote-workspace-real-adapter-admin-handoff-digest-smoke-result.json"), + (New-Stage -Key "c19z64_admin_handoff_digest_compatibility" -Artifact "artifacts/c19z64-remote-workspace-real-adapter-admin-handoff-digest-compatibility-smoke-result.json"), + (New-Stage -Key "c19z65_admin_handoff_digest_rollup" -Artifact "artifacts/c19z65-remote-workspace-real-adapter-admin-handoff-digest-rollup-smoke-result.json"), + (New-Stage -Key "c19z66_admin_handoff_digest_rollup_compatibility" -Artifact "artifacts/c19z66-remote-workspace-real-adapter-admin-handoff-digest-rollup-compatibility-smoke-result.json") +) + +$fullChainSummary = [ordered]@{ + schema_version = "rap.remote_workspace_real_adapter_admin_handoff_full_chain_summary.v1" + source_rollup_schema = Get-PropertyValue -Item $rollup -Name "schema_version" -Default $null + chain_status = "complete" + proven_stage_count = $stages.Count + admin_status = Get-PropertyValue -Item $rollup -Name "admin_status" -Default $null + primary_action = Get-PropertyValue -Item $rollup -Name "primary_action" -Default $null + runtime_effect = "contract_only_no_runtime_enablement" + guardrail_summary = $guardrails + proven_stages = $stages +} + +$stagesCompatible = Test-StagesCompatible -Stages $stages +$summaryCompatible = ( + [string](Get-PropertyValue -Item $fullChainSummary -Name "schema_version" -Default "") -eq "rap.remote_workspace_real_adapter_admin_handoff_full_chain_summary.v1" -and + [string](Get-PropertyValue -Item $fullChainSummary -Name "source_rollup_schema" -Default "") -eq "rap.remote_workspace_real_adapter_admin_handoff_digest_rollup.v1" -and + [string](Get-PropertyValue -Item $fullChainSummary -Name "chain_status" -Default "") -eq "complete" -and + [int](Get-PropertyValue -Item $fullChainSummary -Name "proven_stage_count" -Default -1) -eq $expectedStageKeys.Count -and + [string](Get-PropertyValue -Item $fullChainSummary -Name "admin_status" -Default "") -eq "not_ready" -and + [string](Get-PropertyValue -Item $fullChainSummary -Name "primary_action" -Default "") -eq "keep_real_adapter_disabled" -and + [string](Get-PropertyValue -Item $fullChainSummary -Name "runtime_effect" -Default "") -eq "contract_only_no_runtime_enablement" +) +$guardrailsCompatible = ( + [bool](Get-PropertyValue -Item $guardrails -Name "activation_blocked" -Default $false) -and + -not [bool](Get-PropertyValue -Item $guardrails -Name "process_start_allowed" -Default $true) -and + -not [bool](Get-PropertyValue -Item $guardrails -Name "health_probe_enabled" -Default $true) -and + [string](Get-PropertyValue -Item $guardrails -Name "payload_traffic" -Default "") -eq "none" -and + -not [bool](Get-PropertyValue -Item $guardrails -Name "allows_process_start" -Default $true) -and + -not [bool](Get-PropertyValue -Item $guardrails -Name "allows_payload_traffic" -Default $true) +) + +$checks = [ordered]@{ + source_smoke_passed = ([bool]$sourceResult.passed) + source_schema_expected = ([string]$sourceResult.schema_version -eq "c19z66.remote_workspace_real_adapter_admin_handoff_digest_rollup_compatibility_smoke.v1") + summary_compatible = $summaryCompatible + stages_compatible = $stagesCompatible + guardrails_compatible = $guardrailsCompatible +} +$failed = @($checks.GetEnumerator() | Where-Object { -not $_.Value } | ForEach-Object { $_.Key }) + +$result = [ordered]@{ + schema_version = "c19z67.remote_workspace_real_adapter_admin_handoff_full_chain_summary_smoke.v1" + source_result_path = $sourceFile + cluster_id = $ClusterID + expected_stage_keys = $expectedStageKeys + required_stage_fields = $requiredStageFields + admin_handoff_full_chain_summary = $fullChainSummary + checks = $checks + failed_checks = $failed + passed = ($failed.Count -eq 0) +} + +$fullResultPath = Join-Path $repoRoot $ResultPath +$resultDir = Split-Path -Parent $fullResultPath +if ($resultDir) { New-Item -ItemType Directory -Force -Path $resultDir | Out-Null } +$result | ConvertTo-Json -Depth 100 | Set-Content -Encoding UTF8 -Path $fullResultPath + +if (-not $result.passed) { + throw "C19Z67 remote workspace real-adapter admin handoff full-chain summary smoke failed. Result: $fullResultPath Failed: $($failed -join ', ')" +} + +Write-Host "C19Z67 remote workspace real-adapter admin handoff full-chain summary smoke passed. Result: $fullResultPath" +$result diff --git a/scripts/fabric/c19z68-remote-workspace-real-adapter-admin-handoff-full-chain-summary-compatibility-smoke.ps1 b/scripts/fabric/c19z68-remote-workspace-real-adapter-admin-handoff-full-chain-summary-compatibility-smoke.ps1 new file mode 100644 index 0000000..e099eb3 --- /dev/null +++ b/scripts/fabric/c19z68-remote-workspace-real-adapter-admin-handoff-full-chain-summary-compatibility-smoke.ps1 @@ -0,0 +1,153 @@ +param( + [string]$ApiBaseUrl = "http://192.168.200.61:18121/api/v1", + [string]$ClusterID = "cfc0743d-d960-49fb-9de8-96e063d5e4aa", + [string]$ActorUserID = "f67d943f-5397-4b3a-a229-695fe67ad700", + [string]$RequestedNodeName = "test-1", + [string]$DefaultNodeName = "test-2", + [string]$MatrixNodeName = "test-1", + [string]$ResultPath = "artifacts\c19z68-remote-workspace-real-adapter-admin-handoff-full-chain-summary-compatibility-smoke-result.json" +) + +Set-StrictMode -Version Latest +$ErrorActionPreference = "Stop" + +$repoRoot = (Resolve-Path (Join-Path $PSScriptRoot "..\..")).ProviderPath +$sourceResultPath = "artifacts\c19z68-remote-workspace-real-adapter-admin-handoff-full-chain-summary-source-result.json" +$requiredSummaryFields = @("schema_version", "source_rollup_schema", "chain_status", "proven_stage_count", "admin_status", "primary_action", "runtime_effect", "guardrail_summary", "proven_stages") +$requiredStageFields = @("stage_key", "status", "artifact", "runtime_effect") +$expectedStageKeys = @( + "c19z54_handoff_v4", + "c19z55_mode_matrix_v3", + "c19z56_mode_matrix_v3_compatibility", + "c19z57_readiness_handoff_summary", + "c19z58_readiness_handoff_summary_compatibility", + "c19z59_operator_action_map", + "c19z60_operator_action_map_compatibility", + "c19z61_admin_handoff_bundle", + "c19z62_admin_handoff_bundle_compatibility", + "c19z63_admin_handoff_digest", + "c19z64_admin_handoff_digest_compatibility", + "c19z65_admin_handoff_digest_rollup", + "c19z66_admin_handoff_digest_rollup_compatibility" +) + +function Get-PropertyValue { + param([object]$Item, [string]$Name, [object]$Default = $null) + if ($null -eq $Item) { return $Default } + if ($Item -is [System.Collections.IDictionary]) { + if ($Item.Contains($Name)) { return $Item[$Name] } + return $Default + } + $property = $Item.PSObject.Properties[$Name] + if ($null -eq $property) { return $Default } + return $property.Value +} + +function Test-ObjectHasFields { + param([object]$Item, [string[]]$Fields) + if ($null -eq $Item) { return $false } + foreach ($field in $Fields) { + if ($Item -is [System.Collections.IDictionary]) { + if (-not $Item.Contains($field)) { return $false } + continue + } + if ($null -eq $Item.PSObject.Properties[$field]) { return $false } + } + return $true +} + +function Get-StageByKey { + param([object[]]$Stages, [string]$Key) + return @($Stages | Where-Object { [string](Get-PropertyValue -Item $_ -Name "stage_key" -Default "") -eq $Key } | Select-Object -First 1) +} + +function Test-Stage { + param([object]$Stage, [string]$Key) + if ($null -eq $Stage) { return $false } + return ( + (Test-ObjectHasFields -Item $Stage -Fields $requiredStageFields) -and + [string](Get-PropertyValue -Item $Stage -Name "stage_key" -Default "") -eq $Key -and + [string](Get-PropertyValue -Item $Stage -Name "status" -Default "") -eq "passed" -and + [string](Get-PropertyValue -Item $Stage -Name "artifact" -Default "") -like "artifacts/c19z*" -and + [string](Get-PropertyValue -Item $Stage -Name "runtime_effect" -Default "") -eq "contract_only_no_runtime_enablement" + ) +} + +& powershell -ExecutionPolicy Bypass -File (Join-Path $PSScriptRoot "c19z67-remote-workspace-real-adapter-admin-handoff-full-chain-summary-smoke.ps1") ` + -ApiBaseUrl $ApiBaseUrl ` + -ClusterID $ClusterID ` + -ActorUserID $ActorUserID ` + -RequestedNodeName $RequestedNodeName ` + -DefaultNodeName $DefaultNodeName ` + -MatrixNodeName $MatrixNodeName ` + -ResultPath $sourceResultPath | Out-Null + +$sourceFile = Join-Path $repoRoot $sourceResultPath +$sourceResult = Get-Content -Raw -Path $sourceFile | ConvertFrom-Json +$summary = Get-PropertyValue -Item $sourceResult -Name "admin_handoff_full_chain_summary" -Default $null +$guardrails = Get-PropertyValue -Item $summary -Name "guardrail_summary" -Default $null +$stages = @((Get-PropertyValue -Item $summary -Name "proven_stages" -Default @())) +$stageChecks = [ordered]@{} +foreach ($key in $expectedStageKeys) { + $stageChecks[$key] = Test-Stage -Stage (Get-StageByKey -Stages $stages -Key $key) -Key $key +} + +$summaryFieldsCompatible = Test-ObjectHasFields -Item $summary -Fields $requiredSummaryFields +$summaryValuesCompatible = ( + [string](Get-PropertyValue -Item $summary -Name "schema_version" -Default "") -eq "rap.remote_workspace_real_adapter_admin_handoff_full_chain_summary.v1" -and + [string](Get-PropertyValue -Item $summary -Name "source_rollup_schema" -Default "") -eq "rap.remote_workspace_real_adapter_admin_handoff_digest_rollup.v1" -and + [string](Get-PropertyValue -Item $summary -Name "chain_status" -Default "") -eq "complete" -and + [int](Get-PropertyValue -Item $summary -Name "proven_stage_count" -Default -1) -eq $expectedStageKeys.Count -and + [string](Get-PropertyValue -Item $summary -Name "admin_status" -Default "") -eq "not_ready" -and + [string](Get-PropertyValue -Item $summary -Name "primary_action" -Default "") -eq "keep_real_adapter_disabled" -and + [string](Get-PropertyValue -Item $summary -Name "runtime_effect" -Default "") -eq "contract_only_no_runtime_enablement" +) +$guardrailsCompatible = ( + [bool](Get-PropertyValue -Item $guardrails -Name "activation_blocked" -Default $false) -and + -not [bool](Get-PropertyValue -Item $guardrails -Name "process_start_allowed" -Default $true) -and + -not [bool](Get-PropertyValue -Item $guardrails -Name "health_probe_enabled" -Default $true) -and + [string](Get-PropertyValue -Item $guardrails -Name "payload_traffic" -Default "") -eq "none" -and + -not [bool](Get-PropertyValue -Item $guardrails -Name "allows_process_start" -Default $true) -and + -not [bool](Get-PropertyValue -Item $guardrails -Name "allows_payload_traffic" -Default $true) +) +$stagesCompatible = ( + $stages.Count -eq $expectedStageKeys.Count -and + @($stageChecks.GetEnumerator() | Where-Object { -not $_.Value }).Count -eq 0 +) + +$checks = [ordered]@{ + source_smoke_passed = ([bool]$sourceResult.passed) + source_schema_expected = ([string]$sourceResult.schema_version -eq "c19z67.remote_workspace_real_adapter_admin_handoff_full_chain_summary_smoke.v1") + summary_present = ($null -ne $summary) + summary_fields_compatible = $summaryFieldsCompatible + summary_values_compatible = $summaryValuesCompatible + stages_compatible = $stagesCompatible + guardrails_compatible = $guardrailsCompatible +} +$failed = @($checks.GetEnumerator() | Where-Object { -not $_.Value } | ForEach-Object { $_.Key }) + +$result = [ordered]@{ + schema_version = "c19z68.remote_workspace_real_adapter_admin_handoff_full_chain_summary_compatibility_smoke.v1" + source_result_path = $sourceFile + cluster_id = $ClusterID + required_summary_fields = $requiredSummaryFields + required_stage_fields = $requiredStageFields + expected_stage_keys = $expectedStageKeys + stage_checks = $stageChecks + admin_handoff_full_chain_summary = $summary + checks = $checks + failed_checks = $failed + passed = ($failed.Count -eq 0) +} + +$fullResultPath = Join-Path $repoRoot $ResultPath +$resultDir = Split-Path -Parent $fullResultPath +if ($resultDir) { New-Item -ItemType Directory -Force -Path $resultDir | Out-Null } +$result | ConvertTo-Json -Depth 100 | Set-Content -Encoding UTF8 -Path $fullResultPath + +if (-not $result.passed) { + throw "C19Z68 remote workspace real-adapter admin handoff full-chain summary compatibility smoke failed. Result: $fullResultPath Failed: $($failed -join ', ')" +} + +Write-Host "C19Z68 remote workspace real-adapter admin handoff full-chain summary compatibility smoke passed. Result: $fullResultPath" +$result diff --git a/scripts/fabric/c19z69-remote-workspace-real-adapter-admin-handoff-release-marker-smoke.ps1 b/scripts/fabric/c19z69-remote-workspace-real-adapter-admin-handoff-release-marker-smoke.ps1 new file mode 100644 index 0000000..ac8d74c --- /dev/null +++ b/scripts/fabric/c19z69-remote-workspace-real-adapter-admin-handoff-release-marker-smoke.ps1 @@ -0,0 +1,132 @@ +param( + [string]$ApiBaseUrl = "http://192.168.200.61:18121/api/v1", + [string]$ClusterID = "cfc0743d-d960-49fb-9de8-96e063d5e4aa", + [string]$ActorUserID = "f67d943f-5397-4b3a-a229-695fe67ad700", + [string]$RequestedNodeName = "test-1", + [string]$DefaultNodeName = "test-2", + [string]$MatrixNodeName = "test-1", + [string]$ResultPath = "artifacts\c19z69-remote-workspace-real-adapter-admin-handoff-release-marker-smoke-result.json" +) + +Set-StrictMode -Version Latest +$ErrorActionPreference = "Stop" + +$repoRoot = (Resolve-Path (Join-Path $PSScriptRoot "..\..")).ProviderPath +$sourceResultPath = "artifacts\c19z69-remote-workspace-real-adapter-admin-handoff-release-marker-source-result.json" +$requiredReleaseFields = @( + "schema_version", + "source_full_chain_schema", + "release_status", + "release_marker", + "real_runtime_stage", + "runtime_effect", + "admin_status", + "primary_action", + "proven_stage_count", + "guardrail_summary" +) + +function Get-PropertyValue { + param([object]$Item, [string]$Name, [object]$Default = $null) + if ($null -eq $Item) { return $Default } + if ($Item -is [System.Collections.IDictionary]) { + if ($Item.Contains($Name)) { return $Item[$Name] } + return $Default + } + $property = $Item.PSObject.Properties[$Name] + if ($null -eq $property) { return $Default } + return $property.Value +} + +function Test-ObjectHasFields { + param([object]$Item, [string[]]$Fields) + if ($null -eq $Item) { return $false } + foreach ($field in $Fields) { + if ($Item -is [System.Collections.IDictionary]) { + if (-not $Item.Contains($field)) { return $false } + continue + } + if ($null -eq $Item.PSObject.Properties[$field]) { return $false } + } + return $true +} + +& powershell -ExecutionPolicy Bypass -File (Join-Path $PSScriptRoot "c19z68-remote-workspace-real-adapter-admin-handoff-full-chain-summary-compatibility-smoke.ps1") ` + -ApiBaseUrl $ApiBaseUrl ` + -ClusterID $ClusterID ` + -ActorUserID $ActorUserID ` + -RequestedNodeName $RequestedNodeName ` + -DefaultNodeName $DefaultNodeName ` + -MatrixNodeName $MatrixNodeName ` + -ResultPath $sourceResultPath | Out-Null + +$sourceFile = Join-Path $repoRoot $sourceResultPath +$sourceResult = Get-Content -Raw -Path $sourceFile | ConvertFrom-Json +$summary = Get-PropertyValue -Item $sourceResult -Name "admin_handoff_full_chain_summary" -Default $null +$guardrails = Get-PropertyValue -Item $summary -Name "guardrail_summary" -Default $null + +$releaseMarker = [ordered]@{ + schema_version = "rap.remote_workspace_real_adapter_admin_handoff_release_marker.v1" + source_full_chain_schema = Get-PropertyValue -Item $summary -Name "schema_version" -Default $null + release_status = "contract_only_ready_for_admin_handoff" + release_marker = "c19z69_disabled_real_adapter_admin_handoff_contract_only" + real_runtime_stage = "blocked" + runtime_effect = Get-PropertyValue -Item $summary -Name "runtime_effect" -Default $null + admin_status = Get-PropertyValue -Item $summary -Name "admin_status" -Default $null + primary_action = Get-PropertyValue -Item $summary -Name "primary_action" -Default $null + proven_stage_count = Get-PropertyValue -Item $summary -Name "proven_stage_count" -Default $null + guardrail_summary = $guardrails +} + +$releaseFieldsCompatible = Test-ObjectHasFields -Item $releaseMarker -Fields $requiredReleaseFields +$releaseValuesCompatible = ( + [string](Get-PropertyValue -Item $releaseMarker -Name "schema_version" -Default "") -eq "rap.remote_workspace_real_adapter_admin_handoff_release_marker.v1" -and + [string](Get-PropertyValue -Item $releaseMarker -Name "source_full_chain_schema" -Default "") -eq "rap.remote_workspace_real_adapter_admin_handoff_full_chain_summary.v1" -and + [string](Get-PropertyValue -Item $releaseMarker -Name "release_status" -Default "") -eq "contract_only_ready_for_admin_handoff" -and + [string](Get-PropertyValue -Item $releaseMarker -Name "release_marker" -Default "") -eq "c19z69_disabled_real_adapter_admin_handoff_contract_only" -and + [string](Get-PropertyValue -Item $releaseMarker -Name "real_runtime_stage" -Default "") -eq "blocked" -and + [string](Get-PropertyValue -Item $releaseMarker -Name "runtime_effect" -Default "") -eq "contract_only_no_runtime_enablement" -and + [string](Get-PropertyValue -Item $releaseMarker -Name "admin_status" -Default "") -eq "not_ready" -and + [string](Get-PropertyValue -Item $releaseMarker -Name "primary_action" -Default "") -eq "keep_real_adapter_disabled" -and + [int](Get-PropertyValue -Item $releaseMarker -Name "proven_stage_count" -Default -1) -eq 13 +) +$guardrailsCompatible = ( + [bool](Get-PropertyValue -Item $guardrails -Name "activation_blocked" -Default $false) -and + -not [bool](Get-PropertyValue -Item $guardrails -Name "process_start_allowed" -Default $true) -and + -not [bool](Get-PropertyValue -Item $guardrails -Name "health_probe_enabled" -Default $true) -and + [string](Get-PropertyValue -Item $guardrails -Name "payload_traffic" -Default "") -eq "none" -and + -not [bool](Get-PropertyValue -Item $guardrails -Name "allows_process_start" -Default $true) -and + -not [bool](Get-PropertyValue -Item $guardrails -Name "allows_payload_traffic" -Default $true) +) + +$checks = [ordered]@{ + source_smoke_passed = ([bool]$sourceResult.passed) + source_schema_expected = ([string]$sourceResult.schema_version -eq "c19z68.remote_workspace_real_adapter_admin_handoff_full_chain_summary_compatibility_smoke.v1") + release_fields_compatible = $releaseFieldsCompatible + release_values_compatible = $releaseValuesCompatible + guardrails_compatible = $guardrailsCompatible +} +$failed = @($checks.GetEnumerator() | Where-Object { -not $_.Value } | ForEach-Object { $_.Key }) + +$result = [ordered]@{ + schema_version = "c19z69.remote_workspace_real_adapter_admin_handoff_release_marker_smoke.v1" + source_result_path = $sourceFile + cluster_id = $ClusterID + required_release_fields = $requiredReleaseFields + admin_handoff_release_marker = $releaseMarker + checks = $checks + failed_checks = $failed + passed = ($failed.Count -eq 0) +} + +$fullResultPath = Join-Path $repoRoot $ResultPath +$resultDir = Split-Path -Parent $fullResultPath +if ($resultDir) { New-Item -ItemType Directory -Force -Path $resultDir | Out-Null } +$result | ConvertTo-Json -Depth 100 | Set-Content -Encoding UTF8 -Path $fullResultPath + +if (-not $result.passed) { + throw "C19Z69 remote workspace real-adapter admin handoff release marker smoke failed. Result: $fullResultPath Failed: $($failed -join ', ')" +} + +Write-Host "C19Z69 remote workspace real-adapter admin handoff release marker smoke passed. Result: $fullResultPath" +$result diff --git a/scripts/fabric/c19z7-remote-workspace-mailbox-preflight-severity-smoke.ps1 b/scripts/fabric/c19z7-remote-workspace-mailbox-preflight-severity-smoke.ps1 new file mode 100644 index 0000000..47e27d6 --- /dev/null +++ b/scripts/fabric/c19z7-remote-workspace-mailbox-preflight-severity-smoke.ps1 @@ -0,0 +1,72 @@ +param( + [string]$ApiBaseUrl = "http://192.168.200.61:18121/api/v1", + [string]$ClusterID = "cfc0743d-d960-49fb-9de8-96e063d5e4aa", + [string]$ActorUserID = "f67d943f-5397-4b3a-a229-695fe67ad700", + [string]$EntryNodeName = "test-1", + [string]$ExitNodeName = "test-2", + [string]$EntryBaseUrl = "http://192.168.200.61:19131", + [string]$ResultPath = "artifacts\c19z7-remote-workspace-mailbox-preflight-severity-smoke-result.json" +) + +Set-StrictMode -Version Latest +$ErrorActionPreference = "Stop" + +$repoRoot = (Resolve-Path (Join-Path $PSScriptRoot "..\..")).ProviderPath +$sourceResultPath = "artifacts\c19z7-remote-workspace-mailbox-preflight-severity-source-result.json" + +function Get-PropertyValue { + param([object]$Item, [string]$Name, [object]$Default = $null) + if ($null -eq $Item) { return $Default } + $property = $Item.PSObject.Properties[$Name] + if ($null -eq $property) { return $Default } + return $property.Value +} + +$source = & powershell -ExecutionPolicy Bypass -File (Join-Path $PSScriptRoot "c19z6-remote-workspace-mailbox-preflight-summary-smoke.ps1") ` + -ApiBaseUrl $ApiBaseUrl ` + -ClusterID $ClusterID ` + -ActorUserID $ActorUserID ` + -EntryNodeName $EntryNodeName ` + -ExitNodeName $ExitNodeName ` + -EntryBaseUrl $EntryBaseUrl ` + -ResultPath $sourceResultPath + +$sourceFile = Join-Path $repoRoot $sourceResultPath +$sourceResult = Get-Content -Raw -Path $sourceFile | ConvertFrom-Json +$preflight = Get-PropertyValue -Item $sourceResult -Name "preflight" -Default $null +$preflightJson = Get-PropertyValue -Item $preflight -Name "json" -Default $null +$summaryFields = Get-PropertyValue -Item $preflightJson -Name "operator_summary_fields" -Default $null + +$checks = [ordered]@{ + source_smoke_passed = ([bool]$sourceResult.passed) + operator_status_visible = ([string](Get-PropertyValue -Item $preflightJson -Name "operator_status" -Default "") -eq "resync_required") + operator_severity_visible = ([string](Get-PropertyValue -Item $preflightJson -Name "operator_severity" -Default "") -eq "warn") + summary_status_visible = ([string](Get-PropertyValue -Item $summaryFields -Name "operator_status" -Default "") -eq "resync_required") + summary_severity_visible = ([string](Get-PropertyValue -Item $summaryFields -Name "operator_severity" -Default "") -eq "warn") + stale_state_still_visible = ([string](Get-PropertyValue -Item $preflightJson -Name "diagnostic_state" -Default "") -eq "stale_cursor_gap") + action_still_visible = ([string](Get-PropertyValue -Item $preflightJson -Name "recommended_action" -Default "") -eq "reset_consumer_and_resync") +} +$failed = @($checks.GetEnumerator() | Where-Object { -not $_.Value } | ForEach-Object { $_.Key }) + +$result = [ordered]@{ + schema_version = "c19z7.remote_workspace_mailbox_preflight_severity_smoke.v1" + source_result_path = $sourceFile + cluster_id = $ClusterID + adapter_session_id = [string](Get-PropertyValue -Item $sourceResult -Name "adapter_session_id" -Default "") + preflight = $preflight + checks = $checks + failed_checks = $failed + passed = ($failed.Count -eq 0) +} + +$fullResultPath = Join-Path $repoRoot $ResultPath +$resultDir = Split-Path -Parent $fullResultPath +if ($resultDir) { New-Item -ItemType Directory -Force -Path $resultDir | Out-Null } +$result | ConvertTo-Json -Depth 100 | Set-Content -Encoding UTF8 -Path $fullResultPath + +if (-not $result.passed) { + throw "C19Z7 remote workspace mailbox preflight severity smoke failed. Result: $fullResultPath Failed: $($failed -join ', ')" +} + +Write-Host "C19Z7 remote workspace mailbox preflight severity smoke passed. Result: $fullResultPath" +$result diff --git a/scripts/fabric/c19z70-remote-workspace-real-adapter-admin-handoff-release-marker-compatibility-smoke.ps1 b/scripts/fabric/c19z70-remote-workspace-real-adapter-admin-handoff-release-marker-compatibility-smoke.ps1 new file mode 100644 index 0000000..f5258a6 --- /dev/null +++ b/scripts/fabric/c19z70-remote-workspace-real-adapter-admin-handoff-release-marker-compatibility-smoke.ps1 @@ -0,0 +1,123 @@ +param( + [string]$ApiBaseUrl = "http://192.168.200.61:18121/api/v1", + [string]$ClusterID = "cfc0743d-d960-49fb-9de8-96e063d5e4aa", + [string]$ActorUserID = "f67d943f-5397-4b3a-a229-695fe67ad700", + [string]$RequestedNodeName = "test-1", + [string]$DefaultNodeName = "test-2", + [string]$MatrixNodeName = "test-1", + [string]$ResultPath = "artifacts\c19z70-remote-workspace-real-adapter-admin-handoff-release-marker-compatibility-smoke-result.json" +) + +Set-StrictMode -Version Latest +$ErrorActionPreference = "Stop" + +$repoRoot = (Resolve-Path (Join-Path $PSScriptRoot "..\..")).ProviderPath +$sourceResultPath = "artifacts\c19z70-remote-workspace-real-adapter-admin-handoff-release-marker-source-result.json" +$requiredReleaseFields = @( + "schema_version", + "source_full_chain_schema", + "release_status", + "release_marker", + "real_runtime_stage", + "runtime_effect", + "admin_status", + "primary_action", + "proven_stage_count", + "guardrail_summary" +) +$requiredGuardrailFields = @("activation_blocked", "process_start_allowed", "health_probe_enabled", "payload_traffic", "allows_process_start", "allows_payload_traffic") + +function Get-PropertyValue { + param([object]$Item, [string]$Name, [object]$Default = $null) + if ($null -eq $Item) { return $Default } + if ($Item -is [System.Collections.IDictionary]) { + if ($Item.Contains($Name)) { return $Item[$Name] } + return $Default + } + $property = $Item.PSObject.Properties[$Name] + if ($null -eq $property) { return $Default } + return $property.Value +} + +function Test-ObjectHasFields { + param([object]$Item, [string[]]$Fields) + if ($null -eq $Item) { return $false } + foreach ($field in $Fields) { + if ($Item -is [System.Collections.IDictionary]) { + if (-not $Item.Contains($field)) { return $false } + continue + } + if ($null -eq $Item.PSObject.Properties[$field]) { return $false } + } + return $true +} + +& powershell -ExecutionPolicy Bypass -File (Join-Path $PSScriptRoot "c19z69-remote-workspace-real-adapter-admin-handoff-release-marker-smoke.ps1") ` + -ApiBaseUrl $ApiBaseUrl ` + -ClusterID $ClusterID ` + -ActorUserID $ActorUserID ` + -RequestedNodeName $RequestedNodeName ` + -DefaultNodeName $DefaultNodeName ` + -MatrixNodeName $MatrixNodeName ` + -ResultPath $sourceResultPath | Out-Null + +$sourceFile = Join-Path $repoRoot $sourceResultPath +$sourceResult = Get-Content -Raw -Path $sourceFile | ConvertFrom-Json +$marker = Get-PropertyValue -Item $sourceResult -Name "admin_handoff_release_marker" -Default $null +$guardrails = Get-PropertyValue -Item $marker -Name "guardrail_summary" -Default $null + +$releaseFieldsCompatible = Test-ObjectHasFields -Item $marker -Fields $requiredReleaseFields +$releaseValuesCompatible = ( + [string](Get-PropertyValue -Item $marker -Name "schema_version" -Default "") -eq "rap.remote_workspace_real_adapter_admin_handoff_release_marker.v1" -and + [string](Get-PropertyValue -Item $marker -Name "source_full_chain_schema" -Default "") -eq "rap.remote_workspace_real_adapter_admin_handoff_full_chain_summary.v1" -and + [string](Get-PropertyValue -Item $marker -Name "release_status" -Default "") -eq "contract_only_ready_for_admin_handoff" -and + [string](Get-PropertyValue -Item $marker -Name "release_marker" -Default "") -eq "c19z69_disabled_real_adapter_admin_handoff_contract_only" -and + [string](Get-PropertyValue -Item $marker -Name "real_runtime_stage" -Default "") -eq "blocked" -and + [string](Get-PropertyValue -Item $marker -Name "runtime_effect" -Default "") -eq "contract_only_no_runtime_enablement" -and + [string](Get-PropertyValue -Item $marker -Name "admin_status" -Default "") -eq "not_ready" -and + [string](Get-PropertyValue -Item $marker -Name "primary_action" -Default "") -eq "keep_real_adapter_disabled" -and + [int](Get-PropertyValue -Item $marker -Name "proven_stage_count" -Default -1) -eq 13 +) +$guardrailsCompatible = ( + (Test-ObjectHasFields -Item $guardrails -Fields $requiredGuardrailFields) -and + [bool](Get-PropertyValue -Item $guardrails -Name "activation_blocked" -Default $false) -and + -not [bool](Get-PropertyValue -Item $guardrails -Name "process_start_allowed" -Default $true) -and + -not [bool](Get-PropertyValue -Item $guardrails -Name "health_probe_enabled" -Default $true) -and + [string](Get-PropertyValue -Item $guardrails -Name "payload_traffic" -Default "") -eq "none" -and + -not [bool](Get-PropertyValue -Item $guardrails -Name "allows_process_start" -Default $true) -and + -not [bool](Get-PropertyValue -Item $guardrails -Name "allows_payload_traffic" -Default $true) +) + +$checks = [ordered]@{ + source_smoke_passed = ([bool]$sourceResult.passed) + source_schema_expected = ([string]$sourceResult.schema_version -eq "c19z69.remote_workspace_real_adapter_admin_handoff_release_marker_smoke.v1") + release_marker_present = ($null -ne $marker) + release_fields_compatible = $releaseFieldsCompatible + release_values_compatible = $releaseValuesCompatible + guardrails_compatible = $guardrailsCompatible +} +$failed = @($checks.GetEnumerator() | Where-Object { -not $_.Value } | ForEach-Object { $_.Key }) + +$result = [ordered]@{ + schema_version = "c19z70.remote_workspace_real_adapter_admin_handoff_release_marker_compatibility_smoke.v1" + source_result_path = $sourceFile + cluster_id = $ClusterID + required_release_fields = $requiredReleaseFields + required_guardrail_fields = $requiredGuardrailFields + admin_handoff_release_marker = $marker + checks = $checks + failed_checks = $failed + passed = ($failed.Count -eq 0) +} + +$fullResultPath = Join-Path $repoRoot $ResultPath +$resultDir = Split-Path -Parent $fullResultPath +if ($resultDir) { New-Item -ItemType Directory -Force -Path $resultDir | Out-Null } +$result | ConvertTo-Json -Depth 100 | Set-Content -Encoding UTF8 -Path $fullResultPath + +if (-not $result.passed) { + throw "C19Z70 remote workspace real-adapter admin handoff release marker compatibility smoke failed. Result: $fullResultPath Failed: $($failed -join ', ')" +} + +Write-Host "C19Z70 remote workspace real-adapter admin handoff release marker compatibility smoke passed. Result: $fullResultPath" +$result diff --git a/scripts/fabric/c19z71-remote-workspace-real-adapter-admin-handoff-package-index-smoke.ps1 b/scripts/fabric/c19z71-remote-workspace-real-adapter-admin-handoff-package-index-smoke.ps1 new file mode 100644 index 0000000..9146993 --- /dev/null +++ b/scripts/fabric/c19z71-remote-workspace-real-adapter-admin-handoff-package-index-smoke.ps1 @@ -0,0 +1,152 @@ +param( + [string]$ApiBaseUrl = "http://192.168.200.61:18121/api/v1", + [string]$ClusterID = "cfc0743d-d960-49fb-9de8-96e063d5e4aa", + [string]$ActorUserID = "f67d943f-5397-4b3a-a229-695fe67ad700", + [string]$RequestedNodeName = "test-1", + [string]$DefaultNodeName = "test-2", + [string]$MatrixNodeName = "test-1", + [string]$ResultPath = "artifacts\c19z71-remote-workspace-real-adapter-admin-handoff-package-index-smoke-result.json" +) + +Set-StrictMode -Version Latest +$ErrorActionPreference = "Stop" + +$repoRoot = (Resolve-Path (Join-Path $PSScriptRoot "..\..")).ProviderPath +$sourceResultPath = "artifacts\c19z71-remote-workspace-real-adapter-admin-handoff-package-index-source-result.json" +$requiredPackageFields = @( + "schema_version", + "source_release_marker_schema", + "package_status", + "package_marker", + "covered_stage_range", + "covered_stage_count", + "proven_full_chain_stage_count", + "latest_compatibility_stage", + "real_runtime_stage", + "runtime_effect", + "admin_status", + "primary_action", + "guardrail_summary", + "closeout_notes" +) +$requiredGuardrailFields = @("activation_blocked", "process_start_allowed", "health_probe_enabled", "payload_traffic", "allows_process_start", "allows_payload_traffic") + +function Get-PropertyValue { + param([object]$Item, [string]$Name, [object]$Default = $null) + if ($null -eq $Item) { return $Default } + if ($Item -is [System.Collections.IDictionary]) { + if ($Item.Contains($Name)) { return $Item[$Name] } + return $Default + } + $property = $Item.PSObject.Properties[$Name] + if ($null -eq $property) { return $Default } + return $property.Value +} + +function Test-ObjectHasFields { + param([object]$Item, [string[]]$Fields) + if ($null -eq $Item) { return $false } + foreach ($field in $Fields) { + if ($Item -is [System.Collections.IDictionary]) { + if (-not $Item.Contains($field)) { return $false } + continue + } + if ($null -eq $Item.PSObject.Properties[$field]) { return $false } + } + return $true +} + +& powershell -ExecutionPolicy Bypass -File (Join-Path $PSScriptRoot "c19z70-remote-workspace-real-adapter-admin-handoff-release-marker-compatibility-smoke.ps1") ` + -ApiBaseUrl $ApiBaseUrl ` + -ClusterID $ClusterID ` + -ActorUserID $ActorUserID ` + -RequestedNodeName $RequestedNodeName ` + -DefaultNodeName $DefaultNodeName ` + -MatrixNodeName $MatrixNodeName ` + -ResultPath $sourceResultPath | Out-Null + +$sourceFile = Join-Path $repoRoot $sourceResultPath +$sourceResult = Get-Content -Raw -Path $sourceFile | ConvertFrom-Json +$marker = Get-PropertyValue -Item $sourceResult -Name "admin_handoff_release_marker" -Default $null +$guardrails = Get-PropertyValue -Item $marker -Name "guardrail_summary" -Default $null + +$packageIndex = [ordered]@{ + schema_version = "rap.remote_workspace_real_adapter_admin_handoff_package_index.v1" + source_release_marker_schema = Get-PropertyValue -Item $marker -Name "schema_version" -Default $null + package_status = "closed_contract_only" + package_marker = "c19z71_disabled_real_adapter_admin_handoff_package_closed_contract_only" + covered_stage_range = "C19Z54-C19Z70" + covered_stage_count = 17 + proven_full_chain_stage_count = Get-PropertyValue -Item $marker -Name "proven_stage_count" -Default $null + latest_compatibility_stage = "C19Z70" + real_runtime_stage = Get-PropertyValue -Item $marker -Name "real_runtime_stage" -Default $null + runtime_effect = Get-PropertyValue -Item $marker -Name "runtime_effect" -Default $null + admin_status = Get-PropertyValue -Item $marker -Name "admin_status" -Default $null + primary_action = Get-PropertyValue -Item $marker -Name "primary_action" -Default $null + guardrail_summary = $guardrails + closeout_notes = @( + "admin_handoff_contract_only_package_indexed", + "real_runtime_gate_not_enabled", + "process_start_disabled", + "payload_forwarding_disabled" + ) +} + +$packageFieldsCompatible = Test-ObjectHasFields -Item $packageIndex -Fields $requiredPackageFields +$packageValuesCompatible = ( + [string](Get-PropertyValue -Item $packageIndex -Name "schema_version" -Default "") -eq "rap.remote_workspace_real_adapter_admin_handoff_package_index.v1" -and + [string](Get-PropertyValue -Item $packageIndex -Name "source_release_marker_schema" -Default "") -eq "rap.remote_workspace_real_adapter_admin_handoff_release_marker.v1" -and + [string](Get-PropertyValue -Item $packageIndex -Name "package_status" -Default "") -eq "closed_contract_only" -and + [string](Get-PropertyValue -Item $packageIndex -Name "package_marker" -Default "") -eq "c19z71_disabled_real_adapter_admin_handoff_package_closed_contract_only" -and + [string](Get-PropertyValue -Item $packageIndex -Name "covered_stage_range" -Default "") -eq "C19Z54-C19Z70" -and + [int](Get-PropertyValue -Item $packageIndex -Name "covered_stage_count" -Default -1) -eq 17 -and + [int](Get-PropertyValue -Item $packageIndex -Name "proven_full_chain_stage_count" -Default -1) -eq 13 -and + [string](Get-PropertyValue -Item $packageIndex -Name "latest_compatibility_stage" -Default "") -eq "C19Z70" -and + [string](Get-PropertyValue -Item $packageIndex -Name "real_runtime_stage" -Default "") -eq "blocked" -and + [string](Get-PropertyValue -Item $packageIndex -Name "runtime_effect" -Default "") -eq "contract_only_no_runtime_enablement" -and + [string](Get-PropertyValue -Item $packageIndex -Name "admin_status" -Default "") -eq "not_ready" -and + [string](Get-PropertyValue -Item $packageIndex -Name "primary_action" -Default "") -eq "keep_real_adapter_disabled" +) +$guardrailsCompatible = ( + (Test-ObjectHasFields -Item $guardrails -Fields $requiredGuardrailFields) -and + [bool](Get-PropertyValue -Item $guardrails -Name "activation_blocked" -Default $false) -and + -not [bool](Get-PropertyValue -Item $guardrails -Name "process_start_allowed" -Default $true) -and + -not [bool](Get-PropertyValue -Item $guardrails -Name "health_probe_enabled" -Default $true) -and + [string](Get-PropertyValue -Item $guardrails -Name "payload_traffic" -Default "") -eq "none" -and + -not [bool](Get-PropertyValue -Item $guardrails -Name "allows_process_start" -Default $true) -and + -not [bool](Get-PropertyValue -Item $guardrails -Name "allows_payload_traffic" -Default $true) +) + +$checks = [ordered]@{ + source_smoke_passed = ([bool]$sourceResult.passed) + source_schema_expected = ([string]$sourceResult.schema_version -eq "c19z70.remote_workspace_real_adapter_admin_handoff_release_marker_compatibility_smoke.v1") + release_marker_present = ($null -ne $marker) + package_fields_compatible = $packageFieldsCompatible + package_values_compatible = $packageValuesCompatible + guardrails_compatible = $guardrailsCompatible +} +$failed = @($checks.GetEnumerator() | Where-Object { -not $_.Value } | ForEach-Object { $_.Key }) + +$result = [ordered]@{ + schema_version = "c19z71.remote_workspace_real_adapter_admin_handoff_package_index_smoke.v1" + source_result_path = $sourceFile + cluster_id = $ClusterID + required_package_fields = $requiredPackageFields + required_guardrail_fields = $requiredGuardrailFields + admin_handoff_package_index = $packageIndex + checks = $checks + failed_checks = $failed + passed = ($failed.Count -eq 0) +} + +$fullResultPath = Join-Path $repoRoot $ResultPath +$resultDir = Split-Path -Parent $fullResultPath +if ($resultDir) { New-Item -ItemType Directory -Force -Path $resultDir | Out-Null } +$result | ConvertTo-Json -Depth 100 | Set-Content -Encoding UTF8 -Path $fullResultPath + +if (-not $result.passed) { + throw "C19Z71 remote workspace real-adapter admin handoff package index smoke failed. Result: $fullResultPath Failed: $($failed -join ', ')" +} + +Write-Host "C19Z71 remote workspace real-adapter admin handoff package index smoke passed. Result: $fullResultPath" +$result diff --git a/scripts/fabric/c19z72-remote-workspace-real-adapter-admin-handoff-package-index-compatibility-smoke.ps1 b/scripts/fabric/c19z72-remote-workspace-real-adapter-admin-handoff-package-index-compatibility-smoke.ps1 new file mode 100644 index 0000000..92ceb03 --- /dev/null +++ b/scripts/fabric/c19z72-remote-workspace-real-adapter-admin-handoff-package-index-compatibility-smoke.ps1 @@ -0,0 +1,144 @@ +param( + [string]$ApiBaseUrl = "http://192.168.200.61:18121/api/v1", + [string]$ClusterID = "cfc0743d-d960-49fb-9de8-96e063d5e4aa", + [string]$ActorUserID = "f67d943f-5397-4b3a-a229-695fe67ad700", + [string]$RequestedNodeName = "test-1", + [string]$DefaultNodeName = "test-2", + [string]$MatrixNodeName = "test-1", + [string]$ResultPath = "artifacts\c19z72-remote-workspace-real-adapter-admin-handoff-package-index-compatibility-smoke-result.json" +) + +Set-StrictMode -Version Latest +$ErrorActionPreference = "Stop" + +$repoRoot = (Resolve-Path (Join-Path $PSScriptRoot "..\..")).ProviderPath +$sourceResultPath = "artifacts\c19z72-remote-workspace-real-adapter-admin-handoff-package-index-source-result.json" +$requiredPackageFields = @( + "schema_version", + "source_release_marker_schema", + "package_status", + "package_marker", + "covered_stage_range", + "covered_stage_count", + "proven_full_chain_stage_count", + "latest_compatibility_stage", + "real_runtime_stage", + "runtime_effect", + "admin_status", + "primary_action", + "guardrail_summary", + "closeout_notes" +) +$requiredGuardrailFields = @("activation_blocked", "process_start_allowed", "health_probe_enabled", "payload_traffic", "allows_process_start", "allows_payload_traffic") +$requiredCloseoutNotes = @("admin_handoff_contract_only_package_indexed", "real_runtime_gate_not_enabled", "process_start_disabled", "payload_forwarding_disabled") + +function Get-PropertyValue { + param([object]$Item, [string]$Name, [object]$Default = $null) + if ($null -eq $Item) { return $Default } + if ($Item -is [System.Collections.IDictionary]) { + if ($Item.Contains($Name)) { return $Item[$Name] } + return $Default + } + $property = $Item.PSObject.Properties[$Name] + if ($null -eq $property) { return $Default } + return $property.Value +} + +function Test-ObjectHasFields { + param([object]$Item, [string[]]$Fields) + if ($null -eq $Item) { return $false } + foreach ($field in $Fields) { + if ($Item -is [System.Collections.IDictionary]) { + if (-not $Item.Contains($field)) { return $false } + continue + } + if ($null -eq $Item.PSObject.Properties[$field]) { return $false } + } + return $true +} + +function Test-ArrayContainsAll { + param([object[]]$Actual, [string[]]$Required) + $values = @($Actual | ForEach-Object { [string]$_ }) + foreach ($item in $Required) { + if ($values -notcontains $item) { return $false } + } + return $true +} + +& powershell -ExecutionPolicy Bypass -File (Join-Path $PSScriptRoot "c19z71-remote-workspace-real-adapter-admin-handoff-package-index-smoke.ps1") ` + -ApiBaseUrl $ApiBaseUrl ` + -ClusterID $ClusterID ` + -ActorUserID $ActorUserID ` + -RequestedNodeName $RequestedNodeName ` + -DefaultNodeName $DefaultNodeName ` + -MatrixNodeName $MatrixNodeName ` + -ResultPath $sourceResultPath | Out-Null + +$sourceFile = Join-Path $repoRoot $sourceResultPath +$sourceResult = Get-Content -Raw -Path $sourceFile | ConvertFrom-Json +$packageIndex = Get-PropertyValue -Item $sourceResult -Name "admin_handoff_package_index" -Default $null +$guardrails = Get-PropertyValue -Item $packageIndex -Name "guardrail_summary" -Default $null +$closeoutNotes = @(Get-PropertyValue -Item $packageIndex -Name "closeout_notes" -Default @()) + +$packageFieldsCompatible = Test-ObjectHasFields -Item $packageIndex -Fields $requiredPackageFields +$packageValuesCompatible = ( + [string](Get-PropertyValue -Item $packageIndex -Name "schema_version" -Default "") -eq "rap.remote_workspace_real_adapter_admin_handoff_package_index.v1" -and + [string](Get-PropertyValue -Item $packageIndex -Name "source_release_marker_schema" -Default "") -eq "rap.remote_workspace_real_adapter_admin_handoff_release_marker.v1" -and + [string](Get-PropertyValue -Item $packageIndex -Name "package_status" -Default "") -eq "closed_contract_only" -and + [string](Get-PropertyValue -Item $packageIndex -Name "package_marker" -Default "") -eq "c19z71_disabled_real_adapter_admin_handoff_package_closed_contract_only" -and + [string](Get-PropertyValue -Item $packageIndex -Name "covered_stage_range" -Default "") -eq "C19Z54-C19Z70" -and + [int](Get-PropertyValue -Item $packageIndex -Name "covered_stage_count" -Default -1) -eq 17 -and + [int](Get-PropertyValue -Item $packageIndex -Name "proven_full_chain_stage_count" -Default -1) -eq 13 -and + [string](Get-PropertyValue -Item $packageIndex -Name "latest_compatibility_stage" -Default "") -eq "C19Z70" -and + [string](Get-PropertyValue -Item $packageIndex -Name "real_runtime_stage" -Default "") -eq "blocked" -and + [string](Get-PropertyValue -Item $packageIndex -Name "runtime_effect" -Default "") -eq "contract_only_no_runtime_enablement" -and + [string](Get-PropertyValue -Item $packageIndex -Name "admin_status" -Default "") -eq "not_ready" -and + [string](Get-PropertyValue -Item $packageIndex -Name "primary_action" -Default "") -eq "keep_real_adapter_disabled" +) +$closeoutNotesCompatible = Test-ArrayContainsAll -Actual $closeoutNotes -Required $requiredCloseoutNotes +$guardrailsCompatible = ( + (Test-ObjectHasFields -Item $guardrails -Fields $requiredGuardrailFields) -and + [bool](Get-PropertyValue -Item $guardrails -Name "activation_blocked" -Default $false) -and + -not [bool](Get-PropertyValue -Item $guardrails -Name "process_start_allowed" -Default $true) -and + -not [bool](Get-PropertyValue -Item $guardrails -Name "health_probe_enabled" -Default $true) -and + [string](Get-PropertyValue -Item $guardrails -Name "payload_traffic" -Default "") -eq "none" -and + -not [bool](Get-PropertyValue -Item $guardrails -Name "allows_process_start" -Default $true) -and + -not [bool](Get-PropertyValue -Item $guardrails -Name "allows_payload_traffic" -Default $true) +) + +$checks = [ordered]@{ + source_smoke_passed = ([bool]$sourceResult.passed) + source_schema_expected = ([string]$sourceResult.schema_version -eq "c19z71.remote_workspace_real_adapter_admin_handoff_package_index_smoke.v1") + package_index_present = ($null -ne $packageIndex) + package_fields_compatible = $packageFieldsCompatible + package_values_compatible = $packageValuesCompatible + closeout_notes_compatible = $closeoutNotesCompatible + guardrails_compatible = $guardrailsCompatible +} +$failed = @($checks.GetEnumerator() | Where-Object { -not $_.Value } | ForEach-Object { $_.Key }) + +$result = [ordered]@{ + schema_version = "c19z72.remote_workspace_real_adapter_admin_handoff_package_index_compatibility_smoke.v1" + source_result_path = $sourceFile + cluster_id = $ClusterID + required_package_fields = $requiredPackageFields + required_guardrail_fields = $requiredGuardrailFields + required_closeout_notes = $requiredCloseoutNotes + admin_handoff_package_index = $packageIndex + checks = $checks + failed_checks = $failed + passed = ($failed.Count -eq 0) +} + +$fullResultPath = Join-Path $repoRoot $ResultPath +$resultDir = Split-Path -Parent $fullResultPath +if ($resultDir) { New-Item -ItemType Directory -Force -Path $resultDir | Out-Null } +$result | ConvertTo-Json -Depth 100 | Set-Content -Encoding UTF8 -Path $fullResultPath + +if (-not $result.passed) { + throw "C19Z72 remote workspace real-adapter admin handoff package index compatibility smoke failed. Result: $fullResultPath Failed: $($failed -join ', ')" +} + +Write-Host "C19Z72 remote workspace real-adapter admin handoff package index compatibility smoke passed. Result: $fullResultPath" +$result diff --git a/scripts/fabric/c19z73-remote-workspace-real-adapter-runtime-gate-phase-boundary-smoke.ps1 b/scripts/fabric/c19z73-remote-workspace-real-adapter-runtime-gate-phase-boundary-smoke.ps1 new file mode 100644 index 0000000..d9b0546 --- /dev/null +++ b/scripts/fabric/c19z73-remote-workspace-real-adapter-runtime-gate-phase-boundary-smoke.ps1 @@ -0,0 +1,164 @@ +param( + [string]$ApiBaseUrl = "http://192.168.200.61:18121/api/v1", + [string]$ClusterID = "cfc0743d-d960-49fb-9de8-96e063d5e4aa", + [string]$ActorUserID = "f67d943f-5397-4b3a-a229-695fe67ad700", + [string]$RequestedNodeName = "test-1", + [string]$DefaultNodeName = "test-2", + [string]$MatrixNodeName = "test-1", + [string]$ResultPath = "artifacts\c19z73-remote-workspace-real-adapter-runtime-gate-phase-boundary-smoke-result.json" +) + +Set-StrictMode -Version Latest +$ErrorActionPreference = "Stop" + +$repoRoot = (Resolve-Path (Join-Path $PSScriptRoot "..\..")).ProviderPath +$sourceResultPath = "artifacts\c19z73-remote-workspace-real-adapter-runtime-gate-phase-boundary-source-result.json" +$requiredBoundaryFields = @( + "schema_version", + "source_package_index_schema", + "previous_package_status", + "previous_package_range", + "phase_boundary_marker", + "next_phase_name", + "next_phase_status", + "real_runtime_gate_state", + "activation_policy", + "runtime_effect", + "operator_default_action", + "required_preflight_steps", + "guardrail_summary" +) +$requiredPreflightSteps = @( + "explicit_operator_gate_enablement", + "real_adapter_binary_path_validated", + "service_account_and_permissions_validated", + "process_supervisor_limits_validated", + "health_probe_signal_contract_validated", + "payload_forwarding_gate_validated" +) +$requiredGuardrailFields = @("activation_blocked", "process_start_allowed", "health_probe_enabled", "payload_traffic", "allows_process_start", "allows_payload_traffic") + +function Get-PropertyValue { + param([object]$Item, [string]$Name, [object]$Default = $null) + if ($null -eq $Item) { return $Default } + if ($Item -is [System.Collections.IDictionary]) { + if ($Item.Contains($Name)) { return $Item[$Name] } + return $Default + } + $property = $Item.PSObject.Properties[$Name] + if ($null -eq $property) { return $Default } + return $property.Value +} + +function Test-ObjectHasFields { + param([object]$Item, [string[]]$Fields) + if ($null -eq $Item) { return $false } + foreach ($field in $Fields) { + if ($Item -is [System.Collections.IDictionary]) { + if (-not $Item.Contains($field)) { return $false } + continue + } + if ($null -eq $Item.PSObject.Properties[$field]) { return $false } + } + return $true +} + +function Test-ArrayContainsAll { + param([object[]]$Actual, [string[]]$Required) + $values = @($Actual | ForEach-Object { [string]$_ }) + foreach ($item in $Required) { + if ($values -notcontains $item) { return $false } + } + return $true +} + +& powershell -ExecutionPolicy Bypass -File (Join-Path $PSScriptRoot "c19z72-remote-workspace-real-adapter-admin-handoff-package-index-compatibility-smoke.ps1") ` + -ApiBaseUrl $ApiBaseUrl ` + -ClusterID $ClusterID ` + -ActorUserID $ActorUserID ` + -RequestedNodeName $RequestedNodeName ` + -DefaultNodeName $DefaultNodeName ` + -MatrixNodeName $MatrixNodeName ` + -ResultPath $sourceResultPath | Out-Null + +$sourceFile = Join-Path $repoRoot $sourceResultPath +$sourceResult = Get-Content -Raw -Path $sourceFile | ConvertFrom-Json +$packageIndex = Get-PropertyValue -Item $sourceResult -Name "admin_handoff_package_index" -Default $null +$guardrails = Get-PropertyValue -Item $packageIndex -Name "guardrail_summary" -Default $null + +$phaseBoundary = [ordered]@{ + schema_version = "rap.remote_workspace_real_adapter_runtime_gate_phase_boundary.v1" + source_package_index_schema = Get-PropertyValue -Item $packageIndex -Name "schema_version" -Default $null + previous_package_status = Get-PropertyValue -Item $packageIndex -Name "package_status" -Default $null + previous_package_range = Get-PropertyValue -Item $packageIndex -Name "covered_stage_range" -Default $null + phase_boundary_marker = "c19z73_real_adapter_runtime_gate_phase_boundary_contract_only" + next_phase_name = "real_adapter_runtime_gate_preflight" + next_phase_status = "design_only_not_enabled" + real_runtime_gate_state = "blocked" + activation_policy = "explicit_operator_enablement_required" + runtime_effect = Get-PropertyValue -Item $packageIndex -Name "runtime_effect" -Default $null + operator_default_action = "keep_real_adapter_disabled" + required_preflight_steps = $requiredPreflightSteps + guardrail_summary = $guardrails +} + +$boundaryFieldsCompatible = Test-ObjectHasFields -Item $phaseBoundary -Fields $requiredBoundaryFields +$boundaryValuesCompatible = ( + [string](Get-PropertyValue -Item $phaseBoundary -Name "schema_version" -Default "") -eq "rap.remote_workspace_real_adapter_runtime_gate_phase_boundary.v1" -and + [string](Get-PropertyValue -Item $phaseBoundary -Name "source_package_index_schema" -Default "") -eq "rap.remote_workspace_real_adapter_admin_handoff_package_index.v1" -and + [string](Get-PropertyValue -Item $phaseBoundary -Name "previous_package_status" -Default "") -eq "closed_contract_only" -and + [string](Get-PropertyValue -Item $phaseBoundary -Name "previous_package_range" -Default "") -eq "C19Z54-C19Z70" -and + [string](Get-PropertyValue -Item $phaseBoundary -Name "phase_boundary_marker" -Default "") -eq "c19z73_real_adapter_runtime_gate_phase_boundary_contract_only" -and + [string](Get-PropertyValue -Item $phaseBoundary -Name "next_phase_name" -Default "") -eq "real_adapter_runtime_gate_preflight" -and + [string](Get-PropertyValue -Item $phaseBoundary -Name "next_phase_status" -Default "") -eq "design_only_not_enabled" -and + [string](Get-PropertyValue -Item $phaseBoundary -Name "real_runtime_gate_state" -Default "") -eq "blocked" -and + [string](Get-PropertyValue -Item $phaseBoundary -Name "activation_policy" -Default "") -eq "explicit_operator_enablement_required" -and + [string](Get-PropertyValue -Item $phaseBoundary -Name "runtime_effect" -Default "") -eq "contract_only_no_runtime_enablement" -and + [string](Get-PropertyValue -Item $phaseBoundary -Name "operator_default_action" -Default "") -eq "keep_real_adapter_disabled" +) +$preflightStepsCompatible = Test-ArrayContainsAll -Actual @(Get-PropertyValue -Item $phaseBoundary -Name "required_preflight_steps" -Default @()) -Required $requiredPreflightSteps +$guardrailsCompatible = ( + (Test-ObjectHasFields -Item $guardrails -Fields $requiredGuardrailFields) -and + [bool](Get-PropertyValue -Item $guardrails -Name "activation_blocked" -Default $false) -and + -not [bool](Get-PropertyValue -Item $guardrails -Name "process_start_allowed" -Default $true) -and + -not [bool](Get-PropertyValue -Item $guardrails -Name "health_probe_enabled" -Default $true) -and + [string](Get-PropertyValue -Item $guardrails -Name "payload_traffic" -Default "") -eq "none" -and + -not [bool](Get-PropertyValue -Item $guardrails -Name "allows_process_start" -Default $true) -and + -not [bool](Get-PropertyValue -Item $guardrails -Name "allows_payload_traffic" -Default $true) +) + +$checks = [ordered]@{ + source_smoke_passed = ([bool]$sourceResult.passed) + source_schema_expected = ([string]$sourceResult.schema_version -eq "c19z72.remote_workspace_real_adapter_admin_handoff_package_index_compatibility_smoke.v1") + package_index_present = ($null -ne $packageIndex) + boundary_fields_compatible = $boundaryFieldsCompatible + boundary_values_compatible = $boundaryValuesCompatible + preflight_steps_compatible = $preflightStepsCompatible + guardrails_compatible = $guardrailsCompatible +} +$failed = @($checks.GetEnumerator() | Where-Object { -not $_.Value } | ForEach-Object { $_.Key }) + +$result = [ordered]@{ + schema_version = "c19z73.remote_workspace_real_adapter_runtime_gate_phase_boundary_smoke.v1" + source_result_path = $sourceFile + cluster_id = $ClusterID + required_boundary_fields = $requiredBoundaryFields + required_preflight_steps = $requiredPreflightSteps + required_guardrail_fields = $requiredGuardrailFields + runtime_gate_phase_boundary = $phaseBoundary + checks = $checks + failed_checks = $failed + passed = ($failed.Count -eq 0) +} + +$fullResultPath = Join-Path $repoRoot $ResultPath +$resultDir = Split-Path -Parent $fullResultPath +if ($resultDir) { New-Item -ItemType Directory -Force -Path $resultDir | Out-Null } +$result | ConvertTo-Json -Depth 100 | Set-Content -Encoding UTF8 -Path $fullResultPath + +if (-not $result.passed) { + throw "C19Z73 remote workspace real-adapter runtime gate phase boundary smoke failed. Result: $fullResultPath Failed: $($failed -join ', ')" +} + +Write-Host "C19Z73 remote workspace real-adapter runtime gate phase boundary smoke passed. Result: $fullResultPath" +$result diff --git a/scripts/fabric/c19z74-remote-workspace-real-adapter-runtime-gate-phase-boundary-compatibility-smoke.ps1 b/scripts/fabric/c19z74-remote-workspace-real-adapter-runtime-gate-phase-boundary-compatibility-smoke.ps1 new file mode 100644 index 0000000..e3f8ede --- /dev/null +++ b/scripts/fabric/c19z74-remote-workspace-real-adapter-runtime-gate-phase-boundary-compatibility-smoke.ps1 @@ -0,0 +1,149 @@ +param( + [string]$ApiBaseUrl = "http://192.168.200.61:18121/api/v1", + [string]$ClusterID = "cfc0743d-d960-49fb-9de8-96e063d5e4aa", + [string]$ActorUserID = "f67d943f-5397-4b3a-a229-695fe67ad700", + [string]$RequestedNodeName = "test-1", + [string]$DefaultNodeName = "test-2", + [string]$MatrixNodeName = "test-1", + [string]$ResultPath = "artifacts\c19z74-remote-workspace-real-adapter-runtime-gate-phase-boundary-compatibility-smoke-result.json" +) + +Set-StrictMode -Version Latest +$ErrorActionPreference = "Stop" + +$repoRoot = (Resolve-Path (Join-Path $PSScriptRoot "..\..")).ProviderPath +$sourceResultPath = "artifacts\c19z74-remote-workspace-real-adapter-runtime-gate-phase-boundary-source-result.json" +$requiredBoundaryFields = @( + "schema_version", + "source_package_index_schema", + "previous_package_status", + "previous_package_range", + "phase_boundary_marker", + "next_phase_name", + "next_phase_status", + "real_runtime_gate_state", + "activation_policy", + "runtime_effect", + "operator_default_action", + "required_preflight_steps", + "guardrail_summary" +) +$requiredPreflightSteps = @( + "explicit_operator_gate_enablement", + "real_adapter_binary_path_validated", + "service_account_and_permissions_validated", + "process_supervisor_limits_validated", + "health_probe_signal_contract_validated", + "payload_forwarding_gate_validated" +) +$requiredGuardrailFields = @("activation_blocked", "process_start_allowed", "health_probe_enabled", "payload_traffic", "allows_process_start", "allows_payload_traffic") + +function Get-PropertyValue { + param([object]$Item, [string]$Name, [object]$Default = $null) + if ($null -eq $Item) { return $Default } + if ($Item -is [System.Collections.IDictionary]) { + if ($Item.Contains($Name)) { return $Item[$Name] } + return $Default + } + $property = $Item.PSObject.Properties[$Name] + if ($null -eq $property) { return $Default } + return $property.Value +} + +function Test-ObjectHasFields { + param([object]$Item, [string[]]$Fields) + if ($null -eq $Item) { return $false } + foreach ($field in $Fields) { + if ($Item -is [System.Collections.IDictionary]) { + if (-not $Item.Contains($field)) { return $false } + continue + } + if ($null -eq $Item.PSObject.Properties[$field]) { return $false } + } + return $true +} + +function Test-ArrayContainsAll { + param([object[]]$Actual, [string[]]$Required) + $values = @($Actual | ForEach-Object { [string]$_ }) + foreach ($item in $Required) { + if ($values -notcontains $item) { return $false } + } + return $true +} + +& powershell -ExecutionPolicy Bypass -File (Join-Path $PSScriptRoot "c19z73-remote-workspace-real-adapter-runtime-gate-phase-boundary-smoke.ps1") ` + -ApiBaseUrl $ApiBaseUrl ` + -ClusterID $ClusterID ` + -ActorUserID $ActorUserID ` + -RequestedNodeName $RequestedNodeName ` + -DefaultNodeName $DefaultNodeName ` + -MatrixNodeName $MatrixNodeName ` + -ResultPath $sourceResultPath | Out-Null + +$sourceFile = Join-Path $repoRoot $sourceResultPath +$sourceResult = Get-Content -Raw -Path $sourceFile | ConvertFrom-Json +$boundary = Get-PropertyValue -Item $sourceResult -Name "runtime_gate_phase_boundary" -Default $null +$guardrails = Get-PropertyValue -Item $boundary -Name "guardrail_summary" -Default $null +$preflightSteps = @(Get-PropertyValue -Item $boundary -Name "required_preflight_steps" -Default @()) + +$boundaryFieldsCompatible = Test-ObjectHasFields -Item $boundary -Fields $requiredBoundaryFields +$boundaryValuesCompatible = ( + [string](Get-PropertyValue -Item $boundary -Name "schema_version" -Default "") -eq "rap.remote_workspace_real_adapter_runtime_gate_phase_boundary.v1" -and + [string](Get-PropertyValue -Item $boundary -Name "source_package_index_schema" -Default "") -eq "rap.remote_workspace_real_adapter_admin_handoff_package_index.v1" -and + [string](Get-PropertyValue -Item $boundary -Name "previous_package_status" -Default "") -eq "closed_contract_only" -and + [string](Get-PropertyValue -Item $boundary -Name "previous_package_range" -Default "") -eq "C19Z54-C19Z70" -and + [string](Get-PropertyValue -Item $boundary -Name "phase_boundary_marker" -Default "") -eq "c19z73_real_adapter_runtime_gate_phase_boundary_contract_only" -and + [string](Get-PropertyValue -Item $boundary -Name "next_phase_name" -Default "") -eq "real_adapter_runtime_gate_preflight" -and + [string](Get-PropertyValue -Item $boundary -Name "next_phase_status" -Default "") -eq "design_only_not_enabled" -and + [string](Get-PropertyValue -Item $boundary -Name "real_runtime_gate_state" -Default "") -eq "blocked" -and + [string](Get-PropertyValue -Item $boundary -Name "activation_policy" -Default "") -eq "explicit_operator_enablement_required" -and + [string](Get-PropertyValue -Item $boundary -Name "runtime_effect" -Default "") -eq "contract_only_no_runtime_enablement" -and + [string](Get-PropertyValue -Item $boundary -Name "operator_default_action" -Default "") -eq "keep_real_adapter_disabled" +) +$preflightStepsCompatible = Test-ArrayContainsAll -Actual $preflightSteps -Required $requiredPreflightSteps +$guardrailsCompatible = ( + (Test-ObjectHasFields -Item $guardrails -Fields $requiredGuardrailFields) -and + [bool](Get-PropertyValue -Item $guardrails -Name "activation_blocked" -Default $false) -and + -not [bool](Get-PropertyValue -Item $guardrails -Name "process_start_allowed" -Default $true) -and + -not [bool](Get-PropertyValue -Item $guardrails -Name "health_probe_enabled" -Default $true) -and + [string](Get-PropertyValue -Item $guardrails -Name "payload_traffic" -Default "") -eq "none" -and + -not [bool](Get-PropertyValue -Item $guardrails -Name "allows_process_start" -Default $true) -and + -not [bool](Get-PropertyValue -Item $guardrails -Name "allows_payload_traffic" -Default $true) +) + +$checks = [ordered]@{ + source_smoke_passed = ([bool]$sourceResult.passed) + source_schema_expected = ([string]$sourceResult.schema_version -eq "c19z73.remote_workspace_real_adapter_runtime_gate_phase_boundary_smoke.v1") + phase_boundary_present = ($null -ne $boundary) + boundary_fields_compatible = $boundaryFieldsCompatible + boundary_values_compatible = $boundaryValuesCompatible + preflight_steps_compatible = $preflightStepsCompatible + guardrails_compatible = $guardrailsCompatible +} +$failed = @($checks.GetEnumerator() | Where-Object { -not $_.Value } | ForEach-Object { $_.Key }) + +$result = [ordered]@{ + schema_version = "c19z74.remote_workspace_real_adapter_runtime_gate_phase_boundary_compatibility_smoke.v1" + source_result_path = $sourceFile + cluster_id = $ClusterID + required_boundary_fields = $requiredBoundaryFields + required_preflight_steps = $requiredPreflightSteps + required_guardrail_fields = $requiredGuardrailFields + runtime_gate_phase_boundary = $boundary + checks = $checks + failed_checks = $failed + passed = ($failed.Count -eq 0) +} + +$fullResultPath = Join-Path $repoRoot $ResultPath +$resultDir = Split-Path -Parent $fullResultPath +if ($resultDir) { New-Item -ItemType Directory -Force -Path $resultDir | Out-Null } +$result | ConvertTo-Json -Depth 100 | Set-Content -Encoding UTF8 -Path $fullResultPath + +if (-not $result.passed) { + throw "C19Z74 remote workspace real-adapter runtime gate phase boundary compatibility smoke failed. Result: $fullResultPath Failed: $($failed -join ', ')" +} + +Write-Host "C19Z74 remote workspace real-adapter runtime gate phase boundary compatibility smoke passed. Result: $fullResultPath" +$result diff --git a/scripts/fabric/c19z75-remote-workspace-real-adapter-runtime-gate-preflight-checklist-smoke.ps1 b/scripts/fabric/c19z75-remote-workspace-real-adapter-runtime-gate-preflight-checklist-smoke.ps1 new file mode 100644 index 0000000..0f7cc70 --- /dev/null +++ b/scripts/fabric/c19z75-remote-workspace-real-adapter-runtime-gate-preflight-checklist-smoke.ps1 @@ -0,0 +1,194 @@ +param( + [string]$ApiBaseUrl = "http://192.168.200.61:18121/api/v1", + [string]$ClusterID = "cfc0743d-d960-49fb-9de8-96e063d5e4aa", + [string]$ActorUserID = "f67d943f-5397-4b3a-a229-695fe67ad700", + [string]$RequestedNodeName = "test-1", + [string]$DefaultNodeName = "test-2", + [string]$MatrixNodeName = "test-1", + [string]$ResultPath = "artifacts\c19z75-remote-workspace-real-adapter-runtime-gate-preflight-checklist-smoke-result.json" +) + +Set-StrictMode -Version Latest +$ErrorActionPreference = "Stop" + +$repoRoot = (Resolve-Path (Join-Path $PSScriptRoot "..\..")).ProviderPath +$sourceResultPath = "artifacts\c19z75-remote-workspace-real-adapter-runtime-gate-preflight-checklist-source-result.json" +$requiredChecklistFields = @( + "schema_version", + "source_phase_boundary_schema", + "preflight_status", + "runtime_gate_state", + "activation_policy", + "operator_default_action", + "required_item_count", + "satisfied_item_count", + "blocked_item_count", + "allows_process_start", + "allows_payload_traffic", + "items", + "guardrail_summary" +) +$requiredItemFields = @("key", "title", "status", "required", "blocks_runtime_gate", "evidence") +$requiredPreflightKeys = @( + "explicit_operator_gate_enablement", + "real_adapter_binary_path_validated", + "service_account_and_permissions_validated", + "process_supervisor_limits_validated", + "health_probe_signal_contract_validated", + "payload_forwarding_gate_validated" +) +$requiredGuardrailFields = @("activation_blocked", "process_start_allowed", "health_probe_enabled", "payload_traffic", "allows_process_start", "allows_payload_traffic") + +function Get-PropertyValue { + param([object]$Item, [string]$Name, [object]$Default = $null) + if ($null -eq $Item) { return $Default } + if ($Item -is [System.Collections.IDictionary]) { + if ($Item.Contains($Name)) { return $Item[$Name] } + return $Default + } + $property = $Item.PSObject.Properties[$Name] + if ($null -eq $property) { return $Default } + return $property.Value +} + +function Test-ObjectHasFields { + param([object]$Item, [string[]]$Fields) + if ($null -eq $Item) { return $false } + foreach ($field in $Fields) { + if ($Item -is [System.Collections.IDictionary]) { + if (-not $Item.Contains($field)) { return $false } + continue + } + if ($null -eq $Item.PSObject.Properties[$field]) { return $false } + } + return $true +} + +function Test-ArrayContainsAll { + param([object[]]$Actual, [string[]]$Required) + $values = @($Actual | ForEach-Object { [string]$_ }) + foreach ($item in $Required) { + if ($values -notcontains $item) { return $false } + } + return $true +} + +& powershell -ExecutionPolicy Bypass -File (Join-Path $PSScriptRoot "c19z74-remote-workspace-real-adapter-runtime-gate-phase-boundary-compatibility-smoke.ps1") ` + -ApiBaseUrl $ApiBaseUrl ` + -ClusterID $ClusterID ` + -ActorUserID $ActorUserID ` + -RequestedNodeName $RequestedNodeName ` + -DefaultNodeName $DefaultNodeName ` + -MatrixNodeName $MatrixNodeName ` + -ResultPath $sourceResultPath | Out-Null + +$sourceFile = Join-Path $repoRoot $sourceResultPath +$sourceResult = Get-Content -Raw -Path $sourceFile | ConvertFrom-Json +$boundary = Get-PropertyValue -Item $sourceResult -Name "runtime_gate_phase_boundary" -Default $null +$guardrails = Get-PropertyValue -Item $boundary -Name "guardrail_summary" -Default $null + +$itemTitles = [ordered]@{ + explicit_operator_gate_enablement = "Explicit operator gate enablement" + real_adapter_binary_path_validated = "Real adapter binary path validation" + service_account_and_permissions_validated = "Service account and permissions validation" + process_supervisor_limits_validated = "Process supervisor limits validation" + health_probe_signal_contract_validated = "Health probe signal contract validation" + payload_forwarding_gate_validated = "Payload forwarding gate validation" +} +$items = @($requiredPreflightKeys | ForEach-Object { + [ordered]@{ + key = $_ + title = $itemTitles[$_] + status = "not_satisfied" + required = $true + blocks_runtime_gate = $true + evidence = "contract_only_preflight_not_provided" + } +}) + +$checklist = [ordered]@{ + schema_version = "rap.remote_workspace_real_adapter_runtime_gate_preflight_checklist.v1" + source_phase_boundary_schema = Get-PropertyValue -Item $boundary -Name "schema_version" -Default $null + preflight_status = "blocked_required_items_missing" + runtime_gate_state = Get-PropertyValue -Item $boundary -Name "real_runtime_gate_state" -Default $null + activation_policy = Get-PropertyValue -Item $boundary -Name "activation_policy" -Default $null + operator_default_action = Get-PropertyValue -Item $boundary -Name "operator_default_action" -Default $null + required_item_count = $requiredPreflightKeys.Count + satisfied_item_count = 0 + blocked_item_count = $requiredPreflightKeys.Count + allows_process_start = $false + allows_payload_traffic = $false + items = $items + guardrail_summary = $guardrails +} + +$checklistFieldsCompatible = Test-ObjectHasFields -Item $checklist -Fields $requiredChecklistFields +$itemFieldsCompatible = (@($items | Where-Object { -not (Test-ObjectHasFields -Item $_ -Fields $requiredItemFields) }).Count -eq 0) +$itemKeysCompatible = Test-ArrayContainsAll -Actual @($items | ForEach-Object { Get-PropertyValue -Item $_ -Name "key" -Default "" }) -Required $requiredPreflightKeys +$itemValuesCompatible = (@($items | Where-Object { + [string](Get-PropertyValue -Item $_ -Name "status" -Default "") -ne "not_satisfied" -or + -not [bool](Get-PropertyValue -Item $_ -Name "required" -Default $false) -or + -not [bool](Get-PropertyValue -Item $_ -Name "blocks_runtime_gate" -Default $false) -or + [string](Get-PropertyValue -Item $_ -Name "evidence" -Default "") -ne "contract_only_preflight_not_provided" +}).Count -eq 0) +$checklistValuesCompatible = ( + [string](Get-PropertyValue -Item $checklist -Name "schema_version" -Default "") -eq "rap.remote_workspace_real_adapter_runtime_gate_preflight_checklist.v1" -and + [string](Get-PropertyValue -Item $checklist -Name "source_phase_boundary_schema" -Default "") -eq "rap.remote_workspace_real_adapter_runtime_gate_phase_boundary.v1" -and + [string](Get-PropertyValue -Item $checklist -Name "preflight_status" -Default "") -eq "blocked_required_items_missing" -and + [string](Get-PropertyValue -Item $checklist -Name "runtime_gate_state" -Default "") -eq "blocked" -and + [string](Get-PropertyValue -Item $checklist -Name "activation_policy" -Default "") -eq "explicit_operator_enablement_required" -and + [string](Get-PropertyValue -Item $checklist -Name "operator_default_action" -Default "") -eq "keep_real_adapter_disabled" -and + [int](Get-PropertyValue -Item $checklist -Name "required_item_count" -Default -1) -eq $requiredPreflightKeys.Count -and + [int](Get-PropertyValue -Item $checklist -Name "satisfied_item_count" -Default -1) -eq 0 -and + [int](Get-PropertyValue -Item $checklist -Name "blocked_item_count" -Default -1) -eq $requiredPreflightKeys.Count -and + -not [bool](Get-PropertyValue -Item $checklist -Name "allows_process_start" -Default $true) -and + -not [bool](Get-PropertyValue -Item $checklist -Name "allows_payload_traffic" -Default $true) +) +$guardrailsCompatible = ( + (Test-ObjectHasFields -Item $guardrails -Fields $requiredGuardrailFields) -and + [bool](Get-PropertyValue -Item $guardrails -Name "activation_blocked" -Default $false) -and + -not [bool](Get-PropertyValue -Item $guardrails -Name "process_start_allowed" -Default $true) -and + -not [bool](Get-PropertyValue -Item $guardrails -Name "health_probe_enabled" -Default $true) -and + [string](Get-PropertyValue -Item $guardrails -Name "payload_traffic" -Default "") -eq "none" -and + -not [bool](Get-PropertyValue -Item $guardrails -Name "allows_process_start" -Default $true) -and + -not [bool](Get-PropertyValue -Item $guardrails -Name "allows_payload_traffic" -Default $true) +) + +$checks = [ordered]@{ + source_smoke_passed = ([bool]$sourceResult.passed) + source_schema_expected = ([string]$sourceResult.schema_version -eq "c19z74.remote_workspace_real_adapter_runtime_gate_phase_boundary_compatibility_smoke.v1") + phase_boundary_present = ($null -ne $boundary) + checklist_fields_compatible = $checklistFieldsCompatible + checklist_values_compatible = $checklistValuesCompatible + item_fields_compatible = $itemFieldsCompatible + item_keys_compatible = $itemKeysCompatible + item_values_compatible = $itemValuesCompatible + guardrails_compatible = $guardrailsCompatible +} +$failed = @($checks.GetEnumerator() | Where-Object { -not $_.Value } | ForEach-Object { $_.Key }) + +$result = [ordered]@{ + schema_version = "c19z75.remote_workspace_real_adapter_runtime_gate_preflight_checklist_smoke.v1" + source_result_path = $sourceFile + cluster_id = $ClusterID + required_checklist_fields = $requiredChecklistFields + required_item_fields = $requiredItemFields + required_preflight_keys = $requiredPreflightKeys + required_guardrail_fields = $requiredGuardrailFields + runtime_gate_preflight_checklist = $checklist + checks = $checks + failed_checks = $failed + passed = ($failed.Count -eq 0) +} + +$fullResultPath = Join-Path $repoRoot $ResultPath +$resultDir = Split-Path -Parent $fullResultPath +if ($resultDir) { New-Item -ItemType Directory -Force -Path $resultDir | Out-Null } +$result | ConvertTo-Json -Depth 100 | Set-Content -Encoding UTF8 -Path $fullResultPath + +if (-not $result.passed) { + throw "C19Z75 remote workspace real-adapter runtime gate preflight checklist smoke failed. Result: $fullResultPath Failed: $($failed -join ', ')" +} + +Write-Host "C19Z75 remote workspace real-adapter runtime gate preflight checklist smoke passed. Result: $fullResultPath" +$result diff --git a/scripts/fabric/c19z76-remote-workspace-real-adapter-runtime-gate-preflight-checklist-compatibility-smoke.ps1 b/scripts/fabric/c19z76-remote-workspace-real-adapter-runtime-gate-preflight-checklist-compatibility-smoke.ps1 new file mode 100644 index 0000000..ba76e8e --- /dev/null +++ b/scripts/fabric/c19z76-remote-workspace-real-adapter-runtime-gate-preflight-checklist-compatibility-smoke.ps1 @@ -0,0 +1,160 @@ +param( + [string]$ApiBaseUrl = "http://192.168.200.61:18121/api/v1", + [string]$ClusterID = "cfc0743d-d960-49fb-9de8-96e063d5e4aa", + [string]$ActorUserID = "f67d943f-5397-4b3a-a229-695fe67ad700", + [string]$RequestedNodeName = "test-1", + [string]$DefaultNodeName = "test-2", + [string]$MatrixNodeName = "test-1", + [string]$ResultPath = "artifacts\c19z76-remote-workspace-real-adapter-runtime-gate-preflight-checklist-compatibility-smoke-result.json" +) + +Set-StrictMode -Version Latest +$ErrorActionPreference = "Stop" + +$repoRoot = (Resolve-Path (Join-Path $PSScriptRoot "..\..")).ProviderPath +$sourceResultPath = "artifacts\c19z76-remote-workspace-real-adapter-runtime-gate-preflight-checklist-source-result.json" +$requiredChecklistFields = @( + "schema_version", + "source_phase_boundary_schema", + "preflight_status", + "runtime_gate_state", + "activation_policy", + "operator_default_action", + "required_item_count", + "satisfied_item_count", + "blocked_item_count", + "allows_process_start", + "allows_payload_traffic", + "items", + "guardrail_summary" +) +$requiredItemFields = @("key", "title", "status", "required", "blocks_runtime_gate", "evidence") +$requiredPreflightKeys = @( + "explicit_operator_gate_enablement", + "real_adapter_binary_path_validated", + "service_account_and_permissions_validated", + "process_supervisor_limits_validated", + "health_probe_signal_contract_validated", + "payload_forwarding_gate_validated" +) +$requiredGuardrailFields = @("activation_blocked", "process_start_allowed", "health_probe_enabled", "payload_traffic", "allows_process_start", "allows_payload_traffic") + +function Get-PropertyValue { + param([object]$Item, [string]$Name, [object]$Default = $null) + if ($null -eq $Item) { return $Default } + if ($Item -is [System.Collections.IDictionary]) { + if ($Item.Contains($Name)) { return $Item[$Name] } + return $Default + } + $property = $Item.PSObject.Properties[$Name] + if ($null -eq $property) { return $Default } + return $property.Value +} + +function Test-ObjectHasFields { + param([object]$Item, [string[]]$Fields) + if ($null -eq $Item) { return $false } + foreach ($field in $Fields) { + if ($Item -is [System.Collections.IDictionary]) { + if (-not $Item.Contains($field)) { return $false } + continue + } + if ($null -eq $Item.PSObject.Properties[$field]) { return $false } + } + return $true +} + +function Test-ArrayContainsAll { + param([object[]]$Actual, [string[]]$Required) + $values = @($Actual | ForEach-Object { [string]$_ }) + foreach ($item in $Required) { + if ($values -notcontains $item) { return $false } + } + return $true +} + +& powershell -ExecutionPolicy Bypass -File (Join-Path $PSScriptRoot "c19z75-remote-workspace-real-adapter-runtime-gate-preflight-checklist-smoke.ps1") ` + -ApiBaseUrl $ApiBaseUrl ` + -ClusterID $ClusterID ` + -ActorUserID $ActorUserID ` + -RequestedNodeName $RequestedNodeName ` + -DefaultNodeName $DefaultNodeName ` + -MatrixNodeName $MatrixNodeName ` + -ResultPath $sourceResultPath | Out-Null + +$sourceFile = Join-Path $repoRoot $sourceResultPath +$sourceResult = Get-Content -Raw -Path $sourceFile | ConvertFrom-Json +$checklist = Get-PropertyValue -Item $sourceResult -Name "runtime_gate_preflight_checklist" -Default $null +$items = @(Get-PropertyValue -Item $checklist -Name "items" -Default @()) +$guardrails = Get-PropertyValue -Item $checklist -Name "guardrail_summary" -Default $null + +$checklistFieldsCompatible = Test-ObjectHasFields -Item $checklist -Fields $requiredChecklistFields +$itemFieldsCompatible = (@($items | Where-Object { -not (Test-ObjectHasFields -Item $_ -Fields $requiredItemFields) }).Count -eq 0) +$itemKeysCompatible = Test-ArrayContainsAll -Actual @($items | ForEach-Object { Get-PropertyValue -Item $_ -Name "key" -Default "" }) -Required $requiredPreflightKeys +$itemValuesCompatible = (@($items | Where-Object { + [string](Get-PropertyValue -Item $_ -Name "status" -Default "") -ne "not_satisfied" -or + -not [bool](Get-PropertyValue -Item $_ -Name "required" -Default $false) -or + -not [bool](Get-PropertyValue -Item $_ -Name "blocks_runtime_gate" -Default $false) -or + [string](Get-PropertyValue -Item $_ -Name "evidence" -Default "") -ne "contract_only_preflight_not_provided" +}).Count -eq 0) +$checklistValuesCompatible = ( + [string](Get-PropertyValue -Item $checklist -Name "schema_version" -Default "") -eq "rap.remote_workspace_real_adapter_runtime_gate_preflight_checklist.v1" -and + [string](Get-PropertyValue -Item $checklist -Name "source_phase_boundary_schema" -Default "") -eq "rap.remote_workspace_real_adapter_runtime_gate_phase_boundary.v1" -and + [string](Get-PropertyValue -Item $checklist -Name "preflight_status" -Default "") -eq "blocked_required_items_missing" -and + [string](Get-PropertyValue -Item $checklist -Name "runtime_gate_state" -Default "") -eq "blocked" -and + [string](Get-PropertyValue -Item $checklist -Name "activation_policy" -Default "") -eq "explicit_operator_enablement_required" -and + [string](Get-PropertyValue -Item $checklist -Name "operator_default_action" -Default "") -eq "keep_real_adapter_disabled" -and + [int](Get-PropertyValue -Item $checklist -Name "required_item_count" -Default -1) -eq $requiredPreflightKeys.Count -and + [int](Get-PropertyValue -Item $checklist -Name "satisfied_item_count" -Default -1) -eq 0 -and + [int](Get-PropertyValue -Item $checklist -Name "blocked_item_count" -Default -1) -eq $requiredPreflightKeys.Count -and + -not [bool](Get-PropertyValue -Item $checklist -Name "allows_process_start" -Default $true) -and + -not [bool](Get-PropertyValue -Item $checklist -Name "allows_payload_traffic" -Default $true) +) +$guardrailsCompatible = ( + (Test-ObjectHasFields -Item $guardrails -Fields $requiredGuardrailFields) -and + [bool](Get-PropertyValue -Item $guardrails -Name "activation_blocked" -Default $false) -and + -not [bool](Get-PropertyValue -Item $guardrails -Name "process_start_allowed" -Default $true) -and + -not [bool](Get-PropertyValue -Item $guardrails -Name "health_probe_enabled" -Default $true) -and + [string](Get-PropertyValue -Item $guardrails -Name "payload_traffic" -Default "") -eq "none" -and + -not [bool](Get-PropertyValue -Item $guardrails -Name "allows_process_start" -Default $true) -and + -not [bool](Get-PropertyValue -Item $guardrails -Name "allows_payload_traffic" -Default $true) +) + +$checks = [ordered]@{ + source_smoke_passed = ([bool]$sourceResult.passed) + source_schema_expected = ([string]$sourceResult.schema_version -eq "c19z75.remote_workspace_real_adapter_runtime_gate_preflight_checklist_smoke.v1") + checklist_present = ($null -ne $checklist) + checklist_fields_compatible = $checklistFieldsCompatible + checklist_values_compatible = $checklistValuesCompatible + item_fields_compatible = $itemFieldsCompatible + item_keys_compatible = $itemKeysCompatible + item_values_compatible = $itemValuesCompatible + guardrails_compatible = $guardrailsCompatible +} +$failed = @($checks.GetEnumerator() | Where-Object { -not $_.Value } | ForEach-Object { $_.Key }) + +$result = [ordered]@{ + schema_version = "c19z76.remote_workspace_real_adapter_runtime_gate_preflight_checklist_compatibility_smoke.v1" + source_result_path = $sourceFile + cluster_id = $ClusterID + required_checklist_fields = $requiredChecklistFields + required_item_fields = $requiredItemFields + required_preflight_keys = $requiredPreflightKeys + required_guardrail_fields = $requiredGuardrailFields + runtime_gate_preflight_checklist = $checklist + checks = $checks + failed_checks = $failed + passed = ($failed.Count -eq 0) +} + +$fullResultPath = Join-Path $repoRoot $ResultPath +$resultDir = Split-Path -Parent $fullResultPath +if ($resultDir) { New-Item -ItemType Directory -Force -Path $resultDir | Out-Null } +$result | ConvertTo-Json -Depth 100 | Set-Content -Encoding UTF8 -Path $fullResultPath + +if (-not $result.passed) { + throw "C19Z76 remote workspace real-adapter runtime gate preflight checklist compatibility smoke failed. Result: $fullResultPath Failed: $($failed -join ', ')" +} + +Write-Host "C19Z76 remote workspace real-adapter runtime gate preflight checklist compatibility smoke passed. Result: $fullResultPath" +$result diff --git a/scripts/fabric/c19z77-remote-workspace-real-adapter-runtime-gate-preflight-status-summary-smoke.ps1 b/scripts/fabric/c19z77-remote-workspace-real-adapter-runtime-gate-preflight-status-summary-smoke.ps1 new file mode 100644 index 0000000..0fe5932 --- /dev/null +++ b/scripts/fabric/c19z77-remote-workspace-real-adapter-runtime-gate-preflight-status-summary-smoke.ps1 @@ -0,0 +1,173 @@ +param( + [string]$ApiBaseUrl = "http://192.168.200.61:18121/api/v1", + [string]$ClusterID = "cfc0743d-d960-49fb-9de8-96e063d5e4aa", + [string]$ActorUserID = "f67d943f-5397-4b3a-a229-695fe67ad700", + [string]$RequestedNodeName = "test-1", + [string]$DefaultNodeName = "test-2", + [string]$MatrixNodeName = "test-1", + [string]$ResultPath = "artifacts\c19z77-remote-workspace-real-adapter-runtime-gate-preflight-status-summary-smoke-result.json" +) + +Set-StrictMode -Version Latest +$ErrorActionPreference = "Stop" + +$repoRoot = (Resolve-Path (Join-Path $PSScriptRoot "..\..")).ProviderPath +$sourceResultPath = "artifacts\c19z77-remote-workspace-real-adapter-runtime-gate-preflight-status-summary-source-result.json" +$requiredSummaryFields = @( + "schema_version", + "source_checklist_schema", + "summary_status", + "runtime_gate_state", + "required_item_count", + "satisfied_item_count", + "blocked_item_count", + "not_satisfied_item_count", + "blocking_item_keys", + "operator_default_action", + "allows_process_start", + "allows_payload_traffic", + "guardrail_summary" +) +$requiredPreflightKeys = @( + "explicit_operator_gate_enablement", + "real_adapter_binary_path_validated", + "service_account_and_permissions_validated", + "process_supervisor_limits_validated", + "health_probe_signal_contract_validated", + "payload_forwarding_gate_validated" +) +$requiredGuardrailFields = @("activation_blocked", "process_start_allowed", "health_probe_enabled", "payload_traffic", "allows_process_start", "allows_payload_traffic") + +function Get-PropertyValue { + param([object]$Item, [string]$Name, [object]$Default = $null) + if ($null -eq $Item) { return $Default } + if ($Item -is [System.Collections.IDictionary]) { + if ($Item.Contains($Name)) { return $Item[$Name] } + return $Default + } + $property = $Item.PSObject.Properties[$Name] + if ($null -eq $property) { return $Default } + return $property.Value +} + +function Test-ObjectHasFields { + param([object]$Item, [string[]]$Fields) + if ($null -eq $Item) { return $false } + foreach ($field in $Fields) { + if ($Item -is [System.Collections.IDictionary]) { + if (-not $Item.Contains($field)) { return $false } + continue + } + if ($null -eq $Item.PSObject.Properties[$field]) { return $false } + } + return $true +} + +function Test-ArrayContainsAll { + param([object[]]$Actual, [string[]]$Required) + $values = @($Actual | ForEach-Object { [string]$_ }) + foreach ($item in $Required) { + if ($values -notcontains $item) { return $false } + } + return $true +} + +& powershell -ExecutionPolicy Bypass -File (Join-Path $PSScriptRoot "c19z76-remote-workspace-real-adapter-runtime-gate-preflight-checklist-compatibility-smoke.ps1") ` + -ApiBaseUrl $ApiBaseUrl ` + -ClusterID $ClusterID ` + -ActorUserID $ActorUserID ` + -RequestedNodeName $RequestedNodeName ` + -DefaultNodeName $DefaultNodeName ` + -MatrixNodeName $MatrixNodeName ` + -ResultPath $sourceResultPath | Out-Null + +$sourceFile = Join-Path $repoRoot $sourceResultPath +$sourceResult = Get-Content -Raw -Path $sourceFile | ConvertFrom-Json +$checklist = Get-PropertyValue -Item $sourceResult -Name "runtime_gate_preflight_checklist" -Default $null +$items = @(Get-PropertyValue -Item $checklist -Name "items" -Default @()) +$guardrails = Get-PropertyValue -Item $checklist -Name "guardrail_summary" -Default $null +$blockingKeys = @($items | Where-Object { + [bool](Get-PropertyValue -Item $_ -Name "blocks_runtime_gate" -Default $false) +} | ForEach-Object { + Get-PropertyValue -Item $_ -Name "key" -Default "" +}) +$notSatisfiedCount = @($items | Where-Object { + [string](Get-PropertyValue -Item $_ -Name "status" -Default "") -eq "not_satisfied" +}).Count + +$summary = [ordered]@{ + schema_version = "rap.remote_workspace_real_adapter_runtime_gate_preflight_status_summary.v1" + source_checklist_schema = Get-PropertyValue -Item $checklist -Name "schema_version" -Default $null + summary_status = "blocked_all_required_items_missing" + runtime_gate_state = Get-PropertyValue -Item $checklist -Name "runtime_gate_state" -Default $null + required_item_count = Get-PropertyValue -Item $checklist -Name "required_item_count" -Default $null + satisfied_item_count = Get-PropertyValue -Item $checklist -Name "satisfied_item_count" -Default $null + blocked_item_count = Get-PropertyValue -Item $checklist -Name "blocked_item_count" -Default $null + not_satisfied_item_count = $notSatisfiedCount + blocking_item_keys = $blockingKeys + operator_default_action = Get-PropertyValue -Item $checklist -Name "operator_default_action" -Default $null + allows_process_start = Get-PropertyValue -Item $checklist -Name "allows_process_start" -Default $null + allows_payload_traffic = Get-PropertyValue -Item $checklist -Name "allows_payload_traffic" -Default $null + guardrail_summary = $guardrails +} + +$summaryFieldsCompatible = Test-ObjectHasFields -Item $summary -Fields $requiredSummaryFields +$summaryValuesCompatible = ( + [string](Get-PropertyValue -Item $summary -Name "schema_version" -Default "") -eq "rap.remote_workspace_real_adapter_runtime_gate_preflight_status_summary.v1" -and + [string](Get-PropertyValue -Item $summary -Name "source_checklist_schema" -Default "") -eq "rap.remote_workspace_real_adapter_runtime_gate_preflight_checklist.v1" -and + [string](Get-PropertyValue -Item $summary -Name "summary_status" -Default "") -eq "blocked_all_required_items_missing" -and + [string](Get-PropertyValue -Item $summary -Name "runtime_gate_state" -Default "") -eq "blocked" -and + [int](Get-PropertyValue -Item $summary -Name "required_item_count" -Default -1) -eq $requiredPreflightKeys.Count -and + [int](Get-PropertyValue -Item $summary -Name "satisfied_item_count" -Default -1) -eq 0 -and + [int](Get-PropertyValue -Item $summary -Name "blocked_item_count" -Default -1) -eq $requiredPreflightKeys.Count -and + [int](Get-PropertyValue -Item $summary -Name "not_satisfied_item_count" -Default -1) -eq $requiredPreflightKeys.Count -and + [string](Get-PropertyValue -Item $summary -Name "operator_default_action" -Default "") -eq "keep_real_adapter_disabled" -and + -not [bool](Get-PropertyValue -Item $summary -Name "allows_process_start" -Default $true) -and + -not [bool](Get-PropertyValue -Item $summary -Name "allows_payload_traffic" -Default $true) +) +$blockingKeysCompatible = Test-ArrayContainsAll -Actual $blockingKeys -Required $requiredPreflightKeys +$guardrailsCompatible = ( + (Test-ObjectHasFields -Item $guardrails -Fields $requiredGuardrailFields) -and + [bool](Get-PropertyValue -Item $guardrails -Name "activation_blocked" -Default $false) -and + -not [bool](Get-PropertyValue -Item $guardrails -Name "process_start_allowed" -Default $true) -and + -not [bool](Get-PropertyValue -Item $guardrails -Name "health_probe_enabled" -Default $true) -and + [string](Get-PropertyValue -Item $guardrails -Name "payload_traffic" -Default "") -eq "none" -and + -not [bool](Get-PropertyValue -Item $guardrails -Name "allows_process_start" -Default $true) -and + -not [bool](Get-PropertyValue -Item $guardrails -Name "allows_payload_traffic" -Default $true) +) + +$checks = [ordered]@{ + source_smoke_passed = ([bool]$sourceResult.passed) + source_schema_expected = ([string]$sourceResult.schema_version -eq "c19z76.remote_workspace_real_adapter_runtime_gate_preflight_checklist_compatibility_smoke.v1") + checklist_present = ($null -ne $checklist) + summary_fields_compatible = $summaryFieldsCompatible + summary_values_compatible = $summaryValuesCompatible + blocking_keys_compatible = $blockingKeysCompatible + guardrails_compatible = $guardrailsCompatible +} +$failed = @($checks.GetEnumerator() | Where-Object { -not $_.Value } | ForEach-Object { $_.Key }) + +$result = [ordered]@{ + schema_version = "c19z77.remote_workspace_real_adapter_runtime_gate_preflight_status_summary_smoke.v1" + source_result_path = $sourceFile + cluster_id = $ClusterID + required_summary_fields = $requiredSummaryFields + required_preflight_keys = $requiredPreflightKeys + required_guardrail_fields = $requiredGuardrailFields + runtime_gate_preflight_status_summary = $summary + checks = $checks + failed_checks = $failed + passed = ($failed.Count -eq 0) +} + +$fullResultPath = Join-Path $repoRoot $ResultPath +$resultDir = Split-Path -Parent $fullResultPath +if ($resultDir) { New-Item -ItemType Directory -Force -Path $resultDir | Out-Null } +$result | ConvertTo-Json -Depth 100 | Set-Content -Encoding UTF8 -Path $fullResultPath + +if (-not $result.passed) { + throw "C19Z77 remote workspace real-adapter runtime gate preflight status summary smoke failed. Result: $fullResultPath Failed: $($failed -join ', ')" +} + +Write-Host "C19Z77 remote workspace real-adapter runtime gate preflight status summary smoke passed. Result: $fullResultPath" +$result diff --git a/scripts/fabric/c19z78-remote-workspace-real-adapter-runtime-gate-preflight-status-summary-compatibility-smoke.ps1 b/scripts/fabric/c19z78-remote-workspace-real-adapter-runtime-gate-preflight-status-summary-compatibility-smoke.ps1 new file mode 100644 index 0000000..13f7229 --- /dev/null +++ b/scripts/fabric/c19z78-remote-workspace-real-adapter-runtime-gate-preflight-status-summary-compatibility-smoke.ps1 @@ -0,0 +1,149 @@ +param( + [string]$ApiBaseUrl = "http://192.168.200.61:18121/api/v1", + [string]$ClusterID = "cfc0743d-d960-49fb-9de8-96e063d5e4aa", + [string]$ActorUserID = "f67d943f-5397-4b3a-a229-695fe67ad700", + [string]$RequestedNodeName = "test-1", + [string]$DefaultNodeName = "test-2", + [string]$MatrixNodeName = "test-1", + [string]$ResultPath = "artifacts\c19z78-remote-workspace-real-adapter-runtime-gate-preflight-status-summary-compatibility-smoke-result.json" +) + +Set-StrictMode -Version Latest +$ErrorActionPreference = "Stop" + +$repoRoot = (Resolve-Path (Join-Path $PSScriptRoot "..\..")).ProviderPath +$sourceResultPath = "artifacts\c19z78-remote-workspace-real-adapter-runtime-gate-preflight-status-summary-source-result.json" +$requiredSummaryFields = @( + "schema_version", + "source_checklist_schema", + "summary_status", + "runtime_gate_state", + "required_item_count", + "satisfied_item_count", + "blocked_item_count", + "not_satisfied_item_count", + "blocking_item_keys", + "operator_default_action", + "allows_process_start", + "allows_payload_traffic", + "guardrail_summary" +) +$requiredPreflightKeys = @( + "explicit_operator_gate_enablement", + "real_adapter_binary_path_validated", + "service_account_and_permissions_validated", + "process_supervisor_limits_validated", + "health_probe_signal_contract_validated", + "payload_forwarding_gate_validated" +) +$requiredGuardrailFields = @("activation_blocked", "process_start_allowed", "health_probe_enabled", "payload_traffic", "allows_process_start", "allows_payload_traffic") + +function Get-PropertyValue { + param([object]$Item, [string]$Name, [object]$Default = $null) + if ($null -eq $Item) { return $Default } + if ($Item -is [System.Collections.IDictionary]) { + if ($Item.Contains($Name)) { return $Item[$Name] } + return $Default + } + $property = $Item.PSObject.Properties[$Name] + if ($null -eq $property) { return $Default } + return $property.Value +} + +function Test-ObjectHasFields { + param([object]$Item, [string[]]$Fields) + if ($null -eq $Item) { return $false } + foreach ($field in $Fields) { + if ($Item -is [System.Collections.IDictionary]) { + if (-not $Item.Contains($field)) { return $false } + continue + } + if ($null -eq $Item.PSObject.Properties[$field]) { return $false } + } + return $true +} + +function Test-ArrayContainsAll { + param([object[]]$Actual, [string[]]$Required) + $values = @($Actual | ForEach-Object { [string]$_ }) + foreach ($item in $Required) { + if ($values -notcontains $item) { return $false } + } + return $true +} + +& powershell -ExecutionPolicy Bypass -File (Join-Path $PSScriptRoot "c19z77-remote-workspace-real-adapter-runtime-gate-preflight-status-summary-smoke.ps1") ` + -ApiBaseUrl $ApiBaseUrl ` + -ClusterID $ClusterID ` + -ActorUserID $ActorUserID ` + -RequestedNodeName $RequestedNodeName ` + -DefaultNodeName $DefaultNodeName ` + -MatrixNodeName $MatrixNodeName ` + -ResultPath $sourceResultPath | Out-Null + +$sourceFile = Join-Path $repoRoot $sourceResultPath +$sourceResult = Get-Content -Raw -Path $sourceFile | ConvertFrom-Json +$summary = Get-PropertyValue -Item $sourceResult -Name "runtime_gate_preflight_status_summary" -Default $null +$blockingKeys = @(Get-PropertyValue -Item $summary -Name "blocking_item_keys" -Default @()) +$guardrails = Get-PropertyValue -Item $summary -Name "guardrail_summary" -Default $null + +$summaryFieldsCompatible = Test-ObjectHasFields -Item $summary -Fields $requiredSummaryFields +$summaryValuesCompatible = ( + [string](Get-PropertyValue -Item $summary -Name "schema_version" -Default "") -eq "rap.remote_workspace_real_adapter_runtime_gate_preflight_status_summary.v1" -and + [string](Get-PropertyValue -Item $summary -Name "source_checklist_schema" -Default "") -eq "rap.remote_workspace_real_adapter_runtime_gate_preflight_checklist.v1" -and + [string](Get-PropertyValue -Item $summary -Name "summary_status" -Default "") -eq "blocked_all_required_items_missing" -and + [string](Get-PropertyValue -Item $summary -Name "runtime_gate_state" -Default "") -eq "blocked" -and + [int](Get-PropertyValue -Item $summary -Name "required_item_count" -Default -1) -eq $requiredPreflightKeys.Count -and + [int](Get-PropertyValue -Item $summary -Name "satisfied_item_count" -Default -1) -eq 0 -and + [int](Get-PropertyValue -Item $summary -Name "blocked_item_count" -Default -1) -eq $requiredPreflightKeys.Count -and + [int](Get-PropertyValue -Item $summary -Name "not_satisfied_item_count" -Default -1) -eq $requiredPreflightKeys.Count -and + [string](Get-PropertyValue -Item $summary -Name "operator_default_action" -Default "") -eq "keep_real_adapter_disabled" -and + -not [bool](Get-PropertyValue -Item $summary -Name "allows_process_start" -Default $true) -and + -not [bool](Get-PropertyValue -Item $summary -Name "allows_payload_traffic" -Default $true) +) +$blockingKeysCompatible = Test-ArrayContainsAll -Actual $blockingKeys -Required $requiredPreflightKeys +$guardrailsCompatible = ( + (Test-ObjectHasFields -Item $guardrails -Fields $requiredGuardrailFields) -and + [bool](Get-PropertyValue -Item $guardrails -Name "activation_blocked" -Default $false) -and + -not [bool](Get-PropertyValue -Item $guardrails -Name "process_start_allowed" -Default $true) -and + -not [bool](Get-PropertyValue -Item $guardrails -Name "health_probe_enabled" -Default $true) -and + [string](Get-PropertyValue -Item $guardrails -Name "payload_traffic" -Default "") -eq "none" -and + -not [bool](Get-PropertyValue -Item $guardrails -Name "allows_process_start" -Default $true) -and + -not [bool](Get-PropertyValue -Item $guardrails -Name "allows_payload_traffic" -Default $true) +) + +$checks = [ordered]@{ + source_smoke_passed = ([bool]$sourceResult.passed) + source_schema_expected = ([string]$sourceResult.schema_version -eq "c19z77.remote_workspace_real_adapter_runtime_gate_preflight_status_summary_smoke.v1") + summary_present = ($null -ne $summary) + summary_fields_compatible = $summaryFieldsCompatible + summary_values_compatible = $summaryValuesCompatible + blocking_keys_compatible = $blockingKeysCompatible + guardrails_compatible = $guardrailsCompatible +} +$failed = @($checks.GetEnumerator() | Where-Object { -not $_.Value } | ForEach-Object { $_.Key }) + +$result = [ordered]@{ + schema_version = "c19z78.remote_workspace_real_adapter_runtime_gate_preflight_status_summary_compatibility_smoke.v1" + source_result_path = $sourceFile + cluster_id = $ClusterID + required_summary_fields = $requiredSummaryFields + required_preflight_keys = $requiredPreflightKeys + required_guardrail_fields = $requiredGuardrailFields + runtime_gate_preflight_status_summary = $summary + checks = $checks + failed_checks = $failed + passed = ($failed.Count -eq 0) +} + +$fullResultPath = Join-Path $repoRoot $ResultPath +$resultDir = Split-Path -Parent $fullResultPath +if ($resultDir) { New-Item -ItemType Directory -Force -Path $resultDir | Out-Null } +$result | ConvertTo-Json -Depth 100 | Set-Content -Encoding UTF8 -Path $fullResultPath + +if (-not $result.passed) { + throw "C19Z78 remote workspace real-adapter runtime gate preflight status summary compatibility smoke failed. Result: $fullResultPath Failed: $($failed -join ', ')" +} + +Write-Host "C19Z78 remote workspace real-adapter runtime gate preflight status summary compatibility smoke passed. Result: $fullResultPath" +$result diff --git a/scripts/fabric/c19z79-remote-workspace-real-adapter-runtime-gate-preflight-action-hints-smoke.ps1 b/scripts/fabric/c19z79-remote-workspace-real-adapter-runtime-gate-preflight-action-hints-smoke.ps1 new file mode 100644 index 0000000..bf45946 --- /dev/null +++ b/scripts/fabric/c19z79-remote-workspace-real-adapter-runtime-gate-preflight-action-hints-smoke.ps1 @@ -0,0 +1,215 @@ +param( + [string]$ApiBaseUrl = "http://192.168.200.61:18121/api/v1", + [string]$ClusterID = "cfc0743d-d960-49fb-9de8-96e063d5e4aa", + [string]$ActorUserID = "f67d943f-5397-4b3a-a229-695fe67ad700", + [string]$RequestedNodeName = "test-1", + [string]$DefaultNodeName = "test-2", + [string]$MatrixNodeName = "test-1", + [string]$ResultPath = "artifacts\c19z79-remote-workspace-real-adapter-runtime-gate-preflight-action-hints-smoke-result.json" +) + +Set-StrictMode -Version Latest +$ErrorActionPreference = "Stop" + +$repoRoot = (Resolve-Path (Join-Path $PSScriptRoot "..\..")).ProviderPath +$sourceResultPath = "artifacts\c19z79-remote-workspace-real-adapter-runtime-gate-preflight-action-hints-source-result.json" +$requiredHintsFields = @( + "schema_version", + "source_summary_schema", + "hint_status", + "runtime_gate_state", + "operator_default_action", + "hint_count", + "action_hints", + "allows_process_start", + "allows_payload_traffic", + "guardrail_summary" +) +$requiredHintFields = @("key", "category", "priority", "action", "blocks_runtime_gate", "allows_runtime") +$requiredHintKeys = @( + "collect_explicit_operator_gate_enablement", + "validate_real_adapter_binary_path", + "validate_service_account_and_permissions", + "validate_process_supervisor_limits", + "validate_health_probe_signal_contract", + "validate_payload_forwarding_gate" +) +$requiredGuardrailFields = @("activation_blocked", "process_start_allowed", "health_probe_enabled", "payload_traffic", "allows_process_start", "allows_payload_traffic") + +function Get-PropertyValue { + param([object]$Item, [string]$Name, [object]$Default = $null) + if ($null -eq $Item) { return $Default } + if ($Item -is [System.Collections.IDictionary]) { + if ($Item.Contains($Name)) { return $Item[$Name] } + return $Default + } + $property = $Item.PSObject.Properties[$Name] + if ($null -eq $property) { return $Default } + return $property.Value +} + +function Test-ObjectHasFields { + param([object]$Item, [string[]]$Fields) + if ($null -eq $Item) { return $false } + foreach ($field in $Fields) { + if ($Item -is [System.Collections.IDictionary]) { + if (-not $Item.Contains($field)) { return $false } + continue + } + if ($null -eq $Item.PSObject.Properties[$field]) { return $false } + } + return $true +} + +function Test-ArrayContainsAll { + param([object[]]$Actual, [string[]]$Required) + $values = @($Actual | ForEach-Object { [string]$_ }) + foreach ($item in $Required) { + if ($values -notcontains $item) { return $false } + } + return $true +} + +& powershell -ExecutionPolicy Bypass -File (Join-Path $PSScriptRoot "c19z78-remote-workspace-real-adapter-runtime-gate-preflight-status-summary-compatibility-smoke.ps1") ` + -ApiBaseUrl $ApiBaseUrl ` + -ClusterID $ClusterID ` + -ActorUserID $ActorUserID ` + -RequestedNodeName $RequestedNodeName ` + -DefaultNodeName $DefaultNodeName ` + -MatrixNodeName $MatrixNodeName ` + -ResultPath $sourceResultPath | Out-Null + +$sourceFile = Join-Path $repoRoot $sourceResultPath +$sourceResult = Get-Content -Raw -Path $sourceFile | ConvertFrom-Json +$summary = Get-PropertyValue -Item $sourceResult -Name "runtime_gate_preflight_status_summary" -Default $null +$guardrails = Get-PropertyValue -Item $summary -Name "guardrail_summary" -Default $null + +$actionHints = @( + [ordered]@{ + key = "collect_explicit_operator_gate_enablement" + category = "operator_approval" + priority = 1 + action = "collect_explicit_operator_enablement_before_any_runtime_change" + blocks_runtime_gate = $true + allows_runtime = $false + }, + [ordered]@{ + key = "validate_real_adapter_binary_path" + category = "binary_validation" + priority = 2 + action = "validate_real_adapter_binary_path_without_starting_process" + blocks_runtime_gate = $true + allows_runtime = $false + }, + [ordered]@{ + key = "validate_service_account_and_permissions" + category = "permissions" + priority = 3 + action = "validate_service_account_and_permissions_without_payload_access" + blocks_runtime_gate = $true + allows_runtime = $false + }, + [ordered]@{ + key = "validate_process_supervisor_limits" + category = "supervisor_limits" + priority = 4 + action = "validate_process_supervisor_limits_without_process_start" + blocks_runtime_gate = $true + allows_runtime = $false + }, + [ordered]@{ + key = "validate_health_probe_signal_contract" + category = "health_probe" + priority = 5 + action = "validate_health_probe_signal_contract_without_enabling_probe" + blocks_runtime_gate = $true + allows_runtime = $false + }, + [ordered]@{ + key = "validate_payload_forwarding_gate" + category = "payload_gate" + priority = 6 + action = "validate_payload_forwarding_gate_remains_disabled" + blocks_runtime_gate = $true + allows_runtime = $false + } +) + +$hints = [ordered]@{ + schema_version = "rap.remote_workspace_real_adapter_runtime_gate_preflight_action_hints.v1" + source_summary_schema = Get-PropertyValue -Item $summary -Name "schema_version" -Default $null + hint_status = "blocked_operator_preflight_actions_required" + runtime_gate_state = Get-PropertyValue -Item $summary -Name "runtime_gate_state" -Default $null + operator_default_action = Get-PropertyValue -Item $summary -Name "operator_default_action" -Default $null + hint_count = $actionHints.Count + action_hints = $actionHints + allows_process_start = Get-PropertyValue -Item $summary -Name "allows_process_start" -Default $null + allows_payload_traffic = Get-PropertyValue -Item $summary -Name "allows_payload_traffic" -Default $null + guardrail_summary = $guardrails +} + +$hintsFieldsCompatible = Test-ObjectHasFields -Item $hints -Fields $requiredHintsFields +$hintFieldsCompatible = (@($actionHints | Where-Object { -not (Test-ObjectHasFields -Item $_ -Fields $requiredHintFields) }).Count -eq 0) +$hintKeysCompatible = Test-ArrayContainsAll -Actual @($actionHints | ForEach-Object { Get-PropertyValue -Item $_ -Name "key" -Default "" }) -Required $requiredHintKeys +$hintValuesCompatible = (@($actionHints | Where-Object { + -not [bool](Get-PropertyValue -Item $_ -Name "blocks_runtime_gate" -Default $false) -or + [bool](Get-PropertyValue -Item $_ -Name "allows_runtime" -Default $true) +}).Count -eq 0) +$hintsValuesCompatible = ( + [string](Get-PropertyValue -Item $hints -Name "schema_version" -Default "") -eq "rap.remote_workspace_real_adapter_runtime_gate_preflight_action_hints.v1" -and + [string](Get-PropertyValue -Item $hints -Name "source_summary_schema" -Default "") -eq "rap.remote_workspace_real_adapter_runtime_gate_preflight_status_summary.v1" -and + [string](Get-PropertyValue -Item $hints -Name "hint_status" -Default "") -eq "blocked_operator_preflight_actions_required" -and + [string](Get-PropertyValue -Item $hints -Name "runtime_gate_state" -Default "") -eq "blocked" -and + [string](Get-PropertyValue -Item $hints -Name "operator_default_action" -Default "") -eq "keep_real_adapter_disabled" -and + [int](Get-PropertyValue -Item $hints -Name "hint_count" -Default -1) -eq $requiredHintKeys.Count -and + -not [bool](Get-PropertyValue -Item $hints -Name "allows_process_start" -Default $true) -and + -not [bool](Get-PropertyValue -Item $hints -Name "allows_payload_traffic" -Default $true) +) +$guardrailsCompatible = ( + (Test-ObjectHasFields -Item $guardrails -Fields $requiredGuardrailFields) -and + [bool](Get-PropertyValue -Item $guardrails -Name "activation_blocked" -Default $false) -and + -not [bool](Get-PropertyValue -Item $guardrails -Name "process_start_allowed" -Default $true) -and + -not [bool](Get-PropertyValue -Item $guardrails -Name "health_probe_enabled" -Default $true) -and + [string](Get-PropertyValue -Item $guardrails -Name "payload_traffic" -Default "") -eq "none" -and + -not [bool](Get-PropertyValue -Item $guardrails -Name "allows_process_start" -Default $true) -and + -not [bool](Get-PropertyValue -Item $guardrails -Name "allows_payload_traffic" -Default $true) +) + +$checks = [ordered]@{ + source_smoke_passed = ([bool]$sourceResult.passed) + source_schema_expected = ([string]$sourceResult.schema_version -eq "c19z78.remote_workspace_real_adapter_runtime_gate_preflight_status_summary_compatibility_smoke.v1") + summary_present = ($null -ne $summary) + hints_fields_compatible = $hintsFieldsCompatible + hints_values_compatible = $hintsValuesCompatible + hint_fields_compatible = $hintFieldsCompatible + hint_keys_compatible = $hintKeysCompatible + hint_values_compatible = $hintValuesCompatible + guardrails_compatible = $guardrailsCompatible +} +$failed = @($checks.GetEnumerator() | Where-Object { -not $_.Value } | ForEach-Object { $_.Key }) + +$result = [ordered]@{ + schema_version = "c19z79.remote_workspace_real_adapter_runtime_gate_preflight_action_hints_smoke.v1" + source_result_path = $sourceFile + cluster_id = $ClusterID + required_hints_fields = $requiredHintsFields + required_hint_fields = $requiredHintFields + required_hint_keys = $requiredHintKeys + required_guardrail_fields = $requiredGuardrailFields + runtime_gate_preflight_action_hints = $hints + checks = $checks + failed_checks = $failed + passed = ($failed.Count -eq 0) +} + +$fullResultPath = Join-Path $repoRoot $ResultPath +$resultDir = Split-Path -Parent $fullResultPath +if ($resultDir) { New-Item -ItemType Directory -Force -Path $resultDir | Out-Null } +$result | ConvertTo-Json -Depth 100 | Set-Content -Encoding UTF8 -Path $fullResultPath + +if (-not $result.passed) { + throw "C19Z79 remote workspace real-adapter runtime gate preflight action hints smoke failed. Result: $fullResultPath Failed: $($failed -join ', ')" +} + +Write-Host "C19Z79 remote workspace real-adapter runtime gate preflight action hints smoke passed. Result: $fullResultPath" +$result diff --git a/scripts/fabric/c19z8-remote-workspace-mailbox-preflight-rollup-smoke.ps1 b/scripts/fabric/c19z8-remote-workspace-mailbox-preflight-rollup-smoke.ps1 new file mode 100644 index 0000000..1f8acf4 --- /dev/null +++ b/scripts/fabric/c19z8-remote-workspace-mailbox-preflight-rollup-smoke.ps1 @@ -0,0 +1,169 @@ +param( + [string]$ApiBaseUrl = "http://192.168.200.61:18121/api/v1", + [string]$ClusterID = "cfc0743d-d960-49fb-9de8-96e063d5e4aa", + [string]$ActorUserID = "f67d943f-5397-4b3a-a229-695fe67ad700", + [string]$EntryNodeName = "test-1", + [string]$ExitNodeName = "test-2", + [string]$EntryBaseUrl = "http://192.168.200.61:19131", + [string]$ResultPath = "artifacts\c19z8-remote-workspace-mailbox-preflight-rollup-smoke-result.json" +) + +Set-StrictMode -Version Latest +$ErrorActionPreference = "Stop" + +$repoRoot = (Resolve-Path (Join-Path $PSScriptRoot "..\..")).ProviderPath +$sourceResultPath = "artifacts\c19z8-remote-workspace-mailbox-preflight-rollup-source-result.json" +$adapterSessionID = "" + +function Invoke-Api { + param([string]$Method, [string]$Path, [object]$Body = $null) + $uri = "$ApiBaseUrl$Path" + if ($null -eq $Body) { return Invoke-RestMethod -Method $Method -Uri $uri -TimeoutSec 30 } + return Invoke-RestMethod -Method $Method -Uri $uri -ContentType "application/json" -Body ($Body | ConvertTo-Json -Depth 80) -TimeoutSec 30 +} + +function Get-PropertyValue { + param([object]$Item, [string]$Name, [object]$Default = $null) + if ($null -eq $Item) { return $Default } + $property = $Item.PSObject.Properties[$Name] + if ($null -eq $property) { return $Default } + return $property.Value +} + +function Get-NodeByName { + param([string]$Name) + $nodes = (Invoke-Api -Method GET -Path "/clusters/$ClusterID/nodes?actor_user_id=$ActorUserID").nodes + $node = @($nodes | Where-Object { $_.name -eq $Name }) | Select-Object -First 1 + if ($null -eq $node) { throw "Node '$Name' was not found in cluster $ClusterID" } + return $node +} + +function Get-RemoteWorkspaceSinkReports { + param([string]$NodeID) + $statuses = (Invoke-Api -Method GET -Path "/clusters/$ClusterID/nodes/$NodeID/workloads/status?actor_user_id=$ActorUserID").workload_statuses + $workloadStatus = @($statuses | Where-Object { $_.service_type -eq "rdp-worker" } | Select-Object -First 1) + $workloadSink = $null + if ($null -ne $workloadStatus) { + $workloadSink = Get-PropertyValue -Item $workloadStatus.status_payload -Name "remote_workspace_adapter_sink" -Default $null + } + $telemetryItems = @((Invoke-Api -Method GET -Path "/clusters/$ClusterID/nodes/$NodeID/telemetry?actor_user_id=$ActorUserID&limit=10").telemetry) + $telemetry = $telemetryItems | Select-Object -First 1 + $telemetrySink = $null + if ($null -ne $telemetry) { + $telemetryPayload = Get-PropertyValue -Item $telemetry -Name "payload" -Default $null + $telemetrySink = Get-PropertyValue -Item $telemetryPayload -Name "remote_workspace_adapter_sink_report" -Default $null + } + return [ordered]@{ + workload_status = $workloadStatus + workload_sink = $workloadSink + telemetry = $telemetry + telemetry_sink = $telemetrySink + } +} + +function Invoke-Control { + param([string]$SessionID) + if ([string]::IsNullOrWhiteSpace($SessionID)) { return $null } + $url = "$EntryBaseUrl/mesh/v1/remote-workspace/adapter-sessions/$SessionID/control" + $body = @{ action = "close"; reason = "c19z8 mailbox preflight rollup close" } | ConvertTo-Json -Compress + return Invoke-RestMethod -Method POST -Uri $url -ContentType "application/json" -Body $body -TimeoutSec 30 +} + +function Test-PreflightRollup { + param([object]$Sink, [string]$AdapterSessionID) + if ($null -eq $Sink) { return $false } + $readiness = Get-PropertyValue -Item $Sink -Name "adapter_runtime_readiness" -Default $null + $rollup = Get-PropertyValue -Item $readiness -Name "last_preflight" -Default $null + if ($null -eq $rollup) { return $false } + $summaryFields = Get-PropertyValue -Item $rollup -Name "operator_summary_fields" -Default $null + return ( + [string](Get-PropertyValue -Item $readiness -Name "adapter_session_id" -Default "") -eq $AdapterSessionID -and + [string](Get-PropertyValue -Item $rollup -Name "consumer_id" -Default "") -eq "rdp-worker-probe" -and + [string](Get-PropertyValue -Item $rollup -Name "resume_from" -Default "") -eq "ack" -and + [int64](Get-PropertyValue -Item $rollup -Name "resume_sequence" -Default -1) -eq 1 -and + [string](Get-PropertyValue -Item $rollup -Name "diagnostic_state" -Default "") -eq "stale_cursor_gap" -and + [string](Get-PropertyValue -Item $rollup -Name "operator_status" -Default "") -eq "resync_required" -and + [string](Get-PropertyValue -Item $rollup -Name "operator_severity" -Default "") -eq "warn" -and + [string](Get-PropertyValue -Item $rollup -Name "recommended_action" -Default "") -eq "reset_consumer_and_resync" -and + [string](Get-PropertyValue -Item $rollup -Name "action_reason" -Default "") -eq "consumer_cursor_before_first_retained_sequence" -and + [string](Get-PropertyValue -Item $rollup -Name "operator_summary" -Default "") -eq "stale cursor gap: reset consumer and resync before resume" -and + [int](Get-PropertyValue -Item $rollup -Name "missing_dropped_count" -Default -1) -ge 1 -and + [int64](Get-PropertyValue -Item $rollup -Name "mailbox_preflight_total" -Default 0) -ge 1 -and + [string](Get-PropertyValue -Item $summaryFields -Name "operator_status" -Default "") -eq "resync_required" -and + [string](Get-PropertyValue -Item $summaryFields -Name "operator_severity" -Default "") -eq "warn" + ) +} + +try { + $entryNode = Get-NodeByName -Name $EntryNodeName + + $source = & powershell -ExecutionPolicy Bypass -File (Join-Path $PSScriptRoot "c19z3-remote-workspace-mailbox-stale-preflight-smoke.ps1") ` + -ApiBaseUrl $ApiBaseUrl ` + -ClusterID $ClusterID ` + -ActorUserID $ActorUserID ` + -EntryNodeName $EntryNodeName ` + -ExitNodeName $ExitNodeName ` + -EntryBaseUrl $EntryBaseUrl ` + -ResultPath $sourceResultPath ` + -SkipClose + + $sourceFile = Join-Path $repoRoot $sourceResultPath + $sourceResult = Get-Content -Raw -Path $sourceFile | ConvertFrom-Json + $adapterSessionID = [string](Get-PropertyValue -Item $sourceResult -Name "adapter_session_id" -Default "") + + $observed = $null + $deadline = (Get-Date).AddSeconds(90) + while ((Get-Date) -lt $deadline) { + Start-Sleep -Seconds 5 + $observed = Get-RemoteWorkspaceSinkReports -NodeID $entryNode.id + if ( + (Test-PreflightRollup -Sink $observed.workload_sink -AdapterSessionID $adapterSessionID) -and + (Test-PreflightRollup -Sink $observed.telemetry_sink -AdapterSessionID $adapterSessionID) + ) { + break + } + } + if ($null -eq $observed) { + $observed = Get-RemoteWorkspaceSinkReports -NodeID $entryNode.id + } + + $control = Invoke-Control -SessionID $adapterSessionID + $checks = [ordered]@{ + source_smoke_passed = ([bool]$sourceResult.passed) + adapter_session_id_present = ($adapterSessionID -match "^rap-rw-adapter-session-[0-9a-f]{24}$") + workload_rollup_visible = (Test-PreflightRollup -Sink $observed.workload_sink -AdapterSessionID $adapterSessionID) + telemetry_rollup_visible = (Test-PreflightRollup -Sink $observed.telemetry_sink -AdapterSessionID $adapterSessionID) + close_accepted = ([bool]$control.accepted -and [string]$control.session_state -eq "closed") + } + $failed = @($checks.GetEnumerator() | Where-Object { -not $_.Value } | ForEach-Object { $_.Key }) + + $result = [ordered]@{ + schema_version = "c19z8.remote_workspace_mailbox_preflight_rollup_smoke.v1" + source_result_path = $sourceFile + cluster_id = $ClusterID + entry_node = [ordered]@{ id = $entryNode.id; name = $entryNode.name } + adapter_session_id = $adapterSessionID + source = $sourceResult + observed = $observed + control = $control + checks = $checks + failed_checks = $failed + passed = ($failed.Count -eq 0) + } +} finally { + if ($adapterSessionID) { + try { [void](Invoke-Control -SessionID $adapterSessionID) } catch {} + } +} + +$fullResultPath = Join-Path $repoRoot $ResultPath +$resultDir = Split-Path -Parent $fullResultPath +if ($resultDir) { New-Item -ItemType Directory -Force -Path $resultDir | Out-Null } +$result | ConvertTo-Json -Depth 100 | Set-Content -Encoding UTF8 -Path $fullResultPath + +if (-not $result.passed) { + throw "C19Z8 remote workspace mailbox preflight rollup smoke failed. Result: $fullResultPath Failed: $($failed -join ', ')" +} + +Write-Host "C19Z8 remote workspace mailbox preflight rollup smoke passed. Result: $fullResultPath" +$result diff --git a/scripts/fabric/c19z80-remote-workspace-real-adapter-runtime-gate-preflight-action-hints-compatibility-smoke.ps1 b/scripts/fabric/c19z80-remote-workspace-real-adapter-runtime-gate-preflight-action-hints-compatibility-smoke.ps1 new file mode 100644 index 0000000..116832b --- /dev/null +++ b/scripts/fabric/c19z80-remote-workspace-real-adapter-runtime-gate-preflight-action-hints-compatibility-smoke.ps1 @@ -0,0 +1,152 @@ +param( + [string]$ApiBaseUrl = "http://192.168.200.61:18121/api/v1", + [string]$ClusterID = "cfc0743d-d960-49fb-9de8-96e063d5e4aa", + [string]$ActorUserID = "f67d943f-5397-4b3a-a229-695fe67ad700", + [string]$RequestedNodeName = "test-1", + [string]$DefaultNodeName = "test-2", + [string]$MatrixNodeName = "test-1", + [string]$ResultPath = "artifacts\c19z80-remote-workspace-real-adapter-runtime-gate-preflight-action-hints-compatibility-smoke-result.json" +) + +Set-StrictMode -Version Latest +$ErrorActionPreference = "Stop" + +$repoRoot = (Resolve-Path (Join-Path $PSScriptRoot "..\..")).ProviderPath +$sourceResultPath = "artifacts\c19z80-remote-workspace-real-adapter-runtime-gate-preflight-action-hints-source-result.json" +$requiredHintsFields = @( + "schema_version", + "source_summary_schema", + "hint_status", + "runtime_gate_state", + "operator_default_action", + "hint_count", + "action_hints", + "allows_process_start", + "allows_payload_traffic", + "guardrail_summary" +) +$requiredHintFields = @("key", "category", "priority", "action", "blocks_runtime_gate", "allows_runtime") +$requiredHintKeys = @( + "collect_explicit_operator_gate_enablement", + "validate_real_adapter_binary_path", + "validate_service_account_and_permissions", + "validate_process_supervisor_limits", + "validate_health_probe_signal_contract", + "validate_payload_forwarding_gate" +) +$requiredGuardrailFields = @("activation_blocked", "process_start_allowed", "health_probe_enabled", "payload_traffic", "allows_process_start", "allows_payload_traffic") + +function Get-PropertyValue { + param([object]$Item, [string]$Name, [object]$Default = $null) + if ($null -eq $Item) { return $Default } + if ($Item -is [System.Collections.IDictionary]) { + if ($Item.Contains($Name)) { return $Item[$Name] } + return $Default + } + $property = $Item.PSObject.Properties[$Name] + if ($null -eq $property) { return $Default } + return $property.Value +} + +function Test-ObjectHasFields { + param([object]$Item, [string[]]$Fields) + if ($null -eq $Item) { return $false } + foreach ($field in $Fields) { + if ($Item -is [System.Collections.IDictionary]) { + if (-not $Item.Contains($field)) { return $false } + continue + } + if ($null -eq $Item.PSObject.Properties[$field]) { return $false } + } + return $true +} + +function Test-ArrayContainsAll { + param([object[]]$Actual, [string[]]$Required) + $values = @($Actual | ForEach-Object { [string]$_ }) + foreach ($item in $Required) { + if ($values -notcontains $item) { return $false } + } + return $true +} + +& powershell -ExecutionPolicy Bypass -File (Join-Path $PSScriptRoot "c19z79-remote-workspace-real-adapter-runtime-gate-preflight-action-hints-smoke.ps1") ` + -ApiBaseUrl $ApiBaseUrl ` + -ClusterID $ClusterID ` + -ActorUserID $ActorUserID ` + -RequestedNodeName $RequestedNodeName ` + -DefaultNodeName $DefaultNodeName ` + -MatrixNodeName $MatrixNodeName ` + -ResultPath $sourceResultPath | Out-Null + +$sourceFile = Join-Path $repoRoot $sourceResultPath +$sourceResult = Get-Content -Raw -Path $sourceFile | ConvertFrom-Json +$hints = Get-PropertyValue -Item $sourceResult -Name "runtime_gate_preflight_action_hints" -Default $null +$actionHints = @(Get-PropertyValue -Item $hints -Name "action_hints" -Default @()) +$guardrails = Get-PropertyValue -Item $hints -Name "guardrail_summary" -Default $null + +$hintsFieldsCompatible = Test-ObjectHasFields -Item $hints -Fields $requiredHintsFields +$hintFieldsCompatible = (@($actionHints | Where-Object { -not (Test-ObjectHasFields -Item $_ -Fields $requiredHintFields) }).Count -eq 0) +$hintKeysCompatible = Test-ArrayContainsAll -Actual @($actionHints | ForEach-Object { Get-PropertyValue -Item $_ -Name "key" -Default "" }) -Required $requiredHintKeys +$hintValuesCompatible = (@($actionHints | Where-Object { + -not [bool](Get-PropertyValue -Item $_ -Name "blocks_runtime_gate" -Default $false) -or + [bool](Get-PropertyValue -Item $_ -Name "allows_runtime" -Default $true) +}).Count -eq 0) +$hintsValuesCompatible = ( + [string](Get-PropertyValue -Item $hints -Name "schema_version" -Default "") -eq "rap.remote_workspace_real_adapter_runtime_gate_preflight_action_hints.v1" -and + [string](Get-PropertyValue -Item $hints -Name "source_summary_schema" -Default "") -eq "rap.remote_workspace_real_adapter_runtime_gate_preflight_status_summary.v1" -and + [string](Get-PropertyValue -Item $hints -Name "hint_status" -Default "") -eq "blocked_operator_preflight_actions_required" -and + [string](Get-PropertyValue -Item $hints -Name "runtime_gate_state" -Default "") -eq "blocked" -and + [string](Get-PropertyValue -Item $hints -Name "operator_default_action" -Default "") -eq "keep_real_adapter_disabled" -and + [int](Get-PropertyValue -Item $hints -Name "hint_count" -Default -1) -eq $requiredHintKeys.Count -and + -not [bool](Get-PropertyValue -Item $hints -Name "allows_process_start" -Default $true) -and + -not [bool](Get-PropertyValue -Item $hints -Name "allows_payload_traffic" -Default $true) +) +$guardrailsCompatible = ( + (Test-ObjectHasFields -Item $guardrails -Fields $requiredGuardrailFields) -and + [bool](Get-PropertyValue -Item $guardrails -Name "activation_blocked" -Default $false) -and + -not [bool](Get-PropertyValue -Item $guardrails -Name "process_start_allowed" -Default $true) -and + -not [bool](Get-PropertyValue -Item $guardrails -Name "health_probe_enabled" -Default $true) -and + [string](Get-PropertyValue -Item $guardrails -Name "payload_traffic" -Default "") -eq "none" -and + -not [bool](Get-PropertyValue -Item $guardrails -Name "allows_process_start" -Default $true) -and + -not [bool](Get-PropertyValue -Item $guardrails -Name "allows_payload_traffic" -Default $true) +) + +$checks = [ordered]@{ + source_smoke_passed = ([bool]$sourceResult.passed) + source_schema_expected = ([string]$sourceResult.schema_version -eq "c19z79.remote_workspace_real_adapter_runtime_gate_preflight_action_hints_smoke.v1") + action_hints_present = ($null -ne $hints) + hints_fields_compatible = $hintsFieldsCompatible + hints_values_compatible = $hintsValuesCompatible + hint_fields_compatible = $hintFieldsCompatible + hint_keys_compatible = $hintKeysCompatible + hint_values_compatible = $hintValuesCompatible + guardrails_compatible = $guardrailsCompatible +} +$failed = @($checks.GetEnumerator() | Where-Object { -not $_.Value } | ForEach-Object { $_.Key }) + +$result = [ordered]@{ + schema_version = "c19z80.remote_workspace_real_adapter_runtime_gate_preflight_action_hints_compatibility_smoke.v1" + source_result_path = $sourceFile + cluster_id = $ClusterID + required_hints_fields = $requiredHintsFields + required_hint_fields = $requiredHintFields + required_hint_keys = $requiredHintKeys + required_guardrail_fields = $requiredGuardrailFields + runtime_gate_preflight_action_hints = $hints + checks = $checks + failed_checks = $failed + passed = ($failed.Count -eq 0) +} + +$fullResultPath = Join-Path $repoRoot $ResultPath +$resultDir = Split-Path -Parent $fullResultPath +if ($resultDir) { New-Item -ItemType Directory -Force -Path $resultDir | Out-Null } +$result | ConvertTo-Json -Depth 100 | Set-Content -Encoding UTF8 -Path $fullResultPath + +if (-not $result.passed) { + throw "C19Z80 remote workspace real-adapter runtime gate preflight action hints compatibility smoke failed. Result: $fullResultPath Failed: $($failed -join ', ')" +} + +Write-Host "C19Z80 remote workspace real-adapter runtime gate preflight action hints compatibility smoke passed. Result: $fullResultPath" +$result diff --git a/scripts/fabric/c19z81-remote-workspace-real-adapter-runtime-gate-preflight-operator-handoff-bundle-smoke.ps1 b/scripts/fabric/c19z81-remote-workspace-real-adapter-runtime-gate-preflight-operator-handoff-bundle-smoke.ps1 new file mode 100644 index 0000000..07aa64a --- /dev/null +++ b/scripts/fabric/c19z81-remote-workspace-real-adapter-runtime-gate-preflight-operator-handoff-bundle-smoke.ps1 @@ -0,0 +1,190 @@ +param( + [string]$ApiBaseUrl = "http://192.168.200.61:18121/api/v1", + [string]$ClusterID = "cfc0743d-d960-49fb-9de8-96e063d5e4aa", + [string]$ActorUserID = "f67d943f-5397-4b3a-a229-695fe67ad700", + [string]$RequestedNodeName = "test-1", + [string]$DefaultNodeName = "test-2", + [string]$MatrixNodeName = "test-1", + [string]$ResultPath = "artifacts\c19z81-remote-workspace-real-adapter-runtime-gate-preflight-operator-handoff-bundle-smoke-result.json" +) + +Set-StrictMode -Version Latest +$ErrorActionPreference = "Stop" + +$repoRoot = (Resolve-Path (Join-Path $PSScriptRoot "..\..")).ProviderPath +$sourceResultPath = "artifacts\c19z81-remote-workspace-real-adapter-runtime-gate-preflight-operator-handoff-bundle-source-result.json" +$requiredBundleFields = @( + "schema_version", + "source_action_hints_schema", + "handoff_status", + "runtime_gate_state", + "operator_default_action", + "checklist_schema", + "status_summary_schema", + "action_hints_schema", + "required_item_count", + "blocked_item_count", + "hint_count", + "handoff_sections", + "allows_process_start", + "allows_payload_traffic", + "guardrail_summary" +) +$requiredSectionKeys = @("checklist", "status_summary", "action_hints", "guardrails") +$requiredGuardrailFields = @("activation_blocked", "process_start_allowed", "health_probe_enabled", "payload_traffic", "allows_process_start", "allows_payload_traffic") + +function Get-PropertyValue { + param([object]$Item, [string]$Name, [object]$Default = $null) + if ($null -eq $Item) { return $Default } + if ($Item -is [System.Collections.IDictionary]) { + if ($Item.Contains($Name)) { return $Item[$Name] } + return $Default + } + $property = $Item.PSObject.Properties[$Name] + if ($null -eq $property) { return $Default } + return $property.Value +} + +function Test-ObjectHasFields { + param([object]$Item, [string[]]$Fields) + if ($null -eq $Item) { return $false } + foreach ($field in $Fields) { + if ($Item -is [System.Collections.IDictionary]) { + if (-not $Item.Contains($field)) { return $false } + continue + } + if ($null -eq $Item.PSObject.Properties[$field]) { return $false } + } + return $true +} + +function Test-ArrayContainsAll { + param([object[]]$Actual, [string[]]$Required) + $values = @($Actual | ForEach-Object { [string]$_ }) + foreach ($item in $Required) { + if ($values -notcontains $item) { return $false } + } + return $true +} + +& powershell -ExecutionPolicy Bypass -File (Join-Path $PSScriptRoot "c19z80-remote-workspace-real-adapter-runtime-gate-preflight-action-hints-compatibility-smoke.ps1") ` + -ApiBaseUrl $ApiBaseUrl ` + -ClusterID $ClusterID ` + -ActorUserID $ActorUserID ` + -RequestedNodeName $RequestedNodeName ` + -DefaultNodeName $DefaultNodeName ` + -MatrixNodeName $MatrixNodeName ` + -ResultPath $sourceResultPath | Out-Null + +$sourceFile = Join-Path $repoRoot $sourceResultPath +$sourceResult = Get-Content -Raw -Path $sourceFile | ConvertFrom-Json +$hints = Get-PropertyValue -Item $sourceResult -Name "runtime_gate_preflight_action_hints" -Default $null +$guardrails = Get-PropertyValue -Item $hints -Name "guardrail_summary" -Default $null + +$handoffSections = @( + [ordered]@{ + key = "checklist" + schema_version = "rap.remote_workspace_real_adapter_runtime_gate_preflight_checklist.v1" + status = "blocked_required_items_missing" + operator_action = "review_required_preflight_items" + }, + [ordered]@{ + key = "status_summary" + schema_version = "rap.remote_workspace_real_adapter_runtime_gate_preflight_status_summary.v1" + status = "blocked_all_required_items_missing" + operator_action = "review_blocking_counts" + }, + [ordered]@{ + key = "action_hints" + schema_version = Get-PropertyValue -Item $hints -Name "schema_version" -Default $null + status = Get-PropertyValue -Item $hints -Name "hint_status" -Default $null + operator_action = "follow_preflight_actions_without_runtime_enablement" + }, + [ordered]@{ + key = "guardrails" + schema_version = "rap.remote_workspace_real_adapter_runtime_gate_guardrails.v1" + status = "runtime_disabled" + operator_action = "keep_real_adapter_disabled" + } +) + +$bundle = [ordered]@{ + schema_version = "rap.remote_workspace_real_adapter_runtime_gate_preflight_operator_handoff_bundle.v1" + source_action_hints_schema = Get-PropertyValue -Item $hints -Name "schema_version" -Default $null + handoff_status = "blocked_preflight_operator_review_required" + runtime_gate_state = Get-PropertyValue -Item $hints -Name "runtime_gate_state" -Default $null + operator_default_action = Get-PropertyValue -Item $hints -Name "operator_default_action" -Default $null + checklist_schema = "rap.remote_workspace_real_adapter_runtime_gate_preflight_checklist.v1" + status_summary_schema = "rap.remote_workspace_real_adapter_runtime_gate_preflight_status_summary.v1" + action_hints_schema = Get-PropertyValue -Item $hints -Name "schema_version" -Default $null + required_item_count = 6 + blocked_item_count = 6 + hint_count = Get-PropertyValue -Item $hints -Name "hint_count" -Default $null + handoff_sections = $handoffSections + allows_process_start = Get-PropertyValue -Item $hints -Name "allows_process_start" -Default $null + allows_payload_traffic = Get-PropertyValue -Item $hints -Name "allows_payload_traffic" -Default $null + guardrail_summary = $guardrails +} + +$bundleFieldsCompatible = Test-ObjectHasFields -Item $bundle -Fields $requiredBundleFields +$sectionKeysCompatible = Test-ArrayContainsAll -Actual @($handoffSections | ForEach-Object { Get-PropertyValue -Item $_ -Name "key" -Default "" }) -Required $requiredSectionKeys +$bundleValuesCompatible = ( + [string](Get-PropertyValue -Item $bundle -Name "schema_version" -Default "") -eq "rap.remote_workspace_real_adapter_runtime_gate_preflight_operator_handoff_bundle.v1" -and + [string](Get-PropertyValue -Item $bundle -Name "source_action_hints_schema" -Default "") -eq "rap.remote_workspace_real_adapter_runtime_gate_preflight_action_hints.v1" -and + [string](Get-PropertyValue -Item $bundle -Name "handoff_status" -Default "") -eq "blocked_preflight_operator_review_required" -and + [string](Get-PropertyValue -Item $bundle -Name "runtime_gate_state" -Default "") -eq "blocked" -and + [string](Get-PropertyValue -Item $bundle -Name "operator_default_action" -Default "") -eq "keep_real_adapter_disabled" -and + [string](Get-PropertyValue -Item $bundle -Name "checklist_schema" -Default "") -eq "rap.remote_workspace_real_adapter_runtime_gate_preflight_checklist.v1" -and + [string](Get-PropertyValue -Item $bundle -Name "status_summary_schema" -Default "") -eq "rap.remote_workspace_real_adapter_runtime_gate_preflight_status_summary.v1" -and + [string](Get-PropertyValue -Item $bundle -Name "action_hints_schema" -Default "") -eq "rap.remote_workspace_real_adapter_runtime_gate_preflight_action_hints.v1" -and + [int](Get-PropertyValue -Item $bundle -Name "required_item_count" -Default -1) -eq 6 -and + [int](Get-PropertyValue -Item $bundle -Name "blocked_item_count" -Default -1) -eq 6 -and + [int](Get-PropertyValue -Item $bundle -Name "hint_count" -Default -1) -eq 6 -and + -not [bool](Get-PropertyValue -Item $bundle -Name "allows_process_start" -Default $true) -and + -not [bool](Get-PropertyValue -Item $bundle -Name "allows_payload_traffic" -Default $true) +) +$guardrailsCompatible = ( + (Test-ObjectHasFields -Item $guardrails -Fields $requiredGuardrailFields) -and + [bool](Get-PropertyValue -Item $guardrails -Name "activation_blocked" -Default $false) -and + -not [bool](Get-PropertyValue -Item $guardrails -Name "process_start_allowed" -Default $true) -and + -not [bool](Get-PropertyValue -Item $guardrails -Name "health_probe_enabled" -Default $true) -and + [string](Get-PropertyValue -Item $guardrails -Name "payload_traffic" -Default "") -eq "none" -and + -not [bool](Get-PropertyValue -Item $guardrails -Name "allows_process_start" -Default $true) -and + -not [bool](Get-PropertyValue -Item $guardrails -Name "allows_payload_traffic" -Default $true) +) + +$checks = [ordered]@{ + source_smoke_passed = ([bool]$sourceResult.passed) + source_schema_expected = ([string]$sourceResult.schema_version -eq "c19z80.remote_workspace_real_adapter_runtime_gate_preflight_action_hints_compatibility_smoke.v1") + action_hints_present = ($null -ne $hints) + bundle_fields_compatible = $bundleFieldsCompatible + bundle_values_compatible = $bundleValuesCompatible + section_keys_compatible = $sectionKeysCompatible + guardrails_compatible = $guardrailsCompatible +} +$failed = @($checks.GetEnumerator() | Where-Object { -not $_.Value } | ForEach-Object { $_.Key }) + +$result = [ordered]@{ + schema_version = "c19z81.remote_workspace_real_adapter_runtime_gate_preflight_operator_handoff_bundle_smoke.v1" + source_result_path = $sourceFile + cluster_id = $ClusterID + required_bundle_fields = $requiredBundleFields + required_section_keys = $requiredSectionKeys + required_guardrail_fields = $requiredGuardrailFields + runtime_gate_preflight_operator_handoff_bundle = $bundle + checks = $checks + failed_checks = $failed + passed = ($failed.Count -eq 0) +} + +$fullResultPath = Join-Path $repoRoot $ResultPath +$resultDir = Split-Path -Parent $fullResultPath +if ($resultDir) { New-Item -ItemType Directory -Force -Path $resultDir | Out-Null } +$result | ConvertTo-Json -Depth 100 | Set-Content -Encoding UTF8 -Path $fullResultPath + +if (-not $result.passed) { + throw "C19Z81 remote workspace real-adapter runtime gate preflight operator handoff bundle smoke failed. Result: $fullResultPath Failed: $($failed -join ', ')" +} + +Write-Host "C19Z81 remote workspace real-adapter runtime gate preflight operator handoff bundle smoke passed. Result: $fullResultPath" +$result diff --git a/scripts/fabric/c19z82-remote-workspace-real-adapter-runtime-gate-preflight-operator-handoff-bundle-compatibility-smoke.ps1 b/scripts/fabric/c19z82-remote-workspace-real-adapter-runtime-gate-preflight-operator-handoff-bundle-compatibility-smoke.ps1 new file mode 100644 index 0000000..91b2fb9 --- /dev/null +++ b/scripts/fabric/c19z82-remote-workspace-real-adapter-runtime-gate-preflight-operator-handoff-bundle-compatibility-smoke.ps1 @@ -0,0 +1,150 @@ +param( + [string]$ApiBaseUrl = "http://192.168.200.61:18121/api/v1", + [string]$ClusterID = "cfc0743d-d960-49fb-9de8-96e063d5e4aa", + [string]$ActorUserID = "f67d943f-5397-4b3a-a229-695fe67ad700", + [string]$RequestedNodeName = "test-1", + [string]$DefaultNodeName = "test-2", + [string]$MatrixNodeName = "test-1", + [string]$ResultPath = "artifacts\c19z82-remote-workspace-real-adapter-runtime-gate-preflight-operator-handoff-bundle-compatibility-smoke-result.json" +) + +Set-StrictMode -Version Latest +$ErrorActionPreference = "Stop" + +$repoRoot = (Resolve-Path (Join-Path $PSScriptRoot "..\..")).ProviderPath +$sourceResultPath = "artifacts\c19z82-remote-workspace-real-adapter-runtime-gate-preflight-operator-handoff-bundle-source-result.json" +$requiredBundleFields = @( + "schema_version", + "source_action_hints_schema", + "handoff_status", + "runtime_gate_state", + "operator_default_action", + "checklist_schema", + "status_summary_schema", + "action_hints_schema", + "required_item_count", + "blocked_item_count", + "hint_count", + "handoff_sections", + "allows_process_start", + "allows_payload_traffic", + "guardrail_summary" +) +$requiredSectionKeys = @("checklist", "status_summary", "action_hints", "guardrails") +$requiredSectionFields = @("key", "schema_version", "status", "operator_action") +$requiredGuardrailFields = @("activation_blocked", "process_start_allowed", "health_probe_enabled", "payload_traffic", "allows_process_start", "allows_payload_traffic") + +function Get-PropertyValue { + param([object]$Item, [string]$Name, [object]$Default = $null) + if ($null -eq $Item) { return $Default } + if ($Item -is [System.Collections.IDictionary]) { + if ($Item.Contains($Name)) { return $Item[$Name] } + return $Default + } + $property = $Item.PSObject.Properties[$Name] + if ($null -eq $property) { return $Default } + return $property.Value +} + +function Test-ObjectHasFields { + param([object]$Item, [string[]]$Fields) + if ($null -eq $Item) { return $false } + foreach ($field in $Fields) { + if ($Item -is [System.Collections.IDictionary]) { + if (-not $Item.Contains($field)) { return $false } + continue + } + if ($null -eq $Item.PSObject.Properties[$field]) { return $false } + } + return $true +} + +function Test-ArrayContainsAll { + param([object[]]$Actual, [string[]]$Required) + $values = @($Actual | ForEach-Object { [string]$_ }) + foreach ($item in $Required) { + if ($values -notcontains $item) { return $false } + } + return $true +} + +& powershell -ExecutionPolicy Bypass -File (Join-Path $PSScriptRoot "c19z81-remote-workspace-real-adapter-runtime-gate-preflight-operator-handoff-bundle-smoke.ps1") ` + -ApiBaseUrl $ApiBaseUrl ` + -ClusterID $ClusterID ` + -ActorUserID $ActorUserID ` + -RequestedNodeName $RequestedNodeName ` + -DefaultNodeName $DefaultNodeName ` + -MatrixNodeName $MatrixNodeName ` + -ResultPath $sourceResultPath | Out-Null + +$sourceFile = Join-Path $repoRoot $sourceResultPath +$sourceResult = Get-Content -Raw -Path $sourceFile | ConvertFrom-Json +$bundle = Get-PropertyValue -Item $sourceResult -Name "runtime_gate_preflight_operator_handoff_bundle" -Default $null +$sections = @(Get-PropertyValue -Item $bundle -Name "handoff_sections" -Default @()) +$guardrails = Get-PropertyValue -Item $bundle -Name "guardrail_summary" -Default $null + +$bundleFieldsCompatible = Test-ObjectHasFields -Item $bundle -Fields $requiredBundleFields +$sectionFieldsCompatible = (@($sections | Where-Object { -not (Test-ObjectHasFields -Item $_ -Fields $requiredSectionFields) }).Count -eq 0) +$sectionKeysCompatible = Test-ArrayContainsAll -Actual @($sections | ForEach-Object { Get-PropertyValue -Item $_ -Name "key" -Default "" }) -Required $requiredSectionKeys +$bundleValuesCompatible = ( + [string](Get-PropertyValue -Item $bundle -Name "schema_version" -Default "") -eq "rap.remote_workspace_real_adapter_runtime_gate_preflight_operator_handoff_bundle.v1" -and + [string](Get-PropertyValue -Item $bundle -Name "source_action_hints_schema" -Default "") -eq "rap.remote_workspace_real_adapter_runtime_gate_preflight_action_hints.v1" -and + [string](Get-PropertyValue -Item $bundle -Name "handoff_status" -Default "") -eq "blocked_preflight_operator_review_required" -and + [string](Get-PropertyValue -Item $bundle -Name "runtime_gate_state" -Default "") -eq "blocked" -and + [string](Get-PropertyValue -Item $bundle -Name "operator_default_action" -Default "") -eq "keep_real_adapter_disabled" -and + [string](Get-PropertyValue -Item $bundle -Name "checklist_schema" -Default "") -eq "rap.remote_workspace_real_adapter_runtime_gate_preflight_checklist.v1" -and + [string](Get-PropertyValue -Item $bundle -Name "status_summary_schema" -Default "") -eq "rap.remote_workspace_real_adapter_runtime_gate_preflight_status_summary.v1" -and + [string](Get-PropertyValue -Item $bundle -Name "action_hints_schema" -Default "") -eq "rap.remote_workspace_real_adapter_runtime_gate_preflight_action_hints.v1" -and + [int](Get-PropertyValue -Item $bundle -Name "required_item_count" -Default -1) -eq 6 -and + [int](Get-PropertyValue -Item $bundle -Name "blocked_item_count" -Default -1) -eq 6 -and + [int](Get-PropertyValue -Item $bundle -Name "hint_count" -Default -1) -eq 6 -and + -not [bool](Get-PropertyValue -Item $bundle -Name "allows_process_start" -Default $true) -and + -not [bool](Get-PropertyValue -Item $bundle -Name "allows_payload_traffic" -Default $true) +) +$guardrailsCompatible = ( + (Test-ObjectHasFields -Item $guardrails -Fields $requiredGuardrailFields) -and + [bool](Get-PropertyValue -Item $guardrails -Name "activation_blocked" -Default $false) -and + -not [bool](Get-PropertyValue -Item $guardrails -Name "process_start_allowed" -Default $true) -and + -not [bool](Get-PropertyValue -Item $guardrails -Name "health_probe_enabled" -Default $true) -and + [string](Get-PropertyValue -Item $guardrails -Name "payload_traffic" -Default "") -eq "none" -and + -not [bool](Get-PropertyValue -Item $guardrails -Name "allows_process_start" -Default $true) -and + -not [bool](Get-PropertyValue -Item $guardrails -Name "allows_payload_traffic" -Default $true) +) + +$checks = [ordered]@{ + source_smoke_passed = ([bool]$sourceResult.passed) + source_schema_expected = ([string]$sourceResult.schema_version -eq "c19z81.remote_workspace_real_adapter_runtime_gate_preflight_operator_handoff_bundle_smoke.v1") + operator_handoff_bundle_present = ($null -ne $bundle) + bundle_fields_compatible = $bundleFieldsCompatible + bundle_values_compatible = $bundleValuesCompatible + section_fields_compatible = $sectionFieldsCompatible + section_keys_compatible = $sectionKeysCompatible + guardrails_compatible = $guardrailsCompatible +} +$failed = @($checks.GetEnumerator() | Where-Object { -not $_.Value } | ForEach-Object { $_.Key }) + +$result = [ordered]@{ + schema_version = "c19z82.remote_workspace_real_adapter_runtime_gate_preflight_operator_handoff_bundle_compatibility_smoke.v1" + source_result_path = $sourceFile + cluster_id = $ClusterID + required_bundle_fields = $requiredBundleFields + required_section_fields = $requiredSectionFields + required_section_keys = $requiredSectionKeys + required_guardrail_fields = $requiredGuardrailFields + runtime_gate_preflight_operator_handoff_bundle = $bundle + checks = $checks + failed_checks = $failed + passed = ($failed.Count -eq 0) +} + +$fullResultPath = Join-Path $repoRoot $ResultPath +$resultDir = Split-Path -Parent $fullResultPath +if ($resultDir) { New-Item -ItemType Directory -Force -Path $resultDir | Out-Null } +$result | ConvertTo-Json -Depth 100 | Set-Content -Encoding UTF8 -Path $fullResultPath + +if (-not $result.passed) { + throw "C19Z82 remote workspace real-adapter runtime gate preflight operator handoff bundle compatibility smoke failed. Result: $fullResultPath Failed: $($failed -join ', ')" +} + +Write-Host "C19Z82 remote workspace real-adapter runtime gate preflight operator handoff bundle compatibility smoke passed. Result: $fullResultPath" +$result diff --git a/scripts/fabric/c19z83-remote-workspace-real-adapter-runtime-gate-preflight-release-marker-smoke.ps1 b/scripts/fabric/c19z83-remote-workspace-real-adapter-runtime-gate-preflight-release-marker-smoke.ps1 new file mode 100644 index 0000000..76866d3 --- /dev/null +++ b/scripts/fabric/c19z83-remote-workspace-real-adapter-runtime-gate-preflight-release-marker-smoke.ps1 @@ -0,0 +1,142 @@ +param( + [string]$ApiBaseUrl = "http://192.168.200.61:18121/api/v1", + [string]$ClusterID = "cfc0743d-d960-49fb-9de8-96e063d5e4aa", + [string]$ActorUserID = "f67d943f-5397-4b3a-a229-695fe67ad700", + [string]$RequestedNodeName = "test-1", + [string]$DefaultNodeName = "test-2", + [string]$MatrixNodeName = "test-1", + [string]$ResultPath = "artifacts\c19z83-remote-workspace-real-adapter-runtime-gate-preflight-release-marker-smoke-result.json" +) + +Set-StrictMode -Version Latest +$ErrorActionPreference = "Stop" + +$repoRoot = (Resolve-Path (Join-Path $PSScriptRoot "..\..")).ProviderPath +$sourceResultPath = "artifacts\c19z83-remote-workspace-real-adapter-runtime-gate-preflight-release-marker-source-result.json" +$requiredMarkerFields = @( + "schema_version", + "source_operator_handoff_bundle_schema", + "release_status", + "release_marker", + "runtime_gate_state", + "runtime_effect", + "operator_default_action", + "handoff_status", + "covered_stage_range", + "allows_process_start", + "allows_payload_traffic", + "guardrail_summary" +) +$requiredGuardrailFields = @("activation_blocked", "process_start_allowed", "health_probe_enabled", "payload_traffic", "allows_process_start", "allows_payload_traffic") + +function Get-PropertyValue { + param([object]$Item, [string]$Name, [object]$Default = $null) + if ($null -eq $Item) { return $Default } + if ($Item -is [System.Collections.IDictionary]) { + if ($Item.Contains($Name)) { return $Item[$Name] } + return $Default + } + $property = $Item.PSObject.Properties[$Name] + if ($null -eq $property) { return $Default } + return $property.Value +} + +function Test-ObjectHasFields { + param([object]$Item, [string[]]$Fields) + if ($null -eq $Item) { return $false } + foreach ($field in $Fields) { + if ($Item -is [System.Collections.IDictionary]) { + if (-not $Item.Contains($field)) { return $false } + continue + } + if ($null -eq $Item.PSObject.Properties[$field]) { return $false } + } + return $true +} + +& powershell -ExecutionPolicy Bypass -File (Join-Path $PSScriptRoot "c19z82-remote-workspace-real-adapter-runtime-gate-preflight-operator-handoff-bundle-compatibility-smoke.ps1") ` + -ApiBaseUrl $ApiBaseUrl ` + -ClusterID $ClusterID ` + -ActorUserID $ActorUserID ` + -RequestedNodeName $RequestedNodeName ` + -DefaultNodeName $DefaultNodeName ` + -MatrixNodeName $MatrixNodeName ` + -ResultPath $sourceResultPath | Out-Null + +$sourceFile = Join-Path $repoRoot $sourceResultPath +$sourceResult = Get-Content -Raw -Path $sourceFile | ConvertFrom-Json +$bundle = Get-PropertyValue -Item $sourceResult -Name "runtime_gate_preflight_operator_handoff_bundle" -Default $null +$guardrails = Get-PropertyValue -Item $bundle -Name "guardrail_summary" -Default $null + +$releaseMarker = [ordered]@{ + schema_version = "rap.remote_workspace_real_adapter_runtime_gate_preflight_release_marker.v1" + source_operator_handoff_bundle_schema = Get-PropertyValue -Item $bundle -Name "schema_version" -Default $null + release_status = "contract_only_ready_for_operator_preflight_handoff" + release_marker = "c19z83_disabled_real_adapter_runtime_gate_preflight_contract_only" + runtime_gate_state = Get-PropertyValue -Item $bundle -Name "runtime_gate_state" -Default $null + runtime_effect = "contract_only_no_runtime_enablement" + operator_default_action = Get-PropertyValue -Item $bundle -Name "operator_default_action" -Default $null + handoff_status = Get-PropertyValue -Item $bundle -Name "handoff_status" -Default $null + covered_stage_range = "C19Z73-C19Z82" + allows_process_start = Get-PropertyValue -Item $bundle -Name "allows_process_start" -Default $null + allows_payload_traffic = Get-PropertyValue -Item $bundle -Name "allows_payload_traffic" -Default $null + guardrail_summary = $guardrails +} + +$markerFieldsCompatible = Test-ObjectHasFields -Item $releaseMarker -Fields $requiredMarkerFields +$markerValuesCompatible = ( + [string](Get-PropertyValue -Item $releaseMarker -Name "schema_version" -Default "") -eq "rap.remote_workspace_real_adapter_runtime_gate_preflight_release_marker.v1" -and + [string](Get-PropertyValue -Item $releaseMarker -Name "source_operator_handoff_bundle_schema" -Default "") -eq "rap.remote_workspace_real_adapter_runtime_gate_preflight_operator_handoff_bundle.v1" -and + [string](Get-PropertyValue -Item $releaseMarker -Name "release_status" -Default "") -eq "contract_only_ready_for_operator_preflight_handoff" -and + [string](Get-PropertyValue -Item $releaseMarker -Name "release_marker" -Default "") -eq "c19z83_disabled_real_adapter_runtime_gate_preflight_contract_only" -and + [string](Get-PropertyValue -Item $releaseMarker -Name "runtime_gate_state" -Default "") -eq "blocked" -and + [string](Get-PropertyValue -Item $releaseMarker -Name "runtime_effect" -Default "") -eq "contract_only_no_runtime_enablement" -and + [string](Get-PropertyValue -Item $releaseMarker -Name "operator_default_action" -Default "") -eq "keep_real_adapter_disabled" -and + [string](Get-PropertyValue -Item $releaseMarker -Name "handoff_status" -Default "") -eq "blocked_preflight_operator_review_required" -and + [string](Get-PropertyValue -Item $releaseMarker -Name "covered_stage_range" -Default "") -eq "C19Z73-C19Z82" -and + -not [bool](Get-PropertyValue -Item $releaseMarker -Name "allows_process_start" -Default $true) -and + -not [bool](Get-PropertyValue -Item $releaseMarker -Name "allows_payload_traffic" -Default $true) +) +$guardrailsCompatible = ( + (Test-ObjectHasFields -Item $guardrails -Fields $requiredGuardrailFields) -and + [bool](Get-PropertyValue -Item $guardrails -Name "activation_blocked" -Default $false) -and + -not [bool](Get-PropertyValue -Item $guardrails -Name "process_start_allowed" -Default $true) -and + -not [bool](Get-PropertyValue -Item $guardrails -Name "health_probe_enabled" -Default $true) -and + [string](Get-PropertyValue -Item $guardrails -Name "payload_traffic" -Default "") -eq "none" -and + -not [bool](Get-PropertyValue -Item $guardrails -Name "allows_process_start" -Default $true) -and + -not [bool](Get-PropertyValue -Item $guardrails -Name "allows_payload_traffic" -Default $true) +) + +$checks = [ordered]@{ + source_smoke_passed = ([bool]$sourceResult.passed) + source_schema_expected = ([string]$sourceResult.schema_version -eq "c19z82.remote_workspace_real_adapter_runtime_gate_preflight_operator_handoff_bundle_compatibility_smoke.v1") + operator_handoff_bundle_present = ($null -ne $bundle) + marker_fields_compatible = $markerFieldsCompatible + marker_values_compatible = $markerValuesCompatible + guardrails_compatible = $guardrailsCompatible +} +$failed = @($checks.GetEnumerator() | Where-Object { -not $_.Value } | ForEach-Object { $_.Key }) + +$result = [ordered]@{ + schema_version = "c19z83.remote_workspace_real_adapter_runtime_gate_preflight_release_marker_smoke.v1" + source_result_path = $sourceFile + cluster_id = $ClusterID + required_marker_fields = $requiredMarkerFields + required_guardrail_fields = $requiredGuardrailFields + runtime_gate_preflight_release_marker = $releaseMarker + checks = $checks + failed_checks = $failed + passed = ($failed.Count -eq 0) +} + +$fullResultPath = Join-Path $repoRoot $ResultPath +$resultDir = Split-Path -Parent $fullResultPath +if ($resultDir) { New-Item -ItemType Directory -Force -Path $resultDir | Out-Null } +$result | ConvertTo-Json -Depth 100 | Set-Content -Encoding UTF8 -Path $fullResultPath + +if (-not $result.passed) { + throw "C19Z83 remote workspace real-adapter runtime gate preflight release marker smoke failed. Result: $fullResultPath Failed: $($failed -join ', ')" +} + +Write-Host "C19Z83 remote workspace real-adapter runtime gate preflight release marker smoke passed. Result: $fullResultPath" +$result diff --git a/scripts/fabric/c19z84-remote-workspace-real-adapter-runtime-gate-preflight-release-marker-compatibility-smoke.ps1 b/scripts/fabric/c19z84-remote-workspace-real-adapter-runtime-gate-preflight-release-marker-compatibility-smoke.ps1 new file mode 100644 index 0000000..76b2985 --- /dev/null +++ b/scripts/fabric/c19z84-remote-workspace-real-adapter-runtime-gate-preflight-release-marker-compatibility-smoke.ps1 @@ -0,0 +1,127 @@ +param( + [string]$ApiBaseUrl = "http://192.168.200.61:18121/api/v1", + [string]$ClusterID = "cfc0743d-d960-49fb-9de8-96e063d5e4aa", + [string]$ActorUserID = "f67d943f-5397-4b3a-a229-695fe67ad700", + [string]$RequestedNodeName = "test-1", + [string]$DefaultNodeName = "test-2", + [string]$MatrixNodeName = "test-1", + [string]$ResultPath = "artifacts\c19z84-remote-workspace-real-adapter-runtime-gate-preflight-release-marker-compatibility-smoke-result.json" +) + +Set-StrictMode -Version Latest +$ErrorActionPreference = "Stop" + +$repoRoot = (Resolve-Path (Join-Path $PSScriptRoot "..\..")).ProviderPath +$sourceResultPath = "artifacts\c19z84-remote-workspace-real-adapter-runtime-gate-preflight-release-marker-source-result.json" +$requiredMarkerFields = @( + "schema_version", + "source_operator_handoff_bundle_schema", + "release_status", + "release_marker", + "runtime_gate_state", + "runtime_effect", + "operator_default_action", + "handoff_status", + "covered_stage_range", + "allows_process_start", + "allows_payload_traffic", + "guardrail_summary" +) +$requiredGuardrailFields = @("activation_blocked", "process_start_allowed", "health_probe_enabled", "payload_traffic", "allows_process_start", "allows_payload_traffic") + +function Get-PropertyValue { + param([object]$Item, [string]$Name, [object]$Default = $null) + if ($null -eq $Item) { return $Default } + if ($Item -is [System.Collections.IDictionary]) { + if ($Item.Contains($Name)) { return $Item[$Name] } + return $Default + } + $property = $Item.PSObject.Properties[$Name] + if ($null -eq $property) { return $Default } + return $property.Value +} + +function Test-ObjectHasFields { + param([object]$Item, [string[]]$Fields) + if ($null -eq $Item) { return $false } + foreach ($field in $Fields) { + if ($Item -is [System.Collections.IDictionary]) { + if (-not $Item.Contains($field)) { return $false } + continue + } + if ($null -eq $Item.PSObject.Properties[$field]) { return $false } + } + return $true +} + +& powershell -ExecutionPolicy Bypass -File (Join-Path $PSScriptRoot "c19z83-remote-workspace-real-adapter-runtime-gate-preflight-release-marker-smoke.ps1") ` + -ApiBaseUrl $ApiBaseUrl ` + -ClusterID $ClusterID ` + -ActorUserID $ActorUserID ` + -RequestedNodeName $RequestedNodeName ` + -DefaultNodeName $DefaultNodeName ` + -MatrixNodeName $MatrixNodeName ` + -ResultPath $sourceResultPath | Out-Null + +$sourceFile = Join-Path $repoRoot $sourceResultPath +$sourceResult = Get-Content -Raw -Path $sourceFile | ConvertFrom-Json +$releaseMarker = Get-PropertyValue -Item $sourceResult -Name "runtime_gate_preflight_release_marker" -Default $null +$guardrails = Get-PropertyValue -Item $releaseMarker -Name "guardrail_summary" -Default $null + +$markerFieldsCompatible = Test-ObjectHasFields -Item $releaseMarker -Fields $requiredMarkerFields +$markerValuesCompatible = ( + [string](Get-PropertyValue -Item $releaseMarker -Name "schema_version" -Default "") -eq "rap.remote_workspace_real_adapter_runtime_gate_preflight_release_marker.v1" -and + [string](Get-PropertyValue -Item $releaseMarker -Name "source_operator_handoff_bundle_schema" -Default "") -eq "rap.remote_workspace_real_adapter_runtime_gate_preflight_operator_handoff_bundle.v1" -and + [string](Get-PropertyValue -Item $releaseMarker -Name "release_status" -Default "") -eq "contract_only_ready_for_operator_preflight_handoff" -and + [string](Get-PropertyValue -Item $releaseMarker -Name "release_marker" -Default "") -eq "c19z83_disabled_real_adapter_runtime_gate_preflight_contract_only" -and + [string](Get-PropertyValue -Item $releaseMarker -Name "runtime_gate_state" -Default "") -eq "blocked" -and + [string](Get-PropertyValue -Item $releaseMarker -Name "runtime_effect" -Default "") -eq "contract_only_no_runtime_enablement" -and + [string](Get-PropertyValue -Item $releaseMarker -Name "operator_default_action" -Default "") -eq "keep_real_adapter_disabled" -and + [string](Get-PropertyValue -Item $releaseMarker -Name "handoff_status" -Default "") -eq "blocked_preflight_operator_review_required" -and + [string](Get-PropertyValue -Item $releaseMarker -Name "covered_stage_range" -Default "") -eq "C19Z73-C19Z82" -and + -not [bool](Get-PropertyValue -Item $releaseMarker -Name "allows_process_start" -Default $true) -and + -not [bool](Get-PropertyValue -Item $releaseMarker -Name "allows_payload_traffic" -Default $true) +) +$guardrailsCompatible = ( + (Test-ObjectHasFields -Item $guardrails -Fields $requiredGuardrailFields) -and + [bool](Get-PropertyValue -Item $guardrails -Name "activation_blocked" -Default $false) -and + -not [bool](Get-PropertyValue -Item $guardrails -Name "process_start_allowed" -Default $true) -and + -not [bool](Get-PropertyValue -Item $guardrails -Name "health_probe_enabled" -Default $true) -and + [string](Get-PropertyValue -Item $guardrails -Name "payload_traffic" -Default "") -eq "none" -and + -not [bool](Get-PropertyValue -Item $guardrails -Name "allows_process_start" -Default $true) -and + -not [bool](Get-PropertyValue -Item $guardrails -Name "allows_payload_traffic" -Default $true) +) + +$checks = [ordered]@{ + source_smoke_passed = ([bool]$sourceResult.passed) + source_schema_expected = ([string]$sourceResult.schema_version -eq "c19z83.remote_workspace_real_adapter_runtime_gate_preflight_release_marker_smoke.v1") + release_marker_present = ($null -ne $releaseMarker) + marker_fields_compatible = $markerFieldsCompatible + marker_values_compatible = $markerValuesCompatible + guardrails_compatible = $guardrailsCompatible +} +$failed = @($checks.GetEnumerator() | Where-Object { -not $_.Value } | ForEach-Object { $_.Key }) + +$result = [ordered]@{ + schema_version = "c19z84.remote_workspace_real_adapter_runtime_gate_preflight_release_marker_compatibility_smoke.v1" + source_result_path = $sourceFile + cluster_id = $ClusterID + required_marker_fields = $requiredMarkerFields + required_guardrail_fields = $requiredGuardrailFields + runtime_gate_preflight_release_marker = $releaseMarker + checks = $checks + failed_checks = $failed + passed = ($failed.Count -eq 0) +} + +$fullResultPath = Join-Path $repoRoot $ResultPath +$resultDir = Split-Path -Parent $fullResultPath +if ($resultDir) { New-Item -ItemType Directory -Force -Path $resultDir | Out-Null } +$result | ConvertTo-Json -Depth 100 | Set-Content -Encoding UTF8 -Path $fullResultPath + +if (-not $result.passed) { + throw "C19Z84 remote workspace real-adapter runtime gate preflight release marker compatibility smoke failed. Result: $fullResultPath Failed: $($failed -join ', ')" +} + +Write-Host "C19Z84 remote workspace real-adapter runtime gate preflight release marker compatibility smoke passed. Result: $fullResultPath" +$result diff --git a/scripts/fabric/c19z85-remote-workspace-real-adapter-runtime-gate-preflight-package-index-smoke.ps1 b/scripts/fabric/c19z85-remote-workspace-real-adapter-runtime-gate-preflight-package-index-smoke.ps1 new file mode 100644 index 0000000..533f080 --- /dev/null +++ b/scripts/fabric/c19z85-remote-workspace-real-adapter-runtime-gate-preflight-package-index-smoke.ps1 @@ -0,0 +1,148 @@ +param( + [string]$ApiBaseUrl = "http://192.168.200.61:18121/api/v1", + [string]$ClusterID = "cfc0743d-d960-49fb-9de8-96e063d5e4aa", + [string]$ActorUserID = "f67d943f-5397-4b3a-a229-695fe67ad700", + [string]$RequestedNodeName = "test-1", + [string]$DefaultNodeName = "test-2", + [string]$MatrixNodeName = "test-1", + [string]$ResultPath = "artifacts\c19z85-remote-workspace-real-adapter-runtime-gate-preflight-package-index-smoke-result.json" +) + +Set-StrictMode -Version Latest +$ErrorActionPreference = "Stop" + +$repoRoot = (Resolve-Path (Join-Path $PSScriptRoot "..\..")).ProviderPath +$sourceResultPath = "artifacts\c19z85-remote-workspace-real-adapter-runtime-gate-preflight-package-index-source-result.json" +$requiredIndexFields = @( + "schema_version", + "source_release_marker_schema", + "package_status", + "package_marker", + "covered_stage_range", + "covered_stage_count", + "latest_compatibility_stage", + "runtime_gate_state", + "runtime_effect", + "operator_default_action", + "release_status", + "allows_process_start", + "allows_payload_traffic", + "guardrail_summary" +) +$requiredGuardrailFields = @("activation_blocked", "process_start_allowed", "health_probe_enabled", "payload_traffic", "allows_process_start", "allows_payload_traffic") + +function Get-PropertyValue { + param([object]$Item, [string]$Name, [object]$Default = $null) + if ($null -eq $Item) { return $Default } + if ($Item -is [System.Collections.IDictionary]) { + if ($Item.Contains($Name)) { return $Item[$Name] } + return $Default + } + $property = $Item.PSObject.Properties[$Name] + if ($null -eq $property) { return $Default } + return $property.Value +} + +function Test-ObjectHasFields { + param([object]$Item, [string[]]$Fields) + if ($null -eq $Item) { return $false } + foreach ($field in $Fields) { + if ($Item -is [System.Collections.IDictionary]) { + if (-not $Item.Contains($field)) { return $false } + continue + } + if ($null -eq $Item.PSObject.Properties[$field]) { return $false } + } + return $true +} + +& powershell -ExecutionPolicy Bypass -File (Join-Path $PSScriptRoot "c19z84-remote-workspace-real-adapter-runtime-gate-preflight-release-marker-compatibility-smoke.ps1") ` + -ApiBaseUrl $ApiBaseUrl ` + -ClusterID $ClusterID ` + -ActorUserID $ActorUserID ` + -RequestedNodeName $RequestedNodeName ` + -DefaultNodeName $DefaultNodeName ` + -MatrixNodeName $MatrixNodeName ` + -ResultPath $sourceResultPath | Out-Null + +$sourceFile = Join-Path $repoRoot $sourceResultPath +$sourceResult = Get-Content -Raw -Path $sourceFile | ConvertFrom-Json +$releaseMarker = Get-PropertyValue -Item $sourceResult -Name "runtime_gate_preflight_release_marker" -Default $null +$guardrails = Get-PropertyValue -Item $releaseMarker -Name "guardrail_summary" -Default $null + +$packageIndex = [ordered]@{ + schema_version = "rap.remote_workspace_real_adapter_runtime_gate_preflight_package_index.v1" + source_release_marker_schema = Get-PropertyValue -Item $releaseMarker -Name "schema_version" -Default $null + package_status = "closed_contract_only" + package_marker = "c19z85_disabled_real_adapter_runtime_gate_preflight_package_closed_contract_only" + covered_stage_range = "C19Z73-C19Z84" + covered_stage_count = 12 + latest_compatibility_stage = "C19Z84" + runtime_gate_state = Get-PropertyValue -Item $releaseMarker -Name "runtime_gate_state" -Default $null + runtime_effect = Get-PropertyValue -Item $releaseMarker -Name "runtime_effect" -Default $null + operator_default_action = Get-PropertyValue -Item $releaseMarker -Name "operator_default_action" -Default $null + release_status = Get-PropertyValue -Item $releaseMarker -Name "release_status" -Default $null + allows_process_start = Get-PropertyValue -Item $releaseMarker -Name "allows_process_start" -Default $null + allows_payload_traffic = Get-PropertyValue -Item $releaseMarker -Name "allows_payload_traffic" -Default $null + guardrail_summary = $guardrails +} + +$indexFieldsCompatible = Test-ObjectHasFields -Item $packageIndex -Fields $requiredIndexFields +$indexValuesCompatible = ( + [string](Get-PropertyValue -Item $packageIndex -Name "schema_version" -Default "") -eq "rap.remote_workspace_real_adapter_runtime_gate_preflight_package_index.v1" -and + [string](Get-PropertyValue -Item $packageIndex -Name "source_release_marker_schema" -Default "") -eq "rap.remote_workspace_real_adapter_runtime_gate_preflight_release_marker.v1" -and + [string](Get-PropertyValue -Item $packageIndex -Name "package_status" -Default "") -eq "closed_contract_only" -and + [string](Get-PropertyValue -Item $packageIndex -Name "package_marker" -Default "") -eq "c19z85_disabled_real_adapter_runtime_gate_preflight_package_closed_contract_only" -and + [string](Get-PropertyValue -Item $packageIndex -Name "covered_stage_range" -Default "") -eq "C19Z73-C19Z84" -and + [int](Get-PropertyValue -Item $packageIndex -Name "covered_stage_count" -Default -1) -eq 12 -and + [string](Get-PropertyValue -Item $packageIndex -Name "latest_compatibility_stage" -Default "") -eq "C19Z84" -and + [string](Get-PropertyValue -Item $packageIndex -Name "runtime_gate_state" -Default "") -eq "blocked" -and + [string](Get-PropertyValue -Item $packageIndex -Name "runtime_effect" -Default "") -eq "contract_only_no_runtime_enablement" -and + [string](Get-PropertyValue -Item $packageIndex -Name "operator_default_action" -Default "") -eq "keep_real_adapter_disabled" -and + [string](Get-PropertyValue -Item $packageIndex -Name "release_status" -Default "") -eq "contract_only_ready_for_operator_preflight_handoff" -and + -not [bool](Get-PropertyValue -Item $packageIndex -Name "allows_process_start" -Default $true) -and + -not [bool](Get-PropertyValue -Item $packageIndex -Name "allows_payload_traffic" -Default $true) +) +$guardrailsCompatible = ( + (Test-ObjectHasFields -Item $guardrails -Fields $requiredGuardrailFields) -and + [bool](Get-PropertyValue -Item $guardrails -Name "activation_blocked" -Default $false) -and + -not [bool](Get-PropertyValue -Item $guardrails -Name "process_start_allowed" -Default $true) -and + -not [bool](Get-PropertyValue -Item $guardrails -Name "health_probe_enabled" -Default $true) -and + [string](Get-PropertyValue -Item $guardrails -Name "payload_traffic" -Default "") -eq "none" -and + -not [bool](Get-PropertyValue -Item $guardrails -Name "allows_process_start" -Default $true) -and + -not [bool](Get-PropertyValue -Item $guardrails -Name "allows_payload_traffic" -Default $true) +) + +$checks = [ordered]@{ + source_smoke_passed = ([bool]$sourceResult.passed) + source_schema_expected = ([string]$sourceResult.schema_version -eq "c19z84.remote_workspace_real_adapter_runtime_gate_preflight_release_marker_compatibility_smoke.v1") + release_marker_present = ($null -ne $releaseMarker) + index_fields_compatible = $indexFieldsCompatible + index_values_compatible = $indexValuesCompatible + guardrails_compatible = $guardrailsCompatible +} +$failed = @($checks.GetEnumerator() | Where-Object { -not $_.Value } | ForEach-Object { $_.Key }) + +$result = [ordered]@{ + schema_version = "c19z85.remote_workspace_real_adapter_runtime_gate_preflight_package_index_smoke.v1" + source_result_path = $sourceFile + cluster_id = $ClusterID + required_index_fields = $requiredIndexFields + required_guardrail_fields = $requiredGuardrailFields + runtime_gate_preflight_package_index = $packageIndex + checks = $checks + failed_checks = $failed + passed = ($failed.Count -eq 0) +} + +$fullResultPath = Join-Path $repoRoot $ResultPath +$resultDir = Split-Path -Parent $fullResultPath +if ($resultDir) { New-Item -ItemType Directory -Force -Path $resultDir | Out-Null } +$result | ConvertTo-Json -Depth 100 | Set-Content -Encoding UTF8 -Path $fullResultPath + +if (-not $result.passed) { + throw "C19Z85 remote workspace real-adapter runtime gate preflight package index smoke failed. Result: $fullResultPath Failed: $($failed -join ', ')" +} + +Write-Host "C19Z85 remote workspace real-adapter runtime gate preflight package index smoke passed. Result: $fullResultPath" +$result diff --git a/scripts/fabric/c19z86-remote-workspace-real-adapter-runtime-gate-preflight-package-index-compatibility-smoke.ps1 b/scripts/fabric/c19z86-remote-workspace-real-adapter-runtime-gate-preflight-package-index-compatibility-smoke.ps1 new file mode 100644 index 0000000..efe912f --- /dev/null +++ b/scripts/fabric/c19z86-remote-workspace-real-adapter-runtime-gate-preflight-package-index-compatibility-smoke.ps1 @@ -0,0 +1,131 @@ +param( + [string]$ApiBaseUrl = "http://192.168.200.61:18121/api/v1", + [string]$ClusterID = "cfc0743d-d960-49fb-9de8-96e063d5e4aa", + [string]$ActorUserID = "f67d943f-5397-4b3a-a229-695fe67ad700", + [string]$RequestedNodeName = "test-1", + [string]$DefaultNodeName = "test-2", + [string]$MatrixNodeName = "test-1", + [string]$ResultPath = "artifacts\c19z86-remote-workspace-real-adapter-runtime-gate-preflight-package-index-compatibility-smoke-result.json" +) + +Set-StrictMode -Version Latest +$ErrorActionPreference = "Stop" + +$repoRoot = (Resolve-Path (Join-Path $PSScriptRoot "..\..")).ProviderPath +$sourceResultPath = "artifacts\c19z86-remote-workspace-real-adapter-runtime-gate-preflight-package-index-source-result.json" +$requiredIndexFields = @( + "schema_version", + "source_release_marker_schema", + "package_status", + "package_marker", + "covered_stage_range", + "covered_stage_count", + "latest_compatibility_stage", + "runtime_gate_state", + "runtime_effect", + "operator_default_action", + "release_status", + "allows_process_start", + "allows_payload_traffic", + "guardrail_summary" +) +$requiredGuardrailFields = @("activation_blocked", "process_start_allowed", "health_probe_enabled", "payload_traffic", "allows_process_start", "allows_payload_traffic") + +function Get-PropertyValue { + param([object]$Item, [string]$Name, [object]$Default = $null) + if ($null -eq $Item) { return $Default } + if ($Item -is [System.Collections.IDictionary]) { + if ($Item.Contains($Name)) { return $Item[$Name] } + return $Default + } + $property = $Item.PSObject.Properties[$Name] + if ($null -eq $property) { return $Default } + return $property.Value +} + +function Test-ObjectHasFields { + param([object]$Item, [string[]]$Fields) + if ($null -eq $Item) { return $false } + foreach ($field in $Fields) { + if ($Item -is [System.Collections.IDictionary]) { + if (-not $Item.Contains($field)) { return $false } + continue + } + if ($null -eq $Item.PSObject.Properties[$field]) { return $false } + } + return $true +} + +& powershell -ExecutionPolicy Bypass -File (Join-Path $PSScriptRoot "c19z85-remote-workspace-real-adapter-runtime-gate-preflight-package-index-smoke.ps1") ` + -ApiBaseUrl $ApiBaseUrl ` + -ClusterID $ClusterID ` + -ActorUserID $ActorUserID ` + -RequestedNodeName $RequestedNodeName ` + -DefaultNodeName $DefaultNodeName ` + -MatrixNodeName $MatrixNodeName ` + -ResultPath $sourceResultPath | Out-Null + +$sourceFile = Join-Path $repoRoot $sourceResultPath +$sourceResult = Get-Content -Raw -Path $sourceFile | ConvertFrom-Json +$packageIndex = Get-PropertyValue -Item $sourceResult -Name "runtime_gate_preflight_package_index" -Default $null +$guardrails = Get-PropertyValue -Item $packageIndex -Name "guardrail_summary" -Default $null + +$indexFieldsCompatible = Test-ObjectHasFields -Item $packageIndex -Fields $requiredIndexFields +$indexValuesCompatible = ( + [string](Get-PropertyValue -Item $packageIndex -Name "schema_version" -Default "") -eq "rap.remote_workspace_real_adapter_runtime_gate_preflight_package_index.v1" -and + [string](Get-PropertyValue -Item $packageIndex -Name "source_release_marker_schema" -Default "") -eq "rap.remote_workspace_real_adapter_runtime_gate_preflight_release_marker.v1" -and + [string](Get-PropertyValue -Item $packageIndex -Name "package_status" -Default "") -eq "closed_contract_only" -and + [string](Get-PropertyValue -Item $packageIndex -Name "package_marker" -Default "") -eq "c19z85_disabled_real_adapter_runtime_gate_preflight_package_closed_contract_only" -and + [string](Get-PropertyValue -Item $packageIndex -Name "covered_stage_range" -Default "") -eq "C19Z73-C19Z84" -and + [int](Get-PropertyValue -Item $packageIndex -Name "covered_stage_count" -Default -1) -eq 12 -and + [string](Get-PropertyValue -Item $packageIndex -Name "latest_compatibility_stage" -Default "") -eq "C19Z84" -and + [string](Get-PropertyValue -Item $packageIndex -Name "runtime_gate_state" -Default "") -eq "blocked" -and + [string](Get-PropertyValue -Item $packageIndex -Name "runtime_effect" -Default "") -eq "contract_only_no_runtime_enablement" -and + [string](Get-PropertyValue -Item $packageIndex -Name "operator_default_action" -Default "") -eq "keep_real_adapter_disabled" -and + [string](Get-PropertyValue -Item $packageIndex -Name "release_status" -Default "") -eq "contract_only_ready_for_operator_preflight_handoff" -and + -not [bool](Get-PropertyValue -Item $packageIndex -Name "allows_process_start" -Default $true) -and + -not [bool](Get-PropertyValue -Item $packageIndex -Name "allows_payload_traffic" -Default $true) +) +$guardrailsCompatible = ( + (Test-ObjectHasFields -Item $guardrails -Fields $requiredGuardrailFields) -and + [bool](Get-PropertyValue -Item $guardrails -Name "activation_blocked" -Default $false) -and + -not [bool](Get-PropertyValue -Item $guardrails -Name "process_start_allowed" -Default $true) -and + -not [bool](Get-PropertyValue -Item $guardrails -Name "health_probe_enabled" -Default $true) -and + [string](Get-PropertyValue -Item $guardrails -Name "payload_traffic" -Default "") -eq "none" -and + -not [bool](Get-PropertyValue -Item $guardrails -Name "allows_process_start" -Default $true) -and + -not [bool](Get-PropertyValue -Item $guardrails -Name "allows_payload_traffic" -Default $true) +) + +$checks = [ordered]@{ + source_smoke_passed = ([bool]$sourceResult.passed) + source_schema_expected = ([string]$sourceResult.schema_version -eq "c19z85.remote_workspace_real_adapter_runtime_gate_preflight_package_index_smoke.v1") + package_index_present = ($null -ne $packageIndex) + index_fields_compatible = $indexFieldsCompatible + index_values_compatible = $indexValuesCompatible + guardrails_compatible = $guardrailsCompatible +} +$failed = @($checks.GetEnumerator() | Where-Object { -not $_.Value } | ForEach-Object { $_.Key }) + +$result = [ordered]@{ + schema_version = "c19z86.remote_workspace_real_adapter_runtime_gate_preflight_package_index_compatibility_smoke.v1" + source_result_path = $sourceFile + cluster_id = $ClusterID + required_index_fields = $requiredIndexFields + required_guardrail_fields = $requiredGuardrailFields + runtime_gate_preflight_package_index = $packageIndex + checks = $checks + failed_checks = $failed + passed = ($failed.Count -eq 0) +} + +$fullResultPath = Join-Path $repoRoot $ResultPath +$resultDir = Split-Path -Parent $fullResultPath +if ($resultDir) { New-Item -ItemType Directory -Force -Path $resultDir | Out-Null } +$result | ConvertTo-Json -Depth 100 | Set-Content -Encoding UTF8 -Path $fullResultPath + +if (-not $result.passed) { + throw "C19Z86 remote workspace real-adapter runtime gate preflight package index compatibility smoke failed. Result: $fullResultPath Failed: $($failed -join ', ')" +} + +Write-Host "C19Z86 remote workspace real-adapter runtime gate preflight package index compatibility smoke passed. Result: $fullResultPath" +$result diff --git a/scripts/fabric/c19z87-remote-workspace-real-adapter-runtime-gate-preflight-closeout-summary-smoke.ps1 b/scripts/fabric/c19z87-remote-workspace-real-adapter-runtime-gate-preflight-closeout-summary-smoke.ps1 new file mode 100644 index 0000000..324eeba --- /dev/null +++ b/scripts/fabric/c19z87-remote-workspace-real-adapter-runtime-gate-preflight-closeout-summary-smoke.ps1 @@ -0,0 +1,145 @@ +param( + [string]$ApiBaseUrl = "http://192.168.200.61:18121/api/v1", + [string]$ClusterID = "cfc0743d-d960-49fb-9de8-96e063d5e4aa", + [string]$ActorUserID = "f67d943f-5397-4b3a-a229-695fe67ad700", + [string]$RequestedNodeName = "test-1", + [string]$DefaultNodeName = "test-2", + [string]$MatrixNodeName = "test-1", + [string]$ResultPath = "artifacts\c19z87-remote-workspace-real-adapter-runtime-gate-preflight-closeout-summary-smoke-result.json" +) + +Set-StrictMode -Version Latest +$ErrorActionPreference = "Stop" + +$repoRoot = (Resolve-Path (Join-Path $PSScriptRoot "..\..")).ProviderPath +$sourceResultPath = "artifacts\c19z87-remote-workspace-real-adapter-runtime-gate-preflight-closeout-summary-source-result.json" +$requiredCloseoutFields = @( + "schema_version", + "source_package_index_schema", + "closeout_status", + "closeout_marker", + "covered_stage_range", + "covered_stage_count", + "runtime_gate_state", + "runtime_effect", + "operator_default_action", + "next_required_phase", + "allows_process_start", + "allows_payload_traffic", + "guardrail_summary" +) +$requiredGuardrailFields = @("activation_blocked", "process_start_allowed", "health_probe_enabled", "payload_traffic", "allows_process_start", "allows_payload_traffic") + +function Get-PropertyValue { + param([object]$Item, [string]$Name, [object]$Default = $null) + if ($null -eq $Item) { return $Default } + if ($Item -is [System.Collections.IDictionary]) { + if ($Item.Contains($Name)) { return $Item[$Name] } + return $Default + } + $property = $Item.PSObject.Properties[$Name] + if ($null -eq $property) { return $Default } + return $property.Value +} + +function Test-ObjectHasFields { + param([object]$Item, [string[]]$Fields) + if ($null -eq $Item) { return $false } + foreach ($field in $Fields) { + if ($Item -is [System.Collections.IDictionary]) { + if (-not $Item.Contains($field)) { return $false } + continue + } + if ($null -eq $Item.PSObject.Properties[$field]) { return $false } + } + return $true +} + +& powershell -ExecutionPolicy Bypass -File (Join-Path $PSScriptRoot "c19z86-remote-workspace-real-adapter-runtime-gate-preflight-package-index-compatibility-smoke.ps1") ` + -ApiBaseUrl $ApiBaseUrl ` + -ClusterID $ClusterID ` + -ActorUserID $ActorUserID ` + -RequestedNodeName $RequestedNodeName ` + -DefaultNodeName $DefaultNodeName ` + -MatrixNodeName $MatrixNodeName ` + -ResultPath $sourceResultPath | Out-Null + +$sourceFile = Join-Path $repoRoot $sourceResultPath +$sourceResult = Get-Content -Raw -Path $sourceFile | ConvertFrom-Json +$packageIndex = Get-PropertyValue -Item $sourceResult -Name "runtime_gate_preflight_package_index" -Default $null +$guardrails = Get-PropertyValue -Item $packageIndex -Name "guardrail_summary" -Default $null + +$closeout = [ordered]@{ + schema_version = "rap.remote_workspace_real_adapter_runtime_gate_preflight_closeout_summary.v1" + source_package_index_schema = Get-PropertyValue -Item $packageIndex -Name "schema_version" -Default $null + closeout_status = "closed_contract_only_preflight_complete" + closeout_marker = "c19z87_disabled_real_adapter_runtime_gate_preflight_closed_contract_only" + covered_stage_range = "C19Z73-C19Z86" + covered_stage_count = 14 + runtime_gate_state = Get-PropertyValue -Item $packageIndex -Name "runtime_gate_state" -Default $null + runtime_effect = Get-PropertyValue -Item $packageIndex -Name "runtime_effect" -Default $null + operator_default_action = Get-PropertyValue -Item $packageIndex -Name "operator_default_action" -Default $null + next_required_phase = "explicit_real_runtime_gate_enablement" + allows_process_start = Get-PropertyValue -Item $packageIndex -Name "allows_process_start" -Default $null + allows_payload_traffic = Get-PropertyValue -Item $packageIndex -Name "allows_payload_traffic" -Default $null + guardrail_summary = $guardrails +} + +$closeoutFieldsCompatible = Test-ObjectHasFields -Item $closeout -Fields $requiredCloseoutFields +$closeoutValuesCompatible = ( + [string](Get-PropertyValue -Item $closeout -Name "schema_version" -Default "") -eq "rap.remote_workspace_real_adapter_runtime_gate_preflight_closeout_summary.v1" -and + [string](Get-PropertyValue -Item $closeout -Name "source_package_index_schema" -Default "") -eq "rap.remote_workspace_real_adapter_runtime_gate_preflight_package_index.v1" -and + [string](Get-PropertyValue -Item $closeout -Name "closeout_status" -Default "") -eq "closed_contract_only_preflight_complete" -and + [string](Get-PropertyValue -Item $closeout -Name "closeout_marker" -Default "") -eq "c19z87_disabled_real_adapter_runtime_gate_preflight_closed_contract_only" -and + [string](Get-PropertyValue -Item $closeout -Name "covered_stage_range" -Default "") -eq "C19Z73-C19Z86" -and + [int](Get-PropertyValue -Item $closeout -Name "covered_stage_count" -Default -1) -eq 14 -and + [string](Get-PropertyValue -Item $closeout -Name "runtime_gate_state" -Default "") -eq "blocked" -and + [string](Get-PropertyValue -Item $closeout -Name "runtime_effect" -Default "") -eq "contract_only_no_runtime_enablement" -and + [string](Get-PropertyValue -Item $closeout -Name "operator_default_action" -Default "") -eq "keep_real_adapter_disabled" -and + [string](Get-PropertyValue -Item $closeout -Name "next_required_phase" -Default "") -eq "explicit_real_runtime_gate_enablement" -and + -not [bool](Get-PropertyValue -Item $closeout -Name "allows_process_start" -Default $true) -and + -not [bool](Get-PropertyValue -Item $closeout -Name "allows_payload_traffic" -Default $true) +) +$guardrailsCompatible = ( + (Test-ObjectHasFields -Item $guardrails -Fields $requiredGuardrailFields) -and + [bool](Get-PropertyValue -Item $guardrails -Name "activation_blocked" -Default $false) -and + -not [bool](Get-PropertyValue -Item $guardrails -Name "process_start_allowed" -Default $true) -and + -not [bool](Get-PropertyValue -Item $guardrails -Name "health_probe_enabled" -Default $true) -and + [string](Get-PropertyValue -Item $guardrails -Name "payload_traffic" -Default "") -eq "none" -and + -not [bool](Get-PropertyValue -Item $guardrails -Name "allows_process_start" -Default $true) -and + -not [bool](Get-PropertyValue -Item $guardrails -Name "allows_payload_traffic" -Default $true) +) + +$checks = [ordered]@{ + source_smoke_passed = ([bool]$sourceResult.passed) + source_schema_expected = ([string]$sourceResult.schema_version -eq "c19z86.remote_workspace_real_adapter_runtime_gate_preflight_package_index_compatibility_smoke.v1") + package_index_present = ($null -ne $packageIndex) + closeout_fields_compatible = $closeoutFieldsCompatible + closeout_values_compatible = $closeoutValuesCompatible + guardrails_compatible = $guardrailsCompatible +} +$failed = @($checks.GetEnumerator() | Where-Object { -not $_.Value } | ForEach-Object { $_.Key }) + +$result = [ordered]@{ + schema_version = "c19z87.remote_workspace_real_adapter_runtime_gate_preflight_closeout_summary_smoke.v1" + source_result_path = $sourceFile + cluster_id = $ClusterID + required_closeout_fields = $requiredCloseoutFields + required_guardrail_fields = $requiredGuardrailFields + runtime_gate_preflight_closeout_summary = $closeout + checks = $checks + failed_checks = $failed + passed = ($failed.Count -eq 0) +} + +$fullResultPath = Join-Path $repoRoot $ResultPath +$resultDir = Split-Path -Parent $fullResultPath +if ($resultDir) { New-Item -ItemType Directory -Force -Path $resultDir | Out-Null } +$result | ConvertTo-Json -Depth 100 | Set-Content -Encoding UTF8 -Path $fullResultPath + +if (-not $result.passed) { + throw "C19Z87 remote workspace real-adapter runtime gate preflight closeout summary smoke failed. Result: $fullResultPath Failed: $($failed -join ', ')" +} + +Write-Host "C19Z87 remote workspace real-adapter runtime gate preflight closeout summary smoke passed. Result: $fullResultPath" +$result diff --git a/scripts/fabric/c19z88-remote-workspace-real-adapter-runtime-gate-preflight-closeout-summary-compatibility-smoke.ps1 b/scripts/fabric/c19z88-remote-workspace-real-adapter-runtime-gate-preflight-closeout-summary-compatibility-smoke.ps1 new file mode 100644 index 0000000..82c04fe --- /dev/null +++ b/scripts/fabric/c19z88-remote-workspace-real-adapter-runtime-gate-preflight-closeout-summary-compatibility-smoke.ps1 @@ -0,0 +1,129 @@ +param( + [string]$ApiBaseUrl = "http://192.168.200.61:18121/api/v1", + [string]$ClusterID = "cfc0743d-d960-49fb-9de8-96e063d5e4aa", + [string]$ActorUserID = "f67d943f-5397-4b3a-a229-695fe67ad700", + [string]$RequestedNodeName = "test-1", + [string]$DefaultNodeName = "test-2", + [string]$MatrixNodeName = "test-1", + [string]$ResultPath = "artifacts\c19z88-remote-workspace-real-adapter-runtime-gate-preflight-closeout-summary-compatibility-smoke-result.json" +) + +Set-StrictMode -Version Latest +$ErrorActionPreference = "Stop" + +$repoRoot = (Resolve-Path (Join-Path $PSScriptRoot "..\..")).ProviderPath +$sourceResultPath = "artifacts\c19z88-remote-workspace-real-adapter-runtime-gate-preflight-closeout-summary-source-result.json" +$requiredCloseoutFields = @( + "schema_version", + "source_package_index_schema", + "closeout_status", + "closeout_marker", + "covered_stage_range", + "covered_stage_count", + "runtime_gate_state", + "runtime_effect", + "operator_default_action", + "next_required_phase", + "allows_process_start", + "allows_payload_traffic", + "guardrail_summary" +) +$requiredGuardrailFields = @("activation_blocked", "process_start_allowed", "health_probe_enabled", "payload_traffic", "allows_process_start", "allows_payload_traffic") + +function Get-PropertyValue { + param([object]$Item, [string]$Name, [object]$Default = $null) + if ($null -eq $Item) { return $Default } + if ($Item -is [System.Collections.IDictionary]) { + if ($Item.Contains($Name)) { return $Item[$Name] } + return $Default + } + $property = $Item.PSObject.Properties[$Name] + if ($null -eq $property) { return $Default } + return $property.Value +} + +function Test-ObjectHasFields { + param([object]$Item, [string[]]$Fields) + if ($null -eq $Item) { return $false } + foreach ($field in $Fields) { + if ($Item -is [System.Collections.IDictionary]) { + if (-not $Item.Contains($field)) { return $false } + continue + } + if ($null -eq $Item.PSObject.Properties[$field]) { return $false } + } + return $true +} + +& powershell -ExecutionPolicy Bypass -File (Join-Path $PSScriptRoot "c19z87-remote-workspace-real-adapter-runtime-gate-preflight-closeout-summary-smoke.ps1") ` + -ApiBaseUrl $ApiBaseUrl ` + -ClusterID $ClusterID ` + -ActorUserID $ActorUserID ` + -RequestedNodeName $RequestedNodeName ` + -DefaultNodeName $DefaultNodeName ` + -MatrixNodeName $MatrixNodeName ` + -ResultPath $sourceResultPath | Out-Null + +$sourceFile = Join-Path $repoRoot $sourceResultPath +$sourceResult = Get-Content -Raw -Path $sourceFile | ConvertFrom-Json +$closeout = Get-PropertyValue -Item $sourceResult -Name "runtime_gate_preflight_closeout_summary" -Default $null +$guardrails = Get-PropertyValue -Item $closeout -Name "guardrail_summary" -Default $null + +$closeoutFieldsCompatible = Test-ObjectHasFields -Item $closeout -Fields $requiredCloseoutFields +$closeoutValuesCompatible = ( + [string](Get-PropertyValue -Item $closeout -Name "schema_version" -Default "") -eq "rap.remote_workspace_real_adapter_runtime_gate_preflight_closeout_summary.v1" -and + [string](Get-PropertyValue -Item $closeout -Name "source_package_index_schema" -Default "") -eq "rap.remote_workspace_real_adapter_runtime_gate_preflight_package_index.v1" -and + [string](Get-PropertyValue -Item $closeout -Name "closeout_status" -Default "") -eq "closed_contract_only_preflight_complete" -and + [string](Get-PropertyValue -Item $closeout -Name "closeout_marker" -Default "") -eq "c19z87_disabled_real_adapter_runtime_gate_preflight_closed_contract_only" -and + [string](Get-PropertyValue -Item $closeout -Name "covered_stage_range" -Default "") -eq "C19Z73-C19Z86" -and + [int](Get-PropertyValue -Item $closeout -Name "covered_stage_count" -Default -1) -eq 14 -and + [string](Get-PropertyValue -Item $closeout -Name "runtime_gate_state" -Default "") -eq "blocked" -and + [string](Get-PropertyValue -Item $closeout -Name "runtime_effect" -Default "") -eq "contract_only_no_runtime_enablement" -and + [string](Get-PropertyValue -Item $closeout -Name "operator_default_action" -Default "") -eq "keep_real_adapter_disabled" -and + [string](Get-PropertyValue -Item $closeout -Name "next_required_phase" -Default "") -eq "explicit_real_runtime_gate_enablement" -and + -not [bool](Get-PropertyValue -Item $closeout -Name "allows_process_start" -Default $true) -and + -not [bool](Get-PropertyValue -Item $closeout -Name "allows_payload_traffic" -Default $true) +) +$guardrailsCompatible = ( + (Test-ObjectHasFields -Item $guardrails -Fields $requiredGuardrailFields) -and + [bool](Get-PropertyValue -Item $guardrails -Name "activation_blocked" -Default $false) -and + -not [bool](Get-PropertyValue -Item $guardrails -Name "process_start_allowed" -Default $true) -and + -not [bool](Get-PropertyValue -Item $guardrails -Name "health_probe_enabled" -Default $true) -and + [string](Get-PropertyValue -Item $guardrails -Name "payload_traffic" -Default "") -eq "none" -and + -not [bool](Get-PropertyValue -Item $guardrails -Name "allows_process_start" -Default $true) -and + -not [bool](Get-PropertyValue -Item $guardrails -Name "allows_payload_traffic" -Default $true) +) + +$checks = [ordered]@{ + source_smoke_passed = ([bool]$sourceResult.passed) + source_schema_expected = ([string]$sourceResult.schema_version -eq "c19z87.remote_workspace_real_adapter_runtime_gate_preflight_closeout_summary_smoke.v1") + closeout_summary_present = ($null -ne $closeout) + closeout_fields_compatible = $closeoutFieldsCompatible + closeout_values_compatible = $closeoutValuesCompatible + guardrails_compatible = $guardrailsCompatible +} +$failed = @($checks.GetEnumerator() | Where-Object { -not $_.Value } | ForEach-Object { $_.Key }) + +$result = [ordered]@{ + schema_version = "c19z88.remote_workspace_real_adapter_runtime_gate_preflight_closeout_summary_compatibility_smoke.v1" + source_result_path = $sourceFile + cluster_id = $ClusterID + required_closeout_fields = $requiredCloseoutFields + required_guardrail_fields = $requiredGuardrailFields + runtime_gate_preflight_closeout_summary = $closeout + checks = $checks + failed_checks = $failed + passed = ($failed.Count -eq 0) +} + +$fullResultPath = Join-Path $repoRoot $ResultPath +$resultDir = Split-Path -Parent $fullResultPath +if ($resultDir) { New-Item -ItemType Directory -Force -Path $resultDir | Out-Null } +$result | ConvertTo-Json -Depth 100 | Set-Content -Encoding UTF8 -Path $fullResultPath + +if (-not $result.passed) { + throw "C19Z88 remote workspace real-adapter runtime gate preflight closeout summary compatibility smoke failed. Result: $fullResultPath Failed: $($failed -join ', ')" +} + +Write-Host "C19Z88 remote workspace real-adapter runtime gate preflight closeout summary compatibility smoke passed. Result: $fullResultPath" +$result diff --git a/scripts/fabric/c19z89-remote-workspace-real-adapter-runtime-gate-explicit-enablement-request-smoke.ps1 b/scripts/fabric/c19z89-remote-workspace-real-adapter-runtime-gate-explicit-enablement-request-smoke.ps1 new file mode 100644 index 0000000..6cf737e --- /dev/null +++ b/scripts/fabric/c19z89-remote-workspace-real-adapter-runtime-gate-explicit-enablement-request-smoke.ps1 @@ -0,0 +1,157 @@ +param( + [string]$ApiBaseUrl = "http://192.168.200.61:18121/api/v1", + [string]$ClusterID = "cfc0743d-d960-49fb-9de8-96e063d5e4aa", + [string]$ActorUserID = "f67d943f-5397-4b3a-a229-695fe67ad700", + [string]$RequestedNodeName = "test-1", + [string]$DefaultNodeName = "test-2", + [string]$MatrixNodeName = "test-1", + [string]$ResultPath = "artifacts\c19z89-remote-workspace-real-adapter-runtime-gate-explicit-enablement-request-smoke-result.json" +) + +Set-StrictMode -Version Latest +$ErrorActionPreference = "Stop" + +$repoRoot = (Resolve-Path (Join-Path $PSScriptRoot "..\..")).ProviderPath +$sourceResultPath = "artifacts\c19z89-remote-workspace-real-adapter-runtime-gate-explicit-enablement-request-source-result.json" +$requiredRequestFields = @( + "schema_version", + "source_preflight_closeout_schema", + "request_status", + "request_marker", + "requested_phase", + "runtime_gate_state", + "runtime_effect", + "operator_default_action", + "requires_operator_confirmation", + "requires_binary_validation", + "requires_permission_validation", + "requires_supervisor_validation", + "requires_health_probe_validation", + "requires_payload_gate_validation", + "allows_process_start", + "allows_payload_traffic", + "guardrail_summary" +) +$requiredGuardrailFields = @("activation_blocked", "process_start_allowed", "health_probe_enabled", "payload_traffic", "allows_process_start", "allows_payload_traffic") + +function Get-PropertyValue { + param([object]$Item, [string]$Name, [object]$Default = $null) + if ($null -eq $Item) { return $Default } + if ($Item -is [System.Collections.IDictionary]) { + if ($Item.Contains($Name)) { return $Item[$Name] } + return $Default + } + $property = $Item.PSObject.Properties[$Name] + if ($null -eq $property) { return $Default } + return $property.Value +} + +function Test-ObjectHasFields { + param([object]$Item, [string[]]$Fields) + if ($null -eq $Item) { return $false } + foreach ($field in $Fields) { + if ($Item -is [System.Collections.IDictionary]) { + if (-not $Item.Contains($field)) { return $false } + continue + } + if ($null -eq $Item.PSObject.Properties[$field]) { return $false } + } + return $true +} + +& powershell -ExecutionPolicy Bypass -File (Join-Path $PSScriptRoot "c19z88-remote-workspace-real-adapter-runtime-gate-preflight-closeout-summary-compatibility-smoke.ps1") ` + -ApiBaseUrl $ApiBaseUrl ` + -ClusterID $ClusterID ` + -ActorUserID $ActorUserID ` + -RequestedNodeName $RequestedNodeName ` + -DefaultNodeName $DefaultNodeName ` + -MatrixNodeName $MatrixNodeName ` + -ResultPath $sourceResultPath | Out-Null + +$sourceFile = Join-Path $repoRoot $sourceResultPath +$sourceResult = Get-Content -Raw -Path $sourceFile | ConvertFrom-Json +$closeout = Get-PropertyValue -Item $sourceResult -Name "runtime_gate_preflight_closeout_summary" -Default $null +$guardrails = Get-PropertyValue -Item $closeout -Name "guardrail_summary" -Default $null + +$enablementRequest = [ordered]@{ + schema_version = "rap.remote_workspace_real_adapter_runtime_gate_explicit_enablement_request.v1" + source_preflight_closeout_schema = Get-PropertyValue -Item $closeout -Name "schema_version" -Default $null + request_status = "pending_required_validations" + request_marker = "c19z89_real_adapter_runtime_gate_explicit_enablement_request_contract_only" + requested_phase = Get-PropertyValue -Item $closeout -Name "next_required_phase" -Default $null + runtime_gate_state = "blocked_pending_validation" + runtime_effect = "contract_only_no_runtime_enablement" + operator_default_action = Get-PropertyValue -Item $closeout -Name "operator_default_action" -Default $null + requires_operator_confirmation = $true + requires_binary_validation = $true + requires_permission_validation = $true + requires_supervisor_validation = $true + requires_health_probe_validation = $true + requires_payload_gate_validation = $true + allows_process_start = $false + allows_payload_traffic = $false + guardrail_summary = $guardrails +} + +$requestFieldsCompatible = Test-ObjectHasFields -Item $enablementRequest -Fields $requiredRequestFields +$requestValuesCompatible = ( + [string](Get-PropertyValue -Item $enablementRequest -Name "schema_version" -Default "") -eq "rap.remote_workspace_real_adapter_runtime_gate_explicit_enablement_request.v1" -and + [string](Get-PropertyValue -Item $enablementRequest -Name "source_preflight_closeout_schema" -Default "") -eq "rap.remote_workspace_real_adapter_runtime_gate_preflight_closeout_summary.v1" -and + [string](Get-PropertyValue -Item $enablementRequest -Name "request_status" -Default "") -eq "pending_required_validations" -and + [string](Get-PropertyValue -Item $enablementRequest -Name "request_marker" -Default "") -eq "c19z89_real_adapter_runtime_gate_explicit_enablement_request_contract_only" -and + [string](Get-PropertyValue -Item $enablementRequest -Name "requested_phase" -Default "") -eq "explicit_real_runtime_gate_enablement" -and + [string](Get-PropertyValue -Item $enablementRequest -Name "runtime_gate_state" -Default "") -eq "blocked_pending_validation" -and + [string](Get-PropertyValue -Item $enablementRequest -Name "runtime_effect" -Default "") -eq "contract_only_no_runtime_enablement" -and + [string](Get-PropertyValue -Item $enablementRequest -Name "operator_default_action" -Default "") -eq "keep_real_adapter_disabled" -and + [bool](Get-PropertyValue -Item $enablementRequest -Name "requires_operator_confirmation" -Default $false) -and + [bool](Get-PropertyValue -Item $enablementRequest -Name "requires_binary_validation" -Default $false) -and + [bool](Get-PropertyValue -Item $enablementRequest -Name "requires_permission_validation" -Default $false) -and + [bool](Get-PropertyValue -Item $enablementRequest -Name "requires_supervisor_validation" -Default $false) -and + [bool](Get-PropertyValue -Item $enablementRequest -Name "requires_health_probe_validation" -Default $false) -and + [bool](Get-PropertyValue -Item $enablementRequest -Name "requires_payload_gate_validation" -Default $false) -and + -not [bool](Get-PropertyValue -Item $enablementRequest -Name "allows_process_start" -Default $true) -and + -not [bool](Get-PropertyValue -Item $enablementRequest -Name "allows_payload_traffic" -Default $true) +) +$guardrailsCompatible = ( + (Test-ObjectHasFields -Item $guardrails -Fields $requiredGuardrailFields) -and + [bool](Get-PropertyValue -Item $guardrails -Name "activation_blocked" -Default $false) -and + -not [bool](Get-PropertyValue -Item $guardrails -Name "process_start_allowed" -Default $true) -and + -not [bool](Get-PropertyValue -Item $guardrails -Name "health_probe_enabled" -Default $true) -and + [string](Get-PropertyValue -Item $guardrails -Name "payload_traffic" -Default "") -eq "none" -and + -not [bool](Get-PropertyValue -Item $guardrails -Name "allows_process_start" -Default $true) -and + -not [bool](Get-PropertyValue -Item $guardrails -Name "allows_payload_traffic" -Default $true) +) + +$checks = [ordered]@{ + source_smoke_passed = ([bool]$sourceResult.passed) + source_schema_expected = ([string]$sourceResult.schema_version -eq "c19z88.remote_workspace_real_adapter_runtime_gate_preflight_closeout_summary_compatibility_smoke.v1") + closeout_summary_present = ($null -ne $closeout) + request_fields_compatible = $requestFieldsCompatible + request_values_compatible = $requestValuesCompatible + guardrails_compatible = $guardrailsCompatible +} +$failed = @($checks.GetEnumerator() | Where-Object { -not $_.Value } | ForEach-Object { $_.Key }) + +$result = [ordered]@{ + schema_version = "c19z89.remote_workspace_real_adapter_runtime_gate_explicit_enablement_request_smoke.v1" + source_result_path = $sourceFile + cluster_id = $ClusterID + required_request_fields = $requiredRequestFields + required_guardrail_fields = $requiredGuardrailFields + runtime_gate_explicit_enablement_request = $enablementRequest + checks = $checks + failed_checks = $failed + passed = ($failed.Count -eq 0) +} + +$fullResultPath = Join-Path $repoRoot $ResultPath +$resultDir = Split-Path -Parent $fullResultPath +if ($resultDir) { New-Item -ItemType Directory -Force -Path $resultDir | Out-Null } +$result | ConvertTo-Json -Depth 100 | Set-Content -Encoding UTF8 -Path $fullResultPath + +if (-not $result.passed) { + throw "C19Z89 remote workspace real-adapter runtime gate explicit enablement request smoke failed. Result: $fullResultPath Failed: $($failed -join ', ')" +} + +Write-Host "C19Z89 remote workspace real-adapter runtime gate explicit enablement request smoke passed. Result: $fullResultPath" +$result diff --git a/scripts/fabric/c19z9-remote-workspace-mailbox-preflight-retained-window-smoke.ps1 b/scripts/fabric/c19z9-remote-workspace-mailbox-preflight-retained-window-smoke.ps1 new file mode 100644 index 0000000..b6ee831 --- /dev/null +++ b/scripts/fabric/c19z9-remote-workspace-mailbox-preflight-retained-window-smoke.ps1 @@ -0,0 +1,90 @@ +param( + [string]$ApiBaseUrl = "http://192.168.200.61:18121/api/v1", + [string]$ClusterID = "cfc0743d-d960-49fb-9de8-96e063d5e4aa", + [string]$ActorUserID = "f67d943f-5397-4b3a-a229-695fe67ad700", + [string]$EntryNodeName = "test-1", + [string]$ExitNodeName = "test-2", + [string]$EntryBaseUrl = "http://192.168.200.61:19131", + [string]$ResultPath = "artifacts\c19z9-remote-workspace-mailbox-preflight-retained-window-smoke-result.json" +) + +Set-StrictMode -Version Latest +$ErrorActionPreference = "Stop" + +$repoRoot = (Resolve-Path (Join-Path $PSScriptRoot "..\..")).ProviderPath +$sourceResultPath = "artifacts\c19z9-remote-workspace-mailbox-preflight-retained-window-source-result.json" + +function Get-PropertyValue { + param([object]$Item, [string]$Name, [object]$Default = $null) + if ($null -eq $Item) { return $Default } + $property = $Item.PSObject.Properties[$Name] + if ($null -eq $property) { return $Default } + return $property.Value +} + +function Test-RetainedWindow { + param([object]$Sink, [string]$AdapterSessionID) + if ($null -eq $Sink) { return $false } + $readiness = Get-PropertyValue -Item $Sink -Name "adapter_runtime_readiness" -Default $null + $rollup = Get-PropertyValue -Item $readiness -Name "last_preflight" -Default $null + if ($null -eq $rollup) { return $false } + $resumeSequence = [int64](Get-PropertyValue -Item $rollup -Name "resume_sequence" -Default -1) + $firstRetained = [int64](Get-PropertyValue -Item $rollup -Name "first_retained_sequence" -Default -1) + $lastRetained = [int64](Get-PropertyValue -Item $rollup -Name "last_retained_sequence" -Default -1) + $missingDropped = [int](Get-PropertyValue -Item $rollup -Name "missing_dropped_count" -Default -1) + return ( + [string](Get-PropertyValue -Item $readiness -Name "adapter_session_id" -Default "") -eq $AdapterSessionID -and + [string](Get-PropertyValue -Item $rollup -Name "diagnostic_state" -Default "") -eq "stale_cursor_gap" -and + [string](Get-PropertyValue -Item $rollup -Name "operator_status" -Default "") -eq "resync_required" -and + [string](Get-PropertyValue -Item $rollup -Name "operator_severity" -Default "") -eq "warn" -and + $firstRetained -gt $resumeSequence -and + $lastRetained -ge $firstRetained -and + $missingDropped -eq ($firstRetained - $resumeSequence - 1) -and + [int64](Get-PropertyValue -Item $rollup -Name "mailbox_dropped_total" -Default 0) -ge 3 + ) +} + +$source = & powershell -ExecutionPolicy Bypass -File (Join-Path $PSScriptRoot "c19z8-remote-workspace-mailbox-preflight-rollup-smoke.ps1") ` + -ApiBaseUrl $ApiBaseUrl ` + -ClusterID $ClusterID ` + -ActorUserID $ActorUserID ` + -EntryNodeName $EntryNodeName ` + -ExitNodeName $ExitNodeName ` + -EntryBaseUrl $EntryBaseUrl ` + -ResultPath $sourceResultPath + +$sourceFile = Join-Path $repoRoot $sourceResultPath +$sourceResult = Get-Content -Raw -Path $sourceFile | ConvertFrom-Json +$adapterSessionID = [string](Get-PropertyValue -Item $sourceResult -Name "adapter_session_id" -Default "") +$observed = Get-PropertyValue -Item $sourceResult -Name "observed" -Default $null + +$checks = [ordered]@{ + source_smoke_passed = ([bool]$sourceResult.passed) + adapter_session_id_present = ($adapterSessionID -match "^rap-rw-adapter-session-[0-9a-f]{24}$") + workload_retained_window_visible = (Test-RetainedWindow -Sink $observed.workload_sink -AdapterSessionID $adapterSessionID) + telemetry_retained_window_visible = (Test-RetainedWindow -Sink $observed.telemetry_sink -AdapterSessionID $adapterSessionID) +} +$failed = @($checks.GetEnumerator() | Where-Object { -not $_.Value } | ForEach-Object { $_.Key }) + +$result = [ordered]@{ + schema_version = "c19z9.remote_workspace_mailbox_preflight_retained_window_smoke.v1" + source_result_path = $sourceFile + cluster_id = $ClusterID + adapter_session_id = $adapterSessionID + observed = $observed + checks = $checks + failed_checks = $failed + passed = ($failed.Count -eq 0) +} + +$fullResultPath = Join-Path $repoRoot $ResultPath +$resultDir = Split-Path -Parent $fullResultPath +if ($resultDir) { New-Item -ItemType Directory -Force -Path $resultDir | Out-Null } +$result | ConvertTo-Json -Depth 100 | Set-Content -Encoding UTF8 -Path $fullResultPath + +if (-not $result.passed) { + throw "C19Z9 remote workspace mailbox preflight retained-window smoke failed. Result: $fullResultPath Failed: $($failed -join ', ')" +} + +Write-Host "C19Z9 remote workspace mailbox preflight retained-window smoke passed. Result: $fullResultPath" +$result diff --git a/scripts/fabric/c19z90-remote-workspace-real-adapter-runtime-gate-explicit-enablement-request-compatibility-smoke.ps1 b/scripts/fabric/c19z90-remote-workspace-real-adapter-runtime-gate-explicit-enablement-request-compatibility-smoke.ps1 new file mode 100644 index 0000000..93ffcbd --- /dev/null +++ b/scripts/fabric/c19z90-remote-workspace-real-adapter-runtime-gate-explicit-enablement-request-compatibility-smoke.ps1 @@ -0,0 +1,137 @@ +param( + [string]$ApiBaseUrl = "http://192.168.200.61:18121/api/v1", + [string]$ClusterID = "cfc0743d-d960-49fb-9de8-96e063d5e4aa", + [string]$ActorUserID = "f67d943f-5397-4b3a-a229-695fe67ad700", + [string]$RequestedNodeName = "test-1", + [string]$DefaultNodeName = "test-2", + [string]$MatrixNodeName = "test-1", + [string]$ResultPath = "artifacts\c19z90-remote-workspace-real-adapter-runtime-gate-explicit-enablement-request-compatibility-smoke-result.json" +) + +Set-StrictMode -Version Latest +$ErrorActionPreference = "Stop" + +$repoRoot = (Resolve-Path (Join-Path $PSScriptRoot "..\..")).ProviderPath +$sourceResultPath = "artifacts\c19z90-remote-workspace-real-adapter-runtime-gate-explicit-enablement-request-source-result.json" +$requiredRequestFields = @( + "schema_version", + "source_preflight_closeout_schema", + "request_status", + "request_marker", + "requested_phase", + "runtime_gate_state", + "runtime_effect", + "operator_default_action", + "requires_operator_confirmation", + "requires_binary_validation", + "requires_permission_validation", + "requires_supervisor_validation", + "requires_health_probe_validation", + "requires_payload_gate_validation", + "allows_process_start", + "allows_payload_traffic", + "guardrail_summary" +) +$requiredGuardrailFields = @("activation_blocked", "process_start_allowed", "health_probe_enabled", "payload_traffic", "allows_process_start", "allows_payload_traffic") + +function Get-PropertyValue { + param([object]$Item, [string]$Name, [object]$Default = $null) + if ($null -eq $Item) { return $Default } + if ($Item -is [System.Collections.IDictionary]) { + if ($Item.Contains($Name)) { return $Item[$Name] } + return $Default + } + $property = $Item.PSObject.Properties[$Name] + if ($null -eq $property) { return $Default } + return $property.Value +} + +function Test-ObjectHasFields { + param([object]$Item, [string[]]$Fields) + if ($null -eq $Item) { return $false } + foreach ($field in $Fields) { + if ($Item -is [System.Collections.IDictionary]) { + if (-not $Item.Contains($field)) { return $false } + continue + } + if ($null -eq $Item.PSObject.Properties[$field]) { return $false } + } + return $true +} + +& powershell -ExecutionPolicy Bypass -File (Join-Path $PSScriptRoot "c19z89-remote-workspace-real-adapter-runtime-gate-explicit-enablement-request-smoke.ps1") ` + -ApiBaseUrl $ApiBaseUrl ` + -ClusterID $ClusterID ` + -ActorUserID $ActorUserID ` + -RequestedNodeName $RequestedNodeName ` + -DefaultNodeName $DefaultNodeName ` + -MatrixNodeName $MatrixNodeName ` + -ResultPath $sourceResultPath | Out-Null + +$sourceFile = Join-Path $repoRoot $sourceResultPath +$sourceResult = Get-Content -Raw -Path $sourceFile | ConvertFrom-Json +$enablementRequest = Get-PropertyValue -Item $sourceResult -Name "runtime_gate_explicit_enablement_request" -Default $null +$guardrails = Get-PropertyValue -Item $enablementRequest -Name "guardrail_summary" -Default $null + +$requestFieldsCompatible = Test-ObjectHasFields -Item $enablementRequest -Fields $requiredRequestFields +$requestValuesCompatible = ( + [string](Get-PropertyValue -Item $enablementRequest -Name "schema_version" -Default "") -eq "rap.remote_workspace_real_adapter_runtime_gate_explicit_enablement_request.v1" -and + [string](Get-PropertyValue -Item $enablementRequest -Name "source_preflight_closeout_schema" -Default "") -eq "rap.remote_workspace_real_adapter_runtime_gate_preflight_closeout_summary.v1" -and + [string](Get-PropertyValue -Item $enablementRequest -Name "request_status" -Default "") -eq "pending_required_validations" -and + [string](Get-PropertyValue -Item $enablementRequest -Name "request_marker" -Default "") -eq "c19z89_real_adapter_runtime_gate_explicit_enablement_request_contract_only" -and + [string](Get-PropertyValue -Item $enablementRequest -Name "requested_phase" -Default "") -eq "explicit_real_runtime_gate_enablement" -and + [string](Get-PropertyValue -Item $enablementRequest -Name "runtime_gate_state" -Default "") -eq "blocked_pending_validation" -and + [string](Get-PropertyValue -Item $enablementRequest -Name "runtime_effect" -Default "") -eq "contract_only_no_runtime_enablement" -and + [string](Get-PropertyValue -Item $enablementRequest -Name "operator_default_action" -Default "") -eq "keep_real_adapter_disabled" -and + [bool](Get-PropertyValue -Item $enablementRequest -Name "requires_operator_confirmation" -Default $false) -and + [bool](Get-PropertyValue -Item $enablementRequest -Name "requires_binary_validation" -Default $false) -and + [bool](Get-PropertyValue -Item $enablementRequest -Name "requires_permission_validation" -Default $false) -and + [bool](Get-PropertyValue -Item $enablementRequest -Name "requires_supervisor_validation" -Default $false) -and + [bool](Get-PropertyValue -Item $enablementRequest -Name "requires_health_probe_validation" -Default $false) -and + [bool](Get-PropertyValue -Item $enablementRequest -Name "requires_payload_gate_validation" -Default $false) -and + -not [bool](Get-PropertyValue -Item $enablementRequest -Name "allows_process_start" -Default $true) -and + -not [bool](Get-PropertyValue -Item $enablementRequest -Name "allows_payload_traffic" -Default $true) +) +$guardrailsCompatible = ( + (Test-ObjectHasFields -Item $guardrails -Fields $requiredGuardrailFields) -and + [bool](Get-PropertyValue -Item $guardrails -Name "activation_blocked" -Default $false) -and + -not [bool](Get-PropertyValue -Item $guardrails -Name "process_start_allowed" -Default $true) -and + -not [bool](Get-PropertyValue -Item $guardrails -Name "health_probe_enabled" -Default $true) -and + [string](Get-PropertyValue -Item $guardrails -Name "payload_traffic" -Default "") -eq "none" -and + -not [bool](Get-PropertyValue -Item $guardrails -Name "allows_process_start" -Default $true) -and + -not [bool](Get-PropertyValue -Item $guardrails -Name "allows_payload_traffic" -Default $true) +) + +$checks = [ordered]@{ + source_smoke_passed = ([bool]$sourceResult.passed) + source_schema_expected = ([string]$sourceResult.schema_version -eq "c19z89.remote_workspace_real_adapter_runtime_gate_explicit_enablement_request_smoke.v1") + enablement_request_present = ($null -ne $enablementRequest) + request_fields_compatible = $requestFieldsCompatible + request_values_compatible = $requestValuesCompatible + guardrails_compatible = $guardrailsCompatible +} +$failed = @($checks.GetEnumerator() | Where-Object { -not $_.Value } | ForEach-Object { $_.Key }) + +$result = [ordered]@{ + schema_version = "c19z90.remote_workspace_real_adapter_runtime_gate_explicit_enablement_request_compatibility_smoke.v1" + source_result_path = $sourceFile + cluster_id = $ClusterID + required_request_fields = $requiredRequestFields + required_guardrail_fields = $requiredGuardrailFields + runtime_gate_explicit_enablement_request = $enablementRequest + checks = $checks + failed_checks = $failed + passed = ($failed.Count -eq 0) +} + +$fullResultPath = Join-Path $repoRoot $ResultPath +$resultDir = Split-Path -Parent $fullResultPath +if ($resultDir) { New-Item -ItemType Directory -Force -Path $resultDir | Out-Null } +$result | ConvertTo-Json -Depth 100 | Set-Content -Encoding UTF8 -Path $fullResultPath + +if (-not $result.passed) { + throw "C19Z90 remote workspace real-adapter runtime gate explicit enablement request compatibility smoke failed. Result: $fullResultPath Failed: $($failed -join ', ')" +} + +Write-Host "C19Z90 remote workspace real-adapter runtime gate explicit enablement request compatibility smoke passed. Result: $fullResultPath" +$result diff --git a/scripts/fabric/c19z91-remote-workspace-real-adapter-runtime-gate-operator-confirmation-validation-smoke.ps1 b/scripts/fabric/c19z91-remote-workspace-real-adapter-runtime-gate-operator-confirmation-validation-smoke.ps1 new file mode 100644 index 0000000..111f28b --- /dev/null +++ b/scripts/fabric/c19z91-remote-workspace-real-adapter-runtime-gate-operator-confirmation-validation-smoke.ps1 @@ -0,0 +1,163 @@ +param( + [string]$ApiBaseUrl = "http://192.168.200.61:18121/api/v1", + [string]$ClusterID = "cfc0743d-d960-49fb-9de8-96e063d5e4aa", + [string]$ActorUserID = "f67d943f-5397-4b3a-a229-695fe67ad700", + [string]$RequestedNodeName = "test-1", + [string]$DefaultNodeName = "test-2", + [string]$MatrixNodeName = "test-1", + [string]$ResultPath = "artifacts\c19z91-remote-workspace-real-adapter-runtime-gate-operator-confirmation-validation-smoke-result.json" +) + +Set-StrictMode -Version Latest +$ErrorActionPreference = "Stop" + +$repoRoot = (Resolve-Path (Join-Path $PSScriptRoot "..\..")).ProviderPath +$sourceResultPath = "artifacts\c19z91-remote-workspace-real-adapter-runtime-gate-operator-confirmation-validation-source-result.json" +$requiredValidationFields = @( + "schema_version", + "source_enablement_request_schema", + "validation_key", + "validation_status", + "operator_confirmation_required", + "operator_confirmation_present", + "remaining_required_validations", + "runtime_gate_state", + "runtime_effect", + "operator_default_action", + "allows_process_start", + "allows_payload_traffic", + "guardrail_summary" +) +$remainingRequiredValidations = @( + "binary_validation", + "permission_validation", + "supervisor_validation", + "health_probe_validation", + "payload_gate_validation" +) +$requiredGuardrailFields = @("activation_blocked", "process_start_allowed", "health_probe_enabled", "payload_traffic", "allows_process_start", "allows_payload_traffic") + +function Get-PropertyValue { + param([object]$Item, [string]$Name, [object]$Default = $null) + if ($null -eq $Item) { return $Default } + if ($Item -is [System.Collections.IDictionary]) { + if ($Item.Contains($Name)) { return $Item[$Name] } + return $Default + } + $property = $Item.PSObject.Properties[$Name] + if ($null -eq $property) { return $Default } + return $property.Value +} + +function Test-ObjectHasFields { + param([object]$Item, [string[]]$Fields) + if ($null -eq $Item) { return $false } + foreach ($field in $Fields) { + if ($Item -is [System.Collections.IDictionary]) { + if (-not $Item.Contains($field)) { return $false } + continue + } + if ($null -eq $Item.PSObject.Properties[$field]) { return $false } + } + return $true +} + +function Test-ArrayContainsAll { + param([object[]]$Actual, [string[]]$Required) + $values = @($Actual | ForEach-Object { [string]$_ }) + foreach ($item in $Required) { + if ($values -notcontains $item) { return $false } + } + return $true +} + +& powershell -ExecutionPolicy Bypass -File (Join-Path $PSScriptRoot "c19z90-remote-workspace-real-adapter-runtime-gate-explicit-enablement-request-compatibility-smoke.ps1") ` + -ApiBaseUrl $ApiBaseUrl ` + -ClusterID $ClusterID ` + -ActorUserID $ActorUserID ` + -RequestedNodeName $RequestedNodeName ` + -DefaultNodeName $DefaultNodeName ` + -MatrixNodeName $MatrixNodeName ` + -ResultPath $sourceResultPath | Out-Null + +$sourceFile = Join-Path $repoRoot $sourceResultPath +$sourceResult = Get-Content -Raw -Path $sourceFile | ConvertFrom-Json +$enablementRequest = Get-PropertyValue -Item $sourceResult -Name "runtime_gate_explicit_enablement_request" -Default $null +$guardrails = Get-PropertyValue -Item $enablementRequest -Name "guardrail_summary" -Default $null + +$validation = [ordered]@{ + schema_version = "rap.remote_workspace_real_adapter_runtime_gate_operator_confirmation_validation.v1" + source_enablement_request_schema = Get-PropertyValue -Item $enablementRequest -Name "schema_version" -Default $null + validation_key = "operator_confirmation" + validation_status = "satisfied_contract_only" + operator_confirmation_required = Get-PropertyValue -Item $enablementRequest -Name "requires_operator_confirmation" -Default $null + operator_confirmation_present = $true + remaining_required_validations = $remainingRequiredValidations + runtime_gate_state = "blocked_pending_remaining_validations" + runtime_effect = "contract_only_no_runtime_enablement" + operator_default_action = Get-PropertyValue -Item $enablementRequest -Name "operator_default_action" -Default $null + allows_process_start = $false + allows_payload_traffic = $false + guardrail_summary = $guardrails +} + +$validationFieldsCompatible = Test-ObjectHasFields -Item $validation -Fields $requiredValidationFields +$remainingValidationsCompatible = Test-ArrayContainsAll -Actual @(Get-PropertyValue -Item $validation -Name "remaining_required_validations" -Default @()) -Required $remainingRequiredValidations +$validationValuesCompatible = ( + [string](Get-PropertyValue -Item $validation -Name "schema_version" -Default "") -eq "rap.remote_workspace_real_adapter_runtime_gate_operator_confirmation_validation.v1" -and + [string](Get-PropertyValue -Item $validation -Name "source_enablement_request_schema" -Default "") -eq "rap.remote_workspace_real_adapter_runtime_gate_explicit_enablement_request.v1" -and + [string](Get-PropertyValue -Item $validation -Name "validation_key" -Default "") -eq "operator_confirmation" -and + [string](Get-PropertyValue -Item $validation -Name "validation_status" -Default "") -eq "satisfied_contract_only" -and + [bool](Get-PropertyValue -Item $validation -Name "operator_confirmation_required" -Default $false) -and + [bool](Get-PropertyValue -Item $validation -Name "operator_confirmation_present" -Default $false) -and + [string](Get-PropertyValue -Item $validation -Name "runtime_gate_state" -Default "") -eq "blocked_pending_remaining_validations" -and + [string](Get-PropertyValue -Item $validation -Name "runtime_effect" -Default "") -eq "contract_only_no_runtime_enablement" -and + [string](Get-PropertyValue -Item $validation -Name "operator_default_action" -Default "") -eq "keep_real_adapter_disabled" -and + -not [bool](Get-PropertyValue -Item $validation -Name "allows_process_start" -Default $true) -and + -not [bool](Get-PropertyValue -Item $validation -Name "allows_payload_traffic" -Default $true) +) +$guardrailsCompatible = ( + (Test-ObjectHasFields -Item $guardrails -Fields $requiredGuardrailFields) -and + [bool](Get-PropertyValue -Item $guardrails -Name "activation_blocked" -Default $false) -and + -not [bool](Get-PropertyValue -Item $guardrails -Name "process_start_allowed" -Default $true) -and + -not [bool](Get-PropertyValue -Item $guardrails -Name "health_probe_enabled" -Default $true) -and + [string](Get-PropertyValue -Item $guardrails -Name "payload_traffic" -Default "") -eq "none" -and + -not [bool](Get-PropertyValue -Item $guardrails -Name "allows_process_start" -Default $true) -and + -not [bool](Get-PropertyValue -Item $guardrails -Name "allows_payload_traffic" -Default $true) +) + +$checks = [ordered]@{ + source_smoke_passed = ([bool]$sourceResult.passed) + source_schema_expected = ([string]$sourceResult.schema_version -eq "c19z90.remote_workspace_real_adapter_runtime_gate_explicit_enablement_request_compatibility_smoke.v1") + enablement_request_present = ($null -ne $enablementRequest) + validation_fields_compatible = $validationFieldsCompatible + validation_values_compatible = $validationValuesCompatible + remaining_validations_compatible = $remainingValidationsCompatible + guardrails_compatible = $guardrailsCompatible +} +$failed = @($checks.GetEnumerator() | Where-Object { -not $_.Value } | ForEach-Object { $_.Key }) + +$result = [ordered]@{ + schema_version = "c19z91.remote_workspace_real_adapter_runtime_gate_operator_confirmation_validation_smoke.v1" + source_result_path = $sourceFile + cluster_id = $ClusterID + required_validation_fields = $requiredValidationFields + remaining_required_validations = $remainingRequiredValidations + required_guardrail_fields = $requiredGuardrailFields + runtime_gate_operator_confirmation_validation = $validation + checks = $checks + failed_checks = $failed + passed = ($failed.Count -eq 0) +} + +$fullResultPath = Join-Path $repoRoot $ResultPath +$resultDir = Split-Path -Parent $fullResultPath +if ($resultDir) { New-Item -ItemType Directory -Force -Path $resultDir | Out-Null } +$result | ConvertTo-Json -Depth 100 | Set-Content -Encoding UTF8 -Path $fullResultPath + +if (-not $result.passed) { + throw "C19Z91 remote workspace real-adapter runtime gate operator confirmation validation smoke failed. Result: $fullResultPath Failed: $($failed -join ', ')" +} + +Write-Host "C19Z91 remote workspace real-adapter runtime gate operator confirmation validation smoke passed. Result: $fullResultPath" +$result diff --git a/scripts/fabric/c19z92-remote-workspace-real-adapter-runtime-gate-operator-confirmation-validation-compatibility-smoke.ps1 b/scripts/fabric/c19z92-remote-workspace-real-adapter-runtime-gate-operator-confirmation-validation-compatibility-smoke.ps1 new file mode 100644 index 0000000..ee39346 --- /dev/null +++ b/scripts/fabric/c19z92-remote-workspace-real-adapter-runtime-gate-operator-confirmation-validation-compatibility-smoke.ps1 @@ -0,0 +1,147 @@ +param( + [string]$ApiBaseUrl = "http://192.168.200.61:18121/api/v1", + [string]$ClusterID = "cfc0743d-d960-49fb-9de8-96e063d5e4aa", + [string]$ActorUserID = "f67d943f-5397-4b3a-a229-695fe67ad700", + [string]$RequestedNodeName = "test-1", + [string]$DefaultNodeName = "test-2", + [string]$MatrixNodeName = "test-1", + [string]$ResultPath = "artifacts\c19z92-remote-workspace-real-adapter-runtime-gate-operator-confirmation-validation-compatibility-smoke-result.json" +) + +Set-StrictMode -Version Latest +$ErrorActionPreference = "Stop" + +$repoRoot = (Resolve-Path (Join-Path $PSScriptRoot "..\..")).ProviderPath +$sourceResultPath = "artifacts\c19z92-remote-workspace-real-adapter-runtime-gate-operator-confirmation-validation-source-result.json" +$requiredValidationFields = @( + "schema_version", + "source_enablement_request_schema", + "validation_key", + "validation_status", + "operator_confirmation_required", + "operator_confirmation_present", + "remaining_required_validations", + "runtime_gate_state", + "runtime_effect", + "operator_default_action", + "allows_process_start", + "allows_payload_traffic", + "guardrail_summary" +) +$remainingRequiredValidations = @( + "binary_validation", + "permission_validation", + "supervisor_validation", + "health_probe_validation", + "payload_gate_validation" +) +$requiredGuardrailFields = @("activation_blocked", "process_start_allowed", "health_probe_enabled", "payload_traffic", "allows_process_start", "allows_payload_traffic") + +function Get-PropertyValue { + param([object]$Item, [string]$Name, [object]$Default = $null) + if ($null -eq $Item) { return $Default } + if ($Item -is [System.Collections.IDictionary]) { + if ($Item.Contains($Name)) { return $Item[$Name] } + return $Default + } + $property = $Item.PSObject.Properties[$Name] + if ($null -eq $property) { return $Default } + return $property.Value +} + +function Test-ObjectHasFields { + param([object]$Item, [string[]]$Fields) + if ($null -eq $Item) { return $false } + foreach ($field in $Fields) { + if ($Item -is [System.Collections.IDictionary]) { + if (-not $Item.Contains($field)) { return $false } + continue + } + if ($null -eq $Item.PSObject.Properties[$field]) { return $false } + } + return $true +} + +function Test-ArrayContainsAll { + param([object[]]$Actual, [string[]]$Required) + $values = @($Actual | ForEach-Object { [string]$_ }) + foreach ($item in $Required) { + if ($values -notcontains $item) { return $false } + } + return $true +} + +& powershell -ExecutionPolicy Bypass -File (Join-Path $PSScriptRoot "c19z91-remote-workspace-real-adapter-runtime-gate-operator-confirmation-validation-smoke.ps1") ` + -ApiBaseUrl $ApiBaseUrl ` + -ClusterID $ClusterID ` + -ActorUserID $ActorUserID ` + -RequestedNodeName $RequestedNodeName ` + -DefaultNodeName $DefaultNodeName ` + -MatrixNodeName $MatrixNodeName ` + -ResultPath $sourceResultPath | Out-Null + +$sourceFile = Join-Path $repoRoot $sourceResultPath +$sourceResult = Get-Content -Raw -Path $sourceFile | ConvertFrom-Json +$validation = Get-PropertyValue -Item $sourceResult -Name "runtime_gate_operator_confirmation_validation" -Default $null +$guardrails = Get-PropertyValue -Item $validation -Name "guardrail_summary" -Default $null + +$validationFieldsCompatible = Test-ObjectHasFields -Item $validation -Fields $requiredValidationFields +$remainingValidationsCompatible = Test-ArrayContainsAll -Actual @(Get-PropertyValue -Item $validation -Name "remaining_required_validations" -Default @()) -Required $remainingRequiredValidations +$validationValuesCompatible = ( + [string](Get-PropertyValue -Item $validation -Name "schema_version" -Default "") -eq "rap.remote_workspace_real_adapter_runtime_gate_operator_confirmation_validation.v1" -and + [string](Get-PropertyValue -Item $validation -Name "source_enablement_request_schema" -Default "") -eq "rap.remote_workspace_real_adapter_runtime_gate_explicit_enablement_request.v1" -and + [string](Get-PropertyValue -Item $validation -Name "validation_key" -Default "") -eq "operator_confirmation" -and + [string](Get-PropertyValue -Item $validation -Name "validation_status" -Default "") -eq "satisfied_contract_only" -and + [bool](Get-PropertyValue -Item $validation -Name "operator_confirmation_required" -Default $false) -and + [bool](Get-PropertyValue -Item $validation -Name "operator_confirmation_present" -Default $false) -and + [string](Get-PropertyValue -Item $validation -Name "runtime_gate_state" -Default "") -eq "blocked_pending_remaining_validations" -and + [string](Get-PropertyValue -Item $validation -Name "runtime_effect" -Default "") -eq "contract_only_no_runtime_enablement" -and + [string](Get-PropertyValue -Item $validation -Name "operator_default_action" -Default "") -eq "keep_real_adapter_disabled" -and + -not [bool](Get-PropertyValue -Item $validation -Name "allows_process_start" -Default $true) -and + -not [bool](Get-PropertyValue -Item $validation -Name "allows_payload_traffic" -Default $true) +) +$guardrailsCompatible = ( + (Test-ObjectHasFields -Item $guardrails -Fields $requiredGuardrailFields) -and + [bool](Get-PropertyValue -Item $guardrails -Name "activation_blocked" -Default $false) -and + -not [bool](Get-PropertyValue -Item $guardrails -Name "process_start_allowed" -Default $true) -and + -not [bool](Get-PropertyValue -Item $guardrails -Name "health_probe_enabled" -Default $true) -and + [string](Get-PropertyValue -Item $guardrails -Name "payload_traffic" -Default "") -eq "none" -and + -not [bool](Get-PropertyValue -Item $guardrails -Name "allows_process_start" -Default $true) -and + -not [bool](Get-PropertyValue -Item $guardrails -Name "allows_payload_traffic" -Default $true) +) + +$checks = [ordered]@{ + source_smoke_passed = ([bool]$sourceResult.passed) + source_schema_expected = ([string]$sourceResult.schema_version -eq "c19z91.remote_workspace_real_adapter_runtime_gate_operator_confirmation_validation_smoke.v1") + operator_confirmation_validation_present = ($null -ne $validation) + validation_fields_compatible = $validationFieldsCompatible + validation_values_compatible = $validationValuesCompatible + remaining_validations_compatible = $remainingValidationsCompatible + guardrails_compatible = $guardrailsCompatible +} +$failed = @($checks.GetEnumerator() | Where-Object { -not $_.Value } | ForEach-Object { $_.Key }) + +$result = [ordered]@{ + schema_version = "c19z92.remote_workspace_real_adapter_runtime_gate_operator_confirmation_validation_compatibility_smoke.v1" + source_result_path = $sourceFile + cluster_id = $ClusterID + required_validation_fields = $requiredValidationFields + remaining_required_validations = $remainingRequiredValidations + required_guardrail_fields = $requiredGuardrailFields + runtime_gate_operator_confirmation_validation = $validation + checks = $checks + failed_checks = $failed + passed = ($failed.Count -eq 0) +} + +$fullResultPath = Join-Path $repoRoot $ResultPath +$resultDir = Split-Path -Parent $fullResultPath +if ($resultDir) { New-Item -ItemType Directory -Force -Path $resultDir | Out-Null } +$result | ConvertTo-Json -Depth 100 | Set-Content -Encoding UTF8 -Path $fullResultPath + +if (-not $result.passed) { + throw "C19Z92 remote workspace real-adapter runtime gate operator confirmation validation compatibility smoke failed. Result: $fullResultPath Failed: $($failed -join ', ')" +} + +Write-Host "C19Z92 remote workspace real-adapter runtime gate operator confirmation validation compatibility smoke passed. Result: $fullResultPath" +$result diff --git a/scripts/fabric/c19z93-remote-workspace-real-adapter-runtime-gate-binary-validation-smoke.ps1 b/scripts/fabric/c19z93-remote-workspace-real-adapter-runtime-gate-binary-validation-smoke.ps1 new file mode 100644 index 0000000..da49e64 --- /dev/null +++ b/scripts/fabric/c19z93-remote-workspace-real-adapter-runtime-gate-binary-validation-smoke.ps1 @@ -0,0 +1,165 @@ +param( + [string]$ApiBaseUrl = "http://192.168.200.61:18121/api/v1", + [string]$ClusterID = "cfc0743d-d960-49fb-9de8-96e063d5e4aa", + [string]$ActorUserID = "f67d943f-5397-4b3a-a229-695fe67ad700", + [string]$RequestedNodeName = "test-1", + [string]$DefaultNodeName = "test-2", + [string]$MatrixNodeName = "test-1", + [string]$ResultPath = "artifacts\c19z93-remote-workspace-real-adapter-runtime-gate-binary-validation-smoke-result.json" +) + +Set-StrictMode -Version Latest +$ErrorActionPreference = "Stop" + +$repoRoot = (Resolve-Path (Join-Path $PSScriptRoot "..\..")).ProviderPath +$sourceResultPath = "artifacts\c19z93-remote-workspace-real-adapter-runtime-gate-binary-validation-source-result.json" +$requiredValidationFields = @( + "schema_version", + "source_operator_confirmation_schema", + "validation_key", + "validation_status", + "binary_validation_required", + "binary_path_present", + "binary_identity_verified", + "remaining_required_validations", + "runtime_gate_state", + "runtime_effect", + "operator_default_action", + "allows_process_start", + "allows_payload_traffic", + "guardrail_summary" +) +$remainingRequiredValidations = @( + "permission_validation", + "supervisor_validation", + "health_probe_validation", + "payload_gate_validation" +) +$requiredGuardrailFields = @("activation_blocked", "process_start_allowed", "health_probe_enabled", "payload_traffic", "allows_process_start", "allows_payload_traffic") + +function Get-PropertyValue { + param([object]$Item, [string]$Name, [object]$Default = $null) + if ($null -eq $Item) { return $Default } + if ($Item -is [System.Collections.IDictionary]) { + if ($Item.Contains($Name)) { return $Item[$Name] } + return $Default + } + $property = $Item.PSObject.Properties[$Name] + if ($null -eq $property) { return $Default } + return $property.Value +} + +function Test-ObjectHasFields { + param([object]$Item, [string[]]$Fields) + if ($null -eq $Item) { return $false } + foreach ($field in $Fields) { + if ($Item -is [System.Collections.IDictionary]) { + if (-not $Item.Contains($field)) { return $false } + continue + } + if ($null -eq $Item.PSObject.Properties[$field]) { return $false } + } + return $true +} + +function Test-ArrayContainsAll { + param([object[]]$Actual, [string[]]$Required) + $values = @($Actual | ForEach-Object { [string]$_ }) + foreach ($item in $Required) { + if ($values -notcontains $item) { return $false } + } + return $true +} + +& powershell -ExecutionPolicy Bypass -File (Join-Path $PSScriptRoot "c19z92-remote-workspace-real-adapter-runtime-gate-operator-confirmation-validation-compatibility-smoke.ps1") ` + -ApiBaseUrl $ApiBaseUrl ` + -ClusterID $ClusterID ` + -ActorUserID $ActorUserID ` + -RequestedNodeName $RequestedNodeName ` + -DefaultNodeName $DefaultNodeName ` + -MatrixNodeName $MatrixNodeName ` + -ResultPath $sourceResultPath | Out-Null + +$sourceFile = Join-Path $repoRoot $sourceResultPath +$sourceResult = Get-Content -Raw -Path $sourceFile | ConvertFrom-Json +$operatorValidation = Get-PropertyValue -Item $sourceResult -Name "runtime_gate_operator_confirmation_validation" -Default $null +$guardrails = Get-PropertyValue -Item $operatorValidation -Name "guardrail_summary" -Default $null + +$validation = [ordered]@{ + schema_version = "rap.remote_workspace_real_adapter_runtime_gate_binary_validation.v1" + source_operator_confirmation_schema = Get-PropertyValue -Item $operatorValidation -Name "schema_version" -Default $null + validation_key = "binary_validation" + validation_status = "satisfied_contract_only" + binary_validation_required = $true + binary_path_present = $true + binary_identity_verified = $true + remaining_required_validations = $remainingRequiredValidations + runtime_gate_state = "blocked_pending_remaining_validations" + runtime_effect = "contract_only_no_runtime_enablement" + operator_default_action = Get-PropertyValue -Item $operatorValidation -Name "operator_default_action" -Default $null + allows_process_start = $false + allows_payload_traffic = $false + guardrail_summary = $guardrails +} + +$validationFieldsCompatible = Test-ObjectHasFields -Item $validation -Fields $requiredValidationFields +$remainingValidationsCompatible = Test-ArrayContainsAll -Actual @(Get-PropertyValue -Item $validation -Name "remaining_required_validations" -Default @()) -Required $remainingRequiredValidations +$validationValuesCompatible = ( + [string](Get-PropertyValue -Item $validation -Name "schema_version" -Default "") -eq "rap.remote_workspace_real_adapter_runtime_gate_binary_validation.v1" -and + [string](Get-PropertyValue -Item $validation -Name "source_operator_confirmation_schema" -Default "") -eq "rap.remote_workspace_real_adapter_runtime_gate_operator_confirmation_validation.v1" -and + [string](Get-PropertyValue -Item $validation -Name "validation_key" -Default "") -eq "binary_validation" -and + [string](Get-PropertyValue -Item $validation -Name "validation_status" -Default "") -eq "satisfied_contract_only" -and + [bool](Get-PropertyValue -Item $validation -Name "binary_validation_required" -Default $false) -and + [bool](Get-PropertyValue -Item $validation -Name "binary_path_present" -Default $false) -and + [bool](Get-PropertyValue -Item $validation -Name "binary_identity_verified" -Default $false) -and + [string](Get-PropertyValue -Item $validation -Name "runtime_gate_state" -Default "") -eq "blocked_pending_remaining_validations" -and + [string](Get-PropertyValue -Item $validation -Name "runtime_effect" -Default "") -eq "contract_only_no_runtime_enablement" -and + [string](Get-PropertyValue -Item $validation -Name "operator_default_action" -Default "") -eq "keep_real_adapter_disabled" -and + -not [bool](Get-PropertyValue -Item $validation -Name "allows_process_start" -Default $true) -and + -not [bool](Get-PropertyValue -Item $validation -Name "allows_payload_traffic" -Default $true) +) +$guardrailsCompatible = ( + (Test-ObjectHasFields -Item $guardrails -Fields $requiredGuardrailFields) -and + [bool](Get-PropertyValue -Item $guardrails -Name "activation_blocked" -Default $false) -and + -not [bool](Get-PropertyValue -Item $guardrails -Name "process_start_allowed" -Default $true) -and + -not [bool](Get-PropertyValue -Item $guardrails -Name "health_probe_enabled" -Default $true) -and + [string](Get-PropertyValue -Item $guardrails -Name "payload_traffic" -Default "") -eq "none" -and + -not [bool](Get-PropertyValue -Item $guardrails -Name "allows_process_start" -Default $true) -and + -not [bool](Get-PropertyValue -Item $guardrails -Name "allows_payload_traffic" -Default $true) +) + +$checks = [ordered]@{ + source_smoke_passed = ([bool]$sourceResult.passed) + source_schema_expected = ([string]$sourceResult.schema_version -eq "c19z92.remote_workspace_real_adapter_runtime_gate_operator_confirmation_validation_compatibility_smoke.v1") + operator_confirmation_validation_present = ($null -ne $operatorValidation) + validation_fields_compatible = $validationFieldsCompatible + validation_values_compatible = $validationValuesCompatible + remaining_validations_compatible = $remainingValidationsCompatible + guardrails_compatible = $guardrailsCompatible +} +$failed = @($checks.GetEnumerator() | Where-Object { -not $_.Value } | ForEach-Object { $_.Key }) + +$result = [ordered]@{ + schema_version = "c19z93.remote_workspace_real_adapter_runtime_gate_binary_validation_smoke.v1" + source_result_path = $sourceFile + cluster_id = $ClusterID + required_validation_fields = $requiredValidationFields + remaining_required_validations = $remainingRequiredValidations + required_guardrail_fields = $requiredGuardrailFields + runtime_gate_binary_validation = $validation + checks = $checks + failed_checks = $failed + passed = ($failed.Count -eq 0) +} + +$fullResultPath = Join-Path $repoRoot $ResultPath +$resultDir = Split-Path -Parent $fullResultPath +if ($resultDir) { New-Item -ItemType Directory -Force -Path $resultDir | Out-Null } +$result | ConvertTo-Json -Depth 100 | Set-Content -Encoding UTF8 -Path $fullResultPath + +if (-not $result.passed) { + throw "C19Z93 remote workspace real-adapter runtime gate binary validation smoke failed. Result: $fullResultPath Failed: $($failed -join ', ')" +} + +Write-Host "C19Z93 remote workspace real-adapter runtime gate binary validation smoke passed. Result: $fullResultPath" +$result diff --git a/scripts/fabric/c19z94-remote-workspace-real-adapter-runtime-gate-binary-validation-compatibility-smoke.ps1 b/scripts/fabric/c19z94-remote-workspace-real-adapter-runtime-gate-binary-validation-compatibility-smoke.ps1 new file mode 100644 index 0000000..bd5c67c --- /dev/null +++ b/scripts/fabric/c19z94-remote-workspace-real-adapter-runtime-gate-binary-validation-compatibility-smoke.ps1 @@ -0,0 +1,148 @@ +param( + [string]$ApiBaseUrl = "http://192.168.200.61:18121/api/v1", + [string]$ClusterID = "cfc0743d-d960-49fb-9de8-96e063d5e4aa", + [string]$ActorUserID = "f67d943f-5397-4b3a-a229-695fe67ad700", + [string]$RequestedNodeName = "test-1", + [string]$DefaultNodeName = "test-2", + [string]$MatrixNodeName = "test-1", + [string]$ResultPath = "artifacts\c19z94-remote-workspace-real-adapter-runtime-gate-binary-validation-compatibility-smoke-result.json" +) + +Set-StrictMode -Version Latest +$ErrorActionPreference = "Stop" + +$repoRoot = (Resolve-Path (Join-Path $PSScriptRoot "..\..")).ProviderPath +$sourceResultPath = "artifacts\c19z94-remote-workspace-real-adapter-runtime-gate-binary-validation-source-result.json" +$requiredValidationFields = @( + "schema_version", + "source_operator_confirmation_schema", + "validation_key", + "validation_status", + "binary_validation_required", + "binary_path_present", + "binary_identity_verified", + "remaining_required_validations", + "runtime_gate_state", + "runtime_effect", + "operator_default_action", + "allows_process_start", + "allows_payload_traffic", + "guardrail_summary" +) +$remainingRequiredValidations = @( + "permission_validation", + "supervisor_validation", + "health_probe_validation", + "payload_gate_validation" +) +$requiredGuardrailFields = @("activation_blocked", "process_start_allowed", "health_probe_enabled", "payload_traffic", "allows_process_start", "allows_payload_traffic") + +function Get-PropertyValue { + param([object]$Item, [string]$Name, [object]$Default = $null) + if ($null -eq $Item) { return $Default } + if ($Item -is [System.Collections.IDictionary]) { + if ($Item.Contains($Name)) { return $Item[$Name] } + return $Default + } + $property = $Item.PSObject.Properties[$Name] + if ($null -eq $property) { return $Default } + return $property.Value +} + +function Test-ObjectHasFields { + param([object]$Item, [string[]]$Fields) + if ($null -eq $Item) { return $false } + foreach ($field in $Fields) { + if ($Item -is [System.Collections.IDictionary]) { + if (-not $Item.Contains($field)) { return $false } + continue + } + if ($null -eq $Item.PSObject.Properties[$field]) { return $false } + } + return $true +} + +function Test-ArrayContainsAll { + param([object[]]$Actual, [string[]]$Required) + $values = @($Actual | ForEach-Object { [string]$_ }) + foreach ($item in $Required) { + if ($values -notcontains $item) { return $false } + } + return $true +} + +& powershell -ExecutionPolicy Bypass -File (Join-Path $PSScriptRoot "c19z93-remote-workspace-real-adapter-runtime-gate-binary-validation-smoke.ps1") ` + -ApiBaseUrl $ApiBaseUrl ` + -ClusterID $ClusterID ` + -ActorUserID $ActorUserID ` + -RequestedNodeName $RequestedNodeName ` + -DefaultNodeName $DefaultNodeName ` + -MatrixNodeName $MatrixNodeName ` + -ResultPath $sourceResultPath | Out-Null + +$sourceFile = Join-Path $repoRoot $sourceResultPath +$sourceResult = Get-Content -Raw -Path $sourceFile | ConvertFrom-Json +$validation = Get-PropertyValue -Item $sourceResult -Name "runtime_gate_binary_validation" -Default $null +$guardrails = Get-PropertyValue -Item $validation -Name "guardrail_summary" -Default $null + +$validationFieldsCompatible = Test-ObjectHasFields -Item $validation -Fields $requiredValidationFields +$remainingValidationsCompatible = Test-ArrayContainsAll -Actual @(Get-PropertyValue -Item $validation -Name "remaining_required_validations" -Default @()) -Required $remainingRequiredValidations +$validationValuesCompatible = ( + [string](Get-PropertyValue -Item $validation -Name "schema_version" -Default "") -eq "rap.remote_workspace_real_adapter_runtime_gate_binary_validation.v1" -and + [string](Get-PropertyValue -Item $validation -Name "source_operator_confirmation_schema" -Default "") -eq "rap.remote_workspace_real_adapter_runtime_gate_operator_confirmation_validation.v1" -and + [string](Get-PropertyValue -Item $validation -Name "validation_key" -Default "") -eq "binary_validation" -and + [string](Get-PropertyValue -Item $validation -Name "validation_status" -Default "") -eq "satisfied_contract_only" -and + [bool](Get-PropertyValue -Item $validation -Name "binary_validation_required" -Default $false) -and + [bool](Get-PropertyValue -Item $validation -Name "binary_path_present" -Default $false) -and + [bool](Get-PropertyValue -Item $validation -Name "binary_identity_verified" -Default $false) -and + [string](Get-PropertyValue -Item $validation -Name "runtime_gate_state" -Default "") -eq "blocked_pending_remaining_validations" -and + [string](Get-PropertyValue -Item $validation -Name "runtime_effect" -Default "") -eq "contract_only_no_runtime_enablement" -and + [string](Get-PropertyValue -Item $validation -Name "operator_default_action" -Default "") -eq "keep_real_adapter_disabled" -and + -not [bool](Get-PropertyValue -Item $validation -Name "allows_process_start" -Default $true) -and + -not [bool](Get-PropertyValue -Item $validation -Name "allows_payload_traffic" -Default $true) +) +$guardrailsCompatible = ( + (Test-ObjectHasFields -Item $guardrails -Fields $requiredGuardrailFields) -and + [bool](Get-PropertyValue -Item $guardrails -Name "activation_blocked" -Default $false) -and + -not [bool](Get-PropertyValue -Item $guardrails -Name "process_start_allowed" -Default $true) -and + -not [bool](Get-PropertyValue -Item $guardrails -Name "health_probe_enabled" -Default $true) -and + [string](Get-PropertyValue -Item $guardrails -Name "payload_traffic" -Default "") -eq "none" -and + -not [bool](Get-PropertyValue -Item $guardrails -Name "allows_process_start" -Default $true) -and + -not [bool](Get-PropertyValue -Item $guardrails -Name "allows_payload_traffic" -Default $true) +) + +$checks = [ordered]@{ + source_smoke_passed = ([bool]$sourceResult.passed) + source_schema_expected = ([string]$sourceResult.schema_version -eq "c19z93.remote_workspace_real_adapter_runtime_gate_binary_validation_smoke.v1") + binary_validation_present = ($null -ne $validation) + validation_fields_compatible = $validationFieldsCompatible + validation_values_compatible = $validationValuesCompatible + remaining_validations_compatible = $remainingValidationsCompatible + guardrails_compatible = $guardrailsCompatible +} +$failed = @($checks.GetEnumerator() | Where-Object { -not $_.Value } | ForEach-Object { $_.Key }) + +$result = [ordered]@{ + schema_version = "c19z94.remote_workspace_real_adapter_runtime_gate_binary_validation_compatibility_smoke.v1" + source_result_path = $sourceFile + cluster_id = $ClusterID + required_validation_fields = $requiredValidationFields + remaining_required_validations = $remainingRequiredValidations + required_guardrail_fields = $requiredGuardrailFields + runtime_gate_binary_validation = $validation + checks = $checks + failed_checks = $failed + passed = ($failed.Count -eq 0) +} + +$fullResultPath = Join-Path $repoRoot $ResultPath +$resultDir = Split-Path -Parent $fullResultPath +if ($resultDir) { New-Item -ItemType Directory -Force -Path $resultDir | Out-Null } +$result | ConvertTo-Json -Depth 100 | Set-Content -Encoding UTF8 -Path $fullResultPath + +if (-not $result.passed) { + throw "C19Z94 remote workspace real-adapter runtime gate binary validation compatibility smoke failed. Result: $fullResultPath Failed: $($failed -join ', ')" +} + +Write-Host "C19Z94 remote workspace real-adapter runtime gate binary validation compatibility smoke passed. Result: $fullResultPath" +$result diff --git a/scripts/fabric/c19z95-remote-workspace-real-adapter-runtime-gate-permission-validation-smoke.ps1 b/scripts/fabric/c19z95-remote-workspace-real-adapter-runtime-gate-permission-validation-smoke.ps1 new file mode 100644 index 0000000..5665f10 --- /dev/null +++ b/scripts/fabric/c19z95-remote-workspace-real-adapter-runtime-gate-permission-validation-smoke.ps1 @@ -0,0 +1,164 @@ +param( + [string]$ApiBaseUrl = "http://192.168.200.61:18121/api/v1", + [string]$ClusterID = "cfc0743d-d960-49fb-9de8-96e063d5e4aa", + [string]$ActorUserID = "f67d943f-5397-4b3a-a229-695fe67ad700", + [string]$RequestedNodeName = "test-1", + [string]$DefaultNodeName = "test-2", + [string]$MatrixNodeName = "test-1", + [string]$ResultPath = "artifacts\c19z95-remote-workspace-real-adapter-runtime-gate-permission-validation-smoke-result.json" +) + +Set-StrictMode -Version Latest +$ErrorActionPreference = "Stop" + +$repoRoot = (Resolve-Path (Join-Path $PSScriptRoot "..\..")).ProviderPath +$sourceResultPath = "artifacts\c19z95-remote-workspace-real-adapter-runtime-gate-permission-validation-source-result.json" +$requiredValidationFields = @( + "schema_version", + "source_binary_validation_schema", + "validation_key", + "validation_status", + "permission_validation_required", + "service_account_present", + "least_privilege_scope_verified", + "remaining_required_validations", + "runtime_gate_state", + "runtime_effect", + "operator_default_action", + "allows_process_start", + "allows_payload_traffic", + "guardrail_summary" +) +$remainingRequiredValidations = @( + "supervisor_validation", + "health_probe_validation", + "payload_gate_validation" +) +$requiredGuardrailFields = @("activation_blocked", "process_start_allowed", "health_probe_enabled", "payload_traffic", "allows_process_start", "allows_payload_traffic") + +function Get-PropertyValue { + param([object]$Item, [string]$Name, [object]$Default = $null) + if ($null -eq $Item) { return $Default } + if ($Item -is [System.Collections.IDictionary]) { + if ($Item.Contains($Name)) { return $Item[$Name] } + return $Default + } + $property = $Item.PSObject.Properties[$Name] + if ($null -eq $property) { return $Default } + return $property.Value +} + +function Test-ObjectHasFields { + param([object]$Item, [string[]]$Fields) + if ($null -eq $Item) { return $false } + foreach ($field in $Fields) { + if ($Item -is [System.Collections.IDictionary]) { + if (-not $Item.Contains($field)) { return $false } + continue + } + if ($null -eq $Item.PSObject.Properties[$field]) { return $false } + } + return $true +} + +function Test-ArrayContainsAll { + param([object[]]$Actual, [string[]]$Required) + $values = @($Actual | ForEach-Object { [string]$_ }) + foreach ($item in $Required) { + if ($values -notcontains $item) { return $false } + } + return $true +} + +& powershell -ExecutionPolicy Bypass -File (Join-Path $PSScriptRoot "c19z94-remote-workspace-real-adapter-runtime-gate-binary-validation-compatibility-smoke.ps1") ` + -ApiBaseUrl $ApiBaseUrl ` + -ClusterID $ClusterID ` + -ActorUserID $ActorUserID ` + -RequestedNodeName $RequestedNodeName ` + -DefaultNodeName $DefaultNodeName ` + -MatrixNodeName $MatrixNodeName ` + -ResultPath $sourceResultPath | Out-Null + +$sourceFile = Join-Path $repoRoot $sourceResultPath +$sourceResult = Get-Content -Raw -Path $sourceFile | ConvertFrom-Json +$binaryValidation = Get-PropertyValue -Item $sourceResult -Name "runtime_gate_binary_validation" -Default $null +$guardrails = Get-PropertyValue -Item $binaryValidation -Name "guardrail_summary" -Default $null + +$validation = [ordered]@{ + schema_version = "rap.remote_workspace_real_adapter_runtime_gate_permission_validation.v1" + source_binary_validation_schema = Get-PropertyValue -Item $binaryValidation -Name "schema_version" -Default $null + validation_key = "permission_validation" + validation_status = "satisfied_contract_only" + permission_validation_required = $true + service_account_present = $true + least_privilege_scope_verified = $true + remaining_required_validations = $remainingRequiredValidations + runtime_gate_state = "blocked_pending_remaining_validations" + runtime_effect = "contract_only_no_runtime_enablement" + operator_default_action = Get-PropertyValue -Item $binaryValidation -Name "operator_default_action" -Default $null + allows_process_start = $false + allows_payload_traffic = $false + guardrail_summary = $guardrails +} + +$validationFieldsCompatible = Test-ObjectHasFields -Item $validation -Fields $requiredValidationFields +$remainingValidationsCompatible = Test-ArrayContainsAll -Actual @(Get-PropertyValue -Item $validation -Name "remaining_required_validations" -Default @()) -Required $remainingRequiredValidations +$validationValuesCompatible = ( + [string](Get-PropertyValue -Item $validation -Name "schema_version" -Default "") -eq "rap.remote_workspace_real_adapter_runtime_gate_permission_validation.v1" -and + [string](Get-PropertyValue -Item $validation -Name "source_binary_validation_schema" -Default "") -eq "rap.remote_workspace_real_adapter_runtime_gate_binary_validation.v1" -and + [string](Get-PropertyValue -Item $validation -Name "validation_key" -Default "") -eq "permission_validation" -and + [string](Get-PropertyValue -Item $validation -Name "validation_status" -Default "") -eq "satisfied_contract_only" -and + [bool](Get-PropertyValue -Item $validation -Name "permission_validation_required" -Default $false) -and + [bool](Get-PropertyValue -Item $validation -Name "service_account_present" -Default $false) -and + [bool](Get-PropertyValue -Item $validation -Name "least_privilege_scope_verified" -Default $false) -and + [string](Get-PropertyValue -Item $validation -Name "runtime_gate_state" -Default "") -eq "blocked_pending_remaining_validations" -and + [string](Get-PropertyValue -Item $validation -Name "runtime_effect" -Default "") -eq "contract_only_no_runtime_enablement" -and + [string](Get-PropertyValue -Item $validation -Name "operator_default_action" -Default "") -eq "keep_real_adapter_disabled" -and + -not [bool](Get-PropertyValue -Item $validation -Name "allows_process_start" -Default $true) -and + -not [bool](Get-PropertyValue -Item $validation -Name "allows_payload_traffic" -Default $true) +) +$guardrailsCompatible = ( + (Test-ObjectHasFields -Item $guardrails -Fields $requiredGuardrailFields) -and + [bool](Get-PropertyValue -Item $guardrails -Name "activation_blocked" -Default $false) -and + -not [bool](Get-PropertyValue -Item $guardrails -Name "process_start_allowed" -Default $true) -and + -not [bool](Get-PropertyValue -Item $guardrails -Name "health_probe_enabled" -Default $true) -and + [string](Get-PropertyValue -Item $guardrails -Name "payload_traffic" -Default "") -eq "none" -and + -not [bool](Get-PropertyValue -Item $guardrails -Name "allows_process_start" -Default $true) -and + -not [bool](Get-PropertyValue -Item $guardrails -Name "allows_payload_traffic" -Default $true) +) + +$checks = [ordered]@{ + source_smoke_passed = ([bool]$sourceResult.passed) + source_schema_expected = ([string]$sourceResult.schema_version -eq "c19z94.remote_workspace_real_adapter_runtime_gate_binary_validation_compatibility_smoke.v1") + binary_validation_present = ($null -ne $binaryValidation) + validation_fields_compatible = $validationFieldsCompatible + validation_values_compatible = $validationValuesCompatible + remaining_validations_compatible = $remainingValidationsCompatible + guardrails_compatible = $guardrailsCompatible +} +$failed = @($checks.GetEnumerator() | Where-Object { -not $_.Value } | ForEach-Object { $_.Key }) + +$result = [ordered]@{ + schema_version = "c19z95.remote_workspace_real_adapter_runtime_gate_permission_validation_smoke.v1" + source_result_path = $sourceFile + cluster_id = $ClusterID + required_validation_fields = $requiredValidationFields + remaining_required_validations = $remainingRequiredValidations + required_guardrail_fields = $requiredGuardrailFields + runtime_gate_permission_validation = $validation + checks = $checks + failed_checks = $failed + passed = ($failed.Count -eq 0) +} + +$fullResultPath = Join-Path $repoRoot $ResultPath +$resultDir = Split-Path -Parent $fullResultPath +if ($resultDir) { New-Item -ItemType Directory -Force -Path $resultDir | Out-Null } +$result | ConvertTo-Json -Depth 100 | Set-Content -Encoding UTF8 -Path $fullResultPath + +if (-not $result.passed) { + throw "C19Z95 remote workspace real-adapter runtime gate permission validation smoke failed. Result: $fullResultPath Failed: $($failed -join ', ')" +} + +Write-Host "C19Z95 remote workspace real-adapter runtime gate permission validation smoke passed. Result: $fullResultPath" +$result diff --git a/scripts/fabric/c19z96-remote-workspace-real-adapter-runtime-gate-permission-validation-compatibility-smoke.ps1 b/scripts/fabric/c19z96-remote-workspace-real-adapter-runtime-gate-permission-validation-compatibility-smoke.ps1 new file mode 100644 index 0000000..c9cbf3b --- /dev/null +++ b/scripts/fabric/c19z96-remote-workspace-real-adapter-runtime-gate-permission-validation-compatibility-smoke.ps1 @@ -0,0 +1,147 @@ +param( + [string]$ApiBaseUrl = "http://192.168.200.61:18121/api/v1", + [string]$ClusterID = "cfc0743d-d960-49fb-9de8-96e063d5e4aa", + [string]$ActorUserID = "f67d943f-5397-4b3a-a229-695fe67ad700", + [string]$RequestedNodeName = "test-1", + [string]$DefaultNodeName = "test-2", + [string]$MatrixNodeName = "test-1", + [string]$ResultPath = "artifacts\c19z96-remote-workspace-real-adapter-runtime-gate-permission-validation-compatibility-smoke-result.json" +) + +Set-StrictMode -Version Latest +$ErrorActionPreference = "Stop" + +$repoRoot = (Resolve-Path (Join-Path $PSScriptRoot "..\..")).ProviderPath +$sourceResultPath = "artifacts\c19z96-remote-workspace-real-adapter-runtime-gate-permission-validation-source-result.json" +$requiredValidationFields = @( + "schema_version", + "source_binary_validation_schema", + "validation_key", + "validation_status", + "permission_validation_required", + "service_account_present", + "least_privilege_scope_verified", + "remaining_required_validations", + "runtime_gate_state", + "runtime_effect", + "operator_default_action", + "allows_process_start", + "allows_payload_traffic", + "guardrail_summary" +) +$remainingRequiredValidations = @( + "supervisor_validation", + "health_probe_validation", + "payload_gate_validation" +) +$requiredGuardrailFields = @("activation_blocked", "process_start_allowed", "health_probe_enabled", "payload_traffic", "allows_process_start", "allows_payload_traffic") + +function Get-PropertyValue { + param([object]$Item, [string]$Name, [object]$Default = $null) + if ($null -eq $Item) { return $Default } + if ($Item -is [System.Collections.IDictionary]) { + if ($Item.Contains($Name)) { return $Item[$Name] } + return $Default + } + $property = $Item.PSObject.Properties[$Name] + if ($null -eq $property) { return $Default } + return $property.Value +} + +function Test-ObjectHasFields { + param([object]$Item, [string[]]$Fields) + if ($null -eq $Item) { return $false } + foreach ($field in $Fields) { + if ($Item -is [System.Collections.IDictionary]) { + if (-not $Item.Contains($field)) { return $false } + continue + } + if ($null -eq $Item.PSObject.Properties[$field]) { return $false } + } + return $true +} + +function Test-ArrayContainsAll { + param([object[]]$Actual, [string[]]$Required) + $values = @($Actual | ForEach-Object { [string]$_ }) + foreach ($item in $Required) { + if ($values -notcontains $item) { return $false } + } + return $true +} + +& powershell -ExecutionPolicy Bypass -File (Join-Path $PSScriptRoot "c19z95-remote-workspace-real-adapter-runtime-gate-permission-validation-smoke.ps1") ` + -ApiBaseUrl $ApiBaseUrl ` + -ClusterID $ClusterID ` + -ActorUserID $ActorUserID ` + -RequestedNodeName $RequestedNodeName ` + -DefaultNodeName $DefaultNodeName ` + -MatrixNodeName $MatrixNodeName ` + -ResultPath $sourceResultPath | Out-Null + +$sourceFile = Join-Path $repoRoot $sourceResultPath +$sourceResult = Get-Content -Raw -Path $sourceFile | ConvertFrom-Json +$validation = Get-PropertyValue -Item $sourceResult -Name "runtime_gate_permission_validation" -Default $null +$guardrails = Get-PropertyValue -Item $validation -Name "guardrail_summary" -Default $null + +$validationFieldsCompatible = Test-ObjectHasFields -Item $validation -Fields $requiredValidationFields +$remainingValidationsCompatible = Test-ArrayContainsAll -Actual @(Get-PropertyValue -Item $validation -Name "remaining_required_validations" -Default @()) -Required $remainingRequiredValidations +$validationValuesCompatible = ( + [string](Get-PropertyValue -Item $validation -Name "schema_version" -Default "") -eq "rap.remote_workspace_real_adapter_runtime_gate_permission_validation.v1" -and + [string](Get-PropertyValue -Item $validation -Name "source_binary_validation_schema" -Default "") -eq "rap.remote_workspace_real_adapter_runtime_gate_binary_validation.v1" -and + [string](Get-PropertyValue -Item $validation -Name "validation_key" -Default "") -eq "permission_validation" -and + [string](Get-PropertyValue -Item $validation -Name "validation_status" -Default "") -eq "satisfied_contract_only" -and + [bool](Get-PropertyValue -Item $validation -Name "permission_validation_required" -Default $false) -and + [bool](Get-PropertyValue -Item $validation -Name "service_account_present" -Default $false) -and + [bool](Get-PropertyValue -Item $validation -Name "least_privilege_scope_verified" -Default $false) -and + [string](Get-PropertyValue -Item $validation -Name "runtime_gate_state" -Default "") -eq "blocked_pending_remaining_validations" -and + [string](Get-PropertyValue -Item $validation -Name "runtime_effect" -Default "") -eq "contract_only_no_runtime_enablement" -and + [string](Get-PropertyValue -Item $validation -Name "operator_default_action" -Default "") -eq "keep_real_adapter_disabled" -and + -not [bool](Get-PropertyValue -Item $validation -Name "allows_process_start" -Default $true) -and + -not [bool](Get-PropertyValue -Item $validation -Name "allows_payload_traffic" -Default $true) +) +$guardrailsCompatible = ( + (Test-ObjectHasFields -Item $guardrails -Fields $requiredGuardrailFields) -and + [bool](Get-PropertyValue -Item $guardrails -Name "activation_blocked" -Default $false) -and + -not [bool](Get-PropertyValue -Item $guardrails -Name "process_start_allowed" -Default $true) -and + -not [bool](Get-PropertyValue -Item $guardrails -Name "health_probe_enabled" -Default $true) -and + [string](Get-PropertyValue -Item $guardrails -Name "payload_traffic" -Default "") -eq "none" -and + -not [bool](Get-PropertyValue -Item $guardrails -Name "allows_process_start" -Default $true) -and + -not [bool](Get-PropertyValue -Item $guardrails -Name "allows_payload_traffic" -Default $true) +) + +$checks = [ordered]@{ + source_smoke_passed = ([bool]$sourceResult.passed) + source_schema_expected = ([string]$sourceResult.schema_version -eq "c19z95.remote_workspace_real_adapter_runtime_gate_permission_validation_smoke.v1") + permission_validation_present = ($null -ne $validation) + validation_fields_compatible = $validationFieldsCompatible + validation_values_compatible = $validationValuesCompatible + remaining_validations_compatible = $remainingValidationsCompatible + guardrails_compatible = $guardrailsCompatible +} +$failed = @($checks.GetEnumerator() | Where-Object { -not $_.Value } | ForEach-Object { $_.Key }) + +$result = [ordered]@{ + schema_version = "c19z96.remote_workspace_real_adapter_runtime_gate_permission_validation_compatibility_smoke.v1" + source_result_path = $sourceFile + cluster_id = $ClusterID + required_validation_fields = $requiredValidationFields + remaining_required_validations = $remainingRequiredValidations + required_guardrail_fields = $requiredGuardrailFields + runtime_gate_permission_validation = $validation + checks = $checks + failed_checks = $failed + passed = ($failed.Count -eq 0) +} + +$fullResultPath = Join-Path $repoRoot $ResultPath +$resultDir = Split-Path -Parent $fullResultPath +if ($resultDir) { New-Item -ItemType Directory -Force -Path $resultDir | Out-Null } +$result | ConvertTo-Json -Depth 100 | Set-Content -Encoding UTF8 -Path $fullResultPath + +if (-not $result.passed) { + throw "C19Z96 remote workspace real-adapter runtime gate permission validation compatibility smoke failed. Result: $fullResultPath Failed: $($failed -join ', ')" +} + +Write-Host "C19Z96 remote workspace real-adapter runtime gate permission validation compatibility smoke passed. Result: $fullResultPath" +$result diff --git a/scripts/fabric/c19z97-remote-workspace-real-adapter-runtime-gate-supervisor-validation-smoke.ps1 b/scripts/fabric/c19z97-remote-workspace-real-adapter-runtime-gate-supervisor-validation-smoke.ps1 new file mode 100644 index 0000000..ad995fa --- /dev/null +++ b/scripts/fabric/c19z97-remote-workspace-real-adapter-runtime-gate-supervisor-validation-smoke.ps1 @@ -0,0 +1,163 @@ +param( + [string]$ApiBaseUrl = "http://192.168.200.61:18121/api/v1", + [string]$ClusterID = "cfc0743d-d960-49fb-9de8-96e063d5e4aa", + [string]$ActorUserID = "f67d943f-5397-4b3a-a229-695fe67ad700", + [string]$RequestedNodeName = "test-1", + [string]$DefaultNodeName = "test-2", + [string]$MatrixNodeName = "test-1", + [string]$ResultPath = "artifacts\c19z97-remote-workspace-real-adapter-runtime-gate-supervisor-validation-smoke-result.json" +) + +Set-StrictMode -Version Latest +$ErrorActionPreference = "Stop" + +$repoRoot = (Resolve-Path (Join-Path $PSScriptRoot "..\..")).ProviderPath +$sourceResultPath = "artifacts\c19z97-remote-workspace-real-adapter-runtime-gate-supervisor-validation-source-result.json" +$requiredValidationFields = @( + "schema_version", + "source_permission_validation_schema", + "validation_key", + "validation_status", + "supervisor_validation_required", + "process_limits_verified", + "restart_policy_verified", + "remaining_required_validations", + "runtime_gate_state", + "runtime_effect", + "operator_default_action", + "allows_process_start", + "allows_payload_traffic", + "guardrail_summary" +) +$remainingRequiredValidations = @( + "health_probe_validation", + "payload_gate_validation" +) +$requiredGuardrailFields = @("activation_blocked", "process_start_allowed", "health_probe_enabled", "payload_traffic", "allows_process_start", "allows_payload_traffic") + +function Get-PropertyValue { + param([object]$Item, [string]$Name, [object]$Default = $null) + if ($null -eq $Item) { return $Default } + if ($Item -is [System.Collections.IDictionary]) { + if ($Item.Contains($Name)) { return $Item[$Name] } + return $Default + } + $property = $Item.PSObject.Properties[$Name] + if ($null -eq $property) { return $Default } + return $property.Value +} + +function Test-ObjectHasFields { + param([object]$Item, [string[]]$Fields) + if ($null -eq $Item) { return $false } + foreach ($field in $Fields) { + if ($Item -is [System.Collections.IDictionary]) { + if (-not $Item.Contains($field)) { return $false } + continue + } + if ($null -eq $Item.PSObject.Properties[$field]) { return $false } + } + return $true +} + +function Test-ArrayContainsAll { + param([object[]]$Actual, [string[]]$Required) + $values = @($Actual | ForEach-Object { [string]$_ }) + foreach ($item in $Required) { + if ($values -notcontains $item) { return $false } + } + return $true +} + +& powershell -ExecutionPolicy Bypass -File (Join-Path $PSScriptRoot "c19z96-remote-workspace-real-adapter-runtime-gate-permission-validation-compatibility-smoke.ps1") ` + -ApiBaseUrl $ApiBaseUrl ` + -ClusterID $ClusterID ` + -ActorUserID $ActorUserID ` + -RequestedNodeName $RequestedNodeName ` + -DefaultNodeName $DefaultNodeName ` + -MatrixNodeName $MatrixNodeName ` + -ResultPath $sourceResultPath | Out-Null + +$sourceFile = Join-Path $repoRoot $sourceResultPath +$sourceResult = Get-Content -Raw -Path $sourceFile | ConvertFrom-Json +$permissionValidation = Get-PropertyValue -Item $sourceResult -Name "runtime_gate_permission_validation" -Default $null +$guardrails = Get-PropertyValue -Item $permissionValidation -Name "guardrail_summary" -Default $null + +$validation = [ordered]@{ + schema_version = "rap.remote_workspace_real_adapter_runtime_gate_supervisor_validation.v1" + source_permission_validation_schema = Get-PropertyValue -Item $permissionValidation -Name "schema_version" -Default $null + validation_key = "supervisor_validation" + validation_status = "satisfied_contract_only" + supervisor_validation_required = $true + process_limits_verified = $true + restart_policy_verified = $true + remaining_required_validations = $remainingRequiredValidations + runtime_gate_state = "blocked_pending_remaining_validations" + runtime_effect = "contract_only_no_runtime_enablement" + operator_default_action = Get-PropertyValue -Item $permissionValidation -Name "operator_default_action" -Default $null + allows_process_start = $false + allows_payload_traffic = $false + guardrail_summary = $guardrails +} + +$validationFieldsCompatible = Test-ObjectHasFields -Item $validation -Fields $requiredValidationFields +$remainingValidationsCompatible = Test-ArrayContainsAll -Actual @(Get-PropertyValue -Item $validation -Name "remaining_required_validations" -Default @()) -Required $remainingRequiredValidations +$validationValuesCompatible = ( + [string](Get-PropertyValue -Item $validation -Name "schema_version" -Default "") -eq "rap.remote_workspace_real_adapter_runtime_gate_supervisor_validation.v1" -and + [string](Get-PropertyValue -Item $validation -Name "source_permission_validation_schema" -Default "") -eq "rap.remote_workspace_real_adapter_runtime_gate_permission_validation.v1" -and + [string](Get-PropertyValue -Item $validation -Name "validation_key" -Default "") -eq "supervisor_validation" -and + [string](Get-PropertyValue -Item $validation -Name "validation_status" -Default "") -eq "satisfied_contract_only" -and + [bool](Get-PropertyValue -Item $validation -Name "supervisor_validation_required" -Default $false) -and + [bool](Get-PropertyValue -Item $validation -Name "process_limits_verified" -Default $false) -and + [bool](Get-PropertyValue -Item $validation -Name "restart_policy_verified" -Default $false) -and + [string](Get-PropertyValue -Item $validation -Name "runtime_gate_state" -Default "") -eq "blocked_pending_remaining_validations" -and + [string](Get-PropertyValue -Item $validation -Name "runtime_effect" -Default "") -eq "contract_only_no_runtime_enablement" -and + [string](Get-PropertyValue -Item $validation -Name "operator_default_action" -Default "") -eq "keep_real_adapter_disabled" -and + -not [bool](Get-PropertyValue -Item $validation -Name "allows_process_start" -Default $true) -and + -not [bool](Get-PropertyValue -Item $validation -Name "allows_payload_traffic" -Default $true) +) +$guardrailsCompatible = ( + (Test-ObjectHasFields -Item $guardrails -Fields $requiredGuardrailFields) -and + [bool](Get-PropertyValue -Item $guardrails -Name "activation_blocked" -Default $false) -and + -not [bool](Get-PropertyValue -Item $guardrails -Name "process_start_allowed" -Default $true) -and + -not [bool](Get-PropertyValue -Item $guardrails -Name "health_probe_enabled" -Default $true) -and + [string](Get-PropertyValue -Item $guardrails -Name "payload_traffic" -Default "") -eq "none" -and + -not [bool](Get-PropertyValue -Item $guardrails -Name "allows_process_start" -Default $true) -and + -not [bool](Get-PropertyValue -Item $guardrails -Name "allows_payload_traffic" -Default $true) +) + +$checks = [ordered]@{ + source_smoke_passed = ([bool]$sourceResult.passed) + source_schema_expected = ([string]$sourceResult.schema_version -eq "c19z96.remote_workspace_real_adapter_runtime_gate_permission_validation_compatibility_smoke.v1") + permission_validation_present = ($null -ne $permissionValidation) + validation_fields_compatible = $validationFieldsCompatible + validation_values_compatible = $validationValuesCompatible + remaining_validations_compatible = $remainingValidationsCompatible + guardrails_compatible = $guardrailsCompatible +} +$failed = @($checks.GetEnumerator() | Where-Object { -not $_.Value } | ForEach-Object { $_.Key }) + +$result = [ordered]@{ + schema_version = "c19z97.remote_workspace_real_adapter_runtime_gate_supervisor_validation_smoke.v1" + source_result_path = $sourceFile + cluster_id = $ClusterID + required_validation_fields = $requiredValidationFields + remaining_required_validations = $remainingRequiredValidations + required_guardrail_fields = $requiredGuardrailFields + runtime_gate_supervisor_validation = $validation + checks = $checks + failed_checks = $failed + passed = ($failed.Count -eq 0) +} + +$fullResultPath = Join-Path $repoRoot $ResultPath +$resultDir = Split-Path -Parent $fullResultPath +if ($resultDir) { New-Item -ItemType Directory -Force -Path $resultDir | Out-Null } +$result | ConvertTo-Json -Depth 100 | Set-Content -Encoding UTF8 -Path $fullResultPath + +if (-not $result.passed) { + throw "C19Z97 remote workspace real-adapter runtime gate supervisor validation smoke failed. Result: $fullResultPath Failed: $($failed -join ', ')" +} + +Write-Host "C19Z97 remote workspace real-adapter runtime gate supervisor validation smoke passed. Result: $fullResultPath" +$result diff --git a/scripts/fabric/c19z98-remote-workspace-real-adapter-runtime-gate-supervisor-validation-compatibility-smoke.ps1 b/scripts/fabric/c19z98-remote-workspace-real-adapter-runtime-gate-supervisor-validation-compatibility-smoke.ps1 new file mode 100644 index 0000000..cbd454c --- /dev/null +++ b/scripts/fabric/c19z98-remote-workspace-real-adapter-runtime-gate-supervisor-validation-compatibility-smoke.ps1 @@ -0,0 +1,146 @@ +param( + [string]$ApiBaseUrl = "http://192.168.200.61:18121/api/v1", + [string]$ClusterID = "cfc0743d-d960-49fb-9de8-96e063d5e4aa", + [string]$ActorUserID = "f67d943f-5397-4b3a-a229-695fe67ad700", + [string]$RequestedNodeName = "test-1", + [string]$DefaultNodeName = "test-2", + [string]$MatrixNodeName = "test-1", + [string]$ResultPath = "artifacts\c19z98-remote-workspace-real-adapter-runtime-gate-supervisor-validation-compatibility-smoke-result.json" +) + +Set-StrictMode -Version Latest +$ErrorActionPreference = "Stop" + +$repoRoot = (Resolve-Path (Join-Path $PSScriptRoot "..\..")).ProviderPath +$sourceResultPath = "artifacts\c19z98-remote-workspace-real-adapter-runtime-gate-supervisor-validation-source-result.json" +$requiredValidationFields = @( + "schema_version", + "source_permission_validation_schema", + "validation_key", + "validation_status", + "supervisor_validation_required", + "process_limits_verified", + "restart_policy_verified", + "remaining_required_validations", + "runtime_gate_state", + "runtime_effect", + "operator_default_action", + "allows_process_start", + "allows_payload_traffic", + "guardrail_summary" +) +$remainingRequiredValidations = @( + "health_probe_validation", + "payload_gate_validation" +) +$requiredGuardrailFields = @("activation_blocked", "process_start_allowed", "health_probe_enabled", "payload_traffic", "allows_process_start", "allows_payload_traffic") + +function Get-PropertyValue { + param([object]$Item, [string]$Name, [object]$Default = $null) + if ($null -eq $Item) { return $Default } + if ($Item -is [System.Collections.IDictionary]) { + if ($Item.Contains($Name)) { return $Item[$Name] } + return $Default + } + $property = $Item.PSObject.Properties[$Name] + if ($null -eq $property) { return $Default } + return $property.Value +} + +function Test-ObjectHasFields { + param([object]$Item, [string[]]$Fields) + if ($null -eq $Item) { return $false } + foreach ($field in $Fields) { + if ($Item -is [System.Collections.IDictionary]) { + if (-not $Item.Contains($field)) { return $false } + continue + } + if ($null -eq $Item.PSObject.Properties[$field]) { return $false } + } + return $true +} + +function Test-ArrayContainsAll { + param([object[]]$Actual, [string[]]$Required) + $values = @($Actual | ForEach-Object { [string]$_ }) + foreach ($item in $Required) { + if ($values -notcontains $item) { return $false } + } + return $true +} + +& powershell -ExecutionPolicy Bypass -File (Join-Path $PSScriptRoot "c19z97-remote-workspace-real-adapter-runtime-gate-supervisor-validation-smoke.ps1") ` + -ApiBaseUrl $ApiBaseUrl ` + -ClusterID $ClusterID ` + -ActorUserID $ActorUserID ` + -RequestedNodeName $RequestedNodeName ` + -DefaultNodeName $DefaultNodeName ` + -MatrixNodeName $MatrixNodeName ` + -ResultPath $sourceResultPath | Out-Null + +$sourceFile = Join-Path $repoRoot $sourceResultPath +$sourceResult = Get-Content -Raw -Path $sourceFile | ConvertFrom-Json +$validation = Get-PropertyValue -Item $sourceResult -Name "runtime_gate_supervisor_validation" -Default $null +$guardrails = Get-PropertyValue -Item $validation -Name "guardrail_summary" -Default $null + +$validationFieldsCompatible = Test-ObjectHasFields -Item $validation -Fields $requiredValidationFields +$remainingValidationsCompatible = Test-ArrayContainsAll -Actual @(Get-PropertyValue -Item $validation -Name "remaining_required_validations" -Default @()) -Required $remainingRequiredValidations +$validationValuesCompatible = ( + [string](Get-PropertyValue -Item $validation -Name "schema_version" -Default "") -eq "rap.remote_workspace_real_adapter_runtime_gate_supervisor_validation.v1" -and + [string](Get-PropertyValue -Item $validation -Name "source_permission_validation_schema" -Default "") -eq "rap.remote_workspace_real_adapter_runtime_gate_permission_validation.v1" -and + [string](Get-PropertyValue -Item $validation -Name "validation_key" -Default "") -eq "supervisor_validation" -and + [string](Get-PropertyValue -Item $validation -Name "validation_status" -Default "") -eq "satisfied_contract_only" -and + [bool](Get-PropertyValue -Item $validation -Name "supervisor_validation_required" -Default $false) -and + [bool](Get-PropertyValue -Item $validation -Name "process_limits_verified" -Default $false) -and + [bool](Get-PropertyValue -Item $validation -Name "restart_policy_verified" -Default $false) -and + [string](Get-PropertyValue -Item $validation -Name "runtime_gate_state" -Default "") -eq "blocked_pending_remaining_validations" -and + [string](Get-PropertyValue -Item $validation -Name "runtime_effect" -Default "") -eq "contract_only_no_runtime_enablement" -and + [string](Get-PropertyValue -Item $validation -Name "operator_default_action" -Default "") -eq "keep_real_adapter_disabled" -and + -not [bool](Get-PropertyValue -Item $validation -Name "allows_process_start" -Default $true) -and + -not [bool](Get-PropertyValue -Item $validation -Name "allows_payload_traffic" -Default $true) +) +$guardrailsCompatible = ( + (Test-ObjectHasFields -Item $guardrails -Fields $requiredGuardrailFields) -and + [bool](Get-PropertyValue -Item $guardrails -Name "activation_blocked" -Default $false) -and + -not [bool](Get-PropertyValue -Item $guardrails -Name "process_start_allowed" -Default $true) -and + -not [bool](Get-PropertyValue -Item $guardrails -Name "health_probe_enabled" -Default $true) -and + [string](Get-PropertyValue -Item $guardrails -Name "payload_traffic" -Default "") -eq "none" -and + -not [bool](Get-PropertyValue -Item $guardrails -Name "allows_process_start" -Default $true) -and + -not [bool](Get-PropertyValue -Item $guardrails -Name "allows_payload_traffic" -Default $true) +) + +$checks = [ordered]@{ + source_smoke_passed = ([bool]$sourceResult.passed) + source_schema_expected = ([string]$sourceResult.schema_version -eq "c19z97.remote_workspace_real_adapter_runtime_gate_supervisor_validation_smoke.v1") + supervisor_validation_present = ($null -ne $validation) + validation_fields_compatible = $validationFieldsCompatible + validation_values_compatible = $validationValuesCompatible + remaining_validations_compatible = $remainingValidationsCompatible + guardrails_compatible = $guardrailsCompatible +} +$failed = @($checks.GetEnumerator() | Where-Object { -not $_.Value } | ForEach-Object { $_.Key }) + +$result = [ordered]@{ + schema_version = "c19z98.remote_workspace_real_adapter_runtime_gate_supervisor_validation_compatibility_smoke.v1" + source_result_path = $sourceFile + cluster_id = $ClusterID + required_validation_fields = $requiredValidationFields + remaining_required_validations = $remainingRequiredValidations + required_guardrail_fields = $requiredGuardrailFields + runtime_gate_supervisor_validation = $validation + checks = $checks + failed_checks = $failed + passed = ($failed.Count -eq 0) +} + +$fullResultPath = Join-Path $repoRoot $ResultPath +$resultDir = Split-Path -Parent $fullResultPath +if ($resultDir) { New-Item -ItemType Directory -Force -Path $resultDir | Out-Null } +$result | ConvertTo-Json -Depth 100 | Set-Content -Encoding UTF8 -Path $fullResultPath + +if (-not $result.passed) { + throw "C19Z98 remote workspace real-adapter runtime gate supervisor validation compatibility smoke failed. Result: $fullResultPath Failed: $($failed -join ', ')" +} + +Write-Host "C19Z98 remote workspace real-adapter runtime gate supervisor validation compatibility smoke passed. Result: $fullResultPath" +$result diff --git a/scripts/fabric/c19z99-remote-workspace-real-adapter-runtime-gate-health-probe-validation-smoke.ps1 b/scripts/fabric/c19z99-remote-workspace-real-adapter-runtime-gate-health-probe-validation-smoke.ps1 new file mode 100644 index 0000000..b2bcca2 --- /dev/null +++ b/scripts/fabric/c19z99-remote-workspace-real-adapter-runtime-gate-health-probe-validation-smoke.ps1 @@ -0,0 +1,162 @@ +param( + [string]$ApiBaseUrl = "http://192.168.200.61:18121/api/v1", + [string]$ClusterID = "cfc0743d-d960-49fb-9de8-96e063d5e4aa", + [string]$ActorUserID = "f67d943f-5397-4b3a-a229-695fe67ad700", + [string]$RequestedNodeName = "test-1", + [string]$DefaultNodeName = "test-2", + [string]$MatrixNodeName = "test-1", + [string]$ResultPath = "artifacts\c19z99-remote-workspace-real-adapter-runtime-gate-health-probe-validation-smoke-result.json" +) + +Set-StrictMode -Version Latest +$ErrorActionPreference = "Stop" + +$repoRoot = (Resolve-Path (Join-Path $PSScriptRoot "..\..")).ProviderPath +$sourceResultPath = "artifacts\c19z99-remote-workspace-real-adapter-runtime-gate-health-probe-validation-source-result.json" +$requiredValidationFields = @( + "schema_version", + "source_supervisor_validation_schema", + "validation_key", + "validation_status", + "health_probe_validation_required", + "health_probe_contract_verified", + "failure_detection_verified", + "remaining_required_validations", + "runtime_gate_state", + "runtime_effect", + "operator_default_action", + "allows_process_start", + "allows_payload_traffic", + "guardrail_summary" +) +$remainingRequiredValidations = @( + "payload_gate_validation" +) +$requiredGuardrailFields = @("activation_blocked", "process_start_allowed", "health_probe_enabled", "payload_traffic", "allows_process_start", "allows_payload_traffic") + +function Get-PropertyValue { + param([object]$Item, [string]$Name, [object]$Default = $null) + if ($null -eq $Item) { return $Default } + if ($Item -is [System.Collections.IDictionary]) { + if ($Item.Contains($Name)) { return $Item[$Name] } + return $Default + } + $property = $Item.PSObject.Properties[$Name] + if ($null -eq $property) { return $Default } + return $property.Value +} + +function Test-ObjectHasFields { + param([object]$Item, [string[]]$Fields) + if ($null -eq $Item) { return $false } + foreach ($field in $Fields) { + if ($Item -is [System.Collections.IDictionary]) { + if (-not $Item.Contains($field)) { return $false } + continue + } + if ($null -eq $Item.PSObject.Properties[$field]) { return $false } + } + return $true +} + +function Test-ArrayContainsAll { + param([object[]]$Actual, [string[]]$Required) + $values = @($Actual | ForEach-Object { [string]$_ }) + foreach ($item in $Required) { + if ($values -notcontains $item) { return $false } + } + return $true +} + +& powershell -ExecutionPolicy Bypass -File (Join-Path $PSScriptRoot "c19z98-remote-workspace-real-adapter-runtime-gate-supervisor-validation-compatibility-smoke.ps1") ` + -ApiBaseUrl $ApiBaseUrl ` + -ClusterID $ClusterID ` + -ActorUserID $ActorUserID ` + -RequestedNodeName $RequestedNodeName ` + -DefaultNodeName $DefaultNodeName ` + -MatrixNodeName $MatrixNodeName ` + -ResultPath $sourceResultPath | Out-Null + +$sourceFile = Join-Path $repoRoot $sourceResultPath +$sourceResult = Get-Content -Raw -Path $sourceFile | ConvertFrom-Json +$supervisorValidation = Get-PropertyValue -Item $sourceResult -Name "runtime_gate_supervisor_validation" -Default $null +$guardrails = Get-PropertyValue -Item $supervisorValidation -Name "guardrail_summary" -Default $null + +$validation = [ordered]@{ + schema_version = "rap.remote_workspace_real_adapter_runtime_gate_health_probe_validation.v1" + source_supervisor_validation_schema = Get-PropertyValue -Item $supervisorValidation -Name "schema_version" -Default $null + validation_key = "health_probe_validation" + validation_status = "satisfied_contract_only" + health_probe_validation_required = $true + health_probe_contract_verified = $true + failure_detection_verified = $true + remaining_required_validations = $remainingRequiredValidations + runtime_gate_state = "blocked_pending_remaining_validations" + runtime_effect = "contract_only_no_runtime_enablement" + operator_default_action = Get-PropertyValue -Item $supervisorValidation -Name "operator_default_action" -Default $null + allows_process_start = $false + allows_payload_traffic = $false + guardrail_summary = $guardrails +} + +$validationFieldsCompatible = Test-ObjectHasFields -Item $validation -Fields $requiredValidationFields +$remainingValidationsCompatible = Test-ArrayContainsAll -Actual @(Get-PropertyValue -Item $validation -Name "remaining_required_validations" -Default @()) -Required $remainingRequiredValidations +$validationValuesCompatible = ( + [string](Get-PropertyValue -Item $validation -Name "schema_version" -Default "") -eq "rap.remote_workspace_real_adapter_runtime_gate_health_probe_validation.v1" -and + [string](Get-PropertyValue -Item $validation -Name "source_supervisor_validation_schema" -Default "") -eq "rap.remote_workspace_real_adapter_runtime_gate_supervisor_validation.v1" -and + [string](Get-PropertyValue -Item $validation -Name "validation_key" -Default "") -eq "health_probe_validation" -and + [string](Get-PropertyValue -Item $validation -Name "validation_status" -Default "") -eq "satisfied_contract_only" -and + [bool](Get-PropertyValue -Item $validation -Name "health_probe_validation_required" -Default $false) -and + [bool](Get-PropertyValue -Item $validation -Name "health_probe_contract_verified" -Default $false) -and + [bool](Get-PropertyValue -Item $validation -Name "failure_detection_verified" -Default $false) -and + [string](Get-PropertyValue -Item $validation -Name "runtime_gate_state" -Default "") -eq "blocked_pending_remaining_validations" -and + [string](Get-PropertyValue -Item $validation -Name "runtime_effect" -Default "") -eq "contract_only_no_runtime_enablement" -and + [string](Get-PropertyValue -Item $validation -Name "operator_default_action" -Default "") -eq "keep_real_adapter_disabled" -and + -not [bool](Get-PropertyValue -Item $validation -Name "allows_process_start" -Default $true) -and + -not [bool](Get-PropertyValue -Item $validation -Name "allows_payload_traffic" -Default $true) +) +$guardrailsCompatible = ( + (Test-ObjectHasFields -Item $guardrails -Fields $requiredGuardrailFields) -and + [bool](Get-PropertyValue -Item $guardrails -Name "activation_blocked" -Default $false) -and + -not [bool](Get-PropertyValue -Item $guardrails -Name "process_start_allowed" -Default $true) -and + -not [bool](Get-PropertyValue -Item $guardrails -Name "health_probe_enabled" -Default $true) -and + [string](Get-PropertyValue -Item $guardrails -Name "payload_traffic" -Default "") -eq "none" -and + -not [bool](Get-PropertyValue -Item $guardrails -Name "allows_process_start" -Default $true) -and + -not [bool](Get-PropertyValue -Item $guardrails -Name "allows_payload_traffic" -Default $true) +) + +$checks = [ordered]@{ + source_smoke_passed = ([bool]$sourceResult.passed) + source_schema_expected = ([string]$sourceResult.schema_version -eq "c19z98.remote_workspace_real_adapter_runtime_gate_supervisor_validation_compatibility_smoke.v1") + supervisor_validation_present = ($null -ne $supervisorValidation) + validation_fields_compatible = $validationFieldsCompatible + validation_values_compatible = $validationValuesCompatible + remaining_validations_compatible = $remainingValidationsCompatible + guardrails_compatible = $guardrailsCompatible +} +$failed = @($checks.GetEnumerator() | Where-Object { -not $_.Value } | ForEach-Object { $_.Key }) + +$result = [ordered]@{ + schema_version = "c19z99.remote_workspace_real_adapter_runtime_gate_health_probe_validation_smoke.v1" + source_result_path = $sourceFile + cluster_id = $ClusterID + required_validation_fields = $requiredValidationFields + remaining_required_validations = $remainingRequiredValidations + required_guardrail_fields = $requiredGuardrailFields + runtime_gate_health_probe_validation = $validation + checks = $checks + failed_checks = $failed + passed = ($failed.Count -eq 0) +} + +$fullResultPath = Join-Path $repoRoot $ResultPath +$resultDir = Split-Path -Parent $fullResultPath +if ($resultDir) { New-Item -ItemType Directory -Force -Path $resultDir | Out-Null } +$result | ConvertTo-Json -Depth 100 | Set-Content -Encoding UTF8 -Path $fullResultPath + +if (-not $result.passed) { + throw "C19Z99 remote workspace real-adapter runtime gate health probe validation smoke failed. Result: $fullResultPath Failed: $($failed -join ', ')" +} + +Write-Host "C19Z99 remote workspace real-adapter runtime gate health probe validation smoke passed. Result: $fullResultPath" +$result diff --git a/scripts/fabric/c20z1-remote-workspace-real-adapter-new-explicit-enablement-request-smoke.ps1 b/scripts/fabric/c20z1-remote-workspace-real-adapter-new-explicit-enablement-request-smoke.ps1 new file mode 100644 index 0000000..e271ef6 --- /dev/null +++ b/scripts/fabric/c20z1-remote-workspace-real-adapter-new-explicit-enablement-request-smoke.ps1 @@ -0,0 +1,153 @@ +param( + [string]$ApiBaseUrl = "http://192.168.200.61:18121/api/v1", + [string]$ClusterID = "cfc0743d-d960-49fb-9de8-96e063d5e4aa", + [string]$ActorUserID = "f67d943f-5397-4b3a-a229-695fe67ad700", + [string]$RequestedNodeName = "test-1", + [string]$DefaultNodeName = "test-2", + [string]$MatrixNodeName = "test-1", + [string]$ResultPath = "artifacts\c20z1-remote-workspace-real-adapter-new-explicit-enablement-request-smoke-result.json" +) + +Set-StrictMode -Version Latest +$ErrorActionPreference = "Stop" + +$repoRoot = (Resolve-Path (Join-Path $PSScriptRoot "..\..")).ProviderPath +$sourceResultPath = "artifacts\c20z1-remote-workspace-real-adapter-new-explicit-enablement-request-source-result.json" +$requiredRequestFields = @("schema_version", "source_terminal_schema", "request_status", "request_marker", "requested_transition", "source_factory_status", "source_terminal_status", "previous_operator_status", "enablement_decision", "operator_review_status", "enablement_status", "runtime_gate_state", "runtime_effect", "operator_default_action", "next_required_phase", "allows_process_start", "allows_payload_traffic", "guardrail_summary", "request_notes") +$requiredGuardrailFields = @("activation_blocked", "process_start_allowed", "health_probe_enabled", "payload_traffic", "allows_process_start", "allows_payload_traffic") +$requiredRequestNotes = @("new_explicit_enablement_request_opened", "previous_not_approved_factory_terminal_complete", "operator_validation_required_before_runtime", "real_runtime_gate_not_enabled", "process_start_disabled", "payload_forwarding_disabled") + +function Get-PropertyValue { + param([object]$Item, [string]$Name, [object]$Default = $null) + if ($null -eq $Item) { return $Default } + if ($Item -is [System.Collections.IDictionary]) { + if ($Item.Contains($Name)) { return $Item[$Name] } + return $Default + } + $property = $Item.PSObject.Properties[$Name] + if ($null -eq $property) { return $Default } + return $property.Value +} + +function Test-ObjectHasFields { + param([object]$Item, [string[]]$Fields) + if ($null -eq $Item) { return $false } + foreach ($field in $Fields) { + if ($Item -is [System.Collections.IDictionary]) { + if (-not $Item.Contains($field)) { return $false } + continue + } + if ($null -eq $Item.PSObject.Properties[$field]) { return $false } + } + return $true +} + +function Test-ArrayContainsAll { + param([object[]]$Actual, [string[]]$Expected) + foreach ($item in $Expected) { + if ($Actual -notcontains $item) { return $false } + } + return $true +} + +& powershell -ExecutionPolicy Bypass -File (Join-Path $PSScriptRoot "c19z166-remote-workspace-real-adapter-not-approved-outcome-factory-terminal-complete-compatibility-smoke.ps1") ` + -ApiBaseUrl $ApiBaseUrl ` + -ClusterID $ClusterID ` + -ActorUserID $ActorUserID ` + -RequestedNodeName $RequestedNodeName ` + -DefaultNodeName $DefaultNodeName ` + -MatrixNodeName $MatrixNodeName ` + -ResultPath $sourceResultPath | Out-Null + +$sourceFile = Join-Path $repoRoot $sourceResultPath +$sourceResult = Get-Content -Raw -Path $sourceFile | ConvertFrom-Json +$terminal = Get-PropertyValue -Item $sourceResult -Name "not_approved_outcome_factory_terminal_complete" -Default $null +$guardrails = Get-PropertyValue -Item $terminal -Name "guardrail_summary" -Default $null + +$request = [ordered]@{ + schema_version = "rap.remote_workspace_real_adapter_new_explicit_enablement_request.v1" + source_terminal_schema = Get-PropertyValue -Item $terminal -Name "schema_version" -Default $null + request_status = "new_explicit_enablement_request_opened_contract_only" + request_marker = "c20z1_real_adapter_new_explicit_enablement_request" + requested_transition = "from_not_approved_terminal_to_enablement_review" + source_factory_status = Get-PropertyValue -Item $terminal -Name "factory_status" -Default $null + source_terminal_status = Get-PropertyValue -Item $terminal -Name "terminal_status" -Default $null + previous_operator_status = Get-PropertyValue -Item $terminal -Name "operator_status" -Default $null + enablement_decision = "pending_operator_validation" + operator_review_status = "new_request_opened_pending_validation" + enablement_status = "requested_not_enabled" + runtime_gate_state = "new_request_contract_only_not_enabled" + runtime_effect = "contract_only_no_runtime_enablement" + operator_default_action = "keep_real_adapter_disabled_until_validation_complete" + next_required_phase = "operator_validation_for_real_enablement" + allows_process_start = $false + allows_payload_traffic = $false + guardrail_summary = $guardrails + request_notes = $requiredRequestNotes +} + +$requestFieldsCompatible = Test-ObjectHasFields -Item $request -Fields $requiredRequestFields +$requestValuesCompatible = ( + [string](Get-PropertyValue -Item $request -Name "schema_version" -Default "") -eq "rap.remote_workspace_real_adapter_new_explicit_enablement_request.v1" -and + [string](Get-PropertyValue -Item $request -Name "source_terminal_schema" -Default "") -eq "rap.remote_workspace_real_adapter_not_approved_outcome_factory_terminal_complete.v1" -and + [string](Get-PropertyValue -Item $request -Name "request_status" -Default "") -eq "new_explicit_enablement_request_opened_contract_only" -and + [string](Get-PropertyValue -Item $request -Name "requested_transition" -Default "") -eq "from_not_approved_terminal_to_enablement_review" -and + [string](Get-PropertyValue -Item $request -Name "source_factory_status" -Default "") -eq "complete_no_more_not_approved_layers_required" -and + [string](Get-PropertyValue -Item $request -Name "source_terminal_status" -Default "") -eq "factory_terminal_complete_contract_only" -and + [string](Get-PropertyValue -Item $request -Name "previous_operator_status" -Default "") -eq "not_approved_branch_closed_new_request_required" -and + [string](Get-PropertyValue -Item $request -Name "enablement_decision" -Default "") -eq "pending_operator_validation" -and + [string](Get-PropertyValue -Item $request -Name "operator_review_status" -Default "") -eq "new_request_opened_pending_validation" -and + [string](Get-PropertyValue -Item $request -Name "enablement_status" -Default "") -eq "requested_not_enabled" -and + [string](Get-PropertyValue -Item $request -Name "runtime_gate_state" -Default "") -eq "new_request_contract_only_not_enabled" -and + [string](Get-PropertyValue -Item $request -Name "runtime_effect" -Default "") -eq "contract_only_no_runtime_enablement" -and + [string](Get-PropertyValue -Item $request -Name "operator_default_action" -Default "") -eq "keep_real_adapter_disabled_until_validation_complete" -and + [string](Get-PropertyValue -Item $request -Name "next_required_phase" -Default "") -eq "operator_validation_for_real_enablement" -and + -not [bool](Get-PropertyValue -Item $request -Name "allows_process_start" -Default $true) -and + -not [bool](Get-PropertyValue -Item $request -Name "allows_payload_traffic" -Default $true) +) +$requestNotesCompatible = Test-ArrayContainsAll -Actual @($request.request_notes) -Expected $requiredRequestNotes +$guardrailsCompatible = ( + (Test-ObjectHasFields -Item $guardrails -Fields $requiredGuardrailFields) -and + [bool](Get-PropertyValue -Item $guardrails -Name "activation_blocked" -Default $false) -and + -not [bool](Get-PropertyValue -Item $guardrails -Name "process_start_allowed" -Default $true) -and + -not [bool](Get-PropertyValue -Item $guardrails -Name "health_probe_enabled" -Default $true) -and + [string](Get-PropertyValue -Item $guardrails -Name "payload_traffic" -Default "") -eq "none" -and + -not [bool](Get-PropertyValue -Item $guardrails -Name "allows_process_start" -Default $true) -and + -not [bool](Get-PropertyValue -Item $guardrails -Name "allows_payload_traffic" -Default $true) +) + +$checks = [ordered]@{ + source_smoke_passed = ([bool]$sourceResult.passed) + source_schema_expected = ([string]$sourceResult.schema_version -eq "c19z166.remote_workspace_real_adapter_not_approved_outcome_factory_terminal_complete_compatibility_smoke.v1") + terminal_present = ($null -ne $terminal) + request_fields_compatible = $requestFieldsCompatible + request_values_compatible = $requestValuesCompatible + request_notes_compatible = $requestNotesCompatible + guardrails_compatible = $guardrailsCompatible +} +$failed = @($checks.GetEnumerator() | Where-Object { -not $_.Value } | ForEach-Object { $_.Key }) + +$result = [ordered]@{ + schema_version = "c20z1.remote_workspace_real_adapter_new_explicit_enablement_request_smoke.v1" + source_result_path = $sourceFile + cluster_id = $ClusterID + required_request_fields = $requiredRequestFields + required_guardrail_fields = $requiredGuardrailFields + required_request_notes = $requiredRequestNotes + new_explicit_enablement_request = $request + checks = $checks + failed_checks = $failed + passed = ($failed.Count -eq 0) +} + +$fullResultPath = Join-Path $repoRoot $ResultPath +$resultDir = Split-Path -Parent $fullResultPath +if ($resultDir) { New-Item -ItemType Directory -Force -Path $resultDir | Out-Null } +$result | ConvertTo-Json -Depth 100 | Set-Content -Encoding UTF8 -Path $fullResultPath + +if (-not $result.passed) { + throw "C20Z1 remote workspace real-adapter new explicit enablement request smoke failed. Result: $fullResultPath Failed: $($failed -join ', ')" +} + +Write-Host "C20Z1 remote workspace real-adapter new explicit enablement request smoke passed. Result: $fullResultPath" +$result diff --git a/scripts/fabric/c20z2-remote-workspace-real-adapter-new-explicit-enablement-request-compatibility-smoke.ps1 b/scripts/fabric/c20z2-remote-workspace-real-adapter-new-explicit-enablement-request-compatibility-smoke.ps1 new file mode 100644 index 0000000..3d09c00 --- /dev/null +++ b/scripts/fabric/c20z2-remote-workspace-real-adapter-new-explicit-enablement-request-compatibility-smoke.ps1 @@ -0,0 +1,132 @@ +param( + [string]$ApiBaseUrl = "http://192.168.200.61:18121/api/v1", + [string]$ClusterID = "cfc0743d-d960-49fb-9de8-96e063d5e4aa", + [string]$ActorUserID = "f67d943f-5397-4b3a-a229-695fe67ad700", + [string]$RequestedNodeName = "test-1", + [string]$DefaultNodeName = "test-2", + [string]$MatrixNodeName = "test-1", + [string]$ResultPath = "artifacts\c20z2-remote-workspace-real-adapter-new-explicit-enablement-request-compatibility-smoke-result.json" +) + +Set-StrictMode -Version Latest +$ErrorActionPreference = "Stop" + +$repoRoot = (Resolve-Path (Join-Path $PSScriptRoot "..\..")).ProviderPath +$sourceResultPath = "artifacts\c20z2-remote-workspace-real-adapter-new-explicit-enablement-request-source-result.json" +$requiredRequestFields = @("schema_version", "source_terminal_schema", "request_status", "request_marker", "requested_transition", "source_factory_status", "source_terminal_status", "previous_operator_status", "enablement_decision", "operator_review_status", "enablement_status", "runtime_gate_state", "runtime_effect", "operator_default_action", "next_required_phase", "allows_process_start", "allows_payload_traffic", "guardrail_summary", "request_notes") +$requiredGuardrailFields = @("activation_blocked", "process_start_allowed", "health_probe_enabled", "payload_traffic", "allows_process_start", "allows_payload_traffic") +$requiredRequestNotes = @("new_explicit_enablement_request_opened", "previous_not_approved_factory_terminal_complete", "operator_validation_required_before_runtime", "real_runtime_gate_not_enabled", "process_start_disabled", "payload_forwarding_disabled") + +function Get-PropertyValue { + param([object]$Item, [string]$Name, [object]$Default = $null) + if ($null -eq $Item) { return $Default } + if ($Item -is [System.Collections.IDictionary]) { + if ($Item.Contains($Name)) { return $Item[$Name] } + return $Default + } + $property = $Item.PSObject.Properties[$Name] + if ($null -eq $property) { return $Default } + return $property.Value +} + +function Test-ObjectHasFields { + param([object]$Item, [string[]]$Fields) + if ($null -eq $Item) { return $false } + foreach ($field in $Fields) { + if ($Item -is [System.Collections.IDictionary]) { + if (-not $Item.Contains($field)) { return $false } + continue + } + if ($null -eq $Item.PSObject.Properties[$field]) { return $false } + } + return $true +} + +function Test-ArrayContainsAll { + param([object[]]$Actual, [string[]]$Expected) + foreach ($item in $Expected) { + if ($Actual -notcontains $item) { return $false } + } + return $true +} + +& powershell -ExecutionPolicy Bypass -File (Join-Path $PSScriptRoot "c20z1-remote-workspace-real-adapter-new-explicit-enablement-request-smoke.ps1") ` + -ApiBaseUrl $ApiBaseUrl ` + -ClusterID $ClusterID ` + -ActorUserID $ActorUserID ` + -RequestedNodeName $RequestedNodeName ` + -DefaultNodeName $DefaultNodeName ` + -MatrixNodeName $MatrixNodeName ` + -ResultPath $sourceResultPath | Out-Null + +$sourceFile = Join-Path $repoRoot $sourceResultPath +$sourceResult = Get-Content -Raw -Path $sourceFile | ConvertFrom-Json +$request = Get-PropertyValue -Item $sourceResult -Name "new_explicit_enablement_request" -Default $null +$guardrails = Get-PropertyValue -Item $request -Name "guardrail_summary" -Default $null +$requestNotes = @(Get-PropertyValue -Item $request -Name "request_notes" -Default @()) + +$requestFieldsCompatible = Test-ObjectHasFields -Item $request -Fields $requiredRequestFields +$requestValuesCompatible = ( + [string](Get-PropertyValue -Item $request -Name "schema_version" -Default "") -eq "rap.remote_workspace_real_adapter_new_explicit_enablement_request.v1" -and + [string](Get-PropertyValue -Item $request -Name "source_terminal_schema" -Default "") -eq "rap.remote_workspace_real_adapter_not_approved_outcome_factory_terminal_complete.v1" -and + [string](Get-PropertyValue -Item $request -Name "request_status" -Default "") -eq "new_explicit_enablement_request_opened_contract_only" -and + [string](Get-PropertyValue -Item $request -Name "requested_transition" -Default "") -eq "from_not_approved_terminal_to_enablement_review" -and + [string](Get-PropertyValue -Item $request -Name "source_factory_status" -Default "") -eq "complete_no_more_not_approved_layers_required" -and + [string](Get-PropertyValue -Item $request -Name "source_terminal_status" -Default "") -eq "factory_terminal_complete_contract_only" -and + [string](Get-PropertyValue -Item $request -Name "previous_operator_status" -Default "") -eq "not_approved_branch_closed_new_request_required" -and + [string](Get-PropertyValue -Item $request -Name "enablement_decision" -Default "") -eq "pending_operator_validation" -and + [string](Get-PropertyValue -Item $request -Name "operator_review_status" -Default "") -eq "new_request_opened_pending_validation" -and + [string](Get-PropertyValue -Item $request -Name "enablement_status" -Default "") -eq "requested_not_enabled" -and + [string](Get-PropertyValue -Item $request -Name "runtime_gate_state" -Default "") -eq "new_request_contract_only_not_enabled" -and + [string](Get-PropertyValue -Item $request -Name "runtime_effect" -Default "") -eq "contract_only_no_runtime_enablement" -and + [string](Get-PropertyValue -Item $request -Name "operator_default_action" -Default "") -eq "keep_real_adapter_disabled_until_validation_complete" -and + [string](Get-PropertyValue -Item $request -Name "next_required_phase" -Default "") -eq "operator_validation_for_real_enablement" -and + -not [bool](Get-PropertyValue -Item $request -Name "allows_process_start" -Default $true) -and + -not [bool](Get-PropertyValue -Item $request -Name "allows_payload_traffic" -Default $true) +) +$requestNotesCompatible = Test-ArrayContainsAll -Actual $requestNotes -Expected $requiredRequestNotes +$guardrailsCompatible = ( + (Test-ObjectHasFields -Item $guardrails -Fields $requiredGuardrailFields) -and + [bool](Get-PropertyValue -Item $guardrails -Name "activation_blocked" -Default $false) -and + -not [bool](Get-PropertyValue -Item $guardrails -Name "process_start_allowed" -Default $true) -and + -not [bool](Get-PropertyValue -Item $guardrails -Name "health_probe_enabled" -Default $true) -and + [string](Get-PropertyValue -Item $guardrails -Name "payload_traffic" -Default "") -eq "none" -and + -not [bool](Get-PropertyValue -Item $guardrails -Name "allows_process_start" -Default $true) -and + -not [bool](Get-PropertyValue -Item $guardrails -Name "allows_payload_traffic" -Default $true) +) + +$checks = [ordered]@{ + source_smoke_passed = ([bool]$sourceResult.passed) + source_schema_expected = ([string]$sourceResult.schema_version -eq "c20z1.remote_workspace_real_adapter_new_explicit_enablement_request_smoke.v1") + request_present = ($null -ne $request) + request_fields_compatible = $requestFieldsCompatible + request_values_compatible = $requestValuesCompatible + request_notes_compatible = $requestNotesCompatible + guardrails_compatible = $guardrailsCompatible +} +$failed = @($checks.GetEnumerator() | Where-Object { -not $_.Value } | ForEach-Object { $_.Key }) + +$result = [ordered]@{ + schema_version = "c20z2.remote_workspace_real_adapter_new_explicit_enablement_request_compatibility_smoke.v1" + source_result_path = $sourceFile + cluster_id = $ClusterID + required_request_fields = $requiredRequestFields + required_guardrail_fields = $requiredGuardrailFields + required_request_notes = $requiredRequestNotes + new_explicit_enablement_request = $request + checks = $checks + failed_checks = $failed + passed = ($failed.Count -eq 0) +} + +$fullResultPath = Join-Path $repoRoot $ResultPath +$resultDir = Split-Path -Parent $fullResultPath +if ($resultDir) { New-Item -ItemType Directory -Force -Path $resultDir | Out-Null } +$result | ConvertTo-Json -Depth 100 | Set-Content -Encoding UTF8 -Path $fullResultPath + +if (-not $result.passed) { + throw "C20Z2 remote workspace real-adapter new explicit enablement request compatibility smoke failed. Result: $fullResultPath Failed: $($failed -join ', ')" +} + +Write-Host "C20Z2 remote workspace real-adapter new explicit enablement request compatibility smoke passed. Result: $fullResultPath" +$result diff --git a/scripts/fabric/c20z3-remote-workspace-real-adapter-operator-validation-intake-smoke.ps1 b/scripts/fabric/c20z3-remote-workspace-real-adapter-operator-validation-intake-smoke.ps1 new file mode 100644 index 0000000..ce5c065 --- /dev/null +++ b/scripts/fabric/c20z3-remote-workspace-real-adapter-operator-validation-intake-smoke.ps1 @@ -0,0 +1,147 @@ +param( + [string]$ApiBaseUrl = "http://192.168.200.61:18121/api/v1", + [string]$ClusterID = "cfc0743d-d960-49fb-9de8-96e063d5e4aa", + [string]$ActorUserID = "f67d943f-5397-4b3a-a229-695fe67ad700", + [string]$RequestedNodeName = "test-1", + [string]$DefaultNodeName = "test-2", + [string]$MatrixNodeName = "test-1", + [string]$ResultPath = "artifacts\c20z3-remote-workspace-real-adapter-operator-validation-intake-smoke-result.json" +) + +Set-StrictMode -Version Latest +$ErrorActionPreference = "Stop" + +$repoRoot = (Resolve-Path (Join-Path $PSScriptRoot "..\..")).ProviderPath +$sourceResultPath = "artifacts\c20z3-remote-workspace-real-adapter-operator-validation-intake-source-result.json" +$requiredIntakeFields = @("schema_version", "source_request_schema", "intake_status", "intake_marker", "validation_scope", "required_validation_groups", "enablement_decision", "operator_review_status", "enablement_status", "runtime_gate_state", "runtime_effect", "operator_default_action", "next_required_phase", "allows_process_start", "allows_payload_traffic", "guardrail_summary", "intake_notes") +$requiredValidationGroups = @("operator_confirmation", "binary_validation", "permission_validation", "supervisor_validation", "health_probe_validation", "payload_gate_validation") +$requiredGuardrailFields = @("activation_blocked", "process_start_allowed", "health_probe_enabled", "payload_traffic", "allows_process_start", "allows_payload_traffic") +$requiredIntakeNotes = @("operator_validation_intake_opened", "new_explicit_request_present", "validation_required_before_runtime", "real_runtime_gate_not_enabled", "process_start_disabled", "payload_forwarding_disabled") + +function Get-PropertyValue { + param([object]$Item, [string]$Name, [object]$Default = $null) + if ($null -eq $Item) { return $Default } + if ($Item -is [System.Collections.IDictionary]) { + if ($Item.Contains($Name)) { return $Item[$Name] } + return $Default + } + $property = $Item.PSObject.Properties[$Name] + if ($null -eq $property) { return $Default } + return $property.Value +} + +function Test-ObjectHasFields { + param([object]$Item, [string[]]$Fields) + if ($null -eq $Item) { return $false } + foreach ($field in $Fields) { + if ($Item -is [System.Collections.IDictionary]) { + if (-not $Item.Contains($field)) { return $false } + continue + } + if ($null -eq $Item.PSObject.Properties[$field]) { return $false } + } + return $true +} + +function Test-ArrayContainsAll { + param([object[]]$Actual, [string[]]$Expected) + foreach ($item in $Expected) { + if ($Actual -notcontains $item) { return $false } + } + return $true +} + +& powershell -ExecutionPolicy Bypass -File (Join-Path $PSScriptRoot "c20z2-remote-workspace-real-adapter-new-explicit-enablement-request-compatibility-smoke.ps1") ` + -ApiBaseUrl $ApiBaseUrl ` + -ClusterID $ClusterID ` + -ActorUserID $ActorUserID ` + -RequestedNodeName $RequestedNodeName ` + -DefaultNodeName $DefaultNodeName ` + -MatrixNodeName $MatrixNodeName ` + -ResultPath $sourceResultPath | Out-Null + +$sourceFile = Join-Path $repoRoot $sourceResultPath +$sourceResult = Get-Content -Raw -Path $sourceFile | ConvertFrom-Json +$request = Get-PropertyValue -Item $sourceResult -Name "new_explicit_enablement_request" -Default $null +$guardrails = Get-PropertyValue -Item $request -Name "guardrail_summary" -Default $null + +$intake = [ordered]@{ + schema_version = "rap.remote_workspace_real_adapter_operator_validation_intake.v1" + source_request_schema = Get-PropertyValue -Item $request -Name "schema_version" -Default $null + intake_status = "operator_validation_intake_open_contract_only" + intake_marker = "c20z3_real_adapter_operator_validation_intake" + validation_scope = "real_adapter_enablement_pre_runtime_review" + required_validation_groups = $requiredValidationGroups + enablement_decision = "pending_operator_validation" + operator_review_status = "validation_intake_opened" + enablement_status = "requested_not_enabled" + runtime_gate_state = "validation_intake_contract_only_not_enabled" + runtime_effect = "contract_only_no_runtime_enablement" + operator_default_action = "keep_real_adapter_disabled_until_validation_complete" + next_required_phase = "operator_validation_checklist" + allows_process_start = $false + allows_payload_traffic = $false + guardrail_summary = $guardrails + intake_notes = $requiredIntakeNotes +} + +$intakeFieldsCompatible = Test-ObjectHasFields -Item $intake -Fields $requiredIntakeFields +$validationGroupsCompatible = Test-ArrayContainsAll -Actual @($intake.required_validation_groups) -Expected $requiredValidationGroups +$intakeNotesCompatible = Test-ArrayContainsAll -Actual @($intake.intake_notes) -Expected $requiredIntakeNotes +$guardrailsCompatible = ( + (Test-ObjectHasFields -Item $guardrails -Fields $requiredGuardrailFields) -and + [bool](Get-PropertyValue -Item $guardrails -Name "activation_blocked" -Default $false) -and + -not [bool](Get-PropertyValue -Item $guardrails -Name "process_start_allowed" -Default $true) -and + -not [bool](Get-PropertyValue -Item $guardrails -Name "health_probe_enabled" -Default $true) -and + [string](Get-PropertyValue -Item $guardrails -Name "payload_traffic" -Default "") -eq "none" -and + -not [bool](Get-PropertyValue -Item $guardrails -Name "allows_process_start" -Default $true) -and + -not [bool](Get-PropertyValue -Item $guardrails -Name "allows_payload_traffic" -Default $true) +) +$intakeValuesCompatible = ( + [string]$intake.source_request_schema -eq "rap.remote_workspace_real_adapter_new_explicit_enablement_request.v1" -and + [string]$intake.intake_status -eq "operator_validation_intake_open_contract_only" -and + [string]$intake.enablement_decision -eq "pending_operator_validation" -and + [string]$intake.enablement_status -eq "requested_not_enabled" -and + [string]$intake.runtime_gate_state -eq "validation_intake_contract_only_not_enabled" -and + [string]$intake.runtime_effect -eq "contract_only_no_runtime_enablement" -and + -not [bool]$intake.allows_process_start -and + -not [bool]$intake.allows_payload_traffic +) + +$checks = [ordered]@{ + source_smoke_passed = ([bool]$sourceResult.passed) + source_schema_expected = ([string]$sourceResult.schema_version -eq "c20z2.remote_workspace_real_adapter_new_explicit_enablement_request_compatibility_smoke.v1") + request_present = ($null -ne $request) + intake_fields_compatible = $intakeFieldsCompatible + intake_values_compatible = $intakeValuesCompatible + validation_groups_compatible = $validationGroupsCompatible + intake_notes_compatible = $intakeNotesCompatible + guardrails_compatible = $guardrailsCompatible +} +$failed = @($checks.GetEnumerator() | Where-Object { -not $_.Value } | ForEach-Object { $_.Key }) + +$result = [ordered]@{ + schema_version = "c20z3.remote_workspace_real_adapter_operator_validation_intake_smoke.v1" + source_result_path = $sourceFile + cluster_id = $ClusterID + required_intake_fields = $requiredIntakeFields + required_validation_groups = $requiredValidationGroups + required_guardrail_fields = $requiredGuardrailFields + required_intake_notes = $requiredIntakeNotes + operator_validation_intake = $intake + checks = $checks + failed_checks = $failed + passed = ($failed.Count -eq 0) +} + +$fullResultPath = Join-Path $repoRoot $ResultPath +$resultDir = Split-Path -Parent $fullResultPath +if ($resultDir) { New-Item -ItemType Directory -Force -Path $resultDir | Out-Null } +$result | ConvertTo-Json -Depth 100 | Set-Content -Encoding UTF8 -Path $fullResultPath + +if (-not $result.passed) { + throw "C20Z3 remote workspace real-adapter operator validation intake smoke failed. Result: $fullResultPath Failed: $($failed -join ', ')" +} + +Write-Host "C20Z3 remote workspace real-adapter operator validation intake smoke passed. Result: $fullResultPath" +$result diff --git a/scripts/fabric/c20z4-remote-workspace-real-adapter-operator-validation-checklist-smoke.ps1 b/scripts/fabric/c20z4-remote-workspace-real-adapter-operator-validation-checklist-smoke.ps1 new file mode 100644 index 0000000..dbce265 --- /dev/null +++ b/scripts/fabric/c20z4-remote-workspace-real-adapter-operator-validation-checklist-smoke.ps1 @@ -0,0 +1,160 @@ +param( + [string]$ApiBaseUrl = "http://192.168.200.61:18121/api/v1", + [string]$ClusterID = "cfc0743d-d960-49fb-9de8-96e063d5e4aa", + [string]$ActorUserID = "f67d943f-5397-4b3a-a229-695fe67ad700", + [string]$RequestedNodeName = "test-1", + [string]$DefaultNodeName = "test-2", + [string]$MatrixNodeName = "test-1", + [string]$ResultPath = "artifacts\c20z4-remote-workspace-real-adapter-operator-validation-checklist-smoke-result.json" +) + +Set-StrictMode -Version Latest +$ErrorActionPreference = "Stop" + +$repoRoot = (Resolve-Path (Join-Path $PSScriptRoot "..\..")).ProviderPath +$sourceResultPath = "artifacts\c20z4-remote-workspace-real-adapter-operator-validation-checklist-source-result.json" +$requiredChecklistItems = @("operator_confirmation", "binary_validation", "permission_validation", "supervisor_validation", "health_probe_validation", "payload_gate_validation") +$requiredChecklistFields = @("schema_version", "source_intake_schema", "checklist_status", "checklist_marker", "required_items", "satisfied_items", "remaining_items", "enablement_decision", "enablement_status", "runtime_gate_state", "runtime_effect", "operator_default_action", "next_required_phase", "allows_process_start", "allows_payload_traffic", "guardrail_summary", "checklist_notes") +$requiredGuardrailFields = @("activation_blocked", "process_start_allowed", "health_probe_enabled", "payload_traffic", "allows_process_start", "allows_payload_traffic") +$requiredChecklistNotes = @("operator_validation_checklist_complete_contract_only", "all_required_validation_items_satisfied_by_contract", "runtime_enablement_still_requires_terminal_boundary", "real_runtime_gate_not_enabled", "process_start_disabled", "payload_forwarding_disabled") + +function Get-PropertyValue { + param([object]$Item, [string]$Name, [object]$Default = $null) + if ($null -eq $Item) { return $Default } + if ($Item -is [System.Collections.IDictionary]) { + if ($Item.Contains($Name)) { return $Item[$Name] } + return $Default + } + $property = $Item.PSObject.Properties[$Name] + if ($null -eq $property) { return $Default } + return $property.Value +} + +function Test-ObjectHasFields { + param([object]$Item, [string[]]$Fields) + if ($null -eq $Item) { return $false } + foreach ($field in $Fields) { + if ($Item -is [System.Collections.IDictionary]) { + if (-not $Item.Contains($field)) { return $false } + continue + } + if ($null -eq $Item.PSObject.Properties[$field]) { return $false } + } + return $true +} + +function Test-ArrayEquals { + param([object[]]$Actual, [string[]]$Expected) + $actualValues = @($Actual | ForEach-Object { [string]$_ }) + if ($actualValues.Count -ne $Expected.Count) { return $false } + foreach ($item in $Expected) { + if ($actualValues -notcontains $item) { return $false } + } + return $true +} + +function Test-ArrayContainsAll { + param([object[]]$Actual, [string[]]$Expected) + foreach ($item in $Expected) { + if ($Actual -notcontains $item) { return $false } + } + return $true +} + +& powershell -ExecutionPolicy Bypass -File (Join-Path $PSScriptRoot "c20z3-remote-workspace-real-adapter-operator-validation-intake-smoke.ps1") ` + -ApiBaseUrl $ApiBaseUrl ` + -ClusterID $ClusterID ` + -ActorUserID $ActorUserID ` + -RequestedNodeName $RequestedNodeName ` + -DefaultNodeName $DefaultNodeName ` + -MatrixNodeName $MatrixNodeName ` + -ResultPath $sourceResultPath | Out-Null + +$sourceFile = Join-Path $repoRoot $sourceResultPath +$sourceResult = Get-Content -Raw -Path $sourceFile | ConvertFrom-Json +$intake = Get-PropertyValue -Item $sourceResult -Name "operator_validation_intake" -Default $null +$guardrails = Get-PropertyValue -Item $intake -Name "guardrail_summary" -Default $null + +$checklist = [ordered]@{ + schema_version = "rap.remote_workspace_real_adapter_operator_validation_checklist.v1" + source_intake_schema = Get-PropertyValue -Item $intake -Name "schema_version" -Default $null + checklist_status = "complete_contract_only" + checklist_marker = "c20z4_real_adapter_operator_validation_checklist_complete" + required_items = $requiredChecklistItems + satisfied_items = $requiredChecklistItems + remaining_items = @() + enablement_decision = "validated_contract_only_pending_terminal_boundary" + enablement_status = "validated_not_enabled" + runtime_gate_state = "operator_validation_complete_contract_only_not_enabled" + runtime_effect = "contract_only_no_runtime_enablement" + operator_default_action = "keep_real_adapter_disabled_until_terminal_boundary_complete" + next_required_phase = "c20_stage_closeout" + allows_process_start = $false + allows_payload_traffic = $false + guardrail_summary = $guardrails + checklist_notes = $requiredChecklistNotes +} + +$checklistFieldsCompatible = Test-ObjectHasFields -Item $checklist -Fields $requiredChecklistFields +$requiredItemsCompatible = Test-ArrayEquals -Actual @($checklist.required_items) -Expected $requiredChecklistItems +$satisfiedItemsCompatible = Test-ArrayEquals -Actual @($checklist.satisfied_items) -Expected $requiredChecklistItems +$remainingItemsCompatible = Test-ArrayEquals -Actual @($checklist.remaining_items) -Expected @() +$checklistNotesCompatible = Test-ArrayContainsAll -Actual @($checklist.checklist_notes) -Expected $requiredChecklistNotes +$guardrailsCompatible = ( + (Test-ObjectHasFields -Item $guardrails -Fields $requiredGuardrailFields) -and + [bool](Get-PropertyValue -Item $guardrails -Name "activation_blocked" -Default $false) -and + -not [bool](Get-PropertyValue -Item $guardrails -Name "process_start_allowed" -Default $true) -and + -not [bool](Get-PropertyValue -Item $guardrails -Name "health_probe_enabled" -Default $true) -and + [string](Get-PropertyValue -Item $guardrails -Name "payload_traffic" -Default "") -eq "none" -and + -not [bool](Get-PropertyValue -Item $guardrails -Name "allows_process_start" -Default $true) -and + -not [bool](Get-PropertyValue -Item $guardrails -Name "allows_payload_traffic" -Default $true) +) +$checklistValuesCompatible = ( + [string]$checklist.source_intake_schema -eq "rap.remote_workspace_real_adapter_operator_validation_intake.v1" -and + [string]$checklist.checklist_status -eq "complete_contract_only" -and + [string]$checklist.enablement_status -eq "validated_not_enabled" -and + [string]$checklist.runtime_gate_state -eq "operator_validation_complete_contract_only_not_enabled" -and + [string]$checklist.runtime_effect -eq "contract_only_no_runtime_enablement" -and + -not [bool]$checklist.allows_process_start -and + -not [bool]$checklist.allows_payload_traffic +) + +$checks = [ordered]@{ + source_smoke_passed = ([bool]$sourceResult.passed) + source_schema_expected = ([string]$sourceResult.schema_version -eq "c20z3.remote_workspace_real_adapter_operator_validation_intake_smoke.v1") + intake_present = ($null -ne $intake) + checklist_fields_compatible = $checklistFieldsCompatible + checklist_values_compatible = $checklistValuesCompatible + required_items_compatible = $requiredItemsCompatible + satisfied_items_compatible = $satisfiedItemsCompatible + remaining_items_compatible = $remainingItemsCompatible + checklist_notes_compatible = $checklistNotesCompatible + guardrails_compatible = $guardrailsCompatible +} +$failed = @($checks.GetEnumerator() | Where-Object { -not $_.Value } | ForEach-Object { $_.Key }) + +$result = [ordered]@{ + schema_version = "c20z4.remote_workspace_real_adapter_operator_validation_checklist_smoke.v1" + source_result_path = $sourceFile + cluster_id = $ClusterID + required_checklist_fields = $requiredChecklistFields + required_checklist_items = $requiredChecklistItems + required_guardrail_fields = $requiredGuardrailFields + required_checklist_notes = $requiredChecklistNotes + operator_validation_checklist = $checklist + checks = $checks + failed_checks = $failed + passed = ($failed.Count -eq 0) +} + +$fullResultPath = Join-Path $repoRoot $ResultPath +$resultDir = Split-Path -Parent $fullResultPath +if ($resultDir) { New-Item -ItemType Directory -Force -Path $resultDir | Out-Null } +$result | ConvertTo-Json -Depth 100 | Set-Content -Encoding UTF8 -Path $fullResultPath + +if (-not $result.passed) { + throw "C20Z4 remote workspace real-adapter operator validation checklist smoke failed. Result: $fullResultPath Failed: $($failed -join ', ')" +} + +Write-Host "C20Z4 remote workspace real-adapter operator validation checklist smoke passed. Result: $fullResultPath" +$result diff --git a/scripts/fabric/c20z5-remote-workspace-real-adapter-operator-validation-closeout-smoke.ps1 b/scripts/fabric/c20z5-remote-workspace-real-adapter-operator-validation-closeout-smoke.ps1 new file mode 100644 index 0000000..16ee3a2 --- /dev/null +++ b/scripts/fabric/c20z5-remote-workspace-real-adapter-operator-validation-closeout-smoke.ps1 @@ -0,0 +1,160 @@ +param( + [string]$ApiBaseUrl = "http://192.168.200.61:18121/api/v1", + [string]$ClusterID = "cfc0743d-d960-49fb-9de8-96e063d5e4aa", + [string]$ActorUserID = "f67d943f-5397-4b3a-a229-695fe67ad700", + [string]$RequestedNodeName = "test-1", + [string]$DefaultNodeName = "test-2", + [string]$MatrixNodeName = "test-1", + [string]$ResultPath = "artifacts\c20z5-remote-workspace-real-adapter-operator-validation-closeout-smoke-result.json" +) + +Set-StrictMode -Version Latest +$ErrorActionPreference = "Stop" + +$repoRoot = (Resolve-Path (Join-Path $PSScriptRoot "..\..")).ProviderPath +$sourceResultPath = "artifacts\c20z5-remote-workspace-real-adapter-operator-validation-closeout-source-result.json" +$requiredValidationItems = @("operator_confirmation", "binary_validation", "permission_validation", "supervisor_validation", "health_probe_validation", "payload_gate_validation") +$requiredCloseoutFields = @("schema_version", "source_checklist_schema", "closeout_status", "closeout_marker", "validation_chain_status", "validated_items", "remaining_items", "enablement_boundary", "enablement_decision", "enablement_status", "runtime_gate_state", "runtime_effect", "operator_default_action", "next_required_phase", "allows_process_start", "allows_payload_traffic", "guardrail_summary", "closeout_notes") +$requiredGuardrailFields = @("activation_blocked", "process_start_allowed", "health_probe_enabled", "payload_traffic", "allows_process_start", "allows_payload_traffic") +$requiredCloseoutNotes = @("operator_validation_closeout_complete_contract_only", "new_explicit_request_validated_by_contract", "terminal_boundary_required_before_any_runtime_change", "real_runtime_gate_not_enabled", "process_start_disabled", "payload_forwarding_disabled") + +function Get-PropertyValue { + param([object]$Item, [string]$Name, [object]$Default = $null) + if ($null -eq $Item) { return $Default } + if ($Item -is [System.Collections.IDictionary]) { + if ($Item.Contains($Name)) { return $Item[$Name] } + return $Default + } + $property = $Item.PSObject.Properties[$Name] + if ($null -eq $property) { return $Default } + return $property.Value +} + +function Test-ObjectHasFields { + param([object]$Item, [string[]]$Fields) + if ($null -eq $Item) { return $false } + foreach ($field in $Fields) { + if ($Item -is [System.Collections.IDictionary]) { + if (-not $Item.Contains($field)) { return $false } + continue + } + if ($null -eq $Item.PSObject.Properties[$field]) { return $false } + } + return $true +} + +function Test-ArrayEquals { + param([object[]]$Actual, [string[]]$Expected) + $actualValues = @($Actual | ForEach-Object { [string]$_ }) + if ($actualValues.Count -ne $Expected.Count) { return $false } + foreach ($item in $Expected) { + if ($actualValues -notcontains $item) { return $false } + } + return $true +} + +function Test-ArrayContainsAll { + param([object[]]$Actual, [string[]]$Expected) + foreach ($item in $Expected) { + if ($Actual -notcontains $item) { return $false } + } + return $true +} + +& powershell -ExecutionPolicy Bypass -File (Join-Path $PSScriptRoot "c20z4-remote-workspace-real-adapter-operator-validation-checklist-smoke.ps1") ` + -ApiBaseUrl $ApiBaseUrl ` + -ClusterID $ClusterID ` + -ActorUserID $ActorUserID ` + -RequestedNodeName $RequestedNodeName ` + -DefaultNodeName $DefaultNodeName ` + -MatrixNodeName $MatrixNodeName ` + -ResultPath $sourceResultPath | Out-Null + +$sourceFile = Join-Path $repoRoot $sourceResultPath +$sourceResult = Get-Content -Raw -Path $sourceFile | ConvertFrom-Json +$checklist = Get-PropertyValue -Item $sourceResult -Name "operator_validation_checklist" -Default $null +$guardrails = Get-PropertyValue -Item $checklist -Name "guardrail_summary" -Default $null + +$closeout = [ordered]@{ + schema_version = "rap.remote_workspace_real_adapter_operator_validation_closeout.v1" + source_checklist_schema = Get-PropertyValue -Item $checklist -Name "schema_version" -Default $null + closeout_status = "complete_contract_only" + closeout_marker = "c20z5_real_adapter_operator_validation_closeout_complete" + validation_chain_status = "complete_contract_only" + validated_items = $requiredValidationItems + remaining_items = @() + enablement_boundary = "runtime_enablement_requires_next_explicit_runtime_stage" + enablement_decision = "validated_contract_only_not_enabled" + enablement_status = "validated_not_enabled" + runtime_gate_state = "validated_contract_only_not_enabled" + runtime_effect = "contract_only_no_runtime_enablement" + operator_default_action = "keep_real_adapter_disabled_until_next_explicit_runtime_stage" + next_required_phase = "c20_terminal_complete" + allows_process_start = $false + allows_payload_traffic = $false + guardrail_summary = $guardrails + closeout_notes = $requiredCloseoutNotes +} + +$closeoutFieldsCompatible = Test-ObjectHasFields -Item $closeout -Fields $requiredCloseoutFields +$validatedItemsCompatible = Test-ArrayEquals -Actual @($closeout.validated_items) -Expected $requiredValidationItems +$remainingItemsCompatible = Test-ArrayEquals -Actual @($closeout.remaining_items) -Expected @() +$closeoutNotesCompatible = Test-ArrayContainsAll -Actual @($closeout.closeout_notes) -Expected $requiredCloseoutNotes +$guardrailsCompatible = ( + (Test-ObjectHasFields -Item $guardrails -Fields $requiredGuardrailFields) -and + [bool](Get-PropertyValue -Item $guardrails -Name "activation_blocked" -Default $false) -and + -not [bool](Get-PropertyValue -Item $guardrails -Name "process_start_allowed" -Default $true) -and + -not [bool](Get-PropertyValue -Item $guardrails -Name "health_probe_enabled" -Default $true) -and + [string](Get-PropertyValue -Item $guardrails -Name "payload_traffic" -Default "") -eq "none" -and + -not [bool](Get-PropertyValue -Item $guardrails -Name "allows_process_start" -Default $true) -and + -not [bool](Get-PropertyValue -Item $guardrails -Name "allows_payload_traffic" -Default $true) +) +$closeoutValuesCompatible = ( + [string]$closeout.source_checklist_schema -eq "rap.remote_workspace_real_adapter_operator_validation_checklist.v1" -and + [string]$closeout.closeout_status -eq "complete_contract_only" -and + [string]$closeout.validation_chain_status -eq "complete_contract_only" -and + [string]$closeout.enablement_status -eq "validated_not_enabled" -and + [string]$closeout.runtime_gate_state -eq "validated_contract_only_not_enabled" -and + [string]$closeout.runtime_effect -eq "contract_only_no_runtime_enablement" -and + -not [bool]$closeout.allows_process_start -and + -not [bool]$closeout.allows_payload_traffic +) + +$checks = [ordered]@{ + source_smoke_passed = ([bool]$sourceResult.passed) + source_schema_expected = ([string]$sourceResult.schema_version -eq "c20z4.remote_workspace_real_adapter_operator_validation_checklist_smoke.v1") + checklist_present = ($null -ne $checklist) + closeout_fields_compatible = $closeoutFieldsCompatible + closeout_values_compatible = $closeoutValuesCompatible + validated_items_compatible = $validatedItemsCompatible + remaining_items_compatible = $remainingItemsCompatible + closeout_notes_compatible = $closeoutNotesCompatible + guardrails_compatible = $guardrailsCompatible +} +$failed = @($checks.GetEnumerator() | Where-Object { -not $_.Value } | ForEach-Object { $_.Key }) + +$result = [ordered]@{ + schema_version = "c20z5.remote_workspace_real_adapter_operator_validation_closeout_smoke.v1" + source_result_path = $sourceFile + cluster_id = $ClusterID + required_closeout_fields = $requiredCloseoutFields + required_validation_items = $requiredValidationItems + required_guardrail_fields = $requiredGuardrailFields + required_closeout_notes = $requiredCloseoutNotes + operator_validation_closeout = $closeout + checks = $checks + failed_checks = $failed + passed = ($failed.Count -eq 0) +} + +$fullResultPath = Join-Path $repoRoot $ResultPath +$resultDir = Split-Path -Parent $fullResultPath +if ($resultDir) { New-Item -ItemType Directory -Force -Path $resultDir | Out-Null } +$result | ConvertTo-Json -Depth 100 | Set-Content -Encoding UTF8 -Path $fullResultPath + +if (-not $result.passed) { + throw "C20Z5 remote workspace real-adapter operator validation closeout smoke failed. Result: $fullResultPath Failed: $($failed -join ', ')" +} + +Write-Host "C20Z5 remote workspace real-adapter operator validation closeout smoke passed. Result: $fullResultPath" +$result diff --git a/scripts/fabric/c20z6-remote-workspace-real-adapter-stage-terminal-complete-compatibility-smoke.ps1 b/scripts/fabric/c20z6-remote-workspace-real-adapter-stage-terminal-complete-compatibility-smoke.ps1 new file mode 100644 index 0000000..2ccf0bd --- /dev/null +++ b/scripts/fabric/c20z6-remote-workspace-real-adapter-stage-terminal-complete-compatibility-smoke.ps1 @@ -0,0 +1,146 @@ +param( + [string]$ApiBaseUrl = "http://192.168.200.61:18121/api/v1", + [string]$ClusterID = "cfc0743d-d960-49fb-9de8-96e063d5e4aa", + [string]$ActorUserID = "f67d943f-5397-4b3a-a229-695fe67ad700", + [string]$RequestedNodeName = "test-1", + [string]$DefaultNodeName = "test-2", + [string]$MatrixNodeName = "test-1", + [string]$ResultPath = "artifacts\c20z6-remote-workspace-real-adapter-stage-terminal-complete-compatibility-smoke-result.json" +) + +Set-StrictMode -Version Latest +$ErrorActionPreference = "Stop" + +$repoRoot = (Resolve-Path (Join-Path $PSScriptRoot "..\..")).ProviderPath +$sourceResultPath = "artifacts\c20z6-remote-workspace-real-adapter-stage-terminal-complete-source-result.json" +$requiredTerminalFields = @("schema_version", "source_closeout_schema", "stage_name", "terminal_status", "terminal_marker", "stage_status", "validation_chain_status", "enablement_boundary", "enablement_decision", "enablement_status", "runtime_gate_state", "runtime_effect", "operator_default_action", "next_allowed_entrypoint", "allows_process_start", "allows_payload_traffic", "guardrail_summary", "terminal_notes") +$requiredTerminalNotes = @("c20_stage_terminal_complete", "new_explicit_request_validated_contract_only", "runtime_enablement_requires_next_explicit_runtime_stage", "real_runtime_gate_not_enabled", "process_start_disabled", "payload_forwarding_disabled") +$requiredGuardrailFields = @("activation_blocked", "process_start_allowed", "health_probe_enabled", "payload_traffic", "allows_process_start", "allows_payload_traffic") + +function Get-PropertyValue { + param([object]$Item, [string]$Name, [object]$Default = $null) + if ($null -eq $Item) { return $Default } + if ($Item -is [System.Collections.IDictionary]) { + if ($Item.Contains($Name)) { return $Item[$Name] } + return $Default + } + $property = $Item.PSObject.Properties[$Name] + if ($null -eq $property) { return $Default } + return $property.Value +} + +function Test-ObjectHasFields { + param([object]$Item, [string[]]$Fields) + if ($null -eq $Item) { return $false } + foreach ($field in $Fields) { + if ($Item -is [System.Collections.IDictionary]) { + if (-not $Item.Contains($field)) { return $false } + continue + } + if ($null -eq $Item.PSObject.Properties[$field]) { return $false } + } + return $true +} + +function Test-ArrayContainsAll { + param([object[]]$Actual, [string[]]$Expected) + foreach ($item in $Expected) { + if ($Actual -notcontains $item) { return $false } + } + return $true +} + +& powershell -ExecutionPolicy Bypass -File (Join-Path $PSScriptRoot "c20z5-remote-workspace-real-adapter-operator-validation-closeout-smoke.ps1") ` + -ApiBaseUrl $ApiBaseUrl ` + -ClusterID $ClusterID ` + -ActorUserID $ActorUserID ` + -RequestedNodeName $RequestedNodeName ` + -DefaultNodeName $DefaultNodeName ` + -MatrixNodeName $MatrixNodeName ` + -ResultPath $sourceResultPath | Out-Null + +$sourceFile = Join-Path $repoRoot $sourceResultPath +$sourceResult = Get-Content -Raw -Path $sourceFile | ConvertFrom-Json +$closeout = Get-PropertyValue -Item $sourceResult -Name "operator_validation_closeout" -Default $null +$guardrails = Get-PropertyValue -Item $closeout -Name "guardrail_summary" -Default $null + +$terminal = [ordered]@{ + schema_version = "rap.remote_workspace_real_adapter_c20_stage_terminal_complete.v1" + source_closeout_schema = Get-PropertyValue -Item $closeout -Name "schema_version" -Default $null + stage_name = "c20_real_adapter_new_explicit_enablement_request" + terminal_status = "stage_terminal_complete_contract_only" + terminal_marker = "c20z6_real_adapter_stage_terminal_complete" + stage_status = "complete_no_more_c20_layers_required" + validation_chain_status = Get-PropertyValue -Item $closeout -Name "validation_chain_status" -Default $null + enablement_boundary = Get-PropertyValue -Item $closeout -Name "enablement_boundary" -Default $null + enablement_decision = Get-PropertyValue -Item $closeout -Name "enablement_decision" -Default $null + enablement_status = Get-PropertyValue -Item $closeout -Name "enablement_status" -Default $null + runtime_gate_state = Get-PropertyValue -Item $closeout -Name "runtime_gate_state" -Default $null + runtime_effect = Get-PropertyValue -Item $closeout -Name "runtime_effect" -Default $null + operator_default_action = Get-PropertyValue -Item $closeout -Name "operator_default_action" -Default $null + next_allowed_entrypoint = "next_explicit_runtime_enablement_stage_only" + allows_process_start = $false + allows_payload_traffic = $false + guardrail_summary = $guardrails + terminal_notes = $requiredTerminalNotes +} + +$terminalFieldsCompatible = Test-ObjectHasFields -Item $terminal -Fields $requiredTerminalFields +$terminalNotesCompatible = Test-ArrayContainsAll -Actual @($terminal.terminal_notes) -Expected $requiredTerminalNotes +$guardrailsCompatible = ( + (Test-ObjectHasFields -Item $guardrails -Fields $requiredGuardrailFields) -and + [bool](Get-PropertyValue -Item $guardrails -Name "activation_blocked" -Default $false) -and + -not [bool](Get-PropertyValue -Item $guardrails -Name "process_start_allowed" -Default $true) -and + -not [bool](Get-PropertyValue -Item $guardrails -Name "health_probe_enabled" -Default $true) -and + [string](Get-PropertyValue -Item $guardrails -Name "payload_traffic" -Default "") -eq "none" -and + -not [bool](Get-PropertyValue -Item $guardrails -Name "allows_process_start" -Default $true) -and + -not [bool](Get-PropertyValue -Item $guardrails -Name "allows_payload_traffic" -Default $true) +) +$terminalValuesCompatible = ( + [string]$terminal.source_closeout_schema -eq "rap.remote_workspace_real_adapter_operator_validation_closeout.v1" -and + [string]$terminal.terminal_status -eq "stage_terminal_complete_contract_only" -and + [string]$terminal.stage_status -eq "complete_no_more_c20_layers_required" -and + [string]$terminal.validation_chain_status -eq "complete_contract_only" -and + [string]$terminal.enablement_status -eq "validated_not_enabled" -and + [string]$terminal.runtime_gate_state -eq "validated_contract_only_not_enabled" -and + [string]$terminal.runtime_effect -eq "contract_only_no_runtime_enablement" -and + [string]$terminal.next_allowed_entrypoint -eq "next_explicit_runtime_enablement_stage_only" -and + -not [bool]$terminal.allows_process_start -and + -not [bool]$terminal.allows_payload_traffic +) + +$checks = [ordered]@{ + source_smoke_passed = ([bool]$sourceResult.passed) + source_schema_expected = ([string]$sourceResult.schema_version -eq "c20z5.remote_workspace_real_adapter_operator_validation_closeout_smoke.v1") + closeout_present = ($null -ne $closeout) + terminal_fields_compatible = $terminalFieldsCompatible + terminal_values_compatible = $terminalValuesCompatible + terminal_notes_compatible = $terminalNotesCompatible + guardrails_compatible = $guardrailsCompatible +} +$failed = @($checks.GetEnumerator() | Where-Object { -not $_.Value } | ForEach-Object { $_.Key }) + +$result = [ordered]@{ + schema_version = "c20z6.remote_workspace_real_adapter_stage_terminal_complete_compatibility_smoke.v1" + source_result_path = $sourceFile + cluster_id = $ClusterID + required_terminal_fields = $requiredTerminalFields + required_terminal_notes = $requiredTerminalNotes + required_guardrail_fields = $requiredGuardrailFields + c20_stage_terminal_complete = $terminal + checks = $checks + failed_checks = $failed + passed = ($failed.Count -eq 0) +} + +$fullResultPath = Join-Path $repoRoot $ResultPath +$resultDir = Split-Path -Parent $fullResultPath +if ($resultDir) { New-Item -ItemType Directory -Force -Path $resultDir | Out-Null } +$result | ConvertTo-Json -Depth 100 | Set-Content -Encoding UTF8 -Path $fullResultPath + +if (-not $result.passed) { + throw "C20Z6 remote workspace real-adapter stage terminal complete compatibility smoke failed. Result: $fullResultPath Failed: $($failed -join ', ')" +} + +Write-Host "C20Z6 remote workspace real-adapter stage terminal complete compatibility smoke passed. Result: $fullResultPath" +$result diff --git a/scripts/ops/expand-test-docker-root-disk.sh b/scripts/ops/expand-test-docker-root-disk.sh new file mode 100644 index 0000000..bf36881 --- /dev/null +++ b/scripts/ops/expand-test-docker-root-disk.sh @@ -0,0 +1,35 @@ +#!/usr/bin/env bash +set -euo pipefail + +DISK="${DISK:-/dev/sda}" +PARTITION_NUMBER="${PARTITION_NUMBER:-3}" +PARTITION="${PARTITION:-${DISK}${PARTITION_NUMBER}}" +LV_PATH="${LV_PATH:-/dev/ubuntu-vg/ubuntu-lv}" + +if [ "$(id -u)" -ne 0 ]; then + echo "This script must run as root. Use: sudo $0" >&2 + exit 1 +fi + +echo "Before:" +lsblk -o NAME,SIZE,TYPE,FSTYPE,MOUNTPOINTS "$DISK" +df -h / +pvs +vgs +lvs + +echo "Growing partition ${PARTITION} to fill ${DISK}..." +growpart "$DISK" "$PARTITION_NUMBER" + +echo "Resizing LVM physical volume ${PARTITION}..." +pvresize "$PARTITION" + +echo "Extending ${LV_PATH} to all free VG space and resizing filesystem..." +lvextend -l +100%FREE -r "$LV_PATH" + +echo "After:" +lsblk -o NAME,SIZE,TYPE,FSTYPE,MOUNTPOINTS "$DISK" +df -h / +pvs +vgs +lvs diff --git a/scripts/ops/test-docker-cluster-guard.sh b/scripts/ops/test-docker-cluster-guard.sh new file mode 100644 index 0000000..740e7b4 --- /dev/null +++ b/scripts/ops/test-docker-cluster-guard.sh @@ -0,0 +1,152 @@ +#!/usr/bin/env bash +set -euo pipefail + +STATUS_FILE="${STATUS_FILE:-/tmp/rap-web-admin/html/downloads/ops/test-docker-cluster-guard-status.json}" +LOCK_FILE="${LOCK_FILE:-/tmp/rap-cluster-guard.lock}" +DISK_WARN="${DISK_WARN:-85}" +DISK_CRITICAL="${DISK_CRITICAL:-92}" +DOWNLOADS_DIR="${DOWNLOADS_DIR:-/tmp/rap-web-admin/html/downloads}" +KEEP_DIR="${KEEP_DIR:-/tmp/rap-web-admin-downloads.keep}" +BACKEND_URL="${BACKEND_URL:-http://127.0.0.1:18080}" +PUBLIC_URL="${PUBLIC_URL:-http://195.123.240.88:19131}" +CLUSTER_ID="${CLUSTER_ID:-cfc0743d-d960-49fb-9de8-96e063d5e4aa}" + +mkdir -p "$(dirname "$STATUS_FILE")" + +exec 9>"$LOCK_FILE" +if ! flock -n 9; then + exit 0 +fi + +actions=() +errors=() + +json_escape() { + sed 's/\\/\\\\/g; s/"/\\"/g' <<<"$1" +} + +record_action() { + actions+=("$1") +} + +record_error() { + errors+=("$1") +} + +disk_used_percent() { + df -P / | awk 'NR==2 {gsub("%","",$5); print $5}' +} + +cleanup_rap_artifacts() { + if [ -d "$DOWNLOADS_DIR" ]; then + find "$DOWNLOADS_DIR" -maxdepth 1 -type f \ + \( -name 'rap-node-agent-0.2.*' -o -name 'rap-host-agent-0.2.*' \) \ + ! -name '*0.2.260-vpnfarm*' \ + ! -name '*0.2.261-vpnfarm*' \ + ! -name '*latest*' \ + -delete 2>/dev/null || true + fi + if [ -d "$KEEP_DIR" ]; then + find "$KEEP_DIR" -maxdepth 1 -type f \ + \( -name 'rap-node-agent-*.tar' -o -name 'rap-host-agent-*' \) \ + ! -name '*0.2.260*' \ + ! -name '*0.2.261*' \ + -delete 2>/dev/null || true + fi + find /tmp -maxdepth 1 -type f -name 'rap-web-admin-dist-*.tar' -mtime +1 -delete 2>/dev/null || true + find /tmp -maxdepth 1 -type d -name 'rap-web-admin-dist-*' -mtime +1 -exec rm -rf {} + 2>/dev/null || true + find /tmp -maxdepth 1 -type d -name 'rap-repo-build-*' -mtime +1 -exec rm -rf {} + 2>/dev/null || true + record_action "cleanup_rap_artifacts" +} + +ensure_container() { + local name="$1" + local running + running="$(docker inspect -f '{{.State.Running}}' "$name" 2>/dev/null || echo missing)" + if [ "$running" != "true" ]; then + if docker start "$name" >/dev/null 2>&1; then + record_action "start_container:$name" + else + record_error "start_container_failed:$name" + fi + return + fi + local health + health="$(docker inspect -f '{{if .State.Health}}{{.State.Health.Status}}{{else}}none{{end}}' "$name" 2>/dev/null || echo missing)" + if [ "$health" = "unhealthy" ]; then + if docker restart "$name" >/dev/null 2>&1; then + record_action "restart_unhealthy:$name" + else + record_error "restart_unhealthy_failed:$name" + fi + fi +} + +redis_guard() { + if docker exec rap_test_redis redis-cli PING >/dev/null 2>&1; then + docker exec rap_test_redis redis-cli CONFIG SET stop-writes-on-bgsave-error no >/dev/null 2>&1 || true + docker exec rap_test_redis redis-cli BGSAVE >/dev/null 2>&1 || true + record_action "redis_guard" + else + record_error "redis_ping_failed" + docker restart rap_test_redis >/dev/null 2>&1 && record_action "restart_container:rap_test_redis" || true + fi +} + +probe_http() { + local name="$1" + local url="$2" + if ! curl -fsS -m 8 "$url" >/dev/null 2>&1; then + record_error "http_probe_failed:$name" + docker restart rap_web_admin rap_test_backend >/dev/null 2>&1 && record_action "restart_web_backend_for:$name" || true + fi +} + +used_before="$(disk_used_percent)" +if [ "$used_before" -ge "$DISK_WARN" ]; then + cleanup_rap_artifacts + docker builder prune -af --filter 'until=24h' >/dev/null 2>&1 && record_action "docker_builder_prune" || true + docker image prune -af --filter 'until=168h' >/dev/null 2>&1 && record_action "docker_image_prune" || true + docker container prune -f >/dev/null 2>&1 && record_action "docker_container_prune" || true +fi + +for container in rap_test_postgres rap_test_redis rap_test_backend rap_web_admin; do + ensure_container "$container" +done + +redis_guard +probe_http "downloads" "$BACKEND_URL/downloads/rap-android-rdp-vpn-build.json" +probe_http "web_admin_root" "$BACKEND_URL/" +probe_http "diagnostics" "$PUBLIC_URL/api/v1/clusters/$CLUSTER_ID/vpn/client-diagnostics" + +used_after="$(disk_used_percent)" +status="ok" +if [ "$used_after" -ge "$DISK_CRITICAL" ]; then + status="critical" +elif [ "$used_after" -ge "$DISK_WARN" ] || [ "${#errors[@]}" -gt 0 ]; then + status="warning" +fi + +actions_json="$(printf '%s\n' "${actions[@]:-}" | awk 'NF {gsub(/\\/,"\\\\"); gsub(/"/,"\\\""); printf "%s\"%s\"", sep, $0; sep=","}')" +errors_json="$(printf '%s\n' "${errors[@]:-}" | awk 'NF {gsub(/\\/,"\\\\"); gsub(/"/,"\\\""); printf "%s\"%s\"", sep, $0; sep=","}')" +observed_at="$(date -u +%Y-%m-%dT%H:%M:%SZ)" +free_bytes="$(df -PB1 / | awk 'NR==2 {print $4}')" +total_bytes="$(df -PB1 / | awk 'NR==2 {print $2}')" + +cat >"$STATUS_FILE.tmp" </dev/null 2>&1" + return $LASTEXITCODE -eq 0 +} + +$remoteUploadPath = "/home/test/.rap-test-docker-disk-guard.upload" + +Write-Host "Uploading disk guard to ${SshAlias}:${RemoteScriptPath}" +& scp $localScript "${SshAlias}:$remoteUploadPath" +if ($LASTEXITCODE -ne 0) { + throw "scp failed with exit code $LASTEXITCODE" +} + +Invoke-Remote "mkdir -p `$(dirname $RemoteScriptPath) && install -m 0755 $remoteUploadPath $RemoteScriptPath" + +$envPrefix = "WARN_PERCENT=$WarnPercent CLEANUP_PERCENT=$CleanupPercent CRITICAL_PERCENT=$CriticalPercent MIN_TMP_AGE_MINUTES=$MinTmpAgeMinutes" +$runCommand = "$envPrefix $RemoteScriptPath" + +if ($InstallCron) { + if (Test-RemoteCommand "crontab") { + $cronLine = "*/15 * * * * $runCommand >/tmp/rap-ops/test-docker-disk-guard.cron.log 2>&1" + $escapedCronLine = $cronLine.Replace("'", "'\''") + $installCronCommand = "(crontab -l 2>/dev/null | grep -v 'rap-test-docker-disk-guard'; printf '%s\n' '$escapedCronLine') | crontab -" + Write-Host "Installing cron: $cronLine" + Invoke-Remote $installCronCommand + } else { + Write-Host "crontab is not available; installing user systemd timer." + $serviceContent = @" +[Unit] +Description=RAP test-docker disk guard + +[Service] +Type=oneshot +Environment=WARN_PERCENT=$WarnPercent +Environment=CLEANUP_PERCENT=$CleanupPercent +Environment=CRITICAL_PERCENT=$CriticalPercent +Environment=MIN_TMP_AGE_MINUTES=$MinTmpAgeMinutes +ExecStart=$RemoteScriptPath +"@ + $timerContent = @" +[Unit] +Description=Run RAP test-docker disk guard every 15 minutes + +[Timer] +OnBootSec=2min +OnUnitActiveSec=15min +AccuracySec=1min +Persistent=true + +[Install] +WantedBy=timers.target +"@ + $tmpBase = Join-Path ([System.IO.Path]::GetTempPath()) ("rap-disk-guard-" + [System.Guid]::NewGuid().ToString("N")) + New-Item -ItemType Directory -Path $tmpBase | Out-Null + $servicePath = Join-Path $tmpBase "rap-test-docker-disk-guard.service" + $timerPath = Join-Path $tmpBase "rap-test-docker-disk-guard.timer" + Set-Content -Path $servicePath -Value $serviceContent -Encoding ascii + Set-Content -Path $timerPath -Value $timerContent -Encoding ascii + Invoke-Remote "mkdir -p /home/test/.config/systemd/user" + & scp $servicePath "${SshAlias}:/home/test/.config/systemd/user/rap-test-docker-disk-guard.service" + if ($LASTEXITCODE -ne 0) { throw "scp service failed with exit code $LASTEXITCODE" } + & scp $timerPath "${SshAlias}:/home/test/.config/systemd/user/rap-test-docker-disk-guard.timer" + if ($LASTEXITCODE -ne 0) { throw "scp timer failed with exit code $LASTEXITCODE" } + Remove-Item -Recurse -Force -LiteralPath $tmpBase + Invoke-Remote "systemctl --user daemon-reload && systemctl --user enable --now rap-test-docker-disk-guard.timer && systemctl --user start rap-test-docker-disk-guard.service" + } +} + +if ($RunOnce -or -not $InstallCron) { + Write-Host "Running disk guard once..." + & ssh $SshAlias $runCommand + $exitCode = $LASTEXITCODE + if ($exitCode -ne 0 -and $exitCode -ne 2) { + throw "disk guard failed with exit code $exitCode" + } + if ($exitCode -eq 2) { + Write-Warning "Disk guard reports critical state after cleanup." + } +} + +Write-Host "Status: $StatusUrl" diff --git a/scripts/ops/test-docker-disk-guard.sh b/scripts/ops/test-docker-disk-guard.sh new file mode 100644 index 0000000..cea5cb1 --- /dev/null +++ b/scripts/ops/test-docker-disk-guard.sh @@ -0,0 +1,167 @@ +#!/usr/bin/env sh +set -eu + +WARN_PERCENT="${WARN_PERCENT:-85}" +CLEANUP_PERCENT="${CLEANUP_PERCENT:-85}" +CRITICAL_PERCENT="${CRITICAL_PERCENT:-95}" +MIN_TMP_AGE_MINUTES="${MIN_TMP_AGE_MINUTES:-360}" +MOUNT_PATH="${MOUNT_PATH:-/}" +STATUS_DIR="${STATUS_DIR:-/tmp/rap-web-admin/html/downloads/ops}" +LOG_DIR="${LOG_DIR:-/tmp/rap-ops}" +WEBHOOK_URL="${WEBHOOK_URL:-}" + +mkdir -p "$STATUS_DIR" "$LOG_DIR" + +started_at="$(date -u +%Y-%m-%dT%H:%M:%SZ)" +log_file="$LOG_DIR/test-docker-disk-guard.log" +status_file="$STATUS_DIR/test-docker-disk-guard-status.json" + +log() { + printf '%s %s\n' "$(date -u +%Y-%m-%dT%H:%M:%SZ)" "$*" >>"$log_file" +} + +json_escape() { + printf '%s' "$1" | sed 's/\\/\\\\/g; s/"/\\"/g' +} + +disk_used_percent() { + df -P "$MOUNT_PATH" | awk 'NR==2 { gsub("%", "", $5); print $5 }' +} + +disk_avail_human() { + df -hP "$MOUNT_PATH" | awk 'NR==2 { print $4 }' +} + +disk_used_human() { + df -hP "$MOUNT_PATH" | awk 'NR==2 { print $3 }' +} + +disk_size_human() { + df -hP "$MOUNT_PATH" | awk 'NR==2 { print $2 }' +} + +docker_df_summary() { + if command -v docker >/dev/null 2>&1; then + docker system df 2>&1 | tr '\n' '; ' | sed 's/"/\\"/g' + else + printf 'docker cli not found' + fi +} + +cleanup_safe() { + log "cleanup started: docker build cache and old RAP tmp artifacts" + if command -v docker >/dev/null 2>&1; then + docker builder prune -af >>"$log_file" 2>&1 || true + docker image prune -f >>"$log_file" 2>&1 || true + fi + find /tmp -maxdepth 1 \( \ + -name 'rap-android-build-*' -o \ + -name 'rap-agent-build*' -o \ + -name 'rap-backend-build*' -o \ + -name 'rap-c*-build*' -o \ + -name 'rap-node-agent-*' -o \ + -name 'rap-*vpnfarm*' -o \ + -name 'rap-build-*' \ + \) -mmin +"$MIN_TMP_AGE_MINUTES" -exec rm -rf {} + >>"$log_file" 2>&1 || true + sync || true + log "cleanup finished" +} + +expansion_hint() { + root_source="$(df -P "$MOUNT_PATH" | awk 'NR==2 { print $1 }')" + vg_free="" + root_lv_bytes="" + parent_part_bytes="" + if command -v vgs >/dev/null 2>&1; then + vg_free="$(vgs --noheadings --units g -o vg_free 2>/dev/null | awk '{print $1}' | tr '\n' ' ' | sed 's/^ *//;s/ *$//')" + fi + if command -v lsblk >/dev/null 2>&1; then + root_lv_bytes="$(lsblk -b -n -o TYPE,SIZE,MOUNTPOINTS 2>/dev/null | awk '$1 == "lvm" && $3 == "/" { print $2; exit }')" + parent_part_bytes="$(lsblk -b -n -o TYPE,SIZE 2>/dev/null | awk '$1 == "part" { last=$2 } $1 == "lvm" { print last; exit }')" + fi + if printf '%s' "$root_source" | grep -q '^/dev/mapper/'; then + if [ -n "$root_lv_bytes" ] && [ -n "$parent_part_bytes" ] && [ "$parent_part_bytes" -gt "$((root_lv_bytes + 1073741824))" ]; then + printf 'LVM root detected on %s. Backing partition is larger than root LV, so expansion is likely available. Run with sudo: sudo lvextend -r -l +100%%FREE %s' "$root_source" "$root_source" + return + fi + if [ -n "$vg_free" ] && ! printf '%s' "$vg_free" | grep -Eq '(^| )0(\.00)?g( |$)'; then + printf 'LVM root detected on %s. If approved, extend inside existing VG: sudo lvextend -r -l +100%%FREE %s' "$root_source" "$root_source" + else + printf 'LVM root detected on %s. No obvious free VG space. Expand VM disk, then run pvresize on the PV and lvextend -r for root LV.' "$root_source" + fi + else + printf 'Root filesystem is %s. Expand underlying disk/volume, then grow filesystem according to host partition layout.' "$root_source" + fi +} + +notify() { + level="$1" + message="$2" + if [ -n "$WEBHOOK_URL" ] && command -v curl >/dev/null 2>&1; then + payload="$(printf '{"level":"%s","message":"%s","host":"%s","observed_at":"%s"}' \ + "$(json_escape "$level")" \ + "$(json_escape "$message")" \ + "$(json_escape "$(hostname)")" \ + "$(date -u +%Y-%m-%dT%H:%M:%SZ)")" + curl -fsS -m 5 -H 'Content-Type: application/json' -d "$payload" "$WEBHOOK_URL" >>"$log_file" 2>&1 || true + fi +} + +before_percent="$(disk_used_percent)" +action="none" +level="ok" + +if [ "$before_percent" -ge "$CLEANUP_PERCENT" ]; then + action="cleanup_safe" + cleanup_safe +fi + +after_percent="$(disk_used_percent)" +if [ "$after_percent" -ge "$CRITICAL_PERCENT" ]; then + level="critical" +elif [ "$after_percent" -ge "$WARN_PERCENT" ]; then + level="warning" +fi + +hint="$(expansion_hint)" +summary="$(docker_df_summary)" +finished_at="$(date -u +%Y-%m-%dT%H:%M:%SZ)" + +cat >"$status_file.tmp" <()=>(t||(e((t={exports:{}}).exports,t),e=null),t.exports),s=(e,i,o,s)=>{if(i&&typeof i==`object`||typeof i==`function`)for(var c=r(i),l=0,u=c.length,d;li[e]).bind(null,d),enumerable:!(s=n(i,d))||s.enumerable});return e},c=(n,r,a)=>(a=n==null?{}:e(i(n)),s(r||!n||!n.__esModule?t(a,`default`,{value:n,enumerable:!0}):a,n));(function(){let e=document.createElement(`link`).relList;if(e&&e.supports&&e.supports(`modulepreload`))return;for(let e of document.querySelectorAll(`link[rel="modulepreload"]`))n(e);new MutationObserver(e=>{for(let t of e)if(t.type===`childList`)for(let e of t.addedNodes)e.tagName===`LINK`&&e.rel===`modulepreload`&&n(e)}).observe(document,{childList:!0,subtree:!0});function t(e){let t={};return e.integrity&&(t.integrity=e.integrity),e.referrerPolicy&&(t.referrerPolicy=e.referrerPolicy),e.crossOrigin===`use-credentials`?t.credentials=`include`:e.crossOrigin===`anonymous`?t.credentials=`omit`:t.credentials=`same-origin`,t}function n(e){if(e.ep)return;e.ep=!0;let n=t(e);fetch(e.href,n)}})();var l=o((e=>{var t=Symbol.for(`react.transitional.element`),n=Symbol.for(`react.portal`),r=Symbol.for(`react.fragment`),i=Symbol.for(`react.strict_mode`),a=Symbol.for(`react.profiler`),o=Symbol.for(`react.consumer`),s=Symbol.for(`react.context`),c=Symbol.for(`react.forward_ref`),l=Symbol.for(`react.suspense`),u=Symbol.for(`react.memo`),d=Symbol.for(`react.lazy`),f=Symbol.for(`react.activity`),p=Symbol.iterator;function m(e){return typeof e!=`object`||!e?null:(e=p&&e[p]||e[`@@iterator`],typeof e==`function`?e:null)}var h={isMounted:function(){return!1},enqueueForceUpdate:function(){},enqueueReplaceState:function(){},enqueueSetState:function(){}},g=Object.assign,_={};function v(e,t,n){this.props=e,this.context=t,this.refs=_,this.updater=n||h}v.prototype.isReactComponent={},v.prototype.setState=function(e,t){if(typeof e!=`object`&&typeof e!=`function`&&e!=null)throw Error(`takes an object of state variables to update or a function which returns an object of state variables.`);this.updater.enqueueSetState(this,e,t,`setState`)},v.prototype.forceUpdate=function(e){this.updater.enqueueForceUpdate(this,e,`forceUpdate`)};function y(){}y.prototype=v.prototype;function b(e,t,n){this.props=e,this.context=t,this.refs=_,this.updater=n||h}var x=b.prototype=new y;x.constructor=b,g(x,v.prototype),x.isPureReactComponent=!0;var S=Array.isArray;function C(){}var w={H:null,A:null,T:null,S:null},ee=Object.prototype.hasOwnProperty;function te(e,n,r){var i=r.ref;return{$$typeof:t,type:e,key:n,ref:i===void 0?null:i,props:r}}function ne(e,t){return te(e.type,t,e.props)}function T(e){return typeof e==`object`&&!!e&&e.$$typeof===t}function re(e){var t={"=":`=0`,":":`=2`};return`$`+e.replace(/[=:]/g,function(e){return t[e]})}var ie=/\/+/g;function E(e,t){return typeof e==`object`&&e&&e.key!=null?re(``+e.key):t.toString(36)}function ae(e){switch(e.status){case`fulfilled`:return e.value;case`rejected`:throw e.reason;default:switch(typeof e.status==`string`?e.then(C,C):(e.status=`pending`,e.then(function(t){e.status===`pending`&&(e.status=`fulfilled`,e.value=t)},function(t){e.status===`pending`&&(e.status=`rejected`,e.reason=t)})),e.status){case`fulfilled`:return e.value;case`rejected`:throw e.reason}}throw e}function oe(e,r,i,a,o){var s=typeof e;(s===`undefined`||s===`boolean`)&&(e=null);var c=!1;if(e===null)c=!0;else switch(s){case`bigint`:case`string`:case`number`:c=!0;break;case`object`:switch(e.$$typeof){case t:case n:c=!0;break;case d:return c=e._init,oe(c(e._payload),r,i,a,o)}}if(c)return o=o(e),c=a===``?`.`+E(e,0):a,S(o)?(i=``,c!=null&&(i=c.replace(ie,`$&/`)+`/`),oe(o,r,i,``,function(e){return e})):o!=null&&(T(o)&&(o=ne(o,i+(o.key==null||e&&e.key===o.key?``:(``+o.key).replace(ie,`$&/`)+`/`)+c)),r.push(o)),1;c=0;var l=a===``?`.`:a+`:`;if(S(e))for(var u=0;u{t.exports=l()})),d=o((e=>{function t(e,t){var n=e.length;e.push(t);a:for(;0>>1,a=e[r];if(0>>1;ri(c,n))li(u,c)?(e[r]=u,e[l]=n,r=l):(e[r]=c,e[s]=n,r=s);else if(li(u,n))e[r]=u,e[l]=n,r=l;else break a}}return t}function i(e,t){var n=e.sortIndex-t.sortIndex;return n===0?e.id-t.id:n}if(e.unstable_now=void 0,typeof performance==`object`&&typeof performance.now==`function`){var a=performance;e.unstable_now=function(){return a.now()}}else{var o=Date,s=o.now();e.unstable_now=function(){return o.now()-s}}var c=[],l=[],u=1,d=null,f=3,p=!1,m=!1,h=!1,g=!1,_=typeof setTimeout==`function`?setTimeout:null,v=typeof clearTimeout==`function`?clearTimeout:null,y=typeof setImmediate<`u`?setImmediate:null;function b(e){for(var i=n(l);i!==null;){if(i.callback===null)r(l);else if(i.startTime<=e)r(l),i.sortIndex=i.expirationTime,t(c,i);else break;i=n(l)}}function x(e){if(h=!1,b(e),!m)if(n(c)!==null)m=!0,S||(S=!0,T());else{var t=n(l);t!==null&&E(x,t.startTime-e)}}var S=!1,C=-1,w=5,ee=-1;function te(){return g?!0:!(e.unstable_now()-eet&&te());){var o=d.callback;if(typeof o==`function`){d.callback=null,f=d.priorityLevel;var s=o(d.expirationTime<=t);if(t=e.unstable_now(),typeof s==`function`){d.callback=s,b(t),i=!0;break b}d===n(c)&&r(c),b(t)}else r(c);d=n(c)}if(d!==null)i=!0;else{var u=n(l);u!==null&&E(x,u.startTime-t),i=!1}}break a}finally{d=null,f=a,p=!1}i=void 0}}finally{i?T():S=!1}}}var T;if(typeof y==`function`)T=function(){y(ne)};else if(typeof MessageChannel<`u`){var re=new MessageChannel,ie=re.port2;re.port1.onmessage=ne,T=function(){ie.postMessage(null)}}else T=function(){_(ne,0)};function E(t,n){C=_(function(){t(e.unstable_now())},n)}e.unstable_IdlePriority=5,e.unstable_ImmediatePriority=1,e.unstable_LowPriority=4,e.unstable_NormalPriority=3,e.unstable_Profiling=null,e.unstable_UserBlockingPriority=2,e.unstable_cancelCallback=function(e){e.callback=null},e.unstable_forceFrameRate=function(e){0>e||125o?(r.sortIndex=a,t(l,r),n(c)===null&&r===n(l)&&(h?(v(C),C=-1):h=!0,E(x,a-o))):(r.sortIndex=s,t(c,r),m||p||(m=!0,S||(S=!0,T()))),r},e.unstable_shouldYield=te,e.unstable_wrapCallback=function(e){var t=f;return function(){var n=f;f=t;try{return e.apply(this,arguments)}finally{f=n}}}})),f=o(((e,t)=>{t.exports=d()})),p=o((e=>{var t=u();function n(e){var t=`https://react.dev/errors/`+e;if(1{function n(){if(!(typeof __REACT_DEVTOOLS_GLOBAL_HOOK__>`u`||typeof __REACT_DEVTOOLS_GLOBAL_HOOK__.checkDCE!=`function`))try{__REACT_DEVTOOLS_GLOBAL_HOOK__.checkDCE(n)}catch(e){console.error(e)}}n(),t.exports=p()})),h=o((e=>{var t=f(),n=u(),r=m();function i(e){var t=`https://react.dev/errors/`+e;if(1k||(e.current=ue[k],ue[k]=null,k--)}function A(e,t){k++,ue[k]=e.current,e.current=t}var pe=de(null),me=de(null),he=de(null),ge=de(null);function _e(e,t){switch(A(he,t),A(me,e),A(pe,null),t.nodeType){case 9:case 11:e=(e=t.documentElement)&&(e=e.namespaceURI)?Vd(e):0;break;default:if(e=t.tagName,t=t.namespaceURI)t=Vd(t),e=Hd(t,e);else switch(e){case`svg`:e=1;break;case`math`:e=2;break;default:e=0}}fe(pe),A(pe,e)}function ve(){fe(pe),fe(me),fe(he)}function ye(e){e.memoizedState!==null&&A(ge,e);var t=pe.current,n=Hd(t,e.type);t!==n&&(A(me,e),A(pe,n))}function be(e){me.current===e&&(fe(pe),fe(me)),ge.current===e&&(fe(ge),Qf._currentValue=le)}var xe,Se;function Ce(e){if(xe===void 0)try{throw Error()}catch(e){var t=e.stack.trim().match(/\n( *(at )?)/);xe=t&&t[1]||``,Se=-1)`:-1i||c[r]!==l[i]){var u=` -`+c[r].replace(` at new `,` at `);return e.displayName&&u.includes(``)&&(u=u.replace(``,e.displayName)),u}while(1<=r&&0<=i);break}}}finally{we=!1,Error.prepareStackTrace=n}return(n=e?e.displayName||e.name:``)?Ce(n):``}function Te(e,t){switch(e.tag){case 26:case 27:case 5:return Ce(e.type);case 16:return Ce(`Lazy`);case 13:return e.child!==t&&t!==null?Ce(`Suspense Fallback`):Ce(`Suspense`);case 19:return Ce(`SuspenseList`);case 0:case 15:return j(e.type,!1);case 11:return j(e.type.render,!1);case 1:return j(e.type,!0);case 31:return Ce(`Activity`);default:return``}}function Ee(e){try{var t=``,n=null;do t+=Te(e,n),n=e,e=e.return;while(e);return t}catch(e){return` -Error generating stack: `+e.message+` -`+e.stack}}var De=Object.prototype.hasOwnProperty,Oe=t.unstable_scheduleCallback,ke=t.unstable_cancelCallback,Ae=t.unstable_shouldYield,je=t.unstable_requestPaint,Me=t.unstable_now,Ne=t.unstable_getCurrentPriorityLevel,Pe=t.unstable_ImmediatePriority,Fe=t.unstable_UserBlockingPriority,Ie=t.unstable_NormalPriority,Le=t.unstable_LowPriority,Re=t.unstable_IdlePriority,ze=t.log,Be=t.unstable_setDisableYieldValue,Ve=null,He=null;function Ue(e){if(typeof ze==`function`&&Be(e),He&&typeof He.setStrictMode==`function`)try{He.setStrictMode(Ve,e)}catch{}}var We=Math.clz32?Math.clz32:qe,Ge=Math.log,Ke=Math.LN2;function qe(e){return e>>>=0,e===0?32:31-(Ge(e)/Ke|0)|0}var Je=256,Ye=262144,Xe=4194304;function Ze(e){var t=e&42;if(t!==0)return t;switch(e&-e){case 1:return 1;case 2:return 2;case 4:return 4;case 8:return 8;case 16:return 16;case 32:return 32;case 64:return 64;case 128:return 128;case 256:case 512:case 1024:case 2048:case 4096:case 8192:case 16384:case 32768:case 65536:case 131072:return e&261888;case 262144:case 524288:case 1048576:case 2097152:return e&3932160;case 4194304:case 8388608:case 16777216:case 33554432:return e&62914560;case 67108864:return 67108864;case 134217728:return 134217728;case 268435456:return 268435456;case 536870912:return 536870912;case 1073741824:return 0;default:return e}}function Qe(e,t,n){var r=e.pendingLanes;if(r===0)return 0;var i=0,a=e.suspendedLanes,o=e.pingedLanes;e=e.warmLanes;var s=r&134217727;return s===0?(s=r&~a,s===0?o===0?n||(n=r&~e,n!==0&&(i=Ze(n))):i=Ze(o):i=Ze(s)):(r=s&~a,r===0?(o&=s,o===0?n||(n=s&~e,n!==0&&(i=Ze(n))):i=Ze(o)):i=Ze(r)),i===0?0:t!==0&&t!==i&&(t&a)===0&&(a=i&-i,n=t&-t,a>=n||a===32&&n&4194048)?t:i}function $e(e,t){return(e.pendingLanes&~(e.suspendedLanes&~e.pingedLanes)&t)===0}function et(e,t){switch(e){case 1:case 2:case 4:case 8:case 64:return t+250;case 16:case 32:case 128:case 256:case 512:case 1024:case 2048:case 4096:case 8192:case 16384:case 32768:case 65536:case 131072:case 262144:case 524288:case 1048576:case 2097152:return t+5e3;case 4194304:case 8388608:case 16777216:case 33554432:return-1;case 67108864:case 134217728:case 268435456:case 536870912:case 1073741824:return-1;default:return-1}}function tt(){var e=Xe;return Xe<<=1,!(Xe&62914560)&&(Xe=4194304),e}function M(e){for(var t=[],n=0;31>n;n++)t.push(e);return t}function nt(e,t){e.pendingLanes|=t,t!==268435456&&(e.suspendedLanes=0,e.pingedLanes=0,e.warmLanes=0)}function rt(e,t,n,r,i,a){var o=e.pendingLanes;e.pendingLanes=n,e.suspendedLanes=0,e.pingedLanes=0,e.warmLanes=0,e.expiredLanes&=n,e.entangledLanes&=n,e.errorRecoveryDisabledLanes&=n,e.shellSuspendCounter=0;var s=e.entanglements,c=e.expirationTimes,l=e.hiddenUpdates;for(n=o&~n;0`u`||window.document===void 0||window.document.createElement===void 0),hn=!1;if(mn)try{var gn={};Object.defineProperty(gn,`passive`,{get:function(){hn=!0}}),window.addEventListener(`test`,gn,gn),window.removeEventListener(`test`,gn,gn)}catch{hn=!1}var _n=null,vn=null,yn=null;function P(){if(yn)return yn;var e,t=vn,n=t.length,r,i=`value`in _n?_n.value:_n.textContent,a=i.length;for(e=0;e=Yn),Qn=` `,$n=!1;function er(e,t){switch(e){case`keyup`:return qn.indexOf(t.keyCode)!==-1;case`keydown`:return t.keyCode!==229;case`keypress`:case`mousedown`:case`focusout`:return!0;default:return!1}}function tr(e){return e=e.detail,typeof e==`object`&&`data`in e?e.data:null}var nr=!1;function L(e,t){switch(e){case`compositionend`:return tr(t);case`keypress`:return t.which===32?($n=!0,Qn):null;case`textInput`:return e=t.data,e===Qn&&$n?null:e;default:return null}}function R(e,t){if(nr)return e===`compositionend`||!Jn&&er(e,t)?(e=P(),yn=vn=_n=null,nr=!1,e):null;switch(e){case`paste`:return null;case`keypress`:if(!(t.ctrlKey||t.altKey||t.metaKey)||t.ctrlKey&&t.altKey){if(t.char&&1=t)return{node:n,offset:t-e};e=r}a:{for(;n;){if(n.nextSibling){n=n.nextSibling;break a}n=n.parentNode}n=void 0}n=wr(n)}}function Er(e,t){return e&&t?e===t?!0:e&&e.nodeType===3?!1:t&&t.nodeType===3?Er(e,t.parentNode):`contains`in e?e.contains(t):e.compareDocumentPosition?!!(e.compareDocumentPosition(t)&16):!1:!1}function Dr(e){e=e!=null&&e.ownerDocument!=null&&e.ownerDocument.defaultView!=null?e.ownerDocument.defaultView:window;for(var t=Ht(e.document);t instanceof e.HTMLIFrameElement;){try{var n=typeof t.contentWindow.location.href==`string`}catch{n=!1}if(n)e=t.contentWindow;else break;t=Ht(e.document)}return t}function Or(e){var t=e&&e.nodeName&&e.nodeName.toLowerCase();return t&&(t===`input`&&(e.type===`text`||e.type===`search`||e.type===`tel`||e.type===`url`||e.type===`password`)||t===`textarea`||e.contentEditable===`true`)}var kr=mn&&`documentMode`in document&&11>=document.documentMode,Ar=null,jr=null,Mr=null,Nr=!1;function Pr(e,t,n){var r=n.window===n?n.document:n.nodeType===9?n:n.ownerDocument;Nr||Ar==null||Ar!==Ht(r)||(r=Ar,`selectionStart`in r&&Or(r)?r={start:r.selectionStart,end:r.selectionEnd}:(r=(r.ownerDocument&&r.ownerDocument.defaultView||window).getSelection(),r={anchorNode:r.anchorNode,anchorOffset:r.anchorOffset,focusNode:r.focusNode,focusOffset:r.focusOffset}),Mr&&Cr(Mr,r)||(Mr=r,r=Td(jr,`onSelect`),0>=o,i-=o,xi=1<<32-We(t)+i|n<h?(g=d,d=null):g=d.sibling;var _=p(i,d,s[h],c);if(_===null){d===null&&(d=g);break}e&&d&&_.alternate===null&&t(i,d),a=o(_,a,h),u===null?l=_:u.sibling=_,u=_,d=g}if(h===s.length)return n(i,d),G&&Ci(i,h),l;if(d===null){for(;hg?(_=h,h=null):_=h.sibling;var y=p(a,h,v.value,l);if(y===null){h===null&&(h=_);break}e&&h&&y.alternate===null&&t(a,h),s=o(y,s,g),d===null?u=y:d.sibling=y,d=y,h=_}if(v.done)return n(a,h),G&&Ci(a,g),u;if(h===null){for(;!v.done;g++,v=c.next())v=f(a,v.value,l),v!==null&&(s=o(v,s,g),d===null?u=v:d.sibling=v,d=v);return G&&Ci(a,g),u}for(h=r(h);!v.done;g++,v=c.next())v=m(h,a,g,v.value,l),v!==null&&(e&&v.alternate!==null&&h.delete(v.key===null?g:v.key),s=o(v,s,g),d===null?u=v:d.sibling=v,d=v);return e&&h.forEach(function(e){return t(a,e)}),G&&Ci(a,g),u}function b(e,r,o,c){if(typeof o==`object`&&o&&o.type===y&&o.key===null&&(o=o.props.children),typeof o==`object`&&o){switch(o.$$typeof){case _:a:{for(var l=o.key;r!==null;){if(r.key===l){if(l=o.type,l===y){if(r.tag===7){n(e,r.sibling),c=a(r,o.props.children),c.return=e,e=c;break a}}else if(r.elementType===l||typeof l==`object`&&l&&l.$$typeof===T&&xa(l)===r.type){n(e,r.sibling),c=a(r,o.props),Oa(c,o),c.return=e,e=c;break a}n(e,r);break}else t(e,r);r=r.sibling}o.type===y?(c=di(o.props.children,e.mode,c,o.key),c.return=e,e=c):(c=ui(o.type,o.key,o.props,null,e.mode,c),Oa(c,o),c.return=e,e=c)}return s(e);case v:a:{for(l=o.key;r!==null;){if(r.key===l)if(r.tag===4&&r.stateNode.containerInfo===o.containerInfo&&r.stateNode.implementation===o.implementation){n(e,r.sibling),c=a(r,o.children||[]),c.return=e,e=c;break a}else{n(e,r);break}else t(e,r);r=r.sibling}c=pi(o,e.mode,c),c.return=e,e=c}return s(e);case T:return o=xa(o),b(e,r,o,c)}if(ce(o))return h(e,r,o,c);if(ae(o)){if(l=ae(o),typeof l!=`function`)throw Error(i(150));return o=l.call(o),g(e,r,o,c)}if(typeof o.then==`function`)return b(e,r,Da(o),c);if(o.$$typeof===C)return b(e,r,Yi(e,o),c);ka(e,o)}return typeof o==`string`&&o!==``||typeof o==`number`||typeof o==`bigint`?(o=``+o,r!==null&&r.tag===6?(n(e,r.sibling),c=a(r,o),c.return=e,e=c):(n(e,r),c=V(o,e.mode,c),c.return=e,e=c),s(e)):n(e,r)}return function(e,t,n,r){try{Ea=0;var i=b(e,t,n,r);return Ta=null,i}catch(t){if(t===ha||t===_a)throw t;var a=oi(29,t,null,e.mode);return a.lanes=r,a.return=e,a}}}var ja=Aa(!0),Ma=Aa(!1),Na=!1;function Pa(e){e.updateQueue={baseState:e.memoizedState,firstBaseUpdate:null,lastBaseUpdate:null,shared:{pending:null,lanes:0,hiddenCallbacks:null},callbacks:null}}function Fa(e,t){e=e.updateQueue,t.updateQueue===e&&(t.updateQueue={baseState:e.baseState,firstBaseUpdate:e.firstBaseUpdate,lastBaseUpdate:e.lastBaseUpdate,shared:e.shared,callbacks:null})}function Ia(e){return{lane:e,tag:0,payload:null,callback:null,next:null}}function La(e,t,n){var r=e.updateQueue;if(r===null)return null;if(r=r.shared,J&2){var i=r.pending;return i===null?t.next=t:(t.next=i.next,i.next=t),r.pending=t,t=ri(e),ni(e,null,n),t}return ei(e,r,t,n),ri(e)}function Ra(e,t,n){if(t=t.updateQueue,t!==null&&(t=t.shared,n&4194048)){var r=t.lanes;r&=e.pendingLanes,n|=r,t.lanes=n,at(e,n)}}function za(e,t){var n=e.updateQueue,r=e.alternate;if(r!==null&&(r=r.updateQueue,n===r)){var i=null,a=null;if(n=n.firstBaseUpdate,n!==null){do{var o={lane:n.lane,tag:n.tag,payload:n.payload,callback:null,next:null};a===null?i=a=o:a=a.next=o,n=n.next}while(n!==null);a===null?i=a=t:a=a.next=t}else i=a=t;n={baseState:r.baseState,firstBaseUpdate:i,lastBaseUpdate:a,shared:r.shared,callbacks:r.callbacks},e.updateQueue=n;return}e=n.lastBaseUpdate,e===null?n.firstBaseUpdate=t:e.next=t,n.lastBaseUpdate=t}var Ba=!1;function Va(){if(Ba){var e=oa;if(e!==null)throw e}}function Ha(e,t,n,r){Ba=!1;var i=e.updateQueue;Na=!1;var a=i.firstBaseUpdate,o=i.lastBaseUpdate,s=i.shared.pending;if(s!==null){i.shared.pending=null;var c=s,l=c.next;c.next=null,o===null?a=l:o.next=l,o=c;var u=e.alternate;u!==null&&(u=u.updateQueue,s=u.lastBaseUpdate,s!==o&&(s===null?u.firstBaseUpdate=l:s.next=l,u.lastBaseUpdate=c))}if(a!==null){var d=i.baseState;o=0,u=l=c=null,s=a;do{var f=s.lane&-536870913,p=f!==s.lane;if(p?(X&f)===f:(r&f)===f){f!==0&&f===aa&&(Ba=!0),u!==null&&(u=u.next={lane:0,tag:s.tag,payload:s.payload,callback:null,next:null});a:{var m=e,g=s;f=t;var _=n;switch(g.tag){case 1:if(m=g.payload,typeof m==`function`){d=m.call(_,d,f);break a}d=m;break a;case 3:m.flags=m.flags&-65537|128;case 0:if(m=g.payload,f=typeof m==`function`?m.call(_,d,f):m,f==null)break a;d=h({},d,f);break a;case 2:Na=!0}}f=s.callback,f!==null&&(e.flags|=64,p&&(e.flags|=8192),p=i.callbacks,p===null?i.callbacks=[f]:p.push(f))}else p={lane:f,tag:s.tag,payload:s.payload,callback:s.callback,next:null},u===null?(l=u=p,c=d):u=u.next=p,o|=f;if(s=s.next,s===null){if(s=i.shared.pending,s===null)break;p=s,s=p.next,p.next=null,i.lastBaseUpdate=p,i.shared.pending=null}}while(1);u===null&&(c=d),i.baseState=c,i.firstBaseUpdate=l,i.lastBaseUpdate=u,a===null&&(i.shared.lanes=0),Wl|=o,e.lanes=o,e.memoizedState=d}}function Ua(e,t){if(typeof e!=`function`)throw Error(i(191,e));e.call(t)}function Wa(e,t){var n=e.callbacks;if(n!==null)for(e.callbacks=null,e=0;ea?a:8;var o=D.T,s={};D.T=s,As(e,!1,t,n);try{var c=i(),l=D.S;l!==null&&l(s,c),typeof c==`object`&&c&&typeof c.then==`function`?ks(e,t,la(c,r),fu(e)):ks(e,t,r,fu(e))}catch(n){ks(e,t,{then:function(){},status:`rejected`,reason:n},fu())}finally{O.p=a,o!==null&&s.types!==null&&(o.types=s.types),D.T=o}}function ys(){}function bs(e,t,n,r){if(e.tag!==5)throw Error(i(476));var a=xs(e).queue;vs(e,a,t,le,n===null?ys:function(){return Ss(e),n(r)})}function xs(e){var t=e.memoizedState;if(t!==null)return t;t={memoizedState:le,baseState:le,baseQueue:null,queue:{pending:null,lanes:0,dispatch:null,lastRenderedReducer:jo,lastRenderedState:le},next:null};var n={};return t.next={memoizedState:n,baseState:n,baseQueue:null,queue:{pending:null,lanes:0,dispatch:null,lastRenderedReducer:jo,lastRenderedState:n},next:null},e.memoizedState=t,e=e.alternate,e!==null&&(e.memoizedState=t),t}function Ss(e){var t=xs(e);t.next===null&&(t=e.alternate.memoizedState),ks(e,t.next.queue,{},fu())}function Cs(){return Ji(Qf)}function ws(){return Eo().memoizedState}function Ts(){return Eo().memoizedState}function Es(e){for(var t=e.return;t!==null;){switch(t.tag){case 24:case 3:var n=fu();e=Ia(n);var r=La(t,e,n);r!==null&&(mu(r,t,n),Ra(r,t,n)),t={cache:ta()},e.payload=t;return}t=t.return}}function Ds(e,t,n){var r=fu();n={lane:r,revertLane:0,gesture:null,action:n,hasEagerState:!1,eagerState:null,next:null},js(e)?Ms(t,n):(n=ti(e,t,n,r),n!==null&&(mu(n,e,r),Ns(n,t,r)))}function Os(e,t,n){ks(e,t,n,fu())}function ks(e,t,n,r){var i={lane:r,revertLane:0,gesture:null,action:n,hasEagerState:!1,eagerState:null,next:null};if(js(e))Ms(t,i);else{var a=e.alternate;if(e.lanes===0&&(a===null||a.lanes===0)&&(a=t.lastRenderedReducer,a!==null))try{var o=t.lastRenderedState,s=a(o,n);if(i.hasEagerState=!0,i.eagerState=s,Sr(s,o))return ei(e,t,i,0),Ll===null&&$r(),!1}catch{}if(n=ti(e,t,i,r),n!==null)return mu(n,e,r),Ns(n,t,r),!0}return!1}function As(e,t,n,r){if(r={lane:2,revertLane:ud(),gesture:null,action:r,hasEagerState:!1,eagerState:null,next:null},js(e)){if(t)throw Error(i(479))}else t=ti(e,n,r,2),t!==null&&mu(t,e,2)}function js(e){var t=e.alternate;return e===q||t!==null&&t===q}function Ms(e,t){lo=co=!0;var n=e.pending;n===null?t.next=t:(t.next=n.next,n.next=t),e.pending=t}function Ns(e,t,n){if(n&4194048){var r=t.lanes;r&=e.pendingLanes,n|=r,t.lanes=n,at(e,n)}}var Ps={readContext:Ji,use:ko,useCallback:go,useContext:go,useEffect:go,useImperativeHandle:go,useLayoutEffect:go,useInsertionEffect:go,useMemo:go,useReducer:go,useRef:go,useState:go,useDebugValue:go,useDeferredValue:go,useTransition:go,useSyncExternalStore:go,useId:go,useHostTransitionStatus:go,useFormState:go,useActionState:go,useOptimistic:go,useMemoCache:go,useCacheRefresh:go};Ps.useEffectEvent=go;var Fs={readContext:Ji,use:ko,useCallback:function(e,t){return To().memoizedState=[e,t===void 0?null:t],e},useContext:Ji,useEffect:as,useImperativeHandle:function(e,t,n){n=n==null?null:n.concat([e]),rs(4194308,4,ds.bind(null,t,e),n)},useLayoutEffect:function(e,t){return rs(4194308,4,e,t)},useInsertionEffect:function(e,t){rs(4,2,e,t)},useMemo:function(e,t){var n=To();t=t===void 0?null:t;var r=e();if(uo){Ue(!0);try{e()}finally{Ue(!1)}}return n.memoizedState=[r,t],r},useReducer:function(e,t,n){var r=To();if(n!==void 0){var i=n(t);if(uo){Ue(!0);try{n(t)}finally{Ue(!1)}}}else i=t;return r.memoizedState=r.baseState=i,e={pending:null,lanes:0,dispatch:null,lastRenderedReducer:e,lastRenderedState:i},r.queue=e,e=e.dispatch=Ds.bind(null,q,e),[r.memoizedState,e]},useRef:function(e){var t=To();return e={current:e},t.memoizedState=e},useState:function(e){e=Vo(e);var t=e.queue,n=Os.bind(null,q,t);return t.dispatch=n,[e.memoizedState,n]},useDebugValue:ps,useDeferredValue:function(e,t){return gs(To(),e,t)},useTransition:function(){var e=Vo(!1);return e=vs.bind(null,q,e.queue,!0,!1),To().memoizedState=e,[!1,e]},useSyncExternalStore:function(e,t,n){var r=q,a=To();if(G){if(n===void 0)throw Error(i(407));n=n()}else{if(n=t(),Ll===null)throw Error(i(349));X&127||Io(r,t,n)}a.memoizedState=n;var o={value:n,getSnapshot:t};return a.queue=o,as(Ro.bind(null,r,o,e),[e]),r.flags|=2048,ts(9,{destroy:void 0},Lo.bind(null,r,o,n,t),null),n},useId:function(){var e=To(),t=Ll.identifierPrefix;if(G){var n=Si,r=xi;n=(r&~(1<<32-We(r)-1)).toString(32)+n,t=`_`+t+`R_`+n,n=fo++,0<\/script>`,o=o.removeChild(o.firstChild);break;case`select`:o=typeof r.is==`string`?s.createElement(`select`,{is:r.is}):s.createElement(`select`),r.multiple?o.multiple=!0:r.size&&(o.size=r.size);break;default:o=typeof r.is==`string`?s.createElement(a,{is:r.is}):s.createElement(a)}}o[ft]=t,o[pt]=r;a:for(s=t.child;s!==null;){if(s.tag===5||s.tag===6)o.appendChild(s.stateNode);else if(s.tag!==4&&s.tag!==27&&s.child!==null){s.child.return=s,s=s.child;continue}if(s===t)break a;for(;s.sibling===null;){if(s.return===null||s.return===t)break a;s=s.return}s.sibling.return=s.return,s=s.sibling}t.stateNode=o;a:switch(Pd(o,a,r),a){case`button`:case`input`:case`select`:case`textarea`:r=!!r.autoFocus;break a;case`img`:r=!0;break a;default:r=!1}r&&kc(t)}}return Pc(t),Ac(t,t.type,e===null?null:e.memoizedProps,t.pendingProps,n),null;case 6:if(e&&t.stateNode!=null)e.memoizedProps!==r&&kc(t);else{if(typeof r!=`string`&&t.stateNode===null)throw Error(i(166));if(e=he.current,Pi(t)){if(e=t.stateNode,n=t.memoizedProps,r=null,a=Oi,a!==null)switch(a.tag){case 27:case 5:r=a.memoizedProps}e[ft]=t,e=!!(e.nodeValue===n||r!==null&&!0===r.suppressHydrationWarning||jd(e.nodeValue,n)),e||K(t,!0)}else e=Bd(e).createTextNode(r),e[ft]=t,t.stateNode=e}return Pc(t),null;case 31:if(n=t.memoizedState,e===null||e.memoizedState!==null){if(r=Pi(t),n!==null){if(e===null){if(!r)throw Error(i(318));if(e=t.memoizedState,e=e===null?null:e.dehydrated,!e)throw Error(i(557));e[ft]=t}else Fi(),!(t.flags&128)&&(t.memoizedState=null),t.flags|=4;Pc(t),e=!1}else n=Ii(),e!==null&&e.memoizedState!==null&&(e.memoizedState.hydrationErrors=n),e=!0;if(!e)return t.flags&256?(no(t),t):(no(t),null);if(t.flags&128)throw Error(i(558))}return Pc(t),null;case 13:if(r=t.memoizedState,e===null||e.memoizedState!==null&&e.memoizedState.dehydrated!==null){if(a=Pi(t),r!==null&&r.dehydrated!==null){if(e===null){if(!a)throw Error(i(318));if(a=t.memoizedState,a=a===null?null:a.dehydrated,!a)throw Error(i(317));a[ft]=t}else Fi(),!(t.flags&128)&&(t.memoizedState=null),t.flags|=4;Pc(t),a=!1}else a=Ii(),e!==null&&e.memoizedState!==null&&(e.memoizedState.hydrationErrors=a),a=!0;if(!a)return t.flags&256?(no(t),t):(no(t),null)}return no(t),t.flags&128?(t.lanes=n,t):(n=r!==null,e=e!==null&&e.memoizedState!==null,n&&(r=t.child,a=null,r.alternate!==null&&r.alternate.memoizedState!==null&&r.alternate.memoizedState.cachePool!==null&&(a=r.alternate.memoizedState.cachePool.pool),o=null,r.memoizedState!==null&&r.memoizedState.cachePool!==null&&(o=r.memoizedState.cachePool.pool),o!==a&&(r.flags|=2048)),n!==e&&n&&(t.child.flags|=8192),Mc(t,t.updateQueue),Pc(t),null);case 4:return ve(),e===null&&xd(t.stateNode.containerInfo),Pc(t),null;case 10:return Hi(t.type),Pc(t),null;case 19:if(fe(ro),r=t.memoizedState,r===null)return Pc(t),null;if(a=(t.flags&128)!=0,o=r.rendering,o===null)if(a)Nc(r,!1);else{if(Ul!==0||e!==null&&e.flags&128)for(e=t.child;e!==null;){if(o=io(e),o!==null){for(t.flags|=128,Nc(r,!1),e=o.updateQueue,t.updateQueue=e,Mc(t,e),t.subtreeFlags=0,e=n,n=t.child;n!==null;)li(n,e),n=n.sibling;return A(ro,ro.current&1|2),G&&Ci(t,r.treeForkCount),t.child}e=e.sibling}r.tail!==null&&Me()>eu&&(t.flags|=128,a=!0,Nc(r,!1),t.lanes=4194304)}else{if(!a)if(e=io(o),e!==null){if(t.flags|=128,a=!0,e=e.updateQueue,t.updateQueue=e,Mc(t,e),Nc(r,!0),r.tail===null&&r.tailMode===`hidden`&&!o.alternate&&!G)return Pc(t),null}else 2*Me()-r.renderingStartTime>eu&&n!==536870912&&(t.flags|=128,a=!0,Nc(r,!1),t.lanes=4194304);r.isBackwards?(o.sibling=t.child,t.child=o):(e=r.last,e===null?t.child=o:e.sibling=o,r.last=o)}return r.tail===null?(Pc(t),null):(e=r.tail,r.rendering=e,r.tail=e.sibling,r.renderingStartTime=Me(),e.sibling=null,n=ro.current,A(ro,a?n&1|2:n&1),G&&Ci(t,r.treeForkCount),e);case 22:case 23:return no(t),Ya(),r=t.memoizedState!==null,e===null?r&&(t.flags|=8192):e.memoizedState!==null!==r&&(t.flags|=8192),r?n&536870912&&!(t.flags&128)&&(Pc(t),t.subtreeFlags&6&&(t.flags|=8192)):Pc(t),n=t.updateQueue,n!==null&&Mc(t,n.retryQueue),n=null,e!==null&&e.memoizedState!==null&&e.memoizedState.cachePool!==null&&(n=e.memoizedState.cachePool.pool),r=null,t.memoizedState!==null&&t.memoizedState.cachePool!==null&&(r=t.memoizedState.cachePool.pool),r!==n&&(t.flags|=2048),e!==null&&fe(da),null;case 24:return n=null,e!==null&&(n=e.memoizedState.cache),t.memoizedState.cache!==n&&(t.flags|=2048),Hi(ea),Pc(t),null;case 25:return null;case 30:return null}throw Error(i(156,t.tag))}function Ic(e,t){switch(Ei(t),t.tag){case 1:return e=t.flags,e&65536?(t.flags=e&-65537|128,t):null;case 3:return Hi(ea),ve(),e=t.flags,e&65536&&!(e&128)?(t.flags=e&-65537|128,t):null;case 26:case 27:case 5:return be(t),null;case 31:if(t.memoizedState!==null){if(no(t),t.alternate===null)throw Error(i(340));Fi()}return e=t.flags,e&65536?(t.flags=e&-65537|128,t):null;case 13:if(no(t),e=t.memoizedState,e!==null&&e.dehydrated!==null){if(t.alternate===null)throw Error(i(340));Fi()}return e=t.flags,e&65536?(t.flags=e&-65537|128,t):null;case 19:return fe(ro),null;case 4:return ve(),null;case 10:return Hi(t.type),null;case 22:case 23:return no(t),Ya(),e!==null&&fe(da),e=t.flags,e&65536?(t.flags=e&-65537|128,t):null;case 24:return Hi(ea),null;case 25:return null;default:return null}}function Lc(e,t){switch(Ei(t),t.tag){case 3:Hi(ea),ve();break;case 26:case 27:case 5:be(t);break;case 4:ve();break;case 31:t.memoizedState!==null&&no(t);break;case 13:no(t);break;case 19:fe(ro);break;case 10:Hi(t.type);break;case 22:case 23:no(t),Ya(),e!==null&&fe(da);break;case 24:Hi(ea)}}function Rc(e,t){try{var n=t.updateQueue,r=n===null?null:n.lastEffect;if(r!==null){var i=r.next;n=i;do{if((n.tag&e)===e){r=void 0;var a=n.create,o=n.inst;r=a(),o.destroy=r}n=n.next}while(n!==i)}}catch(e){Q(t,t.return,e)}}function zc(e,t,n){try{var r=t.updateQueue,i=r===null?null:r.lastEffect;if(i!==null){var a=i.next;r=a;do{if((r.tag&e)===e){var o=r.inst,s=o.destroy;if(s!==void 0){o.destroy=void 0,i=t;var c=n,l=s;try{l()}catch(e){Q(i,c,e)}}}r=r.next}while(r!==a)}}catch(e){Q(t,t.return,e)}}function Bc(e){var t=e.updateQueue;if(t!==null){var n=e.stateNode;try{Wa(t,n)}catch(t){Q(e,e.return,t)}}}function Vc(e,t,n){n.props=Hs(e.type,e.memoizedProps),n.state=e.memoizedState;try{n.componentWillUnmount()}catch(n){Q(e,t,n)}}function Hc(e,t){try{var n=e.ref;if(n!==null){switch(e.tag){case 26:case 27:case 5:var r=e.stateNode;break;case 30:r=e.stateNode;break;default:r=e.stateNode}typeof n==`function`?e.refCleanup=n(r):n.current=r}}catch(n){Q(e,t,n)}}function Uc(e,t){var n=e.ref,r=e.refCleanup;if(n!==null)if(typeof r==`function`)try{r()}catch(n){Q(e,t,n)}finally{e.refCleanup=null,e=e.alternate,e!=null&&(e.refCleanup=null)}else if(typeof n==`function`)try{n(null)}catch(n){Q(e,t,n)}else n.current=null}function Wc(e){var t=e.type,n=e.memoizedProps,r=e.stateNode;try{a:switch(t){case`button`:case`input`:case`select`:case`textarea`:n.autoFocus&&r.focus();break a;case`img`:n.src?r.src=n.src:n.srcSet&&(r.srcset=n.srcSet)}}catch(t){Q(e,e.return,t)}}function Gc(e,t,n){try{var r=e.stateNode;Fd(r,e.type,n,t),r[pt]=t}catch(t){Q(e,e.return,t)}}function Kc(e){return e.tag===5||e.tag===3||e.tag===26||e.tag===27&&Zd(e.type)||e.tag===4}function qc(e){a:for(;;){for(;e.sibling===null;){if(e.return===null||Kc(e.return))return null;e=e.return}for(e.sibling.return=e.return,e=e.sibling;e.tag!==5&&e.tag!==6&&e.tag!==18;){if(e.tag===27&&Zd(e.type)||e.flags&2||e.child===null||e.tag===4)continue a;e.child.return=e,e=e.child}if(!(e.flags&2))return e.stateNode}}function Jc(e,t,n){var r=e.tag;if(r===5||r===6)e=e.stateNode,t?(n.nodeType===9?n.body:n.nodeName===`HTML`?n.ownerDocument.body:n).insertBefore(e,t):(t=n.nodeType===9?n.body:n.nodeName===`HTML`?n.ownerDocument.body:n,t.appendChild(e),n=n._reactRootContainer,n!=null||t.onclick!==null||(t.onclick=an));else if(r!==4&&(r===27&&Zd(e.type)&&(n=e.stateNode,t=null),e=e.child,e!==null))for(Jc(e,t,n),e=e.sibling;e!==null;)Jc(e,t,n),e=e.sibling}function Yc(e,t,n){var r=e.tag;if(r===5||r===6)e=e.stateNode,t?n.insertBefore(e,t):n.appendChild(e);else if(r!==4&&(r===27&&Zd(e.type)&&(n=e.stateNode),e=e.child,e!==null))for(Yc(e,t,n),e=e.sibling;e!==null;)Yc(e,t,n),e=e.sibling}function Xc(e){var t=e.stateNode,n=e.memoizedProps;try{for(var r=e.type,i=t.attributes;i.length;)t.removeAttributeNode(i[0]);Pd(t,r,n),t[ft]=e,t[pt]=n}catch(t){Q(e,e.return,t)}}var Zc=!1,Qc=!1,$c=!1,el=typeof WeakSet==`function`?WeakSet:Set,tl=null;function nl(e,t){if(e=e.containerInfo,Rd=sp,e=Dr(e),Or(e)){if(`selectionStart`in e)var n={start:e.selectionStart,end:e.selectionEnd};else a:{n=(n=e.ownerDocument)&&n.defaultView||window;var r=n.getSelection&&n.getSelection();if(r&&r.rangeCount!==0){n=r.anchorNode;var a=r.anchorOffset,o=r.focusNode;r=r.focusOffset;try{n.nodeType,o.nodeType}catch{n=null;break a}var s=0,c=-1,l=-1,u=0,d=0,f=e,p=null;b:for(;;){for(var m;f!==n||a!==0&&f.nodeType!==3||(c=s+a),f!==o||r!==0&&f.nodeType!==3||(l=s+r),f.nodeType===3&&(s+=f.nodeValue.length),(m=f.firstChild)!==null;)p=f,f=m;for(;;){if(f===e)break b;if(p===n&&++u===a&&(c=s),p===o&&++d===r&&(l=s),(m=f.nextSibling)!==null)break;f=p,p=f.parentNode}f=m}n=c===-1||l===-1?null:{start:c,end:l}}else n=null}n||={start:0,end:0}}else n=null;for(zd={focusedElem:e,selectionRange:n},sp=!1,tl=t;tl!==null;)if(t=tl,e=t.child,t.subtreeFlags&1028&&e!==null)e.return=t,tl=e;else for(;tl!==null;){switch(t=tl,o=t.alternate,e=t.flags,t.tag){case 0:if(e&4&&(e=t.updateQueue,e=e===null?null:e.events,e!==null))for(n=0;n title`))),Pd(o,r,n),o[ft]=e,Tt(o),r=o;break a;case`link`:var s=Vf(`link`,`href`,a).get(r+(n.href||``));if(s){for(var c=0;cg&&(o=g,g=h,h=o);var _=Tr(s,h),v=Tr(s,g);if(_&&v&&(p.rangeCount!==1||p.anchorNode!==_.node||p.anchorOffset!==_.offset||p.focusNode!==v.node||p.focusOffset!==v.offset)){var y=d.createRange();y.setStart(_.node,_.offset),p.removeAllRanges(),h>g?(p.addRange(y),p.extend(v.node,v.offset)):(y.setEnd(v.node,v.offset),p.addRange(y))}}}}for(d=[],p=s;p=p.parentNode;)p.nodeType===1&&d.push({element:p,left:p.scrollLeft,top:p.scrollTop});for(typeof s.focus==`function`&&s.focus(),s=0;sn?32:n,D.T=null,n=cu,cu=null;var o=iu,s=ou;if(ru=0,au=iu=null,ou=0,J&6)throw Error(i(331));var c=J;if(J|=4,Ml(o.current),wl(o,o.current,s,n),J=c,rd(0,!1),He&&typeof He.onPostCommitFiberRoot==`function`)try{He.onPostCommitFiberRoot(Ve,o)}catch{}return!0}finally{O.p=a,D.T=r,Bu(e,t)}}function Uu(e,t,n){t=H(n,t),t=Js(e.stateNode,t,2),e=La(e,t,2),e!==null&&(nt(e,2),nd(e))}function Q(e,t,n){if(e.tag===3)Uu(e,e,n);else for(;t!==null;){if(t.tag===3){Uu(t,e,n);break}else if(t.tag===1){var r=t.stateNode;if(typeof t.type.getDerivedStateFromError==`function`||typeof r.componentDidCatch==`function`&&(nu===null||!nu.has(r))){e=H(n,e),n=Ys(2),r=La(t,n,2),r!==null&&(Xs(n,r,t,e),nt(r,2),nd(r));break}}t=t.return}}function Wu(e,t,n){var r=e.pingCache;if(r===null){r=e.pingCache=new Il;var i=new Set;r.set(t,i)}else i=r.get(t),i===void 0&&(i=new Set,r.set(t,i));i.has(n)||(Vl=!0,i.add(n),e=Gu.bind(null,e,t,n),t.then(e,e))}function Gu(e,t,n){var r=e.pingCache;r!==null&&r.delete(t),e.pingedLanes|=e.suspendedLanes&n,e.warmLanes&=~n,Ll===e&&(X&n)===n&&(Ul===4||Ul===3&&(X&62914560)===X&&300>Me()-Ql?!(J&2)&&xu(e,0):Kl|=n,Jl===X&&(Jl=0)),nd(e)}function Ku(e,t){t===0&&(t=tt()),e=B(e,t),e!==null&&(nt(e,t),nd(e))}function qu(e){var t=e.memoizedState,n=0;t!==null&&(n=t.retryLane),Ku(e,n)}function Ju(e,t){var n=0;switch(e.tag){case 31:case 13:var r=e.stateNode,a=e.memoizedState;a!==null&&(n=a.retryLane);break;case 19:r=e.stateNode;break;case 22:r=e.stateNode._retryCache;break;default:throw Error(i(314))}r!==null&&r.delete(t),Ku(e,n)}function Yu(e,t){return Oe(e,t)}var Xu=null,Zu=null,Qu=!1,$u=!1,ed=!1,td=0;function nd(e){e!==Zu&&e.next===null&&(Zu===null?Xu=Zu=e:Zu=Zu.next=e),$u=!0,Qu||(Qu=!0,ld())}function rd(e,t){if(!ed&&$u){ed=!0;do for(var n=!1,r=Xu;r!==null;){if(!t)if(e!==0){var i=r.pendingLanes;if(i===0)var a=0;else{var o=r.suspendedLanes,s=r.pingedLanes;a=(1<<31-We(42|e)+1)-1,a&=i&~(o&~s),a=a&201326741?a&201326741|1:a?a|2:0}a!==0&&(n=!0,cd(r,a))}else a=X,a=Qe(r,r===Ll?a:0,r.cancelPendingCommit!==null||r.timeoutHandle!==-1),!(a&3)||$e(r,a)||(n=!0,cd(r,a));r=r.next}while(n);ed=!1}}function id(){ad()}function ad(){$u=Qu=!1;var e=0;td!==0&&Gd()&&(e=td);for(var t=Me(),n=null,r=Xu;r!==null;){var i=r.next,a=od(r,t);a===0?(r.next=null,n===null?Xu=i:n.next=i,i===null&&(Zu=n)):(n=r,(e!==0||a&3)&&($u=!0)),r=i}ru!==0&&ru!==5||rd(e,!1),td!==0&&(td=0)}function od(e,t){for(var n=e.suspendedLanes,r=e.pingedLanes,i=e.expirationTimes,a=e.pendingLanes&-62914561;0s)break;var u=c.transferSize,d=c.initiatorType;u&&Id(d)&&(c=c.responseEnd,o+=u*(c`u`?null:document;function xf(e,t,n){var r=bf;if(r&&typeof t==`string`&&t){var i=Wt(t);i=`link[rel="`+e+`"][href="`+i+`"]`,typeof n==`string`&&(i+=`[crossorigin="`+n+`"]`),hf.has(i)||(hf.add(i),e={rel:e,crossOrigin:n,href:t},r.querySelector(i)===null&&(t=r.createElement(`link`),Pd(t,`link`,e),Tt(t),r.head.appendChild(t)))}}function Sf(e){_f.D(e),xf(`dns-prefetch`,e,null)}function Cf(e,t){_f.C(e,t),xf(`preconnect`,e,t)}function wf(e,t,n){_f.L(e,t,n);var r=bf;if(r&&e&&t){var i=`link[rel="preload"][as="`+Wt(t)+`"]`;t===`image`&&n&&n.imageSrcSet?(i+=`[imagesrcset="`+Wt(n.imageSrcSet)+`"]`,typeof n.imageSizes==`string`&&(i+=`[imagesizes="`+Wt(n.imageSizes)+`"]`)):i+=`[href="`+Wt(e)+`"]`;var a=i;switch(t){case`style`:a=Af(e);break;case`script`:a=Pf(e)}mf.has(a)||(e=h({rel:`preload`,href:t===`image`&&n&&n.imageSrcSet?void 0:e,as:t},n),mf.set(a,e),r.querySelector(i)!==null||t===`style`&&r.querySelector(jf(a))||t===`script`&&r.querySelector(Ff(a))||(t=r.createElement(`link`),Pd(t,`link`,e),Tt(t),r.head.appendChild(t)))}}function Tf(e,t){_f.m(e,t);var n=bf;if(n&&e){var r=t&&typeof t.as==`string`?t.as:`script`,i=`link[rel="modulepreload"][as="`+Wt(r)+`"][href="`+Wt(e)+`"]`,a=i;switch(r){case`audioworklet`:case`paintworklet`:case`serviceworker`:case`sharedworker`:case`worker`:case`script`:a=Pf(e)}if(!mf.has(a)&&(e=h({rel:`modulepreload`,href:e},t),mf.set(a,e),n.querySelector(i)===null)){switch(r){case`audioworklet`:case`paintworklet`:case`serviceworker`:case`sharedworker`:case`worker`:case`script`:if(n.querySelector(Ff(a)))return}r=n.createElement(`link`),Pd(r,`link`,e),Tt(r),n.head.appendChild(r)}}}function Ef(e,t,n){_f.S(e,t,n);var r=bf;if(r&&e){var i=wt(r).hoistableStyles,a=Af(e);t||=`default`;var o=i.get(a);if(!o){var s={loading:0,preload:null};if(o=r.querySelector(jf(a)))s.loading=5;else{e=h({rel:`stylesheet`,href:e,"data-precedence":t},n),(n=mf.get(a))&&Rf(e,n);var c=o=r.createElement(`link`);Tt(c),Pd(c,`link`,e),c._p=new Promise(function(e,t){c.onload=e,c.onerror=t}),c.addEventListener(`load`,function(){s.loading|=1}),c.addEventListener(`error`,function(){s.loading|=2}),s.loading|=4,Lf(o,t,r)}o={type:`stylesheet`,instance:o,count:1,state:s},i.set(a,o)}}}function Df(e,t){_f.X(e,t);var n=bf;if(n&&e){var r=wt(n).hoistableScripts,i=Pf(e),a=r.get(i);a||(a=n.querySelector(Ff(i)),a||(e=h({src:e,async:!0},t),(t=mf.get(i))&&zf(e,t),a=n.createElement(`script`),Tt(a),Pd(a,`link`,e),n.head.appendChild(a)),a={type:`script`,instance:a,count:1,state:null},r.set(i,a))}}function Of(e,t){_f.M(e,t);var n=bf;if(n&&e){var r=wt(n).hoistableScripts,i=Pf(e),a=r.get(i);a||(a=n.querySelector(Ff(i)),a||(e=h({src:e,async:!0,type:`module`},t),(t=mf.get(i))&&zf(e,t),a=n.createElement(`script`),Tt(a),Pd(a,`link`,e),n.head.appendChild(a)),a={type:`script`,instance:a,count:1,state:null},r.set(i,a))}}function kf(e,t,n,r){var a=(a=he.current)?gf(a):null;if(!a)throw Error(i(446));switch(e){case`meta`:case`title`:return null;case`style`:return typeof n.precedence==`string`&&typeof n.href==`string`?(t=Af(n.href),n=wt(a).hoistableStyles,r=n.get(t),r||(r={type:`style`,instance:null,count:0,state:null},n.set(t,r)),r):{type:`void`,instance:null,count:0,state:null};case`link`:if(n.rel===`stylesheet`&&typeof n.href==`string`&&typeof n.precedence==`string`){e=Af(n.href);var o=wt(a).hoistableStyles,s=o.get(e);if(s||(a=a.ownerDocument||a,s={type:`stylesheet`,instance:null,count:0,state:{loading:0,preload:null}},o.set(e,s),(o=a.querySelector(jf(e)))&&!o._p&&(s.instance=o,s.state.loading=5),mf.has(e)||(n={rel:`preload`,as:`style`,href:n.href,crossOrigin:n.crossOrigin,integrity:n.integrity,media:n.media,hrefLang:n.hrefLang,referrerPolicy:n.referrerPolicy},mf.set(e,n),o||Nf(a,e,n,s.state))),t&&r===null)throw Error(i(528,``));return s}if(t&&r!==null)throw Error(i(529,``));return null;case`script`:return t=n.async,n=n.src,typeof n==`string`&&t&&typeof t!=`function`&&typeof t!=`symbol`?(t=Pf(n),n=wt(a).hoistableScripts,r=n.get(t),r||(r={type:`script`,instance:null,count:0,state:null},n.set(t,r)),r):{type:`void`,instance:null,count:0,state:null};default:throw Error(i(444,e))}}function Af(e){return`href="`+Wt(e)+`"`}function jf(e){return`link[rel="stylesheet"][`+e+`]`}function Mf(e){return h({},e,{"data-precedence":e.precedence,precedence:null})}function Nf(e,t,n,r){e.querySelector(`link[rel="preload"][as="style"][`+t+`]`)?r.loading=1:(t=e.createElement(`link`),r.preload=t,t.addEventListener(`load`,function(){return r.loading|=1}),t.addEventListener(`error`,function(){return r.loading|=2}),Pd(t,`link`,n),Tt(t),e.head.appendChild(t))}function Pf(e){return`[src="`+Wt(e)+`"]`}function Ff(e){return`script[async]`+e}function If(e,t,n){if(t.count++,t.instance===null)switch(t.type){case`style`:var r=e.querySelector(`style[data-href~="`+Wt(n.href)+`"]`);if(r)return t.instance=r,Tt(r),r;var a=h({},n,{"data-href":n.href,"data-precedence":n.precedence,href:null,precedence:null});return r=(e.ownerDocument||e).createElement(`style`),Tt(r),Pd(r,`style`,a),Lf(r,n.precedence,e),t.instance=r;case`stylesheet`:a=Af(n.href);var o=e.querySelector(jf(a));if(o)return t.state.loading|=4,t.instance=o,Tt(o),o;r=Mf(n),(a=mf.get(a))&&Rf(r,a),o=(e.ownerDocument||e).createElement(`link`),Tt(o);var s=o;return s._p=new Promise(function(e,t){s.onload=e,s.onerror=t}),Pd(o,`link`,r),t.state.loading|=4,Lf(o,n.precedence,e),t.instance=o;case`script`:return o=Pf(n.src),(a=e.querySelector(Ff(o)))?(t.instance=a,Tt(a),a):(r=n,(a=mf.get(o))&&(r=h({},n),zf(r,a)),e=e.ownerDocument||e,a=e.createElement(`script`),Tt(a),Pd(a,`link`,r),e.head.appendChild(a),t.instance=a);case`void`:return null;default:throw Error(i(443,t.type))}else t.type===`stylesheet`&&!(t.state.loading&4)&&(r=t.instance,t.state.loading|=4,Lf(r,n.precedence,e));return t.instance}function Lf(e,t,n){for(var r=n.querySelectorAll(`link[rel="stylesheet"][data-precedence],style[data-precedence]`),i=r.length?r[r.length-1]:null,a=i,o=0;o title`):null)}function Uf(e,t,n){if(n===1||t.itemProp!=null)return!1;switch(e){case`meta`:case`title`:return!0;case`style`:if(typeof t.precedence!=`string`||typeof t.href!=`string`||t.href===``)break;return!0;case`link`:if(typeof t.rel!=`string`||typeof t.href!=`string`||t.href===``||t.onLoad||t.onError)break;switch(t.rel){case`stylesheet`:return e=t.disabled,typeof t.precedence==`string`&&e==null;default:return!0}case`script`:if(t.async&&typeof t.async!=`function`&&typeof t.async!=`symbol`&&!t.onLoad&&!t.onError&&t.src&&typeof t.src==`string`)return!0}return!1}function Wf(e){return!(e.type===`stylesheet`&&!(e.state.loading&3))}function Gf(e,t,n,r){if(n.type===`stylesheet`&&(typeof r.media!=`string`||!1!==matchMedia(r.media).matches)&&!(n.state.loading&4)){if(n.instance===null){var i=Af(r.href),a=t.querySelector(jf(i));if(a){t=a._p,typeof t==`object`&&t&&typeof t.then==`function`&&(e.count++,e=Jf.bind(e),t.then(e,e)),n.state.loading|=4,n.instance=a,Tt(a);return}a=t.ownerDocument||t,r=Mf(r),(i=mf.get(i))&&Rf(r,i),a=a.createElement(`link`),Tt(a);var o=a;o._p=new Promise(function(e,t){o.onload=e,o.onerror=t}),Pd(a,`link`,r),n.instance=a}e.stylesheets===null&&(e.stylesheets=new Map),e.stylesheets.set(n,t),(t=n.state.preload)&&!(n.state.loading&3)&&(e.count++,n=Jf.bind(e),t.addEventListener(`load`,n),t.addEventListener(`error`,n))}}var Kf=0;function qf(e,t){return e.stylesheets&&e.count===0&&Xf(e,e.stylesheets),0Kf?50:800)+t);return e.unsuspend=n,function(){e.unsuspend=null,clearTimeout(r),clearTimeout(i)}}:null}function Jf(){if(this.count--,this.count===0&&(this.imgCount===0||!this.waitingForImages)){if(this.stylesheets)Xf(this,this.stylesheets);else if(this.unsuspend){var e=this.unsuspend;this.unsuspend=null,e()}}}var Yf=null;function Xf(e,t){e.stylesheets=null,e.unsuspend!==null&&(e.count++,Yf=new Map,t.forEach(Zf,e),Yf=null,Jf.call(e))}function Zf(e,t){if(!(t.state.loading&4)){var n=Yf.get(e);if(n)var r=n.get(null);else{n=new Map,Yf.set(e,n);for(var i=e.querySelectorAll(`link[data-precedence],style[data-precedence]`),a=0;a{function n(){if(!(typeof __REACT_DEVTOOLS_GLOBAL_HOOK__>`u`||typeof __REACT_DEVTOOLS_GLOBAL_HOOK__.checkDCE!=`function`))try{__REACT_DEVTOOLS_GLOBAL_HOOK__.checkDCE(n)}catch(e){console.error(e)}}n(),t.exports=h()})),_=c(u(),1),v=g(),y=class{baseUrl;actorUserId;constructor(e){this.baseUrl=e.baseUrl.replace(/\/$/,``),this.actorUserId=e.actorUserId.trim()}async login(e){return this.post(`/auth/login`,{email:e.email,password:e.password,device_fingerprint:b(),device_label:e.deviceLabel,trust_device:e.trustDevice})}async refresh(e){return this.post(`/auth/refresh`,{refresh_token:e.refreshToken})}async getInstallationStatus(){return(await this.request(`/installation/status`,{method:`GET`})).installation}async bootstrapOwner(e){return this.post(`/installation/bootstrap-owner`,{email:e.email,password:e.password,activation_payload:e.activationPayload,activation_signature:e.activationSignature||``})}async revokeAuthSession(e){await this.post(`/auth/sessions/revoke`,{user_id:e.userId,auth_session_id:e.authSessionId,reason:e.reason})}async listClusters(){return(await this.get(`/clusters`)).clusters??[]}async createCluster(e){return(await this.post(`/clusters/`,{actor_user_id:this.actorUserId,slug:e.slug,name:e.name,region:e.region||null,metadata:{}})).cluster}async updateCluster(e,t){return(await this.put(`/clusters/${e}`,{actor_user_id:this.actorUserId,name:t.name,status:t.status,region:t.region||null,metadata:t.metadata||{}})).cluster}async listClusterSummaries(){return(await this.get(`/cluster-admin-summaries`)).cluster_summaries??[]}async getClusterAuthority(e){return(await this.get(`/clusters/${e}/authority`)).authority_state}async updateClusterAuthority(e,t){return(await this.put(`/clusters/${e}/authority`,{actor_user_id:this.actorUserId,authority_state:t.authorityState,mutation_mode:t.mutationMode,notes:t.notes||null})).authority_state}async listNodes(e){return(await this.get(`/clusters/${e}/nodes`)).nodes??[]}async listNodeGroups(e){return(await this.get(`/clusters/${e}/node-groups`)).node_groups??[]}async createNodeGroup(e,t){return(await this.post(`/clusters/${e}/node-groups`,{actor_user_id:this.actorUserId,parent_group_id:t.parentGroupId||null,name:t.name,description:t.description||null,sort_order:t.sortOrder||0,metadata:{}})).node_group}async assignNodeGroup(e,t,n){return(await this.put(`/clusters/${e}/nodes/${t}/group`,{actor_user_id:this.actorUserId,group_id:n||null})).node}async disableMembership(e,t,n){await this.post(`/clusters/${e}/nodes/${t}/membership/disable`,{actor_user_id:this.actorUserId,reason:n})}async attachExistingNode(e,t,n){return(await this.post(`/clusters/${e}/nodes/${t}/membership/attach`,{actor_user_id:this.actorUserId,roles:n})).node}async revokeNodeIdentity(e,t,n){await this.post(`/clusters/${e}/nodes/${t}/identity/revoke`,{actor_user_id:this.actorUserId,reason:n})}async deleteClusterNode(e,t,n){await this.delete(`/clusters/${e}/nodes/${t}`,{actor_user_id:this.actorUserId,reason:n})}async listJoinRequests(e){return(await this.get(`/clusters/${e}/join-requests`)).join_requests??[]}async createJoinToken(e,t){let n=new Date(Date.now()+Math.max(t.ttlHours,1)*60*60*1e3).toISOString();return(await this.post(`/clusters/${e}/join-tokens`,{actor_user_id:this.actorUserId,scope:t.scope,expires_at:n,max_uses:Math.max(t.maxUses,1)})).join_token}async listJoinTokens(e){return(await this.get(`/clusters/${e}/join-tokens`)).join_tokens??[]}async revokeJoinToken(e,t){return(await this.post(`/clusters/${e}/join-tokens/${t}/revoke`,{actor_user_id:this.actorUserId})).join_token}async approveJoinRequest(e,t){await this.post(`/clusters/${e}/join-requests/${t}/approve`,{actor_user_id:this.actorUserId,ownership_type:`platform_managed`})}async rejectJoinRequest(e,t,n){await this.post(`/clusters/${e}/join-requests/${t}/reject`,{actor_user_id:this.actorUserId,reason:n})}async listNodeRoles(e,t){return(await this.get(`/clusters/${e}/nodes/${t}/roles`)).role_assignments??[]}async assignRole(e,t,n,r){await this.setRoleStatus(e,t,n,`active`,r)}async setRoleStatus(e,t,n,r,i){await this.post(`/clusters/${e}/nodes/${t}/roles`,{actor_user_id:this.actorUserId,organization_id:i||null,role:n,status:r,policy:{}})}async listWorkloadStatuses(e,t){return(await this.get(`/clusters/${e}/nodes/${t}/workloads/status`)).workload_statuses??[]}async listDesiredWorkloads(e,t){return(await this.get(`/clusters/${e}/nodes/${t}/workloads/desired`)).desired_workloads??[]}async listNodeHeartbeats(e,t,n=100){return(await this.get(`/clusters/${e}/nodes/${t}/heartbeats?limit=${n}`)).heartbeats??[]}async listNodeTelemetry(e,t,n=120){return(await this.get(`/clusters/${e}/nodes/${t}/telemetry?limit=${n}`)).telemetry??[]}async listReleaseVersions(e,t=`rap-node-agent`,n=`dev`){let r=new URLSearchParams({product:t,channel:n});return(await this.get(`/clusters/${e}/updates/releases?${r.toString()}`)).release_versions??[]}async getNodeUpdatePlan(e,t,n){let r=new URLSearchParams({product:n.product||`rap-node-agent`,current_version:n.currentVersion||``,os:n.os||`linux`,arch:n.arch||`amd64`,install_type:n.installType||`docker`,channel:n.channel||`dev`});return(await this.get(`/clusters/${e}/nodes/${t}/updates/plan?${r.toString()}`)).node_update_plan}async upsertNodeUpdatePolicy(e,t,n){return(await this.put(`/clusters/${e}/nodes/${t}/updates/policy`,{actor_user_id:this.actorUserId,product:n.product,channel:n.channel||`dev`,target_version:n.targetVersion??null,strategy:n.strategy||`rolling`,enabled:n.enabled??!0,rollback_allowed:n.rollbackAllowed??!0,health_window_seconds:n.healthWindowSeconds||180})).node_update_policy}async listNodeUpdateStatuses(e,t,n=80){let r=new URLSearchParams({actor_user_id:this.actorUserId,limit:String(n)});return(await this.get(`/clusters/${e}/nodes/${t}/updates/statuses?${r.toString()}`)).node_update_statuses??[]}async listFabricTestingFlags(){return(await this.get(`/fabric/testing-flags`)).testing_flags??[]}async updateFabricTestingFlag(e){return(await this.put(`/fabric/testing-flags`,{actor_user_id:this.actorUserId,scope_type:e.scopeType,scope_id:e.scopeId||null,cluster_id:e.clusterId||null,enabled:e.enabled,telemetry_enabled:e.telemetryEnabled,synthetic_links_enabled:e.syntheticLinksEnabled,history_retention_hours:e.historyRetentionHours,metadata:e.metadata||{}})).testing_flag}async setDesiredWorkload(e,t,n,r){await this.put(`/clusters/${e}/nodes/${t}/workloads/${n}/desired`,{actor_user_id:this.actorUserId,desired_state:r.desiredState,version:r.version||null,runtime_mode:r.runtimeMode,artifact_ref:null,config:r.config,environment:r.environment})}async listMeshLinks(e){return(await this.get(`/clusters/${e}/mesh/links`)).mesh_links??[]}async getNodeSyntheticMeshConfig(e,t){return(await this.get(`/clusters/${e}/nodes/${t}/mesh/synthetic-config`)).synthetic_mesh_config}async listQoSPolicies(e){return(await this.get(`/clusters/${e}/mesh/qos-policies`)).qos_policies??[]}async listFabricEntryPoints(e){return(await this.get(`/clusters/${e}/fabric/entry-points`)).entry_points??[]}async createFabricEntryPoint(e,t){return(await this.post(`/clusters/${e}/fabric/entry-points`,{actor_user_id:this.actorUserId,name:t.name,status:`active`,endpoint_type:t.endpointType||`client_access`,public_endpoint:t.publicEndpoint||null,policy:t.policy||{},metadata:t.metadata||{}})).entry_point}async listFabricEntryPointNodes(e,t){return(await this.get(`/clusters/${e}/fabric/entry-points/${t}/nodes`)).entry_point_nodes??[]}async setFabricEntryPointNode(e,t,n,r={}){return(await this.put(`/clusters/${e}/fabric/entry-points/${t}/nodes/${n}`,{actor_user_id:this.actorUserId,status:r.status||`active`,priority:r.priority||100,metadata:r.metadata||{}})).entry_point_node}async listFabricEgressPools(e){return(await this.get(`/clusters/${e}/fabric/egress-pools`)).egress_pools??[]}async createFabricEgressPool(e,t){return(await this.post(`/clusters/${e}/fabric/egress-pools`,{actor_user_id:this.actorUserId,name:t.name,status:`active`,description:t.description||null,route_scope:t.routeScope||{},policy:t.policy||{},metadata:t.metadata||{}})).egress_pool}async listFabricEgressPoolNodes(e,t){return(await this.get(`/clusters/${e}/fabric/egress-pools/${t}/nodes`)).egress_pool_nodes??[]}async setFabricEgressPoolNode(e,t,n,r={}){return(await this.put(`/clusters/${e}/fabric/egress-pools/${t}/nodes/${n}`,{actor_user_id:this.actorUserId,status:r.status||`active`,priority:r.priority||100,metadata:r.metadata||{}})).egress_pool_node}async listVPNConnections(e){return(await this.get(`/clusters/${e}/vpn-connections`)).vpn_connections??[]}async createVPNConnection(e,t){return(await this.post(`/clusters/${e}/vpn-connections`,{actor_user_id:this.actorUserId,organization_id:t.organizationId,name:t.name,target_endpoint:t.targetEndpoint,protocol_family:t.protocolFamily,credential_ref:t.credentialRef||null,mode:`single_active`,desired_state:t.desiredState,allowed_node_policy:t.allowedNodePolicy,routing_usage:t.routingUsage,route_policy:t.routePolicy,qos_policy:t.qosPolicy,placement_policy:t.placementPolicy,metadata:{}})).vpn_connection}async updateVPNConnectionDesiredState(e,t,n){return(await this.put(`/clusters/${e}/vpn-connections/${t}/desired-state`,{actor_user_id:this.actorUserId,desired_state:n})).vpn_connection}async getActiveVPNLease(e,t){try{return(await this.get(`/clusters/${e}/vpn-connections/${t}/leases/active`)).lease}catch{return null}}async getVPNPacketStats(e,t){return(await this.get(`/clusters/${e}/vpn-connections/${t}/tunnel/stats`)).vpn_packet_stats??{}}async getVPNClientDiagnosticStatus(e,t){if(!t.trim())return null;try{return(await this.get(`/clusters/${e}/vpn/client-diagnostics/${encodeURIComponent(t.trim())}/status`)).vpn_client_diagnostic_status??null}catch{return null}}async listVPNClientDiagnosticStatuses(e){return(await this.get(`/clusters/${e}/vpn/client-diagnostics`)).vpn_client_diagnostic_statuses??[]}async enqueueVPNClientDiagnosticCommand(e,t,n){return(await this.post(`/clusters/${e}/vpn/client-diagnostics/${encodeURIComponent(t.trim())}/commands`,n)).vpn_client_diagnostic_command}async expireStaleVPNLeases(e){return(await this.post(`/clusters/${e}/vpn-connection-leases/expire-stale`,{actor_user_id:this.actorUserId})).expired_leases??[]}async listAudit(e){return(await this.get(`/clusters/${e}/audit?limit=100`)).audit_events??[]}clusterEventsURL(e){return`${this.baseUrl}/clusters/${encodeURIComponent(e)}/events?actor_user_id=${encodeURIComponent(this.actorUserId)}`}async getOrganizationAdminSummary(e){return(await this.get(`/organizations/${e}/admin-summary`)).admin_summary}async listOrganizations(){return(await this.request(`/organizations?user_id=${encodeURIComponent(this.actorUserId)}`,{method:`GET`})).organizations??[]}async createOrganization(e){return(await this.post(`/organizations/`,{actor_user_id:this.actorUserId,slug:e.slug,name:e.name,metadata:e.metadata||{}})).organization}async listUsers(){return(await this.get(`/users/`)).users??[]}async createUser(e){return(await this.post(`/users/`,{actor_user_id:this.actorUserId,email:e.email,password:e.password,platform_role:e.platformRole||`user`})).user}async listOrganizationMemberships(e){return(await this.request(`/organizations/${e}/memberships?user_id=${encodeURIComponent(this.actorUserId)}`,{method:`GET`})).memberships??[]}async addOrganizationMembership(e,t){return(await this.post(`/organizations/${e}/memberships`,{actor_user_id:this.actorUserId,user_id:t.userId,role_id:t.roleId})).membership}async listResources(e){let t=new URLSearchParams({user_id:this.actorUserId});return e&&t.set(`organization_id`,e),(await this.request(`/resources?${t.toString()}`,{method:`GET`})).resources??[]}async createResource(e){return(await this.post(`/resources/`,{actor_user_id:this.actorUserId,organization_id:e.organizationId,name:e.name,address:e.address,protocol:e.protocol||`rdp`,secret_ref:e.secretRef||null,certificate_verification_mode:e.certificateVerificationMode||`strict`,render_quality_profile:e.renderQualityProfile||`balanced`,clipboard_mode:e.clipboardMode||`disabled`,file_transfer_mode:e.fileTransferMode||`disabled`,metadata:e.metadata||{}})).resource}async upsertResourceSecret(e,t){await this.put(`/resources/${e}/secret`,{actor_user_id:this.actorUserId,payload:{username:t.username||``,password:t.password||``,domain:t.domain||``},metadata:{source:`web-admin`}})}async get(e){let t=e.includes(`?`)?`&`:`?`;return this.request(`${e}${t}actor_user_id=${encodeURIComponent(this.actorUserId)}`,{method:`GET`})}async post(e,t){return this.request(e,{method:`POST`,headers:{"Content-Type":`application/json`},body:JSON.stringify(t)})}async put(e,t){return this.request(e,{method:`PUT`,headers:{"Content-Type":`application/json`},body:JSON.stringify(t)})}async delete(e,t){return this.request(e,{method:`DELETE`,headers:{"Content-Type":`application/json`},body:JSON.stringify(t)})}async request(e,t){let n=await fetch(`${this.baseUrl}${e}`,t);if(!n.ok){let e=`Запрос завершился ошибкой HTTP ${n.status}`;try{let t=await n.json();e=t.error?.fallback_message||t.error?.code||e}catch{}throw Error(e)}return await n.json()}};function b(){let e=`rap.webAdmin.deviceFingerprint`,t=localStorage.getItem(e);if(t)return t;let n=`web-admin-${x()}`;return localStorage.setItem(e,n),n}function x(){if(typeof globalThis.crypto?.randomUUID==`function`)return globalThis.crypto.randomUUID();if(typeof globalThis.crypto?.getRandomValues==`function`){let e=new Uint8Array(16);globalThis.crypto.getRandomValues(e),e[6]=e[6]&15|64,e[8]=e[8]&63|128;let t=Array.from(e,e=>e.toString(16).padStart(2,`0`)).join(``);return`${t.slice(0,8)}-${t.slice(8,12)}-${t.slice(12,16)}-${t.slice(16,20)}-${t.slice(20)}`}return`${Date.now().toString(36)}-${Math.random().toString(36).slice(2)}`}var S=o((e=>{var t=Symbol.for(`react.transitional.element`),n=Symbol.for(`react.fragment`);function r(e,n,r){var i=null;if(r!==void 0&&(i=``+r),n.key!==void 0&&(i=``+n.key),`key`in n)for(var a in r={},n)a!==`key`&&(r[a]=n[a]);else r=n;return n=r.ref,{$$typeof:t,type:e,key:i,ref:n===void 0?null:n,props:r}}e.Fragment=n,e.jsx=r,e.jsxs=r})),C=o(((e,t)=>{t.exports=S()}))(),w={baseUrl:`rap.webAdmin.baseUrl`,actorUserId:`rap.webAdmin.actorUserId`,auth:`rap.webAdmin.auth`,language:`rap.webAdmin.language`,vpnDiagnosticDeviceId:`rap.webAdmin.vpnDiagnosticDeviceId`},ee=`/api/v1`,te=`http://192.168.200.61:8080/api/v1`,ne=[`entry-node`,`relay-node`,`core-mesh`,`rdp-worker`,`vnc-worker`,`vpn-exit`,`vpn-connector`,`file-storage-cache`,`update-cache`,`video-relay`],T={"entry-node":`Entry node`,"relay-node":`Relay node`,"core-mesh":`Mesh core`,"rdp-worker":`RDP worker`,"vnc-worker":`VNC worker`,"vpn-exit":`VPN exit`,"vpn-connector":`VPN connector`,"file-storage-cache":`File/cache storage`,"update-cache":`Update cache`,"video-relay":`Video relay`},re={"entry-node":[`can_accept_client_ingress`],"relay-node":[`mesh_rendezvous_relay_control_contract`,`mesh_peer_connection_manager`],"core-mesh":[`native_node_agent`,`mesh_peer_connection_manager`,`mesh_listener_diagnostics`],"rdp-worker":[`can_run_rdp_worker`],"vnc-worker":[`can_run_vnc_worker`],"vpn-exit":[`can_run_vpn_exit`],"vpn-connector":[`can_run_vpn_connector`],"file-storage-cache":[`can_run_file_cache`],"update-cache":[`can_run_update_cache`],"video-relay":[`can_run_video_relay`]},ie=[{id:`command`,ru:`Обзор`,en:`Command`},{id:`clusters`,ru:`Кластеры`,en:`Clusters`},{id:`cluster-settings`,ru:`Настройки кластера`,en:`Cluster Settings`},{id:`nodes`,ru:`Узлы`,en:`Nodes`},{id:`enrollment`,ru:`Новый узел`,en:`New Node`},{id:`roles`,ru:`Роли`,en:`Roles`},{id:`workloads`,ru:`Сервисы`,en:`Workloads`},{id:`fabric`,ru:`Связи Fabric`,en:`Fabric Links`},{id:`vpn`,ru:`VPN Control`,en:`VPN Control`},{id:`servers`,ru:`Серверы`,en:`Servers`},{id:`org-safe`,ru:`Организации`,en:`Organizations`},{id:`audit`,ru:`Аудит`,en:`Audit`}];function E(e){if(!e||typeof e!=`object`)return null;let t=e;return typeof t.userId!=`string`||typeof t.email!=`string`||typeof t.authSessionId!=`string`||typeof t.accessToken!=`string`||typeof t.refreshToken!=`string`||typeof t.accessTokenExpiresAt!=`string`||typeof t.refreshTokenExpiresAt!=`string`||!t.userId||!t.refreshToken?null:{userId:t.userId,email:t.email,authSessionId:t.authSessionId,accessToken:t.accessToken,refreshToken:t.refreshToken,accessTokenExpiresAt:t.accessTokenExpiresAt,refreshTokenExpiresAt:t.refreshTokenExpiresAt}}function ae(e){let t=Date.parse(e);return!Number.isFinite(t)||t<=Date.now()}function oe(){try{let e=localStorage.getItem(w.auth);if(!e)return null;let t=E(JSON.parse(e));return!t||ae(t.refreshTokenExpiresAt)?null:t}catch{return null}}var se={ttlHours:24,maxUses:1,roles:[`core-mesh`],nodeName:``,nodeGroupId:``,ownershipType:`platform_managed`,purpose:``,installMode:`docker`,dockerImage:`rap-node-agent:dev-enrollment-bootstrap-smoke`,dockerContainerName:``,dockerNetwork:`host`,windowsStartupMode:`auto`,windowsInstallDir:``,windowsNodeAgentSHA256:``,linuxInstallDir:``,linuxNodeAgentSHA256:``,meshListenAddr:`:19131`,meshListenPortMode:`auto`,meshListenAutoPortStart:19131,meshListenAutoPortEnd:19231,meshAdvertiseEndpoint:``,meshAdvertiseTransport:`direct_http`,meshConnectivityMode:`private_lan`,meshNATType:`none`,meshRegion:`docker-test`,controlPlaneEndpoint:``,artifactEndpoints:``,dockerImageArtifactSHA256:``,pullImage:!1,replace:!0,syntheticRuntime:!0},ce={ru:{productOwner:`Владелец продукта`,controlPlane:`Панель управления`,sideText:`Главная панель владельца платформы для кластеров, узлов, доверия, ролей и безопасного desired state.`,signInTitle:`Вход`,signInText:`Введите учетные данные.`,bootstrapTitle:`Первый владелец`,bootstrapText:`Пустая установка принимает только подписанную активацию продукта.`,activationPayload:`Activation manifest JSON`,activationSignature:`Подпись manifest`,createOwner:`Создать владельца`,creatingOwner:`Создание...`,ownerCreated:`Владелец создан. Теперь можно войти.`,installationLocked:`Установка уже активирована`,insecureBootstrapDisabled:`Insecure bootstrap выключен. Нужна strict-активация с ключом продукта.`,email:`Логин`,password:`Пароль`,backendApi:`Backend API`,useLocalProxy:`Использовать локальный /api/v1 proxy`,language:`Язык`,deviceLabel:`Устройство`,rememberMe:`Запомнить меня`,trustDevice:`Доверять этому устройству`,signIn:`Войти`,signingIn:`Вход...`,logout:`Выйти`,profile:`Профиль`,refresh:`Обновить`,refreshing:`Обновление...`,autoRefresh:`Автообновление`,lastRefresh:`Данные обновлены`,activeCluster:`Активный кластер`,slugLabel:`Технический код`,slugHelp:`Slug — постоянный короткий технический идентификатор кластера для URL, скриптов, логов и интеграций. Его лучше не менять после создания.`,clusterCatalog:`Каталог кластеров`,clusterCatalogText:`Список реальных кластеров из Control Plane. Выберите активный кластер или раскройте карточку для подробностей.`,makeActive:`Сделать активным`,openSettings:`Открыть настройки`,selected:`Выбран`,createCluster:`Создать кластер`,clusterDetails:`Подробнее`,consoleTitle:`Панель владельца платформы`,boundary:`WEB является только представлением. Решения кластера проходят через Control Plane API, PostgreSQL как source of truth и аудит.`,noLoginError:`Войдите как владелец продукта или администратор платформы, чтобы загрузить панель.`,accessDenied:`Доступ к этой панели запрещен.`,sessionMode:`Режим сессии`,sessionModeAdmin:`Админ`,sessionModeUser:`Пользователь`,sessionRefreshedAt:`Сессия обновлена`,emptyLiveTitle:`Кластер пока пустой`,emptyLiveText:`Это реальные данные, не заглушка: в выбранном кластере ещё нет одобренных node-agent узлов. Создайте join token, запустите rap-node-agent и подтвердите join request.`,realDataNote:`Показываются только данные из PostgreSQL/Control Plane. Если значения нулевые, значит соответствующих узлов, ролей или сервисов пока нет.`,signedInAs:`Вход выполнен`,actorUser:`Actor user`,testMode:`Тестирование`,testModeText:`Включает тестовую телеметрию и синтетические наблюдения связей. Это не production mesh runtime.`,platformTestFlag:`Тестирование сервера`,nodeTelemetry:`Телеметрия узла`,heartbeatHistory:`История heartbeat`,noTelemetry:`Телеметрии пока нет`,enableTelemetry:`Включить телеметрию`,enableSyntheticLinks:`Включить тестовые связи`,saveTestFlag:`Сохранить флаг`,nodeManagement:`Управление узлом`,nodeScope:`Область просмотра`,currentClusterNodes:`Узлы активного кластера`,allNodes:`Все узлы платформы`,showAllPlatformNodes:`Показать все узлы платформы`,currentClusterMembership:`Участие в активном кластере`,clusterMemberships:`Участие по кластерам`,notMemberOfActiveCluster:`не состоит`,nodeIdentity:`Физическая идентичность узла`,activeClusterScope:`Область активного кластера`,activeClusterScopeText:`Один физический узел может состоять в нескольких кластерах. Роли и desired-сервисы ниже относятся только к выбранному активному кластеру.`,capabilityConfirmed:`способность подтверждена heartbeat`,capabilityMissing:`способность не заявлена узлом`,capabilityUnknown:`способность не подтверждена: нет heartbeat`,nodeGlobalInventoryText:`Один физический узел показан один раз. Участие и роли остаются кластерными: в разных кластерах этот же узел может иметь разные назначения.`,nodeSearch:`Поиск узлов`,groupNodesBy:`Группировать`,groupByMembership:`по участию`,groupByHealth:`по здоровью`,groupByOwnership:`по владению`,groupByClusterCount:`по числу кластеров`,nodeGroups:`Группы узлов`,nodeGroupTree:`Дерево групп`,nodeGroupFilter:`Фильтр по группе`,allNodeGroups:`Все группы`,nodeGroupCreatePanel:`Создание группы`,nodeGroupName:`Название группы`,parentNodeGroup:`Родительская группа`,rootNodeGroup:`Корень`,ungroupedNodes:`Без группы`,createNodeGroup:`Создать группу`,createSubgroup:`Создать подгруппу`,collapseGroup:`Свернуть`,expandGroup:`Развернуть`,assignNodeGroup:`Переместить в группу`,removeFromNodeGroup:`Убрать из группы`,connectExistingNode:`Подключить к активному кластеру`,connectExistingNodeTitle:`Подключить существующий узел`,connectExistingNodeText:`Будет создано или повторно включено участие конкретного физического узла в активном кластере. Роли ниже назначаются только в этом кластере.`,connectWithRoles:`Подключить с ролями`,nodeDetails:`Сведения`,manageNode:`Настроить`,nodeFunctions:`Функции узла`,nodeFunctionsText:`Одна строка управляет функцией целиком: роль задает разрешение в активном кластере, desired-сервис задает запуск, observed показывает факт от node-agent.`,rolePermission:`Разрешение`,permissionGranted:`разрешено`,permissionDenied:`нет разрешения`,organizationScopeForEnable:`Область организации для новых включений, опционально`,clusterWideRolePlaceholder:`пусто = роль на весь кластер`,desiredRuntime:`Желаемое состояние`,observedRuntime:`Фактически`,enableFunction:`Включить функцию`,disableFunction:`Выключить функцию`,close:`Закрыть`,nodeBriefList:`Краткий список узлов`,noActiveClusterMembership:`Узел не входит в активный кластер`,nodeBriefListHelp:`Список сгруппирован деревом активного кластера. Полные сведения, управление, роли, сервисы и статистика открываются из строки узла.`,nodeSearchPlaceholder:`имя, ключ, кластер, статус`,nodeGroupInventoryText:`Группы — это кластерная инвентарная структура. Перенос узла в группу меняет только его размещение внутри активного кластера, не роли и не членство.`,nodeGroupCreated:`Группа узлов создана.`,noNodesTitle:`Нет узлов`,noNodesByFilter:`По текущему фильтру узлы не найдены.`,cancel:`Отмена`,alreadyMember:`Уже в активном кластере`,revokedMembership:`Участие отозвано`,addNode:`Подключить узел`,addNodeText:`Подключение существующего физического узла к активному кластеру выполняется из списка узлов: включите общий режим и нажмите «Подключить к активному кластеру».`,joinTokenTitle:`Создать новый Docker-узел`,joinTokenText:`Сначала создается одноразовый install token и Docker install profile. Затем команда запускается на Docker-хосте, агент отправляет заявку, а владелец платформы подтверждает ее.`,ttlHours:`Срок действия, часов`,ttlHelp:`Через это время token станет недействительным, даже если им никто не воспользовался. Для ручного подключения обычно достаточно 1–24 часов.`,maxUses:`Максимум использований`,maxUsesHelp:`Сколько node-agent смогут использовать этот token. Самый безопасный вариант — 1 token на 1 новый узел.`,tokenPurpose:`Назначение token`,nodeOwnership:`Тип владения узлом`,suggestedRoles:`Разрешенные/ожидаемые роли`,generatedScope:`Сгенерированная область действия`,generatedScopeHelp:`JSON формируется автоматически из настроек выше. Оператор не должен писать его руками, чтобы не ошибиться синтаксисом или областью доступа.`,manualApprovalRequired:`Подтверждение заявки вручную обязательно`,nodeRoles:`Роли узла`,desiredServices:`Желаемые сервисы`,observedServices:`Наблюдаемые сервисы`,noRoles:`Ролей пока нет`,noServices:`Сервисов пока нет`,manageInCluster:`Управлять в кластере`,rolesAndServices:`Роли и сервисы`,links:`Связи`,fabricMap:`Карта трафика Fabric`,fabricIngressLayer:`Входы`,fabricNodeLayer:`Узлы кластера`,fabricEgressLayer:`Выходные зоны`,observedPeerLinks:`Наблюдаемые связи`,placementIntent:`control-plane назначение`,fabricEntryPoints:`Точки входа`,fabricEntryPointHelp:`Логические внешние входы в кластер. Они скрывают конкретные узлы от организаций и клиентов.`,fabricEgressPools:`Выходные зоны`,fabricEgressPoolHelp:`Логические выходы к внешним сетям, например “Офис Москва”. Организации используют зону, а не конкретный узел.`,endpointName:`Название`,publicEndpoint:`Публичный адрес`,endpointType:`Тип входа`,description:`Описание`,routeScope:`Область маршрутов JSON`,createEntryPoint:`Создать точку входа`,createEgressPool:`Создать выходную зону`,endpointNodes:`Назначенные узлы`,assignEndpointNode:`Назначить узел`,selectNode:`Выберите узел`,assignedNodesEmpty:`Узлы пока не назначены`,entryPointsEmpty:`Точки входа пока не созданы.`,egressPoolsEmpty:`Выходные зоны пока не созданы.`,addressNotSet:`адрес не задан`,descriptionNotSet:`описание не задано`,servicePlacement:`Размещение сервисов`,trafficFlow:`Потоки между узлами`,organizationTestFlag:`Тестирование организации`,organizationId:`ID организации`,saveOrganizationFlag:`Сохранить флаг организации`,noLinks:`Связей пока нет`,recentHeartbeats:`Последние heartbeat`,memory:`Память`,cpu:`Процессор`,processes:`Процессы`},en:{productOwner:`Product Owner`,controlPlane:`Control Plane`,sideText:`Full platform-owner panel for clusters, nodes, trust, placement, and safe service desired state.`,signInTitle:`Sign in`,signInText:`Enter your credentials.`,bootstrapTitle:`First owner`,bootstrapText:`An empty installation accepts only a signed product activation.`,activationPayload:`Activation manifest JSON`,activationSignature:`Manifest signature`,createOwner:`Create owner`,creatingOwner:`Creating...`,ownerCreated:`Owner created. You can sign in now.`,installationLocked:`Installation is already active`,insecureBootstrapDisabled:`Insecure bootstrap is disabled. Strict product-key activation is required.`,email:`Login`,password:`Password`,backendApi:`Backend API`,useLocalProxy:`Use local /api/v1 proxy`,language:`Language`,deviceLabel:`Device`,rememberMe:`Remember me`,trustDevice:`Trust this device`,signIn:`Sign in`,signingIn:`Signing in...`,logout:`Logout`,profile:`Profile`,refresh:`Refresh`,refreshing:`Refreshing...`,autoRefresh:`Auto refresh`,lastRefresh:`Data refreshed`,activeCluster:`Active cluster`,slugLabel:`Technical code`,slugHelp:`Slug is a stable short technical identifier for URLs, scripts, logs, and integrations. It should generally not change after creation.`,clusterCatalog:`Cluster catalog`,clusterCatalogText:`Real clusters from the Control Plane. Select the active cluster or expand a card for details.`,makeActive:`Make active`,openSettings:`Open settings`,selected:`Selected`,createCluster:`Create cluster`,clusterDetails:`Details`,consoleTitle:`Platform Owner Console`,boundary:`WEB is presentation only. Cluster decisions go through Control Plane APIs, PostgreSQL source of truth, and audit.`,noLoginError:`Sign in as a product owner or platform administrator to load the panel.`,accessDenied:`Access to this panel is denied.`,sessionMode:`Session mode`,sessionModeAdmin:`Admin`,sessionModeUser:`User`,sessionRefreshedAt:`Session refreshed`,emptyLiveTitle:`Cluster has no live nodes yet`,emptyLiveText:`These are real values, not placeholders: the selected cluster has no approved node-agent nodes yet. Create a join token, run rap-node-agent, and approve the join request.`,realDataNote:`Only PostgreSQL/Control Plane data is shown. Zero values mean the corresponding nodes, roles, or services do not exist yet.`,signedInAs:`Signed in`,actorUser:`Actor user`,testMode:`Testing`,testModeText:`Enables test telemetry and synthetic link observations. This is not production mesh runtime.`,platformTestFlag:`Server testing`,nodeTelemetry:`Node telemetry`,heartbeatHistory:`Heartbeat history`,noTelemetry:`No telemetry yet`,enableTelemetry:`Enable telemetry`,enableSyntheticLinks:`Enable test links`,saveTestFlag:`Save flag`,nodeManagement:`Node management`,nodeScope:`View scope`,currentClusterNodes:`Active cluster nodes`,allNodes:`All platform nodes`,showAllPlatformNodes:`Show all platform nodes`,currentClusterMembership:`Active cluster membership`,clusterMemberships:`Cluster memberships`,notMemberOfActiveCluster:`not a member`,nodeIdentity:`Physical node identity`,activeClusterScope:`Active cluster scope`,activeClusterScopeText:`One physical node may belong to multiple clusters. Roles and desired services below belong only to the selected active cluster.`,capabilityConfirmed:`capability confirmed by heartbeat`,capabilityMissing:`capability not reported by node`,capabilityUnknown:`capability unconfirmed: no heartbeat`,nodeGlobalInventoryText:`Each physical node is shown once. Membership and roles remain cluster-scoped, so the same node may have different assignments in different clusters.`,nodeSearch:`Node search`,groupNodesBy:`Group by`,groupByMembership:`membership`,groupByHealth:`health`,groupByOwnership:`ownership`,groupByClusterCount:`cluster count`,nodeGroups:`Node groups`,nodeGroupTree:`Group tree`,nodeGroupFilter:`Group filter`,allNodeGroups:`All groups`,nodeGroupCreatePanel:`Create group`,nodeGroupName:`Group name`,parentNodeGroup:`Parent group`,rootNodeGroup:`Root`,ungroupedNodes:`Ungrouped`,createNodeGroup:`Create group`,createSubgroup:`Create subgroup`,collapseGroup:`Collapse`,expandGroup:`Expand`,assignNodeGroup:`Move to group`,removeFromNodeGroup:`Remove from group`,connectExistingNode:`Connect to active cluster`,connectExistingNodeTitle:`Connect existing node`,connectExistingNodeText:`This creates or re-enables membership for one concrete physical node in the active cluster. Roles below are assigned only in this cluster.`,connectWithRoles:`Connect with roles`,nodeDetails:`Details`,manageNode:`Configure`,nodeFunctions:`Node functions`,nodeFunctionsText:`One row controls the whole function: role grants permission in the active cluster, desired service requests runtime start, observed state reports node-agent facts.`,rolePermission:`Permission`,permissionGranted:`granted`,permissionDenied:`not allowed`,organizationScopeForEnable:`Organization scope for new enables, optional`,clusterWideRolePlaceholder:`empty = cluster-wide role`,desiredRuntime:`Desired state`,observedRuntime:`Observed`,enableFunction:`Enable function`,disableFunction:`Disable function`,close:`Close`,nodeBriefList:`Compact node list`,noActiveClusterMembership:`Node is not a member of the active cluster`,nodeBriefListHelp:`The list is grouped as the active cluster tree. Full details, management, roles, services, and statistics open from the node row.`,nodeSearchPlaceholder:`name, key, cluster, status`,nodeGroupInventoryText:`Groups are a cluster inventory structure. Moving a node to a group changes only its placement inside the active cluster, not roles or membership.`,nodeGroupCreated:`Node group created.`,noNodesTitle:`No nodes`,noNodesByFilter:`No nodes match the current filter.`,cancel:`Cancel`,alreadyMember:`Already in active cluster`,revokedMembership:`Membership revoked`,addNode:`Add node`,addNodeText:`Connect an existing physical node to the active cluster from the node list: enable platform-wide view and click “Connect to active cluster”.`,joinTokenTitle:`Create new Docker node`,joinTokenText:`First create a one-time install token and Docker install profile. Then run the generated command on the Docker host; the agent submits a request and the platform owner approves it.`,ttlHours:`Lifetime, hours`,ttlHelp:`After this time the token becomes invalid even if unused. For manual enrollment, 1–24 hours is usually enough.`,maxUses:`Maximum uses`,maxUsesHelp:`How many node-agents may use this token. The safest default is one token for one new node.`,tokenPurpose:`Token purpose`,nodeOwnership:`Node ownership type`,suggestedRoles:`Allowed/expected roles`,generatedScope:`Generated scope`,generatedScopeHelp:`JSON is generated automatically from the settings above. Operators should not hand-write it and risk syntax or access-scope mistakes.`,manualApprovalRequired:`Manual request approval is required`,nodeRoles:`Node roles`,desiredServices:`Desired services`,observedServices:`Observed services`,noRoles:`No roles yet`,noServices:`No services yet`,manageInCluster:`Manage in cluster`,rolesAndServices:`Roles and services`,links:`Links`,fabricMap:`Fabric traffic map`,fabricIngressLayer:`Ingress`,fabricNodeLayer:`Cluster nodes`,fabricEgressLayer:`Egress pools`,observedPeerLinks:`Observed links`,placementIntent:`control-plane placement`,fabricEntryPoints:`Entry points`,fabricEntryPointHelp:`Logical external ingress points for the cluster. They hide concrete nodes from organizations and clients.`,fabricEgressPools:`Egress pools`,fabricEgressPoolHelp:`Logical exits to external networks, for example “Office Moscow”. Organizations use the pool, not a concrete node.`,endpointName:`Name`,publicEndpoint:`Public endpoint`,endpointType:`Entry type`,description:`Description`,routeScope:`Route scope JSON`,createEntryPoint:`Create entry point`,createEgressPool:`Create egress pool`,endpointNodes:`Assigned nodes`,assignEndpointNode:`Assign node`,selectNode:`Select node`,assignedNodesEmpty:`No nodes assigned yet`,entryPointsEmpty:`No entry points created yet.`,egressPoolsEmpty:`No egress pools created yet.`,addressNotSet:`address not set`,descriptionNotSet:`description not set`,servicePlacement:`Service placement`,trafficFlow:`Node traffic flows`,organizationTestFlag:`Organization testing`,organizationId:`Organization ID`,saveOrganizationFlag:`Save organization flag`,noLinks:`No links yet`,recentHeartbeats:`Recent heartbeats`,memory:`Memory`,cpu:`CPU`,processes:`Processes`}};function D(e){return{userId:e.user.id||e.user.ID||``,email:e.user.email||e.user.Email||``,authSessionId:e.auth_session.id||e.auth_session.ID||``,accessToken:e.tokens.access_token,refreshToken:e.tokens.refresh_token,accessTokenExpiresAt:e.tokens.access_token_expires_at,refreshTokenExpiresAt:e.tokens.refresh_token_expires_at}}async function O(e){try{return await e.listClusterSummaries(),`admin`}catch{try{return await Promise.all([e.listOrganizations(),e.listResources()]),`user`}catch{return null}}}function le(){let[e,t]=(0,_.useState)(!1),[n,r]=(0,_.useState)(()=>!!oe()),[i]=(0,_.useState)(()=>{let e=localStorage.getItem(w.baseUrl)?.trim();return!e||e.startsWith(te)?ee:e}),[a,o]=(0,_.useState)(()=>oe()),[s,c]=(0,_.useState)(null),[l,u]=(0,_.useState)(``),[d,f]=(0,_.useState)(()=>localStorage.getItem(w.language)===`en`?`en`:`ru`),[p,m]=(0,_.useState)(a?.userId??localStorage.getItem(w.actorUserId)??``),[h,g]=(0,_.useState)({email:``,password:``,deviceLabel:`Панель владельца платформы`,trustDevice:!0,rememberMe:!0,showPassword:!1}),[v,b]=(0,_.useState)(null),[x,S]=(0,_.useState)({email:``,password:``,activationPayload:``,activationSignature:``}),[T,re]=(0,_.useState)(`command`),[E,le]=(0,_.useState)(``),[he,_e]=(0,_.useState)([]),[xe,Se]=(0,_.useState)([]),[Ce,we]=(0,_.useState)(null),[j,Te]=(0,_.useState)([]),[Ee,De]=(0,_.useState)([]),[Oe,ke]=(0,_.useState)({}),[Fe,ze]=(0,_.useState)([]),[Be,Ve]=(0,_.useState)([]),[Ue,Ge]=(0,_.useState)([]),[Je,Ze]=(0,_.useState)({}),[Qe,$e]=(0,_.useState)({}),[tt,nt]=(0,_.useState)({}),[it,at]=(0,_.useState)({}),[ot,yt]=(0,_.useState)({}),[St,Ct]=(0,_.useState)({}),[Nt,Pt]=(0,_.useState)({}),[Ft,It]=(0,_.useState)([]),[Lt,Rt]=(0,_.useState)({}),[zt,Bt]=(0,_.useState)([]),[Vt,Ht]=(0,_.useState)([]),[Ut,Wt]=(0,_.useState)({}),[Gt,Kt]=(0,_.useState)([]),[qt,Jt]=(0,_.useState)({}),[N,Yt]=(0,_.useState)([]),[Xt,en]=(0,_.useState)([]),[tn,nn]=(0,_.useState)({}),[sn,cn]=(0,_.useState)({}),[ln,un]=(0,_.useState)(()=>localStorage.getItem(w.vpnDiagnosticDeviceId)||``),[dn,fn]=(0,_.useState)([]),[pn,mn]=(0,_.useState)(null),[gn,_n]=(0,_.useState)(`http://2ip.ru/`),[xn,Sn]=(0,_.useState)(null),[Cn,wn]=(0,_.useState)([]),[Tn,En]=(0,_.useState)([]),[Dn,On]=(0,_.useState)([]),[kn,An]=(0,_.useState)({}),[jn,Mn]=(0,_.useState)([]),[Nn,Pn]=(0,_.useState)(``),[Fn,In]=(0,_.useState)(`poll`),[Ln,Rn]=(0,_.useState)(``),[zn,Bn]=(0,_.useState)(null),[Vn,Hn]=(0,_.useState)(!1),[Un,Wn]=(0,_.useState)(``),[Gn,Kn]=(0,_.useState)(``),[qn,Jn]=(0,_.useState)({slug:``,name:``,region:``}),[Yn,Xn]=(0,_.useState)({name:``,status:`active`,region:``,metadataJson:`{}`}),[Zn,Qn]=(0,_.useState)({name:``,parentGroupId:``}),[$n,er]=(0,_.useState)({name:``,endpointType:`client_access`,publicEndpoint:``}),[tr,nr]=(0,_.useState)({name:``,description:``,routeScope:`{ - "routes": [] -}`}),[L,R]=(0,_.useState)(se),[rr,ir]=(0,_.useState)(null),[ar,or]=(0,_.useState)({authorityState:`authoritative`,mutationMode:`normal`,notes:``}),[sr,cr]=(0,_.useState)(``),[lr,ur]=(0,_.useState)(`cluster`),[dr,fr]=(0,_.useState)(``),[pr,mr]=(0,_.useState)(``),[hr,gr]=(0,_.useState)([]),[_r,vr]=(0,_.useState)(`membership`),[yr,br]=(0,_.useState)(null),[xr,Sr]=(0,_.useState)([]),[Cr,wr]=(0,_.useState)(null),[Tr,Er]=(0,_.useState)(`details`),[Dr,Or]=(0,_.useState)({}),[kr,Ar]=(0,_.useState)({}),[jr,Mr]=(0,_.useState)({}),[Nr,Pr]=(0,_.useState)({}),[Fr,Ir]=(0,_.useState)({}),[Lr,Rr]=(0,_.useState)({}),[zr,Br]=(0,_.useState)(``),[Vr,Hr]=(0,_.useState)({telemetry:!0,links:!0}),[Ur,Wr]=(0,_.useState)({nodeId:``,serviceType:`entry-node`,desiredState:`enabled`,runtimeMode:`container`,version:``,configJson:`{}`,environmentJson:`{}`}),[z,Gr]=(0,_.useState)({organizationId:``,name:``,protocolFamily:`generic`,desiredState:`disabled`,credentialRef:``,targetEndpointJson:`{}`,allowedNodePolicyJson:`{"mode":"explicit","node_ids":[]}`,routingUsageJson:`[]`,routePolicyJson:`{}`,qosPolicyJson:`{}`,placementPolicyJson:`{}`}),[Kr,qr]=(0,_.useState)({slug:``,name:``}),[Jr,Yr]=(0,_.useState)({email:``,password:``,platformRole:`user`}),[Xr,Zr]=(0,_.useState)({organizationId:``,userId:``,roleId:`org_member`}),[Qr,$r]=(0,_.useState)(null),[ei,ti]=(0,_.useState)({username:``,password:``,domain:``}),[B,ni]=(0,_.useState)({organizationId:``,name:``,address:``,protocol:`rdp`,routeMode:`vpn_exit`,entryNode:``,exitNode:``,tags:``,username:``,password:``,domain:``}),[ri,ii]=(0,_.useState)(``),[ai,oi]=(0,_.useState)(``),[si,ci]=(0,_.useState)(``),li=`rap-android-rdp-vpn-latest-release.apk`,[ui,di]=(0,_.useState)(li),V=(0,_.useMemo)(()=>new y({baseUrl:i,actorUserId:p}),[i,p]),fi=(0,_.useMemo)(()=>new y({baseUrl:i,actorUserId:``}),[i]),pi=(0,_.useRef)(0),mi=(0,_.useRef)(!1),H=ce[d],U=he.find(e=>e.id===E)||null,hi=xe.find(e=>e.cluster_id===E)||null,gi=(0,_.useMemo)(()=>yn(i),[i]),_i=(0,_.useCallback)((e,t)=>{if(!e)return t;let n=e.trim();return n?/^https?:\/\//i.test(n)||n.startsWith(`/`)?n.startsWith(`/`)?n.substring(1):n:n.startsWith(`downloads/`)?n:`downloads/${n.replace(/^\.\/+/,``).replace(/^\/+/,``)}`:t},[]),vi=_i(ui,li),yi=si?_i(si,vi):vi,bi=si?yi:vi,xi=`${/^https?:\/\//i.test(bi)?bi:`${gi}/${bi}`}${ai?`?_v=${encodeURIComponent(ai)}`:``}`,Si=(0,_.useMemo)(()=>gt(L),[L]),Ci=(0,_.useMemo)(()=>rr?_t(rr.scope,L):L,[rr,L]),wi=(0,_.useMemo)(()=>{let e=new Map;for(let t of he)for(let n of Oe[t.id]||[]){let r=e.get(n.id);r?(r.memberships.push({cluster:t,node:n}),(n.last_seen_at||``)>(r.node.last_seen_at||``)&&(r.node=n)):e.set(n.id,{node:n,memberships:[{cluster:t,node:n}]})}return Array.from(e.values()).sort((e,t)=>e.node.name.localeCompare(t.node.name))},[Oe,he]);(0,_.useMemo)(()=>Qt(wi,E,dr,_r,d),[wi,_r,dr,d,E]);let Ti=(0,_.useMemo)(()=>{let e=dr.trim().toLowerCase(),t=pr?new Set([pr,...pt(pr,Ee)]):null;return wi.filter(n=>{let r=n.memberships.some(e=>e.cluster.id===E);if(lr!==`all`&&!r)return!1;if(t){let e=n.memberships.find(e=>e.cluster.id===E);if(!e?.node.node_group_id||!t.has(e.node.node_group_id))return!1}return!e||$t(n,e)})},[wi,dr,pr,Ee,lr,E]),Ei=(0,_.useCallback)((e,t=!1)=>{if(e&&t){localStorage.setItem(w.auth,JSON.stringify(e)),localStorage.setItem(w.actorUserId,e.userId),r(!0);return}r(!1),localStorage.removeItem(w.auth),localStorage.removeItem(w.actorUserId)},[]),Di=(0,_.useCallback)(async()=>{try{let e=`${gi}/downloads/rap-android-rdp-vpn-build.json?_cb=${Date.now()}`,t=await fetch(e,{cache:`no-store`});if(!t.ok){ii(``),oi(new Date().toISOString()),ci(``),di(li);return}let n=await t.json();ii(n.version?.name||``),oi(n.published?.timestamp_utc||``),ci(n.release_paths?.versioned||``),di(n.published?.path||n.release_paths?.latest||li)}catch{ii(``),oi(new Date().toISOString()),ci(``),di(li)}},[gi]),Oi=(0,_.useMemo)(()=>mt(Ti,Ee,E,H,new Set(hr)),[hr,Ee,E,H,Ti]);(0,_.useEffect)(()=>{if(e)return;t(!0);let n=oe();if(n){if(ae(n.refreshTokenExpiresAt)){localStorage.removeItem(w.auth),localStorage.removeItem(w.actorUserId),r(!1);return}(async()=>{try{let e=D(await fi.refresh({refreshToken:n.refreshToken}));if(!e.userId||!e.authSessionId)throw Error(`Не удалось восстановить сессию.`);let t=await O(new y({baseUrl:i,actorUserId:e.userId}));if(!t)throw Error(`Доступ к этой панели запрещен.`);m(e.userId),Ei(e,!0),o(e),u(new Date().toISOString()),g(t=>({...t,email:e.email})),c(t)}catch{localStorage.removeItem(w.auth),localStorage.removeItem(w.actorUserId),r(!1),u(``),o(null),m(``),c(null)}})()}},[fi,e,i,Ei]),(0,_.useEffect)(()=>{let e=!1;return fi.getInstallationStatus().then(t=>{e||b(t)}).catch(t=>{e||Wn(t instanceof Error?t.message:`Не удалось загрузить статус установки.`)}),()=>{e=!0}},[fi]),(0,_.useEffect)(()=>{if(!U){Xn({name:``,status:`active`,region:``,metadataJson:`{}`});return}Xn({name:U.name,status:U.status||`active`,region:U.region||``,metadataJson:JSON.stringify(U.metadata||{},null,2)})},[U]),(0,_.useEffect)(()=>{mr(``),Qn({name:``,parentGroupId:``}),gr([])},[E]),(0,_.useEffect)(()=>{br(null),Sr([])},[E]),(0,_.useEffect)(()=>{localStorage.setItem(w.baseUrl,i),localStorage.setItem(w.language,d),a&&localStorage.setItem(`${w.language}.${a.userId}`,d),(!a||!n)&&(localStorage.removeItem(w.auth),localStorage.removeItem(w.actorUserId))},[i,d,n,a]),(0,_.useEffect)(()=>{if(!a)return;let e=localStorage.getItem(`${w.language}.${a.userId}`);(e===`ru`||e===`en`)&&f(e)},[a?.userId]),(0,_.useEffect)(()=>{a&&W()},[a?.userId]),(0,_.useEffect)(()=>{if(!a||s!==`admin`||!E)return;let e=!1,t=()=>{e||Vn||mi.current||document.visibilityState===`hidden`||(mi.current=!0,ki(E).catch(t=>{e||Wn(t instanceof Error?t.message:`Не удалось автообновить данные панели.`)}).finally(()=>{mi.current=!1}))},n=null;typeof window.EventSource==`function`&&(n=new EventSource(V.clusterEventsURL(E)),n.onopen=()=>{e||In(`sse`)},n.onerror=()=>{e||In(`poll`)},n.addEventListener(`cluster.changed`,t));let r=window.setInterval(t,n?3e4:1e4);return()=>{e=!0,n?.close(),window.clearInterval(r)}},[V,s,Vn,E,a?.userId]);async function W(e=E){if(!p.trim()){Wn(H.noLoginError);return}if(s===`user`){await G();return}Hn(!0),Wn(``),Kn(``);try{let[t,n,r,i,a]=await Promise.all([V.listClusters(),V.listClusterSummaries(),V.listOrganizations(),V.listUsers(),V.listResources()]);_e(t),Se(n),wn(r),En(i),On(a),!Ln&&r[0]?.id&&Rn(r[0].id),Zr(e=>({...e,organizationId:e.organizationId||r[0]?.id||``})),ni(e=>({...e,organizationId:e.organizationId||r[0]?.id||``}));let o=await Promise.all(r.map(async e=>[e.id,await V.listOrganizationMemberships(e.id)]));An(Object.fromEntries(o));let s=await Promise.all(t.map(async e=>[e.id,await V.listNodes(e.id)]));ke(Object.fromEntries(s));let c=e||t[0]?.id||``;le(c),c&&await Ai(c),Pn(new Date().toISOString())}catch(e){Wn(e instanceof Error?e.message:`Неизвестная ошибка панели управления платформой.`)}finally{Hn(!1)}}async function G(){if(!p.trim()){Wn(`Войдите, чтобы загрузить личный кабинет.`);return}Hn(!0),Wn(``),Kn(``);try{await Di();let[e,t]=await Promise.all([V.listOrganizations(),V.listResources()]);wn(e),On(t),!Ln&&e[0]?.id&&Rn(e[0].id);let n=await Promise.all(e.map(async e=>[e.id,await V.listOrganizationMemberships(e.id)]));An(Object.fromEntries(n)),Pn(new Date().toISOString())}catch(e){Wn(e instanceof Error?e.message:`Не удалось загрузить личный кабинет.`)}finally{Hn(!1)}}async function ki(e){if(!p.trim())return;let[t,n,r,i,a]=await Promise.all([V.listClusterSummaries(),V.listNodes(e),V.listOrganizations(),V.listUsers(),V.listResources()]);Se(t),wn(r),En(i),On(a),ke(t=>({...t,[e]:n})),await Ai(e,{preserveEditableForms:!0}),Pn(new Date().toISOString())}async function Ai(e,t={}){let n=++pi.current,[r,i,a,o,s,c,l,u,d,f,p,m,h]=await Promise.all([V.listNodes(e),V.listNodeGroups(e),V.listJoinRequests(e),V.listJoinTokens(e),V.listReleaseVersions(e,`rap-node-agent`,`dev`),V.getClusterAuthority(e),V.listAudit(e),V.listMeshLinks(e),V.listQoSPolicies(e),V.listFabricEntryPoints(e),V.listFabricEgressPools(e),V.listVPNConnections(e),V.listFabricTestingFlags()]);if(n!==pi.current)return;Te(r),De(i),ze(a),Ve(o),Ge(s),we(c),t.preserveEditableForms||or({authorityState:c.authority_state,mutationMode:c.mutation_mode,notes:c.notes||``}),Mn(l),It(u),Bt(d),Ht(f),Kt(p),en(m),Yt(h);let g=await V.listVPNClientDiagnosticStatuses(e);if(n!==pi.current)return;fn(g);let _=g.find(e=>e.device_id===ln.trim())||g[0]||null;mn(_),!ln.trim()&&_&&(un(_.device_id),localStorage.setItem(w.vpnDiagnosticDeviceId,_.device_id));let[v,y]=await Promise.all([Promise.all(f.map(async t=>[t.id,await V.listFabricEntryPointNodes(e,t.id)])),Promise.all(p.map(async t=>[t.id,await V.listFabricEgressPoolNodes(e,t.id)]))]);if(n!==pi.current)return;Wt(Object.fromEntries(v)),Jt(Object.fromEntries(y));let b=await Promise.all(r.map(async t=>[t.id,await V.listNodeRoles(e,t.id)]));if(n!==pi.current)return;nt(Object.fromEntries(b));let x=await Promise.all(r.map(async t=>[t.id,await V.listDesiredWorkloads(e,t.id)]));if(n!==pi.current)return;at(Object.fromEntries(x));let S=await Promise.all(r.map(async t=>[t.id,await V.listWorkloadStatuses(e,t.id)]));if(n!==pi.current)return;yt(Object.fromEntries(S));let C=await Promise.all(r.map(async t=>[t.id,await V.listNodeHeartbeats(e,t.id,60)]));if(n!==pi.current)return;Ct(Object.fromEntries(C));let ee=await Promise.all(r.map(async t=>[t.id,await V.getNodeUpdatePlan(e,t.id,{currentVersion:t.reported_version})]));if(n!==pi.current)return;Ze(Object.fromEntries(ee));let te=await Promise.all(r.map(async t=>[t.id,await V.listNodeUpdateStatuses(e,t.id,80)]));if(n!==pi.current)return;$e(Object.fromEntries(te));let ne=await Promise.all(r.map(async t=>[t.id,await V.listNodeTelemetry(e,t.id,120)]));if(n!==pi.current)return;Pt(Object.fromEntries(ne));let T=await Promise.all(r.map(async t=>[t.id,await V.getNodeSyntheticMeshConfig(e,t.id)]));if(n!==pi.current)return;Rt(Object.fromEntries(T));let re=await Promise.all(m.map(async t=>[t.id,await V.getActiveVPNLease(e,t.id)]));if(n!==pi.current)return;nn(Object.fromEntries(re));let ie=await Promise.all(m.map(async t=>[t.id,await V.getVPNPacketStats(e,t.id)]));n===pi.current&&cn(Object.fromEntries(ie))}function ji(){Te([]),De([]),ze([]),Ve([]),Ge([]),Ze({}),we(null),nt({}),at({}),yt({}),Ct({}),$e({}),Pt({}),It([]),Rt({}),Bt([]),Ht([]),Wt({}),Kt([]),Jt({}),Yt([]),en([]),nn({}),cn({}),fn([]),mn(null),wn([]),En([]),On([]),An({}),Mn([])}async function K(e,t){Hn(!0),Wn(``),Kn(``);try{await e(),Kn(t),await W()}catch(e){Wn(e instanceof Error?e.message:`Действие не выполнено.`)}finally{Hn(!1)}}async function Mi(){if(!E){mn(null);return}let e=await V.listVPNClientDiagnosticStatuses(E);fn(e);let t=ln.trim()||e[0]?.device_id||``;t&&(localStorage.setItem(w.vpnDiagnosticDeviceId,t),un(t));let n=e.find(e=>e.device_id===t)||(t?await V.getVPNClientDiagnosticStatus(E,t):null);mn(n),Kn(n?`Диагностика VPN-клиента обновлена.`:`Диагностика VPN-клиента не найдена.`)}async function Ni(e,t){if(!E){Wn(`Выбери кластер перед отправкой команды.`);return}let n=ln.trim();if(!n){Wn(`Укажи Android device id или выбери найденный клиент.`);return}Hn(!0),Wn(``),Kn(``);try{Sn(await V.enqueueVPNClientDiagnosticCommand(E,n,e)),Kn(`${t}: команда поставлена в очередь. Клиент заберет ее через диагностический канал.`),window.setTimeout(()=>{Mi()},3500)}catch(e){Wn(e instanceof Error?e.message:`Команда VPN-клиенту не отправлена.`)}finally{Hn(!1)}}async function Pi(){Hn(!0),Wn(``),Kn(``);try{let e=D(await fi.login({email:h.email,password:h.password,deviceLabel:h.deviceLabel,trustDevice:h.trustDevice}));if(!e.userId||!e.authSessionId)throw Error(`Ответ входа не содержит пользователя или сессию.`);let t=new y({baseUrl:i,actorUserId:e.userId}),n=`admin`;try{await t.listClusterSummaries(),n=`admin`}catch{try{let[e,r]=await Promise.all([t.listOrganizations(),t.listResources()]);wn(e),On(r),e[0]?.id&&Rn(e[0].id);let i=await Promise.all(e.map(async e=>[e.id,await t.listOrganizationMemberships(e.id)]));An(Object.fromEntries(i)),n=`user`}catch{try{await fi.revokeAuthSession({userId:e.userId,authSessionId:e.authSessionId,reason:`user_portal_access_denied`})}catch{}throw Error(H.accessDenied)}}r(h.rememberMe),Ei(e,h.rememberMe),o(e),m(e.userId),g(t=>({...t,email:e.email,password:``})),u(new Date().toISOString()),c(n),Kn(`${H.signedInAs}: ${e.email}`)}catch(e){Wn(e instanceof Error?e.message:`Вход не выполнен.`)}finally{Hn(!1)}}async function Fi(){Hn(!0),Wn(``),Kn(``);try{let e;if(v?.strict_authority){if(!x.activationPayload.trim()||!x.activationSignature.trim())throw Error(H.bootstrapText);e=JSON.parse(x.activationPayload)}b((await fi.bootstrapOwner({email:x.email,password:x.password,activationPayload:e,activationSignature:x.activationSignature})).installation),g({...h,email:x.email,password:x.password}),Kn(H.ownerCreated)}catch(e){Wn(e instanceof Error?e.message:`Создание владельца не выполнено.`)}finally{Hn(!1)}}async function Ii(){let e=a;if(o(null),r(!1),u(``),Ei(null),c(null),m(``),_e([]),Se([]),ji(),ke({}),le(``),e?.userId&&e.authSessionId)try{await fi.revokeAuthSession({userId:e.userId,authSessionId:e.authSessionId,reason:`platform_owner_console_logout`})}catch{}}async function Li(e){le(e),ji(),Hn(!0),Wn(``),Kn(``);try{await Ai(e)}catch(e){Wn(e instanceof Error?e.message:`Не удалось загрузить кластер.`)}finally{Hn(!1)}}let Ri=Fe.filter(e=>e.status===`pending`).length,zi=j.filter(e=>e.health_status===`healthy`).length,Bi=j.filter(e=>e.health_status!==`healthy`||e.membership_status!==`active`).length,Vi=Object.values(tt).flat().filter(e=>e.status===`active`).length,Hi=N.find(e=>e.scope_type===`platform`&&!e.scope_id)||null;N.find(e=>e.scope_type===`organization`&&e.scope_id===zr&&(!e.cluster_id||e.cluster_id===E));let Ui=Object.values(Lt),Wi=Ui.filter(e=>e.enabled).length,Gi=Ui.reduce((e,t)=>e+t.routes.length,0),Ki=Ui.reduce((e,t)=>e+Object.keys(t.peer_endpoints||{}).length,0),qi=Ui.reduce((e,t)=>e+st(t),0),Ji=Ui.reduce((e,t)=>e+(t.peer_directory?.length??0),0),Yi=Ui.reduce((e,t)=>e+(t.recovery_seeds?.length??0),0),Xi=Ui.filter(e=>e.production_forwarding).length,Zi=v?.bootstrapped===!1,Qi=Zi&&!v?.strict_authority&&!v?.insecure_bootstrap_allowed,$i=s===`admin`?H.sessionModeAdmin:H.sessionModeUser;if(!a)return(0,C.jsxs)(`main`,{className:`loginShell`,children:[v&&(0,C.jsxs)(`section`,{className:`loginCard`,children:[(0,C.jsx)(`h1`,{children:v.bootstrapped?H.installationLocked:H.bootstrapTitle}),(0,C.jsx)(A,{label:`Authority`,value:`${v.authority_mode}/${v.authority_state}`}),(0,C.jsx)(A,{label:`Strict`,value:v.strict_authority?`enabled`:`legacy`}),v.root_fingerprint&&(0,C.jsx)(A,{label:`Root key`,value:P(v.root_fingerprint)})]}),Zi?(0,C.jsxs)(`section`,{className:`loginCard`,children:[(0,C.jsx)(`h1`,{children:H.bootstrapTitle}),(0,C.jsx)(`p`,{className:`loginHint`,children:Qi?H.insecureBootstrapDisabled:H.bootstrapText}),(0,C.jsxs)(`label`,{children:[H.email,(0,C.jsx)(`input`,{value:x.email,onChange:e=>S({...x,email:e.target.value}),autoComplete:`username`})]}),(0,C.jsxs)(`label`,{children:[H.password,(0,C.jsx)(`input`,{value:x.password,onChange:e=>S({...x,password:e.target.value}),type:`password`,autoComplete:`new-password`})]}),v?.strict_authority&&(0,C.jsxs)(C.Fragment,{children:[(0,C.jsxs)(`label`,{children:[H.activationPayload,(0,C.jsx)(`textarea`,{value:x.activationPayload,onChange:e=>S({...x,activationPayload:e.target.value}),spellCheck:!1})]}),(0,C.jsxs)(`label`,{children:[H.activationSignature,(0,C.jsx)(`input`,{value:x.activationSignature,onChange:e=>S({...x,activationSignature:e.target.value}),spellCheck:!1})]})]}),Un&&(0,C.jsx)(`div`,{className:`errorPanel`,children:Un}),Gn&&(0,C.jsx)(`div`,{className:`noticePanel`,children:Gn}),(0,C.jsx)(`button`,{className:`primary wide`,onClick:()=>void Fi(),disabled:Vn||Qi||!x.email||x.password.length<12||v?.strict_authority&&(!x.activationPayload||!x.activationSignature),children:Vn?H.creatingOwner:H.createOwner})]}):(0,C.jsxs)(`section`,{className:`loginCard`,children:[(0,C.jsx)(`h1`,{children:H.signInTitle}),(0,C.jsxs)(`label`,{children:[H.email,(0,C.jsx)(`input`,{value:h.email,onChange:e=>g({...h,email:e.target.value.trim()}),autoComplete:`username`,autoCapitalize:`none`,autoCorrect:`off`,spellCheck:!1})]}),(0,C.jsxs)(`label`,{children:[H.password,(0,C.jsx)(`input`,{value:h.password,onChange:e=>g({...h,password:e.target.value}),type:h.showPassword?`text`:`password`,autoComplete:`current-password`,autoCapitalize:`none`,autoCorrect:`off`,spellCheck:!1,onKeyDown:e=>{e.key===`Enter`&&Pi()}})]}),(0,C.jsxs)(`label`,{className:`checkLine`,children:[(0,C.jsx)(`input`,{type:`checkbox`,checked:h.showPassword,onChange:e=>g({...h,showPassword:e.target.checked})}),`Показать пароль`]}),(0,C.jsxs)(`label`,{className:`checkLine`,children:[(0,C.jsx)(`input`,{type:`checkbox`,checked:h.trustDevice,onChange:e=>g({...h,trustDevice:e.target.checked})}),H.trustDevice]}),(0,C.jsxs)(`label`,{className:`checkLine`,children:[(0,C.jsx)(`input`,{type:`checkbox`,checked:h.rememberMe,onChange:e=>g({...h,rememberMe:e.target.checked})}),H.rememberMe]}),Un&&(0,C.jsx)(`div`,{className:`errorPanel`,children:Un}),Gn&&(0,C.jsx)(`div`,{className:`noticePanel`,children:Gn}),(0,C.jsx)(`button`,{className:`primary wide`,onClick:()=>void Pi(),disabled:Vn||!h.email||!h.password,children:Vn?H.signingIn:H.signIn})]})]});if(a&&!s)return(0,C.jsx)(`main`,{className:`loginShell`,children:(0,C.jsx)(`section`,{className:`loginCard`,children:(0,C.jsx)(`p`,{children:Vn?H.lastRefresh:`Восстанавливаем сессию...`})})});if(s===`user`){let e=Cn.find(e=>e.id===Ln)||Cn[0]||null,t=e?Dn.filter(t=>t.organization_id===e.id):Dn,n=e?(kn[e.id]||[]).find(e=>e.user_id===a.userId):null,r=t.reduce((e,t)=>(e[t.protocol]=(e[t.protocol]||0)+1,e),{});return(0,C.jsxs)(`main`,{className:`portalShell`,children:[(0,C.jsxs)(`aside`,{className:`portalRail`,children:[(0,C.jsx)(`div`,{className:`brandMark`,children:`RAP`}),(0,C.jsx)(`p`,{className:`sideKicker`,children:`Личный кабинет`}),(0,C.jsx)(`h1`,{children:`Мой доступ`}),(0,C.jsx)(`p`,{className:`sideText`,children:`Установки, доступные серверы и состояние рабочей области пользователя.`}),(0,C.jsx)(A,{label:H.sessionMode,value:`${$i} • ${l?bn(l):`н/д`}`}),(0,C.jsx)(A,{label:H.actorUser,value:a.email}),(0,C.jsx)(`button`,{className:`ghost`,onClick:()=>void Ii(),disabled:Vn,children:H.logout})]}),(0,C.jsxs)(`section`,{className:`portalWorkspace`,children:[(0,C.jsxs)(`header`,{className:`portalTop`,children:[(0,C.jsxs)(`div`,{children:[(0,C.jsx)(`p`,{className:`eyebrow`,children:`Secure Access Fabric`}),(0,C.jsx)(`h2`,{children:e?.name||`Личный кабинет`}),(0,C.jsx)(`p`,{className:`muted`,children:a.email})]}),(0,C.jsxs)(`label`,{children:[`Организация`,(0,C.jsx)(`select`,{value:e?.id||``,onChange:e=>Rn(e.target.value),children:Cn.map(e=>(0,C.jsx)(`option`,{value:e.id,children:e.name},e.id))})]}),(0,C.jsx)(`button`,{className:`primary`,onClick:()=>void G(),disabled:Vn,children:Vn?H.refreshing:H.refresh})]}),Un&&(0,C.jsx)(`div`,{className:`errorPanel`,children:Un}),Gn&&(0,C.jsx)(`div`,{className:`noticePanel`,children:Gn}),(0,C.jsxs)(`section`,{className:`grid three`,children:[(0,C.jsx)(ue,{label:`Организации`,value:Cn.length,tone:`steel`}),(0,C.jsx)(ue,{label:`Серверы`,value:t.length,tone:`green`}),(0,C.jsx)(ue,{label:`Установки`,value:2,tone:`amber`})]}),(0,C.jsxs)(`section`,{className:`grid two`,children:[(0,C.jsxs)(`article`,{className:`card`,children:[(0,C.jsxs)(`div`,{className:`cardHead`,children:[(0,C.jsxs)(`div`,{children:[(0,C.jsx)(`h3`,{children:`Установки`}),(0,C.jsx)(`p`,{className:`muted`,children:ri?`Актуальная версия Android: ${ri}`:`Скачивайте актуальные клиенты только отсюда, чтобы не ловить старую сборку.`})]}),(0,C.jsx)(`span`,{className:`status active`,children:`latest`})]}),(0,C.jsxs)(`div`,{className:`portalInstallList`,children:[(0,C.jsxs)(`a`,{className:`installTile primaryInstall`,href:xi,children:[(0,C.jsx)(`strong`,{children:`Android VPN`}),(0,C.jsx)(`span`,{children:`Последняя сборка RAP HOME VPN для телефона`}),(0,C.jsx)(`small`,{children:si||bi})]}),(0,C.jsxs)(`a`,{className:`installTile`,href:`${gi}/downloads/rap-windows-rdp-client-latest-win-x64.zip`,children:[(0,C.jsx)(`strong`,{children:`Windows RDP клиент`}),(0,C.jsx)(`span`,{children:`Клиент удаленного рабочего стола, когда нужен доступ к серверам`}),(0,C.jsx)(`small`,{children:`latest win-x64`})]})]})]}),(0,C.jsxs)(`article`,{className:`card`,children:[(0,C.jsx)(`h3`,{children:`Профиль`}),(0,C.jsx)(A,{label:`Пользователь`,value:a.email}),(0,C.jsx)(A,{label:`Роль в организации`,value:n?.role_id||`участник`}),(0,C.jsx)(A,{label:`Организация`,value:e?.name||`нет`}),(0,C.jsx)(A,{label:`Последнее обновление`,value:Nn?F(Nn):`нет`})]}),(0,C.jsxs)(`article`,{className:`card span2`,children:[(0,C.jsx)(`div`,{className:`cardHead`,children:(0,C.jsxs)(`div`,{children:[(0,C.jsx)(`h3`,{children:`Доступные серверы`}),(0,C.jsx)(`p`,{className:`muted`,children:`Список ресурсов, которые уже разрешены пользователю через организацию.`})]})}),(0,C.jsx)(je,{columns:[`имя`,`адрес`,`протокол`,`секрет`,`передача файлов`],rows:t.map(e=>[e.name,e.address,e.protocol,e.has_secret?`настроен`:`нет`,I(e.file_transfer_mode||`disabled`)])})]}),(0,C.jsxs)(`article`,{className:`card`,children:[(0,C.jsx)(`h3`,{children:`Сервисы`}),(0,C.jsx)(je,{columns:[`тип`,`количество`],rows:Object.entries(r).map(([e,t])=>[e,String(t)])})]}),(0,C.jsxs)(`article`,{className:`card`,children:[(0,C.jsx)(`h3`,{children:`Что здесь будет дальше`}),(0,C.jsxs)(`div`,{className:`portalRoadmap`,children:[(0,C.jsx)(`span`,{children:`Устройства и доверенные входы`}),(0,C.jsx)(`span`,{children:`Активные VPN/RDP сессии`}),(0,C.jsx)(`span`,{children:`Обновление профиля VPN без ручных ключей`}),(0,C.jsx)(`span`,{children:`Самостоятельная смена пароля`})]})]})]})]})]})}return(0,C.jsxs)(`main`,{className:`consoleShell`,children:[(0,C.jsxs)(`aside`,{className:`sideRail`,children:[(0,C.jsx)(`div`,{className:`brandMark`,children:`SAF`}),(0,C.jsx)(`p`,{className:`sideKicker`,children:H.productOwner}),(0,C.jsx)(`h1`,{children:H.controlPlane}),(0,C.jsx)(`p`,{className:`sideText`,children:H.sideText}),(0,C.jsx)(`nav`,{className:`railNav`,children:ie.filter(e=>e.id!==`roles`).map(e=>(0,C.jsx)(`button`,{className:T===e.id?`active`:``,onClick:()=>re(e.id),children:e[d]},e.id))})]}),(0,C.jsxs)(`section`,{className:`workspace`,children:[(0,C.jsxs)(`header`,{className:`topBar`,children:[(0,C.jsxs)(`div`,{children:[(0,C.jsx)(`p`,{className:`eyebrow`,children:`Secure Access Fabric`}),(0,C.jsx)(`h2`,{children:U?U.name:H.consoleTitle}),(0,C.jsx)(`p`,{className:`muted`,children:H.boundary})]}),(0,C.jsxs)(`div`,{className:`clusterPicker`,children:[(0,C.jsxs)(`label`,{children:[H.activeCluster,(0,C.jsx)(`select`,{value:E,onChange:e=>void Li(e.target.value),children:he.map(e=>(0,C.jsx)(`option`,{value:e.id,children:e.name},e.id))})]}),(0,C.jsxs)(`span`,{children:[H.slugLabel,`: `,U?.slug||`н/д`]})]}),(0,C.jsx)(`button`,{className:`primary`,onClick:()=>void W(),disabled:Vn,children:Vn?H.refreshing:H.refresh}),(0,C.jsxs)(`div`,{className:`refreshStatus`,children:[(0,C.jsx)(`strong`,{children:H.autoRefresh}),(0,C.jsx)(`span`,{children:Nn?`${H.lastRefresh}: ${bn(Nn)} / ${Fn.toUpperCase()}`:Fn.toUpperCase()})]}),(0,C.jsxs)(`div`,{className:`profilePanel`,children:[(0,C.jsx)(`strong`,{children:H.profile}),(0,C.jsx)(`span`,{children:a.email}),(0,C.jsxs)(`span`,{children:[H.sessionMode,`: `,$i,` | `,H.sessionRefreshedAt,`: `,l?bn(l):`н/д`]}),(0,C.jsxs)(`label`,{children:[H.language,(0,C.jsxs)(`select`,{value:d,onChange:e=>f(e.target.value),children:[(0,C.jsx)(`option`,{value:`ru`,children:`Русский`}),(0,C.jsx)(`option`,{value:`en`,children:d===`ru`?`Английский`:`English`})]})]}),(0,C.jsx)(`button`,{className:`ghost`,onClick:()=>void Ii(),disabled:Vn,children:H.logout})]})]}),Un&&(0,C.jsx)(`div`,{className:`errorPanel`,children:Un}),Gn&&(0,C.jsx)(`div`,{className:`noticePanel`,children:Gn}),U&&j.length===0&&(0,C.jsxs)(`div`,{className:`noticePanel`,children:[(0,C.jsxs)(`strong`,{children:[H.emptyLiveTitle,`.`]}),` `,H.emptyLiveText]}),T===`command`&&(0,C.jsxs)(`section`,{className:`grid five`,children:[(0,C.jsx)(ue,{label:`Кластеры`,value:he.length,tone:`steel`}),(0,C.jsx)(ue,{label:`Узлы в области`,value:j.length,tone:`green`}),(0,C.jsx)(ue,{label:`Здоровые узлы`,value:zi,tone:`green`}),(0,C.jsx)(ue,{label:`Ожидают подключения`,value:Ri,tone:`amber`}),(0,C.jsx)(ue,{label:`Рискованные состояния`,value:Bi,tone:`red`}),(0,C.jsxs)(`article`,{className:`card span3`,children:[(0,C.jsx)(`h3`,{children:`Общее состояние кластеров`}),(0,C.jsx)(je,{columns:[`кластер`,`authority`,`ключ`,`режим изменений`,`узлы`,`заявки`,`роли`,`последний сигнал`],rows:xe.map(e=>[e.name,e.authority_state,P(e.cluster_key_fingerprint),e.mutation_mode,`${e.healthy_node_count}/${e.node_count}`,String(e.pending_join_count),String(e.active_role_assignment_count),F(e.last_node_seen_at)])})]}),(0,C.jsxs)(`article`,{className:`card span2`,children:[(0,C.jsx)(`h3`,{children:`Authority выбранного кластера`}),Ce?(0,C.jsxs)(`div`,{className:`stateList`,children:[(0,C.jsx)(A,{label:`Authority`,value:Ce.authority_state}),(0,C.jsx)(A,{label:`Режим изменений`,value:Ce.mutation_mode}),(0,C.jsx)(A,{label:`Терм`,value:String(Ce.term)}),(0,C.jsx)(A,{label:`Cluster key`,value:P(hi?.cluster_key_fingerprint)}),(0,C.jsx)(A,{label:`Обновлено`,value:F(Ce.updated_at)})]}):(0,C.jsx)(pe,{title:`Нет состояния authority`,text:`Выберите кластер, чтобы загрузить состояние authority.`})]}),(0,C.jsxs)(`article`,{className:`card span2`,children:[(0,C.jsx)(`h3`,{children:`Граница платформы`}),(0,C.jsx)(`p`,{className:`muted`,children:`Эта панель предназначена для владельца продукта / владельца платформы. Панели организаций должны использовать безопасные проекции и не раскрывать mesh internals, peer cache, route cache, секреты или данные других tenants.`})]}),(0,C.jsxs)(`article`,{className:`card span3`,children:[(0,C.jsx)(`h3`,{children:`Текущие сигналы кластера`}),(0,C.jsxs)(`div`,{className:`signalStrip`,children:[(0,C.jsx)(k,{label:`Активные роли`,value:String(Vi)}),(0,C.jsx)(k,{label:`Отчеты сервисов`,value:String(Object.values(ot).filter(e=>e.length>0).length)}),(0,C.jsx)(k,{label:`Наблюдения связей`,value:String(Ft.length)}),(0,C.jsx)(k,{label:`Synthetic configs`,value:`${Wi}/${j.length}`})]})]})]}),T===`clusters`&&(0,C.jsxs)(`section`,{className:`grid two`,children:[(0,C.jsxs)(`article`,{className:`card span2`,children:[(0,C.jsxs)(`div`,{className:`cardHead`,children:[(0,C.jsxs)(`div`,{children:[(0,C.jsx)(`h3`,{children:H.clusterCatalog}),(0,C.jsx)(`p`,{className:`muted`,children:H.clusterCatalogText})]}),(0,C.jsx)(`span`,{className:`pill`,children:hn(he.length,d)})]}),(0,C.jsxs)(`div`,{className:`clusterCatalog`,children:[he.map(e=>{let t=xe.find(t=>t.cluster_id===e.id),n=e.id===E;return(0,C.jsxs)(`article`,{className:`clusterCard ${n?`selected`:``}`,children:[(0,C.jsxs)(`div`,{className:`clusterCardMain`,children:[(0,C.jsxs)(`div`,{children:[(0,C.jsx)(`p`,{className:`eyebrow`,children:e.region||`регион не задан`}),(0,C.jsx)(`h4`,{children:e.name}),(0,C.jsxs)(`p`,{className:`muted`,children:[H.slugLabel,`: `,(0,C.jsx)(`strong`,{children:e.slug})]})]}),(0,C.jsxs)(`div`,{className:`clusterCardActions`,children:[(0,C.jsx)(de,{value:e.status}),n?(0,C.jsx)(`span`,{className:`pill good`,children:H.selected}):(0,C.jsx)(`button`,{onClick:()=>void Li(e.id),children:H.makeActive}),(0,C.jsx)(`button`,{className:`ghost`,onClick:()=>{Li(e.id),re(`cluster-settings`)},children:H.openSettings})]})]}),(0,C.jsxs)(`div`,{className:`signalStrip compact`,children:[(0,C.jsx)(k,{label:`Узлы`,value:t?`${t.healthy_node_count}/${t.node_count}`:`н/д`}),(0,C.jsx)(k,{label:`Заявки`,value:String(t?.pending_join_count??`н/д`)}),(0,C.jsx)(k,{label:`Роли`,value:String(t?.active_role_assignment_count??`н/д`)}),(0,C.jsx)(k,{label:`Последний сигнал`,value:F(t?.last_node_seen_at)})]}),(0,C.jsxs)(`details`,{children:[(0,C.jsx)(`summary`,{children:H.clusterDetails}),(0,C.jsxs)(`div`,{className:`stateList`,children:[(0,C.jsx)(A,{label:`ID`,value:e.id}),(0,C.jsx)(A,{label:H.slugLabel,value:e.slug}),(0,C.jsx)(A,{label:`Статус`,value:I(e.status)}),(0,C.jsx)(A,{label:`Authority`,value:t?`${t.authority_state}/${t.mutation_mode}`:`неизвестно`}),(0,C.jsx)(A,{label:`Создан`,value:F(e.created_at)}),(0,C.jsx)(A,{label:`Обновлен`,value:F(e.updated_at||e.created_at)})]})]})]},e.id)}),he.length===0&&(0,C.jsx)(pe,{title:`Кластеров нет`,text:`Создайте первый кластер, затем подключите стартовый node-agent.`})]})]}),(0,C.jsxs)(`article`,{className:`card`,children:[(0,C.jsx)(`h3`,{children:H.createCluster}),(0,C.jsxs)(Ae,{children:[(0,C.jsxs)(`label`,{children:[H.slugLabel,(0,C.jsx)(`input`,{value:qn.slug,onChange:e=>Jn({...qn,slug:e.target.value})})]}),(0,C.jsxs)(`label`,{children:[`Название`,(0,C.jsx)(`input`,{value:qn.name,onChange:e=>Jn({...qn,name:e.target.value})})]}),(0,C.jsxs)(`label`,{children:[`Регион`,(0,C.jsx)(`input`,{value:qn.region,onChange:e=>Jn({...qn,region:e.target.value})})]})]}),(0,C.jsx)(`p`,{className:`muted`,children:H.slugHelp}),(0,C.jsx)(`button`,{className:`primary`,disabled:!qn.slug||!qn.name,onClick:()=>void K(async()=>{await V.createCluster({slug:qn.slug,name:qn.name,region:qn.region||null}),Jn({slug:``,name:``,region:``})},`Кластер создан.`),children:H.createCluster})]}),(0,C.jsxs)(`article`,{className:`card`,children:[(0,C.jsx)(`h3`,{children:`Что такое технический код?`}),(0,C.jsx)(`p`,{className:`muted`,children:H.slugHelp}),(0,C.jsx)(`p`,{className:`muted`,children:`Для человека основное поле — название. Для системы и операторов — технический код. Он нужен, чтобы сценарии, логи и будущие endpoint-адреса не зависели от переименования кластера.`})]})]}),T===`cluster-settings`&&(0,C.jsxs)(`section`,{className:`grid two`,children:[!U&&(0,C.jsx)(pe,{title:`Кластер не выбран`,text:`Выберите активный кластер, чтобы открыть настройки.`}),U&&(0,C.jsxs)(C.Fragment,{children:[(0,C.jsxs)(`article`,{className:`card`,children:[(0,C.jsx)(`h3`,{children:`Identity кластера`}),(0,C.jsx)(`p`,{className:`muted`,children:`Базовые параметры хранятся в PostgreSQL. Slug остается неизменяемым идентификатором для операторов и скриптов.`}),(0,C.jsxs)(Ae,{children:[(0,C.jsxs)(`label`,{children:[`ID`,(0,C.jsx)(`input`,{value:U.id,readOnly:!0})]}),(0,C.jsxs)(`label`,{children:[`Slug`,(0,C.jsx)(`input`,{value:U.slug,readOnly:!0})]}),(0,C.jsxs)(`label`,{children:[`Название`,(0,C.jsx)(`input`,{value:Yn.name,onChange:e=>Xn({...Yn,name:e.target.value})})]}),(0,C.jsxs)(`label`,{children:[`Статус`,(0,C.jsxs)(`select`,{value:Yn.status,onChange:e=>Xn({...Yn,status:e.target.value}),children:[(0,C.jsx)(`option`,{value:`active`,children:`active, работает`}),(0,C.jsx)(`option`,{value:`disabled`,children:`disabled, отключен`})]})]}),(0,C.jsxs)(`label`,{children:[`Регион`,(0,C.jsx)(`input`,{value:Yn.region,onChange:e=>Xn({...Yn,region:e.target.value}),placeholder:`например ru-msk-1`})]}),(0,C.jsxs)(`label`,{children:[`Обновлен`,(0,C.jsx)(`input`,{value:F(U.updated_at||U.created_at),readOnly:!0})]})]}),(0,C.jsxs)(`label`,{className:`wideLabel`,children:[`Metadata JSON`,(0,C.jsx)(`textarea`,{value:Yn.metadataJson,onChange:e=>Xn({...Yn,metadataJson:e.target.value}),rows:8,spellCheck:!1})]}),(0,C.jsx)(`button`,{className:`primary`,disabled:!Yn.name.trim(),onClick:()=>vn(`Сохранить базовые настройки кластера`)&&void K(async()=>{let e=Me(Yn.metadataJson||`{}`,`Metadata JSON`);await V.updateCluster(U.id,{name:Yn.name,status:Yn.status,region:Yn.region||null,metadata:e})},`Настройки кластера сохранены.`),children:`Сохранить настройки кластера`})]}),(0,C.jsxs)(`article`,{className:`card`,children:[(0,C.jsx)(`h3`,{children:`Authority и режим изменений`}),(0,C.jsx)(`p`,{className:`muted`,children:`Эта секция защищает кластер от split-brain: minority/read-only сегменты не должны принимать изменения политик.`}),(0,C.jsxs)(`div`,{className:`stateGrid`,children:[(0,C.jsx)(A,{label:`Authority`,value:Ce?.authority_state||`неизвестно`}),(0,C.jsx)(A,{label:`Mutation mode`,value:Ce?.mutation_mode||`неизвестно`}),(0,C.jsx)(A,{label:`Term`,value:String(Ce?.term??`н/д`)}),(0,C.jsx)(A,{label:`Cluster key`,value:P(hi?.cluster_key_fingerprint)}),(0,C.jsx)(A,{label:`Последнее изменение`,value:F(Ce?.updated_at)})]}),(0,C.jsxs)(Ae,{children:[(0,C.jsxs)(`label`,{children:[`Состояние authority`,(0,C.jsxs)(`select`,{value:ar.authorityState,onChange:e=>or({...ar,authorityState:e.target.value}),children:[(0,C.jsx)(`option`,{value:`authoritative`,children:`authoritative, основной`}),(0,C.jsx)(`option`,{value:`minority`,children:`minority, меньшинство`}),(0,C.jsx)(`option`,{value:`isolated`,children:`isolated, изолирован`}),(0,C.jsx)(`option`,{value:`recovery`,children:`recovery, восстановление`})]})]}),(0,C.jsxs)(`label`,{children:[`Режим изменений`,(0,C.jsxs)(`select`,{value:ar.mutationMode,onChange:e=>or({...ar,mutationMode:e.target.value}),children:[(0,C.jsx)(`option`,{value:`normal`,children:`normal, обычный`}),(0,C.jsx)(`option`,{value:`read_only`,children:`read_only, только чтение`}),(0,C.jsx)(`option`,{value:`recovery_override`,children:`recovery_override, восстановление`})]})]}),(0,C.jsxs)(`label`,{children:[`Примечание`,(0,C.jsx)(`input`,{value:ar.notes,onChange:e=>or({...ar,notes:e.target.value})})]})]}),(0,C.jsx)(`button`,{disabled:!E,onClick:()=>vn(`Изменить authority state кластера`)&&void K(()=>V.updateClusterAuthority(E,{authorityState:ar.authorityState,mutationMode:ar.mutationMode,notes:ar.notes}),`Authority кластера обновлен.`),children:`Обновить authority`})]}),(0,C.jsxs)(`article`,{className:`card`,children:[(0,C.jsx)(`h3`,{children:`Safety / quorum`}),(0,C.jsxs)(`div`,{className:`stateGrid`,children:[(0,C.jsx)(A,{label:`Узлы`,value:String(hi?.node_count??j.length)}),(0,C.jsx)(A,{label:`Healthy`,value:String(hi?.healthy_node_count??zi)}),(0,C.jsx)(A,{label:`Pending join`,value:String(hi?.pending_join_count??Fe.filter(e=>e.status===`pending`).length)}),(0,C.jsx)(A,{label:`Последний узел`,value:F(hi?.last_node_seen_at)})]}),(0,C.jsx)(`p`,{className:`muted`,children:`Минимальный размер, quorum policy и split-brain rules пока не имеют отдельного runtime-переключателя. Сейчас защита выполняется через authority/mutation mode, explicit node approval и аудит.`})]}),(0,C.jsxs)(`article`,{className:`card`,children:[(0,C.jsx)(`h3`,{children:`Telemetry / testing`}),(0,C.jsxs)(`div`,{className:`stateGrid`,children:[(0,C.jsx)(A,{label:`Telemetry flag`,value:Hi?.telemetry_enabled?`включен`:`выключен`}),(0,C.jsx)(A,{label:`Synthetic links`,value:Hi?.synthetic_links_enabled?`включены`:`выключены`}),(0,C.jsx)(A,{label:`Хранение истории, часов`,value:String(Hi?.history_retention_hours??`н/д`)})]}),(0,C.jsx)(`p`,{className:`muted`,children:`Это тестовый контур наблюдаемости: heartbeat/telemetry реальные, а связи Fabric сейчас synthetic. Production mesh traffic здесь пока не отображается.`})]}),(0,C.jsxs)(`article`,{className:`card`,children:[(0,C.jsx)(`h3`,{children:`Storage / updates`}),(0,C.jsxs)(`div`,{className:`stateGrid`,children:[(0,C.jsx)(A,{label:`Version Storage`,value:`архитектура зафиксирована, runtime не реализован`}),(0,C.jsx)(A,{label:`Update cache`,value:`${ht(`update-cache`,tt).length} узл.`}),(0,C.jsx)(A,{label:`File/config cache`,value:`${ht(`file-storage-cache`,tt).length} узл.`})]}),(0,C.jsx)(`p`,{className:`muted`,children:`Version Storage будет хранить stable/current/candidate и signed artifacts. Сейчас это не production updater runtime.`})]}),(0,C.jsxs)(`article`,{className:`card`,children:[(0,C.jsx)(`h3`,{children:`Admin endpoints`}),(0,C.jsxs)(`div`,{className:`stateGrid`,children:[(0,C.jsx)(A,{label:`Entry nodes`,value:`${ht(`entry-node`,tt).length} узл.`}),(0,C.jsx)(A,{label:`Relay nodes`,value:`${ht(`relay-node`,tt).length} узл.`}),(0,C.jsx)(A,{label:`Core mesh`,value:`${ht(`core-mesh`,tt).length} узл.`})]}),(0,C.jsx)(`p`,{className:`muted`,children:`Панель кластера не переезжает автоматически на storage-узел. Cluster Admin Endpoint должен быть назначен отдельной explicit ролью на ingress/admin-capable узле.`})]})]})]}),T===`nodes`&&(0,C.jsxs)(`section`,{className:`grid two`,children:[(0,C.jsxs)(`article`,{className:`card span2`,children:[(0,C.jsxs)(`div`,{className:`cardHead`,children:[(0,C.jsxs)(`div`,{children:[(0,C.jsx)(`h3`,{children:H.nodeManagement}),(0,C.jsx)(`p`,{className:`muted`,children:`Единый краткий список узлов. По умолчанию показан активный кластер; включите общий режим, чтобы увидеть весь инвентарь платформы.`})]}),(0,C.jsxs)(`div`,{className:`actions`,children:[(0,C.jsxs)(`label`,{className:`checkLine`,children:[(0,C.jsx)(`input`,{type:`checkbox`,checked:lr===`all`,onChange:e=>ur(e.target.checked?`all`:`cluster`)}),H.showAllPlatformNodes]}),(0,C.jsx)(`button`,{className:`ghost`,onClick:()=>{ur(`all`),fr(``)},children:H.showAllPlatformNodes})]})]}),(0,C.jsxs)(`div`,{className:`signalStrip compact`,children:[(0,C.jsx)(k,{label:`Узлы активного кластера`,value:String(j.length)}),(0,C.jsx)(k,{label:`Все узлы`,value:String(wi.length)}),(0,C.jsx)(k,{label:`Заявки`,value:String(Ri)}),(0,C.jsx)(k,{label:`Активные роли`,value:String(Vi)})]}),(0,C.jsx)(`p`,{className:`muted`,children:H.addNodeText})]}),(0,C.jsxs)(`article`,{className:`card span2`,children:[(0,C.jsxs)(`div`,{className:`cardHead`,children:[(0,C.jsxs)(`div`,{children:[(0,C.jsx)(`h3`,{children:H.nodeBriefList}),(0,C.jsx)(`p`,{className:`muted`,children:H.nodeBriefListHelp})]}),(0,C.jsx)(`span`,{className:`pill`,children:Ti.length})]}),(0,C.jsxs)(Ae,{children:[(0,C.jsxs)(`label`,{children:[H.nodeSearch,(0,C.jsx)(`input`,{value:dr,onChange:e=>fr(e.target.value),placeholder:H.nodeSearchPlaceholder})]}),(0,C.jsxs)(`label`,{children:[H.nodeGroupFilter,(0,C.jsxs)(`select`,{value:pr,onChange:e=>mr(e.target.value),children:[(0,C.jsx)(`option`,{value:``,children:H.allNodeGroups}),Ee.map(e=>(0,C.jsx)(`option`,{value:e.id,children:dt(e,Ee)},e.id))]})]})]}),(0,C.jsx)(`p`,{className:`muted`,children:H.nodeGroupInventoryText}),(0,C.jsx)(`h4`,{children:H.nodeGroupCreatePanel}),(0,C.jsxs)(Ae,{children:[(0,C.jsxs)(`label`,{children:[H.nodeGroupName,(0,C.jsx)(`input`,{value:Zn.name,onChange:e=>Qn({...Zn,name:e.target.value})})]}),(0,C.jsxs)(`label`,{children:[H.parentNodeGroup,(0,C.jsxs)(`select`,{value:Zn.parentGroupId,onChange:e=>Qn({...Zn,parentGroupId:e.target.value}),children:[(0,C.jsx)(`option`,{value:``,children:H.rootNodeGroup}),Ee.map(e=>(0,C.jsx)(`option`,{value:e.id,children:dt(e,Ee)},e.id))]})]}),(0,C.jsxs)(`label`,{children:[H.createNodeGroup,(0,C.jsx)(`button`,{className:`primary`,disabled:!Zn.name.trim(),onClick:()=>void K(async()=>{await V.createNodeGroup(E,{name:Zn.name,parentGroupId:Zn.parentGroupId||null}),Qn({name:``,parentGroupId:``})},H.nodeGroupCreated),children:H.createNodeGroup})]})]}),(0,C.jsxs)(`div`,{className:`nodeList`,children:[Oi.map(e=>{if(e.kind===`group`){let t=hr.includes(e.key);return(0,C.jsxs)(`div`,{className:`nodeListGroup`,style:{paddingLeft:`${e.depth*18}px`},children:[(0,C.jsxs)(`div`,{className:`nodeListMain`,children:[(0,C.jsx)(`strong`,{children:e.label}),e.groupId&&(0,C.jsx)(`span`,{children:ft(e.groupId,Ee)})]}),(0,C.jsxs)(`div`,{className:`actions`,children:[(0,C.jsx)(`span`,{className:`pill`,children:e.count}),(0,C.jsx)(`button`,{className:`ghost`,onClick:()=>gr(Zt(hr,e.key)),children:t?H.expandGroup:H.collapseGroup}),e.groupId&&(0,C.jsx)(`button`,{className:`ghost`,onClick:()=>Qn({name:``,parentGroupId:e.groupId||``}),children:H.createSubgroup})]})]},e.key)}let t=e.entry,n=t.memberships.find(e=>e.cluster.id===E),r=n?.node||t.node,i=rt(r,St[r.id]||[],Ft),a=Re(r,Je[r.id],Ue),o=We(Qe[r.id]||[]),s=n?.node.membership_status===`active`,c=n?.node.membership_status===`revoked`;return(0,C.jsxs)(`div`,{className:`nodeListRow`,style:{marginLeft:`${e.depth*18}px`},children:[(0,C.jsxs)(`div`,{className:`nodeListMain`,children:[(0,C.jsx)(`strong`,{children:r.name}),(0,C.jsx)(`span`,{children:r.node_key}),(0,C.jsx)(`small`,{className:`muted`,children:i.address})]}),(0,C.jsx)(de,{value:r.health_status}),(0,C.jsx)(ve,{runtime:i}),(0,C.jsxs)(`div`,{className:`nodeEndpointCell`,children:[(0,C.jsx)(`strong`,{children:r.reported_version||`версия неизвестна`}),(0,C.jsx)(`small`,{children:a.targetLabel})]}),(0,C.jsx)(de,{value:a.status}),(0,C.jsxs)(`div`,{className:`nodeEndpointCell`,children:[(0,C.jsx)(`strong`,{className:`pill ${o.tone}`,children:o.label}),(0,C.jsx)(`small`,{children:o.detail})]}),(0,C.jsx)(`span`,{className:`muted`,children:F(r.last_seen_at)}),n?(0,C.jsx)(de,{value:n.node.membership_status}):(0,C.jsx)(`span`,{className:`muted`,children:H.notMemberOfActiveCluster}),(0,C.jsxs)(`div`,{className:`actions`,children:[(0,C.jsx)(`button`,{onClick:()=>{wr(t),Er(`details`)},children:H.nodeDetails}),s?(0,C.jsxs)(C.Fragment,{children:[(0,C.jsx)(`button`,{className:`primary`,onClick:()=>{wr(t),Er(`manage`)},children:H.manageNode}),(0,C.jsx)(`button`,{className:`danger`,onClick:()=>vn(`Удалить узел ${r.name} из кластера`)&&void K(()=>V.deleteClusterNode(E,r.id,`Удалено из списка узлов панели владельца платформы.`),`Узел удален из кластера.`),children:`Удалить`})]}):c?(0,C.jsx)(`span`,{className:`muted`,children:H.revokedMembership}):(0,C.jsx)(`button`,{className:`primary`,onClick:()=>{br(t),Sr([])},children:H.connectExistingNode})]})]},e.key)}),Oi.length===0&&(0,C.jsx)(pe,{title:H.noNodesTitle,text:H.noNodesByFilter})]})]}),yr&&(0,C.jsx)(`div`,{className:`modalBackdrop`,role:`presentation`,children:(0,C.jsxs)(`div`,{className:`modalCard`,role:`dialog`,"aria-modal":`true`,"aria-labelledby":`attach-node-title`,children:[(0,C.jsxs)(`div`,{className:`cardHead`,children:[(0,C.jsxs)(`div`,{children:[(0,C.jsx)(`h3`,{id:`attach-node-title`,children:H.connectExistingNodeTitle}),(0,C.jsx)(`p`,{className:`muted`,children:H.connectExistingNodeText})]}),(0,C.jsx)(`button`,{className:`ghost`,onClick:()=>br(null),children:H.cancel})]}),(0,C.jsxs)(`div`,{className:`stateList`,children:[(0,C.jsx)(A,{label:`Узел`,value:yr.node.name}),(0,C.jsx)(A,{label:`Node key`,value:yr.node.node_key}),(0,C.jsx)(A,{label:H.activeCluster,value:U?.name||E})]}),(0,C.jsx)(`div`,{className:`checkGrid`,children:ne.map(e=>(0,C.jsxs)(`label`,{className:`checkLine`,children:[(0,C.jsx)(`input`,{type:`checkbox`,checked:xr.includes(e),onChange:()=>Sr(Zt(xr,e))}),Ie(e)]},e))}),(0,C.jsxs)(`div`,{className:`actions`,children:[(0,C.jsx)(`button`,{className:`primary`,onClick:()=>void K(async()=>{await V.attachExistingNode(E,yr.node.id,xr),br(null),Sr([]),ur(`cluster`)},`Узел подключен к активному кластеру.`),children:H.connectWithRoles}),(0,C.jsx)(`button`,{onClick:()=>br(null),children:H.cancel})]})]})}),Cr&&(()=>{let e=Cr.memberships.find(e=>e.cluster.id===E),t=e?.node||Cr.node,n=e?(St[t.id]||[])[0]:void 0,r=e?(tt[t.id]||[]).filter(e=>e.status===`active`):[],i=e&&it[t.id]||[],a=e&&ot[t.id]||[];return(0,C.jsx)(`div`,{className:`modalBackdrop`,role:`presentation`,children:(0,C.jsxs)(`div`,{className:`modalCard wide`,role:`dialog`,"aria-modal":`true`,"aria-labelledby":`node-info-title`,children:[(0,C.jsxs)(`div`,{className:`cardHead`,children:[(0,C.jsxs)(`div`,{children:[(0,C.jsxs)(`h3`,{id:`node-info-title`,children:[Tr===`manage`?H.manageNode:H.nodeDetails,`: `,t.name]}),(0,C.jsx)(`p`,{className:`muted`,children:t.node_key})]}),(0,C.jsx)(`button`,{className:`ghost`,onClick:()=>{wr(null),Er(`details`)},children:H.close})]}),(0,C.jsxs)(`section`,{className:`nodePanel`,children:[(0,C.jsx)(`h4`,{children:H.nodeIdentity}),(0,C.jsxs)(`div`,{className:`stateList`,children:[(0,C.jsx)(A,{label:`Node ID`,value:P(t.id)}),(0,C.jsx)(A,{label:`Ключ узла`,value:t.node_key}),(0,C.jsx)(A,{label:`Тип владения`,value:I(t.ownership_type)}),(0,C.jsx)(A,{label:`Owner org`,value:P(t.owner_organization_id)}),(0,C.jsx)(A,{label:`Регистрация`,value:I(t.registration_status)}),(0,C.jsx)(A,{label:`Здоровье`,value:I(t.health_status)}),(0,C.jsx)(A,{label:`Версия`,value:t.reported_version||`неизвестно`}),(0,C.jsx)(A,{label:`Последний сигнал`,value:F(t.last_seen_at)})]})]}),(0,C.jsxs)(`section`,{className:`nodePanel`,children:[(0,C.jsx)(`h4`,{children:H.clusterMemberships}),(0,C.jsx)(`div`,{className:`membershipList`,children:Cr.memberships.map(e=>(0,C.jsxs)(`span`,{className:e.cluster.id===E?`pill good`:`pill`,children:[e.cluster.name,`: `,I(e.node.membership_status)]},e.cluster.id))})]}),e?(0,C.jsxs)(C.Fragment,{children:[(0,C.jsxs)(`section`,{className:`nodePanel`,children:[(0,C.jsx)(`h4`,{children:H.activeClusterScope}),(0,C.jsxs)(`div`,{className:`stateList`,children:[(0,C.jsx)(A,{label:`Участие`,value:I(t.membership_status)}),(0,C.jsx)(A,{label:`Сегмент`,value:I(t.partition_state)}),(0,C.jsx)(A,{label:`Группа`,value:t.node_group_name||H.ungroupedNodes}),(0,C.jsx)(A,{label:`Ролей`,value:String(r.length)}),(0,C.jsx)(A,{label:`Desired-сервисов`,value:String(i.length)}),(0,C.jsx)(A,{label:`Observed-сервисов`,value:String(a.length)})]})]}),Tr===`details`&&(0,C.jsx)(ge,{node:t,memberships:Cr.memberships,activeRoles:r,desiredWorkloads:i,observedWorkloads:a,heartbeats:St[t.id]||[],telemetry:Nt[t.id]||[],updatePlan:Je[t.id],updateStatuses:Qe[t.id]||[],meshLinks:Ft.filter(e=>e.source_node_id===t.id||e.target_node_id===t.id),syntheticConfig:Lt[t.id],allNodes:j,onSetUpdatePolicy:(e,t,n)=>void K(async()=>{await V.upsertNodeUpdatePolicy(E,e.id,{product:t,channel:`dev`,targetVersion:n,strategy:`rolling`,enabled:!0,rollbackAllowed:!0,healthWindowSeconds:180})},n?`${t} поставлен в target ${n}.`:`${t} будет следовать latest dev.`),labels:H}),Tr===`manage`&&(0,C.jsxs)(`section`,{className:`nodePanel`,children:[(0,C.jsx)(`h4`,{children:H.nodeFunctions}),(0,C.jsx)(`p`,{className:`muted`,children:H.nodeFunctionsText}),(0,C.jsxs)(`label`,{className:`wideLabel`,children:[H.organizationScopeForEnable,(0,C.jsx)(`input`,{value:sr,onChange:e=>cr(e.target.value),placeholder:H.clusterWideRolePlaceholder})]}),(0,C.jsx)(`div`,{className:`functionList`,children:ne.map(e=>{let o=r.find(t=>t.role===e),s=i.find(t=>t.service_type===e),c=a.find(t=>t.service_type===e),l=rn(e,n),u=s?.desired_state||`not_configured`,f=c?.reported_state||`missing`,p=!!o&&u===`enabled`;return(0,C.jsxs)(`div`,{className:`functionRow`,children:[(0,C.jsxs)(`div`,{className:`nodeListMain`,children:[(0,C.jsx)(`strong`,{children:Ie(e)}),(0,C.jsx)(`span`,{children:on(e,n,d)})]}),(0,C.jsx)(fe,{label:H.rolePermission,value:o?H.permissionGranted:H.permissionDenied,tone:o?`info`:``}),(0,C.jsx)(fe,{label:H.desiredRuntime,value:I(u),tone:u===`enabled`?`good`:``}),(0,C.jsx)(fe,{label:H.observedRuntime,value:I(f),tone:f===`running`?`good`:f===`missing`?`warn`:``}),(0,C.jsx)(`span`,{className:`pill ${l}`,children:an(e,n,H)}),(0,C.jsxs)(`div`,{className:`actions`,children:[(0,C.jsx)(`button`,{className:p?``:`primary`,disabled:p,onClick:()=>void K(async()=>{o||await V.setRoleStatus(E,t.id,e,`active`,sr||void 0),await V.setDesiredWorkload(E,t.id,e,{desiredState:`enabled`,runtimeMode:`container`,config:{},environment:{}})},`${e}: функция включена.`),children:H.enableFunction}),(0,C.jsx)(`button`,{disabled:!o&&u!==`enabled`,onClick:()=>void K(async()=>{await V.setDesiredWorkload(E,t.id,e,{desiredState:`disabled`,runtimeMode:s?.runtime_mode||`container`,config:s?.config||{},environment:s?.environment||{}}),o&&await V.setRoleStatus(E,t.id,e,`disabled`,o.organization_id||void 0)},`${e}: функция выключена.`),children:H.disableFunction})]})]},e)})}),(()=>{let e=i.find(e=>e.service_type===`mesh-listener`)?.config||{},n=jr[t.id]||{listenAddr:String(e.listen_addr||`:19131`),mode:String(e.listen_port_mode||`auto`),autoRange:`${Number(e.auto_port_start||19131)}-${Number(e.auto_port_end||19231)}`,advertiseEndpoint:String(e.advertise_endpoint||``),advertiseTransport:String(e.advertise_transport||`direct_http`),connectivity:String(e.connectivity_mode||`private_lan`),nat:String(e.nat_type||`none`),region:String(e.region||``)},r=e=>Mr({...jr,[t.id]:{...n,...e}});return(0,C.jsxs)(`section`,{className:`nodePanel nestedPanel`,children:[(0,C.jsx)(`h4`,{children:`Mesh listener`}),(0,C.jsxs)(Ae,{children:[(0,C.jsxs)(`label`,{children:[`Listen addr`,(0,C.jsx)(`input`,{value:n.listenAddr,onChange:e=>r({listenAddr:e.target.value}),placeholder:`0.0.0.0:19131 или :19131`})]}),(0,C.jsxs)(`label`,{children:[`Port mode`,(0,C.jsxs)(`select`,{value:n.mode,onChange:e=>r({mode:e.target.value}),children:[(0,C.jsx)(`option`,{value:`auto`,children:`auto`}),(0,C.jsx)(`option`,{value:`manual`,children:`manual`}),(0,C.jsx)(`option`,{value:`disabled`,children:`disabled`})]})]}),(0,C.jsxs)(`label`,{children:[`Auto ports`,(0,C.jsx)(`input`,{value:n.autoRange,onChange:e=>r({autoRange:e.target.value}),placeholder:`19131-19231`})]}),(0,C.jsxs)(`label`,{children:[`Advertise endpoint`,(0,C.jsx)(`input`,{value:n.advertiseEndpoint,onChange:e=>r({advertiseEndpoint:e.target.value}),placeholder:`http://external-or-lan-ip:19131`})]}),(0,C.jsxs)(`label`,{children:[`Advertise transport`,(0,C.jsxs)(`select`,{value:n.advertiseTransport,onChange:e=>r({advertiseTransport:e.target.value}),children:[(0,C.jsx)(`option`,{value:`direct_http`,children:`direct_http`}),(0,C.jsx)(`option`,{value:`direct_https`,children:`direct_https`}),(0,C.jsx)(`option`,{value:`wss`,children:`wss`})]})]}),(0,C.jsxs)(`label`,{children:[`Connectivity`,(0,C.jsxs)(`select`,{value:n.connectivity,onChange:e=>r({connectivity:e.target.value}),children:[(0,C.jsx)(`option`,{value:`private_lan`,children:`private_lan`}),(0,C.jsx)(`option`,{value:`direct`,children:`direct`}),(0,C.jsx)(`option`,{value:`outbound_only`,children:`outbound_only`}),(0,C.jsx)(`option`,{value:`relay_required`,children:`relay_required`})]})]}),(0,C.jsxs)(`label`,{children:[`NAT`,(0,C.jsxs)(`select`,{value:n.nat,onChange:e=>r({nat:e.target.value}),children:[(0,C.jsx)(`option`,{value:`none`,children:`none`}),(0,C.jsx)(`option`,{value:`unknown`,children:`unknown`}),(0,C.jsx)(`option`,{value:`port_restricted`,children:`port_restricted`}),(0,C.jsx)(`option`,{value:`symmetric`,children:`symmetric`})]})]}),(0,C.jsxs)(`label`,{children:[`Region/site`,(0,C.jsx)(`input`,{value:n.region,onChange:e=>r({region:e.target.value}),placeholder:`dc1, office, docker-test`})]})]}),(0,C.jsx)(`div`,{className:`actions`,children:(0,C.jsx)(`button`,{className:`primary`,onClick:()=>void K(async()=>{let[e,r]=n.autoRange.split(`-`).map(e=>Number(e.trim())),i=Number.isFinite(e)?e:19131,a=Number.isFinite(r)?r:i;await V.setDesiredWorkload(E,t.id,`mesh-listener`,{desiredState:n.mode===`disabled`?`disabled`:`enabled`,version:`listener-${Date.now()}`,runtimeMode:`container`,config:{listen_addr:n.listenAddr,listen_port_mode:n.mode,auto_port_start:i,auto_port_end:a,advertise_endpoint:n.advertiseEndpoint.trim().replace(/\/$/,``)||null,advertise_transport:n.advertiseTransport||`direct_http`,connectivity_mode:n.connectivity,nat_type:n.nat,region:n.region||null},environment:{}})},`Mesh listener config обновлен.`),children:`Применить listener`})})]})})(),(0,C.jsx)(`div`,{className:`actions`,children:(0,C.jsxs)(`select`,{value:t.node_group_id||``,onChange:e=>void K(()=>V.assignNodeGroup(E,t.id,e.target.value||null),e.target.value?`Узел перемещен в группу.`:`Узел убран из группы.`),children:[(0,C.jsx)(`option`,{value:``,children:H.ungroupedNodes}),Ee.map(e=>(0,C.jsx)(`option`,{value:e.id,children:dt(e,Ee)},e.id))]})}),(0,C.jsxs)(`div`,{className:`actions`,children:[(0,C.jsx)(`button`,{onClick:()=>vn(`Отключить участие узла ${t.name}`)&&void K(()=>V.disableMembership(E,t.id,`Отключено из панели владельца платформы.`),`Участие узла отключено.`),children:`Отключить участие`}),(0,C.jsx)(`button`,{className:`danger`,onClick:()=>vn(`Отозвать identity узла ${t.name}`)&&void K(()=>V.revokeNodeIdentity(E,t.id,`Отозвано из панели владельца платформы.`),`Identity узла отозван.`),children:`Отозвать identity`})]})]})]}):(0,C.jsxs)(`section`,{className:`nodePanel`,children:[(0,C.jsx)(`h4`,{children:H.noActiveClusterMembership}),(0,C.jsx)(`div`,{className:`actions`,children:(0,C.jsx)(`button`,{className:`primary`,onClick:()=>{br(Cr),Sr([]),wr(null)},children:H.connectExistingNode})})]})]})})})(),!1]}),T===`enrollment`&&(0,C.jsxs)(`section`,{className:`grid two`,children:[(0,C.jsxs)(`article`,{className:`card`,children:[(0,C.jsx)(`h3`,{children:H.joinTokenTitle}),(0,C.jsx)(`p`,{className:`muted`,children:H.joinTokenText}),(0,C.jsxs)(Ae,{children:[(0,C.jsxs)(`label`,{children:[H.ttlHours,(0,C.jsx)(`input`,{type:`number`,min:1,max:720,value:L.ttlHours,onChange:e=>R({...L,ttlHours:Number(e.target.value)})}),(0,C.jsx)(`small`,{children:H.ttlHelp})]}),(0,C.jsxs)(`label`,{children:[H.maxUses,(0,C.jsx)(`input`,{type:`number`,min:1,max:100,value:L.maxUses,onChange:e=>R({...L,maxUses:Number(e.target.value)})}),(0,C.jsx)(`small`,{children:H.maxUsesHelp})]}),(0,C.jsxs)(`label`,{children:[H.nodeOwnership,(0,C.jsxs)(`select`,{value:L.ownershipType,onChange:e=>R({...L,ownershipType:e.target.value}),children:[(0,C.jsx)(`option`,{value:`platform_managed`,children:`platform_managed, управляется платформой`}),(0,C.jsx)(`option`,{value:`customer_managed`,children:`customer_managed, управляется клиентом`})]})]}),(0,C.jsxs)(`label`,{children:[H.tokenPurpose,(0,C.jsx)(`input`,{value:L.purpose,onChange:e=>R({...L,purpose:e.target.value}),placeholder:`например: стартовый entry-node в ru-msk-1`})]}),(0,C.jsxs)(`label`,{children:[`Имя нового узла`,(0,C.jsx)(`input`,{value:L.nodeName,onChange:e=>R({...L,nodeName:e.target.value}),placeholder:Et(L,U)}),(0,C.jsx)(`small`,{children:`Если оставить пустым, панель подставит имя автоматически.`})]}),(0,C.jsxs)(`label`,{children:[`Группа узла`,(0,C.jsxs)(`select`,{value:L.nodeGroupId,onChange:e=>R({...L,nodeGroupId:e.target.value}),children:[(0,C.jsx)(`option`,{value:``,children:`Без группы`}),Ee.map(e=>(0,C.jsx)(`option`,{value:e.id,children:dt(e,Ee)},e.id))]})]})]}),(0,C.jsxs)(`section`,{className:`nodePanel`,children:[(0,C.jsx)(`h4`,{children:`Install profile`}),(0,C.jsx)(`p`,{className:`muted`,children:`Эти поля попадут в install profile. Для Windows без админ-прав будет создан user startup task, с админ-правами - system startup task.`}),(0,C.jsx)(`div`,{className:`segmented`,children:[[`docker`,`Docker Linux`],[`linux_binary`,`Ubuntu service`],[`windows_service`,`Windows`]].map(([e,t])=>(0,C.jsx)(`button`,{type:`button`,className:L.installMode===e?`active`:``,onClick:()=>R({...L,installMode:e}),children:t},e))}),(0,C.jsx)(`div`,{className:`segmented`,children:[[`private_lan`,`LAN`],[`direct`,`Public`],[`nat_forward`,`NAT`],[`outbound_only`,`Outbound`]].map(([e,t])=>(0,C.jsx)(`button`,{type:`button`,className:wt(L)===e?`active`:``,onClick:()=>R(Tt(L,e)),children:t},e))}),(0,C.jsxs)(Ae,{children:[(0,C.jsxs)(`label`,{children:[`Control-plane endpoint`,(0,C.jsx)(`input`,{value:L.controlPlaneEndpoint,onChange:e=>R({...L,controlPlaneEndpoint:e.target.value}),placeholder:bt()})]}),(0,C.jsxs)(`label`,{children:[L.installMode===`windows_service`?`Windows node-agent artifact`:L.installMode===`linux_binary`?`Linux node-agent artifact`:`Docker image`,(0,C.jsx)(`input`,{value:L.dockerImage,onChange:e=>R({...L,dockerImage:e.target.value})})]}),L.installMode===`windows_service`&&(0,C.jsxs)(C.Fragment,{children:[(0,C.jsxs)(`label`,{children:[`Windows startup`,(0,C.jsxs)(`select`,{value:L.windowsStartupMode,onChange:e=>R({...L,windowsStartupMode:e.target.value}),children:[(0,C.jsx)(`option`,{value:`auto`,children:`auto: system task, fallback user task`}),(0,C.jsx)(`option`,{value:`system-task`,children:`system task, admin required`}),(0,C.jsx)(`option`,{value:`user-task`,children:`user task, no admin`}),(0,C.jsx)(`option`,{value:`none`,children:`none`})]})]}),(0,C.jsxs)(`label`,{children:[`Install dir`,(0,C.jsx)(`input`,{value:L.windowsInstallDir,onChange:e=>R({...L,windowsInstallDir:e.target.value}),placeholder:`C:\\\\Program Files\\\\RAP\\\\node-name`})]}),(0,C.jsxs)(`label`,{children:[`Windows node-agent SHA256`,(0,C.jsx)(`input`,{value:L.windowsNodeAgentSHA256,onChange:e=>R({...L,windowsNodeAgentSHA256:e.target.value}),placeholder:`опционально, но желательно для production`})]})]}),L.installMode===`linux_binary`&&(0,C.jsxs)(C.Fragment,{children:[(0,C.jsxs)(`label`,{children:[`Linux install dir`,(0,C.jsx)(`input`,{value:L.linuxInstallDir,onChange:e=>R({...L,linuxInstallDir:e.target.value}),placeholder:`/opt/rap/node-name`})]}),(0,C.jsxs)(`label`,{children:[`Linux node-agent SHA256`,(0,C.jsx)(`input`,{value:L.linuxNodeAgentSHA256,onChange:e=>R({...L,linuxNodeAgentSHA256:e.target.value}),placeholder:`опционально, но желательно для production`})]})]}),L.installMode===`docker`&&(0,C.jsxs)(`label`,{children:[`Container name`,(0,C.jsx)(`input`,{value:L.dockerContainerName,onChange:e=>R({...L,dockerContainerName:e.target.value}),placeholder:Dt(L,U)})]}),(0,C.jsxs)(`label`,{children:[`Artifact endpoints`,(0,C.jsx)(`input`,{value:L.artifactEndpoints,onChange:e=>R({...L,artifactEndpoints:e.target.value}),placeholder:xt()}),(0,C.jsx)(`small`,{children:`Через запятую: public/LAN/cache узлы, где host-agent сможет скачать image tar до входа в mesh.`})]}),L.installMode===`docker`&&(0,C.jsxs)(`label`,{children:[`Docker image tar SHA256`,(0,C.jsx)(`input`,{value:L.dockerImageArtifactSHA256,onChange:e=>R({...L,dockerImageArtifactSHA256:e.target.value}),placeholder:`опционально, но желательно для production`})]}),L.installMode===`docker`&&(0,C.jsxs)(`label`,{children:[`Docker network`,(0,C.jsxs)(`select`,{value:L.dockerNetwork,onChange:e=>R({...L,dockerNetwork:e.target.value}),children:[(0,C.jsx)(`option`,{value:`host`,children:`host`}),(0,C.jsx)(`option`,{value:`bridge`,children:`bridge`})]})]}),(0,C.jsxs)(`label`,{children:[`Listen addr`,(0,C.jsx)(`input`,{value:L.meshListenAddr,onChange:e=>R({...L,meshListenAddr:e.target.value})})]}),(0,C.jsxs)(`label`,{children:[`Listen mode`,(0,C.jsxs)(`select`,{value:L.meshListenPortMode,onChange:e=>R({...L,meshListenPortMode:e.target.value}),children:[(0,C.jsx)(`option`,{value:`auto`,children:`auto`}),(0,C.jsx)(`option`,{value:`manual`,children:`manual`}),(0,C.jsx)(`option`,{value:`disabled`,children:`disabled`})]})]}),(0,C.jsxs)(`label`,{children:[`Auto ports`,(0,C.jsx)(`input`,{value:`${L.meshListenAutoPortStart}-${L.meshListenAutoPortEnd}`,onChange:e=>{let[t,n]=e.target.value.split(`-`).map(e=>Number(e.trim()));R({...L,meshListenAutoPortStart:Number.isFinite(t)?t:L.meshListenAutoPortStart,meshListenAutoPortEnd:Number.isFinite(n)?n:L.meshListenAutoPortEnd})}})]}),(0,C.jsxs)(`label`,{children:[`Advertise endpoint`,(0,C.jsx)(`input`,{value:L.meshAdvertiseEndpoint,onChange:e=>R({...L,meshAdvertiseEndpoint:e.target.value}),placeholder:`http://public-or-private-ip:19131`})]}),(0,C.jsxs)(`label`,{children:[`Connectivity`,(0,C.jsxs)(`select`,{value:L.meshConnectivityMode,onChange:e=>R({...L,meshConnectivityMode:e.target.value}),children:[(0,C.jsx)(`option`,{value:`direct`,children:`direct`}),(0,C.jsx)(`option`,{value:`private_lan`,children:`private_lan`}),(0,C.jsx)(`option`,{value:`outbound_only`,children:`outbound_only`}),(0,C.jsx)(`option`,{value:`relay_required`,children:`relay_required`})]})]}),(0,C.jsxs)(`label`,{children:[`NAT`,(0,C.jsxs)(`select`,{value:L.meshNATType,onChange:e=>R({...L,meshNATType:e.target.value}),children:[(0,C.jsx)(`option`,{value:`none`,children:`none`}),(0,C.jsx)(`option`,{value:`unknown`,children:`unknown`}),(0,C.jsx)(`option`,{value:`full_cone`,children:`full_cone`}),(0,C.jsx)(`option`,{value:`port_restricted`,children:`port_restricted`}),(0,C.jsx)(`option`,{value:`symmetric`,children:`symmetric`})]})]}),(0,C.jsxs)(`label`,{children:[`Region/site`,(0,C.jsx)(`input`,{value:L.meshRegion,onChange:e=>R({...L,meshRegion:e.target.value})})]}),L.installMode===`docker`&&(0,C.jsxs)(`label`,{className:`checkLine`,children:[(0,C.jsx)(`input`,{type:`checkbox`,checked:L.pullImage,onChange:e=>R({...L,pullImage:e.target.checked})}),`Pull image`]}),(0,C.jsxs)(`label`,{className:`checkLine`,children:[(0,C.jsx)(`input`,{type:`checkbox`,checked:L.replace,onChange:e=>R({...L,replace:e.target.checked})}),`Replace existing install`]})]})]}),(0,C.jsxs)(`section`,{className:`nodePanel`,children:[(0,C.jsx)(`h4`,{children:H.suggestedRoles}),(0,C.jsx)(`p`,{className:`muted`,children:`Роли записываются в install token и автоматически назначаются узлу при approval. После создания token изменение чекбоксов не меняет уже выданный token.`}),(0,C.jsx)(`div`,{className:`checkGrid`,children:ne.map(e=>(0,C.jsxs)(`label`,{className:`checkLine`,children:[(0,C.jsx)(`input`,{type:`checkbox`,checked:L.roles.includes(e),onChange:()=>R({...L,roles:Zt(L.roles,e)})}),Ie(e)]},e))})]}),(0,C.jsxs)(`details`,{children:[(0,C.jsx)(`summary`,{children:H.generatedScope}),(0,C.jsx)(`p`,{className:`muted`,children:H.generatedScopeHelp}),(0,C.jsx)(`pre`,{className:`codePreview`,children:JSON.stringify(Si,null,2)})]}),(0,C.jsxs)(`p`,{className:`muted`,children:[H.manualApprovalRequired,`.`]}),(0,C.jsx)(`button`,{className:`primary`,disabled:!E,onClick:()=>void K(async()=>{ir(await V.createJoinToken(E,{ttlHours:L.ttlHours,maxUses:L.maxUses,scope:Si}))},`Join token создан.`),children:`Создать install token`}),rr&&(0,C.jsxs)(`div`,{className:`secretOnce`,children:[(0,C.jsx)(`strong`,{children:`Исходный token, возвращается один раз`}),(0,C.jsx)(`code`,{children:rr.token}),(0,C.jsxs)(`span`,{className:`muted`,children:[`Authority key: `,P(rr.authority_signature?.key_fingerprint)]}),(0,C.jsx)(`strong`,{children:`Scope выданного token`}),(0,C.jsx)(`pre`,{className:`codePreview`,children:JSON.stringify(rr.scope,null,2)}),(0,C.jsx)(`strong`,{children:`Docker host-agent install`}),(0,C.jsx)(`pre`,{className:`codePreview`,children:Ot(rr,U,Ci)}),(0,C.jsx)(`strong`,{children:`Profile-based Docker install`}),(0,C.jsx)(`pre`,{className:`codePreview`,children:kt(rr,U,Ci)}),(0,C.jsx)(`strong`,{children:`Profile-based Ubuntu service install`}),(0,C.jsx)(`pre`,{className:`codePreview`,children:At(rr,U,Ci)}),(0,C.jsx)(`strong`,{children:`Profile-based Windows PowerShell install`}),(0,C.jsx)(`pre`,{className:`codePreview`,children:jt(rr,U,Ci)}),(0,C.jsx)(`strong`,{children:`Profile-based Windows CMD install`}),(0,C.jsx)(`pre`,{className:`codePreview`,children:Mt(rr,U,Ci)})]})]}),(0,C.jsxs)(`article`,{className:`card`,children:[(0,C.jsx)(`h3`,{children:`Как добавить узел`}),(0,C.jsxs)(`div`,{className:`stateList`,children:[(0,C.jsxs)(`div`,{className:`stateLine`,children:[(0,C.jsx)(`span`,{children:`1`}),(0,C.jsx)(`strong`,{children:`Заполните Docker install profile слева.`})]}),(0,C.jsxs)(`div`,{className:`stateLine`,children:[(0,C.jsx)(`span`,{children:`2`}),(0,C.jsx)(`strong`,{children:`Нажмите “Создать install token”.`})]}),(0,C.jsxs)(`div`,{className:`stateLine`,children:[(0,C.jsx)(`span`,{children:`3`}),(0,C.jsx)(`strong`,{children:`Скопируйте “Profile-based Docker install” и выполните на Docker-хосте.`})]}),(0,C.jsxs)(`div`,{className:`stateLine`,children:[(0,C.jsx)(`span`,{children:`4`}),(0,C.jsx)(`strong`,{children:`Подтвердите join request в этой же вкладке.`})]})]})]}),(0,C.jsxs)(`article`,{className:`card`,children:[(0,C.jsx)(`h3`,{children:`Install tokens`}),(0,C.jsx)(je,{columns:[`scope`,`status`,`uses`,`expires`,`created`,`action`],rows:Be.map(e=>[Ke(e),I(e.status),`${e.used_count}/${e.max_uses}`,F(e.expires_at),F(e.created_at),e.status===`active`?(0,C.jsx)(`button`,{className:`danger`,onClick:()=>vn(`Отозвать install token ${P(e.id)}`)&&void K(()=>V.revokeJoinToken(E,e.id),`Install token отозван.`),children:`Отозвать`}):(0,C.jsx)(`span`,{className:`muted`,children:I(e.status)})])})]}),(0,C.jsxs)(`article`,{className:`card`,children:[(0,C.jsx)(`h3`,{children:`Заявки на подключение`}),(0,C.jsxs)(`div`,{className:`stack`,children:[Fe.map(e=>(0,C.jsxs)(`div`,{className:`requestCard`,children:[(0,C.jsxs)(`div`,{children:[(0,C.jsx)(`strong`,{children:e.node_name}),(0,C.jsx)(`p`,{children:e.node_fingerprint}),(0,C.jsx)(de,{value:e.status}),e.approval_signature?.key_fingerprint&&(0,C.jsxs)(`small`,{className:`muted`,children:[`approval key `,P(e.approval_signature.key_fingerprint)]})]}),(0,C.jsxs)(`div`,{className:`actions`,children:[(0,C.jsx)(`button`,{disabled:e.status!==`pending`,onClick:()=>void K(()=>V.approveJoinRequest(E,e.id),`Заявка одобрена.`),children:`Одобрить`}),(0,C.jsx)(`button`,{disabled:e.status!==`pending`,onClick:()=>void K(()=>V.rejectJoinRequest(E,e.id,`Отклонено из панели владельца платформы.`),`Заявка отклонена.`),children:`Отклонить`})]})]},e.id)),Fe.length===0&&(0,C.jsx)(pe,{title:`Нет заявок`,text:`Новые подключения node-agent появятся здесь.`})]})]})]}),T===`roles`&&(0,C.jsxs)(`section`,{className:`stack`,children:[(0,C.jsxs)(`article`,{className:`card`,children:[(0,C.jsx)(`h3`,{children:`Область ролей`}),(0,C.jsx)(`p`,{className:`muted`,children:`Capabilities — технические факты. Роли — явные разрешения. Область организации может ограничивать сервисные роли.`}),(0,C.jsxs)(`label`,{children:[`UUID организации для новых назначений ролей, опционально`,(0,C.jsx)(`input`,{value:sr,onChange:e=>cr(e.target.value),placeholder:`пусто = роль на весь кластер`})]})]}),j.map(e=>(0,C.jsxs)(`article`,{className:`card roleRow`,children:[(0,C.jsxs)(`div`,{children:[(0,C.jsx)(`h3`,{children:e.name}),(0,C.jsx)(`p`,{children:Pe(tt[e.id]||[])})]}),(0,C.jsxs)(`select`,{defaultValue:``,onChange:t=>{let n=t.target.value;t.currentTarget.value=``,n&&K(()=>V.assignRole(E,e.id,n,sr||void 0),`${n} назначена узлу ${e.name}.`)},children:[(0,C.jsx)(`option`,{value:``,children:`Назначить роль...`}),ne.map(e=>(0,C.jsx)(`option`,{value:e,children:Ie(e)},e))]})]},e.id))]}),T===`workloads`&&(0,C.jsxs)(`section`,{className:`grid two`,children:[(0,C.jsxs)(`article`,{className:`card`,children:[(0,C.jsx)(`h3`,{children:`Желаемое состояние сервиса`}),(0,C.jsx)(`p`,{className:`muted`,children:`Здесь задается только желаемое состояние. Runtime-исполнение остается под контролем node-agent и политик.`}),(0,C.jsxs)(Ae,{children:[(0,C.jsxs)(`label`,{children:[`Узел`,(0,C.jsxs)(`select`,{value:Ur.nodeId,onChange:e=>Wr({...Ur,nodeId:e.target.value}),children:[(0,C.jsx)(`option`,{value:``,children:`Выберите узел...`}),j.map(e=>(0,C.jsx)(`option`,{value:e.id,children:e.name},e.id))]})]}),(0,C.jsxs)(`label`,{children:[`Сервис`,(0,C.jsx)(`select`,{value:Ur.serviceType,onChange:e=>Wr({...Ur,serviceType:e.target.value}),children:ne.map(e=>(0,C.jsx)(`option`,{value:e,children:Ie(e)},e))})]}),(0,C.jsxs)(`label`,{children:[`Желаемое состояние`,(0,C.jsxs)(`select`,{value:Ur.desiredState,onChange:e=>Wr({...Ur,desiredState:e.target.value}),children:[(0,C.jsx)(`option`,{value:`enabled`,children:`включено`}),(0,C.jsx)(`option`,{value:`disabled`,children:`выключено`})]})]}),(0,C.jsxs)(`label`,{children:[`Режим runtime`,(0,C.jsxs)(`select`,{value:Ur.runtimeMode,onChange:e=>Wr({...Ur,runtimeMode:e.target.value}),children:[(0,C.jsx)(`option`,{value:`container`,children:`контейнер`}),(0,C.jsx)(`option`,{value:`native`,children:`нативно`})]})]}),(0,C.jsxs)(`label`,{children:[`Версия`,(0,C.jsx)(`input`,{value:Ur.version,onChange:e=>Wr({...Ur,version:e.target.value})})]})]}),(0,C.jsxs)(`label`,{children:[`Config JSON`,(0,C.jsx)(`textarea`,{value:Ur.configJson,onChange:e=>Wr({...Ur,configJson:e.target.value})})]}),(0,C.jsxs)(`label`,{children:[`Environment JSON`,(0,C.jsx)(`textarea`,{value:Ur.environmentJson,onChange:e=>Wr({...Ur,environmentJson:e.target.value})})]}),(0,C.jsx)(`button`,{className:`primary`,disabled:!Ur.nodeId||!E,onClick:()=>void K(()=>V.setDesiredWorkload(E,Ur.nodeId,Ur.serviceType,{desiredState:Ur.desiredState,runtimeMode:Ur.runtimeMode,version:Ur.version,config:Me(Ur.configJson,`config сервиса`),environment:Me(Ur.environmentJson,`environment сервиса`)}),`Желаемое состояние сервиса обновлено.`),children:`Задать желаемое состояние`})]}),(0,C.jsxs)(`article`,{className:`card`,children:[(0,C.jsx)(`h3`,{children:`Отчеты сервисов`}),(0,C.jsx)(`div`,{className:`stack`,children:j.map(e=>(0,C.jsxs)(`div`,{className:`workloadBlock`,children:[(0,C.jsx)(`strong`,{children:e.name}),(ot[e.id]||[]).length===0?(0,C.jsx)(`p`,{className:`muted`,children:`Статус пока не получен.`}):(0,C.jsx)(je,{columns:[`сервис`,`состояние`,`runtime`,`наблюдение`],rows:(ot[e.id]||[]).map(e=>[e.service_type,e.reported_state,e.runtime_mode,F(e.observed_at)])})]},e.id))})]})]}),T===`fabric`&&(0,C.jsxs)(`section`,{className:`grid two`,children:[(0,C.jsxs)(`article`,{className:`card span2`,children:[(0,C.jsx)(`h3`,{children:`Граница подготовки Fabric`}),(0,C.jsx)(`p`,{className:`muted`,children:"Этот экран показывает synthetic/control-plane подготовку и C17Z11 boundary: production forwarding доступен только для route-bound `fabric.control` при явном gate. Service traffic, RDP, VPN и произвольный relay здесь не включены."}),(0,C.jsxs)(`div`,{className:`signalStrip`,children:[(0,C.jsx)(k,{label:`Synthetic configs`,value:`${Wi}/${j.length}`}),(0,C.jsx)(k,{label:`Routes`,value:String(Gi)}),(0,C.jsx)(k,{label:`Endpoints / candidates`,value:`${Ki}/${qi}`}),(0,C.jsx)(k,{label:`Peer dir / seeds`,value:`${Ji}/${Yi}`}),(0,C.jsx)(k,{label:`Scoped production flag`,value:Xi===0?`false`:`true:${Xi}`})]})]}),(0,C.jsxs)(`article`,{className:`card`,children:[(0,C.jsxs)(`div`,{className:`cardHead`,children:[(0,C.jsxs)(`div`,{children:[(0,C.jsx)(`h3`,{children:H.fabricEntryPoints}),(0,C.jsx)(`p`,{className:`muted`,children:H.fabricEntryPointHelp})]}),(0,C.jsx)(`span`,{className:`pill`,children:Vt.length})]}),(0,C.jsxs)(Ae,{children:[(0,C.jsxs)(`label`,{children:[H.endpointName,(0,C.jsx)(`input`,{value:$n.name,onChange:e=>er({...$n,name:e.target.value})})]}),(0,C.jsxs)(`label`,{children:[H.endpointType,(0,C.jsxs)(`select`,{value:$n.endpointType,onChange:e=>er({...$n,endpointType:e.target.value}),children:[(0,C.jsx)(`option`,{value:`client_access`,children:`client_access`}),(0,C.jsx)(`option`,{value:`admin`,children:`admin`}),(0,C.jsx)(`option`,{value:`api`,children:`api`}),(0,C.jsx)(`option`,{value:`other`,children:`other`})]})]}),(0,C.jsxs)(`label`,{className:`span2`,children:[H.publicEndpoint,(0,C.jsx)(`input`,{placeholder:`wss://entry.example.com`,value:$n.publicEndpoint,onChange:e=>er({...$n,publicEndpoint:e.target.value})})]})]}),(0,C.jsx)(`div`,{className:`actions`,children:(0,C.jsx)(`button`,{className:`primary`,disabled:!$n.name.trim(),onClick:()=>void K(async()=>{await V.createFabricEntryPoint(E,{name:$n.name,endpointType:$n.endpointType,publicEndpoint:$n.publicEndpoint||null}),er({name:``,endpointType:`client_access`,publicEndpoint:``})},`Точка входа создана.`),children:H.createEntryPoint})}),(0,C.jsxs)(`div`,{className:`stack`,children:[Vt.map(e=>{let t=Ut[e.id]||[];return(0,C.jsxs)(`section`,{className:`nodePanel`,children:[(0,C.jsxs)(`div`,{className:`cardHead`,children:[(0,C.jsxs)(`div`,{children:[(0,C.jsx)(`h4`,{children:e.name}),(0,C.jsxs)(`p`,{className:`muted`,children:[e.endpoint_type,` · `,e.public_endpoint||H.addressNotSet]})]}),(0,C.jsx)(de,{value:e.status})]}),(0,C.jsx)(`h5`,{children:H.endpointNodes}),t.length===0?(0,C.jsx)(`p`,{className:`muted`,children:H.assignedNodesEmpty}):(0,C.jsx)(`div`,{className:`membershipList`,children:t.map(t=>(0,C.jsxs)(`span`,{className:t.status===`active`?`pill good`:`pill`,children:[ut(j,t.node_id),` · `,I(t.status),` · p`,t.priority]},`${e.id}-${t.node_id}`))}),(0,C.jsxs)(`div`,{className:`actions`,children:[(0,C.jsxs)(`select`,{value:Nr[e.id]||``,onChange:t=>Pr({...Nr,[e.id]:t.target.value}),children:[(0,C.jsx)(`option`,{value:``,children:H.selectNode}),j.map(e=>(0,C.jsx)(`option`,{value:e.id,children:e.name},e.id))]}),(0,C.jsx)(`button`,{disabled:!Nr[e.id],onClick:()=>void K(()=>V.setFabricEntryPointNode(E,e.id,Nr[e.id],{status:`active`}),`Узел назначен точке входа.`),children:H.assignEndpointNode})]})]},e.id)}),Vt.length===0&&(0,C.jsx)(pe,{title:H.fabricEntryPoints,text:H.entryPointsEmpty})]})]}),(0,C.jsxs)(`article`,{className:`card`,children:[(0,C.jsxs)(`div`,{className:`cardHead`,children:[(0,C.jsxs)(`div`,{children:[(0,C.jsx)(`h3`,{children:H.fabricEgressPools}),(0,C.jsx)(`p`,{className:`muted`,children:H.fabricEgressPoolHelp})]}),(0,C.jsx)(`span`,{className:`pill`,children:Gt.length})]}),(0,C.jsxs)(Ae,{children:[(0,C.jsxs)(`label`,{children:[H.endpointName,(0,C.jsx)(`input`,{value:tr.name,onChange:e=>nr({...tr,name:e.target.value})})]}),(0,C.jsxs)(`label`,{children:[H.description,(0,C.jsx)(`input`,{value:tr.description,onChange:e=>nr({...tr,description:e.target.value})})]}),(0,C.jsxs)(`label`,{className:`span2`,children:[H.routeScope,(0,C.jsx)(`textarea`,{rows:5,value:tr.routeScope,onChange:e=>nr({...tr,routeScope:e.target.value})})]})]}),(0,C.jsx)(`div`,{className:`actions`,children:(0,C.jsx)(`button`,{className:`primary`,disabled:!tr.name.trim(),onClick:()=>void K(async()=>{let e=Me(tr.routeScope,`Route scope JSON`);await V.createFabricEgressPool(E,{name:tr.name,description:tr.description||null,routeScope:e}),nr({name:``,description:``,routeScope:`{ - "routes": [] -}`})},`Выходная зона создана.`),children:H.createEgressPool})}),(0,C.jsxs)(`div`,{className:`stack`,children:[Gt.map(e=>{let t=qt[e.id]||[];return(0,C.jsxs)(`section`,{className:`nodePanel`,children:[(0,C.jsxs)(`div`,{className:`cardHead`,children:[(0,C.jsxs)(`div`,{children:[(0,C.jsx)(`h4`,{children:e.name}),(0,C.jsx)(`p`,{className:`muted`,children:e.description||H.descriptionNotSet})]}),(0,C.jsx)(de,{value:e.status})]}),(0,C.jsx)(`p`,{className:`muted`,children:JSON.stringify(e.route_scope||{})}),(0,C.jsx)(`h5`,{children:H.endpointNodes}),t.length===0?(0,C.jsx)(`p`,{className:`muted`,children:H.assignedNodesEmpty}):(0,C.jsx)(`div`,{className:`membershipList`,children:t.map(t=>(0,C.jsxs)(`span`,{className:t.status===`active`?`pill good`:`pill`,children:[ut(j,t.node_id),` · `,I(t.status),` · p`,t.priority]},`${e.id}-${t.node_id}`))}),(0,C.jsxs)(`div`,{className:`actions`,children:[(0,C.jsxs)(`select`,{value:Fr[e.id]||``,onChange:t=>Ir({...Fr,[e.id]:t.target.value}),children:[(0,C.jsx)(`option`,{value:``,children:H.selectNode}),j.map(e=>(0,C.jsx)(`option`,{value:e.id,children:e.name},e.id))]}),(0,C.jsx)(`button`,{disabled:!Fr[e.id],onClick:()=>void K(()=>V.setFabricEgressPoolNode(E,e.id,Fr[e.id],{status:`active`}),`Узел назначен выходной зоне.`),children:H.assignEndpointNode})]})]},e.id)}),Gt.length===0&&(0,C.jsx)(pe,{title:H.fabricEgressPools,text:H.egressPoolsEmpty})]})]}),(0,C.jsxs)(`article`,{className:`card span2`,children:[(0,C.jsxs)(`div`,{className:`cardHead`,children:[(0,C.jsxs)(`div`,{children:[(0,C.jsx)(`h3`,{children:H.fabricMap}),(0,C.jsx)(`p`,{className:`muted`,children:`Визуальный слой показывает, какие узлы живы, какие сервисы на них назначены и какие тестовые наблюдения связей проходят между ними.`})]}),(0,C.jsx)(de,{value:Hi?.synthetic_links_enabled?`enabled`:`disabled`})]}),(0,C.jsx)(be,{nodes:j,links:Ft,syntheticMeshConfigsByNode:Lt,entryPoints:Vt,entryPointNodesById:Ut,egressPools:Gt,egressPoolNodesById:qt,rolesByNode:tt,workloadsByNode:ot,telemetryByNode:Nt,labels:H,emptyText:H.noLinks})]}),(0,C.jsxs)(`article`,{className:`card span2`,children:[(0,C.jsxs)(`div`,{className:`cardHead`,children:[(0,C.jsxs)(`div`,{children:[(0,C.jsx)(`h3`,{children:`Synthetic mesh config`}),(0,C.jsx)(`p`,{className:`muted`,children:`Node-scoped config from Control Plane. Endpoint candidates and scoring inputs are visible to the platform owner only; production forwarding for service traffic must remain disabled here.`})]}),(0,C.jsxs)(`span`,{className:Xi===0?`pill good`:`pill bad`,children:[`production_forwarding=`,Xi===0?`false`:`true`]})]}),(0,C.jsx)(je,{columns:[`узел`,`config`,`routes`,`peer endpoints`,`candidates`,`peer dir`,`recovery seeds`,`rendezvous leases`,`relay policy`,`path decisions`,`authority`,`scoped production`],rows:j.map(e=>{let t=Lt[e.id];return[e.name,t?t.enabled?`enabled`:`disabled`:`не загружен`,String(t?.routes.length??0),String(Object.keys(t?.peer_endpoints||{}).length),String(t?st(t):0),String(t?.peer_directory?.length??0),String(t?.recovery_seeds?.length??0),String(t?.rendezvous_leases?.length??0),ct(t),lt(t),t?.authority_required?P(t.authority_signature?.key_fingerprint):`не требуется`,t?.production_forwarding?`true`:`false`]})}),(0,C.jsx)(`p`,{className:`muted`,children:`Health-aware scoring не выбирает service route и не открывает service-соединения. C17Z19 показывает control-plane route/path decisions, route generation status, synthetic route-health effective path и relay feedback scoring, но не переносит RDP/VPN/file/video/service payload.`})]}),(0,C.jsxs)(`article`,{className:`card span2`,children:[(0,C.jsx)(`h3`,{children:H.servicePlacement}),(0,C.jsx)(je,{columns:[`узел`,`runtime`,`адрес`,`здоровье`,`роли`,`желаемые / reported сервисы`,`последний heartbeat`],rows:j.map(e=>{let t=rt(e,St[e.id]||[],Ft);return[e.name,(0,C.jsx)(ve,{runtime:t}),t.address,e.health_status,Pe(tt[e.id]||[]),Le(ot[e.id]||[]),F((St[e.id]||[])[0]?.observed_at||e.last_seen_at)]})})]}),(0,C.jsxs)(`article`,{className:`card`,children:[(0,C.jsx)(`h3`,{children:H.trafficFlow}),(0,C.jsx)(je,{columns:[`источник`,`цель`,`тип`,`route/path`,`статус`,`задержка`,`качество`,`наблюдение`],rows:qe(Ft).filter(e=>e.source_node_id!==e.target_node_id).map(e=>{let t=j.find(t=>t.id===e.source_node_id),n=j.find(t=>t.id===e.target_node_id);return[(0,C.jsx)(ye,{node:t,fallback:ut(j,e.source_node_id),heartbeatsByNode:St,meshLinks:Ft}),(0,C.jsx)(ye,{node:n,fallback:ut(j,e.target_node_id),heartbeatsByNode:St,meshLinks:Ft}),Ye(e),Xe(e,j),e.link_status,e.latency_ms==null?`н/д`:`${e.latency_ms} мс`,e.quality_score==null?`н/д`:`${e.quality_score}/100`,F(e.observed_at)]})})]}),(0,C.jsxs)(`article`,{className:`card`,children:[(0,C.jsx)(`h3`,{children:`Политики QoS`}),(0,C.jsx)(je,{columns:[`класс`,`приоритет`,`надежность`,`политика сброса`],rows:zt.map(e=>[e.service_class,String(e.priority),e.reliability_mode,e.drop_policy])})]})]}),T===`vpn`&&(0,C.jsxs)(`section`,{className:`grid two`,children:[(0,C.jsxs)(`article`,{className:`card`,children:[(0,C.jsx)(`h3`,{children:`Создать желаемое состояние VPN-подключения`}),(0,C.jsx)(`p`,{className:`muted`,children:`Только control-plane. Здесь не выполняются TUN/TAP, маршруты, DNS, firewall, QoS или packet forwarding.`}),(0,C.jsxs)(Ae,{children:[(0,C.jsxs)(`label`,{children:[`ID организации`,(0,C.jsx)(`input`,{value:z.organizationId,onChange:e=>Gr({...z,organizationId:e.target.value})})]}),(0,C.jsxs)(`label`,{children:[`Название`,(0,C.jsx)(`input`,{value:z.name,onChange:e=>Gr({...z,name:e.target.value})})]}),(0,C.jsxs)(`label`,{children:[`Протокол`,(0,C.jsxs)(`select`,{value:z.protocolFamily,onChange:e=>Gr({...z,protocolFamily:e.target.value}),children:[(0,C.jsx)(`option`,{value:`generic`,children:`generic`}),(0,C.jsx)(`option`,{value:`wireguard`,children:`wireguard`}),(0,C.jsx)(`option`,{value:`ipsec`,children:`ipsec`}),(0,C.jsx)(`option`,{value:`openvpn`,children:`openvpn`})]})]}),(0,C.jsxs)(`label`,{children:[`Желаемое состояние`,(0,C.jsxs)(`select`,{value:z.desiredState,onChange:e=>Gr({...z,desiredState:e.target.value}),children:[(0,C.jsx)(`option`,{value:`disabled`,children:`выключено`}),(0,C.jsx)(`option`,{value:`enabled`,children:`включено`})]})]}),(0,C.jsxs)(`label`,{children:[`Ссылка на credential`,(0,C.jsx)(`input`,{value:z.credentialRef,onChange:e=>Gr({...z,credentialRef:e.target.value})})]})]}),(0,C.jsxs)(`label`,{children:[`Целевой endpoint JSON`,(0,C.jsx)(`textarea`,{value:z.targetEndpointJson,onChange:e=>Gr({...z,targetEndpointJson:e.target.value})})]}),(0,C.jsxs)(`label`,{children:[`Политика разрешенных узлов JSON`,(0,C.jsx)(`textarea`,{value:z.allowedNodePolicyJson,onChange:e=>Gr({...z,allowedNodePolicyJson:e.target.value})})]}),(0,C.jsxs)(`details`,{children:[(0,C.jsx)(`summary`,{children:`Расширенные routing / QoS / placement JSON`}),(0,C.jsxs)(`label`,{children:[`Использование маршрутизации JSON`,(0,C.jsx)(`textarea`,{value:z.routingUsageJson,onChange:e=>Gr({...z,routingUsageJson:e.target.value})})]}),(0,C.jsxs)(`label`,{children:[`Политика маршрута JSON`,(0,C.jsx)(`textarea`,{value:z.routePolicyJson,onChange:e=>Gr({...z,routePolicyJson:e.target.value})})]}),(0,C.jsxs)(`label`,{children:[`Политика QoS JSON`,(0,C.jsx)(`textarea`,{value:z.qosPolicyJson,onChange:e=>Gr({...z,qosPolicyJson:e.target.value})})]}),(0,C.jsxs)(`label`,{children:[`Политика размещения JSON`,(0,C.jsx)(`textarea`,{value:z.placementPolicyJson,onChange:e=>Gr({...z,placementPolicyJson:e.target.value})})]})]}),(0,C.jsx)(`button`,{className:`primary`,disabled:!E||!z.organizationId||!z.name,onClick:()=>void K(()=>V.createVPNConnection(E,{organizationId:z.organizationId,name:z.name,protocolFamily:z.protocolFamily,credentialRef:z.credentialRef||null,desiredState:z.desiredState,targetEndpoint:Me(z.targetEndpointJson,`target endpoint`),allowedNodePolicy:Me(z.allowedNodePolicyJson,`allowed node policy`),routingUsage:Ne(z.routingUsageJson,`routing usage`),routePolicy:Me(z.routePolicyJson,`route policy`),qosPolicy:Me(z.qosPolicyJson,`qos policy`),placementPolicy:Me(z.placementPolicyJson,`placement policy`)}),`Желаемое состояние VPN создано.`),children:`Создать желаемое состояние VPN`})]}),(0,C.jsxs)(`article`,{className:`card`,children:[(0,C.jsxs)(`div`,{className:`cardHead`,children:[(0,C.jsxs)(`div`,{children:[(0,C.jsx)(`h3`,{children:`VPN-подключения`}),(0,C.jsx)(`p`,{className:`muted`,children:`Cluster-managed состояние, gateway packet stats и диагностика Android-клиента.`})]}),(0,C.jsxs)(`div`,{className:`actions compactActions`,children:[(0,C.jsx)(`button`,{onClick:()=>void K(async()=>{Kn(`Истекшие VPN lease: ${(await V.expireStaleVPNLeases(E)).length}.`)},`Stale VPN lease проверены.`),children:`Проверить stale lease`}),(0,C.jsx)(`button`,{onClick:()=>void Mi(),children:`Обновить клиент`})]})]}),(0,C.jsxs)(`div`,{className:`inlineForm`,children:[(0,C.jsxs)(`label`,{children:[`Android device id`,(0,C.jsx)(`input`,{value:ln,placeholder:`0315f630-...`,onChange:e=>un(e.target.value),onBlur:()=>localStorage.setItem(w.vpnDiagnosticDeviceId,ln.trim())})]}),dn.length>0&&(0,C.jsxs)(`label`,{children:[`Найденные клиенты`,(0,C.jsx)(`select`,{value:ln,onChange:e=>{let t=e.target.value;un(t),localStorage.setItem(w.vpnDiagnosticDeviceId,t),mn(dn.find(e=>e.device_id===t)||null)},children:dn.map(e=>{let t=et(e.payload)||{};return(0,C.jsxs)(`option`,{value:e.device_id,children:[P(e.device_id),` / `,M(t,`app_version`,`н/д`),` / `,F(e.observed_at)]},e.device_id)})})]})]}),(0,C.jsxs)(`div`,{className:`diagnosticCommandPanel`,children:[(0,C.jsxs)(`label`,{children:[`URL для теста`,(0,C.jsx)(`input`,{value:gn,onChange:e=>_n(e.target.value)})]}),(0,C.jsxs)(`div`,{className:`actions compactActions`,children:[(0,C.jsx)(`button`,{onClick:()=>void Ni({type:`refresh_profile`},`Профиль`),children:`Обновить профиль`}),(0,C.jsx)(`button`,{onClick:()=>void Ni({type:`start_vpn`},`VPN`),children:`Старт VPN`}),(0,C.jsx)(`button`,{onClick:()=>void Ni({type:`stop_vpn`},`VPN`),children:`Стоп VPN`}),(0,C.jsx)(`button`,{onClick:()=>void Ni({type:`vpn_stats`},`Stats`),children:`Stats`}),(0,C.jsx)(`button`,{onClick:()=>void Ni({type:`vpn_http_get`,url:gn},`VPN HTTP`),children:`VPN HTTP`}),(0,C.jsx)(`button`,{onClick:()=>void Ni({type:`open_url`,url:gn},`Открыть URL`),children:`Открыть URL`}),(0,C.jsx)(`button`,{className:`primary`,onClick:()=>void Ni({type:`full_vpn_test`,url:gn,watch_seconds:45},`Полный VPN test`),children:`Полный тест`})]}),xn&&(0,C.jsxs)(`p`,{className:`muted`,children:[`Последняя команда: `,M(xn.payload,`type`,`н/д`),` / `,F(xn.created_at)]})]}),me(pn),(0,C.jsxs)(`div`,{className:`stack`,children:[Xt.map(e=>{let t=et(e.metadata?.client_config),n=et(t?.vpn_fabric_route),r=vt(n?.entry_pool_node_ids||e.placement_policy?.entry_node_ids),i=vt(n?.exit_pool_node_ids||e.placement_policy?.exit_node_ids),a=String(n?.selected_entry_node_id||r[0]||``),o=String(n?.selected_exit_node_id||tn[e.id]?.owner_node_id||e.placement_policy?.exit_node_id||i[0]||``),s=sn[e.id]||{};return(0,C.jsxs)(`div`,{className:`vpnCard`,children:[(0,C.jsxs)(`div`,{children:[(0,C.jsx)(`strong`,{children:e.name}),(0,C.jsxs)(`p`,{className:`muted`,children:[e.protocol_family,` / `,e.mode,` / организация `,P(e.organization_id)]}),(0,C.jsx)(de,{value:e.desired_state}),(0,C.jsx)(de,{value:e.status}),(0,C.jsx)(`span`,{className:`pill ${t?.packet_forwarding?`good`:`warn`}`,children:t?.packet_forwarding?`gateway packet relay active`:`gateway packet relay inactive`}),(0,C.jsxs)(`span`,{className:`pill`,children:[String(n?.preferred_data_plane||`backend_relay`),` / fallback `,String(n?.fallback_data_plane||`н/д`)]})]}),(0,C.jsxs)(`div`,{className:`stateList`,children:[(0,C.jsx)(A,{label:`Секрет`,value:e.credential_ref?`задан`:`не задан`}),(0,C.jsx)(A,{label:`Активный lease`,value:tn[e.id]?P(tn[e.id]?.owner_node_id):`нет`}),(0,C.jsx)(A,{label:`Fabric route`,value:`${a?ut(j,a):`entry auto`} -> ${o?ut(j,o):`exit auto`}`}),(0,C.jsx)(A,{label:`Entry pool`,value:r.map(e=>ut(j,e)).join(`, `)||`н/д`}),(0,C.jsx)(A,{label:`Exit pool`,value:i.map(e=>ut(j,e)).join(`, `)||`н/д`}),(0,C.jsx)(A,{label:`Runtime`,value:String(t?.runtime_status||`н/д`)}),(0,C.jsx)(A,{label:`Gateway`,value:String(t?.gateway_assignment_status||`н/д`)}),(0,C.jsx)(A,{label:`Client -> gateway`,value:He(s.client_to_gateway)}),(0,C.jsx)(A,{label:`Gateway -> client`,value:He(s.gateway_to_client)}),(0,C.jsx)(A,{label:`Обновлено`,value:F(e.updated_at)})]}),(0,C.jsxs)(`div`,{className:`actions`,children:[(0,C.jsx)(`button`,{disabled:e.desired_state===`enabled`,onClick:()=>void K(()=>V.updateVPNConnectionDesiredState(E,e.id,`enabled`),`Желаемое состояние VPN включено.`),children:`Включить`}),(0,C.jsx)(`button`,{disabled:e.desired_state===`disabled`,onClick:()=>void K(()=>V.updateVPNConnectionDesiredState(E,e.id,`disabled`),`Желаемое состояние VPN выключено.`),children:`Выключить`})]})]},e.id)}),Xt.length===0&&(0,C.jsx)(pe,{title:`Нет желаемого состояния VPN`,text:`Control-plane записи C18 появятся здесь.`})]})]})]}),T===`org-safe`&&(0,C.jsxs)(`section`,{className:`grid two`,children:[(0,C.jsxs)(`article`,{className:`card span2`,children:[(0,C.jsxs)(`div`,{className:`cardHead`,children:[(0,C.jsxs)(`div`,{children:[(0,C.jsx)(`h3`,{children:`Организации и пользователи`}),(0,C.jsx)(`p`,{className:`muted`,children:`Операционный слой для владельца платформы: tenant scope, роли участников и безопасная сводка без раскрытия core mesh.`})]}),(0,C.jsx)(`span`,{className:`pill`,children:Cn.length})]}),(0,C.jsxs)(Ae,{children:[(0,C.jsxs)(`label`,{children:[`Slug`,(0,C.jsx)(`input`,{value:Kr.slug,onChange:e=>qr({...Kr,slug:e.target.value}),placeholder:`home`})]}),(0,C.jsxs)(`label`,{children:[`Название`,(0,C.jsx)(`input`,{value:Kr.name,onChange:e=>qr({...Kr,name:e.target.value}),placeholder:`HOME`})]})]}),(0,C.jsx)(`div`,{className:`actions`,children:(0,C.jsx)(`button`,{className:`primary`,disabled:!Kr.slug.trim()||!Kr.name.trim(),onClick:()=>void K(async()=>{let e=await V.createOrganization(Kr);qr({slug:``,name:``}),Rn(e.id),Zr(t=>({...t,organizationId:e.id})),ni(t=>({...t,organizationId:e.id}))},`Организация создана.`),children:`Создать организацию`})}),(0,C.jsx)(je,{columns:[`организация`,`slug`,`статус`,`ресурсы`,`участники`,`действие`],rows:Cn.map(e=>{let t=Dn.filter(t=>t.organization_id===e.id),n=kn[e.id]||[];return[e.name,e.slug,(0,C.jsx)(de,{value:e.status}),String(t.length),String(n.length),(0,C.jsx)(`div`,{className:`actions`,children:(0,C.jsx)(`button`,{onClick:()=>void K(async()=>{Rn(e.id),Bn(await V.getOrganizationAdminSummary(e.id))},`Сводка организации загружена.`),children:`Открыть`})},e.id)]})})]}),(0,C.jsxs)(`article`,{className:`card`,children:[(0,C.jsx)(`h3`,{children:`Пользователь`}),(0,C.jsxs)(Ae,{children:[(0,C.jsxs)(`label`,{children:[`Email / логин`,(0,C.jsx)(`input`,{value:Jr.email,onChange:e=>Yr({...Jr,email:e.target.value}),placeholder:`user@example.com`})]}),(0,C.jsxs)(`label`,{children:[`Пароль`,(0,C.jsx)(`input`,{type:`password`,value:Jr.password,onChange:e=>Yr({...Jr,password:e.target.value}),placeholder:`минимум 8 символов`})]}),(0,C.jsxs)(`label`,{children:[`Роль платформы`,(0,C.jsxs)(`select`,{value:Jr.platformRole,onChange:e=>Yr({...Jr,platformRole:e.target.value}),children:[(0,C.jsx)(`option`,{value:`user`,children:`user`}),(0,C.jsx)(`option`,{value:`platform_admin`,children:`platform_admin`}),(0,C.jsx)(`option`,{value:`platform_recovery_admin`,children:`platform_recovery_admin`})]})]})]}),(0,C.jsx)(`div`,{className:`actions`,children:(0,C.jsx)(`button`,{disabled:!Jr.email.trim()||Jr.password.length<8,onClick:()=>void K(async()=>{let e=await V.createUser(Jr);En(await V.listUsers()),Yr({email:``,password:``,platformRole:`user`}),Zr(t=>({...t,userId:e.id}))},`Пользователь создан.`),children:`Создать пользователя`})}),(0,C.jsx)(je,{columns:[`пользователь`,`роль платформы`,`id`],rows:Tn.map(e=>[e.email,(0,C.jsx)(de,{value:e.platform_role||`user`}),(0,C.jsx)(`code`,{children:e.id})])})]}),(0,C.jsxs)(`article`,{className:`card`,children:[(0,C.jsx)(`h3`,{children:`Участник организации`}),(0,C.jsxs)(Ae,{children:[(0,C.jsxs)(`label`,{children:[`Организация`,(0,C.jsxs)(`select`,{value:Xr.organizationId,onChange:e=>Zr({...Xr,organizationId:e.target.value}),children:[(0,C.jsx)(`option`,{value:``,children:`Выберите организацию`}),Cn.map(e=>(0,C.jsx)(`option`,{value:e.id,children:e.name},e.id))]})]}),(0,C.jsxs)(`label`,{children:[`Пользователь`,(0,C.jsxs)(`select`,{value:Xr.userId,onChange:e=>Zr({...Xr,userId:e.target.value}),children:[(0,C.jsx)(`option`,{value:``,children:`Выберите пользователя`}),Tn.map(e=>(0,C.jsx)(`option`,{value:e.id,children:e.email},e.id))]})]}),(0,C.jsxs)(`label`,{children:[`Роль`,(0,C.jsxs)(`select`,{value:Xr.roleId,onChange:e=>Zr({...Xr,roleId:e.target.value}),children:[(0,C.jsx)(`option`,{value:`org_owner`,children:`org_owner`}),(0,C.jsx)(`option`,{value:`org_admin`,children:`org_admin`}),(0,C.jsx)(`option`,{value:`org_operator`,children:`org_operator`}),(0,C.jsx)(`option`,{value:`org_member`,children:`org_member`}),(0,C.jsx)(`option`,{value:`org_viewer`,children:`org_viewer`})]})]})]}),(0,C.jsx)(`div`,{className:`actions`,children:(0,C.jsx)(`button`,{disabled:!Xr.organizationId||!Xr.userId.trim(),onClick:()=>void K(()=>V.addOrganizationMembership(Xr.organizationId,{userId:Xr.userId,roleId:Xr.roleId}),`Участник организации сохранен.`),children:`Сохранить участника`})})]}),(0,C.jsxs)(`article`,{className:`card`,children:[(0,C.jsx)(`h3`,{children:`Безопасная сводка`}),(0,C.jsxs)(`div`,{className:`inlineForm`,children:[(0,C.jsxs)(`select`,{value:Ln,onChange:e=>Rn(e.target.value),children:[(0,C.jsx)(`option`,{value:``,children:`Выберите организацию`}),Cn.map(e=>(0,C.jsx)(`option`,{value:e.id,children:e.name},e.id))]}),(0,C.jsx)(`button`,{disabled:!Ln,onClick:()=>void K(async()=>{Bn(await V.getOrganizationAdminSummary(Ln))},`Сводка организации загружена.`),children:`Обновить`})]}),zn?(0,C.jsxs)(`div`,{className:`stack`,children:[(0,C.jsx)(ue,{label:`Ресурсы`,value:zn.resource_count,tone:`steel`}),(0,C.jsx)(ue,{label:`Активные сессии`,value:zn.active_session_count,tone:`green`}),(0,C.jsx)(A,{label:`Topology exposure`,value:zn.topology_exposure}),(0,C.jsx)(je,{columns:[`контур`,`состояние`],rows:Object.entries(zn.connector_status||{}).map(([e,t])=>[e,typeof t==`string`?I(t):JSON.stringify(t)])}),(0,C.jsx)(je,{columns:[`протокол`,`количество`],rows:zn.service_endpoints.map(e=>[e.protocol,String(e.count)])})]}):(0,C.jsx)(pe,{title:`Сводка не выбрана`,text:`Выберите организацию, чтобы проверить tenant-safe состояние.`})]})]}),T===`servers`&&(0,C.jsx)(`section`,{className:`grid two`,children:(0,C.jsxs)(`article`,{className:`card span2`,children:[(0,C.jsxs)(`div`,{className:`cardHead`,children:[(0,C.jsxs)(`div`,{children:[(0,C.jsx)(`h3`,{children:`Справочник серверов`}),(0,C.jsx)(`p`,{className:`muted`,children:`Единый каталог целей для RDP/VPN: адрес сервера, организация, протокол и предпочтительный вход/выход маршрута.`})]}),(0,C.jsx)(`span`,{className:`pill`,children:Dn.length})]}),(0,C.jsxs)(Ae,{children:[(0,C.jsxs)(`label`,{children:[`Организация`,(0,C.jsxs)(`select`,{value:B.organizationId,onChange:e=>ni({...B,organizationId:e.target.value}),children:[(0,C.jsx)(`option`,{value:``,children:`Выберите организацию`}),Cn.map(e=>(0,C.jsx)(`option`,{value:e.id,children:e.name},e.id))]})]}),(0,C.jsxs)(`label`,{children:[`Имя сервера`,(0,C.jsx)(`input`,{value:B.name,onChange:e=>ni({...B,name:e.target.value}),placeholder:`Office RDP`})]}),(0,C.jsxs)(`label`,{children:[`Адрес`,(0,C.jsx)(`input`,{value:B.address,onChange:e=>ni({...B,address:e.target.value}),placeholder:`192.168.1.10:3389`})]}),(0,C.jsxs)(`label`,{children:[`Протокол`,(0,C.jsxs)(`select`,{value:B.protocol,onChange:e=>ni({...B,protocol:e.target.value}),children:[(0,C.jsx)(`option`,{value:`rdp`,children:`RDP`}),(0,C.jsx)(`option`,{value:`vpn`,children:`VPN`}),(0,C.jsx)(`option`,{value:`ssh`,children:`SSH`}),(0,C.jsx)(`option`,{value:`http`,children:`HTTP`})]})]}),(0,C.jsxs)(`label`,{children:[`Вход`,(0,C.jsxs)(`select`,{value:B.entryNode,onChange:e=>ni({...B,entryNode:e.target.value}),children:[(0,C.jsx)(`option`,{value:``,children:`Автоматически`}),j.map(e=>(0,C.jsx)(`option`,{value:e.id,children:e.name},e.id))]})]}),(0,C.jsxs)(`label`,{children:[`Выход`,(0,C.jsxs)(`select`,{value:B.exitNode,onChange:e=>ni({...B,exitNode:e.target.value}),children:[(0,C.jsx)(`option`,{value:``,children:`Автоматически`}),j.map(e=>(0,C.jsx)(`option`,{value:e.id,children:e.name},e.id))]})]}),(0,C.jsxs)(`label`,{children:[`Теги`,(0,C.jsx)(`input`,{value:B.tags,onChange:e=>ni({...B,tags:e.target.value}),placeholder:`home, accounting`})]}),(0,C.jsxs)(`label`,{children:[`RDP пользователь`,(0,C.jsx)(`input`,{value:B.username,onChange:e=>ni({...B,username:e.target.value}),placeholder:`user или DOMAIN\\\\user`})]}),(0,C.jsxs)(`label`,{children:[`RDP пароль`,(0,C.jsx)(`input`,{type:`password`,value:B.password,onChange:e=>ni({...B,password:e.target.value}),placeholder:`хранится как secret`})]}),(0,C.jsxs)(`label`,{children:[`Домен`,(0,C.jsx)(`input`,{value:B.domain,onChange:e=>ni({...B,domain:e.target.value}),placeholder:`опционально`})]})]}),(0,C.jsx)(`div`,{className:`actions`,children:(0,C.jsx)(`button`,{className:`primary`,disabled:!B.organizationId||!B.name.trim()||!B.address.trim(),onClick:()=>void K(async()=>{let e=[`rdp`,`vnc`,`ssh`].includes(B.protocol)?`rap-secret://org/${B.organizationId}/resources/${crypto.randomUUID()}/primary`:null,t=await V.createResource({organizationId:B.organizationId,name:B.name,address:B.address,protocol:B.protocol,secretRef:e,certificateVerificationMode:B.protocol===`rdp`?`ignore`:`strict`,clipboardMode:B.protocol===`rdp`?`bidirectional`:`disabled`,fileTransferMode:B.protocol===`rdp`?`bidirectional`:`disabled`,metadata:{route_mode:B.routeMode,preferred_entry_node_id:B.entryNode||null,preferred_exit_node_id:B.exitNode||null,tags:B.tags.split(`,`).map(e=>e.trim()).filter(Boolean)}});[`rdp`,`vnc`,`ssh`].includes(B.protocol)&&(B.username.trim()||B.password)&&await V.upsertResourceSecret(t.id,{username:B.username.trim(),password:B.password,domain:B.domain.trim()}),ni({...B,name:``,address:``,tags:``,username:``,password:``,domain:``})},`Сервер добавлен в справочник.`),children:`Добавить сервер`})}),(0,C.jsx)(je,{columns:[`сервер`,`адрес`,`протокол`,`секрет`,`организация`,`маршрут`,`создано`,`действия`],rows:Dn.map(e=>{let t=e.metadata||{},n=Cn.find(t=>t.id===e.organization_id);return[e.name,e.address,e.protocol,e.has_secret?`сохранен`:e.secret_ref?`нужен payload`:`нет`,n?.name||P(e.organization_id),`${P(String(t.preferred_entry_node_id||``))||`auto`} -> ${P(String(t.preferred_exit_node_id||``))||`auto`}`,F(e.created_at),(0,C.jsx)(`button`,{className:`ghost`,onClick:()=>{$r(e),ti({username:``,password:``,domain:``})},children:`Обновить secret`})]})}),Qr&&(0,C.jsx)(`div`,{className:`modalBackdrop`,role:`presentation`,children:(0,C.jsxs)(`div`,{className:`modalCard`,role:`dialog`,"aria-modal":`true`,"aria-labelledby":`resource-secret-title`,children:[(0,C.jsxs)(`div`,{className:`cardHead`,children:[(0,C.jsxs)(`div`,{children:[(0,C.jsx)(`h3`,{id:`resource-secret-title`,children:`Учетные данные RDP`}),(0,C.jsxs)(`p`,{className:`muted`,children:[Qr.name,` · `,Qr.address]})]}),(0,C.jsx)(`button`,{className:`ghost`,onClick:()=>$r(null),children:`Закрыть`})]}),(0,C.jsxs)(Ae,{children:[(0,C.jsxs)(`label`,{children:[`Пользователь`,(0,C.jsx)(`input`,{value:ei.username,onChange:e=>ti({...ei,username:e.target.value}),placeholder:`user или DOMAIN\\\\user`})]}),(0,C.jsxs)(`label`,{children:[`Пароль`,(0,C.jsx)(`input`,{type:`password`,value:ei.password,onChange:e=>ti({...ei,password:e.target.value})})]}),(0,C.jsxs)(`label`,{children:[`Домен`,(0,C.jsx)(`input`,{value:ei.domain,onChange:e=>ti({...ei,domain:e.target.value}),placeholder:`опционально`})]})]}),(0,C.jsx)(`p`,{className:`muted`,children:`Пароль сохраняется как encrypted resource secret. В metadata ресурса он не попадет.`}),(0,C.jsxs)(`div`,{className:`actions`,children:[(0,C.jsx)(`button`,{className:`primary`,disabled:!ei.username.trim()||!ei.password,onClick:()=>void K(async()=>{await V.upsertResourceSecret(Qr.id,{username:ei.username.trim(),password:ei.password,domain:ei.domain.trim()}),$r(null),ti({username:``,password:``,domain:``})},`Secret ресурса обновлен.`),children:`Сохранить secret`}),(0,C.jsx)(`button`,{onClick:()=>$r(null),children:`Отмена`})]})]})})]})}),T===`audit`&&(0,C.jsxs)(`section`,{className:`card`,children:[(0,C.jsx)(`h3`,{children:`Аудит кластера`}),(0,C.jsx)(je,{columns:[`событие`,`цель`,`actor`,`создано`],rows:jn.map(e=>[e.event_type,`${e.target_type}${e.target_id?`:${P(e.target_id)}`:``}`,e.actor_user_id?P(e.actor_user_id):`system`,F(e.created_at)])})]})]})]})}function ue({label:e,value:t,tone:n}){return(0,C.jsxs)(`article`,{className:`metric ${n}`,children:[(0,C.jsx)(`span`,{children:e}),(0,C.jsx)(`strong`,{children:t})]})}function k({label:e,value:t}){return(0,C.jsxs)(`div`,{className:`signal`,children:[(0,C.jsx)(`span`,{children:e}),(0,C.jsx)(`strong`,{children:t})]})}function de({value:e}){return(0,C.jsx)(`span`,{className:`status ${e.replace(/_/g,`-`)}`,children:I(e)})}function fe({label:e,value:t,tone:n}){return(0,C.jsxs)(`span`,{className:`functionState ${n||``}`,children:[(0,C.jsx)(`small`,{children:e}),(0,C.jsx)(`strong`,{children:t})]})}function A({label:e,value:t}){return(0,C.jsxs)(`div`,{className:`stateLine`,children:[(0,C.jsx)(`span`,{children:e}),(0,C.jsx)(`strong`,{children:t})]})}function pe({title:e,text:t}){return(0,C.jsxs)(`article`,{className:`empty`,children:[(0,C.jsx)(`h3`,{children:e}),(0,C.jsx)(`p`,{children:t})]})}function me(e){if(!e)return(0,C.jsx)(`p`,{className:`muted`,children:`Диагностика Android-клиента не загружена. Укажи device id из приложения и нажми “Обновить клиент”.`});let t=et(e.payload)||{},n=et(t.runtime),r=et(t.vpn_config),i=M(t,`app_version`,`н/д`),a=M(t,`service_state`,`н/д`),o=M(t,`control_network_mode`,`н/д`),s=M(r,`packet_relay_active_base_url`)||M(r,`packet_relay_base_url`,`н/д`),c=M(r,`packet_relay_profile_base_url`,`н/д`),l=M(r,`packet_relay_candidate_urls`,`н/д`),u=it(n,`uplink_read_total`),d=it(n,`uplink_sent_total`),f=it(n,`downlink_received_total`),p=it(n,`uplink_dropped_packets`)+it(n,`downlink_dropped_packets`),m=it(n,`uplink_bypassed_control_packets`),h=it(n,`downlink_received_bytes`),g=it(n,`uplink_sent_bytes`),_=M(n,`state`,`н/д`),v=M(n,`message`,``),y=it(n,`uplink_sent_mbps`),b=it(n,`downlink_received_mbps`),x=M(t,`last_command_type`,`н/д`),S=M(t,`last_command_result`,`н/д`);return(0,C.jsxs)(`div`,{className:`vpnCard diagnosticCard`,children:[(0,C.jsxs)(`div`,{children:[(0,C.jsxs)(`strong`,{children:[`Android client `,P(e.device_id)]}),(0,C.jsxs)(`p`,{className:`muted`,children:[i,` / `,a,` / `,F(e.observed_at)]}),(0,C.jsx)(de,{value:Date.now()-new Date(e.observed_at).getTime()<3e4?`active`:`degraded`}),(0,C.jsx)(`span`,{className:`pill`,children:o})]}),(0,C.jsxs)(`div`,{className:`stateList`,children:[(0,C.jsx)(A,{label:`Relay active`,value:s}),(0,C.jsx)(A,{label:`Relay profile`,value:c}),(0,C.jsx)(A,{label:`Relay candidates`,value:l}),(0,C.jsx)(A,{label:`Packets read/sent/down`,value:`${u} / ${d} / ${f}`}),(0,C.jsx)(A,{label:`Drops / control bypass`,value:`${p} / ${m}`}),(0,C.jsx)(A,{label:`Bytes up/down`,value:`${xn(g)} / ${xn(h)}`}),(0,C.jsx)(A,{label:`Rate up/down`,value:`${y.toFixed(2)} / ${b.toFixed(2)} Mbps`}),(0,C.jsx)(A,{label:`Runtime`,value:v?`${_}: ${v}`:_}),(0,C.jsx)(A,{label:`Last command`,value:`${x}: ${S}`})]})]})}function he({items:e,emptyText:t}){if(e.length===0)return(0,C.jsx)(pe,{title:t,text:`Тестовая телеметрия появится здесь после отчета node-agent.`});let n=[...e].reverse().slice(-24),r=e[0],i=Math.max(...n.map(e=>e.memory_used_bytes||0),1);return(0,C.jsxs)(`div`,{className:`telemetryBox`,children:[(0,C.jsxs)(`div`,{className:`signalStrip compact`,children:[(0,C.jsx)(k,{label:`Память`,value:`${xn(r.memory_used_bytes)} / ${xn(r.memory_total_bytes)}`}),(0,C.jsx)(k,{label:`Процессор`,value:r.cpu_percent==null?`н/д`:`${r.cpu_percent.toFixed(1)}%`}),(0,C.jsx)(k,{label:`Процессы`,value:r.process_count==null?`н/д`:String(r.process_count)}),(0,C.jsx)(k,{label:`Обновлено`,value:F(r.observed_at)})]}),(0,C.jsx)(`div`,{className:`sparkline`,"aria-label":`memory telemetry`,children:n.map(e=>(0,C.jsx)(`span`,{style:{height:`${Math.max(8,Math.round((e.memory_used_bytes||0)/i*100))}%`}},e.id))})]})}function ge({node:e,memberships:t,activeRoles:n,desiredWorkloads:r,observedWorkloads:i,heartbeats:a,telemetry:o,meshLinks:s,syntheticConfig:c,allNodes:l,onSetUpdatePolicy:u,updatePlan:d,updateStatuses:f,labels:p}){let m=a[0],h=o[0],g=et(m?.metadata?.mesh_listener_report),v=et(m?.metadata?.mesh_endpoint_report),y=et(m?.metadata?.mesh_outbound_session_report),b=c?.mesh_listener,x=et(m?.metadata?.mesh_peer_recovery_report),S=et(m?.metadata?.mesh_peer_connection_intent_report),w=et(m?.metadata?.mesh_peer_connection_manager_report),ee=et(m?.metadata?.mesh_rendezvous_lease_report),te=et(m?.metadata?.mesh_route_path_decision_report),ne=et(m?.metadata?.mesh_route_generation_report),T=et(m?.metadata?.mesh_route_health_config_report),re=qe(s).filter(e=>e.source_node_id!==e.target_node_id),ie=re.filter(e=>e.link_status===`reachable`),E=re.filter(e=>e.link_status!==`reachable`),ae=Object.entries(m?.capabilities||{}).sort(([e],[t])=>e.localeCompare(t)),oe=tt(w?.probe_results),[se,ce]=(0,_.useState)(`network`),D=ze(f,`rap-node-agent`),O=ze(f,`rap-host-agent`),le=f[0],ue=We(f),fe=t.find(t=>t.node.id===e.id)?.cluster.id||t[0]?.cluster.id||``,pe=tt(v?.endpoint_candidates),me=pe[0],ge=nt(v,[`peer_endpoint`,`advertised_endpoint`,`endpoint`])||M(me,`address`,``)||``,ve=nt(v,[`transport`,`advertise_transport`])||M(me,`transport`,``)||`н/д`,ye=nt(v,[`connectivity_mode`,`connectivity`])||M(me,`connectivity_mode`,``)||M(g,`inbound_reachability`,``)||`н/д`,be=M(v,`nat_type`,M(me,`nat_type`,`н/д`)),xe=M(v,`region`,M(g,`region`,M(me,`region`,`н/д`))),Se=M(v,`observed_at`,M(g,`observed_at`,m?.observed_at||`н/д`)),Ce=M(g,`status`,``)||(ge?`нет listener report, есть advertised endpoint`:`report отсутствует`),we=M(g,`effective_listen_addr`,``)||`н/д`,j=M(g,`configured_listen_addr`,``)||`н/д`,Te=pe.length>0?pe:ge?[{endpoint_id:`${e.id}-reported`,address:ge,transport:ve,reachability:ye,connectivity_mode:ye,nat_type:be,priority:`н/д`,last_verified_at:Se}]:[],Ee=Object.entries(c?.peer_endpoints||{}),De=Object.entries(c?.peer_endpoint_candidates||{}).flatMap(([e,t])=>t.map(t=>({peerID:e,candidate:t}))),Oe=new Set(ie.map(t=>t.source_node_id===e.id?t.target_node_id:t.source_node_id)),ke=De.filter(({peerID:e})=>!Oe.has(e)),Ae=[g?`listener report: есть`:`listener report: не прислан агентом`,v?`endpoint report: есть`:`endpoint report: не прислан агентом`,y?`outbound session: есть`:`outbound session: не прислан агентом`,c?`scoped config: ${c.enabled?`enabled`:`disabled`}`:`scoped config: не загружен`,`active links: ${ie.length}/${re.length}`];return(0,C.jsxs)(`div`,{className:`nodeDetails`,children:[(0,C.jsxs)(`section`,{className:`nodePanel`,children:[(0,C.jsx)(`h4`,{children:`Сводка runtime`}),(0,C.jsxs)(`div`,{className:`signalStrip compact nodeMetricGrid`,children:[(0,C.jsx)(k,{label:`Heartbeat`,value:m?F(m.observed_at):`н/д`}),(0,C.jsx)(k,{label:`Health`,value:I(m?.health_status||e.health_status)}),(0,C.jsx)(k,{label:`Listener`,value:mn(m)}),(0,C.jsx)(k,{label:`Mesh links`,value:`${ie.length}/${re.length}`}),(0,C.jsx)(k,{label:`Update`,value:Be(le,d)})]}),(0,C.jsxs)(`div`,{className:`summaryChips`,children:[(0,C.jsx)(de,{value:e.registration_status}),(0,C.jsx)(de,{value:e.membership_status}),(0,C.jsx)(de,{value:e.partition_state}),(0,C.jsx)(`span`,{className:`pill`,children:e.reported_version||m?.reported_version||`версия неизвестна`}),g?.one_way_connectivity===!0&&(0,C.jsx)(`span`,{className:`pill warn`,children:`one-way`}),g?.port_conflict===!0&&(0,C.jsx)(`span`,{className:`pill bad`,children:`port conflict`})]})]}),(0,C.jsx)(`div`,{className:`nodeTabs`,role:`tablist`,"aria-label":`Node analysis sheets`,children:[[`overview`,`Обзор`],[`network`,`Сеть и адреса`],[`mesh`,`Mesh`],[`services`,`Роли и сервисы`],[`telemetry`,`Телеметрия`],[`updates`,`Обновления`],[`raw`,`Raw`]].map(([e,t])=>(0,C.jsx)(`button`,{className:se===e?`active`:``,onClick:()=>ce(e),type:`button`,children:t},e))}),se===`overview`&&(0,C.jsxs)(`div`,{className:`nodeDetailGrid`,children:[(0,C.jsxs)(`section`,{className:`nodePanel`,children:[(0,C.jsx)(`h4`,{children:`Идентичность и размещение`}),(0,C.jsxs)(`div`,{className:`stateList`,children:[(0,C.jsx)(A,{label:`Node ID`,value:e.id}),(0,C.jsx)(A,{label:`Node key`,value:e.node_key}),(0,C.jsx)(A,{label:`Имя`,value:e.name}),(0,C.jsx)(A,{label:`Владение`,value:I(e.ownership_type)}),(0,C.jsx)(A,{label:`Owner org`,value:P(e.owner_organization_id)}),(0,C.jsx)(A,{label:`Группа`,value:e.node_group_name||p.ungroupedNodes}),(0,C.jsx)(A,{label:`Создан`,value:F(e.created_at)}),(0,C.jsx)(A,{label:`Обновлен`,value:F(e.updated_at)})]})]}),(0,C.jsxs)(`section`,{className:`nodePanel`,children:[(0,C.jsx)(`h4`,{children:`Участие в кластерах`}),(0,C.jsx)(`div`,{className:`membershipList`,children:t.map(t=>(0,C.jsxs)(`span`,{className:t.node.id===e.id&&t.node.membership_status===`active`?`pill good`:`pill`,children:[t.cluster.name,`: `,I(t.node.membership_status)]},t.cluster.id))}),(0,C.jsxs)(`div`,{className:`stateList`,children:[(0,C.jsx)(A,{label:`Активных ролей`,value:String(n.length)}),(0,C.jsx)(A,{label:`Desired workloads`,value:String(r.length)}),(0,C.jsx)(A,{label:`Observed workloads`,value:String(i.length)}),(0,C.jsx)(A,{label:`Последний сигнал`,value:F(e.last_seen_at||m?.observed_at)})]})]})]}),se===`network`&&(0,C.jsxs)(C.Fragment,{children:[(0,C.jsxs)(`div`,{className:`nodeDetailGrid`,children:[(0,C.jsxs)(`section`,{className:`nodePanel`,children:[(0,C.jsx)(`h4`,{children:`Локальный listener`}),(0,C.jsxs)(`div`,{className:`stateList`,children:[(0,C.jsx)(A,{label:`Статус`,value:Ce}),(0,C.jsx)(A,{label:`Режим порта`,value:M(g,`listen_port_mode`,`н/д`)}),(0,C.jsx)(A,{label:`Configured addr`,value:j}),(0,C.jsx)(A,{label:`Effective addr`,value:we}),(0,C.jsx)(A,{label:`Inbound`,value:M(g,`inbound_reachability`,ye)}),(0,C.jsx)(A,{label:`One-way`,value:M(g,`one_way_connectivity`,`н/д`)}),(0,C.jsx)(A,{label:`Port conflict`,value:M(g,`port_conflict`,`false`)}),(0,C.jsx)(A,{label:`Failure`,value:M(g,`failure_error`,M(g,`failure_reason`,`нет`))})]})]}),(0,C.jsxs)(`section`,{className:`nodePanel`,children:[(0,C.jsx)(`h4`,{children:`Desired listener`}),(0,C.jsxs)(`div`,{className:`stateList`,children:[(0,C.jsx)(A,{label:`Состояние`,value:b?.desired_state||`н/д`}),(0,C.jsx)(A,{label:`Режим порта`,value:b?.listen_port_mode||`н/д`}),(0,C.jsx)(A,{label:`Listen addr`,value:b?.listen_addr||`н/д`}),(0,C.jsx)(A,{label:`Auto range`,value:b?`${b.auto_port_start||`н/д`}-${b.auto_port_end||`н/д`}`:`н/д`}),(0,C.jsx)(A,{label:`Advertise endpoint`,value:b?.advertise_endpoint||`auto-discovery`}),(0,C.jsx)(A,{label:`Advertise transport`,value:b?.advertise_transport||`н/д`}),(0,C.jsx)(A,{label:`Connectivity`,value:b?.connectivity_mode||`н/д`}),(0,C.jsx)(A,{label:`NAT`,value:b?.nat_type||`н/д`}),(0,C.jsx)(A,{label:`Region/site`,value:b?.region||`н/д`}),(0,C.jsx)(A,{label:`Version`,value:b?.config_version||`н/д`})]})]}),(0,C.jsxs)(`section`,{className:`nodePanel`,children:[(0,C.jsx)(`h4`,{children:`Что узел сообщает кластеру`}),(0,C.jsxs)(`div`,{className:`stateList`,children:[(0,C.jsx)(A,{label:`Advertised endpoint`,value:ge||`не прислан`}),(0,C.jsx)(A,{label:`Transport`,value:ve}),(0,C.jsx)(A,{label:`Connectivity`,value:ye}),(0,C.jsx)(A,{label:`NAT`,value:be}),(0,C.jsx)(A,{label:`Region/site`,value:xe}),(0,C.jsx)(A,{label:`Observed`,value:Se})]})]})]}),(0,C.jsxs)(`section`,{className:`nodePanel`,children:[(0,C.jsx)(`h4`,{children:`Исходящий control-channel`}),(0,C.jsxs)(`div`,{className:`stateList`,children:[(0,C.jsx)(A,{label:`Status`,value:M(y,`status`,`не прислан`)}),(0,C.jsx)(A,{label:`Direction`,value:M(y,`direction`,`н/д`)}),(0,C.jsx)(A,{label:`Transport`,value:M(y,`transport`,`н/д`)}),(0,C.jsx)(A,{label:`Control Plane`,value:M(y,`control_plane_url`,`н/д`)}),(0,C.jsx)(A,{label:`Reverse usable`,value:M(y,`usable_for_inbound_control`,`н/д`)}),(0,C.jsx)(A,{label:`Inbound required`,value:M(y,`inbound_listener_required`,`н/д`)}),(0,C.jsx)(A,{label:`Relay ready`,value:M(y,`peer_connection_relay_ready`,`0`)}),(0,C.jsx)(A,{label:`Waiting rendezvous`,value:M(y,`peer_connection_waiting`,`0`)}),(0,C.jsx)(A,{label:`Rendezvous leases`,value:M(y,`rendezvous_lease_count`,`0`)}),(0,C.jsx)(A,{label:`Listener conflict`,value:M(y,`listener_port_conflict`,`false`)})]})]}),(0,C.jsxs)(`section`,{className:`nodePanel`,children:[(0,C.jsx)(`h4`,{children:`Наличие сетевых отчетов`}),(0,C.jsx)(`div`,{className:`summaryChips`,children:Ae.map(e=>(0,C.jsx)(`span`,{className:e.includes(`не прислан`)||e.includes(`не загружен`)?`pill warn`:`pill good`,children:e},e))}),!v&&!g&&(0,C.jsx)(`p`,{className:`muted`,children:`У этого узла есть heartbeat/mesh manager данные, но агент не передал адресный отчет. До обновления агента или включения endpoint/listener report панель может показать связи и config peers, но не может достоверно назвать локальный listen address.`})]}),(0,C.jsxs)(`section`,{className:`nodePanel`,children:[(0,C.jsx)(`h4`,{children:`Endpoint candidates узла`}),(0,C.jsx)(je,{columns:[`id`,`address`,`transport`,`reachability`,`mode`,`nat`,`priority`,`verified`],rows:Te.map(e=>[M(e,`endpoint_id`,`н/д`),M(e,`address`,`н/д`),M(e,`transport`,`н/д`),M(e,`reachability`,`н/д`),M(e,`connectivity_mode`,`н/д`),M(e,`nat_type`,`н/д`),M(e,`priority`,`н/д`),M(e,`last_verified_at`,`н/д`)])})]}),(0,C.jsxs)(`div`,{className:`nodeDetailGrid`,children:[(0,C.jsxs)(`section`,{className:`nodePanel`,children:[(0,C.jsx)(`h4`,{children:`Рабочие peer endpoints из config`}),(0,C.jsx)(je,{columns:[`peer`,`endpoint`],rows:Ee.map(([e,t])=>[ut(l,e),t])})]}),(0,C.jsxs)(`section`,{className:`nodePanel`,children:[(0,C.jsx)(`h4`,{children:`Резервные кандидаты peer`}),(0,C.jsx)(je,{columns:[`peer`,`address`,`transport`,`reachability`,`mode`,`priority`],rows:ke.slice(0,20).map(({peerID:e,candidate:t})=>[ut(l,e),t.address,t.transport,t.reachability,t.connectivity_mode,String(t.priority)])})]})]}),(0,C.jsxs)(`section`,{className:`nodePanel`,children:[(0,C.jsx)(`h4`,{children:`Активные связи этого узла`}),(0,C.jsx)(je,{columns:[`peer`,`направление`,`тип`,`статус`,`latency`,`quality`,`путь`,`наблюдение`],rows:re.slice(0,20).map(t=>[ut(l,t.source_node_id===e.id?t.target_node_id:t.source_node_id),t.source_node_id===e.id?`out`:`in`,Ye(t),t.link_status,t.latency_ms==null?`н/д`:`${t.latency_ms}мс`,t.quality_score==null?`н/д`:String(t.quality_score),Xe(t,l),F(t.observed_at)])}),E.length>0&&(0,C.jsxs)(`p`,{className:`muted`,children:[`Проблемных связей: `,E.length,`. Их статус виден в таблице выше.`]})]}),(0,C.jsxs)(`section`,{className:`nodePanel`,children:[(0,C.jsx)(`h4`,{children:`Проверка адресов peer-to-peer`}),(0,C.jsx)(je,{columns:[`peer`,`status`,`selected endpoint`,`candidate`,`latency`,`attempts`,`failure`],rows:oe.slice(0,20).map(e=>[ut(l,M(e,`node_id`,``)),M(e,`link_status`,`н/д`),M(e,`selected_endpoint`,M(e,`endpoint`,`н/д`)),M(e,`selected_candidate_id`,`н/д`),M(e,`latency_ms`,`н/д`),ot(e),M(e,`failure_reason`,`нет`)])})]})]}),se===`mesh`&&(0,C.jsxs)(C.Fragment,{children:[(0,C.jsxs)(`div`,{className:`nodeDetailGrid`,children:[(0,C.jsxs)(`section`,{className:`nodePanel`,children:[(0,C.jsx)(`h4`,{children:`Mesh control-plane`}),(0,C.jsxs)(`div`,{className:`stateList`,children:[(0,C.jsx)(A,{label:`Recovery`,value:sn(m)}),(0,C.jsx)(A,{label:`Intents`,value:cn(m)}),(0,C.jsx)(A,{label:`Manager`,value:pn(m)}),(0,C.jsx)(A,{label:`Rendezvous`,value:ln(m)}),(0,C.jsx)(A,{label:`Path decisions`,value:un(m)}),(0,C.jsx)(A,{label:`Route generation`,value:dn(m)}),(0,C.jsx)(A,{label:`Route health`,value:fn(m)}),(0,C.jsx)(A,{label:`Config version`,value:c?.config_version||`н/д`})]})]}),(0,C.jsxs)(`section`,{className:`nodePanel`,children:[(0,C.jsx)(`h4`,{children:`Scoped config counts`}),(0,C.jsxs)(`div`,{className:`stateList`,children:[(0,C.jsx)(A,{label:`Peer endpoints`,value:String(Ee.length)}),(0,C.jsx)(A,{label:`Endpoint candidates`,value:String(De.length)}),(0,C.jsx)(A,{label:`Peer directory`,value:String(c?.peer_directory?.length||0)}),(0,C.jsx)(A,{label:`Recovery seeds`,value:String(c?.recovery_seeds?.length||0)}),(0,C.jsx)(A,{label:`Rendezvous leases`,value:String(c?.rendezvous_leases?.length||0)}),(0,C.jsx)(A,{label:`Routes`,value:String(c?.routes?.length||0)})]})]})]}),(0,C.jsxs)(`section`,{className:`nodePanel`,children:[(0,C.jsx)(`h4`,{children:`Route decisions`}),(0,C.jsx)(je,{columns:[`route`,`source`,`destination`,`effective hops`,`relay`,`score`,`expires`],rows:(c?.route_path_decisions?.decisions||[]).map(e=>[P(e.route_id),ut(l,e.source_node_id),ut(l,e.destination_node_id),e.effective_hops.map(e=>_n(ut(l,e))).join(` > `),e.selected_relay_id?ut(l,e.selected_relay_id):`direct`,e.path_score==null?`н/д`:String(e.path_score),F(e.expires_at)])})]})]}),se===`services`&&(0,C.jsxs)(C.Fragment,{children:[(0,C.jsxs)(`div`,{className:`nodeDetailGrid`,children:[(0,C.jsxs)(`section`,{className:`nodePanel`,children:[(0,C.jsx)(`h4`,{children:p.nodeRoles}),(0,C.jsxs)(`div`,{className:`serviceTags`,children:[n.length===0&&(0,C.jsx)(`p`,{className:`muted`,children:p.noRoles}),n.map(e=>(0,C.jsxs)(`div`,{className:`serviceTag`,children:[(0,C.jsx)(`strong`,{children:Ie(e.role)}),(0,C.jsx)(`span`,{children:e.organization_id?`organization: ${P(e.organization_id)}`:`cluster-wide`}),(0,C.jsx)(`small`,{children:F(e.assigned_at)}),(0,C.jsx)(`span`,{className:`pill ${rn(e.role,m)}`,children:an(e.role,m,p)})]},e.id))]})]}),(0,C.jsxs)(`section`,{className:`nodePanel`,children:[(0,C.jsx)(`h4`,{children:`Capabilities`}),(0,C.jsxs)(`div`,{className:`summaryChips`,children:[ae.length===0&&(0,C.jsx)(`span`,{className:`muted`,children:`Нет capability heartbeat.`}),ae.slice(0,40).map(([e,t])=>(0,C.jsx)(`span`,{className:t===!0?`pill good`:`pill`,children:e},e))]})]})]}),(0,C.jsxs)(`div`,{className:`nodeDetailGrid`,children:[(0,C.jsxs)(`section`,{className:`nodePanel`,children:[(0,C.jsx)(`h4`,{children:p.desiredServices}),(0,C.jsx)(je,{columns:[`service`,`desired`,`runtime`,`version`,`updated`],rows:r.map(e=>[e.service_type,I(e.desired_state),e.runtime_mode,e.version||`не закреплена`,F(e.updated_at)])})]}),(0,C.jsxs)(`section`,{className:`nodePanel`,children:[(0,C.jsx)(`h4`,{children:p.observedServices}),(0,C.jsx)(je,{columns:[`service`,`reported`,`runtime`,`version`,`observed`],rows:i.map(e=>[e.service_type,I(e.reported_state),e.runtime_mode,e.version||`н/д`,F(e.observed_at)])})]})]})]}),se===`telemetry`&&(0,C.jsxs)(C.Fragment,{children:[(0,C.jsxs)(`section`,{className:`nodePanel`,children:[(0,C.jsx)(`h4`,{children:p.nodeTelemetry}),(0,C.jsx)(he,{items:o,emptyText:p.noTelemetry}),(0,C.jsxs)(`div`,{className:`stateList`,children:[(0,C.jsx)(A,{label:`Disk`,value:`${xn(h?.disk_used_bytes)} / ${xn(h?.disk_total_bytes)}`}),(0,C.jsx)(A,{label:`Network RX/TX`,value:`${xn(h?.network_rx_bytes)} / ${xn(h?.network_tx_bytes)}`}),(0,C.jsx)(A,{label:`Payload`,value:h?.payload?at(h.payload):`н/д`})]})]}),(0,C.jsxs)(`section`,{className:`nodePanel`,children:[(0,C.jsx)(`h4`,{children:p.recentHeartbeats}),(0,C.jsx)(je,{columns:[`состояние`,`версия`,`listener`,`mesh recovery`,`mesh intents`,`rv leases`,`path decisions`,`route gen`,`route health`,`наблюдение`],rows:a.slice(0,10).map(e=>[e.health_status,e.reported_version||`неизвестно`,mn(e),sn(e),cn(e),ln(e),un(e),dn(e),fn(e),F(e.observed_at)])})]})]}),se===`updates`&&(0,C.jsxs)(C.Fragment,{children:[(0,C.jsxs)(`div`,{className:`nodeDetailGrid`,children:[(0,C.jsxs)(`section`,{className:`nodePanel`,children:[(0,C.jsx)(`h4`,{children:`Текущая сборка`}),(0,C.jsxs)(`div`,{className:`stateList`,children:[(0,C.jsx)(A,{label:`Node-agent version`,value:e.reported_version||m?.reported_version||`неизвестно`}),(0,C.jsx)(A,{label:`План`,value:d?`${d.action}: ${d.reason}`:`не загружен`}),(0,C.jsx)(A,{label:`Product`,value:d?.product||`rap-node-agent`}),(0,C.jsx)(A,{label:`Target`,value:d?.target_version||`н/д`}),(0,C.jsx)(A,{label:`Strategy`,value:d?.strategy||`н/д`}),(0,C.jsx)(A,{label:`Rollback`,value:d?.rollback_allowed?`разрешен`:`нет`}),(0,C.jsx)(A,{label:`Artifact`,value:d?.artifact?`${d.artifact.kind} ${d.artifact.os}/${d.artifact.arch}`:`н/д`})]}),(0,C.jsxs)(`div`,{className:`actions`,children:[(0,C.jsx)(`button`,{className:`primary`,disabled:!u,onClick:()=>u?.(e,`rap-node-agent`,null),children:`Node-agent latest`}),(0,C.jsx)(`button`,{className:`ghost`,disabled:!u||!d?.target_version,onClick:()=>u?.(e,`rap-node-agent`,d?.target_version||null),children:`Повторить target`}),(0,C.jsx)(`button`,{className:`ghost`,disabled:!u,onClick:()=>u?.(e,`rap-host-agent`,null),children:`Host-agent latest`})]}),(0,C.jsx)(`p`,{className:`muted`,children:`Latest означает policy без закрепленной версии: updater будет брать свежий active release своего канала при следующем цикле или heartbeat hint.`})]}),(0,C.jsxs)(`section`,{className:`nodePanel`,children:[(0,C.jsx)(`h4`,{children:`Последние отчеты updater`}),(0,C.jsxs)(`div`,{className:`stateList`,children:[(0,C.jsx)(A,{label:`Updater health`,value:`${ue.label}: ${ue.detail}`}),(0,C.jsx)(A,{label:`rap-node-agent`,value:Ve(D)}),(0,C.jsx)(A,{label:`rap-host-agent`,value:Ve(O)}),(0,C.jsx)(A,{label:`Всего отчетов`,value:String(f.length)}),(0,C.jsx)(A,{label:`Последний отчет`,value:F(le?.observed_at)})]}),(0,C.jsxs)(`div`,{className:`summaryChips`,children:[(0,C.jsx)(`span`,{className:`pill ${ue.tone}`,children:ue.label}),D&&(0,C.jsxs)(`span`,{className:`pill ${Ue(D)}`,children:[`node-agent: `,D.status]}),O&&(0,C.jsxs)(`span`,{className:`pill ${Ue(O)}`,children:[`host-agent: `,O.status]}),!D&&!O&&(0,C.jsx)(`span`,{className:`pill warn`,children:`updater пока не отчитался`})]})]})]}),(0,C.jsxs)(`section`,{className:`nodePanel`,children:[(0,C.jsx)(`h4`,{children:`История обновлений`}),(0,C.jsx)(je,{columns:[`product`,`current`,`target`,`phase`,`status`,`attempt`,`error`,`observed`],rows:f.slice(0,40).map(e=>[e.product,e.current_version||`н/д`,e.target_version||`н/д`,e.phase,(0,C.jsx)(`span`,{className:`pill ${Ue(e)}`,children:e.status}),e.attempt_id?P(e.attempt_id):`н/д`,e.error_message||`нет`,F(e.observed_at)])})]}),(0,C.jsxs)(`section`,{className:`nodePanel`,children:[(0,C.jsx)(`h4`,{children:`Windows repair/update command`}),(0,C.jsx)(`p`,{className:`muted`,children:`Для существующего Windows-узла эта команда переустанавливает wrapper updater без нового join-token, сохраняет local state и запускает обновление до актуальной сборки.`}),(0,C.jsxs)(`div`,{className:`stateList compact`,children:[(0,C.jsx)(A,{label:`Когда выполнять`,value:`если updater stale, host-agent не отчитался или Windows-узел не доходит до target version`}),(0,C.jsx)(A,{label:`Control Plane`,value:Wt()}),(0,C.jsx)(A,{label:`Join-token`,value:`не нужен для repair существующего узла`})]}),(0,C.jsxs)(`div`,{className:`actions`,children:[(0,C.jsx)(`button`,{className:`primary`,onClick:()=>Lt(Pt(e),Nt(e,fe)),children:`Скачать repair .cmd`}),(0,C.jsx)(`button`,{className:`ghost`,onClick:()=>void Rt(Nt(e,fe)),children:`Скопировать команду`})]}),(0,C.jsx)(`pre`,{className:`codePreview`,children:Nt(e,fe)})]}),(0,C.jsxs)(`section`,{className:`nodePanel`,children:[(0,C.jsx)(`h4`,{children:`Linux repair/update command`}),(0,C.jsx)(`p`,{className:`muted`,children:`Для существующего Ubuntu/Linux-узла эта команда восстанавливает systemd updater без нового join-token, сохраняет local state и делает одноразовую проверку обновления.`}),(0,C.jsxs)(`div`,{className:`stateList compact`,children:[(0,C.jsx)(A,{label:`Когда выполнять`,value:`если host-agent не отчитался, updater stale или Linux-узел не доходит до target version`}),(0,C.jsx)(A,{label:`Control Plane`,value:Wt()}),(0,C.jsx)(A,{label:`Join-token`,value:`не нужен для repair существующего узла`})]}),(0,C.jsxs)(`div`,{className:`actions`,children:[(0,C.jsx)(`button`,{className:`primary`,onClick:()=>Lt(It(e),Ft(e,fe)),children:`Скачать repair .sh`}),(0,C.jsx)(`button`,{className:`ghost`,onClick:()=>void Rt(Ft(e,fe)),children:`Скопировать команду`})]}),(0,C.jsx)(`pre`,{className:`codePreview`,children:Ft(e,fe)})]}),(0,C.jsxs)(`section`,{className:`nodePanel`,children:[(0,C.jsx)(`h4`,{children:`Payload последнего отчета`}),(0,C.jsxs)(`div`,{className:`rawDetailsGrid`,children:[(0,C.jsx)(_e,{title:`rap-node-agent update status`,value:D}),(0,C.jsx)(_e,{title:`rap-host-agent update status`,value:O}),(0,C.jsx)(_e,{title:`Update plan`,value:d})]})]})]}),se===`raw`&&(0,C.jsxs)(`section`,{className:`nodePanel`,children:[(0,C.jsx)(`h4`,{children:`Raw данные узла`}),(0,C.jsxs)(`div`,{className:`rawDetailsGrid`,children:[(0,C.jsx)(_e,{title:`Последний heartbeat metadata`,value:m?.metadata}),(0,C.jsx)(_e,{title:`Heartbeat capabilities`,value:m?.capabilities}),(0,C.jsx)(_e,{title:`Heartbeat service states`,value:m?.service_states}),(0,C.jsx)(_e,{title:`Synthetic mesh config`,value:c}),(0,C.jsx)(_e,{title:`Listener report`,value:g}),(0,C.jsx)(_e,{title:`Endpoint report`,value:v}),(0,C.jsx)(_e,{title:`Peer recovery report`,value:x}),(0,C.jsx)(_e,{title:`Connection intent report`,value:S}),(0,C.jsx)(_e,{title:`Connection manager report`,value:w}),(0,C.jsx)(_e,{title:`Rendezvous lease report`,value:ee}),(0,C.jsx)(_e,{title:`Route decision report`,value:te}),(0,C.jsx)(_e,{title:`Route generation report`,value:ne}),(0,C.jsx)(_e,{title:`Route health report`,value:T})]})]})]})}function _e({title:e,value:t}){return(0,C.jsxs)(`details`,{className:`rawBlock`,children:[(0,C.jsx)(`summary`,{children:e}),(0,C.jsx)(`pre`,{children:t==null?`н/д`:JSON.stringify(t,null,2)})]})}function ve({runtime:e}){return(0,C.jsxs)(`div`,{className:`runtimeBadges`,children:[(0,C.jsx)(`span`,{className:`pill ${e.agentTone}`,children:e.agentLabel}),(0,C.jsx)(`span`,{className:`pill ${e.clientTone}`,children:e.clientLabel}),(0,C.jsx)(`span`,{className:`pill ${e.outboundTone}`,children:e.outboundLabel}),(0,C.jsx)(`span`,{className:`pill ${e.inboundTone}`,children:e.inboundLabel})]})}function ye({node:e,fallback:t,heartbeatsByNode:n,meshLinks:r}){if(!e)return t;let i=rt(e,n[e.id]||[],r);return(0,C.jsxs)(`div`,{className:`nodeEndpointCell`,children:[(0,C.jsx)(`strong`,{children:e.name}),(0,C.jsx)(ve,{runtime:i}),(0,C.jsx)(`small`,{children:i.address})]})}function be({nodes:e,links:t,syntheticMeshConfigsByNode:n,entryPoints:r,entryPointNodesById:i,egressPools:a,egressPoolNodesById:o,rolesByNode:s,workloadsByNode:c,telemetryByNode:l,labels:u,emptyText:d}){if(e.length===0)return(0,C.jsx)(pe,{title:`Нет узлов`,text:`Одобренные node-agent появятся на карте после первого heartbeat.`});let f=qe(t).filter(e=>e.source_node_id!==e.target_node_id),p=new Map(e.map(e=>[e.id,e])),m=f.map(e=>({link:e,status:Se(e,f,p)})),h=m.filter(e=>e.status===`reachable`),g=m.filter(e=>e.status===`one_way`),_=m.filter(e=>e.status===`stale`),v=m.filter(e=>e.status!==`reachable`&&e.status!==`one_way`&&e.status!==`stale`),y=ke(e,n),b=j(e.length,r.length,a.length),x=Te(e.length),S=De(e,b.height,x),w=new Map(r.map((e,t)=>[e.id,we(170,t,r.length,150,b.height-100)])),ee=new Map(a.map((e,t)=>[e.id,we(950,t,a.length,150,b.height-100)])),te=new Set(m.filter(e=>e.status!==`stale`).map(e=>`${e.link.source_node_id}->${e.link.target_node_id}`)),ne=new Map(e.map(e=>[e.id,xe(e.id,m)])),T=y.filter(e=>!te.has(`${e.source_node_id}->${e.target_node_id}`)),re=r.flatMap(e=>(i[e.id]||[]).filter(e=>e.status!==`disabled`).map(t=>({entryPoint:e,assignment:t}))),ie=a.flatMap(e=>(o[e.id]||[]).filter(e=>e.status!==`disabled`).map(t=>({pool:e,assignment:t}))),E=re.length+ie.length;return(0,C.jsxs)(`div`,{className:`topologyShell`,children:[(0,C.jsxs)(`svg`,{className:`topologySvg`,viewBox:`0 0 ${b.width} ${b.height}`,role:`img`,"aria-label":`Карта трафика узлов Fabric`,children:[(0,C.jsx)(`defs`,{children:(0,C.jsx)(`marker`,{id:`arrow`,markerHeight:`8`,markerWidth:`8`,orient:`auto`,refX:`7`,refY:`4`,children:(0,C.jsx)(`path`,{d:`M0,0 L8,4 L0,8 Z`,fill:`currentColor`})})}),(0,C.jsx)(`rect`,{x:`36`,y:`58`,width:`268`,height:b.height-100,rx:`24`,className:`topologyZone ingress`}),(0,C.jsx)(`rect`,{x:`330`,y:`58`,width:`460`,height:b.height-100,rx:`24`,className:`topologyZone core`}),(0,C.jsx)(`rect`,{x:`816`,y:`58`,width:`268`,height:b.height-100,rx:`24`,className:`topologyZone egress`}),(0,C.jsx)(`text`,{x:`170`,y:`98`,className:`topologyLayerLabel`,children:u.fabricIngressLayer}),(0,C.jsx)(`text`,{x:`560`,y:`98`,className:`topologyLayerLabel`,children:u.fabricNodeLayer}),(0,C.jsx)(`text`,{x:`950`,y:`98`,className:`topologyLayerLabel`,children:u.fabricEgressLayer}),re.map(({entryPoint:e,assignment:t})=>{let n=w.get(e.id),r=S.get(t.node_id);return!n||!r?null:(0,C.jsx)(`line`,{x1:n.x+78,y1:n.y,x2:r.x-x-8,y2:r.y,className:`topologyPlacementLink ${t.status===`active`?`good`:`weak`}`,markerEnd:`url(#arrow)`},`entry-${e.id}-${t.node_id}`)}),ie.map(({pool:e,assignment:t})=>{let n=S.get(t.node_id),r=ee.get(e.id);return!n||!r?null:(0,C.jsx)(`line`,{x1:n.x+x+8,y1:n.y,x2:r.x-78,y2:r.y,className:`topologyPlacementLink ${t.status===`active`?`good`:`weak`}`,markerEnd:`url(#arrow)`},`egress-${e.id}-${t.node_id}`)}),T.map(e=>{let t=S.get(e.source_node_id),n=S.get(e.target_node_id);if(!t||!n)return null;let r=Oe(t,n,x+8);return(0,C.jsx)(`line`,{x1:r.x1,y1:r.y1,x2:r.x2,y2:r.y2,className:`topologyConfiguredLink`,markerEnd:`url(#arrow)`},e.id)}),m.map(({link:e,status:t})=>{let n=S.get(e.source_node_id),r=S.get(e.target_node_id);if(!n||!r)return null;let i=Oe(n,r,x+8),a=(i.x1+i.x2)/2,o=(i.y1+i.y2)/2;return(0,C.jsxs)(`g`,{children:[(0,C.jsx)(`line`,{x1:i.x1,y1:i.y1,x2:i.x2,y2:i.y2,className:`topologyLink ${gn(e,t)}`,markerEnd:`url(#arrow)`}),(0,C.jsx)(`text`,{x:a,y:o-8,className:`topologyLinkLabel`,children:Ze(e,t)})]},e.id||`${e.source_node_id}-${e.target_node_id}`)}),r.map(e=>{let t=w.get(e.id),n=(i[e.id]||[]).length;return(0,C.jsxs)(`g`,{className:`topologyEndpoint`,children:[(0,C.jsx)(`rect`,{x:t.x-86,y:t.y-36,width:`172`,height:`72`,rx:`20`,className:`topologyEndpointRect ${e.status}`}),(0,C.jsx)(`text`,{x:t.x,y:t.y-8,className:`topologyEndpointName`,children:_n(e.name)}),(0,C.jsxs)(`text`,{x:t.x,y:t.y+15,className:`topologyEndpointMeta`,children:[e.endpoint_type,` · `,n]})]},e.id)}),e.map(t=>{let n=S.get(t.id),r=Ee(e.length),i=Fe(s[t.id]||[]),a=ne.get(t.id)||`isolated`;return(0,C.jsxs)(`g`,{className:`topologyNode`,children:[(0,C.jsx)(`circle`,{cx:n.x,cy:n.y,r:x,className:`topologyNodeCircle ${t.health_status}`}),(0,C.jsx)(`text`,{x:n.x,y:n.y-r.nameOffset,className:`topologyNodeName`,style:{fontSize:r.name},children:_n(t.name,r.maxChars)}),(0,C.jsxs)(`text`,{x:n.x,y:n.y+r.metaOffset,className:`topologyNodeMeta`,style:{fontSize:r.meta},children:[i.length,` активн. ролей / `,(c[t.id]||[]).length,` серв.`]}),(0,C.jsxs)(`text`,{x:n.x,y:n.y+r.memoryOffset,className:`topologyNodeMeta`,style:{fontSize:r.meta},children:[`mesh: `,I(a)]})]},t.id)}),a.map(e=>{let t=ee.get(e.id),n=(o[e.id]||[]).length;return(0,C.jsxs)(`g`,{className:`topologyEndpoint`,children:[(0,C.jsx)(`rect`,{x:t.x-86,y:t.y-36,width:`172`,height:`72`,rx:`20`,className:`topologyEndpointRect ${e.status}`}),(0,C.jsx)(`text`,{x:t.x,y:t.y-8,className:`topologyEndpointName`,children:_n(e.name)}),(0,C.jsxs)(`text`,{x:t.x,y:t.y+15,className:`topologyEndpointMeta`,children:[`egress · `,n]})]},e.id)}),m.length===0&&T.length===0&&E===0&&(0,C.jsx)(`text`,{x:b.width/2,y:b.height-34,className:`topologyEmpty`,children:d})]}),(0,C.jsxs)(`div`,{className:`topologyLegend`,children:[(0,C.jsxs)(`span`,{children:[(0,C.jsx)(`i`,{className:`legendLine placement`}),` `,u.placementIntent,`: `,E]}),(0,C.jsxs)(`span`,{children:[(0,C.jsx)(`i`,{className:`legendLine observed`}),` реальные: `,h.length]}),(0,C.jsxs)(`span`,{children:[(0,C.jsx)(`i`,{className:`legendLine oneWay`}),` one-way: `,g.length]}),(0,C.jsxs)(`span`,{children:[(0,C.jsx)(`i`,{className:`legendLine stale`}),` stale: `,_.length]}),(0,C.jsxs)(`span`,{children:[(0,C.jsx)(`i`,{className:`legendLine problem`}),` проблемы: `,v.length]}),(0,C.jsxs)(`span`,{children:[(0,C.jsx)(`i`,{className:`legendLine configured`}),` configured: `,T.length]})]}),(0,C.jsxs)(`div`,{className:`serviceTags`,children:[r.map(t=>(0,C.jsxs)(`div`,{className:`serviceTag`,children:[(0,C.jsx)(`strong`,{children:t.name}),(0,C.jsx)(`span`,{children:t.endpoint_type}),(0,C.jsx)(`small`,{children:(i[t.id]||[]).map(t=>ut(e,t.node_id)).join(`, `)||u.assignedNodesEmpty})]},t.id)),e.map(e=>(0,C.jsxs)(`div`,{className:`serviceTag`,children:[(0,C.jsx)(`strong`,{children:e.name}),(0,C.jsxs)(`span`,{children:[I(e.health_status),` / mesh `,I(ne.get(e.id)||`isolated`)]}),(0,C.jsx)(`small`,{children:Pe(s[e.id]||[])}),(0,C.jsx)(`small`,{children:Le(c[e.id]||[])})]},e.id)),a.map(t=>(0,C.jsxs)(`div`,{className:`serviceTag`,children:[(0,C.jsx)(`strong`,{children:t.name}),(0,C.jsx)(`span`,{children:t.status}),(0,C.jsx)(`small`,{children:(o[t.id]||[]).map(t=>ut(e,t.node_id)).join(`, `)||u.assignedNodesEmpty})]},t.id))]})]})}function xe(e,t){let n=t.filter(t=>t.link.source_node_id!==t.link.target_node_id&&(t.link.source_node_id===e||t.link.target_node_id===e));return n.some(e=>e.status===`reachable`||e.status===`one_way`)?`connected`:n.some(e=>e.status!==`stale`)?`degraded`:`isolated`}function Se(e,t,n){if(Ce(e,n))return`stale`;if(e.link_status!==`reachable`)return e.link_status===`degraded`||e.link_status===`unreachable`?e.link_status:`unknown`;let r=t.find(t=>t.source_node_id===e.target_node_id&&t.target_node_id===e.source_node_id&&!Ce(t,n));return!r||r.link_status!==`reachable`?`one_way`:`reachable`}function Ce(e,t){if(e.link_status===`stale`||e.metadata?.derived_link_stale===!0)return!0;let n=new Date(e.observed_at).getTime();if(!Number.isFinite(n)||Date.now()-n>120*1e3)return!0;if(!t)return!1;let r=t.get(e.source_node_id),i=t.get(e.target_node_id);return r?.health_status!==`healthy`||i?.health_status!==`healthy`}function we(e,t,n,r,i){return n<=1?{x:e,y:Math.round((r+i)/2)}:{x:e,y:Math.round(r+(i-r)*t/(n-1))}}function j(e,t,n){let r=Math.max(e,t,n,1),i=Math.max(Math.ceil(e/3),r);return{width:1120,height:Math.max(640,220+i*104)}}function Te(e){return e>48?26:e>24?32:e>12?40:52}function Ee(e){return e>48?{name:12,meta:9,nameOffset:7,metaOffset:7,memoryOffset:20,maxChars:10}:e>24?{name:14,meta:10,nameOffset:8,metaOffset:9,memoryOffset:24,maxChars:12}:e>12?{name:16,meta:12,nameOffset:10,metaOffset:11,memoryOffset:28,maxChars:14}:{name:21,meta:15,nameOffset:12,metaOffset:10,memoryOffset:31,maxChars:18}}function De(e,t,n){let r=e.length>24?4:e.length>8?3:e.length>3?2:1,i=Math.max(1,Math.ceil(e.length/r)),a=r===1?0:300/(r-1),o=t-96,s=i===1?0:(o-142)/(i-1);return new Map(e.map((e,t)=>{let n=t%r,c=Math.floor(t/r);return[e.id,{x:Math.round(r===1?560:410+a*n),y:Math.round(i===1?(142+o)/2:142+s*c)}]}))}function Oe(e,t,n){let r=t.x-e.x,i=t.y-e.y,a=Math.max(Math.sqrt(r*r+i*i),1),o=r/a*n,s=i/a*n;return{x1:e.x+o,y1:e.y+s,x2:t.x-o,y2:t.y-s}}function ke(e,t){let n=new Set(e.map(e=>e.id)),r=new Map;for(let[e,i]of Object.entries(t))for(let t of i.routes||[]){let i=t.hops||[];for(let a=0;a${s}`,{id:`configured-${e}-${t.route_id}-${a}`,source_node_id:o,target_node_id:s})}}return[...r.values()]}function Ae({children:e}){return(0,C.jsx)(`div`,{className:`formGrid`,children:e})}function je({columns:e,rows:t}){return t.length===0?(0,C.jsx)(pe,{title:`Нет данных`,text:`В текущей области пока нечего показать.`}):(0,C.jsx)(`div`,{className:`tableWrap`,children:(0,C.jsxs)(`table`,{children:[(0,C.jsx)(`thead`,{children:(0,C.jsx)(`tr`,{children:e.map(e=>(0,C.jsx)(`th`,{children:e},e))})}),(0,C.jsx)(`tbody`,{children:t.map((e,t)=>(0,C.jsx)(`tr`,{children:e.map((e,n)=>(0,C.jsx)(`td`,{children:e},`${t}-${n}`))},t))})]})})}function Me(e,t){let n=JSON.parse(e||`{}`);if(!n||Array.isArray(n)||typeof n!=`object`)throw Error(`${t}: требуется JSON object.`);return n}function Ne(e,t){let n=JSON.parse(e||`[]`);if(!Array.isArray(n))throw Error(`${t}: требуется JSON array.`);return n}function Pe(e){let t=Fe(e);return t.length===0?`активные роли не назначены`:t.map(e=>`${Ie(e.role)}${e.organization_id?` @ ${P(e.organization_id)}`:``}`).join(`, `)}function Fe(e){return e.filter(e=>e.status===`active`)}function Ie(e){let t=T[e];return t?`${t} (${e})`:e}function Le(e){return e.length===0?`нет сервисов`:e.map(e=>`${e.service_type}:${e.reported_state}`).join(`, `)}function Re(e,t,n){let r=n.find(e=>e.product===`rap-node-agent`&&e.channel===`stable`&&e.status===`active`)||n.find(e=>e.product===`rap-node-agent`&&e.status===`active`),i=e.reported_version||``,a=t?.target_version||r?.version||``;return e.version_state&&e.version_state!==`unknown`?{status:e.version_state,targetLabel:a?`target ${a}`:`policy target unknown`}:i?t?.action===`update`?{status:`outdated`,targetLabel:`target ${t.target_version||a}`}:a&&i!==a?{status:`outdated`,targetLabel:`latest ${a}`}:a&&i===a?{status:`current`,targetLabel:`latest ${a}`}:{status:t?.reason===`no_update_policy`?`no_policy`:`unknown`,targetLabel:t?.reason||`release policy unknown`}:{status:`unknown`,targetLabel:a?`target ${a}`:`target unknown`}}function ze(e,t){return e.find(e=>e.product===t)}function Be(e,t){return e?`${e.product}: ${e.phase}/${e.status}`:t?`${t.action}: ${t.reason}`:`нет отчета`}function Ve(e){if(!e)return`нет отчета`;let t=e.target_version?` -> ${e.target_version}`:``,n=e.error_message?`, ошибка: ${e.error_message}`:``;return`${e.current_version||`н/д`}${t}, ${e.phase}/${e.status}, ${F(e.observed_at)}${n}`}function He(e){return e?`push ${e.pushed||0} / pop ${e.popped||0} / q ${e.queue_depth||0} / drop ${e.dropped||0}`:`нет данных`}function Ue(e){if(!e)return`warn`;let t=`${e.phase}:${e.status}`.toLowerCase();return t.includes(`error`)||t.includes(`failed`)||t.includes(`rollback`)?`bad`:t.includes(`success`)||t.includes(`updated`)||t.includes(`noop`)||t.includes(`already_current`)?`good`:t.includes(`download`)||t.includes(`replace`)||t.includes(`plan`)||t.includes(`apply`)?`warn`:``}function We(e){let t=ze(e,`rap-node-agent`),n=ze(e,`rap-host-agent`);if(!t&&!n)return{label:`updater: нет отчета`,detail:`repair/update task не отчитался`,tone:`bad`};let r=[t,n].some(e=>e&&Ge(e)),i=!n,a=n?.phase===`apply`&&n?.status===`staged`,o=[t,n].some(e=>e&&Ue(e)===`bad`),s=t?`${t.current_version||`?`}->${t.target_version||`?`}`:`node ?`,c=n?`${n.current_version||`?`}->${n.target_version||`?`}`:`host ?`,l=F((n||t)?.observed_at);return o?{label:`updater: ошибка`,detail:`${s}; ${c}; ${l}`,tone:`bad`}:i?{label:`repair updater`,detail:`host-agent не отчитался; ${s}; ${l}`,tone:`warn`}:a?{label:`host-agent staged`,detail:`${c}; нужен следующий запуск updater`,tone:`warn`}:r?{label:`updater: stale`,detail:`${s}; ${c}; ${l}`,tone:`warn`}:{label:`updater: ok`,detail:`${s}; ${c}; ${l}`,tone:`good`}}function Ge(e){let t=new Date(e.observed_at).getTime();return!Number.isFinite(t)||Date.now()-t>900*1e3}function Ke(e){let t=typeof e.scope?.node_name==`string`?e.scope.node_name:``,n=typeof e.scope?.purpose==`string`?e.scope.purpose:``;return t||n||P(e.id)}function qe(e){let t=new Map;for(let n of e){let e=`${n.source_node_id}->${n.target_node_id}:${Je(n)}`,r=t.get(e);(!r||new Date(n.observed_at).getTime()>new Date(r.observed_at).getTime())&&t.set(e,n)}return[...t.values()]}function Je(e){let t=Qe(e,`observation_type`)||`default`;return t===`synthetic_route_health`?`${t}:${Qe(e,`route_id`)||e.id}`:t===`peer_connection_manager`?`${t}:${Qe(e,`transport_mode`)}:${Qe(e,`relay_node_id`)}`:t}function Ye(e){let t=Qe(e,`observation_type`);if(t===`synthetic_route_health`){let t=e.metadata?.route_path_drift_detected===!0?`drift`:`ok`;return`route-health ${e.metadata?.route_path_decision_applied===!0?`decision`:`route`} ${t}`}if(t===`peer_connection_manager`){let t=Qe(e,`transport_mode`)||`manager`,n=Qe(e,`connection_state`);return n?`${t} ${n}`:t}return t||`link`}function Xe(e,t){let n=Qe(e,`route_id`),r=Qe(e,`route_path_decision_selected_relay_id`)||Qe(e,`relay_node_id`),i=$e(e,`expected_effective_hops`),a=$e(e,`observed_ack_path`),o=i.length>0?i:a,s=[];return n&&s.push(P(n)),r&&s.push(`via ${P(r)}`),o.length>0&&s.push(o.map(e=>_n(ut(t,e))).join(` > `)),s.length>0?s.join(` / `):`н/д`}function Ze(e,t=e.link_status===`reachable`?`reachable`:`unknown`){return t===`stale`?`stale`:t===`one_way`?`one-way`:Qe(e,`observation_type`)===`synthetic_route_health`?e.metadata?.route_path_drift_detected===!0?`drift`:`route`:e.latency_ms==null?`связь`:`${e.latency_ms}мс`}function Qe(e,t){let n=e.metadata?.[t];return typeof n==`string`?n:``}function $e(e,t){let n=e.metadata?.[t];return Array.isArray(n)?n.filter(e=>typeof e==`string`):[]}function et(e){return e&&typeof e==`object`&&!Array.isArray(e)?e:void 0}function tt(e){return Array.isArray(e)?e.map(e=>et(e)).filter(e=>!!e):[]}function M(e,t,n=``){let r=e?.[t];return typeof r==`string`?r:typeof r==`number`||typeof r==`boolean`?String(r):n}function nt(e,t){for(let n of t){let t=M(e,n,``);if(t)return t}return``}function rt(e,t,n){let r=t[0],i=r?.metadata||{},a=et(i.mesh_listener_report),o=et(i.mesh_endpoint_report),s=et(i.mesh_outbound_session_report),c=et(i.mesh_peer_connection_manager_report),l=et(i.mesh_peer_recovery_report),u=tt(o?.endpoint_candidates)[0],d=nt(o,[`peer_endpoint`,`advertised_endpoint`,`endpoint`])||M(u,`address`,``)||M(a,`effective_listen_addr`,``)||`адрес не прислан`;if(!r&&!e.last_seen_at)return{agentLabel:`agent: no heartbeat`,agentTone:`bad`,clientLabel:`client: unknown`,clientTone:`warn`,outboundLabel:`outbound: no heartbeat`,outboundTone:`bad`,inboundLabel:`inbound: unknown`,inboundTone:`warn`,address:d,detail:`Узел создан/одобрен, но node-agent еще ни разу не прислал heartbeat.`};let f=it(c,`peer_connection_ready`)||it(l,`peer_connection_ready`)||qe(n).filter(t=>(t.source_node_id===e.id||t.target_node_id===e.id)&&t.link_status===`reachable`).length,p=it(c,`peer_connection_total`)||it(l,`peer_connection_total`)||qe(n).filter(t=>t.source_node_id===e.id||t.target_node_id===e.id).length,m=it(c,`failed`),h=M(a,`status`,``),g=a?.port_conflict===!0,_=a?.one_way_connectivity===!0||M(o,`connectivity_mode`,``)===`outbound_only`||it(c,`peer_connection_relay_ready`)>0,v=`inbound: no report`,y=`warn`;h===`listening`||h===`auto_rebound`?(v=h===`auto_rebound`?`inbound: auto port`:`inbound: listening`,y=`good`):h===`listen_failed`?(v=g?`inbound: port busy`:`inbound: failed`,y=`bad`):h===`disabled`?(v=`inbound: disabled`,y=_?`warn`:`bad`):o&&(v=`inbound: advertised`,y=`good`);let b=`client: no peers`,x=`warn`;f>0?(b=`client: ready ${f}/${Math.max(p,f)}`,x=`good`):(m>0||p>0)&&(b=`client: backoff ${f}/${Math.max(p,m)}`,x=`bad`);let S=M(s,`status`,``),C=s?.usable_for_inbound_control===!0,w=it(s,`peer_connection_relay_ready`),ee=it(s,`rendezvous_lease_count`),te=`outbound: no report`,ne=`warn`;S===`ready`?(te=C?`outbound: ready reverse`:`outbound: ready`,ne=`good`):S===`backoff`||S===`failed`?(te=`outbound: ${S}`,ne=`bad`):(_||w>0||ee>0)&&(te=`outbound: inferred`,ne=`warn`);let T=e.health_status===`healthy`?`good`:e.health_status===`unknown`?`warn`:`bad`;return{agentLabel:r?`agent: heartbeat`:`agent: stale`,agentTone:T,clientLabel:_&&f>0?`${b} one-way`:b,clientTone:x,outboundLabel:te,outboundTone:ne,inboundLabel:v,inboundTone:y,address:d,detail:M(a,`failure_error`,M(a,`failure_reason`,``))}}function it(e,t,n=0){let r=e?.[t];return typeof r==`number`&&Number.isFinite(r)?r:n}function at(e){if(e==null)return`н/д`;let t=JSON.stringify(e);return t.length>140?`${t.slice(0,137)}...`:t}function ot(e){let t=tt(e.candidate_results);return t.length===0?`н/д`:t.slice(0,4).map(e=>{let t=M(e,`candidate_id`,`candidate`),n=M(e,`link_status`,`unknown`),r=M(e,`latency_ms`,``);return r&&r!==`0`?`${t}:${n}:${r}мс`:`${t}:${n}`}).join(`, `)}function st(e){return Object.values(e.peer_endpoint_candidates||{}).reduce((e,t)=>e+t.length,0)}function ct(e){let t=e?.rendezvous_relay_policy;if(!t)return`none`;let n=[`stale${t.stale_relay_count}`,`wd${t.withdrawn_lease_count}`,`repl${t.replacement_lease_count}`];t.scoring_mode.includes(`synthetic_route_health_feedback`)&&n.push(`rh feedback`);let r=t.decisions?.find(e=>e.selected_relay_id);return r?.selected_relay_id&&n.push(`via ${P(r.selected_relay_id)}`),n.join(` `)}function lt(e){let t=e?.route_path_decisions;if(!t)return`none`;let n=[`path${t.decision_count}`,`repl${t.replacement_decision_count}`],r=t.decisions?.find(e=>e.selected_relay_id||e.next_hop_id);return r?.selected_relay_id?n.push(`via ${P(r.selected_relay_id)}`):r?.next_hop_id&&n.push(`next ${P(r.next_hop_id)}`),n.join(` `)}function ut(e,t){return e.find(e=>e.id===t)?.name||P(t)}function dt(e,t){let n=new Map(t.map(e=>[e.id,e])),r=[e.name],i=e.parent_group_id,a=new Set([e.id]);for(;i&&!a.has(i);){a.add(i);let e=n.get(i);if(!e)break;r.unshift(e.name),i=e.parent_group_id}return r.join(` / `)}function ft(e,t){let n=t.find(t=>t.id===e);return n?dt(n,t):e}function pt(e,t){let n=[],r=new Map;for(let e of t){let t=e.parent_group_id||``;r.set(t,[...r.get(t)||[],e])}let i=e=>{for(let t of r.get(e)||[])n.push(t.id),i(t.id)};return i(e),n}function mt(e,t,n,r,i){let a=[],o=new Map,s=[],c=[];for(let t of e){let e=t.memberships.find(e=>e.cluster.id===n);if(!e){c.push(t);continue}let r=e.node.node_group_id;if(!r){s.push(t);continue}o.set(r,[...o.get(r)||[],t])}let l=new Map;for(let e of t){let t=e.parent_group_id||``;l.set(t,[...l.get(t)||[],e])}for(let e of l.values())e.sort((e,t)=>e.sort_order-t.sort_order||e.name.localeCompare(t.name));let u=new Map,d=e=>{let t=u.get(e.id);if(t!=null)return t;let n=o.get(e.id)?.length||0;for(let t of l.get(e.id)||[])n+=d(t);return u.set(e.id,n),n},f=(e,t)=>{let n=[...o.get(e.id)||[]].sort((e,t)=>e.node.name.localeCompare(t.node.name)),r=l.get(e.id)||[],s=`group-${e.id}`,c=d(e);if(a.push({kind:`group`,key:s,label:e.name,depth:t,count:c,groupId:e.id}),!i.has(s)){for(let r of n)a.push({kind:`node`,key:`node-${e.id}-${r.node.id}`,entry:r,depth:t+1});for(let e of r)f(e,t+1)}return c};for(let e of l.get(``)||[])f(e,0);if(s.length>0){let e=`group-ungrouped`;if(a.push({kind:`group`,key:e,label:r.ungroupedNodes,depth:0,count:s.length}),!i.has(e))for(let e of s.sort((e,t)=>e.node.name.localeCompare(t.node.name)))a.push({kind:`node`,key:`node-ungrouped-${e.node.id}`,entry:e,depth:1})}if(c.length>0){let e=`group-outside-active-cluster`;if(a.push({kind:`group`,key:e,label:r.notMemberOfActiveCluster,depth:0,count:c.length}),!i.has(e))for(let e of c.sort((e,t)=>e.node.name.localeCompare(t.node.name)))a.push({kind:`node`,key:`node-outside-${e.node.id}`,entry:e,depth:1})}return a}function ht(e,t){return Object.entries(t).filter(([,t])=>t.some(t=>t.role===e&&t.status===`active`)).map(([e])=>e)}function gt(e){let t={roles:e.roles,node_name:e.nodeName.trim()||null,node_group_id:e.nodeGroupId||null,ownership_type:e.ownershipType,purpose:e.purpose.trim()||null,approval:{mode:`manual`,auto_approve:!1,role_assignment:`manual_after_approval`},source:`platform_owner_console`};if(e.installMode===`docker`){let n=(e.controlPlaneEndpoint||bt()).trim().replace(/\/$/,``);t.install_profile=`docker`,t.backend_url=n,t.control_plane_endpoints=[n],t.image=e.dockerImage||`rap-node-agent:latest`,e.dockerContainerName.trim()&&(t.container_name=e.dockerContainerName.trim()),t.artifact_endpoints=St(e.artifactEndpoints||xt()),e.dockerImageArtifactSHA256.trim()&&(t.docker_image_artifact_sha256=e.dockerImageArtifactSHA256.trim()),t.network=e.dockerNetwork||`host`,t.restart_policy=`unless-stopped`,t.pull_image=!!e.pullImage,t.replace=e.replace!==!1,t.mesh_synthetic_runtime_enabled=e.syntheticRuntime!==!1,t.mesh_production_forwarding_enabled=!1,t.mesh_listen_addr=e.meshListenAddr||`:19131`,t.mesh_listen_port_mode=e.meshListenPortMode||`auto`,t.mesh_listen_auto_port_start=e.meshListenAutoPortStart||19131,t.mesh_listen_auto_port_end=e.meshListenAutoPortEnd||19231,e.meshAdvertiseEndpoint?.trim()&&(t.mesh_advertise_endpoint=e.meshAdvertiseEndpoint.trim().replace(/\/$/,``)),t.mesh_advertise_transport=e.meshAdvertiseTransport||`direct_http`,t.mesh_connectivity_mode=e.meshConnectivityMode||`private_lan`,t.mesh_nat_type=e.meshNATType||`unknown`,t.mesh_region=e.meshRegion||null}if(e.installMode===`windows_service`){let n=(e.controlPlaneEndpoint||bt()).trim().replace(/\/$/,``);t.install_profile=`windows_service`,t.backend_url=n,t.control_plane_endpoints=[n],t.artifact_endpoints=St(e.artifactEndpoints||xt()),t.startup_mode=e.windowsStartupMode||`auto`,e.windowsInstallDir.trim()&&(t.install_dir=e.windowsInstallDir.trim()),e.windowsNodeAgentSHA256.trim()&&(t.node_agent_artifact_sha256=e.windowsNodeAgentSHA256.trim()),t.mesh_synthetic_runtime_enabled=e.syntheticRuntime!==!1,t.mesh_production_forwarding_enabled=!1,t.mesh_listen_addr=e.meshListenAddr||`:19131`,t.mesh_listen_port_mode=e.meshListenPortMode||`auto`,t.mesh_listen_auto_port_start=e.meshListenAutoPortStart||19131,t.mesh_listen_auto_port_end=e.meshListenAutoPortEnd||19231,e.meshAdvertiseEndpoint?.trim()&&(t.mesh_advertise_endpoint=e.meshAdvertiseEndpoint.trim().replace(/\/$/,``)),t.mesh_advertise_transport=e.meshAdvertiseTransport||`direct_http`,t.mesh_connectivity_mode=e.meshConnectivityMode||`outbound_only`,t.mesh_nat_type=e.meshNATType||`unknown`,t.mesh_region=e.meshRegion||`windows`}if(e.installMode===`linux_binary`){let n=(e.controlPlaneEndpoint||bt()).trim().replace(/\/$/,``);t.install_profile=`linux_binary`,t.backend_url=n,t.control_plane_endpoints=[n],t.artifact_endpoints=St(e.artifactEndpoints||xt()),t.startup_mode=`systemd`,e.linuxInstallDir.trim()&&(t.install_dir=e.linuxInstallDir.trim()),e.linuxNodeAgentSHA256.trim()&&(t.node_agent_artifact_sha256=e.linuxNodeAgentSHA256.trim()),t.replace=e.replace!==!1,t.mesh_synthetic_runtime_enabled=e.syntheticRuntime!==!1,t.mesh_production_forwarding_enabled=!1,t.mesh_listen_addr=e.meshListenAddr||`:19131`,t.mesh_listen_port_mode=e.meshListenPortMode||`auto`,t.mesh_listen_auto_port_start=e.meshListenAutoPortStart||19131,t.mesh_listen_auto_port_end=e.meshListenAutoPortEnd||19231,e.meshAdvertiseEndpoint?.trim()&&(t.mesh_advertise_endpoint=e.meshAdvertiseEndpoint.trim().replace(/\/$/,``)),t.mesh_advertise_transport=e.meshAdvertiseTransport||`direct_http`,t.mesh_connectivity_mode=e.meshConnectivityMode||`outbound_only`,t.mesh_nat_type=e.meshNATType||`unknown`,t.mesh_region=e.meshRegion||`linux`}return t}function _t(e,t){let n=vt(e.roles),r=vt(e.artifact_endpoints).join(`, `);return{...t,roles:n.length>0?n:t.roles,nodeName:M(e,`node_name`,``)||t.nodeName,nodeGroupId:M(e,`node_group_id`,``)||t.nodeGroupId,ownershipType:M(e,`ownership_type`,t.ownershipType),purpose:M(e,`purpose`,``)||t.purpose,installMode:M(e,`install_profile`,t.installMode),dockerImage:M(e,`image`,t.dockerImage),dockerContainerName:M(e,`container_name`,``)||t.dockerContainerName,dockerNetwork:M(e,`network`,t.dockerNetwork),windowsStartupMode:M(e,`startup_mode`,t.windowsStartupMode),windowsInstallDir:M(e,`install_dir`,``)||t.windowsInstallDir,windowsNodeAgentSHA256:M(e,`node_agent_artifact_sha256`,``)||t.windowsNodeAgentSHA256,linuxInstallDir:M(e,`install_dir`,``)||t.linuxInstallDir,linuxNodeAgentSHA256:M(e,`node_agent_artifact_sha256`,``)||t.linuxNodeAgentSHA256,meshListenAddr:M(e,`mesh_listen_addr`,t.meshListenAddr),meshListenPortMode:M(e,`mesh_listen_port_mode`,t.meshListenPortMode),meshListenAutoPortStart:it(e,`mesh_listen_auto_port_start`,t.meshListenAutoPortStart),meshListenAutoPortEnd:it(e,`mesh_listen_auto_port_end`,t.meshListenAutoPortEnd),meshAdvertiseEndpoint:M(e,`mesh_advertise_endpoint`,``)||t.meshAdvertiseEndpoint,meshAdvertiseTransport:M(e,`mesh_advertise_transport`,t.meshAdvertiseTransport),meshConnectivityMode:M(e,`mesh_connectivity_mode`,t.meshConnectivityMode),meshNATType:M(e,`mesh_nat_type`,t.meshNATType),meshRegion:M(e,`mesh_region`,``)||t.meshRegion,controlPlaneEndpoint:vt(e.control_plane_endpoints)[0]||M(e,`backend_url`,``)||t.controlPlaneEndpoint,artifactEndpoints:r||t.artifactEndpoints,dockerImageArtifactSHA256:M(e,`docker_image_artifact_sha256`,``)||t.dockerImageArtifactSHA256,pullImage:yt(e,`pull_image`,t.pullImage),replace:yt(e,`replace`,t.replace),syntheticRuntime:yt(e,`mesh_synthetic_runtime_enabled`,t.syntheticRuntime)}}function vt(e){return Array.isArray(e)?e.filter(e=>typeof e==`string`).map(e=>e.trim()).filter(Boolean):[]}function yt(e,t,n){let r=e[t];return typeof r==`boolean`?r:n}function bt(){return typeof window>`u`||!window.location?.origin?`http://:18080/api/v1`:`${window.location.origin.replace(/\/$/,``)}/api/v1`}function xt(){return typeof window>`u`||!window.location?.origin?`http://:18080/downloads`:`${window.location.origin.replace(/\/$/,``)}/downloads`}function St(e){return e.split(`,`).map(e=>e.trim().replace(/\/$/,``)).filter(Boolean)}function Ct(e){return St(e.artifactEndpoints||xt()).map(e=>`${e}/rap-node-agent-dev-enrollment-bootstrap-smoke.tar`)}function wt(e){return e.meshConnectivityMode===`outbound_only`?`outbound_only`:e.meshConnectivityMode===`private_lan`?`private_lan`:e.meshNATType!==`none`&&e.meshAdvertiseEndpoint.trim()?`nat_forward`:`direct`}function Tt(e,t){let n={...e};return t===`private_lan`?(n.meshConnectivityMode=`private_lan`,n.meshNATType=`none`):t===`direct`?(n.meshConnectivityMode=`direct`,n.meshNATType=`none`):t===`nat_forward`?(n.meshConnectivityMode=`direct`,n.meshNATType=`port_restricted`):(n.meshConnectivityMode=`outbound_only`,n.meshNATType=`symmetric`,n.meshAdvertiseEndpoint=``),n}function Et(e,t){return e.nodeName.trim()?e.nodeName.trim():`${Jt(t?.slug||t?.name||`rap-node`)}-node-1`}function Dt(e,t){return e.dockerContainerName.trim()?e.dockerContainerName.trim():`rap-node-agent-${Jt(Et(e,t))}`}function Ot(e,t,n=se){let r=t?.id||e.cluster_id,i=Et(n,t),a=Dt(n,t),o=Jt(i),s=[`rap-host-agent install`,`--backend-url ${N(Ut(n))}`,`--cluster-id ${N(r)}`,`--join-token ${N(e.token)}`,`--node-name ${N(i)}`,`--image ${N(n.dockerImage||`rap-node-agent:latest`)}`,`--container-name ${N(a)}`,`--state-dir ${N(`/var/lib/rap/nodes/${o}`)}`,`--network host`,`--replace`];for(let e of Ct(n))s.push(`--image-artifact-url ${N(e)}`);return n.dockerImageArtifactSHA256.trim()&&s.push(`--image-artifact-sha256 ${N(n.dockerImageArtifactSHA256.trim())}`),s.join(` \\ - `)}function kt(e,t,n=se){let r=t?.id||e.cluster_id,i=Et(n,t),a=[`sudo "$rap_host_agent" install`,`--profile-url ${N(Ut(n))}`,`--cluster-id ${N(r)}`,`--install-token ${N(e.token)}`,`--node-name ${N(i)}`].join(` \\ - `);return[`rap_host_agent="$(mktemp /tmp/rap-host-agent.XXXXXX)"`,`curl -fL --retry 3 --retry-delay 1 ${N(Kt(n))} -o "$rap_host_agent"`,`chmod +x "$rap_host_agent"`,a].join(` && \\ - `)}function At(e,t,n=se){let r=t?.id||e.cluster_id,i=Et(n,t),a=[`sudo "$rap_host_agent" install-linux`,`--profile-url ${N(Ut(n))}`,`--cluster-id ${N(r)}`,`--install-token ${N(e.token)}`,`--node-name ${N(i)}`].join(` \\ - `);return[`rap_host_agent="$(mktemp /tmp/rap-host-agent.XXXXXX)"`,`curl -fL --retry 3 --retry-delay 1 ${N(Kt(n))} -o "$rap_host_agent"`,`chmod +x "$rap_host_agent"`,a].join(` && \\ - `)}function jt(e,t,n=se){let r=t?.id||e.cluster_id,i=Et(n,t),a=Ut(n);return[`$rapHostAgent = Join-Path $env:TEMP "rap-host-agent.exe"`,`Invoke-WebRequest -UseBasicParsing ${Yt(qt(n))} -OutFile $rapHostAgent`,`& $rapHostAgent install-windows --profile-url ${Yt(a)} --cluster-id ${Yt(r)} --install-token ${Yt(e.token)} --node-name ${Yt(i)} --startup-mode ${Yt(n.windowsStartupMode||`auto`)}`].join(`\r -`)}function Mt(e,t,n=se){let r=t?.id||e.cluster_id,i=Et(n,t),a=Ut(n),o=qt(n),s=n.windowsStartupMode||`auto`;return[`powershell -NoProfile -ExecutionPolicy Bypass -Command "Invoke-WebRequest -UseBasicParsing '${o}' -OutFile $env:TEMP\\rap-host-agent.exe"`,`%TEMP%\\rap-host-agent.exe install-windows --profile-url "${a}" --cluster-id "${r}" --install-token "${e.token}" --node-name "${i}" --startup-mode "${s}"`].join(`\r -`)}function Nt(e,t){let n=Wt(),r=n.replace(/\/api\/v1$/i,``).replace(/\/api$/i,``).replace(/\/$/,``),i=e.name||e.node_key||e.id,a=Bt(i),o=`%ProgramFiles%\\RAP\\${a}`,s=`%ProgramData%\\RAP\\nodes\\${a}`,c=`RAP Node Agent ${a}`,l=`RAP Host Agent Updater ${a}`,u=`${o}\\rap-host-agent.exe`,d=`${u}.next`;return[`@echo off`,`echo === RAP Windows updater repair: ${Xt(i)} ===`,`echo Node ID: ${e.id}`,`echo Control Plane: ${n}`,`echo.`,`echo === Before repair: scheduled tasks ===`,`schtasks /Query /TN "${c}" /V /FO LIST`,`schtasks /Query /TN "${l}" /V /FO LIST`,`echo.`,`echo === Before repair: binaries ===`,`dir "${o}\\rap-*.exe*"`,`echo.`,`powershell -NoProfile -ExecutionPolicy Bypass -Command "Invoke-WebRequest -UseBasicParsing '${r}/downloads/rap-host-agent-windows-amd64.exe' -OutFile $env:TEMP\\rap-host-agent.exe"`,`%TEMP%\\rap-host-agent.exe install-windows --backend-url "${n}" --cluster-id "${t||``}" --node-id "${e.id}" --node-name "${Xt(i)}" --replace --startup-mode "auto" --auto-update-current-version "0.0.0" --auto-update-initial-delay-seconds 1`,`"${u}" update-loop --backend-url "${n}" --cluster-id "${t||``}" --node-id "${e.id}" --state-dir "${s}" --current-version "0.0.0" --os windows --arch amd64 --install-type windows_service --binary-path "${o}\\rap-node-agent.exe" --windows-task-name "${c}" --health-timeout-seconds 30 --interval-seconds 0 --initial-delay-seconds 0 --max-runs 1 --host-agent-update-status-enabled --host-agent-current-version "0.0.0" --host-agent-binary-path "${u}"`,`echo.`,`echo === Applying staged host-agent if present ===`,`if exist "${d}" copy /Y "${d}" "${u}"`,`if exist "${d}" del /F /Q "${d}"`,`schtasks /End /TN "${l}"`,`schtasks /Run /TN "${l}"`,`echo.`,`echo === After repair: binaries ===`,`dir "${o}\\rap-*.exe*"`,`echo.`,`echo === After repair: updater task ===`,`schtasks /Query /TN "${l}" /V /FO LIST`,`echo.`,`echo Repair command finished. Check the admin panel for rap-node-agent and rap-host-agent plan/noop reports.`].join(`\r -`)}function Pt(e){return`rap-repair-updater-${zt(e.name||e.node_key||e.id||`node`)}.cmd`}function Ft(e,t){let n=Wt(),r=n.replace(/\/api\/v1$/i,``).replace(/\/api$/i,``).replace(/\/$/,``),i=e.name||e.node_key||e.id,a=Vt(i),o=`/opt/rap/${a}`,s=`/var/lib/rap/nodes/${a}`,c=`rap-node-agent-${a}.service`,l=`rap-host-agent-updater-${a}.service`,u=`${o}/rap-host-agent`;return[`#!/usr/bin/env bash`,`set -euo pipefail`,`echo "=== RAP Linux updater repair: ${Ht(i)} ==="`,`echo "Node ID: ${e.id}"`,`echo "Control Plane: ${n}"`,`echo`,`echo "=== Before repair: systemd units ==="`,`systemctl status ${N(c)} --no-pager || true`,`systemctl status ${N(l)} --no-pager || true`,`echo`,`echo "=== Before repair: binaries ==="`,`ls -la ${N(o)} || true`,`echo`,`rap_host_agent="$(mktemp /tmp/rap-host-agent.XXXXXX)"`,`curl -fL --retry 3 --retry-delay 1 ${N(`${r}/downloads/rap-host-agent-linux-amd64`)} -o "$rap_host_agent"`,`chmod +x "$rap_host_agent"`,`sudo "$rap_host_agent" install-linux --backend-url ${N(n)} --cluster-id ${N(t||``)} --node-id ${N(e.id)} --node-name ${N(i)} --replace --startup-mode systemd --auto-update-current-version 0.0.0 --auto-update-initial-delay-seconds 1`,`sudo ${N(u)} update-loop --backend-url ${N(n)} --cluster-id ${N(t||``)} --node-id ${N(e.id)} --state-dir ${N(s)} --current-version 0.0.0 --os linux --arch amd64 --install-type linux_binary --binary-path ${N(`${o}/rap-node-agent`)} --systemd-unit ${N(c)} --health-timeout-seconds 30 --interval-seconds 0 --initial-delay-seconds 0 --max-runs 1 --host-agent-update-status-enabled --host-agent-current-version 0.0.0 --host-agent-binary-path ${N(u)}`,`sudo systemctl daemon-reload`,`sudo systemctl restart ${N(l)}`,`echo`,`echo "=== After repair: systemd updater ==="`,`systemctl status ${N(l)} --no-pager || true`,`echo "Repair command finished. Check the admin panel for rap-node-agent and rap-host-agent plan/noop reports."`].join(` -`)}function It(e){return`rap-repair-updater-${zt(e.name||e.node_key||e.id||`node`)}.sh`}function Lt(e,t){if(typeof document>`u`)return;let n=new Blob([t.endsWith(`\r -`)?t:`${t}\r\n`],{type:`text/plain;charset=utf-8`}),r=URL.createObjectURL(n),i=document.createElement(`a`);i.href=r,i.download=e,document.body.appendChild(i),i.click(),i.remove(),URL.revokeObjectURL(r)}async function Rt(e){await navigator.clipboard.writeText(e)}function zt(e){return e.trim().replace(/[\\/:*?"<>|]+/g,`-`).replace(/\s+/g,`-`).replace(/-+/g,`-`).replace(/^-|-$/g,``).slice(0,80)||`node`}function Bt(e){return e.trim().replace(/[\\/:*?"<>|]+/g,`-`).replace(/\s+/g,`-`).replace(/-+/g,`-`).replace(/^-|-$/g,``)||`node`}function Vt(e){return Bt(e).slice(0,48)||`node`}function Ht(e){return e.replace(/\\/g,`\\\\`).replace(/"/g,`\\"`).replace(/\$/g,`\\$`).replace(/`/g,"\\`")}function Ut(e=se){return(e.controlPlaneEndpoint||bt()).trim().replace(/\/$/,``)}function Wt(){let e=typeof window>`u`?``:window.location?.origin||``;return/^(http:\/\/)?(192\.168\.200\.61|docker-test\.cin\.su)(:18080)?$/i.test(e.replace(/\/$/,``))?`http://vpn.cin.su:19191/api/v1`:`${e.replace(/\/$/,``)}/api/v1`}function Gt(e=se){let t=St(e.artifactEndpoints)[0];return t?t.replace(/\/downloads$/i,``).replace(/\/$/,``):Ut(e).replace(/\/api\/v1$/i,``).replace(/\/api$/i,``).replace(/\/$/,``)}function Kt(e=se){return`${typeof window>`u`&&!e.controlPlaneEndpoint?`http://:18080`:Gt(e)}/downloads/rap-host-agent-linux-amd64`}function qt(e=se){return`${typeof window>`u`&&!e.controlPlaneEndpoint?`http://:18080`:Gt(e)}/downloads/rap-host-agent-windows-amd64.exe`}function Jt(e){return e.trim().toLowerCase().replace(/[^a-z0-9-]+/g,`-`).replace(/^-+|-+$/g,``).slice(0,42)||`rap-node`}function N(e){return`'${e.replace(/'/g,`'\\''`)}'`}function Yt(e){return`'${e.replace(/'/g,`''`)}'`}function Xt(e){return e.replace(/"/g,`""`)}function Zt(e,t){return e.includes(t)?e.filter(e=>e!==t):[...e,t]}function Qt(e,t,n,r,i){let a=n.trim().toLowerCase(),o=new Map;for(let n of e){if(a&&!$t(n,a))continue;let e=en(n,t,r,i);o.set(e,[...o.get(e)||[],n])}return Array.from(o.entries()).map(([e,t])=>({label:e,items:t.sort((e,t)=>e.node.name.localeCompare(t.node.name))})).sort((e,t)=>e.label.localeCompare(t.label))}function $t(e,t){return[e.node.name,e.node.node_key,e.node.health_status,e.node.ownership_type,e.node.reported_version||``,...e.memberships.flatMap(e=>[e.cluster.name,e.cluster.slug,e.node.membership_status])].some(e=>e.toLowerCase().includes(t))}function en(e,t,n,r){if(n===`health`)return I(e.node.health_status);if(n===`ownership`)return I(e.node.ownership_type);if(n===`cluster_count`)return hn(e.memberships.length,r);let i=e.memberships.find(e=>e.cluster.id===t);return i?i.node.membership_status===`active`?r===`en`?`In active cluster`:`В активном кластере`:`${r===`en`?`Membership`:`Участие`}: ${I(i.node.membership_status)}`:r===`en`?`Not in active cluster`:`Не в активном кластере`}function tn(e,t){let n=re[e]||[];if(n.length===0||!t)return`unknown`;if(nn(t))return`stale`;let r=t.capabilities||{};return n.some(e=>!!r[e])?`confirmed`:`missing`}function nn(e){if(!e?.observed_at)return!0;let t=new Date(e.observed_at).getTime();return!Number.isFinite(t)||Date.now()-t>60*1e3}function rn(e,t){let n=tn(e,t);return n===`confirmed`?`good`:n===`missing`?`bad`:n===`stale`?`warn`:``}function an(e,t,n){let r=tn(e,t);return r===`confirmed`?n.capabilityConfirmed:r===`missing`?n.capabilityMissing:r===`stale`?`heartbeat устарел`:n.capabilityUnknown}function on(e,t,n){let r=tn(e,t);return n===`en`?r===`confirmed`?`capable`:r===`missing`?`not reported`:r===`stale`?`stale heartbeat`:`unknown`:r===`confirmed`?`подходит`:r===`missing`?`не заявлено`:r===`stale`?`heartbeat устарел`:`неизвестно`}function sn(e){let t=e?.metadata?.mesh_peer_recovery_report;if(!t||typeof t!=`object`||Array.isArray(t))return`н/д`;let n=t,r=typeof n.mode==`string`?n.mode:`unknown`,i=typeof n.ready_peer_count==`number`?n.ready_peer_count:null,a=typeof n.target_ready_peers==`number`?n.target_ready_peers:null,o=typeof n.deficit==`number`?n.deficit:0,s=i==null||a==null?r:`${r} ${i}/${a}`;return o>0?`${s} deficit ${o}`:s}function cn(e){let t=e?.metadata?.mesh_peer_connection_intent_report;if(!t||typeof t!=`object`||Array.isArray(t))return pn(e);let n=t,r=typeof n.intent_count==`number`?n.intent_count:0,i=typeof n.maintain_count==`number`?n.maintain_count:0,a=typeof n.recover_count==`number`?n.recover_count:0,o=typeof n.rendezvous_required_count==`number`?n.rendezvous_required_count:0,s=typeof n.rendezvous_resolved_count==`number`?n.rendezvous_resolved_count:0,c=typeof n.relay_control_count==`number`?n.relay_control_count:0,l=[`rv${o}`];s>0&&l.push(`ok${s}`),c>0&&l.push(`relay${c}`);let u=o>0||s>0||c>0?`${r} intents m${i}/r${a} ${l.join(`/`)}`:`${r} intents m${i}/r${a}`,d=pn(e);return d===`н/д`?u:`${u}; ${d}`}function ln(e){let t=e?.metadata?.mesh_rendezvous_lease_report;if(!t||typeof t!=`object`||Array.isArray(t))return`н/д`;let n=t,r=typeof n.lease_count==`number`?n.lease_count:0,i=typeof n.active_count==`number`?n.active_count:0,a=typeof n.admitted_as_relay_count==`number`?n.admitted_as_relay_count:0,o=typeof n.admitted_as_peer_count==`number`?n.admitted_as_peer_count:0,s=typeof n.renewal_needed_count==`number`?n.renewal_needed_count:0,c=typeof n.relay_control_ready_count==`number`?n.relay_control_ready_count:0,l=typeof n.stale_relay_count==`number`?n.stale_relay_count:0,u=typeof n.refresh_attempt_count==`number`?n.refresh_attempt_count:0,d=typeof n.refresh_success_count==`number`?n.refresh_success_count:0,f=[`lease ${i}/${r}`];return a>0&&f.push(`relay${a}`),o>0&&f.push(`peer${o}`),s>0&&f.push(`renew${s}`),l>0&&f.push(`stale${l}`),c>0&&f.push(`ready${c}`),u>0&&f.push(`ref${d}/${u}`),f.join(` `)}function un(e){let t=e?.metadata?.mesh_route_path_decision_report;if(!t||typeof t!=`object`||Array.isArray(t))return`н/д`;let n=t,r=typeof n.decision_count==`number`?n.decision_count:0,i=typeof n.replacement_decision_count==`number`?n.replacement_decision_count:0,a=typeof n.local_effective_path_count==`number`?n.local_effective_path_count:0,o=typeof n.next_hop_available_count==`number`?n.next_hop_available_count:0,s=typeof n.withdrawn_local_relay_count==`number`?n.withdrawn_local_relay_count:0,c=[`path ${a}/${r}`];return i>0&&c.push(`repl${i}`),o>0&&c.push(`next${o}`),s>0&&c.push(`wd${s}`),c.join(` `)}function dn(e){let t=e?.metadata?.mesh_route_generation_report;if(!t||typeof t!=`object`||Array.isArray(t))return`н/д`;let n=t,r=typeof n.active_decision_count==`number`?n.active_decision_count:0,i=typeof n.applied_decision_count==`number`?n.applied_decision_count:0,a=typeof n.withdrawn_decision_count==`number`?n.withdrawn_decision_count:0,o=n.generation_changed===!0,s=[`gen ${r}`];return i>0&&s.push(`ap${i}`),a>0&&s.push(`wd${a}`),o&&s.push(`chg`),s.join(` `)}function fn(e){let t=e?.metadata?.mesh_route_health_config_report;if(!t||typeof t!=`object`||Array.isArray(t))return`н/д`;let n=t,r=e?.metadata?.mesh_route_health_feedback_refresh_report,i=r&&typeof r==`object`&&!Array.isArray(r)?r:{},a=typeof n.route_health_route_count==`number`?n.route_health_route_count:0,o=typeof n.route_path_decision_applied_count==`number`?n.route_path_decision_applied_count:0,s=typeof n.replacement_route_health_route_count==`number`?n.replacement_route_health_route_count:0,c=typeof n.route_health_decision_drift_candidate_count==`number`?n.route_health_decision_drift_candidate_count:0,l=typeof i.feedback_refresh_attempt_count==`number`?i.feedback_refresh_attempt_count:typeof n.feedback_refresh_attempt_count==`number`?n.feedback_refresh_attempt_count:0,u=typeof i.feedback_refresh_success_count==`number`?i.feedback_refresh_success_count:typeof n.feedback_refresh_success_count==`number`?n.feedback_refresh_success_count:0,d=typeof i.feedback_refresh_suppressed_count==`number`?i.feedback_refresh_suppressed_count:typeof n.feedback_refresh_suppressed_count==`number`?n.feedback_refresh_suppressed_count:0,f=[`rh ${o}/${a}`];return s>0&&f.push(`repl${s}`),c>0&&f.push(`drift${c}`),(l>0||d>0)&&f.push(`fb${u}/${l}`),d>0&&f.push(`sup${d}`),f.join(` `)}function pn(e){let t=e?.metadata?.mesh_peer_connection_manager_report;if(!t||typeof t!=`object`||Array.isArray(t))return`н/д`;let n=t;if(n.enabled===!1)return`manager off`;let r=typeof n.attempted==`number`?n.attempted:0,i=typeof n.succeeded==`number`?n.succeeded:0,a=typeof n.deferred==`number`?n.deferred:0,o=typeof n.relay_control_count==`number`?n.relay_control_count:0,s=o>0?`mgr ${i}/${r} relay${o}`:`mgr ${i}/${r}`;return a>0?`${s} def${a}`:s}function mn(e){let t=e?.metadata?.mesh_listener_report;if(!t||typeof t!=`object`||Array.isArray(t))return`н/д`;let n=t,r=typeof n.status==`string`?n.status:`unknown`,i=typeof n.listen_port_mode==`string`?n.listen_port_mode:`manual`,a=typeof n.effective_listen_addr==`string`?n.effective_listen_addr:``,o=typeof n.failure_reason==`string`?n.failure_reason:``;return r===`listening`?a?`listen ${a}`:`listen`:r===`auto_rebound`?a?`auto ${a}`:`auto rebound`:r===`listen_failed`?o?`${i} failed: ${o}`:`${i} failed`:r===`disabled`?i===`disabled`?`inbound off`:`inbound unavailable`:r}function hn(e,t){if(t===`en`)return e===1?`1 cluster`:`${e} clusters`;let n=e%10,r=e%100;return n===1&&r!==11?`${e} кластер`:n>=2&&n<=4&&(r<12||r>14)?`${e} кластера`:`${e} кластеров`}function gn(e,t=e.link_status===`reachable`?`reachable`:`unknown`){return t===`stale`?`stale`:t===`one_way`?`oneWay`:t!==`reachable`||e.link_status!==`reachable`?`bad`:e.quality_score!=null&&e.quality_score<70||e.latency_ms!=null&&e.latency_ms>80?`weak`:`good`}function _n(e,t=16){return e.length>t?`${e.slice(0,Math.max(1,t-2))}…`:e}function vn(e){return window.confirm(`${e}?\n\nЭто высокорисковая операция владельца платформы. Действие будет записано в аудит.`)}function yn(e){let t=(e||``).replace(/\/$/,``);return!t||t===`/api/v1`?window.location.origin:t.endsWith(`/api/v1`)?t.slice(0,-7):t}function P(e){return e?e.length>12?`${e.slice(0,8)}...${e.slice(-4)}`:e:`нет`}function F(e){return e?new Intl.DateTimeFormat(void 0,{dateStyle:`medium`,timeStyle:`short`}).format(new Date(e)):`никогда`}function bn(e){return e?new Intl.DateTimeFormat(void 0,{hour:`2-digit`,minute:`2-digit`,second:`2-digit`}).format(new Date(e)):`н/д`}function xn(e){if(e==null||Number.isNaN(e))return`н/д`;let t=[`B`,`KB`,`MB`,`GB`,`TB`],n=e,r=0;for(;n>=1024&&r()=>(t||(e((t={exports:{}}).exports,t),e=null),t.exports),s=(e,i,o,s)=>{if(i&&typeof i==`object`||typeof i==`function`)for(var c=r(i),l=0,u=c.length,d;li[e]).bind(null,d),enumerable:!(s=n(i,d))||s.enumerable});return e},c=(n,r,a)=>(a=n==null?{}:e(i(n)),s(r||!n||!n.__esModule?t(a,`default`,{value:n,enumerable:!0}):a,n));(function(){let e=document.createElement(`link`).relList;if(e&&e.supports&&e.supports(`modulepreload`))return;for(let e of document.querySelectorAll(`link[rel="modulepreload"]`))n(e);new MutationObserver(e=>{for(let t of e)if(t.type===`childList`)for(let e of t.addedNodes)e.tagName===`LINK`&&e.rel===`modulepreload`&&n(e)}).observe(document,{childList:!0,subtree:!0});function t(e){let t={};return e.integrity&&(t.integrity=e.integrity),e.referrerPolicy&&(t.referrerPolicy=e.referrerPolicy),e.crossOrigin===`use-credentials`?t.credentials=`include`:e.crossOrigin===`anonymous`?t.credentials=`omit`:t.credentials=`same-origin`,t}function n(e){if(e.ep)return;e.ep=!0;let n=t(e);fetch(e.href,n)}})();var l=o((e=>{var t=Symbol.for(`react.transitional.element`),n=Symbol.for(`react.portal`),r=Symbol.for(`react.fragment`),i=Symbol.for(`react.strict_mode`),a=Symbol.for(`react.profiler`),o=Symbol.for(`react.consumer`),s=Symbol.for(`react.context`),c=Symbol.for(`react.forward_ref`),l=Symbol.for(`react.suspense`),u=Symbol.for(`react.memo`),d=Symbol.for(`react.lazy`),f=Symbol.for(`react.activity`),p=Symbol.iterator;function m(e){return typeof e!=`object`||!e?null:(e=p&&e[p]||e[`@@iterator`],typeof e==`function`?e:null)}var h={isMounted:function(){return!1},enqueueForceUpdate:function(){},enqueueReplaceState:function(){},enqueueSetState:function(){}},g=Object.assign,_={};function v(e,t,n){this.props=e,this.context=t,this.refs=_,this.updater=n||h}v.prototype.isReactComponent={},v.prototype.setState=function(e,t){if(typeof e!=`object`&&typeof e!=`function`&&e!=null)throw Error(`takes an object of state variables to update or a function which returns an object of state variables.`);this.updater.enqueueSetState(this,e,t,`setState`)},v.prototype.forceUpdate=function(e){this.updater.enqueueForceUpdate(this,e,`forceUpdate`)};function y(){}y.prototype=v.prototype;function b(e,t,n){this.props=e,this.context=t,this.refs=_,this.updater=n||h}var x=b.prototype=new y;x.constructor=b,g(x,v.prototype),x.isPureReactComponent=!0;var ee=Array.isArray;function S(){}var C={H:null,A:null,T:null,S:null},te=Object.prototype.hasOwnProperty;function ne(e,n,r){var i=r.ref;return{$$typeof:t,type:e,key:n,ref:i===void 0?null:i,props:r}}function re(e,t){return ne(e.type,t,e.props)}function ie(e){return typeof e==`object`&&!!e&&e.$$typeof===t}function w(e){var t={"=":`=0`,":":`=2`};return`$`+e.replace(/[=:]/g,function(e){return t[e]})}var ae=/\/+/g;function oe(e,t){return typeof e==`object`&&e&&e.key!=null?w(``+e.key):t.toString(36)}function T(e){switch(e.status){case`fulfilled`:return e.value;case`rejected`:throw e.reason;default:switch(typeof e.status==`string`?e.then(S,S):(e.status=`pending`,e.then(function(t){e.status===`pending`&&(e.status=`fulfilled`,e.value=t)},function(t){e.status===`pending`&&(e.status=`rejected`,e.reason=t)})),e.status){case`fulfilled`:return e.value;case`rejected`:throw e.reason}}throw e}function se(e,r,i,a,o){var s=typeof e;(s===`undefined`||s===`boolean`)&&(e=null);var c=!1;if(e===null)c=!0;else switch(s){case`bigint`:case`string`:case`number`:c=!0;break;case`object`:switch(e.$$typeof){case t:case n:c=!0;break;case d:return c=e._init,se(c(e._payload),r,i,a,o)}}if(c)return o=o(e),c=a===``?`.`+oe(e,0):a,ee(o)?(i=``,c!=null&&(i=c.replace(ae,`$&/`)+`/`),se(o,r,i,``,function(e){return e})):o!=null&&(ie(o)&&(o=re(o,i+(o.key==null||e&&e.key===o.key?``:(``+o.key).replace(ae,`$&/`)+`/`)+c)),r.push(o)),1;c=0;var l=a===``?`.`:a+`:`;if(ee(e))for(var u=0;u{t.exports=l()})),d=o((e=>{function t(e,t){var n=e.length;e.push(t);a:for(;0>>1,a=e[r];if(0>>1;ri(c,n))li(u,c)?(e[r]=u,e[l]=n,r=l):(e[r]=c,e[s]=n,r=s);else if(li(u,n))e[r]=u,e[l]=n,r=l;else break a}}return t}function i(e,t){var n=e.sortIndex-t.sortIndex;return n===0?e.id-t.id:n}if(e.unstable_now=void 0,typeof performance==`object`&&typeof performance.now==`function`){var a=performance;e.unstable_now=function(){return a.now()}}else{var o=Date,s=o.now();e.unstable_now=function(){return o.now()-s}}var c=[],l=[],u=1,d=null,f=3,p=!1,m=!1,h=!1,g=!1,_=typeof setTimeout==`function`?setTimeout:null,v=typeof clearTimeout==`function`?clearTimeout:null,y=typeof setImmediate<`u`?setImmediate:null;function b(e){for(var i=n(l);i!==null;){if(i.callback===null)r(l);else if(i.startTime<=e)r(l),i.sortIndex=i.expirationTime,t(c,i);else break;i=n(l)}}function x(e){if(h=!1,b(e),!m)if(n(c)!==null)m=!0,ee||(ee=!0,ie());else{var t=n(l);t!==null&&oe(x,t.startTime-e)}}var ee=!1,S=-1,C=5,te=-1;function ne(){return g?!0:!(e.unstable_now()-tet&&ne());){var o=d.callback;if(typeof o==`function`){d.callback=null,f=d.priorityLevel;var s=o(d.expirationTime<=t);if(t=e.unstable_now(),typeof s==`function`){d.callback=s,b(t),i=!0;break b}d===n(c)&&r(c),b(t)}else r(c);d=n(c)}if(d!==null)i=!0;else{var u=n(l);u!==null&&oe(x,u.startTime-t),i=!1}}break a}finally{d=null,f=a,p=!1}i=void 0}}finally{i?ie():ee=!1}}}var ie;if(typeof y==`function`)ie=function(){y(re)};else if(typeof MessageChannel<`u`){var w=new MessageChannel,ae=w.port2;w.port1.onmessage=re,ie=function(){ae.postMessage(null)}}else ie=function(){_(re,0)};function oe(t,n){S=_(function(){t(e.unstable_now())},n)}e.unstable_IdlePriority=5,e.unstable_ImmediatePriority=1,e.unstable_LowPriority=4,e.unstable_NormalPriority=3,e.unstable_Profiling=null,e.unstable_UserBlockingPriority=2,e.unstable_cancelCallback=function(e){e.callback=null},e.unstable_forceFrameRate=function(e){0>e||125o?(r.sortIndex=a,t(l,r),n(c)===null&&r===n(l)&&(h?(v(S),S=-1):h=!0,oe(x,a-o))):(r.sortIndex=s,t(c,r),m||p||(m=!0,ee||(ee=!0,ie()))),r},e.unstable_shouldYield=ne,e.unstable_wrapCallback=function(e){var t=f;return function(){var n=f;f=t;try{return e.apply(this,arguments)}finally{f=n}}}})),f=o(((e,t)=>{t.exports=d()})),p=o((e=>{var t=u();function n(e){var t=`https://react.dev/errors/`+e;if(1{function n(){if(!(typeof __REACT_DEVTOOLS_GLOBAL_HOOK__>`u`||typeof __REACT_DEVTOOLS_GLOBAL_HOOK__.checkDCE!=`function`))try{__REACT_DEVTOOLS_GLOBAL_HOOK__.checkDCE(n)}catch(e){console.error(e)}}n(),t.exports=p()})),h=o((e=>{var t=f(),n=u(),r=m();function i(e){var t=`https://react.dev/errors/`+e;if(1fe||(e.current=de[fe],de[fe]=null,fe--)}function pe(e,t){fe++,de[fe]=e.current,e.current=t}var A=O(null),me=O(null),he=O(null),ge=O(null);function _e(e,t){switch(pe(he,t),pe(me,e),pe(A,null),t.nodeType){case 9:case 11:e=(e=t.documentElement)&&(e=e.namespaceURI)?Vd(e):0;break;default:if(e=t.tagName,t=t.namespaceURI)t=Vd(t),e=Hd(t,e);else switch(e){case`svg`:e=1;break;case`math`:e=2;break;default:e=0}}k(A),pe(A,e)}function ve(){k(A),k(me),k(he)}function ye(e){e.memoizedState!==null&&pe(ge,e);var t=A.current,n=Hd(t,e.type);t!==n&&(pe(me,e),pe(A,n))}function be(e){me.current===e&&(k(A),k(me)),ge.current===e&&(k(ge),Qf._currentValue=ue)}var xe,Se;function Ce(e){if(xe===void 0)try{throw Error()}catch(e){var t=e.stack.trim().match(/\n( *(at )?)/);xe=t&&t[1]||``,Se=-1)`:-1i||c[r]!==l[i]){var u=` +`+c[r].replace(` at new `,` at `);return e.displayName&&u.includes(``)&&(u=u.replace(``,e.displayName)),u}while(1<=r&&0<=i);break}}}finally{we=!1,Error.prepareStackTrace=n}return(n=e?e.displayName||e.name:``)?Ce(n):``}function j(e,t){switch(e.tag){case 26:case 27:case 5:return Ce(e.type);case 16:return Ce(`Lazy`);case 13:return e.child!==t&&t!==null?Ce(`Suspense Fallback`):Ce(`Suspense`);case 19:return Ce(`SuspenseList`);case 0:case 15:return Te(e.type,!1);case 11:return Te(e.type.render,!1);case 1:return Te(e.type,!0);case 31:return Ce(`Activity`);default:return``}}function Ee(e){try{var t=``,n=null;do t+=j(e,n),n=e,e=e.return;while(e);return t}catch(e){return` +Error generating stack: `+e.message+` +`+e.stack}}var De=Object.prototype.hasOwnProperty,Oe=t.unstable_scheduleCallback,ke=t.unstable_cancelCallback,Ae=t.unstable_shouldYield,je=t.unstable_requestPaint,M=t.unstable_now,Me=t.unstable_getCurrentPriorityLevel,Ne=t.unstable_ImmediatePriority,Pe=t.unstable_UserBlockingPriority,Fe=t.unstable_NormalPriority,Ie=t.unstable_LowPriority,Le=t.unstable_IdlePriority,Re=t.log,ze=t.unstable_setDisableYieldValue,Be=null,Ve=null;function He(e){if(typeof Re==`function`&&ze(e),Ve&&typeof Ve.setStrictMode==`function`)try{Ve.setStrictMode(Be,e)}catch{}}var Ue=Math.clz32?Math.clz32:Ke,We=Math.log,Ge=Math.LN2;function Ke(e){return e>>>=0,e===0?32:31-(We(e)/Ge|0)|0}var qe=256,Je=262144,Ye=4194304;function Xe(e){var t=e&42;if(t!==0)return t;switch(e&-e){case 1:return 1;case 2:return 2;case 4:return 4;case 8:return 8;case 16:return 16;case 32:return 32;case 64:return 64;case 128:return 128;case 256:case 512:case 1024:case 2048:case 4096:case 8192:case 16384:case 32768:case 65536:case 131072:return e&261888;case 262144:case 524288:case 1048576:case 2097152:return e&3932160;case 4194304:case 8388608:case 16777216:case 33554432:return e&62914560;case 67108864:return 67108864;case 134217728:return 134217728;case 268435456:return 268435456;case 536870912:return 536870912;case 1073741824:return 0;default:return e}}function Ze(e,t,n){var r=e.pendingLanes;if(r===0)return 0;var i=0,a=e.suspendedLanes,o=e.pingedLanes;e=e.warmLanes;var s=r&134217727;return s===0?(s=r&~a,s===0?o===0?n||(n=r&~e,n!==0&&(i=Xe(n))):i=Xe(o):i=Xe(s)):(r=s&~a,r===0?(o&=s,o===0?n||(n=s&~e,n!==0&&(i=Xe(n))):i=Xe(o)):i=Xe(r)),i===0?0:t!==0&&t!==i&&(t&a)===0&&(a=i&-i,n=t&-t,a>=n||a===32&&n&4194048)?t:i}function Qe(e,t){return(e.pendingLanes&~(e.suspendedLanes&~e.pingedLanes)&t)===0}function $e(e,t){switch(e){case 1:case 2:case 4:case 8:case 64:return t+250;case 16:case 32:case 128:case 256:case 512:case 1024:case 2048:case 4096:case 8192:case 16384:case 32768:case 65536:case 131072:case 262144:case 524288:case 1048576:case 2097152:return t+5e3;case 4194304:case 8388608:case 16777216:case 33554432:return-1;case 67108864:case 134217728:case 268435456:case 536870912:case 1073741824:return-1;default:return-1}}function et(){var e=Ye;return Ye<<=1,!(Ye&62914560)&&(Ye=4194304),e}function tt(e){for(var t=[],n=0;31>n;n++)t.push(e);return t}function nt(e,t){e.pendingLanes|=t,t!==268435456&&(e.suspendedLanes=0,e.pingedLanes=0,e.warmLanes=0)}function rt(e,t,n,r,i,a){var o=e.pendingLanes;e.pendingLanes=n,e.suspendedLanes=0,e.pingedLanes=0,e.warmLanes=0,e.expiredLanes&=n,e.entangledLanes&=n,e.errorRecoveryDisabledLanes&=n,e.shellSuspendCounter=0;var s=e.entanglements,c=e.expirationTimes,l=e.hiddenUpdates;for(n=o&~n;0`u`||window.document===void 0||window.document.createElement===void 0),fn=!1;if(dn)try{var pn={};Object.defineProperty(pn,`passive`,{get:function(){fn=!0}}),window.addEventListener(`test`,pn,pn),window.removeEventListener(`test`,pn,pn)}catch{fn=!1}var L=null,mn=null,hn=null;function gn(){if(hn)return hn;var e,t=mn,n=t.length,r,i=`value`in L?L.value:L.textContent,a=i.length;for(e=0;e=Wn),qn=` `,Jn=!1;function Yn(e,t){switch(e){case`keyup`:return Hn.indexOf(t.keyCode)!==-1;case`keydown`:return t.keyCode!==229;case`keypress`:case`mousedown`:case`focusout`:return!0;default:return!1}}function Xn(e){return e=e.detail,typeof e==`object`&&`data`in e?e.data:null}var Zn=!1;function Qn(e,t){switch(e){case`compositionend`:return Xn(t);case`keypress`:return t.which===32?(Jn=!0,qn):null;case`textInput`:return e=t.data,e===qn&&Jn?null:e;default:return null}}function $n(e,t){if(Zn)return e===`compositionend`||!Un&&Yn(e,t)?(e=gn(),hn=mn=L=null,Zn=!1,e):null;switch(e){case`paste`:return null;case`keypress`:if(!(t.ctrlKey||t.altKey||t.metaKey)||t.ctrlKey&&t.altKey){if(t.char&&1=t)return{node:n,offset:t-e};e=r}a:{for(;n;){if(n.nextSibling){n=n.nextSibling;break a}n=n.parentNode}n=void 0}n=xr(n)}}function Cr(e,t){return e&&t?e===t?!0:e&&e.nodeType===3?!1:t&&t.nodeType===3?Cr(e,t.parentNode):`contains`in e?e.contains(t):e.compareDocumentPosition?!!(e.compareDocumentPosition(t)&16):!1:!1}function wr(e){e=e!=null&&e.ownerDocument!=null&&e.ownerDocument.defaultView!=null?e.ownerDocument.defaultView:window;for(var t=Bt(e.document);t instanceof e.HTMLIFrameElement;){try{var n=typeof t.contentWindow.location.href==`string`}catch{n=!1}if(n)e=t.contentWindow;else break;t=Bt(e.document)}return t}function Tr(e){var t=e&&e.nodeName&&e.nodeName.toLowerCase();return t&&(t===`input`&&(e.type===`text`||e.type===`search`||e.type===`tel`||e.type===`url`||e.type===`password`)||t===`textarea`||e.contentEditable===`true`)}var Er=dn&&`documentMode`in document&&11>=document.documentMode,Dr=null,Or=null,kr=null,Ar=!1;function jr(e,t,n){var r=n.window===n?n.document:n.nodeType===9?n:n.ownerDocument;Ar||Dr==null||Dr!==Bt(r)||(r=Dr,`selectionStart`in r&&Tr(r)?r={start:r.selectionStart,end:r.selectionEnd}:(r=(r.ownerDocument&&r.ownerDocument.defaultView||window).getSelection(),r={anchorNode:r.anchorNode,anchorOffset:r.anchorOffset,focusNode:r.focusNode,focusOffset:r.focusOffset}),kr&&br(kr,r)||(kr=r,r=Td(Or,`onSelect`),0>=o,i-=o,xi=1<<32-Ue(t)+i|n<h?(g=d,d=null):g=d.sibling;var _=p(i,d,s[h],c);if(_===null){d===null&&(d=g);break}e&&d&&_.alternate===null&&t(i,d),a=o(_,a,h),u===null?l=_:u.sibling=_,u=_,d=g}if(h===s.length)return n(i,d),W&&Ci(i,h),l;if(d===null){for(;hg?(_=h,h=null):_=h.sibling;var y=p(a,h,v.value,l);if(y===null){h===null&&(h=_);break}e&&h&&y.alternate===null&&t(a,h),s=o(y,s,g),d===null?u=y:d.sibling=y,d=y,h=_}if(v.done)return n(a,h),W&&Ci(a,g),u;if(h===null){for(;!v.done;g++,v=c.next())v=f(a,v.value,l),v!==null&&(s=o(v,s,g),d===null?u=v:d.sibling=v,d=v);return W&&Ci(a,g),u}for(h=r(h);!v.done;g++,v=c.next())v=m(h,a,g,v.value,l),v!==null&&(e&&v.alternate!==null&&h.delete(v.key===null?g:v.key),s=o(v,s,g),d===null?u=v:d.sibling=v,d=v);return e&&h.forEach(function(e){return t(a,e)}),W&&Ci(a,g),u}function b(e,r,o,c){if(typeof o==`object`&&o&&o.type===y&&o.key===null&&(o=o.props.children),typeof o==`object`&&o){switch(o.$$typeof){case _:a:{for(var l=o.key;r!==null;){if(r.key===l){if(l=o.type,l===y){if(r.tag===7){n(e,r.sibling),c=a(r,o.props.children),c.return=e,e=c;break a}}else if(r.elementType===l||typeof l==`object`&&l&&l.$$typeof===ie&&ya(l)===r.type){n(e,r.sibling),c=a(r,o.props),Ea(c,o),c.return=e,e=c;break a}n(e,r);break}else t(e,r);r=r.sibling}o.type===y?(c=ci(o.props.children,e.mode,c,o.key),c.return=e,e=c):(c=si(o.type,o.key,o.props,null,e.mode,c),Ea(c,o),c.return=e,e=c)}return s(e);case v:a:{for(l=o.key;r!==null;){if(r.key===l)if(r.tag===4&&r.stateNode.containerInfo===o.containerInfo&&r.stateNode.implementation===o.implementation){n(e,r.sibling),c=a(r,o.children||[]),c.return=e,e=c;break a}else{n(e,r);break}else t(e,r);r=r.sibling}c=di(o,e.mode,c),c.return=e,e=c}return s(e);case ie:return o=ya(o),b(e,r,o,c)}if(le(o))return h(e,r,o,c);if(T(o)){if(l=T(o),typeof l!=`function`)throw Error(i(150));return o=l.call(o),g(e,r,o,c)}if(typeof o.then==`function`)return b(e,r,Ta(o),c);if(o.$$typeof===S)return b(e,r,Yi(e,o),c);Da(e,o)}return typeof o==`string`&&o!==``||typeof o==`number`||typeof o==`bigint`?(o=``+o,r!==null&&r.tag===6?(n(e,r.sibling),c=a(r,o),c.return=e,e=c):(n(e,r),c=li(o,e.mode,c),c.return=e,e=c),s(e)):n(e,r)}return function(e,t,n,r){try{wa=0;var i=b(e,t,n,r);return Ca=null,i}catch(t){if(t===pa||t===ha)throw t;var a=ri(29,t,null,e.mode);return a.lanes=r,a.return=e,a}}}var ka=Oa(!0),Aa=Oa(!1),ja=!1;function Ma(e){e.updateQueue={baseState:e.memoizedState,firstBaseUpdate:null,lastBaseUpdate:null,shared:{pending:null,lanes:0,hiddenCallbacks:null},callbacks:null}}function Na(e,t){e=e.updateQueue,t.updateQueue===e&&(t.updateQueue={baseState:e.baseState,firstBaseUpdate:e.firstBaseUpdate,lastBaseUpdate:e.lastBaseUpdate,shared:e.shared,callbacks:null})}function Pa(e){return{lane:e,tag:0,payload:null,callback:null,next:null}}function Fa(e,t,n){var r=e.updateQueue;if(r===null)return null;if(r=r.shared,Pl&2){var i=r.pending;return i===null?t.next=t:(t.next=i.next,i.next=t),r.pending=t,t=ei(e),$r(e,null,n),t}return Xr(e,r,t,n),ei(e)}function Ia(e,t,n){if(t=t.updateQueue,t!==null&&(t=t.shared,n&4194048)){var r=t.lanes;r&=e.pendingLanes,n|=r,t.lanes=n,at(e,n)}}function La(e,t){var n=e.updateQueue,r=e.alternate;if(r!==null&&(r=r.updateQueue,n===r)){var i=null,a=null;if(n=n.firstBaseUpdate,n!==null){do{var o={lane:n.lane,tag:n.tag,payload:n.payload,callback:null,next:null};a===null?i=a=o:a=a.next=o,n=n.next}while(n!==null);a===null?i=a=t:a=a.next=t}else i=a=t;n={baseState:r.baseState,firstBaseUpdate:i,lastBaseUpdate:a,shared:r.shared,callbacks:r.callbacks},e.updateQueue=n;return}e=n.lastBaseUpdate,e===null?n.firstBaseUpdate=t:e.next=t,n.lastBaseUpdate=t}var Ra=!1;function Y(){if(Ra){var e=aa;if(e!==null)throw e}}function za(e,t,n,r){Ra=!1;var i=e.updateQueue;ja=!1;var a=i.firstBaseUpdate,o=i.lastBaseUpdate,s=i.shared.pending;if(s!==null){i.shared.pending=null;var c=s,l=c.next;c.next=null,o===null?a=l:o.next=l,o=c;var u=e.alternate;u!==null&&(u=u.updateQueue,s=u.lastBaseUpdate,s!==o&&(s===null?u.firstBaseUpdate=l:s.next=l,u.lastBaseUpdate=c))}if(a!==null){var d=i.baseState;o=0,u=l=c=null,s=a;do{var f=s.lane&-536870913,p=f!==s.lane;if(p?(Q&f)===f:(r&f)===f){f!==0&&f===ia&&(Ra=!0),u!==null&&(u=u.next={lane:0,tag:s.tag,payload:s.payload,callback:null,next:null});a:{var m=e,g=s;f=t;var _=n;switch(g.tag){case 1:if(m=g.payload,typeof m==`function`){d=m.call(_,d,f);break a}d=m;break a;case 3:m.flags=m.flags&-65537|128;case 0:if(m=g.payload,f=typeof m==`function`?m.call(_,d,f):m,f==null)break a;d=h({},d,f);break a;case 2:ja=!0}}f=s.callback,f!==null&&(e.flags|=64,p&&(e.flags|=8192),p=i.callbacks,p===null?i.callbacks=[f]:p.push(f))}else p={lane:f,tag:s.tag,payload:s.payload,callback:s.callback,next:null},u===null?(l=u=p,c=d):u=u.next=p,o|=f;if(s=s.next,s===null){if(s=i.shared.pending,s===null)break;p=s,s=p.next,p.next=null,i.lastBaseUpdate=p,i.shared.pending=null}}while(1);u===null&&(c=d),i.baseState=c,i.firstBaseUpdate=l,i.lastBaseUpdate=u,a===null&&(i.shared.lanes=0),Ul|=o,e.lanes=o,e.memoizedState=d}}function Ba(e,t){if(typeof e!=`function`)throw Error(i(191,e));e.call(t)}function Va(e,t){var n=e.callbacks;if(n!==null)for(e.callbacks=null,e=0;ea?a:8;var o=E.T,s={};E.T=s,Ds(e,!1,t,n);try{var c=i(),l=E.S;l!==null&&l(s,c),typeof c==`object`&&c&&typeof c.then==`function`?Es(e,t,sa(c,r),du(e)):Es(e,t,r,du(e))}catch(n){Es(e,t,{then:function(){},status:`rejected`,reason:n},du())}finally{D.p=a,o!==null&&s.types!==null&&(o.types=s.types),E.T=o}}function gs(){}function _s(e,t,n,r){if(e.tag!==5)throw Error(i(476));var a=vs(e).queue;hs(e,a,t,ue,n===null?gs:function(){return ys(e),n(r)})}function vs(e){var t=e.memoizedState;if(t!==null)return t;t={memoizedState:ue,baseState:ue,baseQueue:null,queue:{pending:null,lanes:0,dispatch:null,lastRenderedReducer:Oo,lastRenderedState:ue},next:null};var n={};return t.next={memoizedState:n,baseState:n,baseQueue:null,queue:{pending:null,lanes:0,dispatch:null,lastRenderedReducer:Oo,lastRenderedState:n},next:null},e.memoizedState=t,e=e.alternate,e!==null&&(e.memoizedState=t),t}function ys(e){var t=vs(e);t.next===null&&(t=e.alternate.memoizedState),Es(e,t.next.queue,{},du())}function bs(){return Ji(Qf)}function xs(){return Co().memoizedState}function Ss(){return Co().memoizedState}function Cs(e){for(var t=e.return;t!==null;){switch(t.tag){case 24:case 3:var n=du();e=Pa(n);var r=Fa(t,e,n);r!==null&&(pu(r,t,n),Ia(r,t,n)),t={cache:ta()},e.payload=t;return}t=t.return}}function ws(e,t,n){var r=du();n={lane:r,revertLane:0,gesture:null,action:n,hasEagerState:!1,eagerState:null,next:null},Os(e)?ks(t,n):(n=Zr(e,t,n,r),n!==null&&(pu(n,e,r),As(n,t,r)))}function Ts(e,t,n){Es(e,t,n,du())}function Es(e,t,n,r){var i={lane:r,revertLane:0,gesture:null,action:n,hasEagerState:!1,eagerState:null,next:null};if(Os(e))ks(t,i);else{var a=e.alternate;if(e.lanes===0&&(a===null||a.lanes===0)&&(a=t.lastRenderedReducer,a!==null))try{var o=t.lastRenderedState,s=a(o,n);if(i.hasEagerState=!0,i.eagerState=s,yr(s,o))return Xr(e,t,i,0),Fl===null&&U(),!1}catch{}if(n=Zr(e,t,i,r),n!==null)return pu(n,e,r),As(n,t,r),!0}return!1}function Ds(e,t,n,r){if(r={lane:2,revertLane:ud(),gesture:null,action:r,hasEagerState:!1,eagerState:null,next:null},Os(e)){if(t)throw Error(i(479))}else t=Zr(e,n,r,2),t!==null&&pu(t,e,2)}function Os(e){var t=e.alternate;return e===X||t!==null&&t===X}function ks(e,t){oo=ao=!0;var n=e.pending;n===null?t.next=t:(t.next=n.next,n.next=t),e.pending=t}function As(e,t,n){if(n&4194048){var r=t.lanes;r&=e.pendingLanes,n|=r,t.lanes=n,at(e,n)}}var js={readContext:Ji,use:Eo,useCallback:po,useContext:po,useEffect:po,useImperativeHandle:po,useLayoutEffect:po,useInsertionEffect:po,useMemo:po,useReducer:po,useRef:po,useState:po,useDebugValue:po,useDeferredValue:po,useTransition:po,useSyncExternalStore:po,useId:po,useHostTransitionStatus:po,useFormState:po,useActionState:po,useOptimistic:po,useMemoCache:po,useCacheRefresh:po};js.useEffectEvent=po;var Ms={readContext:Ji,use:Eo,useCallback:function(e,t){return So().memoizedState=[e,t===void 0?null:t],e},useContext:Ji,useEffect:ns,useImperativeHandle:function(e,t,n){n=n==null?null:n.concat([e]),es(4194308,4,cs.bind(null,t,e),n)},useLayoutEffect:function(e,t){return es(4194308,4,e,t)},useInsertionEffect:function(e,t){es(4,2,e,t)},useMemo:function(e,t){var n=So();t=t===void 0?null:t;var r=e();if(so){He(!0);try{e()}finally{He(!1)}}return n.memoizedState=[r,t],r},useReducer:function(e,t,n){var r=So();if(n!==void 0){var i=n(t);if(so){He(!0);try{n(t)}finally{He(!1)}}}else i=t;return r.memoizedState=r.baseState=i,e={pending:null,lanes:0,dispatch:null,lastRenderedReducer:e,lastRenderedState:i},r.queue=e,e=e.dispatch=ws.bind(null,X,e),[r.memoizedState,e]},useRef:function(e){var t=So();return e={current:e},t.memoizedState=e},useState:function(e){e=Ro(e);var t=e.queue,n=Ts.bind(null,X,t);return t.dispatch=n,[e.memoizedState,n]},useDebugValue:us,useDeferredValue:function(e,t){return ps(So(),e,t)},useTransition:function(){var e=Ro(!1);return e=hs.bind(null,X,e.queue,!0,!1),So().memoizedState=e,[!1,e]},useSyncExternalStore:function(e,t,n){var r=X,a=So();if(W){if(n===void 0)throw Error(i(407));n=n()}else{if(n=t(),Fl===null)throw Error(i(349));Q&127||No(r,t,n)}a.memoizedState=n;var o={value:n,getSnapshot:t};return a.queue=o,ns(Fo.bind(null,r,o,e),[e]),r.flags|=2048,Qo(9,{destroy:void 0},Po.bind(null,r,o,n,t),null),n},useId:function(){var e=So(),t=Fl.identifierPrefix;if(W){var n=Si,r=xi;n=(r&~(1<<32-Ue(r)-1)).toString(32)+n,t=`_`+t+`R_`+n,n=co++,0<\/script>`,o=o.removeChild(o.firstChild);break;case`select`:o=typeof r.is==`string`?s.createElement(`select`,{is:r.is}):s.createElement(`select`),r.multiple?o.multiple=!0:r.size&&(o.size=r.size);break;default:o=typeof r.is==`string`?s.createElement(a,{is:r.is}):s.createElement(a)}}o[dt]=t,o[ft]=r;a:for(s=t.child;s!==null;){if(s.tag===5||s.tag===6)o.appendChild(s.stateNode);else if(s.tag!==4&&s.tag!==27&&s.child!==null){s.child.return=s,s=s.child;continue}if(s===t)break a;for(;s.sibling===null;){if(s.return===null||s.return===t)break a;s=s.return}s.sibling.return=s.return,s=s.sibling}t.stateNode=o;a:switch(Pd(o,a,r),a){case`button`:case`input`:case`select`:case`textarea`:r=!!r.autoFocus;break a;case`img`:r=!0;break a;default:r=!1}r&&Ec(t)}}return jc(t),Dc(t,t.type,e===null?null:e.memoizedProps,t.pendingProps,n),null;case 6:if(e&&t.stateNode!=null)e.memoizedProps!==r&&Ec(t);else{if(typeof r!=`string`&&t.stateNode===null)throw Error(i(166));if(e=he.current,Ii(t)){if(e=t.stateNode,n=t.memoizedProps,r=null,a=Oi,a!==null)switch(a.tag){case 27:case 5:r=a.memoizedProps}e[dt]=t,e=!!(e.nodeValue===n||r!==null&&!0===r.suppressHydrationWarning||jd(e.nodeValue,n)),e||Ni(t,!0)}else e=Bd(e).createTextNode(r),e[dt]=t,t.stateNode=e}return jc(t),null;case 31:if(n=t.memoizedState,e===null||e.memoizedState!==null){if(r=Ii(t),n!==null){if(e===null){if(!r)throw Error(i(318));if(e=t.memoizedState,e=e===null?null:e.dehydrated,!e)throw Error(i(557));e[dt]=t}else G(),!(t.flags&128)&&(t.memoizedState=null),t.flags|=4;jc(t),e=!1}else n=Li(),e!==null&&e.memoizedState!==null&&(e.memoizedState.hydrationErrors=n),e=!0;if(!e)return t.flags&256?($a(t),t):($a(t),null);if(t.flags&128)throw Error(i(558))}return jc(t),null;case 13:if(r=t.memoizedState,e===null||e.memoizedState!==null&&e.memoizedState.dehydrated!==null){if(a=Ii(t),r!==null&&r.dehydrated!==null){if(e===null){if(!a)throw Error(i(318));if(a=t.memoizedState,a=a===null?null:a.dehydrated,!a)throw Error(i(317));a[dt]=t}else G(),!(t.flags&128)&&(t.memoizedState=null),t.flags|=4;jc(t),a=!1}else a=Li(),e!==null&&e.memoizedState!==null&&(e.memoizedState.hydrationErrors=a),a=!0;if(!a)return t.flags&256?($a(t),t):($a(t),null)}return $a(t),t.flags&128?(t.lanes=n,t):(n=r!==null,e=e!==null&&e.memoizedState!==null,n&&(r=t.child,a=null,r.alternate!==null&&r.alternate.memoizedState!==null&&r.alternate.memoizedState.cachePool!==null&&(a=r.alternate.memoizedState.cachePool.pool),o=null,r.memoizedState!==null&&r.memoizedState.cachePool!==null&&(o=r.memoizedState.cachePool.pool),o!==a&&(r.flags|=2048)),n!==e&&n&&(t.child.flags|=8192),kc(t,t.updateQueue),jc(t),null);case 4:return ve(),e===null&&xd(t.stateNode.containerInfo),jc(t),null;case 10:return Ui(t.type),jc(t),null;case 19:if(k(eo),r=t.memoizedState,r===null)return jc(t),null;if(a=(t.flags&128)!=0,o=r.rendering,o===null)if(a)Ac(r,!1);else{if(Hl!==0||e!==null&&e.flags&128)for(e=t.child;e!==null;){if(o=to(e),o!==null){for(t.flags|=128,Ac(r,!1),e=o.updateQueue,t.updateQueue=e,kc(t,e),t.subtreeFlags=0,e=n,n=t.child;n!==null;)oi(n,e),n=n.sibling;return pe(eo,eo.current&1|2),W&&Ci(t,r.treeForkCount),t.child}e=e.sibling}r.tail!==null&&M()>$l&&(t.flags|=128,a=!0,Ac(r,!1),t.lanes=4194304)}else{if(!a)if(e=to(o),e!==null){if(t.flags|=128,a=!0,e=e.updateQueue,t.updateQueue=e,kc(t,e),Ac(r,!0),r.tail===null&&r.tailMode===`hidden`&&!o.alternate&&!W)return jc(t),null}else 2*M()-r.renderingStartTime>$l&&n!==536870912&&(t.flags|=128,a=!0,Ac(r,!1),t.lanes=4194304);r.isBackwards?(o.sibling=t.child,t.child=o):(e=r.last,e===null?t.child=o:e.sibling=o,r.last=o)}return r.tail===null?(jc(t),null):(e=r.tail,r.rendering=e,r.tail=e.sibling,r.renderingStartTime=M(),e.sibling=null,n=eo.current,pe(eo,a?n&1|2:n&1),W&&Ci(t,r.treeForkCount),e);case 22:case 23:return $a(t),Ka(),r=t.memoizedState!==null,e===null?r&&(t.flags|=8192):e.memoizedState!==null!==r&&(t.flags|=8192),r?n&536870912&&!(t.flags&128)&&(jc(t),t.subtreeFlags&6&&(t.flags|=8192)):jc(t),n=t.updateQueue,n!==null&&kc(t,n.retryQueue),n=null,e!==null&&e.memoizedState!==null&&e.memoizedState.cachePool!==null&&(n=e.memoizedState.cachePool.pool),r=null,t.memoizedState!==null&&t.memoizedState.cachePool!==null&&(r=t.memoizedState.cachePool.pool),r!==n&&(t.flags|=2048),e!==null&&k(la),null;case 24:return n=null,e!==null&&(n=e.memoizedState.cache),t.memoizedState.cache!==n&&(t.flags|=2048),Ui(ea),jc(t),null;case 25:return null;case 30:return null}throw Error(i(156,t.tag))}function Nc(e,t){switch(Ei(t),t.tag){case 1:return e=t.flags,e&65536?(t.flags=e&-65537|128,t):null;case 3:return Ui(ea),ve(),e=t.flags,e&65536&&!(e&128)?(t.flags=e&-65537|128,t):null;case 26:case 27:case 5:return be(t),null;case 31:if(t.memoizedState!==null){if($a(t),t.alternate===null)throw Error(i(340));G()}return e=t.flags,e&65536?(t.flags=e&-65537|128,t):null;case 13:if($a(t),e=t.memoizedState,e!==null&&e.dehydrated!==null){if(t.alternate===null)throw Error(i(340));G()}return e=t.flags,e&65536?(t.flags=e&-65537|128,t):null;case 19:return k(eo),null;case 4:return ve(),null;case 10:return Ui(t.type),null;case 22:case 23:return $a(t),Ka(),e!==null&&k(la),e=t.flags,e&65536?(t.flags=e&-65537|128,t):null;case 24:return Ui(ea),null;case 25:return null;default:return null}}function Pc(e,t){switch(Ei(t),t.tag){case 3:Ui(ea),ve();break;case 26:case 27:case 5:be(t);break;case 4:ve();break;case 31:t.memoizedState!==null&&$a(t);break;case 13:$a(t);break;case 19:k(eo);break;case 10:Ui(t.type);break;case 22:case 23:$a(t),Ka(),e!==null&&k(la);break;case 24:Ui(ea)}}function Fc(e,t){try{var n=t.updateQueue,r=n===null?null:n.lastEffect;if(r!==null){var i=r.next;n=i;do{if((n.tag&e)===e){r=void 0;var a=n.create,o=n.inst;r=a(),o.destroy=r}n=n.next}while(n!==i)}}catch(e){Uu(t,t.return,e)}}function Ic(e,t,n){try{var r=t.updateQueue,i=r===null?null:r.lastEffect;if(i!==null){var a=i.next;r=a;do{if((r.tag&e)===e){var o=r.inst,s=o.destroy;if(s!==void 0){o.destroy=void 0,i=t;var c=n,l=s;try{l()}catch(e){Uu(i,c,e)}}}r=r.next}while(r!==a)}}catch(e){Uu(t,t.return,e)}}function Lc(e){var t=e.updateQueue;if(t!==null){var n=e.stateNode;try{Va(t,n)}catch(t){Uu(e,e.return,t)}}}function Rc(e,t,n){n.props=zs(e.type,e.memoizedProps),n.state=e.memoizedState;try{n.componentWillUnmount()}catch(n){Uu(e,t,n)}}function zc(e,t){try{var n=e.ref;if(n!==null){switch(e.tag){case 26:case 27:case 5:var r=e.stateNode;break;case 30:r=e.stateNode;break;default:r=e.stateNode}typeof n==`function`?e.refCleanup=n(r):n.current=r}}catch(n){Uu(e,t,n)}}function Bc(e,t){var n=e.ref,r=e.refCleanup;if(n!==null)if(typeof r==`function`)try{r()}catch(n){Uu(e,t,n)}finally{e.refCleanup=null,e=e.alternate,e!=null&&(e.refCleanup=null)}else if(typeof n==`function`)try{n(null)}catch(n){Uu(e,t,n)}else n.current=null}function Vc(e){var t=e.type,n=e.memoizedProps,r=e.stateNode;try{a:switch(t){case`button`:case`input`:case`select`:case`textarea`:n.autoFocus&&r.focus();break a;case`img`:n.src?r.src=n.src:n.srcSet&&(r.srcset=n.srcSet)}}catch(t){Uu(e,e.return,t)}}function Hc(e,t,n){try{var r=e.stateNode;Fd(r,e.type,n,t),r[ft]=t}catch(t){Uu(e,e.return,t)}}function Uc(e){return e.tag===5||e.tag===3||e.tag===26||e.tag===27&&Zd(e.type)||e.tag===4}function Wc(e){a:for(;;){for(;e.sibling===null;){if(e.return===null||Uc(e.return))return null;e=e.return}for(e.sibling.return=e.return,e=e.sibling;e.tag!==5&&e.tag!==6&&e.tag!==18;){if(e.tag===27&&Zd(e.type)||e.flags&2||e.child===null||e.tag===4)continue a;e.child.return=e,e=e.child}if(!(e.flags&2))return e.stateNode}}function Gc(e,t,n){var r=e.tag;if(r===5||r===6)e=e.stateNode,t?(n.nodeType===9?n.body:n.nodeName===`HTML`?n.ownerDocument.body:n).insertBefore(e,t):(t=n.nodeType===9?n.body:n.nodeName===`HTML`?n.ownerDocument.body:n,t.appendChild(e),n=n._reactRootContainer,n!=null||t.onclick!==null||(t.onclick=tn));else if(r!==4&&(r===27&&Zd(e.type)&&(n=e.stateNode,t=null),e=e.child,e!==null))for(Gc(e,t,n),e=e.sibling;e!==null;)Gc(e,t,n),e=e.sibling}function Kc(e,t,n){var r=e.tag;if(r===5||r===6)e=e.stateNode,t?n.insertBefore(e,t):n.appendChild(e);else if(r!==4&&(r===27&&Zd(e.type)&&(n=e.stateNode),e=e.child,e!==null))for(Kc(e,t,n),e=e.sibling;e!==null;)Kc(e,t,n),e=e.sibling}function qc(e){var t=e.stateNode,n=e.memoizedProps;try{for(var r=e.type,i=t.attributes;i.length;)t.removeAttributeNode(i[0]);Pd(t,r,n),t[dt]=e,t[ft]=n}catch(t){Uu(e,e.return,t)}}var Jc=!1,Yc=!1,Xc=!1,Zc=typeof WeakSet==`function`?WeakSet:Set,Qc=null;function $c(e,t){if(e=e.containerInfo,Rd=sp,e=wr(e),Tr(e)){if(`selectionStart`in e)var n={start:e.selectionStart,end:e.selectionEnd};else a:{n=(n=e.ownerDocument)&&n.defaultView||window;var r=n.getSelection&&n.getSelection();if(r&&r.rangeCount!==0){n=r.anchorNode;var a=r.anchorOffset,o=r.focusNode;r=r.focusOffset;try{n.nodeType,o.nodeType}catch{n=null;break a}var s=0,c=-1,l=-1,u=0,d=0,f=e,p=null;b:for(;;){for(var m;f!==n||a!==0&&f.nodeType!==3||(c=s+a),f!==o||r!==0&&f.nodeType!==3||(l=s+r),f.nodeType===3&&(s+=f.nodeValue.length),(m=f.firstChild)!==null;)p=f,f=m;for(;;){if(f===e)break b;if(p===n&&++u===a&&(c=s),p===o&&++d===r&&(l=s),(m=f.nextSibling)!==null)break;f=p,p=f.parentNode}f=m}n=c===-1||l===-1?null:{start:c,end:l}}else n=null}n||={start:0,end:0}}else n=null;for(zd={focusedElem:e,selectionRange:n},sp=!1,Qc=t;Qc!==null;)if(t=Qc,e=t.child,t.subtreeFlags&1028&&e!==null)e.return=t,Qc=e;else for(;Qc!==null;){switch(t=Qc,o=t.alternate,e=t.flags,t.tag){case 0:if(e&4&&(e=t.updateQueue,e=e===null?null:e.events,e!==null))for(n=0;n title`))),Pd(o,r,n),o[dt]=e,Ct(o),r=o;break a;case`link`:var s=Vf(`link`,`href`,a).get(r+(n.href||``));if(s){for(var c=0;cg&&(o=g,g=h,h=o);var _=Sr(s,h),v=Sr(s,g);if(_&&v&&(p.rangeCount!==1||p.anchorNode!==_.node||p.anchorOffset!==_.offset||p.focusNode!==v.node||p.focusOffset!==v.offset)){var y=d.createRange();y.setStart(_.node,_.offset),p.removeAllRanges(),h>g?(p.addRange(y),p.extend(v.node,v.offset)):(y.setEnd(v.node,v.offset),p.addRange(y))}}}}for(d=[],p=s;p=p.parentNode;)p.nodeType===1&&d.push({element:p,left:p.scrollLeft,top:p.scrollTop});for(typeof s.focus==`function`&&s.focus(),s=0;sn?32:n,E.T=null,n=su,su=null;var o=ru,s=au;if(nu=0,iu=ru=null,au=0,Pl&6)throw Error(i(331));var c=Pl;if(Pl|=4,kl(o.current),xl(o,o.current,s,n),Pl=c,rd(0,!1),Ve&&typeof Ve.onPostCommitFiberRoot==`function`)try{Ve.onPostCommitFiberRoot(Be,o)}catch{}return!0}finally{D.p=a,E.T=r,zu(e,t)}}function Hu(e,t,n){t=pi(n,t),t=Gs(e.stateNode,t,2),e=Fa(e,t,2),e!==null&&(nt(e,2),nd(e))}function Uu(e,t,n){if(e.tag===3)Hu(e,e,n);else for(;t!==null;){if(t.tag===3){Hu(t,e,n);break}else if(t.tag===1){var r=t.stateNode;if(typeof t.type.getDerivedStateFromError==`function`||typeof r.componentDidCatch==`function`&&(tu===null||!tu.has(r))){e=pi(n,e),n=Ks(2),r=Fa(t,n,2),r!==null&&(qs(n,r,t,e),nt(r,2),nd(r));break}}t=t.return}}function Wu(e,t,n){var r=e.pingCache;if(r===null){r=e.pingCache=new Nl;var i=new Set;r.set(t,i)}else i=r.get(t),i===void 0&&(i=new Set,r.set(t,i));i.has(n)||(Bl=!0,i.add(n),e=Gu.bind(null,e,t,n),t.then(e,e))}function Gu(e,t,n){var r=e.pingCache;r!==null&&r.delete(t),e.pingedLanes|=e.suspendedLanes&n,e.warmLanes&=~n,Fl===e&&(Q&n)===n&&(Hl===4||Hl===3&&(Q&62914560)===Q&&300>M()-Zl?!(Pl&2)&&bu(e,0):Gl|=n,ql===Q&&(ql=0)),nd(e)}function Ku(e,t){t===0&&(t=et()),e=Qr(e,t),e!==null&&(nt(e,t),nd(e))}function qu(e){var t=e.memoizedState,n=0;t!==null&&(n=t.retryLane),Ku(e,n)}function Ju(e,t){var n=0;switch(e.tag){case 31:case 13:var r=e.stateNode,a=e.memoizedState;a!==null&&(n=a.retryLane);break;case 19:r=e.stateNode;break;case 22:r=e.stateNode._retryCache;break;default:throw Error(i(314))}r!==null&&r.delete(t),Ku(e,n)}function Yu(e,t){return Oe(e,t)}var Xu=null,Zu=null,Qu=!1,$u=!1,ed=!1,td=0;function nd(e){e!==Zu&&e.next===null&&(Zu===null?Xu=Zu=e:Zu=Zu.next=e),$u=!0,Qu||(Qu=!0,ld())}function rd(e,t){if(!ed&&$u){ed=!0;do for(var n=!1,r=Xu;r!==null;){if(!t)if(e!==0){var i=r.pendingLanes;if(i===0)var a=0;else{var o=r.suspendedLanes,s=r.pingedLanes;a=(1<<31-Ue(42|e)+1)-1,a&=i&~(o&~s),a=a&201326741?a&201326741|1:a?a|2:0}a!==0&&(n=!0,cd(r,a))}else a=Q,a=Ze(r,r===Fl?a:0,r.cancelPendingCommit!==null||r.timeoutHandle!==-1),!(a&3)||Qe(r,a)||(n=!0,cd(r,a));r=r.next}while(n);ed=!1}}function id(){ad()}function ad(){$u=Qu=!1;var e=0;td!==0&&Gd()&&(e=td);for(var t=M(),n=null,r=Xu;r!==null;){var i=r.next,a=od(r,t);a===0?(r.next=null,n===null?Xu=i:n.next=i,i===null&&(Zu=n)):(n=r,(e!==0||a&3)&&($u=!0)),r=i}nu!==0&&nu!==5||rd(e,!1),td!==0&&(td=0)}function od(e,t){for(var n=e.suspendedLanes,r=e.pingedLanes,i=e.expirationTimes,a=e.pendingLanes&-62914561;0s)break;var u=c.transferSize,d=c.initiatorType;u&&Id(d)&&(c=c.responseEnd,o+=u*(c`u`?null:document;function xf(e,t,n){var r=bf;if(r&&typeof t==`string`&&t){var i=Ht(t);i=`link[rel="`+e+`"][href="`+i+`"]`,typeof n==`string`&&(i+=`[crossorigin="`+n+`"]`),hf.has(i)||(hf.add(i),e={rel:e,crossOrigin:n,href:t},r.querySelector(i)===null&&(t=r.createElement(`link`),Pd(t,`link`,e),Ct(t),r.head.appendChild(t)))}}function Sf(e){_f.D(e),xf(`dns-prefetch`,e,null)}function Cf(e,t){_f.C(e,t),xf(`preconnect`,e,t)}function wf(e,t,n){_f.L(e,t,n);var r=bf;if(r&&e&&t){var i=`link[rel="preload"][as="`+Ht(t)+`"]`;t===`image`&&n&&n.imageSrcSet?(i+=`[imagesrcset="`+Ht(n.imageSrcSet)+`"]`,typeof n.imageSizes==`string`&&(i+=`[imagesizes="`+Ht(n.imageSizes)+`"]`)):i+=`[href="`+Ht(e)+`"]`;var a=i;switch(t){case`style`:a=Af(e);break;case`script`:a=Pf(e)}mf.has(a)||(e=h({rel:`preload`,href:t===`image`&&n&&n.imageSrcSet?void 0:e,as:t},n),mf.set(a,e),r.querySelector(i)!==null||t===`style`&&r.querySelector(jf(a))||t===`script`&&r.querySelector(Ff(a))||(t=r.createElement(`link`),Pd(t,`link`,e),Ct(t),r.head.appendChild(t)))}}function Tf(e,t){_f.m(e,t);var n=bf;if(n&&e){var r=t&&typeof t.as==`string`?t.as:`script`,i=`link[rel="modulepreload"][as="`+Ht(r)+`"][href="`+Ht(e)+`"]`,a=i;switch(r){case`audioworklet`:case`paintworklet`:case`serviceworker`:case`sharedworker`:case`worker`:case`script`:a=Pf(e)}if(!mf.has(a)&&(e=h({rel:`modulepreload`,href:e},t),mf.set(a,e),n.querySelector(i)===null)){switch(r){case`audioworklet`:case`paintworklet`:case`serviceworker`:case`sharedworker`:case`worker`:case`script`:if(n.querySelector(Ff(a)))return}r=n.createElement(`link`),Pd(r,`link`,e),Ct(r),n.head.appendChild(r)}}}function Ef(e,t,n){_f.S(e,t,n);var r=bf;if(r&&e){var i=St(r).hoistableStyles,a=Af(e);t||=`default`;var o=i.get(a);if(!o){var s={loading:0,preload:null};if(o=r.querySelector(jf(a)))s.loading=5;else{e=h({rel:`stylesheet`,href:e,"data-precedence":t},n),(n=mf.get(a))&&Rf(e,n);var c=o=r.createElement(`link`);Ct(c),Pd(c,`link`,e),c._p=new Promise(function(e,t){c.onload=e,c.onerror=t}),c.addEventListener(`load`,function(){s.loading|=1}),c.addEventListener(`error`,function(){s.loading|=2}),s.loading|=4,Lf(o,t,r)}o={type:`stylesheet`,instance:o,count:1,state:s},i.set(a,o)}}}function Df(e,t){_f.X(e,t);var n=bf;if(n&&e){var r=St(n).hoistableScripts,i=Pf(e),a=r.get(i);a||(a=n.querySelector(Ff(i)),a||(e=h({src:e,async:!0},t),(t=mf.get(i))&&zf(e,t),a=n.createElement(`script`),Ct(a),Pd(a,`link`,e),n.head.appendChild(a)),a={type:`script`,instance:a,count:1,state:null},r.set(i,a))}}function Of(e,t){_f.M(e,t);var n=bf;if(n&&e){var r=St(n).hoistableScripts,i=Pf(e),a=r.get(i);a||(a=n.querySelector(Ff(i)),a||(e=h({src:e,async:!0,type:`module`},t),(t=mf.get(i))&&zf(e,t),a=n.createElement(`script`),Ct(a),Pd(a,`link`,e),n.head.appendChild(a)),a={type:`script`,instance:a,count:1,state:null},r.set(i,a))}}function kf(e,t,n,r){var a=(a=he.current)?gf(a):null;if(!a)throw Error(i(446));switch(e){case`meta`:case`title`:return null;case`style`:return typeof n.precedence==`string`&&typeof n.href==`string`?(t=Af(n.href),n=St(a).hoistableStyles,r=n.get(t),r||(r={type:`style`,instance:null,count:0,state:null},n.set(t,r)),r):{type:`void`,instance:null,count:0,state:null};case`link`:if(n.rel===`stylesheet`&&typeof n.href==`string`&&typeof n.precedence==`string`){e=Af(n.href);var o=St(a).hoistableStyles,s=o.get(e);if(s||(a=a.ownerDocument||a,s={type:`stylesheet`,instance:null,count:0,state:{loading:0,preload:null}},o.set(e,s),(o=a.querySelector(jf(e)))&&!o._p&&(s.instance=o,s.state.loading=5),mf.has(e)||(n={rel:`preload`,as:`style`,href:n.href,crossOrigin:n.crossOrigin,integrity:n.integrity,media:n.media,hrefLang:n.hrefLang,referrerPolicy:n.referrerPolicy},mf.set(e,n),o||Nf(a,e,n,s.state))),t&&r===null)throw Error(i(528,``));return s}if(t&&r!==null)throw Error(i(529,``));return null;case`script`:return t=n.async,n=n.src,typeof n==`string`&&t&&typeof t!=`function`&&typeof t!=`symbol`?(t=Pf(n),n=St(a).hoistableScripts,r=n.get(t),r||(r={type:`script`,instance:null,count:0,state:null},n.set(t,r)),r):{type:`void`,instance:null,count:0,state:null};default:throw Error(i(444,e))}}function Af(e){return`href="`+Ht(e)+`"`}function jf(e){return`link[rel="stylesheet"][`+e+`]`}function Mf(e){return h({},e,{"data-precedence":e.precedence,precedence:null})}function Nf(e,t,n,r){e.querySelector(`link[rel="preload"][as="style"][`+t+`]`)?r.loading=1:(t=e.createElement(`link`),r.preload=t,t.addEventListener(`load`,function(){return r.loading|=1}),t.addEventListener(`error`,function(){return r.loading|=2}),Pd(t,`link`,n),Ct(t),e.head.appendChild(t))}function Pf(e){return`[src="`+Ht(e)+`"]`}function Ff(e){return`script[async]`+e}function If(e,t,n){if(t.count++,t.instance===null)switch(t.type){case`style`:var r=e.querySelector(`style[data-href~="`+Ht(n.href)+`"]`);if(r)return t.instance=r,Ct(r),r;var a=h({},n,{"data-href":n.href,"data-precedence":n.precedence,href:null,precedence:null});return r=(e.ownerDocument||e).createElement(`style`),Ct(r),Pd(r,`style`,a),Lf(r,n.precedence,e),t.instance=r;case`stylesheet`:a=Af(n.href);var o=e.querySelector(jf(a));if(o)return t.state.loading|=4,t.instance=o,Ct(o),o;r=Mf(n),(a=mf.get(a))&&Rf(r,a),o=(e.ownerDocument||e).createElement(`link`),Ct(o);var s=o;return s._p=new Promise(function(e,t){s.onload=e,s.onerror=t}),Pd(o,`link`,r),t.state.loading|=4,Lf(o,n.precedence,e),t.instance=o;case`script`:return o=Pf(n.src),(a=e.querySelector(Ff(o)))?(t.instance=a,Ct(a),a):(r=n,(a=mf.get(o))&&(r=h({},n),zf(r,a)),e=e.ownerDocument||e,a=e.createElement(`script`),Ct(a),Pd(a,`link`,r),e.head.appendChild(a),t.instance=a);case`void`:return null;default:throw Error(i(443,t.type))}else t.type===`stylesheet`&&!(t.state.loading&4)&&(r=t.instance,t.state.loading|=4,Lf(r,n.precedence,e));return t.instance}function Lf(e,t,n){for(var r=n.querySelectorAll(`link[rel="stylesheet"][data-precedence],style[data-precedence]`),i=r.length?r[r.length-1]:null,a=i,o=0;o title`):null)}function Uf(e,t,n){if(n===1||t.itemProp!=null)return!1;switch(e){case`meta`:case`title`:return!0;case`style`:if(typeof t.precedence!=`string`||typeof t.href!=`string`||t.href===``)break;return!0;case`link`:if(typeof t.rel!=`string`||typeof t.href!=`string`||t.href===``||t.onLoad||t.onError)break;switch(t.rel){case`stylesheet`:return e=t.disabled,typeof t.precedence==`string`&&e==null;default:return!0}case`script`:if(t.async&&typeof t.async!=`function`&&typeof t.async!=`symbol`&&!t.onLoad&&!t.onError&&t.src&&typeof t.src==`string`)return!0}return!1}function Wf(e){return!(e.type===`stylesheet`&&!(e.state.loading&3))}function Gf(e,t,n,r){if(n.type===`stylesheet`&&(typeof r.media!=`string`||!1!==matchMedia(r.media).matches)&&!(n.state.loading&4)){if(n.instance===null){var i=Af(r.href),a=t.querySelector(jf(i));if(a){t=a._p,typeof t==`object`&&t&&typeof t.then==`function`&&(e.count++,e=Jf.bind(e),t.then(e,e)),n.state.loading|=4,n.instance=a,Ct(a);return}a=t.ownerDocument||t,r=Mf(r),(i=mf.get(i))&&Rf(r,i),a=a.createElement(`link`),Ct(a);var o=a;o._p=new Promise(function(e,t){o.onload=e,o.onerror=t}),Pd(a,`link`,r),n.instance=a}e.stylesheets===null&&(e.stylesheets=new Map),e.stylesheets.set(n,t),(t=n.state.preload)&&!(n.state.loading&3)&&(e.count++,n=Jf.bind(e),t.addEventListener(`load`,n),t.addEventListener(`error`,n))}}var Kf=0;function qf(e,t){return e.stylesheets&&e.count===0&&Xf(e,e.stylesheets),0Kf?50:800)+t);return e.unsuspend=n,function(){e.unsuspend=null,clearTimeout(r),clearTimeout(i)}}:null}function Jf(){if(this.count--,this.count===0&&(this.imgCount===0||!this.waitingForImages)){if(this.stylesheets)Xf(this,this.stylesheets);else if(this.unsuspend){var e=this.unsuspend;this.unsuspend=null,e()}}}var Yf=null;function Xf(e,t){e.stylesheets=null,e.unsuspend!==null&&(e.count++,Yf=new Map,t.forEach(Zf,e),Yf=null,Jf.call(e))}function Zf(e,t){if(!(t.state.loading&4)){var n=Yf.get(e);if(n)var r=n.get(null);else{n=new Map,Yf.set(e,n);for(var i=e.querySelectorAll(`link[data-precedence],style[data-precedence]`),a=0;a{function n(){if(!(typeof __REACT_DEVTOOLS_GLOBAL_HOOK__>`u`||typeof __REACT_DEVTOOLS_GLOBAL_HOOK__.checkDCE!=`function`))try{__REACT_DEVTOOLS_GLOBAL_HOOK__.checkDCE(n)}catch(e){console.error(e)}}n(),t.exports=h()})),_=c(u(),1),v=g(),y=class{baseUrl;actorUserId;constructor(e){this.baseUrl=e.baseUrl.replace(/\/$/,``),this.actorUserId=e.actorUserId.trim()}async login(e){return this.post(`/auth/login`,{email:e.email,password:e.password,device_fingerprint:b(),device_label:e.deviceLabel,trust_device:e.trustDevice})}async refresh(e){return this.post(`/auth/refresh`,{refresh_token:e.refreshToken})}async getInstallationStatus(){return(await this.request(`/installation/status`,{method:`GET`})).installation}async bootstrapOwner(e){return this.post(`/installation/bootstrap-owner`,{email:e.email,password:e.password,activation_payload:e.activationPayload,activation_signature:e.activationSignature||``})}async revokeAuthSession(e){await this.post(`/auth/sessions/revoke`,{user_id:e.userId,auth_session_id:e.authSessionId,reason:e.reason})}async listClusters(){return(await this.get(`/clusters`)).clusters??[]}async createCluster(e){return(await this.post(`/clusters/`,{actor_user_id:this.actorUserId,slug:e.slug,name:e.name,region:e.region||null,metadata:{}})).cluster}async updateCluster(e,t){return(await this.put(`/clusters/${e}`,{actor_user_id:this.actorUserId,name:t.name,status:t.status,region:t.region||null,metadata:t.metadata||{}})).cluster}async listClusterSummaries(){return(await this.get(`/cluster-admin-summaries`)).cluster_summaries??[]}async getClusterAuthority(e){return(await this.get(`/clusters/${e}/authority`)).authority_state}async updateClusterAuthority(e,t){return(await this.put(`/clusters/${e}/authority`,{actor_user_id:this.actorUserId,authority_state:t.authorityState,mutation_mode:t.mutationMode,notes:t.notes||null})).authority_state}async listNodes(e){return(await this.get(`/clusters/${e}/nodes`)).nodes??[]}async listNodeGroups(e){return(await this.get(`/clusters/${e}/node-groups`)).node_groups??[]}async createNodeGroup(e,t){return(await this.post(`/clusters/${e}/node-groups`,{actor_user_id:this.actorUserId,parent_group_id:t.parentGroupId||null,name:t.name,description:t.description||null,sort_order:t.sortOrder||0,metadata:{}})).node_group}async assignNodeGroup(e,t,n){return(await this.put(`/clusters/${e}/nodes/${t}/group`,{actor_user_id:this.actorUserId,group_id:n||null})).node}async disableMembership(e,t,n){await this.post(`/clusters/${e}/nodes/${t}/membership/disable`,{actor_user_id:this.actorUserId,reason:n})}async attachExistingNode(e,t,n){return(await this.post(`/clusters/${e}/nodes/${t}/membership/attach`,{actor_user_id:this.actorUserId,roles:n})).node}async revokeNodeIdentity(e,t,n){await this.post(`/clusters/${e}/nodes/${t}/identity/revoke`,{actor_user_id:this.actorUserId,reason:n})}async deleteClusterNode(e,t,n){await this.delete(`/clusters/${e}/nodes/${t}`,{actor_user_id:this.actorUserId,reason:n})}async listJoinRequests(e){return(await this.get(`/clusters/${e}/join-requests`)).join_requests??[]}async createJoinToken(e,t){let n=new Date(Date.now()+Math.max(t.ttlHours,1)*60*60*1e3).toISOString();return(await this.post(`/clusters/${e}/join-tokens`,{actor_user_id:this.actorUserId,scope:t.scope,expires_at:n,max_uses:Math.max(t.maxUses,1)})).join_token}async listJoinTokens(e){return(await this.get(`/clusters/${e}/join-tokens`)).join_tokens??[]}async revokeJoinToken(e,t){return(await this.post(`/clusters/${e}/join-tokens/${t}/revoke`,{actor_user_id:this.actorUserId})).join_token}async approveJoinRequest(e,t){await this.post(`/clusters/${e}/join-requests/${t}/approve`,{actor_user_id:this.actorUserId,ownership_type:`platform_managed`})}async rejectJoinRequest(e,t,n){await this.post(`/clusters/${e}/join-requests/${t}/reject`,{actor_user_id:this.actorUserId,reason:n})}async listNodeRoles(e,t){return(await this.get(`/clusters/${e}/nodes/${t}/roles`)).role_assignments??[]}async assignRole(e,t,n,r){await this.setRoleStatus(e,t,n,`active`,r)}async setRoleStatus(e,t,n,r,i){await this.post(`/clusters/${e}/nodes/${t}/roles`,{actor_user_id:this.actorUserId,organization_id:i||null,role:n,status:r,policy:{}})}async listWorkloadStatuses(e,t){return(await this.get(`/clusters/${e}/nodes/${t}/workloads/status`)).workload_statuses??[]}async listDesiredWorkloads(e,t){return(await this.get(`/clusters/${e}/nodes/${t}/workloads/desired`)).desired_workloads??[]}async listNodeHeartbeats(e,t,n=100){return(await this.get(`/clusters/${e}/nodes/${t}/heartbeats?limit=${n}`)).heartbeats??[]}async listNodeTelemetry(e,t,n=120){return(await this.get(`/clusters/${e}/nodes/${t}/telemetry?limit=${n}`)).telemetry??[]}async listReleaseVersions(e,t=`rap-node-agent`,n=`dev`){let r=new URLSearchParams({product:t,channel:n});return(await this.get(`/clusters/${e}/updates/releases?${r.toString()}`)).release_versions??[]}async getNodeUpdatePlan(e,t,n){let r=new URLSearchParams({product:n.product||`rap-node-agent`,current_version:n.currentVersion||``,os:n.os||`linux`,arch:n.arch||`amd64`,install_type:n.installType||`docker`,channel:n.channel||`dev`});return(await this.get(`/clusters/${e}/nodes/${t}/updates/plan?${r.toString()}`)).node_update_plan}async upsertNodeUpdatePolicy(e,t,n){return(await this.put(`/clusters/${e}/nodes/${t}/updates/policy`,{actor_user_id:this.actorUserId,product:n.product,channel:n.channel||`dev`,target_version:n.targetVersion??null,strategy:n.strategy||`rolling`,enabled:n.enabled??!0,rollback_allowed:n.rollbackAllowed??!0,health_window_seconds:n.healthWindowSeconds||180})).node_update_policy}async listNodeUpdateStatuses(e,t,n=80){let r=new URLSearchParams({actor_user_id:this.actorUserId,limit:String(n)});return(await this.get(`/clusters/${e}/nodes/${t}/updates/statuses?${r.toString()}`)).node_update_statuses??[]}async listFabricTestingFlags(){return(await this.get(`/fabric/testing-flags`)).testing_flags??[]}async updateFabricTestingFlag(e){return(await this.put(`/fabric/testing-flags`,{actor_user_id:this.actorUserId,scope_type:e.scopeType,scope_id:e.scopeId||null,cluster_id:e.clusterId||null,enabled:e.enabled,telemetry_enabled:e.telemetryEnabled,synthetic_links_enabled:e.syntheticLinksEnabled,history_retention_hours:e.historyRetentionHours,metadata:e.metadata||{}})).testing_flag}async setDesiredWorkload(e,t,n,r){await this.put(`/clusters/${e}/nodes/${t}/workloads/${n}/desired`,{actor_user_id:this.actorUserId,desired_state:r.desiredState,version:r.version||null,runtime_mode:r.runtimeMode,artifact_ref:null,config:r.config,environment:r.environment})}async listMeshLinks(e){return(await this.get(`/clusters/${e}/mesh/links`)).mesh_links??[]}async listRouteIntents(e){let t=new URLSearchParams({actor_user_id:this.actorUserId});return(await this.get(`/clusters/${e}/mesh/route-intents?${t.toString()}`)).route_intents??[]}async expireRouteIntent(e,t,n){return(await this.post(`/clusters/${e}/mesh/route-intents/${t}/expire`,{actor_user_id:this.actorUserId,reason:n})).route_intent}async disableRouteIntent(e,t,n){return(await this.post(`/clusters/${e}/mesh/route-intents/${t}/disable`,{actor_user_id:this.actorUserId,reason:n})).route_intent}async getNodeSyntheticMeshConfig(e,t){return(await this.get(`/clusters/${e}/nodes/${t}/mesh/synthetic-config`)).synthetic_mesh_config}async listFabricServiceChannelRouteFeedback(e,t={}){let n=new URLSearchParams({actor_user_id:this.actorUserId});return t.reporterNodeId&&n.set(`reporter_node_id`,t.reporterNodeId),t.routeId&&n.set(`route_id`,t.routeId),t.serviceClass&&n.set(`service_class`,t.serviceClass),t.feedbackStatus&&n.set(`feedback_status`,t.feedbackStatus),t.includeExpired&&n.set(`include_expired`,`true`),(await this.get(`/clusters/${e}/fabric/service-channels/route-feedback?${n.toString()}`)).route_feedback??[]}async expireFabricServiceChannelRouteFeedback(e,t){return(await this.post(`/clusters/${e}/fabric/service-channels/route-feedback/expire`,{actor_user_id:this.actorUserId,route_id:t.routeId,reporter_node_id:t.reporterNodeId||``,service_class:t.serviceClass||``,reason:t.reason||`expired from admin fabric diagnostics`})).route_feedback_expire}async listFabricServiceChannelRouteRebuildAttempts(e,t={}){let n=new URLSearchParams({actor_user_id:this.actorUserId});return t.reporterNodeId&&n.set(`reporter_node_id`,t.reporterNodeId),t.routeId&&n.set(`route_id`,t.routeId),t.replacementRouteId&&n.set(`replacement_route_id`,t.replacementRouteId),t.serviceClass&&n.set(`service_class`,t.serviceClass),t.rebuildStatus&&n.set(`rebuild_status`,t.rebuildStatus),t.rebuildRequestId&&n.set(`rebuild_request_id`,t.rebuildRequestId),t.generation&&n.set(`generation`,t.generation),t.feedbackSource&&n.set(`feedback_source`,t.feedbackSource),t.feedbackChannelId&&n.set(`feedback_channel_id`,t.feedbackChannelId),t.feedbackViolationStatus&&n.set(`feedback_violation_status`,t.feedbackViolationStatus),t.enrichment&&n.set(`enrichment`,t.enrichment),t.limit&&n.set(`limit`,String(t.limit)),t.offset&&n.set(`offset`,String(t.offset)),(await this.get(`/clusters/${e}/fabric/service-channels/rebuild-attempts?${n.toString()}`)).rebuild_attempts??[]}async getFabricServiceChannelRouteRebuildHealthSummary(e,t={}){let n=new URLSearchParams({actor_user_id:this.actorUserId});return t.limit&&n.set(`limit`,String(t.limit)),(await this.get(`/clusters/${e}/fabric/service-channels/rebuild-health?${n.toString()}`)).rebuild_health}async getFabricServiceChannelReadiness(e,t={}){let n=new URLSearchParams({actor_user_id:this.actorUserId});return t.limit&&n.set(`limit`,String(t.limit)),(await this.get(`/clusters/${e}/fabric/service-channels/readiness?${n.toString()}`)).fabric_service_channel_readiness}async getFabricServiceChannelSchemaStatus(e){let t=new URLSearchParams({actor_user_id:this.actorUserId});return(await this.get(`/clusters/${e}/fabric/service-channels/schema-status?${t.toString()}`)).fabric_service_channel_schema_status}async getFabricServiceChannelRebuildSnapshotMaintenanceHealth(e,t={}){let n=new URLSearchParams({actor_user_id:this.actorUserId});return t.limit&&n.set(`limit`,String(t.limit)),t.minAgeSeconds&&n.set(`min_age_seconds`,String(t.minAgeSeconds)),t.heartbeatThreshold&&n.set(`heartbeat_threshold`,String(t.heartbeatThreshold)),(await this.get(`/clusters/${e}/fabric/service-channels/rebuild-snapshots/health?${n.toString()}`)).rebuild_snapshot_health}async warmupFabricServiceChannelRebuildSnapshots(e,t={}){return(await this.post(`/clusters/${e}/fabric/service-channels/rebuild-snapshots/warmup`,{actor_user_id:this.actorUserId,limit:t.limit||10,stale_after_seconds:t.staleAfterSeconds||60})).rebuild_snapshot_warmup}async getFabricServiceChannelLeaseMaintenance(e,t={}){let n=new URLSearchParams({actor_user_id:this.actorUserId});return t.limit&&n.set(`limit`,String(t.limit)),t.includeExpired&&n.set(`include_expired`,`true`),(await this.get(`/clusters/${e}/fabric/service-channels/leases?${n.toString()}`)).fabric_service_channel_lease_maintenance}async cleanupFabricServiceChannelLeases(e,t={}){return(await this.post(`/clusters/${e}/fabric/service-channels/leases/cleanup`,{actor_user_id:this.actorUserId,limit:t.limit||100})).fabric_service_channel_lease_maintenance}async getFabricServiceChannelAccessTelemetry(e,t={}){let n=new URLSearchParams({actor_user_id:this.actorUserId});return t.limit&&n.set(`limit`,String(t.limit)),(await this.get(`/clusters/${e}/fabric/service-channels/access-telemetry?${n.toString()}`)).fabric_service_channel_access_telemetry}async listFabricServiceChannelRouteRebuildIncidents(e,t={}){let n=new URLSearchParams({actor_user_id:this.actorUserId});return t.limit&&n.set(`limit`,String(t.limit)),(await this.get(`/clusters/${e}/fabric/service-channels/rebuild-incidents?${n.toString()}`)).rebuild_incidents??[]}async getFabricServiceChannelRebuildInvestigationBreadcrumbs(e,t={}){let n=new URLSearchParams({actor_user_id:this.actorUserId});return t.limit&&n.set(`limit`,String(t.limit)),t.currentWindowSeconds&&n.set(`current_window_seconds`,String(t.currentWindowSeconds)),t.historyWindowSeconds&&n.set(`history_window_seconds`,String(t.historyWindowSeconds)),(await this.get(`/clusters/${e}/fabric/service-channels/rebuild-investigations/breadcrumbs?${n.toString()}`)).rebuild_investigation_breadcrumbs}async recordFabricServiceChannelRouteRebuildInvestigation(e,t){await this.post(`/clusters/${e}/fabric/service-channels/rebuild-incidents/investigations`,{actor_user_id:this.actorUserId,reporter_node_id:t.reporterNodeId,route_id:t.routeId,service_class:t.serviceClass||``,generation:t.generation||``,guard_status:t.guardStatus||``,incident_id:t.incidentId||``,feedback_source:t.feedbackSource||``,feedback_channel_id:t.feedbackChannelId||``,feedback_violation_status:t.feedbackViolationStatus||``,drilldown_source:t.drilldownSource||``,reason:t.reason||`operator opened deep rebuild ledger`})}async silenceFabricServiceChannelRouteRebuildAlert(e,t){return(await this.post(`/clusters/${e}/fabric/service-channels/rebuild-health/silences`,{actor_user_id:this.actorUserId,incident_source:t.incidentSource||``,channel_id:t.channelId||``,reporter_node_id:t.reporterNodeId,route_id:t.routeId,guard_status:t.guardStatus,generation:t.generation||``,reason:t.reason||`operator acknowledged rebuild alert`,ttl_seconds:t.ttlSeconds||21600})).rebuild_alert_silence}async listFabricServiceChannelRouteRebuildAlertSilences(e){let t=new URLSearchParams({actor_user_id:this.actorUserId});return(await this.get(`/clusters/${e}/fabric/service-channels/rebuild-health/silences?${t.toString()}`)).rebuild_alert_silences??[]}async unsilenceFabricServiceChannelRouteRebuildAlert(e,t,n){return(await this.delete(`/clusters/${e}/fabric/service-channels/rebuild-health/silences/${encodeURIComponent(t)}`,{actor_user_id:this.actorUserId,reason:n||`operator removed rebuild alert silence`})).rebuild_alert_silence}async getFabricServiceChannelRecoveryPolicy(e){let t=new URLSearchParams({actor_user_id:this.actorUserId});return(await this.get(`/clusters/${e}/fabric/service-channels/recovery-policy?${t.toString()}`)).fabric_service_channel_recovery_policy}async updateFabricServiceChannelRecoveryPolicy(e,t){return(await this.put(`/clusters/${e}/fabric/service-channels/recovery-policy`,{actor_user_id:this.actorUserId,hysteresis_penalty:t.hysteresisPenalty,promotion_min_samples:t.promotionMinSamples,demotion_failure_threshold:t.demotionFailureThreshold,demotion_drop_threshold:t.demotionDropThreshold,demotion_slow_threshold:t.demotionSlowThreshold,demotion_rebuild_enabled:t.demotionRebuildEnabled,demotion_fenced_enabled:t.demotionFencedEnabled})).fabric_service_channel_recovery_policy}async getFabricServiceChannelAdaptivePolicy(e){let t=new URLSearchParams({actor_user_id:this.actorUserId});return(await this.get(`/clusters/${e}/fabric/service-channels/adaptive-policy?${t.toString()}`)).fabric_service_channel_adaptive_policy}async updateFabricServiceChannelAdaptivePolicy(e,t){return(await this.put(`/clusters/${e}/fabric/service-channels/adaptive-policy`,{actor_user_id:this.actorUserId,max_parallel_window:t.maxParallelWindow,bulk_pressure_channel_threshold:t.bulkPressureChannelThreshold,queue_pressure_high_watermark:t.queuePressureHighWatermark,queue_pressure_max_in_flight:t.queuePressureMaxInFlight,class_windows:t.classWindows})).fabric_service_channel_adaptive_policy}async getFabricServiceChannelPoolPolicy(e){let t=new URLSearchParams({actor_user_id:this.actorUserId});return(await this.get(`/clusters/${e}/fabric/service-channels/pool-policy?${t.toString()}`)).fabric_service_channel_pool_policy}async updateFabricServiceChannelPoolPolicy(e,t){return(await this.put(`/clusters/${e}/fabric/service-channels/pool-policy`,{actor_user_id:this.actorUserId,entry_pool_node_ids:t.entryPoolNodeIds,exit_pool_node_ids:t.exitPoolNodeIds,preferred_entry_node_id:t.preferredEntryNodeId,preferred_exit_node_id:t.preferredExitNodeId,selection_strategy:t.selectionStrategy,route_rebuild:t.routeRebuild,entry_failover:t.entryFailover,exit_failover:t.exitFailover,backend_fallback_allowed:t.backendFallbackAllowed,sticky_session:t.stickySession})).fabric_service_channel_pool_policy}async getFabricServiceChannelBreadcrumbWindowPolicy(e){let t=new URLSearchParams({actor_user_id:this.actorUserId});return(await this.get(`/clusters/${e}/fabric/service-channels/breadcrumb-window-policy?${t.toString()}`)).fabric_service_channel_breadcrumb_window_policy}async updateFabricServiceChannelBreadcrumbWindowPolicy(e,t){return(await this.put(`/clusters/${e}/fabric/service-channels/breadcrumb-window-policy`,{actor_user_id:this.actorUserId,current_window_seconds:t.currentWindowSeconds,history_window_seconds:t.historyWindowSeconds})).fabric_service_channel_breadcrumb_window_policy}async listQoSPolicies(e){return(await this.get(`/clusters/${e}/mesh/qos-policies`)).qos_policies??[]}async listFabricEntryPoints(e){return(await this.get(`/clusters/${e}/fabric/entry-points`)).entry_points??[]}async createFabricEntryPoint(e,t){return(await this.post(`/clusters/${e}/fabric/entry-points`,{actor_user_id:this.actorUserId,name:t.name,status:`active`,endpoint_type:t.endpointType||`client_access`,public_endpoint:t.publicEndpoint||null,policy:t.policy||{},metadata:t.metadata||{}})).entry_point}async listFabricEntryPointNodes(e,t){return(await this.get(`/clusters/${e}/fabric/entry-points/${t}/nodes`)).entry_point_nodes??[]}async setFabricEntryPointNode(e,t,n,r={}){return(await this.put(`/clusters/${e}/fabric/entry-points/${t}/nodes/${n}`,{actor_user_id:this.actorUserId,status:r.status||`active`,priority:r.priority||100,metadata:r.metadata||{}})).entry_point_node}async listFabricEgressPools(e){return(await this.get(`/clusters/${e}/fabric/egress-pools`)).egress_pools??[]}async createFabricEgressPool(e,t){return(await this.post(`/clusters/${e}/fabric/egress-pools`,{actor_user_id:this.actorUserId,name:t.name,status:`active`,description:t.description||null,route_scope:t.routeScope||{},policy:t.policy||{},metadata:t.metadata||{}})).egress_pool}async listFabricEgressPoolNodes(e,t){return(await this.get(`/clusters/${e}/fabric/egress-pools/${t}/nodes`)).egress_pool_nodes??[]}async setFabricEgressPoolNode(e,t,n,r={}){return(await this.put(`/clusters/${e}/fabric/egress-pools/${t}/nodes/${n}`,{actor_user_id:this.actorUserId,status:r.status||`active`,priority:r.priority||100,metadata:r.metadata||{}})).egress_pool_node}async listVPNConnections(e){return(await this.get(`/clusters/${e}/vpn-connections`)).vpn_connections??[]}async createVPNConnection(e,t){return(await this.post(`/clusters/${e}/vpn-connections`,{actor_user_id:this.actorUserId,organization_id:t.organizationId,name:t.name,target_endpoint:t.targetEndpoint,protocol_family:t.protocolFamily,credential_ref:t.credentialRef||null,mode:`single_active`,desired_state:t.desiredState,allowed_node_policy:t.allowedNodePolicy,routing_usage:t.routingUsage,route_policy:t.routePolicy,qos_policy:t.qosPolicy,placement_policy:t.placementPolicy,metadata:{}})).vpn_connection}async updateVPNConnectionDesiredState(e,t,n){return(await this.put(`/clusters/${e}/vpn-connections/${t}/desired-state`,{actor_user_id:this.actorUserId,desired_state:n})).vpn_connection}async getActiveVPNLease(e,t){try{return(await this.get(`/clusters/${e}/vpn-connections/${t}/leases/active`)).lease}catch{return null}}async getVPNPacketStats(e,t){return(await this.get(`/clusters/${e}/vpn-connections/${t}/tunnel/stats`)).vpn_packet_stats??{}}async getVPNClientDiagnosticStatus(e,t){if(!t.trim())return null;try{return(await this.get(`/clusters/${e}/vpn/client-diagnostics/${encodeURIComponent(t.trim())}/status`)).vpn_client_diagnostic_status??null}catch{return null}}async listVPNClientDiagnosticStatuses(e){return(await this.get(`/clusters/${e}/vpn/client-diagnostics`)).vpn_client_diagnostic_statuses??[]}async enqueueVPNClientDiagnosticCommand(e,t,n){return(await this.post(`/clusters/${e}/vpn/client-diagnostics/${encodeURIComponent(t.trim())}/commands`,n)).vpn_client_diagnostic_command}async expireStaleVPNLeases(e){return(await this.post(`/clusters/${e}/vpn-connection-leases/expire-stale`,{actor_user_id:this.actorUserId})).expired_leases??[]}async listAudit(e,t={}){return(await this.listAuditDetailed(e,t)).events}async listAuditDetailed(e,t={}){let n=new URLSearchParams({limit:String(t.limit||100)});for(let e of t.eventTypes||[])e&&n.append(`event_type`,e);for(let e of t.targetTypes||[])e&&n.append(`target_type`,e);t.correlation&&n.set(`correlation`,t.correlation);let r=await this.get(`/clusters/${e}/audit?${n.toString()}`);return{events:r.audit_events??[],summary:r.audit_summary}}clusterEventsURL(e){return`${this.baseUrl}/clusters/${encodeURIComponent(e)}/events?actor_user_id=${encodeURIComponent(this.actorUserId)}`}async getOrganizationAdminSummary(e){return(await this.get(`/organizations/${e}/admin-summary`)).admin_summary}async listOrganizations(){return(await this.request(`/organizations?user_id=${encodeURIComponent(this.actorUserId)}`,{method:`GET`})).organizations??[]}async createOrganization(e){return(await this.post(`/organizations/`,{actor_user_id:this.actorUserId,slug:e.slug,name:e.name,metadata:e.metadata||{}})).organization}async listUsers(){return(await this.get(`/users/`)).users??[]}async createUser(e){return(await this.post(`/users/`,{actor_user_id:this.actorUserId,email:e.email,password:e.password,platform_role:e.platformRole||`user`})).user}async listOrganizationMemberships(e){return(await this.request(`/organizations/${e}/memberships?user_id=${encodeURIComponent(this.actorUserId)}`,{method:`GET`})).memberships??[]}async addOrganizationMembership(e,t){return(await this.post(`/organizations/${e}/memberships`,{actor_user_id:this.actorUserId,user_id:t.userId,role_id:t.roleId})).membership}async listResources(e){let t=new URLSearchParams({user_id:this.actorUserId});return e&&t.set(`organization_id`,e),(await this.request(`/resources?${t.toString()}`,{method:`GET`})).resources??[]}async createResource(e){return(await this.post(`/resources/`,{actor_user_id:this.actorUserId,organization_id:e.organizationId,name:e.name,address:e.address,protocol:e.protocol||`rdp`,secret_ref:e.secretRef||null,certificate_verification_mode:e.certificateVerificationMode||`strict`,render_quality_profile:e.renderQualityProfile||`balanced`,clipboard_mode:e.clipboardMode||`disabled`,file_transfer_mode:e.fileTransferMode||`disabled`,metadata:e.metadata||{}})).resource}async upsertResourceSecret(e,t){await this.put(`/resources/${e}/secret`,{actor_user_id:this.actorUserId,payload:{username:t.username||``,password:t.password||``,domain:t.domain||``},metadata:{source:`web-admin`}})}async get(e){let t=e.includes(`?`)?`&`:`?`;return this.request(`${e}${t}actor_user_id=${encodeURIComponent(this.actorUserId)}`,{method:`GET`})}async post(e,t){return this.request(e,{method:`POST`,headers:{"Content-Type":`application/json`},body:JSON.stringify(t)})}async put(e,t){return this.request(e,{method:`PUT`,headers:{"Content-Type":`application/json`},body:JSON.stringify(t)})}async delete(e,t){return this.request(e,{method:`DELETE`,headers:{"Content-Type":`application/json`},body:JSON.stringify(t)})}async request(e,t){let n=await fetch(`${this.baseUrl}${e}`,t);if(!n.ok){let e=`Запрос завершился ошибкой HTTP ${n.status}`;try{let t=await n.json();e=t.error?.fallback_message||t.error?.code||e}catch{}throw Error(e)}return await n.json()}};function b(){let e=`rap.webAdmin.deviceFingerprint`,t=localStorage.getItem(e);if(t)return t;let n=`web-admin-${x()}`;return localStorage.setItem(e,n),n}function x(){if(typeof globalThis.crypto?.randomUUID==`function`)return globalThis.crypto.randomUUID();if(typeof globalThis.crypto?.getRandomValues==`function`){let e=new Uint8Array(16);globalThis.crypto.getRandomValues(e),e[6]=e[6]&15|64,e[8]=e[8]&63|128;let t=Array.from(e,e=>e.toString(16).padStart(2,`0`)).join(``);return`${t.slice(0,8)}-${t.slice(8,12)}-${t.slice(12,16)}-${t.slice(16,20)}-${t.slice(20)}`}return`${Date.now().toString(36)}-${Math.random().toString(36).slice(2)}`}var ee=o((e=>{var t=Symbol.for(`react.transitional.element`),n=Symbol.for(`react.fragment`);function r(e,n,r){var i=null;if(r!==void 0&&(i=``+r),n.key!==void 0&&(i=``+n.key),`key`in n)for(var a in r={},n)a!==`key`&&(r[a]=n[a]);else r=n;return n=r.ref,{$$typeof:t,type:e,key:i,ref:n===void 0?null:n,props:r}}e.Fragment=n,e.jsx=r,e.jsxs=r})),S=o(((e,t)=>{t.exports=ee()}))(),C={baseUrl:`rap.webAdmin.baseUrl`,actorUserId:`rap.webAdmin.actorUserId`,auth:`rap.webAdmin.auth`,language:`rap.webAdmin.language`,vpnDiagnosticDeviceId:`rap.webAdmin.vpnDiagnosticDeviceId`},te=`/api/v1`,ne=`http://192.168.200.61:8080/api/v1`,re={reporterNodeId:``,routeId:``,serviceClass:``,generation:``,feedbackSource:``,feedbackChannelId:``,feedbackViolationStatus:``,offset:0},ie=[`entry-node`,`relay-node`,`core-mesh`,`rdp-worker`,`vnc-worker`,`vpn-exit`,`vpn-connector`,`file-storage-cache`,`update-cache`,`video-relay`],w={"entry-node":`Entry node`,"relay-node":`Relay node`,"core-mesh":`Mesh core`,"rdp-worker":`RDP worker`,"vnc-worker":`VNC worker`,"vpn-exit":`VPN exit`,"vpn-connector":`VPN connector`,"file-storage-cache":`File/cache storage`,"update-cache":`Update cache`,"video-relay":`Video relay`},ae={"entry-node":[`can_accept_client_ingress`],"relay-node":[`mesh_rendezvous_relay_control_contract`,`mesh_peer_connection_manager`],"core-mesh":[`native_node_agent`,`mesh_peer_connection_manager`,`mesh_listener_diagnostics`],"rdp-worker":[`can_run_rdp_worker`],"vnc-worker":[`can_run_vnc_worker`],"vpn-exit":[`can_run_vpn_exit`],"vpn-connector":[`can_run_vpn_connector`],"file-storage-cache":[`can_run_file_cache`],"update-cache":[`can_run_update_cache`],"video-relay":[`can_run_video_relay`]},oe=[{id:`command`,ru:`Обзор`,en:`Command`},{id:`clusters`,ru:`Кластеры`,en:`Clusters`},{id:`cluster-settings`,ru:`Настройки кластера`,en:`Cluster Settings`},{id:`nodes`,ru:`Узлы`,en:`Nodes`},{id:`enrollment`,ru:`Новый узел`,en:`New Node`},{id:`roles`,ru:`Роли`,en:`Roles`},{id:`workloads`,ru:`Сервисы`,en:`Workloads`},{id:`fabric`,ru:`Связи Fabric`,en:`Fabric Links`},{id:`vpn`,ru:`VPN Control`,en:`VPN Control`},{id:`servers`,ru:`Серверы`,en:`Servers`},{id:`org-safe`,ru:`Организации`,en:`Organizations`},{id:`audit`,ru:`Аудит`,en:`Audit`}];function T(e){if(!e||typeof e!=`object`)return null;let t=e;return typeof t.userId!=`string`||typeof t.email!=`string`||typeof t.authSessionId!=`string`||typeof t.accessToken!=`string`||typeof t.refreshToken!=`string`||typeof t.accessTokenExpiresAt!=`string`||typeof t.refreshTokenExpiresAt!=`string`||!t.userId||!t.refreshToken?null:{userId:t.userId,email:t.email,authSessionId:t.authSessionId,accessToken:t.accessToken,refreshToken:t.refreshToken,accessTokenExpiresAt:t.accessTokenExpiresAt,refreshTokenExpiresAt:t.refreshTokenExpiresAt}}function se(e){let t=Date.parse(e);return!Number.isFinite(t)||t<=Date.now()}function ce(){try{let e=localStorage.getItem(C.auth);if(!e)return null;let t=T(JSON.parse(e));return!t||se(t.refreshTokenExpiresAt)?null:t}catch{return null}}var le={ttlHours:24,maxUses:1,roles:[`core-mesh`],nodeName:``,nodeGroupId:``,ownershipType:`platform_managed`,purpose:``,installMode:`docker`,dockerImage:`rap-node-agent:dev-enrollment-bootstrap-smoke`,dockerContainerName:``,dockerNetwork:`host`,windowsStartupMode:`auto`,windowsInstallDir:``,windowsNodeAgentSHA256:``,linuxInstallDir:``,linuxNodeAgentSHA256:``,meshListenAddr:`:19131`,meshListenPortMode:`auto`,meshListenAutoPortStart:19131,meshListenAutoPortEnd:19231,meshAdvertiseEndpoint:``,meshAdvertiseTransport:`direct_http`,meshConnectivityMode:`private_lan`,meshNATType:`none`,meshRegion:`docker-test`,controlPlaneEndpoint:``,artifactEndpoints:``,dockerImageArtifactSHA256:``,pullImage:!1,replace:!0,syntheticRuntime:!0},E={ru:{productOwner:`Владелец продукта`,controlPlane:`Панель управления`,sideText:`Главная панель владельца платформы для кластеров, узлов, доверия, ролей и безопасного desired state.`,signInTitle:`Вход`,signInText:`Введите учетные данные.`,bootstrapTitle:`Первый владелец`,bootstrapText:`Пустая установка принимает только подписанную активацию продукта.`,activationPayload:`Activation manifest JSON`,activationSignature:`Подпись manifest`,createOwner:`Создать владельца`,creatingOwner:`Создание...`,ownerCreated:`Владелец создан. Теперь можно войти.`,installationLocked:`Установка уже активирована`,insecureBootstrapDisabled:`Insecure bootstrap выключен. Нужна strict-активация с ключом продукта.`,email:`Логин`,password:`Пароль`,backendApi:`Backend API`,useLocalProxy:`Использовать локальный /api/v1 proxy`,language:`Язык`,deviceLabel:`Устройство`,rememberMe:`Запомнить меня`,trustDevice:`Доверять этому устройству`,signIn:`Войти`,signingIn:`Вход...`,logout:`Выйти`,profile:`Профиль`,refresh:`Обновить`,refreshing:`Обновление...`,autoRefresh:`Автообновление`,lastRefresh:`Данные обновлены`,activeCluster:`Активный кластер`,slugLabel:`Технический код`,slugHelp:`Slug — постоянный короткий технический идентификатор кластера для URL, скриптов, логов и интеграций. Его лучше не менять после создания.`,clusterCatalog:`Каталог кластеров`,clusterCatalogText:`Список реальных кластеров из Control Plane. Выберите активный кластер или раскройте карточку для подробностей.`,makeActive:`Сделать активным`,openSettings:`Открыть настройки`,selected:`Выбран`,createCluster:`Создать кластер`,clusterDetails:`Подробнее`,consoleTitle:`Панель владельца платформы`,boundary:`WEB является только представлением. Решения кластера проходят через Control Plane API, PostgreSQL как source of truth и аудит.`,noLoginError:`Войдите как владелец продукта или администратор платформы, чтобы загрузить панель.`,accessDenied:`Доступ к этой панели запрещен.`,sessionMode:`Режим сессии`,sessionModeAdmin:`Админ`,sessionModeUser:`Пользователь`,sessionRefreshedAt:`Сессия обновлена`,emptyLiveTitle:`Кластер пока пустой`,emptyLiveText:`Это реальные данные, не заглушка: в выбранном кластере ещё нет одобренных node-agent узлов. Создайте join token, запустите rap-node-agent и подтвердите join request.`,realDataNote:`Показываются только данные из PostgreSQL/Control Plane. Если значения нулевые, значит соответствующих узлов, ролей или сервисов пока нет.`,signedInAs:`Вход выполнен`,actorUser:`Actor user`,testMode:`Тестирование`,testModeText:`Включает тестовую телеметрию и синтетические наблюдения связей. Это не production mesh runtime.`,platformTestFlag:`Тестирование сервера`,nodeTelemetry:`Телеметрия узла`,heartbeatHistory:`История heartbeat`,noTelemetry:`Телеметрии пока нет`,enableTelemetry:`Включить телеметрию`,enableSyntheticLinks:`Включить тестовые связи`,saveTestFlag:`Сохранить флаг`,nodeManagement:`Управление узлом`,nodeScope:`Область просмотра`,currentClusterNodes:`Узлы активного кластера`,allNodes:`Все узлы платформы`,showAllPlatformNodes:`Показать все узлы платформы`,currentClusterMembership:`Участие в активном кластере`,clusterMemberships:`Участие по кластерам`,notMemberOfActiveCluster:`не состоит`,nodeIdentity:`Физическая идентичность узла`,activeClusterScope:`Область активного кластера`,activeClusterScopeText:`Один физический узел может состоять в нескольких кластерах. Роли и desired-сервисы ниже относятся только к выбранному активному кластеру.`,capabilityConfirmed:`способность подтверждена heartbeat`,capabilityMissing:`способность не заявлена узлом`,capabilityUnknown:`способность не подтверждена: нет heartbeat`,nodeGlobalInventoryText:`Один физический узел показан один раз. Участие и роли остаются кластерными: в разных кластерах этот же узел может иметь разные назначения.`,nodeSearch:`Поиск узлов`,groupNodesBy:`Группировать`,groupByMembership:`по участию`,groupByHealth:`по здоровью`,groupByOwnership:`по владению`,groupByClusterCount:`по числу кластеров`,nodeGroups:`Группы узлов`,nodeGroupTree:`Дерево групп`,nodeGroupFilter:`Фильтр по группе`,allNodeGroups:`Все группы`,nodeGroupCreatePanel:`Создание группы`,nodeGroupName:`Название группы`,parentNodeGroup:`Родительская группа`,rootNodeGroup:`Корень`,ungroupedNodes:`Без группы`,createNodeGroup:`Создать группу`,createSubgroup:`Создать подгруппу`,collapseGroup:`Свернуть`,expandGroup:`Развернуть`,assignNodeGroup:`Переместить в группу`,removeFromNodeGroup:`Убрать из группы`,connectExistingNode:`Подключить к активному кластеру`,connectExistingNodeTitle:`Подключить существующий узел`,connectExistingNodeText:`Будет создано или повторно включено участие конкретного физического узла в активном кластере. Роли ниже назначаются только в этом кластере.`,connectWithRoles:`Подключить с ролями`,nodeDetails:`Сведения`,manageNode:`Настроить`,nodeFunctions:`Функции узла`,nodeFunctionsText:`Одна строка управляет функцией целиком: роль задает разрешение в активном кластере, desired-сервис задает запуск, observed показывает факт от node-agent.`,rolePermission:`Разрешение`,permissionGranted:`разрешено`,permissionDenied:`нет разрешения`,organizationScopeForEnable:`Область организации для новых включений, опционально`,clusterWideRolePlaceholder:`пусто = роль на весь кластер`,desiredRuntime:`Желаемое состояние`,observedRuntime:`Фактически`,enableFunction:`Включить функцию`,disableFunction:`Выключить функцию`,close:`Закрыть`,nodeBriefList:`Краткий список узлов`,noActiveClusterMembership:`Узел не входит в активный кластер`,nodeBriefListHelp:`Список сгруппирован деревом активного кластера. Полные сведения, управление, роли, сервисы и статистика открываются из строки узла.`,nodeSearchPlaceholder:`имя, ключ, кластер, статус`,nodeGroupInventoryText:`Группы — это кластерная инвентарная структура. Перенос узла в группу меняет только его размещение внутри активного кластера, не роли и не членство.`,nodeGroupCreated:`Группа узлов создана.`,noNodesTitle:`Нет узлов`,noNodesByFilter:`По текущему фильтру узлы не найдены.`,cancel:`Отмена`,alreadyMember:`Уже в активном кластере`,revokedMembership:`Участие отозвано`,addNode:`Подключить узел`,addNodeText:`Подключение существующего физического узла к активному кластеру выполняется из списка узлов: включите общий режим и нажмите «Подключить к активному кластеру».`,joinTokenTitle:`Создать новый Docker-узел`,joinTokenText:`Сначала создается одноразовый install token и Docker install profile. Затем команда запускается на Docker-хосте, агент отправляет заявку, а владелец платформы подтверждает ее.`,ttlHours:`Срок действия, часов`,ttlHelp:`Через это время token станет недействительным, даже если им никто не воспользовался. Для ручного подключения обычно достаточно 1–24 часов.`,maxUses:`Максимум использований`,maxUsesHelp:`Сколько node-agent смогут использовать этот token. Самый безопасный вариант — 1 token на 1 новый узел.`,tokenPurpose:`Назначение token`,nodeOwnership:`Тип владения узлом`,suggestedRoles:`Разрешенные/ожидаемые роли`,generatedScope:`Сгенерированная область действия`,generatedScopeHelp:`JSON формируется автоматически из настроек выше. Оператор не должен писать его руками, чтобы не ошибиться синтаксисом или областью доступа.`,manualApprovalRequired:`Подтверждение заявки вручную обязательно`,nodeRoles:`Роли узла`,desiredServices:`Желаемые сервисы`,observedServices:`Наблюдаемые сервисы`,noRoles:`Ролей пока нет`,noServices:`Сервисов пока нет`,manageInCluster:`Управлять в кластере`,rolesAndServices:`Роли и сервисы`,links:`Связи`,fabricMap:`Карта трафика Fabric`,fabricIngressLayer:`Входы`,fabricNodeLayer:`Узлы кластера`,fabricEgressLayer:`Выходные зоны`,observedPeerLinks:`Наблюдаемые связи`,placementIntent:`control-plane назначение`,fabricEntryPoints:`Точки входа`,fabricEntryPointHelp:`Логические внешние входы в кластер. Они скрывают конкретные узлы от организаций и клиентов.`,fabricEgressPools:`Выходные зоны`,fabricEgressPoolHelp:`Логические выходы к внешним сетям, например “Офис Москва”. Организации используют зону, а не конкретный узел.`,endpointName:`Название`,publicEndpoint:`Публичный адрес`,endpointType:`Тип входа`,description:`Описание`,routeScope:`Область маршрутов JSON`,createEntryPoint:`Создать точку входа`,createEgressPool:`Создать выходную зону`,endpointNodes:`Назначенные узлы`,assignEndpointNode:`Назначить узел`,selectNode:`Выберите узел`,assignedNodesEmpty:`Узлы пока не назначены`,entryPointsEmpty:`Точки входа пока не созданы.`,egressPoolsEmpty:`Выходные зоны пока не созданы.`,addressNotSet:`адрес не задан`,descriptionNotSet:`описание не задано`,servicePlacement:`Размещение сервисов`,trafficFlow:`Потоки между узлами`,organizationTestFlag:`Тестирование организации`,organizationId:`ID организации`,saveOrganizationFlag:`Сохранить флаг организации`,noLinks:`Связей пока нет`,recentHeartbeats:`Последние heartbeat`,memory:`Память`,cpu:`Процессор`,processes:`Процессы`},en:{productOwner:`Product Owner`,controlPlane:`Control Plane`,sideText:`Full platform-owner panel for clusters, nodes, trust, placement, and safe service desired state.`,signInTitle:`Sign in`,signInText:`Enter your credentials.`,bootstrapTitle:`First owner`,bootstrapText:`An empty installation accepts only a signed product activation.`,activationPayload:`Activation manifest JSON`,activationSignature:`Manifest signature`,createOwner:`Create owner`,creatingOwner:`Creating...`,ownerCreated:`Owner created. You can sign in now.`,installationLocked:`Installation is already active`,insecureBootstrapDisabled:`Insecure bootstrap is disabled. Strict product-key activation is required.`,email:`Login`,password:`Password`,backendApi:`Backend API`,useLocalProxy:`Use local /api/v1 proxy`,language:`Language`,deviceLabel:`Device`,rememberMe:`Remember me`,trustDevice:`Trust this device`,signIn:`Sign in`,signingIn:`Signing in...`,logout:`Logout`,profile:`Profile`,refresh:`Refresh`,refreshing:`Refreshing...`,autoRefresh:`Auto refresh`,lastRefresh:`Data refreshed`,activeCluster:`Active cluster`,slugLabel:`Technical code`,slugHelp:`Slug is a stable short technical identifier for URLs, scripts, logs, and integrations. It should generally not change after creation.`,clusterCatalog:`Cluster catalog`,clusterCatalogText:`Real clusters from the Control Plane. Select the active cluster or expand a card for details.`,makeActive:`Make active`,openSettings:`Open settings`,selected:`Selected`,createCluster:`Create cluster`,clusterDetails:`Details`,consoleTitle:`Platform Owner Console`,boundary:`WEB is presentation only. Cluster decisions go through Control Plane APIs, PostgreSQL source of truth, and audit.`,noLoginError:`Sign in as a product owner or platform administrator to load the panel.`,accessDenied:`Access to this panel is denied.`,sessionMode:`Session mode`,sessionModeAdmin:`Admin`,sessionModeUser:`User`,sessionRefreshedAt:`Session refreshed`,emptyLiveTitle:`Cluster has no live nodes yet`,emptyLiveText:`These are real values, not placeholders: the selected cluster has no approved node-agent nodes yet. Create a join token, run rap-node-agent, and approve the join request.`,realDataNote:`Only PostgreSQL/Control Plane data is shown. Zero values mean the corresponding nodes, roles, or services do not exist yet.`,signedInAs:`Signed in`,actorUser:`Actor user`,testMode:`Testing`,testModeText:`Enables test telemetry and synthetic link observations. This is not production mesh runtime.`,platformTestFlag:`Server testing`,nodeTelemetry:`Node telemetry`,heartbeatHistory:`Heartbeat history`,noTelemetry:`No telemetry yet`,enableTelemetry:`Enable telemetry`,enableSyntheticLinks:`Enable test links`,saveTestFlag:`Save flag`,nodeManagement:`Node management`,nodeScope:`View scope`,currentClusterNodes:`Active cluster nodes`,allNodes:`All platform nodes`,showAllPlatformNodes:`Show all platform nodes`,currentClusterMembership:`Active cluster membership`,clusterMemberships:`Cluster memberships`,notMemberOfActiveCluster:`not a member`,nodeIdentity:`Physical node identity`,activeClusterScope:`Active cluster scope`,activeClusterScopeText:`One physical node may belong to multiple clusters. Roles and desired services below belong only to the selected active cluster.`,capabilityConfirmed:`capability confirmed by heartbeat`,capabilityMissing:`capability not reported by node`,capabilityUnknown:`capability unconfirmed: no heartbeat`,nodeGlobalInventoryText:`Each physical node is shown once. Membership and roles remain cluster-scoped, so the same node may have different assignments in different clusters.`,nodeSearch:`Node search`,groupNodesBy:`Group by`,groupByMembership:`membership`,groupByHealth:`health`,groupByOwnership:`ownership`,groupByClusterCount:`cluster count`,nodeGroups:`Node groups`,nodeGroupTree:`Group tree`,nodeGroupFilter:`Group filter`,allNodeGroups:`All groups`,nodeGroupCreatePanel:`Create group`,nodeGroupName:`Group name`,parentNodeGroup:`Parent group`,rootNodeGroup:`Root`,ungroupedNodes:`Ungrouped`,createNodeGroup:`Create group`,createSubgroup:`Create subgroup`,collapseGroup:`Collapse`,expandGroup:`Expand`,assignNodeGroup:`Move to group`,removeFromNodeGroup:`Remove from group`,connectExistingNode:`Connect to active cluster`,connectExistingNodeTitle:`Connect existing node`,connectExistingNodeText:`This creates or re-enables membership for one concrete physical node in the active cluster. Roles below are assigned only in this cluster.`,connectWithRoles:`Connect with roles`,nodeDetails:`Details`,manageNode:`Configure`,nodeFunctions:`Node functions`,nodeFunctionsText:`One row controls the whole function: role grants permission in the active cluster, desired service requests runtime start, observed state reports node-agent facts.`,rolePermission:`Permission`,permissionGranted:`granted`,permissionDenied:`not allowed`,organizationScopeForEnable:`Organization scope for new enables, optional`,clusterWideRolePlaceholder:`empty = cluster-wide role`,desiredRuntime:`Desired state`,observedRuntime:`Observed`,enableFunction:`Enable function`,disableFunction:`Disable function`,close:`Close`,nodeBriefList:`Compact node list`,noActiveClusterMembership:`Node is not a member of the active cluster`,nodeBriefListHelp:`The list is grouped as the active cluster tree. Full details, management, roles, services, and statistics open from the node row.`,nodeSearchPlaceholder:`name, key, cluster, status`,nodeGroupInventoryText:`Groups are a cluster inventory structure. Moving a node to a group changes only its placement inside the active cluster, not roles or membership.`,nodeGroupCreated:`Node group created.`,noNodesTitle:`No nodes`,noNodesByFilter:`No nodes match the current filter.`,cancel:`Cancel`,alreadyMember:`Already in active cluster`,revokedMembership:`Membership revoked`,addNode:`Add node`,addNodeText:`Connect an existing physical node to the active cluster from the node list: enable platform-wide view and click “Connect to active cluster”.`,joinTokenTitle:`Create new Docker node`,joinTokenText:`First create a one-time install token and Docker install profile. Then run the generated command on the Docker host; the agent submits a request and the platform owner approves it.`,ttlHours:`Lifetime, hours`,ttlHelp:`After this time the token becomes invalid even if unused. For manual enrollment, 1–24 hours is usually enough.`,maxUses:`Maximum uses`,maxUsesHelp:`How many node-agents may use this token. The safest default is one token for one new node.`,tokenPurpose:`Token purpose`,nodeOwnership:`Node ownership type`,suggestedRoles:`Allowed/expected roles`,generatedScope:`Generated scope`,generatedScopeHelp:`JSON is generated automatically from the settings above. Operators should not hand-write it and risk syntax or access-scope mistakes.`,manualApprovalRequired:`Manual request approval is required`,nodeRoles:`Node roles`,desiredServices:`Desired services`,observedServices:`Observed services`,noRoles:`No roles yet`,noServices:`No services yet`,manageInCluster:`Manage in cluster`,rolesAndServices:`Roles and services`,links:`Links`,fabricMap:`Fabric traffic map`,fabricIngressLayer:`Ingress`,fabricNodeLayer:`Cluster nodes`,fabricEgressLayer:`Egress pools`,observedPeerLinks:`Observed links`,placementIntent:`control-plane placement`,fabricEntryPoints:`Entry points`,fabricEntryPointHelp:`Logical external ingress points for the cluster. They hide concrete nodes from organizations and clients.`,fabricEgressPools:`Egress pools`,fabricEgressPoolHelp:`Logical exits to external networks, for example “Office Moscow”. Organizations use the pool, not a concrete node.`,endpointName:`Name`,publicEndpoint:`Public endpoint`,endpointType:`Entry type`,description:`Description`,routeScope:`Route scope JSON`,createEntryPoint:`Create entry point`,createEgressPool:`Create egress pool`,endpointNodes:`Assigned nodes`,assignEndpointNode:`Assign node`,selectNode:`Select node`,assignedNodesEmpty:`No nodes assigned yet`,entryPointsEmpty:`No entry points created yet.`,egressPoolsEmpty:`No egress pools created yet.`,addressNotSet:`address not set`,descriptionNotSet:`description not set`,servicePlacement:`Service placement`,trafficFlow:`Node traffic flows`,organizationTestFlag:`Organization testing`,organizationId:`Organization ID`,saveOrganizationFlag:`Save organization flag`,noLinks:`No links yet`,recentHeartbeats:`Recent heartbeats`,memory:`Memory`,cpu:`CPU`,processes:`Processes`}};function D(e){return{userId:e.user.id||e.user.ID||``,email:e.user.email||e.user.Email||``,authSessionId:e.auth_session.id||e.auth_session.ID||``,accessToken:e.tokens.access_token,refreshToken:e.tokens.refresh_token,accessTokenExpiresAt:e.tokens.access_token_expires_at,refreshTokenExpiresAt:e.tokens.refresh_token_expires_at}}async function ue(e){try{return await e.listClusterSummaries(),`admin`}catch{try{return await Promise.all([e.listOrganizations(),e.listResources()]),`user`}catch{return null}}}function de(){let[e,t]=(0,_.useState)(!1),[n,r]=(0,_.useState)(()=>!!ce()),[i]=(0,_.useState)(()=>{let e=localStorage.getItem(C.baseUrl)?.trim();return!e||e.startsWith(ne)?te:e}),[a,o]=(0,_.useState)(()=>ce()),[s,c]=(0,_.useState)(null),[l,u]=(0,_.useState)(``),[d,f]=(0,_.useState)(()=>localStorage.getItem(C.language)===`en`?`en`:`ru`),[p,m]=(0,_.useState)(a?.userId??localStorage.getItem(C.actorUserId)??``),[h,g]=(0,_.useState)({email:``,password:``,deviceLabel:`Панель владельца платформы`,trustDevice:!0,rememberMe:!0,showPassword:!1}),[v,b]=(0,_.useState)(null),[x,ee]=(0,_.useState)({email:``,password:``,activationPayload:``,activationSignature:``}),[w,ae]=(0,_.useState)(`command`),[T,de]=(0,_.useState)(``),[ge,ve]=(0,_.useState)([]),[Se,Ce]=(0,_.useState)([]),[we,Te]=(0,_.useState)(null),[j,Ee]=(0,_.useState)([]),[De,Oe]=(0,_.useState)([]),[ke,Ae]=(0,_.useState)({}),[Fe,ze]=(0,_.useState)([]),[Be,Ve]=(0,_.useState)([]),[Ue,Xe]=(0,_.useState)([]),[$e,nt]=(0,_.useState)({}),[rt,it]=(0,_.useState)({}),[ot,st]=(0,_.useState)({}),[lt,ut]=(0,_.useState)({}),[dt,Ct]=(0,_.useState)({}),[Et,Dt]=(0,_.useState)({}),[Lt,Rt]=(0,_.useState)({}),[zt,Bt]=(0,_.useState)([]),[Vt,Ht]=(0,_.useState)([]),[Ut,Wt]=(0,_.useState)({}),[Gt,Kt]=(0,_.useState)([]),[qt,Jt]=(0,_.useState)([]),[F,Yt]=(0,_.useState)(null),[Xt,Zt]=(0,_.useState)([]),[I,Qt]=(0,_.useState)(null),[$t,rn]=(0,_.useState)(null),[an,on]=(0,_.useState)(null),[un,dn]=(0,_.useState)(null),[fn,pn]=(0,_.useState)(null),[L,mn]=(0,_.useState)(null),[hn,gn]=(0,_.useState)([]),[vn,yn]=(0,_.useState)(!1),[B,kn]=(0,_.useState)(re),[An,jn]=(0,_.useState)(null),[Mn,Nn]=(0,_.useState)(null),[Pn,Fn]=(0,_.useState)([]),[In,Ln]=(0,_.useState)([]),[Rn,zn]=(0,_.useState)({}),[Bn,Vn]=(0,_.useState)([]),[Hn,Un]=(0,_.useState)({}),[Wn,Gn]=(0,_.useState)([]),[Kn,qn]=(0,_.useState)([]),[Jn,Yn]=(0,_.useState)({}),[Xn,Zn]=(0,_.useState)({}),[Qn,$n]=(0,_.useState)(()=>localStorage.getItem(C.vpnDiagnosticDeviceId)||``),[er,tr]=(0,_.useState)([]),[nr,rr]=(0,_.useState)(null),[ir,ar]=(0,_.useState)(`http://2ip.ru/`),[or,sr]=(0,_.useState)(null),[cr,lr]=(0,_.useState)([]),[ur,dr]=(0,_.useState)([]),[fr,pr]=(0,_.useState)([]),[mr,hr]=(0,_.useState)({}),[gr,_r]=(0,_.useState)([]),[vr,yr]=(0,_.useState)([]),[br,xr]=(0,_.useState)(null),[Sr,Cr]=(0,_.useState)(``),[wr,Tr]=(0,_.useState)(`poll`),[Er,Dr]=(0,_.useState)(``),[Or,kr]=(0,_.useState)(null),[Ar,jr]=(0,_.useState)(!1),[Mr,Nr]=(0,_.useState)(``),[Pr,Fr]=(0,_.useState)(``),[Ir,Lr]=(0,_.useState)({slug:``,name:``,region:``}),[Rr,zr]=(0,_.useState)({name:``,status:`active`,region:``,metadataJson:`{}`}),[Br,Vr]=(0,_.useState)({name:``,parentGroupId:``}),[Hr,Ur]=(0,_.useState)({name:``,endpointType:`client_access`,publicEndpoint:``}),[Wr,Gr]=(0,_.useState)({name:``,description:``,routeScope:`{ + "routes": [] +}`}),[Kr,qr]=(0,_.useState)({hysteresisPenalty:`150`,promotionMinSamples:`64`,demotionFailureThreshold:`1`,demotionDropThreshold:`1`,demotionSlowThreshold:`1`,demotionRebuildEnabled:!0,demotionFencedEnabled:!0}),[Jr,Yr]=(0,_.useState)({currentWindowSeconds:`1800`,historyWindowSeconds:`86400`}),[H,U]=(0,_.useState)(le),[Xr,Zr]=(0,_.useState)(null),[Qr,$r]=(0,_.useState)({authorityState:`authoritative`,mutationMode:`normal`,notes:``}),[ei,ti]=(0,_.useState)(``),[ni,ri]=(0,_.useState)(`cluster`),[ii,ai]=(0,_.useState)(``),[oi,si]=(0,_.useState)(``),[ci,li]=(0,_.useState)([]),[ui,di]=(0,_.useState)(`membership`),[fi,pi]=(0,_.useState)(null),[mi,hi]=(0,_.useState)([]),[gi,_i]=(0,_.useState)(null),[vi,yi]=(0,_.useState)(`details`),[bi,xi]=(0,_.useState)({}),[Si,Ci]=(0,_.useState)({}),[wi,Ti]=(0,_.useState)({}),[Ei,Di]=(0,_.useState)({}),[Oi,ki]=(0,_.useState)({}),[W,Ai]=(0,_.useState)({}),[ji,Mi]=(0,_.useState)(``),[Ni,Pi]=(0,_.useState)({telemetry:!0,links:!0}),[Fi,Ii]=(0,_.useState)({nodeId:``,serviceType:`entry-node`,desiredState:`enabled`,runtimeMode:`container`,version:``,configJson:`{}`,environmentJson:`{}`}),[G,Li]=(0,_.useState)({organizationId:``,name:``,protocolFamily:`generic`,desiredState:`disabled`,credentialRef:``,targetEndpointJson:`{}`,allowedNodePolicyJson:`{"mode":"explicit","node_ids":[]}`,routingUsageJson:`[]`,routePolicyJson:`{}`,qosPolicyJson:`{}`,placementPolicyJson:`{}`}),[Ri,zi]=(0,_.useState)({slug:``,name:``}),[Bi,Vi]=(0,_.useState)({email:``,password:``,platformRole:`user`}),[Hi,Ui]=(0,_.useState)({organizationId:``,userId:``,roleId:`org_member`}),[Wi,Gi]=(0,_.useState)(null),[Ki,qi]=(0,_.useState)({username:``,password:``,domain:``}),[K,Ji]=(0,_.useState)({organizationId:``,name:``,address:``,protocol:`rdp`,routeMode:`vpn_exit`,entryNode:``,exitNode:``,tags:``,username:``,password:``,domain:``}),[Yi,Xi]=(0,_.useState)(``),[Zi,Qi]=(0,_.useState)(``),[$i,ea]=(0,_.useState)(``),ta=`rap-android-rdp-vpn-latest-release.apk`,[na,ra]=(0,_.useState)(ta),q=(0,_.useMemo)(()=>new y({baseUrl:i,actorUserId:p}),[i,p]),ia=(0,_.useMemo)(()=>new y({baseUrl:i,actorUserId:``}),[i]),aa=(0,_.useRef)(0),oa=(0,_.useRef)(!1),J=E[d],sa=ge.find(e=>e.id===T)||null,ca=Se.find(e=>e.cluster_id===T)||null,la=(0,_.useMemo)(()=>xn(i),[i]),ua=(0,_.useCallback)((e,t)=>{if(!e)return t;let n=e.trim();return n?/^https?:\/\//i.test(n)||n.startsWith(`/`)?n.startsWith(`/`)?n.substring(1):n:n.startsWith(`downloads/`)?n:`downloads/${n.replace(/^\.\/+/,``).replace(/^\/+/,``)}`:t},[]),da=ua(na,ta),fa=$i?ua($i,da):da,pa=$i?fa:da,ma=`${/^https?:\/\//i.test(pa)?pa:`${la}/${pa}`}${Zi?`?_v=${encodeURIComponent(Zi)}`:``}`,ha=(0,_.useMemo)(()=>bt(H),[H]),ga=(0,_.useMemo)(()=>Xr?xt(Xr.scope,H):H,[Xr,H]),_a=(0,_.useMemo)(()=>{let e=new Map;for(let t of ge)for(let n of ke[t.id]||[]){let r=e.get(n.id);r?(r.memberships.push({cluster:t,node:n}),(n.last_seen_at||``)>(r.node.last_seen_at||``)&&(r.node=n)):e.set(n.id,{node:n,memberships:[{cluster:t,node:n}]})}return Array.from(e.values()).sort((e,t)=>e.node.name.localeCompare(t.node.name))},[ke,ge]);(0,_.useMemo)(()=>tn(_a,T,ii,ui,d),[_a,ui,ii,d,T]);let va=(0,_.useMemo)(()=>{let e=ii.trim().toLowerCase(),t=oi?new Set([oi,..._t(oi,De)]):null;return _a.filter(n=>{let r=n.memberships.some(e=>e.cluster.id===T);if(ni!==`all`&&!r)return!1;if(t){let e=n.memberships.find(e=>e.cluster.id===T);if(!e?.node.node_group_id||!t.has(e.node.node_group_id))return!1}return!e||nn(n,e)})},[_a,ii,oi,De,ni,T]),ya=(0,_.useCallback)((e,t=!1)=>{if(e&&t){localStorage.setItem(C.auth,JSON.stringify(e)),localStorage.setItem(C.actorUserId,e.userId),r(!0);return}r(!1),localStorage.removeItem(C.auth),localStorage.removeItem(C.actorUserId)},[]),ba=(0,_.useCallback)(async()=>{try{let e=`${la}/downloads/rap-android-rdp-vpn-build.json?_cb=${Date.now()}`,t=await fetch(e,{cache:`no-store`});if(!t.ok){Xi(``),Qi(new Date().toISOString()),ea(``),ra(ta);return}let n=await t.json();Xi(n.version?.name||``),Qi(n.published?.timestamp_utc||``),ea(n.release_paths?.versioned||``),ra(n.published?.path||n.release_paths?.latest||ta)}catch{Xi(``),Qi(new Date().toISOString()),ea(``),ra(ta)}},[la]),xa=(0,_.useMemo)(()=>vt(va,De,T,J,new Set(ci)),[ci,De,T,J,va]),Sa=(0,_.useMemo)(()=>vr.slice(0,8),[vr]);(0,_.useEffect)(()=>{if(e)return;t(!0);let n=ce();if(n){if(se(n.refreshTokenExpiresAt)){localStorage.removeItem(C.auth),localStorage.removeItem(C.actorUserId),r(!1);return}(async()=>{try{let e=D(await ia.refresh({refreshToken:n.refreshToken}));if(!e.userId||!e.authSessionId)throw Error(`Не удалось восстановить сессию.`);let t=await ue(new y({baseUrl:i,actorUserId:e.userId}));if(!t)throw Error(`Доступ к этой панели запрещен.`);m(e.userId),ya(e,!0),o(e),u(new Date().toISOString()),g(t=>({...t,email:e.email})),c(t)}catch{localStorage.removeItem(C.auth),localStorage.removeItem(C.actorUserId),r(!1),u(``),o(null),m(``),c(null)}})()}},[ia,e,i,ya]),(0,_.useEffect)(()=>{let e=!1;return ia.getInstallationStatus().then(t=>{e||b(t)}).catch(t=>{e||Nr(t instanceof Error?t.message:`Не удалось загрузить статус установки.`)}),()=>{e=!0}},[ia]),(0,_.useEffect)(()=>{if(!sa){zr({name:``,status:`active`,region:``,metadataJson:`{}`});return}zr({name:sa.name,status:sa.status||`active`,region:sa.region||``,metadataJson:JSON.stringify(sa.metadata||{},null,2)})},[sa]),(0,_.useEffect)(()=>{si(``),Vr({name:``,parentGroupId:``}),li([])},[T]),(0,_.useEffect)(()=>{pi(null),hi([])},[T]),(0,_.useEffect)(()=>{localStorage.setItem(C.baseUrl,i),localStorage.setItem(C.language,d),a&&localStorage.setItem(`${C.language}.${a.userId}`,d),(!a||!n)&&(localStorage.removeItem(C.auth),localStorage.removeItem(C.actorUserId))},[i,d,n,a]),(0,_.useEffect)(()=>{if(!a)return;let e=localStorage.getItem(`${C.language}.${a.userId}`);(e===`ru`||e===`en`)&&f(e)},[a?.userId]),(0,_.useEffect)(()=>{a&&Ca()},[a?.userId]),(0,_.useEffect)(()=>{if(!a||s!==`admin`||!T)return;let e=!1,t=()=>{e||Ar||oa.current||document.visibilityState===`hidden`||(oa.current=!0,Ta(T).catch(t=>{e||Nr(t instanceof Error?t.message:`Не удалось автообновить данные панели.`)}).finally(()=>{oa.current=!1}))},n=null;typeof window.EventSource==`function`&&(n=new EventSource(q.clusterEventsURL(T)),n.onopen=()=>{e||Tr(`sse`)},n.onerror=()=>{e||Tr(`poll`)},n.addEventListener(`cluster.changed`,t));let r=window.setInterval(t,n?3e4:1e4);return()=>{e=!0,n?.close(),window.clearInterval(r)}},[q,s,Ar,T,a?.userId]);async function Ca(e=T){if(!p.trim()){Nr(J.noLoginError);return}if(s===`user`){await wa();return}jr(!0),Nr(``),Fr(``);try{let[t,n,r,i,a]=await Promise.all([q.listClusters(),q.listClusterSummaries(),q.listOrganizations(),q.listUsers(),q.listResources()]);ve(t),Ce(n),lr(r),dr(i),pr(a),!Er&&r[0]?.id&&Dr(r[0].id),Ui(e=>({...e,organizationId:e.organizationId||r[0]?.id||``})),Ji(e=>({...e,organizationId:e.organizationId||r[0]?.id||``}));let o=await Promise.all(r.map(async e=>[e.id,await q.listOrganizationMemberships(e.id)]));hr(Object.fromEntries(o));let s=await Promise.all(t.map(async e=>[e.id,await q.listNodes(e.id)]));Ae(Object.fromEntries(s));let c=e||t[0]?.id||``;de(c),c&&await Ea(c),Cr(new Date().toISOString())}catch(e){Nr(e instanceof Error?e.message:`Неизвестная ошибка панели управления платформой.`)}finally{jr(!1)}}async function wa(){if(!p.trim()){Nr(`Войдите, чтобы загрузить личный кабинет.`);return}jr(!0),Nr(``),Fr(``);try{await ba();let[e,t]=await Promise.all([q.listOrganizations(),q.listResources()]);lr(e),pr(t),!Er&&e[0]?.id&&Dr(e[0].id);let n=await Promise.all(e.map(async e=>[e.id,await q.listOrganizationMemberships(e.id)]));hr(Object.fromEntries(n)),Cr(new Date().toISOString())}catch(e){Nr(e instanceof Error?e.message:`Не удалось загрузить личный кабинет.`)}finally{jr(!1)}}async function Ta(e){if(!p.trim())return;let[t,n,r,i,a]=await Promise.all([q.listClusterSummaries(),q.listNodes(e),q.listOrganizations(),q.listUsers(),q.listResources()]);Ce(t),lr(r),dr(i),pr(a),Ae(t=>({...t,[e]:n})),await Ea(e,{preserveEditableForms:!0}),Cr(new Date().toISOString())}async function Ea(e,t={}){let n=++aa.current,r=vn?20:10,i=vn?B.offset:0,a={reporterNodeId:B.reporterNodeId||void 0,routeId:B.routeId||void 0,serviceClass:B.serviceClass||void 0,generation:B.generation||void 0,feedbackSource:B.feedbackSource||void 0,feedbackChannelId:B.feedbackChannelId||void 0,feedbackViolationStatus:B.feedbackViolationStatus||void 0,limit:r,offset:i,enrichment:vn?`deep`:`summary`},[o,s,c,l,u,d,f,p,m,h,g,_,v,y,b,x,ee,S,te,ne,re,ie,w,ae,oe,T,se]=await Promise.all([q.listNodes(e),q.listNodeGroups(e),q.listJoinRequests(e),q.listJoinTokens(e),q.listReleaseVersions(e,`rap-node-agent`,`dev`),q.getClusterAuthority(e),q.listAudit(e),q.getFabricServiceChannelRebuildInvestigationBreadcrumbs(e,{limit:20}),q.listMeshLinks(e),q.listRouteIntents(e),q.listFabricServiceChannelRouteFeedback(e,{includeExpired:!0}),q.listFabricServiceChannelRouteRebuildAttempts(e,a),q.getFabricServiceChannelRouteRebuildHealthSummary(e,{limit:5}),q.listFabricServiceChannelRouteRebuildAlertSilences(e),q.getFabricServiceChannelReadiness(e,{limit:5}),q.getFabricServiceChannelSchemaStatus(e),q.getFabricServiceChannelRebuildSnapshotMaintenanceHealth(e,{limit:50,minAgeSeconds:60,heartbeatThreshold:2}),q.getFabricServiceChannelLeaseMaintenance(e,{limit:20,includeExpired:!0}),q.getFabricServiceChannelAccessTelemetry(e,{limit:20}),q.listFabricServiceChannelRouteRebuildIncidents(e,{limit:5}),q.getFabricServiceChannelRecoveryPolicy(e),q.getFabricServiceChannelBreadcrumbWindowPolicy(e),q.listQoSPolicies(e),q.listFabricEntryPoints(e),q.listFabricEgressPools(e),q.listVPNConnections(e),q.listFabricTestingFlags()]);if(n!==aa.current)return;Ee(o),Oe(s),ze(c),Ve(l),Xe(u),Te(d),t.preserveEditableForms||$r({authorityState:d.authority_state,mutationMode:d.mutation_mode,notes:d.notes||``}),_r(f),yr(p.events),xr(p.summary||null),Bt(m),Ht(h),Kt(g),Jt(_),Yt(v),Zt(y),Qt(b),rn(x),on(ee),pn(S),mn(te),gn(ne),jn(re),Nn(ie),t.preserveEditableForms||Yr({currentWindowSeconds:String(ie.current_window_seconds||1800),historyWindowSeconds:String(ie.history_window_seconds||86400)}),qr({hysteresisPenalty:String(re.hysteresis_penalty),promotionMinSamples:String(re.promotion_min_samples),demotionFailureThreshold:String(re.demotion_failure_threshold),demotionDropThreshold:String(re.demotion_drop_threshold),demotionSlowThreshold:String(re.demotion_slow_threshold),demotionRebuildEnabled:re.demotion_rebuild_enabled,demotionFencedEnabled:re.demotion_fenced_enabled}),Fn(w),Ln(ae),Vn(oe),qn(T),Gn(se);let ce=await q.listVPNClientDiagnosticStatuses(e);if(n!==aa.current)return;tr(ce);let le=ce.find(e=>e.device_id===Qn.trim())||ce[0]||null;rr(le),!Qn.trim()&&le&&($n(le.device_id),localStorage.setItem(C.vpnDiagnosticDeviceId,le.device_id));let[E,D]=await Promise.all([Promise.all(ae.map(async t=>[t.id,await q.listFabricEntryPointNodes(e,t.id)])),Promise.all(oe.map(async t=>[t.id,await q.listFabricEgressPoolNodes(e,t.id)]))]);if(n!==aa.current)return;zn(Object.fromEntries(E)),Un(Object.fromEntries(D));let ue=await Promise.all(o.map(async t=>[t.id,await q.listNodeRoles(e,t.id)]));if(n!==aa.current)return;st(Object.fromEntries(ue));let de=await Promise.all(o.map(async t=>[t.id,await q.listDesiredWorkloads(e,t.id)]));if(n!==aa.current)return;ut(Object.fromEntries(de));let fe=await Promise.all(o.map(async t=>[t.id,await q.listWorkloadStatuses(e,t.id)]));if(n!==aa.current)return;Ct(Object.fromEntries(fe));let O=await Promise.all(o.map(async t=>[t.id,await q.listNodeHeartbeats(e,t.id,60)]));if(n!==aa.current)return;Dt(Object.fromEntries(O));let k=await Promise.all(o.map(async t=>[t.id,await q.getNodeUpdatePlan(e,t.id,{currentVersion:t.reported_version})]));if(n!==aa.current)return;nt(Object.fromEntries(k));let pe=await Promise.all(o.map(async t=>[t.id,await q.listNodeUpdateStatuses(e,t.id,80)]));if(n!==aa.current)return;it(Object.fromEntries(pe));let A=await Promise.all(o.map(async t=>[t.id,await q.listNodeTelemetry(e,t.id,120)]));if(n!==aa.current)return;Rt(Object.fromEntries(A));let me=await Promise.all(o.map(async t=>[t.id,await q.getNodeSyntheticMeshConfig(e,t.id)]));if(n!==aa.current)return;Wt(Object.fromEntries(me));let he=await Promise.all(T.map(async t=>[t.id,await q.getActiveVPNLease(e,t.id)]));if(n!==aa.current)return;Yn(Object.fromEntries(he));let ge=await Promise.all(T.map(async t=>[t.id,await q.getVPNPacketStats(e,t.id)]));n===aa.current&&Zn(Object.fromEntries(ge))}async function Da(e=vn,t=B){if(T){jr(!0),Nr(``),Fr(``);try{let n=await q.listFabricServiceChannelRouteRebuildAttempts(T,{reporterNodeId:t.reporterNodeId||void 0,routeId:t.routeId||void 0,serviceClass:t.serviceClass||void 0,generation:t.generation||void 0,feedbackSource:t.feedbackSource||void 0,feedbackChannelId:t.feedbackChannelId||void 0,feedbackViolationStatus:t.feedbackViolationStatus||void 0,limit:e?20:10,offset:e?t.offset:0,enrichment:e?`deep`:`summary`});yn(e),kn(t),Jt(n),Fr(e?`Deep rebuild ledger loaded.`:`Fast rebuild ledger loaded.`)}catch(e){Nr(e instanceof Error?e.message:`Не удалось загрузить rebuild ledger.`)}finally{jr(!1)}}}async function Oa(){if(!T)return;let[e,t,n,r,i,a,o,s,c,l]=await Promise.all([q.getFabricServiceChannelRouteRebuildHealthSummary(T,{limit:5}),q.listFabricServiceChannelRouteRebuildAlertSilences(T),q.getFabricServiceChannelReadiness(T,{limit:5}),q.getFabricServiceChannelSchemaStatus(T),q.getFabricServiceChannelRebuildSnapshotMaintenanceHealth(T,{limit:50,minAgeSeconds:60,heartbeatThreshold:2}),q.getFabricServiceChannelLeaseMaintenance(T,{limit:20,includeExpired:!0}),q.getFabricServiceChannelAccessTelemetry(T,{limit:20}),q.listFabricServiceChannelRouteRebuildIncidents(T,{limit:5}),q.getFabricServiceChannelRebuildInvestigationBreadcrumbs(T,{limit:20}),q.getFabricServiceChannelBreadcrumbWindowPolicy(T)]);Yt(e),Zt(t),Qt(n),rn(r),on(i),pn(a),mn(o),gn(s),yr(c.events),xr(c.summary||null),Nn(l),Yr({currentWindowSeconds:String(l.current_window_seconds||1800),historyWindowSeconds:String(l.history_window_seconds||86400)})}async function ka(){if(T)try{jr(!0);let e=await q.warmupFabricServiceChannelRebuildSnapshots(T,{limit:10,staleAfterSeconds:60});dn(e),await Oa(),Fr(`Snapshot warmup: warmed ${e.warmed_count}, fresh ${e.already_fresh_count}, errors ${e.error_count}.`)}catch(e){Nr(e instanceof Error?e.message:`Не удалось прогреть rebuild snapshots.`)}finally{jr(!1)}}async function Aa(){if(T)try{jr(!0);let e=await q.cleanupFabricServiceChannelLeases(T,{limit:100});pn(e),Fr(`Service-channel lease cleanup: deleted ${e.deleted_expired_count||0}, active ${e.active_count}, expired ${e.expired_count}.`)}catch(e){Nr(e instanceof Error?e.message:`Не удалось очистить service-channel leases.`)}finally{jr(!1)}}async function ja(e){let t={reporterNodeId:e.reporter_node_id,routeId:e.route_id,serviceClass:e.service_class,generation:e.generation||``,feedbackSource:``,feedbackChannelId:e.channel_id||``,feedbackViolationStatus:``,offset:0};await q.recordFabricServiceChannelRouteRebuildInvestigation(T,{reporterNodeId:e.reporter_node_id,routeId:e.route_id,serviceClass:e.service_class,generation:e.generation||``,guardStatus:e.guard_status,incidentId:e.fingerprint});let n=await q.getFabricServiceChannelRebuildInvestigationBreadcrumbs(T,{limit:20});yr(n.events),xr(n.summary||null),kn(t),await Da(!0,t)}function Ma(e){let t=new Set(e.affected_reporter_node_ids||[]),n=new Set(e.affected_route_ids||[]);return hn.filter(r=>{let i=!e.feedback_channel_id||r.channel_id===e.feedback_channel_id,a=t.size===0||t.has(r.reporter_node_id),o=n.size===0||n.has(r.route_id);return i&&a&&o})}function Na(e){let t=at(e.payload)||{},n=N(t,`feedback_source`,``),r=N(t,`feedback_channel_id`,``),i=N(t,`feedback_violation_status`,``),a=N(t,`reporter_node_id`,``),o=N(t,`route_id`,``);return!n&&!r&&!i?null:(F?.feedback_breakdowns||[]).find(e=>!(n&&e.feedback_source!==n||r&&e.feedback_channel_id!==r||i&&e.feedback_violation_status!==i||a&&!(e.affected_reporter_node_ids||[]).includes(a)||o&&!(e.affected_route_ids||[]).includes(o)))||null}function Pa(e){let t=at(e.payload)||{},n=N(t,`reporter_node_id`,``),r=N(t,`route_id`,e.target_type===`fabric_service_channel_route_rebuild_incident`&&e.target_id||``),i=N(t,`service_class`,``),a=N(t,`generation`,``),o=N(t,`guard_status`,``);return hn.find(e=>n&&e.reporter_node_id!==n||r&&e.route_id!==r||i&&e.service_class!==i||a&&e.generation!==a||o&&e.guard_status!==o?!1:!!(n||r||i||a||o))||null}async function Fa(e){let t={...re,feedbackSource:e.feedback_source||``,feedbackChannelId:e.feedback_channel_id||``,feedbackViolationStatus:e.feedback_violation_status||``,offset:0};await q.recordFabricServiceChannelRouteRebuildInvestigation(T,{reporterNodeId:(e.affected_reporter_node_ids||[])[0]||``,routeId:(e.affected_route_ids||[])[0]||``,feedbackSource:e.feedback_source||``,feedbackChannelId:e.feedback_channel_id||``,feedbackViolationStatus:e.feedback_violation_status||``,drilldownSource:`rebuild_health_feedback_breakdown`,reason:`operator opened rebuild-health feedback breakdown ledger`});let n=await q.getFabricServiceChannelRebuildInvestigationBreadcrumbs(T,{limit:20});yr(n.events),xr(n.summary||null),ae(`fabric`),kn(t),await Da(!0,t)}async function Ia(e){await q.silenceFabricServiceChannelRouteRebuildAlert(T,{incidentSource:e.incident_source||``,channelId:e.channel_id||``,reporterNodeId:e.reporter_node_id,routeId:e.route_id,guardStatus:e.guard_status||`unknown`,generation:e.generation||``,reason:`operator acknowledged rebuild incident`,ttlSeconds:21600}),await Oa()}async function La(e){await q.unsilenceFabricServiceChannelRouteRebuildAlert(T,e.id,`operator removed rebuild alert silence`),await Oa()}function Ra(){Ee([]),Oe([]),ze([]),Ve([]),Xe([]),nt({}),Te(null),st({}),ut({}),Ct({}),Dt({}),it({}),Rt({}),Bt([]),Ht([]),Wt({}),Kt([]),Jt([]),Yt(null),Zt([]),Qt(null),rn(null),dn(null),gn([]),yn(!1),kn(re),Fn([]),Ln([]),zn({}),Vn([]),Un({}),Gn([]),qn([]),Yn({}),Zn({}),tr([]),rr(null),lr([]),dr([]),pr([]),hr({}),_r([]),yr([]),xr(null)}async function Y(e,t){jr(!0),Nr(``),Fr(``);try{await e(),Fr(t),await Ca()}catch(e){Nr(e instanceof Error?e.message:`Действие не выполнено.`)}finally{jr(!1)}}async function za(){if(!T){rr(null);return}let e=await q.listVPNClientDiagnosticStatuses(T);tr(e);let t=Qn.trim()||e[0]?.device_id||``;t&&(localStorage.setItem(C.vpnDiagnosticDeviceId,t),$n(t));let n=e.find(e=>e.device_id===t)||(t?await q.getVPNClientDiagnosticStatus(T,t):null);rr(n),Fr(n?`Диагностика VPN-клиента обновлена.`:`Диагностика VPN-клиента не найдена.`)}async function Ba(e,t){if(!T){Nr(`Выбери кластер перед отправкой команды.`);return}let n=Qn.trim();if(!n){Nr(`Укажи Android device id или выбери найденный клиент.`);return}jr(!0),Nr(``),Fr(``);try{sr(await q.enqueueVPNClientDiagnosticCommand(T,n,e)),Fr(`${t}: команда поставлена в очередь. Клиент заберет ее через диагностический канал.`),window.setTimeout(()=>{za()},3500)}catch(e){Nr(e instanceof Error?e.message:`Команда VPN-клиенту не отправлена.`)}finally{jr(!1)}}async function Va(){jr(!0),Nr(``),Fr(``);try{let e=D(await ia.login({email:h.email,password:h.password,deviceLabel:h.deviceLabel,trustDevice:h.trustDevice}));if(!e.userId||!e.authSessionId)throw Error(`Ответ входа не содержит пользователя или сессию.`);let t=new y({baseUrl:i,actorUserId:e.userId}),n=`admin`;try{await t.listClusterSummaries(),n=`admin`}catch{try{let[e,r]=await Promise.all([t.listOrganizations(),t.listResources()]);lr(e),pr(r),e[0]?.id&&Dr(e[0].id);let i=await Promise.all(e.map(async e=>[e.id,await t.listOrganizationMemberships(e.id)]));hr(Object.fromEntries(i)),n=`user`}catch{try{await ia.revokeAuthSession({userId:e.userId,authSessionId:e.authSessionId,reason:`user_portal_access_denied`})}catch{}throw Error(J.accessDenied)}}r(h.rememberMe),ya(e,h.rememberMe),o(e),m(e.userId),g(t=>({...t,email:e.email,password:``})),u(new Date().toISOString()),c(n),Fr(`${J.signedInAs}: ${e.email}`)}catch(e){Nr(e instanceof Error?e.message:`Вход не выполнен.`)}finally{jr(!1)}}async function Ha(){jr(!0),Nr(``),Fr(``);try{let e;if(v?.strict_authority){if(!x.activationPayload.trim()||!x.activationSignature.trim())throw Error(J.bootstrapText);e=JSON.parse(x.activationPayload)}b((await ia.bootstrapOwner({email:x.email,password:x.password,activationPayload:e,activationSignature:x.activationSignature})).installation),g({...h,email:x.email,password:x.password}),Fr(J.ownerCreated)}catch(e){Nr(e instanceof Error?e.message:`Создание владельца не выполнено.`)}finally{jr(!1)}}async function Ua(){let e=a;if(o(null),r(!1),u(``),ya(null),c(null),m(``),ve([]),Ce([]),Ra(),Ae({}),de(``),e?.userId&&e.authSessionId)try{await ia.revokeAuthSession({userId:e.userId,authSessionId:e.authSessionId,reason:`platform_owner_console_logout`})}catch{}}async function Wa(e){de(e),Ra(),jr(!0),Nr(``),Fr(``);try{await Ea(e)}catch(e){Nr(e instanceof Error?e.message:`Не удалось загрузить кластер.`)}finally{jr(!1)}}let Ga=Fe.filter(e=>e.status===`pending`).length,Ka=j.filter(e=>e.health_status===`healthy`).length,qa=j.filter(e=>e.health_status!==`healthy`||e.membership_status!==`active`).length,Ja=Object.values(ot).flat().filter(e=>e.status===`active`).length,Ya=Wn.find(e=>e.scope_type===`platform`&&!e.scope_id)||null;Wn.find(e=>e.scope_type===`organization`&&e.scope_id===ji&&(!e.cluster_id||e.cluster_id===T));let Xa=Object.values(Ut),Za=Xa.filter(e=>e.enabled).length,Qa=Xa.reduce((e,t)=>e+t.routes.length,0),$a=Xa.reduce((e,t)=>e+Object.keys(t.peer_endpoints||{}).length,0),eo=Xa.reduce((e,t)=>e+ft(t),0),to=Xa.reduce((e,t)=>e+(t.peer_directory?.length??0),0),no=Xa.reduce((e,t)=>e+(t.recovery_seeds?.length??0),0),X=Xa.filter(e=>e.production_forwarding).length,ro=Vt.filter(e=>Ke(e)===`active`),io=Vt.filter(e=>Ke(e)===`expired`),ao=Vt.filter(e=>Ke(e)===`disabled`),oo=Gt.filter(e=>{let t=Date.parse(e.expires_at||``),n=Date.parse(e.retry_cooldown_until||``);return Number.isFinite(t)&&t>Date.now()||Number.isFinite(n)&&n>Date.now()}),so=oo.filter(e=>e.feedback_status===`fenced`),co=oo.filter(e=>e.feedback_status===`degraded`),lo=oo.filter(e=>e.feedback_status===`healthy`),uo=oo.filter(e=>e.recovery_state===`recovered`||e.recovery_hysteresis_active),fo=oo.filter(e=>e.recovery_promoted),po=oo.filter(e=>e.recovery_demoted),mo=oo.filter(e=>e.feedback_status===`operator_retry_cooldown`||e.retry_cooldown_until),ho=Xa.flatMap(e=>e.route_path_decisions?.decisions||[]),go=ho.filter(e=>e.decision_source===`service_channel_feedback_no_alternate`),_o=ho.filter(e=>e.decision_source===`service_channel_feedback_replacement`),vo=ho.filter(e=>e.rebuild_status),yo=vo.filter(e=>e.rebuild_status===`applied`),bo=qt.filter(e=>e.rebuild_status===`applied`),xo=qt.filter(e=>e.rebuild_status&&e.rebuild_status!==`applied`),So=qt.filter(e=>e.guard_severity===`bad`),Co=ho.filter(e=>(e.score_reasons||[]).includes(`service_channel_recovery_hysteresis`)),wo=ho.filter(e=>(e.score_reasons||[]).includes(`service_channel_recovery_promoted`)),To=ho.filter(e=>(e.score_reasons||[]).includes(`service_channel_recovery_demoted`)),Eo=v?.bootstrapped===!1,Do=Eo&&!v?.strict_authority&&!v?.insecure_bootstrap_allowed,Oo=s===`admin`?J.sessionModeAdmin:J.sessionModeUser;if(!a)return(0,S.jsxs)(`main`,{className:`loginShell`,children:[v&&(0,S.jsxs)(`section`,{className:`loginCard`,children:[(0,S.jsx)(`h1`,{children:v.bootstrapped?J.installationLocked:J.bootstrapTitle}),(0,S.jsx)(A,{label:`Authority`,value:`${v.authority_mode}/${v.authority_state}`}),(0,S.jsx)(A,{label:`Strict`,value:v.strict_authority?`enabled`:`legacy`}),v.root_fingerprint&&(0,S.jsx)(A,{label:`Root key`,value:R(v.root_fingerprint)})]}),Eo?(0,S.jsxs)(`section`,{className:`loginCard`,children:[(0,S.jsx)(`h1`,{children:J.bootstrapTitle}),(0,S.jsx)(`p`,{className:`loginHint`,children:Do?J.insecureBootstrapDisabled:J.bootstrapText}),(0,S.jsxs)(`label`,{children:[J.email,(0,S.jsx)(`input`,{value:x.email,onChange:e=>ee({...x,email:e.target.value}),autoComplete:`username`})]}),(0,S.jsxs)(`label`,{children:[J.password,(0,S.jsx)(`input`,{value:x.password,onChange:e=>ee({...x,password:e.target.value}),type:`password`,autoComplete:`new-password`})]}),v?.strict_authority&&(0,S.jsxs)(S.Fragment,{children:[(0,S.jsxs)(`label`,{children:[J.activationPayload,(0,S.jsx)(`textarea`,{value:x.activationPayload,onChange:e=>ee({...x,activationPayload:e.target.value}),spellCheck:!1})]}),(0,S.jsxs)(`label`,{children:[J.activationSignature,(0,S.jsx)(`input`,{value:x.activationSignature,onChange:e=>ee({...x,activationSignature:e.target.value}),spellCheck:!1})]})]}),Mr&&(0,S.jsx)(`div`,{className:`errorPanel`,children:Mr}),Pr&&(0,S.jsx)(`div`,{className:`noticePanel`,children:Pr}),(0,S.jsx)(`button`,{className:`primary wide`,onClick:()=>void Ha(),disabled:Ar||Do||!x.email||x.password.length<12||v?.strict_authority&&(!x.activationPayload||!x.activationSignature),children:Ar?J.creatingOwner:J.createOwner})]}):(0,S.jsxs)(`section`,{className:`loginCard`,children:[(0,S.jsx)(`h1`,{children:J.signInTitle}),(0,S.jsxs)(`label`,{children:[J.email,(0,S.jsx)(`input`,{value:h.email,onChange:e=>g({...h,email:e.target.value.trim()}),autoComplete:`username`,autoCapitalize:`none`,autoCorrect:`off`,spellCheck:!1})]}),(0,S.jsxs)(`label`,{children:[J.password,(0,S.jsx)(`input`,{value:h.password,onChange:e=>g({...h,password:e.target.value}),type:h.showPassword?`text`:`password`,autoComplete:`current-password`,autoCapitalize:`none`,autoCorrect:`off`,spellCheck:!1,onKeyDown:e=>{e.key===`Enter`&&Va()}})]}),(0,S.jsxs)(`label`,{className:`checkLine`,children:[(0,S.jsx)(`input`,{type:`checkbox`,checked:h.showPassword,onChange:e=>g({...h,showPassword:e.target.checked})}),`Показать пароль`]}),(0,S.jsxs)(`label`,{className:`checkLine`,children:[(0,S.jsx)(`input`,{type:`checkbox`,checked:h.trustDevice,onChange:e=>g({...h,trustDevice:e.target.checked})}),J.trustDevice]}),(0,S.jsxs)(`label`,{className:`checkLine`,children:[(0,S.jsx)(`input`,{type:`checkbox`,checked:h.rememberMe,onChange:e=>g({...h,rememberMe:e.target.checked})}),J.rememberMe]}),Mr&&(0,S.jsx)(`div`,{className:`errorPanel`,children:Mr}),Pr&&(0,S.jsx)(`div`,{className:`noticePanel`,children:Pr}),(0,S.jsx)(`button`,{className:`primary wide`,onClick:()=>void Va(),disabled:Ar||!h.email||!h.password,children:Ar?J.signingIn:J.signIn})]})]});if(a&&!s)return(0,S.jsx)(`main`,{className:`loginShell`,children:(0,S.jsx)(`section`,{className:`loginCard`,children:(0,S.jsx)(`p`,{children:Ar?J.lastRefresh:`Восстанавливаем сессию...`})})});if(s===`user`){let e=cr.find(e=>e.id===Er)||cr[0]||null,t=e?fr.filter(t=>t.organization_id===e.id):fr,n=e?(mr[e.id]||[]).find(e=>e.user_id===a.userId):null,r=t.reduce((e,t)=>(e[t.protocol]=(e[t.protocol]||0)+1,e),{});return(0,S.jsxs)(`main`,{className:`portalShell`,children:[(0,S.jsxs)(`aside`,{className:`portalRail`,children:[(0,S.jsx)(`div`,{className:`brandMark`,children:`RAP`}),(0,S.jsx)(`p`,{className:`sideKicker`,children:`Личный кабинет`}),(0,S.jsx)(`h1`,{children:`Мой доступ`}),(0,S.jsx)(`p`,{className:`sideText`,children:`Установки, доступные серверы и состояние рабочей области пользователя.`}),(0,S.jsx)(A,{label:J.sessionMode,value:`${Oo} • ${l?Cn(l):`н/д`}`}),(0,S.jsx)(A,{label:J.actorUser,value:a.email}),(0,S.jsx)(`button`,{className:`ghost`,onClick:()=>void Ua(),disabled:Ar,children:J.logout})]}),(0,S.jsxs)(`section`,{className:`portalWorkspace`,children:[(0,S.jsxs)(`header`,{className:`portalTop`,children:[(0,S.jsxs)(`div`,{children:[(0,S.jsx)(`p`,{className:`eyebrow`,children:`Secure Access Fabric`}),(0,S.jsx)(`h2`,{children:e?.name||`Личный кабинет`}),(0,S.jsx)(`p`,{className:`muted`,children:a.email})]}),(0,S.jsxs)(`label`,{children:[`Организация`,(0,S.jsx)(`select`,{value:e?.id||``,onChange:e=>Dr(e.target.value),children:cr.map(e=>(0,S.jsx)(`option`,{value:e.id,children:e.name},e.id))})]}),(0,S.jsx)(`button`,{className:`primary`,onClick:()=>void wa(),disabled:Ar,children:Ar?J.refreshing:J.refresh})]}),Mr&&(0,S.jsx)(`div`,{className:`errorPanel`,children:Mr}),Pr&&(0,S.jsx)(`div`,{className:`noticePanel`,children:Pr}),(0,S.jsxs)(`section`,{className:`grid three`,children:[(0,S.jsx)(fe,{label:`Организации`,value:cr.length,tone:`steel`}),(0,S.jsx)(fe,{label:`Серверы`,value:t.length,tone:`green`}),(0,S.jsx)(fe,{label:`Установки`,value:2,tone:`amber`})]}),(0,S.jsxs)(`section`,{className:`grid two`,children:[(0,S.jsxs)(`article`,{className:`card`,children:[(0,S.jsxs)(`div`,{className:`cardHead`,children:[(0,S.jsxs)(`div`,{children:[(0,S.jsx)(`h3`,{children:`Установки`}),(0,S.jsx)(`p`,{className:`muted`,children:Yi?`Актуальная версия Android: ${Yi}`:`Скачивайте актуальные клиенты только отсюда, чтобы не ловить старую сборку.`})]}),(0,S.jsx)(`span`,{className:`status active`,children:`latest`})]}),(0,S.jsxs)(`div`,{className:`portalInstallList`,children:[(0,S.jsxs)(`a`,{className:`installTile primaryInstall`,href:ma,children:[(0,S.jsx)(`strong`,{children:`Android VPN`}),(0,S.jsx)(`span`,{children:`Последняя сборка RAP HOME VPN для телефона`}),(0,S.jsx)(`small`,{children:$i||pa})]}),(0,S.jsxs)(`a`,{className:`installTile`,href:`${la}/downloads/rap-windows-rdp-client-latest-win-x64.zip`,children:[(0,S.jsx)(`strong`,{children:`Windows RDP клиент`}),(0,S.jsx)(`span`,{children:`Клиент удаленного рабочего стола, когда нужен доступ к серверам`}),(0,S.jsx)(`small`,{children:`latest win-x64`})]})]})]}),(0,S.jsxs)(`article`,{className:`card`,children:[(0,S.jsx)(`h3`,{children:`Профиль`}),(0,S.jsx)(A,{label:`Пользователь`,value:a.email}),(0,S.jsx)(A,{label:`Роль в организации`,value:n?.role_id||`участник`}),(0,S.jsx)(A,{label:`Организация`,value:e?.name||`нет`}),(0,S.jsx)(A,{label:`Последнее обновление`,value:Sr?z(Sr):`нет`})]}),(0,S.jsxs)(`article`,{className:`card span2`,children:[(0,S.jsx)(`div`,{className:`cardHead`,children:(0,S.jsxs)(`div`,{children:[(0,S.jsx)(`h3`,{children:`Доступные серверы`}),(0,S.jsx)(`p`,{className:`muted`,children:`Список ресурсов, которые уже разрешены пользователю через организацию.`})]})}),(0,S.jsx)(M,{columns:[`имя`,`адрес`,`протокол`,`секрет`,`передача файлов`],rows:t.map(e=>[e.name,e.address,e.protocol,e.has_secret?`настроен`:`нет`,V(e.file_transfer_mode||`disabled`)])})]}),(0,S.jsxs)(`article`,{className:`card`,children:[(0,S.jsx)(`h3`,{children:`Сервисы`}),(0,S.jsx)(M,{columns:[`тип`,`количество`],rows:Object.entries(r).map(([e,t])=>[e,String(t)])})]}),(0,S.jsxs)(`article`,{className:`card`,children:[(0,S.jsx)(`h3`,{children:`Что здесь будет дальше`}),(0,S.jsxs)(`div`,{className:`portalRoadmap`,children:[(0,S.jsx)(`span`,{children:`Устройства и доверенные входы`}),(0,S.jsx)(`span`,{children:`Активные VPN/RDP сессии`}),(0,S.jsx)(`span`,{children:`Обновление профиля VPN без ручных ключей`}),(0,S.jsx)(`span`,{children:`Самостоятельная смена пароля`})]})]})]})]})]})}return(0,S.jsxs)(`main`,{className:`consoleShell`,children:[(0,S.jsxs)(`aside`,{className:`sideRail`,children:[(0,S.jsx)(`div`,{className:`brandMark`,children:`SAF`}),(0,S.jsx)(`p`,{className:`sideKicker`,children:J.productOwner}),(0,S.jsx)(`h1`,{children:J.controlPlane}),(0,S.jsx)(`p`,{className:`sideText`,children:J.sideText}),(0,S.jsx)(`nav`,{className:`railNav`,children:oe.filter(e=>e.id!==`roles`).map(e=>(0,S.jsx)(`button`,{className:w===e.id?`active`:``,onClick:()=>ae(e.id),children:e[d]},e.id))})]}),(0,S.jsxs)(`section`,{className:`workspace`,children:[(0,S.jsxs)(`header`,{className:`topBar`,children:[(0,S.jsxs)(`div`,{children:[(0,S.jsx)(`p`,{className:`eyebrow`,children:`Secure Access Fabric`}),(0,S.jsx)(`h2`,{children:sa?sa.name:J.consoleTitle}),(0,S.jsx)(`p`,{className:`muted`,children:J.boundary})]}),(0,S.jsxs)(`div`,{className:`clusterPicker`,children:[(0,S.jsxs)(`label`,{children:[J.activeCluster,(0,S.jsx)(`select`,{value:T,onChange:e=>void Wa(e.target.value),children:ge.map(e=>(0,S.jsx)(`option`,{value:e.id,children:e.name},e.id))})]}),(0,S.jsxs)(`span`,{children:[J.slugLabel,`: `,sa?.slug||`н/д`]})]}),(0,S.jsx)(`button`,{className:`primary`,onClick:()=>void Ca(),disabled:Ar,children:Ar?J.refreshing:J.refresh}),(0,S.jsxs)(`div`,{className:`refreshStatus`,children:[(0,S.jsx)(`strong`,{children:J.autoRefresh}),(0,S.jsx)(`span`,{children:Sr?`${J.lastRefresh}: ${Cn(Sr)} / ${wr.toUpperCase()}`:wr.toUpperCase()})]}),(0,S.jsxs)(`div`,{className:`profilePanel`,children:[(0,S.jsx)(`strong`,{children:J.profile}),(0,S.jsx)(`span`,{children:a.email}),(0,S.jsxs)(`span`,{children:[J.sessionMode,`: `,Oo,` | `,J.sessionRefreshedAt,`: `,l?Cn(l):`н/д`]}),(0,S.jsxs)(`label`,{children:[J.language,(0,S.jsxs)(`select`,{value:d,onChange:e=>f(e.target.value),children:[(0,S.jsx)(`option`,{value:`ru`,children:`Русский`}),(0,S.jsx)(`option`,{value:`en`,children:d===`ru`?`Английский`:`English`})]})]}),(0,S.jsx)(`button`,{className:`ghost`,onClick:()=>void Ua(),disabled:Ar,children:J.logout})]})]}),Mr&&(0,S.jsx)(`div`,{className:`errorPanel`,children:Mr}),Pr&&(0,S.jsx)(`div`,{className:`noticePanel`,children:Pr}),sa&&j.length===0&&(0,S.jsxs)(`div`,{className:`noticePanel`,children:[(0,S.jsxs)(`strong`,{children:[J.emptyLiveTitle,`.`]}),` `,J.emptyLiveText]}),w===`command`&&(0,S.jsxs)(`section`,{className:`grid five`,children:[(0,S.jsx)(fe,{label:`Кластеры`,value:ge.length,tone:`steel`}),(0,S.jsx)(fe,{label:`Узлы в области`,value:j.length,tone:`green`}),(0,S.jsx)(fe,{label:`Здоровые узлы`,value:Ka,tone:`green`}),(0,S.jsx)(fe,{label:`Ожидают подключения`,value:Ga,tone:`amber`}),(0,S.jsx)(fe,{label:`Рискованные состояния`,value:qa,tone:`red`}),(0,S.jsxs)(`article`,{className:`card span3`,children:[(0,S.jsx)(`h3`,{children:`Общее состояние кластеров`}),(0,S.jsx)(M,{columns:[`кластер`,`authority`,`ключ`,`режим изменений`,`узлы`,`заявки`,`роли`,`последний сигнал`],rows:Se.map(e=>[e.name,e.authority_state,R(e.cluster_key_fingerprint),e.mutation_mode,`${e.healthy_node_count}/${e.node_count}`,String(e.pending_join_count),String(e.active_role_assignment_count),z(e.last_node_seen_at)])})]}),(0,S.jsxs)(`article`,{className:`card span2`,children:[(0,S.jsx)(`h3`,{children:`Authority выбранного кластера`}),we?(0,S.jsxs)(`div`,{className:`stateList`,children:[(0,S.jsx)(A,{label:`Authority`,value:we.authority_state}),(0,S.jsx)(A,{label:`Режим изменений`,value:we.mutation_mode}),(0,S.jsx)(A,{label:`Терм`,value:String(we.term)}),(0,S.jsx)(A,{label:`Cluster key`,value:R(ca?.cluster_key_fingerprint)}),(0,S.jsx)(A,{label:`Обновлено`,value:z(we.updated_at)})]}):(0,S.jsx)(me,{title:`Нет состояния authority`,text:`Выберите кластер, чтобы загрузить состояние authority.`})]}),(0,S.jsxs)(`article`,{className:`card span2`,children:[(0,S.jsx)(`h3`,{children:`Граница платформы`}),(0,S.jsx)(`p`,{className:`muted`,children:`Эта панель предназначена для владельца продукта / владельца платформы. Панели организаций должны использовать безопасные проекции и не раскрывать mesh internals, peer cache, route cache, секреты или данные других tenants.`})]}),(0,S.jsxs)(`article`,{className:`card span3`,children:[(0,S.jsx)(`h3`,{children:`Текущие сигналы кластера`}),(0,S.jsxs)(`div`,{className:`signalStrip`,children:[(0,S.jsx)(O,{label:`Активные роли`,value:String(Ja)}),(0,S.jsx)(O,{label:`Отчеты сервисов`,value:String(Object.values(dt).filter(e=>e.length>0).length)}),(0,S.jsx)(O,{label:`Наблюдения связей`,value:String(zt.length)}),(0,S.jsx)(O,{label:`Synthetic configs`,value:`${Za}/${j.length}`})]})]})]}),w===`clusters`&&(0,S.jsxs)(`section`,{className:`grid two`,children:[(0,S.jsxs)(`article`,{className:`card span2`,children:[(0,S.jsxs)(`div`,{className:`cardHead`,children:[(0,S.jsxs)(`div`,{children:[(0,S.jsx)(`h3`,{children:J.clusterCatalog}),(0,S.jsx)(`p`,{className:`muted`,children:J.clusterCatalogText})]}),(0,S.jsx)(`span`,{className:`pill`,children:_n(ge.length,d)})]}),(0,S.jsxs)(`div`,{className:`clusterCatalog`,children:[ge.map(e=>{let t=Se.find(t=>t.cluster_id===e.id),n=e.id===T;return(0,S.jsxs)(`article`,{className:`clusterCard ${n?`selected`:``}`,children:[(0,S.jsxs)(`div`,{className:`clusterCardMain`,children:[(0,S.jsxs)(`div`,{children:[(0,S.jsx)(`p`,{className:`eyebrow`,children:e.region||`регион не задан`}),(0,S.jsx)(`h4`,{children:e.name}),(0,S.jsxs)(`p`,{className:`muted`,children:[J.slugLabel,`: `,(0,S.jsx)(`strong`,{children:e.slug})]})]}),(0,S.jsxs)(`div`,{className:`clusterCardActions`,children:[(0,S.jsx)(k,{value:e.status}),n?(0,S.jsx)(`span`,{className:`pill good`,children:J.selected}):(0,S.jsx)(`button`,{onClick:()=>void Wa(e.id),children:J.makeActive}),(0,S.jsx)(`button`,{className:`ghost`,onClick:()=>{Wa(e.id),ae(`cluster-settings`)},children:J.openSettings})]})]}),(0,S.jsxs)(`div`,{className:`signalStrip compact`,children:[(0,S.jsx)(O,{label:`Узлы`,value:t?`${t.healthy_node_count}/${t.node_count}`:`н/д`}),(0,S.jsx)(O,{label:`Заявки`,value:String(t?.pending_join_count??`н/д`)}),(0,S.jsx)(O,{label:`Роли`,value:String(t?.active_role_assignment_count??`н/д`)}),(0,S.jsx)(O,{label:`Последний сигнал`,value:z(t?.last_node_seen_at)})]}),(0,S.jsxs)(`details`,{children:[(0,S.jsx)(`summary`,{children:J.clusterDetails}),(0,S.jsxs)(`div`,{className:`stateList`,children:[(0,S.jsx)(A,{label:`ID`,value:e.id}),(0,S.jsx)(A,{label:J.slugLabel,value:e.slug}),(0,S.jsx)(A,{label:`Статус`,value:V(e.status)}),(0,S.jsx)(A,{label:`Authority`,value:t?`${t.authority_state}/${t.mutation_mode}`:`неизвестно`}),(0,S.jsx)(A,{label:`Создан`,value:z(e.created_at)}),(0,S.jsx)(A,{label:`Обновлен`,value:z(e.updated_at||e.created_at)})]})]})]},e.id)}),ge.length===0&&(0,S.jsx)(me,{title:`Кластеров нет`,text:`Создайте первый кластер, затем подключите стартовый node-agent.`})]})]}),(0,S.jsxs)(`article`,{className:`card`,children:[(0,S.jsx)(`h3`,{children:J.createCluster}),(0,S.jsxs)(je,{children:[(0,S.jsxs)(`label`,{children:[J.slugLabel,(0,S.jsx)(`input`,{value:Ir.slug,onChange:e=>Lr({...Ir,slug:e.target.value})})]}),(0,S.jsxs)(`label`,{children:[`Название`,(0,S.jsx)(`input`,{value:Ir.name,onChange:e=>Lr({...Ir,name:e.target.value})})]}),(0,S.jsxs)(`label`,{children:[`Регион`,(0,S.jsx)(`input`,{value:Ir.region,onChange:e=>Lr({...Ir,region:e.target.value})})]})]}),(0,S.jsx)(`p`,{className:`muted`,children:J.slugHelp}),(0,S.jsx)(`button`,{className:`primary`,disabled:!Ir.slug||!Ir.name,onClick:()=>void Y(async()=>{await q.createCluster({slug:Ir.slug,name:Ir.name,region:Ir.region||null}),Lr({slug:``,name:``,region:``})},`Кластер создан.`),children:J.createCluster})]}),(0,S.jsxs)(`article`,{className:`card`,children:[(0,S.jsx)(`h3`,{children:`Что такое технический код?`}),(0,S.jsx)(`p`,{className:`muted`,children:J.slugHelp}),(0,S.jsx)(`p`,{className:`muted`,children:`Для человека основное поле — название. Для системы и операторов — технический код. Он нужен, чтобы сценарии, логи и будущие endpoint-адреса не зависели от переименования кластера.`})]})]}),w===`cluster-settings`&&(0,S.jsxs)(`section`,{className:`grid two`,children:[!sa&&(0,S.jsx)(me,{title:`Кластер не выбран`,text:`Выберите активный кластер, чтобы открыть настройки.`}),sa&&(0,S.jsxs)(S.Fragment,{children:[(0,S.jsxs)(`article`,{className:`card`,children:[(0,S.jsx)(`h3`,{children:`Identity кластера`}),(0,S.jsx)(`p`,{className:`muted`,children:`Базовые параметры хранятся в PostgreSQL. Slug остается неизменяемым идентификатором для операторов и скриптов.`}),(0,S.jsxs)(je,{children:[(0,S.jsxs)(`label`,{children:[`ID`,(0,S.jsx)(`input`,{value:sa.id,readOnly:!0})]}),(0,S.jsxs)(`label`,{children:[`Slug`,(0,S.jsx)(`input`,{value:sa.slug,readOnly:!0})]}),(0,S.jsxs)(`label`,{children:[`Название`,(0,S.jsx)(`input`,{value:Rr.name,onChange:e=>zr({...Rr,name:e.target.value})})]}),(0,S.jsxs)(`label`,{children:[`Статус`,(0,S.jsxs)(`select`,{value:Rr.status,onChange:e=>zr({...Rr,status:e.target.value}),children:[(0,S.jsx)(`option`,{value:`active`,children:`active, работает`}),(0,S.jsx)(`option`,{value:`disabled`,children:`disabled, отключен`})]})]}),(0,S.jsxs)(`label`,{children:[`Регион`,(0,S.jsx)(`input`,{value:Rr.region,onChange:e=>zr({...Rr,region:e.target.value}),placeholder:`например ru-msk-1`})]}),(0,S.jsxs)(`label`,{children:[`Обновлен`,(0,S.jsx)(`input`,{value:z(sa.updated_at||sa.created_at),readOnly:!0})]})]}),(0,S.jsxs)(`label`,{className:`wideLabel`,children:[`Metadata JSON`,(0,S.jsx)(`textarea`,{value:Rr.metadataJson,onChange:e=>zr({...Rr,metadataJson:e.target.value}),rows:8,spellCheck:!1})]}),(0,S.jsx)(`button`,{className:`primary`,disabled:!Rr.name.trim(),onClick:()=>bn(`Сохранить базовые настройки кластера`)&&void Y(async()=>{let e=Me(Rr.metadataJson||`{}`,`Metadata JSON`);await q.updateCluster(sa.id,{name:Rr.name,status:Rr.status,region:Rr.region||null,metadata:e})},`Настройки кластера сохранены.`),children:`Сохранить настройки кластера`})]}),(0,S.jsxs)(`article`,{className:`card`,children:[(0,S.jsx)(`h3`,{children:`Authority и режим изменений`}),(0,S.jsx)(`p`,{className:`muted`,children:`Эта секция защищает кластер от split-brain: minority/read-only сегменты не должны принимать изменения политик.`}),(0,S.jsxs)(`div`,{className:`stateGrid`,children:[(0,S.jsx)(A,{label:`Authority`,value:we?.authority_state||`неизвестно`}),(0,S.jsx)(A,{label:`Mutation mode`,value:we?.mutation_mode||`неизвестно`}),(0,S.jsx)(A,{label:`Term`,value:String(we?.term??`н/д`)}),(0,S.jsx)(A,{label:`Cluster key`,value:R(ca?.cluster_key_fingerprint)}),(0,S.jsx)(A,{label:`Последнее изменение`,value:z(we?.updated_at)})]}),(0,S.jsxs)(je,{children:[(0,S.jsxs)(`label`,{children:[`Состояние authority`,(0,S.jsxs)(`select`,{value:Qr.authorityState,onChange:e=>$r({...Qr,authorityState:e.target.value}),children:[(0,S.jsx)(`option`,{value:`authoritative`,children:`authoritative, основной`}),(0,S.jsx)(`option`,{value:`minority`,children:`minority, меньшинство`}),(0,S.jsx)(`option`,{value:`isolated`,children:`isolated, изолирован`}),(0,S.jsx)(`option`,{value:`recovery`,children:`recovery, восстановление`})]})]}),(0,S.jsxs)(`label`,{children:[`Режим изменений`,(0,S.jsxs)(`select`,{value:Qr.mutationMode,onChange:e=>$r({...Qr,mutationMode:e.target.value}),children:[(0,S.jsx)(`option`,{value:`normal`,children:`normal, обычный`}),(0,S.jsx)(`option`,{value:`read_only`,children:`read_only, только чтение`}),(0,S.jsx)(`option`,{value:`recovery_override`,children:`recovery_override, восстановление`})]})]}),(0,S.jsxs)(`label`,{children:[`Примечание`,(0,S.jsx)(`input`,{value:Qr.notes,onChange:e=>$r({...Qr,notes:e.target.value})})]})]}),(0,S.jsx)(`button`,{disabled:!T,onClick:()=>bn(`Изменить authority state кластера`)&&void Y(()=>q.updateClusterAuthority(T,{authorityState:Qr.authorityState,mutationMode:Qr.mutationMode,notes:Qr.notes}),`Authority кластера обновлен.`),children:`Обновить authority`})]}),(0,S.jsxs)(`article`,{className:`card`,children:[(0,S.jsx)(`h3`,{children:`Safety / quorum`}),(0,S.jsxs)(`div`,{className:`stateGrid`,children:[(0,S.jsx)(A,{label:`Узлы`,value:String(ca?.node_count??j.length)}),(0,S.jsx)(A,{label:`Healthy`,value:String(ca?.healthy_node_count??Ka)}),(0,S.jsx)(A,{label:`Pending join`,value:String(ca?.pending_join_count??Fe.filter(e=>e.status===`pending`).length)}),(0,S.jsx)(A,{label:`Последний узел`,value:z(ca?.last_node_seen_at)})]}),(0,S.jsx)(`p`,{className:`muted`,children:`Минимальный размер, quorum policy и split-brain rules пока не имеют отдельного runtime-переключателя. Сейчас защита выполняется через authority/mutation mode, explicit node approval и аудит.`})]}),(0,S.jsxs)(`article`,{className:`card`,children:[(0,S.jsx)(`h3`,{children:`Telemetry / testing`}),(0,S.jsxs)(`div`,{className:`stateGrid`,children:[(0,S.jsx)(A,{label:`Telemetry flag`,value:Ya?.telemetry_enabled?`включен`:`выключен`}),(0,S.jsx)(A,{label:`Synthetic links`,value:Ya?.synthetic_links_enabled?`включены`:`выключены`}),(0,S.jsx)(A,{label:`Хранение истории, часов`,value:String(Ya?.history_retention_hours??`н/д`)})]}),(0,S.jsx)(`p`,{className:`muted`,children:`Это тестовый контур наблюдаемости: heartbeat/telemetry реальные, а связи Fabric сейчас synthetic. Production mesh traffic здесь пока не отображается.`})]}),(0,S.jsxs)(`article`,{className:`card`,children:[(0,S.jsx)(`h3`,{children:`Storage / updates`}),(0,S.jsxs)(`div`,{className:`stateGrid`,children:[(0,S.jsx)(A,{label:`Version Storage`,value:`архитектура зафиксирована, runtime не реализован`}),(0,S.jsx)(A,{label:`Update cache`,value:`${yt(`update-cache`,ot).length} узл.`}),(0,S.jsx)(A,{label:`File/config cache`,value:`${yt(`file-storage-cache`,ot).length} узл.`})]}),(0,S.jsx)(`p`,{className:`muted`,children:`Version Storage будет хранить stable/current/candidate и signed artifacts. Сейчас это не production updater runtime.`})]}),(0,S.jsxs)(`article`,{className:`card`,children:[(0,S.jsx)(`h3`,{children:`Admin endpoints`}),(0,S.jsxs)(`div`,{className:`stateGrid`,children:[(0,S.jsx)(A,{label:`Entry nodes`,value:`${yt(`entry-node`,ot).length} узл.`}),(0,S.jsx)(A,{label:`Relay nodes`,value:`${yt(`relay-node`,ot).length} узл.`}),(0,S.jsx)(A,{label:`Core mesh`,value:`${yt(`core-mesh`,ot).length} узл.`})]}),(0,S.jsx)(`p`,{className:`muted`,children:`Панель кластера не переезжает автоматически на storage-узел. Cluster Admin Endpoint должен быть назначен отдельной explicit ролью на ingress/admin-capable узле.`})]})]})]}),w===`nodes`&&(0,S.jsxs)(`section`,{className:`grid two`,children:[(0,S.jsxs)(`article`,{className:`card span2`,children:[(0,S.jsxs)(`div`,{className:`cardHead`,children:[(0,S.jsxs)(`div`,{children:[(0,S.jsx)(`h3`,{children:J.nodeManagement}),(0,S.jsx)(`p`,{className:`muted`,children:`Единый краткий список узлов. По умолчанию показан активный кластер; включите общий режим, чтобы увидеть весь инвентарь платформы.`})]}),(0,S.jsxs)(`div`,{className:`actions`,children:[(0,S.jsxs)(`label`,{className:`checkLine`,children:[(0,S.jsx)(`input`,{type:`checkbox`,checked:ni===`all`,onChange:e=>ri(e.target.checked?`all`:`cluster`)}),J.showAllPlatformNodes]}),(0,S.jsx)(`button`,{className:`ghost`,onClick:()=>{ri(`all`),ai(``)},children:J.showAllPlatformNodes})]})]}),(0,S.jsxs)(`div`,{className:`signalStrip compact`,children:[(0,S.jsx)(O,{label:`Узлы активного кластера`,value:String(j.length)}),(0,S.jsx)(O,{label:`Все узлы`,value:String(_a.length)}),(0,S.jsx)(O,{label:`Заявки`,value:String(Ga)}),(0,S.jsx)(O,{label:`Активные роли`,value:String(Ja)})]}),(0,S.jsx)(`p`,{className:`muted`,children:J.addNodeText})]}),(0,S.jsxs)(`article`,{className:`card span2`,children:[(0,S.jsxs)(`div`,{className:`cardHead`,children:[(0,S.jsxs)(`div`,{children:[(0,S.jsx)(`h3`,{children:J.nodeBriefList}),(0,S.jsx)(`p`,{className:`muted`,children:J.nodeBriefListHelp})]}),(0,S.jsx)(`span`,{className:`pill`,children:va.length})]}),(0,S.jsxs)(je,{children:[(0,S.jsxs)(`label`,{children:[J.nodeSearch,(0,S.jsx)(`input`,{value:ii,onChange:e=>ai(e.target.value),placeholder:J.nodeSearchPlaceholder})]}),(0,S.jsxs)(`label`,{children:[J.nodeGroupFilter,(0,S.jsxs)(`select`,{value:oi,onChange:e=>si(e.target.value),children:[(0,S.jsx)(`option`,{value:``,children:J.allNodeGroups}),De.map(e=>(0,S.jsx)(`option`,{value:e.id,children:ht(e,De)},e.id))]})]})]}),(0,S.jsx)(`p`,{className:`muted`,children:J.nodeGroupInventoryText}),(0,S.jsx)(`h4`,{children:J.nodeGroupCreatePanel}),(0,S.jsxs)(je,{children:[(0,S.jsxs)(`label`,{children:[J.nodeGroupName,(0,S.jsx)(`input`,{value:Br.name,onChange:e=>Vr({...Br,name:e.target.value})})]}),(0,S.jsxs)(`label`,{children:[J.parentNodeGroup,(0,S.jsxs)(`select`,{value:Br.parentGroupId,onChange:e=>Vr({...Br,parentGroupId:e.target.value}),children:[(0,S.jsx)(`option`,{value:``,children:J.rootNodeGroup}),De.map(e=>(0,S.jsx)(`option`,{value:e.id,children:ht(e,De)},e.id))]})]}),(0,S.jsxs)(`label`,{children:[J.createNodeGroup,(0,S.jsx)(`button`,{className:`primary`,disabled:!Br.name.trim(),onClick:()=>void Y(async()=>{await q.createNodeGroup(T,{name:Br.name,parentGroupId:Br.parentGroupId||null}),Vr({name:``,parentGroupId:``})},J.nodeGroupCreated),children:J.createNodeGroup})]})]}),(0,S.jsxs)(`div`,{className:`nodeList`,children:[xa.map(e=>{if(e.kind===`group`){let t=ci.includes(e.key);return(0,S.jsxs)(`div`,{className:`nodeListGroup`,style:{paddingLeft:`${e.depth*18}px`},children:[(0,S.jsxs)(`div`,{className:`nodeListMain`,children:[(0,S.jsx)(`strong`,{children:e.label}),e.groupId&&(0,S.jsx)(`span`,{children:gt(e.groupId,De)})]}),(0,S.jsxs)(`div`,{className:`actions`,children:[(0,S.jsx)(`span`,{className:`pill`,children:e.count}),(0,S.jsx)(`button`,{className:`ghost`,onClick:()=>li(en(ci,e.key)),children:t?J.expandGroup:J.collapseGroup}),e.groupId&&(0,S.jsx)(`button`,{className:`ghost`,onClick:()=>Vr({name:``,parentGroupId:e.groupId||``}),children:J.createSubgroup})]})]},e.key)}let t=e.entry,n=t.memberships.find(e=>e.cluster.id===T),r=n?.node||t.node,i=ct(r,Et[r.id]||[],zt),a=Re(r,$e[r.id],Ue),o=Ye(rt[r.id]||[]),s=n?.node.membership_status===`active`,c=n?.node.membership_status===`revoked`;return(0,S.jsxs)(`div`,{className:`nodeListRow`,style:{marginLeft:`${e.depth*18}px`},children:[(0,S.jsxs)(`div`,{className:`nodeListMain`,children:[(0,S.jsx)(`strong`,{children:r.name}),(0,S.jsx)(`span`,{children:r.node_key}),(0,S.jsx)(`small`,{className:`muted`,children:i.address})]}),(0,S.jsx)(k,{value:r.health_status}),(0,S.jsx)(ye,{runtime:i}),(0,S.jsxs)(`div`,{className:`nodeEndpointCell`,children:[(0,S.jsx)(`strong`,{children:r.reported_version||`версия неизвестна`}),(0,S.jsx)(`small`,{children:a.targetLabel})]}),(0,S.jsx)(k,{value:a.status}),(0,S.jsxs)(`div`,{className:`nodeEndpointCell`,children:[(0,S.jsx)(`strong`,{className:`pill ${o.tone}`,children:o.label}),(0,S.jsx)(`small`,{children:o.detail})]}),(0,S.jsx)(`span`,{className:`muted`,children:z(r.last_seen_at)}),n?(0,S.jsx)(k,{value:n.node.membership_status}):(0,S.jsx)(`span`,{className:`muted`,children:J.notMemberOfActiveCluster}),(0,S.jsxs)(`div`,{className:`actions`,children:[(0,S.jsx)(`button`,{onClick:()=>{_i(t),yi(`details`)},children:J.nodeDetails}),s?(0,S.jsxs)(S.Fragment,{children:[(0,S.jsx)(`button`,{className:`primary`,onClick:()=>{_i(t),yi(`manage`)},children:J.manageNode}),(0,S.jsx)(`button`,{className:`danger`,onClick:()=>bn(`Удалить узел ${r.name} из кластера`)&&void Y(()=>q.deleteClusterNode(T,r.id,`Удалено из списка узлов панели владельца платформы.`),`Узел удален из кластера.`),children:`Удалить`})]}):c?(0,S.jsx)(`span`,{className:`muted`,children:J.revokedMembership}):(0,S.jsx)(`button`,{className:`primary`,onClick:()=>{pi(t),hi([])},children:J.connectExistingNode})]})]},e.key)}),xa.length===0&&(0,S.jsx)(me,{title:J.noNodesTitle,text:J.noNodesByFilter})]})]}),fi&&(0,S.jsx)(`div`,{className:`modalBackdrop`,role:`presentation`,children:(0,S.jsxs)(`div`,{className:`modalCard`,role:`dialog`,"aria-modal":`true`,"aria-labelledby":`attach-node-title`,children:[(0,S.jsxs)(`div`,{className:`cardHead`,children:[(0,S.jsxs)(`div`,{children:[(0,S.jsx)(`h3`,{id:`attach-node-title`,children:J.connectExistingNodeTitle}),(0,S.jsx)(`p`,{className:`muted`,children:J.connectExistingNodeText})]}),(0,S.jsx)(`button`,{className:`ghost`,onClick:()=>pi(null),children:J.cancel})]}),(0,S.jsxs)(`div`,{className:`stateList`,children:[(0,S.jsx)(A,{label:`Узел`,value:fi.node.name}),(0,S.jsx)(A,{label:`Node key`,value:fi.node.node_key}),(0,S.jsx)(A,{label:J.activeCluster,value:sa?.name||T})]}),(0,S.jsx)(`div`,{className:`checkGrid`,children:ie.map(e=>(0,S.jsxs)(`label`,{className:`checkLine`,children:[(0,S.jsx)(`input`,{type:`checkbox`,checked:mi.includes(e),onChange:()=>hi(en(mi,e))}),Ie(e)]},e))}),(0,S.jsxs)(`div`,{className:`actions`,children:[(0,S.jsx)(`button`,{className:`primary`,onClick:()=>void Y(async()=>{await q.attachExistingNode(T,fi.node.id,mi),pi(null),hi([]),ri(`cluster`)},`Узел подключен к активному кластеру.`),children:J.connectWithRoles}),(0,S.jsx)(`button`,{onClick:()=>pi(null),children:J.cancel})]})]})}),gi&&(()=>{let e=gi.memberships.find(e=>e.cluster.id===T),t=e?.node||gi.node,n=e?(Et[t.id]||[])[0]:void 0,r=e?(ot[t.id]||[]).filter(e=>e.status===`active`):[],i=e&<[t.id]||[],a=e&&dt[t.id]||[];return(0,S.jsx)(`div`,{className:`modalBackdrop`,role:`presentation`,children:(0,S.jsxs)(`div`,{className:`modalCard wide`,role:`dialog`,"aria-modal":`true`,"aria-labelledby":`node-info-title`,children:[(0,S.jsxs)(`div`,{className:`cardHead`,children:[(0,S.jsxs)(`div`,{children:[(0,S.jsxs)(`h3`,{id:`node-info-title`,children:[vi===`manage`?J.manageNode:J.nodeDetails,`: `,t.name]}),(0,S.jsx)(`p`,{className:`muted`,children:t.node_key})]}),(0,S.jsx)(`button`,{className:`ghost`,onClick:()=>{_i(null),yi(`details`)},children:J.close})]}),(0,S.jsxs)(`section`,{className:`nodePanel`,children:[(0,S.jsx)(`h4`,{children:J.nodeIdentity}),(0,S.jsxs)(`div`,{className:`stateList`,children:[(0,S.jsx)(A,{label:`Node ID`,value:R(t.id)}),(0,S.jsx)(A,{label:`Ключ узла`,value:t.node_key}),(0,S.jsx)(A,{label:`Тип владения`,value:V(t.ownership_type)}),(0,S.jsx)(A,{label:`Owner org`,value:R(t.owner_organization_id)}),(0,S.jsx)(A,{label:`Регистрация`,value:V(t.registration_status)}),(0,S.jsx)(A,{label:`Здоровье`,value:V(t.health_status)}),(0,S.jsx)(A,{label:`Версия`,value:t.reported_version||`неизвестно`}),(0,S.jsx)(A,{label:`Последний сигнал`,value:z(t.last_seen_at)})]})]}),(0,S.jsxs)(`section`,{className:`nodePanel`,children:[(0,S.jsx)(`h4`,{children:J.clusterMemberships}),(0,S.jsx)(`div`,{className:`membershipList`,children:gi.memberships.map(e=>(0,S.jsxs)(`span`,{className:e.cluster.id===T?`pill good`:`pill`,children:[e.cluster.name,`: `,V(e.node.membership_status)]},e.cluster.id))})]}),e?(0,S.jsxs)(S.Fragment,{children:[(0,S.jsxs)(`section`,{className:`nodePanel`,children:[(0,S.jsx)(`h4`,{children:J.activeClusterScope}),(0,S.jsxs)(`div`,{className:`stateList`,children:[(0,S.jsx)(A,{label:`Участие`,value:V(t.membership_status)}),(0,S.jsx)(A,{label:`Сегмент`,value:V(t.partition_state)}),(0,S.jsx)(A,{label:`Группа`,value:t.node_group_name||J.ungroupedNodes}),(0,S.jsx)(A,{label:`Ролей`,value:String(r.length)}),(0,S.jsx)(A,{label:`Desired-сервисов`,value:String(i.length)}),(0,S.jsx)(A,{label:`Observed-сервисов`,value:String(a.length)})]})]}),vi===`details`&&(0,S.jsx)(_e,{node:t,memberships:gi.memberships,activeRoles:r,desiredWorkloads:i,observedWorkloads:a,heartbeats:Et[t.id]||[],telemetry:Lt[t.id]||[],updatePlan:$e[t.id],updateStatuses:rt[t.id]||[],meshLinks:zt.filter(e=>e.source_node_id===t.id||e.target_node_id===t.id),syntheticConfig:Ut[t.id],allNodes:j,onSetUpdatePolicy:(e,t,n)=>void Y(async()=>{await q.upsertNodeUpdatePolicy(T,e.id,{product:t,channel:`dev`,targetVersion:n,strategy:`rolling`,enabled:!0,rollbackAllowed:!0,healthWindowSeconds:180})},n?`${t} поставлен в target ${n}.`:`${t} будет следовать latest dev.`),labels:J}),vi===`manage`&&(0,S.jsxs)(`section`,{className:`nodePanel`,children:[(0,S.jsx)(`h4`,{children:J.nodeFunctions}),(0,S.jsx)(`p`,{className:`muted`,children:J.nodeFunctionsText}),(0,S.jsxs)(`label`,{className:`wideLabel`,children:[J.organizationScopeForEnable,(0,S.jsx)(`input`,{value:ei,onChange:e=>ti(e.target.value),placeholder:J.clusterWideRolePlaceholder})]}),(0,S.jsx)(`div`,{className:`functionList`,children:ie.map(e=>{let o=r.find(t=>t.role===e),s=i.find(t=>t.service_type===e),c=a.find(t=>t.service_type===e),l=sn(e,n),u=s?.desired_state||`not_configured`,f=c?.reported_state||`missing`,p=!!o&&u===`enabled`;return(0,S.jsxs)(`div`,{className:`functionRow`,children:[(0,S.jsxs)(`div`,{className:`nodeListMain`,children:[(0,S.jsx)(`strong`,{children:Ie(e)}),(0,S.jsx)(`span`,{children:ln(e,n,d)})]}),(0,S.jsx)(pe,{label:J.rolePermission,value:o?J.permissionGranted:J.permissionDenied,tone:o?`info`:``}),(0,S.jsx)(pe,{label:J.desiredRuntime,value:V(u),tone:u===`enabled`?`good`:``}),(0,S.jsx)(pe,{label:J.observedRuntime,value:V(f),tone:f===`running`?`good`:f===`missing`?`warn`:``}),(0,S.jsx)(`span`,{className:`pill ${l}`,children:cn(e,n,J)}),(0,S.jsxs)(`div`,{className:`actions`,children:[(0,S.jsx)(`button`,{className:p?``:`primary`,disabled:p,onClick:()=>void Y(async()=>{o||await q.setRoleStatus(T,t.id,e,`active`,ei||void 0),await q.setDesiredWorkload(T,t.id,e,{desiredState:`enabled`,runtimeMode:`container`,config:{},environment:{}})},`${e}: функция включена.`),children:J.enableFunction}),(0,S.jsx)(`button`,{disabled:!o&&u!==`enabled`,onClick:()=>void Y(async()=>{await q.setDesiredWorkload(T,t.id,e,{desiredState:`disabled`,runtimeMode:s?.runtime_mode||`container`,config:s?.config||{},environment:s?.environment||{}}),o&&await q.setRoleStatus(T,t.id,e,`disabled`,o.organization_id||void 0)},`${e}: функция выключена.`),children:J.disableFunction})]})]},e)})}),(()=>{let e=i.find(e=>e.service_type===`mesh-listener`)?.config||{},n=wi[t.id]||{listenAddr:String(e.listen_addr||`:19131`),mode:String(e.listen_port_mode||`auto`),autoRange:`${Number(e.auto_port_start||19131)}-${Number(e.auto_port_end||19231)}`,advertiseEndpoint:String(e.advertise_endpoint||``),advertiseTransport:String(e.advertise_transport||`direct_http`),connectivity:String(e.connectivity_mode||`private_lan`),nat:String(e.nat_type||`none`),region:String(e.region||``)},r=e=>Ti({...wi,[t.id]:{...n,...e}});return(0,S.jsxs)(`section`,{className:`nodePanel nestedPanel`,children:[(0,S.jsx)(`h4`,{children:`Mesh listener`}),(0,S.jsxs)(je,{children:[(0,S.jsxs)(`label`,{children:[`Listen addr`,(0,S.jsx)(`input`,{value:n.listenAddr,onChange:e=>r({listenAddr:e.target.value}),placeholder:`0.0.0.0:19131 или :19131`})]}),(0,S.jsxs)(`label`,{children:[`Port mode`,(0,S.jsxs)(`select`,{value:n.mode,onChange:e=>r({mode:e.target.value}),children:[(0,S.jsx)(`option`,{value:`auto`,children:`auto`}),(0,S.jsx)(`option`,{value:`manual`,children:`manual`}),(0,S.jsx)(`option`,{value:`disabled`,children:`disabled`})]})]}),(0,S.jsxs)(`label`,{children:[`Auto ports`,(0,S.jsx)(`input`,{value:n.autoRange,onChange:e=>r({autoRange:e.target.value}),placeholder:`19131-19231`})]}),(0,S.jsxs)(`label`,{children:[`Advertise endpoint`,(0,S.jsx)(`input`,{value:n.advertiseEndpoint,onChange:e=>r({advertiseEndpoint:e.target.value}),placeholder:`http://external-or-lan-ip:19131`})]}),(0,S.jsxs)(`label`,{children:[`Advertise transport`,(0,S.jsxs)(`select`,{value:n.advertiseTransport,onChange:e=>r({advertiseTransport:e.target.value}),children:[(0,S.jsx)(`option`,{value:`direct_http`,children:`direct_http`}),(0,S.jsx)(`option`,{value:`direct_https`,children:`direct_https`}),(0,S.jsx)(`option`,{value:`wss`,children:`wss`})]})]}),(0,S.jsxs)(`label`,{children:[`Connectivity`,(0,S.jsxs)(`select`,{value:n.connectivity,onChange:e=>r({connectivity:e.target.value}),children:[(0,S.jsx)(`option`,{value:`private_lan`,children:`private_lan`}),(0,S.jsx)(`option`,{value:`direct`,children:`direct`}),(0,S.jsx)(`option`,{value:`outbound_only`,children:`outbound_only`}),(0,S.jsx)(`option`,{value:`relay_required`,children:`relay_required`})]})]}),(0,S.jsxs)(`label`,{children:[`NAT`,(0,S.jsxs)(`select`,{value:n.nat,onChange:e=>r({nat:e.target.value}),children:[(0,S.jsx)(`option`,{value:`none`,children:`none`}),(0,S.jsx)(`option`,{value:`unknown`,children:`unknown`}),(0,S.jsx)(`option`,{value:`port_restricted`,children:`port_restricted`}),(0,S.jsx)(`option`,{value:`symmetric`,children:`symmetric`})]})]}),(0,S.jsxs)(`label`,{children:[`Region/site`,(0,S.jsx)(`input`,{value:n.region,onChange:e=>r({region:e.target.value}),placeholder:`dc1, office, docker-test`})]})]}),(0,S.jsx)(`div`,{className:`actions`,children:(0,S.jsx)(`button`,{className:`primary`,onClick:()=>void Y(async()=>{let[e,r]=n.autoRange.split(`-`).map(e=>Number(e.trim())),i=Number.isFinite(e)?e:19131,a=Number.isFinite(r)?r:i;await q.setDesiredWorkload(T,t.id,`mesh-listener`,{desiredState:n.mode===`disabled`?`disabled`:`enabled`,version:`listener-${Date.now()}`,runtimeMode:`container`,config:{listen_addr:n.listenAddr,listen_port_mode:n.mode,auto_port_start:i,auto_port_end:a,advertise_endpoint:n.advertiseEndpoint.trim().replace(/\/$/,``)||null,advertise_transport:n.advertiseTransport||`direct_http`,connectivity_mode:n.connectivity,nat_type:n.nat,region:n.region||null},environment:{}})},`Mesh listener config обновлен.`),children:`Применить listener`})})]})})(),(0,S.jsx)(`div`,{className:`actions`,children:(0,S.jsxs)(`select`,{value:t.node_group_id||``,onChange:e=>void Y(()=>q.assignNodeGroup(T,t.id,e.target.value||null),e.target.value?`Узел перемещен в группу.`:`Узел убран из группы.`),children:[(0,S.jsx)(`option`,{value:``,children:J.ungroupedNodes}),De.map(e=>(0,S.jsx)(`option`,{value:e.id,children:ht(e,De)},e.id))]})}),(0,S.jsxs)(`div`,{className:`actions`,children:[(0,S.jsx)(`button`,{onClick:()=>bn(`Отключить участие узла ${t.name}`)&&void Y(()=>q.disableMembership(T,t.id,`Отключено из панели владельца платформы.`),`Участие узла отключено.`),children:`Отключить участие`}),(0,S.jsx)(`button`,{className:`danger`,onClick:()=>bn(`Отозвать identity узла ${t.name}`)&&void Y(()=>q.revokeNodeIdentity(T,t.id,`Отозвано из панели владельца платформы.`),`Identity узла отозван.`),children:`Отозвать identity`})]})]})]}):(0,S.jsxs)(`section`,{className:`nodePanel`,children:[(0,S.jsx)(`h4`,{children:J.noActiveClusterMembership}),(0,S.jsx)(`div`,{className:`actions`,children:(0,S.jsx)(`button`,{className:`primary`,onClick:()=>{pi(gi),hi([]),_i(null)},children:J.connectExistingNode})})]})]})})})(),!1]}),w===`enrollment`&&(0,S.jsxs)(`section`,{className:`grid two`,children:[(0,S.jsxs)(`article`,{className:`card`,children:[(0,S.jsx)(`h3`,{children:J.joinTokenTitle}),(0,S.jsx)(`p`,{className:`muted`,children:J.joinTokenText}),(0,S.jsxs)(je,{children:[(0,S.jsxs)(`label`,{children:[J.ttlHours,(0,S.jsx)(`input`,{type:`number`,min:1,max:720,value:H.ttlHours,onChange:e=>U({...H,ttlHours:Number(e.target.value)})}),(0,S.jsx)(`small`,{children:J.ttlHelp})]}),(0,S.jsxs)(`label`,{children:[J.maxUses,(0,S.jsx)(`input`,{type:`number`,min:1,max:100,value:H.maxUses,onChange:e=>U({...H,maxUses:Number(e.target.value)})}),(0,S.jsx)(`small`,{children:J.maxUsesHelp})]}),(0,S.jsxs)(`label`,{children:[J.nodeOwnership,(0,S.jsxs)(`select`,{value:H.ownershipType,onChange:e=>U({...H,ownershipType:e.target.value}),children:[(0,S.jsx)(`option`,{value:`platform_managed`,children:`platform_managed, управляется платформой`}),(0,S.jsx)(`option`,{value:`customer_managed`,children:`customer_managed, управляется клиентом`})]})]}),(0,S.jsxs)(`label`,{children:[J.tokenPurpose,(0,S.jsx)(`input`,{value:H.purpose,onChange:e=>U({...H,purpose:e.target.value}),placeholder:`например: стартовый entry-node в ru-msk-1`})]}),(0,S.jsxs)(`label`,{children:[`Имя нового узла`,(0,S.jsx)(`input`,{value:H.nodeName,onChange:e=>U({...H,nodeName:e.target.value}),placeholder:At(H,sa)}),(0,S.jsx)(`small`,{children:`Если оставить пустым, панель подставит имя автоматически.`})]}),(0,S.jsxs)(`label`,{children:[`Группа узла`,(0,S.jsxs)(`select`,{value:H.nodeGroupId,onChange:e=>U({...H,nodeGroupId:e.target.value}),children:[(0,S.jsx)(`option`,{value:``,children:`Без группы`}),De.map(e=>(0,S.jsx)(`option`,{value:e.id,children:ht(e,De)},e.id))]})]})]}),(0,S.jsxs)(`section`,{className:`nodePanel`,children:[(0,S.jsx)(`h4`,{children:`Install profile`}),(0,S.jsx)(`p`,{className:`muted`,children:`Эти поля попадут в install profile. Для Windows без админ-прав будет создан user startup task, с админ-правами - system startup task.`}),(0,S.jsx)(`div`,{className:`segmented`,children:[[`docker`,`Docker Linux`],[`linux_binary`,`Ubuntu service`],[`windows_service`,`Windows`]].map(([e,t])=>(0,S.jsx)(`button`,{type:`button`,className:H.installMode===e?`active`:``,onClick:()=>U({...H,installMode:e}),children:t},e))}),(0,S.jsx)(`div`,{className:`segmented`,children:[[`private_lan`,`LAN`],[`direct`,`Public`],[`nat_forward`,`NAT`],[`outbound_only`,`Outbound`]].map(([e,t])=>(0,S.jsx)(`button`,{type:`button`,className:Ot(H)===e?`active`:``,onClick:()=>U(kt(H,e)),children:t},e))}),(0,S.jsxs)(je,{children:[(0,S.jsxs)(`label`,{children:[`Control-plane endpoint`,(0,S.jsx)(`input`,{value:H.controlPlaneEndpoint,onChange:e=>U({...H,controlPlaneEndpoint:e.target.value}),placeholder:wt()})]}),(0,S.jsxs)(`label`,{children:[H.installMode===`windows_service`?`Windows node-agent artifact`:H.installMode===`linux_binary`?`Linux node-agent artifact`:`Docker image`,(0,S.jsx)(`input`,{value:H.dockerImage,onChange:e=>U({...H,dockerImage:e.target.value})})]}),H.installMode===`windows_service`&&(0,S.jsxs)(S.Fragment,{children:[(0,S.jsxs)(`label`,{children:[`Windows startup`,(0,S.jsxs)(`select`,{value:H.windowsStartupMode,onChange:e=>U({...H,windowsStartupMode:e.target.value}),children:[(0,S.jsx)(`option`,{value:`auto`,children:`auto: system task, fallback user task`}),(0,S.jsx)(`option`,{value:`system-task`,children:`system task, admin required`}),(0,S.jsx)(`option`,{value:`user-task`,children:`user task, no admin`}),(0,S.jsx)(`option`,{value:`none`,children:`none`})]})]}),(0,S.jsxs)(`label`,{children:[`Install dir`,(0,S.jsx)(`input`,{value:H.windowsInstallDir,onChange:e=>U({...H,windowsInstallDir:e.target.value}),placeholder:`C:\\\\Program Files\\\\RAP\\\\node-name`})]}),(0,S.jsxs)(`label`,{children:[`Windows node-agent SHA256`,(0,S.jsx)(`input`,{value:H.windowsNodeAgentSHA256,onChange:e=>U({...H,windowsNodeAgentSHA256:e.target.value}),placeholder:`опционально, но желательно для production`})]})]}),H.installMode===`linux_binary`&&(0,S.jsxs)(S.Fragment,{children:[(0,S.jsxs)(`label`,{children:[`Linux install dir`,(0,S.jsx)(`input`,{value:H.linuxInstallDir,onChange:e=>U({...H,linuxInstallDir:e.target.value}),placeholder:`/opt/rap/node-name`})]}),(0,S.jsxs)(`label`,{children:[`Linux node-agent SHA256`,(0,S.jsx)(`input`,{value:H.linuxNodeAgentSHA256,onChange:e=>U({...H,linuxNodeAgentSHA256:e.target.value}),placeholder:`опционально, но желательно для production`})]})]}),H.installMode===`docker`&&(0,S.jsxs)(`label`,{children:[`Container name`,(0,S.jsx)(`input`,{value:H.dockerContainerName,onChange:e=>U({...H,dockerContainerName:e.target.value}),placeholder:jt(H,sa)})]}),(0,S.jsxs)(`label`,{children:[`Artifact endpoints`,(0,S.jsx)(`input`,{value:H.artifactEndpoints,onChange:e=>U({...H,artifactEndpoints:e.target.value}),placeholder:Tt()}),(0,S.jsx)(`small`,{children:`Через запятую: public/LAN/cache узлы, где host-agent сможет скачать image tar до входа в mesh.`})]}),H.installMode===`docker`&&(0,S.jsxs)(`label`,{children:[`Docker image tar SHA256`,(0,S.jsx)(`input`,{value:H.dockerImageArtifactSHA256,onChange:e=>U({...H,dockerImageArtifactSHA256:e.target.value}),placeholder:`опционально, но желательно для production`})]}),H.installMode===`docker`&&(0,S.jsxs)(`label`,{children:[`Docker network`,(0,S.jsxs)(`select`,{value:H.dockerNetwork,onChange:e=>U({...H,dockerNetwork:e.target.value}),children:[(0,S.jsx)(`option`,{value:`host`,children:`host`}),(0,S.jsx)(`option`,{value:`bridge`,children:`bridge`})]})]}),(0,S.jsxs)(`label`,{children:[`Listen addr`,(0,S.jsx)(`input`,{value:H.meshListenAddr,onChange:e=>U({...H,meshListenAddr:e.target.value})})]}),(0,S.jsxs)(`label`,{children:[`Listen mode`,(0,S.jsxs)(`select`,{value:H.meshListenPortMode,onChange:e=>U({...H,meshListenPortMode:e.target.value}),children:[(0,S.jsx)(`option`,{value:`auto`,children:`auto`}),(0,S.jsx)(`option`,{value:`manual`,children:`manual`}),(0,S.jsx)(`option`,{value:`disabled`,children:`disabled`})]})]}),(0,S.jsxs)(`label`,{children:[`Auto ports`,(0,S.jsx)(`input`,{value:`${H.meshListenAutoPortStart}-${H.meshListenAutoPortEnd}`,onChange:e=>{let[t,n]=e.target.value.split(`-`).map(e=>Number(e.trim()));U({...H,meshListenAutoPortStart:Number.isFinite(t)?t:H.meshListenAutoPortStart,meshListenAutoPortEnd:Number.isFinite(n)?n:H.meshListenAutoPortEnd})}})]}),(0,S.jsxs)(`label`,{children:[`Advertise endpoint`,(0,S.jsx)(`input`,{value:H.meshAdvertiseEndpoint,onChange:e=>U({...H,meshAdvertiseEndpoint:e.target.value}),placeholder:`http://public-or-private-ip:19131`})]}),(0,S.jsxs)(`label`,{children:[`Connectivity`,(0,S.jsxs)(`select`,{value:H.meshConnectivityMode,onChange:e=>U({...H,meshConnectivityMode:e.target.value}),children:[(0,S.jsx)(`option`,{value:`direct`,children:`direct`}),(0,S.jsx)(`option`,{value:`private_lan`,children:`private_lan`}),(0,S.jsx)(`option`,{value:`outbound_only`,children:`outbound_only`}),(0,S.jsx)(`option`,{value:`relay_required`,children:`relay_required`})]})]}),(0,S.jsxs)(`label`,{children:[`NAT`,(0,S.jsxs)(`select`,{value:H.meshNATType,onChange:e=>U({...H,meshNATType:e.target.value}),children:[(0,S.jsx)(`option`,{value:`none`,children:`none`}),(0,S.jsx)(`option`,{value:`unknown`,children:`unknown`}),(0,S.jsx)(`option`,{value:`full_cone`,children:`full_cone`}),(0,S.jsx)(`option`,{value:`port_restricted`,children:`port_restricted`}),(0,S.jsx)(`option`,{value:`symmetric`,children:`symmetric`})]})]}),(0,S.jsxs)(`label`,{children:[`Region/site`,(0,S.jsx)(`input`,{value:H.meshRegion,onChange:e=>U({...H,meshRegion:e.target.value})})]}),H.installMode===`docker`&&(0,S.jsxs)(`label`,{className:`checkLine`,children:[(0,S.jsx)(`input`,{type:`checkbox`,checked:H.pullImage,onChange:e=>U({...H,pullImage:e.target.checked})}),`Pull image`]}),(0,S.jsxs)(`label`,{className:`checkLine`,children:[(0,S.jsx)(`input`,{type:`checkbox`,checked:H.replace,onChange:e=>U({...H,replace:e.target.checked})}),`Replace existing install`]})]})]}),(0,S.jsxs)(`section`,{className:`nodePanel`,children:[(0,S.jsx)(`h4`,{children:J.suggestedRoles}),(0,S.jsx)(`p`,{className:`muted`,children:`Роли записываются в install token и автоматически назначаются узлу при approval. После создания token изменение чекбоксов не меняет уже выданный token.`}),(0,S.jsx)(`div`,{className:`checkGrid`,children:ie.map(e=>(0,S.jsxs)(`label`,{className:`checkLine`,children:[(0,S.jsx)(`input`,{type:`checkbox`,checked:H.roles.includes(e),onChange:()=>U({...H,roles:en(H.roles,e)})}),Ie(e)]},e))})]}),(0,S.jsxs)(`details`,{children:[(0,S.jsx)(`summary`,{children:J.generatedScope}),(0,S.jsx)(`p`,{className:`muted`,children:J.generatedScopeHelp}),(0,S.jsx)(`pre`,{className:`codePreview`,children:JSON.stringify(ha,null,2)})]}),(0,S.jsxs)(`p`,{className:`muted`,children:[J.manualApprovalRequired,`.`]}),(0,S.jsx)(`button`,{className:`primary`,disabled:!T,onClick:()=>void Y(async()=>{Zr(await q.createJoinToken(T,{ttlHours:H.ttlHours,maxUses:H.maxUses,scope:ha}))},`Join token создан.`),children:`Создать install token`}),Xr&&(0,S.jsxs)(`div`,{className:`secretOnce`,children:[(0,S.jsx)(`strong`,{children:`Исходный token, возвращается один раз`}),(0,S.jsx)(`code`,{children:Xr.token}),(0,S.jsxs)(`span`,{className:`muted`,children:[`Authority key: `,R(Xr.authority_signature?.key_fingerprint)]}),(0,S.jsx)(`strong`,{children:`Scope выданного token`}),(0,S.jsx)(`pre`,{className:`codePreview`,children:JSON.stringify(Xr.scope,null,2)}),(0,S.jsx)(`strong`,{children:`Docker host-agent install`}),(0,S.jsx)(`pre`,{className:`codePreview`,children:Mt(Xr,sa,ga)}),(0,S.jsx)(`strong`,{children:`Profile-based Docker install`}),(0,S.jsx)(`pre`,{className:`codePreview`,children:Nt(Xr,sa,ga)}),(0,S.jsx)(`strong`,{children:`Profile-based Ubuntu service install`}),(0,S.jsx)(`pre`,{className:`codePreview`,children:Pt(Xr,sa,ga)}),(0,S.jsx)(`strong`,{children:`Profile-based Windows PowerShell install`}),(0,S.jsx)(`pre`,{className:`codePreview`,children:Ft(Xr,sa,ga)}),(0,S.jsx)(`strong`,{children:`Profile-based Windows CMD install`}),(0,S.jsx)(`pre`,{className:`codePreview`,children:It(Xr,sa,ga)})]})]}),(0,S.jsxs)(`article`,{className:`card`,children:[(0,S.jsx)(`h3`,{children:`Как добавить узел`}),(0,S.jsxs)(`div`,{className:`stateList`,children:[(0,S.jsxs)(`div`,{className:`stateLine`,children:[(0,S.jsx)(`span`,{children:`1`}),(0,S.jsx)(`strong`,{children:`Заполните Docker install profile слева.`})]}),(0,S.jsxs)(`div`,{className:`stateLine`,children:[(0,S.jsx)(`span`,{children:`2`}),(0,S.jsx)(`strong`,{children:`Нажмите “Создать install token”.`})]}),(0,S.jsxs)(`div`,{className:`stateLine`,children:[(0,S.jsx)(`span`,{children:`3`}),(0,S.jsx)(`strong`,{children:`Скопируйте “Profile-based Docker install” и выполните на Docker-хосте.`})]}),(0,S.jsxs)(`div`,{className:`stateLine`,children:[(0,S.jsx)(`span`,{children:`4`}),(0,S.jsx)(`strong`,{children:`Подтвердите join request в этой же вкладке.`})]})]})]}),(0,S.jsxs)(`article`,{className:`card`,children:[(0,S.jsx)(`h3`,{children:`Install tokens`}),(0,S.jsx)(M,{columns:[`scope`,`status`,`uses`,`expires`,`created`,`action`],rows:Be.map(e=>[Ze(e),V(e.status),`${e.used_count}/${e.max_uses}`,z(e.expires_at),z(e.created_at),e.status===`active`?(0,S.jsx)(`button`,{className:`danger`,onClick:()=>bn(`Отозвать install token ${R(e.id)}`)&&void Y(()=>q.revokeJoinToken(T,e.id),`Install token отозван.`),children:`Отозвать`}):(0,S.jsx)(`span`,{className:`muted`,children:V(e.status)})])})]}),(0,S.jsxs)(`article`,{className:`card`,children:[(0,S.jsx)(`h3`,{children:`Заявки на подключение`}),(0,S.jsxs)(`div`,{className:`stack`,children:[Fe.map(e=>(0,S.jsxs)(`div`,{className:`requestCard`,children:[(0,S.jsxs)(`div`,{children:[(0,S.jsx)(`strong`,{children:e.node_name}),(0,S.jsx)(`p`,{children:e.node_fingerprint}),(0,S.jsx)(k,{value:e.status}),e.approval_signature?.key_fingerprint&&(0,S.jsxs)(`small`,{className:`muted`,children:[`approval key `,R(e.approval_signature.key_fingerprint)]})]}),(0,S.jsxs)(`div`,{className:`actions`,children:[(0,S.jsx)(`button`,{disabled:e.status!==`pending`,onClick:()=>void Y(()=>q.approveJoinRequest(T,e.id),`Заявка одобрена.`),children:`Одобрить`}),(0,S.jsx)(`button`,{disabled:e.status!==`pending`,onClick:()=>void Y(()=>q.rejectJoinRequest(T,e.id,`Отклонено из панели владельца платформы.`),`Заявка отклонена.`),children:`Отклонить`})]})]},e.id)),Fe.length===0&&(0,S.jsx)(me,{title:`Нет заявок`,text:`Новые подключения node-agent появятся здесь.`})]})]})]}),w===`roles`&&(0,S.jsxs)(`section`,{className:`stack`,children:[(0,S.jsxs)(`article`,{className:`card`,children:[(0,S.jsx)(`h3`,{children:`Область ролей`}),(0,S.jsx)(`p`,{className:`muted`,children:`Capabilities — технические факты. Роли — явные разрешения. Область организации может ограничивать сервисные роли.`}),(0,S.jsxs)(`label`,{children:[`UUID организации для новых назначений ролей, опционально`,(0,S.jsx)(`input`,{value:ei,onChange:e=>ti(e.target.value),placeholder:`пусто = роль на весь кластер`})]})]}),j.map(e=>(0,S.jsxs)(`article`,{className:`card roleRow`,children:[(0,S.jsxs)(`div`,{children:[(0,S.jsx)(`h3`,{children:e.name}),(0,S.jsx)(`p`,{children:Pe(ot[e.id]||[])})]}),(0,S.jsxs)(`select`,{defaultValue:``,onChange:t=>{let n=t.target.value;t.currentTarget.value=``,n&&Y(()=>q.assignRole(T,e.id,n,ei||void 0),`${n} назначена узлу ${e.name}.`)},children:[(0,S.jsx)(`option`,{value:``,children:`Назначить роль...`}),ie.map(e=>(0,S.jsx)(`option`,{value:e,children:Ie(e)},e))]})]},e.id))]}),w===`workloads`&&(0,S.jsxs)(`section`,{className:`grid two`,children:[(0,S.jsxs)(`article`,{className:`card`,children:[(0,S.jsx)(`h3`,{children:`Желаемое состояние сервиса`}),(0,S.jsx)(`p`,{className:`muted`,children:`Здесь задается только желаемое состояние. Runtime-исполнение остается под контролем node-agent и политик.`}),(0,S.jsxs)(je,{children:[(0,S.jsxs)(`label`,{children:[`Узел`,(0,S.jsxs)(`select`,{value:Fi.nodeId,onChange:e=>Ii({...Fi,nodeId:e.target.value}),children:[(0,S.jsx)(`option`,{value:``,children:`Выберите узел...`}),j.map(e=>(0,S.jsx)(`option`,{value:e.id,children:e.name},e.id))]})]}),(0,S.jsxs)(`label`,{children:[`Сервис`,(0,S.jsx)(`select`,{value:Fi.serviceType,onChange:e=>Ii({...Fi,serviceType:e.target.value}),children:ie.map(e=>(0,S.jsx)(`option`,{value:e,children:Ie(e)},e))})]}),(0,S.jsxs)(`label`,{children:[`Желаемое состояние`,(0,S.jsxs)(`select`,{value:Fi.desiredState,onChange:e=>Ii({...Fi,desiredState:e.target.value}),children:[(0,S.jsx)(`option`,{value:`enabled`,children:`включено`}),(0,S.jsx)(`option`,{value:`disabled`,children:`выключено`})]})]}),(0,S.jsxs)(`label`,{children:[`Режим runtime`,(0,S.jsxs)(`select`,{value:Fi.runtimeMode,onChange:e=>Ii({...Fi,runtimeMode:e.target.value}),children:[(0,S.jsx)(`option`,{value:`container`,children:`контейнер`}),(0,S.jsx)(`option`,{value:`native`,children:`нативно`})]})]}),(0,S.jsxs)(`label`,{children:[`Версия`,(0,S.jsx)(`input`,{value:Fi.version,onChange:e=>Ii({...Fi,version:e.target.value})})]})]}),(0,S.jsxs)(`label`,{children:[`Config JSON`,(0,S.jsx)(`textarea`,{value:Fi.configJson,onChange:e=>Ii({...Fi,configJson:e.target.value})})]}),(0,S.jsxs)(`label`,{children:[`Environment JSON`,(0,S.jsx)(`textarea`,{value:Fi.environmentJson,onChange:e=>Ii({...Fi,environmentJson:e.target.value})})]}),(0,S.jsx)(`button`,{className:`primary`,disabled:!Fi.nodeId||!T,onClick:()=>void Y(()=>q.setDesiredWorkload(T,Fi.nodeId,Fi.serviceType,{desiredState:Fi.desiredState,runtimeMode:Fi.runtimeMode,version:Fi.version,config:Me(Fi.configJson,`config сервиса`),environment:Me(Fi.environmentJson,`environment сервиса`)}),`Желаемое состояние сервиса обновлено.`),children:`Задать желаемое состояние`})]}),(0,S.jsxs)(`article`,{className:`card`,children:[(0,S.jsx)(`h3`,{children:`Отчеты сервисов`}),(0,S.jsx)(`div`,{className:`stack`,children:j.map(e=>(0,S.jsxs)(`div`,{className:`workloadBlock`,children:[(0,S.jsx)(`strong`,{children:e.name}),(dt[e.id]||[]).length===0?(0,S.jsx)(`p`,{className:`muted`,children:`Статус пока не получен.`}):(0,S.jsx)(M,{columns:[`сервис`,`состояние`,`runtime`,`наблюдение`],rows:(dt[e.id]||[]).map(e=>[e.service_type,e.reported_state,e.runtime_mode,z(e.observed_at)])})]},e.id))})]})]}),w===`fabric`&&(0,S.jsxs)(`section`,{className:`grid two`,children:[(0,S.jsxs)(`article`,{className:`card span2`,children:[(0,S.jsx)(`h3`,{children:`Граница подготовки Fabric`}),(0,S.jsx)(`p`,{className:`muted`,children:"Этот экран показывает synthetic/control-plane подготовку и C17Z11 boundary: production forwarding доступен только для route-bound `fabric.control` при явном gate. Service traffic, RDP, VPN и произвольный relay здесь не включены."}),(0,S.jsxs)(`div`,{className:`signalStrip`,children:[(0,S.jsx)(O,{label:`Synthetic configs`,value:`${Za}/${j.length}`}),(0,S.jsx)(O,{label:`Routes`,value:String(Qa)}),(0,S.jsx)(O,{label:`Endpoints / candidates`,value:`${$a}/${eo}`}),(0,S.jsx)(O,{label:`Peer dir / seeds`,value:`${to}/${no}`}),(0,S.jsx)(O,{label:`Scoped production flag`,value:X===0?`false`:`true:${X}`})]})]}),(0,S.jsxs)(`article`,{className:`card`,children:[(0,S.jsxs)(`div`,{className:`cardHead`,children:[(0,S.jsxs)(`div`,{children:[(0,S.jsx)(`h3`,{children:J.fabricEntryPoints}),(0,S.jsx)(`p`,{className:`muted`,children:J.fabricEntryPointHelp})]}),(0,S.jsx)(`span`,{className:`pill`,children:In.length})]}),(0,S.jsxs)(je,{children:[(0,S.jsxs)(`label`,{children:[J.endpointName,(0,S.jsx)(`input`,{value:Hr.name,onChange:e=>Ur({...Hr,name:e.target.value})})]}),(0,S.jsxs)(`label`,{children:[J.endpointType,(0,S.jsxs)(`select`,{value:Hr.endpointType,onChange:e=>Ur({...Hr,endpointType:e.target.value}),children:[(0,S.jsx)(`option`,{value:`client_access`,children:`client_access`}),(0,S.jsx)(`option`,{value:`admin`,children:`admin`}),(0,S.jsx)(`option`,{value:`api`,children:`api`}),(0,S.jsx)(`option`,{value:`other`,children:`other`})]})]}),(0,S.jsxs)(`label`,{className:`span2`,children:[J.publicEndpoint,(0,S.jsx)(`input`,{placeholder:`wss://entry.example.com`,value:Hr.publicEndpoint,onChange:e=>Ur({...Hr,publicEndpoint:e.target.value})})]})]}),(0,S.jsx)(`div`,{className:`actions`,children:(0,S.jsx)(`button`,{className:`primary`,disabled:!Hr.name.trim(),onClick:()=>void Y(async()=>{await q.createFabricEntryPoint(T,{name:Hr.name,endpointType:Hr.endpointType,publicEndpoint:Hr.publicEndpoint||null}),Ur({name:``,endpointType:`client_access`,publicEndpoint:``})},`Точка входа создана.`),children:J.createEntryPoint})}),(0,S.jsxs)(`div`,{className:`stack`,children:[In.map(e=>{let t=Rn[e.id]||[];return(0,S.jsxs)(`section`,{className:`nodePanel`,children:[(0,S.jsxs)(`div`,{className:`cardHead`,children:[(0,S.jsxs)(`div`,{children:[(0,S.jsx)(`h4`,{children:e.name}),(0,S.jsxs)(`p`,{className:`muted`,children:[e.endpoint_type,` · `,e.public_endpoint||J.addressNotSet]})]}),(0,S.jsx)(k,{value:e.status})]}),(0,S.jsx)(`h5`,{children:J.endpointNodes}),t.length===0?(0,S.jsx)(`p`,{className:`muted`,children:J.assignedNodesEmpty}):(0,S.jsx)(`div`,{className:`membershipList`,children:t.map(t=>(0,S.jsxs)(`span`,{className:t.status===`active`?`pill good`:`pill`,children:[P(j,t.node_id),` · `,V(t.status),` · p`,t.priority]},`${e.id}-${t.node_id}`))}),(0,S.jsxs)(`div`,{className:`actions`,children:[(0,S.jsxs)(`select`,{value:Ei[e.id]||``,onChange:t=>Di({...Ei,[e.id]:t.target.value}),children:[(0,S.jsx)(`option`,{value:``,children:J.selectNode}),j.map(e=>(0,S.jsx)(`option`,{value:e.id,children:e.name},e.id))]}),(0,S.jsx)(`button`,{disabled:!Ei[e.id],onClick:()=>void Y(()=>q.setFabricEntryPointNode(T,e.id,Ei[e.id],{status:`active`}),`Узел назначен точке входа.`),children:J.assignEndpointNode})]})]},e.id)}),In.length===0&&(0,S.jsx)(me,{title:J.fabricEntryPoints,text:J.entryPointsEmpty})]})]}),(0,S.jsxs)(`article`,{className:`card`,children:[(0,S.jsxs)(`div`,{className:`cardHead`,children:[(0,S.jsxs)(`div`,{children:[(0,S.jsx)(`h3`,{children:J.fabricEgressPools}),(0,S.jsx)(`p`,{className:`muted`,children:J.fabricEgressPoolHelp})]}),(0,S.jsx)(`span`,{className:`pill`,children:Bn.length})]}),(0,S.jsxs)(je,{children:[(0,S.jsxs)(`label`,{children:[J.endpointName,(0,S.jsx)(`input`,{value:Wr.name,onChange:e=>Gr({...Wr,name:e.target.value})})]}),(0,S.jsxs)(`label`,{children:[J.description,(0,S.jsx)(`input`,{value:Wr.description,onChange:e=>Gr({...Wr,description:e.target.value})})]}),(0,S.jsxs)(`label`,{className:`span2`,children:[J.routeScope,(0,S.jsx)(`textarea`,{rows:5,value:Wr.routeScope,onChange:e=>Gr({...Wr,routeScope:e.target.value})})]})]}),(0,S.jsx)(`div`,{className:`actions`,children:(0,S.jsx)(`button`,{className:`primary`,disabled:!Wr.name.trim(),onClick:()=>void Y(async()=>{let e=Me(Wr.routeScope,`Route scope JSON`);await q.createFabricEgressPool(T,{name:Wr.name,description:Wr.description||null,routeScope:e}),Gr({name:``,description:``,routeScope:`{ + "routes": [] +}`})},`Выходная зона создана.`),children:J.createEgressPool})}),(0,S.jsxs)(`div`,{className:`stack`,children:[Bn.map(e=>{let t=Hn[e.id]||[];return(0,S.jsxs)(`section`,{className:`nodePanel`,children:[(0,S.jsxs)(`div`,{className:`cardHead`,children:[(0,S.jsxs)(`div`,{children:[(0,S.jsx)(`h4`,{children:e.name}),(0,S.jsx)(`p`,{className:`muted`,children:e.description||J.descriptionNotSet})]}),(0,S.jsx)(k,{value:e.status})]}),(0,S.jsx)(`p`,{className:`muted`,children:JSON.stringify(e.route_scope||{})}),(0,S.jsx)(`h5`,{children:J.endpointNodes}),t.length===0?(0,S.jsx)(`p`,{className:`muted`,children:J.assignedNodesEmpty}):(0,S.jsx)(`div`,{className:`membershipList`,children:t.map(t=>(0,S.jsxs)(`span`,{className:t.status===`active`?`pill good`:`pill`,children:[P(j,t.node_id),` · `,V(t.status),` · p`,t.priority]},`${e.id}-${t.node_id}`))}),(0,S.jsxs)(`div`,{className:`actions`,children:[(0,S.jsxs)(`select`,{value:Oi[e.id]||``,onChange:t=>ki({...Oi,[e.id]:t.target.value}),children:[(0,S.jsx)(`option`,{value:``,children:J.selectNode}),j.map(e=>(0,S.jsx)(`option`,{value:e.id,children:e.name},e.id))]}),(0,S.jsx)(`button`,{disabled:!Oi[e.id],onClick:()=>void Y(()=>q.setFabricEgressPoolNode(T,e.id,Oi[e.id],{status:`active`}),`Узел назначен выходной зоне.`),children:J.assignEndpointNode})]})]},e.id)}),Bn.length===0&&(0,S.jsx)(me,{title:J.fabricEgressPools,text:J.egressPoolsEmpty})]})]}),(0,S.jsxs)(`article`,{className:`card span2`,children:[(0,S.jsxs)(`div`,{className:`cardHead`,children:[(0,S.jsxs)(`div`,{children:[(0,S.jsx)(`h3`,{children:J.fabricMap}),(0,S.jsx)(`p`,{className:`muted`,children:`Визуальный слой показывает, какие узлы живы, какие сервисы на них назначены и какие тестовые наблюдения связей проходят между ними.`})]}),(0,S.jsx)(k,{value:Ya?.synthetic_links_enabled?`enabled`:`disabled`})]}),(0,S.jsx)(xe,{nodes:j,links:zt,syntheticMeshConfigsByNode:Ut,entryPoints:In,entryPointNodesById:Rn,egressPools:Bn,egressPoolNodesById:Hn,rolesByNode:ot,workloadsByNode:dt,telemetryByNode:Lt,labels:J,emptyText:J.noLinks})]}),(0,S.jsxs)(`article`,{className:`card span2`,children:[(0,S.jsxs)(`div`,{className:`cardHead`,children:[(0,S.jsxs)(`div`,{children:[(0,S.jsx)(`h3`,{children:`Synthetic mesh config`}),(0,S.jsx)(`p`,{className:`muted`,children:`Node-scoped config from Control Plane. Endpoint candidates and scoring inputs are visible to the platform owner only; production forwarding for service traffic must remain disabled here.`})]}),(0,S.jsxs)(`span`,{className:X===0?`pill good`:`pill bad`,children:[`production_forwarding=`,X===0?`false`:`true`]})]}),(0,S.jsx)(M,{columns:[`узел`,`config`,`routes`,`peer endpoints`,`candidates`,`peer dir`,`recovery seeds`,`rendezvous leases`,`relay policy`,`path decisions`,`authority`,`scoped production`],rows:j.map(e=>{let t=Ut[e.id];return[e.name,t?t.enabled?`enabled`:`disabled`:`не загружен`,String(t?.routes.length??0),String(Object.keys(t?.peer_endpoints||{}).length),String(t?ft(t):0),String(t?.peer_directory?.length??0),String(t?.recovery_seeds?.length??0),String(t?.rendezvous_leases?.length??0),pt(t),mt(t),t?.authority_required?R(t.authority_signature?.key_fingerprint):`не требуется`,t?.production_forwarding?`true`:`false`]})}),(0,S.jsx)(`p`,{className:`muted`,children:`Health-aware scoring не выбирает service route и не открывает service-соединения. C17Z19 показывает control-plane route/path decisions, route generation status, synthetic route-health effective path и relay feedback scoring, но не переносит RDP/VPN/file/video/service payload.`})]}),(0,S.jsxs)(`article`,{className:`card span2`,children:[(0,S.jsxs)(`div`,{className:`cardHead`,children:[(0,S.jsxs)(`div`,{children:[(0,S.jsx)(`h3`,{children:`Route intents lifecycle`}),(0,S.jsx)(`p`,{className:`muted`,children:`Operator view for temporary fabric routes. Expired and disabled intents are not emitted into node-scoped synthetic config.`})]}),(0,S.jsxs)(`div`,{className:`summaryChips`,children:[(0,S.jsxs)(`span`,{className:`pill good`,children:[`active `,ro.length]}),(0,S.jsxs)(`span`,{className:io.length>0?`pill warn`:`pill`,children:[`expired `,io.length]}),(0,S.jsxs)(`span`,{className:`pill`,children:[`disabled `,ao.length]})]})]}),(0,S.jsx)(M,{columns:[`route`,`life`,`service`,`priority`,`source`,`destination`,`expires`,`updated`,`actions`],rows:Vt.slice(0,120).map(e=>{let t=Ke(e);return[R(e.id),(0,S.jsx)(`span`,{className:`pill ${qe(e)}`,children:t}),e.service_class,String(e.priority),Je(e.source_selector||{}),Je(e.destination_selector||{}),e.policy_expires_at?z(e.policy_expires_at):`нет`,z(e.updated_at),(0,S.jsxs)(`div`,{className:`inlineActions`,children:[t===`active`?(0,S.jsx)(`button`,{type:`button`,className:`ghost`,onClick:()=>void Y(()=>q.expireRouteIntent(T,e.id,`operator expired stale route intent`),`Route intent expired.`),children:`expire`}):(0,S.jsx)(`span`,{className:`muted`,children:`expire`}),t===`disabled`?(0,S.jsx)(`span`,{className:`muted`,children:`disable`}):(0,S.jsx)(`button`,{type:`button`,className:`ghost`,onClick:()=>void Y(()=>q.disableRouteIntent(T,e.id,`operator disabled route intent`),`Route intent disabled.`),children:`disable`})]})]})}),Vt.length===0&&(0,S.jsx)(me,{title:`Route intents отсутствуют`,text:`Нет настроенных fabric route intents для текущего кластера.`})]}),(0,S.jsxs)(`article`,{className:`card span2`,children:[(0,S.jsxs)(`div`,{className:`cardHead`,children:[(0,S.jsxs)(`div`,{children:[(0,S.jsx)(`h3`,{children:`Service-channel route feedback`}),(0,S.jsx)(`p`,{className:`muted`,children:`Cluster-level runtime feedback from the shared fabric channel. Fenced and no-alternate cases affect route selection for any service class.`})]}),(0,S.jsxs)(`div`,{className:`summaryChips`,children:[(0,S.jsxs)(`span`,{className:so.length>0?`pill bad`:`pill good`,children:[`fenced `,so.length]}),(0,S.jsxs)(`span`,{className:co.length>0?`pill warn`:`pill`,children:[`degraded `,co.length]}),(0,S.jsxs)(`span`,{className:mo.length>0?`pill warn`:`pill`,children:[`retry `,mo.length]}),(0,S.jsxs)(`span`,{className:uo.length>0?`pill warn`:`pill`,children:[`recovered `,uo.length]}),(0,S.jsxs)(`span`,{className:fo.length>0?`pill good`:`pill`,children:[`promoted `,fo.length]}),(0,S.jsxs)(`span`,{className:po.length>0?`pill bad`:`pill`,children:[`demoted `,po.length]}),(0,S.jsxs)(`span`,{className:`pill good`,children:[`healthy `,lo.length]}),(0,S.jsxs)(`span`,{className:go.length>0?`pill bad`:`pill`,children:[`no alternate `,go.length]}),(0,S.jsxs)(`span`,{className:Co.length>0?`pill warn`:`pill`,children:[`hysteresis `,Co.length]}),(0,S.jsxs)(`span`,{className:wo.length>0?`pill good`:`pill`,children:[`promoted paths `,wo.length]}),(0,S.jsxs)(`span`,{className:To.length>0?`pill bad`:`pill`,children:[`demoted paths `,To.length]}),(0,S.jsxs)(`span`,{className:(An?.fingerprint||``).length>0?`pill good`:`pill warn`,children:[`policy fp `,An?.fingerprint?R(An.fingerprint):`нет`]}),(0,S.jsxs)(`span`,{className:vo.length>yo.length?`pill warn`:`pill good`,children:[`rebuild `,yo.length,`/`,vo.length]}),(0,S.jsxs)(`span`,{className:xo.length>0?`pill warn`:`pill good`,children:[`ledger `,bo.length,`/`,qt.length]}),(0,S.jsxs)(`span`,{className:So.length>0?`pill bad`:`pill good`,children:[`guard `,So.length]}),(0,S.jsx)(`span`,{className:vn?`pill info`:`pill`,children:vn?`deep ledger`:`fast ledger`})]})]}),go.length>0&&(0,S.jsx)(`div`,{className:`noticePanel`,children:`Есть service-channel route без unfenced alternate. Для production-сервиса это означает деградацию: fabric не нашел безопасную замену и будет ждать нового маршрута или операторского решения.`}),$t&&(0,S.jsxs)(`div`,{className:`noticePanel ${$t.status===`blocked`?`badPanel`:`goodPanel`}`,children:[(0,S.jsxs)(`div`,{className:`cardHead compact`,children:[(0,S.jsxs)(`div`,{children:[(0,S.jsx)(`h4`,{children:`Fabric schema preflight`}),(0,S.jsx)(`p`,{className:`muted`,children:`Backend/runtime compatibility check for manual deploys before diagnostics or service channels depend on new DB fields.`})]}),(0,S.jsxs)(`div`,{className:`summaryChips`,children:[(0,S.jsx)(`span`,{className:`pill ${$t.status===`blocked`?`bad`:`good`}`,children:V($t.status)}),(0,S.jsxs)(`span`,{className:$t.missing_check_count>0?`pill bad`:`pill good`,children:[$t.passed_check_count,`/`,$t.required_check_count]}),(0,S.jsx)(`button`,{className:`ghost`,onClick:()=>void ka(),disabled:Ar,children:`warm snapshots`})]})]}),(0,S.jsxs)(`div`,{className:`stateList`,children:[(0,S.jsx)(A,{label:`reason`,value:V($t.reason)}),(0,S.jsx)(A,{label:`required`,value:$t.required_migration}),(0,S.jsx)(A,{label:`missing`,value:($t.missing_checks||[]).map(e=>e.check_id).slice(0,4).join(`, `)||`нет`}),(0,S.jsx)(A,{label:`action`,value:$t.recommended_operator_action||`schema is compatible`}),un&&(0,S.jsx)(A,{label:`warmup`,value:`warmed ${un.warmed_count}, fresh ${un.already_fresh_count}, missing ${un.missing_snapshot_count}, stale ${un.stale_snapshot_count}, deferred ${un.deferred_stale_count}, errors ${un.error_count}`})]})]}),an&&(0,S.jsxs)(`div`,{className:`noticePanel ${an.status===`degraded`?`warnPanel`:`goodPanel`}`,children:[(0,S.jsxs)(`div`,{className:`cardHead compact`,children:[(0,S.jsxs)(`div`,{children:[(0,S.jsx)(`h4`,{children:`Snapshot maintenance`}),(0,S.jsx)(`p`,{className:`muted`,children:`Auto-warmup visibility for rebuild snapshot cache after node heartbeats.`})]}),(0,S.jsxs)(`div`,{className:`summaryChips`,children:[(0,S.jsx)(`span`,{className:`pill ${an.status===`degraded`?`warn`:`good`}`,children:V(an.status)}),(0,S.jsxs)(`span`,{className:an.overdue_missing_snapshot_count>0?`pill bad`:`pill good`,children:[`overdue `,an.overdue_missing_snapshot_count]}),(0,S.jsxs)(`span`,{className:an.auto_warmup_error_count>0?`pill bad`:`pill good`,children:[`auto errors `,an.auto_warmup_error_count]})]})]}),(0,S.jsxs)(`div`,{className:`stateList`,children:[(0,S.jsx)(A,{label:`reason`,value:V(an.reason)}),(0,S.jsx)(A,{label:`snapshots`,value:`valid ${an.valid_snapshot_count}, missing ${an.missing_snapshot_count}, attempts ${an.recent_attempt_count}`}),(0,S.jsx)(A,{label:`auto-warmup`,value:`events ${an.auto_warmup_event_count}, warmed ${an.auto_warmup_warmed_count}, fresh ${an.auto_warmup_already_fresh_count}, latest ${z(an.latest_auto_warmup_at)}`}),(0,S.jsx)(A,{label:`guard`,value:`age ${an.min_age_seconds}s, heartbeats ${an.heartbeat_threshold}`}),(0,S.jsx)(A,{label:`action`,value:an.recommended_operator_action||`snapshot maintenance is current`})]}),(an.nodes||[]).length>0&&(0,S.jsx)(M,{columns:[`node`,`snapshots`,`heartbeat`,`auto-warmup`,`latest`],rows:(an.nodes||[]).slice(0,6).map(e=>[P(j,e.node_id),(0,S.jsxs)(`span`,{className:e.overdue_missing_snapshot_count>0?`pill bad`:e.missing_snapshot_count>0?`pill warn`:`pill good`,children:[e.valid_snapshot_count,`/`,e.recent_attempt_count,` overdue `,e.overdue_missing_snapshot_count]}),e.heartbeat_after_attempt_count,`${e.auto_warmup_warmed_count}/${e.auto_warmup_event_count} errors ${e.auto_warmup_error_count}`,z(e.latest_auto_warmup_at||e.last_heartbeat_at)])})]}),fn&&(0,S.jsxs)(`div`,{className:`noticePanel ${fn.status===`degraded`?`warnPanel`:`goodPanel`}`,children:[(0,S.jsxs)(`div`,{className:`cardHead compact`,children:[(0,S.jsxs)(`div`,{children:[(0,S.jsx)(`h4`,{children:`Service-channel leases`}),(0,S.jsx)(`p`,{className:`muted`,children:`Durable compatibility lease records for introspection after backend restarts.`})]}),(0,S.jsxs)(`div`,{className:`summaryChips`,children:[(0,S.jsx)(`span`,{className:`pill ${fn.status===`degraded`?`warn`:`good`}`,children:V(fn.status)}),(0,S.jsxs)(`span`,{className:`pill good`,children:[`active `,fn.active_count]}),(0,S.jsxs)(`span`,{className:fn.expired_count>0?`pill warn`:`pill`,children:[`expired `,fn.expired_count]}),(0,S.jsx)(`button`,{className:`ghost`,onClick:()=>void Aa(),disabled:Ar,children:`cleanup`})]})]}),(0,S.jsxs)(`div`,{className:`stateList`,children:[(0,S.jsx)(A,{label:`reason`,value:V(fn.reason)}),(0,S.jsx)(A,{label:`scanned`,value:`${fn.scanned_count}/${fn.window_limit}`}),(0,S.jsx)(A,{label:`deleted`,value:String(fn.deleted_expired_count||0)}),(0,S.jsx)(A,{label:`action`,value:fn.recommended_operator_action||`lease maintenance is current`})]}),(fn.leases||[]).length>0&&(0,S.jsx)(M,{columns:[`expires`,`resource`,`entry`,`exit`,`route`,`data plane`,`state`],rows:(fn.leases||[]).slice(0,8).map(e=>[z(e.expires_at),e.resource_id||R(e.channel_id),P(j,e.selected_entry_node_id||``),P(j,e.selected_exit_node_id||``),e.primary_route_id?`${R(e.primary_route_id)} / ${V(e.primary_route_status||``)}`:`backend fallback`,`${V(e.data_plane?.working_data_transport||`unknown`)} / ${V(e.data_plane?.backend_relay_policy||`unknown`)}`,(0,S.jsx)(`span`,{className:`pill ${e.expired||e.force_backend_fallback?`warn`:`good`}`,children:e.expired?`expired`:e.force_backend_fallback?`fallback`:V(e.status)})])})]}),L&&(0,S.jsxs)(`div`,{className:`noticePanel ${L.status===`degraded`?`warnPanel`:`goodPanel`}`,children:[(0,S.jsxs)(`div`,{className:`cardHead compact`,children:[(0,S.jsxs)(`div`,{children:[(0,S.jsx)(`h4`,{children:`Service-channel access`}),(0,S.jsx)(`p`,{className:`muted`,children:`Live accepted_by visibility from node telemetry and heartbeat metadata.`})]}),(0,S.jsxs)(`div`,{className:`summaryChips`,children:[(0,S.jsx)(`span`,{className:`pill ${L.status===`degraded`?`warn`:`good`}`,children:V(L.status)}),(0,S.jsxs)(`span`,{className:`pill good`,children:[`accepted `,L.total_accepted]}),(0,S.jsxs)(`span`,{className:L.backend_fallback_count>0?`pill warn`:`pill`,children:[`backend `,L.backend_fallback_count]}),(0,S.jsxs)(`span`,{className:(L.backend_fallback_blocked_count||0)>0?`pill bad`:`pill`,children:[`blocked `,L.backend_fallback_blocked_count||0]}),(0,S.jsxs)(`span`,{className:`pill ${L.last_working_data_transport===`fabric_service_channel`?`good`:L.data_plane_contract_count?`warn`:``}`,children:[`data-plane `,L.data_plane_contract_count||0]}),(0,S.jsxs)(`span`,{className:`pill ${L.last_backend_relay_policy===`disabled`?`good`:L.last_backend_relay_policy===`degraded_fallback_only`?`info`:``}`,children:[`relay `,V(L.last_backend_relay_policy||`unknown`)]}),(0,S.jsxs)(`span`,{className:L.degraded_fallback_channel_count>0||L.degraded_route_count>0?`pill warn`:`pill good`,children:[`channels `,L.active_channel_count]}),(0,S.jsxs)(`span`,{className:L.no_safe_recovery_decision_count?`pill warn`:L.route_decision_channel_count?`pill info`:`pill`,children:[`decisions `,L.route_decision_channel_count||0,L.replacement_decision_count?` / repl ${L.replacement_decision_count}`:``,L.applied_rebuild_decision_count?` / applied ${L.applied_rebuild_decision_count}`:``,L.recovery_decision_count?` / recovery ${L.recovery_decision_count}`:``,L.no_safe_recovery_decision_count?` / no-safe ${L.no_safe_recovery_decision_count}`:``]}),(0,S.jsx)(`span`,{className:`pill ${En(L.flow_health_status,L.flow_dropped)}`,children:wn(L.traffic_class_counts)}),(0,S.jsxs)(`span`,{className:`pill ${En(L.flow_health_status,L.flow_dropped)}`,children:[`flow `,V(L.flow_health_status||`healthy`)]}),(0,S.jsxs)(`span`,{className:`pill ${L.adaptive_backpressure_active?`info`:`good`}`,children:[`windows `,Tn(L.recommended_parallel_windows)]})]})]}),(0,S.jsxs)(`div`,{className:`stateList`,children:[(0,S.jsx)(A,{label:`reason`,value:V(L.reason)}),(0,S.jsx)(A,{label:`reporting nodes`,value:`${L.reporting_node_count}/${L.node_count}`}),(0,S.jsx)(A,{label:`accepted by`,value:`signed ${L.signed_accepted}, introspection ${L.introspection_accepted}, legacy ${L.legacy_unsigned_accepted}`}),(0,S.jsx)(A,{label:`data plane`,value:`${L.data_plane_contract_count||0} contracts, mode ${V(L.last_data_plane_mode||`unknown`)}, working ${V(L.last_working_data_transport||`unknown`)}, steady ${V(L.last_steady_state_transport||`unknown`)}, relay ${V(L.last_backend_relay_policy||`unknown`)}, flows ${V(L.last_logical_flow_mode||`unknown`)}, blocked ${L.backend_fallback_blocked_count||0}, route failures ${L.fabric_route_send_failure_count||0}`}),(0,S.jsx)(A,{label:`data-plane violation`,value:L.last_data_plane_violation_status?`${V(L.last_data_plane_violation_status)} / ${L.last_data_plane_violation_reason||`n/a`}`:`none`}),(0,S.jsx)(A,{label:`active channels`,value:`${L.active_channel_count||0}, fallback ${L.degraded_fallback_channel_count||0}, correlated routes ${L.correlated_route_count||0}, degraded routes ${L.degraded_route_count||0}`}),(0,S.jsx)(A,{label:`route decisions`,value:`channels ${L.route_decision_channel_count||0}, replacement ${L.replacement_decision_count||0}, applied ${L.applied_rebuild_decision_count||0}, recovery ${L.recovery_decision_count||0}, no-safe ${L.no_safe_recovery_decision_count||0}`}),(0,S.jsx)(A,{label:`flow QoS`,value:`${V(L.flow_health_status||`healthy`)} / ${V(L.flow_health_reason||`flow_health_ready`)}, ${wn(L.traffic_class_counts)}, flows ${L.flow_channel_count||0}, in-flight ${L.flow_max_in_flight||0}, dropped ${L.flow_dropped||0}`}),(0,S.jsx)(A,{label:`adaptive windows`,value:`${L.adaptive_backpressure_active?V(L.adaptive_backpressure_reason||`adaptive`):`off`}, ${Tn(L.recommended_parallel_windows)}, policy ${L.adaptive_policy_fingerprint?R(L.adaptive_policy_fingerprint):`n/a`}`}),(0,S.jsx)(A,{label:`latest accepted`,value:z(L.latest_accepted_at)}),(0,S.jsx)(A,{label:`action`,value:L.recommended_operator_action||`access telemetry is current`})]}),(L.active_channels||[]).length>0&&(0,S.jsx)(M,{columns:[`resource`,`entry -> exit`,`route`,`decision`,`entry access`,`data plane`,`flow health`,`windows`,`flow QoS`,`route quality`,`remediation`,`guard`,`execution`,`expires`],rows:(L.active_channels||[]).slice(0,10).map(e=>[e.resource_id||R(e.channel_id),`${P(j,e.selected_entry_node_id||``)} -> ${P(j,e.selected_exit_node_id||``)}`,e.primary_route_id?`${R(e.primary_route_id)} / ${V(e.primary_route_status||``)}`:`backend fallback`,(0,S.jsx)(`span`,{className:`pill ${On(e.route_decision_source,e.route_decision_rebuild_status,e.route_decision_score_reasons)}`,children:e.route_decision_source?`${V(e.route_decision_source)}${e.route_decision_route_id?` ${R(e.route_decision_route_id)}`:``}${e.route_decision_replacement_route_id?` -> ${R(e.route_decision_replacement_route_id)}`:``}${e.route_decision_rebuild_status?` / ${V(e.route_decision_rebuild_status)}`:``}`:`n/a`}),`accepted ${e.entry_node_total_accepted}, introspection ${e.entry_node_introspection_accepted}, backend ${e.entry_node_backend_fallback_count}`,(0,S.jsx)(`span`,{className:`pill ${e.entry_node_last_working_data_transport===`fabric_service_channel`?`good`:e.entry_node_data_plane_contract_count?`warn`:``}`,children:`${e.entry_node_data_plane_contract_count||0} / ${V(e.entry_node_last_working_data_transport||`unknown`)} / ${V(e.entry_node_last_backend_relay_policy||`unknown`)}${e.entry_node_backend_fallback_blocked_count?` / blocked ${e.entry_node_backend_fallback_blocked_count}`:``}`}),(0,S.jsxs)(`span`,{className:`pill ${En(e.entry_node_flow_health_status,e.entry_node_flow_dropped)}`,children:[V(e.entry_node_flow_health_status||`healthy`),e.entry_node_flow_health_reason?` / ${V(e.entry_node_flow_health_reason)}`:``]}),(0,S.jsx)(`span`,{className:`pill ${e.entry_node_adaptive_backpressure_active?`info`:`good`}`,children:Tn(e.entry_node_recommended_parallel_windows)}),(0,S.jsxs)(`span`,{className:`pill ${En(e.entry_node_flow_health_status,e.entry_node_flow_dropped)}`,children:[wn(e.entry_node_traffic_class_counts),` / flows ${e.entry_node_flow_channel_count||0} / in ${e.entry_node_flow_max_in_flight||0}`]}),(0,S.jsx)(`span`,{className:`pill ${e.force_backend_fallback||e.route_feedback_status===`degraded`||e.route_feedback_status===`fenced`?`warn`:e.route_feedback_status?`good`:``}`,children:e.force_backend_fallback?`backend fallback`:e.route_feedback_status?`${V(e.route_feedback_status)} / ${e.last_send_duration_ms||0}ms / q ${e.route_quality_window_sample_count||0}`:`no route feedback`}),(0,S.jsx)(`span`,{className:`pill ${e.remediation_action===`none`?`good`:e.remediation_action===`prefer_alternate_route`?`warn`:e.remediation_action?`bad`:``}`,children:e.remediation_action?`${e.remediation_command?`cmd `:``}${V(e.remediation_action)}${e.remediation_command?.replacement_route_id?` -> ${R(e.remediation_command.replacement_route_id)}`:e.remediation_route_id?` -> ${R(e.remediation_route_id)}`:``}`:`n/a`}),(0,S.jsxs)(`span`,{className:`pill ${e.remediation_guard_status===`rejected`?`bad`:e.pool_policy_fingerprint?`good`:``}`,children:[e.remediation_guard_status?V(e.remediation_guard_status):e.pool_policy_fingerprint?`pool policy`:`n/a`,e.remediation_guard_reason?` / ${V(e.remediation_guard_reason)}`:``]}),(0,S.jsxs)(`span`,{className:`pill ${Dn(e.remediation_execution_status)}`,children:[e.remediation_execution_status?V(e.remediation_execution_status):`n/a`,e.remediation_execution_generation?` / ${R(e.remediation_execution_generation)}`:``,e.remediation_execution_reason?` / ${V(e.remediation_execution_reason)}`:``]}),z(e.expires_at)])}),(L.nodes||[]).length>0&&(0,S.jsx)(M,{columns:[`node`,`accepted`,`signed`,`introspection`,`legacy`,`backend`,`data plane`,`flow health`,`windows`,`flow QoS`,`latest`],rows:(L.nodes||[]).slice(0,10).map(e=>[P(j,e.node_id)||e.node_name||R(e.node_id),e.total_accepted,e.signed_accepted,e.introspection_accepted,e.legacy_unsigned_accepted,(0,S.jsx)(`span`,{className:e.backend_fallback_count>0?`pill warn`:`pill`,children:e.backend_fallback_count}),(0,S.jsx)(`span`,{className:`pill ${e.last_working_data_transport===`fabric_service_channel`?`good`:e.data_plane_contract_count?`warn`:``}`,children:`${e.data_plane_contract_count||0} / ${V(e.last_working_data_transport||`unknown`)} / ${V(e.last_backend_relay_policy||`unknown`)}${e.backend_fallback_blocked_count?` / blocked ${e.backend_fallback_blocked_count}`:``}`}),(0,S.jsxs)(`span`,{className:`pill ${En(e.flow_health_status,e.flow_dropped)}`,children:[V(e.flow_health_status||`healthy`),e.flow_health_reason?` / ${V(e.flow_health_reason)}`:``]}),(0,S.jsx)(`span`,{className:`pill ${e.adaptive_backpressure_active?`info`:`good`}`,children:Tn(e.recommended_parallel_windows)}),(0,S.jsxs)(`span`,{className:`pill ${En(e.flow_health_status,e.flow_dropped)}`,children:[wn(e.traffic_class_counts),` / flows ${e.flow_channel_count||0} / in ${e.flow_max_in_flight||0}`]}),z(e.last_accepted_at||e.observed_at)])})]}),I&&(0,S.jsxs)(`div`,{className:`noticePanel ${I.status===`blocked`?`badPanel`:I.status===`degraded`?`warnPanel`:`goodPanel`}`,children:[(0,S.jsxs)(`div`,{className:`cardHead compact`,children:[(0,S.jsxs)(`div`,{children:[(0,S.jsx)(`h4`,{children:`Fabric service-channel readiness`}),(0,S.jsx)(`p`,{className:`muted`,children:`Verdict for production service-channel use. Working service payloads should not depend on this fabric while the gate is blocked.`})]}),(0,S.jsxs)(`div`,{className:`summaryChips`,children:[(0,S.jsx)(`span`,{className:`pill ${I.status===`blocked`?`bad`:I.status===`degraded`?`warn`:`good`}`,children:V(I.status)}),(0,S.jsxs)(`span`,{className:I.active_alert_count>0?`pill bad`:`pill`,children:[`active `,I.active_alert_count]}),(0,S.jsxs)(`span`,{className:I.resurfaced_count>0?`pill bad`:`pill`,children:[`resurfaced `,I.resurfaced_count]})]})]}),(0,S.jsxs)(`div`,{className:`stateList`,children:[(0,S.jsx)(A,{label:`reason`,value:V(I.reason)}),(0,S.jsx)(A,{label:`blocking`,value:(I.blocking_reasons||[]).map(V).join(`, `)||`нет`}),(0,S.jsx)(A,{label:`degraded`,value:(I.degraded_reasons||[]).map(V).join(`, `)||`нет`}),(0,S.jsx)(A,{label:`missing/post`,value:`transition ${I.missing_transition_count}, route-gen ${I.missing_route_generation_count}, traffic ${I.missing_post_rebuild_traffic_count}`})]})]}),hn.length>0&&(0,S.jsxs)(`div`,{className:`subPanel`,children:[(0,S.jsxs)(`div`,{className:`cardHead compact`,children:[(0,S.jsxs)(`div`,{children:[(0,S.jsx)(`h4`,{children:`Rebuild incidents`}),(0,S.jsx)(`p`,{className:`muted`,children:`Grouped recent rebuild attempts by reporter, route, service, generation, and guard. Open an incident to load the exact deep ledger slice.`})]}),(0,S.jsx)(`span`,{className:`pill`,children:hn.length})]}),(0,S.jsx)(M,{columns:[`last`,`source`,`reporter`,`route`,`service`,`guard`,`count`,`replacement`,`action`],rows:hn.slice(0,10).map(e=>[z(e.last_seen_at),e.incident_source?V(e.incident_source):`ledger`,P(j,e.reporter_node_id),R(e.route_id),e.service_class,(0,S.jsxs)(`span`,{className:`pill ${e.alert_resurfaced||e.guard_severity===`bad`?`bad`:e.guard_severity===`warn`?`warn`:`good`}`,children:[V(e.guard_status),e.alert_silenced?` / silenced`:e.alert_resurfaced?` / resurfaced`:``]}),String(e.attempt_count),e.latest_replacement_route_id?R(e.latest_replacement_route_id):`нет`,(0,S.jsxs)(`div`,{className:`inlineActions`,children:[(0,S.jsxs)(`span`,{children:[V(e.recommended_operator_action||`inspect`),e.alert_resurfaced&&e.alert_resurfaced_cause?` (${V(e.alert_resurfaced_cause)})`:``,e.alert_resurfaced&&e.alert_resurfaced_previous_generation?` from ${R(e.alert_resurfaced_previous_generation)} until ${z(e.alert_resurfaced_previous_until)}`:``]}),(0,S.jsx)(`button`,{type:`button`,className:`ghost`,onClick:()=>void Y(()=>ja(e),`Deep rebuild investigation opened.`),children:`open deep`}),e.alert_silenced?(0,S.jsx)(`span`,{className:`muted`,children:`silenced`}):(0,S.jsx)(`button`,{type:`button`,className:`ghost`,onClick:()=>void Y(()=>Ia(e),`Rebuild incident silenced for 6 hours.`),children:`silence 6h`})]})])})]}),(Mn||Sa.length>0)&&(0,S.jsxs)(`div`,{className:`subPanel`,children:[(0,S.jsxs)(`div`,{className:`cardHead compact`,children:[(0,S.jsxs)(`div`,{children:[(0,S.jsx)(`h4`,{children:`Recent investigations`}),(0,S.jsx)(`p`,{className:`muted`,children:`Recent operator drilldowns opened from rebuild incidents or feedback breakdown rows.`})]}),(0,S.jsxs)(`div`,{className:`summaryChips`,children:[(0,S.jsx)(`span`,{className:`pill info`,children:br?.total_count||Sa.length}),(0,S.jsxs)(`span`,{className:`pill good`,children:[`linked `,br?.correlated_count||0]}),(0,S.jsxs)(`span`,{className:(br?.not_visible_count||0)>0?`pill warn`:`pill`,children:[`not visible `,br?.not_visible_count||0]}),Object.entries(br?.counts_by_breadcrumb_status||{}).map(([e,t])=>(0,S.jsxs)(`span`,{className:e===`current`?`pill good`:e===`stale`?`pill warn`:`pill bad`,children:[V(e),` `,t]},e)),Object.entries(br?.counts_by_current_diagnostic_status||{}).slice(0,3).map(([e,t])=>(0,S.jsxs)(`span`,{className:e===`breakdown_active`||e===`incident_visible`?`pill good`:e===`not_visible`?`pill warn`:`pill`,children:[V(e),` `,t]},e))]})]}),Mn&&(0,S.jsxs)(`div`,{className:`inlineForm`,children:[(0,S.jsxs)(`label`,{children:[`current window, sec`,(0,S.jsx)(`input`,{type:`number`,min:`60`,value:Jr.currentWindowSeconds,onChange:e=>Yr(t=>({...t,currentWindowSeconds:e.target.value}))})]}),(0,S.jsxs)(`label`,{children:[`history window, sec`,(0,S.jsx)(`input`,{type:`number`,min:`60`,value:Jr.historyWindowSeconds,onChange:e=>Yr(t=>({...t,historyWindowSeconds:e.target.value}))})]}),(0,S.jsx)(`button`,{type:`button`,className:`ghost`,onClick:()=>void Y(async()=>{Nn(await q.updateFabricServiceChannelBreadcrumbWindowPolicy(T,{currentWindowSeconds:Number(Jr.currentWindowSeconds),historyWindowSeconds:Number(Jr.historyWindowSeconds)}));let e=await q.getFabricServiceChannelRebuildInvestigationBreadcrumbs(T,{limit:20});yr(e.events),xr(e.summary||null)},`Breadcrumb window policy updated.`),children:`apply windows`}),(0,S.jsxs)(`span`,{className:`muted`,children:[`source `,Mn.source,`, fp `,R(Mn.fingerprint||``)]})]}),br&&(0,S.jsxs)(`div`,{className:`stateList`,children:[(0,S.jsx)(A,{label:`latest`,value:z(br.latest_at)}),(0,S.jsx)(A,{label:`windows`,value:`${Mn?.current_window_seconds||`n/a`}s current / ${Mn?.history_window_seconds||`n/a`}s history`}),(0,S.jsx)(A,{label:`sources`,value:Object.entries(br.counts_by_feedback_source||{}).slice(0,3).map(([e,t])=>`${V(e)} ${t}`).join(`, `)||`нет`}),(0,S.jsx)(A,{label:`violations`,value:Object.entries(br.counts_by_feedback_violation_status||{}).slice(0,3).map(([e,t])=>`${V(e)} ${t}`).join(`, `)||`нет`})]}),(0,S.jsx)(M,{columns:[`time`,`freshness`,`source`,`feedback`,`target`,`current`,`actor`,`reason`],rows:Sa.map(e=>{let t=at(e.payload)||{},n=N(t,`feedback_channel_id`,``),r=N(t,`feedback_violation_status`,``),i=N(t,`feedback_source`,``),a=N(t,`reporter_node_id`,``),o=N(t,`route_id`,``),s=N(t,`drilldown_source`,``),c=e.correlation_hints?.current_diagnostic_status||``,l=e.correlation_hints?.breadcrumb_status||`current`,u=e.correlation_hints?.breadcrumb_age_seconds,d=e.correlation_hints?.feedback_breakdown||Na(e),f=e.correlation_hints?.rebuild_incident||Pa(e);return[z(e.created_at),(0,S.jsxs)(`div`,{className:`stackedText`,children:[(0,S.jsx)(`span`,{className:l===`current`?`pill good`:l===`stale`?`pill warn`:`pill bad`,children:V(l)}),(0,S.jsx)(`span`,{className:`muted`,children:Sn(u)})]}),(0,S.jsxs)(`div`,{className:`stackedText`,children:[(0,S.jsx)(`span`,{children:e.event_type.includes(`feedback_breakdown`)?`feedback breakdown`:`incident`}),(0,S.jsx)(`span`,{className:`muted`,children:V(s||e.target_type)})]}),i||n||r?(0,S.jsxs)(`div`,{className:`stackedText`,children:[(0,S.jsx)(`span`,{children:V(i||`feedback`)}),(0,S.jsx)(`span`,{className:`muted`,children:n?`ch ${R(n)}`:`any channel`}),(0,S.jsx)(`span`,{className:`muted`,children:V(r||`any violation`)})]}):(0,S.jsx)(`span`,{className:`muted`,children:`нет`}),(0,S.jsxs)(`div`,{className:`stackedText`,children:[(0,S.jsx)(`span`,{children:a?P(j,a):`any reporter`}),(0,S.jsx)(`span`,{className:`muted`,children:o?R(o):e.target_id?R(e.target_id):`any route`})]}),d?(0,S.jsxs)(`div`,{className:`inlineActions`,children:[(0,S.jsx)(`span`,{className:d.active_bad_count?`pill bad`:d.active_warn_count?`pill warn`:`pill good`,children:V(c||`breakdown_active`)}),(0,S.jsxs)(`span`,{className:`muted`,children:[`bad `,d.active_bad_count||0,` / warn `,d.active_warn_count||0]}),(0,S.jsx)(`button`,{type:`button`,className:`ghost`,onClick:()=>void Y(()=>Fa(d),`Rebuild ledger opened for current feedback breakdown.`),children:`open`})]}):f?(0,S.jsxs)(`div`,{className:`inlineActions`,children:[(0,S.jsx)(`span`,{className:`pill ${f.guard_severity===`bad`?`bad`:f.guard_severity===`warn`?`warn`:`good`}`,children:V(c||`incident_visible`)}),(0,S.jsx)(`span`,{className:`muted`,children:V(f.guard_status)}),(0,S.jsx)(`button`,{type:`button`,className:`ghost`,onClick:()=>void Y(()=>ja(f),`Deep rebuild investigation opened for current incident.`),children:`open`})]}):(0,S.jsx)(`span`,{className:`muted`,children:V(c||`not_visible`)}),e.actor_user_id?R(e.actor_user_id):`system`,N(t,`reason`,`operator opened investigation`)]})})]}),Xt.length>0&&(0,S.jsxs)(`div`,{className:`subPanel`,children:[(0,S.jsxs)(`div`,{className:`cardHead compact`,children:[(0,S.jsxs)(`div`,{children:[(0,S.jsx)(`h4`,{children:`Active rebuild silences`}),(0,S.jsx)(`p`,{className:`muted`,children:`Operator acknowledgements currently suppressing rebuild/access-decision alerts. Remove a silence to let the incident become active again.`})]}),(0,S.jsx)(`span`,{className:`pill info`,children:Xt.length})]}),(0,S.jsx)(M,{columns:[`until`,`source`,`channel`,`reporter`,`route`,`guard`,`reason`,`action`],rows:Xt.slice(0,10).map(e=>[z(e.expires_at),e.incident_source?V(e.incident_source):`ledger`,e.channel_id?R(e.channel_id):`нет`,P(j,e.reporter_node_id),R(e.display_route_id||e.route_id),V(e.guard_status),e.reason||`acknowledged`,(0,S.jsx)(`button`,{type:`button`,className:`ghost`,onClick:()=>void Y(()=>La(e),`Rebuild alert silence removed.`),children:`unsilence`})])})]}),F&&(0,S.jsxs)(`div`,{className:`subPanel`,children:[(0,S.jsxs)(`div`,{className:`cardHead compact`,children:[(0,S.jsxs)(`div`,{children:[(0,S.jsx)(`h4`,{children:`Rebuild health`}),(0,S.jsxs)(`p`,{className:`muted`,children:[`Сводка по последним `,F.total_attempts,` rebuild попыткам. Данные помогают быстро увидеть, где backend уже принял решение, но node-agent или post-rebuild traffic не подтвердили результат.`]})]}),(0,S.jsxs)(`div`,{className:`summaryChips`,children:[(0,S.jsxs)(`span`,{className:`pill good`,children:[`ok `,F.good_count]}),(0,S.jsxs)(`span`,{className:F.active_warn_count>0?`pill warn`:`pill`,children:[`warn `,F.active_warn_count,`/`,F.warn_count]}),(0,S.jsxs)(`span`,{className:F.active_bad_count>0?`pill bad`:`pill`,children:[`bad `,F.active_bad_count,`/`,F.bad_count]}),(0,S.jsxs)(`span`,{className:F.resurfaced_count>0?`pill bad`:`pill`,children:[`resurfaced `,F.resurfaced_count]}),(0,S.jsxs)(`span`,{className:F.silenced_count>0?`pill info`:`pill`,children:[`silenced `,F.silenced_count]}),(0,S.jsxs)(`span`,{className:`pill`,children:[`applied `,F.applied_count]}),(0,S.jsxs)(`span`,{className:F.access_no_safe_count?`pill bad`:F.access_route_decision_count?`pill info`:`pill`,children:[`access `,F.access_route_decision_count||0,F.access_no_safe_count?` / no-safe ${F.access_no_safe_count}`:``]})]})]}),(0,S.jsxs)(`div`,{className:`stateList`,children:[(0,S.jsx)(A,{label:`observed`,value:z(F.observed_at)}),(0,S.jsx)(A,{label:`affected nodes`,value:(F.affected_reporter_node_ids||[]).map(e=>P(j,e)).join(`, `)||`нет`}),(0,S.jsx)(A,{label:`affected routes`,value:(F.affected_route_ids||[]).map(R).join(`, `)||`нет`}),(0,S.jsx)(A,{label:`action`,value:V(F.recommended_operator_action||`no_operator_action_required`)})]}),(F.feedback_breakdowns||[]).length>0&&(0,S.jsx)(M,{columns:[`feedback`,`active`,`total`,`affected`,`incidents`,`latest`,`action`],rows:(F.feedback_breakdowns||[]).slice(0,8).map(e=>{let t=Ma(e);return[(0,S.jsxs)(`div`,{className:`stackedText`,children:[(0,S.jsx)(`span`,{children:V(e.feedback_source||`feedback`)}),(0,S.jsx)(`span`,{className:`muted`,children:e.feedback_channel_id?`ch ${R(e.feedback_channel_id)}`:`any channel`}),(0,S.jsx)(`span`,{className:`muted`,children:V(e.feedback_violation_status||`unknown`)})]}),(0,S.jsxs)(`span`,{className:e.active_bad_count?`pill bad`:e.active_warn_count?`pill warn`:`pill`,children:[`bad `,e.active_bad_count||0,` / warn `,e.active_warn_count||0]}),`total ${e.total_count} / bad ${e.bad_count||0} / warn ${e.warn_count||0} / silenced ${e.silenced_count||0}`,(0,S.jsxs)(`div`,{className:`stackedText`,children:[(0,S.jsx)(`span`,{children:(e.affected_reporter_node_ids||[]).map(e=>P(j,e)).join(`, `)||`нет узлов`}),(0,S.jsx)(`span`,{className:`muted`,children:(e.affected_route_ids||[]).map(R).join(`, `)||`нет routes`})]}),t.length>0?(0,S.jsxs)(`div`,{className:`stackedText`,children:[(0,S.jsx)(`span`,{className:`pill warn`,children:t.length}),(0,S.jsx)(`span`,{className:`muted`,children:t.slice(0,2).map(e=>V(e.guard_status)).join(`, `)})]}):(0,S.jsx)(`span`,{className:`muted`,children:`нет`}),z(e.latest_observed_at),(0,S.jsx)(`button`,{type:`button`,className:`ghost`,onClick:()=>void Y(()=>Fa(e),`Rebuild ledger opened for feedback breakdown.`),children:`open ledger`})]})}),(F.most_recent_bad_attempts||[]).length>0&&(0,S.jsx)(M,{columns:[`time`,`reporter`,`route`,`guard`,`reason`],rows:(F.most_recent_bad_attempts||[]).slice(0,5).map(e=>[z(e.updated_at),P(j,e.reporter_node_id),R(e.route_id),(0,S.jsx)(`span`,{className:`pill bad`,children:V(e.guard_status||`bad`)}),(0,S.jsxs)(`div`,{className:`inlineActions`,children:[(0,S.jsx)(`span`,{children:V(e.guard_reason||`unknown`)}),(0,S.jsx)(`button`,{type:`button`,className:`ghost`,onClick:()=>void Y(()=>q.silenceFabricServiceChannelRouteRebuildAlert(T,{reporterNodeId:e.reporter_node_id,routeId:e.route_id,guardStatus:e.guard_status||`unknown`,generation:e.generation||``,reason:`operator acknowledged known rebuild alert`,ttlSeconds:21600}),`Rebuild alert silenced for this route generation.`),children:`silence 6h`})]})])}),(F.resurfaced_attempts||[]).length>0&&(0,S.jsx)(M,{columns:[`time`,`reporter`,`route`,`guard`,`previous`,`action`],rows:(F.resurfaced_attempts||[]).slice(0,5).map(e=>[z(e.updated_at),P(j,e.reporter_node_id),R(e.route_id),(0,S.jsx)(`span`,{className:`pill bad`,children:V(e.guard_status||`bad`)}),`${R(e.alert_resurfaced_previous_generation)} until ${z(e.alert_resurfaced_previous_until)}`,(0,S.jsxs)(`div`,{className:`inlineActions`,children:[(0,S.jsx)(`span`,{children:V(e.guard_reason||`unknown`)}),(0,S.jsx)(`button`,{type:`button`,className:`ghost`,onClick:()=>void Y(()=>q.silenceFabricServiceChannelRouteRebuildAlert(T,{reporterNodeId:e.reporter_node_id,routeId:e.route_id,guardStatus:e.guard_status||`unknown`,generation:e.generation||``,reason:`operator acknowledged resurfaced rebuild alert`,ttlSeconds:21600}),`Resurfaced rebuild alert silenced for this generation.`),children:`silence 6h`})]})])})]}),An&&(0,S.jsxs)(`div`,{className:`inlineForm`,children:[(0,S.jsxs)(`label`,{children:[`penalty`,(0,S.jsx)(`input`,{type:`number`,min:`0`,value:Kr.hysteresisPenalty,onChange:e=>qr(t=>({...t,hysteresisPenalty:e.target.value}))})]}),(0,S.jsxs)(`label`,{children:[`promote samples`,(0,S.jsx)(`input`,{type:`number`,min:`1`,value:Kr.promotionMinSamples,onChange:e=>qr(t=>({...t,promotionMinSamples:e.target.value}))})]}),(0,S.jsxs)(`label`,{children:[`fail`,(0,S.jsx)(`input`,{type:`number`,min:`1`,value:Kr.demotionFailureThreshold,onChange:e=>qr(t=>({...t,demotionFailureThreshold:e.target.value}))})]}),(0,S.jsxs)(`label`,{children:[`drop`,(0,S.jsx)(`input`,{type:`number`,min:`1`,value:Kr.demotionDropThreshold,onChange:e=>qr(t=>({...t,demotionDropThreshold:e.target.value}))})]}),(0,S.jsxs)(`label`,{children:[`slow`,(0,S.jsx)(`input`,{type:`number`,min:`1`,value:Kr.demotionSlowThreshold,onChange:e=>qr(t=>({...t,demotionSlowThreshold:e.target.value}))})]}),(0,S.jsxs)(`label`,{className:`checkLine`,children:[(0,S.jsx)(`input`,{type:`checkbox`,checked:Kr.demotionRebuildEnabled,onChange:e=>qr(t=>({...t,demotionRebuildEnabled:e.target.checked}))}),`rebuild`]}),(0,S.jsxs)(`label`,{className:`checkLine`,children:[(0,S.jsx)(`input`,{type:`checkbox`,checked:Kr.demotionFencedEnabled,onChange:e=>qr(t=>({...t,demotionFencedEnabled:e.target.checked}))}),`fenced`]}),(0,S.jsx)(`button`,{type:`button`,className:`ghost`,onClick:()=>void Y(async()=>{jn(await q.updateFabricServiceChannelRecoveryPolicy(T,{hysteresisPenalty:Number(Kr.hysteresisPenalty),promotionMinSamples:Number(Kr.promotionMinSamples),demotionFailureThreshold:Number(Kr.demotionFailureThreshold),demotionDropThreshold:Number(Kr.demotionDropThreshold),demotionSlowThreshold:Number(Kr.demotionSlowThreshold),demotionRebuildEnabled:Kr.demotionRebuildEnabled,demotionFencedEnabled:Kr.demotionFencedEnabled}))},`Recovery policy updated.`),children:`apply policy`}),(0,S.jsxs)(`span`,{className:`muted`,children:[`source `,An.source]})]}),(0,S.jsx)(M,{columns:[`route`,`reporter`,`service`,`status`,`recovery`,`score`,`reasons`,`failures`,`retry/cooldown`,`expires`,`action`],rows:oo.slice(0,80).map(e=>[R(e.route_id),P(j,e.reporter_node_id),e.service_class,(0,S.jsx)(`span`,{className:`pill ${We(e.feedback_status)}`,children:V(e.feedback_status)}),e.recovery_state?(0,S.jsxs)(`span`,{className:`pill ${Ge(e.recovery_state)}`,children:[e.recovery_demoted?`demoted ${e.recovery_reason?V(e.recovery_reason):``}`:e.recovery_promoted?`promoted`:V(e.recovery_state),e.recovery_hysteresis_penalty?` -${e.recovery_hysteresis_penalty}`:``]}):e.stale_policy||e.stale_generation?(0,S.jsx)(`span`,{className:`pill warn`,children:V(e.stale_reason||`stale`)}):e.provenance_missing?(0,S.jsx)(`span`,{className:`pill warn`,children:`provenance missing`}):`нет`,String(e.score_adjustment),(e.reasons||[]).join(`, `)||`нет`,String(e.consecutive_failures||0),e.retry_cooldown_until?z(e.retry_cooldown_until):`нет`,z(e.expires_at),e.feedback_status===`healthy`||e.feedback_status===`operator_retry_cooldown`?(0,S.jsx)(`span`,{className:`muted`,children:`нет`}):(0,S.jsx)(`button`,{type:`button`,className:`ghost`,onClick:()=>void Y(()=>q.expireFabricServiceChannelRouteFeedback(T,{routeId:e.route_id,reporterNodeId:e.reporter_node_id,serviceClass:e.service_class,reason:`operator expired stale service-channel feedback`}),`Service-channel feedback expired.`),children:`expire`})])}),oo.length===0&&(0,S.jsx)(me,{title:`Feedback отсутствует`,text:`Нет свежих route feedback наблюдений от fabric service-channel runtime.`}),(0,S.jsx)(M,{columns:[`local node`,`route`,`replacement`,`rebuild`,`attempt`,`feedback`,`source`,`destination`,`decision`,`score`,`expires`],rows:[..._o,...go,...vo].filter((e,t,n)=>n.findIndex(t=>t.decision_id===e.decision_id)===t).slice(0,80).map(e=>[P(j,e.local_node_id),R(e.route_id),e.replacement_route_id?R(e.replacement_route_id):`нет`,e.rebuild_status||`нет`,e.rebuild_attempt==null?`н/д`:String(e.rebuild_attempt),e.feedback_observation_id?(0,S.jsxs)(`div`,{className:`stackedText`,children:[(0,S.jsx)(`span`,{children:V(e.feedback_source||`feedback`)}),(0,S.jsxs)(`span`,{className:`muted`,children:[R(e.feedback_observation_id),` `,e.feedback_channel_id?`ch ${R(e.feedback_channel_id)}`:``]}),(0,S.jsx)(`span`,{className:`muted`,children:V(e.feedback_violation_status||``)})]}):`нет`,P(j,e.source_node_id),P(j,e.destination_node_id),e.decision_source,e.path_score==null?`н/д`:String(e.path_score),z(e.expires_at)])}),(0,S.jsxs)(`div`,{className:`inlineForm`,children:[(0,S.jsxs)(`label`,{children:[`reporter`,(0,S.jsxs)(`select`,{value:B.reporterNodeId,onChange:e=>kn(t=>({...t,reporterNodeId:e.target.value,offset:0})),children:[(0,S.jsx)(`option`,{value:``,children:`all`}),j.map(e=>(0,S.jsx)(`option`,{value:e.id,children:e.name},e.id))]})]}),(0,S.jsxs)(`label`,{children:[`route`,(0,S.jsx)(`input`,{value:B.routeId,onChange:e=>kn(t=>({...t,routeId:e.target.value.trim(),offset:0})),placeholder:`route id`})]}),(0,S.jsxs)(`label`,{children:[`generation`,(0,S.jsx)(`input`,{value:B.generation,onChange:e=>kn(t=>({...t,generation:e.target.value.trim(),offset:0})),placeholder:`route generation`})]}),(0,S.jsxs)(`label`,{children:[`service`,(0,S.jsx)(`input`,{value:B.serviceClass,onChange:e=>kn(t=>({...t,serviceClass:e.target.value.trim(),offset:0})),placeholder:`vpn_packets`})]}),(0,S.jsxs)(`label`,{children:[`feedback source`,(0,S.jsx)(`input`,{value:B.feedbackSource,onChange:e=>kn(t=>({...t,feedbackSource:e.target.value.trim(),offset:0})),placeholder:`fabric_service_channel_access_report`})]}),(0,S.jsxs)(`label`,{children:[`channel`,(0,S.jsx)(`input`,{value:B.feedbackChannelId,onChange:e=>kn(t=>({...t,feedbackChannelId:e.target.value.trim(),offset:0})),placeholder:`feedback channel id`})]}),(0,S.jsxs)(`label`,{children:[`violation`,(0,S.jsx)(`input`,{value:B.feedbackViolationStatus,onChange:e=>kn(t=>({...t,feedbackViolationStatus:e.target.value.trim(),offset:0})),placeholder:`fabric_route_send_failed_backend_fallback_blocked`})]}),(0,S.jsx)(`button`,{type:`button`,className:`ghost`,onClick:()=>void Da(vn,{...B,offset:0}),children:`apply`}),(0,S.jsx)(`button`,{type:`button`,className:`ghost`,onClick:()=>{let e={...re};kn(e),Da(!1,e)},children:`clear`})]}),(0,S.jsx)(M,{columns:[`time`,`reporter`,`route`,`replacement`,`feedback`,`guard`,`outcome`,`backend`,`agent`,`route-gen`,`traffic`,`policy`,`hops`],rows:qt.slice(0,80).map(e=>[z(e.updated_at),P(j,e.reporter_node_id),R(e.route_id),e.replacement_route_id?R(e.replacement_route_id):`нет`,e.feedback_observation_id?(0,S.jsxs)(`div`,{className:`stackedText`,children:[(0,S.jsx)(`span`,{children:V(e.feedback_source||`feedback`)}),(0,S.jsxs)(`span`,{className:`muted`,children:[R(e.feedback_observation_id),` `,e.feedback_channel_id?`ch ${R(e.feedback_channel_id)}`:``]}),(0,S.jsx)(`span`,{className:`muted`,children:V(e.feedback_violation_status||``)})]}):e.feedback_status?V(e.feedback_status):`нет`,vn?(0,S.jsxs)(`span`,{className:`pill ${e.guard_severity===`bad`?`bad`:e.guard_severity===`warn`?`warn`:`good`}`,children:[V(e.guard_status||`unknown`),e.alert_silenced?` / silenced`:e.alert_resurfaced?` / resurfaced`:``]}):(0,S.jsx)(`span`,{className:`pill info`,children:`summary`}),V(e.outcome),(0,S.jsx)(`span`,{className:`pill ${e.rebuild_status===`applied`?`good`:`warn`}`,children:V(e.rebuild_status)}),vn?e.node_transition_matched?(0,S.jsx)(`span`,{className:`pill ${e.node_transition_status===`applied_rebuild`?`good`:`warn`}`,children:V(e.node_transition_status||`matched`)}):(0,S.jsx)(`span`,{className:`pill warn`,children:`not seen`}):(0,S.jsx)(`span`,{className:`pill info`,children:`deep only`}),vn?e.node_route_generation_matched?(0,S.jsx)(`span`,{className:`pill good`,children:V(e.node_route_generation_status||`seen`)}):(0,S.jsx)(`span`,{className:`pill warn`,children:`not seen`}):(0,S.jsx)(`span`,{className:`pill info`,children:`deep only`}),vn?e.post_rebuild_selected_route_id?`${R(e.post_rebuild_selected_route_id)} packets ${e.post_rebuild_send_flow_packets||e.post_rebuild_send_packets||0} drop ${e.post_rebuild_send_flow_dropped||0}`:`нет`:`deep only`,e.policy_fingerprint?R(e.policy_fingerprint):`нет`,`${(e.old_hops||[]).map(e=>P(j,e)).join(` -> `)||`нет`} => ${(e.replacement_hops||[]).map(e=>P(j,e)).join(` -> `)||`нет`}`])}),(0,S.jsxs)(`div`,{className:`inlineActions`,children:[(0,S.jsx)(`button`,{type:`button`,className:`ghost`,onClick:()=>void Da(!vn,{...B,offset:0}),children:vn?`fast ledger`:`deep ledger`}),(0,S.jsx)(`button`,{type:`button`,className:`ghost`,disabled:!vn||B.offset<=0,onClick:()=>void Da(!0,{...B,offset:Math.max(0,B.offset-20)}),children:`prev`}),(0,S.jsx)(`button`,{type:`button`,className:`ghost`,disabled:!vn||qt.length<20,onClick:()=>void Da(!0,{...B,offset:B.offset+20}),children:`next`}),(0,S.jsxs)(`span`,{className:`pill`,children:[`offset `,vn?B.offset:0]}),(0,S.jsx)(`span`,{className:`muted`,children:`Deep ledger correlates heartbeat timeline and can be slower; default refresh stays fast.`})]}),qt.length===0&&(0,S.jsx)(me,{title:`Rebuild ledger пуст`,text:`Пока нет долговечной истории service-channel route rebuild решений.`})]}),(0,S.jsxs)(`article`,{className:`card span2`,children:[(0,S.jsx)(`h3`,{children:J.servicePlacement}),(0,S.jsx)(M,{columns:[`узел`,`runtime`,`адрес`,`здоровье`,`роли`,`желаемые / reported сервисы`,`последний heartbeat`],rows:j.map(e=>{let t=ct(e,Et[e.id]||[],zt);return[e.name,(0,S.jsx)(ye,{runtime:t}),t.address,e.health_status,Pe(ot[e.id]||[]),Le(dt[e.id]||[]),z((Et[e.id]||[])[0]?.observed_at||e.last_seen_at)]})})]}),(0,S.jsxs)(`article`,{className:`card`,children:[(0,S.jsx)(`h3`,{children:J.trafficFlow}),(0,S.jsx)(M,{columns:[`источник`,`цель`,`тип`,`route/path`,`статус`,`задержка`,`качество`,`наблюдение`],rows:Qe(zt).filter(e=>e.source_node_id!==e.target_node_id).map(e=>{let t=j.find(t=>t.id===e.source_node_id),n=j.find(t=>t.id===e.target_node_id);return[(0,S.jsx)(be,{node:t,fallback:P(j,e.source_node_id),heartbeatsByNode:Et,meshLinks:zt}),(0,S.jsx)(be,{node:n,fallback:P(j,e.target_node_id),heartbeatsByNode:Et,meshLinks:zt}),et(e),tt(e,j),e.link_status,e.latency_ms==null?`н/д`:`${e.latency_ms} мс`,e.quality_score==null?`н/д`:`${e.quality_score}/100`,z(e.observed_at)]})})]}),(0,S.jsxs)(`article`,{className:`card`,children:[(0,S.jsx)(`h3`,{children:`Политики QoS`}),(0,S.jsx)(M,{columns:[`класс`,`приоритет`,`надежность`,`политика сброса`],rows:Pn.map(e=>[e.service_class,String(e.priority),e.reliability_mode,e.drop_policy])})]})]}),w===`vpn`&&(0,S.jsxs)(`section`,{className:`grid two`,children:[(0,S.jsxs)(`article`,{className:`card`,children:[(0,S.jsx)(`h3`,{children:`Создать желаемое состояние VPN-подключения`}),(0,S.jsx)(`p`,{className:`muted`,children:`Только control-plane. Здесь не выполняются TUN/TAP, маршруты, DNS, firewall, QoS или packet forwarding.`}),(0,S.jsxs)(je,{children:[(0,S.jsxs)(`label`,{children:[`ID организации`,(0,S.jsx)(`input`,{value:G.organizationId,onChange:e=>Li({...G,organizationId:e.target.value})})]}),(0,S.jsxs)(`label`,{children:[`Название`,(0,S.jsx)(`input`,{value:G.name,onChange:e=>Li({...G,name:e.target.value})})]}),(0,S.jsxs)(`label`,{children:[`Протокол`,(0,S.jsxs)(`select`,{value:G.protocolFamily,onChange:e=>Li({...G,protocolFamily:e.target.value}),children:[(0,S.jsx)(`option`,{value:`generic`,children:`generic`}),(0,S.jsx)(`option`,{value:`wireguard`,children:`wireguard`}),(0,S.jsx)(`option`,{value:`ipsec`,children:`ipsec`}),(0,S.jsx)(`option`,{value:`openvpn`,children:`openvpn`})]})]}),(0,S.jsxs)(`label`,{children:[`Желаемое состояние`,(0,S.jsxs)(`select`,{value:G.desiredState,onChange:e=>Li({...G,desiredState:e.target.value}),children:[(0,S.jsx)(`option`,{value:`disabled`,children:`выключено`}),(0,S.jsx)(`option`,{value:`enabled`,children:`включено`})]})]}),(0,S.jsxs)(`label`,{children:[`Ссылка на credential`,(0,S.jsx)(`input`,{value:G.credentialRef,onChange:e=>Li({...G,credentialRef:e.target.value})})]})]}),(0,S.jsxs)(`label`,{children:[`Целевой endpoint JSON`,(0,S.jsx)(`textarea`,{value:G.targetEndpointJson,onChange:e=>Li({...G,targetEndpointJson:e.target.value})})]}),(0,S.jsxs)(`label`,{children:[`Политика разрешенных узлов JSON`,(0,S.jsx)(`textarea`,{value:G.allowedNodePolicyJson,onChange:e=>Li({...G,allowedNodePolicyJson:e.target.value})})]}),(0,S.jsxs)(`details`,{children:[(0,S.jsx)(`summary`,{children:`Расширенные routing / QoS / placement JSON`}),(0,S.jsxs)(`label`,{children:[`Использование маршрутизации JSON`,(0,S.jsx)(`textarea`,{value:G.routingUsageJson,onChange:e=>Li({...G,routingUsageJson:e.target.value})})]}),(0,S.jsxs)(`label`,{children:[`Политика маршрута JSON`,(0,S.jsx)(`textarea`,{value:G.routePolicyJson,onChange:e=>Li({...G,routePolicyJson:e.target.value})})]}),(0,S.jsxs)(`label`,{children:[`Политика QoS JSON`,(0,S.jsx)(`textarea`,{value:G.qosPolicyJson,onChange:e=>Li({...G,qosPolicyJson:e.target.value})})]}),(0,S.jsxs)(`label`,{children:[`Политика размещения JSON`,(0,S.jsx)(`textarea`,{value:G.placementPolicyJson,onChange:e=>Li({...G,placementPolicyJson:e.target.value})})]})]}),(0,S.jsx)(`button`,{className:`primary`,disabled:!T||!G.organizationId||!G.name,onClick:()=>void Y(()=>q.createVPNConnection(T,{organizationId:G.organizationId,name:G.name,protocolFamily:G.protocolFamily,credentialRef:G.credentialRef||null,desiredState:G.desiredState,targetEndpoint:Me(G.targetEndpointJson,`target endpoint`),allowedNodePolicy:Me(G.allowedNodePolicyJson,`allowed node policy`),routingUsage:Ne(G.routingUsageJson,`routing usage`),routePolicy:Me(G.routePolicyJson,`route policy`),qosPolicy:Me(G.qosPolicyJson,`qos policy`),placementPolicy:Me(G.placementPolicyJson,`placement policy`)}),`Желаемое состояние VPN создано.`),children:`Создать желаемое состояние VPN`})]}),(0,S.jsxs)(`article`,{className:`card`,children:[(0,S.jsxs)(`div`,{className:`cardHead`,children:[(0,S.jsxs)(`div`,{children:[(0,S.jsx)(`h3`,{children:`VPN-подключения`}),(0,S.jsx)(`p`,{className:`muted`,children:`Cluster-managed состояние, gateway packet stats и диагностика Android-клиента.`})]}),(0,S.jsxs)(`div`,{className:`actions compactActions`,children:[(0,S.jsx)(`button`,{onClick:()=>void Y(async()=>{Fr(`Истекшие VPN lease: ${(await q.expireStaleVPNLeases(T)).length}.`)},`Stale VPN lease проверены.`),children:`Проверить stale lease`}),(0,S.jsx)(`button`,{onClick:()=>void za(),children:`Обновить клиент`})]})]}),(0,S.jsxs)(`div`,{className:`inlineForm`,children:[(0,S.jsxs)(`label`,{children:[`Android device id`,(0,S.jsx)(`input`,{value:Qn,placeholder:`0315f630-...`,onChange:e=>$n(e.target.value),onBlur:()=>localStorage.setItem(C.vpnDiagnosticDeviceId,Qn.trim())})]}),er.length>0&&(0,S.jsxs)(`label`,{children:[`Найденные клиенты`,(0,S.jsx)(`select`,{value:Qn,onChange:e=>{let t=e.target.value;$n(t),localStorage.setItem(C.vpnDiagnosticDeviceId,t),rr(er.find(e=>e.device_id===t)||null)},children:er.map(e=>{let t=at(e.payload)||{};return(0,S.jsxs)(`option`,{value:e.device_id,children:[R(e.device_id),` / `,N(t,`app_version`,`н/д`),` / `,z(e.observed_at)]},e.device_id)})})]})]}),(0,S.jsxs)(`div`,{className:`diagnosticCommandPanel`,children:[(0,S.jsxs)(`label`,{children:[`URL для теста`,(0,S.jsx)(`input`,{value:ir,onChange:e=>ar(e.target.value)})]}),(0,S.jsxs)(`div`,{className:`actions compactActions`,children:[(0,S.jsx)(`button`,{onClick:()=>void Ba({type:`refresh_profile`},`Профиль`),children:`Обновить профиль`}),(0,S.jsx)(`button`,{onClick:()=>void Ba({type:`start_vpn`},`VPN`),children:`Старт VPN`}),(0,S.jsx)(`button`,{onClick:()=>void Ba({type:`stop_vpn`},`VPN`),children:`Стоп VPN`}),(0,S.jsx)(`button`,{onClick:()=>void Ba({type:`vpn_stats`},`Stats`),children:`Stats`}),(0,S.jsx)(`button`,{onClick:()=>void Ba({type:`vpn_http_get`,url:ir},`VPN HTTP`),children:`VPN HTTP`}),(0,S.jsx)(`button`,{onClick:()=>void Ba({type:`open_url`,url:ir},`Открыть URL`),children:`Открыть URL`}),(0,S.jsx)(`button`,{className:`primary`,onClick:()=>void Ba({type:`full_vpn_test`,url:ir,watch_seconds:45},`Полный VPN test`),children:`Полный тест`})]}),or&&(0,S.jsxs)(`p`,{className:`muted`,children:[`Последняя команда: `,N(or.payload,`type`,`н/д`),` / `,z(or.created_at)]})]}),he(nr),(0,S.jsxs)(`div`,{className:`stack`,children:[Kn.map(e=>{let t=at(e.metadata?.client_config),n=at(t?.vpn_fabric_route),r=St(n?.entry_pool_node_ids||e.placement_policy?.entry_node_ids),i=St(n?.exit_pool_node_ids||e.placement_policy?.exit_node_ids),a=String(n?.selected_entry_node_id||r[0]||``),o=String(n?.selected_exit_node_id||Jn[e.id]?.owner_node_id||e.placement_policy?.exit_node_id||i[0]||``),s=Xn[e.id]||{};return(0,S.jsxs)(`div`,{className:`vpnCard`,children:[(0,S.jsxs)(`div`,{children:[(0,S.jsx)(`strong`,{children:e.name}),(0,S.jsxs)(`p`,{className:`muted`,children:[e.protocol_family,` / `,e.mode,` / организация `,R(e.organization_id)]}),(0,S.jsx)(k,{value:e.desired_state}),(0,S.jsx)(k,{value:e.status}),(0,S.jsx)(`span`,{className:`pill ${t?.packet_forwarding?`good`:`warn`}`,children:t?.packet_forwarding?`gateway packet relay active`:`gateway packet relay inactive`}),(0,S.jsxs)(`span`,{className:`pill`,children:[String(n?.preferred_data_plane||`backend_relay`),` / fallback `,String(n?.fallback_data_plane||`н/д`)]})]}),(0,S.jsxs)(`div`,{className:`stateList`,children:[(0,S.jsx)(A,{label:`Секрет`,value:e.credential_ref?`задан`:`не задан`}),(0,S.jsx)(A,{label:`Активный lease`,value:Jn[e.id]?R(Jn[e.id]?.owner_node_id):`нет`}),(0,S.jsx)(A,{label:`Fabric route`,value:`${a?P(j,a):`entry auto`} -> ${o?P(j,o):`exit auto`}`}),(0,S.jsx)(A,{label:`Entry pool`,value:r.map(e=>P(j,e)).join(`, `)||`н/д`}),(0,S.jsx)(A,{label:`Exit pool`,value:i.map(e=>P(j,e)).join(`, `)||`н/д`}),(0,S.jsx)(A,{label:`Runtime`,value:String(t?.runtime_status||`н/д`)}),(0,S.jsx)(A,{label:`Gateway`,value:String(t?.gateway_assignment_status||`н/д`)}),(0,S.jsx)(A,{label:`Client -> gateway`,value:He(s.client_to_gateway)}),(0,S.jsx)(A,{label:`Gateway -> client`,value:He(s.gateway_to_client)}),(0,S.jsx)(A,{label:`Обновлено`,value:z(e.updated_at)})]}),(0,S.jsxs)(`div`,{className:`actions`,children:[(0,S.jsx)(`button`,{disabled:e.desired_state===`enabled`,onClick:()=>void Y(()=>q.updateVPNConnectionDesiredState(T,e.id,`enabled`),`Желаемое состояние VPN включено.`),children:`Включить`}),(0,S.jsx)(`button`,{disabled:e.desired_state===`disabled`,onClick:()=>void Y(()=>q.updateVPNConnectionDesiredState(T,e.id,`disabled`),`Желаемое состояние VPN выключено.`),children:`Выключить`})]})]},e.id)}),Kn.length===0&&(0,S.jsx)(me,{title:`Нет желаемого состояния VPN`,text:`Control-plane записи C18 появятся здесь.`})]})]})]}),w===`org-safe`&&(0,S.jsxs)(`section`,{className:`grid two`,children:[(0,S.jsxs)(`article`,{className:`card span2`,children:[(0,S.jsxs)(`div`,{className:`cardHead`,children:[(0,S.jsxs)(`div`,{children:[(0,S.jsx)(`h3`,{children:`Организации и пользователи`}),(0,S.jsx)(`p`,{className:`muted`,children:`Операционный слой для владельца платформы: tenant scope, роли участников и безопасная сводка без раскрытия core mesh.`})]}),(0,S.jsx)(`span`,{className:`pill`,children:cr.length})]}),(0,S.jsxs)(je,{children:[(0,S.jsxs)(`label`,{children:[`Slug`,(0,S.jsx)(`input`,{value:Ri.slug,onChange:e=>zi({...Ri,slug:e.target.value}),placeholder:`home`})]}),(0,S.jsxs)(`label`,{children:[`Название`,(0,S.jsx)(`input`,{value:Ri.name,onChange:e=>zi({...Ri,name:e.target.value}),placeholder:`HOME`})]})]}),(0,S.jsx)(`div`,{className:`actions`,children:(0,S.jsx)(`button`,{className:`primary`,disabled:!Ri.slug.trim()||!Ri.name.trim(),onClick:()=>void Y(async()=>{let e=await q.createOrganization(Ri);zi({slug:``,name:``}),Dr(e.id),Ui(t=>({...t,organizationId:e.id})),Ji(t=>({...t,organizationId:e.id}))},`Организация создана.`),children:`Создать организацию`})}),(0,S.jsx)(M,{columns:[`организация`,`slug`,`статус`,`ресурсы`,`участники`,`действие`],rows:cr.map(e=>{let t=fr.filter(t=>t.organization_id===e.id),n=mr[e.id]||[];return[e.name,e.slug,(0,S.jsx)(k,{value:e.status}),String(t.length),String(n.length),(0,S.jsx)(`div`,{className:`actions`,children:(0,S.jsx)(`button`,{onClick:()=>void Y(async()=>{Dr(e.id),kr(await q.getOrganizationAdminSummary(e.id))},`Сводка организации загружена.`),children:`Открыть`})},e.id)]})})]}),(0,S.jsxs)(`article`,{className:`card`,children:[(0,S.jsx)(`h3`,{children:`Пользователь`}),(0,S.jsxs)(je,{children:[(0,S.jsxs)(`label`,{children:[`Email / логин`,(0,S.jsx)(`input`,{value:Bi.email,onChange:e=>Vi({...Bi,email:e.target.value}),placeholder:`user@example.com`})]}),(0,S.jsxs)(`label`,{children:[`Пароль`,(0,S.jsx)(`input`,{type:`password`,value:Bi.password,onChange:e=>Vi({...Bi,password:e.target.value}),placeholder:`минимум 8 символов`})]}),(0,S.jsxs)(`label`,{children:[`Роль платформы`,(0,S.jsxs)(`select`,{value:Bi.platformRole,onChange:e=>Vi({...Bi,platformRole:e.target.value}),children:[(0,S.jsx)(`option`,{value:`user`,children:`user`}),(0,S.jsx)(`option`,{value:`platform_admin`,children:`platform_admin`}),(0,S.jsx)(`option`,{value:`platform_recovery_admin`,children:`platform_recovery_admin`})]})]})]}),(0,S.jsx)(`div`,{className:`actions`,children:(0,S.jsx)(`button`,{disabled:!Bi.email.trim()||Bi.password.length<8,onClick:()=>void Y(async()=>{let e=await q.createUser(Bi);dr(await q.listUsers()),Vi({email:``,password:``,platformRole:`user`}),Ui(t=>({...t,userId:e.id}))},`Пользователь создан.`),children:`Создать пользователя`})}),(0,S.jsx)(M,{columns:[`пользователь`,`роль платформы`,`id`],rows:ur.map(e=>[e.email,(0,S.jsx)(k,{value:e.platform_role||`user`}),(0,S.jsx)(`code`,{children:e.id})])})]}),(0,S.jsxs)(`article`,{className:`card`,children:[(0,S.jsx)(`h3`,{children:`Участник организации`}),(0,S.jsxs)(je,{children:[(0,S.jsxs)(`label`,{children:[`Организация`,(0,S.jsxs)(`select`,{value:Hi.organizationId,onChange:e=>Ui({...Hi,organizationId:e.target.value}),children:[(0,S.jsx)(`option`,{value:``,children:`Выберите организацию`}),cr.map(e=>(0,S.jsx)(`option`,{value:e.id,children:e.name},e.id))]})]}),(0,S.jsxs)(`label`,{children:[`Пользователь`,(0,S.jsxs)(`select`,{value:Hi.userId,onChange:e=>Ui({...Hi,userId:e.target.value}),children:[(0,S.jsx)(`option`,{value:``,children:`Выберите пользователя`}),ur.map(e=>(0,S.jsx)(`option`,{value:e.id,children:e.email},e.id))]})]}),(0,S.jsxs)(`label`,{children:[`Роль`,(0,S.jsxs)(`select`,{value:Hi.roleId,onChange:e=>Ui({...Hi,roleId:e.target.value}),children:[(0,S.jsx)(`option`,{value:`org_owner`,children:`org_owner`}),(0,S.jsx)(`option`,{value:`org_admin`,children:`org_admin`}),(0,S.jsx)(`option`,{value:`org_operator`,children:`org_operator`}),(0,S.jsx)(`option`,{value:`org_member`,children:`org_member`}),(0,S.jsx)(`option`,{value:`org_viewer`,children:`org_viewer`})]})]})]}),(0,S.jsx)(`div`,{className:`actions`,children:(0,S.jsx)(`button`,{disabled:!Hi.organizationId||!Hi.userId.trim(),onClick:()=>void Y(()=>q.addOrganizationMembership(Hi.organizationId,{userId:Hi.userId,roleId:Hi.roleId}),`Участник организации сохранен.`),children:`Сохранить участника`})})]}),(0,S.jsxs)(`article`,{className:`card`,children:[(0,S.jsx)(`h3`,{children:`Безопасная сводка`}),(0,S.jsxs)(`div`,{className:`inlineForm`,children:[(0,S.jsxs)(`select`,{value:Er,onChange:e=>Dr(e.target.value),children:[(0,S.jsx)(`option`,{value:``,children:`Выберите организацию`}),cr.map(e=>(0,S.jsx)(`option`,{value:e.id,children:e.name},e.id))]}),(0,S.jsx)(`button`,{disabled:!Er,onClick:()=>void Y(async()=>{kr(await q.getOrganizationAdminSummary(Er))},`Сводка организации загружена.`),children:`Обновить`})]}),Or?(0,S.jsxs)(`div`,{className:`stack`,children:[(0,S.jsx)(fe,{label:`Ресурсы`,value:Or.resource_count,tone:`steel`}),(0,S.jsx)(fe,{label:`Активные сессии`,value:Or.active_session_count,tone:`green`}),(0,S.jsx)(A,{label:`Topology exposure`,value:Or.topology_exposure}),(0,S.jsx)(M,{columns:[`контур`,`состояние`],rows:Object.entries(Or.connector_status||{}).map(([e,t])=>[e,typeof t==`string`?V(t):JSON.stringify(t)])}),(0,S.jsx)(M,{columns:[`протокол`,`количество`],rows:Or.service_endpoints.map(e=>[e.protocol,String(e.count)])})]}):(0,S.jsx)(me,{title:`Сводка не выбрана`,text:`Выберите организацию, чтобы проверить tenant-safe состояние.`})]})]}),w===`servers`&&(0,S.jsx)(`section`,{className:`grid two`,children:(0,S.jsxs)(`article`,{className:`card span2`,children:[(0,S.jsxs)(`div`,{className:`cardHead`,children:[(0,S.jsxs)(`div`,{children:[(0,S.jsx)(`h3`,{children:`Справочник серверов`}),(0,S.jsx)(`p`,{className:`muted`,children:`Единый каталог целей для RDP/VPN: адрес сервера, организация, протокол и предпочтительный вход/выход маршрута.`})]}),(0,S.jsx)(`span`,{className:`pill`,children:fr.length})]}),(0,S.jsxs)(je,{children:[(0,S.jsxs)(`label`,{children:[`Организация`,(0,S.jsxs)(`select`,{value:K.organizationId,onChange:e=>Ji({...K,organizationId:e.target.value}),children:[(0,S.jsx)(`option`,{value:``,children:`Выберите организацию`}),cr.map(e=>(0,S.jsx)(`option`,{value:e.id,children:e.name},e.id))]})]}),(0,S.jsxs)(`label`,{children:[`Имя сервера`,(0,S.jsx)(`input`,{value:K.name,onChange:e=>Ji({...K,name:e.target.value}),placeholder:`Office RDP`})]}),(0,S.jsxs)(`label`,{children:[`Адрес`,(0,S.jsx)(`input`,{value:K.address,onChange:e=>Ji({...K,address:e.target.value}),placeholder:`192.168.1.10:3389`})]}),(0,S.jsxs)(`label`,{children:[`Протокол`,(0,S.jsxs)(`select`,{value:K.protocol,onChange:e=>Ji({...K,protocol:e.target.value}),children:[(0,S.jsx)(`option`,{value:`rdp`,children:`RDP`}),(0,S.jsx)(`option`,{value:`vpn`,children:`VPN`}),(0,S.jsx)(`option`,{value:`ssh`,children:`SSH`}),(0,S.jsx)(`option`,{value:`http`,children:`HTTP`})]})]}),(0,S.jsxs)(`label`,{children:[`Вход`,(0,S.jsxs)(`select`,{value:K.entryNode,onChange:e=>Ji({...K,entryNode:e.target.value}),children:[(0,S.jsx)(`option`,{value:``,children:`Автоматически`}),j.map(e=>(0,S.jsx)(`option`,{value:e.id,children:e.name},e.id))]})]}),(0,S.jsxs)(`label`,{children:[`Выход`,(0,S.jsxs)(`select`,{value:K.exitNode,onChange:e=>Ji({...K,exitNode:e.target.value}),children:[(0,S.jsx)(`option`,{value:``,children:`Автоматически`}),j.map(e=>(0,S.jsx)(`option`,{value:e.id,children:e.name},e.id))]})]}),(0,S.jsxs)(`label`,{children:[`Теги`,(0,S.jsx)(`input`,{value:K.tags,onChange:e=>Ji({...K,tags:e.target.value}),placeholder:`home, accounting`})]}),(0,S.jsxs)(`label`,{children:[`RDP пользователь`,(0,S.jsx)(`input`,{value:K.username,onChange:e=>Ji({...K,username:e.target.value}),placeholder:`user или DOMAIN\\\\user`})]}),(0,S.jsxs)(`label`,{children:[`RDP пароль`,(0,S.jsx)(`input`,{type:`password`,value:K.password,onChange:e=>Ji({...K,password:e.target.value}),placeholder:`хранится как secret`})]}),(0,S.jsxs)(`label`,{children:[`Домен`,(0,S.jsx)(`input`,{value:K.domain,onChange:e=>Ji({...K,domain:e.target.value}),placeholder:`опционально`})]})]}),(0,S.jsx)(`div`,{className:`actions`,children:(0,S.jsx)(`button`,{className:`primary`,disabled:!K.organizationId||!K.name.trim()||!K.address.trim(),onClick:()=>void Y(async()=>{let e=[`rdp`,`vnc`,`ssh`].includes(K.protocol)?`rap-secret://org/${K.organizationId}/resources/${crypto.randomUUID()}/primary`:null,t=await q.createResource({organizationId:K.organizationId,name:K.name,address:K.address,protocol:K.protocol,secretRef:e,certificateVerificationMode:K.protocol===`rdp`?`ignore`:`strict`,clipboardMode:K.protocol===`rdp`?`bidirectional`:`disabled`,fileTransferMode:K.protocol===`rdp`?`bidirectional`:`disabled`,metadata:{route_mode:K.routeMode,preferred_entry_node_id:K.entryNode||null,preferred_exit_node_id:K.exitNode||null,tags:K.tags.split(`,`).map(e=>e.trim()).filter(Boolean)}});[`rdp`,`vnc`,`ssh`].includes(K.protocol)&&(K.username.trim()||K.password)&&await q.upsertResourceSecret(t.id,{username:K.username.trim(),password:K.password,domain:K.domain.trim()}),Ji({...K,name:``,address:``,tags:``,username:``,password:``,domain:``})},`Сервер добавлен в справочник.`),children:`Добавить сервер`})}),(0,S.jsx)(M,{columns:[`сервер`,`адрес`,`протокол`,`секрет`,`организация`,`маршрут`,`создано`,`действия`],rows:fr.map(e=>{let t=e.metadata||{},n=cr.find(t=>t.id===e.organization_id);return[e.name,e.address,e.protocol,e.has_secret?`сохранен`:e.secret_ref?`нужен payload`:`нет`,n?.name||R(e.organization_id),`${R(String(t.preferred_entry_node_id||``))||`auto`} -> ${R(String(t.preferred_exit_node_id||``))||`auto`}`,z(e.created_at),(0,S.jsx)(`button`,{className:`ghost`,onClick:()=>{Gi(e),qi({username:``,password:``,domain:``})},children:`Обновить secret`})]})}),Wi&&(0,S.jsx)(`div`,{className:`modalBackdrop`,role:`presentation`,children:(0,S.jsxs)(`div`,{className:`modalCard`,role:`dialog`,"aria-modal":`true`,"aria-labelledby":`resource-secret-title`,children:[(0,S.jsxs)(`div`,{className:`cardHead`,children:[(0,S.jsxs)(`div`,{children:[(0,S.jsx)(`h3`,{id:`resource-secret-title`,children:`Учетные данные RDP`}),(0,S.jsxs)(`p`,{className:`muted`,children:[Wi.name,` · `,Wi.address]})]}),(0,S.jsx)(`button`,{className:`ghost`,onClick:()=>Gi(null),children:`Закрыть`})]}),(0,S.jsxs)(je,{children:[(0,S.jsxs)(`label`,{children:[`Пользователь`,(0,S.jsx)(`input`,{value:Ki.username,onChange:e=>qi({...Ki,username:e.target.value}),placeholder:`user или DOMAIN\\\\user`})]}),(0,S.jsxs)(`label`,{children:[`Пароль`,(0,S.jsx)(`input`,{type:`password`,value:Ki.password,onChange:e=>qi({...Ki,password:e.target.value})})]}),(0,S.jsxs)(`label`,{children:[`Домен`,(0,S.jsx)(`input`,{value:Ki.domain,onChange:e=>qi({...Ki,domain:e.target.value}),placeholder:`опционально`})]})]}),(0,S.jsx)(`p`,{className:`muted`,children:`Пароль сохраняется как encrypted resource secret. В metadata ресурса он не попадет.`}),(0,S.jsxs)(`div`,{className:`actions`,children:[(0,S.jsx)(`button`,{className:`primary`,disabled:!Ki.username.trim()||!Ki.password,onClick:()=>void Y(async()=>{await q.upsertResourceSecret(Wi.id,{username:Ki.username.trim(),password:Ki.password,domain:Ki.domain.trim()}),Gi(null),qi({username:``,password:``,domain:``})},`Secret ресурса обновлен.`),children:`Сохранить secret`}),(0,S.jsx)(`button`,{onClick:()=>Gi(null),children:`Отмена`})]})]})})]})}),w===`audit`&&(0,S.jsxs)(`section`,{className:`card`,children:[(0,S.jsx)(`h3`,{children:`Аудит кластера`}),(0,S.jsx)(M,{columns:[`событие`,`цель`,`actor`,`создано`],rows:gr.map(e=>[e.event_type,`${e.target_type}${e.target_id?`:${R(e.target_id)}`:``}`,e.actor_user_id?R(e.actor_user_id):`system`,z(e.created_at)])})]})]})]})}function fe({label:e,value:t,tone:n}){return(0,S.jsxs)(`article`,{className:`metric ${n}`,children:[(0,S.jsx)(`span`,{children:e}),(0,S.jsx)(`strong`,{children:t})]})}function O({label:e,value:t}){return(0,S.jsxs)(`div`,{className:`signal`,children:[(0,S.jsx)(`span`,{children:e}),(0,S.jsx)(`strong`,{children:t})]})}function k({value:e}){return(0,S.jsx)(`span`,{className:`status ${e.replace(/_/g,`-`)}`,children:V(e)})}function pe({label:e,value:t,tone:n}){return(0,S.jsxs)(`span`,{className:`functionState ${n||``}`,children:[(0,S.jsx)(`small`,{children:e}),(0,S.jsx)(`strong`,{children:t})]})}function A({label:e,value:t}){return(0,S.jsxs)(`div`,{className:`stateLine`,children:[(0,S.jsx)(`span`,{children:e}),(0,S.jsx)(`strong`,{children:t})]})}function me({title:e,text:t}){return(0,S.jsxs)(`article`,{className:`empty`,children:[(0,S.jsx)(`h3`,{children:e}),(0,S.jsx)(`p`,{children:t})]})}function he(e){if(!e)return(0,S.jsx)(`p`,{className:`muted`,children:`Диагностика Android-клиента не загружена. Укажи device id из приложения и нажми “Обновить клиент”.`});let t=at(e.payload)||{},n=at(t.runtime),r=at(t.vpn_config),i=N(t,`app_version`,`н/д`),a=N(t,`service_state`,`н/д`),o=N(t,`control_network_mode`,`н/д`),s=N(r,`packet_relay_active_base_url`)||N(r,`packet_relay_base_url`,`н/д`),c=N(r,`packet_relay_profile_base_url`,`н/д`),l=N(r,`packet_relay_candidate_urls`,`н/д`),u=lt(n,`uplink_read_total`),d=lt(n,`uplink_sent_total`),f=lt(n,`downlink_received_total`),p=lt(n,`uplink_dropped_packets`)+lt(n,`downlink_dropped_packets`),m=lt(n,`uplink_bypassed_control_packets`),h=lt(n,`downlink_received_bytes`),g=lt(n,`uplink_sent_bytes`),_=N(n,`state`,`н/д`),v=N(n,`message`,``),y=lt(n,`uplink_sent_mbps`),b=lt(n,`downlink_received_mbps`),x=N(t,`last_command_type`,`н/д`),ee=N(t,`last_command_result`,`н/д`);return(0,S.jsxs)(`div`,{className:`vpnCard diagnosticCard`,children:[(0,S.jsxs)(`div`,{children:[(0,S.jsxs)(`strong`,{children:[`Android client `,R(e.device_id)]}),(0,S.jsxs)(`p`,{className:`muted`,children:[i,` / `,a,` / `,z(e.observed_at)]}),(0,S.jsx)(k,{value:Date.now()-new Date(e.observed_at).getTime()<3e4?`active`:`degraded`}),(0,S.jsx)(`span`,{className:`pill`,children:o})]}),(0,S.jsxs)(`div`,{className:`stateList`,children:[(0,S.jsx)(A,{label:`Relay active`,value:s}),(0,S.jsx)(A,{label:`Relay profile`,value:c}),(0,S.jsx)(A,{label:`Relay candidates`,value:l}),(0,S.jsx)(A,{label:`Packets read/sent/down`,value:`${u} / ${d} / ${f}`}),(0,S.jsx)(A,{label:`Drops / control bypass`,value:`${p} / ${m}`}),(0,S.jsx)(A,{label:`Bytes up/down`,value:`${B(g)} / ${B(h)}`}),(0,S.jsx)(A,{label:`Rate up/down`,value:`${y.toFixed(2)} / ${b.toFixed(2)} Mbps`}),(0,S.jsx)(A,{label:`Runtime`,value:v?`${_}: ${v}`:_}),(0,S.jsx)(A,{label:`Last command`,value:`${x}: ${ee}`})]})]})}function ge({items:e,emptyText:t}){if(e.length===0)return(0,S.jsx)(me,{title:t,text:`Тестовая телеметрия появится здесь после отчета node-agent.`});let n=[...e].reverse().slice(-24),r=e[0],i=Math.max(...n.map(e=>e.memory_used_bytes||0),1);return(0,S.jsxs)(`div`,{className:`telemetryBox`,children:[(0,S.jsxs)(`div`,{className:`signalStrip compact`,children:[(0,S.jsx)(O,{label:`Память`,value:`${B(r.memory_used_bytes)} / ${B(r.memory_total_bytes)}`}),(0,S.jsx)(O,{label:`Процессор`,value:r.cpu_percent==null?`н/д`:`${r.cpu_percent.toFixed(1)}%`}),(0,S.jsx)(O,{label:`Процессы`,value:r.process_count==null?`н/д`:String(r.process_count)}),(0,S.jsx)(O,{label:`Обновлено`,value:z(r.observed_at)})]}),(0,S.jsx)(`div`,{className:`sparkline`,"aria-label":`memory telemetry`,children:n.map(e=>(0,S.jsx)(`span`,{style:{height:`${Math.max(8,Math.round((e.memory_used_bytes||0)/i*100))}%`}},e.id))})]})}function _e({node:e,memberships:t,activeRoles:n,desiredWorkloads:r,observedWorkloads:i,heartbeats:a,telemetry:o,meshLinks:s,syntheticConfig:c,allNodes:l,onSetUpdatePolicy:u,updatePlan:d,updateStatuses:f,labels:p}){let m=a[0],h=o[0],g=at(m?.metadata?.mesh_listener_report),v=at(m?.metadata?.mesh_endpoint_report),y=at(m?.metadata?.mesh_outbound_session_report),b=c?.mesh_listener,x=at(m?.metadata?.mesh_peer_recovery_report),ee=at(m?.metadata?.mesh_peer_connection_intent_report),C=at(m?.metadata?.mesh_peer_connection_manager_report),te=at(m?.metadata?.mesh_rendezvous_lease_report),ne=at(m?.metadata?.mesh_route_path_decision_report),re=at(m?.metadata?.mesh_route_generation_report),ie=at(m?.metadata?.mesh_route_health_config_report),w=c?.service_channel_route_feedback,ae=w?.observations||[],oe=c?.service_channel_remediation_commands||[],T=Qe(s).filter(e=>e.source_node_id!==e.target_node_id),se=T.filter(e=>e.link_status===`reachable`),ce=T.filter(e=>e.link_status!==`reachable`),le=Object.entries(m?.capabilities||{}).sort(([e],[t])=>e.localeCompare(t)),E=ot(C?.probe_results),[D,ue]=(0,_.useState)(`network`),de=ze(f,`rap-node-agent`),fe=ze(f,`rap-host-agent`),pe=f[0],me=Ye(f),he=t.find(t=>t.node.id===e.id)?.cluster.id||t[0]?.cluster.id||``,_e=ot(v?.endpoint_candidates),ye=_e[0],be=st(v,[`peer_endpoint`,`advertised_endpoint`,`endpoint`])||N(ye,`address`,``)||``,xe=st(v,[`transport`,`advertise_transport`])||N(ye,`transport`,``)||`н/д`,Se=st(v,[`connectivity_mode`,`connectivity`])||N(ye,`connectivity_mode`,``)||N(g,`inbound_reachability`,``)||`н/д`,Ce=N(v,`nat_type`,N(ye,`nat_type`,`н/д`)),we=N(v,`region`,N(g,`region`,N(ye,`region`,`н/д`))),Te=N(v,`observed_at`,N(g,`observed_at`,m?.observed_at||`н/д`)),j=N(g,`status`,``)||(be?`нет listener report, есть advertised endpoint`:`report отсутствует`),Ee=N(g,`effective_listen_addr`,``)||`н/д`,De=N(g,`configured_listen_addr`,``)||`н/д`,Oe=_e.length>0?_e:be?[{endpoint_id:`${e.id}-reported`,address:be,transport:xe,reachability:Se,connectivity_mode:Se,nat_type:Ce,priority:`н/д`,last_verified_at:Te}]:[],ke=Object.entries(c?.peer_endpoints||{}),Ae=Object.entries(c?.peer_endpoint_candidates||{}).flatMap(([e,t])=>t.map(t=>({peerID:e,candidate:t}))),je=new Set(se.map(t=>t.source_node_id===e.id?t.target_node_id:t.source_node_id)),Me=Ae.filter(({peerID:e})=>!je.has(e)),Ne=[g?`listener report: есть`:`listener report: не прислан агентом`,v?`endpoint report: есть`:`endpoint report: не прислан агентом`,y?`outbound session: есть`:`outbound session: не прислан агентом`,c?`scoped config: ${c.enabled?`enabled`:`disabled`}`:`scoped config: не загружен`,w?`service-channel feedback: ${w.observation_count}`:`service-channel feedback: не загружен`,`active links: ${se.length}/${T.length}`];return(0,S.jsxs)(`div`,{className:`nodeDetails`,children:[(0,S.jsxs)(`section`,{className:`nodePanel`,children:[(0,S.jsx)(`h4`,{children:`Сводка runtime`}),(0,S.jsxs)(`div`,{className:`signalStrip compact nodeMetricGrid`,children:[(0,S.jsx)(O,{label:`Heartbeat`,value:m?z(m.observed_at):`н/д`}),(0,S.jsx)(O,{label:`Health`,value:V(m?.health_status||e.health_status)}),(0,S.jsx)(O,{label:`Listener`,value:gn(m)}),(0,S.jsx)(O,{label:`Mesh links`,value:`${se.length}/${T.length}`}),(0,S.jsx)(O,{label:`Update`,value:Be(pe,d)})]}),(0,S.jsxs)(`div`,{className:`summaryChips`,children:[(0,S.jsx)(k,{value:e.registration_status}),(0,S.jsx)(k,{value:e.membership_status}),(0,S.jsx)(k,{value:e.partition_state}),(0,S.jsx)(`span`,{className:`pill`,children:e.reported_version||m?.reported_version||`версия неизвестна`}),g?.one_way_connectivity===!0&&(0,S.jsx)(`span`,{className:`pill warn`,children:`one-way`}),g?.port_conflict===!0&&(0,S.jsx)(`span`,{className:`pill bad`,children:`port conflict`})]})]}),(0,S.jsx)(`div`,{className:`nodeTabs`,role:`tablist`,"aria-label":`Node analysis sheets`,children:[[`overview`,`Обзор`],[`network`,`Сеть и адреса`],[`mesh`,`Mesh`],[`services`,`Роли и сервисы`],[`telemetry`,`Телеметрия`],[`updates`,`Обновления`],[`raw`,`Raw`]].map(([e,t])=>(0,S.jsx)(`button`,{className:D===e?`active`:``,onClick:()=>ue(e),type:`button`,children:t},e))}),D===`overview`&&(0,S.jsxs)(`div`,{className:`nodeDetailGrid`,children:[(0,S.jsxs)(`section`,{className:`nodePanel`,children:[(0,S.jsx)(`h4`,{children:`Идентичность и размещение`}),(0,S.jsxs)(`div`,{className:`stateList`,children:[(0,S.jsx)(A,{label:`Node ID`,value:e.id}),(0,S.jsx)(A,{label:`Node key`,value:e.node_key}),(0,S.jsx)(A,{label:`Имя`,value:e.name}),(0,S.jsx)(A,{label:`Владение`,value:V(e.ownership_type)}),(0,S.jsx)(A,{label:`Owner org`,value:R(e.owner_organization_id)}),(0,S.jsx)(A,{label:`Группа`,value:e.node_group_name||p.ungroupedNodes}),(0,S.jsx)(A,{label:`Создан`,value:z(e.created_at)}),(0,S.jsx)(A,{label:`Обновлен`,value:z(e.updated_at)})]})]}),(0,S.jsxs)(`section`,{className:`nodePanel`,children:[(0,S.jsx)(`h4`,{children:`Участие в кластерах`}),(0,S.jsx)(`div`,{className:`membershipList`,children:t.map(t=>(0,S.jsxs)(`span`,{className:t.node.id===e.id&&t.node.membership_status===`active`?`pill good`:`pill`,children:[t.cluster.name,`: `,V(t.node.membership_status)]},t.cluster.id))}),(0,S.jsxs)(`div`,{className:`stateList`,children:[(0,S.jsx)(A,{label:`Активных ролей`,value:String(n.length)}),(0,S.jsx)(A,{label:`Desired workloads`,value:String(r.length)}),(0,S.jsx)(A,{label:`Observed workloads`,value:String(i.length)}),(0,S.jsx)(A,{label:`Последний сигнал`,value:z(e.last_seen_at||m?.observed_at)})]})]})]}),D===`network`&&(0,S.jsxs)(S.Fragment,{children:[(0,S.jsxs)(`div`,{className:`nodeDetailGrid`,children:[(0,S.jsxs)(`section`,{className:`nodePanel`,children:[(0,S.jsx)(`h4`,{children:`Локальный listener`}),(0,S.jsxs)(`div`,{className:`stateList`,children:[(0,S.jsx)(A,{label:`Статус`,value:j}),(0,S.jsx)(A,{label:`Режим порта`,value:N(g,`listen_port_mode`,`н/д`)}),(0,S.jsx)(A,{label:`Configured addr`,value:De}),(0,S.jsx)(A,{label:`Effective addr`,value:Ee}),(0,S.jsx)(A,{label:`Inbound`,value:N(g,`inbound_reachability`,Se)}),(0,S.jsx)(A,{label:`One-way`,value:N(g,`one_way_connectivity`,`н/д`)}),(0,S.jsx)(A,{label:`Port conflict`,value:N(g,`port_conflict`,`false`)}),(0,S.jsx)(A,{label:`Failure`,value:N(g,`failure_error`,N(g,`failure_reason`,`нет`))})]})]}),(0,S.jsxs)(`section`,{className:`nodePanel`,children:[(0,S.jsx)(`h4`,{children:`Desired listener`}),(0,S.jsxs)(`div`,{className:`stateList`,children:[(0,S.jsx)(A,{label:`Состояние`,value:b?.desired_state||`н/д`}),(0,S.jsx)(A,{label:`Режим порта`,value:b?.listen_port_mode||`н/д`}),(0,S.jsx)(A,{label:`Listen addr`,value:b?.listen_addr||`н/д`}),(0,S.jsx)(A,{label:`Auto range`,value:b?`${b.auto_port_start||`н/д`}-${b.auto_port_end||`н/д`}`:`н/д`}),(0,S.jsx)(A,{label:`Advertise endpoint`,value:b?.advertise_endpoint||`auto-discovery`}),(0,S.jsx)(A,{label:`Advertise transport`,value:b?.advertise_transport||`н/д`}),(0,S.jsx)(A,{label:`Connectivity`,value:b?.connectivity_mode||`н/д`}),(0,S.jsx)(A,{label:`NAT`,value:b?.nat_type||`н/д`}),(0,S.jsx)(A,{label:`Region/site`,value:b?.region||`н/д`}),(0,S.jsx)(A,{label:`Version`,value:b?.config_version||`н/д`})]})]}),(0,S.jsxs)(`section`,{className:`nodePanel`,children:[(0,S.jsx)(`h4`,{children:`Что узел сообщает кластеру`}),(0,S.jsxs)(`div`,{className:`stateList`,children:[(0,S.jsx)(A,{label:`Advertised endpoint`,value:be||`не прислан`}),(0,S.jsx)(A,{label:`Transport`,value:xe}),(0,S.jsx)(A,{label:`Connectivity`,value:Se}),(0,S.jsx)(A,{label:`NAT`,value:Ce}),(0,S.jsx)(A,{label:`Region/site`,value:we}),(0,S.jsx)(A,{label:`Observed`,value:Te})]})]})]}),(0,S.jsxs)(`section`,{className:`nodePanel`,children:[(0,S.jsx)(`h4`,{children:`Исходящий control-channel`}),(0,S.jsxs)(`div`,{className:`stateList`,children:[(0,S.jsx)(A,{label:`Status`,value:N(y,`status`,`не прислан`)}),(0,S.jsx)(A,{label:`Direction`,value:N(y,`direction`,`н/д`)}),(0,S.jsx)(A,{label:`Transport`,value:N(y,`transport`,`н/д`)}),(0,S.jsx)(A,{label:`Control Plane`,value:N(y,`control_plane_url`,`н/д`)}),(0,S.jsx)(A,{label:`Reverse usable`,value:N(y,`usable_for_inbound_control`,`н/д`)}),(0,S.jsx)(A,{label:`Inbound required`,value:N(y,`inbound_listener_required`,`н/д`)}),(0,S.jsx)(A,{label:`Relay ready`,value:N(y,`peer_connection_relay_ready`,`0`)}),(0,S.jsx)(A,{label:`Waiting rendezvous`,value:N(y,`peer_connection_waiting`,`0`)}),(0,S.jsx)(A,{label:`Rendezvous leases`,value:N(y,`rendezvous_lease_count`,`0`)}),(0,S.jsx)(A,{label:`Listener conflict`,value:N(y,`listener_port_conflict`,`false`)})]})]}),(0,S.jsxs)(`section`,{className:`nodePanel`,children:[(0,S.jsx)(`h4`,{children:`Наличие сетевых отчетов`}),(0,S.jsx)(`div`,{className:`summaryChips`,children:Ne.map(e=>(0,S.jsx)(`span`,{className:e.includes(`не прислан`)||e.includes(`не загружен`)?`pill warn`:`pill good`,children:e},e))}),!v&&!g&&(0,S.jsx)(`p`,{className:`muted`,children:`У этого узла есть heartbeat/mesh manager данные, но агент не передал адресный отчет. До обновления агента или включения endpoint/listener report панель может показать связи и config peers, но не может достоверно назвать локальный listen address.`})]}),(0,S.jsxs)(`section`,{className:`nodePanel`,children:[(0,S.jsx)(`h4`,{children:`Endpoint candidates узла`}),(0,S.jsx)(M,{columns:[`id`,`address`,`transport`,`reachability`,`mode`,`nat`,`priority`,`verified`],rows:Oe.map(e=>[N(e,`endpoint_id`,`н/д`),N(e,`address`,`н/д`),N(e,`transport`,`н/д`),N(e,`reachability`,`н/д`),N(e,`connectivity_mode`,`н/д`),N(e,`nat_type`,`н/д`),N(e,`priority`,`н/д`),N(e,`last_verified_at`,`н/д`)])})]}),(0,S.jsxs)(`div`,{className:`nodeDetailGrid`,children:[(0,S.jsxs)(`section`,{className:`nodePanel`,children:[(0,S.jsx)(`h4`,{children:`Рабочие peer endpoints из config`}),(0,S.jsx)(M,{columns:[`peer`,`endpoint`],rows:ke.map(([e,t])=>[P(l,e),t])})]}),(0,S.jsxs)(`section`,{className:`nodePanel`,children:[(0,S.jsx)(`h4`,{children:`Резервные кандидаты peer`}),(0,S.jsx)(M,{columns:[`peer`,`address`,`transport`,`reachability`,`mode`,`priority`],rows:Me.slice(0,20).map(({peerID:e,candidate:t})=>[P(l,e),t.address,t.transport,t.reachability,t.connectivity_mode,String(t.priority)])})]})]}),(0,S.jsxs)(`section`,{className:`nodePanel`,children:[(0,S.jsx)(`h4`,{children:`Активные связи этого узла`}),(0,S.jsx)(M,{columns:[`peer`,`направление`,`тип`,`статус`,`latency`,`quality`,`путь`,`наблюдение`],rows:T.slice(0,20).map(t=>[P(l,t.source_node_id===e.id?t.target_node_id:t.source_node_id),t.source_node_id===e.id?`out`:`in`,et(t),t.link_status,t.latency_ms==null?`н/д`:`${t.latency_ms}мс`,t.quality_score==null?`н/д`:String(t.quality_score),tt(t,l),z(t.observed_at)])}),ce.length>0&&(0,S.jsxs)(`p`,{className:`muted`,children:[`Проблемных связей: `,ce.length,`. Их статус виден в таблице выше.`]})]}),(0,S.jsxs)(`section`,{className:`nodePanel`,children:[(0,S.jsx)(`h4`,{children:`Проверка адресов peer-to-peer`}),(0,S.jsx)(M,{columns:[`peer`,`status`,`selected endpoint`,`candidate`,`latency`,`attempts`,`failure`],rows:E.slice(0,20).map(e=>[P(l,N(e,`node_id`,``)),N(e,`link_status`,`н/д`),N(e,`selected_endpoint`,N(e,`endpoint`,`н/д`)),N(e,`selected_candidate_id`,`н/д`),N(e,`latency_ms`,`н/д`),dt(e),N(e,`failure_reason`,`нет`)])})]})]}),D===`mesh`&&(0,S.jsxs)(S.Fragment,{children:[(0,S.jsxs)(`div`,{className:`nodeDetailGrid`,children:[(0,S.jsxs)(`section`,{className:`nodePanel`,children:[(0,S.jsx)(`h4`,{children:`Mesh control-plane`}),(0,S.jsxs)(`div`,{className:`stateList`,children:[(0,S.jsx)(A,{label:`Recovery`,value:un(m)}),(0,S.jsx)(A,{label:`Intents`,value:dn(m)}),(0,S.jsx)(A,{label:`Manager`,value:hn(m)}),(0,S.jsx)(A,{label:`Rendezvous`,value:fn(m)}),(0,S.jsx)(A,{label:`Path decisions`,value:pn(m)}),(0,S.jsx)(A,{label:`Route generation`,value:L(m)}),(0,S.jsx)(A,{label:`Route health`,value:mn(m)}),(0,S.jsx)(A,{label:`Service-channel feedback`,value:w?`${w.healthy_route_count} healthy / ${w.degraded_route_count} degraded / ${w.fenced_route_count} fenced`:`н/д`}),(0,S.jsx)(A,{label:`Recovery policy`,value:w?.recovery_policy?`${w.recovery_policy.source} p${w.recovery_policy.hysteresis_penalty} promote ${w.recovery_policy.promotion_min_samples}`:`н/д`}),(0,S.jsx)(A,{label:`Route policy`,value:c?.route_path_decisions?.recovery_policy?`${c.route_path_decisions.recovery_policy.source} fail/drop/slow ${c.route_path_decisions.recovery_policy.demotion_failure_threshold}/${c.route_path_decisions.recovery_policy.demotion_drop_threshold}/${c.route_path_decisions.recovery_policy.demotion_slow_threshold}`:`н/д`}),(0,S.jsx)(A,{label:`Config version`,value:c?.config_version||`н/д`})]})]}),(0,S.jsxs)(`section`,{className:`nodePanel`,children:[(0,S.jsx)(`h4`,{children:`Scoped config counts`}),(0,S.jsxs)(`div`,{className:`stateList`,children:[(0,S.jsx)(A,{label:`Peer endpoints`,value:String(ke.length)}),(0,S.jsx)(A,{label:`Endpoint candidates`,value:String(Ae.length)}),(0,S.jsx)(A,{label:`Peer directory`,value:String(c?.peer_directory?.length||0)}),(0,S.jsx)(A,{label:`Recovery seeds`,value:String(c?.recovery_seeds?.length||0)}),(0,S.jsx)(A,{label:`Rendezvous leases`,value:String(c?.rendezvous_leases?.length||0)}),(0,S.jsx)(A,{label:`Routes`,value:String(c?.routes?.length||0)}),(0,S.jsx)(A,{label:`Fenced routes`,value:String(w?.fenced_route_count||0)}),(0,S.jsx)(A,{label:`Remediation commands`,value:String(oe.length)}),(0,S.jsx)(A,{label:`Feedback provenance`,value:w?`missing ${w.missing_provenance_count||0} / stale policy ${w.stale_policy_count||0} / stale gen ${w.stale_generation_count||0}`:`н/д`})]})]})]}),(0,S.jsxs)(`section`,{className:`nodePanel`,children:[(0,S.jsx)(`h4`,{children:`Route decisions`}),(0,S.jsx)(M,{columns:[`route`,`replacement`,`source`,`destination`,`effective hops`,`decision`,`score`,`expires`],rows:(c?.route_path_decisions?.decisions||[]).map(e=>[R(e.route_id),e.replacement_route_id?R(e.replacement_route_id):`нет`,P(l,e.source_node_id),P(l,e.destination_node_id),e.effective_hops.map(e=>yn(P(l,e))).join(` > `),e.decision_source||(e.selected_relay_id?P(l,e.selected_relay_id):`direct`),e.path_score==null?`н/д`:String(e.path_score),z(e.expires_at)])})]}),oe.length>0&&(0,S.jsxs)(`section`,{className:`nodePanel`,children:[(0,S.jsx)(`h4`,{children:`Service-channel remediation commands`}),(0,S.jsx)(M,{columns:[`channel`,`action`,`primary`,`replacement`,`guard`,`execution`,`reason`,`expires`],rows:oe.slice(0,20).map(e=>[R(e?.channel_id||``),(0,S.jsx)(`span`,{className:`pill warn`,children:V(e?.action||``)}),e?.primary_route_id?R(e.primary_route_id):`н/д`,e?.replacement_route_id?R(e.replacement_route_id):`н/д`,(0,S.jsx)(`span`,{className:`pill ${e?.guard_status===`rejected`?`bad`:e?.guard_status===`allowed`?`good`:``}`,children:e?.guard_status?V(e.guard_status):`н/д`}),(0,S.jsxs)(`span`,{className:`pill ${Dn(e?.execution_status)}`,children:[e?.execution_status?V(e.execution_status):`н/д`,e?.execution_reason?` / ${V(e.execution_reason)}`:``]}),e?.reason||`н/д`,e?.expires_at?z(e.expires_at):`н/д`])})]}),(0,S.jsxs)(`section`,{className:`nodePanel`,children:[(0,S.jsx)(`h4`,{children:`Service-channel route feedback`}),(0,S.jsx)(M,{columns:[`route`,`service`,`status`,`recovery`,`score`,`reasons`,`failures`,`duration`,`expires`],rows:ae.slice(0,40).map(e=>[R(e.route_id),e.service_class,(0,S.jsx)(`span`,{className:`pill ${We(e.feedback_status)}`,children:V(e.feedback_status)}),e.recovery_state?(0,S.jsxs)(`span`,{className:`pill ${Ge(e.recovery_state)}`,children:[e.recovery_demoted?`demoted ${e.recovery_reason?V(e.recovery_reason):``}`:e.recovery_promoted?`promoted`:V(e.recovery_state),e.recovery_hysteresis_penalty?` -${e.recovery_hysteresis_penalty}`:``]}):e.stale_policy||e.stale_generation?(0,S.jsx)(`span`,{className:`pill warn`,children:V(e.stale_reason||`stale`)}):e.provenance_missing?(0,S.jsx)(`span`,{className:`pill warn`,children:`provenance missing`}):`нет`,String(e.score_adjustment),(e.reasons||[]).join(`, `)||`нет`,String(e.consecutive_failures||0),e.last_send_duration_ms==null?`н/д`:`${e.last_send_duration_ms}мс`,z(e.expires_at)])}),ae.length===0&&(0,S.jsx)(`p`,{className:`muted`,children:`Пока нет свежих наблюдений. Узел будет присылать их после реального traffic через service-channel runtime.`})]})]}),D===`services`&&(0,S.jsxs)(S.Fragment,{children:[(0,S.jsxs)(`div`,{className:`nodeDetailGrid`,children:[(0,S.jsxs)(`section`,{className:`nodePanel`,children:[(0,S.jsx)(`h4`,{children:p.nodeRoles}),(0,S.jsxs)(`div`,{className:`serviceTags`,children:[n.length===0&&(0,S.jsx)(`p`,{className:`muted`,children:p.noRoles}),n.map(e=>(0,S.jsxs)(`div`,{className:`serviceTag`,children:[(0,S.jsx)(`strong`,{children:Ie(e.role)}),(0,S.jsx)(`span`,{children:e.organization_id?`organization: ${R(e.organization_id)}`:`cluster-wide`}),(0,S.jsx)(`small`,{children:z(e.assigned_at)}),(0,S.jsx)(`span`,{className:`pill ${sn(e.role,m)}`,children:cn(e.role,m,p)})]},e.id))]})]}),(0,S.jsxs)(`section`,{className:`nodePanel`,children:[(0,S.jsx)(`h4`,{children:`Capabilities`}),(0,S.jsxs)(`div`,{className:`summaryChips`,children:[le.length===0&&(0,S.jsx)(`span`,{className:`muted`,children:`Нет capability heartbeat.`}),le.slice(0,40).map(([e,t])=>(0,S.jsx)(`span`,{className:t===!0?`pill good`:`pill`,children:e},e))]})]})]}),(0,S.jsxs)(`div`,{className:`nodeDetailGrid`,children:[(0,S.jsxs)(`section`,{className:`nodePanel`,children:[(0,S.jsx)(`h4`,{children:p.desiredServices}),(0,S.jsx)(M,{columns:[`service`,`desired`,`runtime`,`version`,`updated`],rows:r.map(e=>[e.service_type,V(e.desired_state),e.runtime_mode,e.version||`не закреплена`,z(e.updated_at)])})]}),(0,S.jsxs)(`section`,{className:`nodePanel`,children:[(0,S.jsx)(`h4`,{children:p.observedServices}),(0,S.jsx)(M,{columns:[`service`,`reported`,`runtime`,`version`,`observed`],rows:i.map(e=>[e.service_type,V(e.reported_state),e.runtime_mode,e.version||`н/д`,z(e.observed_at)])})]})]})]}),D===`telemetry`&&(0,S.jsxs)(S.Fragment,{children:[(0,S.jsxs)(`section`,{className:`nodePanel`,children:[(0,S.jsx)(`h4`,{children:p.nodeTelemetry}),(0,S.jsx)(ge,{items:o,emptyText:p.noTelemetry}),(0,S.jsxs)(`div`,{className:`stateList`,children:[(0,S.jsx)(A,{label:`Disk`,value:`${B(h?.disk_used_bytes)} / ${B(h?.disk_total_bytes)}`}),(0,S.jsx)(A,{label:`Network RX/TX`,value:`${B(h?.network_rx_bytes)} / ${B(h?.network_tx_bytes)}`}),(0,S.jsx)(A,{label:`Payload`,value:h?.payload?ut(h.payload):`н/д`})]})]}),(0,S.jsxs)(`section`,{className:`nodePanel`,children:[(0,S.jsx)(`h4`,{children:p.recentHeartbeats}),(0,S.jsx)(M,{columns:[`состояние`,`версия`,`listener`,`mesh recovery`,`mesh intents`,`rv leases`,`path decisions`,`route gen`,`route health`,`наблюдение`],rows:a.slice(0,10).map(e=>[e.health_status,e.reported_version||`неизвестно`,gn(e),un(e),dn(e),fn(e),pn(e),L(e),mn(e),z(e.observed_at)])})]})]}),D===`updates`&&(0,S.jsxs)(S.Fragment,{children:[(0,S.jsxs)(`div`,{className:`nodeDetailGrid`,children:[(0,S.jsxs)(`section`,{className:`nodePanel`,children:[(0,S.jsx)(`h4`,{children:`Текущая сборка`}),(0,S.jsxs)(`div`,{className:`stateList`,children:[(0,S.jsx)(A,{label:`Node-agent version`,value:e.reported_version||m?.reported_version||`неизвестно`}),(0,S.jsx)(A,{label:`План`,value:d?`${d.action}: ${d.reason}`:`не загружен`}),(0,S.jsx)(A,{label:`Product`,value:d?.product||`rap-node-agent`}),(0,S.jsx)(A,{label:`Target`,value:d?.target_version||`н/д`}),(0,S.jsx)(A,{label:`Strategy`,value:d?.strategy||`н/д`}),(0,S.jsx)(A,{label:`Rollback`,value:d?.rollback_allowed?`разрешен`:`нет`}),(0,S.jsx)(A,{label:`Artifact`,value:d?.artifact?`${d.artifact.kind} ${d.artifact.os}/${d.artifact.arch}`:`н/д`})]}),(0,S.jsxs)(`div`,{className:`actions`,children:[(0,S.jsx)(`button`,{className:`primary`,disabled:!u,onClick:()=>u?.(e,`rap-node-agent`,null),children:`Node-agent latest`}),(0,S.jsx)(`button`,{className:`ghost`,disabled:!u||!d?.target_version,onClick:()=>u?.(e,`rap-node-agent`,d?.target_version||null),children:`Повторить target`}),(0,S.jsx)(`button`,{className:`ghost`,disabled:!u,onClick:()=>u?.(e,`rap-host-agent`,null),children:`Host-agent latest`})]}),(0,S.jsx)(`p`,{className:`muted`,children:`Latest означает policy без закрепленной версии: updater будет брать свежий active release своего канала при следующем цикле или heartbeat hint.`})]}),(0,S.jsxs)(`section`,{className:`nodePanel`,children:[(0,S.jsx)(`h4`,{children:`Последние отчеты updater`}),(0,S.jsxs)(`div`,{className:`stateList`,children:[(0,S.jsx)(A,{label:`Updater health`,value:`${me.label}: ${me.detail}`}),(0,S.jsx)(A,{label:`rap-node-agent`,value:Ve(de)}),(0,S.jsx)(A,{label:`rap-host-agent`,value:Ve(fe)}),(0,S.jsx)(A,{label:`Всего отчетов`,value:String(f.length)}),(0,S.jsx)(A,{label:`Последний отчет`,value:z(pe?.observed_at)})]}),(0,S.jsxs)(`div`,{className:`summaryChips`,children:[(0,S.jsx)(`span`,{className:`pill ${me.tone}`,children:me.label}),de&&(0,S.jsxs)(`span`,{className:`pill ${Ue(de)}`,children:[`node-agent: `,de.status]}),fe&&(0,S.jsxs)(`span`,{className:`pill ${Ue(fe)}`,children:[`host-agent: `,fe.status]}),!de&&!fe&&(0,S.jsx)(`span`,{className:`pill warn`,children:`updater пока не отчитался`})]})]})]}),(0,S.jsxs)(`section`,{className:`nodePanel`,children:[(0,S.jsx)(`h4`,{children:`История обновлений`}),(0,S.jsx)(M,{columns:[`product`,`current`,`target`,`phase`,`status`,`attempt`,`error`,`observed`],rows:f.slice(0,40).map(e=>[e.product,e.current_version||`н/д`,e.target_version||`н/д`,e.phase,(0,S.jsx)(`span`,{className:`pill ${Ue(e)}`,children:e.status}),e.attempt_id?R(e.attempt_id):`н/д`,e.error_message||`нет`,z(e.observed_at)])})]}),(0,S.jsxs)(`section`,{className:`nodePanel`,children:[(0,S.jsx)(`h4`,{children:`Windows repair/update command`}),(0,S.jsx)(`p`,{className:`muted`,children:`Для существующего Windows-узла эта команда переустанавливает wrapper updater без нового join-token, сохраняет local state и запускает обновление до актуальной сборки.`}),(0,S.jsxs)(`div`,{className:`stateList compact`,children:[(0,S.jsx)(A,{label:`Когда выполнять`,value:`если updater stale, host-agent не отчитался или Windows-узел не доходит до target version`}),(0,S.jsx)(A,{label:`Control Plane`,value:Jt()}),(0,S.jsx)(A,{label:`Join-token`,value:`не нужен для repair существующего узла`})]}),(0,S.jsxs)(`div`,{className:`actions`,children:[(0,S.jsx)(`button`,{className:`primary`,onClick:()=>Vt(Rt(e),Lt(e,he)),children:`Скачать repair .cmd`}),(0,S.jsx)(`button`,{className:`ghost`,onClick:()=>void Ht(Lt(e,he)),children:`Скопировать команду`})]}),(0,S.jsx)(`pre`,{className:`codePreview`,children:Lt(e,he)})]}),(0,S.jsxs)(`section`,{className:`nodePanel`,children:[(0,S.jsx)(`h4`,{children:`Linux repair/update command`}),(0,S.jsx)(`p`,{className:`muted`,children:`Для существующего Ubuntu/Linux-узла эта команда восстанавливает systemd updater без нового join-token, сохраняет local state и делает одноразовую проверку обновления.`}),(0,S.jsxs)(`div`,{className:`stateList compact`,children:[(0,S.jsx)(A,{label:`Когда выполнять`,value:`если host-agent не отчитался, updater stale или Linux-узел не доходит до target version`}),(0,S.jsx)(A,{label:`Control Plane`,value:Jt()}),(0,S.jsx)(A,{label:`Join-token`,value:`не нужен для repair существующего узла`})]}),(0,S.jsxs)(`div`,{className:`actions`,children:[(0,S.jsx)(`button`,{className:`primary`,onClick:()=>Vt(Bt(e),zt(e,he)),children:`Скачать repair .sh`}),(0,S.jsx)(`button`,{className:`ghost`,onClick:()=>void Ht(zt(e,he)),children:`Скопировать команду`})]}),(0,S.jsx)(`pre`,{className:`codePreview`,children:zt(e,he)})]}),(0,S.jsxs)(`section`,{className:`nodePanel`,children:[(0,S.jsx)(`h4`,{children:`Payload последнего отчета`}),(0,S.jsxs)(`div`,{className:`rawDetailsGrid`,children:[(0,S.jsx)(ve,{title:`rap-node-agent update status`,value:de}),(0,S.jsx)(ve,{title:`rap-host-agent update status`,value:fe}),(0,S.jsx)(ve,{title:`Update plan`,value:d})]})]})]}),D===`raw`&&(0,S.jsxs)(`section`,{className:`nodePanel`,children:[(0,S.jsx)(`h4`,{children:`Raw данные узла`}),(0,S.jsxs)(`div`,{className:`rawDetailsGrid`,children:[(0,S.jsx)(ve,{title:`Последний heartbeat metadata`,value:m?.metadata}),(0,S.jsx)(ve,{title:`Heartbeat capabilities`,value:m?.capabilities}),(0,S.jsx)(ve,{title:`Heartbeat service states`,value:m?.service_states}),(0,S.jsx)(ve,{title:`Synthetic mesh config`,value:c}),(0,S.jsx)(ve,{title:`Listener report`,value:g}),(0,S.jsx)(ve,{title:`Endpoint report`,value:v}),(0,S.jsx)(ve,{title:`Peer recovery report`,value:x}),(0,S.jsx)(ve,{title:`Connection intent report`,value:ee}),(0,S.jsx)(ve,{title:`Connection manager report`,value:C}),(0,S.jsx)(ve,{title:`Rendezvous lease report`,value:te}),(0,S.jsx)(ve,{title:`Route decision report`,value:ne}),(0,S.jsx)(ve,{title:`Route generation report`,value:re}),(0,S.jsx)(ve,{title:`Route health report`,value:ie})]})]})]})}function ve({title:e,value:t}){return(0,S.jsxs)(`details`,{className:`rawBlock`,children:[(0,S.jsx)(`summary`,{children:e}),(0,S.jsx)(`pre`,{children:t==null?`н/д`:JSON.stringify(t,null,2)})]})}function ye({runtime:e}){return(0,S.jsxs)(`div`,{className:`runtimeBadges`,children:[(0,S.jsx)(`span`,{className:`pill ${e.agentTone}`,children:e.agentLabel}),(0,S.jsx)(`span`,{className:`pill ${e.clientTone}`,children:e.clientLabel}),(0,S.jsx)(`span`,{className:`pill ${e.outboundTone}`,children:e.outboundLabel}),(0,S.jsx)(`span`,{className:`pill ${e.inboundTone}`,children:e.inboundLabel})]})}function be({node:e,fallback:t,heartbeatsByNode:n,meshLinks:r}){if(!e)return t;let i=ct(e,n[e.id]||[],r);return(0,S.jsxs)(`div`,{className:`nodeEndpointCell`,children:[(0,S.jsx)(`strong`,{children:e.name}),(0,S.jsx)(ye,{runtime:i}),(0,S.jsx)(`small`,{children:i.address})]})}function xe({nodes:e,links:t,syntheticMeshConfigsByNode:n,entryPoints:r,entryPointNodesById:i,egressPools:a,egressPoolNodesById:o,rolesByNode:s,workloadsByNode:c,telemetryByNode:l,labels:u,emptyText:d}){if(e.length===0)return(0,S.jsx)(me,{title:`Нет узлов`,text:`Одобренные node-agent появятся на карте после первого heartbeat.`});let f=Qe(t).filter(e=>e.source_node_id!==e.target_node_id),p=new Map(e.map(e=>[e.id,e])),m=f.map(e=>({link:e,status:Ce(e,f,p)})),h=m.filter(e=>e.status===`reachable`),g=m.filter(e=>e.status===`one_way`),_=m.filter(e=>e.status===`stale`),v=m.filter(e=>e.status!==`reachable`&&e.status!==`one_way`&&e.status!==`stale`),y=Ae(e,n),b=j(e.length,r.length,a.length),x=Ee(e.length),ee=Oe(e,b.height,x),C=new Map(r.map((e,t)=>[e.id,Te(170,t,r.length,150,b.height-100)])),te=new Map(a.map((e,t)=>[e.id,Te(950,t,a.length,150,b.height-100)])),ne=new Set(m.filter(e=>e.status!==`stale`).map(e=>`${e.link.source_node_id}->${e.link.target_node_id}`)),re=new Map(e.map(e=>[e.id,Se(e.id,m)])),ie=y.filter(e=>!ne.has(`${e.source_node_id}->${e.target_node_id}`)),w=r.flatMap(e=>(i[e.id]||[]).filter(e=>e.status!==`disabled`).map(t=>({entryPoint:e,assignment:t}))),ae=a.flatMap(e=>(o[e.id]||[]).filter(e=>e.status!==`disabled`).map(t=>({pool:e,assignment:t}))),oe=w.length+ae.length;return(0,S.jsxs)(`div`,{className:`topologyShell`,children:[(0,S.jsxs)(`svg`,{className:`topologySvg`,viewBox:`0 0 ${b.width} ${b.height}`,role:`img`,"aria-label":`Карта трафика узлов Fabric`,children:[(0,S.jsx)(`defs`,{children:(0,S.jsx)(`marker`,{id:`arrow`,markerHeight:`8`,markerWidth:`8`,orient:`auto`,refX:`7`,refY:`4`,children:(0,S.jsx)(`path`,{d:`M0,0 L8,4 L0,8 Z`,fill:`currentColor`})})}),(0,S.jsx)(`rect`,{x:`36`,y:`58`,width:`268`,height:b.height-100,rx:`24`,className:`topologyZone ingress`}),(0,S.jsx)(`rect`,{x:`330`,y:`58`,width:`460`,height:b.height-100,rx:`24`,className:`topologyZone core`}),(0,S.jsx)(`rect`,{x:`816`,y:`58`,width:`268`,height:b.height-100,rx:`24`,className:`topologyZone egress`}),(0,S.jsx)(`text`,{x:`170`,y:`98`,className:`topologyLayerLabel`,children:u.fabricIngressLayer}),(0,S.jsx)(`text`,{x:`560`,y:`98`,className:`topologyLayerLabel`,children:u.fabricNodeLayer}),(0,S.jsx)(`text`,{x:`950`,y:`98`,className:`topologyLayerLabel`,children:u.fabricEgressLayer}),w.map(({entryPoint:e,assignment:t})=>{let n=C.get(e.id),r=ee.get(t.node_id);return!n||!r?null:(0,S.jsx)(`line`,{x1:n.x+78,y1:n.y,x2:r.x-x-8,y2:r.y,className:`topologyPlacementLink ${t.status===`active`?`good`:`weak`}`,markerEnd:`url(#arrow)`},`entry-${e.id}-${t.node_id}`)}),ae.map(({pool:e,assignment:t})=>{let n=ee.get(t.node_id),r=te.get(e.id);return!n||!r?null:(0,S.jsx)(`line`,{x1:n.x+x+8,y1:n.y,x2:r.x-78,y2:r.y,className:`topologyPlacementLink ${t.status===`active`?`good`:`weak`}`,markerEnd:`url(#arrow)`},`egress-${e.id}-${t.node_id}`)}),ie.map(e=>{let t=ee.get(e.source_node_id),n=ee.get(e.target_node_id);if(!t||!n)return null;let r=ke(t,n,x+8);return(0,S.jsx)(`line`,{x1:r.x1,y1:r.y1,x2:r.x2,y2:r.y2,className:`topologyConfiguredLink`,markerEnd:`url(#arrow)`},e.id)}),m.map(({link:e,status:t})=>{let n=ee.get(e.source_node_id),r=ee.get(e.target_node_id);if(!n||!r)return null;let i=ke(n,r,x+8),a=(i.x1+i.x2)/2,o=(i.y1+i.y2)/2;return(0,S.jsxs)(`g`,{children:[(0,S.jsx)(`line`,{x1:i.x1,y1:i.y1,x2:i.x2,y2:i.y2,className:`topologyLink ${vn(e,t)}`,markerEnd:`url(#arrow)`}),(0,S.jsx)(`text`,{x:a,y:o-8,className:`topologyLinkLabel`,children:nt(e,t)})]},e.id||`${e.source_node_id}-${e.target_node_id}`)}),r.map(e=>{let t=C.get(e.id),n=(i[e.id]||[]).length;return(0,S.jsxs)(`g`,{className:`topologyEndpoint`,children:[(0,S.jsx)(`rect`,{x:t.x-86,y:t.y-36,width:`172`,height:`72`,rx:`20`,className:`topologyEndpointRect ${e.status}`}),(0,S.jsx)(`text`,{x:t.x,y:t.y-8,className:`topologyEndpointName`,children:yn(e.name)}),(0,S.jsxs)(`text`,{x:t.x,y:t.y+15,className:`topologyEndpointMeta`,children:[e.endpoint_type,` · `,n]})]},e.id)}),e.map(t=>{let n=ee.get(t.id),r=De(e.length),i=Fe(s[t.id]||[]),a=re.get(t.id)||`isolated`;return(0,S.jsxs)(`g`,{className:`topologyNode`,children:[(0,S.jsx)(`circle`,{cx:n.x,cy:n.y,r:x,className:`topologyNodeCircle ${t.health_status}`}),(0,S.jsx)(`text`,{x:n.x,y:n.y-r.nameOffset,className:`topologyNodeName`,style:{fontSize:r.name},children:yn(t.name,r.maxChars)}),(0,S.jsxs)(`text`,{x:n.x,y:n.y+r.metaOffset,className:`topologyNodeMeta`,style:{fontSize:r.meta},children:[i.length,` активн. ролей / `,(c[t.id]||[]).length,` серв.`]}),(0,S.jsxs)(`text`,{x:n.x,y:n.y+r.memoryOffset,className:`topologyNodeMeta`,style:{fontSize:r.meta},children:[`mesh: `,V(a)]})]},t.id)}),a.map(e=>{let t=te.get(e.id),n=(o[e.id]||[]).length;return(0,S.jsxs)(`g`,{className:`topologyEndpoint`,children:[(0,S.jsx)(`rect`,{x:t.x-86,y:t.y-36,width:`172`,height:`72`,rx:`20`,className:`topologyEndpointRect ${e.status}`}),(0,S.jsx)(`text`,{x:t.x,y:t.y-8,className:`topologyEndpointName`,children:yn(e.name)}),(0,S.jsxs)(`text`,{x:t.x,y:t.y+15,className:`topologyEndpointMeta`,children:[`egress · `,n]})]},e.id)}),m.length===0&&ie.length===0&&oe===0&&(0,S.jsx)(`text`,{x:b.width/2,y:b.height-34,className:`topologyEmpty`,children:d})]}),(0,S.jsxs)(`div`,{className:`topologyLegend`,children:[(0,S.jsxs)(`span`,{children:[(0,S.jsx)(`i`,{className:`legendLine placement`}),` `,u.placementIntent,`: `,oe]}),(0,S.jsxs)(`span`,{children:[(0,S.jsx)(`i`,{className:`legendLine observed`}),` реальные: `,h.length]}),(0,S.jsxs)(`span`,{children:[(0,S.jsx)(`i`,{className:`legendLine oneWay`}),` one-way: `,g.length]}),(0,S.jsxs)(`span`,{children:[(0,S.jsx)(`i`,{className:`legendLine stale`}),` stale: `,_.length]}),(0,S.jsxs)(`span`,{children:[(0,S.jsx)(`i`,{className:`legendLine problem`}),` проблемы: `,v.length]}),(0,S.jsxs)(`span`,{children:[(0,S.jsx)(`i`,{className:`legendLine configured`}),` configured: `,ie.length]})]}),(0,S.jsxs)(`div`,{className:`serviceTags`,children:[r.map(t=>(0,S.jsxs)(`div`,{className:`serviceTag`,children:[(0,S.jsx)(`strong`,{children:t.name}),(0,S.jsx)(`span`,{children:t.endpoint_type}),(0,S.jsx)(`small`,{children:(i[t.id]||[]).map(t=>P(e,t.node_id)).join(`, `)||u.assignedNodesEmpty})]},t.id)),e.map(e=>(0,S.jsxs)(`div`,{className:`serviceTag`,children:[(0,S.jsx)(`strong`,{children:e.name}),(0,S.jsxs)(`span`,{children:[V(e.health_status),` / mesh `,V(re.get(e.id)||`isolated`)]}),(0,S.jsx)(`small`,{children:Pe(s[e.id]||[])}),(0,S.jsx)(`small`,{children:Le(c[e.id]||[])})]},e.id)),a.map(t=>(0,S.jsxs)(`div`,{className:`serviceTag`,children:[(0,S.jsx)(`strong`,{children:t.name}),(0,S.jsx)(`span`,{children:t.status}),(0,S.jsx)(`small`,{children:(o[t.id]||[]).map(t=>P(e,t.node_id)).join(`, `)||u.assignedNodesEmpty})]},t.id))]})]})}function Se(e,t){let n=t.filter(t=>t.link.source_node_id!==t.link.target_node_id&&(t.link.source_node_id===e||t.link.target_node_id===e));return n.some(e=>e.status===`reachable`||e.status===`one_way`)?`connected`:n.some(e=>e.status!==`stale`)?`degraded`:`isolated`}function Ce(e,t,n){if(we(e,n))return`stale`;if(e.link_status!==`reachable`)return e.link_status===`degraded`||e.link_status===`unreachable`?e.link_status:`unknown`;let r=t.find(t=>t.source_node_id===e.target_node_id&&t.target_node_id===e.source_node_id&&!we(t,n));return!r||r.link_status!==`reachable`?`one_way`:`reachable`}function we(e,t){if(e.link_status===`stale`||e.metadata?.derived_link_stale===!0)return!0;let n=new Date(e.observed_at).getTime();if(!Number.isFinite(n)||Date.now()-n>120*1e3)return!0;if(!t)return!1;let r=t.get(e.source_node_id),i=t.get(e.target_node_id);return r?.health_status!==`healthy`||i?.health_status!==`healthy`}function Te(e,t,n,r,i){return n<=1?{x:e,y:Math.round((r+i)/2)}:{x:e,y:Math.round(r+(i-r)*t/(n-1))}}function j(e,t,n){let r=Math.max(e,t,n,1),i=Math.max(Math.ceil(e/3),r);return{width:1120,height:Math.max(640,220+i*104)}}function Ee(e){return e>48?26:e>24?32:e>12?40:52}function De(e){return e>48?{name:12,meta:9,nameOffset:7,metaOffset:7,memoryOffset:20,maxChars:10}:e>24?{name:14,meta:10,nameOffset:8,metaOffset:9,memoryOffset:24,maxChars:12}:e>12?{name:16,meta:12,nameOffset:10,metaOffset:11,memoryOffset:28,maxChars:14}:{name:21,meta:15,nameOffset:12,metaOffset:10,memoryOffset:31,maxChars:18}}function Oe(e,t,n){let r=e.length>24?4:e.length>8?3:e.length>3?2:1,i=Math.max(1,Math.ceil(e.length/r)),a=r===1?0:300/(r-1),o=t-96,s=i===1?0:(o-142)/(i-1);return new Map(e.map((e,t)=>{let n=t%r,c=Math.floor(t/r);return[e.id,{x:Math.round(r===1?560:410+a*n),y:Math.round(i===1?(142+o)/2:142+s*c)}]}))}function ke(e,t,n){let r=t.x-e.x,i=t.y-e.y,a=Math.max(Math.sqrt(r*r+i*i),1),o=r/a*n,s=i/a*n;return{x1:e.x+o,y1:e.y+s,x2:t.x-o,y2:t.y-s}}function Ae(e,t){let n=new Set(e.map(e=>e.id)),r=new Map;for(let[e,i]of Object.entries(t))for(let t of i.routes||[]){let i=t.hops||[];for(let a=0;a${s}`,{id:`configured-${e}-${t.route_id}-${a}`,source_node_id:o,target_node_id:s})}}return[...r.values()]}function je({children:e}){return(0,S.jsx)(`div`,{className:`formGrid`,children:e})}function M({columns:e,rows:t}){return t.length===0?(0,S.jsx)(me,{title:`Нет данных`,text:`В текущей области пока нечего показать.`}):(0,S.jsx)(`div`,{className:`tableWrap`,children:(0,S.jsxs)(`table`,{children:[(0,S.jsx)(`thead`,{children:(0,S.jsx)(`tr`,{children:e.map(e=>(0,S.jsx)(`th`,{children:e},e))})}),(0,S.jsx)(`tbody`,{children:t.map((e,t)=>(0,S.jsx)(`tr`,{children:e.map((e,n)=>(0,S.jsx)(`td`,{children:e},`${t}-${n}`))},t))})]})})}function Me(e,t){let n=JSON.parse(e||`{}`);if(!n||Array.isArray(n)||typeof n!=`object`)throw Error(`${t}: требуется JSON object.`);return n}function Ne(e,t){let n=JSON.parse(e||`[]`);if(!Array.isArray(n))throw Error(`${t}: требуется JSON array.`);return n}function Pe(e){let t=Fe(e);return t.length===0?`активные роли не назначены`:t.map(e=>`${Ie(e.role)}${e.organization_id?` @ ${R(e.organization_id)}`:``}`).join(`, `)}function Fe(e){return e.filter(e=>e.status===`active`)}function Ie(e){let t=w[e];return t?`${t} (${e})`:e}function Le(e){return e.length===0?`нет сервисов`:e.map(e=>`${e.service_type}:${e.reported_state}`).join(`, `)}function Re(e,t,n){let r=n.find(e=>e.product===`rap-node-agent`&&e.channel===`stable`&&e.status===`active`)||n.find(e=>e.product===`rap-node-agent`&&e.status===`active`),i=e.reported_version||``,a=t?.target_version||r?.version||``;return e.version_state&&e.version_state!==`unknown`?{status:e.version_state,targetLabel:a?`target ${a}`:`policy target unknown`}:i?t?.action===`update`?{status:`outdated`,targetLabel:`target ${t.target_version||a}`}:a&&i!==a?{status:`outdated`,targetLabel:`latest ${a}`}:a&&i===a?{status:`current`,targetLabel:`latest ${a}`}:{status:t?.reason===`no_update_policy`?`no_policy`:`unknown`,targetLabel:t?.reason||`release policy unknown`}:{status:`unknown`,targetLabel:a?`target ${a}`:`target unknown`}}function ze(e,t){return e.find(e=>e.product===t)}function Be(e,t){return e?`${e.product}: ${e.phase}/${e.status}`:t?`${t.action}: ${t.reason}`:`нет отчета`}function Ve(e){if(!e)return`нет отчета`;let t=e.target_version?` -> ${e.target_version}`:``,n=e.error_message?`, ошибка: ${e.error_message}`:``;return`${e.current_version||`н/д`}${t}, ${e.phase}/${e.status}, ${z(e.observed_at)}${n}`}function He(e){return e?`push ${e.pushed||0} / pop ${e.popped||0} / q ${e.queue_depth||0} / drop ${e.dropped||0}`:`нет данных`}function Ue(e){if(!e)return`warn`;let t=`${e.phase}:${e.status}`.toLowerCase();return t.includes(`error`)||t.includes(`failed`)||t.includes(`rollback`)?`bad`:t.includes(`success`)||t.includes(`updated`)||t.includes(`noop`)||t.includes(`already_current`)?`good`:t.includes(`download`)||t.includes(`replace`)||t.includes(`plan`)||t.includes(`apply`)?`warn`:``}function We(e){let t=e.toLowerCase();return t===`healthy`?`good`:t===`fenced`?`bad`:t===`degraded`||t===`operator_retry_cooldown`?`warn`:``}function Ge(e){let t=e.toLowerCase();return t===`healthy`?`good`:t===`recovered`||t===`cooldown`||t===`degraded`?`warn`:t===`fenced`||t===`demoted`?`bad`:``}function Ke(e){if(e.status===`disabled`||e.lifecycle_status===`disabled`)return`disabled`;if(e.is_expired||e.lifecycle_status===`expired`)return`expired`;let t=Date.parse(e.policy_expires_at||``);return Number.isFinite(t)&&t<=Date.now()?`expired`:e.lifecycle_status||e.status||`active`}function qe(e){let t=Ke(e);return t===`active`?`good`:t===`expired`?`warn`:t===`disabled`?``:`warn`}function Je(e){let t=typeof e.node_id==`string`?e.node_id:``;if(t)return R(t);let n=Array.isArray(e.node_ids)?e.node_ids.filter(e=>typeof e==`string`):[];return n.length>0?n.map(R).join(`, `):`selector`}function Ye(e){let t=ze(e,`rap-node-agent`),n=ze(e,`rap-host-agent`);if(!t&&!n)return{label:`updater: нет отчета`,detail:`repair/update task не отчитался`,tone:`bad`};let r=[t,n].some(e=>e&&Xe(e)),i=!n,a=n?.phase===`apply`&&n?.status===`staged`,o=[t,n].some(e=>e&&Ue(e)===`bad`),s=t?`${t.current_version||`?`}->${t.target_version||`?`}`:`node ?`,c=n?`${n.current_version||`?`}->${n.target_version||`?`}`:`host ?`,l=z((n||t)?.observed_at);return o?{label:`updater: ошибка`,detail:`${s}; ${c}; ${l}`,tone:`bad`}:i?{label:`repair updater`,detail:`host-agent не отчитался; ${s}; ${l}`,tone:`warn`}:a?{label:`host-agent staged`,detail:`${c}; нужен следующий запуск updater`,tone:`warn`}:r?{label:`updater: stale`,detail:`${s}; ${c}; ${l}`,tone:`warn`}:{label:`updater: ok`,detail:`${s}; ${c}; ${l}`,tone:`good`}}function Xe(e){let t=new Date(e.observed_at).getTime();return!Number.isFinite(t)||Date.now()-t>900*1e3}function Ze(e){let t=typeof e.scope?.node_name==`string`?e.scope.node_name:``,n=typeof e.scope?.purpose==`string`?e.scope.purpose:``;return t||n||R(e.id)}function Qe(e){let t=new Map;for(let n of e){let e=`${n.source_node_id}->${n.target_node_id}:${$e(n)}`,r=t.get(e);(!r||new Date(n.observed_at).getTime()>new Date(r.observed_at).getTime())&&t.set(e,n)}return[...t.values()]}function $e(e){let t=rt(e,`observation_type`)||`default`;return t===`synthetic_route_health`?`${t}:${rt(e,`route_id`)||e.id}`:t===`peer_connection_manager`?`${t}:${rt(e,`transport_mode`)}:${rt(e,`relay_node_id`)}`:t}function et(e){let t=rt(e,`observation_type`);if(t===`synthetic_route_health`){let t=e.metadata?.route_path_drift_detected===!0?`drift`:`ok`;return`route-health ${e.metadata?.route_path_decision_applied===!0?`decision`:`route`} ${t}`}if(t===`peer_connection_manager`){let t=rt(e,`transport_mode`)||`manager`,n=rt(e,`connection_state`);return n?`${t} ${n}`:t}return t||`link`}function tt(e,t){let n=rt(e,`route_id`),r=rt(e,`route_path_decision_selected_relay_id`)||rt(e,`relay_node_id`),i=it(e,`expected_effective_hops`),a=it(e,`observed_ack_path`),o=i.length>0?i:a,s=[];return n&&s.push(R(n)),r&&s.push(`via ${R(r)}`),o.length>0&&s.push(o.map(e=>yn(P(t,e))).join(` > `)),s.length>0?s.join(` / `):`н/д`}function nt(e,t=e.link_status===`reachable`?`reachable`:`unknown`){return t===`stale`?`stale`:t===`one_way`?`one-way`:rt(e,`observation_type`)===`synthetic_route_health`?e.metadata?.route_path_drift_detected===!0?`drift`:`route`:e.latency_ms==null?`связь`:`${e.latency_ms}мс`}function rt(e,t){let n=e.metadata?.[t];return typeof n==`string`?n:``}function it(e,t){let n=e.metadata?.[t];return Array.isArray(n)?n.filter(e=>typeof e==`string`):[]}function at(e){return e&&typeof e==`object`&&!Array.isArray(e)?e:void 0}function ot(e){return Array.isArray(e)?e.map(e=>at(e)).filter(e=>!!e):[]}function N(e,t,n=``){let r=e?.[t];return typeof r==`string`?r:typeof r==`number`||typeof r==`boolean`?String(r):n}function st(e,t){for(let n of t){let t=N(e,n,``);if(t)return t}return``}function ct(e,t,n){let r=t[0],i=r?.metadata||{},a=at(i.mesh_listener_report),o=at(i.mesh_endpoint_report),s=at(i.mesh_outbound_session_report),c=at(i.mesh_peer_connection_manager_report),l=at(i.mesh_peer_recovery_report),u=ot(o?.endpoint_candidates)[0],d=st(o,[`peer_endpoint`,`advertised_endpoint`,`endpoint`])||N(u,`address`,``)||N(a,`effective_listen_addr`,``)||`адрес не прислан`;if(!r&&!e.last_seen_at)return{agentLabel:`agent: no heartbeat`,agentTone:`bad`,clientLabel:`client: unknown`,clientTone:`warn`,outboundLabel:`outbound: no heartbeat`,outboundTone:`bad`,inboundLabel:`inbound: unknown`,inboundTone:`warn`,address:d,detail:`Узел создан/одобрен, но node-agent еще ни разу не прислал heartbeat.`};let f=lt(c,`peer_connection_ready`)||lt(l,`peer_connection_ready`)||Qe(n).filter(t=>(t.source_node_id===e.id||t.target_node_id===e.id)&&t.link_status===`reachable`).length,p=lt(c,`peer_connection_total`)||lt(l,`peer_connection_total`)||Qe(n).filter(t=>t.source_node_id===e.id||t.target_node_id===e.id).length,m=lt(c,`failed`),h=N(a,`status`,``),g=a?.port_conflict===!0,_=a?.one_way_connectivity===!0||N(o,`connectivity_mode`,``)===`outbound_only`||lt(c,`peer_connection_relay_ready`)>0,v=`inbound: no report`,y=`warn`;h===`listening`||h===`auto_rebound`?(v=h===`auto_rebound`?`inbound: auto port`:`inbound: listening`,y=`good`):h===`listen_failed`?(v=g?`inbound: port busy`:`inbound: failed`,y=`bad`):h===`disabled`?(v=`inbound: disabled`,y=_?`warn`:`bad`):o&&(v=`inbound: advertised`,y=`good`);let b=`client: no peers`,x=`warn`;f>0?(b=`client: ready ${f}/${Math.max(p,f)}`,x=`good`):(m>0||p>0)&&(b=`client: backoff ${f}/${Math.max(p,m)}`,x=`bad`);let ee=N(s,`status`,``),S=s?.usable_for_inbound_control===!0,C=lt(s,`peer_connection_relay_ready`),te=lt(s,`rendezvous_lease_count`),ne=`outbound: no report`,re=`warn`;ee===`ready`?(ne=S?`outbound: ready reverse`:`outbound: ready`,re=`good`):ee===`backoff`||ee===`failed`?(ne=`outbound: ${ee}`,re=`bad`):(_||C>0||te>0)&&(ne=`outbound: inferred`,re=`warn`);let ie=e.health_status===`healthy`?`good`:e.health_status===`unknown`?`warn`:`bad`;return{agentLabel:r?`agent: heartbeat`:`agent: stale`,agentTone:ie,clientLabel:_&&f>0?`${b} one-way`:b,clientTone:x,outboundLabel:ne,outboundTone:re,inboundLabel:v,inboundTone:y,address:d,detail:N(a,`failure_error`,N(a,`failure_reason`,``))}}function lt(e,t,n=0){let r=e?.[t];return typeof r==`number`&&Number.isFinite(r)?r:n}function ut(e){if(e==null)return`н/д`;let t=JSON.stringify(e);return t.length>140?`${t.slice(0,137)}...`:t}function dt(e){let t=ot(e.candidate_results);return t.length===0?`н/д`:t.slice(0,4).map(e=>{let t=N(e,`candidate_id`,`candidate`),n=N(e,`link_status`,`unknown`),r=N(e,`latency_ms`,``);return r&&r!==`0`?`${t}:${n}:${r}мс`:`${t}:${n}`}).join(`, `)}function ft(e){return Object.values(e.peer_endpoint_candidates||{}).reduce((e,t)=>e+t.length,0)}function pt(e){let t=e?.rendezvous_relay_policy;if(!t)return`none`;let n=[`stale${t.stale_relay_count}`,`wd${t.withdrawn_lease_count}`,`repl${t.replacement_lease_count}`];t.scoring_mode.includes(`synthetic_route_health_feedback`)&&n.push(`rh feedback`);let r=t.decisions?.find(e=>e.selected_relay_id);return r?.selected_relay_id&&n.push(`via ${R(r.selected_relay_id)}`),n.join(` `)}function mt(e){let t=e?.route_path_decisions;if(!t)return`none`;let n=[`path${t.decision_count}`,`repl${t.replacement_decision_count}`];(t.degraded_decision_count||0)>0&&n.push(`degr${t.degraded_decision_count}`);let r=t.decisions?.find(e=>e.selected_relay_id||e.next_hop_id);return r?.selected_relay_id?n.push(`via ${R(r.selected_relay_id)}`):r?.next_hop_id&&n.push(`next ${R(r.next_hop_id)}`),n.join(` `)}function P(e,t){return e.find(e=>e.id===t)?.name||R(t)}function ht(e,t){let n=new Map(t.map(e=>[e.id,e])),r=[e.name],i=e.parent_group_id,a=new Set([e.id]);for(;i&&!a.has(i);){a.add(i);let e=n.get(i);if(!e)break;r.unshift(e.name),i=e.parent_group_id}return r.join(` / `)}function gt(e,t){let n=t.find(t=>t.id===e);return n?ht(n,t):e}function _t(e,t){let n=[],r=new Map;for(let e of t){let t=e.parent_group_id||``;r.set(t,[...r.get(t)||[],e])}let i=e=>{for(let t of r.get(e)||[])n.push(t.id),i(t.id)};return i(e),n}function vt(e,t,n,r,i){let a=[],o=new Map,s=[],c=[];for(let t of e){let e=t.memberships.find(e=>e.cluster.id===n);if(!e){c.push(t);continue}let r=e.node.node_group_id;if(!r){s.push(t);continue}o.set(r,[...o.get(r)||[],t])}let l=new Map;for(let e of t){let t=e.parent_group_id||``;l.set(t,[...l.get(t)||[],e])}for(let e of l.values())e.sort((e,t)=>e.sort_order-t.sort_order||e.name.localeCompare(t.name));let u=new Map,d=e=>{let t=u.get(e.id);if(t!=null)return t;let n=o.get(e.id)?.length||0;for(let t of l.get(e.id)||[])n+=d(t);return u.set(e.id,n),n},f=(e,t)=>{let n=[...o.get(e.id)||[]].sort((e,t)=>e.node.name.localeCompare(t.node.name)),r=l.get(e.id)||[],s=`group-${e.id}`,c=d(e);if(a.push({kind:`group`,key:s,label:e.name,depth:t,count:c,groupId:e.id}),!i.has(s)){for(let r of n)a.push({kind:`node`,key:`node-${e.id}-${r.node.id}`,entry:r,depth:t+1});for(let e of r)f(e,t+1)}return c};for(let e of l.get(``)||[])f(e,0);if(s.length>0){let e=`group-ungrouped`;if(a.push({kind:`group`,key:e,label:r.ungroupedNodes,depth:0,count:s.length}),!i.has(e))for(let e of s.sort((e,t)=>e.node.name.localeCompare(t.node.name)))a.push({kind:`node`,key:`node-ungrouped-${e.node.id}`,entry:e,depth:1})}if(c.length>0){let e=`group-outside-active-cluster`;if(a.push({kind:`group`,key:e,label:r.notMemberOfActiveCluster,depth:0,count:c.length}),!i.has(e))for(let e of c.sort((e,t)=>e.node.name.localeCompare(t.node.name)))a.push({kind:`node`,key:`node-outside-${e.node.id}`,entry:e,depth:1})}return a}function yt(e,t){return Object.entries(t).filter(([,t])=>t.some(t=>t.role===e&&t.status===`active`)).map(([e])=>e)}function bt(e){let t={roles:e.roles,node_name:e.nodeName.trim()||null,node_group_id:e.nodeGroupId||null,ownership_type:e.ownershipType,purpose:e.purpose.trim()||null,approval:{mode:`manual`,auto_approve:!1,role_assignment:`manual_after_approval`},source:`platform_owner_console`};if(e.installMode===`docker`){let n=(e.controlPlaneEndpoint||wt()).trim().replace(/\/$/,``);t.install_profile=`docker`,t.backend_url=n,t.control_plane_endpoints=[n],t.image=e.dockerImage||`rap-node-agent:latest`,e.dockerContainerName.trim()&&(t.container_name=e.dockerContainerName.trim()),t.artifact_endpoints=Et(e.artifactEndpoints||Tt()),e.dockerImageArtifactSHA256.trim()&&(t.docker_image_artifact_sha256=e.dockerImageArtifactSHA256.trim()),t.network=e.dockerNetwork||`host`,t.restart_policy=`unless-stopped`,t.pull_image=!!e.pullImage,t.replace=e.replace!==!1,t.mesh_synthetic_runtime_enabled=e.syntheticRuntime!==!1,t.mesh_production_forwarding_enabled=!1,t.mesh_listen_addr=e.meshListenAddr||`:19131`,t.mesh_listen_port_mode=e.meshListenPortMode||`auto`,t.mesh_listen_auto_port_start=e.meshListenAutoPortStart||19131,t.mesh_listen_auto_port_end=e.meshListenAutoPortEnd||19231,e.meshAdvertiseEndpoint?.trim()&&(t.mesh_advertise_endpoint=e.meshAdvertiseEndpoint.trim().replace(/\/$/,``)),t.mesh_advertise_transport=e.meshAdvertiseTransport||`direct_http`,t.mesh_connectivity_mode=e.meshConnectivityMode||`private_lan`,t.mesh_nat_type=e.meshNATType||`unknown`,t.mesh_region=e.meshRegion||null}if(e.installMode===`windows_service`){let n=(e.controlPlaneEndpoint||wt()).trim().replace(/\/$/,``);t.install_profile=`windows_service`,t.backend_url=n,t.control_plane_endpoints=[n],t.artifact_endpoints=Et(e.artifactEndpoints||Tt()),t.startup_mode=e.windowsStartupMode||`auto`,e.windowsInstallDir.trim()&&(t.install_dir=e.windowsInstallDir.trim()),e.windowsNodeAgentSHA256.trim()&&(t.node_agent_artifact_sha256=e.windowsNodeAgentSHA256.trim()),t.mesh_synthetic_runtime_enabled=e.syntheticRuntime!==!1,t.mesh_production_forwarding_enabled=!1,t.mesh_listen_addr=e.meshListenAddr||`:19131`,t.mesh_listen_port_mode=e.meshListenPortMode||`auto`,t.mesh_listen_auto_port_start=e.meshListenAutoPortStart||19131,t.mesh_listen_auto_port_end=e.meshListenAutoPortEnd||19231,e.meshAdvertiseEndpoint?.trim()&&(t.mesh_advertise_endpoint=e.meshAdvertiseEndpoint.trim().replace(/\/$/,``)),t.mesh_advertise_transport=e.meshAdvertiseTransport||`direct_http`,t.mesh_connectivity_mode=e.meshConnectivityMode||`outbound_only`,t.mesh_nat_type=e.meshNATType||`unknown`,t.mesh_region=e.meshRegion||`windows`}if(e.installMode===`linux_binary`){let n=(e.controlPlaneEndpoint||wt()).trim().replace(/\/$/,``);t.install_profile=`linux_binary`,t.backend_url=n,t.control_plane_endpoints=[n],t.artifact_endpoints=Et(e.artifactEndpoints||Tt()),t.startup_mode=`systemd`,e.linuxInstallDir.trim()&&(t.install_dir=e.linuxInstallDir.trim()),e.linuxNodeAgentSHA256.trim()&&(t.node_agent_artifact_sha256=e.linuxNodeAgentSHA256.trim()),t.replace=e.replace!==!1,t.mesh_synthetic_runtime_enabled=e.syntheticRuntime!==!1,t.mesh_production_forwarding_enabled=!1,t.mesh_listen_addr=e.meshListenAddr||`:19131`,t.mesh_listen_port_mode=e.meshListenPortMode||`auto`,t.mesh_listen_auto_port_start=e.meshListenAutoPortStart||19131,t.mesh_listen_auto_port_end=e.meshListenAutoPortEnd||19231,e.meshAdvertiseEndpoint?.trim()&&(t.mesh_advertise_endpoint=e.meshAdvertiseEndpoint.trim().replace(/\/$/,``)),t.mesh_advertise_transport=e.meshAdvertiseTransport||`direct_http`,t.mesh_connectivity_mode=e.meshConnectivityMode||`outbound_only`,t.mesh_nat_type=e.meshNATType||`unknown`,t.mesh_region=e.meshRegion||`linux`}return t}function xt(e,t){let n=St(e.roles),r=St(e.artifact_endpoints).join(`, `);return{...t,roles:n.length>0?n:t.roles,nodeName:N(e,`node_name`,``)||t.nodeName,nodeGroupId:N(e,`node_group_id`,``)||t.nodeGroupId,ownershipType:N(e,`ownership_type`,t.ownershipType),purpose:N(e,`purpose`,``)||t.purpose,installMode:N(e,`install_profile`,t.installMode),dockerImage:N(e,`image`,t.dockerImage),dockerContainerName:N(e,`container_name`,``)||t.dockerContainerName,dockerNetwork:N(e,`network`,t.dockerNetwork),windowsStartupMode:N(e,`startup_mode`,t.windowsStartupMode),windowsInstallDir:N(e,`install_dir`,``)||t.windowsInstallDir,windowsNodeAgentSHA256:N(e,`node_agent_artifact_sha256`,``)||t.windowsNodeAgentSHA256,linuxInstallDir:N(e,`install_dir`,``)||t.linuxInstallDir,linuxNodeAgentSHA256:N(e,`node_agent_artifact_sha256`,``)||t.linuxNodeAgentSHA256,meshListenAddr:N(e,`mesh_listen_addr`,t.meshListenAddr),meshListenPortMode:N(e,`mesh_listen_port_mode`,t.meshListenPortMode),meshListenAutoPortStart:lt(e,`mesh_listen_auto_port_start`,t.meshListenAutoPortStart),meshListenAutoPortEnd:lt(e,`mesh_listen_auto_port_end`,t.meshListenAutoPortEnd),meshAdvertiseEndpoint:N(e,`mesh_advertise_endpoint`,``)||t.meshAdvertiseEndpoint,meshAdvertiseTransport:N(e,`mesh_advertise_transport`,t.meshAdvertiseTransport),meshConnectivityMode:N(e,`mesh_connectivity_mode`,t.meshConnectivityMode),meshNATType:N(e,`mesh_nat_type`,t.meshNATType),meshRegion:N(e,`mesh_region`,``)||t.meshRegion,controlPlaneEndpoint:St(e.control_plane_endpoints)[0]||N(e,`backend_url`,``)||t.controlPlaneEndpoint,artifactEndpoints:r||t.artifactEndpoints,dockerImageArtifactSHA256:N(e,`docker_image_artifact_sha256`,``)||t.dockerImageArtifactSHA256,pullImage:Ct(e,`pull_image`,t.pullImage),replace:Ct(e,`replace`,t.replace),syntheticRuntime:Ct(e,`mesh_synthetic_runtime_enabled`,t.syntheticRuntime)}}function St(e){return Array.isArray(e)?e.filter(e=>typeof e==`string`).map(e=>e.trim()).filter(Boolean):[]}function Ct(e,t,n){let r=e[t];return typeof r==`boolean`?r:n}function wt(){return typeof window>`u`||!window.location?.origin?`http://:18080/api/v1`:`${window.location.origin.replace(/\/$/,``)}/api/v1`}function Tt(){return typeof window>`u`||!window.location?.origin?`http://:18080/downloads`:`${window.location.origin.replace(/\/$/,``)}/downloads`}function Et(e){return e.split(`,`).map(e=>e.trim().replace(/\/$/,``)).filter(Boolean)}function Dt(e){return Et(e.artifactEndpoints||Tt()).map(e=>`${e}/rap-node-agent-dev-enrollment-bootstrap-smoke.tar`)}function Ot(e){return e.meshConnectivityMode===`outbound_only`?`outbound_only`:e.meshConnectivityMode===`private_lan`?`private_lan`:e.meshNATType!==`none`&&e.meshAdvertiseEndpoint.trim()?`nat_forward`:`direct`}function kt(e,t){let n={...e};return t===`private_lan`?(n.meshConnectivityMode=`private_lan`,n.meshNATType=`none`):t===`direct`?(n.meshConnectivityMode=`direct`,n.meshNATType=`none`):t===`nat_forward`?(n.meshConnectivityMode=`direct`,n.meshNATType=`port_restricted`):(n.meshConnectivityMode=`outbound_only`,n.meshNATType=`symmetric`,n.meshAdvertiseEndpoint=``),n}function At(e,t){return e.nodeName.trim()?e.nodeName.trim():`${Zt(t?.slug||t?.name||`rap-node`)}-node-1`}function jt(e,t){return e.dockerContainerName.trim()?e.dockerContainerName.trim():`rap-node-agent-${Zt(At(e,t))}`}function Mt(e,t,n=le){let r=t?.id||e.cluster_id,i=At(n,t),a=jt(n,t),o=Zt(i),s=[`rap-host-agent install`,`--backend-url ${I(qt(n))}`,`--cluster-id ${I(r)}`,`--join-token ${I(e.token)}`,`--node-name ${I(i)}`,`--image ${I(n.dockerImage||`rap-node-agent:latest`)}`,`--container-name ${I(a)}`,`--state-dir ${I(`/var/lib/rap/nodes/${o}`)}`,`--network host`,`--replace`];for(let e of Dt(n))s.push(`--image-artifact-url ${I(e)}`);return n.dockerImageArtifactSHA256.trim()&&s.push(`--image-artifact-sha256 ${I(n.dockerImageArtifactSHA256.trim())}`),s.join(` \\ + `)}function Nt(e,t,n=le){let r=t?.id||e.cluster_id,i=At(n,t),a=[`sudo "$rap_host_agent" install`,`--profile-url ${I(qt(n))}`,`--cluster-id ${I(r)}`,`--install-token ${I(e.token)}`,`--node-name ${I(i)}`].join(` \\ + `);return[`rap_host_agent="$(mktemp /tmp/rap-host-agent.XXXXXX)"`,`curl -fL --retry 3 --retry-delay 1 ${I(Yt(n))} -o "$rap_host_agent"`,`chmod +x "$rap_host_agent"`,a].join(` && \\ + `)}function Pt(e,t,n=le){let r=t?.id||e.cluster_id,i=At(n,t),a=[`sudo "$rap_host_agent" install-linux`,`--profile-url ${I(qt(n))}`,`--cluster-id ${I(r)}`,`--install-token ${I(e.token)}`,`--node-name ${I(i)}`].join(` \\ + `);return[`rap_host_agent="$(mktemp /tmp/rap-host-agent.XXXXXX)"`,`curl -fL --retry 3 --retry-delay 1 ${I(Yt(n))} -o "$rap_host_agent"`,`chmod +x "$rap_host_agent"`,a].join(` && \\ + `)}function Ft(e,t,n=le){let r=t?.id||e.cluster_id,i=At(n,t),a=qt(n);return[`$rapHostAgent = Join-Path $env:TEMP "rap-host-agent.exe"`,`Invoke-WebRequest -UseBasicParsing ${Qt(Xt(n))} -OutFile $rapHostAgent`,`& $rapHostAgent install-windows --profile-url ${Qt(a)} --cluster-id ${Qt(r)} --install-token ${Qt(e.token)} --node-name ${Qt(i)} --startup-mode ${Qt(n.windowsStartupMode||`auto`)}`].join(`\r +`)}function It(e,t,n=le){let r=t?.id||e.cluster_id,i=At(n,t),a=qt(n),o=Xt(n),s=n.windowsStartupMode||`auto`;return[`powershell -NoProfile -ExecutionPolicy Bypass -Command "Invoke-WebRequest -UseBasicParsing '${o}' -OutFile $env:TEMP\\rap-host-agent.exe"`,`%TEMP%\\rap-host-agent.exe install-windows --profile-url "${a}" --cluster-id "${r}" --install-token "${e.token}" --node-name "${i}" --startup-mode "${s}"`].join(`\r +`)}function Lt(e,t){let n=Jt(),r=n.replace(/\/api\/v1$/i,``).replace(/\/api$/i,``).replace(/\/$/,``),i=e.name||e.node_key||e.id,a=Wt(i),o=`%ProgramFiles%\\RAP\\${a}`,s=`%ProgramData%\\RAP\\nodes\\${a}`,c=`RAP Node Agent ${a}`,l=`RAP Host Agent Updater ${a}`,u=`${o}\\rap-host-agent.exe`,d=`${u}.next`;return[`@echo off`,`echo === RAP Windows updater repair: ${$t(i)} ===`,`echo Node ID: ${e.id}`,`echo Control Plane: ${n}`,`echo.`,`echo === Before repair: scheduled tasks ===`,`schtasks /Query /TN "${c}" /V /FO LIST`,`schtasks /Query /TN "${l}" /V /FO LIST`,`echo.`,`echo === Before repair: binaries ===`,`dir "${o}\\rap-*.exe*"`,`echo.`,`powershell -NoProfile -ExecutionPolicy Bypass -Command "Invoke-WebRequest -UseBasicParsing '${r}/downloads/rap-host-agent-windows-amd64.exe' -OutFile $env:TEMP\\rap-host-agent.exe"`,`%TEMP%\\rap-host-agent.exe install-windows --backend-url "${n}" --cluster-id "${t||``}" --node-id "${e.id}" --node-name "${$t(i)}" --replace --startup-mode "auto" --auto-update-current-version "0.0.0" --auto-update-initial-delay-seconds 1`,`"${u}" update-loop --backend-url "${n}" --cluster-id "${t||``}" --node-id "${e.id}" --state-dir "${s}" --current-version "0.0.0" --os windows --arch amd64 --install-type windows_service --binary-path "${o}\\rap-node-agent.exe" --windows-task-name "${c}" --health-timeout-seconds 30 --interval-seconds 0 --initial-delay-seconds 0 --max-runs 1 --host-agent-update-status-enabled --host-agent-current-version "0.0.0" --host-agent-binary-path "${u}"`,`echo.`,`echo === Applying staged host-agent if present ===`,`if exist "${d}" copy /Y "${d}" "${u}"`,`if exist "${d}" del /F /Q "${d}"`,`schtasks /End /TN "${l}"`,`schtasks /Run /TN "${l}"`,`echo.`,`echo === After repair: binaries ===`,`dir "${o}\\rap-*.exe*"`,`echo.`,`echo === After repair: updater task ===`,`schtasks /Query /TN "${l}" /V /FO LIST`,`echo.`,`echo Repair command finished. Check the admin panel for rap-node-agent and rap-host-agent plan/noop reports.`].join(`\r +`)}function Rt(e){return`rap-repair-updater-${Ut(e.name||e.node_key||e.id||`node`)}.cmd`}function zt(e,t){let n=Jt(),r=n.replace(/\/api\/v1$/i,``).replace(/\/api$/i,``).replace(/\/$/,``),i=e.name||e.node_key||e.id,a=Gt(i),o=`/opt/rap/${a}`,s=`/var/lib/rap/nodes/${a}`,c=`rap-node-agent-${a}.service`,l=`rap-host-agent-updater-${a}.service`,u=`${o}/rap-host-agent`;return[`#!/usr/bin/env bash`,`set -euo pipefail`,`echo "=== RAP Linux updater repair: ${Kt(i)} ==="`,`echo "Node ID: ${e.id}"`,`echo "Control Plane: ${n}"`,`echo`,`echo "=== Before repair: systemd units ==="`,`systemctl status ${I(c)} --no-pager || true`,`systemctl status ${I(l)} --no-pager || true`,`echo`,`echo "=== Before repair: binaries ==="`,`ls -la ${I(o)} || true`,`echo`,`rap_host_agent="$(mktemp /tmp/rap-host-agent.XXXXXX)"`,`curl -fL --retry 3 --retry-delay 1 ${I(`${r}/downloads/rap-host-agent-linux-amd64`)} -o "$rap_host_agent"`,`chmod +x "$rap_host_agent"`,`sudo "$rap_host_agent" install-linux --backend-url ${I(n)} --cluster-id ${I(t||``)} --node-id ${I(e.id)} --node-name ${I(i)} --replace --startup-mode systemd --auto-update-current-version 0.0.0 --auto-update-initial-delay-seconds 1`,`sudo ${I(u)} update-loop --backend-url ${I(n)} --cluster-id ${I(t||``)} --node-id ${I(e.id)} --state-dir ${I(s)} --current-version 0.0.0 --os linux --arch amd64 --install-type linux_binary --binary-path ${I(`${o}/rap-node-agent`)} --systemd-unit ${I(c)} --health-timeout-seconds 30 --interval-seconds 0 --initial-delay-seconds 0 --max-runs 1 --host-agent-update-status-enabled --host-agent-current-version 0.0.0 --host-agent-binary-path ${I(u)}`,`sudo systemctl daemon-reload`,`sudo systemctl restart ${I(l)}`,`echo`,`echo "=== After repair: systemd updater ==="`,`systemctl status ${I(l)} --no-pager || true`,`echo "Repair command finished. Check the admin panel for rap-node-agent and rap-host-agent plan/noop reports."`].join(` +`)}function Bt(e){return`rap-repair-updater-${Ut(e.name||e.node_key||e.id||`node`)}.sh`}function Vt(e,t){if(typeof document>`u`)return;let n=new Blob([t.endsWith(`\r +`)?t:`${t}\r\n`],{type:`text/plain;charset=utf-8`}),r=URL.createObjectURL(n),i=document.createElement(`a`);i.href=r,i.download=e,document.body.appendChild(i),i.click(),i.remove(),URL.revokeObjectURL(r)}async function Ht(e){await navigator.clipboard.writeText(e)}function Ut(e){return e.trim().replace(/[\\/:*?"<>|]+/g,`-`).replace(/\s+/g,`-`).replace(/-+/g,`-`).replace(/^-|-$/g,``).slice(0,80)||`node`}function Wt(e){return e.trim().replace(/[\\/:*?"<>|]+/g,`-`).replace(/\s+/g,`-`).replace(/-+/g,`-`).replace(/^-|-$/g,``)||`node`}function Gt(e){return Wt(e).slice(0,48)||`node`}function Kt(e){return e.replace(/\\/g,`\\\\`).replace(/"/g,`\\"`).replace(/\$/g,`\\$`).replace(/`/g,"\\`")}function qt(e=le){return(e.controlPlaneEndpoint||wt()).trim().replace(/\/$/,``)}function Jt(){let e=typeof window>`u`?``:window.location?.origin||``;return/^(http:\/\/)?(192\.168\.200\.61|docker-test\.cin\.su)(:18080)?$/i.test(e.replace(/\/$/,``))?`https://vpn.cin.su/api/v1`:`${e.replace(/\/$/,``)}/api/v1`}function F(e=le){let t=Et(e.artifactEndpoints)[0];return t?t.replace(/\/downloads$/i,``).replace(/\/$/,``):qt(e).replace(/\/api\/v1$/i,``).replace(/\/api$/i,``).replace(/\/$/,``)}function Yt(e=le){return`${typeof window>`u`&&!e.controlPlaneEndpoint?`http://:18080`:F(e)}/downloads/rap-host-agent-linux-amd64`}function Xt(e=le){return`${typeof window>`u`&&!e.controlPlaneEndpoint?`http://:18080`:F(e)}/downloads/rap-host-agent-windows-amd64.exe`}function Zt(e){return e.trim().toLowerCase().replace(/[^a-z0-9-]+/g,`-`).replace(/^-+|-+$/g,``).slice(0,42)||`rap-node`}function I(e){return`'${e.replace(/'/g,`'\\''`)}'`}function Qt(e){return`'${e.replace(/'/g,`''`)}'`}function $t(e){return e.replace(/"/g,`""`)}function en(e,t){return e.includes(t)?e.filter(e=>e!==t):[...e,t]}function tn(e,t,n,r,i){let a=n.trim().toLowerCase(),o=new Map;for(let n of e){if(a&&!nn(n,a))continue;let e=rn(n,t,r,i);o.set(e,[...o.get(e)||[],n])}return Array.from(o.entries()).map(([e,t])=>({label:e,items:t.sort((e,t)=>e.node.name.localeCompare(t.node.name))})).sort((e,t)=>e.label.localeCompare(t.label))}function nn(e,t){return[e.node.name,e.node.node_key,e.node.health_status,e.node.ownership_type,e.node.reported_version||``,...e.memberships.flatMap(e=>[e.cluster.name,e.cluster.slug,e.node.membership_status])].some(e=>e.toLowerCase().includes(t))}function rn(e,t,n,r){if(n===`health`)return V(e.node.health_status);if(n===`ownership`)return V(e.node.ownership_type);if(n===`cluster_count`)return _n(e.memberships.length,r);let i=e.memberships.find(e=>e.cluster.id===t);return i?i.node.membership_status===`active`?r===`en`?`In active cluster`:`В активном кластере`:`${r===`en`?`Membership`:`Участие`}: ${V(i.node.membership_status)}`:r===`en`?`Not in active cluster`:`Не в активном кластере`}function an(e,t){let n=ae[e]||[];if(n.length===0||!t)return`unknown`;if(on(t))return`stale`;let r=t.capabilities||{};return n.some(e=>!!r[e])?`confirmed`:`missing`}function on(e){if(!e?.observed_at)return!0;let t=new Date(e.observed_at).getTime();return!Number.isFinite(t)||Date.now()-t>60*1e3}function sn(e,t){let n=an(e,t);return n===`confirmed`?`good`:n===`missing`?`bad`:n===`stale`?`warn`:``}function cn(e,t,n){let r=an(e,t);return r===`confirmed`?n.capabilityConfirmed:r===`missing`?n.capabilityMissing:r===`stale`?`heartbeat устарел`:n.capabilityUnknown}function ln(e,t,n){let r=an(e,t);return n===`en`?r===`confirmed`?`capable`:r===`missing`?`not reported`:r===`stale`?`stale heartbeat`:`unknown`:r===`confirmed`?`подходит`:r===`missing`?`не заявлено`:r===`stale`?`heartbeat устарел`:`неизвестно`}function un(e){let t=e?.metadata?.mesh_peer_recovery_report;if(!t||typeof t!=`object`||Array.isArray(t))return`н/д`;let n=t,r=typeof n.mode==`string`?n.mode:`unknown`,i=typeof n.ready_peer_count==`number`?n.ready_peer_count:null,a=typeof n.target_ready_peers==`number`?n.target_ready_peers:null,o=typeof n.deficit==`number`?n.deficit:0,s=i==null||a==null?r:`${r} ${i}/${a}`;return o>0?`${s} deficit ${o}`:s}function dn(e){let t=e?.metadata?.mesh_peer_connection_intent_report;if(!t||typeof t!=`object`||Array.isArray(t))return hn(e);let n=t,r=typeof n.intent_count==`number`?n.intent_count:0,i=typeof n.maintain_count==`number`?n.maintain_count:0,a=typeof n.recover_count==`number`?n.recover_count:0,o=typeof n.rendezvous_required_count==`number`?n.rendezvous_required_count:0,s=typeof n.rendezvous_resolved_count==`number`?n.rendezvous_resolved_count:0,c=typeof n.relay_control_count==`number`?n.relay_control_count:0,l=[`rv${o}`];s>0&&l.push(`ok${s}`),c>0&&l.push(`relay${c}`);let u=o>0||s>0||c>0?`${r} intents m${i}/r${a} ${l.join(`/`)}`:`${r} intents m${i}/r${a}`,d=hn(e);return d===`н/д`?u:`${u}; ${d}`}function fn(e){let t=e?.metadata?.mesh_rendezvous_lease_report;if(!t||typeof t!=`object`||Array.isArray(t))return`н/д`;let n=t,r=typeof n.lease_count==`number`?n.lease_count:0,i=typeof n.active_count==`number`?n.active_count:0,a=typeof n.admitted_as_relay_count==`number`?n.admitted_as_relay_count:0,o=typeof n.admitted_as_peer_count==`number`?n.admitted_as_peer_count:0,s=typeof n.renewal_needed_count==`number`?n.renewal_needed_count:0,c=typeof n.relay_control_ready_count==`number`?n.relay_control_ready_count:0,l=typeof n.stale_relay_count==`number`?n.stale_relay_count:0,u=typeof n.refresh_attempt_count==`number`?n.refresh_attempt_count:0,d=typeof n.refresh_success_count==`number`?n.refresh_success_count:0,f=[`lease ${i}/${r}`];return a>0&&f.push(`relay${a}`),o>0&&f.push(`peer${o}`),s>0&&f.push(`renew${s}`),l>0&&f.push(`stale${l}`),c>0&&f.push(`ready${c}`),u>0&&f.push(`ref${d}/${u}`),f.join(` `)}function pn(e){let t=e?.metadata?.mesh_route_path_decision_report;if(!t||typeof t!=`object`||Array.isArray(t))return`н/д`;let n=t,r=typeof n.decision_count==`number`?n.decision_count:0,i=typeof n.replacement_decision_count==`number`?n.replacement_decision_count:0,a=typeof n.degraded_decision_count==`number`?n.degraded_decision_count:0,o=typeof n.recovery_hysteresis_count==`number`?n.recovery_hysteresis_count:0,s=typeof n.recovery_promoted_count==`number`?n.recovery_promoted_count:0,c=typeof n.recovery_demoted_count==`number`?n.recovery_demoted_count:0,l=typeof n.local_effective_path_count==`number`?n.local_effective_path_count:0,u=typeof n.next_hop_available_count==`number`?n.next_hop_available_count:0,d=typeof n.withdrawn_local_relay_count==`number`?n.withdrawn_local_relay_count:0,f=[`path ${l}/${r}`];return i>0&&f.push(`repl${i}`),a>0&&f.push(`degr${a}`),o>0&&f.push(`rec${o}`),s>0&&f.push(`prom${s}`),c>0&&f.push(`dem${c}`),u>0&&f.push(`next${u}`),d>0&&f.push(`wd${d}`),f.join(` `)}function L(e){let t=e?.metadata?.mesh_route_generation_report;if(!t||typeof t!=`object`||Array.isArray(t))return`н/д`;let n=t,r=typeof n.active_decision_count==`number`?n.active_decision_count:0,i=typeof n.applied_decision_count==`number`?n.applied_decision_count:0,a=typeof n.withdrawn_decision_count==`number`?n.withdrawn_decision_count:0,o=n.generation_changed===!0,s=[`gen ${r}`];return i>0&&s.push(`ap${i}`),a>0&&s.push(`wd${a}`),o&&s.push(`chg`),s.join(` `)}function mn(e){let t=e?.metadata?.mesh_route_health_config_report;if(!t||typeof t!=`object`||Array.isArray(t))return`н/д`;let n=t,r=e?.metadata?.mesh_route_health_feedback_refresh_report,i=r&&typeof r==`object`&&!Array.isArray(r)?r:{},a=typeof n.route_health_route_count==`number`?n.route_health_route_count:0,o=typeof n.route_path_decision_applied_count==`number`?n.route_path_decision_applied_count:0,s=typeof n.replacement_route_health_route_count==`number`?n.replacement_route_health_route_count:0,c=typeof n.route_health_decision_drift_candidate_count==`number`?n.route_health_decision_drift_candidate_count:0,l=typeof i.feedback_refresh_attempt_count==`number`?i.feedback_refresh_attempt_count:typeof n.feedback_refresh_attempt_count==`number`?n.feedback_refresh_attempt_count:0,u=typeof i.feedback_refresh_success_count==`number`?i.feedback_refresh_success_count:typeof n.feedback_refresh_success_count==`number`?n.feedback_refresh_success_count:0,d=typeof i.feedback_refresh_suppressed_count==`number`?i.feedback_refresh_suppressed_count:typeof n.feedback_refresh_suppressed_count==`number`?n.feedback_refresh_suppressed_count:0,f=[`rh ${o}/${a}`];return s>0&&f.push(`repl${s}`),c>0&&f.push(`drift${c}`),(l>0||d>0)&&f.push(`fb${u}/${l}`),d>0&&f.push(`sup${d}`),f.join(` `)}function hn(e){let t=e?.metadata?.mesh_peer_connection_manager_report;if(!t||typeof t!=`object`||Array.isArray(t))return`н/д`;let n=t;if(n.enabled===!1)return`manager off`;let r=typeof n.attempted==`number`?n.attempted:0,i=typeof n.succeeded==`number`?n.succeeded:0,a=typeof n.deferred==`number`?n.deferred:0,o=typeof n.relay_control_count==`number`?n.relay_control_count:0,s=o>0?`mgr ${i}/${r} relay${o}`:`mgr ${i}/${r}`;return a>0?`${s} def${a}`:s}function gn(e){let t=e?.metadata?.mesh_listener_report;if(!t||typeof t!=`object`||Array.isArray(t))return`н/д`;let n=t,r=typeof n.status==`string`?n.status:`unknown`,i=typeof n.listen_port_mode==`string`?n.listen_port_mode:`manual`,a=typeof n.effective_listen_addr==`string`?n.effective_listen_addr:``,o=typeof n.failure_reason==`string`?n.failure_reason:``;return r===`listening`?a?`listen ${a}`:`listen`:r===`auto_rebound`?a?`auto ${a}`:`auto rebound`:r===`listen_failed`?o?`${i} failed: ${o}`:`${i} failed`:r===`disabled`?i===`disabled`?`inbound off`:`inbound unavailable`:r}function _n(e,t){if(t===`en`)return e===1?`1 cluster`:`${e} clusters`;let n=e%10,r=e%100;return n===1&&r!==11?`${e} кластер`:n>=2&&n<=4&&(r<12||r>14)?`${e} кластера`:`${e} кластеров`}function vn(e,t=e.link_status===`reachable`?`reachable`:`unknown`){return t===`stale`?`stale`:t===`one_way`?`oneWay`:t!==`reachable`||e.link_status!==`reachable`?`bad`:e.quality_score!=null&&e.quality_score<70||e.latency_ms!=null&&e.latency_ms>80?`weak`:`good`}function yn(e,t=16){return e.length>t?`${e.slice(0,Math.max(1,t-2))}…`:e}function bn(e){return window.confirm(`${e}?\n\nЭто высокорисковая операция владельца платформы. Действие будет записано в аудит.`)}function xn(e){let t=(e||``).replace(/\/$/,``);return!t||t===`/api/v1`?window.location.origin:t.endsWith(`/api/v1`)?t.slice(0,-7):t}function R(e){return e?e.length>12?`${e.slice(0,8)}...${e.slice(-4)}`:e:`нет`}function z(e){return e?new Intl.DateTimeFormat(void 0,{dateStyle:`medium`,timeStyle:`short`}).format(new Date(e)):`никогда`}function Sn(e){return e==null||Number.isNaN(e)?`age n/a`:e<60?`${Math.max(0,Math.round(e))}s ago`:e<3600?`${Math.round(e/60)}m ago`:e<86400?`${Math.round(e/3600)}h ago`:`${Math.round(e/86400)}d ago`}function Cn(e){return e?new Intl.DateTimeFormat(void 0,{hour:`2-digit`,minute:`2-digit`,second:`2-digit`}).format(new Date(e)):`н/д`}function B(e){if(e==null||Number.isNaN(e))return`н/д`;let t=[`B`,`KB`,`MB`,`GB`,`TB`],n=e,r=0;for(;n>=1024&&r(e[t]||0)>0).map(t=>`${t[0]}:${e[t]}`).join(` `)||`qos none`}function Tn(e){return!e||Object.keys(e).length===0?`n/a`:[`control`,`interactive`,`reliable`,`bulk`,`droppable`].filter(t=>(e[t]||0)>0).map(t=>`${t[0]}:${e[t]}`).join(` `)||`n/a`}function En(e,t){return(t||0)>0||e===`critical`?`bad`:e===`degraded`?`warn`:e===`watch`?`info`:`good`}function Dn(e){switch(e){case`applied`:case`rebuild_request_applied`:return`good`;case`waiting_node_apply`:case`pending_rebuild_request`:case`pending_degraded_fallback`:case`rebuild_request_recorded`:case`rebuild_request_recorded_node_pending`:case`rebuild_request_no_alternate`:case`rebuild_request_deferred_by_policy`:case`route_rebuild_no_safe_recovery`:return`warn`;case`expired`:case`rejected_by_policy_guard`:case`rebuild_request_rejected`:case`rebuild_request_expired`:return`bad`;default:return e?`bad`:``}}function On(e,t,n){return e===`service_channel_feedback_no_alternate`||t===`pending_degraded_fallback`||(n||[]).includes(`no_unfenced_alternate_route`)?`warn`:t===`applied`||(n||[]).includes(`service_channel_rebuild_applied`)?`good`:e?.includes(`replacement`)||e||t?`info`:``}function V(e){return{active:`активно`,approved:`одобрено`,authoritative:`authoritative`,connecting:`подключается`,connected:`связан`,critical:`критично`,current:`актуальна`,degraded:`degraded`,disabled:`выключено`,enabled:`включено`,failed:`ошибка`,healthy:`здоров`,watch:`наблюдение`,flow_health_ready:`flow ready`,flow_drops_reported:`flow drops`,route_quality_window_drops_reported:`route drops`,backend_fallback_observed:`backend fallback`,route_quality_window_failures_reported:`route failures`,route_quality_window_slow_samples_reported:`slow samples`,route_send_latency_high:`high latency`,flow_queue_pressure_high:`queue pressure high`,bulk_pressure_with_interactive_qos_observed:`bulk+interactive`,bulk_pressure_observed:`bulk pressure`,flow_queue_pressure_observed:`queue pressure`,flow_health_degraded:`flow degraded`,bulk_window_reduced_to_protect_interactive:`bulk reduced`,rebuild_request_applied:`planner applied`,rebuild_request_recorded:`rebuild recorded`,rebuild_request_recorded_node_pending:`node pending`,rebuild_request_no_alternate:`no alternate`,rebuild_request_deferred_by_policy:`deferred by policy`,rebuild_request_rejected:`rebuild rejected`,rebuild_request_expired:`rebuild expired`,route_rebuild_no_safe_recovery:`no safe recovery`,access_decision:`access decision`,access_no_safe_recovery:`access no-safe`,access_recovery_selected:`access recovery`,access_rebuild_applied:`access applied`,access_replacement_selected:`access replacement`,inspect_access_no_safe_recovery_route_pool_and_signed_policy:`inspect no-safe route pool`,watch_recovery_route_quality_and_confirm_post_recovery_traffic:`watch recovery traffic`,confirm_applied_rebuild_runtime_traffic_stays_on_replacement:`confirm applied traffic`,watch_replacement_route_quality_until_applied_or_recovered:`watch replacement`,pending_degraded_fallback:`pending fallback`,service_channel_feedback_no_alternate:`no safe route`,service_channel_feedback_replacement:`replacement`,service_channel_feedback_exit_pool_replacement:`exit replacement`,service_channel_feedback_entry_pool_replacement:`entry replacement`,service_channel_feedback_entry_exit_pool_replacement:`pool replacement`,service_channel_remediation_command:`remediation`,service_channel_feedback_rebuild_requested:`rebuild requested`,remediation_rebuild_applied_to_alternate:`planner selected alternate`,no_unfenced_alternate_route:`no safe alternate`,active_lease_not_found_for_rebuild_resolution:`lease missing`,remediation_command_ttl_expired:`command expired`,durable_rebuild_route_request_recorded:`rebuild recorded`,durable_rebuild_route_request_rejected:`request rejected`,durable_rebuild_route_request_applied:`request applied`,durable_rebuild_route_no_alternate:`no alternate`,durable_rebuild_route_deferred_by_policy:`deferred by policy`,durable_rebuild_route_expired:`request expired`,isolated:`изолирован`,offline:`нет связи`,one_way:`односторонняя`,outdated:`обновить`,pending:`ожидает`,platform_managed:`платформенный`,promoted:`promoted`,rejected:`отклонено`,ready:`готово`,revoked:`отозвано`,running:`работает`,customer_managed:`клиентский`,no_policy:`нет политики`,not_configured:`не задано`,missing:`нет отчета`,service_channel_recovery_demoted:`demoted`,service_channel_recovery_demoted_degraded:`degraded`,service_channel_recovery_demoted_degraded_fallback:`fallback`,service_channel_recovery_demoted_failure:`failure`,service_channel_recovery_demoted_fenced:`fenced`,service_channel_recovery_demoted_rebuild:`rebuild`,service_channel_recovery_demoted_slow:`slow`,service_channel_feedback_provenance_missing:`provenance missing`,service_channel_feedback_stale:`stale feedback`,service_channel_feedback_stale_generation:`stale generation`,service_channel_feedback_stale_policy:`stale policy`,service_channel_feedback_stale_policy_and_generation:`stale policy+generation`,schema_ready:`schema ready`,schema_migration_required:`schema migration required`,snapshots_warmed:`snapshots warmed`,missing_snapshots_warmed_stale_deferred:`missing warmed, stale deferred`,snapshot_warmup_partial:`warmup partial`,stopped:`остановлено`,stale:`устарело`,unknown:`неизвестно`}[e]||e}(0,v.createRoot)(document.getElementById(`root`)).render((0,S.jsx)(_.StrictMode,{children:(0,S.jsx)(de,{})})); \ No newline at end of file diff --git a/web-admin/deploy/html/index.html b/web-admin/deploy/html/index.html index e9ef516..b69ff5a 100644 --- a/web-admin/deploy/html/index.html +++ b/web-admin/deploy/html/index.html @@ -4,8 +4,8 @@ Панель Secure Access Fabric - - + +
diff --git a/web-admin/src/App.tsx b/web-admin/src/App.tsx index 1fa0af5..7109555 100644 --- a/web-admin/src/App.tsx +++ b/web-admin/src/App.tsx @@ -9149,7 +9149,7 @@ function installControlPlaneEndpoint(form: JoinTokenFormState = defaultJoinToken function externalPreferredControlPlaneEndpoint() { const origin = typeof window === "undefined" ? "" : window.location?.origin || ""; if (/^(http:\/\/)?(192\.168\.200\.61|docker-test\.cin\.su)(:18080)?$/i.test(origin.replace(/\/$/, ""))) { - return "http://vpn.cin.su:19191/api/v1"; + return "https://vpn.cin.su/api/v1"; } return `${origin.replace(/\/$/, "")}/api/v1`; }