direxio-deployer 0.1.0 → 0.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (49) hide show
  1. package/README.md +3 -1
  2. package/README_zh.md +3 -1
  3. package/SKILL.md +2 -2
  4. package/bin/direxio-deployer.mjs +1 -2
  5. package/package.json +2 -3
  6. package/references/deployment-lessons.md +5 -7
  7. package/references/deployment-workflow.md +1 -1
  8. package/references/tooling.md +11 -12
  9. package/references/user-journey.md +2 -2
  10. package/references/voip-turn-runbook.md +2 -2
  11. package/references/windows-deployment-notes.md +2 -1
  12. package/scripts/destroy.sh +24 -43
  13. package/scripts/json.mjs +841 -0
  14. package/scripts/lib/aws.sh +5 -1
  15. package/scripts/lib/json.sh +114 -0
  16. package/scripts/lib/operation_report.sh +8 -195
  17. package/scripts/lib/ops.sh +8 -21
  18. package/scripts/lib/state.sh +18 -44
  19. package/scripts/mcp-tools-list.mjs +21 -4
  20. package/scripts/orchestrate.sh +147 -249
  21. package/scripts/phases/s3_provision.sh +5 -10
  22. package/scripts/phases/s5_init_tokens.sh +7 -17
  23. package/scripts/phases/s6_wire_local.sh +9 -35
  24. package/scripts/phases/s7_verify_e2e.sh +5 -5
  25. package/scripts/pricing-estimate.sh +36 -80
  26. package/tests/aws_credentials_test.sh +0 -139
  27. package/tests/connect_daemon_runtime_check_test.sh +0 -120
  28. package/tests/default_paths_test.sh +0 -58
  29. package/tests/destroy_local_bridge_test.sh +0 -154
  30. package/tests/destroy_root_identity_test.sh +0 -91
  31. package/tests/destroy_route53_zone_test.sh +0 -80
  32. package/tests/domain_authoritative_dns_test.sh +0 -49
  33. package/tests/mcp_doctor_runtime_check_test.sh +0 -86
  34. package/tests/mcp_smoke_runtime_check_test.sh +0 -121
  35. package/tests/mcp_tools_runtime_check_test.sh +0 -123
  36. package/tests/npm_skill_distribution_test.sh +0 -95
  37. package/tests/operation_report_test.sh +0 -258
  38. package/tests/orchestrate_status_recovery_test.sh +0 -91
  39. package/tests/phase_timeout_test.sh +0 -88
  40. package/tests/pricing_estimate_test.sh +0 -159
  41. package/tests/render_userdata_remote_nodes_test.sh +0 -40
  42. package/tests/root_volume_tracking_test.sh +0 -41
  43. package/tests/route53_overwrite_guard_test.sh +0 -86
  44. package/tests/route53_zone_auto_create_test.sh +0 -66
  45. package/tests/runtime_summary_check_test.sh +0 -203
  46. package/tests/s6_wire_local_test.sh +0 -405
  47. package/tests/skill_structure_test.sh +0 -298
  48. package/tests/update_reset_ops_test.sh +0 -230
  49. package/tests/user_confirmation_gates_test.sh +0 -152
@@ -23,9 +23,7 @@ set -uo pipefail
23
23
  HERE=$(cd "$(dirname "$0")" && pwd)
24
24
  P2P_INSTALL_SCRIPTS_DIR="$HERE"
25
25
 
26
- # Prefer workspace-local tools when present. On Windows, jq may be downloaded
27
- # into .tools/bin/jq.exe by the operator/system and is discoverable from
28
- # Git Bash/MSYS only when this path is prepended.
26
+ # Prefer workspace-local tools when present.
29
27
  REPO_ROOT=$(cd "$HERE/.." && pwd)
30
28
  if [ -d "$REPO_ROOT/.tools/bin" ]; then
31
29
  PATH="$REPO_ROOT/.tools/bin:$PATH"
@@ -58,17 +56,12 @@ phase_file() {
58
56
  # Dependency check.
59
57
  check_deps() {
60
58
  local b missing=""
61
- for b in aws jq ssh scp curl; do
59
+ for b in aws ssh scp curl; do
62
60
  command -v "$b" >/dev/null 2>&1 || missing="$missing $b"
63
61
  done
64
62
  [ -z "$missing" ] && return 0
65
63
 
66
64
  warn "Missing dependencies:$missing"
67
- case " $missing " in
68
- *" jq "*)
69
- warn "jq is required for state.json. If this workspace has .tools/bin/jq.exe, run from a POSIX shell that can see that path."
70
- ;;
71
- esac
72
65
  case " $missing " in
73
66
  *" aws "*)
74
67
  warn "Install AWS CLI v2 and configure credentials first:"
