fix(detmir): stabilize grafana freshness guard
This commit is contained in:
@@ -19,6 +19,7 @@ const REQUIRED_MEASUREMENTS: &[&str] = &[
|
|||||||
"aw_rdp_worktime_daily",
|
"aw_rdp_worktime_daily",
|
||||||
"aw_rdp_worktime_summary_daily",
|
"aw_rdp_worktime_summary_daily",
|
||||||
"aw_true_active_app_daily",
|
"aw_true_active_app_daily",
|
||||||
|
"aw_worktime_exporter_heartbeat",
|
||||||
];
|
];
|
||||||
|
|
||||||
#[derive(Debug, Parser)]
|
#[derive(Debug, Parser)]
|
||||||
|
|||||||
@@ -34,12 +34,12 @@ fn run() -> Result<()> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn quality_gate(root: &Path) -> Result<()> {
|
fn quality_gate(root: &Path) -> Result<()> {
|
||||||
println!("[1/5] Bash syntax check");
|
println!("[1/6] Bash syntax check");
|
||||||
for file in collect_by_extension(root, &["aw-server", "proxmox", "scripts"], "sh")? {
|
for file in collect_by_extension(root, &["aw-server", "proxmox", "scripts"], "sh")? {
|
||||||
run_status(Command::new("bash").arg("-n").arg(&file), false)?;
|
run_status(Command::new("bash").arg("-n").arg(&file), false)?;
|
||||||
}
|
}
|
||||||
|
|
||||||
println!("[2/5] Shellcheck (if available)");
|
println!("[2/6] Shellcheck (if available)");
|
||||||
if command_exists("shellcheck") {
|
if command_exists("shellcheck") {
|
||||||
let mut files = collect_by_extension(root, &["aw-server", "proxmox"], "sh")?;
|
let mut files = collect_by_extension(root, &["aw-server", "proxmox"], "sh")?;
|
||||||
files.push(root.join("scripts/aw-webui-browser-smoke.sh"));
|
files.push(root.join("scripts/aw-webui-browser-smoke.sh"));
|
||||||
@@ -53,7 +53,7 @@ fn quality_gate(root: &Path) -> Result<()> {
|
|||||||
println!("shellcheck not found, skipping.");
|
println!("shellcheck not found, skipping.");
|
||||||
}
|
}
|
||||||
|
|
||||||
println!("[3/5] Node syntax check (if node available)");
|
println!("[3/6] Node syntax check (if node available)");
|
||||||
if command_exists("node") {
|
if command_exists("node") {
|
||||||
run_status(
|
run_status(
|
||||||
Command::new("node")
|
Command::new("node")
|
||||||
@@ -66,7 +66,7 @@ fn quality_gate(root: &Path) -> Result<()> {
|
|||||||
println!("node not found, skipping.");
|
println!("node not found, skipping.");
|
||||||
}
|
}
|
||||||
|
|
||||||
println!("[4/5] PowerShell parse check (if pwsh available)");
|
println!("[4/6] PowerShell parse check (if pwsh available)");
|
||||||
if command_exists("pwsh") {
|
if command_exists("pwsh") {
|
||||||
let ps = r#"
|
let ps = r#"
|
||||||
$ErrorActionPreference = "Stop"
|
$ErrorActionPreference = "Stop"
|
||||||
@@ -107,7 +107,7 @@ foreach ($path in @("windows/ActivityWatch.Windows.Common.psm1", "windows/Activi
|
|||||||
println!("pwsh not found, skipping.");
|
println!("pwsh not found, skipping.");
|
||||||
}
|
}
|
||||||
|
|
||||||
println!("[5/5] Ansible syntax check (if ansible-playbook available)");
|
println!("[5/6] Ansible syntax check (if ansible-playbook available)");
|
||||||
if command_exists("ansible-playbook") {
|
if command_exists("ansible-playbook") {
|
||||||
for playbook in collect_top_level_yml(&root.join("ansible"))? {
|
for playbook in collect_top_level_yml(&root.join("ansible"))? {
|
||||||
run_status(
|
run_status(
|
||||||
@@ -124,6 +124,9 @@ foreach ($path in @("windows/ActivityWatch.Windows.Common.psm1", "windows/Activi
|
|||||||
println!("ansible-playbook not found, skipping.");
|
println!("ansible-playbook not found, skipping.");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
println!("[6/6] DetMir Python runtime retirement guard");
|
||||||
|
python_runtime_guard(root)?;
|
||||||
|
|
||||||
println!("quality-gate: OK");
|
println!("quality-gate: OK");
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
@@ -200,13 +203,107 @@ fn collect_top_level_yml(dir: &Path) -> Result<Vec<PathBuf>> {
|
|||||||
Ok(out)
|
Ok(out)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn python_runtime_guard(root: &Path) -> Result<()> {
|
||||||
|
let mut violations = Vec::new();
|
||||||
|
for rel in tracked_files(root)? {
|
||||||
|
if is_allowed_python_runtime_path(&rel) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if rel.ends_with(".py") && is_detmir_retired_runtime_path(&rel) {
|
||||||
|
violations.push(rel);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if !violations.is_empty() {
|
||||||
|
violations.sort();
|
||||||
|
bail!(
|
||||||
|
"Python runtime regression in Rust-retired DetMir paths:\n{}",
|
||||||
|
violations.join("\n")
|
||||||
|
);
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn tracked_files(root: &Path) -> Result<Vec<String>> {
|
||||||
|
if command_exists("git") {
|
||||||
|
let output = Command::new("git")
|
||||||
|
.arg("ls-files")
|
||||||
|
.current_dir(root)
|
||||||
|
.output()
|
||||||
|
.context("run git ls-files")?;
|
||||||
|
if output.status.success() {
|
||||||
|
return Ok(String::from_utf8_lossy(&output.stdout)
|
||||||
|
.lines()
|
||||||
|
.map(str::trim)
|
||||||
|
.filter(|line| !line.is_empty())
|
||||||
|
.map(ToOwned::to_owned)
|
||||||
|
.collect());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut out = Vec::new();
|
||||||
|
collect_tracked_fallback(root, root, &mut out)?;
|
||||||
|
out.sort();
|
||||||
|
Ok(out)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn collect_tracked_fallback(root: &Path, path: &Path, out: &mut Vec<String>) -> Result<()> {
|
||||||
|
for entry in fs::read_dir(path).with_context(|| format!("read dir {}", path.display()))? {
|
||||||
|
let entry = entry.with_context(|| format!("read dir entry {}", path.display()))?;
|
||||||
|
let entry_path = entry.path();
|
||||||
|
let file_type = entry
|
||||||
|
.file_type()
|
||||||
|
.with_context(|| format!("read file type {}", entry_path.display()))?;
|
||||||
|
let name = entry.file_name();
|
||||||
|
let name = name.to_string_lossy();
|
||||||
|
if file_type.is_dir() {
|
||||||
|
if matches!(
|
||||||
|
name.as_ref(),
|
||||||
|
".git" | "target" | "node_modules" | ".venv" | ".ops" | ".playwright-cli"
|
||||||
|
) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
collect_tracked_fallback(root, &entry_path, out)?;
|
||||||
|
} else if file_type.is_file() {
|
||||||
|
let rel = entry_path
|
||||||
|
.strip_prefix(root)
|
||||||
|
.with_context(|| format!("strip root from {}", entry_path.display()))?
|
||||||
|
.to_string_lossy()
|
||||||
|
.replace('\\', "/");
|
||||||
|
out.push(rel);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn is_allowed_python_runtime_path(rel: &str) -> bool {
|
||||||
|
rel.starts_with("aw-server/dlp-content-analysis/")
|
||||||
|
|| rel.starts_with("clickhouse-1c/ai/")
|
||||||
|
|| rel.starts_with("clickhouse-1c/etl/")
|
||||||
|
|| rel == "detmir-mcp/main.py"
|
||||||
|
|| rel.starts_with("grafana-1c/")
|
||||||
|
|| rel.starts_with("pfsense/")
|
||||||
|
|| rel == "proxmox/tsj_guardian_bot.py"
|
||||||
|
|| rel == "proxmox/test_tsj_guardian_bot.py"
|
||||||
|
}
|
||||||
|
|
||||||
|
fn is_detmir_retired_runtime_path(rel: &str) -> bool {
|
||||||
|
rel.starts_with("aw-server/")
|
||||||
|
|| rel.starts_with("proxmox/")
|
||||||
|
|| rel.starts_with("scripts/")
|
||||||
|
|| rel.starts_with("ansible/")
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use std::fs;
|
use std::fs;
|
||||||
|
|
||||||
use tempfile::tempdir;
|
use tempfile::tempdir;
|
||||||
|
|
||||||
use super::{collect_by_extension, collect_top_level_yml};
|
use super::{
|
||||||
|
collect_by_extension, collect_top_level_yml, is_allowed_python_runtime_path,
|
||||||
|
is_detmir_retired_runtime_path,
|
||||||
|
};
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn collects_recursive_shell_files_sorted() {
|
fn collects_recursive_shell_files_sorted() {
|
||||||
@@ -233,4 +330,31 @@ mod tests {
|
|||||||
assert_eq!(files.len(), 1);
|
assert_eq!(files.len(), 1);
|
||||||
assert_eq!(files[0].file_name().unwrap(), "a.yml");
|
assert_eq!(files[0].file_name().unwrap(), "a.yml");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn allows_agreed_python_runtime_exceptions() {
|
||||||
|
assert!(is_allowed_python_runtime_path(
|
||||||
|
"aw-server/dlp-content-analysis/content_analyzer.py"
|
||||||
|
));
|
||||||
|
assert!(is_allowed_python_runtime_path(
|
||||||
|
"proxmox/tsj_guardian_bot.py"
|
||||||
|
));
|
||||||
|
assert!(is_allowed_python_runtime_path("clickhouse-1c/etl/load.py"));
|
||||||
|
assert!(is_allowed_python_runtime_path("detmir-mcp/main.py"));
|
||||||
|
assert!(is_allowed_python_runtime_path(
|
||||||
|
"pfsense/pfsense-aw-poller.py"
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn flags_retired_detmir_python_runtime_paths() {
|
||||||
|
assert!(is_detmir_retired_runtime_path(
|
||||||
|
"aw-server/dlp-policy-engine/server.py"
|
||||||
|
));
|
||||||
|
assert!(is_detmir_retired_runtime_path("scripts/dlp-admin-cli.py"));
|
||||||
|
assert!(!is_allowed_python_runtime_path(
|
||||||
|
"aw-server/dlp-policy-engine/server.py"
|
||||||
|
));
|
||||||
|
assert!(!is_allowed_python_runtime_path("scripts/dlp-admin-cli.py"));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -889,6 +889,14 @@ fn run(cli: &Cli) -> Result<RunSummary> {
|
|||||||
for day in &config.days {
|
for day in &config.days {
|
||||||
lines.extend(build_lines_for_day(&client, &config, host, day)?);
|
lines.extend(build_lines_for_day(&client, &config, host, day)?);
|
||||||
}
|
}
|
||||||
|
if let Some(line) = line(
|
||||||
|
"aw_worktime_exporter_heartbeat",
|
||||||
|
vec![("host", host.to_string())],
|
||||||
|
vec![("run", FieldValue::Int(1))],
|
||||||
|
timestamp_ns(utc_now()),
|
||||||
|
) {
|
||||||
|
lines.push(line);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
let written = if cli.dry_run {
|
let written = if cli.dry_run {
|
||||||
0
|
0
|
||||||
@@ -981,6 +989,21 @@ mod tests {
|
|||||||
assert_eq!(out, "m,host=A\\ B\\,C\\=D text=\"a\\\"b\" 10");
|
assert_eq!(out, "m,host=A\\ B\\,C\\=D text=\"a\\\"b\" 10");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn heartbeat_line_uses_current_exporter_timestamp() {
|
||||||
|
let out = line(
|
||||||
|
"aw_worktime_exporter_heartbeat",
|
||||||
|
vec![("host", "SHARKON2025".to_string())],
|
||||||
|
vec![("run", FieldValue::Int(1))],
|
||||||
|
42,
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
assert_eq!(
|
||||||
|
out,
|
||||||
|
"aw_worktime_exporter_heartbeat,host=SHARKON2025 run=1i 42"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn aggregates_daily_and_hourly_active_samples() {
|
fn aggregates_daily_and_hourly_active_samples() {
|
||||||
let config = test_config();
|
let config = test_config();
|
||||||
|
|||||||
@@ -12,6 +12,8 @@
|
|||||||
grafana_check_user: "{{ detmir_grafana_check_user | default(lookup('env', 'DETMIR_GRAFANA_USER') | default(lookup('env', 'GRAFANA_USER'), true), true) }}"
|
grafana_check_user: "{{ detmir_grafana_check_user | default(lookup('env', 'DETMIR_GRAFANA_USER') | default(lookup('env', 'GRAFANA_USER'), true), true) }}"
|
||||||
grafana_check_password: "{{ detmir_grafana_check_password | default(lookup('env', 'DETMIR_GRAFANA_PASSWORD') | default(lookup('env', 'GRAFANA_PASSWORD'), true), true) }}"
|
grafana_check_password: "{{ detmir_grafana_check_password | default(lookup('env', 'DETMIR_GRAFANA_PASSWORD') | default(lookup('env', 'GRAFANA_PASSWORD'), true), true) }}"
|
||||||
grafana_check_dashboard_uid: "{{ detmir_grafana_check_dashboard_uid | default('detmir-aw-main') }}"
|
grafana_check_dashboard_uid: "{{ detmir_grafana_check_dashboard_uid | default('detmir-aw-main') }}"
|
||||||
|
grafana_check_dashboard_file_src: "{{ aw_repo_root }}/grafana/detmir-aw-main-dashboard.json"
|
||||||
|
grafana_check_dashboard_file_dest: /etc/grafana/provisioning/dashboards/aw/detmir-aw-main.json
|
||||||
grafana_check_host: "{{ detmir_grafana_check_host | default('SHARKON2025') }}"
|
grafana_check_host: "{{ detmir_grafana_check_host | default('SHARKON2025') }}"
|
||||||
grafana_check_max_freshness_minutes: "{{ detmir_grafana_check_max_freshness_minutes | default(360) }}"
|
grafana_check_max_freshness_minutes: "{{ detmir_grafana_check_max_freshness_minutes | default(360) }}"
|
||||||
grafana_check_on_calendar: "{{ detmir_grafana_check_on_calendar | default('*:0/15') }}"
|
grafana_check_on_calendar: "{{ detmir_grafana_check_on_calendar | default('*:0/15') }}"
|
||||||
@@ -37,6 +39,18 @@
|
|||||||
msg: "Missing {{ aw_rust_release_dir }}/detmir-grafana-check. Build with cargo build --release -p detmir-grafana-check."
|
msg: "Missing {{ aw_rust_release_dir }}/detmir-grafana-check. Build with cargo build --release -p detmir-grafana-check."
|
||||||
when: not (grafana_check_binary.stat.exists | default(false))
|
when: not (grafana_check_binary.stat.exists | default(false))
|
||||||
|
|
||||||
|
- name: Check local DetMir Grafana dashboard JSON
|
||||||
|
ansible.builtin.stat:
|
||||||
|
path: "{{ grafana_check_dashboard_file_src }}"
|
||||||
|
delegate_to: localhost
|
||||||
|
become: false
|
||||||
|
register: grafana_check_dashboard
|
||||||
|
|
||||||
|
- name: Fail when DetMir Grafana dashboard JSON is absent
|
||||||
|
ansible.builtin.fail:
|
||||||
|
msg: "Missing {{ grafana_check_dashboard_file_src }}."
|
||||||
|
when: not (grafana_check_dashboard.stat.exists | default(false))
|
||||||
|
|
||||||
- name: Copy detmir-grafana-check binary to Proxmox staging
|
- name: Copy detmir-grafana-check binary to Proxmox staging
|
||||||
ansible.builtin.copy:
|
ansible.builtin.copy:
|
||||||
src: "{{ aw_rust_release_dir }}/detmir-grafana-check"
|
src: "{{ aw_rust_release_dir }}/detmir-grafana-check"
|
||||||
@@ -67,6 +81,38 @@
|
|||||||
- /usr/local/bin/detmir-grafana-check
|
- /usr/local/bin/detmir-grafana-check
|
||||||
changed_when: true
|
changed_when: true
|
||||||
|
|
||||||
|
- name: Copy DetMir Grafana dashboard JSON to Proxmox staging
|
||||||
|
ansible.builtin.copy:
|
||||||
|
src: "{{ grafana_check_dashboard_file_src }}"
|
||||||
|
dest: /tmp/detmir-aw-main-dashboard.json
|
||||||
|
owner: root
|
||||||
|
group: root
|
||||||
|
mode: "0644"
|
||||||
|
|
||||||
|
- name: Ensure Grafana provisioning dashboard directory exists in CT
|
||||||
|
ansible.builtin.command:
|
||||||
|
argv:
|
||||||
|
- pct
|
||||||
|
- exec
|
||||||
|
- "{{ grafana_check_ct_id }}"
|
||||||
|
- --
|
||||||
|
- install
|
||||||
|
- "-d"
|
||||||
|
- "-m"
|
||||||
|
- "0755"
|
||||||
|
- "{{ grafana_check_dashboard_file_dest | dirname }}"
|
||||||
|
changed_when: true
|
||||||
|
|
||||||
|
- name: Install DetMir Grafana dashboard JSON into CT provisioning
|
||||||
|
ansible.builtin.command:
|
||||||
|
argv:
|
||||||
|
- pct
|
||||||
|
- push
|
||||||
|
- "{{ grafana_check_ct_id }}"
|
||||||
|
- /tmp/detmir-aw-main-dashboard.json
|
||||||
|
- "{{ grafana_check_dashboard_file_dest }}"
|
||||||
|
changed_when: true
|
||||||
|
|
||||||
- name: Create Grafana check runtime in CT
|
- name: Create Grafana check runtime in CT
|
||||||
ansible.builtin.command:
|
ansible.builtin.command:
|
||||||
argv:
|
argv:
|
||||||
@@ -89,7 +135,7 @@
|
|||||||
DETMIR_GRAFANA_USER={{ grafana_check_user }}
|
DETMIR_GRAFANA_USER={{ grafana_check_user }}
|
||||||
DETMIR_GRAFANA_PASSWORD={{ grafana_check_password }}
|
DETMIR_GRAFANA_PASSWORD={{ grafana_check_password }}
|
||||||
DETMIR_GRAFANA_DASHBOARD_UID={{ grafana_check_dashboard_uid }}
|
DETMIR_GRAFANA_DASHBOARD_UID={{ grafana_check_dashboard_uid }}
|
||||||
DETMIR_GRAFANA_DASHBOARD_FILE=/etc/grafana/provisioning/dashboards/aw/detmir-aw-main.json
|
DETMIR_GRAFANA_DASHBOARD_FILE={{ grafana_check_dashboard_file_dest }}
|
||||||
DETMIR_GRAFANA_HOST={{ grafana_check_host }}
|
DETMIR_GRAFANA_HOST={{ grafana_check_host }}
|
||||||
DETMIR_GRAFANA_MAX_FRESHNESS_MINUTES={{ grafana_check_max_freshness_minutes }}
|
DETMIR_GRAFANA_MAX_FRESHNESS_MINUTES={{ grafana_check_max_freshness_minutes }}
|
||||||
DETMIR_GRAFANA_OUTPUT_JSON=/var/lib/detmir-grafana-check/latest.json
|
DETMIR_GRAFANA_OUTPUT_JSON=/var/lib/detmir-grafana-check/latest.json
|
||||||
|
|||||||
@@ -120,7 +120,7 @@ extract_zip_normalized() {
|
|||||||
while IFS= read -r entry; do
|
while IFS= read -r entry; do
|
||||||
normalized="${entry//\\//}"
|
normalized="${entry//\\//}"
|
||||||
case "${normalized}" in
|
case "${normalized}" in
|
||||||
""|.|/*|*"/../"*|../*|*".."|*"/..")
|
""|.|/*|*"/../"*|../*|*"..")
|
||||||
fail "unsafe zip entry: ${entry}"
|
fail "unsafe zip entry: ${entry}"
|
||||||
;;
|
;;
|
||||||
esac
|
esac
|
||||||
|
|||||||
@@ -27,6 +27,17 @@
|
|||||||
|
|
||||||
## Проверка
|
## Проверка
|
||||||
|
|
||||||
|
Основной guard встроен в `quality-gate`:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
scripts/quality-gate.sh
|
||||||
|
```
|
||||||
|
|
||||||
|
Он проверяет tracked-файлы и блокирует возврат `.py` entrypoints в Rust-retired
|
||||||
|
runtime paths (`aw-server`, `proxmox`, `scripts`, `ansible`) за исключением
|
||||||
|
согласованных зон: Telegram bot, OCR/content-analysis, 1C/AI/ETL, MCP,
|
||||||
|
pfSense/no-touch и Grafana-1C.
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
rg -n '\.py\b|python3' ansible aw-server scripts adk-rust \
|
rg -n '\.py\b|python3' ansible aw-server scripts adk-rust \
|
||||||
--glob '!adk-rust/target/**'
|
--glob '!adk-rust/target/**'
|
||||||
|
|||||||
@@ -45,7 +45,7 @@
|
|||||||
"id": 5,
|
"id": 5,
|
||||||
"targets": [
|
"targets": [
|
||||||
{
|
{
|
||||||
"query": "from(bucket: \"aw_metrics\")\n |> range(start: -24h)\n |> filter(fn: (r) => (r._measurement == \"aw_rdp_worktime_hourly\" or r._measurement == \"aw_rdp_worktime_daily\" or r._measurement == \"aw_true_active_app_daily\") and (r._field == \"active_seconds\" or r._field == \"proved_work_seconds\"))\n |> last()\n |> group()\n |> sort(columns: [\"_time\"], desc: true)\n |> limit(n: 1)\n |> map(fn: (r) => ({ r with _value: (float(v: uint(v: now()) - uint(v: r._time)) / 1000000000.0) / 60.0 }))\n |> set(key: \"_field\", value: \"Свежесть\")\n |> keep(columns: [\"_value\", \"_field\"])\n |> yield(name: \"freshness_minutes\")\n",
|
"query": "from(bucket: \"aw_metrics\")\n |> range(start: -24h)\n |> filter(fn: (r) => r._measurement == \"aw_worktime_exporter_heartbeat\" and r._field == \"run\")\n |> last()\n |> group()\n |> sort(columns: [\"_time\"], desc: true)\n |> limit(n: 1)\n |> map(fn: (r) => ({ r with _value: (float(v: uint(v: now()) - uint(v: r._time)) / 1000000000.0) / 60.0 }))\n |> set(key: \"_field\", value: \"Свежесть\")\n |> keep(columns: [\"_value\", \"_field\"])\n |> yield(name: \"freshness_minutes\")\n",
|
||||||
"refId": "A"
|
"refId": "A"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
|
|||||||
Binary file not shown.
Binary file not shown.
@@ -35,7 +35,7 @@ aedffecfa24834968742cb2477faef80bf794345275a9679ac12c5a1f609acc2 install-kit-aw
|
|||||||
4e5b23300ba5c9878b2c8ce6eea65043b7a4c5cb2d14619243cfca6d202aec9f install-kit-awindows-20260427-211240/scripts/aw-webui-browser-smoke.mjs
|
4e5b23300ba5c9878b2c8ce6eea65043b7a4c5cb2d14619243cfca6d202aec9f install-kit-awindows-20260427-211240/scripts/aw-webui-browser-smoke.mjs
|
||||||
62980eeebe01d7da243be8535a48f5d41d4eead83cf94015df807111c3d920ab install-kit-awindows-20260427-211240/scripts/aw-webui-browser-smoke.sh
|
62980eeebe01d7da243be8535a48f5d41d4eead83cf94015df807111c3d920ab install-kit-awindows-20260427-211240/scripts/aw-webui-browser-smoke.sh
|
||||||
6770275fac17607770653a522a64cf5ab31a7a11993b68e7e80a2858c3b8930e install-kit-awindows-20260427-211240/scripts/check_install_kit_vs_repo.sh
|
6770275fac17607770653a522a64cf5ab31a7a11993b68e7e80a2858c3b8930e install-kit-awindows-20260427-211240/scripts/check_install_kit_vs_repo.sh
|
||||||
fe8d2b846a64f1357dda48b4c817c7919a3c7398921ddca2adf9a99711ac2613 install-kit-awindows-20260427-211240/scripts/quality-gate.sh
|
4e5bc6ce977e9f5b9e87c393061d5ecac0f90d5e1314e3325093692e5d19965e install-kit-awindows-20260427-211240/scripts/quality-gate.sh
|
||||||
db1a9b0ccd21aec78d8e2aa08dfa28368e5ba6209c57361e6824e85c1af9f6ca install-kit-awindows-20260427-211240/scripts/rebuild_install_kit.sh
|
db1a9b0ccd21aec78d8e2aa08dfa28368e5ba6209c57361e6824e85c1af9f6ca install-kit-awindows-20260427-211240/scripts/rebuild_install_kit.sh
|
||||||
38ad2e112796c2fa9bdc1cc2403c679bb896f90ae2b51d6ccc9a9ec103b4dea6 install-kit-awindows-20260427-211240/scripts/validate_install_kit.sh
|
38ad2e112796c2fa9bdc1cc2403c679bb896f90ae2b51d6ccc9a9ec103b4dea6 install-kit-awindows-20260427-211240/scripts/validate_install_kit.sh
|
||||||
0b49e6db51d5abcecaeee0b85f186efd4f10d3360cf04e8aea2e4c7d4465dbda install-kit-awindows-20260427-211240/scripts/verify_innosetup_installer.sh
|
0b49e6db51d5abcecaeee0b85f186efd4f10d3360cf04e8aea2e4c7d4465dbda install-kit-awindows-20260427-211240/scripts/verify_innosetup_installer.sh
|
||||||
|
|||||||
@@ -23,10 +23,10 @@ for candidate in "${rust_candidates[@]}"; do
|
|||||||
fi
|
fi
|
||||||
done
|
done
|
||||||
|
|
||||||
echo "[1/5] Bash syntax check"
|
echo "[1/6] Bash syntax check"
|
||||||
find aw-server proxmox scripts -type f -name "*.sh" -print0 | xargs -0 -r -n1 bash -n
|
find aw-server proxmox scripts -type f -name "*.sh" -print0 | xargs -0 -r -n1 bash -n
|
||||||
|
|
||||||
echo "[2/5] Shellcheck (if available)"
|
echo "[2/6] Shellcheck (if available)"
|
||||||
if command -v shellcheck >/dev/null 2>&1; then
|
if command -v shellcheck >/dev/null 2>&1; then
|
||||||
{
|
{
|
||||||
find aw-server proxmox -type f -name "*.sh"
|
find aw-server proxmox -type f -name "*.sh"
|
||||||
@@ -36,14 +36,14 @@ else
|
|||||||
echo "shellcheck not found, skipping."
|
echo "shellcheck not found, skipping."
|
||||||
fi
|
fi
|
||||||
|
|
||||||
echo "[3/5] Node syntax check (if node available)"
|
echo "[3/6] Node syntax check (if node available)"
|
||||||
if command -v node >/dev/null 2>&1; then
|
if command -v node >/dev/null 2>&1; then
|
||||||
node --check scripts/aw-webui-browser-smoke.mjs >/dev/null
|
node --check scripts/aw-webui-browser-smoke.mjs >/dev/null
|
||||||
else
|
else
|
||||||
echo "node not found, skipping."
|
echo "node not found, skipping."
|
||||||
fi
|
fi
|
||||||
|
|
||||||
echo "[4/5] PowerShell parse check (if pwsh available)"
|
echo "[4/6] PowerShell parse check (if pwsh available)"
|
||||||
if command -v pwsh >/dev/null 2>&1; then
|
if command -v pwsh >/dev/null 2>&1; then
|
||||||
pwsh -NoLogo -NoProfile -Command '
|
pwsh -NoLogo -NoProfile -Command '
|
||||||
$ErrorActionPreference = "Stop"
|
$ErrorActionPreference = "Stop"
|
||||||
@@ -60,7 +60,7 @@ fi
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
echo "[5/5] Ansible syntax check (if ansible-playbook available)"
|
echo "[5/6] Ansible syntax check (if ansible-playbook available)"
|
||||||
if command -v ansible-playbook >/dev/null 2>&1; then
|
if command -v ansible-playbook >/dev/null 2>&1; then
|
||||||
for playbook in ansible/*.yml; do
|
for playbook in ansible/*.yml; do
|
||||||
ansible-playbook --syntax-check "$playbook" -i ansible/inventory.example.ini >/dev/null
|
ansible-playbook --syntax-check "$playbook" -i ansible/inventory.example.ini >/dev/null
|
||||||
@@ -69,4 +69,29 @@ else
|
|||||||
echo "ansible-playbook not found, skipping."
|
echo "ansible-playbook not found, skipping."
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
echo "[6/6] DetMir Python runtime retirement guard"
|
||||||
|
if command -v git >/dev/null 2>&1; then
|
||||||
|
mapfile -t tracked_py < <(git ls-files '*.py')
|
||||||
|
else
|
||||||
|
mapfile -t tracked_py < <(find aw-server proxmox scripts ansible -type f -name '*.py' 2>/dev/null)
|
||||||
|
fi
|
||||||
|
violations=()
|
||||||
|
for path in "${tracked_py[@]}"; do
|
||||||
|
case "$path" in
|
||||||
|
aw-server/dlp-content-analysis/*|clickhouse-1c/ai/*|clickhouse-1c/etl/*|detmir-mcp/main.py|grafana-1c/*|pfsense/*|proxmox/tsj_guardian_bot.py|proxmox/test_tsj_guardian_bot.py)
|
||||||
|
continue
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
case "$path" in
|
||||||
|
aw-server/*|proxmox/*|scripts/*|ansible/*)
|
||||||
|
violations+=("$path")
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
done
|
||||||
|
if (( ${#violations[@]} > 0 )); then
|
||||||
|
printf 'Python runtime regression in Rust-retired DetMir paths:\\n' >&2
|
||||||
|
printf '%s\\n' "${violations[@]}" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
echo "quality-gate: OK"
|
echo "quality-gate: OK"
|
||||||
|
|||||||
+30
-5
@@ -23,10 +23,10 @@ for candidate in "${rust_candidates[@]}"; do
|
|||||||
fi
|
fi
|
||||||
done
|
done
|
||||||
|
|
||||||
echo "[1/5] Bash syntax check"
|
echo "[1/6] Bash syntax check"
|
||||||
find aw-server proxmox scripts -type f -name "*.sh" -print0 | xargs -0 -r -n1 bash -n
|
find aw-server proxmox scripts -type f -name "*.sh" -print0 | xargs -0 -r -n1 bash -n
|
||||||
|
|
||||||
echo "[2/5] Shellcheck (if available)"
|
echo "[2/6] Shellcheck (if available)"
|
||||||
if command -v shellcheck >/dev/null 2>&1; then
|
if command -v shellcheck >/dev/null 2>&1; then
|
||||||
{
|
{
|
||||||
find aw-server proxmox -type f -name "*.sh"
|
find aw-server proxmox -type f -name "*.sh"
|
||||||
@@ -36,14 +36,14 @@ else
|
|||||||
echo "shellcheck not found, skipping."
|
echo "shellcheck not found, skipping."
|
||||||
fi
|
fi
|
||||||
|
|
||||||
echo "[3/5] Node syntax check (if node available)"
|
echo "[3/6] Node syntax check (if node available)"
|
||||||
if command -v node >/dev/null 2>&1; then
|
if command -v node >/dev/null 2>&1; then
|
||||||
node --check scripts/aw-webui-browser-smoke.mjs >/dev/null
|
node --check scripts/aw-webui-browser-smoke.mjs >/dev/null
|
||||||
else
|
else
|
||||||
echo "node not found, skipping."
|
echo "node not found, skipping."
|
||||||
fi
|
fi
|
||||||
|
|
||||||
echo "[4/5] PowerShell parse check (if pwsh available)"
|
echo "[4/6] PowerShell parse check (if pwsh available)"
|
||||||
if command -v pwsh >/dev/null 2>&1; then
|
if command -v pwsh >/dev/null 2>&1; then
|
||||||
pwsh -NoLogo -NoProfile -Command '
|
pwsh -NoLogo -NoProfile -Command '
|
||||||
$ErrorActionPreference = "Stop"
|
$ErrorActionPreference = "Stop"
|
||||||
@@ -60,7 +60,7 @@ fi
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
echo "[5/5] Ansible syntax check (if ansible-playbook available)"
|
echo "[5/6] Ansible syntax check (if ansible-playbook available)"
|
||||||
if command -v ansible-playbook >/dev/null 2>&1; then
|
if command -v ansible-playbook >/dev/null 2>&1; then
|
||||||
for playbook in ansible/*.yml; do
|
for playbook in ansible/*.yml; do
|
||||||
ansible-playbook --syntax-check "$playbook" -i ansible/inventory.example.ini >/dev/null
|
ansible-playbook --syntax-check "$playbook" -i ansible/inventory.example.ini >/dev/null
|
||||||
@@ -69,4 +69,29 @@ else
|
|||||||
echo "ansible-playbook not found, skipping."
|
echo "ansible-playbook not found, skipping."
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
echo "[6/6] DetMir Python runtime retirement guard"
|
||||||
|
if command -v git >/dev/null 2>&1; then
|
||||||
|
mapfile -t tracked_py < <(git ls-files '*.py')
|
||||||
|
else
|
||||||
|
mapfile -t tracked_py < <(find aw-server proxmox scripts ansible -type f -name '*.py' 2>/dev/null)
|
||||||
|
fi
|
||||||
|
violations=()
|
||||||
|
for path in "${tracked_py[@]}"; do
|
||||||
|
case "$path" in
|
||||||
|
aw-server/dlp-content-analysis/*|clickhouse-1c/ai/*|clickhouse-1c/etl/*|detmir-mcp/main.py|grafana-1c/*|pfsense/*|proxmox/tsj_guardian_bot.py|proxmox/test_tsj_guardian_bot.py)
|
||||||
|
continue
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
case "$path" in
|
||||||
|
aw-server/*|proxmox/*|scripts/*|ansible/*)
|
||||||
|
violations+=("$path")
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
done
|
||||||
|
if (( ${#violations[@]} > 0 )); then
|
||||||
|
printf 'Python runtime regression in Rust-retired DetMir paths:\\n' >&2
|
||||||
|
printf '%s\\n' "${violations[@]}" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
echo "quality-gate: OK"
|
echo "quality-gate: OK"
|
||||||
|
|||||||
Reference in New Issue
Block a user