@@ -108,9 +101,9 @@ cmd_status_inventory() {
108
101
  [ -f "$state" ] || continue
109
102
  found=1
110
103
  service_dir=${state%/state.json}
111
- domain=$(jq -r '.domain // empty' "$state")
112
- phase=$(jq -r '.phase // empty' "$state")
113
- instance=$(jq -r '.resources.instance_id // empty' "$state")
104
+ domain=$(json_get "$state" domain)
105
+ phase=$(json_get "$state" phase)
106
+ instance=$(json_get "$state" resources.instance_id)
114
107
  if STATE_JSON="$state" first_unfinished_phase >/dev/null 2>&1; then
115
108
  current=$(STATE_JSON="$state" first_unfinished_phase)
116
109
  else
@@ -287,7 +280,7 @@ cmd_status() {
287
280
  printf " %-20s %s\n" "$p" "$(phase_status "$p")"
288
281
  done
289
282
  echo "-- resources --"
290
- jq -r '.resources | to_entries[]? | " \(.key)=\(.value)"' "$STATE_JSON"
283
+ json_entries "$STATE_JSON" resources | sed 's/^/ /'
291
284
  print_recovery_summary "$current"
292
285
  }
293
286
 
@@ -320,10 +313,10 @@ print_delivery() {
320
313
  install_mode=$(state_get agent_install_mode)
321
314
  install_status=$(state_get agent_install_status)
322
315
  install_command=$(state_get agent_install_command)
323
- runtime_summary=$(jq -r '.runtime_checks.summary.status // "not_run"' "$STATE_JSON")
324
- app_gate=$(jq -r '.user_confirmations.app_initialization.status // "pending_user_confirmation"' "$STATE_JSON")
325
- real_chat_gate=$(jq -r '.user_confirmations.real_chat.status // "pending_user_confirmation"' "$STATE_JSON")
326
- agent_runtime_gate=$(jq -r '.user_confirmations.agent_mcp_runtime.status // "pending_runtime_confirmation"' "$STATE_JSON")
316
+ runtime_summary=$(json_get "$STATE_JSON" runtime_checks.summary.status "not_run")
317
+ app_gate=$(json_get "$STATE_JSON" user_confirmations.app_initialization.status "pending_user_confirmation")
318
+ real_chat_gate=$(json_get "$STATE_JSON" user_confirmations.real_chat.status "pending_user_confirmation")
319
+ agent_runtime_gate=$(json_get "$STATE_JSON" user_confirmations.agent_mcp_runtime.status "pending_runtime_confirmation")
327
320
  echo
328
321
  echo -e "\033[32m========== Automated Deployment Gates Passed ==========\033[0m"
329
322
  echo " App domain : $domain"
@@ -391,10 +384,10 @@ ensure_cost_estimate() {
391
384
  fi
392
385
 
393
386
  if output=$(bash "$HERE/pricing-estimate.sh" "${args[@]}" 2>/dev/null); then
394
- status=$(printf '%s\n' "$output" | jq -r '.pricing_status // "unknown"' 2>/dev/null)
395
- total=$(printf '%s\n' "$output" | jq -r '.total_monthly_usd // "unknown"' 2>/dev/null)
396
- region=$(printf '%s\n' "$output" | jq -r '.region // "unknown"' 2>/dev/null)
397
- instance_type=$(printf '%s\n' "$output" | jq -r '.components.ec2_instance.instance_type // "unknown"' 2>/dev/null)
387
+ status=$(printf '%s\n' "$output" | json_stdin_get pricing_status "unknown" 2>/dev/null)
388
+ total=$(printf '%s\n' "$output" | json_stdin_get total_monthly_usd "unknown" 2>/dev/null)
389
+ region=$(printf '%s\n' "$output" | json_stdin_get region "unknown" 2>/dev/null)
390
+ instance_type=$(printf '%s\n' "$output" | json_stdin_get components.ec2_instance.instance_type "unknown" 2>/dev/null)
398
391
  log "Cost estimate recorded (status=${status:-unknown}, region=${region:-unknown}, instance=${instance_type:-unknown}, monthly_usd≈${total:-unknown})."
399
392
  if [ "$status" = "fallback" ]; then
400
393
  warn "AWS Pricing API was unavailable or incomplete; cost_estimate uses conservative fallback values."
@@ -439,7 +432,7 @@ ensure_production_domain_selected() {
439
432
  state_domain=$(domain_normalize "$state_domain")
440
433
  state_mode=$(state_get domain_mode)
441
434
  env_domain=$(domain_normalize "${DOMAIN:-}")
442
- confirmed=$(jq -r '.domain_confirmed_irreversible // false' "$STATE_JSON")
435
+ confirmed=$(json_get "$STATE_JSON" domain_confirmed_irreversible false)
443
436
 
444
437
  if [ -n "$env_domain" ] && [ -n "$state_domain" ] && [ "$env_domain" != "$state_domain" ]; then
445
438
  warn "Deployment blocked: current state is bound to DOMAIN=$state_domain, but this run passed DOMAIN=${env_domain}."
@@ -484,22 +477,22 @@ ensure_production_domain_selected() {
484
477
  guard_existing_state() {
485
478
  [ -f "$STATE_JSON" ] || return 0
486
479
  local resources_count confirmed action
487
- resources_count=$(jq -r '.resources | length' "$STATE_JSON")
480
+ resources_count=$(json_length "$STATE_JSON" resources)
488
481
  [ "$resources_count" -eq 0 ] && return 0
489
- if [ "$(jq -r '.domain_mode // empty' "$STATE_JSON")" = "ec2" ]; then
482
+ if [ "$(json_get "$STATE_JSON" domain_mode)" = "ec2" ]; then
490
483
  warn "Found legacy temporary-domain deployment state (domain_mode=ec2). Production deployment no longer supports resuming this mode."
491
484
  warn "Destroy and rebuild, or use a new service directory:"
492
485
  warn " P2P_EXISTING_STATE_ACTION=destroy bash $0"
493
486
  warn " DOMAIN=__DOMAIN__ DOMAIN_MODE=user CONFIRM_DOMAIN_BINDING=1 bash $0"
494
487
  return 2
495
488
  fi
496
- confirmed=$(jq -r '.existing_state_confirmed // false' "$STATE_JSON")
489
+ confirmed=$(json_get "$STATE_JSON" existing_state_confirmed false)
497
490
  [ "$confirmed" = "true" ] && return 0
498
491
 
499
492
  action=${P2P_EXISTING_STATE_ACTION:-}
500
493
  if [ -z "$action" ] && [ -t 0 ]; then
501
494
  warn "Found existing deployment state with recorded AWS resources:"
502
- jq -r '.resources | to_entries[]? | " \(.key)=\(.value)"' "$STATE_JSON" >&2
495
+ json_entries "$STATE_JSON" resources | sed 's/^/ /' >&2
503
496
  warn "Choose: continue=resume / destroy=destroy and rebuild / abort=stop now"
504
497
  printf "Action [abort]: " >&2
505
498
  read -r action
@@ -601,7 +594,7 @@ cmd_confirm() {
601
594
  warn "DIREXIO_CONFIRM_EVIDENCE is too short; provide a concrete user/runtime evidence note."
602
595
  return 1
603
596
  fi
604
- runtime_summary_status=$(jq -r '.runtime_checks.summary.status // "not_run"' "$STATE_JSON")
597
+ runtime_summary_status=$(json_get "$STATE_JSON" runtime_checks.summary.status "not_run")
605
598
  runtime_probe_confirmed=false
606
599
  if [ "$gate" = "agent_mcp_runtime" ]; then
607
600
  if [ "$runtime_summary_status" != "passed" ]; then
@@ -614,21 +607,19 @@ cmd_confirm() {
614
607
  fi
615
608
  runtime_probe_confirmed=true
616
609
  fi
617
- _state_write '
618
- .user_confirmations[$gate] = {
619
- status: "confirmed",
620
- ts: $ts,
621
- evidence: $evidence
622
- }
623
- + (if $gate == "agent_mcp_runtime" then {
624
- runtime_summary_status: $runtime_summary_status,
625
- runtime_probe_confirmed: ($runtime_probe_confirmed == "true")
626
- } else {} end)
627
- ' --arg gate "$gate" \
628
- --arg ts "$(_now)" \
629
- --arg evidence "$evidence" \
630
- --arg runtime_summary_status "$runtime_summary_status" \
631
- --arg runtime_probe_confirmed "$runtime_probe_confirmed"
610
+ if [ "$gate" = "agent_mcp_runtime" ]; then
611
+ state_set_object "user_confirmations.$gate" \
612
+ status=confirmed \
613
+ "ts=$(_now)" \
614
+ "evidence=$evidence" \
615
+ "runtime_summary_status=$runtime_summary_status" \
616
+ "runtime_probe_confirmed=$runtime_probe_confirmed"
617
+ else
618
+ state_set_object "user_confirmations.$gate" \
619
+ status=confirmed \
620
+ "ts=$(_now)" \
621
+ "evidence=$evidence"
622
+ fi
632
623
  echo "confirmed gate: $gate"
633
624
  }
634
625
 
@@ -638,10 +629,11 @@ cmd_verify_mcp_doctor() {
638
629
  return 1
639
630
  }
640
631
 
641
- local credentials mcp_cmd node_id out err report token_status
642
- credentials=$(jq -r '.agent_credentials_file // .mcp_credentials_file // empty' "$STATE_JSON")
643
- mcp_cmd=$(jq -r '.mcp_command // "direxio-mcp"' "$STATE_JSON")
644
- node_id=$(jq -r '.agent_node_id // empty' "$STATE_JSON")
632
+ local credentials mcp_cmd node_id out err report token_status report_domain report_room
633
+ credentials=$(json_get "$STATE_JSON" agent_credentials_file)
634
+ [ -n "$credentials" ] || credentials=$(json_get "$STATE_JSON" mcp_credentials_file)
635
+ mcp_cmd=$(json_get "$STATE_JSON" mcp_command "direxio-mcp")
636
+ node_id=$(json_get "$STATE_JSON" agent_node_id)
645
637
  [ -n "$credentials" ] || {
646
638
  warn "mcp doctor check requires agent_credentials_file or mcp_credentials_file in state.json"
647
639
  return 1
@@ -651,45 +643,34 @@ cmd_verify_mcp_doctor() {
651
643
  out=$(mktemp)
652
644
  err=$(mktemp)
653
645
  if ! DIREXIO_CREDENTIALS_FILE="$credentials" DIREXIO_AGENT_NODE_ID="$node_id" bash -c "$mcp_cmd doctor --json" > "$out" 2> "$err"; then
654
- _state_write '
655
- .runtime_checks.mcp_doctor = {
656
- status: "failed",
657
- ts: $ts,
658
- evidence: "direxio-mcp doctor failed"
659
- }
660
- ' --arg ts "$(_now)"
646
+ state_set_object runtime_checks.mcp_doctor status=failed "ts=$(_now)" "evidence=direxio-mcp doctor failed"
661
647
  cat "$err" >&2
662
648
  rm -f "$out" "$err"
663
649
  return 1
664
650
  fi
665
- if ! jq empty "$out" >/dev/null 2>&1; then
666
- _state_write '
667
- .runtime_checks.mcp_doctor = {
668
- status: "failed",
669
- ts: $ts,
670
- evidence: "direxio-mcp doctor returned non-json output"
671
- }
672
- ' --arg ts "$(_now)"
651
+ if ! json_valid "$out" >/dev/null 2>&1; then
652
+ state_set_object runtime_checks.mcp_doctor status=failed "ts=$(_now)" "evidence=direxio-mcp doctor returned non-json output"
673
653
  rm -f "$out" "$err"
674
654
  return 1
675
655
  fi
676
656
  report=$(cat "$out")
677
- token_status=$(printf '%s\n' "$report" | jq -r '
678
- if (.token // "") == "redacted" then "redacted"
679
- elif ((.token // "") | tostring | length) > 0 then "present_redacted"
680
- else "missing"
681
- end
682
- ')
683
- _state_write '
684
- .runtime_checks.mcp_doctor = {
685
- status: "passed",
686
- ts: $ts,
687
- evidence: "direxio-mcp doctor --json succeeded",
688
- domain: ($report.domain // ""),
689
- agent_room_id: ($report.agent_room_id // ""),
690
- token: $token_status
691
- }
692
- ' --arg ts "$(_now)" --argjson report "$report" --arg token_status "$token_status"
657
+ token_status=$(printf '%s\n' "$report" | json_stdin_get token)
658
+ if [ "$token_status" = "redacted" ]; then
659
+ token_status=redacted
660
+ elif [ -n "$token_status" ]; then
661
+ token_status=present_redacted
662
+ else
663
+ token_status=missing
664
+ fi
665
+ report_domain=$(json_get "$out" domain)
666
+ report_room=$(json_get "$out" agent_room_id)
667
+ state_set_object runtime_checks.mcp_doctor \
668
+ status=passed \
669
+ "ts=$(_now)" \
670
+ "evidence=direxio-mcp doctor --json succeeded" \
671
+ "domain=$report_domain" \
672
+ "agent_room_id=$report_room" \
673
+ "token=$token_status"
693
674
  rm -f "$out" "$err"
694
675
  echo "verified runtime check: mcp_doctor"
695
676
  }
@@ -700,56 +681,49 @@ cmd_verify_mcp_smoke() {
700
681
  return 1
701
682
  }
702
683
 
703
- local service_url token room_id body code payload tmp url
704
- service_url=$(jq -r '.as_url // empty' "$STATE_JSON")
684
+ local service_url token room_id body code payload url response_room_id response_messages_type
685
+ service_url=$(json_get "$STATE_JSON" as_url)
705
686
  if [ -z "$service_url" ]; then
706
687
  local domain
707
- domain=$(jq -r '.domain // empty' "$STATE_JSON")
688
+ domain=$(json_get "$STATE_JSON" domain)
708
689
  [ -n "$domain" ] && service_url="https://$domain"
709
690
  fi
710
- token=$(jq -r '.agent_token // empty' "$STATE_JSON")
711
- room_id=$(jq -r '.agent_room_id // empty' "$STATE_JSON")
691
+ token=$(json_get "$STATE_JSON" agent_token)
692
+ room_id=$(json_get "$STATE_JSON" agent_room_id)
712
693
  if [ -z "$service_url" ] || [ -z "$token" ] || [ -z "$room_id" ]; then
713
694
  warn "mcp smoke check requires as_url/domain, agent_token, and agent_room_id in state.json"
714
695
  return 1
715
696
  fi
716
697
 
717
698
  body=$(mktemp)
718
- payload=$(jq -cn --arg room_id "$room_id" '{action:"mcp.messages.list", params:{room_id:$room_id, limit:1}}')
699
+ payload=$(json_build mcp-messages-list "$room_id")
719
700
  url="${service_url%/}/_p2p/query"
720
701
  code=$(curl -sk -o "$body" -w '%{http_code}' \
721
702
  -X POST "$url" \
722
703
  -H 'Content-Type: application/json' \
723
704
  -H "Authorization: Bearer $token" \
724
705
  -d "$payload" 2>/dev/null)
725
- if [ "$code" != "200" ] || ! jq -e '(.messages | type == "array") and (.room_id | type == "string")' "$body" >/dev/null 2>&1; then
726
- _state_write '
727
- .runtime_checks.mcp_smoke = {
728
- status: "failed",
729
- ts: $ts,
730
- action: "mcp.messages.list",
731
- evidence: $evidence
732
- }
733
- ' --arg ts "$(_now)" --arg evidence "mcp.messages.list returned HTTP $code or invalid response"
706
+ if [ "$code" != "200" ] || ! json_assert "$body" messages-response >/dev/null 2>&1; then
707
+ state_set_object runtime_checks.mcp_smoke \
708
+ status=failed \
709
+ "ts=$(_now)" \
710
+ action=mcp.messages.list \
711
+ "evidence=mcp.messages.list returned HTTP $code or invalid response"
734
712
  rm -f "$body"
735
713
  return 1
736
714
  fi
737
715
 
738
- tmp=$(mktemp)
739
- jq -n --slurpfile response "$body" \
740
- --arg ts "$(_now)" \
741
- --arg room_id "$room_id" \
742
- '{
743
- status: "passed",
744
- ts: $ts,
745
- action: "mcp.messages.list",
746
- room_id: $room_id,
747
- response_room_id: ($response[0].room_id // ""),
748
- response_messages_type: (($response[0].messages // null) | type),
749
- evidence: "read-only backend smoke check succeeded"
750
- }' > "$tmp"
751
- _state_write '.runtime_checks.mcp_smoke = $check[0]' --slurpfile check "$tmp"
752
- rm -f "$body" "$tmp"
716
+ response_room_id=$(json_get "$body" room_id)
717
+ response_messages_type=$(json_type "$body" messages)
718
+ state_set_object runtime_checks.mcp_smoke \
719
+ status=passed \
720
+ "ts=$(_now)" \
721
+ action=mcp.messages.list \
722
+ "room_id=$room_id" \
723
+ "response_room_id=$response_room_id" \
724
+ "response_messages_type=$response_messages_type" \
725
+ "evidence=read-only backend smoke check succeeded"
726
+ rm -f "$body"
753
727
  echo "verified runtime check: mcp_smoke"
754
728
  }
755
729
 
@@ -760,9 +734,10 @@ cmd_verify_mcp_tools() {
760
734
  }
761
735
 
762
736
  local credentials mcp_cmd node_id node_cmd node_script out err report
763
- credentials=$(jq -r '.agent_credentials_file // .mcp_credentials_file // empty' "$STATE_JSON")
764
- mcp_cmd=$(jq -r '.mcp_command // "direxio-mcp"' "$STATE_JSON")
765
- node_id=$(jq -r '.agent_node_id // empty' "$STATE_JSON")
737
+ credentials=$(json_get "$STATE_JSON" agent_credentials_file)
738
+ [ -n "$credentials" ] || credentials=$(json_get "$STATE_JSON" mcp_credentials_file)
739
+ mcp_cmd=$(json_get "$STATE_JSON" mcp_command "direxio-mcp")
740
+ node_id=$(json_get "$STATE_JSON" agent_node_id)
766
741
  [ -n "$credentials" ] || {
767
742
  warn "mcp tools check requires agent_credentials_file or mcp_credentials_file in state.json"
768
743
  return 1
@@ -778,52 +753,29 @@ cmd_verify_mcp_tools() {
778
753
  out=$(mktemp)
779
754
  err=$(mktemp)
780
755
  if ! DIREXIO_CREDENTIALS_FILE="$credentials" DIREXIO_AGENT_NODE_ID="$node_id" "$node_cmd" "$node_script" "$mcp_cmd" > "$out" 2> "$err"; then
781
- _state_write '
782
- .runtime_checks.mcp_tools = {
783
- status: "failed",
784
- ts: $ts,
785
- evidence: "MCP tools/list failed"
786
- }
787
- ' --arg ts "$(_now)"
756
+ state_set_object runtime_checks.mcp_tools status=failed "ts=$(_now)" "evidence=MCP tools/list failed"
788
757
  cat "$err" >&2
789
758
  rm -f "$out" "$err"
790
759
  return 1
791
760
  fi
792
- if ! jq -e '(.tools | type == "array") and (.tool_count | type == "number")' "$out" >/dev/null 2>&1; then
793
- _state_write '
794
- .runtime_checks.mcp_tools = {
795
- status: "failed",
796
- ts: $ts,
797
- evidence: "MCP tools/list returned invalid output"
798
- }
799
- ' --arg ts "$(_now)"
761
+ if ! json_assert "$out" tools-list >/dev/null 2>&1; then
762
+ state_set_object runtime_checks.mcp_tools status=failed "ts=$(_now)" "evidence=MCP tools/list returned invalid output"
800
763
  rm -f "$out" "$err"
801
764
  return 1
802
765
  fi
803
766
  report=$(cat "$out")
804
- _state_write '
805
- .runtime_checks.mcp_tools = {
806
- status: "passed",
807
- ts: $ts,
808
- evidence: "MCP tools/list succeeded",
809
- tool_count: ($report.tool_count // 0),
810
- tools: ($report.tools // [])
811
- }
812
- ' --arg ts "$(_now)" --argjson report "$report"
767
+ state_set_object runtime_checks.mcp_tools \
768
+ status=passed \
769
+ "ts=$(_now)" \
770
+ "evidence=MCP tools/list succeeded" \
771
+ "tool_count=$(json_get "$out" tool_count 0)" \
772
+ "tools=$(json_get "$out" tools "[]")"
813
773
  rm -f "$out" "$err"
814
774
  echo "verified runtime check: mcp_tools"
815
775
  }
816
776
 
817
777
  _node_command() {
818
- if command -v node >/dev/null 2>&1; then
819
- command -v node
820
- return 0
821
- fi
822
- if command -v node.exe >/dev/null 2>&1; then
823
- command -v node.exe
824
- return 0
825
- fi
826
- return 1
778
+ json_node
827
779
  }
828
780
 
829
781
  _node_script_path() {
@@ -907,11 +859,12 @@ cmd_verify_connect_daemon() {
907
859
  }
908
860
 
909
861
  local service_name service_dir config runtime_dir binary target_work_dir status_out daemon_status work_dir evidence agent_error
910
- service_name=$(jq -r '.agent_service_id // .domain // empty' "$STATE_JSON")
911
- service_dir=$(jq -r '.agent_service_dir // empty' "$STATE_JSON")
912
- config=$(jq -r '.cc_connect_config // empty' "$STATE_JSON")
913
- runtime_dir=$(jq -r '.cc_connect_runtime_dir // empty' "$STATE_JSON")
914
- binary=$(jq -r '.cc_connect_binary // "direxio-connect"' "$STATE_JSON")
862
+ service_name=$(json_get "$STATE_JSON" agent_service_id)
863
+ [ -n "$service_name" ] || service_name=$(json_get "$STATE_JSON" domain)
864
+ service_dir=$(json_get "$STATE_JSON" agent_service_dir)
865
+ config=$(json_get "$STATE_JSON" cc_connect_config)
866
+ runtime_dir=$(json_get "$STATE_JSON" cc_connect_runtime_dir)
867
+ binary=$(json_get "$STATE_JSON" cc_connect_binary "direxio-connect")
915
868
  [ -n "$service_name" ] || service_name=cc-connect
916
869
  [ -n "$binary" ] || binary=direxio-connect
917
870
 
@@ -930,13 +883,7 @@ cmd_verify_connect_daemon() {
930
883
  */*|[A-Za-z]:/*|[A-Za-z]:\\*) ;;
931
884
  *)
932
885
  command -v "$binary" >/dev/null 2>&1 || {
933
- _state_write '
934
- .runtime_checks.connect_daemon = {
935
- status: "failed",
936
- ts: $ts,
937
- evidence: "direxio-connect binary not found"
938
- }
939
- ' --arg ts "$(_now)"
886
+ state_set_object runtime_checks.connect_daemon status=failed "ts=$(_now)" "evidence=direxio-connect binary not found"
940
887
  warn "connect daemon check could not find binary: $binary"
941
888
  return 1
942
889
  }
@@ -944,14 +891,7 @@ cmd_verify_connect_daemon() {
944
891
  esac
945
892
 
946
893
  status_out=$("$binary" daemon status --service-name "$service_name" 2>/dev/null) || {
947
- _state_write '
948
- .runtime_checks.connect_daemon = {
949
- status: "failed",
950
- ts: $ts,
951
- service_name: $service_name,
952
- evidence: "direxio-connect daemon status failed"
953
- }
954
- ' --arg ts "$(_now)" --arg service_name "$service_name"
894
+ state_set_object runtime_checks.connect_daemon status=failed "ts=$(_now)" "service_name=$service_name" "evidence=direxio-connect daemon status failed"
955
895
  return 1
956
896
  }
957
897
  daemon_status=$(printf '%s\n' "$status_out" | sed -nE 's/^[[:space:]]*Status:[[:space:]]*//p' | head -n 1)
@@ -966,68 +906,45 @@ cmd_verify_connect_daemon() {
966
906
  else
967
907
  agent_error=$(connect_daemon_agent_error_from_logs "$binary" "$service_name")
968
908
  if [ -n "$agent_error" ]; then
969
- _state_write '
970
- .runtime_checks.connect_daemon = {
971
- status: "failed",
972
- ts: $ts,
973
- evidence: "direxio-connect daemon logs report ACP session initialization failure",
974
- service_name: $service_name,
975
- daemon_status: $daemon_status,
976
- work_dir: $work_dir,
977
- expected_work_dir: $target_work_dir,
978
- agent_error: $agent_error
979
- }
980
- ' --arg ts "$(_now)" \
981
- --arg service_name "$service_name" \
982
- --arg daemon_status "$daemon_status" \
983
- --arg work_dir "$(normalize_check_path "$work_dir")" \
984
- --arg target_work_dir "$(normalize_check_path "$target_work_dir")" \
985
- --arg agent_error "$agent_error"
909
+ state_set_object runtime_checks.connect_daemon \
910
+ status=failed \
911
+ "ts=$(_now)" \
912
+ "evidence=direxio-connect daemon logs report ACP session initialization failure" \
913
+ "service_name=$service_name" \
914
+ "daemon_status=$daemon_status" \
915
+ "work_dir=$(normalize_check_path "$work_dir")" \
916
+ "expected_work_dir=$(normalize_check_path "$target_work_dir")" \
917
+ "agent_error=$agent_error"
986
918
  warn "direxio-connect daemon logs report ACP session initialization failure"
987
919
  return 1
988
920
  fi
989
- _state_write '
990
- .runtime_checks.connect_daemon = {
991
- status: "passed",
992
- ts: $ts,
993
- evidence: "direxio-connect daemon is running for this service",
994
- service_name: $service_name,
995
- daemon_status: $daemon_status,
996
- work_dir: $work_dir,
997
- expected_work_dir: $target_work_dir
998
- }
999
- ' --arg ts "$(_now)" \
1000
- --arg service_name "$service_name" \
1001
- --arg daemon_status "$daemon_status" \
1002
- --arg work_dir "$(normalize_check_path "$work_dir")" \
1003
- --arg target_work_dir "$(normalize_check_path "$target_work_dir")"
921
+ state_set_object runtime_checks.connect_daemon \
922
+ status=passed \
923
+ "ts=$(_now)" \
924
+ "evidence=direxio-connect daemon is running for this service" \
925
+ "service_name=$service_name" \
926
+ "daemon_status=$daemon_status" \
927
+ "work_dir=$(normalize_check_path "$work_dir")" \
928
+ "expected_work_dir=$(normalize_check_path "$target_work_dir")"
1004
929
  echo "verified runtime check: connect_daemon"
1005
930
  return 0
1006
931
  fi
1007
932
 
1008
- _state_write '
1009
- .runtime_checks.connect_daemon = {
1010
- status: "failed",
1011
- ts: $ts,
1012
- evidence: $evidence,
1013
- service_name: $service_name,
1014
- daemon_status: $daemon_status,
1015
- work_dir: $work_dir,
1016
- expected_work_dir: $target_work_dir
1017
- }
1018
- ' --arg ts "$(_now)" \
1019
- --arg evidence "$evidence" \
1020
- --arg service_name "$service_name" \
1021
- --arg daemon_status "$daemon_status" \
1022
- --arg work_dir "$(normalize_check_path "$work_dir")" \
1023
- --arg target_work_dir "$(normalize_check_path "$target_work_dir")"
933
+ state_set_object runtime_checks.connect_daemon \
934
+ status=failed \
935
+ "ts=$(_now)" \
936
+ "evidence=$evidence" \
937
+ "service_name=$service_name" \
938
+ "daemon_status=$daemon_status" \
939
+ "work_dir=$(normalize_check_path "$work_dir")" \
940
+ "expected_work_dir=$(normalize_check_path "$target_work_dir")"
1024
941
  warn "$evidence"
1025
942
  return 1
1026
943
  }
1027
944
 
1028
945
  runtime_check_status() {
1029
946
  local check=$1
1030
- jq -r --arg check "$check" '.runtime_checks[$check].status // "not_run"' "$STATE_JSON"
947
+ json_get "$STATE_JSON" "runtime_checks.$check.status" "not_run"
1031
948
  }
1032
949
 
1033
950
  cmd_verify_runtime() {
@@ -1053,47 +970,28 @@ cmd_verify_runtime() {
1053
970
  done
1054
971
 
1055
972
  if [ "$failed_count" -eq 0 ]; then
1056
- _state_write '
1057
- .runtime_checks.summary = {
1058
- status: "passed",
1059
- ts: $ts,
1060
- failed_count: 0,
1061
- evidence: "all runtime checks passed",
1062
- checks: {
1063
- connect_daemon: $connect_status,
1064
- mcp_doctor: $doctor_status,
1065
- mcp_tools: $tools_status,
1066
- mcp_smoke: $smoke_status
1067
- }
1068
- }
1069
- ' --arg ts "$(_now)" \
1070
- --arg connect_status "$connect_status" \
1071
- --arg doctor_status "$doctor_status" \
1072
- --arg tools_status "$tools_status" \
1073
- --arg smoke_status "$smoke_status"
973
+ state_set_object runtime_checks.summary \
974
+ status=passed \
975
+ "ts=$(_now)" \
976
+ failed_count=0 \
977
+ "evidence=all runtime checks passed" \
978
+ "checks.connect_daemon=$connect_status" \
979
+ "checks.mcp_doctor=$doctor_status" \
980
+ "checks.mcp_tools=$tools_status" \
981
+ "checks.mcp_smoke=$smoke_status"
1074
982
  echo "verified runtime checks: passed"
1075
983
  return 0
1076
984
  fi
1077
985
 
1078
- _state_write '
1079
- .runtime_checks.summary = {
1080
- status: "failed",
1081
- ts: $ts,
1082
- failed_count: ($failed_count | tonumber),
1083
- evidence: "one or more runtime checks failed",
1084
- checks: {
1085
- connect_daemon: $connect_status,
1086
- mcp_doctor: $doctor_status,
1087
- mcp_tools: $tools_status,
1088
- mcp_smoke: $smoke_status
1089
- }
1090
- }
1091
- ' --arg ts "$(_now)" \
1092
- --arg failed_count "$failed_count" \
1093
- --arg connect_status "$connect_status" \
1094
- --arg doctor_status "$doctor_status" \
1095
- --arg tools_status "$tools_status" \
1096
- --arg smoke_status "$smoke_status"
986
+ state_set_object runtime_checks.summary \
987
+ status=failed \
988
+ "ts=$(_now)" \
989
+ "failed_count=$failed_count" \
990
+ "evidence=one or more runtime checks failed" \
991
+ "checks.connect_daemon=$connect_status" \
992
+ "checks.mcp_doctor=$doctor_status" \
993
+ "checks.mcp_tools=$tools_status" \
994
+ "checks.mcp_smoke=$smoke_status"
1097
995
  warn "runtime checks failed: $failed_count"
1098
996
  return "${rc:-1}"
1099
997
  }
@@ -253,12 +253,7 @@ _route53_existing_a_value() {
253
253
  local zone_id=$1 domain=$2 records name
254
254
  name="${domain}."
255
255
  records=$(aws route53 list-resource-record-sets --hosted-zone-id "$zone_id" --output json 2>/dev/null) || return 0
256
- printf '%s\n' "$records" | jq -r --arg name "$name" '
257
- .ResourceRecordSets[]?
258
- | select(.Name == $name and .Type == "A")
259
- | [.ResourceRecords[]?.Value]
260
- | join(",")
261
- ' | sed -n '1p'
256
+ printf '%s\n' "$records" | json_stdin_route53_a_values "$name" | sed -n '1p'
262
257
  }
263
258
 
264
259
  _guard_route53_a_overwrite() {
@@ -342,7 +337,7 @@ _find_route53_zone() {
342
337
  fi
343
338
  ;;
344
339
  esac
345
- done < <(printf '%s\n' "$zones_json" | jq -r '.HostedZones[] | [.Id, .Name] | @tsv')
340
+ done < <(printf '%s\n' "$zones_json" | json_stdin_tsv HostedZones Id Name)
346
341
  [ -n "$best_id" ] || return 1
347
342
  printf '%s\t%s\n' "$best_id" "$best_name"
348
343
  }
@@ -355,9 +350,9 @@ _create_route53_zone() {
355
350
  --name "$zone_name" \
356
351
  --caller-reference "$caller" \
357
352
  --output json) || return 1
358
- zone_id=$(printf '%s\n' "$created" | jq -r '.HostedZone.Id // empty' | sed 's#^/hostedzone/##')
359
- returned_name=$(printf '%s\n' "$created" | jq -r '.HostedZone.Name // empty')
360
- name_servers=$(printf '%s\n' "$created" | jq -r '(.DelegationSet.NameServers // []) | join(",")')
353
+ zone_id=$(printf '%s\n' "$created" | json_stdin_get HostedZone.Id | sed 's#^/hostedzone/##')
354
+ returned_name=$(printf '%s\n' "$created" | json_stdin_get HostedZone.Name)
355
+ name_servers=$(printf '%s\n' "$created" | json_stdin_join DelegationSet.NameServers ",")
361
356
  [ -n "$zone_id" ] && [ -n "$returned_name" ] || return 1
362
357
 
363
358
  _record_route53_zone "$zone_id" "${returned_name%.}" true "$name_servers"